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

@ -74,7 +74,8 @@ runs {
// workingDirectory project.file('run-data') // workingDirectory project.file('run-data')
// Specify the modid for data generation, where to output the resulting resource, and where to look for existing resources. // 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() programArguments.addAll '--mod', project.mod_id, '--all', '--output', file('src/generated/resources/').getAbsolutePath(),
'--existing', file('src/main/resources/').getAbsolutePath()
} }
} }

View file

@ -2,6 +2,7 @@ package dev.exhq.ajarc;
import dev.exhq.ajarc.config.Config; import dev.exhq.ajarc.config.Config;
import dev.exhq.ajarc.register.Register; import dev.exhq.ajarc.register.Register;
import net.minecraft.resources.ResourceLocation;
import org.slf4j.Logger; import org.slf4j.Logger;
import com.mojang.logging.LogUtils; import com.mojang.logging.LogUtils;
@ -20,4 +21,8 @@ public class Ajar {
Register.registerAll(modEventBus); Register.registerAll(modEventBus);
modContainer.registerConfig(ModConfig.Type.COMMON, Config.SPEC); 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 dev.exhq.ajarc.register.NeaBlock;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.world.InteractionResult; import net.minecraft.world.InteractionResult;
import net.minecraft.world.SimpleMenuProvider;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.EntityBlock; 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 BlockPos pPos, @NotNull Player pPlayer,
@NotNull BlockHitResult pHitResult) { @NotNull BlockHitResult pHitResult) {
if (pLevel.isClientSide()) return InteractionResult.SUCCESS; if (pLevel.isClientSide()) return InteractionResult.SUCCESS;
var blockEntity = (ComputerBlockEntity) pLevel.getBlockEntity(pPos);
blockEntity.openMenu(pPlayer);
return InteractionResult.CONSUME; return InteractionResult.CONSUME;
} }
} }

View file

@ -1,38 +1,71 @@
package dev.exhq.ajarc.computer; package dev.exhq.ajarc.computer;
import com.mojang.serialization.MapCodec;
import dev.exhq.ajarc.Ajar; import dev.exhq.ajarc.Ajar;
import dev.exhq.ajarc.network.ComputerScreenUpdate;
import dev.exhq.ajarc.register.Register; import dev.exhq.ajarc.register.Register;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.HolderLookup; import net.minecraft.core.HolderLookup;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtOps; 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.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
public class ComputerBlockEntity extends BlockEntity { public class ComputerBlockEntity extends BlockEntity {
public ComputerBlockEntity(BlockPos pPos, BlockState pBlockState) { public ComputerBlockEntity(BlockPos pPos, BlockState pBlockState) {
super(Register.COMPUTER_BLOCK_ENTITY.get(), pPos, pBlockState); super(Register.COMPUTER_BLOCK_ENTITY.get(), pPos, pBlockState);
} }
private AjarFileSystem fileSystem = AjarFileSystem.ofBlank(); 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 @Override
protected void loadAdditional(@NotNull CompoundTag pTag, @NotNull HolderLookup.Provider pRegistries) { protected void loadAdditional(@NotNull CompoundTag pTag, @NotNull HolderLookup.Provider pRegistries) {
super.loadAdditional(pTag, pRegistries); super.loadAdditional(pTag, pRegistries);
var compound = pTag.getCompound("fileSystem"); var mapLike = NbtOps.INSTANCE.getMap(pTag).getOrThrow();
fileSystem = AjarFileSystem.CODEC.codec() fileSystem = fileSystemCodec.decode(NbtOps.INSTANCE, mapLike)
.parse(NbtOps.INSTANCE, compound) .promotePartial(Ajar.LOGGER::error)
.resultOrPartial(Ajar.LOGGER::error) .getPartialOrThrow();
.orElseGet(AjarFileSystem::ofBlank); screen = screenCodec.decode(NbtOps.INSTANCE, mapLike)
.promotePartial(Ajar.LOGGER::error)
.getOrThrow();
} }
@Override @Override
protected void saveAdditional(@NotNull CompoundTag pTag, HolderLookup.@NotNull Provider pRegistries) { protected void saveAdditional(@NotNull CompoundTag pTag, HolderLookup.@NotNull Provider pRegistries) {
super.saveAdditional(pTag, pRegistries); super.saveAdditional(pTag, pRegistries);
pTag.put("fileSystem", var builder = NbtOps.INSTANCE.mapBuilder();
AjarFileSystem.CODEC fileSystemCodec.encode(fileSystem, NbtOps.INSTANCE, builder);
.codec().encodeStart(NbtOps.INSTANCE, fileSystem) screenCodec.encode(screen, NbtOps.INSTANCE, builder);
.getOrThrow()); 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.ComputerBlock;
import dev.exhq.ajarc.computer.ComputerBlockEntity; 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.Holder;
import net.minecraft.core.registries.Registries; import net.minecraft.core.registries.Registries;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.world.food.FoodProperties; import net.minecraft.world.food.FoodProperties;
import net.minecraft.world.inventory.MenuType;
import net.minecraft.world.item.CreativeModeTab; import net.minecraft.world.item.CreativeModeTab;
import net.minecraft.world.item.CreativeModeTabs; import net.minecraft.world.item.CreativeModeTabs;
import net.minecraft.world.item.Item; 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.block.state.BlockBehaviour;
import net.minecraft.world.level.material.MapColor; import net.minecraft.world.level.material.MapColor;
import net.neoforged.bus.api.IEventBus; import net.neoforged.bus.api.IEventBus;
import net.neoforged.neoforge.common.extensions.IMenuTypeExtension;
import net.neoforged.neoforge.registries.DeferredBlock; import net.neoforged.neoforge.registries.DeferredBlock;
import net.neoforged.neoforge.registries.DeferredHolder; import net.neoforged.neoforge.registries.DeferredHolder;
import net.neoforged.neoforge.registries.DeferredItem; import net.neoforged.neoforge.registries.DeferredItem;
@ -44,6 +48,8 @@ public class Register {
registry(DeferredRegister.create(Registries.CREATIVE_MODE_TAB, MODID)); registry(DeferredRegister.create(Registries.CREATIVE_MODE_TAB, MODID));
public static final DeferredRegister<BlockEntityType<?>> BLOCK_ENTITIES = public static final DeferredRegister<BlockEntityType<?>> BLOCK_ENTITIES =
registry(DeferredRegister.create(Registries.BLOCK_ENTITY_TYPE, MODID)); 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>
/// <editor-fold desc="Registration helpers" defaultstate="collapsed"> /// <editor-fold desc="Registration helpers" defaultstate="collapsed">
@ -77,6 +83,8 @@ public class Register {
/// </editor-fold> /// </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 = public static final DeferredHolder<CreativeModeTab, CreativeModeTab> EXAMPLE_TAB =
CREATIVE_MODE_TABS.register( CREATIVE_MODE_TABS.register(
"basic_tab", "basic_tab",
@ -84,7 +92,7 @@ public class Register {
.builder() .builder()
.title(Component.translatable("itemGroup.ajarcomputers.basic_tab")) .title(Component.translatable("itemGroup.ajarcomputers.basic_tab"))
.withTabsBefore(CreativeModeTabs.COMBAT) .withTabsBefore(CreativeModeTabs.COMBAT)
.icon(() -> EXAMPLE_ITEM.get().getDefaultInstance()) .icon(() -> EXAMPLE_BLOCK.asItem().getDefaultInstance())
.displayItems((parameters, output) -> { .displayItems((parameters, output) -> {
ITEMS.getEntries() ITEMS.getEntries()
.stream() .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);
}
}