diff --git a/build.gradle b/build.gradle index 6588f7a..16fa9ae 100644 --- a/build.gradle +++ b/build.gradle @@ -1,29 +1,29 @@ plugins { - id 'java-library' - id 'eclipse' - id 'idea' - id 'maven-publish' - id 'net.neoforged.gradle.userdev' version '7.0.145' + id 'java-library' + id 'eclipse' + id 'idea' + id 'maven-publish' + id 'net.neoforged.gradle.userdev' version '7.0.145' } tasks.named('wrapper', Wrapper).configure { - // Define wrapper values here so as to not have to always do so when updating gradlew.properties. - // Switching this to Wrapper.DistributionType.ALL will download the full gradle sources that comes with - // documentation attached on cursor hover of gradle classes and methods. However, this comes with increased - // file size for Gradle. If you do switch this to ALL, run the Gradle wrapper task twice afterwards. - // (Verify by checking gradle/wrapper/gradle-wrapper.properties to see if distributionUrl now points to `-all`) - distributionType = Wrapper.DistributionType.BIN + // Define wrapper values here so as to not have to always do so when updating gradlew.properties. + // Switching this to Wrapper.DistributionType.ALL will download the full gradle sources that comes with + // documentation attached on cursor hover of gradle classes and methods. However, this comes with increased + // file size for Gradle. If you do switch this to ALL, run the Gradle wrapper task twice afterwards. + // (Verify by checking gradle/wrapper/gradle-wrapper.properties to see if distributionUrl now points to `-all`) + distributionType = Wrapper.DistributionType.BIN } version = mod_version group = mod_group_id repositories { - mavenLocal() + mavenLocal() } base { - archivesName = mod_id + archivesName = mod_id } // Mojang ships Java 21 to end users starting in 1.20.5, so mods should target Java 21. @@ -35,47 +35,48 @@ java.toolchain.languageVersion = JavaLanguageVersion.of(21) // Default run configurations. // These can be tweaked, removed, or duplicated as needed. runs { - // applies to all the run configs below - configureEach { - // Recommended logging data for a userdev environment - // The markers can be added/remove as needed separated by commas. - // "SCAN": For mods scan. - // "REGISTRIES": For firing of registry events. - // "REGISTRYDUMP": For getting the contents of all registries. - systemProperty 'forge.logging.markers', 'REGISTRIES' + // applies to all the run configs below + configureEach { + // Recommended logging data for a userdev environment + // The markers can be added/remove as needed separated by commas. + // "SCAN": For mods scan. + // "REGISTRIES": For firing of registry events. + // "REGISTRYDUMP": For getting the contents of all registries. + systemProperty 'forge.logging.markers', 'REGISTRIES' - // Recommended logging level for the console - // You can set various levels here. - // Please read: https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levels - systemProperty 'forge.logging.console.level', 'debug' + // Recommended logging level for the console + // You can set various levels here. + // Please read: https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levels + systemProperty 'forge.logging.console.level', 'debug' - modSource project.sourceSets.main - } + modSource project.sourceSets.main + } - client { - // Comma-separated list of namespaces to load gametests from. Empty = all namespaces. - systemProperty 'forge.enabledGameTestNamespaces', project.mod_id - } + client { + // Comma-separated list of namespaces to load gametests from. Empty = all namespaces. + systemProperty 'forge.enabledGameTestNamespaces', project.mod_id + } - server { - systemProperty 'forge.enabledGameTestNamespaces', project.mod_id - programArgument '--nogui' - } + server { + systemProperty 'forge.enabledGameTestNamespaces', project.mod_id + programArgument '--nogui' + } - // This run config launches GameTestServer and runs all registered gametests, then exits. - // By default, the server will crash when no gametests are provided. - // The gametest system is also enabled by default for other run configs under the /test command. - gameTestServer { - systemProperty 'forge.enabledGameTestNamespaces', project.mod_id - } + // This run config launches GameTestServer and runs all registered gametests, then exits. + // By default, the server will crash when no gametests are provided. + // The gametest system is also enabled by default for other run configs under the /test command. + gameTestServer { + systemProperty 'forge.enabledGameTestNamespaces', project.mod_id + } - data { - // example of overriding the workingDirectory set in configureEach above, uncomment if you want to use it - // workingDirectory project.file('run-data') + data { + // example of overriding the workingDirectory set in configureEach above, uncomment if you want to use it + // workingDirectory project.file('run-data') - // Specify the modid for data generation, where to output the resulting resource, and where to look for existing resources. - programArguments.addAll '--mod', project.mod_id, '--all', '--output', file('src/generated/resources/').getAbsolutePath(), '--existing', file('src/main/resources/').getAbsolutePath() - } + // Specify the modid for data generation, where to output the resulting resource, and where to look for existing resources. + programArguments.addAll '--mod', project.mod_id, '--all', '--output', file('src/generated/resources/').getAbsolutePath(), + '--existing', file('src/main/resources/').getAbsolutePath() + } } // Include resources generated by data generators. @@ -86,39 +87,39 @@ sourceSets.main.resources { srcDir 'src/generated/resources' } // a dependency that will be present for runtime testing but that is // "optional", meaning it will not be pulled by dependents of this mod. configurations { - runtimeClasspath.extendsFrom localRuntime + runtimeClasspath.extendsFrom localRuntime } dependencies { - // Specify the version of Minecraft to use. - // Depending on the plugin applied there are several options. We will assume you applied the userdev plugin as shown above. - // The group for userdev is net.neoforged, the module name is neoforge, and the version is the same as the neoforge version. - // You can however also use the vanilla plugin (net.neoforged.gradle.vanilla) to use a version of Minecraft without the neoforge loader. - // And its provides the option to then use net.minecraft as the group, and one of; client, server or joined as the module name, plus the game version as version. - // For all intends and purposes: You can treat this dependency as if it is a normal library you would use. - implementation "net.neoforged:neoforge:${neo_version}" + // Specify the version of Minecraft to use. + // Depending on the plugin applied there are several options. We will assume you applied the userdev plugin as shown above. + // The group for userdev is net.neoforged, the module name is neoforge, and the version is the same as the neoforge version. + // You can however also use the vanilla plugin (net.neoforged.gradle.vanilla) to use a version of Minecraft without the neoforge loader. + // And its provides the option to then use net.minecraft as the group, and one of; client, server or joined as the module name, plus the game version as version. + // For all intends and purposes: You can treat this dependency as if it is a normal library you would use. + implementation "net.neoforged:neoforge:${neo_version}" - // Example optional mod dependency with JEI - // The JEI API is declared for compile time use, while the full JEI artifact is used at runtime - // compileOnly "mezz.jei:jei-${mc_version}-common-api:${jei_version}" - // compileOnly "mezz.jei:jei-${mc_version}-neoforge-api:${jei_version}" - // We add the full version to localRuntime, not runtimeOnly, so that we do not publish a dependency on it - // localRuntime "mezz.jei:jei-${mc_version}-neoforge:${jei_version}" + // Example optional mod dependency with JEI + // The JEI API is declared for compile time use, while the full JEI artifact is used at runtime + // compileOnly "mezz.jei:jei-${mc_version}-common-api:${jei_version}" + // compileOnly "mezz.jei:jei-${mc_version}-neoforge-api:${jei_version}" + // We add the full version to localRuntime, not runtimeOnly, so that we do not publish a dependency on it + // localRuntime "mezz.jei:jei-${mc_version}-neoforge:${jei_version}" - // Example mod dependency using a mod jar from ./libs with a flat dir repository - // This maps to ./libs/coolmod-${mc_version}-${coolmod_version}.jar - // The group id is ignored when searching -- in this case, it is "blank" - // implementation "blank:coolmod-${mc_version}:${coolmod_version}" + // Example mod dependency using a mod jar from ./libs with a flat dir repository + // This maps to ./libs/coolmod-${mc_version}-${coolmod_version}.jar + // The group id is ignored when searching -- in this case, it is "blank" + // implementation "blank:coolmod-${mc_version}:${coolmod_version}" - // Example mod dependency using a file as dependency - // implementation files("libs/coolmod-${mc_version}-${coolmod_version}.jar") + // Example mod dependency using a file as dependency + // implementation files("libs/coolmod-${mc_version}-${coolmod_version}.jar") - // Example project dependency using a sister or child project: - // implementation project(":myproject") + // Example project dependency using a sister or child project: + // implementation project(":myproject") - // For more info: - // http://www.gradle.org/docs/current/userguide/artifact_dependencies_tutorial.html - // http://www.gradle.org/docs/current/userguide/dependency_management.html + // For more info: + // http://www.gradle.org/docs/current/userguide/artifact_dependencies_tutorial.html + // http://www.gradle.org/docs/current/userguide/dependency_management.html } // This block of code expands all declared replace properties in the specified resource targets. @@ -126,48 +127,48 @@ dependencies { // When "copyIdeResources" is enabled, this will also run before the game launches in IDE environments. // See https://docs.gradle.org/current/dsl/org.gradle.language.jvm.tasks.ProcessResources.html tasks.withType(ProcessResources).configureEach { - var replaceProperties = [ - minecraft_version : minecraft_version, - minecraft_version_range: minecraft_version_range, - neo_version : neo_version, - neo_version_range : neo_version_range, - loader_version_range : loader_version_range, - mod_id : mod_id, - mod_name : mod_name, - mod_license : mod_license, - mod_version : mod_version, - mod_authors : mod_authors, - mod_description : mod_description - ] - inputs.properties replaceProperties + var replaceProperties = [ + minecraft_version : minecraft_version, + minecraft_version_range: minecraft_version_range, + neo_version : neo_version, + neo_version_range : neo_version_range, + loader_version_range : loader_version_range, + mod_id : mod_id, + mod_name : mod_name, + mod_license : mod_license, + mod_version : mod_version, + mod_authors : mod_authors, + mod_description : mod_description + ] + inputs.properties replaceProperties - filesMatching(['META-INF/neoforge.mods.toml']) { - expand replaceProperties - } + filesMatching(['META-INF/neoforge.mods.toml']) { + expand replaceProperties + } } // Example configuration to allow publishing using the maven-publish plugin publishing { - publications { - register('mavenJava', MavenPublication) { - from components.java - } - } - repositories { - maven { - url "file://${project.projectDir}/repo" - } - } + publications { + register('mavenJava', MavenPublication) { + from components.java + } + } + repositories { + maven { + url "file://${project.projectDir}/repo" + } + } } tasks.withType(JavaCompile).configureEach { - options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation + options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation } // IDEA no longer automatically downloads sources/javadoc jars for dependencies, so we need to explicitly enable the behavior. idea { - module { - downloadSources = true - downloadJavadoc = true - } + module { + downloadSources = true + downloadJavadoc = true + } } diff --git a/src/main/java/dev/exhq/ajarc/Ajar.java b/src/main/java/dev/exhq/ajarc/Ajar.java index 20e2e13..9a4b177 100644 --- a/src/main/java/dev/exhq/ajarc/Ajar.java +++ b/src/main/java/dev/exhq/ajarc/Ajar.java @@ -2,6 +2,7 @@ package dev.exhq.ajarc; import dev.exhq.ajarc.config.Config; import dev.exhq.ajarc.register.Register; +import net.minecraft.resources.ResourceLocation; import org.slf4j.Logger; import com.mojang.logging.LogUtils; @@ -20,4 +21,8 @@ public class Ajar { Register.registerAll(modEventBus); modContainer.registerConfig(ModConfig.Type.COMMON, Config.SPEC); } + + public static ResourceLocation identifier(String path) { + return ResourceLocation.fromNamespaceAndPath(MODID, path); + } } diff --git a/src/main/java/dev/exhq/ajarc/ClientEvents.java b/src/main/java/dev/exhq/ajarc/ClientEvents.java new file mode 100644 index 0000000..4783e1b --- /dev/null +++ b/src/main/java/dev/exhq/ajarc/ClientEvents.java @@ -0,0 +1,16 @@ +package dev.exhq.ajarc; + +import dev.exhq.ajarc.computer.ComputerScreen; +import dev.exhq.ajarc.register.Register; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.neoforge.client.event.RegisterMenuScreensEvent; + +@EventBusSubscriber(bus = EventBusSubscriber.Bus.MOD, modid = Ajar.MODID, value = Dist.CLIENT) +public class ClientEvents { + @SubscribeEvent + public static void registerScreens(RegisterMenuScreensEvent event) { + event.register(Register.COMPUTER_MENU.get(), ComputerScreen::new); + } +} diff --git a/src/main/java/dev/exhq/ajarc/ModEvents.java b/src/main/java/dev/exhq/ajarc/ModEvents.java new file mode 100644 index 0000000..1d32ddc --- /dev/null +++ b/src/main/java/dev/exhq/ajarc/ModEvents.java @@ -0,0 +1,16 @@ +package dev.exhq.ajarc; + +import dev.exhq.ajarc.network.ComputerScreenUpdate; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent; + +@EventBusSubscriber(bus = EventBusSubscriber.Bus.MOD, modid = Ajar.MODID) +public class ModEvents { + @SubscribeEvent + public static void register(RegisterPayloadHandlersEvent event) { + var registrar = event.registrar("1"); + registrar.playToClient(ComputerScreenUpdate.TYPE, ComputerScreenUpdate.STREAM_CODEC, ComputerScreenUpdate::handle); + } + +} diff --git a/src/main/java/dev/exhq/ajarc/computer/ComputerBlock.java b/src/main/java/dev/exhq/ajarc/computer/ComputerBlock.java index 1e74985..c325a08 100644 --- a/src/main/java/dev/exhq/ajarc/computer/ComputerBlock.java +++ b/src/main/java/dev/exhq/ajarc/computer/ComputerBlock.java @@ -2,7 +2,9 @@ package dev.exhq.ajarc.computer; import dev.exhq.ajarc.register.NeaBlock; import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; import net.minecraft.world.InteractionResult; +import net.minecraft.world.SimpleMenuProvider; import net.minecraft.world.entity.player.Player; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.EntityBlock; @@ -30,6 +32,8 @@ public class ComputerBlock extends NeaBlock implements EntityBlock { @NotNull BlockPos pPos, @NotNull Player pPlayer, @NotNull BlockHitResult pHitResult) { if (pLevel.isClientSide()) return InteractionResult.SUCCESS; + var blockEntity = (ComputerBlockEntity) pLevel.getBlockEntity(pPos); + blockEntity.openMenu(pPlayer); return InteractionResult.CONSUME; } } diff --git a/src/main/java/dev/exhq/ajarc/computer/ComputerBlockEntity.java b/src/main/java/dev/exhq/ajarc/computer/ComputerBlockEntity.java index 1736766..4df4d71 100644 --- a/src/main/java/dev/exhq/ajarc/computer/ComputerBlockEntity.java +++ b/src/main/java/dev/exhq/ajarc/computer/ComputerBlockEntity.java @@ -1,38 +1,71 @@ package dev.exhq.ajarc.computer; +import com.mojang.serialization.MapCodec; import dev.exhq.ajarc.Ajar; +import dev.exhq.ajarc.network.ComputerScreenUpdate; import dev.exhq.ajarc.register.Register; import net.minecraft.core.BlockPos; import net.minecraft.core.HolderLookup; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtOps; +import net.minecraft.network.chat.Component; +import net.minecraft.world.SimpleMenuProvider; +import net.minecraft.world.entity.player.Player; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; import org.jetbrains.annotations.NotNull; +import java.util.Arrays; + public class ComputerBlockEntity extends BlockEntity { public ComputerBlockEntity(BlockPos pPos, BlockState pBlockState) { super(Register.COMPUTER_BLOCK_ENTITY.get(), pPos, pBlockState); } private AjarFileSystem fileSystem = AjarFileSystem.ofBlank(); + private ComputerTerminal screen = ComputerTerminal.ofSize(20, 30); + + private static final MapCodec fileSystemCodec = AjarFileSystem.CODEC + .fieldOf("fileSystem") + .setPartial(AjarFileSystem::ofBlank); + private static final MapCodec screenCodec = ComputerTerminal.CODEC + .fieldOf("screen") + .setPartial(() -> ComputerTerminal.ofSize(20, 30)); @Override protected void loadAdditional(@NotNull CompoundTag pTag, @NotNull HolderLookup.Provider pRegistries) { super.loadAdditional(pTag, pRegistries); - var compound = pTag.getCompound("fileSystem"); - fileSystem = AjarFileSystem.CODEC.codec() - .parse(NbtOps.INSTANCE, compound) - .resultOrPartial(Ajar.LOGGER::error) - .orElseGet(AjarFileSystem::ofBlank); + var mapLike = NbtOps.INSTANCE.getMap(pTag).getOrThrow(); + fileSystem = fileSystemCodec.decode(NbtOps.INSTANCE, mapLike) + .promotePartial(Ajar.LOGGER::error) + .getPartialOrThrow(); + screen = screenCodec.decode(NbtOps.INSTANCE, mapLike) + .promotePartial(Ajar.LOGGER::error) + .getOrThrow(); } @Override protected void saveAdditional(@NotNull CompoundTag pTag, HolderLookup.@NotNull Provider pRegistries) { super.saveAdditional(pTag, pRegistries); - pTag.put("fileSystem", - AjarFileSystem.CODEC - .codec().encodeStart(NbtOps.INSTANCE, fileSystem) - .getOrThrow()); + var builder = NbtOps.INSTANCE.mapBuilder(); + fileSystemCodec.encode(fileSystem, NbtOps.INSTANCE, builder); + screenCodec.encode(screen, NbtOps.INSTANCE, builder); + builder.build((CompoundTag) null).resultOrPartial(Ajar.LOGGER::error) + .ifPresent(it -> pTag.merge((CompoundTag) it)); + } + + public void openMenu(Player pPlayer) { + pPlayer.openMenu(new SimpleMenuProvider( + (pContainerId, pPlayerInventory, pPlayer1) -> new ComputerMenu(pContainerId, this), + Component.translatable("ajarc.computer.screen") + ), buf -> ComputerScreenUpdate.STREAM_CODEC.encode(buf, getSyncPacket(0))); + } + + public ComputerTerminal getTerminal() { + return screen; + } + + public ComputerScreenUpdate getSyncPacket(int windowId) { + return new ComputerScreenUpdate(windowId, Arrays.asList("Hiii", "Hello"), screen.dimensions().rows(), screen.dimensions().columns()); } } diff --git a/src/main/java/dev/exhq/ajarc/computer/ComputerMenu.java b/src/main/java/dev/exhq/ajarc/computer/ComputerMenu.java new file mode 100644 index 0000000..59d29b5 --- /dev/null +++ b/src/main/java/dev/exhq/ajarc/computer/ComputerMenu.java @@ -0,0 +1,71 @@ +package dev.exhq.ajarc.computer; + +import dev.exhq.ajarc.network.ComputerScreenUpdate; +import dev.exhq.ajarc.register.Register; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.item.ItemStack; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +public class ComputerMenu extends AbstractContainerMenu { + + private ComputerBlockEntity computer; + private int columns = 80; + private int rows = 40; + private List lines = new ArrayList<>(); + + public ComputerMenu(int pContainerId, ComputerBlockEntity computer) { + this(pContainerId); + this.computer = computer; + this.syncFromComputer(); + } + + public ComputerMenu(int pContainerId) { + super(Register.COMPUTER_MENU.get(), pContainerId); + } + + @Override + public @NotNull ItemStack quickMoveStack(@NotNull Player pPlayer, int pIndex) { + return ItemStack.EMPTY; + } + + @Override + public boolean stillValid(@NotNull Player pPlayer) { + return true; + } + + public int getColumns() { + return columns; + } + + public int getRows() { + return rows; + } + + public List getLines() { + return lines; + } + + + public void syncFromComputer() { + // TODO: make this automatic using some mechanism + // Maybe compare an int and update that int everytime a change is done in the computer + // Maybe even replace all that with getUpdatePacket() and read from the block entity + this.columns = computer.getTerminal().dimensions().columns(); + this.rows = computer.getTerminal().dimensions().rows(); + } + + public void syncToClient(ServerPlayer player) { + player.connection.send(this.computer.getSyncPacket(containerId)); + } + + public void updateScreen(ComputerScreenUpdate computerScreenUpdate) { + this.columns = computerScreenUpdate.columns(); + this.rows = computerScreenUpdate.rows(); + this.lines = computerScreenUpdate.lines(); + } +} diff --git a/src/main/java/dev/exhq/ajarc/computer/ComputerMenuFactory.java b/src/main/java/dev/exhq/ajarc/computer/ComputerMenuFactory.java new file mode 100644 index 0000000..76e6664 --- /dev/null +++ b/src/main/java/dev/exhq/ajarc/computer/ComputerMenuFactory.java @@ -0,0 +1,17 @@ +package dev.exhq.ajarc.computer; + +import dev.exhq.ajarc.network.ComputerScreenUpdate; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.world.entity.player.Inventory; +import net.neoforged.neoforge.network.IContainerFactory; +import org.jetbrains.annotations.NotNull; + +public class ComputerMenuFactory implements IContainerFactory { + @Override + public @NotNull ComputerMenu create(int pContainerId, @NotNull Inventory pPlayerInventory, @NotNull RegistryFriendlyByteBuf data) { + var menu = new ComputerMenu(pContainerId); + var update = ComputerScreenUpdate.STREAM_CODEC.decode(data); + menu.updateScreen(update); + return menu; + } +} diff --git a/src/main/java/dev/exhq/ajarc/computer/ComputerScreen.java b/src/main/java/dev/exhq/ajarc/computer/ComputerScreen.java new file mode 100644 index 0000000..230ef6e --- /dev/null +++ b/src/main/java/dev/exhq/ajarc/computer/ComputerScreen.java @@ -0,0 +1,36 @@ +package dev.exhq.ajarc.computer; + +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; +import net.minecraft.network.chat.Component; +import net.minecraft.world.entity.player.Inventory; +import org.jetbrains.annotations.NotNull; + +public class ComputerScreen extends AbstractContainerScreen { + public ComputerScreen(ComputerMenu pMenu, Inventory pPlayerInventory, Component pTitle) { + super(pMenu, pPlayerInventory, pTitle); + } + + @Override + protected void init() { + imageHeight = menu.getRows() * 11; + imageWidth = menu.getColumns() * 10; + super.init(); + } + + @Override + protected void renderBg(@NotNull GuiGraphics graphics, float pPartialTick, int pMouseX, int pMouseY) { + graphics.fill(leftPos, topPos, leftPos + imageWidth, topPos + imageHeight, + 0xFF000000); + + } + + @Override + protected void renderLabels(@NotNull GuiGraphics graphics, int pMouseX, int pMouseY) { + int offsetY = 0; + for (String line : menu.getLines()) { + graphics.drawString(font, line, 0, offsetY, 0xFFFFFFFF); + offsetY += 11; + } + } +} diff --git a/src/main/java/dev/exhq/ajarc/computer/ComputerTerminal.java b/src/main/java/dev/exhq/ajarc/computer/ComputerTerminal.java new file mode 100644 index 0000000..44ed40d --- /dev/null +++ b/src/main/java/dev/exhq/ajarc/computer/ComputerTerminal.java @@ -0,0 +1,35 @@ +package dev.exhq.ajarc.computer; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.Util; + +import java.util.List; + +public record ComputerTerminal( + Dimensions dimensions +) { + public static ComputerTerminal ofSize(int rows, int columns) { + return new ComputerTerminal(new Dimensions(rows, columns)); + } + + public record Dimensions( + int rows, int columns + ) { + public static final Codec CODEC = + Codec.INT.listOf() + .comapFlatMap( + list -> Util.fixedSize(list, 2) + .map(fixedList -> new Dimensions(fixedList.get(0), fixedList.get(1))), + dimensions -> List.of(dimensions.rows, dimensions.columns) + ); + } + + public static final Codec CODEC = + RecordCodecBuilder.create( + builder -> + builder.group( + ComputerTerminal.Dimensions.CODEC.fieldOf("dimensions").forGetter(ComputerTerminal::dimensions) + ).apply(builder, ComputerTerminal::new)); +} + diff --git a/src/main/java/dev/exhq/ajarc/network/ComputerScreenUpdate.java b/src/main/java/dev/exhq/ajarc/network/ComputerScreenUpdate.java new file mode 100644 index 0000000..c45ae1a --- /dev/null +++ b/src/main/java/dev/exhq/ajarc/network/ComputerScreenUpdate.java @@ -0,0 +1,48 @@ +package dev.exhq.ajarc.network; + +import dev.exhq.ajarc.Ajar; +import dev.exhq.ajarc.computer.ComputerMenu; +import io.netty.buffer.ByteBuf; +import net.minecraft.network.codec.ByteBufCodecs; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.neoforged.neoforge.network.handling.IPayloadContext; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +public record ComputerScreenUpdate( + int windowId, + List lines, + int rows, + int columns +) implements CustomPacketPayload { + public static final StreamCodec STREAM_CODEC = + StreamCodec.composite( + ByteBufCodecs.VAR_INT, + ComputerScreenUpdate::windowId, + ByteBufCodecs.collection(ArrayList::new, ByteBufCodecs.STRING_UTF8), + ComputerScreenUpdate::lines, + ByteBufCodecs.VAR_INT, + ComputerScreenUpdate::rows, + ByteBufCodecs.VAR_INT, + ComputerScreenUpdate::columns, + ComputerScreenUpdate::new + ); + + public static final Type TYPE = new Type<>(Ajar.identifier("screen")); + + @Override + public @NotNull Type type() { + return TYPE; + } + + public void handle(IPayloadContext iPayloadContext) { + var menu = iPayloadContext.player().containerMenu; + if (menu instanceof ComputerMenu computerMenu && computerMenu.containerId == windowId) { + computerMenu.updateScreen(this); + } + } +} + diff --git a/src/main/java/dev/exhq/ajarc/register/Register.java b/src/main/java/dev/exhq/ajarc/register/Register.java index 9d6fb97..f35717b 100644 --- a/src/main/java/dev/exhq/ajarc/register/Register.java +++ b/src/main/java/dev/exhq/ajarc/register/Register.java @@ -2,10 +2,13 @@ package dev.exhq.ajarc.register; import dev.exhq.ajarc.computer.ComputerBlock; import dev.exhq.ajarc.computer.ComputerBlockEntity; +import dev.exhq.ajarc.computer.ComputerMenu; +import dev.exhq.ajarc.computer.ComputerMenuFactory; import net.minecraft.core.Holder; import net.minecraft.core.registries.Registries; import net.minecraft.network.chat.Component; import net.minecraft.world.food.FoodProperties; +import net.minecraft.world.inventory.MenuType; import net.minecraft.world.item.CreativeModeTab; import net.minecraft.world.item.CreativeModeTabs; import net.minecraft.world.item.Item; @@ -13,6 +16,7 @@ import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.state.BlockBehaviour; import net.minecraft.world.level.material.MapColor; import net.neoforged.bus.api.IEventBus; +import net.neoforged.neoforge.common.extensions.IMenuTypeExtension; import net.neoforged.neoforge.registries.DeferredBlock; import net.neoforged.neoforge.registries.DeferredHolder; import net.neoforged.neoforge.registries.DeferredItem; @@ -44,6 +48,8 @@ public class Register { registry(DeferredRegister.create(Registries.CREATIVE_MODE_TAB, MODID)); public static final DeferredRegister> BLOCK_ENTITIES = registry(DeferredRegister.create(Registries.BLOCK_ENTITY_TYPE, MODID)); + public static final DeferredRegister> MENU_TYPES = + registry(DeferredRegister.create(Registries.MENU, MODID)); /// /// @@ -77,6 +83,8 @@ public class Register { /// + public static final Supplier> COMPUTER_MENU = MENU_TYPES.register("computer", () -> IMenuTypeExtension.create(new ComputerMenuFactory())); + public static final DeferredHolder EXAMPLE_TAB = CREATIVE_MODE_TABS.register( "basic_tab", @@ -84,7 +92,7 @@ public class Register { .builder() .title(Component.translatable("itemGroup.ajarcomputers.basic_tab")) .withTabsBefore(CreativeModeTabs.COMBAT) - .icon(() -> EXAMPLE_ITEM.get().getDefaultInstance()) + .icon(() -> EXAMPLE_BLOCK.asItem().getDefaultInstance()) .displayItems((parameters, output) -> { ITEMS.getEntries() .stream() diff --git a/src/main/java/dev/exhq/ajarc/util/AjarCodecUtil.java b/src/main/java/dev/exhq/ajarc/util/AjarCodecUtil.java new file mode 100644 index 0000000..13f266e --- /dev/null +++ b/src/main/java/dev/exhq/ajarc/util/AjarCodecUtil.java @@ -0,0 +1,22 @@ +package dev.exhq.ajarc.util; + +import com.mojang.serialization.DataResult; +import com.mojang.serialization.DynamicOps; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.MapLike; +import org.slf4j.Logger; + +import java.util.Optional; + +public class AjarCodecUtil { + public static Optional getOrPartialAndLog(DataResult result, Logger logger) { + return result.resultOrPartial(logger::error); + } + + public static T parseVariable( + MapCodec codec, DynamicOps ops, MapLike mapLike, Logger logger) { + return getOrPartialAndLog(codec.decode(ops, mapLike), logger) + .orElseThrow(() -> new IllegalArgumentException("Provided MapCodec does not have a partial")); + } + +} diff --git a/src/main/java/dev/exhq/ajarc/util/NbtMergingRecordBuilder.java b/src/main/java/dev/exhq/ajarc/util/NbtMergingRecordBuilder.java new file mode 100644 index 0000000..39969b3 --- /dev/null +++ b/src/main/java/dev/exhq/ajarc/util/NbtMergingRecordBuilder.java @@ -0,0 +1,88 @@ +package dev.exhq.ajarc.util; + +import com.mojang.serialization.DataResult; +import com.mojang.serialization.DynamicOps; +import com.mojang.serialization.Lifecycle; +import com.mojang.serialization.RecordBuilder; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtOps; +import net.minecraft.nbt.Tag; + +import java.util.function.UnaryOperator; + +public class NbtMergingRecordBuilder implements RecordBuilder { + private DataResult result; + private final CompoundTag tag; + + private NbtMergingRecordBuilder(CompoundTag tag) { + this.result = DataResult.success(tag); + this.tag = tag; + } + + public static NbtMergingRecordBuilder ofWrapping(CompoundTag tag) { + return new NbtMergingRecordBuilder(tag); + } + + + private DataResult mergeError(DataResult errorBearer) { + if (errorBearer.isError()) { + result = result.flatMap(partialValue -> errorBearer.map(ignored -> partialValue)); + } + return errorBearer; + } + + @Override + public DynamicOps ops() { + return NbtOps.INSTANCE; + } + + @Override + public RecordBuilder add(Tag key, Tag value) { + return add(key, DataResult.success(value)); + } + + @Override + public RecordBuilder add(Tag key, DataResult value) { + return add(DataResult.success(key), value); + } + + @Override + public RecordBuilder add(DataResult key, DataResult value) { + DataResult stringValue = mergeError(key.flatMap(ops()::getStringValue)); + mergeError(value); + value.ifSuccess( + unboxedValue -> + stringValue.ifSuccess( + stringKey -> { + if (tag.contains(stringKey)) + mergeError(DataResult.error( + () -> "Found duplicate key " + stringKey + " during nbt record building")); + else tag.put(stringKey, unboxedValue); + } + )); + return this; + } + + @Override + public RecordBuilder withErrorsFrom(DataResult result) { + mergeError(result); + return this; + } + + @Override + public RecordBuilder setLifecycle(Lifecycle lifecycle) { + result.setLifecycle(lifecycle); + return this; + } + + @Override + public RecordBuilder mapError(UnaryOperator onError) { + result.mapError(onError); + return this; + } + + @Override + public DataResult build(Tag prefix) { + return result.map(it -> it); + } +}