Add screen

This commit is contained in:
Linnea Gräf 2024-07-16 19:06:49 +02:00
parent d7ade1a759
commit a86c1086b7
No known key found for this signature in database
GPG key ID: AA563E93EB628D91
14 changed files with 514 additions and 114 deletions

View file

@ -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
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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<AjarFileSystem> fileSystemCodec = AjarFileSystem.CODEC
.fieldOf("fileSystem")
.setPartial(AjarFileSystem::ofBlank);
private static final MapCodec<ComputerTerminal> 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());
}
}

View file

@ -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<String> 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<String> 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();
}
}

View file

@ -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<ComputerMenu> {
@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;
}
}

View file

@ -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<ComputerMenu> {
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;
}
}
}

View file

@ -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<Dimensions> 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<ComputerTerminal> CODEC =
RecordCodecBuilder.create(
builder ->
builder.group(
ComputerTerminal.Dimensions.CODEC.fieldOf("dimensions").forGetter(ComputerTerminal::dimensions)
).apply(builder, ComputerTerminal::new));
}

View file

@ -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<String> lines,
int rows,
int columns
) implements CustomPacketPayload {
public static final StreamCodec<ByteBuf, ComputerScreenUpdate> 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<ComputerScreenUpdate> TYPE = new Type<>(Ajar.identifier("screen"));
@Override
public @NotNull Type<? extends CustomPacketPayload> type() {
return TYPE;
}
public void handle(IPayloadContext iPayloadContext) {
var menu = iPayloadContext.player().containerMenu;
if (menu instanceof ComputerMenu computerMenu && computerMenu.containerId == windowId) {
computerMenu.updateScreen(this);
}
}
}

View file

@ -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<BlockEntityType<?>> BLOCK_ENTITIES =
registry(DeferredRegister.create(Registries.BLOCK_ENTITY_TYPE, MODID));
public static final DeferredRegister<MenuType<?>> MENU_TYPES =
registry(DeferredRegister.create(Registries.MENU, MODID));
/// </editor-fold>
/// <editor-fold desc="Registration helpers" defaultstate="collapsed">
@ -77,6 +83,8 @@ public class Register {
/// </editor-fold>
public static final Supplier<MenuType<ComputerMenu>> COMPUTER_MENU = MENU_TYPES.register("computer", () -> IMenuTypeExtension.create(new ComputerMenuFactory()));
public static final DeferredHolder<CreativeModeTab, CreativeModeTab> 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()

View file

@ -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 <T> Optional<T> getOrPartialAndLog(DataResult<T> result, Logger logger) {
return result.resultOrPartial(logger::error);
}
public static <T, Ops> T parseVariable(
MapCodec<T> codec, DynamicOps<Ops> ops, MapLike<Ops> mapLike, Logger logger) {
return getOrPartialAndLog(codec.decode(ops, mapLike), logger)
.orElseThrow(() -> new IllegalArgumentException("Provided MapCodec does not have a partial"));
}
}

View file

@ -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<Tag> {
private DataResult<CompoundTag> 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 <T> DataResult<T> mergeError(DataResult<T> errorBearer) {
if (errorBearer.isError()) {
result = result.flatMap(partialValue -> errorBearer.map(ignored -> partialValue));
}
return errorBearer;
}
@Override
public DynamicOps<Tag> ops() {
return NbtOps.INSTANCE;
}
@Override
public RecordBuilder<Tag> add(Tag key, Tag value) {
return add(key, DataResult.success(value));
}
@Override
public RecordBuilder<Tag> add(Tag key, DataResult<Tag> value) {
return add(DataResult.success(key), value);
}
@Override
public RecordBuilder<Tag> add(DataResult<Tag> key, DataResult<Tag> value) {
DataResult<String> 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<Tag> withErrorsFrom(DataResult<?> result) {
mergeError(result);
return this;
}
@Override
public RecordBuilder<Tag> setLifecycle(Lifecycle lifecycle) {
result.setLifecycle(lifecycle);
return this;
}
@Override
public RecordBuilder<Tag> mapError(UnaryOperator<String> onError) {
result.mapError(onError);
return this;
}
@Override
public DataResult<Tag> build(Tag prefix) {
return result.map(it -> it);
}
}