commit 6e915394129935844c82295a8f89ead9f66b9019 Author: oxmc <67136658+oxmc@users.noreply.github.com> Date: Fri Mar 28 11:36:56 2025 -0400 Upload base diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..23d710b --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +# eclipse +bin +*.launch +.settings +.metadata +.classpath +.project + +# idea +out +*.ipr +*.iws +*.iml +.idea + +# gradle +build +.gradle + +# other +eclipse +run +/_OLD diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b2669d7 --- /dev/null +++ b/LICENSE @@ -0,0 +1 @@ +This mod and its source code is now in public domain. Feel free to do whatever you want with it; make forks, distribute it... whatever I would appreciate it, of course, if you credited me 😊 Thank you! diff --git a/README.md b/README.md new file mode 100644 index 0000000..6438132 --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# WebDisplays for Minecraft 1.12.2 +This is the WebDisplays mod for Minecraft 1.12.2. I don't have time to maintain it anymore, so I changed the license recently and anybody is welcome to create forks and/or re-distribute it. For more info see [LICENSE](LICENSE). + +### Wiki +* The Wiki that details all blocks/items can be found on my website https://montoyo.net/wdwiki/ + +### Things before release +* Release ready. Targeted release date: 17/02/2018. + +### Delayed things +* Plugin API +* The Shop +* CC Interface, if CC gets updated... +* Center camera to screen when using keyboard +* minePad management: check GuiContainer.draggedStack for minePad +* In-game command to add/remove blacklisted domains +* Config: RPMP (Real pixels per Minecraft pixels) +* Disable miniserv in solo diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..c21c3eb --- /dev/null +++ b/build.gradle @@ -0,0 +1,80 @@ +buildscript { + repositories { + jcenter() + maven { url = "http://files.minecraftforge.net/maven" } + } + dependencies { + classpath 'net.minecraftforge.gradle:ForgeGradle:2.3-SNAPSHOT' + } +} +apply plugin: 'net.minecraftforge.gradle.forge' +//Only edit below this line, the above code adds and enables the necessary things for Forge to be setup. + + +version = "1.12.2-1.4" +group = "net.montoyo.wd" // http://maven.apache.org/guides/mini/guide-naming-conventions.html +archivesBaseName = "webdisplays" + +sourceCompatibility = targetCompatibility = '1.8' // Need this here so eclipse task generates correctly. +compileJava { + sourceCompatibility = targetCompatibility = '1.8' +} + +minecraft { + version = "1.12.2-14.23.5.2768" + runDir = "run" + + // the mappings can be changed at any time, and must be in the following format. + // snapshot_YYYYMMDD snapshot are built nightly. + // stable_# stables are built at the discretion of the MCP team. + // Use non-default mappings at your own risk. they may not always work. + // simply re-run your setup task after changing the mappings to update your workspace. + mappings = "snapshot_20171003" + makeObfSourceJar = false // an Srg named sources jar is made by default. uncomment this to disable. +} + +repositories { + mavenCentral() + maven { + name = "forge" + url = "http://files.minecraftforge.net/maven" + } + maven { + name = "ComputerCraft and CC:Tweaked" + url = 'https://maven.squiddev.cc/' + } + maven { + name = "OpenComputers" + url = "http://maven.cil.li/" + } +} + +dependencies { + deobfCompile "org.squiddev:cc-tweaked-1.12.2:1.89.2" + compile "li.cil.oc:OpenComputers:MC1.12.2-1.7.5.+:api" +} + +jar { + exclude 'org/**' + exclude 'net/montoyo/mcef/**' + exclude 'assets/mcef/**' +} + +processResources { + // this will ensure that this task is redone when the versions change. + inputs.property "version", project.version + inputs.property "mcversion", project.minecraft.version + + // replace stuff in mcmod.info, nothing else + from(sourceSets.main.resources.srcDirs) { + include 'mcmod.info' + + // replace version and mcversion + expand 'version':project.version, 'mcversion':project.minecraft.version + } + + // copy everything else except the mcmod.info + from(sourceSets.main.resources.srcDirs) { + exclude 'mcmod.info' + } +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..e9b9fd5 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,3 @@ +# Sets default memory used for gradle commands. Can be overridden by user or command line properties. +# This is required to provide enough memory for the Minecraft decompilation process. +org.gradle.jvmargs=-Xmx3G diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..30d399d Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..e18cba7 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Sep 14 12:28:28 PDT 2015 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14-bin.zip diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..91a7e26 --- /dev/null +++ b/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..8a0b282 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/libs/mcef.jar b/libs/mcef.jar new file mode 100644 index 0000000..a303b59 Binary files /dev/null and b/libs/mcef.jar differ diff --git a/src/main/java/net/montoyo/wd/SharedProxy.java b/src/main/java/net/montoyo/wd/SharedProxy.java new file mode 100644 index 0000000..1200cc5 --- /dev/null +++ b/src/main/java/net/montoyo/wd/SharedProxy.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2019 BARBOTIN Nicolas + */ + +package net.montoyo.wd; + +import com.mojang.authlib.GameProfile; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.BlockPos; +import net.minecraft.world.World; +import net.minecraftforge.common.DimensionManager; +import net.minecraftforge.fml.server.FMLServerHandler; +import net.montoyo.wd.utilities.Log; +import net.montoyo.wd.core.HasAdvancement; +import net.montoyo.wd.core.JSServerRequest; +import net.montoyo.wd.data.GuiData; +import net.montoyo.wd.entity.TileEntityScreen; +import net.montoyo.wd.utilities.*; + +import javax.annotation.Nonnull; + +public class SharedProxy { + + public static final int CURRENT_DIMENSION = Integer.MAX_VALUE; + + public void preInit() { + } + + public void init() { + } + + public void postInit() { + } + + public World getWorld(int dim) { + if(dim == CURRENT_DIMENSION) + throw new RuntimeException("Current dimension not available server side..."); + + return DimensionManager.getWorld(dim); + } + + public void enqueue(Runnable r) { + FMLServerHandler.instance().getServer().addScheduledTask(r); + } + + public void displayGui(GuiData data) { + Log.error("Called SharedProxy.displayGui() on server side..."); + } + + public void trackScreen(TileEntityScreen tes, boolean track) { + } + + public void onAutocompleteResult(NameUUIDPair pairs[]) { + } + + public GameProfile[] getOnlineGameProfiles() { + return FMLServerHandler.instance().getServer().getOnlinePlayerProfiles(); + } + + public void screenUpdateResolutionInGui(Vector3i pos, BlockSide side, Vector2i res) { + } + + public void screenUpdateRotationInGui(Vector3i pos, BlockSide side, Rotation rot) { + } + + public void screenUpdateAutoVolumeInGui(Vector3i pos, BlockSide side, boolean av) { + } + + public void displaySetPadURLGui(String padURL) { + Log.error("Called SharedProxy.displaySetPadURLGui() on server side..."); + } + + public void openMinePadGui(int padId) { + Log.error("Called SharedProxy.openMinePadGui() on server side..."); + } + + public void handleJSResponseSuccess(int reqId, JSServerRequest type, byte[] data) { + Log.error("Called SharedProxy.handleJSResponseSuccess() on server side..."); + } + + public void handleJSResponseError(int reqId, JSServerRequest type, int errCode, String err) { + Log.error("Called SharedProxy.handleJSResponseError() on server side..."); + } + + @Nonnull + public HasAdvancement hasClientPlayerAdvancement(@Nonnull ResourceLocation rl) { + return HasAdvancement.DONT_KNOW; + } + + public MinecraftServer getServer() { + return FMLServerHandler.instance().getServer(); + } + + public void setMiniservClientPort(int port) { + } + + public void startMiniservClient() { + } + + public boolean isMiniservDisabled() { + return false; + } + + public void closeGui(BlockPos bp, BlockSide bs) { + } + + public void renderRecipes() { + } + + public boolean isShiftDown() { + return false; + } + +} diff --git a/src/main/java/net/montoyo/wd/WebDisplays.java b/src/main/java/net/montoyo/wd/WebDisplays.java new file mode 100644 index 0000000..646dd60 --- /dev/null +++ b/src/main/java/net/montoyo/wd/WebDisplays.java @@ -0,0 +1,512 @@ +/* + * Copyright (C) 2019 BARBOTIN Nicolas + */ + +package net.montoyo.wd; + +import com.google.gson.Gson; +import net.minecraft.advancements.Advancement; +import net.minecraft.advancements.CriteriaTriggers; +import net.minecraft.block.Block; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.init.SoundEvents; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.SoundCategory; +import net.minecraft.util.SoundEvent; +import net.minecraft.util.text.TextFormatting; +import net.minecraftforge.client.event.ClientChatEvent; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.common.capabilities.CapabilityManager; +import net.minecraftforge.common.config.Configuration; +import net.minecraftforge.common.config.Property; +import net.minecraftforge.event.AttachCapabilitiesEvent; +import net.minecraftforge.event.RegistryEvent; +import net.minecraftforge.event.ServerChatEvent; +import net.minecraftforge.event.entity.item.ItemTossEvent; +import net.minecraftforge.event.world.WorldEvent; +import net.minecraftforge.fml.common.Loader; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.common.SidedProxy; +import net.minecraftforge.fml.common.event.*; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.PlayerEvent; +import net.minecraftforge.fml.common.network.NetworkRegistry; +import net.minecraftforge.fml.common.network.simpleimpl.SimpleNetworkWrapper; +import net.minecraftforge.fml.common.registry.GameRegistry; +import net.montoyo.wd.block.BlockKeyboardRight; +import net.montoyo.wd.block.BlockPeripheral; +import net.montoyo.wd.block.BlockScreen; +import net.montoyo.wd.core.*; +import net.montoyo.wd.entity.TileEntityScreen; +import net.montoyo.wd.item.*; +import net.montoyo.wd.miniserv.server.Server; +import net.montoyo.wd.net.client.CMessageServerInfo; +import net.montoyo.wd.net.Messages; +import net.montoyo.wd.utilities.Log; +import net.montoyo.wd.utilities.Util; + +import java.io.*; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Arrays; +import java.util.UUID; + +@Mod(modid = "webdisplays", version = WebDisplays.MOD_VERSION, dependencies = "required-after:mcef@[1.0,3.0);after:opencomputers;after:computercraft;") +public class WebDisplays { + + public static final String MOD_VERSION = "1.4"; + + @Mod.Instance(owner = "webdisplays") + public static WebDisplays INSTANCE; + + @SidedProxy(serverSide = "net.montoyo.wd.SharedProxy", clientSide = "net.montoyo.wd.client.ClientProxy") + public static SharedProxy PROXY; + + public static SimpleNetworkWrapper NET_HANDLER; + public static WDCreativeTab CREATIVE_TAB; + 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(); + public static final ResourceLocation CAPABILITY = new ResourceLocation("webdisplays", "customdatacap"); + + //Blocks + public BlockScreen blockScreen; + public BlockPeripheral blockPeripheral; + public BlockKeyboardRight blockKbRight; + + //Items + public ItemScreenConfigurator itemScreenCfg; + public ItemOwnershipThief itemOwnerThief; + public ItemLinker itemLinker; + public ItemMinePad2 itemMinePad; + public ItemUpgrade itemUpgrade; + public ItemLaserPointer itemLaserPointer; + public ItemCraftComponent itemCraftComp; + public ItemMulti itemAdvIcon; + + //Sounds + public SoundEvent soundTyping; + public SoundEvent soundUpgradeAdd; + public SoundEvent soundUpgradeDel; + public SoundEvent soundScreenCfg; + public SoundEvent soundServer; + public SoundEvent soundIronic; + + //Criterions + public Criterion criterionPadBreak; + public Criterion criterionUpgradeScreen; + public Criterion criterionLinkPeripheral; + public Criterion criterionKeyboardCat; + + //Config + public static final double PAD_RATIO = 59.0 / 30.0; + public String homePage; + public double padResX; + public double padResY; + private int lastPadId = 0; + public boolean doHardRecipe; + private boolean hasOC; + private boolean hasCC; + private String[] blacklist; + public boolean disableOwnershipThief; + public double unloadDistance2; + public double loadDistance2; + public int maxResX; + public int maxResY; + public int maxScreenX; + public int maxScreenY; + public int miniservPort; + public long miniservQuota; + public boolean enableSoundDistance; + public float ytVolume; + public float avDist100; + public float avDist0; + + @Mod.EventHandler + public void onPreInit(FMLPreInitializationEvent ev) { + //Load config + Configuration cfg = new Configuration(ev.getSuggestedConfigurationFile()); + cfg.load(); + + //CAT: Main + Property blacklist = cfg.get("main", "blacklist", new String[0]); + Property padHeight = cfg.get("main", "padHeight", 480); + Property hardRecipe = cfg.get("main", "hardRecipes", true); + Property homePage = cfg.get("main", "homepage", "mod://webdisplays/main.html"); + Property disableOT = cfg.get("main", "disableOwnershipThief", false); + Property maxResX = cfg.get("main", "maxResolutionX", 1920); + Property maxResY = cfg.get("main", "maxResolutionY", 1080); + Property miniservPort = cfg.get("main", "miniservPort", 25566); + Property miniservQuota = cfg.get("main", "miniservQuota", 1024); //It's stored as a string anyway + Property maxScreenX = cfg.get("main", "maxScreenSizeX", 16); + Property maxScreenY = cfg.get("main", "maxScreenSizeY", 16); + + //CAT: Client options + Property loadDistance = cfg.get("client", "loadDistance", 30.0); + Property unloadDistance = cfg.get("client", "unloadDistance", 32.0); + + //CAT: Auto volume config (client-side) + Property enableAutoVol = cfg.get("clientAutoVolume", "enableAutoVolume", true); + Property ytVolume = cfg.get("clientAutoVolume", "ytVolume", 100.0); + Property dist100 = cfg.get("clientAutoVolume", "dist100", 10.0); + Property dist0 = cfg.get("clientAutoVolume", "dist0", 30.0); + + + //Comments & shit + blacklist.setComment("An array of domain names you don't want to load."); + padHeight.setComment("The minePad Y resolution in pixels. padWidth = padHeight * " + PAD_RATIO); + hardRecipe.setComment("If true, breaking the minePad is required to craft upgrades."); + homePage.setComment("The URL that will be loaded each time you create a screen"); + disableOT.setComment("If true, the ownership thief item will be disabled"); + loadDistance.setComment("All screens outside this range will be unloaded"); + unloadDistance.setComment("All unloaded screens inside this range will be loaded"); + maxResX.setComment("Maximum horizontal screen resolution, in pixels"); + maxResY.setComment("Maximum vertical screen resolution, in pixels"); + miniservPort.setComment("The port used by miniserv. 0 to disable."); + miniservPort.setMaxValue(Short.MAX_VALUE); + miniservQuota.setComment("The amount of data that can be uploaded to miniserv, in KiB (so 1024 = 1 MiO)"); + maxScreenX.setComment("Maximum screen width, in blocks. Resolution will be clamped by maxResolutionX."); + maxScreenY.setComment("Maximum screen height, in blocks. Resolution will be clamped by maxResolutionY."); + enableAutoVol.setComment("If true, the volume of YouTube videos will change depending on how far you are"); + ytVolume.setComment("Volume for YouTube videos. This will have no effect if enableSoundDistance is set to false"); + ytVolume.setMinValue(0.0); + ytVolume.setMaxValue(100.0); + dist100.setComment("Distance after which the sound starts dropping (in blocks)"); + dist100.setMinValue(0.0); + dist0.setComment("Distance after which you can't hear anything (in blocks)"); + dist0.setMinValue(0.0); + + if(unloadDistance.getDouble() < loadDistance.getDouble() + 2.0) + unloadDistance.set(loadDistance.getDouble() + 2.0); + + if(dist0.getDouble() < dist100.getDouble() + 0.1) + dist0.set(dist100.getDouble() + 0.1); + + cfg.save(); + + this.blacklist = blacklist.getStringList(); + doHardRecipe = hardRecipe.getBoolean(); + this.homePage = homePage.getString(); + disableOwnershipThief = disableOT.getBoolean(); + unloadDistance2 = unloadDistance.getDouble() * unloadDistance.getDouble(); + loadDistance2 = loadDistance.getDouble() * loadDistance.getDouble(); + this.maxResX = maxResX.getInt(); + this.maxResY = maxResY.getInt(); + this.miniservPort = miniservPort.getInt(); + this.miniservQuota = miniservQuota.getLong() * 1024L; + this.maxScreenX = maxScreenX.getInt(); + this.maxScreenY = maxScreenY.getInt(); + enableSoundDistance = enableAutoVol.getBoolean(); + this.ytVolume = (float) ytVolume.getDouble(); + avDist100 = (float) dist100.getDouble(); + avDist0 = (float) dist0.getDouble(); + + CREATIVE_TAB = new WDCreativeTab(); + + //Criterions + criterionPadBreak = new Criterion("pad_break"); + criterionUpgradeScreen = new Criterion("upgrade_screen"); + criterionLinkPeripheral = new Criterion("link_peripheral"); + criterionKeyboardCat = new Criterion("keyboard_cat"); + registerTrigger(criterionPadBreak, criterionUpgradeScreen, criterionLinkPeripheral, criterionKeyboardCat); + + //Read configuration + padResY = (double) padHeight.getInt(); + padResX = padResY * PAD_RATIO; + + //Init blocks + blockScreen = new BlockScreen(); + blockScreen.makeItemBlock(); + + blockPeripheral = new BlockPeripheral(); + blockPeripheral.makeItemBlock(); + + blockKbRight = new BlockKeyboardRight(); + + //Init items + itemScreenCfg = new ItemScreenConfigurator(); + itemOwnerThief = new ItemOwnershipThief(); + itemLinker = new ItemLinker(); + itemMinePad = new ItemMinePad2(); + itemUpgrade = new ItemUpgrade(); + itemLaserPointer = new ItemLaserPointer(); + itemCraftComp = new ItemCraftComponent(); + + itemAdvIcon = new ItemMulti(AdvancementIcon.class); + itemAdvIcon.setUnlocalizedName("webdisplays.advicon"); + itemAdvIcon.setRegistryName("advicon"); + + PROXY.preInit(); + MinecraftForge.EVENT_BUS.register(this); + } + + @Mod.EventHandler + public void onInit(FMLInitializationEvent ev) { + //Register tile entities + GameRegistry.registerTileEntity(TileEntityScreen.class, new ResourceLocation("webdisplays", "screen")); + for(DefaultPeripheral dp: DefaultPeripheral.values()) { + if(dp.getTEClass() != null) + GameRegistry.registerTileEntity(dp.getTEClass(), new ResourceLocation("webdisplays", dp.getName())); + } + + //Other things + CapabilityManager.INSTANCE.register(IWDDCapability.class, new WDDCapability.Storage(), new WDDCapability.Factory()); + PROXY.init(); + NET_HANDLER = NetworkRegistry.INSTANCE.newSimpleChannel("webdisplays"); + Messages.registerAll(NET_HANDLER); + } + + @Mod.EventHandler + public void onPostInit(FMLPostInitializationEvent ev) { + PROXY.postInit(); + hasOC = Loader.isModLoaded("opencomputers"); + hasCC = Loader.isModLoaded("computercraft"); + + if(hasCC) { + try { + //We have to do this because the "register" method might be stripped out if CC isn't loaded + CCPeripheralProvider.class.getMethod("register").invoke(null); + } catch(Throwable t) { + Log.error("ComputerCraft was found, but WebDisplays wasn't able to register its CC Interface Peripheral"); + t.printStackTrace(); + } + } + } + + @SubscribeEvent + public void onRegisterBlocks(RegistryEvent.Register ev) { + ev.getRegistry().registerAll(blockScreen, blockPeripheral, blockKbRight); + } + + @SubscribeEvent + public void onRegisterItems(RegistryEvent.Register ev) { + ev.getRegistry().registerAll(blockScreen.getItem(), blockPeripheral.getItem()); + ev.getRegistry().registerAll(itemScreenCfg, itemOwnerThief, itemLinker, itemMinePad, itemUpgrade, itemLaserPointer, itemCraftComp, itemAdvIcon); + } + + @SubscribeEvent + public void onRegisterSounds(RegistryEvent.Register ev) { + soundTyping = registerSound(ev, "keyboardType"); + soundUpgradeAdd = registerSound(ev, "upgradeAdd"); + soundUpgradeDel = registerSound(ev, "upgradeDel"); + soundScreenCfg = registerSound(ev, "screencfgOpen"); + soundServer = registerSound(ev, "server"); + soundIronic = registerSound(ev, "ironic"); + } + + @SubscribeEvent + public void onWorldLoad(WorldEvent.Load ev) { + if(ev.getWorld().isRemote || ev.getWorld().provider.getDimension() != 0) + return; + + File worldDir = ev.getWorld().getSaveHandler().getWorldDirectory(); + File f = new File(worldDir, "wd_next.txt"); + + if(f.exists()) { + try { + BufferedReader br = new BufferedReader(new FileReader(f)); + String idx = br.readLine(); + Util.silentClose(br); + + if(idx == null) + throw new RuntimeException("Seems like the file is empty (1)"); + + idx = idx.trim(); + if(idx.isEmpty()) + throw new RuntimeException("Seems like the file is empty (2)"); + + lastPadId = Integer.parseInt(idx); //This will throw NumberFormatException if it goes wrong + } catch(Throwable t) { + Log.warningEx("Could not read last minePad ID from %s. I'm afraid this might break all minePads.", t, f.getAbsolutePath()); + } + } + + if(miniservPort != 0) { + Server sv = Server.getInstance(); + sv.setPort(miniservPort); + sv.setDirectory(new File(worldDir, "wd_filehost")); + sv.start(); + } + } + + @SubscribeEvent + public void onWorldSave(WorldEvent.Save ev) { + if(ev.getWorld().isRemote || ev.getWorld().provider.getDimension() != 0) + return; + + File f = new File(ev.getWorld().getSaveHandler().getWorldDirectory(), "wd_next.txt"); + + try { + BufferedWriter bw = new BufferedWriter(new FileWriter(f)); + bw.write("" + lastPadId + "\n"); + Util.silentClose(bw); + } catch(Throwable t) { + Log.warningEx("Could not save last minePad ID (%d) to %s. I'm afraid this might break all minePads.", t, lastPadId, f.getAbsolutePath()); + } + } + + @SubscribeEvent + public void onToss(ItemTossEvent ev) { + if(!ev.getEntityItem().world.isRemote) { + ItemStack is = ev.getEntityItem().getItem(); + + if(is.getItem() == itemMinePad) { + NBTTagCompound tag = is.getTagCompound(); + + if(tag == null) { + tag = new NBTTagCompound(); + is.setTagCompound(tag); + } + + UUID thrower = ev.getPlayer().getGameProfile().getId(); + tag.setLong("ThrowerMSB", thrower.getMostSignificantBits()); + tag.setLong("ThrowerLSB", thrower.getLeastSignificantBits()); + tag.setDouble("ThrowHeight", ev.getPlayer().posY + ev.getPlayer().getEyeHeight()); + } + } + } + + @SubscribeEvent + public void onPlayerCraft(PlayerEvent.ItemCraftedEvent ev) { + if(doHardRecipe && ev.crafting.getItem() == itemCraftComp && ev.crafting.getMetadata() == CraftComponent.EXTENSION_CARD.ordinal()) { + if((ev.player instanceof EntityPlayerMP && !hasPlayerAdvancement((EntityPlayerMP) ev.player, ADV_PAD_BREAK)) || PROXY.hasClientPlayerAdvancement(ADV_PAD_BREAK) != HasAdvancement.YES) { + ev.crafting.setItemDamage(CraftComponent.BAD_EXTENSION_CARD.ordinal()); + + if(!ev.player.world.isRemote) + ev.player.world.playSound(null, ev.player.posX, ev.player.posY, ev.player.posZ, SoundEvents.ENTITY_ITEM_BREAK, SoundCategory.MASTER, 1.0f, 1.0f); + } + } + } + + @Mod.EventHandler + public void onServerStop(FMLServerStoppingEvent ev) { + Server.getInstance().stopServer(); + } + + @SubscribeEvent + public void onLogIn(PlayerEvent.PlayerLoggedInEvent ev) { + if(!ev.player.world.isRemote && ev.player instanceof EntityPlayerMP) { + WebDisplays.NET_HANDLER.sendTo(new CMessageServerInfo(miniservPort), (EntityPlayerMP) ev.player); + IWDDCapability cap = ev.player.getCapability(WDDCapability.INSTANCE, null); + + if(cap == null) + Log.warning("Player %s (%s) has null IWDDCapability!", ev.player.getName(), ev.player.getGameProfile().getId().toString()); + else if(cap.isFirstRun()) { + Util.toast(ev.player, TextFormatting.LIGHT_PURPLE, "welcome1"); + Util.toast(ev.player, TextFormatting.LIGHT_PURPLE, "welcome2"); + Util.toast(ev.player, TextFormatting.LIGHT_PURPLE, "welcome3"); + + cap.clearFirstRun(); + } + } + } + + @SubscribeEvent + public void onLogOut(PlayerEvent.PlayerLoggedOutEvent ev) { + if(!ev.player.world.isRemote) + Server.getInstance().getClientManager().revokeClientKey(ev.player.getGameProfile().getId()); + } + + @SubscribeEvent + public void attachEntityCaps(AttachCapabilitiesEvent ev) { + if(ev.getObject() instanceof EntityPlayer) + ev.addCapability(CAPABILITY, new WDDCapability.Provider()); + } + + @SubscribeEvent + public void onPlayerClone(net.minecraftforge.event.entity.player.PlayerEvent.Clone ev) { + IWDDCapability src = ev.getOriginal().getCapability(WDDCapability.INSTANCE, null); + IWDDCapability dst = ev.getEntityPlayer().getCapability(WDDCapability.INSTANCE, null); + + if(src == null) { + Log.error("src is null"); + return; + } + + if(dst == null) { + Log.error("dst is null"); + return; + } + + src.cloneTo(dst); + } + + @SubscribeEvent + public void onServerChat(ServerChatEvent ev) { + String msg = ev.getMessage().trim().replaceAll("\\s+", " ").toLowerCase(); + StringBuilder sb = new StringBuilder(msg.length()); + for(int i = 0; i < msg.length(); i++) { + char chr = msg.charAt(i); + + 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")) { + EntityPlayer ply = ev.getPlayer(); + ply.world.playSound(null, ply.posX, ply.posY, ply.posZ, soundIronic, SoundCategory.PLAYERS, 1.0f, 1.0f); + } + } + + @SubscribeEvent + public void onClientChat(ClientChatEvent ev) { + if(ev.getMessage().equals("!WD render recipes")) + PROXY.renderRecipes(); + } + + private boolean hasPlayerAdvancement(EntityPlayerMP ply, ResourceLocation rl) { + MinecraftServer server = PROXY.getServer(); + if(server == null) + return false; + + Advancement adv = server.getAdvancementManager().getAdvancement(rl); + return adv != null && ply.getAdvancements().getProgress(adv).isDone(); + } + + public static int getNextAvailablePadID() { + return INSTANCE.lastPadId++; + } + + private static SoundEvent registerSound(RegistryEvent.Register ev, String resName) { + ResourceLocation resLoc = new ResourceLocation("webdisplays", resName); + SoundEvent ret = new SoundEvent(resLoc); + ret.setRegistryName(resLoc); + + ev.getRegistry().register(ret); + return ret; + } + + private static void registerTrigger(Criterion ... criteria) { + for(Criterion c: criteria) + CriteriaTriggers.register(c); + } + + public static boolean isOpenComputersAvailable() { + return INSTANCE.hasOC; + } + + public static boolean isComputerCraftAvailable() { + return INSTANCE.hasCC; + } + + public static boolean isSiteBlacklisted(String url) { + try { + URL url2 = new URL(Util.addProtocol(url)); + return Arrays.stream(INSTANCE.blacklist).anyMatch(str -> str.equalsIgnoreCase(url2.getHost())); + } catch(MalformedURLException ex) { + return false; + } + } + + public static String applyBlacklist(String url) { + return isSiteBlacklisted(url) ? BLACKLIST_URL : url; + } + +} + diff --git a/src/main/java/net/montoyo/wd/block/BlockKeyboardRight.java b/src/main/java/net/montoyo/wd/block/BlockKeyboardRight.java new file mode 100644 index 0000000..3f1a9f3 --- /dev/null +++ b/src/main/java/net/montoyo/wd/block/BlockKeyboardRight.java @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2018 BARBOTIN Nicolas + */ + +package net.montoyo.wd.block; + +import net.minecraft.block.Block; +import net.minecraft.block.material.EnumPushReaction; +import net.minecraft.block.material.Material; +import net.minecraft.block.properties.IProperty; +import net.minecraft.block.properties.PropertyInteger; +import net.minecraft.block.state.BlockStateContainer; +import net.minecraft.block.state.IBlockState; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemStack; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.RayTraceResult; +import net.minecraft.world.Explosion; +import net.minecraft.world.IBlockAccess; +import net.minecraft.world.World; +import net.montoyo.wd.WebDisplays; +import net.montoyo.wd.core.DefaultPeripheral; +import net.montoyo.wd.core.IPeripheral; +import net.montoyo.wd.entity.TileEntityKeyboard; +import net.montoyo.wd.item.ItemLinker; +import net.montoyo.wd.utilities.BlockSide; +import net.montoyo.wd.utilities.Vector3i; + +import javax.annotation.Nonnull; +import java.util.Random; + +public class BlockKeyboardRight extends Block implements IPeripheral { + + public static final PropertyInteger facing = PropertyInteger.create("facing", 0, 3); + private static final IProperty[] properties = new IProperty[] { facing }; + public static final AxisAlignedBB KEYBOARD_AABB = new AxisAlignedBB(0.0, 0.0, 0.0, 1.0, 1.0 / 16.0, 1.0); + + public BlockKeyboardRight() { + super(Material.ROCK); + setHardness(1.5f); + setResistance(10.f); + setUnlocalizedName("webdisplays.peripheral.keyboard"); + setRegistryName("keyboard"); + fullBlock = false; + } + + @Override + @Nonnull + protected BlockStateContainer createBlockState() { + return new BlockStateContainer(this, properties); + } + + @Override + public int quantityDropped(Random random) { + return 0; + } + + @Override + public boolean isFullCube(IBlockState state) { + return false; + } + + @Override + public boolean isFullBlock(IBlockState state) { + return false; + } + + @Override + public boolean isNormalCube(IBlockState state, IBlockAccess world, BlockPos pos) { + return false; + } + + @Override + public boolean isOpaqueCube(IBlockState state) { + return false; + } + + @Override + public boolean doesSideBlockRendering(IBlockState state, IBlockAccess world, BlockPos pos, EnumFacing face) { + return false; + } + + @Override + @Nonnull + public AxisAlignedBB getBoundingBox(IBlockState state, IBlockAccess source, BlockPos pos) { + return KEYBOARD_AABB; + } + + @Override + @Nonnull + public IBlockState getStateFromMeta(int meta) { + return getDefaultState().withProperty(facing, meta); + } + + @Override + public int getMetaFromState(IBlockState state) { + return state.getValue(facing); + } + + @Override + @Nonnull + public ItemStack getPickBlock(@Nonnull IBlockState state, RayTraceResult target, @Nonnull World world, @Nonnull BlockPos pos, EntityPlayer player) { + return new ItemStack(WebDisplays.INSTANCE.blockPeripheral, 1, 0); + } + + private TileEntityKeyboard getTileEntity(World world, BlockPos pos) { + for(EnumFacing nf: EnumFacing.HORIZONTALS) { + BlockPos np = pos.add(nf.getDirectionVec()); + IBlockState ns = world.getBlockState(np); + + if(ns.getBlock() instanceof BlockPeripheral && ns.getValue(BlockPeripheral.type) == DefaultPeripheral.KEYBOARD) { + TileEntity te = world.getTileEntity(np); + if(te != null && te instanceof TileEntityKeyboard) + return (TileEntityKeyboard) te; + + break; + } + } + + return null; + } + + @Override + public boolean connect(World world, BlockPos pos, IBlockState state, Vector3i scrPos, BlockSide scrSide) { + TileEntityKeyboard keyboard = getTileEntity(world, pos); + return keyboard != null && keyboard.connect(world, pos, state, scrPos, scrSide); + } + + @Override + @Nonnull + public EnumPushReaction getMobilityFlag(IBlockState state) { + return EnumPushReaction.IGNORE; + } + + public static boolean checkNeighborhood(IBlockAccess world, BlockPos bp, BlockPos ignore) { + for(EnumFacing neighbor: EnumFacing.HORIZONTALS) { + BlockPos np = bp.add(neighbor.getDirectionVec()); + + if(ignore == null || !np.equals(ignore)) { + IBlockState state = world.getBlockState(np); + + if(state.getBlock() instanceof BlockPeripheral) { + if(state.getValue(BlockPeripheral.type) == DefaultPeripheral.KEYBOARD) + return false; + } else if(state.getBlock() instanceof BlockKeyboardRight) + return false; + } + } + + return true; + } + + public void removeLeftPiece(World world, BlockPos pos, boolean dropItem) { + for(EnumFacing nf: EnumFacing.HORIZONTALS) { + BlockPos np = pos.add(nf.getDirectionVec()); + IBlockState ns = world.getBlockState(np); + + if(ns.getBlock() instanceof BlockPeripheral && ns.getValue(BlockPeripheral.type) == DefaultPeripheral.KEYBOARD) { + if(dropItem) + ns.getBlock().dropBlockAsItem(world, np, ns, 0); + + world.setBlockToAir(np); + break; + } + } + } + + @Override + public void neighborChanged(IBlockState state, World world, BlockPos pos, Block neighborType, BlockPos neighbor) { + if(world.isRemote) + return; + + if(neighbor.getX() == pos.getX() && neighbor.getY() == pos.getY() - 1 && neighbor.getZ() == pos.getZ() && world.isAirBlock(neighbor)) { + removeLeftPiece(world, pos, true); + world.setBlockToAir(pos); + } + } + + @Override + public boolean removedByPlayer(@Nonnull IBlockState state, World world, @Nonnull BlockPos pos, @Nonnull EntityPlayer ply, boolean willHarvest) { + if(!world.isRemote) + removeLeftPiece(world, pos, !ply.isCreative()); + + return super.removedByPlayer(state, world, pos, ply, willHarvest); + } + + @Override + public void onBlockDestroyedByExplosion(World world, BlockPos pos, Explosion explosionIn) { + if(!world.isRemote) + removeLeftPiece(world, pos, true); + } + + @Override + public void onEntityCollidedWithBlock(World world, BlockPos pos, IBlockState state, Entity entity) { + double rpos = (entity.posY - ((double) pos.getY())) * 16.0; + if(!world.isRemote && rpos >= 1.0 && rpos <= 2.0 && Math.random() < 0.25) { + TileEntityKeyboard tek = getTileEntity(world, pos); + + if(tek != null) + tek.simulateCat(entity); + } + } + + @Override + public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumHand hand, EnumFacing facing, float hitX, float hitY, float hitZ) { + if(player.isSneaking()) + return false; + + if(player.getHeldItem(hand).getItem() instanceof ItemLinker) + return false; + + TileEntityKeyboard tek = getTileEntity(world, pos); + if(tek != null) + return tek.onRightClick(player, hand, BlockSide.values()[facing.ordinal()]); + + return false; + } + +} diff --git a/src/main/java/net/montoyo/wd/block/BlockPeripheral.java b/src/main/java/net/montoyo/wd/block/BlockPeripheral.java new file mode 100644 index 0000000..49be3ac --- /dev/null +++ b/src/main/java/net/montoyo/wd/block/BlockPeripheral.java @@ -0,0 +1,281 @@ +/* + * Copyright (C) 2019 BARBOTIN Nicolas + */ + +package net.montoyo.wd.block; + +import net.minecraft.block.Block; +import net.minecraft.block.material.EnumPushReaction; +import net.minecraft.block.material.Material; +import net.minecraft.block.properties.IProperty; +import net.minecraft.block.properties.PropertyEnum; +import net.minecraft.block.properties.PropertyInteger; +import net.minecraft.block.state.BlockStateContainer; +import net.minecraft.block.state.IBlockState; +import net.minecraft.creativetab.CreativeTabs; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.ItemBlock; +import net.minecraft.item.ItemStack; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.EnumBlockRenderType; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.NonNullList; +import net.minecraft.util.math.AxisAlignedBB; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.Vec3i; +import net.minecraft.world.Explosion; +import net.minecraft.world.IBlockAccess; +import net.minecraft.world.World; +import net.minecraftforge.fml.common.network.NetworkRegistry; +import net.montoyo.wd.WebDisplays; +import net.montoyo.wd.core.DefaultPeripheral; +import net.montoyo.wd.entity.*; +import net.montoyo.wd.item.ItemLinker; +import net.montoyo.wd.item.ItemPeripheral; +import net.montoyo.wd.net.client.CMessageCloseGui; +import net.montoyo.wd.utilities.BlockSide; +import net.montoyo.wd.utilities.Log; + +import javax.annotation.Nonnull; + +public class BlockPeripheral extends WDBlockContainer { + + public static final PropertyEnum type = PropertyEnum.create("type", DefaultPeripheral.class); + public static final PropertyInteger facing = PropertyInteger.create("facing", 0, 3); + private static final IProperty[] properties = new IProperty[] { type, facing }; + + public BlockPeripheral() { + super(Material.ROCK); + setHardness(1.5f); + setResistance(10.f); + setCreativeTab(WebDisplays.CREATIVE_TAB); + setName("peripheral"); + } + + @Override + protected ItemBlock createItemBlock() { + return new ItemPeripheral(this); + } + + @Override + @Nonnull + protected BlockStateContainer createBlockState() { + return new BlockStateContainer(this, properties); + } + + @Override + @Nonnull + public IBlockState getStateForPlacement(@Nonnull World world, @Nonnull BlockPos pos, @Nonnull EnumFacing nocare, float hitX, + float hitY, float hitZ, int meta, @Nonnull EntityLivingBase placer, EnumHand hand) { + int rot = MathHelper.floor(((double) (placer.rotationYaw * 4.0f / 360.0f)) + 2.5) & 3; + return getDefaultState().withProperty(type, DefaultPeripheral.fromMetadata(meta)).withProperty(facing, rot); + } + + @Override + public void getSubBlocks(CreativeTabs tab, NonNullList list) { + for(DefaultPeripheral dp : DefaultPeripheral.values()) + list.add(new ItemStack(getItem(), 1, dp.toMetadata(0))); + } + + @Override + @Nonnull + public IBlockState getStateFromMeta(int meta) { + DefaultPeripheral dp = DefaultPeripheral.fromMetadata(meta); + IBlockState state = getDefaultState().withProperty(type, dp); + + if(dp.hasFacing()) + state = state.withProperty(facing, (meta >> 2) & 3); + + return state; + } + + @Override + public int getMetaFromState(IBlockState state) { + return state.getValue(type).toMetadata(state.getValue(facing)); + } + + @Override + public TileEntity createNewTileEntity(@Nonnull World world, int meta) { + Class cls = DefaultPeripheral.fromMetadata(meta).getTEClass(); + if(cls == null) + return null; + + try { + return cls.newInstance(); + } catch(Throwable t) { + Log.errorEx("Couldn't instantiate peripheral TileEntity:", t); + } + + return null; + } + + @Override + @Nonnull + public EnumBlockRenderType getRenderType(IBlockState state) { + return EnumBlockRenderType.MODEL; + } + + @Override + public int damageDropped(IBlockState state) { + return state.getValue(type).toMetadata(0); + } + + @Override + public boolean onBlockActivated(World world, BlockPos pos, IBlockState state, EntityPlayer player, EnumHand hand, EnumFacing facing, float hitX, float hitY, float hitZ) { + if(player.isSneaking()) + return false; + + if(player.getHeldItem(hand).getItem() instanceof ItemLinker) + return false; + + TileEntity te = world.getTileEntity(pos); + + if(te instanceof TileEntityPeripheralBase) + return ((TileEntityPeripheralBase) te).onRightClick(player, hand, BlockSide.values()[facing.ordinal()]); + else if(te instanceof TileEntityServer) { + ((TileEntityServer) te).onPlayerRightClick(player); + return true; + } else + return false; + } + + @Override + public boolean isFullCube(IBlockState state) { + return state.getValue(type) != DefaultPeripheral.KEYBOARD; + } + + @Override + public boolean isFullBlock(IBlockState state) { + return state.getValue(type) != DefaultPeripheral.KEYBOARD; + } + + @Override + public boolean isNormalCube(IBlockState state, IBlockAccess world, BlockPos pos) { + return state.getValue(type) != DefaultPeripheral.KEYBOARD; + } + + @Override + public boolean isOpaqueCube(IBlockState state) { + return state.getValue(type) != DefaultPeripheral.KEYBOARD; + } + + @Override + public boolean doesSideBlockRendering(IBlockState state, IBlockAccess world, BlockPos pos, EnumFacing face) { + return state.getValue(type) != DefaultPeripheral.KEYBOARD; + } + + @Override + @Nonnull + public AxisAlignedBB getBoundingBox(IBlockState state, IBlockAccess source, BlockPos pos) { + return state.getValue(type) == DefaultPeripheral.KEYBOARD ? BlockKeyboardRight.KEYBOARD_AABB : FULL_BLOCK_AABB; + } + + @Override + public void onBlockPlacedBy(World world, BlockPos pos, IBlockState state, EntityLivingBase placer, ItemStack stack) { + if(world.isRemote) + return; + + if(state.getValue(type) == DefaultPeripheral.KEYBOARD) { + //Keyboard special treatment + int f = state.getValue(facing); + Vec3i dir = EnumFacing.getHorizontal(f).rotateY().getDirectionVec(); + BlockPos left = pos.add(dir); + BlockPos right = pos.subtract(dir); + + if(!world.isAirBlock(pos.down()) && BlockKeyboardRight.checkNeighborhood(world, pos, null)) { + if(world.isAirBlock(right) && !world.isAirBlock(right.down()) && BlockKeyboardRight.checkNeighborhood(world, right, pos)) { + world.setBlockState(right, WebDisplays.INSTANCE.blockKbRight.getDefaultState().withProperty(BlockKeyboardRight.facing, f)); + return; + } else if(world.isAirBlock(left) && !world.isAirBlock(left.down()) && BlockKeyboardRight.checkNeighborhood(world, left, pos)) { + world.setBlockState(left, state); + world.setBlockState(pos, WebDisplays.INSTANCE.blockKbRight.getDefaultState().withProperty(BlockKeyboardRight.facing, f)); + return; + } + } + + //Not good; remove this shit... + world.setBlockToAir(pos); + if(!(placer instanceof EntityPlayer) || !((EntityPlayer) placer).isCreative()) + dropBlockAsItem(world, pos, state, 0); + } else if(placer instanceof EntityPlayer) { + TileEntity te = world.getTileEntity(pos); + + if(te instanceof TileEntityServer) + ((TileEntityServer) te).setOwner((EntityPlayer) placer); + else if(te instanceof TileEntityInterfaceBase) + ((TileEntityInterfaceBase) te).setOwner((EntityPlayer) placer); + } + } + + @Override + @Nonnull + public EnumPushReaction getMobilityFlag(IBlockState state) { + return EnumPushReaction.IGNORE; + } + + private void removeRightPiece(World world, BlockPos pos) { + for(EnumFacing nf: EnumFacing.HORIZONTALS) { + BlockPos np = pos.add(nf.getDirectionVec()); + + if(world.getBlockState(np).getBlock() instanceof BlockKeyboardRight) { + world.setBlockToAir(np); + break; + } + } + } + + @Override + public void neighborChanged(IBlockState state, World world, BlockPos pos, Block neighborType, BlockPos neighbor) { + TileEntity te = world.getTileEntity(pos); + if(te != null && te instanceof TileEntityPeripheralBase) + ((TileEntityPeripheralBase) te).onNeighborChange(neighborType, neighbor); + + if(world.isRemote || state.getValue(type) != DefaultPeripheral.KEYBOARD) + return; + + if(neighbor.getX() == pos.getX() && neighbor.getY() == pos.getY() - 1 && neighbor.getZ() == pos.getZ() && world.isAirBlock(neighbor)) { + removeRightPiece(world, pos); + world.setBlockToAir(pos); + dropBlockAsItem(world, pos, state, 0); + WebDisplays.NET_HANDLER.sendToAllAround(new CMessageCloseGui(pos), point(world, pos)); + } + } + + @Override + public void onBlockDestroyedByPlayer(World world, BlockPos pos, IBlockState state) { + if(!world.isRemote) { + if(state.getBlock() == this && state.getValue(type) == DefaultPeripheral.KEYBOARD) + removeRightPiece(world, pos); + + WebDisplays.NET_HANDLER.sendToAllAround(new CMessageCloseGui(pos), point(world, pos)); + } + } + + @Override + public void onBlockDestroyedByExplosion(World world, BlockPos pos, Explosion explosion) { + onBlockDestroyedByPlayer(world, pos, world.getBlockState(pos)); + } + + @Override + public void onEntityCollidedWithBlock(World world, BlockPos pos, IBlockState state, Entity entity) { + if(!world.isRemote && world.getBlockState(pos).getValue(type) == DefaultPeripheral.KEYBOARD) { + double rpos = (entity.posY - ((double) pos.getY())) * 16.0; + + if(rpos >= 1.0 && rpos <= 2.0 && Math.random() < 0.25) { + TileEntity te = world.getTileEntity(pos); + + if(te != null && te instanceof TileEntityKeyboard) + ((TileEntityKeyboard) te).simulateCat(entity); + } + } + } + + private static NetworkRegistry.TargetPoint point(World world, BlockPos bp) { + return new NetworkRegistry.TargetPoint(world.provider.getDimension(), (double) bp.getX(), (double) bp.getY(), (double) bp.getZ(), 64.0); + } + +} diff --git a/src/main/java/net/montoyo/wd/block/BlockScreen.java b/src/main/java/net/montoyo/wd/block/BlockScreen.java new file mode 100644 index 0000000..5bdc5ac --- /dev/null +++ b/src/main/java/net/montoyo/wd/block/BlockScreen.java @@ -0,0 +1,400 @@ +/* + * Copyright (C) 2018 BARBOTIN Nicolas + */ + +package net.montoyo.wd.block; + +import net.minecraft.block.Block; +import net.minecraft.block.material.EnumPushReaction; +import net.minecraft.block.material.Material; +import net.minecraft.block.properties.IProperty; +import net.minecraft.block.properties.PropertyBool; +import net.minecraft.block.properties.PropertyInteger; +import net.minecraft.block.state.BlockStateContainer; +import net.minecraft.block.state.IBlockState; +import net.minecraft.client.util.ITooltipFlag; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.ItemBlock; +import net.minecraft.item.ItemStack; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.EnumBlockRenderType; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.EnumHand; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.text.TextFormatting; +import net.minecraft.world.Explosion; +import net.minecraft.world.IBlockAccess; +import net.minecraft.world.World; +import net.minecraftforge.common.property.ExtendedBlockState; +import net.minecraftforge.common.property.IExtendedBlockState; +import net.minecraftforge.common.property.IUnlistedProperty; +import net.minecraftforge.common.property.Properties; +import net.montoyo.wd.WebDisplays; +import net.montoyo.wd.core.DefaultUpgrade; +import net.montoyo.wd.core.ScreenRights; +import net.montoyo.wd.core.IUpgrade; +import net.montoyo.wd.data.SetURLData; +import net.montoyo.wd.entity.TileEntityScreen; +import net.montoyo.wd.item.WDItem; +import net.montoyo.wd.utilities.*; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.List; + +public class BlockScreen extends WDBlockContainer { + + public static final PropertyBool hasTE = PropertyBool.create("haste"); + public static final PropertyBool emitting = PropertyBool.create("emitting"); + private static final IProperty[] properties = new IProperty[] { hasTE, emitting }; + public static final IUnlistedProperty[] sideFlags = new IUnlistedProperty[6]; + static { + for(int i = 0; i < sideFlags.length; i++) + sideFlags[i] = Properties.toUnlisted(PropertyInteger.create("neighbor" + i, 0, 15)); + } + + private static final int BAR_BOT = 1; + private static final int BAR_RIGHT = 2; + private static final int BAR_TOP = 4; + private static final int BAR_LEFT = 8; + + public BlockScreen() { + super(Material.ROCK); + setHardness(1.5f); + setResistance(10.f); + setCreativeTab(WebDisplays.CREATIVE_TAB); + setName("screen"); + setDefaultState(blockState.getBaseState().withProperty(hasTE, false).withProperty(emitting, false)); + } + + @Override + @Nonnull + public EnumBlockRenderType getRenderType(IBlockState state) { + return EnumBlockRenderType.MODEL; + } + + @Override + @Nonnull + protected BlockStateContainer createBlockState() { + return new ExtendedBlockState(this, properties, sideFlags); + } + + public static boolean isntScreenBlock(IBlockAccess world, Vector3i pos) { + return world.getBlockState(pos.toBlock()).getBlock() != WebDisplays.INSTANCE.blockScreen; + } + + @Override + @Nonnull + public IBlockState getExtendedState(@Nonnull IBlockState state, IBlockAccess world, BlockPos bpos) { + IExtendedBlockState ret = (IExtendedBlockState) blockState.getBaseState(); + Vector3i pos = new Vector3i(bpos); + + for(BlockSide side : BlockSide.values()) { + int icon = 0; + if(isntScreenBlock(world, side.up.clone().add(pos))) icon |= BAR_TOP; + if(isntScreenBlock(world, side.down.clone().add(pos))) icon |= BAR_BOT; + if(isntScreenBlock(world, side.left.clone().add(pos))) icon |= BAR_LEFT; + if(isntScreenBlock(world, side.right.clone().add(pos))) icon |= BAR_RIGHT; + + ret = ret.withProperty(sideFlags[side.ordinal()], icon); + } + + return ret; + } + + @Override + @Nonnull + public IBlockState getStateFromMeta(int meta) { + return getDefaultState().withProperty(hasTE, (meta & 1) != 0).withProperty(emitting, (meta & 2) != 0); + } + + @Override + public int getMetaFromState(IBlockState state) { + int ret = 0; + if(state.getValue(hasTE)) + ret |= 1; + + if(state.getValue(emitting)) + ret |= 2; + + return ret; + } + + @Override + public boolean onBlockActivated(World world, BlockPos bpos, IBlockState state, EntityPlayer player, EnumHand hand, EnumFacing facing, float hitX, float hitY, float hitZ) { + ItemStack heldItem = player.getHeldItem(hand); + if(heldItem.isEmpty()) + heldItem = null; //Easier to work with + else if(!(heldItem.getItem() instanceof IUpgrade)) + return false; + + if(world.isRemote) + return true; + + boolean sneaking = player.isSneaking(); + Vector3i pos = new Vector3i(bpos); + BlockSide side = BlockSide.values()[facing.ordinal()]; + + Multiblock.findOrigin(world, pos, side, null); + TileEntityScreen te = (TileEntityScreen) world.getTileEntity(pos.toBlock()); + + if(te != null && te.getScreen(side) != null) { + TileEntityScreen.Screen scr = te.getScreen(side); + + if(sneaking) { //Set URL + if((scr.rightsFor(player) & ScreenRights.CHANGE_URL) == 0) + Util.toast(player, "restrictions"); + else + (new SetURLData(pos, scr.side, scr.url)).sendTo((EntityPlayerMP) player); + + return true; + } else if(heldItem != null && !te.hasUpgrade(side, heldItem)) { //Add upgrade + if((scr.rightsFor(player) & ScreenRights.MANAGE_UPGRADES) == 0) { + Util.toast(player, "restrictions"); + return true; + } + + if(te.addUpgrade(side, heldItem, player, false)) { + if(!player.isCreative()) + heldItem.shrink(1); + + Util.toast(player, TextFormatting.AQUA, "upgradeOk"); + if(player instanceof EntityPlayerMP) + WebDisplays.INSTANCE.criterionUpgradeScreen.trigger(((EntityPlayerMP) player).getAdvancements()); + } else + Util.toast(player, "upgradeError"); + + return true; + } else { //Click + if((scr.rightsFor(player) & ScreenRights.CLICK) == 0) { + Util.toast(player, "restrictions"); + return true; + } + + Vector2i tmp = new Vector2i(); + if(hit2pixels(side, bpos, pos, scr, hitX, hitY, hitZ, tmp)) + te.click(side, tmp); + + return true; + } + } else if(sneaking) { + Util.toast(player, "turnOn"); + return true; + } + + Vector2i size = Multiblock.measure(world, pos, side); + if(size.x < 2 || size.y < 2) { + Util.toast(player, "tooSmall"); + return true; + } + + if(size.x > WebDisplays.INSTANCE.maxScreenX || size.y > WebDisplays.INSTANCE.maxScreenY) { + Util.toast(player, "tooBig", WebDisplays.INSTANCE.maxScreenX, WebDisplays.INSTANCE.maxScreenY); + return true; + } + + Vector3i err = Multiblock.check(world, pos, size, side); + if(err != null) { + Util.toast(player, "invalid", err.toString()); + return true; + } + + boolean created = false; + Log.info("Player %s (UUID %s) created a screen at %s of size %dx%d", player.getName(), player.getGameProfile().getId().toString(), pos.toString(), size.x, size.y); + + if(te == null) { + BlockPos bp = pos.toBlock(); + world.setBlockState(bp, world.getBlockState(bp).withProperty(hasTE, true)); + te = (TileEntityScreen) world.getTileEntity(bp); + created = true; + } + + te.addScreen(side, size, null, player, !created); + return true; + } + + @Override + public void neighborChanged(IBlockState state, World world, BlockPos pos, Block block, BlockPos source) { + if(block != this && !world.isRemote && !state.getValue(emitting)) { + for(BlockSide side: BlockSide.values()) { + Vector3i vec = new Vector3i(pos); + Multiblock.findOrigin(world, vec, side, null); + + TileEntityScreen tes = (TileEntityScreen) world.getTileEntity(vec.toBlock()); + if(tes != null && tes.hasUpgrade(side, DefaultUpgrade.REDSTONE_INPUT)) { + EnumFacing facing = EnumFacing.VALUES[side.reverse().ordinal()]; //Opposite face + vec.sub(pos.getX(), pos.getY(), pos.getZ()).neg(); + tes.updateJSRedstone(side, new Vector2i(vec.dot(side.right), vec.dot(side.up)), world.getRedstonePower(pos, facing)); + } + } + } + } + + public static boolean hit2pixels(BlockSide side, BlockPos bpos, Vector3i pos, TileEntityScreen.Screen scr, float hitX, float hitY, float hitZ, Vector2i dst) { + if(side.right.x < 0) + hitX -= 1.f; + + if(side.right.z < 0 || side == BlockSide.TOP || side == BlockSide.BOTTOM) + hitZ -= 1.f; + + Vector3f rel = new Vector3f(bpos.getX(), bpos.getY(), bpos.getZ()); + rel.sub((float) pos.x, (float) pos.y, (float) pos.z); + rel.add(hitX, hitY, hitZ); + + float cx = rel.dot(side.right.toFloat()) - 2.f / 16.f; + float cy = rel.dot(side.up.toFloat()) - 2.f / 16.f; + float sw = ((float) scr.size.x) - 4.f / 16.f; + float sh = ((float) scr.size.y) - 4.f / 16.f; + + cx /= sw; + cy /= sh; + + if(cx >= 0.f && cx <= 1.0 && cy >= 0.f && cy <= 1.f) { + if(side != BlockSide.BOTTOM) + cy = 1.f - cy; + + switch(scr.rotation) { + case ROT_90: + cy = 1.0f - cy; + break; + + case ROT_180: + cx = 1.0f - cx; + cy = 1.0f - cy; + break; + + case ROT_270: + cx = 1.0f - cx; + break; + + default: + break; + } + + cx *= (float) scr.resolution.x; + cy *= (float) scr.resolution.y; + + if(scr.rotation.isVertical) { + dst.x = (int) cy; + dst.y = (int) cx; + } else { + dst.x = (int) cx; + dst.y = (int) cy; + } + + return true; + } + + return false; + } + + @Nullable + @Override + public TileEntity createNewTileEntity(@Nonnull World world, int meta) { + if((meta & 1) == 0) + return null; + + return ((meta & 1) == 0) ? null : new TileEntityScreen(); + } + + /************************************************* DESTRUCTION HANDLING *************************************************/ + + private void onDestroy(World world, BlockPos pos, EntityPlayer ply) { + if(!world.isRemote) { + Vector3i bp = new Vector3i(pos); + Multiblock.BlockOverride override = new Multiblock.BlockOverride(bp, Multiblock.OverrideAction.SIMULATE); + + for(BlockSide bs: BlockSide.values()) + destroySide(world, bp.clone(), bs, override, ply); + } + } + + private void destroySide(World world, Vector3i pos, BlockSide side, Multiblock.BlockOverride override, EntityPlayer source) { + Multiblock.findOrigin(world, pos, side, override); + BlockPos bp = pos.toBlock(); + TileEntity te = world.getTileEntity(bp); + + if(te != null && te instanceof TileEntityScreen) { + ((TileEntityScreen) te).onDestroy(source); + world.setBlockState(bp, world.getBlockState(bp).withProperty(hasTE, false)); //Destroy tile entity. + } + } + + @Override + public boolean removedByPlayer(@Nonnull IBlockState state, World world, @Nonnull BlockPos pos, @Nonnull EntityPlayer ply, boolean willHarvest) { + onDestroy(world, pos, ply); + return super.removedByPlayer(state, world, pos, ply, willHarvest); + } + + @Override + public void onBlockDestroyedByExplosion(World world, BlockPos pos, Explosion explosion) { + onDestroy(world, pos, null); + } + + @Override + public void onBlockPlacedBy(World world, BlockPos pos, IBlockState state, EntityLivingBase whoDidThisShit, ItemStack stack) { + if(world.isRemote) + return; + + Multiblock.BlockOverride override = new Multiblock.BlockOverride(new Vector3i(pos), Multiblock.OverrideAction.IGNORE); + Vector3i[] neighbors = new Vector3i[6]; + + neighbors[0] = new Vector3i(pos.getX() + 1, pos.getY(), pos.getZ()); + neighbors[1] = new Vector3i(pos.getX() - 1, pos.getY(), pos.getZ()); + neighbors[2] = new Vector3i(pos.getX(), pos.getY() + 1, pos.getZ()); + neighbors[3] = new Vector3i(pos.getX(), pos.getY() - 1, pos.getZ()); + neighbors[4] = new Vector3i(pos.getX(), pos.getY(), pos.getZ() + 1); + neighbors[5] = new Vector3i(pos.getX(), pos.getY(), pos.getZ() - 1); + + for(Vector3i neighbor: neighbors) { + if(world.getBlockState(neighbor.toBlock()).getBlock() instanceof BlockScreen) { + for(BlockSide bs: BlockSide.values()) + destroySide(world, neighbor.clone(), bs, override, (whoDidThisShit instanceof EntityPlayer) ? ((EntityPlayer) whoDidThisShit) : null); + } + } + } + + @Override + @Nonnull + public EnumPushReaction getMobilityFlag(IBlockState state) { + return EnumPushReaction.IGNORE; + } + + @Override + public int getWeakPower(IBlockState state, IBlockAccess world, BlockPos pos, EnumFacing side) { + return state.getValue(emitting) ? 15 : 0; + } + + @Override + public boolean canProvidePower(IBlockState state) { + return state.getValue(emitting); + } + + @Override + protected ItemBlock createItemBlock() { + return new ItemBlockScreen(this); + } + + private static class ItemBlockScreen extends ItemBlock implements WDItem { + + public ItemBlockScreen(BlockScreen screen) { + super(screen); + } + + @Nullable + @Override + public String getWikiName(@Nonnull ItemStack is) { + return "Screen"; + } + + @Override + public void addInformation(@Nullable ItemStack is, @Nullable World world, @Nullable List tt, @Nullable ITooltipFlag ttFlags) { + super.addInformation(is, world, tt, ttFlags); + WDItem.addInformation(tt); + } + + } + +} diff --git a/src/main/java/net/montoyo/wd/block/WDBlock.java b/src/main/java/net/montoyo/wd/block/WDBlock.java new file mode 100644 index 0000000..4dab4a7 --- /dev/null +++ b/src/main/java/net/montoyo/wd/block/WDBlock.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2018 BARBOTIN Nicolas + */ + +package net.montoyo.wd.block; + +import net.minecraft.block.Block; +import net.minecraft.block.material.MapColor; +import net.minecraft.block.material.Material; +import net.minecraft.item.ItemBlock; + +public abstract class WDBlock extends Block { + + protected ItemBlock itemBlock; + + public WDBlock(Material mat, MapColor color) { + super(mat, color); + } + + public WDBlock(Material material) { + super(material); + } + + protected void setName(String name) { + setUnlocalizedName("webdisplays." + name); + setRegistryName(name); + } + + public void makeItemBlock() { + if(itemBlock != null) + throw new RuntimeException("WDBlock.makeItemBlock() called twice!"); + + itemBlock = new ItemBlock(this); + itemBlock.setUnlocalizedName(getUnlocalizedName()); + itemBlock.setRegistryName(getRegistryName()); + } + + public ItemBlock getItem() { + return itemBlock; + } + +} diff --git a/src/main/java/net/montoyo/wd/block/WDBlockContainer.java b/src/main/java/net/montoyo/wd/block/WDBlockContainer.java new file mode 100644 index 0000000..4e14b43 --- /dev/null +++ b/src/main/java/net/montoyo/wd/block/WDBlockContainer.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2018 BARBOTIN Nicolas + */ + +package net.montoyo.wd.block; + +import net.minecraft.block.BlockContainer; +import net.minecraft.block.material.MapColor; +import net.minecraft.block.material.Material; +import net.minecraft.item.ItemBlock; + +public abstract class WDBlockContainer extends BlockContainer { + + protected ItemBlock itemBlock; + + public WDBlockContainer(Material material) { + super(material); + } + + protected void setName(String name) { + setUnlocalizedName("webdisplays." + name); + setRegistryName(name); + } + + protected abstract ItemBlock createItemBlock(); + + public void makeItemBlock() { + if(itemBlock != null) + throw new RuntimeException("WDBlockContainer.makeItemBlock() called twice!"); + + itemBlock = createItemBlock(); + itemBlock.setUnlocalizedName(getUnlocalizedName()); + itemBlock.setRegistryName(getRegistryName()); + } + + public ItemBlock getItem() { + return itemBlock; + } + +} diff --git a/src/main/java/net/montoyo/wd/client/ClientProxy.java b/src/main/java/net/montoyo/wd/client/ClientProxy.java new file mode 100644 index 0000000..a9e5645 --- /dev/null +++ b/src/main/java/net/montoyo/wd/client/ClientProxy.java @@ -0,0 +1,790 @@ +/* + * Copyright (C) 2019 BARBOTIN Nicolas + */ + +package net.montoyo.wd.client; + +import com.mojang.authlib.GameProfile; +import net.minecraft.advancements.Advancement; +import net.minecraft.advancements.AdvancementProgress; +import net.minecraft.block.Block; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.gui.inventory.GuiContainer; +import net.minecraft.client.multiplayer.ClientAdvancementManager; +import net.minecraft.client.renderer.block.model.ModelResourceLocation; +import net.minecraft.client.renderer.texture.TextureMap; +import net.minecraft.client.resources.IResourceManager; +import net.minecraft.client.resources.SimpleReloadableResourceManager; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.inventory.Slot; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.EnumHand; +import net.minecraft.util.EnumHandSide; +import net.minecraft.util.NonNullList; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.BlockPos; +import net.minecraft.util.math.RayTraceResult; +import net.minecraft.util.math.Vec3d; +import net.minecraft.world.World; +import net.minecraftforge.client.event.*; +import net.minecraftforge.client.model.ModelLoader; +import net.minecraftforge.client.resource.IResourceType; +import net.minecraftforge.client.resource.ISelectiveResourceReloadListener; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.world.WorldEvent; +import net.minecraftforge.fml.client.registry.ClientRegistry; +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent; +import net.minecraftforge.fml.common.gameevent.TickEvent; +import net.montoyo.mcef.api.*; +import net.montoyo.wd.SharedProxy; +import net.montoyo.wd.WebDisplays; +import net.montoyo.wd.block.BlockScreen; +import net.montoyo.wd.client.gui.*; +import net.montoyo.wd.client.gui.loading.GuiLoader; +import net.montoyo.wd.client.renderers.*; +import net.montoyo.wd.core.DefaultUpgrade; +import net.montoyo.wd.core.HasAdvancement; +import net.montoyo.wd.core.JSServerRequest; +import net.montoyo.wd.data.GuiData; +import net.montoyo.wd.entity.TileEntityScreen; +import net.montoyo.wd.item.ItemMulti; +import net.montoyo.wd.item.WDItem; +import net.montoyo.wd.miniserv.client.Client; +import net.montoyo.wd.net.server.SMessagePadCtrl; +import net.montoyo.wd.net.server.SMessageScreenCtrl; +import net.montoyo.wd.utilities.*; +import org.lwjgl.input.Keyboard; +import paulscode.sound.SoundSystemConfig; + +import javax.annotation.Nonnull; +import java.lang.reflect.Field; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.*; +import java.util.function.Predicate; + +public class ClientProxy extends SharedProxy implements ISelectiveResourceReloadListener, IDisplayHandler, IJSQueryHandler { + + public class PadData { + + public IBrowser view; + private boolean isInHotbar; + private final int id; + private long lastURLSent; + + private PadData(String url, int id) { + view = mcef.createBrowser(WebDisplays.applyBlacklist(url)); + view.resize((int) WebDisplays.INSTANCE.padResX, (int) WebDisplays.INSTANCE.padResY); + isInHotbar = true; + this.id = id; + } + + } + + private Minecraft mc; + private final ArrayList modelBakers = new ArrayList<>(); + private net.montoyo.mcef.api.API mcef; + private MinePadRenderer minePadRenderer; + private JSQueryDispatcher jsDispatcher; + private LaserPointerRenderer laserPointerRenderer; + private GuiScreen nextScreen; + private boolean isF1Down; + + //Miniserv handling + private int miniservPort; + private boolean msClientStarted; + + //Client-side advancement hack + private final Field advancementToProgressField = findAdvancementToProgressField(); + private ClientAdvancementManager lastAdvMgr; + private Map advancementToProgress; + + //Laser pointer + private TileEntityScreen pointedScreen; + private BlockSide pointedScreenSide; + private long lastPointPacket; + + //Tracking + private final ArrayList screenTracking = new ArrayList<>(); + private int lastTracked = 0; + + //MinePads Management + private final HashMap padMap = new HashMap<>(); + private final ArrayList padList = new ArrayList<>(); + private int minePadTickCounter = 0; + + /**************************************** INHERITED METHODS ****************************************/ + + @Override + public void preInit() { + mc = Minecraft.getMinecraft(); + MinecraftForge.EVENT_BUS.register(this); + registerCustomBlockBaker(new ScreenBaker(), WebDisplays.INSTANCE.blockScreen); + + mcef = MCEFApi.getAPI(); + if(mcef != null) + mcef.registerScheme("wd", WDScheme.class, true, false, false, true, true, false, false); + } + + @Override + public void init() { + ClientRegistry.bindTileEntitySpecialRenderer(TileEntityScreen.class, new ScreenRenderer()); + jsDispatcher = new JSQueryDispatcher(this); + minePadRenderer = new MinePadRenderer(); + laserPointerRenderer = new LaserPointerRenderer(); + } + + @Override + public void postInit() { + ((SimpleReloadableResourceManager) mc.getResourceManager()).registerReloadListener(this); + + if(mcef == null) + throw new RuntimeException("MCEF is missing"); + + mcef.registerDisplayHandler(this); + mcef.registerJSQueryHandler(this); + findAdvancementToProgressField(); + } + + @Override + public World getWorld(int dim) { + World ret = mc.world; + if(dim == CURRENT_DIMENSION) + return ret; + + if(ret.provider.getDimension() != dim) + throw new RuntimeException("Can't get non-current dimension " + dim + " from client."); + + return ret; + } + + @Override + public void enqueue(Runnable r) { + mc.addScheduledTask(r); + } + + @Override + public void displayGui(GuiData data) { + GuiScreen gui = data.createGui(mc.currentScreen, mc.world); + if(gui != null) + mc.displayGuiScreen(gui); + } + + @Override + public void trackScreen(TileEntityScreen tes, boolean track) { + int idx = -1; + for(int i = 0; i < screenTracking.size(); i++) { + if(screenTracking.get(i) == tes) { + idx = i; + break; + } + } + + if(track) { + if(idx < 0) + screenTracking.add(tes); + } else if(idx >= 0) + screenTracking.remove(idx); + } + + @Override + public void onAutocompleteResult(NameUUIDPair[] pairs) { + if(mc.currentScreen != null && mc.currentScreen instanceof WDScreen) { + if(pairs.length == 0) + ((WDScreen) mc.currentScreen).onAutocompleteFailure(); + else + ((WDScreen) mc.currentScreen).onAutocompleteResult(pairs); + } + } + + @Override + public GameProfile[] getOnlineGameProfiles() { + return new GameProfile[] { mc.player.getGameProfile() }; + } + + @Override + public void screenUpdateResolutionInGui(Vector3i pos, BlockSide side, Vector2i res) { + if(mc.currentScreen != null && mc.currentScreen instanceof GuiScreenConfig) { + GuiScreenConfig gsc = (GuiScreenConfig) mc.currentScreen; + + if(gsc.isForBlock(pos.toBlock(), side)) + gsc.updateResolution(res); + } + } + + @Override + public void screenUpdateRotationInGui(Vector3i pos, BlockSide side, Rotation rot) { + if(mc.currentScreen != null && mc.currentScreen instanceof GuiScreenConfig) { + GuiScreenConfig gsc = (GuiScreenConfig) mc.currentScreen; + + if(gsc.isForBlock(pos.toBlock(), side)) + gsc.updateRotation(rot); + } + } + + @Override + public void screenUpdateAutoVolumeInGui(Vector3i pos, BlockSide side, boolean av) { + if(mc.currentScreen != null && mc.currentScreen instanceof GuiScreenConfig) { + GuiScreenConfig gsc = (GuiScreenConfig) mc.currentScreen; + + if(gsc.isForBlock(pos.toBlock(), side)) + gsc.updateAutoVolume(av); + } + } + + @Override + public void displaySetPadURLGui(String padURL) { + mc.displayGuiScreen(new GuiSetURL2(padURL)); + } + + @Override + public void openMinePadGui(int padId) { + PadData pd = padMap.get(padId); + + if(pd != null && pd.view != null) + mc.displayGuiScreen(new GuiMinePad(pd)); + } + + @Override + @Nonnull + public HasAdvancement hasClientPlayerAdvancement(@Nonnull ResourceLocation rl) { + if(advancementToProgressField != null && mc.player != null && mc.player.connection != null) { + ClientAdvancementManager cam = mc.player.connection.getAdvancementManager(); + Advancement adv = cam.getAdvancementList().getAdvancement(rl); + + if(adv == null) + return HasAdvancement.DONT_KNOW; + + if(lastAdvMgr != cam) { + lastAdvMgr = cam; + + try { + advancementToProgress = (Map) advancementToProgressField.get(cam); + } catch(Throwable t) { + Log.warningEx("Could not get ClientAdvancementManager.advancementToProgress field", t); + advancementToProgress = null; + return HasAdvancement.DONT_KNOW; + } + } + + if(advancementToProgress == null) + return HasAdvancement.DONT_KNOW; + + Object progress = advancementToProgress.get(adv); + if(progress == null) + return HasAdvancement.NO; + + if(!(progress instanceof AdvancementProgress)) { + Log.warning("The ClientAdvancementManager.advancementToProgress map does not contain AdvancementProgress instances"); + advancementToProgress = null; //Invalidate this: it's wrong + return HasAdvancement.DONT_KNOW; + } + + return ((AdvancementProgress) progress).isDone() ? HasAdvancement.YES : HasAdvancement.NO; + } + + return HasAdvancement.DONT_KNOW; + } + + @Override + public MinecraftServer getServer() { + return mc.getIntegratedServer(); + } + + @Override + public void handleJSResponseSuccess(int reqId, JSServerRequest type, byte[] data) { + JSQueryDispatcher.ServerQuery q = jsDispatcher.fulfillQuery(reqId); + + if(q == null) + Log.warning("Received success response for invalid query ID %d of type %s", reqId, type.toString()); + else { + if(type == JSServerRequest.CLEAR_REDSTONE || type == JSServerRequest.SET_REDSTONE_AT) + q.success("{\"status\":\"success\"}"); + else + Log.warning("Received success response for query ID %d, but type is invalid", reqId); + } + } + + @Override + public void handleJSResponseError(int reqId, JSServerRequest type, int errCode, String err) { + JSQueryDispatcher.ServerQuery q = jsDispatcher.fulfillQuery(reqId); + + if(q == null) + Log.warning("Received error response for invalid query ID %d of type %s", reqId, type.toString()); + else + q.error(errCode, err); + } + + @Override + public void setMiniservClientPort(int port) { + miniservPort = port; + } + + @Override + public void startMiniservClient() { + if(miniservPort <= 0) { + Log.warning("Can't start miniserv client: miniserv is disabled"); + return; + } + + if(mc.player == null) { + Log.warning("Can't start miniserv client: player is null"); + return; + } + + SocketAddress saddr = mc.player.connection.getNetworkManager().channel().remoteAddress(); + if(saddr == null || !(saddr instanceof InetSocketAddress)) { + Log.warning("Miniserv client: remote address is not inet, assuming local address"); + saddr = new InetSocketAddress("127.0.0.1", 1234); + } + + InetSocketAddress msAddr = new InetSocketAddress(((InetSocketAddress) saddr).getAddress(), miniservPort); + Client.getInstance().start(msAddr); + msClientStarted = true; + } + + @Override + public boolean isMiniservDisabled() { + return miniservPort <= 0; + } + + @Override + public void closeGui(BlockPos bp, BlockSide bs) { + if(mc.currentScreen instanceof WDScreen) { + WDScreen scr = (WDScreen) mc.currentScreen; + + if(scr.isForBlock(bp, bs)) + mc.displayGuiScreen(null); + } + } + + @Override + public void renderRecipes() { + nextScreen = new RenderRecipe(); + } + + @Override + public boolean isShiftDown() { + return Keyboard.isKeyDown(Keyboard.KEY_LSHIFT) || Keyboard.isKeyDown(Keyboard.KEY_RSHIFT); + } + + /**************************************** RESOURCE MANAGER METHODS ****************************************/ + + @Override + public void onResourceManagerReload(IResourceManager resourceManager, Predicate resourcePredicate) { + Log.info("Resource manager reload: clearing GUI cache..."); + GuiLoader.clearCache(); + } + + /**************************************** DISPLAY HANDLER METHODS ****************************************/ + + @Override + public void onAddressChange(IBrowser browser, String url) { + if(browser != null) { + long t = System.currentTimeMillis(); + + for(PadData pd: padList) { + if(pd.view == browser && t - pd.lastURLSent >= 1000) { + if(WebDisplays.isSiteBlacklisted(url)) + pd.view.loadURL(WebDisplays.BLACKLIST_URL); + else { + pd.lastURLSent = t; //Avoid spamming the server with porn URLs + WebDisplays.NET_HANDLER.sendToServer(new SMessagePadCtrl(pd.id, url)); + } + + break; + } + } + + for(TileEntityScreen tes: screenTracking) + tes.updateClientSideURL(browser, url); + } + } + + @Override + public void onTitleChange(IBrowser browser, String title) { + } + + @Override + public void onTooltip(IBrowser browser, String text) { + } + + @Override + public void onStatusMessage(IBrowser browser, String value) { + } + + /**************************************** JS HANDLER METHODS ****************************************/ + + @Override + public boolean handleQuery(IBrowser browser, long queryId, String query, boolean persistent, IJSQueryCallback cb) { + if(browser != null && persistent && query != null && cb != null) { + query = query.toLowerCase(); + + if(query.startsWith("webdisplays_")) { + query = query.substring(12); + + String args; + int parenthesis = query.indexOf('('); + if(parenthesis < 0) + args = null; + else { + if(query.indexOf(')') != query.length() - 1) { + cb.failure(400, "Malformed request"); + return true; + } + + args = query.substring(parenthesis + 1, query.length() - 1); + query = query.substring(0, parenthesis); + } + + if(jsDispatcher.canHandleQuery(query)) + jsDispatcher.enqueueQuery(browser, query, args, cb); + else + cb.failure(404, "Unknown WebDisplays query"); + + return true; + } + } + + return false; + } + + @Override + public void cancelQuery(IBrowser browser, long queryId) { + } + + /**************************************** EVENT METHODS ****************************************/ + + @SubscribeEvent + public void onStitchTextures(TextureStitchEvent.Pre ev) { + TextureMap texMap = ev.getMap(); + + if(texMap == mc.getTextureMapBlocks()) { + for(ResourceModelPair pair : modelBakers) + pair.getModel().loadTextures(texMap); + } + } + + @SubscribeEvent + public void onBakeModel(ModelBakeEvent ev) { + for(ResourceModelPair pair : modelBakers) + ev.getModelRegistry().putObject(pair.getResourceLocation(), pair.getModel()); + } + + @SubscribeEvent + public void onRegisterModels(ModelRegistryEvent ev) { + final WebDisplays wd = WebDisplays.INSTANCE; + + //I hope I'm doing this right because it doesn't seem like it... + registerItemModel(wd.blockScreen.getItem(), 0, "inventory"); + ModelLoader.setCustomModelResourceLocation(wd.blockPeripheral.getItem(), 0, new ModelResourceLocation("webdisplays:kb_inv", "normal")); + registerItemModel(wd.blockPeripheral.getItem(), 1, "facing=2,type=ccinterface"); + registerItemModel(wd.blockPeripheral.getItem(), 2, "facing=2,type=cointerface"); + registerItemModel(wd.blockPeripheral.getItem(), 3, "facing=0,type=remotectrl"); + registerItemModel(wd.blockPeripheral.getItem(), 7, "facing=0,type=redstonectrl"); + registerItemModel(wd.blockPeripheral.getItem(), 11, "facing=0,type=server"); + registerItemModel(wd.itemScreenCfg, 0, "normal"); + registerItemModel(wd.itemOwnerThief, 0, "normal"); + registerItemModel(wd.itemLinker, 0, "normal"); + registerItemModel(wd.itemMinePad, 0, "normal"); + registerItemModel(wd.itemMinePad, 1, "normal"); + registerItemModel(wd.itemLaserPointer, 0, "normal"); + registerItemMultiModels(wd.itemUpgrade); + registerItemMultiModels(wd.itemCraftComp); + registerItemMultiModels(wd.itemAdvIcon); + } + + @SubscribeEvent + public void onTick(TickEvent.ClientTickEvent ev) { + if(ev.phase == TickEvent.Phase.END) { + //Help + if(Keyboard.isKeyDown(Keyboard.KEY_F1)) { + if(!isF1Down) { + isF1Down = true; + + String wikiName = null; + if(mc.currentScreen instanceof WDScreen) + wikiName = ((WDScreen) mc.currentScreen).getWikiPageName(); + else if(mc.currentScreen instanceof GuiContainer) { + Slot slot = ((GuiContainer) mc.currentScreen).getSlotUnderMouse(); + + if(slot != null && slot.getHasStack() && slot.getStack().getItem() instanceof WDItem) + wikiName = ((WDItem) slot.getStack().getItem()).getWikiName(slot.getStack()); + } + + if(wikiName != null) + mcef.openExampleBrowser("https://montoyo.net/wdwiki/index.php/" + wikiName); + } + } else if(isF1Down) + isF1Down = false; + + //Workaround cuz chat sux + if(nextScreen != null && mc.currentScreen == null) { + mc.displayGuiScreen(nextScreen); + nextScreen = null; + } + + //Unload/load screens depending on client player distance + if(mc.player != null && !screenTracking.isEmpty()) { + int id = lastTracked % screenTracking.size(); + lastTracked++; + + TileEntityScreen tes = screenTracking.get(id); + double dist2 = mc.player.getDistanceSq(tes.getPos()); + + if(tes.isLoaded()) { + if(dist2 > WebDisplays.INSTANCE.unloadDistance2) + tes.unload(); + else if(WebDisplays.INSTANCE.enableSoundDistance) + tes.updateTrackDistance(dist2, SoundSystemConfig.getMasterGain()); + } else if(dist2 <= WebDisplays.INSTANCE.loadDistance2) + tes.load(); + } + + //Load/unload minePads depending on which item is in the player's hand + if(++minePadTickCounter >= 10) { + minePadTickCounter = 0; + EntityPlayer ep = mc.player; + + for(PadData pd: padList) + pd.isInHotbar = false; + + if(ep != null) { + updateInventory(ep.inventory.mainInventory, ep.getHeldItem(EnumHand.MAIN_HAND), 9); + updateInventory(ep.inventory.offHandInventory, ep.getHeldItem(EnumHand.OFF_HAND), 1); //Is this okay? + } + + //TODO: Check for GuiContainer.draggedStack + + for(int i = padList.size() - 1; i >= 0; i--) { + PadData pd = padList.get(i); + + if(!pd.isInHotbar) { + pd.view.close(); + pd.view = null; //This is for GuiMinePad, in case the player dies with the GUI open + padList.remove(i); + padMap.remove(pd.id); + } + } + } + + //Laser pointer raycast + boolean raycastHit = false; + + if(mc.player != null && mc.world != null && mc.player.getHeldItem(EnumHand.MAIN_HAND).getItem() == WebDisplays.INSTANCE.itemLaserPointer + && mc.gameSettings.keyBindUseItem.isKeyDown() + && (mc.objectMouseOver == null || mc.objectMouseOver.typeOfHit != RayTraceResult.Type.BLOCK)) { + laserPointerRenderer.isOn = true; + RayTraceResult result = raycast(64.0); //TODO: Make that distance configurable + + if(result != null) { + BlockPos bpos = result.getBlockPos(); + + if(result.typeOfHit == RayTraceResult.Type.BLOCK && mc.world.getBlockState(bpos).getBlock() == WebDisplays.INSTANCE.blockScreen) { + Vector3i pos = new Vector3i(result.getBlockPos()); + BlockSide side = BlockSide.values()[result.sideHit.ordinal()]; + + Multiblock.findOrigin(mc.world, pos, side, null); + TileEntityScreen te = (TileEntityScreen) mc.world.getTileEntity(pos.toBlock()); + + if(te != null && te.hasUpgrade(side, DefaultUpgrade.LASER_MOUSE)) { //hasUpgrade returns false is there's no screen on side 'side' + //Since rights aren't synchronized, let the server check them for us... + TileEntityScreen.Screen scr = te.getScreen(side); + + if(scr.browser != null) { + float hitX = ((float) result.hitVec.x) - (float) bpos.getX(); + float hitY = ((float) result.hitVec.y) - (float) bpos.getY(); + float hitZ = ((float) result.hitVec.z) - (float) bpos.getZ(); + Vector2i tmp = new Vector2i(); + + if(BlockScreen.hit2pixels(side, bpos, pos, scr, hitX, hitY, hitZ, tmp)) { + laserClick(te, side, scr, tmp); + raycastHit = true; + } + } + } + } + } + } else + laserPointerRenderer.isOn = false; + + if(!raycastHit) + deselectScreen(); + + //Handle JS queries + jsDispatcher.handleQueries(); + + //Miniserv + if(msClientStarted && mc.player == null) { + msClientStarted = false; + Client.getInstance().stop(); + } + } + } + + @SubscribeEvent + public void onRenderPlayerHand(RenderSpecificHandEvent ev) { + Item item = ev.getItemStack().getItem(); + IItemRenderer renderer; + + if(item == WebDisplays.INSTANCE.itemMinePad) + renderer = minePadRenderer; + else if(item == WebDisplays.INSTANCE.itemLaserPointer) + renderer = laserPointerRenderer; + else + return; + + EnumHandSide handSide = mc.player.getPrimaryHand(); + if(ev.getHand() == EnumHand.OFF_HAND) + handSide = handSide.opposite(); + + renderer.render(ev.getItemStack(), (handSide == EnumHandSide.RIGHT) ? 1.0f : -1.0f, ev.getSwingProgress(), ev.getEquipProgress()); + ev.setCanceled(true); + } + + @SubscribeEvent + public void onWorldUnload(WorldEvent.Unload ev) { + Log.info("World unloaded; killing screens..."); + int dim = ev.getWorld().provider.getDimension(); + + for(int i = screenTracking.size() - 1; i >= 0; i--) { + if(screenTracking.get(i).getWorld().provider.getDimension() == dim) //Could be world == ev.getWorld() + screenTracking.remove(i).unload(); + } + } + + /**************************************** OTHER METHODS ****************************************/ + + private void laserClick(TileEntityScreen tes, BlockSide side, TileEntityScreen.Screen scr, Vector2i hit) { + if(pointedScreen == tes && pointedScreenSide == side) { + long t = System.currentTimeMillis(); + + if(t - lastPointPacket >= 100) { + lastPointPacket = t; + WebDisplays.NET_HANDLER.sendToServer(SMessageScreenCtrl.vec2(tes, side, SMessageScreenCtrl.CTRL_LASER_MOVE, hit)); + } + } else { + deselectScreen(); + pointedScreen = tes; + pointedScreenSide = side; + WebDisplays.NET_HANDLER.sendToServer(SMessageScreenCtrl.vec2(tes, side, SMessageScreenCtrl.CTRL_LASER_DOWN, hit)); + } + } + + private void deselectScreen() { + if(pointedScreen != null && pointedScreenSide != null) { + WebDisplays.NET_HANDLER.sendToServer(SMessageScreenCtrl.laserUp(pointedScreen, pointedScreenSide)); + pointedScreen = null; + pointedScreenSide = null; + } + } + + private RayTraceResult raycast(double dist) { + Vec3d start = mc.player.getPositionEyes(1.0f); + Vec3d lookVec = mc.player.getLook(1.0f); + Vec3d end = start.addVector(lookVec.x * dist, lookVec.y * dist, lookVec.z * dist); + + return mc.world.rayTraceBlocks(start, end, true, true, false); + } + + private void updateInventory(NonNullList inv, ItemStack heldStack, int cnt) { + for(int i = 0; i < cnt; i++) { + ItemStack item = inv.get(i); + + if(item.getItem() == WebDisplays.INSTANCE.itemMinePad) { + NBTTagCompound tag = item.getTagCompound(); + + if(tag != null && tag.hasKey("PadID")) + updatePad(tag.getInteger("PadID"), tag, item == heldStack); + } + } + } + + private void registerCustomBlockBaker(IModelBaker baker, Block block0) { + ModelResourceLocation normalLoc = new ModelResourceLocation(block0.getRegistryName(), "normal"); + ResourceModelPair pair = new ResourceModelPair(normalLoc, baker); + modelBakers.add(pair); + ModelLoader.setCustomStateMapper(block0, new StaticStateMapper(normalLoc)); + } + + private void registerItemModel(Item item, int meta, String variant) { + ModelLoader.setCustomModelResourceLocation(item, meta, new ModelResourceLocation(item.getRegistryName(), variant)); + } + + private void registerItemMultiModels(ItemMulti item) { + Enum[] values = item.getEnumValues(); + + for(int i = 0; i < values.length; i++) + ModelLoader.setCustomModelResourceLocation(item, i, new ModelResourceLocation(item.getRegistryName().toString() + '_' + values[i], "normal")); + } + + private void updatePad(int id, NBTTagCompound tag, boolean isSelected) { + PadData pd = padMap.get(id); + + if(pd != null) + pd.isInHotbar = true; + else if(isSelected && tag.hasKey("PadURL")) { + pd = new PadData(tag.getString("PadURL"), id); + padMap.put(id, pd); + padList.add(pd); + } + } + + public MinePadRenderer getMinePadRenderer() { + return minePadRenderer; + } + + public PadData getPadByID(int id) { + return padMap.get(id); + } + + public net.montoyo.mcef.api.API getMCEF() { + return mcef; + } + + public static final class ScreenSidePair { + + public TileEntityScreen tes; + public BlockSide side; + + } + + public boolean findScreenFromBrowser(IBrowser browser, ScreenSidePair pair) { + for(TileEntityScreen tes: screenTracking) { + for(int i = 0; i < tes.screenCount(); i++) { + TileEntityScreen.Screen scr = tes.getScreen(i); + + if(scr.browser == browser) { + pair.tes = tes; + pair.side = scr.side; + return true; + } + } + } + + return false; + } + + private static Field findAdvancementToProgressField() { + Field[] fields = ClientAdvancementManager.class.getDeclaredFields(); + Optional result = Arrays.stream(fields).filter(f -> f.getType() == Map.class).findAny(); + + if(result.isPresent()) { + try { + Field ret = result.get(); + ret.setAccessible(true); + return ret; + } catch(Throwable t) { + t.printStackTrace(); + } + } + + Log.warning("ClientAdvancementManager.advancementToProgress field could not be found"); + return null; + } + +} diff --git a/src/main/java/net/montoyo/wd/client/JSQueryDispatcher.java b/src/main/java/net/montoyo/wd/client/JSQueryDispatcher.java new file mode 100644 index 0000000..ba67596 --- /dev/null +++ b/src/main/java/net/montoyo/wd/client/JSQueryDispatcher.java @@ -0,0 +1,370 @@ +/* + * Copyright (C) 2018 BARBOTIN Nicolas + */ + +package net.montoyo.wd.client; + +import net.minecraft.client.Minecraft; +import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumFacing; +import net.minecraft.util.math.BlockPos; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import net.montoyo.mcef.api.IBrowser; +import net.montoyo.mcef.api.IJSQueryCallback; +import net.montoyo.wd.WebDisplays; +import net.montoyo.wd.block.BlockScreen; +import net.montoyo.wd.core.DefaultUpgrade; +import net.montoyo.wd.core.IScreenQueryHandler; +import net.montoyo.wd.core.IUpgrade; +import net.montoyo.wd.core.JSServerRequest; +import net.montoyo.wd.entity.TileEntityScreen; +import net.montoyo.wd.net.server.SMessageScreenCtrl; +import net.montoyo.wd.utilities.*; + +import java.util.*; + +@SideOnly(Side.CLIENT) +public final class JSQueryDispatcher { + + private static final class QueryData { + + private final IBrowser browser; + private final String query; + private final String args; + private final IJSQueryCallback callback; + + private QueryData(IBrowser b, String q, String a, IJSQueryCallback cb) { + browser = b; + query = q; + args = a; + callback = cb; + } + + } + + public static final class ServerQuery { + + private static int lastId = 0; + + private final TileEntityScreen tes; + private final BlockSide side; + private final IJSQueryCallback callback; + private final int id; + + private ServerQuery(TileEntityScreen t, BlockSide s, IJSQueryCallback cb) { + tes = t; + side = s; + callback = cb; + id = lastId++; + } + + public TileEntityScreen getTileEntity() { + return tes; + } + + public BlockSide getSide() { + return side; + } + + public TileEntityScreen.Screen getScreen() { + return tes.getScreen(side); + } + + public void success(String resp) { + callback.success(resp); + } + + public void error(int errId, String errStr) { + callback.failure(errId, errStr); + } + + } + + private final ClientProxy proxy; + private final ArrayDeque queue = new ArrayDeque<>(); + private final ClientProxy.ScreenSidePair lookupResult = new ClientProxy.ScreenSidePair(); + private final HashMap handlers = new HashMap<>(); + private final ArrayList serverQueries = new ArrayList<>(); + private final Minecraft mc = Minecraft.getMinecraft(); + + public JSQueryDispatcher(ClientProxy proxy) { + this.proxy = proxy; + registerDefaults(); + } + + public void enqueueQuery(IBrowser b, String q, String a, IJSQueryCallback cb) { + synchronized(queue) { + queue.offer(new QueryData(b, q, a, cb)); + } + } + + public void handleQueries() { + while(true) { + QueryData next; + synchronized(queue) { + next = queue.poll(); + } + + if(next == null) + break; + + if(proxy.findScreenFromBrowser(next.browser, lookupResult)) { + Object[] args = (next.args == null) ? new Object[0] : parseArgs(next.args); + + if(args == null) + next.callback.failure(400, "Malformed request parameters"); + else { + try { + handlers.get(next.query).handleQuery(next.callback, lookupResult.tes, lookupResult.side, args); + } catch(Throwable t) { + Log.warningEx("Could not execute JS query %s(%s)", t, next.query, (next.args == null) ? "" : next.args); + next.callback.failure(500, "Internal error"); + } + } + } else + next.callback.failure(403, "A screen is required"); + } + } + + public boolean canHandleQuery(String q) { + return handlers.containsKey(q); + } + + private static Object[] parseArgs(String args) { + ArrayList array = new ArrayList<>(); + int lastIdx = 0; + boolean inString = false; + boolean escape = false; + boolean hadString = false; + + for(int i = 0; i < args.length(); i++) { + char chr = args.charAt(i); + + if(inString) { + if(escape) + escape = false; + else { + if(chr == '\"') + inString = false; + else if(chr == '\\') + escape = true; + } + } else if(chr == '\"') { + if(hadString) + return null; + + inString = true; + hadString = true; + } else if(chr == ',') { + array.add(args.substring(lastIdx, i).trim()); + lastIdx = i + 1; + hadString = false; + } + } + + if(inString) + return null; //Non terminated string + + array.add(args.substring(lastIdx).trim()); + Object[] ret = new Object[array.size()]; + + for(int i = 0; i < ret.length; i++) { + String str = array.get(i); + if(str.isEmpty()) + return null; //Nah... + + if(str.charAt(0) == '\"') //String + ret[i] = str.substring(1, str.length() - 1); + else { + try { + ret[i] = Double.parseDouble(str); + } catch(NumberFormatException ex) { + return null; + } + } + } + + return ret; + } + + public void register(String query, IScreenQueryHandler handler) { + handlers.put(query.toLowerCase(), handler); + } + + public ServerQuery fulfillQuery(int id) { + int toRemove = -1; + + for(int i = 0; i < serverQueries.size(); i++) { + ServerQuery sq = serverQueries.get(i); + + if(sq.id == id) { + toRemove = i; + break; + } + } + + if(toRemove < 0) + return null; + else + return serverQueries.remove(toRemove); + } + + private void makeServerQuery(TileEntityScreen tes, BlockSide side, IJSQueryCallback cb, JSServerRequest type, Object ... data) { + ServerQuery ret = new ServerQuery(tes, side, cb); + serverQueries.add(ret); + + WebDisplays.NET_HANDLER.sendToServer(SMessageScreenCtrl.jsRequest(tes, side, ret.id, type, data)); + } + + private void registerDefaults() { + register("GetSize", (cb, tes, side, args) -> { + Vector2i size = tes.getScreen(side).size; + cb.success("{\"x\":" + size.x + ",\"y\":" + size.y + "}"); + }); + + register("GetRedstoneAt", (cb, tes, side, args) -> { + if(!tes.hasUpgrade(side, DefaultUpgrade.REDSTONE_INPUT)) { + cb.failure(403, "Missing upgrade"); + return; + } + + if(args.length == 2 && args[0] instanceof Double && args[1] instanceof Double) { + TileEntityScreen.Screen scr = tes.getScreen(side); + int x = ((Double) args[0]).intValue(); + int y = ((Double) args[1]).intValue(); + + if(x < 0 || x >= scr.size.x || y < 0 || y >= scr.size.y) + cb.failure(403, "Out of range"); + else { + BlockPos bpos = (new Vector3i(tes.getPos())).addMul(side.right, x).addMul(side.up, y).toBlock(); + int level = tes.getWorld().getBlockState(bpos).getValue(BlockScreen.emitting) ? 0 : tes.getWorld().getRedstonePower(bpos, EnumFacing.VALUES[side.reverse().ordinal()]); + cb.success("{\"level\":" + level + "}"); + } + } else + cb.failure(400, "Wrong arguments"); + }); + + register("GetRedstoneArray", (cb, tes, side, args) -> { + if(tes.hasUpgrade(side, DefaultUpgrade.REDSTONE_INPUT)) { + final EnumFacing facing = EnumFacing.VALUES[side.reverse().ordinal()]; + final StringJoiner resp = new StringJoiner(",", "{\"levels\":[", "]}"); + + tes.forEachScreenBlocks(side, bp -> { + if(tes.getWorld().getBlockState(bp).getValue(BlockScreen.emitting)) + resp.add("0"); + else + resp.add("" + tes.getWorld().getRedstonePower(bp, facing)); + }); + + cb.success(resp.toString()); + } else + cb.failure(403, "Missing upgrade"); + }); + + register("ClearRedstone", (cb, tes, side, args) -> { + if(tes.hasUpgrade(side, DefaultUpgrade.REDSTONE_OUTPUT)) { + if(tes.getScreen(side).owner.uuid.equals(mc.player.getGameProfile().getId())) + makeServerQuery(tes, side, cb, JSServerRequest.CLEAR_REDSTONE); + else + cb.success("{\"status\":\"notOwner\"}"); + } else + cb.failure(403, "Missing upgrade"); + }); + + register("SetRedstoneAt", (cb, tes, side, args) -> { + if(args.length != 3 || !Arrays.stream(args).allMatch((obj) -> obj instanceof Double)) { + cb.failure(400, "Wrong arguments"); + return; + } + + if(!tes.hasUpgrade(side, DefaultUpgrade.REDSTONE_OUTPUT)) { + cb.failure(403, "Missing upgrade"); + return; + } + + if(!tes.getScreen(side).owner.uuid.equals(mc.player.getGameProfile().getId())) { + cb.success("{\"status\":\"notOwner\"}"); + return; + } + + int x = ((Double) args[0]).intValue(); + int y = ((Double) args[1]).intValue(); + boolean state = ((Double) args[2]) > 0.0; + + Vector2i size = tes.getScreen(side).size; + if(x < 0 || x >= size.x || y < 0 || y >= size.y) { + cb.failure(403, "Out of range"); + return; + } + + makeServerQuery(tes, side, cb, JSServerRequest.SET_REDSTONE_AT, x, y, state); + }); + + register("IsEmitting", (cb, tes, side, args) -> { + if(!tes.hasUpgrade(side, DefaultUpgrade.REDSTONE_OUTPUT)) { + cb.failure(403, "Missing upgrade"); + return; + } + + if(args.length == 2 && args[0] instanceof Double && args[1] instanceof Double) { + TileEntityScreen.Screen scr = tes.getScreen(side); + int x = ((Double) args[0]).intValue(); + int y = ((Double) args[1]).intValue(); + + if(x < 0 || x >= scr.size.x || y < 0 || y >= scr.size.y) + cb.failure(403, "Out of range"); + else { + BlockPos bpos = (new Vector3i(tes.getPos())).addMul(side.right, x).addMul(side.up, y).toBlock(); + boolean e = tes.getWorld().getBlockState(bpos).getValue(BlockScreen.emitting); + cb.success("{\"emitting\":" + (e ? "true" : "false") + "}"); + } + } else + cb.failure(400, "Wrong arguments"); + }); + + register("GetEmissionArray", (cb, tes, side, args) -> { + if(tes.hasUpgrade(side, DefaultUpgrade.REDSTONE_OUTPUT)) { + final StringJoiner resp = new StringJoiner(",", "{\"emission\":[", "]}"); + tes.forEachScreenBlocks(side, bp -> resp.add(tes.getWorld().getBlockState(bp).getValue(BlockScreen.emitting) ? "1" : "0")); + cb.success(resp.toString()); + } else + cb.failure(403, "Missing upgrade"); + }); + + register("GetLocation", (cb, tes, side, args) -> { + if(!tes.hasUpgrade(side, DefaultUpgrade.GPS)) { + cb.failure(403, "Missing upgrade"); + return; + } + + BlockPos bp = tes.getPos(); + cb.success("{\"x\":" + bp.getX() + ",\"y\":" + bp.getY() + ",\"z\":" + bp.getZ() + ",\"side\":\"" + side + "\"}"); + }); + + register("GetUpgrades", (cb, tes, side, args) -> { + final StringBuilder sb = new StringBuilder("{\"upgrades\":["); + final ArrayList upgrades = tes.getScreen(side).upgrades; + + for(int i = 0; i < upgrades.size(); i++) { + if(i > 0) + sb.append(','); + + sb.append('\"'); + sb.append(Util.addSlashes(((IUpgrade) upgrades.get(i).getItem()).getJSName(upgrades.get(i)))); + sb.append('\"'); + } + + cb.success(sb.append("]}").toString()); + }); + + register("IsOwner", (cb, tes, side, args) -> { + boolean res = (tes.getScreen(side).owner != null && tes.getScreen(side).owner.uuid.equals(mc.player.getGameProfile().getId())); + cb.success("{\"isOwner\":" + (res ? "true}" : "false}")); + }); + + register("GetRotation", (cb, tes, side, args) -> cb.success("{\"rotation\":" + tes.getScreen(side).rotation.ordinal() + "}")); + register("GetSide", (cb, tes, side, args) -> cb.success("{\"side\":" + tes.getScreen(side).side.ordinal() + "}")); + } + +} diff --git a/src/main/java/net/montoyo/wd/client/ResourceModelPair.java b/src/main/java/net/montoyo/wd/client/ResourceModelPair.java new file mode 100644 index 0000000..c1f6664 --- /dev/null +++ b/src/main/java/net/montoyo/wd/client/ResourceModelPair.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2018 BARBOTIN Nicolas + */ + +package net.montoyo.wd.client; + +import net.minecraft.client.renderer.block.model.ModelResourceLocation; +import net.montoyo.wd.client.renderers.IModelBaker; + +public class ResourceModelPair { + + private final ModelResourceLocation resLoc; + private final IModelBaker model; + + public ResourceModelPair(ModelResourceLocation rl, IModelBaker m) { + resLoc = rl; + model = m; + } + + public ModelResourceLocation getResourceLocation() { + return resLoc; + } + + public IModelBaker getModel() { + return model; + } + +} diff --git a/src/main/java/net/montoyo/wd/client/StaticStateMapper.java b/src/main/java/net/montoyo/wd/client/StaticStateMapper.java new file mode 100644 index 0000000..ad5f825 --- /dev/null +++ b/src/main/java/net/montoyo/wd/client/StaticStateMapper.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2018 BARBOTIN Nicolas + */ + +package net.montoyo.wd.client; + +import net.minecraft.block.state.IBlockState; +import net.minecraft.client.renderer.block.model.ModelResourceLocation; +import net.minecraft.client.renderer.block.statemap.StateMapperBase; + +import javax.annotation.Nonnull; + +public class StaticStateMapper extends StateMapperBase { + + private final ModelResourceLocation resLoc; + + public StaticStateMapper(ModelResourceLocation rl) { + resLoc = rl; + } + + @Override + @Nonnull + protected ModelResourceLocation getModelResourceLocation(@Nonnull IBlockState state) { + return resLoc; + } + +} diff --git a/src/main/java/net/montoyo/wd/client/WDScheme.java b/src/main/java/net/montoyo/wd/client/WDScheme.java new file mode 100644 index 0000000..8baa669 --- /dev/null +++ b/src/main/java/net/montoyo/wd/client/WDScheme.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2018 BARBOTIN Nicolas + */ + +package net.montoyo.wd.client; + +import net.montoyo.mcef.api.IScheme; +import net.montoyo.mcef.api.ISchemeResponseData; +import net.montoyo.mcef.api.ISchemeResponseHeaders; +import net.montoyo.mcef.api.SchemePreResponse; +import net.montoyo.wd.WebDisplays; +import net.montoyo.wd.miniserv.Constants; +import net.montoyo.wd.miniserv.client.Client; +import net.montoyo.wd.miniserv.client.ClientTaskGetFile; +import net.montoyo.wd.utilities.Log; +import net.montoyo.wd.utilities.Util; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.UUID; + +public class WDScheme implements IScheme { + + private static final String ERROR_PAGE = "

%d %s


Miniserv powered by WebDisplays"; + private ClientTaskGetFile task; + private boolean isErrorPage; + + @Override + public SchemePreResponse processRequest(String url) { + url = url.substring("wd://".length()); + + int pos = url.indexOf('/'); + if(pos < 0) + return SchemePreResponse.NOT_HANDLED; + + String uuidStr = url.substring(0, pos); + String fileStr = url.substring(pos + 1); + + try { + fileStr = URLDecoder.decode(fileStr, "UTF-8"); + } catch(UnsupportedEncodingException ex) { + Log.warningEx("UTF-8 isn't supported... yeah... and I'm a billionaire...", ex); + } + + if(uuidStr.isEmpty() || Util.isFileNameInvalid(fileStr)) + return SchemePreResponse.NOT_HANDLED; + + UUID uuid; + try { + uuid = UUID.fromString(uuidStr); + } catch(IllegalArgumentException ex) { + return SchemePreResponse.NOT_HANDLED; //Invalid UUID + } + + task = new ClientTaskGetFile(uuid, fileStr); + return Client.getInstance().addTask(task) ? SchemePreResponse.HANDLED_CONTINUE : SchemePreResponse.NOT_HANDLED; + } + + @Override + public void getResponseHeaders(ISchemeResponseHeaders resp) { + Log.info("Waiting for response..."); + int status = task.waitForResponse(); + Log.info("Got response %d", status); + + if(status == 0) { + //OK + int extPos = task.getFileName().lastIndexOf('.'); + if(extPos >= 0) { + String mime = ((ClientProxy) WebDisplays.PROXY).getMCEF().mimeTypeFromExtension(task.getFileName().substring(extPos + 1)); + + if(mime != null) + resp.setMimeType(mime); + } + + resp.setStatus(200); + resp.setStatusText("OK"); + resp.setResponseLength(-1); + return; + } + + int errCode; + String errStr; + + if(status == Constants.GETF_STATUS_NOT_FOUND) { + errCode = 404; + errStr = "Not Found"; + } else { + errCode = 500; + errStr = "Internal Server Error"; + } + + resp.setStatus(errCode); + resp.setStatusText(errStr); + + try { + dataToWrite = String.format(ERROR_PAGE, errCode, errStr).getBytes("UTF-8"); + dataOffset = 0; + amountToWrite = dataToWrite.length; + isErrorPage = true; + resp.setResponseLength(amountToWrite); + } catch(UnsupportedEncodingException ex) { + resp.setResponseLength(0); + } + } + + private byte[] dataToWrite; + private int dataOffset; + private int amountToWrite; + + @Override + public boolean readResponse(ISchemeResponseData data) { + if(dataToWrite == null) { + if(isErrorPage) { + data.setAmountRead(0); + return false; + } + + dataToWrite = task.waitForData(); + dataOffset = 3; //packet ID + size + amountToWrite = task.getDataLength(); + + if(amountToWrite <= 0) { + dataToWrite = null; + data.setAmountRead(0); + return false; + } + } + + int toWrite = data.getBytesToRead(); + if(toWrite > amountToWrite) + toWrite = amountToWrite; + + System.arraycopy(dataToWrite, dataOffset, data.getDataArray(), 0, toWrite); + data.setAmountRead(toWrite); + + dataOffset += toWrite; + amountToWrite -= toWrite; + + if(amountToWrite <= 0) { + if(!isErrorPage) + task.nextData(); + + dataToWrite = null; + } + + return true; + } + +} diff --git a/src/main/java/net/montoyo/wd/client/gui/CommandHandler.java b/src/main/java/net/montoyo/wd/client/gui/CommandHandler.java new file mode 100644 index 0000000..ed4f299 --- /dev/null +++ b/src/main/java/net/montoyo/wd/client/gui/CommandHandler.java @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2018 BARBOTIN Nicolas + */ + +package net.montoyo.wd.client.gui; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface CommandHandler { + + String value(); + +} diff --git a/src/main/java/net/montoyo/wd/client/gui/GuiKeyboard.java b/src/main/java/net/montoyo/wd/client/gui/GuiKeyboard.java new file mode 100644 index 0000000..e4d5739 --- /dev/null +++ b/src/main/java/net/montoyo/wd/client/gui/GuiKeyboard.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2019 BARBOTIN Nicolas + */ + +package net.montoyo.wd.client.gui; + +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.BlockPos; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import net.montoyo.wd.WebDisplays; +import net.montoyo.wd.client.gui.controls.Button; +import net.montoyo.wd.client.gui.controls.Control; +import net.montoyo.wd.client.gui.controls.Label; +import net.montoyo.wd.client.gui.loading.FillControl; +import net.montoyo.wd.entity.TileEntityScreen; +import net.montoyo.wd.net.server.SMessageScreenCtrl; +import net.montoyo.wd.utilities.BlockSide; +import net.montoyo.wd.utilities.Log; +import net.montoyo.wd.utilities.TypeData; +import net.montoyo.wd.utilities.Util; +import org.lwjgl.input.Keyboard; + +import java.io.*; +import java.util.ArrayList; +import java.util.Map; + +@SideOnly(Side.CLIENT) +public class GuiKeyboard extends WDScreen { + + private static final String WARNING_FNAME = "wd_keyboard_warning.txt"; + + private TileEntityScreen tes; + private BlockSide side; + private final ArrayList evStack = new ArrayList<>(); + private BlockPos kbPos; + private boolean showWarning = true; + + @FillControl + private Label lblInfo; + + @FillControl + private Button btnOk; + + public GuiKeyboard() { + } + + public GuiKeyboard(TileEntityScreen tes, BlockSide side, BlockPos kbPos) { + this.tes = tes; + this.side = side; + this.kbPos = kbPos; + } + + @Override + protected void addLoadCustomVariables(Map vars) { + vars.put("showWarning", showWarning ? 1.0 : 0.0); + } + + @Override + public void initGui() { + super.initGui(); + + if(mc.isIntegratedServerRunning() && mc.getIntegratedServer() != null && !mc.getIntegratedServer().getPublic()) + showWarning = false; //NO NEED + else + showWarning = !hasUserReadWarning(); + + loadFrom(new ResourceLocation("webdisplays", "gui/keyboard.json")); + + if(showWarning) { + int maxLabelW = 0; + int totalH = 0; + + for(Control ctrl : controls) { + if(ctrl != lblInfo && ctrl instanceof Label) { + if(ctrl.getWidth() > maxLabelW) + maxLabelW = ctrl.getWidth(); + + totalH += ctrl.getHeight(); + ctrl.setPos((width - ctrl.getWidth()) / 2, 0); + } + } + + btnOk.setWidth(maxLabelW); + btnOk.setPos((width - maxLabelW) / 2, 0); + totalH += btnOk.getHeight(); + + int y = (height - totalH) / 2; + for(Control ctrl : controls) { + if(ctrl != lblInfo) { + ctrl.setPos(ctrl.getX(), y); + y += ctrl.getHeight(); + } + } + } else { + mc.inGameHasFocus = true; + mc.mouseHelper.grabMouseCursor(); + } + + defaultBackground = showWarning; + syncTicks = 5; + } + + @Override + public void handleInput() { + if(showWarning) { + try { + super.handleInput(); + } catch(IOException ex) { + Log.warningEx("Caught exception while handling screen input", ex); + } + + return; + } + + if(Keyboard.isCreated()) { + while(Keyboard.next()) { + if(Keyboard.getEventKey() == Keyboard.KEY_ESCAPE) + mc.displayGuiScreen(null); + else { + char chr = Keyboard.getEventCharacter(); + + if(Keyboard.getEventKeyState()) { + int kc = Keyboard.getEventKey(); + + evStack.add(new TypeData(TypeData.Action.PRESS, kc, chr)); + evStack.add(new TypeData(TypeData.Action.RELEASE, kc, chr)); + } + + if(chr != 0) + evStack.add(new TypeData(TypeData.Action.TYPE, 0, chr)); + } + } + + if(!evStack.isEmpty() && !syncRequested()) + requestSync(); + } + } + + @Override + protected void sync() { + if(!evStack.isEmpty()) { + WebDisplays.NET_HANDLER.sendToServer(SMessageScreenCtrl.type(tes, side, WebDisplays.GSON.toJson(evStack), kbPos)); + evStack.clear(); + } + } + + @GuiSubscribe + public void onClick(Button.ClickEvent ev) { + if(showWarning && ev.getSource() == btnOk) { + writeUserAcknowledge(); + + for(Control ctrl: controls) { + if(ctrl instanceof Label) { + Label lbl = (Label) ctrl; + lbl.setVisible(!lbl.isVisible()); + } + } + + btnOk.setDisabled(true); + btnOk.setVisible(false); + showWarning = false; + defaultBackground = false; + mc.inGameHasFocus = true; + mc.mouseHelper.grabMouseCursor(); + } + } + + private boolean hasUserReadWarning() { + try { + File f = new File(mc.mcDataDir, WARNING_FNAME); + + if(f.exists()) { + BufferedReader br = new BufferedReader(new FileReader(f)); + String str = br.readLine(); + Util.silentClose(br); + + return str != null && str.trim().equalsIgnoreCase("read"); + } + } catch(Throwable t) { + Log.warningEx("Can't know if user has already read the warning", t); + } + + return false; + } + + private void writeUserAcknowledge() { + try { + File f = new File(mc.mcDataDir, WARNING_FNAME); + + BufferedWriter bw = new BufferedWriter(new FileWriter(f)); + bw.write("read\n"); + Util.silentClose(bw); + } catch(Throwable t) { + Log.warningEx("Can't write that the user read the warning", t); + } + } + + @Override + public boolean isForBlock(BlockPos bp, BlockSide side) { + return bp.equals(kbPos) || (bp.equals(tes.getPos()) && side == this.side); + } + +} diff --git a/src/main/java/net/montoyo/wd/client/gui/GuiMinePad.java b/src/main/java/net/montoyo/wd/client/gui/GuiMinePad.java new file mode 100644 index 0000000..60d4bf0 --- /dev/null +++ b/src/main/java/net/montoyo/wd/client/gui/GuiMinePad.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2019 BARBOTIN Nicolas + */ + +package net.montoyo.wd.client.gui; + +import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.util.math.BlockPos; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import net.montoyo.wd.WebDisplays; +import net.montoyo.wd.client.ClientProxy; +import net.montoyo.wd.utilities.BlockSide; +import org.lwjgl.input.Keyboard; +import org.lwjgl.input.Mouse; + +import static org.lwjgl.opengl.GL11.*; + +@SideOnly(Side.CLIENT) +public class GuiMinePad extends WDScreen { + + private ClientProxy.PadData pad; + private double vx; + private double vy; + private double vw; + private double vh; + + public GuiMinePad() { + } + + public GuiMinePad(ClientProxy.PadData pad) { + this.pad = pad; + } + + @Override + public void initGui() { + super.initGui(); + + vw = ((double) width) - 32.0f; + vh = vw / WebDisplays.PAD_RATIO; + vx = 16.0f; + vy = (((double) height) - vh) / 2.0f; + } + + private static void addRect(BufferBuilder bb, double x, double y, double w, double h) { + bb.pos(x, y, 0.0).endVertex(); + bb.pos(x + w, y, 0.0).endVertex(); + bb.pos(x + w, y + h, 0.0).endVertex(); + bb.pos(x, y + h, 0.0).endVertex(); + } + + @Override + public void drawScreen(int mouseX, int mouseY, float ptt) { + drawDefaultBackground(); + + glDisable(GL_TEXTURE_2D); + glDisable(GL_CULL_FACE); + glColor4f(0.73f, 0.73f, 0.73f, 1.0f); + + Tessellator t = Tessellator.getInstance(); + BufferBuilder bb = t.getBuffer(); + bb.begin(GL_QUADS, DefaultVertexFormats.POSITION); + addRect(bb, vx, vy - 16, vw, 16); + addRect(bb, vx, vy + vh, vw, 16); + addRect(bb, vx - 16, vy, 16, vh); + addRect(bb, vx + vw, vy, 16, vh); + t.draw(); + + glEnable(GL_TEXTURE_2D); + + if(pad.view != null) { + glColor4f(1.0f, 1.0f, 1.0f, 1.0f); + pad.view.draw(vx, vy + vh, vx + vw, vy); + } + + glEnable(GL_CULL_FACE); + } + + @Override + public void handleInput() { + while(Keyboard.next()) { + char key = Keyboard.getEventCharacter(); + int keycode = Keyboard.getEventKey(); + boolean pressed = Keyboard.getEventKeyState(); + + if(Keyboard.getEventKey() == Keyboard.KEY_ESCAPE) { + mc.displayGuiScreen(null); + return; + } + + if(pad.view != null) { + if(pressed) + pad.view.injectKeyPressedByKeyCode(keycode, key, 0); + else + pad.view.injectKeyReleasedByKeyCode(keycode, key, 0); + + if(key != 0) + pad.view.injectKeyTyped(key, 0); + } + } + + int vx = screen2DisplayX((int) this.vx); + int vy = screen2DisplayY((int) this.vy); + int vh = screen2DisplayX((int) this.vh); + int vw = screen2DisplayY((int) this.vw); + + while(Mouse.next()) { + int btn = Mouse.getEventButton(); + boolean pressed = Mouse.getEventButtonState(); + int sx = Mouse.getEventX(); + int sy = Mouse.getEventY(); + + if(pad.view != null && sx >= vx && sx <= vx + vw && sy >= vy && sy <= vy + vh) { + sx -= vx; + sy -= vy; + sy = vh - sy; + + //Scale again according to the webview + sx = (int) (((double) sx) / ((double) vw) * WebDisplays.INSTANCE.padResX); + sy = (int) (((double) sy) / ((double) vh) * WebDisplays.INSTANCE.padResY); + + if(btn == -1) + pad.view.injectMouseMove(sx, sy, 0, false); + else + pad.view.injectMouseButton(sx, sy, 0, btn + 1, pressed, 1); + } + } + } + + @Override + public void updateScreen() { + if(pad.view == null) + mc.displayGuiScreen(null); //In case the user dies with the pad in the hand + } + + @Override + public boolean isForBlock(BlockPos bp, BlockSide side) { + return false; + } + +} diff --git a/src/main/java/net/montoyo/wd/client/gui/GuiRedstoneCtrl.java b/src/main/java/net/montoyo/wd/client/gui/GuiRedstoneCtrl.java new file mode 100644 index 0000000..823a5eb --- /dev/null +++ b/src/main/java/net/montoyo/wd/client/gui/GuiRedstoneCtrl.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2019 BARBOTIN Nicolas + */ + +package net.montoyo.wd.client.gui; + +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.BlockPos; +import net.montoyo.mcef.api.API; +import net.montoyo.wd.client.ClientProxy; +import net.montoyo.wd.WebDisplays; +import net.montoyo.wd.client.gui.controls.Button; +import net.montoyo.wd.client.gui.controls.TextField; +import net.montoyo.wd.client.gui.loading.FillControl; +import net.montoyo.wd.net.server.SMessageRedstoneCtrl; +import net.montoyo.wd.utilities.BlockSide; +import net.montoyo.wd.utilities.Util; +import net.montoyo.wd.utilities.Vector3i; + +import javax.annotation.Nullable; + +public class GuiRedstoneCtrl extends WDScreen { + + private int dimension; + private Vector3i pos; + private String risingEdgeURL; + private String fallingEdgeURL; + + @FillControl + private TextField tfRisingEdge; + + @FillControl + private TextField tfFallingEdge; + + @FillControl + private Button btnOk; + + public GuiRedstoneCtrl() { + } + + public GuiRedstoneCtrl(int d, Vector3i p, String r, String f) { + dimension = d; + pos = p; + risingEdgeURL = r; + fallingEdgeURL = f; + } + + @Override + public void initGui() { + super.initGui(); + loadFrom(new ResourceLocation("webdisplays", "gui/redstonectrl.json")); + tfRisingEdge.setText(risingEdgeURL); + tfFallingEdge.setText(fallingEdgeURL); + } + + @GuiSubscribe + public void onClick(Button.ClickEvent ev) { + if(ev.getSource() == btnOk) { + API mcef = ((ClientProxy) WebDisplays.PROXY).getMCEF(); + + String rising = mcef.punycode(Util.addProtocol(tfRisingEdge.getText())); + String falling = mcef.punycode(Util.addProtocol(tfFallingEdge.getText())); + WebDisplays.NET_HANDLER.sendToServer(new SMessageRedstoneCtrl(dimension, pos, rising, falling)); + } + + mc.displayGuiScreen(null); + } + + @Override + public boolean isForBlock(BlockPos bp, BlockSide side) { + return pos.equalsBlockPos(bp); + } + + @Nullable + @Override + public String getWikiPageName() { + return "Redstone_Controller"; + } + +} diff --git a/src/main/java/net/montoyo/wd/client/gui/GuiScreenConfig.java b/src/main/java/net/montoyo/wd/client/gui/GuiScreenConfig.java new file mode 100644 index 0000000..473b925 --- /dev/null +++ b/src/main/java/net/montoyo/wd/client/gui/GuiScreenConfig.java @@ -0,0 +1,503 @@ +/* + * Copyright (C) 2018 BARBOTIN Nicolas + */ + +package net.montoyo.wd.client.gui; + +import net.minecraft.client.audio.PositionedSoundRecord; +import net.minecraft.client.resources.I18n; +import net.minecraft.item.ItemStack; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.BlockPos; +import net.montoyo.wd.WebDisplays; +import net.montoyo.wd.client.gui.controls.*; +import net.montoyo.wd.client.gui.loading.FillControl; +import net.montoyo.wd.core.ScreenRights; +import net.montoyo.wd.entity.TileEntityScreen; +import net.montoyo.wd.item.WDItem; +import net.montoyo.wd.net.server.SMessageScreenCtrl; +import net.montoyo.wd.utilities.*; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.UUID; + +public class GuiScreenConfig extends WDScreen { + + //Screen data + private final TileEntityScreen tes; + private final BlockSide side; + private NameUUIDPair owner; + private NameUUIDPair[] friends; + private int friendRights; + private int otherRights; + private Rotation rotation = Rotation.ROT_0; + private float aspectRatio; + + //Autocomplete handling + private boolean waitingAC; + private int acFailTicks = -1; + + private final ArrayList acResults = new ArrayList<>(); + private boolean adding; + + //Controls + @FillControl + private Label lblOwner; + + @FillControl + private List lstFriends; + + @FillControl + private Button btnAdd; + + @FillControl + private TextField tfFriend; + + @FillControl + private TextField tfResX; + + @FillControl + private TextField tfResY; + + @FillControl + private ControlGroup grpFriends; + + @FillControl + private ControlGroup grpOthers; + + @FillControl + private CheckBox boxFSetUrl; + + @FillControl + private CheckBox boxFClick; + + @FillControl + private CheckBox boxFFriends; + + @FillControl + private CheckBox boxFOthers; + + @FillControl + private CheckBox boxFUpgrades; + + @FillControl + private CheckBox boxFResolution; + + @FillControl + private CheckBox boxOSetUrl; + + @FillControl + private CheckBox boxOClick; + + @FillControl + private CheckBox boxOUpgrades; + + @FillControl + private CheckBox boxOResolution; + + @FillControl + private Button btnSetRes; + + @FillControl + private UpgradeGroup ugUpgrades; + + @FillControl + private Button btnChangeRot; + + @FillControl + private CheckBox cbLockRatio; + + @FillControl + private CheckBox cbAutoVolume; + + private CheckBox[] friendBoxes; + private CheckBox[] otherBoxes; + + public GuiScreenConfig(TileEntityScreen tes, BlockSide side, NameUUIDPair[] friends, int fr, int or) { + this.tes = tes; + this.side = side; + this.friends = friends; + friendRights = fr; + otherRights = or; + } + + @Override + public void initGui() { + super.initGui(); + loadFrom(new ResourceLocation("webdisplays", "gui/screencfg.json")); + + friendBoxes = new CheckBox[] { boxFResolution, boxFUpgrades, boxFOthers, boxFFriends, boxFClick, boxFSetUrl }; + boxFResolution.setUserdata(ScreenRights.CHANGE_RESOLUTION); + boxFUpgrades.setUserdata(ScreenRights.MANAGE_UPGRADES); + boxFOthers.setUserdata(ScreenRights.MANAGE_OTHER_RIGHTS); + boxFFriends.setUserdata(ScreenRights.MANAGE_FRIEND_LIST); + boxFClick.setUserdata(ScreenRights.CLICK); + boxFSetUrl.setUserdata(ScreenRights.CHANGE_URL); + + otherBoxes = new CheckBox[] { boxOResolution, boxOUpgrades, boxOClick, boxOSetUrl }; + boxOResolution.setUserdata(ScreenRights.CHANGE_RESOLUTION); + boxOUpgrades.setUserdata(ScreenRights.MANAGE_UPGRADES); + boxOClick.setUserdata(ScreenRights.CLICK); + boxOSetUrl.setUserdata(ScreenRights.CHANGE_URL); + + TileEntityScreen.Screen scr = tes.getScreen(side); + if(scr != null) { + owner = scr.owner; + rotation = scr.rotation; + + tfResX.setText("" + scr.resolution.x); + tfResY.setText("" + scr.resolution.y); + aspectRatio = ((float) scr.resolution.x) / ((float) scr.resolution.y); + + //Hopefully upgrades have been synchronized... + ugUpgrades.setUpgrades(scr.upgrades); + cbAutoVolume.setChecked(scr.autoVolume); + } + + if(owner == null) + owner = new NameUUIDPair("???", UUID.randomUUID()); + + lblOwner.setLabel(lblOwner.getLabel() + ' ' + owner.name); + for(NameUUIDPair f : friends) + lstFriends.addElementRaw(f.name, f); + + lstFriends.updateContent(); + updateRights(friendRights, friendRights, friendBoxes, true); + updateRights(otherRights, otherRights, otherBoxes, true); + updateMyRights(); + updateRotationStr(); + + mc.getSoundHandler().playSound(PositionedSoundRecord.getRecord(WebDisplays.INSTANCE.soundScreenCfg, 1.0f, 1.0f)); + } + + private void updateRotationStr() { + btnChangeRot.setLabel(I18n.format("webdisplays.gui.screencfg.rot" + rotation.getAngleAsInt())); + } + + private void addFriend(String name) { + if(!name.isEmpty()) { + requestAutocomplete(name, true); + tfFriend.setDisabled(true); + adding = true; + waitingAC = true; + } + } + + private void clickSetRes() { + TileEntityScreen.Screen scr = tes.getScreen(side); + if(scr == null) + return; //WHATDAFUQ? + + try { + int x = Integer.parseInt(tfResX.getText()); + int y = Integer.parseInt(tfResY.getText()); + if(x < 1 || y < 1) + throw new NumberFormatException(); //I'm lazy + + if(x != scr.resolution.x || y != scr.resolution.y) + WebDisplays.NET_HANDLER.sendToServer(SMessageScreenCtrl.vec2(tes, side, SMessageScreenCtrl.CTRL_SET_RESOLUTION, new Vector2i(x, y))); + } catch(NumberFormatException ex) { + //Roll back + tfResX.setText("" + scr.resolution.x); + tfResY.setText("" + scr.resolution.y); + } + + btnSetRes.setDisabled(true); + } + + @GuiSubscribe + public void onClick(Button.ClickEvent ev) { + if(ev.getSource() == btnAdd && !waitingAC) + addFriend(tfFriend.getText().trim()); + else if(ev.getSource() == btnSetRes) + clickSetRes(); + else if(ev.getSource() == btnChangeRot) { + Rotation[] rots = Rotation.values(); + WebDisplays.NET_HANDLER.sendToServer(new SMessageScreenCtrl(tes, side, rots[(rotation.ordinal() + 1) % rots.length])); + } + } + + @GuiSubscribe + public void onEnterPressed(TextField.EnterPressedEvent ev) { + if(ev.getSource() == tfFriend && !waitingAC) + addFriend(ev.getText().trim()); + else if((ev.getSource() == tfResX || ev.getSource() == tfResY) && !btnSetRes.isDisabled()) + clickSetRes(); + } + + @GuiSubscribe + public void onAutocomplete(TextField.TabPressedEvent ev) { + if(ev.getSource() == tfFriend && !waitingAC && !ev.getBeginning().isEmpty()) { + if(acResults.isEmpty()) { + waitingAC = true; + requestAutocomplete(ev.getBeginning(), false); + } else { + NameUUIDPair pair = acResults.remove(0); + tfFriend.setText(pair.name); + } + } else if(ev.getSource() == tfResX) { + tfResX.setFocused(false); + tfResY.focus(); + tfResY.getMcField().setCursorPositionZero(); + tfResY.getMcField().setSelectionPos(tfResY.getText().length()); + } + } + + @GuiSubscribe + public void onTextChanged(TextField.TextChangedEvent ev) { + if(ev.getSource() == tfResX || ev.getSource() == tfResY) { + for(int i = 0; i < ev.getNewContent().length(); i++) { + if(!Character.isDigit(ev.getNewContent().charAt(i))) { + ev.getSource().setText(ev.getOldContent()); + return; + } + } + + if(cbLockRatio.isChecked()) { + if(ev.getSource() == tfResX) { + try { + float val = (float) Integer.parseInt(ev.getNewContent()); + val /= aspectRatio; + tfResY.setText("" + ((int) val)); + } catch(NumberFormatException ex) {} + } else { + try { + float val = (float) Integer.parseInt(ev.getNewContent()); + val *= aspectRatio; + tfResX.setText("" + ((int) val)); + } catch(NumberFormatException ex) {} + } + } + + btnSetRes.setDisabled(false); + } + } + + @GuiSubscribe + public void onRemovePlayer(List.EntryClick ev) { + if(ev.getSource() == lstFriends) + WebDisplays.NET_HANDLER.sendToServer(new SMessageScreenCtrl(tes, side, (NameUUIDPair) ev.getUserdata(), true)); + } + + @GuiSubscribe + public void onCheckboxChanged(CheckBox.CheckedEvent ev) { + if(isFriendCheckbox(ev.getSource())) { + int flag = (Integer) ev.getSource().getUserdata(); + if(ev.isChecked()) + friendRights |= flag; + else + friendRights &= ~flag; + + requestSync(); + } else if(isOtherCheckbox(ev.getSource())) { + int flag = (Integer) ev.getSource().getUserdata(); + if(ev.isChecked()) + otherRights |= flag; + else + otherRights &= ~flag; + + requestSync(); + } else if(ev.getSource() == cbLockRatio && ev.isChecked()) { + try { + int x = Integer.parseInt(tfResX.getText()); + int y = Integer.parseInt(tfResY.getText()); + + aspectRatio = ((float) x) / ((float) y); + } catch(NumberFormatException ex) { + cbLockRatio.setChecked(false); + } + } else if(ev.getSource() == cbAutoVolume) + WebDisplays.NET_HANDLER.sendToServer(SMessageScreenCtrl.autoVol(tes, side, ev.isChecked())); + } + + @GuiSubscribe + public void onRemoveUpgrade(UpgradeGroup.ClickEvent ev) { + WebDisplays.NET_HANDLER.sendToServer(new SMessageScreenCtrl(tes, side, ev.getMouseOverStack())); + } + + public boolean isFriendCheckbox(CheckBox cb) { + return Arrays.stream(friendBoxes).anyMatch(fb -> cb == fb); + } + + public boolean isOtherCheckbox(CheckBox cb) { + return Arrays.stream(otherBoxes).anyMatch(ob -> cb == ob); + } + + public boolean hasFriend(NameUUIDPair f) { + return Arrays.stream(friends).anyMatch(f::equals); + } + + @Override + public void onAutocompleteResult(NameUUIDPair pairs[]) { + waitingAC = false; + + if(adding) { + if(!hasFriend(pairs[0])) + WebDisplays.NET_HANDLER.sendToServer(new SMessageScreenCtrl(tes, side, pairs[0], false)); + + tfFriend.setDisabled(false); + tfFriend.clear(); + tfFriend.focus(); + adding = false; + } else { + acResults.clear(); + acResults.addAll(Arrays.asList(pairs)); + + NameUUIDPair pair = acResults.remove(0); + tfFriend.setText(pair.name); + } + } + + @Override + public void onAutocompleteFailure() { + waitingAC = false; + acResults.clear(); + acFailTicks = 0; + tfFriend.setTextColor(Control.COLOR_RED); + + if(adding) { + tfFriend.setDisabled(false); + adding = false; + } + } + + @Override + public void updateScreen() { + super.updateScreen(); + + if(acFailTicks >= 0) { + if(++acFailTicks >= 10) { + acFailTicks = -1; + tfFriend.setTextColor(TextField.DEFAULT_TEXT_COLOR); + } + } + } + + public void updateFriends(NameUUIDPair[] friends) { + boolean diff = false; + if(friends.length != this.friends.length) + diff = true; + else { + for(NameUUIDPair pair : friends) { + if(!hasFriend(pair)) { + diff = true; + break; + } + } + } + + if(diff) { + this.friends = friends; + lstFriends.clearRaw(); + for(NameUUIDPair pair : friends) + lstFriends.addElementRaw(pair.name, pair); + + lstFriends.updateContent(); + } + } + + private int updateRights(int current, int newVal, CheckBox[] boxes, boolean force) { + if(force || current != newVal) { + for(CheckBox box : boxes) { + int flag = (Integer) box.getUserdata(); + box.setChecked((newVal & flag) != 0); + } + + if(!force) { + Log.info("Screen check boxes were updated"); + abortSync(); //Value changed by another user, abort modifications by local user + } + } + + return newVal; + } + + public void updateFriendRights(int rights) { + friendRights = updateRights(friendRights, rights, friendBoxes, false); + } + + public void updateOtherRights(int rights) { + otherRights = updateRights(otherRights, rights, otherBoxes, false); + } + + @Override + protected void sync() { + WebDisplays.NET_HANDLER.sendToServer(new SMessageScreenCtrl(tes, side, friendRights, otherRights)); + Log.info("Sent sync packet"); + } + + public void updateMyRights() { + NameUUIDPair me = new NameUUIDPair(mc.player.getGameProfile()); + int myRights; + boolean clientIsOwner = false; + + if(me.equals(owner)) { + myRights = ScreenRights.ALL; + clientIsOwner = true; + } else if(hasFriend(me)) + myRights = friendRights; + else + myRights = otherRights; + + //Disable components according to client rights + grpFriends.setDisabled(!clientIsOwner); + + boolean flag = (myRights & ScreenRights.MANAGE_FRIEND_LIST) == 0; + lstFriends.setDisabled(flag); + tfFriend.setDisabled(flag); + btnAdd.setDisabled(flag); + + flag = (myRights & ScreenRights.MANAGE_OTHER_RIGHTS) == 0; + grpOthers.setDisabled(flag); + + flag = (myRights & ScreenRights.CHANGE_RESOLUTION) == 0; + tfResX.setDisabled(flag); + tfResY.setDisabled(flag); + btnChangeRot.setDisabled(flag); + + if(flag) + btnSetRes.setDisabled(true); + + flag = (myRights & ScreenRights.MANAGE_UPGRADES) == 0; + ugUpgrades.setDisabled(flag); + cbAutoVolume.setDisabled(flag); + } + + public void updateResolution(Vector2i res) { + aspectRatio = ((float) res.x) / ((float) res.y); + tfResX.setText("" + res.x); + tfResY.setText("" + res.y); + btnSetRes.setDisabled(true); + } + + public void updateRotation(Rotation rot) { + rotation = rot; + updateRotationStr(); + } + + public void updateAutoVolume(boolean av) { + cbAutoVolume.setChecked(av); + } + + @Override + public boolean isForBlock(BlockPos bp, BlockSide side) { + return bp.equals(tes.getPos()) && side == this.side; + } + + @Nullable + @Override + public String getWikiPageName() { + ItemStack is = ugUpgrades.getMouseOverUpgrade(); + if(is != null) { + if(is.getItem() instanceof WDItem) + return ((WDItem) is.getItem()).getWikiName(is); + else + return null; + } + + return "Screen_Configurator"; + } + +} diff --git a/src/main/java/net/montoyo/wd/client/gui/GuiServer.java b/src/main/java/net/montoyo/wd/client/gui/GuiServer.java new file mode 100644 index 0000000..51a4213 --- /dev/null +++ b/src/main/java/net/montoyo/wd/client/gui/GuiServer.java @@ -0,0 +1,711 @@ +/* + * Copyright (C) 2018 BARBOTIN Nicolas + */ + +package net.montoyo.wd.client.gui; + +import net.minecraft.client.audio.ISound; +import net.minecraft.client.audio.PositionedSoundRecord; +import net.minecraft.client.renderer.BufferBuilder; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.vertex.DefaultVertexFormats; +import net.minecraft.client.resources.I18n; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.SoundCategory; +import net.minecraft.util.math.BlockPos; +import net.montoyo.wd.WebDisplays; +import net.montoyo.wd.miniserv.Constants; +import net.montoyo.wd.miniserv.client.*; +import net.montoyo.wd.utilities.*; +import org.lwjgl.input.Keyboard; + +import javax.annotation.Nullable; +import javax.swing.filechooser.FileSystemView; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.text.Collator; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; + +import static org.lwjgl.opengl.GL11.*; + +public class GuiServer extends WDScreen { + + private static final ResourceLocation BG_IMAGE = new ResourceLocation("webdisplays", "textures/gui/server_bg.png"); + private static final ResourceLocation FG_IMAGE = new ResourceLocation("webdisplays", "textures/gui/server_fg.png"); + private static final HashMap COMMAND_MAP = new HashMap<>(); + private static final int MAX_LINE_LEN = 32; + private static final int MAX_LINES = 12; + + private final Vector3i serverPos; + private final NameUUIDPair owner; + private final ArrayList lines = new ArrayList<>(); + private String prompt = ""; + private String userPrompt; + private int blinkTime; + private String lastCmd; + private boolean promptLocked; + private volatile long queryTime; + private ClientTask currentTask; + private int selectedLine = -1; + + //Access command + private int accessTrials; + private int accessTime; + private int accessState = -1; + private PositionedSoundRecord accessSound; + + //Upload wizard + private boolean uploadWizard; + private File uploadDir; + private final ArrayList uploadFiles = new ArrayList<>(); + private int uploadOffset; + private boolean uploadFirstIsParent; + private String uploadFilter = ""; + private long uploadFilterTime; + + public GuiServer(Vector3i vec, NameUUIDPair owner) { + serverPos = vec; + this.owner = owner; + //userPrompt = owner.name + "@miniserv$ "; + userPrompt = "> "; + + if(COMMAND_MAP.isEmpty()) + buildCommandMap(); + + lines.add("MiniServ 1.0"); + lines.add(tr("info")); + uploadCD(FileSystemView.getFileSystemView().getDefaultDirectory()); + } + + private static String tr(String key, Object ... args) { + return I18n.format("webdisplays.server." + key, args); + } + + @Override + public void drawScreen(int mouseX, int mouseY, float ptt) { + super.drawScreen(mouseX, mouseY, ptt); + + int x = (width - 256) / 2; + int y = (height - 176) / 2; + + GlStateManager.enableTexture2D(); + mc.renderEngine.bindTexture(BG_IMAGE); + GlStateManager.color(1.0f, 1.0f, 1.0f, 1.0f); + drawTexturedModalRect(x, y, 0, 0, 256, 176); + + x += 18; + y += 18; + + for(int i = 0; i < lines.size(); i++) { + if(selectedLine == i) { + drawWhiteQuad(x - 1, y - 2, fontRenderer.getStringWidth(lines.get(i)) + 1, 12); + fontRenderer.drawString(lines.get(i), x, y, 0xFF129700, false); + } else + fontRenderer.drawString(lines.get(i), x, y, 0xFFFFFFFF, false); + + y += 12; + } + + if(!promptLocked) { + x = fontRenderer.drawString(userPrompt, x, y, 0xFFFFFFFF, false); + x = fontRenderer.drawString(prompt, x, y, 0xFFFFFFFF, false); + } + + if(!uploadWizard && blinkTime < 5) + drawWhiteQuad(x + 1, y, 6, 8); + + GlStateManager.disableAlpha(); + GlStateManager.enableTexture2D(); + GlStateManager.enableBlend(); + GlStateManager.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA); + mc.renderEngine.bindTexture(FG_IMAGE); + GlStateManager.color(1.0f, 1.0f, 1.0f, 1.0f); + drawTexturedModalRect((width - 256) / 2, (height - 176) / 2, 0, 0, 256, 176); + GlStateManager.enableAlpha(); + } + + private void drawWhiteQuad(int x, int y, int w, int h) { + double xd = (double) x; + double xd2 = (double) (x + w); + double yd = (double) y; + double yd2 = (double) (y + h); + double zd = (double) zLevel; + + GlStateManager.disableTexture2D(); + GlStateManager.color(1.0f, 1.0f, 1.0f, 1.0f); + Tessellator t = Tessellator.getInstance(); + BufferBuilder bb = t.getBuffer(); + bb.begin(GL_QUADS, DefaultVertexFormats.POSITION); + bb.pos(xd, yd2, zd).endVertex(); + bb.pos(xd2, yd2, zd).endVertex(); + bb.pos(xd2, yd, zd).endVertex(); + bb.pos(xd, yd, zd).endVertex(); + t.draw(); + GlStateManager.enableTexture2D(); + } + + @Override + public void updateScreen() { + super.updateScreen(); + + if(accessState >= 0) { + if(--accessTime <= 0) { + accessState++; + + if(accessState == 1) { + if(lines.size() > 0) + lines.remove(lines.size() - 1); + + lines.add("access: PERMISSION DENIED....and..."); + accessTime = 20; + } else { + if(accessSound == null) { + accessSound = new PositionedSoundRecord(WebDisplays.INSTANCE.soundServer.getSoundName(), SoundCategory.MASTER, 1.0f, 1.0f, true, 0, ISound.AttenuationType.NONE, 0.0f, 0.0f, 0.0f); + mc.getSoundHandler().playSound(accessSound); + } + + writeLine("YOU DIDN'T SAY THE MAGIC WORD!"); + accessTime = 2; + } + } + } else { + blinkTime = (blinkTime + 1) % 10; + + if(currentTask != null) { + long queryTime; + synchronized(this) { + queryTime = this.queryTime; + } + + if(System.currentTimeMillis() - queryTime >= 10000) { + writeLine(tr("timeout")); + currentTask.cancel(); + clearTask(); + } + } + + if(!uploadFilter.isEmpty() && System.currentTimeMillis() - uploadFilterTime >= 1000) { + Log.info("Upload filter cleared"); + uploadFilter = ""; + } + } + } + + @Override + public void handleKeyboardInput() throws IOException { + boolean keyState = Keyboard.getEventKeyState(); + int keyCode = Keyboard.getEventKey(); + + if(uploadWizard) { + if(keyState) { + if(keyCode == Keyboard.KEY_UP) { + if(selectedLine > 3) + selectedLine--; + else if(uploadOffset > 0) { + uploadOffset--; + updateUploadScreen(); + } + } else if(keyCode == Keyboard.KEY_DOWN) { + if(selectedLine < MAX_LINES - 1) + selectedLine++; + else if(uploadOffset + selectedLine - 2 < uploadFiles.size()) { + uploadOffset++; + updateUploadScreen(); + } + } else if(keyCode == Keyboard.KEY_PRIOR) { + selectedLine = 3; + int dst = uploadOffset - (MAX_LINES - 3); + if(dst < 0) + dst = 0; + + selectFile(dst); + } else if(keyCode == Keyboard.KEY_NEXT) { + selectedLine = 3; + int dst = uploadOffset + (MAX_LINES - 3); + if(dst >= uploadFiles.size()) + dst = uploadFiles.size() - 1; + + selectFile(dst); + } else if(keyCode == Keyboard.KEY_RETURN || keyCode == Keyboard.KEY_NUMPADENTER) { + File file = uploadFiles.get(uploadOffset + selectedLine - 3); + + if(file.isDirectory()) { + uploadCD(file); + updateUploadScreen(); + } else + startFileUpload(file, true); + } else if(keyCode == Keyboard.KEY_F5) { + uploadCD(uploadDir); + updateUploadScreen(); + } + } + + if(keyCode == Keyboard.KEY_ESCAPE) { + quitUploadWizard(); + return; //Don't let the screen handle this + } + + super.handleKeyboardInput(); + } else { + super.handleKeyboardInput(); + + if(keyState) { + boolean ctrl = Keyboard.isKeyDown(Keyboard.KEY_LCONTROL) || Keyboard.isKeyDown(Keyboard.KEY_RCONTROL); + + if(keyCode == Keyboard.KEY_L && ctrl) + lines.clear(); + else if(keyCode == Keyboard.KEY_V && ctrl) { + prompt += getClipboardString(); + + if(prompt.length() > MAX_LINE_LEN) + prompt = prompt.substring(0, MAX_LINE_LEN); + } else if(keyCode == Keyboard.KEY_UP) { + if(lastCmd != null) { + String tmp = prompt; + prompt = lastCmd; + lastCmd = tmp; + } + } + } + } + } + + @Override + protected void keyTyped(char typedChar, int keyCode) throws IOException { + super.keyTyped(typedChar, keyCode); + + if(uploadWizard) { + boolean found = false; + uploadFilter += Character.toLowerCase(typedChar); + uploadFilterTime = System.currentTimeMillis(); + + for(int i = uploadFirstIsParent ? 1 : 0; i < uploadFiles.size(); i++) { + if(uploadFiles.get(i).getName().toLowerCase().startsWith(uploadFilter)) { + selectFile(i); + found = true; + break; + } + } + + if(!found && uploadFilter.length() == 1) + uploadFilter = ""; + + return; + } else if(promptLocked) + return; + + if(keyCode == Keyboard.KEY_BACK) { + if(prompt.length() > 0) + prompt = prompt.substring(0, prompt.length() - 1); + } else if(keyCode == Keyboard.KEY_RETURN || keyCode == Keyboard.KEY_NUMPADENTER) { + if(prompt.length() > 0) { + writeLine(userPrompt + prompt); + evaluateCommand(prompt); + lastCmd = prompt; + prompt = ""; + } else + writeLine(userPrompt); + } else if(prompt.length() + 1 < MAX_LINE_LEN && typedChar >= 32 && typedChar <= 126) + prompt = prompt + typedChar; + + blinkTime = 0; + } + + private void evaluateCommand(String str) { + String[] args = str.trim().split("\\s+"); + Method handler = COMMAND_MAP.get(args[0].toLowerCase()); + + if(handler == null) { + writeLine(tr("unknowncmd")); + return; + } + + Object[] params; + if(handler.getParameterCount() == 0) + params = new Object[0]; + else { + String[] args2 = new String[args.length - 1]; + System.arraycopy(args, 1, args2, 0, args2.length); + params = new Object[] { args2 }; + } + + try { + handler.invoke(this, params); + } catch(IllegalAccessException | InvocationTargetException e) { + Log.errorEx("Caught exception while running command \"%s\"", e, str); + writeLine(tr("error")); + } + } + + private void writeLine(String line) { + final int maxl = uploadWizard ? MAX_LINES : (MAX_LINES - 1); //Cuz prompt is hidden + while(lines.size() >= maxl) + lines.remove(0); + + lines.add(line); + } + + private static void buildCommandMap() { + COMMAND_MAP.clear(); + + Method[] methods = GuiServer.class.getMethods(); + for(Method m: methods) { + CommandHandler cmd = m.getAnnotation(CommandHandler.class); + + if(cmd != null && Modifier.isPublic(m.getModifiers())) { + if(m.getParameterCount() == 0 || (m.getParameterCount() == 1 && m.getParameterTypes()[0] == String[].class)) + COMMAND_MAP.put(cmd.value().toLowerCase(), m); + } + } + } + + private void quitUploadWizard() { + lines.clear(); + promptLocked = false; + uploadWizard = false; + selectedLine = -1; + } + + @Override + public void onGuiClosed() { + super.onGuiClosed(); + + if(accessSound != null) + mc.getSoundHandler().stopSound(accessSound); + } + + private boolean queueTask(ClientTask task) { + if(Client.getInstance().addTask(task)) { + promptLocked = true; + queryTime = System.currentTimeMillis(); //No task is running so it's okay to have an unsynchronized access here + currentTask = task; + return true; + } else { + writeLine(tr("queryerr")); + return false; + } + } + + private void clearTask() { + promptLocked = false; + currentTask = null; + } + + private static String trimStringL(String str) { + int delta = str.length() - MAX_LINE_LEN; + if(delta <= 0) + return str; + + return "..." + str.substring(delta + 3); + } + + private static String trimStringR(String str) { + return (str.length() <= MAX_LINE_LEN) ? str : (str.substring(0, MAX_LINE_LEN - 3) + "..."); + } + + @CommandHandler("clear") + public void commandClear() { + lines.clear(); + } + + @CommandHandler("help") + public void commandHelp(String[] args) { + if(args.length > 0) { + String cmd = args[0].toLowerCase(); + + if(COMMAND_MAP.containsKey(cmd)) + writeLine(tr("help." + cmd)); + else + writeLine(tr("unknowncmd")); + } else { + for(String c : COMMAND_MAP.keySet()) + writeLine(c + " - " + tr("help." + c)); + } + } + + @CommandHandler("exit") + public void commandExit() { + mc.displayGuiScreen(null); + } + + @CommandHandler("access") + public void commandAccess(String[] args) { + boolean handled = false; + + if(args.length >= 1 && args[0].equalsIgnoreCase("security")) { + if(args.length == 1 || (args.length == 2 && args[1].equalsIgnoreCase("grid"))) + handled = true; + } else if(args.length == 3 && args[0].equalsIgnoreCase("main") && args[1].equalsIgnoreCase("security") && args[2].equalsIgnoreCase("grid")) + handled = true; + + if(handled) { + writeLine("access: PERMISSION DENIED."); + + if(++accessTrials >= 3) { + promptLocked = true; + accessState = 0; + accessTime = 20; + } + } else + writeLine(tr("argerror")); + } + + @CommandHandler("owner") + public void commandOwner() { + writeLine(tr("ownername", owner.name)); + writeLine(tr("owneruuid")); + writeLine(owner.uuid.toString()); + } + + @CommandHandler("quota") + public void commandQuota() { + if(!mc.player.getGameProfile().getId().equals(owner.uuid)) { + writeLine(tr("errowner")); + return; + } + + ClientTaskGetQuota task = new ClientTaskGetQuota(); + task.setFinishCallback((t) -> { + writeLine(tr("quota", Util.sizeString(t.getQuota()), Util.sizeString(t.getMaxQuota()))); + clearTask(); + }); + + queueTask(task); + } + + @CommandHandler("ls") + public void commandList() { + ClientTaskGetFileList task = new ClientTaskGetFileList(owner.uuid); + task.setFinishCallback((t) -> { + String[] files = t.getFileList(); + if(files != null) + Arrays.stream(files).forEach(this::writeLine); + + clearTask(); + }); + + queueTask(task); + } + + @CommandHandler("url") + public void commandURL(String[] args) { + if(args.length < 1) { + writeLine(tr("fnamearg")); + return; + } + + String fname = Util.join(args, " "); + if(Util.isFileNameInvalid(fname)) { + writeLine(tr("nameerr")); + return; + } + + ClientTaskCheckFile task = new ClientTaskCheckFile(owner.uuid, fname); + task.setFinishCallback((t) -> { + int status = t.getStatus(); + if(status == 0) { + writeLine(tr("urlcopied")); + setClipboardString(t.getURL()); + } else if(status == Constants.GETF_STATUS_NOT_FOUND) + writeLine(tr("notfound")); + else + writeLine(tr("error2", status)); + + clearTask(); + }); + + queueTask(task); + } + + private void uploadCD(File newDir) { + try { + uploadDir = newDir.getCanonicalFile(); + } catch(IOException ex) { + uploadDir = newDir; + } + + uploadFiles.clear(); + File parent = uploadDir.getParentFile(); + + if(parent != null && parent.exists()) { + uploadFiles.add(parent); + uploadFirstIsParent = true; + } else + uploadFirstIsParent = false; + + File[] children = uploadDir.listFiles(); + if(children != null) { + Collator c = Collator.getInstance(); + c.setStrength(Collator.SECONDARY); + c.setDecomposition(Collator.CANONICAL_DECOMPOSITION); + + Arrays.stream(children).filter(f -> !f.isHidden() && (f.isDirectory() || f.isFile())).sorted((a, b) -> c.compare(a.getName(), b.getName())).forEach(uploadFiles::add); + } + + uploadOffset = 0; + uploadFilter = ""; + + if(uploadWizard) + selectedLine = 3; + } + + private void updateUploadScreen() { + lines.clear(); + + lines.add(tr("upload.info")); + lines.add(trimStringL(uploadDir.getPath())); + lines.add(""); + + for(int i = uploadOffset; i < uploadFiles.size() && lines.size() < MAX_LINES; i++) { + if(i == 0 && uploadFirstIsParent) + lines.add(tr("upload.parent")); + else + lines.add(trimStringR(uploadFiles.get(i).getName())); + } + } + + private void selectFile(int i) { + int pos = 3 + i - uploadOffset; + if(pos >= 3 && pos < MAX_LINES) { + selectedLine = pos; + return; + } + + uploadOffset = i; + if(uploadOffset + MAX_LINES - 3 > uploadFiles.size()) + uploadOffset = uploadFiles.size() - MAX_LINES + 3; + + updateUploadScreen(); + selectedLine = 3 + i - uploadOffset; + } + + @CommandHandler("upload") + public void commandUpload(String[] args) { + if(!mc.player.getGameProfile().getId().equals(owner.uuid)) { + writeLine(tr("errowner")); + return; + } + + if(args.length > 0) { + File fle = new File(Util.join(args, " ")); + if(!fle.exists()) { + writeLine(tr("notfound")); + return; + } + + if(fle.isDirectory()) + uploadCD(fle); + else if(fle.isFile()) { + startFileUpload(fle, false); + return; + } else { + writeLine(tr("notfound")); + return; + } + } + + uploadWizard = true; + promptLocked = true; + uploadOffset = 0; + selectedLine = 3; + updateUploadScreen(); + } + + @CommandHandler("rm") + public void commandDelete(String[] args) { + if(!mc.player.getGameProfile().getId().equals(owner.uuid)) { + writeLine(tr("errowner")); + return; + } + + if(args.length < 1) { + writeLine(tr("fnamearg")); + return; + } + + String fname = Util.join(args, " "); + if(Util.isFileNameInvalid(fname)) { + writeLine(tr("nameerr")); + return; + } + + ClientTaskDeleteFile task = new ClientTaskDeleteFile(fname); + task.setFinishCallback((t) -> { + int status = t.getStatus(); + if(status == 1) + writeLine(tr("notfound")); + else if(status != 0) + writeLine(tr("error")); + + clearTask(); + }); + + queueTask(task); + } + + @CommandHandler("reconnect") + public void commandReconnect() { + Client.getInstance().stop(); + WebDisplays.NET_HANDLER.sendToServer(Client.getInstance().beginConnection()); + } + + private void startFileUpload(File f, boolean quit) { + if(quit) + quitUploadWizard(); + + if(Util.isFileNameInvalid(f.getName()) || f.getName().length() >= MAX_LINE_LEN - 3) { + writeLine(tr("nameerr")); + return; + } + + ClientTaskUploadFile task; + try { + task = new ClientTaskUploadFile(f); + } catch(IOException ex) { + writeLine(tr("error")); + ex.printStackTrace(); + return; + } + + task.setProgressCallback((cur, total) -> { + synchronized(GuiServer.this) { + queryTime = System.currentTimeMillis(); + } + }); + + task.setFinishCallback(t -> { + int status = t.getUploadStatus(); + if(status == 0) + writeLine(tr("upload.done")); + else if(status == Constants.FUPA_STATUS_FILE_EXISTS) + writeLine(tr("upload.exists")); + else if(status == Constants.FUPA_STATUS_EXCEEDS_QUOTA) + writeLine(tr("upload.quota")); + else + writeLine(tr("error2", status)); + + clearTask(); + }); + + if(queueTask(task)) + writeLine(tr("upload.uploading")); + } + + @Override + public boolean isForBlock(BlockPos bp, BlockSide side) { + return serverPos.equalsBlockPos(bp); + } + + @Nullable + @Override + public String getWikiPageName() { + return "Server"; + } + +} diff --git a/src/main/java/net/montoyo/wd/client/gui/GuiSetURL2.java b/src/main/java/net/montoyo/wd/client/gui/GuiSetURL2.java new file mode 100644 index 0000000..0fa337f --- /dev/null +++ b/src/main/java/net/montoyo/wd/client/gui/GuiSetURL2.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2019 BARBOTIN Nicolas + */ + +package net.montoyo.wd.client.gui; + +import net.minecraft.item.ItemStack; +import net.minecraft.util.EnumHand; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.BlockPos; +import net.montoyo.wd.WebDisplays; +import net.montoyo.wd.client.ClientProxy; +import net.montoyo.wd.client.gui.controls.Button; +import net.montoyo.wd.client.gui.controls.TextField; +import net.montoyo.wd.client.gui.loading.FillControl; +import net.montoyo.wd.entity.TileEntityScreen; +import net.montoyo.wd.net.server.SMessagePadCtrl; +import net.montoyo.wd.net.server.SMessageScreenCtrl; +import net.montoyo.wd.utilities.BlockSide; +import net.montoyo.wd.utilities.Util; +import net.montoyo.wd.utilities.Vector3i; + +import java.util.Map; + +public class GuiSetURL2 extends WDScreen { + + //Screen data + private TileEntityScreen tileEntity; + private BlockSide screenSide; + private Vector3i remoteLocation; + + //Pad data + private final boolean isPad; + + //Common + private final String screenURL; + + @FillControl + private TextField tfURL; + + @FillControl + private Button btnShutDown; + + @FillControl + private Button btnCancel; + + @FillControl + private Button btnOk; + + public GuiSetURL2(TileEntityScreen tes, BlockSide side, String url, Vector3i rl) { + tileEntity = tes; + screenSide = side; + remoteLocation = rl; + isPad = false; + screenURL = url; + } + + public GuiSetURL2(String url) { + isPad = true; + screenURL = url; + } + + @Override + public void initGui() { + super.initGui(); + loadFrom(new ResourceLocation("webdisplays", "gui/seturl.json")); + tfURL.setText(screenURL); + } + + @Override + protected void addLoadCustomVariables(Map vars) { + vars.put("isPad", isPad ? 1.0 : 0.0); + } + + @GuiSubscribe + public void onButtonClicked(Button.ClickEvent ev) { + if(ev.getSource() == btnCancel) + mc.displayGuiScreen(null); + else if(ev.getSource() == btnOk) + validate(tfURL.getText()); + else if(ev.getSource() == btnShutDown) { + if(isPad) + WebDisplays.NET_HANDLER.sendToServer(new SMessagePadCtrl("")); + + mc.displayGuiScreen(null); + } + } + + @GuiSubscribe + public void onEnterPressed(TextField.EnterPressedEvent ev) { + validate(ev.getText()); + } + + private void validate(String url) { + if(!url.isEmpty()) { + url = Util.addProtocol(url); + url = ((ClientProxy) WebDisplays.PROXY).getMCEF().punycode(url); + + if(isPad) { + WebDisplays.NET_HANDLER.sendToServer(new SMessagePadCtrl(url)); + ItemStack held = mc.player.getHeldItem(EnumHand.MAIN_HAND); + + if(held.getItem() == WebDisplays.INSTANCE.itemMinePad && held.getTagCompound() != null && held.getTagCompound().hasKey("PadID")) { + ClientProxy.PadData pd = ((ClientProxy) WebDisplays.PROXY).getPadByID(held.getTagCompound().getInteger("PadID")); + + if(pd != null && pd.view != null) + pd.view.loadURL(WebDisplays.applyBlacklist(url)); + } + } else + WebDisplays.NET_HANDLER.sendToServer(SMessageScreenCtrl.setURL(tileEntity, screenSide, url, remoteLocation)); + } + + mc.displayGuiScreen(null); + } + + @Override + public boolean isForBlock(BlockPos bp, BlockSide side) { + return (remoteLocation != null && remoteLocation.equalsBlockPos(bp)) || (bp.equals(tileEntity.getPos()) && side == screenSide); + } + +} diff --git a/src/main/java/net/montoyo/wd/client/gui/GuiSubscribe.java b/src/main/java/net/montoyo/wd/client/gui/GuiSubscribe.java new file mode 100644 index 0000000..5814b77 --- /dev/null +++ b/src/main/java/net/montoyo/wd/client/gui/GuiSubscribe.java @@ -0,0 +1,15 @@ +/* + * Copyright (C) 2018 BARBOTIN Nicolas + */ + +package net.montoyo.wd.client.gui; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface GuiSubscribe { +} diff --git a/src/main/java/net/montoyo/wd/client/gui/RenderRecipe.java b/src/main/java/net/montoyo/wd/client/gui/RenderRecipe.java new file mode 100644 index 0000000..116785d --- /dev/null +++ b/src/main/java/net/montoyo/wd/client/gui/RenderRecipe.java @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2018 BARBOTIN Nicolas + */ + +package net.montoyo.wd.client.gui; + +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.RenderHelper; +import net.minecraft.client.renderer.RenderItem; +import net.minecraft.client.renderer.texture.TextureUtil; +import net.minecraft.client.resources.I18n; +import net.minecraft.item.ItemStack; +import net.minecraft.item.crafting.CraftingManager; +import net.minecraft.item.crafting.IRecipe; +import net.minecraft.item.crafting.Ingredient; +import net.minecraft.item.crafting.ShapedRecipes; +import net.minecraft.util.NonNullList; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; +import net.montoyo.wd.utilities.Log; +import org.lwjgl.BufferUtils; +import org.lwjgl.opengl.EXTBgra; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.nio.IntBuffer; +import java.util.ArrayList; +import java.util.stream.IntStream; + +import static org.lwjgl.opengl.GL11.*; + +@SideOnly(Side.CLIENT) +public class RenderRecipe extends GuiScreen { + + private static class NameRecipePair { + + private final String name; + private final ShapedRecipes recipe; + + private NameRecipePair(String n, ShapedRecipes r) { + this.name = n; + this.recipe = r; + } + + } + + private static final ResourceLocation CRAFTING_TABLE_GUI_TEXTURES = new ResourceLocation("textures/gui/container/crafting_table.png"); + private static final int SIZE_X = 176; + private static final int SIZE_Y = 166; + private int x; + private int y; + private RenderItem renderItem; + private final ItemStack[] recipe = new ItemStack[3 * 3]; + private ItemStack recipeResult; + private String recipeName; + private final ArrayList recipes = new ArrayList<>(); + private IntBuffer buffer; + private int[] array; + + @Override + public void initGui() { + x = (width - SIZE_X) / 2; + y = (height - SIZE_Y) / 2; + renderItem = mc.getRenderItem(); + + for(IRecipe recipe: CraftingManager.REGISTRY) { + ResourceLocation regName = recipe.getRegistryName(); + + if(regName != null && regName.getResourceDomain().equals("webdisplays")) { + if(recipe instanceof ShapedRecipes) + recipes.add(new NameRecipePair(regName.getResourcePath(), (ShapedRecipes) recipe)); + else + Log.warning("Found non-shaped recipe %s", regName.toString()); + } + } + + Log.info("Loaded %d recipes", recipes.size()); + nextRecipe(); + } + + @Override + public void drawScreen(int mouseX, int mouseY, float partialTicks) { + drawDefaultBackground(); + GlStateManager.color(1.0f, 1.0f, 1.0f); + mc.getTextureManager().bindTexture(CRAFTING_TABLE_GUI_TEXTURES); + drawTexturedModalRect(x, y, 0, 0, SIZE_X, SIZE_Y); + fontRenderer.drawString(I18n.format("container.crafting"), x + 28, y + 6, 0x404040); + + RenderHelper.enableGUIStandardItemLighting(); + GlStateManager.disableLighting(); + + for(int sy = 0; sy < 3; sy++) { + for(int sx = 0; sx < 3; sx++) { + ItemStack is = recipe[sy * 3 + sx]; + + if(is != null) { + int x = this.x + 30 + sx * 18; + int y = this.y + 17 + sy * 18; + + renderItem.renderItemAndEffectIntoGUI(mc.player, is, x, y); + renderItem.renderItemOverlayIntoGUI(fontRenderer, is, x, y, null); + } + } + } + + if(recipeResult != null) { + renderItem.renderItemAndEffectIntoGUI(mc.player, recipeResult, x + 124, y + 35); + renderItem.renderItemOverlayIntoGUI(fontRenderer, recipeResult, x + 124, y + 35, null); + } + + GlStateManager.enableLighting(); + RenderHelper.disableStandardItemLighting(); + } + + private void setRecipe(ShapedRecipes recipe) { + IntStream.range(0, this.recipe.length).forEach(i -> this.recipe[i] = null); + NonNullList ingredients = recipe.getIngredients(); + int pos = 0; + + for(int y = 0; y < recipe.getRecipeHeight(); y++) { + for(int x = 0; x < recipe.getRecipeWidth(); x++) { + ItemStack[] stacks = ingredients.get(pos++).getMatchingStacks(); + + if(stacks.length > 0) + this.recipe[y * 3 + x] = stacks[0]; + } + } + + recipeResult = recipe.getRecipeOutput(); + } + + private void nextRecipe() { + if(recipes.isEmpty()) + mc.displayGuiScreen(null); + else { + NameRecipePair pair = recipes.remove(0); + setRecipe(pair.recipe); + recipeName = pair.name; + } + } + + private int screen2DisplayX(int x) { + double ret = ((double) x) / ((double) width) * ((double) mc.displayWidth); + return (int) ret; + } + + private int screen2DisplayY(int y) { + double ret = ((double) y) / ((double) height) * ((double) mc.displayHeight); + return (int) ret; + } + + private void takeScreenshot() throws Throwable { + int x = screen2DisplayX(this.x + 27); + int y = mc.displayHeight - screen2DisplayY(this.y + 4); + int w = screen2DisplayX(120); + int h = screen2DisplayY(68); + y -= h; + + if(buffer == null) + buffer = BufferUtils.createIntBuffer(w * h); + + int oldPack = glGetInteger(GL_PACK_ALIGNMENT); + glPixelStorei(GL_PACK_ALIGNMENT, 1); + buffer.clear(); + glReadPixels(x, y, w, h, EXTBgra.GL_BGRA_EXT, GL_UNSIGNED_BYTE, buffer); + glPixelStorei(GL_PACK_ALIGNMENT, oldPack); + + if(array == null) + array = new int[w * h]; + + buffer.clear(); + buffer.get(array); + TextureUtil.processPixelValues(array, w, h); + + File f = new File(mc.mcDataDir, "wd_recipes"); + if(!f.exists()) + f.mkdir(); + + f = new File(f, recipeName + ".png"); + + BufferedImage bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); + bi.setRGB(0, 0, w, h, array, 0, w); + ImageIO.write(bi, "PNG", f); + } + + @Override + public void updateScreen() { + if(recipeName != null) { + try { + takeScreenshot(); + nextRecipe(); + } catch(Throwable t) { + t.printStackTrace(); + mc.displayGuiScreen(null); + } + } + } + +} diff --git a/src/main/java/net/montoyo/wd/client/gui/WDScreen.java b/src/main/java/net/montoyo/wd/client/gui/WDScreen.java new file mode 100644 index 0000000..c3e74b3 --- /dev/null +++ b/src/main/java/net/montoyo/wd/client/gui/WDScreen.java @@ -0,0 +1,367 @@ +/* + * Copyright (C) 2018 BARBOTIN Nicolas + */ + +package net.montoyo.wd.client.gui; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.item.ItemStack; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.math.BlockPos; +import net.montoyo.wd.WebDisplays; +import net.montoyo.wd.client.gui.controls.Container; +import net.montoyo.wd.client.gui.controls.Control; +import net.montoyo.wd.client.gui.controls.Event; +import net.montoyo.wd.client.gui.controls.List; +import net.montoyo.wd.client.gui.loading.FillControl; +import net.montoyo.wd.client.gui.loading.GuiLoader; +import net.montoyo.wd.client.gui.loading.JsonOWrapper; +import net.montoyo.wd.net.server.SMessageACQuery; +import net.montoyo.wd.utilities.BlockSide; +import net.montoyo.wd.utilities.Bounds; +import net.montoyo.wd.utilities.Log; +import net.montoyo.wd.utilities.NameUUIDPair; +import org.lwjgl.input.Keyboard; +import org.lwjgl.input.Mouse; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +public abstract class WDScreen extends GuiScreen { + + public static WDScreen CURRENT_SCREEN = null; + + protected final ArrayList controls = new ArrayList<>(); + protected final ArrayList postDrawList = new ArrayList<>(); + private final HashMap, Method> eventMap = new HashMap<>(); + protected boolean quitOnEscape = true; + protected boolean defaultBackground = true; + protected int syncTicks = 40; + private int syncTicksLeft = -1; + + public WDScreen() { + Method[] methods = getClass().getMethods(); + + for(Method m : methods) { + if(m.getAnnotation(GuiSubscribe.class) != null) { + if(!Modifier.isPublic(m.getModifiers())) + throw new RuntimeException("Found non public @GuiSubscribe"); + + Class params[] = m.getParameterTypes(); + if(params.length != 1 || !Event.class.isAssignableFrom(params[0])) + throw new RuntimeException("Invalid parameters for @GuiSubscribe"); + + eventMap.put((Class) params[0], m); + } + } + } + + protected T addControl(T ctrl) { + controls.add(ctrl); + return ctrl; + } + + public int screen2DisplayX(int x) { + double ret = ((double) x) / ((double) width) * ((double) mc.displayWidth); + return (int) ret; + } + + public int screen2DisplayY(int y) { + double ret = ((double) y) / ((double) height) * ((double) mc.displayHeight); + return (int) ret; + } + + public int display2ScreenX(int x) { + double ret = ((double) x) / ((double) mc.displayWidth) * ((double) width); + return (int) ret; + } + + public int display2ScreenY(int y) { + double ret = ((double) y) / ((double) mc.displayHeight) * ((double) height); + return (int) ret; + } + + protected void centerControls() { + //Determine bounding box + Bounds bounds = Control.findBounds(controls); + + //Translation vector + int diffX = (width - bounds.maxX - bounds.minX) / 2; + int diffY = (height - bounds.maxY - bounds.minY) / 2; + + //Translate controls + for(Control ctrl : controls) { + int x = ctrl.getX(); + int y = ctrl.getY(); + + ctrl.setPos(x + diffX, y + diffY); + } + } + + @Override + public void drawScreen(int mouseX, int mouseY, float ptt) { + if(defaultBackground) + drawDefaultBackground(); + + for(Control ctrl: controls) + ctrl.draw(mouseX, mouseY, ptt); + + for(Control ctrl: postDrawList) + ctrl.postDraw(mouseX, mouseY, ptt); + } + + @Override + protected void keyTyped(char typedChar, int keyCode) throws IOException { + if(quitOnEscape && keyCode == Keyboard.KEY_ESCAPE) { + mc.displayGuiScreen(null); + return; + } + + for(Control ctrl: controls) + ctrl.keyTyped(typedChar, keyCode); + } + + @Override + protected void mouseClicked(int mouseX, int mouseY, int mouseButton) { + for(Control ctrl: controls) + ctrl.mouseClicked(mouseX, mouseY, mouseButton); + } + + @Override + protected void mouseReleased(int mouseX, int mouseY, int state) { + for(Control ctrl: controls) + ctrl.mouseReleased(mouseX, mouseY, state); + } + + @Override + protected void mouseClickMove(int mouseX, int mouseY, int clickedMouseButton, long timeSinceLastClick) { + for(Control ctrl: controls) + ctrl.mouseClickMove(mouseX, mouseY, clickedMouseButton, timeSinceLastClick); + } + + @Override + public void initGui() { + CURRENT_SCREEN = this; + Keyboard.enableRepeatEvents(true); + } + + @Override + public void onGuiClosed() { + if(syncTicksLeft >= 0) { + sync(); + syncTicksLeft = -1; + } + + for(Control ctrl : controls) + ctrl.destroy(); + + Keyboard.enableRepeatEvents(false); + CURRENT_SCREEN = null; + } + + @Override + public void handleMouseInput() throws IOException { + super.handleMouseInput(); + + int x = Mouse.getEventX() * width / mc.displayWidth; + int y = height - Mouse.getEventY() * height / mc.displayHeight - 1; + int dw = Mouse.getEventDWheel(); + + if(dw != 0) + onMouseScroll(x, y, dw); + else if(Mouse.getEventButton() == -1) + onMouseMove(x, y); + } + + @Override + public void handleKeyboardInput() throws IOException { + super.handleKeyboardInput(); + + int key = Keyboard.getEventKey(); + if(key != Keyboard.KEY_NONE) { + if(Keyboard.getEventKeyState()) { + for(Control ctrl : controls) + ctrl.keyDown(key); + } else { + for(Control ctrl : controls) + ctrl.keyUp(key); + } + } + } + + public void onMouseScroll(int mouseX, int mouseY, int amount) { + for(Control ctrl : controls) + ctrl.mouseScroll(mouseX, mouseY, amount); + } + + public void onMouseMove(int mouseX, int mouseY) { + for(Control ctrl : controls) + ctrl.mouseMove(mouseX, mouseY); + } + + public Object actionPerformed(Event ev) { + Method m = eventMap.get(ev.getClass()); + + if(m != null) { + try { + return m.invoke(this, ev); + } catch(IllegalAccessException e) { + Log.errorEx("Access to event %s of screen %s is denied", e, ev.getClass().getSimpleName(), getClass().getSimpleName()); + } catch(InvocationTargetException e) { + Log.errorEx("Event %s of screen %s failed", e, ev.getClass().getSimpleName(), getClass().getSimpleName()); + } + } + + return null; + } + + public T getControlByName(String name) { + for(Control ctrl : controls) { + if(name.equals(ctrl.getName())) + return (T) ctrl; + + if(ctrl instanceof Container) { + Control ret = ((Container) ctrl).getByName(name); + + if(ret != null) + return (T) ret; + } + } + + return null; + } + + protected void addLoadCustomVariables(Map vars) { + } + + public void loadFrom(ResourceLocation resLoc) { + JsonObject root = GuiLoader.getJson(resLoc); + if(root == null) + throw new RuntimeException("Could not load GUI file " + resLoc.toString()); + + if(!root.has("controls") || !root.get("controls").isJsonArray()) + throw new RuntimeException("In GUI file " + resLoc.toString() + ": missing root 'controls' object."); + + HashMap vars = new HashMap<>(); + vars.put("width", (double) width); + vars.put("height", (double) height); + vars.put("displayWidth", (double) mc.displayWidth); + vars.put("displayHeight", (double) mc.displayHeight); + addLoadCustomVariables(vars); + + JsonArray content = root.get("controls").getAsJsonArray(); + for(JsonElement elem: content) + controls.add(GuiLoader.create(new JsonOWrapper(elem.getAsJsonObject(), vars))); + + Field[] fields = getClass().getDeclaredFields(); + for(Field f: fields) { + f.setAccessible(true); + FillControl fc = f.getAnnotation(FillControl.class); + + if(fc != null) { + String name = fc.name().isEmpty() ? f.getName() : fc.name(); + Control ctrl = getControlByName(name); + + if(ctrl == null) { + if(fc.required()) + throw new RuntimeException("In GUI file " + resLoc.toString() + ": missing required control " + name); + + continue; + } + + if(!f.getType().isAssignableFrom(ctrl.getClass())) + throw new RuntimeException("In GUI file " + resLoc.toString() + ": invalid type for control " + name); + + try { + f.set(this, ctrl); + } catch(IllegalAccessException e) { + if(fc.required()) + throw new RuntimeException(e); + } + } + } + + if(root.has("center") && root.get("center").getAsBoolean()) + centerControls(); + } + + @Override + public void onResize(@Nonnull Minecraft mcIn, int w, int h) { + for(Control ctrl : controls) + ctrl.destroy(); + + controls.clear(); + super.onResize(mcIn, w, h); + } + + protected void requestAutocomplete(String beginning, boolean matchExact) { + WebDisplays.NET_HANDLER.sendToServer(new SMessageACQuery(beginning, matchExact)); + } + + public void onAutocompleteResult(NameUUIDPair pairs[]) { + } + + public void onAutocompleteFailure() { + } + + protected void requestSync() { + syncTicksLeft = syncTicks - 1; + } + + protected boolean syncRequested() { + return syncTicksLeft >= 0; + } + + protected void abortSync() { + syncTicksLeft = -1; + } + + protected void sync() { + } + + @Override + public void updateScreen() { + if(syncTicksLeft >= 0) { + if(--syncTicksLeft < 0) + sync(); + } + } + + public void drawItemStackTooltip(ItemStack is, int x, int y) { + renderToolTip(is, x, y); //Since it's protected... + } + + public void drawTooltip(java.util.List lines, int x, int y) { + drawHoveringText(lines, x, y, fontRenderer); //This is also protected... + } + + public void requirePostDraw(Control ctrl) { + if(!postDrawList.contains(ctrl)) + postDrawList.add(ctrl); + } + + @Override + public boolean doesGuiPauseGame() { + return false; + } + + public abstract boolean isForBlock(BlockPos bp, BlockSide side); + + @Nullable + public String getWikiPageName() { + return null; + } + +} diff --git a/src/main/java/net/montoyo/wd/client/gui/controls/BasicControl.java b/src/main/java/net/montoyo/wd/client/gui/controls/BasicControl.java new file mode 100644 index 0000000..7d886e9 --- /dev/null +++ b/src/main/java/net/montoyo/wd/client/gui/controls/BasicControl.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2018 BARBOTIN Nicolas + */ + +package net.montoyo.wd.client.gui.controls; + +import net.montoyo.wd.client.gui.loading.JsonOWrapper; + +public abstract class BasicControl extends Control { + + protected int x; + protected int y; + protected boolean visible = true; + protected boolean disabled = false; + + @Override + public int getX() { + return x; + } + + @Override + public int getY() { + return y; + } + + @Override + public void setPos(int x, int y) { + this.x = x; + this.y = y; + } + + public boolean isDisabled() { + return disabled; + } + + public void setDisabled(boolean disabled) { + this.disabled = disabled; + } + + public void enable() { + disabled = false; + } + + public void disable() { + disabled = true; + } + + public boolean isVisible() { + return visible; + } + + public void setVisible(boolean visible) { + this.visible = visible; + } + + public void show() { + visible = true; + } + + public void hide() { + visible = false; + } + + @Override + public void load(JsonOWrapper json) { + super.load(json); + x = json.getInt("x", 0); + y = json.getInt("y", 0); + disabled = json.getBool("disabled", false); + visible = json.getBool("visible", true); + } + +} diff --git a/src/main/java/net/montoyo/wd/client/gui/controls/Button.java b/src/main/java/net/montoyo/wd/client/gui/controls/Button.java new file mode 100644 index 0000000..007df39 --- /dev/null +++ b/src/main/java/net/montoyo/wd/client/gui/controls/Button.java @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2018 BARBOTIN Nicolas + */ + +package net.montoyo.wd.client.gui.controls; + +import net.minecraft.client.gui.GuiButton; +import net.montoyo.wd.client.gui.loading.JsonOWrapper; +import org.lwjgl.input.Keyboard; + +public class Button extends Control { + + protected final GuiButton btn; + protected boolean selected = false; + protected boolean shiftDown = false; + protected int originalColor = 0; + protected int shiftColor = 0; + + public static class ClickEvent extends Event