diff --git a/build.gradle b/build.gradle index f32675c..043d4d5 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ plugins { id 'net.minecraftforge.gradle' version '5.1.+' } -version = '1.19-2.38' +version = '1.19-2.40' group = 'nl.requios.effortlessbuilding' archivesBaseName = 'effortlessbuilding' @@ -36,7 +36,11 @@ minecraft { // Comma-separated list of namespaces to load gametests from. Empty = all namespaces. property 'forge.enabledGameTestNamespaces', 'effortlessbuilding' - + + // Flywheel + property 'mixin.env.remapRefMap', 'true' + property 'mixin.env.refMapRemappingFile', "${projectDir}/build/createSrgToMcp/output.srg" + mods { effortlessbuilding { source sourceSets.main @@ -53,6 +57,10 @@ minecraft { property 'forge.enabledGameTestNamespaces', 'effortlessbuilding' + // Flywheel + property 'mixin.env.remapRefMap', 'true' + property 'mixin.env.refMapRemappingFile', "${projectDir}/build/createSrgToMcp/output.srg" + mods { effortlessbuilding { source sourceSets.main @@ -109,6 +117,12 @@ repositories { // flatDir { // dir 'libs' // } + + //Flywheel + maven { + name "tterrag maven" + url "https://maven.tterrag.com/" + } } dependencies { @@ -128,6 +142,10 @@ dependencies { // For more info... // http://www.gradle.org/docs/current/userguide/artifact_dependencies_tutorial.html // http://www.gradle.org/docs/current/userguide/dependency_management.html + + //Flywheel + //Versions: https://maven.tterrag.com/com/jozufozu/flywheel/flywheel-forge-1.19.2/ + implementation fg.deobf("com.jozufozu.flywheel:flywheel-forge-1.19.2:0.6.8-13") } // Example for how to get properties into the manifest for reading by the runtime.. diff --git a/src/main/java/nl/requios/effortlessbuilding/ClientConfig.java b/src/main/java/nl/requios/effortlessbuilding/ClientConfig.java new file mode 100644 index 0000000..e2021d2 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/ClientConfig.java @@ -0,0 +1,36 @@ +package nl.requios.effortlessbuilding; + +import net.minecraftforge.common.ForgeConfigSpec; + +public class ClientConfig { + + private static final ForgeConfigSpec.Builder builder = new ForgeConfigSpec.Builder(); + public static final Visuals visuals = new Visuals(builder); + public static final ForgeConfigSpec spec = builder.build(); + + public static class Visuals { + public final ForgeConfigSpec.ConfigValue showBlockPreviews; + public final ForgeConfigSpec.ConfigValue alwaysShowBlockPreview; + public final ForgeConfigSpec.ConfigValue maxBlockPreviews; + + public Visuals(ForgeConfigSpec.Builder builder) { + builder.push("Visuals"); + + showBlockPreviews = builder + .comment("Show previews of the blocks while placing them") + .define("useShaders", true); + + alwaysShowBlockPreview = builder + .comment("Show a block preview if you have a block in hand even in the 'Disabled' build mode") + .define("alwaysShowBlockPreview", false); + + maxBlockPreviews = builder + .comment("Don't show block previews when placing more than this many blocks. " + + "The outline will always be rendered.") + .define("shaderTreshold", 500); + + + builder.pop(); + } + } +} diff --git a/src/main/java/nl/requios/effortlessbuilding/ClientEvents.java b/src/main/java/nl/requios/effortlessbuilding/ClientEvents.java new file mode 100644 index 0000000..e189f9b --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/ClientEvents.java @@ -0,0 +1,337 @@ +package nl.requios.effortlessbuilding; + +import com.mojang.blaze3d.platform.InputConstants; +import com.mojang.blaze3d.vertex.DefaultVertexFormat; +import net.minecraft.ChatFormatting; +import net.minecraft.client.KeyMapping; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.client.renderer.ShaderInstance; +import net.minecraft.core.BlockPos; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.sounds.SoundSource; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.ClipContext; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.SoundType; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.HitResult; +import net.minecraft.world.phys.Vec3; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.client.event.InputEvent; +import net.minecraftforge.client.event.RegisterKeyMappingsEvent; +import net.minecraftforge.client.event.RegisterShadersEvent; +import net.minecraftforge.client.event.ScreenEvent; +import net.minecraftforge.client.settings.KeyConflictContext; +import net.minecraftforge.client.settings.KeyModifier; +import net.minecraftforge.event.TickEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod.EventBusSubscriber; +import nl.requios.effortlessbuilding.buildmode.BuildModes; +import nl.requios.effortlessbuilding.buildmode.ModeOptions; +import nl.requios.effortlessbuilding.buildmode.ModeSettingsManager; +import nl.requios.effortlessbuilding.buildmodifier.ModifierSettingsManager; +import nl.requios.effortlessbuilding.compatibility.CompatHelper; +import nl.requios.effortlessbuilding.gui.buildmode.PlayerSettingsGui; +import nl.requios.effortlessbuilding.gui.buildmode.RadialMenu; +import nl.requios.effortlessbuilding.gui.buildmodifier.ModifierSettingsGui; +import nl.requios.effortlessbuilding.helper.ReachHelper; +import nl.requios.effortlessbuilding.network.*; +import nl.requios.effortlessbuilding.render.BuildRenderTypes; +import org.lwjgl.glfw.GLFW; + +import java.io.IOException; + +@EventBusSubscriber(Dist.CLIENT) +public class ClientEvents { + + public static KeyMapping[] keyBindings; + public static HitResult previousLookAt; + public static HitResult currentLookAt; + public static int ticksInGame = 0; + private static int placeCooldown = 0; + private static int breakCooldown = 0; + + //Mod Bus Events + @EventBusSubscriber(value = Dist.CLIENT, bus = EventBusSubscriber.Bus.MOD) + public static class ModBusEvents { + + @SubscribeEvent + public static void registerKeyMappings(RegisterKeyMappingsEvent event) { + EffortlessBuilding.log("Registering KeyMappings!"); + + // register key bindings + keyBindings = new KeyMapping[6]; + + // instantiate the key bindings + keyBindings[0] = new KeyMapping("key.effortlessbuilding.hud.desc", KeyConflictContext.IN_GAME, InputConstants.getKey(GLFW.GLFW_KEY_KP_ADD, 0), "key.effortlessbuilding.category"); + keyBindings[1] = new KeyMapping("key.effortlessbuilding.replace.desc", KeyConflictContext.IN_GAME, InputConstants.getKey(GLFW.GLFW_KEY_KP_SUBTRACT, 0), "key.effortlessbuilding.category"); + keyBindings[2] = new KeyMapping("key.effortlessbuilding.mode.desc", KeyConflictContext.IN_GAME, InputConstants.getKey(GLFW.GLFW_KEY_LEFT_ALT, 0), "key.effortlessbuilding.category"); + keyBindings[3] = new KeyMapping("key.effortlessbuilding.undo.desc", KeyConflictContext.IN_GAME, KeyModifier.CONTROL, InputConstants.getKey(GLFW.GLFW_KEY_Z, 0), "key.effortlessbuilding.category"); + keyBindings[4] = new KeyMapping("key.effortlessbuilding.redo.desc", KeyConflictContext.IN_GAME, KeyModifier.CONTROL, InputConstants.getKey(GLFW.GLFW_KEY_Y, 0), "key.effortlessbuilding.category"); + keyBindings[5] = new KeyMapping("key.effortlessbuilding.altplacement.desc", KeyConflictContext.IN_GAME, InputConstants.getKey(GLFW.GLFW_KEY_LEFT_CONTROL, 0), "key.effortlessbuilding.category"); + + for (KeyMapping keyBinding : keyBindings) { + event.register(keyBinding); + } + } + + @SubscribeEvent + public static void registerShaders(RegisterShadersEvent event) throws IOException { + event.registerShader(new ShaderInstance(event.getResourceManager(), + new ResourceLocation(EffortlessBuilding.MODID, "dissolve"), + DefaultVertexFormat.BLOCK), + shaderInstance -> BuildRenderTypes.dissolveShaderInstance = shaderInstance); + } + } + + @SubscribeEvent + public static void onClientTick(TickEvent.ClientTickEvent event) { + if (event.phase == TickEvent.Phase.START) { + onMouseInput(); + + //Update previousLookAt + HitResult objectMouseOver = Minecraft.getInstance().hitResult; + //Checking for null is necessary! Even in vanilla when looking down ladders it is occasionally null (instead of Type MISS) + if (objectMouseOver == null) return; + + if (currentLookAt == null) { + currentLookAt = objectMouseOver; + previousLookAt = objectMouseOver; + return; + } + + if (objectMouseOver.getType() == HitResult.Type.BLOCK) { + if (currentLookAt.getType() != HitResult.Type.BLOCK) { + currentLookAt = objectMouseOver; + previousLookAt = objectMouseOver; + } else { + if (((BlockHitResult) currentLookAt).getBlockPos() != ((BlockHitResult) objectMouseOver).getBlockPos()) { + previousLookAt = currentLookAt; + currentLookAt = objectMouseOver; + } + } + } + } else if (event.phase == TickEvent.Phase.END) { + Screen gui = Minecraft.getInstance().screen; + if (gui == null || !gui.isPauseScreen()) { + ticksInGame++; + } + } + + } + + private static void onMouseInput() { + Minecraft mc = Minecraft.getInstance(); + LocalPlayer player = mc.player; + if (player == null) return; + BuildModes.BuildModeEnum buildMode = ModeSettingsManager.getModeSettings(player).getBuildMode(); + + if (mc.screen != null || + buildMode == BuildModes.BuildModeEnum.DISABLED || + RadialMenu.instance.isVisible()) { + return; + } + + if (mc.options.keyUse.isDown()) { + + //KeyBinding.setKeyBindState(mc.gameSettings.keyBindUseItem.getKeyCode(), false); + + if (placeCooldown <= 0) { + placeCooldown = 4; + + ItemStack currentItemStack = player.getItemInHand(InteractionHand.MAIN_HAND); + if (currentItemStack.getItem() instanceof BlockItem || + (CompatHelper.isItemBlockProxy(currentItemStack) && !player.isShiftKeyDown())) { + + ItemStack itemStack = CompatHelper.getItemBlockFromStack(currentItemStack); + + //find position in distance + HitResult lookingAt = getLookingAt(player); + if (lookingAt != null && lookingAt.getType() == HitResult.Type.BLOCK) { + BlockHitResult blockLookingAt = (BlockHitResult) lookingAt; + + BuildModes.onBlockPlacedMessage(player, new BlockPlacedMessage(blockLookingAt, true)); + PacketHandler.INSTANCE.sendToServer(new BlockPlacedMessage(blockLookingAt, true)); + + //play sound if further than normal + if ((blockLookingAt.getLocation().subtract(player.getEyePosition(1f))).lengthSqr() > 25f && + itemStack.getItem() instanceof BlockItem) { + + BlockState state = ((BlockItem) itemStack.getItem()).getBlock().defaultBlockState(); + BlockPos blockPos = blockLookingAt.getBlockPos(); + SoundType soundType = state.getBlock().getSoundType(state, player.level, blockPos, player); + player.level.playSound(player, player.blockPosition(), soundType.getPlaceSound(), SoundSource.BLOCKS, + 0.4f, soundType.getPitch()); + player.swing(InteractionHand.MAIN_HAND); + } + } else { + BuildModes.onBlockPlacedMessage(player, new BlockPlacedMessage()); + PacketHandler.INSTANCE.sendToServer(new BlockPlacedMessage()); + } + } + } else if (buildMode == BuildModes.BuildModeEnum.SINGLE) { + placeCooldown--; + if (ModeOptions.getBuildSpeed() == ModeOptions.ActionEnum.FAST_SPEED) placeCooldown = 0; + } + } else { + placeCooldown = 0; + } + + if (mc.options.keyAttack.isDown()) { + + //Break block in distance in creative (or survival if enabled in config) + if (breakCooldown <= 0) { + breakCooldown = 4; + + HitResult lookingAt = getLookingAt(player); + if (lookingAt != null && lookingAt.getType() == HitResult.Type.BLOCK) { + BlockHitResult blockLookingAt = (BlockHitResult) lookingAt; + + BuildModes.onBlockBrokenMessage(player, new BlockBrokenMessage(blockLookingAt)); + PacketHandler.INSTANCE.sendToServer(new BlockBrokenMessage(blockLookingAt)); + + //play sound if further than normal + if ((blockLookingAt.getLocation().subtract(player.getEyePosition(1f))).lengthSqr() > 25f) { + + BlockPos blockPos = blockLookingAt.getBlockPos(); + BlockState state = player.level.getBlockState(blockPos); + SoundType soundtype = state.getBlock().getSoundType(state, player.level, blockPos, player); + player.level.playSound(player, player.blockPosition(), soundtype.getBreakSound(), SoundSource.BLOCKS, + 0.4f, soundtype.getPitch()); + player.swing(InteractionHand.MAIN_HAND); + } + } else { + BuildModes.onBlockBrokenMessage(player, new BlockBrokenMessage()); + PacketHandler.INSTANCE.sendToServer(new BlockBrokenMessage()); + } + } else if (buildMode == BuildModes.BuildModeEnum.SINGLE) { + breakCooldown--; + if (ModeOptions.getBuildSpeed() == ModeOptions.ActionEnum.FAST_SPEED) breakCooldown = 0; + } + + //EffortlessBuilding.packetHandler.sendToServer(new CancelModeMessage()); + + } else { + breakCooldown = 0; + } + } + + @SubscribeEvent(receiveCanceled = true) + public static void onKeyPress(InputEvent.Key event) { + LocalPlayer player = Minecraft.getInstance().player; + if (player == null) + return; + + //Remember to send packet to server if necessary + //Show Modifier Settings GUI + if (keyBindings[0].consumeClick()) { + openModifierSettings(); + } + + //QuickReplace toggle + if (keyBindings[1].consumeClick()) { + ModifierSettingsManager.ModifierSettings modifierSettings = ModifierSettingsManager.getModifierSettings(player); + modifierSettings.setQuickReplace(!modifierSettings.doQuickReplace()); + EffortlessBuilding.log(player, "Set " + ChatFormatting.GOLD + "Quick Replace " + ChatFormatting.RESET + ( + modifierSettings.doQuickReplace() ? "on" : "off")); + PacketHandler.INSTANCE.sendToServer(new ModifierSettingsMessage(modifierSettings)); + } + + //Radial menu + if (keyBindings[2].isDown()) { + if (ReachHelper.getMaxReach(player) > 0) { + if (!RadialMenu.instance.isVisible()) { + Minecraft.getInstance().setScreen(RadialMenu.instance); + } + } else { + EffortlessBuilding.log(player, "Build modes are disabled until your reach has increased. Increase your reach with craftable reach upgrades."); + } + } + + //Undo (Ctrl+Z) + if (keyBindings[3].consumeClick()) { + ModeOptions.ActionEnum action = ModeOptions.ActionEnum.UNDO; + ModeOptions.performAction(player, action); + PacketHandler.INSTANCE.sendToServer(new ModeActionMessage(action)); + } + + //Redo (Ctrl+Y) + if (keyBindings[4].consumeClick()) { + ModeOptions.ActionEnum action = ModeOptions.ActionEnum.REDO; + ModeOptions.performAction(player, action); + PacketHandler.INSTANCE.sendToServer(new ModeActionMessage(action)); + } + + //Change placement mode + if (keyBindings[5].consumeClick()) { + //Toggle between first two actions of the first option of the current build mode + BuildModes.BuildModeEnum currentBuildMode = ModeSettingsManager.getModeSettings(player).getBuildMode(); + if (currentBuildMode.options.length > 0) { + ModeOptions.OptionEnum option = currentBuildMode.options[0]; + if (option.actions.length >= 2) { + if (ModeOptions.getOptionSetting(option) == option.actions[0]) { + ModeOptions.performAction(player, option.actions[1]); + PacketHandler.INSTANCE.sendToServer(new ModeActionMessage(option.actions[1])); + } else { + ModeOptions.performAction(player, option.actions[0]); + PacketHandler.INSTANCE.sendToServer(new ModeActionMessage(option.actions[0])); + } + } + } + } + + } + + public static void openModifierSettings() { + Minecraft mc = Minecraft.getInstance(); + LocalPlayer player = mc.player; + if (player == null) return; + + //Disabled if max reach is 0, might be set in the config that way. + if (ReachHelper.getMaxReach(player) == 0) { + EffortlessBuilding.log(player, "Build modifiers are disabled until your reach has increased. Increase your reach with craftable reach upgrades."); + } else { + mc.setScreen(new ModifierSettingsGui()); + } + } + + public static void openPlayerSettings() { + Minecraft mc = Minecraft.getInstance(); + mc.setScreen(new PlayerSettingsGui()); + } + + @SubscribeEvent + public static void onGuiOpen(ScreenEvent event) { + Player player = Minecraft.getInstance().player; + if (player != null) { + BuildModes.initializeMode(player); + } + } + + public static boolean isKeybindDown(int keybindIndex) { + return InputConstants.isKeyDown( + Minecraft.getInstance().getWindow().getWindow(), + keyBindings[2].getKey().getValue()); + } + + public static HitResult getLookingAt(Player player) { + Level world = player.level; + + //base distance off of player ability (config) + float raytraceRange = ReachHelper.getPlacementReach(player); + + Vec3 look = player.getLookAngle(); + Vec3 start = new Vec3(player.getX(), player.getY() + player.getEyeHeight(), player.getZ()); + Vec3 end = new Vec3(player.getX() + look.x * raytraceRange, player.getY() + player.getEyeHeight() + look.y * raytraceRange, player.getZ() + look.z * raytraceRange); +// return player.rayTrace(raytraceRange, 1f, RayTraceFluidMode.NEVER); + //TODO 1.14 check if correct + return world.clip(new ClipContext(start, end, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, player)); + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/BuildConfig.java b/src/main/java/nl/requios/effortlessbuilding/CommonConfig.java similarity index 74% rename from src/main/java/nl/requios/effortlessbuilding/BuildConfig.java rename to src/main/java/nl/requios/effortlessbuilding/CommonConfig.java index 2c84c58..9db7c50 100644 --- a/src/main/java/nl/requios/effortlessbuilding/BuildConfig.java +++ b/src/main/java/nl/requios/effortlessbuilding/CommonConfig.java @@ -1,8 +1,9 @@ package nl.requios.effortlessbuilding; import net.minecraftforge.common.ForgeConfigSpec; +import nl.requios.effortlessbuilding.create.foundation.render.SuperByteBufferCache; -public class BuildConfig { +public class CommonConfig { private static final ForgeConfigSpec.Builder builder = new ForgeConfigSpec.Builder(); public static final Reach reach = new Reach(builder); @@ -80,33 +81,21 @@ public class BuildConfig { } public static class Visuals { - public final ForgeConfigSpec.ConfigValue alwaysShowBlockPreview; - public final ForgeConfigSpec.ConfigValue dissolveTimeMultiplier; - public final ForgeConfigSpec.ConfigValue shaderThreshold; - public final ForgeConfigSpec.ConfigValue useShaders; + public final ForgeConfigSpec.ConfigValue appearAnimationLength; + public final ForgeConfigSpec.ConfigValue breakAnimationLength; - public Visuals(ForgeConfigSpec.Builder builder) { + public Visuals(ForgeConfigSpec.Builder builder) { builder.push("Visuals"); - alwaysShowBlockPreview = builder - .comment("Show a block preview if you have a block in hand even in the 'Normal' build mode") - .define("alwaysShowBlockPreview", false); + appearAnimationLength = builder + .comment("How long it takes for a block to appear when placed in ticks.", + "Set to 0 to disable animation.") + .define("appearAnimationLength", 5); - dissolveTimeMultiplier = builder - .comment("How long the dissolve effect takes when placing blocks.", - "Default between 30 and 60 ticks, you can multiply that here.", - "Recommended values:", - "Snappy: 0.7", - "Relaxing: 1.5") - .define("dissolveTimeMultiplier", 1.0); - - shaderThreshold = builder - .comment("Switch to using the simple performance shader when placing more than this many blocks.") - .define("shaderTreshold", 1500); - - useShaders = builder - .comment("Use fancy shaders while placing blocks") - .define("useShaders", true); + breakAnimationLength = builder + .comment("How long the break animation is in ticks.", + "Set to 0 to disable animation.") + .define("breakAnimationLength", 10); builder.pop(); } diff --git a/src/main/java/nl/requios/effortlessbuilding/EventHandler.java b/src/main/java/nl/requios/effortlessbuilding/CommonEvents.java similarity index 88% rename from src/main/java/nl/requios/effortlessbuilding/EventHandler.java rename to src/main/java/nl/requios/effortlessbuilding/CommonEvents.java index 3aec2e2..541555e 100644 --- a/src/main/java/nl/requios/effortlessbuilding/EventHandler.java +++ b/src/main/java/nl/requios/effortlessbuilding/CommonEvents.java @@ -8,13 +8,14 @@ import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.player.Player; import net.minecraft.server.level.ServerPlayer; import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.common.capabilities.RegisterCapabilitiesEvent; import net.minecraftforge.common.util.FakePlayer; import net.minecraftforge.event.AttachCapabilitiesEvent; -import net.minecraftforge.event.RegisterCommandsEvent; +import net.minecraftforge.event.TickEvent; import net.minecraftforge.event.entity.player.PlayerEvent; import net.minecraftforge.event.level.BlockEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; -import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.Mod.EventBusSubscriber; import net.minecraftforge.network.PacketDistributor; import nl.requios.effortlessbuilding.buildmode.BuildModes; import nl.requios.effortlessbuilding.buildmode.ModeSettingsManager; @@ -29,8 +30,19 @@ import nl.requios.effortlessbuilding.network.ClearUndoMessage; import nl.requios.effortlessbuilding.network.PacketHandler; import nl.requios.effortlessbuilding.network.RequestLookAtMessage; -@Mod.EventBusSubscriber(modid = EffortlessBuilding.MODID, bus = Mod.EventBusSubscriber.Bus.FORGE) -public class EventHandler { +@EventBusSubscriber +public class CommonEvents { + + //Mod Bus Events + @EventBusSubscriber(bus = EventBusSubscriber.Bus.MOD) + public static class ModBusEvents { + + @SubscribeEvent + public void registerCapabilities(RegisterCapabilitiesEvent event){ + event.register(ModifierCapabilityManager.IModifierCapability.class); + event.register(ModeCapabilityManager.IModeCapability.class); + } + } @SubscribeEvent public static void attachCapabilities(AttachCapabilitiesEvent event) { @@ -41,6 +53,13 @@ public class EventHandler { } } + @SubscribeEvent + public static void onTick(TickEvent.LevelTickEvent event) { + if (event.phase != TickEvent.Phase.START) return; + + EffortlessBuilding.DELAYED_BLOCK_PLACER.tick(); + } + @SubscribeEvent public static void onBlockPlaced(BlockEvent.EntityPlaceEvent event) { if (event.getLevel().isClientSide()) return; @@ -54,7 +73,7 @@ public class EventHandler { BuildModes.BuildModeEnum buildMode = ModeSettingsManager.getModeSettings(player).getBuildMode(); ModifierSettingsManager.ModifierSettings modifierSettings = ModifierSettingsManager.getModifierSettings(player); - if (buildMode != BuildModes.BuildModeEnum.NORMAL) { + if (buildMode != BuildModes.BuildModeEnum.DISABLED) { //Only cancel if itemblock in hand //Fixed issue with e.g. Create Wrench shift-rightclick disassembling being cancelled. @@ -93,7 +112,7 @@ public class EventHandler { //Cancel event if necessary //If cant break far then dont cancel event ever BuildModes.BuildModeEnum buildMode = ModeSettingsManager.getModeSettings(event.getPlayer()).getBuildMode(); - if (buildMode != BuildModes.BuildModeEnum.NORMAL && ReachHelper.canBreakFar(event.getPlayer())) { + if (buildMode != BuildModes.BuildModeEnum.DISABLED && ReachHelper.canBreakFar(event.getPlayer())) { event.setCanceled(true); } else { //NORMAL mode, let vanilla handle block breaking @@ -144,7 +163,7 @@ public class EventHandler { //Set build mode to normal ModeSettingsManager.ModeSettings modeSettings = ModeSettingsManager.getModeSettings(player); - modeSettings.setBuildMode(BuildModes.BuildModeEnum.NORMAL); + modeSettings.setBuildMode(BuildModes.BuildModeEnum.DISABLED); ModeSettingsManager.setModeSettings(player, modeSettings); //Disable modifiers @@ -172,4 +191,6 @@ public class EventHandler { ModifierSettingsManager.setModifierSettings(newPlayer, ModifierSettingsManager.getModifierSettings(oldPlayer)); ModeSettingsManager.setModeSettings(newPlayer, ModeSettingsManager.getModeSettings(oldPlayer)); } + + } diff --git a/src/main/java/nl/requios/effortlessbuilding/EffortlessBuilding.java b/src/main/java/nl/requios/effortlessbuilding/EffortlessBuilding.java index 4331413..5748382 100644 --- a/src/main/java/nl/requios/effortlessbuilding/EffortlessBuilding.java +++ b/src/main/java/nl/requios/effortlessbuilding/EffortlessBuilding.java @@ -5,25 +5,24 @@ import net.minecraft.world.entity.player.Player; import net.minecraft.world.inventory.AbstractContainerMenu; import net.minecraft.world.inventory.MenuType; import net.minecraft.world.item.Item; -import net.minecraftforge.common.capabilities.RegisterCapabilitiesEvent; -import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.eventbus.api.IEventBus; import net.minecraftforge.fml.DistExecutor; import net.minecraftforge.fml.ModLoadingContext; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.config.ModConfig; -import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; import net.minecraftforge.network.IContainerFactory; -import net.minecraftforge.registries.RegistryObject; import net.minecraftforge.registries.DeferredRegister; import net.minecraftforge.registries.ForgeRegistries; -import nl.requios.effortlessbuilding.capability.ModeCapabilityManager; -import nl.requios.effortlessbuilding.capability.ModifierCapabilityManager; +import net.minecraftforge.registries.RegistryObject; import nl.requios.effortlessbuilding.compatibility.CompatHelper; import nl.requios.effortlessbuilding.gui.DiamondRandomizerBagContainer; import nl.requios.effortlessbuilding.gui.GoldenRandomizerBagContainer; import nl.requios.effortlessbuilding.gui.RandomizerBagContainer; +import nl.requios.effortlessbuilding.helper.DelayedBlockPlacer; import nl.requios.effortlessbuilding.item.*; import nl.requios.effortlessbuilding.network.PacketHandler; import nl.requios.effortlessbuilding.proxy.ClientProxy; @@ -39,7 +38,9 @@ public class EffortlessBuilding { public static final Logger logger = LogManager.getLogger(); public static EffortlessBuilding instance; - public static IProxy proxy = DistExecutor.runForDist(() -> ClientProxy::new, () -> ServerProxy::new); + public static IProxy proxy = DistExecutor.safeRunForDist(() -> ClientProxy::new, () -> ServerProxy::new); + + public static final DelayedBlockPlacer DELAYED_BLOCK_PLACER = new DelayedBlockPlacer(); //Registration private static final DeferredRegister ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, MODID); @@ -59,14 +60,26 @@ public class EffortlessBuilding { public EffortlessBuilding() { instance = this; - // Register ourselves for server and other game events we are interested in - FMLJavaModLoadingContext.get().getModEventBus().register(this); + ModLoadingContext modLoadingContext = ModLoadingContext.get(); + IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus(); + IEventBus forgeEventBus = MinecraftForge.EVENT_BUS; + + modEventBus.addListener(EffortlessBuilding::setup); + + DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> EffortlessBuildingClient.onConstructorClient(modEventBus, forgeEventBus)); ITEMS.register(FMLJavaModLoadingContext.get().getModEventBus()); CONTAINERS.register(FMLJavaModLoadingContext.get().getModEventBus()); //Register config - ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, BuildConfig.spec); + ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, CommonConfig.spec); + ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, ClientConfig.spec); + } + + public static void setup(final FMLCommonSetupEvent event) { + PacketHandler.register(); + + CompatHelper.setup(); } public static MenuType registerContainer(IContainerFactory fact){ @@ -74,26 +87,6 @@ public class EffortlessBuilding { return type; } - @SubscribeEvent - public void setup(final FMLCommonSetupEvent event) { - PacketHandler.register(); - - proxy.setup(event); - - CompatHelper.setup(); - } - - @SubscribeEvent - public void clientSetup(final FMLClientSetupEvent event) { - proxy.clientSetup(event); - } - - @SubscribeEvent - public void registerCapabilities(RegisterCapabilitiesEvent event){ - event.register(ModifierCapabilityManager.IModifierCapability.class); - event.register(ModeCapabilityManager.IModeCapability.class); - } - public static void log(String msg) { logger.info(msg); } @@ -110,4 +103,5 @@ public class EffortlessBuilding { public static void logTranslate(Player player, String prefix, String translationKey, String suffix, boolean actionBar) { proxy.logTranslate(player, prefix, translationKey, suffix, actionBar); } + } diff --git a/src/main/java/nl/requios/effortlessbuilding/EffortlessBuildingClient.java b/src/main/java/nl/requios/effortlessbuilding/EffortlessBuildingClient.java new file mode 100644 index 0000000..91d64d0 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/EffortlessBuildingClient.java @@ -0,0 +1,21 @@ +package nl.requios.effortlessbuilding; + +import net.minecraft.client.gui.screens.MenuScreens; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; +import nl.requios.effortlessbuilding.gui.DiamondRandomizerBagScreen; +import nl.requios.effortlessbuilding.gui.GoldenRandomizerBagScreen; +import nl.requios.effortlessbuilding.gui.RandomizerBagScreen; + +public class EffortlessBuildingClient { + + public static void onConstructorClient(IEventBus modEventBus, IEventBus forgeEventBus) { + modEventBus.addListener(EffortlessBuildingClient::clientSetup); + } + + public static void clientSetup(final FMLClientSetupEvent event) { + MenuScreens.register(EffortlessBuilding.RANDOMIZER_BAG_CONTAINER.get(), RandomizerBagScreen::new); + MenuScreens.register(EffortlessBuilding.GOLDEN_RANDOMIZER_BAG_CONTAINER.get(), GoldenRandomizerBagScreen::new); + MenuScreens.register(EffortlessBuilding.DIAMOND_RANDOMIZER_BAG_CONTAINER.get(), DiamondRandomizerBagScreen::new); + } +} diff --git a/src/main/java/nl/requios/effortlessbuilding/ModClientEventHandler.java b/src/main/java/nl/requios/effortlessbuilding/ModClientEventHandler.java deleted file mode 100644 index 2218e4f..0000000 --- a/src/main/java/nl/requios/effortlessbuilding/ModClientEventHandler.java +++ /dev/null @@ -1,51 +0,0 @@ -package nl.requios.effortlessbuilding; - -import com.mojang.blaze3d.platform.InputConstants; -import com.mojang.blaze3d.vertex.DefaultVertexFormat; -import net.minecraft.client.KeyMapping; -import net.minecraft.client.renderer.ShaderInstance; -import net.minecraft.resources.ResourceLocation; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.client.event.RegisterKeyMappingsEvent; -import net.minecraftforge.client.event.RegisterShadersEvent; -import net.minecraftforge.client.settings.KeyConflictContext; -import net.minecraftforge.client.settings.KeyModifier; -import net.minecraftforge.eventbus.api.SubscribeEvent; -import net.minecraftforge.fml.common.Mod; -import nl.requios.effortlessbuilding.proxy.ClientProxy; -import nl.requios.effortlessbuilding.render.BuildRenderTypes; -import org.lwjgl.glfw.GLFW; - -import java.io.IOException; - -@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD, value = {Dist.CLIENT}) -public class ModClientEventHandler { - - @SubscribeEvent - public static void registerKeyMappings(RegisterKeyMappingsEvent event) { - EffortlessBuilding.log("Registering KeyMappings!"); - - // register key bindings - ClientProxy.keyBindings = new KeyMapping[6]; - - // instantiate the key bindings - ClientProxy.keyBindings[0] = new KeyMapping("key.effortlessbuilding.hud.desc", KeyConflictContext.IN_GAME, InputConstants.getKey(GLFW.GLFW_KEY_KP_ADD, 0), "key.effortlessbuilding.category"); - ClientProxy.keyBindings[1] = new KeyMapping("key.effortlessbuilding.replace.desc", KeyConflictContext.IN_GAME, InputConstants.getKey(GLFW.GLFW_KEY_KP_SUBTRACT, 0), "key.effortlessbuilding.category"); - ClientProxy.keyBindings[2] = new KeyMapping("key.effortlessbuilding.mode.desc", KeyConflictContext.IN_GAME, InputConstants.getKey(GLFW.GLFW_KEY_LEFT_ALT, 0), "key.effortlessbuilding.category"); - ClientProxy.keyBindings[3] = new KeyMapping("key.effortlessbuilding.undo.desc", KeyConflictContext.IN_GAME, KeyModifier.CONTROL, InputConstants.getKey(GLFW.GLFW_KEY_Z, 0), "key.effortlessbuilding.category"); - ClientProxy.keyBindings[4] = new KeyMapping("key.effortlessbuilding.redo.desc", KeyConflictContext.IN_GAME, KeyModifier.CONTROL, InputConstants.getKey(GLFW.GLFW_KEY_Y, 0), "key.effortlessbuilding.category"); - ClientProxy.keyBindings[5] = new KeyMapping("key.effortlessbuilding.altplacement.desc", KeyConflictContext.IN_GAME, InputConstants.getKey(GLFW.GLFW_KEY_LEFT_CONTROL, 0), "key.effortlessbuilding.category"); - - for (KeyMapping keyBinding : ClientProxy.keyBindings) { - event.register(keyBinding); - } - } - - @SubscribeEvent - public static void registerShaders(RegisterShadersEvent event) throws IOException { - event.registerShader(new ShaderInstance(event.getResourceManager(), - new ResourceLocation(EffortlessBuilding.MODID, "dissolve"), - DefaultVertexFormat.BLOCK), - shaderInstance -> BuildRenderTypes.dissolveShaderInstance = shaderInstance); - } -} diff --git a/src/main/java/nl/requios/effortlessbuilding/buildmode/BuildModes.java b/src/main/java/nl/requios/effortlessbuilding/buildmode/BuildModes.java index 2812685..469f5cb 100644 --- a/src/main/java/nl/requios/effortlessbuilding/buildmode/BuildModes.java +++ b/src/main/java/nl/requios/effortlessbuilding/buildmode/BuildModes.java @@ -66,7 +66,7 @@ public class BuildModes { //Check if player reach does not exceed startpos int maxReach = ReachHelper.getMaxReach(player); - if (buildMode != BuildModeEnum.NORMAL && player.blockPosition().distSqr(startPos) > maxReach * maxReach) { + if (buildMode != BuildModeEnum.DISABLED && player.blockPosition().distSqr(startPos) > maxReach * maxReach) { EffortlessBuilding.log(player, "Placement exceeds your reach."); return; } @@ -186,9 +186,6 @@ public class BuildModes { return new Vec3(x, y, z); } - - //-- Common build mode functionality --// - public static Vec3 findYBound(double y, Vec3 start, Vec3 look) { //then x and z are double x = (y - start.y) / look.y * look.x + start.x; @@ -246,8 +243,8 @@ public class BuildModes { } public enum BuildModeEnum { - NORMAL("normal", new Normal(), BuildModeCategoryEnum.BASIC), - NORMAL_PLUS("normal_plus", new NormalPlus(), BuildModeCategoryEnum.BASIC, OptionEnum.BUILD_SPEED), + DISABLED("normal", new Disabled(), BuildModeCategoryEnum.BASIC), + SINGLE("normal_plus", new Single(), BuildModeCategoryEnum.BASIC, OptionEnum.BUILD_SPEED), LINE("line", new Line(), BuildModeCategoryEnum.BASIC /*, OptionEnum.THICKNESS*/), WALL("wall", new Wall(), BuildModeCategoryEnum.BASIC, OptionEnum.FILL), FLOOR("floor", new Floor(), BuildModeCategoryEnum.BASIC, OptionEnum.FILL), diff --git a/src/main/java/nl/requios/effortlessbuilding/buildmode/ModeOptions.java b/src/main/java/nl/requios/effortlessbuilding/buildmode/ModeOptions.java index 101c6d5..dc75454 100644 --- a/src/main/java/nl/requios/effortlessbuilding/buildmode/ModeOptions.java +++ b/src/main/java/nl/requios/effortlessbuilding/buildmode/ModeOptions.java @@ -2,6 +2,7 @@ package nl.requios.effortlessbuilding.buildmode; import net.minecraft.world.entity.player.Player; import net.minecraft.ChatFormatting; +import nl.requios.effortlessbuilding.ClientEvents; import nl.requios.effortlessbuilding.EffortlessBuilding; import nl.requios.effortlessbuilding.buildmodifier.ModifierSettingsManager; import nl.requios.effortlessbuilding.buildmodifier.UndoRedo; @@ -78,11 +79,11 @@ public class ModeOptions { break; case OPEN_MODIFIER_SETTINGS: if (player.level.isClientSide) - ClientProxy.openModifierSettings(); + ClientEvents.openModifierSettings(); break; case OPEN_PLAYER_SETTINGS: if (player.level.isClientSide) - ClientProxy.openPlayerSettings(); + ClientEvents.openPlayerSettings(); break; case NORMAL_SPEED: diff --git a/src/main/java/nl/requios/effortlessbuilding/buildmode/ModeSettingsManager.java b/src/main/java/nl/requios/effortlessbuilding/buildmode/ModeSettingsManager.java index 264939c..e34510c 100644 --- a/src/main/java/nl/requios/effortlessbuilding/buildmode/ModeSettingsManager.java +++ b/src/main/java/nl/requios/effortlessbuilding/buildmode/ModeSettingsManager.java @@ -77,7 +77,7 @@ public class ModeSettingsManager { } public static class ModeSettings { - private BuildModes.BuildModeEnum buildMode = BuildModes.BuildModeEnum.NORMAL; + private BuildModes.BuildModeEnum buildMode = BuildModes.BuildModeEnum.DISABLED; public ModeSettings() { } diff --git a/src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/NormalPlus.java b/src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/Disabled.java similarity index 95% rename from src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/NormalPlus.java rename to src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/Disabled.java index 07c3403..5ffee68 100644 --- a/src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/NormalPlus.java +++ b/src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/Disabled.java @@ -9,7 +9,7 @@ import nl.requios.effortlessbuilding.buildmode.IBuildMode; import java.util.ArrayList; import java.util.List; -public class NormalPlus implements IBuildMode { +public class Disabled implements IBuildMode { @Override public void initialize(Player player) { diff --git a/src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/Normal.java b/src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/Single.java similarity index 95% rename from src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/Normal.java rename to src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/Single.java index 3f71149..e68b6f7 100644 --- a/src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/Normal.java +++ b/src/main/java/nl/requios/effortlessbuilding/buildmode/buildmodes/Single.java @@ -9,7 +9,7 @@ import nl.requios.effortlessbuilding.buildmode.IBuildMode; import java.util.ArrayList; import java.util.List; -public class Normal implements IBuildMode { +public class Single implements IBuildMode { @Override public void initialize(Player player) { diff --git a/src/main/java/nl/requios/effortlessbuilding/buildmodifier/BuildModifiers.java b/src/main/java/nl/requios/effortlessbuilding/buildmodifier/BuildModifiers.java index db2363a..1826f37 100644 --- a/src/main/java/nl/requios/effortlessbuilding/buildmodifier/BuildModifiers.java +++ b/src/main/java/nl/requios/effortlessbuilding/buildmodifier/BuildModifiers.java @@ -14,11 +14,13 @@ import net.minecraft.core.BlockPos; import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.Vec3; import net.minecraft.world.level.Level; +import nl.requios.effortlessbuilding.CommonConfig; +import nl.requios.effortlessbuilding.EffortlessBuilding; import nl.requios.effortlessbuilding.compatibility.CompatHelper; -import nl.requios.effortlessbuilding.helper.InventoryHelper; +import nl.requios.effortlessbuilding.helper.DelayedBlockPlacer; import nl.requios.effortlessbuilding.helper.SurvivalHelper; import nl.requios.effortlessbuilding.item.AbstractRandomizerBagItem; -import nl.requios.effortlessbuilding.render.BlockPreviewRenderer; +import nl.requios.effortlessbuilding.render.BlockPreviews; import java.util.ArrayList; import java.util.Collections; @@ -42,55 +44,17 @@ public class BuildModifiers { //check if valid blockstates if (blockStates.size() == 0 || coordinates.size() != blockStates.size()) return; - //remember previous blockstates for undo - List previousBlockStates = new ArrayList<>(coordinates.size()); - List newBlockStates = new ArrayList<>(coordinates.size()); - for (BlockPos coordinate : coordinates) { - previousBlockStates.add(world.getBlockState(coordinate)); - } - if (world.isClientSide) { - BlockPreviewRenderer.onBlocksPlaced(); - - newBlockStates = blockStates; + BlockPreviews.onBlocksPlaced(); } else { - //place blocks - for (int i = placeStartPos ? 0 : 1; i < coordinates.size(); i++) { - BlockPos blockPos = coordinates.get(i); - BlockState blockState = blockStates.get(i); - ItemStack itemStack = itemStacks.get(i); + int delay = CommonConfig.visuals.appearAnimationLength.get() * 3 - 3; //DelayedBlockPlacer is called 3 times per tick? - if (world.isLoaded(blockPos)) { - //check itemstack empty - if (itemStack.isEmpty()) { - //try to find new stack, otherwise continue - itemStack = InventoryHelper.findItemStackInInventory(player, blockState.getBlock()); - if (itemStack.isEmpty()) continue; - } - SurvivalHelper.placeBlock(world, player, blockPos, blockState, itemStack, Direction.UP, hitVec, false, false, false); - } - } - - //find actual new blockstates for undo - for (BlockPos coordinate : coordinates) { - newBlockStates.add(world.getBlockState(coordinate)); - } - } - - //Set first previousBlockState to empty if in NORMAL mode, to make undo/redo work - //(Block is placed by the time it gets here, and unplaced after this) - if (!placeStartPos) previousBlockStates.set(0, Blocks.AIR.defaultBlockState()); - - //If all new blockstates are air then no use in adding it, no block was actually placed - //Can happen when e.g. placing one block in yourself - if (Collections.frequency(newBlockStates, Blocks.AIR.defaultBlockState()) != newBlockStates.size()) { - //add to undo stack - BlockPos firstPos = startCoordinates.get(0); - BlockPos secondPos = startCoordinates.get(startCoordinates.size() - 1); - UndoRedo.addUndo(player, new BlockSet(coordinates, previousBlockStates, newBlockStates, hitVec, firstPos, secondPos)); + //place blocks after delay + EffortlessBuilding.DELAYED_BLOCK_PLACER.placeBlocksDelayed(new DelayedBlockPlacer.Entry(world, player, coordinates, + blockStates, itemStacks, hitVec, placeStartPos, delay)); } } @@ -109,7 +73,7 @@ public class BuildModifiers { } if (world.isClientSide) { - BlockPreviewRenderer.onBlocksBroken(); + BlockPreviews.onBlocksBroken(); //list of air blockstates for (int i = 0; i < coordinates.size(); i++) { @@ -237,6 +201,7 @@ public class BuildModifiers { } public static boolean isEnabled(ModifierSettingsManager.ModifierSettings modifierSettings, BlockPos startPos) { + //startPos can be null return Mirror.isEnabled(modifierSettings.getMirrorSettings(), startPos) || Array.isEnabled(modifierSettings.getArraySettings()) || RadialMirror.isEnabled(modifierSettings.getRadialMirrorSettings(), startPos) || diff --git a/src/main/java/nl/requios/effortlessbuilding/buildmodifier/ModifierSettingsManager.java b/src/main/java/nl/requios/effortlessbuilding/buildmodifier/ModifierSettingsManager.java index 7583398..87d9e2e 100644 --- a/src/main/java/nl/requios/effortlessbuilding/buildmodifier/ModifierSettingsManager.java +++ b/src/main/java/nl/requios/effortlessbuilding/buildmodifier/ModifierSettingsManager.java @@ -5,7 +5,7 @@ import net.minecraft.server.level.ServerPlayer; import net.minecraftforge.common.util.LazyOptional; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.network.PacketDistributor; -import nl.requios.effortlessbuilding.BuildConfig; +import nl.requios.effortlessbuilding.CommonConfig; import nl.requios.effortlessbuilding.EffortlessBuilding; import nl.requios.effortlessbuilding.capability.ModifierCapabilityManager; import nl.requios.effortlessbuilding.helper.ReachHelper; @@ -192,16 +192,16 @@ public class ModifierSettingsManager { int reach = 10; switch (reachUpgrade) { case 0: - reach = BuildConfig.reach.maxReachLevel0.get(); + reach = CommonConfig.reach.maxReachLevel0.get(); break; case 1: - reach = BuildConfig.reach.maxReachLevel1.get(); + reach = CommonConfig.reach.maxReachLevel1.get(); break; case 2: - reach = BuildConfig.reach.maxReachLevel2.get(); + reach = CommonConfig.reach.maxReachLevel2.get(); break; case 3: - reach = BuildConfig.reach.maxReachLevel3.get(); + reach = CommonConfig.reach.maxReachLevel3.get(); break; } diff --git a/src/main/java/nl/requios/effortlessbuilding/buildmodifier/UndoRedo.java b/src/main/java/nl/requios/effortlessbuilding/buildmodifier/UndoRedo.java index 8d0152c..b27b0d5 100644 --- a/src/main/java/nl/requios/effortlessbuilding/buildmodifier/UndoRedo.java +++ b/src/main/java/nl/requios/effortlessbuilding/buildmodifier/UndoRedo.java @@ -10,12 +10,12 @@ import net.minecraft.core.Direction; import net.minecraft.core.BlockPos; import net.minecraft.world.phys.Vec3; import net.minecraft.server.level.ServerLevel; -import nl.requios.effortlessbuilding.BuildConfig; +import nl.requios.effortlessbuilding.CommonConfig; import nl.requios.effortlessbuilding.EffortlessBuilding; import nl.requios.effortlessbuilding.helper.FixedStack; import nl.requios.effortlessbuilding.helper.InventoryHelper; import nl.requios.effortlessbuilding.helper.SurvivalHelper; -import nl.requios.effortlessbuilding.render.BlockPreviewRenderer; +import nl.requios.effortlessbuilding.render.BlockPreviews; import java.util.*; @@ -50,7 +50,7 @@ public class UndoRedo { //If no stack exists, make one if (!undoStacks.containsKey(player.getUUID())) { - undoStacks.put(player.getUUID(), new FixedStack<>(new BlockSet[BuildConfig.survivalBalancers.undoStackSize.get()])); + undoStacks.put(player.getUUID(), new FixedStack<>(new BlockSet[CommonConfig.survivalBalancers.undoStackSize.get()])); } undoStacks.get(player.getUUID()).push(blockSet); @@ -63,7 +63,7 @@ public class UndoRedo { //If no stack exists, make one if (!redoStacks.containsKey(player.getUUID())) { - redoStacks.put(player.getUUID(), new FixedStack<>(new BlockSet[BuildConfig.survivalBalancers.undoStackSize.get()])); + redoStacks.put(player.getUUID(), new FixedStack<>(new BlockSet[CommonConfig.survivalBalancers.undoStackSize.get()])); } redoStacks.get(player.getUUID()).push(blockSet); @@ -88,7 +88,7 @@ public class UndoRedo { List itemStacks = findItemStacksInInventory(player, previousBlockStates); if (player.level.isClientSide) { - BlockPreviewRenderer.onBlocksBroken(coordinates, itemStacks, newBlockStates, blockSet.getSecondPos(), blockSet.getFirstPos()); + BlockPreviews.onBlocksBroken(coordinates, itemStacks, newBlockStates, blockSet.getSecondPos(), blockSet.getFirstPos()); } else { //break all those blocks, reset to what they were for (int i = 0; i < coordinates.size(); i++) { @@ -98,14 +98,14 @@ public class UndoRedo { if (previousBlockStates.get(i).equals(newBlockStates.get(i))) continue; //get blockstate from itemstack - BlockState previousBlockState = Blocks.AIR.defaultBlockState(); + BlockState previousBlockState = previousBlockStates.get(i); if (itemStack.getItem() instanceof BlockItem) { previousBlockState = ((BlockItem) itemStack.getItem()).getBlock().defaultBlockState(); } if (player.level.isLoaded(coordinate)) { //check itemstack empty - if (itemStack.isEmpty()) { + if (itemStack.isEmpty() && !player.isCreative()) { itemStack = findItemStackInInventory(player, previousBlockStates.get(i)); //get blockstate from new itemstack if (!itemStack.isEmpty() && itemStack.getItem() instanceof BlockItem) { @@ -148,7 +148,7 @@ public class UndoRedo { List itemStacks = findItemStacksInInventory(player, newBlockStates); if (player.level.isClientSide) { - BlockPreviewRenderer.onBlocksPlaced(coordinates, itemStacks, newBlockStates, blockSet.getFirstPos(), blockSet.getSecondPos()); + BlockPreviews.onBlocksPlaced(coordinates, itemStacks, newBlockStates, blockSet.getFirstPos(), blockSet.getSecondPos()); } else { //place blocks for (int i = 0; i < coordinates.size(); i++) { @@ -158,14 +158,14 @@ public class UndoRedo { if (previousBlockStates.get(i).equals(newBlockStates.get(i))) continue; //get blockstate from itemstack - BlockState newBlockState = Blocks.AIR.defaultBlockState(); + BlockState newBlockState = newBlockStates.get(i); if (itemStack.getItem() instanceof BlockItem) { newBlockState = ((BlockItem) itemStack.getItem()).getBlock().defaultBlockState(); } if (player.level.isLoaded(coordinate)) { //check itemstack empty - if (itemStack.isEmpty()) { + if (itemStack.isEmpty() && !player.isCreative()) { itemStack = findItemStackInInventory(player, newBlockStates.get(i)); //get blockstate from new itemstack if (!itemStack.isEmpty() && itemStack.getItem() instanceof BlockItem) { diff --git a/src/main/java/nl/requios/effortlessbuilding/capability/ModeCapabilityManager.java b/src/main/java/nl/requios/effortlessbuilding/capability/ModeCapabilityManager.java index cc40ab2..6942e87 100644 --- a/src/main/java/nl/requios/effortlessbuilding/capability/ModeCapabilityManager.java +++ b/src/main/java/nl/requios/effortlessbuilding/capability/ModeCapabilityManager.java @@ -100,7 +100,7 @@ public class ModeCapabilityManager { //TODO add mode settings - ModeSettings modeSettings = new ModeSettings(BuildModes.BuildModeEnum.NORMAL); + ModeSettings modeSettings = new ModeSettings(BuildModes.BuildModeEnum.DISABLED); instance.setModeData(modeSettings); } diff --git a/src/main/java/nl/requios/effortlessbuilding/create/AllSpecialTextures.java b/src/main/java/nl/requios/effortlessbuilding/create/AllSpecialTextures.java new file mode 100644 index 0000000..0808f30 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/AllSpecialTextures.java @@ -0,0 +1,33 @@ +package nl.requios.effortlessbuilding.create; + +import com.mojang.blaze3d.systems.RenderSystem; +import net.minecraft.resources.ResourceLocation; + +public enum AllSpecialTextures { + + BLANK("blank.png"), + CHECKERED("checkerboard.png"), + THIN_CHECKERED("thin_checkerboard.png"), + CUTOUT_CHECKERED("cutout_checkerboard.png"), + HIGHLIGHT_CHECKERED("highlighted_checkerboard.png"), + SELECTION("selection.png"), + GLUE("glue.png"), + + ; + + public static final String ASSET_PATH = "textures/special/"; + private ResourceLocation location; + + private AllSpecialTextures(String filename) { + location = Create.asResource(ASSET_PATH + filename); + } + + public void bind() { + RenderSystem.setShaderTexture(0, location); + } + + public ResourceLocation getLocation() { + return location; + } + +} \ No newline at end of file diff --git a/src/main/java/nl/requios/effortlessbuilding/create/Create.java b/src/main/java/nl/requios/effortlessbuilding/create/Create.java new file mode 100644 index 0000000..70d417c --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/Create.java @@ -0,0 +1,16 @@ +package nl.requios.effortlessbuilding.create; + +import com.mojang.logging.LogUtils; +import net.minecraft.resources.ResourceLocation; +import nl.requios.effortlessbuilding.EffortlessBuilding; +import org.slf4j.Logger; + +public class Create { + public static final String ID = EffortlessBuilding.MODID; + + public static final Logger LOGGER = LogUtils.getLogger(); + + public static ResourceLocation asResource(String path) { + return new ResourceLocation(EffortlessBuilding.MODID, path); + } +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/CreateClient.java b/src/main/java/nl/requios/effortlessbuilding/create/CreateClient.java new file mode 100644 index 0000000..a983c25 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/CreateClient.java @@ -0,0 +1,15 @@ +package nl.requios.effortlessbuilding.create; + +import nl.requios.effortlessbuilding.create.foundation.render.SuperByteBufferCache; +import nl.requios.effortlessbuilding.create.foundation.utility.ghost.GhostBlocks; +import nl.requios.effortlessbuilding.create.foundation.utility.outliner.Outliner; + +public class CreateClient { + public static final SuperByteBufferCache BUFFER_CACHE = new SuperByteBufferCache(); + public static final Outliner OUTLINER = new Outliner(); + public static final GhostBlocks GHOST_BLOCKS = new GhostBlocks(); + + public static void invalidateRenderers() { + CreateClient.BUFFER_CACHE.invalidate(); + } +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/CreateClientTest.java b/src/main/java/nl/requios/effortlessbuilding/create/CreateClientTest.java new file mode 100644 index 0000000..c19bd0b --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/CreateClientTest.java @@ -0,0 +1,31 @@ +package nl.requios.effortlessbuilding.create; + +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.phys.AABB; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.event.TickEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; +import nl.requios.effortlessbuilding.create.foundation.utility.Color; + +@Mod.EventBusSubscriber(Dist.CLIENT) +public class CreateClientTest { + +// @SubscribeEvent +// public static void onTick(TickEvent.ClientTickEvent event) { +// CreateClient.GHOST_BLOCKS.showGhostState(1, Blocks.SPRUCE_LOG.defaultBlockState()) +// .at(0, 120, 0) +// .breathingAlpha(); +// CreateClient.GHOST_BLOCKS.showGhostState(2, Blocks.SPRUCE_LOG.defaultBlockState()) +// .at(1, 120, 0) +// .breathingAlpha(); +// +// CreateClient.OUTLINER.showAABB(1, new AABB(0, 0, 0, 10, 2, 6) +// .move(10, 120, 0)) +// .withFaceTexture(AllSpecialTextures.CHECKERED) +// .colored(new Color(0.11f, 0.49f, 0.7f, 1f)) +//// .colored(0xbfbfbf) +// .disableNormals() +// .lineWidth(1 / 32f); +// } +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/events/ClientEvents.java b/src/main/java/nl/requios/effortlessbuilding/create/events/ClientEvents.java new file mode 100644 index 0000000..c849e88 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/events/ClientEvents.java @@ -0,0 +1,96 @@ +package nl.requios.effortlessbuilding.create.events; + +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.phys.Vec3; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.client.event.RenderLevelLastEvent; +import net.minecraftforge.client.event.ViewportEvent; +import net.minecraftforge.event.TickEvent.ClientTickEvent; +import net.minecraftforge.event.level.LevelEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod.EventBusSubscriber; +import nl.requios.effortlessbuilding.create.Create; +import nl.requios.effortlessbuilding.create.CreateClient; +import nl.requios.effortlessbuilding.create.foundation.render.SuperRenderTypeBuffer; +import nl.requios.effortlessbuilding.create.foundation.utility.AnimationTickHolder; +import nl.requios.effortlessbuilding.create.foundation.utility.CameraAngleAnimationService; +import nl.requios.effortlessbuilding.create.foundation.utility.worldWrappers.WrappedClientWorld; + +@EventBusSubscriber(Dist.CLIENT) +public class ClientEvents { + + private static final String ITEM_PREFIX = "item." + Create.ID; + private static final String BLOCK_PREFIX = "block." + Create.ID; + + @SubscribeEvent + public static void onTick(ClientTickEvent event) { + if (!isGameActive()) + return; + + Level world = Minecraft.getInstance().level; + AnimationTickHolder.tick(); + + CreateClient.GHOST_BLOCKS.tickGhosts(); + CreateClient.OUTLINER.tickOutlines(); + CameraAngleAnimationService.tick(); + } + + @SubscribeEvent + public static void onLoadWorld(LevelEvent.Load event) { + LevelAccessor world = event.getLevel(); + if (world.isClientSide() && world instanceof ClientLevel && !(world instanceof WrappedClientWorld)) { + CreateClient.invalidateRenderers(); + AnimationTickHolder.reset(); + } + } + + @SubscribeEvent + public static void onUnloadWorld(LevelEvent.Unload event) { + if (!event.getLevel() + .isClientSide()) + return; + CreateClient.invalidateRenderers(); + AnimationTickHolder.reset(); + } + + @SubscribeEvent + public static void onRenderWorld(RenderLevelLastEvent event) { + Vec3 cameraPos = Minecraft.getInstance().gameRenderer.getMainCamera() + .getPosition(); + float pt = AnimationTickHolder.getPartialTicks(); + + PoseStack ms = event.getPoseStack(); + ms.pushPose(); + ms.translate(-cameraPos.x(), -cameraPos.y(), -cameraPos.z()); + SuperRenderTypeBuffer buffer = SuperRenderTypeBuffer.getInstance(); + + CreateClient.GHOST_BLOCKS.renderAll(ms, buffer); + + CreateClient.OUTLINER.renderOutlines(ms, buffer, pt); + buffer.draw(); + RenderSystem.enableCull(); + + ms.popPose(); + } + + @SubscribeEvent + public static void onCameraSetup(ViewportEvent.ComputeCameraAngles event) { + float partialTicks = AnimationTickHolder.getPartialTicks(); + + if (CameraAngleAnimationService.isYawAnimating()) + event.setYaw(CameraAngleAnimationService.getYaw(partialTicks)); + + if (CameraAngleAnimationService.isPitchAnimating()) + event.setPitch(CameraAngleAnimationService.getPitch(partialTicks)); + } + + public static boolean isGameActive() { + return !(Minecraft.getInstance().level == null || Minecraft.getInstance().player == null); + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/events/CommonEvents.java b/src/main/java/nl/requios/effortlessbuilding/create/events/CommonEvents.java new file mode 100644 index 0000000..5e83123 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/events/CommonEvents.java @@ -0,0 +1,39 @@ +package nl.requios.effortlessbuilding.create.events; + +import net.minecraft.world.level.LevelAccessor; +import net.minecraftforge.event.level.LevelEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod.EventBusSubscriber; +import nl.requios.effortlessbuilding.create.foundation.utility.WorldAttached; + +@EventBusSubscriber +public class CommonEvents { + + @SubscribeEvent + public static void onUnloadWorld(LevelEvent.Unload event) { + LevelAccessor world = event.getLevel(); + WorldAttached.invalidateWorld(world); + } + + + @EventBusSubscriber(bus = EventBusSubscriber.Bus.MOD) + public static class ModBusEvents { + +// @SubscribeEvent +// public static void addPackFinders(AddPackFindersEvent event) { +// if (event.getPackType() == PackType.CLIENT_RESOURCES) { +// IModFileInfo modFileInfo = ModList.get().getModFileById(Create.ID); +// if (modFileInfo == null) { +// Create.LOGGER.error("Could not find Create mod file info; built-in resource packs will be missing!"); +// return; +// } +// IModFile modFile = modFileInfo.getFile(); +// event.addRepositorySource((consumer, constructor) -> { +// consumer.accept(Pack.create(Create.asResource("legacy_copper").toString(), false, () -> new ModFilePackResources("Create Legacy Copper", modFile, "resourcepacks/legacy_copper"), constructor, Pack.Position.TOP, PackSource.DEFAULT)); +// }); +// } +// } + + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/ClientResourceReloadListener.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/ClientResourceReloadListener.java new file mode 100644 index 0000000..f3ec8d0 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/ClientResourceReloadListener.java @@ -0,0 +1,16 @@ +package nl.requios.effortlessbuilding.create.foundation; + +import nl.requios.effortlessbuilding.create.foundation.utility.LangNumberFormat; +import net.minecraft.server.packs.resources.ResourceManager; +import net.minecraft.server.packs.resources.ResourceManagerReloadListener; + +public class ClientResourceReloadListener implements ResourceManagerReloadListener { + + @Override + public void onResourceManagerReload(ResourceManager resourceManager) { +// CreateClient.invalidateRenderers(); +// SoundScapes.invalidateAll(); + LangNumberFormat.numberFormat.update(); + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/ModFilePackResources.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/ModFilePackResources.java new file mode 100644 index 0000000..e3715db --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/ModFilePackResources.java @@ -0,0 +1,25 @@ +package nl.requios.effortlessbuilding.create.foundation; + +import net.minecraftforge.forgespi.locating.IModFile; +import net.minecraftforge.resource.PathPackResources; + +import java.nio.file.Path; + +public class ModFilePackResources extends PathPackResources { + protected final IModFile modFile; + protected final String sourcePath; + + public ModFilePackResources(String name, IModFile modFile, String sourcePath) { + super(name, modFile.findResource(sourcePath)); + this.modFile = modFile; + this.sourcePath = sourcePath; + } + + @Override + protected Path resolve(String... paths) { + String[] allPaths = new String[paths.length + 1]; + allPaths[0] = sourcePath; + System.arraycopy(paths, 0, allPaths, 1, paths.length); + return modFile.findResource(allPaths); + } +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/block/render/SpriteShiftEntry.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/block/render/SpriteShiftEntry.java new file mode 100644 index 0000000..334dafe --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/block/render/SpriteShiftEntry.java @@ -0,0 +1,49 @@ +package nl.requios.effortlessbuilding.create.foundation.block.render; + +import com.jozufozu.flywheel.core.StitchedSprite; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.resources.ResourceLocation; + +public class SpriteShiftEntry { + protected StitchedSprite original; + protected StitchedSprite target; + + public void set(ResourceLocation originalTextureLocation, ResourceLocation targetTextureLocation) { + original = new StitchedSprite(originalTextureLocation); + target = new StitchedSprite(targetTextureLocation); + } + + public ResourceLocation getOriginalResourceLocation() { + return original.getLocation(); + } + + public ResourceLocation getTargetResourceLocation() { + return target.getLocation(); + } + + public TextureAtlasSprite getOriginal() { + return original.get(); + } + + public TextureAtlasSprite getTarget() { + return target.get(); + } + + public float getTargetU(float localU) { + return getTarget().getU(getUnInterpolatedU(getOriginal(), localU)); + } + + public float getTargetV(float localV) { + return getTarget().getV(getUnInterpolatedV(getOriginal(), localV)); + } + + public static float getUnInterpolatedU(TextureAtlasSprite sprite, float u) { + float f = sprite.getU1() - sprite.getU0(); + return (u - sprite.getU0()) / f * 16.0F; + } + + public static float getUnInterpolatedV(TextureAtlasSprite sprite, float v) { + float f = sprite.getV1() - sprite.getV0(); + return (v - sprite.getV0()) / f * 16.0F; + } +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/render/BakedModelRenderHelper.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/render/BakedModelRenderHelper.java new file mode 100644 index 0000000..695c31c --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/render/BakedModelRenderHelper.java @@ -0,0 +1,29 @@ +package nl.requios.effortlessbuilding.create.foundation.render; + +import com.jozufozu.flywheel.core.model.ModelUtil; +import com.jozufozu.flywheel.util.Pair; +import com.mojang.blaze3d.vertex.BufferBuilder.RenderedBuffer; +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.block.BlockRenderDispatcher; +import net.minecraft.client.resources.model.BakedModel; +import net.minecraft.world.level.block.state.BlockState; + +public class BakedModelRenderHelper { + + public static SuperByteBuffer standardBlockRender(BlockState renderedState) { + BlockRenderDispatcher dispatcher = Minecraft.getInstance() + .getBlockRenderer(); + return standardModelRender(dispatcher.getBlockModel(renderedState), renderedState); + } + + public static SuperByteBuffer standardModelRender(BakedModel model, BlockState referenceState) { + return standardModelRender(model, referenceState, new PoseStack()); + } + + public static SuperByteBuffer standardModelRender(BakedModel model, BlockState referenceState, PoseStack ms) { + Pair pair = ModelUtil.getBufferBuilder(model, referenceState, ms); + return new SuperByteBuffer(pair.first(), pair.second()); + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/render/CachedBufferer.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/render/CachedBufferer.java new file mode 100644 index 0000000..aca56b4 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/render/CachedBufferer.java @@ -0,0 +1,88 @@ +package nl.requios.effortlessbuilding.create.foundation.render; + +import com.jozufozu.flywheel.core.PartialModel; +import com.jozufozu.flywheel.util.transform.TransformStack; +import com.mojang.blaze3d.vertex.PoseStack; +import nl.requios.effortlessbuilding.EffortlessBuildingClient; +import nl.requios.effortlessbuilding.create.CreateClient; +import nl.requios.effortlessbuilding.create.foundation.render.SuperByteBufferCache.Compartment; +import nl.requios.effortlessbuilding.create.foundation.utility.AngleHelper; +import net.minecraft.core.Direction; +import net.minecraft.world.level.block.state.BlockState; +import org.apache.commons.lang3.tuple.Pair; + +import java.util.function.Supplier; + +import static net.minecraft.world.level.block.state.properties.BlockStateProperties.FACING; + +public class CachedBufferer { + + public static final Compartment GENERIC_TILE = new Compartment<>(); + public static final Compartment PARTIAL = new Compartment<>(); + public static final Compartment> DIRECTIONAL_PARTIAL = new Compartment<>(); + + public static SuperByteBuffer block(BlockState toRender) { + return block(GENERIC_TILE, toRender); + } + + public static SuperByteBuffer block(Compartment compartment, BlockState toRender) { + return CreateClient.BUFFER_CACHE.get(compartment, toRender, () -> BakedModelRenderHelper.standardBlockRender(toRender)); + } + + public static SuperByteBuffer partial(PartialModel partial, BlockState referenceState) { + return CreateClient.BUFFER_CACHE.get(PARTIAL, partial, + () -> BakedModelRenderHelper.standardModelRender(partial.get(), referenceState)); + } + + public static SuperByteBuffer partial(PartialModel partial, BlockState referenceState, + Supplier modelTransform) { + return CreateClient.BUFFER_CACHE.get(PARTIAL, partial, + () -> BakedModelRenderHelper.standardModelRender(partial.get(), referenceState, modelTransform.get())); + } + + public static SuperByteBuffer partialFacing(PartialModel partial, BlockState referenceState) { + Direction facing = referenceState.getValue(FACING); + return partialFacing(partial, referenceState, facing); + } + + public static SuperByteBuffer partialFacing(PartialModel partial, BlockState referenceState, Direction facing) { + return partialDirectional(partial, referenceState, facing, + rotateToFace(facing)); + } + + public static SuperByteBuffer partialFacingVertical(PartialModel partial, BlockState referenceState, Direction facing) { + return partialDirectional(partial, referenceState, facing, + rotateToFaceVertical(facing)); + } + + public static SuperByteBuffer partialDirectional(PartialModel partial, BlockState referenceState, Direction dir, + Supplier modelTransform) { + return CreateClient.BUFFER_CACHE.get(DIRECTIONAL_PARTIAL, Pair.of(dir, partial), + () -> BakedModelRenderHelper.standardModelRender(partial.get(), referenceState, modelTransform.get())); + } + + public static Supplier rotateToFace(Direction facing) { + return () -> { + PoseStack stack = new PoseStack(); + TransformStack.cast(stack) + .centre() + .rotateY(AngleHelper.horizontalAngle(facing)) + .rotateX(AngleHelper.verticalAngle(facing)) + .unCentre(); + return stack; + }; + } + + public static Supplier rotateToFaceVertical(Direction facing) { + return () -> { + PoseStack stack = new PoseStack(); + TransformStack.cast(stack) + .centre() + .rotateY(AngleHelper.horizontalAngle(facing)) + .rotateX(AngleHelper.verticalAngle(facing) + 90) + .unCentre(); + return stack; + }; + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/render/ForcedDiffuseState.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/render/ForcedDiffuseState.java new file mode 100644 index 0000000..8e1b34c --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/render/ForcedDiffuseState.java @@ -0,0 +1,30 @@ +package nl.requios.effortlessbuilding.create.foundation.render; + +import com.jozufozu.flywheel.util.DiffuseLightCalculator; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; + +import javax.annotation.Nullable; + +public final class ForcedDiffuseState { + private static final ThreadLocal> FORCED_DIFFUSE = ThreadLocal.withInitial(ObjectArrayList::new); + + private ForcedDiffuseState() { + } + + public static void pushCalculator(DiffuseLightCalculator calculator) { + FORCED_DIFFUSE.get().push(calculator); + } + + public static void popCalculator() { + FORCED_DIFFUSE.get().pop(); + } + + @Nullable + public static DiffuseLightCalculator getForcedCalculator() { + ObjectArrayList stack = FORCED_DIFFUSE.get(); + if (stack.isEmpty()) { + return null; + } + return stack.top(); + } +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/render/RenderTypes.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/render/RenderTypes.java new file mode 100644 index 0000000..2ac1860 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/render/RenderTypes.java @@ -0,0 +1,160 @@ +package nl.requios.effortlessbuilding.create.foundation.render; + +import com.mojang.blaze3d.vertex.DefaultVertexFormat; +import com.mojang.blaze3d.vertex.VertexFormat; +import nl.requios.effortlessbuilding.create.AllSpecialTextures; +import nl.requios.effortlessbuilding.create.Create; +import net.minecraft.client.renderer.RenderStateShard; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.ShaderInstance; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.ResourceManager; +import net.minecraft.world.inventory.InventoryMenu; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.client.event.RegisterShadersEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod.EventBusSubscriber; + +import java.io.IOException; + +// TODO 1.17: use custom shaders instead of vanilla ones +public class RenderTypes extends RenderStateShard { + + public static final ShaderStateShard GLOWING_SHADER = new ShaderStateShard(() -> Shaders.glowingShader); + + private static final RenderType OUTLINE_SOLID = + RenderType.create(createLayerName("outline_solid"), DefaultVertexFormat.NEW_ENTITY, VertexFormat.Mode.QUADS, 256, false, + false, RenderType.CompositeState.builder() + .setShaderState(RENDERTYPE_ENTITY_SOLID_SHADER) + .setTextureState(new TextureStateShard(AllSpecialTextures.BLANK.getLocation(), false, false)) + .setCullState(CULL) + .setLightmapState(LIGHTMAP) + .setOverlayState(OVERLAY) + .createCompositeState(false)); + + public static RenderType getOutlineSolid() { + return OUTLINE_SOLID; + } + + public static RenderType getOutlineTranslucent(ResourceLocation texture, boolean cull) { + return RenderType.create(createLayerName("outline_translucent" + (cull ? "_cull" : "")), + DefaultVertexFormat.NEW_ENTITY, VertexFormat.Mode.QUADS, 256, false, true, RenderType.CompositeState.builder() + .setShaderState(cull ? RENDERTYPE_ENTITY_TRANSLUCENT_CULL_SHADER : RENDERTYPE_ENTITY_TRANSLUCENT_SHADER) + .setTextureState(new TextureStateShard(texture, false, false)) + .setTransparencyState(TRANSLUCENT_TRANSPARENCY) + .setCullState(cull ? CULL : NO_CULL) + .setLightmapState(LIGHTMAP) + .setOverlayState(OVERLAY) + .setWriteMaskState(COLOR_WRITE) + .createCompositeState(false)); + } + + public static RenderType getGlowingSolid(ResourceLocation texture) { + return RenderType.create(createLayerName("glowing_solid"), DefaultVertexFormat.NEW_ENTITY, VertexFormat.Mode.QUADS, 256, + true, false, RenderType.CompositeState.builder() + .setShaderState(GLOWING_SHADER) + .setTextureState(new TextureStateShard(texture, false, false)) + .setCullState(CULL) + .setLightmapState(LIGHTMAP) + .setOverlayState(OVERLAY) + .createCompositeState(true)); + } + + private static final RenderType GLOWING_SOLID_DEFAULT = getGlowingSolid(InventoryMenu.BLOCK_ATLAS); + + public static RenderType getGlowingSolid() { + return GLOWING_SOLID_DEFAULT; + } + + public static RenderType getGlowingTranslucent(ResourceLocation texture) { + return RenderType.create(createLayerName("glowing_translucent"), DefaultVertexFormat.NEW_ENTITY, VertexFormat.Mode.QUADS, + 256, true, true, RenderType.CompositeState.builder() + .setShaderState(GLOWING_SHADER) + .setTextureState(new TextureStateShard(texture, false, false)) + .setTransparencyState(TRANSLUCENT_TRANSPARENCY) + .setLightmapState(LIGHTMAP) + .setOverlayState(OVERLAY) + .createCompositeState(true)); + } + + private static final RenderType ADDITIVE = RenderType.create(createLayerName("additive"), DefaultVertexFormat.BLOCK, + VertexFormat.Mode.QUADS, 256, true, true, RenderType.CompositeState.builder() + .setShaderState(BLOCK_SHADER) + .setTextureState(new TextureStateShard(InventoryMenu.BLOCK_ATLAS, false, false)) + .setTransparencyState(ADDITIVE_TRANSPARENCY) + .setCullState(NO_CULL) + .setLightmapState(LIGHTMAP) + .setOverlayState(OVERLAY) + .createCompositeState(true)); + + public static RenderType getAdditive() { + return ADDITIVE; + } + + private static final RenderType GLOWING_TRANSLUCENT_DEFAULT = getGlowingTranslucent(InventoryMenu.BLOCK_ATLAS); + + public static RenderType getGlowingTranslucent() { + return GLOWING_TRANSLUCENT_DEFAULT; + } + + private static final RenderType ITEM_PARTIAL_SOLID = + RenderType.create(createLayerName("item_partial_solid"), DefaultVertexFormat.NEW_ENTITY, VertexFormat.Mode.QUADS, 256, true, + false, RenderType.CompositeState.builder() + .setShaderState(RENDERTYPE_ENTITY_SOLID_SHADER) + .setTextureState(BLOCK_SHEET) + .setCullState(CULL) + .setLightmapState(LIGHTMAP) + .setOverlayState(OVERLAY) + .createCompositeState(true)); + + public static RenderType getItemPartialSolid() { + return ITEM_PARTIAL_SOLID; + } + + private static final RenderType ITEM_PARTIAL_TRANSLUCENT = RenderType.create(createLayerName("item_partial_translucent"), + DefaultVertexFormat.NEW_ENTITY, VertexFormat.Mode.QUADS, 256, true, true, RenderType.CompositeState.builder() + .setShaderState(RENDERTYPE_ENTITY_TRANSLUCENT_CULL_SHADER) + .setTextureState(BLOCK_SHEET) + .setTransparencyState(TRANSLUCENT_TRANSPARENCY) + .setLightmapState(LIGHTMAP) + .setOverlayState(OVERLAY) + .createCompositeState(true)); + + public static RenderType getItemPartialTranslucent() { + return ITEM_PARTIAL_TRANSLUCENT; + } + + private static final RenderType FLUID = RenderType.create(createLayerName("fluid"), + DefaultVertexFormat.NEW_ENTITY, VertexFormat.Mode.QUADS, 256, false, true, RenderType.CompositeState.builder() + .setShaderState(RENDERTYPE_ENTITY_TRANSLUCENT_CULL_SHADER) + .setTextureState(BLOCK_SHEET_MIPPED) + .setTransparencyState(TRANSLUCENT_TRANSPARENCY) + .setLightmapState(LIGHTMAP) + .setOverlayState(OVERLAY) + .createCompositeState(true)); + + public static RenderType getFluid() { + return FLUID; + } + + private static String createLayerName(String name) { + return Create.ID + ":" + name; + } + + // Mmm gimme those protected fields + private RenderTypes() { + super(null, null, null); + } + + @EventBusSubscriber(value = Dist.CLIENT, bus = EventBusSubscriber.Bus.MOD) + private static class Shaders { + private static ShaderInstance glowingShader; + + @SubscribeEvent + public static void onRegisterShaders(RegisterShadersEvent event) throws IOException { + ResourceManager resourceManager = event.getResourceManager(); + event.registerShader(new ShaderInstance(resourceManager, Create.asResource("glowing_shader"), DefaultVertexFormat.NEW_ENTITY), shader -> glowingShader = shader); + } + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/render/ShadowRenderHelper.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/render/ShadowRenderHelper.java new file mode 100644 index 0000000..d81b292 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/render/ShadowRenderHelper.java @@ -0,0 +1,112 @@ +package nl.requios.effortlessbuilding.create.foundation.render; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import net.minecraft.client.renderer.LightTexture; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.texture.OverlayTexture; +import net.minecraft.core.BlockPos; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Mth; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.RenderShape; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; +import net.minecraft.world.phys.shapes.VoxelShape; + +/** + * Taken from EntityRendererManager + */ +public class ShadowRenderHelper { + + private static final RenderType SHADOW_LAYER = + RenderType.entityNoOutline(new ResourceLocation("textures/misc/shadow.png")); + + public static void renderShadow(PoseStack matrixStack, MultiBufferSource buffer, float opacity, float radius) { + PoseStack.Pose entry = matrixStack.last(); + VertexConsumer builder = buffer.getBuffer(SHADOW_LAYER); + + opacity /= 2; + shadowVertex(entry, builder, opacity, -1 * radius, 0, -1 * radius, 0, 0); + shadowVertex(entry, builder, opacity, -1 * radius, 0, 1 * radius, 0, 1); + shadowVertex(entry, builder, opacity, 1 * radius, 0, 1 * radius, 1, 1); + shadowVertex(entry, builder, opacity, 1 * radius, 0, -1 * radius, 1, 0); + } + + public static void renderShadow(PoseStack matrixStack, MultiBufferSource buffer, LevelReader world, + Vec3 pos, float opacity, float radius) { + float f = radius; + + double d2 = pos.x(); + double d0 = pos.y(); + double d1 = pos.z(); + int i = Mth.floor(d2 - (double) f); + int j = Mth.floor(d2 + (double) f); + int k = Mth.floor(d0 - (double) f); + int l = Mth.floor(d0); + int i1 = Mth.floor(d1 - (double) f); + int j1 = Mth.floor(d1 + (double) f); + PoseStack.Pose entry = matrixStack.last(); + VertexConsumer builder = buffer.getBuffer(SHADOW_LAYER); + + for (BlockPos blockpos : BlockPos.betweenClosed(new BlockPos(i, k, i1), new BlockPos(j, l, j1))) { + renderBlockShadow(entry, builder, world, blockpos, d2, d0, d1, f, + opacity); + } + } + + private static void renderBlockShadow(PoseStack.Pose entry, VertexConsumer builder, + LevelReader world, BlockPos pos, double x, double y, double z, + float radius, float opacity) { + BlockPos blockpos = pos.below(); + BlockState blockstate = world.getBlockState(blockpos); + if (blockstate.getRenderShape() != RenderShape.INVISIBLE && world.getMaxLocalRawBrightness(pos) > 3) { + if (blockstate.isCollisionShapeFullBlock(world, blockpos)) { + VoxelShape voxelshape = blockstate.getShape(world, pos.below()); + if (!voxelshape.isEmpty()) { + float brightness = LightTexture.getBrightness(world.dimensionType(), world.getMaxLocalRawBrightness(pos)); + float f = (float) ((opacity - (y - pos.getY()) / 2.0D) * 0.5D * brightness); + if (f >= 0.0F) { + if (f > 1.0F) { + f = 1.0F; + } + + AABB AABB = voxelshape.bounds(); + double d0 = (double) pos.getX() + AABB.minX; + double d1 = (double) pos.getX() + AABB.maxX; + double d2 = (double) pos.getY() + AABB.minY; + double d3 = (double) pos.getZ() + AABB.minZ; + double d4 = (double) pos.getZ() + AABB.maxZ; + float f1 = (float) (d0 - x); + float f2 = (float) (d1 - x); + float f3 = (float) (d2 - y + 0.015625D); + float f4 = (float) (d3 - z); + float f5 = (float) (d4 - z); + float f6 = -f1 / 2.0F / radius + 0.5F; + float f7 = -f2 / 2.0F / radius + 0.5F; + float f8 = -f4 / 2.0F / radius + 0.5F; + float f9 = -f5 / 2.0F / radius + 0.5F; + shadowVertex(entry, builder, f, f1, f3, f4, f6, f8); + shadowVertex(entry, builder, f, f1, f3, f5, f6, f9); + shadowVertex(entry, builder, f, f2, f3, f5, f7, f9); + shadowVertex(entry, builder, f, f2, f3, f4, f7, f8); + } + } + } + } + } + + private static void shadowVertex(PoseStack.Pose entry, VertexConsumer builder, float alpha, + float x, float y, float z, float u, float v) { + builder.vertex(entry.pose(), x, y, z) + .color(1.0F, 1.0F, 1.0F, alpha) + .uv(u, v) + .overlayCoords(OverlayTexture.NO_OVERLAY) + .uv2(LightTexture.FULL_BRIGHT) + .normal(entry.normal(), 0.0F, 1.0F, 0.0F) + .endVertex(); + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/render/SuperByteBuffer.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/render/SuperByteBuffer.java new file mode 100644 index 0000000..fff4a6f --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/render/SuperByteBuffer.java @@ -0,0 +1,464 @@ +package nl.requios.effortlessbuilding.create.foundation.render; + +import com.jozufozu.flywheel.api.vertex.ShadedVertexList; +import com.jozufozu.flywheel.backend.ShadersModHandler; +import com.jozufozu.flywheel.core.vertex.BlockVertexList; +import com.jozufozu.flywheel.util.DiffuseLightCalculator; +import com.jozufozu.flywheel.util.transform.TStack; +import com.jozufozu.flywheel.util.transform.Transform; +import com.mojang.blaze3d.vertex.BufferBuilder.DrawState; +import com.mojang.blaze3d.vertex.BufferBuilder.RenderedBuffer; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import com.mojang.math.*; +import nl.requios.effortlessbuilding.create.foundation.block.render.SpriteShiftEntry; +import nl.requios.effortlessbuilding.create.foundation.utility.Color; +import it.unimi.dsi.fastutil.longs.Long2IntMap; +import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.LevelRenderer; +import net.minecraft.client.renderer.LightTexture; +import net.minecraft.client.renderer.texture.OverlayTexture; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.util.Mth; +import net.minecraft.world.level.Level; + +public class SuperByteBuffer implements Transform, TStack { + + private final ShadedVertexList template; + + // Vertex Position + private final PoseStack transforms; + + // Vertex Coloring + private boolean shouldColor; + private int r, g, b, a; + private boolean disableDiffuseMult; + private DiffuseLightCalculator diffuseCalculator; + + // Vertex Texture Coords + private SpriteShiftFunc spriteShiftFunc; + + // Vertex Overlay Color + private boolean hasOverlay; + private int overlay = OverlayTexture.NO_OVERLAY; + + // Vertex Lighting + private boolean useWorldLight; + private Matrix4f lightTransform; + private boolean hasCustomLight; + private int packedLightCoords; + private boolean hybridLight; + + // Vertex Normals + private boolean fullNormalTransform; + + // Temporary + private static final Long2IntMap WORLD_LIGHT_CACHE = new Long2IntOpenHashMap(); + + public SuperByteBuffer(RenderedBuffer buf, int unshadedStartVertex) { + DrawState drawState = buf.drawState(); + template = new BlockVertexList.Shaded(buf.vertexBuffer(), drawState.vertexCount(), drawState.format().getVertexSize(), unshadedStartVertex); + + transforms = new PoseStack(); + transforms.pushPose(); + } + + public void renderInto(PoseStack input, VertexConsumer builder) { + if (isEmpty()) + return; + + Matrix4f modelMat = input.last() + .pose() + .copy(); + Matrix4f localTransforms = transforms.last() + .pose(); + modelMat.multiply(localTransforms); + + Matrix3f normalMat; + if (fullNormalTransform) { + normalMat = input.last() + .normal() + .copy(); + Matrix3f localNormalTransforms = transforms.last() + .normal(); + normalMat.mul(localNormalTransforms); + } else { + normalMat = transforms.last() + .normal() + .copy(); + } + + if (useWorldLight) { + WORLD_LIGHT_CACHE.clear(); + } + + final Vector4f pos = new Vector4f(); + final Vector3f normal = new Vector3f(); + final Vector4f lightPos = new Vector4f(); + + DiffuseLightCalculator diffuseCalculator = ForcedDiffuseState.getForcedCalculator(); + final boolean disableDiffuseMult = + this.disableDiffuseMult || (ShadersModHandler.isShaderPackInUse() && diffuseCalculator == null); + if (diffuseCalculator == null) { + diffuseCalculator = this.diffuseCalculator; + if (diffuseCalculator == null) { + diffuseCalculator = DiffuseLightCalculator.forCurrentLevel(); + } + } + + final int vertexCount = template.getVertexCount(); + for (int i = 0; i < vertexCount; i++) { + float x = template.getX(i); + float y = template.getY(i); + float z = template.getZ(i); + + pos.set(x, y, z, 1F); + pos.transform(modelMat); + builder.vertex(pos.x(), pos.y(), pos.z()); + + float normalX = template.getNX(i); + float normalY = template.getNY(i); + float normalZ = template.getNZ(i); + + normal.set(normalX, normalY, normalZ); + normal.transform(normalMat); + float nx = normal.x(); + float ny = normal.y(); + float nz = normal.z(); + + byte r, g, b, a; + if (shouldColor) { + r = (byte) this.r; + g = (byte) this.g; + b = (byte) this.b; + a = (byte) this.a; + } else { + r = template.getR(i); + g = template.getG(i); + b = template.getB(i); + a = template.getA(i); + } + if (disableDiffuseMult) { + builder.color(r, g, b, a); + } else { + float instanceDiffuse = diffuseCalculator.getDiffuse(nx, ny, nz, template.isShaded(i)); + int colorR = transformColor(r, instanceDiffuse); + int colorG = transformColor(g, instanceDiffuse); + int colorB = transformColor(b, instanceDiffuse); + builder.color(colorR, colorG, colorB, a); + } + + float u = template.getU(i); + float v = template.getV(i); + if (spriteShiftFunc != null) { + spriteShiftFunc.shift(builder, u, v); + } else { + builder.uv(u, v); + } + + if (hasOverlay) { + builder.overlayCoords(overlay); + } + + int light; + if (useWorldLight) { + lightPos.set(((x - .5f) * 15 / 16f) + .5f, (y - .5f) * 15 / 16f + .5f, (z - .5f) * 15 / 16f + .5f, 1f); + lightPos.transform(localTransforms); + if (lightTransform != null) { + lightPos.transform(lightTransform); + } + + light = getLight(Minecraft.getInstance().level, lightPos); + if (hasCustomLight) { + light = maxLight(light, packedLightCoords); + } + } else if (hasCustomLight) { + light = packedLightCoords; + } else { + light = template.getLight(i); + } + + if (hybridLight) { + builder.uv2(maxLight(light, template.getLight(i))); + } else { + builder.uv2(light); + } + + builder.normal(nx, ny, nz); + + builder.endVertex(); + } + + reset(); + } + + public SuperByteBuffer reset() { + while (!transforms.clear()) + transforms.popPose(); + transforms.pushPose(); + + shouldColor = false; + r = 0; + g = 0; + b = 0; + a = 0; + disableDiffuseMult = false; + diffuseCalculator = null; + spriteShiftFunc = null; + hasOverlay = false; + overlay = OverlayTexture.NO_OVERLAY; + useWorldLight = false; + lightTransform = null; + hasCustomLight = false; + packedLightCoords = 0; + hybridLight = false; + fullNormalTransform = false; + return this; + } + + public boolean isEmpty() { + return template.isEmpty(); + } + + public PoseStack getTransforms() { + return transforms; + } + + @Override + public SuperByteBuffer translate(double x, double y, double z) { + transforms.translate(x, y, z); + return this; + } + + @Override + public SuperByteBuffer multiply(Quaternion quaternion) { + transforms.mulPose(quaternion); + return this; + } + + @Override + public SuperByteBuffer scale(float factorX, float factorY, float factorZ) { + transforms.scale(factorX, factorY, factorZ); + return this; + } + + @Override + public SuperByteBuffer pushPose() { + transforms.pushPose(); + return this; + } + + @Override + public SuperByteBuffer popPose() { + transforms.popPose(); + return this; + } + + @Override + public SuperByteBuffer mulPose(Matrix4f pose) { + transforms.last() + .pose() + .multiply(pose); + return this; + } + + @Override + public SuperByteBuffer mulNormal(Matrix3f normal) { + transforms.last() + .normal() + .mul(normal); + return this; + } + + public SuperByteBuffer transform(PoseStack stack) { + transforms.last() + .pose() + .multiply(stack.last() + .pose()); + transforms.last() + .normal() + .mul(stack.last() + .normal()); + return this; + } + + public SuperByteBuffer rotateCentered(Direction axis, float radians) { + translate(.5f, .5f, .5f).rotate(axis, radians) + .translate(-.5f, -.5f, -.5f); + return this; + } + + public SuperByteBuffer rotateCentered(Quaternion q) { + translate(.5f, .5f, .5f).multiply(q) + .translate(-.5f, -.5f, -.5f); + return this; + } + + public SuperByteBuffer color(int r, int g, int b, int a) { + shouldColor = true; + this.r = r; + this.g = g; + this.b = b; + this.a = a; + return this; + } + + public SuperByteBuffer color(int color) { + shouldColor = true; + r = ((color >> 16) & 0xFF); + g = ((color >> 8) & 0xFF); + b = (color & 0xFF); + a = 255; + return this; + } + + public SuperByteBuffer color(Color c) { + return color(c.getRGB()); + } + + /** + * Prevents vertex colors from being multiplied by the diffuse value calculated + * from the final transformed normal vector. Useful for entity rendering, when + * diffuse is applied automatically later. + */ + public SuperByteBuffer disableDiffuse() { + disableDiffuseMult = true; + return this; + } + + public SuperByteBuffer diffuseCalculator(DiffuseLightCalculator diffuseCalculator) { + this.diffuseCalculator = diffuseCalculator; + return this; + } + + public SuperByteBuffer shiftUV(SpriteShiftEntry entry) { + this.spriteShiftFunc = (builder, u, v) -> { + builder.uv(entry.getTargetU(u), entry.getTargetV(v)); + }; + return this; + } + + public SuperByteBuffer shiftUVScrolling(SpriteShiftEntry entry, float scrollV) { + return this.shiftUVScrolling(entry, 0, scrollV); + } + + public SuperByteBuffer shiftUVScrolling(SpriteShiftEntry entry, float scrollU, float scrollV) { + this.spriteShiftFunc = (builder, u, v) -> { + float targetU = u - entry.getOriginal() + .getU0() + entry.getTarget() + .getU0() + + scrollU; + float targetV = v - entry.getOriginal() + .getV0() + entry.getTarget() + .getV0() + + scrollV; + builder.uv(targetU, targetV); + }; + return this; + } + + public SuperByteBuffer shiftUVtoSheet(SpriteShiftEntry entry, float uTarget, float vTarget, int sheetSize) { + this.spriteShiftFunc = (builder, u, v) -> { + float targetU = entry.getTarget() + .getU((SpriteShiftEntry.getUnInterpolatedU(entry.getOriginal(), u) / sheetSize) + uTarget * 16); + float targetV = entry.getTarget() + .getV((SpriteShiftEntry.getUnInterpolatedV(entry.getOriginal(), v) / sheetSize) + vTarget * 16); + builder.uv(targetU, targetV); + }; + return this; + } + + public SuperByteBuffer overlay() { + hasOverlay = true; + return this; + } + + public SuperByteBuffer overlay(int overlay) { + hasOverlay = true; + this.overlay = overlay; + return this; + } + + public SuperByteBuffer light() { + useWorldLight = true; + return this; + } + + public SuperByteBuffer light(Matrix4f lightTransform) { + useWorldLight = true; + this.lightTransform = lightTransform; + return this; + } + + public SuperByteBuffer light(int packedLightCoords) { + hasCustomLight = true; + this.packedLightCoords = packedLightCoords; + return this; + } + + public SuperByteBuffer light(Matrix4f lightTransform, int packedLightCoords) { + light(lightTransform); + light(packedLightCoords); + return this; + } + + /** + * Uses max light from calculated light (world light or custom light) and vertex + * light for the final light value. Ineffective if any other light method was + * not called. + */ + public SuperByteBuffer hybridLight() { + hybridLight = true; + return this; + } + + /** + * Transforms normals not only by the local matrix stack, but also by the passed + * matrix stack. + */ + public SuperByteBuffer fullNormalTransform() { + fullNormalTransform = true; + return this; + } + + public SuperByteBuffer forEntityRender() { + disableDiffuse(); + overlay(); + fullNormalTransform(); + return this; + } + + public static int transformColor(byte component, float scale) { + return Mth.clamp((int) (Byte.toUnsignedInt(component) * scale), 0, 255); + } + + public static int transformColor(int component, float scale) { + return Mth.clamp((int) (component * scale), 0, 255); + } + + public static int maxLight(int packedLight1, int packedLight2) { + int blockLight1 = LightTexture.block(packedLight1); + int skyLight1 = LightTexture.sky(packedLight1); + int blockLight2 = LightTexture.block(packedLight2); + int skyLight2 = LightTexture.sky(packedLight2); + return LightTexture.pack(Math.max(blockLight1, blockLight2), Math.max(skyLight1, skyLight2)); + } + + private static int getLight(Level world, Vector4f lightPos) { + BlockPos pos = new BlockPos(lightPos.x(), lightPos.y(), lightPos.z()); + return WORLD_LIGHT_CACHE.computeIfAbsent(pos.asLong(), $ -> LevelRenderer.getLightColor(world, pos)); + } + + @FunctionalInterface + public interface SpriteShiftFunc { + void shift(VertexConsumer builder, float u, float v); + } + + @FunctionalInterface + public interface VertexLighter { + int getPackedLight(float x, float y, float z); + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/render/SuperByteBufferCache.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/render/SuperByteBufferCache.java new file mode 100644 index 0000000..00b4052 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/render/SuperByteBufferCache.java @@ -0,0 +1,54 @@ +package nl.requios.effortlessbuilding.create.foundation.render; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +public class SuperByteBufferCache { + + protected final Map, Cache> caches = new HashMap<>(); + + public synchronized void registerCompartment(Compartment compartment) { + caches.put(compartment, CacheBuilder.newBuilder() + .build()); + } + + public synchronized void registerCompartment(Compartment compartment, long ticksUntilExpired) { + caches.put(compartment, CacheBuilder.newBuilder() + .expireAfterAccess(ticksUntilExpired * 50, TimeUnit.MILLISECONDS) + .build()); + } + + public SuperByteBuffer get(Compartment compartment, T key, Callable callable) { + Cache cache = caches.get(compartment); + if (cache != null) { + try { + return cache.get(key, callable); + } catch (ExecutionException e) { + e.printStackTrace(); + } + } + return null; + } + + public void invalidate(Compartment compartment, T key) { + caches.get(compartment).invalidate(key); + } + + public void invalidate(Compartment compartment) { + caches.get(compartment).invalidateAll(); + } + + public void invalidate() { + caches.forEach((compartment, cache) -> cache.invalidateAll()); + } + + public static class Compartment { + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/render/SuperRenderTypeBuffer.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/render/SuperRenderTypeBuffer.java new file mode 100644 index 0000000..b586c4a --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/render/SuperRenderTypeBuffer.java @@ -0,0 +1,94 @@ +package nl.requios.effortlessbuilding.create.foundation.render; + +import com.mojang.blaze3d.vertex.BufferBuilder; +import com.mojang.blaze3d.vertex.VertexConsumer; +import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; +import net.minecraft.Util; +import net.minecraft.client.renderer.ChunkBufferBuilderPack; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.Sheets; +import net.minecraft.client.resources.model.ModelBakery; + +import java.util.SortedMap; + +public class SuperRenderTypeBuffer implements MultiBufferSource { + + private static final SuperRenderTypeBuffer INSTANCE = new SuperRenderTypeBuffer(); + + public static SuperRenderTypeBuffer getInstance() { + return INSTANCE; + } + + private SuperRenderTypeBufferPhase earlyBuffer; + private SuperRenderTypeBufferPhase defaultBuffer; + private SuperRenderTypeBufferPhase lateBuffer; + + public SuperRenderTypeBuffer() { + earlyBuffer = new SuperRenderTypeBufferPhase(); + defaultBuffer = new SuperRenderTypeBufferPhase(); + lateBuffer = new SuperRenderTypeBufferPhase(); + } + + public VertexConsumer getEarlyBuffer(RenderType type) { + return earlyBuffer.bufferSource.getBuffer(type); + } + + @Override + public VertexConsumer getBuffer(RenderType type) { + return defaultBuffer.bufferSource.getBuffer(type); + } + + public VertexConsumer getLateBuffer(RenderType type) { + return lateBuffer.bufferSource.getBuffer(type); + } + + public void draw() { + earlyBuffer.bufferSource.endBatch(); + defaultBuffer.bufferSource.endBatch(); + lateBuffer.bufferSource.endBatch(); + } + + public void draw(RenderType type) { + earlyBuffer.bufferSource.endBatch(type); + defaultBuffer.bufferSource.endBatch(type); + lateBuffer.bufferSource.endBatch(type); + } + + private static class SuperRenderTypeBufferPhase { + + // Visible clones from RenderBuffers + private final ChunkBufferBuilderPack fixedBufferPack = new ChunkBufferBuilderPack(); + private final SortedMap fixedBuffers = Util.make(new Object2ObjectLinkedOpenHashMap<>(), map -> { + map.put(Sheets.solidBlockSheet(), fixedBufferPack.builder(RenderType.solid())); + map.put(Sheets.cutoutBlockSheet(), fixedBufferPack.builder(RenderType.cutout())); + map.put(Sheets.bannerSheet(), fixedBufferPack.builder(RenderType.cutoutMipped())); + map.put(Sheets.translucentCullBlockSheet(), fixedBufferPack.builder(RenderType.translucent())); + put(map, Sheets.shieldSheet()); + put(map, Sheets.bedSheet()); + put(map, Sheets.shulkerBoxSheet()); + put(map, Sheets.signSheet()); + put(map, Sheets.chestSheet()); + put(map, RenderType.translucentNoCrumbling()); + put(map, RenderType.armorGlint()); + put(map, RenderType.armorEntityGlint()); + put(map, RenderType.glint()); + put(map, RenderType.glintDirect()); + put(map, RenderType.glintTranslucent()); + put(map, RenderType.entityGlint()); + put(map, RenderType.entityGlintDirect()); + put(map, RenderType.waterMask()); + put(map, RenderTypes.getOutlineSolid()); + ModelBakery.DESTROY_TYPES.forEach((p_173062_) -> { + put(map, p_173062_); + }); + }); + private final BufferSource bufferSource = MultiBufferSource.immediateWithBuffers(fixedBuffers, new BufferBuilder(256)); + + private static void put(Object2ObjectLinkedOpenHashMap map, RenderType type) { + map.put(type, new BufferBuilder(type.bufferSize())); + } + + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/render/TileEntityRenderHelper.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/render/TileEntityRenderHelper.java new file mode 100644 index 0000000..a3a9bcb --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/render/TileEntityRenderHelper.java @@ -0,0 +1,113 @@ +package nl.requios.effortlessbuilding.create.foundation.render; + +import com.jozufozu.flywheel.backend.Backend; +import com.jozufozu.flywheel.backend.instancing.InstancedRenderRegistry; +import com.jozufozu.flywheel.config.BackendType; +import com.jozufozu.flywheel.core.virtual.VirtualRenderWorld; +import com.jozufozu.flywheel.util.transform.TransformStack; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.math.Matrix4f; +import com.mojang.math.Vector4f; +import nl.requios.effortlessbuilding.create.Create; +import nl.requios.effortlessbuilding.create.foundation.utility.AnimationTickHolder; +import nl.requios.effortlessbuilding.create.foundation.utility.RegisteredObjects; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.LevelRenderer; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.blockentity.BlockEntityRenderer; +import net.minecraft.client.renderer.texture.OverlayTexture; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; + +import javax.annotation.Nullable; +import java.util.Iterator; + +public class TileEntityRenderHelper { + + public static void renderTileEntities(Level world, Iterable customRenderTEs, PoseStack ms, + MultiBufferSource buffer) { + renderTileEntities(world, null, customRenderTEs, ms, null, buffer); + } + + public static void renderTileEntities(Level world, Iterable customRenderTEs, PoseStack ms, + MultiBufferSource buffer, float pt) { + renderTileEntities(world, null, customRenderTEs, ms, null, buffer, pt); + } + + public static void renderTileEntities(Level world, @Nullable VirtualRenderWorld renderWorld, + Iterable customRenderTEs, PoseStack ms, @Nullable Matrix4f lightTransform, MultiBufferSource buffer) { + renderTileEntities(world, renderWorld, customRenderTEs, ms, lightTransform, buffer, + AnimationTickHolder.getPartialTicks()); + } + + public static void renderTileEntities(Level world, @Nullable VirtualRenderWorld renderWorld, + Iterable customRenderTEs, PoseStack ms, @Nullable Matrix4f lightTransform, MultiBufferSource buffer, + float pt) { + Iterator iterator = customRenderTEs.iterator(); + while (iterator.hasNext()) { + BlockEntity tileEntity = iterator.next(); + if (Backend.getBackendType() == BackendType.INSTANCING && Backend.isFlywheelWorld(renderWorld) && InstancedRenderRegistry.shouldSkipRender(tileEntity)) + continue; + + BlockEntityRenderer renderer = Minecraft.getInstance().getBlockEntityRenderDispatcher().getRenderer(tileEntity); + if (renderer == null) { + iterator.remove(); + continue; + } + + BlockPos pos = tileEntity.getBlockPos(); + ms.pushPose(); + TransformStack.cast(ms) + .translate(pos); + + try { + int worldLight = getCombinedLight(world, getLightPos(lightTransform, pos), renderWorld, pos); + + if (renderWorld != null) { + // Swap the real world for the render world so that the renderer gets contraption-local information + tileEntity.setLevel(renderWorld); + renderer.render(tileEntity, pt, ms, buffer, worldLight, OverlayTexture.NO_OVERLAY); + tileEntity.setLevel(world); + } else { + renderer.render(tileEntity, pt, ms, buffer, worldLight, OverlayTexture.NO_OVERLAY); + } + + } catch (Exception e) { + iterator.remove(); + + String message = "BlockEntity " + RegisteredObjects.getKeyOrThrow(tileEntity.getType()) + .toString() + " could not be rendered virtually."; +// if (AllConfigs.CLIENT.explainRenderErrors.get()) + Create.LOGGER.error(message, e); +// else +// Create.LOGGER.error(message); + } + + ms.popPose(); + } + } + + private static BlockPos getLightPos(@Nullable Matrix4f lightTransform, BlockPos contraptionPos) { + if (lightTransform != null) { + Vector4f lightVec = new Vector4f(contraptionPos.getX() + .5f, contraptionPos.getY() + .5f, contraptionPos.getZ() + .5f, 1); + lightVec.transform(lightTransform); + return new BlockPos(lightVec.x(), lightVec.y(), lightVec.z()); + } else { + return contraptionPos; + } + } + + public static int getCombinedLight(Level world, BlockPos worldPos, @Nullable VirtualRenderWorld renderWorld, + BlockPos renderWorldPos) { + int worldLight = LevelRenderer.getLightColor(world, worldPos); + + if (renderWorld != null) { + int renderWorldLight = LevelRenderer.getLightColor(renderWorld, renderWorldPos); + return SuperByteBuffer.maxLight(worldLight, renderWorldLight); + } + + return worldLight; + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/AbstractBlockBreakQueue.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/AbstractBlockBreakQueue.java new file mode 100644 index 0000000..5408b96 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/AbstractBlockBreakQueue.java @@ -0,0 +1,36 @@ +package nl.requios.effortlessbuilding.create.foundation.utility; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraftforge.event.ForgeEventFactory; + +import javax.annotation.Nullable; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +public abstract class AbstractBlockBreakQueue { + protected Consumer makeCallbackFor(Level world, float effectChance, ItemStack toDamage, + @Nullable Player playerEntity, BiConsumer drop) { + return pos -> { + ItemStack usedTool = toDamage.copy(); + BlockHelper.destroyBlockAs(world, pos, playerEntity, toDamage, effectChance, + stack -> drop.accept(pos, stack)); + if (toDamage.isEmpty() && !usedTool.isEmpty()) + ForgeEventFactory.onPlayerDestroyItem(playerEntity, usedTool, InteractionHand.MAIN_HAND); + }; + } + + public void destroyBlocks(Level world, @Nullable LivingEntity entity, BiConsumer drop) { + Player playerEntity = entity instanceof Player ? ((Player) entity) : null; + ItemStack toDamage = + playerEntity != null && !playerEntity.isCreative() ? playerEntity.getMainHandItem() : ItemStack.EMPTY; + destroyBlocks(world, toDamage, playerEntity, drop); + } + + public abstract void destroyBlocks(Level world, ItemStack toDamage, @Nullable Player playerEntity, + BiConsumer drop); +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/AngleHelper.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/AngleHelper.java new file mode 100644 index 0000000..3855074 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/AngleHelper.java @@ -0,0 +1,52 @@ +package nl.requios.effortlessbuilding.create.foundation.utility; + +import net.minecraft.core.Direction; +import net.minecraft.core.Direction.Axis; +import net.minecraft.util.Mth; + +public class AngleHelper { + + public static float horizontalAngle(Direction facing) { + if (facing.getAxis().isVertical()) + return 0; + float angle = facing.toYRot(); + if (facing.getAxis() == Axis.X) + angle = -angle; + return angle; + } + + public static float verticalAngle(Direction facing) { + return facing == Direction.UP ? -90 : facing == Direction.DOWN ? 90 : 0; + } + + public static float rad(double angle) { + if (angle == 0) + return 0; + return (float) (angle / 180 * Math.PI); + } + + public static float deg(double angle) { + if (angle == 0) + return 0; + return (float) (angle * 180 / Math.PI); + } + + public static float angleLerp(double pct, double current, double target) { + return (float) (current + getShortestAngleDiff(current, target) * pct); + } + + public static float getShortestAngleDiff(double current, double target) { + current = current % 360; + target = target % 360; + return (float) (((((target - current) % 360) + 540) % 360) - 180); + } + + public static float getShortestAngleDiff(double current, double target, float hint) { + float diff = getShortestAngleDiff(current, target); + if (Mth.equal(Math.abs(diff), 180) && Math.signum(diff) != Math.signum(hint)) { + return diff + 360*Math.signum(hint); + } + return diff; + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/AnimationTickHolder.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/AnimationTickHolder.java new file mode 100644 index 0000000..6269bad --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/AnimationTickHolder.java @@ -0,0 +1,56 @@ +package nl.requios.effortlessbuilding.create.foundation.utility; + +import nl.requios.effortlessbuilding.create.foundation.utility.worldWrappers.WrappedClientWorld; +import net.minecraft.client.Minecraft; +import net.minecraft.world.level.LevelAccessor; + +public class AnimationTickHolder { + + private static int ticks; + private static int pausedTicks; + + public static void reset() { + ticks = 0; + pausedTicks = 0; + } + + public static void tick() { + if (!Minecraft.getInstance() + .isPaused()) { + ticks = (ticks + 1) % 1_728_000; // wrap around every 24 hours so we maintain enough floating point precision + } else { + pausedTicks = (pausedTicks + 1) % 1_728_000; + } + } + + public static int getTicks() { + return getTicks(false); + } + + public static int getTicks(boolean includePaused) { + return includePaused ? ticks + pausedTicks : ticks; + } + + public static float getRenderTime() { + return getTicks() + getPartialTicks(); + } + + public static float getPartialTicks() { + Minecraft mc = Minecraft.getInstance(); + return (mc.isPaused() ? mc.pausePartialTick : mc.getFrameTime()); + } + + public static int getTicks(LevelAccessor world) { + if (world instanceof WrappedClientWorld) + return getTicks(((WrappedClientWorld) world).getWrappedWorld()); + return getTicks(); + } + + public static float getRenderTime(LevelAccessor world) { + return getTicks(world) + getPartialTicks(world); + } + + public static float getPartialTicks(LevelAccessor world) { + return getPartialTicks(); + } +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/BBHelper.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/BBHelper.java new file mode 100644 index 0000000..7b9ee4a --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/BBHelper.java @@ -0,0 +1,20 @@ +package nl.requios.effortlessbuilding.create.foundation.utility; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.levelgen.structure.BoundingBox; + +public class BBHelper { + + public static BoundingBox encapsulate(BoundingBox bb, BlockPos pos) { + return new BoundingBox(Math.min(bb.minX(), pos.getX()), Math.min(bb.minY(), pos.getY()), + Math.min(bb.minZ(), pos.getZ()), Math.max(bb.maxX(), pos.getX()), Math.max(bb.maxY(), pos.getY()), + Math.max(bb.maxZ(), pos.getZ())); + } + + public static BoundingBox encapsulate(BoundingBox bb, BoundingBox bb2) { + return new BoundingBox(Math.min(bb.minX(), bb2.minX()), Math.min(bb.minY(), bb2.minY()), + Math.min(bb.minZ(), bb2.minZ()), Math.max(bb.maxX(), bb2.maxX()), Math.max(bb.maxY(), bb2.maxY()), + Math.max(bb.maxZ(), bb2.maxZ())); + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/BlockFace.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/BlockFace.java new file mode 100644 index 0000000..925af11 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/BlockFace.java @@ -0,0 +1,52 @@ +package nl.requios.effortlessbuilding.create.foundation.utility; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtUtils; + +public class BlockFace extends Pair { + + public BlockFace(BlockPos first, Direction second) { + super(first, second); + } + + public boolean isEquivalent(BlockFace other) { + if (equals(other)) + return true; + return getConnectedPos().equals(other.getPos()) && getPos().equals(other.getConnectedPos()); + } + + public BlockPos getPos() { + return getFirst(); + } + + public Direction getFace() { + return getSecond(); + } + + public Direction getOppositeFace() { + return getSecond().getOpposite(); + } + + public BlockFace getOpposite() { + return new BlockFace(getConnectedPos(), getOppositeFace()); + } + + public BlockPos getConnectedPos() { + return getPos().relative(getFace()); + } + + public CompoundTag serializeNBT() { + CompoundTag compoundNBT = new CompoundTag(); + compoundNBT.put("Pos", NbtUtils.writeBlockPos(getPos())); + NBTHelper.writeEnum(compoundNBT, "Face", getFace()); + return compoundNBT; + } + + public static BlockFace fromNBT(CompoundTag compound) { + return new BlockFace(NbtUtils.readBlockPos(compound.getCompound("Pos")), + NBTHelper.readEnum(compound, "Face", Direction.class)); + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/BlockHelper.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/BlockHelper.java new file mode 100644 index 0000000..11e130f --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/BlockHelper.java @@ -0,0 +1,337 @@ +package nl.requios.effortlessbuilding.create.foundation.utility; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.Registry; +import net.minecraft.core.SectionPos; +import net.minecraft.core.particles.ParticleTypes; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.sounds.SoundSource; +import net.minecraft.stats.Stats; +import net.minecraft.tags.BlockTags; +import net.minecraft.tags.FluidTags; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.enchantment.Enchantments; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.GameRules; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.*; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.level.block.state.properties.Property; +import net.minecraft.world.level.block.state.properties.SlabType; +import net.minecraft.world.level.chunk.LevelChunk; +import net.minecraft.world.level.chunk.LevelChunkSection; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.level.material.Material; +import net.minecraftforge.common.IPlantable; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.level.BlockEvent; + +import javax.annotation.Nullable; +import java.util.function.Consumer; + +public class BlockHelper { + + public static BlockState setZeroAge(BlockState blockState) { + if (blockState.hasProperty(BlockStateProperties.AGE_1)) + return blockState.setValue(BlockStateProperties.AGE_1, 0); + if (blockState.hasProperty(BlockStateProperties.AGE_2)) + return blockState.setValue(BlockStateProperties.AGE_2, 0); + if (blockState.hasProperty(BlockStateProperties.AGE_3)) + return blockState.setValue(BlockStateProperties.AGE_3, 0); + if (blockState.hasProperty(BlockStateProperties.AGE_5)) + return blockState.setValue(BlockStateProperties.AGE_5, 0); + if (blockState.hasProperty(BlockStateProperties.AGE_7)) + return blockState.setValue(BlockStateProperties.AGE_7, 0); + if (blockState.hasProperty(BlockStateProperties.AGE_15)) + return blockState.setValue(BlockStateProperties.AGE_15, 0); + if (blockState.hasProperty(BlockStateProperties.AGE_25)) + return blockState.setValue(BlockStateProperties.AGE_25, 0); + if (blockState.hasProperty(BlockStateProperties.LEVEL_HONEY)) + return blockState.setValue(BlockStateProperties.LEVEL_HONEY, 0); + if (blockState.hasProperty(BlockStateProperties.HATCH)) + return blockState.setValue(BlockStateProperties.HATCH, 0); + if (blockState.hasProperty(BlockStateProperties.STAGE)) + return blockState.setValue(BlockStateProperties.STAGE, 0); + if (blockState.is(BlockTags.CAULDRONS)) + return Blocks.CAULDRON.defaultBlockState(); + if (blockState.hasProperty(BlockStateProperties.LEVEL_COMPOSTER)) + return blockState.setValue(BlockStateProperties.LEVEL_COMPOSTER, 0); + if (blockState.hasProperty(BlockStateProperties.EXTENDED)) + return blockState.setValue(BlockStateProperties.EXTENDED, false); + return blockState; + } + + public static int findAndRemoveInInventory(BlockState block, Player player, int amount) { + int amountFound = 0; + Item required = getRequiredItem(block).getItem(); + + boolean needsTwo = block.hasProperty(BlockStateProperties.SLAB_TYPE) + && block.getValue(BlockStateProperties.SLAB_TYPE) == SlabType.DOUBLE; + + if (needsTwo) + amount *= 2; + + if (block.hasProperty(BlockStateProperties.EGGS)) + amount *= block.getValue(BlockStateProperties.EGGS); + + if (block.hasProperty(BlockStateProperties.PICKLES)) + amount *= block.getValue(BlockStateProperties.PICKLES); + + { + // Try held Item first + int preferredSlot = player.getInventory().selected; + ItemStack itemstack = player.getInventory() + .getItem(preferredSlot); + int count = itemstack.getCount(); + if (itemstack.getItem() == required && count > 0) { + int taken = Math.min(count, amount - amountFound); + player.getInventory() + .setItem(preferredSlot, new ItemStack(itemstack.getItem(), count - taken)); + amountFound += taken; + } + } + + // Search inventory + for (int i = 0; i < player.getInventory() + .getContainerSize(); ++i) { + if (amountFound == amount) + break; + + ItemStack itemstack = player.getInventory() + .getItem(i); + int count = itemstack.getCount(); + if (itemstack.getItem() == required && count > 0) { + int taken = Math.min(count, amount - amountFound); + player.getInventory() + .setItem(i, new ItemStack(itemstack.getItem(), count - taken)); + amountFound += taken; + } + } + + if (needsTwo) { + // Give back 1 if uneven amount was removed + if (amountFound % 2 != 0) + player.getInventory() + .add(new ItemStack(required)); + amountFound /= 2; + } + + return amountFound; + } + + public static ItemStack getRequiredItem(BlockState state) { + ItemStack itemStack = new ItemStack(state.getBlock()); + Item item = itemStack.getItem(); + if (item == Items.FARMLAND || item == Items.DIRT_PATH) + itemStack = new ItemStack(Items.DIRT); + return itemStack; + } + + public static void destroyBlock(Level world, BlockPos pos, float effectChance) { + destroyBlock(world, pos, effectChance, stack -> Block.popResource(world, pos, stack)); + } + + public static void destroyBlock(Level world, BlockPos pos, float effectChance, + Consumer droppedItemCallback) { + destroyBlockAs(world, pos, null, ItemStack.EMPTY, effectChance, droppedItemCallback); + } + + public static void destroyBlockAs(Level world, BlockPos pos, @Nullable Player player, ItemStack usedTool, + float effectChance, Consumer droppedItemCallback) { + FluidState fluidState = world.getFluidState(pos); + BlockState state = world.getBlockState(pos); + + if (world.random.nextFloat() < effectChance) + world.levelEvent(2001, pos, Block.getId(state)); + BlockEntity tileentity = state.hasBlockEntity() ? world.getBlockEntity(pos) : null; + + if (player != null) { + BlockEvent.BreakEvent event = new BlockEvent.BreakEvent(world, pos, state, player); + MinecraftForge.EVENT_BUS.post(event); + if (event.isCanceled()) + return; + + if (event.getExpToDrop() > 0 && world instanceof ServerLevel) + state.getBlock() + .popExperience((ServerLevel) world, pos, event.getExpToDrop()); + + usedTool.mineBlock(world, state, pos, player); + player.awardStat(Stats.BLOCK_MINED.get(state.getBlock())); + } + + if (world instanceof ServerLevel && world.getGameRules() + .getBoolean(GameRules.RULE_DOBLOCKDROPS) && !world.restoringBlockSnapshots + && (player == null || !player.isCreative())) { + for (ItemStack itemStack : Block.getDrops(state, (ServerLevel) world, pos, tileentity, player, usedTool)) + droppedItemCallback.accept(itemStack); + + // Simulating IceBlock#playerDestroy. Not calling method directly as it would drop item + // entities as a side-effect + if (state.getBlock() instanceof IceBlock && usedTool.getEnchantmentLevel(Enchantments.SILK_TOUCH) == 0) { + if (world.dimensionType() + .ultraWarm()) + return; + + Material material = world.getBlockState(pos.below()) + .getMaterial(); + if (material.blocksMotion() || material.isLiquid()) + world.setBlockAndUpdate(pos, Blocks.WATER.defaultBlockState()); + return; + } + + state.spawnAfterBreak((ServerLevel) world, pos, ItemStack.EMPTY, true); + } + + world.setBlockAndUpdate(pos, fluidState.createLegacyBlock()); + } + + public static boolean isSolidWall(BlockGetter reader, BlockPos fromPos, Direction toDirection) { + return hasBlockSolidSide(reader.getBlockState(fromPos.relative(toDirection)), reader, + fromPos.relative(toDirection), toDirection.getOpposite()); + } + + public static boolean noCollisionInSpace(BlockGetter reader, BlockPos pos) { + return reader.getBlockState(pos) + .getCollisionShape(reader, pos) + .isEmpty(); + } + + private static void placeRailWithoutUpdate(Level world, BlockState state, BlockPos target) { + LevelChunk chunk = world.getChunkAt(target); + int idx = chunk.getSectionIndex(target.getY()); + LevelChunkSection chunksection = chunk.getSection(idx); + if (chunksection == null) { + chunksection = new LevelChunkSection(chunk.getSectionYFromSectionIndex(idx), world.registryAccess() + .registryOrThrow(Registry.BIOME_REGISTRY)); + chunk.getSections()[idx] = chunksection; + } + BlockState old = chunksection.setBlockState(SectionPos.sectionRelative(target.getX()), + SectionPos.sectionRelative(target.getY()), SectionPos.sectionRelative(target.getZ()), state); + chunk.setUnsaved(true); + world.markAndNotifyBlock(target, chunk, old, state, 82, 512); + + world.setBlock(target, state, 82); + world.neighborChanged(target, world.getBlockState(target.below()) + .getBlock(), target.below()); + } + + public static void placeSchematicBlock(Level world, BlockState state, BlockPos target, ItemStack stack, + @Nullable CompoundTag data) { + BlockEntity existingTile = world.getBlockEntity(target); + + // Piston + if (state.hasProperty(BlockStateProperties.EXTENDED)) + state = state.setValue(BlockStateProperties.EXTENDED, Boolean.FALSE); + if (state.hasProperty(BlockStateProperties.WATERLOGGED)) + state = state.setValue(BlockStateProperties.WATERLOGGED, Boolean.FALSE); + +// if (AllBlocks.BELT.has(state)) { +// world.setBlock(target, state, 2); +// return; +// } else if (state.getBlock() == Blocks.COMPOSTER) +// state = Blocks.COMPOSTER.defaultBlockState(); +// else if (state.getBlock() != Blocks.SEA_PICKLE && state.getBlock() instanceof IPlantable) +// state = ((IPlantable) state.getBlock()).getPlant(world, target); +// else if (state.is(BlockTags.CAULDRONS)) +// state = Blocks.CAULDRON.defaultBlockState(); + + if (world.dimensionType() + .ultraWarm() && state.getFluidState().is(FluidTags.WATER)) { + int i = target.getX(); + int j = target.getY(); + int k = target.getZ(); + world.playSound(null, target, SoundEvents.FIRE_EXTINGUISH, SoundSource.BLOCKS, 0.5F, + 2.6F + (world.random.nextFloat() - world.random.nextFloat()) * 0.8F); + + for (int l = 0; l < 8; ++l) { + world.addParticle(ParticleTypes.LARGE_SMOKE, i + Math.random(), j + Math.random(), k + Math.random(), + 0.0D, 0.0D, 0.0D); + } + Block.dropResources(state, world, target); + return; + } + + if (state.getBlock() instanceof BaseRailBlock) { + placeRailWithoutUpdate(world, state, target); + } else { + world.setBlock(target, state, 18); + } + + if (data != null) { +// if (existingTile instanceof IMergeableTE mergeable) { +// BlockEntity loaded = BlockEntity.loadStatic(target, state, data); +// if (existingTile.getType() +// .equals(loaded.getType())) { +// mergeable.accept(loaded); +// return; +// } +// } + BlockEntity tile = world.getBlockEntity(target); + if (tile != null) { + data.putInt("x", target.getX()); + data.putInt("y", target.getY()); + data.putInt("z", target.getZ()); +// if (tile instanceof KineticTileEntity) +// ((KineticTileEntity) tile).warnOfMovement(); + tile.load(data); + } + } + + try { + state.getBlock() + .setPlacedBy(world, target, state, null, stack); + } catch (Exception e) { + } + } + + public static double getBounceMultiplier(Block block) { + if (block instanceof SlimeBlock) + return 0.8D; + if (block instanceof BedBlock) + return 0.66 * 0.8D; + return 0; + } + + public static boolean hasBlockSolidSide(BlockState p_220056_0_, BlockGetter p_220056_1_, BlockPos p_220056_2_, + Direction p_220056_3_) { + return !p_220056_0_.is(BlockTags.LEAVES) + && Block.isFaceFull(p_220056_0_.getCollisionShape(p_220056_1_, p_220056_2_), p_220056_3_); + } + + public static boolean extinguishFire(Level world, @Nullable Player p_175719_1_, BlockPos p_175719_2_, + Direction p_175719_3_) { + p_175719_2_ = p_175719_2_.relative(p_175719_3_); + if (world.getBlockState(p_175719_2_) + .getBlock() == Blocks.FIRE) { + world.levelEvent(p_175719_1_, 1009, p_175719_2_, 0); + world.removeBlock(p_175719_2_, false); + return true; + } else { + return false; + } + } + + public static BlockState copyProperties(BlockState fromState, BlockState toState) { + for (Property property : fromState.getProperties()) { + toState = copyProperty(property, fromState, toState); + } + return toState; + } + + public static > BlockState copyProperty(Property property, BlockState fromState, + BlockState toState) { + if (fromState.hasProperty(property) && toState.hasProperty(property)) { + return toState.setValue(property, fromState.getValue(property)); + } + return toState; + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/CameraAngleAnimationService.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/CameraAngleAnimationService.java new file mode 100644 index 0000000..5148b13 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/CameraAngleAnimationService.java @@ -0,0 +1,90 @@ +package nl.requios.effortlessbuilding.create.foundation.utility; + +import nl.requios.effortlessbuilding.create.foundation.utility.animation.LerpedFloat; +import net.minecraft.client.Minecraft; +import net.minecraft.util.Mth; + +public class CameraAngleAnimationService { + + private static final LerpedFloat yRotation = LerpedFloat.angular().startWithValue(0); + private static final LerpedFloat xRotation = LerpedFloat.angular().startWithValue(0); + + private static Mode animationMode = Mode.LINEAR; + private static float animationSpeed = -1; + + public static void tick() { + + yRotation.tickChaser(); + xRotation.tickChaser(); + + if (Minecraft.getInstance().player != null) { + if (!yRotation.settled()) + Minecraft.getInstance().player.setYRot(yRotation.getValue(1)); + + if (!xRotation.settled()) + Minecraft.getInstance().player.setXRot(xRotation.getValue(1)); + } + } + + public static boolean isYawAnimating() { + return !yRotation.settled(); + } + + public static boolean isPitchAnimating() { + return !xRotation.settled(); + } + + public static float getYaw(float partialTicks) { + return yRotation.getValue(partialTicks); + } + + public static float getPitch(float partialTicks) { + return xRotation.getValue(partialTicks); + } + + public static void setAnimationMode(Mode mode) { + animationMode = mode; + } + + public static void setAnimationSpeed(float speed) { + animationSpeed = speed; + } + + public static void setYawTarget(float yaw) { + float currentYaw = getCurrentYaw(); + yRotation.startWithValue(currentYaw); + setupChaser(yRotation, currentYaw + AngleHelper.getShortestAngleDiff(currentYaw, Mth.wrapDegrees(yaw))); + } + + public static void setPitchTarget(float pitch) { + float currentPitch = getCurrentPitch(); + xRotation.startWithValue(currentPitch); + setupChaser(xRotation, currentPitch + AngleHelper.getShortestAngleDiff(currentPitch, Mth.wrapDegrees(pitch))); + } + + private static float getCurrentYaw() { + if (Minecraft.getInstance().player == null) + return 0; + return Mth.wrapDegrees(Minecraft.getInstance().player.getYRot()); + } + + private static float getCurrentPitch() { + if (Minecraft.getInstance().player == null) + return 0; + + return Mth.wrapDegrees(Minecraft.getInstance().player.getXRot()); + } + + private static void setupChaser(LerpedFloat rotation, float target) { + if (animationMode == Mode.LINEAR) { + rotation.chase(target, animationSpeed > 0 ? animationSpeed : 2, LerpedFloat.Chaser.LINEAR); + } else if (animationMode == Mode.EXPONENTIAL) { + rotation.chase(target, animationSpeed > 0 ? animationSpeed : 0.25, LerpedFloat.Chaser.EXP); + } + } + + public enum Mode { + LINEAR, + EXPONENTIAL + } +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/Color.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/Color.java new file mode 100644 index 0000000..ed4a220 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/Color.java @@ -0,0 +1,310 @@ +package nl.requios.effortlessbuilding.create.foundation.utility; + +import com.google.common.hash.Hashing; +import com.mojang.math.Vector3f; +import net.minecraft.util.Mth; +import net.minecraft.world.phys.Vec3; + +import javax.annotation.Nonnull; +import java.util.function.UnaryOperator; + +public class Color { + public final static Color TRANSPARENT_BLACK = new Color(0, 0, 0, 0).setImmutable(); + public final static Color BLACK = new Color(0, 0, 0).setImmutable(); + public final static Color WHITE = new Color(255, 255, 255).setImmutable(); + public final static Color RED = new Color(255, 0, 0).setImmutable(); + public final static Color GREEN = new Color(0, 255, 0).setImmutable(); + public final static Color SPRING_GREEN = new Color(0, 255, 187).setImmutable(); + + protected boolean mutable = true; + protected int value; + + public Color(int r, int g, int b) { + this(r, g, b, 0xff); + } + + public Color(int r, int g, int b, int a) { + value = ((a & 0xff) << 24) | + ((r & 0xff) << 16) | + ((g & 0xff) << 8) | + ((b & 0xff) << 0); + } + + public Color(float r, float g, float b, float a) { + this( + (int) (0.5 + 0xff * Mth.clamp(r, 0, 1)), + (int) (0.5 + 0xff * Mth.clamp(g, 0, 1)), + (int) (0.5 + 0xff * Mth.clamp(b, 0, 1)), + (int) (0.5 + 0xff * Mth.clamp(a, 0, 1)) + ); + } + + public Color(int rgba) { + value = rgba; + } + + public Color(int rgb, boolean hasAlpha) { + if (hasAlpha) { + value = rgb; + } else { + value = rgb | 0xff_000000; + } + } + + public Color copy() { + return copy(true); + } + + public Color copy(boolean mutable) { + if (mutable) + return new Color(value); + else + return new Color(value).setImmutable(); + } + + /** + * Mark this color as immutable. Attempting to mutate this color in the future + * will instead cause a copy to be created that can me modified. + */ + public Color setImmutable() { + this.mutable = false; + return this; + } + + /** + * @return the red component in the range 0-255. + * @see #getRGB + */ + public int getRed() { + return (getRGB() >> 16) & 0xff; + } + + /** + * @return the green component in the range 0-255. + * @see #getRGB + */ + public int getGreen() { + return (getRGB() >> 8) & 0xff; + } + + /** + * @return the blue component in the range 0-255. + * @see #getRGB + */ + public int getBlue() { + return (getRGB() >> 0) & 0xff; + } + + /** + * @return the alpha component in the range 0-255. + * @see #getRGB + */ + public int getAlpha() { + return (getRGB() >> 24) & 0xff; + } + + /** + * @return the red component in the range 0-1f. + */ + public float getRedAsFloat() { + return getRed() / 255f; + } + + /** + * @return the green component in the range 0-1f. + */ + public float getGreenAsFloat() { + return getGreen() / 255f; + } + + /** + * @return the blue component in the range 0-1f. + */ + public float getBlueAsFloat() { + return getBlue() / 255f; + } + + /** + * @return the alpha component in the range 0-1f. + */ + public float getAlphaAsFloat() { + return getAlpha() / 255f; + } + + /** + * Returns the RGB value representing this color + * (Bits 24-31 are alpha, 16-23 are red, 8-15 are green, 0-7 are blue). + * @return the RGB value of the color + */ + public int getRGB() { + return value; + } + + public Vec3 asVector() { + return new Vec3(getRedAsFloat(), getGreenAsFloat(), getBlueAsFloat()); + } + + public Vector3f asVectorF() { + return new Vector3f(getRedAsFloat(), getGreenAsFloat(), getBlueAsFloat()); + } + + public Color setRed(int r) { + return ensureMutable().setRedUnchecked(r); + } + + public Color setGreen(int g) { + return ensureMutable().setGreenUnchecked(g); + } + + public Color setBlue(int b) { + return ensureMutable().setBlueUnchecked(b); + } + + public Color setAlpha(int a) { + return ensureMutable().setAlphaUnchecked(a); + } + + public Color setRed(float r) { + return ensureMutable().setRedUnchecked((int) (0xff * Mth.clamp(r, 0, 1))); + } + + public Color setGreen(float g) { + return ensureMutable().setGreenUnchecked((int) (0xff * Mth.clamp(g, 0, 1))); + } + + public Color setBlue(float b) { + return ensureMutable().setBlueUnchecked((int) (0xff * Mth.clamp(b, 0, 1))); + } + + public Color setAlpha(float a) { + return ensureMutable().setAlphaUnchecked((int) (0xff * Mth.clamp(a, 0, 1))); + } + + public Color scaleAlpha(float factor) { + return ensureMutable().setAlphaUnchecked((int) (getAlpha() * Mth.clamp(factor, 0, 1))); + } + + public Color mixWith(Color other, float weight) { + return ensureMutable() + .setRedUnchecked((int) (getRed() + (other.getRed() - getRed()) * weight)) + .setGreenUnchecked((int) (getGreen() + (other.getGreen() - getGreen()) * weight)) + .setBlueUnchecked((int) (getBlue() + (other.getBlue() - getBlue()) * weight)) + .setAlphaUnchecked((int) (getAlpha() + (other.getAlpha() - getAlpha()) * weight)); + } + + public Color darker() { + int a = getAlpha(); + return ensureMutable().mixWith(BLACK, .25f).setAlphaUnchecked(a); + } + + public Color brighter() { + int a = getAlpha(); + return ensureMutable().mixWith(WHITE, .25f).setAlphaUnchecked(a); + } + + public Color setValue(int value) { + return ensureMutable().setValueUnchecked(value); + } + + public Color modifyValue(UnaryOperator function) { + int newValue = function.apply(value); + if (newValue == value) + return this; + + return ensureMutable().setValueUnchecked(newValue); + } + + // ********* // + + protected Color ensureMutable() { + if (this.mutable) + return this; + + return new Color(this.value); + } + + protected Color setRedUnchecked(int r) { + this.value = (this.value & 0xff_00ffff) | ((r & 0xff) << 16); + return this; + } + + protected Color setGreenUnchecked(int g) { + this.value = (this.value & 0xff_ff00ff) | ((g & 0xff) << 8); + return this; + } + + protected Color setBlueUnchecked(int b) { + this.value = (this.value & 0xff_ffff00) | ((b & 0xff) << 0); + return this; + } + + protected Color setAlphaUnchecked(int a) { + this.value = (this.value & 0x00_ffffff) | ((a & 0xff) << 24); + return this; + } + + protected Color setValueUnchecked(int value) { + this.value = value; + return this; + } + + // ********* // + + public static Color mixColors(@Nonnull Color c1, @Nonnull Color c2, float w) { + return new Color( + (int) (c1.getRed() + (c2.getRed() - c1.getRed()) * w), + (int) (c1.getGreen() + (c2.getGreen() - c1.getGreen()) * w), + (int) (c1.getBlue() + (c2.getBlue() - c1.getBlue()) * w), + (int) (c1.getAlpha() + (c2.getAlpha() - c1.getAlpha()) * w) + ); + } + + public static Color mixColors(@Nonnull Couple colors, float w) { + return mixColors(colors.getFirst(), colors.getSecond(), w); + } + + public static int mixColors(int color1, int color2, float w) { + int a1 = (color1 >> 24); + int r1 = (color1 >> 16) & 0xFF; + int g1 = (color1 >> 8) & 0xFF; + int b1 = color1 & 0xFF; + int a2 = (color2 >> 24); + int r2 = (color2 >> 16) & 0xFF; + int g2 = (color2 >> 8) & 0xFF; + int b2 = color2 & 0xFF; + + return + ((int) (a1 + (a2 - a1) * w) << 24) + + ((int) (r1 + (r2 - r1) * w) << 16) + + ((int) (g1 + (g2 - g1) * w) << 8) + + ((int) (b1 + (b2 - b1) * w) << 0); + } + + public static Color rainbowColor(int timeStep) { + int localTimeStep = Math.abs(timeStep) % 1536; + int timeStepInPhase = localTimeStep % 256; + int phaseBlue = localTimeStep / 256; + int red = colorInPhase(phaseBlue + 4, timeStepInPhase); + int green = colorInPhase(phaseBlue + 2, timeStepInPhase); + int blue = colorInPhase(phaseBlue, timeStepInPhase); + return new Color(red, green, blue); + } + + private static int colorInPhase(int phase, int progress) { + phase = phase % 6; + if (phase <= 1) + return 0; + if (phase == 2) + return progress; + if (phase <= 4) + return 255; + else + return 255 - progress; + } + + public static Color generateFromLong(long l) { + return rainbowColor(Hashing.crc32().hashLong(l).asInt()) + .mixWith(WHITE, 0.5f); + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/ColorHandlers.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/ColorHandlers.java new file mode 100644 index 0000000..cec8e18 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/ColorHandlers.java @@ -0,0 +1,26 @@ +package nl.requios.effortlessbuilding.create.foundation.utility; + +import net.minecraft.client.color.block.BlockColor; +import net.minecraft.client.color.item.ItemColor; +import net.minecraft.client.renderer.BiomeColors; +import net.minecraft.world.level.GrassColor; +import net.minecraft.world.level.block.RedStoneWireBlock; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; + +public class ColorHandlers { + + public static BlockColor getGrassyBlock() { + return (state, world, pos, layer) -> pos != null && world != null ? BiomeColors.getAverageGrassColor(world, pos) + : GrassColor.get(0.5D, 1.0D); + } + + public static ItemColor getGrassyItem() { + return (stack, layer) -> GrassColor.get(0.5D, 1.0D); + } + + public static BlockColor getRedstonePower() { + return (state, world, pos, layer) -> RedStoneWireBlock + .getColorForPower(pos != null && world != null ? state.getValue(BlockStateProperties.POWER) : 0); + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/Components.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/Components.java new file mode 100644 index 0000000..1f83124 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/Components.java @@ -0,0 +1,33 @@ +package nl.requios.effortlessbuilding.create.foundation.utility; + +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; + +public final class Components { + private static final Component IMMUTABLE_EMPTY = Component.empty(); + + public static Component immutableEmpty() { + return IMMUTABLE_EMPTY; + } + + /** Use {@link #immutableEmpty()} when possible to prevent creating an extra object. */ + public static MutableComponent empty() { + return Component.empty(); + } + + public static MutableComponent literal(String str) { + return Component.literal(str); + } + + public static MutableComponent translatable(String key) { + return Component.translatable(key); + } + + public static MutableComponent translatable(String key, Object... args) { + return Component.translatable(key, args); + } + + public static MutableComponent keybind(String name) { + return Component.keybind(name); + } +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/Couple.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/Couple.java new file mode 100644 index 0000000..d04238b --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/Couple.java @@ -0,0 +1,151 @@ +package nl.requios.effortlessbuilding.create.foundation.utility; + +import com.google.common.collect.ImmutableList; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; + +import java.util.Iterator; +import java.util.List; +import java.util.function.*; +import java.util.stream.Stream; + +public class Couple extends Pair implements Iterable { + + private static final Couple TRUE_AND_FALSE = Couple.create(true, false); + + protected Couple(T first, T second) { + super(first, second); + } + + public static Couple create(T first, T second) { + return new Couple<>(first, second); + } + + public static Couple create(Supplier factory) { + return new Couple<>(factory.get(), factory.get()); + } + + public static Couple createWithContext(Function factory) { + return new Couple<>(factory.apply(true), factory.apply(false)); + } + + public T get(boolean first) { + return first ? getFirst() : getSecond(); + } + + public void set(boolean first, T value) { + if (first) + setFirst(value); + else + setSecond(value); + } + + @Override + public Couple copy() { + return create(first, second); + } + + public Couple map(Function function) { + return Couple.create(function.apply(first), function.apply(second)); + } + + public Couple mapWithContext(BiFunction function) { + return Couple.create(function.apply(first, true), function.apply(second, false)); + } + + public Couple mapWithParams(BiFunction function, Couple values) { + return Couple.create(function.apply(first, values.first), function.apply(second, values.second)); + } + + public Couple mapNotNullWithParam(BiFunction function, R value) { + return Couple.create(first != null ? function.apply(first, value) : null, + second != null ? function.apply(second, value) : null); + } + + public boolean both(Predicate test) { + return test.test(getFirst()) && test.test(getSecond()); + } + + public boolean either(Predicate test) { + return test.test(getFirst()) || test.test(getSecond()); + } + + public void replace(Function function) { + setFirst(function.apply(getFirst())); + setSecond(function.apply(getSecond())); + } + + public void replaceWithContext(BiFunction function) { + replaceWithParams(function, TRUE_AND_FALSE); + } + + public void replaceWithParams(BiFunction function, Couple values) { + setFirst(function.apply(getFirst(), values.getFirst())); + setSecond(function.apply(getSecond(), values.getSecond())); + } + + @Override + public void forEach(Consumer consumer) { + consumer.accept(getFirst()); + consumer.accept(getSecond()); + } + + public void forEachWithContext(BiConsumer consumer) { + forEachWithParams(consumer, TRUE_AND_FALSE); + } + + public void forEachWithParams(BiConsumer function, Couple values) { + function.accept(getFirst(), values.getFirst()); + function.accept(getSecond(), values.getSecond()); + } + + public Couple swap() { + return Couple.create(second, first); + } + + public ListTag serializeEach(Function serializer) { + return NBTHelper.writeCompoundList(ImmutableList.of(first, second), serializer); + } + + public static Couple deserializeEach(ListTag list, Function deserializer) { + List readCompoundList = NBTHelper.readCompoundList(list, deserializer); + return new Couple<>(readCompoundList.get(0), readCompoundList.get(1)); + } + + @Override + public Iterator iterator() { + return new Couplerator<>(this); + } + + public Stream stream() { + return Stream.of(first, second); + } + + private static class Couplerator implements Iterator { + + int state; + private final Couple couple; + + public Couplerator(Couple couple) { + this.couple = couple; + state = 0; + } + + @Override + public boolean hasNext() { + return state != 2; + } + + @Override + public T next() { + state++; + if (state == 1) + return couple.first; + if (state == 2) + return couple.second; + return null; + } + + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/CreateRegistry.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/CreateRegistry.java new file mode 100644 index 0000000..d9545fb --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/CreateRegistry.java @@ -0,0 +1,102 @@ +package nl.requios.effortlessbuilding.create.foundation.utility; + +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.registries.IForgeRegistry; +import nl.requios.effortlessbuilding.EffortlessBuilding; +import nl.requios.effortlessbuilding.create.Create; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +public class CreateRegistry { + private static final List> ALL = new ArrayList<>(); + + protected final IForgeRegistry objectRegistry; + protected final Map locationMap = new HashMap<>(); + protected final Map objectMap = new IdentityHashMap<>(); + protected boolean unwrapped = false; + + public CreateRegistry(IForgeRegistry objectRegistry) { + this.objectRegistry = objectRegistry; + ALL.add(this); + } + + public void register(ResourceLocation location, V value) { + if (!unwrapped) { + locationMap.put(location, value); + } else { + K object = objectRegistry.getValue(location); + if (object != null) { + objectMap.put(object, value); + } else { + Create.LOGGER.warn("Could not get object for location '" + location + "' in CreateRegistry after unwrapping!"); + } + } + } + + public void register(K object, V value) { + if (unwrapped) { + objectMap.put(object, value); + } else { + ResourceLocation location = objectRegistry.getKey(object); + if (location != null) { + locationMap.put(location, value); + } else { + Create.LOGGER.warn("Could not get location of object '" + object + "' in CreateRegistry before unwrapping!"); + } + } + } + + @Nullable + public V get(ResourceLocation location) { + if (!unwrapped) { + return locationMap.get(location); + } else { + K object = objectRegistry.getValue(location); + if (object != null) { + return objectMap.get(object); + } else { + Create.LOGGER.warn("Could not get object for location '" + location + "' in CreateRegistry after unwrapping!"); + return null; + } + } + } + + @Nullable + public V get(K object) { + if (unwrapped) { + return objectMap.get(object); + } else { + ResourceLocation location = objectRegistry.getKey(object); + if (location != null) { + return locationMap.get(location); + } else { + Create.LOGGER.warn("Could not get location of object '" + object + "' in CreateRegistry before unwrapping!"); + return null; + } + } + } + + public boolean isUnwrapped() { + return unwrapped; + } + + protected void unwrap() { + for (Map.Entry entry : locationMap.entrySet()) { + ResourceLocation location = entry.getKey(); + K object = objectRegistry.getValue(location); + if (object != null) { + objectMap.put(object, entry.getValue()); + } else { + Create.LOGGER.warn("Could not get object for location '" + location + "' in CreateRegistry during unwrapping!"); + } + } + unwrapped = true; + } + + public static void unwrapAll() { + for (CreateRegistry registry : ALL) { + registry.unwrap(); + } + } +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/Debug.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/Debug.java new file mode 100644 index 0000000..ba00b87 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/Debug.java @@ -0,0 +1,67 @@ +package nl.requios.effortlessbuilding.create.foundation.utility; + +import nl.requios.effortlessbuilding.create.Create; +import net.minecraft.ChatFormatting; +import net.minecraft.client.Minecraft; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import net.minecraftforge.fml.util.thread.EffectiveSide; + +/** Deprecated so simi doensn't forget to remove debug calls **/ +@OnlyIn(value = Dist.CLIENT) +public class Debug { + + @Deprecated + public static void debugChat(String message) { + if (Minecraft.getInstance().player != null) + Minecraft.getInstance().player.displayClientMessage(Components.literal(message), false); + } + + @Deprecated + public static void debugChatAndShowStack(String message, int depth) { + if (Minecraft.getInstance().player != null) + Minecraft.getInstance().player.displayClientMessage(Components.literal(message).append("@") + .append(debugStack(depth)), false); + } + + @Deprecated + public static void debugMessage(String message) { + if (Minecraft.getInstance().player != null) + Minecraft.getInstance().player.displayClientMessage(Components.literal(message), true); + } + + @Deprecated + public static void log(String message) { + Create.LOGGER.info(message); + } + + @Deprecated + public static String getLogicalSide() { + return EffectiveSide.get() + .isClient() ? "CL" : "SV"; + } + + @Deprecated + public static Component debugStack(int depth) { + StackTraceElement[] stackTraceElements = Thread.currentThread() + .getStackTrace(); + MutableComponent text = Components.literal("[") + .append(Components.literal(getLogicalSide()).withStyle(ChatFormatting.GOLD)) + .append("] "); + for (int i = 1; i < depth + 2 && i < stackTraceElements.length; i++) { + StackTraceElement e = stackTraceElements[i]; + if (e.getClassName() + .equals(Debug.class.getName())) + continue; + text.append(Components.literal(e.getMethodName()).withStyle(ChatFormatting.YELLOW)) + .append(", "); + } + return text.append(Components.literal(" ...").withStyle(ChatFormatting.GRAY)); + } + + @Deprecated + public static void markTemporary() {} + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/DyeHelper.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/DyeHelper.java new file mode 100644 index 0000000..b53b435 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/DyeHelper.java @@ -0,0 +1,75 @@ +package nl.requios.effortlessbuilding.create.foundation.utility; + +import com.google.common.collect.ImmutableMap; +import net.minecraft.world.item.DyeColor; +import net.minecraft.world.level.ItemLike; +import net.minecraft.world.level.block.Blocks; + +import java.util.Map; + +public class DyeHelper { + + public static ItemLike getWoolOfDye(DyeColor color) { + switch (color) { + case BLACK: + return Blocks.BLACK_WOOL; + case BLUE: + return Blocks.BLUE_WOOL; + case BROWN: + return Blocks.BROWN_WOOL; + case CYAN: + return Blocks.CYAN_WOOL; + case GRAY: + return Blocks.GRAY_WOOL; + case GREEN: + return Blocks.GREEN_WOOL; + case LIGHT_BLUE: + return Blocks.LIGHT_BLUE_WOOL; + case LIGHT_GRAY: + return Blocks.LIGHT_GRAY_WOOL; + case LIME: + return Blocks.LIME_WOOL; + case MAGENTA: + return Blocks.MAGENTA_WOOL; + case ORANGE: + return Blocks.ORANGE_WOOL; + case PINK: + return Blocks.PINK_WOOL; + case PURPLE: + return Blocks.PURPLE_WOOL; + case RED: + return Blocks.RED_WOOL; + case YELLOW: + return Blocks.YELLOW_WOOL; + case WHITE: + default: + return Blocks.WHITE_WOOL; + } + } + + public static final Map> DYE_TABLE = new ImmutableMap.Builder>() + + // DyeColor, ( Front RGB, Back RGB ) + .put(DyeColor.BLACK, Couple.create(0x45403B, 0x21201F)) + .put(DyeColor.RED, Couple.create(0xB13937, 0x632737)) + .put(DyeColor.GREEN, Couple.create(0x208A46, 0x1D6045)) + .put(DyeColor.BROWN, Couple.create(0xAC855C, 0x68533E)) + + .put(DyeColor.BLUE, Couple.create(0x5391E1, 0x504B90)) + .put(DyeColor.GRAY, Couple.create(0x5D666F, 0x313538)) + .put(DyeColor.LIGHT_GRAY, Couple.create(0x95969B, 0x707070)) + .put(DyeColor.PURPLE, Couple.create(0x9F54AE, 0x63366C)) + + .put(DyeColor.CYAN, Couple.create(0x3EABB4, 0x3C7872)) + .put(DyeColor.PINK, Couple.create(0xD5A8CB, 0xB86B95)) + .put(DyeColor.LIME, Couple.create(0xA3DF55, 0x4FB16F)) + .put(DyeColor.YELLOW, Couple.create(0xE6D756, 0xE9AC29)) + + .put(DyeColor.LIGHT_BLUE, Couple.create(0x69CED2, 0x508AA5)) + .put(DyeColor.ORANGE, Couple.create(0xEE9246, 0xD94927)) + .put(DyeColor.MAGENTA, Couple.create(0xF062B0, 0xC04488)) + .put(DyeColor.WHITE, Couple.create(0xEDEAE5, 0xBBB6B0)) + + .build(); + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/DynamicComponent.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/DynamicComponent.java new file mode 100644 index 0000000..6fc46da --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/DynamicComponent.java @@ -0,0 +1,94 @@ +package nl.requios.effortlessbuilding.create.foundation.utility; + +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.google.gson.JsonParser; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import nl.requios.effortlessbuilding.create.Create; +import net.minecraft.commands.CommandSource; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.core.BlockPos; +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.server.level.ServerLevel; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.Vec2; +import net.minecraft.world.phys.Vec3; + +public class DynamicComponent { + + private JsonElement rawCustomText; + private Component parsedCustomText; + + public DynamicComponent() {} + + public void displayCustomText(Level level, BlockPos pos, String tagElement) { + if (tagElement == null) + return; + + rawCustomText = getJsonFromString(tagElement); + parsedCustomText = parseCustomText(level, pos, rawCustomText); + } + + public boolean sameAs(String tagElement) { + return isValid() && rawCustomText.equals(getJsonFromString(tagElement)); + } + + public boolean isValid() { + return parsedCustomText != null && rawCustomText != null; + } + + public String resolve() { + return parsedCustomText.getString(); + } + + public MutableComponent get() { + return parsedCustomText == null ? Components.empty() : parsedCustomText.copy(); + } + + public void read(Level level, BlockPos pos, CompoundTag nbt) { + rawCustomText = getJsonFromString(nbt.getString("RawCustomText")); + try { + parsedCustomText = Component.Serializer.fromJson(nbt.getString("CustomText")); + } catch (JsonParseException e) { + parsedCustomText = null; + } + } + + public void write(CompoundTag nbt) { + if (!isValid()) + return; + + nbt.putString("RawCustomText", rawCustomText.toString()); + nbt.putString("CustomText", Component.Serializer.toJson(parsedCustomText)); + } + + public static JsonElement getJsonFromString(String string) { + try { + return JsonParser.parseString(string); + } catch (JsonParseException e) { + return null; + } + } + + public static Component parseCustomText(Level level, BlockPos pos, JsonElement customText) { + if (!(level instanceof ServerLevel serverLevel)) + return null; + try { + return ComponentUtils.updateForEntity(getCommandSource(serverLevel, pos), + Component.Serializer.fromJson(customText), null, 0); + } catch (JsonParseException e) { + return null; + } catch (CommandSyntaxException e) { + return null; + } + } + + public static CommandSourceStack getCommandSource(ServerLevel level, BlockPos pos) { + return new CommandSourceStack(CommandSource.NULL, Vec3.atCenterOf(pos), Vec2.ZERO, level, 2, Create.ID, + Components.literal(Create.ID), level.getServer(), null); + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/FilesHelper.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/FilesHelper.java new file mode 100644 index 0000000..8fba34d --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/FilesHelper.java @@ -0,0 +1,101 @@ +package nl.requios.effortlessbuilding.create.foundation.utility; + +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import com.google.gson.internal.Streams; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import nl.requios.effortlessbuilding.create.Create; +import net.minecraft.nbt.CompoundTag; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; + +public class FilesHelper { + + public static void createFolderIfMissing(String name) { + try { + Files.createDirectories(Paths.get(name)); + } catch (IOException e) { + Create.LOGGER.warn("Could not create Folder: {}", name); + } + } + + public static String findFirstValidFilename(String name, String folderPath, String extension) { + int index = 0; + String filename; + String filepath; + do { + filename = slug(name) + ((index == 0) ? "" : "_" + index) + "." + extension; + index++; + filepath = folderPath + "/" + filename; + } while (Files.exists(Paths.get(filepath))); + return filename; + } + + public static String slug(String name) { + return Lang.asId(name) + .replaceAll("\\W+", "_"); + } + + public static boolean saveTagCompoundAsJson(CompoundTag compound, String path) { + try { + Files.deleteIfExists(Paths.get(path)); + JsonWriter writer = new JsonWriter(Files.newBufferedWriter(Paths.get(path), StandardOpenOption.CREATE)); + writer.setIndent(" "); + Streams.write(JsonParser.parseString(compound.toString()), writer); + writer.close(); + return true; + } catch (IOException e) { + e.printStackTrace(); + } + return false; + } + + public static boolean saveTagCompoundAsJsonCompact(CompoundTag compound, String path) { + try { + Files.deleteIfExists(Paths.get(path)); + JsonWriter writer = new JsonWriter(Files.newBufferedWriter(Paths.get(path), StandardOpenOption.CREATE)); + Streams.write(JsonParser.parseString(compound.toString()), writer); + writer.close(); + return true; + } catch (IOException e) { + e.printStackTrace(); + } + return false; + + } + + private static JsonElement loadJson(InputStream inputStream) { + try { + JsonReader reader = new JsonReader(new BufferedReader(new InputStreamReader(inputStream))); + reader.setLenient(true); + JsonElement element = Streams.parse(reader); + reader.close(); + inputStream.close(); + return element; + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + public static JsonElement loadJsonResource(String filepath) { + return loadJson(ClassLoader.getSystemResourceAsStream(filepath)); + } + + public static JsonElement loadJson(String filepath) { + try { + return loadJson(Files.newInputStream(Paths.get(filepath), StandardOpenOption.READ)); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/FluidFormatter.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/FluidFormatter.java new file mode 100644 index 0000000..ec19ea9 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/FluidFormatter.java @@ -0,0 +1,26 @@ +package nl.requios.effortlessbuilding.create.foundation.utility; + +import net.minecraft.network.chat.MutableComponent; + +public class FluidFormatter { + + public static String asString(long amount, boolean shorten) { + Couple couple = asComponents(amount, shorten); + return couple.getFirst().getString() + " " + couple.getSecond().getString(); + } + + public static Couple asComponents(long amount, boolean shorten) { + if (shorten && amount >= 1000) { + return Couple.create( + Components.literal(String.format("%.1f" , amount / 1000d)), + Lang.translateDirect("generic.unit.buckets") + ); + } + + return Couple.create( + Components.literal(String.valueOf(amount)), + Lang.translateDirect("generic.unit.millibuckets") + ); + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/FontHelper.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/FontHelper.java new file mode 100644 index 0000000..092c995 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/FontHelper.java @@ -0,0 +1,87 @@ +package nl.requios.effortlessbuilding.create.foundation.utility; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.Tesselator; +import com.mojang.math.Matrix4f; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.renderer.LightTexture; +import net.minecraft.client.renderer.MultiBufferSource; + +import java.text.BreakIterator; +import java.util.LinkedList; +import java.util.List; + +public final class FontHelper { + + private FontHelper() {} + + public static List cutString(Font font, String text, int maxWidthPerLine) { + // Split words + List words = new LinkedList<>(); + BreakIterator iterator = BreakIterator.getLineInstance(Minecraft.getInstance().getLocale()); + iterator.setText(text); + int start = iterator.first(); + for (int end = iterator.next(); end != BreakIterator.DONE; start = end, end = iterator.next()) { + String word = text.substring(start, end); + words.add(word); + } + // Apply hard wrap + List lines = new LinkedList<>(); + StringBuilder currentLine = new StringBuilder(); + int width = 0; + for (String word : words) { + int newWidth = font.width(word); + if (width + newWidth > maxWidthPerLine) { + if (width > 0) { + String line = currentLine.toString(); + lines.add(line); + currentLine = new StringBuilder(); + width = 0; + } else { + lines.add(word); + continue; + } + } + currentLine.append(word); + width += newWidth; + } + if (width > 0) { + lines.add(currentLine.toString()); + } + return lines; + } + + public static void drawSplitString(PoseStack ms, Font font, String text, int x, int y, int width, + int color) { + List list = cutString(font, text, width); + Matrix4f matrix4f = ms.last() + .pose(); + + for (String s : list) { + float f = (float) x; + if (font.isBidirectional()) { + int i = font.width(font.bidirectionalShaping(s)); + f += (float) (width - i); + } + + draw(font, s, f, (float) y, color, matrix4f, false); + y += 9; + } + } + + private static int draw(Font font, String p_228078_1_, float p_228078_2_, float p_228078_3_, + int p_228078_4_, Matrix4f p_228078_5_, boolean p_228078_6_) { + if (p_228078_1_ == null) { + return 0; + } else { + MultiBufferSource.BufferSource irendertypebuffer$impl = MultiBufferSource.immediate(Tesselator.getInstance() + .getBuilder()); + int i = font.drawInBatch(p_228078_1_, p_228078_2_, p_228078_3_, p_228078_4_, p_228078_6_, p_228078_5_, + irendertypebuffer$impl, false, 0, LightTexture.FULL_BRIGHT); + irendertypebuffer$impl.endBatch(); + return i; + } + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/ICoordinate.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/ICoordinate.java new file mode 100644 index 0000000..1f466d6 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/ICoordinate.java @@ -0,0 +1,8 @@ +package nl.requios.effortlessbuilding.create.foundation.utility; + +import net.minecraft.core.BlockPos; + +@FunctionalInterface +public interface ICoordinate { + float get(BlockPos from); +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/IInteractionChecker.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/IInteractionChecker.java new file mode 100644 index 0000000..0dc13c9 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/IInteractionChecker.java @@ -0,0 +1,7 @@ +package nl.requios.effortlessbuilding.create.foundation.utility; + +import net.minecraft.world.entity.player.Player; + +public interface IInteractionChecker { + boolean canPlayerUse(Player player); +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/IPartialSafeNBT.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/IPartialSafeNBT.java new file mode 100644 index 0000000..dfe8a01 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/IPartialSafeNBT.java @@ -0,0 +1,8 @@ +package nl.requios.effortlessbuilding.create.foundation.utility; + +import net.minecraft.nbt.CompoundTag; + +public interface IPartialSafeNBT { + /** This method always runs on the logical server. */ + public void writeSafe(CompoundTag compound); +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/IntAttached.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/IntAttached.java new file mode 100644 index 0000000..3d9e28e --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/IntAttached.java @@ -0,0 +1,61 @@ +package nl.requios.effortlessbuilding.create.foundation.utility; + +import net.minecraft.nbt.CompoundTag; + +import java.util.Comparator; +import java.util.function.Function; + +public class IntAttached extends Pair { + + protected IntAttached(Integer first, V second) { + super(first, second); + } + + public static IntAttached with(int number, V value) { + return new IntAttached<>(number, value); + } + + public static IntAttached withZero(V value) { + return new IntAttached<>(0, value); + } + + public boolean isZero() { + return first.intValue() == 0; + } + + public boolean exceeds(int value) { + return first.intValue() > value; + } + + public boolean isOrBelowZero() { + return first.intValue() <= 0; + } + + public void increment() { + first++; + } + + public void decrement() { + first--; + } + + public V getValue() { + return getSecond(); + } + + public CompoundTag serializeNBT(Function serializer) { + CompoundTag nbt = new CompoundTag(); + nbt.put("Item", serializer.apply(getValue())); + nbt.putInt("Location", getFirst()); + return nbt; + } + + public static Comparator> comparator() { + return (i1, i2) -> Integer.compare(i2.getFirst(), i1.getFirst()); + } + + public static IntAttached read(CompoundTag nbt, Function deserializer) { + return IntAttached.with(nbt.getInt("Location"), deserializer.apply(nbt.getCompound("Item"))); + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/Iterate.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/Iterate.java new file mode 100644 index 0000000..6382596 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/Iterate.java @@ -0,0 +1,48 @@ +package nl.requios.effortlessbuilding.create.foundation.utility; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.Direction.Axis; + +import java.util.Arrays; +import java.util.EnumSet; +import java.util.List; + +public class Iterate { + + public static final boolean[] trueAndFalse = { true, false }; + public static final boolean[] falseAndTrue = { false, true }; + public static final int[] zeroAndOne = { 0, 1 }; + public static final int[] positiveAndNegative = { 1, -1 }; + public static final Direction[] directions = Direction.values(); + public static final Direction[] horizontalDirections = getHorizontals(); + public static final Axis[] axes = Axis.values(); + public static final EnumSet axisSet = EnumSet.allOf(Axis.class); + + private static Direction[] getHorizontals() { + Direction[] directions = new Direction[4]; + for (int i = 0; i < 4; i++) + directions[i] = Direction.from2DDataValue(i); + return directions; + } + + public static Direction[] directionsInAxis(Axis axis) { + switch (axis) { + case X: + return new Direction[] { Direction.EAST, Direction.WEST }; + case Y: + return new Direction[] { Direction.UP, Direction.DOWN }; + default: + case Z: + return new Direction[] { Direction.SOUTH, Direction.NORTH }; + } + } + + public static List hereAndBelow(BlockPos pos) { + return Arrays.asList(pos, pos.below()); + } + + public static List hereBelowAndAbove(BlockPos pos) { + return Arrays.asList(pos, pos.below(), pos.above()); + } +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/Lang.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/Lang.java new file mode 100644 index 0000000..e14385c --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/Lang.java @@ -0,0 +1,91 @@ +package nl.requios.effortlessbuilding.create.foundation.utility; + +import nl.requios.effortlessbuilding.create.Create; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraftforge.fluids.FluidStack; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +public class Lang { + + /** + * legacy-ish. Use Lang.translate and other builder methods where possible + * + * @param key + * @param args + * @return + */ + public static MutableComponent translateDirect(String key, Object... args) { + return Components.translatable(Create.ID + "." + key, resolveBuilders(args)); + } + + public static String asId(String name) { + return name.toLowerCase(Locale.ROOT); + } + + public static String nonPluralId(String name) { + String asId = asId(name); + return asId.endsWith("s") ? asId.substring(0, asId.length() - 1) : asId; + } + + public static List translatedOptions(String prefix, String... keys) { + List result = new ArrayList<>(keys.length); + for (String key : keys) + result.add(translate((prefix != null ? prefix + "." : "") + key).component()); + return result; + } + + // + + public static LangBuilder builder() { + return new LangBuilder(Create.ID); + } + + public static LangBuilder builder(String namespace) { + return new LangBuilder(namespace); + } + + // + + public static LangBuilder blockName(BlockState state) { + return builder().add(state.getBlock() + .getName()); + } + + public static LangBuilder itemName(ItemStack stack) { + return builder().add(stack.getHoverName() + .copy()); + } + + public static LangBuilder fluidName(FluidStack stack) { + return builder().add(stack.getDisplayName() + .copy()); + } + + public static LangBuilder number(double d) { + return builder().text(LangNumberFormat.format(d)); + } + + public static LangBuilder translate(String langKey, Object... args) { + return builder().translate(langKey, args); + } + + public static LangBuilder text(String text) { + return builder().text(text); + } + + // + + public static Object[] resolveBuilders(Object[] args) { + for (int i = 0; i < args.length; i++) + if (args[i]instanceof LangBuilder cb) + args[i] = cb.component(); + return args; + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/LangBuilder.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/LangBuilder.java new file mode 100644 index 0000000..b143a0b --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/LangBuilder.java @@ -0,0 +1,165 @@ +package nl.requios.effortlessbuilding.create.foundation.utility; + +import joptsimple.internal.Strings; +import net.minecraft.ChatFormatting; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.world.entity.player.Player; + +import java.util.List; + +public class LangBuilder { + + String namespace; + MutableComponent component; + + public LangBuilder(String namespace) { + this.namespace = namespace; + } + + public LangBuilder space() { + return text(" "); + } + + public LangBuilder newLine() { + return text("\n"); + } + + /** + * Appends a localised component
+ * To add an independently formatted localised component, use add() and a nested + * builder + * + * @param langKey + * @param args + * @return + */ + public LangBuilder translate(String langKey, Object... args) { + return add(Components.translatable(namespace + "." + langKey, Lang.resolveBuilders(args))); + } + + /** + * Appends a text component + * + * @param literalText + * @return + */ + public LangBuilder text(String literalText) { + return add(Components.literal(literalText)); + } + + /** + * Appends a colored text component + * + * @param format + * @param literalText + * @return + */ + public LangBuilder text(ChatFormatting format, String literalText) { + return add(Components.literal(literalText).withStyle(format)); + } + + /** + * Appends a colored text component + * + * @param color + * @param literalText + * @return + */ + public LangBuilder text(int color, String literalText) { + return add(Components.literal(literalText).withStyle(s -> s.withColor(color))); + } + + /** + * Appends the contents of another builder + * + * @param otherBuilder + * @return + */ + public LangBuilder add(LangBuilder otherBuilder) { + return add(otherBuilder.component()); + } + + /** + * Appends a component + * + * @param customComponent + * @return + */ + public LangBuilder add(MutableComponent customComponent) { + component = component == null ? customComponent : component.append(customComponent); + return this; + } + + // + + /** + * Applies the format to all added components + * + * @param format + * @return + */ + public LangBuilder style(ChatFormatting format) { + assertComponent(); + component = component.withStyle(format); + return this; + } + + /** + * Applies the color to all added components + * + * @param color + * @return + */ + public LangBuilder color(int color) { + assertComponent(); + component = component.withStyle(s -> s.withColor(color)); + return this; + } + + // + + public MutableComponent component() { + assertComponent(); + return component; + } + + public String string() { + return component().getString(); + } + + public String json() { + return Component.Serializer.toJson(component()); + } + + public void sendStatus(Player player) { + player.displayClientMessage(component(), true); + } + + public void sendChat(Player player) { + player.displayClientMessage(component(), false); + } + + public void addTo(List tooltip) { + tooltip.add(component()); + } + + public void forGoggles(List tooltip) { + forGoggles(tooltip, 0); + } + + public void forGoggles(List tooltip, int indents) { + tooltip.add(Lang.builder() + .text(Strings.repeat(' ', 4 + indents)) + .add(this) + .component()); + } + + // + + private void assertComponent() { + if (component == null) + throw new IllegalStateException("No components were added to builder"); + } + +} \ No newline at end of file diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/LangNumberFormat.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/LangNumberFormat.java new file mode 100644 index 0000000..bcf9d41 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/LangNumberFormat.java @@ -0,0 +1,33 @@ +package nl.requios.effortlessbuilding.create.foundation.utility; + +import net.minecraft.client.Minecraft; + +import java.text.NumberFormat; +import java.util.Locale; + +public class LangNumberFormat { + + private NumberFormat format = NumberFormat.getNumberInstance(Locale.ROOT); + public static LangNumberFormat numberFormat = new LangNumberFormat(); + + public NumberFormat get() { + return format; + } + + public void update() { + format = NumberFormat.getInstance(Minecraft.getInstance() + .getLanguageManager() + .getSelected() + .getJavaLocale()); + format.setMaximumFractionDigits(2); + format.setMinimumFractionDigits(0); + format.setGroupingUsed(true); + } + + public static String format(double d) { + return numberFormat.get() + .format(d) + .replace("\u00A0", " "); + } + +} \ No newline at end of file diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/NBTHelper.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/NBTHelper.java new file mode 100644 index 0000000..d409073 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/NBTHelper.java @@ -0,0 +1,106 @@ +package nl.requios.effortlessbuilding.create.foundation.utility; + +import net.minecraft.core.Vec3i; +import net.minecraft.nbt.*; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.phys.AABB; + +import javax.annotation.Nonnull; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Function; + +public class NBTHelper { + + public static void putMarker(CompoundTag nbt, String marker) { + nbt.putBoolean(marker, true); + } + + public static > T readEnum(CompoundTag nbt, String key, Class enumClass) { + T[] enumConstants = enumClass.getEnumConstants(); + if (enumConstants == null) + throw new IllegalArgumentException("Non-Enum class passed to readEnum: " + enumClass.getName()); + if (nbt.contains(key, Tag.TAG_STRING)) { + String name = nbt.getString(key); + for (T t : enumConstants) { + if (t.name() + .equals(name)) + return t; + } + } + return enumConstants[0]; + } + + public static > void writeEnum(CompoundTag nbt, String key, T enumConstant) { + nbt.putString(key, enumConstant.name()); + } + + public static ListTag writeCompoundList(Iterable list, Function serializer) { + ListTag listNBT = new ListTag(); + list.forEach(t -> { + CompoundTag apply = serializer.apply(t); + if (apply == null) + return; + listNBT.add(apply); + }); + return listNBT; + } + + public static List readCompoundList(ListTag listNBT, Function deserializer) { + List list = new ArrayList<>(listNBT.size()); + listNBT.forEach(inbt -> list.add(deserializer.apply((CompoundTag) inbt))); + return list; + } + + public static void iterateCompoundList(ListTag listNBT, Consumer consumer) { + listNBT.forEach(inbt -> consumer.accept((CompoundTag) inbt)); + } + + public static ListTag writeItemList(Iterable stacks) { + return writeCompoundList(stacks, ItemStack::serializeNBT); + } + + public static List readItemList(ListTag stacks) { + return readCompoundList(stacks, ItemStack::of); + } + + public static ListTag writeAABB(AABB bb) { + ListTag bbtag = new ListTag(); + bbtag.add(FloatTag.valueOf((float) bb.minX)); + bbtag.add(FloatTag.valueOf((float) bb.minY)); + bbtag.add(FloatTag.valueOf((float) bb.minZ)); + bbtag.add(FloatTag.valueOf((float) bb.maxX)); + bbtag.add(FloatTag.valueOf((float) bb.maxY)); + bbtag.add(FloatTag.valueOf((float) bb.maxZ)); + return bbtag; + } + + public static AABB readAABB(ListTag bbtag) { + if (bbtag == null || bbtag.isEmpty()) + return null; + return new AABB(bbtag.getFloat(0), bbtag.getFloat(1), bbtag.getFloat(2), bbtag.getFloat(3), + bbtag.getFloat(4), bbtag.getFloat(5)); + } + + public static ListTag writeVec3i(Vec3i vec) { + ListTag tag = new ListTag(); + tag.add(IntTag.valueOf(vec.getX())); + tag.add(IntTag.valueOf(vec.getY())); + tag.add(IntTag.valueOf(vec.getZ())); + return tag; + } + + public static Vec3i readVec3i(ListTag tag) { + return new Vec3i(tag.getInt(0), tag.getInt(1), tag.getInt(2)); + } + + @Nonnull + public static Tag getINBT(CompoundTag nbt, String id) { + Tag inbt = nbt.get(id); + if (inbt != null) + return inbt; + return new CompoundTag(); + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/NBTProcessors.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/NBTProcessors.java new file mode 100644 index 0000000..218024e --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/NBTProcessors.java @@ -0,0 +1,84 @@ +package nl.requios.effortlessbuilding.create.foundation.utility; + +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.Tag; +import net.minecraft.network.chat.Component; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.entity.SpawnerBlockEntity; + +import javax.annotation.Nullable; +import java.util.HashMap; +import java.util.Map; +import java.util.function.UnaryOperator; + +public final class NBTProcessors { + + private static final Map, UnaryOperator> processors = new HashMap<>(); + private static final Map, UnaryOperator> survivalProcessors = new HashMap<>(); + + public static synchronized void addProcessor(BlockEntityType type, UnaryOperator processor) { + processors.put(type, processor); + } + + public static synchronized void addSurvivalProcessor(BlockEntityType type, UnaryOperator processor) { + survivalProcessors.put(type, processor); + } + + static { + addProcessor(BlockEntityType.SIGN, data -> { + for (int i = 0; i < 4; ++i) { + if (textComponentHasClickEvent(data.getString("Text" + (i + 1)))) + return null; + } + return data; + }); + addProcessor(BlockEntityType.LECTERN, data -> { + if (!data.contains("Book", Tag.TAG_COMPOUND)) + return data; + CompoundTag book = data.getCompound("Book"); + + if (!book.contains("tag", Tag.TAG_COMPOUND)) + return data; + CompoundTag tag = book.getCompound("tag"); + + if (!tag.contains("pages", Tag.TAG_LIST)) + return data; + ListTag pages = tag.getList("pages", Tag.TAG_STRING); + + for (Tag inbt : pages) { + if (textComponentHasClickEvent(inbt.getAsString())) + return null; + } + return data; + }); + } + + public static boolean textComponentHasClickEvent(String json) { + Component component = Component.Serializer.fromJson(json.isEmpty() ? "\"\"" : json); + return component != null && component.getStyle() != null && component.getStyle().getClickEvent() != null; + } + + private NBTProcessors() { + } + + @Nullable + public static CompoundTag process(BlockEntity tileEntity, CompoundTag compound, boolean survival) { + if (compound == null) + return null; + BlockEntityType type = tileEntity.getType(); + if (survival && survivalProcessors.containsKey(type)) + compound = survivalProcessors.get(type) + .apply(compound); + if (compound != null && processors.containsKey(type)) + return processors.get(type) + .apply(compound); + if (tileEntity instanceof SpawnerBlockEntity) + return compound; + if (tileEntity.onlyOpCanSetNbt()) + return null; + return compound; + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/Pair.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/Pair.java new file mode 100644 index 0000000..569b1e4 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/Pair.java @@ -0,0 +1,68 @@ +package nl.requios.effortlessbuilding.create.foundation.utility; + +import java.util.Objects; + +public class Pair { + + F first; + S second; + + protected Pair(F first, S second) { + this.first = first; + this.second = second; + } + + public static Pair of(F first, S second) { + return new Pair<>(first, second); + } + + public F getFirst() { + return first; + } + + public S getSecond() { + return second; + } + + public void setFirst(F first) { + this.first = first; + } + + public void setSecond(S second) { + this.second = second; + } + + public Pair copy() { + return Pair.of(first, second); + } + + @Override + public boolean equals(final Object obj) { + if (obj == this) + return true; + if (obj instanceof Pair) { + final Pair other = (Pair) obj; + return Objects.equals(first, other.first) && Objects.equals(second, other.second); + } + return false; + } + + @Override + public int hashCode() { + return (nullHash(first) * 31) ^ nullHash(second); + } + + int nullHash(Object o) { + return o == null ? 0 : o.hashCode(); + } + + @Override + public String toString() { + return "(" + first + ", " + second + ")"; + } + + public Pair swap() { + return Pair.of(second, first); + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/Pointing.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/Pointing.java new file mode 100644 index 0000000..18a1716 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/Pointing.java @@ -0,0 +1,35 @@ +package nl.requios.effortlessbuilding.create.foundation.utility; + +import net.minecraft.core.Direction; +import net.minecraft.core.Direction.Axis; +import net.minecraft.core.Direction.AxisDirection; +import net.minecraft.util.StringRepresentable; + +public enum Pointing implements StringRepresentable { + UP(0), LEFT(270), DOWN(180), RIGHT(90); + + private int xRotation; + + private Pointing(int xRotation) { + this.xRotation = xRotation; + } + + @Override + public String getSerializedName() { + return Lang.asId(name()); + } + + public int getXRotation() { + return xRotation; + } + + public Direction getCombinedDirection(Direction direction) { + Axis axis = direction.getAxis(); + Direction top = axis == Axis.Y ? Direction.SOUTH : Direction.UP; + int rotations = direction.getAxisDirection() == AxisDirection.NEGATIVE ? 4 - ordinal() : ordinal(); + for (int i = 0; i < rotations; i++) + top = top.getClockWise(axis); + return top; + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/RaycastHelper.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/RaycastHelper.java new file mode 100644 index 0000000..b11259e --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/RaycastHelper.java @@ -0,0 +1,197 @@ +package nl.requios.effortlessbuilding.create.foundation.utility; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.BlockPos.MutableBlockPos; +import net.minecraft.core.Direction; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.ClipContext; +import net.minecraft.world.level.ClipContext.Block; +import net.minecraft.world.level.ClipContext.Fluid; +import net.minecraft.world.level.Level; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.Vec3; + +import java.util.function.Predicate; + +public class RaycastHelper { + + public static BlockHitResult rayTraceRange(Level worldIn, Player playerIn, double range) { + Vec3 origin = getTraceOrigin(playerIn); + Vec3 target = getTraceTarget(playerIn, range, origin); + ClipContext context = new ClipContext(origin, target, Block.COLLIDER, Fluid.NONE, playerIn); + return worldIn.clip(context); + } + + public static PredicateTraceResult rayTraceUntil(Player playerIn, double range, + Predicate predicate) { + Vec3 origin = getTraceOrigin(playerIn); + Vec3 target = getTraceTarget(playerIn, range, origin); + return rayTraceUntil(origin, target, predicate); + } + + public static Vec3 getTraceTarget(Player playerIn, double range, Vec3 origin) { + float f = playerIn.getXRot(); + float f1 = playerIn.getYRot(); + float f2 = Mth.cos(-f1 * 0.017453292F - (float) Math.PI); + float f3 = Mth.sin(-f1 * 0.017453292F - (float) Math.PI); + float f4 = -Mth.cos(-f * 0.017453292F); + float f5 = Mth.sin(-f * 0.017453292F); + float f6 = f3 * f4; + float f7 = f2 * f4; + double d3 = range; + Vec3 Vector3d1 = origin.add((double) f6 * d3, (double) f5 * d3, (double) f7 * d3); + return Vector3d1; + } + + public static Vec3 getTraceOrigin(Player playerIn) { + double d0 = playerIn.getX(); + double d1 = playerIn.getY() + (double) playerIn.getEyeHeight(); + double d2 = playerIn.getZ(); + Vec3 Vector3d = new Vec3(d0, d1, d2); + return Vector3d; + } + + public static PredicateTraceResult rayTraceUntil(Vec3 start, Vec3 end, Predicate predicate) { + if (Double.isNaN(start.x) || Double.isNaN(start.y) || Double.isNaN(start.z)) + return null; + if (Double.isNaN(end.x) || Double.isNaN(end.y) || Double.isNaN(end.z)) + return null; + + int dx = Mth.floor(end.x); + int dy = Mth.floor(end.y); + int dz = Mth.floor(end.z); + int x = Mth.floor(start.x); + int y = Mth.floor(start.y); + int z = Mth.floor(start.z); + + MutableBlockPos currentPos = new BlockPos(x, y, z).mutable(); + + if (predicate.test(currentPos)) + return new PredicateTraceResult(currentPos.immutable(), Direction.getNearest(dx - x, dy - y, dz - z)); + + int remainingDistance = 200; + + while (remainingDistance-- >= 0) { + if (Double.isNaN(start.x) || Double.isNaN(start.y) || Double.isNaN(start.z)) { + return null; + } + + if (x == dx && y == dy && z == dz) { + return new PredicateTraceResult(); + } + + boolean flag2 = true; + boolean flag = true; + boolean flag1 = true; + double d0 = 999.0D; + double d1 = 999.0D; + double d2 = 999.0D; + + if (dx > x) { + d0 = (double) x + 1.0D; + } else if (dx < x) { + d0 = (double) x + 0.0D; + } else { + flag2 = false; + } + + if (dy > y) { + d1 = (double) y + 1.0D; + } else if (dy < y) { + d1 = (double) y + 0.0D; + } else { + flag = false; + } + + if (dz > z) { + d2 = (double) z + 1.0D; + } else if (dz < z) { + d2 = (double) z + 0.0D; + } else { + flag1 = false; + } + + double d3 = 999.0D; + double d4 = 999.0D; + double d5 = 999.0D; + double d6 = end.x - start.x; + double d7 = end.y - start.y; + double d8 = end.z - start.z; + + if (flag2) { + d3 = (d0 - start.x) / d6; + } + + if (flag) { + d4 = (d1 - start.y) / d7; + } + + if (flag1) { + d5 = (d2 - start.z) / d8; + } + + if (d3 == -0.0D) { + d3 = -1.0E-4D; + } + + if (d4 == -0.0D) { + d4 = -1.0E-4D; + } + + if (d5 == -0.0D) { + d5 = -1.0E-4D; + } + + Direction enumfacing; + + if (d3 < d4 && d3 < d5) { + enumfacing = dx > x ? Direction.WEST : Direction.EAST; + start = new Vec3(d0, start.y + d7 * d3, start.z + d8 * d3); + } else if (d4 < d5) { + enumfacing = dy > y ? Direction.DOWN : Direction.UP; + start = new Vec3(start.x + d6 * d4, d1, start.z + d8 * d4); + } else { + enumfacing = dz > z ? Direction.NORTH : Direction.SOUTH; + start = new Vec3(start.x + d6 * d5, start.y + d7 * d5, d2); + } + + x = Mth.floor(start.x) - (enumfacing == Direction.EAST ? 1 : 0); + y = Mth.floor(start.y) - (enumfacing == Direction.UP ? 1 : 0); + z = Mth.floor(start.z) - (enumfacing == Direction.SOUTH ? 1 : 0); + currentPos.set(x, y, z); + + if (predicate.test(currentPos)) + return new PredicateTraceResult(currentPos.immutable(), enumfacing); + } + + return new PredicateTraceResult(); + } + + public static class PredicateTraceResult { + private BlockPos pos; + private Direction facing; + + public PredicateTraceResult(BlockPos pos, Direction facing) { + this.pos = pos; + this.facing = facing; + } + + public PredicateTraceResult() { + // missed, no result + } + + public Direction getFacing() { + return facing; + } + + public BlockPos getPos() { + return pos; + } + + public boolean missed() { + return this.pos == null; + } + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/RegisteredObjects.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/RegisteredObjects.java new file mode 100644 index 0000000..68e3108 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/RegisteredObjects.java @@ -0,0 +1,66 @@ +package nl.requios.effortlessbuilding.create.foundation.utility; + +import net.minecraft.core.particles.ParticleType; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.alchemy.Potion; +import net.minecraft.world.item.crafting.RecipeSerializer; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.material.Fluid; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.IForgeRegistry; +import org.jetbrains.annotations.NotNull; + +public final class RegisteredObjects { + // registry argument for easier porting to 1.19 + @NotNull + public static ResourceLocation getKeyOrThrow(IForgeRegistry registry, V value) { + ResourceLocation key = registry.getKey(value); + if (key == null) { + throw new IllegalArgumentException("Could not get key for value " + value + "!"); + } + return key; + } + + @NotNull + public static ResourceLocation getKeyOrThrow(Block value) { + return getKeyOrThrow(ForgeRegistries.BLOCKS, value); + } + + @NotNull + public static ResourceLocation getKeyOrThrow(Item value) { + return getKeyOrThrow(ForgeRegistries.ITEMS, value); + } + + @NotNull + public static ResourceLocation getKeyOrThrow(Fluid value) { + return getKeyOrThrow(ForgeRegistries.FLUIDS, value); + } + + @NotNull + public static ResourceLocation getKeyOrThrow(EntityType value) { + return getKeyOrThrow(ForgeRegistries.ENTITY_TYPES, value); + } + + @NotNull + public static ResourceLocation getKeyOrThrow(BlockEntityType value) { + return getKeyOrThrow(ForgeRegistries.BLOCK_ENTITY_TYPES, value); + } + + @NotNull + public static ResourceLocation getKeyOrThrow(Potion value) { + return getKeyOrThrow(ForgeRegistries.POTIONS, value); + } + + @NotNull + public static ResourceLocation getKeyOrThrow(ParticleType value) { + return getKeyOrThrow(ForgeRegistries.PARTICLE_TYPES, value); + } + + @NotNull + public static ResourceLocation getKeyOrThrow(RecipeSerializer value) { + return getKeyOrThrow(ForgeRegistries.RECIPE_SERIALIZERS, value); + } +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/ResetableLazy.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/ResetableLazy.java new file mode 100644 index 0000000..b18f34f --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/ResetableLazy.java @@ -0,0 +1,32 @@ +package nl.requios.effortlessbuilding.create.foundation.utility; + +import net.minecraftforge.common.util.NonNullSupplier; + +import java.util.function.Supplier; + +public class ResetableLazy implements Supplier { + + private final NonNullSupplier supplier; + private T value; + + public ResetableLazy(NonNullSupplier supplier) { + this.supplier = supplier; + } + + @Override + public T get() { + if (value == null) { + value = supplier.get(); + } + return value; + } + + public void reset() { + value = null; + } + + public static ResetableLazy of(NonNullSupplier supplier) { + return new ResetableLazy<>(supplier); + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/UniqueLinkedList.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/UniqueLinkedList.java new file mode 100644 index 0000000..d5d148c --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/UniqueLinkedList.java @@ -0,0 +1,102 @@ +package nl.requios.effortlessbuilding.create.foundation.utility; + +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Collectors; + +public class UniqueLinkedList extends LinkedList { + /** + * + */ + private static final long serialVersionUID = 1L; + private final HashSet contained = new HashSet<>(); + + @Override + public boolean contains(Object o) { + return contained.contains(o); + } + + @Override + public E poll() { + E e = super.poll(); + contained.remove(e); + return e; + } + + @Override + public boolean add(E e) { + if (contained.add(e)) + return super.add(e); + else + return false; + } + + @Override + public void add(int index, E element) { + if (contained.add(element)) + super.add(index, element); + } + + @Override + public void addFirst(E e) { + if (contained.add(e)) + super.addFirst(e); + } + + @Override + public void addLast(E e) { + if (contained.add(e)) + super.addLast(e); + } + + @Override + public boolean addAll(Collection c) { + List filtered = c.stream() + .filter(it -> !contained.contains(it)) + .collect(Collectors.toList()); + return super.addAll(filtered); + } + + @Override + public boolean addAll(int index, Collection c) { + List filtered = c.stream() + .filter(it -> !contained.contains(it)) + .collect(Collectors.toList()); + return super.addAll(index, filtered); + } + + @Override + public boolean remove(Object o) { + contained.remove(o); + return super.remove(o); + } + + @Override + public E remove(int index) { + E e = super.remove(index); + contained.remove(e); + return e; + } + + @Override + public E removeFirst() { + E e = super.removeFirst(); + contained.remove(e); + return e; + } + + @Override + public E removeLast() { + E e = super.removeLast(); + contained.remove(e); + return e; + } + + @Override + public void clear() { + super.clear(); + contained.clear(); + } +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/VecHelper.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/VecHelper.java new file mode 100644 index 0000000..e9ea1d0 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/VecHelper.java @@ -0,0 +1,343 @@ +package nl.requios.effortlessbuilding.create.foundation.utility; + +import com.mojang.math.Quaternion; +import com.mojang.math.Vector3f; +import net.minecraft.client.Camera; +import net.minecraft.client.Minecraft; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.Direction.Axis; +import net.minecraft.core.Vec3i; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.DoubleTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.Tag; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.util.Mth; +import net.minecraft.util.RandomSource; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.block.Mirror; +import net.minecraft.world.phys.Vec3; + +import javax.annotation.Nullable; + +public class VecHelper { + + public static final Vec3 CENTER_OF_ORIGIN = new Vec3(.5, .5, .5); + + public static Vec3 rotate(Vec3 vec, Vec3 rotationVec) { + return rotate(vec, rotationVec.x, rotationVec.y, rotationVec.z); + } + + public static Vec3 rotate(Vec3 vec, double xRot, double yRot, double zRot) { + return rotate(rotate(rotate(vec, xRot, Axis.X), yRot, Axis.Y), zRot, Axis.Z); + } + + public static Vec3 rotateCentered(Vec3 vec, double deg, Axis axis) { + Vec3 shift = getCenterOf(BlockPos.ZERO); + return VecHelper.rotate(vec.subtract(shift), deg, axis) + .add(shift); + } + + public static Vec3 rotate(Vec3 vec, double deg, Axis axis) { + if (deg == 0) + return vec; + if (vec == Vec3.ZERO) + return vec; + + float angle = (float) (deg / 180f * Math.PI); + double sin = Mth.sin(angle); + double cos = Mth.cos(angle); + double x = vec.x; + double y = vec.y; + double z = vec.z; + + if (axis == Axis.X) + return new Vec3(x, y * cos - z * sin, z * cos + y * sin); + if (axis == Axis.Y) + return new Vec3(x * cos + z * sin, y, z * cos - x * sin); + if (axis == Axis.Z) + return new Vec3(x * cos - y * sin, y * cos + x * sin, z); + return vec; + } + + public static Vec3 mirrorCentered(Vec3 vec, Mirror mirror) { + Vec3 shift = getCenterOf(BlockPos.ZERO); + return VecHelper.mirror(vec.subtract(shift), mirror) + .add(shift); + } + + public static Vec3 mirror(Vec3 vec, Mirror mirror) { + if (mirror == null || mirror == Mirror.NONE) + return vec; + if (vec == Vec3.ZERO) + return vec; + + double x = vec.x; + double y = vec.y; + double z = vec.z; + + if (mirror == Mirror.LEFT_RIGHT) + return new Vec3(x, y, -z); + if (mirror == Mirror.FRONT_BACK) + return new Vec3(-x, y, z); + return vec; + } + + public static Vec3 lookAt(Vec3 vec, Vec3 fwd) { + fwd = fwd.normalize(); + Vec3 up = new Vec3(0, 1, 0); + double dot = fwd.dot(up); + if (Math.abs(dot) > 1 - 1.0E-3) + up = new Vec3(0, 0, dot > 0 ? 1 : -1); + Vec3 right = fwd.cross(up) + .normalize(); + up = right.cross(fwd) + .normalize(); + double x = vec.x * right.x + vec.y * up.x + vec.z * fwd.x; + double y = vec.x * right.y + vec.y * up.y + vec.z * fwd.y; + double z = vec.x * right.z + vec.y * up.z + vec.z * fwd.z; + return new Vec3(x, y, z); + } + + public static boolean isVecPointingTowards(Vec3 vec, Direction direction) { + return Vec3.atLowerCornerOf(direction.getNormal()) + .dot(vec.normalize()) > 0.125; // slight tolerance to activate perpendicular movement actors + } + + public static Vec3 getCenterOf(Vec3i pos) { + if (pos.equals(Vec3i.ZERO)) + return CENTER_OF_ORIGIN; + return Vec3.atLowerCornerOf(pos) + .add(.5f, .5f, .5f); + } + + public static Vec3 offsetRandomly(Vec3 vec, RandomSource r, float radius) { + return new Vec3(vec.x + (r.nextFloat() - .5f) * 2 * radius, vec.y + (r.nextFloat() - .5f) * 2 * radius, + vec.z + (r.nextFloat() - .5f) * 2 * radius); + } + + public static Vec3 axisAlingedPlaneOf(Vec3 vec) { + vec = vec.normalize(); + return new Vec3(1, 1, 1).subtract(Math.abs(vec.x), Math.abs(vec.y), Math.abs(vec.z)); + } + + public static Vec3 axisAlingedPlaneOf(Direction face) { + return axisAlingedPlaneOf(Vec3.atLowerCornerOf(face.getNormal())); + } + + public static ListTag writeNBT(Vec3 vec) { + ListTag listnbt = new ListTag(); + listnbt.add(DoubleTag.valueOf(vec.x)); + listnbt.add(DoubleTag.valueOf(vec.y)); + listnbt.add(DoubleTag.valueOf(vec.z)); + return listnbt; + } + + public static CompoundTag writeNBTCompound(Vec3 vec) { + CompoundTag compoundTag = new CompoundTag(); + compoundTag.put("V", writeNBT(vec)); + return compoundTag; + } + + public static Vec3 readNBT(ListTag list) { + if (list.isEmpty()) + return Vec3.ZERO; + return new Vec3(list.getDouble(0), list.getDouble(1), list.getDouble(2)); + } + + public static Vec3 readNBTCompound(CompoundTag nbt) { + return readNBT(nbt.getList("V", Tag.TAG_DOUBLE)); + } + + public static void write(Vec3 vec, FriendlyByteBuf buffer) { + buffer.writeDouble(vec.x); + buffer.writeDouble(vec.y); + buffer.writeDouble(vec.z); + } + + public static Vec3 read(FriendlyByteBuf buffer) { + return new Vec3(buffer.readDouble(), buffer.readDouble(), buffer.readDouble()); + } + + public static Vec3 voxelSpace(double x, double y, double z) { + return new Vec3(x, y, z).scale(1 / 16f); + } + + public static int getCoordinate(Vec3i pos, Axis axis) { + return axis.choose(pos.getX(), pos.getY(), pos.getZ()); + } + + public static float getCoordinate(Vec3 vec, Axis axis) { + return (float) axis.choose(vec.x, vec.y, vec.z); + } + + public static boolean onSameAxis(BlockPos pos1, BlockPos pos2, Axis axis) { + if (pos1.equals(pos2)) + return true; + for (Axis otherAxis : Axis.values()) + if (axis != otherAxis) + if (getCoordinate(pos1, otherAxis) != getCoordinate(pos2, otherAxis)) + return false; + return true; + } + + public static Vec3 clamp(Vec3 vec, float maxLength) { + return vec.length() > maxLength ? vec.normalize() + .scale(maxLength) : vec; + } + + public static Vec3 lerp(float p, Vec3 from, Vec3 to) { + return from.add(to.subtract(from) + .scale(p)); + } + + public static Vec3 slerp(float p, Vec3 from, Vec3 to) { + double theta = Math.acos(from.dot(to)); + return from.scale(Mth.sin(1 - p) * theta) + .add(to.scale(Mth.sin((float) (theta * p)))) + .scale(1 / Mth.sin((float) theta)); + } + + public static Vec3 clampComponentWise(Vec3 vec, float maxLength) { + return new Vec3(Mth.clamp(vec.x, -maxLength, maxLength), Mth.clamp(vec.y, -maxLength, maxLength), + Mth.clamp(vec.z, -maxLength, maxLength)); + } + + public static Vec3 project(Vec3 vec, Vec3 ontoVec) { + if (ontoVec.equals(Vec3.ZERO)) + return Vec3.ZERO; + return ontoVec.scale(vec.dot(ontoVec) / ontoVec.lengthSqr()); + } + + @Nullable + public static Vec3 intersectSphere(Vec3 origin, Vec3 lineDirection, Vec3 sphereCenter, double radius) { + if (lineDirection.equals(Vec3.ZERO)) + return null; + if (lineDirection.length() != 1) + lineDirection = lineDirection.normalize(); + + Vec3 diff = origin.subtract(sphereCenter); + double lineDotDiff = lineDirection.dot(diff); + double delta = lineDotDiff * lineDotDiff - (diff.lengthSqr() - radius * radius); + if (delta < 0) + return null; + double t = -lineDotDiff + Math.sqrt(delta); + return origin.add(lineDirection.scale(t)); + } + + // https://forums.minecraftforge.net/topic/88562-116solved-3d-to-2d-conversion/?do=findComment&comment=413573 + // slightly modified + public static Vec3 projectToPlayerView(Vec3 target, float partialTicks) { + /* + * The (centered) location on the screen of the given 3d point in the world. + * Result is (dist right of center screen, dist up from center screen, if < 0, + * then in front of view plane) + */ + Camera ari = Minecraft.getInstance().gameRenderer.getMainCamera(); + Vec3 camera_pos = ari.getPosition(); + Quaternion camera_rotation_conj = ari.rotation() + .copy(); + camera_rotation_conj.conj(); + + Vector3f result3f = new Vector3f((float) (camera_pos.x - target.x), (float) (camera_pos.y - target.y), + (float) (camera_pos.z - target.z)); + result3f.transform(camera_rotation_conj); + + // ----- compensate for view bobbing (if active) ----- + // the following code adapted from GameRenderer::applyBobbing (to invert it) + Minecraft mc = Minecraft.getInstance(); + if (mc.options.bobView().get()) { + Entity renderViewEntity = mc.getCameraEntity(); + if (renderViewEntity instanceof Player) { + Player playerentity = (Player) renderViewEntity; + float distwalked_modified = playerentity.walkDist; + + float f = distwalked_modified - playerentity.walkDistO; + float f1 = -(distwalked_modified + f * partialTicks); + float f2 = Mth.lerp(partialTicks, playerentity.oBob, playerentity.bob); + Quaternion q2 = + new Quaternion(Vector3f.XP, Math.abs(Mth.cos(f1 * (float) Math.PI - 0.2F) * f2) * 5.0F, true); + q2.conj(); + result3f.transform(q2); + + Quaternion q1 = new Quaternion(Vector3f.ZP, Mth.sin(f1 * (float) Math.PI) * f2 * 3.0F, true); + q1.conj(); + result3f.transform(q1); + + Vector3f bob_translation = new Vector3f((Mth.sin(f1 * (float) Math.PI) * f2 * 0.5F), + (-Math.abs(Mth.cos(f1 * (float) Math.PI) * f2)), 0.0f); + bob_translation.setY(-bob_translation.y()); // this is weird but hey, if it works + result3f.add(bob_translation); + } + } + + // ----- adjust for fov ----- + float fov = 70;//(float) ((GameRendererAccessor) mc.gameRenderer).create$callGetFov(ari, partialTicks, true); + + float half_height = (float) mc.getWindow() + .getGuiScaledHeight() / 2; + float scale_factor = half_height / (result3f.z() * (float) Math.tan(Math.toRadians(fov / 2))); + return new Vec3(-result3f.x() * scale_factor, result3f.y() * scale_factor, result3f.z()); + } + + public static Vec3 bezier(Vec3 p1, Vec3 p2, Vec3 q1, Vec3 q2, float t) { + Vec3 v1 = lerp(t, p1, q1); + Vec3 v2 = lerp(t, q1, q2); + Vec3 v3 = lerp(t, q2, p2); + Vec3 inner1 = lerp(t, v1, v2); + Vec3 inner2 = lerp(t, v2, v3); + Vec3 result = lerp(t, inner1, inner2); + return result; + } + + public static Vec3 bezierDerivative(Vec3 p1, Vec3 p2, Vec3 q1, Vec3 q2, float t) { + return p1.scale(-3 * t * t + 6 * t - 3) + .add(q1.scale(9 * t * t - 12 * t + 3)) + .add(q2.scale(-9 * t * t + 6 * t)) + .add(p2.scale(3 * t * t)); + } + + @Nullable + public static double[] intersectRanged(Vec3 p1, Vec3 q1, Vec3 p2, Vec3 q2, Axis plane) { + Vec3 pDiff = p2.subtract(p1); + Vec3 qDiff = q2.subtract(q1); + double[] intersect = intersect(p1, q1, pDiff.normalize(), qDiff.normalize(), plane); + if (intersect == null) + return null; + if (intersect[0] < 0 || intersect[1] < 0) + return null; + if (intersect[0] > pDiff.length() || intersect[1] > qDiff.length()) + return null; + return intersect; + } + + @Nullable + public static double[] intersect(Vec3 p1, Vec3 p2, Vec3 r, Vec3 s, Axis plane) { + if (plane == Axis.X) { + p1 = new Vec3(p1.y, 0, p1.z); + p2 = new Vec3(p2.y, 0, p2.z); + r = new Vec3(r.y, 0, r.z); + s = new Vec3(s.y, 0, s.z); + } + + if (plane == Axis.Z) { + p1 = new Vec3(p1.x, 0, p1.y); + p2 = new Vec3(p2.x, 0, p2.y); + r = new Vec3(r.x, 0, r.y); + s = new Vec3(s.x, 0, s.y); + } + + Vec3 qminusp = p2.subtract(p1); + double rcs = r.x * s.z - r.z * s.x; + if (Mth.equal(rcs, 0)) + return null; + Vec3 rdivrcs = r.scale(1 / rcs); + Vec3 sdivrcs = s.scale(1 / rcs); + double t = qminusp.x * sdivrcs.z - qminusp.z * sdivrcs.x; + double u = qminusp.x * rdivrcs.z - qminusp.z * rdivrcs.x; + return new double[] { t, u }; + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/VoxelShaper.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/VoxelShaper.java new file mode 100644 index 0000000..c18807b --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/VoxelShaper.java @@ -0,0 +1,142 @@ +package nl.requios.effortlessbuilding.create.foundation.utility; + +import net.minecraft.core.Direction; +import net.minecraft.core.Direction.Axis; +import net.minecraft.core.Direction.AxisDirection; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.phys.Vec3; +import net.minecraft.world.phys.shapes.Shapes; +import net.minecraft.world.phys.shapes.VoxelShape; +import org.apache.commons.lang3.mutable.MutableObject; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; + +public class VoxelShaper { + + private Map shapes = new HashMap<>(); + + public VoxelShape get(Direction direction) { + return shapes.get(direction); + } + + public VoxelShape get(Axis axis) { + return shapes.get(axisAsFace(axis)); + } + + public static VoxelShaper forHorizontal(VoxelShape shape, Direction facing) { + return forDirectionsWithRotation(shape, facing, Direction.Plane.HORIZONTAL, new HorizontalRotationValues()); + } + + public static VoxelShaper forHorizontalAxis(VoxelShape shape, Axis along) { + return forDirectionsWithRotation(shape, axisAsFace(along), Arrays.asList(Direction.SOUTH, Direction.EAST), + new HorizontalRotationValues()); + } + + public static VoxelShaper forDirectional(VoxelShape shape, Direction facing) { + return forDirectionsWithRotation(shape, facing, Arrays.asList(Iterate.directions), new DefaultRotationValues()); + } + + public static VoxelShaper forAxis(VoxelShape shape, Axis along) { + return forDirectionsWithRotation(shape, axisAsFace(along), + Arrays.asList(Direction.SOUTH, Direction.EAST, Direction.UP), new DefaultRotationValues()); + } + + public VoxelShaper withVerticalShapes(VoxelShape upShape) { + shapes.put(Direction.UP, upShape); + shapes.put(Direction.DOWN, rotatedCopy(upShape, new Vec3(180, 0, 0))); + return this; + } + + public VoxelShaper withShape(VoxelShape shape, Direction facing) { + shapes.put(facing, shape); + return this; + } + + public static Direction axisAsFace(Axis axis) { + return Direction.get(AxisDirection.POSITIVE, axis); + } + + protected static float horizontalAngleFromDirection(Direction direction) { + return (float) ((Math.max(direction.get2DDataValue(), 0) & 3) * 90); + } + + protected static VoxelShaper forDirectionsWithRotation(VoxelShape shape, Direction facing, + Iterable directions, Function rotationValues) { + VoxelShaper voxelShaper = new VoxelShaper(); + for (Direction dir : directions) { + voxelShaper.shapes.put(dir, rotate(shape, facing, dir, rotationValues)); + } + return voxelShaper; + } + + protected static VoxelShape rotate(VoxelShape shape, Direction from, Direction to, + Function usingValues) { + if (from == to) + return shape; + + return rotatedCopy(shape, usingValues.apply(from) + .reverse() + .add(usingValues.apply(to))); + } + + protected static VoxelShape rotatedCopy(VoxelShape shape, Vec3 rotation) { + if (rotation.equals(Vec3.ZERO)) + return shape; + + MutableObject result = new MutableObject<>(Shapes.empty()); + Vec3 center = new Vec3(8, 8, 8); + + shape.forAllBoxes((x1, y1, z1, x2, y2, z2) -> { + Vec3 v1 = new Vec3(x1, y1, z1).scale(16) + .subtract(center); + Vec3 v2 = new Vec3(x2, y2, z2).scale(16) + .subtract(center); + + v1 = VecHelper.rotate(v1, (float) rotation.x, Axis.X); + v1 = VecHelper.rotate(v1, (float) rotation.y, Axis.Y); + v1 = VecHelper.rotate(v1, (float) rotation.z, Axis.Z) + .add(center); + + v2 = VecHelper.rotate(v2, (float) rotation.x, Axis.X); + v2 = VecHelper.rotate(v2, (float) rotation.y, Axis.Y); + v2 = VecHelper.rotate(v2, (float) rotation.z, Axis.Z) + .add(center); + + VoxelShape rotated = blockBox(v1, v2); + result.setValue(Shapes.or(result.getValue(), rotated)); + }); + + return result.getValue(); + } + + protected static VoxelShape blockBox(Vec3 v1, Vec3 v2) { + return Block.box( + Math.min(v1.x, v2.x), + Math.min(v1.y, v2.y), + Math.min(v1.z, v2.z), + Math.max(v1.x, v2.x), + Math.max(v1.y, v2.y), + Math.max(v1.z, v2.z) + ); + } + + protected static class DefaultRotationValues implements Function { + // assume facing up as the default rotation + @Override + public Vec3 apply(Direction direction) { + return new Vec3(direction == Direction.UP ? 0 : (Direction.Plane.VERTICAL.test(direction) ? 180 : 90), + -horizontalAngleFromDirection(direction), 0); + } + } + + protected static class HorizontalRotationValues implements Function { + @Override + public Vec3 apply(Direction direction) { + return new Vec3(0, -horizontalAngleFromDirection(direction), 0); + } + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/WorldAttached.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/WorldAttached.java new file mode 100644 index 0000000..9c9c081 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/WorldAttached.java @@ -0,0 +1,101 @@ +package nl.requios.effortlessbuilding.create.foundation.utility; + +import net.minecraft.world.level.LevelAccessor; + +import javax.annotation.Nonnull; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +public class WorldAttached { + + // weak references to prevent leaking hashmaps when a WorldAttached is GC'd during runtime + static List>> allMaps = new ArrayList<>(); + private final Map attached; + private final Function factory; + + public WorldAttached(Function factory) { + this.factory = factory; + // Weak key hashmaps prevent worlds not existing anywhere else from leaking memory. + // This is only a fallback in the event that unload events fail to fire for any reason. + attached = new WeakHashMap<>(); + allMaps.add(new WeakReference<>(attached)); + } + + public static void invalidateWorld(LevelAccessor world) { + var i = allMaps.iterator(); + while (i.hasNext()) { + Map map = i.next() + .get(); + if (map == null) { + // If the map has been GC'd, remove the weak reference + i.remove(); + } else { + // Prevent leaks + map.remove(world); + } + } + } + + @Nonnull + public T get(LevelAccessor world) { + T t = attached.get(world); + if (t != null) return t; + T entry = factory.apply(world); + put(world, entry); + return entry; + } + + public void put(LevelAccessor world, T entry) { + attached.put(world, entry); + } + + /** + * Replaces the entry with a new one from the factory and returns the new entry. + */ + @Nonnull + public T replace(LevelAccessor world) { + attached.remove(world); + + return get(world); + } + + /** + * Replaces the entry with a new one from the factory and returns the new entry. + */ + @Nonnull + public T replace(LevelAccessor world, Consumer finalizer) { + T remove = attached.remove(world); + + if (remove != null) + finalizer.accept(remove); + + return get(world); + } + + /** + * Deletes all entries after calling a function on them. + * + * @param finalizer Do something with all of the world-value pairs + */ + public void empty(BiConsumer finalizer) { + attached.forEach(finalizer); + attached.clear(); + } + + /** + * Deletes all entries after calling a function on them. + * + * @param finalizer Do something with all of the values + */ + public void empty(Consumer finalizer) { + attached.values() + .forEach(finalizer); + attached.clear(); + } +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/WorldHelper.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/WorldHelper.java new file mode 100644 index 0000000..fa26d0e --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/WorldHelper.java @@ -0,0 +1,13 @@ +package nl.requios.effortlessbuilding.create.foundation.utility; + +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.LevelAccessor; + +public class WorldHelper { + public static ResourceLocation getDimensionID(LevelAccessor world) { + return world.registryAccess() + .registryOrThrow(Registry.DIMENSION_TYPE_REGISTRY) + .getKey(world.dimensionType()); + } +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/animation/Force.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/animation/Force.java new file mode 100644 index 0000000..493a4cf --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/animation/Force.java @@ -0,0 +1,102 @@ +package nl.requios.effortlessbuilding.create.foundation.utility.animation; + +public interface Force { + + float get(float mass, float value, float speed); + + boolean finished(); + + class Drag implements Force { + final float dragFactor; + + public Drag(float dragFactor) { + this.dragFactor = dragFactor; + } + + @Override + public float get(float mass, float value, float speed) { + return -speed * dragFactor; + } + + @Override + public boolean finished() { + return false; + } + } + + class Zeroing implements Force { + final float g; + + public Zeroing(float g) { + this.g = g / 20; + } + + @Override + public float get(float mass, float value, float speed) { + return -Math.signum(value) * g * mass; + } + + @Override + public boolean finished() { + return false; + } + } + + class Impulse implements Force { + + float force; + + public Impulse(float force) { + this.force = force; + } + + @Override + public float get(float mass, float value, float speed) { + return force; + } + + @Override + public boolean finished() { + return true; + } + } + + class OverTime implements Force { + int timeRemaining; + float f; + + public OverTime(int time, float totalAcceleration) { + this.timeRemaining = time; + this.f = totalAcceleration / (float) time; + } + + @Override + public float get(float mass, float value, float speed) { + timeRemaining--; + return f; + } + + @Override + public boolean finished() { + return timeRemaining <= 0; + } + } + + class Static implements Force { + float force; + + public Static(float force) { + this.force = force; + } + + @Override + public float get(float mass, float value, float speed) { + return force; + } + + @Override + public boolean finished() { + return false; + } + } +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/animation/LerpedFloat.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/animation/LerpedFloat.java new file mode 100644 index 0000000..1043202 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/animation/LerpedFloat.java @@ -0,0 +1,149 @@ +package nl.requios.effortlessbuilding.create.foundation.utility.animation; + +import nl.requios.effortlessbuilding.create.foundation.utility.AngleHelper; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.util.Mth; + +public class LerpedFloat { + + protected Interpolator interpolator; + protected float previousValue; + protected float value; + + protected Chaser chaseFunction; + protected float chaseTarget; + protected float chaseSpeed; + protected boolean angularChase; + + protected boolean forcedSync; + + public LerpedFloat(Interpolator interpolator) { + this.interpolator = interpolator; + startWithValue(0); + forcedSync = true; + } + + public static LerpedFloat linear() { + return new LerpedFloat((p, c, t) -> (float) Mth.lerp(p, c, t)); + } + + public static LerpedFloat angular() { + LerpedFloat lerpedFloat = new LerpedFloat(AngleHelper::angleLerp); + lerpedFloat.angularChase = true; + return lerpedFloat; + } + + public LerpedFloat startWithValue(double value) { + float f = (float) value; + this.previousValue = f; + this.chaseTarget = f; + this.value = f; + return this; + } + + public LerpedFloat chase(double value, double speed, Chaser chaseFunction) { + updateChaseTarget((float) value); + this.chaseSpeed = (float) speed; + this.chaseFunction = chaseFunction; + return this; + } + + public LerpedFloat disableSmartAngleChasing() { + angularChase = false; + return this; + } + + public void updateChaseTarget(float target) { + if (angularChase) + target = value + AngleHelper.getShortestAngleDiff(value, target); + this.chaseTarget = target; + } + + public boolean updateChaseSpeed(double speed) { + float prevSpeed = this.chaseSpeed; + this.chaseSpeed = (float) speed; + return !Mth.equal(prevSpeed, speed); + } + + public void tickChaser() { + previousValue = value; + if (chaseFunction == null) + return; + if (Mth.equal((double) value, chaseTarget)) { + value = chaseTarget; + return; + } + value = chaseFunction.chase(value, chaseSpeed, chaseTarget); + } + + public void setValueNoUpdate(double value) { + this.value = (float) value; + } + + public void setValue(double value) { + this.previousValue = this.value; + this.value = (float) value; + } + + public float getValue() { + return getValue(1); + } + + public float getValue(float partialTicks) { + return interpolator.interpolate(partialTicks, previousValue, value); + } + + public boolean settled() { + return Mth.equal((double) previousValue, value); + } + + public float getChaseTarget() { + return chaseTarget; + } + + public void forceNextSync() { + forcedSync = true; + } + + public CompoundTag writeNBT() { + CompoundTag compoundNBT = new CompoundTag(); + compoundNBT.putFloat("Speed", chaseSpeed); + compoundNBT.putFloat("Target", chaseTarget); + compoundNBT.putFloat("Value", value); + if (forcedSync) + compoundNBT.putBoolean("Force", true); + forcedSync = false; + return compoundNBT; + } + + public void readNBT(CompoundTag compoundNBT, boolean clientPacket) { + if (!clientPacket || compoundNBT.contains("Force")) + startWithValue(compoundNBT.getFloat("Value")); + readChaser(compoundNBT); + } + + protected void readChaser(CompoundTag compoundNBT) { + chaseSpeed = compoundNBT.getFloat("Speed"); + chaseTarget = compoundNBT.getFloat("Target"); + } + + @FunctionalInterface + public interface Interpolator { + float interpolate(double progress, double current, double target); + } + + @FunctionalInterface + public interface Chaser { + + Chaser IDLE = (c, s, t) -> (float) c; + Chaser EXP = exp(Double.MAX_VALUE); + Chaser LINEAR = (c, s, t) -> (float) (c + Mth.clamp(t - c, -s, s)); + + static Chaser exp(double maxEffectiveSpeed) { + return (c, s, t) -> (float) (c + Mth.clamp((t - c) * s, -maxEffectiveSpeed, maxEffectiveSpeed)); + } + + float chase(double current, double speed, double target); + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/animation/PhysicalFloat.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/animation/PhysicalFloat.java new file mode 100644 index 0000000..c4eb9d4 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/animation/PhysicalFloat.java @@ -0,0 +1,90 @@ +package nl.requios.effortlessbuilding.create.foundation.utility.animation; + +import net.minecraft.util.Mth; + +import java.util.ArrayList; + +public class PhysicalFloat { + + float previousValue; + float value; + + float previousSpeed; + float speed; + float limit = Float.NaN; + + float mass; + + private final ArrayList forces = new ArrayList<>(); + + public static PhysicalFloat create() { + return new PhysicalFloat(1); + } + + public static PhysicalFloat create(float mass) { + return new PhysicalFloat(mass); + } + + public PhysicalFloat(float mass) { + this.mass = mass; + } + + public PhysicalFloat startAt(double value) { + previousValue = this.value = (float) value; + return this; + } + + public PhysicalFloat withDrag(double drag) { + return addForce(new Force.Drag((float) drag)); + } + + public PhysicalFloat zeroing(double g) { + return addForce(new Force.Zeroing((float) g)); + } + + public PhysicalFloat withLimit(float limit) { + this.limit = limit; + return this; + } + + public void tick() { + previousSpeed = speed; + previousValue = value; + + float totalImpulse = 0; + for (Force force : forces) + totalImpulse += force.get(mass, value, speed) / mass; + + speed += totalImpulse; + + forces.removeIf(Force::finished); + + if (Float.isFinite(limit)) { + speed = Mth.clamp(speed, -limit, limit); + } + + value += speed; + } + + public PhysicalFloat addForce(Force f) { + forces.add(f); + return this; + } + + public PhysicalFloat bump(double force) { + return addForce(new Force.Impulse((float) force)); + } + + public PhysicalFloat bump(int time, double force) { + return addForce(new Force.OverTime(time, (float) force)); + } + + public float getValue() { + return getValue(1); + } + + public float getValue(float partialTicks) { + return Mth.lerp(partialTicks, previousValue, value); + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/ghost/GhostBlockParams.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/ghost/GhostBlockParams.java new file mode 100644 index 0000000..7202e91 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/ghost/GhostBlockParams.java @@ -0,0 +1,83 @@ +package nl.requios.effortlessbuilding.create.foundation.utility.ghost; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import nl.requios.effortlessbuilding.create.foundation.utility.Color; +import nl.requios.effortlessbuilding.create.foundation.utility.outliner.Outline; + +import java.util.function.Supplier; + +public class GhostBlockParams { + + protected final BlockState state; + protected BlockPos pos; + protected Supplier alphaSupplier; + protected Supplier scaleSupplier; + public Supplier rgbSupplier; + + private GhostBlockParams(BlockState state) { + this.state = state; + this.pos = BlockPos.ZERO; + this.alphaSupplier = () -> 1f; + this.scaleSupplier = () -> 0.85f; + this.rgbSupplier = () -> Color.WHITE; + } + + public static GhostBlockParams of(BlockState state) { + return new GhostBlockParams(state); + } + + public static GhostBlockParams of(Block block) { + return of(block.defaultBlockState()); + } + + public GhostBlockParams at(BlockPos pos) { + this.pos = pos; + return this; + } + + public GhostBlockParams at(int x, int y, int z) { + return this.at(new BlockPos(x, y, z)); + } + + public GhostBlockParams alpha(Supplier alphaSupplier) { + this.alphaSupplier = alphaSupplier; + return this; + } + + public GhostBlockParams alpha(float alpha) { + return this.alpha(() -> alpha); + } + + public GhostBlockParams breathingAlpha() { + return this.alpha(() -> (float) GhostBlocks.getBreathingAlpha()); + } + + public GhostBlockParams scale(Supplier scaleSupplier) { + this.scaleSupplier = scaleSupplier; + return this; + } + + public GhostBlockParams scale(float scale) { + return this.scale(() -> scale); + } + + public GhostBlockParams colored(Supplier colorSupplier) { + this.rgbSupplier = colorSupplier; + return this; + } + + public GhostBlockParams colored(Color color) { + return this.colored(() -> color); + } + + public GhostBlockParams colored(int color) { + var color2 = new Color(color, false); + return this.colored(() -> color2); + } + + public GhostBlockParams breathingCyan() { + return this.colored(() -> new Color((float) GhostBlocks.getBreathingColor(), 1f, 1f, 1f)); + } +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/ghost/GhostBlockRenderer.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/ghost/GhostBlockRenderer.java new file mode 100644 index 0000000..fb29bc1 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/ghost/GhostBlockRenderer.java @@ -0,0 +1,143 @@ +package nl.requios.effortlessbuilding.create.foundation.utility.ghost; + +import com.jozufozu.flywheel.core.model.ModelUtil; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import nl.requios.effortlessbuilding.create.foundation.render.SuperRenderTypeBuffer; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.LevelRenderer; +import net.minecraft.client.renderer.LightTexture; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.block.BlockRenderDispatcher; +import net.minecraft.client.renderer.block.ModelBlockRenderer; +import net.minecraft.client.renderer.block.model.BakedQuad; +import net.minecraft.client.renderer.texture.OverlayTexture; +import net.minecraft.client.resources.model.BakedModel; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.util.Mth; +import net.minecraft.util.RandomSource; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraftforge.client.model.data.ModelData; +import nl.requios.effortlessbuilding.create.foundation.utility.Color; + +import javax.annotation.Nullable; +import java.util.List; + +public abstract class GhostBlockRenderer { + + private static final GhostBlockRenderer STANDARD = new DefaultGhostBlockRenderer(); + + public static GhostBlockRenderer standard() { + return STANDARD; + } + + private static final GhostBlockRenderer TRANSPARENT = new TransparentGhostBlockRenderer(); + + public static GhostBlockRenderer transparent() { + return TRANSPARENT; + } + + public abstract void render(PoseStack ms, SuperRenderTypeBuffer buffer, GhostBlockParams params); + + private static class DefaultGhostBlockRenderer extends GhostBlockRenderer { + + @Override + public void render(PoseStack ms, SuperRenderTypeBuffer buffer, GhostBlockParams params) { + BlockRenderDispatcher dispatcher = Minecraft.getInstance() + .getBlockRenderer(); + ModelBlockRenderer renderer = dispatcher.getModelRenderer(); + + BlockState state = params.state; + BlockPos pos = params.pos; + + BakedModel model = dispatcher.getBlockModel(state); + + ms.pushPose(); + ms.translate(pos.getX(), pos.getY(), pos.getZ()); + + for (RenderType layer : model.getRenderTypes(state, RandomSource.create(42L), ModelUtil.VIRTUAL_DATA)) { + VertexConsumer vb = buffer.getEarlyBuffer(layer); + renderer.renderModel(ms.last(), vb, state, model, 1f, 1f, 1f, LightTexture.FULL_BRIGHT, OverlayTexture.NO_OVERLAY, + ModelUtil.VIRTUAL_DATA, layer); + } + + ms.popPose(); + } + + } + + private static class TransparentGhostBlockRenderer extends GhostBlockRenderer { + + @Override + public void render(PoseStack ms, SuperRenderTypeBuffer buffer, GhostBlockParams params) { + Minecraft mc = Minecraft.getInstance(); + BlockRenderDispatcher dispatcher = mc.getBlockRenderer(); + + BlockState state = params.state; + BlockPos pos = params.pos; + float alpha = params.alphaSupplier.get()/* * .75f* PlacementHelpers.getCurrentAlpha()*/; + float scale = params.scaleSupplier.get(); + Color color = params.rgbSupplier.get(); + + BakedModel model = dispatcher.getBlockModel(state); + RenderType layer = RenderType.translucent(); + VertexConsumer vb = buffer.getEarlyBuffer(layer); + + ms.pushPose(); + ms.translate(pos.getX(), pos.getY(), pos.getZ()); + + ms.translate(.5, .5, .5); + ms.scale(scale, scale, scale); + ms.translate(-.5, -.5, -.5); + + renderModel(ms.last(), vb, state, model, color.getRedAsFloat(), color.getGreenAsFloat(), color.getBlueAsFloat(), alpha, + LevelRenderer.getLightColor(mc.level, pos), OverlayTexture.NO_OVERLAY, + ModelUtil.VIRTUAL_DATA, layer); + + ms.popPose(); + } + + // ModelBlockRenderer + public void renderModel(PoseStack.Pose pose, VertexConsumer consumer, + @Nullable BlockState state, BakedModel model, float red, float green, float blue, + float alpha, int packedLight, int packedOverlay, ModelData modelData, RenderType renderType) { + RandomSource random = RandomSource.create(); + + for (Direction direction : Direction.values()) { + random.setSeed(42L); + renderQuadList(pose, consumer, red, green, blue, alpha, + model.getQuads(state, direction, random, modelData, renderType), packedLight, packedOverlay); + } + + random.setSeed(42L); + renderQuadList(pose, consumer, red, green, blue, alpha, + model.getQuads(state, null, random, modelData, renderType), packedLight, packedOverlay); + } + + // ModelBlockRenderer + private static void renderQuadList(PoseStack.Pose pose, VertexConsumer consumer, + float red, float green, float blue, float alpha, List quads, + int packedLight, int packedOverlay) { + for (BakedQuad quad : quads) { + float f; + float f1; + float f2; +// if (quad.isTinted()) { + f = Mth.clamp(red, 0.0F, 1.0F); + f1 = Mth.clamp(green, 0.0F, 1.0F); + f2 = Mth.clamp(blue, 0.0F, 1.0F); +// } else { +// f = 1.0F; +// f1 = 1.0F; +// f2 = 1.0F; +// } + + consumer.putBulkData(pose, quad, f, f1, f2, alpha, packedLight, packedOverlay, true); + } + + } + + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/ghost/GhostBlocks.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/ghost/GhostBlocks.java new file mode 100644 index 0000000..96c2893 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/ghost/GhostBlocks.java @@ -0,0 +1,90 @@ +package nl.requios.effortlessbuilding.create.foundation.utility.ghost; + +import com.mojang.blaze3d.vertex.PoseStack; +import nl.requios.effortlessbuilding.create.foundation.render.SuperRenderTypeBuffer; +import net.minecraft.util.Mth; +import net.minecraft.world.level.block.state.BlockState; + +import java.util.HashMap; +import java.util.Map; + +public class GhostBlocks { + + public static double getBreathingAlpha() { + double period = 2500; + double timer = System.currentTimeMillis() % period; + double offset = Mth.cos((float) ((2d/period) * Math.PI * timer)); + return 0.55d - 0.2d * offset; + } + + public static double getBreathingColor() { + double period = 2500; + double timer = System.currentTimeMillis() % period; + double offset = Mth.cos((float) ((2d/period) * Math.PI * timer)); + return 0.35d + 0.35d * offset; + } + + final Map ghosts; + + public GhostBlockParams showGhostState(Object slot, BlockState state) { + return showGhostState(slot, state, 1); + } + + public GhostBlockParams showGhostState(Object slot, BlockState state, int ttl) { + Entry e = refresh(slot, GhostBlockRenderer.transparent(), GhostBlockParams.of(state), ttl); + return e.params; + } + + public GhostBlockParams showGhost(Object slot, GhostBlockRenderer ghost, GhostBlockParams params, int ttl) { + Entry e = refresh(slot, ghost, params, ttl); + return e.params; + } + + private Entry refresh(Object slot, GhostBlockRenderer ghost, GhostBlockParams params, int ttl) { + if (!ghosts.containsKey(slot)) + ghosts.put(slot, new Entry(ghost, params, ttl)); + + Entry e = ghosts.get(slot); + e.ticksToLive = ttl; + e.params = params; + e.ghost = ghost; + return e; + } + + public GhostBlocks() { + ghosts = new HashMap<>(); + } + + public void tickGhosts() { + ghosts.forEach((slot, entry) -> entry.ticksToLive--); + ghosts.entrySet().removeIf(e -> !e.getValue().isAlive()); + } + + public void renderAll(PoseStack ms, SuperRenderTypeBuffer buffer) { + ghosts.forEach((slot, entry) -> { + GhostBlockRenderer ghost = entry.ghost; + ghost.render(ms, buffer, entry.params); + }); + } + + static class Entry { + + private GhostBlockRenderer ghost; + private GhostBlockParams params; + private int ticksToLive; + + public Entry(GhostBlockRenderer ghost, GhostBlockParams params) { + this(ghost, params, 1); + } + + public Entry(GhostBlockRenderer ghost, GhostBlockParams params, int ttl) { + this.ghost = ghost; + this.params = params; + this.ticksToLive = ttl; + } + + public boolean isAlive() { + return ticksToLive >= 0; + } + } +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/outliner/AABBOutline.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/outliner/AABBOutline.java new file mode 100644 index 0000000..5c85797 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/outliner/AABBOutline.java @@ -0,0 +1,100 @@ +package nl.requios.effortlessbuilding.create.foundation.utility.outliner; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import nl.requios.effortlessbuilding.create.foundation.render.RenderTypes; +import nl.requios.effortlessbuilding.create.foundation.render.SuperRenderTypeBuffer; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.core.Direction; +import net.minecraft.core.Direction.Axis; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; + +public class AABBOutline extends Outline { + + protected AABB bb; + + public AABBOutline(AABB bb) { + this.setBounds(bb); + } + + @Override + public void render(PoseStack ms, SuperRenderTypeBuffer buffer, float pt) { + renderBB(ms, buffer, bb); + } + + public void renderBB(PoseStack ms, SuperRenderTypeBuffer buffer, AABB bb) { + Vec3 projectedView = Minecraft.getInstance().gameRenderer.getMainCamera() + .getPosition(); + boolean noCull = bb.contains(projectedView); + bb = bb.inflate(noCull ? -1 / 128d : 1 / 128d); + noCull |= params.disableCull; + + Vec3 xyz = new Vec3(bb.minX, bb.minY, bb.minZ); + Vec3 Xyz = new Vec3(bb.maxX, bb.minY, bb.minZ); + Vec3 xYz = new Vec3(bb.minX, bb.maxY, bb.minZ); + Vec3 XYz = new Vec3(bb.maxX, bb.maxY, bb.minZ); + Vec3 xyZ = new Vec3(bb.minX, bb.minY, bb.maxZ); + Vec3 XyZ = new Vec3(bb.maxX, bb.minY, bb.maxZ); + Vec3 xYZ = new Vec3(bb.minX, bb.maxY, bb.maxZ); + Vec3 XYZ = new Vec3(bb.maxX, bb.maxY, bb.maxZ); + + Vec3 start = xyz; + renderAACuboidLine(ms, buffer, start, Xyz); + renderAACuboidLine(ms, buffer, start, xYz); + renderAACuboidLine(ms, buffer, start, xyZ); + + start = XyZ; + renderAACuboidLine(ms, buffer, start, xyZ); + renderAACuboidLine(ms, buffer, start, XYZ); + renderAACuboidLine(ms, buffer, start, Xyz); + + start = XYz; + renderAACuboidLine(ms, buffer, start, xYz); + renderAACuboidLine(ms, buffer, start, Xyz); + renderAACuboidLine(ms, buffer, start, XYZ); + + start = xYZ; + renderAACuboidLine(ms, buffer, start, XYZ); + renderAACuboidLine(ms, buffer, start, xyZ); + renderAACuboidLine(ms, buffer, start, xYz); + + renderFace(ms, buffer, Direction.NORTH, xYz, XYz, Xyz, xyz, noCull); + renderFace(ms, buffer, Direction.SOUTH, XYZ, xYZ, xyZ, XyZ, noCull); + renderFace(ms, buffer, Direction.EAST, XYz, XYZ, XyZ, Xyz, noCull); + renderFace(ms, buffer, Direction.WEST, xYZ, xYz, xyz, xyZ, noCull); + renderFace(ms, buffer, Direction.UP, xYZ, XYZ, XYz, xYz, noCull); + renderFace(ms, buffer, Direction.DOWN, xyz, Xyz, XyZ, xyZ, noCull); + + } + + protected void renderFace(PoseStack ms, SuperRenderTypeBuffer buffer, Direction direction, Vec3 p1, Vec3 p2, + Vec3 p3, Vec3 p4, boolean noCull) { + if (!params.faceTexture.isPresent()) + return; + + ResourceLocation faceTexture = params.faceTexture.get() + .getLocation(); + float alphaBefore = params.alpha; + params.alpha = + (direction == params.getHighlightedFace() && params.hightlightedFaceTexture.isPresent()) ? 1 : 0.5f; + + RenderType translucentType = RenderTypes.getOutlineTranslucent(faceTexture, !noCull); + VertexConsumer builder = buffer.getLateBuffer(translucentType); + + Axis axis = direction.getAxis(); + Vec3 uDiff = p2.subtract(p1); + Vec3 vDiff = p4.subtract(p1); + float maxU = (float) Math.abs(axis == Axis.X ? uDiff.z : uDiff.x); + float maxV = (float) Math.abs(axis == Axis.Y ? vDiff.z : vDiff.y); + putQuadUV(ms, builder, p1, p2, p3, p4, 0, 0, maxU, maxV, Direction.UP); + params.alpha = alphaBefore; + } + + public void setBounds(AABB bb) { + this.bb = bb; + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/outliner/BlockClusterOutline.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/outliner/BlockClusterOutline.java new file mode 100644 index 0000000..3ef0a55 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/outliner/BlockClusterOutline.java @@ -0,0 +1,178 @@ +package nl.requios.effortlessbuilding.create.foundation.utility.outliner; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import nl.requios.effortlessbuilding.create.AllSpecialTextures; +import nl.requios.effortlessbuilding.create.foundation.render.RenderTypes; +import nl.requios.effortlessbuilding.create.foundation.render.SuperRenderTypeBuffer; +import nl.requios.effortlessbuilding.create.foundation.utility.Iterate; +import nl.requios.effortlessbuilding.create.foundation.utility.VecHelper; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.Direction.Axis; +import net.minecraft.core.Direction.AxisDirection; +import net.minecraft.world.phys.Vec3; + +import java.util.*; + +public class BlockClusterOutline extends Outline { + + private Cluster cluster; + + public BlockClusterOutline(Iterable selection) { + cluster = new Cluster(); + selection.forEach(cluster::include); + } + + @Override + public void render(PoseStack ms, SuperRenderTypeBuffer buffer, float pt) { + cluster.visibleEdges.forEach(edge -> { + Vec3 start = Vec3.atLowerCornerOf(edge.pos); + Direction direction = Direction.get(AxisDirection.POSITIVE, edge.axis); + renderAACuboidLine(ms, buffer, start, Vec3.atLowerCornerOf(edge.pos.relative(direction))); + }); + + Optional faceTexture = params.faceTexture; + if (!faceTexture.isPresent()) + return; + + RenderType translucentType = RenderTypes.getOutlineTranslucent(faceTexture.get() + .getLocation(), true); + VertexConsumer builder = buffer.getLateBuffer(translucentType); + + cluster.visibleFaces.forEach((face, axisDirection) -> { + Direction direction = Direction.get(axisDirection, face.axis); + BlockPos pos = face.pos; + if (axisDirection == AxisDirection.POSITIVE) + pos = pos.relative(direction.getOpposite()); + renderBlockFace(ms, builder, pos, direction); + }); + } + + static Vec3 xyz = new Vec3(-.5, -.5, -.5); + static Vec3 Xyz = new Vec3(.5, -.5, -.5); + static Vec3 xYz = new Vec3(-.5, .5, -.5); + static Vec3 XYz = new Vec3(.5, .5, -.5); + static Vec3 xyZ = new Vec3(-.5, -.5, .5); + static Vec3 XyZ = new Vec3(.5, -.5, .5); + static Vec3 xYZ = new Vec3(-.5, .5, .5); + static Vec3 XYZ = new Vec3(.5, .5, .5); + + protected void renderBlockFace(PoseStack ms, VertexConsumer builder, BlockPos pos, Direction face) { + Vec3 center = VecHelper.getCenterOf(pos); + Vec3 offset = Vec3.atLowerCornerOf(face.getNormal()); + offset = offset.scale(1 / 128d); + center = center.add(offset); + + ms.pushPose(); + ms.translate(center.x, center.y, center.z); + + switch (face) { + case DOWN: + putQuad(ms, builder, xyz, Xyz, XyZ, xyZ, face); + break; + case EAST: + putQuad(ms, builder, XYz, XYZ, XyZ, Xyz, face); + break; + case NORTH: + putQuad(ms, builder, xYz, XYz, Xyz, xyz, face); + break; + case SOUTH: + putQuad(ms, builder, XYZ, xYZ, xyZ, XyZ, face); + break; + case UP: + putQuad(ms, builder, xYZ, XYZ, XYz, xYz, face); + break; + case WEST: + putQuad(ms, builder, xYZ, xYz, xyz, xyZ, face); + default: + break; + } + + ms.popPose(); + } + + private static class Cluster { + + private Map visibleFaces; + private Set visibleEdges; + + public Cluster() { + visibleEdges = new HashSet<>(); + visibleFaces = new HashMap<>(); + } + + public void include(BlockPos pos) { + + // 6 FACES + for (Axis axis : Iterate.axes) { + Direction direction = Direction.get(AxisDirection.POSITIVE, axis); + for (int offset : Iterate.zeroAndOne) { + MergeEntry entry = new MergeEntry(axis, pos.relative(direction, offset)); + if (visibleFaces.remove(entry) == null) + visibleFaces.put(entry, offset == 0 ? AxisDirection.NEGATIVE : AxisDirection.POSITIVE); + } + } + + // 12 EDGES + for (Axis axis : Iterate.axes) { + for (Axis axis2 : Iterate.axes) { + if (axis == axis2) + continue; + for (Axis axis3 : Iterate.axes) { + if (axis == axis3) + continue; + if (axis2 == axis3) + continue; + + Direction direction = Direction.get(AxisDirection.POSITIVE, axis2); + Direction direction2 = Direction.get(AxisDirection.POSITIVE, axis3); + + for (int offset : Iterate.zeroAndOne) { + BlockPos entryPos = pos.relative(direction, offset); + for (int offset2 : Iterate.zeroAndOne) { + entryPos = entryPos.relative(direction2, offset2); + MergeEntry entry = new MergeEntry(axis, entryPos); + if (!visibleEdges.remove(entry)) + visibleEdges.add(entry); + } + } + } + + break; + } + } + + } + + } + + private static class MergeEntry { + + private Axis axis; + private BlockPos pos; + + public MergeEntry(Axis axis, BlockPos pos) { + this.axis = axis; + this.pos = pos; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof MergeEntry)) + return false; + + MergeEntry other = (MergeEntry) o; + return this.axis == other.axis && this.pos.equals(other.pos); + } + + @Override + public int hashCode() { + return this.pos.hashCode() * 31 + axis.ordinal(); + } + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/outliner/ChasingAABBOutline.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/outliner/ChasingAABBOutline.java new file mode 100644 index 0000000..d5639ea --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/outliner/ChasingAABBOutline.java @@ -0,0 +1,41 @@ +package nl.requios.effortlessbuilding.create.foundation.utility.outliner; + +import com.mojang.blaze3d.vertex.PoseStack; +import nl.requios.effortlessbuilding.create.foundation.render.SuperRenderTypeBuffer; +import net.minecraft.util.Mth; +import net.minecraft.world.phys.AABB; + +public class ChasingAABBOutline extends AABBOutline { + + AABB targetBB; + AABB prevBB; + + public ChasingAABBOutline(AABB bb) { + super(bb); + prevBB = bb.inflate(0); + targetBB = bb.inflate(0); + } + + public void target(AABB target) { + targetBB = target; + } + + @Override + public void tick() { + prevBB = bb; + setBounds(interpolateBBs(bb, targetBB, .5f)); + } + + @Override + public void render(PoseStack ms, SuperRenderTypeBuffer buffer, float pt) { + renderBB(ms, buffer, interpolateBBs(prevBB, bb, pt)); + } + + private static AABB interpolateBBs(AABB current, AABB target, float pt) { + return new AABB(Mth.lerp(pt, current.minX, target.minX), + Mth.lerp(pt, current.minY, target.minY), Mth.lerp(pt, current.minZ, target.minZ), + Mth.lerp(pt, current.maxX, target.maxX), Mth.lerp(pt, current.maxY, target.maxY), + Mth.lerp(pt, current.maxZ, target.maxZ)); + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/outliner/LineOutline.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/outliner/LineOutline.java new file mode 100644 index 0000000..2829a8d --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/outliner/LineOutline.java @@ -0,0 +1,65 @@ +package nl.requios.effortlessbuilding.create.foundation.utility.outliner; + +import com.mojang.blaze3d.vertex.PoseStack; +import nl.requios.effortlessbuilding.create.foundation.render.SuperRenderTypeBuffer; +import net.minecraft.util.Mth; +import net.minecraft.world.phys.Vec3; + +public class LineOutline extends Outline { + + protected Vec3 start = Vec3.ZERO; + protected Vec3 end = Vec3.ZERO; + + public LineOutline set(Vec3 start, Vec3 end) { + this.start = start; + this.end = end; + return this; + } + + @Override + public void render(PoseStack ms, SuperRenderTypeBuffer buffer, float pt) { + renderCuboidLine(ms, buffer, start, end); + } + + public static class EndChasingLineOutline extends LineOutline { + + float prevProgress = 0; + float progress = 0; + private boolean lockStart; + + public EndChasingLineOutline(boolean lockStart) { + this.lockStart = lockStart; + } + + @Override + public void tick() {} + + public EndChasingLineOutline setProgress(float progress) { + prevProgress = this.progress; + this.progress = progress; + return this; + } + + @Override + public LineOutline set(Vec3 start, Vec3 end) { + if (!end.equals(this.end)) + super.set(start, end); + return this; + } + + @Override + public void render(PoseStack ms, SuperRenderTypeBuffer buffer, float pt) { + float distanceToTarget = Mth.lerp(pt, prevProgress, progress); + if (!lockStart) + distanceToTarget = 1 - distanceToTarget; + Vec3 start = lockStart ? this.end : this.start; + Vec3 end = lockStart ? this.start : this.end; + + start = end.add(this.start.subtract(end) + .scale(distanceToTarget)); + renderCuboidLine(ms, buffer, start, end); + } + + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/outliner/Outline.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/outliner/Outline.java new file mode 100644 index 0000000..a5d3e8e --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/outliner/Outline.java @@ -0,0 +1,246 @@ +package nl.requios.effortlessbuilding.create.foundation.utility.outliner; + +import com.jozufozu.flywheel.util.transform.TransformStack; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import com.mojang.math.Matrix3f; +import nl.requios.effortlessbuilding.create.AllSpecialTextures; +import nl.requios.effortlessbuilding.create.foundation.render.RenderTypes; +import nl.requios.effortlessbuilding.create.foundation.render.SuperRenderTypeBuffer; +import nl.requios.effortlessbuilding.create.foundation.utility.AngleHelper; +import nl.requios.effortlessbuilding.create.foundation.utility.Color; +import nl.requios.effortlessbuilding.create.foundation.utility.VecHelper; +import net.minecraft.client.renderer.LightTexture; +import net.minecraft.client.renderer.texture.OverlayTexture; +import net.minecraft.core.Direction; +import net.minecraft.core.Direction.Axis; +import net.minecraft.util.Mth; +import net.minecraft.world.phys.Vec3; + +import javax.annotation.Nullable; +import java.util.Optional; + +public abstract class Outline { + + protected OutlineParams params; + protected Matrix3f transformNormals; // TODO: not used? + + public Outline() { + params = new OutlineParams(); + } + + public abstract void render(PoseStack ms, SuperRenderTypeBuffer buffer, float pt); + + public void tick() {} + + public OutlineParams getParams() { + return params; + } + + public void renderCuboidLine(PoseStack ms, SuperRenderTypeBuffer buffer, Vec3 start, Vec3 end) { + Vec3 diff = end.subtract(start); + float hAngle = AngleHelper.deg(Mth.atan2(diff.x, diff.z)); + float hDistance = (float) diff.multiply(1, 0, 1) + .length(); + float vAngle = AngleHelper.deg(Mth.atan2(hDistance, diff.y)) - 90; + ms.pushPose(); + TransformStack.cast(ms) + .translate(start) + .rotateY(hAngle).rotateX(vAngle); + renderAACuboidLine(ms, buffer, Vec3.ZERO, new Vec3(0, 0, diff.length())); + ms.popPose(); + } + + public void renderAACuboidLine(PoseStack ms, SuperRenderTypeBuffer buffer, Vec3 start, Vec3 end) { + float lineWidth = params.getLineWidth(); + if (lineWidth == 0) + return; + + VertexConsumer builder = buffer.getBuffer(RenderTypes.getOutlineSolid()); + + Vec3 diff = end.subtract(start); + if (diff.x + diff.y + diff.z < 0) { + Vec3 temp = start; + start = end; + end = temp; + diff = diff.scale(-1); + } + + Vec3 extension = diff.normalize() + .scale(lineWidth / 2); + Vec3 plane = VecHelper.axisAlingedPlaneOf(diff); + Direction face = Direction.getNearest(diff.x, diff.y, diff.z); + Axis axis = face.getAxis(); + + start = start.subtract(extension); + end = end.add(extension); + plane = plane.scale(lineWidth / 2); + + Vec3 a1 = plane.add(start); + Vec3 b1 = plane.add(end); + plane = VecHelper.rotate(plane, -90, axis); + Vec3 a2 = plane.add(start); + Vec3 b2 = plane.add(end); + plane = VecHelper.rotate(plane, -90, axis); + Vec3 a3 = plane.add(start); + Vec3 b3 = plane.add(end); + plane = VecHelper.rotate(plane, -90, axis); + Vec3 a4 = plane.add(start); + Vec3 b4 = plane.add(end); + + if (params.disableNormals) { + face = Direction.UP; + putQuad(ms, builder, b4, b3, b2, b1, face); + putQuad(ms, builder, a1, a2, a3, a4, face); + putQuad(ms, builder, a1, b1, b2, a2, face); + putQuad(ms, builder, a2, b2, b3, a3, face); + putQuad(ms, builder, a3, b3, b4, a4, face); + putQuad(ms, builder, a4, b4, b1, a1, face); + return; + } + + putQuad(ms, builder, b4, b3, b2, b1, face); + putQuad(ms, builder, a1, a2, a3, a4, face.getOpposite()); + Vec3 vec = a1.subtract(a4); + face = Direction.getNearest(vec.x, vec.y, vec.z); + putQuad(ms, builder, a1, b1, b2, a2, face); + vec = VecHelper.rotate(vec, -90, axis); + face = Direction.getNearest(vec.x, vec.y, vec.z); + putQuad(ms, builder, a2, b2, b3, a3, face); + vec = VecHelper.rotate(vec, -90, axis); + face = Direction.getNearest(vec.x, vec.y, vec.z); + putQuad(ms, builder, a3, b3, b4, a4, face); + vec = VecHelper.rotate(vec, -90, axis); + face = Direction.getNearest(vec.x, vec.y, vec.z); + putQuad(ms, builder, a4, b4, b1, a1, face); + } + + public void putQuad(PoseStack ms, VertexConsumer builder, Vec3 v1, Vec3 v2, Vec3 v3, Vec3 v4, + Direction normal) { + putQuadUV(ms, builder, v1, v2, v3, v4, 0, 0, 1, 1, normal); + } + + public void putQuadUV(PoseStack ms, VertexConsumer builder, Vec3 v1, Vec3 v2, Vec3 v3, Vec3 v4, float minU, + float minV, float maxU, float maxV, Direction normal) { + putVertex(ms, builder, v1, minU, minV, normal); + putVertex(ms, builder, v2, maxU, minV, normal); + putVertex(ms, builder, v3, maxU, maxV, normal); + putVertex(ms, builder, v4, minU, maxV, normal); + } + + protected void putVertex(PoseStack ms, VertexConsumer builder, Vec3 pos, float u, float v, Direction normal) { + putVertex(ms.last(), builder, (float) pos.x, (float) pos.y, (float) pos.z, u, v, normal); + } + + protected void putVertex(PoseStack.Pose pose, VertexConsumer builder, float x, float y, float z, float u, float v, Direction normal) { + Color rgb = params.rgb; + if (transformNormals == null) + transformNormals = pose.normal(); + + int xOffset = 0; + int yOffset = 0; + int zOffset = 0; + + if (normal != null) { + xOffset = normal.getStepX(); + yOffset = normal.getStepY(); + zOffset = normal.getStepZ(); + } + + builder.vertex(pose.pose(), x, y, z) + .color(rgb.getRedAsFloat(), rgb.getGreenAsFloat(), rgb.getBlueAsFloat(), rgb.getAlphaAsFloat() * params.alpha) + .uv(u, v) + .overlayCoords(OverlayTexture.NO_OVERLAY) + .uv2(params.lightMap) + .normal(pose.normal(), xOffset, yOffset, zOffset) + .endVertex(); + + transformNormals = null; + } + + public static class OutlineParams { + protected Optional faceTexture; + protected Optional hightlightedFaceTexture; + protected Direction highlightedFace; + protected boolean fadeLineWidth; + protected boolean disableCull; + protected boolean disableNormals; + protected float alpha; + protected int lightMap; + protected Color rgb; + private float lineWidth; + + public OutlineParams() { + faceTexture = hightlightedFaceTexture = Optional.empty(); + alpha = 1; + lineWidth = 1 / 32f; + fadeLineWidth = true; + rgb = Color.WHITE; + lightMap = LightTexture.FULL_BRIGHT; + } + + // builder + + public OutlineParams colored(int color) { + rgb = new Color(color, false); + return this; + } + + public OutlineParams colored(Color c) { + rgb = c.copy(); + return this; + } + + public OutlineParams lightMap(int light) { + lightMap = light; + return this; + } + + public OutlineParams lineWidth(float width) { + this.lineWidth = width; + return this; + } + + public OutlineParams withFaceTexture(AllSpecialTextures texture) { + this.faceTexture = Optional.ofNullable(texture); + return this; + } + + public OutlineParams clearTextures() { + return this.withFaceTextures(null, null); + } + + public OutlineParams withFaceTextures(AllSpecialTextures texture, AllSpecialTextures highlightTexture) { + this.faceTexture = Optional.ofNullable(texture); + this.hightlightedFaceTexture = Optional.ofNullable(highlightTexture); + return this; + } + + public OutlineParams highlightFace(@Nullable Direction face) { + highlightedFace = face; + return this; + } + + public OutlineParams disableNormals() { + disableNormals = true; + return this; + } + + public OutlineParams disableCull() { + disableCull = true; + return this; + } + + // getter + + public float getLineWidth() { + return fadeLineWidth ? alpha * lineWidth : lineWidth; + } + + public Direction getHighlightedFace() { + return highlightedFace; + } + + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/outliner/Outliner.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/outliner/Outliner.java new file mode 100644 index 0000000..c1df937 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/outliner/Outliner.java @@ -0,0 +1,185 @@ +package nl.requios.effortlessbuilding.create.foundation.utility.outliner; + +import com.mojang.blaze3d.vertex.PoseStack; +import nl.requios.effortlessbuilding.create.foundation.render.SuperRenderTypeBuffer; +import nl.requios.effortlessbuilding.create.foundation.utility.outliner.LineOutline.EndChasingLineOutline; +import nl.requios.effortlessbuilding.create.foundation.utility.outliner.Outline.OutlineParams; +import net.minecraft.core.BlockPos; +import net.minecraft.util.Mth; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; + +import java.util.*; + +public class Outliner { + + private final Map outlines = Collections.synchronizedMap(new HashMap<>()); + private final Map outlinesView = Collections.unmodifiableMap(outlines); + + // Facade + + public OutlineParams showLine(Object slot, Vec3 start, Vec3 end) { + if (!outlines.containsKey(slot)) { + LineOutline outline = new LineOutline(); + outlines.put(slot, new OutlineEntry(outline)); + } + OutlineEntry entry = outlines.get(slot); + entry.ticksTillRemoval = 1; + ((LineOutline) entry.outline).set(start, end); + return entry.outline.getParams(); + } + + public OutlineParams endChasingLine(Object slot, Vec3 start, Vec3 end, float chasingProgress, boolean lockStart) { + if (!outlines.containsKey(slot)) { + EndChasingLineOutline outline = new EndChasingLineOutline(lockStart); + outlines.put(slot, new OutlineEntry(outline)); + } + OutlineEntry entry = outlines.get(slot); + entry.ticksTillRemoval = 1; + ((EndChasingLineOutline) entry.outline).setProgress(chasingProgress) + .set(start, end); + return entry.outline.getParams(); + } + + public OutlineParams showAABB(Object slot, AABB bb, int ttl) { + createAABBOutlineIfMissing(slot, bb); + ChasingAABBOutline outline = getAndRefreshAABB(slot, ttl); + outline.prevBB = outline.targetBB = outline.bb = bb; + return outline.getParams(); + } + + public OutlineParams showAABB(Object slot, AABB bb) { + createAABBOutlineIfMissing(slot, bb); + ChasingAABBOutline outline = getAndRefreshAABB(slot); + outline.prevBB = outline.targetBB = outline.bb = bb; + return outline.getParams(); + } + + public OutlineParams chaseAABB(Object slot, AABB bb) { + createAABBOutlineIfMissing(slot, bb); + ChasingAABBOutline outline = getAndRefreshAABB(slot); + outline.targetBB = bb; + return outline.getParams(); + } + + public OutlineParams showCluster(Object slot, Iterable selection) { + BlockClusterOutline outline = new BlockClusterOutline(selection); + OutlineEntry entry = new OutlineEntry(outline); + outlines.put(slot, entry); + return entry.getOutline() + .getParams(); + } + + public void keep(Object slot) { + if (outlines.containsKey(slot)) + outlines.get(slot).ticksTillRemoval = 1; + } + + //ADDED + public void keep(Object slot, int ticks) { + if (outlines.containsKey(slot)) + outlines.get(slot).ticksTillRemoval = ticks; + } + + public void remove(Object slot) { + outlines.remove(slot); + } + + public Optional edit(Object slot) { + keep(slot); + if (outlines.containsKey(slot)) + return Optional.of(outlines.get(slot) + .getOutline() + .getParams()); + return Optional.empty(); + } + + public Map getOutlines() { + return outlinesView; + } + + // Utility + + private void createAABBOutlineIfMissing(Object slot, AABB bb) { + if (!outlines.containsKey(slot) || !(outlines.get(slot).outline instanceof AABBOutline)) { + ChasingAABBOutline outline = new ChasingAABBOutline(bb); + outlines.put(slot, new OutlineEntry(outline)); + } + } + + private ChasingAABBOutline getAndRefreshAABB(Object slot) { + OutlineEntry entry = outlines.get(slot); + entry.ticksTillRemoval = 1; + return (ChasingAABBOutline) entry.getOutline(); + } + + private ChasingAABBOutline getAndRefreshAABB(Object slot, int ttl) { + OutlineEntry entry = outlines.get(slot); + entry.ticksTillRemoval = ttl; + return (ChasingAABBOutline) entry.getOutline(); + } + + // Maintenance + + public void tickOutlines() { + Iterator iterator = outlines.values() + .iterator(); + while (iterator.hasNext()) { + OutlineEntry entry = iterator.next(); + entry.tick(); + if (!entry.isAlive()) + iterator.remove(); + } + } + + public void renderOutlines(PoseStack ms, SuperRenderTypeBuffer buffer, float pt) { + outlines.forEach((key, entry) -> { + Outline outline = entry.getOutline(); + OutlineParams params = outline.getParams(); + params.alpha = 1; + if (entry.isFading()) { + int prevTicks = entry.ticksTillRemoval + 1; + float fadeticks = OutlineEntry.fadeTicks; + float lastAlpha = prevTicks >= 0 ? 1 : 1 + (prevTicks / fadeticks); + float currentAlpha = 1 + (entry.ticksTillRemoval / fadeticks); + float alpha = Mth.lerp(pt, lastAlpha, currentAlpha); + + params.alpha = alpha * alpha * alpha; + if (params.alpha < 1 / 8f) + return; + } + outline.render(ms, buffer, pt); + }); + } + + public static class OutlineEntry { + + static final int fadeTicks = 8; + private Outline outline; + private int ticksTillRemoval; + + public OutlineEntry(Outline outline) { + this.outline = outline; + ticksTillRemoval = 1; + } + + public void tick() { + ticksTillRemoval--; + outline.tick(); + } + + public boolean isAlive() { + return ticksTillRemoval >= -fadeTicks; + } + + public boolean isFading() { + return ticksTillRemoval < 0; + } + + public Outline getOutline() { + return outline; + } + + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/worldWrappers/DummyLevelEntityGetter.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/worldWrappers/DummyLevelEntityGetter.java new file mode 100644 index 0000000..294fbed --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/worldWrappers/DummyLevelEntityGetter.java @@ -0,0 +1,41 @@ +package nl.requios.effortlessbuilding.create.foundation.utility.worldWrappers; + +import net.minecraft.world.level.entity.EntityAccess; +import net.minecraft.world.level.entity.EntityTypeTest; +import net.minecraft.world.level.entity.LevelEntityGetter; +import net.minecraft.world.phys.AABB; + +import java.util.Collections; +import java.util.UUID; +import java.util.function.Consumer; + +public class DummyLevelEntityGetter implements LevelEntityGetter { + + @Override + public T get(int p_156931_) { + return null; + } + + @Override + public T get(UUID pUuid) { + return null; + } + + @Override + public Iterable getAll() { + return Collections.emptyList(); + } + + @Override + public void get(EntityTypeTest p_156935_, Consumer p_156936_) { + } + + @Override + public void get(AABB p_156937_, Consumer p_156938_) { + } + + @Override + public void get(EntityTypeTest p_156932_, AABB p_156933_, Consumer p_156934_) { + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/worldWrappers/DummyStatusListener.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/worldWrappers/DummyStatusListener.java new file mode 100644 index 0000000..ad9856e --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/worldWrappers/DummyStatusListener.java @@ -0,0 +1,23 @@ +package nl.requios.effortlessbuilding.create.foundation.utility.worldWrappers; + +import net.minecraft.server.level.progress.ChunkProgressListener; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.chunk.ChunkStatus; + +import javax.annotation.Nullable; + +public class DummyStatusListener implements ChunkProgressListener { + + @Override + public void updateSpawnPos(ChunkPos pCenter) {} + + @Override + public void onStatusChange(ChunkPos pChunkPosition, @Nullable ChunkStatus pNewStatus) {} + + @Override + public void start() {} + + @Override + public void stop() {} + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/worldWrappers/PlacementSimulationServerWorld.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/worldWrappers/PlacementSimulationServerWorld.java new file mode 100644 index 0000000..e596b9d --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/worldWrappers/PlacementSimulationServerWorld.java @@ -0,0 +1,62 @@ +package nl.requios.effortlessbuilding.create.foundation.utility.worldWrappers; + +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.FluidState; + +import java.util.HashMap; +import java.util.function.Predicate; + +public class PlacementSimulationServerWorld extends WrappedServerWorld { + public HashMap blocksAdded; + + public PlacementSimulationServerWorld(ServerLevel wrapped) { + super(wrapped); + blocksAdded = new HashMap<>(); + } + + public void clear() { + blocksAdded.clear(); + } + + @Override + public boolean setBlock(BlockPos pos, BlockState newState, int flags) { + blocksAdded.put(pos.immutable(), newState); + return true; + } + + @Override + public boolean setBlockAndUpdate(BlockPos pos, BlockState state) { + return setBlock(pos, state, 0); + } + + @Override + public boolean isStateAtPosition(BlockPos pos, Predicate condition) { + return condition.test(getBlockState(pos)); + } + + @Override + public boolean isLoaded(BlockPos pos) { + return true; + } + + @Override + public boolean isAreaLoaded(BlockPos center, int range) { + return true; + } + + @Override + public BlockState getBlockState(BlockPos pos) { + if (blocksAdded.containsKey(pos)) + return blocksAdded.get(pos); + return Blocks.AIR.defaultBlockState(); + } + + @Override + public FluidState getFluidState(BlockPos pos) { + return getBlockState(pos).getFluidState(); + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/worldWrappers/RayTraceWorld.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/worldWrappers/RayTraceWorld.java new file mode 100644 index 0000000..74b907b --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/worldWrappers/RayTraceWorld.java @@ -0,0 +1,47 @@ +package nl.requios.effortlessbuilding.create.foundation.utility.worldWrappers; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.FluidState; + +import java.util.function.BiFunction; + +public class RayTraceWorld implements BlockGetter { + + private final LevelAccessor template; + private final BiFunction stateGetter; + + public RayTraceWorld(LevelAccessor template, BiFunction stateGetter) { + this.template = template; + this.stateGetter = stateGetter; + } + + @Override + public BlockEntity getBlockEntity(BlockPos pos) { + return template.getBlockEntity(pos); + } + + @Override + public BlockState getBlockState(BlockPos pos) { + return stateGetter.apply(pos, template.getBlockState(pos)); + } + + @Override + public FluidState getFluidState(BlockPos pos) { + return template.getFluidState(pos); + } + + @Override + public int getHeight() { + return template.getHeight(); + } + + @Override + public int getMinBuildHeight() { + return template.getMinBuildHeight(); + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/worldWrappers/WrappedClientWorld.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/worldWrappers/WrappedClientWorld.java new file mode 100644 index 0000000..31ed743 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/worldWrappers/WrappedClientWorld.java @@ -0,0 +1,140 @@ +package nl.requios.effortlessbuilding.create.foundation.utility.worldWrappers; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.ClientLevel; +import net.minecraft.core.BlockPos; +import net.minecraft.core.particles.ParticleOptions; +import net.minecraft.sounds.SoundEvent; +import net.minecraft.sounds.SoundSource; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.ai.targeting.TargetingConditions; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.ColorResolver; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LightLayer; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.FluidState; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import javax.annotation.Nullable; +import javax.annotation.ParametersAreNonnullByDefault; +import java.util.List; + +@OnlyIn(Dist.CLIENT) +@SuppressWarnings("deprecation") +@ParametersAreNonnullByDefault +public class WrappedClientWorld extends ClientLevel { + private static final Minecraft mc = Minecraft.getInstance(); + protected Level world; + + private WrappedClientWorld(Level world) { + super(mc.getConnection(), mc.level.getLevelData(), world.dimension(), world.dimensionTypeRegistration(), + mc.getConnection().serverChunkRadius, mc.level.getServerSimulationDistance(), world.getProfilerSupplier(), + mc.levelRenderer, world.isDebug(), world.getBiomeManager().biomeZoomSeed); + this.world = world; + } + + public static WrappedClientWorld of(Level world) { + return new WrappedClientWorld(world); + } + + @Override + public boolean hasChunkAt(BlockPos pos) { + return world.hasChunkAt(pos); + } + + @Override + public boolean isLoaded(BlockPos pos) { + return world.isLoaded(pos); + } + + @Override + public BlockState getBlockState(BlockPos pos) { + return world.getBlockState(pos); + } + + // FIXME: blockstate#getCollisionShape with WrappedClientWorld gives unreliable + // data (maybe) + + @Override + public int getBrightness(LightLayer type, BlockPos pos) { + return world.getBrightness(type, pos); + } + + @Override + public int getLightEmission(BlockPos pos) { + return world.getLightEmission(pos); + } + + @Override + public FluidState getFluidState(BlockPos pos) { + return world.getFluidState(pos); + } + + @Nullable + @Override + public T getNearestEntity(List p_217361_1_, TargetingConditions p_217361_2_, + @Nullable LivingEntity p_217361_3_, double p_217361_4_, double p_217361_6_, double p_217361_8_) { + return world.getNearestEntity(p_217361_1_, p_217361_2_, p_217361_3_, p_217361_4_, p_217361_6_, p_217361_8_); + } + + @Override + public int getBlockTint(BlockPos p_225525_1_, ColorResolver p_225525_2_) { + return world.getBlockTint(p_225525_1_, p_225525_2_); + } + + // FIXME: Emissive Lighting might not light stuff properly + + @Override + public void addParticle(ParticleOptions p_195594_1_, double p_195594_2_, double p_195594_4_, double p_195594_6_, + double p_195594_8_, double p_195594_10_, double p_195594_12_) { + world.addParticle(p_195594_1_, p_195594_2_, p_195594_4_, p_195594_6_, p_195594_8_, p_195594_10_, p_195594_12_); + } + + @Override + public void addParticle(ParticleOptions p_195590_1_, boolean p_195590_2_, double p_195590_3_, double p_195590_5_, + double p_195590_7_, double p_195590_9_, double p_195590_11_, double p_195590_13_) { + world.addParticle(p_195590_1_, p_195590_2_, p_195590_3_, p_195590_5_, p_195590_7_, p_195590_9_, p_195590_11_, + p_195590_13_); + } + + @Override + public void addAlwaysVisibleParticle(ParticleOptions p_195589_1_, double p_195589_2_, double p_195589_4_, + double p_195589_6_, double p_195589_8_, double p_195589_10_, double p_195589_12_) { + world.addAlwaysVisibleParticle(p_195589_1_, p_195589_2_, p_195589_4_, p_195589_6_, p_195589_8_, p_195589_10_, + p_195589_12_); + } + + @Override + public void addAlwaysVisibleParticle(ParticleOptions p_217404_1_, boolean p_217404_2_, double p_217404_3_, + double p_217404_5_, double p_217404_7_, double p_217404_9_, double p_217404_11_, double p_217404_13_) { + world.addAlwaysVisibleParticle(p_217404_1_, p_217404_2_, p_217404_3_, p_217404_5_, p_217404_7_, p_217404_9_, + p_217404_11_, p_217404_13_); + } + + @Override + public void playLocalSound(double p_184134_1_, double p_184134_3_, double p_184134_5_, SoundEvent p_184134_7_, + SoundSource p_184134_8_, float p_184134_9_, float p_184134_10_, boolean p_184134_11_) { + world.playLocalSound(p_184134_1_, p_184134_3_, p_184134_5_, p_184134_7_, p_184134_8_, p_184134_9_, p_184134_10_, + p_184134_11_); + } + + @Override + public void playSound(@Nullable Player p_184148_1_, double p_184148_2_, double p_184148_4_, double p_184148_6_, + SoundEvent p_184148_8_, SoundSource p_184148_9_, float p_184148_10_, float p_184148_11_) { + world.playSound(p_184148_1_, p_184148_2_, p_184148_4_, p_184148_6_, p_184148_8_, p_184148_9_, p_184148_10_, + p_184148_11_); + } + + @Nullable + @Override + public BlockEntity getBlockEntity(BlockPos p_175625_1_) { + return world.getBlockEntity(p_175625_1_); + } + + public Level getWrappedWorld() { + return world; + } +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/worldWrappers/WrappedServerWorld.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/worldWrappers/WrappedServerWorld.java new file mode 100644 index 0000000..9c92219 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/worldWrappers/WrappedServerWorld.java @@ -0,0 +1,120 @@ +package nl.requios.effortlessbuilding.create.foundation.utility.worldWrappers; + +import net.minecraft.MethodsReturnNonnullByDefault; +import net.minecraft.Util; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Holder; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.sounds.SoundEvent; +import net.minecraft.sounds.SoundSource; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.crafting.RecipeManager; +import net.minecraft.world.level.biome.Biome; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.dimension.LevelStem; +import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.level.saveddata.maps.MapItemSavedData; +import net.minecraft.world.level.storage.ServerLevelData; +import net.minecraft.world.ticks.LevelTicks; + +import javax.annotation.ParametersAreNonnullByDefault; +import java.util.Collections; +import java.util.List; + +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +public class WrappedServerWorld extends ServerLevel { + + protected ServerLevel world; + + public WrappedServerWorld(ServerLevel world) { + super(world.getServer(), Util.backgroundExecutor(), world.getServer().storageSource, + (ServerLevelData) world.getLevelData(), world.dimension(), + new LevelStem(world.dimensionTypeRegistration(), world.getChunkSource().getGenerator()), + new DummyStatusListener(), world.isDebug(), world.getBiomeManager().biomeZoomSeed, + Collections.emptyList(), false); + this.world = world; + } + + @Override + public float getSunAngle(float p_72826_1_) { + return 0; + } + + @Override + public int getMaxLocalRawBrightness(BlockPos pos) { + return 15; + } + + @Override + public void sendBlockUpdated(BlockPos pos, BlockState oldState, BlockState newState, int flags) { + world.sendBlockUpdated(pos, oldState, newState, flags); + } + + @Override + public LevelTicks getBlockTicks() { + return super.getBlockTicks(); + } + + @Override + public LevelTicks getFluidTicks() { + return super.getFluidTicks(); + } + + @Override + public void levelEvent(Player player, int type, BlockPos pos, int data) {} + + @Override + public List players() { + return Collections.emptyList(); + } + + @Override + public void playSound(Player player, double x, double y, double z, SoundEvent soundIn, SoundSource category, + float volume, float pitch) {} + + @Override + public void playSound(Player p_217384_1_, Entity p_217384_2_, SoundEvent p_217384_3_, SoundSource p_217384_4_, + float p_217384_5_, float p_217384_6_) {} + + @Override + public Entity getEntity(int id) { + return null; + } + + @Override + public MapItemSavedData getMapData(String mapName) { + return null; + } + + @Override + public boolean addFreshEntity(Entity entityIn) { + entityIn.level = world; + return world.addFreshEntity(entityIn); + } + + @Override + public void setMapData(String mapId, MapItemSavedData mapDataIn) {} + + @Override + public int getFreeMapId() { + return 0; + } + + @Override + public void destroyBlockProgress(int breakerId, BlockPos pos, int progress) {} + + @Override + public RecipeManager getRecipeManager() { + return world.getRecipeManager(); + } + + @Override + public Holder getUncachedNoiseBiome(int p_225604_1_, int p_225604_2_, int p_225604_3_) { + return world.getUncachedNoiseBiome(p_225604_1_, p_225604_2_, p_225604_3_); + } + +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/worldWrappers/WrappedWorld.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/worldWrappers/WrappedWorld.java new file mode 100644 index 0000000..4040b5e --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/worldWrappers/WrappedWorld.java @@ -0,0 +1,250 @@ +package nl.requios.effortlessbuilding.create.foundation.utility.worldWrappers; + +import net.minecraft.core.*; +import net.minecraft.sounds.SoundEvent; +import net.minecraft.sounds.SoundSource; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.crafting.RecipeManager; +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.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.ChunkSource; +import net.minecraft.world.level.entity.LevelEntityGetter; +import net.minecraft.world.level.gameevent.GameEvent; +import net.minecraft.world.level.gameevent.GameEvent.Context; +import net.minecraft.world.level.lighting.LevelLightEngine; +import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.level.saveddata.maps.MapItemSavedData; +import net.minecraft.world.level.storage.WritableLevelData; +import net.minecraft.world.phys.Vec3; +import net.minecraft.world.scores.Scoreboard; +import net.minecraft.world.ticks.LevelTickAccess; + +import javax.annotation.Nullable; +import java.util.Collections; +import java.util.List; +import java.util.function.Predicate; + +public class WrappedWorld extends Level { + + protected Level world; + protected ChunkSource chunkSource; + + protected LevelEntityGetter entityGetter = new DummyLevelEntityGetter<>(); + + public WrappedWorld(Level world) { + super((WritableLevelData) world.getLevelData(), world.dimension(), world.dimensionTypeRegistration(), + world::getProfiler, world.isClientSide, world.isDebug(), 0, 0); + this.world = world; + } + + public void setChunkSource(ChunkSource source) { + this.chunkSource = source; + } + + public Level getLevel() { + return world; + } + + @Override + public LevelLightEngine getLightEngine() { + return world.getLightEngine(); + } + + @Override + public BlockState getBlockState(@Nullable BlockPos pos) { + return world.getBlockState(pos); + } + + @Override + public boolean isStateAtPosition(BlockPos p_217375_1_, Predicate p_217375_2_) { + return world.isStateAtPosition(p_217375_1_, p_217375_2_); + } + + @Override + @Nullable + public BlockEntity getBlockEntity(BlockPos pos) { + return world.getBlockEntity(pos); + } + + @Override + public boolean setBlock(BlockPos pos, BlockState newState, int flags) { + return world.setBlock(pos, newState, flags); + } + + @Override + public int getMaxLocalRawBrightness(BlockPos pos) { + return 15; + } + + @Override + public void sendBlockUpdated(BlockPos pos, BlockState oldState, BlockState newState, int flags) { + world.sendBlockUpdated(pos, oldState, newState, flags); + } + + @Override + public LevelTickAccess getBlockTicks() { + return world.getBlockTicks(); + } + + @Override + public LevelTickAccess getFluidTicks() { + return world.getFluidTicks(); + } + + @Override + public ChunkSource getChunkSource() { + return chunkSource != null ? chunkSource : world.getChunkSource(); + } + + @Override + public void levelEvent(@Nullable Player player, int type, BlockPos pos, int data) {} + + @Override + public List players() { + return Collections.emptyList(); + } + + @Override + public void playSeededSound(Player p_220363_, double p_220364_, double p_220365_, double p_220366_, + SoundEvent p_220367_, SoundSource p_220368_, float p_220369_, float p_220370_, long p_220371_) {} + + @Override + public void playSeededSound(Player p_220372_, Entity p_220373_, SoundEvent p_220374_, SoundSource p_220375_, + float p_220376_, float p_220377_, long p_220378_) {} + + @Override + public void playSound(@Nullable Player player, double x, double y, double z, SoundEvent soundIn, + SoundSource category, float volume, float pitch) {} + + @Override + public void playSound(@Nullable Player p_217384_1_, Entity p_217384_2_, SoundEvent p_217384_3_, + SoundSource p_217384_4_, float p_217384_5_, float p_217384_6_) {} + + @Override + public Entity getEntity(int id) { + return null; + } + + @Override + public MapItemSavedData getMapData(String mapName) { + return null; + } + + @Override + public boolean addFreshEntity(Entity entityIn) { + entityIn.level = world; + return world.addFreshEntity(entityIn); + } + + @Override + public void setMapData(String pMapId, MapItemSavedData pData) {} + + @Override + public int getFreeMapId() { + return world.getFreeMapId(); + } + + @Override + public void destroyBlockProgress(int breakerId, BlockPos pos, int progress) {} + + @Override + public Scoreboard getScoreboard() { + return world.getScoreboard(); + } + + @Override + public RecipeManager getRecipeManager() { + return world.getRecipeManager(); + } + + @Override + public Holder getUncachedNoiseBiome(int p_225604_1_, int p_225604_2_, int p_225604_3_) { + return world.getUncachedNoiseBiome(p_225604_1_, p_225604_2_, p_225604_3_); + } + + @Override + public RegistryAccess registryAccess() { + return world.registryAccess(); + } + + @Override + public float getShade(Direction p_230487_1_, boolean p_230487_2_) { + return world.getShade(p_230487_1_, p_230487_2_); + } + + @Override + public void updateNeighbourForOutputSignal(BlockPos p_175666_1_, Block p_175666_2_) {} + + @Override + public void gameEvent(Entity pEntity, GameEvent pEvent, BlockPos pPos) {} + + @Override + public void gameEvent(GameEvent p_220404_, Vec3 p_220405_, Context p_220406_) {} + + @Override + public String gatherChunkSourceStats() { + return world.gatherChunkSourceStats(); + } + + @Override + protected LevelEntityGetter getEntities() { + return entityGetter; + } + + // Intentionally copied from LevelHeightAccessor. Workaround for issues caused + // when other mods (such as Lithium) + // override the vanilla implementations in ways which cause WrappedWorlds to + // return incorrect, default height info. + // WrappedWorld subclasses should implement their own getMinBuildHeight and + // getHeight overrides where they deviate + // from the defaults for their dimension. + + @Override + public int getMaxBuildHeight() { + return this.getMinBuildHeight() + this.getHeight(); + } + + @Override + public int getSectionsCount() { + return this.getMaxSection() - this.getMinSection(); + } + + @Override + public int getMinSection() { + return SectionPos.blockToSectionCoord(this.getMinBuildHeight()); + } + + @Override + public int getMaxSection() { + return SectionPos.blockToSectionCoord(this.getMaxBuildHeight() - 1) + 1; + } + + @Override + public boolean isOutsideBuildHeight(BlockPos pos) { + return this.isOutsideBuildHeight(pos.getY()); + } + + @Override + public boolean isOutsideBuildHeight(int y) { + return y < this.getMinBuildHeight() || y >= this.getMaxBuildHeight(); + } + + @Override + public int getSectionIndex(int y) { + return this.getSectionIndexFromSectionY(SectionPos.blockToSectionCoord(y)); + } + + @Override + public int getSectionIndexFromSectionY(int sectionY) { + return sectionY - this.getMinSection(); + } + + @Override + public int getSectionYFromSectionIndex(int sectionIndex) { + return sectionIndex + this.getMinSection(); + } +} diff --git a/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/worldWrappers/package-info.java b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/worldWrappers/package-info.java new file mode 100644 index 0000000..bb6a251 --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/foundation/utility/worldWrappers/package-info.java @@ -0,0 +1,6 @@ +@ParametersAreNonnullByDefault @MethodsReturnNonnullByDefault +package nl.requios.effortlessbuilding.create.foundation.utility.worldWrappers; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/src/main/java/nl/requios/effortlessbuilding/create/license.txt b/src/main/java/nl/requios/effortlessbuilding/create/license.txt new file mode 100644 index 0000000..86204cf --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/create/license.txt @@ -0,0 +1,12 @@ +All files within this folder fall under the MIT license. + +The MIT License Copyright (c) Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the +following conditions: The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/src/main/java/nl/requios/effortlessbuilding/gui/buildmode/RadialMenu.java b/src/main/java/nl/requios/effortlessbuilding/gui/buildmode/RadialMenu.java index f3b9d1e..67d3cc3 100644 --- a/src/main/java/nl/requios/effortlessbuilding/gui/buildmode/RadialMenu.java +++ b/src/main/java/nl/requios/effortlessbuilding/gui/buildmode/RadialMenu.java @@ -18,6 +18,7 @@ import net.minecraft.resources.ResourceLocation; import net.minecraft.sounds.SoundEvents; import net.minecraft.sounds.SoundSource; import net.minecraft.util.RandomSource; +import nl.requios.effortlessbuilding.ClientEvents; import nl.requios.effortlessbuilding.EffortlessBuilding; import nl.requios.effortlessbuilding.buildmode.ModeOptions; import nl.requios.effortlessbuilding.buildmode.ModeSettingsManager; @@ -92,7 +93,7 @@ public class RadialMenu extends Screen { public void tick() { super.tick(); - if (!ClientProxy.isKeybindDown(2)) { + if (!ClientEvents.isKeybindDown(2)) { onClose(); } } @@ -397,7 +398,7 @@ public class RadialMenu extends Screen { if (button.action == ActionEnum.OPEN_MODIFIER_SETTINGS) keybindingIndex = 0; if (keybindingIndex != -1) { - KeyMapping keyMap = ClientProxy.keyBindings[keybindingIndex]; + KeyMapping keyMap = ClientEvents.keyBindings[keybindingIndex]; if (!keyMap.getKeyModifier().name().equals("none")) { result = keyMap.getKeyModifier().name() + " "; @@ -409,7 +410,7 @@ public class RadialMenu extends Screen { //Add (ctrl) to first two actions of first option if (button.action == currentBuildMode.options[0].actions[0] || button.action == currentBuildMode.options[0].actions[1]) { - result = I18n.get(ClientProxy.keyBindings[5].getKey().getName()); + result = I18n.get(ClientEvents.keyBindings[5].getKey().getName()); if (result.equals("Left Control")) result = "Ctrl"; } } diff --git a/src/main/java/nl/requios/effortlessbuilding/gui/buildmodifier/ModifierSettingsGui.java b/src/main/java/nl/requios/effortlessbuilding/gui/buildmodifier/ModifierSettingsGui.java index 5a7807c..a0ae3cf 100644 --- a/src/main/java/nl/requios/effortlessbuilding/gui/buildmodifier/ModifierSettingsGui.java +++ b/src/main/java/nl/requios/effortlessbuilding/gui/buildmodifier/ModifierSettingsGui.java @@ -7,6 +7,7 @@ import net.minecraft.client.gui.components.Button; import net.minecraft.network.chat.Component; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; +import nl.requios.effortlessbuilding.ClientEvents; import nl.requios.effortlessbuilding.EffortlessBuilding; import nl.requios.effortlessbuilding.buildmodifier.Array; import nl.requios.effortlessbuilding.buildmodifier.Mirror; @@ -87,7 +88,7 @@ public class ModifierSettingsGui extends Screen { @Override public boolean keyPressed(int keyCode, int p_96553_, int p_96554_) { - if (keyCode == ClientProxy.keyBindings[0].getKey().getValue()) { + if (keyCode == ClientEvents.keyBindings[0].getKey().getValue()) { minecraft.player.closeContainer(); return true; } diff --git a/src/main/java/nl/requios/effortlessbuilding/helper/DelayedBlockPlacer.java b/src/main/java/nl/requios/effortlessbuilding/helper/DelayedBlockPlacer.java new file mode 100644 index 0000000..bac8f8c --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/helper/DelayedBlockPlacer.java @@ -0,0 +1,108 @@ +package nl.requios.effortlessbuilding.helper; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +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.Blocks; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.Vec3; +import nl.requios.effortlessbuilding.buildmodifier.BlockSet; +import nl.requios.effortlessbuilding.buildmodifier.UndoRedo; +import nl.requios.effortlessbuilding.create.foundation.utility.AnimationTickHolder; + +import java.util.*; + +public class DelayedBlockPlacer { + + private final Set entries = Collections.synchronizedSet(new HashSet<>()); + private final Set entriesView = Collections.unmodifiableSet(entries); + + public void placeBlocksDelayed(Entry entry) { + if (entry.world.isClientSide) return; + + entries.add(entry); + } + + public void tick() { + for (Entry entry : entries) { + entry.ticksTillPlacement--; + if (entry.ticksTillPlacement <= 0) { + entry.place(); + entries.remove(entry); + } + } + } + + public Set getEntries() { + return entriesView; + } + + public static class Entry { + private Level world; + private Player player; + private List coordinates; + private List blockStates; + private List itemStacks; + private Vec3 hitVec; + private boolean placeStartPos; + private int ticksTillPlacement; + + public Entry(Level world, Player player, List coordinates, List blockStates, + List itemStacks, Vec3 hitVec, boolean placeStartPos, int ticksTillPlacement) { + this.world = world; + this.player = player; + this.coordinates = coordinates; + this.blockStates = blockStates; + this.itemStacks = itemStacks; + this.hitVec = hitVec; + this.placeStartPos = placeStartPos; + this.ticksTillPlacement = ticksTillPlacement; + } + + public void place() { + //remember previous blockstates for undo + List previousBlockStates = new ArrayList<>(coordinates.size()); + for (BlockPos coordinate : coordinates) { + previousBlockStates.add(world.getBlockState(coordinate)); + } + + for (int i = placeStartPos ? 0 : 1; i < coordinates.size(); i++) { + BlockPos blockPos = coordinates.get(i); + BlockState blockState = blockStates.get(i); + ItemStack itemStack = itemStacks.get(i); + + if (world.isLoaded(blockPos)) { + //check itemstack empty + if (itemStack.isEmpty()) { + //try to find new stack, otherwise continue + itemStack = InventoryHelper.findItemStackInInventory(player, blockState.getBlock()); + if (itemStack.isEmpty()) continue; + } + SurvivalHelper.placeBlock(world, player, blockPos, blockState, itemStack, Direction.UP, hitVec, false, false, false); + } + } + + //find actual new blockstates for undo + List newBlockStates = new ArrayList<>(coordinates.size()); + for (BlockPos coordinate : coordinates) { + newBlockStates.add(world.getBlockState(coordinate)); + } + + //Set first previousBlockState to empty if in NORMAL mode, to make undo/redo work + //(Block is placed by the time it gets here, and unplaced after this) + if (!placeStartPos) previousBlockStates.set(0, Blocks.AIR.defaultBlockState()); + + //If all new blockstates are air then no use in adding it, no block was actually placed + //Can happen when e.g. placing one block in yourself + if (Collections.frequency(newBlockStates, Blocks.AIR.defaultBlockState()) != newBlockStates.size()) { + //add to undo stack + BlockPos firstPos = coordinates.get(0); + BlockPos secondPos = coordinates.get(coordinates.size() - 1); + UndoRedo.addUndo(player, new BlockSet(coordinates, previousBlockStates, newBlockStates, hitVec, firstPos, secondPos)); + } + } + + } +} diff --git a/src/main/java/nl/requios/effortlessbuilding/helper/ReachHelper.java b/src/main/java/nl/requios/effortlessbuilding/helper/ReachHelper.java index a22b6e7..656259d 100644 --- a/src/main/java/nl/requios/effortlessbuilding/helper/ReachHelper.java +++ b/src/main/java/nl/requios/effortlessbuilding/helper/ReachHelper.java @@ -2,28 +2,28 @@ package nl.requios.effortlessbuilding.helper; import net.minecraft.world.entity.player.Player; import net.minecraft.util.Mth; -import nl.requios.effortlessbuilding.BuildConfig; +import nl.requios.effortlessbuilding.CommonConfig; import nl.requios.effortlessbuilding.buildmodifier.ModifierSettingsManager; public class ReachHelper { public static int getMaxReach(Player player) { - if (player.isCreative()) return BuildConfig.reach.maxReachCreative.get(); + if (player.isCreative()) return CommonConfig.reach.maxReachCreative.get(); - if (!BuildConfig.reach.enableReachUpgrades.get()) return BuildConfig.reach.maxReachLevel3.get(); + if (!CommonConfig.reach.enableReachUpgrades.get()) return CommonConfig.reach.maxReachLevel3.get(); //Check buildsettings for reachUpgrade int reachUpgrade = ModifierSettingsManager.getModifierSettings(player).getReachUpgrade(); switch (reachUpgrade) { case 0: - return BuildConfig.reach.maxReachLevel0.get(); + return CommonConfig.reach.maxReachLevel0.get(); case 1: - return BuildConfig.reach.maxReachLevel1.get(); + return CommonConfig.reach.maxReachLevel1.get(); case 2: - return BuildConfig.reach.maxReachLevel2.get(); + return CommonConfig.reach.maxReachLevel2.get(); case 3: - return BuildConfig.reach.maxReachLevel3.get(); + return CommonConfig.reach.maxReachLevel3.get(); } - return BuildConfig.reach.maxReachLevel0.get(); + return CommonConfig.reach.maxReachLevel0.get(); } public static int getPlacementReach(Player player) { diff --git a/src/main/java/nl/requios/effortlessbuilding/helper/SurvivalHelper.java b/src/main/java/nl/requios/effortlessbuilding/helper/SurvivalHelper.java index 64434a6..19d9650 100644 --- a/src/main/java/nl/requios/effortlessbuilding/helper/SurvivalHelper.java +++ b/src/main/java/nl/requios/effortlessbuilding/helper/SurvivalHelper.java @@ -23,7 +23,7 @@ import net.minecraft.world.level.material.Material; import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.shapes.VoxelShape; import net.minecraftforge.event.ForgeEventFactory; -import nl.requios.effortlessbuilding.BuildConfig; +import nl.requios.effortlessbuilding.CommonConfig; import nl.requios.effortlessbuilding.buildmodifier.ModifierSettingsManager; import nl.requios.effortlessbuilding.compatibility.CompatHelper; @@ -187,17 +187,21 @@ public class SurvivalHelper { */ public static boolean canPlace(Level world, Player player, BlockPos pos, BlockState newBlockState, ItemStack itemStack, boolean skipCollisionCheck, Direction sidePlacedOn) { - //Check if itemstack is correct - if (!(itemStack.getItem() instanceof BlockItem) || Block.byItem(itemStack.getItem()) != newBlockState.getBlock()) { -// EffortlessBuilding.log(player, "Cannot (re)place block", true); -// EffortlessBuilding.log("SurvivalHelper#canPlace: itemstack " + itemStack.toString() + " does not match blockstate " + newBlockState.toString()); - //Happens when breaking blocks, no need to notify in that case - return false; + if (!player.isCreative()) { + //Check if itemstack is correct + if (itemStack.isEmpty() || !(itemStack.getItem() instanceof BlockItem) || + Block.byItem(itemStack.getItem()) != newBlockState.getBlock()) { + return false; + } } - Block block = ((BlockItem) itemStack.getItem()).getBlock(); + Block block = null; + if (itemStack != null && !itemStack.isEmpty() && itemStack.getItem() instanceof BlockItem) + block = ((BlockItem) itemStack.getItem()).getBlock(); + else //In creative we might not have an itemstack + block = newBlockState.getBlock(); - return !itemStack.isEmpty() && canPlayerEdit(player, world, pos, itemStack) && + return canPlayerEdit(player, world, pos, itemStack) && mayPlace(world, block, newBlockState, pos, skipCollisionCheck, sidePlacedOn, player) && canReplace(world, player, pos); } @@ -208,7 +212,7 @@ public class SurvivalHelper { BlockState state = world.getBlockState(pos); - int miningLevel = BuildConfig.survivalBalancers.quickReplaceMiningLevel.get(); + int miningLevel = CommonConfig.survivalBalancers.quickReplaceMiningLevel.get(); switch (miningLevel) { case -1: return !state.requiresCorrectToolForDrops(); @@ -245,7 +249,7 @@ public class SurvivalHelper { //From World#mayPlace private static boolean mayPlace(Level world, Block blockIn, BlockState newBlockState, BlockPos pos, boolean skipCollisionCheck, Direction sidePlacedOn, @Nullable Entity placer) { - BlockState iblockstate1 = world.getBlockState(pos); + BlockState currentBlockState = world.getBlockState(pos); VoxelShape voxelShape = skipCollisionCheck ? null : blockIn.defaultBlockState().getCollisionShape(world, pos); if (voxelShape != null && !world.isUnobstructed(placer, voxelShape)) { @@ -259,22 +263,20 @@ public class SurvivalHelper { //Check if same block //Necessary otherwise extra items will be dropped - if (iblockstate1 == newBlockState) { + if (currentBlockState == newBlockState) { return false; } - //TODO 1.14 check what Material.CIRCUITS has become - if (iblockstate1.getMaterial() == Material.BUILDABLE_GLASS && blockIn == Blocks.ANVIL) { + if (currentBlockState.getMaterial() == Material.BUILDABLE_GLASS && blockIn == Blocks.ANVIL) { return true; } //Check quickreplace - if (placer instanceof Player && ModifierSettingsManager.getModifierSettings(((Player) placer)).doQuickReplace()) { + if (placer != null && ModifierSettingsManager.getModifierSettings(((Player) placer)).doQuickReplace()) { return true; } - //TODO 1.13 replaceable - return iblockstate1.getMaterial().isReplaceable() /*&& canPlaceBlockOnSide(world, pos, sidePlacedOn)*/; + return currentBlockState.getMaterial().isReplaceable() /*&& canPlaceBlockOnSide(world, pos, sidePlacedOn)*/; } diff --git a/src/main/java/nl/requios/effortlessbuilding/item/AbstractRandomizerBagItem.java b/src/main/java/nl/requios/effortlessbuilding/item/AbstractRandomizerBagItem.java index aa9eb22..0617705 100644 --- a/src/main/java/nl/requios/effortlessbuilding/item/AbstractRandomizerBagItem.java +++ b/src/main/java/nl/requios/effortlessbuilding/item/AbstractRandomizerBagItem.java @@ -136,7 +136,7 @@ public abstract class AbstractRandomizerBagItem extends Item { //Only place manually if in normal vanilla mode BuildModes.BuildModeEnum buildMode = ModeSettingsManager.getModeSettings(player).getBuildMode(); ModifierSettingsManager.ModifierSettings modifierSettings = ModifierSettingsManager.getModifierSettings(player); - if (buildMode != BuildModes.BuildModeEnum.NORMAL || modifierSettings.doQuickReplace()) { + if (buildMode != BuildModes.BuildModeEnum.DISABLED || modifierSettings.doQuickReplace()) { return InteractionResult.FAIL; } diff --git a/src/main/java/nl/requios/effortlessbuilding/item/DiamondRandomizerBagItem.java b/src/main/java/nl/requios/effortlessbuilding/item/DiamondRandomizerBagItem.java index 069a4ce..b7620b9 100644 --- a/src/main/java/nl/requios/effortlessbuilding/item/DiamondRandomizerBagItem.java +++ b/src/main/java/nl/requios/effortlessbuilding/item/DiamondRandomizerBagItem.java @@ -33,7 +33,7 @@ public class DiamondRandomizerBagItem extends AbstractRandomizerBagItem{ @Override public Component getDisplayName() { - return Component.translatable("effortlessbuilding:diamond_randomizer_bag"); + return Component.translatable("item.effortlessbuilding.diamond_randomizer_bag"); } @Nullable diff --git a/src/main/java/nl/requios/effortlessbuilding/item/GoldenRandomizerBagItem.java b/src/main/java/nl/requios/effortlessbuilding/item/GoldenRandomizerBagItem.java index 1d05c7d..d853f62 100644 --- a/src/main/java/nl/requios/effortlessbuilding/item/GoldenRandomizerBagItem.java +++ b/src/main/java/nl/requios/effortlessbuilding/item/GoldenRandomizerBagItem.java @@ -33,7 +33,7 @@ public class GoldenRandomizerBagItem extends AbstractRandomizerBagItem{ @Override public Component getDisplayName() { - return Component.translatable("effortlessbuilding:golden_randomizer_bag"); + return Component.translatable("item.effortlessbuilding.golden_randomizer_bag"); } @Nullable diff --git a/src/main/java/nl/requios/effortlessbuilding/item/RandomizerBagItem.java b/src/main/java/nl/requios/effortlessbuilding/item/RandomizerBagItem.java index ac41deb..06c7687 100644 --- a/src/main/java/nl/requios/effortlessbuilding/item/RandomizerBagItem.java +++ b/src/main/java/nl/requios/effortlessbuilding/item/RandomizerBagItem.java @@ -33,7 +33,7 @@ public class RandomizerBagItem extends AbstractRandomizerBagItem { @Override public Component getDisplayName() { - return Component.translatable("effortlessbuilding:randomizer_bag"); + return Component.translatable("item.effortlessbuilding.randomizer_bag"); } @Nullable diff --git a/src/main/java/nl/requios/effortlessbuilding/item/ReachUpgrade1Item.java b/src/main/java/nl/requios/effortlessbuilding/item/ReachUpgrade1Item.java index cd78755..8caef7a 100644 --- a/src/main/java/nl/requios/effortlessbuilding/item/ReachUpgrade1Item.java +++ b/src/main/java/nl/requios/effortlessbuilding/item/ReachUpgrade1Item.java @@ -9,7 +9,7 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.network.chat.Component; import net.minecraft.ChatFormatting; import net.minecraft.world.level.Level; -import nl.requios.effortlessbuilding.BuildConfig; +import nl.requios.effortlessbuilding.CommonConfig; import nl.requios.effortlessbuilding.EffortlessBuilding; import nl.requios.effortlessbuilding.buildmodifier.ModifierSettingsManager; import nl.requios.effortlessbuilding.helper.ReachHelper; @@ -63,7 +63,7 @@ public class ReachUpgrade1Item extends Item { @Override public void appendHoverText(ItemStack stack, @Nullable Level world, List tooltip, TooltipFlag flag) { - tooltip.add(Component.literal(ChatFormatting.GRAY + "Consume to increase reach to " + ChatFormatting.BLUE + BuildConfig.reach.maxReachLevel1.get())); + tooltip.add(Component.literal(ChatFormatting.GRAY + "Consume to increase reach to " + ChatFormatting.BLUE + CommonConfig.reach.maxReachLevel1.get())); } } diff --git a/src/main/java/nl/requios/effortlessbuilding/item/ReachUpgrade2Item.java b/src/main/java/nl/requios/effortlessbuilding/item/ReachUpgrade2Item.java index 17fbdce..5ac62ed 100644 --- a/src/main/java/nl/requios/effortlessbuilding/item/ReachUpgrade2Item.java +++ b/src/main/java/nl/requios/effortlessbuilding/item/ReachUpgrade2Item.java @@ -9,14 +9,13 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.network.chat.Component; import net.minecraft.ChatFormatting; import net.minecraft.world.level.Level; -import nl.requios.effortlessbuilding.BuildConfig; +import nl.requios.effortlessbuilding.CommonConfig; import nl.requios.effortlessbuilding.EffortlessBuilding; import nl.requios.effortlessbuilding.buildmodifier.ModifierSettingsManager; import nl.requios.effortlessbuilding.helper.ReachHelper; import javax.annotation.Nullable; import javax.annotation.ParametersAreNonnullByDefault; -import java.awt.*; import java.util.List; import net.minecraft.resources.ResourceLocation; @@ -68,7 +67,7 @@ public class ReachUpgrade2Item extends Item { @Override public void appendHoverText(ItemStack stack, @Nullable Level world, List tooltip, TooltipFlag flag) { - tooltip.add(Component.literal(ChatFormatting.GRAY + "Consume to increase reach to " + ChatFormatting.BLUE + BuildConfig.reach.maxReachLevel2.get())); + tooltip.add(Component.literal(ChatFormatting.GRAY + "Consume to increase reach to " + ChatFormatting.BLUE + CommonConfig.reach.maxReachLevel2.get())); tooltip.add(Component.literal(ChatFormatting.GRAY + "Previous upgrades need to be consumed first")); } } diff --git a/src/main/java/nl/requios/effortlessbuilding/item/ReachUpgrade3Item.java b/src/main/java/nl/requios/effortlessbuilding/item/ReachUpgrade3Item.java index 7aedbc6..2ed5a91 100644 --- a/src/main/java/nl/requios/effortlessbuilding/item/ReachUpgrade3Item.java +++ b/src/main/java/nl/requios/effortlessbuilding/item/ReachUpgrade3Item.java @@ -9,7 +9,7 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.network.chat.Component; import net.minecraft.ChatFormatting; import net.minecraft.world.level.Level; -import nl.requios.effortlessbuilding.BuildConfig; +import nl.requios.effortlessbuilding.CommonConfig; import nl.requios.effortlessbuilding.EffortlessBuilding; import nl.requios.effortlessbuilding.buildmodifier.ModifierSettingsManager; import nl.requios.effortlessbuilding.helper.ReachHelper; @@ -70,7 +70,7 @@ public class ReachUpgrade3Item extends Item { @Override public void appendHoverText(ItemStack stack, @Nullable Level world, List tooltip, TooltipFlag flag) { - tooltip.add(Component.literal(ChatFormatting.GRAY + "Consume to increase reach to " + ChatFormatting.BLUE + BuildConfig.reach.maxReachLevel3.get())); + tooltip.add(Component.literal(ChatFormatting.GRAY + "Consume to increase reach to " + ChatFormatting.BLUE + CommonConfig.reach.maxReachLevel3.get())); tooltip.add(Component.literal(ChatFormatting.GRAY + "Previous upgrades need to be consumed first")); } diff --git a/src/main/java/nl/requios/effortlessbuilding/network/AddUndoMessage.java b/src/main/java/nl/requios/effortlessbuilding/network/AddUndoMessage.java index 4a639b2..4a99723 100644 --- a/src/main/java/nl/requios/effortlessbuilding/network/AddUndoMessage.java +++ b/src/main/java/nl/requios/effortlessbuilding/network/AddUndoMessage.java @@ -14,7 +14,6 @@ import net.minecraftforge.network.NetworkEvent; import nl.requios.effortlessbuilding.EffortlessBuilding; import nl.requios.effortlessbuilding.buildmodifier.BlockSet; import nl.requios.effortlessbuilding.buildmodifier.UndoRedo; -import nl.requios.effortlessbuilding.render.BlockPreviewRenderer; import java.util.ArrayList; import java.util.function.Supplier; diff --git a/src/main/java/nl/requios/effortlessbuilding/network/BlockPlacedMessage.java b/src/main/java/nl/requios/effortlessbuilding/network/BlockPlacedMessage.java index 2d68d31..f588936 100644 --- a/src/main/java/nl/requios/effortlessbuilding/network/BlockPlacedMessage.java +++ b/src/main/java/nl/requios/effortlessbuilding/network/BlockPlacedMessage.java @@ -12,7 +12,7 @@ import net.minecraftforge.fml.DistExecutor; import net.minecraftforge.fml.LogicalSide; import net.minecraftforge.network.NetworkEvent; import nl.requios.effortlessbuilding.buildmode.BuildModes; -import nl.requios.effortlessbuilding.render.BlockPreviewRenderer; +import nl.requios.effortlessbuilding.render.BlockPreviews; import java.util.function.Supplier; @@ -112,7 +112,7 @@ public class BlockPlacedMessage { public static class ClientHandler { public static void handle(BlockPlacedMessage message, Supplier ctx) { //Nod RenderHandler to do the dissolve shader effect - BlockPreviewRenderer.onBlocksPlaced(); + BlockPreviews.onBlocksPlaced(); } } } diff --git a/src/main/java/nl/requios/effortlessbuilding/network/ClearUndoMessage.java b/src/main/java/nl/requios/effortlessbuilding/network/ClearUndoMessage.java index 0bd946c..1358242 100644 --- a/src/main/java/nl/requios/effortlessbuilding/network/ClearUndoMessage.java +++ b/src/main/java/nl/requios/effortlessbuilding/network/ClearUndoMessage.java @@ -9,7 +9,6 @@ import net.minecraftforge.fml.LogicalSide; import net.minecraftforge.network.NetworkEvent; import nl.requios.effortlessbuilding.EffortlessBuilding; import nl.requios.effortlessbuilding.buildmodifier.UndoRedo; -import nl.requios.effortlessbuilding.render.BlockPreviewRenderer; import java.util.function.Supplier; diff --git a/src/main/java/nl/requios/effortlessbuilding/network/RequestLookAtMessage.java b/src/main/java/nl/requios/effortlessbuilding/network/RequestLookAtMessage.java index bd534cd..093dcce 100644 --- a/src/main/java/nl/requios/effortlessbuilding/network/RequestLookAtMessage.java +++ b/src/main/java/nl/requios/effortlessbuilding/network/RequestLookAtMessage.java @@ -9,9 +9,8 @@ import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.fml.DistExecutor; import net.minecraftforge.fml.LogicalSide; import net.minecraftforge.network.NetworkEvent; +import nl.requios.effortlessbuilding.ClientEvents; import nl.requios.effortlessbuilding.EffortlessBuilding; -import nl.requios.effortlessbuilding.proxy.ClientProxy; -import nl.requios.effortlessbuilding.render.BlockPreviewRenderer; import java.util.function.Supplier; @@ -63,8 +62,8 @@ public class RequestLookAtMessage { //Prevent double placing in normal mode with placeStartPos false //Unless QuickReplace is on, then we do need to place start pos. - if (ClientProxy.previousLookAt.getType() == HitResult.Type.BLOCK) { - PacketHandler.INSTANCE.sendToServer(new BlockPlacedMessage((BlockHitResult) ClientProxy.previousLookAt, message.getPlaceStartPos())); + if (ClientEvents.previousLookAt.getType() == HitResult.Type.BLOCK) { + PacketHandler.INSTANCE.sendToServer(new BlockPlacedMessage((BlockHitResult) ClientEvents.previousLookAt, message.getPlaceStartPos())); } else { PacketHandler.INSTANCE.sendToServer(new BlockPlacedMessage()); } diff --git a/src/main/java/nl/requios/effortlessbuilding/proxy/ClientProxy.java b/src/main/java/nl/requios/effortlessbuilding/proxy/ClientProxy.java index 5e5af73..4047301 100644 --- a/src/main/java/nl/requios/effortlessbuilding/proxy/ClientProxy.java +++ b/src/main/java/nl/requios/effortlessbuilding/proxy/ClientProxy.java @@ -1,333 +1,23 @@ package nl.requios.effortlessbuilding.proxy; -import net.minecraft.MethodsReturnNonnullByDefault; -import net.minecraft.client.gui.screens.MenuScreens; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.SoundType; import net.minecraft.client.Minecraft; -import net.minecraft.client.player.LocalPlayer; -import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.resources.language.I18n; -import net.minecraft.client.KeyMapping; -import com.mojang.blaze3d.platform.InputConstants; import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.BlockItem; -import net.minecraft.world.item.ItemStack; -import net.minecraft.world.InteractionHand; -import net.minecraft.sounds.SoundSource; -import net.minecraft.core.BlockPos; -import net.minecraft.world.phys.BlockHitResult; -import net.minecraft.world.level.ClipContext; -import net.minecraft.world.phys.HitResult; -import net.minecraft.world.phys.Vec3; -import net.minecraft.ChatFormatting; -import net.minecraft.world.level.Level; import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.client.event.InputEvent; -import net.minecraftforge.client.event.RegisterKeyMappingsEvent; -import net.minecraftforge.client.event.ScreenEvent; -import net.minecraftforge.client.settings.KeyConflictContext; -import net.minecraftforge.client.settings.KeyModifier; -import net.minecraftforge.event.TickEvent; -import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.fml.LogicalSide; -import net.minecraftforge.fml.common.Mod; -import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; -import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; import net.minecraftforge.network.NetworkEvent; import nl.requios.effortlessbuilding.EffortlessBuilding; -import nl.requios.effortlessbuilding.buildmode.BuildModes; -import nl.requios.effortlessbuilding.buildmode.ModeOptions; -import nl.requios.effortlessbuilding.buildmode.ModeSettingsManager; -import nl.requios.effortlessbuilding.buildmodifier.ModifierSettingsManager; -import nl.requios.effortlessbuilding.compatibility.CompatHelper; -import nl.requios.effortlessbuilding.gui.DiamondRandomizerBagScreen; -import nl.requios.effortlessbuilding.gui.GoldenRandomizerBagScreen; -import nl.requios.effortlessbuilding.gui.RandomizerBagScreen; -import nl.requios.effortlessbuilding.gui.buildmode.PlayerSettingsGui; -import nl.requios.effortlessbuilding.gui.buildmode.RadialMenu; -import nl.requios.effortlessbuilding.gui.buildmodifier.ModifierSettingsGui; -import nl.requios.effortlessbuilding.helper.ReachHelper; -import nl.requios.effortlessbuilding.network.*; -import org.lwjgl.glfw.GLFW; -import javax.annotation.ParametersAreNonnullByDefault; import java.util.function.Supplier; -@Mod.EventBusSubscriber(value = {Dist.CLIENT}) -@ParametersAreNonnullByDefault -@MethodsReturnNonnullByDefault +@OnlyIn(Dist.CLIENT) public class ClientProxy implements IProxy { - public static KeyMapping[] keyBindings; - public static HitResult previousLookAt; - public static HitResult currentLookAt; - public static int ticksInGame = 0; - private static int placeCooldown = 0; - private static int breakCooldown = 0; - - @Override - public void setup(FMLCommonSetupEvent event) { - } - - @Override - public void clientSetup(FMLClientSetupEvent event) { - //Keybindings are setup and registered in ModClientEventHandler - - MenuScreens.register(EffortlessBuilding.RANDOMIZER_BAG_CONTAINER.get(), RandomizerBagScreen::new); - MenuScreens.register(EffortlessBuilding.GOLDEN_RANDOMIZER_BAG_CONTAINER.get(), GoldenRandomizerBagScreen::new); - MenuScreens.register(EffortlessBuilding.DIAMOND_RANDOMIZER_BAG_CONTAINER.get(), DiamondRandomizerBagScreen::new); - } - - @SubscribeEvent - public static void onClientTick(TickEvent.ClientTickEvent event) { - - if (event.phase == TickEvent.Phase.START) { - onMouseInput(); - - //Update previousLookAt - HitResult objectMouseOver = Minecraft.getInstance().hitResult; - //Checking for null is necessary! Even in vanilla when looking down ladders it is occasionally null (instead of Type MISS) - if (objectMouseOver == null) return; - - if (currentLookAt == null) { - currentLookAt = objectMouseOver; - previousLookAt = objectMouseOver; - return; - } - - if (objectMouseOver.getType() == HitResult.Type.BLOCK) { - if (currentLookAt.getType() != HitResult.Type.BLOCK) { - currentLookAt = objectMouseOver; - previousLookAt = objectMouseOver; - } else { - if (((BlockHitResult) currentLookAt).getBlockPos() != ((BlockHitResult) objectMouseOver).getBlockPos()) { - previousLookAt = currentLookAt; - currentLookAt = objectMouseOver; - } - } - } - } else if (event.phase == TickEvent.Phase.END) { - Screen gui = Minecraft.getInstance().screen; - if (gui == null || !gui.isPauseScreen()) { - ticksInGame++; - } - } - - } - - private static void onMouseInput() { - Minecraft mc = Minecraft.getInstance(); - LocalPlayer player = mc.player; - if (player == null) return; - BuildModes.BuildModeEnum buildMode = ModeSettingsManager.getModeSettings(player).getBuildMode(); - - if (mc.screen != null || - buildMode == BuildModes.BuildModeEnum.NORMAL || - RadialMenu.instance.isVisible()) { - return; - } - - if (mc.options.keyUse.isDown()) { - - //KeyBinding.setKeyBindState(mc.gameSettings.keyBindUseItem.getKeyCode(), false); - - if (placeCooldown <= 0) { - placeCooldown = 4; - - ItemStack currentItemStack = player.getItemInHand(InteractionHand.MAIN_HAND); - if (currentItemStack.getItem() instanceof BlockItem || - (CompatHelper.isItemBlockProxy(currentItemStack) && !player.isShiftKeyDown())) { - - ItemStack itemStack = CompatHelper.getItemBlockFromStack(currentItemStack); - - //find position in distance - HitResult lookingAt = getLookingAt(player); - if (lookingAt != null && lookingAt.getType() == HitResult.Type.BLOCK) { - BlockHitResult blockLookingAt = (BlockHitResult) lookingAt; - - BuildModes.onBlockPlacedMessage(player, new BlockPlacedMessage(blockLookingAt, true)); - PacketHandler.INSTANCE.sendToServer(new BlockPlacedMessage(blockLookingAt, true)); - - //play sound if further than normal - if ((blockLookingAt.getLocation().subtract(player.getEyePosition(1f))).lengthSqr() > 25f && - itemStack.getItem() instanceof BlockItem) { - - BlockState state = ((BlockItem) itemStack.getItem()).getBlock().defaultBlockState(); - BlockPos blockPos = blockLookingAt.getBlockPos(); - SoundType soundType = state.getBlock().getSoundType(state, player.level, blockPos, player); - player.level.playSound(player, player.blockPosition(), soundType.getPlaceSound(), SoundSource.BLOCKS, - 0.4f, soundType.getPitch()); - player.swing(InteractionHand.MAIN_HAND); - } - } else { - BuildModes.onBlockPlacedMessage(player, new BlockPlacedMessage()); - PacketHandler.INSTANCE.sendToServer(new BlockPlacedMessage()); - } - } - } else if (buildMode == BuildModes.BuildModeEnum.NORMAL_PLUS) { - placeCooldown--; - if (ModeOptions.getBuildSpeed() == ModeOptions.ActionEnum.FAST_SPEED) placeCooldown = 0; - } - } else { - placeCooldown = 0; - } - - if (mc.options.keyAttack.isDown()) { - - //Break block in distance in creative (or survival if enabled in config) - if (breakCooldown <= 0) { - breakCooldown = 4; - - HitResult lookingAt = getLookingAt(player); - if (lookingAt != null && lookingAt.getType() == HitResult.Type.BLOCK) { - BlockHitResult blockLookingAt = (BlockHitResult) lookingAt; - - BuildModes.onBlockBrokenMessage(player, new BlockBrokenMessage(blockLookingAt)); - PacketHandler.INSTANCE.sendToServer(new BlockBrokenMessage(blockLookingAt)); - - //play sound if further than normal - if ((blockLookingAt.getLocation().subtract(player.getEyePosition(1f))).lengthSqr() > 25f) { - - BlockPos blockPos = blockLookingAt.getBlockPos(); - BlockState state = player.level.getBlockState(blockPos); - SoundType soundtype = state.getBlock().getSoundType(state, player.level, blockPos, player); - player.level.playSound(player, player.blockPosition(), soundtype.getBreakSound(), SoundSource.BLOCKS, - 0.4f, soundtype.getPitch()); - player.swing(InteractionHand.MAIN_HAND); - } - } else { - BuildModes.onBlockBrokenMessage(player, new BlockBrokenMessage()); - PacketHandler.INSTANCE.sendToServer(new BlockBrokenMessage()); - } - } else if (buildMode == BuildModes.BuildModeEnum.NORMAL_PLUS) { - breakCooldown--; - if (ModeOptions.getBuildSpeed() == ModeOptions.ActionEnum.FAST_SPEED) breakCooldown = 0; - } - - //EffortlessBuilding.packetHandler.sendToServer(new CancelModeMessage()); - - } else { - breakCooldown = 0; - } - } - - @SubscribeEvent(receiveCanceled = true) - public static void onKeyPress(InputEvent.Key event) { - LocalPlayer player = Minecraft.getInstance().player; - if (player == null) - return; - - //Remember to send packet to server if necessary - //Show Modifier Settings GUI - if (keyBindings[0].consumeClick()) { - openModifierSettings(); - } - - //QuickReplace toggle - if (keyBindings[1].consumeClick()) { - ModifierSettingsManager.ModifierSettings modifierSettings = ModifierSettingsManager.getModifierSettings(player); - modifierSettings.setQuickReplace(!modifierSettings.doQuickReplace()); - EffortlessBuilding.log(player, "Set " + ChatFormatting.GOLD + "Quick Replace " + ChatFormatting.RESET + ( - modifierSettings.doQuickReplace() ? "on" : "off")); - PacketHandler.INSTANCE.sendToServer(new ModifierSettingsMessage(modifierSettings)); - } - - //Radial menu - if (keyBindings[2].isDown()) { - if (ReachHelper.getMaxReach(player) > 0) { - if (!RadialMenu.instance.isVisible()) { - Minecraft.getInstance().setScreen(RadialMenu.instance); - } - } else { - EffortlessBuilding.log(player, "Build modes are disabled until your reach has increased. Increase your reach with craftable reach upgrades."); - } - } - - //Undo (Ctrl+Z) - if (keyBindings[3].consumeClick()) { - ModeOptions.ActionEnum action = ModeOptions.ActionEnum.UNDO; - ModeOptions.performAction(player, action); - PacketHandler.INSTANCE.sendToServer(new ModeActionMessage(action)); - } - - //Redo (Ctrl+Y) - if (keyBindings[4].consumeClick()) { - ModeOptions.ActionEnum action = ModeOptions.ActionEnum.REDO; - ModeOptions.performAction(player, action); - PacketHandler.INSTANCE.sendToServer(new ModeActionMessage(action)); - } - - //Change placement mode - if (keyBindings[5].consumeClick()) { - //Toggle between first two actions of the first option of the current build mode - BuildModes.BuildModeEnum currentBuildMode = ModeSettingsManager.getModeSettings(player).getBuildMode(); - if (currentBuildMode.options.length > 0) { - ModeOptions.OptionEnum option = currentBuildMode.options[0]; - if (option.actions.length >= 2) { - if (ModeOptions.getOptionSetting(option) == option.actions[0]) { - ModeOptions.performAction(player, option.actions[1]); - PacketHandler.INSTANCE.sendToServer(new ModeActionMessage(option.actions[1])); - } else { - ModeOptions.performAction(player, option.actions[0]); - PacketHandler.INSTANCE.sendToServer(new ModeActionMessage(option.actions[0])); - } - } - } - } - - } - - public static void openModifierSettings() { - Minecraft mc = Minecraft.getInstance(); - LocalPlayer player = mc.player; - if (player == null) return; - - //Disabled if max reach is 0, might be set in the config that way. - if (ReachHelper.getMaxReach(player) == 0) { - EffortlessBuilding.log(player, "Build modifiers are disabled until your reach has increased. Increase your reach with craftable reach upgrades."); - } else { - mc.setScreen(new ModifierSettingsGui()); - } - } - - public static void openPlayerSettings() { - Minecraft mc = Minecraft.getInstance(); - mc.setScreen(new PlayerSettingsGui()); - } - - @SubscribeEvent - public static void onGuiOpen(ScreenEvent event) { - Player player = Minecraft.getInstance().player; - if (player != null) { - BuildModes.initializeMode(player); - } - } - - public static boolean isKeybindDown(int keybindIndex) { - return InputConstants.isKeyDown( - Minecraft.getInstance().getWindow().getWindow(), - ClientProxy.keyBindings[2].getKey().getValue()); - } - - public static HitResult getLookingAt(Player player) { - Level world = player.level; - - //base distance off of player ability (config) - float raytraceRange = ReachHelper.getPlacementReach(player); - - Vec3 look = player.getLookAngle(); - Vec3 start = new Vec3(player.getX(), player.getY() + player.getEyeHeight(), player.getZ()); - Vec3 end = new Vec3(player.getX() + look.x * raytraceRange, player.getY() + player.getEyeHeight() + look.y * raytraceRange, player.getZ() + look.z * raytraceRange); -// return player.rayTrace(raytraceRange, 1f, RayTraceFluidMode.NEVER); - //TODO 1.14 check if correct - return world.clip(new ClipContext(start, end, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, player)); - } public Player getPlayerEntityFromContext(Supplier ctx) { return (ctx.get().getDirection().getReceptionSide() == LogicalSide.CLIENT ? Minecraft.getInstance().player : ctx.get().getSender()); } - @Override public void logTranslate(Player player, String prefix, String translationKey, String suffix, boolean actionBar) { EffortlessBuilding.log(Minecraft.getInstance().player, prefix + I18n.get(translationKey) + suffix, actionBar); } diff --git a/src/main/java/nl/requios/effortlessbuilding/proxy/IProxy.java b/src/main/java/nl/requios/effortlessbuilding/proxy/IProxy.java index e898373..189e1ff 100644 --- a/src/main/java/nl/requios/effortlessbuilding/proxy/IProxy.java +++ b/src/main/java/nl/requios/effortlessbuilding/proxy/IProxy.java @@ -1,17 +1,11 @@ package nl.requios.effortlessbuilding.proxy; import net.minecraft.world.entity.player.Player; -import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; -import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; import net.minecraftforge.network.NetworkEvent; import java.util.function.Supplier; public interface IProxy { - void setup(final FMLCommonSetupEvent event); - - void clientSetup(final FMLClientSetupEvent event); - Player getPlayerEntityFromContext(Supplier ctx); void logTranslate(Player player, String prefix, String translationKey, String suffix, boolean actionBar); diff --git a/src/main/java/nl/requios/effortlessbuilding/proxy/ServerProxy.java b/src/main/java/nl/requios/effortlessbuilding/proxy/ServerProxy.java index 6c47894..71f6024 100644 --- a/src/main/java/nl/requios/effortlessbuilding/proxy/ServerProxy.java +++ b/src/main/java/nl/requios/effortlessbuilding/proxy/ServerProxy.java @@ -1,9 +1,7 @@ package nl.requios.effortlessbuilding.proxy; -import net.minecraft.world.entity.player.Player; import net.minecraft.server.level.ServerPlayer; -import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; -import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; +import net.minecraft.world.entity.player.Player; import net.minecraftforge.network.NetworkEvent; import net.minecraftforge.network.PacketDistributor; import nl.requios.effortlessbuilding.network.PacketHandler; @@ -13,20 +11,11 @@ import java.util.function.Supplier; public class ServerProxy implements IProxy { //Only physical server! Singleplayer server is seen as clientproxy - @Override - public void setup(FMLCommonSetupEvent event) { - - } - - @Override - public void clientSetup(FMLClientSetupEvent event) { - } public Player getPlayerEntityFromContext(Supplier ctx) { return ctx.get().getSender(); } - @Override public void logTranslate(Player player, String prefix, String translationKey, String suffix, boolean actionBar) { PacketHandler.INSTANCE.send(PacketDistributor.PLAYER.with(() -> (ServerPlayer) player), new TranslatedLogMessage(prefix, translationKey, suffix, actionBar)); } diff --git a/src/main/java/nl/requios/effortlessbuilding/render/BlockPreviewRenderer.java b/src/main/java/nl/requios/effortlessbuilding/render/BlockPreviewRenderer.java deleted file mode 100644 index 9122648..0000000 --- a/src/main/java/nl/requios/effortlessbuilding/render/BlockPreviewRenderer.java +++ /dev/null @@ -1,384 +0,0 @@ -package nl.requios.effortlessbuilding.render; - -import com.mojang.blaze3d.vertex.PoseStack; -import com.mojang.blaze3d.vertex.VertexConsumer; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.level.block.SoundType; -import net.minecraft.client.Minecraft; -import net.minecraft.client.player.LocalPlayer; -import net.minecraft.client.renderer.block.BlockRenderDispatcher; -import net.minecraft.client.renderer.MultiBufferSource; -import net.minecraft.world.entity.player.Player; -import net.minecraft.world.item.ItemStack; -import net.minecraft.core.Direction; -import net.minecraft.sounds.SoundSource; -import net.minecraft.core.BlockPos; -import net.minecraft.world.phys.BlockHitResult; -import net.minecraft.util.Mth; -import net.minecraft.world.phys.HitResult; -import net.minecraft.world.phys.shapes.VoxelShape; -import net.minecraft.world.phys.Vec3; -import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.api.distmarker.OnlyIn; -import nl.requios.effortlessbuilding.BuildConfig; -import nl.requios.effortlessbuilding.EffortlessBuilding; -import nl.requios.effortlessbuilding.buildmode.BuildModes; -import nl.requios.effortlessbuilding.buildmode.IBuildMode; -import nl.requios.effortlessbuilding.buildmode.ModeSettingsManager; -import nl.requios.effortlessbuilding.buildmode.ModeSettingsManager.ModeSettings; -import nl.requios.effortlessbuilding.buildmodifier.BuildModifiers; -import nl.requios.effortlessbuilding.buildmodifier.ModifierSettingsManager; -import nl.requios.effortlessbuilding.buildmodifier.ModifierSettingsManager.ModifierSettings; -import nl.requios.effortlessbuilding.compatibility.CompatHelper; -import nl.requios.effortlessbuilding.helper.ReachHelper; -import nl.requios.effortlessbuilding.helper.SurvivalHelper; -import nl.requios.effortlessbuilding.item.AbstractRandomizerBagItem; -import nl.requios.effortlessbuilding.proxy.ClientProxy; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -@OnlyIn(Dist.CLIENT) -public class BlockPreviewRenderer { - private static final List placedDataList = new ArrayList<>(); - private static List previousCoordinates; - private static List previousBlockStates; - private static List previousItemStacks; - private static BlockPos previousFirstPos; - private static BlockPos previousSecondPos; - private static int soundTime = 0; - - public static void render(PoseStack matrixStack, MultiBufferSource.BufferSource renderTypeBuffer, Player player, ModifierSettings modifierSettings, ModeSettings modeSettings) { - - //Render placed blocks with dissolve effect - //Use fancy shader if config allows, otherwise no dissolve - if (BuildConfig.visuals.useShaders.get()) { - for (int i = 0; i < placedDataList.size(); i++) { - PlacedData placed = placedDataList.get(i); - if (placed.coordinates != null && !placed.coordinates.isEmpty()) { - - double totalTime = Mth.clampedLerp(30, 60, placed.firstPos.distSqr(placed.secondPos) / 100.0) * BuildConfig.visuals.dissolveTimeMultiplier.get(); - float dissolve = (ClientProxy.ticksInGame - placed.time) / (float) totalTime; - renderBlockPreviews(matrixStack, renderTypeBuffer, placed.coordinates, placed.blockStates, placed.itemStacks, dissolve, placed.firstPos, placed.secondPos, false, placed.breaking); - } - } - } - //Expire - placedDataList.removeIf(placed -> { - double totalTime = Mth.clampedLerp(30, 60, placed.firstPos.distSqr(placed.secondPos) / 100.0) * BuildConfig.visuals.dissolveTimeMultiplier.get(); - return placed.time + totalTime < ClientProxy.ticksInGame; - }); - - //Render block previews - HitResult lookingAt = ClientProxy.getLookingAt(player); - if (modeSettings.getBuildMode() == BuildModes.BuildModeEnum.NORMAL) - lookingAt = Minecraft.getInstance().hitResult; - - ItemStack mainhand = player.getMainHandItem(); - boolean noBlockInHand = !(!mainhand.isEmpty() && CompatHelper.isItemBlockProxy(mainhand)); - - BlockPos startPos = null; - Direction sideHit = null; - Vec3 hitVec = null; - - //Checking for null is necessary! Even in vanilla when looking down ladders it is occasionally null (instead of Type MISS) - if (lookingAt != null && lookingAt.getType() == HitResult.Type.BLOCK) { - BlockHitResult blockLookingAt = (BlockHitResult) lookingAt; - startPos = blockLookingAt.getBlockPos(); - - //Check if tool (or none) in hand - //TODO 1.13 replaceable - boolean replaceable = player.level.getBlockState(startPos).getMaterial().isReplaceable(); - boolean becomesDoubleSlab = SurvivalHelper.doesBecomeDoubleSlab(player, startPos, blockLookingAt.getDirection()); - if (!modifierSettings.doQuickReplace() && !noBlockInHand && !replaceable && !becomesDoubleSlab) { - startPos = startPos.relative(blockLookingAt.getDirection()); - } - - //Get under tall grass and other replaceable blocks - if (modifierSettings.doQuickReplace() && !noBlockInHand && replaceable) { - startPos = startPos.below(); - } - - sideHit = blockLookingAt.getDirection(); - hitVec = blockLookingAt.getLocation(); - } - - //Dont render if in normal mode and modifiers are disabled - //Unless alwaysShowBlockPreview is true in config - if (doRenderBlockPreviews(modifierSettings, modeSettings, startPos)) { - - //Keep blockstate the same for every block in the buildmode - //So dont rotate blocks when in the middle of placing wall etc. - if (BuildModes.isActive(player)) { - IBuildMode buildModeInstance = modeSettings.getBuildMode().instance; - if (buildModeInstance.getSideHit(player) != null) sideHit = buildModeInstance.getSideHit(player); - if (buildModeInstance.getHitVec(player) != null) hitVec = buildModeInstance.getHitVec(player); - } - - if (sideHit != null) { - - //Should be red? - boolean breaking = BuildModes.currentlyBreakingClient.get(player) != null && BuildModes.currentlyBreakingClient.get(player); - - //get coordinates - List startCoordinates = BuildModes.findCoordinates(player, startPos, breaking || modifierSettings.doQuickReplace()); - - //Remember first and last point for the shader - BlockPos firstPos = BlockPos.ZERO, secondPos = BlockPos.ZERO; - if (!startCoordinates.isEmpty()) { - firstPos = startCoordinates.get(0); - secondPos = startCoordinates.get(startCoordinates.size() - 1); - } - - //Limit number of blocks you can place - int limit = ReachHelper.getMaxBlocksPlacedAtOnce(player); - if (startCoordinates.size() > limit) { - startCoordinates = startCoordinates.subList(0, limit); - } - - List newCoordinates = BuildModifiers.findCoordinates(player, startCoordinates); - - sortOnDistanceToPlayer(newCoordinates, player); - - hitVec = new Vec3(Math.abs(hitVec.x - ((int) hitVec.x)), Math.abs(hitVec.y - ((int) hitVec.y)), - Math.abs(hitVec.z - ((int) hitVec.z))); - - //Get blockstates - List itemStacks = new ArrayList<>(); - List blockStates = new ArrayList<>(); - if (breaking) { - //Find blockstate of world - for (BlockPos coordinate : newCoordinates) { - blockStates.add(player.level.getBlockState(coordinate)); - } - } else { - blockStates = BuildModifiers.findBlockStates(player, startCoordinates, hitVec, sideHit, itemStacks); - } - - - //Check if they are different from previous - //TODO fix triggering when moving player - if (!BuildModifiers.compareCoordinates(previousCoordinates, newCoordinates)) { - previousCoordinates = newCoordinates; - //remember the rest for placed blocks - previousBlockStates = blockStates; - previousItemStacks = itemStacks; - previousFirstPos = firstPos; - previousSecondPos = secondPos; - - //if so, renew randomness of randomizer bag - AbstractRandomizerBagItem.renewRandomness(); - //and play sound (max once every tick) - if (newCoordinates.size() > 1 && blockStates.size() > 1 && soundTime < ClientProxy.ticksInGame - 0) { - soundTime = ClientProxy.ticksInGame; - - if (blockStates.get(0) != null) { - SoundType soundType = blockStates.get(0).getBlock().getSoundType(blockStates.get(0), player.level, - newCoordinates.get(0), player); - player.level.playSound(player, player.blockPosition(), breaking ? soundType.getBreakSound() : soundType.getPlaceSound(), - SoundSource.BLOCKS, 0.3f, 0.8f); - } - } - } - - //Render block previews - if (blockStates.size() != 0 && newCoordinates.size() == blockStates.size()) { - int blockCount; - - //Use fancy shader if config allows, otherwise outlines - if (BuildConfig.visuals.useShaders.get() && newCoordinates.size() < BuildConfig.visuals.shaderThreshold.get()) { - blockCount = renderBlockPreviews(matrixStack, renderTypeBuffer, newCoordinates, blockStates, itemStacks, 0f, firstPos, secondPos, !breaking, breaking); - } else { - VertexConsumer buffer = RenderHandler.beginLines(renderTypeBuffer); - - Vec3 color = new Vec3(1f, 1f, 1f); - if (breaking) color = new Vec3(1f, 0f, 0f); - - for (int i = newCoordinates.size() - 1; i >= 0; i--) { - VoxelShape collisionShape = blockStates.get(i).getCollisionShape(player.level, newCoordinates.get(i)); - RenderHandler.renderBlockOutline(matrixStack, buffer, newCoordinates.get(i), collisionShape, color); - } - - RenderHandler.endLines(renderTypeBuffer); - - blockCount = newCoordinates.size(); - } - - //Display block count and dimensions in actionbar - if (BuildModes.isActive(player)) { - - //Find min and max values (not simply firstPos and secondPos because that doesn't work with circles) - int minX = Integer.MAX_VALUE, maxX = Integer.MIN_VALUE; - int minY = Integer.MAX_VALUE, maxY = Integer.MIN_VALUE; - int minZ = Integer.MAX_VALUE, maxZ = Integer.MIN_VALUE; - for (BlockPos pos : startCoordinates) { - if (pos.getX() < minX) minX = pos.getX(); - if (pos.getX() > maxX) maxX = pos.getX(); - if (pos.getY() < minY) minY = pos.getY(); - if (pos.getY() > maxY) maxY = pos.getY(); - if (pos.getZ() < minZ) minZ = pos.getZ(); - if (pos.getZ() > maxZ) maxZ = pos.getZ(); - } - BlockPos dim = new BlockPos(maxX - minX + 1, maxY - minY + 1, maxZ - minZ + 1); - - String dimensions = "("; - if (dim.getX() > 1) dimensions += dim.getX() + "x"; - if (dim.getZ() > 1) dimensions += dim.getZ() + "x"; - if (dim.getY() > 1) dimensions += dim.getY() + "x"; - dimensions = dimensions.substring(0, dimensions.length() - 1); - if (dimensions.length() > 1) dimensions += ")"; - - EffortlessBuilding.log(player, blockCount + " blocks " + dimensions, true); - } - } - - - } - - //Draw outlines if no block in hand - //Find proper raytrace: either normal range or increased range depending on canBreakFar - VertexConsumer buffer = RenderHandler.beginLines(renderTypeBuffer); - HitResult objectMouseOver = Minecraft.getInstance().hitResult; - HitResult breakingRaytrace = ReachHelper.canBreakFar(player) ? lookingAt : objectMouseOver; - - if (player.isCreative() && noBlockInHand && breakingRaytrace != null && breakingRaytrace.getType() == HitResult.Type.BLOCK) { - BlockHitResult blockBreakingRaytrace = (BlockHitResult) breakingRaytrace; - List breakCoordinates = BuildModifiers.findCoordinates(player, blockBreakingRaytrace.getBlockPos()); - - //Only render first outline if further than normal reach - boolean excludeFirst = objectMouseOver != null && objectMouseOver.getType() == HitResult.Type.BLOCK; - for (int i = excludeFirst ? 1 : 0; i < breakCoordinates.size(); i++) { - BlockPos coordinate = breakCoordinates.get(i); - - BlockState blockState = player.level.getBlockState(coordinate); - if (!blockState.isAir()) { - if (SurvivalHelper.canBreak(player.level, player, coordinate) || i == 0) { - VoxelShape collisionShape = blockState.getCollisionShape(player.level, coordinate); - RenderHandler.renderBlockOutline(matrixStack, buffer, coordinate, collisionShape, new Vec3(0f, 0f, 0f)); - } - } - } - } - RenderHandler.endLines(renderTypeBuffer); - } - } - - //Whether to draw any block previews or outlines - public static boolean doRenderBlockPreviews(ModifierSettings modifierSettings, ModeSettings modeSettings, BlockPos startPos) { - return modeSettings.getBuildMode() != BuildModes.BuildModeEnum.NORMAL || - (startPos != null && BuildModifiers.isEnabled(modifierSettings, startPos)) || - BuildConfig.visuals.alwaysShowBlockPreview.get(); - } - - protected static int renderBlockPreviews(PoseStack matrixStack, MultiBufferSource.BufferSource renderTypeBuffer, List coordinates, List blockStates, - List itemStacks, float dissolve, BlockPos firstPos, - BlockPos secondPos, boolean checkCanPlace, boolean red) { - Player player = Minecraft.getInstance().player; - ModifierSettings modifierSettings = ModifierSettingsManager.getModifierSettings(player); - BlockRenderDispatcher dispatcher = Minecraft.getInstance().getBlockRenderer(); - int blocksValid = 0; - - if (coordinates.isEmpty()) return blocksValid; - - for (int i = coordinates.size() - 1; i >= 0; i--) { - BlockPos blockPos = coordinates.get(i); - BlockState blockState = blockStates.get(i); - ItemStack itemstack = itemStacks.isEmpty() ? ItemStack.EMPTY : itemStacks.get(i); - if (CompatHelper.isItemBlockProxy(itemstack)) - itemstack = CompatHelper.getItemBlockByState(itemstack, blockState); - - //Check if can place - //If check is turned off, check if blockstate is the same (for dissolve effect) - if ((!checkCanPlace /*&& player.world.getNewBlockState(blockPos) == blockState*/) || //TODO enable (breaks the breaking shader) - SurvivalHelper.canPlace(player.level, player, blockPos, blockState, itemstack, modifierSettings.doQuickReplace(), Direction.UP)) { - - RenderHandler.renderBlockPreview(matrixStack, renderTypeBuffer, dispatcher, blockPos, blockState, dissolve, firstPos, secondPos, red); - blocksValid++; - } - } - return blocksValid; - } - - public static void onBlocksPlaced() { - onBlocksPlaced(previousCoordinates, previousItemStacks, previousBlockStates, previousFirstPos, previousSecondPos); - } - - public static void onBlocksPlaced(List coordinates, List itemStacks, List blockStates, - BlockPos firstPos, BlockPos secondPos) { - LocalPlayer player = Minecraft.getInstance().player; - ModifierSettings modifierSettings = ModifierSettingsManager.getModifierSettings(player); - ModeSettings modeSettings = ModeSettingsManager.getModeSettings(player); - - //Check if block previews are enabled - if (doRenderBlockPreviews(modifierSettings, modeSettings, firstPos)) { - - //Save current coordinates, blockstates and itemstacks - if (!coordinates.isEmpty() && blockStates.size() == coordinates.size() && - coordinates.size() > 1 && coordinates.size() < BuildConfig.visuals.shaderThreshold.get()) { - - placedDataList.add(new PlacedData(ClientProxy.ticksInGame, coordinates, blockStates, - itemStacks, firstPos, secondPos, false)); - } - } - - } - - public static void onBlocksBroken() { - onBlocksBroken(previousCoordinates, previousItemStacks, previousBlockStates, previousFirstPos, previousSecondPos); - } - - public static void onBlocksBroken(List coordinates, List itemStacks, List blockStates, - BlockPos firstPos, BlockPos secondPos) { - LocalPlayer player = Minecraft.getInstance().player; - ModifierSettings modifierSettings = ModifierSettingsManager.getModifierSettings(player); - ModeSettings modeSettings = ModeSettingsManager.getModeSettings(player); - - //Check if block previews are enabled - if (doRenderBlockPreviews(modifierSettings, modeSettings, firstPos)) { - - //Save current coordinates, blockstates and itemstacks - if (!coordinates.isEmpty() && blockStates.size() == coordinates.size() && - coordinates.size() > 1 && coordinates.size() < BuildConfig.visuals.shaderThreshold.get()) { - - sortOnDistanceToPlayer(coordinates, player); - - placedDataList.add(new PlacedData(ClientProxy.ticksInGame, coordinates, blockStates, - itemStacks, firstPos, secondPos, true)); - } - } - - } - - private static void sortOnDistanceToPlayer(List coordinates, Player player) { - - Collections.sort(coordinates, (lhs, rhs) -> { - // -1 - less than, 1 - greater than, 0 - equal - double lhsDistanceToPlayer = Vec3.atLowerCornerOf(lhs).subtract(player.getEyePosition(1f)).lengthSqr(); - double rhsDistanceToPlayer = Vec3.atLowerCornerOf(rhs).subtract(player.getEyePosition(1f)).lengthSqr(); - return (int) Math.signum(lhsDistanceToPlayer - rhsDistanceToPlayer); - }); - - } - - static class PlacedData { - float time; - List coordinates; - List blockStates; - List itemStacks; - BlockPos firstPos; - BlockPos secondPos; - boolean breaking; - - public PlacedData(float time, List coordinates, List blockStates, - List itemStacks, BlockPos firstPos, BlockPos secondPos, boolean breaking) { - this.time = time; - this.coordinates = coordinates; - this.blockStates = blockStates; - this.itemStacks = itemStacks; - this.firstPos = firstPos; - this.secondPos = secondPos; - this.breaking = breaking; - } - } -} diff --git a/src/main/java/nl/requios/effortlessbuilding/render/BlockPreviews.java b/src/main/java/nl/requios/effortlessbuilding/render/BlockPreviews.java new file mode 100644 index 0000000..78ee7dc --- /dev/null +++ b/src/main/java/nl/requios/effortlessbuilding/render/BlockPreviews.java @@ -0,0 +1,432 @@ +package nl.requios.effortlessbuilding.render; + +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.SoundType; +import net.minecraft.client.Minecraft; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.core.Direction; +import net.minecraft.sounds.SoundSource; +import net.minecraft.core.BlockPos; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.util.Mth; +import net.minecraft.world.phys.HitResult; +import net.minecraft.world.phys.Vec3; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import nl.requios.effortlessbuilding.ClientConfig; +import nl.requios.effortlessbuilding.ClientEvents; +import nl.requios.effortlessbuilding.CommonConfig; +import nl.requios.effortlessbuilding.EffortlessBuilding; +import nl.requios.effortlessbuilding.buildmode.BuildModes; +import nl.requios.effortlessbuilding.buildmode.IBuildMode; +import nl.requios.effortlessbuilding.buildmode.ModeSettingsManager; +import nl.requios.effortlessbuilding.buildmode.ModeSettingsManager.ModeSettings; +import nl.requios.effortlessbuilding.buildmodifier.BuildModifiers; +import nl.requios.effortlessbuilding.buildmodifier.ModifierSettingsManager; +import nl.requios.effortlessbuilding.buildmodifier.ModifierSettingsManager.ModifierSettings; +import nl.requios.effortlessbuilding.compatibility.CompatHelper; +import nl.requios.effortlessbuilding.create.AllSpecialTextures; +import nl.requios.effortlessbuilding.create.CreateClient; +import nl.requios.effortlessbuilding.create.foundation.utility.Color; +import nl.requios.effortlessbuilding.create.foundation.utility.VecHelper; +import nl.requios.effortlessbuilding.helper.ReachHelper; +import nl.requios.effortlessbuilding.helper.SurvivalHelper; +import nl.requios.effortlessbuilding.item.AbstractRandomizerBagItem; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@OnlyIn(Dist.CLIENT) +public class BlockPreviews { + private static final List placedDataList = new ArrayList<>(); + private static List previousCoordinates; + private static List previousBlockStates; + private static List previousItemStacks; + private static BlockPos previousFirstPos; + private static BlockPos previousSecondPos; + private static int soundTime = 0; + + public static void drawPlacedBlocks(Player player, ModifierSettings modifierSettings) { + //Render placed blocks with appear animation + if (ClientConfig.visuals.showBlockPreviews.get()) { + for (PlacedData placed : placedDataList) { + if (placed.coordinates != null && !placed.coordinates.isEmpty()) { + + int totalTime = placed.breaking ? CommonConfig.visuals.breakAnimationLength.get() : CommonConfig.visuals.appearAnimationLength.get(); + if (totalTime <= 0) continue; + + float dissolve = (ClientEvents.ticksInGame - placed.time) / (float) totalTime; + renderBlockPreviews(player, modifierSettings, placed.coordinates, placed.blockStates, placed.itemStacks, dissolve, placed.firstPos, placed.secondPos, false, placed.breaking); + } + } + } + + //Expire + placedDataList.removeIf(placed -> { + int totalTime = placed.breaking ? CommonConfig.visuals.breakAnimationLength.get() : CommonConfig.visuals.appearAnimationLength.get(); + return placed.time + totalTime < ClientEvents.ticksInGame; + }); + } + + public static void drawLookAtPreview(Player player, ModeSettings modeSettings, ModifierSettings modifierSettings, BlockPos startPos, Direction sideHit, Vec3 hitVec) { + if (!doShowBlockPreviews(modifierSettings, modeSettings, startPos)) return; + + //Keep blockstate the same for every block in the buildmode + //So dont rotate blocks when in the middle of placing wall etc. + if (BuildModes.isActive(player)) { + IBuildMode buildModeInstance = modeSettings.getBuildMode().instance; + if (buildModeInstance.getSideHit(player) != null) sideHit = buildModeInstance.getSideHit(player); + if (buildModeInstance.getHitVec(player) != null) hitVec = buildModeInstance.getHitVec(player); + } + + if (sideHit == null) return; + + //Should be red? + boolean breaking = BuildModes.currentlyBreakingClient.get(player) != null && BuildModes.currentlyBreakingClient.get(player); + + //get coordinates + List startCoordinates = BuildModes.findCoordinates(player, startPos, breaking || modifierSettings.doQuickReplace()); + + //Remember first and last point for the shader + BlockPos firstPos = BlockPos.ZERO, secondPos = BlockPos.ZERO; + if (!startCoordinates.isEmpty()) { + firstPos = startCoordinates.get(0); + secondPos = startCoordinates.get(startCoordinates.size() - 1); + } + + //Limit number of blocks you can place + int limit = ReachHelper.getMaxBlocksPlacedAtOnce(player); + if (startCoordinates.size() > limit) { + startCoordinates = startCoordinates.subList(0, limit); + } + + List newCoordinates = BuildModifiers.findCoordinates(player, startCoordinates); + + sortOnDistanceToPlayer(newCoordinates, player); + + hitVec = new Vec3(Math.abs(hitVec.x - ((int) hitVec.x)), Math.abs(hitVec.y - ((int) hitVec.y)), + Math.abs(hitVec.z - ((int) hitVec.z))); + + //Get blockstates + List itemStacks = new ArrayList<>(); + List blockStates = new ArrayList<>(); + if (breaking) { + //Find blockstate of world + for (BlockPos coordinate : newCoordinates) { + blockStates.add(player.level.getBlockState(coordinate)); + } + } else { + blockStates = BuildModifiers.findBlockStates(player, startCoordinates, hitVec, sideHit, itemStacks); + } + + + //Check if they are different from previous + //TODO fix triggering when moving player + if (!BuildModifiers.compareCoordinates(previousCoordinates, newCoordinates)) { + previousCoordinates = newCoordinates; + //remember the rest for placed blocks + previousBlockStates = blockStates; + previousItemStacks = itemStacks; + previousFirstPos = firstPos; + previousSecondPos = secondPos; + + //if so, renew randomness of randomizer bag + AbstractRandomizerBagItem.renewRandomness(); + //and play sound (max once every tick) + if (newCoordinates.size() > 1 && blockStates.size() > 1 && soundTime < ClientEvents.ticksInGame - 0) { + soundTime = ClientEvents.ticksInGame; + + if (blockStates.get(0) != null) { + SoundType soundType = blockStates.get(0).getBlock().getSoundType(blockStates.get(0), player.level, + newCoordinates.get(0), player); + player.level.playSound(player, player.blockPosition(), breaking ? soundType.getBreakSound() : soundType.getPlaceSound(), + SoundSource.BLOCKS, 0.3f, 0.8f); + } + } + } + + if (blockStates.size() == 0 || newCoordinates.size() != blockStates.size()) return; + + int blockCount; + + Object outlineID = firstPos; + //Dont fade out the outline if we are still determining where to place + //Every outline with same ID will not fade out (because it gets replaced) + if (newCoordinates.size() == 1 || BuildModifiers.isEnabled(modifierSettings, firstPos)) outlineID = "single"; + + if (!breaking) { + //Use fancy shader if config allows, otherwise outlines + if (ClientConfig.visuals.showBlockPreviews.get() && newCoordinates.size() < ClientConfig.visuals.maxBlockPreviews.get()) { + blockCount = renderBlockPreviews(player, modifierSettings, newCoordinates, blockStates, itemStacks, 0f, firstPos, secondPos, !breaking, breaking); + + CreateClient.OUTLINER.showCluster(outlineID, newCoordinates) + .withFaceTexture(AllSpecialTextures.CHECKERED) + .disableNormals() + .lineWidth(1 / 32f) + .colored(new Color(1f, 1f, 1f, 1f)); + } else { + //Thicker outline without block previews + CreateClient.OUTLINER.showCluster(outlineID, newCoordinates) + .withFaceTexture(AllSpecialTextures.HIGHLIGHT_CHECKERED) + .disableNormals() + .lineWidth(1 / 16f) + .colored(new Color(1f, 1f, 1f, 1f)); + + blockCount = newCoordinates.size(); + } + + } else { + //Breaking + CreateClient.OUTLINER.showCluster(outlineID, newCoordinates) + .withFaceTexture(AllSpecialTextures.THIN_CHECKERED) + .disableNormals() + .lineWidth(1 / 16f) + .colored(new Color(0.8f, 0.1f, 0.1f, 1f)); + blockCount = newCoordinates.size(); + } + + //Display block count and dimensions in actionbar + if (BuildModes.isActive(player)) { + + //Find min and max values (not simply firstPos and secondPos because that doesn't work with circles) + int minX = Integer.MAX_VALUE, maxX = Integer.MIN_VALUE; + int minY = Integer.MAX_VALUE, maxY = Integer.MIN_VALUE; + int minZ = Integer.MAX_VALUE, maxZ = Integer.MIN_VALUE; + for (BlockPos pos : startCoordinates) { + if (pos.getX() < minX) minX = pos.getX(); + if (pos.getX() > maxX) maxX = pos.getX(); + if (pos.getY() < minY) minY = pos.getY(); + if (pos.getY() > maxY) maxY = pos.getY(); + if (pos.getZ() < minZ) minZ = pos.getZ(); + if (pos.getZ() > maxZ) maxZ = pos.getZ(); + } + BlockPos dim = new BlockPos(maxX - minX + 1, maxY - minY + 1, maxZ - minZ + 1); + + String dimensions = "("; + if (dim.getX() > 1) dimensions += dim.getX() + "x"; + if (dim.getZ() > 1) dimensions += dim.getZ() + "x"; + if (dim.getY() > 1) dimensions += dim.getY() + "x"; + dimensions = dimensions.substring(0, dimensions.length() - 1); + if (dimensions.length() > 1) dimensions += ")"; + + EffortlessBuilding.log(player, blockCount + " blocks " + dimensions, true); + } + } + + public static void drawOutlinesIfNoBlockInHand(Player player, HitResult lookingAt) { + //Draw outlines if no block in hand + //Find proper raytrace: either normal range or increased range depending on canBreakFar + HitResult objectMouseOver = Minecraft.getInstance().hitResult; + HitResult breakingRaytrace = ReachHelper.canBreakFar(player) ? lookingAt : objectMouseOver; + + if (player.isCreative() && breakingRaytrace != null && breakingRaytrace.getType() == HitResult.Type.BLOCK) { + BlockHitResult blockBreakingRaytrace = (BlockHitResult) breakingRaytrace; + List breakCoordinates = BuildModifiers.findCoordinates(player, blockBreakingRaytrace.getBlockPos()); + + //Only render first outline if further than normal reach + if (objectMouseOver != null && objectMouseOver.getType() == HitResult.Type.BLOCK) + breakCoordinates.remove(0); + + breakCoordinates.removeIf(pos -> { + BlockState blockState = player.level.getBlockState(pos); + if (blockState.isAir() || blockState.getMaterial().isLiquid()) return true; + return !SurvivalHelper.canBreak(player.level, player, pos); + }); + + if (!breakCoordinates.isEmpty()) { + CreateClient.OUTLINER.showCluster("break", breakCoordinates) + .disableNormals() + .lineWidth(1 / 64f) + .colored(0x222222); + } + } + } + + //Whether to draw any block previews or outlines + public static boolean doShowBlockPreviews(ModifierSettings modifierSettings, ModeSettings modeSettings, BlockPos startPos) { + if (!ClientConfig.visuals.showBlockPreviews.get()) return false; + return modeSettings.getBuildMode() != BuildModes.BuildModeEnum.DISABLED || + (startPos != null && BuildModifiers.isEnabled(modifierSettings, startPos)) || + ClientConfig.visuals.alwaysShowBlockPreview.get(); + } + + protected static int renderBlockPreviews(Player player, ModifierSettings modifierSettings, List coordinates, + List blockStates, List itemStacks, float dissolve, + BlockPos firstPos, BlockPos secondPos, boolean checkCanPlace, boolean red) { + int blocksValid = 0; + + if (coordinates.isEmpty()) return blocksValid; + + for (int i = coordinates.size() - 1; i >= 0; i--) { + BlockPos blockPos = coordinates.get(i); + BlockState blockState = blockStates.get(i); + ItemStack itemstack = itemStacks.isEmpty() ? ItemStack.EMPTY : itemStacks.get(i); + if (CompatHelper.isItemBlockProxy(itemstack)) + itemstack = CompatHelper.getItemBlockByState(itemstack, blockState); + + //Check if we can place + boolean canPlace = true; + if (checkCanPlace) { + canPlace = SurvivalHelper.canPlace(player.level, player, blockPos, blockState, itemstack, modifierSettings.doQuickReplace(), Direction.UP); + } else { + //If check is turned off, check if blockstate is the same (for dissolve effect) + canPlace = player.level.getBlockState(blockPos) != blockState; + } + + if (canPlace) { + renderBlockPreview(blockPos, blockState, dissolve, firstPos, secondPos, red); + blocksValid++; + } + } + return blocksValid; + } + + protected static void renderBlockPreview(BlockPos blockPos, BlockState blockState, float dissolve, BlockPos firstPos, BlockPos secondPos, boolean breaking) { + if (blockState == null) return; + + float scale = 0.5f; + float alpha = 0.7f; + if (dissolve > 0f) { + float animationLength = 0.8f; + + double firstToSecond = secondPos.distSqr(firstPos); + double place = 0; + if (firstToSecond > 0.5) { + double placeFromFirst = firstPos.distSqr(blockPos) / firstToSecond; + double placeFromSecond = secondPos.distSqr(blockPos) / firstToSecond; + place = (placeFromFirst + (1.0 - placeFromSecond)) / 2.0; + } //else only one block + + //Scale place so we start first animation at 0 and end last animation at 1 + place *= 1f - animationLength; + float diff = dissolve - (float) place; + float t = diff / animationLength; + t = Mth.clamp(t, 0, 1); + //Now we got a usable t value for this block + + t = (float) Mth.smoothstep(t); +// t = (float) bezier(t, .58,-0.08,.23,1.33); + + if (!breaking) { + scale = 0.5f + (t * 0.3f); + alpha = 0.7f + (t * 0.3f); + } else { + t = 1f - t; + scale = 0.5f + (t * 0.5f); + alpha = 0.7f + (t * 0.3f); + } + alpha = Mth.clamp(alpha, 0, 1); + } + + CreateClient.GHOST_BLOCKS.showGhostState(blockPos.toShortString(), blockState) + .at(blockPos) + .alpha(alpha) + .scale(scale); + } + + //A bezier easing function where implicit first and last control points are (0,0) and (1,1). + public static double bezier(double t, double x1, double y1, double x2, double y2) { + double t2 = t * t; + double t3 = t2 * t; + + double cx = 3.0 * x1; + double bx = 3.0 * (x2 - x1) - cx; + double ax = 1.0 - cx -bx; + + double cy = 3.0 * y1; + double by = 3.0 * (y2 - y1) - cy; + double ay = 1.0 - cy - by; + + // Calculate the curve point at parameter value t + return (ay * t3) + (by * t2) + (cy * t) + 0; + } + + public static void onBlocksPlaced() { + onBlocksPlaced(previousCoordinates, previousItemStacks, previousBlockStates, previousFirstPos, previousSecondPos); + } + + public static void onBlocksPlaced(List coordinates, List itemStacks, List blockStates, + BlockPos firstPos, BlockPos secondPos) { + LocalPlayer player = Minecraft.getInstance().player; + ModifierSettings modifierSettings = ModifierSettingsManager.getModifierSettings(player); + ModeSettings modeSettings = ModeSettingsManager.getModeSettings(player); + + //Check if block previews are enabled + if (doShowBlockPreviews(modifierSettings, modeSettings, firstPos)) { + + //Save current coordinates, blockstates and itemstacks + if (!coordinates.isEmpty() && blockStates.size() == coordinates.size() && + coordinates.size() > 1 && coordinates.size() < ClientConfig.visuals.maxBlockPreviews.get()) { + + placedDataList.add(new PlacedData(ClientEvents.ticksInGame, coordinates, blockStates, + itemStacks, firstPos, secondPos, false)); + } + + CreateClient.OUTLINER.keep(firstPos, CommonConfig.visuals.appearAnimationLength.get()); + } + + } + + public static void onBlocksBroken() { + onBlocksBroken(previousCoordinates, previousItemStacks, previousBlockStates, previousFirstPos, previousSecondPos); + } + + public static void onBlocksBroken(List coordinates, List itemStacks, List blockStates, + BlockPos firstPos, BlockPos secondPos) { + LocalPlayer player = Minecraft.getInstance().player; + ModifierSettings modifierSettings = ModifierSettingsManager.getModifierSettings(player); + ModeSettings modeSettings = ModeSettingsManager.getModeSettings(player); + + //Check if block previews are enabled + if (doShowBlockPreviews(modifierSettings, modeSettings, firstPos)) { + + //Save current coordinates, blockstates and itemstacks + if (!coordinates.isEmpty() && blockStates.size() == coordinates.size() && + coordinates.size() > 1 && coordinates.size() < ClientConfig.visuals.maxBlockPreviews.get()) { + + sortOnDistanceToPlayer(coordinates, player); + + placedDataList.add(new PlacedData(ClientEvents.ticksInGame, coordinates, blockStates, + itemStacks, firstPos, secondPos, true)); + } + + CreateClient.OUTLINER.keep(firstPos, CommonConfig.visuals.breakAnimationLength.get()); + } + + } + + private static void sortOnDistanceToPlayer(List coordinates, Player player) { + + Collections.sort(coordinates, (lhs, rhs) -> { + // -1 - less than, 1 - greater than, 0 - equal + double lhsDistanceToPlayer = Vec3.atLowerCornerOf(lhs).subtract(player.getEyePosition(1f)).lengthSqr(); + double rhsDistanceToPlayer = Vec3.atLowerCornerOf(rhs).subtract(player.getEyePosition(1f)).lengthSqr(); + return (int) Math.signum(lhsDistanceToPlayer - rhsDistanceToPlayer); + }); + + } + + static class PlacedData { + float time; + List coordinates; + List blockStates; + List itemStacks; + BlockPos firstPos; + BlockPos secondPos; + boolean breaking; + + public PlacedData(float time, List coordinates, List blockStates, + List itemStacks, BlockPos firstPos, BlockPos secondPos, boolean breaking) { + this.time = time; + this.coordinates = coordinates; + this.blockStates = blockStates; + this.itemStacks = itemStacks; + this.firstPos = firstPos; + this.secondPos = secondPos; + this.breaking = breaking; + } + } +} diff --git a/src/main/java/nl/requios/effortlessbuilding/render/RenderHandler.java b/src/main/java/nl/requios/effortlessbuilding/render/RenderHandler.java index 4bbcd7b..65c5668 100644 --- a/src/main/java/nl/requios/effortlessbuilding/render/RenderHandler.java +++ b/src/main/java/nl/requios/effortlessbuilding/render/RenderHandler.java @@ -5,62 +5,103 @@ import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.Tesselator; import com.mojang.blaze3d.vertex.VertexConsumer; import net.minecraft.client.Minecraft; -import net.minecraft.client.renderer.LevelRenderer; import net.minecraft.client.renderer.MultiBufferSource; -import net.minecraft.client.renderer.RenderType; -import net.minecraft.client.renderer.Sheets; -import net.minecraft.client.renderer.block.BlockRenderDispatcher; -import net.minecraft.client.renderer.texture.OverlayTexture; -import net.minecraft.client.resources.model.BakedModel; import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; import net.minecraft.world.entity.player.Player; -import net.minecraft.world.level.block.state.BlockState; -import net.minecraft.world.phys.AABB; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.HitResult; import net.minecraft.world.phys.Vec3; -import net.minecraft.world.phys.shapes.VoxelShape; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.client.event.RenderLevelStageEvent; -import net.minecraftforge.client.model.data.ModelData; -import net.minecraftforge.eventbus.api.EventPriority; +import net.minecraftforge.event.TickEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; -import net.minecraftforge.fml.common.Mod; -import nl.requios.effortlessbuilding.EffortlessBuilding; +import net.minecraftforge.fml.common.Mod.EventBusSubscriber; +import nl.requios.effortlessbuilding.ClientEvents; +import nl.requios.effortlessbuilding.buildmode.BuildModes; import nl.requios.effortlessbuilding.buildmode.ModeSettingsManager; import nl.requios.effortlessbuilding.buildmodifier.ModifierSettingsManager; - -import static net.minecraftforge.client.event.RenderLevelStageEvent.Stage.AFTER_PARTICLES; +import nl.requios.effortlessbuilding.compatibility.CompatHelper; +import nl.requios.effortlessbuilding.helper.SurvivalHelper; /*** * Main render class for Effortless Building */ -@Mod.EventBusSubscriber(value = Dist.CLIENT) +@EventBusSubscriber(Dist.CLIENT) public class RenderHandler { @SubscribeEvent - public static void onRender(RenderLevelStageEvent event) { - if (event.getPhase() != EventPriority.NORMAL || event.getStage() != AFTER_PARTICLES) - return; + public static void onTick(TickEvent.ClientTickEvent event) { + if (!nl.requios.effortlessbuilding.create.events.ClientEvents.isGameActive()) return; - PoseStack matrixStack = event.getPoseStack(); + var player = Minecraft.getInstance().player; + var modeSettings = ModeSettingsManager.getModeSettings(player); + var modifierSettings = ModifierSettingsManager.getModifierSettings(player); + + BlockPreviews.drawPlacedBlocks(player, modifierSettings); + + + HitResult lookingAt = ClientEvents.getLookingAt(player); + if (modeSettings.getBuildMode() == BuildModes.BuildModeEnum.DISABLED) + lookingAt = Minecraft.getInstance().hitResult; + + ItemStack mainhand = player.getMainHandItem(); + boolean noBlockInHand = !(!mainhand.isEmpty() && CompatHelper.isItemBlockProxy(mainhand)); + + //Find start position, side hit and hit vector + BlockPos startPos = null; + Direction sideHit = null; + Vec3 hitVec = null; + + //Checking for null is necessary! Even in vanilla when looking down ladders it is occasionally null (instead of Type MISS) + if (lookingAt != null && lookingAt.getType() == HitResult.Type.BLOCK) { + BlockHitResult blockLookingAt = (BlockHitResult) lookingAt; + startPos = blockLookingAt.getBlockPos(); + + //Check if tool (or none) in hand + boolean replaceable = player.level.getBlockState(startPos).getMaterial().isReplaceable(); + boolean becomesDoubleSlab = SurvivalHelper.doesBecomeDoubleSlab(player, startPos, blockLookingAt.getDirection()); + if (!modifierSettings.doQuickReplace() && !noBlockInHand && !replaceable && !becomesDoubleSlab) { + startPos = startPos.relative(blockLookingAt.getDirection()); + } + + //Get under tall grass and other replaceable blocks + if (modifierSettings.doQuickReplace() && !noBlockInHand && replaceable) { + startPos = startPos.below(); + } + + sideHit = blockLookingAt.getDirection(); + hitVec = blockLookingAt.getLocation(); + } + + + BlockPreviews.drawLookAtPreview(player, modeSettings, modifierSettings, startPos, sideHit, hitVec); + + if (noBlockInHand) BlockPreviews.drawOutlinesIfNoBlockInHand(player, lookingAt); + } + + @SubscribeEvent + public static void onRender(RenderLevelStageEvent event) { + if (event.getStage() != RenderLevelStageEvent.Stage.AFTER_TRANSLUCENT_BLOCKS) return; + + Vec3 cameraPos = Minecraft.getInstance().gameRenderer.getMainCamera().getPosition(); + + PoseStack ms = event.getPoseStack(); BufferBuilder bufferBuilder = Tesselator.getInstance().getBuilder(); - MultiBufferSource.BufferSource renderTypeBuffer = MultiBufferSource.immediate(bufferBuilder); + MultiBufferSource.BufferSource buffer = MultiBufferSource.immediate(bufferBuilder); Player player = Minecraft.getInstance().player; ModeSettingsManager.ModeSettings modeSettings = ModeSettingsManager.getModeSettings(player); ModifierSettingsManager.ModifierSettings modifierSettings = ModifierSettingsManager.getModifierSettings(player); - Vec3 projectedView = Minecraft.getInstance().gameRenderer.getMainCamera().getPosition(); - - matrixStack.pushPose(); - matrixStack.translate(-projectedView.x, -projectedView.y, -projectedView.z); + ms.pushPose(); + ms.translate(-cameraPos.x, -cameraPos.y, -cameraPos.z); //Mirror and radial mirror lines and areas - ModifierRenderer.render(matrixStack, renderTypeBuffer, modifierSettings); + ModifierRenderer.render(ms, buffer, modifierSettings); - //Render block previews - BlockPreviewRenderer.render(matrixStack, renderTypeBuffer, player, modifierSettings, modeSettings); - - matrixStack.popPose(); + ms.popPose(); } protected static VertexConsumer beginLines(MultiBufferSource.BufferSource renderTypeBuffer) { @@ -79,77 +120,4 @@ public class RenderHandler { renderTypeBuffer.endBatch(); } - protected static void renderBlockPreview(PoseStack matrixStack, MultiBufferSource.BufferSource renderTypeBuffer, BlockRenderDispatcher dispatcher, - BlockPos blockPos, BlockState blockState, float dissolve, BlockPos firstPos, BlockPos secondPos, boolean red) { - if (blockState == null) return; - - matrixStack.pushPose(); - matrixStack.translate(blockPos.getX(), blockPos.getY(), blockPos.getZ()); -// matrixStack.rotate(Vector3f.YP.rotationDegrees(-90f)); - matrixStack.translate(-0.01f, -0.01f, -0.01f); - matrixStack.scale(1.02f, 1.02f, 1.02f); - - //Begin block preview rendering - RenderType blockPreviewRenderType = BuildRenderTypes.getBlockPreviewRenderType(dissolve, blockPos, firstPos, secondPos, red); - VertexConsumer buffer = renderTypeBuffer.getBuffer(blockPreviewRenderType); - - try { - BakedModel model = dispatcher.getBlockModel(blockState); - dispatcher.getModelRenderer().renderModel(matrixStack.last(), buffer, blockState, model, - 1f, 1f, 1f, 0, OverlayTexture.NO_OVERLAY, ModelData.EMPTY, blockPreviewRenderType); - } catch (NullPointerException e) { - EffortlessBuilding.logger.warn("RenderHandler::renderBlockPreview cannot render " + blockState.getBlock().toString()); - - //Render outline as backup, escape out of the current renderstack - matrixStack.popPose(); - renderTypeBuffer.endBatch(); - VertexConsumer lineBuffer = beginLines(renderTypeBuffer); - renderBlockOutline(matrixStack, lineBuffer, blockPos, new Vec3(1f, 1f, 1f)); - endLines(renderTypeBuffer); - buffer = renderTypeBuffer.getBuffer(Sheets.translucentCullBlockSheet()); //any type will do, as long as we have something on the stack - matrixStack.pushPose(); - } - - renderTypeBuffer.endBatch(); - matrixStack.popPose(); - } - - protected static void renderBlockOutline(PoseStack matrixStack, VertexConsumer buffer, BlockPos pos, Vec3 color) { - renderBlockOutline(matrixStack, buffer, pos, pos, color); - } - - //Renders outline. Pos1 has to be minimal x,y,z and pos2 maximal x,y,z - protected static void renderBlockOutline(PoseStack matrixStack, VertexConsumer buffer, BlockPos pos1, BlockPos pos2, Vec3 color) { - AABB aabb = new AABB(pos1, pos2.offset(1, 1, 1)).inflate(0.0020000000949949026); - - LevelRenderer.renderLineBox(matrixStack, buffer, aabb, (float) color.x, (float) color.y, (float) color.z, 0.4f); -// WorldRenderer.drawSelectionBoundingBox(aabb, (float) color.x, (float) color.y, (float) color.z, 0.4f); - } - - //Renders outline with given bounding box - protected static void renderBlockOutline(PoseStack matrixStack, VertexConsumer buffer, BlockPos pos, VoxelShape collisionShape, Vec3 color) { -// WorldRenderer.drawShape(collisionShape, pos.getX(), pos.getY(), pos.getZ(), (float) color.x, (float) color.y, (float) color.z, 0.4f); - LevelRenderer.renderVoxelShape(matrixStack, buffer, collisionShape, pos.getX(), pos.getY(), pos.getZ(), (float) color.x, (float) color.y, (float) color.z, 0.4f); - } - - //TODO - //Sends breaking progress for all coordinates to renderglobal, so all blocks get visually broken -// @Override -// public void sendBlockBreakProgress(int breakerId, BlockPos pos, int progress) { -// Minecraft mc = Minecraft.getInstance(); -// -// ModifierSettingsManager.ModifierSettings modifierSettings = ModifierSettingsManager.getModifierSettings(mc.player); -// if (!BuildModifiers.isEnabled(modifierSettings, pos)) return; -// -// List coordinates = BuildModifiers.findCoordinates(mc.player, pos); -// for (int i = 1; i < coordinates.size(); i++) { -// BlockPos coordinate = coordinates.get(i); -// if (SurvivalHelper.canBreak(mc.world, mc.player, coordinate)) { -// //Send i as entity id because only one block can be broken per id -// //Unless i happens to be the player id, then take something else -// int fakeId = mc.player.getEntityId() != i ? i : coordinates.size(); -// mc.renderGlobal.sendBlockBreakProgress(fakeId, coordinate, progress); -// } -// } -// } } diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg index 6569222..2752a32 100644 --- a/src/main/resources/META-INF/accesstransformer.cfg +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -1,3 +1,49 @@ public net.minecraft.client.gui.components.AbstractSelectionList f_93398_ # renderHeader public net.minecraft.client.gui.Font m_92803_(Ljava/lang/String;FFILcom/mojang/math/Matrix4f;ZZ)I # renderString -public net.minecraft.world.inventory.MenuType m_39988_(Ljava/lang/String;Lnet/minecraft/world/inventory/MenuType$MenuSupplier;)Lnet/minecraft/world/inventory/MenuType; # register \ No newline at end of file +public net.minecraft.world.inventory.MenuType m_39988_(Ljava/lang/String;Lnet/minecraft/world/inventory/MenuType$MenuSupplier;)Lnet/minecraft/world/inventory/MenuType; # register + +#create +public net.minecraft.client.Minecraft f_91013_ # pausePartialTick +public net.minecraft.client.gui.Font m_92863_(Lnet/minecraft/resources/ResourceLocation;)Lnet/minecraft/client/gui/font/FontSet; # getFontSet +public net.minecraft.client.gui.screens.TitleScreen f_96729_ # panorama +public net.minecraft.client.multiplayer.ClientPacketListener f_104897_ # serverChunkRadius +protected net.minecraft.client.particle.Particle f_107205_ # stoppedByCollision +public net.minecraft.client.renderer.ItemInHandRenderer f_109300_ # mainHandItem +public net.minecraft.client.renderer.ItemInHandRenderer f_109301_ # offHandItem +public net.minecraft.client.renderer.entity.ItemRenderer f_115096_ # textureManager + +public-f net.minecraft.network.protocol.game.ClientboundPlayerAbilitiesPacket f_132663_ # flyingSpeed + +public net.minecraft.server.MinecraftServer f_129744_ # storageSource +public net.minecraft.server.network.ServerGamePacketListenerImpl f_9737_ # aboveGroundTickCount +public net.minecraft.server.network.ServerGamePacketListenerImpl f_9739_ # aboveGroundVehicleTickCount + +public net.minecraft.world.entity.Entity f_146795_ # removalReason +protected net.minecraft.world.entity.Entity m_19956_(Lnet/minecraft/world/entity/Entity;Lnet/minecraft/world/entity/Entity$MoveFunction;)V # positionRider +public net.minecraft.world.entity.LivingEntity f_20899_ # jumping + +public-f net.minecraft.world.item.HoneycombItem f_150863_ # WAXABLES +public net.minecraft.world.item.alchemy.PotionBrewing f_43494_ # POTION_MIXES +public net.minecraft.world.item.alchemy.PotionBrewing f_43495_ # CONTAINER_MIXES +public net.minecraft.world.item.alchemy.PotionBrewing f_43497_ # ALLOWED_CONTAINER +public net.minecraft.world.item.crafting.Ingredient f_43902_ # values +public net.minecraft.world.item.crafting.RecipeManager f_44007_ # recipes + +public net.minecraft.world.level.BaseSpawner f_45443_ # spawnPotentials +public net.minecraft.world.level.BaseSpawner f_45444_ # nextSpawnData +public net.minecraft.world.level.biome.BiomeManager f_47863_ # biomeZoomSeed +public net.minecraft.world.level.block.entity.BeaconBlockEntity f_58648_ # beamSections +public net.minecraft.world.level.chunk.HashMapPalette f_62658_ # values +public net.minecraft.world.level.chunk.PaletteResize + +public net.minecraft.client.model.geom.ModelPart f_104212_ # cubes +public net.minecraft.client.model.geom.ModelPart f_104213_ # children +public net.minecraft.client.model.AgeableListModel f_102007_ # scaleHead +public net.minecraft.client.model.AgeableListModel f_170338_ # babyYHeadOffset +public net.minecraft.client.model.AgeableListModel f_170339_ # babyZHeadOffset +public net.minecraft.client.model.AgeableListModel f_102010_ # babyHeadScale +public net.minecraft.client.model.AgeableListModel f_102011_ # babyBodyScale +public net.minecraft.client.model.AgeableListModel f_102012_ # bodyYOffset + +public net.minecraft.client.gui.components.CommandSuggestions f_93866_ # suggestions +public net.minecraft.client.gui.components.CommandSuggestions$SuggestionsList (Lnet/minecraft/client/gui/components/CommandSuggestions;IIILjava/util/List;Z)V # \ No newline at end of file diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index 37db863..d7d8f41 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -20,13 +20,20 @@ Makes building easier by providing tools like mirrors, arrays, build modes and a [[dependencies.effortlessbuilding]] modId="forge" mandatory=true - versionRange="[41,)" + versionRange="[43.0.0,)" ordering="NONE" side="BOTH" [[dependencies.effortlessbuilding]] modId="minecraft" mandatory=true - versionRange="[1.18.1,1.20)" + versionRange="[1.19.1,1.20)" ordering="NONE" - side="BOTH" \ No newline at end of file + side="BOTH" + +[[dependencies.effortlessbuilding]] + modId="flywheel" + mandatory=true + versionRange="[0.6.8,0.6.9)" + ordering="AFTER" + side="CLIENT" \ No newline at end of file diff --git a/src/main/resources/assets/effortlessbuilding/lang/en_us.json b/src/main/resources/assets/effortlessbuilding/lang/en_us.json index 3a37363..e5da310 100644 --- a/src/main/resources/assets/effortlessbuilding/lang/en_us.json +++ b/src/main/resources/assets/effortlessbuilding/lang/en_us.json @@ -11,12 +11,12 @@ "key.effortlessbuilding.redo.desc": "Redo", "key.effortlessbuilding.altplacement.desc": "Alternative placement", - "effortlessbuilding:randomizer_bag": "Leather Randomizer Bag", - "effortlessbuilding:golden_randomizer_bag": "Golden Randomizer Bag", - "effortlessbuilding:diamond_randomizer_bag": "Diamond Randomizer Bag", - "effortlessbuilding:reach_upgrade1": "Reach Upgrade 1", - "effortlessbuilding:reach_upgrade2": "Reach Upgrade 2", - "effortlessbuilding:reach_upgrade3": "Reach Upgrade 3", + "item.effortlessbuilding.randomizer_bag": "Leather Randomizer Bag", + "item.effortlessbuilding.golden_randomizer_bag": "Golden Randomizer Bag", + "item.effortlessbuilding.diamond_randomizer_bag": "Diamond Randomizer Bag", + "item.effortlessbuilding.reach_upgrade1": "Reach Upgrade 1", + "item.effortlessbuilding.reach_upgrade2": "Reach Upgrade 2", + "item.effortlessbuilding.reach_upgrade3": "Reach Upgrade 3", "effortlessbuilding.mode.normal": "Disable", "effortlessbuilding.mode.normal_plus": "Single", diff --git a/src/main/resources/assets/effortlessbuilding/shaders/core/glowing_shader.fsh b/src/main/resources/assets/effortlessbuilding/shaders/core/glowing_shader.fsh new file mode 100644 index 0000000..ff27aab --- /dev/null +++ b/src/main/resources/assets/effortlessbuilding/shaders/core/glowing_shader.fsh @@ -0,0 +1,24 @@ +#version 150 + +#moj_import + +uniform sampler2D Sampler0; + +uniform vec4 ColorModulator; +uniform float FogStart; +uniform float FogEnd; +uniform vec4 FogColor; + +in float vertexDistance; +in vec4 vertexColor; +in vec4 lightMapColor; +in vec2 texCoord0; +in vec4 normal; + +out vec4 fragColor; + +void main() { + vec4 color = texture(Sampler0, texCoord0) * vertexColor * ColorModulator; + color *= lightMapColor; + fragColor = linear_fog(color, vertexDistance, FogStart, FogEnd, FogColor); +} diff --git a/src/main/resources/assets/effortlessbuilding/shaders/core/glowing_shader.json b/src/main/resources/assets/effortlessbuilding/shaders/core/glowing_shader.json new file mode 100644 index 0000000..52cfb6d --- /dev/null +++ b/src/main/resources/assets/effortlessbuilding/shaders/core/glowing_shader.json @@ -0,0 +1,31 @@ +{ + "blend": { + "func": "add", + "srcrgb": "srcalpha", + "dstrgb": "1-srcalpha" + }, + "vertex": "effortlessbuilding:glowing_shader", + "fragment": "effortlessbuilding:glowing_shader", + "attributes": [ + "Position", + "Color", + "UV0", + "UV1", + "UV2", + "Normal" + ], + "samplers": [ + { "name": "Sampler0" }, + { "name": "Sampler2" } + ], + "uniforms": [ + { "name": "ModelViewMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "ProjMat", "type": "matrix4x4", "count": 16, "values": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "IViewRotMat", "type": "matrix3x3", "count": 9, "values": [ 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0 ] }, + { "name": "ColorModulator", "type": "float", "count": 4, "values": [ 1.0, 1.0, 1.0, 1.0 ] }, + { "name": "FogStart", "type": "float", "count": 1, "values": [ 0.0 ] }, + { "name": "FogEnd", "type": "float", "count": 1, "values": [ 1.0 ] }, + { "name": "FogColor", "type": "float", "count": 4, "values": [ 0.0, 0.0, 0.0, 0.0 ] }, + { "name": "FogShape", "type": "int", "count": 1, "values": [ 0 ] } + ] +} diff --git a/src/main/resources/assets/effortlessbuilding/shaders/core/glowing_shader.vsh b/src/main/resources/assets/effortlessbuilding/shaders/core/glowing_shader.vsh new file mode 100644 index 0000000..b47f08e --- /dev/null +++ b/src/main/resources/assets/effortlessbuilding/shaders/core/glowing_shader.vsh @@ -0,0 +1,36 @@ +#version 150 + +#moj_import + +in vec3 Position; +in vec4 Color; +in vec2 UV0; +in vec2 UV1; +in ivec2 UV2; +in vec3 Normal; + +uniform sampler2D Sampler1; +uniform sampler2D Sampler2; + +uniform mat4 ModelViewMat; +uniform mat4 ProjMat; +uniform mat3 IViewRotMat; +uniform int FogShape; + +out float vertexDistance; +out vec4 vertexColor; +out vec4 lightMapColor; +out vec2 texCoord0; +out vec2 texCoord1; +out vec4 normal; + +void main() { + gl_Position = ProjMat * ModelViewMat * vec4(Position, 1.0); + + vertexDistance = fog_distance(ModelViewMat, IViewRotMat * Position, FogShape); + vertexColor = Color; + lightMapColor = texelFetch(Sampler2, UV2 / 16, 0); + texCoord0 = UV0; + texCoord1 = UV1; + normal = ProjMat * ModelViewMat * vec4(Normal, 0.0); +} diff --git a/src/main/resources/assets/effortlessbuilding/textures/icons/normal.png b/src/main/resources/assets/effortlessbuilding/textures/icons/disabled.png similarity index 100% rename from src/main/resources/assets/effortlessbuilding/textures/icons/normal.png rename to src/main/resources/assets/effortlessbuilding/textures/icons/disabled.png diff --git a/src/main/resources/assets/effortlessbuilding/textures/icons/normal_plus.png b/src/main/resources/assets/effortlessbuilding/textures/icons/single.png similarity index 100% rename from src/main/resources/assets/effortlessbuilding/textures/icons/normal_plus.png rename to src/main/resources/assets/effortlessbuilding/textures/icons/single.png diff --git a/src/main/resources/assets/effortlessbuilding/textures/special/blank.png b/src/main/resources/assets/effortlessbuilding/textures/special/blank.png new file mode 100644 index 0000000..2e084d9 Binary files /dev/null and b/src/main/resources/assets/effortlessbuilding/textures/special/blank.png differ diff --git a/src/main/resources/assets/effortlessbuilding/textures/special/checkerboard.png b/src/main/resources/assets/effortlessbuilding/textures/special/checkerboard.png new file mode 100644 index 0000000..36b5c17 Binary files /dev/null and b/src/main/resources/assets/effortlessbuilding/textures/special/checkerboard.png differ diff --git a/src/main/resources/assets/effortlessbuilding/textures/special/cutout_checkerboard.png b/src/main/resources/assets/effortlessbuilding/textures/special/cutout_checkerboard.png new file mode 100644 index 0000000..7479d32 Binary files /dev/null and b/src/main/resources/assets/effortlessbuilding/textures/special/cutout_checkerboard.png differ diff --git a/src/main/resources/assets/effortlessbuilding/textures/special/glue.png b/src/main/resources/assets/effortlessbuilding/textures/special/glue.png new file mode 100644 index 0000000..f5a28c1 Binary files /dev/null and b/src/main/resources/assets/effortlessbuilding/textures/special/glue.png differ diff --git a/src/main/resources/assets/effortlessbuilding/textures/special/highlighted_checkerboard.png b/src/main/resources/assets/effortlessbuilding/textures/special/highlighted_checkerboard.png new file mode 100644 index 0000000..e74e272 Binary files /dev/null and b/src/main/resources/assets/effortlessbuilding/textures/special/highlighted_checkerboard.png differ diff --git a/src/main/resources/assets/effortlessbuilding/textures/special/selection.png b/src/main/resources/assets/effortlessbuilding/textures/special/selection.png new file mode 100644 index 0000000..177a344 Binary files /dev/null and b/src/main/resources/assets/effortlessbuilding/textures/special/selection.png differ diff --git a/src/main/resources/assets/effortlessbuilding/textures/special/thin_checkerboard.png b/src/main/resources/assets/effortlessbuilding/textures/special/thin_checkerboard.png new file mode 100644 index 0000000..2bdb33c Binary files /dev/null and b/src/main/resources/assets/effortlessbuilding/textures/special/thin_checkerboard.png differ