From 55acbd4868bf6b9500fe84a59e8be0e9192a357e Mon Sep 17 00:00:00 2001 From: zontreck Date: Tue, 16 Jan 2024 17:25:12 -0700 Subject: [PATCH 01/21] Finish backporting libzontreck to 1.18.2 --- build.gradle | 8 +- gradle.properties | 16 ++-- gradle/wrapper/gradle-wrapper.properties | 2 +- settings.gradle | 6 +- .../dev/zontreck/libzontreck/LibZontreck.java | 2 - .../libzontreck/blocks/BlockImitation.java | 95 ------------------- .../zontreck/libzontreck/chat/HoverTip.java | 3 +- .../libzontreck/chestgui/ChestGUI.java | 3 +- .../libzontreck/currency/Account.java | 1 - .../events/ForgeEventHandlers.java | 10 +- .../libzontreck/items/CreativeModeTabs.java | 46 --------- .../libzontreck/menus/ChestGUIScreen.java | 13 +-- .../libzontreck/networking/ModMessages.java | 10 +- .../packets/S2CPlaySoundPacket.java | 3 +- .../libzontreck/types/ModMenuTypes.java | 2 +- .../libzontreck/util/ChatHelpers.java | 2 +- .../libzontreck/util/ServerUtilities.java | 2 +- .../libzontreck/util/heads/CreditsEntry.java | 3 +- .../libzontreck/vectors/WorldPosition.java | 2 +- src/main/resources/META-INF/mods.toml | 70 +++++++------- src/main/resources/pack.mcmeta | 8 +- 21 files changed, 80 insertions(+), 227 deletions(-) delete mode 100644 src/main/java/dev/zontreck/libzontreck/blocks/BlockImitation.java delete mode 100644 src/main/java/dev/zontreck/libzontreck/items/CreativeModeTabs.java diff --git a/build.gradle b/build.gradle index 783d07c..d55b489 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ plugins { id 'idea' id 'maven-publish' id 'java-library' - id 'net.minecraftforge.gradle' version '[6.0,6.2)' + id 'net.minecraftforge.gradle' version '5.1.+' id 'org.parchmentmc.librarian.forgegradle' version '1.+' } @@ -53,7 +53,7 @@ minecraft { // This property allows configuring Gradle's ProcessResources task(s) to run on IDE output locations before launching the game. // It is REQUIRED to be set to true for this template to function. // See https://docs.gradle.org/current/dsl/org.gradle.language.jvm.tasks.ProcessResources.html - copyIdeResources = true + //copyIdeResources = true // When true, this property will add the folder name of all declared run configurations to generated IDE run configurations. // The folder name can be set on a run configuration using the "folderName" property. @@ -187,7 +187,7 @@ dependencies { // A missing property will result in an error. Properties are expanded using ${} Groovy notation. // When "copyIdeResources" is enabled, this will also run before the game launches in IDE environments. // See https://docs.gradle.org/current/dsl/org.gradle.language.jvm.tasks.ProcessResources.html -tasks.named('processResources', ProcessResources).configure { +/*tasks.named('processResources', ProcessResources).configure { var replaceProperties = [ minecraft_version : minecraft_version, minecraft_version_range: minecraft_version_range, forge_version : forge_version, forge_version_range: forge_version_range, @@ -200,7 +200,7 @@ tasks.named('processResources', ProcessResources).configure { filesMatching(['META-INF/mods.toml', 'pack.mcmeta']) { expand replaceProperties + [project: project] } -} +}*/ // Example for how to get properties into the manifest for reading at runtime. tasks.named('jar', Jar).configure { diff --git a/gradle.properties b/gradle.properties index 50bba12..45e2100 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ org.gradle.jvmargs=-Xmx3G org.gradle.daemon=false -parchment_version=2023.09.03 +parchment_version=2022.11.06 # luckperms_api_version=5.4 libac=1.4.18 @@ -12,17 +12,17 @@ eventsbus=1.0.31 ## Environment Properties # The Minecraft version must agree with the Forge version to get a valid artifact -minecraft_version=1.20.1 +minecraft_version=1.18.2 # The Minecraft version range can use any release version of Minecraft as bounds. # Snapshots, pre-releases, and release candidates are not guaranteed to sort properly # as they do not follow standard versioning conventions. -minecraft_version_range=[1.20.1,1.21) +minecraft_version_range=[1.18.2,1.19) # The Forge version must agree with the Minecraft version to get a valid artifact -forge_version=47.2.0 +forge_version=40.2.17 # The Forge version range can use any version of Forge as bounds or match the loader version range -forge_version_range=[47,) +forge_version_range=[40,) # The loader version range can only use the major version of Forge/FML as bounds -loader_version_range=[47,) +loader_version_range=[40,) # The mapping channel to use for mappings. # The default set of supported mapping channels are ["official", "snapshot", "snapshot_nodoc", "stable", "stable_nodoc"]. # Additional mapping channels can be registered through the "channelProviders" extension in a Gradle plugin. @@ -40,7 +40,7 @@ loader_version_range=[47,) mapping_channel=parchment # The mapping version to query from the mapping channel. # This must match the format required by the mapping channel. -mapping_version=2023.09.03-1.20.1 +mapping_version=2022.11.06-1.18.2 ## Mod Properties @@ -53,7 +53,7 @@ mod_name=Zontreck Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1.10.011524.0045 +mod_version=1.10.011624.1712 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index fae0804..ae04661 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/settings.gradle b/settings.gradle index 8a72e7b..7f4682f 100644 --- a/settings.gradle +++ b/settings.gradle @@ -3,8 +3,4 @@ pluginManagement { gradlePluginPortal() maven { url = "https://maven.zontreck.com/repository/internal" } } -} - -plugins { - id 'org.gradle.toolchains.foojay-resolver-convention' version '0.5.0' -} +} \ No newline at end of file diff --git a/src/main/java/dev/zontreck/libzontreck/LibZontreck.java b/src/main/java/dev/zontreck/libzontreck/LibZontreck.java index eafd174..bc5fe5e 100644 --- a/src/main/java/dev/zontreck/libzontreck/LibZontreck.java +++ b/src/main/java/dev/zontreck/libzontreck/LibZontreck.java @@ -13,13 +13,11 @@ import dev.zontreck.eventsbus.Bus; import dev.zontreck.libzontreck.chestgui.ChestGUIRegistry; import dev.zontreck.libzontreck.currency.Bank; import dev.zontreck.libzontreck.currency.CurrencyHelper; -import dev.zontreck.libzontreck.items.CreativeModeTabs; import dev.zontreck.libzontreck.items.ModItems; import dev.zontreck.libzontreck.menus.ChestGUIScreen; import dev.zontreck.libzontreck.types.ModMenuTypes; import dev.zontreck.libzontreck.networking.NetworkEvents; import net.minecraft.client.gui.screens.MenuScreens; -import net.minecraftforge.registries.RegisterEvent; import org.slf4j.Logger; import com.mojang.logging.LogUtils; diff --git a/src/main/java/dev/zontreck/libzontreck/blocks/BlockImitation.java b/src/main/java/dev/zontreck/libzontreck/blocks/BlockImitation.java deleted file mode 100644 index ab23bd1..0000000 --- a/src/main/java/dev/zontreck/libzontreck/blocks/BlockImitation.java +++ /dev/null @@ -1,95 +0,0 @@ -package dev.zontreck.libzontreck.blocks; - -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.util.RandomSource; -import net.minecraft.world.entity.Entity; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.BlockGetter; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.biome.Biome; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.state.BlockState; - -/** - * Code partially taken from HT's TreeChop - *

- * Source Material - */ -public abstract class BlockImitation extends Block -{ - public BlockImitation(Properties pProperties) { - super(pProperties); - } - - - public abstract BlockState getImitatedBlockState(BlockGetter level, BlockPos pos); - - public abstract void updateImitation(BlockState newState, BlockGetter level, BlockPos pos); - - @Override - public void animateTick(BlockState blockState, Level level, BlockPos pos, RandomSource random) { - BlockState imitatedBlockState = getImitatedBlockState(level, pos); - imitatedBlockState.getBlock().animateTick(imitatedBlockState, level, pos, random); - } - - @Override - public void stepOn(Level level, BlockPos pos, BlockState blockState, Entity entity) { - BlockState imitatedBlockState = getImitatedBlockState(level, pos); - imitatedBlockState.getBlock().stepOn(level, pos, imitatedBlockState, entity); - } - - @Override - public void fallOn(Level level, BlockState blockState, BlockPos pos, Entity entity, float speed) { - BlockState imitatedBlockState = getImitatedBlockState(level, pos); - imitatedBlockState.getBlock().fallOn(level, imitatedBlockState, pos, entity, speed); - } - - @Override - public ItemStack getCloneItemStack(BlockGetter level, BlockPos pos, BlockState blockState) { - BlockState imitatedBlockState = getImitatedBlockState(level, pos); - return imitatedBlockState.getBlock().getCloneItemStack(level, pos, imitatedBlockState); - } - - @Override - public void handlePrecipitation(BlockState blockState, Level level, BlockPos pos, Biome.Precipitation precipitation) { - BlockState imitatedBlockState = getImitatedBlockState(level, pos); - imitatedBlockState.getBlock().handlePrecipitation(imitatedBlockState, level, pos, precipitation); - } - - @Override - public int getLightBlock(BlockState blockState, BlockGetter level, BlockPos pos) { - return super.getLightBlock(blockState, level, pos); - } - - @Override - public float getShadeBrightness(BlockState blockState, BlockGetter level, BlockPos pos) { - return super.getShadeBrightness(blockState, level, pos); - } - - @Override - public int getAnalogOutputSignal(BlockState blockState, Level level, BlockPos pos) { - return getImitatedBlockState(level, pos).getAnalogOutputSignal(level, pos); - } - - @Override - public void randomTick(BlockState blockState, ServerLevel level, BlockPos pos, RandomSource random) { - getImitatedBlockState(level, pos).randomTick(level, pos, random); - } - - @Override - public void tick(BlockState blockState, ServerLevel level, BlockPos pos, RandomSource random) { - getImitatedBlockState(level, pos).tick(level, pos, random); - } - - @Override - public int getSignal(BlockState blockState, BlockGetter level, BlockPos pos, Direction direction) { - return getImitatedBlockState(level, pos).getSignal(level, pos, direction); - } - - @Override - public int getDirectSignal(BlockState blockState, BlockGetter level, BlockPos pos, Direction direction) { - return getImitatedBlockState(level, pos).getDirectSignal(level, pos, direction); - } -} diff --git a/src/main/java/dev/zontreck/libzontreck/chat/HoverTip.java b/src/main/java/dev/zontreck/libzontreck/chat/HoverTip.java index 2eafb13..083d974 100644 --- a/src/main/java/dev/zontreck/libzontreck/chat/HoverTip.java +++ b/src/main/java/dev/zontreck/libzontreck/chat/HoverTip.java @@ -1,5 +1,6 @@ package dev.zontreck.libzontreck.chat; +import dev.zontreck.libzontreck.util.ChatHelpers; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.HoverEvent; import net.minecraft.network.chat.HoverEvent.Action; @@ -17,7 +18,7 @@ public class HoverTip { */ public static HoverEvent get(String text) { - return new HoverEvent(Action.SHOW_TEXT, Component.literal(text)); + return new HoverEvent(Action.SHOW_TEXT, ChatHelpers.macro(text)); } /** diff --git a/src/main/java/dev/zontreck/libzontreck/chestgui/ChestGUI.java b/src/main/java/dev/zontreck/libzontreck/chestgui/ChestGUI.java index ae829ec..445281c 100644 --- a/src/main/java/dev/zontreck/libzontreck/chestgui/ChestGUI.java +++ b/src/main/java/dev/zontreck/libzontreck/chestgui/ChestGUI.java @@ -7,6 +7,7 @@ import dev.zontreck.libzontreck.items.ModItems; import dev.zontreck.libzontreck.menus.ChestGUIMenu; import dev.zontreck.libzontreck.networking.ModMessages; import dev.zontreck.libzontreck.networking.packets.S2CCloseChestGUI; +import dev.zontreck.libzontreck.util.ChatHelpers; import dev.zontreck.libzontreck.util.ServerUtilities; import dev.zontreck.libzontreck.vectors.Vector2; import dev.zontreck.libzontreck.vectors.Vector2i; @@ -198,7 +199,7 @@ public class ChestGUI { updateUtilityButtons(); MinecraftForge.EVENT_BUS.post(new OpenGUIEvent(id, player, this)); - NetworkHooks.openScreen(ServerUtilities.getPlayerByID(player.toString()), new SimpleMenuProvider(ChestGUIMenu.getServerMenu(this), Component.literal((MenuTitle != "") ? MenuTitle : "No Title"))); + NetworkHooks.openGui(ServerUtilities.getPlayerByID(player.toString()), new SimpleMenuProvider(ChestGUIMenu.getServerMenu(this), ChatHelpers.macro(((MenuTitle != "") ? MenuTitle : "No Title")))); } } diff --git a/src/main/java/dev/zontreck/libzontreck/currency/Account.java b/src/main/java/dev/zontreck/libzontreck/currency/Account.java index e0b81f8..bef02ff 100644 --- a/src/main/java/dev/zontreck/libzontreck/currency/Account.java +++ b/src/main/java/dev/zontreck/libzontreck/currency/Account.java @@ -6,7 +6,6 @@ import dev.zontreck.libzontreck.chat.ChatColor; import dev.zontreck.libzontreck.currency.events.TransactionHistoryFlushEvent; import dev.zontreck.libzontreck.profiles.Profile; import dev.zontreck.libzontreck.profiles.UserProfileNotYetExistsException; -import net.minecraft.core.UUIDUtil; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.nbt.NbtUtils; diff --git a/src/main/java/dev/zontreck/libzontreck/events/ForgeEventHandlers.java b/src/main/java/dev/zontreck/libzontreck/events/ForgeEventHandlers.java index c2813ec..22895f2 100644 --- a/src/main/java/dev/zontreck/libzontreck/events/ForgeEventHandlers.java +++ b/src/main/java/dev/zontreck/libzontreck/events/ForgeEventHandlers.java @@ -23,9 +23,9 @@ import net.minecraftforge.fml.common.Mod; public class ForgeEventHandlers { @SubscribeEvent - public void onPlayerTick(LivingEvent.LivingTickEvent ev) + public void onPlayerTick(LivingEvent.LivingUpdateEvent ev) { - if(ev.getEntity().level().isClientSide) return; + if(ServerUtilities.isClient()) return; if(ev.getEntity() instanceof ServerPlayer player) { @@ -44,11 +44,11 @@ public class ForgeEventHandlers { @SubscribeEvent public void onPlayerJoin(final PlayerEvent.PlayerLoggedInEvent ev) { - if(ev.getEntity().level().isClientSide)return; + if(ServerUtilities.isClient())return; ServerPlayer player = (ServerPlayer)ev.getEntity(); Profile prof = Profile.factory(player); - ServerLevel level = player.serverLevel(); + ServerLevel level = player.getLevel(); MinecraftForge.EVENT_BUS.post(new ProfileLoadedEvent(prof, player, level)); @@ -68,7 +68,7 @@ public class ForgeEventHandlers { public void onLeave(final PlayerEvent.PlayerLoggedOutEvent ev) { LibZontreck.LIBZONTRECK_SERVER_AVAILABLE=false; // Yes do this even on the client! - if(ev.getEntity().level().isClientSide)return; + if(ServerUtilities.isClient()) return; // Get player profile, send disconnect alert, then commit profile and remove it from memory try { diff --git a/src/main/java/dev/zontreck/libzontreck/items/CreativeModeTabs.java b/src/main/java/dev/zontreck/libzontreck/items/CreativeModeTabs.java deleted file mode 100644 index 5a24937..0000000 --- a/src/main/java/dev/zontreck/libzontreck/items/CreativeModeTabs.java +++ /dev/null @@ -1,46 +0,0 @@ -package dev.zontreck.libzontreck.items; - -import dev.zontreck.libzontreck.LibZontreck; -import net.minecraft.core.registries.Registries; -import net.minecraft.network.chat.Component; -import net.minecraft.world.item.CreativeModeTab; -import net.minecraft.world.item.Item; -import net.minecraft.world.item.Items; -import net.minecraft.world.level.ItemLike; -import net.minecraftforge.eventbus.api.IEventBus; -import net.minecraftforge.fml.common.Mod; -import net.minecraftforge.registries.DeferredRegister; -import net.minecraftforge.registries.ForgeRegistries; -import net.minecraftforge.registries.RegistryObject; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Supplier; - -//@Mod.EventBusSubscriber(modid = LibZontreck.MOD_ID, bus = Mod.EventBusSubscriber.Bus.MOD) -public class CreativeModeTabs -{ - public static final DeferredRegister REGISTRY = DeferredRegister.create(Registries.CREATIVE_MODE_TAB, LibZontreck.MOD_ID); - - public static final List> LZ_MOD_ITEMS = new ArrayList<>(); - - public static final RegistryObject LIBZONTRECK_TAB = REGISTRY.register("libzontreck", ()->CreativeModeTab.builder() - .title(Component.translatable("itemGroup.tabs.libzontreck")) - .icon(Items.BARRIER::getDefaultInstance) - .displayItems((display,output)->LZ_MOD_ITEMS.forEach(it->output.accept(it.get()))) - .build() - ); - - public static RegistryObject addToLZTab(RegistryObject item) - { - LZ_MOD_ITEMS.add(item); - return item; - } - - - public static void register(IEventBus bus) - { - REGISTRY.register(bus); - } - -} diff --git a/src/main/java/dev/zontreck/libzontreck/menus/ChestGUIScreen.java b/src/main/java/dev/zontreck/libzontreck/menus/ChestGUIScreen.java index 9ae9ee4..c736c76 100644 --- a/src/main/java/dev/zontreck/libzontreck/menus/ChestGUIScreen.java +++ b/src/main/java/dev/zontreck/libzontreck/menus/ChestGUIScreen.java @@ -1,9 +1,9 @@ package dev.zontreck.libzontreck.menus; import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.PoseStack; import dev.zontreck.libzontreck.LibZontreck; import dev.zontreck.libzontreck.chestgui.ChestGUI; -import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; import net.minecraft.client.renderer.GameRenderer; import net.minecraft.network.chat.Component; @@ -35,7 +35,7 @@ public class ChestGUIScreen extends AbstractContainerScreen { } @Override - public void render(GuiGraphics pGuiGraphics, int pMouseX, int pMouseY, float pPartialTick) { + public void render(PoseStack pGuiGraphics, int pMouseX, int pMouseY, float pPartialTick) { this.renderBackground(pGuiGraphics); super.render(pGuiGraphics, pMouseX, pMouseY, pPartialTick); this.renderTooltip(pGuiGraphics, pMouseX, pMouseY); @@ -50,17 +50,18 @@ public class ChestGUIScreen extends AbstractContainerScreen { } @Override - protected void renderBg(GuiGraphics guiGraphics, float v, int i, int i1) { + protected void renderBg(PoseStack guiGraphics, float v, int i, int i1) { renderBackground(guiGraphics); RenderSystem.setShader(GameRenderer::getPositionTexShader); RenderSystem.setShaderColor(1,1,1,1); RenderSystem.setShaderTexture(0, TEXTURE); - guiGraphics.blit(TEXTURE, this.leftPos, this.topPos, 0, 0, imageWidth, imageHeight); + blit(guiGraphics, this.leftPos, this.topPos, 0, 0, imageWidth, imageHeight); } @Override - protected void renderLabels(GuiGraphics pGuiGraphics, int pMouseX, int pMouseY) { - pGuiGraphics.drawString(this.font, this.title, this.titleLabelX, this.titleLabelY, 4210752, false); + protected void renderLabels(PoseStack pGuiGraphics, int pMouseX, int pMouseY) { + + drawString(pGuiGraphics, this.font, this.title, this.titleLabelX, this.titleLabelY, 4210752); } } diff --git a/src/main/java/dev/zontreck/libzontreck/networking/ModMessages.java b/src/main/java/dev/zontreck/libzontreck/networking/ModMessages.java index 002a9ac..5345c37 100644 --- a/src/main/java/dev/zontreck/libzontreck/networking/ModMessages.java +++ b/src/main/java/dev/zontreck/libzontreck/networking/ModMessages.java @@ -49,21 +49,21 @@ public class ModMessages { net.messageBuilder(S2CPlaySoundPacket.class, PACKET_ID.getAndIncrement(), NetworkDirection.PLAY_TO_CLIENT) .decoder(S2CPlaySoundPacket::new) - .encoder(S2CPlaySoundPacket::toBytes) - .consumerMainThread(S2CPlaySoundPacket::handle) - .add(); + .encoder(S2CPlaySoundPacket::toBytes) + .consumer(S2CPlaySoundPacket::handle) + .add(); net.messageBuilder(S2CCloseChestGUI.class, PACKET_ID.getAndIncrement(), NetworkDirection.PLAY_TO_CLIENT) .decoder(S2CCloseChestGUI::new) .encoder(S2CCloseChestGUI::toBytes) - .consumerMainThread(S2CCloseChestGUI::handle) + .consumer(S2CCloseChestGUI::handle) .add(); net.messageBuilder(S2CServerAvailable.class, PACKET_ID.getAndIncrement(), NetworkDirection.PLAY_TO_CLIENT) .decoder(S2CServerAvailable::new) .encoder(S2CServerAvailable::toBytes) - .consumerMainThread(S2CServerAvailable::handle) + .consumer(S2CServerAvailable::handle) .add(); } diff --git a/src/main/java/dev/zontreck/libzontreck/networking/packets/S2CPlaySoundPacket.java b/src/main/java/dev/zontreck/libzontreck/networking/packets/S2CPlaySoundPacket.java index 4d79616..da1732b 100644 --- a/src/main/java/dev/zontreck/libzontreck/networking/packets/S2CPlaySoundPacket.java +++ b/src/main/java/dev/zontreck/libzontreck/networking/packets/S2CPlaySoundPacket.java @@ -48,7 +48,8 @@ public class S2CPlaySoundPacket implements IPacket ctx.enqueueWork(()->{ // We are on the client now, enqueue the sound! - SoundEvent ev = SoundEvent.createFixedRangeEvent(sound, 2.0f); + SoundEvent ev = new SoundEvent(sound); + // Play sound for player! Minecraft.getInstance().player.playSound(ev, 1, BinUtil.getARandomInstance().nextFloat(0, 1)); }); diff --git a/src/main/java/dev/zontreck/libzontreck/types/ModMenuTypes.java b/src/main/java/dev/zontreck/libzontreck/types/ModMenuTypes.java index cfecba4..0e8d0e8 100644 --- a/src/main/java/dev/zontreck/libzontreck/types/ModMenuTypes.java +++ b/src/main/java/dev/zontreck/libzontreck/types/ModMenuTypes.java @@ -13,7 +13,7 @@ import net.minecraftforge.registries.RegistryObject; public class ModMenuTypes { - public static DeferredRegister> REGISTRY = DeferredRegister.create(ForgeRegistries.MENU_TYPES, LibZontreck.MOD_ID); + public static DeferredRegister> REGISTRY = DeferredRegister.create(ForgeRegistries.CONTAINERS, LibZontreck.MOD_ID); public static RegistryObject> CHEST_GUI_MENU = registerMenuType(ChestGUIMenu::new, "chestgui"); diff --git a/src/main/java/dev/zontreck/libzontreck/util/ChatHelpers.java b/src/main/java/dev/zontreck/libzontreck/util/ChatHelpers.java index fb6dc04..02f6f56 100644 --- a/src/main/java/dev/zontreck/libzontreck/util/ChatHelpers.java +++ b/src/main/java/dev/zontreck/libzontreck/util/ChatHelpers.java @@ -111,7 +111,7 @@ public class ChatHelpers { */ public static MutableComponent macro(String input, String... inputs) { - return Component.literal(macroize(input,inputs)); + return new TextComponent(macroize(input,inputs)); } /** * Returns the output with colors applied, and chat entries replaced using [number] as the format diff --git a/src/main/java/dev/zontreck/libzontreck/util/ServerUtilities.java b/src/main/java/dev/zontreck/libzontreck/util/ServerUtilities.java index abd8d57..d1b753b 100644 --- a/src/main/java/dev/zontreck/libzontreck/util/ServerUtilities.java +++ b/src/main/java/dev/zontreck/libzontreck/util/ServerUtilities.java @@ -40,7 +40,7 @@ public class ServerUtilities channel.messageBuilder(type, ModMessages.id(), packet.getDirection()) .decoder(decoder) .encoder(X::toBytes) - .consumerMainThread(X::handle) + .consumer(X::handle) .add(); } diff --git a/src/main/java/dev/zontreck/libzontreck/util/heads/CreditsEntry.java b/src/main/java/dev/zontreck/libzontreck/util/heads/CreditsEntry.java index 67a92a0..2c228bf 100644 --- a/src/main/java/dev/zontreck/libzontreck/util/heads/CreditsEntry.java +++ b/src/main/java/dev/zontreck/libzontreck/util/heads/CreditsEntry.java @@ -4,6 +4,7 @@ import dev.zontreck.libzontreck.chat.ChatColor; import dev.zontreck.libzontreck.lore.ExtraLore; import dev.zontreck.libzontreck.lore.LoreContainer; import dev.zontreck.libzontreck.lore.LoreEntry; +import dev.zontreck.libzontreck.util.ChatHelpers; import dev.zontreck.libzontreck.util.heads.HeadCache.HeadCacheItem; import net.minecraft.network.chat.Component; import net.minecraft.world.item.ItemStack; @@ -28,7 +29,7 @@ public class CreditsEntry { public ItemStack compile() { ItemStack stack = player.getAsItem(""); - stack.setHoverName(Component.literal(name)); + stack.setHoverName(ChatHelpers.macro(name)); LoreContainer contain = new LoreContainer(stack); contain.clear(); LoreEntry.Builder builder = new LoreEntry.Builder(); diff --git a/src/main/java/dev/zontreck/libzontreck/vectors/WorldPosition.java b/src/main/java/dev/zontreck/libzontreck/vectors/WorldPosition.java index fa69b61..2a2afad 100644 --- a/src/main/java/dev/zontreck/libzontreck/vectors/WorldPosition.java +++ b/src/main/java/dev/zontreck/libzontreck/vectors/WorldPosition.java @@ -35,7 +35,7 @@ public class WorldPosition { } public WorldPosition(ServerPlayer player) { - this(new Vector3(player.position()), player.serverLevel()); + this(new Vector3(player.position()), player.getLevel()); } public WorldPosition(Vector3 pos, ServerLevel lvl) { diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index a1b1cbb..a69184f 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -6,30 +6,32 @@ # The name of the mod loader type to load - for regular FML @Mod mods it should be javafml modLoader="javafml" #mandatory # A version range to match for said mod loader - for regular FML @Mod it will be the forge version -loaderVersion="${loader_version_range}" #mandatory This is typically bumped every Minecraft version by Forge. See our download page for lists of versions. +loaderVersion="[40,)" #mandatory This is typically bumped every Minecraft version by Forge. See our download page for lists of versions. # The license for you mod. This is mandatory metadata and allows for easier comprehension of your redistributive properties. # Review your options at https://choosealicense.com/. All rights reserved is the default copyright stance, and is thus the default here. -license="${mod_license}" +license="GPLv2" # A URL to refer people to when problems occur with this mod -issueTrackerURL="https://github.com/zontreck/LibZontreckMod/issues" #optional +issueTrackerURL="https://github.com/zontreck/LibZontreckMod/issues/" #optional # A list of mods - how many allowed here is determined by the individual mod loader [[mods]] #mandatory # The modid of the mod -modId="${mod_id}" #mandatory -# The version number of the mod -version="${mod_version}" #mandatory +modId="libzontreck" #mandatory +# The version number of the mod - there's a few well known ${} variables useable here or just hardcode it +# ${file.jarVersion} will substitute the value of the Implementation-Version as read from the mod's JAR file metadata +# see the associated build.gradle script for how to populate this completely automatically during a build +version="${file.jarVersion}" #mandatory # A display name for the mod -displayName="${mod_name}" #mandatory -# A URL to query for updates for this mod. See the JSON update specification https://docs.minecraftforge.net/en/latest/misc/updatechecker/ +displayName="LibZontreck" #mandatory +# A URL to query for updates for this mod. See the JSON update specification https://mcforge.readthedocs.io/en/latest/gettingstarted/autoupdate/ #updateJSONURL="https://change.me.example.invalid/updates.json" #optional # A URL for the "homepage" for this mod, displayed in the mod UI #displayURL="https://change.me.to.your.mods.homepage.example.invalid/" #optional # A file name (in the root of the mod JAR) containing a logo for display #logoFile="examplemod.png" #optional # A text field displayed in the mod UI -#credits="" #optional +credits="Zontreck" #optional # A text field displayed in the mod UI -authors="${mod_authors}" #optional +authors="zontreck" #optional # Display Test controls the display for your mod in the server connection screen # MATCH_VERSION means that your mod will cause a red X if the versions on client and server differ. This is the default behaviour and should be what you choose if you have server and client elements to your mod. # IGNORE_SERVER_VERSION means that your mod will not cause a red X if it's present on the server but not on the client. This is what you should use if you're a server only mod. @@ -39,32 +41,26 @@ authors="${mod_authors}" #optional #displayTest="MATCH_VERSION" # MATCH_VERSION is the default if nothing is specified (#optional) # The description text for the mod (multi line!) (#mandatory) -description='''${mod_description}''' +description=''' +This is a library mod for common code for all zontreck's mods. +''' # A dependency - use the . to indicate dependency for a specific modid. Dependencies are optional. -[[dependencies.${mod_id}]] #optional - # the modid of the dependency - modId="forge" #mandatory - # Does this dependency have to exist - if not, ordering below must be specified - mandatory=true #mandatory - # The version range of the dependency - versionRange="${forge_version_range}" #mandatory - # An ordering relationship for the dependency - BEFORE or AFTER required if the dependency is not mandatory - # BEFORE - This mod is loaded BEFORE the dependency - # AFTER - This mod is loaded AFTER the dependency - ordering="NONE" - # Side this dependency is applied on - BOTH, CLIENT, or SERVER - side="BOTH" +[[dependencies.libzontreck]] #optional +# the modid of the dependency +modId="forge" #mandatory +# Does this dependency have to exist - if not, ordering below must be specified +mandatory=true #mandatory +# The version range of the dependency +versionRange="[40,)" #mandatory +# An ordering relationship for the dependency - BEFORE or AFTER required if the relationship is not mandatory +ordering="NONE" +# Side this dependency is applied on - BOTH, CLIENT or SERVER +side="BOTH" # Here's another dependency -[[dependencies.${mod_id}]] - modId="minecraft" - mandatory=true - # This version range declares a minimum of the current minecraft version up to but not including the next major version - versionRange="${minecraft_version_range}" - ordering="NONE" - side="BOTH" - -# Features are specific properties of the game environment, that you may want to declare you require. This example declares -# that your mod requires GL version 3.2 or higher. Other features will be added. They are side aware so declaring this won't -# stop your mod loading on the server for example. -#[features.${mod_id}] -#openGLVersion="[3.2,)" \ No newline at end of file +[[dependencies.libzontreck]] +modId="minecraft" +mandatory=true +# This version range declares a minimum of the current minecraft version up to but not including the next major version +versionRange="[1.18.2,1.19)" +ordering="NONE" +side="BOTH" \ No newline at end of file diff --git a/src/main/resources/pack.mcmeta b/src/main/resources/pack.mcmeta index eca79ae..954af3c 100644 --- a/src/main/resources/pack.mcmeta +++ b/src/main/resources/pack.mcmeta @@ -1,8 +1,8 @@ { "pack": { - "description": { - "text": "${mod_id} resources" - }, - "pack_format": 15 + "description": "LibZontreck resources", + "pack_format": 9, + "forge:resource_pack_format": 8, + "forge:data_pack_format": 9 } } \ No newline at end of file From 275288447fd1aca8e695fc6d0fcbd0b9f05d7df9 Mon Sep 17 00:00:00 2001 From: zontreck Date: Sun, 21 Jan 2024 17:10:26 -0700 Subject: [PATCH 02/21] Add some new APIs for file and snbt --- gradle.properties | 4 +- .../dev/zontreck/libzontreck/util/SNbtIo.java | 46 +++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 src/main/java/dev/zontreck/libzontreck/util/SNbtIo.java diff --git a/gradle.properties b/gradle.properties index 45e2100..84387da 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,7 +7,7 @@ org.gradle.daemon=false parchment_version=2022.11.06 # luckperms_api_version=5.4 -libac=1.4.18 +libac=1.4.19 eventsbus=1.0.31 ## Environment Properties @@ -53,7 +53,7 @@ mod_name=Zontreck Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1.10.011624.1712 +mod_version=1.10.012124.1709 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/dev/zontreck/libzontreck/util/SNbtIo.java b/src/main/java/dev/zontreck/libzontreck/util/SNbtIo.java new file mode 100644 index 0000000..40fcbb6 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/util/SNbtIo.java @@ -0,0 +1,46 @@ +package dev.zontreck.libzontreck.util; + +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import dev.zontreck.ariaslib.util.FileIO; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtIo; +import net.minecraft.nbt.NbtUtils; + +import java.io.File; +import java.nio.file.Path; + +/** + * Provides helpers for reading and writing snbt to file + */ +public class SNbtIo +{ + /** + * Read the file at the path, and deserialize from snbt + * @param path The file to load + * @return The deserialized compound tag, or a blank tag + */ + public static CompoundTag loadSnbt(Path path) + { + if(!path.toFile().exists()) + return new CompoundTag(); + else { + File fi = path.toFile(); + try { + return NbtUtils.snbtToStructure(FileIO.readFile(fi.getAbsolutePath())); + } catch (CommandSyntaxException e) { + return new CompoundTag(); + } + } + } + + /** + * Writes the tag to the file specified + * @param path The file to write + * @param tag The tag to serialize + */ + public static void writeSnbt(Path path, CompoundTag tag) + { + String snbt = NbtUtils.structureToSnbt(tag); + FileIO.writeFile(path.toFile().getAbsolutePath(), snbt); + } +} From dabc717bd48901ee7e11bedffdf207a64e872900 Mon Sep 17 00:00:00 2001 From: Tara Piccari Date: Wed, 31 Jan 2024 17:57:01 -0700 Subject: [PATCH 03/21] Update to 1.19.2 --- build.gradle | 2 +- changelog.txt | 460 ------------------ gradle.properties | 17 +- gradle/wrapper/gradle-wrapper.jar | Bin 61574 -> 60756 bytes gradlew | 12 +- gradlew.bat | 1 - .../libzontreck/chat/ChatColorFactory.java | 9 + .../libzontreck/chestgui/ChestGUI.java | 2 +- .../events/ForgeEventHandlers.java | 2 +- .../libzontreck/networking/ModMessages.java | 6 +- .../libzontreck/profiles/Profile.java | 2 +- .../libzontreck/types/ModMenuTypes.java | 2 +- .../libzontreck/util/ChatHelpers.java | 2 +- .../libzontreck/util/ServerUtilities.java | 2 +- src/main/resources/META-INF/mods.toml | 6 +- 15 files changed, 33 insertions(+), 492 deletions(-) delete mode 100644 changelog.txt diff --git a/build.gradle b/build.gradle index d55b489..6321e02 100644 --- a/build.gradle +++ b/build.gradle @@ -43,7 +43,7 @@ minecraft { // // Use non-default mappings at your own risk. They may not always work. // Simply re-run your setup task after changing the mappings to update your workspace. - mappings channel: mapping_channel, version: mapping_version + mappings channel: mapping_channel, version: "${parchment_version}-${minecraft_version}" // When true, this property will have all Eclipse/IntelliJ IDEA run configurations run the "prepareX" task for the given run configuration before launching the game. // In most cases, it is not necessary to enable. diff --git a/changelog.txt b/changelog.txt deleted file mode 100644 index b834962..0000000 --- a/changelog.txt +++ /dev/null @@ -1,460 +0,0 @@ -1.19.x Changelog -43.1 -==== - - 43.1.1 Add ability to Auto register capabilities via annotation (#8972) - - 43.1.0 1.19.2 RB - -43.0 -==== - - 43.0.22 Added ItemDecorator API (#8794) - - 43.0.21 [1.19.x] Custom usage animations for items (#8932) - - 43.0.20 Allow registering custom `ColorResolver`s (#8880) - - 43.0.19 [1.19] Allow custom outline rendering on EntityRenderers and BlockEntityRenderers (#8938) - - 43.0.18 Redirect checks for entity selector use to a permission (#8947) - This allows greater flexibility for configuring servers with - operator-like permissions to user groups through the permissions API and - their permissions handler of choice without needing to grant the - vanilla operator permission to any player. - The new permission is "forge:use_entity_selectors", which is granted by - default to players with permission level 2 (GAMEMASTERS) and above. - The hook falls back to checking the permission level if the source of - the command is not a ServerPlayer, such as for command blocks and - functions. - - 43.0.17 Allow FakePlayer to report its position (#8963) - - 43.0.16 Add alternate version of renderEntityInInventory to allow for directly specifying the angles (#8961) - - 43.0.15 Add cancellable ToastAddEvent (#8952) - - 43.0.14 Modify ScreenEvent.RenderInventoryMobEffects to allow moving the effect stack left or right (#8951) - - 43.0.13 Fix Enchantment#doPostHurt and Enchantment#doPostAttack being called twice for players. Fixes MC-248272 (#8948) - - 43.0.12 Remove reflective implementation of ICustomPacket. (#8973) - Make vanilla custom packets able to be sent multiple times. Closes #8969 - - 43.0.11 Filter name spaces to directories only. Closes #8413 - - 43.0.10 Fix a corner case where the UMLB can not extract a version from a library. (#8967) - - 43.0.9 Fix worlds with removed dimension types unable to load. (#8959) Closes #8800 - - 43.0.8 Fix issue where unknown chunk generators would cause DFU to fail. (#8957) - - 43.0.7 Fix comments and documentation that were missed during the review of #8712 (#8945) - - 43.0.6 Make AnvilUpdateEvent fire even if the second input is empty, which means it fires even if only changing the item name. (#8905) - - 43.0.5 Fix `LivingEntity#isBlocking` to use `ToolActions#SHIELD_BLOCK` instead of `UseAnim#BLOCK` (#8933) - - 43.0.4 Add Custom HolderSet Types allowing for logical combining of sets. (#8928) - - 43.0.3 Add values to VersionSupportMatrix to support loading mods that restrict versions to 1.19.1 on 1.19.2 (#8946) - - 43.0.2 Fix certain particles not updating their bounding box when their position changes (#8925) - - 43.0.1 Update EventBus to address concurrency issue in ModLauncher Factory. Closes #8924 - - 43.0.0 1.19.2 - -42.0 -==== - - 42.0.9 Remove calls to getStepHeight in Player#maybeBackOffFromEdge (#8927) - - 42.0.8 Add forge tags for tools and armors, these DO NOT replace ToolActions, and are designed just for Recipes. (#8914) - - 42.0.7 Add Biomes.BEACH to Tags (#8892) - - 42.0.6 Let NetworkInstance.isRemotePresent check minecraft:register for channel IDs. (#8921) - - 42.0.5 Add an event for when the chunk ticket level is updated (#8909) - - 42.0.4 Re-add PotentialSpawns event (#8712) - - 42.0.3 Fix misplaced patch in ItemEntityRenderer breaking ItemEntityRenderer#shouldBob() (#8919) - - 42.0.2 [1.19] [HotFix] Fix the dedicated server not having access to the JiJ filesystems. (#8931) - - 42.0.1 Match Mojang's action bar fix for MC-72687 (#8917) - - 42.0.0 Forge 1.19.1 - Load natives from classpath - Make command argument types a forge registry - Add `EntityMobGriefingEvent` to `Allay#wantsToPickUp` - Overhaul `ServerChatEvent` to use `ChatDecorator` system - Remove `ClientChatEvent#setMessage` for now - Gradle 7.5 - -41.1 -==== - - 41.1.0 Mark 1.19 RB - -41.0 -==== - - 41.0.113 Allow faces of an "elements" model to be made emissive (#8890) - - 41.0.112 Fix invalid channel names sent from the server causing the network thread to error. (#8902) - - 41.0.111 Fix PlayerEvent.BreakSpeed using magic block position to signify invalid position. Closes #8906 - - 41.0.110 Fix cases where URIs would not work properly with JarInJar (#8900) - - 41.0.109 Add new hook to allow modification of lightmap via Dimension special effects (#8863) - - 41.0.108 Fix Forge's packet handling on play messages. (#8875) - - 41.0.107 Add API for tab list header/footer (#8803) - - 41.0.106 Allow modded blocks overriding canStickTo prevent sticking to vanilla blocks/other modded blocks (#8837) - - 41.0.105 Multiple tweaks and fixes to the recent changes in the client refactor PR: Part 3 (#8864) - Fix weighted baked models not respecting children render types - Allow fluid container model to use base texture as particle - Fix inverted behavior in composite model building. Fixes #8871 - - 41.0.104 Fix crossbows not firing ArrowLooseEvent (#8887) - - 41.0.103 Add User-Agent header to requests made by the update checker (#8881) - Format: Java-http-client/ MinecraftForge/ / - - 41.0.102 Output the full path in a crash report so it is easier to find the outer mod when a crash in Jar-In-Jar occurs. (#8856) - - 41.0.101 Clean up the pick item ("middle mouse click") patches (#8870) - - 41.0.100 [1.19.x] Hotfix for test mods while the refactor is ongoing - - 41.0.99 add event to SugarCaneBlock (#8877) - - 41.0.98 Fix Global Loot Modifiers not using Dispatch Codec (#8859) - - 41.0.97 Allow block render types to be set in datagen (#8852) - - 41.0.96 Fix renderBreakingTexture not using the target's model data (#8849) - - 41.0.95 Multiple tweaks and fixes to the recent changes in the client refactor PR: Part 2 (#8854) - * Add getter for the component names in an unbaked geometry - * Fix render type hint not being copied in BlockGeometryBakingContext - * Ensure BlockRenderDispatches's renderSingleBlock uses the correct buffer - - 41.0.94 [1.19.x] Apply general renames, A SRG is provided for modders. (#8840) - See https://gist.github.com/SizableShrimp/882a671ff74256d150776da08c89ef72 - - 41.0.93 Fix mob block breaking AI not working correctly when chunk 0,0 is unloaded. Closes #8853 - - 41.0.92 Fix crash when breaking blocks with multipart models and remove caching. Closes #8850 - - 41.0.91 Fixed `CompositeModel.Baked.Builder.build()` passing arguments in the wrong order (#8846) - - 41.0.90 Make cutout mipmaps explicitly opt-in for item/entity rendering (#8845) - * Make cutout mipmaps explicitly opt-in for item/entity rendering - * Default render type domain to "minecraft" in model datagens - - 41.0.89 Fixed multipart block models not using the new model driven render type system. (#8844) - - 41.0.88 Update to the latest JarJar to fix a collision issue where multiple jars could provide an exact match. (#8847) - - 41.0.87 Add FML config to disable DFU optimizations client-side. (#8842) - * Add client-side command line argument to disable DFU optimizations. - * Switch to using FMLConfig value instead. - - 41.0.86 [1.19] Fixed broken BufferBuilder.putBulkData(ByteBuffer) added by Forge (#8819) - * Fixes BufferBuilder.putBulkData(ByteBuffer) - * use nextElementByte - * Fixed merge conflict - - 41.0.85 [1.19.x] Fix shulker boxes allowing input of items, that return false for Item#canFitInsideContainerItems, through hoppers. (#8823) - * Make ShulkerBoxBlockEntity#canPlaceItemThroughFace delegate to Item#canFitInsideContainerItems. - * Switch to using Or and add comment. - * Switch Or to And. - - 41.0.84 [1.19.x] Added RenderLevelStageEvent to replace RenderLevelLastEvent (#8820) - * Ported RenderLevelStageEvent from 1.18.2 - * Updated to fix merge conflicts - - 41.0.83 [1.19.x] Fix door datagenerator (#8821) - * Fix door datagenerator - Fix datagenerator for door blocks. Successor to #8687, addresses comments made there about statement complexity. - * Fix extra space around parameter - Fix extra space before comma around a parameter. - - 41.0.82 Create PieceBeardifierModifier to re-enable piecewise beardifier definitions (#8798) - - 41.0.81 Allow blocks to provide a dynamic MaterialColor for display on maps (#8812) - - 41.0.80 [1.19.x] BiomeTags Fixes/Improvements (#8711) - * dimension specific tag fix - * remove forge:is_beach cause vanilla has it already - * remove forge tags for new 1.19 vanilla tags (savanna, beach, overworld, end) - Co-authored-by: Flemmli97 - - 41.0.79 1.19 - Remove GlobalLootModifierSerializer and move to Codecs (#8721) - * convert GLM serializer class to codec - * cleanup - * GLM list needs to be sorted - * datagen - * simplify serialization - * fix test mods (oops) - * properly use suppliers for codec as they are registry obj - - 41.0.78 Implement item hooks for potions and enchantments (#8718) - * Implement item hooks for potions and enchantments - * code style fixes - - 41.0.77 Re-apply missing patch to ServerLevel.EntityCallbacks#onTrackingEnd() (#8828) - - 41.0.76 Double Bar Rendering fixed (#8806) (#8807) - * Double Bar Rendering fixed (#8806) - * Added requested changes by sciwhiz12 - - 41.0.75 Multiple tweaks and fixes to the recent changes in the client refactor PR (#8836) - * Add an easy way to get the NamedGuiOverlay from a vanilla overlay - * Fix static member ordering crash in UnitTextureAtlasSprite - * Allow boss bar rendering to be cancelled - * Make fluid container datagen use the new name - - 41.0.74 Add FogMode to ViewportEvent.RenderFog (#8825) - - 41.0.73 Provide additional context to the getFieldOfView event (#8830) - - 41.0.72 Pass renderType to IForgeBakedModel.useAmbientOcclusion (#8834) - - 41.0.71 Load custom ITransformationServices from the classpath in dev (#8818) - * Add a classpath transformer discoverer to load custom transformation services from the classpath - * Update ClasspathTransformerDiscoverer to 1.18 - * Update license year - * Update license header - * Fix the other license headers - * Update ClasspathTransformerDiscoverer to 1.19 - - 41.0.70 Handle modded packets on the network thread (#8703) - * Handle modded packets on the network thread - - On the server we simply need to remove the call to - ensureRunningOnSameThread. - - On the client side, we now handle the packet at the very start of the - call. We make sure we're running from a network thread to prevent - calling the handling code twice. - While this does mean we no longer call .release(), in practice this - doesn't cause any leaks as ClientboundCustomPayloadPacket releases - for us. - * Clarify behaviour a little in the documentation - * Javadoc formatting - * Add a helper method for handling packets on the main thread - Also rename the network thread one. Should make it clearer the expected - behaviour of the two, and make it clearer there's a potentially breaking - change. - * Add back consumer() methods - Also document EventNetworkChannel, to clarify the thread behaviour - there. - * Add since = "1.19" to deprecated annotations - - 41.0.69 Cache resource listing calls in resource packs (#8829) - * Make the resource lookups cached. - * Include configurability and handle patch cleanup. - * Document and comment the cache manager. - * Make thread selection configurable. - * Implement a configurable loading mechanic that falls back to default behaviour when the config is not bound yet. - * Use boolean supplier and fix wildcard import. - * Clean up the VPR since this is more elegant. - * Clean up the VPR since this is more elegant. - * Address review comments. - * Address more review comments. - * Fix formatting on `getSource` - * Address comments by ichtt - * Adapt to pups requests. - * Stupid idea. - * Attempt this again with a copy on write list. - * Fix a concurrency and loading issue. - * Fix #8813 - Checks if the paths are valid resource paths. - * Move the new methods on vanilla Patch. - - 41.0.68 Update SJH and JIJ - - 41.0.67 Fix #8833 (#8835) - - 41.0.66 Fix backwards fabulous check in SimpleBakedModel (#8832) - Yet another blunder we missed during the review of #8786. - - 41.0.65 Make texture atlas in StandaloneGeometryBakingContext configurable (#8831) - - 41.0.64 [1.19.X] Client code cleanup, updates, and other refactors (#8786) - * Revert "Allow safely registering RenderType predicates at any time (#8685)" - This reverts commit be7275443fd939db9c58bcad47079c3767789ac1. - * Renderable API refactors - - Rename "render values" to "context" - - Rename SimpleRenderable to CompositeRenderable to better reflect its use - - Remove IMultipartRenderValues since it doesn't have any real use - - Add extensive customization options to BakedModelRenderable - * ClientRegistry and MinecraftForgeClient refactors - - Add sprite loader manager and registration event - - Add spectator shader manager and registration event - - Add client tooltip factory manager and registration event - - Add recipe book manager and registration event - - Add key mapping registration event - - Remove ClientRegistry, as everything has been moved out of it - - Remove registration methods from MinecraftForgeClient, as they have dedicated events now - * Dimension special effects refactors - - Fold handlers into an extension class and remove public mutable fields - - Add dimension special effects manager and registration event - * HUD overlay refactors - - Rename to IGuiOverlay match vanilla (instead of Ingame) - - Add overlay manager and registration event - - Move vanilla overlays to a standalone enum - * Model loader refactors - - Rename IModelLoader to IGeometryLoader - - Add loader manager and registration event - - Fold all model events into one - - Move registration of additionally loaded models to an event - - Remove ForgeModelBakery and related classes as they served no purpose anymore - * Render properties refactors - - Rename all render properties to client extensions and relocate accordingly - - Move lookups to the respective interfaces - * Model data refactors - - Convert model data to a final class backed by an immutable map and document mutability requirements. This addresses several thread-safety issues in the current implementation which could result in race conditions - - Transfer ownership of the data manager to the client level. This addresses several issues that arise when multiple levels are used at once - * GUI and widget refactors - - Move all widgets to the correct package - - Rename GuiUtils and children to match vanilla naming - * New vertex pipeline API - - Move to vanilla's VertexConsumer - - Roll back recent PR making VertexConsumer format-aware. This is the opposite of what vanilla does, and should not be relevant with the updated lighting pipeline - * Lighting pipeline refactors - - Move to dedicated lighting package - - Separate flat and smooth lighters - - Convert from a vertex pipeline transformer to a pure vertex source (input is baked quads) - * Model geometry API refactors - - Rename IModelGeometry to IUnbakedGeometry - - Rename IModelConfiguration to IGeometryBakingContext - - Rename other elements to match vanilla naming - - Remove current changes to ModelState, as they do not belong there. Transforms should be specified through vanilla's system. ModelState is intended to transfer state from the blockstate JSON - - Remove multipart geometries and geometry parts. After some discussion, these should not be exposed. Instead, geometries should be baked with only the necessary parts enabled - * Make render types a first-class citizen in baked models - - Add named render types (block + entity + fabulous entity) - - Add named render type manager + registration event - - Make BakedModel aware of render types and transfer control over which ones are used to it instead of ItemBlockRenderTypes (fallback) - - (additional) Add concatenated list view. A wrapper for multiple lists that iterates through them in order without the cost of merging them. Useful for merging lists of baked quads - * General event refactors - - Several renames to either match vanilla or improve clarity - - Relocate client chat event dispatching out of common code - * Forge model type refactors - - Rename SeparatePerspectiveModel to SeparateTransformsModel - - Rename ItemModelMesherForge to ForgeItemModelShaper - - Rename DynamicBucketModel to DynamicFluidContainerModel - - Prefix all OBJ-related classes with "Obj" and decouple parsing from construction - - Extract ElementsModel from model loader registry - - Add EmptyModel (baked, unbaked and loader) - - Refactor CompositeModel to take over ItemMultiLayerBakedModel - - Remove FluidModel as it's not used and isn't compatible with the new fluid rendering in modern versions - - Move model loader registration to a proper event handler - - Update names of several JSON fields (backwards-compatible) - - Update datagens to match - * Miscellaneous changes and overlapping patches - - Dispatch all new registration events - - Convert ExtendedServerListData to a record - - Add/remove hooks from ForgeHooksClient as necessary - * Update test mods - * Fix VertexConsumerWrapper returning parent instead of itself - * Additional event cleanup pass - As discussed on Discord: - - Remove "@hidden" and "@see " javadoc annotations from all client events and replace them with @ApiStatus.Internal annotation - - Make all events that shouldn't be fired directly into abstract classes with protected constructors - - Another styling pass, just in case (caught some missed classes) - * Add proper deprecation javadocs and de-dupe some vertex consumer code - * Replace sets of chunk render types with a faster BitSet-backed collection - This largely addresses potential performance concerns that using a plain HashSet might involve by making lookups and iteration as linear as they can likely be (aside from using a plain byte/int/long for bit storage). Further performance concerns related to the implementation may be addressed separately, as all the implementation details are hidden from the end user - * Requested changes - - Remove MinecraftForgeClient and move members to Minecraft, IForgeMinecraft and StencilManager - - Allow non-default elements to be passed into VertexConsumer and add support to derived classes - - Move array instantiation out of quad processing in lighting pipeline - - Fix flipped fluid container model - - Set default UV1 to the correct values in the remapping pipeline - - Minor documentation changes - * Add/update EXC entries and fix AT comment - * Add test mod as per Orion's request - * Additional requested changes - * Allow custom model types to request the particle texture to be loaded - * Even more requested changes - * Improve generics in ConcatenatedListView and add missing fallbacks - * Fix fluid render types being bound to the fluid and not its holder - * Remove non-contractual nullability in ChunkRenderTypeSet and add isEmpty - Additionally, introduce chunk render type checks in ItemBlockRenderTypes - Co-authored-by: Dennis C - - 41.0.63 Implement full support for IPv6 (#8742) - - 41.0.62 Fix certain user-configured options being overwritten incorrectly due to validators. (#8780) - - 41.0.61 Allow safely registering RenderType predicates at any time (#8685) - - 41.0.60 Fix crash after loading error due to fluid texture gathering and config lookup (#8802) - - 41.0.59 Remove the configuration option for handling empty tags in ingredients. (#8799) - Now empty tags are considered broken in all states. - - 41.0.58 Fix MC-105317 Structure blocks do not rotate entities correctly when loading (#8792) - - 41.0.57 Fire ChunkWatchEvents after sending packets (#8747) - - 41.0.56 Add item handler capability to chest boats (#8787) - - 41.0.55 Add getter for correct BiomeSpecialEffectsBuilder to BiomeInfo$Builder (#8781) - - 41.0.54 Fix BlockToolModificationEvent missing cancelable annotation (#8778) - - 41.0.53 Fix ticking chunk tickets from forge's chunk manager not causing chunks to fully tick (#8775) - - 41.0.52 Fix default audio device config loading string comparison issue (#8767) - - 41.0.51 Fix missed vanilla method overrides in ForgeRegistry (#8766) - - 41.0.50 Add MinecraftServer reference to ServerTickEvent (#8765) - - 41.0.49 Fix TagsProviders for datapack registries not recognizing existing files (#8761) - - 41.0.48 Add callback after a BlockState was changed and the neighbors were updated (#8686) - - 41.0.47 Add biome tag entries for 1.19 biomes (#8684) - - 41.0.46 Make fishing rods use tool actions for relevant logic (#8681) - - 41.0.45 Update BootstrapLauncher to 1.1.1 and remove the forced - merge of text2speech since new BSL does it. - - 41.0.44 Merge text2speech libs together so the natives are part of the jar - - 41.0.43 Make Forge ConfigValues implement Supplier. (#8776) - - 41.0.42 Fix merge derp in AbstractModProvider and logic derp in ModDiscoverer - - 41.0.41 Add "send to mods in order" method to ModList and use it (#8759) - * Add "send to mods in order" method to ModList and use it in RegistryEvents and DataGen.. - * Also preserve order in runAll - * Do better comparator thanks @pupnewfster - * postEvent as well. - - 41.0.40 Update SJH to 2.0.2.. (#8774) - * Update SJH to 2.0.3.. - - 41.0.39 Sanity check the version specified in the mod file (#8749) - * Sanity check the version specified in the mod file to - make sure it's compatible with JPMS standards for - version strings. - Closes #8748 - Requires SPI 6 - - 41.0.38 Fix SP-Devtime world loading crash due to missing server configs (#8757) - - 41.0.37 Remove ForgeWorldPreset and related code (#8756) - Vanilla has a working replacement. - - 41.0.36 Change ConfigValue#get() to throw if called before config loaded (#8236) - This prevents silent issues where a mod gets the value of the setting - before configs are loaded, which means the default value is always - returned. - As there may be situations where the getting the config setting before - configs are loaded is needed, and it is not preferable to hardcode the - default value, the original behavior is made available through #getRaw. - Implements and closes #7716 - * Remove getRaw() method - This is effectively replaced with the expression `spec.isLoaded() ? - configValue.get() : configValue.getDefault()`. - * Remove forceSystemNanoTime config setting - As implemented, it never had any effect as any place where the config - value would be queried happens before the configs are loaded. - - 41.0.35 Fix EnumArgument to use enum names for suggestions (#8728) - Previously, the suggestions used the string representation of the enum - through Enum#toString, which can differ from the name of the enum as - required by Enum#valueOf, causing invalid suggestions (both in gui and - through the error message). - - 41.0.34 Jar-In-Jar (#8715) - - 41.0.33 [1.19] Fix data-gen output path of custom data-pack registries (#8724) - - 41.0.32 Fix player dive and surface animations in custom fluids (#8738) - - 41.0.31 [1.19.x] Affect ItemEntity Motion in Custom Fluids (#8737) - - 41.0.30 [1.19] Add support for items to add enchantments without setting them in NBT (#8719) - - 41.0.29 [1.19.x] Add stock biome modifier types for adding features and spawns (#8697) - - 41.0.28 [1.19.x] Fluid API Overhaul (#8695) - - 41.0.27 Replace StructureSpawnListGatherEvent with StructureModifiers (#8717) - - 41.0.26 Use stack sensitive translation key by default for FluidAttributes. (#8707) - - 41.0.25 Delete LootItemRandomChanceCondition which added looting bonus enchantment incorrectly. (#8733) - - 41.0.24 Update EventBus to 6.0, ModLauncher to 10.0.1 and BootstrapLauncher to 1.1 (#8725) - - 41.0.23 Replace support bot with support action (#8700) - - 41.0.22 Fix Reach Distance / Attack Range being clamped at 6.0 (#8699) - - 41.0.21 [1.19.x] Fix mods' worldgen data not being loaded when creating new singleplayer worlds (#8693) - - 41.0.20 [1.19.x] Fix experimental confirmation screen (#8727) - - 41.0.19 Move is_mountain to forge's tag instead of vanilla's (#8726) - - 41.0.18 [1.19.x] Add CommandBuildContext to Register Command Events (#8716) - - 41.0.17 Only rewrite datagen cache when needed (#8709) - - 41.0.16 Implement a simple feature system for Forge (#8670) - * Implement a simple feature system for Forge. Allows mods to demand certain features are available in the loading system. An example for java_version is provided, but not expected to be used widely. This is more targeted to properties of the display, such as GL version and glsl profile. - Requires https://github.com/MinecraftForge/ForgeSPI/pull/13 to be merged first in ForgeSPI, and the SPI to be updated appropriately in build.gradle files. - * rebase onto 1.19 and add in SPI update - - 41.0.15 displayTest option in mods.toml (#8656) - * displayTest option in mods.toml - * "MATCH_VERSION" (or none) is existing match version string behaviour - * "IGNORE_SERVER_VERSION" accepts anything and sends special SERVERONLY string - * "IGNORE_ALL_VERSION" accepts anything and sends an empty string - * "NONE" allows the mod to supply their own displaytest using the IExtensionPoint mechanism. - * Update display test with feedback and added the mods.toml discussion in mdk. - - 41.0.14 Update forgeSPI to v5 (#8696) - - 41.0.13 Make IVertexConsumers such as the lighting pipeline, be aware of which format they are dealing with. (#8692) - Also fix Lighting pipeline ignoring the overlay coords from the block renderer. - - 41.0.12 Fixed misaligned patch to invalidateCaps in Entity (#8705) - - 41.0.11 Fix readAdditionalLevelSaveData (#8704) - - 41.0.10 Fixes setPos to syncPacketPositionCodec (#8702) - - 41.0.9 Fix wrong param passed to PlayLevelSoundEvent.AtEntity (#8688) - - 41.0.8 Override initialize in SlotItemHandler, so it uses the itemhandler instead of container (#8679) - - 41.0.7 Update MDK for 1.19 changes (#8675) - - 41.0.6 Add helper to RecipeType, and fix eclipse compiler error in test class. - - 41.0.5 Update modlauncher to latest (#8691) - - 41.0.4 Fix getting entity data serializer id crashing due to improper port to new registry system (#8678) - - 41.0.3 Fire registry events in the order vanilla registers to registries (#8677) - Custom registries are still fired in alphabetical order, after all vanilla registries. - Move forge's data_serializers registry to forge namespace. - - 41.0.2 Add method with pre/post wrap to allow setting/clearing mod context. (#8682) - Fixes ActiveContainer in ModContext not being present in registry events. Closes #8680 - - 41.0.1 Fix the Curlie oopsie - - 41.0.0 Forge 1.19 - * Bump pack.mcmeta formats - * 1.19 biome modifiers - * Mark ClientPlayerNetworkEvent.LoggedOutEvent's getters as nullable - * Add docs and package-info to client extension interfaces package - * Move RenderBlockOverlayEvent hooks to ForgeHooksClient - * Add package-infos to client events package - * Rename SoundLoadEvent to SoundEngineLoadEvent - This reduces confusion from consumers which may think the - name SoundLoadEvent refers to an individual sound being loaded rather - than the sound engine. - * Document and change SoundLoadEvent to fire on mod bus - Previously, it fired on both the mod bus and the Forge bus, which is - confusing for consumers. - * Delete SoundSetupEvent - Looking at its original implementation shows that there isn't an - appropriate place in the new sound code to reinsert the event, and the - place of 'sound engine/manager initialization event' is taken already by SoundLoadEvent. - * Perform some cleanup on client events - - Removed nullable annotations from ClientPlayerNetworkEvent - - Renamed #getPartialTicks methods to #getPartialTick, to be consistent - with vanilla's naming of the partial tick - - Cleanup documentation to remove line breaks, use the - spelling 'cancelled' over - 'canceled', and improve docs on existing and - new methods. - * Remove EntityEvent.CanUpdate - Closes MinecraftForge/MinecraftForge#6394 - * Switch to Jetbrains nullability annotations - * New PlayLevelSoundEvent; replaces old PlaySoundAtEntityEvent - * Remove ForgeWorldPresetScreens - * Remove IForgeRegistryEntry - * Remove use of List in FML's CompletableFutures - * Add docs to mod loading stages, stages, and phases - * Gradle 7.4.2 - * Use SLF4J in FMLLoader and other subprojects - * Switch dynamic versions in subprojects to pinned ones - * Switch ForgeRoot and MDK to FG plugin markers - * Configure Forge javadoc task - The task now uses a custom stylesheet with MCForge elements, and - configured to combine the generation from the four FML subprojects - (fmlloader, fmlcore, javafmllanguage, mclanguage) and the Forge project - into the javadoc output. - * Update docs/md files, for 1.19 update and the move away from IRC to Discord. - * Make "Potentially dangerous alternative prefix" a debug warning, not info. - Co-authored-by: Curle - Co-authored-by: sciwhiz12 - diff --git a/gradle.properties b/gradle.properties index 84387da..7873924 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ org.gradle.jvmargs=-Xmx3G org.gradle.daemon=false -parchment_version=2022.11.06 +parchment_version=2022.11.27 # luckperms_api_version=5.4 libac=1.4.19 @@ -12,17 +12,17 @@ eventsbus=1.0.31 ## Environment Properties # The Minecraft version must agree with the Forge version to get a valid artifact -minecraft_version=1.18.2 +minecraft_version=1.19.2 # The Minecraft version range can use any release version of Minecraft as bounds. # Snapshots, pre-releases, and release candidates are not guaranteed to sort properly # as they do not follow standard versioning conventions. -minecraft_version_range=[1.18.2,1.19) +minecraft_version_range=[1.19,1.20) # The Forge version must agree with the Minecraft version to get a valid artifact -forge_version=40.2.17 +forge_version=43.3.0 # The Forge version range can use any version of Forge as bounds or match the loader version range -forge_version_range=[40,) +forge_version_range=[43,) # The loader version range can only use the major version of Forge/FML as bounds -loader_version_range=[40,) +loader_version_range=[43,) # The mapping channel to use for mappings. # The default set of supported mapping channels are ["official", "snapshot", "snapshot_nodoc", "stable", "stable_nodoc"]. # Additional mapping channels can be registered through the "channelProviders" extension in a Gradle plugin. @@ -38,9 +38,6 @@ loader_version_range=[40,) # Parchment is an unofficial project maintained by ParchmentMC, separate from Minecraft Forge. # Additional setup is needed to use their mappings, see https://parchmentmc.org/docs/getting-started mapping_channel=parchment -# The mapping version to query from the mapping channel. -# This must match the format required by the mapping channel. -mapping_version=2022.11.06-1.18.2 ## Mod Properties @@ -53,7 +50,7 @@ mod_name=Zontreck Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1.10.012124.1709 +mod_version=1.10.013124.1729 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 943f0cbfa754578e88a3dae77fce6e3dea56edbf..249e5832f090a2944b7473328c07c9755baa3196 100644 GIT binary patch delta 35766 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%0DGK?7GHTj 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 VwlMN}`%er%aGR6olj~j${vQ;P=LY}) delta 36524 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

+ *

* This is used to contain common player data, as well as be capable of serializing the player's data and sending to/from the client. */ public class Profile { diff --git a/src/main/java/dev/zontreck/libzontreck/types/ModMenuTypes.java b/src/main/java/dev/zontreck/libzontreck/types/ModMenuTypes.java index 0e8d0e8..cfecba4 100644 --- a/src/main/java/dev/zontreck/libzontreck/types/ModMenuTypes.java +++ b/src/main/java/dev/zontreck/libzontreck/types/ModMenuTypes.java @@ -13,7 +13,7 @@ import net.minecraftforge.registries.RegistryObject; public class ModMenuTypes { - public static DeferredRegister> REGISTRY = DeferredRegister.create(ForgeRegistries.CONTAINERS, LibZontreck.MOD_ID); + public static DeferredRegister> REGISTRY = DeferredRegister.create(ForgeRegistries.MENU_TYPES, LibZontreck.MOD_ID); public static RegistryObject> CHEST_GUI_MENU = registerMenuType(ChestGUIMenu::new, "chestgui"); diff --git a/src/main/java/dev/zontreck/libzontreck/util/ChatHelpers.java b/src/main/java/dev/zontreck/libzontreck/util/ChatHelpers.java index 02f6f56..fb6dc04 100644 --- a/src/main/java/dev/zontreck/libzontreck/util/ChatHelpers.java +++ b/src/main/java/dev/zontreck/libzontreck/util/ChatHelpers.java @@ -111,7 +111,7 @@ public class ChatHelpers { */ public static MutableComponent macro(String input, String... inputs) { - return new TextComponent(macroize(input,inputs)); + return Component.literal(macroize(input,inputs)); } /** * Returns the output with colors applied, and chat entries replaced using [number] as the format diff --git a/src/main/java/dev/zontreck/libzontreck/util/ServerUtilities.java b/src/main/java/dev/zontreck/libzontreck/util/ServerUtilities.java index d1b753b..abd8d57 100644 --- a/src/main/java/dev/zontreck/libzontreck/util/ServerUtilities.java +++ b/src/main/java/dev/zontreck/libzontreck/util/ServerUtilities.java @@ -40,7 +40,7 @@ public class ServerUtilities channel.messageBuilder(type, ModMessages.id(), packet.getDirection()) .decoder(decoder) .encoder(X::toBytes) - .consumer(X::handle) + .consumerMainThread(X::handle) .add(); } diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index a69184f..ac70534 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -6,7 +6,7 @@ # The name of the mod loader type to load - for regular FML @Mod mods it should be javafml modLoader="javafml" #mandatory # A version range to match for said mod loader - for regular FML @Mod it will be the forge version -loaderVersion="[40,)" #mandatory This is typically bumped every Minecraft version by Forge. See our download page for lists of versions. +loaderVersion="[43,)" #mandatory This is typically bumped every Minecraft version by Forge. See our download page for lists of versions. # The license for you mod. This is mandatory metadata and allows for easier comprehension of your redistributive properties. # Review your options at https://choosealicense.com/. All rights reserved is the default copyright stance, and is thus the default here. license="GPLv2" @@ -51,7 +51,7 @@ modId="forge" #mandatory # Does this dependency have to exist - if not, ordering below must be specified mandatory=true #mandatory # The version range of the dependency -versionRange="[40,)" #mandatory +versionRange="[43,)" #mandatory # An ordering relationship for the dependency - BEFORE or AFTER required if the relationship is not mandatory ordering="NONE" # Side this dependency is applied on - BOTH, CLIENT or SERVER @@ -61,6 +61,6 @@ side="BOTH" modId="minecraft" mandatory=true # This version range declares a minimum of the current minecraft version up to but not including the next major version -versionRange="[1.18.2,1.19)" +versionRange="[1.19,1.20)" ordering="NONE" side="BOTH" \ No newline at end of file From 8319cd850a4b489ea2b681bda4a7d7bfabd4c701 Mon Sep 17 00:00:00 2001 From: Zontreck Date: Tue, 6 Feb 2024 22:18:35 -0700 Subject: [PATCH 04/21] Update the LibAC package with the new one built by CI/CD --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 7873924..0c45ef0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,7 +7,7 @@ org.gradle.daemon=false parchment_version=2022.11.27 # luckperms_api_version=5.4 -libac=1.4.19 +libac=1.4.24 eventsbus=1.0.31 ## Environment Properties From 2715c3fbb5bd0021d5cfe5a9d4d1a80989e47047 Mon Sep 17 00:00:00 2001 From: Zontreck Date: Tue, 6 Feb 2024 22:26:57 -0700 Subject: [PATCH 05/21] Increment LibZ version number --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 0c45ef0..4426b83 100644 --- a/gradle.properties +++ b/gradle.properties @@ -50,7 +50,7 @@ mod_name=Zontreck Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1.10.013124.1729 +mod_version=1.10.020624.2226 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html From 1c27c3da013e54fa721dfe82cae780ad0297d571 Mon Sep 17 00:00:00 2001 From: Zontreck Date: Tue, 13 Feb 2024 22:00:37 -0700 Subject: [PATCH 06/21] Increment libac and eventbus versions --- gradle.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index 4426b83..d09bf23 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,8 +7,8 @@ org.gradle.daemon=false parchment_version=2022.11.27 # luckperms_api_version=5.4 -libac=1.4.24 -eventsbus=1.0.31 +libac=1.4.46 +eventsbus=1.0.45 ## Environment Properties # The Minecraft version must agree with the Forge version to get a valid artifact From c3d9096e6360f5b45d4c2c0264d6dfd0e3852ede Mon Sep 17 00:00:00 2001 From: Zontreck Date: Tue, 13 Feb 2024 22:43:36 -0700 Subject: [PATCH 07/21] Merge the 1.19 changes into the 1.20 codebase --- gradle.properties | 6 +-- .../dev/zontreck/libzontreck/LibZontreck.java | 2 - .../zontreck/libzontreck/chat/HoverTip.java | 3 +- .../libzontreck/chestgui/ChestGUI.java | 3 +- .../libzontreck/items/CreativeModeTabs.java | 46 ------------------- .../dev/zontreck/libzontreck/util/SNbtIo.java | 45 ++++++++++++++++++ .../libzontreck/util/heads/CreditsEntry.java | 3 +- 7 files changed, 54 insertions(+), 54 deletions(-) delete mode 100644 src/main/java/dev/zontreck/libzontreck/items/CreativeModeTabs.java create mode 100644 src/main/java/dev/zontreck/libzontreck/util/SNbtIo.java diff --git a/gradle.properties b/gradle.properties index 50bba12..5ff2ff9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,8 +7,8 @@ org.gradle.daemon=false parchment_version=2023.09.03 # luckperms_api_version=5.4 -libac=1.4.18 -eventsbus=1.0.31 +libac=1.4.46 +eventsbus=1.0.45 ## Environment Properties # The Minecraft version must agree with the Forge version to get a valid artifact @@ -53,7 +53,7 @@ mod_name=Zontreck Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1.10.011524.0045 +mod_version=1.10.021324.2257 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/dev/zontreck/libzontreck/LibZontreck.java b/src/main/java/dev/zontreck/libzontreck/LibZontreck.java index eafd174..bc5fe5e 100644 --- a/src/main/java/dev/zontreck/libzontreck/LibZontreck.java +++ b/src/main/java/dev/zontreck/libzontreck/LibZontreck.java @@ -13,13 +13,11 @@ import dev.zontreck.eventsbus.Bus; import dev.zontreck.libzontreck.chestgui.ChestGUIRegistry; import dev.zontreck.libzontreck.currency.Bank; import dev.zontreck.libzontreck.currency.CurrencyHelper; -import dev.zontreck.libzontreck.items.CreativeModeTabs; import dev.zontreck.libzontreck.items.ModItems; import dev.zontreck.libzontreck.menus.ChestGUIScreen; import dev.zontreck.libzontreck.types.ModMenuTypes; import dev.zontreck.libzontreck.networking.NetworkEvents; import net.minecraft.client.gui.screens.MenuScreens; -import net.minecraftforge.registries.RegisterEvent; import org.slf4j.Logger; import com.mojang.logging.LogUtils; diff --git a/src/main/java/dev/zontreck/libzontreck/chat/HoverTip.java b/src/main/java/dev/zontreck/libzontreck/chat/HoverTip.java index 2eafb13..083d974 100644 --- a/src/main/java/dev/zontreck/libzontreck/chat/HoverTip.java +++ b/src/main/java/dev/zontreck/libzontreck/chat/HoverTip.java @@ -1,5 +1,6 @@ package dev.zontreck.libzontreck.chat; +import dev.zontreck.libzontreck.util.ChatHelpers; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.HoverEvent; import net.minecraft.network.chat.HoverEvent.Action; @@ -17,7 +18,7 @@ public class HoverTip { */ public static HoverEvent get(String text) { - return new HoverEvent(Action.SHOW_TEXT, Component.literal(text)); + return new HoverEvent(Action.SHOW_TEXT, ChatHelpers.macro(text)); } /** diff --git a/src/main/java/dev/zontreck/libzontreck/chestgui/ChestGUI.java b/src/main/java/dev/zontreck/libzontreck/chestgui/ChestGUI.java index ae829ec..8e41c10 100644 --- a/src/main/java/dev/zontreck/libzontreck/chestgui/ChestGUI.java +++ b/src/main/java/dev/zontreck/libzontreck/chestgui/ChestGUI.java @@ -7,6 +7,7 @@ import dev.zontreck.libzontreck.items.ModItems; import dev.zontreck.libzontreck.menus.ChestGUIMenu; import dev.zontreck.libzontreck.networking.ModMessages; import dev.zontreck.libzontreck.networking.packets.S2CCloseChestGUI; +import dev.zontreck.libzontreck.util.ChatHelpers; import dev.zontreck.libzontreck.util.ServerUtilities; import dev.zontreck.libzontreck.vectors.Vector2; import dev.zontreck.libzontreck.vectors.Vector2i; @@ -198,7 +199,7 @@ public class ChestGUI { updateUtilityButtons(); MinecraftForge.EVENT_BUS.post(new OpenGUIEvent(id, player, this)); - NetworkHooks.openScreen(ServerUtilities.getPlayerByID(player.toString()), new SimpleMenuProvider(ChestGUIMenu.getServerMenu(this), Component.literal((MenuTitle != "") ? MenuTitle : "No Title"))); + NetworkHooks.openScreen(ServerUtilities.getPlayerByID(player.toString()), new SimpleMenuProvider(ChestGUIMenu.getServerMenu(this), ChatHelpers.macro((MenuTitle != "") ? MenuTitle : "No Title"))); } } diff --git a/src/main/java/dev/zontreck/libzontreck/items/CreativeModeTabs.java b/src/main/java/dev/zontreck/libzontreck/items/CreativeModeTabs.java deleted file mode 100644 index 5a24937..0000000 --- a/src/main/java/dev/zontreck/libzontreck/items/CreativeModeTabs.java +++ /dev/null @@ -1,46 +0,0 @@ -package dev.zontreck.libzontreck.items; - -import dev.zontreck.libzontreck.LibZontreck; -import net.minecraft.core.registries.Registries; -import net.minecraft.network.chat.Component; -import net.minecraft.world.item.CreativeModeTab; -import net.minecraft.world.item.Item; -import net.minecraft.world.item.Items; -import net.minecraft.world.level.ItemLike; -import net.minecraftforge.eventbus.api.IEventBus; -import net.minecraftforge.fml.common.Mod; -import net.minecraftforge.registries.DeferredRegister; -import net.minecraftforge.registries.ForgeRegistries; -import net.minecraftforge.registries.RegistryObject; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Supplier; - -//@Mod.EventBusSubscriber(modid = LibZontreck.MOD_ID, bus = Mod.EventBusSubscriber.Bus.MOD) -public class CreativeModeTabs -{ - public static final DeferredRegister REGISTRY = DeferredRegister.create(Registries.CREATIVE_MODE_TAB, LibZontreck.MOD_ID); - - public static final List> LZ_MOD_ITEMS = new ArrayList<>(); - - public static final RegistryObject LIBZONTRECK_TAB = REGISTRY.register("libzontreck", ()->CreativeModeTab.builder() - .title(Component.translatable("itemGroup.tabs.libzontreck")) - .icon(Items.BARRIER::getDefaultInstance) - .displayItems((display,output)->LZ_MOD_ITEMS.forEach(it->output.accept(it.get()))) - .build() - ); - - public static RegistryObject addToLZTab(RegistryObject item) - { - LZ_MOD_ITEMS.add(item); - return item; - } - - - public static void register(IEventBus bus) - { - REGISTRY.register(bus); - } - -} diff --git a/src/main/java/dev/zontreck/libzontreck/util/SNbtIo.java b/src/main/java/dev/zontreck/libzontreck/util/SNbtIo.java new file mode 100644 index 0000000..120a93e --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/util/SNbtIo.java @@ -0,0 +1,45 @@ +package dev.zontreck.libzontreck.util; + +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import dev.zontreck.ariaslib.util.FileIO; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtUtils; + +import java.io.File; +import java.nio.file.Path; + +/** + * Provides helpers for reading and writing snbt to file + */ +public class SNbtIo +{ + /** + * Read the file at the path, and deserialize from snbt + * @param path The file to load + * @return The deserialized compound tag, or a blank tag + */ + public static CompoundTag loadSnbt(Path path) + { + if(!path.toFile().exists()) + return new CompoundTag(); + else { + File fi = path.toFile(); + try { + return NbtUtils.snbtToStructure(FileIO.readFile(fi.getAbsolutePath())); + } catch (CommandSyntaxException e) { + return new CompoundTag(); + } + } + } + + /** + * Writes the tag to the file specified + * @param path The file to write + * @param tag The tag to serialize + */ + public static void writeSnbt(Path path, CompoundTag tag) + { + String snbt = NbtUtils.structureToSnbt(tag); + FileIO.writeFile(path.toFile().getAbsolutePath(), snbt); + } +} \ No newline at end of file diff --git a/src/main/java/dev/zontreck/libzontreck/util/heads/CreditsEntry.java b/src/main/java/dev/zontreck/libzontreck/util/heads/CreditsEntry.java index 67a92a0..2c228bf 100644 --- a/src/main/java/dev/zontreck/libzontreck/util/heads/CreditsEntry.java +++ b/src/main/java/dev/zontreck/libzontreck/util/heads/CreditsEntry.java @@ -4,6 +4,7 @@ import dev.zontreck.libzontreck.chat.ChatColor; import dev.zontreck.libzontreck.lore.ExtraLore; import dev.zontreck.libzontreck.lore.LoreContainer; import dev.zontreck.libzontreck.lore.LoreEntry; +import dev.zontreck.libzontreck.util.ChatHelpers; import dev.zontreck.libzontreck.util.heads.HeadCache.HeadCacheItem; import net.minecraft.network.chat.Component; import net.minecraft.world.item.ItemStack; @@ -28,7 +29,7 @@ public class CreditsEntry { public ItemStack compile() { ItemStack stack = player.getAsItem(""); - stack.setHoverName(Component.literal(name)); + stack.setHoverName(ChatHelpers.macro(name)); LoreContainer contain = new LoreContainer(stack); contain.clear(); LoreEntry.Builder builder = new LoreEntry.Builder(); From e4b59167f6a8dc3b0b13f08e8703631d1f326563 Mon Sep 17 00:00:00 2001 From: zontreck Date: Thu, 12 Sep 2024 15:40:26 -0700 Subject: [PATCH 08/21] Update build files --- build.gradle | 47 ++++++++++++----------- gradle.properties | 17 ++++---- gradle/wrapper/gradle-wrapper.jar | Bin 60756 -> 62076 bytes gradle/wrapper/gradle-wrapper.properties | 3 +- settings.gradle | 12 +++++- 5 files changed, 47 insertions(+), 32 deletions(-) diff --git a/build.gradle b/build.gradle index 6321e02..3e6d508 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,7 @@ plugins { id 'idea' id 'maven-publish' id 'java-library' - id 'net.minecraftforge.gradle' version '5.1.+' + id 'net.minecraftforge.gradle' version '[6.0,6.2)' id 'org.parchmentmc.librarian.forgegradle' version '1.+' } @@ -39,7 +39,7 @@ minecraft { // See more information here: https://github.com/MinecraftForge/MCPConfig/blob/master/Mojang.md // // Parchment is an unofficial project maintained by ParchmentMC, separate from MinecraftForge - // Additional setup is needed to use their mappings: https://parchmentmc.org/docs/getting-started + // Additional setup is needed to use their mappings: https://github.com/ParchmentMC/Parchment/wiki/Getting-Started // // Use non-default mappings at your own risk. They may not always work. // Simply re-run your setup task after changing the mappings to update your workspace. @@ -53,7 +53,7 @@ minecraft { // This property allows configuring Gradle's ProcessResources task(s) to run on IDE output locations before launching the game. // It is REQUIRED to be set to true for this template to function. // See https://docs.gradle.org/current/dsl/org.gradle.language.jvm.tasks.ProcessResources.html - //copyIdeResources = true + copyIdeResources = true // When true, this property will add the folder name of all declared run configurations to generated IDE run configurations. // The folder name can be set on a run configuration using the "folderName" property. @@ -87,6 +87,9 @@ minecraft { // Please read: https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levels property 'forge.logging.console.level', 'debug' + // Comma-separated list of namespaces to load gametests from. Empty = all namespaces. + property 'forge.enabledGameTestNamespaces', mod_id + mods { "${mod_id}" { source sourceSets.main @@ -125,11 +128,7 @@ minecraft { sourceSets.main.resources { srcDir 'src/generated/resources' } repositories { - //mavenCentral() - maven { - name = "Aria's Creations Caches" - url = "https://maven.zontreck.com/repository/internal" - } + mavenCentral() // Put repositories for dependencies here // ForgeGradle automatically adds the Forge maven and Maven Central for you @@ -145,7 +144,7 @@ repositories { maven { name = "zontreck Maven" - url = "https://maven.zontreck.com/repository/zontreck" + url = "https://git.zontreck.com/api/packages/AriasCreations/maven" } } @@ -187,20 +186,21 @@ dependencies { // A missing property will result in an error. Properties are expanded using ${} Groovy notation. // When "copyIdeResources" is enabled, this will also run before the game launches in IDE environments. // See https://docs.gradle.org/current/dsl/org.gradle.language.jvm.tasks.ProcessResources.html -/*tasks.named('processResources', ProcessResources).configure { +tasks.named('processResources', ProcessResources).configure { var replaceProperties = [ - minecraft_version : minecraft_version, minecraft_version_range: minecraft_version_range, - forge_version : forge_version, forge_version_range: forge_version_range, + minecraft_version: minecraft_version, minecraft_version_range: minecraft_version_range, + forge_version: forge_version, forge_version_range: forge_version_range, loader_version_range: loader_version_range, - mod_id : mod_id, mod_name: mod_name, mod_license: mod_license, mod_version: mod_version, - mod_authors : mod_authors, mod_description: mod_description, + mod_id: mod_id, mod_name: mod_name, mod_license: mod_license, mod_version: mod_version, + mod_authors: mod_authors, mod_description: mod_description, ] inputs.properties replaceProperties filesMatching(['META-INF/mods.toml', 'pack.mcmeta']) { expand replaceProperties + [project: project] } -}*/ +} + // Example for how to get properties into the manifest for reading at runtime. tasks.named('jar', Jar).configure { @@ -231,7 +231,9 @@ tasks.named('jar', Jar).configure { // } -def MAVEN_PASSWORD_PROPERTY = "AriasCreationsMavenPassword" +def MAVEN_PASSWORD = "AriasCreationsMavenPassword" +def MAVEN_USER = "AriasCreationsMavenUser" + publishing { publications { mavenJava(MavenPublication) { @@ -240,20 +242,21 @@ publishing { artifact javadocJar } } + repositories { maven { - url = "https://maven.zontreck.com/repository/zontreck" + url = "https://git.zontreck.com/api/packages/AriasCreations/maven" name = "ariascreations" - if (project.findProperty(MAVEN_PASSWORD_PROPERTY) != null) { - credentials { - username = "admin" - password = project.findProperty(MAVEN_PASSWORD_PROPERTY) - } + + credentials { + username = project.findProperty(MAVEN_USER) + password = project.findProperty(MAVEN_PASSWORD) } } } } + tasks.withType(JavaCompile).configureEach { options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation } diff --git a/gradle.properties b/gradle.properties index d09bf23..6de7929 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,11 +4,6 @@ org.gradle.jvmargs=-Xmx3G org.gradle.daemon=false -parchment_version=2022.11.27 -# luckperms_api_version=5.4 - -libac=1.4.46 -eventsbus=1.0.45 ## Environment Properties # The Minecraft version must agree with the Forge version to get a valid artifact @@ -16,9 +11,9 @@ minecraft_version=1.19.2 # The Minecraft version range can use any release version of Minecraft as bounds. # Snapshots, pre-releases, and release candidates are not guaranteed to sort properly # as they do not follow standard versioning conventions. -minecraft_version_range=[1.19,1.20) +minecraft_version_range=[1.19.2,1.20) # The Forge version must agree with the Minecraft version to get a valid artifact -forge_version=43.3.0 +forge_version=43.4.2 # The Forge version range can use any version of Forge as bounds or match the loader version range forge_version_range=[43,) # The loader version range can only use the major version of Forge/FML as bounds @@ -38,6 +33,14 @@ loader_version_range=[43,) # Parchment is an unofficial project maintained by ParchmentMC, separate from Minecraft Forge. # Additional setup is needed to use their mappings, see https://parchmentmc.org/docs/getting-started mapping_channel=parchment +# The mapping version to query from the mapping channel. +# This must match the format required by the mapping channel. +parchment_version=2022.11.27 +# luckperms_api_version=5.4 + +libac=1.5.33 +eventsbus=1.0.47 +## Environment Properties ## Mod Properties diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 249e5832f090a2944b7473328c07c9755baa3196..c1962a79e29d3e0ab67b14947c167a862655af9b 100644 GIT binary patch delta 39834 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 G$^QWPgI3l6 delta 38507 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 Date: Thu, 12 Sep 2024 15:54:37 -0700 Subject: [PATCH 09/21] Backport 1.20 changes and improvements --- .../dev/zontreck/libzontreck/LibZontreck.java | 52 +- .../dev/zontreck/libzontreck/api/Vector2.java | 138 ++ .../dev/zontreck/libzontreck/api/Vector3.java | 158 +++ .../libzontreck/blocks/BlockCustomVoxels.java | 22 + .../libzontreck/blocks/BlockImitation.java | 95 ++ .../blocks/PartialTransparentBlock.java | 18 + .../blocks/PartialTransparentSlabBlock.java | 19 + .../libzontreck/blocks/RedstoneBlock.java | 98 ++ .../libzontreck/blocks/RotatableBlock.java | 39 + .../blocks/RotatableBlockCustomVoxels.java | 35 + .../libzontreck/chestgui/ChestGUI.java | 10 +- .../libzontreck/chestgui/ChestGUIButton.java | 4 +- .../libzontreck/config/ServerConfig.java | 39 + .../config/sections/DatabaseSection.java | 76 ++ .../libzontreck/currency/Account.java | 5 +- .../zontreck/libzontreck/currency/Bank.java | 32 +- .../events/BankAccountCreatedEvent.java | 2 +- .../currency/events/BankReadyEvent.java | 2 +- .../currency/events/TransactionEvent.java | 8 +- .../events/TransactionHistoryFlushEvent.java | 2 +- .../currency/events/WalletUpdatedEvent.java | 2 +- .../libzontreck/edlibmc/Auxiliaries.java | 590 +++++++++ .../libzontreck/edlibmc/Containers.java | 138 ++ .../libzontreck/edlibmc/Crafting.java | 440 +++++++ .../libzontreck/edlibmc/Fluidics.java | 485 +++++++ .../zontreck/libzontreck/edlibmc/Guis.java | 466 +++++++ .../libzontreck/edlibmc/Inventories.java | 1134 +++++++++++++++++ .../libzontreck/edlibmc/Networking.java | 409 ++++++ .../edlibmc/OptionalRecipeCondition.java | 191 +++ .../zontreck/libzontreck/edlibmc/Overlay.java | 165 +++ .../zontreck/libzontreck/edlibmc/README.md | 4 + .../libzontreck/edlibmc/Registries.java | 263 ++++ .../libzontreck/edlibmc/RfEnergy.java | 180 +++ .../libzontreck/edlibmc/RsSignals.java | 42 + .../libzontreck/edlibmc/SidedProxy.java | 122 ++ .../libzontreck/edlibmc/SlabSliceBlock.java | 228 ++++ .../libzontreck/edlibmc/StandardBlocks.java | 698 ++++++++++ .../edlibmc/StandardDoorBlock.java | 175 +++ .../edlibmc/StandardEntityBlocks.java | 72 ++ .../edlibmc/StandardFenceBlock.java | 203 +++ .../edlibmc/StandardStairsBlock.java | 59 + .../libzontreck/edlibmc/TooltipDisplay.java | 130 ++ .../libzontreck/edlibmc/VariantSlabBlock.java | 220 ++++ .../libzontreck/edlibmc/VariantWallBlock.java | 200 +++ .../BlockRestoreQueueRegistrationEvent.java | 20 + .../events/ForgeEventHandlers.java | 5 +- .../events/RegisterMigrationsEvent.java | 22 + .../events/RegisterPacketsEvent.java | 16 - .../libzontreck/events/TeleportEvent.java | 32 + .../items/InputItemStackHandler.java | 47 + .../items/OutputItemStackHandler.java | 45 + .../memory/{ => player}/PlayerComponent.java | 6 +- .../memory/{ => player}/PlayerContainer.java | 6 +- .../{ => player}/VolatilePlayerStorage.java | 2 +- .../memory/world/BlockRestore.java | 19 + .../memory/world/BlockRestoreQueue.java | 333 +++++ .../world/BlockRestoreQueueRegistry.java | 65 + .../memory/world/BlockRestoreRunner.java | 56 + .../memory/world/DatabaseMigrations.java | 208 +++ .../memory/world/DatabaseUploadRunner.java | 26 + .../memory/world/DatabaseWrapper.java | 163 +++ .../memory/world/PrimitiveBlock.java | 80 ++ .../memory/world/SaveDataCoordinates.java | 47 + .../memory/world/SaveDataFactory.java | 267 ++++ .../libzontreck/memory/world/SavedBlock.java | 112 ++ .../memory/world/SortedBlockQueue.java | 41 + .../libzontreck/networking/ModMessages.java | 7 - .../libzontreck/networking/NetworkEvents.java | 12 - .../libzontreck/profiles/Profile.java | 2 +- .../zontreck/libzontreck/util/BlocksUtil.java | 6 +- .../libzontreck/util/PositionUtil.java | 156 +++ .../dev/zontreck/libzontreck/util/SNbtIo.java | 3 +- .../libzontreck/util/ServerUtilities.java | 6 +- .../zontreck/libzontreck/util/TagUtils.java | 45 + .../libzontreck/util/heads/HeadCache.java | 6 +- .../libzontreck/vectors/ChunkPos.java | 10 +- .../libzontreck/vectors/NonAbsVector3.java | 2 +- .../zontreck/libzontreck/vectors/Points.java | 10 +- .../zontreck/libzontreck/vectors/Vector2.java | 120 -- .../libzontreck/vectors/Vector2f.java | 200 +++ .../libzontreck/vectors/Vector2i.java | 151 ++- .../zontreck/libzontreck/vectors/Vector3.java | 285 ----- .../libzontreck/vectors/Vector3d.java | 253 ++++ .../libzontreck/vectors/Vector3i.java | 246 ++++ .../libzontreck/vectors/WorldPosition.java | 37 +- src/main/resources/META-INF/mods.toml | 42 +- src/main/resources/pack.mcmeta | 14 +- 87 files changed, 10114 insertions(+), 587 deletions(-) create mode 100644 src/main/java/dev/zontreck/libzontreck/api/Vector2.java create mode 100644 src/main/java/dev/zontreck/libzontreck/api/Vector3.java create mode 100644 src/main/java/dev/zontreck/libzontreck/blocks/BlockCustomVoxels.java create mode 100644 src/main/java/dev/zontreck/libzontreck/blocks/BlockImitation.java create mode 100644 src/main/java/dev/zontreck/libzontreck/blocks/PartialTransparentBlock.java create mode 100644 src/main/java/dev/zontreck/libzontreck/blocks/PartialTransparentSlabBlock.java create mode 100644 src/main/java/dev/zontreck/libzontreck/blocks/RedstoneBlock.java create mode 100644 src/main/java/dev/zontreck/libzontreck/blocks/RotatableBlock.java create mode 100644 src/main/java/dev/zontreck/libzontreck/blocks/RotatableBlockCustomVoxels.java create mode 100644 src/main/java/dev/zontreck/libzontreck/config/ServerConfig.java create mode 100644 src/main/java/dev/zontreck/libzontreck/config/sections/DatabaseSection.java create mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/Auxiliaries.java create mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/Containers.java create mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/Crafting.java create mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/Fluidics.java create mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/Guis.java create mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/Inventories.java create mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/Networking.java create mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/OptionalRecipeCondition.java create mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/Overlay.java create mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/README.md create mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/Registries.java create mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/RfEnergy.java create mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/RsSignals.java create mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/SidedProxy.java create mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/SlabSliceBlock.java create mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/StandardBlocks.java create mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/StandardDoorBlock.java create mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/StandardEntityBlocks.java create mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/StandardFenceBlock.java create mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/StandardStairsBlock.java create mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/TooltipDisplay.java create mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/VariantSlabBlock.java create mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/VariantWallBlock.java create mode 100644 src/main/java/dev/zontreck/libzontreck/events/BlockRestoreQueueRegistrationEvent.java create mode 100644 src/main/java/dev/zontreck/libzontreck/events/RegisterMigrationsEvent.java delete mode 100644 src/main/java/dev/zontreck/libzontreck/events/RegisterPacketsEvent.java create mode 100644 src/main/java/dev/zontreck/libzontreck/events/TeleportEvent.java create mode 100644 src/main/java/dev/zontreck/libzontreck/items/InputItemStackHandler.java create mode 100644 src/main/java/dev/zontreck/libzontreck/items/OutputItemStackHandler.java rename src/main/java/dev/zontreck/libzontreck/memory/{ => player}/PlayerComponent.java (86%) rename src/main/java/dev/zontreck/libzontreck/memory/{ => player}/PlayerContainer.java (70%) rename src/main/java/dev/zontreck/libzontreck/memory/{ => player}/VolatilePlayerStorage.java (97%) create mode 100644 src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestore.java create mode 100644 src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java create mode 100644 src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueueRegistry.java create mode 100644 src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java create mode 100644 src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java create mode 100644 src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseUploadRunner.java create mode 100644 src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseWrapper.java create mode 100644 src/main/java/dev/zontreck/libzontreck/memory/world/PrimitiveBlock.java create mode 100644 src/main/java/dev/zontreck/libzontreck/memory/world/SaveDataCoordinates.java create mode 100644 src/main/java/dev/zontreck/libzontreck/memory/world/SaveDataFactory.java create mode 100644 src/main/java/dev/zontreck/libzontreck/memory/world/SavedBlock.java create mode 100644 src/main/java/dev/zontreck/libzontreck/memory/world/SortedBlockQueue.java create mode 100644 src/main/java/dev/zontreck/libzontreck/util/PositionUtil.java create mode 100644 src/main/java/dev/zontreck/libzontreck/util/TagUtils.java delete mode 100644 src/main/java/dev/zontreck/libzontreck/vectors/Vector2.java create mode 100644 src/main/java/dev/zontreck/libzontreck/vectors/Vector2f.java delete mode 100644 src/main/java/dev/zontreck/libzontreck/vectors/Vector3.java create mode 100644 src/main/java/dev/zontreck/libzontreck/vectors/Vector3d.java create mode 100644 src/main/java/dev/zontreck/libzontreck/vectors/Vector3i.java diff --git a/src/main/java/dev/zontreck/libzontreck/LibZontreck.java b/src/main/java/dev/zontreck/libzontreck/LibZontreck.java index bc5fe5e..7a8271e 100644 --- a/src/main/java/dev/zontreck/libzontreck/LibZontreck.java +++ b/src/main/java/dev/zontreck/libzontreck/LibZontreck.java @@ -3,32 +3,39 @@ package dev.zontreck.libzontreck; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.sql.SQLException; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.UUID; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; -import dev.zontreck.ariaslib.util.DelayedExecutorService; -import dev.zontreck.eventsbus.Bus; import dev.zontreck.libzontreck.chestgui.ChestGUIRegistry; +import dev.zontreck.libzontreck.config.ServerConfig; import dev.zontreck.libzontreck.currency.Bank; import dev.zontreck.libzontreck.currency.CurrencyHelper; +import dev.zontreck.libzontreck.events.BlockRestoreQueueRegistrationEvent; import dev.zontreck.libzontreck.items.ModItems; +import dev.zontreck.libzontreck.memory.world.BlockRestoreQueue; +import dev.zontreck.libzontreck.memory.world.BlockRestoreQueueRegistry; +import dev.zontreck.libzontreck.memory.world.DatabaseMigrations; +import dev.zontreck.libzontreck.memory.world.DatabaseWrapper; import dev.zontreck.libzontreck.menus.ChestGUIScreen; import dev.zontreck.libzontreck.types.ModMenuTypes; import dev.zontreck.libzontreck.networking.NetworkEvents; import net.minecraft.client.gui.screens.MenuScreens; +import net.minecraft.server.level.ServerLevel; import org.slf4j.Logger; import com.mojang.logging.LogUtils; import dev.zontreck.libzontreck.commands.Commands; import dev.zontreck.libzontreck.events.ForgeEventHandlers; -import dev.zontreck.libzontreck.memory.VolatilePlayerStorage; +import dev.zontreck.libzontreck.memory.player.VolatilePlayerStorage; import dev.zontreck.libzontreck.networking.ModMessages; import dev.zontreck.libzontreck.profiles.Profile; import dev.zontreck.libzontreck.util.FileTreeDatastore; -import net.minecraft.server.MinecraftServer; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.server.ServerStartedEvent; import net.minecraftforge.event.server.ServerStoppingEvent; @@ -45,7 +52,6 @@ public class LibZontreck { public static final Logger LOGGER = LogUtils.getLogger(); public static final String MOD_ID = "libzontreck"; public static final Map PROFILES; - public static MinecraftServer THE_SERVER; public static VolatilePlayerStorage playerStorage; public static boolean ALIVE=true; public static final String FILESTORE = FileTreeDatastore.get(); @@ -55,6 +61,7 @@ public class LibZontreck { public static final UUID NULL_ID; public static boolean LIBZONTRECK_SERVER_AVAILABLE=false; + public static ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); public static LogicalSide CURRENT_SIDE; @@ -81,6 +88,8 @@ public class LibZontreck { IEventBus bus = FMLJavaModLoadingContext.get().getModEventBus(); // Register the setup method for modloading bus.addListener(this::setup); + + ServerConfig.init(); MinecraftForge.EVENT_BUS.register(this); @@ -89,14 +98,14 @@ public class LibZontreck { MinecraftForge.EVENT_BUS.register(new NetworkEvents()); MinecraftForge.EVENT_BUS.register(ChestGUIRegistry.class); - Bus.Reset(); ModMenuTypes.REGISTRY.register(bus); //CreativeModeTabs.register(bus); ModItems.register(bus); - Bus.Register(CurrencyHelper.class, null); - Bus.Register(Bank.class, null); + MinecraftForge.EVENT_BUS.register(CurrencyHelper.class); + MinecraftForge.EVENT_BUS.register(Bank.class); + } private void setup(final FMLCommonSetupEvent event) @@ -108,16 +117,39 @@ public class LibZontreck { @SubscribeEvent public void onServerStarted(final ServerStartedEvent event) { - THE_SERVER = event.getServer(); ALIVE=true; + + ServerConfig.init(); + try { + DatabaseWrapper.start(); + }catch(RuntimeException e) { + LOGGER.warn("Database not configured properly, it will not be available."); + DatabaseWrapper.invalidate(); + } + CURRENT_SIDE = LogicalSide.SERVER; + + MinecraftForge.EVENT_BUS.post(new BlockRestoreQueueRegistrationEvent()); + + for(ServerLevel level : event.getServer().getAllLevels()) + { + // Queues have been registered, but we now need to initialize the queue's data from saveddata + BlockRestoreQueueRegistry.init(level); + } + + if(!DatabaseWrapper.hasDB)return; + + try { + DatabaseMigrations.initMigrations(); + } catch (SQLException e) { + e.printStackTrace(); + } } @SubscribeEvent public void onServerStopping(final ServerStoppingEvent ev) { ALIVE=false; - DelayedExecutorService.stop(); Iterator iProfile = PROFILES.values().iterator(); while(iProfile.hasNext()) diff --git a/src/main/java/dev/zontreck/libzontreck/api/Vector2.java b/src/main/java/dev/zontreck/libzontreck/api/Vector2.java new file mode 100644 index 0000000..e251c81 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/api/Vector2.java @@ -0,0 +1,138 @@ +package dev.zontreck.libzontreck.api; + +import dev.zontreck.libzontreck.vectors.Vector2f; +import dev.zontreck.libzontreck.vectors.Vector2i; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.phys.Vec2; + +public interface Vector2 extends Cloneable, Comparable> +{ + /** + * Converts the current Vector2 representation into a minecraft Vec2 + * @return Minecraft equivalent Vec2 + */ + Vec2 asMinecraftVector(); + + /** + * Parses a string in serialized format. + * @param vector2 Expects it in the same format returned by Vector2#toString + * @return New Vector2, or a null Vector2 initialized with zeros if invalid data + */ + static Vector2 parseString(String vector2){ + throw new UnsupportedOperationException("This method is not implemented by this implementation"); + } + + /** + * Copies the values to a new and detached instance + * @return New Vector2 + */ + Vector2 Clone(); + + /** + * Saves the X and Y positions to a NBT tag + * @return NBT compound tag + */ + CompoundTag serialize(); + + /** + * Loads a Vector2 from a NBT tag + * @param tag The NBT tag to load + */ + static Vector2 deserialize(CompoundTag tag) + { + throw new UnsupportedOperationException("This method is not implemented by this implementation"); + } + + /** + * Compares the two vector2 instances + * @param other The position to check + * @return True if same position + */ + boolean Same(Vector2 other); + + /** + * True if the current position is inside the two points + * @param point1 Lowest point + * @param point2 Hightest Point + * @return True if inside + */ + boolean Inside(Vector2 point1, Vector2 point2); + + /** + * Converts, if necessary, to Vector2d + * @return A vector2d instance + */ + Vector2f asVector2f(); + + /** + * Converts, if necessary, to Vector2i + * @return A vector2i instance + */ + Vector2i asVector2i(); + + /** + * Checks if the current vector is greater than the provided one + * @param other The other vector to check + * @return True if greater + */ + boolean greater(Vector2 other); + + /** + * Checks if the current vector is less than the provided one + * @param other The vector to check + * @return True if less than other + */ + boolean less(Vector2 other); + + /** + * Alias for Vector2#same + * @param other Vector to check + * @return True if same position + */ + boolean equal(Vector2 other); + + /** + * Adds the two vectors together + * @param other Vector to add + * @return New instance after adding the other vector + */ + Vector2 add(Vector2 other); + + /** + * Subtracts the other vector from this one + * @param other Vector to subtract + * @return New instance after subtracting + */ + Vector2 subtract(Vector2 other); + + /** + * Calculates the distance between the two vectors + * @param other + * @return The distance + */ + double distance(Vector2 other); + + /** + * Increments the Y axis by 1 + * @return New instance + */ + Vector2 moveUp(); + + /** + * Decrements the Y axis by 1 + * @return New instance + */ + Vector2 moveDown(); + + /** + * Gets the X value + * @return X + */ + T getX(); + + /** + * Gets the Y value + * @return Y + */ + T getY(); +} diff --git a/src/main/java/dev/zontreck/libzontreck/api/Vector3.java b/src/main/java/dev/zontreck/libzontreck/api/Vector3.java new file mode 100644 index 0000000..ffca2a8 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/api/Vector3.java @@ -0,0 +1,158 @@ +package dev.zontreck.libzontreck.api; + +import dev.zontreck.libzontreck.vectors.Vector3d; +import dev.zontreck.libzontreck.vectors.Vector3i; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Vec3i; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.phys.Vec3; + +public interface Vector3 extends Cloneable, Comparable> +{ + /** + * Converts the current Vector3 representation into a minecraft Vec3 + * @return Minecraft equivalent Vec3 + */ + Vec3 asMinecraftVector(); + + /** + * Converts to a vec3i position + * @return Equivalent vec3i + */ + Vec3i asVec3i(); + + /** + * Converts to a block position + * @return Equivalent block position + */ + BlockPos asBlockPos(); + + /** + * Parses a string in serialized format. + * @param vector3 Expects it in the same format returned by Vector3#toString + * @return New Vector3, or a null Vector3 initialized with zeros if invalid data + */ + static Vector3 parseString(String vector3){ + throw new UnsupportedOperationException("This method is not implemented by this implementation"); + } + + /** + * Copies the values to a new and detached instance + * @return New Vector3 + */ + Vector3 Clone(); + + /** + * Saves the X, Y, and Z positions to a NBT tag + * @return NBT compound tag + */ + CompoundTag serialize(); + + /** + * Loads a Vector3 from a NBT tag + * @param tag The NBT tag to load + */ + static Vector3 deserialize(CompoundTag tag) + { + throw new UnsupportedOperationException("This method is not implemented by this implementation"); + } + + /** + * Compares the two vector3 instances + * @param other The position to check + * @return True if same position + */ + boolean Same(Vector3 other); + + /** + * True if the current position is inside the two points + * @param point1 Lowest point + * @param point2 Hightest Point + * @return True if inside + */ + boolean Inside(Vector3 point1, Vector3 point2); + + /** + * Converts, if necessary, to Vector3d + * @return A vector2d instance + */ + Vector3d asVector3d(); + + /** + * Converts, if necessary, to Vector3i + * @return A vector3i instance + */ + Vector3i asVector3i(); + + /** + * Checks if the current vector is greater than the provided one + * @param other The other vector to check + * @return True if greater + */ + boolean greater(Vector3 other); + + /** + * Checks if the current vector is less than the provided one + * @param other The vector to check + * @return True if less than other + */ + boolean less(Vector3 other); + + /** + * Alias for Vector3#same + * @param other Vector to check + * @return True if same position + */ + boolean equal(Vector3 other); + + /** + * Adds the two vectors together + * @param other Vector to add + * @return New instance after adding the other vector + */ + Vector3 add(Vector3 other); + + /** + * Subtracts the other vector from this one + * @param other Vector to subtract + * @return New instance after subtracting + */ + Vector3 subtract(Vector3 other); + + /** + * Calculates the distance between the two vectors + * @param other + * @return The distance + */ + double distance(Vector3 other); + + /** + * Increments the Y axis by 1 + * @return New instance + */ + Vector3 moveUp(); + + /** + * Decrements the Y axis by 1 + * @return New instance + */ + Vector3 moveDown(); + + /** + * Gets the X value + * @return X + */ + T getX(); + + /** + * Gets the Y value + * @return Y + */ + T getY(); + + /** + * Gets the Z value + * @return Z + */ + T getZ(); +} diff --git a/src/main/java/dev/zontreck/libzontreck/blocks/BlockCustomVoxels.java b/src/main/java/dev/zontreck/libzontreck/blocks/BlockCustomVoxels.java new file mode 100644 index 0000000..f567242 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/blocks/BlockCustomVoxels.java @@ -0,0 +1,22 @@ +package dev.zontreck.libzontreck.blocks; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.VoxelShape; + +public class BlockCustomVoxels extends PartialTransparentBlock +{ + private VoxelShape superShape; + public BlockCustomVoxels(Properties p_54120_, VoxelShape shape) { + super(p_54120_); + this.superShape = shape; + } + + @Override + public VoxelShape getShape(BlockState p_60555_, BlockGetter p_60556_, BlockPos p_60557_, CollisionContext p_60558_) { + return superShape; + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/blocks/BlockImitation.java b/src/main/java/dev/zontreck/libzontreck/blocks/BlockImitation.java new file mode 100644 index 0000000..ab23bd1 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/blocks/BlockImitation.java @@ -0,0 +1,95 @@ +package dev.zontreck.libzontreck.blocks; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.RandomSource; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; + +/** + * Code partially taken from HT's TreeChop + *

+ * Source Material + */ +public abstract class BlockImitation extends Block +{ + public BlockImitation(Properties pProperties) { + super(pProperties); + } + + + public abstract BlockState getImitatedBlockState(BlockGetter level, BlockPos pos); + + public abstract void updateImitation(BlockState newState, BlockGetter level, BlockPos pos); + + @Override + public void animateTick(BlockState blockState, Level level, BlockPos pos, RandomSource random) { + BlockState imitatedBlockState = getImitatedBlockState(level, pos); + imitatedBlockState.getBlock().animateTick(imitatedBlockState, level, pos, random); + } + + @Override + public void stepOn(Level level, BlockPos pos, BlockState blockState, Entity entity) { + BlockState imitatedBlockState = getImitatedBlockState(level, pos); + imitatedBlockState.getBlock().stepOn(level, pos, imitatedBlockState, entity); + } + + @Override + public void fallOn(Level level, BlockState blockState, BlockPos pos, Entity entity, float speed) { + BlockState imitatedBlockState = getImitatedBlockState(level, pos); + imitatedBlockState.getBlock().fallOn(level, imitatedBlockState, pos, entity, speed); + } + + @Override + public ItemStack getCloneItemStack(BlockGetter level, BlockPos pos, BlockState blockState) { + BlockState imitatedBlockState = getImitatedBlockState(level, pos); + return imitatedBlockState.getBlock().getCloneItemStack(level, pos, imitatedBlockState); + } + + @Override + public void handlePrecipitation(BlockState blockState, Level level, BlockPos pos, Biome.Precipitation precipitation) { + BlockState imitatedBlockState = getImitatedBlockState(level, pos); + imitatedBlockState.getBlock().handlePrecipitation(imitatedBlockState, level, pos, precipitation); + } + + @Override + public int getLightBlock(BlockState blockState, BlockGetter level, BlockPos pos) { + return super.getLightBlock(blockState, level, pos); + } + + @Override + public float getShadeBrightness(BlockState blockState, BlockGetter level, BlockPos pos) { + return super.getShadeBrightness(blockState, level, pos); + } + + @Override + public int getAnalogOutputSignal(BlockState blockState, Level level, BlockPos pos) { + return getImitatedBlockState(level, pos).getAnalogOutputSignal(level, pos); + } + + @Override + public void randomTick(BlockState blockState, ServerLevel level, BlockPos pos, RandomSource random) { + getImitatedBlockState(level, pos).randomTick(level, pos, random); + } + + @Override + public void tick(BlockState blockState, ServerLevel level, BlockPos pos, RandomSource random) { + getImitatedBlockState(level, pos).tick(level, pos, random); + } + + @Override + public int getSignal(BlockState blockState, BlockGetter level, BlockPos pos, Direction direction) { + return getImitatedBlockState(level, pos).getSignal(level, pos, direction); + } + + @Override + public int getDirectSignal(BlockState blockState, BlockGetter level, BlockPos pos, Direction direction) { + return getImitatedBlockState(level, pos).getDirectSignal(level, pos, direction); + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/blocks/PartialTransparentBlock.java b/src/main/java/dev/zontreck/libzontreck/blocks/PartialTransparentBlock.java new file mode 100644 index 0000000..0ee305d --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/blocks/PartialTransparentBlock.java @@ -0,0 +1,18 @@ +package dev.zontreck.libzontreck.blocks; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.block.AbstractGlassBlock; +import net.minecraft.world.level.block.state.BlockState; + +public class PartialTransparentBlock extends AbstractGlassBlock +{ + public PartialTransparentBlock(Properties p_48729_) { + super(p_48729_); + } + + @Override + public boolean propagatesSkylightDown(BlockState p_48740_, BlockGetter p_48741_, BlockPos p_48742_) { + return true; + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/blocks/PartialTransparentSlabBlock.java b/src/main/java/dev/zontreck/libzontreck/blocks/PartialTransparentSlabBlock.java new file mode 100644 index 0000000..ff74562 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/blocks/PartialTransparentSlabBlock.java @@ -0,0 +1,19 @@ +package dev.zontreck.libzontreck.blocks; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.block.AbstractGlassBlock; +import net.minecraft.world.level.block.SlabBlock; +import net.minecraft.world.level.block.state.BlockState; + +public class PartialTransparentSlabBlock extends SlabBlock +{ + public PartialTransparentSlabBlock(Properties p_48729_) { + super(p_48729_); + } + + @Override + public boolean propagatesSkylightDown(BlockState p_48740_, BlockGetter p_48741_, BlockPos p_48742_) { + return true; + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/blocks/RedstoneBlock.java b/src/main/java/dev/zontreck/libzontreck/blocks/RedstoneBlock.java new file mode 100644 index 0000000..6b498b6 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/blocks/RedstoneBlock.java @@ -0,0 +1,98 @@ +package dev.zontreck.libzontreck.blocks; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.level.block.state.properties.BooleanProperty; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public abstract class RedstoneBlock extends RotatableBlock +{ + public static final BooleanProperty INPUT_POWER = BooleanProperty.create("inputpower"); + public static final BooleanProperty POWERED = BlockStateProperties.POWERED; + private List sides; + + protected RedstoneBlock(Properties pProperties, Direction... validSides) { + super(pProperties); + sides=List.of(validSides); + } + + @Override + public BlockState getStateForPlacement(BlockPlaceContext pContext) { + return super.getStateForPlacement(pContext).setValue(INPUT_POWER, false).setValue(POWERED, false); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder pBuilder) { + super.createBlockStateDefinition(pBuilder); + pBuilder.add(INPUT_POWER, POWERED); + } + + @Override + public boolean canConnectRedstone(BlockState state, BlockGetter level, BlockPos pos, @Nullable Direction direction) { + if(sides.contains(direction)) return true; + else return false; + } + + private boolean redstoneIsActivated(LevelReader level, BlockPos pos) + { + if(level.hasNeighborSignal(pos)) + return true; + else return false; + } + + @Override + public int getSignal(BlockState p_60483_, BlockGetter p_60484_, BlockPos p_60485_, Direction p_60486_) { + if(!sides.contains(p_60486_)) return 0; + + if(p_60483_.getValue(POWERED)) + return 15; + else return 0; + } + + protected abstract void onRedstone(LevelReader level, BlockPos pos, boolean on); + protected abstract void onRedstoneInputChanged(LevelReader level, BlockPos pos, boolean on); + + @Override + public void onNeighborChange(BlockState state, LevelReader level, BlockPos pos, BlockPos neighbor) { + boolean rs = redstoneIsActivated(level, pos); + onRedstone(level, pos, rs); + boolean inp = state.getValue(INPUT_POWER); + state.setValue(INPUT_POWER, rs); + + if(inp != rs) + onRedstoneInputChanged(level, pos, rs); + } + + + @Override + public void neighborChanged(BlockState state, Level level, BlockPos pos, Block block, BlockPos other, boolean unknown) { + boolean rs = redstoneIsActivated(level, pos); + onRedstone(level, pos, rs); + boolean inp = state.getValue(INPUT_POWER); + state.setValue(INPUT_POWER, rs); + + if(inp != rs) + onRedstoneInputChanged(level, pos, rs); + } + + @Override + public void onPlace(BlockState state, Level level, BlockPos pos, BlockState p_60569_, boolean p_60570_) { + boolean rs = redstoneIsActivated(level, pos); + onRedstone(level, pos, rs); + boolean inp = state.getValue(INPUT_POWER); + state.setValue(INPUT_POWER, rs); + + if(inp != rs) + onRedstoneInputChanged(level, pos, rs); + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/blocks/RotatableBlock.java b/src/main/java/dev/zontreck/libzontreck/blocks/RotatableBlock.java new file mode 100644 index 0000000..8526891 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/blocks/RotatableBlock.java @@ -0,0 +1,39 @@ +package dev.zontreck.libzontreck.blocks; + +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.HorizontalDirectionalBlock; +import net.minecraft.world.level.block.Mirror; +import net.minecraft.world.level.block.Rotation; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; + +public class RotatableBlock extends HorizontalDirectionalBlock +{ + public RotatableBlock(Properties pProperties) { + super(pProperties); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder pBuilder) { + super.createBlockStateDefinition(pBuilder); + pBuilder.add(FACING); + } + + + @Override + public BlockState rotate(BlockState p_55115_, Rotation p_55116_) { + return p_55115_.setValue(FACING, p_55116_.rotate(p_55115_.getValue(FACING))); + } + + @Override + public BlockState mirror(BlockState p_55112_, Mirror p_55113_) { + return p_55112_.rotate(p_55113_.getRotation(p_55112_.getValue(FACING))); + } + + + @Override + public BlockState getStateForPlacement(BlockPlaceContext pContext) { + return defaultBlockState().setValue(FACING, pContext.getHorizontalDirection()); + } +} \ No newline at end of file diff --git a/src/main/java/dev/zontreck/libzontreck/blocks/RotatableBlockCustomVoxels.java b/src/main/java/dev/zontreck/libzontreck/blocks/RotatableBlockCustomVoxels.java new file mode 100644 index 0000000..6bf6ac4 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/blocks/RotatableBlockCustomVoxels.java @@ -0,0 +1,35 @@ +package dev.zontreck.libzontreck.blocks; + +import dev.zontreck.ariaslib.util.Maps; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.Shapes; +import net.minecraft.world.phys.shapes.VoxelShape; + +import java.util.HashMap; +import java.util.Map; + + +public class RotatableBlockCustomVoxels extends RotatableBlock +{ + private Map rotatedShapes = new HashMap<>(); + + public RotatableBlockCustomVoxels(Properties properties, VoxelShape north, VoxelShape south, VoxelShape east, VoxelShape west) { + super(properties); + rotatedShapes = Maps.of(new Maps.Entry<>(Direction.NORTH, north), new Maps.Entry<>(Direction.SOUTH, south), new Maps.Entry<>(Direction.WEST, west), new Maps.Entry<>(Direction.EAST, east), new Maps.Entry<>(Direction.NORTH, north), new Maps.Entry<>(Direction.DOWN, north)); + } + + @Override + public VoxelShape getShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext context) { + Direction facing = state.getValue(FACING); + return rotatedShapes.get(facing); + } + + @Override + public boolean propagatesSkylightDown(BlockState p_49928_, BlockGetter p_49929_, BlockPos p_49930_) { + return true; + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/chestgui/ChestGUI.java b/src/main/java/dev/zontreck/libzontreck/chestgui/ChestGUI.java index 826bc44..d41046b 100644 --- a/src/main/java/dev/zontreck/libzontreck/chestgui/ChestGUI.java +++ b/src/main/java/dev/zontreck/libzontreck/chestgui/ChestGUI.java @@ -9,10 +9,8 @@ import dev.zontreck.libzontreck.networking.ModMessages; import dev.zontreck.libzontreck.networking.packets.S2CCloseChestGUI; import dev.zontreck.libzontreck.util.ChatHelpers; import dev.zontreck.libzontreck.util.ServerUtilities; -import dev.zontreck.libzontreck.vectors.Vector2; +import dev.zontreck.libzontreck.vectors.Vector2f; import dev.zontreck.libzontreck.vectors.Vector2i; -import net.minecraft.network.chat.Component; -import net.minecraft.resources.ResourceLocation; import net.minecraft.world.SimpleMenuProvider; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; @@ -24,8 +22,6 @@ import net.minecraftforge.network.NetworkHooks; import java.util.ArrayList; import java.util.List; import java.util.UUID; -import java.util.concurrent.Callable; -import java.util.function.Function; /** * Zontreck's ChestGUI Interface @@ -199,7 +195,7 @@ public class ChestGUI { updateUtilityButtons(); MinecraftForge.EVENT_BUS.post(new OpenGUIEvent(id, player, this)); - NetworkHooks.openScreen(ServerUtilities.getPlayerByID(player.toString()), new SimpleMenuProvider(ChestGUIMenu.getServerMenu(this), ChatHelpers.macro(((MenuTitle != "") ? MenuTitle : "No Title")))); + NetworkHooks.openScreen(ServerUtilities.getPlayerByID(player.toString()), new SimpleMenuProvider(ChestGUIMenu.getServerMenu(this), ChatHelpers.macro((MenuTitle != "") ? MenuTitle : "No Title"))); } } @@ -232,7 +228,7 @@ public class ChestGUI return this.id.equals(id); } - public void handleButtonClicked(int slot, Vector2 pos, Item item) { + public void handleButtonClicked(int slot, Vector2f pos, Item item) { for(ChestGUIButton button : buttons) { if(button.getSlotNum() == slot) diff --git a/src/main/java/dev/zontreck/libzontreck/chestgui/ChestGUIButton.java b/src/main/java/dev/zontreck/libzontreck/chestgui/ChestGUIButton.java index 4368831..0ed5b46 100644 --- a/src/main/java/dev/zontreck/libzontreck/chestgui/ChestGUIButton.java +++ b/src/main/java/dev/zontreck/libzontreck/chestgui/ChestGUIButton.java @@ -3,10 +3,8 @@ package dev.zontreck.libzontreck.chestgui; import dev.zontreck.libzontreck.lore.LoreContainer; import dev.zontreck.libzontreck.lore.LoreEntry; import dev.zontreck.libzontreck.util.ChatHelpers; -import dev.zontreck.libzontreck.vectors.Vector2; import dev.zontreck.libzontreck.vectors.Vector2i; import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.NbtUtils; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraftforge.items.ItemStackHandler; @@ -144,7 +142,7 @@ public class ChestGUIButton */ public boolean matchesSlot(Vector2i slot) { - return position.same(slot); + return position.Same(slot); } /** diff --git a/src/main/java/dev/zontreck/libzontreck/config/ServerConfig.java b/src/main/java/dev/zontreck/libzontreck/config/ServerConfig.java new file mode 100644 index 0000000..404f834 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/config/ServerConfig.java @@ -0,0 +1,39 @@ +package dev.zontreck.libzontreck.config; + +import dev.zontreck.libzontreck.LibZontreck; +import dev.zontreck.libzontreck.config.sections.DatabaseSection; +import dev.zontreck.libzontreck.util.SNbtIo; +import net.minecraft.nbt.CompoundTag; + +import java.nio.file.Path; + +public class ServerConfig +{ + public static final Path BASE = LibZontreck.BASE_CONFIG.resolve("server.snbt"); + + public static DatabaseSection database; + + public static void init() + { + if(BASE.toFile().exists()) + { + var config = SNbtIo.loadSnbt(BASE); + + database = DatabaseSection.deserialize(config.getCompound(DatabaseSection.TAG_NAME)); + + commit(); + } else { + database = new DatabaseSection(); + + commit(); + } + } + + public static void commit() + { + CompoundTag tag = new CompoundTag(); + tag.put(DatabaseSection.TAG_NAME, database.serialize()); + + SNbtIo.writeSnbt(BASE, tag); + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/config/sections/DatabaseSection.java b/src/main/java/dev/zontreck/libzontreck/config/sections/DatabaseSection.java new file mode 100644 index 0000000..774b5a5 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/config/sections/DatabaseSection.java @@ -0,0 +1,76 @@ +package dev.zontreck.libzontreck.config.sections; + +import dev.zontreck.libzontreck.config.ServerConfig; +import net.minecraft.nbt.CompoundTag; + +public class DatabaseSection +{ + public static final String TAG_NAME = "database"; + public static final String TAG_USER = "username"; + public static final String TAG_PASSWORD = "password"; + public static final String TAG_HOST = "host"; + public static final String TAG_DATABASE = "database"; + public static final String TAG_VERSION = "rev"; + + + public String user = "root"; + public String password = ""; + public String host = "localhost:3306"; // IP:port + public String database = ""; + public int version; + + public static final int VERSION = 1; + + public static DatabaseSection deserialize(CompoundTag tag) + { + DatabaseSection ret = new DatabaseSection(); + ret.version = tag.getInt(TAG_VERSION); + + if(ret.version == 0) + { + ret.version = VERSION; + return ret; + } + + if(ret.version >= 1) + { + ret.user = tag.getString(TAG_USER); + ret.password = tag.getString(TAG_PASSWORD); + ret.host = tag.getString(TAG_HOST); + ret.database = tag.getString(TAG_DATABASE); + } + + + ret.version = VERSION; + return ret; + } + + public DatabaseSection(){ + + } + + public DatabaseSection(String user, String password, String host, String database) + { + this.user = user; + this.password = password; + this.host = host; + this.database = database; + } + + public String getAsSQLFileName() + { + return database + ".sql"; + } + + public CompoundTag serialize() + { + CompoundTag tag = new CompoundTag(); + tag.putString(TAG_USER, user); + tag.putString(TAG_PASSWORD, password); + tag.putString(TAG_HOST, host); + tag.putString(TAG_DATABASE, database); + tag.putInt(TAG_VERSION, version); + + return tag; + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/currency/Account.java b/src/main/java/dev/zontreck/libzontreck/currency/Account.java index bef02ff..394be28 100644 --- a/src/main/java/dev/zontreck/libzontreck/currency/Account.java +++ b/src/main/java/dev/zontreck/libzontreck/currency/Account.java @@ -1,15 +1,16 @@ package dev.zontreck.libzontreck.currency; -import dev.zontreck.eventsbus.Bus; import dev.zontreck.libzontreck.chat.ChatColor; import dev.zontreck.libzontreck.currency.events.TransactionHistoryFlushEvent; import dev.zontreck.libzontreck.profiles.Profile; import dev.zontreck.libzontreck.profiles.UserProfileNotYetExistsException; +import net.minecraft.core.UUIDUtil; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.nbt.NbtUtils; import net.minecraft.nbt.Tag; +import net.minecraftforge.common.MinecraftForge; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; @@ -93,7 +94,7 @@ public class Account LongTermTransactionHistoryRecord rec = LongTermTransactionHistoryRecord.of(player_id); rec.addHistory(history); rec.commit(); - Bus.Post(new TransactionHistoryFlushEvent(this, rec, history)); + MinecraftForge.EVENT_BUS.post(new TransactionHistoryFlushEvent(this, rec, history)); rec = null; history = new ArrayList<>(); } diff --git a/src/main/java/dev/zontreck/libzontreck/currency/Bank.java b/src/main/java/dev/zontreck/libzontreck/currency/Bank.java index a94b4c2..4473d88 100644 --- a/src/main/java/dev/zontreck/libzontreck/currency/Bank.java +++ b/src/main/java/dev/zontreck/libzontreck/currency/Bank.java @@ -1,11 +1,6 @@ package dev.zontreck.libzontreck.currency; -import com.google.common.collect.Lists; -import dev.zontreck.eventsbus.Bus; -import dev.zontreck.eventsbus.Subscribe; import dev.zontreck.libzontreck.LibZontreck; -import dev.zontreck.libzontreck.chat.ChatColor; -import dev.zontreck.libzontreck.chat.ChatColorFactory; import dev.zontreck.libzontreck.currency.events.BankAccountCreatedEvent; import dev.zontreck.libzontreck.currency.events.BankReadyEvent; import dev.zontreck.libzontreck.currency.events.TransactionEvent; @@ -21,7 +16,10 @@ import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.nbt.NbtIo; import net.minecraft.nbt.Tag; +import net.minecraft.server.MinecraftServer; import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.server.ServerLifecycleHooks; import java.io.IOException; import java.lang.reflect.InvocationTargetException; @@ -81,7 +79,7 @@ public class Bank accounts.add(new Account((CompoundTag) t)); } - Bus.Post(new BankReadyEvent()); + MinecraftForge.EVENT_BUS.post(new BankReadyEvent()); } catch (IOException e) { throw new RuntimeException(e); } @@ -128,7 +126,7 @@ public class Bank instance.accounts.add(new Account(ID)); instance.commit(); - Bus.Post(new BankAccountCreatedEvent(getAccount(ID))); + MinecraftForge.EVENT_BUS.post(new BankAccountCreatedEvent(getAccount(ID))); }else { } } @@ -143,7 +141,10 @@ public class Bank protected static boolean postTx(Transaction tx) throws InvalidSideException, InvocationTargetException, IllegalAccessException { if(ServerUtilities.isClient())return false; TransactionEvent ev = new TransactionEvent(tx); - if(Bus.Post(ev)) + + MinecraftServer server = ServerLifecycleHooks.getCurrentServer(); + + if(MinecraftForge.EVENT_BUS.post(ev)) { // Send the list of reasons to the user String reasonStr = String.join("\n", ev.reasons); @@ -151,13 +152,14 @@ public class Bank Account from = ev.tx.from.get(); Account to = ev.tx.to.get(); + if(from.isValidPlayer()) { - ChatHelpers.broadcastTo(from.player_id, ChatHelpers.macro("!Dark_Gray![!Dark_Blue!Bank!Dark_Gray!] !Dark_Red!The transaction could not be completed because of the following reasons: " + reasonStr), LibZontreck.THE_SERVER); + ChatHelpers.broadcastTo(from.player_id, ChatHelpers.macro("!Dark_Gray![!Dark_Blue!Bank!Dark_Gray!] !Dark_Red!The transaction could not be completed because of the following reasons: " + reasonStr), server); } if(to.isValidPlayer()) { - ChatHelpers.broadcastTo(to.player_id, ChatHelpers.macro("!Dark_Gray![!Dark_Blue!Bank!Dark_Gray!] !Dark_Red!The transaction could not be completed because of the following reasons: " + reasonStr), LibZontreck.THE_SERVER); + ChatHelpers.broadcastTo(to.player_id, ChatHelpers.macro("!Dark_Gray![!Dark_Blue!Bank!Dark_Gray!] !Dark_Red!The transaction could not be completed because of the following reasons: " + reasonStr), server); } return false; @@ -198,19 +200,19 @@ public class Bank } if(from.isValidPlayer()) - ChatHelpers.broadcastTo(from.player_id, ChatHelpers.macro("!Dark_Gray![!Dark_Blue!Bank!Dark_Gray!] !Dark_Green!You sent !White!${0} !Dark_green!to {1}", String.valueOf(tx.amount), toProf.name_color+toProf.nickname), LibZontreck.THE_SERVER); + ChatHelpers.broadcastTo(from.player_id, ChatHelpers.macro("!Dark_Gray![!Dark_Blue!Bank!Dark_Gray!] !Dark_Green!You sent !White!${0} !Dark_green!to {1}", String.valueOf(tx.amount), toProf.name_color+toProf.nickname), server); if(to.isValidPlayer()) - ChatHelpers.broadcastTo(from.player_id, ChatHelpers.macro("!Dark_Gray![!Dark_Blue!Bank!Dark_Gray!] {0} !Dark_Green!paid you ${1}", String.valueOf(tx.amount), toProf.name_color+toProf.nickname), LibZontreck.THE_SERVER); + ChatHelpers.broadcastTo(from.player_id, ChatHelpers.macro("!Dark_Gray![!Dark_Blue!Bank!Dark_Gray!] {0} !Dark_Green!paid you ${1}", String.valueOf(tx.amount), toProf.name_color+toProf.nickname), server); if(to.isValidPlayer() && ServerUtilities.playerIsOffline(to.player_id)) Profile.unload(toProf); if(from.isValidPlayer() && ServerUtilities.playerIsOffline(from.player_id)) Profile.unload(fromProf); - Bus.Post(new WalletUpdatedEvent(from.player_id, fromOld, from.balance, tx)); + MinecraftForge.EVENT_BUS.post(new WalletUpdatedEvent(from.player_id, fromOld, from.balance, tx)); - Bus.Post(new WalletUpdatedEvent(to.player_id, toOld, to.balance, tx)); + MinecraftForge.EVENT_BUS.post(new WalletUpdatedEvent(to.player_id, toOld, to.balance, tx)); if(from.isValidPlayer() && !ServerUtilities.playerIsOffline(from.player_id)) { @@ -232,7 +234,7 @@ public class Bank * This event is fired when wallets get updated. It cannot be cancelled * @param ev The event containing the player ID and new+old wallet data */ - @Subscribe + @SubscribeEvent public static void onWalletUpdate(WalletUpdatedEvent ev) { diff --git a/src/main/java/dev/zontreck/libzontreck/currency/events/BankAccountCreatedEvent.java b/src/main/java/dev/zontreck/libzontreck/currency/events/BankAccountCreatedEvent.java index 1961f97..6ff98b0 100644 --- a/src/main/java/dev/zontreck/libzontreck/currency/events/BankAccountCreatedEvent.java +++ b/src/main/java/dev/zontreck/libzontreck/currency/events/BankAccountCreatedEvent.java @@ -1,8 +1,8 @@ package dev.zontreck.libzontreck.currency.events; -import dev.zontreck.eventsbus.Event; import dev.zontreck.libzontreck.currency.Account; +import net.minecraftforge.eventbus.api.Event; public class BankAccountCreatedEvent extends Event { diff --git a/src/main/java/dev/zontreck/libzontreck/currency/events/BankReadyEvent.java b/src/main/java/dev/zontreck/libzontreck/currency/events/BankReadyEvent.java index c7fcfc0..5dcf914 100644 --- a/src/main/java/dev/zontreck/libzontreck/currency/events/BankReadyEvent.java +++ b/src/main/java/dev/zontreck/libzontreck/currency/events/BankReadyEvent.java @@ -1,7 +1,7 @@ package dev.zontreck.libzontreck.currency.events; -import dev.zontreck.eventsbus.Event; +import net.minecraftforge.eventbus.api.Event; /** * Contains no information by itself, it only signals that the Bank is open for business diff --git a/src/main/java/dev/zontreck/libzontreck/currency/events/TransactionEvent.java b/src/main/java/dev/zontreck/libzontreck/currency/events/TransactionEvent.java index 95e2bc9..d0786ea 100644 --- a/src/main/java/dev/zontreck/libzontreck/currency/events/TransactionEvent.java +++ b/src/main/java/dev/zontreck/libzontreck/currency/events/TransactionEvent.java @@ -1,15 +1,13 @@ package dev.zontreck.libzontreck.currency.events; -import dev.zontreck.eventsbus.Cancellable; -import dev.zontreck.eventsbus.Event; import dev.zontreck.libzontreck.currency.Transaction; +import net.minecraftforge.eventbus.api.Cancelable; +import net.minecraftforge.eventbus.api.Event; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.util.List; -@Cancellable +@Cancelable public class TransactionEvent extends Event { public Transaction tx; diff --git a/src/main/java/dev/zontreck/libzontreck/currency/events/TransactionHistoryFlushEvent.java b/src/main/java/dev/zontreck/libzontreck/currency/events/TransactionHistoryFlushEvent.java index ef1d1dc..34bb7a8 100644 --- a/src/main/java/dev/zontreck/libzontreck/currency/events/TransactionHistoryFlushEvent.java +++ b/src/main/java/dev/zontreck/libzontreck/currency/events/TransactionHistoryFlushEvent.java @@ -1,10 +1,10 @@ package dev.zontreck.libzontreck.currency.events; -import dev.zontreck.eventsbus.Event; import dev.zontreck.libzontreck.currency.Account; import dev.zontreck.libzontreck.currency.LongTermTransactionHistoryRecord; import dev.zontreck.libzontreck.currency.Transaction; +import net.minecraftforge.eventbus.api.Event; import java.util.List; diff --git a/src/main/java/dev/zontreck/libzontreck/currency/events/WalletUpdatedEvent.java b/src/main/java/dev/zontreck/libzontreck/currency/events/WalletUpdatedEvent.java index 24cdf15..511ec7d 100644 --- a/src/main/java/dev/zontreck/libzontreck/currency/events/WalletUpdatedEvent.java +++ b/src/main/java/dev/zontreck/libzontreck/currency/events/WalletUpdatedEvent.java @@ -1,8 +1,8 @@ package dev.zontreck.libzontreck.currency.events; -import dev.zontreck.eventsbus.Event; import dev.zontreck.libzontreck.currency.Transaction; import net.minecraft.world.entity.player.Player; +import net.minecraftforge.eventbus.api.Event; import java.util.UUID; diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/Auxiliaries.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/Auxiliaries.java new file mode 100644 index 0000000..eb57f30 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/Auxiliaries.java @@ -0,0 +1,590 @@ +/* + * @file Auxiliaries.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * General commonly used functionality. + */ +package dev.zontreck.libzontreck.edlibmc; + +import com.mojang.blaze3d.platform.InputConstants; +import net.minecraft.ChatFormatting; +import net.minecraft.SharedConstants; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.Registry; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.ComponentUtils; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.shapes.BooleanOp; +import net.minecraft.world.phys.shapes.Shapes; +import net.minecraft.world.phys.shapes.VoxelShape; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import net.minecraftforge.fml.ModList; +import net.minecraftforge.registries.ForgeRegistries; +import org.slf4j.Logger; +import org.lwjgl.glfw.GLFW; + +import javax.annotation.Nullable; +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; + + +public class Auxiliaries { + private static String modid; + private static Logger logger; + private static Supplier server_config_supplier = CompoundTag::new; + + public static void init(String modid, Logger logger, Supplier server_config_supplier) { + Auxiliaries.modid = modid; + Auxiliaries.logger = logger; + Auxiliaries.server_config_supplier = server_config_supplier; + } + + // ------------------------------------------------------------------------------------------------------------------- + // Mod specific exports + // ------------------------------------------------------------------------------------------------------------------- + + public static String modid() { + return modid; + } + + public static Logger logger() { + return logger; + } + + // ------------------------------------------------------------------------------------------------------------------- + // Sidedness, system/environment, tagging interfaces + // ------------------------------------------------------------------------------------------------------------------- + + public interface IExperimentalFeature { + } + + public static boolean isModLoaded(final String registry_name) { + return ModList.get().isLoaded(registry_name); + } + + public static boolean isDevelopmentMode() { + return SharedConstants.IS_RUNNING_IN_IDE; + } + + @OnlyIn(Dist.CLIENT) + public static boolean isShiftDown() { + return (InputConstants.isKeyDown(SidedProxy.mc().getWindow().getWindow(), GLFW.GLFW_KEY_LEFT_SHIFT) || + InputConstants.isKeyDown(SidedProxy.mc().getWindow().getWindow(), GLFW.GLFW_KEY_RIGHT_SHIFT)); + } + + @OnlyIn(Dist.CLIENT) + public static boolean isCtrlDown() { + return (InputConstants.isKeyDown(SidedProxy.mc().getWindow().getWindow(), GLFW.GLFW_KEY_LEFT_CONTROL) || + InputConstants.isKeyDown(SidedProxy.mc().getWindow().getWindow(), GLFW.GLFW_KEY_RIGHT_CONTROL)); + } + + // ------------------------------------------------------------------------------------------------------------------- + // Logging + // ------------------------------------------------------------------------------------------------------------------- + + public static void logInfo(final String msg) { + logger.info(msg); + } + + public static void logWarn(final String msg) { + logger.warn(msg); + } + + public static void logError(final String msg) { + logger.error(msg); + } + + public static void logDebug(final String msg) { /*logger.debug(msg);*/ } + + // ------------------------------------------------------------------------------------------------------------------- + // Localization, text formatting + // ------------------------------------------------------------------------------------------------------------------- + + /** + * Text localization wrapper, implicitly prepends `MODID` to the + * translation keys. Forces formatting argument, nullable if no special formatting shall be applied.. + */ + public static MutableComponent localizable(String modtrkey, Object... args) { + return Component.translatable((modtrkey.startsWith("block.") || (modtrkey.startsWith("item."))) ? (modtrkey) : (modid + "." + modtrkey), args); + } + + public static MutableComponent localizable(String modtrkey, @Nullable ChatFormatting color, Object... args) { + final MutableComponent tr = Component.translatable(modid + "." + modtrkey, args); + if (color != null) tr.getStyle().applyFormat(color); + return tr; + } + + public static Component localizable(String modtrkey) { + return localizable(modtrkey, new Object[]{}); + } + + public static Component localizable_block_key(String blocksubkey) { + return Component.translatable("block." + modid + "." + blocksubkey); + } + + @OnlyIn(Dist.CLIENT) + public static String localize(String translationKey, Object... args) { + Component tr = Component.translatable(translationKey, args); + tr.getStyle().applyFormat(ChatFormatting.RESET); + final String ft = tr.getString(); + if (ft.contains("${")) { + // Non-recursive, non-argument lang file entry cross referencing. + Pattern pt = Pattern.compile("\\$\\{([^}]+)\\}"); + Matcher mt = pt.matcher(ft); + StringBuffer sb = new StringBuffer(); + while (mt.find()) { + String m = mt.group(1); + if (m.contains("?")) { + String[] kv = m.split("\\?", 2); + String key = kv[0].trim(); + boolean not = key.startsWith("!"); + if (not) key = key.replaceFirst("!", ""); + m = kv[1].trim(); + if (!server_config_supplier.get().contains(key)) { + m = ""; + } else { + boolean r = server_config_supplier.get().getBoolean(key); + if (not) r = !r; + if (!r) m = ""; + } + } + mt.appendReplacement(sb, Matcher.quoteReplacement((Component.translatable(m)).getString().trim())); + } + mt.appendTail(sb); + return sb.toString(); + } else { + return ft; + } + } + + /** + * Returns true if a given key is translated for the current language. + */ + @OnlyIn(Dist.CLIENT) + public static boolean hasTranslation(String key) { + return net.minecraft.client.resources.language.I18n.exists(key); + } + + public static MutableComponent join(Collection components, String separator) { + return ComponentUtils.formatList(components, Component.literal(separator), Function.identity()); + } + + public static MutableComponent join(Component... components) { + final MutableComponent tc = Component.empty(); + for (Component c : components) { + tc.append(c); + } + return tc; + } + + public static boolean isEmpty(Component component) { + return component.getSiblings().isEmpty() && component.getString().isEmpty(); + } + + public static final class Tooltip { + @OnlyIn(Dist.CLIENT) + public static boolean extendedTipCondition() { + return isShiftDown(); + } + + @OnlyIn(Dist.CLIENT) + public static boolean helpCondition() { + return isShiftDown() && isCtrlDown(); + } + + /** + * Adds an extended tooltip or help tooltip depending on the key states of CTRL and SHIFT. + * Returns true if the localisable help/tip was added, false if not (either not CTL/SHIFT or + * no translation found). + */ + @OnlyIn(Dist.CLIENT) + public static boolean addInformation(@Nullable String advancedTooltipTranslationKey, @Nullable String helpTranslationKey, List tooltip, TooltipFlag flag, boolean addAdvancedTooltipHints) { + // Note: intentionally not using keybinding here, this must be `control` or `shift`. + final boolean help_available = (helpTranslationKey != null) && Auxiliaries.hasTranslation(helpTranslationKey + ".help"); + final boolean tip_available = (advancedTooltipTranslationKey != null) && Auxiliaries.hasTranslation(helpTranslationKey + ".tip"); + if ((!help_available) && (!tip_available)) return false; + String tip_text = ""; + if (helpCondition()) { + if (help_available) tip_text = localize(helpTranslationKey + ".help"); + } else if (extendedTipCondition()) { + if (tip_available) tip_text = localize(advancedTooltipTranslationKey + ".tip"); + } else if (addAdvancedTooltipHints) { + if (tip_available) tip_text += localize(modid + ".tooltip.hint.extended") + (help_available ? " " : ""); + if (help_available) tip_text += localize(modid + ".tooltip.hint.help"); + } + if (tip_text.isEmpty()) return false; + String[] tip_list = tip_text.split("\\r?\\n"); + for (String tip : tip_list) { + tooltip.add(Component.literal(tip.replaceAll("\\s+$", "").replaceAll("^\\s+", "")).withStyle(ChatFormatting.GRAY)); + } + return true; + } + + /** + * Adds an extended tooltip or help tooltip for a given stack depending on the key states of CTRL and SHIFT. + * Format in the lang file is (e.g. for items): "item.MODID.REGISTRYNAME.tip" and "item.MODID.REGISTRYNAME.help". + * Return value see method pattern above. + */ + @OnlyIn(Dist.CLIENT) + public static boolean addInformation(ItemStack stack, @Nullable BlockGetter world, List tooltip, TooltipFlag flag, boolean addAdvancedTooltipHints) { + return addInformation(stack.getDescriptionId(), stack.getDescriptionId(), tooltip, flag, addAdvancedTooltipHints); + } + + @OnlyIn(Dist.CLIENT) + public static boolean addInformation(String translation_key, List tooltip) { + if (!Auxiliaries.hasTranslation(translation_key)) return false; + tooltip.add(Component.literal(localize(translation_key).replaceAll("\\s+$", "").replaceAll("^\\s+", "")).withStyle(ChatFormatting.GRAY)); + return true; + } + + } + + @SuppressWarnings("unused") + public static void playerChatMessage(final Player player, final String message) { + player.displayClientMessage(Component.translatable(message.trim()), true); + } + + public static @Nullable Component unserializeTextComponent(String serialized) { + return Component.Serializer.fromJson(serialized); + } + + public static String serializeTextComponent(Component tc) { + return (tc == null) ? ("") : (Component.Serializer.toJson(tc)); + } + + // ------------------------------------------------------------------------------------------------------------------- + // Tag Handling + // ------------------------------------------------------------------------------------------------------------------- + + @SuppressWarnings("deprecation") + public static boolean isInItemTag(Item item, ResourceLocation tag) { + return ForgeRegistries.ITEMS.tags().stream().filter(tg -> tg.getKey().location().equals(tag)).anyMatch(tk -> tk.contains(item)); + } + + @SuppressWarnings("deprecation") + public static boolean isInBlockTag(Block block, ResourceLocation tag) { + return ForgeRegistries.BLOCKS.tags().stream().filter(tg -> tg.getKey().location().equals(tag)).anyMatch(tk -> tk.contains(block)); + } + + @SuppressWarnings("deprecation") + public static ResourceLocation getResourceLocation(Item item) { + return ForgeRegistries.ITEMS.getKey(item); + } + + @SuppressWarnings("deprecation") + public static ResourceLocation getResourceLocation(Block block) { + return ForgeRegistries.BLOCKS.getKey(block); + } + + @SuppressWarnings("deprecation") + public static ResourceLocation getResourceLocation(net.minecraft.world.inventory.MenuType menu) { + return ForgeRegistries.MENU_TYPES.getKey(menu); + } + + @SuppressWarnings("deprecation") + public static ResourceLocation getResourceLocation(net.minecraft.world.level.material.Fluid fluid) { + return ForgeRegistries.FLUIDS.getKey(fluid); + } + + // ------------------------------------------------------------------------------------------------------------------- + // Item NBT data + // ------------------------------------------------------------------------------------------------------------------- + + /** + * Equivalent to getDisplayName(), returns null if no custom name is set. + */ + public static @Nullable Component getItemLabel(ItemStack stack) { + CompoundTag nbt = stack.getTagElement("display"); + if (nbt != null && nbt.contains("Name", 8)) { + try { + Component tc = unserializeTextComponent(nbt.getString("Name")); + if (tc != null) return tc; + nbt.remove("Name"); + } catch (Exception e) { + nbt.remove("Name"); + } + } + return null; + } + + public static ItemStack setItemLabel(ItemStack stack, @Nullable Component name) { + if (name != null) { + CompoundTag nbt = stack.getOrCreateTagElement("display"); + nbt.putString("Name", serializeTextComponent(name)); + } else { + if (stack.hasTag()) stack.removeTagKey("display"); + } + return stack; + } + + // ------------------------------------------------------------------------------------------------------------------- + // Block handling + // ------------------------------------------------------------------------------------------------------------------- + + public static boolean isWaterLogged(BlockState state) { + return state.hasProperty(BlockStateProperties.WATERLOGGED) && state.getValue(BlockStateProperties.WATERLOGGED); + } + + public static AABB getPixeledAABB(double x0, double y0, double z0, double x1, double y1, double z1) { + return new AABB(x0 / 16.0, y0 / 16.0, z0 / 16.0, x1 / 16.0, y1 / 16.0, z1 / 16.0); + } + + public static AABB getRotatedAABB(AABB bb, Direction new_facing) { + return getRotatedAABB(bb, new_facing, false); + } + + public static AABB[] getRotatedAABB(AABB[] bb, Direction new_facing) { + return getRotatedAABB(bb, new_facing, false); + } + + public static AABB getRotatedAABB(AABB bb, Direction new_facing, boolean horizontal_rotation) { + if (!horizontal_rotation) { + switch (new_facing.get3DDataValue()) { + case 0: + return new AABB(1 - bb.maxX, bb.minZ, bb.minY, 1 - bb.minX, bb.maxZ, bb.maxY); // D + case 1: + return new AABB(1 - bb.maxX, 1 - bb.maxZ, 1 - bb.maxY, 1 - bb.minX, 1 - bb.minZ, 1 - bb.minY); // U + case 2: + return new AABB(bb.minX, bb.minY, bb.minZ, bb.maxX, bb.maxY, bb.maxZ); // N --> bb + case 3: + return new AABB(1 - bb.maxX, bb.minY, 1 - bb.maxZ, 1 - bb.minX, bb.maxY, 1 - bb.minZ); // S + case 4: + return new AABB(bb.minZ, bb.minY, 1 - bb.maxX, bb.maxZ, bb.maxY, 1 - bb.minX); // W + case 5: + return new AABB(1 - bb.maxZ, bb.minY, bb.minX, 1 - bb.minZ, bb.maxY, bb.maxX); // E + } + } else { + switch (new_facing.get3DDataValue()) { + case 0: + return new AABB(bb.minX, bb.minY, bb.minZ, bb.maxX, bb.maxY, bb.maxZ); // D --> bb + case 1: + return new AABB(bb.minX, bb.minY, bb.minZ, bb.maxX, bb.maxY, bb.maxZ); // U --> bb + case 2: + return new AABB(bb.minX, bb.minY, bb.minZ, bb.maxX, bb.maxY, bb.maxZ); // N --> bb + case 3: + return new AABB(1 - bb.maxX, bb.minY, 1 - bb.maxZ, 1 - bb.minX, bb.maxY, 1 - bb.minZ); // S + case 4: + return new AABB(bb.minZ, bb.minY, 1 - bb.maxX, bb.maxZ, bb.maxY, 1 - bb.minX); // W + case 5: + return new AABB(1 - bb.maxZ, bb.minY, bb.minX, 1 - bb.minZ, bb.maxY, bb.maxX); // E + } + } + return bb; + } + + public static AABB[] getRotatedAABB(AABB[] bbs, Direction new_facing, boolean horizontal_rotation) { + final AABB[] transformed = new AABB[bbs.length]; + for (int i = 0; i < bbs.length; ++i) transformed[i] = getRotatedAABB(bbs[i], new_facing, horizontal_rotation); + return transformed; + } + + public static AABB getYRotatedAABB(AABB bb, int clockwise_90deg_steps) { + final Direction[] direction_map = new Direction[]{Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST}; + return getRotatedAABB(bb, direction_map[(clockwise_90deg_steps + 4096) & 0x03], true); + } + + public static AABB[] getYRotatedAABB(AABB[] bbs, int clockwise_90deg_steps) { + final AABB[] transformed = new AABB[bbs.length]; + for (int i = 0; i < bbs.length; ++i) transformed[i] = getYRotatedAABB(bbs[i], clockwise_90deg_steps); + return transformed; + } + + public static AABB getMirroredAABB(AABB bb, Direction.Axis axis) { + return switch (axis) { + case X -> new AABB(1 - bb.maxX, bb.minY, bb.minZ, 1 - bb.minX, bb.maxY, bb.maxZ); + case Y -> new AABB(bb.minX, 1 - bb.maxY, bb.minZ, bb.maxX, 1 - bb.minY, bb.maxZ); + case Z -> new AABB(bb.minX, bb.minY, 1 - bb.maxZ, bb.maxX, bb.maxY, 1 - bb.minZ); + }; + } + + public static AABB[] getMirroredAABB(AABB[] bbs, Direction.Axis axis) { + final AABB[] transformed = new AABB[bbs.length]; + for (int i = 0; i < bbs.length; ++i) transformed[i] = getMirroredAABB(bbs[i], axis); + return transformed; + } + + public static VoxelShape getUnionShape(AABB... aabbs) { + VoxelShape shape = Shapes.empty(); + for (AABB aabb : aabbs) shape = Shapes.joinUnoptimized(shape, Shapes.create(aabb), BooleanOp.OR); + return shape; + } + + public static VoxelShape getUnionShape(AABB[]... aabb_list) { + VoxelShape shape = Shapes.empty(); + for (AABB[] aabbs : aabb_list) { + for (AABB aabb : aabbs) shape = Shapes.joinUnoptimized(shape, Shapes.create(aabb), BooleanOp.OR); + } + return shape; + } + + public static AABB[] getMappedAABB(AABB[] bbs, Function mapper) { + final AABB[] transformed = new AABB[bbs.length]; + for (int i = 0; i < bbs.length; ++i) transformed[i] = mapper.apply(bbs[i]); + return transformed; + } + + public static final class BlockPosRange implements Iterable { + private final int x0, x1, y0, y1, z0, z1; + + public BlockPosRange(int x0, int y0, int z0, int x1, int y1, int z1) { + this.x0 = Math.min(x0, x1); + this.x1 = Math.max(x0, x1); + this.y0 = Math.min(y0, y1); + this.y1 = Math.max(y0, y1); + this.z0 = Math.min(z0, z1); + this.z1 = Math.max(z0, z1); + } + + public static BlockPosRange of(AABB range) { + return new BlockPosRange( + (int) Math.floor(range.minX), + (int) Math.floor(range.minY), + (int) Math.floor(range.minZ), + (int) Math.floor(range.maxX - .0625), + (int) Math.floor(range.maxY - .0625), + (int) Math.floor(range.maxZ - .0625) + ); + } + + public int getXSize() { + return x1 - x0 + 1; + } + + public int getYSize() { + return y1 - y0 + 1; + } + + public int getZSize() { + return z1 - z0 + 1; + } + + public int getArea() { + return getXSize() * getZSize(); + } + + public int getHeight() { + return getYSize(); + } + + public int getVolume() { + return getXSize() * getYSize() * getZSize(); + } + + public BlockPos byXZYIndex(int xyz_index) { + final int xsz = getXSize(), ysz = getYSize(), zsz = getZSize(); + xyz_index = xyz_index % (xsz * ysz * zsz); + final int y = xyz_index / (xsz * zsz); + xyz_index -= y * (xsz * zsz); + final int z = xyz_index / xsz; + xyz_index -= z * xsz; + final int x = xyz_index; + return new BlockPos(x0 + x, y0 + y, z0 + z); + } + + public BlockPos byXZIndex(int xz_index, int y_offset) { + final int xsz = getXSize(), zsz = getZSize(); + xz_index = xz_index % (xsz * zsz); + final int z = xz_index / xsz; + xz_index -= z * xsz; + final int x = xz_index; + return new BlockPos(x0 + x, y0 + y_offset, z0 + z); + } + + public static final class BlockRangeIterator implements Iterator { + private final BlockPosRange range_; + private int x, y, z; + + public BlockRangeIterator(BlockPosRange range) { + range_ = range; + x = range.x0; + y = range.y0; + z = range.z0; + } + + @Override + public boolean hasNext() { + return (z <= range_.z1); + } + + @Override + public BlockPos next() { + if (!hasNext()) throw new NoSuchElementException(); + final BlockPos pos = new BlockPos(x, y, z); + ++x; + if (x > range_.x1) { + x = range_.x0; + ++y; + if (y > range_.y1) { + y = range_.y0; + ++z; + } + } + return pos; + } + } + + @Override + public BlockRangeIterator iterator() { + return new BlockRangeIterator(this); + } + + public Stream stream() { + return java.util.stream.StreamSupport.stream(spliterator(), false); + } + } + + // ------------------------------------------------------------------------------------------------------------------- + // JAR resource related + // ------------------------------------------------------------------------------------------------------------------- + + public static String loadResourceText(InputStream is) { + try { + if (is == null) return ""; + BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8)); + return br.lines().collect(Collectors.joining("\n")); + } catch (Throwable e) { + return ""; + } + } + + public static String loadResourceText(String path) { + return loadResourceText(Auxiliaries.class.getResourceAsStream(path)); + } + + public static void logGitVersion(String mod_name) { + try { + // Done during construction to have an exact version in case of a crash while registering. + String version = Auxiliaries.loadResourceText("/.gitversion-" + modid).trim(); + logInfo(mod_name + ((version.isEmpty()) ? (" (dev build)") : (" GIT id #" + version)) + "."); + } catch (Throwable e) { + // (void)e; well, then not. Priority is not to get unneeded crashes because of version logging. + } + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/Containers.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/Containers.java new file mode 100644 index 0000000..37a1814 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/Containers.java @@ -0,0 +1,138 @@ +package dev.zontreck.libzontreck.edlibmc; + +import net.minecraft.util.Mth; +import net.minecraft.world.Container; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import java.util.function.BiConsumer; + +public class Containers { + // ------------------------------------------------------------------------------------------------------------------- + // Slots + // ------------------------------------------------------------------------------------------------------------------- + + public static class StorageSlot extends Slot { + protected BiConsumer slot_change_action_ = (oldStack, newStack) -> { + }; + protected int stack_limit_ = 64; + public boolean enabled = true; + + public StorageSlot(Container inventory, int index, int x, int y) { + super(inventory, index, x, y); + } + + public StorageSlot setSlotStackLimit(int limit) { + stack_limit_ = Mth.clamp(limit, 1, 64); + return this; + } + + public int getMaxStackSize() { + return stack_limit_; + } + + public StorageSlot setSlotChangeNotifier(BiConsumer action) { + slot_change_action_ = action; + return this; + } + + @Override + public void onQuickCraft(ItemStack oldStack, ItemStack newStack) { + slot_change_action_.accept(oldStack, newStack); + } // no crafting trigger + + @Override + public void set(ItemStack stack) { + if (stack.is(getItem().getItem())) { + super.set(stack); + } else { + final ItemStack before = getItem().copy(); + super.set(stack); // whatever this does else next to setting inventory. + slot_change_action_.accept(before, getItem()); + } + } + + @Override + public boolean mayPlace(ItemStack stack) { + return enabled && this.container.canPlaceItem(this.getSlotIndex(), stack); + } + + @Override + public int getMaxStackSize(ItemStack stack) { + return Math.min(getMaxStackSize(), stack_limit_); + } + + @OnlyIn(Dist.CLIENT) + public boolean isActive() { + return enabled; + } + } + + public static class LockedSlot extends Slot { + protected int stack_limit_ = 64; + public boolean enabled = true; + + public LockedSlot(Container inventory, int index, int x, int y) { + super(inventory, index, x, y); + } + + public LockedSlot setSlotStackLimit(int limit) { + stack_limit_ = Mth.clamp(limit, 1, 64); + return this; + } + + public int getMaxStackSize() { + return stack_limit_; + } + + @Override + public int getMaxStackSize(ItemStack stack) { + return Math.min(getMaxStackSize(), stack_limit_); + } + + @Override + public boolean mayPlace(ItemStack stack) { + return false; + } + + @Override + public boolean mayPickup(Player player) { + return false; + } + + @OnlyIn(Dist.CLIENT) + public boolean isActive() { + return enabled; + } + } + + public static class HiddenSlot extends Slot { + public HiddenSlot(Container inventory, int index) { + super(inventory, index, 0, 0); + } + + @Override + public int getMaxStackSize(ItemStack stack) { + return getMaxStackSize(); + } + + @Override + public boolean mayPlace(ItemStack stack) { + return false; + } + + @Override + public boolean mayPickup(Player player) { + return false; + } + + @OnlyIn(Dist.CLIENT) + public boolean isActive() { + return false; + } + } + +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/Crafting.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/Crafting.java new file mode 100644 index 0000000..2708733 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/Crafting.java @@ -0,0 +1,440 @@ +/* + * @file Recipes.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * Recipe utility functionality. + */ +package dev.zontreck.libzontreck.edlibmc; + +import net.minecraft.core.NonNullList; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Mth; +import net.minecraft.util.Tuple; +import net.minecraft.world.Container; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.entity.player.StackedContents; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.CraftingContainer; +import net.minecraft.world.item.EnchantedBookItem; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.crafting.*; +import net.minecraft.world.item.enchantment.Enchantment; +import net.minecraft.world.item.enchantment.EnchantmentHelper; +import net.minecraft.world.item.enchantment.EnchantmentInstance; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.ComposterBlock; +import net.minecraftforge.common.ForgeHooks; +import net.minecraftforge.common.brewing.BrewingRecipeRegistry; + +import javax.annotation.Nullable; +import java.util.*; +import java.util.function.BiPredicate; + + +public class Crafting { + // ------------------------------------------------------------------------------------------------------------------- + + /** + * Returns a Crafting recipe by registry name. + */ + public static Optional getCraftingRecipe(Level world, ResourceLocation recipe_id) { + Recipe recipe = world.getRecipeManager().byKey(recipe_id).orElse(null); + return (recipe instanceof CraftingRecipe) ? Optional.of((CraftingRecipe) recipe) : Optional.empty(); + } + + /** + * Returns a list of matching recipes by the first N slots (crafting grid slots) of the given inventory. + */ + public static List get3x3CraftingRecipes(Level world, Container crafting_grid_slots) { + return CraftingGrid.instance3x3.getRecipes(world, crafting_grid_slots); + } + + /** + * Returns a recipe by the first N slots (crafting grid slots). + */ + public static Optional get3x3CraftingRecipe(Level world, Container crafting_grid_slots) { + return get3x3CraftingRecipes(world, crafting_grid_slots).stream().findFirst(); + } + + /** + * Returns the result item of the recipe with the given grid layout. + */ + public static ItemStack get3x3CraftingResult(Level world, Container grid, CraftingRecipe recipe) { + return CraftingGrid.instance3x3.getCraftingResult(world, grid, recipe); + } + + /** + * Returns the items remaining in the grid after crafting 3x3. + */ + public static List get3x3RemainingItems(Level world, Container grid, CraftingRecipe recipe) { + return CraftingGrid.instance3x3.getRemainingItems(world, grid, recipe); + } + + public static List get3x3Placement(Level world, CraftingRecipe recipe, Container item_inventory, @Nullable Container crafting_grid) { + final int width = 3; + final int height = 3; + if (!recipe.canCraftInDimensions(width, height)) return Collections.emptyList(); + List used = new ArrayList<>(); //NonNullList.withSize(width*height); + for (int i = width * height; i > 0; --i) used.add(ItemStack.EMPTY); + Container check_inventory = Inventories.copyOf(item_inventory); + Inventories.InventoryRange source = new Inventories.InventoryRange(check_inventory); + final List ingredients = recipe.getIngredients(); + final List preferred = new ArrayList<>(width * height); + if (crafting_grid != null) { + for (int i = 0; i < crafting_grid.getContainerSize(); ++i) { + ItemStack stack = crafting_grid.getItem(i); + if (stack.isEmpty()) continue; + stack = stack.copy(); + stack.setCount(1); + if (!source.extract(stack).isEmpty()) preferred.add(stack); + } + } + for (int i = 0; i < ingredients.size(); ++i) { + final Ingredient ingredient = ingredients.get(i); + if (ingredient == Ingredient.EMPTY) continue; + ItemStack stack = preferred.stream().filter(ingredient).findFirst().orElse(ItemStack.EMPTY); + if (!stack.isEmpty()) { + preferred.remove(stack); + } else { + stack = source.stream().filter(ingredient).findFirst().orElse(ItemStack.EMPTY); + if (stack.isEmpty()) return Collections.emptyList(); + stack = stack.copy(); + stack.setCount(1); + if (source.extract(stack).isEmpty()) return Collections.emptyList(); + } + used.set(i, stack); + } + if (recipe instanceof ShapedRecipe shaped) { + List placement = NonNullList.withSize(width * height, ItemStack.EMPTY); + for (int row = 0; row < shaped.getRecipeHeight(); ++row) { + for (int col = 0; col < shaped.getRecipeWidth(); ++col) { + placement.set(width * row + col, used.get(row * shaped.getRecipeWidth() + col)); + } + } + return placement; + } else { + return used; + } + } + + /** + * Returns the recipe for a given input stack to smelt, null if there is no recipe + * for the given type (SMELTING,BLASTING,SMOKING, etc). + */ + public static > Optional getFurnaceRecipe(RecipeType recipe_type, Level world, ItemStack input_stack) { + if (input_stack.isEmpty()) { + return Optional.empty(); + } else if (recipe_type == RecipeType.SMELTING) { + SimpleContainer inventory = new SimpleContainer(3); + inventory.setItem(0, input_stack); + SmeltingRecipe recipe = world.getRecipeManager().getRecipeFor(RecipeType.SMELTING, inventory, world).orElse(null); + return (recipe == null) ? Optional.empty() : Optional.of(recipe); + } else if (recipe_type == RecipeType.BLASTING) { + SimpleContainer inventory = new SimpleContainer(3); + inventory.setItem(0, input_stack); + BlastingRecipe recipe = world.getRecipeManager().getRecipeFor(RecipeType.BLASTING, inventory, world).orElse(null); + return (recipe == null) ? Optional.empty() : Optional.of(recipe); + } else if (recipe_type == RecipeType.SMOKING) { + SimpleContainer inventory = new SimpleContainer(3); + inventory.setItem(0, input_stack); + SmokingRecipe recipe = world.getRecipeManager().getRecipeFor(RecipeType.SMOKING, inventory, world).orElse(null); + return (recipe == null) ? Optional.empty() : Optional.of(recipe); + } else { + return Optional.empty(); + } + } + + // ------------------------------------------------------------------------------------------------------------------- + + public static > int getSmeltingTimeNeeded(RecipeType recipe_type, Level world, ItemStack stack) { + if (stack.isEmpty()) return 0; + final int t = getFurnaceRecipe(recipe_type, world, stack).map((AbstractCookingRecipe::getCookingTime)).orElse(0); + return (t <= 0) ? 200 : t; + } + + /** + * Returns the burn time of an item when used as fuel, 0 if it is no fuel. + */ + public static int getFuelBurntime(Level world, ItemStack stack) { + if (stack.isEmpty()) return 0; + int t = ForgeHooks.getBurnTime(stack, null); + return Math.max(t, 0); + } + + /** + * Returns true if an item can be used as fuel. + */ + public static boolean isFuel(Level world, ItemStack stack) { + return (getFuelBurntime(world, stack) > 0) || (stack.getItem() == Items.LAVA_BUCKET); + } + + /** + * Returns burntime and remaining stack then the item shall be used as fuel. + */ + public static Tuple consumeFuel(Level world, ItemStack stack) { + if (stack.isEmpty()) return new Tuple<>(0, stack); + int burnime = getFuelBurntime(world, stack); + if ((stack.getItem() == Items.LAVA_BUCKET)) { + if (burnime <= 0) burnime = 1000 * 20; + return new Tuple<>(burnime, new ItemStack(Items.BUCKET)); + } else if (burnime <= 0) { + return new Tuple<>(0, stack); + } else { + ItemStack left_over = stack.copy(); + left_over.shrink(1); + return new Tuple<>(burnime, left_over); + } + } + + /** + * Returns true if the item can be used as brewing fuel. + */ + public static boolean isBrewingFuel(Level world, ItemStack stack) { + return (stack.getItem() == Items.BLAZE_POWDER) || (stack.getItem() == Items.BLAZE_ROD); + } + + // ------------------------------------------------------------------------------------------------------------------- + + /** + * Returns true if the item can be used as brewing ingredient. + */ + public static boolean isBrewingIngredient(Level world, ItemStack stack) { + return BrewingRecipeRegistry.isValidIngredient(stack); + } + + /** + * Returns true if the item can be used as brewing bottle. + */ + public static boolean isBrewingInput(Level world, ItemStack stack) { + return BrewingRecipeRegistry.isValidInput(stack); + } + + /** + * Returns the burn time for brewing of the given stack. + */ + public static int getBrewingFuelBurntime(Level world, ItemStack stack) { + if (stack.isEmpty()) return 0; + if (stack.getItem() == Items.BLAZE_POWDER) return (400 * 20); + if (stack.getItem() == Items.BLAZE_ROD) return (400 * 40); + return 0; + } + + /** + * Returns brewing burn time and remaining stack if the item shall be used as fuel. + */ + public static Tuple consumeBrewingFuel(Level world, ItemStack stack) { + int burntime = getBrewingFuelBurntime(world, stack); + if (burntime <= 0) return new Tuple<>(0, stack.copy()); + stack = stack.copy(); + stack.shrink(1); + return new Tuple<>(burntime, stack.isEmpty() ? ItemStack.EMPTY : stack); + } + + public static double getCompostingChance(ItemStack stack) { + return ComposterBlock.COMPOSTABLES.getOrDefault(stack.getItem(), 0); + } + + /** + * Returns the enchtments bound to the given stack. + */ + public static Map getEnchantmentsOnItem(Level world, ItemStack stack) { + return (stack.isEmpty() || (stack.getTag() == null)) ? Collections.emptyMap() : EnchantmentHelper.getEnchantments(stack); + } + + // ------------------------------------------------------------------------------------------------------------------- + + /** + * Returns an enchanted book with the given enchantment, emtpy stack if not applicable. + */ + public static ItemStack getEnchantmentBook(Level world, Enchantment enchantment, int level) { + return ((!enchantment.isAllowedOnBooks()) || (level <= 0)) ? ItemStack.EMPTY : EnchantedBookItem.createForEnchantment(new EnchantmentInstance(enchantment, level)); + } + + // ------------------------------------------------------------------------------------------------------------------- + + /** + * Returns the accumulated repair cost for the given enchantments. + */ + public static int getEnchantmentRepairCost(Level world, Map enchantments) { + int repair_cost = 0; + for (Map.Entry e : enchantments.entrySet()) + repair_cost = repair_cost * 2 + 1; // @see: RepairContainer.getNewRepairCost() + return repair_cost; + } + + /** + * Trys to add an enchtment to the given stack, returns boolean success. + */ + public static boolean addEnchantmentOnItem(Level world, ItemStack stack, Enchantment enchantment, int level) { + if (stack.isEmpty() || (level <= 0) || (!stack.isEnchantable()) || (level >= enchantment.getMaxLevel())) + return false; + final Map on_item = getEnchantmentsOnItem(world, stack); + if (on_item.keySet().stream().anyMatch(ench -> ench.isCompatibleWith(enchantment))) return false; + final ItemStack book = EnchantedBookItem.createForEnchantment(new EnchantmentInstance(enchantment, level)); + if ((!(stack.isBookEnchantable(book) && enchantment.isAllowedOnBooks())) && (!stack.canApplyAtEnchantingTable(enchantment)) && (!enchantment.canEnchant(stack))) + return false; + final int existing_level = on_item.getOrDefault(enchantment, 0); + if (existing_level > 0) level = Mth.clamp(level + existing_level, 1, enchantment.getMaxLevel()); + on_item.put(enchantment, level); + EnchantmentHelper.setEnchantments(on_item, stack); + stack.setRepairCost(getEnchantmentRepairCost(world, on_item)); + return true; + } + + /** + * Removes enchantments from a stack, returns the removed enchantments. + */ + public static Map removeEnchantmentsOnItem(Level world, ItemStack stack, BiPredicate filter) { + if (stack.isEmpty()) return Collections.emptyMap(); + final Map on_item = getEnchantmentsOnItem(world, stack); + final Map removed = new HashMap<>(); + for (Map.Entry e : on_item.entrySet()) { + if (filter.test(e.getKey(), e.getValue())) { + removed.put(e.getKey(), e.getValue()); + } + } + for (Enchantment e : removed.keySet()) { + on_item.remove(e); + } + EnchantmentHelper.setEnchantments(on_item, stack); + stack.setRepairCost(getEnchantmentRepairCost(world, on_item)); + return removed; + } + + public static final class CraftingGrid implements CraftingContainer { + private static final CraftingGrid instance3x3 = new CraftingGrid(3, 3); + + final int _width; + + private CraftingGrid(int width, int height) { + _width=width; + } + + private void fill(Container grid) { + for (int i = 0; i < getContainerSize(); ++i) + setItem(i, i >= grid.getContainerSize() ? ItemStack.EMPTY : grid.getItem(i)); + } + + public List getRecipes(Level world, Container grid) { + fill(grid); + return world.getRecipeManager().getRecipesFor(RecipeType.CRAFTING, this, world); + } + + public List getRemainingItems(Level world, Container grid, CraftingRecipe recipe) { + fill(grid); + return recipe.getRemainingItems(this); + } + + public ItemStack getCraftingResult(Level world, Container grid, CraftingRecipe recipe) { + fill(grid); + return recipe.assemble(this, world.registryAccess()); + } + + @Override + public int getWidth() { + return _width; + } + + @Override + public int getHeight() { + return 0; + } + + @Override + public List getItems() { + return null; + } + + @Override + public int getContainerSize() { + return 0; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public ItemStack getItem(int i) { + return null; + } + + @Override + public ItemStack removeItem(int i, int i1) { + return null; + } + + @Override + public ItemStack removeItemNoUpdate(int i) { + return null; + } + + @Override + public void setItem(int i, ItemStack itemStack) { + + } + + @Override + public void setChanged() { + + } + + @Override + public boolean stillValid(Player player) { + return false; + } + + @Override + public void clearContent() { + + } + + @Override + public void fillStackedContents(StackedContents stackedContents) { + + } + } + + public static final class BrewingOutput { + public static final int DEFAULT_BREWING_TIME = 400; + public static final BrewingOutput EMPTY = new BrewingOutput(ItemStack.EMPTY, new SimpleContainer(1), new SimpleContainer(1), 0, 0, DEFAULT_BREWING_TIME); + public final ItemStack item; + public final Container potionInventory; + public final Container ingredientInventory; + public final int potionSlot; + public final int ingredientSlot; + public final int brewTime; + + public BrewingOutput(ItemStack output_potion, Container potion_inventory, Container ingredient_inventory, int potion_slot, int ingredient_slot, int time_needed) { + item = output_potion; + potionInventory = potion_inventory; + ingredientInventory = ingredient_inventory; + potionSlot = potion_slot; + ingredientSlot = ingredient_slot; + brewTime = time_needed; + } + + public static BrewingOutput find(Level world, Container potion_inventory, Container ingredient_inventory) { + for (int potion_slot = 0; potion_slot < potion_inventory.getContainerSize(); ++potion_slot) { + final ItemStack pstack = potion_inventory.getItem(potion_slot); + if (!isBrewingInput(world, pstack)) continue; + for (int ingredient_slot = 0; ingredient_slot < ingredient_inventory.getContainerSize(); ++ingredient_slot) { + final ItemStack istack = ingredient_inventory.getItem(ingredient_slot); + if ((!isBrewingIngredient(world, istack)) || (ingredient_slot == potion_slot) || (isBrewingFuel(world, istack))) + continue; + final ItemStack result = BrewingRecipeRegistry.getOutput(pstack, istack); + if (result.isEmpty()) continue; + return new BrewingOutput(result, potion_inventory, ingredient_inventory, potion_slot, ingredient_slot, DEFAULT_BREWING_TIME); + } + } + return BrewingOutput.EMPTY; + } + } + + +} \ No newline at end of file diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/Fluidics.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/Fluidics.java new file mode 100644 index 0000000..6ac9dbd --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/Fluidics.java @@ -0,0 +1,485 @@ +/* + * @file Fluidics.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * General fluid handling functionality. + */ +package dev.zontreck.libzontreck.edlibmc; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.util.Mth; +import net.minecraft.util.Tuple; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.material.Fluid; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.capabilities.ForgeCapabilities; +import net.minecraftforge.common.capabilities.ICapabilityProvider; +import net.minecraft.nbt.Tag; +import net.minecraftforge.common.util.LazyOptional; +import net.minecraftforge.fluids.FluidActionResult; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fluids.FluidUtil; +import net.minecraftforge.fluids.IFluidTank; +import net.minecraftforge.fluids.capability.IFluidHandler; +import net.minecraftforge.fluids.capability.IFluidHandler.FluidAction; +import net.minecraftforge.fluids.capability.IFluidHandlerItem; +import net.minecraftforge.items.IItemHandler; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.function.Predicate; + + +public class Fluidics { + public static class SingleTankFluidHandler implements IFluidHandler { + private final IFluidTank tank_; + + public SingleTankFluidHandler(IFluidTank tank) { + tank_ = tank; + } + + @Override + public int getTanks() { + return 1; + } + + @Override + public FluidStack getFluidInTank(int tank) { + return tank_.getFluid(); + } + + @Override + public int getTankCapacity(int tank) { + return tank_.getCapacity(); + } + + @Override + public boolean isFluidValid(int tank, @Nonnull FluidStack stack) { + return tank_.isFluidValid(stack); + } + + @Override + public int fill(FluidStack resource, FluidAction action) { + return tank_.fill(resource, action); + } + + @Override + public FluidStack drain(FluidStack resource, FluidAction action) { + return tank_.drain(resource, action); + } + + @Override + public FluidStack drain(int maxDrain, FluidAction action) { + return tank_.drain(maxDrain, action); + } + } + + private static class SingleTankOutputFluidHandler implements IFluidHandler { + private final IFluidTank tank_; + + public SingleTankOutputFluidHandler(IFluidTank tank) { + tank_ = tank; + } + + @Override + public int getTanks() { + return 1; + } + + @Override + public FluidStack getFluidInTank(int tank) { + return tank_.getFluid().copy(); + } + + @Override + public int getTankCapacity(int tank) { + return tank_.getCapacity(); + } + + @Override + public boolean isFluidValid(int tank, @Nonnull FluidStack stack) { + return true; + } + + @Override + public int fill(FluidStack resource, FluidAction action) { + return 0; + } + + @Override + public FluidStack drain(FluidStack resource, FluidAction action) { + return tank_.drain(resource, action); + } + + @Override + public FluidStack drain(int maxDrain, FluidAction action) { + return tank_.drain(maxDrain, action); + } + } + + public static class Tank implements IFluidTank { + private Predicate validator_ = ((e) -> true); + private BiConsumer interaction_notifier_ = ((tank, diff) -> { + }); + private FluidStack fluid_ = FluidStack.EMPTY; + private int capacity_; + private int fill_rate_; + private int drain_rate_; + + public Tank(int capacity) { + this(capacity, capacity, capacity); + } + + public Tank(int capacity, int fill_rate, int drain_rate) { + this(capacity, fill_rate, drain_rate, e -> true); + } + + public Tank(int capacity, int fill_rate, int drain_rate, Predicate validator) { + capacity_ = capacity; + setMaxFillRate(fill_rate); + setMaxDrainRate(drain_rate); + setValidator(validator); + } + + public Tank load(CompoundTag nbt) { + if (nbt.contains("tank", Tag.TAG_COMPOUND)) { + setFluid(FluidStack.loadFluidStackFromNBT(nbt.getCompound("tank"))); + } else { + clear(); + } + return this; + } + + public CompoundTag save(CompoundTag nbt) { + if (!isEmpty()) { + nbt.put("tank", fluid_.writeToNBT(new CompoundTag())); + } + return nbt; + } + + public void reset() { + clear(); + } + + public Tank clear() { + setFluid(null); + return this; + } + + public int getCapacity() { + return capacity_; + } + + public Tank setCapacity(int capacity) { + capacity_ = capacity; + return this; + } + + public int getMaxDrainRate() { + return drain_rate_; + } + + public Tank setMaxDrainRate(int rate) { + drain_rate_ = Mth.clamp(rate, 0, capacity_); + return this; + } + + public int getMaxFillRate() { + return fill_rate_; + } + + public Tank setMaxFillRate(int rate) { + fill_rate_ = Mth.clamp(rate, 0, capacity_); + return this; + } + + public Tank setValidator(Predicate validator) { + validator_ = (validator != null) ? validator : ((e) -> true); + return this; + } + + public Tank setInteractionNotifier(BiConsumer notifier) { + interaction_notifier_ = (notifier != null) ? notifier : ((tank, diff) -> { + }); + return this; + } + + public LazyOptional createFluidHandler() { + return LazyOptional.of(() -> new Fluidics.SingleTankFluidHandler(this)); + } + + public LazyOptional createOutputFluidHandler() { + return LazyOptional.of(() -> new Fluidics.SingleTankOutputFluidHandler(this)); + } + + // IFluidTank ------------------------------------------------------------------------------------ + + @Nonnull + public FluidStack getFluid() { + return fluid_; + } + + public void setFluid(@Nullable FluidStack stack) { + fluid_ = (stack == null) ? FluidStack.EMPTY : stack; + } + + public int getFluidAmount() { + return fluid_.getAmount(); + } + + public boolean isEmpty() { + return fluid_.isEmpty(); + } + + public boolean isFull() { + return getFluidAmount() >= getCapacity(); + } + + public boolean isFluidValid(FluidStack stack) { + return validator_.test(stack); + } + + public boolean isFluidEqual(FluidStack stack) { + return (stack == null) ? (fluid_.isEmpty()) : fluid_.isFluidEqual(stack); + } + + @Override + public int fill(FluidStack fs, FluidAction action) { + if ((fs == null) || fs.isEmpty() || (!isFluidValid(fs))) { + return 0; + } else if (action.simulate()) { + if (fluid_.isEmpty()) return Math.min(capacity_, fs.getAmount()); + if (!fluid_.isFluidEqual(fs)) return 0; + return Math.min(capacity_ - fluid_.getAmount(), fs.getAmount()); + } else if (fluid_.isEmpty()) { + fluid_ = new FluidStack(fs, Math.min(capacity_, fs.getAmount())); + return fluid_.getAmount(); + } else if (!fluid_.isFluidEqual(fs)) { + return 0; + } else { + int amount = capacity_ - fluid_.getAmount(); + if (fs.getAmount() < amount) { + fluid_.grow(fs.getAmount()); + amount = fs.getAmount(); + } else { + fluid_.setAmount(capacity_); + } + if (amount != 0) interaction_notifier_.accept(this, amount); + return amount; + } + } + + @Nonnull + public FluidStack drain(int maxDrain) { + return drain(maxDrain, FluidAction.EXECUTE); + } + + @Nonnull + @Override + public FluidStack drain(FluidStack fs, FluidAction action) { + return ((fs.isEmpty()) || (!fs.isFluidEqual(fluid_))) ? FluidStack.EMPTY : drain(fs.getAmount(), action); + } + + @Nonnull + @Override + public FluidStack drain(int maxDrain, FluidAction action) { + final int amount = Math.min(fluid_.getAmount(), maxDrain); + final FluidStack stack = new FluidStack(fluid_, amount); + if ((amount > 0) && action.execute()) { + fluid_.shrink(amount); + if (fluid_.isEmpty()) fluid_ = FluidStack.EMPTY; + if (amount != 0) interaction_notifier_.accept(this, -amount); + } + return stack; + } + } + + // ------------------------------------------------------------------------------------------------------------------- + + public static @Nullable IFluidHandler handler(Level world, BlockPos pos, @Nullable Direction side) { + return FluidUtil.getFluidHandler(world, pos, side).orElse(null); + } + + /** + * Fills or drains items with fluid handlers from or into tile blocks with fluid handlers. + */ + public static boolean manualFluidHandlerInteraction(Level world, BlockPos pos, @Nullable Direction side, Player player, InteractionHand hand) { + return manualTrackedFluidHandlerInteraction(world, pos, side, player, hand) != null; + } + + public static boolean manualFluidHandlerInteraction(Player player, InteractionHand hand, IFluidHandler handler) { + return FluidUtil.interactWithFluidHandler(player, hand, handler); + } + + /** + * Fills or drains items with fluid handlers from or into tile blocks with fluid handlers. + * Returns the fluid and (possibly negative) amount that transferred from the item into the block. + */ + public static @Nullable Tuple manualTrackedFluidHandlerInteraction(Level world, BlockPos pos, @Nullable Direction side, Player player, InteractionHand hand) { + if (world.isClientSide()) return null; + final ItemStack held = player.getItemInHand(hand); + if (held.isEmpty()) return null; + final IFluidHandler fh = handler(world, pos, side); + if (fh == null) return null; + final IItemHandler ih = player.getCapability(ForgeCapabilities.ITEM_HANDLER).orElse(null); + if (ih == null) return null; + FluidActionResult far = FluidUtil.tryFillContainerAndStow(held, fh, ih, Integer.MAX_VALUE, player, true); + if (!far.isSuccess()) far = FluidUtil.tryEmptyContainerAndStow(held, fh, ih, Integer.MAX_VALUE, player, true); + if (!far.isSuccess()) return null; + final ItemStack rstack = far.getResult().copy(); + player.setItemInHand(hand, far.getResult()); + final IFluidHandler fh_before = FluidUtil.getFluidHandler(held).orElse(null); + final IFluidHandler fh_after = FluidUtil.getFluidHandler(rstack).orElse(null); + if ((fh_before == null) || (fh_after == null) || (fh_after.getTanks() != fh_before.getTanks())) + return null; // should not be, but y'never know. + for (int i = 0; i < fh_before.getTanks(); ++i) { + final int vol_before = fh_before.getFluidInTank(i).getAmount(); + final int vol_after = fh_after.getFluidInTank(i).getAmount(); + if (vol_before != vol_after) { + return new Tuple<>( + (vol_before > 0) ? (fh_before.getFluidInTank(i).getFluid()) : (fh_after.getFluidInTank(i).getFluid()), + (vol_before - vol_after) + ); + } + } + return null; + } + + public static boolean manualFluidHandlerInteraction(Player player, InteractionHand hand, Level world, BlockPos pos, @Nullable Direction side) { + return FluidUtil.interactWithFluidHandler(player, hand, world, pos, side); + } + + public static int fill(Level world, BlockPos pos, Direction side, FluidStack fs, FluidAction action) { + IFluidHandler fh = FluidUtil.getFluidHandler(world, pos, side).orElse(null); + return (fh == null) ? (0) : (fh.fill(fs, action)); + } + + public static int fill(Level world, BlockPos pos, Direction side, FluidStack fs) { + return fill(world, pos, side, fs, FluidAction.EXECUTE); + } + + /** + * Fluid tank access when itemized. + */ + public static class FluidContainerItemCapabilityWrapper implements IFluidHandlerItem, ICapabilityProvider { + private final LazyOptional handler_ = LazyOptional.of(() -> this); + private final Function nbt_getter_; + private final BiConsumer nbt_setter_; + private final Predicate validator_; + private final ItemStack container_; + private final int capacity_; + private final int transfer_rate_; + + public FluidContainerItemCapabilityWrapper(ItemStack container, int capacity, int transfer_rate, + Function nbt_getter, + BiConsumer nbt_setter, + Predicate validator) { + container_ = container; + capacity_ = capacity; + transfer_rate_ = transfer_rate; + nbt_getter_ = nbt_getter; + nbt_setter_ = nbt_setter; + validator_ = (validator != null) ? validator : (e -> true); + } + + @Override + public LazyOptional getCapability(Capability capability, @Nullable Direction side) { + return (capability == ForgeCapabilities.FLUID_HANDLER) ? handler_.cast() : LazyOptional.empty(); + } + + protected FluidStack readnbt() { + final CompoundTag nbt = nbt_getter_.apply(container_); + return ((nbt == null) || (nbt.isEmpty())) ? FluidStack.EMPTY : FluidStack.loadFluidStackFromNBT(nbt); + } + + protected void writenbt(FluidStack fs) { + CompoundTag nbt = new CompoundTag(); + if (!fs.isEmpty()) fs.writeToNBT(nbt); + nbt_setter_.accept(container_, nbt); + } + + @Override + public ItemStack getContainer() { + return container_; + } + + @Override + public int getTanks() { + return 1; + } + + @Override + public FluidStack getFluidInTank(int tank) { + return readnbt(); + } + + @Override + public int getTankCapacity(int tank) { + return capacity_; + } + + @Override + public boolean isFluidValid(int tank, FluidStack fs) { + return isFluidValid(fs); + } + + public boolean isFluidValid(FluidStack fs) { + return validator_.test(fs); + } + + @Override + public int fill(FluidStack fs, FluidAction action) { + if ((fs.isEmpty()) || (!isFluidValid(fs) || (container_.getCount() != 1))) return 0; + FluidStack tank = readnbt(); + final int amount = Math.min(Math.min(fs.getAmount(), transfer_rate_), capacity_ - tank.getAmount()); + if (amount <= 0) return 0; + if (tank.isEmpty()) { + if (action.execute()) { + tank = new FluidStack(fs.getFluid(), amount, fs.getTag()); + writenbt(tank); + } + } else { + if (!tank.isFluidEqual(fs)) { + return 0; + } else if (action.execute()) { + tank.grow(amount); + writenbt(tank); + } + } + return amount; + } + + @Override + public FluidStack drain(FluidStack fs, FluidAction action) { + if ((fs.isEmpty()) || (container_.getCount() != 1)) return FluidStack.EMPTY; + final FluidStack tank = readnbt(); + if ((!tank.isEmpty()) && (!tank.isFluidEqual(fs))) return FluidStack.EMPTY; + return drain(fs.getAmount(), action); + } + + @Override + public FluidStack drain(int max, FluidAction action) { + if ((max <= 0) || (container_.getCount() != 1)) return FluidStack.EMPTY; + FluidStack tank = readnbt(); + if (tank.isEmpty()) return FluidStack.EMPTY; + final int amount = Math.min(Math.min(tank.getAmount(), max), transfer_rate_); + final FluidStack fs = tank.copy(); + fs.setAmount(amount); + if (action.execute()) { + tank.shrink(amount); + writenbt(tank); + } + return fs; + } + } + +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/Guis.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/Guis.java new file mode 100644 index 0000000..1374a19 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/Guis.java @@ -0,0 +1,466 @@ +/* + * @file Guis.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * Gui Wrappers and Widgets. + */ +package dev.zontreck.libzontreck.edlibmc; + +import com.mojang.blaze3d.platform.Window; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; +import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.client.renderer.entity.ItemRenderer; +import net.minecraft.client.sounds.SoundManager; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import java.util.Arrays; +import java.util.function.Consumer; +import java.util.function.Function; + + +public class Guis { + // ------------------------------------------------------------------------------------------------------------------- + // Gui base + // ------------------------------------------------------------------------------------------------------------------- + + @OnlyIn(Dist.CLIENT) + public static abstract class ContainerGui extends AbstractContainerScreen { + protected final ResourceLocation background_image_; + protected final Player player_; + protected final Guis.BackgroundImage gui_background_; + protected final TooltipDisplay tooltip_ = new TooltipDisplay(); + + public ContainerGui(T menu, Inventory player_inv, Component title, String background_image, int width, int height) { + super(menu, player_inv, title); + this.background_image_ = new ResourceLocation(Auxiliaries.modid(), background_image); + this.player_ = player_inv.player; + this.imageWidth = width; + this.imageHeight = height; + gui_background_ = new Guis.BackgroundImage(background_image_, width, height, Coord2d.ORIGIN); + } + + public ContainerGui(T menu, Inventory player_inv, Component title, String background_image) { + super(menu, player_inv, title); + this.background_image_ = new ResourceLocation(Auxiliaries.modid(), background_image); + this.player_ = player_inv.player; + gui_background_ = new Guis.BackgroundImage(background_image_, imageWidth, imageHeight, Coord2d.ORIGIN); + } + + @Override + public void init() { + super.init(); + gui_background_.init(this, Coord2d.ORIGIN).show(); + } + + @Override + public void render(GuiGraphics mx, int mouseX, int mouseY, float partialTicks) { + renderBackground(mx); + super.render(mx, mouseX, mouseY, partialTicks); + if (!tooltip_.render(mx, this, mouseX, mouseY)) renderTooltip(mx, mouseX, mouseY); + } + + @Override + protected void renderLabels(GuiGraphics mx, int x, int y) { + } + + @Override + @SuppressWarnings("deprecation") + protected final void renderBg(GuiGraphics mx, float partialTicks, int mouseX, int mouseY) { + RenderSystem.setShader(GameRenderer::getPositionTexShader); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + RenderSystem.enableDepthTest(); + gui_background_.draw(mx, this); + renderBgWidgets(mx, partialTicks, mouseX, mouseY); + RenderSystem.disableBlend(); + } + + public final ResourceLocation getBackgroundImage() { + return background_image_; + } + + protected void renderBgWidgets(GuiGraphics mx, float partialTicks, int mouseX, int mouseY) { + } + + protected void renderItemTemplate(GuiGraphics mx, ItemStack stack, int x, int y) { + final int x0 = getGuiLeft(); + final int y0 = getGuiTop(); + + mx.renderFakeItem(stack, x0 + x, y0 + y); + RenderSystem.disableColorLogicOp(); //RenderSystem.disableColorMaterial(); + RenderSystem.enableDepthTest(); //RenderSystem.enableAlphaTest(); + RenderSystem.defaultBlendFunc(); + RenderSystem.enableBlend(); + RenderSystem.colorMask(true, true, true, true); + RenderSystem.setShaderColor(0.7f, 0.7f, 0.7f, 0.8f); + RenderSystem.setShaderTexture(0, background_image_); + mx.blit(background_image_,x0 + x, y0 + y, x, y, 16, 16); + RenderSystem.setShaderColor(1f, 1f, 1f, 1f); + } + } + + // ------------------------------------------------------------------------------------------------------------------- + // Gui elements + // ------------------------------------------------------------------------------------------------------------------- + + @OnlyIn(Dist.CLIENT) + public static class Coord2d { + public static final Coord2d ORIGIN = new Coord2d(0, 0); + public final int x, y; + + public Coord2d(int x, int y) { + this.x = x; + this.y = y; + } + + public static Coord2d of(int x, int y) { + return new Coord2d(x, y); + } + + public String toString() { + return "[" + x + "," + y + "]"; + } + } + + @OnlyIn(Dist.CLIENT) + public static class UiWidget extends AbstractWidget { + protected static final Component EMPTY_TEXT = Component.literal(""); + protected static final Function NO_TOOLTIP = (uiw) -> EMPTY_TEXT; + + private final Minecraft mc_; + private Function tooltip_ = NO_TOOLTIP; + private Screen parent_; + + public UiWidget(int x, int y, int width, int height, Component title) { + super(x, y, width, height, title); + mc_ = Minecraft.getInstance(); + } + + public UiWidget init(Screen parent) { + this.parent_ = parent; + this.setX(((parent instanceof AbstractContainerScreen) ? ((AbstractContainerScreen) parent).getGuiLeft() : 0)); + this.setY(((parent instanceof AbstractContainerScreen) ? ((AbstractContainerScreen) parent).getGuiTop() : 0)); + return this; + } + + public UiWidget init(Screen parent, Coord2d position) { + this.parent_ = parent; + this.setX(position.x + ((parent instanceof AbstractContainerScreen) ? ((AbstractContainerScreen) parent).getGuiLeft() : 0)); + this.setY(position.y + ((parent instanceof AbstractContainerScreen) ? ((AbstractContainerScreen) parent).getGuiTop() : 0)); + return this; + } + + public final UiWidget tooltip(Function tip) { + tooltip_ = tip; + return this; + } + + public final UiWidget tooltip(Component tip) { + tooltip_ = (o) -> tip; + return this; + } + + public final int getWidth() { + return this.width; + } + + @Override + protected void updateWidgetNarration(NarrationElementOutput narrationElementOutput) { + + } + + public final int getHeight() { + return this.height; + } + + public Coord2d getMousePosition() { + final Window win = mc_.getWindow(); + return Coord2d.of( + Mth.clamp(((int) (mc_.mouseHandler.xpos() * (double) win.getGuiScaledWidth() / (double) win.getScreenWidth())) - this.getX(), -1, this.width + 1), + Mth.clamp(((int) (mc_.mouseHandler.ypos() * (double) win.getGuiScaledHeight() / (double) win.getScreenHeight())) - this.getY(), -1, this.height + 1) + ); + } + + protected final Coord2d screenCoordinates(Coord2d xy, boolean reverse) { + return (reverse) ? (Coord2d.of(xy.x + getX(), xy.y + getY())) : (Coord2d.of(xy.x - getX(), xy.y - getY())); + } + + public UiWidget show() { + visible = true; + return this; + } + + public UiWidget hide() { + visible = false; + return this; + } + + @Override + public void renderWidget(GuiGraphics mxs, int mouseX, int mouseY, float partialTicks) { + //super.renderWidget(mxs, mouseX, mouseY, partialTicks); + if (isHovered) renderToolTip(mxs, mouseX, mouseY); + } + + @SuppressWarnings("all") + public void renderToolTip(GuiGraphics mx, int mouseX, int mouseY) { + if (!visible || (!active) || (tooltip_ == NO_TOOLTIP)) return; + final Component tip = tooltip_.apply(this); + if (tip.getString().trim().isEmpty()) return; + mx.renderTooltip(mc_.font, Arrays.asList(tip.getVisualOrderText()), mouseX, mouseY); + } + } + + @OnlyIn(Dist.CLIENT) + public static class HorizontalProgressBar extends UiWidget { + private final Coord2d texture_position_base_; + private final Coord2d texture_position_filled_; + private final ResourceLocation atlas_; + private double progress_max_ = 100; + private double progress_ = 0; + + public HorizontalProgressBar(ResourceLocation atlas, int width, int height, Coord2d base_texture_xy, Coord2d filled_texture_xy) { + super(0, 0, width, height, EMPTY_TEXT); + atlas_ = atlas; + texture_position_base_ = base_texture_xy; + texture_position_filled_ = filled_texture_xy; + } + + public HorizontalProgressBar setProgress(double progress) { + progress_ = Mth.clamp(progress, 0, progress_max_); + return this; + } + + public double getProgress() { + return progress_; + } + + public HorizontalProgressBar setMaxProgress(double progress) { + progress_max_ = Math.max(progress, 0); + return this; + } + + public double getMaxProgress() { + return progress_max_; + } + + public HorizontalProgressBar show() { + visible = true; + return this; + } + + public HorizontalProgressBar hide() { + visible = false; + return this; + } + + @Override + public void playDownSound(SoundManager handler) { + } + + @Override + public void renderWidget(GuiGraphics mxs, int mouseX, int mouseY, float partialTicks) { + RenderSystem.setShaderTexture(0, atlas_); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, this.alpha); + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + RenderSystem.enableDepthTest(); + mxs.blit(atlas_, getX(), getY(), texture_position_base_.x, texture_position_base_.y, width, height); + if ((progress_max_ > 0) && (progress_ > 0)) { + int w = Mth.clamp((int) Math.round((progress_ * width) / progress_max_), 0, width); + mxs.blit(atlas_, getX(), getY(), texture_position_filled_.x, texture_position_filled_.y, w, height); + } + if (isHovered) renderToolTip(mxs, mouseX, mouseY); + } + } + + @OnlyIn(Dist.CLIENT) + public static class BackgroundImage extends UiWidget { + private final ResourceLocation atlas_; + private final Coord2d atlas_position_; + public boolean visible; + + public BackgroundImage(ResourceLocation atlas, int width, int height, Coord2d atlas_position) { + super(0, 0, width, height, EMPTY_TEXT); + atlas_ = atlas; + atlas_position_ = atlas_position; + this.width = width; + this.height = height; + visible = true; + } + + public void draw(GuiGraphics mx, Screen parent) { + if (!visible) return; + RenderSystem.setShaderTexture(0, atlas_); + mx.blit(atlas_, getX(), getY(), atlas_position_.x, atlas_position_.y, width, height); + } + } + + @OnlyIn(Dist.CLIENT) + public static class CheckBox extends UiWidget { + private final Coord2d texture_position_off_; + private final Coord2d texture_position_on_; + private final ResourceLocation atlas_; + private boolean checked_ = false; + private Consumer on_click_ = (checkbox) -> { + }; + + public CheckBox(ResourceLocation atlas, int width, int height, Coord2d atlas_texture_position_off, Coord2d atlas_texture_position_on) { + super(0, 0, width, height, EMPTY_TEXT); + texture_position_off_ = atlas_texture_position_off; + texture_position_on_ = atlas_texture_position_on; + atlas_ = atlas; + } + + public boolean checked() { + return checked_; + } + + public CheckBox checked(boolean on) { + checked_ = on; + return this; + } + + public CheckBox onclick(Consumer action) { + on_click_ = action; + return this; + } + + @Override + public void onClick(double mouseX, double mouseY) { + checked_ = !checked_; + on_click_.accept(this); + } + + @Override + public void renderWidget(GuiGraphics mxs, int mouseX, int mouseY, float partialTicks) { + RenderSystem.setShader(GameRenderer::getPositionTexShader); + RenderSystem.setShaderTexture(0, atlas_); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, this.alpha); + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + RenderSystem.enableDepthTest(); + Coord2d pos = checked_ ? texture_position_on_ : texture_position_off_; + mxs.blit(atlas_, getX(), getY(), pos.x, pos.y, width, height); + if (isHovered) renderToolTip(mxs, mouseX, mouseY); + } + } + + @OnlyIn(Dist.CLIENT) + public static class ImageButton extends UiWidget { + private final Coord2d texture_position_; + private final ResourceLocation atlas_; + private Consumer on_click_ = (bt) -> { + }; + + + public ImageButton(ResourceLocation atlas, int width, int height, Coord2d atlas_texture_position) { + super(0, 0, width, height, Component.empty()); + texture_position_ = atlas_texture_position; + atlas_ = atlas; + } + + public ImageButton onclick(Consumer action) { + on_click_ = action; + return this; + } + + @Override + public void onClick(double mouseX, double mouseY) { + on_click_.accept(this); + } + + @Override + public void renderWidget(GuiGraphics mxs, int mouseX, int mouseY, float partialTicks) { + RenderSystem.setShader(GameRenderer::getPositionTexShader); + RenderSystem.setShaderTexture(0, atlas_); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, this.alpha); + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + RenderSystem.enableDepthTest(); + Coord2d pos = texture_position_; + mxs.blit(atlas_, getX(), getY(), pos.x, pos.y, width, height); + if (isHovered) renderToolTip(mxs, mouseX, mouseY); + } + } + + @OnlyIn(Dist.CLIENT) + public static class Image extends UiWidget { + private final Coord2d texture_position_; + private final ResourceLocation atlas_; + + public Image(ResourceLocation atlas, int width, int height, Coord2d atlas_texture_position) { + super(0, 0, width, height, Component.empty()); + texture_position_ = atlas_texture_position; + atlas_ = atlas; + } + + @Override + public void onClick(double mouseX, double mouseY) { + } + + @Override + public void renderWidget(GuiGraphics mxs, int mouseX, int mouseY, float partialTicks) { + RenderSystem.setShader(GameRenderer::getPositionTexShader); + RenderSystem.setShaderTexture(0, atlas_); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, this.alpha); + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + RenderSystem.enableDepthTest(); + Coord2d pos = texture_position_; + mxs.blit(atlas_, getX(), getY(), pos.x, pos.y, width, height); + if (isHovered) renderToolTip(mxs, mouseX, mouseY); + } + } + + @OnlyIn(Dist.CLIENT) + public static class TextBox extends net.minecraft.client.gui.components.EditBox { + public TextBox(int x, int y, int width, int height, Component title, Font font) { + super(font, x, y, width, height, title); + setBordered(false); + } + + public TextBox withMaxLength(int len) { + super.setMaxLength(len); + return this; + } + + public TextBox withBordered(boolean b) { + super.setBordered(b); + return this; + } + + public TextBox withValue(String s) { + super.setValue(s); + return this; + } + + public TextBox withEditable(boolean e) { + super.setEditable(e); + return this; + } + + public TextBox withResponder(Consumer r) { + super.setResponder(r); + return this; + } + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/Inventories.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/Inventories.java new file mode 100644 index 0000000..e7442e0 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/Inventories.java @@ -0,0 +1,1134 @@ +/* + * @file Inventories.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * General inventory item handling functionality. + */ +package dev.zontreck.libzontreck.edlibmc; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.NonNullList; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.util.Mth; +import net.minecraft.world.*; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; +import net.minecraft.nbt.Tag; +import net.minecraftforge.common.capabilities.ForgeCapabilities; +import net.minecraftforge.common.util.LazyOptional; +import net.minecraftforge.items.IItemHandler; +import net.minecraftforge.items.ItemHandlerHelper; +import net.minecraftforge.items.wrapper.InvWrapper; +import net.minecraftforge.items.wrapper.PlayerMainInvWrapper; +import net.minecraftforge.items.wrapper.SidedInvWrapper; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.*; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.BiPredicate; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + + +public class Inventories { + public static boolean areItemStacksIdentical(ItemStack a, ItemStack b) { + return (a.getItem() == b.getItem()) && ItemStack.isSameItemSameTags(a, b); + } + + public static boolean areItemStacksDifferent(ItemStack a, ItemStack b) { + return (a.getItem() != b.getItem()) || (!ItemStack.isSameItemSameTags(a, b)); + } + + public static IItemHandler itemhandler(Level world, BlockPos pos, @Nullable Direction side) { + BlockEntity te = world.getBlockEntity(pos); + if (te == null) return null; + IItemHandler ih = te.getCapability(ForgeCapabilities.ITEM_HANDLER, side).orElse(null); + if (ih != null) return ih; + if ((side != null) && (te instanceof WorldlyContainer)) return new SidedInvWrapper((WorldlyContainer) te, side); + if (te instanceof Container) return new InvWrapper((Container) te); + return null; + } + + public static IItemHandler itemhandler(Level world, BlockPos pos, @Nullable Direction side, boolean including_entities) { + IItemHandler ih = itemhandler(world, pos, side); + if (ih != null) return ih; + if (!including_entities) return null; + Entity entity = world.getEntitiesOfClass(Entity.class, new AABB(pos), (e) -> (e instanceof Container)).stream().findFirst().orElse(null); + return (entity == null) ? (null) : (itemhandler(entity, side)); + } + + public static IItemHandler itemhandler(Player player) { + return new PlayerMainInvWrapper(player.getInventory()); + } + + public static IItemHandler itemhandler(Entity entity) { + return itemhandler(entity, null); + } + + public static IItemHandler itemhandler(Entity entity, @Nullable Direction side) { + if (entity == null) return null; + final IItemHandler ih = entity.getCapability(ForgeCapabilities.ITEM_HANDLER, side).orElse(null); + if (ih != null) return ih; + if (entity instanceof Container container) return (new InvWrapper(container)); + return null; + } + + public static boolean insertionPossible(Level world, BlockPos pos, @Nullable Direction side, boolean including_entities) { + return itemhandler(world, pos, side, including_entities) != null; + } + + public static ItemStack insert(IItemHandler handler, ItemStack stack, boolean simulate) { + return ItemHandlerHelper.insertItemStacked(handler, stack, simulate); + } + + public static ItemStack insert(BlockEntity te, @Nullable Direction side, ItemStack stack, boolean simulate) { + if (te == null) return stack; + IItemHandler hnd = te.getCapability(ForgeCapabilities.ITEM_HANDLER, side).orElse(null); + if (hnd == null) { + if ((side != null) && (te instanceof WorldlyContainer)) { + hnd = new SidedInvWrapper((WorldlyContainer) te, side); + } else if (te instanceof Container) { + hnd = new InvWrapper((Container) te); + } + } + return (hnd == null) ? stack : insert(hnd, stack, simulate); + } + + public static ItemStack insert(Level world, BlockPos pos, @Nullable Direction side, ItemStack stack, boolean simulate, boolean including_entities) { + return insert(itemhandler(world, pos, side, including_entities), stack, simulate); + } + + public static ItemStack insert(Level world, BlockPos pos, @Nullable Direction side, ItemStack stack, boolean simulate) { + return insert(world, pos, side, stack, simulate, false); + } + + public static ItemStack extract(IItemHandler inventory, @Nullable ItemStack match, int amount, boolean simulate) { + if ((inventory == null) || (amount <= 0) || ((match != null) && (match.isEmpty()))) return ItemStack.EMPTY; + final int max = inventory.getSlots(); + ItemStack out_stack = ItemStack.EMPTY; + for (int i = 0; i < max; ++i) { + final ItemStack stack = inventory.getStackInSlot(i); + if (stack.isEmpty()) continue; + if (out_stack.isEmpty()) { + if ((match != null) && areItemStacksDifferent(stack, match)) continue; + out_stack = inventory.extractItem(i, amount, simulate); + } else if (areItemStacksIdentical(stack, out_stack)) { + ItemStack es = inventory.extractItem(i, (amount - out_stack.getCount()), simulate); + out_stack.grow(es.getCount()); + } + if (out_stack.getCount() >= amount) break; + } + return out_stack; + } + + private static ItemStack checked(ItemStack stack) { + return stack.isEmpty() ? ItemStack.EMPTY : stack; + } + + public static Container copyOf(Container src) { + final int size = src.getContainerSize(); + SimpleContainer dst = new SimpleContainer(size); + for (int i = 0; i < size; ++i) dst.setItem(i, src.getItem(i).copy()); + return dst; + } + + //-------------------------------------------------------------------------------------------------------------------- + + public static ItemStack insert(InventoryRange[] to_ranges, ItemStack stack) { + ItemStack remaining = stack.copy(); + for (InventoryRange range : to_ranges) { + remaining = range.insert(remaining, false, 0, false, true); + if (remaining.isEmpty()) return remaining; + } + return remaining; + } + + //-------------------------------------------------------------------------------------------------------------------- + + public static class MappedItemHandler implements IItemHandler { + private final BiPredicate extraction_predicate_; + private final BiPredicate insertion_predicate_; + private final BiConsumer insertion_notifier_; + private final BiConsumer extraction_notifier_; + private final List slot_map_; + private final Container inv_; + + public MappedItemHandler(Container inv, BiPredicate extraction_predicate, BiPredicate insertion_predicate, BiConsumer insertion_notifier, BiConsumer extraction_notifier) { + inv_ = inv; + extraction_predicate_ = extraction_predicate; + insertion_predicate_ = insertion_predicate; + insertion_notifier_ = insertion_notifier; + extraction_notifier_ = extraction_notifier; + slot_map_ = IntStream.range(0, inv.getContainerSize()).boxed().collect(Collectors.toList()); + } + + public MappedItemHandler(Container inv, List slot_map, BiPredicate extraction_predicate, BiPredicate insertion_predicate, BiConsumer insertion_notifier, BiConsumer extraction_notifier) { + inv_ = inv; + extraction_predicate_ = extraction_predicate; + insertion_predicate_ = insertion_predicate; + insertion_notifier_ = insertion_notifier; + extraction_notifier_ = extraction_notifier; + slot_map_ = slot_map; + } + + public MappedItemHandler(Container inv, List slot_map, BiPredicate extraction_predicate, BiPredicate insertion_predicate) { + this(inv, slot_map, extraction_predicate, insertion_predicate, (i, s) -> { + }, (i, s) -> { + }); + } + + public MappedItemHandler(Container inv, BiPredicate extraction_predicate, BiPredicate insertion_predicate) { + this(inv, IntStream.range(0, inv.getContainerSize()).boxed().collect(Collectors.toList()), extraction_predicate, insertion_predicate); + } + + public MappedItemHandler(Container inv) { + this(inv, (i, s) -> true, (i, s) -> true); + } + + @Override + public int hashCode() { + return inv_.hashCode(); + } + + @Override + public boolean equals(Object o) { + return (o == this) || ((o != null) && (getClass() == o.getClass()) && (inv_.equals(((MappedItemHandler) o).inv_))); + } + + // IItemHandler ----------------------------------------------------------------------------------------------- + + @Override + public int getSlots() { + return slot_map_.size(); + } + + @Override + @Nonnull + public ItemStack getStackInSlot(int slot) { + return (slot >= slot_map_.size()) ? ItemStack.EMPTY : inv_.getItem(slot_map_.get(slot)); + } + + @Override + public int getSlotLimit(int slot) { + return inv_.getMaxStackSize(); + } + + @Override + public boolean isItemValid(int slot, @Nonnull ItemStack stack) { + if (slot >= slot_map_.size()) return false; + slot = slot_map_.get(slot); + return insertion_predicate_.test(slot, stack) && inv_.canPlaceItem(slot, stack); + } + + @Override + @Nonnull + public ItemStack insertItem(int slot, @Nonnull ItemStack stack, boolean simulate) { + if (stack.isEmpty()) return ItemStack.EMPTY; + if (slot >= slot_map_.size()) return stack; + slot = slot_map_.get(slot); + if (!insertion_predicate_.test(slot, stack)) return stack; + if (!inv_.canPlaceItem(slot, stack)) return stack; + ItemStack sst = inv_.getItem(slot); + final int slot_limit = inv_.getMaxStackSize(); + if (!sst.isEmpty()) { + if (sst.getCount() >= Math.min(sst.getMaxStackSize(), slot_limit)) return stack; + if (!ItemHandlerHelper.canItemStacksStack(stack, sst)) return stack; + final int limit = Math.min(stack.getMaxStackSize(), slot_limit) - sst.getCount(); + if (stack.getCount() <= limit) { + if (!simulate) { + stack = stack.copy(); + stack.grow(sst.getCount()); + inv_.setItem(slot, stack); + inv_.setChanged(); + insertion_notifier_.accept(slot, sst); + } + return ItemStack.EMPTY; + } else { + stack = stack.copy(); + if (simulate) { + stack.shrink(limit); + } else { + final ItemStack diff = stack.split(limit); + sst.grow(diff.getCount()); + inv_.setItem(slot, sst); + inv_.setChanged(); + insertion_notifier_.accept(slot, diff); + } + return stack; + } + } else { + final int limit = Math.min(slot_limit, stack.getMaxStackSize()); + if (stack.getCount() >= limit) { + stack = stack.copy(); + final ItemStack ins = stack.split(limit); + if (!simulate) { + inv_.setItem(slot, ins); + inv_.setChanged(); + insertion_notifier_.accept(slot, ins.copy()); + } + if (stack.isEmpty()) { + stack = ItemStack.EMPTY; + } + return stack; + } else { + if (!simulate) { + inv_.setItem(slot, stack.copy()); + inv_.setChanged(); + insertion_notifier_.accept(slot, stack.copy()); + } + return ItemStack.EMPTY; + } + } + } + + @Override + public ItemStack extractItem(int slot, int amount, boolean simulate) { + if (amount <= 0) return ItemStack.EMPTY; + if (slot >= slot_map_.size()) return ItemStack.EMPTY; + slot = slot_map_.get(slot); + ItemStack stack = inv_.getItem(slot); + if (!extraction_predicate_.test(slot, stack)) return ItemStack.EMPTY; + if (simulate) { + stack = stack.copy(); + if (amount < stack.getCount()) stack.setCount(amount); + } else { + stack = inv_.removeItem(slot, Math.min(stack.getCount(), amount)); + inv_.setChanged(); + extraction_notifier_.accept(slot, stack.copy()); + } + return stack; + } + + // Factories -------------------------------------------------------------------------------------------- + + public static LazyOptional createGenericHandler(Container inv, BiPredicate extraction_predicate, BiPredicate insertion_predicate, BiConsumer insertion_notifier, BiConsumer extraction_notifier) { + return LazyOptional.of(() -> new MappedItemHandler(inv, extraction_predicate, insertion_predicate, insertion_notifier, extraction_notifier)); + } + + public static LazyOptional createGenericHandler(Container inv, BiPredicate extraction_predicate, BiPredicate insertion_predicate, BiConsumer insertion_notifier, BiConsumer extraction_notifier, List slot_map) { + return LazyOptional.of(() -> new MappedItemHandler(inv, slot_map, extraction_predicate, insertion_predicate, insertion_notifier, extraction_notifier)); + } + + public static LazyOptional createGenericHandler(Container inv, BiPredicate extraction_predicate, BiPredicate insertion_predicate, List slot_map) { + return LazyOptional.of(() -> new MappedItemHandler(inv, slot_map, extraction_predicate, insertion_predicate)); + } + + public static LazyOptional createGenericHandler(Container inv, BiPredicate extraction_predicate, BiPredicate insertion_predicate) { + return LazyOptional.of(() -> new MappedItemHandler(inv, extraction_predicate, insertion_predicate)); + } + + public static LazyOptional createGenericHandler(Container inv) { + return LazyOptional.of(() -> new MappedItemHandler(inv)); + } + + public static LazyOptional createExtractionHandler(Container inv, BiPredicate extraction_predicate, List slot_map) { + return LazyOptional.of(() -> new MappedItemHandler(inv, slot_map, extraction_predicate, (i, s) -> false)); + } + + public static LazyOptional createExtractionHandler(Container inv, BiPredicate extraction_predicate) { + return LazyOptional.of(() -> new MappedItemHandler(inv, extraction_predicate, (i, s) -> false)); + } + + public static LazyOptional createExtractionHandler(Container inv, Integer... slots) { + return LazyOptional.of(() -> new MappedItemHandler(inv, Arrays.asList(slots), (i, s) -> true, (i, s) -> false)); + } + + public static LazyOptional createExtractionHandler(Container inv) { + return LazyOptional.of(() -> new MappedItemHandler(inv, (i, s) -> true, (i, s) -> false)); + } + + public static LazyOptional createInsertionHandler(Container inv, BiPredicate insertion_predicate, List slot_map) { + return LazyOptional.of(() -> new MappedItemHandler(inv, slot_map, (i, s) -> false, insertion_predicate)); + } + + public static LazyOptional createInsertionHandler(Container inv, Integer... slots) { + return LazyOptional.of(() -> new MappedItemHandler(inv, Arrays.asList(slots), (i, s) -> false, (i, s) -> true)); + } + + public static LazyOptional createInsertionHandler(Container inv, BiPredicate insertion_predicate) { + return LazyOptional.of(() -> new MappedItemHandler(inv, (i, s) -> false, insertion_predicate)); + } + + public static LazyOptional createInsertionHandler(Container inv) { + return LazyOptional.of(() -> new MappedItemHandler(inv, (i, s) -> false, (i, s) -> true)); + } + } + + //-------------------------------------------------------------------------------------------------------------------- + + public static class InventoryRange implements Container, Iterable { + protected final Container inventory_; + protected final int offset_, size_, num_rows; + protected int max_stack_size_ = 64; + protected BiPredicate validator_ = (index, stack) -> true; + + public static InventoryRange fromPlayerHotbar(Player player) { + return new InventoryRange(player.getInventory(), 0, 9, 1); + } + + public static InventoryRange fromPlayerStorage(Player player) { + return new InventoryRange(player.getInventory(), 9, 27, 3); + } + + public static InventoryRange fromPlayerInventory(Player player) { + return new InventoryRange(player.getInventory(), 0, 36, 4); + } + + public InventoryRange(Container inventory, int offset, int size, int num_rows) { + this.inventory_ = inventory; + this.offset_ = Mth.clamp(offset, 0, inventory.getContainerSize() - 1); + this.size_ = Mth.clamp(size, 0, inventory.getContainerSize() - this.offset_); + this.num_rows = num_rows; + } + + public InventoryRange(Container inventory, int offset, int size) { + this(inventory, offset, size, 1); + } + + public InventoryRange(Container inventory) { + this(inventory, 0, inventory.getContainerSize(), 1); + } + + public final Container inventory() { + return inventory_; + } + + public final int size() { + return size_; + } + + public final int offset() { + return offset_; + } + + public final ItemStack get(int index) { + return inventory_.getItem(offset_ + index); + } + + public final void set(int index, ItemStack stack) { + inventory_.setItem(offset_ + index, stack); + } + + public final InventoryRange setValidator(BiPredicate validator) { + validator_ = validator; + return this; + } + + public final BiPredicate getValidator() { + return validator_; + } + + public final InventoryRange setMaxStackSize(int count) { + max_stack_size_ = Math.max(count, 1); + return this; + } + + // Container ------------------------------------------------------------------------------------------------------ + + @Override + public void clearContent() { + for (int i = 0; i < size_; ++i) setItem(i, ItemStack.EMPTY); + } + + @Override + public int getContainerSize() { + return size_; + } + + @Override + public boolean isEmpty() { + for (int i = 0; i < size_; ++i) + if (!inventory_.getItem(offset_ + i).isEmpty()) { + return false; + } + return true; + } + + @Override + public ItemStack getItem(int index) { + return inventory_.getItem(offset_ + index); + } + + @Override + public ItemStack removeItem(int index, int count) { + return inventory_.removeItem(offset_ + index, count); + } + + @Override + public ItemStack removeItemNoUpdate(int index) { + return inventory_.removeItemNoUpdate(offset_ + index); + } + + @Override + public void setItem(int index, ItemStack stack) { + inventory_.setItem(offset_ + index, stack); + } + + @Override + public int getMaxStackSize() { + return Math.min(max_stack_size_, inventory_.getMaxStackSize()); + } + + @Override + public void setChanged() { + inventory_.setChanged(); + } + + @Override + public boolean stillValid(Player player) { + return inventory_.stillValid(player); + } + + @Override + public void startOpen(Player player) { + inventory_.startOpen(player); + } + + @Override + public void stopOpen(Player player) { + inventory_.stopOpen(player); + } + + @Override + public boolean canPlaceItem(int index, ItemStack stack) { + return validator_.test(offset_ + index, stack) && inventory_.canPlaceItem(offset_ + index, stack); + } + + //------------------------------------------------------------------------------------------------------------------ + + /** + * Iterates using a function (slot, stack) -> bool until the function matches (returns true). + */ + public boolean iterate(BiPredicate fn) { + for (int i = 0; i < size_; ++i) { + if (fn.test(i, getItem(i))) { + return true; + } + } + return false; + } + + public boolean contains(ItemStack stack) { + for (int i = 0; i < size_; ++i) { + if (areItemStacksIdentical(stack, getItem(i))) { + return true; + } + } + return false; + } + + public int indexOf(ItemStack stack) { + for (int i = 0; i < size_; ++i) { + if (areItemStacksIdentical(stack, getItem(i))) { + return i; + } + } + return -1; + } + + public Optional find(BiFunction> fn) { + for (int i = 0; i < size_; ++i) { + Optional r = fn.apply(i, getItem(i)); + if (r.isPresent()) return r; + } + return Optional.empty(); + } + + public List collect(BiFunction> fn) { + List data = new ArrayList<>(); + for (int i = 0; i < size_; ++i) { + fn.apply(i, getItem(i)).ifPresent(data::add); + } + return data; + } + + public Stream stream() { + return java.util.stream.StreamSupport.stream(this.spliterator(), false); + } + + public Iterator iterator() { + return new InventoryRangeIterator(this); + } + + public static class InventoryRangeIterator implements Iterator { + private final InventoryRange parent_; + private int index = 0; + + public InventoryRangeIterator(InventoryRange range) { + parent_ = range; + } + + public boolean hasNext() { + return index < parent_.size_; + } + + public ItemStack next() { + if (index >= parent_.size_) throw new NoSuchElementException(); + return parent_.getItem(index++); + } + } + + //------------------------------------------------------------------------------------------------------------------ + + /** + * Returns the number of stacks that match the given stack with NBT. + */ + public int stackMatchCount(final ItemStack ref_stack) { + int n = 0; // ... std::accumulate() the old school way. + for (int i = 0; i < size_; ++i) { + if (areItemStacksIdentical(ref_stack, getItem(i))) ++n; + } + return n; + } + + public int totalMatchingItemCount(final ItemStack ref_stack) { + int n = 0; + for (int i = 0; i < size_; ++i) { + ItemStack stack = getItem(i); + if (areItemStacksIdentical(ref_stack, stack)) n += stack.getCount(); + } + return n; + } + + //------------------------------------------------------------------------------------------------------------------ + + /** + * Moves as much items from the stack to the slots in range [offset_, end_slot] of the inventory_, + * filling up existing stacks first, then (player inventory_ only) checks appropriate empty slots next + * to stacks that have that item already, and last uses any empty slot that can be found. + * Returns the stack that is still remaining in the referenced `stack`. + */ + public ItemStack insert(final ItemStack input_stack, boolean only_fillup, int limit, boolean reverse, boolean force_group_stacks) { + final ItemStack mvstack = input_stack.copy(); + //final int end_slot = offset_ + size; + if (mvstack.isEmpty()) return checked(mvstack); + int limit_left = (limit > 0) ? (Math.min(limit, mvstack.getMaxStackSize())) : (mvstack.getMaxStackSize()); + boolean[] matches = new boolean[size_]; + boolean[] empties = new boolean[size_]; + int num_matches = 0; + for (int i = 0; i < size_; ++i) { + final int sno = reverse ? (size_ - 1 - i) : (i); + final ItemStack stack = getItem(sno); + if (stack.isEmpty()) { + empties[sno] = true; + } else if (areItemStacksIdentical(stack, mvstack)) { + matches[sno] = true; + ++num_matches; + } + } + // first iteration: fillup existing stacks + for (int i = 0; i < size_; ++i) { + final int sno = reverse ? (size_ - 1 - i) : (i); + if ((empties[sno]) || (!matches[sno])) continue; + final ItemStack stack = getItem(sno); + int nmax = Math.min(limit_left, stack.getMaxStackSize() - stack.getCount()); + if (mvstack.getCount() <= nmax) { + stack.setCount(stack.getCount() + mvstack.getCount()); + setItem(sno, stack); + return ItemStack.EMPTY; + } else { + stack.grow(nmax); + mvstack.shrink(nmax); + setItem(sno, stack); + limit_left -= nmax; + } + } + if (only_fillup) return checked(mvstack); + if ((num_matches > 0) && ((force_group_stacks) || (inventory_ instanceof Inventory))) { + // second iteration: use appropriate empty slots, + // a) between + { + int insert_start = -1; + int insert_end = -1; + int i = 1; + for (; i < size_ - 1; ++i) { + final int sno = reverse ? (size_ - 1 - i) : (i); + if (insert_start < 0) { + if (matches[sno]) insert_start = sno; + } else if (matches[sno]) { + insert_end = sno; + } + } + for (i = insert_start; i < insert_end; ++i) { + final int sno = reverse ? (size_ - 1 - i) : (i); + if ((!empties[sno]) || (!canPlaceItem(sno, mvstack))) continue; + int nmax = Math.min(limit_left, mvstack.getCount()); + ItemStack moved = mvstack.copy(); + moved.setCount(nmax); + mvstack.shrink(nmax); + setItem(sno, moved); + return checked(mvstack); + } + } + // b) before/after + { + for (int i = 1; i < size_ - 1; ++i) { + final int sno = reverse ? (size_ - 1 - i) : (i); + if (!matches[sno]) continue; + int ii = (empties[sno - 1]) ? (sno - 1) : (empties[sno + 1] ? (sno + 1) : -1); + if ((ii >= 0) && (canPlaceItem(ii, mvstack))) { + int nmax = Math.min(limit_left, mvstack.getCount()); + ItemStack moved = mvstack.copy(); + moved.setCount(nmax); + mvstack.shrink(nmax); + setItem(ii, moved); + return checked(mvstack); + } + } + } + } + // third iteration: use any empty slots + for (int i = 0; i < size_; ++i) { + final int sno = reverse ? (size_ - 1 - i) : (i); + if ((!empties[sno]) || (!canPlaceItem(sno, mvstack))) continue; + int nmax = Math.min(limit_left, mvstack.getCount()); + ItemStack placed = mvstack.copy(); + placed.setCount(nmax); + mvstack.shrink(nmax); + setItem(sno, placed); + return checked(mvstack); + } + return checked(mvstack); + } + + public ItemStack insert(final ItemStack stack_to_move) { + return insert(stack_to_move, false, 0, false, true); + } + + public ItemStack insert(final int index, final ItemStack stack_to_move) { + if (stack_to_move.isEmpty()) return stack_to_move; + final ItemStack stack = getItem(index); + final int limit = Math.min(getMaxStackSize(), stack.getMaxStackSize()); + if (stack.isEmpty()) { + setItem(index, stack_to_move.copy()); + return ItemStack.EMPTY; + } else if ((stack.getCount() >= limit) || !areItemStacksIdentical(stack, stack_to_move)) { + return stack_to_move; + } else { + final int amount = Math.min(limit - stack.getCount(), stack_to_move.getCount()); + ItemStack remaining = stack_to_move.copy(); + remaining.shrink(amount); + stack.grow(amount); + return remaining.isEmpty() ? ItemStack.EMPTY : remaining; + } + } + + //------------------------------------------------------------------------------------------------------------------ + + /** + * Extracts maximum amount of items from the inventory_. + * The first non-empty stack defines the item. + */ + public ItemStack extract(int amount) { + return extract(amount, false); + } + + public ItemStack extract(int amount, boolean random) { + ItemStack out_stack = ItemStack.EMPTY; + int offset = random ? (int) (Math.random() * size_) : 0; + for (int k = 0; k < size_; ++k) { + int i = (offset + k) % size_; + final ItemStack stack = getItem(i); + if (stack.isEmpty()) continue; + if (out_stack.isEmpty()) { + if (stack.getCount() < amount) { + out_stack = stack; + setItem(i, ItemStack.EMPTY); + if (!out_stack.isStackable()) break; + amount -= out_stack.getCount(); + } else { + out_stack = stack.split(amount); + break; + } + } else if (areItemStacksIdentical(stack, out_stack)) { + if (stack.getCount() <= amount) { + out_stack.grow(stack.getCount()); + amount -= stack.getCount(); + setItem(i, ItemStack.EMPTY); + } else { + out_stack.grow(amount); + stack.shrink(amount); + if (stack.isEmpty()) setItem(i, ItemStack.EMPTY); + break; + } + } + } + if (!out_stack.isEmpty()) setChanged(); + return out_stack; + } + + /** + * Moves as much items from the slots in range [offset_, end_slot] of the inventory_ into a new stack. + * Implicitly shrinks the inventory_ stacks and the `request_stack`. + */ + public ItemStack extract(final ItemStack request_stack) { + if (request_stack.isEmpty()) return ItemStack.EMPTY; + List matches = new ArrayList<>(); + for (int i = 0; i < size_; ++i) { + final ItemStack stack = getItem(i); + if ((!stack.isEmpty()) && (areItemStacksIdentical(stack, request_stack))) { + if (stack.hasTag()) { + final CompoundTag nbt = stack.getOrCreateTag(); + int n = nbt.size(); + if ((n > 0) && (nbt.contains("Damage"))) --n; + if (n > 0) continue; + } + matches.add(stack); + } + } + matches.sort(Comparator.comparingInt(ItemStack::getCount)); + if (matches.isEmpty()) return ItemStack.EMPTY; + int n_left = request_stack.getCount(); + ItemStack fetched_stack = matches.get(0).split(n_left); + n_left -= fetched_stack.getCount(); + for (int i = 1; (i < matches.size()) && (n_left > 0); ++i) { + ItemStack stack = matches.get(i).split(n_left); + n_left -= stack.getCount(); + fetched_stack.grow(stack.getCount()); + } + return checked(fetched_stack); + } + + //------------------------------------------------------------------------------------------------------------------ + + /** + * Moves items from this inventory_ range to another. Returns true if something was moved + * (if the inventories should be marked dirty). + */ + public boolean move(int index, final InventoryRange target_range, boolean all_identical_stacks, boolean only_fillup, boolean reverse, boolean force_group_stacks) { + final ItemStack source_stack = getItem(index); + if (source_stack.isEmpty()) return false; + if (!all_identical_stacks) { + ItemStack remaining = target_range.insert(source_stack, only_fillup, 0, reverse, force_group_stacks); + setItem(index, remaining); + return (remaining.getCount() != source_stack.getCount()); + } else { + ItemStack remaining = source_stack.copy(); + setItem(index, ItemStack.EMPTY); + final ItemStack ref_stack = remaining.copy(); + ref_stack.setCount(ref_stack.getMaxStackSize()); + for (int i = size_; (i > 0) && (!remaining.isEmpty()); --i) { + remaining = target_range.insert(remaining, only_fillup, 0, reverse, force_group_stacks); + if (!remaining.isEmpty()) break; + remaining = this.extract(ref_stack); + } + if (!remaining.isEmpty()) { + setItem(index, remaining); // put back + } + return (remaining.getCount() != source_stack.getCount()); + } + } + + public boolean move(int index, final InventoryRange target_range) { + return move(index, target_range, false, false, false, true); + } + + /** + * Moves/clears the complete range to another range if possible. Returns true if something was moved + * (if the inventories should be marked dirty). + */ + public boolean move(final InventoryRange target_range, boolean only_fillup, boolean reverse, boolean force_group_stacks) { + boolean changed = false; + for (int i = 0; i < size_; ++i) + changed |= move(i, target_range, false, only_fillup, reverse, force_group_stacks); + return changed; + } + + public boolean move(final InventoryRange target_range, boolean only_fillup) { + return move(target_range, only_fillup, false, true); + } + + public boolean move(final InventoryRange target_range) { + return move(target_range, false, false, true); + } + + } + + //-------------------------------------------------------------------------------------------------------------------- + + public static class StorageInventory implements Container, Iterable { + protected final NonNullList stacks_; + protected final BlockEntity te_; + protected final int size_; + protected final int num_rows_; + protected int stack_limit_ = 64; + protected BiPredicate validator_ = (index, stack) -> true; + protected Consumer open_action_ = (player) -> { + }; + protected Consumer close_action_ = (player) -> { + }; + protected BiConsumer slot_set_action_ = (index, stack) -> { + }; + + public StorageInventory(BlockEntity te, int size) { + this(te, size, 1); + } + + public StorageInventory(BlockEntity te, int size, int num_rows) { + te_ = te; + size_ = Math.max(size, 1); + stacks_ = NonNullList.withSize(size_, ItemStack.EMPTY); + num_rows_ = Mth.clamp(num_rows, 1, size_); + } + + public CompoundTag save(CompoundTag nbt, String key) { + nbt.put(key, save(new CompoundTag(), false)); + return nbt; + } + + public CompoundTag save(CompoundTag nbt) { + return ContainerHelper.saveAllItems(nbt, stacks_); + } + + public CompoundTag save(CompoundTag nbt, boolean save_empty) { + return ContainerHelper.saveAllItems(nbt, stacks_, save_empty); + } + + public CompoundTag save(boolean save_empty) { + return save(new CompoundTag(), save_empty); + } + + public StorageInventory load(CompoundTag nbt, String key) { + if (!nbt.contains("key", Tag.TAG_COMPOUND)) { + stacks_.clear(); + return this; + } else { + return load(nbt.getCompound(key)); + } + } + + public StorageInventory load(CompoundTag nbt) { + stacks_.clear(); + ContainerHelper.loadAllItems(nbt, stacks_); + return this; + } + + public List stacks() { + return stacks_; + } + + public BlockEntity getBlockEntity() { + return te_; + } + + public StorageInventory setOpenAction(Consumer fn) { + open_action_ = fn; + return this; + } + + public StorageInventory setCloseAction(Consumer fn) { + close_action_ = fn; + return this; + } + + public StorageInventory setSlotChangeAction(BiConsumer fn) { + slot_set_action_ = fn; + return this; + } + + public StorageInventory setStackLimit(int max_slot_stack_size) { + stack_limit_ = Math.max(max_slot_stack_size, 1); + return this; + } + + public StorageInventory setValidator(BiPredicate validator) { + validator_ = validator; + return this; + } + + public BiPredicate getValidator() { + return validator_; + } + + // Iterable --------------------------------------------------------------------- + + public Iterator iterator() { + return stacks_.iterator(); + } + + public Stream stream() { + return stacks_.stream(); + } + + // Container ------------------------------------------------------------------------------ + + @Override + public int getContainerSize() { + return size_; + } + + @Override + public boolean isEmpty() { + for (ItemStack stack : stacks_) { + if (!stack.isEmpty()) return false; + } + return true; + } + + @Override + public ItemStack getItem(int index) { + return (index < size_) ? stacks_.get(index) : ItemStack.EMPTY; + } + + @Override + public ItemStack removeItem(int index, int count) { + return ContainerHelper.removeItem(stacks_, index, count); + } + + @Override + public ItemStack removeItemNoUpdate(int index) { + return ContainerHelper.takeItem(stacks_, index); + } + + @Override + public void setItem(int index, ItemStack stack) { + stacks_.set(index, stack); + if ((stack.getCount() != stacks_.get(index).getCount()) || !areItemStacksDifferent(stacks_.get(index), stack)) { + slot_set_action_.accept(index, stack); + } + } + + @Override + public int getMaxStackSize() { + return stack_limit_; + } + + @Override + public void setChanged() { + te_.setChanged(); + } + + @Override + public boolean stillValid(Player player) { + return ((te_.getLevel().getBlockEntity(te_.getBlockPos()) == te_)) && (te_.getBlockPos().distSqr(player.blockPosition()) < 64); + } + + @Override + public void startOpen(Player player) { + open_action_.accept(player); + } + + @Override + public void stopOpen(Player player) { + setChanged(); + close_action_.accept(player); + } + + @Override + public boolean canPlaceItem(int index, ItemStack stack) { + return validator_.test(index, stack); + } + + @Override + public void clearContent() { + stacks_.clear(); + setChanged(); + } + + } + + //-------------------------------------------------------------------------------------------------------------------- + + public static void give(Player entity, ItemStack stack) { + ItemHandlerHelper.giveItemToPlayer(entity, stack); + } + + public static void setItemInPlayerHand(Player player, InteractionHand hand, ItemStack stack) { + if (stack.isEmpty()) stack = ItemStack.EMPTY; + if (hand == InteractionHand.MAIN_HAND) { + player.getInventory().items.set(player.getInventory().selected, stack); + } else { + player.getInventory().offhand.set(0, stack); + } + } + + //-------------------------------------------------------------------------------------------------------------------- + + public static Container readNbtStacks(CompoundTag nbt, String key, Container target) { + NonNullList stacks = Inventories.readNbtStacks(nbt, key, target.getContainerSize()); + for (int i = 0; i < stacks.size(); ++i) target.setItem(i, stacks.get(i)); + return target; + } + + public static NonNullList readNbtStacks(CompoundTag nbt, String key, int size) { + NonNullList stacks = NonNullList.withSize(size, ItemStack.EMPTY); + if ((nbt == null) || (!nbt.contains(key, Tag.TAG_COMPOUND))) return stacks; + CompoundTag stacknbt = nbt.getCompound(key); + ContainerHelper.loadAllItems(stacknbt, stacks); + return stacks; + } + + public static NonNullList readNbtStacks(CompoundTag nbt, int size) { + NonNullList stacks = NonNullList.withSize(size, ItemStack.EMPTY); + if ((nbt == null) || (!nbt.contains("Items", Tag.TAG_LIST))) return stacks; + ContainerHelper.loadAllItems(nbt, stacks); + return stacks; + } + + public static CompoundTag writeNbtStacks(CompoundTag nbt, String key, NonNullList stacks, boolean omit_trailing_empty) { + CompoundTag stacknbt = new CompoundTag(); + if (omit_trailing_empty) { + for (int i = stacks.size() - 1; i >= 0; --i) { + if (!stacks.get(i).isEmpty()) break; + stacks.remove(i); + } + } + ContainerHelper.saveAllItems(stacknbt, stacks); + if (nbt == null) nbt = new CompoundTag(); + nbt.put(key, stacknbt); + return nbt; + } + + public static CompoundTag writeNbtStacks(CompoundTag nbt, String key, NonNullList stacks) { + return writeNbtStacks(nbt, key, stacks, false); + } + + //-------------------------------------------------------------------------------------------------------------------- + + public static void dropStack(Level world, Vec3 pos, ItemStack stack, Vec3 velocity, double position_noise, double velocity_noise) { + if (stack.isEmpty()) return; + if (position_noise > 0) { + position_noise = Math.min(position_noise, 0.8); + pos = pos.add( + position_noise * (world.getRandom().nextDouble() - .5), + position_noise * (world.getRandom().nextDouble() - .5), + position_noise * (world.getRandom().nextDouble() - .5) + ); + } + if (velocity_noise > 0) { + velocity_noise = Math.min(velocity_noise, 1.0); + velocity = velocity.add( + (velocity_noise) * (world.getRandom().nextDouble() - .5), + (velocity_noise) * (world.getRandom().nextDouble() - .5), + (velocity_noise) * (world.getRandom().nextDouble() - .5) + ); + } + ItemEntity e = new ItemEntity(world, pos.x, pos.y, pos.z, stack); + e.setDeltaMovement((float) velocity.x, (float) velocity.y, (float) velocity.z); + e.setDefaultPickUpDelay(); + world.addFreshEntity(e); + } + + public static void dropStack(Level world, Vec3 pos, ItemStack stack, Vec3 velocity) { + dropStack(world, pos, stack, velocity, 0.3, 0.2); + } + + public static void dropStack(Level world, Vec3 pos, ItemStack stack) { + dropStack(world, pos, stack, Vec3.ZERO, 0.3, 0.2); + } + +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/Networking.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/Networking.java new file mode 100644 index 0000000..67a3540 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/Networking.java @@ -0,0 +1,409 @@ +/* + * @file Networking.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * Main client/server message handling. + */ +package dev.zontreck.libzontreck.edlibmc; + +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraftforge.common.util.FakePlayer; +import net.minecraftforge.network.NetworkDirection; +import net.minecraftforge.network.NetworkEvent; +import net.minecraftforge.network.NetworkRegistry; +import net.minecraftforge.network.simple.SimpleChannel; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Supplier; + + +public class Networking { + private static final String PROTOCOL = "1"; + private static SimpleChannel DEFAULT_CHANNEL; + + public static void init(String modid) { + DEFAULT_CHANNEL = NetworkRegistry.ChannelBuilder + .named(new ResourceLocation(modid, "default_ch")) + .clientAcceptedVersions(PROTOCOL::equals).serverAcceptedVersions(PROTOCOL::equals).networkProtocolVersion(() -> PROTOCOL) + .simpleChannel(); + int discr = -1; + DEFAULT_CHANNEL.registerMessage(++discr, PacketTileNotifyClientToServer.class, PacketTileNotifyClientToServer::compose, PacketTileNotifyClientToServer::parse, PacketTileNotifyClientToServer.Handler::handle); + DEFAULT_CHANNEL.registerMessage(++discr, PacketTileNotifyServerToClient.class, PacketTileNotifyServerToClient::compose, PacketTileNotifyServerToClient::parse, PacketTileNotifyServerToClient.Handler::handle); + DEFAULT_CHANNEL.registerMessage(++discr, PacketContainerSyncClientToServer.class, PacketContainerSyncClientToServer::compose, PacketContainerSyncClientToServer::parse, PacketContainerSyncClientToServer.Handler::handle); + DEFAULT_CHANNEL.registerMessage(++discr, PacketContainerSyncServerToClient.class, PacketContainerSyncServerToClient::compose, PacketContainerSyncServerToClient::parse, PacketContainerSyncServerToClient.Handler::handle); + DEFAULT_CHANNEL.registerMessage(++discr, PacketNbtNotifyClientToServer.class, PacketNbtNotifyClientToServer::compose, PacketNbtNotifyClientToServer::parse, PacketNbtNotifyClientToServer.Handler::handle); + DEFAULT_CHANNEL.registerMessage(++discr, PacketNbtNotifyServerToClient.class, PacketNbtNotifyServerToClient::compose, PacketNbtNotifyServerToClient::parse, PacketNbtNotifyServerToClient.Handler::handle); + DEFAULT_CHANNEL.registerMessage(++discr, OverlayTextMessage.class, OverlayTextMessage::compose, OverlayTextMessage::parse, OverlayTextMessage.Handler::handle); + } + + //-------------------------------------------------------------------------------------------------------------------- + // Tile entity notifications + //-------------------------------------------------------------------------------------------------------------------- + + public interface IPacketTileNotifyReceiver { + default void onServerPacketReceived(CompoundTag nbt) { + } + + default void onClientPacketReceived(Player player, CompoundTag nbt) { + } + } + + public static class PacketTileNotifyClientToServer { + CompoundTag nbt = null; + BlockPos pos = BlockPos.ZERO; + + public static void sendToServer(BlockPos pos, CompoundTag nbt) { + if ((pos != null) && (nbt != null)) + DEFAULT_CHANNEL.sendToServer(new PacketTileNotifyClientToServer(pos, nbt)); + } + + public static void sendToServer(BlockEntity te, CompoundTag nbt) { + if ((te != null) && (nbt != null)) + DEFAULT_CHANNEL.sendToServer(new PacketTileNotifyClientToServer(te, nbt)); + } + + public PacketTileNotifyClientToServer() { + } + + public PacketTileNotifyClientToServer(BlockPos pos, CompoundTag nbt) { + this.nbt = nbt; + this.pos = pos; + } + + public PacketTileNotifyClientToServer(BlockEntity te, CompoundTag nbt) { + this.nbt = nbt; + pos = te.getBlockPos(); + } + + public static PacketTileNotifyClientToServer parse(final FriendlyByteBuf buf) { + return new PacketTileNotifyClientToServer(buf.readBlockPos(), buf.readNbt()); + } + + public static void compose(final PacketTileNotifyClientToServer pkt, final FriendlyByteBuf buf) { + buf.writeBlockPos(pkt.pos); + buf.writeNbt(pkt.nbt); + } + + @SuppressWarnings("all") + public static class Handler { + public static void handle(final PacketTileNotifyClientToServer pkt, final Supplier ctx) { + ctx.get().enqueueWork(() -> { + Player player = ctx.get().getSender(); + if (player == null) return; + Level world = player.level(); + final BlockEntity te = world.getBlockEntity(pkt.pos); + if (!(te instanceof IPacketTileNotifyReceiver)) return; + ((IPacketTileNotifyReceiver) te).onClientPacketReceived(ctx.get().getSender(), pkt.nbt); + }); + ctx.get().setPacketHandled(true); + } + } + } + + public static class PacketTileNotifyServerToClient { + CompoundTag nbt = null; + BlockPos pos = BlockPos.ZERO; + + public static void sendToPlayer(Player player, BlockEntity te, CompoundTag nbt) { + if ((!(player instanceof ServerPlayer)) || (player instanceof FakePlayer) || (te == null) || (nbt == null)) + return; + DEFAULT_CHANNEL.sendTo(new PacketTileNotifyServerToClient(te, nbt), ((ServerPlayer) player).connection.connection, NetworkDirection.PLAY_TO_CLIENT); + } + + public static void sendToPlayers(BlockEntity te, CompoundTag nbt) { + if (te == null || te.getLevel() == null) return; + for (Player player : te.getLevel().players()) sendToPlayer(player, te, nbt); + } + + public PacketTileNotifyServerToClient() { + } + + public PacketTileNotifyServerToClient(BlockPos pos, CompoundTag nbt) { + this.nbt = nbt; + this.pos = pos; + } + + public PacketTileNotifyServerToClient(BlockEntity te, CompoundTag nbt) { + this.nbt = nbt; + pos = te.getBlockPos(); + } + + public static PacketTileNotifyServerToClient parse(final FriendlyByteBuf buf) { + return new PacketTileNotifyServerToClient(buf.readBlockPos(), buf.readNbt()); + } + + public static void compose(final PacketTileNotifyServerToClient pkt, final FriendlyByteBuf buf) { + buf.writeBlockPos(pkt.pos); + buf.writeNbt(pkt.nbt); + } + + public static class Handler { + public static void handle(final PacketTileNotifyServerToClient pkt, final Supplier ctx) { + ctx.get().enqueueWork(() -> { + Level world = SidedProxy.getWorldClientSide(); + if (world == null) return; + final BlockEntity te = world.getBlockEntity(pkt.pos); + if (!(te instanceof IPacketTileNotifyReceiver)) return; + ((IPacketTileNotifyReceiver) te).onServerPacketReceived(pkt.nbt); + }); + ctx.get().setPacketHandled(true); + } + } + } + + //-------------------------------------------------------------------------------------------------------------------- + // (GUI) Container synchronization + //-------------------------------------------------------------------------------------------------------------------- + + public interface INetworkSynchronisableContainer { + void onServerPacketReceived(int windowId, CompoundTag nbt); + + void onClientPacketReceived(int windowId, Player player, CompoundTag nbt); + } + + public static class PacketContainerSyncClientToServer { + int id = -1; + CompoundTag nbt = null; + + public static void sendToServer(int windowId, CompoundTag nbt) { + if (nbt != null) DEFAULT_CHANNEL.sendToServer(new PacketContainerSyncClientToServer(windowId, nbt)); + } + + public static void sendToServer(AbstractContainerMenu container, CompoundTag nbt) { + if (nbt != null) + DEFAULT_CHANNEL.sendToServer(new PacketContainerSyncClientToServer(container.containerId, nbt)); + } + + public PacketContainerSyncClientToServer() { + } + + public PacketContainerSyncClientToServer(int id, CompoundTag nbt) { + this.nbt = nbt; + this.id = id; + } + + public static PacketContainerSyncClientToServer parse(final FriendlyByteBuf buf) { + return new PacketContainerSyncClientToServer(buf.readInt(), buf.readNbt()); + } + + public static void compose(final PacketContainerSyncClientToServer pkt, final FriendlyByteBuf buf) { + buf.writeInt(pkt.id); + buf.writeNbt(pkt.nbt); + } + + public static class Handler { + public static void handle(final PacketContainerSyncClientToServer pkt, final Supplier ctx) { + ctx.get().enqueueWork(() -> { + Player player = ctx.get().getSender(); + if ((player == null) || !(player.containerMenu instanceof INetworkSynchronisableContainer)) return; + if (player.containerMenu.containerId != pkt.id) return; + ((INetworkSynchronisableContainer) player.containerMenu).onClientPacketReceived(pkt.id, player, pkt.nbt); + }); + ctx.get().setPacketHandled(true); + } + } + } + + public static class PacketContainerSyncServerToClient { + int id = -1; + CompoundTag nbt = null; + + public static void sendToPlayer(Player player, int windowId, CompoundTag nbt) { + if ((!(player instanceof ServerPlayer)) || (player instanceof FakePlayer) || (nbt == null)) return; + DEFAULT_CHANNEL.sendTo(new PacketContainerSyncServerToClient(windowId, nbt), ((ServerPlayer) player).connection.connection, NetworkDirection.PLAY_TO_CLIENT); + } + + public static void sendToPlayer(Player player, AbstractContainerMenu container, CompoundTag nbt) { + if ((!(player instanceof ServerPlayer)) || (player instanceof FakePlayer) || (nbt == null)) return; + DEFAULT_CHANNEL.sendTo(new PacketContainerSyncServerToClient(container.containerId, nbt), ((ServerPlayer) player).connection.connection, NetworkDirection.PLAY_TO_CLIENT); + } + + public static + void sendToListeners(Level world, C container, CompoundTag nbt) { + for (Player player : world.players()) { + if (player.containerMenu.containerId != container.containerId) continue; + sendToPlayer(player, container.containerId, nbt); + } + } + + public PacketContainerSyncServerToClient() { + } + + public PacketContainerSyncServerToClient(int id, CompoundTag nbt) { + this.nbt = nbt; + this.id = id; + } + + public static PacketContainerSyncServerToClient parse(final FriendlyByteBuf buf) { + return new PacketContainerSyncServerToClient(buf.readInt(), buf.readNbt()); + } + + public static void compose(final PacketContainerSyncServerToClient pkt, final FriendlyByteBuf buf) { + buf.writeInt(pkt.id); + buf.writeNbt(pkt.nbt); + } + + public static class Handler { + public static void handle(final PacketContainerSyncServerToClient pkt, final Supplier ctx) { + ctx.get().enqueueWork(() -> { + Player player = SidedProxy.getPlayerClientSide(); + if ((player == null) || !(player.containerMenu instanceof INetworkSynchronisableContainer)) return; + if (player.containerMenu.containerId != pkt.id) return; + ((INetworkSynchronisableContainer) player.containerMenu).onServerPacketReceived(pkt.id, pkt.nbt); + }); + ctx.get().setPacketHandled(true); + } + } + } + + //-------------------------------------------------------------------------------------------------------------------- + // World notifications + //-------------------------------------------------------------------------------------------------------------------- + + public static class PacketNbtNotifyClientToServer { + public static final Map> handlers = new HashMap<>(); + final CompoundTag nbt; + + public static void sendToServer(CompoundTag nbt) { + if (nbt != null) DEFAULT_CHANNEL.sendToServer(new PacketNbtNotifyClientToServer(nbt)); + } + + public PacketNbtNotifyClientToServer(CompoundTag nbt) { + this.nbt = nbt; + } + + public static PacketNbtNotifyClientToServer parse(final FriendlyByteBuf buf) { + return new PacketNbtNotifyClientToServer(buf.readNbt()); + } + + public static void compose(final PacketNbtNotifyClientToServer pkt, final FriendlyByteBuf buf) { + buf.writeNbt(pkt.nbt); + } + + public static class Handler { + public static void handle(final PacketNbtNotifyClientToServer pkt, final Supplier ctx) { + ctx.get().enqueueWork(() -> { + final ServerPlayer player = ctx.get().getSender(); + if (player == null) return; + final String hnd = pkt.nbt.getString("hnd"); + if (hnd.isEmpty()) return; + if (handlers.containsKey(hnd)) handlers.get(hnd).accept(player, pkt.nbt); + }); + ctx.get().setPacketHandled(true); + } + } + } + + public static class PacketNbtNotifyServerToClient { + public static final Map> handlers = new HashMap<>(); + final CompoundTag nbt; + + public static void sendToPlayer(Player player, CompoundTag nbt) { + if ((!(player instanceof ServerPlayer)) || (player instanceof FakePlayer) || (nbt == null)) return; + DEFAULT_CHANNEL.sendTo(new PacketNbtNotifyServerToClient(nbt), ((ServerPlayer) player).connection.connection, NetworkDirection.PLAY_TO_CLIENT); + } + + public static void sendToPlayers(Level world, CompoundTag nbt) { + for (Player player : world.players()) sendToPlayer(player, nbt); + } + + public PacketNbtNotifyServerToClient(CompoundTag nbt) { + this.nbt = nbt; + } + + public static PacketNbtNotifyServerToClient parse(final FriendlyByteBuf buf) { + return new PacketNbtNotifyServerToClient(buf.readNbt()); + } + + public static void compose(final PacketNbtNotifyServerToClient pkt, final FriendlyByteBuf buf) { + buf.writeNbt(pkt.nbt); + } + + public static class Handler { + public static void handle(final PacketNbtNotifyServerToClient pkt, final Supplier ctx) { + ctx.get().enqueueWork(() -> { + final String hnd = pkt.nbt.getString("hnd"); + if (hnd.isEmpty()) return; + if (handlers.containsKey(hnd)) handlers.get(hnd).accept(pkt.nbt); + }); + ctx.get().setPacketHandled(true); + } + } + } + + //-------------------------------------------------------------------------------------------------------------------- + // Main window GUI text message + //-------------------------------------------------------------------------------------------------------------------- + + public static class OverlayTextMessage { + public static final int DISPLAY_TIME_MS = 3000; + private static BiConsumer handler_ = null; + private final Component data_; + private int delay_ = DISPLAY_TIME_MS; + + private Component data() { + return data_; + } + + private int delay() { + return delay_; + } + + public static void setHandler(BiConsumer handler) { + if (handler_ == null) handler_ = handler; + } + + public static void sendToPlayer(Player player, Component message, int delay) { + if ((!(player instanceof ServerPlayer)) || (player instanceof FakePlayer) || Auxiliaries.isEmpty(message)) + return; + DEFAULT_CHANNEL.sendTo(new OverlayTextMessage(message, delay), ((ServerPlayer) player).connection.connection, NetworkDirection.PLAY_TO_CLIENT); + } + + public OverlayTextMessage() { + data_ = Component.translatable("[unset]"); + } + + public OverlayTextMessage(final Component tct, int delay) { + data_ = tct.copy(); + delay_ = delay; + } + + public static OverlayTextMessage parse(final FriendlyByteBuf buf) { + try { + return new OverlayTextMessage(buf.readComponent(), DISPLAY_TIME_MS); + } catch (Throwable e) { + return new OverlayTextMessage(Component.translatable("[incorrect translation]"), DISPLAY_TIME_MS); + } + } + + public static void compose(final OverlayTextMessage pkt, final FriendlyByteBuf buf) { + try { + buf.writeComponent(pkt.data()); + } catch (Throwable e) { + Auxiliaries.logger().error("OverlayTextMessage.toBytes() failed: " + e); + } + } + + public static class Handler { + public static void handle(final OverlayTextMessage pkt, final Supplier ctx) { + if (handler_ != null) ctx.get().enqueueWork(() -> handler_.accept(pkt.data(), pkt.delay())); + ctx.get().setPacketHandled(true); + } + } + } + +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/OptionalRecipeCondition.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/OptionalRecipeCondition.java new file mode 100644 index 0000000..681daff --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/OptionalRecipeCondition.java @@ -0,0 +1,191 @@ +/* + * @file OptionalRecipeCondition.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * Recipe condition to enable opt'ing out JSON based recipes. + */ +package dev.zontreck.libzontreck.edlibmc; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.GsonHelper; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; +import net.minecraftforge.common.crafting.conditions.ICondition; +import net.minecraftforge.common.crafting.conditions.IConditionSerializer; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.IForgeRegistry; +import org.slf4j.Logger; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + + +public class OptionalRecipeCondition implements ICondition { + private static ResourceLocation NAME; + + private final List all_required; + private final List any_missing; + private final List all_required_tags; + private final List any_missing_tags; + private final @Nullable ResourceLocation result; + private final boolean result_is_tag; + private final boolean experimental; + + private static boolean with_experimental = false; + private static boolean without_recipes = false; + private static Predicate block_optouts = (block) -> false; + private static Predicate item_optouts = (item) -> false; + + public static void init(String modid, Logger logger) { + NAME = new ResourceLocation(modid, "optional"); + } + + public static void on_config(boolean enable_experimental, boolean disable_all_recipes, + Predicate block_optout_provider, + Predicate item_optout_provider) { + with_experimental = enable_experimental; + without_recipes = disable_all_recipes; + block_optouts = block_optout_provider; + item_optouts = item_optout_provider; + } + + public OptionalRecipeCondition(ResourceLocation result, List required, List missing, List required_tags, List missing_tags, boolean isexperimental, boolean result_is_tag) { + all_required = required; + any_missing = missing; + all_required_tags = required_tags; + any_missing_tags = missing_tags; + this.result = result; + this.result_is_tag = result_is_tag; + experimental = isexperimental; + } + + @Override + public ResourceLocation getID() { + return NAME; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("Optional recipe, all-required: ["); + for (ResourceLocation e : all_required) sb.append(e.toString()).append(","); + for (ResourceLocation e : all_required_tags) sb.append("#").append(e.toString()).append(","); + sb.delete(sb.length() - 1, sb.length()).append("], any-missing: ["); + for (ResourceLocation e : any_missing) sb.append(e.toString()).append(","); + for (ResourceLocation e : any_missing_tags) sb.append("#").append(e.toString()).append(","); + sb.delete(sb.length() - 1, sb.length()).append("]"); + if (experimental) sb.append(" EXPERIMENTAL"); + return sb.toString(); + } + + @Override + public boolean test(IContext context) { + if (without_recipes) return false; + if ((experimental) && (!with_experimental)) return false; + final IForgeRegistry item_registry = ForgeRegistries.ITEMS; + //final Collection item_tags = SerializationTags.getInstance().getOrEmpty(Registry.ITEM_REGISTRY).getAvailableTags(); + if (result != null) { + boolean item_registered = item_registry.containsKey(result); + if (!item_registered) return false; // required result not registered + if (item_optouts.test(item_registry.getValue(result))) return false; + if (ForgeRegistries.BLOCKS.containsKey(result) && block_optouts.test(ForgeRegistries.BLOCKS.getValue(result))) + return false; + } + if (!all_required.isEmpty()) { + for (ResourceLocation rl : all_required) { + if (!item_registry.containsKey(rl)) return false; + } + } + if (!all_required_tags.isEmpty()) { + for (ResourceLocation rl : all_required_tags) { + if (item_registry.tags().getTagNames().noneMatch(tk -> tk.location().equals(rl))) + return false; // if(!item_tags.contains(rl)) return false; + } + } + if (!any_missing.isEmpty()) { + for (ResourceLocation rl : any_missing) { + if (!item_registry.containsKey(rl)) return true; + } + return false; + } + if (!any_missing_tags.isEmpty()) { + for (ResourceLocation rl : any_missing_tags) { + if (item_registry.tags().getTagNames().noneMatch(tk -> tk.location().equals(rl))) + return true; // if(!item_tags.contains(rl)) return true; + } + return false; + } + return true; + } + + public static class Serializer implements IConditionSerializer { + public static final Serializer INSTANCE = new Serializer(); + + @Override + public ResourceLocation getID() { + return OptionalRecipeCondition.NAME; + } + + @Override + public void write(JsonObject json, OptionalRecipeCondition condition) { + JsonArray required = new JsonArray(); + JsonArray missing = new JsonArray(); + for (ResourceLocation e : condition.all_required) required.add(e.toString()); + for (ResourceLocation e : condition.any_missing) missing.add(e.toString()); + json.add("required", required); + json.add("missing", missing); + if (condition.result != null) { + json.addProperty("result", (condition.result_is_tag ? "#" : "") + condition.result); + } + } + + @Override + public OptionalRecipeCondition read(JsonObject json) { + List required = new ArrayList<>(); + List missing = new ArrayList<>(); + List required_tags = new ArrayList<>(); + List missing_tags = new ArrayList<>(); + ResourceLocation result = null; + boolean experimental = false; + boolean result_is_tag = false; + if (json.has("result")) { + String s = json.get("result").getAsString(); + if (s.startsWith("#")) { + result = new ResourceLocation(s.substring(1)); + result_is_tag = true; + } else { + result = new ResourceLocation(s); + } + } + if (json.has("required")) { + for (JsonElement e : GsonHelper.getAsJsonArray(json, "required")) { + String s = e.getAsString(); + if (s.startsWith("#")) { + required_tags.add(new ResourceLocation(s.substring(1))); + } else { + required.add(new ResourceLocation(s)); + } + } + } + if (json.has("missing")) { + for (JsonElement e : GsonHelper.getAsJsonArray(json, "missing")) { + String s = e.getAsString(); + if (s.startsWith("#")) { + missing_tags.add(new ResourceLocation(s.substring(1))); + } else { + missing.add(new ResourceLocation(s)); + } + } + } + if (json.has("experimental")) experimental = json.get("experimental").getAsBoolean(); + return new OptionalRecipeCondition(result, required, missing, required_tags, missing_tags, experimental, result_is_tag); + } + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/Overlay.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/Overlay.java new file mode 100644 index 0000000..43f71cc --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/Overlay.java @@ -0,0 +1,165 @@ +/* + * @file Overlay.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * Renders status messages in one line. + */ +package dev.zontreck.libzontreck.edlibmc; + +import com.mojang.blaze3d.platform.Window; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.client.renderer.LightTexture; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.texture.OverlayTexture; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.util.Mth; +import net.minecraft.util.Tuple; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.LightLayer; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import net.minecraftforge.fml.DistExecutor; + +import javax.annotation.Nullable; +import java.util.Optional; + + +public class Overlay { + public static void show(Player player, final Component message) { + Networking.OverlayTextMessage.sendToPlayer(player, message, 3000); + } + + public static void show(Player player, final Component message, int delay) { + Networking.OverlayTextMessage.sendToPlayer(player, message, delay); + } + + public static void show(BlockState state, BlockPos pos) { + show(state, pos, 100); + } + + public static void show(BlockState state, BlockPos pos, int displayTimeoutMs) { + DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> (() -> TextOverlayGui.show(state, pos, displayTimeoutMs))); + } // Only called when client side + + // ----------------------------------------------------------------------------- + // Client side handler + // ----------------------------------------------------------------------------- + + @OnlyIn(Dist.CLIENT) + public static class TextOverlayGui extends Screen { + public static final TextOverlayGui INSTANCE = new TextOverlayGui(); + private static final Component EMPTY_TEXT = Component.literal(""); + private static final BlockState EMPTY_STATE = null; + private static double overlay_y_ = 0.75; + private static int text_color_ = 0x00ffaa00; + private static int border_color_ = 0xaa333333; + private static int background_color1_ = 0xaa333333; + private static int background_color2_ = 0xaa444444; + private static long text_deadline_ = 0; + private static Component text_ = EMPTY_TEXT; + private static long state_deadline_ = 0; + private static @Nullable BlockState state_ = EMPTY_STATE; + private static BlockPos pos_ = BlockPos.ZERO; + + public static void on_config(double overlay_y) { + on_config(overlay_y, 0x00ffaa00, 0xaa333333, 0xaa333333, 0xaa444444); + } + + public static void on_config(double overlay_y, int text_color, int border_color, int background_color1, int background_color2) { + overlay_y_ = overlay_y; + text_color_ = text_color; + border_color_ = border_color; + background_color1_ = background_color1; + background_color2_ = background_color2; + } + + public static synchronized Component text() { + return text_; + } + + public static synchronized long deadline() { + return text_deadline_; + } + + public static synchronized void hide() { + text_deadline_ = 0; + text_ = EMPTY_TEXT; + } + + public static synchronized void show(Component s, int displayTimeoutMs) { + text_ = (s == null) ? (EMPTY_TEXT) : (s.copy()); + text_deadline_ = System.currentTimeMillis() + displayTimeoutMs; + } + + public static synchronized void show(String s, int displayTimeoutMs) { + text_ = ((s == null) || (s.isEmpty())) ? (EMPTY_TEXT) : (Component.literal(s)); + text_deadline_ = System.currentTimeMillis() + displayTimeoutMs; + } + + public static synchronized void show(BlockState state, BlockPos pos, int displayTimeoutMs) { + pos_ = new BlockPos(pos); + state_ = state; + state_deadline_ = System.currentTimeMillis() + displayTimeoutMs; + } + + private static synchronized Optional> state_pos() { + return ((state_deadline_ < System.currentTimeMillis()) || (state_ == EMPTY_STATE)) ? Optional.empty() : Optional.of(new Tuple<>(state_, pos_)); + } + + TextOverlayGui() { + super(Component.literal("")); + } + + public void onRenderGui(final GuiGraphics mxs) { + if (deadline() < System.currentTimeMillis()) return; + if (text() == EMPTY_TEXT) return; + String txt = text().getString(); + if (txt.isEmpty()) return; + final net.minecraft.client.Minecraft mc = net.minecraft.client.Minecraft.getInstance(); + final Window win = mc.getWindow(); + final Font fr = mc.font; + final boolean was_unicode = fr.isBidirectional(); + final int cx = win.getGuiScaledWidth() / 2; + final int cy = (int) (win.getGuiScaledHeight() * overlay_y_); + final int w = fr.width(txt); + final int h = fr.lineHeight; + mxs.fillGradient(cx - (w / 2) - 3, cy - 2, cx + (w / 2) + 2, cy + h + 2, 0xaa333333, 0xaa444444); + mxs.hLine(cx - (w / 2) - 3, cx + (w / 2) + 2, cy - 2, 0xaa333333); + mxs.hLine(cx - (w / 2) - 3, cx + (w / 2) + 2, cy + h + 2, 0xaa333333); + mxs.vLine(cx - (w / 2) - 3, cy - 2, cy + h + 2, 0xaa333333); + mxs.vLine(cx + (w / 2) + 2, cy - 2, cy + h + 2, 0xaa333333); + mxs.drawCenteredString(fr, text(), cx, cy + 1, 0x00ffaa00); + } + + @SuppressWarnings("deprecation") + public void onRenderWorldOverlay(final com.mojang.blaze3d.vertex.PoseStack mxs, final double partialTick) { + final Optional> sp = state_pos(); + if (sp.isEmpty()) return; + final ClientLevel world = Minecraft.getInstance().level; + final LocalPlayer player = Minecraft.getInstance().player; + if ((player == null) || (world == null)) return; + final BlockState state = sp.get().getA(); + final BlockPos pos = sp.get().getB(); + final int light = (world.hasChunkAt(pos)) ? LightTexture.pack(world.getBrightness(LightLayer.BLOCK, pos), world.getBrightness(LightLayer.SKY, pos)) : LightTexture.pack(15, 15); + final MultiBufferSource buffer = Minecraft.getInstance().renderBuffers().bufferSource(); + final double px = Mth.lerp(partialTick, player.xo, player.getX()); + final double py = Mth.lerp(partialTick, player.yo, player.getY()); + final double pz = Mth.lerp(partialTick, player.zo, player.getZ()); + mxs.pushPose(); + mxs.translate((pos.getX() - px), (pos.getY() - py - player.getEyeHeight()), (pos.getZ() - pz)); + Minecraft.getInstance().getBlockRenderer().renderSingleBlock(state, mxs, buffer, light, OverlayTexture.NO_OVERLAY); + mxs.popPose(); + } + + } + +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/README.md b/src/main/java/dev/zontreck/libzontreck/edlibmc/README.md new file mode 100644 index 0000000..f4bf684 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/README.md @@ -0,0 +1,4 @@ +# Engineer's Decor LibMC +____________ + +As Engineer's Decor has been abandoned and discontinued, i've adopted the LibMC, and split the mod up among my various mods. Engineer's Decor blocks, and items, will now reside in Thresholds. \ No newline at end of file diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/Registries.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/Registries.java new file mode 100644 index 0000000..02542bc --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/Registries.java @@ -0,0 +1,263 @@ +/* + * @file Registries.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * Common game registry handling. + */ +package dev.zontreck.libzontreck.edlibmc; + +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.flag.FeatureFlagSet; +import net.minecraft.world.flag.FeatureFlags; +import net.minecraft.world.inventory.MenuType; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.CreativeModeTab; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.RecipeSerializer; +import net.minecraft.world.level.ItemLike; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.RegistryObject; + +import javax.annotation.Nonnull; +import java.util.*; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +public class Registries { + private static String modid = null; + private static String creative_tab_icon = ""; + private static CreativeModeTab creative_tab = null; + private static final Map> registered_block_tag_keys = new HashMap<>(); + private static final Map> registered_item_tag_keys = new HashMap<>(); + + private static final Map> registered_blocks = new HashMap<>(); + private static final Map> registered_items = new HashMap<>(); + private static final Map>> registered_block_entity_types = new HashMap<>(); + private static final Map>> registered_entity_types = new HashMap<>(); + private static final Map>> registered_menu_types = new HashMap<>(); + private static final Map>> recipe_serializers = new HashMap<>(); + private static final Map> registered_tabs = new HashMap<>(); + + public static final List> tabItems = new ArrayList<>(); + + + + private static DeferredRegister BLOCKS; + private static DeferredRegister ITEMS; + private static DeferredRegister TABS; + private static DeferredRegister> BLOCK_ENTITIES; + private static DeferredRegister> MENUS; + private static DeferredRegister> ENTITIES; + private static DeferredRegister> RECIPE_SERIALIZERS; + private static List> MOD_REGISTRIES; + + public static void init(String mod_id, String creative_tab_icon_item_name, IEventBus bus) { + modid = mod_id; + creative_tab_icon = creative_tab_icon_item_name; + BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, modid); + ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, modid); + BLOCK_ENTITIES = DeferredRegister.create(ForgeRegistries.BLOCK_ENTITY_TYPES, modid); + MENUS = DeferredRegister.create(ForgeRegistries.MENU_TYPES, modid); + ENTITIES = DeferredRegister.create(ForgeRegistries.ENTITY_TYPES, modid); + RECIPE_SERIALIZERS = DeferredRegister.create(ForgeRegistries.RECIPE_SERIALIZERS, modid); + TABS = DeferredRegister.create(net.minecraft.core.registries.Registries.CREATIVE_MODE_TAB, modid); + + Consumer> consumer = (X)->{ + X.register(bus); + }; + + List.of(BLOCKS, ITEMS, BLOCK_ENTITIES, MENUS, ENTITIES, RECIPE_SERIALIZERS, TABS).forEach(consumer); + } + + // ------------------------------------------------------------------------------------------------------------- + + public static Block getBlock(String block_name) { + return registered_blocks.get(block_name).get(); + } + + public static RegistryObject withTab(RegistryObject item) + { + tabItems.add(item); + + return item; + } + + public static Item getItem(String name) { + return registered_items.get(name).get(); + } + + public static EntityType getEntityType(String name) { + return registered_entity_types.get(name).get(); + } + + public static BlockEntityType getBlockEntityType(String block_name) { + return registered_block_entity_types.get(block_name).get(); + } + + public static MenuType getMenuType(String name) { + return registered_menu_types.get(name).get(); + } + + public static RecipeSerializer getRecipeSerializer(String name) { + return recipe_serializers.get(name).get(); + } + + public static BlockEntityType getBlockEntityTypeOfBlock(String block_name) { + return getBlockEntityType("tet_" + block_name); + } + + public static BlockEntityType getBlockEntityTypeOfBlock(Block block) { + return getBlockEntityTypeOfBlock(ForgeRegistries.BLOCKS.getKey(block).getPath()); + } + + public static MenuType getMenuTypeOfBlock(String name) { + return getMenuType("ct_" + name); + } + + public static MenuType getMenuTypeOfBlock(Block block) { + return getMenuTypeOfBlock(ForgeRegistries.BLOCKS.getKey(block).getPath()); + } + + public static TagKey getBlockTagKey(String name) { + return registered_block_tag_keys.get(name); + } + + public static TagKey getItemTagKey(String name) { + return registered_item_tag_keys.get(name); + } + + // ------------------------------------------------------------------------------------------------------------- + + @Nonnull + public static List getRegisteredBlocks() { + return Collections.unmodifiableList(registered_blocks.values().stream().map(RegistryObject::get).toList()); + } + + @Nonnull + public static List getRegisteredItems() { + return Collections.unmodifiableList(registered_items.values().stream().map(RegistryObject::get).toList()); + } + + @Nonnull + public static List> getRegisteredBlockEntityTypes() { + return Collections.unmodifiableList(registered_block_entity_types.values().stream().map(RegistryObject::get).toList()); + } + + @Nonnull + public static List> getRegisteredEntityTypes() { + return Collections.unmodifiableList(registered_entity_types.values().stream().map(RegistryObject::get).toList()); + } + + // ------------------------------------------------------------------------------------------------------------- + + public static void addItem(String registry_name, Supplier supplier) { + registered_items.put(registry_name, withTab(ITEMS.register(registry_name, supplier))); + + } + + public static void addBlock(String registry_name, Supplier block_supplier) { + registered_blocks.put(registry_name, BLOCKS.register(registry_name, block_supplier)); + registered_items.put(registry_name, withTab(ITEMS.register(registry_name, () -> new BlockItem(registered_blocks.get(registry_name).get(), (new Item.Properties()))))); + } + + public static void addCreativeModeTab(String id, Component title, ItemStack icon) + { + registered_tabs.put(id, TABS.register(id, ()-> CreativeModeTab + .builder() + .icon(()->icon) + .title(title) + .withSearchBar() + + .displayItems((display, output) -> tabItems.forEach(it->output.accept(it.get()))) + + .build() + )); + } + + public static void addBlock(String registry_name, Supplier block_supplier, Supplier item_supplier) { + registered_blocks.put(registry_name, BLOCKS.register(registry_name, block_supplier)); + registered_items.put(registry_name, ITEMS.register(registry_name, item_supplier)); + } + + public static void addBlockEntityType(String registry_name, BlockEntityType.BlockEntitySupplier ctor, String... block_names) { + registered_block_entity_types.put(registry_name, BLOCK_ENTITIES.register(registry_name, () -> { + final Block[] blocks = Arrays.stream(block_names).map(s -> { + Block b = BLOCKS.getEntries().stream().filter((ro) -> ro.getId().getPath().equals(s)).findFirst().map(RegistryObject::get).orElse(null); + if (b == null) Auxiliaries.logError("registered_blocks does not encompass '" + s + "'"); + return b; + }).filter(Objects::nonNull).collect(Collectors.toList()).toArray(new Block[]{}); + return BlockEntityType.Builder.of(ctor, blocks).build(null); + })); + } + + public static > void addEntityType(String registry_name, Supplier> supplier) { + registered_entity_types.put(registry_name, ENTITIES.register(registry_name, supplier)); + } + + public static > void addMenuType(String registry_name, MenuType.MenuSupplier supplier, FeatureFlagSet flags) { + registered_menu_types.put(registry_name, MENUS.register(registry_name, () -> new MenuType<>(supplier, flags))); + } + + public static void addRecipeSerializer(String registry_name, Supplier> serializer_supplier) { + recipe_serializers.put(registry_name, RECIPE_SERIALIZERS.register(registry_name, serializer_supplier)); + } + + public static void addOptionalBlockTag(String tag_name, ResourceLocation... default_blocks) { + final Set> default_suppliers = new HashSet<>(); + for (ResourceLocation rl : default_blocks) default_suppliers.add(() -> ForgeRegistries.BLOCKS.getValue(rl)); + final TagKey key = ForgeRegistries.BLOCKS.tags().createOptionalTagKey(new ResourceLocation(modid, tag_name), default_suppliers); + registered_block_tag_keys.put(tag_name, key); + } + + public static void addOptionaItemTag(String tag_name, ResourceLocation... default_items) { + final Set> default_suppliers = new HashSet<>(); + for (ResourceLocation rl : default_items) default_suppliers.add(() -> ForgeRegistries.ITEMS.getValue(rl)); + final TagKey key = ForgeRegistries.ITEMS.tags().createOptionalTagKey(new ResourceLocation(modid, tag_name), default_suppliers); + registered_item_tag_keys.put(tag_name, key); + } + + // ------------------------------------------------------------------------------------------------------------- + + @Deprecated + /** + * This function is to be removed as it cannot add items to a tab anymore + */ + public static void addBlock(String registry_name, Supplier block_supplier, BiFunction item_builder) { + addBlock(registry_name, block_supplier, () -> item_builder.apply(registered_blocks.get(registry_name).get(), (new Item.Properties()))); + } + + public static void addBlock(String registry_name, Supplier block_supplier, BlockEntityType.BlockEntitySupplier block_entity_ctor) { + addBlock(registry_name, block_supplier); + addBlockEntityType("tet_" + registry_name, block_entity_ctor, registry_name); + } + + public static void addBlock(String registry_name, Supplier block_supplier, BiFunction item_builder, BlockEntityType.BlockEntitySupplier block_entity_ctor) { + addBlock(registry_name, block_supplier, item_builder); + addBlockEntityType("tet_" + registry_name, block_entity_ctor, registry_name); + } + + public static void addBlock(String registry_name, Supplier block_supplier, BiFunction item_builder, BlockEntityType.BlockEntitySupplier block_entity_ctor, MenuType.MenuSupplier menu_type_supplier, FeatureFlagSet menuFlags) { + addBlock(registry_name, block_supplier, item_builder); + addBlockEntityType("tet_" + registry_name, block_entity_ctor, registry_name); + addMenuType("ct_" + registry_name, menu_type_supplier, menuFlags); + } + + public static void addBlock(String registry_name, Supplier block_supplier, BlockEntityType.BlockEntitySupplier block_entity_ctor, MenuType.MenuSupplier menu_type_supplier, FeatureFlagSet menuFlags) { + addBlock(registry_name, block_supplier, block_entity_ctor); + addMenuType("ct_" + registry_name, menu_type_supplier, menuFlags); + } + +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/RfEnergy.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/RfEnergy.java new file mode 100644 index 0000000..8636926 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/RfEnergy.java @@ -0,0 +1,180 @@ +/* + * @file RfEnergy.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * General RF/FE energy handling functionality. + */ +package dev.zontreck.libzontreck.edlibmc; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.util.Mth; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraftforge.common.capabilities.ForgeCapabilities; +import net.minecraftforge.common.util.LazyOptional; +import net.minecraftforge.energy.IEnergyStorage; + +import javax.annotation.Nullable; + +public class RfEnergy { + public static int feed(Level world, BlockPos pos, @Nullable Direction side, int rf_energy) { + final BlockEntity te = world.getBlockEntity(pos); + if (te == null) return 0; + final IEnergyStorage es = te.getCapability(ForgeCapabilities.ENERGY, side).orElse(null); + if (es == null) return 0; + return es.receiveEnergy(rf_energy, false); + } + + public static class Battery implements IEnergyStorage { + protected int capacity_; + protected int charge_rate_; + protected int discharge_rate_; + protected int energy_; + + public Battery(int capacity) { + this(capacity, capacity); + } + + public Battery(int capacity, int transfer_rate) { + this(capacity, transfer_rate, transfer_rate, 0); + } + + public Battery(int capacity, int charge_rate, int discharge_rate) { + this(capacity, charge_rate, discharge_rate, 0); + } + + public Battery(int capacity, int charge_rate, int discharge_rate, int energy) { + capacity_ = Math.max(capacity, 1); + charge_rate_ = Mth.clamp(charge_rate, 0, capacity_); + discharge_rate_ = Mth.clamp(discharge_rate, 0, capacity_); + energy_ = Mth.clamp(energy, 0, capacity_); + } + + // --------------------------------------------------------------------------------------------------- + + public Battery setMaxEnergyStored(int capacity) { + capacity_ = Math.max(capacity, 1); + return this; + } + + public Battery setEnergyStored(int energy) { + energy_ = Mth.clamp(energy, 0, capacity_); + return this; + } + + public Battery setChargeRate(int in_rate) { + charge_rate_ = Mth.clamp(in_rate, 0, capacity_); + return this; + } + + public Battery setDischargeRate(int out_rate) { + discharge_rate_ = Mth.clamp(out_rate, 0, capacity_); + return this; + } + + public int getChargeRate() { + return charge_rate_; + } + + public int getDischargeRate() { + return discharge_rate_; + } + + public boolean isEmpty() { + return energy_ <= 0; + } + + public boolean isFull() { + return energy_ >= capacity_; + } + + public int getSOC() { + return (int) Mth.clamp((100.0 * energy_ / capacity_ + .5), 0, 100); + } + + public int getComparatorOutput() { + return (int) Mth.clamp((15.0 * energy_ / capacity_ + .2), 0, 15); + } + + public boolean draw(int energy) { + if (energy_ < energy) return false; + energy_ -= energy; + return true; + } + + public boolean feed(int energy) { + energy_ = Math.min(energy_ + energy, capacity_); + return energy_ >= capacity_; + } + + public Battery clear() { + energy_ = 0; + return this; + } + + public Battery load(CompoundTag nbt, String key) { + setEnergyStored(nbt.getInt(key)); + return this; + } + + public Battery load(CompoundTag nbt) { + return load(nbt, "Energy"); + } + + public CompoundTag save(CompoundTag nbt, String key) { + nbt.putInt(key, energy_); + return nbt; + } + + public CompoundTag save(CompoundTag nbt) { + return save(nbt, "Energy"); + } + + public LazyOptional createEnergyHandler() { + return LazyOptional.of(() -> this); + } + + // IEnergyStorage ------------------------------------------------------------------------------------ + + @Override + public int receiveEnergy(int feed_energy, boolean simulate) { + if (!canReceive()) return 0; + int e = Math.min(Math.min(charge_rate_, feed_energy), capacity_ - energy_); + if (!simulate) energy_ += e; + return e; + } + + @Override + public int extractEnergy(int draw_energy, boolean simulate) { + if (!canExtract()) return 0; + int e = Math.min(Math.min(discharge_rate_, draw_energy), energy_); + if (!simulate) energy_ -= e; + return e; + } + + @Override + public int getEnergyStored() { + return energy_; + } + + @Override + public int getMaxEnergyStored() { + return capacity_; + } + + @Override + public boolean canExtract() { + return discharge_rate_ > 0; + } + + @Override + public boolean canReceive() { + return charge_rate_ > 0; + } + + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/RsSignals.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/RsSignals.java new file mode 100644 index 0000000..70509bd --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/RsSignals.java @@ -0,0 +1,42 @@ +/* + * @file RsSignals.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * General redstone signal related functionality. + */ +package dev.zontreck.libzontreck.edlibmc; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.Container; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.block.state.BlockState; + +import javax.annotation.Nullable; + +public class RsSignals { + + public static boolean hasSignalConnector(BlockState state, BlockGetter world, BlockPos pos, @Nullable Direction realSide) { + return state.isSignalSource(); + } + + public static int fromContainer(@Nullable Container container) { + if (container == null) return 0; + final double max = container.getMaxStackSize(); + if (max <= 0) return 0; + boolean nonempty = false; + double fill_level = 0; + for (int i = 0; i < container.getContainerSize(); ++i) { + ItemStack stack = container.getItem(i); + if (stack.isEmpty() || (stack.getMaxStackSize() <= 0)) continue; + fill_level += ((double) stack.getCount()) / Math.min(max, stack.getMaxStackSize()); + nonempty = true; + } + fill_level /= container.getContainerSize(); + return (int) (Math.floor(fill_level * 14) + (nonempty ? 1 : 0)); // vanilla compliant calculation. + } + +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/SidedProxy.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/SidedProxy.java new file mode 100644 index 0000000..ba5f04b --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/SidedProxy.java @@ -0,0 +1,122 @@ +/* + * @file SidedProxy.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * General client/server sidedness selection proxy. + */ +package dev.zontreck.libzontreck.edlibmc; + +import net.minecraft.client.Minecraft; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.minecraftforge.fml.DistExecutor; + +import javax.annotation.Nullable; +import java.util.Optional; + +public class SidedProxy { + @Nullable + public static Player getPlayerClientSide() { + return proxy.getPlayerClientSide(); + } + + @Nullable + public static Level getWorldClientSide() { + return proxy.getWorldClientSide(); + } + + @Nullable + public static Minecraft mc() { + return proxy.mc(); + } + + public static Optional isCtrlDown() { + return proxy.isCtrlDown(); + } + + public static Optional isShiftDown() { + return proxy.isShiftDown(); + } + + public static Optional getClipboard() { + return proxy.getClipboard(); + } + + public static boolean setClipboard(String text) { + return proxy.setClipboard(text); + } + + // -------------------------------------------------------------------------------------------------------- + + private static final ISidedProxy proxy = DistExecutor.unsafeRunForDist(() -> ClientProxy::new, () -> ServerProxy::new); + + private interface ISidedProxy { + default @Nullable Player getPlayerClientSide() { + return null; + } + + default @Nullable Level getWorldClientSide() { + return null; + } + + default @Nullable Minecraft mc() { + return null; + } + + default Optional isCtrlDown() { + return Optional.empty(); + } + + default Optional isShiftDown() { + return Optional.empty(); + } + + default Optional getClipboard() { + return Optional.empty(); + } + + default boolean setClipboard(String text) { + return false; + } + } + + private static final class ClientProxy implements ISidedProxy { + public @Nullable Player getPlayerClientSide() { + return Minecraft.getInstance().player; + } + + public @Nullable Level getWorldClientSide() { + return Minecraft.getInstance().level; + } + + public @Nullable Minecraft mc() { + return Minecraft.getInstance(); + } + + public Optional isCtrlDown() { + return Optional.of(Auxiliaries.isCtrlDown()); + } + + public Optional isShiftDown() { + return Optional.of(Auxiliaries.isShiftDown()); + } + + public Optional getClipboard() { + return (mc() == null) ? Optional.empty() : Optional.of(net.minecraft.client.gui.font.TextFieldHelper.getClipboardContents(mc())); + } + + public boolean setClipboard(String text) { + if (mc() == null) { + return false; + } + net.minecraft.client.gui.font.TextFieldHelper.setClipboardContents(Minecraft.getInstance(), text); + return true; + } + } + + private static final class ServerProxy implements ISidedProxy { + } + +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/SlabSliceBlock.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/SlabSliceBlock.java new file mode 100644 index 0000000..40e3401 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/SlabSliceBlock.java @@ -0,0 +1,228 @@ +/* + * @file SlabSliceBlock.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * Half slab ("slab slices") characteristics class. Actually + * it's now a quarter slab, but who cares. + */ +package dev.zontreck.libzontreck.edlibmc; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.network.chat.Component; +import net.minecraft.sounds.SoundSource; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.SpawnPlacements; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Mirror; +import net.minecraft.world.level.block.Rotation; +import net.minecraft.world.level.block.SoundType; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.IntegerProperty; +import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.Shapes; +import net.minecraft.world.phys.shapes.VoxelShape; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class SlabSliceBlock extends StandardBlocks.WaterLoggable implements StandardBlocks.IStandardBlock { + public static final IntegerProperty PARTS = IntegerProperty.create("parts", 0, 14); + + protected static final VoxelShape[] AABBs = { + Shapes.create(new AABB(0, 0. / 16, 0, 1, 2. / 16, 1)), + Shapes.create(new AABB(0, 0. / 16, 0, 1, 4. / 16, 1)), + Shapes.create(new AABB(0, 0. / 16, 0, 1, 6. / 16, 1)), + Shapes.create(new AABB(0, 0. / 16, 0, 1, 8. / 16, 1)), + Shapes.create(new AABB(0, 0. / 16, 0, 1, 10. / 16, 1)), + Shapes.create(new AABB(0, 0. / 16, 0, 1, 12. / 16, 1)), + Shapes.create(new AABB(0, 0. / 16, 0, 1, 14. / 16, 1)), + Shapes.create(new AABB(0, 0. / 16, 0, 1, 16. / 16, 1)), + Shapes.create(new AABB(0, 2. / 16, 0, 1, 16. / 16, 1)), + Shapes.create(new AABB(0, 4. / 16, 0, 1, 16. / 16, 1)), + Shapes.create(new AABB(0, 6. / 16, 0, 1, 16. / 16, 1)), + Shapes.create(new AABB(0, 8. / 16, 0, 1, 16. / 16, 1)), + Shapes.create(new AABB(0, 10. / 16, 0, 1, 16. / 16, 1)), + Shapes.create(new AABB(0, 12. / 16, 0, 1, 16. / 16, 1)), + Shapes.create(new AABB(0, 14. / 16, 0, 1, 16. / 16, 1)), + Shapes.create(new AABB(0, 0, 0, 1, 1, 1)) // <- with 4bit fill + }; + + protected static final int[] num_slabs_contained_in_parts_ = {1, 2, 3, 4, 5, 6, 7, 8, 7, 6, 5, 4, 3, 2, 1, 0x1}; // <- with 4bit fill + private static boolean with_pickup = false; + + public static void on_config(boolean direct_slab_pickup) { + with_pickup = direct_slab_pickup; + } + + public SlabSliceBlock(long config, BlockBehaviour.Properties builder) { + super(config, builder); + } + + protected boolean is_cube(BlockState state) { + return state.getValue(PARTS) == 0x07; + } + + @Override + @OnlyIn(Dist.CLIENT) + public void appendHoverText(ItemStack stack, @Nullable BlockGetter world, List tooltip, TooltipFlag flag) { + if (!Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true)) return; + if (with_pickup) Auxiliaries.Tooltip.addInformation("engineersdecor.tooltip.slabpickup", tooltip); + } + + @Override + public RenderTypeHint getRenderTypeHint() { + return (((config & StandardBlocks.CFG_TRANSLUCENT) != 0) ? (RenderTypeHint.TRANSLUCENT) : (RenderTypeHint.CUTOUT)); + } + + @Override + public boolean isPossibleToRespawnInThis(BlockState p_279289_) { + return false; + } + + @Override + public boolean isValidSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType entityType) { + return false; + } + + @Override + public VoxelShape getShape(BlockState state, BlockGetter source, BlockPos pos, CollisionContext selectionContext) { + return AABBs[state.getValue(PARTS) & 0xf]; + } + + @Override + public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext) { + return getShape(state, world, pos, selectionContext); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + super.createBlockStateDefinition(builder); + builder.add(PARTS); + } + + @Override + @Nullable + public BlockState getStateForPlacement(BlockPlaceContext context) { + final BlockPos pos = context.getClickedPos(); + BlockState state = context.getLevel().getBlockState(pos); + if (state.getBlock() == this) { + int parts = state.getValue(PARTS); + if (parts == 7) return null; // -> is already a full block. + parts += (parts < 7) ? 1 : -1; + if (parts == 7) state = state.setValue(WATERLOGGED, false); + return state.setValue(PARTS, parts); + } else { + final Direction face = context.getClickedFace(); + final BlockState placement_state = super.getStateForPlacement(context); // fluid state + if (face == Direction.UP) return placement_state.setValue(PARTS, 0); + if (face == Direction.DOWN) return placement_state.setValue(PARTS, 14); + if (!face.getAxis().isHorizontal()) return placement_state; + final boolean isupper = ((context.getClickLocation().y() - context.getClickedPos().getY()) > 0.5); + return placement_state.setValue(PARTS, isupper ? 14 : 0); + } + } + + @Override + @SuppressWarnings("deprecation") + public boolean canBeReplaced(BlockState state, BlockPlaceContext context) { + if (context.getItemInHand().getItem() != this.asItem()) return false; + if (!context.replacingClickedOnBlock()) return true; + final Direction face = context.getClickedFace(); + final int parts = state.getValue(PARTS); + if (parts == 7) return false; + if ((face == Direction.UP) && (parts < 7)) return true; + if ((face == Direction.DOWN) && (parts > 7)) return true; + if (!face.getAxis().isHorizontal()) return false; + final boolean isupper = ((context.getClickLocation().y() - context.getClickedPos().getY()) > 0.5); + return isupper ? (parts == 0) : (parts == 1); + } + + @Override + @SuppressWarnings("deprecation") + public BlockState rotate(BlockState state, Rotation rot) { + return state; + } + + @Override + @SuppressWarnings("deprecation") + public BlockState mirror(BlockState state, Mirror mirrorIn) { + return state; + } + + @Override + public boolean hasDynamicDropList() { + return true; + } + + @Override + public List dropList(BlockState state, Level world, BlockEntity te, boolean explosion) { + return new ArrayList<>(Collections.singletonList(new ItemStack(this.asItem(), num_slabs_contained_in_parts_[state.getValue(PARTS) & 0xf]))); + } + + @Override + @SuppressWarnings("deprecation") + public void attack(BlockState state, Level world, BlockPos pos, Player player) { + if ((world.isClientSide) || (!with_pickup)) return; + final ItemStack stack = player.getMainHandItem(); + if (stack.isEmpty() || (Block.byItem(stack.getItem()) != this)) return; + if (stack.getCount() >= stack.getMaxStackSize()) return; + Vec3 lv = player.getLookAngle(); + Direction facing = Direction.getNearest((float) lv.x, (float) lv.y, (float) lv.z); + if ((facing != Direction.UP) && (facing != Direction.DOWN)) return; + if (state.getBlock() != this) return; + int parts = state.getValue(PARTS); + if ((facing == Direction.DOWN) && (parts <= 7)) { + if (parts > 0) { + world.setBlock(pos, state.setValue(PARTS, parts - 1), 3); + } else { + world.removeBlock(pos, false); + } + } else if ((facing == Direction.UP) && (parts >= 7)) { + if (parts < 14) { + world.setBlock(pos, state.setValue(PARTS, parts + 1), 3); + } else { + world.removeBlock(pos, false); + } + } else { + return; + } + if (!player.isCreative()) { + stack.grow(1); + if (player.getInventory() != null) player.getInventory().setChanged(); + } + SoundType st = this.getSoundType(state, world, pos, null); + world.playSound(player, pos, st.getPlaceSound(), SoundSource.BLOCKS, (st.getVolume() + 1f) / 2.5f, 0.9f * st.getPitch()); + } + + @Override + public boolean placeLiquid(LevelAccessor world, BlockPos pos, BlockState state, FluidState fluidState) { + return (state.getValue(PARTS) != 14) && (super.placeLiquid(world, pos, state, fluidState)); + } + + @Override + public boolean canPlaceLiquid(BlockGetter world, BlockPos pos, BlockState state, Fluid fluid) { + return (state.getValue(PARTS) != 14) && (super.canPlaceLiquid(world, pos, state, fluid)); + } + +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardBlocks.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardBlocks.java new file mode 100644 index 0000000..69343b1 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardBlocks.java @@ -0,0 +1,698 @@ +/* + * @file StandardBlocks.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * Common functionality class for decor blocks. + */ +package dev.zontreck.libzontreck.edlibmc; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.sounds.SoundEvent; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.SpawnPlacements; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Mirror; +import net.minecraft.world.level.block.Rotation; +import net.minecraft.world.level.block.SimpleWaterloggedBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.level.block.state.properties.BooleanProperty; +import net.minecraft.world.level.block.state.properties.DirectionProperty; +import net.minecraft.world.level.block.state.properties.EnumProperty; +import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.level.material.Fluids; +import net.minecraft.world.level.material.PushReaction; +import net.minecraft.world.level.pathfinder.PathComputationType; +import net.minecraft.world.level.storage.loot.LootContext; +import net.minecraft.world.level.storage.loot.LootParams; +import net.minecraft.world.level.storage.loot.parameters.LootContextParams; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.shapes.BooleanOp; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.Shapes; +import net.minecraft.world.phys.shapes.VoxelShape; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import javax.annotation.Nullable; +import java.util.*; +import java.util.function.Function; +import java.util.function.Supplier; + + +@SuppressWarnings("deprecation") +public class StandardBlocks { + public static final long CFG_DEFAULT = 0x0000000000000000L; // no special config + public static final long CFG_CUTOUT = 0x0000000000000001L; // cutout rendering + public static final long CFG_MIPPED = 0x0000000000000002L; // cutout mipped rendering + public static final long CFG_TRANSLUCENT = 0x0000000000000004L; // indicates a block/pane is glass like (transparent, etc) + public static final long CFG_WATERLOGGABLE = 0x0000000000000008L; // The derived block extends IWaterLoggable + public static final long CFG_HORIZIONTAL = 0x0000000000000010L; // horizontal block, affects bounding box calculation at construction time and placement + public static final long CFG_LOOK_PLACEMENT = 0x0000000000000020L; // placed in direction the player is looking when placing. + public static final long CFG_FACING_PLACEMENT = 0x0000000000000040L; // placed on the facing the player has clicked. + public static final long CFG_OPPOSITE_PLACEMENT = 0x0000000000000080L; // placed placed in the opposite direction of the face the player clicked. + public static final long CFG_FLIP_PLACEMENT_IF_SAME = 0x0000000000000100L; // placement direction flipped if an instance of the same class was clicked + public static final long CFG_FLIP_PLACEMENT_SHIFTCLICK = 0x0000000000000200L; // placement direction flipped if player is sneaking + public static final long CFG_STRICT_CONNECTIONS = 0x0000000000000400L; // blocks do not connect to similar blocks around (implementation details may vary a bit) + public static final long CFG_AI_PASSABLE = 0x0000000000000800L; // does not block movement path for AI, needed for non-opaque blocks with collision shapes not thin at the bottom or one side. + + public interface IStandardBlock { + default long config() { + return 0; + } + + default boolean hasDynamicDropList() { + return false; + } + + default List dropList(BlockState state, Level world, @Nullable BlockEntity te, boolean explosion) { + return Collections.singletonList((!world.isClientSide()) ? (new ItemStack(state.getBlock().asItem())) : (ItemStack.EMPTY)); + } + + enum RenderTypeHint {SOLID, CUTOUT, CUTOUT_MIPPED, TRANSLUCENT, TRANSLUCENT_NO_CRUMBLING} + + default RenderTypeHint getRenderTypeHint() { + return getRenderTypeHint(config()); + } + + default RenderTypeHint getRenderTypeHint(long config) { + if ((config & CFG_CUTOUT) != 0) return RenderTypeHint.CUTOUT; + if ((config & CFG_MIPPED) != 0) return RenderTypeHint.CUTOUT_MIPPED; + if ((config & CFG_TRANSLUCENT) != 0) return RenderTypeHint.TRANSLUCENT; + return RenderTypeHint.SOLID; + } + } + + public static class BaseBlock extends Block implements IStandardBlock, SimpleWaterloggedBlock { + public static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED; + public final long config; + + public BaseBlock(long conf, BlockBehaviour.Properties properties) { + super(properties); + config = conf; + BlockState state = getStateDefinition().any(); + if ((conf & CFG_WATERLOGGABLE) != 0) state = state.setValue(WATERLOGGED, false); + registerDefaultState(state); + } + + @Override + public long config() { + return config; + } + + @Override + @OnlyIn(Dist.CLIENT) + public void appendHoverText(ItemStack stack, @Nullable BlockGetter world, List tooltip, TooltipFlag flag) { + Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); + } + + @Override + public RenderTypeHint getRenderTypeHint() { + return getRenderTypeHint(config); + } + + @Override + @SuppressWarnings("deprecation") + public boolean isPathfindable(BlockState state, BlockGetter world, BlockPos pos, PathComputationType type) { + return ((config & CFG_AI_PASSABLE) != 0) && (super.isPathfindable(state, world, pos, type)); + } + + public boolean hasSignalConnector(BlockState state, BlockGetter world, BlockPos pos, @Nullable Direction side) { + return state.isSignalSource(); + } + + @Override + @SuppressWarnings("deprecation") + public void onRemove(BlockState state, Level world, BlockPos pos, BlockState newState, boolean isMoving) { + final boolean rsup = (state.hasBlockEntity() && (state.getBlock() != newState.getBlock())); + super.onRemove(state, world, pos, newState, isMoving); + if (rsup) world.updateNeighbourForOutputSignal(pos, this); + } + + @Override + public boolean propagatesSkylightDown(BlockState state, BlockGetter reader, BlockPos pos) { + return (((config & CFG_WATERLOGGABLE) == 0) || (!state.getValue(WATERLOGGED))) && super.propagatesSkylightDown(state, reader, pos); + } + + @Override + @SuppressWarnings("deprecation") + public FluidState getFluidState(BlockState state) { + return (((config & CFG_WATERLOGGABLE) != 0) && state.getValue(WATERLOGGED)) ? Fluids.WATER.getSource(false) : super.getFluidState(state); + } + + @Override + @SuppressWarnings("deprecation") + public BlockState updateShape(BlockState state, Direction facing, BlockState facingState, LevelAccessor world, BlockPos pos, BlockPos facingPos) { + if (((config & CFG_WATERLOGGABLE) != 0) && (state.getValue(WATERLOGGED))) + world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world)); + return state; + } + + @Override // SimpleWaterloggedBlock + public boolean canPlaceLiquid(BlockGetter world, BlockPos pos, BlockState state, Fluid fluid) { + return ((config & CFG_WATERLOGGABLE) != 0) && SimpleWaterloggedBlock.super.canPlaceLiquid(world, pos, state, fluid); + } + + @Override // SimpleWaterloggedBlock + public boolean placeLiquid(LevelAccessor world, BlockPos pos, BlockState state, FluidState fluidState) { + return ((config & CFG_WATERLOGGABLE) != 0) && SimpleWaterloggedBlock.super.placeLiquid(world, pos, state, fluidState); + } + + @Override // SimpleWaterloggedBlock + public ItemStack pickupBlock(LevelAccessor world, BlockPos pos, BlockState state) { + return ((config & CFG_WATERLOGGABLE) != 0) ? (SimpleWaterloggedBlock.super.pickupBlock(world, pos, state)) : (ItemStack.EMPTY); + } + + @Override // SimpleWaterloggedBlock + public Optional getPickupSound() { + return ((config & CFG_WATERLOGGABLE) != 0) ? (SimpleWaterloggedBlock.super.getPickupSound()) : Optional.empty(); + } + } + + public static class Cutout extends BaseBlock implements IStandardBlock { + private final VoxelShape vshape; + + public Cutout(long conf, BlockBehaviour.Properties properties) { + this(conf, properties, Auxiliaries.getPixeledAABB(0, 0, 0, 16, 16, 16)); + } + + public Cutout(long conf, BlockBehaviour.Properties properties, AABB aabb) { + this(conf, properties, Shapes.create(aabb)); + } + + public Cutout(long conf, BlockBehaviour.Properties properties, AABB[] aabbs) { + this(conf, properties, Arrays.stream(aabbs).map(Shapes::create).reduce(Shapes.empty(), (shape, aabb) -> Shapes.joinUnoptimized(shape, aabb, BooleanOp.OR))); + } + + public Cutout(long conf, BlockBehaviour.Properties properties, VoxelShape voxel_shape) { + super(conf, properties); + vshape = voxel_shape; + } + + @Override + @SuppressWarnings("deprecation") + public VoxelShape getShape(BlockState state, BlockGetter source, BlockPos pos, CollisionContext selectionContext) { + return vshape; + } + + @Override + @SuppressWarnings("deprecation") + public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext) { + return vshape; + } + + @Override + @Nullable + public BlockState getStateForPlacement(BlockPlaceContext context) { + BlockState state = super.getStateForPlacement(context); + if ((config & CFG_WATERLOGGABLE) != 0) { + FluidState fs = context.getLevel().getFluidState(context.getClickedPos()); + state = state.setValue(WATERLOGGED, fs.getType() == Fluids.WATER); + } + return state; + } + + + @Override + public boolean isPossibleToRespawnInThis(BlockState p_279289_) { + return false; + } + + @Override + @SuppressWarnings("deprecation") + public PushReaction getPistonPushReaction(BlockState state) { + return PushReaction.NORMAL; + } + + @Override + public boolean propagatesSkylightDown(BlockState state, BlockGetter reader, BlockPos pos) { + if ((config & CFG_WATERLOGGABLE) != 0) { + if (state.getValue(WATERLOGGED)) return false; + } + return super.propagatesSkylightDown(state, reader, pos); + } + + @Override + @SuppressWarnings("deprecation") + public FluidState getFluidState(BlockState state) { + if ((config & CFG_WATERLOGGABLE) != 0) { + return state.getValue(WATERLOGGED) ? Fluids.WATER.getSource(false) : super.getFluidState(state); + } + return super.getFluidState(state); + } + + @Override + @SuppressWarnings("deprecation") + public BlockState updateShape(BlockState state, Direction facing, BlockState facingState, LevelAccessor world, BlockPos pos, BlockPos facingPos) { + if ((config & CFG_WATERLOGGABLE) != 0) { + if (state.getValue(WATERLOGGED)) + world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world)); + } + return state; + } + } + + public static class WaterLoggable extends Cutout implements IStandardBlock { + public WaterLoggable(long config, BlockBehaviour.Properties properties) { + super(config | CFG_WATERLOGGABLE, properties); + } + + public WaterLoggable(long config, BlockBehaviour.Properties properties, AABB aabb) { + super(config | CFG_WATERLOGGABLE, properties, aabb); + } + + public WaterLoggable(long config, BlockBehaviour.Properties properties, VoxelShape voxel_shape) { + super(config | CFG_WATERLOGGABLE, properties, voxel_shape); + } + + public WaterLoggable(long config, BlockBehaviour.Properties properties, AABB[] aabbs) { + super(config | CFG_WATERLOGGABLE, properties, aabbs); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + super.createBlockStateDefinition(builder); + builder.add(WATERLOGGED); + } + } + + public static class Directed extends Cutout implements IStandardBlock { + public static final DirectionProperty FACING = BlockStateProperties.FACING; + protected final Map vshapes; + + public Directed(long config, BlockBehaviour.Properties properties, final Function, Map> shape_supplier) { + super(config, properties); + registerDefaultState(super.defaultBlockState().setValue(FACING, Direction.UP)); + vshapes = shape_supplier.apply(getStateDefinition().getPossibleStates()); + } + + public Directed(long config, BlockBehaviour.Properties properties, final Supplier> shape_supplier) { + this(config, properties, (states) -> { + final Map vshapes = new HashMap<>(); + final ArrayList indexed_shapes = shape_supplier.get(); + for (BlockState state : states) + vshapes.put(state, indexed_shapes.get(state.getValue(FACING).get3DDataValue())); + return vshapes; + }); + } + + public Directed(long config, BlockBehaviour.Properties properties, final AABB[] unrotatedAABBs) { + this(config, properties, (states) -> { + final boolean is_horizontal = ((config & CFG_HORIZIONTAL) != 0); + Map vshapes = new HashMap<>(); + for (BlockState state : states) { + vshapes.put(state, Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(unrotatedAABBs, state.getValue(FACING), is_horizontal))); + } + return vshapes; + }); + } + + public Directed(long config, BlockBehaviour.Properties properties, final AABB unrotatedAABB) { + this(config, properties, new AABB[]{unrotatedAABB}); + } + + + @Override + public boolean isPossibleToRespawnInThis(BlockState p_279289_) { + return false; + } + + @Override + public boolean isValidSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType entityType) { + return false; + } + + @Override + public VoxelShape getShape(BlockState state, BlockGetter source, BlockPos pos, CollisionContext selectionContext) { + return vshapes.get(state); + } + + @Override + public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext) { + return getShape(state, world, pos, selectionContext); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + super.createBlockStateDefinition(builder); + builder.add(FACING); + } + + @Override + @Nullable + public BlockState getStateForPlacement(BlockPlaceContext context) { + BlockState state = super.getStateForPlacement(context); + if (state == null) return null; + Direction facing = context.getClickedFace(); + if ((config & (CFG_HORIZIONTAL | CFG_LOOK_PLACEMENT)) == (CFG_HORIZIONTAL | CFG_LOOK_PLACEMENT)) { + // horizontal placement in direction the player is looking + facing = context.getHorizontalDirection(); + } else if ((config & (CFG_HORIZIONTAL | CFG_LOOK_PLACEMENT)) == (CFG_HORIZIONTAL)) { + // horizontal placement on a face + if (((facing == Direction.UP) || (facing == Direction.DOWN))) return null; + } else if ((config & CFG_LOOK_PLACEMENT) != 0) { + // placement in direction the player is looking, with up and down + facing = context.getNearestLookingDirection(); + } + if ((config & CFG_OPPOSITE_PLACEMENT) != 0) facing = facing.getOpposite(); + if (((config & CFG_FLIP_PLACEMENT_SHIFTCLICK) != 0) && (context.getPlayer() != null) && (context.getPlayer().isShiftKeyDown())) + facing = facing.getOpposite(); + return state.setValue(FACING, facing); + } + } + + public static class AxisAligned extends Cutout implements IStandardBlock { + public static final EnumProperty AXIS = BlockStateProperties.AXIS; + protected final ArrayList vshapes; + + public AxisAligned(long config, BlockBehaviour.Properties properties, final Supplier> shape_supplier) { + super(config, properties); + registerDefaultState(super.defaultBlockState().setValue(AXIS, Direction.Axis.X)); + vshapes = shape_supplier.get(); + } + + public AxisAligned(long config, BlockBehaviour.Properties properties, final AABB[] unrotatedAABBs) { + this(config, properties, () -> new ArrayList<>(Arrays.asList( + Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(unrotatedAABBs, Direction.EAST, false)), + Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(unrotatedAABBs, Direction.UP, false)), + Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(unrotatedAABBs, Direction.SOUTH, false)), + Shapes.block() + ))); + } + + public AxisAligned(long config, BlockBehaviour.Properties properties, final AABB unrotatedAABB) { + this(config, properties, new AABB[]{unrotatedAABB}); + } + + + @Override + public boolean isPossibleToRespawnInThis(BlockState p_279289_) { + return false; + } + + @Override + public boolean isValidSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType entityType) { + return false; + } + + @Override + public VoxelShape getShape(BlockState state, BlockGetter source, BlockPos pos, CollisionContext selectionContext) { + return vshapes.get((state.getValue(AXIS)).ordinal() & 0x3); + } + + @Override + public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext) { + return getShape(state, world, pos, selectionContext); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + super.createBlockStateDefinition(builder); + builder.add(AXIS); + } + + @Override + @Nullable + public BlockState getStateForPlacement(BlockPlaceContext context) { + Direction facing; + if ((config & CFG_LOOK_PLACEMENT) != 0) { + facing = context.getNearestLookingDirection(); + } else { + facing = context.getClickedFace(); + } + return super.getStateForPlacement(context).setValue(AXIS, facing.getAxis()); + } + + @Override + @SuppressWarnings("deprecation") + public BlockState rotate(BlockState state, Rotation rotation) { + switch (rotation) { + case CLOCKWISE_90: + case COUNTERCLOCKWISE_90: + switch (state.getValue(AXIS)) { + case X: + return state.setValue(AXIS, Direction.Axis.Z); + case Z: + return state.setValue(AXIS, Direction.Axis.X); + } + } + return state; + } + } + + public static class Horizontal extends Cutout implements IStandardBlock { + public static final DirectionProperty HORIZONTAL_FACING = BlockStateProperties.HORIZONTAL_FACING; + protected final Map vshapes; + protected final Map cshapes; + + public Horizontal(long config, BlockBehaviour.Properties properties, final Function, Map> shape_supplier) { + super(config | CFG_HORIZIONTAL, properties); + registerDefaultState(super.defaultBlockState().setValue(HORIZONTAL_FACING, Direction.NORTH)); + vshapes = shape_supplier.apply(getStateDefinition().getPossibleStates()); + cshapes = shape_supplier.apply(getStateDefinition().getPossibleStates()); + } + + public Horizontal(long config, BlockBehaviour.Properties properties, final Supplier> shape_supplier) { + this(config, properties, (states) -> { + final Map vshapes = new HashMap<>(); + final ArrayList indexed_shapes = shape_supplier.get(); + for (BlockState state : states) + vshapes.put(state, indexed_shapes.get(state.getValue(HORIZONTAL_FACING).get3DDataValue())); + return vshapes; + }); + } + + public Horizontal(long config, BlockBehaviour.Properties properties, final AABB unrotatedAABB) { + this(config, properties, new AABB[]{unrotatedAABB}); + } + + public Horizontal(long config, BlockBehaviour.Properties properties, final AABB[] unrotatedAABBs) { + this(config, properties, (states) -> { + Map vshapes = new HashMap<>(); + for (BlockState state : states) { + vshapes.put(state, Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(unrotatedAABBs, state.getValue(HORIZONTAL_FACING), true))); + } + return vshapes; + }); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + super.createBlockStateDefinition(builder); + builder.add(HORIZONTAL_FACING); + } + + @Override + public VoxelShape getShape(BlockState state, BlockGetter source, BlockPos pos, CollisionContext selectionContext) { + return vshapes.get(state); + } + + @Override + public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext) { + return cshapes.get(state); + } + + @Override + @Nullable + public BlockState getStateForPlacement(BlockPlaceContext context) { + BlockState state = super.getStateForPlacement(context); + if (state == null) return null; + Direction facing = context.getClickedFace(); + if ((config & CFG_LOOK_PLACEMENT) != 0) { + // horizontal placement in direction the player is looking + facing = context.getHorizontalDirection(); + } else { + // horizontal placement on a face + facing = ((facing == Direction.UP) || (facing == Direction.DOWN)) ? (context.getHorizontalDirection()) : facing; + } + if ((config & CFG_OPPOSITE_PLACEMENT) != 0) facing = facing.getOpposite(); + if (((config & CFG_FLIP_PLACEMENT_SHIFTCLICK) != 0) && (context.getPlayer() != null) && (context.getPlayer().isShiftKeyDown())) + facing = facing.getOpposite(); + return state.setValue(HORIZONTAL_FACING, facing); + } + + @Override + @SuppressWarnings("deprecation") + public BlockState rotate(BlockState state, Rotation rot) { + return state.setValue(HORIZONTAL_FACING, rot.rotate(state.getValue(HORIZONTAL_FACING))); + } + + @Override + @SuppressWarnings("deprecation") + public BlockState mirror(BlockState state, Mirror mirrorIn) { + return state.rotate(mirrorIn.getRotation(state.getValue(HORIZONTAL_FACING))); + } + } + + public static class DirectedWaterLoggable extends Directed implements IStandardBlock { + public DirectedWaterLoggable(long config, BlockBehaviour.Properties properties, AABB aabb) { + super(config | CFG_WATERLOGGABLE, properties, aabb); + } + + public DirectedWaterLoggable(long config, BlockBehaviour.Properties properties, AABB[] aabbs) { + super(config | CFG_WATERLOGGABLE, properties, aabbs); + } + + public DirectedWaterLoggable(long config, BlockBehaviour.Properties properties, final Function, Map> shape_supplier) { + super(config | CFG_WATERLOGGABLE, properties, shape_supplier); + } + + public DirectedWaterLoggable(long config, BlockBehaviour.Properties properties, final Supplier> shape_supplier) { + super(config | CFG_WATERLOGGABLE, properties, shape_supplier); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + super.createBlockStateDefinition(builder); + builder.add(WATERLOGGED); + } + } + + public static class AxisAlignedWaterLoggable extends AxisAligned implements IStandardBlock { + public AxisAlignedWaterLoggable(long config, BlockBehaviour.Properties properties, AABB aabb) { + super(config | CFG_WATERLOGGABLE, properties, aabb); + } + + public AxisAlignedWaterLoggable(long config, BlockBehaviour.Properties properties, AABB[] aabbs) { + super(config | CFG_WATERLOGGABLE, properties, aabbs); + } + + public AxisAlignedWaterLoggable(long config, BlockBehaviour.Properties properties, final Supplier> shape_supplier) { + super(config | CFG_WATERLOGGABLE, properties, shape_supplier); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + super.createBlockStateDefinition(builder); + builder.add(WATERLOGGED); + } + } + + public static class HorizontalWaterLoggable extends Horizontal implements IStandardBlock { + public HorizontalWaterLoggable(long config, BlockBehaviour.Properties properties, AABB aabb) { + super(config | CFG_WATERLOGGABLE | CFG_HORIZIONTAL, properties, aabb); + } + + public HorizontalWaterLoggable(long config, BlockBehaviour.Properties properties, AABB[] aabbs) { + super(config | CFG_WATERLOGGABLE | CFG_HORIZIONTAL, properties, aabbs); + } + + public HorizontalWaterLoggable(long config, BlockBehaviour.Properties properties, final Supplier> shape_supplier) { + super(config | CFG_WATERLOGGABLE | CFG_HORIZIONTAL, properties, shape_supplier); + } + + public HorizontalWaterLoggable(long config, BlockBehaviour.Properties properties, final Function, Map> shape_supplier) { + super(config | CFG_WATERLOGGABLE | CFG_HORIZIONTAL, properties, shape_supplier); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + super.createBlockStateDefinition(builder); + builder.add(WATERLOGGED); + } + } + + static public class HorizontalFourWayWaterLoggable extends WaterLoggable implements IStandardBlock { + public static final BooleanProperty NORTH = BlockStateProperties.NORTH; + public static final BooleanProperty EAST = BlockStateProperties.EAST; + public static final BooleanProperty SOUTH = BlockStateProperties.SOUTH; + public static final BooleanProperty WEST = BlockStateProperties.WEST; + protected final Map shapes; + protected final Map collision_shapes; + + public HorizontalFourWayWaterLoggable(long config, BlockBehaviour.Properties properties, AABB base_aabb, final AABB[] side_aabb, int railing_height_extension) { + super(config, properties, base_aabb); + Map build_shapes = new HashMap<>(); + Map build_collision_shapes = new HashMap<>(); + for (BlockState state : getStateDefinition().getPossibleStates()) { + { + VoxelShape shape = ((base_aabb.getXsize() == 0) || (base_aabb.getYsize() == 0) || (base_aabb.getZsize() == 0)) ? Shapes.empty() : Shapes.create(base_aabb); + if (state.getValue(NORTH)) + shape = Shapes.joinUnoptimized(shape, Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(side_aabb, Direction.NORTH, true)), BooleanOp.OR); + if (state.getValue(EAST)) + shape = Shapes.joinUnoptimized(shape, Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(side_aabb, Direction.EAST, true)), BooleanOp.OR); + if (state.getValue(SOUTH)) + shape = Shapes.joinUnoptimized(shape, Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(side_aabb, Direction.SOUTH, true)), BooleanOp.OR); + if (state.getValue(WEST)) + shape = Shapes.joinUnoptimized(shape, Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(side_aabb, Direction.WEST, true)), BooleanOp.OR); + if (shape.isEmpty()) shape = Shapes.block(); + build_shapes.put(state.setValue(WATERLOGGED, false), shape); + build_shapes.put(state.setValue(WATERLOGGED, true), shape); + } + { + // how the hack to extend a shape, these are the above with y+4px. + VoxelShape shape = ((base_aabb.getXsize() == 0) || (base_aabb.getYsize() == 0) || (base_aabb.getZsize() == 0)) ? Shapes.empty() : Shapes.create(base_aabb); + if (state.getValue(NORTH)) + shape = Shapes.joinUnoptimized(shape, Auxiliaries.getUnionShape(Auxiliaries.getMappedAABB(Auxiliaries.getRotatedAABB(side_aabb, + Direction.NORTH, true), bb -> bb.expandTowards(0, railing_height_extension, 0))), BooleanOp.OR); + if (state.getValue(EAST)) + shape = Shapes.joinUnoptimized(shape, Auxiliaries.getUnionShape(Auxiliaries.getMappedAABB(Auxiliaries.getRotatedAABB(side_aabb, + Direction.EAST, true), bb -> bb.expandTowards(0, railing_height_extension, 0))), BooleanOp.OR); + if (state.getValue(SOUTH)) + shape = Shapes.joinUnoptimized(shape, Auxiliaries.getUnionShape(Auxiliaries.getMappedAABB(Auxiliaries.getRotatedAABB(side_aabb, + Direction.SOUTH, true), bb -> bb.expandTowards(0, railing_height_extension, 0))), BooleanOp.OR); + if (state.getValue(WEST)) + shape = Shapes.joinUnoptimized(shape, Auxiliaries.getUnionShape(Auxiliaries.getMappedAABB(Auxiliaries.getRotatedAABB(side_aabb, + Direction.WEST, true), bb -> bb.expandTowards(0, railing_height_extension, 0))), BooleanOp.OR); + if (shape.isEmpty()) shape = Shapes.block(); + build_collision_shapes.put(state.setValue(WATERLOGGED, false), shape); + build_collision_shapes.put(state.setValue(WATERLOGGED, true), shape); + } + } + shapes = build_shapes; + collision_shapes = build_collision_shapes; + registerDefaultState(super.defaultBlockState().setValue(NORTH, false).setValue(EAST, false).setValue(SOUTH, false).setValue(WEST, false).setValue(WATERLOGGED, false)); + } + + public HorizontalFourWayWaterLoggable(long config, BlockBehaviour.Properties properties, AABB base_aabb, final AABB side_aabb, int railing_height_extension) { + this(config, properties, base_aabb, new AABB[]{side_aabb}, railing_height_extension); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + super.createBlockStateDefinition(builder); + builder.add(NORTH, EAST, SOUTH, WEST); + } + + @Override + @Nullable + public BlockState getStateForPlacement(BlockPlaceContext context) { + return super.getStateForPlacement(context).setValue(NORTH, false).setValue(EAST, false).setValue(SOUTH, false).setValue(WEST, false); + } + + @Override + public VoxelShape getShape(BlockState state, BlockGetter worldIn, BlockPos pos, CollisionContext context) { + return shapes.getOrDefault(state, Shapes.block()); + } + + @Override + public VoxelShape getCollisionShape(BlockState state, BlockGetter worldIn, BlockPos pos, CollisionContext context) { + return collision_shapes.getOrDefault(state, Shapes.block()); + } + + public static BooleanProperty getDirectionProperty(Direction face) { + return switch (face) { + case EAST -> HorizontalFourWayWaterLoggable.EAST; + case SOUTH -> HorizontalFourWayWaterLoggable.SOUTH; + case WEST -> HorizontalFourWayWaterLoggable.WEST; + default -> HorizontalFourWayWaterLoggable.NORTH; + }; + } + } + +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardDoorBlock.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardDoorBlock.java new file mode 100644 index 0000000..8c7f10b --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardDoorBlock.java @@ -0,0 +1,175 @@ +/* + * @file StandardDoorBlock.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * Door blocks, almost entirely based on vanilla. + */ +package dev.zontreck.libzontreck.edlibmc; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.network.chat.Component; +import net.minecraft.sounds.SoundEvent; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.sounds.SoundSource; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.SpawnPlacements; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.DoorBlock; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.BlockSetType; +import net.minecraft.world.level.block.state.properties.DoorHingeSide; +import net.minecraft.world.level.block.state.properties.DoubleBlockHalf; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.shapes.BooleanOp; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.Shapes; +import net.minecraft.world.phys.shapes.VoxelShape; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import javax.annotation.Nullable; +import java.util.List; + + +public class StandardDoorBlock extends DoorBlock implements StandardBlocks.IStandardBlock { + private final long config_; + protected final VoxelShape[][][][] shapes_; + protected final SoundEvent open_sound_; + protected final SoundEvent close_sound_; + + public StandardDoorBlock(long config, BlockBehaviour.Properties properties, AABB[] open_aabbs_top, AABB[] open_aabbs_bottom, AABB[] closed_aabbs_top, AABB[] closed_aabbs_bottom, SoundEvent open_sound, SoundEvent close_sound, BlockSetType blockSetType) { + super(properties, blockSetType); + VoxelShape[][][][] shapes = new VoxelShape[Direction.values().length][2][2][2]; + for (Direction facing : Direction.values()) { + for (boolean open : new boolean[]{false, true}) { + for (DoubleBlockHalf half : new DoubleBlockHalf[]{DoubleBlockHalf.UPPER, DoubleBlockHalf.LOWER}) { + for (boolean hinge_right : new boolean[]{false, true}) { + VoxelShape shape = Shapes.empty(); + if (facing.getAxis() == Direction.Axis.Y) { + shape = Shapes.block(); + } else { + final AABB[] aabbs = (open) ? ((half == DoubleBlockHalf.UPPER) ? open_aabbs_top : open_aabbs_bottom) : ((half == DoubleBlockHalf.UPPER) ? closed_aabbs_top : closed_aabbs_bottom); + for (AABB e : aabbs) { + AABB aabb = Auxiliaries.getRotatedAABB(e, facing, true); + if (!hinge_right) + aabb = Auxiliaries.getMirroredAABB(aabb, facing.getClockWise().getAxis()); + shape = Shapes.join(shape, Shapes.create(aabb), BooleanOp.OR); + } + } + shapes[facing.ordinal()][open ? 1 : 0][hinge_right ? 1 : 0][half == DoubleBlockHalf.UPPER ? 0 : 1] = shape; + } + } + } + } + config_ = config; + shapes_ = shapes; + open_sound_ = open_sound; + close_sound_ = close_sound; + } + + public StandardDoorBlock(long config, BlockBehaviour.Properties properties, AABB open_aabb, AABB closed_aabb, SoundEvent open_sound, SoundEvent close_sound, BlockSetType blockSetType) { + this(config, properties, new AABB[]{open_aabb}, new AABB[]{open_aabb}, new AABB[]{closed_aabb}, new AABB[]{closed_aabb}, open_sound, close_sound, blockSetType); + } + + public StandardDoorBlock(long config, BlockBehaviour.Properties properties, SoundEvent open_sound, SoundEvent close_sound, BlockSetType blockSetType) { + this( + config, properties, + Auxiliaries.getPixeledAABB(13, 0, 0, 16, 16, 16), + Auxiliaries.getPixeledAABB(0, 0, 13, 16, 16, 16), + open_sound, + close_sound, + blockSetType + ); + } + + public StandardDoorBlock(long config, BlockBehaviour.Properties properties) { + this( + config, properties, + Auxiliaries.getPixeledAABB(13, 0, 0, 16, 16, 16), + Auxiliaries.getPixeledAABB(0, 0, 13, 16, 16, 16), + SoundEvents.WOODEN_DOOR_OPEN, + SoundEvents.WOODEN_DOOR_CLOSE, + BlockSetType.OAK + ); + } + + @Override + public long config() { + return config_; + } + + protected void sound(BlockGetter world, BlockPos pos, boolean open) { + if (world instanceof Level) + ((Level) world).playSound(null, pos, open ? open_sound_ : close_sound_, SoundSource.BLOCKS, 0.7f, 1f); + } + + protected void actuate_adjacent_wing(BlockState state, BlockGetter world_ro, BlockPos pos, boolean open) { + if (!(world_ro instanceof final Level world)) return; + final BlockPos adjecent_pos = pos.relative((state.getValue(HINGE) == DoorHingeSide.LEFT) ? (state.getValue(FACING).getClockWise()) : (state.getValue(FACING).getCounterClockWise())); + if (!world.isLoaded(adjecent_pos)) return; + BlockState adjacent_state = world.getBlockState(adjecent_pos); + if (adjacent_state.getBlock() != this) return; + if (adjacent_state.getValue(OPEN) == open) return; + world.setBlock(adjecent_pos, adjacent_state.setValue(OPEN, open), 2 | 10); + } + + @Override + @OnlyIn(Dist.CLIENT) + public void appendHoverText(ItemStack stack, @Nullable BlockGetter world, List tooltip, TooltipFlag flag) { + Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); + } + + + @Override + public boolean isPossibleToRespawnInThis(BlockState p_279289_) { + return false; + } + + @Override + public boolean isValidSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType entityType) { + return false; + } + + @Override + public VoxelShape getShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext context) { + return shapes_[state.getValue(FACING).ordinal()][state.getValue(OPEN) ? 1 : 0][state.getValue(HINGE) == DoorHingeSide.RIGHT ? 1 : 0][state.getValue(HALF) == DoubleBlockHalf.UPPER ? 0 : 1]; + } + + @Override + public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { + setOpen(player, world, state, pos, !state.getValue(OPEN)); + return InteractionResult.sidedSuccess(world.isClientSide()); + } + + @Override + public void neighborChanged(BlockState state, Level world, BlockPos pos, Block block, BlockPos fromPos, boolean isMoving) { + boolean powered = world.hasNeighborSignal(pos) || world.hasNeighborSignal(pos.relative(state.getValue(HALF) == DoubleBlockHalf.LOWER ? Direction.UP : Direction.DOWN)); + if ((block == this) || (powered == state.getValue(POWERED))) return; + world.setBlock(pos, state.setValue(POWERED, powered).setValue(OPEN, powered), 2); + actuate_adjacent_wing(state, world, pos, powered); + if (powered != state.getValue(OPEN)) sound(world, pos, powered); + } + + @Override + public void setOpen(@Nullable Entity entity, Level world, BlockState state, BlockPos pos, boolean open) { + if (!state.is(this) || (state.getValue(OPEN) == open)) return; + state = state.setValue(OPEN, open); + world.setBlock(pos, state, 2 | 8); + sound(world, pos, open); + actuate_adjacent_wing(state, world, pos, open); + } + +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardEntityBlocks.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardEntityBlocks.java new file mode 100644 index 0000000..78bf385 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardEntityBlocks.java @@ -0,0 +1,72 @@ +/* + * @file StandardEntityBlocks.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * Common functionality class for blocks with block entities. + */ +package dev.zontreck.libzontreck.edlibmc; + +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.EntityBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityTicker; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.gameevent.GameEventListener; +import net.minecraftforge.common.util.FakePlayer; + +import javax.annotation.Nullable; + + +public class StandardEntityBlocks { + public interface IStandardEntityBlock extends EntityBlock { + + default boolean isBlockEntityTicking(Level world, BlockState state) { + return false; + } + + default InteractionResult useOpenGui(BlockState state, Level world, BlockPos pos, Player player) { + if (world.isClientSide()) return InteractionResult.SUCCESS; + final BlockEntity te = world.getBlockEntity(pos); + if (!(te instanceof MenuProvider) || ((player instanceof FakePlayer))) return InteractionResult.FAIL; + player.openMenu((MenuProvider) te); + return InteractionResult.CONSUME; + } + + @Override + @Nullable + default BlockEntity newBlockEntity(BlockPos pos, BlockState state) { + BlockEntityType tet = Registries.getBlockEntityTypeOfBlock(state.getBlock()); + return (tet == null) ? null : tet.create(pos, state); + } + + @Override + @Nullable + default BlockEntityTicker getTicker(Level world, BlockState state, BlockEntityType te_type) { + return (world.isClientSide || (!isBlockEntityTicking(world, state))) ? (null) : ((Level w, BlockPos p, BlockState s, T te) -> ((StandardBlockEntity) te).tick()); + } + + @Override + @Nullable + default GameEventListener getListener(ServerLevel world, T te) { + return null; + } + } + + public static abstract class StandardBlockEntity extends BlockEntity { + public StandardBlockEntity(BlockEntityType type, BlockPos pos, BlockState state) { + super(type, pos, state); + } + + public void tick() { + } + } + +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardFenceBlock.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardFenceBlock.java new file mode 100644 index 0000000..0097d76 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardFenceBlock.java @@ -0,0 +1,203 @@ +/* + * @file StandardFenceBlock.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * Wall blocks. + */ +package dev.zontreck.libzontreck.edlibmc; + +import com.google.common.collect.ImmutableMap; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.network.chat.Component; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.SpawnPlacements; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.FenceGateBlock; +import net.minecraft.world.level.block.WallBlock; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.level.block.state.properties.BooleanProperty; +import net.minecraft.world.level.block.state.properties.EnumProperty; +import net.minecraft.world.level.block.state.properties.WallSide; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.level.material.Fluids; +import net.minecraft.world.level.material.PushReaction; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.Shapes; +import net.minecraft.world.phys.shapes.VoxelShape; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import javax.annotation.Nullable; +import java.util.List; +import java.util.Map; + + +public class StandardFenceBlock extends WallBlock implements StandardBlocks.IStandardBlock { + public static final BooleanProperty UP = BlockStateProperties.UP; + public static final EnumProperty WALL_EAST = BlockStateProperties.EAST_WALL; + public static final EnumProperty WALL_NORTH = BlockStateProperties.NORTH_WALL; + public static final EnumProperty WALL_SOUTH = BlockStateProperties.SOUTH_WALL; + public static final EnumProperty WALL_WEST = BlockStateProperties.WEST_WALL; + public static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED; + private final Map shape_voxels; + private final Map collision_shape_voxels; + private final long config; + + public StandardFenceBlock(long config, BlockBehaviour.Properties properties) { + this(config, properties, 1.5, 16, 1.5, 0, 14, 16); + } + + public StandardFenceBlock(long config, BlockBehaviour.Properties properties, double pole_width, double pole_height, double side_width, double side_min_y, double side_max_low_y, double side_max_tall_y) { + super(properties); + shape_voxels = buildShapes(pole_width, pole_height, side_width, side_min_y, side_max_low_y, side_max_tall_y); + collision_shape_voxels = buildShapes(pole_width, 24, pole_width, 0, 24, 24); + this.config = config; + } + + @Override + public long config() { + return config; + } + + @Override + @OnlyIn(Dist.CLIENT) + public void appendHoverText(ItemStack stack, @Nullable BlockGetter world, List tooltip, TooltipFlag flag) { + Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); + } + + private static VoxelShape combinedShape(VoxelShape pole, WallSide height, VoxelShape low, VoxelShape high) { + if (height == WallSide.TALL) return Shapes.or(pole, high); + if (height == WallSide.LOW) return Shapes.or(pole, low); + return pole; + } + + protected Map buildShapes(double pole_width, double pole_height, double side_width, double side_min_y, double side_max_low_y, double side_max_tall_y) { + final double px0 = 8.0 - pole_width, px1 = 8.0 + pole_width, sx0 = 8.0 - side_width, sx1 = 8.0 + side_width; + VoxelShape vp = Block.box(px0, 0, px0, px1, pole_height, px1); + VoxelShape vs1 = Block.box(sx0, side_min_y, 0, sx1, side_max_low_y, sx1); + VoxelShape vs2 = Block.box(sx0, side_min_y, sx0, sx1, side_max_low_y, 16); + VoxelShape vs3 = Block.box(0, side_min_y, sx0, sx1, side_max_low_y, sx1); + VoxelShape vs4 = Block.box(sx0, side_min_y, sx0, 16, side_max_low_y, sx1); + VoxelShape vs5 = Block.box(sx0, side_min_y, 0, sx1, side_max_tall_y, sx1); + VoxelShape vs6 = Block.box(sx0, side_min_y, sx0, sx1, side_max_tall_y, 16); + VoxelShape vs7 = Block.box(0, side_min_y, sx0, sx1, side_max_tall_y, sx1); + VoxelShape vs8 = Block.box(sx0, side_min_y, sx0, 16, side_max_tall_y, sx1); + ImmutableMap.Builder builder = ImmutableMap.builder(); + for (Boolean up : UP.getPossibleValues()) { + for (WallSide wh_east : WALL_EAST.getPossibleValues()) { + for (WallSide wh_north : WALL_NORTH.getPossibleValues()) { + for (WallSide wh_west : WALL_WEST.getPossibleValues()) { + for (WallSide wh_south : WALL_SOUTH.getPossibleValues()) { + VoxelShape shape = Shapes.empty(); + shape = combinedShape(shape, wh_east, vs4, vs8); + shape = combinedShape(shape, wh_west, vs3, vs7); + shape = combinedShape(shape, wh_north, vs1, vs5); + shape = combinedShape(shape, wh_south, vs2, vs6); + if (up) shape = Shapes.or(shape, vp); + BlockState bs = defaultBlockState().setValue(UP, up) + .setValue(WALL_EAST, wh_east) + .setValue(WALL_NORTH, wh_north) + .setValue(WALL_WEST, wh_west) + .setValue(WALL_SOUTH, wh_south); + builder.put(bs.setValue(WATERLOGGED, false), shape); + builder.put(bs.setValue(WATERLOGGED, true), shape); + } + } + } + } + } + return builder.build(); + } + + @Override + public VoxelShape getShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext) { + return shape_voxels.getOrDefault(state, Shapes.block()); + } + + @Override + public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext) { + return collision_shape_voxels.getOrDefault(state, Shapes.block()); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + super.createBlockStateDefinition(builder); + } + + protected boolean attachesTo(BlockState facingState, LevelReader world, BlockPos facingPos, Direction side) { + final Block block = facingState.getBlock(); + if ((block instanceof FenceGateBlock) || (block instanceof StandardFenceBlock) || (block instanceof VariantWallBlock)) + return true; + final BlockState oppositeState = world.getBlockState(facingPos.relative(side, 2)); + if (!(oppositeState.getBlock() instanceof StandardFenceBlock)) return false; + return facingState.isRedstoneConductor(world, facingPos) && canSupportCenter(world, facingPos, side); + } + + protected WallSide selectWallHeight(LevelReader world, BlockPos pos, Direction direction) { + return WallSide.LOW; // @todo: implement + } + + public BlockState getStateForPlacement(BlockPlaceContext context) { + LevelReader world = context.getLevel(); + BlockPos pos = context.getClickedPos(); + FluidState fs = context.getLevel().getFluidState(context.getClickedPos()); + boolean n = attachesTo(world.getBlockState(pos.north()), world, pos.north(), Direction.SOUTH); + boolean e = attachesTo(world.getBlockState(pos.east()), world, pos.east(), Direction.WEST); + boolean s = attachesTo(world.getBlockState(pos.south()), world, pos.south(), Direction.NORTH); + boolean w = attachesTo(world.getBlockState(pos.west()), world, pos.west(), Direction.EAST); + boolean not_straight = (!n || !s || e || w) && (n || s || !e || !w); + return defaultBlockState() + .setValue(UP, not_straight) + .setValue(WALL_NORTH, n ? selectWallHeight(world, pos, Direction.NORTH) : WallSide.NONE) + .setValue(WALL_EAST, e ? selectWallHeight(world, pos, Direction.EAST) : WallSide.NONE) + .setValue(WALL_SOUTH, s ? selectWallHeight(world, pos, Direction.SOUTH) : WallSide.NONE) + .setValue(WALL_WEST, w ? selectWallHeight(world, pos, Direction.WEST) : WallSide.NONE) + .setValue(WATERLOGGED, fs.getType() == Fluids.WATER); + } + + @Override + public BlockState updateShape(BlockState state, Direction side, BlockState facingState, LevelAccessor world, BlockPos pos, BlockPos facingPos) { + if (state.getValue(BlockStateProperties.WATERLOGGED)) + world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world)); + if (side == Direction.DOWN) return super.updateShape(state, side, facingState, world, pos, facingPos); + boolean n = (side == Direction.NORTH) ? attachesTo(facingState, world, facingPos, side) : (state.getValue(WALL_NORTH) != WallSide.NONE); + boolean e = (side == Direction.EAST) ? attachesTo(facingState, world, facingPos, side) : (state.getValue(WALL_EAST) != WallSide.NONE); + boolean s = (side == Direction.SOUTH) ? attachesTo(facingState, world, facingPos, side) : (state.getValue(WALL_SOUTH) != WallSide.NONE); + boolean w = (side == Direction.WEST) ? attachesTo(facingState, world, facingPos, side) : (state.getValue(WALL_WEST) != WallSide.NONE); + boolean not_straight = (!n || !s || e || w) && (n || s || !e || !w); + return state.setValue(UP, not_straight) + .setValue(WALL_NORTH, n ? selectWallHeight(world, pos, Direction.NORTH) : WallSide.NONE) + .setValue(WALL_EAST, e ? selectWallHeight(world, pos, Direction.EAST) : WallSide.NONE) + .setValue(WALL_SOUTH, s ? selectWallHeight(world, pos, Direction.SOUTH) : WallSide.NONE) + .setValue(WALL_WEST, w ? selectWallHeight(world, pos, Direction.WEST) : WallSide.NONE); + } + + @Override + public boolean isValidSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType entityType) { + return false; + } + + + @Override + public boolean isPossibleToRespawnInThis(BlockState p_279289_) { + return false; + } + + @Override + @SuppressWarnings("deprecation") + public PushReaction getPistonPushReaction(BlockState state) { + return PushReaction.NORMAL; + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardStairsBlock.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardStairsBlock.java new file mode 100644 index 0000000..64a9f05 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardStairsBlock.java @@ -0,0 +1,59 @@ +/* + * @file StandardStairsBlock.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * Stairs and roof blocks, almost entirely based on vanilla stairs. + */ +package dev.zontreck.libzontreck.edlibmc; + +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.SpawnPlacements; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.block.StairBlock; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.PushReaction; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import javax.annotation.Nullable; +import java.util.List; + + +public class StandardStairsBlock extends StairBlock implements StandardBlocks.IStandardBlock { + private final long config; + + public StandardStairsBlock(long config, java.util.function.Supplier state, BlockBehaviour.Properties properties) { + super(state, properties); + this.config = config; + } + + @Override + @OnlyIn(Dist.CLIENT) + public void appendHoverText(ItemStack stack, @Nullable BlockGetter world, List tooltip, TooltipFlag flag) { + Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); + } + + + @Override + public boolean isPossibleToRespawnInThis(BlockState p_279289_) { + return false; + } + + @Override + public boolean isValidSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType entityType) { + return false; + } + + @Override + @SuppressWarnings("deprecation") + public PushReaction getPistonPushReaction(BlockState state) { + return PushReaction.NORMAL; + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/TooltipDisplay.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/TooltipDisplay.java new file mode 100644 index 0000000..76eb760 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/TooltipDisplay.java @@ -0,0 +1,130 @@ +/* + * @file Tooltip.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * Delayed tooltip for a selected area. Constructed with a + * GUI, invoked in `render()`. + */ +package dev.zontreck.libzontreck.edlibmc; + +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; +import net.minecraft.network.chat.*; +import net.minecraft.util.Mth; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Supplier; + + +@OnlyIn(Dist.CLIENT) +public class TooltipDisplay { + private static long default_delay = 800; + private static int default_max_deviation = 1; + + public static void config(long delay, int max_deviation) { + default_delay = clamp(delay, 500, 5000); + default_max_deviation = Mth.clamp(max_deviation, 1, 5); + } + private static long clamp(long p1, long a, long b) { + return p1 < a ? a : Math.min(p1, b); + } + + // --------------------------------------------------------------------------------------------------- + + public static class TipRange { + public final int x0, y0, x1, y1; + public final Supplier text; + + public TipRange(int x, int y, int w, int h, Component text) { + this(x, y, w, h, () -> text); + } + + public TipRange(int x, int y, int w, int h, Supplier text) { + this.text = text; + this.x0 = x; + this.y0 = y; + this.x1 = x0 + w - 1; + this.y1 = y0 + h - 1; + } + + } + + // --------------------------------------------------------------------------------------------------- + + private List ranges = new ArrayList<>(); + private long delay = default_delay; + private int max_deviation = default_max_deviation; + private int x_last, y_last; + private long t; + private static boolean had_render_exception = false; + + public TooltipDisplay() { + t = System.currentTimeMillis(); + } + + public TooltipDisplay init(List ranges, long delay_ms, int max_deviation_xy) { + this.ranges = ranges; + this.delay = delay_ms; + this.max_deviation = max_deviation_xy; + t = System.currentTimeMillis(); + x_last = y_last = 0; + return this; + } + + public TooltipDisplay init(List ranges) { + return init(ranges, default_delay, default_max_deviation); + } + + public TooltipDisplay init(TipRange... ranges) { + return init(Arrays.asList(ranges), default_delay, default_max_deviation); + } + + public TooltipDisplay delay(int ms) { + delay = (ms <= 0) ? default_delay : ms; + return this; + } + + public void resetTimer() { + t = System.currentTimeMillis(); + } + + public boolean render(GuiGraphics mx, final AbstractContainerScreen gui, int x, int y) { + if (had_render_exception) return false; + if ((Math.abs(x - x_last) > max_deviation) || (Math.abs(y - y_last) > max_deviation)) { + x_last = x; + y_last = y; + resetTimer(); + return false; + } else if (Math.abs(System.currentTimeMillis() - t) < delay) { + return false; + } else if (ranges.stream().noneMatch( + (tip) -> { + if ((x < tip.x0) || (x > tip.x1) || (y < tip.y0) || (y > tip.y1)) return false; + String text = tip.text.get().toString(); + if (text.isEmpty()) return false; + try { + mx.renderComponentTooltip(Minecraft.getInstance().font, tip.text.get().toFlatList(Style.EMPTY), x, y); + } catch (Exception ex) { + had_render_exception = true; + Auxiliaries.logError("Tooltip rendering disabled due to exception: '" + ex.getMessage() + "'"); + return false; + } + return true; + }) + ) { + resetTimer(); + return false; + } else { + return true; + } + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/VariantSlabBlock.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/VariantSlabBlock.java new file mode 100644 index 0000000..4f2eeb7 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/VariantSlabBlock.java @@ -0,0 +1,220 @@ +/* + * @file VariantSlabBlock.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * Standard half block horizontal slab characteristics class. + */ +package dev.zontreck.libzontreck.edlibmc; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.network.chat.Component; +import net.minecraft.sounds.SoundSource; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.SpawnPlacements; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Mirror; +import net.minecraft.world.level.block.Rotation; +import net.minecraft.world.level.block.SoundType; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.level.block.state.properties.EnumProperty; +import net.minecraft.world.level.block.state.properties.IntegerProperty; +import net.minecraft.world.level.block.state.properties.SlabType; +import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.Shapes; +import net.minecraft.world.phys.shapes.VoxelShape; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + + +public class VariantSlabBlock extends StandardBlocks.WaterLoggable implements StandardBlocks.IStandardBlock { + public static final EnumProperty TYPE = BlockStateProperties.SLAB_TYPE; + public static final IntegerProperty TEXTURE_VARIANT = IntegerProperty.create("tvariant", 0, 3); + + protected static final VoxelShape[] AABBs = { + Shapes.create(new AABB(0, 8. / 16, 0, 1, 16. / 16, 1)), // top slab + Shapes.create(new AABB(0, 0. / 16, 0, 1, 8. / 16, 1)), // bottom slab + Shapes.create(new AABB(0, 0. / 16, 0, 1, 16. / 16, 1)), // both slabs + Shapes.create(new AABB(0, 0. / 16, 0, 1, 16. / 16, 1)) // << 2bit fill + }; + protected static final int[] num_slabs_contained_in_parts_ = {1, 1, 2, 2}; + private static boolean with_pickup = false; + + public static void on_config(boolean direct_slab_pickup) { + with_pickup = direct_slab_pickup; + } + + protected boolean is_cube(BlockState state) { + return state.getValue(TYPE) == SlabType.DOUBLE; + } + + public VariantSlabBlock(long config, BlockBehaviour.Properties builder) { + super(config, builder); + registerDefaultState(defaultBlockState().setValue(TYPE, SlabType.BOTTOM)); + } + + @Override + public RenderTypeHint getRenderTypeHint() { + return (((config & StandardBlocks.CFG_TRANSLUCENT) != 0) ? (RenderTypeHint.TRANSLUCENT) : (RenderTypeHint.CUTOUT)); + } + + @Override + @OnlyIn(Dist.CLIENT) + public void appendHoverText(ItemStack stack, @Nullable BlockGetter world, List tooltip, TooltipFlag flag) { + if (!Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true)) return; + if (with_pickup && Auxiliaries.Tooltip.helpCondition()) + Auxiliaries.Tooltip.addInformation("engineersdecor.tooltip.slabpickup", "engineersdecor.tooltip.slabpickup", tooltip, flag, true); + } + + @Override + @OnlyIn(Dist.CLIENT) + @SuppressWarnings("deprecation") + public boolean skipRendering(BlockState state, BlockState adjacentBlockState, Direction side) { + return (adjacentBlockState == state) || (super.skipRendering(state, adjacentBlockState, side)); + } + + @Override + public boolean isPossibleToRespawnInThis(BlockState state) { + return false; + } + + @Override + public boolean isValidSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType entityType) { + return false; + } + + @Override + public VoxelShape getShape(BlockState state, BlockGetter source, BlockPos pos, CollisionContext selectionContext) { + return AABBs[state.getValue(TYPE).ordinal() & 0x3]; + } + + @Override + public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext) { + return getShape(state, world, pos, selectionContext); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + super.createBlockStateDefinition(builder); + builder.add(TYPE, TEXTURE_VARIANT); + } + + @Override + @Nullable + public BlockState getStateForPlacement(BlockPlaceContext context) { + BlockPos pos = context.getClickedPos(); + if (context.getLevel().getBlockState(pos).getBlock() == this) + return context.getLevel().getBlockState(pos).setValue(TYPE, SlabType.DOUBLE).setValue(WATERLOGGED, false); + final int rnd = Mth.clamp((int) (Mth.getSeed(context.getClickedPos()) & 0x3), 0, 3); + final Direction face = context.getClickedFace(); + final BlockState placement_state = super.getStateForPlacement(context).setValue(TEXTURE_VARIANT, rnd); // fluid state + if (face == Direction.UP) return placement_state.setValue(TYPE, SlabType.BOTTOM); + if (face == Direction.DOWN) return placement_state.setValue(TYPE, SlabType.TOP); + if (!face.getAxis().isHorizontal()) return placement_state; + final boolean isupper = ((context.getClickLocation().y() - context.getClickedPos().getY()) > 0.5); + return placement_state.setValue(TYPE, isupper ? SlabType.TOP : SlabType.BOTTOM); + } + + @Override + @SuppressWarnings("deprecation") + public boolean canBeReplaced(BlockState state, BlockPlaceContext context) { + if (context.getItemInHand().getItem() != this.asItem()) return false; + if (!context.replacingClickedOnBlock()) return true; + final Direction face = context.getClickedFace(); + final SlabType type = state.getValue(TYPE); + if ((face == Direction.UP) && (type == SlabType.BOTTOM)) return true; + if ((face == Direction.DOWN) && (type == SlabType.TOP)) return true; + if (!face.getAxis().isHorizontal()) return false; + final boolean isupper = ((context.getClickLocation().y() - context.getClickedPos().getY()) > 0.5); + return isupper ? (type == SlabType.BOTTOM) : (type == SlabType.TOP); + } + + @Override + @SuppressWarnings("deprecation") + public BlockState rotate(BlockState state, Rotation rot) { + return state; + } + + @Override + @SuppressWarnings("deprecation") + public BlockState mirror(BlockState state, Mirror mirrorIn) { + return state; + } + + @Override + public boolean hasDynamicDropList() { + return true; + } + + @Override + public List dropList(BlockState state, Level world, BlockEntity te, boolean explosion) { + return new ArrayList<>(Collections.singletonList(new ItemStack(this.asItem(), num_slabs_contained_in_parts_[state.getValue(TYPE).ordinal() & 0x3]))); + } + + @Override + @SuppressWarnings("deprecation") + public void attack(BlockState state, Level world, BlockPos pos, Player player) { + if ((world.isClientSide) || (!with_pickup)) return; + final ItemStack stack = player.getMainHandItem(); + if (stack.isEmpty() || (Block.byItem(stack.getItem()) != this)) return; + if (stack.getCount() >= stack.getMaxStackSize()) return; + Vec3 lv = player.getLookAngle(); + Direction facing = Direction.getNearest((float) lv.x, (float) lv.y, (float) lv.z); + if ((facing != Direction.UP) && (facing != Direction.DOWN)) return; + if (state.getBlock() != this) return; + SlabType type = state.getValue(TYPE); + if (facing == Direction.DOWN) { + if (type == SlabType.DOUBLE) { + world.setBlock(pos, state.setValue(TYPE, SlabType.BOTTOM), 3); + } else { + world.removeBlock(pos, false); + } + } else if (facing == Direction.UP) { + if (type == SlabType.DOUBLE) { + world.setBlock(pos, state.setValue(TYPE, SlabType.TOP), 3); + } else { + world.removeBlock(pos, false); + } + } + if (!player.isCreative()) { + stack.grow(1); + if (player.getInventory() != null) player.getInventory().setChanged(); + } + SoundType st = this.getSoundType(state, world, pos, null); + world.playSound(player, pos, st.getPlaceSound(), SoundSource.BLOCKS, (st.getVolume() + 1f) / 2.5f, 0.9f * st.getPitch()); + } + + @Override + public boolean placeLiquid(LevelAccessor world, BlockPos pos, BlockState state, FluidState fluidState) { + return (state.getValue(TYPE) != SlabType.DOUBLE) && super.placeLiquid(world, pos, state, fluidState); + } + + @Override + public boolean canPlaceLiquid(BlockGetter world, BlockPos pos, BlockState state, Fluid fluid) { + return (state.getValue(TYPE) != SlabType.DOUBLE) && super.canPlaceLiquid(world, pos, state, fluid); + } + +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/VariantWallBlock.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/VariantWallBlock.java new file mode 100644 index 0000000..92af666 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/VariantWallBlock.java @@ -0,0 +1,200 @@ +/* + * @file VariantWallBlock.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * Wall blocks. + */ +package dev.zontreck.libzontreck.edlibmc; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMap.Builder; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.network.chat.Component; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.SpawnPlacements; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.FenceGateBlock; +import net.minecraft.world.level.block.WallBlock; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.*; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.level.material.Fluids; +import net.minecraft.world.level.material.PushReaction; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.Shapes; +import net.minecraft.world.phys.shapes.VoxelShape; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import javax.annotation.Nullable; +import java.util.List; +import java.util.Map; + + +public class VariantWallBlock extends WallBlock implements StandardBlocks.IStandardBlock { + public static final BooleanProperty UP = BlockStateProperties.UP; + public static final EnumProperty WALL_EAST = BlockStateProperties.EAST_WALL; + public static final EnumProperty WALL_NORTH = BlockStateProperties.NORTH_WALL; + public static final EnumProperty WALL_SOUTH = BlockStateProperties.SOUTH_WALL; + public static final EnumProperty WALL_WEST = BlockStateProperties.WEST_WALL; + public static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED; + public static final IntegerProperty TEXTURE_VARIANT = IntegerProperty.create("tvariant", 0, 7); + private final Map shape_voxels; + private final Map collision_shape_voxels; + private final long config; + + public VariantWallBlock(long config, BlockBehaviour.Properties builder) { + super(builder); + shape_voxels = buildWallShapes(4, 16, 4, 0, 16, 16); + collision_shape_voxels = buildWallShapes(6, 16, 5, 0, 24, 24); + this.config = config; + } + + @Override + public long config() { + return config; + } + + @Override + @OnlyIn(Dist.CLIENT) + public void appendHoverText(ItemStack stack, @Nullable BlockGetter world, List tooltip, TooltipFlag flag) { + Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); + } + + private static VoxelShape combinedShape(VoxelShape pole, WallSide height, VoxelShape low, VoxelShape high) { + if (height == WallSide.TALL) return Shapes.or(pole, high); + if (height == WallSide.LOW) return Shapes.or(pole, low); + return pole; + } + + protected Map buildWallShapes(double pole_width, double pole_height, double side_width, double side_min_y, double side_max_low_y, double side_max_tall_y) { + final double px0 = 8.0 - pole_width, px1 = 8.0 + pole_width, sx0 = 8.0 - side_width, sx1 = 8.0 + side_width; + VoxelShape vp = Block.box(px0, 0, px0, px1, pole_height, px1); + VoxelShape vs1 = Block.box(sx0, side_min_y, 0, sx1, side_max_low_y, sx1); + VoxelShape vs2 = Block.box(sx0, side_min_y, sx0, sx1, side_max_low_y, 16); + VoxelShape vs3 = Block.box(0, side_min_y, sx0, sx1, side_max_low_y, sx1); + VoxelShape vs4 = Block.box(sx0, side_min_y, sx0, 16, side_max_low_y, sx1); + VoxelShape vs5 = Block.box(sx0, side_min_y, 0, sx1, side_max_tall_y, sx1); + VoxelShape vs6 = Block.box(sx0, side_min_y, sx0, sx1, side_max_tall_y, 16); + VoxelShape vs7 = Block.box(0, side_min_y, sx0, sx1, side_max_tall_y, sx1); + VoxelShape vs8 = Block.box(sx0, side_min_y, sx0, 16, side_max_tall_y, sx1); + Builder builder = ImmutableMap.builder(); + for (Boolean up : UP.getPossibleValues()) { + for (WallSide wh_east : WALL_EAST.getPossibleValues()) { + for (WallSide wh_north : WALL_NORTH.getPossibleValues()) { + for (WallSide wh_west : WALL_WEST.getPossibleValues()) { + for (WallSide wh_south : WALL_SOUTH.getPossibleValues()) { + VoxelShape shape = Shapes.empty(); + shape = combinedShape(shape, wh_east, vs4, vs8); + shape = combinedShape(shape, wh_west, vs3, vs7); + shape = combinedShape(shape, wh_north, vs1, vs5); + shape = combinedShape(shape, wh_south, vs2, vs6); + if (up) shape = Shapes.or(shape, vp); + BlockState bs = defaultBlockState().setValue(UP, up) + .setValue(WALL_EAST, wh_east) + .setValue(WALL_NORTH, wh_north) + .setValue(WALL_WEST, wh_west) + .setValue(WALL_SOUTH, wh_south); + final VoxelShape tvs = shape; + TEXTURE_VARIANT.getPossibleValues().forEach((tv) -> { + builder.put(bs.setValue(TEXTURE_VARIANT, tv).setValue(WATERLOGGED, false), tvs); + builder.put(bs.setValue(TEXTURE_VARIANT, tv).setValue(WATERLOGGED, true), tvs); + }); + } + } + } + } + } + return builder.build(); + } + + @Override + public VoxelShape getShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext) { + return shape_voxels.getOrDefault(state, Shapes.block()); + } + + @Override + public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext) { + return collision_shape_voxels.getOrDefault(state, Shapes.block()); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + super.createBlockStateDefinition(builder); + builder.add(TEXTURE_VARIANT); + } + + protected boolean attachesTo(BlockState facingState, LevelReader world, BlockPos facingPos, Direction side) { + final Block block = facingState.getBlock(); + if ((block instanceof FenceGateBlock) || (block instanceof WallBlock)) return true; + final BlockState oppositeState = world.getBlockState(facingPos.relative(side, 2)); + if (!(oppositeState.getBlock() instanceof VariantWallBlock)) return false; + return facingState.isRedstoneConductor(world, facingPos) && Block.canSupportCenter(world, facingPos, side); + } + + protected WallSide selectWallHeight(LevelReader world, BlockPos pos, Direction direction) { + return WallSide.LOW; + } + + public BlockState getStateForPlacement(BlockPlaceContext context) { + LevelReader world = context.getLevel(); + BlockPos pos = context.getClickedPos(); + FluidState fs = context.getLevel().getFluidState(context.getClickedPos()); + boolean n = attachesTo(world.getBlockState(pos.north()), world, pos.north(), Direction.SOUTH); + boolean e = attachesTo(world.getBlockState(pos.east()), world, pos.east(), Direction.WEST); + boolean s = attachesTo(world.getBlockState(pos.south()), world, pos.south(), Direction.NORTH); + boolean w = attachesTo(world.getBlockState(pos.west()), world, pos.west(), Direction.EAST); + boolean not_straight = (!n || !s || e || w) && (n || s || !e || !w); + return defaultBlockState().setValue(UP, not_straight) + .setValue(WALL_NORTH, n ? selectWallHeight(world, pos, Direction.NORTH) : WallSide.NONE) + .setValue(WALL_EAST, e ? selectWallHeight(world, pos, Direction.EAST) : WallSide.NONE) + .setValue(WALL_SOUTH, s ? selectWallHeight(world, pos, Direction.SOUTH) : WallSide.NONE) + .setValue(WALL_WEST, w ? selectWallHeight(world, pos, Direction.WEST) : WallSide.NONE) + .setValue(WATERLOGGED, fs.getType() == Fluids.WATER); + } + + @Override + public BlockState updateShape(BlockState state, Direction side, BlockState facingState, LevelAccessor world, BlockPos pos, BlockPos facingPos) { + if (state.getValue(WATERLOGGED)) world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world)); + if (side == Direction.DOWN) return super.updateShape(state, side, facingState, world, pos, facingPos); + boolean n = (side == Direction.NORTH) ? this.attachesTo(facingState, world, facingPos, side) : state.getValue(WALL_NORTH) != WallSide.NONE; + boolean e = (side == Direction.EAST) ? this.attachesTo(facingState, world, facingPos, side) : state.getValue(WALL_EAST) != WallSide.NONE; + boolean s = (side == Direction.SOUTH) ? this.attachesTo(facingState, world, facingPos, side) : state.getValue(WALL_SOUTH) != WallSide.NONE; + boolean w = (side == Direction.WEST) ? this.attachesTo(facingState, world, facingPos, side) : state.getValue(WALL_WEST) != WallSide.NONE; + boolean not_straight = (!n || !s || e || w) && (n || s || !e || !w); + return state.setValue(UP, not_straight) + .setValue(WALL_NORTH, n ? selectWallHeight(world, pos, Direction.NORTH) : WallSide.NONE) + .setValue(WALL_EAST, e ? selectWallHeight(world, pos, Direction.EAST) : WallSide.NONE) + .setValue(WALL_SOUTH, s ? selectWallHeight(world, pos, Direction.SOUTH) : WallSide.NONE) + .setValue(WALL_WEST, w ? selectWallHeight(world, pos, Direction.WEST) : WallSide.NONE) + .setValue(TEXTURE_VARIANT, ((int) Mth.getSeed(pos)) & 0x7); + } + + @Override + public boolean isValidSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType entityType) { + return false; + } + + @Override + public boolean isPossibleToRespawnInThis(BlockState state) { + return false; + } + + @Override + @SuppressWarnings("deprecation") + public PushReaction getPistonPushReaction(BlockState state) { + return PushReaction.NORMAL; + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/events/BlockRestoreQueueRegistrationEvent.java b/src/main/java/dev/zontreck/libzontreck/events/BlockRestoreQueueRegistrationEvent.java new file mode 100644 index 0000000..4ae35c5 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/events/BlockRestoreQueueRegistrationEvent.java @@ -0,0 +1,20 @@ +package dev.zontreck.libzontreck.events; + +import dev.zontreck.libzontreck.memory.world.BlockRestoreQueue; +import dev.zontreck.libzontreck.memory.world.BlockRestoreQueueRegistry; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.eventbus.api.Event; + +public class BlockRestoreQueueRegistrationEvent extends Event +{ + /** + * Registers the provided queue to be able to be ticked + * @param queue + */ + public void register(BlockRestoreQueue queue) + { + BlockRestoreQueueRegistry.addQueue(queue); + + MinecraftForge.EVENT_BUS.register(queue); + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/events/ForgeEventHandlers.java b/src/main/java/dev/zontreck/libzontreck/events/ForgeEventHandlers.java index fce1997..d24ffa8 100644 --- a/src/main/java/dev/zontreck/libzontreck/events/ForgeEventHandlers.java +++ b/src/main/java/dev/zontreck/libzontreck/events/ForgeEventHandlers.java @@ -52,7 +52,7 @@ public class ForgeEventHandlers { MinecraftForge.EVENT_BUS.post(new ProfileLoadedEvent(prof, player, level)); - DelayedExecutorService.getInstance().schedule(new Task("send-msg", true) { + Thread tx = new Thread(new Task("send-msg", true) { @Override public void run() { // Check player wallet, then send wallet to client @@ -61,7 +61,8 @@ public class ForgeEventHandlers { S2CServerAvailable avail = new S2CServerAvailable(); avail.send(player); } - }, 10); + }); + tx.start(); } @SubscribeEvent diff --git a/src/main/java/dev/zontreck/libzontreck/events/RegisterMigrationsEvent.java b/src/main/java/dev/zontreck/libzontreck/events/RegisterMigrationsEvent.java new file mode 100644 index 0000000..f305c8f --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/events/RegisterMigrationsEvent.java @@ -0,0 +1,22 @@ +package dev.zontreck.libzontreck.events; + +import dev.zontreck.libzontreck.memory.world.DatabaseMigrations; +import net.minecraftforge.eventbus.api.Event; + +import java.util.ArrayList; +import java.util.List; + +public class RegisterMigrationsEvent extends Event +{ + private List migrations = new ArrayList<>(); + + public void register(DatabaseMigrations.Migration migration) + { + migrations.add(migration); + } + + public List getMigrations() + { + return new ArrayList<>(migrations); + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/events/RegisterPacketsEvent.java b/src/main/java/dev/zontreck/libzontreck/events/RegisterPacketsEvent.java deleted file mode 100644 index 34a126b..0000000 --- a/src/main/java/dev/zontreck/libzontreck/events/RegisterPacketsEvent.java +++ /dev/null @@ -1,16 +0,0 @@ -package dev.zontreck.libzontreck.events; - -import java.util.ArrayList; -import java.util.List; - -import dev.zontreck.libzontreck.networking.packets.IPacket; -import net.minecraftforge.eventbus.api.Event; - -/** - * Used to register your packets with LibZontreck. Packets must extend IPacket and implement PacketSerializable. This is dispatched on both logical sides, and is considered a final event. It is not cancelable - * @see IPacket - */ -public class RegisterPacketsEvent extends Event -{ - public final List packets = new ArrayList<>(); -} diff --git a/src/main/java/dev/zontreck/libzontreck/events/TeleportEvent.java b/src/main/java/dev/zontreck/libzontreck/events/TeleportEvent.java new file mode 100644 index 0000000..2e7d77d --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/events/TeleportEvent.java @@ -0,0 +1,32 @@ +package dev.zontreck.libzontreck.events; + +import dev.zontreck.libzontreck.vectors.WorldPosition; +import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.eventbus.api.Cancelable; +import net.minecraftforge.eventbus.api.Event; + +/** + * This event should be cancelled if a Teleport Implementation is provided and handles the teleport + *
+ * The event not being cancelled should indicate that the sender should handle teleport themselves. + */ +@Cancelable +public class TeleportEvent extends Event +{ + WorldPosition position; + ServerPlayer player; + + public TeleportEvent(WorldPosition position, ServerPlayer player) + { + this.position=position; + this.player=player; + } + + public ServerPlayer getPlayer() { + return player; + } + + public WorldPosition getPosition() { + return position; + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/items/InputItemStackHandler.java b/src/main/java/dev/zontreck/libzontreck/items/InputItemStackHandler.java new file mode 100644 index 0000000..8a38e42 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/items/InputItemStackHandler.java @@ -0,0 +1,47 @@ +package dev.zontreck.libzontreck.items; + +import net.minecraft.core.NonNullList; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.items.ItemStackHandler; + +public class InputItemStackHandler extends ItemStackHandler { + private final ItemStackHandler internalSlot; + + public InputItemStackHandler(ItemStackHandler hidden) { + super(); + internalSlot = hidden; + } + + @Override + public void setSize(int size) { + stacks = NonNullList.withSize(size, ItemStack.EMPTY); + } + + @Override + public void setStackInSlot(int slot, ItemStack stack) { + internalSlot.setStackInSlot(slot, stack); + } + + @Override + public int getSlots() { + return internalSlot.getSlots(); + } + + @Override + public ItemStack getStackInSlot(int slot) { + return internalSlot.getStackInSlot(slot); + } + + @Override + public ItemStack insertItem(int slot, ItemStack stack, boolean simulate) { + setStackInSlot(slot, stack); + + return ItemStack.EMPTY; + } + + @Override + public ItemStack extractItem(int slot, int amount, boolean simulate) { + return ItemStack.EMPTY; + } +} + diff --git a/src/main/java/dev/zontreck/libzontreck/items/OutputItemStackHandler.java b/src/main/java/dev/zontreck/libzontreck/items/OutputItemStackHandler.java new file mode 100644 index 0000000..38edacf --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/items/OutputItemStackHandler.java @@ -0,0 +1,45 @@ +package dev.zontreck.libzontreck.items; + +import net.minecraft.core.NonNullList; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.items.ItemStackHandler; + +public class OutputItemStackHandler extends ItemStackHandler { + private final ItemStackHandler internalSlot; + + public OutputItemStackHandler(ItemStackHandler hidden) { + super(); + internalSlot = hidden; + } + + @Override + public void setSize(int size) { + stacks = NonNullList.withSize(size, ItemStack.EMPTY); + } + + @Override + public void setStackInSlot(int slot, ItemStack stack) { + internalSlot.setStackInSlot(slot, stack); + } + + @Override + public int getSlots() { + return internalSlot.getSlots(); + } + + @Override + public ItemStack getStackInSlot(int slot) { + return internalSlot.getStackInSlot(slot); + } + + @Override + public ItemStack insertItem(int slot, ItemStack stack, boolean simulate) { + return stack; + } + + @Override + public ItemStack extractItem(int slot, int amount, boolean simulate) { + return internalSlot.extractItem(slot, amount, simulate); + } +} + diff --git a/src/main/java/dev/zontreck/libzontreck/memory/PlayerComponent.java b/src/main/java/dev/zontreck/libzontreck/memory/player/PlayerComponent.java similarity index 86% rename from src/main/java/dev/zontreck/libzontreck/memory/PlayerComponent.java rename to src/main/java/dev/zontreck/libzontreck/memory/player/PlayerComponent.java index b169ec1..3c86f7e 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/PlayerComponent.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/player/PlayerComponent.java @@ -1,12 +1,12 @@ -package dev.zontreck.libzontreck.memory; +package dev.zontreck.libzontreck.memory.player; import java.util.UUID; -import dev.zontreck.libzontreck.LibZontreck; import dev.zontreck.libzontreck.exceptions.InvalidDeserialization; import dev.zontreck.libzontreck.vectors.WorldPosition; import net.minecraft.nbt.CompoundTag; import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.server.ServerLifecycleHooks; public class PlayerComponent { @@ -34,7 +34,7 @@ public class PlayerComponent public static PlayerComponent fromID(UUID ID) { - return new PlayerComponent(LibZontreck.THE_SERVER.getPlayerList().getPlayer(ID)); + return new PlayerComponent(ServerLifecycleHooks.getCurrentServer().getPlayerList().getPlayer(ID)); } public CompoundTag serialize() diff --git a/src/main/java/dev/zontreck/libzontreck/memory/PlayerContainer.java b/src/main/java/dev/zontreck/libzontreck/memory/player/PlayerContainer.java similarity index 70% rename from src/main/java/dev/zontreck/libzontreck/memory/PlayerContainer.java rename to src/main/java/dev/zontreck/libzontreck/memory/player/PlayerContainer.java index b898ba5..fcbaa38 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/PlayerContainer.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/player/PlayerContainer.java @@ -1,10 +1,10 @@ -package dev.zontreck.libzontreck.memory; +package dev.zontreck.libzontreck.memory.player; import java.util.UUID; -import dev.zontreck.libzontreck.LibZontreck; import net.minecraft.nbt.CompoundTag; import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.server.ServerLifecycleHooks; public class PlayerContainer { public UUID ID; @@ -13,7 +13,7 @@ public class PlayerContainer { public PlayerContainer(UUID ID) { - this(LibZontreck.THE_SERVER.getPlayerList().getPlayer(ID)); + this(ServerLifecycleHooks.getCurrentServer().getPlayerList().getPlayer(ID)); } public PlayerContainer(ServerPlayer player) { diff --git a/src/main/java/dev/zontreck/libzontreck/memory/VolatilePlayerStorage.java b/src/main/java/dev/zontreck/libzontreck/memory/player/VolatilePlayerStorage.java similarity index 97% rename from src/main/java/dev/zontreck/libzontreck/memory/VolatilePlayerStorage.java rename to src/main/java/dev/zontreck/libzontreck/memory/player/VolatilePlayerStorage.java index d1abae8..630f35e 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/VolatilePlayerStorage.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/player/VolatilePlayerStorage.java @@ -1,4 +1,4 @@ -package dev.zontreck.libzontreck.memory; +package dev.zontreck.libzontreck.memory.player; import java.util.ArrayList; import java.util.Iterator; diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestore.java b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestore.java new file mode 100644 index 0000000..4e85f16 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestore.java @@ -0,0 +1,19 @@ +package dev.zontreck.libzontreck.memory.world; + +public class BlockRestore extends BlockRestoreQueue +{ + @Override + public String getRestoreQueueName() { + return "BasicBlockSnapshots"; + } + + @Override + public void notifyDirtyQueue(boolean blockAdded) { + return; // We dont care. This is a basic queue + } + + @Override + public boolean sorted() { + return false; + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java new file mode 100644 index 0000000..287c95c --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java @@ -0,0 +1,333 @@ +package dev.zontreck.libzontreck.memory.world; + +import dev.zontreck.libzontreck.LibZontreck; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtIo; +import net.minecraft.server.level.ServerLevel; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.TickEvent; +import net.minecraftforge.event.server.ServerStoppingEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; + +import java.io.*; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + + +public abstract class BlockRestoreQueue +{ + private List BLOCK_QUEUE = new ArrayList<>(); + private final BlockRestoreRunner RUNNER; + private ScheduledFuture RUNNING_TASK; + private ScheduledFuture DATABASE_UPLOAD_RUNNER; + + /** + * When in database mode, this flag will be checked by the Restore Runner so a database call is not made unnecessarily. + */ + private boolean hasBlocks = true; + + public BlockRestoreQueue() + { + RUNNER = new BlockRestoreRunner(this); + + MinecraftForge.EVENT_BUS.register(this); + } + + /** + * When true, uses the database to store blocks. The blocks stored in the database will not be loaded into memory at runtime + * @return + */ + public boolean usesDatabase() + { + return false; + } + + /** + * Returns the restore queue name + * @return Name of the restore queue + */ + public abstract String getRestoreQueueName(); + + /** + * @return The number of blocks remaining in this queue + */ + public int getQueuedBlocks() + { + return BLOCK_QUEUE.size(); + } + + /** + * Queues a block to be restored + * @param block + */ + public void enqueueBlock(SavedBlock block) + { + enqueueBlock(block.getBlockPrimitive()); + } + + /** + * Queues a block to be restored + * @param block + */ + public void enqueueBlock(PrimitiveBlock block) + { + /* + if(usesDatabase()) + { + databaseUpdate(block); + notifyDirtyQueue(true); + hasBlocks=true; + return; + }*/ + BLOCK_QUEUE.add(block); + + notifyDirtyQueue(true); + } + + /** + * Called when enqueuing a block, this is a special handler to insert the block to the database. Custom queues should override this to put into a different table, or add additional data + * @param block + */ + public void databaseUpdate(PrimitiveBlock block) + { + + hasBlocks=true; + PreparedStatement pstmt = null; + try { + pstmt = DatabaseWrapper.get().prepareStatement("INSERT INTO `blocks` (queueName, posX, posY, posZ, snapshotID, block) VALUES (?, ?, ?, ?, ?, ?);"); + pstmt.setString(1, getRestoreQueueName()); + pstmt.setInt(2, block.position.getX()); + pstmt.setInt(3, block.position.getY()); + pstmt.setInt(4, block.position.getZ()); + pstmt.setInt(5, 0); + ByteArrayOutputStream blockState = new ByteArrayOutputStream(); + DataOutputStream dos0 = new DataOutputStream(blockState); + NbtIo.write(block.serialize(), dos0); + pstmt.setBytes(6, blockState.toByteArray()); + + + DatabaseWrapper.get().executePreparedStatement(pstmt); + + } catch (Exception e) + { + // Duplicate block insertion, we only cache each block one time by default. If this function is overridden to use a different table, perhaps multiple blocks for the same position could be cached. + } + } + + /** + * Executed when the queue is modified. + * @param blockAdded Whether a block was added or removed from the queue + */ + public abstract void notifyDirtyQueue(boolean blockAdded); + + /** + * Pops a block off the queue, and returns it + * @return A PrimitiveBlock instance of the SavedBlock + */ + public PrimitiveBlock getNextBlock() + { + if (usesDatabase()) { + // Send a query to the database to retrieve the block, and reconstruct here + try { + PreparedStatement sel; + if (sorted()) { + sel = DatabaseWrapper.get().prepareStatement("SELECT * FROM `blocks` WHERE queueName=? ORDER BY posY ASC LIMIT 1;"); + } else + sel = DatabaseWrapper.get().prepareStatement("SELECT * FROM `blocks` WHERE queueName=? LIMIT 1;"); + + sel.setString(1, getRestoreQueueName()); + ResultSet res = DatabaseWrapper.get().executePreparedStatementQuery(sel); + // Now retrieve the block from the database + if (res.next()) { + byte[] data = res.getBytes("block"); + ByteArrayInputStream bais = new ByteArrayInputStream(data); + DataInputStream dis = new DataInputStream(bais); + + PrimitiveBlock block = PrimitiveBlock.deserialize(NbtIo.read(dis)); + + if(block.level.getBlockState(block.position).is(block.blockType)) + { + + try { + res.deleteRow(); + if (!res.rowDeleted()) { + + } + } catch (SQLException e001) { + PreparedStatement pstat = DatabaseWrapper.get().prepareStatement("DELETE FROM `blocks` WHERE queueName=? AND posX=? AND posY=? AND posZ=?;"); + pstat.setString(1, getRestoreQueueName()); + pstat.setInt(2, block.position.getX()); + pstat.setInt(3, block.position.getY()); + pstat.setInt(4, block.position.getZ()); + DatabaseWrapper.get().executePreparedStatement(pstat); + } + } + + return block; + } else return null; + + } catch (SQLException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + PrimitiveBlock blk = BLOCK_QUEUE.get(0); + BLOCK_QUEUE.remove(0); + notifyDirtyQueue(false); + return blk; + } + + /** + * Sets the hasBlocks flag to false to reduce DB Spam + */ + public void setNoBlocks() + { + hasBlocks=false; + } + + /** + * Override to indicate if the list should be sorted by lowest Y value + * + * @return + */ + public abstract boolean sorted(); + + /** + * Whether the queue has blocks or not + * @return + */ + public boolean hasBlocks() + { + if(usesDatabase()) return hasBlocks; + else return getQueuedBlocks() != 0; + } + + /** + * Clears the entire queue, discarding the saved blocks permanently. + */ + public void clear() + { + BLOCK_QUEUE.clear(); + notifyDirtyQueue(false); + } + + /** + * Returns the raw block queue instance + * @return + */ + public List getQueue() { + return BLOCK_QUEUE; + } + + /** + * Sets the block queue, without notifying listeners + * @param queue + */ + public void setQueueNoNotify(List queue) + { + BLOCK_QUEUE = queue; + } + + /** + * Sets the block queue, and notifies any listeners that blocks were potentially added + * @param queue + */ + public void setQueue(List queue) + { + BLOCK_QUEUE = queue; + notifyDirtyQueue(true); + } + + /** + * Gets the restore runner instance initialized for this queue + * @return + */ + public BlockRestoreRunner getRunner() + { + return RUNNER; + } + + /** + * Must be called manually to register a restore queue. This will have a 2 second fixed delay before initial execution + */ + public void schedule(long interval, TimeUnit unit) + { + RUNNING_TASK = LibZontreck.executor.scheduleAtFixedRate(RUNNER, 2000, interval, unit); + + DATABASE_UPLOAD_RUNNER = LibZontreck.executor.scheduleAtFixedRate(new DatabaseUploadRunner(this), 2000, 50, TimeUnit.MILLISECONDS); + + isCancelled=false; + } + + /** + * Cancels the restore job + */ + public void cancel() + { + isCancelled=true; + RUNNING_TASK.cancel(false); + } + + public boolean isCancelled=false; + + /** + * Remove a block from the local queue. This does not impact the database and is used internally + * @param block + */ + public void dequeue(PrimitiveBlock block) + { + BLOCK_QUEUE.remove(block); + } + + /** + * Cancels the repeating upload to database task. This is automatically invoked when cancel has been invoked, and no more tasks are to be uploaded. + */ + public void cancelUploader() + { + DATABASE_UPLOAD_RUNNER.cancel(true); + } + + @SubscribeEvent + public void onServerStopping(ServerStoppingEvent event) + { + cancel(); + } + + /** + * Initialize a restore Queue for a specific level. This will load and import the blocks in the save data into this queue + * @param level The level to load for + * @throws IOException On failure to read a file + */ + public void initialize(ServerLevel level) throws IOException { + if(usesDatabase()) + { + return; + } + var file = SaveDataFactory.builder().withDimension(level).withQueueID(this).withPosition(null).build(); + + if(!file.getSaveDataPath().toFile().exists()) + { + return; + } + CompoundTag tag = NbtIo.read(file.getSaveDataPath().toFile()); + SaveDataFactory.SaveDataManifest manifest = SaveDataFactory.SaveDataManifest.deserialize(tag); + + List files = manifest.get(); + for(SaveDataCoordinates chunk : files) + { + var saved = SaveDataFactory.builder().withDimension(level).withQueueID(this).withPosition(chunk.toBlockPos()).build(); + var saveData = saved.getInstance(); + for(SavedBlock sb : saveData.blocks) + { + enqueueBlock(sb); + } + } + + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueueRegistry.java b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueueRegistry.java new file mode 100644 index 0000000..57e18e0 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueueRegistry.java @@ -0,0 +1,65 @@ +package dev.zontreck.libzontreck.memory.world; + +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.Level; + +import java.io.IOException; +import java.util.*; + +/** + * DANGER: DO NOT USE THIS CLASS DIRECTLY + */ +public class BlockRestoreQueueRegistry +{ + private static Map QUEUES = new HashMap<>(); + + /** + * Internal use only + * + * @see dev.zontreck.libzontreck.events.BlockRestoreQueueRegistrationEvent + * @param queue The queue to register + */ + public static void addQueue(BlockRestoreQueue queue) { + QUEUES.put(queue.getRestoreQueueName(), queue); + } + + /** + * Retrieves a registered restore queue by its name + * @param restoreQueueName Queue Name + * @return + */ + public static BlockRestoreQueue getQueue(String restoreQueueName) { + return QUEUES.get(restoreQueueName); + } + + /** + * Returns a iterator for a list of all the queues. This cannot remove items from the main queue. + * @return + */ + public static Iterator getReadOnlyQueue() { + List queues = new ArrayList<>(); + queues.addAll(QUEUES.values()); + return queues.iterator(); + } + + /** + * Initialize a block restore queue. + *

+ * Block Restore Queues are level independent, but blocks are not. This loads the blocks saved for this queue in that particular level's hierarchy + * @param level The level to load the queue for + */ + public static void init(ServerLevel level) + { + + Iterator it = BlockRestoreQueueRegistry.getReadOnlyQueue(); + while(it.hasNext()) + { + BlockRestoreQueue queue = it.next(); + try { + queue.initialize(level); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java new file mode 100644 index 0000000..8f1fdad --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java @@ -0,0 +1,56 @@ +package dev.zontreck.libzontreck.memory.world; + +import dev.zontreck.libzontreck.vectors.Vector3d; +import net.minecraft.core.BlockPos; +import net.minecraft.sounds.SoundEvent; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.sounds.SoundSource; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; + +import java.util.Random; + +public class BlockRestoreRunner implements Runnable +{ + public BlockRestoreRunner(BlockRestoreQueue queue) + { + this.queue = queue; + } + + private BlockRestoreQueue queue; + public final SoundEvent pop = SoundEvents.ITEM_PICKUP; + + @Override + public void run() { + if(queue.getQueuedBlocks() == 0 && !queue.usesDatabase()) return; // We'll be queued back up later + + if(!queue.hasBlocks()) + return; + + PrimitiveBlock prim = queue.getNextBlock(); + if(prim == null){ + queue.setNoBlocks(); + return; // No more blocks. + } + + Level level = prim.level; + + // Everything is restored, play sound + SoundSource ss = SoundSource.NEUTRAL; + BlockPos pos = prim.position; + Random rng = new Random(); + + level.playSound(null, pos, pop, ss, rng.nextFloat(0.75f,1.0f), rng.nextFloat(1)); + + level.setBlock(pos, prim.blockState, Block.UPDATE_CLIENTS, 0); + + BlockEntity entity = level.getBlockEntity(pos); + if(entity != null) + { + entity.load(prim.blockEntity); + } + + + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java new file mode 100644 index 0000000..d535384 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java @@ -0,0 +1,208 @@ +package dev.zontreck.libzontreck.memory.world; + +import com.google.common.collect.Lists; +import dev.zontreck.libzontreck.LibZontreck; +import dev.zontreck.libzontreck.events.RegisterMigrationsEvent; +import net.minecraftforge.common.MinecraftForge; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +public class DatabaseMigrations +{ + public static class Migration + { + String tableID; + int version; + List migrationActions = new ArrayList<>(); + + private Migration(){ + tableID = ""; + version = 0; + } + + /** + * Builder pattern function - Sets the table ID for the migration + * @param tableID + * @return + */ + public Migration withTableID(String tableID) + { + this.tableID = tableID; + return this; + } + + /** + * Builder pattern function - Sets the table version for the migration + * @param version + * @return + */ + public Migration withVersion(int version) + { + this.version = version; + return this; + } + + /** + * Builder pattern function - Adds the action to be executed. The list will operate as FILO. + * @param pstat + * @return + */ + public Migration withMigrationAction(PreparedStatement pstat) + { + migrationActions.add(pstat); + return this; + } + + /** + * Executes the migration as defined by the builder pattern. + */ + public void execute() + { + for(PreparedStatement pstmt : migrationActions) + { + try { + DatabaseWrapper.get().executePreparedStatement(pstmt); + } catch (SQLException e) { + LibZontreck.LOGGER.warn("There was a problem executing a migration. The migration is " + pstmt+"\n\nThis does not necessarily mean a failure. If everything seems to work fine, this migration might not have been necessary.\n\n"); + e.printStackTrace(); + } + } + + try { + + PreparedStatement pstat = DatabaseWrapper.get().prepareStatement("REPLACE INTO `migrations` (tableID, version) VALUES (?,?);"); + pstat.setString(1, tableID); + pstat.setInt(2, version); + + LibZontreck.LOGGER.info("SQL QUERY: " + pstat); + + pstat.execute(); + } catch (SQLException ex) + { + ex.printStackTrace(); + } + + + } + + } + private static List migrations = new ArrayList<>(); + + public static void initMigrations() throws SQLException { + Migration migrationsTable = builder() + .withVersion(1) + .withTableID("migrations"); + + PreparedStatement statement = DatabaseWrapper.get().prepareStatement("CREATE TABLE `migrations` (" + + " `tableID` varchar(255) NOT NULL," + + " `version` int(11) NOT NULL," + + " PRIMARY KEY (`tableID`)," + + " UNIQUE KEY `tableID` (`tableID`)" + + ") ;"); + migrations.add(migrationsTable.withMigrationAction(statement)); + + Migration blocksTable = builder() + .withTableID("blocks") + .withVersion(1); + + PreparedStatement makeBlocksTable = DatabaseWrapper.get().prepareStatement("CREATE TABLE `blocks` (" + + " `time` timestamp NOT NULL DEFAULT current_timestamp()," + + " `queueName` varchar(255) NOT NULL," + + " `posX` int(11) NOT NULL," + + " `posY` int(11) NOT NULL," + + " `posZ` int(11) NOT NULL," + + " `snapshotID` int(11) NOT NULL DEFAULT 0 COMMENT 'Enables multiple blocks existing at the same position'," + + " `block` blob NOT NULL COMMENT 'NBT Data representing a SavedBlock'," + + " PRIMARY KEY (`time`)" + + ") ;"); + + migrations.add(blocksTable.withMigrationAction(makeBlocksTable)); + + Migration blocksUpdate = builder() + .withTableID("blocks") + .withVersion(2); + PreparedStatement removePKey = DatabaseWrapper.get().prepareStatement("ALTER TABLE `blocks` DROP PRIMARY KEY;"); + blocksUpdate.withMigrationAction(removePKey); + PreparedStatement addIDColumn = DatabaseWrapper.get().prepareStatement("ALTER TABLE `blocks` ADD `ID` INT NOT NULL AUTO_INCREMENT FIRST, ADD PRIMARY KEY (`ID`);"); + blocksUpdate.withMigrationAction(addIDColumn); + + migrations.add(blocksUpdate); + + migrations.add(builder() + .withTableID("blocks") + .withVersion(3) + .withMigrationAction(DatabaseWrapper.get().prepareStatement("ALTER TABLE `blocks` ADD UNIQUE (`posX`, `posY`, `posZ`); "))); + + + + RegisterMigrationsEvent rme = new RegisterMigrationsEvent(); + MinecraftForge.EVENT_BUS.post(rme); + + + migrations.addAll(rme.getMigrations()); + + + executeMigrations(); + } + + private static void executeMigrations() + { + + Migration lastTableChecked = null; + for(Migration m : migrations) + { + + if(lastTableChecked == null) lastTableChecked = getCurrentTable(m.tableID); + else { + if(lastTableChecked.tableID != m.tableID) lastTableChecked = getCurrentTable(m.tableID); + } + + if(lastTableChecked == null || m.version > lastTableChecked.version) { + + LibZontreck.LOGGER.info("Executing migration " + m.tableID + ":" + m.version); + m.execute(); + } else { + LibZontreck.LOGGER.info("Skipping migration on table " + m.tableID + "; Current table version is " + lastTableChecked.version); + } + } + } + + /** + * Gets the current table's version using the Migration structure for data fields. Will be null if there is an error on any table except for migrations. + * @return + */ + private static Migration getCurrentTable(String tableID) + { + try{ + PreparedStatement pst = DatabaseWrapper.get().prepareStatement("SELECT * FROM `migrations` WHERE tableID=?;"); + pst.setString(1, tableID); + + var result = pst.executeQuery(); + if(!result.next()) + { + return builder().withTableID(tableID).withVersion(0); + }else { + return builder().withTableID(tableID).withVersion(result.getInt("version")); + } + + }catch (Exception ex) + { + if(tableID == "migrations") + { + return builder().withTableID(tableID) + .withVersion(0); + } + ex.printStackTrace(); + + return null; + } + } + + public static Migration builder() + { + return new Migration(); + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseUploadRunner.java b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseUploadRunner.java new file mode 100644 index 0000000..2430114 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseUploadRunner.java @@ -0,0 +1,26 @@ +package dev.zontreck.libzontreck.memory.world; + +public class DatabaseUploadRunner implements Runnable { + private BlockRestoreQueue QUEUE; + public DatabaseUploadRunner(BlockRestoreQueue queue) + { + QUEUE = queue; + } + + @Override + public void run() { + if(QUEUE.getQueuedBlocks() == 0) + { + if(QUEUE.isCancelled) + { + QUEUE.cancelUploader(); + return; + } + } else { + PrimitiveBlock block = QUEUE.getQueue().get(0); + QUEUE.dequeue(block); + + QUEUE.databaseUpdate(block); + } + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseWrapper.java b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseWrapper.java new file mode 100644 index 0000000..35c8d59 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseWrapper.java @@ -0,0 +1,163 @@ +package dev.zontreck.libzontreck.memory.world; + + +import dev.zontreck.libzontreck.LibZontreck; +import dev.zontreck.libzontreck.config.ServerConfig; + +import java.sql.*; + +public class DatabaseWrapper { + private Connection connection; + public static boolean hasDB = true; + + private static DatabaseWrapper instance; + + public static DatabaseWrapper get() { + if(!hasDB) { + throw new RuntimeException("Error: Database is not set up"); + } + + if (instance == null) + start(); + + try { + instance.sanityCheck(); + } catch (SQLException e) { + throw new RuntimeException(e); + } + return instance; + } + + /** + * This function will return true if the database drivers are available. + * + * @return + */ + public static boolean databaseIsAvailable() { + return instance.connection!=null; + } + + public DatabaseWrapper() { + connection = null; + } + + public static void start() { + instance = new DatabaseWrapper(); + try { + LibZontreck.LOGGER.info("Connecting to database..."); + LibZontreck.LOGGER.info("jdbc:db ://" + ServerConfig.database.user + "@" + ServerConfig.database.host + "/" + ServerConfig.database.database); + + instance.connect(ServerConfig.database.host, ServerConfig.database.user, ServerConfig.database.password, ServerConfig.database.database); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + public static void invalidate() { + instance=null; + hasDB=false; + } + + private void restart() { + + LibZontreck.LOGGER.info("Reconnecting to database..."); + LibZontreck.LOGGER.info("jdbc:db ://" + ServerConfig.database.user + "@" + ServerConfig.database.host + "/" + ServerConfig.database.database); + + try { + instance.connect(ServerConfig.database.host, ServerConfig.database.user, ServerConfig.database.password, ServerConfig.database.database); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + public void connect(String url, String username, String password, String database) throws SQLException { + if(database.isBlank()) + { + ServerConfig.init(); + if(ServerConfig.database.database.isBlank()) + { + throw new SQLException("Failed to connect to database"); + } else { + start(); + return; + } + } + try { + // Try MariaDB JDBC driver + Class.forName("org.mariadb.jdbc.Driver"); + connection = DriverManager.getConnection("jdbc:mariadb://" + url + "/" + database, username, password); + } catch (ClassNotFoundException | SQLException e) { + // MariaDB not found or failed to connect, try MySQL + LibZontreck.LOGGER.warn("Failed to connect via MariaDB: " + e.getMessage() + "; Attempting to fall back to mysql"); + try { + Class.forName("com.mysql.cj.jdbc.Driver"); + connection = DriverManager.getConnection("jdbc:mysql://" + url + "/" + database, username, password); + } catch (ClassNotFoundException | SQLException ex) { + // MySQL not found or failed to connect, try SQLite + try { + + Class.forName("com.mysql.jdbc.Driver"); + connection = DriverManager.getConnection("jdbc:mysql://" + url + "/" + database, username, password); + } catch (ClassNotFoundException | SQLException ex1) { + + LibZontreck.LOGGER.warn("Failed to connect via MySQL: " + e.getMessage() + "; " + ex1.getMessage() + "; Attempting to fall back to sqlite"); + + try { + Class.forName("org.sqlite.JDBC"); + connection = DriverManager.getConnection("jdbc:sqlite:" + database + ".db"); + } catch (ClassNotFoundException | SQLException exc) { + LibZontreck.LOGGER.error("Failed to connect via SQLite: " + e.getMessage() + "; If you require the use of the block queues, please check the above warnings for explanation on cause. It could be that you do not have the relevant JDBC installed in your server mods list."); + } + } + } + } + } + + private void sanityCheck() throws SQLException { + if(connection.isClosed() || connection == null) { + restart(); + } + } + + public ResultSet executeQuery(String query) throws SQLException { + if (connection == null) { + throw new SQLException("Connection not established."); + } + Statement statement = connection.createStatement(); + return statement.executeQuery(query); + } + + public int executeUpdate(String statement) throws SQLException { + if (connection == null) { + throw new SQLException("Connection not established."); + } + return connection.createStatement().executeUpdate(statement); + } + + public void disconnect() throws SQLException { + if (connection != null && !connection.isClosed()) { + connection.close(); + } + } + + public int executePreparedStatement(PreparedStatement preparedStatement) throws SQLException { + if (connection == null) { + throw new SQLException("Connection not established."); + } + return preparedStatement.executeUpdate(); + } + + public ResultSet executePreparedStatementQuery(PreparedStatement query) throws SQLException { + if (connection == null) { + throw new SQLException("Connection not established."); + } + return query.executeQuery(); + } + + public PreparedStatement prepareStatement(String query) throws SQLException { + if (connection == null) { + throw new SQLException("Connection not established."); + } + return connection.prepareStatement(query); + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/PrimitiveBlock.java b/src/main/java/dev/zontreck/libzontreck/memory/world/PrimitiveBlock.java new file mode 100644 index 0000000..7cfef67 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/PrimitiveBlock.java @@ -0,0 +1,80 @@ +package dev.zontreck.libzontreck.memory.world; + +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; + +public class PrimitiveBlock +{ + public final SavedBlock savedBlock; + public final Block blockType; + public final BlockState blockState; + public final CompoundTag blockEntity; + public final BlockPos position; + public final ServerLevel level; + + public PrimitiveBlock(SavedBlock savedBlock, Block blockType, BlockState blockState, CompoundTag blockEntity, BlockPos position, ServerLevel level) + { + this.savedBlock = savedBlock; + this.blockType = blockType; + this.blockEntity = blockEntity; + this.position = position; + this.level = level; + this.blockState = blockState; + } + + /** + * Alias method + * @see SavedBlock#serialize() + * @return NBT Tag + */ + public CompoundTag serialize() + { + return savedBlock.serialize(); + } + + /** + * Alias Method + * @see SavedBlock#deserialize(CompoundTag) + * @see SavedBlock#getBlockPrimitive() + * @param tag NBT Tag + * @return A Primitive Block + */ + public static PrimitiveBlock deserialize(CompoundTag tag) + { + return SavedBlock.deserialize(tag).getBlockPrimitive(); + } + + /** + * Compare a block with this primitive block + * @param state The block state to compare + * @param entity The block entity to compare + * @return True if identical + */ + public boolean is(BlockState state, BlockEntity entity) + { + if(state.getBlock() == this.blockType) + { + // Check the block state + if(this.blockState.equals(state)) + { + if(blockEntity == null) return true; // Not all blocks have a block entity. + if(blockEntity.equals(entity.serializeNBT())){ + return true; + }else return false; + } else return false; + }else return false; + } + + /** + * Clones the PrimitiveBlock into a new instance + * @return + */ + public PrimitiveBlock copy() + { + return savedBlock.clone().getBlockPrimitive(); + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/SaveDataCoordinates.java b/src/main/java/dev/zontreck/libzontreck/memory/world/SaveDataCoordinates.java new file mode 100644 index 0000000..0b95612 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/SaveDataCoordinates.java @@ -0,0 +1,47 @@ +package dev.zontreck.libzontreck.memory.world; + +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtUtils; +import net.minecraft.tags.BlockTags; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraftforge.common.Tags; + +public class SaveDataCoordinates +{ + public int X; + public int Z; + private BlockPos source; + + public SaveDataCoordinates(BlockPos pos) + { + int X = pos.getX() >> 4 >> 5; + int Z = pos.getZ() >> 4 >> 5; + + source = pos; + } + + public String getFileName() + { + return "r." + X + "." + Z + ".dat"; + } + + public BlockPos toBlockPos() + { + return source; + } + + public static final String TAG_COORDS = "sdc"; + public CompoundTag toNBT() + { + return NbtUtils.writeBlockPos(source); + } + + public static SaveDataCoordinates deserialize(CompoundTag tag) + { + BlockPos pos = NbtUtils.readBlockPos(tag); + + return new SaveDataCoordinates(pos); + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/SaveDataFactory.java b/src/main/java/dev/zontreck/libzontreck/memory/world/SaveDataFactory.java new file mode 100644 index 0000000..0effe5f --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/SaveDataFactory.java @@ -0,0 +1,267 @@ +package dev.zontreck.libzontreck.memory.world; + +import dev.zontreck.libzontreck.LibZontreck; +import dev.zontreck.libzontreck.vectors.WorldPosition; +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.*; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.Level; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class SaveDataFactory +{ + static Map datas = new HashMap<>(); + + protected static boolean hasSaveDataInstance(SaveDataFile file) + { + return datas.containsKey(file.getSaveDataPath().toString()); + } + + protected static SaveData getSaveDataInstance(SaveDataFile file) + { + return datas.get(file.getSaveDataPath().toString()); + } + + public static Builder builder() + { + return new Builder(); + } + static class Builder + { + private String modID = "minecraft"; + private String levelName; + private String queueID; + private boolean DBMode; + private BlockPos position; + + public Builder withDimension(Level level) + { + ResourceLocation lv = level.dimension().location(); + this.modID = lv.getNamespace(); + this.levelName = lv.getPath(); + + return this; + } + + public Builder withQueueID(BlockRestoreQueue queue) + { + queueID = queue.getRestoreQueueName(); + + return this; + } + + public Builder withDatabaseMode() + { + DBMode=true; + + return this; + } + + public Builder withPosition(BlockPos pos) + { + position = pos; + + return this; + } + + public SaveDataManifest getManifest() + { + return new SaveDataManifest(); + } + + public SaveDataFile build() + { + return new SaveDataFile(modID, levelName, queueID, DBMode, position); + } + } + + static class SaveDataFile + { + String mod; + String dimension; + String queue; + boolean database; + BlockPos position; + boolean useManifest = false; + + public SaveDataFile(String modID, String levelName, String queueID, boolean DBMode, BlockPos pos) + { + mod = modID; + dimension = levelName; + queue = queueID; + database = DBMode; + position = pos; + + if(pos == null) + { + useManifest=true; + } + } + + /** + * config/LibZontreck/block_snapshots/[mod]/[dimension]/[queueNickName]/r.x.z.dat + * @return + */ + public Path getSaveDataPath() + { + Path path = LibZontreck.BASE_CONFIG.resolve("block_snapshots"); + if(mod != null) path = path.resolve(mod); + if(dimension != null) path = path.resolve(dimension); + if(queue != null) path = path.resolve(queue); + + path.toFile().mkdirs(); + + if(useManifest) return path.resolve("manifest.nbt"); + + SaveDataCoordinates coordinates = new SaveDataCoordinates(position); + path = path.resolve(coordinates.getFileName()); + return path; + } + + /** + * Reads the save data, or initializes a new instance. + *

+ * Additionally, this will check for a pre-existing POJO instance and return if it exists. + * @return + * @throws IOException + */ + public SaveData getInstance() throws IOException { + if(SaveDataFactory.hasSaveDataInstance(this)) return SaveDataFactory.getSaveDataInstance(this); + Path data = getSaveDataPath(); + if(data.toFile().exists()) + { + CompoundTag tag = NbtIo.read(data.toFile()); + + return SaveData.deserialize(tag, this); + } else { + + return new SaveData(this); + } + } + } + + static class SaveDataManifest { + private List SAVE_DATA = new ArrayList<>(); + public static final String TAG_MANIFEST = "manifest"; + + private SaveDataManifest() + { + } + + public void add(SaveDataCoordinates pos){ + SAVE_DATA.add(pos); + } + + public List get() + { + return new ArrayList<>(SAVE_DATA); + } + + public CompoundTag save() + { + CompoundTag tag = new CompoundTag(); + ListTag lst = new ListTag(); + for(SaveDataCoordinates str : SAVE_DATA) + { + lst.add(str.toNBT()); + } + tag.put(TAG_MANIFEST, lst); + return tag; + + } + + public static SaveDataManifest deserialize(CompoundTag tag) + { + SaveDataManifest ret = new SaveDataManifest(); + ListTag lst = tag.getList(TAG_MANIFEST, Tag.TAG_COMPOUND); + for(Tag entry : lst) + { + if(entry instanceof CompoundTag ct) + { + ret.add(SaveDataCoordinates.deserialize(ct)); + } + } + + return ret; + } + } + + static class SaveData { + SaveDataFile myFile; + public static final String TAG_SAVED_BLOCKS = "sb"; + + public List blocks = new ArrayList<>(); + + public SaveData(SaveDataFile file) + { + myFile = file; + } + + /** + * Read a NBT Tag and reconstruct the SaveData POJO + * @param tag + * @param file + * @return + */ + public static SaveData deserialize(CompoundTag tag, SaveDataFile file) { + SaveData data = new SaveData(file); + ListTag lst = tag.getList(TAG_SAVED_BLOCKS, ListTag.TAG_COMPOUND); + for(Tag xTag : lst) + { + if(xTag instanceof CompoundTag ct) + { + SavedBlock sb = SavedBlock.deserialize(ct); + data.blocks.add(sb); + } + } + + return data; + } + + /** + * Write the current save data to NBT + * @return + */ + public CompoundTag serialize() + { + CompoundTag tag = new CompoundTag(); + ListTag lst = new ListTag(); + for(SavedBlock block : blocks) + { + lst.add(block.serialize()); + } + + tag.put(TAG_SAVED_BLOCKS, lst); + return tag; + } + + /** + * Imports a full queue to the save data file. + * ! WARNING ! This method will overwrite the SaveDataFile's Queue ID + * * * * + * This will only import for the correct dimension. This method is invoked automatically for each level for each queue when the server is shutting down prior to level unload. + * @param queue Queue to import + * @return The current SaveData instance + */ + public SaveData importQueue(BlockRestoreQueue queue) + { + for(PrimitiveBlock blk : queue.getQueue()) + { + if(WorldPosition.getDim(blk.level) == this.myFile.dimension) + blocks.add(blk.savedBlock); + else + continue; // We only want to add to a save data file, the blocks for the dimension in question. + } + + myFile.queue = queue.getRestoreQueueName(); + return this; + } + + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/SavedBlock.java b/src/main/java/dev/zontreck/libzontreck/memory/world/SavedBlock.java new file mode 100644 index 0000000..539d890 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/SavedBlock.java @@ -0,0 +1,112 @@ +package dev.zontreck.libzontreck.memory.world; + +import dev.zontreck.libzontreck.api.Vector3; +import dev.zontreck.libzontreck.exceptions.InvalidDeserialization; +import dev.zontreck.libzontreck.vectors.WorldPosition; +import net.minecraft.core.BlockPos; +import net.minecraft.core.registries.Registries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtUtils; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; + +public class SavedBlock implements Cloneable +{ + private CompoundTag blockState; + private CompoundTag blockEntity; + private boolean hasBlockEntity; + private WorldPosition position; + + /** + * Take a snapshot of the block, and save as primitive type SavedBlock + * @param position The block's position + * @param level The level the position relates to + * @return A instance of the saved block + */ + public static SavedBlock takeSnapshot(Vector3 position, Level level) + { + SavedBlock savedBlock = new SavedBlock(); + BlockPos pos = position.asBlockPos(); + + BlockState state = level.getBlockState(pos); + savedBlock.blockState = NbtUtils.writeBlockState(state); + BlockEntity entity = level.getBlockEntity(pos); + if(entity == null) + { + savedBlock.hasBlockEntity = false; + }else { + savedBlock.hasBlockEntity = true; + savedBlock.blockEntity = entity.serializeNBT(); + } + + savedBlock.position = new WorldPosition(position.asVector3d(), (ServerLevel) level); + + return savedBlock; + } + + /** + * Saves the stored block as a NBT Tag + * @return CompoundTag + */ + public CompoundTag serialize() + { + CompoundTag tag = new CompoundTag(); + tag.put("state", blockState); + if(hasBlockEntity) tag.put("entity", blockEntity); + + tag.put("position", position.serialize()); + + return tag; + } + + /** + * Reads a NBT Tag that represents a stored block, and returns the StoredBlock object + * @param tag Saved NBT + * @return SavedBlock instance + */ + public static SavedBlock deserialize(CompoundTag tag) + { + SavedBlock savedBlock = new SavedBlock(); + savedBlock.blockState = tag.getCompound("state"); + if(tag.contains("entity")) { + savedBlock.blockEntity = tag.getCompound("entity"); + savedBlock.hasBlockEntity=true; + } + + try { + savedBlock.position = new WorldPosition(tag.getCompound("position"), false); + } catch (InvalidDeserialization e) { + throw new RuntimeException(e); + } + + return savedBlock; + } + + public PrimitiveBlock getBlockPrimitive() + { + ServerLevel level = position.getActualDimension(); + + BlockState state = NbtUtils.readBlockState(level.holderLookup(Registries.BLOCK), blockState); + + return new PrimitiveBlock(this, state.getBlock(), state, blockEntity, position.Position.asBlockPos(), level); + } + + @Override + public SavedBlock clone() { + try { + SavedBlock clone = (SavedBlock) super.clone(); + if(blockEntity != null) + clone.blockEntity = blockEntity.copy(); + if(blockState != null) + clone.blockState = blockState.copy(); + if(position != null) + clone.position = position.clone(); + + return clone; + } catch (CloneNotSupportedException e) { + throw new AssertionError(); + } + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/SortedBlockQueue.java b/src/main/java/dev/zontreck/libzontreck/memory/world/SortedBlockQueue.java new file mode 100644 index 0000000..a098a21 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/SortedBlockQueue.java @@ -0,0 +1,41 @@ +package dev.zontreck.libzontreck.memory.world; + +import dev.zontreck.libzontreck.api.Vector3; +import dev.zontreck.libzontreck.util.PositionUtil; +import dev.zontreck.libzontreck.vectors.Vector3i; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + +public class SortedBlockQueue extends BlockRestoreQueue +{ + @Override + public String getRestoreQueueName() { + return "SortedBlockQueue"; + } + @Override + public void notifyDirtyQueue(boolean blockAdded) { + if(blockAdded) { + List queue = getQueue(); + List retQueue = new ArrayList<>(queue.size()); + + // Sort the queue based on block positions + queue.sort(Comparator.comparing(block -> new Vector3i(block.position))); + + // Add blocks in sorted order to the new queue + for (PrimitiveBlock blk : queue) { + retQueue.add(blk.copy()); // Copy block if necessary + } + + setQueueNoNotify(retQueue); + } + } + + @Override + public boolean sorted() { + return true; + } + +} diff --git a/src/main/java/dev/zontreck/libzontreck/networking/ModMessages.java b/src/main/java/dev/zontreck/libzontreck/networking/ModMessages.java index 40a6281..e7bbe1f 100644 --- a/src/main/java/dev/zontreck/libzontreck/networking/ModMessages.java +++ b/src/main/java/dev/zontreck/libzontreck/networking/ModMessages.java @@ -36,16 +36,9 @@ public class ModMessages { .clientAcceptedVersions(s->true) .serverAcceptedVersions(s->true) .simpleChannel(); - - RegisterPacketsEvent event = new RegisterPacketsEvent(); - MinecraftForge.EVENT_BUS.post(event); INSTANCE=net; - for(IPacket packet : event.packets) - { - packet.register(net); - } net.messageBuilder(S2CPlaySoundPacket.class, PACKET_ID.getAndIncrement(), NetworkDirection.PLAY_TO_CLIENT) .decoder(S2CPlaySoundPacket::new) diff --git a/src/main/java/dev/zontreck/libzontreck/networking/NetworkEvents.java b/src/main/java/dev/zontreck/libzontreck/networking/NetworkEvents.java index 13ef44a..c87b891 100644 --- a/src/main/java/dev/zontreck/libzontreck/networking/NetworkEvents.java +++ b/src/main/java/dev/zontreck/libzontreck/networking/NetworkEvents.java @@ -1,17 +1,5 @@ package dev.zontreck.libzontreck.networking; -import dev.zontreck.libzontreck.events.RegisterPacketsEvent; -import dev.zontreck.libzontreck.networking.packets.S2CPlaySoundPacket; -import dev.zontreck.libzontreck.networking.packets.S2CWalletInitialSyncPacket; -import dev.zontreck.libzontreck.networking.packets.S2CWalletUpdatedPacket; -import net.minecraftforge.eventbus.api.SubscribeEvent; - public class NetworkEvents { - @SubscribeEvent - public void onRegisterPackets(RegisterPacketsEvent ev) - { - ev.packets.add(new S2CWalletUpdatedPacket()); - ev.packets.add(new S2CWalletInitialSyncPacket()); - } } diff --git a/src/main/java/dev/zontreck/libzontreck/profiles/Profile.java b/src/main/java/dev/zontreck/libzontreck/profiles/Profile.java index d6e4692..5df6d63 100644 --- a/src/main/java/dev/zontreck/libzontreck/profiles/Profile.java +++ b/src/main/java/dev/zontreck/libzontreck/profiles/Profile.java @@ -18,7 +18,7 @@ import net.minecraftforge.common.MinecraftForge; /** * A libZontreck user profile - *

+ *

* This is used to contain common player data, as well as be capable of serializing the player's data and sending to/from the client. */ public class Profile { diff --git a/src/main/java/dev/zontreck/libzontreck/util/BlocksUtil.java b/src/main/java/dev/zontreck/libzontreck/util/BlocksUtil.java index 4439147..92edc5e 100644 --- a/src/main/java/dev/zontreck/libzontreck/util/BlocksUtil.java +++ b/src/main/java/dev/zontreck/libzontreck/util/BlocksUtil.java @@ -1,6 +1,6 @@ package dev.zontreck.libzontreck.util; -import dev.zontreck.libzontreck.vectors.Vector3; +import dev.zontreck.libzontreck.vectors.Vector3d; import net.minecraft.server.level.ServerLevel; import java.util.ArrayList; @@ -18,9 +18,9 @@ public class BlocksUtil * @param limit The applicable limit for vein detection * @return List of positions for the vein */ - public static List VeinOf(ServerLevel level, Vector3 start, int limit) + public static List VeinOf(ServerLevel level, Vector3d start, int limit) { - List ret = new ArrayList<>(); + List ret = new ArrayList<>(); diff --git a/src/main/java/dev/zontreck/libzontreck/util/PositionUtil.java b/src/main/java/dev/zontreck/libzontreck/util/PositionUtil.java new file mode 100644 index 0000000..39ee999 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/util/PositionUtil.java @@ -0,0 +1,156 @@ +package dev.zontreck.libzontreck.util; + +import dev.zontreck.libzontreck.api.Vector3; +import dev.zontreck.libzontreck.vectors.Vector3d; +import dev.zontreck.libzontreck.vectors.Vector3i; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * Provides helper functions for position related things + */ +public class PositionUtil +{ + + public static List makeCube(Vector3 p1, Vector3 p2) + { + List vecs = new ArrayList<>(); + Vector3 work = new Vector3d(); + Vector3d v1 = p1.asVector3d(); + Vector3d v2 = p2.asVector3d(); + + double xx = v1.x; + double yy = v1.y; + double zz = v1.z; + + int yState = 0; + int zState = 0; + int xState = 0; + + for(xx = Math.round(v1.x); (xx != Math.round(v2.x) && xState != 2);) + { + for(zz = Math.round(v1.z); (zz != Math.round(v2.z) && zState != 2);) + { + for(yy = Math.round(v1.y); (yy != Math.round(v2.y) && yState != 2);) + { + work = new Vector3d(xx, yy, zz); + + if(!vecs.contains(work)) vecs.add(work); + + if(yy > v2.y) + { + yy -= 1.0; + if(yy == Math.round(v2.y) && yState == 0) + { + yState++; + }else{ + if(yState == 1) + { + yState ++; + } + } + } else if(yy < v2.y) + { + yy += 1.0; + if(yy == Math.round(v2.y) && yState == 0){ + yState ++; + }else { + if(yState == 1)yState++; + } + } + } + + yState=0; + work = new Vector3d(xx,yy,zz); + + if(!vecs.contains(work)) vecs.add(work); + + if(zz > v2.z) + { + zz -= 1.0; + + if(zz == Math.round(v2.z) && zState == 0)zState++; + else{ + if(zState == 1)zState++; + } + }else if(zz < v2.z) + { + zz += 1.0; + + if(zz == Math.round(v2.z) && zState == 0)zState++; + else { + if(zState==1)zState++; + } + } + } + + zState=0; + work = new Vector3d(xx,yy,zz); + + if(!vecs.contains(work)) vecs.add(work); + + if(xx > v2.x) + { + xx -= 1.0; + + if(xx == Math.round(v2.x) && xState == 0) xState++; + else{ + if(xState == 1)xState++; + } + }else if(xx < v2.x) + { + xx += 1.0; + + if(xx == Math.round(v2.x) && xState==0)xState++; + else{ + if(xState==1)xState++; + } + } + } + + return vecs; + } + + + public static List sortAscending(List vecs) + { + + List copy = new ArrayList<>(vecs); + List sorted = new ArrayList<>(); + + + while(copy.size()>0) + { + Vector3 lowest = findFirstLowestPosition(copy); + copy.remove(lowest); + sorted.add(lowest); + } + + return sorted; + } + + public static Vector3 findFirstLowestPosition(List vecs) + { + + Vector3i lowest = new Vector3i(0, 500, 0); + List copy = new ArrayList<>(vecs); + + Iterator it = copy.iterator(); + while(it.hasNext()) + { + Vector3i entry = it.next().asVector3i(); + if(entry.y < lowest.y) + { + lowest = entry; + it.remove(); + }else it.remove(); + } + + return lowest; + + } + + +} diff --git a/src/main/java/dev/zontreck/libzontreck/util/SNbtIo.java b/src/main/java/dev/zontreck/libzontreck/util/SNbtIo.java index 40fcbb6..120a93e 100644 --- a/src/main/java/dev/zontreck/libzontreck/util/SNbtIo.java +++ b/src/main/java/dev/zontreck/libzontreck/util/SNbtIo.java @@ -3,7 +3,6 @@ package dev.zontreck.libzontreck.util; import com.mojang.brigadier.exceptions.CommandSyntaxException; import dev.zontreck.ariaslib.util.FileIO; import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.NbtIo; import net.minecraft.nbt.NbtUtils; import java.io.File; @@ -43,4 +42,4 @@ public class SNbtIo String snbt = NbtUtils.structureToSnbt(tag); FileIO.writeFile(path.toFile().getAbsolutePath(), snbt); } -} +} \ No newline at end of file diff --git a/src/main/java/dev/zontreck/libzontreck/util/ServerUtilities.java b/src/main/java/dev/zontreck/libzontreck/util/ServerUtilities.java index abd8d57..8f35d3b 100644 --- a/src/main/java/dev/zontreck/libzontreck/util/ServerUtilities.java +++ b/src/main/java/dev/zontreck/libzontreck/util/ServerUtilities.java @@ -1,5 +1,6 @@ package dev.zontreck.libzontreck.util; +import java.util.List; import java.util.UUID; import java.util.function.Function; import java.util.function.Supplier; @@ -13,6 +14,7 @@ import net.minecraft.server.level.ServerPlayer; import net.minecraftforge.fml.LogicalSide; import net.minecraftforge.network.NetworkEvent; import net.minecraftforge.network.simple.SimpleChannel; +import net.minecraftforge.server.ServerLifecycleHooks; public class ServerUtilities { @@ -23,7 +25,7 @@ public class ServerUtilities */ public static ServerPlayer getPlayerByID(String id) { - return LibZontreck.THE_SERVER.getPlayerList().getPlayer(UUID.fromString(id)); + return ServerLifecycleHooks.getCurrentServer().getPlayerList().getPlayer(UUID.fromString(id)); } /** @@ -79,7 +81,7 @@ public class ServerUtilities public static boolean playerIsOffline(UUID ID) throws InvalidSideException { if(isClient())throw new InvalidSideException("This can only be called on the server"); - if(LibZontreck.THE_SERVER.getPlayerList().getPlayer(ID) == null) return true; + if(ServerLifecycleHooks.getCurrentServer().getPlayerList().getPlayer(ID) == null) return true; else return false; } diff --git a/src/main/java/dev/zontreck/libzontreck/util/TagUtils.java b/src/main/java/dev/zontreck/libzontreck/util/TagUtils.java new file mode 100644 index 0000000..d2eca8a --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/util/TagUtils.java @@ -0,0 +1,45 @@ +package dev.zontreck.libzontreck.util; + +import net.minecraft.nbt.CompoundTag; + +public class TagUtils +{ + /** + * Get either the entry, or supply a default value + * @param tag + * @param entry + * @param other + * @return + */ + public static int intOr(CompoundTag tag, String entry, int other) + { + if(tag.contains(entry)) return tag.getInt(entry); + else return other; + } + + /** + * Get either the entry, or supply a default value + * @param tag + * @param entry + * @param other + * @return + */ + public static String strOr(CompoundTag tag, String entry, String other) + { + if(tag.contains(entry)) return tag.getString(entry); + else return other; + } + + /** + * Get either the entry, or supply a default value + * @param tag + * @param entry + * @param other + * @return + */ + public static boolean boolOr(CompoundTag tag, String entry, boolean other) + { + if(tag.contains(entry)) return tag.getBoolean(entry); + else return other; + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/util/heads/HeadCache.java b/src/main/java/dev/zontreck/libzontreck/util/heads/HeadCache.java index 904688c..84d9070 100644 --- a/src/main/java/dev/zontreck/libzontreck/util/heads/HeadCache.java +++ b/src/main/java/dev/zontreck/libzontreck/util/heads/HeadCache.java @@ -109,10 +109,8 @@ public class HeadCache creds.add( new CreditsEntry(HeadUtilities.cachedLookup("zontreck"), "Aria (zontreck)", "Developer, Designer, Artist", "Aria is the primary developer and project maintainer")); - creds.add( - new CreditsEntry(HeadUtilities.cachedLookup("PossumTheWarrior"), "PossumTheWarrior", "Tester, Adviser, Designer, Artist", "Poss has helped to test the mods from very early on. Poss has also contributed the artwork and mob model for the Possum")); - creds.add( - new CreditsEntry(HeadUtilities.cachedLookup("GemMD"), "GemMD", "Tester, Adviser, Designer", "GemMD has provided advice on marketing and development decisions for various mods")); + creds.add(new CreditsEntry(HeadUtilities.cachedLookup("firesyde424"), "firesyde424", "Tester", "Firesyde has helped to test my mods and given feedback.")); + creds.add(new CreditsEntry(HeadUtilities.cachedLookup("EmberCat42"), "EmberCat42", "Tester", "EmberCat42 has helped to test and reported on a major bug in Night Vision")); CREDITS = creds; diff --git a/src/main/java/dev/zontreck/libzontreck/vectors/ChunkPos.java b/src/main/java/dev/zontreck/libzontreck/vectors/ChunkPos.java index 9cb8fc6..0e60b5a 100644 --- a/src/main/java/dev/zontreck/libzontreck/vectors/ChunkPos.java +++ b/src/main/java/dev/zontreck/libzontreck/vectors/ChunkPos.java @@ -5,10 +5,10 @@ import net.minecraft.server.level.ServerLevel; public class ChunkPos { public Points points; - public Vector2 centerPoints; + public Vector2f centerPoints; public String dim; - public ChunkPos(Vector3 point1, Vector3 point2, ServerLevel lvl) + public ChunkPos(Vector3d point1, Vector3d point2, ServerLevel lvl) { points = new Points(point1, point2, lvl); dim = WorldPosition.getDim(lvl); @@ -17,12 +17,12 @@ public class ChunkPos { public ChunkPos(CompoundTag tag) { points = new Points(tag.getCompound("points")); - centerPoints = new Vector2(tag.getCompound("center")); + centerPoints = Vector2f.deserialize(tag.getCompound("center")); } - public boolean isWithin(Vector3 point) + public boolean isWithin(Vector3d point) { - return point.inside(points.Min, points.Max); + return point.Inside(points.Min, points.Max); } public static ChunkPos getChunkPos(WorldPosition pos) diff --git a/src/main/java/dev/zontreck/libzontreck/vectors/NonAbsVector3.java b/src/main/java/dev/zontreck/libzontreck/vectors/NonAbsVector3.java index d484e1b..bce839f 100644 --- a/src/main/java/dev/zontreck/libzontreck/vectors/NonAbsVector3.java +++ b/src/main/java/dev/zontreck/libzontreck/vectors/NonAbsVector3.java @@ -11,7 +11,7 @@ public class NonAbsVector3 public long y; public long z; - public NonAbsVector3(Vector3 origin) + public NonAbsVector3(Vector3d origin) { x = Math.round(origin.x); y = Math.round(origin.y); diff --git a/src/main/java/dev/zontreck/libzontreck/vectors/Points.java b/src/main/java/dev/zontreck/libzontreck/vectors/Points.java index fdb0cf9..93fba5f 100644 --- a/src/main/java/dev/zontreck/libzontreck/vectors/Points.java +++ b/src/main/java/dev/zontreck/libzontreck/vectors/Points.java @@ -7,8 +7,8 @@ import net.minecraft.server.level.ServerLevel; * Two points within the same dimension */ public class Points { - public Vector3 Min = Vector3.ZERO; - public Vector3 Max = Vector3.ZERO; + public Vector3d Min = Vector3d.ZERO; + public Vector3d Max = Vector3d.ZERO; public String dimension = ""; /** @@ -17,7 +17,7 @@ public class Points { * @param max * @param lvl */ - public Points(Vector3 min, Vector3 max, ServerLevel lvl) + public Points(Vector3d min, Vector3d max, ServerLevel lvl) { dimension = WorldPosition.getDimSafe(lvl); if(min.less(max)) @@ -48,8 +48,8 @@ public class Points { public void deserialize(CompoundTag tag) { - Min = new Vector3(tag.getCompound("min")); - Max = new Vector3(tag.getCompound("max")); + Min = Vector3d.deserialize(tag.getCompound("min")); + Max = Vector3d.deserialize(tag.getCompound("max")); dimension = tag.getString("dim"); } } diff --git a/src/main/java/dev/zontreck/libzontreck/vectors/Vector2.java b/src/main/java/dev/zontreck/libzontreck/vectors/Vector2.java deleted file mode 100644 index 100ae27..0000000 --- a/src/main/java/dev/zontreck/libzontreck/vectors/Vector2.java +++ /dev/null @@ -1,120 +0,0 @@ -package dev.zontreck.libzontreck.vectors; - -import dev.zontreck.libzontreck.exceptions.InvalidDeserialization; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.world.phys.Vec2; - -public class Vector2 -{ - public static final Vector2 ZERO = new Vector2(0, 0); - - public float x; - public float y; - - public Vec2 asMinecraftVector(){ - return new Vec2(x, y); - } - - public Vector2() - { - - } - - public Vector2(float x, float y) - { - this.x=x; - this.y=y; - } - - public Vector2(Vec2 pos) - { - x=pos.x; - y=pos.y; - } - - public Vector2(String pos) throws InvalidDeserialization - { - // This will be serialized most likely from the ToString method - // Parse - if(pos.startsWith("<")) - { - pos=pos.substring(1, pos.length()-1); // Rip off the ending bracket too - String[] positions = pos.split(", "); - if(positions.length!=2) - { - positions = pos.split(","); - } - - if(positions.length!=2) - { - throw new InvalidDeserialization("Positions must be in the same format provided by ToString() (ex. <1,1> or <1, 1>"); - } - - this.x = Float.parseFloat(positions[0]); - this.y = Float.parseFloat(positions[1]); - // We are done now - } - } - - public Vector2 Clone() - { - Vector2 n = new Vector2(x, y); - return n; - } - - @Override - public String toString() - { - return "<"+String.valueOf(x)+", "+String.valueOf(y) + ">"; - } - - - public CompoundTag serialize() - { - CompoundTag tag = new CompoundTag(); - tag.putFloat("x", x); - tag.putFloat("y", y); - - return tag; - } - - public Vector2(CompoundTag tag) { - this.deserialize(tag); - } - public void deserialize(CompoundTag tag) - { - x=tag.getFloat("x"); - y=tag.getFloat("y"); - } - - public boolean same(Vector2 other) - { - if(x == other.x && y==other.y)return true; - else return false; - } - - public boolean inside(Vector2 point1, Vector2 point2) - { - if(point1.x <= x && point2.x >= x){ - if(point1.y <= y && point2.y >= y) - { - return true; - } - } - - return false; - } - - public boolean greater(Vector2 other) - { - return ((x>other.x) && (y>other.y)); - } - public boolean less(Vector2 other) - { - return ((x>other.x) && (y>other.y)); - } - public boolean equal(Vector2 other) - { - return same(other); - } -} diff --git a/src/main/java/dev/zontreck/libzontreck/vectors/Vector2f.java b/src/main/java/dev/zontreck/libzontreck/vectors/Vector2f.java new file mode 100644 index 0000000..a6c33ed --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/vectors/Vector2f.java @@ -0,0 +1,200 @@ +package dev.zontreck.libzontreck.vectors; + +import dev.zontreck.libzontreck.api.Vector2; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.phys.Vec2; +import org.jetbrains.annotations.NotNull; + +public class Vector2f implements Vector2 +{ + public static final Vector2f ZERO = new Vector2f(0, 0); + + public float x; + public float y; + + @Override + public Vec2 asMinecraftVector(){ + return new Vec2(x, y); + } + + public Vector2f() + { + + } + + public Vector2f(float x, float y) + { + this.x=x; + this.y=y; + } + + public Vector2f(Vec2 pos) + { + x=pos.x; + y=pos.y; + } + + public static Vector2f parseString(String vector2) + { + Vector2f vec = new Vector2f(); + // This will be serialized most likely from the ToString method + // Parse + if(vector2.startsWith("<")) + { + vector2=vector2.substring(1, vector2.length()-1); // Rip off the ending bracket too + String[] positions = vector2.split(", "); + if(positions.length!=2) + { + positions = vector2.split(","); + } + + if(positions.length!=2) + { + return ZERO; + } + + vec.x = Float.parseFloat(positions[0]); + vec.y = Float.parseFloat(positions[1]); + // We are done now + } + + return vec; + } + + @Override + public Vector2f Clone() + { + Vector2f n = new Vector2f(x, y); + return n; + } + + @Override + public String toString() + { + return "<"+String.valueOf(x)+", "+String.valueOf(y) + ">"; + } + + @Override + public CompoundTag serialize() + { + CompoundTag tag = new CompoundTag(); + tag.putFloat("x", x); + tag.putFloat("y", y); + + return tag; + } + + public static Vector2f deserialize(CompoundTag tag) + { + Vector2f vec = new Vector2f(); + vec.x=tag.getFloat("x"); + vec.y=tag.getFloat("y"); + + return vec; + } + + @Override + public boolean Same(Vector2 other) + { + Vector2f ov = other.asVector2f(); + if(x == ov.x && y == ov.y) return true; + return false; + } + + @Override + public boolean Inside(Vector2 point1, Vector2 point2) + { + Vector2f p1 = point1.asVector2f(); + Vector2f p2 = point2.asVector2f(); + + if(p1.x <= x && p2.x >= x){ + if(p1.y <= y && p2.y >= y) + { + return true; + } + } + + return false; + } + + @Override + public Vector2f asVector2f() + { + return this; + } + + @Override + public Vector2i asVector2i() { + return new Vector2i(Math.round(x), Math.round(y)); + } + + @Override + public boolean greater(Vector2 other) + { + Vector2f vec = other.asVector2f(); + return ((x > vec.x) && (y > vec.y)); + } + + @Override + public boolean less(Vector2 other) + { + Vector2f vec = other.asVector2f(); + return ((x > vec.x) && (y > vec.y)); + } + public boolean equal(Vector2 other) + { + return Same(other); + } + + @Override + public Vector2f add(Vector2 other) { + Vector2f vec = other.asVector2f(); + return new Vector2f(x + vec.x, y + vec.y); + } + + @Override + public Vector2f subtract(Vector2 other) { + Vector2f vec = other.asVector2f(); + return new Vector2f(x - vec.x, y - vec.y); + } + + @Override + public double distance(Vector2 other) { + Vector2f vec = subtract(other); + return Math.sqrt((vec.x * vec.x + vec.y * vec.y)); + } + + @Override + public Vector2f moveUp() { + return add(new Vector2f(0,1)); + } + + @Override + public Vector2f moveDown() { + return subtract(new Vector2f(0,1)); + } + + @Override + public Float getX() { + return x; + } + + @Override + public Float getY() { + return y; + } + + @Override + public int compareTo(@NotNull Vector2 other) { + if(other instanceof Vector2f v2f){ + + // Compare x coordinates first + int cmp = Float.compare(this.x, v2f.x); + if (cmp != 0) { + return cmp; + } + // If x coordinates are equal, compare y coordinates + return Float.compare(this.y, v2f.y); + } else return -1; + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/vectors/Vector2i.java b/src/main/java/dev/zontreck/libzontreck/vectors/Vector2i.java index bce3c94..b5e90a7 100644 --- a/src/main/java/dev/zontreck/libzontreck/vectors/Vector2i.java +++ b/src/main/java/dev/zontreck/libzontreck/vectors/Vector2i.java @@ -1,16 +1,18 @@ package dev.zontreck.libzontreck.vectors; -import dev.zontreck.libzontreck.exceptions.InvalidDeserialization; +import dev.zontreck.libzontreck.api.Vector2; import net.minecraft.nbt.CompoundTag; import net.minecraft.world.phys.Vec2; +import org.jetbrains.annotations.NotNull; -public class Vector2i +public class Vector2i implements Vector2 { public static final Vector2i ZERO = new Vector2i(0, 0); public int x; public int y; + @Override public Vec2 asMinecraftVector(){ return new Vec2(x, y); } @@ -32,30 +34,34 @@ public class Vector2i y=(int)Math.floor(pos.y); } - public Vector2i(String pos) throws InvalidDeserialization + public static Vector2i parseString(String vector2) { + Vector2i vec = new Vector2i(); // This will be serialized most likely from the ToString method // Parse - if(pos.startsWith("<")) + if(vector2.startsWith("<")) { - pos=pos.substring(1, pos.length()-1); // Rip off the ending bracket too - String[] positions = pos.split(", "); + vector2=vector2.substring(1, vector2.length()-1); // Rip off the ending bracket too + String[] positions = vector2.split(", "); if(positions.length!=2) { - positions = pos.split(","); + positions = vector2.split(","); } if(positions.length!=2) { - throw new InvalidDeserialization("Positions must be in the same format provided by ToString() (ex. <1,1> or <1, 1>"); + return ZERO; } - this.x = Integer.parseInt(positions[0]); - this.y = Integer.parseInt(positions[1]); + vec.x = Integer.parseInt(positions[0]); + vec.y = Integer.parseInt(positions[1]); // We are done now } + + return vec; } + @Override public Vector2i Clone() { Vector2i n = new Vector2i(x, y); @@ -69,6 +75,7 @@ public class Vector2i } + @Override public CompoundTag serialize() { CompoundTag tag = new CompoundTag(); @@ -78,25 +85,33 @@ public class Vector2i return tag; } - public Vector2i(CompoundTag tag) { - this.deserialize(tag); - } - public void deserialize(CompoundTag tag) + public static Vector2i deserialize(CompoundTag tag) { - x=tag.getInt("x"); - y=tag.getInt("y"); + Vector2i vec = new Vector2i(); + + vec.x=tag.getInt("x"); + vec.y=tag.getInt("y"); + + return vec; } - public boolean same(Vector2i other) + @Override + public boolean Same(Vector2 other) { - if(x == other.x && y==other.y)return true; + Vector2i v2i = other.asVector2i(); + + if(x == v2i.x && y==v2i.y)return true; else return false; } - public boolean inside(Vector2i point1, Vector2i point2) + @Override + public boolean Inside(Vector2 point1, Vector2 point2) { - if(point1.x <= x && point2.x >= x){ - if(point1.y <= y && point2.y >= y) + Vector2i v1i = point1.asVector2i(); + Vector2i v2i = point2.asVector2i(); + + if(v1i.x <= x && v2i.x >= x){ + if(v1i.y <= y && v2i.y >= y) { return true; } @@ -105,16 +120,94 @@ public class Vector2i return false; } - public boolean greater(Vector2i other) - { - return ((x>other.x) && (y>other.y)); + @Override + public Vector2f asVector2f() { + return new Vector2f(x,y); } - public boolean less(Vector2i other) - { - return ((x>other.x) && (y>other.y)); + + @Override + public Vector2i asVector2i() { + return this; } - public boolean equal(Vector2i other) + + @Override + public boolean greater(Vector2 other) { - return same(other); + Vector2i vec = other.asVector2i(); + return ((x > vec.x) && (y > vec.y)); + } + + @Override + public boolean less(Vector2 other) + { + Vector2i vec = other.asVector2i(); + return ((x > vec.x) && (y > vec.y)); + } + + @Override + public boolean equal(Vector2 other) + { + return Same(other); + } + + @Override + public Vector2i add(Vector2 other) { + Vector2i vec = other.asVector2i(); + x += vec.x; + y += vec.y; + + return this; + } + + @Override + public Vector2i subtract(Vector2 other) { + Vector2i vec = other.asVector2i(); + + x -= vec.x; + y -= vec.y; + + return this; + } + + @Override + public double distance(Vector2 other) { + Vector2i vec = subtract(other); + return Math.sqrt((vec.x * vec.x + vec.y * vec.y)); + } + + + @Override + public Vector2i moveUp() { + return add(new Vector2i(0,1)); + } + + @Override + public Vector2i moveDown() { + return subtract(new Vector2i(0,1)); + } + + + @Override + public Integer getX() { + return x; + } + + @Override + public Integer getY() { + return y; + } + + @Override + public int compareTo(@NotNull Vector2 other) { + if(other instanceof Vector2i v2i){ + + // Compare x coordinates first + int cmp = Integer.compare(this.x, v2i.x); + if (cmp != 0) { + return cmp; + } + // If x coordinates are equal, compare y coordinates + return Integer.compare(this.y, v2i.y); + } else return -1; } } diff --git a/src/main/java/dev/zontreck/libzontreck/vectors/Vector3.java b/src/main/java/dev/zontreck/libzontreck/vectors/Vector3.java deleted file mode 100644 index c1cf9ee..0000000 --- a/src/main/java/dev/zontreck/libzontreck/vectors/Vector3.java +++ /dev/null @@ -1,285 +0,0 @@ -package dev.zontreck.libzontreck.vectors; - -import java.util.ArrayList; -import java.util.List; - -import dev.zontreck.libzontreck.exceptions.InvalidDeserialization; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Vec3i; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.world.phys.Vec3; - -public class Vector3 -{ - public static final Vector3 ZERO = new Vector3(0, 0, 0); - - - public double x; - public double y; - public double z; - - public Vec3 asMinecraftVector(){ - return new Vec3(x, y, z); - } - - public Vec3i asMinecraftVec3i() - { - return new Vec3i((int) Math.round(x), (int) Math.round(y), (int) Math.round(z)); - } - - public BlockPos asBlockPos() - { - return new BlockPos(asMinecraftVec3i()); - } - - public Vector3() - { - - } - - public Vector3(double x, double y, double z) - { - this.x=x; - this.y=y; - this.z=z; - } - - public Vector3(Vec3 pos) - { - x=pos.x; - y=pos.y; - z=pos.z; - } - - public Vector3(BlockPos pos) - { - x=pos.getX(); - y=pos.getY(); - z=pos.getZ(); - } - - public Vector3(String pos) throws InvalidDeserialization - { - // This will be serialized most likely from the ToString method - // Parse - if(pos.startsWith("<")) - { - pos=pos.substring(1, pos.length()-1); // Rip off the ending bracket too - String[] positions = pos.split(", "); - if(positions.length!=3) - { - positions = pos.split(","); - } - - if(positions.length!=3) - { - throw new InvalidDeserialization("Positions must be in the same format provided by ToString() (ex. <1,1,1> or <1, 1, 1>"); - } - - this.x = Double.parseDouble(positions[0]); - this.y = Double.parseDouble(positions[1]); - this.z = Double.parseDouble(positions[2]); - // We are done now - } - } - - public List makeCube(Vector3 other) - { - List vecs = new ArrayList<>(); - Vector3 work = new Vector3(); - - double xx = x; - double yy = y; - double zz = z; - - int yState = 0; - int zState = 0; - int xState = 0; - - for(xx = Math.round(x); (xx != Math.round(other.x) && xState != 2);) - { - for(zz = Math.round(z); (zz != Math.round(other.z) && zState != 2);) - { - for(yy = Math.round(y); (yy != Math.round(other.y) && yState != 2);) - { - work = new Vector3(xx, yy, zz); - - if(!vecs.contains(work)) vecs.add(work); - - if(yy > other.y) - { - yy -= 1.0; - if(yy == Math.round(other.y) && yState == 0) - { - yState++; - }else{ - if(yState == 1) - { - yState ++; - } - } - } else if(yy < other.y) - { - yy += 1.0; - if(yy == Math.round(other.y) && yState == 0){ - yState ++; - }else { - if(yState == 1)yState++; - } - } - } - - yState=0; - work = new Vector3(xx,yy,zz); - - if(!vecs.contains(work)) vecs.add(work); - - if(zz > other.z) - { - zz -= 1.0; - - if(zz == Math.round(other.z) && zState == 0)zState++; - else{ - if(zState == 1)zState++; - } - }else if(zz < other.z) - { - zz += 1.0; - - if(zz == Math.round(other.z) && zState == 0)zState++; - else { - if(zState==1)zState++; - } - } - } - - zState=0; - work = new Vector3(xx,yy,zz); - - if(!vecs.contains(work)) vecs.add(work); - - if(xx > other.x) - { - xx -= 1.0; - - if(xx == Math.round(other.x) && xState == 0) xState++; - else{ - if(xState == 1)xState++; - } - }else if(xx < other.x) - { - xx += 1.0; - - if(xx == Math.round(other.x) && xState==0)xState++; - else{ - if(xState==1)xState++; - } - } - } - - return vecs; - } - - public Vector3 subtract(Vector3 other) - { - return new Vector3(x-other.x, y-other.y, z-other.z); - } - public Vector3 add(Vector3 other) - { - return new Vector3(x+other.x, y+other.y, z +other.z); - } - - public double distance(Vector3 other) - { - Vector3 sub = subtract(other); - return Math.sqrt((sub.x * sub.x + sub.y * sub.y + sub.z * sub.z)); - } - - public Vector3 moveUp() - { - Vector3 up = Clone(); - up.y+=1; - return up; - } - public Vector3 moveDown() - { - Vector3 up = Clone(); - up.y-=1; - return up; - } - - - public Vector3 Clone() - { - Vector3 n = new Vector3(x, y, z); - return n; - } - - @Override - public String toString() - { - return "<"+String.valueOf(x)+", "+String.valueOf(y)+", "+String.valueOf(z)+">"; - } - - public NonAbsVector3 rounded() - { - NonAbsVector3 cl = new NonAbsVector3(this); - return cl; - } - - public CompoundTag serialize() - { - CompoundTag tag = new CompoundTag(); - tag.putDouble("x", x); - tag.putDouble("y", y); - tag.putDouble("z", z); - - return tag; - } - - public Vector3(CompoundTag tag) { - this.deserialize(tag); - } - public void deserialize(CompoundTag tag) - { - x=tag.getDouble("x"); - y=tag.getDouble("y"); - z=tag.getDouble("z"); - } - - - public boolean same(Vector3 other) - { - if(x == other.x && y==other.y && z==other.z)return true; - else return false; - } - - - public boolean inside(Vector3 point1, Vector3 point2) - { - if(point1.x <= x && point2.x >= x){ - if(point1.y <= y && point2.y >= y) - { - if(point1.z <= z && point2.z >= z) - { - return true; - } - } - } - - return false; - } - - public boolean greater(Vector3 other) - { - return ((x>other.x) && (y>other.y) && (z>other.z)); - } - public boolean less(Vector3 other) - { - return ((x +{ + public static final Vector3d ZERO = new Vector3d(0, 0, 0); + + + public double x; + public double y; + public double z; + + @Override + public Vec3 asMinecraftVector(){ + return new Vec3(x, y, z); + } + + @Override + public Vec3i asVec3i() + { + return new Vec3i((int) Math.round(x), (int) Math.round(y), (int) Math.round(z)); + } + + @Override + public BlockPos asBlockPos() + { + return new BlockPos(asVec3i()); + } + + public Vector3d() + { + + } + + public Vector3d(double x, double y, double z) + { + this.x=x; + this.y=y; + this.z=z; + } + + public Vector3d(Vec3 pos) + { + x=pos.x; + y=pos.y; + z=pos.z; + } + + public Vector3d(BlockPos pos) + { + x=pos.getX(); + y=pos.getY(); + z=pos.getZ(); + } + + public static Vector3d parseString(String vector3) + { + Vector3d vec = new Vector3d(); + // This will be serialized most likely from the ToString method + // Parse + if(vector3.startsWith("<")) + { + vector3=vector3.substring(1, vector3.length()-1); // Rip off the ending bracket too + String[] positions = vector3.split(", "); + if(positions.length!=3) + { + positions = vector3.split(","); + } + + if(positions.length!=3) + { + return ZERO; + } + + vec.x = Double.parseDouble(positions[0]); + vec.y = Double.parseDouble(positions[1]); + vec.z = Double.parseDouble(positions[2]); + // We are done now + } + + return vec; + } + + @Override + public Vector3d subtract(Vector3 other) + { + Vector3d vec = other.asVector3d(); + return new Vector3d(x-vec.x, y-vec.y, z-vec.z); + } + + @Override + public Vector3d add(Vector3 other) + { + Vector3d vec = other.asVector3d(); + return new Vector3d(x+vec.x, y+vec.y, z +vec.z); + } + + @Override + public double distance(Vector3 other) + { + Vector3d sub = subtract(other); + return Math.sqrt((sub.x * sub.x + sub.y * sub.y + sub.z * sub.z)); + } + + @Override + public Vector3d moveUp() + { + return add(new Vector3d(0,1,0)); + } + public Vector3d moveDown() + { + return subtract(new Vector3d(0,1,0)); + } + + + @Override + public Vector3d Clone() + { + return new Vector3d(x, y, z); + } + + @Override + public String toString() + { + return "<"+String.valueOf(x)+", "+String.valueOf(y)+", "+String.valueOf(z)+">"; + } + + public NonAbsVector3 rounded() + { + NonAbsVector3 cl = new NonAbsVector3(this); + return cl; + } + + @Override + public CompoundTag serialize() + { + CompoundTag tag = new CompoundTag(); + tag.putDouble("x", x); + tag.putDouble("y", y); + tag.putDouble("z", z); + + return tag; + } + + public static Vector3d deserialize(CompoundTag tag) + { + Vector3d vec = new Vector3d(); + + vec.x=tag.getDouble("x"); + vec.y=tag.getDouble("y"); + vec.z=tag.getDouble("z"); + + return vec; + } + + @Override + public boolean Same(Vector3 other) + { + Vector3d vec = other.asVector3d(); + if(x == vec.x && y==vec.y && z==vec.z)return true; + else return false; + } + + + @Override + public boolean Inside(Vector3 point1, Vector3 point2) + { + Vector3d v1 = point1.asVector3d(); + Vector3d v2 = point2.asVector3d(); + + if(v1.x <= x && v2.x >= x){ + if(v1.y <= y && v2.y >= y) + { + if(v1.z <= z && v2.z >= z) + { + return true; + } + } + } + + return false; + } + + @Override + public Vector3d asVector3d() { + return this; + } + + @Override + public Vector3i asVector3i() { + return new Vector3i((int) Math.round(x), (int) Math.round(y), (int) Math.round(z)); + } + + @Override + public boolean greater(Vector3 other) + { + Vector3d vec = other.asVector3d(); + return ((x>vec.x) && (y>vec.y) && (z>vec.z)); + } + + @Override + public boolean less(Vector3 other) + { + Vector3d vec = other.asVector3d(); + return ((x doubleVector3) { + if(doubleVector3 instanceof Vector3d v3d) + { + int Ycomp = Double.compare(y, v3d.y); + if(Ycomp!=0)return Ycomp; + int Zcomp = Double.compare(z, v3d.z); + if(Zcomp!=0)return Zcomp; + int Xcomp = Double.compare(x, v3d.x); + if(Xcomp!=0)return Xcomp; + + return 0; + } else return -1; + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/vectors/Vector3i.java b/src/main/java/dev/zontreck/libzontreck/vectors/Vector3i.java new file mode 100644 index 0000000..4cc52b8 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/vectors/Vector3i.java @@ -0,0 +1,246 @@ +package dev.zontreck.libzontreck.vectors; + +import dev.zontreck.libzontreck.api.Vector3; +import dev.zontreck.libzontreck.exceptions.InvalidDeserialization; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Vec3i; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.phys.Vec3; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +public class Vector3i implements Vector3 +{ + public static final Vector3i ZERO = new Vector3i(0, 0, 0); + + + public int x; + public int y; + public int z; + + @Override + public Vec3 asMinecraftVector(){ + return new Vec3(x, y, z); + } + + @Override + public Vec3i asVec3i() + { + return new Vec3i((int) Math.round(x), (int) Math.round(y), (int) Math.round(z)); + } + + @Override + public BlockPos asBlockPos() + { + return new BlockPos(asVec3i()); + } + + public Vector3i() + { + + } + + public Vector3i(int x, int y, int z) + { + this.x=x; + this.y=y; + this.z=z; + } + + public Vector3i(Vec3i pos) + { + x=pos.getX(); + y=pos.getY(); + z=pos.getZ(); + } + + public Vector3i(BlockPos pos) + { + x=pos.getX(); + y=pos.getY(); + z=pos.getZ(); + } + + public static Vector3i parseString(String vector3) throws InvalidDeserialization + { + Vector3i vec = new Vector3i(); + // This will be serialized most likely from the ToString method + // Parse + if(vector3.startsWith("<")) + { + vector3=vector3.substring(1, vector3.length()-1); // Rip off the ending bracket too + String[] positions = vector3.split(", "); + if(positions.length!=3) + { + positions = vector3.split(","); + } + + if(positions.length!=3) + { + throw new InvalidDeserialization("Positions must be in the same format provided by ToString() (ex. <1,1,1> or <1, 1, 1>"); + } + + vec.x = Integer.parseInt(positions[0]); + vec.y = Integer.parseInt(positions[1]); + vec.z = Integer.parseInt(positions[2]); + // We are done now + } + + return vec; + } + + @Override + public Vector3i subtract(Vector3 other) + { + Vector3i vec = other.asVector3i(); + return new Vector3i(x-vec.x, y-vec.y, z-vec.z); + } + + @Override + public Vector3i add(Vector3 other) + { + Vector3i vec = other.asVector3i(); + return new Vector3i(x+vec.x, y+vec.y, z +vec.z); + } + + @Override + public Vector3i asVector3i() { + return this; + } + + @Override + public Vector3d asVector3d() { + return new Vector3d(x, y, z); + } + + @Override + public double distance(Vector3 other) + { + Vector3i sub = subtract(other); + return Math.sqrt((sub.x * sub.x + sub.y * sub.y + sub.z * sub.z)); + } + + @Override + public Vector3i moveUp() + { + return add(new Vector3i(0,1,0)); + } + public Vector3i moveDown() + { + return subtract(new Vector3i(0,1,0)); + } + + @Override + public Vector3i Clone() + { + Vector3i n = new Vector3i(x, y, z); + return n; + } + + @Override + public String toString() + { + return "<"+String.valueOf(x)+", "+String.valueOf(y)+", "+String.valueOf(z)+">"; + } + + @Override + public CompoundTag serialize() + { + CompoundTag tag = new CompoundTag(); + tag.putInt("x", x); + tag.putInt("y", y); + tag.putInt("z", z); + + return tag; + } + + public static Vector3i deserialize(CompoundTag tag) + { + Vector3i vec = new Vector3i(); + + vec.x=tag.getInt("x"); + vec.y=tag.getInt("y"); + vec.z=tag.getInt("z"); + + return vec; + } + + @Override + public boolean Same(Vector3 other) + { + Vector3i vec = other.asVector3i(); + if(x == vec.x && y==vec.y && z==vec.z)return true; + else return false; + } + + @Override + public boolean Inside(Vector3 point1, Vector3 point2) + { + Vector3i v1 = point1.asVector3i(); + Vector3i v2 = point2.asVector3i(); + + if(v1.x <= x && v2.x >= x){ + if(v1.y <= y && v2.y >= y) + { + if(v1.z <= z && v2.z >= z) + { + return true; + } + } + } + + return false; + } + + @Override + public boolean greater(Vector3 other) + { + Vector3i v3 = other.asVector3i(); + return ((x>v3.x) && (y>v3.y) && (z>v3.z)); + } + + @Override + public boolean less(Vector3 other) + { + Vector3i vec = other.asVector3i(); + return ((x integerVector3) { + if(integerVector3 instanceof Vector3i v3i) + { + int Ycomp = Integer.compare(y, v3i.y); + if(Ycomp!=0)return Ycomp; + int Zcomp = Integer.compare(z, v3i.z); + if(Zcomp!=0)return Zcomp; + int Xcomp = Integer.compare(x, v3i.x); + if(Xcomp!=0)return Xcomp; + + return 0; + } else return -1; + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/vectors/WorldPosition.java b/src/main/java/dev/zontreck/libzontreck/vectors/WorldPosition.java index 2a2afad..3d62956 100644 --- a/src/main/java/dev/zontreck/libzontreck/vectors/WorldPosition.java +++ b/src/main/java/dev/zontreck/libzontreck/vectors/WorldPosition.java @@ -4,23 +4,26 @@ import dev.zontreck.libzontreck.LibZontreck; import dev.zontreck.libzontreck.exceptions.InvalidDeserialization; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtUtils; +import net.minecraft.network.protocol.game.ClientboundMoveEntityPacket; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.server.ServerLifecycleHooks; -public class WorldPosition { +public class WorldPosition implements Cloneable +{ - public Vector3 Position; + public Vector3d Position; public String Dimension; public String DimSafe; public WorldPosition(CompoundTag tag, boolean pretty) throws InvalidDeserialization { if (pretty) { - Position = new Vector3(tag.getString("Position")); + Position = Vector3d.parseString(tag.getString("Position")); Dimension = tag.getString("Dimension"); } else { - Position = new Vector3(tag.getCompound("pos")); + Position = Vector3d.deserialize(tag.getCompound("pos")); Dimension = tag.getString("Dimension"); } @@ -28,17 +31,17 @@ public class WorldPosition { } - public WorldPosition(Vector3 pos, String dim) { + public WorldPosition(Vector3d pos, String dim) { Position = pos; Dimension = dim; calcDimSafe(); } public WorldPosition(ServerPlayer player) { - this(new Vector3(player.position()), player.getLevel()); + this(new Vector3d(player.position()), player.serverLevel()); } - public WorldPosition(Vector3 pos, ServerLevel lvl) { + public WorldPosition(Vector3d pos, ServerLevel lvl) { Position = pos; Dimension = lvl.dimension().location().getNamespace() + ":" + lvl.dimension().location().getPath(); calcDimSafe(); @@ -94,7 +97,7 @@ public class WorldPosition { ResourceLocation rl = new ResourceLocation(dims[0], dims[1]); ServerLevel dimL = null; - for (ServerLevel lServerLevel : LibZontreck.THE_SERVER.getAllLevels()) { + for (ServerLevel lServerLevel : ServerLifecycleHooks.getCurrentServer().getAllLevels()) { ResourceLocation XL = lServerLevel.dimension().location(); if (XL.getNamespace().equals(rl.getNamespace())) { @@ -114,14 +117,26 @@ public class WorldPosition { public boolean same(WorldPosition other) { - return Position.same(other.Position) && Dimension == other.Dimension; + return Position.Same(other.Position) && Dimension == other.Dimension; } public ChunkPos getChunkPos() { net.minecraft.world.level.ChunkPos mcChunk = getActualDimension().getChunkAt(Position.asBlockPos()).getPos(); - ChunkPos pos = new ChunkPos(new Vector3(mcChunk.getMinBlockX(), -70, mcChunk.getMinBlockZ()), new Vector3(mcChunk.getMaxBlockX(), 400, mcChunk.getMaxBlockZ()), getActualDimension()); - pos.centerPoints = new Vector2(mcChunk.getMiddleBlockX(), mcChunk.getMiddleBlockZ()); + ChunkPos pos = new ChunkPos(new Vector3d(mcChunk.getMinBlockX(), -70, mcChunk.getMinBlockZ()), new Vector3d(mcChunk.getMaxBlockX(), 400, mcChunk.getMaxBlockZ()), getActualDimension()); + pos.centerPoints = new Vector2f(mcChunk.getMiddleBlockX(), mcChunk.getMiddleBlockZ()); return pos; } + + @Override + public WorldPosition clone() { + try { + WorldPosition clone = (WorldPosition) super.clone(); + if(Position != null) + clone.Position = Position.Clone(); + return clone; + } catch (CloneNotSupportedException e) { + throw new AssertionError(); + } + } } diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index ac70534..8cf3e73 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -6,32 +6,30 @@ # The name of the mod loader type to load - for regular FML @Mod mods it should be javafml modLoader="javafml" #mandatory # A version range to match for said mod loader - for regular FML @Mod it will be the forge version -loaderVersion="[43,)" #mandatory This is typically bumped every Minecraft version by Forge. See our download page for lists of versions. +loaderVersion="${loader_version_range}" #mandatory This is typically bumped every Minecraft version by Forge. See our download page for lists of versions. # The license for you mod. This is mandatory metadata and allows for easier comprehension of your redistributive properties. # Review your options at https://choosealicense.com/. All rights reserved is the default copyright stance, and is thus the default here. -license="GPLv2" +license="${mod_license}" # A URL to refer people to when problems occur with this mod -issueTrackerURL="https://github.com/zontreck/LibZontreckMod/issues/" #optional +#issueTrackerURL="https://change.me.to.your.issue.tracker.example.invalid/" #optional # A list of mods - how many allowed here is determined by the individual mod loader [[mods]] #mandatory # The modid of the mod -modId="libzontreck" #mandatory -# The version number of the mod - there's a few well known ${} variables useable here or just hardcode it -# ${file.jarVersion} will substitute the value of the Implementation-Version as read from the mod's JAR file metadata -# see the associated build.gradle script for how to populate this completely automatically during a build -version="${file.jarVersion}" #mandatory +modId="${mod_id}" #mandatory +# The version number of the mod +version="${mod_version}" #mandatory # A display name for the mod -displayName="LibZontreck" #mandatory -# A URL to query for updates for this mod. See the JSON update specification https://mcforge.readthedocs.io/en/latest/gettingstarted/autoupdate/ +displayName="${mod_name}" #mandatory +# A URL to query for updates for this mod. See the JSON update specification https://docs.minecraftforge.net/en/latest/misc/updatechecker/ #updateJSONURL="https://change.me.example.invalid/updates.json" #optional # A URL for the "homepage" for this mod, displayed in the mod UI #displayURL="https://change.me.to.your.mods.homepage.example.invalid/" #optional # A file name (in the root of the mod JAR) containing a logo for display #logoFile="examplemod.png" #optional # A text field displayed in the mod UI -credits="Zontreck" #optional +#credits="" #optional # A text field displayed in the mod UI -authors="zontreck" #optional +authors="${mod_authors}" #optional # Display Test controls the display for your mod in the server connection screen # MATCH_VERSION means that your mod will cause a red X if the versions on client and server differ. This is the default behaviour and should be what you choose if you have server and client elements to your mod. # IGNORE_SERVER_VERSION means that your mod will not cause a red X if it's present on the server but not on the client. This is what you should use if you're a server only mod. @@ -41,26 +39,26 @@ authors="zontreck" #optional #displayTest="MATCH_VERSION" # MATCH_VERSION is the default if nothing is specified (#optional) # The description text for the mod (multi line!) (#mandatory) -description=''' -This is a library mod for common code for all zontreck's mods. -''' +description='''${mod_description}''' # A dependency - use the . to indicate dependency for a specific modid. Dependencies are optional. -[[dependencies.libzontreck]] #optional +[[dependencies.${mod_id}]] #optional # the modid of the dependency modId="forge" #mandatory # Does this dependency have to exist - if not, ordering below must be specified mandatory=true #mandatory # The version range of the dependency -versionRange="[43,)" #mandatory -# An ordering relationship for the dependency - BEFORE or AFTER required if the relationship is not mandatory +versionRange="${forge_version_range}" #mandatory +# An ordering relationship for the dependency - BEFORE or AFTER required if the dependency is not mandatory +# BEFORE - This mod is loaded BEFORE the dependency +# AFTER - This mod is loaded AFTER the dependency ordering="NONE" -# Side this dependency is applied on - BOTH, CLIENT or SERVER +# Side this dependency is applied on - BOTH, CLIENT, or SERVER side="BOTH" # Here's another dependency -[[dependencies.libzontreck]] +[[dependencies.${mod_id}]] modId="minecraft" mandatory=true # This version range declares a minimum of the current minecraft version up to but not including the next major version -versionRange="[1.19,1.20)" +versionRange="${minecraft_version_range}" ordering="NONE" -side="BOTH" \ No newline at end of file +side="BOTH" diff --git a/src/main/resources/pack.mcmeta b/src/main/resources/pack.mcmeta index 954af3c..81a429d 100644 --- a/src/main/resources/pack.mcmeta +++ b/src/main/resources/pack.mcmeta @@ -1,8 +1,8 @@ { - "pack": { - "description": "LibZontreck resources", - "pack_format": 9, - "forge:resource_pack_format": 8, - "forge:data_pack_format": 9 - } -} \ No newline at end of file + "pack": { + "description": "${mod_id} resources", + "pack_format": 9, + "forge:resource_pack_format": 9, + "forge:data_pack_format": 10 + } +} From 792898135de77f074c81772ed2e053e6e84f2cce Mon Sep 17 00:00:00 2001 From: zontreck Date: Thu, 12 Sep 2024 16:05:01 -0700 Subject: [PATCH 10/21] Add a jenkinsfile --- Jenkinsfile | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 Jenkinsfile diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..43ad96e --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,50 @@ +pipeline { + agent any + + options { + buildDiscarder( + logRotator( + numToKeepStr: '5' + ) + ) + } + + stages { + stage("Build on Linux") { + agent { + label 'linux' + } + + tools { + jdk "jdk17" + } + + steps { + script { + sh ''' + #!/bin/bash + + git clean -xfd + git reset --hard + git fetch + + java -version + + chmod +x gradlew + ./gradlew -Dorg.gradle.java.home="$JAVA_HOME" build publish + + ''' + } + } + + post { + always { + archiveArtifacts artifacts: "build/libs/*.jar" + deleteDir() + } + } + + + } + } +} From 21a40835b5d96b610642c0ef9afdf7823bf59101 Mon Sep 17 00:00:00 2001 From: zontreck Date: Thu, 12 Sep 2024 16:06:19 -0700 Subject: [PATCH 11/21] Adjust maven publish path --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 3e6d508..fe931ad 100644 --- a/build.gradle +++ b/build.gradle @@ -245,7 +245,7 @@ publishing { repositories { maven { - url = "https://git.zontreck.com/api/packages/AriasCreations/maven" + url = "https://git.zontreck.com/api/packages/MinecraftMods/maven" name = "ariascreations" credentials { From d86f5b87f13f289ce860a83daf6a2a735443d79b Mon Sep 17 00:00:00 2001 From: zontreck Date: Thu, 12 Sep 2024 18:00:40 -0700 Subject: [PATCH 12/21] Fix compilation errors --- .../libzontreck/blocks/RedstoneBlock.java | 4 +- .../libzontreck/edlibmc/Auxiliaries.java | 590 --------- .../libzontreck/edlibmc/Containers.java | 138 -- .../libzontreck/edlibmc/Crafting.java | 440 ------- .../libzontreck/edlibmc/Fluidics.java | 485 ------- .../zontreck/libzontreck/edlibmc/Guis.java | 466 ------- .../libzontreck/edlibmc/Inventories.java | 1134 ----------------- .../libzontreck/edlibmc/Networking.java | 409 ------ .../edlibmc/OptionalRecipeCondition.java | 191 --- .../zontreck/libzontreck/edlibmc/Overlay.java | 165 --- .../zontreck/libzontreck/edlibmc/README.md | 4 - .../libzontreck/edlibmc/Registries.java | 263 ---- .../libzontreck/edlibmc/RfEnergy.java | 180 --- .../libzontreck/edlibmc/RsSignals.java | 42 - .../libzontreck/edlibmc/SidedProxy.java | 122 -- .../libzontreck/edlibmc/SlabSliceBlock.java | 228 ---- .../libzontreck/edlibmc/StandardBlocks.java | 698 ---------- .../edlibmc/StandardDoorBlock.java | 175 --- .../edlibmc/StandardEntityBlocks.java | 72 -- .../edlibmc/StandardFenceBlock.java | 203 --- .../edlibmc/StandardStairsBlock.java | 59 - .../libzontreck/edlibmc/TooltipDisplay.java | 130 -- .../libzontreck/edlibmc/VariantSlabBlock.java | 220 ---- .../libzontreck/edlibmc/VariantWallBlock.java | 200 --- .../events/ForgeEventHandlers.java | 2 +- .../libzontreck/memory/world/SavedBlock.java | 4 +- .../libzontreck/networking/ModMessages.java | 1 - .../packets/S2CWalletUpdatedPacket.java | 4 +- .../libzontreck/vectors/WorldPosition.java | 2 +- 29 files changed, 9 insertions(+), 6622 deletions(-) delete mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/Auxiliaries.java delete mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/Containers.java delete mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/Crafting.java delete mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/Fluidics.java delete mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/Guis.java delete mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/Inventories.java delete mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/Networking.java delete mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/OptionalRecipeCondition.java delete mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/Overlay.java delete mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/README.md delete mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/Registries.java delete mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/RfEnergy.java delete mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/RsSignals.java delete mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/SidedProxy.java delete mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/SlabSliceBlock.java delete mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/StandardBlocks.java delete mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/StandardDoorBlock.java delete mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/StandardEntityBlocks.java delete mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/StandardFenceBlock.java delete mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/StandardStairsBlock.java delete mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/TooltipDisplay.java delete mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/VariantSlabBlock.java delete mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/VariantWallBlock.java diff --git a/src/main/java/dev/zontreck/libzontreck/blocks/RedstoneBlock.java b/src/main/java/dev/zontreck/libzontreck/blocks/RedstoneBlock.java index 6b498b6..dd4f548 100644 --- a/src/main/java/dev/zontreck/libzontreck/blocks/RedstoneBlock.java +++ b/src/main/java/dev/zontreck/libzontreck/blocks/RedstoneBlock.java @@ -2,6 +2,7 @@ package dev.zontreck.libzontreck.blocks; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; +import net.minecraft.server.level.ServerLevel; import net.minecraft.world.item.context.BlockPlaceContext; import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.Level; @@ -45,7 +46,8 @@ public abstract class RedstoneBlock extends RotatableBlock private boolean redstoneIsActivated(LevelReader level, BlockPos pos) { - if(level.hasNeighborSignal(pos)) + ServerLevel srv = (ServerLevel)level; + if(srv.hasNeighborSignal(pos)) return true; else return false; } diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/Auxiliaries.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/Auxiliaries.java deleted file mode 100644 index eb57f30..0000000 --- a/src/main/java/dev/zontreck/libzontreck/edlibmc/Auxiliaries.java +++ /dev/null @@ -1,590 +0,0 @@ -/* - * @file Auxiliaries.java - * @author Stefan Wilhelm (wile) - * @copyright (C) 2020 Stefan Wilhelm - * @license MIT (see https://opensource.org/licenses/MIT) - * - * General commonly used functionality. - */ -package dev.zontreck.libzontreck.edlibmc; - -import com.mojang.blaze3d.platform.InputConstants; -import net.minecraft.ChatFormatting; -import net.minecraft.SharedConstants; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.core.Registry; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.ComponentUtils; -import net.minecraft.network.chat.MutableComponent; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.Item; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.TooltipFlag; -import net.minecraft.world.level.BlockGetter; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.state.properties.BlockStateProperties; -import net.minecraft.world.phys.AABB; -import net.minecraft.world.phys.shapes.BooleanOp; -import net.minecraft.world.phys.shapes.Shapes; -import net.minecraft.world.phys.shapes.VoxelShape; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.api.distmarker.OnlyIn; -import net.minecraftforge.fml.ModList; -import net.minecraftforge.registries.ForgeRegistries; -import org.slf4j.Logger; -import org.lwjgl.glfw.GLFW; - -import javax.annotation.Nullable; -import java.io.BufferedReader; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.NoSuchElementException; -import java.util.function.Function; -import java.util.function.Supplier; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import java.util.stream.Stream; - - -public class Auxiliaries { - private static String modid; - private static Logger logger; - private static Supplier server_config_supplier = CompoundTag::new; - - public static void init(String modid, Logger logger, Supplier server_config_supplier) { - Auxiliaries.modid = modid; - Auxiliaries.logger = logger; - Auxiliaries.server_config_supplier = server_config_supplier; - } - - // ------------------------------------------------------------------------------------------------------------------- - // Mod specific exports - // ------------------------------------------------------------------------------------------------------------------- - - public static String modid() { - return modid; - } - - public static Logger logger() { - return logger; - } - - // ------------------------------------------------------------------------------------------------------------------- - // Sidedness, system/environment, tagging interfaces - // ------------------------------------------------------------------------------------------------------------------- - - public interface IExperimentalFeature { - } - - public static boolean isModLoaded(final String registry_name) { - return ModList.get().isLoaded(registry_name); - } - - public static boolean isDevelopmentMode() { - return SharedConstants.IS_RUNNING_IN_IDE; - } - - @OnlyIn(Dist.CLIENT) - public static boolean isShiftDown() { - return (InputConstants.isKeyDown(SidedProxy.mc().getWindow().getWindow(), GLFW.GLFW_KEY_LEFT_SHIFT) || - InputConstants.isKeyDown(SidedProxy.mc().getWindow().getWindow(), GLFW.GLFW_KEY_RIGHT_SHIFT)); - } - - @OnlyIn(Dist.CLIENT) - public static boolean isCtrlDown() { - return (InputConstants.isKeyDown(SidedProxy.mc().getWindow().getWindow(), GLFW.GLFW_KEY_LEFT_CONTROL) || - InputConstants.isKeyDown(SidedProxy.mc().getWindow().getWindow(), GLFW.GLFW_KEY_RIGHT_CONTROL)); - } - - // ------------------------------------------------------------------------------------------------------------------- - // Logging - // ------------------------------------------------------------------------------------------------------------------- - - public static void logInfo(final String msg) { - logger.info(msg); - } - - public static void logWarn(final String msg) { - logger.warn(msg); - } - - public static void logError(final String msg) { - logger.error(msg); - } - - public static void logDebug(final String msg) { /*logger.debug(msg);*/ } - - // ------------------------------------------------------------------------------------------------------------------- - // Localization, text formatting - // ------------------------------------------------------------------------------------------------------------------- - - /** - * Text localization wrapper, implicitly prepends `MODID` to the - * translation keys. Forces formatting argument, nullable if no special formatting shall be applied.. - */ - public static MutableComponent localizable(String modtrkey, Object... args) { - return Component.translatable((modtrkey.startsWith("block.") || (modtrkey.startsWith("item."))) ? (modtrkey) : (modid + "." + modtrkey), args); - } - - public static MutableComponent localizable(String modtrkey, @Nullable ChatFormatting color, Object... args) { - final MutableComponent tr = Component.translatable(modid + "." + modtrkey, args); - if (color != null) tr.getStyle().applyFormat(color); - return tr; - } - - public static Component localizable(String modtrkey) { - return localizable(modtrkey, new Object[]{}); - } - - public static Component localizable_block_key(String blocksubkey) { - return Component.translatable("block." + modid + "." + blocksubkey); - } - - @OnlyIn(Dist.CLIENT) - public static String localize(String translationKey, Object... args) { - Component tr = Component.translatable(translationKey, args); - tr.getStyle().applyFormat(ChatFormatting.RESET); - final String ft = tr.getString(); - if (ft.contains("${")) { - // Non-recursive, non-argument lang file entry cross referencing. - Pattern pt = Pattern.compile("\\$\\{([^}]+)\\}"); - Matcher mt = pt.matcher(ft); - StringBuffer sb = new StringBuffer(); - while (mt.find()) { - String m = mt.group(1); - if (m.contains("?")) { - String[] kv = m.split("\\?", 2); - String key = kv[0].trim(); - boolean not = key.startsWith("!"); - if (not) key = key.replaceFirst("!", ""); - m = kv[1].trim(); - if (!server_config_supplier.get().contains(key)) { - m = ""; - } else { - boolean r = server_config_supplier.get().getBoolean(key); - if (not) r = !r; - if (!r) m = ""; - } - } - mt.appendReplacement(sb, Matcher.quoteReplacement((Component.translatable(m)).getString().trim())); - } - mt.appendTail(sb); - return sb.toString(); - } else { - return ft; - } - } - - /** - * Returns true if a given key is translated for the current language. - */ - @OnlyIn(Dist.CLIENT) - public static boolean hasTranslation(String key) { - return net.minecraft.client.resources.language.I18n.exists(key); - } - - public static MutableComponent join(Collection components, String separator) { - return ComponentUtils.formatList(components, Component.literal(separator), Function.identity()); - } - - public static MutableComponent join(Component... components) { - final MutableComponent tc = Component.empty(); - for (Component c : components) { - tc.append(c); - } - return tc; - } - - public static boolean isEmpty(Component component) { - return component.getSiblings().isEmpty() && component.getString().isEmpty(); - } - - public static final class Tooltip { - @OnlyIn(Dist.CLIENT) - public static boolean extendedTipCondition() { - return isShiftDown(); - } - - @OnlyIn(Dist.CLIENT) - public static boolean helpCondition() { - return isShiftDown() && isCtrlDown(); - } - - /** - * Adds an extended tooltip or help tooltip depending on the key states of CTRL and SHIFT. - * Returns true if the localisable help/tip was added, false if not (either not CTL/SHIFT or - * no translation found). - */ - @OnlyIn(Dist.CLIENT) - public static boolean addInformation(@Nullable String advancedTooltipTranslationKey, @Nullable String helpTranslationKey, List tooltip, TooltipFlag flag, boolean addAdvancedTooltipHints) { - // Note: intentionally not using keybinding here, this must be `control` or `shift`. - final boolean help_available = (helpTranslationKey != null) && Auxiliaries.hasTranslation(helpTranslationKey + ".help"); - final boolean tip_available = (advancedTooltipTranslationKey != null) && Auxiliaries.hasTranslation(helpTranslationKey + ".tip"); - if ((!help_available) && (!tip_available)) return false; - String tip_text = ""; - if (helpCondition()) { - if (help_available) tip_text = localize(helpTranslationKey + ".help"); - } else if (extendedTipCondition()) { - if (tip_available) tip_text = localize(advancedTooltipTranslationKey + ".tip"); - } else if (addAdvancedTooltipHints) { - if (tip_available) tip_text += localize(modid + ".tooltip.hint.extended") + (help_available ? " " : ""); - if (help_available) tip_text += localize(modid + ".tooltip.hint.help"); - } - if (tip_text.isEmpty()) return false; - String[] tip_list = tip_text.split("\\r?\\n"); - for (String tip : tip_list) { - tooltip.add(Component.literal(tip.replaceAll("\\s+$", "").replaceAll("^\\s+", "")).withStyle(ChatFormatting.GRAY)); - } - return true; - } - - /** - * Adds an extended tooltip or help tooltip for a given stack depending on the key states of CTRL and SHIFT. - * Format in the lang file is (e.g. for items): "item.MODID.REGISTRYNAME.tip" and "item.MODID.REGISTRYNAME.help". - * Return value see method pattern above. - */ - @OnlyIn(Dist.CLIENT) - public static boolean addInformation(ItemStack stack, @Nullable BlockGetter world, List tooltip, TooltipFlag flag, boolean addAdvancedTooltipHints) { - return addInformation(stack.getDescriptionId(), stack.getDescriptionId(), tooltip, flag, addAdvancedTooltipHints); - } - - @OnlyIn(Dist.CLIENT) - public static boolean addInformation(String translation_key, List tooltip) { - if (!Auxiliaries.hasTranslation(translation_key)) return false; - tooltip.add(Component.literal(localize(translation_key).replaceAll("\\s+$", "").replaceAll("^\\s+", "")).withStyle(ChatFormatting.GRAY)); - return true; - } - - } - - @SuppressWarnings("unused") - public static void playerChatMessage(final Player player, final String message) { - player.displayClientMessage(Component.translatable(message.trim()), true); - } - - public static @Nullable Component unserializeTextComponent(String serialized) { - return Component.Serializer.fromJson(serialized); - } - - public static String serializeTextComponent(Component tc) { - return (tc == null) ? ("") : (Component.Serializer.toJson(tc)); - } - - // ------------------------------------------------------------------------------------------------------------------- - // Tag Handling - // ------------------------------------------------------------------------------------------------------------------- - - @SuppressWarnings("deprecation") - public static boolean isInItemTag(Item item, ResourceLocation tag) { - return ForgeRegistries.ITEMS.tags().stream().filter(tg -> tg.getKey().location().equals(tag)).anyMatch(tk -> tk.contains(item)); - } - - @SuppressWarnings("deprecation") - public static boolean isInBlockTag(Block block, ResourceLocation tag) { - return ForgeRegistries.BLOCKS.tags().stream().filter(tg -> tg.getKey().location().equals(tag)).anyMatch(tk -> tk.contains(block)); - } - - @SuppressWarnings("deprecation") - public static ResourceLocation getResourceLocation(Item item) { - return ForgeRegistries.ITEMS.getKey(item); - } - - @SuppressWarnings("deprecation") - public static ResourceLocation getResourceLocation(Block block) { - return ForgeRegistries.BLOCKS.getKey(block); - } - - @SuppressWarnings("deprecation") - public static ResourceLocation getResourceLocation(net.minecraft.world.inventory.MenuType menu) { - return ForgeRegistries.MENU_TYPES.getKey(menu); - } - - @SuppressWarnings("deprecation") - public static ResourceLocation getResourceLocation(net.minecraft.world.level.material.Fluid fluid) { - return ForgeRegistries.FLUIDS.getKey(fluid); - } - - // ------------------------------------------------------------------------------------------------------------------- - // Item NBT data - // ------------------------------------------------------------------------------------------------------------------- - - /** - * Equivalent to getDisplayName(), returns null if no custom name is set. - */ - public static @Nullable Component getItemLabel(ItemStack stack) { - CompoundTag nbt = stack.getTagElement("display"); - if (nbt != null && nbt.contains("Name", 8)) { - try { - Component tc = unserializeTextComponent(nbt.getString("Name")); - if (tc != null) return tc; - nbt.remove("Name"); - } catch (Exception e) { - nbt.remove("Name"); - } - } - return null; - } - - public static ItemStack setItemLabel(ItemStack stack, @Nullable Component name) { - if (name != null) { - CompoundTag nbt = stack.getOrCreateTagElement("display"); - nbt.putString("Name", serializeTextComponent(name)); - } else { - if (stack.hasTag()) stack.removeTagKey("display"); - } - return stack; - } - - // ------------------------------------------------------------------------------------------------------------------- - // Block handling - // ------------------------------------------------------------------------------------------------------------------- - - public static boolean isWaterLogged(BlockState state) { - return state.hasProperty(BlockStateProperties.WATERLOGGED) && state.getValue(BlockStateProperties.WATERLOGGED); - } - - public static AABB getPixeledAABB(double x0, double y0, double z0, double x1, double y1, double z1) { - return new AABB(x0 / 16.0, y0 / 16.0, z0 / 16.0, x1 / 16.0, y1 / 16.0, z1 / 16.0); - } - - public static AABB getRotatedAABB(AABB bb, Direction new_facing) { - return getRotatedAABB(bb, new_facing, false); - } - - public static AABB[] getRotatedAABB(AABB[] bb, Direction new_facing) { - return getRotatedAABB(bb, new_facing, false); - } - - public static AABB getRotatedAABB(AABB bb, Direction new_facing, boolean horizontal_rotation) { - if (!horizontal_rotation) { - switch (new_facing.get3DDataValue()) { - case 0: - return new AABB(1 - bb.maxX, bb.minZ, bb.minY, 1 - bb.minX, bb.maxZ, bb.maxY); // D - case 1: - return new AABB(1 - bb.maxX, 1 - bb.maxZ, 1 - bb.maxY, 1 - bb.minX, 1 - bb.minZ, 1 - bb.minY); // U - case 2: - return new AABB(bb.minX, bb.minY, bb.minZ, bb.maxX, bb.maxY, bb.maxZ); // N --> bb - case 3: - return new AABB(1 - bb.maxX, bb.minY, 1 - bb.maxZ, 1 - bb.minX, bb.maxY, 1 - bb.minZ); // S - case 4: - return new AABB(bb.minZ, bb.minY, 1 - bb.maxX, bb.maxZ, bb.maxY, 1 - bb.minX); // W - case 5: - return new AABB(1 - bb.maxZ, bb.minY, bb.minX, 1 - bb.minZ, bb.maxY, bb.maxX); // E - } - } else { - switch (new_facing.get3DDataValue()) { - case 0: - return new AABB(bb.minX, bb.minY, bb.minZ, bb.maxX, bb.maxY, bb.maxZ); // D --> bb - case 1: - return new AABB(bb.minX, bb.minY, bb.minZ, bb.maxX, bb.maxY, bb.maxZ); // U --> bb - case 2: - return new AABB(bb.minX, bb.minY, bb.minZ, bb.maxX, bb.maxY, bb.maxZ); // N --> bb - case 3: - return new AABB(1 - bb.maxX, bb.minY, 1 - bb.maxZ, 1 - bb.minX, bb.maxY, 1 - bb.minZ); // S - case 4: - return new AABB(bb.minZ, bb.minY, 1 - bb.maxX, bb.maxZ, bb.maxY, 1 - bb.minX); // W - case 5: - return new AABB(1 - bb.maxZ, bb.minY, bb.minX, 1 - bb.minZ, bb.maxY, bb.maxX); // E - } - } - return bb; - } - - public static AABB[] getRotatedAABB(AABB[] bbs, Direction new_facing, boolean horizontal_rotation) { - final AABB[] transformed = new AABB[bbs.length]; - for (int i = 0; i < bbs.length; ++i) transformed[i] = getRotatedAABB(bbs[i], new_facing, horizontal_rotation); - return transformed; - } - - public static AABB getYRotatedAABB(AABB bb, int clockwise_90deg_steps) { - final Direction[] direction_map = new Direction[]{Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST}; - return getRotatedAABB(bb, direction_map[(clockwise_90deg_steps + 4096) & 0x03], true); - } - - public static AABB[] getYRotatedAABB(AABB[] bbs, int clockwise_90deg_steps) { - final AABB[] transformed = new AABB[bbs.length]; - for (int i = 0; i < bbs.length; ++i) transformed[i] = getYRotatedAABB(bbs[i], clockwise_90deg_steps); - return transformed; - } - - public static AABB getMirroredAABB(AABB bb, Direction.Axis axis) { - return switch (axis) { - case X -> new AABB(1 - bb.maxX, bb.minY, bb.minZ, 1 - bb.minX, bb.maxY, bb.maxZ); - case Y -> new AABB(bb.minX, 1 - bb.maxY, bb.minZ, bb.maxX, 1 - bb.minY, bb.maxZ); - case Z -> new AABB(bb.minX, bb.minY, 1 - bb.maxZ, bb.maxX, bb.maxY, 1 - bb.minZ); - }; - } - - public static AABB[] getMirroredAABB(AABB[] bbs, Direction.Axis axis) { - final AABB[] transformed = new AABB[bbs.length]; - for (int i = 0; i < bbs.length; ++i) transformed[i] = getMirroredAABB(bbs[i], axis); - return transformed; - } - - public static VoxelShape getUnionShape(AABB... aabbs) { - VoxelShape shape = Shapes.empty(); - for (AABB aabb : aabbs) shape = Shapes.joinUnoptimized(shape, Shapes.create(aabb), BooleanOp.OR); - return shape; - } - - public static VoxelShape getUnionShape(AABB[]... aabb_list) { - VoxelShape shape = Shapes.empty(); - for (AABB[] aabbs : aabb_list) { - for (AABB aabb : aabbs) shape = Shapes.joinUnoptimized(shape, Shapes.create(aabb), BooleanOp.OR); - } - return shape; - } - - public static AABB[] getMappedAABB(AABB[] bbs, Function mapper) { - final AABB[] transformed = new AABB[bbs.length]; - for (int i = 0; i < bbs.length; ++i) transformed[i] = mapper.apply(bbs[i]); - return transformed; - } - - public static final class BlockPosRange implements Iterable { - private final int x0, x1, y0, y1, z0, z1; - - public BlockPosRange(int x0, int y0, int z0, int x1, int y1, int z1) { - this.x0 = Math.min(x0, x1); - this.x1 = Math.max(x0, x1); - this.y0 = Math.min(y0, y1); - this.y1 = Math.max(y0, y1); - this.z0 = Math.min(z0, z1); - this.z1 = Math.max(z0, z1); - } - - public static BlockPosRange of(AABB range) { - return new BlockPosRange( - (int) Math.floor(range.minX), - (int) Math.floor(range.minY), - (int) Math.floor(range.minZ), - (int) Math.floor(range.maxX - .0625), - (int) Math.floor(range.maxY - .0625), - (int) Math.floor(range.maxZ - .0625) - ); - } - - public int getXSize() { - return x1 - x0 + 1; - } - - public int getYSize() { - return y1 - y0 + 1; - } - - public int getZSize() { - return z1 - z0 + 1; - } - - public int getArea() { - return getXSize() * getZSize(); - } - - public int getHeight() { - return getYSize(); - } - - public int getVolume() { - return getXSize() * getYSize() * getZSize(); - } - - public BlockPos byXZYIndex(int xyz_index) { - final int xsz = getXSize(), ysz = getYSize(), zsz = getZSize(); - xyz_index = xyz_index % (xsz * ysz * zsz); - final int y = xyz_index / (xsz * zsz); - xyz_index -= y * (xsz * zsz); - final int z = xyz_index / xsz; - xyz_index -= z * xsz; - final int x = xyz_index; - return new BlockPos(x0 + x, y0 + y, z0 + z); - } - - public BlockPos byXZIndex(int xz_index, int y_offset) { - final int xsz = getXSize(), zsz = getZSize(); - xz_index = xz_index % (xsz * zsz); - final int z = xz_index / xsz; - xz_index -= z * xsz; - final int x = xz_index; - return new BlockPos(x0 + x, y0 + y_offset, z0 + z); - } - - public static final class BlockRangeIterator implements Iterator { - private final BlockPosRange range_; - private int x, y, z; - - public BlockRangeIterator(BlockPosRange range) { - range_ = range; - x = range.x0; - y = range.y0; - z = range.z0; - } - - @Override - public boolean hasNext() { - return (z <= range_.z1); - } - - @Override - public BlockPos next() { - if (!hasNext()) throw new NoSuchElementException(); - final BlockPos pos = new BlockPos(x, y, z); - ++x; - if (x > range_.x1) { - x = range_.x0; - ++y; - if (y > range_.y1) { - y = range_.y0; - ++z; - } - } - return pos; - } - } - - @Override - public BlockRangeIterator iterator() { - return new BlockRangeIterator(this); - } - - public Stream stream() { - return java.util.stream.StreamSupport.stream(spliterator(), false); - } - } - - // ------------------------------------------------------------------------------------------------------------------- - // JAR resource related - // ------------------------------------------------------------------------------------------------------------------- - - public static String loadResourceText(InputStream is) { - try { - if (is == null) return ""; - BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8)); - return br.lines().collect(Collectors.joining("\n")); - } catch (Throwable e) { - return ""; - } - } - - public static String loadResourceText(String path) { - return loadResourceText(Auxiliaries.class.getResourceAsStream(path)); - } - - public static void logGitVersion(String mod_name) { - try { - // Done during construction to have an exact version in case of a crash while registering. - String version = Auxiliaries.loadResourceText("/.gitversion-" + modid).trim(); - logInfo(mod_name + ((version.isEmpty()) ? (" (dev build)") : (" GIT id #" + version)) + "."); - } catch (Throwable e) { - // (void)e; well, then not. Priority is not to get unneeded crashes because of version logging. - } - } -} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/Containers.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/Containers.java deleted file mode 100644 index 37a1814..0000000 --- a/src/main/java/dev/zontreck/libzontreck/edlibmc/Containers.java +++ /dev/null @@ -1,138 +0,0 @@ -package dev.zontreck.libzontreck.edlibmc; - -import net.minecraft.util.Mth; -import net.minecraft.world.Container; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.inventory.Slot; -import net.minecraft.world.item.ItemStack; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.api.distmarker.OnlyIn; - -import java.util.function.BiConsumer; - -public class Containers { - // ------------------------------------------------------------------------------------------------------------------- - // Slots - // ------------------------------------------------------------------------------------------------------------------- - - public static class StorageSlot extends Slot { - protected BiConsumer slot_change_action_ = (oldStack, newStack) -> { - }; - protected int stack_limit_ = 64; - public boolean enabled = true; - - public StorageSlot(Container inventory, int index, int x, int y) { - super(inventory, index, x, y); - } - - public StorageSlot setSlotStackLimit(int limit) { - stack_limit_ = Mth.clamp(limit, 1, 64); - return this; - } - - public int getMaxStackSize() { - return stack_limit_; - } - - public StorageSlot setSlotChangeNotifier(BiConsumer action) { - slot_change_action_ = action; - return this; - } - - @Override - public void onQuickCraft(ItemStack oldStack, ItemStack newStack) { - slot_change_action_.accept(oldStack, newStack); - } // no crafting trigger - - @Override - public void set(ItemStack stack) { - if (stack.is(getItem().getItem())) { - super.set(stack); - } else { - final ItemStack before = getItem().copy(); - super.set(stack); // whatever this does else next to setting inventory. - slot_change_action_.accept(before, getItem()); - } - } - - @Override - public boolean mayPlace(ItemStack stack) { - return enabled && this.container.canPlaceItem(this.getSlotIndex(), stack); - } - - @Override - public int getMaxStackSize(ItemStack stack) { - return Math.min(getMaxStackSize(), stack_limit_); - } - - @OnlyIn(Dist.CLIENT) - public boolean isActive() { - return enabled; - } - } - - public static class LockedSlot extends Slot { - protected int stack_limit_ = 64; - public boolean enabled = true; - - public LockedSlot(Container inventory, int index, int x, int y) { - super(inventory, index, x, y); - } - - public LockedSlot setSlotStackLimit(int limit) { - stack_limit_ = Mth.clamp(limit, 1, 64); - return this; - } - - public int getMaxStackSize() { - return stack_limit_; - } - - @Override - public int getMaxStackSize(ItemStack stack) { - return Math.min(getMaxStackSize(), stack_limit_); - } - - @Override - public boolean mayPlace(ItemStack stack) { - return false; - } - - @Override - public boolean mayPickup(Player player) { - return false; - } - - @OnlyIn(Dist.CLIENT) - public boolean isActive() { - return enabled; - } - } - - public static class HiddenSlot extends Slot { - public HiddenSlot(Container inventory, int index) { - super(inventory, index, 0, 0); - } - - @Override - public int getMaxStackSize(ItemStack stack) { - return getMaxStackSize(); - } - - @Override - public boolean mayPlace(ItemStack stack) { - return false; - } - - @Override - public boolean mayPickup(Player player) { - return false; - } - - @OnlyIn(Dist.CLIENT) - public boolean isActive() { - return false; - } - } - -} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/Crafting.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/Crafting.java deleted file mode 100644 index 2708733..0000000 --- a/src/main/java/dev/zontreck/libzontreck/edlibmc/Crafting.java +++ /dev/null @@ -1,440 +0,0 @@ -/* - * @file Recipes.java - * @author Stefan Wilhelm (wile) - * @copyright (C) 2020 Stefan Wilhelm - * @license MIT (see https://opensource.org/licenses/MIT) - * - * Recipe utility functionality. - */ -package dev.zontreck.libzontreck.edlibmc; - -import net.minecraft.core.NonNullList; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.util.Mth; -import net.minecraft.util.Tuple; -import net.minecraft.world.Container; -import net.minecraft.world.SimpleContainer; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.entity.player.StackedContents; -import net.minecraft.world.inventory.AbstractContainerMenu; -import net.minecraft.world.inventory.CraftingContainer; -import net.minecraft.world.item.EnchantedBookItem; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.Items; -import net.minecraft.world.item.crafting.*; -import net.minecraft.world.item.enchantment.Enchantment; -import net.minecraft.world.item.enchantment.EnchantmentHelper; -import net.minecraft.world.item.enchantment.EnchantmentInstance; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.block.ComposterBlock; -import net.minecraftforge.common.ForgeHooks; -import net.minecraftforge.common.brewing.BrewingRecipeRegistry; - -import javax.annotation.Nullable; -import java.util.*; -import java.util.function.BiPredicate; - - -public class Crafting { - // ------------------------------------------------------------------------------------------------------------------- - - /** - * Returns a Crafting recipe by registry name. - */ - public static Optional getCraftingRecipe(Level world, ResourceLocation recipe_id) { - Recipe recipe = world.getRecipeManager().byKey(recipe_id).orElse(null); - return (recipe instanceof CraftingRecipe) ? Optional.of((CraftingRecipe) recipe) : Optional.empty(); - } - - /** - * Returns a list of matching recipes by the first N slots (crafting grid slots) of the given inventory. - */ - public static List get3x3CraftingRecipes(Level world, Container crafting_grid_slots) { - return CraftingGrid.instance3x3.getRecipes(world, crafting_grid_slots); - } - - /** - * Returns a recipe by the first N slots (crafting grid slots). - */ - public static Optional get3x3CraftingRecipe(Level world, Container crafting_grid_slots) { - return get3x3CraftingRecipes(world, crafting_grid_slots).stream().findFirst(); - } - - /** - * Returns the result item of the recipe with the given grid layout. - */ - public static ItemStack get3x3CraftingResult(Level world, Container grid, CraftingRecipe recipe) { - return CraftingGrid.instance3x3.getCraftingResult(world, grid, recipe); - } - - /** - * Returns the items remaining in the grid after crafting 3x3. - */ - public static List get3x3RemainingItems(Level world, Container grid, CraftingRecipe recipe) { - return CraftingGrid.instance3x3.getRemainingItems(world, grid, recipe); - } - - public static List get3x3Placement(Level world, CraftingRecipe recipe, Container item_inventory, @Nullable Container crafting_grid) { - final int width = 3; - final int height = 3; - if (!recipe.canCraftInDimensions(width, height)) return Collections.emptyList(); - List used = new ArrayList<>(); //NonNullList.withSize(width*height); - for (int i = width * height; i > 0; --i) used.add(ItemStack.EMPTY); - Container check_inventory = Inventories.copyOf(item_inventory); - Inventories.InventoryRange source = new Inventories.InventoryRange(check_inventory); - final List ingredients = recipe.getIngredients(); - final List preferred = new ArrayList<>(width * height); - if (crafting_grid != null) { - for (int i = 0; i < crafting_grid.getContainerSize(); ++i) { - ItemStack stack = crafting_grid.getItem(i); - if (stack.isEmpty()) continue; - stack = stack.copy(); - stack.setCount(1); - if (!source.extract(stack).isEmpty()) preferred.add(stack); - } - } - for (int i = 0; i < ingredients.size(); ++i) { - final Ingredient ingredient = ingredients.get(i); - if (ingredient == Ingredient.EMPTY) continue; - ItemStack stack = preferred.stream().filter(ingredient).findFirst().orElse(ItemStack.EMPTY); - if (!stack.isEmpty()) { - preferred.remove(stack); - } else { - stack = source.stream().filter(ingredient).findFirst().orElse(ItemStack.EMPTY); - if (stack.isEmpty()) return Collections.emptyList(); - stack = stack.copy(); - stack.setCount(1); - if (source.extract(stack).isEmpty()) return Collections.emptyList(); - } - used.set(i, stack); - } - if (recipe instanceof ShapedRecipe shaped) { - List placement = NonNullList.withSize(width * height, ItemStack.EMPTY); - for (int row = 0; row < shaped.getRecipeHeight(); ++row) { - for (int col = 0; col < shaped.getRecipeWidth(); ++col) { - placement.set(width * row + col, used.get(row * shaped.getRecipeWidth() + col)); - } - } - return placement; - } else { - return used; - } - } - - /** - * Returns the recipe for a given input stack to smelt, null if there is no recipe - * for the given type (SMELTING,BLASTING,SMOKING, etc). - */ - public static > Optional getFurnaceRecipe(RecipeType recipe_type, Level world, ItemStack input_stack) { - if (input_stack.isEmpty()) { - return Optional.empty(); - } else if (recipe_type == RecipeType.SMELTING) { - SimpleContainer inventory = new SimpleContainer(3); - inventory.setItem(0, input_stack); - SmeltingRecipe recipe = world.getRecipeManager().getRecipeFor(RecipeType.SMELTING, inventory, world).orElse(null); - return (recipe == null) ? Optional.empty() : Optional.of(recipe); - } else if (recipe_type == RecipeType.BLASTING) { - SimpleContainer inventory = new SimpleContainer(3); - inventory.setItem(0, input_stack); - BlastingRecipe recipe = world.getRecipeManager().getRecipeFor(RecipeType.BLASTING, inventory, world).orElse(null); - return (recipe == null) ? Optional.empty() : Optional.of(recipe); - } else if (recipe_type == RecipeType.SMOKING) { - SimpleContainer inventory = new SimpleContainer(3); - inventory.setItem(0, input_stack); - SmokingRecipe recipe = world.getRecipeManager().getRecipeFor(RecipeType.SMOKING, inventory, world).orElse(null); - return (recipe == null) ? Optional.empty() : Optional.of(recipe); - } else { - return Optional.empty(); - } - } - - // ------------------------------------------------------------------------------------------------------------------- - - public static > int getSmeltingTimeNeeded(RecipeType recipe_type, Level world, ItemStack stack) { - if (stack.isEmpty()) return 0; - final int t = getFurnaceRecipe(recipe_type, world, stack).map((AbstractCookingRecipe::getCookingTime)).orElse(0); - return (t <= 0) ? 200 : t; - } - - /** - * Returns the burn time of an item when used as fuel, 0 if it is no fuel. - */ - public static int getFuelBurntime(Level world, ItemStack stack) { - if (stack.isEmpty()) return 0; - int t = ForgeHooks.getBurnTime(stack, null); - return Math.max(t, 0); - } - - /** - * Returns true if an item can be used as fuel. - */ - public static boolean isFuel(Level world, ItemStack stack) { - return (getFuelBurntime(world, stack) > 0) || (stack.getItem() == Items.LAVA_BUCKET); - } - - /** - * Returns burntime and remaining stack then the item shall be used as fuel. - */ - public static Tuple consumeFuel(Level world, ItemStack stack) { - if (stack.isEmpty()) return new Tuple<>(0, stack); - int burnime = getFuelBurntime(world, stack); - if ((stack.getItem() == Items.LAVA_BUCKET)) { - if (burnime <= 0) burnime = 1000 * 20; - return new Tuple<>(burnime, new ItemStack(Items.BUCKET)); - } else if (burnime <= 0) { - return new Tuple<>(0, stack); - } else { - ItemStack left_over = stack.copy(); - left_over.shrink(1); - return new Tuple<>(burnime, left_over); - } - } - - /** - * Returns true if the item can be used as brewing fuel. - */ - public static boolean isBrewingFuel(Level world, ItemStack stack) { - return (stack.getItem() == Items.BLAZE_POWDER) || (stack.getItem() == Items.BLAZE_ROD); - } - - // ------------------------------------------------------------------------------------------------------------------- - - /** - * Returns true if the item can be used as brewing ingredient. - */ - public static boolean isBrewingIngredient(Level world, ItemStack stack) { - return BrewingRecipeRegistry.isValidIngredient(stack); - } - - /** - * Returns true if the item can be used as brewing bottle. - */ - public static boolean isBrewingInput(Level world, ItemStack stack) { - return BrewingRecipeRegistry.isValidInput(stack); - } - - /** - * Returns the burn time for brewing of the given stack. - */ - public static int getBrewingFuelBurntime(Level world, ItemStack stack) { - if (stack.isEmpty()) return 0; - if (stack.getItem() == Items.BLAZE_POWDER) return (400 * 20); - if (stack.getItem() == Items.BLAZE_ROD) return (400 * 40); - return 0; - } - - /** - * Returns brewing burn time and remaining stack if the item shall be used as fuel. - */ - public static Tuple consumeBrewingFuel(Level world, ItemStack stack) { - int burntime = getBrewingFuelBurntime(world, stack); - if (burntime <= 0) return new Tuple<>(0, stack.copy()); - stack = stack.copy(); - stack.shrink(1); - return new Tuple<>(burntime, stack.isEmpty() ? ItemStack.EMPTY : stack); - } - - public static double getCompostingChance(ItemStack stack) { - return ComposterBlock.COMPOSTABLES.getOrDefault(stack.getItem(), 0); - } - - /** - * Returns the enchtments bound to the given stack. - */ - public static Map getEnchantmentsOnItem(Level world, ItemStack stack) { - return (stack.isEmpty() || (stack.getTag() == null)) ? Collections.emptyMap() : EnchantmentHelper.getEnchantments(stack); - } - - // ------------------------------------------------------------------------------------------------------------------- - - /** - * Returns an enchanted book with the given enchantment, emtpy stack if not applicable. - */ - public static ItemStack getEnchantmentBook(Level world, Enchantment enchantment, int level) { - return ((!enchantment.isAllowedOnBooks()) || (level <= 0)) ? ItemStack.EMPTY : EnchantedBookItem.createForEnchantment(new EnchantmentInstance(enchantment, level)); - } - - // ------------------------------------------------------------------------------------------------------------------- - - /** - * Returns the accumulated repair cost for the given enchantments. - */ - public static int getEnchantmentRepairCost(Level world, Map enchantments) { - int repair_cost = 0; - for (Map.Entry e : enchantments.entrySet()) - repair_cost = repair_cost * 2 + 1; // @see: RepairContainer.getNewRepairCost() - return repair_cost; - } - - /** - * Trys to add an enchtment to the given stack, returns boolean success. - */ - public static boolean addEnchantmentOnItem(Level world, ItemStack stack, Enchantment enchantment, int level) { - if (stack.isEmpty() || (level <= 0) || (!stack.isEnchantable()) || (level >= enchantment.getMaxLevel())) - return false; - final Map on_item = getEnchantmentsOnItem(world, stack); - if (on_item.keySet().stream().anyMatch(ench -> ench.isCompatibleWith(enchantment))) return false; - final ItemStack book = EnchantedBookItem.createForEnchantment(new EnchantmentInstance(enchantment, level)); - if ((!(stack.isBookEnchantable(book) && enchantment.isAllowedOnBooks())) && (!stack.canApplyAtEnchantingTable(enchantment)) && (!enchantment.canEnchant(stack))) - return false; - final int existing_level = on_item.getOrDefault(enchantment, 0); - if (existing_level > 0) level = Mth.clamp(level + existing_level, 1, enchantment.getMaxLevel()); - on_item.put(enchantment, level); - EnchantmentHelper.setEnchantments(on_item, stack); - stack.setRepairCost(getEnchantmentRepairCost(world, on_item)); - return true; - } - - /** - * Removes enchantments from a stack, returns the removed enchantments. - */ - public static Map removeEnchantmentsOnItem(Level world, ItemStack stack, BiPredicate filter) { - if (stack.isEmpty()) return Collections.emptyMap(); - final Map on_item = getEnchantmentsOnItem(world, stack); - final Map removed = new HashMap<>(); - for (Map.Entry e : on_item.entrySet()) { - if (filter.test(e.getKey(), e.getValue())) { - removed.put(e.getKey(), e.getValue()); - } - } - for (Enchantment e : removed.keySet()) { - on_item.remove(e); - } - EnchantmentHelper.setEnchantments(on_item, stack); - stack.setRepairCost(getEnchantmentRepairCost(world, on_item)); - return removed; - } - - public static final class CraftingGrid implements CraftingContainer { - private static final CraftingGrid instance3x3 = new CraftingGrid(3, 3); - - final int _width; - - private CraftingGrid(int width, int height) { - _width=width; - } - - private void fill(Container grid) { - for (int i = 0; i < getContainerSize(); ++i) - setItem(i, i >= grid.getContainerSize() ? ItemStack.EMPTY : grid.getItem(i)); - } - - public List getRecipes(Level world, Container grid) { - fill(grid); - return world.getRecipeManager().getRecipesFor(RecipeType.CRAFTING, this, world); - } - - public List getRemainingItems(Level world, Container grid, CraftingRecipe recipe) { - fill(grid); - return recipe.getRemainingItems(this); - } - - public ItemStack getCraftingResult(Level world, Container grid, CraftingRecipe recipe) { - fill(grid); - return recipe.assemble(this, world.registryAccess()); - } - - @Override - public int getWidth() { - return _width; - } - - @Override - public int getHeight() { - return 0; - } - - @Override - public List getItems() { - return null; - } - - @Override - public int getContainerSize() { - return 0; - } - - @Override - public boolean isEmpty() { - return false; - } - - @Override - public ItemStack getItem(int i) { - return null; - } - - @Override - public ItemStack removeItem(int i, int i1) { - return null; - } - - @Override - public ItemStack removeItemNoUpdate(int i) { - return null; - } - - @Override - public void setItem(int i, ItemStack itemStack) { - - } - - @Override - public void setChanged() { - - } - - @Override - public boolean stillValid(Player player) { - return false; - } - - @Override - public void clearContent() { - - } - - @Override - public void fillStackedContents(StackedContents stackedContents) { - - } - } - - public static final class BrewingOutput { - public static final int DEFAULT_BREWING_TIME = 400; - public static final BrewingOutput EMPTY = new BrewingOutput(ItemStack.EMPTY, new SimpleContainer(1), new SimpleContainer(1), 0, 0, DEFAULT_BREWING_TIME); - public final ItemStack item; - public final Container potionInventory; - public final Container ingredientInventory; - public final int potionSlot; - public final int ingredientSlot; - public final int brewTime; - - public BrewingOutput(ItemStack output_potion, Container potion_inventory, Container ingredient_inventory, int potion_slot, int ingredient_slot, int time_needed) { - item = output_potion; - potionInventory = potion_inventory; - ingredientInventory = ingredient_inventory; - potionSlot = potion_slot; - ingredientSlot = ingredient_slot; - brewTime = time_needed; - } - - public static BrewingOutput find(Level world, Container potion_inventory, Container ingredient_inventory) { - for (int potion_slot = 0; potion_slot < potion_inventory.getContainerSize(); ++potion_slot) { - final ItemStack pstack = potion_inventory.getItem(potion_slot); - if (!isBrewingInput(world, pstack)) continue; - for (int ingredient_slot = 0; ingredient_slot < ingredient_inventory.getContainerSize(); ++ingredient_slot) { - final ItemStack istack = ingredient_inventory.getItem(ingredient_slot); - if ((!isBrewingIngredient(world, istack)) || (ingredient_slot == potion_slot) || (isBrewingFuel(world, istack))) - continue; - final ItemStack result = BrewingRecipeRegistry.getOutput(pstack, istack); - if (result.isEmpty()) continue; - return new BrewingOutput(result, potion_inventory, ingredient_inventory, potion_slot, ingredient_slot, DEFAULT_BREWING_TIME); - } - } - return BrewingOutput.EMPTY; - } - } - - -} \ No newline at end of file diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/Fluidics.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/Fluidics.java deleted file mode 100644 index 6ac9dbd..0000000 --- a/src/main/java/dev/zontreck/libzontreck/edlibmc/Fluidics.java +++ /dev/null @@ -1,485 +0,0 @@ -/* - * @file Fluidics.java - * @author Stefan Wilhelm (wile) - * @copyright (C) 2020 Stefan Wilhelm - * @license MIT (see https://opensource.org/licenses/MIT) - * - * General fluid handling functionality. - */ -package dev.zontreck.libzontreck.edlibmc; - -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.util.Mth; -import net.minecraft.util.Tuple; -import net.minecraft.world.InteractionHand; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.material.Fluid; -import net.minecraftforge.common.capabilities.Capability; -import net.minecraftforge.common.capabilities.ForgeCapabilities; -import net.minecraftforge.common.capabilities.ICapabilityProvider; -import net.minecraft.nbt.Tag; -import net.minecraftforge.common.util.LazyOptional; -import net.minecraftforge.fluids.FluidActionResult; -import net.minecraftforge.fluids.FluidStack; -import net.minecraftforge.fluids.FluidUtil; -import net.minecraftforge.fluids.IFluidTank; -import net.minecraftforge.fluids.capability.IFluidHandler; -import net.minecraftforge.fluids.capability.IFluidHandler.FluidAction; -import net.minecraftforge.fluids.capability.IFluidHandlerItem; -import net.minecraftforge.items.IItemHandler; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.util.function.BiConsumer; -import java.util.function.Function; -import java.util.function.Predicate; - - -public class Fluidics { - public static class SingleTankFluidHandler implements IFluidHandler { - private final IFluidTank tank_; - - public SingleTankFluidHandler(IFluidTank tank) { - tank_ = tank; - } - - @Override - public int getTanks() { - return 1; - } - - @Override - public FluidStack getFluidInTank(int tank) { - return tank_.getFluid(); - } - - @Override - public int getTankCapacity(int tank) { - return tank_.getCapacity(); - } - - @Override - public boolean isFluidValid(int tank, @Nonnull FluidStack stack) { - return tank_.isFluidValid(stack); - } - - @Override - public int fill(FluidStack resource, FluidAction action) { - return tank_.fill(resource, action); - } - - @Override - public FluidStack drain(FluidStack resource, FluidAction action) { - return tank_.drain(resource, action); - } - - @Override - public FluidStack drain(int maxDrain, FluidAction action) { - return tank_.drain(maxDrain, action); - } - } - - private static class SingleTankOutputFluidHandler implements IFluidHandler { - private final IFluidTank tank_; - - public SingleTankOutputFluidHandler(IFluidTank tank) { - tank_ = tank; - } - - @Override - public int getTanks() { - return 1; - } - - @Override - public FluidStack getFluidInTank(int tank) { - return tank_.getFluid().copy(); - } - - @Override - public int getTankCapacity(int tank) { - return tank_.getCapacity(); - } - - @Override - public boolean isFluidValid(int tank, @Nonnull FluidStack stack) { - return true; - } - - @Override - public int fill(FluidStack resource, FluidAction action) { - return 0; - } - - @Override - public FluidStack drain(FluidStack resource, FluidAction action) { - return tank_.drain(resource, action); - } - - @Override - public FluidStack drain(int maxDrain, FluidAction action) { - return tank_.drain(maxDrain, action); - } - } - - public static class Tank implements IFluidTank { - private Predicate validator_ = ((e) -> true); - private BiConsumer interaction_notifier_ = ((tank, diff) -> { - }); - private FluidStack fluid_ = FluidStack.EMPTY; - private int capacity_; - private int fill_rate_; - private int drain_rate_; - - public Tank(int capacity) { - this(capacity, capacity, capacity); - } - - public Tank(int capacity, int fill_rate, int drain_rate) { - this(capacity, fill_rate, drain_rate, e -> true); - } - - public Tank(int capacity, int fill_rate, int drain_rate, Predicate validator) { - capacity_ = capacity; - setMaxFillRate(fill_rate); - setMaxDrainRate(drain_rate); - setValidator(validator); - } - - public Tank load(CompoundTag nbt) { - if (nbt.contains("tank", Tag.TAG_COMPOUND)) { - setFluid(FluidStack.loadFluidStackFromNBT(nbt.getCompound("tank"))); - } else { - clear(); - } - return this; - } - - public CompoundTag save(CompoundTag nbt) { - if (!isEmpty()) { - nbt.put("tank", fluid_.writeToNBT(new CompoundTag())); - } - return nbt; - } - - public void reset() { - clear(); - } - - public Tank clear() { - setFluid(null); - return this; - } - - public int getCapacity() { - return capacity_; - } - - public Tank setCapacity(int capacity) { - capacity_ = capacity; - return this; - } - - public int getMaxDrainRate() { - return drain_rate_; - } - - public Tank setMaxDrainRate(int rate) { - drain_rate_ = Mth.clamp(rate, 0, capacity_); - return this; - } - - public int getMaxFillRate() { - return fill_rate_; - } - - public Tank setMaxFillRate(int rate) { - fill_rate_ = Mth.clamp(rate, 0, capacity_); - return this; - } - - public Tank setValidator(Predicate validator) { - validator_ = (validator != null) ? validator : ((e) -> true); - return this; - } - - public Tank setInteractionNotifier(BiConsumer notifier) { - interaction_notifier_ = (notifier != null) ? notifier : ((tank, diff) -> { - }); - return this; - } - - public LazyOptional createFluidHandler() { - return LazyOptional.of(() -> new Fluidics.SingleTankFluidHandler(this)); - } - - public LazyOptional createOutputFluidHandler() { - return LazyOptional.of(() -> new Fluidics.SingleTankOutputFluidHandler(this)); - } - - // IFluidTank ------------------------------------------------------------------------------------ - - @Nonnull - public FluidStack getFluid() { - return fluid_; - } - - public void setFluid(@Nullable FluidStack stack) { - fluid_ = (stack == null) ? FluidStack.EMPTY : stack; - } - - public int getFluidAmount() { - return fluid_.getAmount(); - } - - public boolean isEmpty() { - return fluid_.isEmpty(); - } - - public boolean isFull() { - return getFluidAmount() >= getCapacity(); - } - - public boolean isFluidValid(FluidStack stack) { - return validator_.test(stack); - } - - public boolean isFluidEqual(FluidStack stack) { - return (stack == null) ? (fluid_.isEmpty()) : fluid_.isFluidEqual(stack); - } - - @Override - public int fill(FluidStack fs, FluidAction action) { - if ((fs == null) || fs.isEmpty() || (!isFluidValid(fs))) { - return 0; - } else if (action.simulate()) { - if (fluid_.isEmpty()) return Math.min(capacity_, fs.getAmount()); - if (!fluid_.isFluidEqual(fs)) return 0; - return Math.min(capacity_ - fluid_.getAmount(), fs.getAmount()); - } else if (fluid_.isEmpty()) { - fluid_ = new FluidStack(fs, Math.min(capacity_, fs.getAmount())); - return fluid_.getAmount(); - } else if (!fluid_.isFluidEqual(fs)) { - return 0; - } else { - int amount = capacity_ - fluid_.getAmount(); - if (fs.getAmount() < amount) { - fluid_.grow(fs.getAmount()); - amount = fs.getAmount(); - } else { - fluid_.setAmount(capacity_); - } - if (amount != 0) interaction_notifier_.accept(this, amount); - return amount; - } - } - - @Nonnull - public FluidStack drain(int maxDrain) { - return drain(maxDrain, FluidAction.EXECUTE); - } - - @Nonnull - @Override - public FluidStack drain(FluidStack fs, FluidAction action) { - return ((fs.isEmpty()) || (!fs.isFluidEqual(fluid_))) ? FluidStack.EMPTY : drain(fs.getAmount(), action); - } - - @Nonnull - @Override - public FluidStack drain(int maxDrain, FluidAction action) { - final int amount = Math.min(fluid_.getAmount(), maxDrain); - final FluidStack stack = new FluidStack(fluid_, amount); - if ((amount > 0) && action.execute()) { - fluid_.shrink(amount); - if (fluid_.isEmpty()) fluid_ = FluidStack.EMPTY; - if (amount != 0) interaction_notifier_.accept(this, -amount); - } - return stack; - } - } - - // ------------------------------------------------------------------------------------------------------------------- - - public static @Nullable IFluidHandler handler(Level world, BlockPos pos, @Nullable Direction side) { - return FluidUtil.getFluidHandler(world, pos, side).orElse(null); - } - - /** - * Fills or drains items with fluid handlers from or into tile blocks with fluid handlers. - */ - public static boolean manualFluidHandlerInteraction(Level world, BlockPos pos, @Nullable Direction side, Player player, InteractionHand hand) { - return manualTrackedFluidHandlerInteraction(world, pos, side, player, hand) != null; - } - - public static boolean manualFluidHandlerInteraction(Player player, InteractionHand hand, IFluidHandler handler) { - return FluidUtil.interactWithFluidHandler(player, hand, handler); - } - - /** - * Fills or drains items with fluid handlers from or into tile blocks with fluid handlers. - * Returns the fluid and (possibly negative) amount that transferred from the item into the block. - */ - public static @Nullable Tuple manualTrackedFluidHandlerInteraction(Level world, BlockPos pos, @Nullable Direction side, Player player, InteractionHand hand) { - if (world.isClientSide()) return null; - final ItemStack held = player.getItemInHand(hand); - if (held.isEmpty()) return null; - final IFluidHandler fh = handler(world, pos, side); - if (fh == null) return null; - final IItemHandler ih = player.getCapability(ForgeCapabilities.ITEM_HANDLER).orElse(null); - if (ih == null) return null; - FluidActionResult far = FluidUtil.tryFillContainerAndStow(held, fh, ih, Integer.MAX_VALUE, player, true); - if (!far.isSuccess()) far = FluidUtil.tryEmptyContainerAndStow(held, fh, ih, Integer.MAX_VALUE, player, true); - if (!far.isSuccess()) return null; - final ItemStack rstack = far.getResult().copy(); - player.setItemInHand(hand, far.getResult()); - final IFluidHandler fh_before = FluidUtil.getFluidHandler(held).orElse(null); - final IFluidHandler fh_after = FluidUtil.getFluidHandler(rstack).orElse(null); - if ((fh_before == null) || (fh_after == null) || (fh_after.getTanks() != fh_before.getTanks())) - return null; // should not be, but y'never know. - for (int i = 0; i < fh_before.getTanks(); ++i) { - final int vol_before = fh_before.getFluidInTank(i).getAmount(); - final int vol_after = fh_after.getFluidInTank(i).getAmount(); - if (vol_before != vol_after) { - return new Tuple<>( - (vol_before > 0) ? (fh_before.getFluidInTank(i).getFluid()) : (fh_after.getFluidInTank(i).getFluid()), - (vol_before - vol_after) - ); - } - } - return null; - } - - public static boolean manualFluidHandlerInteraction(Player player, InteractionHand hand, Level world, BlockPos pos, @Nullable Direction side) { - return FluidUtil.interactWithFluidHandler(player, hand, world, pos, side); - } - - public static int fill(Level world, BlockPos pos, Direction side, FluidStack fs, FluidAction action) { - IFluidHandler fh = FluidUtil.getFluidHandler(world, pos, side).orElse(null); - return (fh == null) ? (0) : (fh.fill(fs, action)); - } - - public static int fill(Level world, BlockPos pos, Direction side, FluidStack fs) { - return fill(world, pos, side, fs, FluidAction.EXECUTE); - } - - /** - * Fluid tank access when itemized. - */ - public static class FluidContainerItemCapabilityWrapper implements IFluidHandlerItem, ICapabilityProvider { - private final LazyOptional handler_ = LazyOptional.of(() -> this); - private final Function nbt_getter_; - private final BiConsumer nbt_setter_; - private final Predicate validator_; - private final ItemStack container_; - private final int capacity_; - private final int transfer_rate_; - - public FluidContainerItemCapabilityWrapper(ItemStack container, int capacity, int transfer_rate, - Function nbt_getter, - BiConsumer nbt_setter, - Predicate validator) { - container_ = container; - capacity_ = capacity; - transfer_rate_ = transfer_rate; - nbt_getter_ = nbt_getter; - nbt_setter_ = nbt_setter; - validator_ = (validator != null) ? validator : (e -> true); - } - - @Override - public LazyOptional getCapability(Capability capability, @Nullable Direction side) { - return (capability == ForgeCapabilities.FLUID_HANDLER) ? handler_.cast() : LazyOptional.empty(); - } - - protected FluidStack readnbt() { - final CompoundTag nbt = nbt_getter_.apply(container_); - return ((nbt == null) || (nbt.isEmpty())) ? FluidStack.EMPTY : FluidStack.loadFluidStackFromNBT(nbt); - } - - protected void writenbt(FluidStack fs) { - CompoundTag nbt = new CompoundTag(); - if (!fs.isEmpty()) fs.writeToNBT(nbt); - nbt_setter_.accept(container_, nbt); - } - - @Override - public ItemStack getContainer() { - return container_; - } - - @Override - public int getTanks() { - return 1; - } - - @Override - public FluidStack getFluidInTank(int tank) { - return readnbt(); - } - - @Override - public int getTankCapacity(int tank) { - return capacity_; - } - - @Override - public boolean isFluidValid(int tank, FluidStack fs) { - return isFluidValid(fs); - } - - public boolean isFluidValid(FluidStack fs) { - return validator_.test(fs); - } - - @Override - public int fill(FluidStack fs, FluidAction action) { - if ((fs.isEmpty()) || (!isFluidValid(fs) || (container_.getCount() != 1))) return 0; - FluidStack tank = readnbt(); - final int amount = Math.min(Math.min(fs.getAmount(), transfer_rate_), capacity_ - tank.getAmount()); - if (amount <= 0) return 0; - if (tank.isEmpty()) { - if (action.execute()) { - tank = new FluidStack(fs.getFluid(), amount, fs.getTag()); - writenbt(tank); - } - } else { - if (!tank.isFluidEqual(fs)) { - return 0; - } else if (action.execute()) { - tank.grow(amount); - writenbt(tank); - } - } - return amount; - } - - @Override - public FluidStack drain(FluidStack fs, FluidAction action) { - if ((fs.isEmpty()) || (container_.getCount() != 1)) return FluidStack.EMPTY; - final FluidStack tank = readnbt(); - if ((!tank.isEmpty()) && (!tank.isFluidEqual(fs))) return FluidStack.EMPTY; - return drain(fs.getAmount(), action); - } - - @Override - public FluidStack drain(int max, FluidAction action) { - if ((max <= 0) || (container_.getCount() != 1)) return FluidStack.EMPTY; - FluidStack tank = readnbt(); - if (tank.isEmpty()) return FluidStack.EMPTY; - final int amount = Math.min(Math.min(tank.getAmount(), max), transfer_rate_); - final FluidStack fs = tank.copy(); - fs.setAmount(amount); - if (action.execute()) { - tank.shrink(amount); - writenbt(tank); - } - return fs; - } - } - -} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/Guis.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/Guis.java deleted file mode 100644 index 1374a19..0000000 --- a/src/main/java/dev/zontreck/libzontreck/edlibmc/Guis.java +++ /dev/null @@ -1,466 +0,0 @@ -/* - * @file Guis.java - * @author Stefan Wilhelm (wile) - * @copyright (C) 2020 Stefan Wilhelm - * @license MIT (see https://opensource.org/licenses/MIT) - * - * Gui Wrappers and Widgets. - */ -package dev.zontreck.libzontreck.edlibmc; - -import com.mojang.blaze3d.platform.Window; -import com.mojang.blaze3d.systems.RenderSystem; -import com.mojang.blaze3d.vertex.PoseStack; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.Font; -import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.gui.components.AbstractWidget; -import net.minecraft.client.gui.narration.NarrationElementOutput; -import net.minecraft.client.gui.screens.Screen; -import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; -import net.minecraft.client.renderer.GameRenderer; -import net.minecraft.client.renderer.entity.ItemRenderer; -import net.minecraft.client.sounds.SoundManager; -import net.minecraft.network.chat.Component; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.util.Mth; -import net.minecraft.world.entity.player.Inventory; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.inventory.AbstractContainerMenu; -import net.minecraft.world.item.ItemStack; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.api.distmarker.OnlyIn; - -import java.util.Arrays; -import java.util.function.Consumer; -import java.util.function.Function; - - -public class Guis { - // ------------------------------------------------------------------------------------------------------------------- - // Gui base - // ------------------------------------------------------------------------------------------------------------------- - - @OnlyIn(Dist.CLIENT) - public static abstract class ContainerGui extends AbstractContainerScreen { - protected final ResourceLocation background_image_; - protected final Player player_; - protected final Guis.BackgroundImage gui_background_; - protected final TooltipDisplay tooltip_ = new TooltipDisplay(); - - public ContainerGui(T menu, Inventory player_inv, Component title, String background_image, int width, int height) { - super(menu, player_inv, title); - this.background_image_ = new ResourceLocation(Auxiliaries.modid(), background_image); - this.player_ = player_inv.player; - this.imageWidth = width; - this.imageHeight = height; - gui_background_ = new Guis.BackgroundImage(background_image_, width, height, Coord2d.ORIGIN); - } - - public ContainerGui(T menu, Inventory player_inv, Component title, String background_image) { - super(menu, player_inv, title); - this.background_image_ = new ResourceLocation(Auxiliaries.modid(), background_image); - this.player_ = player_inv.player; - gui_background_ = new Guis.BackgroundImage(background_image_, imageWidth, imageHeight, Coord2d.ORIGIN); - } - - @Override - public void init() { - super.init(); - gui_background_.init(this, Coord2d.ORIGIN).show(); - } - - @Override - public void render(GuiGraphics mx, int mouseX, int mouseY, float partialTicks) { - renderBackground(mx); - super.render(mx, mouseX, mouseY, partialTicks); - if (!tooltip_.render(mx, this, mouseX, mouseY)) renderTooltip(mx, mouseX, mouseY); - } - - @Override - protected void renderLabels(GuiGraphics mx, int x, int y) { - } - - @Override - @SuppressWarnings("deprecation") - protected final void renderBg(GuiGraphics mx, float partialTicks, int mouseX, int mouseY) { - RenderSystem.setShader(GameRenderer::getPositionTexShader); - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); - RenderSystem.enableBlend(); - RenderSystem.defaultBlendFunc(); - RenderSystem.enableDepthTest(); - gui_background_.draw(mx, this); - renderBgWidgets(mx, partialTicks, mouseX, mouseY); - RenderSystem.disableBlend(); - } - - public final ResourceLocation getBackgroundImage() { - return background_image_; - } - - protected void renderBgWidgets(GuiGraphics mx, float partialTicks, int mouseX, int mouseY) { - } - - protected void renderItemTemplate(GuiGraphics mx, ItemStack stack, int x, int y) { - final int x0 = getGuiLeft(); - final int y0 = getGuiTop(); - - mx.renderFakeItem(stack, x0 + x, y0 + y); - RenderSystem.disableColorLogicOp(); //RenderSystem.disableColorMaterial(); - RenderSystem.enableDepthTest(); //RenderSystem.enableAlphaTest(); - RenderSystem.defaultBlendFunc(); - RenderSystem.enableBlend(); - RenderSystem.colorMask(true, true, true, true); - RenderSystem.setShaderColor(0.7f, 0.7f, 0.7f, 0.8f); - RenderSystem.setShaderTexture(0, background_image_); - mx.blit(background_image_,x0 + x, y0 + y, x, y, 16, 16); - RenderSystem.setShaderColor(1f, 1f, 1f, 1f); - } - } - - // ------------------------------------------------------------------------------------------------------------------- - // Gui elements - // ------------------------------------------------------------------------------------------------------------------- - - @OnlyIn(Dist.CLIENT) - public static class Coord2d { - public static final Coord2d ORIGIN = new Coord2d(0, 0); - public final int x, y; - - public Coord2d(int x, int y) { - this.x = x; - this.y = y; - } - - public static Coord2d of(int x, int y) { - return new Coord2d(x, y); - } - - public String toString() { - return "[" + x + "," + y + "]"; - } - } - - @OnlyIn(Dist.CLIENT) - public static class UiWidget extends AbstractWidget { - protected static final Component EMPTY_TEXT = Component.literal(""); - protected static final Function NO_TOOLTIP = (uiw) -> EMPTY_TEXT; - - private final Minecraft mc_; - private Function tooltip_ = NO_TOOLTIP; - private Screen parent_; - - public UiWidget(int x, int y, int width, int height, Component title) { - super(x, y, width, height, title); - mc_ = Minecraft.getInstance(); - } - - public UiWidget init(Screen parent) { - this.parent_ = parent; - this.setX(((parent instanceof AbstractContainerScreen) ? ((AbstractContainerScreen) parent).getGuiLeft() : 0)); - this.setY(((parent instanceof AbstractContainerScreen) ? ((AbstractContainerScreen) parent).getGuiTop() : 0)); - return this; - } - - public UiWidget init(Screen parent, Coord2d position) { - this.parent_ = parent; - this.setX(position.x + ((parent instanceof AbstractContainerScreen) ? ((AbstractContainerScreen) parent).getGuiLeft() : 0)); - this.setY(position.y + ((parent instanceof AbstractContainerScreen) ? ((AbstractContainerScreen) parent).getGuiTop() : 0)); - return this; - } - - public final UiWidget tooltip(Function tip) { - tooltip_ = tip; - return this; - } - - public final UiWidget tooltip(Component tip) { - tooltip_ = (o) -> tip; - return this; - } - - public final int getWidth() { - return this.width; - } - - @Override - protected void updateWidgetNarration(NarrationElementOutput narrationElementOutput) { - - } - - public final int getHeight() { - return this.height; - } - - public Coord2d getMousePosition() { - final Window win = mc_.getWindow(); - return Coord2d.of( - Mth.clamp(((int) (mc_.mouseHandler.xpos() * (double) win.getGuiScaledWidth() / (double) win.getScreenWidth())) - this.getX(), -1, this.width + 1), - Mth.clamp(((int) (mc_.mouseHandler.ypos() * (double) win.getGuiScaledHeight() / (double) win.getScreenHeight())) - this.getY(), -1, this.height + 1) - ); - } - - protected final Coord2d screenCoordinates(Coord2d xy, boolean reverse) { - return (reverse) ? (Coord2d.of(xy.x + getX(), xy.y + getY())) : (Coord2d.of(xy.x - getX(), xy.y - getY())); - } - - public UiWidget show() { - visible = true; - return this; - } - - public UiWidget hide() { - visible = false; - return this; - } - - @Override - public void renderWidget(GuiGraphics mxs, int mouseX, int mouseY, float partialTicks) { - //super.renderWidget(mxs, mouseX, mouseY, partialTicks); - if (isHovered) renderToolTip(mxs, mouseX, mouseY); - } - - @SuppressWarnings("all") - public void renderToolTip(GuiGraphics mx, int mouseX, int mouseY) { - if (!visible || (!active) || (tooltip_ == NO_TOOLTIP)) return; - final Component tip = tooltip_.apply(this); - if (tip.getString().trim().isEmpty()) return; - mx.renderTooltip(mc_.font, Arrays.asList(tip.getVisualOrderText()), mouseX, mouseY); - } - } - - @OnlyIn(Dist.CLIENT) - public static class HorizontalProgressBar extends UiWidget { - private final Coord2d texture_position_base_; - private final Coord2d texture_position_filled_; - private final ResourceLocation atlas_; - private double progress_max_ = 100; - private double progress_ = 0; - - public HorizontalProgressBar(ResourceLocation atlas, int width, int height, Coord2d base_texture_xy, Coord2d filled_texture_xy) { - super(0, 0, width, height, EMPTY_TEXT); - atlas_ = atlas; - texture_position_base_ = base_texture_xy; - texture_position_filled_ = filled_texture_xy; - } - - public HorizontalProgressBar setProgress(double progress) { - progress_ = Mth.clamp(progress, 0, progress_max_); - return this; - } - - public double getProgress() { - return progress_; - } - - public HorizontalProgressBar setMaxProgress(double progress) { - progress_max_ = Math.max(progress, 0); - return this; - } - - public double getMaxProgress() { - return progress_max_; - } - - public HorizontalProgressBar show() { - visible = true; - return this; - } - - public HorizontalProgressBar hide() { - visible = false; - return this; - } - - @Override - public void playDownSound(SoundManager handler) { - } - - @Override - public void renderWidget(GuiGraphics mxs, int mouseX, int mouseY, float partialTicks) { - RenderSystem.setShaderTexture(0, atlas_); - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, this.alpha); - RenderSystem.enableBlend(); - RenderSystem.defaultBlendFunc(); - RenderSystem.enableDepthTest(); - mxs.blit(atlas_, getX(), getY(), texture_position_base_.x, texture_position_base_.y, width, height); - if ((progress_max_ > 0) && (progress_ > 0)) { - int w = Mth.clamp((int) Math.round((progress_ * width) / progress_max_), 0, width); - mxs.blit(atlas_, getX(), getY(), texture_position_filled_.x, texture_position_filled_.y, w, height); - } - if (isHovered) renderToolTip(mxs, mouseX, mouseY); - } - } - - @OnlyIn(Dist.CLIENT) - public static class BackgroundImage extends UiWidget { - private final ResourceLocation atlas_; - private final Coord2d atlas_position_; - public boolean visible; - - public BackgroundImage(ResourceLocation atlas, int width, int height, Coord2d atlas_position) { - super(0, 0, width, height, EMPTY_TEXT); - atlas_ = atlas; - atlas_position_ = atlas_position; - this.width = width; - this.height = height; - visible = true; - } - - public void draw(GuiGraphics mx, Screen parent) { - if (!visible) return; - RenderSystem.setShaderTexture(0, atlas_); - mx.blit(atlas_, getX(), getY(), atlas_position_.x, atlas_position_.y, width, height); - } - } - - @OnlyIn(Dist.CLIENT) - public static class CheckBox extends UiWidget { - private final Coord2d texture_position_off_; - private final Coord2d texture_position_on_; - private final ResourceLocation atlas_; - private boolean checked_ = false; - private Consumer on_click_ = (checkbox) -> { - }; - - public CheckBox(ResourceLocation atlas, int width, int height, Coord2d atlas_texture_position_off, Coord2d atlas_texture_position_on) { - super(0, 0, width, height, EMPTY_TEXT); - texture_position_off_ = atlas_texture_position_off; - texture_position_on_ = atlas_texture_position_on; - atlas_ = atlas; - } - - public boolean checked() { - return checked_; - } - - public CheckBox checked(boolean on) { - checked_ = on; - return this; - } - - public CheckBox onclick(Consumer action) { - on_click_ = action; - return this; - } - - @Override - public void onClick(double mouseX, double mouseY) { - checked_ = !checked_; - on_click_.accept(this); - } - - @Override - public void renderWidget(GuiGraphics mxs, int mouseX, int mouseY, float partialTicks) { - RenderSystem.setShader(GameRenderer::getPositionTexShader); - RenderSystem.setShaderTexture(0, atlas_); - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, this.alpha); - RenderSystem.enableBlend(); - RenderSystem.defaultBlendFunc(); - RenderSystem.enableDepthTest(); - Coord2d pos = checked_ ? texture_position_on_ : texture_position_off_; - mxs.blit(atlas_, getX(), getY(), pos.x, pos.y, width, height); - if (isHovered) renderToolTip(mxs, mouseX, mouseY); - } - } - - @OnlyIn(Dist.CLIENT) - public static class ImageButton extends UiWidget { - private final Coord2d texture_position_; - private final ResourceLocation atlas_; - private Consumer on_click_ = (bt) -> { - }; - - - public ImageButton(ResourceLocation atlas, int width, int height, Coord2d atlas_texture_position) { - super(0, 0, width, height, Component.empty()); - texture_position_ = atlas_texture_position; - atlas_ = atlas; - } - - public ImageButton onclick(Consumer action) { - on_click_ = action; - return this; - } - - @Override - public void onClick(double mouseX, double mouseY) { - on_click_.accept(this); - } - - @Override - public void renderWidget(GuiGraphics mxs, int mouseX, int mouseY, float partialTicks) { - RenderSystem.setShader(GameRenderer::getPositionTexShader); - RenderSystem.setShaderTexture(0, atlas_); - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, this.alpha); - RenderSystem.enableBlend(); - RenderSystem.defaultBlendFunc(); - RenderSystem.enableDepthTest(); - Coord2d pos = texture_position_; - mxs.blit(atlas_, getX(), getY(), pos.x, pos.y, width, height); - if (isHovered) renderToolTip(mxs, mouseX, mouseY); - } - } - - @OnlyIn(Dist.CLIENT) - public static class Image extends UiWidget { - private final Coord2d texture_position_; - private final ResourceLocation atlas_; - - public Image(ResourceLocation atlas, int width, int height, Coord2d atlas_texture_position) { - super(0, 0, width, height, Component.empty()); - texture_position_ = atlas_texture_position; - atlas_ = atlas; - } - - @Override - public void onClick(double mouseX, double mouseY) { - } - - @Override - public void renderWidget(GuiGraphics mxs, int mouseX, int mouseY, float partialTicks) { - RenderSystem.setShader(GameRenderer::getPositionTexShader); - RenderSystem.setShaderTexture(0, atlas_); - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, this.alpha); - RenderSystem.enableBlend(); - RenderSystem.defaultBlendFunc(); - RenderSystem.enableDepthTest(); - Coord2d pos = texture_position_; - mxs.blit(atlas_, getX(), getY(), pos.x, pos.y, width, height); - if (isHovered) renderToolTip(mxs, mouseX, mouseY); - } - } - - @OnlyIn(Dist.CLIENT) - public static class TextBox extends net.minecraft.client.gui.components.EditBox { - public TextBox(int x, int y, int width, int height, Component title, Font font) { - super(font, x, y, width, height, title); - setBordered(false); - } - - public TextBox withMaxLength(int len) { - super.setMaxLength(len); - return this; - } - - public TextBox withBordered(boolean b) { - super.setBordered(b); - return this; - } - - public TextBox withValue(String s) { - super.setValue(s); - return this; - } - - public TextBox withEditable(boolean e) { - super.setEditable(e); - return this; - } - - public TextBox withResponder(Consumer r) { - super.setResponder(r); - return this; - } - } -} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/Inventories.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/Inventories.java deleted file mode 100644 index e7442e0..0000000 --- a/src/main/java/dev/zontreck/libzontreck/edlibmc/Inventories.java +++ /dev/null @@ -1,1134 +0,0 @@ -/* - * @file Inventories.java - * @author Stefan Wilhelm (wile) - * @copyright (C) 2020 Stefan Wilhelm - * @license MIT (see https://opensource.org/licenses/MIT) - * - * General inventory item handling functionality. - */ -package dev.zontreck.libzontreck.edlibmc; - -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.core.NonNullList; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.util.Mth; -import net.minecraft.world.*; -import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.item.ItemEntity; -import net.minecraft.world.entity.player.Inventory; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraft.world.phys.AABB; -import net.minecraft.world.phys.Vec3; -import net.minecraft.nbt.Tag; -import net.minecraftforge.common.capabilities.ForgeCapabilities; -import net.minecraftforge.common.util.LazyOptional; -import net.minecraftforge.items.IItemHandler; -import net.minecraftforge.items.ItemHandlerHelper; -import net.minecraftforge.items.wrapper.InvWrapper; -import net.minecraftforge.items.wrapper.PlayerMainInvWrapper; -import net.minecraftforge.items.wrapper.SidedInvWrapper; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.util.*; -import java.util.function.BiConsumer; -import java.util.function.BiFunction; -import java.util.function.BiPredicate; -import java.util.function.Consumer; -import java.util.stream.Collectors; -import java.util.stream.IntStream; -import java.util.stream.Stream; - - -public class Inventories { - public static boolean areItemStacksIdentical(ItemStack a, ItemStack b) { - return (a.getItem() == b.getItem()) && ItemStack.isSameItemSameTags(a, b); - } - - public static boolean areItemStacksDifferent(ItemStack a, ItemStack b) { - return (a.getItem() != b.getItem()) || (!ItemStack.isSameItemSameTags(a, b)); - } - - public static IItemHandler itemhandler(Level world, BlockPos pos, @Nullable Direction side) { - BlockEntity te = world.getBlockEntity(pos); - if (te == null) return null; - IItemHandler ih = te.getCapability(ForgeCapabilities.ITEM_HANDLER, side).orElse(null); - if (ih != null) return ih; - if ((side != null) && (te instanceof WorldlyContainer)) return new SidedInvWrapper((WorldlyContainer) te, side); - if (te instanceof Container) return new InvWrapper((Container) te); - return null; - } - - public static IItemHandler itemhandler(Level world, BlockPos pos, @Nullable Direction side, boolean including_entities) { - IItemHandler ih = itemhandler(world, pos, side); - if (ih != null) return ih; - if (!including_entities) return null; - Entity entity = world.getEntitiesOfClass(Entity.class, new AABB(pos), (e) -> (e instanceof Container)).stream().findFirst().orElse(null); - return (entity == null) ? (null) : (itemhandler(entity, side)); - } - - public static IItemHandler itemhandler(Player player) { - return new PlayerMainInvWrapper(player.getInventory()); - } - - public static IItemHandler itemhandler(Entity entity) { - return itemhandler(entity, null); - } - - public static IItemHandler itemhandler(Entity entity, @Nullable Direction side) { - if (entity == null) return null; - final IItemHandler ih = entity.getCapability(ForgeCapabilities.ITEM_HANDLER, side).orElse(null); - if (ih != null) return ih; - if (entity instanceof Container container) return (new InvWrapper(container)); - return null; - } - - public static boolean insertionPossible(Level world, BlockPos pos, @Nullable Direction side, boolean including_entities) { - return itemhandler(world, pos, side, including_entities) != null; - } - - public static ItemStack insert(IItemHandler handler, ItemStack stack, boolean simulate) { - return ItemHandlerHelper.insertItemStacked(handler, stack, simulate); - } - - public static ItemStack insert(BlockEntity te, @Nullable Direction side, ItemStack stack, boolean simulate) { - if (te == null) return stack; - IItemHandler hnd = te.getCapability(ForgeCapabilities.ITEM_HANDLER, side).orElse(null); - if (hnd == null) { - if ((side != null) && (te instanceof WorldlyContainer)) { - hnd = new SidedInvWrapper((WorldlyContainer) te, side); - } else if (te instanceof Container) { - hnd = new InvWrapper((Container) te); - } - } - return (hnd == null) ? stack : insert(hnd, stack, simulate); - } - - public static ItemStack insert(Level world, BlockPos pos, @Nullable Direction side, ItemStack stack, boolean simulate, boolean including_entities) { - return insert(itemhandler(world, pos, side, including_entities), stack, simulate); - } - - public static ItemStack insert(Level world, BlockPos pos, @Nullable Direction side, ItemStack stack, boolean simulate) { - return insert(world, pos, side, stack, simulate, false); - } - - public static ItemStack extract(IItemHandler inventory, @Nullable ItemStack match, int amount, boolean simulate) { - if ((inventory == null) || (amount <= 0) || ((match != null) && (match.isEmpty()))) return ItemStack.EMPTY; - final int max = inventory.getSlots(); - ItemStack out_stack = ItemStack.EMPTY; - for (int i = 0; i < max; ++i) { - final ItemStack stack = inventory.getStackInSlot(i); - if (stack.isEmpty()) continue; - if (out_stack.isEmpty()) { - if ((match != null) && areItemStacksDifferent(stack, match)) continue; - out_stack = inventory.extractItem(i, amount, simulate); - } else if (areItemStacksIdentical(stack, out_stack)) { - ItemStack es = inventory.extractItem(i, (amount - out_stack.getCount()), simulate); - out_stack.grow(es.getCount()); - } - if (out_stack.getCount() >= amount) break; - } - return out_stack; - } - - private static ItemStack checked(ItemStack stack) { - return stack.isEmpty() ? ItemStack.EMPTY : stack; - } - - public static Container copyOf(Container src) { - final int size = src.getContainerSize(); - SimpleContainer dst = new SimpleContainer(size); - for (int i = 0; i < size; ++i) dst.setItem(i, src.getItem(i).copy()); - return dst; - } - - //-------------------------------------------------------------------------------------------------------------------- - - public static ItemStack insert(InventoryRange[] to_ranges, ItemStack stack) { - ItemStack remaining = stack.copy(); - for (InventoryRange range : to_ranges) { - remaining = range.insert(remaining, false, 0, false, true); - if (remaining.isEmpty()) return remaining; - } - return remaining; - } - - //-------------------------------------------------------------------------------------------------------------------- - - public static class MappedItemHandler implements IItemHandler { - private final BiPredicate extraction_predicate_; - private final BiPredicate insertion_predicate_; - private final BiConsumer insertion_notifier_; - private final BiConsumer extraction_notifier_; - private final List slot_map_; - private final Container inv_; - - public MappedItemHandler(Container inv, BiPredicate extraction_predicate, BiPredicate insertion_predicate, BiConsumer insertion_notifier, BiConsumer extraction_notifier) { - inv_ = inv; - extraction_predicate_ = extraction_predicate; - insertion_predicate_ = insertion_predicate; - insertion_notifier_ = insertion_notifier; - extraction_notifier_ = extraction_notifier; - slot_map_ = IntStream.range(0, inv.getContainerSize()).boxed().collect(Collectors.toList()); - } - - public MappedItemHandler(Container inv, List slot_map, BiPredicate extraction_predicate, BiPredicate insertion_predicate, BiConsumer insertion_notifier, BiConsumer extraction_notifier) { - inv_ = inv; - extraction_predicate_ = extraction_predicate; - insertion_predicate_ = insertion_predicate; - insertion_notifier_ = insertion_notifier; - extraction_notifier_ = extraction_notifier; - slot_map_ = slot_map; - } - - public MappedItemHandler(Container inv, List slot_map, BiPredicate extraction_predicate, BiPredicate insertion_predicate) { - this(inv, slot_map, extraction_predicate, insertion_predicate, (i, s) -> { - }, (i, s) -> { - }); - } - - public MappedItemHandler(Container inv, BiPredicate extraction_predicate, BiPredicate insertion_predicate) { - this(inv, IntStream.range(0, inv.getContainerSize()).boxed().collect(Collectors.toList()), extraction_predicate, insertion_predicate); - } - - public MappedItemHandler(Container inv) { - this(inv, (i, s) -> true, (i, s) -> true); - } - - @Override - public int hashCode() { - return inv_.hashCode(); - } - - @Override - public boolean equals(Object o) { - return (o == this) || ((o != null) && (getClass() == o.getClass()) && (inv_.equals(((MappedItemHandler) o).inv_))); - } - - // IItemHandler ----------------------------------------------------------------------------------------------- - - @Override - public int getSlots() { - return slot_map_.size(); - } - - @Override - @Nonnull - public ItemStack getStackInSlot(int slot) { - return (slot >= slot_map_.size()) ? ItemStack.EMPTY : inv_.getItem(slot_map_.get(slot)); - } - - @Override - public int getSlotLimit(int slot) { - return inv_.getMaxStackSize(); - } - - @Override - public boolean isItemValid(int slot, @Nonnull ItemStack stack) { - if (slot >= slot_map_.size()) return false; - slot = slot_map_.get(slot); - return insertion_predicate_.test(slot, stack) && inv_.canPlaceItem(slot, stack); - } - - @Override - @Nonnull - public ItemStack insertItem(int slot, @Nonnull ItemStack stack, boolean simulate) { - if (stack.isEmpty()) return ItemStack.EMPTY; - if (slot >= slot_map_.size()) return stack; - slot = slot_map_.get(slot); - if (!insertion_predicate_.test(slot, stack)) return stack; - if (!inv_.canPlaceItem(slot, stack)) return stack; - ItemStack sst = inv_.getItem(slot); - final int slot_limit = inv_.getMaxStackSize(); - if (!sst.isEmpty()) { - if (sst.getCount() >= Math.min(sst.getMaxStackSize(), slot_limit)) return stack; - if (!ItemHandlerHelper.canItemStacksStack(stack, sst)) return stack; - final int limit = Math.min(stack.getMaxStackSize(), slot_limit) - sst.getCount(); - if (stack.getCount() <= limit) { - if (!simulate) { - stack = stack.copy(); - stack.grow(sst.getCount()); - inv_.setItem(slot, stack); - inv_.setChanged(); - insertion_notifier_.accept(slot, sst); - } - return ItemStack.EMPTY; - } else { - stack = stack.copy(); - if (simulate) { - stack.shrink(limit); - } else { - final ItemStack diff = stack.split(limit); - sst.grow(diff.getCount()); - inv_.setItem(slot, sst); - inv_.setChanged(); - insertion_notifier_.accept(slot, diff); - } - return stack; - } - } else { - final int limit = Math.min(slot_limit, stack.getMaxStackSize()); - if (stack.getCount() >= limit) { - stack = stack.copy(); - final ItemStack ins = stack.split(limit); - if (!simulate) { - inv_.setItem(slot, ins); - inv_.setChanged(); - insertion_notifier_.accept(slot, ins.copy()); - } - if (stack.isEmpty()) { - stack = ItemStack.EMPTY; - } - return stack; - } else { - if (!simulate) { - inv_.setItem(slot, stack.copy()); - inv_.setChanged(); - insertion_notifier_.accept(slot, stack.copy()); - } - return ItemStack.EMPTY; - } - } - } - - @Override - public ItemStack extractItem(int slot, int amount, boolean simulate) { - if (amount <= 0) return ItemStack.EMPTY; - if (slot >= slot_map_.size()) return ItemStack.EMPTY; - slot = slot_map_.get(slot); - ItemStack stack = inv_.getItem(slot); - if (!extraction_predicate_.test(slot, stack)) return ItemStack.EMPTY; - if (simulate) { - stack = stack.copy(); - if (amount < stack.getCount()) stack.setCount(amount); - } else { - stack = inv_.removeItem(slot, Math.min(stack.getCount(), amount)); - inv_.setChanged(); - extraction_notifier_.accept(slot, stack.copy()); - } - return stack; - } - - // Factories -------------------------------------------------------------------------------------------- - - public static LazyOptional createGenericHandler(Container inv, BiPredicate extraction_predicate, BiPredicate insertion_predicate, BiConsumer insertion_notifier, BiConsumer extraction_notifier) { - return LazyOptional.of(() -> new MappedItemHandler(inv, extraction_predicate, insertion_predicate, insertion_notifier, extraction_notifier)); - } - - public static LazyOptional createGenericHandler(Container inv, BiPredicate extraction_predicate, BiPredicate insertion_predicate, BiConsumer insertion_notifier, BiConsumer extraction_notifier, List slot_map) { - return LazyOptional.of(() -> new MappedItemHandler(inv, slot_map, extraction_predicate, insertion_predicate, insertion_notifier, extraction_notifier)); - } - - public static LazyOptional createGenericHandler(Container inv, BiPredicate extraction_predicate, BiPredicate insertion_predicate, List slot_map) { - return LazyOptional.of(() -> new MappedItemHandler(inv, slot_map, extraction_predicate, insertion_predicate)); - } - - public static LazyOptional createGenericHandler(Container inv, BiPredicate extraction_predicate, BiPredicate insertion_predicate) { - return LazyOptional.of(() -> new MappedItemHandler(inv, extraction_predicate, insertion_predicate)); - } - - public static LazyOptional createGenericHandler(Container inv) { - return LazyOptional.of(() -> new MappedItemHandler(inv)); - } - - public static LazyOptional createExtractionHandler(Container inv, BiPredicate extraction_predicate, List slot_map) { - return LazyOptional.of(() -> new MappedItemHandler(inv, slot_map, extraction_predicate, (i, s) -> false)); - } - - public static LazyOptional createExtractionHandler(Container inv, BiPredicate extraction_predicate) { - return LazyOptional.of(() -> new MappedItemHandler(inv, extraction_predicate, (i, s) -> false)); - } - - public static LazyOptional createExtractionHandler(Container inv, Integer... slots) { - return LazyOptional.of(() -> new MappedItemHandler(inv, Arrays.asList(slots), (i, s) -> true, (i, s) -> false)); - } - - public static LazyOptional createExtractionHandler(Container inv) { - return LazyOptional.of(() -> new MappedItemHandler(inv, (i, s) -> true, (i, s) -> false)); - } - - public static LazyOptional createInsertionHandler(Container inv, BiPredicate insertion_predicate, List slot_map) { - return LazyOptional.of(() -> new MappedItemHandler(inv, slot_map, (i, s) -> false, insertion_predicate)); - } - - public static LazyOptional createInsertionHandler(Container inv, Integer... slots) { - return LazyOptional.of(() -> new MappedItemHandler(inv, Arrays.asList(slots), (i, s) -> false, (i, s) -> true)); - } - - public static LazyOptional createInsertionHandler(Container inv, BiPredicate insertion_predicate) { - return LazyOptional.of(() -> new MappedItemHandler(inv, (i, s) -> false, insertion_predicate)); - } - - public static LazyOptional createInsertionHandler(Container inv) { - return LazyOptional.of(() -> new MappedItemHandler(inv, (i, s) -> false, (i, s) -> true)); - } - } - - //-------------------------------------------------------------------------------------------------------------------- - - public static class InventoryRange implements Container, Iterable { - protected final Container inventory_; - protected final int offset_, size_, num_rows; - protected int max_stack_size_ = 64; - protected BiPredicate validator_ = (index, stack) -> true; - - public static InventoryRange fromPlayerHotbar(Player player) { - return new InventoryRange(player.getInventory(), 0, 9, 1); - } - - public static InventoryRange fromPlayerStorage(Player player) { - return new InventoryRange(player.getInventory(), 9, 27, 3); - } - - public static InventoryRange fromPlayerInventory(Player player) { - return new InventoryRange(player.getInventory(), 0, 36, 4); - } - - public InventoryRange(Container inventory, int offset, int size, int num_rows) { - this.inventory_ = inventory; - this.offset_ = Mth.clamp(offset, 0, inventory.getContainerSize() - 1); - this.size_ = Mth.clamp(size, 0, inventory.getContainerSize() - this.offset_); - this.num_rows = num_rows; - } - - public InventoryRange(Container inventory, int offset, int size) { - this(inventory, offset, size, 1); - } - - public InventoryRange(Container inventory) { - this(inventory, 0, inventory.getContainerSize(), 1); - } - - public final Container inventory() { - return inventory_; - } - - public final int size() { - return size_; - } - - public final int offset() { - return offset_; - } - - public final ItemStack get(int index) { - return inventory_.getItem(offset_ + index); - } - - public final void set(int index, ItemStack stack) { - inventory_.setItem(offset_ + index, stack); - } - - public final InventoryRange setValidator(BiPredicate validator) { - validator_ = validator; - return this; - } - - public final BiPredicate getValidator() { - return validator_; - } - - public final InventoryRange setMaxStackSize(int count) { - max_stack_size_ = Math.max(count, 1); - return this; - } - - // Container ------------------------------------------------------------------------------------------------------ - - @Override - public void clearContent() { - for (int i = 0; i < size_; ++i) setItem(i, ItemStack.EMPTY); - } - - @Override - public int getContainerSize() { - return size_; - } - - @Override - public boolean isEmpty() { - for (int i = 0; i < size_; ++i) - if (!inventory_.getItem(offset_ + i).isEmpty()) { - return false; - } - return true; - } - - @Override - public ItemStack getItem(int index) { - return inventory_.getItem(offset_ + index); - } - - @Override - public ItemStack removeItem(int index, int count) { - return inventory_.removeItem(offset_ + index, count); - } - - @Override - public ItemStack removeItemNoUpdate(int index) { - return inventory_.removeItemNoUpdate(offset_ + index); - } - - @Override - public void setItem(int index, ItemStack stack) { - inventory_.setItem(offset_ + index, stack); - } - - @Override - public int getMaxStackSize() { - return Math.min(max_stack_size_, inventory_.getMaxStackSize()); - } - - @Override - public void setChanged() { - inventory_.setChanged(); - } - - @Override - public boolean stillValid(Player player) { - return inventory_.stillValid(player); - } - - @Override - public void startOpen(Player player) { - inventory_.startOpen(player); - } - - @Override - public void stopOpen(Player player) { - inventory_.stopOpen(player); - } - - @Override - public boolean canPlaceItem(int index, ItemStack stack) { - return validator_.test(offset_ + index, stack) && inventory_.canPlaceItem(offset_ + index, stack); - } - - //------------------------------------------------------------------------------------------------------------------ - - /** - * Iterates using a function (slot, stack) -> bool until the function matches (returns true). - */ - public boolean iterate(BiPredicate fn) { - for (int i = 0; i < size_; ++i) { - if (fn.test(i, getItem(i))) { - return true; - } - } - return false; - } - - public boolean contains(ItemStack stack) { - for (int i = 0; i < size_; ++i) { - if (areItemStacksIdentical(stack, getItem(i))) { - return true; - } - } - return false; - } - - public int indexOf(ItemStack stack) { - for (int i = 0; i < size_; ++i) { - if (areItemStacksIdentical(stack, getItem(i))) { - return i; - } - } - return -1; - } - - public Optional find(BiFunction> fn) { - for (int i = 0; i < size_; ++i) { - Optional r = fn.apply(i, getItem(i)); - if (r.isPresent()) return r; - } - return Optional.empty(); - } - - public List collect(BiFunction> fn) { - List data = new ArrayList<>(); - for (int i = 0; i < size_; ++i) { - fn.apply(i, getItem(i)).ifPresent(data::add); - } - return data; - } - - public Stream stream() { - return java.util.stream.StreamSupport.stream(this.spliterator(), false); - } - - public Iterator iterator() { - return new InventoryRangeIterator(this); - } - - public static class InventoryRangeIterator implements Iterator { - private final InventoryRange parent_; - private int index = 0; - - public InventoryRangeIterator(InventoryRange range) { - parent_ = range; - } - - public boolean hasNext() { - return index < parent_.size_; - } - - public ItemStack next() { - if (index >= parent_.size_) throw new NoSuchElementException(); - return parent_.getItem(index++); - } - } - - //------------------------------------------------------------------------------------------------------------------ - - /** - * Returns the number of stacks that match the given stack with NBT. - */ - public int stackMatchCount(final ItemStack ref_stack) { - int n = 0; // ... std::accumulate() the old school way. - for (int i = 0; i < size_; ++i) { - if (areItemStacksIdentical(ref_stack, getItem(i))) ++n; - } - return n; - } - - public int totalMatchingItemCount(final ItemStack ref_stack) { - int n = 0; - for (int i = 0; i < size_; ++i) { - ItemStack stack = getItem(i); - if (areItemStacksIdentical(ref_stack, stack)) n += stack.getCount(); - } - return n; - } - - //------------------------------------------------------------------------------------------------------------------ - - /** - * Moves as much items from the stack to the slots in range [offset_, end_slot] of the inventory_, - * filling up existing stacks first, then (player inventory_ only) checks appropriate empty slots next - * to stacks that have that item already, and last uses any empty slot that can be found. - * Returns the stack that is still remaining in the referenced `stack`. - */ - public ItemStack insert(final ItemStack input_stack, boolean only_fillup, int limit, boolean reverse, boolean force_group_stacks) { - final ItemStack mvstack = input_stack.copy(); - //final int end_slot = offset_ + size; - if (mvstack.isEmpty()) return checked(mvstack); - int limit_left = (limit > 0) ? (Math.min(limit, mvstack.getMaxStackSize())) : (mvstack.getMaxStackSize()); - boolean[] matches = new boolean[size_]; - boolean[] empties = new boolean[size_]; - int num_matches = 0; - for (int i = 0; i < size_; ++i) { - final int sno = reverse ? (size_ - 1 - i) : (i); - final ItemStack stack = getItem(sno); - if (stack.isEmpty()) { - empties[sno] = true; - } else if (areItemStacksIdentical(stack, mvstack)) { - matches[sno] = true; - ++num_matches; - } - } - // first iteration: fillup existing stacks - for (int i = 0; i < size_; ++i) { - final int sno = reverse ? (size_ - 1 - i) : (i); - if ((empties[sno]) || (!matches[sno])) continue; - final ItemStack stack = getItem(sno); - int nmax = Math.min(limit_left, stack.getMaxStackSize() - stack.getCount()); - if (mvstack.getCount() <= nmax) { - stack.setCount(stack.getCount() + mvstack.getCount()); - setItem(sno, stack); - return ItemStack.EMPTY; - } else { - stack.grow(nmax); - mvstack.shrink(nmax); - setItem(sno, stack); - limit_left -= nmax; - } - } - if (only_fillup) return checked(mvstack); - if ((num_matches > 0) && ((force_group_stacks) || (inventory_ instanceof Inventory))) { - // second iteration: use appropriate empty slots, - // a) between - { - int insert_start = -1; - int insert_end = -1; - int i = 1; - for (; i < size_ - 1; ++i) { - final int sno = reverse ? (size_ - 1 - i) : (i); - if (insert_start < 0) { - if (matches[sno]) insert_start = sno; - } else if (matches[sno]) { - insert_end = sno; - } - } - for (i = insert_start; i < insert_end; ++i) { - final int sno = reverse ? (size_ - 1 - i) : (i); - if ((!empties[sno]) || (!canPlaceItem(sno, mvstack))) continue; - int nmax = Math.min(limit_left, mvstack.getCount()); - ItemStack moved = mvstack.copy(); - moved.setCount(nmax); - mvstack.shrink(nmax); - setItem(sno, moved); - return checked(mvstack); - } - } - // b) before/after - { - for (int i = 1; i < size_ - 1; ++i) { - final int sno = reverse ? (size_ - 1 - i) : (i); - if (!matches[sno]) continue; - int ii = (empties[sno - 1]) ? (sno - 1) : (empties[sno + 1] ? (sno + 1) : -1); - if ((ii >= 0) && (canPlaceItem(ii, mvstack))) { - int nmax = Math.min(limit_left, mvstack.getCount()); - ItemStack moved = mvstack.copy(); - moved.setCount(nmax); - mvstack.shrink(nmax); - setItem(ii, moved); - return checked(mvstack); - } - } - } - } - // third iteration: use any empty slots - for (int i = 0; i < size_; ++i) { - final int sno = reverse ? (size_ - 1 - i) : (i); - if ((!empties[sno]) || (!canPlaceItem(sno, mvstack))) continue; - int nmax = Math.min(limit_left, mvstack.getCount()); - ItemStack placed = mvstack.copy(); - placed.setCount(nmax); - mvstack.shrink(nmax); - setItem(sno, placed); - return checked(mvstack); - } - return checked(mvstack); - } - - public ItemStack insert(final ItemStack stack_to_move) { - return insert(stack_to_move, false, 0, false, true); - } - - public ItemStack insert(final int index, final ItemStack stack_to_move) { - if (stack_to_move.isEmpty()) return stack_to_move; - final ItemStack stack = getItem(index); - final int limit = Math.min(getMaxStackSize(), stack.getMaxStackSize()); - if (stack.isEmpty()) { - setItem(index, stack_to_move.copy()); - return ItemStack.EMPTY; - } else if ((stack.getCount() >= limit) || !areItemStacksIdentical(stack, stack_to_move)) { - return stack_to_move; - } else { - final int amount = Math.min(limit - stack.getCount(), stack_to_move.getCount()); - ItemStack remaining = stack_to_move.copy(); - remaining.shrink(amount); - stack.grow(amount); - return remaining.isEmpty() ? ItemStack.EMPTY : remaining; - } - } - - //------------------------------------------------------------------------------------------------------------------ - - /** - * Extracts maximum amount of items from the inventory_. - * The first non-empty stack defines the item. - */ - public ItemStack extract(int amount) { - return extract(amount, false); - } - - public ItemStack extract(int amount, boolean random) { - ItemStack out_stack = ItemStack.EMPTY; - int offset = random ? (int) (Math.random() * size_) : 0; - for (int k = 0; k < size_; ++k) { - int i = (offset + k) % size_; - final ItemStack stack = getItem(i); - if (stack.isEmpty()) continue; - if (out_stack.isEmpty()) { - if (stack.getCount() < amount) { - out_stack = stack; - setItem(i, ItemStack.EMPTY); - if (!out_stack.isStackable()) break; - amount -= out_stack.getCount(); - } else { - out_stack = stack.split(amount); - break; - } - } else if (areItemStacksIdentical(stack, out_stack)) { - if (stack.getCount() <= amount) { - out_stack.grow(stack.getCount()); - amount -= stack.getCount(); - setItem(i, ItemStack.EMPTY); - } else { - out_stack.grow(amount); - stack.shrink(amount); - if (stack.isEmpty()) setItem(i, ItemStack.EMPTY); - break; - } - } - } - if (!out_stack.isEmpty()) setChanged(); - return out_stack; - } - - /** - * Moves as much items from the slots in range [offset_, end_slot] of the inventory_ into a new stack. - * Implicitly shrinks the inventory_ stacks and the `request_stack`. - */ - public ItemStack extract(final ItemStack request_stack) { - if (request_stack.isEmpty()) return ItemStack.EMPTY; - List matches = new ArrayList<>(); - for (int i = 0; i < size_; ++i) { - final ItemStack stack = getItem(i); - if ((!stack.isEmpty()) && (areItemStacksIdentical(stack, request_stack))) { - if (stack.hasTag()) { - final CompoundTag nbt = stack.getOrCreateTag(); - int n = nbt.size(); - if ((n > 0) && (nbt.contains("Damage"))) --n; - if (n > 0) continue; - } - matches.add(stack); - } - } - matches.sort(Comparator.comparingInt(ItemStack::getCount)); - if (matches.isEmpty()) return ItemStack.EMPTY; - int n_left = request_stack.getCount(); - ItemStack fetched_stack = matches.get(0).split(n_left); - n_left -= fetched_stack.getCount(); - for (int i = 1; (i < matches.size()) && (n_left > 0); ++i) { - ItemStack stack = matches.get(i).split(n_left); - n_left -= stack.getCount(); - fetched_stack.grow(stack.getCount()); - } - return checked(fetched_stack); - } - - //------------------------------------------------------------------------------------------------------------------ - - /** - * Moves items from this inventory_ range to another. Returns true if something was moved - * (if the inventories should be marked dirty). - */ - public boolean move(int index, final InventoryRange target_range, boolean all_identical_stacks, boolean only_fillup, boolean reverse, boolean force_group_stacks) { - final ItemStack source_stack = getItem(index); - if (source_stack.isEmpty()) return false; - if (!all_identical_stacks) { - ItemStack remaining = target_range.insert(source_stack, only_fillup, 0, reverse, force_group_stacks); - setItem(index, remaining); - return (remaining.getCount() != source_stack.getCount()); - } else { - ItemStack remaining = source_stack.copy(); - setItem(index, ItemStack.EMPTY); - final ItemStack ref_stack = remaining.copy(); - ref_stack.setCount(ref_stack.getMaxStackSize()); - for (int i = size_; (i > 0) && (!remaining.isEmpty()); --i) { - remaining = target_range.insert(remaining, only_fillup, 0, reverse, force_group_stacks); - if (!remaining.isEmpty()) break; - remaining = this.extract(ref_stack); - } - if (!remaining.isEmpty()) { - setItem(index, remaining); // put back - } - return (remaining.getCount() != source_stack.getCount()); - } - } - - public boolean move(int index, final InventoryRange target_range) { - return move(index, target_range, false, false, false, true); - } - - /** - * Moves/clears the complete range to another range if possible. Returns true if something was moved - * (if the inventories should be marked dirty). - */ - public boolean move(final InventoryRange target_range, boolean only_fillup, boolean reverse, boolean force_group_stacks) { - boolean changed = false; - for (int i = 0; i < size_; ++i) - changed |= move(i, target_range, false, only_fillup, reverse, force_group_stacks); - return changed; - } - - public boolean move(final InventoryRange target_range, boolean only_fillup) { - return move(target_range, only_fillup, false, true); - } - - public boolean move(final InventoryRange target_range) { - return move(target_range, false, false, true); - } - - } - - //-------------------------------------------------------------------------------------------------------------------- - - public static class StorageInventory implements Container, Iterable { - protected final NonNullList stacks_; - protected final BlockEntity te_; - protected final int size_; - protected final int num_rows_; - protected int stack_limit_ = 64; - protected BiPredicate validator_ = (index, stack) -> true; - protected Consumer open_action_ = (player) -> { - }; - protected Consumer close_action_ = (player) -> { - }; - protected BiConsumer slot_set_action_ = (index, stack) -> { - }; - - public StorageInventory(BlockEntity te, int size) { - this(te, size, 1); - } - - public StorageInventory(BlockEntity te, int size, int num_rows) { - te_ = te; - size_ = Math.max(size, 1); - stacks_ = NonNullList.withSize(size_, ItemStack.EMPTY); - num_rows_ = Mth.clamp(num_rows, 1, size_); - } - - public CompoundTag save(CompoundTag nbt, String key) { - nbt.put(key, save(new CompoundTag(), false)); - return nbt; - } - - public CompoundTag save(CompoundTag nbt) { - return ContainerHelper.saveAllItems(nbt, stacks_); - } - - public CompoundTag save(CompoundTag nbt, boolean save_empty) { - return ContainerHelper.saveAllItems(nbt, stacks_, save_empty); - } - - public CompoundTag save(boolean save_empty) { - return save(new CompoundTag(), save_empty); - } - - public StorageInventory load(CompoundTag nbt, String key) { - if (!nbt.contains("key", Tag.TAG_COMPOUND)) { - stacks_.clear(); - return this; - } else { - return load(nbt.getCompound(key)); - } - } - - public StorageInventory load(CompoundTag nbt) { - stacks_.clear(); - ContainerHelper.loadAllItems(nbt, stacks_); - return this; - } - - public List stacks() { - return stacks_; - } - - public BlockEntity getBlockEntity() { - return te_; - } - - public StorageInventory setOpenAction(Consumer fn) { - open_action_ = fn; - return this; - } - - public StorageInventory setCloseAction(Consumer fn) { - close_action_ = fn; - return this; - } - - public StorageInventory setSlotChangeAction(BiConsumer fn) { - slot_set_action_ = fn; - return this; - } - - public StorageInventory setStackLimit(int max_slot_stack_size) { - stack_limit_ = Math.max(max_slot_stack_size, 1); - return this; - } - - public StorageInventory setValidator(BiPredicate validator) { - validator_ = validator; - return this; - } - - public BiPredicate getValidator() { - return validator_; - } - - // Iterable --------------------------------------------------------------------- - - public Iterator iterator() { - return stacks_.iterator(); - } - - public Stream stream() { - return stacks_.stream(); - } - - // Container ------------------------------------------------------------------------------ - - @Override - public int getContainerSize() { - return size_; - } - - @Override - public boolean isEmpty() { - for (ItemStack stack : stacks_) { - if (!stack.isEmpty()) return false; - } - return true; - } - - @Override - public ItemStack getItem(int index) { - return (index < size_) ? stacks_.get(index) : ItemStack.EMPTY; - } - - @Override - public ItemStack removeItem(int index, int count) { - return ContainerHelper.removeItem(stacks_, index, count); - } - - @Override - public ItemStack removeItemNoUpdate(int index) { - return ContainerHelper.takeItem(stacks_, index); - } - - @Override - public void setItem(int index, ItemStack stack) { - stacks_.set(index, stack); - if ((stack.getCount() != stacks_.get(index).getCount()) || !areItemStacksDifferent(stacks_.get(index), stack)) { - slot_set_action_.accept(index, stack); - } - } - - @Override - public int getMaxStackSize() { - return stack_limit_; - } - - @Override - public void setChanged() { - te_.setChanged(); - } - - @Override - public boolean stillValid(Player player) { - return ((te_.getLevel().getBlockEntity(te_.getBlockPos()) == te_)) && (te_.getBlockPos().distSqr(player.blockPosition()) < 64); - } - - @Override - public void startOpen(Player player) { - open_action_.accept(player); - } - - @Override - public void stopOpen(Player player) { - setChanged(); - close_action_.accept(player); - } - - @Override - public boolean canPlaceItem(int index, ItemStack stack) { - return validator_.test(index, stack); - } - - @Override - public void clearContent() { - stacks_.clear(); - setChanged(); - } - - } - - //-------------------------------------------------------------------------------------------------------------------- - - public static void give(Player entity, ItemStack stack) { - ItemHandlerHelper.giveItemToPlayer(entity, stack); - } - - public static void setItemInPlayerHand(Player player, InteractionHand hand, ItemStack stack) { - if (stack.isEmpty()) stack = ItemStack.EMPTY; - if (hand == InteractionHand.MAIN_HAND) { - player.getInventory().items.set(player.getInventory().selected, stack); - } else { - player.getInventory().offhand.set(0, stack); - } - } - - //-------------------------------------------------------------------------------------------------------------------- - - public static Container readNbtStacks(CompoundTag nbt, String key, Container target) { - NonNullList stacks = Inventories.readNbtStacks(nbt, key, target.getContainerSize()); - for (int i = 0; i < stacks.size(); ++i) target.setItem(i, stacks.get(i)); - return target; - } - - public static NonNullList readNbtStacks(CompoundTag nbt, String key, int size) { - NonNullList stacks = NonNullList.withSize(size, ItemStack.EMPTY); - if ((nbt == null) || (!nbt.contains(key, Tag.TAG_COMPOUND))) return stacks; - CompoundTag stacknbt = nbt.getCompound(key); - ContainerHelper.loadAllItems(stacknbt, stacks); - return stacks; - } - - public static NonNullList readNbtStacks(CompoundTag nbt, int size) { - NonNullList stacks = NonNullList.withSize(size, ItemStack.EMPTY); - if ((nbt == null) || (!nbt.contains("Items", Tag.TAG_LIST))) return stacks; - ContainerHelper.loadAllItems(nbt, stacks); - return stacks; - } - - public static CompoundTag writeNbtStacks(CompoundTag nbt, String key, NonNullList stacks, boolean omit_trailing_empty) { - CompoundTag stacknbt = new CompoundTag(); - if (omit_trailing_empty) { - for (int i = stacks.size() - 1; i >= 0; --i) { - if (!stacks.get(i).isEmpty()) break; - stacks.remove(i); - } - } - ContainerHelper.saveAllItems(stacknbt, stacks); - if (nbt == null) nbt = new CompoundTag(); - nbt.put(key, stacknbt); - return nbt; - } - - public static CompoundTag writeNbtStacks(CompoundTag nbt, String key, NonNullList stacks) { - return writeNbtStacks(nbt, key, stacks, false); - } - - //-------------------------------------------------------------------------------------------------------------------- - - public static void dropStack(Level world, Vec3 pos, ItemStack stack, Vec3 velocity, double position_noise, double velocity_noise) { - if (stack.isEmpty()) return; - if (position_noise > 0) { - position_noise = Math.min(position_noise, 0.8); - pos = pos.add( - position_noise * (world.getRandom().nextDouble() - .5), - position_noise * (world.getRandom().nextDouble() - .5), - position_noise * (world.getRandom().nextDouble() - .5) - ); - } - if (velocity_noise > 0) { - velocity_noise = Math.min(velocity_noise, 1.0); - velocity = velocity.add( - (velocity_noise) * (world.getRandom().nextDouble() - .5), - (velocity_noise) * (world.getRandom().nextDouble() - .5), - (velocity_noise) * (world.getRandom().nextDouble() - .5) - ); - } - ItemEntity e = new ItemEntity(world, pos.x, pos.y, pos.z, stack); - e.setDeltaMovement((float) velocity.x, (float) velocity.y, (float) velocity.z); - e.setDefaultPickUpDelay(); - world.addFreshEntity(e); - } - - public static void dropStack(Level world, Vec3 pos, ItemStack stack, Vec3 velocity) { - dropStack(world, pos, stack, velocity, 0.3, 0.2); - } - - public static void dropStack(Level world, Vec3 pos, ItemStack stack) { - dropStack(world, pos, stack, Vec3.ZERO, 0.3, 0.2); - } - -} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/Networking.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/Networking.java deleted file mode 100644 index 67a3540..0000000 --- a/src/main/java/dev/zontreck/libzontreck/edlibmc/Networking.java +++ /dev/null @@ -1,409 +0,0 @@ -/* - * @file Networking.java - * @author Stefan Wilhelm (wile) - * @copyright (C) 2020 Stefan Wilhelm - * @license MIT (see https://opensource.org/licenses/MIT) - * - * Main client/server message handling. - */ -package dev.zontreck.libzontreck.edlibmc; - -import net.minecraft.core.BlockPos; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.network.chat.Component; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.inventory.AbstractContainerMenu; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraftforge.common.util.FakePlayer; -import net.minecraftforge.network.NetworkDirection; -import net.minecraftforge.network.NetworkEvent; -import net.minecraftforge.network.NetworkRegistry; -import net.minecraftforge.network.simple.SimpleChannel; - -import java.util.HashMap; -import java.util.Map; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Supplier; - - -public class Networking { - private static final String PROTOCOL = "1"; - private static SimpleChannel DEFAULT_CHANNEL; - - public static void init(String modid) { - DEFAULT_CHANNEL = NetworkRegistry.ChannelBuilder - .named(new ResourceLocation(modid, "default_ch")) - .clientAcceptedVersions(PROTOCOL::equals).serverAcceptedVersions(PROTOCOL::equals).networkProtocolVersion(() -> PROTOCOL) - .simpleChannel(); - int discr = -1; - DEFAULT_CHANNEL.registerMessage(++discr, PacketTileNotifyClientToServer.class, PacketTileNotifyClientToServer::compose, PacketTileNotifyClientToServer::parse, PacketTileNotifyClientToServer.Handler::handle); - DEFAULT_CHANNEL.registerMessage(++discr, PacketTileNotifyServerToClient.class, PacketTileNotifyServerToClient::compose, PacketTileNotifyServerToClient::parse, PacketTileNotifyServerToClient.Handler::handle); - DEFAULT_CHANNEL.registerMessage(++discr, PacketContainerSyncClientToServer.class, PacketContainerSyncClientToServer::compose, PacketContainerSyncClientToServer::parse, PacketContainerSyncClientToServer.Handler::handle); - DEFAULT_CHANNEL.registerMessage(++discr, PacketContainerSyncServerToClient.class, PacketContainerSyncServerToClient::compose, PacketContainerSyncServerToClient::parse, PacketContainerSyncServerToClient.Handler::handle); - DEFAULT_CHANNEL.registerMessage(++discr, PacketNbtNotifyClientToServer.class, PacketNbtNotifyClientToServer::compose, PacketNbtNotifyClientToServer::parse, PacketNbtNotifyClientToServer.Handler::handle); - DEFAULT_CHANNEL.registerMessage(++discr, PacketNbtNotifyServerToClient.class, PacketNbtNotifyServerToClient::compose, PacketNbtNotifyServerToClient::parse, PacketNbtNotifyServerToClient.Handler::handle); - DEFAULT_CHANNEL.registerMessage(++discr, OverlayTextMessage.class, OverlayTextMessage::compose, OverlayTextMessage::parse, OverlayTextMessage.Handler::handle); - } - - //-------------------------------------------------------------------------------------------------------------------- - // Tile entity notifications - //-------------------------------------------------------------------------------------------------------------------- - - public interface IPacketTileNotifyReceiver { - default void onServerPacketReceived(CompoundTag nbt) { - } - - default void onClientPacketReceived(Player player, CompoundTag nbt) { - } - } - - public static class PacketTileNotifyClientToServer { - CompoundTag nbt = null; - BlockPos pos = BlockPos.ZERO; - - public static void sendToServer(BlockPos pos, CompoundTag nbt) { - if ((pos != null) && (nbt != null)) - DEFAULT_CHANNEL.sendToServer(new PacketTileNotifyClientToServer(pos, nbt)); - } - - public static void sendToServer(BlockEntity te, CompoundTag nbt) { - if ((te != null) && (nbt != null)) - DEFAULT_CHANNEL.sendToServer(new PacketTileNotifyClientToServer(te, nbt)); - } - - public PacketTileNotifyClientToServer() { - } - - public PacketTileNotifyClientToServer(BlockPos pos, CompoundTag nbt) { - this.nbt = nbt; - this.pos = pos; - } - - public PacketTileNotifyClientToServer(BlockEntity te, CompoundTag nbt) { - this.nbt = nbt; - pos = te.getBlockPos(); - } - - public static PacketTileNotifyClientToServer parse(final FriendlyByteBuf buf) { - return new PacketTileNotifyClientToServer(buf.readBlockPos(), buf.readNbt()); - } - - public static void compose(final PacketTileNotifyClientToServer pkt, final FriendlyByteBuf buf) { - buf.writeBlockPos(pkt.pos); - buf.writeNbt(pkt.nbt); - } - - @SuppressWarnings("all") - public static class Handler { - public static void handle(final PacketTileNotifyClientToServer pkt, final Supplier ctx) { - ctx.get().enqueueWork(() -> { - Player player = ctx.get().getSender(); - if (player == null) return; - Level world = player.level(); - final BlockEntity te = world.getBlockEntity(pkt.pos); - if (!(te instanceof IPacketTileNotifyReceiver)) return; - ((IPacketTileNotifyReceiver) te).onClientPacketReceived(ctx.get().getSender(), pkt.nbt); - }); - ctx.get().setPacketHandled(true); - } - } - } - - public static class PacketTileNotifyServerToClient { - CompoundTag nbt = null; - BlockPos pos = BlockPos.ZERO; - - public static void sendToPlayer(Player player, BlockEntity te, CompoundTag nbt) { - if ((!(player instanceof ServerPlayer)) || (player instanceof FakePlayer) || (te == null) || (nbt == null)) - return; - DEFAULT_CHANNEL.sendTo(new PacketTileNotifyServerToClient(te, nbt), ((ServerPlayer) player).connection.connection, NetworkDirection.PLAY_TO_CLIENT); - } - - public static void sendToPlayers(BlockEntity te, CompoundTag nbt) { - if (te == null || te.getLevel() == null) return; - for (Player player : te.getLevel().players()) sendToPlayer(player, te, nbt); - } - - public PacketTileNotifyServerToClient() { - } - - public PacketTileNotifyServerToClient(BlockPos pos, CompoundTag nbt) { - this.nbt = nbt; - this.pos = pos; - } - - public PacketTileNotifyServerToClient(BlockEntity te, CompoundTag nbt) { - this.nbt = nbt; - pos = te.getBlockPos(); - } - - public static PacketTileNotifyServerToClient parse(final FriendlyByteBuf buf) { - return new PacketTileNotifyServerToClient(buf.readBlockPos(), buf.readNbt()); - } - - public static void compose(final PacketTileNotifyServerToClient pkt, final FriendlyByteBuf buf) { - buf.writeBlockPos(pkt.pos); - buf.writeNbt(pkt.nbt); - } - - public static class Handler { - public static void handle(final PacketTileNotifyServerToClient pkt, final Supplier ctx) { - ctx.get().enqueueWork(() -> { - Level world = SidedProxy.getWorldClientSide(); - if (world == null) return; - final BlockEntity te = world.getBlockEntity(pkt.pos); - if (!(te instanceof IPacketTileNotifyReceiver)) return; - ((IPacketTileNotifyReceiver) te).onServerPacketReceived(pkt.nbt); - }); - ctx.get().setPacketHandled(true); - } - } - } - - //-------------------------------------------------------------------------------------------------------------------- - // (GUI) Container synchronization - //-------------------------------------------------------------------------------------------------------------------- - - public interface INetworkSynchronisableContainer { - void onServerPacketReceived(int windowId, CompoundTag nbt); - - void onClientPacketReceived(int windowId, Player player, CompoundTag nbt); - } - - public static class PacketContainerSyncClientToServer { - int id = -1; - CompoundTag nbt = null; - - public static void sendToServer(int windowId, CompoundTag nbt) { - if (nbt != null) DEFAULT_CHANNEL.sendToServer(new PacketContainerSyncClientToServer(windowId, nbt)); - } - - public static void sendToServer(AbstractContainerMenu container, CompoundTag nbt) { - if (nbt != null) - DEFAULT_CHANNEL.sendToServer(new PacketContainerSyncClientToServer(container.containerId, nbt)); - } - - public PacketContainerSyncClientToServer() { - } - - public PacketContainerSyncClientToServer(int id, CompoundTag nbt) { - this.nbt = nbt; - this.id = id; - } - - public static PacketContainerSyncClientToServer parse(final FriendlyByteBuf buf) { - return new PacketContainerSyncClientToServer(buf.readInt(), buf.readNbt()); - } - - public static void compose(final PacketContainerSyncClientToServer pkt, final FriendlyByteBuf buf) { - buf.writeInt(pkt.id); - buf.writeNbt(pkt.nbt); - } - - public static class Handler { - public static void handle(final PacketContainerSyncClientToServer pkt, final Supplier ctx) { - ctx.get().enqueueWork(() -> { - Player player = ctx.get().getSender(); - if ((player == null) || !(player.containerMenu instanceof INetworkSynchronisableContainer)) return; - if (player.containerMenu.containerId != pkt.id) return; - ((INetworkSynchronisableContainer) player.containerMenu).onClientPacketReceived(pkt.id, player, pkt.nbt); - }); - ctx.get().setPacketHandled(true); - } - } - } - - public static class PacketContainerSyncServerToClient { - int id = -1; - CompoundTag nbt = null; - - public static void sendToPlayer(Player player, int windowId, CompoundTag nbt) { - if ((!(player instanceof ServerPlayer)) || (player instanceof FakePlayer) || (nbt == null)) return; - DEFAULT_CHANNEL.sendTo(new PacketContainerSyncServerToClient(windowId, nbt), ((ServerPlayer) player).connection.connection, NetworkDirection.PLAY_TO_CLIENT); - } - - public static void sendToPlayer(Player player, AbstractContainerMenu container, CompoundTag nbt) { - if ((!(player instanceof ServerPlayer)) || (player instanceof FakePlayer) || (nbt == null)) return; - DEFAULT_CHANNEL.sendTo(new PacketContainerSyncServerToClient(container.containerId, nbt), ((ServerPlayer) player).connection.connection, NetworkDirection.PLAY_TO_CLIENT); - } - - public static - void sendToListeners(Level world, C container, CompoundTag nbt) { - for (Player player : world.players()) { - if (player.containerMenu.containerId != container.containerId) continue; - sendToPlayer(player, container.containerId, nbt); - } - } - - public PacketContainerSyncServerToClient() { - } - - public PacketContainerSyncServerToClient(int id, CompoundTag nbt) { - this.nbt = nbt; - this.id = id; - } - - public static PacketContainerSyncServerToClient parse(final FriendlyByteBuf buf) { - return new PacketContainerSyncServerToClient(buf.readInt(), buf.readNbt()); - } - - public static void compose(final PacketContainerSyncServerToClient pkt, final FriendlyByteBuf buf) { - buf.writeInt(pkt.id); - buf.writeNbt(pkt.nbt); - } - - public static class Handler { - public static void handle(final PacketContainerSyncServerToClient pkt, final Supplier ctx) { - ctx.get().enqueueWork(() -> { - Player player = SidedProxy.getPlayerClientSide(); - if ((player == null) || !(player.containerMenu instanceof INetworkSynchronisableContainer)) return; - if (player.containerMenu.containerId != pkt.id) return; - ((INetworkSynchronisableContainer) player.containerMenu).onServerPacketReceived(pkt.id, pkt.nbt); - }); - ctx.get().setPacketHandled(true); - } - } - } - - //-------------------------------------------------------------------------------------------------------------------- - // World notifications - //-------------------------------------------------------------------------------------------------------------------- - - public static class PacketNbtNotifyClientToServer { - public static final Map> handlers = new HashMap<>(); - final CompoundTag nbt; - - public static void sendToServer(CompoundTag nbt) { - if (nbt != null) DEFAULT_CHANNEL.sendToServer(new PacketNbtNotifyClientToServer(nbt)); - } - - public PacketNbtNotifyClientToServer(CompoundTag nbt) { - this.nbt = nbt; - } - - public static PacketNbtNotifyClientToServer parse(final FriendlyByteBuf buf) { - return new PacketNbtNotifyClientToServer(buf.readNbt()); - } - - public static void compose(final PacketNbtNotifyClientToServer pkt, final FriendlyByteBuf buf) { - buf.writeNbt(pkt.nbt); - } - - public static class Handler { - public static void handle(final PacketNbtNotifyClientToServer pkt, final Supplier ctx) { - ctx.get().enqueueWork(() -> { - final ServerPlayer player = ctx.get().getSender(); - if (player == null) return; - final String hnd = pkt.nbt.getString("hnd"); - if (hnd.isEmpty()) return; - if (handlers.containsKey(hnd)) handlers.get(hnd).accept(player, pkt.nbt); - }); - ctx.get().setPacketHandled(true); - } - } - } - - public static class PacketNbtNotifyServerToClient { - public static final Map> handlers = new HashMap<>(); - final CompoundTag nbt; - - public static void sendToPlayer(Player player, CompoundTag nbt) { - if ((!(player instanceof ServerPlayer)) || (player instanceof FakePlayer) || (nbt == null)) return; - DEFAULT_CHANNEL.sendTo(new PacketNbtNotifyServerToClient(nbt), ((ServerPlayer) player).connection.connection, NetworkDirection.PLAY_TO_CLIENT); - } - - public static void sendToPlayers(Level world, CompoundTag nbt) { - for (Player player : world.players()) sendToPlayer(player, nbt); - } - - public PacketNbtNotifyServerToClient(CompoundTag nbt) { - this.nbt = nbt; - } - - public static PacketNbtNotifyServerToClient parse(final FriendlyByteBuf buf) { - return new PacketNbtNotifyServerToClient(buf.readNbt()); - } - - public static void compose(final PacketNbtNotifyServerToClient pkt, final FriendlyByteBuf buf) { - buf.writeNbt(pkt.nbt); - } - - public static class Handler { - public static void handle(final PacketNbtNotifyServerToClient pkt, final Supplier ctx) { - ctx.get().enqueueWork(() -> { - final String hnd = pkt.nbt.getString("hnd"); - if (hnd.isEmpty()) return; - if (handlers.containsKey(hnd)) handlers.get(hnd).accept(pkt.nbt); - }); - ctx.get().setPacketHandled(true); - } - } - } - - //-------------------------------------------------------------------------------------------------------------------- - // Main window GUI text message - //-------------------------------------------------------------------------------------------------------------------- - - public static class OverlayTextMessage { - public static final int DISPLAY_TIME_MS = 3000; - private static BiConsumer handler_ = null; - private final Component data_; - private int delay_ = DISPLAY_TIME_MS; - - private Component data() { - return data_; - } - - private int delay() { - return delay_; - } - - public static void setHandler(BiConsumer handler) { - if (handler_ == null) handler_ = handler; - } - - public static void sendToPlayer(Player player, Component message, int delay) { - if ((!(player instanceof ServerPlayer)) || (player instanceof FakePlayer) || Auxiliaries.isEmpty(message)) - return; - DEFAULT_CHANNEL.sendTo(new OverlayTextMessage(message, delay), ((ServerPlayer) player).connection.connection, NetworkDirection.PLAY_TO_CLIENT); - } - - public OverlayTextMessage() { - data_ = Component.translatable("[unset]"); - } - - public OverlayTextMessage(final Component tct, int delay) { - data_ = tct.copy(); - delay_ = delay; - } - - public static OverlayTextMessage parse(final FriendlyByteBuf buf) { - try { - return new OverlayTextMessage(buf.readComponent(), DISPLAY_TIME_MS); - } catch (Throwable e) { - return new OverlayTextMessage(Component.translatable("[incorrect translation]"), DISPLAY_TIME_MS); - } - } - - public static void compose(final OverlayTextMessage pkt, final FriendlyByteBuf buf) { - try { - buf.writeComponent(pkt.data()); - } catch (Throwable e) { - Auxiliaries.logger().error("OverlayTextMessage.toBytes() failed: " + e); - } - } - - public static class Handler { - public static void handle(final OverlayTextMessage pkt, final Supplier ctx) { - if (handler_ != null) ctx.get().enqueueWork(() -> handler_.accept(pkt.data(), pkt.delay())); - ctx.get().setPacketHandled(true); - } - } - } - -} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/OptionalRecipeCondition.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/OptionalRecipeCondition.java deleted file mode 100644 index 681daff..0000000 --- a/src/main/java/dev/zontreck/libzontreck/edlibmc/OptionalRecipeCondition.java +++ /dev/null @@ -1,191 +0,0 @@ -/* - * @file OptionalRecipeCondition.java - * @author Stefan Wilhelm (wile) - * @copyright (C) 2020 Stefan Wilhelm - * @license MIT (see https://opensource.org/licenses/MIT) - * - * Recipe condition to enable opt'ing out JSON based recipes. - */ -package dev.zontreck.libzontreck.edlibmc; - -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.util.GsonHelper; -import net.minecraft.world.item.Item; -import net.minecraft.world.level.block.Block; -import net.minecraftforge.common.crafting.conditions.ICondition; -import net.minecraftforge.common.crafting.conditions.IConditionSerializer; -import net.minecraftforge.registries.ForgeRegistries; -import net.minecraftforge.registries.IForgeRegistry; -import org.slf4j.Logger; - -import javax.annotation.Nullable; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Predicate; - - -public class OptionalRecipeCondition implements ICondition { - private static ResourceLocation NAME; - - private final List all_required; - private final List any_missing; - private final List all_required_tags; - private final List any_missing_tags; - private final @Nullable ResourceLocation result; - private final boolean result_is_tag; - private final boolean experimental; - - private static boolean with_experimental = false; - private static boolean without_recipes = false; - private static Predicate block_optouts = (block) -> false; - private static Predicate item_optouts = (item) -> false; - - public static void init(String modid, Logger logger) { - NAME = new ResourceLocation(modid, "optional"); - } - - public static void on_config(boolean enable_experimental, boolean disable_all_recipes, - Predicate block_optout_provider, - Predicate item_optout_provider) { - with_experimental = enable_experimental; - without_recipes = disable_all_recipes; - block_optouts = block_optout_provider; - item_optouts = item_optout_provider; - } - - public OptionalRecipeCondition(ResourceLocation result, List required, List missing, List required_tags, List missing_tags, boolean isexperimental, boolean result_is_tag) { - all_required = required; - any_missing = missing; - all_required_tags = required_tags; - any_missing_tags = missing_tags; - this.result = result; - this.result_is_tag = result_is_tag; - experimental = isexperimental; - } - - @Override - public ResourceLocation getID() { - return NAME; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("Optional recipe, all-required: ["); - for (ResourceLocation e : all_required) sb.append(e.toString()).append(","); - for (ResourceLocation e : all_required_tags) sb.append("#").append(e.toString()).append(","); - sb.delete(sb.length() - 1, sb.length()).append("], any-missing: ["); - for (ResourceLocation e : any_missing) sb.append(e.toString()).append(","); - for (ResourceLocation e : any_missing_tags) sb.append("#").append(e.toString()).append(","); - sb.delete(sb.length() - 1, sb.length()).append("]"); - if (experimental) sb.append(" EXPERIMENTAL"); - return sb.toString(); - } - - @Override - public boolean test(IContext context) { - if (without_recipes) return false; - if ((experimental) && (!with_experimental)) return false; - final IForgeRegistry item_registry = ForgeRegistries.ITEMS; - //final Collection item_tags = SerializationTags.getInstance().getOrEmpty(Registry.ITEM_REGISTRY).getAvailableTags(); - if (result != null) { - boolean item_registered = item_registry.containsKey(result); - if (!item_registered) return false; // required result not registered - if (item_optouts.test(item_registry.getValue(result))) return false; - if (ForgeRegistries.BLOCKS.containsKey(result) && block_optouts.test(ForgeRegistries.BLOCKS.getValue(result))) - return false; - } - if (!all_required.isEmpty()) { - for (ResourceLocation rl : all_required) { - if (!item_registry.containsKey(rl)) return false; - } - } - if (!all_required_tags.isEmpty()) { - for (ResourceLocation rl : all_required_tags) { - if (item_registry.tags().getTagNames().noneMatch(tk -> tk.location().equals(rl))) - return false; // if(!item_tags.contains(rl)) return false; - } - } - if (!any_missing.isEmpty()) { - for (ResourceLocation rl : any_missing) { - if (!item_registry.containsKey(rl)) return true; - } - return false; - } - if (!any_missing_tags.isEmpty()) { - for (ResourceLocation rl : any_missing_tags) { - if (item_registry.tags().getTagNames().noneMatch(tk -> tk.location().equals(rl))) - return true; // if(!item_tags.contains(rl)) return true; - } - return false; - } - return true; - } - - public static class Serializer implements IConditionSerializer { - public static final Serializer INSTANCE = new Serializer(); - - @Override - public ResourceLocation getID() { - return OptionalRecipeCondition.NAME; - } - - @Override - public void write(JsonObject json, OptionalRecipeCondition condition) { - JsonArray required = new JsonArray(); - JsonArray missing = new JsonArray(); - for (ResourceLocation e : condition.all_required) required.add(e.toString()); - for (ResourceLocation e : condition.any_missing) missing.add(e.toString()); - json.add("required", required); - json.add("missing", missing); - if (condition.result != null) { - json.addProperty("result", (condition.result_is_tag ? "#" : "") + condition.result); - } - } - - @Override - public OptionalRecipeCondition read(JsonObject json) { - List required = new ArrayList<>(); - List missing = new ArrayList<>(); - List required_tags = new ArrayList<>(); - List missing_tags = new ArrayList<>(); - ResourceLocation result = null; - boolean experimental = false; - boolean result_is_tag = false; - if (json.has("result")) { - String s = json.get("result").getAsString(); - if (s.startsWith("#")) { - result = new ResourceLocation(s.substring(1)); - result_is_tag = true; - } else { - result = new ResourceLocation(s); - } - } - if (json.has("required")) { - for (JsonElement e : GsonHelper.getAsJsonArray(json, "required")) { - String s = e.getAsString(); - if (s.startsWith("#")) { - required_tags.add(new ResourceLocation(s.substring(1))); - } else { - required.add(new ResourceLocation(s)); - } - } - } - if (json.has("missing")) { - for (JsonElement e : GsonHelper.getAsJsonArray(json, "missing")) { - String s = e.getAsString(); - if (s.startsWith("#")) { - missing_tags.add(new ResourceLocation(s.substring(1))); - } else { - missing.add(new ResourceLocation(s)); - } - } - } - if (json.has("experimental")) experimental = json.get("experimental").getAsBoolean(); - return new OptionalRecipeCondition(result, required, missing, required_tags, missing_tags, experimental, result_is_tag); - } - } -} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/Overlay.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/Overlay.java deleted file mode 100644 index 43f71cc..0000000 --- a/src/main/java/dev/zontreck/libzontreck/edlibmc/Overlay.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * @file Overlay.java - * @author Stefan Wilhelm (wile) - * @copyright (C) 2020 Stefan Wilhelm - * @license MIT (see https://opensource.org/licenses/MIT) - * - * Renders status messages in one line. - */ -package dev.zontreck.libzontreck.edlibmc; - -import com.mojang.blaze3d.platform.Window; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.Font; -import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.gui.screens.Screen; -import net.minecraft.client.multiplayer.ClientLevel; -import net.minecraft.client.player.LocalPlayer; -import net.minecraft.client.renderer.LightTexture; -import net.minecraft.client.renderer.MultiBufferSource; -import net.minecraft.client.renderer.texture.OverlayTexture; -import net.minecraft.core.BlockPos; -import net.minecraft.network.chat.Component; -import net.minecraft.util.Mth; -import net.minecraft.util.Tuple; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.level.LightLayer; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.api.distmarker.OnlyIn; -import net.minecraftforge.fml.DistExecutor; - -import javax.annotation.Nullable; -import java.util.Optional; - - -public class Overlay { - public static void show(Player player, final Component message) { - Networking.OverlayTextMessage.sendToPlayer(player, message, 3000); - } - - public static void show(Player player, final Component message, int delay) { - Networking.OverlayTextMessage.sendToPlayer(player, message, delay); - } - - public static void show(BlockState state, BlockPos pos) { - show(state, pos, 100); - } - - public static void show(BlockState state, BlockPos pos, int displayTimeoutMs) { - DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> (() -> TextOverlayGui.show(state, pos, displayTimeoutMs))); - } // Only called when client side - - // ----------------------------------------------------------------------------- - // Client side handler - // ----------------------------------------------------------------------------- - - @OnlyIn(Dist.CLIENT) - public static class TextOverlayGui extends Screen { - public static final TextOverlayGui INSTANCE = new TextOverlayGui(); - private static final Component EMPTY_TEXT = Component.literal(""); - private static final BlockState EMPTY_STATE = null; - private static double overlay_y_ = 0.75; - private static int text_color_ = 0x00ffaa00; - private static int border_color_ = 0xaa333333; - private static int background_color1_ = 0xaa333333; - private static int background_color2_ = 0xaa444444; - private static long text_deadline_ = 0; - private static Component text_ = EMPTY_TEXT; - private static long state_deadline_ = 0; - private static @Nullable BlockState state_ = EMPTY_STATE; - private static BlockPos pos_ = BlockPos.ZERO; - - public static void on_config(double overlay_y) { - on_config(overlay_y, 0x00ffaa00, 0xaa333333, 0xaa333333, 0xaa444444); - } - - public static void on_config(double overlay_y, int text_color, int border_color, int background_color1, int background_color2) { - overlay_y_ = overlay_y; - text_color_ = text_color; - border_color_ = border_color; - background_color1_ = background_color1; - background_color2_ = background_color2; - } - - public static synchronized Component text() { - return text_; - } - - public static synchronized long deadline() { - return text_deadline_; - } - - public static synchronized void hide() { - text_deadline_ = 0; - text_ = EMPTY_TEXT; - } - - public static synchronized void show(Component s, int displayTimeoutMs) { - text_ = (s == null) ? (EMPTY_TEXT) : (s.copy()); - text_deadline_ = System.currentTimeMillis() + displayTimeoutMs; - } - - public static synchronized void show(String s, int displayTimeoutMs) { - text_ = ((s == null) || (s.isEmpty())) ? (EMPTY_TEXT) : (Component.literal(s)); - text_deadline_ = System.currentTimeMillis() + displayTimeoutMs; - } - - public static synchronized void show(BlockState state, BlockPos pos, int displayTimeoutMs) { - pos_ = new BlockPos(pos); - state_ = state; - state_deadline_ = System.currentTimeMillis() + displayTimeoutMs; - } - - private static synchronized Optional> state_pos() { - return ((state_deadline_ < System.currentTimeMillis()) || (state_ == EMPTY_STATE)) ? Optional.empty() : Optional.of(new Tuple<>(state_, pos_)); - } - - TextOverlayGui() { - super(Component.literal("")); - } - - public void onRenderGui(final GuiGraphics mxs) { - if (deadline() < System.currentTimeMillis()) return; - if (text() == EMPTY_TEXT) return; - String txt = text().getString(); - if (txt.isEmpty()) return; - final net.minecraft.client.Minecraft mc = net.minecraft.client.Minecraft.getInstance(); - final Window win = mc.getWindow(); - final Font fr = mc.font; - final boolean was_unicode = fr.isBidirectional(); - final int cx = win.getGuiScaledWidth() / 2; - final int cy = (int) (win.getGuiScaledHeight() * overlay_y_); - final int w = fr.width(txt); - final int h = fr.lineHeight; - mxs.fillGradient(cx - (w / 2) - 3, cy - 2, cx + (w / 2) + 2, cy + h + 2, 0xaa333333, 0xaa444444); - mxs.hLine(cx - (w / 2) - 3, cx + (w / 2) + 2, cy - 2, 0xaa333333); - mxs.hLine(cx - (w / 2) - 3, cx + (w / 2) + 2, cy + h + 2, 0xaa333333); - mxs.vLine(cx - (w / 2) - 3, cy - 2, cy + h + 2, 0xaa333333); - mxs.vLine(cx + (w / 2) + 2, cy - 2, cy + h + 2, 0xaa333333); - mxs.drawCenteredString(fr, text(), cx, cy + 1, 0x00ffaa00); - } - - @SuppressWarnings("deprecation") - public void onRenderWorldOverlay(final com.mojang.blaze3d.vertex.PoseStack mxs, final double partialTick) { - final Optional> sp = state_pos(); - if (sp.isEmpty()) return; - final ClientLevel world = Minecraft.getInstance().level; - final LocalPlayer player = Minecraft.getInstance().player; - if ((player == null) || (world == null)) return; - final BlockState state = sp.get().getA(); - final BlockPos pos = sp.get().getB(); - final int light = (world.hasChunkAt(pos)) ? LightTexture.pack(world.getBrightness(LightLayer.BLOCK, pos), world.getBrightness(LightLayer.SKY, pos)) : LightTexture.pack(15, 15); - final MultiBufferSource buffer = Minecraft.getInstance().renderBuffers().bufferSource(); - final double px = Mth.lerp(partialTick, player.xo, player.getX()); - final double py = Mth.lerp(partialTick, player.yo, player.getY()); - final double pz = Mth.lerp(partialTick, player.zo, player.getZ()); - mxs.pushPose(); - mxs.translate((pos.getX() - px), (pos.getY() - py - player.getEyeHeight()), (pos.getZ() - pz)); - Minecraft.getInstance().getBlockRenderer().renderSingleBlock(state, mxs, buffer, light, OverlayTexture.NO_OVERLAY); - mxs.popPose(); - } - - } - -} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/README.md b/src/main/java/dev/zontreck/libzontreck/edlibmc/README.md deleted file mode 100644 index f4bf684..0000000 --- a/src/main/java/dev/zontreck/libzontreck/edlibmc/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Engineer's Decor LibMC -____________ - -As Engineer's Decor has been abandoned and discontinued, i've adopted the LibMC, and split the mod up among my various mods. Engineer's Decor blocks, and items, will now reside in Thresholds. \ No newline at end of file diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/Registries.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/Registries.java deleted file mode 100644 index 02542bc..0000000 --- a/src/main/java/dev/zontreck/libzontreck/edlibmc/Registries.java +++ /dev/null @@ -1,263 +0,0 @@ -/* - * @file Registries.java - * @author Stefan Wilhelm (wile) - * @copyright (C) 2020 Stefan Wilhelm - * @license MIT (see https://opensource.org/licenses/MIT) - * - * Common game registry handling. - */ -package dev.zontreck.libzontreck.edlibmc; - -import net.minecraft.network.chat.Component; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.tags.TagKey; -import net.minecraft.world.entity.EntityType; -import net.minecraft.world.flag.FeatureFlagSet; -import net.minecraft.world.flag.FeatureFlags; -import net.minecraft.world.inventory.MenuType; -import net.minecraft.world.item.BlockItem; -import net.minecraft.world.item.CreativeModeTab; -import net.minecraft.world.item.Item; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.crafting.RecipeSerializer; -import net.minecraft.world.level.ItemLike; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraft.world.level.block.entity.BlockEntityType; -import net.minecraftforge.eventbus.api.IEventBus; -import net.minecraftforge.registries.DeferredRegister; -import net.minecraftforge.registries.ForgeRegistries; -import net.minecraftforge.registries.RegistryObject; - -import javax.annotation.Nonnull; -import java.util.*; -import java.util.function.BiFunction; -import java.util.function.Consumer; -import java.util.function.Supplier; -import java.util.stream.Collectors; - -public class Registries { - private static String modid = null; - private static String creative_tab_icon = ""; - private static CreativeModeTab creative_tab = null; - private static final Map> registered_block_tag_keys = new HashMap<>(); - private static final Map> registered_item_tag_keys = new HashMap<>(); - - private static final Map> registered_blocks = new HashMap<>(); - private static final Map> registered_items = new HashMap<>(); - private static final Map>> registered_block_entity_types = new HashMap<>(); - private static final Map>> registered_entity_types = new HashMap<>(); - private static final Map>> registered_menu_types = new HashMap<>(); - private static final Map>> recipe_serializers = new HashMap<>(); - private static final Map> registered_tabs = new HashMap<>(); - - public static final List> tabItems = new ArrayList<>(); - - - - private static DeferredRegister BLOCKS; - private static DeferredRegister ITEMS; - private static DeferredRegister TABS; - private static DeferredRegister> BLOCK_ENTITIES; - private static DeferredRegister> MENUS; - private static DeferredRegister> ENTITIES; - private static DeferredRegister> RECIPE_SERIALIZERS; - private static List> MOD_REGISTRIES; - - public static void init(String mod_id, String creative_tab_icon_item_name, IEventBus bus) { - modid = mod_id; - creative_tab_icon = creative_tab_icon_item_name; - BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, modid); - ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, modid); - BLOCK_ENTITIES = DeferredRegister.create(ForgeRegistries.BLOCK_ENTITY_TYPES, modid); - MENUS = DeferredRegister.create(ForgeRegistries.MENU_TYPES, modid); - ENTITIES = DeferredRegister.create(ForgeRegistries.ENTITY_TYPES, modid); - RECIPE_SERIALIZERS = DeferredRegister.create(ForgeRegistries.RECIPE_SERIALIZERS, modid); - TABS = DeferredRegister.create(net.minecraft.core.registries.Registries.CREATIVE_MODE_TAB, modid); - - Consumer> consumer = (X)->{ - X.register(bus); - }; - - List.of(BLOCKS, ITEMS, BLOCK_ENTITIES, MENUS, ENTITIES, RECIPE_SERIALIZERS, TABS).forEach(consumer); - } - - // ------------------------------------------------------------------------------------------------------------- - - public static Block getBlock(String block_name) { - return registered_blocks.get(block_name).get(); - } - - public static RegistryObject withTab(RegistryObject item) - { - tabItems.add(item); - - return item; - } - - public static Item getItem(String name) { - return registered_items.get(name).get(); - } - - public static EntityType getEntityType(String name) { - return registered_entity_types.get(name).get(); - } - - public static BlockEntityType getBlockEntityType(String block_name) { - return registered_block_entity_types.get(block_name).get(); - } - - public static MenuType getMenuType(String name) { - return registered_menu_types.get(name).get(); - } - - public static RecipeSerializer getRecipeSerializer(String name) { - return recipe_serializers.get(name).get(); - } - - public static BlockEntityType getBlockEntityTypeOfBlock(String block_name) { - return getBlockEntityType("tet_" + block_name); - } - - public static BlockEntityType getBlockEntityTypeOfBlock(Block block) { - return getBlockEntityTypeOfBlock(ForgeRegistries.BLOCKS.getKey(block).getPath()); - } - - public static MenuType getMenuTypeOfBlock(String name) { - return getMenuType("ct_" + name); - } - - public static MenuType getMenuTypeOfBlock(Block block) { - return getMenuTypeOfBlock(ForgeRegistries.BLOCKS.getKey(block).getPath()); - } - - public static TagKey getBlockTagKey(String name) { - return registered_block_tag_keys.get(name); - } - - public static TagKey getItemTagKey(String name) { - return registered_item_tag_keys.get(name); - } - - // ------------------------------------------------------------------------------------------------------------- - - @Nonnull - public static List getRegisteredBlocks() { - return Collections.unmodifiableList(registered_blocks.values().stream().map(RegistryObject::get).toList()); - } - - @Nonnull - public static List getRegisteredItems() { - return Collections.unmodifiableList(registered_items.values().stream().map(RegistryObject::get).toList()); - } - - @Nonnull - public static List> getRegisteredBlockEntityTypes() { - return Collections.unmodifiableList(registered_block_entity_types.values().stream().map(RegistryObject::get).toList()); - } - - @Nonnull - public static List> getRegisteredEntityTypes() { - return Collections.unmodifiableList(registered_entity_types.values().stream().map(RegistryObject::get).toList()); - } - - // ------------------------------------------------------------------------------------------------------------- - - public static void addItem(String registry_name, Supplier supplier) { - registered_items.put(registry_name, withTab(ITEMS.register(registry_name, supplier))); - - } - - public static void addBlock(String registry_name, Supplier block_supplier) { - registered_blocks.put(registry_name, BLOCKS.register(registry_name, block_supplier)); - registered_items.put(registry_name, withTab(ITEMS.register(registry_name, () -> new BlockItem(registered_blocks.get(registry_name).get(), (new Item.Properties()))))); - } - - public static void addCreativeModeTab(String id, Component title, ItemStack icon) - { - registered_tabs.put(id, TABS.register(id, ()-> CreativeModeTab - .builder() - .icon(()->icon) - .title(title) - .withSearchBar() - - .displayItems((display, output) -> tabItems.forEach(it->output.accept(it.get()))) - - .build() - )); - } - - public static void addBlock(String registry_name, Supplier block_supplier, Supplier item_supplier) { - registered_blocks.put(registry_name, BLOCKS.register(registry_name, block_supplier)); - registered_items.put(registry_name, ITEMS.register(registry_name, item_supplier)); - } - - public static void addBlockEntityType(String registry_name, BlockEntityType.BlockEntitySupplier ctor, String... block_names) { - registered_block_entity_types.put(registry_name, BLOCK_ENTITIES.register(registry_name, () -> { - final Block[] blocks = Arrays.stream(block_names).map(s -> { - Block b = BLOCKS.getEntries().stream().filter((ro) -> ro.getId().getPath().equals(s)).findFirst().map(RegistryObject::get).orElse(null); - if (b == null) Auxiliaries.logError("registered_blocks does not encompass '" + s + "'"); - return b; - }).filter(Objects::nonNull).collect(Collectors.toList()).toArray(new Block[]{}); - return BlockEntityType.Builder.of(ctor, blocks).build(null); - })); - } - - public static > void addEntityType(String registry_name, Supplier> supplier) { - registered_entity_types.put(registry_name, ENTITIES.register(registry_name, supplier)); - } - - public static > void addMenuType(String registry_name, MenuType.MenuSupplier supplier, FeatureFlagSet flags) { - registered_menu_types.put(registry_name, MENUS.register(registry_name, () -> new MenuType<>(supplier, flags))); - } - - public static void addRecipeSerializer(String registry_name, Supplier> serializer_supplier) { - recipe_serializers.put(registry_name, RECIPE_SERIALIZERS.register(registry_name, serializer_supplier)); - } - - public static void addOptionalBlockTag(String tag_name, ResourceLocation... default_blocks) { - final Set> default_suppliers = new HashSet<>(); - for (ResourceLocation rl : default_blocks) default_suppliers.add(() -> ForgeRegistries.BLOCKS.getValue(rl)); - final TagKey key = ForgeRegistries.BLOCKS.tags().createOptionalTagKey(new ResourceLocation(modid, tag_name), default_suppliers); - registered_block_tag_keys.put(tag_name, key); - } - - public static void addOptionaItemTag(String tag_name, ResourceLocation... default_items) { - final Set> default_suppliers = new HashSet<>(); - for (ResourceLocation rl : default_items) default_suppliers.add(() -> ForgeRegistries.ITEMS.getValue(rl)); - final TagKey key = ForgeRegistries.ITEMS.tags().createOptionalTagKey(new ResourceLocation(modid, tag_name), default_suppliers); - registered_item_tag_keys.put(tag_name, key); - } - - // ------------------------------------------------------------------------------------------------------------- - - @Deprecated - /** - * This function is to be removed as it cannot add items to a tab anymore - */ - public static void addBlock(String registry_name, Supplier block_supplier, BiFunction item_builder) { - addBlock(registry_name, block_supplier, () -> item_builder.apply(registered_blocks.get(registry_name).get(), (new Item.Properties()))); - } - - public static void addBlock(String registry_name, Supplier block_supplier, BlockEntityType.BlockEntitySupplier block_entity_ctor) { - addBlock(registry_name, block_supplier); - addBlockEntityType("tet_" + registry_name, block_entity_ctor, registry_name); - } - - public static void addBlock(String registry_name, Supplier block_supplier, BiFunction item_builder, BlockEntityType.BlockEntitySupplier block_entity_ctor) { - addBlock(registry_name, block_supplier, item_builder); - addBlockEntityType("tet_" + registry_name, block_entity_ctor, registry_name); - } - - public static void addBlock(String registry_name, Supplier block_supplier, BiFunction item_builder, BlockEntityType.BlockEntitySupplier block_entity_ctor, MenuType.MenuSupplier menu_type_supplier, FeatureFlagSet menuFlags) { - addBlock(registry_name, block_supplier, item_builder); - addBlockEntityType("tet_" + registry_name, block_entity_ctor, registry_name); - addMenuType("ct_" + registry_name, menu_type_supplier, menuFlags); - } - - public static void addBlock(String registry_name, Supplier block_supplier, BlockEntityType.BlockEntitySupplier block_entity_ctor, MenuType.MenuSupplier menu_type_supplier, FeatureFlagSet menuFlags) { - addBlock(registry_name, block_supplier, block_entity_ctor); - addMenuType("ct_" + registry_name, menu_type_supplier, menuFlags); - } - -} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/RfEnergy.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/RfEnergy.java deleted file mode 100644 index 8636926..0000000 --- a/src/main/java/dev/zontreck/libzontreck/edlibmc/RfEnergy.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * @file RfEnergy.java - * @author Stefan Wilhelm (wile) - * @copyright (C) 2020 Stefan Wilhelm - * @license MIT (see https://opensource.org/licenses/MIT) - * - * General RF/FE energy handling functionality. - */ -package dev.zontreck.libzontreck.edlibmc; - -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.util.Mth; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraftforge.common.capabilities.ForgeCapabilities; -import net.minecraftforge.common.util.LazyOptional; -import net.minecraftforge.energy.IEnergyStorage; - -import javax.annotation.Nullable; - -public class RfEnergy { - public static int feed(Level world, BlockPos pos, @Nullable Direction side, int rf_energy) { - final BlockEntity te = world.getBlockEntity(pos); - if (te == null) return 0; - final IEnergyStorage es = te.getCapability(ForgeCapabilities.ENERGY, side).orElse(null); - if (es == null) return 0; - return es.receiveEnergy(rf_energy, false); - } - - public static class Battery implements IEnergyStorage { - protected int capacity_; - protected int charge_rate_; - protected int discharge_rate_; - protected int energy_; - - public Battery(int capacity) { - this(capacity, capacity); - } - - public Battery(int capacity, int transfer_rate) { - this(capacity, transfer_rate, transfer_rate, 0); - } - - public Battery(int capacity, int charge_rate, int discharge_rate) { - this(capacity, charge_rate, discharge_rate, 0); - } - - public Battery(int capacity, int charge_rate, int discharge_rate, int energy) { - capacity_ = Math.max(capacity, 1); - charge_rate_ = Mth.clamp(charge_rate, 0, capacity_); - discharge_rate_ = Mth.clamp(discharge_rate, 0, capacity_); - energy_ = Mth.clamp(energy, 0, capacity_); - } - - // --------------------------------------------------------------------------------------------------- - - public Battery setMaxEnergyStored(int capacity) { - capacity_ = Math.max(capacity, 1); - return this; - } - - public Battery setEnergyStored(int energy) { - energy_ = Mth.clamp(energy, 0, capacity_); - return this; - } - - public Battery setChargeRate(int in_rate) { - charge_rate_ = Mth.clamp(in_rate, 0, capacity_); - return this; - } - - public Battery setDischargeRate(int out_rate) { - discharge_rate_ = Mth.clamp(out_rate, 0, capacity_); - return this; - } - - public int getChargeRate() { - return charge_rate_; - } - - public int getDischargeRate() { - return discharge_rate_; - } - - public boolean isEmpty() { - return energy_ <= 0; - } - - public boolean isFull() { - return energy_ >= capacity_; - } - - public int getSOC() { - return (int) Mth.clamp((100.0 * energy_ / capacity_ + .5), 0, 100); - } - - public int getComparatorOutput() { - return (int) Mth.clamp((15.0 * energy_ / capacity_ + .2), 0, 15); - } - - public boolean draw(int energy) { - if (energy_ < energy) return false; - energy_ -= energy; - return true; - } - - public boolean feed(int energy) { - energy_ = Math.min(energy_ + energy, capacity_); - return energy_ >= capacity_; - } - - public Battery clear() { - energy_ = 0; - return this; - } - - public Battery load(CompoundTag nbt, String key) { - setEnergyStored(nbt.getInt(key)); - return this; - } - - public Battery load(CompoundTag nbt) { - return load(nbt, "Energy"); - } - - public CompoundTag save(CompoundTag nbt, String key) { - nbt.putInt(key, energy_); - return nbt; - } - - public CompoundTag save(CompoundTag nbt) { - return save(nbt, "Energy"); - } - - public LazyOptional createEnergyHandler() { - return LazyOptional.of(() -> this); - } - - // IEnergyStorage ------------------------------------------------------------------------------------ - - @Override - public int receiveEnergy(int feed_energy, boolean simulate) { - if (!canReceive()) return 0; - int e = Math.min(Math.min(charge_rate_, feed_energy), capacity_ - energy_); - if (!simulate) energy_ += e; - return e; - } - - @Override - public int extractEnergy(int draw_energy, boolean simulate) { - if (!canExtract()) return 0; - int e = Math.min(Math.min(discharge_rate_, draw_energy), energy_); - if (!simulate) energy_ -= e; - return e; - } - - @Override - public int getEnergyStored() { - return energy_; - } - - @Override - public int getMaxEnergyStored() { - return capacity_; - } - - @Override - public boolean canExtract() { - return discharge_rate_ > 0; - } - - @Override - public boolean canReceive() { - return charge_rate_ > 0; - } - - } -} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/RsSignals.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/RsSignals.java deleted file mode 100644 index 70509bd..0000000 --- a/src/main/java/dev/zontreck/libzontreck/edlibmc/RsSignals.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * @file RsSignals.java - * @author Stefan Wilhelm (wile) - * @copyright (C) 2020 Stefan Wilhelm - * @license MIT (see https://opensource.org/licenses/MIT) - * - * General redstone signal related functionality. - */ -package dev.zontreck.libzontreck.edlibmc; - -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.world.Container; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.level.BlockGetter; -import net.minecraft.world.level.block.state.BlockState; - -import javax.annotation.Nullable; - -public class RsSignals { - - public static boolean hasSignalConnector(BlockState state, BlockGetter world, BlockPos pos, @Nullable Direction realSide) { - return state.isSignalSource(); - } - - public static int fromContainer(@Nullable Container container) { - if (container == null) return 0; - final double max = container.getMaxStackSize(); - if (max <= 0) return 0; - boolean nonempty = false; - double fill_level = 0; - for (int i = 0; i < container.getContainerSize(); ++i) { - ItemStack stack = container.getItem(i); - if (stack.isEmpty() || (stack.getMaxStackSize() <= 0)) continue; - fill_level += ((double) stack.getCount()) / Math.min(max, stack.getMaxStackSize()); - nonempty = true; - } - fill_level /= container.getContainerSize(); - return (int) (Math.floor(fill_level * 14) + (nonempty ? 1 : 0)); // vanilla compliant calculation. - } - -} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/SidedProxy.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/SidedProxy.java deleted file mode 100644 index ba5f04b..0000000 --- a/src/main/java/dev/zontreck/libzontreck/edlibmc/SidedProxy.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * @file SidedProxy.java - * @author Stefan Wilhelm (wile) - * @copyright (C) 2020 Stefan Wilhelm - * @license MIT (see https://opensource.org/licenses/MIT) - * - * General client/server sidedness selection proxy. - */ -package dev.zontreck.libzontreck.edlibmc; - -import net.minecraft.client.Minecraft; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.level.Level; -import net.minecraftforge.fml.DistExecutor; - -import javax.annotation.Nullable; -import java.util.Optional; - -public class SidedProxy { - @Nullable - public static Player getPlayerClientSide() { - return proxy.getPlayerClientSide(); - } - - @Nullable - public static Level getWorldClientSide() { - return proxy.getWorldClientSide(); - } - - @Nullable - public static Minecraft mc() { - return proxy.mc(); - } - - public static Optional isCtrlDown() { - return proxy.isCtrlDown(); - } - - public static Optional isShiftDown() { - return proxy.isShiftDown(); - } - - public static Optional getClipboard() { - return proxy.getClipboard(); - } - - public static boolean setClipboard(String text) { - return proxy.setClipboard(text); - } - - // -------------------------------------------------------------------------------------------------------- - - private static final ISidedProxy proxy = DistExecutor.unsafeRunForDist(() -> ClientProxy::new, () -> ServerProxy::new); - - private interface ISidedProxy { - default @Nullable Player getPlayerClientSide() { - return null; - } - - default @Nullable Level getWorldClientSide() { - return null; - } - - default @Nullable Minecraft mc() { - return null; - } - - default Optional isCtrlDown() { - return Optional.empty(); - } - - default Optional isShiftDown() { - return Optional.empty(); - } - - default Optional getClipboard() { - return Optional.empty(); - } - - default boolean setClipboard(String text) { - return false; - } - } - - private static final class ClientProxy implements ISidedProxy { - public @Nullable Player getPlayerClientSide() { - return Minecraft.getInstance().player; - } - - public @Nullable Level getWorldClientSide() { - return Minecraft.getInstance().level; - } - - public @Nullable Minecraft mc() { - return Minecraft.getInstance(); - } - - public Optional isCtrlDown() { - return Optional.of(Auxiliaries.isCtrlDown()); - } - - public Optional isShiftDown() { - return Optional.of(Auxiliaries.isShiftDown()); - } - - public Optional getClipboard() { - return (mc() == null) ? Optional.empty() : Optional.of(net.minecraft.client.gui.font.TextFieldHelper.getClipboardContents(mc())); - } - - public boolean setClipboard(String text) { - if (mc() == null) { - return false; - } - net.minecraft.client.gui.font.TextFieldHelper.setClipboardContents(Minecraft.getInstance(), text); - return true; - } - } - - private static final class ServerProxy implements ISidedProxy { - } - -} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/SlabSliceBlock.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/SlabSliceBlock.java deleted file mode 100644 index 40e3401..0000000 --- a/src/main/java/dev/zontreck/libzontreck/edlibmc/SlabSliceBlock.java +++ /dev/null @@ -1,228 +0,0 @@ -/* - * @file SlabSliceBlock.java - * @author Stefan Wilhelm (wile) - * @copyright (C) 2020 Stefan Wilhelm - * @license MIT (see https://opensource.org/licenses/MIT) - * - * Half slab ("slab slices") characteristics class. Actually - * it's now a quarter slab, but who cares. - */ -package dev.zontreck.libzontreck.edlibmc; - -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.network.chat.Component; -import net.minecraft.sounds.SoundSource; -import net.minecraft.world.entity.EntityType; -import net.minecraft.world.entity.SpawnPlacements; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.TooltipFlag; -import net.minecraft.world.item.context.BlockPlaceContext; -import net.minecraft.world.level.BlockGetter; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.LevelAccessor; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.Mirror; -import net.minecraft.world.level.block.Rotation; -import net.minecraft.world.level.block.SoundType; -import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraft.world.level.block.state.BlockBehaviour; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.state.StateDefinition; -import net.minecraft.world.level.block.state.properties.IntegerProperty; -import net.minecraft.world.level.material.Fluid; -import net.minecraft.world.level.material.FluidState; -import net.minecraft.world.phys.AABB; -import net.minecraft.world.phys.Vec3; -import net.minecraft.world.phys.shapes.CollisionContext; -import net.minecraft.world.phys.shapes.Shapes; -import net.minecraft.world.phys.shapes.VoxelShape; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.api.distmarker.OnlyIn; - -import javax.annotation.Nullable; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -public class SlabSliceBlock extends StandardBlocks.WaterLoggable implements StandardBlocks.IStandardBlock { - public static final IntegerProperty PARTS = IntegerProperty.create("parts", 0, 14); - - protected static final VoxelShape[] AABBs = { - Shapes.create(new AABB(0, 0. / 16, 0, 1, 2. / 16, 1)), - Shapes.create(new AABB(0, 0. / 16, 0, 1, 4. / 16, 1)), - Shapes.create(new AABB(0, 0. / 16, 0, 1, 6. / 16, 1)), - Shapes.create(new AABB(0, 0. / 16, 0, 1, 8. / 16, 1)), - Shapes.create(new AABB(0, 0. / 16, 0, 1, 10. / 16, 1)), - Shapes.create(new AABB(0, 0. / 16, 0, 1, 12. / 16, 1)), - Shapes.create(new AABB(0, 0. / 16, 0, 1, 14. / 16, 1)), - Shapes.create(new AABB(0, 0. / 16, 0, 1, 16. / 16, 1)), - Shapes.create(new AABB(0, 2. / 16, 0, 1, 16. / 16, 1)), - Shapes.create(new AABB(0, 4. / 16, 0, 1, 16. / 16, 1)), - Shapes.create(new AABB(0, 6. / 16, 0, 1, 16. / 16, 1)), - Shapes.create(new AABB(0, 8. / 16, 0, 1, 16. / 16, 1)), - Shapes.create(new AABB(0, 10. / 16, 0, 1, 16. / 16, 1)), - Shapes.create(new AABB(0, 12. / 16, 0, 1, 16. / 16, 1)), - Shapes.create(new AABB(0, 14. / 16, 0, 1, 16. / 16, 1)), - Shapes.create(new AABB(0, 0, 0, 1, 1, 1)) // <- with 4bit fill - }; - - protected static final int[] num_slabs_contained_in_parts_ = {1, 2, 3, 4, 5, 6, 7, 8, 7, 6, 5, 4, 3, 2, 1, 0x1}; // <- with 4bit fill - private static boolean with_pickup = false; - - public static void on_config(boolean direct_slab_pickup) { - with_pickup = direct_slab_pickup; - } - - public SlabSliceBlock(long config, BlockBehaviour.Properties builder) { - super(config, builder); - } - - protected boolean is_cube(BlockState state) { - return state.getValue(PARTS) == 0x07; - } - - @Override - @OnlyIn(Dist.CLIENT) - public void appendHoverText(ItemStack stack, @Nullable BlockGetter world, List tooltip, TooltipFlag flag) { - if (!Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true)) return; - if (with_pickup) Auxiliaries.Tooltip.addInformation("engineersdecor.tooltip.slabpickup", tooltip); - } - - @Override - public RenderTypeHint getRenderTypeHint() { - return (((config & StandardBlocks.CFG_TRANSLUCENT) != 0) ? (RenderTypeHint.TRANSLUCENT) : (RenderTypeHint.CUTOUT)); - } - - @Override - public boolean isPossibleToRespawnInThis(BlockState p_279289_) { - return false; - } - - @Override - public boolean isValidSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType entityType) { - return false; - } - - @Override - public VoxelShape getShape(BlockState state, BlockGetter source, BlockPos pos, CollisionContext selectionContext) { - return AABBs[state.getValue(PARTS) & 0xf]; - } - - @Override - public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext) { - return getShape(state, world, pos, selectionContext); - } - - @Override - protected void createBlockStateDefinition(StateDefinition.Builder builder) { - super.createBlockStateDefinition(builder); - builder.add(PARTS); - } - - @Override - @Nullable - public BlockState getStateForPlacement(BlockPlaceContext context) { - final BlockPos pos = context.getClickedPos(); - BlockState state = context.getLevel().getBlockState(pos); - if (state.getBlock() == this) { - int parts = state.getValue(PARTS); - if (parts == 7) return null; // -> is already a full block. - parts += (parts < 7) ? 1 : -1; - if (parts == 7) state = state.setValue(WATERLOGGED, false); - return state.setValue(PARTS, parts); - } else { - final Direction face = context.getClickedFace(); - final BlockState placement_state = super.getStateForPlacement(context); // fluid state - if (face == Direction.UP) return placement_state.setValue(PARTS, 0); - if (face == Direction.DOWN) return placement_state.setValue(PARTS, 14); - if (!face.getAxis().isHorizontal()) return placement_state; - final boolean isupper = ((context.getClickLocation().y() - context.getClickedPos().getY()) > 0.5); - return placement_state.setValue(PARTS, isupper ? 14 : 0); - } - } - - @Override - @SuppressWarnings("deprecation") - public boolean canBeReplaced(BlockState state, BlockPlaceContext context) { - if (context.getItemInHand().getItem() != this.asItem()) return false; - if (!context.replacingClickedOnBlock()) return true; - final Direction face = context.getClickedFace(); - final int parts = state.getValue(PARTS); - if (parts == 7) return false; - if ((face == Direction.UP) && (parts < 7)) return true; - if ((face == Direction.DOWN) && (parts > 7)) return true; - if (!face.getAxis().isHorizontal()) return false; - final boolean isupper = ((context.getClickLocation().y() - context.getClickedPos().getY()) > 0.5); - return isupper ? (parts == 0) : (parts == 1); - } - - @Override - @SuppressWarnings("deprecation") - public BlockState rotate(BlockState state, Rotation rot) { - return state; - } - - @Override - @SuppressWarnings("deprecation") - public BlockState mirror(BlockState state, Mirror mirrorIn) { - return state; - } - - @Override - public boolean hasDynamicDropList() { - return true; - } - - @Override - public List dropList(BlockState state, Level world, BlockEntity te, boolean explosion) { - return new ArrayList<>(Collections.singletonList(new ItemStack(this.asItem(), num_slabs_contained_in_parts_[state.getValue(PARTS) & 0xf]))); - } - - @Override - @SuppressWarnings("deprecation") - public void attack(BlockState state, Level world, BlockPos pos, Player player) { - if ((world.isClientSide) || (!with_pickup)) return; - final ItemStack stack = player.getMainHandItem(); - if (stack.isEmpty() || (Block.byItem(stack.getItem()) != this)) return; - if (stack.getCount() >= stack.getMaxStackSize()) return; - Vec3 lv = player.getLookAngle(); - Direction facing = Direction.getNearest((float) lv.x, (float) lv.y, (float) lv.z); - if ((facing != Direction.UP) && (facing != Direction.DOWN)) return; - if (state.getBlock() != this) return; - int parts = state.getValue(PARTS); - if ((facing == Direction.DOWN) && (parts <= 7)) { - if (parts > 0) { - world.setBlock(pos, state.setValue(PARTS, parts - 1), 3); - } else { - world.removeBlock(pos, false); - } - } else if ((facing == Direction.UP) && (parts >= 7)) { - if (parts < 14) { - world.setBlock(pos, state.setValue(PARTS, parts + 1), 3); - } else { - world.removeBlock(pos, false); - } - } else { - return; - } - if (!player.isCreative()) { - stack.grow(1); - if (player.getInventory() != null) player.getInventory().setChanged(); - } - SoundType st = this.getSoundType(state, world, pos, null); - world.playSound(player, pos, st.getPlaceSound(), SoundSource.BLOCKS, (st.getVolume() + 1f) / 2.5f, 0.9f * st.getPitch()); - } - - @Override - public boolean placeLiquid(LevelAccessor world, BlockPos pos, BlockState state, FluidState fluidState) { - return (state.getValue(PARTS) != 14) && (super.placeLiquid(world, pos, state, fluidState)); - } - - @Override - public boolean canPlaceLiquid(BlockGetter world, BlockPos pos, BlockState state, Fluid fluid) { - return (state.getValue(PARTS) != 14) && (super.canPlaceLiquid(world, pos, state, fluid)); - } - -} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardBlocks.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardBlocks.java deleted file mode 100644 index 69343b1..0000000 --- a/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardBlocks.java +++ /dev/null @@ -1,698 +0,0 @@ -/* - * @file StandardBlocks.java - * @author Stefan Wilhelm (wile) - * @copyright (C) 2020 Stefan Wilhelm - * @license MIT (see https://opensource.org/licenses/MIT) - * - * Common functionality class for decor blocks. - */ -package dev.zontreck.libzontreck.edlibmc; - -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.network.chat.Component; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.sounds.SoundEvent; -import net.minecraft.world.entity.EntityType; -import net.minecraft.world.entity.SpawnPlacements; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.TooltipFlag; -import net.minecraft.world.item.context.BlockPlaceContext; -import net.minecraft.world.level.BlockGetter; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.LevelAccessor; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.Mirror; -import net.minecraft.world.level.block.Rotation; -import net.minecraft.world.level.block.SimpleWaterloggedBlock; -import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraft.world.level.block.state.BlockBehaviour; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.state.StateDefinition; -import net.minecraft.world.level.block.state.properties.BlockStateProperties; -import net.minecraft.world.level.block.state.properties.BooleanProperty; -import net.minecraft.world.level.block.state.properties.DirectionProperty; -import net.minecraft.world.level.block.state.properties.EnumProperty; -import net.minecraft.world.level.material.Fluid; -import net.minecraft.world.level.material.FluidState; -import net.minecraft.world.level.material.Fluids; -import net.minecraft.world.level.material.PushReaction; -import net.minecraft.world.level.pathfinder.PathComputationType; -import net.minecraft.world.level.storage.loot.LootContext; -import net.minecraft.world.level.storage.loot.LootParams; -import net.minecraft.world.level.storage.loot.parameters.LootContextParams; -import net.minecraft.world.phys.AABB; -import net.minecraft.world.phys.shapes.BooleanOp; -import net.minecraft.world.phys.shapes.CollisionContext; -import net.minecraft.world.phys.shapes.Shapes; -import net.minecraft.world.phys.shapes.VoxelShape; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.api.distmarker.OnlyIn; - -import javax.annotation.Nullable; -import java.util.*; -import java.util.function.Function; -import java.util.function.Supplier; - - -@SuppressWarnings("deprecation") -public class StandardBlocks { - public static final long CFG_DEFAULT = 0x0000000000000000L; // no special config - public static final long CFG_CUTOUT = 0x0000000000000001L; // cutout rendering - public static final long CFG_MIPPED = 0x0000000000000002L; // cutout mipped rendering - public static final long CFG_TRANSLUCENT = 0x0000000000000004L; // indicates a block/pane is glass like (transparent, etc) - public static final long CFG_WATERLOGGABLE = 0x0000000000000008L; // The derived block extends IWaterLoggable - public static final long CFG_HORIZIONTAL = 0x0000000000000010L; // horizontal block, affects bounding box calculation at construction time and placement - public static final long CFG_LOOK_PLACEMENT = 0x0000000000000020L; // placed in direction the player is looking when placing. - public static final long CFG_FACING_PLACEMENT = 0x0000000000000040L; // placed on the facing the player has clicked. - public static final long CFG_OPPOSITE_PLACEMENT = 0x0000000000000080L; // placed placed in the opposite direction of the face the player clicked. - public static final long CFG_FLIP_PLACEMENT_IF_SAME = 0x0000000000000100L; // placement direction flipped if an instance of the same class was clicked - public static final long CFG_FLIP_PLACEMENT_SHIFTCLICK = 0x0000000000000200L; // placement direction flipped if player is sneaking - public static final long CFG_STRICT_CONNECTIONS = 0x0000000000000400L; // blocks do not connect to similar blocks around (implementation details may vary a bit) - public static final long CFG_AI_PASSABLE = 0x0000000000000800L; // does not block movement path for AI, needed for non-opaque blocks with collision shapes not thin at the bottom or one side. - - public interface IStandardBlock { - default long config() { - return 0; - } - - default boolean hasDynamicDropList() { - return false; - } - - default List dropList(BlockState state, Level world, @Nullable BlockEntity te, boolean explosion) { - return Collections.singletonList((!world.isClientSide()) ? (new ItemStack(state.getBlock().asItem())) : (ItemStack.EMPTY)); - } - - enum RenderTypeHint {SOLID, CUTOUT, CUTOUT_MIPPED, TRANSLUCENT, TRANSLUCENT_NO_CRUMBLING} - - default RenderTypeHint getRenderTypeHint() { - return getRenderTypeHint(config()); - } - - default RenderTypeHint getRenderTypeHint(long config) { - if ((config & CFG_CUTOUT) != 0) return RenderTypeHint.CUTOUT; - if ((config & CFG_MIPPED) != 0) return RenderTypeHint.CUTOUT_MIPPED; - if ((config & CFG_TRANSLUCENT) != 0) return RenderTypeHint.TRANSLUCENT; - return RenderTypeHint.SOLID; - } - } - - public static class BaseBlock extends Block implements IStandardBlock, SimpleWaterloggedBlock { - public static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED; - public final long config; - - public BaseBlock(long conf, BlockBehaviour.Properties properties) { - super(properties); - config = conf; - BlockState state = getStateDefinition().any(); - if ((conf & CFG_WATERLOGGABLE) != 0) state = state.setValue(WATERLOGGED, false); - registerDefaultState(state); - } - - @Override - public long config() { - return config; - } - - @Override - @OnlyIn(Dist.CLIENT) - public void appendHoverText(ItemStack stack, @Nullable BlockGetter world, List tooltip, TooltipFlag flag) { - Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); - } - - @Override - public RenderTypeHint getRenderTypeHint() { - return getRenderTypeHint(config); - } - - @Override - @SuppressWarnings("deprecation") - public boolean isPathfindable(BlockState state, BlockGetter world, BlockPos pos, PathComputationType type) { - return ((config & CFG_AI_PASSABLE) != 0) && (super.isPathfindable(state, world, pos, type)); - } - - public boolean hasSignalConnector(BlockState state, BlockGetter world, BlockPos pos, @Nullable Direction side) { - return state.isSignalSource(); - } - - @Override - @SuppressWarnings("deprecation") - public void onRemove(BlockState state, Level world, BlockPos pos, BlockState newState, boolean isMoving) { - final boolean rsup = (state.hasBlockEntity() && (state.getBlock() != newState.getBlock())); - super.onRemove(state, world, pos, newState, isMoving); - if (rsup) world.updateNeighbourForOutputSignal(pos, this); - } - - @Override - public boolean propagatesSkylightDown(BlockState state, BlockGetter reader, BlockPos pos) { - return (((config & CFG_WATERLOGGABLE) == 0) || (!state.getValue(WATERLOGGED))) && super.propagatesSkylightDown(state, reader, pos); - } - - @Override - @SuppressWarnings("deprecation") - public FluidState getFluidState(BlockState state) { - return (((config & CFG_WATERLOGGABLE) != 0) && state.getValue(WATERLOGGED)) ? Fluids.WATER.getSource(false) : super.getFluidState(state); - } - - @Override - @SuppressWarnings("deprecation") - public BlockState updateShape(BlockState state, Direction facing, BlockState facingState, LevelAccessor world, BlockPos pos, BlockPos facingPos) { - if (((config & CFG_WATERLOGGABLE) != 0) && (state.getValue(WATERLOGGED))) - world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world)); - return state; - } - - @Override // SimpleWaterloggedBlock - public boolean canPlaceLiquid(BlockGetter world, BlockPos pos, BlockState state, Fluid fluid) { - return ((config & CFG_WATERLOGGABLE) != 0) && SimpleWaterloggedBlock.super.canPlaceLiquid(world, pos, state, fluid); - } - - @Override // SimpleWaterloggedBlock - public boolean placeLiquid(LevelAccessor world, BlockPos pos, BlockState state, FluidState fluidState) { - return ((config & CFG_WATERLOGGABLE) != 0) && SimpleWaterloggedBlock.super.placeLiquid(world, pos, state, fluidState); - } - - @Override // SimpleWaterloggedBlock - public ItemStack pickupBlock(LevelAccessor world, BlockPos pos, BlockState state) { - return ((config & CFG_WATERLOGGABLE) != 0) ? (SimpleWaterloggedBlock.super.pickupBlock(world, pos, state)) : (ItemStack.EMPTY); - } - - @Override // SimpleWaterloggedBlock - public Optional getPickupSound() { - return ((config & CFG_WATERLOGGABLE) != 0) ? (SimpleWaterloggedBlock.super.getPickupSound()) : Optional.empty(); - } - } - - public static class Cutout extends BaseBlock implements IStandardBlock { - private final VoxelShape vshape; - - public Cutout(long conf, BlockBehaviour.Properties properties) { - this(conf, properties, Auxiliaries.getPixeledAABB(0, 0, 0, 16, 16, 16)); - } - - public Cutout(long conf, BlockBehaviour.Properties properties, AABB aabb) { - this(conf, properties, Shapes.create(aabb)); - } - - public Cutout(long conf, BlockBehaviour.Properties properties, AABB[] aabbs) { - this(conf, properties, Arrays.stream(aabbs).map(Shapes::create).reduce(Shapes.empty(), (shape, aabb) -> Shapes.joinUnoptimized(shape, aabb, BooleanOp.OR))); - } - - public Cutout(long conf, BlockBehaviour.Properties properties, VoxelShape voxel_shape) { - super(conf, properties); - vshape = voxel_shape; - } - - @Override - @SuppressWarnings("deprecation") - public VoxelShape getShape(BlockState state, BlockGetter source, BlockPos pos, CollisionContext selectionContext) { - return vshape; - } - - @Override - @SuppressWarnings("deprecation") - public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext) { - return vshape; - } - - @Override - @Nullable - public BlockState getStateForPlacement(BlockPlaceContext context) { - BlockState state = super.getStateForPlacement(context); - if ((config & CFG_WATERLOGGABLE) != 0) { - FluidState fs = context.getLevel().getFluidState(context.getClickedPos()); - state = state.setValue(WATERLOGGED, fs.getType() == Fluids.WATER); - } - return state; - } - - - @Override - public boolean isPossibleToRespawnInThis(BlockState p_279289_) { - return false; - } - - @Override - @SuppressWarnings("deprecation") - public PushReaction getPistonPushReaction(BlockState state) { - return PushReaction.NORMAL; - } - - @Override - public boolean propagatesSkylightDown(BlockState state, BlockGetter reader, BlockPos pos) { - if ((config & CFG_WATERLOGGABLE) != 0) { - if (state.getValue(WATERLOGGED)) return false; - } - return super.propagatesSkylightDown(state, reader, pos); - } - - @Override - @SuppressWarnings("deprecation") - public FluidState getFluidState(BlockState state) { - if ((config & CFG_WATERLOGGABLE) != 0) { - return state.getValue(WATERLOGGED) ? Fluids.WATER.getSource(false) : super.getFluidState(state); - } - return super.getFluidState(state); - } - - @Override - @SuppressWarnings("deprecation") - public BlockState updateShape(BlockState state, Direction facing, BlockState facingState, LevelAccessor world, BlockPos pos, BlockPos facingPos) { - if ((config & CFG_WATERLOGGABLE) != 0) { - if (state.getValue(WATERLOGGED)) - world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world)); - } - return state; - } - } - - public static class WaterLoggable extends Cutout implements IStandardBlock { - public WaterLoggable(long config, BlockBehaviour.Properties properties) { - super(config | CFG_WATERLOGGABLE, properties); - } - - public WaterLoggable(long config, BlockBehaviour.Properties properties, AABB aabb) { - super(config | CFG_WATERLOGGABLE, properties, aabb); - } - - public WaterLoggable(long config, BlockBehaviour.Properties properties, VoxelShape voxel_shape) { - super(config | CFG_WATERLOGGABLE, properties, voxel_shape); - } - - public WaterLoggable(long config, BlockBehaviour.Properties properties, AABB[] aabbs) { - super(config | CFG_WATERLOGGABLE, properties, aabbs); - } - - @Override - protected void createBlockStateDefinition(StateDefinition.Builder builder) { - super.createBlockStateDefinition(builder); - builder.add(WATERLOGGED); - } - } - - public static class Directed extends Cutout implements IStandardBlock { - public static final DirectionProperty FACING = BlockStateProperties.FACING; - protected final Map vshapes; - - public Directed(long config, BlockBehaviour.Properties properties, final Function, Map> shape_supplier) { - super(config, properties); - registerDefaultState(super.defaultBlockState().setValue(FACING, Direction.UP)); - vshapes = shape_supplier.apply(getStateDefinition().getPossibleStates()); - } - - public Directed(long config, BlockBehaviour.Properties properties, final Supplier> shape_supplier) { - this(config, properties, (states) -> { - final Map vshapes = new HashMap<>(); - final ArrayList indexed_shapes = shape_supplier.get(); - for (BlockState state : states) - vshapes.put(state, indexed_shapes.get(state.getValue(FACING).get3DDataValue())); - return vshapes; - }); - } - - public Directed(long config, BlockBehaviour.Properties properties, final AABB[] unrotatedAABBs) { - this(config, properties, (states) -> { - final boolean is_horizontal = ((config & CFG_HORIZIONTAL) != 0); - Map vshapes = new HashMap<>(); - for (BlockState state : states) { - vshapes.put(state, Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(unrotatedAABBs, state.getValue(FACING), is_horizontal))); - } - return vshapes; - }); - } - - public Directed(long config, BlockBehaviour.Properties properties, final AABB unrotatedAABB) { - this(config, properties, new AABB[]{unrotatedAABB}); - } - - - @Override - public boolean isPossibleToRespawnInThis(BlockState p_279289_) { - return false; - } - - @Override - public boolean isValidSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType entityType) { - return false; - } - - @Override - public VoxelShape getShape(BlockState state, BlockGetter source, BlockPos pos, CollisionContext selectionContext) { - return vshapes.get(state); - } - - @Override - public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext) { - return getShape(state, world, pos, selectionContext); - } - - @Override - protected void createBlockStateDefinition(StateDefinition.Builder builder) { - super.createBlockStateDefinition(builder); - builder.add(FACING); - } - - @Override - @Nullable - public BlockState getStateForPlacement(BlockPlaceContext context) { - BlockState state = super.getStateForPlacement(context); - if (state == null) return null; - Direction facing = context.getClickedFace(); - if ((config & (CFG_HORIZIONTAL | CFG_LOOK_PLACEMENT)) == (CFG_HORIZIONTAL | CFG_LOOK_PLACEMENT)) { - // horizontal placement in direction the player is looking - facing = context.getHorizontalDirection(); - } else if ((config & (CFG_HORIZIONTAL | CFG_LOOK_PLACEMENT)) == (CFG_HORIZIONTAL)) { - // horizontal placement on a face - if (((facing == Direction.UP) || (facing == Direction.DOWN))) return null; - } else if ((config & CFG_LOOK_PLACEMENT) != 0) { - // placement in direction the player is looking, with up and down - facing = context.getNearestLookingDirection(); - } - if ((config & CFG_OPPOSITE_PLACEMENT) != 0) facing = facing.getOpposite(); - if (((config & CFG_FLIP_PLACEMENT_SHIFTCLICK) != 0) && (context.getPlayer() != null) && (context.getPlayer().isShiftKeyDown())) - facing = facing.getOpposite(); - return state.setValue(FACING, facing); - } - } - - public static class AxisAligned extends Cutout implements IStandardBlock { - public static final EnumProperty AXIS = BlockStateProperties.AXIS; - protected final ArrayList vshapes; - - public AxisAligned(long config, BlockBehaviour.Properties properties, final Supplier> shape_supplier) { - super(config, properties); - registerDefaultState(super.defaultBlockState().setValue(AXIS, Direction.Axis.X)); - vshapes = shape_supplier.get(); - } - - public AxisAligned(long config, BlockBehaviour.Properties properties, final AABB[] unrotatedAABBs) { - this(config, properties, () -> new ArrayList<>(Arrays.asList( - Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(unrotatedAABBs, Direction.EAST, false)), - Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(unrotatedAABBs, Direction.UP, false)), - Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(unrotatedAABBs, Direction.SOUTH, false)), - Shapes.block() - ))); - } - - public AxisAligned(long config, BlockBehaviour.Properties properties, final AABB unrotatedAABB) { - this(config, properties, new AABB[]{unrotatedAABB}); - } - - - @Override - public boolean isPossibleToRespawnInThis(BlockState p_279289_) { - return false; - } - - @Override - public boolean isValidSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType entityType) { - return false; - } - - @Override - public VoxelShape getShape(BlockState state, BlockGetter source, BlockPos pos, CollisionContext selectionContext) { - return vshapes.get((state.getValue(AXIS)).ordinal() & 0x3); - } - - @Override - public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext) { - return getShape(state, world, pos, selectionContext); - } - - @Override - protected void createBlockStateDefinition(StateDefinition.Builder builder) { - super.createBlockStateDefinition(builder); - builder.add(AXIS); - } - - @Override - @Nullable - public BlockState getStateForPlacement(BlockPlaceContext context) { - Direction facing; - if ((config & CFG_LOOK_PLACEMENT) != 0) { - facing = context.getNearestLookingDirection(); - } else { - facing = context.getClickedFace(); - } - return super.getStateForPlacement(context).setValue(AXIS, facing.getAxis()); - } - - @Override - @SuppressWarnings("deprecation") - public BlockState rotate(BlockState state, Rotation rotation) { - switch (rotation) { - case CLOCKWISE_90: - case COUNTERCLOCKWISE_90: - switch (state.getValue(AXIS)) { - case X: - return state.setValue(AXIS, Direction.Axis.Z); - case Z: - return state.setValue(AXIS, Direction.Axis.X); - } - } - return state; - } - } - - public static class Horizontal extends Cutout implements IStandardBlock { - public static final DirectionProperty HORIZONTAL_FACING = BlockStateProperties.HORIZONTAL_FACING; - protected final Map vshapes; - protected final Map cshapes; - - public Horizontal(long config, BlockBehaviour.Properties properties, final Function, Map> shape_supplier) { - super(config | CFG_HORIZIONTAL, properties); - registerDefaultState(super.defaultBlockState().setValue(HORIZONTAL_FACING, Direction.NORTH)); - vshapes = shape_supplier.apply(getStateDefinition().getPossibleStates()); - cshapes = shape_supplier.apply(getStateDefinition().getPossibleStates()); - } - - public Horizontal(long config, BlockBehaviour.Properties properties, final Supplier> shape_supplier) { - this(config, properties, (states) -> { - final Map vshapes = new HashMap<>(); - final ArrayList indexed_shapes = shape_supplier.get(); - for (BlockState state : states) - vshapes.put(state, indexed_shapes.get(state.getValue(HORIZONTAL_FACING).get3DDataValue())); - return vshapes; - }); - } - - public Horizontal(long config, BlockBehaviour.Properties properties, final AABB unrotatedAABB) { - this(config, properties, new AABB[]{unrotatedAABB}); - } - - public Horizontal(long config, BlockBehaviour.Properties properties, final AABB[] unrotatedAABBs) { - this(config, properties, (states) -> { - Map vshapes = new HashMap<>(); - for (BlockState state : states) { - vshapes.put(state, Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(unrotatedAABBs, state.getValue(HORIZONTAL_FACING), true))); - } - return vshapes; - }); - } - - @Override - protected void createBlockStateDefinition(StateDefinition.Builder builder) { - super.createBlockStateDefinition(builder); - builder.add(HORIZONTAL_FACING); - } - - @Override - public VoxelShape getShape(BlockState state, BlockGetter source, BlockPos pos, CollisionContext selectionContext) { - return vshapes.get(state); - } - - @Override - public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext) { - return cshapes.get(state); - } - - @Override - @Nullable - public BlockState getStateForPlacement(BlockPlaceContext context) { - BlockState state = super.getStateForPlacement(context); - if (state == null) return null; - Direction facing = context.getClickedFace(); - if ((config & CFG_LOOK_PLACEMENT) != 0) { - // horizontal placement in direction the player is looking - facing = context.getHorizontalDirection(); - } else { - // horizontal placement on a face - facing = ((facing == Direction.UP) || (facing == Direction.DOWN)) ? (context.getHorizontalDirection()) : facing; - } - if ((config & CFG_OPPOSITE_PLACEMENT) != 0) facing = facing.getOpposite(); - if (((config & CFG_FLIP_PLACEMENT_SHIFTCLICK) != 0) && (context.getPlayer() != null) && (context.getPlayer().isShiftKeyDown())) - facing = facing.getOpposite(); - return state.setValue(HORIZONTAL_FACING, facing); - } - - @Override - @SuppressWarnings("deprecation") - public BlockState rotate(BlockState state, Rotation rot) { - return state.setValue(HORIZONTAL_FACING, rot.rotate(state.getValue(HORIZONTAL_FACING))); - } - - @Override - @SuppressWarnings("deprecation") - public BlockState mirror(BlockState state, Mirror mirrorIn) { - return state.rotate(mirrorIn.getRotation(state.getValue(HORIZONTAL_FACING))); - } - } - - public static class DirectedWaterLoggable extends Directed implements IStandardBlock { - public DirectedWaterLoggable(long config, BlockBehaviour.Properties properties, AABB aabb) { - super(config | CFG_WATERLOGGABLE, properties, aabb); - } - - public DirectedWaterLoggable(long config, BlockBehaviour.Properties properties, AABB[] aabbs) { - super(config | CFG_WATERLOGGABLE, properties, aabbs); - } - - public DirectedWaterLoggable(long config, BlockBehaviour.Properties properties, final Function, Map> shape_supplier) { - super(config | CFG_WATERLOGGABLE, properties, shape_supplier); - } - - public DirectedWaterLoggable(long config, BlockBehaviour.Properties properties, final Supplier> shape_supplier) { - super(config | CFG_WATERLOGGABLE, properties, shape_supplier); - } - - @Override - protected void createBlockStateDefinition(StateDefinition.Builder builder) { - super.createBlockStateDefinition(builder); - builder.add(WATERLOGGED); - } - } - - public static class AxisAlignedWaterLoggable extends AxisAligned implements IStandardBlock { - public AxisAlignedWaterLoggable(long config, BlockBehaviour.Properties properties, AABB aabb) { - super(config | CFG_WATERLOGGABLE, properties, aabb); - } - - public AxisAlignedWaterLoggable(long config, BlockBehaviour.Properties properties, AABB[] aabbs) { - super(config | CFG_WATERLOGGABLE, properties, aabbs); - } - - public AxisAlignedWaterLoggable(long config, BlockBehaviour.Properties properties, final Supplier> shape_supplier) { - super(config | CFG_WATERLOGGABLE, properties, shape_supplier); - } - - @Override - protected void createBlockStateDefinition(StateDefinition.Builder builder) { - super.createBlockStateDefinition(builder); - builder.add(WATERLOGGED); - } - } - - public static class HorizontalWaterLoggable extends Horizontal implements IStandardBlock { - public HorizontalWaterLoggable(long config, BlockBehaviour.Properties properties, AABB aabb) { - super(config | CFG_WATERLOGGABLE | CFG_HORIZIONTAL, properties, aabb); - } - - public HorizontalWaterLoggable(long config, BlockBehaviour.Properties properties, AABB[] aabbs) { - super(config | CFG_WATERLOGGABLE | CFG_HORIZIONTAL, properties, aabbs); - } - - public HorizontalWaterLoggable(long config, BlockBehaviour.Properties properties, final Supplier> shape_supplier) { - super(config | CFG_WATERLOGGABLE | CFG_HORIZIONTAL, properties, shape_supplier); - } - - public HorizontalWaterLoggable(long config, BlockBehaviour.Properties properties, final Function, Map> shape_supplier) { - super(config | CFG_WATERLOGGABLE | CFG_HORIZIONTAL, properties, shape_supplier); - } - - @Override - protected void createBlockStateDefinition(StateDefinition.Builder builder) { - super.createBlockStateDefinition(builder); - builder.add(WATERLOGGED); - } - } - - static public class HorizontalFourWayWaterLoggable extends WaterLoggable implements IStandardBlock { - public static final BooleanProperty NORTH = BlockStateProperties.NORTH; - public static final BooleanProperty EAST = BlockStateProperties.EAST; - public static final BooleanProperty SOUTH = BlockStateProperties.SOUTH; - public static final BooleanProperty WEST = BlockStateProperties.WEST; - protected final Map shapes; - protected final Map collision_shapes; - - public HorizontalFourWayWaterLoggable(long config, BlockBehaviour.Properties properties, AABB base_aabb, final AABB[] side_aabb, int railing_height_extension) { - super(config, properties, base_aabb); - Map build_shapes = new HashMap<>(); - Map build_collision_shapes = new HashMap<>(); - for (BlockState state : getStateDefinition().getPossibleStates()) { - { - VoxelShape shape = ((base_aabb.getXsize() == 0) || (base_aabb.getYsize() == 0) || (base_aabb.getZsize() == 0)) ? Shapes.empty() : Shapes.create(base_aabb); - if (state.getValue(NORTH)) - shape = Shapes.joinUnoptimized(shape, Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(side_aabb, Direction.NORTH, true)), BooleanOp.OR); - if (state.getValue(EAST)) - shape = Shapes.joinUnoptimized(shape, Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(side_aabb, Direction.EAST, true)), BooleanOp.OR); - if (state.getValue(SOUTH)) - shape = Shapes.joinUnoptimized(shape, Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(side_aabb, Direction.SOUTH, true)), BooleanOp.OR); - if (state.getValue(WEST)) - shape = Shapes.joinUnoptimized(shape, Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(side_aabb, Direction.WEST, true)), BooleanOp.OR); - if (shape.isEmpty()) shape = Shapes.block(); - build_shapes.put(state.setValue(WATERLOGGED, false), shape); - build_shapes.put(state.setValue(WATERLOGGED, true), shape); - } - { - // how the hack to extend a shape, these are the above with y+4px. - VoxelShape shape = ((base_aabb.getXsize() == 0) || (base_aabb.getYsize() == 0) || (base_aabb.getZsize() == 0)) ? Shapes.empty() : Shapes.create(base_aabb); - if (state.getValue(NORTH)) - shape = Shapes.joinUnoptimized(shape, Auxiliaries.getUnionShape(Auxiliaries.getMappedAABB(Auxiliaries.getRotatedAABB(side_aabb, - Direction.NORTH, true), bb -> bb.expandTowards(0, railing_height_extension, 0))), BooleanOp.OR); - if (state.getValue(EAST)) - shape = Shapes.joinUnoptimized(shape, Auxiliaries.getUnionShape(Auxiliaries.getMappedAABB(Auxiliaries.getRotatedAABB(side_aabb, - Direction.EAST, true), bb -> bb.expandTowards(0, railing_height_extension, 0))), BooleanOp.OR); - if (state.getValue(SOUTH)) - shape = Shapes.joinUnoptimized(shape, Auxiliaries.getUnionShape(Auxiliaries.getMappedAABB(Auxiliaries.getRotatedAABB(side_aabb, - Direction.SOUTH, true), bb -> bb.expandTowards(0, railing_height_extension, 0))), BooleanOp.OR); - if (state.getValue(WEST)) - shape = Shapes.joinUnoptimized(shape, Auxiliaries.getUnionShape(Auxiliaries.getMappedAABB(Auxiliaries.getRotatedAABB(side_aabb, - Direction.WEST, true), bb -> bb.expandTowards(0, railing_height_extension, 0))), BooleanOp.OR); - if (shape.isEmpty()) shape = Shapes.block(); - build_collision_shapes.put(state.setValue(WATERLOGGED, false), shape); - build_collision_shapes.put(state.setValue(WATERLOGGED, true), shape); - } - } - shapes = build_shapes; - collision_shapes = build_collision_shapes; - registerDefaultState(super.defaultBlockState().setValue(NORTH, false).setValue(EAST, false).setValue(SOUTH, false).setValue(WEST, false).setValue(WATERLOGGED, false)); - } - - public HorizontalFourWayWaterLoggable(long config, BlockBehaviour.Properties properties, AABB base_aabb, final AABB side_aabb, int railing_height_extension) { - this(config, properties, base_aabb, new AABB[]{side_aabb}, railing_height_extension); - } - - @Override - protected void createBlockStateDefinition(StateDefinition.Builder builder) { - super.createBlockStateDefinition(builder); - builder.add(NORTH, EAST, SOUTH, WEST); - } - - @Override - @Nullable - public BlockState getStateForPlacement(BlockPlaceContext context) { - return super.getStateForPlacement(context).setValue(NORTH, false).setValue(EAST, false).setValue(SOUTH, false).setValue(WEST, false); - } - - @Override - public VoxelShape getShape(BlockState state, BlockGetter worldIn, BlockPos pos, CollisionContext context) { - return shapes.getOrDefault(state, Shapes.block()); - } - - @Override - public VoxelShape getCollisionShape(BlockState state, BlockGetter worldIn, BlockPos pos, CollisionContext context) { - return collision_shapes.getOrDefault(state, Shapes.block()); - } - - public static BooleanProperty getDirectionProperty(Direction face) { - return switch (face) { - case EAST -> HorizontalFourWayWaterLoggable.EAST; - case SOUTH -> HorizontalFourWayWaterLoggable.SOUTH; - case WEST -> HorizontalFourWayWaterLoggable.WEST; - default -> HorizontalFourWayWaterLoggable.NORTH; - }; - } - } - -} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardDoorBlock.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardDoorBlock.java deleted file mode 100644 index 8c7f10b..0000000 --- a/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardDoorBlock.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * @file StandardDoorBlock.java - * @author Stefan Wilhelm (wile) - * @copyright (C) 2020 Stefan Wilhelm - * @license MIT (see https://opensource.org/licenses/MIT) - * - * Door blocks, almost entirely based on vanilla. - */ -package dev.zontreck.libzontreck.edlibmc; - -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.network.chat.Component; -import net.minecraft.sounds.SoundEvent; -import net.minecraft.sounds.SoundEvents; -import net.minecraft.sounds.SoundSource; -import net.minecraft.world.InteractionHand; -import net.minecraft.world.InteractionResult; -import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.EntityType; -import net.minecraft.world.entity.SpawnPlacements; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.TooltipFlag; -import net.minecraft.world.level.BlockGetter; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.DoorBlock; -import net.minecraft.world.level.block.state.BlockBehaviour; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.state.properties.BlockSetType; -import net.minecraft.world.level.block.state.properties.DoorHingeSide; -import net.minecraft.world.level.block.state.properties.DoubleBlockHalf; -import net.minecraft.world.phys.AABB; -import net.minecraft.world.phys.BlockHitResult; -import net.minecraft.world.phys.shapes.BooleanOp; -import net.minecraft.world.phys.shapes.CollisionContext; -import net.minecraft.world.phys.shapes.Shapes; -import net.minecraft.world.phys.shapes.VoxelShape; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.api.distmarker.OnlyIn; - -import javax.annotation.Nullable; -import java.util.List; - - -public class StandardDoorBlock extends DoorBlock implements StandardBlocks.IStandardBlock { - private final long config_; - protected final VoxelShape[][][][] shapes_; - protected final SoundEvent open_sound_; - protected final SoundEvent close_sound_; - - public StandardDoorBlock(long config, BlockBehaviour.Properties properties, AABB[] open_aabbs_top, AABB[] open_aabbs_bottom, AABB[] closed_aabbs_top, AABB[] closed_aabbs_bottom, SoundEvent open_sound, SoundEvent close_sound, BlockSetType blockSetType) { - super(properties, blockSetType); - VoxelShape[][][][] shapes = new VoxelShape[Direction.values().length][2][2][2]; - for (Direction facing : Direction.values()) { - for (boolean open : new boolean[]{false, true}) { - for (DoubleBlockHalf half : new DoubleBlockHalf[]{DoubleBlockHalf.UPPER, DoubleBlockHalf.LOWER}) { - for (boolean hinge_right : new boolean[]{false, true}) { - VoxelShape shape = Shapes.empty(); - if (facing.getAxis() == Direction.Axis.Y) { - shape = Shapes.block(); - } else { - final AABB[] aabbs = (open) ? ((half == DoubleBlockHalf.UPPER) ? open_aabbs_top : open_aabbs_bottom) : ((half == DoubleBlockHalf.UPPER) ? closed_aabbs_top : closed_aabbs_bottom); - for (AABB e : aabbs) { - AABB aabb = Auxiliaries.getRotatedAABB(e, facing, true); - if (!hinge_right) - aabb = Auxiliaries.getMirroredAABB(aabb, facing.getClockWise().getAxis()); - shape = Shapes.join(shape, Shapes.create(aabb), BooleanOp.OR); - } - } - shapes[facing.ordinal()][open ? 1 : 0][hinge_right ? 1 : 0][half == DoubleBlockHalf.UPPER ? 0 : 1] = shape; - } - } - } - } - config_ = config; - shapes_ = shapes; - open_sound_ = open_sound; - close_sound_ = close_sound; - } - - public StandardDoorBlock(long config, BlockBehaviour.Properties properties, AABB open_aabb, AABB closed_aabb, SoundEvent open_sound, SoundEvent close_sound, BlockSetType blockSetType) { - this(config, properties, new AABB[]{open_aabb}, new AABB[]{open_aabb}, new AABB[]{closed_aabb}, new AABB[]{closed_aabb}, open_sound, close_sound, blockSetType); - } - - public StandardDoorBlock(long config, BlockBehaviour.Properties properties, SoundEvent open_sound, SoundEvent close_sound, BlockSetType blockSetType) { - this( - config, properties, - Auxiliaries.getPixeledAABB(13, 0, 0, 16, 16, 16), - Auxiliaries.getPixeledAABB(0, 0, 13, 16, 16, 16), - open_sound, - close_sound, - blockSetType - ); - } - - public StandardDoorBlock(long config, BlockBehaviour.Properties properties) { - this( - config, properties, - Auxiliaries.getPixeledAABB(13, 0, 0, 16, 16, 16), - Auxiliaries.getPixeledAABB(0, 0, 13, 16, 16, 16), - SoundEvents.WOODEN_DOOR_OPEN, - SoundEvents.WOODEN_DOOR_CLOSE, - BlockSetType.OAK - ); - } - - @Override - public long config() { - return config_; - } - - protected void sound(BlockGetter world, BlockPos pos, boolean open) { - if (world instanceof Level) - ((Level) world).playSound(null, pos, open ? open_sound_ : close_sound_, SoundSource.BLOCKS, 0.7f, 1f); - } - - protected void actuate_adjacent_wing(BlockState state, BlockGetter world_ro, BlockPos pos, boolean open) { - if (!(world_ro instanceof final Level world)) return; - final BlockPos adjecent_pos = pos.relative((state.getValue(HINGE) == DoorHingeSide.LEFT) ? (state.getValue(FACING).getClockWise()) : (state.getValue(FACING).getCounterClockWise())); - if (!world.isLoaded(adjecent_pos)) return; - BlockState adjacent_state = world.getBlockState(adjecent_pos); - if (adjacent_state.getBlock() != this) return; - if (adjacent_state.getValue(OPEN) == open) return; - world.setBlock(adjecent_pos, adjacent_state.setValue(OPEN, open), 2 | 10); - } - - @Override - @OnlyIn(Dist.CLIENT) - public void appendHoverText(ItemStack stack, @Nullable BlockGetter world, List tooltip, TooltipFlag flag) { - Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); - } - - - @Override - public boolean isPossibleToRespawnInThis(BlockState p_279289_) { - return false; - } - - @Override - public boolean isValidSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType entityType) { - return false; - } - - @Override - public VoxelShape getShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext context) { - return shapes_[state.getValue(FACING).ordinal()][state.getValue(OPEN) ? 1 : 0][state.getValue(HINGE) == DoorHingeSide.RIGHT ? 1 : 0][state.getValue(HALF) == DoubleBlockHalf.UPPER ? 0 : 1]; - } - - @Override - public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { - setOpen(player, world, state, pos, !state.getValue(OPEN)); - return InteractionResult.sidedSuccess(world.isClientSide()); - } - - @Override - public void neighborChanged(BlockState state, Level world, BlockPos pos, Block block, BlockPos fromPos, boolean isMoving) { - boolean powered = world.hasNeighborSignal(pos) || world.hasNeighborSignal(pos.relative(state.getValue(HALF) == DoubleBlockHalf.LOWER ? Direction.UP : Direction.DOWN)); - if ((block == this) || (powered == state.getValue(POWERED))) return; - world.setBlock(pos, state.setValue(POWERED, powered).setValue(OPEN, powered), 2); - actuate_adjacent_wing(state, world, pos, powered); - if (powered != state.getValue(OPEN)) sound(world, pos, powered); - } - - @Override - public void setOpen(@Nullable Entity entity, Level world, BlockState state, BlockPos pos, boolean open) { - if (!state.is(this) || (state.getValue(OPEN) == open)) return; - state = state.setValue(OPEN, open); - world.setBlock(pos, state, 2 | 8); - sound(world, pos, open); - actuate_adjacent_wing(state, world, pos, open); - } - -} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardEntityBlocks.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardEntityBlocks.java deleted file mode 100644 index 78bf385..0000000 --- a/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardEntityBlocks.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * @file StandardEntityBlocks.java - * @author Stefan Wilhelm (wile) - * @copyright (C) 2020 Stefan Wilhelm - * @license MIT (see https://opensource.org/licenses/MIT) - * - * Common functionality class for blocks with block entities. - */ -package dev.zontreck.libzontreck.edlibmc; - -import net.minecraft.core.BlockPos; -import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.InteractionResult; -import net.minecraft.world.MenuProvider; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.block.EntityBlock; -import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraft.world.level.block.entity.BlockEntityTicker; -import net.minecraft.world.level.block.entity.BlockEntityType; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.gameevent.GameEventListener; -import net.minecraftforge.common.util.FakePlayer; - -import javax.annotation.Nullable; - - -public class StandardEntityBlocks { - public interface IStandardEntityBlock extends EntityBlock { - - default boolean isBlockEntityTicking(Level world, BlockState state) { - return false; - } - - default InteractionResult useOpenGui(BlockState state, Level world, BlockPos pos, Player player) { - if (world.isClientSide()) return InteractionResult.SUCCESS; - final BlockEntity te = world.getBlockEntity(pos); - if (!(te instanceof MenuProvider) || ((player instanceof FakePlayer))) return InteractionResult.FAIL; - player.openMenu((MenuProvider) te); - return InteractionResult.CONSUME; - } - - @Override - @Nullable - default BlockEntity newBlockEntity(BlockPos pos, BlockState state) { - BlockEntityType tet = Registries.getBlockEntityTypeOfBlock(state.getBlock()); - return (tet == null) ? null : tet.create(pos, state); - } - - @Override - @Nullable - default BlockEntityTicker getTicker(Level world, BlockState state, BlockEntityType te_type) { - return (world.isClientSide || (!isBlockEntityTicking(world, state))) ? (null) : ((Level w, BlockPos p, BlockState s, T te) -> ((StandardBlockEntity) te).tick()); - } - - @Override - @Nullable - default GameEventListener getListener(ServerLevel world, T te) { - return null; - } - } - - public static abstract class StandardBlockEntity extends BlockEntity { - public StandardBlockEntity(BlockEntityType type, BlockPos pos, BlockState state) { - super(type, pos, state); - } - - public void tick() { - } - } - -} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardFenceBlock.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardFenceBlock.java deleted file mode 100644 index 0097d76..0000000 --- a/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardFenceBlock.java +++ /dev/null @@ -1,203 +0,0 @@ -/* - * @file StandardFenceBlock.java - * @author Stefan Wilhelm (wile) - * @copyright (C) 2020 Stefan Wilhelm - * @license MIT (see https://opensource.org/licenses/MIT) - * - * Wall blocks. - */ -package dev.zontreck.libzontreck.edlibmc; - -import com.google.common.collect.ImmutableMap; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.network.chat.Component; -import net.minecraft.world.entity.EntityType; -import net.minecraft.world.entity.SpawnPlacements; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.TooltipFlag; -import net.minecraft.world.item.context.BlockPlaceContext; -import net.minecraft.world.level.BlockGetter; -import net.minecraft.world.level.LevelAccessor; -import net.minecraft.world.level.LevelReader; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.FenceGateBlock; -import net.minecraft.world.level.block.WallBlock; -import net.minecraft.world.level.block.state.BlockBehaviour; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.state.StateDefinition; -import net.minecraft.world.level.block.state.properties.BlockStateProperties; -import net.minecraft.world.level.block.state.properties.BooleanProperty; -import net.minecraft.world.level.block.state.properties.EnumProperty; -import net.minecraft.world.level.block.state.properties.WallSide; -import net.minecraft.world.level.material.FluidState; -import net.minecraft.world.level.material.Fluids; -import net.minecraft.world.level.material.PushReaction; -import net.minecraft.world.phys.shapes.CollisionContext; -import net.minecraft.world.phys.shapes.Shapes; -import net.minecraft.world.phys.shapes.VoxelShape; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.api.distmarker.OnlyIn; - -import javax.annotation.Nullable; -import java.util.List; -import java.util.Map; - - -public class StandardFenceBlock extends WallBlock implements StandardBlocks.IStandardBlock { - public static final BooleanProperty UP = BlockStateProperties.UP; - public static final EnumProperty WALL_EAST = BlockStateProperties.EAST_WALL; - public static final EnumProperty WALL_NORTH = BlockStateProperties.NORTH_WALL; - public static final EnumProperty WALL_SOUTH = BlockStateProperties.SOUTH_WALL; - public static final EnumProperty WALL_WEST = BlockStateProperties.WEST_WALL; - public static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED; - private final Map shape_voxels; - private final Map collision_shape_voxels; - private final long config; - - public StandardFenceBlock(long config, BlockBehaviour.Properties properties) { - this(config, properties, 1.5, 16, 1.5, 0, 14, 16); - } - - public StandardFenceBlock(long config, BlockBehaviour.Properties properties, double pole_width, double pole_height, double side_width, double side_min_y, double side_max_low_y, double side_max_tall_y) { - super(properties); - shape_voxels = buildShapes(pole_width, pole_height, side_width, side_min_y, side_max_low_y, side_max_tall_y); - collision_shape_voxels = buildShapes(pole_width, 24, pole_width, 0, 24, 24); - this.config = config; - } - - @Override - public long config() { - return config; - } - - @Override - @OnlyIn(Dist.CLIENT) - public void appendHoverText(ItemStack stack, @Nullable BlockGetter world, List tooltip, TooltipFlag flag) { - Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); - } - - private static VoxelShape combinedShape(VoxelShape pole, WallSide height, VoxelShape low, VoxelShape high) { - if (height == WallSide.TALL) return Shapes.or(pole, high); - if (height == WallSide.LOW) return Shapes.or(pole, low); - return pole; - } - - protected Map buildShapes(double pole_width, double pole_height, double side_width, double side_min_y, double side_max_low_y, double side_max_tall_y) { - final double px0 = 8.0 - pole_width, px1 = 8.0 + pole_width, sx0 = 8.0 - side_width, sx1 = 8.0 + side_width; - VoxelShape vp = Block.box(px0, 0, px0, px1, pole_height, px1); - VoxelShape vs1 = Block.box(sx0, side_min_y, 0, sx1, side_max_low_y, sx1); - VoxelShape vs2 = Block.box(sx0, side_min_y, sx0, sx1, side_max_low_y, 16); - VoxelShape vs3 = Block.box(0, side_min_y, sx0, sx1, side_max_low_y, sx1); - VoxelShape vs4 = Block.box(sx0, side_min_y, sx0, 16, side_max_low_y, sx1); - VoxelShape vs5 = Block.box(sx0, side_min_y, 0, sx1, side_max_tall_y, sx1); - VoxelShape vs6 = Block.box(sx0, side_min_y, sx0, sx1, side_max_tall_y, 16); - VoxelShape vs7 = Block.box(0, side_min_y, sx0, sx1, side_max_tall_y, sx1); - VoxelShape vs8 = Block.box(sx0, side_min_y, sx0, 16, side_max_tall_y, sx1); - ImmutableMap.Builder builder = ImmutableMap.builder(); - for (Boolean up : UP.getPossibleValues()) { - for (WallSide wh_east : WALL_EAST.getPossibleValues()) { - for (WallSide wh_north : WALL_NORTH.getPossibleValues()) { - for (WallSide wh_west : WALL_WEST.getPossibleValues()) { - for (WallSide wh_south : WALL_SOUTH.getPossibleValues()) { - VoxelShape shape = Shapes.empty(); - shape = combinedShape(shape, wh_east, vs4, vs8); - shape = combinedShape(shape, wh_west, vs3, vs7); - shape = combinedShape(shape, wh_north, vs1, vs5); - shape = combinedShape(shape, wh_south, vs2, vs6); - if (up) shape = Shapes.or(shape, vp); - BlockState bs = defaultBlockState().setValue(UP, up) - .setValue(WALL_EAST, wh_east) - .setValue(WALL_NORTH, wh_north) - .setValue(WALL_WEST, wh_west) - .setValue(WALL_SOUTH, wh_south); - builder.put(bs.setValue(WATERLOGGED, false), shape); - builder.put(bs.setValue(WATERLOGGED, true), shape); - } - } - } - } - } - return builder.build(); - } - - @Override - public VoxelShape getShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext) { - return shape_voxels.getOrDefault(state, Shapes.block()); - } - - @Override - public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext) { - return collision_shape_voxels.getOrDefault(state, Shapes.block()); - } - - @Override - protected void createBlockStateDefinition(StateDefinition.Builder builder) { - super.createBlockStateDefinition(builder); - } - - protected boolean attachesTo(BlockState facingState, LevelReader world, BlockPos facingPos, Direction side) { - final Block block = facingState.getBlock(); - if ((block instanceof FenceGateBlock) || (block instanceof StandardFenceBlock) || (block instanceof VariantWallBlock)) - return true; - final BlockState oppositeState = world.getBlockState(facingPos.relative(side, 2)); - if (!(oppositeState.getBlock() instanceof StandardFenceBlock)) return false; - return facingState.isRedstoneConductor(world, facingPos) && canSupportCenter(world, facingPos, side); - } - - protected WallSide selectWallHeight(LevelReader world, BlockPos pos, Direction direction) { - return WallSide.LOW; // @todo: implement - } - - public BlockState getStateForPlacement(BlockPlaceContext context) { - LevelReader world = context.getLevel(); - BlockPos pos = context.getClickedPos(); - FluidState fs = context.getLevel().getFluidState(context.getClickedPos()); - boolean n = attachesTo(world.getBlockState(pos.north()), world, pos.north(), Direction.SOUTH); - boolean e = attachesTo(world.getBlockState(pos.east()), world, pos.east(), Direction.WEST); - boolean s = attachesTo(world.getBlockState(pos.south()), world, pos.south(), Direction.NORTH); - boolean w = attachesTo(world.getBlockState(pos.west()), world, pos.west(), Direction.EAST); - boolean not_straight = (!n || !s || e || w) && (n || s || !e || !w); - return defaultBlockState() - .setValue(UP, not_straight) - .setValue(WALL_NORTH, n ? selectWallHeight(world, pos, Direction.NORTH) : WallSide.NONE) - .setValue(WALL_EAST, e ? selectWallHeight(world, pos, Direction.EAST) : WallSide.NONE) - .setValue(WALL_SOUTH, s ? selectWallHeight(world, pos, Direction.SOUTH) : WallSide.NONE) - .setValue(WALL_WEST, w ? selectWallHeight(world, pos, Direction.WEST) : WallSide.NONE) - .setValue(WATERLOGGED, fs.getType() == Fluids.WATER); - } - - @Override - public BlockState updateShape(BlockState state, Direction side, BlockState facingState, LevelAccessor world, BlockPos pos, BlockPos facingPos) { - if (state.getValue(BlockStateProperties.WATERLOGGED)) - world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world)); - if (side == Direction.DOWN) return super.updateShape(state, side, facingState, world, pos, facingPos); - boolean n = (side == Direction.NORTH) ? attachesTo(facingState, world, facingPos, side) : (state.getValue(WALL_NORTH) != WallSide.NONE); - boolean e = (side == Direction.EAST) ? attachesTo(facingState, world, facingPos, side) : (state.getValue(WALL_EAST) != WallSide.NONE); - boolean s = (side == Direction.SOUTH) ? attachesTo(facingState, world, facingPos, side) : (state.getValue(WALL_SOUTH) != WallSide.NONE); - boolean w = (side == Direction.WEST) ? attachesTo(facingState, world, facingPos, side) : (state.getValue(WALL_WEST) != WallSide.NONE); - boolean not_straight = (!n || !s || e || w) && (n || s || !e || !w); - return state.setValue(UP, not_straight) - .setValue(WALL_NORTH, n ? selectWallHeight(world, pos, Direction.NORTH) : WallSide.NONE) - .setValue(WALL_EAST, e ? selectWallHeight(world, pos, Direction.EAST) : WallSide.NONE) - .setValue(WALL_SOUTH, s ? selectWallHeight(world, pos, Direction.SOUTH) : WallSide.NONE) - .setValue(WALL_WEST, w ? selectWallHeight(world, pos, Direction.WEST) : WallSide.NONE); - } - - @Override - public boolean isValidSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType entityType) { - return false; - } - - - @Override - public boolean isPossibleToRespawnInThis(BlockState p_279289_) { - return false; - } - - @Override - @SuppressWarnings("deprecation") - public PushReaction getPistonPushReaction(BlockState state) { - return PushReaction.NORMAL; - } -} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardStairsBlock.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardStairsBlock.java deleted file mode 100644 index 64a9f05..0000000 --- a/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardStairsBlock.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * @file StandardStairsBlock.java - * @author Stefan Wilhelm (wile) - * @copyright (C) 2020 Stefan Wilhelm - * @license MIT (see https://opensource.org/licenses/MIT) - * - * Stairs and roof blocks, almost entirely based on vanilla stairs. - */ -package dev.zontreck.libzontreck.edlibmc; - -import net.minecraft.core.BlockPos; -import net.minecraft.network.chat.Component; -import net.minecraft.world.entity.EntityType; -import net.minecraft.world.entity.SpawnPlacements; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.TooltipFlag; -import net.minecraft.world.level.BlockGetter; -import net.minecraft.world.level.block.StairBlock; -import net.minecraft.world.level.block.state.BlockBehaviour; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.material.PushReaction; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.api.distmarker.OnlyIn; - -import javax.annotation.Nullable; -import java.util.List; - - -public class StandardStairsBlock extends StairBlock implements StandardBlocks.IStandardBlock { - private final long config; - - public StandardStairsBlock(long config, java.util.function.Supplier state, BlockBehaviour.Properties properties) { - super(state, properties); - this.config = config; - } - - @Override - @OnlyIn(Dist.CLIENT) - public void appendHoverText(ItemStack stack, @Nullable BlockGetter world, List tooltip, TooltipFlag flag) { - Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); - } - - - @Override - public boolean isPossibleToRespawnInThis(BlockState p_279289_) { - return false; - } - - @Override - public boolean isValidSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType entityType) { - return false; - } - - @Override - @SuppressWarnings("deprecation") - public PushReaction getPistonPushReaction(BlockState state) { - return PushReaction.NORMAL; - } -} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/TooltipDisplay.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/TooltipDisplay.java deleted file mode 100644 index 76eb760..0000000 --- a/src/main/java/dev/zontreck/libzontreck/edlibmc/TooltipDisplay.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * @file Tooltip.java - * @author Stefan Wilhelm (wile) - * @copyright (C) 2020 Stefan Wilhelm - * @license MIT (see https://opensource.org/licenses/MIT) - * - * Delayed tooltip for a selected area. Constructed with a - * GUI, invoked in `render()`. - */ -package dev.zontreck.libzontreck.edlibmc; - -import com.mojang.blaze3d.vertex.PoseStack; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiGraphics; -import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; -import net.minecraft.network.chat.*; -import net.minecraft.util.Mth; -import net.minecraft.world.inventory.AbstractContainerMenu; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.api.distmarker.OnlyIn; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.function.Supplier; - - -@OnlyIn(Dist.CLIENT) -public class TooltipDisplay { - private static long default_delay = 800; - private static int default_max_deviation = 1; - - public static void config(long delay, int max_deviation) { - default_delay = clamp(delay, 500, 5000); - default_max_deviation = Mth.clamp(max_deviation, 1, 5); - } - private static long clamp(long p1, long a, long b) { - return p1 < a ? a : Math.min(p1, b); - } - - // --------------------------------------------------------------------------------------------------- - - public static class TipRange { - public final int x0, y0, x1, y1; - public final Supplier text; - - public TipRange(int x, int y, int w, int h, Component text) { - this(x, y, w, h, () -> text); - } - - public TipRange(int x, int y, int w, int h, Supplier text) { - this.text = text; - this.x0 = x; - this.y0 = y; - this.x1 = x0 + w - 1; - this.y1 = y0 + h - 1; - } - - } - - // --------------------------------------------------------------------------------------------------- - - private List ranges = new ArrayList<>(); - private long delay = default_delay; - private int max_deviation = default_max_deviation; - private int x_last, y_last; - private long t; - private static boolean had_render_exception = false; - - public TooltipDisplay() { - t = System.currentTimeMillis(); - } - - public TooltipDisplay init(List ranges, long delay_ms, int max_deviation_xy) { - this.ranges = ranges; - this.delay = delay_ms; - this.max_deviation = max_deviation_xy; - t = System.currentTimeMillis(); - x_last = y_last = 0; - return this; - } - - public TooltipDisplay init(List ranges) { - return init(ranges, default_delay, default_max_deviation); - } - - public TooltipDisplay init(TipRange... ranges) { - return init(Arrays.asList(ranges), default_delay, default_max_deviation); - } - - public TooltipDisplay delay(int ms) { - delay = (ms <= 0) ? default_delay : ms; - return this; - } - - public void resetTimer() { - t = System.currentTimeMillis(); - } - - public boolean render(GuiGraphics mx, final AbstractContainerScreen gui, int x, int y) { - if (had_render_exception) return false; - if ((Math.abs(x - x_last) > max_deviation) || (Math.abs(y - y_last) > max_deviation)) { - x_last = x; - y_last = y; - resetTimer(); - return false; - } else if (Math.abs(System.currentTimeMillis() - t) < delay) { - return false; - } else if (ranges.stream().noneMatch( - (tip) -> { - if ((x < tip.x0) || (x > tip.x1) || (y < tip.y0) || (y > tip.y1)) return false; - String text = tip.text.get().toString(); - if (text.isEmpty()) return false; - try { - mx.renderComponentTooltip(Minecraft.getInstance().font, tip.text.get().toFlatList(Style.EMPTY), x, y); - } catch (Exception ex) { - had_render_exception = true; - Auxiliaries.logError("Tooltip rendering disabled due to exception: '" + ex.getMessage() + "'"); - return false; - } - return true; - }) - ) { - resetTimer(); - return false; - } else { - return true; - } - } -} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/VariantSlabBlock.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/VariantSlabBlock.java deleted file mode 100644 index 4f2eeb7..0000000 --- a/src/main/java/dev/zontreck/libzontreck/edlibmc/VariantSlabBlock.java +++ /dev/null @@ -1,220 +0,0 @@ -/* - * @file VariantSlabBlock.java - * @author Stefan Wilhelm (wile) - * @copyright (C) 2020 Stefan Wilhelm - * @license MIT (see https://opensource.org/licenses/MIT) - * - * Standard half block horizontal slab characteristics class. - */ -package dev.zontreck.libzontreck.edlibmc; - -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.network.chat.Component; -import net.minecraft.sounds.SoundSource; -import net.minecraft.util.Mth; -import net.minecraft.world.entity.EntityType; -import net.minecraft.world.entity.SpawnPlacements; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.TooltipFlag; -import net.minecraft.world.item.context.BlockPlaceContext; -import net.minecraft.world.level.BlockGetter; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.LevelAccessor; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.Mirror; -import net.minecraft.world.level.block.Rotation; -import net.minecraft.world.level.block.SoundType; -import net.minecraft.world.level.block.entity.BlockEntity; -import net.minecraft.world.level.block.state.BlockBehaviour; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.state.StateDefinition; -import net.minecraft.world.level.block.state.properties.BlockStateProperties; -import net.minecraft.world.level.block.state.properties.EnumProperty; -import net.minecraft.world.level.block.state.properties.IntegerProperty; -import net.minecraft.world.level.block.state.properties.SlabType; -import net.minecraft.world.level.material.Fluid; -import net.minecraft.world.level.material.FluidState; -import net.minecraft.world.phys.AABB; -import net.minecraft.world.phys.Vec3; -import net.minecraft.world.phys.shapes.CollisionContext; -import net.minecraft.world.phys.shapes.Shapes; -import net.minecraft.world.phys.shapes.VoxelShape; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.api.distmarker.OnlyIn; - -import javax.annotation.Nullable; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - - -public class VariantSlabBlock extends StandardBlocks.WaterLoggable implements StandardBlocks.IStandardBlock { - public static final EnumProperty TYPE = BlockStateProperties.SLAB_TYPE; - public static final IntegerProperty TEXTURE_VARIANT = IntegerProperty.create("tvariant", 0, 3); - - protected static final VoxelShape[] AABBs = { - Shapes.create(new AABB(0, 8. / 16, 0, 1, 16. / 16, 1)), // top slab - Shapes.create(new AABB(0, 0. / 16, 0, 1, 8. / 16, 1)), // bottom slab - Shapes.create(new AABB(0, 0. / 16, 0, 1, 16. / 16, 1)), // both slabs - Shapes.create(new AABB(0, 0. / 16, 0, 1, 16. / 16, 1)) // << 2bit fill - }; - protected static final int[] num_slabs_contained_in_parts_ = {1, 1, 2, 2}; - private static boolean with_pickup = false; - - public static void on_config(boolean direct_slab_pickup) { - with_pickup = direct_slab_pickup; - } - - protected boolean is_cube(BlockState state) { - return state.getValue(TYPE) == SlabType.DOUBLE; - } - - public VariantSlabBlock(long config, BlockBehaviour.Properties builder) { - super(config, builder); - registerDefaultState(defaultBlockState().setValue(TYPE, SlabType.BOTTOM)); - } - - @Override - public RenderTypeHint getRenderTypeHint() { - return (((config & StandardBlocks.CFG_TRANSLUCENT) != 0) ? (RenderTypeHint.TRANSLUCENT) : (RenderTypeHint.CUTOUT)); - } - - @Override - @OnlyIn(Dist.CLIENT) - public void appendHoverText(ItemStack stack, @Nullable BlockGetter world, List tooltip, TooltipFlag flag) { - if (!Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true)) return; - if (with_pickup && Auxiliaries.Tooltip.helpCondition()) - Auxiliaries.Tooltip.addInformation("engineersdecor.tooltip.slabpickup", "engineersdecor.tooltip.slabpickup", tooltip, flag, true); - } - - @Override - @OnlyIn(Dist.CLIENT) - @SuppressWarnings("deprecation") - public boolean skipRendering(BlockState state, BlockState adjacentBlockState, Direction side) { - return (adjacentBlockState == state) || (super.skipRendering(state, adjacentBlockState, side)); - } - - @Override - public boolean isPossibleToRespawnInThis(BlockState state) { - return false; - } - - @Override - public boolean isValidSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType entityType) { - return false; - } - - @Override - public VoxelShape getShape(BlockState state, BlockGetter source, BlockPos pos, CollisionContext selectionContext) { - return AABBs[state.getValue(TYPE).ordinal() & 0x3]; - } - - @Override - public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext) { - return getShape(state, world, pos, selectionContext); - } - - @Override - protected void createBlockStateDefinition(StateDefinition.Builder builder) { - super.createBlockStateDefinition(builder); - builder.add(TYPE, TEXTURE_VARIANT); - } - - @Override - @Nullable - public BlockState getStateForPlacement(BlockPlaceContext context) { - BlockPos pos = context.getClickedPos(); - if (context.getLevel().getBlockState(pos).getBlock() == this) - return context.getLevel().getBlockState(pos).setValue(TYPE, SlabType.DOUBLE).setValue(WATERLOGGED, false); - final int rnd = Mth.clamp((int) (Mth.getSeed(context.getClickedPos()) & 0x3), 0, 3); - final Direction face = context.getClickedFace(); - final BlockState placement_state = super.getStateForPlacement(context).setValue(TEXTURE_VARIANT, rnd); // fluid state - if (face == Direction.UP) return placement_state.setValue(TYPE, SlabType.BOTTOM); - if (face == Direction.DOWN) return placement_state.setValue(TYPE, SlabType.TOP); - if (!face.getAxis().isHorizontal()) return placement_state; - final boolean isupper = ((context.getClickLocation().y() - context.getClickedPos().getY()) > 0.5); - return placement_state.setValue(TYPE, isupper ? SlabType.TOP : SlabType.BOTTOM); - } - - @Override - @SuppressWarnings("deprecation") - public boolean canBeReplaced(BlockState state, BlockPlaceContext context) { - if (context.getItemInHand().getItem() != this.asItem()) return false; - if (!context.replacingClickedOnBlock()) return true; - final Direction face = context.getClickedFace(); - final SlabType type = state.getValue(TYPE); - if ((face == Direction.UP) && (type == SlabType.BOTTOM)) return true; - if ((face == Direction.DOWN) && (type == SlabType.TOP)) return true; - if (!face.getAxis().isHorizontal()) return false; - final boolean isupper = ((context.getClickLocation().y() - context.getClickedPos().getY()) > 0.5); - return isupper ? (type == SlabType.BOTTOM) : (type == SlabType.TOP); - } - - @Override - @SuppressWarnings("deprecation") - public BlockState rotate(BlockState state, Rotation rot) { - return state; - } - - @Override - @SuppressWarnings("deprecation") - public BlockState mirror(BlockState state, Mirror mirrorIn) { - return state; - } - - @Override - public boolean hasDynamicDropList() { - return true; - } - - @Override - public List dropList(BlockState state, Level world, BlockEntity te, boolean explosion) { - return new ArrayList<>(Collections.singletonList(new ItemStack(this.asItem(), num_slabs_contained_in_parts_[state.getValue(TYPE).ordinal() & 0x3]))); - } - - @Override - @SuppressWarnings("deprecation") - public void attack(BlockState state, Level world, BlockPos pos, Player player) { - if ((world.isClientSide) || (!with_pickup)) return; - final ItemStack stack = player.getMainHandItem(); - if (stack.isEmpty() || (Block.byItem(stack.getItem()) != this)) return; - if (stack.getCount() >= stack.getMaxStackSize()) return; - Vec3 lv = player.getLookAngle(); - Direction facing = Direction.getNearest((float) lv.x, (float) lv.y, (float) lv.z); - if ((facing != Direction.UP) && (facing != Direction.DOWN)) return; - if (state.getBlock() != this) return; - SlabType type = state.getValue(TYPE); - if (facing == Direction.DOWN) { - if (type == SlabType.DOUBLE) { - world.setBlock(pos, state.setValue(TYPE, SlabType.BOTTOM), 3); - } else { - world.removeBlock(pos, false); - } - } else if (facing == Direction.UP) { - if (type == SlabType.DOUBLE) { - world.setBlock(pos, state.setValue(TYPE, SlabType.TOP), 3); - } else { - world.removeBlock(pos, false); - } - } - if (!player.isCreative()) { - stack.grow(1); - if (player.getInventory() != null) player.getInventory().setChanged(); - } - SoundType st = this.getSoundType(state, world, pos, null); - world.playSound(player, pos, st.getPlaceSound(), SoundSource.BLOCKS, (st.getVolume() + 1f) / 2.5f, 0.9f * st.getPitch()); - } - - @Override - public boolean placeLiquid(LevelAccessor world, BlockPos pos, BlockState state, FluidState fluidState) { - return (state.getValue(TYPE) != SlabType.DOUBLE) && super.placeLiquid(world, pos, state, fluidState); - } - - @Override - public boolean canPlaceLiquid(BlockGetter world, BlockPos pos, BlockState state, Fluid fluid) { - return (state.getValue(TYPE) != SlabType.DOUBLE) && super.canPlaceLiquid(world, pos, state, fluid); - } - -} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/VariantWallBlock.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/VariantWallBlock.java deleted file mode 100644 index 92af666..0000000 --- a/src/main/java/dev/zontreck/libzontreck/edlibmc/VariantWallBlock.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * @file VariantWallBlock.java - * @author Stefan Wilhelm (wile) - * @copyright (C) 2020 Stefan Wilhelm - * @license MIT (see https://opensource.org/licenses/MIT) - * - * Wall blocks. - */ -package dev.zontreck.libzontreck.edlibmc; - -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableMap.Builder; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Direction; -import net.minecraft.network.chat.Component; -import net.minecraft.util.Mth; -import net.minecraft.world.entity.EntityType; -import net.minecraft.world.entity.SpawnPlacements; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.item.TooltipFlag; -import net.minecraft.world.item.context.BlockPlaceContext; -import net.minecraft.world.level.BlockGetter; -import net.minecraft.world.level.LevelAccessor; -import net.minecraft.world.level.LevelReader; -import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.FenceGateBlock; -import net.minecraft.world.level.block.WallBlock; -import net.minecraft.world.level.block.state.BlockBehaviour; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.state.StateDefinition; -import net.minecraft.world.level.block.state.properties.*; -import net.minecraft.world.level.material.FluidState; -import net.minecraft.world.level.material.Fluids; -import net.minecraft.world.level.material.PushReaction; -import net.minecraft.world.phys.shapes.CollisionContext; -import net.minecraft.world.phys.shapes.Shapes; -import net.minecraft.world.phys.shapes.VoxelShape; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.api.distmarker.OnlyIn; - -import javax.annotation.Nullable; -import java.util.List; -import java.util.Map; - - -public class VariantWallBlock extends WallBlock implements StandardBlocks.IStandardBlock { - public static final BooleanProperty UP = BlockStateProperties.UP; - public static final EnumProperty WALL_EAST = BlockStateProperties.EAST_WALL; - public static final EnumProperty WALL_NORTH = BlockStateProperties.NORTH_WALL; - public static final EnumProperty WALL_SOUTH = BlockStateProperties.SOUTH_WALL; - public static final EnumProperty WALL_WEST = BlockStateProperties.WEST_WALL; - public static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED; - public static final IntegerProperty TEXTURE_VARIANT = IntegerProperty.create("tvariant", 0, 7); - private final Map shape_voxels; - private final Map collision_shape_voxels; - private final long config; - - public VariantWallBlock(long config, BlockBehaviour.Properties builder) { - super(builder); - shape_voxels = buildWallShapes(4, 16, 4, 0, 16, 16); - collision_shape_voxels = buildWallShapes(6, 16, 5, 0, 24, 24); - this.config = config; - } - - @Override - public long config() { - return config; - } - - @Override - @OnlyIn(Dist.CLIENT) - public void appendHoverText(ItemStack stack, @Nullable BlockGetter world, List tooltip, TooltipFlag flag) { - Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); - } - - private static VoxelShape combinedShape(VoxelShape pole, WallSide height, VoxelShape low, VoxelShape high) { - if (height == WallSide.TALL) return Shapes.or(pole, high); - if (height == WallSide.LOW) return Shapes.or(pole, low); - return pole; - } - - protected Map buildWallShapes(double pole_width, double pole_height, double side_width, double side_min_y, double side_max_low_y, double side_max_tall_y) { - final double px0 = 8.0 - pole_width, px1 = 8.0 + pole_width, sx0 = 8.0 - side_width, sx1 = 8.0 + side_width; - VoxelShape vp = Block.box(px0, 0, px0, px1, pole_height, px1); - VoxelShape vs1 = Block.box(sx0, side_min_y, 0, sx1, side_max_low_y, sx1); - VoxelShape vs2 = Block.box(sx0, side_min_y, sx0, sx1, side_max_low_y, 16); - VoxelShape vs3 = Block.box(0, side_min_y, sx0, sx1, side_max_low_y, sx1); - VoxelShape vs4 = Block.box(sx0, side_min_y, sx0, 16, side_max_low_y, sx1); - VoxelShape vs5 = Block.box(sx0, side_min_y, 0, sx1, side_max_tall_y, sx1); - VoxelShape vs6 = Block.box(sx0, side_min_y, sx0, sx1, side_max_tall_y, 16); - VoxelShape vs7 = Block.box(0, side_min_y, sx0, sx1, side_max_tall_y, sx1); - VoxelShape vs8 = Block.box(sx0, side_min_y, sx0, 16, side_max_tall_y, sx1); - Builder builder = ImmutableMap.builder(); - for (Boolean up : UP.getPossibleValues()) { - for (WallSide wh_east : WALL_EAST.getPossibleValues()) { - for (WallSide wh_north : WALL_NORTH.getPossibleValues()) { - for (WallSide wh_west : WALL_WEST.getPossibleValues()) { - for (WallSide wh_south : WALL_SOUTH.getPossibleValues()) { - VoxelShape shape = Shapes.empty(); - shape = combinedShape(shape, wh_east, vs4, vs8); - shape = combinedShape(shape, wh_west, vs3, vs7); - shape = combinedShape(shape, wh_north, vs1, vs5); - shape = combinedShape(shape, wh_south, vs2, vs6); - if (up) shape = Shapes.or(shape, vp); - BlockState bs = defaultBlockState().setValue(UP, up) - .setValue(WALL_EAST, wh_east) - .setValue(WALL_NORTH, wh_north) - .setValue(WALL_WEST, wh_west) - .setValue(WALL_SOUTH, wh_south); - final VoxelShape tvs = shape; - TEXTURE_VARIANT.getPossibleValues().forEach((tv) -> { - builder.put(bs.setValue(TEXTURE_VARIANT, tv).setValue(WATERLOGGED, false), tvs); - builder.put(bs.setValue(TEXTURE_VARIANT, tv).setValue(WATERLOGGED, true), tvs); - }); - } - } - } - } - } - return builder.build(); - } - - @Override - public VoxelShape getShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext) { - return shape_voxels.getOrDefault(state, Shapes.block()); - } - - @Override - public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext) { - return collision_shape_voxels.getOrDefault(state, Shapes.block()); - } - - @Override - protected void createBlockStateDefinition(StateDefinition.Builder builder) { - super.createBlockStateDefinition(builder); - builder.add(TEXTURE_VARIANT); - } - - protected boolean attachesTo(BlockState facingState, LevelReader world, BlockPos facingPos, Direction side) { - final Block block = facingState.getBlock(); - if ((block instanceof FenceGateBlock) || (block instanceof WallBlock)) return true; - final BlockState oppositeState = world.getBlockState(facingPos.relative(side, 2)); - if (!(oppositeState.getBlock() instanceof VariantWallBlock)) return false; - return facingState.isRedstoneConductor(world, facingPos) && Block.canSupportCenter(world, facingPos, side); - } - - protected WallSide selectWallHeight(LevelReader world, BlockPos pos, Direction direction) { - return WallSide.LOW; - } - - public BlockState getStateForPlacement(BlockPlaceContext context) { - LevelReader world = context.getLevel(); - BlockPos pos = context.getClickedPos(); - FluidState fs = context.getLevel().getFluidState(context.getClickedPos()); - boolean n = attachesTo(world.getBlockState(pos.north()), world, pos.north(), Direction.SOUTH); - boolean e = attachesTo(world.getBlockState(pos.east()), world, pos.east(), Direction.WEST); - boolean s = attachesTo(world.getBlockState(pos.south()), world, pos.south(), Direction.NORTH); - boolean w = attachesTo(world.getBlockState(pos.west()), world, pos.west(), Direction.EAST); - boolean not_straight = (!n || !s || e || w) && (n || s || !e || !w); - return defaultBlockState().setValue(UP, not_straight) - .setValue(WALL_NORTH, n ? selectWallHeight(world, pos, Direction.NORTH) : WallSide.NONE) - .setValue(WALL_EAST, e ? selectWallHeight(world, pos, Direction.EAST) : WallSide.NONE) - .setValue(WALL_SOUTH, s ? selectWallHeight(world, pos, Direction.SOUTH) : WallSide.NONE) - .setValue(WALL_WEST, w ? selectWallHeight(world, pos, Direction.WEST) : WallSide.NONE) - .setValue(WATERLOGGED, fs.getType() == Fluids.WATER); - } - - @Override - public BlockState updateShape(BlockState state, Direction side, BlockState facingState, LevelAccessor world, BlockPos pos, BlockPos facingPos) { - if (state.getValue(WATERLOGGED)) world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world)); - if (side == Direction.DOWN) return super.updateShape(state, side, facingState, world, pos, facingPos); - boolean n = (side == Direction.NORTH) ? this.attachesTo(facingState, world, facingPos, side) : state.getValue(WALL_NORTH) != WallSide.NONE; - boolean e = (side == Direction.EAST) ? this.attachesTo(facingState, world, facingPos, side) : state.getValue(WALL_EAST) != WallSide.NONE; - boolean s = (side == Direction.SOUTH) ? this.attachesTo(facingState, world, facingPos, side) : state.getValue(WALL_SOUTH) != WallSide.NONE; - boolean w = (side == Direction.WEST) ? this.attachesTo(facingState, world, facingPos, side) : state.getValue(WALL_WEST) != WallSide.NONE; - boolean not_straight = (!n || !s || e || w) && (n || s || !e || !w); - return state.setValue(UP, not_straight) - .setValue(WALL_NORTH, n ? selectWallHeight(world, pos, Direction.NORTH) : WallSide.NONE) - .setValue(WALL_EAST, e ? selectWallHeight(world, pos, Direction.EAST) : WallSide.NONE) - .setValue(WALL_SOUTH, s ? selectWallHeight(world, pos, Direction.SOUTH) : WallSide.NONE) - .setValue(WALL_WEST, w ? selectWallHeight(world, pos, Direction.WEST) : WallSide.NONE) - .setValue(TEXTURE_VARIANT, ((int) Mth.getSeed(pos)) & 0x7); - } - - @Override - public boolean isValidSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType entityType) { - return false; - } - - @Override - public boolean isPossibleToRespawnInThis(BlockState state) { - return false; - } - - @Override - @SuppressWarnings("deprecation") - public PushReaction getPistonPushReaction(BlockState state) { - return PushReaction.NORMAL; - } -} diff --git a/src/main/java/dev/zontreck/libzontreck/events/ForgeEventHandlers.java b/src/main/java/dev/zontreck/libzontreck/events/ForgeEventHandlers.java index d24ffa8..56ebdf3 100644 --- a/src/main/java/dev/zontreck/libzontreck/events/ForgeEventHandlers.java +++ b/src/main/java/dev/zontreck/libzontreck/events/ForgeEventHandlers.java @@ -4,7 +4,7 @@ import dev.zontreck.ariaslib.terminal.Task; import dev.zontreck.ariaslib.util.DelayedExecutorService; import dev.zontreck.libzontreck.LibZontreck; import dev.zontreck.libzontreck.exceptions.InvalidSideException; -import dev.zontreck.libzontreck.memory.PlayerContainer; +import dev.zontreck.libzontreck.memory.player.PlayerContainer; import dev.zontreck.libzontreck.networking.ModMessages; import dev.zontreck.libzontreck.networking.packets.S2CServerAvailable; import dev.zontreck.libzontreck.networking.packets.S2CWalletInitialSyncPacket; diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/SavedBlock.java b/src/main/java/dev/zontreck/libzontreck/memory/world/SavedBlock.java index 539d890..119ed68 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/SavedBlock.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/SavedBlock.java @@ -4,13 +4,13 @@ import dev.zontreck.libzontreck.api.Vector3; import dev.zontreck.libzontreck.exceptions.InvalidDeserialization; import dev.zontreck.libzontreck.vectors.WorldPosition; import net.minecraft.core.BlockPos; -import net.minecraft.core.registries.Registries; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtUtils; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; +import net.minecraftforge.registries.ForgeRegistries; public class SavedBlock implements Cloneable { @@ -88,7 +88,7 @@ public class SavedBlock implements Cloneable { ServerLevel level = position.getActualDimension(); - BlockState state = NbtUtils.readBlockState(level.holderLookup(Registries.BLOCK), blockState); + BlockState state = NbtUtils.readBlockState(blockState); return new PrimitiveBlock(this, state.getBlock(), state, blockEntity, position.Position.asBlockPos(), level); } diff --git a/src/main/java/dev/zontreck/libzontreck/networking/ModMessages.java b/src/main/java/dev/zontreck/libzontreck/networking/ModMessages.java index e7bbe1f..2c52022 100644 --- a/src/main/java/dev/zontreck/libzontreck/networking/ModMessages.java +++ b/src/main/java/dev/zontreck/libzontreck/networking/ModMessages.java @@ -1,7 +1,6 @@ package dev.zontreck.libzontreck.networking; import dev.zontreck.libzontreck.LibZontreck; -import dev.zontreck.libzontreck.events.RegisterPacketsEvent; import dev.zontreck.libzontreck.networking.packets.IPacket; import dev.zontreck.libzontreck.networking.packets.S2CCloseChestGUI; import dev.zontreck.libzontreck.networking.packets.S2CPlaySoundPacket; diff --git a/src/main/java/dev/zontreck/libzontreck/networking/packets/S2CWalletUpdatedPacket.java b/src/main/java/dev/zontreck/libzontreck/networking/packets/S2CWalletUpdatedPacket.java index 6d26432..0c4b3c8 100644 --- a/src/main/java/dev/zontreck/libzontreck/networking/packets/S2CWalletUpdatedPacket.java +++ b/src/main/java/dev/zontreck/libzontreck/networking/packets/S2CWalletUpdatedPacket.java @@ -6,6 +6,7 @@ import dev.zontreck.libzontreck.currency.events.WalletUpdatedEvent; import dev.zontreck.libzontreck.util.ServerUtilities; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.FriendlyByteBuf; +import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.network.NetworkDirection; import net.minecraftforge.network.NetworkEvent; import net.minecraftforge.network.simple.SimpleChannel; @@ -63,8 +64,7 @@ public class S2CWalletUpdatedPacket implements IPacket return ServerUtilities.handlePacket(supplier, new Runnable() { @Override public void run() { - Bus.Post(new WalletUpdatedEvent(ID, oldBal, balance, tx)); - + MinecraftForge.EVENT_BUS.post(new WalletUpdatedEvent(ID, oldBal, balance, tx)); } }); } diff --git a/src/main/java/dev/zontreck/libzontreck/vectors/WorldPosition.java b/src/main/java/dev/zontreck/libzontreck/vectors/WorldPosition.java index 3d62956..9143a1d 100644 --- a/src/main/java/dev/zontreck/libzontreck/vectors/WorldPosition.java +++ b/src/main/java/dev/zontreck/libzontreck/vectors/WorldPosition.java @@ -38,7 +38,7 @@ public class WorldPosition implements Cloneable } public WorldPosition(ServerPlayer player) { - this(new Vector3d(player.position()), player.serverLevel()); + this(new Vector3d(player.position()), player.getLevel()); } public WorldPosition(Vector3d pos, ServerLevel lvl) { From 93899a86d15e489eb836bccdb03648401697f639 Mon Sep 17 00:00:00 2001 From: zontreck Date: Thu, 12 Sep 2024 18:02:46 -0700 Subject: [PATCH 13/21] Bump mod version number --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 6de7929..bf2764e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1.10.020624.2226 +mod_version=1.10.091224.1802 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html From a9dfaa546c81bc13d0155535d3ea73ab039eb25f Mon Sep 17 00:00:00 2001 From: zontreck Date: Thu, 12 Sep 2024 21:33:40 -0700 Subject: [PATCH 14/21] Fix build! --- build.gradle | 2 +- gradle.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index fe931ad..7bcfef4 100644 --- a/build.gradle +++ b/build.gradle @@ -259,4 +259,4 @@ publishing { tasks.withType(JavaCompile).configureEach { options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation -} +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index bf2764e..e2078ae 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ # Sets default memory used for gradle commands. Can be overridden by user or command line properties. # This is required to provide enough memory for the Minecraft decompilation process. -org.gradle.jvmargs=-Xmx3G +org.gradle.jvmargs=-Xmx3G -Dfile.encoding=utf-8 org.gradle.daemon=false From 472b117f7098a56a5db089d45cbbf8f8ea4b49a5 Mon Sep 17 00:00:00 2001 From: zontreck Date: Thu, 12 Sep 2024 22:15:13 -0700 Subject: [PATCH 15/21] Add a instrucion for environment var --- Jenkinsfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Jenkinsfile b/Jenkinsfile index 43ad96e..86663fe 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -27,6 +27,7 @@ pipeline { git clean -xfd git reset --hard git fetch + export JAVA_OPTS=-Dfile.encoding=UTF-8 java -version From a005e2d0efd5e7efdd2f65ee35917fc4e2e02da1 Mon Sep 17 00:00:00 2001 From: zontreck Date: Sat, 14 Sep 2024 13:50:28 -0700 Subject: [PATCH 16/21] Fix API Version number --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index e2078ae..4eaa58b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1.10.091224.1802 +mod_version=1192.13.091224.1802 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html From 19b122990cfbe6b9da9337ab35f4e2dd3f8b20e6 Mon Sep 17 00:00:00 2001 From: zontreck Date: Sat, 14 Sep 2024 19:44:43 -0700 Subject: [PATCH 17/21] Update build system fixes --- README.md | 2 +- build.gradle | 27 +++++++------------ gradle.properties | 4 +-- .../resources/META-INF/accesstransformer.cfg | 0 src/main/resources/META-INF/mods.toml | 2 +- 5 files changed, 14 insertions(+), 21 deletions(-) create mode 100644 src/main/resources/META-INF/accesstransformer.cfg diff --git a/README.md b/README.md index ae54ab5..c1e42bc 100644 --- a/README.md +++ b/README.md @@ -12,4 +12,4 @@ If the mod becomes widely used, I will begin using deprecation notices. Compatibility ===== -1) Lightman's Currency \ No newline at end of file +1) Lightman's Currency (possible future compatibility) \ No newline at end of file diff --git a/build.gradle b/build.gradle index 7bcfef4..757a4f1 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,6 @@ plugins { id 'eclipse' id 'idea' id 'maven-publish' - id 'java-library' id 'net.minecraftforge.gradle' version '[6.0,6.2)' id 'org.parchmentmc.librarian.forgegradle' version '1.+' } @@ -23,6 +22,8 @@ java { configurations { provided compile.extendsFrom(provided) + implementation.extendsFrom(provided) + minecraftLibrary.extendsFrom(provided) } // Mojang ships Java 17 to end users in 1.18+, so your mod should target Java 17. @@ -43,12 +44,13 @@ minecraft { // // Use non-default mappings at your own risk. They may not always work. // Simply re-run your setup task after changing the mappings to update your workspace. - mappings channel: mapping_channel, version: "${parchment_version}-${minecraft_version}" + //mappings channel: mapping_channel, version: "${parchment_version}-${minecraft_version}" + mappings channel: mapping_channel, version: "${minecraft_version}" // When true, this property will have all Eclipse/IntelliJ IDEA run configurations run the "prepareX" task for the given run configuration before launching the game. // In most cases, it is not necessary to enable. - // enableEclipsePrepareRuns = true - // enableIdeaPrepareRuns = true + enableEclipsePrepareRuns = true + enableIdeaPrepareRuns = true // This property allows configuring Gradle's ProcessResources task(s) to run on IDE output locations before launching the game. // It is REQUIRED to be set to true for this template to function. @@ -58,7 +60,7 @@ minecraft { // When true, this property will add the folder name of all declared run configurations to generated IDE run configurations. // The folder name can be set on a run configuration using the "folderName" property. // By default, the folder name of a run configuration is the name of the Gradle project containing it. - // generateRunFolders = true + generateRunFolders = true // This property enables access transformers for use in development. // They will be applied to the Minecraft artifact. @@ -66,7 +68,7 @@ minecraft { // However, it must be at "META-INF/accesstransformer.cfg" in the final mod jar to be loaded by Forge. // This default location is a best practice to automatically put the file in the right place in the final jar. // See https://docs.minecraftforge.net/en/latest/advanced/accesstransformers/ for more information. - // accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg') + accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg') // Default run configurations. // These can be tweaked, removed, or duplicated as needed. @@ -158,13 +160,7 @@ dependencies { minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}" provided "dev.zontreck:LibAC:${libac}" - implementation "dev.zontreck:LibAC:${libac}" - minecraftLibrary "dev.zontreck:LibAC:${libac}" - - provided "dev.zontreck:EventsBus:${eventsbus}" - implementation "dev.zontreck:EventsBus:${eventsbus}" - minecraftLibrary "dev.zontreck:EventsBus:${eventsbus}" // Example mod dependency with JEI - using fg.deobf() ensures the dependency is remapped to your development mappings // The JEI API is declared for compile time use, while the full JEI artifact is used at runtime @@ -201,7 +197,6 @@ tasks.named('processResources', ProcessResources).configure { } } - // Example for how to get properties into the manifest for reading at runtime. tasks.named('jar', Jar).configure { from { @@ -236,13 +231,12 @@ def MAVEN_USER = "AriasCreationsMavenUser" publishing { publications { - mavenJava(MavenPublication) { + register('mavenJava', MavenPublication) { artifact jar artifact sourcesJar artifact javadocJar } } - repositories { maven { url = "https://git.zontreck.com/api/packages/MinecraftMods/maven" @@ -256,7 +250,6 @@ publishing { } } - tasks.withType(JavaCompile).configureEach { options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation -} \ No newline at end of file +} diff --git a/gradle.properties b/gradle.properties index 4eaa58b..bb9fd01 100644 --- a/gradle.properties +++ b/gradle.properties @@ -32,14 +32,14 @@ loader_version_range=[43,) # # Parchment is an unofficial project maintained by ParchmentMC, separate from Minecraft Forge. # Additional setup is needed to use their mappings, see https://parchmentmc.org/docs/getting-started -mapping_channel=parchment +mapping_channel=official # The mapping version to query from the mapping channel. # This must match the format required by the mapping channel. parchment_version=2022.11.27 # luckperms_api_version=5.4 libac=1.5.33 -eventsbus=1.0.47 +eventsbus=1.0.48 ## Environment Properties diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index 8cf3e73..a4c2119 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -11,7 +11,7 @@ loaderVersion="${loader_version_range}" #mandatory This is typically bumped ever # Review your options at https://choosealicense.com/. All rights reserved is the default copyright stance, and is thus the default here. license="${mod_license}" # A URL to refer people to when problems occur with this mod -#issueTrackerURL="https://change.me.to.your.issue.tracker.example.invalid/" #optional +issueTrackerURL="https://git.zontreck.com/MinecraftMods/LibZontreck/issues" #optional # A list of mods - how many allowed here is determined by the individual mod loader [[mods]] #mandatory # The modid of the mod From 44eb9d1d9c832c85d796e28312601e4a20c96380 Mon Sep 17 00:00:00 2001 From: zontreck Date: Wed, 9 Oct 2024 21:59:58 -0700 Subject: [PATCH 18/21] Convert project to architectury --- build.gradle | 216 ++++----------------- gradle.properties | 70 +------ gradle/wrapper/gradle-wrapper.jar | Bin 62076 -> 43453 bytes gradle/wrapper/gradle-wrapper.properties | 1 + gradlew | 35 ++-- gradlew.bat | 21 +- settings.gradle | 12 +- src/main/resources/META-INF/mods.toml | 90 +++------ src/main/resources/libzontreck.mixins.json | 13 ++ src/main/resources/pack.mcmeta | 11 +- 10 files changed, 131 insertions(+), 338 deletions(-) create mode 100644 src/main/resources/libzontreck.mixins.json diff --git a/build.gradle b/build.gradle index 757a4f1..fb26e77 100644 --- a/build.gradle +++ b/build.gradle @@ -1,22 +1,14 @@ plugins { - id 'eclipse' - id 'idea' + id 'dev.architectury.loom' version '1.6-SNAPSHOT' id 'maven-publish' - id 'net.minecraftforge.gradle' version '[6.0,6.2)' - id 'org.parchmentmc.librarian.forgegradle' version '1.+' + id 'java' } -version = mod_version -group = mod_group_id +group = project.maven_group +version = project.mod_version base { - archivesName = mod_id -} - -java { - - withSourcesJar() - withJavadocJar() + archivesName = project.archives_name } configurations { @@ -26,192 +18,71 @@ configurations { minecraftLibrary.extendsFrom(provided) } -// Mojang ships Java 17 to end users in 1.18+, so your mod should target Java 17. -java.toolchain.languageVersion = JavaLanguageVersion.of(17) +loom { + silentMojangMappingsLicense() -println "Java: ${System.getProperty 'java.version'}, JVM: ${System.getProperty 'java.vm.version'} (${System.getProperty 'java.vendor'}), Arch: ${System.getProperty 'os.arch'}" -minecraft { - // The mappings can be changed at any time and must be in the following format. - // Channel: Version: - // official MCVersion Official field/method names from Mojang mapping files - // parchment YYYY.MM.DD-MCVersion Open community-sourced parameter names and javadocs layered on top of official - // - // You must be aware of the Mojang license when using the 'official' or 'parchment' mappings. - // See more information here: https://github.com/MinecraftForge/MCPConfig/blob/master/Mojang.md - // - // Parchment is an unofficial project maintained by ParchmentMC, separate from MinecraftForge - // Additional setup is needed to use their mappings: https://github.com/ParchmentMC/Parchment/wiki/Getting-Started - // - // Use non-default mappings at your own risk. They may not always work. - // Simply re-run your setup task after changing the mappings to update your workspace. - //mappings channel: mapping_channel, version: "${parchment_version}-${minecraft_version}" - mappings channel: mapping_channel, version: "${minecraft_version}" - - // When true, this property will have all Eclipse/IntelliJ IDEA run configurations run the "prepareX" task for the given run configuration before launching the game. - // In most cases, it is not necessary to enable. - enableEclipsePrepareRuns = true - enableIdeaPrepareRuns = true - - // This property allows configuring Gradle's ProcessResources task(s) to run on IDE output locations before launching the game. - // It is REQUIRED to be set to true for this template to function. - // See https://docs.gradle.org/current/dsl/org.gradle.language.jvm.tasks.ProcessResources.html - copyIdeResources = true - - // When true, this property will add the folder name of all declared run configurations to generated IDE run configurations. - // The folder name can be set on a run configuration using the "folderName" property. - // By default, the folder name of a run configuration is the name of the Gradle project containing it. - generateRunFolders = true - - // This property enables access transformers for use in development. - // They will be applied to the Minecraft artifact. - // The access transformer file can be anywhere in the project. - // However, it must be at "META-INF/accesstransformer.cfg" in the final mod jar to be loaded by Forge. - // This default location is a best practice to automatically put the file in the right place in the final jar. - // See https://docs.minecraftforge.net/en/latest/advanced/accesstransformers/ for more information. - accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg') - - // Default run configurations. - // These can be tweaked, removed, or duplicated as needed. - runs { - // applies to all the run configs below - configureEach { - workingDirectory project.file('run') - - // Recommended logging data for a userdev environment - // The markers can be added/remove as needed separated by commas. - // "SCAN": For mods scan. - // "REGISTRIES": For firing of registry events. - // "REGISTRYDUMP": For getting the contents of all registries. - property 'forge.logging.markers', 'REGISTRIES' - - // Recommended logging level for the console - // You can set various levels here. - // Please read: https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levels - property 'forge.logging.console.level', 'debug' - - // Comma-separated list of namespaces to load gametests from. Empty = all namespaces. - property 'forge.enabledGameTestNamespaces', mod_id - - mods { - "${mod_id}" { - source sourceSets.main - } - } - } - - client { - // Comma-separated list of namespaces to load gametests from. Empty = all namespaces. - property 'forge.enabledGameTestNamespaces', mod_id - } - - server { - property 'forge.enabledGameTestNamespaces', mod_id - args '--nogui' - } - - // This run config launches GameTestServer and runs all registered gametests, then exits. - // By default, the server will crash when no gametests are provided. - // The gametest system is also enabled by default for other run configs under the /test command. - gameTestServer { - property 'forge.enabledGameTestNamespaces', mod_id - } - - data { - // example of overriding the workingDirectory set in configureEach above - workingDirectory project.file('run-data') - - // Specify the modid for data generation, where to output the resulting resource, and where to look for existing resources. - args '--mod', mod_id, '--all', '--output', file('src/generated/resources/'), '--existing', file('src/main/resources/') - } + forge { + mixinConfig 'libzontreck.mixins.json' } } -// Include resources generated by data generators. -sourceSets.main.resources { srcDir 'src/generated/resources' } - repositories { - mavenCentral() - // Put repositories for dependencies here - // ForgeGradle automatically adds the Forge maven and Maven Central for you - - // If you have mod jar dependencies in ./libs, you can declare them as a repository like so: - flatDir { - dir 'libs' - } - - //maven { - // name = "CurseMaven" - // url = "https://cursemaven.com" - //} + // Add repositories to retrieve artifacts from in here. + // You should only use this when depending on other mods because + // Loom adds the essential maven repositories to download Minecraft and libraries from automatically. + // See https://docs.gradle.org/current/userguide/declaring_repositories.html + // for more information about repositories. maven { name = "zontreck Maven" url = "https://git.zontreck.com/api/packages/AriasCreations/maven" } - } dependencies { - // Specify the version of Minecraft to use. - // Any artifact can be supplied so long as it has a "userdev" classifier artifact and is a compatible patcher artifact. - // The "userdev" classifier will be requested and setup by ForgeGradle. - // If the group id is "net.minecraft" and the artifact id is one of ["client", "server", "joined"], - // then special handling is done to allow a setup of a vanilla dependency without the use of an external repository. - minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}" + minecraft "net.minecraft:minecraft:$project.minecraft_version" + mappings loom.officialMojangMappings() + forge "net.minecraftforge:forge:$project.forge_version" + provided "dev.zontreck:LibAC:${libac}" provided "dev.zontreck:EventsBus:${eventsbus}" - - // Example mod dependency with JEI - using fg.deobf() ensures the dependency is remapped to your development mappings - // The JEI API is declared for compile time use, while the full JEI artifact is used at runtime - // compileOnly fg.deobf("mezz.jei:jei-${mc_version}-common-api:${jei_version}") - // compileOnly fg.deobf("mezz.jei:jei-${mc_version}-forge-api:${jei_version}") - // runtimeOnly fg.deobf("mezz.jei:jei-${mc_version}-forge:${jei_version}") - - // Example mod dependency using a mod jar from ./libs with a flat dir repository - // This maps to ./libs/coolmod-${mc_version}-${coolmod_version}.jar - // The group id is ignored when searching -- in this case, it is "blank" - // implementation fg.deobf("blank:coolmod-${mc_version}:${coolmod_version}") - - // For more info: - // http://www.gradle.org/docs/current/userguide/artifact_dependencies_tutorial.html - // http://www.gradle.org/docs/current/userguide/dependency_management.html } -// This block of code expands all declared replace properties in the specified resource targets. -// A missing property will result in an error. Properties are expanded using ${} Groovy notation. -// When "copyIdeResources" is enabled, this will also run before the game launches in IDE environments. -// See https://docs.gradle.org/current/dsl/org.gradle.language.jvm.tasks.ProcessResources.html -tasks.named('processResources', ProcessResources).configure { - var replaceProperties = [ - minecraft_version: minecraft_version, minecraft_version_range: minecraft_version_range, - forge_version: forge_version, forge_version_range: forge_version_range, - loader_version_range: loader_version_range, - mod_id: mod_id, mod_name: mod_name, mod_license: mod_license, mod_version: mod_version, - mod_authors: mod_authors, mod_description: mod_description, - ] - inputs.properties replaceProperties +processResources { + inputs.property 'version', project.version - filesMatching(['META-INF/mods.toml', 'pack.mcmeta']) { - expand replaceProperties + [project: project] + filesMatching('META-INF/mods.toml') { + expand version: project.version } } -// Example for how to get properties into the manifest for reading at runtime. +java { + // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task + // if it is present. + // If you remove this line, sources will not be generated. + withSourcesJar() + withJavadocJar() + + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 +} + +tasks.withType(JavaCompile).configureEach { + it.options.release = 17 +} tasks.named('jar', Jar).configure { from { - configurations.provided.asFileTree.collect{zipTree(it)} + configurations.provided.asFileTree.collect { zipTree(it) } } duplicatesStrategy = DuplicatesStrategy.EXCLUDE manifest { attributes([ - 'Specification-Title' : mod_id, - 'Specification-Vendor' : mod_authors, + 'Specification-Title' : project.name, 'Specification-Version' : '1', // We are version 1 of ourselves 'Implementation-Title' : project.name, 'Implementation-Version' : project.jar.archiveVersion, - 'Implementation-Vendor' : mod_authors, 'Implementation-Timestamp': new Date().format("yyyy-MM-dd'T'HH:mm:ssZ") ]) } @@ -220,14 +91,9 @@ tasks.named('jar', Jar).configure { finalizedBy 'reobfJar' } -// However if you are in a multi-project build, dev time needs unobfed jar files, so you can delay the obfuscation until publishing by doing: -// tasks.named('publish').configure { -// dependsOn 'reobfJar' -// } - - def MAVEN_PASSWORD = "AriasCreationsMavenPassword" def MAVEN_USER = "AriasCreationsMavenUser" +// Configure Maven publishing. publishing { publications { @@ -249,7 +115,3 @@ publishing { } } } - -tasks.withType(JavaCompile).configureEach { - options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation -} diff --git a/gradle.properties b/gradle.properties index bb9fd01..094ae31 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,64 +1,12 @@ -# Sets default memory used for gradle commands. Can be overridden by user or command line properties. -# This is required to provide enough memory for the Minecraft decompilation process. -org.gradle.jvmargs=-Xmx3G -Dfile.encoding=utf-8 -org.gradle.daemon=false - - -## Environment Properties - -# The Minecraft version must agree with the Forge version to get a valid artifact -minecraft_version=1.19.2 -# The Minecraft version range can use any release version of Minecraft as bounds. -# Snapshots, pre-releases, and release candidates are not guaranteed to sort properly -# as they do not follow standard versioning conventions. -minecraft_version_range=[1.19.2,1.20) -# The Forge version must agree with the Minecraft version to get a valid artifact -forge_version=43.4.2 -# The Forge version range can use any version of Forge as bounds or match the loader version range -forge_version_range=[43,) -# The loader version range can only use the major version of Forge/FML as bounds -loader_version_range=[43,) -# The mapping channel to use for mappings. -# The default set of supported mapping channels are ["official", "snapshot", "snapshot_nodoc", "stable", "stable_nodoc"]. -# Additional mapping channels can be registered through the "channelProviders" extension in a Gradle plugin. -# -# | Channel | Version | | -# |-----------|----------------------|--------------------------------------------------------------------------------| -# | official | MCVersion | Official field/method names from Mojang mapping files | -# | parchment | YYYY.MM.DD-MCVersion | Open community-sourced parameter names and javadocs layered on top of official | -# -# You must be aware of the Mojang license when using the 'official' or 'parchment' mappings. -# See more information here: https://github.com/MinecraftForge/MCPConfig/blob/master/Mojang.md -# -# Parchment is an unofficial project maintained by ParchmentMC, separate from Minecraft Forge. -# Additional setup is needed to use their mappings, see https://parchmentmc.org/docs/getting-started -mapping_channel=official -# The mapping version to query from the mapping channel. -# This must match the format required by the mapping channel. -parchment_version=2022.11.27 -# luckperms_api_version=5.4 - +org.gradle.jvmargs=-Xmx1G +loom.platform=forge libac=1.5.33 eventsbus=1.0.48 -## Environment Properties - - -## Mod Properties - -# The unique mod identifier for the mod. Must be lowercase in English locale. Must fit the regex [a-z][a-z0-9_]{1,63} -# Must match the String constant located in the main mod class annotated with @Mod. -mod_id=libzontreck -# The human-readable display name for the mod. -mod_name=Zontreck Library Mod -# The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. -mod_license=GPLv3 -# The mod version. See https://semver.org/ +# Mod properties mod_version=1192.13.091224.1802 -# The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. -# This should match the base package used for the mod sources. -# See https://maven.apache.org/guides/mini/guide-naming-conventions.html -mod_group_id=dev.zontreck -# The authors of the mod. This is a simple text string that is used for display purposes in the mod list. -mod_authors=zontreck -# The description of the mod. This is a simple multiline text string that is used for display purposes in the mod list. -mod_description=LibZontreck\nLibrary Mod! +maven_group=dev.zontreck +archives_name=libzontreck +# Minecraft properties +minecraft_version=1.19.2 +# Dependencies +forge_version=1.19.2-43.4.0 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index c1962a79e29d3e0ab67b14947c167a862655af9b..e6441136f3d4ba8a0da8d277868979cfbc8ad796 100644 GIT binary patch literal 43453 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 yu?e%!_ptzGB|8GrCfu%p?(_ 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
NnX)EH+Nua)3Y(c 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2617362..a441313 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -2,5 +2,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index a69d9cb..1aa94a4 100755 --- a/gradlew +++ b/gradlew @@ -55,7 +55,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -80,13 +80,11 @@ do esac done -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" +# This is normally unused +# shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,22 +131,29 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -193,11 +198,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/gradlew.bat b/gradlew.bat index 53a6b23..7101f8e 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -26,6 +26,7 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -42,11 +43,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -56,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail diff --git a/settings.gradle b/settings.gradle index 25c32b0..f89415a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,14 +1,10 @@ pluginManagement { repositories { + maven { url "https://maven.fabricmc.net/" } + maven { url "https://maven.architectury.dev/" } + maven { url "https://files.minecraftforge.net/maven/" } gradlePluginPortal() - maven { - name = 'MinecraftForge' - url = 'https://maven.minecraftforge.net/' - } - maven { url = 'https://maven.parchmentmc.org' } // Add this line } } -plugins { - id 'org.gradle.toolchains.foojay-resolver-convention' version '0.7.0' -} +rootProject.name = 'libzontreck' diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index a4c2119..021ffce 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -1,64 +1,28 @@ -# This is an example mods.toml file. It contains the data relating to the loading mods. -# There are several mandatory fields (#mandatory), and many more that are optional (#optional). -# The overall format is standard TOML format, v0.5.0. -# Note that there are a couple of TOML lists in this file. -# Find more information on toml format here: https://github.com/toml-lang/toml -# The name of the mod loader type to load - for regular FML @Mod mods it should be javafml -modLoader="javafml" #mandatory -# A version range to match for said mod loader - for regular FML @Mod it will be the forge version -loaderVersion="${loader_version_range}" #mandatory This is typically bumped every Minecraft version by Forge. See our download page for lists of versions. -# The license for you mod. This is mandatory metadata and allows for easier comprehension of your redistributive properties. -# Review your options at https://choosealicense.com/. All rights reserved is the default copyright stance, and is thus the default here. -license="${mod_license}" -# A URL to refer people to when problems occur with this mod -issueTrackerURL="https://git.zontreck.com/MinecraftMods/LibZontreck/issues" #optional -# A list of mods - how many allowed here is determined by the individual mod loader -[[mods]] #mandatory -# The modid of the mod -modId="${mod_id}" #mandatory -# The version number of the mod -version="${mod_version}" #mandatory -# A display name for the mod -displayName="${mod_name}" #mandatory -# A URL to query for updates for this mod. See the JSON update specification https://docs.minecraftforge.net/en/latest/misc/updatechecker/ -#updateJSONURL="https://change.me.example.invalid/updates.json" #optional -# A URL for the "homepage" for this mod, displayed in the mod UI -#displayURL="https://change.me.to.your.mods.homepage.example.invalid/" #optional -# A file name (in the root of the mod JAR) containing a logo for display -#logoFile="examplemod.png" #optional -# A text field displayed in the mod UI -#credits="" #optional -# A text field displayed in the mod UI -authors="${mod_authors}" #optional -# Display Test controls the display for your mod in the server connection screen -# MATCH_VERSION means that your mod will cause a red X if the versions on client and server differ. This is the default behaviour and should be what you choose if you have server and client elements to your mod. -# IGNORE_SERVER_VERSION means that your mod will not cause a red X if it's present on the server but not on the client. This is what you should use if you're a server only mod. -# IGNORE_ALL_VERSION means that your mod will not cause a red X if it's present on the client or the server. This is a special case and should only be used if your mod has no server component. -# NONE means that no display test is set on your mod. You need to do this yourself, see IExtensionPoint.DisplayTest for more information. You can define any scheme you wish with this value. -# IMPORTANT NOTE: this is NOT an instruction as to which environments (CLIENT or DEDICATED SERVER) your mod loads on. Your mod should load (and maybe do nothing!) whereever it finds itself. -#displayTest="MATCH_VERSION" # MATCH_VERSION is the default if nothing is specified (#optional) +modLoader = "javafml" +loaderVersion = "[43,)" +#issueTrackerURL = "" +license = "GPLv3" -# The description text for the mod (multi line!) (#mandatory) -description='''${mod_description}''' -# A dependency - use the . to indicate dependency for a specific modid. Dependencies are optional. -[[dependencies.${mod_id}]] #optional -# the modid of the dependency -modId="forge" #mandatory -# Does this dependency have to exist - if not, ordering below must be specified -mandatory=true #mandatory -# The version range of the dependency -versionRange="${forge_version_range}" #mandatory -# An ordering relationship for the dependency - BEFORE or AFTER required if the dependency is not mandatory -# BEFORE - This mod is loaded BEFORE the dependency -# AFTER - This mod is loaded AFTER the dependency -ordering="NONE" -# Side this dependency is applied on - BOTH, CLIENT, or SERVER -side="BOTH" -# Here's another dependency -[[dependencies.${mod_id}]] -modId="minecraft" -mandatory=true -# This version range declares a minimum of the current minecraft version up to but not including the next major version -versionRange="${minecraft_version_range}" -ordering="NONE" -side="BOTH" +[[mods]] +modId = "libzontreck" +version = "${version}" +displayName = "LibZontreck" +authors = "Me!" +description = ''' +Common code library mod for all of zontreck's mods. +''' +#logoFile = "" + +[[dependencies.libzontreck]] +modId = "forge" +mandatory = true +versionRange = "[43,)" +ordering = "NONE" +side = "BOTH" + +[[dependencies.libzontreck]] +modId = "minecraft" +mandatory = true +versionRange = "[1.19.2,)" +ordering = "NONE" +side = "BOTH" diff --git a/src/main/resources/libzontreck.mixins.json b/src/main/resources/libzontreck.mixins.json new file mode 100644 index 0000000..d52cd3b --- /dev/null +++ b/src/main/resources/libzontreck.mixins.json @@ -0,0 +1,13 @@ +{ + "required": true, + "package": "dev.zontreck.mixin", + "compatibilityLevel": "JAVA_17", + "minVersion": "0.8", + "client": [ + ], + "mixins": [ + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/src/main/resources/pack.mcmeta b/src/main/resources/pack.mcmeta index 81a429d..d66feac 100644 --- a/src/main/resources/pack.mcmeta +++ b/src/main/resources/pack.mcmeta @@ -1,8 +1,7 @@ { - "pack": { - "description": "${mod_id} resources", - "pack_format": 9, - "forge:resource_pack_format": 9, - "forge:data_pack_format": 10 - } + "pack": { + "description": "LibZontreck", + "forge:data_pack_format": 10, + "pack_format": 9 + } } From 307b3427e8a7ae6bd23157ce257aa325f5b73ba5 Mon Sep 17 00:00:00 2001 From: zontreck Date: Wed, 9 Oct 2024 22:21:30 -0700 Subject: [PATCH 19/21] Don't jarjar anymore. --- build.gradle | 15 - .../dev/zontreck/ariaslib/args/Argument.java | 106 + .../zontreck/ariaslib/args/ArgumentType.java | 25 + .../dev/zontreck/ariaslib/args/Arguments.java | 63 + .../ariaslib/args/ArgumentsBuilder.java | 20 + .../ariaslib/args/ArgumentsParser.java | 99 + .../ariaslib/args/BooleanArgument.java | 35 + .../ariaslib/args/IntegerArgument.java | 30 + .../zontreck/ariaslib/args/LongArgument.java | 30 + .../ariaslib/args/StringArgument.java | 30 + .../EventRegistrationException.java | 7 + .../WrongArgumentTypeException.java | 7 + .../dev/zontreck/ariaslib/file/AriaIO.java | 34 + .../dev/zontreck/ariaslib/file/Entry.java | 281 +++ .../dev/zontreck/ariaslib/file/EntryType.java | 36 + .../zontreck/ariaslib/file/EntryUtils.java | 118 + .../dev/zontreck/ariaslib/file/Folder.java | 42 + .../dev/zontreck/ariaslib/html/Bootstrap.java | 283 +++ .../ariaslib/html/ClassAttribute.java | 8 + .../java/dev/zontreck/ariaslib/html/DOM.java | 55 + .../zontreck/ariaslib/html/HTMLAttribute.java | 24 + .../zontreck/ariaslib/html/HTMLElement.java | 72 + .../ariaslib/html/HTMLElementBuilder.java | 118 + .../ariaslib/html/bootstrap/Color.java | 46 + .../ariaslib/html/bootstrap/Icons.java | 1969 +++++++++++++++++ .../ariaslib/html/bootstrap/Size.java | 25 + .../zontreck/ariaslib/http/HTTPMethod.java | 9 + .../zontreck/ariaslib/http/HTTPRequest.java | 15 + .../ariaslib/http/HTTPRequestBuilder.java | 134 ++ .../zontreck/ariaslib/http/HTTPResponse.java | 33 + .../dev/zontreck/ariaslib/json/Completed.java | 15 + .../dev/zontreck/ariaslib/json/DynSerial.java | 11 + .../ariaslib/json/DynamicDeserializer.java | 119 + .../ariaslib/json/DynamicSerializer.java | 115 + .../ariaslib/json/IgnoreSerialization.java | 12 + .../zontreck/ariaslib/json/JsonObject.java | 183 ++ .../dev/zontreck/ariaslib/json/ListOrMap.java | 8 + .../zontreck/ariaslib/json/PreSerialize.java | 13 + .../zontreck/ariaslib/terminal/Banners.java | 47 + .../dev/zontreck/ariaslib/terminal/Task.java | 88 + .../terminal/TaskCompletionToken.java | 20 + .../zontreck/ariaslib/terminal/Terminal.java | 35 + .../ariaslib/util/DelayedExecutorService.java | 17 + .../ariaslib/util/EnvironmentUtils.java | 19 + .../dev/zontreck/ariaslib/util/FileIO.java | 51 + .../dev/zontreck/ariaslib/util/Hashing.java | 120 + .../dev/zontreck/ariaslib/util/Lists.java | 94 + .../java/dev/zontreck/ariaslib/util/Maps.java | 49 + .../dev/zontreck/ariaslib/util/MathUtil.java | 18 + .../dev/zontreck/ariaslib/util/Percent.java | 23 + .../dev/zontreck/ariaslib/util/Progress.java | 56 + .../zontreck/ariaslib/util/ProgressBar.java | 66 + .../zontreck/ariaslib/util/TimeNotation.java | 161 ++ .../dev/zontreck/ariaslib/util/TimeUtil.java | 96 + .../zontreck/ariaslib/xmlrpc/MethodCall.java | 32 + .../ariaslib/xmlrpc/MethodResponse.java | 26 + .../dev/zontreck/ariaslib/xmlrpc/README.md | 7 + .../ariaslib/xmlrpc/XmlRpcDeserializer.java | 44 + .../ariaslib/xmlrpc/XmlRpcException.java | 24 + .../ariaslib/xmlrpc/XmlRpcSerializer.java | 26 + .../ariaslib/xmlrpc/XmlRpcStreamReader.java | 206 ++ .../ariaslib/xmlrpc/XmlRpcStreamWriter.java | 166 ++ .../ariaslib/xmlrpc/XmlRpcTokens.java | 48 + src/main/java/dev/zontreck/eventsbus/Bus.java | 166 ++ .../dev/zontreck/eventsbus/ClassScanner.java | 95 + .../java/dev/zontreck/eventsbus/Event.java | 46 + .../zontreck/eventsbus/EventContainer.java | 45 + .../zontreck/eventsbus/EventDispatcher.java | 124 ++ .../dev/zontreck/eventsbus/PriorityLevel.java | 15 + .../eventsbus/annotations/Cancellable.java | 11 + .../annotations/EventSubscriber.java | 11 + .../eventsbus/annotations/Priority.java | 14 + .../annotations/SingleshotEvent.java | 11 + .../eventsbus/annotations/Subscribe.java | 18 + .../eventsbus/events/EventBusReadyEvent.java | 11 + .../eventsbus/events/ResetEventBusEvent.java | 17 + .../zontreck/eventsbus/events/TickEvent.java | 6 + 77 files changed, 6359 insertions(+), 15 deletions(-) create mode 100644 src/main/java/dev/zontreck/ariaslib/args/Argument.java create mode 100644 src/main/java/dev/zontreck/ariaslib/args/ArgumentType.java create mode 100644 src/main/java/dev/zontreck/ariaslib/args/Arguments.java create mode 100644 src/main/java/dev/zontreck/ariaslib/args/ArgumentsBuilder.java create mode 100644 src/main/java/dev/zontreck/ariaslib/args/ArgumentsParser.java create mode 100644 src/main/java/dev/zontreck/ariaslib/args/BooleanArgument.java create mode 100644 src/main/java/dev/zontreck/ariaslib/args/IntegerArgument.java create mode 100644 src/main/java/dev/zontreck/ariaslib/args/LongArgument.java create mode 100644 src/main/java/dev/zontreck/ariaslib/args/StringArgument.java create mode 100644 src/main/java/dev/zontreck/ariaslib/exceptions/EventRegistrationException.java create mode 100644 src/main/java/dev/zontreck/ariaslib/exceptions/WrongArgumentTypeException.java create mode 100644 src/main/java/dev/zontreck/ariaslib/file/AriaIO.java create mode 100644 src/main/java/dev/zontreck/ariaslib/file/Entry.java create mode 100644 src/main/java/dev/zontreck/ariaslib/file/EntryType.java create mode 100644 src/main/java/dev/zontreck/ariaslib/file/EntryUtils.java create mode 100644 src/main/java/dev/zontreck/ariaslib/file/Folder.java create mode 100644 src/main/java/dev/zontreck/ariaslib/html/Bootstrap.java create mode 100644 src/main/java/dev/zontreck/ariaslib/html/ClassAttribute.java create mode 100644 src/main/java/dev/zontreck/ariaslib/html/DOM.java create mode 100644 src/main/java/dev/zontreck/ariaslib/html/HTMLAttribute.java create mode 100644 src/main/java/dev/zontreck/ariaslib/html/HTMLElement.java create mode 100644 src/main/java/dev/zontreck/ariaslib/html/HTMLElementBuilder.java create mode 100644 src/main/java/dev/zontreck/ariaslib/html/bootstrap/Color.java create mode 100644 src/main/java/dev/zontreck/ariaslib/html/bootstrap/Icons.java create mode 100644 src/main/java/dev/zontreck/ariaslib/html/bootstrap/Size.java create mode 100644 src/main/java/dev/zontreck/ariaslib/http/HTTPMethod.java create mode 100644 src/main/java/dev/zontreck/ariaslib/http/HTTPRequest.java create mode 100644 src/main/java/dev/zontreck/ariaslib/http/HTTPRequestBuilder.java create mode 100644 src/main/java/dev/zontreck/ariaslib/http/HTTPResponse.java create mode 100644 src/main/java/dev/zontreck/ariaslib/json/Completed.java create mode 100644 src/main/java/dev/zontreck/ariaslib/json/DynSerial.java create mode 100644 src/main/java/dev/zontreck/ariaslib/json/DynamicDeserializer.java create mode 100644 src/main/java/dev/zontreck/ariaslib/json/DynamicSerializer.java create mode 100644 src/main/java/dev/zontreck/ariaslib/json/IgnoreSerialization.java create mode 100644 src/main/java/dev/zontreck/ariaslib/json/JsonObject.java create mode 100644 src/main/java/dev/zontreck/ariaslib/json/ListOrMap.java create mode 100644 src/main/java/dev/zontreck/ariaslib/json/PreSerialize.java create mode 100644 src/main/java/dev/zontreck/ariaslib/terminal/Banners.java create mode 100644 src/main/java/dev/zontreck/ariaslib/terminal/Task.java create mode 100644 src/main/java/dev/zontreck/ariaslib/terminal/TaskCompletionToken.java create mode 100644 src/main/java/dev/zontreck/ariaslib/terminal/Terminal.java create mode 100644 src/main/java/dev/zontreck/ariaslib/util/DelayedExecutorService.java create mode 100644 src/main/java/dev/zontreck/ariaslib/util/EnvironmentUtils.java create mode 100644 src/main/java/dev/zontreck/ariaslib/util/FileIO.java create mode 100644 src/main/java/dev/zontreck/ariaslib/util/Hashing.java create mode 100644 src/main/java/dev/zontreck/ariaslib/util/Lists.java create mode 100644 src/main/java/dev/zontreck/ariaslib/util/Maps.java create mode 100644 src/main/java/dev/zontreck/ariaslib/util/MathUtil.java create mode 100644 src/main/java/dev/zontreck/ariaslib/util/Percent.java create mode 100644 src/main/java/dev/zontreck/ariaslib/util/Progress.java create mode 100644 src/main/java/dev/zontreck/ariaslib/util/ProgressBar.java create mode 100644 src/main/java/dev/zontreck/ariaslib/util/TimeNotation.java create mode 100644 src/main/java/dev/zontreck/ariaslib/util/TimeUtil.java create mode 100644 src/main/java/dev/zontreck/ariaslib/xmlrpc/MethodCall.java create mode 100644 src/main/java/dev/zontreck/ariaslib/xmlrpc/MethodResponse.java create mode 100644 src/main/java/dev/zontreck/ariaslib/xmlrpc/README.md create mode 100644 src/main/java/dev/zontreck/ariaslib/xmlrpc/XmlRpcDeserializer.java create mode 100644 src/main/java/dev/zontreck/ariaslib/xmlrpc/XmlRpcException.java create mode 100644 src/main/java/dev/zontreck/ariaslib/xmlrpc/XmlRpcSerializer.java create mode 100644 src/main/java/dev/zontreck/ariaslib/xmlrpc/XmlRpcStreamReader.java create mode 100644 src/main/java/dev/zontreck/ariaslib/xmlrpc/XmlRpcStreamWriter.java create mode 100644 src/main/java/dev/zontreck/ariaslib/xmlrpc/XmlRpcTokens.java create mode 100644 src/main/java/dev/zontreck/eventsbus/Bus.java create mode 100644 src/main/java/dev/zontreck/eventsbus/ClassScanner.java create mode 100644 src/main/java/dev/zontreck/eventsbus/Event.java create mode 100644 src/main/java/dev/zontreck/eventsbus/EventContainer.java create mode 100644 src/main/java/dev/zontreck/eventsbus/EventDispatcher.java create mode 100644 src/main/java/dev/zontreck/eventsbus/PriorityLevel.java create mode 100644 src/main/java/dev/zontreck/eventsbus/annotations/Cancellable.java create mode 100644 src/main/java/dev/zontreck/eventsbus/annotations/EventSubscriber.java create mode 100644 src/main/java/dev/zontreck/eventsbus/annotations/Priority.java create mode 100644 src/main/java/dev/zontreck/eventsbus/annotations/SingleshotEvent.java create mode 100644 src/main/java/dev/zontreck/eventsbus/annotations/Subscribe.java create mode 100644 src/main/java/dev/zontreck/eventsbus/events/EventBusReadyEvent.java create mode 100644 src/main/java/dev/zontreck/eventsbus/events/ResetEventBusEvent.java create mode 100644 src/main/java/dev/zontreck/eventsbus/events/TickEvent.java diff --git a/build.gradle b/build.gradle index fb26e77..8ab7578 100644 --- a/build.gradle +++ b/build.gradle @@ -11,13 +11,6 @@ base { archivesName = project.archives_name } -configurations { - provided - compile.extendsFrom(provided) - implementation.extendsFrom(provided) - minecraftLibrary.extendsFrom(provided) -} - loom { silentMojangMappingsLicense() @@ -43,10 +36,6 @@ dependencies { minecraft "net.minecraft:minecraft:$project.minecraft_version" mappings loom.officialMojangMappings() forge "net.minecraftforge:forge:$project.forge_version" - - - provided "dev.zontreck:LibAC:${libac}" - provided "dev.zontreck:EventsBus:${eventsbus}" } processResources { @@ -72,10 +61,6 @@ tasks.withType(JavaCompile).configureEach { it.options.release = 17 } tasks.named('jar', Jar).configure { - from { - configurations.provided.asFileTree.collect { zipTree(it) } - } - duplicatesStrategy = DuplicatesStrategy.EXCLUDE manifest { attributes([ diff --git a/src/main/java/dev/zontreck/ariaslib/args/Argument.java b/src/main/java/dev/zontreck/ariaslib/args/Argument.java new file mode 100644 index 0000000..64463d8 --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/args/Argument.java @@ -0,0 +1,106 @@ +package dev.zontreck.ariaslib.args; + +import dev.zontreck.ariaslib.exceptions.WrongArgumentTypeException; + +public abstract class Argument implements Cloneable +{ + public boolean hasValue = false; + public String name; + + + /** + * Initializes a boolean only command line toggle + * + * @param name The option name + */ + public Argument(String name) { + this.name = name; + } + + /** + * Retrieves the current argument's type + * + * @return The argument type! + */ + public abstract ArgumentType getType(); + + + /** + * Retrieves the value. + * + * @return The value + * @throws IllegalArgumentException When there is no value + */ + public T getValue() throws IllegalArgumentException { + throw new IllegalArgumentException("No value"); + } + + /** + * Directly cast to the Argument type Long + * @return + * @throws WrongArgumentTypeException Throws when type does not match + */ + public LongArgument getAsLong() throws WrongArgumentTypeException { + if(this instanceof LongArgument) + { + return (LongArgument) this; + } + + throw new WrongArgumentTypeException(); + } + + + /** + * Directly cast to the Argument type String + * @return + * @throws WrongArgumentTypeException Throws when type does not match + */ + public StringArgument getAsString() throws WrongArgumentTypeException { + if(this instanceof StringArgument) + { + return (StringArgument) this; + } + + throw new WrongArgumentTypeException(); + } + + + /** + * Directly cast to the Argument type Integer + * @return + * @throws WrongArgumentTypeException Throws when type does not match + */ + public IntegerArgument getAsInteger() throws WrongArgumentTypeException + { + if(this instanceof IntegerArgument) + { + return (IntegerArgument) this; + } + + throw new WrongArgumentTypeException(); + } + + @Override + public Argument clone() { + Argument arg = null; + try{ + + if(getType() == ArgumentType.LONG) + { + arg = (Argument) new LongArgument(name, getAsLong().getValue()); + } else if(getType() == ArgumentType.STRING) + { + arg = (Argument) new StringArgument(name, getAsString().getValue()); + } else if(getType() == ArgumentType.BOOLEAN) { + arg = (Argument) new BooleanArgument(name); + } else if(getType() == ArgumentType.INTEGER){ + arg = (Argument) new IntegerArgument(name, getAsInteger().getValue()); + } + }catch (WrongArgumentTypeException ex) + { + ex.printStackTrace(); + } + + return arg; + } +} diff --git a/src/main/java/dev/zontreck/ariaslib/args/ArgumentType.java b/src/main/java/dev/zontreck/ariaslib/args/ArgumentType.java new file mode 100644 index 0000000..82c310b --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/args/ArgumentType.java @@ -0,0 +1,25 @@ +package dev.zontreck.ariaslib.args; + +public enum ArgumentType { + /** + * This indicates a string. It may possibly have a default value + */ + STRING, + /** + * This indicates a boolean + *

+ * This may have a default value, which initiates a BooleanArgument + */ + BOOLEAN, + + /** + * This indicates a data type of integer + * The type of integer arg is determined by the length of the integer. + */ + INTEGER, + + /** + * This is a long value, which can hold larger values than a integer. The type of integer arg is determined by the length of the integer. + */ + LONG +} diff --git a/src/main/java/dev/zontreck/ariaslib/args/Arguments.java b/src/main/java/dev/zontreck/ariaslib/args/Arguments.java new file mode 100644 index 0000000..3499274 --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/args/Arguments.java @@ -0,0 +1,63 @@ +package dev.zontreck.ariaslib.args; + +import java.util.HashMap; +import java.util.Map; + +public class Arguments implements Cloneable +{ + private Map> args = new HashMap<>(); + + /** + * Set the argument in the args list + * @param arg + */ + public void setArg(Argument arg) { + args.put(arg.name, arg); + } + + /** + * Checks for and returns the argument + * + * @param argName The argument's name + * @return The argument instance, or null if not found + */ + public Argument getArg(String argName) { + if (hasArg(argName)) + return args.get(argName); + else return null; + } + + /** + * Checks if the argument is set. + * + * @param argName The argument's name to check for + * @return True if the argument is present. This does not indicate if the argument has a value + */ + public boolean hasArg(String argName) { + return args.containsKey(argName); + } + + /** + * Checks the argument (if it exists), for whether a value is set + * + * @param argName This is the argument name + * @return True if a value is set + * @throws IllegalArgumentException If there is no such argument + */ + public boolean argHasValue(String argName) throws IllegalArgumentException { + if (hasArg(argName)) { + return getArg(argName).hasValue; + } else throw new IllegalArgumentException(("No such argument")); + } + + @Override + public Arguments clone() { + Arguments arg = new Arguments(); + for(Map.Entry> entry : args.entrySet()) + { + arg.setArg(entry.getValue().clone()); + } + + return arg; + } +} diff --git a/src/main/java/dev/zontreck/ariaslib/args/ArgumentsBuilder.java b/src/main/java/dev/zontreck/ariaslib/args/ArgumentsBuilder.java new file mode 100644 index 0000000..cf414bd --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/args/ArgumentsBuilder.java @@ -0,0 +1,20 @@ +package dev.zontreck.ariaslib.args; + +public class ArgumentsBuilder { + private Arguments args = new Arguments(); + + public static ArgumentsBuilder builder() { + return new ArgumentsBuilder(); + } + + public ArgumentsBuilder withArgument(Argument arg) { + args.setArg(arg); + return this; + } + + public Arguments build() + { + return args; + } + +} diff --git a/src/main/java/dev/zontreck/ariaslib/args/ArgumentsParser.java b/src/main/java/dev/zontreck/ariaslib/args/ArgumentsParser.java new file mode 100644 index 0000000..8f6bbf5 --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/args/ArgumentsParser.java @@ -0,0 +1,99 @@ +package dev.zontreck.ariaslib.args; + +public class ArgumentsParser { + + /** + * Parses and returns the arguments list with keeping defaults in mind + * @param args + * @param defaults + * @return Arguments with defaults set + */ + public static Arguments parseArguments(String[] args, Arguments defaults) { + Arguments arguments = defaults.clone(); + for (int i = 0; i < args.length; i++) { + Argument arg = parseArgument(args[i]); + if (arg != null) { + Argument defaultArg = null; + if (defaults.hasArg(arg.name)) { + defaultArg = defaults.getArg(arg.name); + } + + if (!arg.hasValue) { + if (defaultArg != null) { + arg = defaultArg; + } + } + + arguments.setArg(arg); + } + } + return arguments; + } + + /** + * Parses and returns an argument with a type set + * @param arg The argument to parse with double tack + * @return Typed Argument + * @throws IllegalArgumentException when no type matches and the input is malformed in some way + */ + public static Argument parseArgument(String arg) { + if (arg.startsWith("--")) { + String[] parts = arg.split("="); + String name = getNamePart(parts[0]); + if (parts.length == 1) { + return new BooleanArgument(name); + + } else if (parts.length == 2) { + String value = getValuePart(parts[1]); + ArgumentType typeOfArg = getArgumentType(value); + switch(typeOfArg) + { + case INTEGER: + { + return new IntegerArgument(name, Integer.parseInt(value)); + } + case LONG: + { + return new LongArgument(name, Long.parseLong(value)); + } + case BOOLEAN: + { + return new BooleanArgument(name); + } + default: + { + return new StringArgument(name, value); + } + } + } else throw new IllegalArgumentException("The argument is malformed. Remember to use --arg=val, or --toggle"); + } else { + throw new IllegalArgumentException("Not a valid argument format"); + } + } + + protected static String getNamePart(String entry) { + return entry.substring(2); + } + + protected static String getValuePart(String entry) { + return entry; + } + + protected static ArgumentType getArgumentType(String input) { + try { + Integer.parseInt(input); + return ArgumentType.INTEGER; + }catch(Exception e){} + try{ + Long.parseLong(input); + return ArgumentType.LONG; + }catch(Exception E){ + + } + + if(input.isEmpty()) + return ArgumentType.BOOLEAN; + else return ArgumentType.STRING; + } +} + diff --git a/src/main/java/dev/zontreck/ariaslib/args/BooleanArgument.java b/src/main/java/dev/zontreck/ariaslib/args/BooleanArgument.java new file mode 100644 index 0000000..cad4620 --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/args/BooleanArgument.java @@ -0,0 +1,35 @@ +package dev.zontreck.ariaslib.args; + +public class BooleanArgument extends Argument { + private boolean value; + + /** + * Initializes a boolean only command line toggle + * + * @param name The option name + */ + public BooleanArgument(String name) { + super(name); + + this.hasValue = true; + this.value = true; + } + + @Override + public Boolean getValue() { + return value; + } + + @Override + public ArgumentType getType() { + return ArgumentType.BOOLEAN; + } + + + @Override + public String toString() { + return "BooleanArgument{" + + name + "=true" + + '}'; + } +} diff --git a/src/main/java/dev/zontreck/ariaslib/args/IntegerArgument.java b/src/main/java/dev/zontreck/ariaslib/args/IntegerArgument.java new file mode 100644 index 0000000..9c7430f --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/args/IntegerArgument.java @@ -0,0 +1,30 @@ +package dev.zontreck.ariaslib.args; + +public class IntegerArgument extends Argument { + private int value; + + public IntegerArgument(String name, int value) { + super(name); + this.hasValue = true; + this.value = value; + } + + @Override + public ArgumentType getType() { + return ArgumentType.INTEGER; + } + + @Override + public Integer getValue() { + return value; + } + + + @Override + public String toString() { + return "IntegerArgument{" + + name + "=" + + value + + '}'; + } +} diff --git a/src/main/java/dev/zontreck/ariaslib/args/LongArgument.java b/src/main/java/dev/zontreck/ariaslib/args/LongArgument.java new file mode 100644 index 0000000..2541d59 --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/args/LongArgument.java @@ -0,0 +1,30 @@ +package dev.zontreck.ariaslib.args; + +public class LongArgument extends Argument { + private long value; + + public LongArgument(String name, long value) { + super(name); + this.hasValue = true; + this.value = value; + } + + @Override + public ArgumentType getType() { + return ArgumentType.LONG; + } + + @Override + public Long getValue() { + return value; + } + + + @Override + public String toString() { + return "LongArgument{" + + name + "=" + + value + + '}'; + } +} diff --git a/src/main/java/dev/zontreck/ariaslib/args/StringArgument.java b/src/main/java/dev/zontreck/ariaslib/args/StringArgument.java new file mode 100644 index 0000000..8c17782 --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/args/StringArgument.java @@ -0,0 +1,30 @@ +package dev.zontreck.ariaslib.args; + +public class StringArgument extends Argument { + + private String value; + + public StringArgument(String name, String value) { + super(name); + this.value = value; + this.hasValue = true; + } + + @Override + public String getValue() { + return value; + } + + @Override + public ArgumentType getType() { + return ArgumentType.STRING; + } + + @Override + public String toString() { + return "StringArgument{" + + name + "=" + + value + + '}'; + } +} diff --git a/src/main/java/dev/zontreck/ariaslib/exceptions/EventRegistrationException.java b/src/main/java/dev/zontreck/ariaslib/exceptions/EventRegistrationException.java new file mode 100644 index 0000000..7446137 --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/exceptions/EventRegistrationException.java @@ -0,0 +1,7 @@ +package dev.zontreck.ariaslib.exceptions; + +public class EventRegistrationException extends Exception{ + public EventRegistrationException(String message){ + super(message); + } +} diff --git a/src/main/java/dev/zontreck/ariaslib/exceptions/WrongArgumentTypeException.java b/src/main/java/dev/zontreck/ariaslib/exceptions/WrongArgumentTypeException.java new file mode 100644 index 0000000..4ab8f8a --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/exceptions/WrongArgumentTypeException.java @@ -0,0 +1,7 @@ +package dev.zontreck.ariaslib.exceptions; + + +public class WrongArgumentTypeException extends Exception +{ + +} diff --git a/src/main/java/dev/zontreck/ariaslib/file/AriaIO.java b/src/main/java/dev/zontreck/ariaslib/file/AriaIO.java new file mode 100644 index 0000000..7cefb9a --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/file/AriaIO.java @@ -0,0 +1,34 @@ +package dev.zontreck.ariaslib.file; + +import java.io.*; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class AriaIO { + public static void write(Path fileName, Entry folder) { + try { + DataOutputStream dos = new DataOutputStream(new FileOutputStream(fileName.toFile())); + folder.write(dos); + dos.close(); + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static Entry read(Path fileName) { + try { + DataInputStream dis = new DataInputStream(new FileInputStream(fileName.toFile())); + return Entry.read(dis); + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static Path resolveDataFile(String main) { + return Paths.get(main + ".aria"); + } +} diff --git a/src/main/java/dev/zontreck/ariaslib/file/Entry.java b/src/main/java/dev/zontreck/ariaslib/file/Entry.java new file mode 100644 index 0000000..8eaba86 --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/file/Entry.java @@ -0,0 +1,281 @@ +package dev.zontreck.ariaslib.file; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * An entry in the serialized file + */ +public class Entry { + public static final byte YES = 1; + public static final byte NO = 0; + + public EntryType type; + public String name; + public K value; + + public Entry(K v, String name) { + value = v; + this.name = name; + if (v instanceof String) { + type = EntryType.STRING; + } else if (v instanceof Integer) { + type = EntryType.INT; + } else if (v instanceof List) { + type = EntryType.FOLDER; + } else if (v instanceof Boolean) { + type = EntryType.BOOL; + } else if (v instanceof Long) { + type = EntryType.LONG; + } else if (v instanceof Short) { + type = EntryType.SHORT; + } else if (v instanceof Byte) { + type = EntryType.BYTE; + } else if (v instanceof Double) { + type = EntryType.DOUBLE; + } else if (v instanceof Float) { + type = EntryType.FLOAT; + } else if (v instanceof int[]) { + type = EntryType.INT_ARRAY; + } else if (v instanceof String[]) { + type = EntryType.STRING_ARRAY; + } else if (v instanceof byte[]) { + type = EntryType.BYTE_ARRAY; + } else if (v instanceof long[]) { + type = EntryType.LONG_ARRAY; + } else { + type = EntryType.INVALID; + } + } + + private Entry() { + } + + public static Entry read(DataInputStream dis) throws IOException { + EntryType et = EntryType.of(dis.readByte()); + int nameLen = dis.readInt(); + byte[] nm = new byte[nameLen]; + for (int i = 0; i < nameLen; i++) { + nm[i] = dis.readByte(); + } + Entry work = new Entry<>(); + work.type = et; + work.name = new String(nm); + //System.out.println("Read start: " + work.name + " [ " + work.type.toString() + " ]"); + + switch (et) { + case FOLDER: { + Entry> entries = (Entry>) work; + entries.value = new ArrayList<>(); + + int numEntries = dis.readInt(); + for (int i = 0; i < numEntries; i++) { + entries.value.add(Entry.read(dis)); + } + + break; + } + case STRING: { + Entry w = (Entry) work; + int vLen = dis.readInt(); + byte[] x = new byte[vLen]; + for (int i = 0; i < vLen; i++) { + x[i] = dis.readByte(); + } + w.value = new String(x); + break; + } + case INT: { + Entry w = (Entry) work; + w.value = dis.readInt(); + break; + } + case BOOL: { + Entry w = (Entry) work; + byte b = dis.readByte(); + if (b == YES) w.value = true; + else w.value = false; + + break; + } + case LONG: { + Entry w = (Entry) work; + w.value = dis.readLong(); + + break; + } + case SHORT: { + Entry w = (Entry) work; + w.value = dis.readShort(); + + break; + } + + case BYTE: { + Entry w = (Entry) work; + w.value = dis.readByte(); + break; + } + case DOUBLE: { + Entry w = (Entry) work; + w.value = dis.readDouble(); + break; + } + case FLOAT: { + Entry w = (Entry) work; + w.value = dis.readFloat(); + break; + } + case INT_ARRAY: { + Entry w = (Entry) work; + int num = dis.readInt(); + w.value = new int[num]; + + for (int i = 0; i < num; i++) { + w.value[i] = dis.readInt(); + } + break; + } + case STRING_ARRAY: { + Entry w = (Entry) work; + int num = dis.readInt(); + w.value = new String[num]; + for (int i = 0; i < num; i++) { + int len = dis.readInt(); + byte[] bStr = new byte[len]; + for (int j = 0; j < len; j++) { + bStr[j] = dis.readByte(); + } + w.value[i] = new String(bStr); + } + break; + } + case BYTE_ARRAY: { + Entry w = (Entry) work; + int num = dis.readInt(); + w.value = new byte[num]; + for (int i = 0; i < num; i++) { + w.value[i] = dis.readByte(); + } + break; + } + case LONG_ARRAY: { + Entry w = (Entry) work; + int num = dis.readInt(); + w.value = new long[num]; + for (int i = 0; i < num; i++) { + w.value[i] = dis.readLong(); + } + break; + } + } + + //System.out.println("Read finished: " + work.name + " [ " + work.type.toString() + " ]"); + + return work; + } + + public void write(DataOutputStream dos) throws IOException { + + dos.writeByte((int) type.value); + byte[] nameBytes = name.getBytes(); + dos.writeInt(nameBytes.length); + dos.write(nameBytes); + + switch (type) { + case FOLDER: { + List> entries = (List>) value; + dos.writeInt(entries.size()); + for (Entry x : + entries) { + x.write(dos); + } + + break; + } + case STRING: { + String s = (String) value; + byte[] bS = s.getBytes(); + dos.writeInt(bS.length); + dos.write(bS); + + break; + } + case INT: { + dos.writeInt((Integer) value); + + break; + } + case BOOL: { + boolean x = (Boolean) value; + + if (x) dos.writeByte(YES); + else dos.writeByte(NO); + + break; + } + case LONG: { + dos.writeLong((Long) value); + + break; + } + case SHORT: { + dos.writeShort((Short) value); + + break; + } + case BYTE: { + dos.write((Byte) value); + break; + } + case DOUBLE: { + dos.writeDouble((Double) value); + break; + } + case FLOAT: { + dos.writeFloat((Float) value); + break; + } + case INT_ARRAY: { + int[] arr = (int[]) value; + dos.writeInt(arr.length); + for (int x : arr + ) { + dos.writeInt(x); + } + break; + } + case STRING_ARRAY: { + String[] arr = (String[]) value; + dos.writeInt(arr.length); + for (String s : arr) { + byte[] bArr = s.getBytes(); + dos.writeInt(bArr.length); + dos.write(bArr); + } + break; + } + case BYTE_ARRAY: { + byte[] arr = (byte[]) value; + dos.writeInt(arr.length); + for (byte b : arr) { + dos.write(b); + } + break; + } + case LONG_ARRAY: { + long[] arr = (long[]) value; + dos.writeInt(arr.length); + for (long L : arr) { + dos.writeLong(L); + } + + break; + } + } + } + +} \ No newline at end of file diff --git a/src/main/java/dev/zontreck/ariaslib/file/EntryType.java b/src/main/java/dev/zontreck/ariaslib/file/EntryType.java new file mode 100644 index 0000000..4ddcdae --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/file/EntryType.java @@ -0,0 +1,36 @@ +package dev.zontreck.ariaslib.file; + +import java.util.Arrays; +import java.util.stream.Collectors; + +public enum EntryType { + FOLDER(0), + STRING(1), + INT(2), + BOOL(3), + LONG(4), + SHORT(5), + BYTE(6), + DOUBLE(7), + FLOAT(8), + INT_ARRAY(9), + STRING_ARRAY(10), + BYTE_ARRAY(11), + LONG_ARRAY(12), + + INVALID(255); + + public byte value; + EntryType(int v) + { + value = (byte)v; + } + + public static EntryType of(byte b) + { + return Arrays.stream(values()) + .filter(c->c.value == b) + .collect(Collectors.toList()) + .get(0); + } +} diff --git a/src/main/java/dev/zontreck/ariaslib/file/EntryUtils.java b/src/main/java/dev/zontreck/ariaslib/file/EntryUtils.java new file mode 100644 index 0000000..3be4678 --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/file/EntryUtils.java @@ -0,0 +1,118 @@ +package dev.zontreck.ariaslib.file; + +import java.util.UUID; + +public class EntryUtils { + public static Entry mkStr(String name, String value) + { + return new Entry(value, name); + } + public static String getStr(Entry e) + { + Entry eS = (Entry) e; + return eS.value; + } + public static Entry mkInt(String name, int value) + { + return new Entry(value, name); + } + public static int getInt(Entry e) + { + Entry eS = (Entry) e; + return eS.value; + } + public static Entry mkBool(String name, boolean value) + { + return new Entry(value, name); + } + public static boolean getBool(Entry e) + { + Entry eS = (Entry) e; + return eS.value; + } + public static Entry mkLong(String name, long value) + { + return new Entry(value, name); + } + public static long getLong(Entry e) + { + Entry eS = (Entry) e; + return eS.value; + } + public static Entry mkShort(String name, short value) + { + return new Entry(value, name); + } + public static short getShort(Entry e) + { + Entry eS = (Entry) e; + return eS.value; + } + public static Entry mkByte(String name, byte value) + { + return new Entry(value, name); + } + public static byte getByte(Entry e) + { + Entry eS = (Entry) e; + return eS.value; + } + public static Entry mkDouble(String name, double value) + { + return new Entry(value, name); + } + public static double getDouble(Entry e) + { + Entry eS = (Entry) e; + return eS.value; + } + public static Entry mkFloat(String name, float value) + { + return new Entry(value, name); + } + public static float getFloat(Entry e) + { + Entry eS = (Entry) e; + return eS.value; + } + public static Entry mkIntArray(String name, int[] value) + { + return new Entry(value, name); + } + public static int[] getIntArray(Entry e) + { + Entry eS = (Entry) e; + return eS.value; + } + public static Entry mkStringArray(String name, String[] value) + { + return new Entry(value, name); + } + public static String[] getStringArray(Entry e) + { + Entry eS = (Entry) e; + return eS.value; + } + public static Entry mkByteArray(String name, byte[] value) + { + return new Entry(value, name); + } + public static byte[] getByteArray(Entry e) + { + Entry eS = (Entry) e; + return eS.value; + } + public static Entry mkUUID(String name, UUID ID) + { + long[] uid = new long[2]; + uid[0] = ID.getLeastSignificantBits(); + uid[1] = ID.getMostSignificantBits(); + return new Entry(uid, name); + } + + public static UUID getUUID(Entry e) + { + Entry uid = (Entry) e; + return new UUID(uid.value[1], uid.value[0]); + } +} diff --git a/src/main/java/dev/zontreck/ariaslib/file/Folder.java b/src/main/java/dev/zontreck/ariaslib/file/Folder.java new file mode 100644 index 0000000..d295e87 --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/file/Folder.java @@ -0,0 +1,42 @@ +package dev.zontreck.ariaslib.file; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class Folder +{ + + public static void add(Entry e, Entry item) + { + ((Entry>)e).value.add(item); + } + + public static void remove(Entry e, Entry item) + { + ((Entry>)e).value.remove(item); + } + + public static Entry getEntry(Entry item, String name) + { + List ret = ((Entry>)item).value; + if(ret.size()>0) + { + for (Entry ent : + ret) { + if(ent.name.equals(name))return ent; + } + } + return null; + } + + public static int size(Entry e) + { + return ((Entry>)e).value.size(); + } + + public static Entry> getNew(String name) + { + return new Entry<>(new ArrayList(), name); + } +} diff --git a/src/main/java/dev/zontreck/ariaslib/html/Bootstrap.java b/src/main/java/dev/zontreck/ariaslib/html/Bootstrap.java new file mode 100644 index 0000000..0fdb0b2 --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/html/Bootstrap.java @@ -0,0 +1,283 @@ +package dev.zontreck.ariaslib.html; + +import dev.zontreck.ariaslib.html.bootstrap.Color; +import dev.zontreck.ariaslib.html.bootstrap.Icons; +import dev.zontreck.ariaslib.html.bootstrap.Size; +import dev.zontreck.ariaslib.util.Percent; + +public class Bootstrap { + public static class Border { + public Side side; + public int width = 1; + public boolean usesSides = false; + public Colors colors; + public boolean invert; + + public static Border make() { + return new Border(); + } + + public Border withColor(Colors color) { + this.colors = color.withPrefix("border"); + return this; + } + + public Border widthSide(Side side) { + this.side = side; + usesSides = true; + return this; + } + + public Border withWidth(int width) { + this.width = width; + return this; + } + + /** + * Removes borders instead of adding + */ + public Border setInverted() { + invert = true; + return this; + } + + public void apply(HTMLElementBuilder elem) { + elem.addClass("border"); + + colors.apply(elem); + + if (usesSides) { + elem.addClass("border-" + side.name().toLowerCase() + (invert ? "-0" : "")); + } else { + if (invert) elem.addClass("border-0"); + } + } + + + public enum Side { + Start, + End, + Top, + Bottom + } + } + + public static class Opacity { + public Percent value; + public String prefix; + + public static Opacity make() { + return new Opacity(); + } + + public Opacity withPercent(Percent val) { + value = val; + return this; + } + + public Opacity withPrefix(String pref) { + this.prefix = pref; + return this; + } + + public void apply(HTMLElementBuilder builder) { + builder.addClass((prefix != "" ? prefix + "-" : "") + "opacity-" + value.get()); + } + } + + public static class Colors { + public Color color; + public boolean emphasis; + public boolean subtle; + public String prefix; + + public static Colors make() { + return new Colors(); + } + + public Colors withColor(Color color) { + this.color = color; + return this; + } + + public Colors setEmphasis() { + emphasis = true; + return this; + } + + public Colors setSubtle() { + subtle = true; + return this; + } + + public Colors withPrefix(String prefix) { + this.prefix = prefix; + return this; + } + + public void apply(HTMLElementBuilder builder) { + builder.addClass(((prefix != "") ? prefix + "-" : "") + color.name().toLowerCase() + (emphasis ? "-emphasis" : "") + (subtle ? "-subtle" : "")); + } + } + + public static class Background { + public Colors color; + public Opacity opacity; + public boolean gradient; + + public static Background make() { + return new Background(); + } + + public Background withColor(Colors color) { + this.color = color.withPrefix("bg"); + return this; + } + + public Background withOpacity(Opacity op) { + this.opacity = op.withPrefix("bg"); + return this; + } + + public Background setGradient() { + gradient = true; + return this; + } + + + public void apply(HTMLElementBuilder builder) { + color.apply(builder); + opacity.apply(builder); + if (gradient) + builder.addClass(".bg-gradient"); + } + } + + public static class Shadow { + public Size size; + + public static Shadow make() { + return new Shadow(); + } + + public Shadow withSize(Size size) { + this.size = size; + return this; + } + + public void apply(HTMLElementBuilder builder) { + builder.addClass("shadow" + size.sizeText()); + } + } + + public static class FocusRing { + public Colors color; + + public static FocusRing make() { + return new FocusRing(); + } + + public FocusRing withColor(Colors color) { + this.color = color.withPrefix("focus-ring"); + return this; + } + + public void apply(HTMLElementBuilder builder) { + builder.addClass("focus-ring"); + color.apply(builder); + } + } + + public static class Link { + public Colors color; + + public static Link make() { + return new Link(); + } + + public Link withColor(Colors color) { + this.color = color.withPrefix("link"); + return this; + } + + public void apply(HTMLElementBuilder builder) { + color.apply(builder); + } + } + + public static class Toast { + public Icons icon; + public HTMLElementBuilder toastHeader; + public HTMLElementBuilder toastBody; + + public Toast() { + toastHeader = new HTMLElementBuilder("div").addClass("toast-header"); + toastHeader.addChild("svg").addClass("bi").addClass(icon.getClassName()).addClass("rounded"); + toastHeader.addChild("strong").addClass("me-auto"); + toastHeader.addChild("small").withText("Text?"); + toastHeader.addChild("button").withAttribute("type", "button").addClass("btn-close").withAttribute("data-bs-dismiss", "toast").withAttribute("aria-label", "Close"); + + toastBody = new HTMLElementBuilder("div").addClass("toast-body"); + } + + public static Toast make() { + return new Toast(); + } + + public Toast withIcon(Icons icon) { + this.icon = icon; + return this; + } + + public void apply(HTMLElementBuilder builder) { + HTMLElementBuilder toast = builder.addChild("div").addClass("toast").withAttribute("role", "alert").withAttribute("aria-live", "assertive").withAttribute("aria-atomic", "true"); + toast.addChild(toastHeader); + toast.addChild(toastBody); + } + } + + public static class Button { + public Colors color; + public boolean outline; + public Size size; + + public static Button make() { + return new Button(); + } + + public Button withColor(Colors color) { + this.color = color; + return this; + } + + public Button setOutline() { + outline = true; + return this; + } + + public Button withSize(Size size) { + this.size = size; + return this; + } + + + public void apply(HTMLElementBuilder builder) { + builder.addClass("btn"); + + if (outline) { + color.withPrefix("btn-outline"); + } else color.withPrefix("btn"); + + color.apply(builder); + if (size != Size.Regular) + builder.addClass("btn" + size.sizeText()); + } + + } + + public static class Disabled { + public static void setDisabled(HTMLElementBuilder builder) { + builder.withAttribute("disabled"); + } + } +} diff --git a/src/main/java/dev/zontreck/ariaslib/html/ClassAttribute.java b/src/main/java/dev/zontreck/ariaslib/html/ClassAttribute.java new file mode 100644 index 0000000..6d17d0e --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/html/ClassAttribute.java @@ -0,0 +1,8 @@ +package dev.zontreck.ariaslib.html; + +// Class attribute class for HTML element classes +class ClassAttribute extends HTMLAttribute { + public ClassAttribute(String value) { + super("class", value); + } +} diff --git a/src/main/java/dev/zontreck/ariaslib/html/DOM.java b/src/main/java/dev/zontreck/ariaslib/html/DOM.java new file mode 100644 index 0000000..aa32b54 --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/html/DOM.java @@ -0,0 +1,55 @@ +package dev.zontreck.ariaslib.html; + +public class DOM { + + + public static HTMLElementBuilder beginBootstrapDOM(String pageTitle) { + HTMLElementBuilder builder = new HTMLElementBuilder("!doctype").withText("html"); + + HTMLElementBuilder html = builder.getOrCreate("html"); + + HTMLElementBuilder head = html.getOrCreate("head"); + + head.addChild("meta").withAttribute("charset", "utf-8"); + + head.addChild("meta").withAttribute("name", "viewport").withAttribute("content", "width=device-width, initial-scale=1"); + + head.getOrCreate("link").withAttribute("href", "https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css").withAttribute("integrity", "sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM").withAttribute("crossorigin", "anonymous").withAttribute("rel", "stylesheet"); + + head.addClass("link").withAttribute("rel", "stylesheet").withAttribute("href", "https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css"); + + head.getOrCreate("title").withText(pageTitle); + + HTMLElementBuilder body = html.getOrCreate("body"); + body.addChild("script").withAttribute("src", "https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js").withAttribute("integrity", "sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz").withAttribute("crossorigin", "anonymous").withText(" "); + + body.addChild("script").withAttribute("src", "https://code.jquery.com/jquery-3.7.0.min.js").withAttribute("crossorigin", "anonymous").withText(" "); + + body.addChild("style").withText("\n" + + " .command-popover{\n" + + " --bs-popover-header-bg: var(--bs-info);\n" + + " --bs-popover-header-color: var(--bs-dark);\n" + + " --bs-popover-bg: var(--bs-dark);\n" + + " --bs-popover-body-color: var(--bs-light);\n" + + " }\n"); + + + return builder; + + } + + public static void addPopOverScan(HTMLElementBuilder builder) { + builder.getChildByTagName("html").getChildByTagName("body").addChild("script").withText("" + + "function scanPopOver()" + + "{" + + "var popoverTriggerList = document.querySelectorAll('[data-bs-toggle=\"popover\"]');\n" + + "var popoverList = [...popoverTriggerList].map(popoverTriggerEl => new bootstrap.Popover(popoverTriggerEl));" + + "" + + "}"); + } + + + public static String closeHTML() { + return ""; + } +} diff --git a/src/main/java/dev/zontreck/ariaslib/html/HTMLAttribute.java b/src/main/java/dev/zontreck/ariaslib/html/HTMLAttribute.java new file mode 100644 index 0000000..73c1eca --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/html/HTMLAttribute.java @@ -0,0 +1,24 @@ +package dev.zontreck.ariaslib.html; + +// Attribute class for HTML element attributes +class HTMLAttribute { + private String name; + private String value; + + public HTMLAttribute(String name, String value) { + this.name = name; + this.value = value; + } + + public HTMLAttribute(String name) { + this(name, null); + } + + public String getName() { + return name; + } + + public String getValue() { + return value; + } +} \ No newline at end of file diff --git a/src/main/java/dev/zontreck/ariaslib/html/HTMLElement.java b/src/main/java/dev/zontreck/ariaslib/html/HTMLElement.java new file mode 100644 index 0000000..2950483 --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/html/HTMLElement.java @@ -0,0 +1,72 @@ +package dev.zontreck.ariaslib.html; + +import java.util.List; + + +// HTML element class supporting tag attributes +public class HTMLElement { + private String tagName; + private String text; + private List attributes; + private List children; + private boolean isEmptyElement; + + public HTMLElement ( String tagName , String text , List attributes , List children , boolean isEmptyElement ) { + this.tagName = tagName; + this.text = text; + this.attributes = attributes; + this.children = children; + this.isEmptyElement = isEmptyElement; + } + + public String getTagName ( ) { + return tagName; + } + + public String generateHTML ( ) { + StringBuilder builder = new StringBuilder ( ); + + if ( "!doctype".equalsIgnoreCase ( tagName ) ) { + builder.append ( "<" ).append ( tagName ).append ( " " ).append ( text ).append ( ">\n" ); + for ( HTMLElement child : children ) { + builder.append ( child.generateHTML ( ) ); + } + return builder.toString ( ); + } + + builder.append ( "<" ).append ( tagName ); + + for ( HTMLAttribute attribute : attributes ) { + builder.append ( " " ) + .append ( attribute.getName ( ) ); + + String value = attribute.getValue ( ); + if ( value != null ) { + builder.append ( "=\"" ).append ( value ).append ( "\"" ); + } + } + + /* + if ( isEmptyElement ) { + builder.append ( " />\n" ); + return builder.toString ( ); + }*/ + + builder.append ( ">" ); + + if ( text != null ) { + builder.append ( text ); + } + + if ( ! isEmptyElement ) { + for ( HTMLElement child : children ) { + builder.append ( child.generateHTML ( ) ); + } + } + + + builder.append ( "\n" ); + + return builder.toString ( ); + } +} \ No newline at end of file diff --git a/src/main/java/dev/zontreck/ariaslib/html/HTMLElementBuilder.java b/src/main/java/dev/zontreck/ariaslib/html/HTMLElementBuilder.java new file mode 100644 index 0000000..461adba --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/html/HTMLElementBuilder.java @@ -0,0 +1,118 @@ +package dev.zontreck.ariaslib.html; + +import java.util.ArrayList; +import java.util.List; + +// Builder class for building HTML elements +public class HTMLElementBuilder { + private String tagName; + private String text; + private List attributes; + private List childElementBuilders; + + public HTMLElementBuilder ( String tagName ) { + this.tagName = tagName; + this.attributes = new ArrayList<> ( ); + this.childElementBuilders = new ArrayList<> ( ); + } + + public HTMLElementBuilder withText ( String text ) { + this.text = text; + return this; + } + + public HTMLElementBuilder withAttribute ( String name , String value ) { + HTMLAttribute attribute = new HTMLAttribute ( name , value ); + this.attributes.add ( attribute ); + return this; + } + + public HTMLElementBuilder withAttribute ( String name ) { + HTMLAttribute attribute = new HTMLAttribute ( name ); + this.attributes.add ( attribute ); + return this; + } + + public HTMLElementBuilder addClass ( String className ) { + ClassAttribute classAttribute = getClassAttribute ( ); + if ( classAttribute == null ) { + classAttribute = new ClassAttribute ( className ); + this.attributes.add ( classAttribute ); + } + else { + String existingValue = classAttribute.getValue ( ); + classAttribute = new ClassAttribute ( existingValue + " " + className ); + this.attributes.removeIf ( attr -> attr.getName ( ).equalsIgnoreCase ( "class" ) ); + this.attributes.add ( classAttribute ); + } + return this; + } + + private ClassAttribute getClassAttribute ( ) { + for ( HTMLAttribute attribute : attributes ) { + if ( attribute instanceof ClassAttribute ) { + return ( ClassAttribute ) attribute; + } + } + return null; + } + + public HTMLElementBuilder addChild ( HTMLElementBuilder childBuilder ) { + childElementBuilders.add ( childBuilder ); + return this; + } + + public HTMLElementBuilder addChild ( String tagName ) { + HTMLElementBuilder childBuilder = new HTMLElementBuilder ( tagName ); + childElementBuilders.add ( childBuilder ); + return childBuilder; + } + + public HTMLElementBuilder getOrCreate ( String tagName ) { + HTMLElementBuilder childBuilder = getChildByTagName ( tagName ); + if ( childBuilder == null ) { + childBuilder = addChild ( tagName ); + } + return childBuilder; + } + + public HTMLElementBuilder getChildByTagName ( String tagName ) { + return getChildByTagName ( tagName , 0 ); + } + + public HTMLElementBuilder getChildByTagName ( String tagName , int index ) { + List matchingChildBuilders = new ArrayList<> ( ); + + for ( HTMLElementBuilder builder : childElementBuilders ) { + if ( builder.tagName.equalsIgnoreCase ( tagName ) ) { + matchingChildBuilders.add ( builder ); + } + } + + if ( matchingChildBuilders.size ( ) > index ) { + return matchingChildBuilders.get ( index ); + } + + return null; + } + + private boolean hasTextOrChildren ( ) { + return text != null || ! childElementBuilders.isEmpty ( ); + } + + public HTMLElement build ( ) { + List children = buildChildren ( ); + boolean isEmptyElement = ! hasTextOrChildren ( ); + return new HTMLElement ( tagName , text , attributes , children , isEmptyElement ); + } + + private List buildChildren ( ) { + List children = new ArrayList<> ( ); + + for ( HTMLElementBuilder builder : childElementBuilders ) { + children.add ( builder.build ( ) ); + } + + return children; + } +} \ No newline at end of file diff --git a/src/main/java/dev/zontreck/ariaslib/html/bootstrap/Color.java b/src/main/java/dev/zontreck/ariaslib/html/bootstrap/Color.java new file mode 100644 index 0000000..2949cda --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/html/bootstrap/Color.java @@ -0,0 +1,46 @@ +package dev.zontreck.ariaslib.html.bootstrap; + +public enum Color +{ + /** + * Dark Blue + */ + Primary, + + /** + * Dark Gray + */ + Secondary, + + /** + * Dark Green + */ + Success, + + /** + * Dark Red + */ + Danger, + + /** + * Yellow + */ + Warning, + + /** + * Light Blue + */ + Info, + + /** + * Black + */ + Dark, + + /** + * Semi-gray + */ + Light, + Black, + White +} diff --git a/src/main/java/dev/zontreck/ariaslib/html/bootstrap/Icons.java b/src/main/java/dev/zontreck/ariaslib/html/bootstrap/Icons.java new file mode 100644 index 0000000..174c4f2 --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/html/bootstrap/Icons.java @@ -0,0 +1,1969 @@ +package dev.zontreck.ariaslib.html.bootstrap; + +/** + * Bootstrap Icons Enumerable + */ +@SuppressWarnings ( "unused" ) +public enum Icons { + + Icon_123, + Icon_alarm_fill, + Icon_alarm, + Icon_align_bottom, + Icon_align_center, + Icon_align_end, + Icon_align_middle, + Icon_align_start, //before { content: "\f107"; } + Icon_align_top, //before { content: "\f108"; } + Icon_alt, //before { content: "\f109"; } + Icon_app_indicator, //before { content: "\f10a"; } + Icon_app, //before { content: "\f10b"; } + Icon_archive_fill, //before { content: "\f10c"; } + Icon_archive, //before { content: "\f10d"; } + Icon_arrow_90deg_down, //before { content: "\f10e"; } + Icon_arrow_90deg_left, //before { content: "\f10f"; } + Icon_arrow_90deg_right, //before { content: "\f110"; } + Icon_arrow_90deg_up, //before { content: "\f111"; } + Icon_arrow_bar_down, //before { content: "\f112"; } + Icon_arrow_bar_left, //before { content: "\f113"; } + Icon_arrow_bar_right, //before { content: "\f114"; } + Icon_arrow_bar_up, //before { content: "\f115"; } + Icon_arrow_clockwise, //before { content: "\f116"; } + Icon_arrow_counterclockwise, //before { content: "\f117"; } + Icon_arrow_down_circle_fill, //before { content: "\f118"; } + Icon_arrow_down_circle, //before { content: "\f119"; } + Icon_arrow_down_left_circle_fill, //before { content: "\f11a"; } + Icon_arrow_down_left_circle, //before { content: "\f11b"; } + Icon_arrow_down_left_square_fill, //before { content: "\f11c"; } + Icon_arrow_down_left_square, //before { content: "\f11d"; } + Icon_arrow_down_left, //before { content: "\f11e"; } + Icon_arrow_down_right_circle_fill, //before { content: "\f11f"; } + Icon_arrow_down_right_circle, //before { content: "\f120"; } + Icon_arrow_down_right_square_fill, //before { content: "\f121"; } + Icon_arrow_down_right_square, //before { content: "\f122"; } + Icon_arrow_down_right, //before { content: "\f123"; } + Icon_arrow_down_short, //before { content: "\f124"; } + Icon_arrow_down_square_fill, //before { content: "\f125"; } + Icon_arrow_down_square, //before { content: "\f126"; } + Icon_arrow_down_up, //before { content: "\f127"; } + Icon_arrow_down, //before { content: "\f128"; } + Icon_arrow_left_circle_fill, //before { content: "\f129"; } + Icon_arrow_left_circle, //before { content: "\f12a"; } + Icon_arrow_left_right, //before { content: "\f12b"; } + Icon_arrow_left_short, //before { content: "\f12c"; } + Icon_arrow_left_square_fill, //before { content: "\f12d"; } + Icon_arrow_left_square, //before { content: "\f12e"; } + Icon_arrow_left, //before { content: "\f12f"; } + Icon_arrow_repeat, //before { content: "\f130"; } + Icon_arrow_return_left, //before { content: "\f131"; } + Icon_arrow_return_right, //before { content: "\f132"; } + Icon_arrow_right_circle_fill, //before { content: "\f133"; } + Icon_arrow_right_circle, //before { content: "\f134"; } + Icon_arrow_right_short, //before { content: "\f135"; } + Icon_arrow_right_square_fill, //before { content: "\f136"; } + Icon_arrow_right_square, //before { content: "\f137"; } + Icon_arrow_right, //before { content: "\f138"; } + Icon_arrow_up_circle_fill, //before { content: "\f139"; } + Icon_arrow_up_circle, //before { content: "\f13a"; } + Icon_arrow_up_left_circle_fill, //before { content: "\f13b"; } + Icon_arrow_up_left_circle, //before { content: "\f13c"; } + Icon_arrow_up_left_square_fill, //before { content: "\f13d"; } + Icon_arrow_up_left_square, //before { content: "\f13e"; } + Icon_arrow_up_left, //before { content: "\f13f"; } + Icon_arrow_up_right_circle_fill, //before { content: "\f140"; } + Icon_arrow_up_right_circle, //before { content: "\f141"; } + Icon_arrow_up_right_square_fill, //before { content: "\f142"; } + Icon_arrow_up_right_square, //before { content: "\f143"; } + Icon_arrow_up_right, //before { content: "\f144"; } + Icon_arrow_up_short, //before { content: "\f145"; } + Icon_arrow_up_square_fill, //before { content: "\f146"; } + Icon_arrow_up_square, //before { content: "\f147"; } + Icon_arrow_up, //before { content: "\f148"; } + Icon_arrows_angle_contract, //before { content: "\f149"; } + Icon_arrows_angle_expand, //before { content: "\f14a"; } + Icon_arrows_collapse, //before { content: "\f14b"; } + Icon_arrows_expand, //before { content: "\f14c"; } + Icon_arrows_fullscreen, //before { content: "\f14d"; } + Icon_arrows_move, //before { content: "\f14e"; } + Icon_aspect_ratio_fill, //before { content: "\f14f"; } + Icon_aspect_ratio, //before { content: "\f150"; } + Icon_asterisk, //before { content: "\f151"; } + Icon_at, //before { content: "\f152"; } + Icon_award_fill, //before { content: "\f153"; } + Icon_award, //before { content: "\f154"; } + Icon_back, //before { content: "\f155"; } + Icon_backspace_fill, //before { content: "\f156"; } + Icon_backspace_reverse_fill, //before { content: "\f157"; } + Icon_backspace_reverse, //before { content: "\f158"; } + Icon_backspace, //before { content: "\f159"; } + Icon_badge_3d_fill, //before { content: "\f15a"; } + Icon_badge_3d, //before { content: "\f15b"; } + Icon_badge_4k_fill, //before { content: "\f15c"; } + Icon_badge_4k, //before { content: "\f15d"; } + Icon_badge_8k_fill, //before { content: "\f15e"; } + Icon_badge_8k, //before { content: "\f15f"; } + Icon_badge_ad_fill, //before { content: "\f160"; } + Icon_badge_ad, //before { content: "\f161"; } + Icon_badge_ar_fill, //before { content: "\f162"; } + Icon_badge_ar, //before { content: "\f163"; } + Icon_badge_cc_fill, //before { content: "\f164"; } + Icon_badge_cc, //before { content: "\f165"; } + Icon_badge_hd_fill, //before { content: "\f166"; } + Icon_badge_hd, //before { content: "\f167"; } + Icon_badge_tm_fill, //before { content: "\f168"; } + Icon_badge_tm, //before { content: "\f169"; } + Icon_badge_vo_fill, //before { content: "\f16a"; } + Icon_badge_vo, //before { content: "\f16b"; } + Icon_badge_vr_fill, //before { content: "\f16c"; } + Icon_badge_vr, //before { content: "\f16d"; } + Icon_badge_wc_fill, //before { content: "\f16e"; } + Icon_badge_wc, //before { content: "\f16f"; } + Icon_bag_check_fill, //before { content: "\f170"; } + Icon_bag_check, //before { content: "\f171"; } + Icon_bag_dash_fill, //before { content: "\f172"; } + Icon_bag_dash, //before { content: "\f173"; } + Icon_bag_fill, //before { content: "\f174"; } + Icon_bag_plus_fill, //before { content: "\f175"; } + Icon_bag_plus, //before { content: "\f176"; } + Icon_bag_x_fill, //before { content: "\f177"; } + Icon_bag_x, //before { content: "\f178"; } + Icon_bag, //before { content: "\f179"; } + Icon_bar_chart_fill, //before { content: "\f17a"; } + Icon_bar_chart_line_fill, //before { content: "\f17b"; } + Icon_bar_chart_line, //before { content: "\f17c"; } + Icon_bar_chart_steps, //before { content: "\f17d"; } + Icon_bar_chart, //before { content: "\f17e"; } + Icon_basket_fill, //before { content: "\f17f"; } + Icon_basket, //before { content: "\f180"; } + Icon_basket2_fill, //before { content: "\f181"; } + Icon_basket2, //before { content: "\f182"; } + Icon_basket3_fill, //before { content: "\f183"; } + Icon_basket3, //before { content: "\f184"; } + Icon_battery_charging, //before { content: "\f185"; } + Icon_battery_full, //before { content: "\f186"; } + Icon_battery_half, //before { content: "\f187"; } + Icon_battery, //before { content: "\f188"; } + Icon_bell_fill, //before { content: "\f189"; } + Icon_bell, //before { content: "\f18a"; } + Icon_bezier, //before { content: "\f18b"; } + Icon_bezier2, //before { content: "\f18c"; } + Icon_bicycle, //before { content: "\f18d"; } + Icon_binoculars_fill, //before { content: "\f18e"; } + Icon_binoculars, //before { content: "\f18f"; } + Icon_blockquote_left, //before { content: "\f190"; } + Icon_blockquote_right, //before { content: "\f191"; } + Icon_book_fill, //before { content: "\f192"; } + Icon_book_half, //before { content: "\f193"; } + Icon_book, //before { content: "\f194"; } + Icon_bookmark_check_fill, //before { content: "\f195"; } + Icon_bookmark_check, //before { content: "\f196"; } + Icon_bookmark_dash_fill, //before { content: "\f197"; } + Icon_bookmark_dash, //before { content: "\f198"; } + Icon_bookmark_fill, //before { content: "\f199"; } + Icon_bookmark_heart_fill, //before { content: "\f19a"; } + Icon_bookmark_heart, //before { content: "\f19b"; } + Icon_bookmark_plus_fill, //before { content: "\f19c"; } + Icon_bookmark_plus, //before { content: "\f19d"; } + Icon_bookmark_star_fill, //before { content: "\f19e"; } + Icon_bookmark_star, //before { content: "\f19f"; } + Icon_bookmark_x_fill, //before { content: "\f1a0"; } + Icon_bookmark_x, //before { content: "\f1a1"; } + Icon_bookmark, //before { content: "\f1a2"; } + Icon_bookmarks_fill, //before { content: "\f1a3"; } + Icon_bookmarks, //before { content: "\f1a4"; } + Icon_bookshelf, //before { content: "\f1a5"; } + Icon_bootstrap_fill, //before { content: "\f1a6"; } + Icon_bootstrap_reboot, //before { content: "\f1a7"; } + Icon_bootstrap, //before { content: "\f1a8"; } + Icon_border_all, //before { content: "\f1a9"; } + Icon_border_bottom, //before { content: "\f1aa"; } + Icon_border_center, //before { content: "\f1ab"; } + Icon_border_inner, //before { content: "\f1ac"; } + Icon_border_left, //before { content: "\f1ad"; } + Icon_border_middle, //before { content: "\f1ae"; } + Icon_border_outer, //before { content: "\f1af"; } + Icon_border_right, //before { content: "\f1b0"; } + Icon_border_style, //before { content: "\f1b1"; } + Icon_border_top, //before { content: "\f1b2"; } + Icon_border_width, //before { content: "\f1b3"; } + Icon_border, //before { content: "\f1b4"; } + Icon_bounding_box_circles, //before { content: "\f1b5"; } + Icon_bounding_box, //before { content: "\f1b6"; } + Icon_box_arrow_down_left, //before { content: "\f1b7"; } + Icon_box_arrow_down_right, //before { content: "\f1b8"; } + Icon_box_arrow_down, //before { content: "\f1b9"; } + Icon_box_arrow_in_down_left, //before { content: "\f1ba"; } + Icon_box_arrow_in_down_right, //before { content: "\f1bb"; } + Icon_box_arrow_in_down, //before { content: "\f1bc"; } + Icon_box_arrow_in_left, //before { content: "\f1bd"; } + Icon_box_arrow_in_right, //before { content: "\f1be"; } + Icon_box_arrow_in_up_left, //before { content: "\f1bf"; } + Icon_box_arrow_in_up_right, //before { content: "\f1c0"; } + Icon_box_arrow_in_up, //before { content: "\f1c1"; } + Icon_box_arrow_left, //before { content: "\f1c2"; } + Icon_box_arrow_right, //before { content: "\f1c3"; } + Icon_box_arrow_up_left, //before { content: "\f1c4"; } + Icon_box_arrow_up_right, //before { content: "\f1c5"; } + Icon_box_arrow_up, //before { content: "\f1c6"; } + Icon_box_seam, //before { content: "\f1c7"; } + Icon_box, //before { content: "\f1c8"; } + Icon_braces, //before { content: "\f1c9"; } + Icon_bricks, //before { content: "\f1ca"; } + Icon_briefcase_fill, //before { content: "\f1cb"; } + Icon_briefcase, //before { content: "\f1cc"; } + Icon_brightness_alt_high_fill, //before { content: "\f1cd"; } + Icon_brightness_alt_high, //before { content: "\f1ce"; } + Icon_brightness_alt_low_fill, //before { content: "\f1cf"; } + Icon_brightness_alt_low, //before { content: "\f1d0"; } + Icon_brightness_high_fill, //before { content: "\f1d1"; } + Icon_brightness_high, //before { content: "\f1d2"; } + Icon_brightness_low_fill, //before { content: "\f1d3"; } + Icon_brightness_low, //before { content: "\f1d4"; } + Icon_broadcast_pin, //before { content: "\f1d5"; } + Icon_broadcast, //before { content: "\f1d6"; } + Icon_brush_fill, //before { content: "\f1d7"; } + Icon_brush, //before { content: "\f1d8"; } + Icon_bucket_fill, //before { content: "\f1d9"; } + Icon_bucket, //before { content: "\f1da"; } + Icon_bug_fill, //before { content: "\f1db"; } + Icon_bug, //before { content: "\f1dc"; } + Icon_building, //before { content: "\f1dd"; } + Icon_bullseye, //before { content: "\f1de"; } + Icon_calculator_fill, //before { content: "\f1df"; } + Icon_calculator, //before { content: "\f1e0"; } + Icon_calendar_check_fill, //before { content: "\f1e1"; } + Icon_calendar_check, //before { content: "\f1e2"; } + Icon_calendar_date_fill, //before { content: "\f1e3"; } + Icon_calendar_date, //before { content: "\f1e4"; } + Icon_calendar_day_fill, //before { content: "\f1e5"; } + Icon_calendar_day, //before { content: "\f1e6"; } + Icon_calendar_event_fill, //before { content: "\f1e7"; } + Icon_calendar_event, //before { content: "\f1e8"; } + Icon_calendar_fill, //before { content: "\f1e9"; } + Icon_calendar_minus_fill, //before { content: "\f1ea"; } + Icon_calendar_minus, //before { content: "\f1eb"; } + Icon_calendar_month_fill, //before { content: "\f1ec"; } + Icon_calendar_month, //before { content: "\f1ed"; } + Icon_calendar_plus_fill, //before { content: "\f1ee"; } + Icon_calendar_plus, //before { content: "\f1ef"; } + Icon_calendar_range_fill, //before { content: "\f1f0"; } + Icon_calendar_range, //before { content: "\f1f1"; } + Icon_calendar_week_fill, //before { content: "\f1f2"; } + Icon_calendar_week, //before { content: "\f1f3"; } + Icon_calendar_x_fill, //before { content: "\f1f4"; } + Icon_calendar_x, //before { content: "\f1f5"; } + Icon_calendar, //before { content: "\f1f6"; } + Icon_calendar2_check_fill, //before { content: "\f1f7"; } + Icon_calendar2_check, //before { content: "\f1f8"; } + Icon_calendar2_date_fill, //before { content: "\f1f9"; } + Icon_calendar2_date, //before { content: "\f1fa"; } + Icon_calendar2_day_fill, //before { content: "\f1fb"; } + Icon_calendar2_day, //before { content: "\f1fc"; } + Icon_calendar2_event_fill, //before { content: "\f1fd"; } + Icon_calendar2_event, //before { content: "\f1fe"; } + Icon_calendar2_fill, //before { content: "\f1ff"; } + Icon_calendar2_minus_fill, //before { content: "\f200"; } + Icon_calendar2_minus, //before { content: "\f201"; } + Icon_calendar2_month_fill, //before { content: "\f202"; } + Icon_calendar2_month, //before { content: "\f203"; } + Icon_calendar2_plus_fill, //before { content: "\f204"; } + Icon_calendar2_plus, //before { content: "\f205"; } + Icon_calendar2_range_fill, //before { content: "\f206"; } + Icon_calendar2_range, //before { content: "\f207"; } + Icon_calendar2_week_fill, //before { content: "\f208"; } + Icon_calendar2_week, //before { content: "\f209"; } + Icon_calendar2_x_fill, //before { content: "\f20a"; } + Icon_calendar2_x, //before { content: "\f20b"; } + Icon_calendar2, //before { content: "\f20c"; } + Icon_calendar3_event_fill, //before { content: "\f20d"; } + Icon_calendar3_event, //before { content: "\f20e"; } + Icon_calendar3_fill, //before { content: "\f20f"; } + Icon_calendar3_range_fill, //before { content: "\f210"; } + Icon_calendar3_range, //before { content: "\f211"; } + Icon_calendar3_week_fill, //before { content: "\f212"; } + Icon_calendar3_week, //before { content: "\f213"; } + Icon_calendar3, //before { content: "\f214"; } + Icon_calendar4_event, //before { content: "\f215"; } + Icon_calendar4_range, //before { content: "\f216"; } + Icon_calendar4_week, //before { content: "\f217"; } + Icon_calendar4, //before { content: "\f218"; } + Icon_camera_fill, //before { content: "\f219"; } + Icon_camera_reels_fill, //before { content: "\f21a"; } + Icon_camera_reels, //before { content: "\f21b"; } + Icon_camera_video_fill, //before { content: "\f21c"; } + Icon_camera_video_off_fill, //before { content: "\f21d"; } + Icon_camera_video_off, //before { content: "\f21e"; } + Icon_camera_video, //before { content: "\f21f"; } + Icon_camera, //before { content: "\f220"; } + Icon_camera2, //before { content: "\f221"; } + Icon_capslock_fill, //before { content: "\f222"; } + Icon_capslock, //before { content: "\f223"; } + Icon_card_checklist, //before { content: "\f224"; } + Icon_card_heading, //before { content: "\f225"; } + Icon_card_image, //before { content: "\f226"; } + Icon_card_list, //before { content: "\f227"; } + Icon_card_text, //before { content: "\f228"; } + Icon_caret_down_fill, //before { content: "\f229"; } + Icon_caret_down_square_fill, //before { content: "\f22a"; } + Icon_caret_down_square, //before { content: "\f22b"; } + Icon_caret_down, //before { content: "\f22c"; } + Icon_caret_left_fill, //before { content: "\f22d"; } + Icon_caret_left_square_fill, //before { content: "\f22e"; } + Icon_caret_left_square, //before { content: "\f22f"; } + Icon_caret_left, //before { content: "\f230"; } + Icon_caret_right_fill, //before { content: "\f231"; } + Icon_caret_right_square_fill, //before { content: "\f232"; } + Icon_caret_right_square, //before { content: "\f233"; } + Icon_caret_right, //before { content: "\f234"; } + Icon_caret_up_fill, //before { content: "\f235"; } + Icon_caret_up_square_fill, //before { content: "\f236"; } + Icon_caret_up_square, //before { content: "\f237"; } + Icon_caret_up, //before { content: "\f238"; } + Icon_cart_check_fill, //before { content: "\f239"; } + Icon_cart_check, //before { content: "\f23a"; } + Icon_cart_dash_fill, //before { content: "\f23b"; } + Icon_cart_dash, //before { content: "\f23c"; } + Icon_cart_fill, //before { content: "\f23d"; } + Icon_cart_plus_fill, //before { content: "\f23e"; } + Icon_cart_plus, //before { content: "\f23f"; } + Icon_cart_x_fill, //before { content: "\f240"; } + Icon_cart_x, //before { content: "\f241"; } + Icon_cart, //before { content: "\f242"; } + Icon_cart2, //before { content: "\f243"; } + Icon_cart3, //before { content: "\f244"; } + Icon_cart4, //before { content: "\f245"; } + Icon_cash_stack, //before { content: "\f246"; } + Icon_cash, //before { content: "\f247"; } + Icon_cast, //before { content: "\f248"; } + Icon_chat_dots_fill, //before { content: "\f249"; } + Icon_chat_dots, //before { content: "\f24a"; } + Icon_chat_fill, //before { content: "\f24b"; } + Icon_chat_left_dots_fill, //before { content: "\f24c"; } + Icon_chat_left_dots, //before { content: "\f24d"; } + Icon_chat_left_fill, //before { content: "\f24e"; } + Icon_chat_left_quote_fill, //before { content: "\f24f"; } + Icon_chat_left_quote, //before { content: "\f250"; } + Icon_chat_left_text_fill, //before { content: "\f251"; } + Icon_chat_left_text, //before { content: "\f252"; } + Icon_chat_left, //before { content: "\f253"; } + Icon_chat_quote_fill, //before { content: "\f254"; } + Icon_chat_quote, //before { content: "\f255"; } + Icon_chat_right_dots_fill, //before { content: "\f256"; } + Icon_chat_right_dots, //before { content: "\f257"; } + Icon_chat_right_fill, //before { content: "\f258"; } + Icon_chat_right_quote_fill, //before { content: "\f259"; } + Icon_chat_right_quote, //before { content: "\f25a"; } + Icon_chat_right_text_fill, //before { content: "\f25b"; } + Icon_chat_right_text, //before { content: "\f25c"; } + Icon_chat_right, //before { content: "\f25d"; } + Icon_chat_square_dots_fill, //before { content: "\f25e"; } + Icon_chat_square_dots, //before { content: "\f25f"; } + Icon_chat_square_fill, //before { content: "\f260"; } + Icon_chat_square_quote_fill, //before { content: "\f261"; } + Icon_chat_square_quote, //before { content: "\f262"; } + Icon_chat_square_text_fill, //before { content: "\f263"; } + Icon_chat_square_text, //before { content: "\f264"; } + Icon_chat_square, //before { content: "\f265"; } + Icon_chat_text_fill, //before { content: "\f266"; } + Icon_chat_text, //before { content: "\f267"; } + Icon_chat, //before { content: "\f268"; } + Icon_check_all, //before { content: "\f269"; } + Icon_check_circle_fill, //before { content: "\f26a"; } + Icon_check_circle, //before { content: "\f26b"; } + Icon_check_square_fill, //before { content: "\f26c"; } + Icon_check_square, //before { content: "\f26d"; } + Icon_check, //before { content: "\f26e"; } + Icon_check2_all, //before { content: "\f26f"; } + Icon_check2_circle, //before { content: "\f270"; } + Icon_check2_square, //before { content: "\f271"; } + Icon_check2, //before { content: "\f272"; } + Icon_chevron_bar_contract, //before { content: "\f273"; } + Icon_chevron_bar_down, //before { content: "\f274"; } + Icon_chevron_bar_expand, //before { content: "\f275"; } + Icon_chevron_bar_left, //before { content: "\f276"; } + Icon_chevron_bar_right, //before { content: "\f277"; } + Icon_chevron_bar_up, //before { content: "\f278"; } + Icon_chevron_compact_down, //before { content: "\f279"; } + Icon_chevron_compact_left, //before { content: "\f27a"; } + Icon_chevron_compact_right, //before { content: "\f27b"; } + Icon_chevron_compact_up, //before { content: "\f27c"; } + Icon_chevron_contract, //before { content: "\f27d"; } + Icon_chevron_double_down, //before { content: "\f27e"; } + Icon_chevron_double_left, //before { content: "\f27f"; } + Icon_chevron_double_right, //before { content: "\f280"; } + Icon_chevron_double_up, //before { content: "\f281"; } + Icon_chevron_down, //before { content: "\f282"; } + Icon_chevron_expand, //before { content: "\f283"; } + Icon_chevron_left, //before { content: "\f284"; } + Icon_chevron_right, //before { content: "\f285"; } + Icon_chevron_up, //before { content: "\f286"; } + Icon_circle_fill, //before { content: "\f287"; } + Icon_circle_half, //before { content: "\f288"; } + Icon_circle_square, //before { content: "\f289"; } + Icon_circle, //before { content: "\f28a"; } + Icon_clipboard_check, //before { content: "\f28b"; } + Icon_clipboard_data, //before { content: "\f28c"; } + Icon_clipboard_minus, //before { content: "\f28d"; } + Icon_clipboard_plus, //before { content: "\f28e"; } + Icon_clipboard_x, //before { content: "\f28f"; } + Icon_clipboard, //before { content: "\f290"; } + Icon_clock_fill, //before { content: "\f291"; } + Icon_clock_history, //before { content: "\f292"; } + Icon_clock, //before { content: "\f293"; } + Icon_cloud_arrow_down_fill, //before { content: "\f294"; } + Icon_cloud_arrow_down, //before { content: "\f295"; } + Icon_cloud_arrow_up_fill, //before { content: "\f296"; } + Icon_cloud_arrow_up, //before { content: "\f297"; } + Icon_cloud_check_fill, //before { content: "\f298"; } + Icon_cloud_check, //before { content: "\f299"; } + Icon_cloud_download_fill, //before { content: "\f29a"; } + Icon_cloud_download, //before { content: "\f29b"; } + Icon_cloud_drizzle_fill, //before { content: "\f29c"; } + Icon_cloud_drizzle, //before { content: "\f29d"; } + Icon_cloud_fill, //before { content: "\f29e"; } + Icon_cloud_fog_fill, //before { content: "\f29f"; } + Icon_cloud_fog, //before { content: "\f2a0"; } + Icon_cloud_fog2_fill, //before { content: "\f2a1"; } + Icon_cloud_fog2, //before { content: "\f2a2"; } + Icon_cloud_hail_fill, //before { content: "\f2a3"; } + Icon_cloud_hail, //before { content: "\f2a4"; } + Icon_cloud_haze_fill, //before { content: "\f2a6"; } + Icon_cloud_haze, //before { content: "\f2a7"; } + Icon_cloud_haze2_fill, //before { content: "\f2a8"; } + Icon_cloud_lightning_fill, //before { content: "\f2a9"; } + Icon_cloud_lightning_rain_fill, //before { content: "\f2aa"; } + Icon_cloud_lightning_rain, //before { content: "\f2ab"; } + Icon_cloud_lightning, //before { content: "\f2ac"; } + Icon_cloud_minus_fill, //before { content: "\f2ad"; } + Icon_cloud_minus, //before { content: "\f2ae"; } + Icon_cloud_moon_fill, //before { content: "\f2af"; } + Icon_cloud_moon, //before { content: "\f2b0"; } + Icon_cloud_plus_fill, //before { content: "\f2b1"; } + Icon_cloud_plus, //before { content: "\f2b2"; } + Icon_cloud_rain_fill, //before { content: "\f2b3"; } + Icon_cloud_rain_heavy_fill, //before { content: "\f2b4"; } + Icon_cloud_rain_heavy, //before { content: "\f2b5"; } + Icon_cloud_rain, //before { content: "\f2b6"; } + Icon_cloud_slash_fill, //before { content: "\f2b7"; } + Icon_cloud_slash, //before { content: "\f2b8"; } + Icon_cloud_sleet_fill, //before { content: "\f2b9"; } + Icon_cloud_sleet, //before { content: "\f2ba"; } + Icon_cloud_snow_fill, //before { content: "\f2bb"; } + Icon_cloud_snow, //before { content: "\f2bc"; } + Icon_cloud_sun_fill, //before { content: "\f2bd"; } + Icon_cloud_sun, //before { content: "\f2be"; } + Icon_cloud_upload_fill, //before { content: "\f2bf"; } + Icon_cloud_upload, //before { content: "\f2c0"; } + Icon_cloud, //before { content: "\f2c1"; } + Icon_clouds_fill, //before { content: "\f2c2"; } + Icon_clouds, //before { content: "\f2c3"; } + Icon_cloudy_fill, //before { content: "\f2c4"; } + Icon_cloudy, //before { content: "\f2c5"; } + Icon_code_slash, //before { content: "\f2c6"; } + Icon_code_square, //before { content: "\f2c7"; } + Icon_code, //before { content: "\f2c8"; } + Icon_collection_fill, //before { content: "\f2c9"; } + Icon_collection_play_fill, //before { content: "\f2ca"; } + Icon_collection_play, //before { content: "\f2cb"; } + Icon_collection, //before { content: "\f2cc"; } + Icon_columns_gap, //before { content: "\f2cd"; } + Icon_columns, //before { content: "\f2ce"; } + Icon_command, //before { content: "\f2cf"; } + Icon_compass_fill, //before { content: "\f2d0"; } + Icon_compass, //before { content: "\f2d1"; } + Icon_cone_striped, //before { content: "\f2d2"; } + Icon_cone, //before { content: "\f2d3"; } + Icon_controller, //before { content: "\f2d4"; } + Icon_cpu_fill, //before { content: "\f2d5"; } + Icon_cpu, //before { content: "\f2d6"; } + Icon_credit_card_2_back_fill, //before { content: "\f2d7"; } + Icon_credit_card_2_back, //before { content: "\f2d8"; } + Icon_credit_card_2_front_fill, //before { content: "\f2d9"; } + Icon_credit_card_2_front, //before { content: "\f2da"; } + Icon_credit_card_fill, //before { content: "\f2db"; } + Icon_credit_card, //before { content: "\f2dc"; } + Icon_crop, //before { content: "\f2dd"; } + Icon_cup_fill, //before { content: "\f2de"; } + Icon_cup_straw, //before { content: "\f2df"; } + Icon_cup, //before { content: "\f2e0"; } + Icon_cursor_fill, //before { content: "\f2e1"; } + Icon_cursor_text, //before { content: "\f2e2"; } + Icon_cursor, //before { content: "\f2e3"; } + Icon_dash_circle_dotted, //before { content: "\f2e4"; } + Icon_dash_circle_fill, //before { content: "\f2e5"; } + Icon_dash_circle, //before { content: "\f2e6"; } + Icon_dash_square_dotted, //before { content: "\f2e7"; } + Icon_dash_square_fill, //before { content: "\f2e8"; } + Icon_dash_square, //before { content: "\f2e9"; } + Icon_dash, //before { content: "\f2ea"; } + Icon_diagram_2_fill, //before { content: "\f2eb"; } + Icon_diagram_2, //before { content: "\f2ec"; } + Icon_diagram_3_fill, //before { content: "\f2ed"; } + Icon_diagram_3, //before { content: "\f2ee"; } + Icon_diamond_fill, //before { content: "\f2ef"; } + Icon_diamond_half, //before { content: "\f2f0"; } + Icon_diamond, //before { content: "\f2f1"; } + Icon_dice_1_fill, //before { content: "\f2f2"; } + Icon_dice_1, //before { content: "\f2f3"; } + Icon_dice_2_fill, //before { content: "\f2f4"; } + Icon_dice_2, //before { content: "\f2f5"; } + Icon_dice_3_fill, //before { content: "\f2f6"; } + Icon_dice_3, //before { content: "\f2f7"; } + Icon_dice_4_fill, //before { content: "\f2f8"; } + Icon_dice_4, //before { content: "\f2f9"; } + Icon_dice_5_fill, //before { content: "\f2fa"; } + Icon_dice_5, //before { content: "\f2fb"; } + Icon_dice_6_fill, //before { content: "\f2fc"; } + Icon_dice_6, //before { content: "\f2fd"; } + Icon_disc_fill, //before { content: "\f2fe"; } + Icon_disc, //before { content: "\f2ff"; } + Icon_discord, //before { content: "\f300"; } + Icon_display_fill, //before { content: "\f301"; } + Icon_display, //before { content: "\f302"; } + Icon_distribute_horizontal, //before { content: "\f303"; } + Icon_distribute_vertical, //before { content: "\f304"; } + Icon_door_closed_fill, //before { content: "\f305"; } + Icon_door_closed, //before { content: "\f306"; } + Icon_door_open_fill, //before { content: "\f307"; } + Icon_door_open, //before { content: "\f308"; } + Icon_dot, //before { content: "\f309"; } + Icon_download, //before { content: "\f30a"; } + Icon_droplet_fill, //before { content: "\f30b"; } + Icon_droplet_half, //before { content: "\f30c"; } + Icon_droplet, //before { content: "\f30d"; } + Icon_earbuds, //before { content: "\f30e"; } + Icon_easel_fill, //before { content: "\f30f"; } + Icon_easel, //before { content: "\f310"; } + Icon_egg_fill, //before { content: "\f311"; } + Icon_egg_fried, //before { content: "\f312"; } + Icon_egg, //before { content: "\f313"; } + Icon_eject_fill, //before { content: "\f314"; } + Icon_eject, //before { content: "\f315"; } + Icon_emoji_angry_fill, //before { content: "\f316"; } + Icon_emoji_angry, //before { content: "\f317"; } + Icon_emoji_dizzy_fill, //before { content: "\f318"; } + Icon_emoji_dizzy, //before { content: "\f319"; } + Icon_emoji_expressionless_fill, //before { content: "\f31a"; } + Icon_emoji_expressionless, //before { content: "\f31b"; } + Icon_emoji_frown_fill, //before { content: "\f31c"; } + Icon_emoji_frown, //before { content: "\f31d"; } + Icon_emoji_heart_eyes_fill, //before { content: "\f31e"; } + Icon_emoji_heart_eyes, //before { content: "\f31f"; } + Icon_emoji_laughing_fill, //before { content: "\f320"; } + Icon_emoji_laughing, //before { content: "\f321"; } + Icon_emoji_neutral_fill, //before { content: "\f322"; } + Icon_emoji_neutral, //before { content: "\f323"; } + Icon_emoji_smile_fill, //before { content: "\f324"; } + Icon_emoji_smile_upside_down_fill, //before { content: "\f325"; } + Icon_emoji_smile_upside_down, //before { content: "\f326"; } + Icon_emoji_smile, //before { content: "\f327"; } + Icon_emoji_sunglasses_fill, //before { content: "\f328"; } + Icon_emoji_sunglasses, //before { content: "\f329"; } + Icon_emoji_wink_fill, //before { content: "\f32a"; } + Icon_emoji_wink, //before { content: "\f32b"; } + Icon_envelope_fill, //before { content: "\f32c"; } + Icon_envelope_open_fill, //before { content: "\f32d"; } + Icon_envelope_open, //before { content: "\f32e"; } + Icon_envelope, //before { content: "\f32f"; } + Icon_eraser_fill, //before { content: "\f330"; } + Icon_eraser, //before { content: "\f331"; } + Icon_exclamation_circle_fill, //before { content: "\f332"; } + Icon_exclamation_circle, //before { content: "\f333"; } + Icon_exclamation_diamond_fill, //before { content: "\f334"; } + Icon_exclamation_diamond, //before { content: "\f335"; } + Icon_exclamation_octagon_fill, //before { content: "\f336"; } + Icon_exclamation_octagon, //before { content: "\f337"; } + Icon_exclamation_square_fill, //before { content: "\f338"; } + Icon_exclamation_square, //before { content: "\f339"; } + Icon_exclamation_triangle_fill, //before { content: "\f33a"; } + Icon_exclamation_triangle, //before { content: "\f33b"; } + Icon_exclamation, //before { content: "\f33c"; } + Icon_exclude, //before { content: "\f33d"; } + Icon_eye_fill, //before { content: "\f33e"; } + Icon_eye_slash_fill, //before { content: "\f33f"; } + Icon_eye_slash, //before { content: "\f340"; } + Icon_eye, //before { content: "\f341"; } + Icon_eyedropper, //before { content: "\f342"; } + Icon_eyeglasses, //before { content: "\f343"; } + Icon_facebook, //before { content: "\f344"; } + Icon_file_arrow_down_fill, //before { content: "\f345"; } + Icon_file_arrow_down, //before { content: "\f346"; } + Icon_file_arrow_up_fill, //before { content: "\f347"; } + Icon_file_arrow_up, //before { content: "\f348"; } + Icon_file_bar_graph_fill, //before { content: "\f349"; } + Icon_file_bar_graph, //before { content: "\f34a"; } + Icon_file_binary_fill, //before { content: "\f34b"; } + Icon_file_binary, //before { content: "\f34c"; } + Icon_file_break_fill, //before { content: "\f34d"; } + Icon_file_break, //before { content: "\f34e"; } + Icon_file_check_fill, //before { content: "\f34f"; } + Icon_file_check, //before { content: "\f350"; } + Icon_file_code_fill, //before { content: "\f351"; } + Icon_file_code, //before { content: "\f352"; } + Icon_file_diff_fill, //before { content: "\f353"; } + Icon_file_diff, //before { content: "\f354"; } + Icon_file_earmark_arrow_down_fill, //before { content: "\f355"; } + Icon_file_earmark_arrow_down, //before { content: "\f356"; } + Icon_file_earmark_arrow_up_fill, //before { content: "\f357"; } + Icon_file_earmark_arrow_up, //before { content: "\f358"; } + Icon_file_earmark_bar_graph_fill, //before { content: "\f359"; } + Icon_file_earmark_bar_graph, //before { content: "\f35a"; } + Icon_file_earmark_binary_fill, //before { content: "\f35b"; } + Icon_file_earmark_binary, //before { content: "\f35c"; } + Icon_file_earmark_break_fill, //before { content: "\f35d"; } + Icon_file_earmark_break, //before { content: "\f35e"; } + Icon_file_earmark_check_fill, //before { content: "\f35f"; } + Icon_file_earmark_check, //before { content: "\f360"; } + Icon_file_earmark_code_fill, //before { content: "\f361"; } + Icon_file_earmark_code, //before { content: "\f362"; } + Icon_file_earmark_diff_fill, //before { content: "\f363"; } + Icon_file_earmark_diff, //before { content: "\f364"; } + Icon_file_earmark_easel_fill, //before { content: "\f365"; } + Icon_file_earmark_easel, //before { content: "\f366"; } + Icon_file_earmark_excel_fill, //before { content: "\f367"; } + Icon_file_earmark_excel, //before { content: "\f368"; } + Icon_file_earmark_fill, //before { content: "\f369"; } + Icon_file_earmark_font_fill, //before { content: "\f36a"; } + Icon_file_earmark_font, //before { content: "\f36b"; } + Icon_file_earmark_image_fill, //before { content: "\f36c"; } + Icon_file_earmark_image, //before { content: "\f36d"; } + Icon_file_earmark_lock_fill, //before { content: "\f36e"; } + Icon_file_earmark_lock, //before { content: "\f36f"; } + Icon_file_earmark_lock2_fill, //before { content: "\f370"; } + Icon_file_earmark_lock2, //before { content: "\f371"; } + Icon_file_earmark_medical_fill, //before { content: "\f372"; } + Icon_file_earmark_medical, //before { content: "\f373"; } + Icon_file_earmark_minus_fill, //before { content: "\f374"; } + Icon_file_earmark_minus, //before { content: "\f375"; } + Icon_file_earmark_music_fill, //before { content: "\f376"; } + Icon_file_earmark_music, //before { content: "\f377"; } + Icon_file_earmark_person_fill, //before { content: "\f378"; } + Icon_file_earmark_person, //before { content: "\f379"; } + Icon_file_earmark_play_fill, //before { content: "\f37a"; } + Icon_file_earmark_play, //before { content: "\f37b"; } + Icon_file_earmark_plus_fill, //before { content: "\f37c"; } + Icon_file_earmark_plus, //before { content: "\f37d"; } + Icon_file_earmark_post_fill, //before { content: "\f37e"; } + Icon_file_earmark_post, //before { content: "\f37f"; } + Icon_file_earmark_ppt_fill, //before { content: "\f380"; } + Icon_file_earmark_ppt, //before { content: "\f381"; } + Icon_file_earmark_richtext_fill, //before { content: "\f382"; } + Icon_file_earmark_richtext, //before { content: "\f383"; } + Icon_file_earmark_ruled_fill, //before { content: "\f384"; } + Icon_file_earmark_ruled, //before { content: "\f385"; } + Icon_file_earmark_slides_fill, //before { content: "\f386"; } + Icon_file_earmark_slides, //before { content: "\f387"; } + Icon_file_earmark_spreadsheet_fill, //before { content: "\f388"; } + Icon_file_earmark_spreadsheet, //before { content: "\f389"; } + Icon_file_earmark_text_fill, //before { content: "\f38a"; } + Icon_file_earmark_text, //before { content: "\f38b"; } + Icon_file_earmark_word_fill, //before { content: "\f38c"; } + Icon_file_earmark_word, //before { content: "\f38d"; } + Icon_file_earmark_x_fill, //before { content: "\f38e"; } + Icon_file_earmark_x, //before { content: "\f38f"; } + Icon_file_earmark_zip_fill, //before { content: "\f390"; } + Icon_file_earmark_zip, //before { content: "\f391"; } + Icon_file_earmark, //before { content: "\f392"; } + Icon_file_easel_fill, //before { content: "\f393"; } + Icon_file_easel, //before { content: "\f394"; } + Icon_file_excel_fill, //before { content: "\f395"; } + Icon_file_excel, //before { content: "\f396"; } + Icon_file_fill, //before { content: "\f397"; } + Icon_file_font_fill, //before { content: "\f398"; } + Icon_file_font, //before { content: "\f399"; } + Icon_file_image_fill, //before { content: "\f39a"; } + Icon_file_image, //before { content: "\f39b"; } + Icon_file_lock_fill, //before { content: "\f39c"; } + Icon_file_lock, //before { content: "\f39d"; } + Icon_file_lock2_fill, //before { content: "\f39e"; } + Icon_file_lock2, //before { content: "\f39f"; } + Icon_file_medical_fill, //before { content: "\f3a0"; } + Icon_file_medical, //before { content: "\f3a1"; } + Icon_file_minus_fill, //before { content: "\f3a2"; } + Icon_file_minus, //before { content: "\f3a3"; } + Icon_file_music_fill, //before { content: "\f3a4"; } + Icon_file_music, //before { content: "\f3a5"; } + Icon_file_person_fill, //before { content: "\f3a6"; } + Icon_file_person, //before { content: "\f3a7"; } + Icon_file_play_fill, //before { content: "\f3a8"; } + Icon_file_play, //before { content: "\f3a9"; } + Icon_file_plus_fill, //before { content: "\f3aa"; } + Icon_file_plus, //before { content: "\f3ab"; } + Icon_file_post_fill, //before { content: "\f3ac"; } + Icon_file_post, //before { content: "\f3ad"; } + Icon_file_ppt_fill, //before { content: "\f3ae"; } + Icon_file_ppt, //before { content: "\f3af"; } + Icon_file_richtext_fill, //before { content: "\f3b0"; } + Icon_file_richtext, //before { content: "\f3b1"; } + Icon_file_ruled_fill, //before { content: "\f3b2"; } + Icon_file_ruled, //before { content: "\f3b3"; } + Icon_file_slides_fill, //before { content: "\f3b4"; } + Icon_file_slides, //before { content: "\f3b5"; } + Icon_file_spreadsheet_fill, //before { content: "\f3b6"; } + Icon_file_spreadsheet, //before { content: "\f3b7"; } + Icon_file_text_fill, //before { content: "\f3b8"; } + Icon_file_text, //before { content: "\f3b9"; } + Icon_file_word_fill, //before { content: "\f3ba"; } + Icon_file_word, //before { content: "\f3bb"; } + Icon_file_x_fill, //before { content: "\f3bc"; } + Icon_file_x, //before { content: "\f3bd"; } + Icon_file_zip_fill, //before { content: "\f3be"; } + Icon_file_zip, //before { content: "\f3bf"; } + Icon_file, //before { content: "\f3c0"; } + Icon_files_alt, //before { content: "\f3c1"; } + Icon_files, //before { content: "\f3c2"; } + Icon_film, //before { content: "\f3c3"; } + Icon_filter_circle_fill, //before { content: "\f3c4"; } + Icon_filter_circle, //before { content: "\f3c5"; } + Icon_filter_left, //before { content: "\f3c6"; } + Icon_filter_right, //before { content: "\f3c7"; } + Icon_filter_square_fill, //before { content: "\f3c8"; } + Icon_filter_square, //before { content: "\f3c9"; } + Icon_filter, //before { content: "\f3ca"; } + Icon_flag_fill, //before { content: "\f3cb"; } + Icon_flag, //before { content: "\f3cc"; } + Icon_flower1, //before { content: "\f3cd"; } + Icon_flower2, //before { content: "\f3ce"; } + Icon_flower3, //before { content: "\f3cf"; } + Icon_folder_check, //before { content: "\f3d0"; } + Icon_folder_fill, //before { content: "\f3d1"; } + Icon_folder_minus, //before { content: "\f3d2"; } + Icon_folder_plus, //before { content: "\f3d3"; } + Icon_folder_symlink_fill, //before { content: "\f3d4"; } + Icon_folder_symlink, //before { content: "\f3d5"; } + Icon_folder_x, //before { content: "\f3d6"; } + Icon_folder, //before { content: "\f3d7"; } + Icon_folder2_open, //before { content: "\f3d8"; } + Icon_folder2, //before { content: "\f3d9"; } + Icon_fonts, //before { content: "\f3da"; } + Icon_forward_fill, //before { content: "\f3db"; } + Icon_forward, //before { content: "\f3dc"; } + Icon_front, //before { content: "\f3dd"; } + Icon_fullscreen_exit, //before { content: "\f3de"; } + Icon_fullscreen, //before { content: "\f3df"; } + Icon_funnel_fill, //before { content: "\f3e0"; } + Icon_funnel, //before { content: "\f3e1"; } + Icon_gear_fill, //before { content: "\f3e2"; } + Icon_gear_wide_connected, //before { content: "\f3e3"; } + Icon_gear_wide, //before { content: "\f3e4"; } + Icon_gear, //before { content: "\f3e5"; } + Icon_gem, //before { content: "\f3e6"; } + Icon_geo_alt_fill, //before { content: "\f3e7"; } + Icon_geo_alt, //before { content: "\f3e8"; } + Icon_geo_fill, //before { content: "\f3e9"; } + Icon_geo, //before { content: "\f3ea"; } + Icon_gift_fill, //before { content: "\f3eb"; } + Icon_gift, //before { content: "\f3ec"; } + Icon_github, //before { content: "\f3ed"; } + Icon_globe, //before { content: "\f3ee"; } + Icon_globe2, //before { content: "\f3ef"; } + Icon_google, //before { content: "\f3f0"; } + Icon_graph_down, //before { content: "\f3f1"; } + Icon_graph_up, //before { content: "\f3f2"; } + Icon_grid_1x2_fill, //before { content: "\f3f3"; } + Icon_grid_1x2, //before { content: "\f3f4"; } + Icon_grid_3x2_gap_fill, //before { content: "\f3f5"; } + Icon_grid_3x2_gap, //before { content: "\f3f6"; } + Icon_grid_3x2, //before { content: "\f3f7"; } + Icon_grid_3x3_gap_fill, //before { content: "\f3f8"; } + Icon_grid_3x3_gap, //before { content: "\f3f9"; } + Icon_grid_3x3, //before { content: "\f3fa"; } + Icon_grid_fill, //before { content: "\f3fb"; } + Icon_grid, //before { content: "\f3fc"; } + Icon_grip_horizontal, //before { content: "\f3fd"; } + Icon_grip_vertical, //before { content: "\f3fe"; } + Icon_hammer, //before { content: "\f3ff"; } + Icon_hand_index_fill, //before { content: "\f400"; } + Icon_hand_index_thumb_fill, //before { content: "\f401"; } + Icon_hand_index_thumb, //before { content: "\f402"; } + Icon_hand_index, //before { content: "\f403"; } + Icon_hand_thumbs_down_fill, //before { content: "\f404"; } + Icon_hand_thumbs_down, //before { content: "\f405"; } + Icon_hand_thumbs_up_fill, //before { content: "\f406"; } + Icon_hand_thumbs_up, //before { content: "\f407"; } + Icon_handbag_fill, //before { content: "\f408"; } + Icon_handbag, //before { content: "\f409"; } + Icon_hash, //before { content: "\f40a"; } + Icon_hdd_fill, //before { content: "\f40b"; } + Icon_hdd_network_fill, //before { content: "\f40c"; } + Icon_hdd_network, //before { content: "\f40d"; } + Icon_hdd_rack_fill, //before { content: "\f40e"; } + Icon_hdd_rack, //before { content: "\f40f"; } + Icon_hdd_stack_fill, //before { content: "\f410"; } + Icon_hdd_stack, //before { content: "\f411"; } + Icon_hdd, //before { content: "\f412"; } + Icon_headphones, //before { content: "\f413"; } + Icon_headset, //before { content: "\f414"; } + Icon_heart_fill, //before { content: "\f415"; } + Icon_heart_half, //before { content: "\f416"; } + Icon_heart, //before { content: "\f417"; } + Icon_heptagon_fill, //before { content: "\f418"; } + Icon_heptagon_half, //before { content: "\f419"; } + Icon_heptagon, //before { content: "\f41a"; } + Icon_hexagon_fill, //before { content: "\f41b"; } + Icon_hexagon_half, //before { content: "\f41c"; } + Icon_hexagon, //before { content: "\f41d"; } + Icon_hourglass_bottom, //before { content: "\f41e"; } + Icon_hourglass_split, //before { content: "\f41f"; } + Icon_hourglass_top, //before { content: "\f420"; } + Icon_hourglass, //before { content: "\f421"; } + Icon_house_door_fill, //before { content: "\f422"; } + Icon_house_door, //before { content: "\f423"; } + Icon_house_fill, //before { content: "\f424"; } + Icon_house, //before { content: "\f425"; } + Icon_hr, //before { content: "\f426"; } + Icon_hurricane, //before { content: "\f427"; } + Icon_image_alt, //before { content: "\f428"; } + Icon_image_fill, //before { content: "\f429"; } + Icon_image, //before { content: "\f42a"; } + Icon_images, //before { content: "\f42b"; } + Icon_inbox_fill, //before { content: "\f42c"; } + Icon_inbox, //before { content: "\f42d"; } + Icon_inboxes_fill, //before { content: "\f42e"; } + Icon_inboxes, //before { content: "\f42f"; } + Icon_info_circle_fill, //before { content: "\f430"; } + Icon_info_circle, //before { content: "\f431"; } + Icon_info_square_fill, //before { content: "\f432"; } + Icon_info_square, //before { content: "\f433"; } + Icon_info, //before { content: "\f434"; } + Icon_input_cursor_text, //before { content: "\f435"; } + Icon_input_cursor, //before { content: "\f436"; } + Icon_instagram, //before { content: "\f437"; } + Icon_intersect, //before { content: "\f438"; } + Icon_journal_album, //before { content: "\f439"; } + Icon_journal_arrow_down, //before { content: "\f43a"; } + Icon_journal_arrow_up, //before { content: "\f43b"; } + Icon_journal_bookmark_fill, //before { content: "\f43c"; } + Icon_journal_bookmark, //before { content: "\f43d"; } + Icon_journal_check, //before { content: "\f43e"; } + Icon_journal_code, //before { content: "\f43f"; } + Icon_journal_medical, //before { content: "\f440"; } + Icon_journal_minus, //before { content: "\f441"; } + Icon_journal_plus, //before { content: "\f442"; } + Icon_journal_richtext, //before { content: "\f443"; } + Icon_journal_text, //before { content: "\f444"; } + Icon_journal_x, //before { content: "\f445"; } + Icon_journal, //before { content: "\f446"; } + Icon_journals, //before { content: "\f447"; } + Icon_joystick, //before { content: "\f448"; } + Icon_justify_left, //before { content: "\f449"; } + Icon_justify_right, //before { content: "\f44a"; } + Icon_justify, //before { content: "\f44b"; } + Icon_kanban_fill, //before { content: "\f44c"; } + Icon_kanban, //before { content: "\f44d"; } + Icon_key_fill, //before { content: "\f44e"; } + Icon_key, //before { content: "\f44f"; } + Icon_keyboard_fill, //before { content: "\f450"; } + Icon_keyboard, //before { content: "\f451"; } + Icon_ladder, //before { content: "\f452"; } + Icon_lamp_fill, //before { content: "\f453"; } + Icon_lamp, //before { content: "\f454"; } + Icon_laptop_fill, //before { content: "\f455"; } + Icon_laptop, //before { content: "\f456"; } + Icon_layer_backward, //before { content: "\f457"; } + Icon_layer_forward, //before { content: "\f458"; } + Icon_layers_fill, //before { content: "\f459"; } + Icon_layers_half, //before { content: "\f45a"; } + Icon_layers, //before { content: "\f45b"; } + Icon_layout_sidebar_inset_reverse, //before { content: "\f45c"; } + Icon_layout_sidebar_inset, //before { content: "\f45d"; } + Icon_layout_sidebar_reverse, //before { content: "\f45e"; } + Icon_layout_sidebar, //before { content: "\f45f"; } + Icon_layout_split, //before { content: "\f460"; } + Icon_layout_text_sidebar_reverse, //before { content: "\f461"; } + Icon_layout_text_sidebar, //before { content: "\f462"; } + Icon_layout_text_window_reverse, //before { content: "\f463"; } + Icon_layout_text_window, //before { content: "\f464"; } + Icon_layout_three_columns, //before { content: "\f465"; } + Icon_layout_wtf, //before { content: "\f466"; } + Icon_life_preserver, //before { content: "\f467"; } + Icon_lightbulb_fill, //before { content: "\f468"; } + Icon_lightbulb_off_fill, //before { content: "\f469"; } + Icon_lightbulb_off, //before { content: "\f46a"; } + Icon_lightbulb, //before { content: "\f46b"; } + Icon_lightning_charge_fill, //before { content: "\f46c"; } + Icon_lightning_charge, //before { content: "\f46d"; } + Icon_lightning_fill, //before { content: "\f46e"; } + Icon_lightning, //before { content: "\f46f"; } + Icon_link_45deg, //before { content: "\f470"; } + Icon_link, //before { content: "\f471"; } + Icon_linkedin, //before { content: "\f472"; } + Icon_list_check, //before { content: "\f473"; } + Icon_list_nested, //before { content: "\f474"; } + Icon_list_ol, //before { content: "\f475"; } + Icon_list_stars, //before { content: "\f476"; } + Icon_list_task, //before { content: "\f477"; } + Icon_list_ul, //before { content: "\f478"; } + Icon_list, //before { content: "\f479"; } + Icon_lock_fill, //before { content: "\f47a"; } + Icon_lock, //before { content: "\f47b"; } + Icon_mailbox, //before { content: "\f47c"; } + Icon_mailbox2, //before { content: "\f47d"; } + Icon_map_fill, //before { content: "\f47e"; } + Icon_map, //before { content: "\f47f"; } + Icon_markdown_fill, //before { content: "\f480"; } + Icon_markdown, //before { content: "\f481"; } + Icon_mask, //before { content: "\f482"; } + Icon_megaphone_fill, //before { content: "\f483"; } + Icon_megaphone, //before { content: "\f484"; } + Icon_menu_app_fill, //before { content: "\f485"; } + Icon_menu_app, //before { content: "\f486"; } + Icon_menu_button_fill, //before { content: "\f487"; } + Icon_menu_button_wide_fill, //before { content: "\f488"; } + Icon_menu_button_wide, //before { content: "\f489"; } + Icon_menu_button, //before { content: "\f48a"; } + Icon_menu_down, //before { content: "\f48b"; } + Icon_menu_up, //before { content: "\f48c"; } + Icon_mic_fill, //before { content: "\f48d"; } + Icon_mic_mute_fill, //before { content: "\f48e"; } + Icon_mic_mute, //before { content: "\f48f"; } + Icon_mic, //before { content: "\f490"; } + Icon_minecart_loaded, //before { content: "\f491"; } + Icon_minecart, //before { content: "\f492"; } + Icon_moisture, //before { content: "\f493"; } + Icon_moon_fill, //before { content: "\f494"; } + Icon_moon_stars_fill, //before { content: "\f495"; } + Icon_moon_stars, //before { content: "\f496"; } + Icon_moon, //before { content: "\f497"; } + Icon_mouse_fill, //before { content: "\f498"; } + Icon_mouse, //before { content: "\f499"; } + Icon_mouse2_fill, //before { content: "\f49a"; } + Icon_mouse2, //before { content: "\f49b"; } + Icon_mouse3_fill, //before { content: "\f49c"; } + Icon_mouse3, //before { content: "\f49d"; } + Icon_music_note_beamed, //before { content: "\f49e"; } + Icon_music_note_list, //before { content: "\f49f"; } + Icon_music_note, //before { content: "\f4a0"; } + Icon_music_player_fill, //before { content: "\f4a1"; } + Icon_music_player, //before { content: "\f4a2"; } + Icon_newspaper, //before { content: "\f4a3"; } + Icon_node_minus_fill, //before { content: "\f4a4"; } + Icon_node_minus, //before { content: "\f4a5"; } + Icon_node_plus_fill, //before { content: "\f4a6"; } + Icon_node_plus, //before { content: "\f4a7"; } + Icon_nut_fill, //before { content: "\f4a8"; } + Icon_nut, //before { content: "\f4a9"; } + Icon_octagon_fill, //before { content: "\f4aa"; } + Icon_octagon_half, //before { content: "\f4ab"; } + Icon_octagon, //before { content: "\f4ac"; } + Icon_option, //before { content: "\f4ad"; } + Icon_outlet, //before { content: "\f4ae"; } + Icon_paint_bucket, //before { content: "\f4af"; } + Icon_palette_fill, //before { content: "\f4b0"; } + Icon_palette, //before { content: "\f4b1"; } + Icon_palette2, //before { content: "\f4b2"; } + Icon_paperclip, //before { content: "\f4b3"; } + Icon_paragraph, //before { content: "\f4b4"; } + Icon_patch_check_fill, //before { content: "\f4b5"; } + Icon_patch_check, //before { content: "\f4b6"; } + Icon_patch_exclamation_fill, //before { content: "\f4b7"; } + Icon_patch_exclamation, //before { content: "\f4b8"; } + Icon_patch_minus_fill, //before { content: "\f4b9"; } + Icon_patch_minus, //before { content: "\f4ba"; } + Icon_patch_plus_fill, //before { content: "\f4bb"; } + Icon_patch_plus, //before { content: "\f4bc"; } + Icon_patch_question_fill, //before { content: "\f4bd"; } + Icon_patch_question, //before { content: "\f4be"; } + Icon_pause_btn_fill, //before { content: "\f4bf"; } + Icon_pause_btn, //before { content: "\f4c0"; } + Icon_pause_circle_fill, //before { content: "\f4c1"; } + Icon_pause_circle, //before { content: "\f4c2"; } + Icon_pause_fill, //before { content: "\f4c3"; } + Icon_pause, //before { content: "\f4c4"; } + Icon_peace_fill, //before { content: "\f4c5"; } + Icon_peace, //before { content: "\f4c6"; } + Icon_pen_fill, //before { content: "\f4c7"; } + Icon_pen, //before { content: "\f4c8"; } + Icon_pencil_fill, //before { content: "\f4c9"; } + Icon_pencil_square, //before { content: "\f4ca"; } + Icon_pencil, //before { content: "\f4cb"; } + Icon_pentagon_fill, //before { content: "\f4cc"; } + Icon_pentagon_half, //before { content: "\f4cd"; } + Icon_pentagon, //before { content: "\f4ce"; } + Icon_people_fill, //before { content: "\f4cf"; } + Icon_people, //before { content: "\f4d0"; } + Icon_percent, //before { content: "\f4d1"; } + Icon_person_badge_fill, //before { content: "\f4d2"; } + Icon_person_badge, //before { content: "\f4d3"; } + Icon_person_bounding_box, //before { content: "\f4d4"; } + Icon_person_check_fill, //before { content: "\f4d5"; } + Icon_person_check, //before { content: "\f4d6"; } + Icon_person_circle, //before { content: "\f4d7"; } + Icon_person_dash_fill, //before { content: "\f4d8"; } + Icon_person_dash, //before { content: "\f4d9"; } + Icon_person_fill, //before { content: "\f4da"; } + Icon_person_lines_fill, //before { content: "\f4db"; } + Icon_person_plus_fill, //before { content: "\f4dc"; } + Icon_person_plus, //before { content: "\f4dd"; } + Icon_person_square, //before { content: "\f4de"; } + Icon_person_x_fill, //before { content: "\f4df"; } + Icon_person_x, //before { content: "\f4e0"; } + Icon_person, //before { content: "\f4e1"; } + Icon_phone_fill, //before { content: "\f4e2"; } + Icon_phone_landscape_fill, //before { content: "\f4e3"; } + Icon_phone_landscape, //before { content: "\f4e4"; } + Icon_phone_vibrate_fill, //before { content: "\f4e5"; } + Icon_phone_vibrate, //before { content: "\f4e6"; } + Icon_phone, //before { content: "\f4e7"; } + Icon_pie_chart_fill, //before { content: "\f4e8"; } + Icon_pie_chart, //before { content: "\f4e9"; } + Icon_pin_angle_fill, //before { content: "\f4ea"; } + Icon_pin_angle, //before { content: "\f4eb"; } + Icon_pin_fill, //before { content: "\f4ec"; } + Icon_pin, //before { content: "\f4ed"; } + Icon_pip_fill, //before { content: "\f4ee"; } + Icon_pip, //before { content: "\f4ef"; } + Icon_play_btn_fill, //before { content: "\f4f0"; } + Icon_play_btn, //before { content: "\f4f1"; } + Icon_play_circle_fill, //before { content: "\f4f2"; } + Icon_play_circle, //before { content: "\f4f3"; } + Icon_play_fill, //before { content: "\f4f4"; } + Icon_play, //before { content: "\f4f5"; } + Icon_plug_fill, //before { content: "\f4f6"; } + Icon_plug, //before { content: "\f4f7"; } + Icon_plus_circle_dotted, //before { content: "\f4f8"; } + Icon_plus_circle_fill, //before { content: "\f4f9"; } + Icon_plus_circle, //before { content: "\f4fa"; } + Icon_plus_square_dotted, //before { content: "\f4fb"; } + Icon_plus_square_fill, //before { content: "\f4fc"; } + Icon_plus_square, //before { content: "\f4fd"; } + Icon_plus, //before { content: "\f4fe"; } + Icon_power, //before { content: "\f4ff"; } + Icon_printer_fill, //before { content: "\f500"; } + Icon_printer, //before { content: "\f501"; } + Icon_puzzle_fill, //before { content: "\f502"; } + Icon_puzzle, //before { content: "\f503"; } + Icon_question_circle_fill, //before { content: "\f504"; } + Icon_question_circle, //before { content: "\f505"; } + Icon_question_diamond_fill, //before { content: "\f506"; } + Icon_question_diamond, //before { content: "\f507"; } + Icon_question_octagon_fill, //before { content: "\f508"; } + Icon_question_octagon, //before { content: "\f509"; } + Icon_question_square_fill, //before { content: "\f50a"; } + Icon_question_square, //before { content: "\f50b"; } + Icon_question, //before { content: "\f50c"; } + Icon_rainbow, //before { content: "\f50d"; } + Icon_receipt_cutoff, //before { content: "\f50e"; } + Icon_receipt, //before { content: "\f50f"; } + Icon_reception_0, //before { content: "\f510"; } + Icon_reception_1, //before { content: "\f511"; } + Icon_reception_2, //before { content: "\f512"; } + Icon_reception_3, //before { content: "\f513"; } + Icon_reception_4, //before { content: "\f514"; } + Icon_record_btn_fill, //before { content: "\f515"; } + Icon_record_btn, //before { content: "\f516"; } + Icon_record_circle_fill, //before { content: "\f517"; } + Icon_record_circle, //before { content: "\f518"; } + Icon_record_fill, //before { content: "\f519"; } + Icon_record, //before { content: "\f51a"; } + Icon_record2_fill, //before { content: "\f51b"; } + Icon_record2, //before { content: "\f51c"; } + Icon_reply_all_fill, //before { content: "\f51d"; } + Icon_reply_all, //before { content: "\f51e"; } + Icon_reply_fill, //before { content: "\f51f"; } + Icon_reply, //before { content: "\f520"; } + Icon_rss_fill, //before { content: "\f521"; } + Icon_rss, //before { content: "\f522"; } + Icon_rulers, //before { content: "\f523"; } + Icon_save_fill, //before { content: "\f524"; } + Icon_save, //before { content: "\f525"; } + Icon_save2_fill, //before { content: "\f526"; } + Icon_save2, //before { content: "\f527"; } + Icon_scissors, //before { content: "\f528"; } + Icon_screwdriver, //before { content: "\f529"; } + Icon_search, //before { content: "\f52a"; } + Icon_segmented_nav, //before { content: "\f52b"; } + Icon_server, //before { content: "\f52c"; } + Icon_share_fill, //before { content: "\f52d"; } + Icon_share, //before { content: "\f52e"; } + Icon_shield_check, //before { content: "\f52f"; } + Icon_shield_exclamation, //before { content: "\f530"; } + Icon_shield_fill_check, //before { content: "\f531"; } + Icon_shield_fill_exclamation, //before { content: "\f532"; } + Icon_shield_fill_minus, //before { content: "\f533"; } + Icon_shield_fill_plus, //before { content: "\f534"; } + Icon_shield_fill_x, //before { content: "\f535"; } + Icon_shield_fill, //before { content: "\f536"; } + Icon_shield_lock_fill, //before { content: "\f537"; } + Icon_shield_lock, //before { content: "\f538"; } + Icon_shield_minus, //before { content: "\f539"; } + Icon_shield_plus, //before { content: "\f53a"; } + Icon_shield_shaded, //before { content: "\f53b"; } + Icon_shield_slash_fill, //before { content: "\f53c"; } + Icon_shield_slash, //before { content: "\f53d"; } + Icon_shield_x, //before { content: "\f53e"; } + Icon_shield, //before { content: "\f53f"; } + Icon_shift_fill, //before { content: "\f540"; } + Icon_shift, //before { content: "\f541"; } + Icon_shop_window, //before { content: "\f542"; } + Icon_shop, //before { content: "\f543"; } + Icon_shuffle, //before { content: "\f544"; } + Icon_signpost_2_fill, //before { content: "\f545"; } + Icon_signpost_2, //before { content: "\f546"; } + Icon_signpost_fill, //before { content: "\f547"; } + Icon_signpost_split_fill, //before { content: "\f548"; } + Icon_signpost_split, //before { content: "\f549"; } + Icon_signpost, //before { content: "\f54a"; } + Icon_sim_fill, //before { content: "\f54b"; } + Icon_sim, //before { content: "\f54c"; } + Icon_skip_backward_btn_fill, //before { content: "\f54d"; } + Icon_skip_backward_btn, //before { content: "\f54e"; } + Icon_skip_backward_circle_fill, //before { content: "\f54f"; } + Icon_skip_backward_circle, //before { content: "\f550"; } + Icon_skip_backward_fill, //before { content: "\f551"; } + Icon_skip_backward, //before { content: "\f552"; } + Icon_skip_end_btn_fill, //before { content: "\f553"; } + Icon_skip_end_btn, //before { content: "\f554"; } + Icon_skip_end_circle_fill, //before { content: "\f555"; } + Icon_skip_end_circle, //before { content: "\f556"; } + Icon_skip_end_fill, //before { content: "\f557"; } + Icon_skip_end, //before { content: "\f558"; } + Icon_skip_forward_btn_fill, //before { content: "\f559"; } + Icon_skip_forward_btn, //before { content: "\f55a"; } + Icon_skip_forward_circle_fill, //before { content: "\f55b"; } + Icon_skip_forward_circle, //before { content: "\f55c"; } + Icon_skip_forward_fill, //before { content: "\f55d"; } + Icon_skip_forward, //before { content: "\f55e"; } + Icon_skip_start_btn_fill, //before { content: "\f55f"; } + Icon_skip_start_btn, //before { content: "\f560"; } + Icon_skip_start_circle_fill, //before { content: "\f561"; } + Icon_skip_start_circle, //before { content: "\f562"; } + Icon_skip_start_fill, //before { content: "\f563"; } + Icon_skip_start, //before { content: "\f564"; } + Icon_slack, //before { content: "\f565"; } + Icon_slash_circle_fill, //before { content: "\f566"; } + Icon_slash_circle, //before { content: "\f567"; } + Icon_slash_square_fill, //before { content: "\f568"; } + Icon_slash_square, //before { content: "\f569"; } + Icon_slash, //before { content: "\f56a"; } + Icon_sliders, //before { content: "\f56b"; } + Icon_smartwatch, //before { content: "\f56c"; } + Icon_snow, //before { content: "\f56d"; } + Icon_snow2, //before { content: "\f56e"; } + Icon_snow3, //before { content: "\f56f"; } + Icon_sort_alpha_down_alt, //before { content: "\f570"; } + Icon_sort_alpha_down, //before { content: "\f571"; } + Icon_sort_alpha_up_alt, //before { content: "\f572"; } + Icon_sort_alpha_up, //before { content: "\f573"; } + Icon_sort_down_alt, //before { content: "\f574"; } + Icon_sort_down, //before { content: "\f575"; } + Icon_sort_numeric_down_alt, //before { content: "\f576"; } + Icon_sort_numeric_down, //before { content: "\f577"; } + Icon_sort_numeric_up_alt, //before { content: "\f578"; } + Icon_sort_numeric_up, //before { content: "\f579"; } + Icon_sort_up_alt, //before { content: "\f57a"; } + Icon_sort_up, //before { content: "\f57b"; } + Icon_soundwave, //before { content: "\f57c"; } + Icon_speaker_fill, //before { content: "\f57d"; } + Icon_speaker, //before { content: "\f57e"; } + Icon_speedometer, //before { content: "\f57f"; } + Icon_speedometer2, //before { content: "\f580"; } + Icon_spellcheck, //before { content: "\f581"; } + Icon_square_fill, //before { content: "\f582"; } + Icon_square_half, //before { content: "\f583"; } + Icon_square, //before { content: "\f584"; } + Icon_stack, //before { content: "\f585"; } + Icon_star_fill, //before { content: "\f586"; } + Icon_star_half, //before { content: "\f587"; } + Icon_star, //before { content: "\f588"; } + Icon_stars, //before { content: "\f589"; } + Icon_stickies_fill, //before { content: "\f58a"; } + Icon_stickies, //before { content: "\f58b"; } + Icon_sticky_fill, //before { content: "\f58c"; } + Icon_sticky, //before { content: "\f58d"; } + Icon_stop_btn_fill, //before { content: "\f58e"; } + Icon_stop_btn, //before { content: "\f58f"; } + Icon_stop_circle_fill, //before { content: "\f590"; } + Icon_stop_circle, //before { content: "\f591"; } + Icon_stop_fill, //before { content: "\f592"; } + Icon_stop, //before { content: "\f593"; } + Icon_stoplights_fill, //before { content: "\f594"; } + Icon_stoplights, //before { content: "\f595"; } + Icon_stopwatch_fill, //before { content: "\f596"; } + Icon_stopwatch, //before { content: "\f597"; } + Icon_subtract, //before { content: "\f598"; } + Icon_suit_club_fill, //before { content: "\f599"; } + Icon_suit_club, //before { content: "\f59a"; } + Icon_suit_diamond_fill, //before { content: "\f59b"; } + Icon_suit_diamond, //before { content: "\f59c"; } + Icon_suit_heart_fill, //before { content: "\f59d"; } + Icon_suit_heart, //before { content: "\f59e"; } + Icon_suit_spade_fill, //before { content: "\f59f"; } + Icon_suit_spade, //before { content: "\f5a0"; } + Icon_sun_fill, //before { content: "\f5a1"; } + Icon_sun, //before { content: "\f5a2"; } + Icon_sunglasses, //before { content: "\f5a3"; } + Icon_sunrise_fill, //before { content: "\f5a4"; } + Icon_sunrise, //before { content: "\f5a5"; } + Icon_sunset_fill, //before { content: "\f5a6"; } + Icon_sunset, //before { content: "\f5a7"; } + Icon_symmetry_horizontal, //before { content: "\f5a8"; } + Icon_symmetry_vertical, //before { content: "\f5a9"; } + Icon_table, //before { content: "\f5aa"; } + Icon_tablet_fill, //before { content: "\f5ab"; } + Icon_tablet_landscape_fill, //before { content: "\f5ac"; } + Icon_tablet_landscape, //before { content: "\f5ad"; } + Icon_tablet, //before { content: "\f5ae"; } + Icon_tag_fill, //before { content: "\f5af"; } + Icon_tag, //before { content: "\f5b0"; } + Icon_tags_fill, //before { content: "\f5b1"; } + Icon_tags, //before { content: "\f5b2"; } + Icon_telegram, //before { content: "\f5b3"; } + Icon_telephone_fill, //before { content: "\f5b4"; } + Icon_telephone_forward_fill, //before { content: "\f5b5"; } + Icon_telephone_forward, //before { content: "\f5b6"; } + Icon_telephone_inbound_fill, //before { content: "\f5b7"; } + Icon_telephone_inbound, //before { content: "\f5b8"; } + Icon_telephone_minus_fill, //before { content: "\f5b9"; } + Icon_telephone_minus, //before { content: "\f5ba"; } + Icon_telephone_outbound_fill, //before { content: "\f5bb"; } + Icon_telephone_outbound, //before { content: "\f5bc"; } + Icon_telephone_plus_fill, //before { content: "\f5bd"; } + Icon_telephone_plus, //before { content: "\f5be"; } + Icon_telephone_x_fill, //before { content: "\f5bf"; } + Icon_telephone_x, //before { content: "\f5c0"; } + Icon_telephone, //before { content: "\f5c1"; } + Icon_terminal_fill, //before { content: "\f5c2"; } + Icon_terminal, //before { content: "\f5c3"; } + Icon_text_center, //before { content: "\f5c4"; } + Icon_text_indent_left, //before { content: "\f5c5"; } + Icon_text_indent_right, //before { content: "\f5c6"; } + Icon_text_left, //before { content: "\f5c7"; } + Icon_text_paragraph, //before { content: "\f5c8"; } + Icon_text_right, //before { content: "\f5c9"; } + Icon_textarea_resize, //before { content: "\f5ca"; } + Icon_textarea_t, //before { content: "\f5cb"; } + Icon_textarea, //before { content: "\f5cc"; } + Icon_thermometer_half, //before { content: "\f5cd"; } + Icon_thermometer_high, //before { content: "\f5ce"; } + Icon_thermometer_low, //before { content: "\f5cf"; } + Icon_thermometer_snow, //before { content: "\f5d0"; } + Icon_thermometer_sun, //before { content: "\f5d1"; } + Icon_thermometer, //before { content: "\f5d2"; } + Icon_three_dots_vertical, //before { content: "\f5d3"; } + Icon_three_dots, //before { content: "\f5d4"; } + Icon_toggle_off, //before { content: "\f5d5"; } + Icon_toggle_on, //before { content: "\f5d6"; } + Icon_toggle2_off, //before { content: "\f5d7"; } + Icon_toggle2_on, //before { content: "\f5d8"; } + Icon_toggles, //before { content: "\f5d9"; } + Icon_toggles2, //before { content: "\f5da"; } + Icon_tools, //before { content: "\f5db"; } + Icon_tornado, //before { content: "\f5dc"; } + Icon_trash_fill, //before { content: "\f5dd"; } + Icon_trash, //before { content: "\f5de"; } + Icon_trash2_fill, //before { content: "\f5df"; } + Icon_trash2, //before { content: "\f5e0"; } + Icon_tree_fill, //before { content: "\f5e1"; } + Icon_tree, //before { content: "\f5e2"; } + Icon_triangle_fill, //before { content: "\f5e3"; } + Icon_triangle_half, //before { content: "\f5e4"; } + Icon_triangle, //before { content: "\f5e5"; } + Icon_trophy_fill, //before { content: "\f5e6"; } + Icon_trophy, //before { content: "\f5e7"; } + Icon_tropical_storm, //before { content: "\f5e8"; } + Icon_truck_flatbed, //before { content: "\f5e9"; } + Icon_truck, //before { content: "\f5ea"; } + Icon_tsunami, //before { content: "\f5eb"; } + Icon_tv_fill, //before { content: "\f5ec"; } + Icon_tv, //before { content: "\f5ed"; } + Icon_twitch, //before { content: "\f5ee"; } + Icon_twitter, //before { content: "\f5ef"; } + Icon_type_bold, //before { content: "\f5f0"; } + Icon_type_h1, //before { content: "\f5f1"; } + Icon_type_h2, //before { content: "\f5f2"; } + Icon_type_h3, //before { content: "\f5f3"; } + Icon_type_italic, //before { content: "\f5f4"; } + Icon_type_strikethrough, //before { content: "\f5f5"; } + Icon_type_underline, //before { content: "\f5f6"; } + Icon_type, //before { content: "\f5f7"; } + Icon_ui_checks_grid, //before { content: "\f5f8"; } + Icon_ui_checks, //before { content: "\f5f9"; } + Icon_ui_radios_grid, //before { content: "\f5fa"; } + Icon_ui_radios, //before { content: "\f5fb"; } + Icon_umbrella_fill, //before { content: "\f5fc"; } + Icon_umbrella, //before { content: "\f5fd"; } + Icon_union, //before { content: "\f5fe"; } + Icon_unlock_fill, //before { content: "\f5ff"; } + Icon_unlock, //before { content: "\f600"; } + Icon_upc_scan, //before { content: "\f601"; } + Icon_upc, //before { content: "\f602"; } + Icon_upload, //before { content: "\f603"; } + Icon_vector_pen, //before { content: "\f604"; } + Icon_view_list, //before { content: "\f605"; } + Icon_view_stacked, //before { content: "\f606"; } + Icon_vinyl_fill, //before { content: "\f607"; } + Icon_vinyl, //before { content: "\f608"; } + Icon_voicemail, //before { content: "\f609"; } + Icon_volume_down_fill, //before { content: "\f60a"; } + Icon_volume_down, //before { content: "\f60b"; } + Icon_volume_mute_fill, //before { content: "\f60c"; } + Icon_volume_mute, //before { content: "\f60d"; } + Icon_volume_off_fill, //before { content: "\f60e"; } + Icon_volume_off, //before { content: "\f60f"; } + Icon_volume_up_fill, //before { content: "\f610"; } + Icon_volume_up, //before { content: "\f611"; } + Icon_vr, //before { content: "\f612"; } + Icon_wallet_fill, //before { content: "\f613"; } + Icon_wallet, //before { content: "\f614"; } + Icon_wallet2, //before { content: "\f615"; } + Icon_watch, //before { content: "\f616"; } + Icon_water, //before { content: "\f617"; } + Icon_whatsapp, //before { content: "\f618"; } + Icon_wifi_1, //before { content: "\f619"; } + Icon_wifi_2, //before { content: "\f61a"; } + Icon_wifi_off, //before { content: "\f61b"; } + Icon_wifi, //before { content: "\f61c"; } + Icon_wind, //before { content: "\f61d"; } + Icon_window_dock, //before { content: "\f61e"; } + Icon_window_sidebar, //before { content: "\f61f"; } + Icon_window, //before { content: "\f620"; } + Icon_wrench, //before { content: "\f621"; } + Icon_x_circle_fill, //before { content: "\f622"; } + Icon_x_circle, //before { content: "\f623"; } + Icon_x_diamond_fill, //before { content: "\f624"; } + Icon_x_diamond, //before { content: "\f625"; } + Icon_x_octagon_fill, //before { content: "\f626"; } + Icon_x_octagon, //before { content: "\f627"; } + Icon_x_square_fill, //before { content: "\f628"; } + Icon_x_square, //before { content: "\f629"; } + Icon_x, //before { content: "\f62a"; } + Icon_youtube, //before { content: "\f62b"; } + Icon_zoom_in, //before { content: "\f62c"; } + Icon_zoom_out, //before { content: "\f62d"; } + Icon_bank, //before { content: "\f62e"; } + Icon_bank2, //before { content: "\f62f"; } + Icon_bell_slash_fill, //before { content: "\f630"; } + Icon_bell_slash, //before { content: "\f631"; } + Icon_cash_coin, //before { content: "\f632"; } + Icon_check_lg, //before { content: "\f633"; } + Icon_coin, //before { content: "\f634"; } + Icon_currency_bitcoin, //before { content: "\f635"; } + Icon_currency_dollar, //before { content: "\f636"; } + Icon_currency_euro, //before { content: "\f637"; } + Icon_currency_exchange, //before { content: "\f638"; } + Icon_currency_pound, //before { content: "\f639"; } + Icon_currency_yen, //before { content: "\f63a"; } + Icon_dash_lg, //before { content: "\f63b"; } + Icon_exclamation_lg, //before { content: "\f63c"; } + Icon_file_earmark_pdf_fill, //before { content: "\f63d"; } + Icon_file_earmark_pdf, //before { content: "\f63e"; } + Icon_file_pdf_fill, //before { content: "\f63f"; } + Icon_file_pdf, //before { content: "\f640"; } + Icon_gender_ambiguous, //before { content: "\f641"; } + Icon_gender_female, //before { content: "\f642"; } + Icon_gender_male, //before { content: "\f643"; } + Icon_gender_trans, //before { content: "\f644"; } + Icon_headset_vr, //before { content: "\f645"; } + Icon_info_lg, //before { content: "\f646"; } + Icon_mastodon, //before { content: "\f647"; } + Icon_messenger, //before { content: "\f648"; } + Icon_piggy_bank_fill, //before { content: "\f649"; } + Icon_piggy_bank, //before { content: "\f64a"; } + Icon_pin_map_fill, //before { content: "\f64b"; } + Icon_pin_map, //before { content: "\f64c"; } + Icon_plus_lg, //before { content: "\f64d"; } + Icon_question_lg, //before { content: "\f64e"; } + Icon_recycle, //before { content: "\f64f"; } + Icon_reddit, //before { content: "\f650"; } + Icon_safe_fill, //before { content: "\f651"; } + Icon_safe2_fill, //before { content: "\f652"; } + Icon_safe2, //before { content: "\f653"; } + Icon_sd_card_fill, //before { content: "\f654"; } + Icon_sd_card, //before { content: "\f655"; } + Icon_skype, //before { content: "\f656"; } + Icon_slash_lg, //before { content: "\f657"; } + Icon_translate, //before { content: "\f658"; } + Icon_x_lg, //before { content: "\f659"; } + Icon_safe, //before { content: "\f65a"; } + Icon_apple, //before { content: "\f65b"; } + Icon_microsoft, //before { content: "\f65d"; } + Icon_windows, //before { content: "\f65e"; } + Icon_behance, //before { content: "\f65c"; } + Icon_dribbble, //before { content: "\f65f"; } + Icon_line, //before { content: "\f660"; } + Icon_medium, //before { content: "\f661"; } + Icon_paypal, //before { content: "\f662"; } + Icon_pinterest, //before { content: "\f663"; } + Icon_signal, //before { content: "\f664"; } + Icon_snapchat, //before { content: "\f665"; } + Icon_spotify, //before { content: "\f666"; } + Icon_stack_overflow, //before { content: "\f667"; } + Icon_strava, //before { content: "\f668"; } + Icon_wordpress, //before { content: "\f669"; } + Icon_vimeo, //before { content: "\f66a"; } + Icon_activity, //before { content: "\f66b"; } + Icon_easel2_fill, //before { content: "\f66c"; } + Icon_easel2, //before { content: "\f66d"; } + Icon_easel3_fill, //before { content: "\f66e"; } + Icon_easel3, //before { content: "\f66f"; } + Icon_fan, //before { content: "\f670"; } + Icon_fingerprint, //before { content: "\f671"; } + Icon_graph_down_arrow, //before { content: "\f672"; } + Icon_graph_up_arrow, //before { content: "\f673"; } + Icon_hypnotize, //before { content: "\f674"; } + Icon_magic, //before { content: "\f675"; } + Icon_person_rolodex, //before { content: "\f676"; } + Icon_person_video, //before { content: "\f677"; } + Icon_person_video2, //before { content: "\f678"; } + Icon_person_video3, //before { content: "\f679"; } + Icon_person_workspace, //before { content: "\f67a"; } + Icon_radioactive, //before { content: "\f67b"; } + Icon_webcam_fill, //before { content: "\f67c"; } + Icon_webcam, //before { content: "\f67d"; } + Icon_yin_yang, //before { content: "\f67e"; } + Icon_bandaid_fill, //before { content: "\f680"; } + Icon_bandaid, //before { content: "\f681"; } + Icon_bluetooth, //before { content: "\f682"; } + Icon_body_text, //before { content: "\f683"; } + Icon_boombox, //before { content: "\f684"; } + Icon_boxes, //before { content: "\f685"; } + Icon_dpad_fill, //before { content: "\f686"; } + Icon_dpad, //before { content: "\f687"; } + Icon_ear_fill, //before { content: "\f688"; } + Icon_ear, //before { content: "\f689"; } + Icon_envelope_check_fill, //before { content: "\f68b"; } + Icon_envelope_check, //before { content: "\f68c"; } + Icon_envelope_dash_fill, //before { content: "\f68e"; } + Icon_envelope_dash, //before { content: "\f68f"; } + Icon_envelope_exclamation_fill, //before { content: "\f691"; } + Icon_envelope_exclamation, //before { content: "\f692"; } + Icon_envelope_plus_fill, //before { content: "\f693"; } + Icon_envelope_plus, //before { content: "\f694"; } + Icon_envelope_slash_fill, //before { content: "\f696"; } + Icon_envelope_slash, //before { content: "\f697"; } + Icon_envelope_x_fill, //before { content: "\f699"; } + Icon_envelope_x, //before { content: "\f69a"; } + Icon_explicit_fill, //before { content: "\f69b"; } + Icon_explicit, //before { content: "\f69c"; } + Icon_git, //before { content: "\f69d"; } + Icon_infinity, //before { content: "\f69e"; } + Icon_list_columns_reverse, //before { content: "\f69f"; } + Icon_list_columns, //before { content: "\f6a0"; } + Icon_meta, //before { content: "\f6a1"; } + Icon_nintendo_switch, //before { content: "\f6a4"; } + Icon_pc_display_horizontal, //before { content: "\f6a5"; } + Icon_pc_display, //before { content: "\f6a6"; } + Icon_pc_horizontal, //before { content: "\f6a7"; } + Icon_pc, //before { content: "\f6a8"; } + Icon_playstation, //before { content: "\f6a9"; } + Icon_plus_slash_minus, //before { content: "\f6aa"; } + Icon_projector_fill, //before { content: "\f6ab"; } + Icon_projector, //before { content: "\f6ac"; } + Icon_qr_code_scan, //before { content: "\f6ad"; } + Icon_qr_code, //before { content: "\f6ae"; } + Icon_quora, //before { content: "\f6af"; } + Icon_quote, //before { content: "\f6b0"; } + Icon_robot, //before { content: "\f6b1"; } + Icon_send_check_fill, //before { content: "\f6b2"; } + Icon_send_check, //before { content: "\f6b3"; } + Icon_send_dash_fill, //before { content: "\f6b4"; } + Icon_send_dash, //before { content: "\f6b5"; } + Icon_send_exclamation_fill, //before { content: "\f6b7"; } + Icon_send_exclamation, //before { content: "\f6b8"; } + Icon_send_fill, //before { content: "\f6b9"; } + Icon_send_plus_fill, //before { content: "\f6ba"; } + Icon_send_plus, //before { content: "\f6bb"; } + Icon_send_slash_fill, //before { content: "\f6bc"; } + Icon_send_slash, //before { content: "\f6bd"; } + Icon_send_x_fill, //before { content: "\f6be"; } + Icon_send_x, //before { content: "\f6bf"; } + Icon_send, //before { content: "\f6c0"; } + Icon_steam, //before { content: "\f6c1"; } + Icon_terminal_dash, //before { content: "\f6c3"; } + Icon_terminal_plus, //before { content: "\f6c4"; } + Icon_terminal_split, //before { content: "\f6c5"; } + Icon_ticket_detailed_fill, //before { content: "\f6c6"; } + Icon_ticket_detailed, //before { content: "\f6c7"; } + Icon_ticket_fill, //before { content: "\f6c8"; } + Icon_ticket_perforated_fill, //before { content: "\f6c9"; } + Icon_ticket_perforated, //before { content: "\f6ca"; } + Icon_ticket, //before { content: "\f6cb"; } + Icon_tiktok, //before { content: "\f6cc"; } + Icon_window_dash, //before { content: "\f6cd"; } + Icon_window_desktop, //before { content: "\f6ce"; } + Icon_window_fullscreen, //before { content: "\f6cf"; } + Icon_window_plus, //before { content: "\f6d0"; } + Icon_window_split, //before { content: "\f6d1"; } + Icon_window_stack, //before { content: "\f6d2"; } + Icon_window_x, //before { content: "\f6d3"; } + Icon_xbox, //before { content: "\f6d4"; } + Icon_ethernet, //before { content: "\f6d5"; } + Icon_hdmi_fill, //before { content: "\f6d6"; } + Icon_hdmi, //before { content: "\f6d7"; } + Icon_usb_c_fill, //before { content: "\f6d8"; } + Icon_usb_c, //before { content: "\f6d9"; } + Icon_usb_fill, //before { content: "\f6da"; } + Icon_usb_plug_fill, //before { content: "\f6db"; } + Icon_usb_plug, //before { content: "\f6dc"; } + Icon_usb_symbol, //before { content: "\f6dd"; } + Icon_usb, //before { content: "\f6de"; } + Icon_boombox_fill, //before { content: "\f6df"; } + Icon_displayport, //before { content: "\f6e1"; } + Icon_gpu_card, //before { content: "\f6e2"; } + Icon_memory, //before { content: "\f6e3"; } + Icon_modem_fill, //before { content: "\f6e4"; } + Icon_modem, //before { content: "\f6e5"; } + Icon_motherboard_fill, //before { content: "\f6e6"; } + Icon_motherboard, //before { content: "\f6e7"; } + Icon_optical_audio_fill, //before { content: "\f6e8"; } + Icon_optical_audio, //before { content: "\f6e9"; } + Icon_pci_card, //before { content: "\f6ea"; } + Icon_router_fill, //before { content: "\f6eb"; } + Icon_router, //before { content: "\f6ec"; } + Icon_thunderbolt_fill, //before { content: "\f6ef"; } + Icon_thunderbolt, //before { content: "\f6f0"; } + Icon_usb_drive_fill, //before { content: "\f6f1"; } + Icon_usb_drive, //before { content: "\f6f2"; } + Icon_usb_micro_fill, //before { content: "\f6f3"; } + Icon_usb_micro, //before { content: "\f6f4"; } + Icon_usb_mini_fill, //before { content: "\f6f5"; } + Icon_usb_mini, //before { content: "\f6f6"; } + Icon_cloud_haze2, //before { content: "\f6f7"; } + Icon_device_hdd_fill, //before { content: "\f6f8"; } + Icon_device_hdd, //before { content: "\f6f9"; } + Icon_device_ssd_fill, //before { content: "\f6fa"; } + Icon_device_ssd, //before { content: "\f6fb"; } + Icon_displayport_fill, //before { content: "\f6fc"; } + Icon_mortarboard_fill, //before { content: "\f6fd"; } + Icon_mortarboard, //before { content: "\f6fe"; } + Icon_terminal_x, //before { content: "\f6ff"; } + Icon_arrow_through_heart_fill, //before { content: "\f700"; } + Icon_arrow_through_heart, //before { content: "\f701"; } + Icon_badge_sd_fill, //before { content: "\f702"; } + Icon_badge_sd, //before { content: "\f703"; } + Icon_bag_heart_fill, //before { content: "\f704"; } + Icon_bag_heart, //before { content: "\f705"; } + Icon_balloon_fill, //before { content: "\f706"; } + Icon_balloon_heart_fill, //before { content: "\f707"; } + Icon_balloon_heart, //before { content: "\f708"; } + Icon_balloon, //before { content: "\f709"; } + Icon_box2_fill, //before { content: "\f70a"; } + Icon_box2_heart_fill, //before { content: "\f70b"; } + Icon_box2_heart, //before { content: "\f70c"; } + Icon_box2, //before { content: "\f70d"; } + Icon_braces_asterisk, //before { content: "\f70e"; } + Icon_calendar_heart_fill, //before { content: "\f70f"; } + Icon_calendar_heart, //before { content: "\f710"; } + Icon_calendar2_heart_fill, //before { content: "\f711"; } + Icon_calendar2_heart, //before { content: "\f712"; } + Icon_chat_heart_fill, //before { content: "\f713"; } + Icon_chat_heart, //before { content: "\f714"; } + Icon_chat_left_heart_fill, //before { content: "\f715"; } + Icon_chat_left_heart, //before { content: "\f716"; } + Icon_chat_right_heart_fill, //before { content: "\f717"; } + Icon_chat_right_heart, //before { content: "\f718"; } + Icon_chat_square_heart_fill, //before { content: "\f719"; } + Icon_chat_square_heart, //before { content: "\f71a"; } + Icon_clipboard_check_fill, //before { content: "\f71b"; } + Icon_clipboard_data_fill, //before { content: "\f71c"; } + Icon_clipboard_fill, //before { content: "\f71d"; } + Icon_clipboard_heart_fill, //before { content: "\f71e"; } + Icon_clipboard_heart, //before { content: "\f71f"; } + Icon_clipboard_minus_fill, //before { content: "\f720"; } + Icon_clipboard_plus_fill, //before { content: "\f721"; } + Icon_clipboard_pulse, //before { content: "\f722"; } + Icon_clipboard_x_fill, //before { content: "\f723"; } + Icon_clipboard2_check_fill, //before { content: "\f724"; } + Icon_clipboard2_check, //before { content: "\f725"; } + Icon_clipboard2_data_fill, //before { content: "\f726"; } + Icon_clipboard2_data, //before { content: "\f727"; } + Icon_clipboard2_fill, //before { content: "\f728"; } + Icon_clipboard2_heart_fill, //before { content: "\f729"; } + Icon_clipboard2_heart, //before { content: "\f72a"; } + Icon_clipboard2_minus_fill, //before { content: "\f72b"; } + Icon_clipboard2_minus, //before { content: "\f72c"; } + Icon_clipboard2_plus_fill, //before { content: "\f72d"; } + Icon_clipboard2_plus, //before { content: "\f72e"; } + Icon_clipboard2_pulse_fill, //before { content: "\f72f"; } + Icon_clipboard2_pulse, //before { content: "\f730"; } + Icon_clipboard2_x_fill, //before { content: "\f731"; } + Icon_clipboard2_x, //before { content: "\f732"; } + Icon_clipboard2, //before { content: "\f733"; } + Icon_emoji_kiss_fill, //before { content: "\f734"; } + Icon_emoji_kiss, //before { content: "\f735"; } + Icon_envelope_heart_fill, //before { content: "\f736"; } + Icon_envelope_heart, //before { content: "\f737"; } + Icon_envelope_open_heart_fill, //before { content: "\f738"; } + Icon_envelope_open_heart, //before { content: "\f739"; } + Icon_envelope_paper_fill, //before { content: "\f73a"; } + Icon_envelope_paper_heart_fill, //before { content: "\f73b"; } + Icon_envelope_paper_heart, //before { content: "\f73c"; } + Icon_envelope_paper, //before { content: "\f73d"; } + Icon_filetype_aac, //before { content: "\f73e"; } + Icon_filetype_ai, //before { content: "\f73f"; } + Icon_filetype_bmp, //before { content: "\f740"; } + Icon_filetype_cs, //before { content: "\f741"; } + Icon_filetype_css, //before { content: "\f742"; } + Icon_filetype_csv, //before { content: "\f743"; } + Icon_filetype_doc, //before { content: "\f744"; } + Icon_filetype_docx, //before { content: "\f745"; } + Icon_filetype_exe, //before { content: "\f746"; } + Icon_filetype_gif, //before { content: "\f747"; } + Icon_filetype_heic, //before { content: "\f748"; } + Icon_filetype_html, //before { content: "\f749"; } + Icon_filetype_java, //before { content: "\f74a"; } + Icon_filetype_jpg, //before { content: "\f74b"; } + Icon_filetype_js, //before { content: "\f74c"; } + Icon_filetype_jsx, //before { content: "\f74d"; } + Icon_filetype_key, //before { content: "\f74e"; } + Icon_filetype_m4p, //before { content: "\f74f"; } + Icon_filetype_md, //before { content: "\f750"; } + Icon_filetype_mdx, //before { content: "\f751"; } + Icon_filetype_mov, //before { content: "\f752"; } + Icon_filetype_mp3, //before { content: "\f753"; } + Icon_filetype_mp4, //before { content: "\f754"; } + Icon_filetype_otf, //before { content: "\f755"; } + Icon_filetype_pdf, //before { content: "\f756"; } + Icon_filetype_php, //before { content: "\f757"; } + Icon_filetype_png, //before { content: "\f758"; } + Icon_filetype_ppt, //before { content: "\f75a"; } + Icon_filetype_psd, //before { content: "\f75b"; } + Icon_filetype_py, //before { content: "\f75c"; } + Icon_filetype_raw, //before { content: "\f75d"; } + Icon_filetype_rb, //before { content: "\f75e"; } + Icon_filetype_sass, //before { content: "\f75f"; } + Icon_filetype_scss, //before { content: "\f760"; } + Icon_filetype_sh, //before { content: "\f761"; } + Icon_filetype_svg, //before { content: "\f762"; } + Icon_filetype_tiff, //before { content: "\f763"; } + Icon_filetype_tsx, //before { content: "\f764"; } + Icon_filetype_ttf, //before { content: "\f765"; } + Icon_filetype_txt, //before { content: "\f766"; } + Icon_filetype_wav, //before { content: "\f767"; } + Icon_filetype_woff, //before { content: "\f768"; } + Icon_filetype_xls, //before { content: "\f76a"; } + Icon_filetype_xml, //before { content: "\f76b"; } + Icon_filetype_yml, //before { content: "\f76c"; } + Icon_heart_arrow, //before { content: "\f76d"; } + Icon_heart_pulse_fill, //before { content: "\f76e"; } + Icon_heart_pulse, //before { content: "\f76f"; } + Icon_heartbreak_fill, //before { content: "\f770"; } + Icon_heartbreak, //before { content: "\f771"; } + Icon_hearts, //before { content: "\f772"; } + Icon_hospital_fill, //before { content: "\f773"; } + Icon_hospital, //before { content: "\f774"; } + Icon_house_heart_fill, //before { content: "\f775"; } + Icon_house_heart, //before { content: "\f776"; } + Icon_incognito, //before { content: "\f777"; } + Icon_magnet_fill, //before { content: "\f778"; } + Icon_magnet, //before { content: "\f779"; } + Icon_person_heart, //before { content: "\f77a"; } + Icon_person_hearts, //before { content: "\f77b"; } + Icon_phone_flip, //before { content: "\f77c"; } + Icon_plugin, //before { content: "\f77d"; } + Icon_postage_fill, //before { content: "\f77e"; } + Icon_postage_heart_fill, //before { content: "\f77f"; } + Icon_postage_heart, //before { content: "\f780"; } + Icon_postage, //before { content: "\f781"; } + Icon_postcard_fill, //before { content: "\f782"; } + Icon_postcard_heart_fill, //before { content: "\f783"; } + Icon_postcard_heart, //before { content: "\f784"; } + Icon_postcard, //before { content: "\f785"; } + Icon_search_heart_fill, //before { content: "\f786"; } + Icon_search_heart, //before { content: "\f787"; } + Icon_sliders2_vertical, //before { content: "\f788"; } + Icon_sliders2, //before { content: "\f789"; } + Icon_trash3_fill, //before { content: "\f78a"; } + Icon_trash3, //before { content: "\f78b"; } + Icon_valentine, //before { content: "\f78c"; } + Icon_valentine2, //before { content: "\f78d"; } + Icon_wrench_adjustable_circle_fill, //before { content: "\f78e"; } + Icon_wrench_adjustable_circle, //before { content: "\f78f"; } + Icon_wrench_adjustable, //before { content: "\f790"; } + Icon_filetype_json, //before { content: "\f791"; } + Icon_filetype_pptx, //before { content: "\f792"; } + Icon_filetype_xlsx, //before { content: "\f793"; } + Icon_1_circle_fill, //before { content: "\f796"; } + Icon_1_circle, //before { content: "\f797"; } + Icon_1_square_fill, //before { content: "\f798"; } + Icon_1_square, //before { content: "\f799"; } + Icon_2_circle_fill, //before { content: "\f79c"; } + Icon_2_circle, //before { content: "\f79d"; } + Icon_2_square_fill, //before { content: "\f79e"; } + Icon_2_square, //before { content: "\f79f"; } + Icon_3_circle_fill, //before { content: "\f7a2"; } + Icon_3_circle, //before { content: "\f7a3"; } + Icon_3_square_fill, //before { content: "\f7a4"; } + Icon_3_square, //before { content: "\f7a5"; } + Icon_4_circle_fill, //before { content: "\f7a8"; } + Icon_4_circle, //before { content: "\f7a9"; } + Icon_4_square_fill, //before { content: "\f7aa"; } + Icon_4_square, //before { content: "\f7ab"; } + Icon_5_circle_fill, //before { content: "\f7ae"; } + Icon_5_circle, //before { content: "\f7af"; } + Icon_5_square_fill, //before { content: "\f7b0"; } + Icon_5_square, //before { content: "\f7b1"; } + Icon_6_circle_fill, //before { content: "\f7b4"; } + Icon_6_circle, //before { content: "\f7b5"; } + Icon_6_square_fill, //before { content: "\f7b6"; } + Icon_6_square, //before { content: "\f7b7"; } + Icon_7_circle_fill, //before { content: "\f7ba"; } + Icon_7_circle, //before { content: "\f7bb"; } + Icon_7_square_fill, //before { content: "\f7bc"; } + Icon_7_square, //before { content: "\f7bd"; } + Icon_8_circle_fill, //before { content: "\f7c0"; } + Icon_8_circle, //before { content: "\f7c1"; } + Icon_8_square_fill, //before { content: "\f7c2"; } + Icon_8_square, //before { content: "\f7c3"; } + Icon_9_circle_fill, //before { content: "\f7c6"; } + Icon_9_circle, //before { content: "\f7c7"; } + Icon_9_square_fill, //before { content: "\f7c8"; } + Icon_9_square, //before { content: "\f7c9"; } + Icon_airplane_engines_fill, //before { content: "\f7ca"; } + Icon_airplane_engines, //before { content: "\f7cb"; } + Icon_airplane_fill, //before { content: "\f7cc"; } + Icon_airplane, //before { content: "\f7cd"; } + Icon_alexa, //before { content: "\f7ce"; } + Icon_alipay, //before { content: "\f7cf"; } + Icon_android, //before { content: "\f7d0"; } + Icon_android2, //before { content: "\f7d1"; } + Icon_box_fill, //before { content: "\f7d2"; } + Icon_box_seam_fill, //before { content: "\f7d3"; } + Icon_browser_chrome, //before { content: "\f7d4"; } + Icon_browser_edge, //before { content: "\f7d5"; } + Icon_browser_firefox, //before { content: "\f7d6"; } + Icon_browser_safari, //before { content: "\f7d7"; } + Icon_c_circle_fill, //before { content: "\f7da"; } + Icon_c_circle, //before { content: "\f7db"; } + Icon_c_square_fill, //before { content: "\f7dc"; } + Icon_c_square, //before { content: "\f7dd"; } + Icon_capsule_pill, //before { content: "\f7de"; } + Icon_capsule, //before { content: "\f7df"; } + Icon_car_front_fill, //before { content: "\f7e0"; } + Icon_car_front, //before { content: "\f7e1"; } + Icon_cassette_fill, //before { content: "\f7e2"; } + Icon_cassette, //before { content: "\f7e3"; } + Icon_cc_circle_fill, //before { content: "\f7e6"; } + Icon_cc_circle, //before { content: "\f7e7"; } + Icon_cc_square_fill, //before { content: "\f7e8"; } + Icon_cc_square, //before { content: "\f7e9"; } + Icon_cup_hot_fill, //before { content: "\f7ea"; } + Icon_cup_hot, //before { content: "\f7eb"; } + Icon_currency_rupee, //before { content: "\f7ec"; } + Icon_dropbox, //before { content: "\f7ed"; } + Icon_escape, //before { content: "\f7ee"; } + Icon_fast_forward_btn_fill, //before { content: "\f7ef"; } + Icon_fast_forward_btn, //before { content: "\f7f0"; } + Icon_fast_forward_circle_fill, //before { content: "\f7f1"; } + Icon_fast_forward_circle, //before { content: "\f7f2"; } + Icon_fast_forward_fill, //before { content: "\f7f3"; } + Icon_fast_forward, //before { content: "\f7f4"; } + Icon_filetype_sql, //before { content: "\f7f5"; } + Icon_fire, //before { content: "\f7f6"; } + Icon_google_play, //before { content: "\f7f7"; } + Icon_h_circle_fill, //before { content: "\f7fa"; } + Icon_h_circle, //before { content: "\f7fb"; } + Icon_h_square_fill, //before { content: "\f7fc"; } + Icon_h_square, //before { content: "\f7fd"; } + Icon_indent, //before { content: "\f7fe"; } + Icon_lungs_fill, //before { content: "\f7ff"; } + Icon_lungs, //before { content: "\f800"; } + Icon_microsoft_teams, //before { content: "\f801"; } + Icon_p_circle_fill, //before { content: "\f804"; } + Icon_p_circle, //before { content: "\f805"; } + Icon_p_square_fill, //before { content: "\f806"; } + Icon_p_square, //before { content: "\f807"; } + Icon_pass_fill, //before { content: "\f808"; } + Icon_pass, //before { content: "\f809"; } + Icon_prescription, //before { content: "\f80a"; } + Icon_prescription2, //before { content: "\f80b"; } + Icon_r_circle_fill, //before { content: "\f80e"; } + Icon_r_circle, //before { content: "\f80f"; } + Icon_r_square_fill, //before { content: "\f810"; } + Icon_r_square, //before { content: "\f811"; } + Icon_repeat_1, //before { content: "\f812"; } + Icon_repeat, //before { content: "\f813"; } + Icon_rewind_btn_fill, //before { content: "\f814"; } + Icon_rewind_btn, //before { content: "\f815"; } + Icon_rewind_circle_fill, //before { content: "\f816"; } + Icon_rewind_circle, //before { content: "\f817"; } + Icon_rewind_fill, //before { content: "\f818"; } + Icon_rewind, //before { content: "\f819"; } + Icon_train_freight_front_fill, //before { content: "\f81a"; } + Icon_train_freight_front, //before { content: "\f81b"; } + Icon_train_front_fill, //before { content: "\f81c"; } + Icon_train_front, //before { content: "\f81d"; } + Icon_train_lightrail_front_fill, //before { content: "\f81e"; } + Icon_train_lightrail_front, //before { content: "\f81f"; } + Icon_truck_front_fill, //before { content: "\f820"; } + Icon_truck_front, //before { content: "\f821"; } + Icon_ubuntu, //before { content: "\f822"; } + Icon_unindent, //before { content: "\f823"; } + Icon_unity, //before { content: "\f824"; } + Icon_universal_access_circle, //before { content: "\f825"; } + Icon_universal_access, //before { content: "\f826"; } + Icon_virus, //before { content: "\f827"; } + Icon_virus2, //before { content: "\f828"; } + Icon_wechat, //before { content: "\f829"; } + Icon_yelp, //before { content: "\f82a"; } + Icon_sign_stop_fill, //before { content: "\f82b"; } + Icon_sign_stop_lights_fill, //before { content: "\f82c"; } + Icon_sign_stop_lights, //before { content: "\f82d"; } + Icon_sign_stop, //before { content: "\f82e"; } + Icon_sign_turn_left_fill, //before { content: "\f82f"; } + Icon_sign_turn_left, //before { content: "\f830"; } + Icon_sign_turn_right_fill, //before { content: "\f831"; } + Icon_sign_turn_right, //before { content: "\f832"; } + Icon_sign_turn_slight_left_fill, //before { content: "\f833"; } + Icon_sign_turn_slight_left, //before { content: "\f834"; } + Icon_sign_turn_slight_right_fill, //before { content: "\f835"; } + Icon_sign_turn_slight_right, //before { content: "\f836"; } + Icon_sign_yield_fill, //before { content: "\f837"; } + Icon_sign_yield, //before { content: "\f838"; } + Icon_ev_station_fill, //before { content: "\f839"; } + Icon_ev_station, //before { content: "\f83a"; } + Icon_fuel_pump_diesel_fill, //before { content: "\f83b"; } + Icon_fuel_pump_diesel, //before { content: "\f83c"; } + Icon_fuel_pump_fill, //before { content: "\f83d"; } + Icon_fuel_pump, //before { content: "\f83e"; } + Icon_0_circle_fill, //before { content: "\f83f"; } + Icon_0_circle, //before { content: "\f840"; } + Icon_0_square_fill, //before { content: "\f841"; } + Icon_0_square, //before { content: "\f842"; } + Icon_rocket_fill, //before { content: "\f843"; } + Icon_rocket_takeoff_fill, //before { content: "\f844"; } + Icon_rocket_takeoff, //before { content: "\f845"; } + Icon_rocket, //before { content: "\f846"; } + Icon_stripe, //before { content: "\f847"; } + Icon_subscript, //before { content: "\f848"; } + Icon_superscript, //before { content: "\f849"; } + Icon_trello, //before { content: "\f84a"; } + Icon_envelope_at_fill, //before { content: "\f84b"; } + Icon_envelope_at, //before { content: "\f84c"; } + Icon_regex, //before { content: "\f84d"; } + Icon_text_wrap, //before { content: "\f84e"; } + Icon_sign_dead_end_fill, //before { content: "\f84f"; } + Icon_sign_dead_end, //before { content: "\f850"; } + Icon_sign_do_not_enter_fill, //before { content: "\f851"; } + Icon_sign_do_not_enter, //before { content: "\f852"; } + Icon_sign_intersection_fill, //before { content: "\f853"; } + Icon_sign_intersection_side_fill, //before { content: "\f854"; } + Icon_sign_intersection_side, //before { content: "\f855"; } + Icon_sign_intersection_t_fill, //before { content: "\f856"; } + Icon_sign_intersection_t, //before { content: "\f857"; } + Icon_sign_intersection_y_fill, //before { content: "\f858"; } + Icon_sign_intersection_y, //before { content: "\f859"; } + Icon_sign_intersection, //before { content: "\f85a"; } + Icon_sign_merge_left_fill, //before { content: "\f85b"; } + Icon_sign_merge_left, //before { content: "\f85c"; } + Icon_sign_merge_right_fill, //before { content: "\f85d"; } + Icon_sign_merge_right, //before { content: "\f85e"; } + Icon_sign_no_left_turn_fill, //before { content: "\f85f"; } + Icon_sign_no_left_turn, //before { content: "\f860"; } + Icon_sign_no_parking_fill, //before { content: "\f861"; } + Icon_sign_no_parking, //before { content: "\f862"; } + Icon_sign_no_right_turn_fill, //before { content: "\f863"; } + Icon_sign_no_right_turn, //before { content: "\f864"; } + Icon_sign_railroad_fill, //before { content: "\f865"; } + Icon_sign_railroad, //before { content: "\f866"; } + Icon_building_add, //before { content: "\f867"; } + Icon_building_check, //before { content: "\f868"; } + Icon_building_dash, //before { content: "\f869"; } + Icon_building_down, //before { content: "\f86a"; } + Icon_building_exclamation, //before { content: "\f86b"; } + Icon_building_fill_add, //before { content: "\f86c"; } + Icon_building_fill_check, //before { content: "\f86d"; } + Icon_building_fill_dash, //before { content: "\f86e"; } + Icon_building_fill_down, //before { content: "\f86f"; } + Icon_building_fill_exclamation, //before { content: "\f870"; } + Icon_building_fill_gear, //before { content: "\f871"; } + Icon_building_fill_lock, //before { content: "\f872"; } + Icon_building_fill_slash, //before { content: "\f873"; } + Icon_building_fill_up, //before { content: "\f874"; } + Icon_building_fill_x, //before { content: "\f875"; } + Icon_building_fill, //before { content: "\f876"; } + Icon_building_gear, //before { content: "\f877"; } + Icon_building_lock, //before { content: "\f878"; } + Icon_building_slash, //before { content: "\f879"; } + Icon_building_up, //before { content: "\f87a"; } + Icon_building_x, //before { content: "\f87b"; } + Icon_buildings_fill, //before { content: "\f87c"; } + Icon_buildings, //before { content: "\f87d"; } + Icon_bus_front_fill, //before { content: "\f87e"; } + Icon_bus_front, //before { content: "\f87f"; } + Icon_ev_front_fill, //before { content: "\f880"; } + Icon_ev_front, //before { content: "\f881"; } + Icon_globe_americas, //before { content: "\f882"; } + Icon_globe_asia_australia, //before { content: "\f883"; } + Icon_globe_central_south_asia, //before { content: "\f884"; } + Icon_globe_europe_africa, //before { content: "\f885"; } + Icon_house_add_fill, //before { content: "\f886"; } + Icon_house_add, //before { content: "\f887"; } + Icon_house_check_fill, //before { content: "\f888"; } + Icon_house_check, //before { content: "\f889"; } + Icon_house_dash_fill, //before { content: "\f88a"; } + Icon_house_dash, //before { content: "\f88b"; } + Icon_house_down_fill, //before { content: "\f88c"; } + Icon_house_down, //before { content: "\f88d"; } + Icon_house_exclamation_fill, //before { content: "\f88e"; } + Icon_house_exclamation, //before { content: "\f88f"; } + Icon_house_gear_fill, //before { content: "\f890"; } + Icon_house_gear, //before { content: "\f891"; } + Icon_house_lock_fill, //before { content: "\f892"; } + Icon_house_lock, //before { content: "\f893"; } + Icon_house_slash_fill, //before { content: "\f894"; } + Icon_house_slash, //before { content: "\f895"; } + Icon_house_up_fill, //before { content: "\f896"; } + Icon_house_up, //before { content: "\f897"; } + Icon_house_x_fill, //before { content: "\f898"; } + Icon_house_x, //before { content: "\f899"; } + Icon_person_add, //before { content: "\f89a"; } + Icon_person_down, //before { content: "\f89b"; } + Icon_person_exclamation, //before { content: "\f89c"; } + Icon_person_fill_add, //before { content: "\f89d"; } + Icon_person_fill_check, //before { content: "\f89e"; } + Icon_person_fill_dash, //before { content: "\f89f"; } + Icon_person_fill_down, //before { content: "\f8a0"; } + Icon_person_fill_exclamation, //before { content: "\f8a1"; } + Icon_person_fill_gear, //before { content: "\f8a2"; } + Icon_person_fill_lock, //before { content: "\f8a3"; } + Icon_person_fill_slash, //before { content: "\f8a4"; } + Icon_person_fill_up, //before { content: "\f8a5"; } + Icon_person_fill_x, //before { content: "\f8a6"; } + Icon_person_gear, //before { content: "\f8a7"; } + Icon_person_lock, //before { content: "\f8a8"; } + Icon_person_slash, //before { content: "\f8a9"; } + Icon_person_up, //before { content: "\f8aa"; } + Icon_scooter, //before { content: "\f8ab"; } + Icon_taxi_front_fill, //before { content: "\f8ac"; } + Icon_taxi_front, //before { content: "\f8ad"; } + Icon_amd, //before { content: "\f8ae"; } + Icon_database_add, //before { content: "\f8af"; } + Icon_database_check, //before { content: "\f8b0"; } + Icon_database_dash, //before { content: "\f8b1"; } + Icon_database_down, //before { content: "\f8b2"; } + Icon_database_exclamation, //before { content: "\f8b3"; } + Icon_database_fill_add, //before { content: "\f8b4"; } + Icon_database_fill_check, //before { content: "\f8b5"; } + Icon_database_fill_dash, //before { content: "\f8b6"; } + Icon_database_fill_down, //before { content: "\f8b7"; } + Icon_database_fill_exclamation, //before { content: "\f8b8"; } + Icon_database_fill_gear, //before { content: "\f8b9"; } + Icon_database_fill_lock, + Icon_database_fill_slash, + Icon_database_fill_up, + Icon_database_fill_x, + Icon_database_fill, + Icon_database_gear, + Icon_database_lock, + Icon_database_slash, + Icon_database_up, + Icon_database_x, + Icon_database, + Icon_houses_fill, + Icon_houses, + Icon_nvidia, + Icon_person_vcard_fill, + Icon_person_vcard, + Icon_sina_weibo, + Icon_tencent_qq, + Icon_wikipedia; + + + public String getClassName() + { + return name().toLowerCase ().replace ( "icon_", "" ).replace ( "_", "-" ); + } + +} diff --git a/src/main/java/dev/zontreck/ariaslib/html/bootstrap/Size.java b/src/main/java/dev/zontreck/ariaslib/html/bootstrap/Size.java new file mode 100644 index 0000000..b537205 --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/html/bootstrap/Size.java @@ -0,0 +1,25 @@ +package dev.zontreck.ariaslib.html.bootstrap; + +public enum Size { + Small, + Regular, + Large, + None; + + public String sizeText() { + switch (this) { + case Small: + return "-sm"; + + case None: + return "-none"; + + case Large: + return "-lg"; + + default: + return ""; + + } + } +} diff --git a/src/main/java/dev/zontreck/ariaslib/http/HTTPMethod.java b/src/main/java/dev/zontreck/ariaslib/http/HTTPMethod.java new file mode 100644 index 0000000..162477a --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/http/HTTPMethod.java @@ -0,0 +1,9 @@ +package dev.zontreck.ariaslib.http; + +public enum HTTPMethod +{ + GET, + POST, + PUT, + DELETE +} diff --git a/src/main/java/dev/zontreck/ariaslib/http/HTTPRequest.java b/src/main/java/dev/zontreck/ariaslib/http/HTTPRequest.java new file mode 100644 index 0000000..158d92a --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/http/HTTPRequest.java @@ -0,0 +1,15 @@ +package dev.zontreck.ariaslib.http; + +public class HTTPRequest +{ + + public String url; + + public String method; + public String body; + public String contentType; + + protected HTTPRequest(){ + + } +} diff --git a/src/main/java/dev/zontreck/ariaslib/http/HTTPRequestBuilder.java b/src/main/java/dev/zontreck/ariaslib/http/HTTPRequestBuilder.java new file mode 100644 index 0000000..4e062d5 --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/http/HTTPRequestBuilder.java @@ -0,0 +1,134 @@ +package dev.zontreck.ariaslib.http; + + +import java.io.*; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; + +public class HTTPRequestBuilder +{ + + private HttpURLConnection connection; + private URL url; + private HTTPRequest request = new HTTPRequest(); + + public static HTTPRequestBuilder builder() + { + return new HTTPRequestBuilder(); + } + + protected HTTPRequestBuilder() + { + + } + + /** + * Sets the url in this request to the one supplied + * @param url The url to connect to + * @return Builder instance + * @throws MalformedURLException If the URL supplied was invalid + */ + public HTTPRequestBuilder withURL( String url) throws MalformedURLException { + request.url = url; + this.url = new URL(url); + + return this; + } + + /** + * Sets the HTTP Request method + * @param method The method you want to use + * @see HTTPMethod + * @return Builder instance + */ + public HTTPRequestBuilder withMethod(HTTPMethod method) + { + switch(method) + { + case GET: + { + request.method = "GET"; + break; + } + case POST: { + request.method = "POST"; + if(request.contentType.isEmpty()) request.contentType = "application/x-www-form-urlencoded"; + break; + } + case DELETE:{ + request.method = "DELETE"; + break; + } + case PUT:{ + request.method = "PUT"; + if(request.contentType.isEmpty()) request.contentType = "application/x-www-form-urlencoded"; + break; + } + } + + return this; + } + + /** + * Sets the request body. This may only be processed by the server when using POST or PUT, depending on the server's setup + * @param body The body to upload + * @return Builder Instance + */ + public HTTPRequestBuilder withBody(String body) + { + request.body = body; + return this; + } + + /** + * Sets the content type header + * Default: application/x-www-form-urlencoded for POST/PUT, and null/not present for GET + * @param type + * @return + */ + public HTTPRequestBuilder withContentType(String type) + { + request.contentType = type; + return this; + } + + public HTTPResponse build() + { + try { + connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod(request.method); + byte[] array = request.body.getBytes("UTF-8"); + connection.setRequestProperty("Content-Length" , "" + array.length); + connection.setRequestProperty("Content-Type", request.contentType); + connection.setDoInput(true); + connection.setUseCaches(false); + connection.setDoOutput(true); + DataOutputStream dos = new DataOutputStream(connection.getOutputStream()); + dos.write(array); + dos.flush(); + dos.close(); + + + // Get the response body + InputStream inputStream = connection.getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + StringBuilder response = new StringBuilder(); + String line; + + while ((line = reader.readLine()) != null) { + response.append(line); + } + reader.close(); + inputStream.close(); + + String responseBody = response.toString(); + + return new HTTPResponse(connection.getContentType(), connection.getResponseCode(), responseBody, request); + } catch (IOException e) { + throw new RuntimeException(e); + }finally { + connection.disconnect(); + } + } +} diff --git a/src/main/java/dev/zontreck/ariaslib/http/HTTPResponse.java b/src/main/java/dev/zontreck/ariaslib/http/HTTPResponse.java new file mode 100644 index 0000000..bd9bdfb --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/http/HTTPResponse.java @@ -0,0 +1,33 @@ +package dev.zontreck.ariaslib.http; + +public class HTTPResponse +{ + private String ContentType; + private int ResponseCode; + private String ResponseBody; + private HTTPRequest OriginalRequest; + + protected HTTPResponse(String contentType, int code, String body, HTTPRequest request){ + this.ContentType = contentType; + this.ResponseCode = code; + this.ResponseBody = body; + this.OriginalRequest = request; + + } + + public String getContentType() { + return ContentType; + } + + public int getResponseCode() { + return ResponseCode; + } + + public String getResponseBody() { + return ResponseBody; + } + + public HTTPRequest getOriginalRequest() { + return OriginalRequest; + } +} diff --git a/src/main/java/dev/zontreck/ariaslib/json/Completed.java b/src/main/java/dev/zontreck/ariaslib/json/Completed.java new file mode 100644 index 0000000..c55b0b4 --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/json/Completed.java @@ -0,0 +1,15 @@ +package dev.zontreck.ariaslib.json; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Serialization or Deserialization has completed. + * + * The method takes 1 argument. + * + * Boolean: True for deserialization. + */ +@Retention ( RetentionPolicy.RUNTIME ) +public @interface Completed { +} diff --git a/src/main/java/dev/zontreck/ariaslib/json/DynSerial.java b/src/main/java/dev/zontreck/ariaslib/json/DynSerial.java new file mode 100644 index 0000000..ac00eaa --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/json/DynSerial.java @@ -0,0 +1,11 @@ +package dev.zontreck.ariaslib.json; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Used on a class to indicate that it is serializable by the dynamic serializer. + */ +@Retention ( RetentionPolicy.RUNTIME ) +public @interface DynSerial { +} diff --git a/src/main/java/dev/zontreck/ariaslib/json/DynamicDeserializer.java b/src/main/java/dev/zontreck/ariaslib/json/DynamicDeserializer.java new file mode 100644 index 0000000..2315866 --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/json/DynamicDeserializer.java @@ -0,0 +1,119 @@ +package dev.zontreck.ariaslib.json; + +import java.io.ByteArrayInputStream; +import java.lang.reflect.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Deserializes objects! + *

+ * YOU MUST HAVE A NO-PARAMETER CONSTRUCTOR + */ +public class DynamicDeserializer { + /** + * Constructs and deserializes an object from serialized data + */ + public static T doDeserialize ( Class clazz , byte[] data ) throws Exception { + ByteArrayInputStream BAIS = new ByteArrayInputStream ( data ); + return deserialize ( ( Map ) JsonObject.parseJSON ( BAIS ).getMap ( ) , clazz ); + } + + private static T deserialize ( Map map , Class clazz ) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { + if ( ! clazz.isAnnotationPresent ( DynSerial.class ) ) + return null; + + T object = clazz.getDeclaredConstructor ( ).newInstance ( ); + + Field[] fields = clazz.getDeclaredFields ( ); + for ( Field field : fields ) { + field.setAccessible ( true ); + + if ( field.isAnnotationPresent ( IgnoreSerialization.class ) ) + continue; + + try { + if ( List.class.isAssignableFrom ( field.getType ( ) ) ) { + Class listType = getListType ( field ); + List list = new ArrayList<> ( ); + List serializedList = ( List ) map.get ( field.getName ( ) ); + if ( serializedList != null ) { + for ( Object listItem : serializedList ) { + if ( listType.isAnnotationPresent ( DynSerial.class ) ) { + Object deserializedItem = deserialize ( ( Map ) listItem , listType ); + list.add ( deserializedItem ); + } + else { + list.add ( listItem ); + } + } + } + field.set ( object , list ); + } + else if ( Map.class.isAssignableFrom ( field.getType ( ) ) ) { + Class valueType = getMapValueType ( field ); + Map serializedMap = ( Map ) map.get ( field.getName ( ) ); + if ( serializedMap != null ) { + Map mapValue = new HashMap<> ( ); + for ( Map.Entry entry : serializedMap.entrySet ( ) ) { + Object deserializedValue; + if ( valueType.isAnnotationPresent ( DynSerial.class ) ) { + deserializedValue = deserialize ( ( Map ) entry.getValue ( ) , valueType ); + } + else { + deserializedValue = entry.getValue ( ); + } + mapValue.put ( entry.getKey ( ) , deserializedValue ); + } + field.set ( object , mapValue ); + } + } + else if ( ! field.getType ( ).isAnnotationPresent ( DynSerial.class ) ) { + field.set ( object , map.get ( field.getName ( ) ) ); + } + else { + Object tmp = deserialize ( ( Map ) map.get ( field.getName ( ) ) , field.getType ( ) ); + field.set ( object , tmp ); + } + } catch ( Exception e ) { + // Handle any exceptions during deserialization + } + } + + Method[] methods = clazz.getDeclaredMethods ( ); + for ( Method method : methods ) { + if ( method.isAnnotationPresent ( Completed.class ) ) { + method.invoke ( object , true ); + } + } + + return object; + } + + + private static Class getListType ( Field field ) { + Type genericType = field.getGenericType ( ); + if ( genericType instanceof ParameterizedType ) { + ParameterizedType paramType = ( ParameterizedType ) genericType; + Type[] actualTypeArgs = paramType.getActualTypeArguments ( ); + if ( actualTypeArgs.length > 0 ) { + return ( Class ) actualTypeArgs[ 0 ]; + } + } + return Object.class; + } + + private static Class getMapValueType ( Field field ) { + Type genericType = field.getGenericType ( ); + if ( genericType instanceof ParameterizedType ) { + ParameterizedType paramType = ( ParameterizedType ) genericType; + Type[] actualTypeArgs = paramType.getActualTypeArguments ( ); + if ( actualTypeArgs.length > 1 ) { + return ( Class ) actualTypeArgs[ 1 ]; + } + } + return Object.class; + } +} diff --git a/src/main/java/dev/zontreck/ariaslib/json/DynamicSerializer.java b/src/main/java/dev/zontreck/ariaslib/json/DynamicSerializer.java new file mode 100644 index 0000000..1d93591 --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/json/DynamicSerializer.java @@ -0,0 +1,115 @@ +package dev.zontreck.ariaslib.json; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class DynamicSerializer { + /** + * Serializes the object instance + * + * @param inst The class object to serialize + * @return A byte array of serialized data + */ + public static byte[] doSerialize(Object inst) throws InvocationTargetException, IllegalAccessException { + Map ret = serialize(inst); + + JsonObject js = new JsonObject(ret); + + return js.toJSONString().getBytes(); + } + + private static Map serialize(Object inst) throws InvocationTargetException, IllegalAccessException { + Class clazz = inst.getClass(); + if (!clazz.isAnnotationPresent(DynSerial.class)) + return null; + Method[] mth = clazz.getDeclaredMethods(); + Method onComplete = null; + for ( + Method mt : + mth + ) { + if (mt.isAnnotationPresent(PreSerialize.class)) { + mt.invoke(inst); + } + + if (mt.isAnnotationPresent(Completed.class)) + onComplete = mt; + } + + Field[] fields = clazz.getDeclaredFields(); + Map ret = new HashMap<>(); + for ( + Field field : + fields + ) { + field.setAccessible(true); + if (field.isAnnotationPresent(IgnoreSerialization.class)) + continue; + + Object fieldVal = field.get(inst); + if (fieldVal == null) continue; + + String fieldName = field.getName(); + + if (field.isAnnotationPresent(ListOrMap.class)) { + // Special handling for List and Map types + ret.put(fieldName, serializeCollectionOrMap(fieldVal)); + continue; + } + if (!(fieldVal.getClass().isAnnotationPresent(DynSerial.class))) { + // Special handler for List and Map is needed right here. + if (fieldVal instanceof List || fieldVal instanceof Map) continue; + + ret.put(fieldName, fieldVal); + } else { + Map TMP = serialize(fieldVal); + ret.put(fieldName, TMP); + } + } + + + if (onComplete != null) + onComplete.invoke(inst, false); + + return ret; + + + } + + @SuppressWarnings("unchecked") + private static Object serializeCollectionOrMap(Object collectionOrMap) throws InvocationTargetException, IllegalAccessException { + if (collectionOrMap instanceof List) { + List list = (List) collectionOrMap; + List serializedList = new ArrayList<>(); + for (Object item : list) { + if (item.getClass().isAnnotationPresent(DynSerial.class)) { + serializedList.add(serialize(item)); + } else { + serializedList.add(item); + } + } + return serializedList; + } else if (collectionOrMap instanceof Map) { + Map mp = (Map) collectionOrMap; + Map map = (Map) mp; + Map serializedMap = new HashMap<>(); + for (Map.Entry entry : map.entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + if (value.getClass().isAnnotationPresent(DynSerial.class)) { + value = serialize(value); + } + serializedMap.put(key, value); + } + return serializedMap; + } + return collectionOrMap; + } + + +} diff --git a/src/main/java/dev/zontreck/ariaslib/json/IgnoreSerialization.java b/src/main/java/dev/zontreck/ariaslib/json/IgnoreSerialization.java new file mode 100644 index 0000000..aef019d --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/json/IgnoreSerialization.java @@ -0,0 +1,12 @@ +package dev.zontreck.ariaslib.json; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Marks an element to be ignored completely by the serializer or deserializer. + */ +@Retention ( RetentionPolicy.RUNTIME ) +public @interface IgnoreSerialization +{ +} diff --git a/src/main/java/dev/zontreck/ariaslib/json/JsonObject.java b/src/main/java/dev/zontreck/ariaslib/json/JsonObject.java new file mode 100644 index 0000000..df78a2d --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/json/JsonObject.java @@ -0,0 +1,183 @@ +package dev.zontreck.ariaslib.json; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class JsonObject { + private Map data; + + public JsonObject() { + data = new HashMap<>(); + } + + public JsonObject(Map dat) { + data = new HashMap<>(dat); + } + + public static JsonObject parseJSON(InputStream inputStream) throws IOException { + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + StringBuilder jsonString = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + jsonString.append(line); + } + return parseJsonObject(jsonString.toString()); + } + + private static JsonObject parseJsonObject(String jsonString) { + JsonObject jsonObject = new JsonObject(); + jsonString = jsonString.trim(); + if (jsonString.startsWith("{") && jsonString.endsWith("}")) { + jsonString = jsonString.substring(1, jsonString.length() - 1); + String[] keyValuePairs = jsonString.split(","); + for (String pair : keyValuePairs) { + String[] keyValue = pair.split(":"); + if (keyValue.length == 2) { + String key = keyValue[0].trim().replace("\"", ""); + String value = keyValue[1].trim(); + jsonObject.put(key, parseValue(value)); + } + } + } + return jsonObject; + } + + private static Object parseValue(String value) { + if (value.startsWith("{") && value.endsWith("}")) { + return parseJsonObject(value); + } else if (value.startsWith("[") && value.endsWith("]")) { + return parseJSONArray(value); + } else if (value.startsWith("\"") && value.endsWith("\"")) { + return value.substring(1, value.length() - 1); + } else if (value.equalsIgnoreCase("true")) { + return true; + } else if (value.equalsIgnoreCase("false")) { + return false; + } else if (value.equalsIgnoreCase("null")) { + return null; + } else { + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + try { + return Double.parseDouble(value); + } catch (NumberFormatException ex) { + return value; + } + } + } + } + + private static List parseJSONArray(String jsonArray) { + List list = new ArrayList<>(); + jsonArray = jsonArray.trim(); + if (jsonArray.startsWith("[") && jsonArray.endsWith("]")) { + jsonArray = jsonArray.substring(1, jsonArray.length() - 1); + String[] elements = jsonArray.split(","); + for (String element : elements) { + list.add(parseValue(element.trim())); + } + } + return list; + } + + public void put(String key, Object value) { + data.put(key, value); + } + + public Object get(String key) { + return data.get(key); + } + + public void merge(Map ret) { + data.putAll(ret); + } + + public Map getMap() { + return new HashMap<>(data); + } + + public void add(String key, Object value) { + if (data.containsKey(key)) { + Object existingValue = data.get(key); + if (existingValue instanceof List) { + ((List) existingValue).add(value); + } else { + List list = new ArrayList<>(); + list.add(existingValue); + list.add(value); + data.put(key, list); + } + } else { + data.put(key, value); + } + } + + public String toJSONString() { + StringBuilder sb = new StringBuilder(); + sb.append("{"); + + boolean first = true; + for (Map.Entry entry : data.entrySet()) { + if (!first) { + sb.append(","); + } + first = false; + + sb.append("\""); + sb.append(escape(entry.getKey())); + sb.append("\":"); + sb.append(toJSONValue(entry.getValue())); + } + + sb.append("}"); + return sb.toString(); + } + + private String escape(String str) { + if (str == null) return ""; + // Add necessary escape characters (e.g., double quotes, backslashes) + // You can implement this method based on your specific requirements. + // This is a simplified version for demonstration purposes. + return str.replace("\"", "\\\""); + } + + private String toJSONValue(Object value) { + if (value instanceof String) { + return "\"" + escape(value.toString()) + "\""; + } else if (value instanceof JsonObject) { + return ((JsonObject) value).toJSONString(); + } else if (value instanceof List) { + return toJSONList((List) value); + } else if (value instanceof Map) { + return new JsonObject((Map) ((Map) value)).toJSONString(); + } else { + return value.toString(); + } + } + + private String toJSONList(List list) { + StringBuilder sb = new StringBuilder(); + sb.append("["); + + boolean first = true; + for (Object item : list) { + if (!first) { + sb.append(","); + } + first = false; + + sb.append(toJSONValue(item)); + } + + sb.append("]"); + return sb.toString(); + } +} + diff --git a/src/main/java/dev/zontreck/ariaslib/json/ListOrMap.java b/src/main/java/dev/zontreck/ariaslib/json/ListOrMap.java new file mode 100644 index 0000000..ceb9895 --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/json/ListOrMap.java @@ -0,0 +1,8 @@ +package dev.zontreck.ariaslib.json; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention ( RetentionPolicy.RUNTIME ) +public @interface ListOrMap { +} diff --git a/src/main/java/dev/zontreck/ariaslib/json/PreSerialize.java b/src/main/java/dev/zontreck/ariaslib/json/PreSerialize.java new file mode 100644 index 0000000..7562bae --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/json/PreSerialize.java @@ -0,0 +1,13 @@ +package dev.zontreck.ariaslib.json; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * To be set on a method, and will invoke that method prior to serialization beginning. + * + * Preparations should be made here + */ +@Retention ( RetentionPolicy.RUNTIME ) +public @interface PreSerialize { +} diff --git a/src/main/java/dev/zontreck/ariaslib/terminal/Banners.java b/src/main/java/dev/zontreck/ariaslib/terminal/Banners.java new file mode 100644 index 0000000..bf28d89 --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/terminal/Banners.java @@ -0,0 +1,47 @@ +package dev.zontreck.ariaslib.terminal; + +import java.util.ArrayList; +import java.util.List; + +public class Banners +{ + + public static String generateBanner(String text) { + int maxLength = calculateMaxLength(text); + List bannerLines = new ArrayList<>(); + StringBuilder border = new StringBuilder(); + for (int i = 0; i < maxLength + 4; i++) { + border.append("*"); + } + bannerLines.add(border.toString()); + bannerLines.add("* " + centerText(text, maxLength) + " *"); + bannerLines.add(border.toString()); + return String.join("\n", bannerLines); + } + + private static String centerText(String text, int maxLength) { + StringBuilder centeredText = new StringBuilder(); + int spacesToAdd = (maxLength - text.length()) / 2; + for (int i = 0; i < spacesToAdd; i++) { + centeredText.append(" "); + } + centeredText.append(text); + for (int i = 0; i < spacesToAdd; i++) { + centeredText.append(" "); + } + if (centeredText.length() < maxLength) { + centeredText.append(" "); + } + return centeredText.toString(); + } + + private static int calculateMaxLength(String text) { + int maxLength = 0; + for (String line : text.split("\n")) { + if (line.length() > maxLength) { + maxLength = line.length(); + } + } + return maxLength; + } +} diff --git a/src/main/java/dev/zontreck/ariaslib/terminal/Task.java b/src/main/java/dev/zontreck/ariaslib/terminal/Task.java new file mode 100644 index 0000000..6f7efe0 --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/terminal/Task.java @@ -0,0 +1,88 @@ +package dev.zontreck.ariaslib.terminal; + +import dev.zontreck.ariaslib.util.DelayedExecutorService; +import dev.zontreck.ariaslib.util.EnvironmentUtils; +import dev.zontreck.ariaslib.util.Progress; + +import java.util.TimerTask; + +public abstract class Task extends TimerTask implements Runnable { + public final String TASK_NAME; + private TaskCompletionToken token = new TaskCompletionToken ( ); + + public static final String CHECK = "P"; + public static final String FAIL = "F"; + // Else use the progress spinner from the Progress class + private boolean isSilent = false; + + public Task ( String name ) { + TASK_NAME = name; + } + + /** + * This constructor is meant to be used to create silent tasks that do not output to the console. (Example usage: DelayedExecutionService) + * + * @param name Task name + * @param silent Whether to print to the terminal + */ + public Task ( String name , boolean silent ) { + this ( name ); + isSilent = silent; + } + + + public boolean isComplete ( ) { + return token.get ( ); + } + + public void startTask ( ) { + Thread tx = new Thread(this); + tx.start(); + + if(! isSilent && !EnvironmentUtils.isRunningInsideDocker()) + { + Thread tx2 = new Thread(new SpinnerTask(token, this)); + tx2.start(); + } + } + + public void stopTask ( ) { + if ( token.get ( ) && ! isSilent ) { + System.out.printf ( "\r" + TASK_NAME + "\t\t[" + token.status + "]\n" ); + } + } + + public void setSuccess ( ) { + token.completed ( CHECK ); + } + + public void setFail ( ) { + token.completed ( FAIL ); + } + + public class SpinnerTask extends Task { + public final Task task; + public final TaskCompletionToken token; + private final Progress spinner = new Progress ( 100 ); + + public SpinnerTask ( TaskCompletionToken token , Task parent ) { + super ( "spinner" , true ); + this.token = token; + this.task = parent; + } + + @Override + public void run ( ) { + while ( ! task.isComplete ( ) ) { + try { + Thread.sleep ( 50L ); + + if ( ! task.isSilent && ! task.isComplete ( ) && ! EnvironmentUtils.isRunningInsideDocker ( ) ) + System.out.printf ( "\r" + task.TASK_NAME + "\t\t" + spinner.getSpinnerTick ( ) + "\r" ); + } catch ( Exception e ) { + e.printStackTrace ( ); + } + } + } + } +} \ No newline at end of file diff --git a/src/main/java/dev/zontreck/ariaslib/terminal/TaskCompletionToken.java b/src/main/java/dev/zontreck/ariaslib/terminal/TaskCompletionToken.java new file mode 100644 index 0000000..37457f7 --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/terminal/TaskCompletionToken.java @@ -0,0 +1,20 @@ +package dev.zontreck.ariaslib.terminal; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Should not be re-used for multiple tasks!!! + */ +public class TaskCompletionToken +{ + private AtomicBoolean complete = new AtomicBoolean(false); + public String status = ""; + public void completed(String reason){ + status=reason; + complete.set(true); + } + + public boolean get(){ + return complete.get(); + } +} diff --git a/src/main/java/dev/zontreck/ariaslib/terminal/Terminal.java b/src/main/java/dev/zontreck/ariaslib/terminal/Terminal.java new file mode 100644 index 0000000..eed0500 --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/terminal/Terminal.java @@ -0,0 +1,35 @@ +package dev.zontreck.ariaslib.terminal; + +import dev.zontreck.ariaslib.util.EnvironmentUtils; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +public class Terminal { + private static final AtomicInteger ID = new AtomicInteger ( 0 ); + private static final AtomicBoolean running = new AtomicBoolean ( true ); + public static String PREFIX = ""; + + /** + * This starts a terminal instance + * + * @return The terminal ID + */ + public static int startTerminal ( ) { + if ( EnvironmentUtils.isRunningInsideDocker ( ) ) + return 0; + running.set ( true ); + //DelayedExecutorService.getInstance ( ).schedule ( new ConsolePrompt ( ) , 1 ); + + + return ID.getAndIncrement ( ); + } + + public static boolean isRunning ( ) { + return running.get ( ); + } + + public static void setRunning ( boolean running ) { + Terminal.running.set ( running ); + } +} diff --git a/src/main/java/dev/zontreck/ariaslib/util/DelayedExecutorService.java b/src/main/java/dev/zontreck/ariaslib/util/DelayedExecutorService.java new file mode 100644 index 0000000..d18b493 --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/util/DelayedExecutorService.java @@ -0,0 +1,17 @@ +package dev.zontreck.ariaslib.util; + +import java.time.Instant; +import java.util.*; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import dev.zontreck.ariaslib.terminal.Task; +import dev.zontreck.ariaslib.terminal.Terminal; + +@Deprecated +public class DelayedExecutorService { + +} diff --git a/src/main/java/dev/zontreck/ariaslib/util/EnvironmentUtils.java b/src/main/java/dev/zontreck/ariaslib/util/EnvironmentUtils.java new file mode 100644 index 0000000..fa729bf --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/util/EnvironmentUtils.java @@ -0,0 +1,19 @@ +package dev.zontreck.ariaslib.util; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.stream.Stream; + +public class EnvironmentUtils { + public static boolean isRunningInsideDocker ( ) { + if ( Files.exists ( Paths.get ( "/.dockerenv" ) ) ) + return true; + try { + Stream str = Files.lines ( Paths.get ( "/proc/1/cgroup" ) ); + return str.anyMatch ( ln -> ln.contains ( "/docker" ) ); + } catch ( IOException e ) { + return false; + } + } +} diff --git a/src/main/java/dev/zontreck/ariaslib/util/FileIO.java b/src/main/java/dev/zontreck/ariaslib/util/FileIO.java new file mode 100644 index 0000000..a0b8184 --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/util/FileIO.java @@ -0,0 +1,51 @@ +package dev.zontreck.ariaslib.util; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + +public class FileIO +{ + + public static String readFile(String filePath) { + try { + byte[] fileBytes = Files.readAllBytes(Paths.get(filePath)); + return new String(fileBytes); + } catch (IOException e) { + return "An error occurred: " + e.getMessage(); + } + } + public static void writeFile(String filePath, String newContent) { + try { + Files.write(Paths.get(filePath), newContent.getBytes()); + } catch (IOException e) { + } + } + + + /** + * Recursively delete a directory + * @param directory The folder to delete + */ + public static void deleteDirectory(File directory) { + if (directory.exists()) { + File[] files = directory.listFiles(); + if (files != null) { + for (File file : files) { + if (file.isDirectory()) { + deleteDirectory(file); + } else { + file.delete(); + } + } + } + // Now directory is empty, so delete it + directory.delete(); + System.out.println("Directory deleted: " + directory.getAbsolutePath()); + } else { + System.out.println("Directory does not exist: " + directory.getAbsolutePath()); + } + } +} + diff --git a/src/main/java/dev/zontreck/ariaslib/util/Hashing.java b/src/main/java/dev/zontreck/ariaslib/util/Hashing.java new file mode 100644 index 0000000..78cd5e0 --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/util/Hashing.java @@ -0,0 +1,120 @@ +package dev.zontreck.ariaslib.util; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public class Hashing +{ + + + /** + * A md5 hashing function that is compatible with literally every other hashing function out there + * @param input The string to hash + * @return The hash + */ + public static String md5(String input) { + try { + MessageDigest md = MessageDigest.getInstance("MD5"); + md.update(input.getBytes()); + + byte[] byteData = md.digest(); + + // Convert the byte array to a hexadecimal string + StringBuilder hexString = new StringBuilder(); + for (byte aByteData : byteData) { + String hex = Integer.toHexString(0xff & aByteData); + if (hex.length() == 1) { + hexString.append('0'); + } + hexString.append(hex); + } + return hexString.toString(); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + return null; + } + } + /** + * A md5 hashing function that is compatible with literally every other hashing function out there + * @param input The bytes to hash + * @return The hash + */ + public static String md5(byte[] input) { + try { + MessageDigest md = MessageDigest.getInstance("MD5"); + md.update(input); + + byte[] byteData = md.digest(); + + // Convert the byte array to a hexadecimal string + StringBuilder hexString = new StringBuilder(); + for (byte aByteData : byteData) { + String hex = Integer.toHexString(0xff & aByteData); + if (hex.length() == 1) { + hexString.append('0'); + } + hexString.append(hex); + } + return hexString.toString(); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + return null; + } + } + + + /** + * A sha256 hashing function that is compatible with literally every other hashing function out there + * @param input The string to hash + * @return The hash + */ + public static String sha256(String input) { + try { + MessageDigest md = MessageDigest.getInstance("SHA256"); + md.update(input.getBytes()); + + byte[] byteData = md.digest(); + + // Convert the byte array to a hexadecimal string + StringBuilder hexString = new StringBuilder(); + for (byte aByteData : byteData) { + String hex = Integer.toHexString(0xff & aByteData); + if (hex.length() == 1) { + hexString.append('0'); + } + hexString.append(hex); + } + return hexString.toString(); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + return null; + } + } + /** + * A sha256 hashing function that is compatible with literally every other hashing function out there + * @param input The bytes to hash + * @return The hash + */ + public static String sha256(byte[] input) { + try { + MessageDigest md = MessageDigest.getInstance("SHA256"); + md.update(input); + + byte[] byteData = md.digest(); + + // Convert the byte array to a hexadecimal string + StringBuilder hexString = new StringBuilder(); + for (byte aByteData : byteData) { + String hex = Integer.toHexString(0xff & aByteData); + if (hex.length() == 1) { + hexString.append('0'); + } + hexString.append(hex); + } + return hexString.toString(); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + return null; + } + } +} diff --git a/src/main/java/dev/zontreck/ariaslib/util/Lists.java b/src/main/java/dev/zontreck/ariaslib/util/Lists.java new file mode 100644 index 0000000..e243545 --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/util/Lists.java @@ -0,0 +1,94 @@ +package dev.zontreck.ariaslib.util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class Lists +{ + /** + * Programatically constructs a list + * @param values The list of values + * @return The new list + * @param An arbitrary type parameter + */ + public static List of(T... values) + { + List arr = new ArrayList<>(); + for(T value : values) + { + arr.add(value); + } + + return arr; + } + + + /** + * Splits a string into a list + * @param input The string to split + * @param delimiters The list of delimiters + * @return A non-strided list + */ + public static List split(String input, String... delimiters) { + List result = new ArrayList<>(); + StringBuilder regex = new StringBuilder("("); + + // Constructing the regular expression pattern with provided delimiters + for (String delimiter : delimiters) { + regex.append(delimiter).append("|"); + } + regex.deleteCharAt(regex.length() - 1); // Remove the extra '|' character + regex.append(")"); + + String[] tokens = input.split(regex.toString()); + + // Add non-empty tokens to the result list + for (String token : tokens) { + if (!token.isEmpty()) { + result.add(token); + } + } + + return result; + } + + /** + * Split a string, and keep the delimiters + * @param input The string to be parsed and split + * @param delimiters A list of delimiters + * @return A strided list containing the parsed options, and the delimiters + */ + public static List splitWithDelim(String input, String... delimiters) { + List result = new ArrayList<>(); + StringBuilder regex = new StringBuilder("("); + + // Constructing the regular expression pattern with provided delimiters + for (String delimiter : delimiters) { + regex.append(delimiter).append("|"); + } + regex.deleteCharAt(regex.length() - 1); // Remove the extra '|' character + regex.append(")"); + + // Splitting the input string using the regex pattern + String[] tokens = input.split(regex.toString()); + + // Adding tokens and delimiters to the result list in a strided manner + for (int i = 0; i < tokens.length; i++) { + if (!tokens[i].isEmpty()) { + result.add(tokens[i]); + } + // Adding delimiter if it exists and it's not the last token + if (i < tokens.length - 1) { + result.add(input.substring(input.indexOf(tokens[i]) + tokens[i].length(), input.indexOf(tokens[i + 1]))); + } + } + + return result; + } + + + private Lists(){ + + } +} diff --git a/src/main/java/dev/zontreck/ariaslib/util/Maps.java b/src/main/java/dev/zontreck/ariaslib/util/Maps.java new file mode 100644 index 0000000..ed063be --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/util/Maps.java @@ -0,0 +1,49 @@ +package dev.zontreck.ariaslib.util; + +import java.util.HashMap; +import java.util.Map; + +/** + * Utility class to assist in creating a dictionary programmatically in one line of code. + */ +public class Maps +{ + /** + * This takes a list of entries and returns a HashMap + * @param entries The entries you want in your hashmap + * @return The map itself + * @param Any typed parameter + * @param Any typed parameter + */ + public static Map of(Entry... entries) { + Map map = new HashMap<>(); + for(Entry E : entries) + { + map.put(E.key, E.value); + } + + return map; + } + + /** + * A virtual entry used only by the Maps#of function. + * @see Maps#of(Entry[]) + * @param Any typed parameter + * @param Any typed parameter + */ + public static class Entry { + public final A key; + public final B value; + + /** + * Initializes the readonly entry + * @param a The dictionary key + * @param b The value + */ + public Entry(A a, B b) + { + this.key=a; + this.value=b; + } + } +} diff --git a/src/main/java/dev/zontreck/ariaslib/util/MathUtil.java b/src/main/java/dev/zontreck/ariaslib/util/MathUtil.java new file mode 100644 index 0000000..63df89f --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/util/MathUtil.java @@ -0,0 +1,18 @@ +package dev.zontreck.ariaslib.util; + +/** + * This class will be used to house math helper functions + */ +public class MathUtil +{ + /** + * A newer helper function to get the percentage with large number support + * @param current Min value + * @param max Maximum value for progress + * @return Percentage + */ + public static int getPercent(long current, long max) + { + return Math.round(current*100/max); + } +} diff --git a/src/main/java/dev/zontreck/ariaslib/util/Percent.java b/src/main/java/dev/zontreck/ariaslib/util/Percent.java new file mode 100644 index 0000000..3b51a2b --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/util/Percent.java @@ -0,0 +1,23 @@ +package dev.zontreck.ariaslib.util; + +import java.io.PrintStream; + +public class Percent +{ + int current; + int maximum; + + public Percent(int cur, int max) + { + current=cur; + maximum=max; + } + + + public int get() + { + return ((current * 100) / maximum); + } + + +} diff --git a/src/main/java/dev/zontreck/ariaslib/util/Progress.java b/src/main/java/dev/zontreck/ariaslib/util/Progress.java new file mode 100644 index 0000000..a0d6670 --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/util/Progress.java @@ -0,0 +1,56 @@ +package dev.zontreck.ariaslib.util; + +import java.util.concurrent.atomic.AtomicInteger; + +public class Progress +{ + private int maximum; + private int current; + private AtomicInteger tickNum = new AtomicInteger(0); + private static final String TICKS="-\\|/"; + + public String getSpinnerTick() + { + if(tickNum.get()>=TICKS.length()) tickNum.set(0); + + return "[" + TICKS.substring(tickNum.getAndIncrement(), tickNum.get()) + "]"; + } + + public Progress(int maximum) + { + current=0; + this.maximum=maximum; + } + + public int getPercent(){ + return (current*100/maximum); + } + + public String getPercentStr() + { + return (getPercent()+"%"); + } + + public static int getPercentOf(int current, int max) + { + return (current*100/max); + } + + public void increment(){ + current++; + sanity(); + } + private void sanity(){ + if(current > maximum) current = maximum; + if(current < 0)current = 0; + } + public void decrement(){ + current--; + sanity(); + } + + public void setCurrent(int cur) + { + current=cur; + } +} diff --git a/src/main/java/dev/zontreck/ariaslib/util/ProgressBar.java b/src/main/java/dev/zontreck/ariaslib/util/ProgressBar.java new file mode 100644 index 0000000..fc7da66 --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/util/ProgressBar.java @@ -0,0 +1,66 @@ +package dev.zontreck.ariaslib.util; + +import java.io.PrintStream; + +/** + * Utility to create an ascii progress bar + */ +public class ProgressBar +{ + + private static final int DEFAULT_BAR_WIDTH = 50; + + /** + * Reserved spaces for the brackets, and the carrot, and the percent value. + */ + private static final int PROGRESS_BAR_RESERVED=5; + + /** + * Always will return 80 + * @return 80 + */ + private static int getConsoleWidth() { + return 80; // Default console width, can be adjusted for different consoles + } + + /** + * Build a progress bar + *

+ * your text here [========= ] 40% your text here + * @param percent The percentage + * @param beforeText + * @param afterText + * @return ProgressBar as a String + */ + public static String printProgressBar(int percent, String beforeText, String afterText) { + StringBuilder sb = new StringBuilder(); + int consoleWidth = getConsoleWidth(); + int barWidth = Math.min(consoleWidth - beforeText.length() - afterText.length() - PROGRESS_BAR_RESERVED, DEFAULT_BAR_WIDTH); + + // Calculate progress + int progressBarLength = (int) ((double) percent / 100 * barWidth); + + // Print before text + sb.append(beforeText); + + // Print progress bar + sb.append("["); + for (int i = 0; i < barWidth; i++) { + if (i < progressBarLength) { + sb.append("="); + }else if(i==progressBarLength) sb.append(">"); + else { + sb.append(" "); + } + } + sb.append("]"); + + // Print percentage + sb.append(" " + percent + "%"); + + // Print after text + sb.append(afterText); + + return sb.toString(); + } +} diff --git a/src/main/java/dev/zontreck/ariaslib/util/TimeNotation.java b/src/main/java/dev/zontreck/ariaslib/util/TimeNotation.java new file mode 100644 index 0000000..b3d04b9 --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/util/TimeNotation.java @@ -0,0 +1,161 @@ +package dev.zontreck.ariaslib.util; + +import java.util.List; + +/** + * Contains useful structures and functions for dealing with, and manipulation of, time. + */ +public class TimeNotation +{ + public int Years; + public int Months; + public int Weeks; + public int Days; + public int Hours; + public int Minutes; + public int Seconds; + + public TimeNotation(int years, int months, int weeks, int days, int hours, int minutes, int seconds) + { + Years=years; + Months=months; + Weeks=weeks; + Days=days; + Hours=hours; + Minutes=minutes; + Seconds = seconds; + } + + private TimeNotation(){} + + + @Override + public String toString() { + String str = + someOrNone(Years,Pluralize(Years, "year") + ", ") + + someOrNone(Months, Pluralize(Months, "month") + ", ") + + someOrNone(Weeks, Pluralize(Weeks, "week") + ", ") + + someOrNone(Days, Pluralize(Days, "day") + ", ") + + someOrNone(Hours, Pluralize(Hours, "hour") + ", ") + + someOrNone(Minutes, Pluralize(Minutes, "minute") + ", ") + + someOrNone(Seconds, Pluralize(Seconds, "second")); + + if(str == ""){ + return "No Seconds"; + } else return str; + } + + /** + * Create a plural version for a number + * @param num The number to prefix + * @param str The singular form of the string + * @return Combined string, num + str in plural form if necessary + */ + private String Pluralize(int num, String str) + { + return num + " " + ((num > 1) ? str+"s" : str); + } + + /** + * A simple function to test a number, return a string, or return nothing at all. + * @param num The number to check + * @param str The string to return if the number is greater than zero + * @return Str if num >1, or empty string + */ + private String someOrNone(int num, String str) + { + if(num > 0) return str; + else return ""; + } + /** + * A simple function to test a number, return a string, or return something else. + * @param num The number to check + * @param str The string to return if the number is greater than zero + * @return Str if num >1, or other string + */ + private String someOrOther(int num, String str, String other) + { + if(num > 0) return str; + else return other; + } + + /** + * Encodes time notation! + * @return A program readable string that can be decoded back to a time notation + */ + public String toNotation() + { + return Years + "Y" + Months + "M" + Weeks + "W" + Days + "d" + Hours + "h" + Minutes + "m" + Seconds + "s"; + } + + /** + * Parses a time notation string + * @param notation Serialized time notation + * @return The deserialized time notation object + */ + public static TimeNotation fromNotation(String notation) + { + TimeNotation notationX = new TimeNotation(); + String[] delims = new String[]{"Y", "M", "W", "d", "h", "m", "s"}; + List opts = Lists.split(notation, delims); + + + int index = 0; + for(String dlim : delims) + { + if(notation.contains(dlim)) + { + switch (dlim) + { + case "Y": + { + notationX.Years = Integer.parseInt(opts.get(index)); + + break; + } + case "M": + { + notationX.Months = Integer.parseInt(opts.get(index)); + + break; + } + case "W": + { + notationX.Weeks = Integer.parseInt(opts.get(index)); + + break; + } + case "d": + { + notationX.Days = Integer.parseInt(opts.get(index)); + + break; + } + case "h": + { + notationX.Hours = Integer.parseInt(opts.get(index)); + + break; + } + case "m": + { + notationX.Minutes = Integer.parseInt(opts.get(index)); + + break; + } + case "s": + { + notationX.Seconds = Integer.parseInt(opts.get(index)); + + break; + } + } + + index++; + } + } + + return notationX; + + } +} diff --git a/src/main/java/dev/zontreck/ariaslib/util/TimeUtil.java b/src/main/java/dev/zontreck/ariaslib/util/TimeUtil.java new file mode 100644 index 0000000..d8f5b8e --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/util/TimeUtil.java @@ -0,0 +1,96 @@ +package dev.zontreck.ariaslib.util; + +/** + * This class is a helper with some minecraft specific functions + */ +public class TimeUtil +{ + /** + * Converts seconds to ticks. (seconds*ticks) + * @param seconds Number of seconds + * @param ticks Number of ticks in a single second + * @return Number of ticks + */ + public static int secondsToTicks(int seconds, int ticks) + { + return seconds*ticks; + } + + /** + * Converts the number of ticks to seconds + * @param ticks The number of ticks to convert + * @param ticksInASecond The number of ticks in a single second + * @return Number of seconds + */ + public static int ticksToSeconds(int ticks, int ticksInASecond) + { + return ticks / ticksInASecond; + } + + /** + * Encodes a LibAC Time Notation + * @param seconds Number of seconds to convert + * @return Time Notation + */ + public static TimeNotation secondsToTimeNotation(int seconds) + { + int years = seconds / YEAR; + if(years > 0) seconds -= YEAR * years; + + int month = seconds / MONTH; + if(month > 0) seconds -= MONTH * month; + + int week = seconds / WEEK; + if(week > 0) seconds -= WEEK * week; + + int day = seconds / DAY; + if(day > 0) seconds -= DAY * day; + + int hour = seconds / HOUR; + if(hour > 0) seconds -= HOUR * hour; + + int minute = seconds / MINUTE; + if(minute > 0) seconds -= MINUTE * minute; + + return new TimeNotation(years, month, week, day, hour, minute, seconds); + } + + /** + * Convert a time notation to seconds + * @param notation Notation to convert + * @return Total seconds + */ + public static int notationToSeconds(TimeNotation notation) + { + int seconds = 0; + + seconds += (notation.Years * YEAR); + seconds += (notation.Months * MONTH); + seconds += (notation.Weeks * WEEK); + seconds += (notation.Days * DAY); + seconds += (notation.Hours * HOUR); + seconds += (notation.Minutes * MINUTE); + seconds += (notation.Seconds); + + return seconds; + } + + + public static final int SECOND; + public static final int MINUTE; + public static final int HOUR; + public static final int DAY; + public static final int WEEK; + public static final int MONTH; + public static final int YEAR; + + static { + SECOND = 1; + MINUTE = SECOND * 60; + HOUR = MINUTE * 60; + DAY = HOUR*24; + WEEK = DAY*7; + MONTH = WEEK*4; + YEAR = DAY*365; + } +} diff --git a/src/main/java/dev/zontreck/ariaslib/xmlrpc/MethodCall.java b/src/main/java/dev/zontreck/ariaslib/xmlrpc/MethodCall.java new file mode 100644 index 0000000..178bf84 --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/xmlrpc/MethodCall.java @@ -0,0 +1,32 @@ +package dev.zontreck.ariaslib.xmlrpc; + + +import java.util.Map; + +public class MethodCall { + private String methodName; + private Object[] params; + public Map parameters; + + + public MethodCall ( String methodName , Object[] params , Map p ) { + this.methodName = methodName; + this.params = params; + this.parameters = p; + } + + public String getMethodName ( ) { + return methodName; + } + + public Object[] getParams ( ) { + return params; + } + + public static MethodCall fromDeserializer ( XmlRpcDeserializer deserializer ) throws Exception { + String methodName = deserializer.readMethodName ( ); + Object[] params = deserializer.readMethodParams ( ); + Map parameters = ( Map ) params[ 0 ]; + return new MethodCall ( methodName , params , parameters ); + } +} diff --git a/src/main/java/dev/zontreck/ariaslib/xmlrpc/MethodResponse.java b/src/main/java/dev/zontreck/ariaslib/xmlrpc/MethodResponse.java new file mode 100644 index 0000000..ddef840 --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/xmlrpc/MethodResponse.java @@ -0,0 +1,26 @@ +package dev.zontreck.ariaslib.xmlrpc; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public class MethodResponse { + public Map parameters = new HashMap<> ( ); + + public MethodResponse ( ) { + } + + + public String toXml ( ) { + ByteArrayOutputStream baos = new ByteArrayOutputStream ( ); + XmlRpcStreamWriter streamWriter = new XmlRpcStreamWriter ( baos ); + + try { + streamWriter.writeMethodResponse ( parameters ); + return new String ( baos.toByteArray ( ) ); + } catch ( IOException e ) { + throw new RuntimeException ( e ); + } + } +} diff --git a/src/main/java/dev/zontreck/ariaslib/xmlrpc/README.md b/src/main/java/dev/zontreck/ariaslib/xmlrpc/README.md new file mode 100644 index 0000000..7addc3b --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/xmlrpc/README.md @@ -0,0 +1,7 @@ +XMLRPC +====== +----------- + + + +This code is heavily auto-generated from ChatGPT, however, it contains a lot of modifications to make it actually work. \ No newline at end of file diff --git a/src/main/java/dev/zontreck/ariaslib/xmlrpc/XmlRpcDeserializer.java b/src/main/java/dev/zontreck/ariaslib/xmlrpc/XmlRpcDeserializer.java new file mode 100644 index 0000000..78c1000 --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/xmlrpc/XmlRpcDeserializer.java @@ -0,0 +1,44 @@ +package dev.zontreck.ariaslib.xmlrpc; + + +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +public class XmlRpcDeserializer { + private XmlRpcStreamReader xmlStreamReader; + + public XmlRpcDeserializer ( InputStream inputStream ) throws Exception { + xmlStreamReader = new XmlRpcStreamReader ( inputStream ); + } + public String skipXmlHeader(String xml) { + int startIndex = xml.indexOf("= 0) { + int endIndex = xml.indexOf("?>", startIndex); + if (endIndex >= 0) { + return xml.substring(endIndex + 2); + } + } + return xml; + } + public XmlRpcDeserializer ( String xml ) throws Exception { + byte[] xmlBytes = xml.getBytes ( ); + ByteArrayInputStream inputStream = new ByteArrayInputStream ( xmlBytes ); + xmlStreamReader = new XmlRpcStreamReader ( inputStream ); + } + + public String readMethodName ( ) throws Exception { + return xmlStreamReader.readMethodCallMethodName ( ); + } + + public Object[] readMethodParams ( ) throws Exception { + return xmlStreamReader.readMethodCallParams ( ); + } + + public Object readMethodResponse ( ) throws Exception { + return xmlStreamReader.readMethodResponseResult ( ); + } + + public void close ( ) throws Exception { + xmlStreamReader.close ( ); + } +} \ No newline at end of file diff --git a/src/main/java/dev/zontreck/ariaslib/xmlrpc/XmlRpcException.java b/src/main/java/dev/zontreck/ariaslib/xmlrpc/XmlRpcException.java new file mode 100644 index 0000000..513a7de --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/xmlrpc/XmlRpcException.java @@ -0,0 +1,24 @@ +package dev.zontreck.ariaslib.xmlrpc; + + +public class XmlRpcException extends Exception { + public XmlRpcException ( int code , String message ) { + super ( message ); + FaultCode = code; + FaultString = message; + + } + + public final String FaultString; + public final int FaultCode; + + @Override + public String toString ( ) { + StringBuilder sb = new StringBuilder ( ); + sb.append ( "Code: " +FaultCode); + sb.append ( "\nMessage: " +FaultString); + sb.append ( "\n\n" ); + + return sb.toString (); + } +} diff --git a/src/main/java/dev/zontreck/ariaslib/xmlrpc/XmlRpcSerializer.java b/src/main/java/dev/zontreck/ariaslib/xmlrpc/XmlRpcSerializer.java new file mode 100644 index 0000000..dbb44e0 --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/xmlrpc/XmlRpcSerializer.java @@ -0,0 +1,26 @@ +package dev.zontreck.ariaslib.xmlrpc; + + +import java.io.IOException; +import java.io.OutputStream; +import java.util.List; + +public class XmlRpcSerializer { + private XmlRpcStreamWriter writer; + + public XmlRpcSerializer ( OutputStream outputStream ) { + this.writer = new XmlRpcStreamWriter ( outputStream ); + } + + public void serializeMethodCall ( String methodName , List params ) throws IOException { + writer.writeMethodCall ( methodName , params ); + } + + public void serializeMethodResponse ( Object value ) throws IOException { + writer.writeMethodResponse ( value ); + } + + public void close ( ) throws IOException { + writer.close ( ); + } +} diff --git a/src/main/java/dev/zontreck/ariaslib/xmlrpc/XmlRpcStreamReader.java b/src/main/java/dev/zontreck/ariaslib/xmlrpc/XmlRpcStreamReader.java new file mode 100644 index 0000000..453b058 --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/xmlrpc/XmlRpcStreamReader.java @@ -0,0 +1,206 @@ +package dev.zontreck.ariaslib.xmlrpc; + +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class XmlRpcStreamReader { + private XMLStreamReader xmlStreamReader; + + public XmlRpcStreamReader ( InputStream inputStream ) throws XMLStreamException { + XMLInputFactory inputFactory = XMLInputFactory.newInstance ( ); + xmlStreamReader = inputFactory.createXMLStreamReader ( inputStream ); + } + + private String CURRENT_TAG_NAME; + private int ELEM_TYPE; + + public boolean nextTag ( ) throws XMLStreamException { + while ( xmlStreamReader.hasNext ( ) ) { + int eventType = xmlStreamReader.next ( ); + if ( eventType == XMLStreamConstants.START_ELEMENT || eventType == XMLStreamConstants.END_ELEMENT ) { + CURRENT_TAG_NAME = getLocalName ( ); + ELEM_TYPE = xmlStreamReader.getEventType ( ); + return true; + } + } + return false; + } + + public String getLocalName ( ) { + return xmlStreamReader.getLocalName ( ); + } + + public String getElementText ( ) throws XMLStreamException { + return xmlStreamReader.getElementText ( ); + } + + public void require ( int type , String namespaceURI , String localName ) throws XMLStreamException { + xmlStreamReader.require ( type , namespaceURI , localName ); + } + + public String readMethodCallMethodName ( ) throws XMLStreamException { + nextTag ( ); + require ( XMLStreamConstants.START_ELEMENT , null , "methodCall" ); + nextTag ( ); + require ( XMLStreamConstants.START_ELEMENT , null , "methodName" ); + return getElementText ( ); + } + + public Object[] readMethodCallParams ( ) throws XMLStreamException { + nextTag ( ); + require ( XMLStreamConstants.START_ELEMENT , null , "params" ); + return deserializeParams ( ); + } + + private Object[] deserializeParams ( ) throws XMLStreamException { + List paramsList = new ArrayList<> ( ); + + while ( xmlStreamReader.hasNext ( ) ) { + int event = xmlStreamReader.next ( ); + if ( event == XMLStreamConstants.START_ELEMENT ) { + String elementName = xmlStreamReader.getLocalName ( ); + if ( elementName.equals ( "param" ) ) { + nextTag ( ); + require ( XMLStreamConstants.START_ELEMENT , null , "value" ); + + + Object value = deserializeValue ( ); + paramsList.add ( value ); + } + } + } + + return paramsList.toArray ( ); + } + + public Object readMethodResponseResult ( ) throws XMLStreamException { + nextTag ( ); + require ( XMLStreamConstants.START_ELEMENT , null , "methodResponse" ); + nextTag ( ); + require ( XMLStreamConstants.START_ELEMENT , null , "params" ); + nextTag ( ); + require ( XMLStreamConstants.START_ELEMENT , null , "param" ); + return deserializeValue ( ); + } + + private Object deserializeValue ( ) throws XMLStreamException { + nextTag ( ); + + + int eventType = xmlStreamReader.getEventType ( ); + if ( eventType == XMLStreamConstants.CHARACTERS || eventType == XMLStreamConstants.CDATA ) { + return xmlStreamReader.getText ( ); + } + else if ( eventType == XMLStreamConstants.START_ELEMENT ) { + String localName = xmlStreamReader.getLocalName ( ); + switch ( localName ) { + case "string": + return deserializeString ( ); + case "i4": + case "int": + return deserializeInt ( ); + case "double": + return deserializeDouble ( ); + case "boolean": + return deserializeBoolean ( ); + case "array": + return deserializeArray ( ); + case "struct": + return deserializeStruct ( ); + case "nil": + return null; + case "i8": + return deserializeLong ( ); + default: + throw new IllegalArgumentException ( "Unsupported element: " + localName ); + } + } + else { + throw new IllegalArgumentException ( "Unexpected event type: " + eventType ); + } + } + + private String deserializeString ( ) throws XMLStreamException { + return getElementText ( ); + } + + private int deserializeInt ( ) throws XMLStreamException { + return Integer.parseInt ( getElementText ( ) ); + } + + private byte deserializeByte ( ) throws XMLStreamException { + return Byte.parseByte ( getElementText ( ) ); + } + + private long deserializeLong ( ) throws XMLStreamException { + return Long.parseLong ( getElementText ( ) ); + } + + private double deserializeDouble ( ) throws XMLStreamException { + return Double.parseDouble ( getElementText ( ) ); + } + + private boolean deserializeBoolean ( ) throws XMLStreamException { + return Boolean.parseBoolean ( getElementText ( ) ); + } + + private Object[] deserializeArray ( ) throws XMLStreamException { + List arr = new ArrayList<> ( ); + while ( nextTag ( ) ) { + if ( CURRENT_TAG_NAME.equals ( "data" ) && ELEM_TYPE == XMLStreamConstants.END_ELEMENT ) { + break; + } + else if ( CURRENT_TAG_NAME.equals ( "value" ) && ELEM_TYPE == XMLStreamConstants.START_ELEMENT ) { + arr.add ( deserializeValue ( ) ); + } + } + + return arr.toArray ( ); + } + + private Map deserializeStruct ( ) throws XMLStreamException { + Map struct = new HashMap<> ( ); + String name = null; + while ( nextTag ( ) ) { + if ( xmlStreamReader.getLocalName ( ).equals ( "member" ) ) { + name = null; + } + else if ( xmlStreamReader.getLocalName ( ).equals ( "name" ) ) { + name = getElementText ( ); + } + else if ( xmlStreamReader.getLocalName ( ).equals ( "value" ) && xmlStreamReader.getEventType ( ) == XMLStreamConstants.START_ELEMENT ) { + if ( name != null ) { + Object value = deserializeValue ( ); + struct.put ( name , value ); + } + } + else if ( CURRENT_TAG_NAME.equals ( "struct" ) && xmlStreamReader.getEventType ( ) == XMLStreamConstants.END_ELEMENT ) { + break; + } + } + return struct; + } + + public static String skipXmlHeader ( String xml ) { + int startIndex = xml.indexOf ( "= 0 ) { + int endIndex = xml.indexOf ( "?>" , startIndex ); + if ( endIndex >= 0 ) { + return xml.substring ( endIndex + 2 ); + } + } + return xml; + } + + + public void close ( ) throws XMLStreamException { + xmlStreamReader.close ( ); + } +} diff --git a/src/main/java/dev/zontreck/ariaslib/xmlrpc/XmlRpcStreamWriter.java b/src/main/java/dev/zontreck/ariaslib/xmlrpc/XmlRpcStreamWriter.java new file mode 100644 index 0000000..eaa45fd --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/xmlrpc/XmlRpcStreamWriter.java @@ -0,0 +1,166 @@ +package dev.zontreck.ariaslib.xmlrpc; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.util.List; +import java.util.Map; + +public class XmlRpcStreamWriter { + private static final String XML_VERSION = ""; + private static final String METHOD_CALL_START_TAG = ""; + private static final String METHOD_CALL_END_TAG = "\n"; + private static final String METHOD_NAME_START_TAG = ""; + private static final String METHOD_NAME_END_TAG = "\n"; + private static final String METHOD_RESPONSE_START_TAG = ""; + private static final String METHOD_RESPONSE_END_TAG = "\n"; + private static final String PARAMS_START_TAG = ""; + private static final String PARAMS_END_TAG = "\n"; + private static final String PARAM_START_TAG = ""; + private static final String PARAM_END_TAG = "\n"; + private static final String VALUE_START_TAG = ""; + private static final String VALUE_END_TAG = "\n"; + private static final String ARRAY_START_TAG = ""; + private static final String ARRAY_END_TAG = "\n"; + private static final String DATA_START_TAG = ""; + private static final String DATA_END_TAG = "\n"; + private static final String STRUCT_START_TAG = ""; + private static final String STRUCT_END_TAG = "\n"; + private static final String MEMBER_START_TAG = ""; + private static final String MEMBER_END_TAG = "\n"; + private static final String NAME_START_TAG = ""; + private static final String NAME_END_TAG = "\n"; + + private Writer writer; + + public XmlRpcStreamWriter ( OutputStream outputStream ) { + this.writer = new OutputStreamWriter ( outputStream ); + } + + public void writeMethodCall ( String methodName , List params ) throws IOException { + writer.write ( XML_VERSION ); + writer.write ( METHOD_CALL_START_TAG ); + writer.write ( METHOD_NAME_START_TAG ); + writer.write ( methodName ); + writer.write ( METHOD_NAME_END_TAG ); + writer.write ( PARAMS_START_TAG ); + + if ( params != null ) { + for ( Object param : params ) { + writer.write ( PARAM_START_TAG ); + writeValue ( param ); + writer.write ( PARAM_END_TAG ); + } + } + writer.write ( PARAMS_END_TAG ); + writer.write ( METHOD_CALL_END_TAG ); + writer.flush ( ); + } + + public void writeMethodResponse ( Object value ) throws IOException { + writer.write ( XML_VERSION ); + writer.write ( METHOD_RESPONSE_START_TAG ); + writer.write ( PARAMS_START_TAG ); + writer.write ( PARAM_START_TAG ); + writeValue ( value ); + writer.write ( PARAM_END_TAG ); + writer.write ( PARAMS_END_TAG ); + writer.write ( METHOD_RESPONSE_END_TAG ); + writer.flush ( ); + } + + private void writeValue ( Object value ) throws IOException { + if ( value == null ) { + writer.write ( VALUE_START_TAG ); + writer.write ( "" ); + writer.write ( VALUE_END_TAG ); + } + else if ( value instanceof String ) { + writer.write ( VALUE_START_TAG ); + writer.write ( "" ); + writer.write ( escapeXml ( ( String ) value ) ); + writer.write ( "\n" ); + writer.write ( VALUE_END_TAG ); + } + else if ( value instanceof Integer ) { + writer.write ( VALUE_START_TAG ); + writer.write ( "" ); + writer.write ( value.toString ( ) ); + writer.write ( "\n" ); + writer.write ( VALUE_END_TAG ); + } else if(value instanceof Long) + { + writer.write ( VALUE_START_TAG ); + writer.write ( "" ); + writer.write ( value.toString () ); // Save it as a int for now due to unclear handling + writer.write ( "\n" ); + writer.write ( VALUE_END_TAG ); + } + else if ( value instanceof Double ) { + writer.write ( VALUE_START_TAG ); + writer.write ( "" ); + writer.write ( value.toString ( ) ); + writer.write ( "\n" ); + writer.write ( VALUE_END_TAG ); + } + else if ( value instanceof Boolean ) { + writer.write ( VALUE_START_TAG ); + writer.write ( "" ); + writer.write ( value.toString ( ) ); + writer.write ( "\n" ); + writer.write ( VALUE_END_TAG ); + } + else if ( value instanceof List ) { + writer.write ( VALUE_START_TAG ); + writer.write ( ARRAY_START_TAG ); + writer.write ( DATA_START_TAG ); + List list = ( List ) value; + for ( Object item : list ) { + writeValue ( item ); + } + writer.write ( DATA_END_TAG ); + writer.write ( ARRAY_END_TAG ); + writer.write ( VALUE_END_TAG ); + } + else if ( value instanceof Map ) { + writer.write ( VALUE_START_TAG ); + writer.write ( STRUCT_START_TAG ); + Map map = ( Map ) value; + for ( Map.Entry entry : map.entrySet ( ) ) { + writer.write ( MEMBER_START_TAG ); + writer.write ( NAME_START_TAG ); + writer.write ( escapeXml ( entry.getKey ( ).toString ( ) ) ); + writer.write ( NAME_END_TAG ); + writeValue ( entry.getValue ( ) ); + writer.write ( MEMBER_END_TAG ); + } + writer.write ( STRUCT_END_TAG ); + writer.write ( VALUE_END_TAG ); + } + else if(value instanceof Byte) + { + writer.write ( VALUE_START_TAG ); + writer.write ( "" ); + writer.write ( value.toString () ); // Treat as a integer for now + writer.write ( "\n" ); + writer.write ( VALUE_END_TAG ); + } + else { + throw new IllegalArgumentException ( "Unsupported data type: " + value.getClass ( ).getName ( ) ); + } + } + + private String escapeXml ( String value ) { + return value + .replace ( "&" , "&" ) + .replace ( "<" , "<" ) + .replace ( ">" , ">" ) + .replace ( "\"" , """ ) + .replace ( "'" , "'" ); + } + + public void close ( ) throws IOException { + writer.close ( ); + } +} diff --git a/src/main/java/dev/zontreck/ariaslib/xmlrpc/XmlRpcTokens.java b/src/main/java/dev/zontreck/ariaslib/xmlrpc/XmlRpcTokens.java new file mode 100644 index 0000000..db9d9fc --- /dev/null +++ b/src/main/java/dev/zontreck/ariaslib/xmlrpc/XmlRpcTokens.java @@ -0,0 +1,48 @@ +package dev.zontreck.ariaslib.xmlrpc; + +public class XmlRpcTokens +{ + public final String ISO_DATETIME = "yyyyMMdd\\THH\\:mm\\:ss"; + + public final String BASE64 = "base64"; + + public final String STRING = "string"; + + public final String INT = "i4"; + + public final String ALT_INT = "int"; + + public final String DATETIME = "dateTime.iso8601"; + + public final String BOOLEAN = "boolean"; + + public final String VALUE = "value"; + + public final String NAME = "name"; + + public final String ARRAY = "array"; + + public final String DATA = "data"; + + public final String MEMBER = "member"; + + public final String STRUCT = "struct"; + + public final String DOUBLE = "double"; + + public final String PARAM = "param"; + + public final String PARAMS = "params"; + + public final String METHOD_CALL = "methodCall"; + + public final String METHOD_NAME = "methodName"; + + public final String METHOD_RESPONSE = "methodResponse"; + + public final String FAULT = "fault"; + + public final String FAULT_CODE = "faultCode"; + + public final String FAULT_STRING = "faultString"; +} diff --git a/src/main/java/dev/zontreck/eventsbus/Bus.java b/src/main/java/dev/zontreck/eventsbus/Bus.java new file mode 100644 index 0000000..9bc744a --- /dev/null +++ b/src/main/java/dev/zontreck/eventsbus/Bus.java @@ -0,0 +1,166 @@ +package dev.zontreck.eventsbus; + +import dev.zontreck.eventsbus.annotations.Priority; +import dev.zontreck.eventsbus.annotations.SingleshotEvent; +import dev.zontreck.eventsbus.annotations.Subscribe; +import dev.zontreck.eventsbus.events.EventBusReadyEvent; +import dev.zontreck.eventsbus.events.ResetEventBusEvent; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.*; +import java.util.stream.Collectors; + +/** + * To be removed + *
+ * Use {EventDispatcher} instead + */ +@Deprecated() +public class Bus { + /** + * The main event bus! + */ + private static Bus Main; + + static { + if(Main == null) + { + Main = new Bus("Main Event Bus", false); + } + } + + public static boolean debug = false; + public final String BusName; + public final boolean UsesInstances; + + public Bus(String name, boolean useInstances) { + BusName = name; + UsesInstances = useInstances; + + Post(new EventBusReadyEvent()); + } + + public Map, List> static_events = new HashMap, List>(); + public Map, List> instanced_events = new HashMap, List>(); + + public static void Register(Class clazz, T instance) { + + List nonStaticMethods = Arrays.stream(clazz.getMethods()) + .filter(x -> x.isAnnotationPresent(Subscribe.class)) + .filter(x -> (x.getModifiers() & Modifier.STATIC) != Modifier.STATIC) + .collect(Collectors.toList()); + + List staticMethods = Arrays.stream(clazz.getMethods()) + .filter(x -> x.isAnnotationPresent(Subscribe.class)) + .filter(x -> (x.getModifiers() & Modifier.STATIC) == Modifier.STATIC) + .collect(Collectors.toList()); + + // Register the non-static methods if applicable + if (instance != null) { + for (Method m : + nonStaticMethods) { + EventContainer container = new EventContainer(); + container.instance = instance; + container.method = m; + container.clazz = clazz; + if (m.isAnnotationPresent(Priority.class)) + container.Level = m.getAnnotation(Priority.class).Level(); + else container.Level = PriorityLevel.LOWEST; + + Class clz = m.getParameters()[0].getType(); + + container.IsSingleshot = m.isAnnotationPresent(SingleshotEvent.class); + + if (Main.instanced_events.containsKey(clz)) + Main.instanced_events.get(clz).add(container); + else { + Main.instanced_events.put(clz, new ArrayList<>()); + Main.instanced_events.get(clz).add(container); + } + } + } + + for (Method m : staticMethods) { + EventContainer container = new EventContainer(); + container.instance = null; + container.method = m; + container.clazz = clazz; + if (m.isAnnotationPresent((Priority.class))) + container.Level = m.getAnnotation(Priority.class).Level(); + else container.Level = PriorityLevel.LOWEST; + + Class clz = m.getParameters()[0].getType(); + container.IsSingleshot = m.isAnnotationPresent(SingleshotEvent.class); + + if (Main.static_events.containsKey(clz)) + Main.static_events.get(clz).add(container); + else { + Main.static_events.put(clz, new ArrayList<>()); + Main.static_events.get(clz).add(container); + } + } + } + + /** + * Posts an event to the bus. + * + * @param event The event you wish to post + * @return True if the event was cancelled. + */ + public static boolean Post(Event event) { + for (PriorityLevel level : + PriorityLevel.values()) { + // Call each priority level in order of declaration + // Static first, then instanced + // At the end, this method will return the cancellation result. + try { + + if (Main.static_events.containsKey(event.getClass())) { + EventContainer[] tempArray = (EventContainer[]) Main.static_events.get(event.getClass()).toArray(); + + for (EventContainer container : tempArray) { + if (container.invoke(event, level)) { + Main.static_events.get(event.getClass()).remove(container); + } + } + } + + if (Main.instanced_events.containsKey(event.getClass())) { + EventContainer[] tempArray = (EventContainer[]) Main.instanced_events.get(event.getClass()).toArray(); + + for (EventContainer container : tempArray) { + if (container.invoke(event, level)) { + Main.instanced_events.get(event.getClass()).remove(container); + } + } + } + } catch (Exception e) { + + } + } + + return event.isCancelled(); + } + + /** + * Attempts to reset the Event Bus + * + * @return True if the bus was successfully reset (If not interrupts!) + * @see dev.zontreck.eventsbus.events.ResetEventBusEvent + */ + public static boolean Reset() { + if (!Post(new ResetEventBusEvent())) { + + Main.static_events = new HashMap<>(); + Main.instanced_events = new HashMap<>(); + + Post(new EventBusReadyEvent()); + + return true; + } + + return false; + + } +} diff --git a/src/main/java/dev/zontreck/eventsbus/ClassScanner.java b/src/main/java/dev/zontreck/eventsbus/ClassScanner.java new file mode 100644 index 0000000..4e2deab --- /dev/null +++ b/src/main/java/dev/zontreck/eventsbus/ClassScanner.java @@ -0,0 +1,95 @@ +package dev.zontreck.eventsbus; + + +import java.io.File; +import java.io.IOException; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + + +@Deprecated +/** + * Used internally. Do not directly invoke + *
+ * Accessor is set Protected intentionally!!!!!!!!! + */ +class ClassScanner { + /** + * Start the process of scanning the classes and forcing them to load in + *
+ * This is used by the event dispatcher + */ + protected static void DoScan() { + // Scan all classes in the classpath + Set> scannedClasses = scanClasses(); + + // Force loading of all scanned classes + for (Class clazz : scannedClasses) { + try { + // Load the class + Class.forName(clazz.getName()); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + } + } + + private static Set> scanClasses() { + String classpath = System.getProperty("java.class.path"); + String[] classpathEntries = classpath.split(File.pathSeparator); + + Set> scannedClasses = new HashSet<>(); + for (String classpathEntry : classpathEntries) { + File file = new File(classpathEntry); + if (file.isDirectory()) { + scanClassesInDirectory(file, "", scannedClasses); + } else if (file.isFile() && classpathEntry.endsWith(".jar")) { + scanClassesInJar(file, scannedClasses); + } + } + + return scannedClasses; + } + + private static void scanClassesInDirectory(File directory, String packageName, Set> scannedClasses) { + File[] files = directory.listFiles(); + if (files == null) { + return; + } + + for (File file : files) { + if (file.isDirectory()) { + scanClassesInDirectory(file, packageName + "." + file.getName(), scannedClasses); + } else if (file.getName().endsWith(".class")) { + String className = packageName + "." + file.getName().substring(0, file.getName().length() - 6); + try { + Class clazz = Class.forName(className); + scannedClasses.add(clazz); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + } + } + } + + private static void scanClassesInJar(File jarFile, Set> scannedClasses) { + try (JarFile jf = new JarFile(jarFile)) { + for (JarEntry entry : Collections.list(jf.entries())) { + if (entry.getName().endsWith(".class")) { + String className = entry.getName().replace("/", ".").substring(0, entry.getName().length() - 6); + try { + Class clazz = Class.forName(className); + scannedClasses.add(clazz); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + } + } + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/dev/zontreck/eventsbus/Event.java b/src/main/java/dev/zontreck/eventsbus/Event.java new file mode 100644 index 0000000..1053644 --- /dev/null +++ b/src/main/java/dev/zontreck/eventsbus/Event.java @@ -0,0 +1,46 @@ +package dev.zontreck.eventsbus; + + +import dev.zontreck.eventsbus.annotations.Cancellable; +import dev.zontreck.eventsbus.annotations.Priority; + +public class Event { + private boolean cancelled = false; + + /** + * Checks if the event can be cancelled. + * + * @return True if the cancellation annotation is present. + * @see Cancellable + */ + public boolean IsCancellable() { + Class Current = this.getClass(); + return Current.isAnnotationPresent(Cancellable.class); + } + + /** + * Checks if the event is cancelled. + * + * @return False if the event cannot be cancelled; or + * The current cancellation status for the event + */ + public boolean isCancelled() { + if (!IsCancellable()) + return false; + return cancelled; + } + + /** + * Sets the cancelled status for the event + * + * @param cancel Whether the event should be marked as cancelled or not. + */ + public void setCancelled(boolean cancel) { + cancelled = cancel; + } + + public PriorityLevel getPriorityLevel() { + Class Current = this.getClass(); + return Current.getAnnotation(Priority.class).Level(); + } +} \ No newline at end of file diff --git a/src/main/java/dev/zontreck/eventsbus/EventContainer.java b/src/main/java/dev/zontreck/eventsbus/EventContainer.java new file mode 100644 index 0000000..e49162c --- /dev/null +++ b/src/main/java/dev/zontreck/eventsbus/EventContainer.java @@ -0,0 +1,45 @@ +package dev.zontreck.eventsbus; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public class EventContainer { + public Class clazz; + public Object instance; + + /** + * The method that gets invoked, either statically, or via the instance. + */ + public Method method; + + /** + * Indicates whether an event gets removed from the register after being invoked once. + */ + public boolean IsSingleshot; + + /** + * The current method's priority level + */ + public PriorityLevel Level; + + /** + * Invokes the event + * + * @param EventArg The event instance to pass to the subscribed function + * @param level Current priority level on the call loop. Will refuse to invoke if the priority level mismatches. + * @return True if the event was single shot and should be deregistered + * @throws InvocationTargetException + * @throws IllegalAccessException + */ + public boolean invoke(Event EventArg, PriorityLevel level) throws InvocationTargetException, IllegalAccessException { + if (Level != level) return false; + + if (instance == null) { + method.invoke(null, EventArg); + } else { + method.invoke(instance, EventArg); + } + + return IsSingleshot; + } +} diff --git a/src/main/java/dev/zontreck/eventsbus/EventDispatcher.java b/src/main/java/dev/zontreck/eventsbus/EventDispatcher.java new file mode 100644 index 0000000..b1c670f --- /dev/null +++ b/src/main/java/dev/zontreck/eventsbus/EventDispatcher.java @@ -0,0 +1,124 @@ +package dev.zontreck.eventsbus; + +import dev.zontreck.eventsbus.annotations.EventSubscriber; +import dev.zontreck.eventsbus.annotations.Priority; +import dev.zontreck.eventsbus.annotations.SingleshotEvent; +import dev.zontreck.eventsbus.annotations.Subscribe; +import dev.zontreck.eventsbus.events.EventBusReadyEvent; +import dev.zontreck.eventsbus.events.ResetEventBusEvent; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class EventDispatcher +{ + private static List singleshot = new ArrayList<>(); + private static List> subscribers = new ArrayList<>(); + + /** + * Scans every Java class that is currently loaded. It then checks for Subscribe, and a proper parameter before posting the Event. + * The Event will only be posted if not cancelled using {@link Event#setCancelled(boolean)} and that {@link Subscribe#allowCancelled()} allows. + * @param event The event to post + * @return True if cancelled. + */ + + public static boolean Post(Event event) + { + for(PriorityLevel level : PriorityLevel.values()) + { + + for(Class clazz : subscribers) + { + for(Method M :clazz.getMethods()) + { + if(!M.isAnnotationPresent(Subscribe.class)) continue; + + Subscribe subscriber = M.getAnnotation(Subscribe.class); + + + boolean canPost=true; + Class param = M.getParameterTypes()[0]; + if(param == event.getClass()) + { + if(M.isAnnotationPresent(SingleshotEvent.class)) + { + if(singleshot.contains(M)) + { + canPost=false; + } + } + } else canPost=false; + + PriorityLevel eventPriotityLevel= PriorityLevel.HIGH; // Default + + if(M.isAnnotationPresent(Priority.class)) + { + Priority prio = M.getAnnotation(Priority.class); + eventPriotityLevel=prio.Level(); + } + + if(level != eventPriotityLevel) + { + canPost=false; + } + + + // Dispatch the event now + + if(!canPost) continue; + + if(!M.canAccess(null)) + { + canPost=false; + System.out.println("ERROR: Even subscriber methods must be static and public"); + } + + try { + if(event.isCancelled() && !subscriber.allowCancelled()) + continue; + else + M.invoke(null, event); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } catch (InvocationTargetException e) { + throw new RuntimeException(e); + } + + } + } + } + + return event.isCancelled(); + } + + + /** + * Register a class + */ + public static void Register(Class clazz) + { + if(clazz.isAnnotationPresent(EventSubscriber.class)) + subscribers.add(clazz); + } + + /** + * Resets the events system. + *
+ * This action clears the Singleshot list for the events that should only be invoked once. And rescans all classes incase new classes were dynamically loaded. + */ + public static void Reset() + { + Post(new ResetEventBusEvent()); + + subscribers.clear(); + singleshot.clear(); + + Post(new EventBusReadyEvent()); + } +} diff --git a/src/main/java/dev/zontreck/eventsbus/PriorityLevel.java b/src/main/java/dev/zontreck/eventsbus/PriorityLevel.java new file mode 100644 index 0000000..be93823 --- /dev/null +++ b/src/main/java/dev/zontreck/eventsbus/PriorityLevel.java @@ -0,0 +1,15 @@ +package dev.zontreck.eventsbus; + +/** + * Event priority level. + *

+ * The higher the priority, the sooner it gets executed. High is executed after + * Highest. + */ +public enum PriorityLevel { + HIGHEST, + HIGH, + MEDIUM, + LOW, + LOWEST +} diff --git a/src/main/java/dev/zontreck/eventsbus/annotations/Cancellable.java b/src/main/java/dev/zontreck/eventsbus/annotations/Cancellable.java new file mode 100644 index 0000000..34be386 --- /dev/null +++ b/src/main/java/dev/zontreck/eventsbus/annotations/Cancellable.java @@ -0,0 +1,11 @@ +package dev.zontreck.eventsbus.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(value = RetentionPolicy.RUNTIME) +@Target(value = ElementType.TYPE) +public @interface Cancellable { +} diff --git a/src/main/java/dev/zontreck/eventsbus/annotations/EventSubscriber.java b/src/main/java/dev/zontreck/eventsbus/annotations/EventSubscriber.java new file mode 100644 index 0000000..0ee5023 --- /dev/null +++ b/src/main/java/dev/zontreck/eventsbus/annotations/EventSubscriber.java @@ -0,0 +1,11 @@ +package dev.zontreck.eventsbus.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(value = RetentionPolicy.RUNTIME) +@Target(value = ElementType.TYPE) +public @interface EventSubscriber { +} diff --git a/src/main/java/dev/zontreck/eventsbus/annotations/Priority.java b/src/main/java/dev/zontreck/eventsbus/annotations/Priority.java new file mode 100644 index 0000000..88a4276 --- /dev/null +++ b/src/main/java/dev/zontreck/eventsbus/annotations/Priority.java @@ -0,0 +1,14 @@ +package dev.zontreck.eventsbus.annotations; + +import dev.zontreck.eventsbus.PriorityLevel; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(value = RetentionPolicy.RUNTIME) +@Target(value = ElementType.METHOD) +public @interface Priority { + PriorityLevel Level(); +} diff --git a/src/main/java/dev/zontreck/eventsbus/annotations/SingleshotEvent.java b/src/main/java/dev/zontreck/eventsbus/annotations/SingleshotEvent.java new file mode 100644 index 0000000..6e8d612 --- /dev/null +++ b/src/main/java/dev/zontreck/eventsbus/annotations/SingleshotEvent.java @@ -0,0 +1,11 @@ +package dev.zontreck.eventsbus.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(value = RetentionPolicy.RUNTIME) +@Target(value = ElementType.METHOD) +public @interface SingleshotEvent { +} diff --git a/src/main/java/dev/zontreck/eventsbus/annotations/Subscribe.java b/src/main/java/dev/zontreck/eventsbus/annotations/Subscribe.java new file mode 100644 index 0000000..9facb6e --- /dev/null +++ b/src/main/java/dev/zontreck/eventsbus/annotations/Subscribe.java @@ -0,0 +1,18 @@ +package dev.zontreck.eventsbus.annotations; + +import dev.zontreck.eventsbus.Event; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(value = RetentionPolicy.RUNTIME) +@Target(value = ElementType.METHOD) + +public @interface Subscribe { + /** + * Marks that the subscribed method will not receive the signal if the event was cancelled with {@link Event#setCancelled(boolean)} + */ + boolean allowCancelled(); +} diff --git a/src/main/java/dev/zontreck/eventsbus/events/EventBusReadyEvent.java b/src/main/java/dev/zontreck/eventsbus/events/EventBusReadyEvent.java new file mode 100644 index 0000000..50370b7 --- /dev/null +++ b/src/main/java/dev/zontreck/eventsbus/events/EventBusReadyEvent.java @@ -0,0 +1,11 @@ +package dev.zontreck.eventsbus.events; + +import dev.zontreck.eventsbus.Event; + +/** + * This event is sent out when the Event Bus is ready. + *

+ * This is also dispatched when a reset occurs, prior to clearing the lists. + */ +public class EventBusReadyEvent extends Event { +} diff --git a/src/main/java/dev/zontreck/eventsbus/events/ResetEventBusEvent.java b/src/main/java/dev/zontreck/eventsbus/events/ResetEventBusEvent.java new file mode 100644 index 0000000..20f2e74 --- /dev/null +++ b/src/main/java/dev/zontreck/eventsbus/events/ResetEventBusEvent.java @@ -0,0 +1,17 @@ +package dev.zontreck.eventsbus.events; + +import dev.zontreck.eventsbus.annotations.Cancellable; +import dev.zontreck.eventsbus.Event; + +/** + * Posted when the event bus is about to be reset. + *

+ * This event can be cancelled to prevent the reset. + *

+ * The default behavior is: Allow Reset. + *

+ * Recommended that handlers be implemented to re-register instances, and classes when a reset occurs. + */ +@Cancellable +public class ResetEventBusEvent extends Event { +} diff --git a/src/main/java/dev/zontreck/eventsbus/events/TickEvent.java b/src/main/java/dev/zontreck/eventsbus/events/TickEvent.java new file mode 100644 index 0000000..52d1d6b --- /dev/null +++ b/src/main/java/dev/zontreck/eventsbus/events/TickEvent.java @@ -0,0 +1,6 @@ +package dev.zontreck.eventsbus.events; + +import dev.zontreck.eventsbus.Event; + +public class TickEvent extends Event { +} From bddc380f8f47dd859eb3c26c1800f0b135312cad Mon Sep 17 00:00:00 2001 From: zontreck Date: Wed, 9 Oct 2024 22:22:00 -0700 Subject: [PATCH 20/21] Bump version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 094ae31..83183f5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ loom.platform=forge libac=1.5.33 eventsbus=1.0.48 # Mod properties -mod_version=1192.13.091224.1802 +mod_version=1192.13.100924.2221 maven_group=dev.zontreck archives_name=libzontreck # Minecraft properties From 5c8db873c6f198b192dc0ca143cbe66dfac11319 Mon Sep 17 00:00:00 2001 From: zontreck Date: Wed, 9 Oct 2024 22:51:45 -0700 Subject: [PATCH 21/21] Revert back to normal forge's build chain --- build.gradle | 214 +++++++++++++++++---- gradle.properties | 65 ++++++- gradle/wrapper/gradle-wrapper.jar | Bin 43453 -> 62076 bytes gradle/wrapper/gradle-wrapper.properties | 1 - gradlew | 35 ++-- gradlew.bat | 21 +- settings.gradle | 12 +- src/main/resources/META-INF/mods.toml | 90 ++++++--- src/main/resources/libzontreck.mixins.json | 13 -- src/main/resources/pack.mcmeta | 11 +- 10 files changed, 331 insertions(+), 131 deletions(-) delete mode 100644 src/main/resources/libzontreck.mixins.json diff --git a/build.gradle b/build.gradle index 8ab7578..67b9c67 100644 --- a/build.gradle +++ b/build.gradle @@ -1,73 +1,202 @@ plugins { - id 'dev.architectury.loom' version '1.6-SNAPSHOT' + id 'eclipse' + id 'idea' id 'maven-publish' - id 'java' + id 'net.minecraftforge.gradle' version '[6.0,6.2)' + id 'org.parchmentmc.librarian.forgegradle' version '1.+' } -group = project.maven_group -version = project.mod_version +version = mod_version +group = mod_group_id base { - archivesName = project.archives_name + archivesName = mod_id } -loom { - silentMojangMappingsLicense() +java { - forge { - mixinConfig 'libzontreck.mixins.json' + withSourcesJar() + withJavadocJar() +} + +// Mojang ships Java 17 to end users in 1.18+, so your mod should target Java 17. +java.toolchain.languageVersion = JavaLanguageVersion.of(17) + +println "Java: ${System.getProperty 'java.version'}, JVM: ${System.getProperty 'java.vm.version'} (${System.getProperty 'java.vendor'}), Arch: ${System.getProperty 'os.arch'}" +minecraft { + // The mappings can be changed at any time and must be in the following format. + // Channel: Version: + // official MCVersion Official field/method names from Mojang mapping files + // parchment YYYY.MM.DD-MCVersion Open community-sourced parameter names and javadocs layered on top of official + // + // You must be aware of the Mojang license when using the 'official' or 'parchment' mappings. + // See more information here: https://github.com/MinecraftForge/MCPConfig/blob/master/Mojang.md + // + // Parchment is an unofficial project maintained by ParchmentMC, separate from MinecraftForge + // Additional setup is needed to use their mappings: https://github.com/ParchmentMC/Parchment/wiki/Getting-Started + // + // Use non-default mappings at your own risk. They may not always work. + // Simply re-run your setup task after changing the mappings to update your workspace. + //mappings channel: mapping_channel, version: "${parchment_version}-${minecraft_version}" + mappings channel: mapping_channel, version: "${minecraft_version}" + + // When true, this property will have all Eclipse/IntelliJ IDEA run configurations run the "prepareX" task for the given run configuration before launching the game. + // In most cases, it is not necessary to enable. + enableEclipsePrepareRuns = true + enableIdeaPrepareRuns = true + + // This property allows configuring Gradle's ProcessResources task(s) to run on IDE output locations before launching the game. + // It is REQUIRED to be set to true for this template to function. + // See https://docs.gradle.org/current/dsl/org.gradle.language.jvm.tasks.ProcessResources.html + copyIdeResources = true + + // When true, this property will add the folder name of all declared run configurations to generated IDE run configurations. + // The folder name can be set on a run configuration using the "folderName" property. + // By default, the folder name of a run configuration is the name of the Gradle project containing it. + generateRunFolders = true + + // This property enables access transformers for use in development. + // They will be applied to the Minecraft artifact. + // The access transformer file can be anywhere in the project. + // However, it must be at "META-INF/accesstransformer.cfg" in the final mod jar to be loaded by Forge. + // This default location is a best practice to automatically put the file in the right place in the final jar. + // See https://docs.minecraftforge.net/en/latest/advanced/accesstransformers/ for more information. + accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg') + + // Default run configurations. + // These can be tweaked, removed, or duplicated as needed. + runs { + // applies to all the run configs below + configureEach { + workingDirectory project.file('run') + + // Recommended logging data for a userdev environment + // The markers can be added/remove as needed separated by commas. + // "SCAN": For mods scan. + // "REGISTRIES": For firing of registry events. + // "REGISTRYDUMP": For getting the contents of all registries. + property 'forge.logging.markers', 'REGISTRIES' + + // Recommended logging level for the console + // You can set various levels here. + // Please read: https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levels + property 'forge.logging.console.level', 'debug' + + // Comma-separated list of namespaces to load gametests from. Empty = all namespaces. + property 'forge.enabledGameTestNamespaces', mod_id + + mods { + "${mod_id}" { + source sourceSets.main + } + } + } + + client { + // Comma-separated list of namespaces to load gametests from. Empty = all namespaces. + property 'forge.enabledGameTestNamespaces', mod_id + } + + server { + property 'forge.enabledGameTestNamespaces', mod_id + args '--nogui' + } + + // This run config launches GameTestServer and runs all registered gametests, then exits. + // By default, the server will crash when no gametests are provided. + // The gametest system is also enabled by default for other run configs under the /test command. + gameTestServer { + property 'forge.enabledGameTestNamespaces', mod_id + } + + data { + // example of overriding the workingDirectory set in configureEach above + workingDirectory project.file('run-data') + + // Specify the modid for data generation, where to output the resulting resource, and where to look for existing resources. + args '--mod', mod_id, '--all', '--output', file('src/generated/resources/'), '--existing', file('src/main/resources/') + } } } +// Include resources generated by data generators. +sourceSets.main.resources { srcDir 'src/generated/resources' } + repositories { - // Add repositories to retrieve artifacts from in here. - // You should only use this when depending on other mods because - // Loom adds the essential maven repositories to download Minecraft and libraries from automatically. - // See https://docs.gradle.org/current/userguide/declaring_repositories.html - // for more information about repositories. + mavenCentral() + // Put repositories for dependencies here + // ForgeGradle automatically adds the Forge maven and Maven Central for you + + // If you have mod jar dependencies in ./libs, you can declare them as a repository like so: + flatDir { + dir 'libs' + } + + //maven { + // name = "CurseMaven" + // url = "https://cursemaven.com" + //} maven { name = "zontreck Maven" url = "https://git.zontreck.com/api/packages/AriasCreations/maven" } + } dependencies { - minecraft "net.minecraft:minecraft:$project.minecraft_version" - mappings loom.officialMojangMappings() - forge "net.minecraftforge:forge:$project.forge_version" + // Specify the version of Minecraft to use. + // Any artifact can be supplied so long as it has a "userdev" classifier artifact and is a compatible patcher artifact. + // The "userdev" classifier will be requested and setup by ForgeGradle. + // If the group id is "net.minecraft" and the artifact id is one of ["client", "server", "joined"], + // then special handling is done to allow a setup of a vanilla dependency without the use of an external repository. + minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}" + + // Example mod dependency with JEI - using fg.deobf() ensures the dependency is remapped to your development mappings + // The JEI API is declared for compile time use, while the full JEI artifact is used at runtime + // compileOnly fg.deobf("mezz.jei:jei-${mc_version}-common-api:${jei_version}") + // compileOnly fg.deobf("mezz.jei:jei-${mc_version}-forge-api:${jei_version}") + // runtimeOnly fg.deobf("mezz.jei:jei-${mc_version}-forge:${jei_version}") + + // Example mod dependency using a mod jar from ./libs with a flat dir repository + // This maps to ./libs/coolmod-${mc_version}-${coolmod_version}.jar + // The group id is ignored when searching -- in this case, it is "blank" + // implementation fg.deobf("blank:coolmod-${mc_version}:${coolmod_version}") + + // For more info: + // http://www.gradle.org/docs/current/userguide/artifact_dependencies_tutorial.html + // http://www.gradle.org/docs/current/userguide/dependency_management.html } -processResources { - inputs.property 'version', project.version +// This block of code expands all declared replace properties in the specified resource targets. +// A missing property will result in an error. Properties are expanded using ${} Groovy notation. +// When "copyIdeResources" is enabled, this will also run before the game launches in IDE environments. +// See https://docs.gradle.org/current/dsl/org.gradle.language.jvm.tasks.ProcessResources.html +tasks.named('processResources', ProcessResources).configure { + var replaceProperties = [ + minecraft_version : minecraft_version, minecraft_version_range: minecraft_version_range, + forge_version : forge_version, forge_version_range: forge_version_range, + loader_version_range: loader_version_range, + mod_id : mod_id, mod_name: mod_name, mod_license: mod_license, mod_version: mod_version, + mod_authors : mod_authors, mod_description: mod_description, + ] + inputs.properties replaceProperties - filesMatching('META-INF/mods.toml') { - expand version: project.version + filesMatching(['META-INF/mods.toml', 'pack.mcmeta']) { + expand replaceProperties + [project: project] } } -java { - // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task - // if it is present. - // If you remove this line, sources will not be generated. - withSourcesJar() - withJavadocJar() - - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 -} - -tasks.withType(JavaCompile).configureEach { - it.options.release = 17 -} +// Example for how to get properties into the manifest for reading at runtime. tasks.named('jar', Jar).configure { - manifest { attributes([ - 'Specification-Title' : project.name, + 'Specification-Title' : mod_id, + 'Specification-Vendor' : mod_authors, 'Specification-Version' : '1', // We are version 1 of ourselves 'Implementation-Title' : project.name, 'Implementation-Version' : project.jar.archiveVersion, + 'Implementation-Vendor' : mod_authors, 'Implementation-Timestamp': new Date().format("yyyy-MM-dd'T'HH:mm:ssZ") ]) } @@ -76,9 +205,14 @@ tasks.named('jar', Jar).configure { finalizedBy 'reobfJar' } +// However if you are in a multi-project build, dev time needs unobfed jar files, so you can delay the obfuscation until publishing by doing: +// tasks.named('publish').configure { +// dependsOn 'reobfJar' +// } + + def MAVEN_PASSWORD = "AriasCreationsMavenPassword" def MAVEN_USER = "AriasCreationsMavenUser" -// Configure Maven publishing. publishing { publications { @@ -100,3 +234,7 @@ publishing { } } } + +tasks.withType(JavaCompile).configureEach { + options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation +} diff --git a/gradle.properties b/gradle.properties index 83183f5..88a12aa 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,12 +1,57 @@ -org.gradle.jvmargs=-Xmx1G -loom.platform=forge +# Sets default memory used for gradle commands. Can be overridden by user or command line properties. +# This is required to provide enough memory for the Minecraft decompilation process. +org.gradle.jvmargs=-Xmx3G -Dfile.encoding=utf-8 +org.gradle.daemon=false +## Environment Properties +# The Minecraft version must agree with the Forge version to get a valid artifact +minecraft_version=1.19.2 +# The Minecraft version range can use any release version of Minecraft as bounds. +# Snapshots, pre-releases, and release candidates are not guaranteed to sort properly +# as they do not follow standard versioning conventions. +minecraft_version_range=[1.19.2,1.20) +# The Forge version must agree with the Minecraft version to get a valid artifact +forge_version=43.4.2 +# The Forge version range can use any version of Forge as bounds or match the loader version range +forge_version_range=[43,) +# The loader version range can only use the major version of Forge/FML as bounds +loader_version_range=[43,) +# The mapping channel to use for mappings. +# The default set of supported mapping channels are ["official", "snapshot", "snapshot_nodoc", "stable", "stable_nodoc"]. +# Additional mapping channels can be registered through the "channelProviders" extension in a Gradle plugin. +# +# | Channel | Version | | +# |-----------|----------------------|--------------------------------------------------------------------------------| +# | official | MCVersion | Official field/method names from Mojang mapping files | +# | parchment | YYYY.MM.DD-MCVersion | Open community-sourced parameter names and javadocs layered on top of official | +# +# You must be aware of the Mojang license when using the 'official' or 'parchment' mappings. +# See more information here: https://github.com/MinecraftForge/MCPConfig/blob/master/Mojang.md +# +# Parchment is an unofficial project maintained by ParchmentMC, separate from Minecraft Forge. +# Additional setup is needed to use their mappings, see https://parchmentmc.org/docs/getting-started +mapping_channel=official +# The mapping version to query from the mapping channel. +# This must match the format required by the mapping channel. +parchment_version=2022.11.27 +# luckperms_api_version=5.4 libac=1.5.33 eventsbus=1.0.48 -# Mod properties -mod_version=1192.13.100924.2221 -maven_group=dev.zontreck -archives_name=libzontreck -# Minecraft properties -minecraft_version=1.19.2 -# Dependencies -forge_version=1.19.2-43.4.0 +## Environment Properties +## Mod Properties +# The unique mod identifier for the mod. Must be lowercase in English locale. Must fit the regex [a-z][a-z0-9_]{1,63} +# Must match the String constant located in the main mod class annotated with @Mod. +mod_id=libzontreck +# The human-readable display name for the mod. +mod_name=Zontreck Library Mod +# The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. +mod_license=GPLv3 +# The mod version. See https://semver.org/ +mod_version=1192.13.100924.2250 +# The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. +# This should match the base package used for the mod sources. +# See https://maven.apache.org/guides/mini/guide-naming-conventions.html +mod_group_id=dev.zontreck +# The authors of the mod. This is a simple text string that is used for display purposes in the mod list. +mod_authors=zontreck +# The description of the mod. This is a simple multiline text string that is used for display purposes in the mod list. +mod_description=LibZontreck\nLibrary Mod! diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e6441136f3d4ba8a0da8d277868979cfbc8ad796..c1962a79e29d3e0ab67b14947c167a862655af9b 100644 GIT binary patch literal 62076 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 literal 43453 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 yu?e%!_ptzGB|8GrCfu%p?(_ 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

NnX)EH+Nua)3Y(c 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 /dev/null && pwd -P ) || exit + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -131,29 +133,22 @@ location of your Java installation." fi else JAVACMD=java - if ! command -v java >/dev/null 2>&1 - then - die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." - fi fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -198,15 +193,11 @@ if "$cygwin" || "$msys" ; then done fi - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, -# and any embedded shellness will be escaped. -# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be -# treated as '${Hostname}' itself on the command line. +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/gradlew.bat b/gradlew.bat index 7101f8e..53a6b23 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -26,7 +26,6 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -43,11 +42,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. goto fail @@ -57,11 +56,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. goto fail diff --git a/settings.gradle b/settings.gradle index f89415a..25c32b0 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,10 +1,14 @@ pluginManagement { repositories { - maven { url "https://maven.fabricmc.net/" } - maven { url "https://maven.architectury.dev/" } - maven { url "https://files.minecraftforge.net/maven/" } gradlePluginPortal() + maven { + name = 'MinecraftForge' + url = 'https://maven.minecraftforge.net/' + } + maven { url = 'https://maven.parchmentmc.org' } // Add this line } } -rootProject.name = 'libzontreck' +plugins { + id 'org.gradle.toolchains.foojay-resolver-convention' version '0.7.0' +} diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index 021ffce..a4c2119 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -1,28 +1,64 @@ -modLoader = "javafml" -loaderVersion = "[43,)" -#issueTrackerURL = "" -license = "GPLv3" +# This is an example mods.toml file. It contains the data relating to the loading mods. +# There are several mandatory fields (#mandatory), and many more that are optional (#optional). +# The overall format is standard TOML format, v0.5.0. +# Note that there are a couple of TOML lists in this file. +# Find more information on toml format here: https://github.com/toml-lang/toml +# The name of the mod loader type to load - for regular FML @Mod mods it should be javafml +modLoader="javafml" #mandatory +# A version range to match for said mod loader - for regular FML @Mod it will be the forge version +loaderVersion="${loader_version_range}" #mandatory This is typically bumped every Minecraft version by Forge. See our download page for lists of versions. +# The license for you mod. This is mandatory metadata and allows for easier comprehension of your redistributive properties. +# Review your options at https://choosealicense.com/. All rights reserved is the default copyright stance, and is thus the default here. +license="${mod_license}" +# A URL to refer people to when problems occur with this mod +issueTrackerURL="https://git.zontreck.com/MinecraftMods/LibZontreck/issues" #optional +# A list of mods - how many allowed here is determined by the individual mod loader +[[mods]] #mandatory +# The modid of the mod +modId="${mod_id}" #mandatory +# The version number of the mod +version="${mod_version}" #mandatory +# A display name for the mod +displayName="${mod_name}" #mandatory +# A URL to query for updates for this mod. See the JSON update specification https://docs.minecraftforge.net/en/latest/misc/updatechecker/ +#updateJSONURL="https://change.me.example.invalid/updates.json" #optional +# A URL for the "homepage" for this mod, displayed in the mod UI +#displayURL="https://change.me.to.your.mods.homepage.example.invalid/" #optional +# A file name (in the root of the mod JAR) containing a logo for display +#logoFile="examplemod.png" #optional +# A text field displayed in the mod UI +#credits="" #optional +# A text field displayed in the mod UI +authors="${mod_authors}" #optional +# Display Test controls the display for your mod in the server connection screen +# MATCH_VERSION means that your mod will cause a red X if the versions on client and server differ. This is the default behaviour and should be what you choose if you have server and client elements to your mod. +# IGNORE_SERVER_VERSION means that your mod will not cause a red X if it's present on the server but not on the client. This is what you should use if you're a server only mod. +# IGNORE_ALL_VERSION means that your mod will not cause a red X if it's present on the client or the server. This is a special case and should only be used if your mod has no server component. +# NONE means that no display test is set on your mod. You need to do this yourself, see IExtensionPoint.DisplayTest for more information. You can define any scheme you wish with this value. +# IMPORTANT NOTE: this is NOT an instruction as to which environments (CLIENT or DEDICATED SERVER) your mod loads on. Your mod should load (and maybe do nothing!) whereever it finds itself. +#displayTest="MATCH_VERSION" # MATCH_VERSION is the default if nothing is specified (#optional) -[[mods]] -modId = "libzontreck" -version = "${version}" -displayName = "LibZontreck" -authors = "Me!" -description = ''' -Common code library mod for all of zontreck's mods. -''' -#logoFile = "" - -[[dependencies.libzontreck]] -modId = "forge" -mandatory = true -versionRange = "[43,)" -ordering = "NONE" -side = "BOTH" - -[[dependencies.libzontreck]] -modId = "minecraft" -mandatory = true -versionRange = "[1.19.2,)" -ordering = "NONE" -side = "BOTH" +# The description text for the mod (multi line!) (#mandatory) +description='''${mod_description}''' +# A dependency - use the . to indicate dependency for a specific modid. Dependencies are optional. +[[dependencies.${mod_id}]] #optional +# the modid of the dependency +modId="forge" #mandatory +# Does this dependency have to exist - if not, ordering below must be specified +mandatory=true #mandatory +# The version range of the dependency +versionRange="${forge_version_range}" #mandatory +# An ordering relationship for the dependency - BEFORE or AFTER required if the dependency is not mandatory +# BEFORE - This mod is loaded BEFORE the dependency +# AFTER - This mod is loaded AFTER the dependency +ordering="NONE" +# Side this dependency is applied on - BOTH, CLIENT, or SERVER +side="BOTH" +# Here's another dependency +[[dependencies.${mod_id}]] +modId="minecraft" +mandatory=true +# This version range declares a minimum of the current minecraft version up to but not including the next major version +versionRange="${minecraft_version_range}" +ordering="NONE" +side="BOTH" diff --git a/src/main/resources/libzontreck.mixins.json b/src/main/resources/libzontreck.mixins.json deleted file mode 100644 index d52cd3b..0000000 --- a/src/main/resources/libzontreck.mixins.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "required": true, - "package": "dev.zontreck.mixin", - "compatibilityLevel": "JAVA_17", - "minVersion": "0.8", - "client": [ - ], - "mixins": [ - ], - "injectors": { - "defaultRequire": 1 - } -} diff --git a/src/main/resources/pack.mcmeta b/src/main/resources/pack.mcmeta index d66feac..81a429d 100644 --- a/src/main/resources/pack.mcmeta +++ b/src/main/resources/pack.mcmeta @@ -1,7 +1,8 @@ { - "pack": { - "description": "LibZontreck", - "forge:data_pack_format": 10, - "pack_format": 9 - } + "pack": { + "description": "${mod_id} resources", + "pack_format": 9, + "forge:resource_pack_format": 9, + "forge:data_pack_format": 10 + } }