1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-09-28 15:08:47 +00:00

Move all event handlers to a common class

While this does now involve a little more indirection, by having a
single location for all hooks it's much easier to keep event listeners
consistent between loaders.

The diff is pretty noisy (I've inlined some classes, and ClientRegistry
got a big restructure), but functionality should be the same.
This commit is contained in:
Jonathan Coates 2022-11-08 10:46:09 +00:00
parent e8f9cdd221
commit 0908acbe9b
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
29 changed files with 646 additions and 494 deletions

View File

@ -24,6 +24,7 @@ import net.minecraftforge.common.capabilities.ForgeCapabilities;
import net.minecraftforge.common.capabilities.RegisterCapabilitiesEvent; import net.minecraftforge.common.capabilities.RegisterCapabilitiesEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.config.ModConfigEvent;
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import net.minecraftforge.registries.NewRegistryEvent; import net.minecraftforge.registries.NewRegistryEvent;
import net.minecraftforge.registries.RegistryBuilder; import net.minecraftforge.registries.RegistryBuilder;
@ -113,4 +114,13 @@ public final class ComputerCraft {
ForgeDetailRegistries.FLUID_STACK.addProvider(FluidData::fill); ForgeDetailRegistries.FLUID_STACK.addProvider(FluidData::fill);
} }
@SubscribeEvent
public static void sync(ModConfigEvent.Loading event) {
Config.sync(event.getConfig());
}
@SubscribeEvent
public static void sync(ModConfigEvent.Reloading event) {
Config.sync(event.getConfig());
}
} }

View File

@ -5,33 +5,158 @@
*/ */
package dan200.computercraft.client; package dan200.computercraft.client;
import dan200.computercraft.ComputerCraft; import com.mojang.blaze3d.audio.Channel;
import com.mojang.blaze3d.vertex.PoseStack;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.client.pocket.ClientPocketComputers; import dan200.computercraft.client.pocket.ClientPocketComputers;
import dan200.computercraft.client.render.CableHighlightRenderer;
import dan200.computercraft.client.render.ItemPocketRenderer;
import dan200.computercraft.client.render.ItemPrintoutRenderer;
import dan200.computercraft.client.render.MonitorHighlightRenderer;
import dan200.computercraft.client.sound.SpeakerManager; import dan200.computercraft.client.sound.SpeakerManager;
import dan200.computercraft.shared.CommonHooks;
import dan200.computercraft.shared.command.CommandComputerCraft;
import dan200.computercraft.shared.computer.core.ServerContext;
import dan200.computercraft.shared.media.items.ItemPrintout;
import dan200.computercraft.shared.peripheral.monitor.ClientMonitor; import dan200.computercraft.shared.peripheral.monitor.ClientMonitor;
import net.minecraftforge.api.distmarker.Dist; import dan200.computercraft.shared.peripheral.monitor.TileMonitor;
import net.minecraftforge.client.event.ClientPlayerNetworkEvent; import dan200.computercraft.shared.pocket.items.ItemPocketComputer;
import net.minecraftforge.event.level.LevelEvent; import dan200.computercraft.shared.turtle.blocks.TileTurtle;
import net.minecraftforge.eventbus.api.SubscribeEvent; import dan200.computercraft.shared.util.PauseAwareTimer;
import net.minecraftforge.fml.common.Mod; import net.minecraft.Util;
import net.minecraft.client.Camera;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.sounds.AudioStream;
import net.minecraft.client.sounds.SoundEngine;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.decoration.ItemFrame;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
@Mod.EventBusSubscriber(modid = ComputerCraft.MOD_ID, value = Dist.CLIENT) import java.io.File;
public class ClientHooks { import java.util.function.Consumer;
@SubscribeEvent
public static void onWorldUnload(LevelEvent.Unload event) { /**
if (event.getLevel().isClientSide()) { * Event listeners for client-only code.
ClientMonitor.destroyAll(); * <p>
SpeakerManager.reset(); * This is the client-only version of {@link CommonHooks}, and so should be where all client-specific event handlers are
* defined.
*/
public final class ClientHooks {
private ClientHooks() {
}
public static void onTick() {
FrameInfo.onTick();
}
public static void onRenderTick() {
PauseAwareTimer.tick(Minecraft.getInstance().isPaused());
FrameInfo.onRenderTick();
}
public static void onWorldUnload() {
ClientMonitor.destroyAll();
SpeakerManager.reset();
ClientPocketComputers.reset();
}
public static boolean onChatMessage(String message) {
return handleOpenComputerCommand(message);
}
public static boolean drawHighlight(PoseStack transform, MultiBufferSource bufferSource, Camera camera, BlockHitResult hit) {
return CableHighlightRenderer.drawHighlight(transform, bufferSource, camera, hit)
|| MonitorHighlightRenderer.drawHighlight(transform, bufferSource, camera, hit);
}
public static boolean onRenderHeldItem(
PoseStack transform, MultiBufferSource render, int lightTexture, InteractionHand hand,
float pitch, float equipProgress, float swingProgress, ItemStack stack
) {
if (stack.getItem() instanceof ItemPocketComputer) {
ItemPocketRenderer.INSTANCE.renderItemFirstPerson(transform, render, lightTexture, hand, pitch, equipProgress, swingProgress, stack);
return true;
}
if (stack.getItem() instanceof ItemPrintout) {
ItemPrintoutRenderer.INSTANCE.renderItemFirstPerson(transform, render, lightTexture, hand, pitch, equipProgress, swingProgress, stack);
return true;
}
return false;
}
public static boolean onRenderItemFrame(PoseStack transform, MultiBufferSource render, ItemFrame frame, ItemStack stack, int light) {
if (stack.getItem() instanceof ItemPrintout) {
ItemPrintoutRenderer.onRenderInFrame(transform, render, frame, stack, light);
return true;
}
return false;
}
public static void onPlayStreaming(SoundEngine engine, Channel channel, AudioStream stream) {
SpeakerManager.onPlayStreaming(engine, channel, stream);
}
/**
* Handle the {@link CommandComputerCraft#OPEN_COMPUTER} "clientside command". This isn't a true command, as we
* don't want it to actually be visible to the user.
*
* @param message The current chat message.
* @return Whether to cancel sending this message.
*/
private static boolean handleOpenComputerCommand(String message) {
if (!message.startsWith(CommandComputerCraft.OPEN_COMPUTER)) return false;
var server = Minecraft.getInstance().getSingleplayerServer();
if (server == null) return false;
var idStr = message.substring(CommandComputerCraft.OPEN_COMPUTER.length()).trim();
int id;
try {
id = Integer.parseInt(idStr);
} catch (NumberFormatException ignore) {
return false;
}
var file = new File(ServerContext.get(server).storageDir().toFile(), "computer/" + id);
if (!file.isDirectory()) return false;
Util.getPlatform().openFile(file);
return true;
}
/**
* Add additional information about the currently targeted block to the debug screen.
*
* @param addText A callback which adds a single line of text.
*/
public static void addDebugInfo(Consumer<String> addText) {
var minecraft = Minecraft.getInstance();
if (!minecraft.options.renderDebug || minecraft.level == null) return;
if (minecraft.hitResult == null || minecraft.hitResult.getType() != HitResult.Type.BLOCK) return;
var tile = minecraft.level.getBlockEntity(((BlockHitResult) minecraft.hitResult).getBlockPos());
if (tile instanceof TileMonitor monitor) {
addText.accept("");
addText.accept(
String.format("Targeted monitor: (%d, %d), %d x %d", monitor.getXIndex(), monitor.getYIndex(), monitor.getWidth(), monitor.getHeight())
);
} else if (tile instanceof TileTurtle turtle) {
addText.accept("");
addText.accept("Targeted turtle:");
addText.accept(String.format("Id: %d", turtle.getComputerID()));
addTurtleUpgrade(addText, turtle, TurtleSide.LEFT);
addTurtleUpgrade(addText, turtle, TurtleSide.RIGHT);
} }
} }
@SubscribeEvent private static void addTurtleUpgrade(Consumer<String> out, TileTurtle turtle, TurtleSide side) {
public static void onLogIn(ClientPlayerNetworkEvent.LoggingIn event) { var upgrade = turtle.getUpgrade(side);
ClientPocketComputers.reset(); if (upgrade != null) out.accept(String.format("Upgrade[%s]: %s", side, upgrade.getUpgradeID()));
}
@SubscribeEvent
public static void onLogOut(ClientPlayerNetworkEvent.LoggingOut event) {
ClientPocketComputers.reset();
} }
} }

View File

@ -10,9 +10,9 @@ import dan200.computercraft.api.client.ComputerCraftAPIClient;
import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller; import dan200.computercraft.api.client.turtle.TurtleUpgradeModeller;
import dan200.computercraft.client.gui.*; import dan200.computercraft.client.gui.*;
import dan200.computercraft.client.pocket.ClientPocketComputers; import dan200.computercraft.client.pocket.ClientPocketComputers;
import dan200.computercraft.client.render.RenderTypes;
import dan200.computercraft.client.render.TileEntityMonitorRenderer; import dan200.computercraft.client.render.TileEntityMonitorRenderer;
import dan200.computercraft.client.render.TileEntityTurtleRenderer; import dan200.computercraft.client.render.TileEntityTurtleRenderer;
import dan200.computercraft.client.render.TurtleModelLoader;
import dan200.computercraft.client.turtle.TurtleModemModeller; import dan200.computercraft.client.turtle.TurtleModemModeller;
import dan200.computercraft.core.util.Colour; import dan200.computercraft.core.util.Colour;
import dan200.computercraft.shared.ModRegistry; import dan200.computercraft.shared.ModRegistry;
@ -21,26 +21,86 @@ import dan200.computercraft.shared.computer.inventory.ContainerComputerBase;
import dan200.computercraft.shared.computer.inventory.ContainerViewComputer; import dan200.computercraft.shared.computer.inventory.ContainerViewComputer;
import dan200.computercraft.shared.media.items.ItemDisk; import dan200.computercraft.shared.media.items.ItemDisk;
import dan200.computercraft.shared.media.items.ItemTreasureDisk; import dan200.computercraft.shared.media.items.ItemTreasureDisk;
import net.minecraft.client.color.item.ItemColor;
import net.minecraft.client.gui.screens.MenuScreens; import net.minecraft.client.gui.screens.MenuScreens;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderers; import net.minecraft.client.renderer.ShaderInstance;
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
import net.minecraft.client.renderer.item.ItemProperties; import net.minecraft.client.renderer.item.ItemProperties;
import net.minecraft.client.renderer.item.ItemPropertyFunction; import net.minecraft.client.renderer.item.ItemPropertyFunction;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.world.item.Item; import net.minecraft.world.item.Item;
import net.minecraftforge.api.distmarker.Dist; import net.minecraft.world.item.ItemStack;
import net.minecraftforge.client.event.ModelEvent; import net.minecraft.world.level.ItemLike;
import net.minecraftforge.client.event.RegisterColorHandlersEvent; import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
import java.io.IOException;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Supplier; import java.util.function.Supplier;
/** /**
* Registers textures and models for items. * Registers client-side objects, such as {@link BlockEntityRendererProvider}s and
* {@link MenuScreens.ScreenConstructor}.
* <p>
* The functions in this class should be called from a loader-specific class.
*
* @see ModRegistry The common registry for actual game objects.
*/ */
@Mod.EventBusSubscriber(modid = ComputerCraft.MOD_ID, value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.MOD)
public final class ClientRegistry { public final class ClientRegistry {
private ClientRegistry() {
}
/**
* Register any client-side objects which don't have to be done on the main thread.
*/
public static void register() {
ComputerCraftAPIClient.registerTurtleUpgradeModeller(ModRegistry.TurtleSerialisers.SPEAKER.get(), TurtleUpgradeModeller.sided(
new ResourceLocation(ComputerCraft.MOD_ID, "block/turtle_speaker_left"),
new ResourceLocation(ComputerCraft.MOD_ID, "block/turtle_speaker_right")
));
ComputerCraftAPIClient.registerTurtleUpgradeModeller(ModRegistry.TurtleSerialisers.WORKBENCH.get(), TurtleUpgradeModeller.sided(
new ResourceLocation(ComputerCraft.MOD_ID, "block/turtle_crafting_table_left"),
new ResourceLocation(ComputerCraft.MOD_ID, "block/turtle_crafting_table_right")
));
ComputerCraftAPIClient.registerTurtleUpgradeModeller(ModRegistry.TurtleSerialisers.WIRELESS_MODEM_NORMAL.get(), new TurtleModemModeller(false));
ComputerCraftAPIClient.registerTurtleUpgradeModeller(ModRegistry.TurtleSerialisers.WIRELESS_MODEM_ADVANCED.get(), new TurtleModemModeller(true));
ComputerCraftAPIClient.registerTurtleUpgradeModeller(ModRegistry.TurtleSerialisers.TOOL.get(), TurtleUpgradeModeller.flatItem());
}
/**
* Register any client-side objects which must be done on the main thread.
*/
public static void registerMainThread() {
MenuScreens.<ContainerComputerBase, GuiComputer<ContainerComputerBase>>register(ModRegistry.Menus.COMPUTER.get(), GuiComputer::new);
MenuScreens.<ContainerComputerBase, GuiComputer<ContainerComputerBase>>register(ModRegistry.Menus.POCKET_COMPUTER.get(), GuiComputer::new);
MenuScreens.<ContainerComputerBase, NoTermComputerScreen<ContainerComputerBase>>register(ModRegistry.Menus.POCKET_COMPUTER_NO_TERM.get(), NoTermComputerScreen::new);
MenuScreens.register(ModRegistry.Menus.TURTLE.get(), GuiTurtle::new);
MenuScreens.register(ModRegistry.Menus.PRINTER.get(), GuiPrinter::new);
MenuScreens.register(ModRegistry.Menus.DISK_DRIVE.get(), GuiDiskDrive::new);
MenuScreens.register(ModRegistry.Menus.PRINTOUT.get(), GuiPrintout::new);
MenuScreens.<ContainerViewComputer, GuiComputer<ContainerViewComputer>>register(ModRegistry.Menus.VIEW_COMPUTER.get(), GuiComputer::new);
registerItemProperty("state",
(stack, world, player, random) -> ClientPocketComputers.get(stack).getState().ordinal(),
ModRegistry.Items.POCKET_COMPUTER_NORMAL, ModRegistry.Items.POCKET_COMPUTER_ADVANCED
);
registerItemProperty("coloured",
(stack, world, player, random) -> IColouredItem.getColourBasic(stack) != -1 ? 1 : 0,
ModRegistry.Items.POCKET_COMPUTER_NORMAL, ModRegistry.Items.POCKET_COMPUTER_ADVANCED
);
}
@SafeVarargs
@SuppressWarnings("deprecation")
private static void registerItemProperty(String name, ItemPropertyFunction getter, Supplier<? extends Item>... items) {
var id = new ResourceLocation(ComputerCraft.MOD_ID, name);
for (var item : items) ItemProperties.register(item.get(), id, getter);
}
private static final String[] EXTRA_MODELS = new String[]{ private static final String[] EXTRA_MODELS = new String[]{
// Turtle upgrades // Turtle upgrades
"block/turtle_modem_normal_off_left", "block/turtle_modem_normal_off_left",
@ -64,114 +124,63 @@ public final class ClientRegistry {
"block/turtle_elf_overlay", "block/turtle_elf_overlay",
}; };
private ClientRegistry() { public static void registerExtraModels(Consumer<ResourceLocation> register) {
for (var model : EXTRA_MODELS) register.accept(new ResourceLocation(ComputerCraft.MOD_ID, model));
} }
@SubscribeEvent public static void registerItemColours(BiConsumer<ItemColor, ItemLike> register) {
public static void registerModelLoaders(ModelEvent.RegisterGeometryLoaders event) {
event.register("turtle", TurtleModelLoader.INSTANCE);
}
@SubscribeEvent
public static void registerModels(ModelEvent.RegisterAdditional event) {
for (var model : EXTRA_MODELS) {
event.register(new ResourceLocation(ComputerCraft.MOD_ID, model));
}
}
@SubscribeEvent
public static void onItemColours(RegisterColorHandlersEvent.Item event) {
if (ModRegistry.Items.DISK == null || ModRegistry.Blocks.TURTLE_NORMAL == null) { if (ModRegistry.Items.DISK == null || ModRegistry.Blocks.TURTLE_NORMAL == null) {
ComputerCraft.log.warn("Block/item registration has failed. Skipping registration of item colours."); ComputerCraft.log.warn("Block/item registration has failed. Skipping registration of item colours.");
return; return;
} }
event.register( register.accept(
(stack, layer) -> layer == 1 ? ((ItemDisk) stack.getItem()).getColour(stack) : 0xFFFFFF, (stack, layer) -> layer == 1 ? ((ItemDisk) stack.getItem()).getColour(stack) : 0xFFFFFF,
ModRegistry.Items.DISK.get() ModRegistry.Items.DISK.get()
); );
event.register( register.accept(
(stack, layer) -> layer == 1 ? ItemTreasureDisk.getColour(stack) : 0xFFFFFF, (stack, layer) -> layer == 1 ? ItemTreasureDisk.getColour(stack) : 0xFFFFFF,
ModRegistry.Items.TREASURE_DISK.get() ModRegistry.Items.TREASURE_DISK.get()
); );
event.register((stack, layer) -> { register.accept(ClientRegistry::getPocketColour, ModRegistry.Items.POCKET_COMPUTER_NORMAL.get());
switch (layer) { register.accept(ClientRegistry::getPocketColour, ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get());
case 0:
default: register.accept(ClientRegistry::getTurtleColour, ModRegistry.Blocks.TURTLE_NORMAL.get());
return 0xFFFFFF; register.accept(ClientRegistry::getTurtleColour, ModRegistry.Blocks.TURTLE_ADVANCED.get());
case 1: // Frame colour }
return IColouredItem.getColourBasic(stack);
case 2: { // Light colour private static int getPocketColour(ItemStack stack, int layer) {
var light = ClientPocketComputers.get(stack).getLightState(); switch (layer) {
return light == -1 ? Colour.BLACK.getHex() : light; case 0:
} default:
return 0xFFFFFF;
case 1: // Frame colour
return IColouredItem.getColourBasic(stack);
case 2: { // Light colour
var light = ClientPocketComputers.get(stack).getLightState();
return light == -1 ? Colour.BLACK.getHex() : light;
} }
}, ModRegistry.Items.POCKET_COMPUTER_NORMAL.get(), ModRegistry.Items.POCKET_COMPUTER_ADVANCED.get());
// Setup turtle colours
event.register(
(stack, tintIndex) -> tintIndex == 0 ? ((IColouredItem) stack.getItem()).getColour(stack) : 0xFFFFFF,
ModRegistry.Blocks.TURTLE_NORMAL.get(), ModRegistry.Blocks.TURTLE_ADVANCED.get()
);
}
@SubscribeEvent
public static void setupClient(FMLClientSetupEvent event) {
// Setup TESRs
BlockEntityRenderers.register(ModRegistry.BlockEntities.MONITOR_NORMAL.get(), TileEntityMonitorRenderer::new);
BlockEntityRenderers.register(ModRegistry.BlockEntities.MONITOR_ADVANCED.get(), TileEntityMonitorRenderer::new);
BlockEntityRenderers.register(ModRegistry.BlockEntities.TURTLE_NORMAL.get(), TileEntityTurtleRenderer::new);
BlockEntityRenderers.register(ModRegistry.BlockEntities.TURTLE_ADVANCED.get(), TileEntityTurtleRenderer::new);
ComputerCraftAPIClient.registerTurtleUpgradeModeller(ModRegistry.TurtleSerialisers.SPEAKER.get(), TurtleUpgradeModeller.sided(
new ResourceLocation(ComputerCraft.MOD_ID, "block/turtle_speaker_left"),
new ResourceLocation(ComputerCraft.MOD_ID, "block/turtle_speaker_right")
));
ComputerCraftAPIClient.registerTurtleUpgradeModeller(ModRegistry.TurtleSerialisers.WORKBENCH.get(), TurtleUpgradeModeller.sided(
new ResourceLocation(ComputerCraft.MOD_ID, "block/turtle_crafting_table_left"),
new ResourceLocation(ComputerCraft.MOD_ID, "block/turtle_crafting_table_right")
));
ComputerCraftAPIClient.registerTurtleUpgradeModeller(ModRegistry.TurtleSerialisers.WIRELESS_MODEM_NORMAL.get(), new TurtleModemModeller(false));
ComputerCraftAPIClient.registerTurtleUpgradeModeller(ModRegistry.TurtleSerialisers.WIRELESS_MODEM_ADVANCED.get(), new TurtleModemModeller(true));
ComputerCraftAPIClient.registerTurtleUpgradeModeller(ModRegistry.TurtleSerialisers.TOOL.get(), TurtleUpgradeModeller.flatItem());
event.enqueueWork(() -> {
registerContainers();
registerItemProperty("state",
(stack, world, player, random) -> ClientPocketComputers.get(stack).getState().ordinal(),
ModRegistry.Items.POCKET_COMPUTER_NORMAL, ModRegistry.Items.POCKET_COMPUTER_ADVANCED
);
registerItemProperty("coloured",
(stack, world, player, random) -> IColouredItem.getColourBasic(stack) != -1 ? 1 : 0,
ModRegistry.Items.POCKET_COMPUTER_NORMAL, ModRegistry.Items.POCKET_COMPUTER_ADVANCED
);
});
}
@SafeVarargs
private static void registerItemProperty(String name, ItemPropertyFunction getter, Supplier<? extends Item>... items) {
var id = new ResourceLocation(ComputerCraft.MOD_ID, name);
for (var item : items) {
ItemProperties.register(item.get(), id, getter);
} }
} }
private static int getTurtleColour(ItemStack stack, int layer) {
return layer == 0 ? ((IColouredItem) stack.getItem()).getColour(stack) : 0xFFFFFF;
}
private static void registerContainers() { public static void registerBlockEntityRenderers(BlockEntityRenderRegistry register) {
// My IDE doesn't think so, but we do actually need these generics. register.register(ModRegistry.BlockEntities.MONITOR_NORMAL.get(), TileEntityMonitorRenderer::new);
register.register(ModRegistry.BlockEntities.MONITOR_ADVANCED.get(), TileEntityMonitorRenderer::new);
register.register(ModRegistry.BlockEntities.TURTLE_NORMAL.get(), TileEntityTurtleRenderer::new);
register.register(ModRegistry.BlockEntities.TURTLE_ADVANCED.get(), TileEntityTurtleRenderer::new);
}
MenuScreens.<ContainerComputerBase, GuiComputer<ContainerComputerBase>>register(ModRegistry.Menus.COMPUTER.get(), GuiComputer::new); public interface BlockEntityRenderRegistry {
MenuScreens.<ContainerComputerBase, GuiComputer<ContainerComputerBase>>register(ModRegistry.Menus.POCKET_COMPUTER.get(), GuiComputer::new); <T extends BlockEntity> void register(BlockEntityType<? extends T> type, BlockEntityRendererProvider<T> provider);
MenuScreens.<ContainerComputerBase, NoTermComputerScreen<ContainerComputerBase>>register(ModRegistry.Menus.POCKET_COMPUTER_NO_TERM.get(), NoTermComputerScreen::new); }
MenuScreens.register(ModRegistry.Menus.TURTLE.get(), GuiTurtle::new);
MenuScreens.register(ModRegistry.Menus.PRINTER.get(), GuiPrinter::new); public static void registerShaders(ResourceManager resources, BiConsumer<ShaderInstance, Consumer<ShaderInstance>> load) throws IOException {
MenuScreens.register(ModRegistry.Menus.DISK_DRIVE.get(), GuiDiskDrive::new); RenderTypes.registerShaders(resources, load);
MenuScreens.register(ModRegistry.Menus.PRINTOUT.get(), GuiPrintout::new);
MenuScreens.<ContainerViewComputer, GuiComputer<ContainerViewComputer>>register(ModRegistry.Menus.VIEW_COMPUTER.get(), GuiComputer::new);
} }
} }

View File

@ -0,0 +1,84 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.client;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.client.sound.SpeakerSound;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.event.*;
import net.minecraftforge.client.event.sound.PlayStreamingSourceEvent;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.event.level.LevelEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
/**
* Forge-specific dispatch for {@link ClientHooks}.
*/
@Mod.EventBusSubscriber(modid = ComputerCraft.MOD_ID, value = Dist.CLIENT)
public final class ForgeClientHooks {
private ForgeClientHooks() {
}
@SubscribeEvent
public static void onTick(TickEvent.ClientTickEvent event) {
if (event.phase == TickEvent.Phase.START) ClientHooks.onTick();
}
@SubscribeEvent
public static void onRenderTick(TickEvent.RenderTickEvent event) {
if (event.phase == TickEvent.Phase.START) ClientHooks.onRenderTick();
}
@SubscribeEvent
public static void onWorldUnload(LevelEvent.Unload event) {
if (event.getLevel().isClientSide()) ClientHooks.onWorldUnload();
}
@SubscribeEvent
public static void drawHighlight(RenderHighlightEvent.Block event) {
if (ClientHooks.drawHighlight(event.getPoseStack(), event.getMultiBufferSource(), event.getCamera(), event.getTarget())) {
event.setCanceled(true);
}
}
@SubscribeEvent
public static void onClientSendMessage(ClientChatEvent event) {
if (ClientHooks.onChatMessage(event.getMessage())) event.setCanceled(true);
}
@SubscribeEvent
public static void onRenderText(CustomizeGuiOverlayEvent.DebugText event) {
ClientHooks.addDebugInfo(event.getRight()::add);
}
@SubscribeEvent
public static void onRenderInHand(RenderHandEvent event) {
if (ClientHooks.onRenderHeldItem(
event.getPoseStack(), event.getMultiBufferSource(), event.getPackedLight(),
event.getHand(), event.getInterpolatedPitch(), event.getEquipProgress(), event.getSwingProgress(), event.getItemStack()
)) {
event.setCanceled(true);
}
}
@SubscribeEvent
public static void onRenderInFrame(RenderItemInFrameEvent event) {
if (ClientHooks.onRenderItemFrame(
event.getPoseStack(), event.getMultiBufferSource(), event.getItemFrameEntity(), event.getItemStack(), event.getPackedLight()
)) {
event.setCanceled(true);
}
}
@SubscribeEvent
public static void playStreaming(PlayStreamingSourceEvent event) {
if (!(event.getSound() instanceof SpeakerSound sound) || sound.getStream() == null) return;
ClientHooks.onPlayStreaming(event.getEngine(), event.getChannel(), sound.getStream());
}
}

View File

@ -0,0 +1,56 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.client;
import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.client.render.TurtleModelLoader;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderers;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.event.ModelEvent;
import net.minecraftforge.client.event.RegisterColorHandlersEvent;
import net.minecraftforge.client.event.RegisterShadersEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
import java.io.IOException;
/**
* Registers textures and models for items.
*/
@Mod.EventBusSubscriber(modid = ComputerCraftAPI.MOD_ID, value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.MOD)
public final class ForgeClientRegistry {
private ForgeClientRegistry() {
}
@SubscribeEvent
public static void registerModelLoaders(ModelEvent.RegisterGeometryLoaders event) {
event.register("turtle", TurtleModelLoader.INSTANCE);
}
@SubscribeEvent
public static void registerModels(ModelEvent.RegisterAdditional event) {
ClientRegistry.registerExtraModels(event::register);
}
@SubscribeEvent
public static void registerShaders(RegisterShadersEvent event) throws IOException {
ClientRegistry.registerShaders(event.getResourceManager(), event::registerShader);
}
@SubscribeEvent
public static void onItemColours(RegisterColorHandlersEvent.Item event) {
ClientRegistry.registerItemColours(event::register);
}
@SubscribeEvent
public static void setupClient(FMLClientSetupEvent event) {
ClientRegistry.register();
ClientRegistry.registerBlockEntityRenderers(BlockEntityRenderers::register);
event.enqueueWork(ClientRegistry::registerMainThread);
}
}

View File

@ -5,13 +5,6 @@
*/ */
package dan200.computercraft.client; package dan200.computercraft.client;
import dan200.computercraft.ComputerCraft;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
@Mod.EventBusSubscriber(modid = ComputerCraft.MOD_ID, value = Dist.CLIENT)
public final class FrameInfo { public final class FrameInfo {
private static int tick; private static int tick;
private static long renderFrame; private static long renderFrame;
@ -27,13 +20,11 @@ public final class FrameInfo {
return renderFrame; return renderFrame;
} }
@SubscribeEvent public static void onTick() {
public static void onTick(TickEvent.ClientTickEvent event) { tick++;
if (event.phase == TickEvent.Phase.START) tick++;
} }
@SubscribeEvent public static void onRenderTick() {
public static void onRenderTick(TickEvent.RenderTickEvent event) { renderFrame++;
if (event.phase == TickEvent.Phase.START) renderFrame++;
} }
} }

View File

@ -5,19 +5,17 @@
*/ */
package dan200.computercraft.client.render; package dan200.computercraft.client.render;
import dan200.computercraft.ComputerCraft; import com.mojang.blaze3d.vertex.PoseStack;
import dan200.computercraft.shared.ModRegistry; import dan200.computercraft.shared.ModRegistry;
import dan200.computercraft.shared.peripheral.modem.wired.BlockCable; import dan200.computercraft.shared.peripheral.modem.wired.BlockCable;
import dan200.computercraft.shared.peripheral.modem.wired.CableShapes; import dan200.computercraft.shared.peripheral.modem.wired.CableShapes;
import dan200.computercraft.shared.util.WorldUtil; import dan200.computercraft.shared.util.WorldUtil;
import net.minecraft.client.Camera;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType; import net.minecraft.client.renderer.RenderType;
import net.minecraft.util.Mth; import net.minecraft.util.Mth;
import net.minecraftforge.api.distmarker.Dist; import net.minecraft.world.phys.BlockHitResult;
import net.minecraftforge.client.event.RenderHighlightEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
@Mod.EventBusSubscriber(modid = ComputerCraft.MOD_ID, value = Dist.CLIENT)
public final class CableHighlightRenderer { public final class CableHighlightRenderer {
private CableHighlightRenderer() { private CableHighlightRenderer() {
} }
@ -25,37 +23,36 @@ public final class CableHighlightRenderer {
/** /**
* Draw an outline for a specific part of a cable "Multipart". * Draw an outline for a specific part of a cable "Multipart".
* *
* @param event The event to observe * @param transform The current transformation matrix.
* @param bufferSource The buffer to draw to.
* @param camera The current camera.
* @param hit The block hit result for the current player.
* @return If we rendered a custom outline.
* @see net.minecraft.client.renderer.LevelRenderer#renderHitOutline * @see net.minecraft.client.renderer.LevelRenderer#renderHitOutline
*/ */
@SubscribeEvent public static boolean drawHighlight(PoseStack transform, MultiBufferSource bufferSource, Camera camera, BlockHitResult hit) {
public static void drawHighlight(RenderHighlightEvent.Block event) {
var hit = event.getTarget();
var pos = hit.getBlockPos(); var pos = hit.getBlockPos();
var world = event.getCamera().getEntity().getCommandSenderWorld(); var world = camera.getEntity().getCommandSenderWorld();
var info = event.getCamera();
var state = world.getBlockState(pos); var state = world.getBlockState(pos);
// We only care about instances with both cable and modem. // We only care about instances with both cable and modem.
if (state.getBlock() != ModRegistry.Blocks.CABLE.get() || state.getValue(BlockCable.MODEM).getFacing() == null || !state.getValue(BlockCable.CABLE)) { if (state.getBlock() != ModRegistry.Blocks.CABLE.get() || state.getValue(BlockCable.MODEM).getFacing() == null || !state.getValue(BlockCable.CABLE)) {
return; return false;
} }
event.setCanceled(true);
var shape = WorldUtil.isVecInside(CableShapes.getModemShape(state), hit.getLocation().subtract(pos.getX(), pos.getY(), pos.getZ())) var shape = WorldUtil.isVecInside(CableShapes.getModemShape(state), hit.getLocation().subtract(pos.getX(), pos.getY(), pos.getZ()))
? CableShapes.getModemShape(state) ? CableShapes.getModemShape(state)
: CableShapes.getCableShape(state); : CableShapes.getCableShape(state);
var cameraPos = info.getPosition(); var cameraPos = camera.getPosition();
var xOffset = pos.getX() - cameraPos.x(); var xOffset = pos.getX() - cameraPos.x();
var yOffset = pos.getY() - cameraPos.y(); var yOffset = pos.getY() - cameraPos.y();
var zOffset = pos.getZ() - cameraPos.z(); var zOffset = pos.getZ() - cameraPos.z();
var buffer = event.getMultiBufferSource().getBuffer(RenderType.lines()); var buffer = bufferSource.getBuffer(RenderType.lines());
var matrix4f = event.getPoseStack().last().pose(); var matrix4f = transform.last().pose();
var normal = event.getPoseStack().last().normal(); var normal = transform.last().normal();
// TODO: Can we just accesstransformer out LevelRenderer.renderShape? // TODO: Can we just accesstransformer out LevelRenderer.renderShape?
shape.forAllEdges((x1, y1, z1, x2, y2, z2) -> { shape.forAllEdges((x1, y1, z1, x2, y2, z2) -> {
var xDelta = (float) (x2 - x1); var xDelta = (float) (x2 - x1);
@ -77,5 +74,7 @@ public final class CableHighlightRenderer {
.normal(normal, xDelta, yDelta, zDelta) .normal(normal, xDelta, yDelta, zDelta)
.endVertex(); .endVertex();
}); });
return true;
} }
} }

View File

@ -1,50 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.client.render;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.turtle.TurtleSide;
import dan200.computercraft.shared.peripheral.monitor.TileMonitor;
import dan200.computercraft.shared.turtle.blocks.TileTurtle;
import net.minecraft.client.Minecraft;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.event.CustomizeGuiOverlayEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import java.util.List;
@Mod.EventBusSubscriber(modid = ComputerCraft.MOD_ID, value = Dist.CLIENT)
public class DebugOverlay {
@SubscribeEvent
public static void onRenderText(CustomizeGuiOverlayEvent.DebugText event) {
var minecraft = Minecraft.getInstance();
if (!minecraft.options.renderDebug || minecraft.level == null) return;
if (minecraft.hitResult == null || minecraft.hitResult.getType() != HitResult.Type.BLOCK) return;
var tile = minecraft.level.getBlockEntity(((BlockHitResult) minecraft.hitResult).getBlockPos());
if (tile instanceof TileMonitor monitor) {
event.getRight().add("");
event.getRight().add(
String.format("Targeted monitor: (%d, %d), %d x %d", monitor.getXIndex(), monitor.getYIndex(), monitor.getWidth(), monitor.getHeight())
);
} else if (tile instanceof TileTurtle turtle) {
event.getRight().add("");
event.getRight().add("Targeted turtle:");
event.getRight().add(String.format("Id: %d", turtle.getComputerID()));
addTurtleUpgrade(event.getRight(), turtle, TurtleSide.LEFT);
addTurtleUpgrade(event.getRight(), turtle, TurtleSide.RIGHT);
}
}
private static void addTurtleUpgrade(List<String> out, TileTurtle turtle, TurtleSide side) {
var upgrade = turtle.getUpgrade(side);
if (upgrade != null) out.add(String.format("Upgrade[%s]: %s", side, upgrade.getUpgradeID()));
}
}

View File

@ -30,7 +30,7 @@ public abstract class ItemMapLikeRenderer {
*/ */
protected abstract void renderItem(PoseStack transform, MultiBufferSource render, ItemStack stack, int light); protected abstract void renderItem(PoseStack transform, MultiBufferSource render, ItemStack stack, int light);
protected void renderItemFirstPerson(PoseStack transform, MultiBufferSource render, int lightTexture, InteractionHand hand, float pitch, float equipProgress, float swingProgress, ItemStack stack) { public void renderItemFirstPerson(PoseStack transform, MultiBufferSource render, int lightTexture, InteractionHand hand, float pitch, float equipProgress, float swingProgress, ItemStack stack) {
Player player = Minecraft.getInstance().player; Player player = Minecraft.getInstance().player;
transform.pushPose(); transform.pushPose();

View File

@ -8,7 +8,6 @@ package dan200.computercraft.client.render;
import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.math.Matrix4f; import com.mojang.math.Matrix4f;
import com.mojang.math.Vector3f; import com.mojang.math.Vector3f;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.client.pocket.ClientPocketComputers; import dan200.computercraft.client.pocket.ClientPocketComputers;
import dan200.computercraft.client.render.text.FixedWidthFontRenderer; import dan200.computercraft.client.render.text.FixedWidthFontRenderer;
import dan200.computercraft.core.util.Colour; import dan200.computercraft.core.util.Colour;
@ -16,10 +15,6 @@ import dan200.computercraft.shared.computer.core.ComputerFamily;
import dan200.computercraft.shared.pocket.items.ItemPocketComputer; import dan200.computercraft.shared.pocket.items.ItemPocketComputer;
import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.event.RenderHandEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import static dan200.computercraft.client.render.ComputerBorderRenderer.*; import static dan200.computercraft.client.render.ComputerBorderRenderer.*;
import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FONT_HEIGHT; import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FONT_HEIGHT;
@ -28,25 +23,12 @@ import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FON
/** /**
* Emulates map rendering for pocket computers. * Emulates map rendering for pocket computers.
*/ */
@Mod.EventBusSubscriber(modid = ComputerCraft.MOD_ID, value = Dist.CLIENT)
public final class ItemPocketRenderer extends ItemMapLikeRenderer { public final class ItemPocketRenderer extends ItemMapLikeRenderer {
private static final ItemPocketRenderer INSTANCE = new ItemPocketRenderer(); public static final ItemPocketRenderer INSTANCE = new ItemPocketRenderer();
private ItemPocketRenderer() { private ItemPocketRenderer() {
} }
@SubscribeEvent
public static void onRenderInHand(RenderHandEvent event) {
var stack = event.getItemStack();
if (!(stack.getItem() instanceof ItemPocketComputer)) return;
event.setCanceled(true);
INSTANCE.renderItemFirstPerson(
event.getPoseStack(), event.getMultiBufferSource(), event.getPackedLight(),
event.getHand(), event.getInterpolatedPitch(), event.getEquipProgress(), event.getSwingProgress(), event.getItemStack()
);
}
@Override @Override
protected void renderItem(PoseStack transform, MultiBufferSource bufferSource, ItemStack stack, int light) { protected void renderItem(PoseStack transform, MultiBufferSource bufferSource, ItemStack stack, int light) {
var computer = ClientPocketComputers.get(stack); var computer = ClientPocketComputers.get(stack);

View File

@ -7,16 +7,11 @@ package dan200.computercraft.client.render;
import com.mojang.blaze3d.vertex.PoseStack; import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.math.Vector3f; import com.mojang.math.Vector3f;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.shared.media.items.ItemPrintout; import dan200.computercraft.shared.media.items.ItemPrintout;
import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.world.entity.EntityType; import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.decoration.ItemFrame;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.event.RenderHandEvent;
import net.minecraftforge.client.event.RenderItemInFrameEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import static dan200.computercraft.client.render.PrintoutRenderer.*; import static dan200.computercraft.client.render.PrintoutRenderer.*;
import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FONT_HEIGHT; import static dan200.computercraft.client.render.text.FixedWidthFontRenderer.FONT_HEIGHT;
@ -27,25 +22,12 @@ import static dan200.computercraft.shared.media.items.ItemPrintout.LINE_MAX_LENG
/** /**
* Emulates map and item-frame rendering for printouts. * Emulates map and item-frame rendering for printouts.
*/ */
@Mod.EventBusSubscriber(modid = ComputerCraft.MOD_ID, value = Dist.CLIENT)
public final class ItemPrintoutRenderer extends ItemMapLikeRenderer { public final class ItemPrintoutRenderer extends ItemMapLikeRenderer {
private static final ItemPrintoutRenderer INSTANCE = new ItemPrintoutRenderer(); public static final ItemPrintoutRenderer INSTANCE = new ItemPrintoutRenderer();
private ItemPrintoutRenderer() { private ItemPrintoutRenderer() {
} }
@SubscribeEvent
public static void onRenderInHand(RenderHandEvent event) {
var stack = event.getItemStack();
if (!(stack.getItem() instanceof ItemPrintout)) return;
event.setCanceled(true);
INSTANCE.renderItemFirstPerson(
event.getPoseStack(), event.getMultiBufferSource(), event.getPackedLight(),
event.getHand(), event.getInterpolatedPitch(), event.getEquipProgress(), event.getSwingProgress(), event.getItemStack()
);
}
@Override @Override
protected void renderItem(PoseStack transform, MultiBufferSource render, ItemStack stack, int light) { protected void renderItem(PoseStack transform, MultiBufferSource render, ItemStack stack, int light) {
transform.mulPose(Vector3f.XP.rotationDegrees(180f)); transform.mulPose(Vector3f.XP.rotationDegrees(180f));
@ -55,13 +37,8 @@ public final class ItemPrintoutRenderer extends ItemMapLikeRenderer {
drawPrintout(transform, render, stack, light); drawPrintout(transform, render, stack, light);
} }
@SubscribeEvent public static void onRenderInFrame(PoseStack transform, MultiBufferSource render, ItemFrame frame, ItemStack stack, int packedLight) {
public static void onRenderInFrame(RenderItemInFrameEvent event) {
var stack = event.getItemStack();
if (!(stack.getItem() instanceof ItemPrintout)) return; if (!(stack.getItem() instanceof ItemPrintout)) return;
event.setCanceled(true);
var transform = event.getPoseStack();
// Move a little bit forward to ensure we're not clipping with the frame // Move a little bit forward to ensure we're not clipping with the frame
transform.translate(0.0f, 0.0f, -0.001f); transform.translate(0.0f, 0.0f, -0.001f);
@ -69,8 +46,8 @@ public final class ItemPrintoutRenderer extends ItemMapLikeRenderer {
transform.scale(0.95f, 0.95f, -0.95f); transform.scale(0.95f, 0.95f, -0.95f);
transform.translate(-0.5f, -0.5f, 0.0f); transform.translate(-0.5f, -0.5f, 0.0f);
var light = event.getItemFrameEntity().getType() == EntityType.GLOW_ITEM_FRAME ? 0xf000d2 : event.getPackedLight(); // See getLightVal. var light = frame.getType() == EntityType.GLOW_ITEM_FRAME ? 0xf000d2 : packedLight; // See getLightVal.
drawPrintout(transform, event.getMultiBufferSource(), stack, light); drawPrintout(transform, render, stack, light);
} }
private static void drawPrintout(PoseStack transform, MultiBufferSource render, ItemStack stack, int light) { private static void drawPrintout(PoseStack transform, MultiBufferSource render, ItemStack stack, int light) {

View File

@ -5,17 +5,16 @@
*/ */
package dan200.computercraft.client.render; package dan200.computercraft.client.render;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.blaze3d.vertex.VertexConsumer; import com.mojang.blaze3d.vertex.VertexConsumer;
import com.mojang.math.Matrix3f; import com.mojang.math.Matrix3f;
import com.mojang.math.Matrix4f; import com.mojang.math.Matrix4f;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.shared.peripheral.monitor.TileMonitor; import dan200.computercraft.shared.peripheral.monitor.TileMonitor;
import net.minecraft.client.Camera;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType; import net.minecraft.client.renderer.RenderType;
import net.minecraft.core.Direction; import net.minecraft.core.Direction;
import net.minecraftforge.api.distmarker.Dist; import net.minecraft.world.phys.BlockHitResult;
import net.minecraftforge.client.event.RenderHighlightEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import java.util.EnumSet; import java.util.EnumSet;
@ -25,23 +24,19 @@ import static net.minecraft.core.Direction.*;
* Overrides monitor highlighting to only render the outline of the <em>whole</em> monitor, rather than the current * Overrides monitor highlighting to only render the outline of the <em>whole</em> monitor, rather than the current
* block. This means you do not get an intrusive outline on top of the screen. * block. This means you do not get an intrusive outline on top of the screen.
*/ */
@Mod.EventBusSubscriber(modid = ComputerCraft.MOD_ID, value = Dist.CLIENT)
public final class MonitorHighlightRenderer { public final class MonitorHighlightRenderer {
private MonitorHighlightRenderer() { private MonitorHighlightRenderer() {
} }
@SubscribeEvent public static boolean drawHighlight(PoseStack transformStack, MultiBufferSource bufferSource, Camera camera, BlockHitResult hit) {
public static void drawHighlight(RenderHighlightEvent.Block event) {
// Preserve normal behaviour when crouching. // Preserve normal behaviour when crouching.
if (event.getCamera().getEntity().isCrouching()) return; if (camera.getEntity().isCrouching()) return false;
var world = event.getCamera().getEntity().getCommandSenderWorld(); var world = camera.getEntity().getCommandSenderWorld();
var pos = event.getTarget().getBlockPos(); var pos = hit.getBlockPos();
var tile = world.getBlockEntity(pos); var tile = world.getBlockEntity(pos);
if (!(tile instanceof TileMonitor monitor)) return; if (!(tile instanceof TileMonitor monitor)) return false;
event.setCanceled(true);
// Determine which sides are part of the external faces of the monitor, and so which need to be rendered. // Determine which sides are part of the external faces of the monitor, and so which need to be rendered.
var faces = EnumSet.allOf(Direction.class); var faces = EnumSet.allOf(Direction.class);
@ -52,13 +47,12 @@ public final class MonitorHighlightRenderer {
if (monitor.getYIndex() != 0) faces.remove(monitor.getDown().getOpposite()); if (monitor.getYIndex() != 0) faces.remove(monitor.getDown().getOpposite());
if (monitor.getYIndex() != monitor.getHeight() - 1) faces.remove(monitor.getDown()); if (monitor.getYIndex() != monitor.getHeight() - 1) faces.remove(monitor.getDown());
var transformStack = event.getPoseStack(); var cameraPos = camera.getPosition();
var cameraPos = event.getCamera().getPosition();
transformStack.pushPose(); transformStack.pushPose();
transformStack.translate(pos.getX() - cameraPos.x(), pos.getY() - cameraPos.y(), pos.getZ() - cameraPos.z()); transformStack.translate(pos.getX() - cameraPos.x(), pos.getY() - cameraPos.y(), pos.getZ() - cameraPos.z());
// I wish I could think of a better way to do this // I wish I could think of a better way to do this
var buffer = event.getMultiBufferSource().getBuffer(RenderType.lines()); var buffer = bufferSource.getBuffer(RenderType.lines());
var transform = transformStack.last().pose(); var transform = transformStack.last().pose();
var normal = transformStack.last().normal(); var normal = transformStack.last().normal();
if (faces.contains(NORTH) || faces.contains(WEST)) line(buffer, transform, normal, 0, 0, 0, UP); if (faces.contains(NORTH) || faces.contains(WEST)) line(buffer, transform, normal, 0, 0, 0, UP);
@ -75,6 +69,7 @@ public final class MonitorHighlightRenderer {
if (faces.contains(EAST) || faces.contains(UP)) line(buffer, transform, normal, 1, 1, 0, SOUTH); if (faces.contains(EAST) || faces.contains(UP)) line(buffer, transform, normal, 1, 1, 0, SOUTH);
transformStack.popPose(); transformStack.popPose();
return true;
} }
private static void line(VertexConsumer buffer, Matrix4f transform, Matrix3f normal, float x, float y, float z, Direction direction) { private static void line(VertexConsumer buffer, Matrix4f transform, Matrix3f normal, float x, float y, float z, Direction direction) {

View File

@ -14,15 +14,13 @@ import net.minecraft.client.renderer.RenderStateShard;
import net.minecraft.client.renderer.RenderType; import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.ShaderInstance; import net.minecraft.client.renderer.ShaderInstance;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraftforge.api.distmarker.Dist; import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraftforge.client.event.RegisterShadersEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.io.IOException; import java.io.IOException;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
@Mod.EventBusSubscriber(modid = ComputerCraft.MOD_ID, value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.MOD)
public class RenderTypes { public class RenderTypes {
public static final int FULL_BRIGHT_LIGHTMAP = (0xF << 4) | (0xF << 20); public static final int FULL_BRIGHT_LIGHTMAP = (0xF << 4) | (0xF << 20);
@ -62,11 +60,10 @@ public class RenderTypes {
return GameRenderer.getRendertypeTextShader(); return GameRenderer.getRendertypeTextShader();
} }
@SubscribeEvent public static void registerShaders(ResourceManager resources, BiConsumer<ShaderInstance, Consumer<ShaderInstance>> load) throws IOException {
public static void registerShaders(RegisterShadersEvent event) throws IOException { load.accept(
event.registerShader(
new MonitorTextureBufferShader( new MonitorTextureBufferShader(
event.getResourceManager(), resources,
new ResourceLocation(ComputerCraft.MOD_ID, "monitor_tbo"), new ResourceLocation(ComputerCraft.MOD_ID, "monitor_tbo"),
MONITOR_TBO.format() MONITOR_TBO.format()
), ),

View File

@ -5,18 +5,22 @@
*/ */
package dan200.computercraft.client.sound; package dan200.computercraft.client.sound;
import com.mojang.blaze3d.audio.Channel;
import dan200.computercraft.shared.peripheral.speaker.SpeakerPeripheral; import dan200.computercraft.shared.peripheral.speaker.SpeakerPeripheral;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import net.minecraft.client.sounds.AudioStream; import net.minecraft.client.sounds.AudioStream;
import net.minecraft.client.sounds.SoundEngine;
import org.lwjgl.BufferUtils; import org.lwjgl.BufferUtils;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioFormat;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.Queue; import java.util.Queue;
import java.util.concurrent.Executor;
class DfpwmStream implements AudioStream { class DfpwmStream implements AudioStream {
private static final int PREC = 10; private static final int PREC = 10;
@ -26,6 +30,23 @@ class DfpwmStream implements AudioStream {
private final Queue<ByteBuffer> buffers = new ArrayDeque<>(2); private final Queue<ByteBuffer> buffers = new ArrayDeque<>(2);
/**
* The {@link Channel} which this sound is playing on.
*
* @see SpeakerInstance#pushAudio(ByteBuf)
*/
@Nullable
Channel channel;
/**
* The underlying {@link SoundEngine} executor.
*
* @see SpeakerInstance#pushAudio(ByteBuf)
* @see SoundEngine#executor
*/
@Nullable
Executor executor;
private int charge = 0; // q private int charge = 0; // q
private int strength = 0; // s private int strength = 0; // s
private int lowPassCharge; private int lowPassCharge;

View File

@ -6,6 +6,7 @@
package dan200.computercraft.client.sound; package dan200.computercraft.client.sound;
import dan200.computercraft.ComputerCraft; import dan200.computercraft.ComputerCraft;
import dan200.computercraft.core.util.Nullability;
import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition; import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
@ -32,9 +33,11 @@ public class SpeakerInstance {
currentStream.push(buffer); currentStream.push(buffer);
// If we've got nothing left in the buffer, enqueue an additional one just in case. // If we've got nothing left in the buffer, enqueue an additional one just in case.
if (exhausted && sound != null && sound.stream == stream && sound.channel != null) { if (exhausted && sound != null && sound.stream == stream && stream.channel != null && stream.executor != null) {
sound.executor.execute(() -> { var actualStream = sound.stream;
if (!sound.channel.stopped()) sound.channel.pumpBuffers(1); stream.executor.execute(() -> {
var channel = Nullability.assertNonNull(actualStream.channel);
if (!channel.stopped()) channel.pumpBuffers(1);
}); });
} }
} }

View File

@ -5,11 +5,10 @@
*/ */
package dan200.computercraft.client.sound; package dan200.computercraft.client.sound;
import com.mojang.blaze3d.audio.Channel;
import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition; import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition;
import net.minecraftforge.api.distmarker.Dist; import net.minecraft.client.sounds.AudioStream;
import net.minecraftforge.client.event.sound.PlayStreamingSourceEvent; import net.minecraft.client.sounds.SoundEngine;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
@ -18,17 +17,15 @@ import java.util.concurrent.ConcurrentHashMap;
/** /**
* Maps speakers source IDs to a {@link SpeakerInstance}. * Maps speakers source IDs to a {@link SpeakerInstance}.
*/ */
@Mod.EventBusSubscriber(Dist.CLIENT)
public class SpeakerManager { public class SpeakerManager {
private static final Map<UUID, SpeakerInstance> sounds = new ConcurrentHashMap<>(); private static final Map<UUID, SpeakerInstance> sounds = new ConcurrentHashMap<>();
@SubscribeEvent public static void onPlayStreaming(SoundEngine engine, Channel channel, AudioStream stream) {
public static void playStreaming(PlayStreamingSourceEvent event) { if (!(stream instanceof DfpwmStream dfpwmStream)) return;
if (!(event.getSound() instanceof SpeakerSound sound) || sound.stream == null) return;
// Associate the sound with the current channel, so SpeakerInstance.pushAudio can queue audio immediately. // Associate the stream with the current channel, so SpeakerInstance.pushAudio can queue audio immediately.
sound.channel = event.getChannel(); dfpwmStream.channel = channel;
sound.executor = event.getEngine().executor; dfpwmStream.executor = engine.executor;
} }
public static SpeakerInstance getSound(UUID source) { public static SpeakerInstance getSound(UUID source) {

View File

@ -5,7 +5,6 @@
*/ */
package dan200.computercraft.client.sound; package dan200.computercraft.client.sound;
import com.mojang.blaze3d.audio.Channel;
import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition; import dan200.computercraft.shared.peripheral.speaker.SpeakerPosition;
import net.minecraft.client.resources.sounds.AbstractSoundInstance; import net.minecraft.client.resources.sounds.AbstractSoundInstance;
import net.minecraft.client.resources.sounds.Sound; import net.minecraft.client.resources.sounds.Sound;
@ -19,11 +18,8 @@ import net.minecraft.world.entity.Entity;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
public class SpeakerSound extends AbstractSoundInstance implements TickableSoundInstance { public class SpeakerSound extends AbstractSoundInstance implements TickableSoundInstance {
Channel channel;
Executor executor;
DfpwmStream stream; DfpwmStream stream;
private Entity entity; private Entity entity;
@ -69,4 +65,8 @@ public class SpeakerSound extends AbstractSoundInstance implements TickableSound
public CompletableFuture<AudioStream> getStream(@Nonnull SoundBufferLibrary soundBuffers, @Nonnull Sound sound, boolean looping) { public CompletableFuture<AudioStream> getStream(@Nonnull SoundBufferLibrary soundBuffers, @Nonnull Sound sound, boolean looping) {
return stream != null ? CompletableFuture.completedFuture(stream) : super.getStream(soundBuffers, sound, looping); return stream != null ? CompletableFuture.completedFuture(stream) : super.getStream(soundBuffers, sound, looping);
} }
public AudioStream getStream() {
return stream;
}
} }

View File

@ -7,57 +7,52 @@ package dan200.computercraft.shared;
import dan200.computercraft.ComputerCraft; import dan200.computercraft.ComputerCraft;
import dan200.computercraft.core.apis.http.NetworkUtils; import dan200.computercraft.core.apis.http.NetworkUtils;
import dan200.computercraft.shared.command.CommandComputerCraft;
import dan200.computercraft.shared.computer.core.ResourceMount; import dan200.computercraft.shared.computer.core.ResourceMount;
import dan200.computercraft.shared.computer.core.ServerContext; import dan200.computercraft.shared.computer.core.ServerContext;
import dan200.computercraft.shared.computer.metrics.ComputerMBean; import dan200.computercraft.shared.computer.metrics.ComputerMBean;
import dan200.computercraft.shared.network.client.UpgradesLoadedMessage;
import dan200.computercraft.shared.peripheral.modem.wireless.WirelessNetwork; import dan200.computercraft.shared.peripheral.modem.wireless.WirelessNetwork;
import dan200.computercraft.shared.platform.PlatformHelper; import dan200.computercraft.shared.peripheral.monitor.MonitorWatcher;
import dan200.computercraft.shared.util.DropConsumer;
import dan200.computercraft.shared.util.TickScheduler;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.dedicated.DedicatedServer; import net.minecraft.server.dedicated.DedicatedServer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.packs.resources.PreparableReloadListener;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.storage.loot.BuiltInLootTables; import net.minecraft.world.level.storage.loot.BuiltInLootTables;
import net.minecraft.world.level.storage.loot.LootPool; import net.minecraft.world.level.storage.loot.LootPool;
import net.minecraft.world.level.storage.loot.entries.LootTableReference; import net.minecraft.world.level.storage.loot.entries.LootTableReference;
import net.minecraft.world.level.storage.loot.providers.number.ConstantValue; import net.minecraft.world.level.storage.loot.providers.number.ConstantValue;
import net.minecraftforge.event.*;
import net.minecraftforge.event.server.ServerStartingEvent;
import net.minecraftforge.event.server.ServerStoppedEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.server.ServerLifecycleHooks;
import javax.annotation.Nullable;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.function.BiConsumer;
/** /**
* Miscellaneous hooks which are present on the client and server. * Event listeners for server/common code.
* <p> * <p>
* These should possibly be refactored into separate classes at some point, but are fine here for now. * All event handlers should be defined in this class, and then invoked from a loader-specific event handler. This means
* * it's much easier to ensure that each hook is called in all loader source sets.
* @see dan200.computercraft.client.ClientHooks For client-specific ones.
*/ */
@Mod.EventBusSubscriber(modid = ComputerCraft.MOD_ID)
public final class CommonHooks { public final class CommonHooks {
private CommonHooks() { private CommonHooks() {
} }
@SubscribeEvent public static void onServerTickStart(MinecraftServer server) {
public static void onServerTick(TickEvent.ServerTickEvent event) { ServerContext.get(server).tick();
if (event.phase == TickEvent.Phase.START) { TickScheduler.tick();
ServerContext.get(ServerLifecycleHooks.getCurrentServer()).tick();
}
} }
@SubscribeEvent public static void onServerTickEnd() {
public static void onRegisterCommand(RegisterCommandsEvent event) { MonitorWatcher.onTick();
CommandComputerCraft.register(event.getDispatcher());
} }
@SubscribeEvent public static void onServerStarting(MinecraftServer server) {
public static void onServerStarting(ServerStartingEvent event) {
var server = event.getServer();
if (server instanceof DedicatedServer dediServer && dediServer.getProperties().enableJmxMonitoring) { if (server instanceof DedicatedServer dediServer && dediServer.getProperties().enableJmxMonitoring) {
ComputerMBean.register(); ComputerMBean.register();
} }
@ -67,8 +62,7 @@ public final class CommonHooks {
ComputerMBean.start(server); ComputerMBean.start(server);
} }
@SubscribeEvent public static void onServerStopped() {
public static void onServerStopped(ServerStoppedEvent event) {
resetState(); resetState();
} }
@ -78,6 +72,10 @@ public final class CommonHooks {
NetworkUtils.reset(); NetworkUtils.reset();
} }
public static void onChunkWatch(LevelChunk chunk, ServerPlayer player) {
MonitorWatcher.onWatch(chunk, player);
}
public static final ResourceLocation LOOT_TREASURE_DISK = new ResourceLocation(ComputerCraft.MOD_ID, "treasure_disk"); public static final ResourceLocation LOOT_TREASURE_DISK = new ResourceLocation(ComputerCraft.MOD_ID, "treasure_disk");
private static final Set<ResourceLocation> TABLES = new HashSet<>(Arrays.asList( private static final Set<ResourceLocation> TABLES = new HashSet<>(Arrays.asList(
@ -93,32 +91,26 @@ public final class CommonHooks {
BuiltInLootTables.VILLAGE_CARTOGRAPHER BuiltInLootTables.VILLAGE_CARTOGRAPHER
)); ));
@SubscribeEvent
public static void lootLoad(LootTableLoadEvent event) {
var name = event.getName();
if (!name.getNamespace().equals("minecraft") || !TABLES.contains(name)) return;
event.getTable().addPool(LootPool.lootPool() public static @Nullable LootPool.Builder getExtraLootPool(ResourceLocation lootTable) {
if (!lootTable.getNamespace().equals("minecraft") || !TABLES.contains(lootTable)) return null;
return LootPool.lootPool()
.add(LootTableReference.lootTableReference(LOOT_TREASURE_DISK)) .add(LootTableReference.lootTableReference(LOOT_TREASURE_DISK))
.setRolls(ConstantValue.exactly(1)) .setRolls(ConstantValue.exactly(1));
.name("computercraft_treasure")
.build());
} }
@SubscribeEvent public static void onDatapackReload(BiConsumer<String, PreparableReloadListener> addReload) {
public static void onAddReloadListeners(AddReloadListenerEvent event) { addReload.accept("mounts", ResourceMount.RELOAD_LISTENER);
event.addListener(ResourceMount.RELOAD_LISTENER); addReload.accept("turtle_upgrades", TurtleUpgrades.instance());
event.addListener(TurtleUpgrades.instance()); addReload.accept("pocket_upgrades", PocketUpgrades.instance());
event.addListener(PocketUpgrades.instance());
} }
@SubscribeEvent public static boolean onEntitySpawn(Entity entity) {
public static void onDatapackSync(OnDatapackSyncEvent event) { return DropConsumer.onEntitySpawn(entity);
var packet = new UpgradesLoadedMessage(); }
if (event.getPlayer() == null) {
PlatformHelper.get().sendToAllPlayers(packet, event.getPlayerList().getServer()); public static boolean onLivingDrop(Entity entity, ItemStack stack) {
} else { return DropConsumer.onLivingDrop(entity, stack);
PlatformHelper.get().sendToPlayer(packet, event.getPlayer());
}
} }
} }

View File

@ -14,11 +14,8 @@ import dan200.computercraft.core.apis.http.options.Action;
import dan200.computercraft.shared.peripheral.monitor.MonitorRenderer; import dan200.computercraft.shared.peripheral.monitor.MonitorRenderer;
import net.minecraftforge.common.ForgeConfigSpec; import net.minecraftforge.common.ForgeConfigSpec;
import net.minecraftforge.common.ForgeConfigSpec.ConfigValue; import net.minecraftforge.common.ForgeConfigSpec.ConfigValue;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.ModLoadingContext; import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.config.ModConfig; import net.minecraftforge.fml.config.ModConfig;
import net.minecraftforge.fml.event.config.ModConfigEvent;
import org.apache.logging.log4j.core.Filter; import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.filter.MarkerFilter; import org.apache.logging.log4j.core.filter.MarkerFilter;
@ -28,7 +25,6 @@ import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@Mod.EventBusSubscriber(modid = ComputerCraft.MOD_ID, bus = Mod.EventBusSubscriber.Bus.MOD)
public final class Config { public final class Config {
private static final int MODEM_MAX_RANGE = 100000; private static final int MODEM_MAX_RANGE = 100000;
@ -422,19 +418,9 @@ public final class Config {
ComputerCraft.uploadNagDelay = uploadNagDelay.get(); ComputerCraft.uploadNagDelay = uploadNagDelay.get();
} }
private static void sync(ModConfig config) { public static void sync(ModConfig config) {
if (!config.getModId().equals(ComputerCraft.MOD_ID)) return; if (!config.getModId().equals(ComputerCraft.MOD_ID)) return;
if (config.getType() == ModConfig.Type.SERVER) syncServer(); if (config.getType() == ModConfig.Type.SERVER) syncServer();
if (config.getType() == ModConfig.Type.CLIENT) syncClient(); if (config.getType() == ModConfig.Type.CLIENT) syncClient();
} }
@SubscribeEvent
public static void sync(ModConfigEvent.Loading event) {
sync(event.getConfig());
}
@SubscribeEvent
public static void sync(ModConfigEvent.Reloading event) {
sync(event.getConfig());
}
} }

View File

@ -7,7 +7,9 @@ package dan200.computercraft.shared;
import dan200.computercraft.ComputerCraft; import dan200.computercraft.ComputerCraft;
import dan200.computercraft.api.ComputerCraftAPI; import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.shared.command.CommandComputerCraft;
import dan200.computercraft.shared.computer.blocks.TileComputer; import dan200.computercraft.shared.computer.blocks.TileComputer;
import dan200.computercraft.shared.network.client.UpgradesLoadedMessage;
import dan200.computercraft.shared.peripheral.commandblock.CommandBlockPeripheral; import dan200.computercraft.shared.peripheral.commandblock.CommandBlockPeripheral;
import dan200.computercraft.shared.peripheral.diskdrive.TileDiskDrive; import dan200.computercraft.shared.peripheral.diskdrive.TileDiskDrive;
import dan200.computercraft.shared.peripheral.modem.wired.TileCable; import dan200.computercraft.shared.peripheral.modem.wired.TileCable;
@ -16,13 +18,20 @@ import dan200.computercraft.shared.peripheral.modem.wireless.TileWirelessModem;
import dan200.computercraft.shared.peripheral.monitor.TileMonitor; import dan200.computercraft.shared.peripheral.monitor.TileMonitor;
import dan200.computercraft.shared.peripheral.printer.TilePrinter; import dan200.computercraft.shared.peripheral.printer.TilePrinter;
import dan200.computercraft.shared.peripheral.speaker.TileSpeaker; import dan200.computercraft.shared.peripheral.speaker.TileSpeaker;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.turtle.blocks.TileTurtle; import dan200.computercraft.shared.turtle.blocks.TileTurtle;
import dan200.computercraft.shared.util.CapabilityProvider; import dan200.computercraft.shared.util.CapabilityProvider;
import dan200.computercraft.shared.util.SidedCapabilityProvider; import dan200.computercraft.shared.util.SidedCapabilityProvider;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.CommandBlockEntity; import net.minecraft.world.level.block.entity.CommandBlockEntity;
import net.minecraftforge.event.AttachCapabilitiesEvent; import net.minecraftforge.event.*;
import net.minecraftforge.event.entity.EntityJoinLevelEvent;
import net.minecraftforge.event.entity.living.LivingDropsEvent;
import net.minecraftforge.event.level.ChunkWatchEvent;
import net.minecraftforge.event.server.ServerStartingEvent;
import net.minecraftforge.event.server.ServerStoppedEvent;
import net.minecraftforge.eventbus.api.EventPriority;
import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.items.wrapper.InvWrapper; import net.minecraftforge.items.wrapper.InvWrapper;
@ -31,8 +40,54 @@ import net.minecraftforge.items.wrapper.SidedInvWrapper;
import static dan200.computercraft.shared.Capabilities.CAPABILITY_PERIPHERAL; import static dan200.computercraft.shared.Capabilities.CAPABILITY_PERIPHERAL;
import static net.minecraftforge.common.capabilities.ForgeCapabilities.ITEM_HANDLER; import static net.minecraftforge.common.capabilities.ForgeCapabilities.ITEM_HANDLER;
/**
* Forge-specific dispatch for {@link CommonHooks}.
*/
@Mod.EventBusSubscriber(modid = ComputerCraftAPI.MOD_ID) @Mod.EventBusSubscriber(modid = ComputerCraftAPI.MOD_ID)
public class ForgeCommonHooks { public class ForgeCommonHooks {
@SubscribeEvent
public static void onServerTick(TickEvent.ServerTickEvent event) {
switch (event.phase) {
case START -> CommonHooks.onServerTickStart(event.getServer());
case END -> CommonHooks.onServerTickEnd();
}
}
@SubscribeEvent
public static void onServerStarting(ServerStartingEvent event) {
CommonHooks.onServerStarting(event.getServer());
}
@SubscribeEvent
public static void onServerStopped(ServerStoppedEvent event) {
CommonHooks.onServerStopped();
}
@SubscribeEvent
public static void onRegisterCommand(RegisterCommandsEvent event) {
CommandComputerCraft.register(event.getDispatcher());
}
@SubscribeEvent
public static void onChunkWatch(ChunkWatchEvent.Watch event) {
CommonHooks.onChunkWatch(event.getChunk(), event.getPlayer());
}
@SubscribeEvent
public static void onAddReloadListeners(AddReloadListenerEvent event) {
CommonHooks.onDatapackReload((id, listener) -> event.addListener(listener));
}
@SubscribeEvent
public static void onDatapackSync(OnDatapackSyncEvent event) {
var packet = new UpgradesLoadedMessage();
if (event.getPlayer() == null) {
PlatformHelper.get().sendToAllPlayers(packet, event.getPlayerList().getServer());
} else {
PlatformHelper.get().sendToPlayer(packet, event.getPlayer());
}
}
private static final ResourceLocation PERIPHERAL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "peripheral"); private static final ResourceLocation PERIPHERAL = new ResourceLocation(ComputerCraftAPI.MOD_ID, "peripheral");
private static final ResourceLocation WIRED_ELEMENT = new ResourceLocation(ComputerCraftAPI.MOD_ID, "wired_node"); private static final ResourceLocation WIRED_ELEMENT = new ResourceLocation(ComputerCraftAPI.MOD_ID, "wired_node");
private static final ResourceLocation INVENTORY = new ResourceLocation(ComputerCraftAPI.MOD_ID, "inventory"); private static final ResourceLocation INVENTORY = new ResourceLocation(ComputerCraftAPI.MOD_ID, "inventory");
@ -83,4 +138,20 @@ public class ForgeCommonHooks {
CapabilityProvider.attach(event, PERIPHERAL, CAPABILITY_PERIPHERAL, () -> new CommandBlockPeripheral(commandBlock)); CapabilityProvider.attach(event, PERIPHERAL, CAPABILITY_PERIPHERAL, () -> new CommandBlockPeripheral(commandBlock));
} }
} }
@SubscribeEvent
public static void lootLoad(LootTableLoadEvent event) {
var pool = CommonHooks.getExtraLootPool(event.getName());
if (pool != null) event.getTable().addPool(pool.build());
}
@SubscribeEvent(priority = EventPriority.HIGHEST)
public static void onEntitySpawn(EntityJoinLevelEvent event) {
if (CommonHooks.onEntitySpawn(event.getEntity())) event.setCanceled(true);
}
@SubscribeEvent(priority = EventPriority.LOW)
public static void onLivingDrops(LivingDropsEvent event) {
event.getDrops().removeIf(itemEntity -> CommonHooks.onLivingDrop(event.getEntity(), itemEntity.getItem()));
}
} }

View File

@ -7,6 +7,7 @@ package dan200.computercraft.shared;
import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.arguments.ArgumentType;
import dan200.computercraft.api.ComputerCraftAPI; import dan200.computercraft.api.ComputerCraftAPI;
import dan200.computercraft.api.detail.IDetailProvider;
import dan200.computercraft.api.detail.VanillaDetailRegistries; import dan200.computercraft.api.detail.VanillaDetailRegistries;
import dan200.computercraft.api.media.IMedia; import dan200.computercraft.api.media.IMedia;
import dan200.computercraft.api.pocket.PocketUpgradeSerialiser; import dan200.computercraft.api.pocket.PocketUpgradeSerialiser;
@ -99,6 +100,12 @@ import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.function.Function; import java.util.function.Function;
/**
* Registers ComputerCraft's registry entries and additional objects, such as {@link CauldronInteraction}s and
* {@link IDetailProvider}s
* <p>
* The functions in this class should be called from a loader-specific class.
*/
public final class ModRegistry { public final class ModRegistry {
private static final CreativeModeTab mainItemGroup = new CreativeTabMain(); private static final CreativeModeTab mainItemGroup = new CreativeTabMain();
@ -354,6 +361,9 @@ public final class ModRegistry {
public static final RegistryEntry<ImpostorShapelessRecipe.Serializer> IMPOSTOR_SHAPELESS = REGISTRY.register("impostor_shapeless", ImpostorShapelessRecipe.Serializer::new); public static final RegistryEntry<ImpostorShapelessRecipe.Serializer> IMPOSTOR_SHAPELESS = REGISTRY.register("impostor_shapeless", ImpostorShapelessRecipe.Serializer::new);
} }
/**
* Register any objects which don't have to be done on the main thread.
*/
public static void register() { public static void register() {
Blocks.REGISTRY.register(); Blocks.REGISTRY.register();
BlockEntities.REGISTRY.register(); BlockEntities.REGISTRY.register();
@ -379,6 +389,9 @@ public final class ModRegistry {
VanillaDetailRegistries.BLOCK_IN_WORLD.addProvider(BlockData::fill); VanillaDetailRegistries.BLOCK_IN_WORLD.addProvider(BlockData::fill);
} }
/**
* Register any objects which must be done on the main thread.
*/
public static void registerMainThread() { public static void registerMainThread() {
CauldronInteraction.WATER.put(ModRegistry.Items.TURTLE_NORMAL.get(), ItemTurtle.CAULDRON_INTERACTION); CauldronInteraction.WATER.put(ModRegistry.Items.TURTLE_NORMAL.get(), ItemTurtle.CAULDRON_INTERACTION);
CauldronInteraction.WATER.put(ModRegistry.Items.TURTLE_ADVANCED.get(), ItemTurtle.CAULDRON_INTERACTION); CauldronInteraction.WATER.put(ModRegistry.Items.TURTLE_ADVANCED.get(), ItemTurtle.CAULDRON_INTERACTION);

View File

@ -1,56 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.command;
import dan200.computercraft.ComputerCraft;
import dan200.computercraft.shared.computer.core.ServerContext;
import net.minecraft.Util;
import net.minecraft.client.Minecraft;
import net.minecraft.server.MinecraftServer;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.event.ClientChatEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import java.io.File;
/**
* Basic client-side commands.
* <p>
* Simply hooks into client chat messages and intercepts matching strings.
*/
@Mod.EventBusSubscriber(modid = ComputerCraft.MOD_ID, value = Dist.CLIENT)
public final class ClientCommands {
public static final String OPEN_COMPUTER = "/computercraft open-computer ";
private ClientCommands() {
}
@SubscribeEvent
public static void onClientSendMessage(ClientChatEvent event) {
// Emulate the command on the client side
if (event.getMessage().startsWith(OPEN_COMPUTER)) {
MinecraftServer server = Minecraft.getInstance().getSingleplayerServer();
if (server == null) return;
event.setCanceled(true);
var idStr = event.getMessage().substring(OPEN_COMPUTER.length()).trim();
int id;
try {
id = Integer.parseInt(idStr);
} catch (NumberFormatException ignore) {
return;
}
var file = new File(ServerContext.get(server).storageDir().toFile(), "computer/" + id);
if (!file.isDirectory()) return;
Util.getPlatform().openFile(file);
}
}
}

View File

@ -51,6 +51,7 @@ import static net.minecraft.commands.Commands.literal;
public final class CommandComputerCraft { public final class CommandComputerCraft {
public static final UUID SYSTEM_UUID = new UUID(0, 0); public static final UUID SYSTEM_UUID = new UUID(0, 0);
public static final String OPEN_COMPUTER = "/computercraft open-computer ";
private CommandComputerCraft() { private CommandComputerCraft() {
} }
@ -310,7 +311,7 @@ public final class CommandComputerCraft {
return link( return link(
text("\u270E"), text("\u270E"),
ClientCommands.OPEN_COMPUTER + id, OPEN_COMPUTER + id,
translate("commands.computercraft.dump.open_path") translate("commands.computercraft.dump.open_path")
); );
} }

View File

@ -12,13 +12,15 @@ import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.shared.MediaProviders; import dan200.computercraft.shared.MediaProviders;
import dan200.computercraft.shared.common.TileGeneric; import dan200.computercraft.shared.common.TileGeneric;
import dan200.computercraft.shared.network.client.PlayRecordClientMessage;
import dan200.computercraft.shared.platform.PlatformHelper;
import dan200.computercraft.shared.util.DefaultInventory; import dan200.computercraft.shared.util.DefaultInventory;
import dan200.computercraft.shared.util.InventoryUtil; import dan200.computercraft.shared.util.InventoryUtil;
import dan200.computercraft.shared.util.RecordUtil;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction; import net.minecraft.core.Direction;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.*; import net.minecraft.world.*;
import net.minecraft.world.entity.item.ItemEntity; import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.entity.player.Inventory;
@ -29,6 +31,7 @@ import net.minecraft.world.level.block.entity.BaseContainerBlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType; import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.Vec3;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -408,14 +411,18 @@ public final class TileDiskDrive extends TileGeneric implements DefaultInventory
var contents = getDiskMedia(); var contents = getDiskMedia();
var record = contents != null ? contents.getAudio(diskStack) : null; var record = contents != null ? contents.getAudio(diskStack) : null;
if (record != null) { if (record != null) {
RecordUtil.playRecord(record, contents.getAudioTitle(diskStack), getLevel(), getBlockPos()); playRecord(new PlayRecordClientMessage(getBlockPos(), record, contents.getAudioTitle(diskStack)));
} else { } else {
RecordUtil.playRecord(null, null, getLevel(), getBlockPos()); stopRecord();
} }
} }
private void stopRecord() { private void stopRecord() {
RecordUtil.playRecord(null, null, getLevel(), getBlockPos()); playRecord(new PlayRecordClientMessage(getBlockPos()));
}
private void playRecord(PlayRecordClientMessage message) {
PlatformHelper.get().sendToAllAround(message, (ServerLevel) getLevel(), Vec3.atCenterOf(getBlockPos()), 64);
} }
@Override @Override

View File

@ -10,15 +10,12 @@ import dan200.computercraft.shared.computer.terminal.TerminalState;
import dan200.computercraft.shared.network.client.MonitorClientMessage; import dan200.computercraft.shared.network.client.MonitorClientMessage;
import dan200.computercraft.shared.platform.PlatformHelper; import dan200.computercraft.shared.platform.PlatformHelper;
import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerLevel;
import net.minecraftforge.event.TickEvent; import net.minecraft.server.level.ServerPlayer;
import net.minecraftforge.event.level.ChunkWatchEvent; import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.Queue; import java.util.Queue;
@Mod.EventBusSubscriber(modid = ComputerCraft.MOD_ID)
public final class MonitorWatcher { public final class MonitorWatcher {
private static final Queue<TileMonitor> watching = new ArrayDeque<>(); private static final Queue<TileMonitor> watching = new ArrayDeque<>();
@ -33,27 +30,23 @@ public final class MonitorWatcher {
watching.add(monitor); watching.add(monitor);
} }
@SubscribeEvent public static void onWatch(LevelChunk chunk, ServerPlayer player) {
public static void onWatch(ChunkWatchEvent.Watch event) {
// Find all origin monitors who are not already on the queue and send the // Find all origin monitors who are not already on the queue and send the
// monitor data to the player. // monitor data to the player.
for (var te : event.getChunk().getBlockEntities().values()) { for (var te : chunk.getBlockEntities().values()) {
if (!(te instanceof TileMonitor monitor)) continue; if (!(te instanceof TileMonitor monitor)) continue;
var serverMonitor = getMonitor(monitor); var serverMonitor = getMonitor(monitor);
if (serverMonitor == null || monitor.enqueued) continue; if (serverMonitor == null || monitor.enqueued) continue;
var state = getState(monitor, serverMonitor); var state = getState(monitor, serverMonitor);
PlatformHelper.get().sendToPlayer(new MonitorClientMessage(monitor.getBlockPos(), state), event.getPlayer()); PlatformHelper.get().sendToPlayer(new MonitorClientMessage(monitor.getBlockPos(), state), player);
} }
} }
@SubscribeEvent public static void onTick() {
public static void onTick(TickEvent.ServerTickEvent event) {
// Find all enqueued monitors and send their contents to all nearby players. // Find all enqueued monitors and send their contents to all nearby players.
if (event.phase != TickEvent.Phase.END) return;
var limit = ComputerCraft.monitorBandwidth; var limit = ComputerCraft.monitorBandwidth;
var obeyLimit = limit > 0; var obeyLimit = limit > 0;

View File

@ -5,7 +5,6 @@
*/ */
package dan200.computercraft.shared.util; package dan200.computercraft.shared.util;
import dan200.computercraft.ComputerCraft;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction; import net.minecraft.core.Direction;
import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.Entity;
@ -13,17 +12,11 @@ import net.minecraft.world.entity.item.ItemEntity;
import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.AABB;
import net.minecraftforge.event.entity.EntityJoinLevelEvent;
import net.minecraftforge.event.entity.living.LivingDropsEvent;
import net.minecraftforge.eventbus.api.EventPriority;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.function.Function; import java.util.function.Function;
@Mod.EventBusSubscriber(modid = ComputerCraft.MOD_ID)
public final class DropConsumer { public final class DropConsumer {
private DropConsumer() { private DropConsumer() {
} }
@ -72,22 +65,21 @@ public final class DropConsumer {
if (!remaining.isEmpty()) remainingDrops.add(remaining); if (!remaining.isEmpty()) remainingDrops.add(remaining);
} }
@SubscribeEvent(priority = EventPriority.HIGHEST) public static boolean onEntitySpawn(Entity entity) {
public static void onEntitySpawn(EntityJoinLevelEvent event) {
// Capture any nearby item spawns // Capture any nearby item spawns
if (dropWorld == event.getLevel() && event.getEntity() instanceof ItemEntity if (dropWorld == entity.getLevel() && entity instanceof ItemEntity
&& dropBounds.contains(event.getEntity().position())) { && dropBounds.contains(entity.position())) {
handleDrops(((ItemEntity) event.getEntity()).getItem()); handleDrops(((ItemEntity) entity).getItem());
event.setCanceled(true); return true;
} }
return false;
} }
@SubscribeEvent(priority = EventPriority.LOW) public static boolean onLivingDrop(Entity entity, ItemStack stack) {
public static void onLivingDrops(LivingDropsEvent drops) { if (dropEntity == null || entity != dropEntity) return false;
if (dropEntity == null || drops.getEntity() != dropEntity) return;
for (var drop : drops.getDrops()) handleDrops(drop.getItem()); handleDrops(stack);
drops.getDrops().clear(); return true;
drops.setCanceled(true);
} }
} }

View File

@ -6,16 +6,10 @@
package dan200.computercraft.shared.util; package dan200.computercraft.shared.util;
import net.minecraft.Util; import net.minecraft.Util;
import net.minecraft.client.Minecraft;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
/** /**
* A monotonically increasing clock which accounts for the game being paused. * A monotonically increasing clock which accounts for the game being paused.
*/ */
@Mod.EventBusSubscriber(Dist.CLIENT)
public final class PauseAwareTimer { public final class PauseAwareTimer {
private static boolean paused; private static boolean paused;
private static long pauseTime; private static long pauseTime;
@ -28,11 +22,7 @@ public final class PauseAwareTimer {
return (paused ? pauseTime : Util.getNanos()) - pauseOffset; return (paused ? pauseTime : Util.getNanos()) - pauseOffset;
} }
@SubscribeEvent public static void tick(boolean isPaused) {
public static void tick(TickEvent.RenderTickEvent event) {
if (event.phase != TickEvent.Phase.START) return;
var isPaused = Minecraft.getInstance().isPaused();
if (isPaused == paused) return; if (isPaused == paused) return;
if (isPaused) { if (isPaused) {

View File

@ -1,25 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.util;
import dan200.computercraft.shared.network.NetworkMessage;
import dan200.computercraft.shared.network.client.PlayRecordClientMessage;
import dan200.computercraft.shared.platform.PlatformHelper;
import net.minecraft.core.BlockPos;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
public final class RecordUtil {
private RecordUtil() {
}
public static void playRecord(SoundEvent record, String recordInfo, Level world, BlockPos pos) {
NetworkMessage packet = record != null ? new PlayRecordClientMessage(pos, record, recordInfo) : new PlayRecordClientMessage(pos);
PlatformHelper.get().sendToAllAround(packet, (ServerLevel) world, Vec3.atCenterOf(pos), 64);
}
}

View File

@ -5,14 +5,10 @@
*/ */
package dan200.computercraft.shared.util; package dan200.computercraft.shared.util;
import dan200.computercraft.ComputerCraft;
import net.minecraft.core.BlockPos; import net.minecraft.core.BlockPos;
import net.minecraft.world.level.LevelAccessor; import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import java.util.Queue; import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.ConcurrentLinkedDeque;
@ -23,7 +19,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
* <p> * <p>
* We use this when modems and other peripherals change a block in a different thread. * We use this when modems and other peripherals change a block in a different thread.
*/ */
@Mod.EventBusSubscriber(modid = ComputerCraft.MOD_ID)
public final class TickScheduler { public final class TickScheduler {
private TickScheduler() { private TickScheduler() {
} }
@ -35,10 +30,7 @@ public final class TickScheduler {
if (world != null && !world.isClientSide && !token.scheduled.getAndSet(true)) toTick.add(token); if (world != null && !world.isClientSide && !token.scheduled.getAndSet(true)) toTick.add(token);
} }
@SubscribeEvent public static void tick() {
public static void tick(TickEvent.ServerTickEvent event) {
if (event.phase != TickEvent.Phase.START) return;
Token token; Token token;
while ((token = toTick.poll()) != null) { while ((token = toTick.poll()) != null) {
token.scheduled.set(false); token.scheduled.set(false);