diff --git a/build.gradle b/build.gradle index 3498e50..7b37f32 100644 --- a/build.gradle +++ b/build.gradle @@ -83,9 +83,10 @@ dependencies { // implementation fg.deobf("curse.maven:spark-361579:4381167") compileOnly fg.deobf("curse.maven:vivecraft-667903:4794431") - implementation fg.deobf("com.cinemamod:mcef-forge:2.0.1-1.20.1") { - transitive = false - } +// implementation fg.deobf("com.cinemamod:mcef-forge:2.0.1-1.20.1") { +// transitive = false +// } + implementation fg.deobf("flatdir.lib:mcef:2.x") } sourceSets { diff --git a/libs/mcef-2.x.jar b/libs/mcef-2.x.jar index f4316ec..412932a 100644 Binary files a/libs/mcef-2.x.jar and b/libs/mcef-2.x.jar differ diff --git a/src/main/java/net/montoyo/wd/WebDisplays.java b/src/main/java/net/montoyo/wd/WebDisplays.java index 8473021..2175895 100644 --- a/src/main/java/net/montoyo/wd/WebDisplays.java +++ b/src/main/java/net/montoyo/wd/WebDisplays.java @@ -4,6 +4,8 @@ package net.montoyo.wd; +import com.cinemamod.mcef.MCEF; +import com.cinemamod.mcef.MCEFBrowser; import com.google.gson.Gson; import net.minecraft.ChatFormatting; import net.minecraft.advancements.Advancement; @@ -48,10 +50,16 @@ import net.montoyo.wd.init.TabInit; import net.montoyo.wd.init.TileInit; import net.montoyo.wd.miniserv.server.Server; import net.montoyo.wd.net.WDNetworkRegistry; +import net.montoyo.wd.net.client_bound.S2CMessageEnableSSR; import net.montoyo.wd.net.client_bound.S2CMessageServerInfo; +import net.montoyo.wd.remote.IWDBrowser; +import net.montoyo.wd.remote.client.RemoteBrowser; +import net.montoyo.wd.remote.server.BlankBrowser; +import net.montoyo.wd.remote.server.ServerBrowser; import net.montoyo.wd.utilities.DistSafety; import net.montoyo.wd.utilities.Log; import net.montoyo.wd.utilities.Util; +import org.cef.browser.CefBrowser; import java.io.*; import java.net.MalformedURLException; @@ -65,7 +73,7 @@ public class WebDisplays { public static WebDisplays INSTANCE; public static SharedProxy PROXY = null; - + public static final ResourceLocation ADV_PAD_BREAK = new ResourceLocation("webdisplays", "webdisplays/pad_break"); public static final String BLACKLIST_URL = "mod://webdisplays/blacklisted.html"; public static final Gson GSON = new Gson(); @@ -96,29 +104,32 @@ public class WebDisplays { public long miniservQuota; public float ytVolume; public float avDist100; + public float avDist0; - // mod detection private boolean hasOC; + private boolean hasCC; + private static boolean SSR = false; + public WebDisplays() { INSTANCE = this; - if(FMLEnvironment.dist.isClient()) { + if (FMLEnvironment.dist.isClient()) { PROXY = DistSafety.createProxy(); } else { PROXY = new SharedProxy(); } - + if (FMLEnvironment.dist.isClient()) { // proxies are annoying, so from now on, I'mma be just registering stuff in here FMLJavaModLoadingContext.get().getModEventBus().addListener(ClientProxy::onKeybindRegistry); MinecraftForge.EVENT_BUS.addListener(ClientProxy::onDrawSelection); ClientConfig.init(); } - + CommonConfig.init(); - + //Criterions criterionPadBreak = new Criterion("pad_break"); criterionUpgradeScreen = new Criterion("upgrade_screen"); @@ -134,9 +145,9 @@ public class WebDisplays { BlockInit.init(bus); ItemInit.init(bus); TileInit.init(bus); - + PROXY.preInit(); - + MinecraftForge.EVENT_BUS.register(this); //Other things @@ -155,7 +166,7 @@ public class WebDisplays { t.printStackTrace(); } } */ - + if (!FMLEnvironment.production) { ScreenControlRegistry.init(); } @@ -210,7 +221,7 @@ public class WebDisplays { if (miniservPort != 0) { Server sv = Server.getInstance(); - if(!serverStartedDimensions.contains(level.dimension())) { + if (!serverStartedDimensions.contains(level.dimension())) { sv.setPort(miniservPort); sv.setDirectory(new File(worldDir, "wd_filehost")); sv.start(); @@ -222,7 +233,7 @@ public class WebDisplays { @SubscribeEvent public void onWorldLeave(LevelEvent.Unload ev) throws IOException { - if(ev.getLevel() instanceof Level level) { + if (ev.getLevel() instanceof Level level) { if (ev.getLevel().isClientSide() || level.dimension() != Level.OVERWORLD) return; Server sw = Server.getInstance(); @@ -233,7 +244,7 @@ public class WebDisplays { @SubscribeEvent public void onWorldSave(LevelEvent.Save ev) { - if(ev.getLevel() instanceof Level level) { + if (ev.getLevel() instanceof Level level) { if (ev.getLevel().isClientSide() || level.dimension() != Level.OVERWORLD) return; File f = new File(Objects.requireNonNull(ev.getLevel().getServer()).getServerDirectory(), "wd_next.txt"); @@ -250,13 +261,13 @@ public class WebDisplays { @SubscribeEvent public void onToss(ItemTossEvent ev) { - if(!ev.getEntity().level().isClientSide) { + if (!ev.getEntity().level().isClientSide) { ItemStack is = ev.getEntity().getItem(); - if(is.getItem() == ItemInit.MINEPAD.get()) { + if (is.getItem() == ItemInit.MINEPAD.get()) { CompoundTag tag = is.getTag(); - if(tag == null) { + if (tag == null) { tag = new CompoundTag(); is.setTag(tag); } @@ -271,11 +282,11 @@ public class WebDisplays { @SubscribeEvent public void onPlayerCraft(PlayerEvent.ItemCraftedEvent ev) { - if(CommonConfig.hardRecipes && ItemInit.isCompCraftItem(ev.getCrafting().getItem()) && (CraftComponent.EXTCARD.makeItemStack().is(ev.getCrafting().getItem()))) { - if((ev.getEntity() instanceof ServerPlayer && !hasPlayerAdvancement((ServerPlayer) ev.getEntity(), ADV_PAD_BREAK)) || PROXY.hasClientPlayerAdvancement(ADV_PAD_BREAK) != HasAdvancement.YES) { + if (CommonConfig.hardRecipes && ItemInit.isCompCraftItem(ev.getCrafting().getItem()) && (CraftComponent.EXTCARD.makeItemStack().is(ev.getCrafting().getItem()))) { + if ((ev.getEntity() instanceof ServerPlayer && !hasPlayerAdvancement((ServerPlayer) ev.getEntity(), ADV_PAD_BREAK)) || PROXY.hasClientPlayerAdvancement(ADV_PAD_BREAK) != HasAdvancement.YES) { ev.getCrafting().setDamageValue(CraftComponent.BADEXTCARD.ordinal()); - if(!ev.getEntity().level().isClientSide) + if (!ev.getEntity().level().isClientSide) ev.getEntity().level().playSound(null, ev.getEntity().getX(), ev.getEntity().getY(), ev.getEntity().getZ(), SoundEvents.ITEM_BREAK, SoundSource.MASTER, 1.0f, 1.0f); } } @@ -288,10 +299,10 @@ public class WebDisplays { @SubscribeEvent public void onLogIn(PlayerEvent.PlayerLoggedInEvent ev) { - if(!ev.getEntity().level().isClientSide && ev.getEntity() instanceof ServerPlayer) { + if (!ev.getEntity().level().isClientSide && ev.getEntity() instanceof ServerPlayer) { IWDDCapability cap = ev.getEntity().getCapability(WDDCapability.Provider.cap, null).orElseThrow(RuntimeException::new); - if(cap.isFirstRun()) { + if (cap.isFirstRun()) { Util.toast(ev.getEntity(), ChatFormatting.LIGHT_PURPLE, "welcome1"); Util.toast(ev.getEntity(), ChatFormatting.LIGHT_PURPLE, "welcome2"); Util.toast(ev.getEntity(), ChatFormatting.LIGHT_PURPLE, "welcome3"); @@ -311,27 +322,27 @@ public class WebDisplays { @SubscribeEvent public void onLogOut(PlayerEvent.PlayerLoggedOutEvent ev) { - if(!ev.getEntity().level().isClientSide) + if (!ev.getEntity().level().isClientSide) Server.getInstance().getClientManager().revokeClientKey(ev.getEntity().getGameProfile().getId()); } @SubscribeEvent public void attachEntityCaps(AttachCapabilitiesEvent ev) { - if(ev.getObject() instanceof Player) + if (ev.getObject() instanceof Player) ev.addCapability(CAPABILITY, new WDDCapability.Provider()); } @SubscribeEvent public void onPlayerClone(PlayerEvent.Clone ev) { - IWDDCapability src = ev.getOriginal().getCapability(WDDCapability.Provider.cap, null).orElse(new WDDCapability.Factory().call()); - IWDDCapability dst = ev.getEntity().getCapability(WDDCapability.Provider.cap, null).orElse(new WDDCapability.Factory().call()); + IWDDCapability src = ev.getOriginal().getCapability(WDDCapability.Provider.cap, null).orElse(new WDDCapability.Factory().call()); + IWDDCapability dst = ev.getEntity().getCapability(WDDCapability.Provider.cap, null).orElse(new WDDCapability.Factory().call()); - if(src == null) { + if (src == null) { Log.error("src is null"); return; } - if(dst == null) { + if (dst == null) { Log.error("dst is null"); return; } @@ -343,14 +354,14 @@ public class WebDisplays { public void onServerChat(ServerChatEvent ev) { String msg = ev.getMessage().getString().replaceAll("\\s+", " ").toLowerCase(); StringBuilder sb = new StringBuilder(msg.length()); - for(int i = 0; i < msg.length(); i++) { + for (int i = 0; i < msg.length(); i++) { char chr = msg.charAt(i); - if(chr != '.' && chr != ',' && chr != ';' && chr != '!' && chr != '?' && chr != ':' && chr != '\'' && chr != '\"' && chr != '`') + if (chr != '.' && chr != ',' && chr != ';' && chr != '!' && chr != '?' && chr != ':' && chr != '\'' && chr != '\"' && chr != '`') sb.append(chr); } - if(sb.toString().equals("ironic he could save others from death but not himself")) { + if (sb.toString().equals("ironic he could save others from death but not himself")) { Player ply = ev.getPlayer(); ply.level().playSound(null, ply.getX(), ply.getY(), ply.getZ(), soundIronic, SoundSource.PLAYERS, 1.0f, 1.0f); } @@ -358,13 +369,13 @@ public class WebDisplays { @SubscribeEvent public void onClientChat(ClientChatEvent ev) { - if(ev.getMessage().equals("!WD render recipes")) + if (ev.getMessage().equals("!WD render recipes")) PROXY.renderRecipes(); } private boolean hasPlayerAdvancement(ServerPlayer ply, ResourceLocation rl) { MinecraftServer server = PROXY.getServer(); - if(server == null) + if (server == null) return false; Advancement adv = server.getAdvancements().getAdvancement(rl); @@ -385,18 +396,18 @@ public class WebDisplays { return ret; } - private static void registerTrigger(Criterion ... criteria) { - for(Criterion c: criteria) + private static void registerTrigger(Criterion... criteria) { + for (Criterion c : criteria) CriteriaTriggers.register(c); } - // public static boolean isOpenComputersAvailable() { - // return INSTANCE.hasOC; - // } + // public static boolean isOpenComputersAvailable() { + // return INSTANCE.hasOC; + // } - // public static boolean isComputerCraftAvailable() { - // return INSTANCE.hasCC; - // } + // public static boolean isComputerCraftAvailable() { + // return INSTANCE.hasCC; + // } public static boolean isSiteBlacklisted(String url) { try { @@ -404,7 +415,7 @@ public class WebDisplays { for (String str : CommonConfig.Browser.blacklist) if (str.equalsIgnoreCase(url2.getHost())) return true; return false; - } catch(MalformedURLException ex) { + } catch (MalformedURLException ex) { return false; } } @@ -412,5 +423,20 @@ public class WebDisplays { public static String applyBlacklist(String url) { return isSiteBlacklisted(url) ? BLACKLIST_URL : url; } -} + public static boolean isSSR() { + return SSR; + } + + public static void markSSR() { + StackTraceElement[] elements = Thread.currentThread().getStackTrace(); + StackTraceElement caller = elements[3]; + try { + Class c = Class.forName(caller.getClassName()); + if (c == S2CMessageEnableSSR.class || c == WebDisplays.class) { + SSR = true; + } + } catch (Throwable err) { + } + } +} diff --git a/src/main/java/net/montoyo/wd/client/renderers/ScreenRenderer.java b/src/main/java/net/montoyo/wd/client/renderers/ScreenRenderer.java index 5bf19c8..fa7036e 100644 --- a/src/main/java/net/montoyo/wd/client/renderers/ScreenRenderer.java +++ b/src/main/java/net/montoyo/wd/client/renderers/ScreenRenderer.java @@ -12,6 +12,7 @@ import net.minecraft.client.renderer.MultiBufferSource; import net.minecraft.client.renderer.blockentity.BlockEntityRenderer; import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider; import net.montoyo.wd.entity.TileEntityScreen; +import net.montoyo.wd.remote.IWDBrowser; import net.montoyo.wd.utilities.Vector3f; import net.montoyo.wd.utilities.Vector3i; import org.jetbrains.annotations.NotNull; @@ -47,7 +48,7 @@ public class ScreenRenderer implements BlockEntityRenderer { for (int i = 0; i < te.screenCount(); i++) { TileEntityScreen.Screen scr = te.getScreen(i); if (scr.browser == null) { - scr.createBrowser(true); + scr.createBrowser(false, true); } // TODO: manually backface cull the screens @@ -120,7 +121,7 @@ public class ScreenRenderer implements BlockEntityRenderer { //TODO: don't use tesselator RenderSystem.enableDepthTest(); RenderSystem.setShader(GameRenderer::getPositionTexColorShader); - RenderSystem._setShaderTexture(0, ((MCEFBrowser) scr.browser).getRenderer().getTextureID()); + RenderSystem._setShaderTexture(0, ((IWDBrowser) scr.browser).getRenderer().getTextureID()); RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f); builder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX_COLOR); builder.vertex(poseStack.last().pose(), -sw, -sh, 0.505f).uv(0.f, 1.f).color(1.f, 1.f, 1.f, 1.f).endVertex(); diff --git a/src/main/java/net/montoyo/wd/entity/TileEntityScreen.java b/src/main/java/net/montoyo/wd/entity/TileEntityScreen.java index 451736e..b935aa4 100644 --- a/src/main/java/net/montoyo/wd/entity/TileEntityScreen.java +++ b/src/main/java/net/montoyo/wd/entity/TileEntityScreen.java @@ -43,6 +43,10 @@ import net.montoyo.wd.net.client_bound.S2CMessageAddScreen; import net.montoyo.wd.net.client_bound.S2CMessageCloseGui; import net.montoyo.wd.net.client_bound.S2CMessageJSResponse; import net.montoyo.wd.net.client_bound.S2CMessageScreenUpdate; +import net.montoyo.wd.remote.BrowserGen; +import net.montoyo.wd.remote.IRemoteBrowser; +import net.montoyo.wd.remote.IWDBrowser; +import net.montoyo.wd.remote.client.RemoteBrowser; import net.montoyo.wd.utilities.*; import org.cef.browser.CefBrowser; import org.lwjgl.opengl.GL11; @@ -59,13 +63,13 @@ import java.util.function.Consumer; import static net.montoyo.wd.block.BlockPeripheral.point; public class TileEntityScreen extends BlockEntity { - + public TileEntityScreen(BlockPos arg2, BlockState arg3) { super(TileInit.SCREEN_BLOCK_ENTITY.get(), arg2, arg3); } public static class Screen { - + public BlockSide side; public Vector2i size; public Vector2i resolution; @@ -76,7 +80,7 @@ public class TileEntityScreen extends BlockEntity { public ArrayList friends; public int friendRights; public int otherRights; - public CefBrowser browser; + public IWDBrowser browser; public ArrayList upgrades; public boolean doTurnOnAnim; public long turnOnTime; @@ -84,9 +88,9 @@ public class TileEntityScreen extends BlockEntity { public final Vector2i lastMousePos = new Vector2i(); public NibbleArray redstoneStatus; //null on client public boolean autoVolume = true; - + public int mouseType; - + public static Screen deserialize(CompoundTag tag) { Screen ret = new Screen(); ret.side = BlockSide.values()[tag.getByte("Side")]; @@ -95,47 +99,47 @@ public class TileEntityScreen extends BlockEntity { ret.rotation = Rotation.values()[tag.getByte("Rotation")]; ret.url = tag.getString("URL"); ret.videoType = VideoType.getTypeFromURL(ret.url); - + if (ret.resolution.x <= 0 || ret.resolution.y <= 0) { float psx = ((float) ret.size.x) * 16.f - 4.f; float psy = ((float) ret.size.y) * 16.f - 4.f; psx *= 8.f; //TODO: Use ratio in config file psy *= 8.f; - + ret.resolution.x = (int) psx; ret.resolution.y = (int) psy; } - + if (tag.contains("OwnerName")) { String name = tag.getString("OwnerName"); UUID uuid = tag.getUUID("OwnerUUID"); ret.owner = new NameUUIDPair(name, uuid); } - + ListTag friends = tag.getList("Friends", 10); ret.friends = new ArrayList<>(friends.size()); - + for (int i = 0; i < friends.size(); i++) { CompoundTag nf = friends.getCompound(i); NameUUIDPair pair = new NameUUIDPair(nf.getString("Name"), nf.getUUID("UUID")); ret.friends.add(pair); } - + ret.friendRights = tag.getByte("FriendRights"); ret.otherRights = tag.getByte("OtherRights"); - + ListTag upgrades = tag.getList("Upgrades", 10); ret.upgrades = new ArrayList<>(); - + for (int i = 0; i < upgrades.size(); i++) ret.upgrades.add(ItemStack.of(upgrades.getCompound(i))); - + if (tag.contains("AutoVolume")) ret.autoVolume = tag.getBoolean("AutoVolume"); - + return ret; } - + public CompoundTag serialize() { CompoundTag tag = new CompoundTag(); tag.putByte("Side", (byte) side.ordinal()); @@ -145,131 +149,133 @@ public class TileEntityScreen extends BlockEntity { tag.putInt("ResolutionY", resolution.y); tag.putByte("Rotation", (byte) rotation.ordinal()); tag.putString("URL", url); - + if (owner == null) Log.warning("Found TES with NO OWNER!!"); else { tag.putString("OwnerName", owner.name); tag.putUUID("OwnerUUID", owner.uuid); } - + ListTag list = new ListTag(); for (NameUUIDPair f : friends) { CompoundTag nf = new CompoundTag(); nf.putString("Name", f.name); nf.putUUID("UUID", f.uuid); - + list.add(nf); } - + tag.put("Friends", list); tag.putByte("FriendRights", (byte) friendRights); tag.putByte("OtherRights", (byte) otherRights); - + list = new ListTag(); for (ItemStack is : upgrades) list.add(is.save(new CompoundTag())); - + tag.put("Upgrades", list); tag.putBoolean("AutoVolume", autoVolume); return tag; } - + public int rightsFor(Player ply) { return rightsFor(ply.getGameProfile().getId()); } - + public int rightsFor(UUID uuid) { if (owner.uuid.equals(uuid)) return ScreenRights.ALL; - + return friends.stream().anyMatch(f -> f.uuid.equals(uuid)) ? friendRights : otherRights; } - + public void setupRedstoneStatus(Level world, BlockPos start) { if (world.isClientSide()) { Log.warning("Called Screen.setupRedstoneStatus() on client."); return; } - + if (redstoneStatus != null) { Log.warning("Called Screen.setupRedstoneStatus() on server, but redstone status is non-null"); return; } - + Direction[] VALUES = Direction.values(); redstoneStatus = new NibbleArray(size.x * size.y); final Direction facing = VALUES[side.reverse().ordinal()]; final ScreenIterator it = new ScreenIterator(start, side, size); - + while (it.hasNext()) { int idx = it.getIndex(); redstoneStatus.set(idx, world.getSignal(it.next(), facing)); } } - - + + public void clampResolution() { if (resolution.x > CommonConfig.Screen.maxResolutionX) { float newY = ((float) resolution.y) * ((float) CommonConfig.Screen.maxResolutionX) / ((float) resolution.x); resolution.x = CommonConfig.Screen.maxResolutionX; resolution.y = (int) newY; } - + if (resolution.y > CommonConfig.Screen.maxResolutionY) { float newX = ((float) resolution.x) * ((float) CommonConfig.Screen.maxResolutionY) / ((float) resolution.y); resolution.x = (int) newX; resolution.y = CommonConfig.Screen.maxResolutionY; } } - - public void createBrowser(boolean doAnim) { - if (WebDisplays.PROXY instanceof ClientProxy clientProxy) { - browser = MCEF.createBrowser(WebDisplays.applyBlacklist(url != null ? url : "https://www.google.com"), false); - - if (browser instanceof MCEFBrowser mcefBrowser) { - if (rotation.isVertical) - mcefBrowser.resize(resolution.y, resolution.x); - else - mcefBrowser.resize(resolution.x, resolution.y); - - // uh yes this is intentional - // basically: on my laptop, this line caused an error inexplicably - // reason: the compiler didn't update this file, so it stayed as a Consumer in the bytecode - //noinspection RedundantCast - mcefBrowser.setCursorChangeListener((MCEFCursorChangeListener) (type) -> mouseType = type); - } - + + public void createBrowser(boolean server, boolean doAnim) { + browser = BrowserGen.createBrowser( + server, + WebDisplays.applyBlacklist(url != null ? url : "https://www.google.com"), + false + ); + + if (browser != null) { + if (rotation.isVertical) + browser.resize(resolution.y, resolution.x); + else + browser.resize(resolution.x, resolution.y); + + // uh yes this is intentional + // basically: on my laptop, this line caused an error inexplicably + // reason: the compiler didn't update this file, so it stayed as a Consumer in the bytecode + //noinspection RedundantCast + browser.setCursorChangeListener((MCEFCursorChangeListener) (type) -> mouseType = type); + doTurnOnAnim = doAnim; turnOnTime = System.currentTimeMillis(); } } } - + public void forEachScreenBlocks(BlockSide side, Consumer func) { Screen scr = getScreen(side); - + if (scr != null) { ScreenIterator it = new ScreenIterator(getBlockPos(), side, scr.size); - + while (it.hasNext()) func.accept(it.next()); } } - + private final ArrayList screens = new ArrayList<>(); private net.minecraft.world.phys.AABB renderBB = new net.minecraft.world.phys.AABB(0.0, 0.0, 0.0, 1.0, 1.0, 1.0); private boolean loaded = true; public float ytVolume = Float.POSITIVE_INFINITY; - + public boolean isLoaded() { return loaded; } - + public void load() { loaded = true; } - + public void unload() { for (Screen scr : screens) { if (scr.browser != null) { @@ -277,57 +283,33 @@ public class TileEntityScreen extends BlockEntity { scr.browser = null; } } - + loaded = false; } - - @Override - public void load(CompoundTag tag) { - super.load(tag); - - ListTag list = tag.getList("WDScreens", Tag.TAG_COMPOUND); - if (list.isEmpty()) - return; - - screens.clear(); - for (int i = 0; i < list.size(); i++) - screens.add(Screen.deserialize(list.getCompound(i))); - } - + @Override public CompoundTag getUpdateTag() { CompoundTag tag = new CompoundTag(); - saveAdditional(tag); + saveAdditional(true, tag); return tag; } - + @Override public void handleUpdateTag(CompoundTag tag) { - load(tag); + load(true, tag); for (Screen screen : screens) { - if (screen.browser == null) screen.createBrowser(false); + if (screen.browser == null) screen.createBrowser(false, false); if (screen.browser != null) screen.browser.loadURL(screen.url); } updateAABB(); } - - @Override - protected void saveAdditional(CompoundTag tag) { - super.saveAdditional(tag); - - ListTag list = new ListTag(); - for (Screen scr : screens) - list.add(scr.serialize()); - - tag.put("WDScreens", list); - } - + public Screen addScreen(BlockSide side, Vector2i size, @Nullable Vector2i resolution, @Nullable Player owner, boolean sendUpdate) { for (Screen scr : screens) { if (scr.side == side) return scr; } - + Screen ret = new Screen(); ret.side = side; ret.size = size; @@ -336,84 +318,87 @@ public class TileEntityScreen extends BlockEntity { ret.friendRights = ScreenRights.DEFAULTS; ret.otherRights = ScreenRights.DEFAULTS; ret.upgrades = new ArrayList<>(); - + if (owner != null) { ret.owner = new NameUUIDPair(owner.getGameProfile()); - + if (side == BlockSide.TOP || side == BlockSide.BOTTOM) { int rot = (int) Math.floor(((double) (owner.getYRot() * 4.0f / 360.0f)) + 2.5) & 3; - + if (side == BlockSide.TOP) { if (rot == 1) rot = 3; else if (rot == 3) rot = 1; } - + ret.rotation = Rotation.values()[rot]; } } - + if (resolution == null || resolution.x < 1 || resolution.y < 1) { float psx = ((float) size.x) * 16.f - 4.f; float psy = ((float) size.y) * 16.f - 4.f; psx *= 8.f; //TODO: Use ratio in config file psy *= 8.f; - + ret.resolution = new Vector2i((int) psx, (int) psy); } else ret.resolution = resolution; - + ret.clampResolution(); - + if (!level.isClientSide) { ret.setupRedstoneStatus(level, getBlockPos()); - + if (sendUpdate) WDNetworkRegistry.INSTANCE.send(PacketDistributor.NEAR.with(() -> point(level, getBlockPos())), new S2CMessageAddScreen(this, ret)); } - + screens.add(ret); - + if (level.isClientSide) updateAABB(); else setChanged(); // level.blockEntityChanged(worldPosition); - + + if (!level.isClientSide && WebDisplays.isSSR()) + ret.createBrowser(true, false); + return ret; } - + public Screen getScreen(BlockSide side) { for (Screen scr : screens) { if (scr.side == side) return scr; } - + return null; } - + public int screenCount() { return screens.size(); } - + public Screen getScreen(int idx) { return screens.get(idx); } - + public void clear() { screens.clear(); - + if (!level.isClientSide) setChanged(); } - + public void requestData(ServerPlayer ep) { if (!level.isClientSide) WDNetworkRegistry.INSTANCE.send(PacketDistributor.PLAYER.with(() -> ep), new S2CMessageAddScreen(this)); } - + public static String url(String url) throws IOException { System.out.println("URL received: " + url); if (!(WebDisplays.PROXY instanceof ClientProxy)) { @@ -427,20 +412,20 @@ public class TileEntityScreen extends BlockEntity { return url; // TODO: ? } } - + public void setScreenURL(BlockSide side, String url) throws IOException { Screen scr = getScreen(side); if (scr == null) { Log.error("Attempt to change URL of non-existing screen on side %s", side.toString()); return; } - + String weburl = url(url); - + weburl = WebDisplays.applyBlacklist(weburl); scr.url = weburl; scr.videoType = VideoType.getTypeFromURL(weburl); - + if (level.isClientSide) { if (scr.browser != null) scr.browser.loadURL(weburl); @@ -449,7 +434,7 @@ public class TileEntityScreen extends BlockEntity { setChanged(); } } - + public void removeScreen(BlockSide side) { int idx = -1; for (int i = 0; i < screens.size(); i++) { @@ -458,12 +443,12 @@ public class TileEntityScreen extends BlockEntity { break; } } - + if (idx < 0) { Log.error("Tried to delete non-existing screen on side %s", side.toString()); return; } - + if (level.isClientSide) { if (screens.get(idx).browser != null) { screens.get(idx).browser.close(true); @@ -471,9 +456,9 @@ public class TileEntityScreen extends BlockEntity { } } else WDNetworkRegistry.INSTANCE.send(PacketDistributor.NEAR.with(() -> point(level, getBlockPos())), new S2CMessageScreenUpdate(this.getBlockPos(), side)); //Delete the screen - + screens.remove(idx); - + if (!level.isClientSide) { if (screens.isEmpty()) //No more screens: remove tile entity level.setBlockAndUpdate(getBlockPos(), BlockInit.blockScreen.get().defaultBlockState().setValue(BlockScreen.hasTE, false)); @@ -481,25 +466,25 @@ public class TileEntityScreen extends BlockEntity { setChanged(); } } - + public void setResolution(BlockSide side, Vector2i res) { if (res.x < 1 || res.y < 1) { Log.warning("Call to TileEntityScreen.setResolution(%s) with suspicious values X=%d and Y=%d", side.toString(), res.x, res.y); return; } - + Screen scr = getScreen(side); if (scr == null) { Log.error("Tried to change resolution of non-existing screen on side %s", side.toString()); return; } - + scr.resolution = res; scr.clampResolution(); - + if (level.isClientSide) { WebDisplays.PROXY.screenUpdateResolutionInGui(new Vector3i(getBlockPos()), side, res); - + if (scr.browser != null) { scr.browser.close(true); scr.browser = null; //Will be re-created by renderer @@ -509,82 +494,81 @@ public class TileEntityScreen extends BlockEntity { setChanged(); } } - + private static Player getLaserUser(Screen scr) { if (scr.laserUser != null) { if (scr.laserUser.isRemoved() || !scr.laserUser.getItemInHand(InteractionHand.MAIN_HAND).getItem().equals(ItemInit.LASER_POINTER.get())) scr.laserUser = null; } - + return scr.laserUser; } - + private static void checkLaserUserRights(Screen scr) { if (scr.laserUser != null && (scr.rightsFor(scr.laserUser) & ScreenRights.INTERACT) == 0) scr.laserUser = null; } - + public void clearLaserUser(BlockSide side) { Screen scr = getScreen(side); - + if (scr != null) scr.laserUser = null; } - + public void click(BlockSide side, Vector2i vec) { Screen scr = getScreen(side); if (scr == null) { Log.error("Attempt click non-existing screen of side %s", side.toString()); return; } - + if (level.isClientSide) Log.warning("TileEntityScreen.click() from client side is useless..."); else if (getLaserUser(scr) == null) WDNetworkRegistry.INSTANCE.send(PacketDistributor.NEAR.with(() -> point(level, getBlockPos())), S2CMessageScreenUpdate.click(this, side, ClickControl.ControlType.CLICK, vec)); } - + void clickUnsafe(BlockSide side, ClickControl.ControlType action, int x, int y) { if (level.isClientSide) { Vector2i vec = (action == ClickControl.ControlType.UP) ? null : new Vector2i(x, y); WDNetworkRegistry.INSTANCE.send(PacketDistributor.NEAR.with(() -> point(level, getBlockPos())), S2CMessageScreenUpdate.click(this, side, action, vec)); } } - + public void handleMouseEvent(BlockSide side, ClickControl.ControlType event, @Nullable Vector2i vec, int button) { if (button > 1) return; // buttons above 1 crash the game - + Screen scr = getScreen(side); if (scr == null) { Log.error("Attempt inject mouse events on non-existing screen of side %s", side.toString()); return; } - - if (scr.browser instanceof MCEFBrowser mcefBrowser) { - if (button == 1) button = 0; - else if (button == 0) button = 1; - - if (event == ClickControl.ControlType.CLICK) { - mcefBrowser.sendMouseMove(vec.x, vec.y); //Move to target - mcefBrowser.sendMousePress(vec.x, vec.y, button); //Press - mcefBrowser.sendMouseRelease(vec.x, vec.y, button); //Release - } else if (event == ClickControl.ControlType.DOWN) { - mcefBrowser.sendMouseMove(vec.x, vec.y); //Move to target - mcefBrowser.sendMousePress(vec.x, vec.y, button); //Press - } else if (event == ClickControl.ControlType.MOVE) - mcefBrowser.sendMouseMove(vec.x, vec.y); //Move - else if (event == ClickControl.ControlType.UP) - mcefBrowser.sendMouseRelease(scr.lastMousePos.x, scr.lastMousePos.y, button); //Release - - mcefBrowser.setFocus(true); - - if (vec != null) { - scr.lastMousePos.x = vec.x; - scr.lastMousePos.y = vec.y; - } + + if (button == 1) button = 0; + else if (button == 0) button = 1; + + IWDBrowser browser = scr.browser; + if (event == ClickControl.ControlType.CLICK) { + browser.sendMouseMove(vec.x, vec.y); //Move to target + browser.sendMousePress(vec.x, vec.y, button); //Press + browser.sendMouseRelease(vec.x, vec.y, button); //Release + } else if (event == ClickControl.ControlType.DOWN) { + browser.sendMouseMove(vec.x, vec.y); //Move to target + browser.sendMousePress(vec.x, vec.y, button); //Press + } else if (event == ClickControl.ControlType.MOVE) + browser.sendMouseMove(vec.x, vec.y); //Move + else if (event == ClickControl.ControlType.UP) + browser.sendMouseRelease(scr.lastMousePos.x, scr.lastMousePos.y, button); //Release + + browser.setFocus(true); + + if (vec != null) { + scr.lastMousePos.x = vec.x; + scr.lastMousePos.y = vec.y; } } - + // public void updateJSRedstone(BlockSide side, Vector2i vec, int redstoneLevel) { // Screen scr = getScreen(side); // if (scr == null) { @@ -680,19 +664,19 @@ public class TileEntityScreen extends BlockEntity { // } else // WDNetworkRegistry.INSTANCE.send(PacketDistributor.PLAYER.with(() -> src), new S2CMessageJSResponse(reqId, req, 400, "Invalid request")); // } - + @Override public void onLoad() { if (level.isClientSide) { WebDisplays.PROXY.trackScreen(this, true); } } - + @Override public void onChunkUnloaded() { if (level.isClientSide) { WebDisplays.PROXY.trackScreen(this, false); - + for (Screen scr : screens) { if (scr.browser != null) { scr.browser.close(true); @@ -701,14 +685,14 @@ public class TileEntityScreen extends BlockEntity { } } } - + private void updateAABB() { Vector3i origin = new Vector3i(getBlockPos()); MutableAABB box = null; - + for (Screen scr : screens) { Vector3i f = scr.side.forward; - + int fx = Math.max(f.x, 0); int fy = Math.max(f.y, 0); int fz = Math.max(f.z, 0); @@ -720,13 +704,13 @@ public class TileEntityScreen extends BlockEntity { scr.side.equals(BlockSide.TOP) || scr.side.equals(BlockSide.BOTTOM) ) oz = 1; - + if (box == null) { box = new MutableAABB( origin.x + fx + ox, origin.y + fy, origin.z + fz + oz, - + origin.x + ox + scr.side.right.x * scr.size.x + fx + scr.side.up.x * scr.size.y, origin.y + scr.side.right.y * scr.size.x + fy + scr.side.up.y * scr.size.y, origin.z + oz + scr.side.right.z * scr.size.x + fz + scr.side.up.z * scr.size.y @@ -736,24 +720,24 @@ public class TileEntityScreen extends BlockEntity { origin.x + fx + ox, origin.y + fy, origin.z + fz + oz, - + origin.x + ox + scr.side.right.x * scr.size.x + fx + scr.side.up.x * scr.size.y, origin.y + scr.side.right.y * scr.size.x + fy + scr.side.up.y * scr.size.y, origin.z + oz + scr.side.right.z * scr.size.x + fz + scr.side.up.z * scr.size.y ); } } - + if (box == null) renderBB = new AABB(worldPosition); else renderBB = box.toMc(); } - + @Override @Nonnull public net.minecraft.world.phys.AABB getRenderBoundingBox() { return renderBB; } - + // //FIXME: Not called if enableSoundDistance is false // public void updateTrackDistance(double d, float masterVolume) { // final WebDisplays wd = WebDisplays.INSTANCE; @@ -787,7 +771,7 @@ public class TileEntityScreen extends BlockEntity { // } // } // } - + public void updateClientSideURL(CefBrowser target, String url) { for (Screen scr : screens) { if (scr.browser == target) { @@ -801,23 +785,23 @@ public class TileEntityScreen extends BlockEntity { scr.url = blacklisted ? WebDisplays.BLACKLIST_URL : url; //FIXME: This is an invalid fix for something that CANNOT be fixed scr.videoType = VideoType.getTypeFromURL(scr.url); ytVolume = Float.POSITIVE_INFINITY; //Force volume update - + if (blacklisted && scr.browser != null) scr.browser.loadURL(WebDisplays.BLACKLIST_URL); - + break; } } } - + @Override public void invalidateCaps() { super.invalidateCaps(); - + if (level.isClientSide) onChunkUnloaded(); } - + public void addFriend(ServerPlayer ply, BlockSide side, NameUUIDPair pair) { if (!level.isClientSide) { Screen scr = getScreen(side); @@ -825,7 +809,7 @@ public class TileEntityScreen extends BlockEntity { Log.error("Tried to add friend to invalid screen side %s", side.toString()); return; } - + if (!scr.friends.contains(pair)) { scr.friends.add(pair); (new ScreenConfigData(new Vector3i(getBlockPos()), side, scr)).updateOnly().sendTo(point(level, getBlockPos())); @@ -833,7 +817,7 @@ public class TileEntityScreen extends BlockEntity { } } } - + public void removeFriend(ServerPlayer ply, BlockSide side, NameUUIDPair pair) { if (!level.isClientSide) { Screen scr = getScreen(side); @@ -841,7 +825,7 @@ public class TileEntityScreen extends BlockEntity { Log.error("Tried to remove friend from invalid screen side %s", side.toString()); return; } - + if (scr.friends.remove(pair)) { checkLaserUserRights(scr); (new ScreenConfigData(new Vector3i(getBlockPos()), side, scr)).updateOnly().sendTo(point(level, getBlockPos())); @@ -849,7 +833,7 @@ public class TileEntityScreen extends BlockEntity { } } } - + public void setRights(ServerPlayer ply, BlockSide side, int fr, int or) { if (!level.isClientSide) { Screen scr = getScreen(side); @@ -857,27 +841,27 @@ public class TileEntityScreen extends BlockEntity { Log.error("Tried to change rights of invalid screen on side %s", side.toString()); return; } - + scr.friendRights = fr; scr.otherRights = or; - + checkLaserUserRights(scr); (new ScreenConfigData(new Vector3i(getBlockPos()), side, scr)).updateOnly().sendTo(point(level, getBlockPos())); setChanged(); } } - + public void type(BlockSide side, String text, BlockPos soundPos) { type(side, text, soundPos, null); } - + public void type(BlockSide side, String text, BlockPos soundPos, @Nullable ServerPlayer sender) { Screen scr = getScreen(side); if (scr == null) { Log.error("Tried to type on invalid screen on side %s", side.toString()); return; } - + if (level.isClientSide) { if (scr.browser instanceof MCEFBrowser mcefBrowser) { try { @@ -886,12 +870,12 @@ public class TileEntityScreen extends BlockEntity { char chr = text.charAt(i); if (chr == 1) break; - + mcefBrowser.sendKeyTyped(chr, 0); } } else { TypeData[] data = WebDisplays.GSON.fromJson(text, TypeData[].class); - + for (TypeData ev : data) { if (ev.getKeyCode() == 257) { ev = new TypeData( @@ -900,7 +884,7 @@ public class TileEntityScreen extends BlockEntity { ev.getScanCode() ); } - + switch (ev.getAction()) { case PRESS -> { mcefBrowser.sendKeyPress(ev.getKeyCode(), ev.getScanCode(), ev.getModifier()); @@ -910,7 +894,7 @@ public class TileEntityScreen extends BlockEntity { case RELEASE -> mcefBrowser.sendKeyRelease(ev.getKeyCode(), ev.getScanCode(), ev.getModifier()); case TYPE -> mcefBrowser.sendKeyTyped((char) ev.getKeyCode(), ev.getModifier()); // TODO: check - + default -> throw new RuntimeException("Invalid type action '" + ev.getAction() + '\''); } } @@ -925,20 +909,20 @@ public class TileEntityScreen extends BlockEntity { () -> point(sender, level, getBlockPos()) : () -> point(level, getBlockPos()) ), S2CMessageScreenUpdate.type(this, side, text)); - + if (soundPos != null) playSoundAt(WebDisplays.INSTANCE.soundTyping, soundPos, 0.25f, 1.f); } } - + private void playSoundAt(SoundEvent snd, BlockPos at, float vol, float pitch) { double x = at.getX(); double y = at.getY(); double z = at.getZ(); - + level.playSound(null, x + 0.5, y + 0.5, z + 0.5, snd, SoundSource.BLOCKS, vol, pitch); } - + // public void updateUpgrades(BlockSide side, ItemStack[] upgrades) { // if (!level.isClientSide) { // Log.error("Tried to call TileEntityScreen.updateUpgrades() from server side..."); @@ -957,11 +941,11 @@ public class TileEntityScreen extends BlockEntity { // if (scr.browser != null) // scr.browser.runJS("if(typeof webdisplaysUpgradesChanged == \"function\") webdisplaysUpgradesChanged();", ""); // } - + private static String safeName(ItemStack is) { return is.getItem().getName(is).getString(); } - + //If equal is null, no duplicate check is preformed public boolean addUpgrade(BlockSide side, ItemStack is, @Nullable Player player, boolean abortIfExisting) { if (level.isClientSide) { @@ -974,30 +958,30 @@ public class TileEntityScreen extends BlockEntity { itemAsUpgrade.onInstall(this, side, player, isCopy); return false; } - + Screen scr = getScreen(side); if (scr == null) { Log.error("Tried to add an upgrade on invalid screen on side %s", side.toString()); return false; } - + if (!(is.getItem() instanceof IUpgrade)) { Log.error("Tried to add a non-upgrade item %s to screen (%s does not implement IUpgrade)", safeName(is), is.getItem().getClass().getCanonicalName()); return false; } - + if (scr.upgrades.size() >= 16) { Log.error("Can't insert upgrade %s in screen %s at %s: too many upgrades already!", safeName(is), side.toString(), getBlockPos().toString()); return false; } - + IUpgrade itemAsUpgrade = (IUpgrade) is.getItem(); if (abortIfExisting && scr.upgrades.stream().anyMatch(otherStack -> itemAsUpgrade.isSameUpgrade(is, otherStack))) return false; //Upgrade already exists - + ItemStack isCopy = is.copy(); //FIXME: Duct tape fix, because the original stack will be shrinked isCopy.setCount(1); - + scr.upgrades.add(isCopy); if (player != null && !player.level().isClientSide) { WDNetworkRegistry.INSTANCE.send(PacketDistributor.NEAR.with(() -> point(level, getBlockPos())), S2CMessageScreenUpdate.upgrade(this, side, true, is)); @@ -1007,19 +991,19 @@ public class TileEntityScreen extends BlockEntity { setChanged(); return true; } - + public boolean hasUpgrade(BlockSide side, ItemStack is) { Screen scr = getScreen(side); if (scr == null) return false; - + if (!(is.getItem() instanceof IUpgrade)) return false; - + IUpgrade itemAsUpgrade = (IUpgrade) is.getItem(); return scr.upgrades.stream().anyMatch(otherStack -> itemAsUpgrade.isSameUpgrade(is, otherStack)); } - + public boolean hasUpgrade(BlockSide side, DefaultUpgrade du) { Screen scr = getScreen(side); if (du == DefaultUpgrade.LASERMOUSE) { @@ -1034,32 +1018,32 @@ public class TileEntityScreen extends BlockEntity { return false; } } - + public void removeUpgrade(BlockSide side, ItemStack is, @Nullable Player player) { if (level.isClientSide) return; - + Screen scr = getScreen(side); if (scr == null) { Log.error("Tried to remove an upgrade on invalid screen on side %s", side.toString()); return; } - + if (!(is.getItem() instanceof IUpgrade)) { Log.error("Tried to remove a non-upgrade item %s to screen (%s does not implement IUpgrade)", safeName(is), is.getItem().getClass().getCanonicalName()); return; } - + int idxToRemove = -1; IUpgrade itemAsUpgrade = (IUpgrade) is.getItem(); - + for (int i = 0; i < scr.upgrades.size(); i++) { if (itemAsUpgrade.isSameUpgrade(is, scr.upgrades.get(i))) { idxToRemove = i; break; } } - + if (idxToRemove >= 0) { dropUpgrade(scr.upgrades.get(idxToRemove), side, player); scr.upgrades.remove(idxToRemove); @@ -1071,51 +1055,51 @@ public class TileEntityScreen extends BlockEntity { } else Log.warning("Tried to remove non-existing upgrade %s to screen %s at %s", safeName(is), side.toString(), getBlockPos().toString()); } - + private void dropUpgrade(ItemStack is, BlockSide side, @Nullable Player ply) { if (!((IUpgrade) is.getItem()).onRemove(this, side, ply, is)) { //Drop upgrade item boolean spawnDrop = true; - + if (ply != null) { if (ply.isCreative() || ply.addItem(is)) spawnDrop = false; //If in creative or if the item was added to the player's inventory, don't spawn drop entity } - + if (spawnDrop) { Vector3f pos = new Vector3f((float) this.getBlockPos().getX(), (float) this.getBlockPos().getY(), (float) this.getBlockPos().getZ()); pos.addMul(side.backward.toFloat(), 1.5f); - + if (level != null) { level.addFreshEntity(new ItemEntity(level, pos.x, pos.y, pos.z, is)); } } } } - + private Screen getScreenForLaserOp(BlockSide side, Player ply) { if (level.isClientSide) return null; - + Screen scr = getScreen(side); if (scr == null) { Log.error("Called laser operation on invalid screen on side %s", side.toString()); return null; } - + if ((scr.rightsFor(ply) & ScreenRights.INTERACT) == 0) return null; //Don't output an error, it can 'legally' happen - + if (scr.upgrades.stream().noneMatch(DefaultUpgrade.LASERMOUSE::matchesLaserMouse)) { Log.error("Called laser operation on side %s, but it's missing the laser sensor upgrade", side.toString()); return null; } - + return scr; //Okay, go for it... } - + public void laserDownMove(BlockSide side, Player ply, Vector2i pos, boolean down, int button) { Screen scr = getScreenForLaserOp(side, ply); - + if (scr != null) { if (button == -1) WDNetworkRegistry.INSTANCE.send(PacketDistributor.NEAR.with(() -> point(ply, level, getBlockPos())), S2CMessageScreenUpdate.click(this, side, ClickControl.ControlType.MOVE, pos)); @@ -1125,10 +1109,10 @@ public class TileEntityScreen extends BlockEntity { WDNetworkRegistry.INSTANCE.send(PacketDistributor.NEAR.with(() -> point(ply, level, getBlockPos())), S2CMessageScreenUpdate.click(this, side, ClickControl.ControlType.UP, pos)); } } - + public void laserUp(BlockSide side, Player ply, int button) { Screen scr = getScreenForLaserOp(side, ply); - + if (scr != null) { if (getLaserUser(scr) == ply) { scr.laserUser = null; @@ -1136,13 +1120,13 @@ public class TileEntityScreen extends BlockEntity { } } } - + public void onDestroy(@Nullable Player ply) { for (Screen scr : screens) { scr.upgrades.forEach(is -> dropUpgrade(is, scr.side, ply)); scr.upgrades.clear(); } - + WDNetworkRegistry.INSTANCE.send(PacketDistributor.NEAR.with(() -> point(level, getBlockPos())), S2CMessageScreenUpdate.turnOff(getBlockPos(), null)); } @@ -1165,43 +1149,43 @@ public class TileEntityScreen extends BlockEntity { remove.upgrades.clear(); screens.remove(remove); } - + public void setOwner(BlockSide side, Player newOwner) { if (level.isClientSide) { Log.error("Called TileEntityScreen.setOwner() on client..."); return; } - + if (newOwner == null) { Log.error("Called TileEntityScreen.setOwner() with null owner"); return; } - + Screen scr = getScreen(side); if (scr == null) { Log.error("Called TileEntityScreen.setOwner() on invalid screen on side %s", side.toString()); return; } - + scr.owner = new NameUUIDPair(newOwner.getGameProfile()); WDNetworkRegistry.INSTANCE.send(PacketDistributor.NEAR.with(() -> point(level, getBlockPos())), S2CMessageScreenUpdate.owner(this, side, scr.owner)); checkLaserUserRights(scr); setChanged(); } - + public void setRotation(BlockSide side, Rotation rot) { Screen scr = getScreen(side); if (scr == null) { Log.error("Trying to change rotation of invalid screen on side %s", side.toString()); return; } - + if (level.isClientSide) { boolean oldWasVertical = scr.rotation.isVertical; scr.rotation = rot; - + WebDisplays.PROXY.screenUpdateRotationInGui(new Vector3i(getBlockPos()), side, rot); - + if (scr.browser != null && oldWasVertical != rot.isVertical) { scr.browser.close(true); scr.browser = null; //Will be re-created by renderer @@ -1212,7 +1196,7 @@ public class TileEntityScreen extends BlockEntity { setChanged(); } } - + // public void evalJS(BlockSide side, String code) { // Screen scr = getScreen(side); // if (scr == null) { @@ -1226,16 +1210,16 @@ public class TileEntityScreen extends BlockEntity { // } //// else WDNetworkRegistry.INSTANCE.send(PacketDistributor.NEAR.with(() -> point(level, getBlockPos())), S2CMessageScreenUpdate.js(this, side, code)); // } - + public void setAutoVolume(BlockSide side, boolean av) { Screen scr = getScreen(side); if (scr == null) { Log.error("Trying to toggle auto-volume on invalid screen (side %s)", side.toString()); return; } - + scr.autoVolume = av; - + if (level.isClientSide) WebDisplays.PROXY.screenUpdateAutoVolumeInGui(new Vector3i(getBlockPos()), side, av); else { @@ -1253,4 +1237,49 @@ public class TileEntityScreen extends BlockEntity { // return oldState.getValue(BlockScreen.hasTE) != newState.getValue(BlockScreen.hasTE); // } + + @Override + public void load(CompoundTag tag) { + load(false, tag); + } + + public void load(boolean syncing, CompoundTag tag) { + super.load(tag); + + ListTag list = tag.getList("WDScreens", Tag.TAG_COMPOUND); + if (list.isEmpty()) + return; + + screens.clear(); + for (int i = 0; i < list.size(); i++) { + CompoundTag tg = list.getCompound(i); + screens.add(Screen.deserialize(tg)); + if (WebDisplays.isSSR()) { + if (!syncing) { + screens.get(i).createBrowser(true, false); + } else { + screens.get(i).createBrowser(false, false); + ((RemoteBrowser) screens.get(i).browser).setUUID(tg.getUUID("uuid")); + } + } + } + } + + @Override + protected void saveAdditional(CompoundTag tag) { + saveAdditional(false, tag); + } + + protected void saveAdditional(boolean syncing, CompoundTag tag) { + super.saveAdditional(tag); + + ListTag list = new ListTag(); + for (Screen scr : screens) { + CompoundTag tg = scr.serialize(); + if (syncing) tg.putUUID("uuid", ((IRemoteBrowser) scr.browser).getUUID()); + list.add(tg); + } + + tag.put("WDScreens", list); + } } diff --git a/src/main/java/net/montoyo/wd/net/client_bound/S2CMessageEnableSSR.java b/src/main/java/net/montoyo/wd/net/client_bound/S2CMessageEnableSSR.java new file mode 100644 index 0000000..f4b71ba --- /dev/null +++ b/src/main/java/net/montoyo/wd/net/client_bound/S2CMessageEnableSSR.java @@ -0,0 +1,14 @@ +package net.montoyo.wd.net.client_bound; + +import net.minecraftforge.network.NetworkEvent; +import net.montoyo.wd.WebDisplays; +import net.montoyo.wd.net.Packet; + +public class S2CMessageEnableSSR extends Packet { + @Override + public void handle(NetworkEvent.Context ctx) { + if (checkClient(ctx)) { + WebDisplays.markSSR(); + } + } +} diff --git a/src/main/java/net/montoyo/wd/remote/BrowserGen.java b/src/main/java/net/montoyo/wd/remote/BrowserGen.java new file mode 100644 index 0000000..dc14056 --- /dev/null +++ b/src/main/java/net/montoyo/wd/remote/BrowserGen.java @@ -0,0 +1,69 @@ +package net.montoyo.wd.remote; + +import com.cinemamod.mcef.MCEF; +import com.cinemamod.mcef.MCEFBrowser; +import net.minecraftforge.fml.loading.FMLEnvironment; +import net.montoyo.wd.WebDisplays; +import net.montoyo.wd.remote.client.RemoteBrowser; +import net.montoyo.wd.remote.server.BlankBrowser; +import net.montoyo.wd.remote.server.ServerBrowser; +import org.cef.browser.CefBrowser; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.UUID; + +public class BrowserGen { + private static ArrayList browsers = new ArrayList<>(); + private static HashMap clientBrowsers = new HashMap<>(); + private static HashMap serverBrowsers = new HashMap<>(); + + public static IWDBrowser createBrowser(boolean server, String url, boolean transparent) { + CefBrowser browser; + if (!server) { + if (WebDisplays.isSSR()) { + browser = new RemoteBrowser() { + @Override + protected void finalize() throws Throwable { + synchronized (browsers) { + browsers.remove(this); + clientBrowsers.remove(getUUID()); + } + super.finalize(); + } + }; + } else + browser = new WDMCEFBrowser(MCEF.getClient(), url, transparent) { + @Override + public void close() { + synchronized (browsers) { + browsers.remove(this); + super.close(); + } + } + }; + } else { + if (WebDisplays.isSSR()) { + browser = new ServerBrowser(MCEF.getClient(), url, transparent) { + @Override + public void close() { + browsers.remove(this); + synchronized (browsers) { + serverBrowsers.remove(getUUID()); + } + super.close(); + } + }; + serverBrowsers.put(((IRemoteBrowser) browser).getUUID(), (IRemoteBrowser) browser); + } else return null; + } + browser.setCloseAllowed(); + browser.createImmediately(); + browsers.add((IWDBrowser) browser); + return (IWDBrowser) browser; + } + + public static void onUUIDAcquired(RemoteBrowser browser) { + clientBrowsers.put(((IRemoteBrowser) browser).getUUID(), browser); + } +} diff --git a/src/main/java/net/montoyo/wd/remote/IRemoteBrowser.java b/src/main/java/net/montoyo/wd/remote/IRemoteBrowser.java new file mode 100644 index 0000000..0ca88f5 --- /dev/null +++ b/src/main/java/net/montoyo/wd/remote/IRemoteBrowser.java @@ -0,0 +1,7 @@ +package net.montoyo.wd.remote; + +import java.util.UUID; + +public interface IRemoteBrowser { + UUID getUUID(); +} diff --git a/src/main/java/net/montoyo/wd/remote/IWDBrowser.java b/src/main/java/net/montoyo/wd/remote/IWDBrowser.java new file mode 100644 index 0000000..23ec226 --- /dev/null +++ b/src/main/java/net/montoyo/wd/remote/IWDBrowser.java @@ -0,0 +1,24 @@ +package net.montoyo.wd.remote; + +import com.cinemamod.mcef.MCEFRenderer; +import com.cinemamod.mcef.listeners.MCEFCursorChangeListener; + +public interface IWDBrowser { + void close(boolean b); + + void loadURL(String url); + + void resize(int y, int x); + + void setCursorChangeListener(MCEFCursorChangeListener mcefCursorChangeListener); + + MCEFRenderer getRenderer(); + + void setFocus(boolean b); + + void sendMouseRelease(int x, int y, int button); + + void sendMouseMove(int x, int y); + + void sendMousePress(int x, int y, int button); +} diff --git a/src/main/java/net/montoyo/wd/remote/VirtualBrowser.java b/src/main/java/net/montoyo/wd/remote/VirtualBrowser.java new file mode 100644 index 0000000..6be03d4 --- /dev/null +++ b/src/main/java/net/montoyo/wd/remote/VirtualBrowser.java @@ -0,0 +1,275 @@ +package net.montoyo.wd.remote; + +import com.cinemamod.mcef.MCEF; +import com.cinemamod.mcef.MCEFRenderer; +import com.cinemamod.mcef.listeners.MCEFCursorChangeListener; +import org.cef.CefClient; +import org.cef.browser.CefBrowser; +import org.cef.browser.CefFrame; +import org.cef.callback.CefPdfPrintCallback; +import org.cef.callback.CefRunFileDialogCallback; +import org.cef.callback.CefStringVisitor; +import org.cef.handler.CefDialogHandler; +import org.cef.handler.CefRenderHandler; +import org.cef.handler.CefWindowHandler; +import org.cef.misc.CefPdfPrintSettings; +import org.cef.network.CefRequest; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.util.Vector; +import java.util.concurrent.CompletableFuture; + +public abstract class VirtualBrowser implements CefBrowser, CefRenderHandler, CefWindowHandler, IWDBrowser { + String url; + + @Override + public void createImmediately() { + + } + + @Override + public CefClient getClient() { + return MCEF.getClient().getHandle(); + } + + @Override + public CefRenderHandler getRenderHandler() { + return this; + } + + @Override + public CefWindowHandler getWindowHandler() { + return this; + } + + @Override + public boolean canGoBack() { + return false; + } + + @Override + public void goBack() { + + } + + @Override + public boolean canGoForward() { + return false; + } + + @Override + public void goForward() { + + } + + @Override + public boolean isLoading() { + return false; + } + + @Override + public void reload() { + + } + + @Override + public void reloadIgnoreCache() { + + } + + @Override + public void stopLoad() { + + } + + @Override + public int getIdentifier() { + return 0; + } + + @Override + public CefFrame getMainFrame() { + return null; + } + + @Override + public CefFrame getFocusedFrame() { + return null; + } + + @Override + public CefFrame getFrame(long l) { + return null; + } + + @Override + public CefFrame getFrame(String s) { + return null; + } + + @Override + public Vector getFrameIdentifiers() { + return null; + } + + @Override + public Vector getFrameNames() { + return null; + } + + @Override + public int getFrameCount() { + return 0; + } + + @Override + public boolean isPopup() { + return false; + } + + @Override + public boolean hasDocument() { + return false; + } + + @Override + public void viewSource() { + + } + + @Override + public void getSource(CefStringVisitor cefStringVisitor) { + + } + + @Override + public void getText(CefStringVisitor cefStringVisitor) { + + } + + @Override + public void loadRequest(CefRequest cefRequest) { + + } + + @Override + public void loadURL(String s) { + this.url = url; + } + + @Override + public void executeJavaScript(String s, String s1, int i) { + + } + + @Override + public String getURL() { + return url; + } + + @Override + public void close(boolean b) { + + } + + @Override + public void setCloseAllowed() { + + } + + @Override + public boolean doClose() { + return true; + } + + @Override + public void onBeforeClose() { + + } + + @Override + public void setFocus(boolean b) { + + } + + @Override + public void setWindowVisibility(boolean b) { + + } + + @Override + public double getZoomLevel() { + return 0; + } + + @Override + public void setZoomLevel(double v) { + + } + + @Override + public void runFileDialog(CefDialogHandler.FileDialogMode fileDialogMode, String s, String s1, Vector vector, int i, CefRunFileDialogCallback cefRunFileDialogCallback) { + + } + + @Override + public void startDownload(String s) { + + } + + @Override + public void print() { + + } + + @Override + public void printToPDF(String s, CefPdfPrintSettings cefPdfPrintSettings, CefPdfPrintCallback cefPdfPrintCallback) { + + } + + @Override + public void find(String s, boolean b, boolean b1, boolean b2) { + + } + + @Override + public void stopFinding(boolean b) { + + } + + @Override + public CefBrowser getDevTools() { + return this; + } + + @Override + public CefBrowser getDevTools(Point point) { + return this; + } + + @Override + public void replaceMisspelling(String s) { + + } + + @Override + public CompletableFuture createScreenshot(boolean b) { + return null; + } + + @Override + public void resize(int y, int x) { + + } + + @Override + public void setCursorChangeListener(MCEFCursorChangeListener mcefCursorChangeListener) { + + } + + @Override + public MCEFRenderer getRenderer() { + return null; + } +} diff --git a/src/main/java/net/montoyo/wd/remote/WDMCEFBrowser.java b/src/main/java/net/montoyo/wd/remote/WDMCEFBrowser.java new file mode 100644 index 0000000..f4820dc --- /dev/null +++ b/src/main/java/net/montoyo/wd/remote/WDMCEFBrowser.java @@ -0,0 +1,10 @@ +package net.montoyo.wd.remote; + +import com.cinemamod.mcef.MCEFBrowser; +import com.cinemamod.mcef.MCEFClient; + +public class WDMCEFBrowser extends MCEFBrowser implements IWDBrowser { + public WDMCEFBrowser(MCEFClient client, String url, boolean transparent) { + super(client, url, transparent); + } +} diff --git a/src/main/java/net/montoyo/wd/remote/client/ImageReader.java b/src/main/java/net/montoyo/wd/remote/client/ImageReader.java new file mode 100644 index 0000000..0bf26b8 --- /dev/null +++ b/src/main/java/net/montoyo/wd/remote/client/ImageReader.java @@ -0,0 +1,53 @@ +package net.montoyo.wd.remote.client; + +import net.minecraft.network.FriendlyByteBuf; + +import java.nio.ByteBuffer; + +public class ImageReader { + ByteBuffer target; + + public ImageReader(ByteBuffer target) { + this.target = target; + } + + public void read(ByteBuffer buf, int bWidth) { + int lim = buf.limit(); + + int count = buf.getInt(); + for (int i = 0; i < count; i++) { + int x = buf.getInt(); + int y = buf.getInt(); + int width = buf.getInt(); + int height = buf.getInt(); + + for (int y1 = 0; y1 < height; y1++) { + buf.limit(buf.position() + width * 4); + target.position( + x * 4 + (y + y1) * 4 * bWidth + ); + target.put(buf); + buf.limit(lim); + } + } + } + + public void read(FriendlyByteBuf buf, int bWidth) { + int count = buf.readInt(); + for (int i = 0; i < count; i++) { + int x = buf.readInt(); + int y = buf.readInt(); + int width = buf.readInt(); + int height = buf.readInt(); + + for (int y1 = 0; y1 < height; y1++) { + ByteBuffer buf1 = buf.internalNioBuffer(buf.readerIndex(), width * 4); + target.position( + x * 4 + (y + y1) * 4 * bWidth + ); + target.put(buf1); + buf.skipBytes(width * 4); + } + } + } +} diff --git a/src/main/java/net/montoyo/wd/remote/client/RemoteBrowser.java b/src/main/java/net/montoyo/wd/remote/client/RemoteBrowser.java new file mode 100644 index 0000000..923ff13 --- /dev/null +++ b/src/main/java/net/montoyo/wd/remote/client/RemoteBrowser.java @@ -0,0 +1,137 @@ +package net.montoyo.wd.remote.client; + +import com.cinemamod.mcef.MCEFRenderer; +import com.mojang.blaze3d.platform.GlStateManager; +import com.mojang.blaze3d.systems.RenderSystem; +import net.minecraft.client.Minecraft; +import net.montoyo.wd.remote.IRemoteBrowser; +import net.montoyo.wd.remote.IWDBrowser; +import net.montoyo.wd.remote.VirtualBrowser; +import org.cef.browser.CefBrowser; +import org.cef.callback.CefDragData; +import org.cef.handler.CefScreenInfo; + +import java.awt.*; +import java.lang.reflect.Constructor; +import java.nio.ByteBuffer; +import java.util.UUID; + +import static org.lwjgl.opengl.GL11C.*; + +public class RemoteBrowser extends VirtualBrowser implements IRemoteBrowser, IWDBrowser { + UUID myId; + + MCEFRenderer renderer; + + public RemoteBrowser() { + try { + Constructor ctor = MCEFRenderer.class.getDeclaredConstructor(boolean.class); + ctor.setAccessible(true); + renderer = ctor.newInstance(false); + Minecraft.getInstance().submit(renderer::initialize); + } catch (Throwable err) { + throw new RuntimeException(err); + } + } + + @Override + public MCEFRenderer getRenderer() { + return renderer; + } + + @Override + public UUID getUUID() { + return myId; + } + + public void setUUID(UUID uuid) { + myId = uuid; + } + + @Override + public Rectangle getViewRect(CefBrowser cefBrowser) { + return null; + } + + @Override + public boolean getScreenInfo(CefBrowser cefBrowser, CefScreenInfo cefScreenInfo) { + return false; + } + + @Override + public Point getScreenPoint(CefBrowser cefBrowser, Point point) { + return null; + } + + @Override + public void onPopupShow(CefBrowser cefBrowser, boolean b) { + + } + + @Override + public void onPopupSize(CefBrowser cefBrowser, Rectangle rectangle) { + + } + + int lastWidth, lastHeight; + + @Override + public void onPaint(CefBrowser browser, boolean popup, Rectangle[] dirtyRects, ByteBuffer buffer, int width, int height) { + if (width != lastWidth || height != lastHeight) { + renderer.onPaint(buffer, width, height); + lastWidth = width; + lastHeight = height; + } else { + if (renderer.getTextureID() == 0) return; + + RenderSystem.bindTexture(renderer.getTextureID()); + if (renderer.isTransparent()) RenderSystem.enableBlend(); + + RenderSystem.pixelStore(GL_UNPACK_ROW_LENGTH, width); + for (Rectangle dirtyRect : dirtyRects) { + GlStateManager._pixelStore(GL_UNPACK_SKIP_PIXELS, dirtyRect.x); + GlStateManager._pixelStore(GL_UNPACK_SKIP_ROWS, dirtyRect.y); + renderer.onPaint(buffer, dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height); + } + } + } + + @Override + public boolean onCursorChange(CefBrowser cefBrowser, int i) { + return false; + } + + @Override + public boolean startDragging(CefBrowser cefBrowser, CefDragData cefDragData, int i, int i1, int i2) { + return false; + } + + @Override + public void updateDragCursor(CefBrowser cefBrowser, int i) { + + } + + @Override + public Rectangle getRect(CefBrowser cefBrowser) { + return null; + } + + @Override + public void onMouseEvent(CefBrowser cefBrowser, int i, int i1, int i2, int i3, int i4) { + + } + + @Override + public void sendMouseRelease(int x, int y, int button) { + } + + @Override + public void sendMouseMove(int x, int y) { + + } + + @Override + public void sendMousePress(int x, int y, int button) { + + } +} diff --git a/src/main/java/net/montoyo/wd/remote/server/BlankBrowser.java b/src/main/java/net/montoyo/wd/remote/server/BlankBrowser.java new file mode 100644 index 0000000..4251ae7 --- /dev/null +++ b/src/main/java/net/montoyo/wd/remote/server/BlankBrowser.java @@ -0,0 +1,78 @@ +package net.montoyo.wd.remote.server; + +import net.montoyo.wd.remote.VirtualBrowser; +import org.cef.browser.CefBrowser; +import org.cef.callback.CefDragData; +import org.cef.handler.CefScreenInfo; + +import java.awt.*; +import java.nio.ByteBuffer; + +public class BlankBrowser extends VirtualBrowser { + @Override + public Rectangle getViewRect(CefBrowser cefBrowser) { + return null; + } + + @Override + public boolean getScreenInfo(CefBrowser cefBrowser, CefScreenInfo cefScreenInfo) { + return false; + } + + @Override + public Point getScreenPoint(CefBrowser cefBrowser, Point point) { + return null; + } + + @Override + public void onPopupShow(CefBrowser cefBrowser, boolean b) { + + } + + @Override + public void onPopupSize(CefBrowser cefBrowser, Rectangle rectangle) { + + } + + @Override + public void onPaint(CefBrowser cefBrowser, boolean b, Rectangle[] rectangles, ByteBuffer byteBuffer, int i, int i1) { + + } + + @Override + public boolean onCursorChange(CefBrowser cefBrowser, int i) { + return false; + } + + @Override + public boolean startDragging(CefBrowser cefBrowser, CefDragData cefDragData, int i, int i1, int i2) { + return false; + } + + @Override + public void updateDragCursor(CefBrowser cefBrowser, int i) { + + } + + @Override + public Rectangle getRect(CefBrowser cefBrowser) { + return null; + } + + @Override + public void onMouseEvent(CefBrowser cefBrowser, int i, int i1, int i2, int i3, int i4) { + + } + + @Override + public void sendMouseRelease(int x, int y, int button) { + } + + @Override + public void sendMouseMove(int x, int y) { + } + + @Override + public void sendMousePress(int x, int y, int button) { + } +} diff --git a/src/main/java/net/montoyo/wd/remote/server/ServerBrowser.java b/src/main/java/net/montoyo/wd/remote/server/ServerBrowser.java new file mode 100644 index 0000000..223b6b5 --- /dev/null +++ b/src/main/java/net/montoyo/wd/remote/server/ServerBrowser.java @@ -0,0 +1,61 @@ +package net.montoyo.wd.remote.server; + +import com.cinemamod.mcef.MCEFHeadlessBrowser; +import com.cinemamod.mcef.MCEFClient; +import com.cinemamod.mcef.MCEFRenderer; +import com.mojang.blaze3d.platform.GlStateManager; +import com.mojang.blaze3d.systems.RenderSystem; +import net.montoyo.wd.remote.IRemoteBrowser; +import net.montoyo.wd.remote.IWDBrowser; +import org.cef.browser.CefBrowser; + +import java.awt.*; +import java.nio.ByteBuffer; +import java.util.UUID; + +public class ServerBrowser extends MCEFHeadlessBrowser implements IRemoteBrowser, IWDBrowser { + private int lastWidth = 0; + private int lastHeight = 0; + + UUID myId; + + ServerRenderer renderer = new ServerRenderer(); + + public ServerBrowser(MCEFClient client, String url, boolean transparent) { + super(client, url, transparent); + this.myId = UUID.randomUUID(); + } + + @Override + public UUID getUUID() { + return myId; + } + + ByteBuffer graphics; + + void store(ByteBuffer srcBuffer, ByteBuffer dstBuffer, Rectangle dirty, int width, int height) { + for (int y = dirty.y; y < dirty.height + dirty.y; y++) { + dstBuffer.position((y * width + dirty.x) * 4); + srcBuffer.position((y * width + dirty.x) * 4); + srcBuffer.limit(dirty.width * 4 + (y * width + dirty.x) * 4); + dstBuffer.put(srcBuffer); + srcBuffer.position(0).limit(srcBuffer.capacity()); + } + dstBuffer.position(0).limit(dstBuffer.capacity()); + } + + @Override + public void onPaint(CefBrowser browser, boolean popup, Rectangle[] dirtyRects, ByteBuffer buffer, int width, int height) { + if ((width != this.lastWidth && height != this.lastHeight)) { + renderer.size(width, height); + + renderer.put(buffer, new Rectangle[]{ + new Rectangle(0, 0, width, height) + }, width); + this.lastWidth = width; + this.lastHeight = height; + } else { + renderer.put(buffer, dirtyRects, width); + } + } +} diff --git a/src/main/java/net/montoyo/wd/remote/server/ServerRenderer.java b/src/main/java/net/montoyo/wd/remote/server/ServerRenderer.java new file mode 100644 index 0000000..2713595 --- /dev/null +++ b/src/main/java/net/montoyo/wd/remote/server/ServerRenderer.java @@ -0,0 +1,57 @@ +package net.montoyo.wd.remote.server; + +import java.awt.*; +import java.nio.ByteBuffer; + +public class ServerRenderer { + ByteBuffer contentChange; + + public ServerRenderer() { + } + + public void size(int width, int height) { + contentChange = ByteBuffer.allocate(width * height * 4); + } + + protected void checkGrow(int cap) { + if (contentChange.position() + cap > contentChange.capacity()) { + ByteBuffer copy = ByteBuffer.allocate((int) (contentChange.capacity() * 1.5)); + copy.position(0); + contentChange.flip(); + copy.put(contentChange); + contentChange = copy; + } + } + + public void put(ByteBuffer buffer, Rectangle[] dirtyRects, int width) { + contentChange.position(0); + contentChange.limit(contentChange.capacity()); + checkGrow(4); + contentChange.putInt(dirtyRects.length); + for (Rectangle dirtyRect : dirtyRects) { + checkGrow(4 * 4); + contentChange.putInt(dirtyRect.x); + contentChange.putInt(dirtyRect.y); + contentChange.putInt(dirtyRect.width); + contentChange.putInt(dirtyRect.height); + + for (int y = dirtyRect.y; y < dirtyRect.height + dirtyRect.y; y++) { + checkGrow(dirtyRect.width * 8); + buffer.position( + dirtyRect.x * 4 + + (y * width * 4) + ).limit( + (dirtyRect.x + dirtyRect.width) * 4 + + (y * width * 4) + ); + contentChange.put(buffer); + buffer.position(0).limit(buffer.capacity()); + } + } + contentChange.flip(); + } + + public ByteBuffer get() { + return contentChange; + } +} diff --git a/src/test/java/NetworkedBrowserTest.java b/src/test/java/NetworkedBrowserTest.java new file mode 100644 index 0000000..0f30176 --- /dev/null +++ b/src/test/java/NetworkedBrowserTest.java @@ -0,0 +1,43 @@ +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufHolder; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.EmptyByteBuf; +import net.minecraft.network.FriendlyByteBuf; +import net.montoyo.wd.remote.client.ImageReader; +import net.montoyo.wd.remote.server.ServerRenderer; + +import java.awt.*; +import java.nio.ByteBuffer; +import java.util.Random; + +public class NetworkedBrowserTest { + public static void main(String[] args) { + int w = 45; + ByteBuffer src = ByteBuffer.allocate((w * w) * 4); + Random rng = new Random(); + byte[] bbyte = new byte[1]; + for (int i = 0; i < (w * w) * 4; i++) { + rng.nextBytes(bbyte); + src.put(bbyte[0]); + } + + ServerRenderer renderer = new ServerRenderer(); + renderer.size(w, w); + renderer.put(src, new Rectangle[]{ + new Rectangle(0, 0, w, w) + }, w); + + ByteBuffer buffer = renderer.get(); + + ByteBuffer dst = ByteBuffer.allocate((w * w) * 4); + ImageReader reader = new ImageReader(dst); + reader.read(buffer, w); + + dst.position(0).limit(dst.capacity()); + src.position(0).limit(src.capacity()); + for (int i = 0; i < src.capacity(); i++) { + if (src.get(i) != dst.get(i)) System.err.println("Mismatch at " + i); +// else System.out.println(" Correct at " + i); + } + } +}