Projector block.
@@ -7,10 +7,7 @@ import li.cil.oc2.client.item.CustomItemModelProperties;
|
||||
import li.cil.oc2.client.model.BusCableModelLoader;
|
||||
import li.cil.oc2.client.renderer.BusInterfaceNameRenderer;
|
||||
import li.cil.oc2.client.renderer.NetworkCableRenderer;
|
||||
import li.cil.oc2.client.renderer.blockentity.ChargerRenderer;
|
||||
import li.cil.oc2.client.renderer.blockentity.ComputerRenderer;
|
||||
import li.cil.oc2.client.renderer.blockentity.DiskDriveRenderer;
|
||||
import li.cil.oc2.client.renderer.blockentity.NetworkConnectorRenderer;
|
||||
import li.cil.oc2.client.renderer.blockentity.*;
|
||||
import li.cil.oc2.client.renderer.color.BusCableBlockColor;
|
||||
import li.cil.oc2.client.renderer.entity.RobotRenderer;
|
||||
import li.cil.oc2.client.renderer.entity.model.RobotModel;
|
||||
@@ -43,6 +40,7 @@ public final class ClientSetup {
|
||||
BlockEntityRenderers.register(BlockEntities.NETWORK_CONNECTOR.get(), NetworkConnectorRenderer::new);
|
||||
BlockEntityRenderers.register(BlockEntities.DISK_DRIVE.get(), DiskDriveRenderer::new);
|
||||
BlockEntityRenderers.register(BlockEntities.CHARGER.get(), ChargerRenderer::new);
|
||||
BlockEntityRenderers.register(BlockEntities.PROJECTOR.get(), ProjectorRenderer::new);
|
||||
|
||||
event.enqueueWork(() -> {
|
||||
CustomItemModelProperties.initialize();
|
||||
|
||||
@@ -1,30 +1,48 @@
|
||||
package li.cil.oc2.client.renderer;
|
||||
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
|
||||
import com.mojang.blaze3d.vertex.VertexFormat;
|
||||
import li.cil.oc2.api.API;
|
||||
import net.minecraft.client.renderer.RenderStateShard;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.client.renderer.texture.DynamicTexture;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public abstract class ModRenderType extends RenderType {
|
||||
private static final RenderType NETWORK_CABLE = create(
|
||||
API.MOD_ID + "/network_cable",
|
||||
DefaultVertexFormat.POSITION_COLOR_LIGHTMAP,
|
||||
VertexFormat.Mode.QUADS,
|
||||
256,
|
||||
false,
|
||||
false,
|
||||
CompositeState.builder()
|
||||
.setShaderState(POSITION_COLOR_LIGHTMAP_SHADER)
|
||||
.setTextureState(NO_TEXTURE)
|
||||
.setTransparencyState(NO_TRANSPARENCY)
|
||||
.setCullState(NO_CULL)
|
||||
.setLightmapState(LIGHTMAP)
|
||||
.createCompositeState(false));
|
||||
API.MOD_ID + "/network_cable",
|
||||
DefaultVertexFormat.POSITION_COLOR_LIGHTMAP,
|
||||
VertexFormat.Mode.QUADS,
|
||||
256,
|
||||
false,
|
||||
false,
|
||||
CompositeState.builder()
|
||||
.setShaderState(POSITION_COLOR_LIGHTMAP_SHADER)
|
||||
.setTextureState(NO_TEXTURE)
|
||||
.setTransparencyState(NO_TRANSPARENCY)
|
||||
.setCullState(NO_CULL)
|
||||
.setLightmapState(LIGHTMAP)
|
||||
.createCompositeState(false));
|
||||
|
||||
private static final RenderType PROJECTOR_LIGHT = create(
|
||||
API.MOD_ID + "/projector_light",
|
||||
DefaultVertexFormat.POSITION_COLOR,
|
||||
VertexFormat.Mode.QUADS,
|
||||
256,
|
||||
false,
|
||||
true,
|
||||
CompositeState.builder()
|
||||
.setShaderState(RENDERTYPE_LIGHTNING_SHADER)
|
||||
.setOutputState(WEATHER_TARGET)
|
||||
.setTransparencyState(LIGHTNING_TRANSPARENCY)
|
||||
.setWriteMaskState(COLOR_WRITE)
|
||||
.setCullState(NO_CULL)
|
||||
.createCompositeState(false));
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -32,6 +50,10 @@ public abstract class ModRenderType extends RenderType {
|
||||
return NETWORK_CABLE;
|
||||
}
|
||||
|
||||
public static RenderType getProjectorLight() {
|
||||
return PROJECTOR_LIGHT;
|
||||
}
|
||||
|
||||
public static RenderType getUnlitBlock(final ResourceLocation location) {
|
||||
final TextureStateShard texture = new TextureStateShard(location, false, true);
|
||||
final RenderType.CompositeState state = RenderType.CompositeState.builder()
|
||||
@@ -69,8 +91,40 @@ public abstract class ModRenderType extends RenderType {
|
||||
state);
|
||||
}
|
||||
|
||||
public static RenderType getProjector(final DynamicTexture texture) {
|
||||
final RenderType.CompositeState state = RenderType.CompositeState.builder()
|
||||
.setShaderState(POSITION_TEX_SHADER)
|
||||
.setTextureState(new DynamicTextureStateShard(texture))
|
||||
.setOutputState(TRANSLUCENT_TARGET)
|
||||
.setTransparencyState(ADDITIVE_TRANSPARENCY)
|
||||
.setCullState(NO_CULL)
|
||||
.createCompositeState(false);
|
||||
return create(
|
||||
API.MOD_ID + "/projector",
|
||||
DefaultVertexFormat.POSITION_TEX,
|
||||
VertexFormat.Mode.QUADS,
|
||||
256,
|
||||
false,
|
||||
true,
|
||||
state);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
private static final class DynamicTextureStateShard extends EmptyTextureStateShard {
|
||||
public DynamicTextureStateShard(final DynamicTexture texture) {
|
||||
super(() -> {
|
||||
RenderSystem.enableTexture();
|
||||
RenderSystem.texParameter(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL13.GL_CLAMP_TO_BORDER);
|
||||
RenderSystem.texParameter(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL13.GL_CLAMP_TO_BORDER);
|
||||
// TODO client side setting
|
||||
// texture.setFilter(true, false);
|
||||
RenderSystem.setShaderTexture(0, texture.getId());
|
||||
}, () -> {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private ModRenderType(final String name, final VertexFormat format, final VertexFormat.Mode drawMode, final int bufferSize, final boolean useDelegate, final boolean needsSorting, final Runnable setupTask, final Runnable clearTask) {
|
||||
super(name, format, drawMode, bufferSize, useDelegate, needsSorting, setupTask, clearTask);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,256 @@
|
||||
package li.cil.oc2.client.renderer.blockentity;
|
||||
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.RemovalNotification;
|
||||
import com.mojang.blaze3d.platform.NativeImage;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
import com.mojang.math.Matrix4f;
|
||||
import com.mojang.math.Quaternion;
|
||||
import com.mojang.math.Vector3f;
|
||||
import li.cil.oc2.client.renderer.ModRenderType;
|
||||
import li.cil.oc2.common.block.ProjectorBlock;
|
||||
import li.cil.oc2.common.blockentity.ProjectorBlockEntity;
|
||||
import li.cil.oc2.common.bus.device.vm.ProjectorVMDevice;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
|
||||
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
|
||||
import net.minecraft.client.renderer.culling.Frustum;
|
||||
import net.minecraft.client.renderer.texture.DynamicTexture;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.event.TickEvent;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.BitSet;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
public final class ProjectorRenderer implements BlockEntityRenderer<ProjectorBlockEntity> {
|
||||
private static final int LIGHT_COLOR_NEAR = 0x22FFFFFF;
|
||||
private static final int LIGHT_COLOR_FAR = 0x00FFFFFF;
|
||||
private static final int LENS_COLOR = 0xDDFFFFFF;
|
||||
private static final int LED_COLOR = 0xCC6688DD;
|
||||
|
||||
private static final float LENS_RIGHT = 0 + 4 / 16f;
|
||||
private static final float LENS_LEFT = 1 - 4 / 16f;
|
||||
private static final float LENS_BOTTOM = 0 + 4 / 16f;
|
||||
private static final float LENS_TOP = 1 - 4 / 16f;
|
||||
|
||||
private static final Cache<ProjectorBlockEntity, RenderInfo> textures = CacheBuilder.newBuilder()
|
||||
.expireAfterAccess(Duration.ofSeconds(5))
|
||||
.removalListener(ProjectorRenderer::handleNoLongerRendering)
|
||||
.build();
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
public ProjectorRenderer(final BlockEntityRendererProvider.Context ignored) {
|
||||
MinecraftForge.EVENT_BUS.addListener(ProjectorRenderer::updateCache);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public boolean shouldRender(final ProjectorBlockEntity projector, final Vec3 position) {
|
||||
return projector.isProjecting() && BlockEntityRenderer.super.shouldRender(projector, position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(final ProjectorBlockEntity projector, final float partialTicks, final PoseStack stack, final MultiBufferSource bufferSource, final int light, final int overlay) {
|
||||
stack.pushPose();
|
||||
|
||||
// Align with front face of block.
|
||||
final Direction blockFacing = projector.getBlockState().getValue(ProjectorBlock.FACING);
|
||||
final Quaternion rotation = new Quaternion(Vector3f.YN, blockFacing.toYRot(), true);
|
||||
stack.translate(0.5f, 0, 0.5f);
|
||||
stack.mulPose(rotation);
|
||||
|
||||
renderProjections(projector, stack, bufferSource);
|
||||
|
||||
renderProjectorLight(stack, bufferSource);
|
||||
|
||||
stack.popPose();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
private void renderProjections(final ProjectorBlockEntity projector, final PoseStack stack, final MultiBufferSource bufferSource) {
|
||||
final ProjectorBlockEntity.VisibilityData visibilityData = projector.getVisibilityData();
|
||||
final BitSet[] visibilities = visibilityData.visibilities();
|
||||
if (hasNoVisibleTiles(visibilities)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final BlockPos projectorPos = projector.getBlockPos();
|
||||
final Frustum frustum = new Frustum(stack.last().pose(), RenderSystem.getProjectionMatrix());
|
||||
frustum.prepare(projectorPos.getX(), projectorPos.getY(), projectorPos.getZ());
|
||||
if (!frustum.isVisible(visibilityData.visibilityBounds())) {
|
||||
return;
|
||||
}
|
||||
|
||||
stack.pushPose();
|
||||
|
||||
stack.translate(0, 0, 0.49);
|
||||
|
||||
final RenderType renderType = getUpdatedRenderType(projector);
|
||||
final VertexConsumer consumer = bufferSource.getBuffer(renderType);
|
||||
for (int distance = 0; distance < visibilities.length; distance++) {
|
||||
final BitSet visibility = visibilities[distance];
|
||||
if (!visibility.isEmpty()) {
|
||||
stack.pushPose();
|
||||
|
||||
stack.translate(0, 0, distance + 1);
|
||||
|
||||
renderProjection(stack, consumer, ProjectorBlockEntity.getLayerSize(distance), visibility);
|
||||
|
||||
stack.popPose();
|
||||
}
|
||||
}
|
||||
|
||||
stack.popPose();
|
||||
}
|
||||
|
||||
private void renderProjection(final PoseStack stack, final VertexConsumer consumer, final ProjectorBlockEntity.LayerSize layerSize, final BitSet visibility) {
|
||||
final float width = layerSize.width();
|
||||
final float height = layerSize.height();
|
||||
final int discreteWidth = layerSize.discreteWidth();
|
||||
final int discreteHeight = layerSize.discreteHeight();
|
||||
final float uOffset = (discreteHeight / height - 1) / 2f;
|
||||
final float vOffset = (discreteWidth / width - 1) / 2f;
|
||||
|
||||
stack.translate(-width / 2.0, (discreteHeight - height) / 2.0, 0);
|
||||
stack.scale(width, height, 1);
|
||||
final Matrix4f matrix = stack.last().pose();
|
||||
|
||||
for (int index = visibility.nextSetBit(0); index >= 0; index = visibility.nextSetBit(index + 1)) {
|
||||
final int x = index % discreteWidth;
|
||||
final int y = index / discreteWidth;
|
||||
|
||||
final float u0 = x / width - vOffset;
|
||||
final float u1 = (x + 1) / width - vOffset;
|
||||
final float v0 = y / height - uOffset;
|
||||
final float v1 = (y + 1) / height - uOffset;
|
||||
|
||||
consumer.vertex(matrix, u0, v0, 0).uv(1 - u0, 1 - v0).endVertex();
|
||||
consumer.vertex(matrix, u1, v0, 0).uv(1 - u1, 1 - v0).endVertex();
|
||||
consumer.vertex(matrix, u1, v1, 0).uv(1 - u1, 1 - v1).endVertex();
|
||||
consumer.vertex(matrix, u0, v1, 0).uv(1 - u0, 1 - v1).endVertex();
|
||||
}
|
||||
}
|
||||
|
||||
private void renderProjectorLight(final PoseStack stack, final MultiBufferSource bufferSource) {
|
||||
stack.pushPose();
|
||||
|
||||
stack.translate(-0.5, 0, 0.5);
|
||||
final VertexConsumer consumer = bufferSource.getBuffer(ModRenderType.getProjectorLight());
|
||||
final Matrix4f matrix = stack.last().pose();
|
||||
|
||||
final float leftFar = 1.25f;
|
||||
final float rightFar = -0.25f;
|
||||
final float topFar = 1.5f;
|
||||
final float bottomFar = 0 + 1 / 16f;
|
||||
|
||||
// Top.
|
||||
consumer.vertex(matrix, leftFar, topFar, 1).color(LIGHT_COLOR_FAR).endVertex(); // top left far
|
||||
consumer.vertex(matrix, LENS_LEFT, LENS_TOP, 0).color(LIGHT_COLOR_NEAR).endVertex(); // top left near
|
||||
consumer.vertex(matrix, LENS_RIGHT, LENS_TOP, 0).color(LIGHT_COLOR_NEAR).endVertex(); // top right near
|
||||
consumer.vertex(matrix, rightFar, topFar, 1).color(LIGHT_COLOR_FAR).endVertex(); // top right far
|
||||
|
||||
// Bottom.
|
||||
consumer.vertex(matrix, leftFar, bottomFar, 1).color(LIGHT_COLOR_FAR).endVertex(); // bottom left far
|
||||
consumer.vertex(matrix, LENS_LEFT, LENS_BOTTOM, 0).color(LIGHT_COLOR_NEAR).endVertex(); // bottom left near
|
||||
consumer.vertex(matrix, LENS_RIGHT, LENS_BOTTOM, 0).color(LIGHT_COLOR_NEAR).endVertex(); // bottom right near
|
||||
consumer.vertex(matrix, rightFar, bottomFar, 1).color(LIGHT_COLOR_FAR).endVertex(); // bottom right far
|
||||
|
||||
// Left.
|
||||
consumer.vertex(matrix, leftFar, topFar, 1).color(LIGHT_COLOR_FAR).endVertex(); // top left far
|
||||
consumer.vertex(matrix, leftFar, bottomFar, 1).color(LIGHT_COLOR_FAR).endVertex(); // bottom left far
|
||||
consumer.vertex(matrix, LENS_LEFT, LENS_BOTTOM, 0).color(LIGHT_COLOR_NEAR).endVertex(); // bottom left near
|
||||
consumer.vertex(matrix, LENS_LEFT, LENS_TOP, 0).color(LIGHT_COLOR_NEAR).endVertex(); // top left near
|
||||
|
||||
// Right.
|
||||
consumer.vertex(matrix, rightFar, topFar, 1).color(LIGHT_COLOR_FAR).endVertex(); // top right far
|
||||
consumer.vertex(matrix, LENS_RIGHT, LENS_TOP, 0).color(LIGHT_COLOR_NEAR).endVertex(); // top right near
|
||||
consumer.vertex(matrix, LENS_RIGHT, LENS_BOTTOM, 0).color(LIGHT_COLOR_NEAR).endVertex(); // bottom right near
|
||||
consumer.vertex(matrix, rightFar, bottomFar, 1).color(LIGHT_COLOR_FAR).endVertex(); // bottom right far
|
||||
|
||||
renderLens(matrix, consumer);
|
||||
renderLed(matrix, consumer);
|
||||
|
||||
stack.popPose();
|
||||
}
|
||||
|
||||
private void renderLens(final Matrix4f matrix, final VertexConsumer consumer) {
|
||||
final float lensDepth = -1 / 16f;
|
||||
consumer.vertex(matrix, LENS_RIGHT, LENS_BOTTOM, lensDepth).color(LENS_COLOR).endVertex();
|
||||
consumer.vertex(matrix, LENS_LEFT, LENS_BOTTOM, lensDepth).color(LENS_COLOR).endVertex();
|
||||
consumer.vertex(matrix, LENS_LEFT, LENS_TOP, lensDepth).color(LENS_COLOR).endVertex();
|
||||
consumer.vertex(matrix, LENS_RIGHT, LENS_TOP, lensDepth).color(LENS_COLOR).endVertex();
|
||||
}
|
||||
|
||||
private void renderLed(final Matrix4f matrix, final VertexConsumer consumer) {
|
||||
final float ledRight = 0 + 7 / 16f;
|
||||
final float ledLeft = 0 + 9 / 16f;
|
||||
final float ledBottom = 0 + 3 / 16f;
|
||||
final float ledTop = 0 + 4 / 16f;
|
||||
final float ledDepth = -0.75f / 16f;
|
||||
|
||||
consumer.vertex(matrix, ledRight, ledBottom, ledDepth).color(LED_COLOR).endVertex();
|
||||
consumer.vertex(matrix, ledLeft, ledBottom, ledDepth).color(LED_COLOR).endVertex();
|
||||
consumer.vertex(matrix, ledLeft, ledTop, ledDepth).color(LED_COLOR).endVertex();
|
||||
consumer.vertex(matrix, ledRight, ledTop, ledDepth).color(LED_COLOR).endVertex();
|
||||
}
|
||||
|
||||
private static boolean hasNoVisibleTiles(final BitSet[] visibilities) {
|
||||
for (final BitSet visibility : visibilities) {
|
||||
if (!visibility.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static RenderType getUpdatedRenderType(final ProjectorBlockEntity projector) {
|
||||
final RenderInfo renderInfo = getRenderInfo(projector);
|
||||
|
||||
final NativeImage image = renderInfo.texture().getPixels();
|
||||
assert image != null;
|
||||
if (projector.applyFramebufferChanges(image::setPixelRGBA)) {
|
||||
renderInfo.texture().upload();
|
||||
}
|
||||
|
||||
return renderInfo.renderType();
|
||||
}
|
||||
|
||||
private static RenderInfo getRenderInfo(final ProjectorBlockEntity projector) {
|
||||
try {
|
||||
return ProjectorRenderer.textures.get(projector, () -> {
|
||||
final DynamicTexture texture = new DynamicTexture(
|
||||
ProjectorVMDevice.WIDTH,
|
||||
ProjectorVMDevice.HEIGHT,
|
||||
false
|
||||
);
|
||||
return new RenderInfo(texture, ModRenderType.getProjector(texture));
|
||||
}
|
||||
);
|
||||
} catch (final ExecutionException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void updateCache(final TickEvent.ClientTickEvent event) {
|
||||
textures.cleanUp();
|
||||
}
|
||||
|
||||
private static void handleNoLongerRendering(final RemovalNotification<ProjectorBlockEntity, RenderInfo> notification) {
|
||||
final RenderInfo renderInfo = notification.getValue();
|
||||
assert renderInfo != null;
|
||||
renderInfo.texture().close();
|
||||
}
|
||||
|
||||
private record RenderInfo(DynamicTexture texture, RenderType renderType) { }
|
||||
}
|
||||
@@ -20,6 +20,8 @@ public final class Config {
|
||||
@Path("energy.blocks") public static int computerEnergyStorage = 2000;
|
||||
@Path("energy.blocks") public static int chargerEnergyPerTick = 2500;
|
||||
@Path("energy.blocks") public static int chargerEnergyStorage = 10000;
|
||||
@Path("energy.blocks") public static int projectorEnergyPerTick = 20;
|
||||
@Path("energy.blocks") public static int projectorEnergyStorage = 2000;
|
||||
|
||||
@Path("energy.entities") public static int robotEnergyPerTick = 5;
|
||||
@Path("energy.entities") public static int robotEnergyStorage = 750000;
|
||||
@@ -37,10 +39,7 @@ public final class Config {
|
||||
@Path("gameplay") public static ResourceLocation blockOperationsModuleToolTier = TierSortingRegistry.getName(Tiers.DIAMOND);
|
||||
|
||||
@Path("admin") public static UUID fakePlayerUUID = UUID.fromString("e39dd9a7-514f-4a2d-aa5e-b6030621416d");
|
||||
|
||||
public static boolean computersUseEnergy() {
|
||||
return computerEnergyPerTick > 0 && computerEnergyStorage > 0;
|
||||
}
|
||||
@Path("admin.blocks") public static int projectorMaxBytesPerTick = 8192;
|
||||
|
||||
public static boolean robotsUseEnergy() {
|
||||
return robotEnergyPerTick > 0 && robotEnergyStorage > 0;
|
||||
|
||||
@@ -19,6 +19,7 @@ import li.cil.oc2.common.tags.BlockTags;
|
||||
import li.cil.oc2.common.tags.ItemTags;
|
||||
import li.cil.oc2.common.util.RegistryUtils;
|
||||
import li.cil.oc2.common.util.SoundEvents;
|
||||
import li.cil.oc2.common.vm.provider.DeviceTreeProviders;
|
||||
import li.cil.sedna.Sedna;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.fml.DistExecutor;
|
||||
@@ -30,6 +31,7 @@ public final class Main {
|
||||
public Main() {
|
||||
Ceres.initialize();
|
||||
Sedna.initialize();
|
||||
DeviceTreeProviders.initialize();
|
||||
Serializers.initialize();
|
||||
|
||||
ConfigManager.add(Config::new);
|
||||
|
||||
@@ -19,6 +19,7 @@ public final class Blocks {
|
||||
public static final RegistryObject<NetworkConnectorBlock> NETWORK_CONNECTOR = BLOCKS.register("network_connector", NetworkConnectorBlock::new);
|
||||
public static final RegistryObject<NetworkHubBlock> NETWORK_HUB = BLOCKS.register("network_hub", NetworkHubBlock::new);
|
||||
public static final RegistryObject<RedstoneInterfaceBlock> REDSTONE_INTERFACE = BLOCKS.register("redstone_interface", RedstoneInterfaceBlock::new);
|
||||
public static final RegistryObject<ProjectorBlock> PROJECTOR = BLOCKS.register("projector", ProjectorBlock::new);
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
60
src/main/java/li/cil/oc2/common/block/ProjectorBlock.java
Normal file
@@ -0,0 +1,60 @@
|
||||
package li.cil.oc2.common.block;
|
||||
|
||||
import li.cil.oc2.common.blockentity.BlockEntities;
|
||||
import li.cil.oc2.common.blockentity.ProjectorBlockEntity;
|
||||
import li.cil.oc2.common.util.BlockEntityUtils;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.item.context.BlockPlaceContext;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.EntityBlock;
|
||||
import net.minecraft.world.level.block.HorizontalDirectionalBlock;
|
||||
import net.minecraft.world.level.block.SoundType;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityTicker;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.block.state.StateDefinition;
|
||||
import net.minecraft.world.level.material.Material;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public final class ProjectorBlock extends HorizontalDirectionalBlock implements EntityBlock {
|
||||
public ProjectorBlock() {
|
||||
super(Properties
|
||||
.of(Material.METAL)
|
||||
.sound(SoundType.METAL)
|
||||
.strength(1.5f, 6.0f));
|
||||
registerDefaultState(getStateDefinition().any()
|
||||
.setValue(FACING, Direction.NORTH));
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public BlockEntity newBlockEntity(final BlockPos pos, final BlockState state) {
|
||||
return BlockEntities.PROJECTOR.get().create(pos, state);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public <T extends BlockEntity> BlockEntityTicker<T> getTicker(final Level level, final BlockState state, final BlockEntityType<T> type) {
|
||||
if (!level.isClientSide()) {
|
||||
return BlockEntityUtils.createTicker(type, BlockEntities.PROJECTOR.get(), ProjectorBlockEntity::serverTick);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState getStateForPlacement(final BlockPlaceContext context) {
|
||||
return super.defaultBlockState().setValue(FACING, context.getHorizontalDirection().getOpposite());
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
protected void createBlockStateDefinition(final StateDefinition.Builder<Block, BlockState> builder) {
|
||||
builder.add(FACING);
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ public final class BlockEntities {
|
||||
public static final RegistryObject<BlockEntityType<DiskDriveBlockEntity>> DISK_DRIVE = register(Blocks.DISK_DRIVE, DiskDriveBlockEntity::new);
|
||||
public static final RegistryObject<BlockEntityType<ChargerBlockEntity>> CHARGER = register(Blocks.CHARGER, ChargerBlockEntity::new);
|
||||
public static final RegistryObject<BlockEntityType<CreativeEnergyBlockEntity>> CREATIVE_ENERGY = register(Blocks.CREATIVE_ENERGY, CreativeEnergyBlockEntity::new);
|
||||
public static final RegistryObject<BlockEntityType<ProjectorBlockEntity>> PROJECTOR = register(Blocks.PROJECTOR, ProjectorBlockEntity::new);
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
@@ -255,7 +255,7 @@ public final class ComputerBlockEntity extends ModBlockEntity implements Termina
|
||||
collector.offer(Capabilities.DEVICE_BUS_ELEMENT, busElement);
|
||||
collector.offer(Capabilities.TERMINAL_USER_PROVIDER, this);
|
||||
|
||||
if (Config.computersUseEnergy()) {
|
||||
if (computersUseEnergy()) {
|
||||
collector.offer(Capabilities.ENERGY_STORAGE, energy);
|
||||
}
|
||||
}
|
||||
@@ -305,6 +305,12 @@ public final class ComputerBlockEntity extends ModBlockEntity implements Termina
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
public static boolean computersUseEnergy() {
|
||||
return Config.computerEnergyPerTick > 0 && Config.computerEnergyStorage > 0;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
private final class ComputerItemStackHandlers extends AbstractVMItemStackHandlers {
|
||||
public ComputerItemStackHandlers() {
|
||||
super(new GroupDefinition(DeviceTypes.MEMORY, MEMORY_SLOTS), new GroupDefinition(DeviceTypes.HARD_DRIVE, HARD_DRIVE_SLOTS), new GroupDefinition(DeviceTypes.FLASH_MEMORY, FLASH_MEMORY_SLOTS), new GroupDefinition(DeviceTypes.CARD, CARD_SLOTS));
|
||||
@@ -424,7 +430,7 @@ public final class ComputerBlockEntity extends ModBlockEntity implements Termina
|
||||
|
||||
@Override
|
||||
protected boolean consumeEnergy(final int amount, final boolean simulate) {
|
||||
if (!Config.computersUseEnergy()) {
|
||||
if (!computersUseEnergy()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,337 @@
|
||||
package li.cil.oc2.common.blockentity;
|
||||
|
||||
import li.cil.oc2.common.Config;
|
||||
import li.cil.oc2.common.block.ProjectorBlock;
|
||||
import li.cil.oc2.common.bus.device.vm.ProjectorVMDevice;
|
||||
import li.cil.oc2.common.capabilities.Capabilities;
|
||||
import li.cil.oc2.common.energy.FixedEnergyStorage;
|
||||
import li.cil.oc2.common.network.Network;
|
||||
import li.cil.oc2.common.network.message.ProjectorFrameBufferTileMessage;
|
||||
import li.cil.oc2.common.network.message.ProjectorStateMessage;
|
||||
import li.cil.oc2.common.vm.device.SimpleFramebufferDevice;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.world.level.ClipContext;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraft.world.phys.HitResult;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.BitSet;
|
||||
import java.util.Optional;
|
||||
|
||||
public final class ProjectorBlockEntity extends ModBlockEntity {
|
||||
private static final int MAX_RENDER_DISTANCE = 12;
|
||||
private static final int MAX_WIDTH = MAX_RENDER_DISTANCE + 1; // +1 To make it odd, so we can center.
|
||||
private static final int MAX_HEIGHT = (MAX_RENDER_DISTANCE * ProjectorVMDevice.HEIGHT / ProjectorVMDevice.WIDTH) + 1; // + 1 To match horizontal margin.
|
||||
private static final LayerSize[] LAYER_SIZES = computeLayerSizes();
|
||||
|
||||
private static final String ENERGY_TAG_NAME = "energy";
|
||||
private static final String IS_PROJECTING_TAG_NAME = "projecting";
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
|
||||
private final ProjectorVMDevice projectorDevice = new ProjectorVMDevice(this);
|
||||
private boolean isProjecting, hasEnergy;
|
||||
private final FixedEnergyStorage energy = new FixedEnergyStorage(Config.projectorEnergyStorage);
|
||||
|
||||
// Client only data.
|
||||
private final BitSet dirtyLines = new BitSet(ProjectorVMDevice.HEIGHT);
|
||||
@Nullable private ByteBuffer buffer;
|
||||
private final BitSet[] visibilities = new BitSet[MAX_RENDER_DISTANCE];
|
||||
private AABB visibilityBounds;
|
||||
private AABB renderBounds;
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
|
||||
public ProjectorBlockEntity(final BlockPos pos, final BlockState state) {
|
||||
super(BlockEntities.PROJECTOR.get(), pos, state);
|
||||
|
||||
for (int i = 0; i < visibilities.length; i++) {
|
||||
visibilities[i] = new BitSet(MAX_WIDTH * MAX_HEIGHT);
|
||||
}
|
||||
visibilityBounds = super.getRenderBoundingBox();
|
||||
|
||||
updateRenderBounds();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
|
||||
public static void serverTick(final Level ignoredLevel, final BlockPos ignoredPos, final BlockState ignoredState, final ProjectorBlockEntity projector) {
|
||||
projector.serverTick();
|
||||
}
|
||||
|
||||
public ProjectorVMDevice getProjectorDevice() {
|
||||
return projectorDevice;
|
||||
}
|
||||
|
||||
public boolean isProjecting() {
|
||||
return isProjecting;
|
||||
}
|
||||
|
||||
public void setProjecting(final boolean value) {
|
||||
isProjecting = value;
|
||||
|
||||
if (!isProjecting) {
|
||||
buffer = null;
|
||||
dirtyLines.set(0, ProjectorVMDevice.HEIGHT);
|
||||
}
|
||||
|
||||
sendRunningState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundTag getUpdateTag() {
|
||||
final CompoundTag tag = super.getUpdateTag();
|
||||
|
||||
tag.putBoolean(IS_PROJECTING_TAG_NAME, isProjecting);
|
||||
projectorDevice.setAllDirty(); // todo is this good enough to be notified of new client observers?
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUpdateTag(final CompoundTag tag) {
|
||||
super.handleUpdateTag(tag);
|
||||
|
||||
setProjecting(tag.getBoolean(IS_PROJECTING_TAG_NAME));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void saveAdditional(final CompoundTag tag) {
|
||||
super.saveAdditional(tag);
|
||||
|
||||
tag.put(ENERGY_TAG_NAME, energy.serializeNBT());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load(final CompoundTag tag) {
|
||||
super.load(tag);
|
||||
|
||||
energy.deserializeNBT(tag.getCompound(ENERGY_TAG_NAME));
|
||||
}
|
||||
|
||||
@Override
|
||||
public AABB getRenderBoundingBox() {
|
||||
return renderBounds;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public void setBlockState(final BlockState state) {
|
||||
super.setBlockState(state);
|
||||
|
||||
updateRenderBounds();
|
||||
}
|
||||
|
||||
public record LayerSize(float width, float height, int discreteWidth, int discreteHeight) { }
|
||||
|
||||
public static LayerSize getLayerSize(final int distance) {
|
||||
return LAYER_SIZES[distance];
|
||||
}
|
||||
|
||||
public record VisibilityData(AABB visibilityBounds, BitSet[] visibilities) { }
|
||||
|
||||
public VisibilityData getVisibilityData() {
|
||||
updateVisibilities();
|
||||
return new VisibilityData(visibilityBounds, visibilities);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface FramebufferPixelSetter {
|
||||
void set(final int x, final int y, final int rgba);
|
||||
}
|
||||
|
||||
public boolean applyFramebufferChanges(final FramebufferPixelSetter setter) {
|
||||
if (dirtyLines.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final ByteBuffer buffer = getOrCreateBuffer();
|
||||
for (int y = dirtyLines.nextSetBit(0); y >= 0; y = dirtyLines.nextSetBit(y + 1)) {
|
||||
for (int x = 0; x < ProjectorVMDevice.WIDTH; x++) {
|
||||
final int index = (x + y * ProjectorVMDevice.WIDTH) * Short.BYTES;
|
||||
final int r5g6b5 = buffer.getShort(index) & 0xFFFF;
|
||||
setter.set(x, y, ProjectorVMDevice.toRGBA(r5g6b5));
|
||||
}
|
||||
}
|
||||
|
||||
dirtyLines.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void applyFramebufferTile(final SimpleFramebufferDevice.Tile tile) {
|
||||
tile.apply(ProjectorVMDevice.WIDTH, getOrCreateBuffer());
|
||||
dirtyLines.set(tile.startPixelY(), tile.startPixelY() + SimpleFramebufferDevice.TILE_WIDTH);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
protected void collectCapabilities(final CapabilityCollector collector, @org.jetbrains.annotations.Nullable final Direction direction) {
|
||||
if (projectorsUseEnergy()) {
|
||||
collector.offer(Capabilities.ENERGY_STORAGE, energy);
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
|
||||
private static boolean projectorsUseEnergy() {
|
||||
return Config.projectorEnergyStorage > 0 && Config.projectorEnergyPerTick > 0;
|
||||
}
|
||||
|
||||
private void serverTick() {
|
||||
if (!isProjecting()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (energy.extractEnergy(Config.projectorEnergyPerTick, true) < Config.projectorEnergyPerTick) {
|
||||
if (hasEnergy) {
|
||||
hasEnergy = false;
|
||||
sendRunningState();
|
||||
}
|
||||
return;
|
||||
} else if (!hasEnergy) {
|
||||
hasEnergy = true;
|
||||
sendRunningState();
|
||||
}
|
||||
|
||||
int byteBudget = Config.projectorMaxBytesPerTick;
|
||||
Optional<SimpleFramebufferDevice.Tile> tile;
|
||||
while (byteBudget > 0 && (tile = projectorDevice.getNextDirtyTile()).isPresent()) {
|
||||
final ProjectorFrameBufferTileMessage message = new ProjectorFrameBufferTileMessage(this, tile.get());
|
||||
Network.sendToClientsTrackingBlockEntity(message, this);
|
||||
byteBudget -= SimpleFramebufferDevice.TILE_SIZE_IN_BYTES;
|
||||
}
|
||||
}
|
||||
|
||||
private void sendRunningState() {
|
||||
if (level != null && !level.isClientSide()) {
|
||||
Network.sendToClientsTrackingBlockEntity(new ProjectorStateMessage(this, isProjecting && hasEnergy), this);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateRenderBounds() {
|
||||
final Direction blockFacing = getBlockState().getValue(ProjectorBlock.FACING);
|
||||
final Direction screenUp = Direction.UP;
|
||||
final Direction screenLeft = blockFacing.getCounterClockWise();
|
||||
|
||||
final BlockPos projectorPos = getBlockPos();
|
||||
final BlockPos screenBasePos = projectorPos.relative(blockFacing, MAX_RENDER_DISTANCE);
|
||||
final BlockPos screenMinPos = screenBasePos.relative(screenLeft.getOpposite(), MAX_WIDTH / 2);
|
||||
final BlockPos screenMaxPos = screenBasePos
|
||||
.relative(screenLeft, MAX_WIDTH / 2)
|
||||
// -1 for the MAX_HEIGHT padding, -1 for auto-expansion of AABB constructor
|
||||
.relative(screenUp, MAX_HEIGHT - 2);
|
||||
|
||||
renderBounds = new AABB(getBlockPos()).minmax(new AABB(screenMinPos)).minmax(new AABB(screenMaxPos));
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild "stencil buffer" of blocks hit in projection direction, per block layer, up to max distance.
|
||||
*/
|
||||
private void updateVisibilities() {
|
||||
final Direction blockFacing = getBlockState().getValue(ProjectorBlock.FACING);
|
||||
final Direction screenUp = Direction.UP;
|
||||
final Direction screenLeft = blockFacing.getCounterClockWise();
|
||||
|
||||
final BlockPos projectorPos = getBlockPos();
|
||||
final BlockPos screenBasePos = projectorPos.relative(blockFacing, MAX_RENDER_DISTANCE + 1);
|
||||
final BlockPos screenOriginPos = screenBasePos.relative(screenLeft.getOpposite(), MAX_WIDTH / 2);
|
||||
|
||||
final Vec3 toFaceCenter = new Vec3(blockFacing.getOpposite().step()).scale(0.45);
|
||||
final Vec3 clipStartPos = Vec3.atCenterOf(projectorPos.relative(blockFacing)).subtract(toFaceCenter);
|
||||
final Vec3 stepOrigin = Vec3.atCenterOf(screenOriginPos).add(toFaceCenter);
|
||||
final Vec3 upStep = new Vec3(screenUp.step());
|
||||
final Vec3 leftStep = new Vec3(screenLeft.step());
|
||||
|
||||
for (final BitSet bitSet : visibilities) {
|
||||
bitSet.clear();
|
||||
}
|
||||
|
||||
AABB bounds = new AABB(getBlockPos()).minmax(new AABB(getBlockPos().relative(blockFacing)));
|
||||
|
||||
final Level level = getLevel();
|
||||
if (level == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int y = 0; y < MAX_HEIGHT + 1; y++) {
|
||||
for (int x = 0; x < MAX_WIDTH; x++) {
|
||||
final Vec3 clipEndPos = stepOrigin.add(upStep.scale(y)).add(leftStep.scale(x));
|
||||
final ClipContext context = new ClipContext(clipStartPos, clipEndPos, ClipContext.Block.VISUAL, ClipContext.Fluid.NONE, null);
|
||||
final BlockHitResult hit = level.clip(context);
|
||||
if (hit.getType() == HitResult.Type.MISS) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hit.getDirection() != blockFacing.getOpposite()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final BlockState blockState = level.getBlockState(hit.getBlockPos());
|
||||
if (!blockState.isFaceSturdy(level, hit.getBlockPos(), blockFacing.getOpposite())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final BlockPos delta = hit.getBlockPos().subtract(projectorPos);
|
||||
final int distance = Math.abs(delta.get(blockFacing.getAxis())) - 2;
|
||||
if (distance >= visibilities.length) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final int globalX = delta.get(screenLeft.getAxis());
|
||||
final int globalY = delta.get(screenUp.getAxis());
|
||||
|
||||
final ProjectorBlockEntity.LayerSize layerSize = getLayerSize(distance);
|
||||
final int discreteWidth = layerSize.discreteWidth();
|
||||
final int discreteHeight = layerSize.discreteHeight();
|
||||
|
||||
if (globalY < 0 || globalY >= discreteHeight) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final int localX = globalX + discreteWidth / 2;
|
||||
if (localX < 0 || localX >= discreteWidth) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bounds = bounds.minmax(new AABB(hit.getBlockPos()));
|
||||
|
||||
final int index = localX + globalY * discreteWidth;
|
||||
visibilities[distance].set(index);
|
||||
}
|
||||
}
|
||||
|
||||
visibilityBounds = bounds;
|
||||
}
|
||||
|
||||
private ByteBuffer getOrCreateBuffer() {
|
||||
if (buffer == null) {
|
||||
buffer = projectorDevice.allocateBuffer();
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private static LayerSize[] computeLayerSizes() {
|
||||
final LayerSize[] layerSizes = new LayerSize[MAX_RENDER_DISTANCE];
|
||||
for (int distance = 0; distance < layerSizes.length; distance++) {
|
||||
final float bufferWidth = ProjectorVMDevice.WIDTH;
|
||||
final float bufferHeight = ProjectorVMDevice.HEIGHT;
|
||||
final float ratio = bufferHeight / bufferWidth;
|
||||
final float width = (MAX_WIDTH - 1) * (float) (distance + 1) / MAX_RENDER_DISTANCE;
|
||||
final float height = width * ratio;
|
||||
int discreteWidth = (int) Math.ceil(width);
|
||||
discreteWidth += 1 - (discreteWidth & 1); // we center, so even values eat up one more
|
||||
int discreteHeight = (int) Math.ceil(height); // we align, so actual height is correct
|
||||
if (Math.abs(discreteHeight - height) < 0.001) discreteHeight++;
|
||||
layerSizes[distance] = new LayerSize(width, height, discreteWidth, discreteHeight);
|
||||
}
|
||||
return layerSizes;
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ public final class Providers {
|
||||
BLOCK_DEVICE_PROVIDERS.register("item_handler", ItemHandlerBlockDeviceProvider::new);
|
||||
|
||||
BLOCK_DEVICE_PROVIDERS.register("disk_drive", DiskDriveDeviceProvider::new);
|
||||
BLOCK_DEVICE_PROVIDERS.register("projector", ProjectorDeviceProvider::new);
|
||||
|
||||
ITEM_DEVICE_PROVIDERS.register("memory", MemoryItemDeviceProvider::new);
|
||||
ITEM_DEVICE_PROVIDERS.register("hard_drive", HardDriveItemDeviceProvider::new);
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package li.cil.oc2.common.bus.device.provider.block;
|
||||
|
||||
import li.cil.oc2.api.bus.device.Device;
|
||||
import li.cil.oc2.api.bus.device.provider.BlockDeviceQuery;
|
||||
import li.cil.oc2.api.util.Invalidatable;
|
||||
import li.cil.oc2.common.block.ProjectorBlock;
|
||||
import li.cil.oc2.common.blockentity.BlockEntities;
|
||||
import li.cil.oc2.common.blockentity.ProjectorBlockEntity;
|
||||
import li.cil.oc2.common.bus.device.provider.util.AbstractBlockEntityDeviceProvider;
|
||||
import net.minecraft.core.Direction;
|
||||
|
||||
public final class ProjectorDeviceProvider extends AbstractBlockEntityDeviceProvider<ProjectorBlockEntity> {
|
||||
public ProjectorDeviceProvider() {
|
||||
super(BlockEntities.PROJECTOR.get());
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
protected Invalidatable<Device> getBlockDevice(final BlockDeviceQuery query, final ProjectorBlockEntity blockEntity) {
|
||||
final Direction blockFacing = blockEntity.getBlockState().getValue(ProjectorBlock.FACING);
|
||||
if (query.getQuerySide() != blockFacing &&
|
||||
query.getQuerySide() != blockFacing.getClockWise() &&
|
||||
query.getQuerySide() != blockFacing.getCounterClockWise()) {
|
||||
return Invalidatable.of(blockEntity.getProjectorDevice());
|
||||
} else {
|
||||
return Invalidatable.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package li.cil.oc2.common.bus.device.vm;
|
||||
|
||||
import li.cil.oc2.api.bus.device.vm.VMDevice;
|
||||
import li.cil.oc2.api.bus.device.vm.VMDeviceLoadResult;
|
||||
import li.cil.oc2.api.bus.device.vm.context.VMContext;
|
||||
import li.cil.oc2.common.Constants;
|
||||
import li.cil.oc2.common.bus.device.util.IdentityProxy;
|
||||
import li.cil.oc2.common.bus.device.util.OptionalAddress;
|
||||
import li.cil.oc2.common.bus.device.util.OptionalInterrupt;
|
||||
import li.cil.oc2.common.serialization.NBTSerialization;
|
||||
import li.cil.oc2.common.util.NBTTagIds;
|
||||
import li.cil.sedna.device.virtio.VirtIOKeyboardDevice;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public final class KeyboardVMDevice<T> extends IdentityProxy<T> implements VMDevice {
|
||||
private static final String DEVICE_TAG_NAME = "device";
|
||||
private static final String ADDRESS_TAG_NAME = "address";
|
||||
private static final String INTERRUPT_TAG_NAME = "interrupt";
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
|
||||
@Nullable private VirtIOKeyboardDevice device;
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
|
||||
private final OptionalAddress address = new OptionalAddress();
|
||||
private final OptionalInterrupt interrupt = new OptionalInterrupt();
|
||||
private CompoundTag deviceTag;
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
|
||||
public KeyboardVMDevice(final T identity) {
|
||||
super(identity);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
|
||||
public void sendKeyEvent(final int keycode, final boolean isDown) {
|
||||
if (device != null) {
|
||||
device.sendKeyEvent(keycode, isDown);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public VMDeviceLoadResult mount(final VMContext context) {
|
||||
if (!allocateDevice(context)) {
|
||||
return VMDeviceLoadResult.fail();
|
||||
}
|
||||
|
||||
assert device != null;
|
||||
if (!address.claim(context, device)) {
|
||||
return VMDeviceLoadResult.fail();
|
||||
}
|
||||
|
||||
if (interrupt.claim(context)) {
|
||||
device.getInterrupt().set(interrupt.getAsInt(), context.getInterruptController());
|
||||
} else {
|
||||
return VMDeviceLoadResult.fail();
|
||||
}
|
||||
|
||||
context.getEventBus().register(this);
|
||||
|
||||
return VMDeviceLoadResult.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unmount() {
|
||||
suspend();
|
||||
deviceTag = null;
|
||||
address.clear();
|
||||
interrupt.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void suspend() {
|
||||
device = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundTag serializeNBT() {
|
||||
final CompoundTag tag = new CompoundTag();
|
||||
|
||||
if (device != null) {
|
||||
deviceTag = NBTSerialization.serialize(device);
|
||||
}
|
||||
if (deviceTag != null) {
|
||||
tag.put(DEVICE_TAG_NAME, deviceTag);
|
||||
}
|
||||
if (address.isPresent()) {
|
||||
tag.putLong(ADDRESS_TAG_NAME, address.getAsLong());
|
||||
}
|
||||
if (interrupt.isPresent()) {
|
||||
tag.putInt(INTERRUPT_TAG_NAME, interrupt.getAsInt());
|
||||
}
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deserializeNBT(final CompoundTag tag) {
|
||||
if (tag.contains(DEVICE_TAG_NAME, NBTTagIds.TAG_COMPOUND)) {
|
||||
deviceTag = tag.getCompound(DEVICE_TAG_NAME);
|
||||
}
|
||||
if (tag.contains(ADDRESS_TAG_NAME, NBTTagIds.TAG_LONG)) {
|
||||
address.set(tag.getLong(ADDRESS_TAG_NAME));
|
||||
}
|
||||
if (tag.contains(INTERRUPT_TAG_NAME, NBTTagIds.TAG_INT)) {
|
||||
interrupt.set(tag.getInt(INTERRUPT_TAG_NAME));
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
|
||||
private boolean allocateDevice(final VMContext context) {
|
||||
if (!context.getMemoryAllocator().claimMemory(Constants.PAGE_SIZE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
device = new VirtIOKeyboardDevice(context.getMemoryMap());
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
package li.cil.oc2.common.bus.device.vm;
|
||||
|
||||
import li.cil.oc2.api.bus.device.vm.VMDevice;
|
||||
import li.cil.oc2.api.bus.device.vm.VMDeviceLoadResult;
|
||||
import li.cil.oc2.api.bus.device.vm.context.VMContext;
|
||||
import li.cil.oc2.common.Constants;
|
||||
import li.cil.oc2.common.blockentity.ProjectorBlockEntity;
|
||||
import li.cil.oc2.common.bus.device.util.IdentityProxy;
|
||||
import li.cil.oc2.common.bus.device.util.OptionalAddress;
|
||||
import li.cil.oc2.common.serialization.BlobStorage;
|
||||
import li.cil.oc2.common.util.NBTTagIds;
|
||||
import li.cil.oc2.common.vm.device.SimpleFramebufferDevice;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.MappedByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public final class ProjectorVMDevice extends IdentityProxy<ProjectorBlockEntity> implements VMDevice {
|
||||
private static final String ADDRESS_TAG_NAME = "address";
|
||||
private static final String BLOB_HANDLE_TAG_NAME = "blob";
|
||||
|
||||
public static final int WIDTH = 640;
|
||||
public static final int HEIGHT = 480;
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
|
||||
@Nullable private SimpleFramebufferDevice device;
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
|
||||
private final OptionalAddress address = new OptionalAddress();
|
||||
@Nullable private UUID blobHandle;
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
|
||||
public ProjectorVMDevice(final ProjectorBlockEntity identity) {
|
||||
super(identity);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
|
||||
public static int toRGBA(final int r5g6b5) {
|
||||
final int r5 = (r5g6b5 >>> 11) & 0b11111;
|
||||
final int g6 = (r5g6b5 >>> 5) & 0b111111;
|
||||
final int b5 = r5g6b5 & 0b11111;
|
||||
final int r = r5 * 255 / 0b11111;
|
||||
final int g = g6 * 255 / 0b111111;
|
||||
final int b = b5 * 255 / 0b11111;
|
||||
return r | (g << 8) | (b << 16) | (0xFF << 24);
|
||||
}
|
||||
|
||||
public ByteBuffer allocateBuffer() {
|
||||
return ByteBuffer.allocate(WIDTH * HEIGHT * SimpleFramebufferDevice.STRIDE).order(ByteOrder.LITTLE_ENDIAN);
|
||||
}
|
||||
|
||||
public void setAllDirty() {
|
||||
if (device != null) {
|
||||
device.setAllDirty();
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<SimpleFramebufferDevice.Tile> getNextDirtyTile() {
|
||||
if (device != null) {
|
||||
return device.getNextDirtyTile();
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public VMDeviceLoadResult mount(final VMContext context) {
|
||||
if (!allocateDevice(context)) {
|
||||
return VMDeviceLoadResult.fail();
|
||||
}
|
||||
|
||||
assert device != null;
|
||||
if (!address.claim(context, device)) {
|
||||
return VMDeviceLoadResult.fail();
|
||||
}
|
||||
|
||||
identity.setProjecting(true);
|
||||
|
||||
return VMDeviceLoadResult.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unmount() {
|
||||
suspend();
|
||||
address.clear();
|
||||
|
||||
identity.setProjecting(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void suspend() {
|
||||
closeBlockDevice();
|
||||
|
||||
if (blobHandle != null) {
|
||||
BlobStorage.close(blobHandle);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompoundTag serializeNBT() {
|
||||
final CompoundTag tag = new CompoundTag();
|
||||
|
||||
if (blobHandle != null) {
|
||||
tag.putUUID(BLOB_HANDLE_TAG_NAME, blobHandle);
|
||||
}
|
||||
if (address.isPresent()) {
|
||||
tag.putLong(ADDRESS_TAG_NAME, address.getAsLong());
|
||||
}
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deserializeNBT(final CompoundTag tag) {
|
||||
if (tag.hasUUID(BLOB_HANDLE_TAG_NAME)) {
|
||||
blobHandle = tag.getUUID(BLOB_HANDLE_TAG_NAME);
|
||||
}
|
||||
if (tag.contains(ADDRESS_TAG_NAME, NBTTagIds.TAG_LONG)) {
|
||||
address.set(tag.getLong(ADDRESS_TAG_NAME));
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
|
||||
private boolean allocateDevice(final VMContext context) {
|
||||
if (!context.getMemoryAllocator().claimMemory(Constants.PAGE_SIZE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
device = createFrameBufferDevice();
|
||||
} catch (final IOException e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private SimpleFramebufferDevice createFrameBufferDevice() throws IOException {
|
||||
blobHandle = BlobStorage.validateHandle(blobHandle);
|
||||
final FileChannel channel = BlobStorage.getOrOpen(blobHandle);
|
||||
final MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, WIDTH * HEIGHT * SimpleFramebufferDevice.STRIDE);
|
||||
return new SimpleFramebufferDevice(WIDTH, HEIGHT, buffer);
|
||||
}
|
||||
|
||||
private void closeBlockDevice() {
|
||||
if (device == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
device.close();
|
||||
|
||||
device = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
@ParametersAreNonnullByDefault
|
||||
@MethodsReturnNonnullByDefault
|
||||
package li.cil.oc2.common.bus.device.vm;
|
||||
|
||||
import net.minecraft.MethodsReturnNonnullByDefault;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
@@ -29,6 +29,7 @@ public final class Items {
|
||||
public static final RegistryObject<Item> NETWORK_CONNECTOR = register(Blocks.NETWORK_CONNECTOR);
|
||||
public static final RegistryObject<Item> NETWORK_HUB = register(Blocks.NETWORK_HUB);
|
||||
public static final RegistryObject<Item> REDSTONE_INTERFACE = register(Blocks.REDSTONE_INTERFACE);
|
||||
public static final RegistryObject<Item> PROJECTOR = register(Blocks.PROJECTOR);
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
@@ -73,6 +73,9 @@ public final class Network {
|
||||
|
||||
registerMessage(NetworkInterfaceCardConfigurationMessage.class, NetworkInterfaceCardConfigurationMessage::new, NetworkDirection.PLAY_TO_SERVER);
|
||||
registerMessage(NetworkTunnelLinkMessage.class, NetworkTunnelLinkMessage::new, NetworkDirection.PLAY_TO_SERVER);
|
||||
|
||||
registerMessage(ProjectorFrameBufferTileMessage.class, ProjectorFrameBufferTileMessage::new, NetworkDirection.PLAY_TO_CLIENT);
|
||||
registerMessage(ProjectorStateMessage.class, ProjectorStateMessage::new, NetworkDirection.PLAY_TO_CLIENT);
|
||||
}
|
||||
|
||||
public static <T> void sendToServer(final T message) {
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package li.cil.oc2.common.network.message;
|
||||
|
||||
import li.cil.oc2.common.blockentity.ProjectorBlockEntity;
|
||||
import li.cil.oc2.common.network.MessageUtils;
|
||||
import li.cil.oc2.common.vm.device.SimpleFramebufferDevice;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
public final class ProjectorFrameBufferTileMessage extends AbstractMessage {
|
||||
private BlockPos pos;
|
||||
private SimpleFramebufferDevice.Tile tile;
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
public ProjectorFrameBufferTileMessage(final ProjectorBlockEntity projector, final SimpleFramebufferDevice.Tile tile) {
|
||||
this.pos = projector.getBlockPos();
|
||||
this.tile = tile;
|
||||
}
|
||||
|
||||
public ProjectorFrameBufferTileMessage(final FriendlyByteBuf buffer) {
|
||||
super(buffer);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void fromBytes(final FriendlyByteBuf buffer) {
|
||||
pos = buffer.readBlockPos();
|
||||
final int startPixelX = buffer.readVarInt();
|
||||
final int startPixelY = buffer.readVarInt();
|
||||
final ByteBuffer data = ByteBuffer.allocate(SimpleFramebufferDevice.TILE_SIZE_IN_BYTES).order(ByteOrder.LITTLE_ENDIAN);
|
||||
buffer.readBytes(data);
|
||||
data.flip();
|
||||
tile = new SimpleFramebufferDevice.Tile(startPixelX, startPixelY, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toBytes(final FriendlyByteBuf buffer) {
|
||||
buffer.writeBlockPos(pos);
|
||||
buffer.writeVarInt(tile.startPixelX());
|
||||
buffer.writeVarInt(tile.startPixelY());
|
||||
buffer.writeBytes(tile.data());
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
protected void handleMessage(final NetworkEvent.Context context) {
|
||||
MessageUtils.withClientBlockEntityAt(pos, ProjectorBlockEntity.class,
|
||||
projector -> projector.applyFramebufferTile(tile));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package li.cil.oc2.common.network.message;
|
||||
|
||||
import li.cil.oc2.common.blockentity.ProjectorBlockEntity;
|
||||
import li.cil.oc2.common.network.MessageUtils;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
|
||||
public class ProjectorStateMessage extends AbstractMessage {
|
||||
private BlockPos pos;
|
||||
private boolean isProjecting;
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
public ProjectorStateMessage(final ProjectorBlockEntity projector, final boolean isProjecting) {
|
||||
this.pos = projector.getBlockPos();
|
||||
this.isProjecting = isProjecting;
|
||||
}
|
||||
|
||||
public ProjectorStateMessage(final FriendlyByteBuf buffer) {
|
||||
super(buffer);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public void fromBytes(final FriendlyByteBuf buffer) {
|
||||
pos = buffer.readBlockPos();
|
||||
isProjecting = buffer.readBoolean();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toBytes(final FriendlyByteBuf buffer) {
|
||||
buffer.writeBlockPos(pos);
|
||||
buffer.writeBoolean(isProjecting);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
protected void handleMessage(final NetworkEvent.Context context) {
|
||||
MessageUtils.withClientBlockEntityAt(pos, ProjectorBlockEntity.class,
|
||||
projector -> projector.setProjecting(isProjecting));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
package li.cil.oc2.common.vm.device;
|
||||
|
||||
import li.cil.sedna.api.device.MemoryMappedDevice;
|
||||
import li.cil.sedna.api.memory.MemoryAccessException;
|
||||
import li.cil.sedna.utils.DirectByteBufferUtils;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.BitSet;
|
||||
import java.util.Optional;
|
||||
|
||||
public final class SimpleFramebufferDevice implements MemoryMappedDevice {
|
||||
public static final int STRIDE = 2;
|
||||
public static final int TILE_WIDTH = 32;
|
||||
public static final int TILE_SIZE = TILE_WIDTH * TILE_WIDTH;
|
||||
public static final int TILE_SIZE_IN_BYTES = TILE_SIZE * STRIDE;
|
||||
|
||||
public record Tile(int startPixelX, int startPixelY, ByteBuffer data) {
|
||||
public void apply(final int width, final ByteBuffer buffer) {
|
||||
if (buffer.capacity() < TILE_SIZE * STRIDE) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
final int startIndex = (startPixelX + startPixelY * width) * STRIDE;
|
||||
final int rowWidth = width * STRIDE;
|
||||
final int tileRowBytes = TILE_WIDTH * STRIDE;
|
||||
buffer.position(startIndex);
|
||||
for (int i = 0; i < TILE_WIDTH; i++) {
|
||||
buffer.slice(startIndex + i * rowWidth, tileRowBytes)
|
||||
.put(data.slice(i * tileRowBytes, tileRowBytes));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
|
||||
private final int width, height;
|
||||
private final ByteBuffer buffer;
|
||||
private int length;
|
||||
private final int tileCount;
|
||||
private final BitSet dirty;
|
||||
private int lastDirtyIndex;
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
|
||||
public SimpleFramebufferDevice(final int width, final int height, final ByteBuffer buffer) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.length = width * height * STRIDE;
|
||||
|
||||
if (buffer.capacity() < length) {
|
||||
throw new IllegalArgumentException("Buffer too small.");
|
||||
}
|
||||
|
||||
this.buffer = buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
this.tileCount = width * height / TILE_SIZE;
|
||||
this.dirty = new BitSet(tileCount);
|
||||
setAllDirty();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
|
||||
public void close() {
|
||||
length = 0;
|
||||
DirectByteBufferUtils.release(buffer);
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
public void setAllDirty() {
|
||||
synchronized (dirty) {
|
||||
dirty.clear();
|
||||
dirty.flip(0, tileCount);
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<Tile> getNextDirtyTile() {
|
||||
final int index = dirty.nextSetBit(lastDirtyIndex);
|
||||
if (index < 0) {
|
||||
lastDirtyIndex = 0;
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
synchronized (dirty) {
|
||||
dirty.clear(index);
|
||||
}
|
||||
|
||||
lastDirtyIndex = index + 1;
|
||||
if (lastDirtyIndex >= tileCount) {
|
||||
lastDirtyIndex = 0;
|
||||
}
|
||||
|
||||
final int tileCountX = width / TILE_WIDTH;
|
||||
final int tileX = index % tileCountX;
|
||||
final int tileY = index / tileCountX;
|
||||
final int startPixelX = tileX * TILE_WIDTH;
|
||||
final int startPixelY = tileY * TILE_WIDTH;
|
||||
return Optional.of(new Tile(startPixelX, startPixelY, getTileData(startPixelX, startPixelY)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long load(final int offset, final int sizeLog2) throws MemoryAccessException {
|
||||
if (offset >= 0 && offset <= length - (1 << sizeLog2)) {
|
||||
return switch (sizeLog2) {
|
||||
case 0 -> buffer.get(offset);
|
||||
case 1 -> buffer.getShort(offset);
|
||||
case 2 -> buffer.getInt(offset);
|
||||
case 3 -> buffer.getLong(offset);
|
||||
default -> throw new IllegalArgumentException();
|
||||
};
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void store(final int offset, final long value, final int sizeLog2) throws MemoryAccessException {
|
||||
if (offset >= 0 && offset <= length - (1 << sizeLog2)) {
|
||||
switch (sizeLog2) {
|
||||
case 0 -> buffer.put(offset, (byte) value);
|
||||
case 1 -> buffer.putShort(offset, (short) value);
|
||||
case 2 -> buffer.putInt(offset, (int) value);
|
||||
case 3 -> buffer.putLong(offset, value);
|
||||
default -> throw new IllegalArgumentException();
|
||||
}
|
||||
setDirty(offset);
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
|
||||
private int getTileIndex(final int offset) {
|
||||
final int pixelIndex = offset / STRIDE;
|
||||
final int tileCountX = width / TILE_WIDTH;
|
||||
final int pixelX = pixelIndex % width;
|
||||
final int pixelY = pixelIndex / width;
|
||||
final int tileX = pixelX / TILE_WIDTH;
|
||||
final int tileY = pixelY / TILE_WIDTH;
|
||||
return tileX + tileY * tileCountX;
|
||||
}
|
||||
|
||||
private ByteBuffer getTileData(final int startPixelX, final int startPixelY) {
|
||||
final ByteBuffer result = ByteBuffer.allocate(TILE_SIZE_IN_BYTES).order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
final int startIndex = (startPixelX + startPixelY * width) * STRIDE;
|
||||
final int rowWidth = width * STRIDE;
|
||||
for (int i = 0; i < TILE_WIDTH; i++) {
|
||||
result.put(buffer.slice(startIndex + i * rowWidth, TILE_WIDTH * 2));
|
||||
}
|
||||
|
||||
result.flip();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void setDirty(final int offset) {
|
||||
final int tileIndex = getTileIndex(offset);
|
||||
if (!dirty.get(tileIndex)) {
|
||||
synchronized (dirty) {
|
||||
dirty.set(tileIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package li.cil.oc2.common.vm.provider;
|
||||
|
||||
import li.cil.oc2.common.vm.device.SimpleFramebufferDevice;
|
||||
import li.cil.sedna.devicetree.DeviceTreeRegistry;
|
||||
|
||||
public final class DeviceTreeProviders {
|
||||
public static void initialize() {
|
||||
DeviceTreeRegistry.putProvider(SimpleFramebufferDevice.class, new SimpleFramebufferDeviceProvider());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package li.cil.oc2.common.vm.provider;
|
||||
|
||||
import li.cil.oc2.common.vm.device.SimpleFramebufferDevice;
|
||||
import li.cil.sedna.api.device.Device;
|
||||
import li.cil.sedna.api.device.MemoryMappedDevice;
|
||||
import li.cil.sedna.api.devicetree.DevicePropertyNames;
|
||||
import li.cil.sedna.api.devicetree.DeviceTree;
|
||||
import li.cil.sedna.api.devicetree.DeviceTreeProvider;
|
||||
import li.cil.sedna.api.memory.MappedMemoryRange;
|
||||
import li.cil.sedna.api.memory.MemoryMap;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public final class SimpleFramebufferDeviceProvider implements DeviceTreeProvider {
|
||||
@Override
|
||||
public Optional<String> getName(final Device device) {
|
||||
return Optional.of("framebuffer");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<DeviceTree> createNode(final DeviceTree root, final MemoryMap memoryMap, final Device device, final String deviceName) {
|
||||
final Optional<MappedMemoryRange> range = memoryMap.getMemoryRange((MemoryMappedDevice) device);
|
||||
return range.map(r -> {
|
||||
final DeviceTree chosen = root.find("/chosen");
|
||||
chosen.addProp(DevicePropertyNames.RANGES);
|
||||
|
||||
return chosen.getChild(deviceName, r.address());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(final DeviceTree node, final MemoryMap memoryMap, final Device device) {
|
||||
final SimpleFramebufferDevice fb = (SimpleFramebufferDevice) device;
|
||||
node
|
||||
.addProp(DevicePropertyNames.COMPATIBLE, "simple-framebuffer")
|
||||
.addProp("width", fb.getWidth())
|
||||
.addProp("height", fb.getHeight())
|
||||
.addProp("stride", fb.getWidth() * SimpleFramebufferDevice.STRIDE)
|
||||
.addProp("format", "r5g6b5")
|
||||
.addProp("no-map")
|
||||
.addProp(DevicePropertyNames.STATUS, "okay");
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ public final class ModBlockStateProvider extends BlockStateProvider {
|
||||
private static final ResourceLocation DISK_DRIVE_MODEL = new ResourceLocation(API.MOD_ID, "block/disk_drive");
|
||||
private static final ResourceLocation NETWORK_CONNECTOR_MODEL = new ResourceLocation(API.MOD_ID, "block/network_connector");
|
||||
private static final ResourceLocation NETWORK_HUB_MODEL = new ResourceLocation(API.MOD_ID, "block/network_hub");
|
||||
private static final ResourceLocation PROJECTOR_MODEL = new ResourceLocation(API.MOD_ID, "block/projector");
|
||||
private static final ResourceLocation REDSTONE_INTERFACE_MODEL = new ResourceLocation(API.MOD_ID, "block/redstone_interface");
|
||||
|
||||
public ModBlockStateProvider(final DataGenerator generator, final ExistingFileHelper existingFileHelper) {
|
||||
@@ -50,6 +51,7 @@ public final class ModBlockStateProvider extends BlockStateProvider {
|
||||
horizontalBlock(Blocks.DISK_DRIVE, Items.DISK_DRIVE, DISK_DRIVE_MODEL);
|
||||
horizontalBlock(Blocks.CHARGER, Items.CHARGER, CHARGER_MODEL);
|
||||
simpleBlock(Blocks.CREATIVE_ENERGY, Items.CREATIVE_ENERGY);
|
||||
horizontalBlock(Blocks.PROJECTOR, Items.PROJECTOR, PROJECTOR_MODEL);
|
||||
|
||||
registerCableStates();
|
||||
}
|
||||
|
||||
@@ -20,7 +20,8 @@ public final class ModBlockTagsProvider extends BlockTagsProvider {
|
||||
tag(DEVICES).add(
|
||||
COMPUTER.get(),
|
||||
REDSTONE_INTERFACE.get(),
|
||||
DISK_DRIVE.get()
|
||||
DISK_DRIVE.get(),
|
||||
PROJECTOR.get()
|
||||
);
|
||||
tag(CABLES).add(
|
||||
BUS_CABLE.get()
|
||||
@@ -32,7 +33,8 @@ public final class ModBlockTagsProvider extends BlockTagsProvider {
|
||||
NETWORK_HUB.get(),
|
||||
REDSTONE_INTERFACE.get(),
|
||||
DISK_DRIVE.get(),
|
||||
CHARGER.get()
|
||||
CHARGER.get(),
|
||||
PROJECTOR.get()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,8 @@ public final class ModItemTagsProvider extends ItemTagsProvider {
|
||||
Items.NETWORK_INTERFACE_CARD.get(),
|
||||
Items.DISK_DRIVE.get(),
|
||||
Items.NETWORK_TUNNEL_CARD.get(),
|
||||
Items.NETWORK_TUNNEL_MODULE.get()
|
||||
Items.NETWORK_TUNNEL_MODULE.get(),
|
||||
Items.PROJECTOR.get()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ public final class ModLootTableProvider extends LootTableProvider {
|
||||
dropSelf(Blocks.NETWORK_HUB.get());
|
||||
dropSelf(Blocks.DISK_DRIVE.get());
|
||||
dropSelf(Blocks.CHARGER.get());
|
||||
dropSelf(Blocks.PROJECTOR.get());
|
||||
|
||||
add(Blocks.COMPUTER.get(), ModBlockLootTables::droppingWithInventory);
|
||||
}
|
||||
|
||||
19
src/main/resources/assets/oc2/blockstates/projector.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"variants": {
|
||||
"facing=north": {
|
||||
"model": "oc2:block/projector"
|
||||
},
|
||||
"facing=south": {
|
||||
"model": "oc2:block/projector",
|
||||
"y": 180
|
||||
},
|
||||
"facing=west": {
|
||||
"model": "oc2:block/projector",
|
||||
"y": 270
|
||||
},
|
||||
"facing=east": {
|
||||
"model": "oc2:block/projector",
|
||||
"y": 90
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,8 @@
|
||||
"block.oc2.charger.desc": "Charges entities and items in containers on top of it.",
|
||||
"block.oc2.creative_energy": "Infinite Energy Cube",
|
||||
"block.oc2.creative_energy.desc": "Provides unlimited energy to adjacent blocks. Intended for testing.",
|
||||
"block.oc2.projector": "Projector",
|
||||
"block.oc2.projector.desc": "Projects to be displayed content onto surfaces in front of it.",
|
||||
|
||||
"item.oc2.wrench": "Scrench",
|
||||
"item.oc2.wrench.desc": "Configures devices and dismantles them (while sneaking).",
|
||||
|
||||
3
src/main/resources/assets/oc2/models/item/projector.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"parent": "oc2:block/projector"
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 628 B |
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"type": "minecraft:block",
|
||||
"pools": [
|
||||
{
|
||||
"rolls": 1.0,
|
||||
"bonus_rolls": 0.0,
|
||||
"entries": [
|
||||
{
|
||||
"type": "minecraft:item",
|
||||
"name": "oc2:projector"
|
||||
}
|
||||
],
|
||||
"conditions": [
|
||||
{
|
||||
"condition": "minecraft:survives_explosion"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
"values": [
|
||||
"oc2:computer",
|
||||
"oc2:redstone_interface",
|
||||
"oc2:disk_drive"
|
||||
"oc2:disk_drive",
|
||||
"oc2:projector"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"oc2:network_hub",
|
||||
"oc2:redstone_interface",
|
||||
"oc2:disk_drive",
|
||||
"oc2:charger"
|
||||
"oc2:charger",
|
||||
"oc2:projector"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"oc2:network_interface_card",
|
||||
"oc2:disk_drive",
|
||||
"oc2:network_tunnel_card",
|
||||
"oc2:network_tunnel_module"
|
||||
"oc2:network_tunnel_module",
|
||||
"oc2:projector"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"oc2:computer",
|
||||
"oc2:redstone_interface",
|
||||
"oc2:disk_drive",
|
||||
"oc2:projector",
|
||||
"#oc2:devices/memory",
|
||||
"#oc2:devices/hard_drive",
|
||||
"#oc2:devices/flash_memory",
|
||||
@@ -11,4 +12,4 @@
|
||||
"#oc2:devices/robot_module",
|
||||
"#oc2:devices/floppy"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||