最初的提交

This commit is contained in:
2025-10-31 13:53:49 +08:00
commit 49d30eb728
380 changed files with 25153 additions and 0 deletions

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 CinemaMod Group
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

49
META-INF/mods.toml Normal file
View File

@@ -0,0 +1,49 @@
modLoader="javafml" #mandatory
# Use NeoForge's FML loader range for 1.21.1
loaderVersion="[1,)" #mandatory
license="MIT"
issueTrackerURL="" #optional
[[mods]] #mandatory
modId="webdisplays" #mandatory
# Align mod version with 1.21.1 build
version="2.0.1-1.21.1" #mandatory
displayName="WebDisplays" #mandatory
displayURL="https://github.com/CinemaMod/webdisplays" #optional
logoFile= "" #optional
credits="" #optional
authors="GiantLuigi4, ds58, Mysticpasta1, montoyo, WaterPicker" #optional
description='''
'''
[[dependencies.webdisplays]] #optional
modId="neoforge" #mandatory
mandatory=true #mandatory
versionRange="[21.1.0,)" #mandatory
ordering="NONE"
side="BOTH"
[[dependencies.webdisplays]]
modId="minecraft"
mandatory=true
versionRange="[1.21.1]"
ordering="NONE"
side="BOTH"
[[dependencies.webdisplays]]
modId="mcef"
mandatory=true
versionRange="[2.1.6-1.21.1, )"
ordering="NONE"
side="BOTH"

View File

@@ -0,0 +1,49 @@
modLoader="javafml" #mandatory
# Use NeoForge's FML loader range for 1.21.1
loaderVersion="[1,)" #mandatory
license="MIT"
issueTrackerURL="" #optional
[[mods]] #mandatory
modId="webdisplays" #mandatory
# Align mod version with 1.21.1 build
version="2.0.1-1.21.1" #mandatory
displayName="WebDisplays" #mandatory
displayURL="https://github.com/CinemaMod/webdisplays" #optional
logoFile= "" #optional
credits="" #optional
authors="GiantLuigi4, ds58, Mysticpasta1, montoyo, WaterPicker" #optional
description='''
'''
[[dependencies.webdisplays]] #optional
modId="neoforge" #mandatory
mandatory=true #mandatory
versionRange="[21.1.0,)" #mandatory
ordering="NONE"
side="BOTH"
[[dependencies.webdisplays]]
modId="minecraft"
mandatory=true
versionRange="[1.21.1]"
ordering="NONE"
side="BOTH"
[[dependencies.webdisplays]]
modId="mcef"
mandatory=true
versionRange="[2.1.6-1.21.1, )"
ordering="NONE"
side="BOTH"

54
README.md Normal file
View File

@@ -0,0 +1,54 @@
# WebDisplaysNeoForge 1.21.1 适配版)
本仓库来源于原始项目并在本地克隆后进行适配与修复:
- 上游仓库:`https://github.com/CinemaMod/webdisplays.git`
- 本分支目标:适配 Minecraft 1.21.1NeoForge并在可用范围内修复关键问题
- 大量代码改动由多位 AI 大模型辅助完成(自动化迁移、代码生成、问题定位与修复)
## 当前状态
- 完成基础功能适配屏幕方块、模型加载、基础网络消息、MinePad 使用流程等)
- 修复 MinePad 初次使用时 URL 为空导致的崩溃(自定义负载编码 NPE
- 调整屏幕模型贴图加载逻辑(通过 `assets/webdisplays/atlases/blocks.json` 注册 `screen0..screen15` 贴图)
- 默认主页为 `https://git.lnkos.cn``CommonConfig.Browser.homepage`
- 已验证可构建并产出 JARWindowsJDK 21NeoGradle
## 已知问题(进行中)
- 外置键盘(左/右部件)在某些朝向或交互下存在异常
- 屏幕边框/部分贴图在某些资源包或拼图场景下异常(已加入图集声明,但仍需进一步兼容性验证)
- 代码中存在一定数量的弃用/迁移警告(不影响构建,后续逐步清理)
## 构建环境与依赖
- JDK`21`
- 构建:`Gradle Wrapper`(已内置)
- Mod 平台:`NeoForge 1.21.1`
- 建议依赖:`MCEF (com.cinemamod:mcef-neoforge)` 对应 1.21.1 版本
## 本地构建
在仓库根目录执行:
- Windows`./gradlew.bat build -x test`
- 其他平台:`./gradlew build -x test`
构建成功后JAR 位于 `build/libs/`
## 安装与试用
- 将生成的 `webdisplays-*.jar` 放入 `mods/`
- 确保安装对应版本的 `NeoForge``MCEF`
- 启动 1.21.1 客户端,创建世界,放置屏幕方块进行验证
## 主要改动摘要
- 屏幕自定义模型:`ScreenModelLoader`/`ScreenBaker`
- 通过 `atlases/blocks.json``screen0..screen15` 注册至方块图集
- 自定义烘焙模型以实现屏幕边框/连接效果
- 网络与数据修复:
- MinePad URL 为空值编码导致崩溃修复(`C2SMessageMinepadUrl` 编码前对 `null` 处理)
- GUI 初始化对 `null` URL 防御式赋值,避免界面 NPE
## 贡献与致谢
- 原始项目与创意来源归上游仓库及其作者所有
- 本地适配工作由多位 AI 大模型参与完成,辅助完成迁移、修复与文档编写
- 欢迎提交 Issue/PR共同完善 1.21.1 的适配与修复
## 许可证
- 许可证遵循上游仓库约定(详见本仓库 `LICENSE`
---
说明:本项目仍在持续迭代阶段,优先确保基础功能在 1.21.1 可用,其它外围与兼容性问题会逐步修复与完善。

View File

@@ -0,0 +1,60 @@
plugins {
id 'net.neoforged.gradle.userdev' version '7.0.+'
id 'org.spongepowered.mixin' version '0.7.+'
id 'maven-publish'
}
var mod_version = project.mod_version
archivesBaseName = project.archives_base_name
version = mod_version
group = maven_group
java.toolchain.languageVersion = JavaLanguageVersion.of(java_version as int)
sourceSets.main.resources.srcDirs += 'src/generated/resources'
mixin.add sourceSets.main, "webdisplays.refmap.json"
repositories {
maven {
name = "cursemaven"
url = "https://www.cursemaven.com"
}
maven {
name = 'mcef-releases'
url = 'https://mcef-download.cinemamod.com/repositories/releases/'
}
maven {
name = 'neoforged'
url = 'https://maven.neoforged.net/releases'
}
}
dependencies {
implementation 'net.neoforged:neoforge:21.1.203'
annotationProcessor 'org.spongepowered:mixin:0.8.5:processor'
// useful for debugging performance problems
implementation "curse.maven:spark-361579:4381167"
// here because we need to manually open the VR keyboard
compileOnly "curse.maven:vivecraft-667903:4794431"
implementation("com.cinemamod:mcef-neoforge:2.1.6-1.21.1") {
transitive = false
}
}
jar {
manifest {
attributes([
"Specification-Title": "WebDisplays",
"Specification-Vendor": "CinemaMod Group",
"Specification-Version": "1",
"Implementation-Title": project.name,
"Implementation-Version": project.version,
"Implementation-Vendor": "CinemaMod Group",
"Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"),
"MixinConfigs": "webdisplays.mixins.json"
])
}
}

View File

@@ -0,0 +1,15 @@
# Done to increase the memory available to gradle.
org.gradle.jvmargs = -Xmx8G
mod_version = 2.0.2-1.21.1
maven_group = com.cinemamod
archives_base_name = webdisplays
# NeoForge 1.21.1 configuration
minecraft_version = 1.21.1
neoforge_version = 21.1.203
java_version = 21
systemProp.http.proxyHost=127.0.0.1
systemProp.http.proxyPort=20803
systemProp.https.proxyHost=127.0.0.1
systemProp.https.proxyPort=20803

View File

@@ -0,0 +1,28 @@
pluginManagement {
repositories {
gradlePluginPortal()
maven {
name = 'NeoForged'
url = 'https://maven.neoforged.net/releases'
}
maven { url = 'https://maven.parchmentmc.org' } // Add this line
}
// resolutionStrategy {
// eachPlugin {
// switch (requested.id.toString()) {
// case "net.minecraftforge.gradle": {
// useModule("${requested.id}:ForgeGradle:${requested.version}")
// break
// }
// case "org.spongepowered.mixin": {
// useModule("org.spongepowered:mixingradle:${requested.version}")
// break;
// }
// }
// }
// }
}
plugins {
id 'org.gradle.toolchains.foojay-resolver-convention' version '0.5.0'
}

45
build.gradle Normal file
View File

@@ -0,0 +1,45 @@
plugins {
id 'java-library'
id 'maven-publish'
id 'net.neoforged.gradle.userdev' version '7.0.192'
}
version = mod_version
group = mod_group_id
repositories {
mavenCentral()
maven { url = 'https://maven.neoforged.net/releases' }
maven {
name = "cursemaven"
url = "https://www.cursemaven.com"
}
maven {
name = 'mcef-releases'
url = 'https://mcef-download.cinemamod.com/repositories/releases/'
}
}
base {
archivesName = mod_id
}
java.toolchain.languageVersion = JavaLanguageVersion.of(21)
dependencies {
implementation "net.neoforged:neoforge:${neo_version}"
// Project dependencies
implementation "curse.maven:spark-361579:4381167"
compileOnly "curse.maven:vivecraft-667903:4794431"
implementation("com.cinemamod:mcef-neoforge:2.1.6-1.21.1") {
transitive = false
}
}
// Improve compiler diagnostics to speed up fixing API changes
tasks.withType(JavaCompile).configureEach {
options.encoding = 'UTF-8'
options.compilerArgs += ['-Xdiags:verbose', '-Xlint:deprecation', '-Xlint:unchecked']
}

1
cursors.piskel Normal file

File diff suppressed because one or more lines are too long

39
gradle.properties Normal file
View File

@@ -0,0 +1,39 @@
# Sets default memory used for gradle commands. Can be overridden by user or command line properties.
org.gradle.jvmargs=-Xmx4G
org.gradle.daemon=true
org.gradle.parallel=true
org.gradle.caching=true
org.gradle.configuration-cache=true
#read more on this at https://github.com/neoforged/NeoGradle/blob/NG_7.0/README.md#apply-parchment-mappings
# you can also find the latest versions at: https://parchmentmc.org/docs/getting-started
neogradle.subsystems.parchment.minecraftVersion=1.21.1
neogradle.subsystems.parchment.mappingsVersion=2024.11.17
# Environment Properties
# You can find the latest versions here: https://projects.neoforged.net/neoforged/neoforge
# The Minecraft version must agree with the Neo version to get a valid artifact
minecraft_version=1.21.1
# The Minecraft version range can use any release version of Minecraft as bounds.
minecraft_version_range=[1.21.1]
# The Neo version must agree with the Minecraft version to get a valid artifact
neo_version=21.1.213
# The loader version range can only use the major version of FML as bounds
loader_version_range=[1,)
## Mod Properties
# The unique mod identifier for the mod. Must be lowercase in English locale.
mod_id=webdisplays
# The human-readable display name for the mod.
mod_name=WebDisplays
# The license of the mod.
mod_license=LGPL-3.0
# The mod version.
mod_version=1.1
# The group ID for the mod.
mod_group_id=net.montoyo.wd
# The authors of the mod.
mod_authors=montoyo, CinemaMod Group
# The description of the mod.
mod_description=A Minecraft mod that adds web displays to the game using MCEF (Minecraft Chromium Embedded Framework).

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

164
gradlew vendored Normal file
View File

@@ -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 "$@"

90
gradlew.bat vendored Normal file
View File

@@ -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

10
settings.gradle Normal file
View File

@@ -0,0 +1,10 @@
pluginManagement {
repositories {
gradlePluginPortal()
maven { url = 'https://maven.neoforged.net/releases' }
}
}
plugins {
id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0'
}

View File

@@ -0,0 +1,148 @@
/*
* Copyright (C) 2019 BARBOTIN Nicolas
*/
package net.montoyo.wd;
import com.cinemamod.mcef.MCEF;
import com.mojang.authlib.GameProfile;
import net.minecraft.core.BlockPos;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.phys.Vec3;
import net.neoforged.neoforge.network.handling.IPayloadContext;
import net.neoforged.neoforge.server.ServerLifecycleHooks;
import net.montoyo.wd.core.HasAdvancement;
import net.montoyo.wd.core.JSServerRequest;
import net.montoyo.wd.data.GuiData;
import net.montoyo.wd.entity.ScreenBlockEntity;
import net.montoyo.wd.entity.ScreenData;
import net.montoyo.wd.utilities.*;
import net.montoyo.wd.utilities.math.Vector2i;
import net.montoyo.wd.utilities.math.Vector3i;
import net.montoyo.wd.utilities.data.BlockSide;
import net.montoyo.wd.utilities.data.Rotation;
import net.montoyo.wd.utilities.serialization.NameUUIDPair;
import org.joml.Vector3d;
import javax.annotation.Nonnull;
import java.util.UUID;
public class SharedProxy {
public void preInit() {
}
public void init() {
MCEF.scheduleForInit((cef) -> onCefInit());
}
public void postInit() {
}
public void onCefInit() {
}
@Deprecated(forRemoval = true)
public Level getWorld(ResourceKey<Level> dim) {
return getServer().getLevel(dim);
}
public BlockGetter getWorld(IPayloadContext context) {
if (context.player() != null) return context.player().level();
return null;
}
public void enqueue(Runnable r) {
ServerLifecycleHooks.getCurrentServer().addTickable(r);
}
public void displayGui(GuiData data) {
Log.error("Called SharedProxy.displayGui() on server side...");
}
public void trackScreen(ScreenBlockEntity tes, boolean track) {
}
public void onAutocompleteResult(NameUUIDPair pairs[]) {
}
public GameProfile[] getOnlineGameProfiles() {
return ServerLifecycleHooks.getCurrentServer().getPlayerList().getPlayers().stream().map(Player::getGameProfile).toArray(GameProfile[]::new);
}
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(ItemStack is, String padURL) {
Log.error("Called SharedProxy.displaySetPadURLGui() on server side...");
}
public void openMinePadGui(UUID 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 ServerLifecycleHooks.getCurrentServer();
}
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;
}
public double distanceTo(ScreenBlockEntity tes, Vec3 position) {
double dist = Double.POSITIVE_INFINITY;
for (int i = 0; i < tes.screenCount(); i++) {
ScreenData scrn = tes.getScreen(i);
Vector3d pos = new Vector3d(
scrn.side.right.x * scrn.size.x / 2d + scrn.size.y * scrn.side.up.x / 2d,
scrn.side.right.y * scrn.size.x / 2d + scrn.size.y * scrn.side.up.y / 2d,
scrn.side.right.z * scrn.size.x / 2d + scrn.size.y * scrn.side.up.z / 2d
).add(tes.getBlockPos().getX(), tes.getBlockPos().getY(), tes.getBlockPos().getZ());
double dist2 = position.distanceToSqr(pos.x, pos.y, pos.z);
dist = Math.min(dist, dist2);
}
return dist;
}
}

View File

@@ -0,0 +1,420 @@
/*
* Copyright (C) 2019 BARBOTIN Nicolas
*/
package net.montoyo.wd;
import com.google.gson.Gson;
import net.minecraft.ChatFormatting;
import net.minecraft.advancements.Advancement;
import net.minecraft.advancements.AdvancementHolder;
import net.minecraft.advancements.CriteriaTriggers;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.Level;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.ModList;
import net.neoforged.fml.common.Mod;
import net.neoforged.fml.ModLoadingContext;
import net.neoforged.fml.loading.FMLEnvironment;
import net.neoforged.neoforge.common.NeoForge;
import net.neoforged.neoforge.event.ServerChatEvent;
import net.neoforged.neoforge.event.entity.item.ItemTossEvent;
import net.neoforged.neoforge.event.entity.player.PlayerEvent;
import net.neoforged.neoforge.event.level.LevelEvent;
import net.neoforged.neoforge.event.server.ServerStoppingEvent;
import net.neoforged.neoforge.registries.DeferredRegister;
import net.minecraft.core.registries.Registries;
import net.montoyo.wd.client.ClientProxy;
import net.montoyo.wd.client.gui.camera.KeyboardCamera;
import net.montoyo.wd.config.ClientConfig;
import net.montoyo.wd.config.CommonConfig;
import net.montoyo.wd.controls.ScreenControlRegistry;
import net.montoyo.wd.core.*;
import net.montoyo.wd.miniserv.server.Server;
import net.montoyo.wd.net.WDNetworkRegistry;
import net.montoyo.wd.net.client_bound.S2CMessageServerInfo;
import net.montoyo.wd.registry.BlockRegistry;
import net.montoyo.wd.registry.ItemRegistry;
import net.montoyo.wd.registry.TileRegistry;
import net.montoyo.wd.registry.WDTabs;
import net.montoyo.wd.utilities.DistSafety;
import net.montoyo.wd.utilities.Log;
import net.montoyo.wd.utilities.serialization.Util;
import net.montoyo.wd.data.WDDataComponents;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Objects;
import java.util.UUID;
@Mod("webdisplays")
public class WebDisplays {
public static final String MOD_ID = "webdisplays";
public static WebDisplays INSTANCE;
public static SharedProxy PROXY = null;
public static final ResourceLocation ADV_PAD_BREAK = ResourceLocation.fromNamespaceAndPath("webdisplays", "pad_break");
public static final String BLACKLIST_URL = "mod://webdisplays/blacklisted.html";
public static final Gson GSON = new Gson();
//Sounds
public SoundEvent soundTyping;
public SoundEvent soundUpgradeAdd;
public SoundEvent soundUpgradeDel;
public SoundEvent soundScreenCfg;
public SoundEvent soundServer;
public SoundEvent soundIronic;
//Criterions
public WDCriterion criterionPadBreak;
public WDCriterion criterionUpgradeScreen;
public WDCriterion criterionLinkPeripheral;
public WDCriterion criterionKeyboardCat;
//Config
public static final double PAD_RATIO = 59.0 / 30.0;
public double padResX;
public double padResY;
private int lastPadId = 0;
public double unloadDistance2;
public double loadDistance2;
public int miniservPort;
public long miniservQuota;
public float ytVolume;
public float avDist100;
public float avDist0;
// mod detection
private boolean hasOC;
private boolean hasCC;
public WebDisplays() {
INSTANCE = this;
if(FMLEnvironment.dist.isClient()) {
PROXY = DistSafety.createProxy();
} else {
PROXY = new SharedProxy();
}
if (FMLEnvironment.dist.isClient()) {
// proxies are annoying, so from now on, I'mma be just registering stuff in here
ModLoadingContext.get().getActiveContainer().getEventBus().addListener(ClientProxy::onKeybindRegistry);
ModLoadingContext.get().getActiveContainer().getEventBus().addListener(ClientProxy::onClientSetup);
ModLoadingContext.get().getActiveContainer().getEventBus().addListener(ClientProxy::onModelRegistryEvent);
// ClientProxy registers instance event handlers in preInit();
// RenderHighlightEvent.Block is handled via ClientProxy#onDrawSelection (instance)
NeoForge.EVENT_BUS.addListener(KeyboardCamera::updateCamera);
NeoForge.EVENT_BUS.addListener(KeyboardCamera::gameTick);
ClientConfig.init();
}
CommonConfig.init();
//Criterions
criterionPadBreak = new WDCriterion();
criterionUpgradeScreen = new WDCriterion();
criterionLinkPeripheral = new WDCriterion();
criterionKeyboardCat = new WDCriterion();
IEventBus bus = ModLoadingContext.get().getActiveContainer().getEventBus();
bus.addListener(WDNetworkRegistry::register);
// Register custom advancement triggers at the correct time
bus.addListener(this::onRegisterTriggers);
WDNetworkRegistry.init();
SOUNDS.register(bus);
onRegisterSounds();
WDTabs.init(bus);
BlockRegistry.init(bus);
ItemRegistry.init(bus);
TileRegistry.init(bus);
WDDataComponents.DATA_COMPONENTS.register(bus);
WDDCapability.ATTACHMENT_TYPES.register(bus);
PROXY.preInit();
NeoForge.EVENT_BUS.register(this);
//Other things
PROXY.init();
PROXY.postInit();
hasOC = ModList.get().isLoaded("opencomputers");
hasCC = ModList.get().isLoaded("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();
}
} */
if (!FMLEnvironment.production) {
ScreenControlRegistry.init();
}
}
// Register custom advancement triggers via registry event to avoid freeze
private void onRegisterTriggers(final net.neoforged.neoforge.registries.RegisterEvent event) {
event.register(Registries.TRIGGER_TYPE,
ResourceLocation.fromNamespaceAndPath(MOD_ID, "pad_break"),
() -> criterionPadBreak);
event.register(Registries.TRIGGER_TYPE,
ResourceLocation.fromNamespaceAndPath(MOD_ID, "upgrade_screen"),
() -> criterionUpgradeScreen);
event.register(Registries.TRIGGER_TYPE,
ResourceLocation.fromNamespaceAndPath(MOD_ID, "link_peripheral"),
() -> criterionLinkPeripheral);
event.register(Registries.TRIGGER_TYPE,
ResourceLocation.fromNamespaceAndPath(MOD_ID, "keyboard_cat"),
() -> criterionKeyboardCat);
}
public void onRegisterSounds() {
soundTyping = registerSound("keyboard_type");
soundUpgradeAdd = registerSound("upgrade_add");
soundUpgradeDel = registerSound("upgrade_del");
soundScreenCfg = registerSound("screencfg_open");
soundServer = registerSound("server");
soundIronic = registerSound("ironic");
}
ArrayList<ResourceKey<Level>> serverStartedDimensions = new ArrayList<>();
@SubscribeEvent
public void onWorldLoad(LevelEvent.Load ev) {
if (ev.getLevel() instanceof Level level) {
if (ev.getLevel().isClientSide() || level.dimension() != Level.OVERWORLD)
return;
Path worldDir = Objects.requireNonNull(ev.getLevel().getServer()).getServerDirectory();
Path f = worldDir.resolve("wd_next.txt");
if (Files.exists(f)) {
try {
BufferedReader br = Files.newBufferedReader(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.toAbsolutePath().toString());
}
}
if (miniservPort != 0) {
Server sv = Server.getInstance();
if(!serverStartedDimensions.contains(level.dimension())) {
sv.setPort(miniservPort);
sv.setDirectory(worldDir.resolve("wd_filehost").toFile());
sv.start();
serverStartedDimensions.add(level.dimension());
}
}
}
}
@SubscribeEvent
public void onWorldLeave(LevelEvent.Unload ev) throws IOException {
if(ev.getLevel() instanceof Level level) {
if (ev.getLevel().isClientSide() || level.dimension() != Level.OVERWORLD)
return;
Server sw = Server.getInstance();
sw.stopServer();
serverStartedDimensions.remove(level.dimension());
}
}
@SubscribeEvent
public void onWorldSave(LevelEvent.Save ev) {
if(ev.getLevel() instanceof Level level) {
if (ev.getLevel().isClientSide() || level.dimension() != Level.OVERWORLD)
return;
Path f = Objects.requireNonNull(ev.getLevel().getServer()).getServerDirectory().resolve("wd_next.txt");
try {
BufferedWriter bw = Files.newBufferedWriter(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.toAbsolutePath().toString());
}
}
}
@SubscribeEvent
public void onToss(ItemTossEvent ev) {
if(!ev.getEntity().level().isClientSide) {
ItemStack is = ev.getEntity().getItem();
if(is.getItem() == ItemRegistry.MINEPAD.get()) {
UUID thrower = ev.getPlayer().getGameProfile().getId();
is.set(WDDataComponents.THROWER_MSB.get(), thrower.getMostSignificantBits());
is.set(WDDataComponents.THROWER_LSB.get(), thrower.getLeastSignificantBits());
is.set(WDDataComponents.THROW_HEIGHT.get(), (float) (ev.getPlayer().getY() + ev.getPlayer().getEyeHeight()));
}
}
}
@SubscribeEvent
public void onPlayerCraft(PlayerEvent.ItemCraftedEvent ev) {
if(CommonConfig.hardRecipes && ItemRegistry.isCompCraftItem(ev.getCrafting().getItem()) && (CraftComponent.EXTCARD.makeItemStack().is(ev.getCrafting().getItem()))) {
if((ev.getEntity() instanceof ServerPlayer && !hasPlayerAdvancement((ServerPlayer) ev.getEntity(), ADV_PAD_BREAK)) || PROXY.hasClientPlayerAdvancement(ADV_PAD_BREAK) != HasAdvancement.YES) {
ev.getCrafting().setDamageValue(CraftComponent.BADEXTCARD.ordinal());
if(!ev.getEntity().level().isClientSide)
ev.getEntity().level().playSound(null, ev.getEntity().getX(), ev.getEntity().getY(), ev.getEntity().getZ(), SoundEvents.ITEM_BREAK, SoundSource.MASTER, 1.0f, 1.0f);
}
}
}
@SubscribeEvent
public void onServerStop(ServerStoppingEvent ev) throws IOException {
Server.getInstance().stopServer();
}
@SubscribeEvent
public void onLogIn(PlayerEvent.PlayerLoggedInEvent ev) {
if (!CommonConfig.joinMessage) {
return;
}
if(!ev.getEntity().level().isClientSide && ev.getEntity() instanceof ServerPlayer) {
IWDDCapability cap = ev.getEntity().getData(WDDCapability.WDD_CAPABILITY.get());
if(cap.isFirstRun()) {
Util.toast(ev.getEntity(), ChatFormatting.LIGHT_PURPLE, "welcome1");
Util.toast(ev.getEntity(), ChatFormatting.LIGHT_PURPLE, "welcome2");
Util.toast(ev.getEntity(), ChatFormatting.LIGHT_PURPLE, "welcome3");
cap.clearFirstRun();
}
S2CMessageServerInfo message = new S2CMessageServerInfo(miniservPort);
WDNetworkRegistry.INSTANCE.send((ServerPlayer) ev.getEntity(), message);
}
}
@SubscribeEvent
public void onLogOut(PlayerEvent.PlayerLoggedOutEvent ev) {
if(!ev.getEntity().level().isClientSide)
Server.getInstance().getClientManager().revokeClientKey(ev.getEntity().getGameProfile().getId());
}
@SubscribeEvent
public void onPlayerClone(PlayerEvent.Clone ev) {
IWDDCapability src = ev.getOriginal().getData(WDDCapability.WDD_CAPABILITY.get());
IWDDCapability dst = ev.getEntity().getData(WDDCapability.WDD_CAPABILITY.get());
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().getString().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")) {
Player ply = ev.getPlayer();
ply.level().playSound(null, ply.getX(), ply.getY(), ply.getZ(), soundIronic, SoundSource.PLAYERS, 1.0f, 1.0f);
}
}
// TODO: Update ClientChatEvent for NeoForge 1.21.1
// @SubscribeEvent
// public void onClientChat(ClientChatEvent ev) {
// if(ev.getMessage().equals("!WD render recipes"))
// PROXY.renderRecipes();
// }
private boolean hasPlayerAdvancement(ServerPlayer ply, ResourceLocation rl) {
MinecraftServer server = PROXY.getServer();
if(server == null)
return false;
AdvancementHolder adv = server.getAdvancements().get(rl);
return adv != null && ply.getAdvancements().getOrStartProgress(adv).isDone();
}
public static int getNextAvailablePadID() {
return new WebDisplays().lastPadId++;
}
public static DeferredRegister<SoundEvent> SOUNDS = DeferredRegister.create(Registries.SOUND_EVENT, "webdisplays");
private static SoundEvent registerSound(String resName) {
ResourceLocation resLoc = ResourceLocation.fromNamespaceAndPath("webdisplays", resName);
SoundEvent ret = SoundEvent.createVariableRangeEvent(resLoc);
SOUNDS.register(resName, () -> ret);
return ret;
}
// Removed unused helper for older advancement API registration
// 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));
for (String str : CommonConfig.Browser.blacklist)
if (str.equalsIgnoreCase(url2.getHost())) return true;
return false;
} catch(MalformedURLException ex) {
return false;
}
}
public static String applyBlacklist(String url) {
return isSiteBlacklisted(url) ? BLACKLIST_URL : url;
}
}

View File

@@ -0,0 +1,146 @@
/*
* Copyright (C) 2019 BARBOTIN Nicolas
*/
package net.montoyo.wd.block;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.DirectionProperty;
import net.minecraft.world.level.block.state.properties.EnumProperty;
import net.minecraft.world.level.block.state.properties.Property;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.neoforged.neoforge.network.PacketDistributor;
import net.montoyo.wd.core.DefaultPeripheral;
import net.montoyo.wd.entity.KeyboardBlockEntity;
import net.montoyo.wd.item.ItemLinker;
import net.montoyo.wd.net.WDNetworkRegistry;
import net.montoyo.wd.net.client_bound.S2CMessageCloseGui;
import org.jetbrains.annotations.NotNull;
public class KeyboardBlockLeft extends PeripheralBlock {
public static final EnumProperty<DefaultPeripheral> TYPE = EnumProperty.create("type", DefaultPeripheral.class);
public static final DirectionProperty FACING = DirectionProperty.create("facing", Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST);
// public static final DirectionProperty HALF = DirectionProperty.create("facing", Direction.EAST, Direction.WEST);
public static final VoxelShape[] KEYBOARD_AABBS = new VoxelShape[]{
Shapes.box(0.0, 0.0, 3.0 / 16, 1.0, 1.0 / 16.0, 1.0),
Shapes.box(0.0, 0.0, 0.0, 1.0, 1.0 / 16.0, 13 / 16.0),
Shapes.box(3.0 / 16, 0.0, 0.0, 1.0, 1.0 / 16.0, 1.0),
Shapes.box(0.0, 0.0, 0.0, 13 / 16.0, 1.0 / 16.0, 1.0),
};
private static final Property<?>[] properties = new Property<?>[] {TYPE, FACING};
public KeyboardBlockLeft() {
super(DefaultPeripheral.KEYBOARD);
}
// TODO: make non static (for extensibility purposes)
public static KeyboardBlockEntity getTileEntity(BlockState state, Level world, BlockPos pos) {
if (state.getBlock() instanceof KeyboardBlockLeft) {
BlockEntity te = world.getBlockEntity(pos); // TODO: check?
if (te instanceof KeyboardBlockEntity)
return (KeyboardBlockEntity) te;
}
BlockPos relative = pos.relative(KeyboardBlockLeft.mapDirection(state.getValue(FACING).getOpposite()));
BlockState ns = world.getBlockState(relative);
if (ns.getBlock() instanceof PeripheralBlock) {
BlockEntity te = world.getBlockEntity(relative); // TODO: check?
if (te instanceof KeyboardBlockEntity)
return (KeyboardBlockEntity) te;
}
return null;
}
public static Direction mapDirection(Direction facing) {
return switch (facing) {
case NORTH -> Direction.EAST;
case EAST -> Direction.SOUTH;
case SOUTH -> Direction.WEST;
case WEST -> Direction.NORTH;
default -> facing;
};
}
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(properties);
}
@Override
public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
double rpos = (entity.getY() - ((double) pos.getY())) * 16.0;
if (!world.isClientSide && rpos >= 1.0 && rpos <= 2.0 && Math.random() < 0.25) {
KeyboardBlockEntity tek = KeyboardBlockLeft.getTileEntity(state, world, pos);
if (tek != null)
tek.simulateCat(entity);
}
}
@Override
protected @NotNull InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) {
// Note: useWithoutItem doesn't have InteractionHand parameter, so we use MAIN_HAND as default
InteractionHand hand = InteractionHand.MAIN_HAND;
if (player.getItemInHand(hand).getItem() instanceof ItemLinker)
return InteractionResult.PASS;
KeyboardBlockEntity tek = KeyboardBlockLeft.getTileEntity(state, level, pos);
if (tek != null)
return tek.onRightClick(player, hand);
return InteractionResult.PASS;
}
@Override
public VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
return KEYBOARD_AABBS[state.getValue(FACING).ordinal() - 2];
}
@Override
public VoxelShape getOcclusionShape(BlockState arg, BlockGetter arg2, BlockPos arg3) {
return Shapes.empty();
}
private static void removeRightPiece(BlockState state, Level world, BlockPos pos) {
BlockPos relative = pos.relative(KeyboardBlockLeft.mapDirection(state.getValue(FACING)));
BlockState ns = world.getBlockState(relative);
if (ns.getBlock() instanceof KeyboardBlockRight)
world.setBlock(relative, Blocks.AIR.defaultBlockState(), 3);
}
public static void remove(BlockState state, Level world, BlockPos pos, boolean setState, boolean drop) {
removeRightPiece(state, world, pos);
if (setState)
world.setBlock(pos, Blocks.AIR.defaultBlockState(), 3);
// TODO: Rewrite for new networking system
// WDNetworkRegistry.INSTANCE.send(PacketDistributor.NEAR.with(() -> point(world, pos)), new S2CMessageCloseGui(pos));
}
@Override
public void onRemove(BlockState arg, Level arg2, BlockPos arg3, BlockState arg4, boolean bl) {
if (!arg2.isClientSide)
remove(arg, arg2, arg3, false, false);
super.onRemove(arg, arg2, arg3, arg4, bl);
}
}

View File

@@ -0,0 +1,119 @@
/*
* Copyright (C) 2018 BARBOTIN Nicolas
*/
package net.montoyo.wd.block;
import net.minecraft.core.BlockPos;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BlockStateProperties;
import net.minecraft.world.level.block.state.properties.DirectionProperty;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.neoforged.neoforge.network.PacketDistributor;
import net.montoyo.wd.core.IPeripheral;
import net.montoyo.wd.entity.KeyboardBlockEntity;
import net.montoyo.wd.item.ItemLinker;
import net.montoyo.wd.net.WDNetworkRegistry;
import net.montoyo.wd.net.client_bound.S2CMessageCloseGui;
import net.montoyo.wd.utilities.data.BlockSide;
import net.montoyo.wd.utilities.math.Vector3i;
import org.jetbrains.annotations.NotNull;
import static net.montoyo.wd.block.KeyboardBlockLeft.KEYBOARD_AABBS;
// TODO: merge into KeyboardLeft
public class KeyboardBlockRight extends Block implements IPeripheral {
public static final DirectionProperty FACING = BlockStateProperties.HORIZONTAL_FACING;
public KeyboardBlockRight() {
super(Properties.ofFullCopy(Blocks.STONE)
.strength(1.5f, 10.f));
}
private static void removeLeftPiece(BlockState state, Level world, BlockPos pos) {
BlockPos relative = pos.relative(KeyboardBlockLeft.mapDirection(state.getValue(FACING).getOpposite()));
BlockState ns = world.getBlockState(relative);
if (ns.getBlock() instanceof KeyboardBlockLeft)
world.setBlock(relative, Blocks.AIR.defaultBlockState(), 3);
}
public static void remove(BlockState state, Level world, BlockPos pos, boolean setState, boolean drop) {
removeLeftPiece(state, world, pos);
if (setState)
world.setBlock(pos, Blocks.AIR.defaultBlockState(), 3);
// TODO: Rewrite for new networking system
// WDNetworkRegistry.INSTANCE.send(PacketDistributor.NEAR.with(() -> point(world, pos)), new S2CMessageCloseGui(pos));
}
@Override
public void onRemove(BlockState arg, Level arg2, BlockPos arg3, BlockState arg4, boolean bl) {
if (!arg2.isClientSide)
remove(arg, arg2, arg3, false, false);
super.onRemove(arg, arg2, arg3, arg4, bl);
}
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(FACING);
}
@Override
public boolean isCollisionShapeFullBlock(BlockState state, BlockGetter level, BlockPos pos) {
return false;
}
@Override
public VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
return KEYBOARD_AABBS[state.getValue(FACING).ordinal() - 2];
}
@Override
public boolean connect(Level world, BlockPos pos, BlockState state, Vector3i scrPos, BlockSide scrSide) {
KeyboardBlockEntity keyboard = KeyboardBlockLeft.getTileEntity(state, world, pos);
return keyboard != null && keyboard.connect(world, pos, state, scrPos, scrSide);
}
@Override
public void entityInside(BlockState state, Level world, BlockPos pos, Entity entity) {
double rpos = (entity.getY() - ((double) pos.getY())) * 16.0;
if (!world.isClientSide && rpos >= 1.0 && rpos <= 2.0 && Math.random() < 0.25) {
KeyboardBlockEntity tek = KeyboardBlockLeft.getTileEntity(state, world, pos);
if (tek != null)
tek.simulateCat(entity);
}
}
@Override
protected @NotNull InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) {
// Note: useWithoutItem doesn't have InteractionHand parameter, so we use MAIN_HAND as default
InteractionHand hand = InteractionHand.MAIN_HAND;
if (player.getItemInHand(hand).getItem() instanceof ItemLinker)
return InteractionResult.PASS;
KeyboardBlockEntity tek = KeyboardBlockLeft.getTileEntity(state, level, pos);
if (tek != null)
return tek.onRightClick(player, hand);
return InteractionResult.PASS;
}
@Override
public VoxelShape getOcclusionShape(BlockState arg, BlockGetter arg2, BlockPos arg3) {
return Shapes.empty();
}
}

View File

@@ -0,0 +1,160 @@
/*
* Copyright (C) 2019 BARBOTIN Nicolas
*/
package net.montoyo.wd.block;
import com.mojang.serialization.MapCodec;
import net.minecraft.core.BlockPos;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Explosion;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.BaseEntityBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.RenderShape;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.PushReaction;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.shapes.CollisionContext;
import net.minecraft.world.phys.shapes.Shapes;
import net.minecraft.world.phys.shapes.VoxelShape;
import net.neoforged.neoforge.network.PacketDistributor;
import net.montoyo.wd.core.DefaultPeripheral;
import net.montoyo.wd.entity.AbstractInterfaceBlockEntity;
import net.montoyo.wd.entity.AbstractPeripheralBlockEntity;
import net.montoyo.wd.entity.ServerBlockEntity;
import net.montoyo.wd.item.ItemLinker;
import net.montoyo.wd.net.WDNetworkRegistry;
import net.montoyo.wd.net.client_bound.S2CMessageCloseGui;
import net.montoyo.wd.utilities.Log;
import org.jetbrains.annotations.Nullable;
public class PeripheralBlock extends WDContainerBlock {
public static final MapCodec<PeripheralBlock> CODEC = simpleCodec(properties -> new PeripheralBlock(DefaultPeripheral.KEYBOARD));
DefaultPeripheral type;
public PeripheralBlock(DefaultPeripheral type) {
super(BlockBehaviour.Properties.ofFullCopy(Blocks.STONE).strength(1.5f, 10.f));
this.type = type;
}
@Override
protected MapCodec<? extends BaseEntityBlock> codec() {
return CODEC;
}
@Nullable
@Override
public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
BlockEntityType.BlockEntitySupplier<? extends BlockEntity> cls = type.getTEClass();
if (cls == null)
return null;
try {
return cls.create(pos, state);
} catch (Throwable t) {
Log.errorEx("Couldn't instantiate peripheral TileEntity:", t);
}
return null;
}
@Override
public RenderShape getRenderShape(BlockState state) {
return RenderShape.MODEL;
}
@Override
protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) {
if (player.isShiftKeyDown())
return InteractionResult.FAIL;
// Note: useWithoutItem doesn't have InteractionHand parameter, so we use MAIN_HAND as default
InteractionHand hand = InteractionHand.MAIN_HAND;
if (player.getItemInHand(hand).getItem() instanceof ItemLinker)
return InteractionResult.FAIL;
BlockEntity te = level.getBlockEntity(pos);
if (te instanceof AbstractPeripheralBlockEntity)
return ((AbstractPeripheralBlockEntity) te).onRightClick(player, hand);
else if (te instanceof ServerBlockEntity) {
((ServerBlockEntity) te).onPlayerRightClick(player);
return InteractionResult.SUCCESS;
} else
return InteractionResult.FAIL;
}
@Override
public VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) {
return Shapes.block();
}
@Override
public void setPlacedBy(Level world, BlockPos pos, BlockState state, @Nullable LivingEntity placer, ItemStack stack) {
if (world.isClientSide)
return;
if (placer instanceof Player) {
BlockEntity te = world.getBlockEntity(pos);
if (te instanceof ServerBlockEntity)
((ServerBlockEntity) te).setOwner((Player) placer);
else if (te instanceof AbstractInterfaceBlockEntity)
((AbstractInterfaceBlockEntity) te).setOwner((Player) placer);
}
}
@Override
public PushReaction getPistonPushReaction(BlockState state) {
return PushReaction.IGNORE;
}
@Override
public void neighborChanged(BlockState state, Level world, BlockPos pos, Block neighborType, BlockPos neighbor, boolean isMoving) {
BlockEntity te = world.getBlockEntity(pos);
if (te instanceof AbstractPeripheralBlockEntity)
((AbstractPeripheralBlockEntity) te).onNeighborChange(neighborType, neighbor);
}
@Override
public void playerDestroy(Level world, Player player, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, ItemStack tool) {
if (!world.isClientSide) {
// TODO: Rewrite for new networking system
// WDNetworkRegistry.INSTANCE.send(PacketDistributor.NEAR.with(() -> point(world, pos)), new S2CMessageCloseGui(pos));
}
super.playerDestroy(world, player, pos, state, blockEntity, tool);
}
@Override
public void onBlockExploded(BlockState state, Level level, BlockPos pos, Explosion explosion) {
playerDestroy(level, null, pos, level.getBlockState(pos), null, null);
}
// Helper method to send packets to players near a position
public static void sendToPlayersNear(Level world, BlockPos bp, CustomPacketPayload payload) {
if (world instanceof ServerLevel serverLevel) {
PacketDistributor.sendToPlayersNear(serverLevel, null, bp.getX(), bp.getY(), bp.getZ(), 64.0, payload);
}
}
public static void sendToPlayersNear(Player exclude, Level world, BlockPos bp, CustomPacketPayload payload) {
if (world instanceof ServerLevel serverLevel) {
PacketDistributor.sendToPlayersNear(serverLevel, (ServerPlayer) exclude, bp.getX(), bp.getY(), bp.getZ(), 64.0, payload);
}
}
}

View File

@@ -0,0 +1,353 @@
/*
* Copyright (C) 2018 BARBOTIN Nicolas
*/
package net.montoyo.wd.block;
import com.mojang.serialization.MapCodec;
import net.minecraft.ChatFormatting;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResult;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.BaseEntityBlock;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.RenderShape;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.block.state.properties.BooleanProperty;
import net.minecraft.world.level.block.state.properties.Property;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.material.PushReaction;
import net.minecraft.world.phys.BlockHitResult;
import net.montoyo.wd.WebDisplays;
import net.montoyo.wd.config.CommonConfig;
import net.montoyo.wd.core.DefaultUpgrade;
import net.montoyo.wd.core.IUpgrade;
import net.montoyo.wd.core.ScreenRights;
import net.montoyo.wd.data.SetURLData;
import net.montoyo.wd.entity.ScreenData;
import net.montoyo.wd.entity.ScreenBlockEntity;
import net.montoyo.wd.item.ItemLaserPointer;
import net.montoyo.wd.utilities.*;
import net.montoyo.wd.utilities.math.Vector2i;
import net.montoyo.wd.utilities.math.Vector3f;
import net.montoyo.wd.utilities.math.Vector3i;
import net.montoyo.wd.utilities.data.BlockSide;
import net.montoyo.wd.utilities.serialization.Util;
import org.jetbrains.annotations.NotNull;
public class ScreenBlock extends BaseEntityBlock {
public static final MapCodec<ScreenBlock> CODEC = simpleCodec(ScreenBlock::new);
public static final BooleanProperty hasTE = BooleanProperty.create("haste");
public static final BooleanProperty emitting = BooleanProperty.create("emitting");
private static final Property<?>[] properties = new Property<?>[]{hasTE, emitting};
public ScreenBlock(Properties properties) {
super(properties.strength(1.5f, 10.f));
this.registerDefaultState(this.defaultBlockState().setValue(hasTE, false).setValue(emitting, false));
}
@Override
protected MapCodec<? extends BaseEntityBlock> codec() {
return CODEC;
}
@Override
public void onRemove(BlockState p_60515_, Level p_60516_, BlockPos p_60517_, BlockState p_60518_, boolean p_60519_) {
// TODO: make this also get called on client?
if (p_60518_.getBlock() == p_60515_.getBlock()) return;
for (BlockSide value : BlockSide.values()) {
Vector3i vec = new Vector3i(p_60517_.getX(), p_60517_.getY(), p_60517_.getZ());
Multiblock.findOrigin(p_60516_, vec, value, null);
BlockPos bp = new BlockPos(vec.x, vec.y, vec.z);
if (!bp.equals(p_60517_)) {
p_60516_.removeBlockEntity(bp);
p_60516_.setBlock(
bp, p_60516_.getBlockState(bp).setValue(hasTE, false),
11
);
}
}
super.onRemove(p_60515_, p_60516_, p_60517_, p_60518_, p_60519_);
}
@Override
protected InteractionResult useWithoutItem(BlockState state, Level level, BlockPos pos, Player player, BlockHitResult hitResult) {
// Note: useWithoutItem doesn't have InteractionHand parameter, so we use MAIN_HAND as default
InteractionHand hand = InteractionHand.MAIN_HAND;
ItemStack heldItem = player.getItemInHand(hand);
boolean isUpgrade = false;
if (heldItem.isEmpty())
heldItem = null; //Easier to work with
else if (!(isUpgrade = heldItem.getItem() instanceof IUpgrade))
return InteractionResult.FAIL;
else if (heldItem.getItem() instanceof ItemLaserPointer)
return InteractionResult.FAIL; // laser pointer already handles stuff
// handling the off hand leads to double clicking
if (!isUpgrade && hand == InteractionHand.OFF_HAND)
return InteractionResult.FAIL;
if (level.isClientSide)
return InteractionResult.FAIL;
boolean sneaking = player.isShiftKeyDown();
Vector3i screenPos = new Vector3i(pos);
BlockSide side = BlockSide.values()[hitResult.getDirection().ordinal()];
Multiblock.findOrigin(level, screenPos, side, null);
ScreenBlockEntity te = (ScreenBlockEntity) level.getBlockEntity(screenPos.toBlock());
if (te != null && te.getScreen(side) != null) {
ScreenData scr = te.getScreen(side);
if (sneaking) { //Right Click
if ((scr.rightsFor(player) & ScreenRights.CHANGE_URL) == 0)
Util.toast(player, "restrictions");
else
(new SetURLData(screenPos, scr.side, scr.url)).sendTo((ServerPlayer) player);
return InteractionResult.SUCCESS;
} else if (heldItem != null) {
if (!te.hasUpgrade(side, heldItem)) {
if ((scr.rightsFor(player) & ScreenRights.MANAGE_UPGRADES) == 0) {
Util.toast(player, "restrictions");
return InteractionResult.CONSUME;
}
if (te.addUpgrade(side, heldItem, player, false)) {
if (!player.isCreative())
heldItem.shrink(1);
Util.toast(player, ChatFormatting.AQUA, "upgradeOk");
if (player instanceof ServerPlayer)
WebDisplays.INSTANCE.criterionUpgradeScreen.trigger((ServerPlayer) player);
} else
Util.toast(player, "upgradeError");
return InteractionResult.CONSUME;
}
} else {
if ((scr.rightsFor(player) & ScreenRights.INTERACT) == 0) {
Util.toast(player, "restrictions");
return InteractionResult.CONSUME;
}
Vector2i tmp = new Vector2i();
float hitX = ((float) hitResult.getLocation().x) - (float) te.getBlockPos().getX();
float hitY = ((float) hitResult.getLocation().y) - (float) te.getBlockPos().getY();
float hitZ = ((float) hitResult.getLocation().z) - (float) te.getBlockPos().getZ();
if (hit2pixels(side, hitResult.getBlockPos(), new Vector3i(hitResult.getBlockPos()), scr, hitX, hitY, hitZ, tmp))
te.click(side, tmp);
return InteractionResult.CONSUME;
}
}
// else if(sneaking) {
// Util.toast(player, "turnOn");
// return InteractionResult.SUCCESS;
// }
Vector2i size = Multiblock.measure(level, screenPos, side);
if (size.x < 2 && size.y < 2) {
Util.toast(player, "tooSmall");
return InteractionResult.SUCCESS;
}
if (size.x > CommonConfig.Screen.maxScreenSizeX || size.y > CommonConfig.Screen.maxScreenSizeY) {
Util.toast(player, "tooBig", CommonConfig.Screen.maxScreenSizeX, CommonConfig.Screen.maxScreenSizeY);
return InteractionResult.SUCCESS;
}
Vector3i err = Multiblock.check(level, screenPos, size, side);
if (err != null) {
Util.toast(player, "invalid", err.toString());
return InteractionResult.SUCCESS;
}
boolean created = false;
Log.info("Player %s (UUID %s) created a screen at %s of size %dx%d", player.getName(), player.getGameProfile().getId().toString(), screenPos.toString(), size.x, size.y);
if (te == null) {
BlockPos bp = screenPos.toBlock();
level.setBlockAndUpdate(bp, level.getBlockState(bp).setValue(hasTE, true));
te = (ScreenBlockEntity) level.getBlockEntity(bp);
created = true;
}
te.addScreen(side, size, null, player, true);
return InteractionResult.SUCCESS;
}
@Override
public void neighborChanged(BlockState state, Level world, BlockPos pos, Block block, BlockPos source,
boolean isMoving) {
if (block != this && !world.isClientSide && !state.getValue(emitting)) {
for (BlockSide side : BlockSide.values()) {
Vector3i vec = new Vector3i(pos);
Multiblock.findOrigin(world, vec, side, null);
ScreenBlockEntity tes = (ScreenBlockEntity) world.getBlockEntity(vec.toBlock());
if (tes != null && tes.hasUpgrade(side, DefaultUpgrade.REDINPUT)) {
Direction facing = Direction.from2DDataValue(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.getSignal(pos, facing));
}
}
}
}
public static boolean hit2pixels(BlockSide side, BlockPos bpos, Vector3i pos, ScreenData 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(hitX, hitY, hitZ);
// this dot is acting as a "get distance from plane" where the plane is the edge of the screen
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;
}
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;
}
/************************************************* DESTRUCTION HANDLING *************************************************/
private void onDestroy(Level world, BlockPos pos, Player ply) {
if (!world.isClientSide) {
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(Level world, Vector3i pos, BlockSide side, Multiblock.BlockOverride override, Player
source) {
Multiblock.findOrigin(world, pos, side, override);
BlockPos bp = pos.toBlock();
BlockEntity te = world.getBlockEntity(bp);
if (te instanceof ScreenBlockEntity) {
((ScreenBlockEntity) te).onDestroy(source);
world.setBlock(bp, world.getBlockState(bp).setValue(hasTE, false), Block.UPDATE_ALL_IMMEDIATE); //Destroy tile entity.
}
}
@Override
public boolean onDestroyedByPlayer(BlockState state, Level level, BlockPos pos, Player player,
boolean willHarvest, FluidState fluid) {
onDestroy(level, pos, player);
return super.onDestroyedByPlayer(state, level, pos, player, willHarvest, fluid);
}
@Override
public void setPlacedBy(Level world, @NotNull BlockPos pos, @NotNull BlockState
state, @org.jetbrains.annotations.Nullable LivingEntity whoDidThisShit, @NotNull ItemStack stack) {
if (world.isClientSide)
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 ScreenBlock) {
for (BlockSide bs : BlockSide.values())
destroySide(world, neighbor.clone(), bs, override, (whoDidThisShit instanceof Player) ? ((Player) whoDidThisShit) : null);
}
}
}
/************************************************* STUFF THAT'S UNLIKELY TO BE TOUCHED BUT NEEDS TO BE HERE *************************************************/
@Override
public @NotNull PushReaction getPistonPushReaction(BlockState state) {
return PushReaction.IGNORE;
}
@Override
public int getSignal(BlockState state, BlockGetter level, BlockPos pos, Direction direction) {
return state.getValue(emitting) ? 15 : 0;
}
@Override
public boolean isSignalSource(BlockState state) {
return state.getValue(emitting);
}
@Override
public BlockEntity newBlockEntity(BlockPos pos, BlockState state) {
return state.getValue(hasTE) ? new ScreenBlockEntity(pos, state) : null;
}
@Override
public RenderShape getRenderShape(BlockState state) {
return RenderShape.MODEL;
}
@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {
builder.add(properties);
}
}

View File

@@ -0,0 +1,22 @@
/*
* Copyright (C) 2018 BARBOTIN Nicolas
*/
package net.montoyo.wd.block;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.level.block.BaseEntityBlock;
public abstract class WDContainerBlock extends BaseEntityBlock {
protected static BlockItem itemBlock;
public WDContainerBlock(Properties arg) {
super(arg);
}
public BlockItem getItem() {
return itemBlock;
}
}

View File

@@ -0,0 +1,54 @@
package net.montoyo.wd.block.item;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.context.BlockPlaceContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.montoyo.wd.block.KeyboardBlockLeft;
import net.montoyo.wd.registry.BlockRegistry;
public class KeyboardItem extends BlockItem {
public KeyboardItem(Block arg, Properties arg2) {
super(arg, arg2);
}
@Override
protected boolean placeBlock(BlockPlaceContext arg, BlockState arg2) {
Direction facing = arg.getHorizontalDirection();
arg2 = arg2.setValue(KeyboardBlockLeft.FACING, facing);
Direction d = KeyboardBlockLeft.mapDirection(facing);
if (isValid(arg.getClickedPos(), arg.getLevel(), arg2, d)) {
Block kbRight = BlockRegistry.blockKbRight.get();
BlockState rightState = kbRight.defaultBlockState();
rightState = rightState.setValue(KeyboardBlockLeft.FACING, facing);
if (!arg.getLevel().setBlock(
arg.getClickedPos().relative(d),
rightState,
11
)) return false;
return arg.getLevel().setBlock(arg.getClickedPos(), arg2, 11);// 161
} else if (isValid(arg.getClickedPos().relative(d.getOpposite(), 2), arg.getLevel(), arg2, d)) {
Block kbRight = BlockRegistry.blockKbRight.get();
BlockState rightState = kbRight.defaultBlockState();
rightState = rightState.setValue(KeyboardBlockLeft.FACING, facing);
if (!arg.getLevel().setBlock(
arg.getClickedPos(),
rightState,
11
)) return false;
return arg.getLevel().setBlock(arg.getClickedPos().relative(d.getOpposite()), arg2, 11);// 161
}
return false;
}
private boolean isValid(BlockPos pos, Level level, BlockState state, Direction d) {
return level.getBlockState(pos.relative(d)).isAir();
}
}

View File

@@ -0,0 +1,862 @@
/*
* Copyright (C) 2019 BARBOTIN Nicolas
*/
package net.montoyo.wd.client;
import com.cinemamod.mcef.MCEF;
import com.cinemamod.mcef.MCEFBrowser;
import com.mojang.authlib.GameProfile;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.platform.InputConstants;
import com.mojang.blaze3d.systems.RenderSystem;
import net.minecraft.advancements.Advancement;
import net.minecraft.advancements.AdvancementHolder;
import net.minecraft.advancements.AdvancementProgress;
import net.minecraft.client.Camera;
import net.minecraft.client.KeyMapping;
import net.minecraft.client.Minecraft;
import net.minecraft.client.Options;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen;
import net.minecraft.client.multiplayer.ClientAdvancements;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderers;
import net.minecraft.core.BlockPos;
import net.minecraft.core.NonNullList;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.packs.resources.ReloadableResourceManager;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.server.packs.resources.ResourceManagerReloadListener;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.HumanoidArm;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.inventory.Slot;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.ClipContext;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec3;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.neoforge.client.event.ModelEvent;
import net.neoforged.neoforge.client.event.RegisterKeyMappingsEvent;
import net.neoforged.neoforge.client.event.RenderHandEvent;
import net.neoforged.neoforge.client.event.RenderHighlightEvent;
import net.neoforged.neoforge.common.NeoForge;
import net.neoforged.neoforge.event.tick.LevelTickEvent;
import net.neoforged.neoforge.client.event.ClientTickEvent;
import net.neoforged.neoforge.event.level.LevelEvent;
import net.neoforged.bus.api.SubscribeEvent;
import net.neoforged.fml.LogicalSide;
import net.neoforged.fml.common.Mod;
import net.neoforged.fml.common.EventBusSubscriber;
import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent;
import net.neoforged.neoforge.network.handling.IPayloadContext;
import net.montoyo.wd.SharedProxy;
import net.montoyo.wd.WebDisplays;
import net.montoyo.wd.block.ScreenBlock;
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.HasAdvancement;
import net.montoyo.wd.data.GuiData;
import net.montoyo.wd.entity.ScreenBlockEntity;
import net.montoyo.wd.entity.ScreenData;
import net.montoyo.wd.item.ItemLaserPointer;
import net.montoyo.wd.item.ItemMinePad2;
import net.montoyo.wd.item.WDItem;
import net.montoyo.wd.miniserv.client.Client;
import net.montoyo.wd.registry.BlockRegistry;
import net.montoyo.wd.registry.ItemRegistry;
import net.montoyo.wd.registry.TileRegistry;
import net.montoyo.wd.utilities.Log;
import net.montoyo.wd.utilities.Multiblock;
import net.montoyo.wd.utilities.browser.WDBrowser;
import net.montoyo.wd.utilities.browser.handlers.DisplayHandler;
import net.montoyo.wd.utilities.browser.handlers.WDRouter;
import net.montoyo.wd.utilities.data.BlockSide;
import net.montoyo.wd.utilities.data.Rotation;
import net.montoyo.wd.utilities.math.Vector2i;
import net.montoyo.wd.utilities.math.Vector3i;
import net.montoyo.wd.utilities.serialization.NameUUIDPair;
import net.montoyo.wd.data.WDDataComponents;
import org.cef.browser.CefBrowser;
import org.cef.browser.CefMessageRouter;
import org.cef.misc.CefCursorType;
import org.lwjgl.glfw.GLFW;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.*;
public class ClientProxy extends SharedProxy implements ResourceManagerReloadListener {
private static ClientProxy INSTANCE;
public ClientProxy() {
INSTANCE = this;
}
public static void renderCrosshair(Options options, int screenWidth, int screenHeight, int offset, GuiGraphics poseStack, CallbackInfo ci) {
ItemStack stack = Minecraft.getInstance().player.getItemInHand(InteractionHand.MAIN_HAND);
ItemStack stack1 = Minecraft.getInstance().player.getItemInHand(InteractionHand.OFF_HAND);
if (stack.getItem() instanceof ItemMinePad2) {
float sign = 1;
if (Minecraft.getInstance().player.getMainArm() == HumanoidArm.LEFT) sign = -1;
if (!MinePadRenderer.renderAtSide(sign)) {
ci.cancel();
return;
}
} else {
if (stack1.getItem() instanceof ItemMinePad2) {
float sign = -1;
if (Minecraft.getInstance().player.getMainArm() == HumanoidArm.LEFT) sign = 1;
if (!MinePadRenderer.renderAtSide(sign)) {
ci.cancel();
return;
}
}
}
if (!(stack.getItem() instanceof ItemLaserPointer ||
stack1.getItem() instanceof ItemLaserPointer))
return;
if (!LaserPointerRenderer.isOn()) {
RenderSystem.blendFuncSeparate(GlStateManager.SourceFactor.ONE_MINUS_DST_COLOR, GlStateManager.DestFactor.ONE_MINUS_SRC_COLOR, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO);
poseStack.blit(ResourceLocation.fromNamespaceAndPath(
"webdisplays", "textures/gui/cursors.png"
), (screenWidth - 15) / 2, (screenHeight - 15) / 2, offset, 240, 240, 15, 15, 256, 256);
ci.cancel();
return;
}
Minecraft mc = Minecraft.getInstance();
BlockHitResult result = raycast(64.0); //TODO: Make that distance configurable
BlockPos bpos = result.getBlockPos();
if (result.getType() != HitResult.Type.BLOCK || mc.level.getBlockState(bpos).getBlock() != BlockRegistry.SCREEN_BLOCk.get()) {
RenderSystem.blendFuncSeparate(GlStateManager.SourceFactor.ONE_MINUS_DST_COLOR, GlStateManager.DestFactor.ONE_MINUS_SRC_COLOR, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO);
poseStack.blit(ResourceLocation.fromNamespaceAndPath(
"webdisplays", "textures/gui/cursors.png"
), (screenWidth - 15) / 2, (screenHeight - 15) / 2, offset, 240, 240, 15, 15, 256, 256);
ci.cancel();
return;
}
Vector3i pos = new Vector3i(result.getBlockPos());
BlockSide side = BlockSide.values()[result.getDirection().ordinal()];
Multiblock.findOrigin(mc.level, pos, side, null);
ScreenBlockEntity te = (ScreenBlockEntity) mc.level.getBlockEntity(pos.toBlock());
ScreenData sc = te.getScreen(side);
if (sc == null) return;
int coordX = sc.mouseType * 15;
int coordY = coordX / 255;
coordX -= coordY * 255;
coordY *= 15;
// for some reason, the cursor gets offset at this value
if (sc.mouseType >= CefCursorType.NOT_ALLOWED.ordinal()) coordX -= 15;
RenderSystem.blendFuncSeparate(GlStateManager.SourceFactor.ONE_MINUS_DST_COLOR, GlStateManager.DestFactor.ONE_MINUS_SRC_COLOR, GlStateManager.SourceFactor.ONE, GlStateManager.DestFactor.ZERO);
poseStack.blit(ResourceLocation.fromNamespaceAndPath(
"webdisplays", "textures/gui/cursors.png"
), (screenWidth - 15) / 2, (screenHeight - 15) / 2, offset, coordX, coordY, 15, 15, 256, 256);
ci.cancel();
}
public List<ScreenBlockEntity> getScreens() {
return screenTracking;
}
public List<PadData> getPads() {
return padList;
}
public class PadData {
public CefBrowser view;
public final UUID id;
private boolean isInHotbar;
private long lastURLSent;
public int activeCursor;
private PadData(String url, UUID id) {
String webUrl;
try {
webUrl = ScreenBlockEntity.url(url);
} catch (IOException e) {
throw new RuntimeException(e);
}
view = WDBrowser.createBrowser(WebDisplays.applyBlacklist(webUrl), false);
if (view instanceof MCEFBrowser browser) {
browser.resize((int) WebDisplays.INSTANCE.padResX, (int) WebDisplays.INSTANCE.padResY);
browser.setCursorChangeListener((cursor) -> {
activeCursor = cursor;
});
}
isInHotbar = true;
this.id = id;
}
public void updateTime() {
lastURLSent = System.currentTimeMillis();
}
public long lastSent() {
return lastURLSent;
}
}
private Minecraft mc;
private MinePadRenderer minePadRenderer;
private LaserPointerRenderer laserPointerRenderer;
private Screen nextScreen;
private boolean isF1Down;
//Miniserv handling
private int miniservPort;
private boolean msClientStarted;
//Client-side advancement hack
private final Field advancementToProgressField = findAdvancementToProgressField();
private ClientAdvancements lastAdvMgr;
private Map advancementToProgress;
//Tracking
private final ArrayList<ScreenBlockEntity> screenTracking = new ArrayList<>();
private int lastTracked = 0;
//MinePads Management
private final HashMap<UUID, PadData> padMap = new HashMap<>();
private final ArrayList<PadData> padList = new ArrayList<>();
private int minePadTickCounter = 0;
/**************************************** INHERITED METHODS ****************************************/
public static void onClientSetup(FMLClientSetupEvent event) {
BlockEntityRenderers.register(TileRegistry.SCREEN_BLOCK_ENTITY.get(), new ScreenRenderer.ScreenRendererProvider());
}
public static void onModelRegistryEvent(ModelEvent.RegisterGeometryLoaders event) {
event.register(ResourceLocation.fromNamespaceAndPath("webdisplays", "screen_loader"), new ScreenModelLoader());
}
@Override
public void preInit() {
super.preInit();
mc = Minecraft.getInstance();
NeoForge.EVENT_BUS.register(this);
}
@Override
public void onCefInit() {
minePadRenderer = new MinePadRenderer();
laserPointerRenderer = new LaserPointerRenderer();
if (!MCEF.isInitialized()) return;
MCEF.getApp().getHandle().registerSchemeHandlerFactory(
"webdisplays", "",
(browser, frame, url, request) -> {
// TODO: check if it's a webdisplays browser?
return new WDScheme(request.getURL());
}
);
MCEF.getClient().addDisplayHandler(DisplayHandler.INSTANCE);
MCEF.getClient().getHandle().addMessageRouter(CefMessageRouter.create(WDRouter.INSTANCE));
findAdvancementToProgressField();
}
@Override
public void postInit() {
((ReloadableResourceManager) mc.getResourceManager()).registerReloadListener(this);
}
@Override
public Level getWorld(ResourceKey<Level> dim) {
Level ret = mc.level;
// if(dim == CURRENT_DIMENSION)
// return ret;
if (ret != null) {
if (!ret.dimension().equals(dim))
throw new RuntimeException("Can't get non-current dimension " + dim + " from client.");
return ret;
} else {
throw new RuntimeException("Level on client is null");
}
}
@Override
public void enqueue(Runnable r) {
mc.submit(r);
}
@Override
public void displayGui(GuiData data) {
Screen gui = data.createGui(mc.screen, mc.level);
if (gui != null)
mc.setScreen(gui);
}
@Override
public void trackScreen(ScreenBlockEntity 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.screen != null && mc.screen instanceof WDScreen screen) {
if (pairs.length == 0)
(screen).onAutocompleteFailure();
else
(screen).onAutocompleteResult(pairs);
}
}
@Override
public GameProfile[] getOnlineGameProfiles() {
return new GameProfile[]{mc.player.getGameProfile()};
}
@Override
public void screenUpdateResolutionInGui(Vector3i pos, BlockSide side, Vector2i res) {
if (mc.screen != null && mc.screen instanceof GuiScreenConfig gsc) {
if (gsc.isForBlock(pos.toBlock(), side))
gsc.updateResolution(res);
}
}
@Override
public void screenUpdateRotationInGui(Vector3i pos, BlockSide side, Rotation rot) {
if (mc.screen != null && mc.screen instanceof GuiScreenConfig gsc) {
if (gsc.isForBlock(pos.toBlock(), side))
gsc.updateRotation(rot);
}
}
@Override
public void screenUpdateAutoVolumeInGui(Vector3i pos, BlockSide side, boolean av) {
if (mc.screen != null && mc.screen instanceof GuiScreenConfig gsc) {
if (gsc.isForBlock(pos.toBlock(), side))
gsc.updateAutoVolume(av);
}
}
@Override
public void displaySetPadURLGui(ItemStack is, String padURL) {
mc.setScreen(new GuiSetURL2(is, padURL));
}
@Override
public void openMinePadGui(UUID padId) {
PadData pd = padMap.get(padId);
if (pd != null && pd.view != null)
mc.setScreen(new GuiMinePad(pd));
}
@Override
@Nonnull
public HasAdvancement hasClientPlayerAdvancement(@Nonnull ResourceLocation rl) {
if (advancementToProgressField != null && mc.player != null && mc.player.connection != null) {
ClientAdvancements cam = mc.player.connection.getAdvancements();
AdvancementHolder adv = cam.get(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.getSingleplayerServer();
}
// @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.getConnection().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.screen instanceof WDScreen) {
WDScreen scr = (WDScreen) mc.screen;
if (scr.isForBlock(bp, bs))
mc.setScreen(null);
}
}
@Override
public void renderRecipes() {
nextScreen = new RenderRecipe();
}
@Override
public boolean isShiftDown() {
return Screen.hasShiftDown();
}
/**************************************** RESOURCE MANAGER METHODS ****************************************/
@Override
public void onResourceManagerReload(ResourceManager resourceManager) {
Log.info("Resource manager reload: clearing GUI cache...");
GuiLoader.clearCache();
}
/**************************************** 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 onLevelTick(LevelTickEvent.Post ev) {
if (!ev.getLevel().isClientSide()) return;
//Unload/load screens depending on client player distance
if (mc.player == null || screenTracking.isEmpty())
return;
int id = lastTracked % screenTracking.size();
ScreenBlockEntity tes = screenTracking.get(id);
if (!tes.getLevel().equals(ev.getLevel()))
return;
lastTracked++;
if (tes.getLevel() != mc.player.level()) {
// TODO: properly handle this
// probably gonna want a helper class for cross-dimensional distances
if (!tes.isLoaded())
tes.load();
} else {
Camera camera = mc.getEntityRenderDispatcher().camera;
Entity entity = null;
// ide inspection says this is a bunch of constant expressions
// THIS IS NOT THE CASE
// a crash HAS occurred because of this going unchecked, and I'm confused about it
//noinspection ConstantValue
if (camera != null) entity = camera.getEntity();
//noinspection ConstantValue
if (entity == null) entity = mc.player;
//noinspection ConstantValue
if (entity != null) {
double dist = distanceTo(tes, entity.getPosition(0));
if (tes.isLoaded()) {
if (dist > WebDisplays.INSTANCE.unloadDistance2 * 16)
tes.deactivate();
// else if (ClientConfig.AutoVolumeControl.enableAutoVolume)
// tes.updateTrackDistance(dist, 80); //ToDo find master volume
} else if (dist <= WebDisplays.INSTANCE.loadDistance2 * 16)
tes.activate();
}
}
}
@SubscribeEvent
public void onTick(ClientTickEvent.Post ev) {
//Help
if (InputConstants.isKeyDown(Minecraft.getInstance().getWindow().getWindow(), GLFW.GLFW_KEY_F1)) {
if (!isF1Down) {
isF1Down = true;
String wikiName = null;
if (mc.screen instanceof WDScreen)
wikiName = ((WDScreen) mc.screen).getWikiPageName();
else if (mc.screen instanceof AbstractContainerScreen) {
Slot slot = ((AbstractContainerScreen) mc.screen).getSlotUnderMouse();
if (slot != null && slot.hasItem() && slot.getItem().getItem() instanceof WDItem)
wikiName = ((WDItem) slot.getItem().getItem()).getWikiName(slot.getItem());
}
// 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.screen == null) {
mc.setScreen(nextScreen);
nextScreen = null;
}
// handle r button
if (KEY_MOUSE.isDown()) {
if (!rDown) {
rDown = true;
mouseOn = !mouseOn;
}
} else rDown = false;
if (
Minecraft.getInstance().player == null ||
!(Minecraft.getInstance().player.getItemInHand(InteractionHand.MAIN_HAND).getItem() instanceof ItemLaserPointer)
) mouseOn = false;
//Load/unload minePads depending on which item is in the player's hand
if (++minePadTickCounter >= 10) {
minePadTickCounter = 0;
Player ep = mc.player;
for (PadData pd : padList)
pd.isInHotbar = false;
if (ep != null) {
updateInventory(ep.getInventory().items, ep.getItemInHand(InteractionHand.MAIN_HAND), 9);
updateInventory(ep.getInventory().offhand, ep.getItemInHand(InteractionHand.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(true);
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
if (LaserPointerRenderer.isOn()) {
ItemLaserPointer.tick(mc);
} else {
ItemLaserPointer.deselect(mc);
}
//Miniserv
if (msClientStarted && mc.player == null) {
msClientStarted = false;
Client.getInstance().stop();
}
}
@SubscribeEvent
public void onRenderPlayerHand(RenderHandEvent ev) {
Item item = ev.getItemStack().getItem();
IItemRenderer renderer;
if (item == ItemRegistry.MINEPAD.get())
renderer = minePadRenderer;
else if (item == ItemRegistry.LASER_POINTER.get())
renderer = laserPointerRenderer;
else
return;
HumanoidArm handSide = mc.player.getMainArm();
if (ev.getHand() == InteractionHand.OFF_HAND)
handSide = handSide.getOpposite();
if (renderer.render(ev.getPoseStack(), ev.getItemStack(), (handSide == HumanoidArm.RIGHT) ? 1.0f : -1.0f, ev.getSwingProgress(), ev.getEquipProgress(), ev.getMultiBufferSource(), ev.getPackedLight())) {
ev.setCanceled(true);
}
}
@SubscribeEvent
public void onWorldUnload(LevelEvent.Unload ev) {
Log.info("World unloaded; killing screens...");
if (ev.getLevel() instanceof Level level) {
ResourceLocation dim = level.dimension().location();
for (int i = screenTracking.size() - 1; i >= 0; i--) {
if (screenTracking.get(i).getLevel().dimension().location().equals(dim)) //Could be world == ev.getWorld()
screenTracking.remove(i).unload();
}
}
}
public static BlockHitResult raycast(double dist) {
Minecraft mc = Minecraft.getInstance();
Vec3 start = mc.player.getEyePosition(1.0f);
Vec3 lookVec = mc.player.getLookAngle();
Vec3 end = start.add(lookVec.x * dist, lookVec.y * dist, lookVec.z * dist);
return mc.level.clip(new ClipContext(start, end, ClipContext.Block.COLLIDER, ClipContext.Fluid.ANY, mc.player));
}
private void updateInventory(NonNullList<ItemStack> inv, ItemStack heldStack, int cnt) {
for (int i = 0; i < cnt; i++) {
ItemStack item = inv.get(i);
if (item.getItem() == ItemRegistry.MINEPAD.get()) {
if (item.has(WDDataComponents.PAD_ID.get())) {
UUID padId = item.get(WDDataComponents.PAD_ID.get());
updatePad(padId, item, item == heldStack);
}
}
}
}
private void updatePad(UUID id, ItemStack stack, boolean isSelected) {
PadData pd = padMap.get(id);
if (pd != null)
pd.isInHotbar = true;
else if (isSelected && stack.has(WDDataComponents.PAD_URL.get())) {
String url = stack.get(WDDataComponents.PAD_URL.get());
pd = new PadData(url, id);
padMap.put(id, pd);
padList.add(pd);
}
}
public MinePadRenderer getMinePadRenderer() {
return minePadRenderer;
}
public PadData getPadByID(UUID id) {
return padMap.get(id);
}
public static final class ScreenSidePair {
public ScreenBlockEntity tes;
public BlockSide side;
}
public boolean findScreenFromBrowser(CefBrowser browser, ScreenSidePair pair) {
for (ScreenBlockEntity tes : screenTracking) {
for (int i = 0; i < tes.screenCount(); i++) {
ScreenData 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 = ClientAdvancements.class.getDeclaredFields();
Optional<Field> 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;
}
@Override
public BlockGetter getWorld(IPayloadContext context) {
BlockGetter senderLevel = super.getWorld(context);
if (senderLevel == null) return Minecraft.getInstance().level;
return senderLevel;
}
@SubscribeEvent
public void onDrawSelection(RenderHighlightEvent.Block event) {
if (event.getTarget() instanceof BlockHitResult bhr) {
BlockState state = Minecraft.getInstance().level.getBlockState(bhr.getBlockPos());
if (state.getBlock() instanceof ScreenBlock screen) {
Vector3i vec = new Vector3i(bhr.getBlockPos().getX(), bhr.getBlockPos().getY(), bhr.getBlockPos().getZ());
BlockSide side = BlockSide.fromInt(bhr.getDirection().ordinal());
Multiblock.findOrigin(
Minecraft.getInstance().level, vec,
side, null
);
BlockPos pos = new BlockPos(vec.x, vec.y, vec.z);
BlockEntity be = Minecraft.getInstance().level.getBlockEntity(
pos
);
if (be instanceof ScreenBlockEntity tes) {
if (tes.getScreen(side) != null) {
event.setCanceled(true);
}
}
}
}
}
/**
* KEYBINDS
**/
public static final KeyMapping KEY_MOUSE = new KeyMapping("webdisplays.key.toggle_mouse", GLFW.GLFW_KEY_R, "key.categories.misc");
static boolean rDown = false;
public static boolean mouseOn = false;
public static void onKeybindRegistry(RegisterKeyMappingsEvent event) {
event.register(KEY_MOUSE);
}
}

View File

@@ -0,0 +1,370 @@
///*
// * Copyright (C) 2018 BARBOTIN Nicolas
// */
//
//package net.montoyo.wd.client;
//
//import net.minecraft.client.Minecraft;
//import net.minecraft.core.BlockPos;
//import net.minecraft.core.Direction;
//import net.minecraft.world.item.ItemStack;
//import net.neoforged.api.distmarker.Dist;
//import net.neoforged.api.distmarker.OnlyIn;
//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.WDNetworkRegistry;
//import net.montoyo.wd.net.server_bound.C2SMessageScreenCtrl;
//import net.montoyo.wd.utilities.*;
//
//import java.util.*;
//
//@OnlyIn(Dist.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<QueryData> queue = new ArrayDeque<>();
// private final ClientProxy.ScreenSidePair lookupResult = new ClientProxy.ScreenSidePair();
// private final HashMap<String, IScreenQueryHandler> handlers = new HashMap<>();
// private final ArrayList<ServerQuery> serverQueries = new ArrayList<>();
// private final Minecraft mc = Minecraft.getInstance();
//
// 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<String> 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);
//
// WDNetworkRegistry.INSTANCE.sendToServer(C2SMessageScreenCtrl.jsRequest(tes, side, ret.id, type, data));
// }
//
// private void registerDefaults() {
// VideoType.registerQueries(this);
//
// 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.REDINPUT)) {
// 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.getBlockPos())).addMul(side.right, x).addMul(side.up, y).toBlock();
// int level = tes.getLevel().getBlockState(bpos).getValue(BlockScreen.emitting) ? 0 : tes.getLevel().getSignal(bpos, Direction.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.REDINPUT)) {
// final Direction facing = Direction.values()[side.reverse().ordinal()];
// final StringJoiner resp = new StringJoiner(",", "{\"levels\":[", "]}");
//
// tes.forEachScreenBlocks(side, bp -> {
// if(tes.getLevel().getBlockState(bp).getValue(BlockScreen.emitting))
// resp.add("0");
// else
// resp.add("" + tes.getLevel().getSignal(bp, facing));
// });
//
// cb.success(resp.toString());
// } else
// cb.failure(403, "Missing upgrade");
// });
//
// register("ClearRedstone", (cb, tes, side, args) -> {
// if(tes.hasUpgrade(side, DefaultUpgrade.REDOUTPUT)) {
// 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.REDOUTPUT)) {
// 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.REDOUTPUT)) {
// 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.getBlockPos())).addMul(side.right, x).addMul(side.up, y).toBlock();
// boolean e = tes.getLevel().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.REDOUTPUT)) {
// final StringJoiner resp = new StringJoiner(",", "{\"emission\":[", "]}");
// tes.forEachScreenBlocks(side, bp -> resp.add(tes.getLevel().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.getBlockPos();
// 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<ItemStack> 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() + "}"));
// }
//
//}

View File

@@ -0,0 +1,215 @@
/*
* Copyright (C) 2018 BARBOTIN Nicolas
*/
package net.montoyo.wd.client;
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.serialization.Util;
import org.cef.callback.CefCallback;
import org.cef.handler.CefResourceHandler;
import org.cef.misc.IntRef;
import org.cef.misc.StringRef;
import org.cef.network.CefRequest;
import org.cef.network.CefResponse;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
public class WDScheme implements CefResourceHandler {
private static final String ERROR_PAGE = "<!DOCTYPE html><html><head></head><body><h1>%d %s</h1><hr /><i>Miniserv powered by WebDisplays</i></body></html>";
private ClientTaskGetFile task;
private boolean isErrorPage;
String url;
boolean onlyError = false;
public WDScheme(String url) {
this.url = url;
}
@Override
public boolean processRequest(CefRequest cefRequest, CefCallback cefCallback) {
url = cefRequest.getURL();
url = url.substring("webdisplays://".length());
int pos = url.indexOf('/');
if (pos < 0)
return false;
String uuidStr = url.substring(0, pos);
String fileStr = url.substring(pos + 1);
fileStr = URLDecoder.decode(fileStr, StandardCharsets.UTF_8);
if (uuidStr.isEmpty() || Util.isFileNameInvalid(fileStr)) {
// invalid URL or no UUID
onlyError = true;
cefCallback.Continue();
return true;
}
UUID uuid;
try {
uuid = UUID.fromString(uuidStr);
} catch (IllegalArgumentException ex) {
// invalid UUID
onlyError = true;
cefCallback.Continue();
return true;
}
task = new ClientTaskGetFile(uuid, fileStr);
boolean doContinue = Client.getInstance().addTask(task);
if (doContinue) cefCallback.Continue();
return doContinue;
}
@Override
public void getResponseHeaders(CefResponse cefResponse, IntRef contentLength, StringRef redir) {
int status;
if (onlyError) {
status = Constants.GETF_STATUS_BAD_NAME;
} else {
Log.info("Waiting for response...");
status = task.waitForResponse();
Log.info("Got response %d", status);
if (status == 0) {
//OK
int extPos = task.getFileName().lastIndexOf('.');
if (extPos >= 0) {
String mime = mapMime(task.getFileName().substring(extPos + 1));
if (mime != null)
cefResponse.setMimeType(mime);
}
cefResponse.setStatus(200);
cefResponse.setStatusText("OK");
contentLength.set(0);
return;
}
}
int errCode;
String errStr;
if (status == Constants.GETF_STATUS_NOT_FOUND) {
errCode = 404;
errStr = "Not Found";
} else if (status == Constants.GETF_STATUS_TIMED_OUT) {
errCode = 408;
errStr = "Timed Out";
} else if (status == Constants.GETF_STATUS_BAD_NAME) {
errCode = 418;
errStr = "I'm a teapot";
} else {
errCode = 500;
errStr = "Internal Server Error";
}
// reporting the actual status and text makes CEF not display the page
cefResponse.setStatus(200);
cefResponse.setStatusText("OK");
cefResponse.setMimeType("text/html");
dataToWrite = String.format(ERROR_PAGE, errCode, errStr).getBytes(StandardCharsets.UTF_8);
dataOffset = 0;
amountToWrite = dataToWrite.length;
isErrorPage = true;
contentLength.set(0);
}
private byte[] dataToWrite;
private int dataOffset;
private int amountToWrite;
@Override
public boolean readResponse(byte[] output, int bytesToRead, IntRef bytesRead, CefCallback cefCallback) {
if (dataToWrite == null) {
if (isErrorPage) {
bytesRead.set(0);
return false;
}
dataToWrite = task.waitForData();
dataOffset = 3; //packet ID + size
amountToWrite = task.getDataLength();
if (amountToWrite <= 0) {
dataToWrite = null;
bytesRead.set(0);
return false;
}
}
int toWrite = bytesToRead;
if (toWrite > amountToWrite)
toWrite = amountToWrite;
System.arraycopy(dataToWrite, dataOffset, output, 0, toWrite);
bytesRead.set(toWrite);
dataOffset += toWrite;
amountToWrite -= toWrite;
if (amountToWrite <= 0) {
if (!isErrorPage)
task.nextData();
dataToWrite = null;
}
return true;
}
@Override
public void cancel() {
Log.info("Scheme query canceled or finished.");
if (!onlyError)
task.cancel();
}
public static String mapMime(String ext) {
switch (ext) {
case "htm":
case "html":
return "text/html";
case "css":
return "text/css";
case "js":
return "text/javascript";
case "png":
return "image/png";
case "jpg":
case "jpeg":
return "image/jpeg";
case "gif":
return "image/gif";
case "svg":
return "image/svg+xml";
case "xml":
return "text/xml";
case "txt":
return "text/plain";
default:
return null;
}
}
}

View File

@@ -0,0 +1,116 @@
package net.montoyo.wd.client.audio;
import net.minecraft.client.resources.sounds.Sound;
import net.minecraft.client.resources.sounds.SoundInstance;
import net.minecraft.client.sounds.AudioStream;
import net.minecraft.client.sounds.SoundBufferLibrary;
import net.minecraft.client.sounds.SoundManager;
import net.minecraft.client.sounds.WeighedSoundEvents;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.RandomSource;
import net.minecraft.util.valueproviders.SampledFloat;
import net.montoyo.wd.entity.ScreenBlockEntity;
import net.montoyo.wd.entity.ScreenData;
import org.jetbrains.annotations.Nullable;
import java.util.concurrent.CompletableFuture;
public class WDAudioSource implements SoundInstance {
private static final ResourceLocation location = ResourceLocation.fromNamespaceAndPath("webdisplays", "audio_source");
private static final WeighedSoundEvents events = new WeighedSoundEvents(
location, "webdisplays.browser"
);
private static final SampledFloat CONST_1 = new SampledFloat() {
@Override
public float sample(RandomSource pRandom) {
return 1.0f;
}
};
private final Sound sound = new Sound(
ResourceLocation.fromNamespaceAndPath("webdisplays", "unused"),
CONST_1,
CONST_1,
1, Sound.Type.SOUND_EVENT,
true, false,
100
);
ScreenBlockEntity blockEntity;
ScreenData data;
public WDAudioSource(ScreenBlockEntity blockEntity, ScreenData data) {
this.blockEntity = blockEntity;
this.data = data;
}
@Override
public ResourceLocation getLocation() {
return location;
}
@Nullable
@Override
public WeighedSoundEvents resolve(SoundManager pManager) {
return events;
}
@Override
public CompletableFuture<AudioStream> getStream(SoundBufferLibrary soundBuffers, Sound sound, boolean looping) {
return null;
}
@Override
public Sound getSound() {
return sound;
}
@Override
public SoundSource getSource() {
return SoundSource.RECORDS;
}
@Override
public boolean isLooping() {
return true;
}
@Override
public boolean isRelative() {
return false;
}
@Override
public int getDelay() {
return 0;
}
@Override
public float getVolume() {
return blockEntity.ytVolume;
}
@Override
public float getPitch() {
return 1;
}
@Override
public double getX() {
return blockEntity.getBlockPos().getX();
}
@Override
public double getY() {
return blockEntity.getBlockPos().getY();
}
@Override
public double getZ() {
return blockEntity.getBlockPos().getZ();
}
@Override
public Attenuation getAttenuation() {
return Attenuation.LINEAR;
}
}

View File

@@ -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();
}

View File

@@ -0,0 +1,377 @@
/*
* Copyright (C) 2019 BARBOTIN Nicolas
*/
package net.montoyo.wd.client.gui;
import com.cinemamod.mcef.MCEFBrowser;
import com.mojang.blaze3d.vertex.PoseStack;
import com.mojang.math.Axis;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.phys.BlockHitResult;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec3;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.api.distmarker.OnlyIn;
import net.neoforged.fml.ModList;
import net.neoforged.fml.loading.FMLPaths;
import net.montoyo.wd.WebDisplays;
import net.montoyo.wd.client.gui.camera.KeyboardCamera;
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.controls.builtin.ClickControl;
import net.montoyo.wd.entity.ScreenBlockEntity;
import net.montoyo.wd.entity.ScreenData;
import net.montoyo.wd.net.WDNetworkRegistry;
import net.montoyo.wd.net.server_bound.C2SMessageScreenCtrl;
import net.montoyo.wd.utilities.Log;
import net.montoyo.wd.utilities.data.BlockSide;
import net.montoyo.wd.utilities.math.Vector2i;
import net.montoyo.wd.utilities.serialization.TypeData;
import net.montoyo.wd.utilities.serialization.Util;
import org.cef.browser.CefBrowser;
import org.cef.misc.CefCursorType;
import org.joml.Matrix4f;
import org.joml.Vector4f;
import org.lwjgl.glfw.GLFW;
import org.vivecraft.client_vr.gameplay.VRPlayer;
import org.vivecraft.client_vr.gameplay.screenhandlers.KeyboardHandler;
import java.io.*;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Map;
import java.util.function.Consumer;
@OnlyIn(Dist.CLIENT)
public class GuiKeyboard extends WDScreen {
private static final String WARNING_FNAME = "wd_keyboard_warning.txt";
private ScreenBlockEntity tes;
private BlockSide side;
private ScreenData data;
private final ArrayList<TypeData> evStack = new ArrayList<>();
private BlockPos kbPos;
private boolean showWarning = true;
@FillControl
private Label lblInfo;
@FillControl
private Button btnOk;
public GuiKeyboard() {
super(Component.nullToEmpty(null));
}
public GuiKeyboard(ScreenBlockEntity tes, BlockSide side, BlockPos kbPos) {
this();
this.tes = tes;
this.side = side;
this.kbPos = kbPos;
}
@Override
protected void addLoadCustomVariables(Map<String, Double> vars) {
vars.put("showWarning", showWarning ? 1.0 : 0.0);
}
private static final boolean vivecraftPresent;
static {
boolean vivePres = false;
if (ModList.get().isLoaded("vivecraft")) vivePres = true;
// I believe the non-mixin version of vivecraft is not a proper mod, so
// detect the mod reflectively if the mod is not found
else {
try {
Class<?> clazz = Class.forName("org.vivecraft.gameplay.screenhandlers.KeyboardHandler");
//noinspection ConstantConditions
if (clazz == null) vivePres = false;
else {
Method m = clazz.getMethod("setOverlayShowing", boolean.class);
//noinspection ConstantConditions
vivePres = m != null;
}
} catch (Throwable ignored) {
vivePres = false;
}
}
vivecraftPresent = vivePres;
}
@Override
public void init() {
super.init();
if (minecraft.getSingleplayerServer() != null && !minecraft.getSingleplayerServer().isPublished())
showWarning = false; //NO NEED
else
showWarning = !hasUserReadWarning();
loadFrom(ResourceLocation.fromNamespaceAndPath("webdisplays", "gui/kb_right.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 {
if (!minecraft.isWindowActive()) {
minecraft.setWindowActive(true);
minecraft.mouseHandler.grabMouse();
}
}
defaultBackground = showWarning;
syncTicks = 5;
if (vivecraftPresent)
if (VRPlayer.get() != null)
KeyboardHandler.setOverlayShowing(true);
KeyboardCamera.focus(tes, side);
data = tes.getScreen(side);
CefBrowser browser = data.browser;
((MCEFBrowser) browser).setCursor(CefCursorType.fromId(data.mouseType));
((MCEFBrowser) browser).setCursorChangeListener((id) -> {
data.mouseType = id;
((MCEFBrowser) browser).setCursor(CefCursorType.fromId(id));
});
}
@Override
public void removed() {
super.removed();
if (vivecraftPresent)
if (VRPlayer.get() != null)
KeyboardHandler.setOverlayShowing(false);
KeyboardCamera.focus(null, null);
CefBrowser browser = data.browser;
if (browser instanceof MCEFBrowser mcef) {
mcef.setCursor(CefCursorType.POINTER);
mcef.setCursorChangeListener((cursor) -> data.mouseType = cursor);
}
}
@Override
public void onClose() {
removed();
super.onClose();
this.minecraft.popGuiLayer();
}
@Override
public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
if (quitOnEscape && keyCode == GLFW.GLFW_KEY_ESCAPE) {
onClose();
return true;
}
addKey(new TypeData(TypeData.Action.PRESS, keyCode, modifiers, scanCode));
return super.keyPressed(keyCode, scanCode, modifiers);
}
@Override
public boolean charTyped(char codePoint, int modifiers) {
addKey(new TypeData(TypeData.Action.TYPE, codePoint, modifiers, 0));
return super.charTyped(codePoint, modifiers);
}
@Override
public boolean keyReleased(int keyCode, int scanCode, int modifiers) {
addKey(new TypeData(TypeData.Action.RELEASE, keyCode, modifiers, scanCode));
return super.keyPressed(keyCode, scanCode, modifiers);
}
void addKey(TypeData data) {
tes.type(side, "[" + WebDisplays.GSON.toJson(data) + "]", kbPos);
evStack.add(data);
if (!evStack.isEmpty() && !syncRequested())
requestSync();
}
@Override
protected void sync() {
if(!evStack.isEmpty()) {
WDNetworkRegistry.INSTANCE.sendToServer(C2SMessageScreenCtrl.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;
minecraft.setWindowActive(true);
minecraft.mouseHandler.grabMouse();
}
}
private boolean hasUserReadWarning() {
try {
File f = new File(FMLPaths.GAMEDIR.get().toString(), 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(FMLPaths.GAMEDIR.get().toString(), 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.getBlockPos()) && side == this.side);
}
protected void mouse(double mouseX, double mouseY, Consumer<Vector2i> func) {
float pct = this.lastPartialTick;
double fov = Minecraft.getInstance().options.fov().get().doubleValue();
mouseX /= width;
mouseY /= height;
mouseX -= 0.5;
mouseY -= 0.5;
mouseY = -mouseY;
Matrix4f proj = Minecraft.getInstance().gameRenderer.getProjectionMatrix(fov);
Entity e = Minecraft.getInstance().getEntityRenderDispatcher().camera.getEntity();
PoseStack camera = new PoseStack();
float[] angle = KeyboardCamera.getAngle(e, pct);
camera.mulPose(Axis.XP.rotationDegrees(angle[0]));
camera.mulPose(Axis.YP.rotationDegrees(angle[1] + 180.0F));
Vector4f coord = new Vector4f(2f * (float) mouseX, 2 * (float) mouseY, 0, 1f);
coord.add(proj.invert().transform(coord));
coord = camera.last().pose().invert().transform(coord);
Vec3 vec3 = e.getEyePosition(pct);
Vec3 vec31 = new Vec3(coord.x, coord.y, coord.z).normalize();
BlockHitResult result = tes.trace(side, vec3, vec31);
if (result.getType() != HitResult.Type.MISS) {
tes.interact(result, func);
}
}
@Override
public void render(GuiGraphics poseStack, int mouseX, int mouseY, float ptt) {
this.lastPartialTick = ptt;
super.render(poseStack, mouseX, mouseY, ptt);
}
private float lastPartialTick = 0f;
@Override
public void mouseMoved(double mouseX, double mouseY) {
mouse(mouseX, mouseY, (hit) -> {
tes.handleMouseEvent(side, ClickControl.ControlType.MOVE, hit, -1);
WDNetworkRegistry.INSTANCE.sendToServer(C2SMessageScreenCtrl.laserMove(tes, side, hit));
});
super.mouseMoved(mouseX, mouseY);
}
@Override
public boolean mouseClicked(double mouseX, double mouseY, int button) {
mouse(mouseX, mouseY, (hit) -> {
tes.handleMouseEvent(side, ClickControl.ControlType.MOVE, hit, -1);
tes.handleMouseEvent(side, ClickControl.ControlType.DOWN, hit, button);
WDNetworkRegistry.INSTANCE.sendToServer(C2SMessageScreenCtrl.laserDown(tes, side, hit, button));
});
KeyboardCamera.setMouse(button, true);
return super.mouseClicked(mouseX, mouseY, button);
}
@Override
public boolean mouseReleased(double mouseX, double mouseY, int button) {
mouse(mouseX, mouseY, (hit) -> {
tes.handleMouseEvent(side, ClickControl.ControlType.MOVE, hit, -1);
tes.handleMouseEvent(side, ClickControl.ControlType.UP, hit, button);
WDNetworkRegistry.INSTANCE.sendToServer(C2SMessageScreenCtrl.laserUp(tes, side, button));
});
KeyboardCamera.setMouse(button, false);
return super.mouseReleased(mouseX, mouseY, button);
}
@Override
public void tick() {
double mouseX = Minecraft.getInstance().mouseHandler.xpos() / Minecraft.getInstance().getWindow().getWidth();
double mouseY = Minecraft.getInstance().mouseHandler.ypos() / Minecraft.getInstance().getWindow().getHeight();
mouse(mouseX * width, mouseY * height, (hit) -> {
tes.handleMouseEvent(side, ClickControl.ControlType.MOVE, hit, -1);
WDNetworkRegistry.INSTANCE.sendToServer(C2SMessageScreenCtrl.laserMove(tes, side, hit));
});
super.tick();
}
}

View File

@@ -0,0 +1,356 @@
/*
* Copyright (C) 2019 BARBOTIN Nicolas
*/
package net.montoyo.wd.client.gui;
import com.cinemamod.mcef.MCEFBrowser;
import com.google.gson.JsonObject;
import com.mojang.blaze3d.platform.InputConstants;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.BufferBuilder;
import com.mojang.blaze3d.vertex.BufferUploader;
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
import com.mojang.blaze3d.vertex.Tesselator;
import com.mojang.blaze3d.vertex.VertexFormat;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.core.BlockPos;
import net.minecraft.locale.Language;
import net.minecraft.network.chat.Component;
import net.neoforged.api.distmarker.OnlyIn;
import net.montoyo.wd.WebDisplays;
import net.montoyo.wd.client.ClientProxy;
import net.montoyo.wd.utilities.browser.WDBrowser;
import net.montoyo.wd.utilities.browser.handlers.js.Scripts;
import net.montoyo.wd.utilities.data.BlockSide;
import org.cef.misc.CefCursorType;
import org.lwjgl.glfw.GLFW;
import java.util.Optional;
import static net.neoforged.api.distmarker.Dist.CLIENT;
@OnlyIn(CLIENT)
public class GuiMinePad extends WDScreen {
private ClientProxy.PadData pad;
private double vx;
private double vy;
private double vw;
private double vh;
public GuiMinePad() {
super(Component.nullToEmpty(null));
}
public GuiMinePad(ClientProxy.PadData pad) {
this();
this.pad = pad;
}
int trueWidth, trueHeight;
@Override
public void init() {
vw = ((double) width) - 32.0f;
vh = vw / WebDisplays.PAD_RATIO;
vx = 16.0f;
vy = (((double) height) - vh) / 2.0f;
trueWidth = width;
trueHeight = height;
this.width = (int) vw;
this.height = (int) vh;
super.init();
((MCEFBrowser) pad.view).setCursor(CefCursorType.fromId(pad.activeCursor));
((MCEFBrowser) pad.view).setCursorChangeListener((id) -> {
pad.activeCursor = id;
((MCEFBrowser) pad.view).setCursor(CefCursorType.fromId(id));
});
}
private static void addRect(BufferBuilder bb, org.joml.Matrix4f pose, float x, float y, float w, float h) {
bb.addVertex(pose, x, y, 0.0f).setColor(255, 255, 255, 255);
bb.addVertex(pose, x + w, y, 0.0f).setColor(255, 255, 255, 255);
bb.addVertex(pose, x + w, y + h, 0.0f).setColor(255, 255, 255, 255);
bb.addVertex(pose, x, y + h, 0.0f).setColor(255, 255, 255, 255);
}
@Override
public void render(GuiGraphics graphics, int mouseX, int mouseY, float ptt) {
width = trueWidth;
height = trueHeight;
renderBackground(graphics, mouseX, mouseY, ptt);
width = (int) vw;
height = (int) vh;
RenderSystem.disableCull();
RenderSystem.setShaderColor(0.73f, 0.73f, 0.73f, 1.0f);
RenderSystem.setShader(GameRenderer::getPositionColorShader);
BufferBuilder bb = Tesselator.getInstance().begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR);
org.joml.Matrix4f pose = graphics.pose().last().pose();
addRect(bb, pose, (float) vx, (float) (vy - 16), (float) vw, 16f);
addRect(bb, pose, (float) vx, (float) (vy + vh), (float) vw, 16f);
addRect(bb, pose, (float) (vx - 16), (float) vy, 16f, (float) vh);
addRect(bb, pose, (float) (vx + vw), (float) vy, 16f, (float) vh);
BufferUploader.drawWithShader(bb.buildOrThrow());
if (pad.view != null) {
// pad.view.draw(poseStack, vx, vy + vh, vx + vw, vy);
RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f);
RenderSystem.disableDepthTest();
RenderSystem.setShader(GameRenderer::getPositionTexColorShader);
RenderSystem.setShaderTexture(0, ((MCEFBrowser) pad.view).getRenderer().getTextureID());
BufferBuilder buffer = Tesselator.getInstance().begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX_COLOR);
double x1 = vx;
double y1 = vy;
double x2 = vx + vw;
double y2 = vy + vh;
buffer.addVertex(graphics.pose().last().pose(), (float) x1, (float) y1, 0.0f).setUv(0.0F, 0.0F).setColor(1f, 1f, 1f, 1f);
buffer.addVertex(graphics.pose().last().pose(), (float) x2, (float) y1, 0.0f).setUv(1.0F, 0.0F).setColor(1f, 1f, 1f, 1f);
buffer.addVertex(graphics.pose().last().pose(), (float) x2, (float) y2, 0.0f).setUv(1.0F, 1.0F).setColor(1f, 1f, 1f, 1f);
buffer.addVertex(graphics.pose().last().pose(), (float) x1, (float) y2, 0.0f).setUv(0.0F, 1.0F).setColor(1f, 1f, 1f, 1f);
BufferUploader.drawWithShader(buffer.buildOrThrow());
RenderSystem.enableDepthTest();
}
RenderSystem.enableCull();
graphics.drawString(
minecraft.font, Language.getInstance().getOrDefault(
"webdisplays.gui.minepad.close"
), (int) vx + 4, (int) vy - minecraft.font.lineHeight - 3, 16777215, true
);
}
@Override
public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
return this.keyChanged(keyCode, scanCode, modifiers, true) || super.keyPressed(keyCode, scanCode, modifiers);
}
@Override
public boolean keyReleased(int keyCode, int scanCode, int modifiers) {
return this.keyChanged(keyCode, scanCode, modifiers, false) || super.keyReleased(keyCode, scanCode, modifiers);
}
@Override
public boolean charTyped(char codePoint, int modifiers) {
if (pad.view != null) {
((MCEFBrowser) pad.view).sendKeyTyped(codePoint, modifiers);
return true;
} else {
return super.charTyped(codePoint, modifiers);
}
}
/* copied from MCEF */
public boolean keyChanged(int keyCode, int scanCode, int modifiers, boolean pressed) {
assert minecraft != null;
if ((modifiers & GLFW.GLFW_MOD_SHIFT) == GLFW.GLFW_MOD_SHIFT && keyCode == GLFW.GLFW_KEY_ESCAPE) {
onClose();
return true;
}
InputConstants.Key iuKey = InputConstants.getKey(keyCode, scanCode);
String keystr = iuKey.getDisplayName().getString();
// System.out.println("KEY STR " + keystr);
if (keystr.length() == 0)
return false;
char key = keystr.charAt(keystr.length() - 1);
if (keystr.equals("Enter")) {
keyCode = 10;
key = '\n';
}
if (pad.view != null) {
if (pressed)
((MCEFBrowser) pad.view).sendKeyPress(keyCode, scanCode, modifiers);
else
((MCEFBrowser) pad.view).sendKeyRelease(keyCode, scanCode, modifiers);
if (pressed && key == '\n')
if (modifiers != 0) ((MCEFBrowser) pad.view).sendKeyTyped('\r', modifiers);
return true;
}
return false;
}
@Override
public void mouseMoved(double mouseX, double mouseY) {
super.mouseMoved(mouseX, mouseY);
mouse(-1, false, (int) mouseX, (int) mouseY, 0);
}
@Override
public boolean mouseClicked(double mouseX, double mouseY, int button) {
mouse(button, true, (int) mouseX, (int) mouseY, 0);
return super.mouseClicked(mouseX, mouseY, button);
}
@Override
public boolean mouseReleased(double mouseX, double mouseY, int button) {
mouse(button, false, (int) mouseX, (int) mouseY, 0);
return super.mouseReleased(mouseX, mouseY, button);
}
@Override
public boolean mouseScrolled(double mouseX, double mouseY, double scrollX, double scrollY) {
double mx = (mouseX - vx) / vw;
double my = (mouseY - vy) / vh;
int sx = (int) (mx * WebDisplays.INSTANCE.padResX);
int sy = (int) (my * WebDisplays.INSTANCE.padResY);
// TODO: this doesn't work, and I don't understand why?
((MCEFBrowser) pad.view).sendMouseWheel(sx, sy, scrollY, (hasControlDown() && !hasAltDown() && !hasShiftDown()) ? GLFW.GLFW_MOD_CONTROL : 0);
return super.mouseScrolled(mouseX, mouseY, scrollX, scrollY);
}
public void capturedMouse(double scaledX, double scaledY, int sx, int sy) {
double centerX = (int) (0.5 * (double) this.minecraft.getWindow().getGuiScaledWidth());
double centerY = (int) (0.5 * (double) this.minecraft.getWindow().getGuiScaledHeight());
if (sx == (int) centerX && sy == (int) centerY) return;
double mx = (centerX - vx) / vw;
double my = (centerY - vy) / vh;
double scaledCentX = (mx * WebDisplays.INSTANCE.padResX);
double scaledCentY = (my * WebDisplays.INSTANCE.padResY);
double deltX = scaledX - scaledCentX;
double deltY = scaledY - scaledCentY;
String scr = Scripts.MOUSE_EVENT;
pad.view.executeJavaScript(
scr
.replace("%xCoord%", "" + (int) centerX)
.replace("%yCoord%", "" + (int) centerY)
.replace("%xDelta%", "" + (deltX))
.replace("%yDelta%", "" + (deltY)),
"WebDisplays", 0
);
// lock mouse
try {
double xpos = (this.minecraft.getWindow().getScreenWidth() / 2);
double ypos = (this.minecraft.getWindow().getScreenHeight() / 2);
GLFW.glfwSetCursorPos(minecraft.getWindow().getWindow(), xpos, ypos);
} catch (Throwable ignored) {
}
}
public void mouse(int btn, boolean pressed, int sx, int sy, double scrollAmount) {
double mx = (sx - vx) / vw;
double my = (sy - vy) / vh;
if (pad.view != null && mx >= 0 && mx <= 1) {
//Scale again according to the webview
int scaledX = (int) (mx * WebDisplays.INSTANCE.padResX);
int scaledY = (int) (my * WebDisplays.INSTANCE.padResY);
if (btn == -1) {
if (locked)
capturedMouse(mx * WebDisplays.INSTANCE.padResX, my * WebDisplays.INSTANCE.padResY, sx, sy);
else ((MCEFBrowser) pad.view).sendMouseMove(scaledX, scaledY);
} else if (pressed)
((MCEFBrowser) pad.view).sendMousePress(scaledX, scaledY, btn);
else ((MCEFBrowser) pad.view).sendMouseRelease(scaledX, scaledY, btn);
pad.view.setFocus(true);
}
}
public static Optional<Character> getChar(int keyCode, int scanCode) {
String keystr = GLFW.glfwGetKeyName(keyCode, scanCode);
if (keystr == null) {
keystr = "\0";
}
if (keyCode == GLFW.GLFW_KEY_ENTER) {
keystr = "\n";
}
if (keystr.length() == 0) {
return Optional.empty();
}
return Optional.of(keystr.charAt(keystr.length() - 1));
}
@Override
public void tick() {
if (pad.view == null)
minecraft.setScreen(null); //In case the user dies with the pad in the hand
pollElement();
}
@Override
public boolean isForBlock(BlockPos bp, BlockSide side) {
return false;
}
@Override
public void removed() {
super.removed();
InputConstants.updateRawMouseInput(
minecraft.getWindow().getWindow(),
Minecraft.getInstance().options.rawMouseInput().get()
);
if (pad.view instanceof MCEFBrowser browser) {
browser.setCursor(CefCursorType.POINTER);
browser.setCursorChangeListener((cursor) -> {
pad.activeCursor = cursor;
});
}
}
@Override
public void onClose() {
super.onClose();
removed();
this.minecraft.popGuiLayer();
}
boolean locked = false;
double lockCenterX = -1;
double lockCenterY = -1;
protected void updateCrd(JsonObject obj) {
if (obj.getAsJsonPrimitive("exists").getAsBoolean()) {
locked = true;
RenderSystem.recordRenderCall(() -> {
InputConstants.updateRawMouseInput(
minecraft.getWindow().getWindow(),
obj.getAsJsonPrimitive("unadjust").getAsBoolean()
);
GLFW.glfwSetInputMode(Minecraft.getInstance().getWindow().getWindow(), 208897, GLFW.GLFW_CURSOR_DISABLED);
});
lockCenterX = obj.getAsJsonPrimitive("x").getAsDouble() + obj.getAsJsonPrimitive("w").getAsDouble() / 2;
lockCenterY = obj.getAsJsonPrimitive("y").getAsDouble() + obj.getAsJsonPrimitive("h").getAsDouble() / 2;
} else {
if (locked) {
locked = false;
RenderSystem.recordRenderCall(()->{
InputConstants.updateRawMouseInput(
minecraft.getWindow().getWindow(),
Minecraft.getInstance().options.rawMouseInput().get()
);
GLFW.glfwSetInputMode(Minecraft.getInstance().getWindow().getWindow(), 208897, GLFW.GLFW_CURSOR_NORMAL);
GLFW.glfwSetCursor(Minecraft.getInstance().getWindow().getWindow(), CefCursorType.fromId(pad.activeCursor).glfwId);
});
}
}
}
protected void pollElement() {
if (pad.view instanceof WDBrowser browser) {
JsonObject object = browser.pointerLockElement().getObj();
if (object != null) updateCrd(object);
}
}
}

View File

@@ -0,0 +1,74 @@
/*
* Copyright (C) 2019 BARBOTIN Nicolas
*/
package net.montoyo.wd.client.gui;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
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.utilities.data.BlockSide;
import net.montoyo.wd.utilities.math.Vector3i;
import javax.annotation.Nullable;
public class GuiRedstoneCtrl extends WDScreen {
private ResourceLocation dimension;
private Vector3i pos;
private String risingEdgeURL;
private String fallingEdgeURL;
@FillControl
private TextField tfRisingEdge;
@FillControl
private TextField tfFallingEdge;
@FillControl
private Button btnOk;
public GuiRedstoneCtrl(Component component, ResourceLocation d, Vector3i p, String r, String f) {
super(component);
dimension = d;
pos = p;
risingEdgeURL = r;
fallingEdgeURL = f;
}
@Override
public void init() {
super.init();
loadFrom(ResourceLocation.fromNamespaceAndPath("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()));
// WDNetworkRegistry.INSTANCE.sendToServer(new C2SMessageRedstoneCtrl(pos, rising, falling));
// }
//
// minecraft.setScreen(null);
// }
@Override
public boolean isForBlock(BlockPos bp, BlockSide side) {
return pos.equalsBlockPos(bp);
}
@Nullable
@Override
public String getWikiPageName() {
return "Redstone_Controller";
}
}

View File

@@ -0,0 +1,523 @@
/*
* Copyright (C) 2018 BARBOTIN Nicolas
*/
package net.montoyo.wd.client.gui;
import net.minecraft.client.Minecraft;
import net.minecraft.client.resources.language.I18n;
import net.minecraft.client.resources.sounds.SimpleSoundInstance;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
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.ScreenData;
import net.montoyo.wd.entity.ScreenBlockEntity;
import net.montoyo.wd.item.WDItem;
import net.montoyo.wd.net.WDNetworkRegistry;
import net.montoyo.wd.net.server_bound.C2SMessageScreenCtrl;
import net.montoyo.wd.utilities.*;
import net.montoyo.wd.utilities.math.Vector2i;
import net.montoyo.wd.utilities.data.BlockSide;
import net.montoyo.wd.utilities.data.Rotation;
import net.montoyo.wd.utilities.serialization.NameUUIDPair;
import org.lwjgl.glfw.GLFW;
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 ScreenBlockEntity 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<NameUUIDPair> 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(Component component, ScreenBlockEntity tes, BlockSide side, NameUUIDPair[] friends, int fr, int or) {
super(component);
this.tes = tes;
this.side = side;
this.friends = friends;
friendRights = fr;
otherRights = or;
}
@Override
public void init() {
super.init();
loadFrom(ResourceLocation.fromNamespaceAndPath("webdisplays", "gui/screencfg.json"));
friendBoxes = new CheckBox[] { boxFResolution, boxFUpgrades, boxFOthers, boxFFriends, boxFClick, boxFSetUrl };
boxFResolution.setUserdata(ScreenRights.MODIFY_SCREEN);
boxFUpgrades.setUserdata(ScreenRights.MANAGE_UPGRADES);
boxFOthers.setUserdata(ScreenRights.MANAGE_OTHER_RIGHTS);
boxFFriends.setUserdata(ScreenRights.MANAGE_FRIEND_LIST);
boxFClick.setUserdata(ScreenRights.INTERACT);
boxFSetUrl.setUserdata(ScreenRights.CHANGE_URL);
otherBoxes = new CheckBox[] { boxOResolution, boxOUpgrades, boxOClick, boxOSetUrl };
boxOResolution.setUserdata(ScreenRights.MODIFY_SCREEN);
boxOUpgrades.setUserdata(ScreenRights.MANAGE_UPGRADES);
boxOClick.setUserdata(ScreenRights.INTERACT);
boxOSetUrl.setUserdata(ScreenRights.CHANGE_URL);
ScreenData 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();
Minecraft.getInstance().getSoundManager().play(SimpleSoundInstance.forUI( WebDisplays.INSTANCE.soundScreenCfg, 1.0f, 1.0f));
}
private void updateRotationStr() {
btnChangeRot.setLabel(I18n.get("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() {
ScreenData 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)
WDNetworkRegistry.INSTANCE.sendToServer(C2SMessageScreenCtrl.resolution(tes, side, 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();
WDNetworkRegistry.INSTANCE.sendToServer(new C2SMessageScreenCtrl(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().setCursorPosition(0);
tfResY.getMcField().setHighlightPos(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)
WDNetworkRegistry.INSTANCE.sendToServer(new C2SMessageScreenCtrl(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) WDNetworkRegistry.INSTANCE.sendToServer(C2SMessageScreenCtrl.autoVol(tes, side, ev.isChecked()));
}
@GuiSubscribe
public void onRemoveUpgrade(UpgradeGroup.ClickEvent ev) {
WDNetworkRegistry.INSTANCE.sendToServer(new C2SMessageScreenCtrl(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]))
WDNetworkRegistry.INSTANCE.sendToServer(new C2SMessageScreenCtrl(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 tick() {
super.tick();
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() {
WDNetworkRegistry.INSTANCE.sendToServer(new C2SMessageScreenCtrl(tes, side, friendRights, otherRights));
Log.info("Sent sync packet");
}
public void updateMyRights() {
NameUUIDPair me = new NameUUIDPair(minecraft.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.MODIFY_SCREEN) == 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.getBlockPos()) && 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";
}
// reason: allow closing the UI, lol
@Override
public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
if (keyCode == GLFW.GLFW_KEY_ESCAPE) {
Minecraft.getInstance().setScreen(null);
return true;
}
return super.keyPressed(keyCode, scanCode, modifiers);
}
}

View File

@@ -0,0 +1,810 @@
/*
* Copyright (C) 2018 BARBOTIN Nicolas
*/
package net.montoyo.wd.client.gui;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.*;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.resources.language.I18n;
import net.minecraft.client.resources.sounds.SimpleSoundInstance;
import net.minecraft.client.resources.sounds.SoundInstance;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.sounds.SoundSource;
import net.minecraft.util.RandomSource;
import net.montoyo.wd.WebDisplays;
import net.montoyo.wd.miniserv.Constants;
import net.montoyo.wd.miniserv.client.*;
import net.montoyo.wd.net.WDNetworkRegistry;
import net.montoyo.wd.utilities.*;
import net.montoyo.wd.utilities.math.Vector3i;
import net.montoyo.wd.utilities.data.BlockSide;
import net.montoyo.wd.utilities.serialization.NameUUIDPair;
import net.montoyo.wd.utilities.serialization.Util;
import org.lwjgl.glfw.GLFW;
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 java.util.function.Supplier;
import static net.montoyo.wd.client.gui.GuiMinePad.getChar;
public class GuiServer extends WDScreen {
private static final ResourceLocation BG_IMAGE = ResourceLocation.fromNamespaceAndPath("webdisplays", "textures/gui/server_bg.png");
private static final ResourceLocation FG_IMAGE = ResourceLocation.fromNamespaceAndPath("webdisplays", "textures/gui/server_fg.png");
private static final HashMap<String, Method> 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<String> 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 SimpleSoundInstance accessSound;
//Upload wizard
private boolean uploadWizard;
private File uploadDir;
private final ArrayList<File> uploadFiles = new ArrayList<>();
private int uploadOffset;
private boolean uploadFirstIsParent;
private String uploadFilter = "";
private long uploadFilterTime;
public GuiServer(Vector3i vec, NameUUIDPair owner) {
super(Component.nullToEmpty(null));
serverPos = vec;
this.owner = owner;
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.get("webdisplays.server." + key, args);
}
@Override
public void render(GuiGraphics graphics, int mouseX, int mouseY, float ptt) {
super.render(graphics, mouseX, mouseY, ptt);
int x = (width - 256) / 2;
int y = (height - 176) / 2;
// RenderSystem.enableTexture();
RenderSystem.setShaderTexture(0, BG_IMAGE);
RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f);
graphics.blit(BG_IMAGE, x, y, 0, 0, 256, 256);
x += 18;
y += 18;
for (int i = 0; i < lines.size(); i++) {
if (selectedLine == i) {
drawWhiteQuad(x - 1, y - 2, font.width(lines.get(i)) + 1, 12);
graphics.drawString(Minecraft.getInstance().font, lines.get(i), x, y, 0xFF129700, false);
} else
graphics.drawString(Minecraft.getInstance().font, lines.get(i), x, y, 0xFFFFFFFF, false);
y += 12;
}
if (!promptLocked) {
if (queue.isEmpty()) {
x = graphics.drawString(Minecraft.getInstance().font, userPrompt, x, y, 0xFFFFFFFF, false);
x = graphics.drawString(Minecraft.getInstance().font, prompt, x, y, 0xFFFFFFFF, false);
} else {
x = graphics.drawString(Minecraft.getInstance().font, tr("press_for_more"), x, y, 0xFFFFFFFF, false);
}
}
if (!uploadWizard && blinkTime < 5)
drawWhiteQuad(x + 1, y, 6, 8);
// RenderSystem.enableTexture();
RenderSystem.enableBlend();
RenderSystem.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA);
RenderSystem.setShaderTexture(0, FG_IMAGE);
RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f);
// blit(graphics,(width - 256) / 2, (height - 176) / 2, 0, 0, 256, 176);
}
private void drawWhiteQuad(int x, int y, int w, int h) {
float xd = (float) x;
float xd2 = (float) (x + w);
float yd = (float) y;
float yd2 = (float) (y + h);
float zd = (float) getBlitOffset();
// RenderSystem.disableTexture();
RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f);
RenderSystem.setShader(GameRenderer::getPositionShader);
BufferBuilder bb = Tesselator.getInstance().begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION);
bb.addVertex(xd, yd2, zd);
bb.addVertex(xd2, yd2, zd);
bb.addVertex(xd2, yd, zd);
bb.addVertex(xd, yd, zd);
BufferUploader.drawWithShader(bb.buildOrThrow());
// RenderSystem.enableTexture();
}
private float getBlitOffset() {
return 0;
}
@Override
public void tick() {
super.tick();
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 SimpleSoundInstance(WebDisplays.INSTANCE.soundServer.getLocation(), SoundSource.MASTER, 1.0f, 1.0f, RandomSource.create(), true, 0, SoundInstance.Attenuation.NONE, 0.0f, 0.0f, 0.0f, false);
minecraft.getSoundManager().play(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 = "";
}
}
final int maxl = uploadWizard ? MAX_LINES : (MAX_LINES - 1); //Cuz prompt is hidden
if (!queue.isEmpty()) {
while (!queue.isEmpty()) {
if (lines.size() >= maxl)
break;
writeLine(queue.remove(0));
}
}
while (lines.size() > maxl)
lines.remove(0);
}
@Override
public boolean keyReleased(int keyCode, int scanCode, int modifiers) {
Supplier<Boolean> predicate = () -> super.keyReleased(keyCode, scanCode, modifiers);
try {
return handleKeyboardInput(keyCode, false, predicate);
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
@Override
public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
if (keyCode == GLFW.GLFW_KEY_ESCAPE && !uploadWizard) {
Minecraft.getInstance().setScreen(null);
return true;
}
getChar(keyCode, scanCode).ifPresent(c -> {
try {
keyTyped(c, keyCode, modifiers);
} catch (IOException e) {
e.printStackTrace();
}
});
try {
return handleKeyboardInput(keyCode, true, () -> true);
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
public boolean handleKeyboardInput(int keyCode, boolean keyState, Supplier<Boolean> booleanSupplier) throws IOException {
if (!queue.isEmpty())
return false;
if (uploadWizard) {
if (keyState) {
if (keyCode == GLFW.GLFW_KEY_UP) {
if (selectedLine > 3)
selectedLine--;
else if (uploadOffset > 0) {
uploadOffset--;
updateUploadScreen();
}
} else if (keyCode == GLFW.GLFW_KEY_DOWN) {
if (selectedLine < MAX_LINES - 1)
selectedLine++;
else if (uploadOffset + selectedLine - 2 < uploadFiles.size()) {
uploadOffset++;
updateUploadScreen();
}
} else if (keyCode == GLFW.GLFW_KEY_PAGE_DOWN) {
selectedLine = 3;
int dst = uploadOffset - (MAX_LINES - 3);
if (dst < 0)
dst = 0;
selectFile(dst);
} else if (keyCode == GLFW.GLFW_KEY_PAGE_UP) {
selectedLine = 3;
int dst = uploadOffset + (MAX_LINES - 3);
if (dst >= uploadFiles.size())
dst = uploadFiles.size() - 1;
selectFile(dst);
} else if (keyCode == GLFW.GLFW_KEY_ENTER || keyCode == GLFW.GLFW_KEY_KP_ENTER) {
File file = uploadFiles.get(uploadOffset + selectedLine - 3);
if (file.isDirectory()) {
uploadCD(file);
updateUploadScreen();
} else
startFileUpload(file, true);
} else if (keyCode == GLFW.GLFW_KEY_F5) {
uploadCD(uploadDir);
updateUploadScreen();
}
}
if (keyCode == GLFW.GLFW_KEY_ESCAPE) {
quitUploadWizard();
return true; //Don't let the screen handle this
}
return booleanSupplier.get();
} else {
boolean value = booleanSupplier.get();
if (keyState) {
boolean ctrl = Screen.hasControlDown();
if (keyCode == GLFW.GLFW_KEY_L && ctrl)
lines.clear();
else if (keyCode == GLFW.GLFW_KEY_V && ctrl) {
prompt += Minecraft.getInstance().keyboardHandler.getClipboard();
if (prompt.length() > MAX_LINE_LEN)
prompt = prompt.substring(0, MAX_LINE_LEN);
} else if (keyCode == GLFW.GLFW_KEY_UP) {
if (lastCmd != null) {
String tmp = prompt;
prompt = lastCmd;
lastCmd = tmp;
}
}
}
return value;
}
}
@Override
public boolean charTyped(char codePoint, int modifiers) {
return super.charTyped(codePoint, modifiers);
}
protected void keyTyped(char typedChar, int keyCode, int modifier) throws IOException {
//this.charTyped(typedChar, modifier);
if (keyCode == GLFW.GLFW_KEY_DOWN) {
if (!queue.isEmpty()) {
writeLine(queue.remove(0));
return;
}
}
if (!queue.isEmpty())
return;
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 == GLFW.GLFW_KEY_SPACE)
typedChar = ' ';
if (
(typedChar == 'v' || typedChar == 'V') &&
(modifier & 2) == 2
) return;
if (keyCode == GLFW.GLFW_KEY_BACKSPACE) {
if (prompt.length() > 0)
prompt = prompt.substring(0, prompt.length() - 1);
} else if (keyCode == GLFW.GLFW_KEY_ENTER || keyCode == GLFW.GLFW_KEY_KP_ENTER) {
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 onClose() {
super.onClose();
if (accessSound != null)
Minecraft.getInstance().getSoundManager().stop(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) {
queueRead = lines.size();
if (args.length > 0) {
String cmd = args[0].toLowerCase();
if (COMMAND_MAP.containsKey(cmd))
queueLine(tr("help." + cmd));
else
queueLine(tr("unknowncmd"));
} else {
for (String c : COMMAND_MAP.keySet())
queueLine(c + " - " + tr("help." + c));
}
}
@CommandHandler("exit")
public void commandExit() {
minecraft.setScreen(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 (!minecraft.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"));
Minecraft.getInstance().keyboardHandler.setClipboard(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 (!minecraft.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 (!minecraft.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();
WDNetworkRegistry.INSTANCE.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";
}
int queueRead = 0;
ArrayList<String> queue = new ArrayList<>();
private void queueLine(String line) {
final int maxl = uploadWizard ? MAX_LINES : (MAX_LINES - 1); //Cuz prompt is hidden
if (lines.size() < maxl)
writeLine(line);
else if (queueRead > 1) {
writeLine(line);
queueRead -= 1;
} else queue.add(line);
}
}

View File

@@ -0,0 +1,155 @@
/*
* Copyright (C) 2019 BARBOTIN Nicolas
*/
package net.montoyo.wd.client.gui;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.neoforged.api.distmarker.Dist;
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.ScreenBlockEntity;
import net.montoyo.wd.item.ItemMinePad2;
import net.montoyo.wd.net.WDNetworkRegistry;
import net.montoyo.wd.net.server_bound.C2SMessageMinepadUrl;
import net.montoyo.wd.net.server_bound.C2SMessageScreenCtrl;
import net.montoyo.wd.utilities.data.BlockSide;
import net.montoyo.wd.utilities.serialization.Util;
import net.montoyo.wd.utilities.math.Vector3i;
import net.montoyo.wd.data.WDDataComponents;
import java.io.IOException;
import java.util.Map;
import java.util.UUID;
public class GuiSetURL2 extends WDScreen {
//Screen data
private ScreenBlockEntity tileEntity;
private BlockSide screenSide;
private Vector3i remoteLocation;
//Pad data
private ItemStack stack;
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(ScreenBlockEntity tes, BlockSide side, String url, Vector3i rl) {
super(Component.nullToEmpty(null));
tileEntity = tes;
screenSide = side;
remoteLocation = rl;
isPad = false;
screenURL = url;
}
public GuiSetURL2(ItemStack is, String url) {
super(Component.nullToEmpty(null));
isPad = true;
stack = is;
screenURL = url;
}
@Override
public void init() {
super.init();
loadFrom(ResourceLocation.fromNamespaceAndPath("webdisplays", "gui/seturl.json"));
// Guard against null URL to avoid UI NPEs
tfURL.setText(screenURL == null ? "" : screenURL);
}
@Override
protected void addLoadCustomVariables(Map<String, Double> vars) {
vars.put("isPad", isPad ? 1.0 : 0.0);
}
protected UUID getUUID() {
if (stack == null || !(stack.getItem() instanceof ItemMinePad2))
throw new RuntimeException("Get UUID is being called for a non-minepad UI");
if (!stack.has(WDDataComponents.PAD_ID.get())) {
UUID newUUID = UUID.randomUUID();
stack.set(WDDataComponents.PAD_ID.get(), newUUID);
}
return stack.get(WDDataComponents.PAD_ID.get());
}
@GuiSubscribe
public void onButtonClicked(Button.ClickEvent ev) {
if (ev.getSource() == btnCancel)
minecraft.setScreen(null);
else if (ev.getSource() == btnOk)
validate(tfURL.getText());
else if (ev.getSource() == btnShutDown) {
if (isPad) {
WDNetworkRegistry.INSTANCE.sendToServer(new C2SMessageMinepadUrl(
getUUID(),
""
));
stack.remove(WDDataComponents.PAD_ID.get());
}
minecraft.setScreen(null);
}
}
@GuiSubscribe
public void onEnterPressed(TextField.EnterPressedEvent ev) {
validate(ev.getText());
}
private void validate(String url) {
if (!url.isEmpty()) {
try {
ScreenBlockEntity.url(url);
} catch (IOException e) {
throw new RuntimeException(e);
}
url = Util.addProtocol(url);
// url = ((ClientProxy) WebDisplays.PROXY).getMCEF().punycode(url);
if (isPad) {
UUID uuid = getUUID();
WDNetworkRegistry.INSTANCE.sendToServer(new C2SMessageMinepadUrl(uuid, url));
stack.set(WDDataComponents.PAD_URL.get(), url);
ClientProxy.PadData pd = ((ClientProxy) WebDisplays.PROXY).getPadByID(uuid);
if (pd != null && pd.view != null) {
pd.view.loadURL(WebDisplays.applyBlacklist(url));
}
} else
WDNetworkRegistry.INSTANCE.sendToServer(C2SMessageScreenCtrl.setURL(tileEntity, screenSide, url, remoteLocation));
}
minecraft.setScreen(null);
}
@Override
public boolean isForBlock(BlockPos bp, BlockSide side) {
return (remoteLocation != null && remoteLocation.equalsBlockPos(bp)) || (bp.equals(tileEntity.getBlockPos()) && side == screenSide);
}
}

View File

@@ -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 {
}

View File

@@ -0,0 +1,205 @@
/*
* Copyright (C) 2018 BARBOTIN Nicolas
*/
package net.montoyo.wd.client.gui;
import com.mojang.blaze3d.platform.Lighting;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.renderer.entity.ItemRenderer;
import net.minecraft.client.resources.language.I18n;
import net.minecraft.core.NonNullList;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.crafting.Ingredient;
import net.minecraft.world.item.crafting.Recipe;
import net.minecraft.world.item.crafting.RecipeHolder;
import net.minecraft.world.item.crafting.ShapedRecipe;
import net.neoforged.api.distmarker.OnlyIn;
import net.montoyo.wd.utilities.Log;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.stream.IntStream;
import static net.neoforged.api.distmarker.Dist.CLIENT;
@OnlyIn(CLIENT)
public class RenderRecipe extends Screen {
public RenderRecipe() {
super(Component.nullToEmpty(null));
}
private static class NameRecipePair {
private final String name;
private final ShapedRecipe recipe;
private NameRecipePair(String n, ShapedRecipe r) {
this.name = n;
this.recipe = r;
}
}
private static final ResourceLocation CRAFTING_TABLE_GUI_TEXTURES = ResourceLocation.fromNamespaceAndPath("minecraft", "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 ItemRenderer renderItem;
private final ItemStack[] recipe = new ItemStack[3 * 3];
private ItemStack recipeResult;
private String recipeName;
private final ArrayList<NameRecipePair> recipes = new ArrayList<>();
private ByteBuffer buffer;
private int[] array;
@Override
public void init() {
x = (width - SIZE_X) / 2;
y = (height - SIZE_Y) / 2;
renderItem = minecraft.getItemRenderer();
for (RecipeHolder<?> holder : minecraft.level.getRecipeManager().getRecipes()) {
ResourceLocation regName = holder.id();
Recipe<?> rec = holder.value();
if (regName != null && regName.getNamespace().equals("webdisplays")) {
if (rec instanceof ShapedRecipe shaped)
recipes.add(new NameRecipePair(regName.getPath(), shaped));
else
Log.warning("Found non-shaped recipe %s", regName.toString());
}
}
Log.info("Loaded %d recipes", recipes.size());
nextRecipe();
}
@Override
public void render(GuiGraphics context, int mouseX, int mouseY, float partialTick) {
renderBackground(context, mouseX, mouseY, partialTick);
RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f);
RenderSystem.setShaderTexture(0, CRAFTING_TABLE_GUI_TEXTURES);
// context.blit(x, y, 0, 0, SIZE_X, SIZE_Y);
// font.draw(poseStack, I18n.get("container.crafting"), x + 28, y + 6, 0x404040);
Lighting.setupForFlatItems();
// RenderSystem.disableLighting(); //TODO: Need this?
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;
context.renderItem(is, x, y);
context.renderItemDecorations(font, is, x, y);
}
}
}
if(recipeResult != null) {
context.renderItem(recipeResult, x, y);
context.renderItemDecorations(font, recipeResult, x, y);
}
// GlStateManager.enableLighting();
Lighting.setupFor3DItems();
}
private void setRecipe(ShapedRecipe recipe) {
IntStream.range(0, this.recipe.length).forEach(i -> this.recipe[i] = null);
NonNullList<Ingredient> ingredients = recipe.getIngredients();
int pos = 0;
for(int y = 0; y < recipe.getHeight(); y++) {
for(int x = 0; x < recipe.getWidth(); x++) {
ItemStack[] stacks = ingredients.get(pos++).getItems();
if(stacks.length > 0)
this.recipe[y * 3 + x] = stacks[0];
}
}
// recipeResult = recipe.getResultItem();
}
private void nextRecipe() {
if(recipes.isEmpty())
minecraft.setScreen(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) minecraft.getWindow().getWidth());
return (int) ret;
}
private int screen2DisplayY(int y) {
double ret = ((double) y) / ((double) height) * ((double) minecraft.getWindow().getHeight());
return (int) ret;
}
private void takeScreenshot() throws Throwable { //TODO: Figure out how to do this.
/*
int x = screen2DisplayX(this.x + 27);
int y = minecraft.getWindow().getHeight() - screen2DisplayY(this.y + 4);
int w = screen2DisplayX(120);
int h = screen2DisplayY(68);
y -= h;
if(buffer == null)
buffer = BufferUtils.createByteBuffer(w * h);
int oldPack = glGetInteger(GL_PACK_ALIGNMENT);
RenderSystem.pixelStore(GL_PACK_ALIGNMENT, 1);
buffer.clear();
RenderSystem.readPixels(x, y, w, h, EXTBGRA.GL_BGRA_EXT, GL_UNSIGNED_BYTE, buffer);
RenderSystem.pixelStore(GL_PACK_ALIGNMENT, oldPack);
if(array == null)
array = new int[w * h];
buffer.clear();
buffer.asIntBuffer().get(array);
TextureUtil.processPixelValues(array, w, h);
File f = new File(minecraft.gameDirectory, "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 tick() {
if(recipeName != null) {
try {
takeScreenshot();
nextRecipe();
} catch(Throwable t) {
t.printStackTrace();
minecraft.setScreen(null);
}
}
}
}

View File

@@ -0,0 +1,410 @@
/*
* 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 com.mojang.blaze3d.systems.RenderSystem;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.core.BlockPos;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.Style;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.FormattedCharSequence;
import net.minecraft.world.item.ItemStack;
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.loading.FillControl;
import net.montoyo.wd.client.gui.loading.GuiLoader;
import net.montoyo.wd.client.gui.loading.JsonOWrapper;
import net.montoyo.wd.net.WDNetworkRegistry;
import net.montoyo.wd.net.server_bound.C2SMessageACQuery;
import net.montoyo.wd.utilities.*;
import net.montoyo.wd.utilities.data.Bounds;
import net.montoyo.wd.utilities.math.Vector3i;
import net.montoyo.wd.utilities.data.BlockSide;
import net.montoyo.wd.utilities.serialization.NameUUIDPair;
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.List;
import java.util.Map;
import java.util.stream.Collectors;
public abstract class WDScreen extends Screen {
public static WDScreen CURRENT_SCREEN = null;
protected final ArrayList<Control> controls = new ArrayList<>();
protected final ArrayList<Control> postDrawList = new ArrayList<>();
private final HashMap<Class<? extends Event>, Method> eventMap = new HashMap<>();
protected boolean quitOnEscape = true;
protected boolean defaultBackground = true;
protected int syncTicks = 40;
private int syncTicksLeft = -1;
public WDScreen(Component component) {
super(component);
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<? extends Event>) params[0], m);
}
}
}
protected <T extends Control> T addControl(T ctrl) {
controls.add(ctrl);
return ctrl;
}
public int screen2DisplayX(int x) {
double ret = ((double) x) / ((double) width) * ((double) minecraft.getWindow().getWidth());
return (int) ret;
}
public int screen2DisplayY(int y) {
double ret = ((double) y) / ((double) height) * ((double) minecraft.getWindow().getHeight());
return (int) ret;
}
public int display2ScreenX(int x) {
double ret = ((double) x) / ((double) minecraft.getWindow().getWidth()) * ((double) width);
return (int) ret;
}
public int display2ScreenY(int y) {
double ret = ((double) y) / ((double) minecraft.getWindow().getHeight()) * ((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 render(GuiGraphics poseStack, int mouseX, int mouseY, float ptt) {
if(defaultBackground)
renderBackground(poseStack, mouseX, mouseY, ptt);
RenderSystem.setShaderColor(1.f, 1.f, 1.f, 1.f);
for(Control ctrl: controls)
ctrl.draw(poseStack, mouseX, mouseY, ptt);
for(Control ctrl: postDrawList)
ctrl.postDraw(poseStack, mouseX, mouseY, ptt);
}
@Override
public boolean charTyped(char codePoint, int modifiers) {
boolean typed = false;
for(Control ctrl: controls)
typed = typed || ctrl.keyTyped(codePoint, modifiers);
return typed;
}
@Override
public boolean mouseClicked(double mouseX, double mouseY, int button) {
boolean clicked = false;
Control clickedEl = null;
for(Control ctrl: controls) {
clicked = ctrl.mouseClicked(mouseX, mouseY, button);
if (clicked) {
clickedEl = ctrl;
break; // don't assume the compiler will optimize stuff
}
}
if (clicked) {
for (Control control : controls) {
if (control != clickedEl)
control.unfocus();
}
}
return clicked;
}
@Override
public boolean mouseReleased(double mouseX, double mouseY, int button) {
boolean mouseReleased = false;
for(Control ctrl: controls)
mouseReleased = mouseReleased || ctrl.mouseReleased(mouseX, mouseY, button);
return mouseReleased;
}
@Override
public boolean mouseDragged(double mouseX, double mouseY, int button, double dragX, double dragY) {
boolean dragged = false;
for(Control ctrl: controls)
dragged = dragged || ctrl.mouseClickMove(mouseX, mouseY, button, dragX, dragY);
return dragged;
}
@Override
protected void init() {
CURRENT_SCREEN = this;
// minecraft.keyboardHandler.setSendRepeatsToGui(true);
}
@Override
public void onClose() {
if(syncTicksLeft >= 0) {
sync();
syncTicksLeft = -1;
}
for(Control ctrl : controls)
ctrl.destroy();
// Minecraft.getInstance().keyboardHandler.setSendRepeatsToGui(false);
CURRENT_SCREEN = null;
}
@Override
public boolean mouseScrolled(double mouseX, double mouseY, double scrollX, double scrollY) {
boolean scrolled = false;
for(Control ctrl : controls)
scrolled = scrolled || ctrl.mouseScroll(mouseX, mouseY, scrollY);
return scrolled;
}
@Override
public void mouseMoved(double mouseX, double mouseY) {
boolean moved = false;
for(Control ctrl : controls)
moved = moved || ctrl.mouseMove(mouseX, mouseY);
super.mouseMoved(mouseX, mouseY);
}
@Override
public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
boolean down = false;
for (Control ctrl : controls)
down = down || ctrl.keyDown(keyCode, scanCode, modifiers);
if (this instanceof GuiKeyboard) {
return down;
} else {
return new GuiServer(new Vector3i(), new NameUUIDPair()).keyPressed(keyCode, scanCode, modifiers);
}
}
@Override
public boolean keyReleased(int keyCode, int scanCode, int modifiers) {
boolean up = false;
for(Control ctrl : controls)
up = up || ctrl.keyUp(keyCode, scanCode, modifiers);
return up || super.keyReleased(keyCode, scanCode, modifiers);
}
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 extends Control> 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<String, Double> vars) {
}
public void loadFrom(ResourceLocation resLoc) {
try {
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<String, Double> vars = new HashMap<>();
vars.put("width", (double) width);
vars.put("height", (double) height);
vars.put("displayWidth", (double) minecraft.getWindow().getWidth());
vars.put("displayHeight", (double) minecraft.getWindow().getHeight());
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();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void resize(Minecraft minecraft, int width, int height) {
for(Control ctrl : controls)
ctrl.destroy();
controls.clear();
super.resize(minecraft, width, height);
}
protected void requestAutocomplete(String beginning, boolean matchExact) {
WDNetworkRegistry.INSTANCE.sendToServer(new C2SMessageACQuery(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 tick() {
if(syncTicksLeft >= 0) {
if(--syncTicksLeft < 0)
sync();
}
}
public void drawItemStackTooltip(GuiGraphics poseStack, ItemStack is, int x, int y) {
poseStack.renderTooltip(Minecraft.getInstance().font, is, x, y); //Since it's protected...
}
public void drawTooltip(GuiGraphics poseStack, List<String> lines, int x, int y) {
poseStack.renderTooltip(Minecraft.getInstance().font, lines.stream().map(a -> FormattedCharSequence.forward(a, Style.EMPTY)).collect(Collectors.toList()), x, y); //This is also protected...
}
public void requirePostDraw(Control ctrl) {
if(!postDrawList.contains(ctrl))
postDrawList.add(ctrl);
}
@Override
public boolean isPauseScreen() {
return false;
}
public abstract boolean isForBlock(BlockPos bp, BlockSide side);
@Nullable
public String getWikiPageName() {
return null;
}
//Bypass for needing to use Components
}

View File

@@ -0,0 +1,271 @@
package net.montoyo.wd.client.gui.camera;
import net.minecraft.client.Minecraft;
import net.minecraft.commands.arguments.EntityAnchorArgument;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.phys.Vec2;
import net.minecraft.world.phys.Vec3;
import net.neoforged.neoforge.client.event.ViewportEvent;
import net.neoforged.neoforge.client.event.ClientTickEvent;
import net.montoyo.wd.client.gui.GuiKeyboard;
import net.montoyo.wd.config.ClientConfig;
import net.montoyo.wd.entity.ScreenBlockEntity;
import net.montoyo.wd.entity.ScreenData;
import net.montoyo.wd.utilities.browser.WDBrowser;
import net.montoyo.wd.utilities.browser.handlers.js.queries.ElementCenterQuery;
import net.montoyo.wd.utilities.data.BlockSide;
public class KeyboardCamera {
private static ScreenBlockEntity tes;
private static BlockSide side;
private static double oxCrd = -1;
private static double xCrd = -1;
private static double nxCrd = -1;
private static double oyCrd = -1;
private static double yCrd = -1;
private static double nyCrd = -1;
private static double nextX = -1;
private static double nextY = -1;
private static double focalX = -1;
private static double focalY = -1;
private static final boolean[] mouseStatus = new boolean[2];
protected static Vec2 pxToHit(ScreenData scr, Vec2 dst) {
float cx, cy;
if (scr.rotation.isVertical) {
cy = dst.x;
cx = dst.y;
} else {
cx = dst.x;
cy = dst.y;
}
cx /= (float) scr.resolution.x;
cy /= (float) scr.resolution.y;
switch (scr.rotation) {
case ROT_270:
cx = 1.0f - cx;
break;
case ROT_180:
cx = 1.0f - cx;
cy = 1.0f - cy;
break;
case ROT_90:
cy = 1.0f - cy;
break;
}
if (side != BlockSide.BOTTOM)
cy = 1.0f - cy;
float swInverse = (((float) scr.size.x) - 4.f / 16.f);
float shInverse = (((float) scr.size.y) - 4.f / 16.f);
cx *= swInverse;
cy *= shInverse;
if (side.right.x > 0 || side.right.z > 0)
cx += 1.f;
if (side == BlockSide.TOP || side == BlockSide.BOTTOM)
cy -= 1.f;
return new Vec2(cx + (2 / 16f), cy + (2 / 16f));
}
protected static void updateCrd(ElementCenterQuery lock) {
ScreenData scr = tes.getScreen(side);
if (scr != null) {
Vec2 c;
if (!mouseStatus[0] && !mouseStatus[1]) {
if (lock.hasFocused()) {
if (ClientConfig.Input.keyboardCamera) {
nextX = lock.getX();
nextY = lock.getY();
c = pxToHit(scr, new Vec2((float) nextX, (float) nextY));
} else c = new Vec2(scr.size.x / 2f, scr.size.y / 2f);
} else c = new Vec2(scr.size.x / 2f, scr.size.y / 2f);
// } else c = new Vec2((float) focalX, (float) focalY);
} else return;
focalX = c.x;
focalY = c.y;
nextX = c.x;
nextY = c.y;
if (nextX < 0) nextX = 0;
else if (nextX > scr.size.x) nextX = scr.size.x;
if (nextY < 0) nextY = 0;
else if (nextY > scr.size.y) nextY = scr.size.y;
float scl = Math.max(scr.size.x, scr.size.y);
double mx = Minecraft.getInstance().mouseHandler.xpos();
mx /= Minecraft.getInstance().getWindow().getWidth();
double my = Minecraft.getInstance().mouseHandler.ypos();
my /= Minecraft.getInstance().getWindow().getHeight();
Vec2 v2 = new Vec2((float) mx, (float) my).add(-0.5f);
nextX += v2.x * scl;
nextY -= v2.y * scl;
}
}
protected static void pollElement() {
ScreenBlockEntity teTmp = tes;
BlockSide sdTmp = side;
// async nonsense can occur here
if (teTmp == null || sdTmp == null) return;
ScreenData scr = teTmp.getScreen(sdTmp);
if (scr != null) {
if (scr.browser instanceof WDBrowser wdBrowser) {
wdBrowser.focusedElement().dispatch(scr.browser);
updateCrd(((WDBrowser) scr.browser).focusedElement());
}
}
}
public static float[] getAngle(Entity e, double pct) {
BlockEntity tes = KeyboardCamera.tes;
BlockSide side = KeyboardCamera.side;
if (tes == null) return new float[]{Float.NaN, 0};
if (side == null) return new float[]{Float.NaN, 0};
double coxCrd = Mth.lerp(0.5 * pct, oxCrd, xCrd);
double coyCrd = Mth.lerp(0.5 * pct, oyCrd, yCrd);
double focalX = tes.getBlockPos().getX() +
side.right.x * (coxCrd - 1) + side.up.x * coyCrd + Math.abs(side.forward.x) * 0.5;
double focalY = tes.getBlockPos().getY() +
side.right.y * (coxCrd - 1) + side.up.y * coyCrd + Math.abs(side.forward.y) * 0.5;
double focalZ = tes.getBlockPos().getZ() +
side.right.z * (coxCrd - 1) + side.up.z * coyCrd + Math.abs(side.forward.z) * 0.5;
focalX += side.forward.x * 0.5f;
focalY += side.forward.y * 0.5f;
focalZ += side.forward.z * 0.5f;
float[] angle = lookAt(
e, EntityAnchorArgument.Anchor.EYES,
new Vec3(focalX, focalY, focalZ)
);
return angle;
}
public static void setMouse(int side, boolean pressed) {
mouseStatus[side] = pressed;
}
public static void updateCamera(ViewportEvent.ComputeCameraAngles event) {
if (tes == null) {
xCrd = -1;
yCrd = -1;
return; // nothing to do
}
if (xCrd == -1) return;
if (yCrd == -1) return;
float[] angle = getAngle(event.getCamera().getEntity(), event.getPartialTick());
if (Float.isNaN(angle[0])) return;
// float xRot = event.getYaw(); // left right
// float yRot = event.getPitch(); // up down
// TODO: smooth in/out
event.setYaw(angle[1]);
event.setPitch(angle[0]);
}
public static void focus(ScreenBlockEntity screen, BlockSide side) {
KeyboardCamera.tes = screen;
KeyboardCamera.side = side;
}
public static float[] lookAt(Entity entity, EntityAnchorArgument.Anchor pAnchor, Vec3 pTarget) {
Vec3 vec3 = pAnchor.apply(entity);
double d0 = pTarget.x - vec3.x;
double d1 = pTarget.y - vec3.y;
double d2 = pTarget.z - vec3.z;
double d3 = Math.sqrt(d0 * d0 + d2 * d2);
float xr = (Mth.wrapDegrees((float) (-(Mth.atan2(d1, d3) * (double) (180F / (float) Math.PI)))));
float yr = (Mth.wrapDegrees((float) (Mth.atan2(d2, d0) * (double) (180F / (float) Math.PI)) - 90.0F));
return new float[]{xr, yr};
}
protected static int delay = 8;
public static void gameTick(ClientTickEvent.Post event) {
if (mouseStatus[0] || mouseStatus[1]) {
oxCrd = Mth.lerp(0.5, oxCrd, xCrd);
oyCrd = Mth.lerp(0.5, oyCrd, yCrd);
return;
}
if (side == null) {
delay = 1;
oxCrd = -1;
oyCrd = -1;
xCrd = -1;
yCrd = -1;
nxCrd = -1;
nyCrd = -1;
return;
}
if (!(Minecraft.getInstance().screen instanceof GuiKeyboard)) {
tes = null;
side = null;
return;
}
pollElement();
double anxx = nextX;
double anxy = nextY;
if (
anxx == -1 || anxy == -1 ||
nxCrd == -1 || nyCrd == -1 ||
oxCrd == -1 || oyCrd == -1 ||
xCrd == -1 || yCrd == -1
) {
ScreenData data = tes.getScreen(side);
if (data == null)
return;
anxx = data.size.x / 2.0;
anxy = data.size.y / 2.0;
if (nxCrd == -1) {
oxCrd = xCrd = anxx;
oyCrd = yCrd = anxy;
}
}
nxCrd = anxx;
nyCrd = anxy;
oxCrd = Mth.lerp(0.5, oxCrd, xCrd);
xCrd = Mth.lerp(0.15, xCrd, nxCrd);
oyCrd = Mth.lerp(0.5, oyCrd, yCrd);
yCrd = Mth.lerp(0.15, yCrd, nyCrd);
}
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,238 @@
/*
* Copyright (C) 2018 BARBOTIN Nicolas
*/
package net.montoyo.wd.client.gui.controls;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.ComponentContents;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.network.chat.Style;
import net.montoyo.wd.client.gui.loading.JsonOWrapper;
import org.lwjgl.glfw.GLFW;
import java.util.function.Supplier;
public class Button extends Control {
protected final net.minecraft.client.gui.components.Button btn;
protected boolean selected = false;
protected boolean shiftDown = false;
protected int originalColor = 0;
protected int shiftColor = 0;
public static class ClickEvent extends Event<Button> {
private final boolean shiftDown;
public ClickEvent(Button btn) {
source = btn;
shiftDown = btn.shiftDown;
}
public boolean isShiftDown() {
return shiftDown;
}
}
public Button() {
btn = net.minecraft.client.gui.components.Button.builder(Component.nullToEmpty(""), a -> {})
.bounds(0, 0, 0, 0)
.build();
}
public Button(String text, int x, int y, int width) {
btn = net.minecraft.client.gui.components.Button.builder(Component.nullToEmpty(text), a -> {})
.bounds(x, y, width, 20)
.build();
}
public Button(String text, int x, int y) {
btn = net.minecraft.client.gui.components.Button.builder(Component.nullToEmpty(text), a -> {})
.bounds(0, 0, x, y)
.build();
}
@Override
public boolean mouseClicked(double mouseX, double mouseY, int mouseButton) {
if(mouseButton == 0 && btn.mouseClicked(mouseX, mouseY, mouseButton)) {
selected = true;
btn.playDownSound(mc.getSoundManager());
if(!onClick())
parent.actionPerformed(new ClickEvent(this));
return true;
}
return false;
}
@Override
public boolean mouseReleased(double mouseX, double mouseY, int state) {
if(selected && state == 0) {
btn.mouseReleased(mouseX, mouseY,state);
selected = false;
return true;
}
return true;
}
@Override
public void draw(GuiGraphics poseStack, int mouseX, int mouseY, float ptt) {
btn.setFGColor(16777215);
btn.render(poseStack, mouseX, mouseY, ptt);
}
public void setLabel(String label) {
btn.setMessage(Component.nullToEmpty(label));
}
public String getLabel() {
return btn.getMessage().getString();
}
public void setWidth(int width) {
btn.setWidth(width);
}
public void setHeight(int height) {
btn.setHeight(height);
}
@Override
public int getWidth() {
return btn.getWidth();
}
@Override
public int getHeight() {
return btn.getHeight();
}
@Override
public void setPos(int x, int y) {
btn.setPosition(x, y);
}
@Override
public int getX() {
return btn.getX();
}
@Override
public int getY() {
return btn.getY();
}
public net.minecraft.client.gui.components.Button getMcButton() {
return btn;
}
public void setDisabled(boolean dis) {
btn.active = !dis;
}
public boolean isDisabled() {
return !btn.active;
}
public void enable() {
btn.active = true;
}
public void disable() {
btn.active = false;
}
public void setVisible(boolean visible) {
btn.visible = visible;
}
public boolean isVisible() {
return btn.visible;
}
public void show() {
btn.visible = true;
}
public void hide() {
btn.visible = false;
}
public boolean isShiftDown() {
return shiftDown;
}
@Override
public boolean keyUp(int key, int scanCode, int modifiers) {
if(key == GLFW.GLFW_KEY_LEFT_SHIFT || key == GLFW.GLFW_KEY_RIGHT_SHIFT) {
shiftDown = false;
btn.setFGColor(originalColor);
return true;
}
return false;
}
@Override
public boolean keyDown(int key, int scanCode, int modifiers) {
if(key == GLFW.GLFW_KEY_LEFT_SHIFT || key == GLFW.GLFW_KEY_RIGHT_SHIFT) {
shiftDown = true;
btn.setFGColor(shiftColor);
return true;
}
return false;
}
public void setTextColor(int color) {
originalColor = color;
if(!shiftDown)
btn.setFGColor(color);
}
public int getTextColor() {
return btn.getFGColor();
}
public void setShiftTextColor(int shiftColor) {
this.shiftColor = shiftColor;
if(shiftDown)
btn.setFGColor(shiftColor);
}
public int getShiftTextColor() {
return shiftColor;
}
@Override
public void load(JsonOWrapper json) {
super.load(json);
btn.setPosition(
json.getInt("x", 0),
json.getInt("y", 0)
);
btn.setWidth(json.getInt("width", 200));
btn.setHeight(json.getInt("height", 20));
btn.setMessage(Component.nullToEmpty(tr(json.getString("label", btn.getMessage().getContents().toString()))));
btn.active = json.getBool("active", btn.active);
btn.visible = json.getBool("visible", btn.visible);
originalColor = json.getColor("color", originalColor);
shiftColor = json.getColor("shiftColor", shiftColor);
btn.setFGColor(originalColor);
}
protected boolean onClick() {
return false;
}
}

View File

@@ -0,0 +1,144 @@
/*
* Copyright (C) 2018 BARBOTIN Nicolas
*/
package net.montoyo.wd.client.gui.controls;
import com.google.common.collect.Lists;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.client.resources.sounds.SimpleSoundInstance;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.sounds.SoundEvents;
import net.montoyo.wd.client.gui.loading.JsonOWrapper;
public class CheckBox extends BasicControl {
private static final ResourceLocation texUnchecked = ResourceLocation.fromNamespaceAndPath("webdisplays", "textures/gui/checkbox.png");
private static final ResourceLocation texChecked = ResourceLocation.fromNamespaceAndPath("webdisplays", "textures/gui/checkbox_checked.png");
public static final int WIDTH = 16;
public static final int HEIGHT = 16;
public static class CheckedEvent extends Event<CheckBox> {
private final boolean checked;
public CheckedEvent(CheckBox cb) {
source = cb;
checked = cb.checked;
}
public boolean isChecked() {
return checked;
}
}
private String label;
private int labelW;
private boolean checked;
private java.util.List<String> tooltip;
public CheckBox() {
label = "";
}
public CheckBox(int x, int y, String label) {
this.label = label;
labelW = font.width(label);
checked = false;
this.x = x;
this.y = y;
}
public CheckBox(int x, int y, String label, boolean val) {
this.label = label;
labelW = font.width(label);
checked = val;
this.x = x;
this.y = y;
}
@Override
public boolean mouseClicked(double mouseX, double mouseY, int mouseButton) {
if(mouseButton == 0 && !disabled) {
if(mouseX >= x && mouseX <= x + WIDTH + 2 + labelW && mouseY >= y && mouseY < y + HEIGHT) {
checked = !checked;
mc.getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1.0F));
parent.actionPerformed(new CheckedEvent(this));
return true;
}
}
return false;
}
@Override
public void draw(GuiGraphics poseStack, int mouseX, int mouseY, float ptt) {
if(visible) {
// GlStateManager.disableAlpha();
poseStack.pose().pushPose();
RenderSystem.enableBlend();
poseStack.blit(
checked ? texChecked : texUnchecked, x, y, 0, 0, 0, WIDTH, HEIGHT, WIDTH, HEIGHT
);
RenderSystem.disableBlend();
poseStack.pose().popPose();
boolean inside = (!disabled && mouseX >= x && mouseX <= x + WIDTH + 2 + labelW && mouseY >= y && mouseY < y + HEIGHT);
poseStack.drawString(Minecraft.getInstance().font, label, x + WIDTH + 2, y + 4, inside ? 0xFF0080FF : COLOR_WHITE, false);
}
}
public void setLabel(String label) {
this.label = label;
labelW = font.width(label);
}
public String getLabel() {
return label;
}
public boolean isChecked() {
return checked;
}
public void setChecked(boolean checked) {
this.checked = checked;
}
@Override
public int getWidth() {
return WIDTH + 2 + labelW;
}
@Override
public int getHeight() {
return HEIGHT;
}
@Override
public void load(JsonOWrapper json) {
super.load(json);
label = tr(json.getString("label", ""));
labelW = font.width(label);
checked = json.getBool("checked", false);
String tt = tr(json.getString("tooltip", ""));
if(!tt.isEmpty()) {
tooltip = Lists.newArrayList(tt.split("\\\\n"));
parent.requirePostDraw(this);
}
}
@Override
public void postDraw(GuiGraphics poseStack, int mouseX, int mouseY, float ptt) {
if(tooltip != null && !disabled && mouseX >= x && mouseX <= x + WIDTH + 2 + labelW && mouseY >= y && mouseY < y + HEIGHT)
parent.drawTooltip(poseStack, tooltip, mouseX, mouseY);
}
}

View File

@@ -0,0 +1,198 @@
/*
* Copyright (C) 2018 BARBOTIN Nicolas
*/
package net.montoyo.wd.client.gui.controls;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.gui.GuiGraphics;
import net.montoyo.wd.client.gui.loading.GuiLoader;
import net.montoyo.wd.client.gui.loading.JsonAWrapper;
import net.montoyo.wd.client.gui.loading.JsonOWrapper;
import java.util.ArrayList;
public abstract class Container extends BasicControl {
protected int paddingX = 0;
protected int paddingY = 0;
protected final ArrayList<Control> childs = new ArrayList<>();
public <T extends Control> T addControl(T ctrl) {
childs.add(ctrl);
return ctrl;
}
@Override
public boolean keyTyped(int keyCode, int modifiers) {
boolean typed = false;
if(!disabled) {
for(Control ctrl : childs)
typed = typed || ctrl.keyTyped(keyCode, modifiers);
}
return typed;
}
@Override
public boolean keyUp(int key, int scanCode, int modifiers) {
boolean up = false;
if(!disabled) {
for(Control ctrl : childs)
up = up || ctrl.keyUp(key, scanCode, modifiers);
}
return up;
}
@Override
public boolean keyDown(int key, int scanCode, int modifiers) {
boolean down = false;
if(!disabled) {
for(Control ctrl : childs)
down = down || ctrl.keyDown(key, scanCode, modifiers);
}
return down;
}
@Override
public boolean mouseClicked(double mouseX, double mouseY, int mouseButton) {
boolean clicked = false;
if(!disabled) {
mouseX -= x + paddingX;
mouseY -= y + paddingY;
for(Control ctrl : childs) {
clicked = ctrl.mouseClicked(mouseX, mouseY, mouseButton);
if (clicked) break; // don't assume compiler optimizations
}
}
return clicked;
}
@Override
public boolean mouseReleased(double mouseX, double mouseY, int state) {
boolean released = false;
if(!disabled) {
mouseX -= x + paddingX;
mouseY -= y + paddingY;
for(Control ctrl : childs)
released = released || ctrl.mouseReleased(mouseX, mouseY, state);
}
return released;
}
@Override
public boolean mouseClickMove(double mouseX, double mouseY, int button, double dragX, double dragY) {
boolean clicked = false;
if(!disabled) {
mouseX -= x + paddingX;
mouseY -= y + paddingY;
for(Control ctrl : childs)
clicked = clicked || ctrl.mouseClickMove(mouseX, mouseY, button, dragX, dragY);
}
return clicked;
}
@Override
public boolean mouseMove(double mouseX, double mouseY) {
boolean clicked = false;
if(!disabled) {
mouseX -= x + paddingX;
mouseY -= y + paddingY;
for(Control ctrl : childs)
clicked = clicked || ctrl.mouseMove(mouseX, mouseY);
}
return clicked;
}
@Override
public boolean mouseScroll(double mouseX, double mouseY, double amount) {
boolean scrolled = false;
if(!disabled) {
mouseX -= x + paddingX;
mouseY -= y + paddingY;
for(Control ctrl : childs)
scrolled = scrolled || ctrl.mouseScroll(mouseX, mouseY, amount);
}
return scrolled;
}
@Override
public void draw(GuiGraphics poseStack, int mouseX, int mouseY, float ptt) {
if(visible) {
mouseX -= x + paddingX;
mouseY -= y + paddingY;
poseStack.pose().pushPose();
poseStack.pose().translate(x + paddingX, y + paddingY, 0.0);
if(disabled) {
for(Control ctrl : childs)
ctrl.draw(poseStack, -1, -1, ptt);
} else {
for(Control ctrl : childs)
ctrl.draw(poseStack, mouseX, mouseY, ptt);
}
poseStack.pose().popPose();
}
}
@Override
public void destroy() {
for(Control ctrl : childs)
ctrl.destroy();
}
@Override
public void load(JsonOWrapper json) {
super.load(json);
JsonAWrapper objs = json.getArray("childs");
for(int i = 0; i < objs.size(); i++)
childs.add(GuiLoader.create(objs.getObject(i)));
}
public Control getByName(String name) {
for(Control ctrl : childs) {
if(name.equals(ctrl.name))
return ctrl;
if(ctrl instanceof Container) {
Control ret = ((Container) ctrl).getByName(name);
if(ret != null)
return ret;
}
}
return null;
}
@Override
public void unfocus() {
for (Control control : childs) {
control.unfocus();
}
}
}

View File

@@ -0,0 +1,319 @@
/*
* Copyright (C) 2018 BARBOTIN Nicolas
*/
package net.montoyo.wd.client.gui.controls;
import com.mojang.blaze3d.pipeline.RenderTarget;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.*;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.resources.language.I18n;
import net.minecraft.resources.ResourceLocation;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.api.distmarker.OnlyIn;
import net.montoyo.wd.client.gui.WDScreen;
import net.montoyo.wd.client.gui.loading.JsonOWrapper;
import net.montoyo.wd.utilities.data.Bounds;
import org.joml.Matrix4f;
import java.util.Arrays;
import static com.mojang.math.Axis.XP;
@OnlyIn(Dist.CLIENT)
public abstract class Control {
public static final int COLOR_BLACK = 0xFF000000;
public static final int COLOR_WHITE = 0xFFFFFFFF;
public static final int COLOR_RED = 0xFFFF0000;
public static final int COLOR_GREEN = 0xFF00FF00;
public static final int COLOR_BLUE = 0xFF0000FF;
public static final int COLOR_CYAN = 0xFF00FFFF;
public static final int COLOR_MANGENTA = 0xFFFF00FF;
public static final int COLOR_YELLOW = 0xFFFFFF00;
protected final Minecraft mc;
protected final Font font;
protected final Tesselator tessellator;
protected static WDScreen parent;
protected String name;
protected Object userdata;
public Control() {
mc = Minecraft.getInstance();
font = mc.font;
tessellator = Tesselator.getInstance();
parent = WDScreen.CURRENT_SCREEN;
}
public Object getUserdata() {
return userdata;
}
public void setUserdata(Object userdata) {
this.userdata = userdata;
}
public boolean keyTyped(int keyCode, int modifier) {
return false;
}
public boolean keyUp(int key, int scanCode, int modifiers) {
return false;
}
public boolean keyDown(int key, int scanCode, int modifiers) {
return false;
}
public boolean mouseClicked(double mouseX, double mouseY, int mouseButton) {
return false;
}
public void unfocus() {
}
public boolean mouseReleased(double mouseX, double mouseY, int state) {
return false;
}
public boolean mouseClickMove(double mouseX, double mouseY, int button, double dragX, double dragY) {
return false;
}
public boolean mouseMove(double mouseX, double mouseY) {
return false;
}
public boolean mouseScroll(double mouseX, double mouseY, double amount) {
return false;
}
public void draw(GuiGraphics poseStack, int mouseX, int mouseY, float ptt) {
}
public void postDraw(GuiGraphics poseStack, int mouseX, int mouseY, float ptt) {
}
public void destroy() {
}
public WDScreen getParent() {
return parent;
}
public abstract int getX();
public abstract int getY();
public abstract int getWidth();
public abstract int getHeight();
public abstract void setPos(int x, int y);
public void fillRect(MultiBufferSource.BufferSource source, int x, double y, int w, int h, int color) {
float x1 = (float) x;
float y1 = (float) y;
float x2 = (float) (x + w);
float y2 = (float) (y + h);
int a = (color >> 24) & 0xFF;
int r = (color >> 16) & 0xFF;
int g = (color >> 8 ) & 0xFF;
int b = color & 0xFF;
float[] sdrCol = Arrays.copyOf(RenderSystem.getShaderColor(), 4);
RenderSystem.setShaderColor(1, 1, 1, 1f);
RenderSystem.enableBlend();
RenderSystem.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA);
VertexConsumer consumer = source.getBuffer(RenderType.gui());
consumer.addVertex(x1, y2, 0.0f).setColor(r, g, b, a);
consumer.addVertex(x2, y2, 0.0f).setColor(r, g, b, a);
consumer.addVertex(x2, y1, 0.0f).setColor(r, g, b, a);
consumer.addVertex(x1, y1, 0.0f).setColor(r, g, b, a);
RenderSystem.setShaderColor(sdrCol[0], sdrCol[1], sdrCol[2], sdrCol[3]);
RenderSystem.disableBlend();
}
public void fillTexturedRect(PoseStack poseStack, int x, int y, int w, int h, double u1, double v1, double u2, double v2) {
float x1 = x;
float y1 = y;
float x2 = (x + w);
float y2 = (y + h);
RenderSystem.setShader(GameRenderer::getPositionTexColorShader);
Matrix4f p = poseStack.last().pose();
BufferBuilder buffer = Tesselator.getInstance().begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX_COLOR);
buffer.addVertex(p, x1, y2, 0.0f).setColor(1f, 1f, 1f, 1f).setUv((float) u1, (float) v2);
buffer.addVertex(p, x2, y2, 0.0f).setColor(1f, 1f, 1f, 1f).setUv((float) u2, (float) v2);
buffer.addVertex(p, x2, y1, 0.0f).setColor(1f, 1f, 1f, 1f).setUv((float) u2, (float) v1);
buffer.addVertex(p, x1, y1, 0.0f).setColor(1f, 1f, 1f, 1f).setUv((float) u1, (float) v1);
BufferUploader.drawWithShader(buffer.buildOrThrow());
}
public static void blend(boolean enable) {
if(enable) {
RenderSystem.enableBlend();
RenderSystem.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_CONSTANT_ALPHA);
} else
RenderSystem.disableBlend();
}
public void bindTexture(ResourceLocation resLoc) {
if(resLoc == null)
RenderSystem.setShaderTexture(0, 0); //Damn state manager
else
RenderSystem.setShaderTexture(0, resLoc);
}
public void drawBorder(GuiGraphics poseStack, int x, int y, int w, int h, int color) {
drawBorder(poseStack, x, y, w, h, color, 1.0);
}
public void drawBorder(GuiGraphics poseStack, int x, int y, int w, int h, int color, double sz) {
double x1 = (double) x;
double y1 = (double) y;
double x2 = (double) (x + w);
double y2 = (double) (y + h);
int a = (color >> 24) & 0xFF;
int r = (color >> 16) & 0xFF;
int g = (color >> 8 ) & 0xFF;
int b = color & 0xFF;
float[] sdrCol = Arrays.copyOf(RenderSystem.getShaderColor(), 4);
RenderSystem.setShaderColor(((float) r) / 255.f, ((float) g) / 255.f, ((float) b) / 255.f, ((float) a) / 255.f);
// RenderSystem.enableTexture();
RenderSystem.enableBlend();
RenderSystem.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA);
RenderSystem.setShader(GameRenderer::getPositionShader);
Matrix4f matrix = poseStack.pose().last().pose();
BufferBuilder buffer = Tesselator.getInstance().begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION);
//Top edge (y = y1)
buffer.addVertex(matrix, (float)x1, (float)(y1 + sz), 0.0f);
buffer.addVertex(matrix, (float)x2, (float)(y1 + sz), 0.0f);
buffer.addVertex(matrix, (float)x2, (float)y1, 0.0f);
buffer.addVertex(matrix, (float)x1, (float)y1, 0.0f);
//Bottom edge (y = y2)
buffer.addVertex(matrix, (float)x1, (float)y2, 0.0f);
buffer.addVertex(matrix, (float)x2, (float)y2, 0.0f);
buffer.addVertex(matrix, (float)x2, (float)(y2 - sz), 0.0f);
buffer.addVertex(matrix, (float)x1, (float)(y2 - sz), 0.0f);
//Left edge (x = x1)
buffer.addVertex(matrix, (float)x1, (float)y2, 0.0f);
buffer.addVertex(matrix, (float)(x1 + sz), (float)y2, 0.0f);
buffer.addVertex(matrix, (float)(x1 + sz), (float)y1, 0.0f);
buffer.addVertex(matrix, (float)x1, (float)y1, 0.0f);
//Right edge (x = x2)
buffer.addVertex(matrix, (float)(x2 - sz), (float)y2, 0.0f);
buffer.addVertex(matrix, (float)x2, (float)y2, 0.0f);
buffer.addVertex(matrix, (float)x2, (float)y1, 0.0f);
buffer.addVertex(matrix, (float)(x2 - sz), (float)y1, 0.0f);
BufferUploader.drawWithShader(buffer.buildOrThrow());
RenderSystem.setShaderColor(sdrCol[0], sdrCol[1], sdrCol[2], sdrCol[3]);
RenderSystem.disableBlend();
// RenderSystem.enableTexture();
}
public GuiGraphics beginFramebuffer(RenderTarget fbo, float vpW, float vpH) {
GuiGraphics tmpGraphics = new GuiGraphics(Minecraft.getInstance(), Minecraft.getInstance().renderBuffers().bufferSource());
fbo.bindWrite(true);
RenderSystem.backupProjectionMatrix();
RenderSystem.setProjectionMatrix(new Matrix4f().ortho(0.0f, vpW, vpH, 0.0f, -1.0f, 1.0f), VertexSorting.ORTHOGRAPHIC_Z);
PoseStack ps = tmpGraphics.pose();
ps.pushPose();
ps.setIdentity();
ps.mulPose(XP.rotationDegrees(180.0f));
org.joml.Matrix4fStack mv = RenderSystem.getModelViewStack();
mv.pushMatrix();
mv.set(ps.last().pose());
RenderSystem.applyModelViewMatrix();
if (!fbo.useDepth)
RenderSystem.disableDepthTest();
return tmpGraphics;
}
public void endFramebuffer(GuiGraphics poseStack, RenderTarget fbo) {
if (!fbo.useDepth)
RenderSystem.enableDepthTest();
RenderSystem.colorMask(true, true, true, true);
RenderSystem.restoreProjectionMatrix();
poseStack.pose().popPose();
org.joml.Matrix4fStack mv = RenderSystem.getModelViewStack();
mv.popMatrix();
RenderSystem.applyModelViewMatrix();
fbo.unbindWrite();
mc.getMainRenderTarget().bindWrite(true);
}
public static String tr(String text) {
if(text.length() >= 2 && text.charAt(0) == '$') {
if(text.charAt(1) == '$')
return text.substring(1);
else
return I18n.get(text.substring(1));
} else
return text;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void load(JsonOWrapper json) {
name = json.getString("name", "");
}
public static Bounds findBounds(java.util.List<Control> controlList) {
int minX = Integer.MAX_VALUE;
int minY = Integer.MAX_VALUE;
int maxX = Integer.MIN_VALUE;
int maxY = Integer.MIN_VALUE;
for(Control ctrl : controlList) {
int x = ctrl.getX();
int y = ctrl.getY();
if(x < minX)
minX = x;
if(y < minY)
minY = y;
x += ctrl.getWidth();
y += ctrl.getHeight();
if(x > maxX)
maxX = x;
if(y >= maxY)
maxY = y;
}
return new Bounds(minX, minY, maxX, maxY);
}
}

View File

@@ -0,0 +1,211 @@
/*
* Copyright (C) 2018 BARBOTIN Nicolas
*/
package net.montoyo.wd.client.gui.controls;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
import com.mojang.blaze3d.vertex.VertexFormat;
import com.mojang.blaze3d.vertex.BufferBuilder;
import com.mojang.blaze3d.vertex.Tesselator;
import com.mojang.blaze3d.vertex.BufferUploader;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.montoyo.wd.client.gui.loading.JsonOWrapper;
import net.montoyo.wd.utilities.data.Bounds;
import java.util.Arrays;
import org.joml.Matrix4f;
public class ControlGroup extends Container {
private int width;
private int height;
private String label;
private int labelW;
private int labelColor = COLOR_WHITE;
private boolean labelShadowed = true;
public ControlGroup() {
width = 100;
height = 100;
label = "";
paddingX = 8;
paddingY = 8;
}
public ControlGroup(int x, int y, int w, int h) {
this.x = x;
this.y = y;
width = w;
height = h;
paddingX = 8;
paddingY = 8;
label = "";
labelW = 0;
}
public ControlGroup(int x, int y, int w, int h, String label) {
this.x = x;
this.y = y;
width = w;
height = h;
this.label = label;
this.labelW = font.width(label);
paddingX = 8;
paddingY = 8;
}
@Override
public int getWidth() {
return width;
}
@Override
public int getHeight() {
return height;
}
public void setSize(int w, int h) {
width = w;
height = h;
}
public void setLabel(String label) {
this.label = label;
labelW = font.width(label);
}
public String getLabel() {
return label;
}
public int getLabelColor() {
return labelColor;
}
public void setLabelColor(int labelColor) {
this.labelColor = labelColor;
}
public boolean isLabelShadowed() {
return labelShadowed;
}
public void setLabelShadowed(boolean labelShadowed) {
this.labelShadowed = labelShadowed;
}
@Override
public void draw(GuiGraphics poseStack, int mouseX, int mouseY, float ptt) {
super.draw(poseStack, mouseX, mouseY, ptt);
if(visible) {
poseStack.pose().pushPose();
float[] sdrCol = Arrays.copyOf(RenderSystem.getShaderColor(), 4);
RenderSystem.setShaderColor(0.5f, 0.5f, 0.5f, 1.f);
// RenderSystem.disableTexture();
RenderSystem.enableBlend();
RenderSystem.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA);
double x1 = x;
double y1 = y;
double x2 = (x + width);
double y2 = (y + height);
double bp = 4.0;
double lw = labelW;
x1 += bp;
y1 += bp;
x2 -= bp;
y2 -= bp;
lw += 12.0;
Matrix4f matrix = poseStack.pose().last().pose();
RenderSystem.setShader(GameRenderer::getPositionColorShader);
BufferBuilder buffer = Tesselator.getInstance().begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR);
//Top edge (y = y1)
if(labelW == 0) {
buffer.addVertex(matrix, (float)x1, (float)(y1 + 1.0), 0.0f).setColor(0.5f, 0.5f, 0.5f, 1.0f);
buffer.addVertex(matrix, (float)x2, (float)(y1 + 1.0), 0.0f).setColor(0.5f, 0.5f, 0.5f, 1.0f);
buffer.addVertex(matrix, (float)x2, (float)y1, 0.0f).setColor(0.5f, 0.5f, 0.5f, 1.0f);
buffer.addVertex(matrix, (float)x1, (float)y1, 0.0f).setColor(0.5f, 0.5f, 0.5f, 1.0f);
} else {
//Left
buffer.addVertex(matrix, (float)x1, (float)(y1 + 1.0), 0.0f).setColor(0.5f, 0.5f, 0.5f, 1.0f);
buffer.addVertex(matrix, (float)(x1 + 8.0), (float)(y1 + 1.0), 0.0f).setColor(0.5f, 0.5f, 0.5f, 1.0f);
buffer.addVertex(matrix, (float)(x1 + 8.0), (float)y1, 0.0f).setColor(0.5f, 0.5f, 0.5f, 1.0f);
buffer.addVertex(matrix, (float)x1, (float)y1, 0.0f).setColor(0.5f, 0.5f, 0.5f, 1.0f);
//Right
buffer.addVertex(matrix, (float)(x1 + lw), (float)(y1 + 1.0), 0.0f).setColor(0.5f, 0.5f, 0.5f, 1.0f);
buffer.addVertex(matrix, (float)x2, (float)(y1 + 1.0), 0.0f).setColor(0.5f, 0.5f, 0.5f, 1.0f);
buffer.addVertex(matrix, (float)x2, (float)y1, 0.0f).setColor(0.5f, 0.5f, 0.5f, 1.0f);
buffer.addVertex(matrix, (float)(x1 + lw), (float)y1, 0.0f).setColor(0.5f, 0.5f, 0.5f, 1.0f);
}
//Bottom edge (y = y2)
buffer.addVertex(matrix, (float)x1, (float)y2, 0.0f).setColor(0.5f, 0.5f, 0.5f, 1.0f);
buffer.addVertex(matrix, (float)x2, (float)y2, 0.0f).setColor(0.5f, 0.5f, 0.5f, 1.0f);
buffer.addVertex(matrix, (float)x2, (float)(y2 - 1.0), 0.0f).setColor(0.5f, 0.5f, 0.5f, 1.0f);
buffer.addVertex(matrix, (float)x1, (float)(y2 - 1.0), 0.0f).setColor(0.5f, 0.5f, 0.5f, 1.0f);
//Left edge (x = x1)
buffer.addVertex(matrix, (float)x1, (float)y2, 0.0f).setColor(0.5f, 0.5f, 0.5f, 1.0f);
buffer.addVertex(matrix, (float)(x1 + 1.0), (float)y2, 0.0f).setColor(0.5f, 0.5f, 0.5f, 1.0f);
buffer.addVertex(matrix, (float)(x1 + 1.0), (float)y1, 0.0f).setColor(0.5f, 0.5f, 0.5f, 1.0f);
buffer.addVertex(matrix, (float)x1, (float)y1, 0.0f).setColor(0.5f, 0.5f, 0.5f, 1.0f);
//Right edge (x = x2)
buffer.addVertex(matrix, (float)(x2 - 1.0), (float)y2, 0.0f).setColor(0.5f, 0.5f, 0.5f, 1.0f);
buffer.addVertex(matrix, (float)x2, (float)y2, 0.0f).setColor(0.5f, 0.5f, 0.5f, 1.0f);
buffer.addVertex(matrix, (float)x2, (float)y1, 0.0f).setColor(0.5f, 0.5f, 0.5f, 1.0f);
buffer.addVertex(matrix, (float)(x2 - 1.0), (float)y1, 0.0f).setColor(0.5f, 0.5f, 0.5f, 1.0f);
BufferUploader.drawWithShader(buffer.buildOrThrow());
RenderSystem.setShaderColor(sdrCol[0], sdrCol[1], sdrCol[2], sdrCol[3]);
RenderSystem.disableBlend();
// RenderSystem.enableTexture();
poseStack.pose().popPose();
if(labelW != 0)
poseStack.drawString(Minecraft.getInstance().font, label, x + 10 + ((int) bp), y, labelColor, labelShadowed);
}
}
public void pack() {
Bounds bounds = findBounds(childs);
for(Control ctrl : childs)
ctrl.setPos(ctrl.getX() - bounds.minX, ctrl.getY() - bounds.minY);
width = bounds.getWidth() + paddingX * 2;
height = bounds.getHeight() + paddingY * 2;
}
@Override
public void unfocus() {
for (Control control : childs) {
control.unfocus();
}
}
@Override
public void load(JsonOWrapper json) {
super.load(json);
width = json.getInt("width", 100);
height = json.getInt("height", 100);
label = tr(json.getString("label", ""));
labelW = font.width(label);
labelColor = json.getColor("labelColor", COLOR_WHITE);
labelShadowed = json.getBool("labelShadowed", true);
if(json.getBool("pack", false))
pack();
}
}

View File

@@ -0,0 +1,14 @@
/*
* Copyright (C) 2018 BARBOTIN Nicolas
*/
package net.montoyo.wd.client.gui.controls;
public abstract class Event<T extends Control> {
protected T source;
public T getSource() {
return source;
}
}

View File

@@ -0,0 +1,98 @@
/*
* Copyright (C) 2018 BARBOTIN Nicolas
*/
package net.montoyo.wd.client.gui.controls;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.resources.ResourceLocation;
import net.montoyo.wd.client.gui.loading.JsonOWrapper;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GL11;
public class Icon extends BasicControl {
protected int width;
protected int height;
protected double u1;
protected double v1;
protected double u2;
protected double v2;
protected ResourceLocation texture;
@Override
public int getWidth() {
return width;
}
@Override
public int getHeight() {
return height;
}
@Override
public void load(JsonOWrapper json) {
super.load(json);
width = json.getInt("width", 16);
height = json.getInt("height", 16);
u1 = json.getDouble("u1", 0.0);
v1 = json.getDouble("v1", 0.0);
u2 = json.getDouble("u2", 1.0);
v2 = json.getDouble("v2", 1.0);
texture = ResourceLocation.tryParse(json.getString("resourceLocation", ""));
}
@Override
public void draw(GuiGraphics poseStack, int mouseX, int mouseY, float ptt) {
if(texture != null) {
poseStack.pose().pushPose();
// RenderSystem.enableTexture();
RenderSystem.setShaderTexture(1, texture);
RenderSystem.bindTexture(1);
RenderSystem.enableBlend();
fillTexturedRect(poseStack.pose(), x, y, width, height, u1, v1, u2, v2);
RenderSystem.disableBlend();
RenderSystem.bindTexture(-1);
poseStack.pose().popPose();
}
}
public void setWidth(int width) {
this.width = width;
}
public void setHeight(int height) {
this.height = height;
}
public void setTextureCoordinates(double u1, double v1, double u2, double v2) {
this.u1 = u1;
this.v1 = v1;
this.u2 = u2;
this.v2 = v2;
}
public void setTexture(ResourceLocation texture) {
this.texture = texture;
}
public double getU1() {
return u1;
}
public double getV1() {
return v1;
}
public double getU2() {
return u2;
}
public double getV2() {
return v2;
}
}

View File

@@ -0,0 +1,104 @@
/*
* Copyright (C) 2018 BARBOTIN Nicolas
*/
package net.montoyo.wd.client.gui.controls;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.montoyo.wd.client.gui.loading.JsonOWrapper;
public class Label extends BasicControl {
private String label;
private int labelW;
private int color;
private boolean shadowed;
public Label() {
label = "";
color = COLOR_WHITE;
}
public Label(int x, int y, String str) {
this.x = x;
this.y = y;
label = str;
labelW = font.width(str);
color = COLOR_WHITE;
shadowed = false;
}
public Label(int x, int y, String str, int color) {
this.x = x;
this.y = y;
label = str;
labelW = font.width(str);
this.color = color;
shadowed = false;
}
public Label(int x, int y, String str, int color, boolean shadowed) {
this.x = x;
this.y = y;
label = str;
labelW = font.width(str);
this.color = color;
this.shadowed = shadowed;
}
public void setLabel(String label) {
this.label = label;
labelW = font.width(label);
}
public String getLabel() {
return label;
}
public void setColor(int color) {
this.color = color;
}
public int getColor() {
return color;
}
public void setShadowed(boolean shadowed) {
this.shadowed = shadowed;
}
public boolean isShadowed() {
return shadowed;
}
@Override
public void draw(GuiGraphics poseStack, int mouseX, int mouseY, float ptt) {
if(visible)
poseStack.drawString(
Minecraft.getInstance().font,
label, x, y, color, shadowed
);
}
@Override
public int getWidth() {
return labelW;
}
@Override
public int getHeight() {
return 12;
}
@Override
public void load(JsonOWrapper json) {
super.load(json);
label = tr(json.getString("label", ""));
labelW = font.width(label);
color = json.getColor("color", COLOR_WHITE);
shadowed = json.getBool("shadowed", false);
}
}

View File

@@ -0,0 +1,421 @@
/*
* Copyright (C) 2018 BARBOTIN Nicolas
*/
package net.montoyo.wd.client.gui.controls;
import com.mojang.blaze3d.pipeline.RenderTarget;
import com.mojang.blaze3d.pipeline.TextureTarget;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.client.renderer.MultiBufferSource;
import net.montoyo.wd.client.gui.loading.JsonOWrapper;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import java.util.ArrayList;
import static org.lwjgl.opengl.GL11.*;
public class List extends BasicControl {
private static class Entry {
public final String text;
public final Object userdata;
public Entry(String t, Object o) {
text = t;
userdata = o;
}
}
public static class EntryClick extends Event<List> {
private final int id;
private final Entry entry;
public EntryClick(List lst) {
source = lst;
id = lst.selected;
entry = lst.content.get(lst.selected);
}
public int getId() {
return id;
}
public String getLabel() {
return entry.text;
}
public Object getUserdata() {
return entry.userdata;
}
}
private int width;
private int height;
private final ArrayList<Entry> content = new ArrayList<>();
private RenderTarget fbo;
private int selected = -1;
private boolean update;
private int selColor = 0xFF0080FF;
//Scroll handling
private int contentH = 0;
private int scrollSize;
private double scrollPos = 0;
private boolean scrolling = false;
private double scrollGrab;
public List() {
content.add(new Entry("", null));
selected = 0;
}
public List(int x, int y, int w, int h) {
this.x = x;
this.y = y;
width = w;
height = h;
scrollSize = h - 2;
createFBO();
}
private int getYOffset() {
double amount = ((double) scrollPos) / ((double) (height - 2 - scrollSize)) * ((double) (contentH - height));
return (int) amount;
}
private boolean isInScrollbar(double mouseX, double mouseY) {
return mouseX >= x + width - 5 && mouseX <= x + width - 1 && mouseY >= y + 1 + scrollPos && mouseY <= y + 1 + scrollPos + scrollSize;
}
private void createFBO() {
if(fbo != null)
fbo.destroyBuffers();
fbo = new TextureTarget(parent.screen2DisplayX(width), parent.screen2DisplayY(height), true, Minecraft.ON_OSX);
fbo.setFilterMode(GL_NEAREST);
fbo.bindWrite(true);
RenderSystem.clearColor(0.0f, 0.0f, 0.0f, 1.f); //Set alpha to 1
RenderSystem.clearDepth(GL_COLOR_BUFFER_BIT);
fbo.unbindWrite();
mc.getMainRenderTarget().bindWrite(true);
update = true;
}
private void renderToFBO(MultiBufferSource.BufferSource source) {
GuiGraphics graphics = beginFramebuffer(fbo, width, height);
GL11.glColorMask(true, true, true, true);
RenderSystem.applyModelViewMatrix();
graphics.fill(0, 0, width, height, COLOR_BLACK);
RenderSystem.setShaderColor(1.f, 1.f, 1.f, 1.f);
int offset = 4 - getYOffset();
for(int i = 0; i < content.size(); i++) {
int pos = i * 12 + offset;
if(pos + 12 >= 1) {
if(pos >= height - 1)
break;
int color = (i == selected) ? selColor : COLOR_WHITE;
graphics.drawString(font, content.get(i).text, 4, i * 12 + offset, color);
}
}
graphics.renderOutline(0, 0, width, height, 0xFF808080);
graphics.flush();
endFramebuffer(graphics, fbo);
}
@Override
public void destroy() {
if(fbo != null)
fbo.destroyBuffers();
}
public void setSize(int w, int h) {
width = w;
height = h;
createFBO();
}
public void setWidth(int width) {
this.width = width;
createFBO();
}
public void setHeight(int height) {
this.height = height;
createFBO();
}
@Override
public int getWidth() {
return width;
}
@Override
public int getHeight() {
return height;
}
public void updateContent() {
contentH = content.size() * 12 + 4;
int h2 = height - 2;
if(contentH <= h2) {
scrollSize = h2;
scrollPos = 0;
} else {
scrollSize = h2 * h2 / contentH;
if(scrollSize < 4)
scrollSize = 4;
}
update = true;
}
public int addElement(String str) {
return addElement(str, null);
}
public int addElement(String str, Object ud) {
content.add(new Entry(str, ud));
updateContent();
return content.size() - 1;
}
public int addElementRaw(String str) {
return addElement(str, null);
}
public int addElementRaw(String str, Object ud) {
content.add(new Entry(str, ud));
return content.size() - 1;
}
@Override
public void setDisabled(boolean dis) {
disabled = dis;
if(dis) {
selected = -1;
update = true;
}
}
@Override
public void disable() {
disabled = true;
selected = -1;
update = true;
}
@Override
public boolean mouseMove(double mouseX, double mouseY) {
int sel = -1;
if(!disabled && mouseX >= x + 1 && mouseX <= x + width - 6 && mouseY >= y + 2 && mouseY <= y + height - 2) {
int offset = y + 4 - getYOffset();
sel = (int) ((mouseY - offset) / 12);
if(sel < 0 || sel >= content.size())
sel = -1;
}
if(selected != sel) {
selected = sel;
update = true;
return true;
}
return false;
}
@Override
public boolean mouseClicked(double mouseX, double mouseY, int mouseButton) {
if(!disabled && mouseButton == 0) {
if(isInScrollbar(mouseX, mouseY)) {
scrolling = true;
scrollGrab = mouseY - (y + 1 + scrollPos);
return true;
} else if(selected >= 0) {
System.out.println(parent.actionPerformed(new EntryClick(this)));
return true;
}
return false;
}
return false;
}
@Override
public boolean mouseReleased(double mouseX, double mouseY, int state) {
if(!disabled && scrolling) {
scrolling = false;
return true;
}
return false;
}
@Override
public boolean mouseScroll(double mouseX, double mouseY, double amount) {
if(!disabled && !scrolling && mouseX >= x && mouseX <= x + width && mouseY >= y && mouseY <= y + height) {
double disp = 12.d * ((double) (height - 2 - scrollSize)) / ((double) (contentH - height));
double sp = scrollPos;
if(amount < 0)
sp += (int) disp;
else
sp -= (int) disp;
if(sp < 0)
sp = 0;
else if(sp > height - 2 - scrollSize)
sp = height - 2 - scrollSize;
if(sp != scrollPos) {
scrollPos = sp;
update = true;
}
return true;
}
return false;
}
@Override
public boolean mouseClickMove(double mouseX, double mouseY, int button, double dragX, double dragY) {
if(!disabled && scrolling) {
double sp = mouseY - scrollGrab - y - 1;
if(sp < 0)
sp = 0;
else if(sp > height - 2 - scrollSize)
sp = height - 2 - scrollSize;
if(scrollPos != sp) {
scrollPos = sp;
update = true;
}
return true;
}
return false;
}
@Override
public void draw(GuiGraphics graphics, int mouseX, int mouseY, float ptt) {
if(visible) {
if(update) {
renderToFBO(graphics.bufferSource());
update = false;
}
RenderSystem.setShaderTexture(0, fbo.getColorTextureId());
RenderSystem.setShaderColor(1.f, 1.f, 1.f, 1.f);
fillTexturedRect(graphics.pose(), x, y, width, height, 0.0, 1.0, 1.0, 0.0);
fillRect(graphics.bufferSource(), x + width - 5, y + 1 + scrollPos, 4, scrollSize, (scrolling || isInScrollbar(mouseX, mouseY)) ? 0xFF202020 : 0xFF404040);
}
}
public String getEntryLabel(int id) {
return content.get(id).text;
}
public Object getEntryUserdata(int id) {
return content.get(id).userdata;
}
public int findEntryByLabel(String label) {
for(int i = 0; i < content.size(); i++) {
if(content.get(i).text.equals(label))
return i;
}
return -1;
}
public int findEntryByUserdata(Object o) {
if(o == null) {
for(int i = 0; i < content.size(); i++) {
if(content.get(i).userdata == null)
return i;
}
} else {
for(int i = 0; i < content.size(); i++) {
if(content.get(i).userdata != null && content.get(i).userdata.equals(o))
return i;
}
}
return -1;
}
public void setSelectionColor(int selColor) {
this.selColor = selColor;
}
public int getSelectionColor() {
return selColor;
}
public int getElementCount() {
return content.size();
}
public void removeElement(int id) {
if(selected != -1 && id == content.size() - 1)
selected = -1;
content.remove(id);
updateContent();
}
public void removeElementRaw(int id) {
if(selected != -1 && id == content.size() - 1)
selected = -1;
content.remove(id);
}
public void clear() {
content.clear();
scrollPos = 0;
scrolling = false;
scrollSize = height - 2;
selected = -1;
update = true;
}
public void clearRaw() {
content.clear();
scrollPos = 0;
scrolling = false;
selected = -1;
}
@Override
public void load(JsonOWrapper json) {
super.load(json);
width = json.getInt("width", 100);
height = json.getInt("height", 100);
selColor = json.getColor("selectionColor", 0xFF0080FF);
createFBO();
}
}

View File

@@ -0,0 +1,373 @@
/*
* Copyright (C) 2018 BARBOTIN Nicolas
*/
package net.montoyo.wd.client.gui.controls;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.EditBox;
import net.minecraft.network.chat.Component;
import net.montoyo.wd.client.gui.loading.JsonOWrapper;
import org.cef.browser.CefBrowserOsr;
import org.lwjgl.glfw.GLFW;
import java.util.ArrayList;
import static java.awt.event.KeyEvent.VK_ENTER;
import static org.lwjgl.glfw.GLFW.*;
public class TextField extends Control {
public static class EnterPressedEvent extends Event<TextField> {
private final String text;
public EnterPressedEvent(TextField field) {
source = field;
text = field.field.getValue();
}
public String getText() {
return text;
}
}
public static class TabPressedEvent extends Event<TextField> {
private final String beginning;
public TabPressedEvent(TextField field) {
source = field;
String text = field.field.getValue();
int max = field.field.getCursorPosition();
int spacePos = 0;
for(int i = max - 1; i >= 0; i--) {
if(Character.isSpaceChar(text.charAt(i))) {
spacePos = i;
break;
}
}
beginning = text.substring(spacePos, max).trim();
}
public String getBeginning() {
return beginning;
}
}
public static class TextChangedEvent extends Event<TextField> {
private final String oldContent;
private final String newContent;
public TextChangedEvent(TextField tf, String old) {
source = tf;
oldContent = old;
newContent = tf.field.getValue();
}
public String getOldContent() {
return oldContent;
}
public String getNewContent() {
return newContent;
}
}
public interface TextChangeListener {
void onTextChange(TextField tf, String oldContent, String newContent);
}
public static final int DEFAULT_TEXT_COLOR = 14737632;
public static final int DEFAULT_DISABLED_COLOR = 7368816;
private final EditBox field;
private boolean enabled = true;
private int textColor = DEFAULT_TEXT_COLOR;
private int disabledColor = DEFAULT_DISABLED_COLOR;
private final ArrayList<TextChangeListener> listeners = new ArrayList<>();
public TextField() {
field = new EditBox(font, 1, 1, 198, 20, Component.nullToEmpty(""));
setFocused(false);
}
public TextField(int x, int y, int width, int height) {
field = new EditBox(font, x + 1, y + 1, width - 2, height - 2, Component.nullToEmpty(""));
setFocused(false);
}
public TextField(int x, int y, int width, int height, String text) {
field = new EditBox(font, x + 1, y + 1, width - 2, height - 2, Component.nullToEmpty(""));
field.setValue(text);
setFocused(false);
}
// TODO: make this public static in CefBrowserOSR
private long mapScanCode(int key, char c) {
if (key == GLFW_KEY_LEFT_CONTROL || key == GLFW_KEY_RIGHT_CONTROL) return 29;
return switch (key) {
case GLFW_KEY_DELETE -> 83;
case GLFW_KEY_LEFT -> 75;
case GLFW_KEY_DOWN -> 80;
case GLFW_KEY_UP -> 72;
case GLFW_KEY_RIGHT -> 77;
case GLFW_KEY_PAGE_DOWN -> 81;
case GLFW_KEY_PAGE_UP -> 73;
case GLFW_KEY_END -> 79;
case GLFW_KEY_HOME -> 71;
case VK_ENTER, GLFW_KEY_ENTER, GLFW_KEY_KP_ENTER -> 28;
default -> GLFW.glfwGetKeyScancode(key);
};
}
@Override
public boolean keyDown(int key, int scanCode, int modifiers) {
return field.keyPressed(key, scanCode, modifiers);
}
@Override
public boolean keyUp(int key, int scanCode, int modifiers) {
return field.keyReleased(key, scanCode, modifiers);
}
@Override
public boolean keyTyped(int keyCode, int modifier) {
if(keyCode == GLFW.GLFW_KEY_ENTER || keyCode == GLFW.GLFW_KEY_KP_ENTER)
parent.actionPerformed(new EnterPressedEvent(this));
else if(keyCode == GLFW.GLFW_KEY_TAB)
parent.actionPerformed(new TabPressedEvent(this));
else {
String old;
if(enabled && field.isFocused())
old = field.getValue();
else
old = null;
if(enabled && field.isFocused() && !field.getValue().equals(old)) {
for(TextChangeListener tcl : listeners)
tcl.onTextChange(this, old, field.getValue());
parent.actionPerformed(new TextChangedEvent(this, old));
}
return field.charTyped((char) keyCode, modifier);
}
return false;
}
@Override
public boolean mouseClicked(double mouseX, double mouseY, int mouseButton) {
if (field.mouseClicked(mouseX, mouseY, mouseButton)) {
setFocused(true);
return true;
}
return false;
}
@Override
public void unfocus() {
setFocused(false);
}
@Override
public boolean mouseReleased(double mouseX, double mouseY, int state) {
return field.mouseReleased(mouseX, mouseY, state);
}
@Override
public boolean mouseClickMove(double mouseX, double mouseY, int button, double dragX, double dragY) {
return field.mouseClicked(mouseX, mouseY, 0);
}
@Override
public boolean mouseMove(double mouseX, double mouseY) {
field.mouseMoved(mouseX, mouseY);
return true;
}
@Override
public boolean mouseScroll(double mouseX, double mouseY, double amount) {
return field.mouseScrolled(mouseX, mouseY, 0.0, amount);
}
@Override
public void draw(GuiGraphics poseStack, int mouseX, int mouseY, float ptt) {
field.render(poseStack, mouseX, mouseY, ptt);
}
public void setText(String text) {
String old = field.getValue();
field.setValue(text);
if(!old.equals(text)) {
for(TextChangeListener tcl : listeners)
tcl.onTextChange(this, old, text);
}
}
public void clear() {
field.setValue("");
}
public String getText() {
return field.getValue();
}
public String getSelectedText() {
return field.getHighlighted();
}
public void setWidth(int width) {
field.setWidth(width - 2);
}
@Override
public int getWidth() {
return field.getWidth() + 2;
}
public void setHeight(int height) {
field.setHeight(height - 2);
}
@Override
public int getHeight() {
return field.getHeight() + 2;
}
public void setSize(int w, int h) {
field.setWidth(w - 2);
field.setHeight(h - 2);
}
@Override
public void setPos(int x, int y) {
field.setPosition(
x + 1,
y + 1
);
}
@Override
public int getX() {
return field.getX() - 1;
}
@Override
public int getY() {
return field.getY() - 1;
}
public void setDisabled(boolean en) {
enabled = !en;
if (!en)
field.setFocused(false);
}
public boolean isDisabled() {
return !enabled;
}
public void enable() {
enabled = true;
}
public void disable() {
field.setFocused(false);
enabled = false;
}
public void setVisible(boolean vi) {
field.setVisible(vi);
}
public boolean isVisible() {
return field.isVisible();
}
public void show() {
field.setVisible(true);
}
public void hide() {
field.setVisible(false);
}
public void setFocused(boolean val) {
field.setFocused(val);
}
public boolean hasFocus() {
return field.isFocused();
}
public void focus() {
field.setFocused(true);
}
public void setTextColor(int color) {
field.setTextColor(color);
textColor = color;
}
public int getTextColor() {
return textColor;
}
public void setDisabledTextColor(int color) {
field.setTextColorUneditable(color);
disabledColor = color;
}
public int getDisabledTextColor() {
return disabledColor;
}
public EditBox getMcField() {
return field;
}
public void addTextChangeListener(TextChangeListener l) {
if(l != null && !listeners.contains(l))
listeners.add(l);
}
public void removeTextChangeListener(TextChangeListener l) {
listeners.remove(l);
}
@Override
public void load(JsonOWrapper json) {
super.load(json);
field.setPosition(
json.getInt("x", 0) + 1,
json.getInt("y", 0) + 1
);
field.setWidth(json.getInt("width", 200) - 2);
field.setHeight(json.getInt("height", 22) - 2);
field.setValue(tr(json.getString("text", "")));
field.setVisible(json.getBool("visible", true));
field.setMaxLength(json.getInt("maxLength", 32));
enabled = !json.getBool("disabled", false);
textColor = json.getColor("textColor", DEFAULT_TEXT_COLOR);
disabledColor = json.getColor("disabledColor", DEFAULT_DISABLED_COLOR);
field.setTextColor(textColor);
field.setTextColorUneditable(disabledColor);
setFocused(false);
}
}

View File

@@ -0,0 +1,154 @@
/*
* Copyright (C) 2018 BARBOTIN Nicolas
*/
package net.montoyo.wd.client.gui.controls;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.entity.ItemRenderer;
import net.minecraft.world.item.ItemStack;
import net.montoyo.wd.client.gui.loading.JsonOWrapper;
import java.util.ArrayList;
public class UpgradeGroup extends BasicControl {
private int width;
private int height;
private ArrayList<ItemStack> upgrades;
private ItemStack overStack;
private ItemStack clickStack;
private final ItemRenderer renderItem = Minecraft.getInstance().getItemRenderer();
public UpgradeGroup() {
parent.requirePostDraw(this);
}
@Override
public void draw(GuiGraphics poseStack, int mouseX, int mouseY, float ptt) {
if(upgrades != null) {
int x = this.x;
for(ItemStack is: upgrades) {
if(is == overStack && !disabled)
fillRect(poseStack.bufferSource(), x, y, 16, 16, 0x80FF0000);
poseStack.renderItem(is, x, y);
poseStack.renderItemDecorations(font, is, x, y);
x += 18;
}
}
}
@Override
public void postDraw(GuiGraphics poseStack, int mouseX, int mouseY, float ptt) {
if(overStack != null)
parent.drawItemStackTooltip(poseStack, overStack, mouseX, mouseY);
}
@Override
public int getWidth() {
return width;
}
@Override
public int getHeight() {
return height;
}
public void setWidth(int w) {
width = w;
}
public void setHeight(int h) {
height = h;
}
public void setUpgrades(ArrayList<ItemStack> upgrades) {
this.upgrades = upgrades;
}
public ArrayList<ItemStack> getUpgrades() {
return upgrades;
}
@Override
public void load(JsonOWrapper json) {
super.load(json);
width = json.getInt("width", 0);
height = json.getInt("height", 16);
}
@Override
public boolean mouseMove(double mouseX, double mouseY) {
if(upgrades != null) {
overStack = null;
if(mouseY >= y && mouseY <= y + 16 && mouseX >= x) {
mouseX -= x;
int sel = (int) (mouseX / 18);
if(sel < upgrades.size() && mouseX % 18 <= 16)
overStack = upgrades.get(sel);
return true;
}
}
return false;
}
@Override
public boolean mouseClicked(double mouseX, double mouseY, int mouseButton) {
if(mouseButton == 0) {
// don't process the click if it's not inbounds, lol
if (
mouseX >= x && mouseX <= x + width &&
mouseY >= y && mouseX <= y + height
) {
clickStack = overStack;
return true;
}
}
return false;
}
@Override
public boolean mouseReleased(double mouseX, double mouseY, int state) {
if(state == 0 && clickStack != null) {
if(clickStack == overStack && !disabled && upgrades.contains(clickStack)) //HOTFIX: Make sure it's actually in the list :p
parent.actionPerformed(new ClickEvent(this));
clickStack = null;
return true;
}
return false;
}
public ItemStack getMouseOverUpgrade() {
return overStack;
}
public static class ClickEvent extends Event<UpgradeGroup> {
private final ItemStack clickStack;
public ClickEvent(UpgradeGroup src) {
source = src;
clickStack = src.clickStack;
}
public ItemStack getMouseOverStack() {
return clickStack;
}
}
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright (C) 2018 BARBOTIN Nicolas
*/
package net.montoyo.wd.client.gui.controls;
import net.minecraft.network.chat.Component;
import net.montoyo.wd.client.gui.loading.JsonOWrapper;
import net.montoyo.wd.utilities.serialization.Util;
import net.montoyo.wd.utilities.VideoType;
import java.net.MalformedURLException;
import java.net.URL;
public class YTButton extends Button implements TextField.TextChangeListener {
private TextField urlField;
public YTButton() {
btn.setMessage(Component.nullToEmpty("YT"));
btn.active = false;
shiftColor = 0xFFFF6464;
}
@Override
protected boolean onClick() {
if(urlField != null) {
String urlStr = Util.addProtocol(urlField.getText());
URL url;
try {
url = new URL(urlStr);
} catch(MalformedURLException ex) {
return true;
}
VideoType vt = VideoType.getTypeFromURL(url);
if(vt == VideoType.YOUTUBE)
urlField.setText(VideoType.YOUTUBE_EMBED.getURLFromID(vt.getVideoIDFromURL(url), shiftDown));
}
return true;
}
public void setURLField(TextField tf) {
if(urlField != null)
tf.removeTextChangeListener(this);
urlField = tf;
if(urlField != null)
tf.addTextChangeListener(this);
}
public TextField getURLField() {
return urlField;
}
@Override
public void load(JsonOWrapper json) {
super.load(json);
String tfName = json.getString("urlField", null);
if(tfName != null) {
Control ctrl = parent.getControlByName(tfName);
if(ctrl != null && ctrl instanceof TextField) {
urlField = (TextField) ctrl;
urlField.addTextChangeListener(this);
}
}
}
@Override
public void onTextChange(TextField tf, String oldContent, String newContent) {
btn.active = (VideoType.getTypeFromURL(Util.addProtocol(newContent)) == VideoType.YOUTUBE);
}
}

View File

@@ -0,0 +1,19 @@
/*
* Copyright (C) 2018 BARBOTIN Nicolas
*/
package net.montoyo.wd.client.gui.loading;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FillControl {
String name() default "";
boolean required() default true;
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright (C) 2018 BARBOTIN Nicolas
*/
package net.montoyo.wd.client.gui.loading;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import net.minecraft.client.Minecraft;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.Resource;
import net.montoyo.wd.client.gui.controls.*;
import net.montoyo.wd.utilities.Log;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Modifier;
import java.util.HashMap;
public class GuiLoader {
private static final HashMap<String, Class<? extends Control>> CONTROLS = new HashMap<>();
private static final HashMap<ResourceLocation, JsonObject> RESOURCES = new HashMap<>();
public static void register(Class<? extends Control> cls) {
if(Modifier.isAbstract(cls.getModifiers()))
throw new RuntimeException("GG retard, you just registered an abstract class...");
String name = cls.getSimpleName();
if(CONTROLS.containsKey(name))
throw new RuntimeException("Control class already registered or name taken!");
CONTROLS.put(name, cls);
}
static {
register(Button.class);
register(CheckBox.class);
register(ControlGroup.class);
register(Label.class);
register(List.class);
register(TextField.class);
register(Icon.class);
register(UpgradeGroup.class);
register(YTButton.class);
}
public static Control create(JsonOWrapper json) {
Control ret;
try {
ret = CONTROLS.get(json.getString("type", null)).newInstance();
} catch(InstantiationException e) {
Log.errorEx("Could not create control from JSON: instantiation exception", e);
throw new RuntimeException(e);
} catch(IllegalAccessException e) {
Log.errorEx("Could not create control from JSON: access denied", e);
throw new RuntimeException(e);
}
ret.load(json);
return ret;
}
public static JsonObject getJson(ResourceLocation resLoc) throws IOException {
JsonObject ret = RESOURCES.get(resLoc);
if(ret == null) {
Resource resource;
resource = Minecraft.getInstance().getResourceManager().getResource(resLoc).get();
JsonParser parser = new JsonParser();
ret = parser.parse(new InputStreamReader(resource.open())).getAsJsonObject();
RESOURCES.put(resLoc, ret);
}
return ret;
}
public static void clearCache() {
RESOURCES.clear();
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright (C) 2018 BARBOTIN Nicolas
*/
package net.montoyo.wd.client.gui.loading;
import com.google.gson.JsonArray;
import java.util.Map;
public class JsonAWrapper {
private final JsonArray array;
private final Map<String, Double> variables;
public JsonAWrapper(JsonArray a, Map<String, Double> vars) {
array = a;
variables = vars;
}
public int size() {
return array.size();
}
public String getString(int i) {
return array.get(i).getAsString();
}
public int getInt(int i) {
return array.get(i).getAsInt();
}
public long getLong(int i) {
return array.get(i).getAsLong();
}
public float getFloat(int i) {
return array.get(i).getAsFloat();
}
public double getDouble(int i) {
return array.get(i).getAsDouble();
}
public boolean getBool(int i) {
return array.get(i).getAsBoolean();
}
public JsonOWrapper getObject(int i) {
return new JsonOWrapper(array.get(i).getAsJsonObject(), variables);
}
public JsonAWrapper getArray(int i) {
return new JsonAWrapper(array.get(i).getAsJsonArray(), variables);
}
public JsonArray getArray() {
return array;
}
}

View File

@@ -0,0 +1,311 @@
/*
* Copyright (C) 2018 BARBOTIN Nicolas
*/
package net.montoyo.wd.client.gui.loading;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import net.montoyo.wd.client.gui.controls.Control;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class JsonOWrapper {
private static final HashMap<String, Integer> defaultColors = new HashMap<>();
static {
defaultColors.put("black", Control.COLOR_BLACK);
defaultColors.put("white", Control.COLOR_WHITE);
defaultColors.put("red", Control.COLOR_RED);
defaultColors.put("green", Control.COLOR_GREEN);
defaultColors.put("blue", Control.COLOR_BLUE);
defaultColors.put("magenta", Control.COLOR_MANGENTA);
defaultColors.put("cyan", Control.COLOR_CYAN);
defaultColors.put("yellow", Control.COLOR_YELLOW);
}
private final JsonObject object;
private final Map<String, Double> variables;
public JsonOWrapper(JsonObject obj, Map<String, Double> vars) {
object = obj;
variables = vars;
}
public String getString(String key, String def) {
return object.has(key) ? object.get(key).getAsString() : def;
}
public long getLong(String key, long def) {
return object.has(key) ? object.get(key).getAsLong() : def;
}
public int getInt(String key, int def) {
if(!object.has(key))
return def;
JsonPrimitive prim = object.get(key).getAsJsonPrimitive();
if(prim.isNumber())
return prim.getAsInt();
return (int) evalExpr(prim.getAsString(), variables);
}
public float getFloat(String key, float def) {
if(!object.has(key))
return def;
JsonPrimitive prim = object.get(key).getAsJsonPrimitive();
if(prim.isNumber())
return prim.getAsFloat();
return (float) evalExpr(prim.getAsString(), variables);
}
public double getDouble(String key, double def) {
if(!object.has(key))
return def;
JsonPrimitive prim = object.get(key).getAsJsonPrimitive();
if(prim.isNumber())
return prim.getAsDouble();
return evalExpr(prim.getAsString(), variables);
}
public boolean getBool(String key, boolean def) {
if(!object.has(key))
return def;
JsonPrimitive prim = object.get(key).getAsJsonPrimitive();
if(prim.isBoolean())
return prim.getAsBoolean();
else if(prim.isNumber())
return prim.getAsInt() != 0;
return evalExpr(prim.getAsString(), variables) != 0.0;
}
public JsonOWrapper getObject(String key) {
return new JsonOWrapper(object.has(key) ? object.get(key).getAsJsonObject() : (new JsonObject()), variables);
}
public JsonAWrapper getArray(String key) {
return new JsonAWrapper(object.has(key) ? object.get(key).getAsJsonArray() : (new JsonArray()), variables);
}
public JsonObject getObject() {
return object;
}
public int getColor(String key, int def) {
if(!object.has(key))
return def;
JsonElement c = object.get(key);
if(c.isJsonPrimitive()) {
JsonPrimitive prim = c.getAsJsonPrimitive();
if(prim.isNumber())
return (int) prim.getAsLong();
else if(prim.isString()) {
String str = prim.getAsString();
Integer dc = defaultColors.get(str.toLowerCase());
if(dc != null)
return dc;
if(!str.isEmpty() && str.charAt(0) == '#')
str = str.substring(1);
long ret = Long.parseLong(str, 16);
if(str.length() <= 6)
ret |= 0xFF000000L;
return (int) ret;
} else
return def;
}
int r, g, b, a;
if(c.isJsonArray()) {
JsonArray array = c.getAsJsonArray();
r = array.get(0).getAsInt();
g = array.get(1).getAsInt();
b = array.get(2).getAsInt();
a = (array.size() >= 4) ? array.get(3).getAsInt() : 255;
} else if(c.isJsonObject()) {
JsonObject obj = c.getAsJsonObject();
r = obj.get("r").getAsInt();
g = obj.get("g").getAsInt();
b = obj.get("b").getAsInt();
a = obj.has("a") ? obj.get("a").getAsInt() : 255;
} else
return def;
return (a << 24) | (r << 16) | (g << 8) | b;
}
private static final String OPS = "+*/%&|"; //The - sign is an exception, don't add it here
private static final String[] OPS_PRIORITY = new String[] { "*/%", "+-", "&|" };
private static class VarOpPair {
double var;
char op;
void setVar(String str, boolean isNumber, String expr, Map<String, Double> variables) {
if(isNumber)
var = Double.parseDouble(str);
else {
boolean neg = (str.charAt(0) == '-');
String varName = neg ? str.substring(1) : str;
Double d = variables.get(varName);
if(d == null)
throw new RuntimeException("Unknown variable \"" + varName + "\" in expression \"" + expr + "\"");
var = neg ? -d : d;
}
}
void setOp(char op) {
this.op = op;
}
}
private static int findPair(List<VarOpPair> list, String ops) {
for(int i = 0; i < list.size(); i++) {
if(ops.indexOf(list.get(i).op) >= 0)
return i;
}
return -1;
}
private static double evalExpr(String expr, Map<String, Double> variables) {
//Apply parenthesis
while(true) {
int pos = expr.indexOf('(');
if(pos < 0)
break;
int end = ++pos;
int lvl = 0;
for(; end < expr.length(); end++) {
char chr = expr.charAt(end);
if(chr == '(')
lvl++;
else if(chr == ')') {
if(lvl == 0)
break;
lvl--;
}
}
if(end >= expr.length())
throw new RuntimeException("Unclosed parenthesis in expression \"" + expr + "\"");
double val = evalExpr(expr.substring(pos, end), variables);
expr = expr.substring(0, pos - 1) + val + expr.substring(end + 1);
}
//Parse into ops
ArrayList<VarOpPair> ops = new ArrayList<>();
StringBuilder str = new StringBuilder();
boolean negIsPartOfStr = true;
boolean strIsNumber = true;
for(int i = 0; i < expr.length(); i++) {
char chr = expr.charAt(i);
if(Character.isSpaceChar(chr))
continue;
if((chr == '-' && !negIsPartOfStr) || OPS.indexOf(chr) >= 0) {
//Parse
VarOpPair pair = new VarOpPair();
pair.setVar(str.toString(), strIsNumber, expr, variables);
pair.setOp(chr);
ops.add(pair);
//Reset
str.setLength(0);
negIsPartOfStr = true;
strIsNumber = true;
} else {
if(strIsNumber && chr != '-' && chr != '.' && !Character.isDigit(chr))
strIsNumber = false;
if(negIsPartOfStr)
negIsPartOfStr = false;
str.append(chr);
}
}
if(str.length() > 0) {
VarOpPair pair = new VarOpPair();
pair.setVar(str.toString(), strIsNumber, expr, variables);
pair.setOp((char) 0);
ops.add(pair);
}
//Compute
while(true) {
int pairId = -1;
for(String opList : OPS_PRIORITY) {
pairId = findPair(ops, opList);
if(pairId >= 0)
break;
}
if(pairId < 0)
break;
VarOpPair a = ops.get(pairId);
VarOpPair b = ops.get(pairId + 1);
if(a.op == '*')
b.var = a.var * b.var;
else if(a.op == '/')
b.var = a.var / b.var;
else if(a.op == '%')
b.var = a.var % b.var;
else if(a.op == '+')
b.var = a.var + b.var;
else if(a.op == '-')
b.var = a.var - b.var;
else if(a.op == '&') {
if(a.var == 0.0)
b.var = 0.0;
//if b.var == 0, b.var stays 0
//if a.var != 0, b.var keeps its value
} else if(a.op == '|') {
if(a.var != 0.0)
b.var = a.var;
//if a.var == 0, b.var keeps its value
//if a.var != 0, b.var takes the value of a
}
ops.remove(pairId);
}
//Check
if(ops.size() != 1 || ops.get(0).op != (char) 0)
throw new RuntimeException("Error while parsing evaluating \"" + expr + "\"");
return ops.get(0).var;
}
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright (C) 2018 BARBOTIN Nicolas
*/
package net.montoyo.wd.client.renderers;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.world.item.ItemStack;
public interface IItemRenderer {
/**
* @param pose the pose stack
* @param stack the item stack
* @param handSideSign TODO:
* @param swingProgress TODO:
* @param equipProgress TODO:
* @param multiBufferSource the buffer source
* @param packedLight packed light
* @return whether or not to cancel vanilla rendering
*/
boolean render(PoseStack pose, ItemStack stack, float handSideSign, float swingProgress, float equipProgress, MultiBufferSource multiBufferSource, int packedLight);
}

View File

@@ -0,0 +1,129 @@
/*
* Copyright (C) 2018 BARBOTIN Nicolas
*/
package net.montoyo.wd.client.renderers;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.*;
import com.mojang.blaze3d.vertex.BufferUploader;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.phys.HitResult;
import net.minecraft.world.phys.Vec3;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.api.distmarker.OnlyIn;
import net.montoyo.wd.client.ClientProxy;
import net.montoyo.wd.registry.ItemRegistry;
import net.montoyo.wd.item.ItemLaserPointer;
import org.joml.Matrix4f;
import static com.mojang.math.Axis.*;
@OnlyIn(Dist.CLIENT)
public final class LaserPointerRenderer implements IItemRenderer {
public LaserPointerRenderer() {
}
public static boolean isOn() {
if (Minecraft.getInstance().screen != null) return false;
Minecraft mc = Minecraft.getInstance();
return mc.player != null && mc.level != null &&
(
ClientProxy.mouseOn ||
ItemLaserPointer.isOn()
) &&
mc.player.getItemInHand(InteractionHand.MAIN_HAND).getItem().equals(ItemRegistry.LASER_POINTER.get()) &&
(mc.hitResult == null || mc.hitResult.getType() == HitResult.Type.BLOCK || mc.hitResult.getType() == HitResult.Type.MISS);
}
@Override
public boolean render(PoseStack poseStack, ItemStack is, float handSideSign, float swingProgress, float equipProgress, MultiBufferSource multiBufferSource, int packedLight) {
RenderSystem.disableCull();
// RenderSystem.disableTexture();
RenderSystem.enableDepthTest();
RenderSystem.enableBlend();
float PI = (float) Math.PI;
float sqrtSwingProg = (float) Math.sqrt(swingProgress);
float sinSqrtSwingProg1 = (float) Math.sin(sqrtSwingProg * PI);
RenderSystem.setShader(GameRenderer::getPositionColorShader);
var matrix0 = poseStack.last().pose();
//Laser pointer
poseStack.pushPose();
poseStack.translate(handSideSign * -0.4f * sinSqrtSwingProg1, (float) (0.2f * Math.sin(sqrtSwingProg * PI * 2.0f)), (float) (-0.2f * Math.sin(swingProgress * PI)));
poseStack.translate(handSideSign * 0.56f, -0.52f - equipProgress * 0.6f, -0.72f);
poseStack.mulPose(YP.rotationDegrees((float) (handSideSign * (45.0f - Math.sin(swingProgress * swingProgress * PI) * 20.0f))));
poseStack.mulPose(ZP.rotationDegrees(handSideSign * sinSqrtSwingProg1 * -20.0f));
poseStack.mulPose(XP.rotationDegrees(sinSqrtSwingProg1 * -80.0f));
poseStack.mulPose(YP.rotationDegrees(handSideSign * -30.0f));
poseStack.translate(0.0f, 0.2f, 0.0f);
poseStack.mulPose(XP.rotationDegrees(10.0f));
poseStack.scale(1.0f / 16.0f, 1.0f / 16.0f, 1.0f / 16.0f);
var matrix = poseStack.last().pose();
BufferBuilder bb = Tesselator.getInstance().begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR);
bb.addVertex(matrix, 0.0f, 0.0f, 0.0f).setColor(0.5f, 0.5f, 0.5f, 1.0f);
bb.addVertex(matrix, 1.0f, 0.0f, 0.0f).setColor(0.5f, 0.5f, 0.5f, 1.0f);
bb.addVertex(matrix, 1.0f, 0.0f, 4.0f).setColor(0.5f, 0.5f, 0.5f, 1.0f);
bb.addVertex(matrix, 0.0f, 0.0f, 4.0f).setColor(0.5f, 0.5f, 0.5f, 1.0f);
bb.addVertex(matrix, 0.0f, 0.0f, 0.0f).setColor(0.5f, 0.5f, 0.5f, 1.0f);
bb.addVertex(matrix, 0.0f, -1.0f, 0.0f).setColor(0.5f, 0.5f, 0.5f, 1.0f);
bb.addVertex(matrix, 0.0f, -1.0f, 4.0f).setColor(0.5f, 0.5f, 0.5f, 1.0f);
bb.addVertex(matrix, 0.0f, 0.0f, 4.0f).setColor(0.5f, 0.5f, 0.5f, 1.0f);
bb.addVertex(matrix, 1.0f, 0.0f, 0.0f).setColor(0.5f, 0.5f, 0.5f, 1.0f);
bb.addVertex(matrix, 1.0f, -1.0f, 0.0f).setColor(0.5f, 0.5f, 0.5f, 1.0f);
bb.addVertex(matrix, 1.0f, -1.0f, 4.0f).setColor(0.5f, 0.5f, 0.5f, 1.0f);
bb.addVertex(matrix, 1.0f, 0.0f, 4.0f).setColor(0.5f, 0.5f, 0.5f, 1.0f);
bb.addVertex(matrix, 0.0f, -1.0f, 4.0f).setColor(0.5f, 0.5f, 0.5f, 1.0f);
bb.addVertex(matrix, 1.0f, -1.0f, 4.0f).setColor(0.5f, 0.5f, 0.5f, 1.0f);
bb.addVertex(matrix, 1.0f, 0.0f, 4.0f).setColor(0.5f, 0.5f, 0.5f, 1.0f);
bb.addVertex(matrix, 0.0f, 0.0f, 4.0f).setColor(0.5f, 0.5f, 0.5f, 1.0f);
if (isOn()) drawLineBetween(bb, matrix0, matrix, new Vec3(0.5f, -0.5f, 0.5f), new Vec3(-40.0f, 4000.5f, -100.0f));
BufferUploader.drawWithShader(bb.buildOrThrow());
RenderSystem.disableBlend();
RenderSystem.disableDepthTest();
// RenderSystem.enableTexture(); //Fix for shitty minecraft fire
RenderSystem.enableCull();
poseStack.popPose();
return true;
}
private static void drawLineBetween(BufferBuilder bb, Matrix4f matrix0, Matrix4f matrix, Vec3 local, Vec3 target) {
//Calculate distance between points -> length of the line
float distance = (float) local.distanceTo(target) / 2;
float quarterWidth = 0.25f;
float biggerWidth = 10;
bb.addVertex(matrix, 0.25f, -0.25f, 0.5f).setColor(0.5f, 0.0f, 0.0f, 1.0f);
bb.addVertex(matrix, quarterWidth + 0.25f, -0.25f, 0.5f).setColor(0.5f, 0.0f, 0.0f, 1.0f);
bb.addVertex(matrix0, biggerWidth - 6f, 3f, -distance).setColor(0.5f, 0.0f, 0.0f, 1.0f);
bb.addVertex(matrix0, -6f, 3f, -distance).setColor(0.5f, 0.0f, 0.0f, 1.0f);
bb.addVertex(matrix, 0.25f, -0.25f, 0.5f).setColor(0.5f, 0.0f, 0.0f, 1.0f);
bb.addVertex(matrix, 0.25f, -quarterWidth - 0.25f, 0.5f).setColor(0.5f, 0.0f, 0.0f, 1.0f);
bb.addVertex(matrix0, -6f, -biggerWidth + 3f, -distance).setColor(0.5f, 0.0f, 0.0f, 1.0f);
bb.addVertex(matrix0, -6f, 3f, -distance).setColor(0.5f, 0.0f, 0.0f, 1.0f);
bb.addVertex(matrix, quarterWidth + 0.25f, -0.25f, 0.5f).setColor(0.5f, 0.0f, 0.0f, 1.0f);
bb.addVertex(matrix, quarterWidth + 0.25f, -quarterWidth - 0.25f, 0.5f).setColor(0.5f, 0.0f, 0.0f, 1.0f);
bb.addVertex(matrix0, biggerWidth - 6f, -biggerWidth + 3f, -distance).setColor(0.5f, 0.0f, 0.0f, 1.0f);
bb.addVertex(matrix0, biggerWidth - 6f, 3f, -distance).setColor(0.5f, 0.0f, 0.0f, 1.0f);
}
}

View File

@@ -0,0 +1,163 @@
/*
* Copyright (C) 2018 BARBOTIN Nicolas
*/
package net.montoyo.wd.client.renderers;
import com.cinemamod.mcef.MCEFBrowser;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.*;
import com.mojang.blaze3d.vertex.BufferUploader;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.entity.player.PlayerRenderer;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.entity.HumanoidArm;
import net.minecraft.world.item.ItemStack;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.api.distmarker.OnlyIn;
import net.montoyo.wd.WebDisplays;
import net.montoyo.wd.client.ClientProxy;
import net.montoyo.wd.data.WDDataComponents;
import net.montoyo.wd.config.ClientConfig;
import net.montoyo.wd.item.ItemMinePad2;
import static com.mojang.math.Axis.*;
@OnlyIn(Dist.CLIENT)
public final class MinePadRenderer implements IItemRenderer {
private static final float PI = (float) Math.PI;
private final Minecraft mc = Minecraft.getInstance();
private final ResourceLocation tex = ResourceLocation.fromNamespaceAndPath("webdisplays", "textures/item/model/minepad.png");
private final ModelMinePad model = new ModelMinePad();
private final ClientProxy clientProxy = (ClientProxy) WebDisplays.PROXY;
private float sinSqrtSwingProg1;
private float sinSqrtSwingProg2;
private float sinSwingProg1;
private float sinSwingProg2;
public static boolean renderAtSide(float handSideSign) {
float relSide = handSideSign;
if (Minecraft.getInstance().player.getMainArm() == HumanoidArm.LEFT) relSide *= -1;
// by default, the player holds the device off to the side
// if they are crouching, they hold it infront of them
// however, if they are holding two at once, then it once again should just be held off to the side
boolean sideHold = Minecraft.getInstance().player.isShiftKeyDown() != ClientConfig.sidePad;
if (
(relSide < 0 && Minecraft.getInstance().player.getItemInHand(InteractionHand.MAIN_HAND).getItem() instanceof ItemMinePad2) ||
(relSide > 0 && Minecraft.getInstance().player.getItemInHand(InteractionHand.OFF_HAND).getItem() instanceof ItemMinePad2)
) sideHold = true;
return sideHold;
}
@Override
public final boolean render(PoseStack stack, ItemStack is, float handSideSign, float swingProgress, float equipProgress, MultiBufferSource multiBufferSource, int packedLight) {
//Pre-compute values
float sqrtSwingProg = (float) Math.sqrt(swingProgress);
sinSqrtSwingProg1 = (float) Math.sin(sqrtSwingProg * PI);
sinSqrtSwingProg2 = (float) Math.sin(sqrtSwingProg * PI * 2.0f);
sinSwingProg1 = (float) Math.sin(swingProgress * PI);
sinSwingProg2 = (float) Math.sin(swingProgress * swingProgress * PI);
boolean sideHold = renderAtSide(handSideSign);
//Render arm
stack.pushPose();
renderArmFirstPerson(stack, multiBufferSource, packedLight, equipProgress, handSideSign);
stack.popPose();
// if (!sideHold && handSideSign == 1 && mc.player.getItemInHand(InteractionHand.OFF_HAND).isEmpty()) {
// stack.pushPose();
// renderArmFirstPerson(stack, multiBufferSource, packedLight, 0, -handSideSign);
// stack.popPose();
// }
//Prepare minePad transform
stack.pushPose();
stack.translate(handSideSign * -0.4f * sinSqrtSwingProg1, 0.2f * sinSqrtSwingProg2, -0.2f * sinSwingProg1);
stack.translate(handSideSign * 0.56f, -0.52f - equipProgress * 0.6f, -0.72f);
stack.mulPose(YP.rotationDegrees(handSideSign * (45.0f - sinSwingProg2 * 20.0f)));
stack.mulPose(ZP.rotationDegrees(handSideSign * sinSqrtSwingProg1 * -20.0f));
stack.mulPose(XP.rotationDegrees(sinSqrtSwingProg1 * -80.0f));
stack.mulPose(YP.rotationDegrees(handSideSign * -45.0f));
if (sideHold) {
stack.translate(0.0f, 0.0f, -0.2f);
stack.mulPose(YP.rotationDegrees(20.0f * -handSideSign));
float total = 0.475f;
float off = -0.025f; // gotta love magic numbers
stack.translate(-(total - off) + (off * handSideSign), -0.1f, 0.0f);
stack.mulPose(ZP.rotationDegrees(1.0f));
} else if (handSideSign >= 0) // right hand
stack.translate(-1.065f, 0.0f, 0.0f);
else // left hand
stack.translate(0.065f, 0.0f, 0.0f);
//Render model
stack.translate(0.063f, 0.28f, 0.001f);
model.render(multiBufferSource, stack);
stack.translate(-0.063f, -0.28f, -0.001f);
// force draw so the browser can be drawn ontop of the model
multiBufferSource.getBuffer(RenderType.LINES);
if (is.has(WDDataComponents.PAD_ID.get())) {
ClientProxy.PadData pd = clientProxy.getPadByID(is.get(WDDataComponents.PAD_ID.get()));
//Render web view
if (pd != null) {
double x1 = 0.0;
double y1 = 0.0;
double x2 = 27.65 / 32.0 + 0.01;
double y2 = 14.0 / 32.0 + 0.002;
stack.translate(0.063f, 0.28f, 0.001f);
// RenderSystem.setShaderTexture(0, tex);
RenderSystem.disableDepthTest();
RenderSystem.setShader(GameRenderer::getPositionTexColorShader);
RenderSystem.setShaderTexture(0, ((MCEFBrowser) pd.view).getRenderer().getTextureID());
BufferBuilder buffer = Tesselator.getInstance().begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX_COLOR);
buffer.addVertex(stack.last().pose(), (float) x1, (float) y1, 0.0f).setUv(0.0F, 1.0F).setColor(1f, 1f, 1f, 1f);
buffer.addVertex(stack.last().pose(), (float) x2, (float) y1, 0.0f).setUv(1.0F, 1.0F).setColor(1f, 1f, 1f, 1f);
buffer.addVertex(stack.last().pose(), (float) x2, (float) y2, 0.0f).setUv(1.0F, 0.0F).setColor(1f, 1f, 1f, 1f);
buffer.addVertex(stack.last().pose(), (float) x1, (float) y2, 0.0f).setUv(0.0F, 0.0F).setColor(1f, 1f, 1f, 1f);
BufferUploader.drawWithShader(buffer.buildOrThrow());
RenderSystem.enableDepthTest();
}
}
stack.popPose();
RenderSystem.enableCull();
return true;
}
private void renderArmFirstPerson(PoseStack stack, MultiBufferSource buffer, int combinedLight, float equipProgress, float handSideSign) {
float tx = -0.3f * sinSqrtSwingProg1;
float ty = 0.4f * sinSqrtSwingProg2;
float tz = -0.4f * sinSwingProg1;
stack.translate(handSideSign * (tx + 0.64000005f), ty - 0.6f - equipProgress * 0.6f, tz - 0.71999997f);
stack.mulPose(YP.rotationDegrees(handSideSign * 45.0f));
stack.mulPose(YP.rotationDegrees(handSideSign * sinSqrtSwingProg1 * 70.0f));
stack.mulPose(ZP.rotationDegrees(handSideSign * sinSwingProg2 * -20.0f));
stack.translate(-handSideSign, 3.6f, 3.5f);
stack.mulPose(ZP.rotationDegrees(handSideSign * 120.0f));
stack.mulPose(XP.rotationDegrees(200.0f));
stack.mulPose(YP.rotationDegrees(handSideSign * -135.0f));
stack.translate(handSideSign * 5.6f, 0.0f, 0.0f);
PlayerRenderer playerRenderer = (PlayerRenderer) mc.getEntityRenderDispatcher().getRenderer(mc.player);
if (handSideSign >= 0.0f)
playerRenderer.renderRightHand(stack, buffer, combinedLight, mc.player);
else
playerRenderer.renderLeftHand(stack, buffer, combinedLight, mc.player);
}
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright (C) 2018 BARBOTIN Nicolas
*/
package net.montoyo.wd.client.renderers;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.*;
import com.mojang.blaze3d.vertex.BufferUploader;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.client.renderer.LightTexture;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.texture.OverlayTexture;
import net.minecraft.resources.ResourceLocation;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.api.distmarker.OnlyIn;
import org.joml.Matrix4f;
@OnlyIn(Dist.CLIENT)
public final class ModelMinePad {
public void render(MultiBufferSource buffers, PoseStack stack) {
// TODO: this needs completing
// TODO: I'd like this to be able to load a model from a JSON if possible
double x1 = 0.0;
double y1 = 0.0;
double x2 = 27.65 / 32.0 + 0.01;
double y2 = 14.0 / 32.0 + 0.002;
Matrix4f positionMatrix = stack.last().pose();
BufferBuilder vb = Tesselator.getInstance().begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_COLOR);
RenderSystem.setShader(GameRenderer::getPositionColorShader);
RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f);
vb.addVertex(positionMatrix, (float) x1, (float) y1, 0.0f).setColor(0f, 0f, 0f, 1f);
vb.addVertex(positionMatrix, (float) x2, (float) y1, 0.0f).setColor(0f, 0f, 0f, 1f);
vb.addVertex(positionMatrix, (float) x2, (float) y2, 0.0f).setColor(0f, 0f, 0f, 1f);
vb.addVertex(positionMatrix, (float) x1, (float) y2, 0.0f).setColor(0f, 0f, 0f, 1f);
BufferUploader.drawWithShader(vb.buildOrThrow());
int width = 32;
int height = 32;
float padding = 1f / 23;
float padding1 = 1f / 21;
float z = 0;
VertexConsumer consumer = buffers.getBuffer(RenderType.entityCutout(ResourceLocation.fromNamespaceAndPath("webdisplays", "textures/item/model/minepad_item.png")));
consumer.addVertex((float) x1, (float) y1 - padding, z).setColor(255, 255, 255, 255).setUv(1f / width, 12f / height).setOverlay(OverlayTexture.NO_OVERLAY).setLight(LightTexture.FULL_BRIGHT).setNormal(0.25f, 0.5f, 1);
consumer.addVertex((float) x2, (float) y1 - padding, z).setColor(255, 255, 255, 255).setUv(19f / width, 12f / height).setOverlay(OverlayTexture.NO_OVERLAY).setLight(LightTexture.FULL_BRIGHT).setNormal(0.25f, 0.5f, 1);
consumer.addVertex((float) x2, (float) y2 + padding, z).setColor(255, 255, 255, 255).setUv(19f / width, 0).setOverlay(OverlayTexture.NO_OVERLAY).setLight(LightTexture.FULL_BRIGHT).setNormal(0.25f, 0.5f, 1);
consumer.addVertex((float) x1, (float) y2 + padding, z).setColor(255, 255, 255, 255).setUv(1f / width, 0).setOverlay(OverlayTexture.NO_OVERLAY).setLight(LightTexture.FULL_BRIGHT).setNormal(0.25f, 0.5f, 1);
consumer.addVertex((float) x1 - padding1, (float) y1, z).setColor(255, 255, 255, 255).setUv(0f / width, 10f / height).setOverlay(OverlayTexture.NO_OVERLAY).setLight(LightTexture.FULL_BRIGHT).setNormal(0.25f, 0.5f, 1);
consumer.addVertex((float) x2 + padding1, (float) y1, z).setColor(255, 255, 255, 255).setUv(20f / width, 10f / height).setOverlay(OverlayTexture.NO_OVERLAY).setLight(LightTexture.FULL_BRIGHT).setNormal(0.25f, 0.5f, 1);
consumer.addVertex((float) x2 + padding1, (float) y2, z).setColor(255, 255, 255, 255).setUv(20f / width, 1f / height).setOverlay(OverlayTexture.NO_OVERLAY).setLight(LightTexture.FULL_BRIGHT).setNormal(0.25f, 0.5f, 1);
consumer.addVertex((float) x1 - padding1, (float) y2, z).setColor(255, 255, 255, 255).setUv(0f / width, 1f / height).setOverlay(OverlayTexture.NO_OVERLAY).setLight(LightTexture.FULL_BRIGHT).setNormal(0.25f, 0.5f, 1);
// consumer.vertex(positionMatrix, (float) x2, (float) y2 + padding, z - padding).color(255, 255, 255, 255).uv(0f / width, 1f / height).overlayCoords(OverlayTexture.NO_OVERLAY).uv2(LightTexture.FULL_BRIGHT).normal(0.25f, 0.5f, 1).endVertex();
// consumer.vertex(positionMatrix, (float) x2, (float) y2 + padding, z).color(255, 255, 255, 255).uv(1f / width, 1f / height).overlayCoords(OverlayTexture.NO_OVERLAY).uv2(LightTexture.FULL_BRIGHT).normal(0.25f, 0.5f, 1).endVertex();
// consumer.vertex(positionMatrix, (float) x2, (float) y1 - padding, z).color(255, 255, 255, 255).uv(1f / width, 2f / height).overlayCoords(OverlayTexture.NO_OVERLAY).uv2(LightTexture.FULL_BRIGHT).normal(0.25f, 0.5f, 1).endVertex();
// consumer.vertex(positionMatrix, (float) x2, (float) y1 - padding, z - padding).color(255, 255, 255, 255).uv(0f / width, 2f / height).overlayCoords(OverlayTexture.NO_OVERLAY).uv2(LightTexture.FULL_BRIGHT).normal(0.25f, 0.5f, 1).endVertex();
//
// consumer.vertex(positionMatrix, (float) x1, (float) y1 - padding, z - padding).color(255, 255, 255, 255).uv(0f / width, 2f / height).overlayCoords(OverlayTexture.NO_OVERLAY).uv2(LightTexture.FULL_BRIGHT).normal(0.25f, 0.5f, 1).endVertex();
// consumer.vertex(positionMatrix, (float) x1, (float) y1 - padding, z).color(255, 255, 255, 255).uv(1f / width, 2f / height).overlayCoords(OverlayTexture.NO_OVERLAY).uv2(LightTexture.FULL_BRIGHT).normal(0.25f, 0.5f, 1).endVertex();
// consumer.vertex(positionMatrix, (float) x1, (float) y2 + padding, z).color(255, 255, 255, 255).uv(1f / width, 1f / height).overlayCoords(OverlayTexture.NO_OVERLAY).uv2(LightTexture.FULL_BRIGHT).normal(0.25f, 0.5f, 1).endVertex();
// consumer.vertex(positionMatrix, (float) x1, (float) y2 + padding, z - padding).color(255, 255, 255, 255).uv(0f / width, 1f / height).overlayCoords(OverlayTexture.NO_OVERLAY).uv2(LightTexture.FULL_BRIGHT).normal(0.25f, 0.5f, 1).endVertex();
}
}

View File

@@ -0,0 +1,236 @@
/*
* Copyright (C) 2018 BARBOTIN Nicolas
*/
package net.montoyo.wd.client.renderers;
import com.google.common.collect.ImmutableList;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.client.renderer.block.model.BakedQuad;
import net.minecraft.client.renderer.block.model.ItemOverrides;
import net.minecraft.client.renderer.block.model.ItemTransforms;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelState;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.BlockAndTintGetter;
import net.minecraft.world.level.block.state.BlockState;
import net.neoforged.neoforge.client.model.data.ModelData;
import net.neoforged.neoforge.client.model.data.ModelProperty;
import net.montoyo.wd.utilities.data.BlockSide;
import net.montoyo.wd.utilities.math.Vector3f;
import net.montoyo.wd.utilities.math.Vector3i;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
public class ScreenBaker implements BakedModel {
private static final List<BakedQuad> noQuads = ImmutableList.of();
private final TextureAtlasSprite[] texs = new TextureAtlasSprite[16];
private final BlockSide[] blockSides = BlockSide.values();
private final Direction[] blockFacings = Direction.values();
private final ModelState modelState;
private final Function<net.minecraft.client.resources.model.Material, TextureAtlasSprite> spriteGetter;
private final ItemOverrides overrides;
private final ItemTransforms itemTransforms;
IntegerModelProperty[] TEXTURES = new IntegerModelProperty[6];
public ScreenBaker(ModelState modelState, Function<net.minecraft.client.resources.model.Material, TextureAtlasSprite> spriteGetter, ItemOverrides overrides, ItemTransforms itemTransforms) {
this.modelState = modelState;
this.spriteGetter = spriteGetter;
this.overrides = overrides;
this.itemTransforms = itemTransforms;
for (int i = 0; i < texs.length; i++) {
texs[i] = spriteGetter.apply(ScreenModelLoader.MATERIALS_SIDES[i]);
}
for (int i = 0; i < TEXTURES.length; i++) {
TEXTURES[i] = new IntegerModelProperty();
}
}
private void putVertex(int[] buf, int pos, Vector3f vpos, TextureAtlasSprite tex, Vector3f uv, Vector3i normal) {
pos *= 8;
buf[pos] = Float.floatToRawIntBits(vpos.x);
buf[pos + 1] = Float.floatToRawIntBits(vpos.y);
buf[pos + 2] = Float.floatToRawIntBits(vpos.z);
buf[pos + 3] = 0xFFFFFFFF; //Color, let this white...
buf[pos + 4] = Float.floatToRawIntBits(tex.getU(uv.x));
buf[pos + 5] = Float.floatToRawIntBits(tex.getV(uv.y));
int nx = (normal.x * 127) & 0xFF;
int ny = (normal.y * 127) & 0xFF;
int nz = (normal.z * 127) & 0xFF;
buf[pos + 7] = nx | (ny << 8) | (nz << 16);
}
private Vector3f rotateVec(Vector3f vec, BlockSide side) {
return switch (side) {
case BOTTOM -> new Vector3f(vec.x, 1.0f, 1.0f - vec.z);
case TOP -> new Vector3f(vec.x, 0.0f, vec.z);
case NORTH -> new Vector3f(vec.x, vec.z, 1.0f);
case SOUTH -> new Vector3f(vec.x, 1.0f - vec.z, 0.0f);
case WEST -> new Vector3f(1.f, vec.x, vec.z);
case EAST -> new Vector3f(0.0f, 1.0f - vec.x, vec.z);
//noinspection UnnecessaryDefault
default -> throw new RuntimeException("Unknown block side " + side);
};
}
private Vector3f rotateTex(BlockSide side, float u, float v) {
return switch (side) {
case BOTTOM, NORTH -> new Vector3f(16.f - u, 16.f - v, 0.0f);
case TOP -> new Vector3f(16.f - u, v, 0.0f);
case SOUTH -> new Vector3f(u, v, 0.0f);
case WEST -> new Vector3f(16.f - v, u, 0.0f);
case EAST -> new Vector3f(v, 16.f - u, 0.0f);
//noinspection UnnecessaryDefault
default -> throw new RuntimeException("Unknown block side " + side);
};
}
private BakedQuad bakeSide(BlockSide side, TextureAtlasSprite tex) {
int[] data = new int[8 * 4];
// I have no idea
int rotation = switch (side) {
case NORTH, TOP, BOTTOM -> 2;
case SOUTH -> 0;
case EAST -> 1;
case WEST -> 3;
//noinspection UnnecessaryDefault
default -> throw new RuntimeException("Unknown block side " + side);
};
putVertex(data, (rotation + 3) % 4, rotateVec(new Vector3f(0.0f, 0.0f, 0.0f), side), tex, rotateTex(side, 16.0f, 0.0f), side.backward);
putVertex(data, (rotation + 2) % 4, rotateVec(new Vector3f(0.0f, 0.0f, 1.0f), side), tex, rotateTex(side, 16.0f, 16.0f), side.backward);
putVertex(data, (rotation + 1) % 4, rotateVec(new Vector3f(1.0f, 0.0f, 1.0f), side), tex, rotateTex(side, 0.0f, 16.0f), side.backward);
putVertex(data, (rotation) % 4, rotateVec(new Vector3f(1.0f, 0.0f, 0.0f), side), tex, rotateTex(side, 0.0f, 0.0f), side.backward);
return new BakedQuad(data, 0xFFFFFFFF, blockFacings[side.ordinal()].getOpposite(), tex, true);
}
@Override
public List<BakedQuad> getQuads(@Nullable BlockState state, @Nullable Direction side, RandomSource random) {
return getQuads(state, side, random, ModelData.EMPTY, null);
}
@Override
public @NotNull List<BakedQuad> getQuads(@Nullable BlockState state, @Nullable Direction side, @NotNull RandomSource rand, @NotNull ModelData data, @Nullable RenderType renderType) {
if (side == null)
return noQuads;
List<BakedQuad> ret = new ArrayList<>();
int sid = BlockSide.reverse(side.ordinal());
BlockSide s = blockSides[sid];
TextureAtlasSprite tex = texs[15];
if (data.has(TEXTURES[side.ordinal()]))
tex = texs[data.get(TEXTURES[side.ordinal()])];
ret.add(bakeSide(s, tex));
return ret;
}
protected byte check(BlockState state, BlockAndTintGetter level, BlockPos pos, Vector3i dir) {
BlockState u = level.getBlockState(pos.offset(dir.x, dir.y, dir.z));
BlockState d = level.getBlockState(pos.offset(-dir.x, -dir.y, -dir.z));
if (
u.getBlock() == state.getBlock() &&
d.getBlock() != state.getBlock()
) return (byte) 1; // away
else if (
d.getBlock() == state.getBlock() &&
u.getBlock() != state.getBlock()
) return (byte) 2; // to
else if (
d.getBlock() != state.getBlock() &&
u.getBlock() != state.getBlock()
) return (byte) 3; // both
return (byte) 0; // none
}
@Override
public @NotNull ModelData getModelData(@NotNull BlockAndTintGetter level, @NotNull BlockPos pos, @NotNull BlockState state, @NotNull ModelData modelData) {
ModelData.Builder builder = ModelData.builder();
final int BAR_BOTTOM = 1;
final int BAR_RIGHT = 2;
final int BAR_TOP = 4;
final int BAR_LEFT = 8;
for (int i = 0; i < TEXTURES.length; i++) {
BlockSide side = blockSides[i];
// check up and down
int res = switch (check(state, level, pos, side.up)) {
case 1 -> BAR_BOTTOM;
case 2 -> BAR_TOP;
case 3 -> BAR_TOP | BAR_BOTTOM;
default -> 0;
};
// check left and right
res |= switch (check(state, level, pos, side.right)) {
case 1 -> BAR_LEFT;
case 2 -> BAR_RIGHT;
case 3 -> BAR_LEFT | BAR_RIGHT;
default -> 0;
};
builder.with(TEXTURES[i], res);
}
return builder.build();
}
@Override
public boolean useAmbientOcclusion() {
return true;
}
@Override
public boolean isGui3d() {
return true;
}
@Override
public boolean usesBlockLight() {
return false;
}
@Override
public boolean isCustomRenderer() {
return false;
}
@Override
@Nonnull
public TextureAtlasSprite getParticleIcon() {
return texs[15];
}
@Override
@Nonnull
public ItemTransforms getTransforms() {
return ItemTransforms.NO_TRANSFORMS;
}
@Override
@Nonnull
public ItemOverrides getOverrides() {
return ItemOverrides.EMPTY;
}
//@formatter:off
public static final class IntegerModelProperty extends ModelProperty<Integer> {}
//@formatter:on
}

View File

@@ -0,0 +1,64 @@
package net.montoyo.wd.client.renderers;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.mojang.datafixers.util.Pair;
import net.minecraft.client.renderer.block.model.ItemOverrides;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.resources.model.*;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.client.renderer.texture.TextureAtlas;
import net.minecraft.world.inventory.InventoryMenu;
import net.neoforged.neoforge.client.model.geometry.IGeometryBakingContext;
import net.neoforged.neoforge.client.model.geometry.IGeometryLoader;
import net.neoforged.neoforge.client.model.geometry.IUnbakedGeometry;
import java.util.Arrays;
import java.util.Collection;
import java.util.Set;
import java.util.function.Function;
public class ScreenModelLoader implements IGeometryLoader<ScreenModelLoader.ScreenModelGeometry> {
public static final ResourceLocation SCREEN_LOADER = ResourceLocation.fromNamespaceAndPath("webdisplays", "screen_loader");
public static final ResourceLocation SCREEN_SIDE = ResourceLocation.fromNamespaceAndPath("webdisplays", "block/screen");
private static final ResourceLocation[] SIDES = new ResourceLocation[16];
public static final Material[] MATERIALS_SIDES = new Material[16];
static {
for (int i = 0; i < SIDES.length; i++) {
SIDES[i] = ResourceLocation.fromNamespaceAndPath(SCREEN_SIDE.getNamespace(), SCREEN_SIDE.getPath() + i);
MATERIALS_SIDES[i] = new Material(InventoryMenu.BLOCK_ATLAS, SIDES[i]);
}
}
@Override
public ScreenModelGeometry read(JsonObject jsonObject, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
return new ScreenModelGeometry();
}
public static class ScreenModelGeometry implements IUnbakedGeometry<ScreenModelGeometry> {
@Override
public BakedModel bake(IGeometryBakingContext context, ModelBaker baker, Function<Material, TextureAtlasSprite> spriteGetter, ModelState modelState, ItemOverrides overrides) {
return new ScreenBaker(modelState, spriteGetter, overrides, context.getTransforms());
}
// @Override
// public void resolveParents(Function<ResourceLocation, UnbakedModel> modelGetter, IGeometryBakingContext context) {
// IUnbakedGeometry.super.resolveParents(modelGetter, context);
// }
// @Override
// public Set<String> getConfigurableComponentNames() {
// return IUnbakedGeometry.super.getConfigurableComponentNames();
// }
// In NeoForge 1.21, materials are discovered via atlas sources.
// We register screen0..screen15 textures in assets/webdisplays/atlases/blocks.json.
}
}

View File

@@ -0,0 +1,194 @@
/*
* Copyright (C) 2018 BARBOTIN Nicolas
*/
package net.montoyo.wd.client.renderers;
import com.cinemamod.mcef.MCEFBrowser;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.*;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.client.renderer.MultiBufferSource;
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer;
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;
import com.mojang.blaze3d.vertex.BufferUploader;
import net.montoyo.wd.WebDisplays;
import net.montoyo.wd.entity.ScreenBlockEntity;
import net.montoyo.wd.entity.ScreenData;
import net.montoyo.wd.utilities.math.Vector3f;
import net.montoyo.wd.utilities.math.Vector3i;
import org.jetbrains.annotations.NotNull;
import static com.mojang.math.Axis.*;
public class ScreenRenderer implements BlockEntityRenderer<ScreenBlockEntity> {
public ScreenRenderer() {
}
public static class ScreenRendererProvider implements BlockEntityRendererProvider<ScreenBlockEntity> {
@Override
public @NotNull BlockEntityRenderer<ScreenBlockEntity> create(@NotNull Context arg) {
return new ScreenRenderer();
}
}
private final Vector3f mid = new Vector3f();
private final Vector3i tmpi = new Vector3i();
private final Vector3f tmpf = new Vector3f();
@Override
public void render(ScreenBlockEntity te, float partialTick, @NotNull PoseStack poseStack, @NotNull MultiBufferSource bufferSource, int packedLight, int packedOverlay) {
if (!te.isLoaded())
return;
//Disable lighting
// RenderSystem.enableTexture();
// RenderSystem.disableCull();
RenderSystem.disableBlend();
for (int i = 0; i < te.screenCount(); i++) {
ScreenData scr = te.getScreen(i);
if (scr.browser == null) {
double dist = WebDisplays.PROXY.distanceTo(te, Minecraft.getInstance().getEntityRenderDispatcher().camera.getPosition());
if (dist <= WebDisplays.INSTANCE.loadDistance2 * 16)
scr.createBrowser(te, true);
else continue;
}
// TODO: manually backface cull the screens
tmpi.set(scr.side.right);
tmpi.mul(scr.size.x);
tmpi.addMul(scr.side.up, scr.size.y);
tmpf.set(tmpi);
mid.set(0.5, 0.5, 0.5);
mid.addMul(tmpf, 0.5f);
tmpf.set(scr.side.left);
mid.addMul(tmpf, 0.5f);
tmpf.set(scr.side.down);
mid.addMul(tmpf, 0.5f);
poseStack.pushPose();
poseStack.translate(mid.x, mid.y, mid.z);
switch (scr.side) {
case BOTTOM:
poseStack.mulPose(XP.rotation(90.f + 49.8f));
break;
case TOP:
poseStack.mulPose(XN.rotation(90.f + 49.8f));
break;
case NORTH:
poseStack.mulPose(YN.rotationDegrees(180.f));
break;
case SOUTH:
break;
case WEST:
poseStack.mulPose(YN.rotationDegrees(90.f));
break;
case EAST:
poseStack.mulPose(YP.rotationDegrees(90.f));
break;
}
if (scr.doTurnOnAnim) {
long lt = System.currentTimeMillis() - scr.turnOnTime;
float ft = ((float) lt) / 100.0f;
if (ft >= 1.0f) {
ft = 1.0f;
scr.doTurnOnAnim = false;
}
poseStack.scale(ft, ft, 1.0f);
}
if (!scr.rotation.isNull)
poseStack.mulPose(ZP.rotationDegrees(scr.rotation.angle));
float sw = ((float) scr.size.x) * 0.5f - 2.f / 16.f;
float sh = ((float) scr.size.y) * 0.5f - 2.f / 16.f;
if (scr.rotation.isVertical) {
float tmp = sw;
sw = sh;
sh = tmp;
}
Tesselator tesselator = Tesselator.getInstance();
BufferBuilder builder = tesselator.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX_COLOR);
//TODO: don't use tesselator
RenderSystem.enableDepthTest();
RenderSystem.setShader(GameRenderer::getPositionTexColorShader);
RenderSystem.setShaderTexture(0, ((MCEFBrowser) scr.browser).getRenderer().getTextureID());
RenderSystem.setShaderColor(1.0f, 1.0f, 1.0f, 1.0f);
builder.addVertex(poseStack.last().pose(), -sw, -sh, 0.505f).setUv(0.f, 1.f).setColor(1.f, 1.f, 1.f, 1.f);
builder.addVertex(poseStack.last().pose(), sw, -sh, 0.505f).setUv(1.f, 1.f).setColor(1.f, 1.f, 1.f, 1.f);
builder.addVertex(poseStack.last().pose(), sw, sh, 0.505f).setUv(1.f, 0.f).setColor(1.f, 1.f, 1.f, 1.f);
builder.addVertex(poseStack.last().pose(), -sw, sh, 0.505f).setUv(0.f, 0.f).setColor(1.f, 1.f, 1.f, 1.f);
BufferUploader.drawWithShader(builder.buildOrThrow());//Minecraft does shit with mah texture otherwise...
RenderSystem.disableDepthTest();
// TODO: it'd be neat to draw a mouse cursor on the screen
// // debug hit2pixels
// HitResult result = Minecraft.getInstance().hitResult;
// VertexConsumer consumer = bufferSource.getBuffer(RenderType.lines());
// poseStack.translate(-sw, -sh, 0);
// if (result instanceof BlockHitResult hit) {
// BlockPos bpos = hit.getBlockPos();
//
// Vector3i pos = new Vector3i(hit.getBlockPos());
// float hitX = ((float) result.getLocation().x) - (float) te.getBlockPos().getX();
// float hitY = ((float) result.getLocation().y) - (float) te.getBlockPos().getY();
// float hitZ = ((float) result.getLocation().z) - (float) te.getBlockPos().getZ();
// Vector2i tmp = new Vector2i();
//
// if (BlockScreen.hit2pixels(scr.side, bpos, pos, scr, hitX, hitY, hitZ, tmp)) {
// float x = tmp.x / (float) scr.resolution.x * scr.size.x;
// float y = tmp.y / (float) scr.resolution.y * scr.size.y;
// y = scr.size.y - y;
//
// x /= scr.size.x;
// y /= scr.size.y;
// x *= sw * 2;
// y *= sh * 2;
//
// LevelRenderer.renderLineBox(
// poseStack,
// consumer, new AABB(
// x - 0.01, y - 0.01, 0.5 - 0.01,
// x + 0.01, y + 0.01, 0.5 + 0.01
// ),
// 1f, 0, 0, 1f
// );
// }
// }
poseStack.popPose();
}
// //Bounding box debugging
// poseStack.pushPose();
// poseStack.translate(-te.getBlockPos().getX(), -te.getBlockPos().getY(), -te.getBlockPos().getZ());
// LevelRenderer.renderLineBox(
// poseStack, bufferSource.getBuffer(RenderType.LINES),
// te.getRenderBoundingBox(), 1, 1, 1, 1f
// );
// poseStack.popPose();
//Re-enable lighting
// RenderSystem.enableCull();
}
@Override
public net.minecraft.world.phys.AABB getRenderBoundingBox(ScreenBlockEntity blockEntity) {
return blockEntity.getRenderBB();
}
}

View File

@@ -0,0 +1,133 @@
package net.montoyo.wd.config;
import net.neoforged.fml.config.ModConfig;
import net.neoforged.fml.ModLoadingContext;
import net.montoyo.wd.WebDisplays;
import net.montoyo.wd.config.annoconfg.AnnoCFG;
import net.montoyo.wd.config.annoconfg.annotation.format.*;
import net.montoyo.wd.config.annoconfg.annotation.value.Default;
import net.montoyo.wd.config.annoconfg.annotation.value.DoubleRange;
import net.montoyo.wd.config.annoconfg.annotation.value.IntRange;
@Config(type = ModConfig.Type.CLIENT)
public class ClientConfig {
@SuppressWarnings("unused")
private static final AnnoCFG CFG = new AnnoCFG(ModLoadingContext.get().getActiveContainer().getEventBus(), ClientConfig.class);
public static void init() {
// loads the class
}
@Name("load_distance")
@Comment("How far (in blocks) you can be before a screen starts rendering")
@Translation("config.webdisplays.load_distance")
@DoubleRange(minV = 0, maxV = Double.MAX_VALUE)
@Default(valueD = 30)
public static double loadDistance = 30.0;
@Name("unload_distance")
@Comment("How far you can be before a screen stops rendering")
@Translation("config.webdisplays.unload_distance")
@DoubleRange(minV = 0, maxV = Double.MAX_VALUE)
@Default(valueD = 32)
public static double unloadDistance = 32.0;
@Name("pad_resolution")
@Comment({
"The resolution that minePads should use",
"Smaller values produce lower qualities, higher values produce higher qualities",
"Due to how web browsers work however, the larger this value is, the smaller text is",
"Also, higher values will invariably lag more",
"A good goto value for this would be the height of your monitor, in pixels",
"A standard monitor is (at least currently) 1080",
})
@Translation("config.webdisplays.pad_res")
@IntRange(minV = 0, maxV = Integer.MAX_VALUE)
@Default(valueI = 720)
public static int padResolution = 720;
@Name("side_pad")
@Comment({
"When this is true, the minePad is placed off to the side of the screen when held, so it's visible but doesn't take up too much of the screen",
"When this is false, the minePad is placed closer to the center of the screen, allow it to be seen better, but taking up more of your view",
})
@Translation("config.webdisplays.side_pad")
@Default(valueBoolean = true)
public static boolean sidePad = true;
@Comment({
"Options relating to input handling"
})
@CFGSegment("input")
public static class Input {
@Name("keyboard_camera")
@Comment({
"If this is on, then the camera will try to focus on the selected element while a keyboard is in use",
"Elsewise, it'll try to focus on the center of the screen",
})
@Translation("config.webdisplays.keyboard_camera")
@Default(valueBoolean = true)
public static boolean keyboardCamera = true;
@Name("switch_buttons")
@Comment("If the left and right buttons should be swapped when using a laser")
@Translation("config.webdisplays.switch_buttons")
@DoubleRange(minV = 0, maxV = Double.MAX_VALUE)
@Default(valueD = 30)
public static boolean switchButtons = true;
}
// @Comment({
// "AutoVolume makes audio fade off based on distance",
// "Currently, this seems to not work"
// })
// @CFGSegment("auto_volume")
// public static class AutoVolumeControl {
// @Name("enabled")
// @Comment("Whether or not auto volume should be enabled")
// @Translation("config.webdisplays.auto_vol")
// @Default(valueBoolean = true)
// public static boolean enableAutoVolume = true;
//
// @Name("youtube_volume")
// @Comment("How loud youtube should be by default")
// @Translation("config.webdisplays.yt_vol")
// @DoubleRange(minV = 0, maxV = 100)
// @Default(valueD = 100)
// public static double ytVolume = 100.0;
//
// @Name("dist0")
// @Comment("Distance after which you can't hear anything (in blocks)")
// @Translation("config.webdisplays.d0")
// @DoubleRange(minV = 0, maxV = Double.MAX_VALUE)
// @Default(valueD = 30)
// public static double dist0 = 30.0;
//
// @Name("dist100")
// @Comment("Distance after which the sound starts dropping (in blocks)")
// @Translation("config.webdisplays.d100")
// @DoubleRange(minV = 0, maxV = Double.MAX_VALUE)
// @Default(valueD = 10)
// public static double dist100 = 10.0;
// }
@SuppressWarnings("unused")
public static void postLoad() {
if (unloadDistance < loadDistance + 2.0)
unloadDistance = loadDistance + 2.0;
// if (AutoVolumeControl.dist0 < AutoVolumeControl.dist100 + 0.1)
// AutoVolumeControl.dist0 = AutoVolumeControl.dist100 + 0.1;
// cache pad resolution
WebDisplays.INSTANCE.padResY = padResolution;
WebDisplays.INSTANCE.padResX = WebDisplays.INSTANCE.padResY * WebDisplays.PAD_RATIO;
// cache unload/load distances
WebDisplays.INSTANCE.unloadDistance2 = unloadDistance * unloadDistance;
WebDisplays.INSTANCE.loadDistance2 = loadDistance * loadDistance;
// WebDisplays.INSTANCE.ytVolume = (float) AutoVolumeControl.ytVolume;
// WebDisplays.INSTANCE.avDist100 = (float) AutoVolumeControl.dist100;
// WebDisplays.INSTANCE.avDist0 = (float) AutoVolumeControl.dist0;
}
}

View File

@@ -0,0 +1,127 @@
package net.montoyo.wd.config;
import net.neoforged.fml.config.ModConfig;
import net.neoforged.fml.ModLoadingContext;
import net.montoyo.wd.WebDisplays;
import net.montoyo.wd.config.annoconfg.AnnoCFG;
import net.montoyo.wd.config.annoconfg.annotation.format.*;
import net.montoyo.wd.config.annoconfg.annotation.value.Default;
import net.montoyo.wd.config.annoconfg.annotation.value.IntRange;
import net.montoyo.wd.config.annoconfg.annotation.value.LongRange;
@SuppressWarnings("DefaultAnnotationParam")
@Config(type = ModConfig.Type.COMMON)
public class CommonConfig {
@SuppressWarnings("unused")
private static final AnnoCFG CFG = new AnnoCFG(ModLoadingContext.get().getActiveContainer().getEventBus(), CommonConfig.class);
public static void init() {
// loads the class
}
@Name("hard_recipes")
@Comment("If true, breaking the minePad is required to craft upgrades.")
@Translation("config.webdisplays.hard_recipes")
@Default(valueBoolean = true)
public static boolean hardRecipes = true;
@Name("join_message")
@Comment("Whether or not webdisplays should thank the user for using the mod")
@Translation("config.webdisplays.join_message")
@Default(valueBoolean = true)
public static boolean joinMessage = true;
@Name("disable_ownership_thief")
@Comment("If true, the ownership thief item will be disabled")
@Translation("config.webdisplays.disable_thief")
@Default(valueBoolean = false)
public static boolean disableOwnershipThief = false;
@Comment("Options for the browsers (both the minePad and the screens)")
@CFGSegment("browser_options")
public static class Browser {
@Name("blacklist")
@Comment("The page which screens should open up to when turning on")
@Translation("config.webdisplays.blacklist")
@Default(valueStr = "")
public static String[] blacklist = new String[0];
@Name("home_page")
@Comment("The page which screens should open up to when turning on")
@Translation("config.webdisplays.home_page")
@Default(valueStr = "https://git.lnkos.cn")
public static String homepage = "https://git.lnkos.cn";
}
@Comment("Options for the in world screen blocks")
@CFGSegment("screen_options")
public static class Screen {
@Name("max_resolution_x")
@Comment("The maximum value screen's horizontal resolution, in pixels")
@Translation("config.webdisplays.max_res_x")
@IntRange(minV = 0, maxV = Integer.MAX_VALUE)
@Default(valueI = 1920)
public static int maxResolutionX = 1920;
@Name("max_resolution_y")
@Comment("The maximum value screen's vertical resolution, in pixels")
@Translation("config.webdisplays.max_res_y")
@IntRange(minV = 0, maxV = Integer.MAX_VALUE)
@Default(valueI = 1080)
public static int maxResolutionY = 1080;
@Name("max_width")
@Comment("The maximum width for the screen multiblock, in blocks")
@Translation("config.webdisplays.max_width")
@IntRange(minV = 0, maxV = Integer.MAX_VALUE)
@Default(valueI = 16)
public static int maxScreenSizeX = 16;
@Name("max_height")
@Comment("The maximum height for the screen multiblock, in blocks")
@Translation("config.webdisplays.max_height")
@IntRange(minV = 0, maxV = Integer.MAX_VALUE)
@Default(valueI = 16)
public static int maxScreenSizeY = 16;
}
@Comment("Options for the miniserver")
@CFGSegment("mini_server")
public static class MiniServ {
@Name("miniserv_port")
@Comment("The port used by miniserv. 0 to disable")
@Translation("config.webdisplays.miniserv_port")
@IntRange(minV = 0, maxV = Short.MAX_VALUE)
@Default(valueI = 25566)
public static int miniservPort = 25566;
@Name("miniserv_quota")
@Comment("The amount of data that can be uploaded to miniserv, in KiB (so 1024 = 1 MiO)")
@Translation("config.webdisplays.miniserv_quota")
@LongRange(minV = 0, maxV = Long.MAX_VALUE)
@Default(valueL = 1920)
public static long miniservQuota = 1024; //It's stored as a string anyway
}
@SuppressWarnings("unused")
public static void postLoad() {
WebDisplays.INSTANCE.miniservPort = MiniServ.miniservPort;
WebDisplays.INSTANCE.miniservQuota = MiniServ.miniservQuota * 1024L;
}
// //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.");
}

View File

@@ -0,0 +1,220 @@
package net.montoyo.wd.config.annoconfg;
import net.neoforged.neoforge.common.ModConfigSpec;
import net.neoforged.bus.api.IEventBus;
import net.neoforged.fml.ModLoadingContext;
import net.neoforged.fml.config.ModConfig;
import net.neoforged.fml.event.config.ModConfigEvent;
import net.montoyo.wd.config.annoconfg.annotation.format.*;
import net.montoyo.wd.config.annoconfg.annotation.value.Default;
import net.montoyo.wd.config.annoconfg.annotation.value.DoubleRange;
import net.montoyo.wd.config.annoconfg.annotation.value.IntRange;
import net.montoyo.wd.config.annoconfg.annotation.value.LongRange;
import net.montoyo.wd.config.annoconfg.handle.UnsafeHandle;
import net.montoyo.wd.config.annoconfg.util.EnumType;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.function.Supplier;
public class AnnoCFG {
private ModConfigSpec mySpec;
private final HashMap<String, ConfigEntry> handles = new HashMap<>();
private static final ArrayList<AnnoCFG> configs = new ArrayList<>();
private final Method postInit;
public AnnoCFG(IEventBus bus, Class<?> clazz) {
bus.addListener(this::onConfigChange);
ModConfigSpec.Builder configBuilder = new ModConfigSpec.Builder();
setup("", configBuilder, clazz);
configs.add(this);
Method m = null;
try {
m = clazz.getDeclaredMethod("postLoad");
} catch (Throwable ignored) {
}
postInit = m;
Config configDescriptor = clazz.getAnnotation(Config.class);
if (configDescriptor != null) {
String pth = configDescriptor.path();
if (!pth.isEmpty()) pth = pth + "/";
switch (configDescriptor.type()) {
case SERVER -> create(ModConfig.Type.SERVER, pth + ModLoadingContext.get().getActiveNamespace() + "_server.toml");
case CLIENT -> create(ModConfig.Type.CLIENT, pth + ModLoadingContext.get().getActiveNamespace() + "_client.toml");
case COMMON -> create(ModConfig.Type.COMMON, pth + ModLoadingContext.get().getActiveNamespace() + "_common.toml");
default -> throw new RuntimeException("wat");
}
}
}
protected void setupCommentsAndTranslations(AnnotatedElement element, ModConfigSpec.Builder builder, String... additionalLines) {
Translation translation = element.getAnnotation(Translation.class);
Comment comment = element.getAnnotation(Comment.class);
StringBuilder builder1 = new StringBuilder();
if (comment != null) {
for (int i = 0; i < comment.value().length; i++) {
String s = comment.value()[i];
builder1.append(s);
if (i != comment.value().length - 1)
builder1.append("\n");
}
}
for (String additionalLine : additionalLines) builder1.append(additionalLine);
if (!builder1.isEmpty())
builder.comment(builder1.toString());
if (translation != null)
builder.translation(translation.value());
}
public void setup(String dir, ModConfigSpec.Builder builder, Class<?> clazz) {
if (dir.startsWith(".")) dir = dir.substring(1);
for (Field field : clazz.getFields()) {
if (field.canAccess(null)) {
Skip skip = field.getAnnotation(Skip.class);
if (skip != null) continue;
Name name = field.getAnnotation(Name.class);
String nameStr = field.getName();
if (name != null) nameStr = name.value();
setupCommentsAndTranslations(field, builder);
Supplier<?> value;
Default defaultValue = field.getAnnotation(Default.class);
try {
switch (EnumType.forClass(field.getType())) {
case INT -> {
IntRange range = field.getAnnotation(IntRange.class);
int v = defaultValue.valueI();
if (range != null) {
int min = range.minV();
int max = range.maxV();
value = builder.defineInRange(nameStr, v, min, max);
} else {
value = builder.define(nameStr, v);
}
}
case LONG -> {
LongRange range = field.getAnnotation(LongRange.class);
long v = defaultValue.valueL();
if (range != null) {
long min = range.minV();
long max = range.maxV();
value = builder.defineInRange(nameStr, v, min, max);
} else {
value = builder.define(nameStr, v);
}
}
case DOUBLE -> {
DoubleRange range = field.getAnnotation(DoubleRange.class);
double v = defaultValue.valueD();
if (range != null) {
double min = range.minV();
double max = range.maxV();
value = builder.defineInRange(nameStr, v, min, max);
} else {
value = builder.define(nameStr, v);
}
}
case BOOLEAN -> {
boolean b = defaultValue.valueBoolean();
value = builder.define(nameStr, b);
}
case OTHER -> {
Class<?> fieldType = field.getType();
if (fieldType.equals(String[].class)) {
Supplier<String> sup = builder.define(nameStr, defaultValue.valueStr());
value = () -> {
String v = sup.get();
return v.split(",");
};
} else if (fieldType.equals(String.class)) {
value = builder.define(nameStr, defaultValue.valueStr());
} else
throw new RuntimeException("NYI " + field.getType());
}
default -> throw new RuntimeException("NYI " + field.getType());
}
} catch (NullPointerException npe) {
String inf = "";
if (npe.getMessage().contains("\"value.Default\""))
inf = " this is likely due to a missing default.";
throw new RuntimeException("A null pointer occurred on " + field.getName() + inf, npe);
}
Object o;
try {
// without this line, this system freaks out due to using theUnsafe
//noinspection UnusedAssignment
o = field.get(null);
} catch (Throwable ignored) {
}
UnsafeHandle handle = new UnsafeHandle(field);
o = handle.get();
handle.set(o);
//noinspection FunctionalExpressionCanBeFolded
handles.put(dir + "." + nameStr, new ConfigEntry(
handle, value::get
));
}
}
// TODO: check if the nested class is a direct nesting
for (Class<?> nestMember : clazz.getClasses()) {
if (nestMember == clazz) continue;
if (!nestMember.getName().startsWith(clazz.getName())) continue;
CFGSegment segment = nestMember.getAnnotation(CFGSegment.class);
if (segment == null) {
System.out.println(nestMember);
throw new RuntimeException("NYI: default name");
}
String name = segment.value();
setupCommentsAndTranslations(nestMember, builder);
builder.push(name);
setup(dir + "." + name, builder, nestMember);
builder.pop();
}
mySpec = builder.build();
}
public void onConfigChange(ModConfigEvent event) {
if (
event.getConfig().getSpec().equals(mySpec) ||
event.getConfig().getSpec() == mySpec
) {
for (String s : handles.keySet()) {
ConfigEntry entry = handles.get(s);
entry.handle.set(entry.supplier.get());
}
}
try {
postInit.invoke(null);
} catch (Throwable err) {
err.printStackTrace();
}
}
public void create(ModConfig.Type type, String file) {
// NeoForge 1.21+: 注册配置通过 ModContainer文件名使用默认命名约定
ModLoadingContext.get().getActiveContainer().registerConfig(type, mySpec);
}
}

View File

@@ -0,0 +1,15 @@
package net.montoyo.wd.config.annoconfg;
import net.montoyo.wd.config.annoconfg.handle.UnsafeHandle;
import java.util.function.Supplier;
public class ConfigEntry {
UnsafeHandle handle;
Supplier<?> supplier;
public ConfigEntry(UnsafeHandle handle, Supplier<?> supplier) {
this.handle = handle;
this.supplier = supplier;
}
}

View File

@@ -0,0 +1,9 @@
package net.montoyo.wd.config.annoconfg.annotation.format;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface CFGSegment {
String value();
}

View File

@@ -0,0 +1,9 @@
package net.montoyo.wd.config.annoconfg.annotation.format;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface Comment {
String[] value();
}

View File

@@ -0,0 +1,12 @@
package net.montoyo.wd.config.annoconfg.annotation.format;
import net.neoforged.fml.config.ModConfig;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface Config {
ModConfig.Type type();
String path() default "";
}

View File

@@ -0,0 +1,9 @@
package net.montoyo.wd.config.annoconfg.annotation.format;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface Name {
String value();
}

View File

@@ -0,0 +1,8 @@
package net.montoyo.wd.config.annoconfg.annotation.format;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface Skip {
}

View File

@@ -0,0 +1,9 @@
package net.montoyo.wd.config.annoconfg.annotation.format;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface Translation {
String value();
}

View File

@@ -0,0 +1,16 @@
package net.montoyo.wd.config.annoconfg.annotation.value;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface Default {
byte valueB() default 0;
short valueS() default 0;
int valueI() default 0;
long valueL() default 0;
float valueF() default 0;
double valueD() default 0;
boolean valueBoolean() default false;
String valueStr() default "";
}

View File

@@ -0,0 +1,10 @@
package net.montoyo.wd.config.annoconfg.annotation.value;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface DoubleRange {
double minV();
double maxV();
}

View File

@@ -0,0 +1,10 @@
package net.montoyo.wd.config.annoconfg.annotation.value;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface IntRange {
int minV();
int maxV();
}

View File

@@ -0,0 +1,10 @@
package net.montoyo.wd.config.annoconfg.annotation.value;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface LongRange {
long minV();
long maxV();
}

View File

@@ -0,0 +1,86 @@
package net.montoyo.wd.config.annoconfg.handle;
import net.montoyo.wd.config.annoconfg.util.EnumType;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class UnsafeHandle {
private static final Unsafe theUnsafe;
static {
try {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
theUnsafe = (Unsafe) f.get(null);
} catch (Throwable ignored) {
throw new RuntimeException("AnnoConfg: Failed to acquire an instance of the unsafe.");
}
}
private final long offset;
private final Consumer<Object> uploader;
private final Supplier<Object> getter;
public UnsafeHandle(Field f) {
this(null, f);
}
public UnsafeHandle(Object relative, Field f) {
offset = theUnsafe.staticFieldOffset(f);
if (relative == null) relative = theUnsafe.staticFieldBase(f);
Object finalRelative = relative;
if (f.getType().isPrimitive()) {
switch (EnumType.forClass(f.getType())) {
case BYTE -> {
uploader = (v) -> theUnsafe.putByte(finalRelative, offset, (byte) v);
getter = () -> theUnsafe.getByte(finalRelative, offset);
}
case SHORT -> {
uploader = (v) -> theUnsafe.putShort(finalRelative, offset, (short) v);
getter = () -> theUnsafe.getShort(finalRelative, offset);
}
case INT -> {
uploader = (v) -> theUnsafe.putInt(finalRelative, offset, (int) v);
getter = () -> theUnsafe.getInt(finalRelative, offset);
}
case LONG -> {
uploader = (v) -> theUnsafe.putLong(finalRelative, offset, (long) v);
getter = () -> theUnsafe.getLong(finalRelative, offset);
}
case FLOAT -> {
uploader = (v) -> theUnsafe.putFloat(finalRelative, offset, (float) v);
getter = () -> theUnsafe.getFloat(finalRelative, offset);
}
case DOUBLE -> {
uploader = (v) -> theUnsafe.putDouble(finalRelative, offset, (double) v);
getter = () -> theUnsafe.getDouble(finalRelative, offset);
}
case BOOLEAN -> {
uploader = (v) -> theUnsafe.putBoolean(finalRelative, offset, (boolean) v);
getter = () -> theUnsafe.getBoolean(finalRelative, offset);
}
default -> {
// TODO: check that I have all primitives?
uploader = null;
getter = () -> null;
}
}
} else {
uploader = (v) -> theUnsafe.putObject(finalRelative, offset, v);
getter = () -> theUnsafe.getObject(finalRelative, offset);
}
}
public void set(Object o) {
uploader.accept(o);
// System.out.println(getter.get());
}
public Object get() {
return getter.get();
}
}

View File

@@ -0,0 +1,25 @@
package net.montoyo.wd.config.annoconfg.util;
public enum EnumType {
BYTE(byte.class),
SHORT(short.class),
INT(int.class),
LONG(long.class),
FLOAT(float.class),
DOUBLE(double.class),
BOOLEAN(boolean.class),
OTHER(Object.class),
;
Class<?> clazz;
EnumType(Class<?> clazz) {
this.clazz = clazz;
}
public static EnumType forClass(Class<?> clazz) {
for (EnumType value : EnumType.values())
if (value.clazz.equals(clazz)) return value;
return OTHER;
}
}

View File

@@ -0,0 +1,38 @@
package net.montoyo.wd.controls;
import net.minecraft.core.BlockPos;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.api.distmarker.OnlyIn;
import net.neoforged.neoforge.network.handling.IPayloadContext;
import net.montoyo.wd.core.MissingPermissionException;
import net.montoyo.wd.entity.ScreenBlockEntity;
import net.montoyo.wd.utilities.data.BlockSide;
import java.util.Objects;
import java.util.function.Function;
public abstract class ScreenControl {
private final ResourceLocation id;
public ScreenControl(ResourceLocation id) {
this.id = id;
}
public abstract void write(FriendlyByteBuf buf);
public abstract void handleServer(BlockPos pos, BlockSide side, ScreenBlockEntity tes, IPayloadContext ctx, Function<Integer, Boolean> permissionChecker) throws MissingPermissionException;
@OnlyIn(Dist.CLIENT)
public abstract void handleClient(BlockPos pos, BlockSide side, ScreenBlockEntity tes, IPayloadContext ctx);
public void checkPerms(int perms, Function<Integer, Boolean> checker, ServerPlayer player) throws MissingPermissionException {
if (!checker.apply(perms)) {
throw new MissingPermissionException(perms, Objects.requireNonNull(player));
}
}
public final ResourceLocation getId() {
return id;
}
}

View File

@@ -0,0 +1,77 @@
package net.montoyo.wd.controls;
import net.minecraft.core.BlockPos;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.api.distmarker.OnlyIn;
import net.neoforged.fml.loading.FMLEnvironment;
import net.neoforged.neoforge.network.handling.IPayloadContext;
import net.montoyo.wd.controls.builtin.*;
import net.montoyo.wd.entity.ScreenBlockEntity;
import net.montoyo.wd.utilities.data.BlockSide;
import net.montoyo.wd.utilities.Log;
import java.lang.reflect.Method;
import java.util.HashMap;
// TODO: enable deferred registry of these
public class ScreenControlRegistry {
private static final HashMap<ResourceLocation, ScreenControlType<?>> CONTROL_TYPES = new HashMap<>();
public static void register(ResourceLocation name, ScreenControlType<?> type) {
if (CONTROL_TYPES.containsKey(name)) {
Log.warning("ScreenControlRegistry#CONTROL_TYPES already contains an entry with name " + name);
throw new IllegalArgumentException("Cannot have two entries with the same name.");
}
CONTROL_TYPES.put(name, type);
// lil thing for sanity
// avoids the pain the dist cleaner causes, hopefully
if (!FMLEnvironment.production) {
if (FMLEnvironment.dist.isClient()) {
boolean shouldThrow = false;
try {
Method m = type.clazz.getMethod("handleClient", BlockPos.class, BlockSide.class, ScreenBlockEntity.class, IPayloadContext.class);
OnlyIn onlyIn = m.getAnnotation(OnlyIn.class);
if (onlyIn == null) shouldThrow = true;
Dist d = onlyIn.value(); // idc if this throws, lol
if (d != Dist.CLIENT) shouldThrow = true;
} catch (Throwable ignored) {
}
if (shouldThrow) {
Log.warning("handleClient on ScreenControl classes MUST be marked with `@OnlyIn(Dist.CLIENT)`, but it is not on " + type.clazz);
throw new IllegalStateException(
"handleClient on ScreenControl classes MUST be marked with `@OnlyIn(Dist.CLIENT)`, but it is not on " + type.clazz
);
}
}
}
}
// if needed, the old code
// https://github.com/Mysticpasta1/webdisplays/blob/ff55cbf1b27773c15f44f17ad3364da3a16b6ed9/src/main/java/net/montoyo/wd/net/server/SMessageScreenCtrl.java#L281-L364
// https://github.com/Mysticpasta1/webdisplays/blob/5ce9e4574df356910645b0382628f74d1401e26d/src/main/java/net/montoyo/wd/net/client_bound/S2CMessageScreenUpdate.java#L261-L284
static {
register(SetURLControl.id, new ScreenControlType<>(SetURLControl.class, SetURLControl::new));
register(KeyTypedControl.id, new ScreenControlType<>(KeyTypedControl.class, KeyTypedControl::new));
register(AutoVolumeControl.id, new ScreenControlType<>(AutoVolumeControl.class, AutoVolumeControl::new));
register(JSRequestControl.id, new ScreenControlType<>(JSRequestControl.class, JSRequestControl::new));
register(LaserControl.id, new ScreenControlType<>(LaserControl.class, LaserControl::new));
register(ScreenModifyControl.id, new ScreenControlType<>(ScreenModifyControl.class, ScreenModifyControl::new));
register(ModifyFriendListControl.id, new ScreenControlType<>(ModifyFriendListControl.class, ModifyFriendListControl::new));
register(ManageRightsAndUpdgradesControl.id, new ScreenControlType<ManageRightsAndUpdgradesControl>(ManageRightsAndUpdgradesControl.class, (FriendlyByteBuf buf) -> new ManageRightsAndUpdgradesControl(buf)));
register(ClickControl.id, new ScreenControlType<>(ClickControl.class, ClickControl::new));
register(OwnerControl.id, new ScreenControlType<>(OwnerControl.class, OwnerControl::new));
register(TurnOffControl.id, new ScreenControlType<>(TurnOffControl.class, (buf) -> TurnOffControl.INSTANCE));
}
public static ScreenControl parse(FriendlyByteBuf buf) {
return CONTROL_TYPES.get(ResourceLocation.parse(buf.readUtf()))
.deserialize(buf);
}
public static void init() {
/* NO-OP: allows static init to run during mod init in dev env */
}
}

View File

@@ -0,0 +1,19 @@
package net.montoyo.wd.controls;
import net.minecraft.network.FriendlyByteBuf;
import java.util.function.Function;
public class ScreenControlType<T extends ScreenControl> {
Class<T> clazz;
Function<FriendlyByteBuf, T> deserializer;
public ScreenControlType(Class<T> clazz, Function<FriendlyByteBuf, T> deserializer) {
this.clazz = clazz;
this.deserializer = deserializer;
}
public ScreenControl deserialize(FriendlyByteBuf buf) {
return deserializer.apply(buf);
}
}

View File

@@ -0,0 +1,50 @@
package net.montoyo.wd.controls.builtin;
import net.minecraft.core.BlockPos;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.api.distmarker.OnlyIn;
import net.neoforged.neoforge.network.handling.IPayloadContext;
import net.minecraft.server.level.ServerPlayer;
import net.montoyo.wd.controls.ScreenControl;
import net.montoyo.wd.core.MissingPermissionException;
import net.montoyo.wd.core.ScreenRights;
import net.montoyo.wd.entity.ScreenBlockEntity;
import net.montoyo.wd.utilities.data.BlockSide;
import java.util.function.Function;
public class AutoVolumeControl extends ScreenControl {
public static final ResourceLocation id = ResourceLocation.fromNamespaceAndPath("webdisplays", "auto_volume");
boolean autoVol;
public AutoVolumeControl(boolean autoVol) {
super(id);
this.autoVol = autoVol;
}
public AutoVolumeControl(FriendlyByteBuf buf) {
super(id);
autoVol = buf.readBoolean();
}
@Override
public void write(FriendlyByteBuf buf) {
buf.writeBoolean(autoVol);
}
@Override
public void handleServer(BlockPos pos, BlockSide side, ScreenBlockEntity tes, IPayloadContext ctx, Function<Integer, Boolean> permissionChecker) throws MissingPermissionException {
// I feel like there's probably a better permission category
checkPerms(ScreenRights.MANAGE_UPGRADES, permissionChecker, (ServerPlayer) ctx.player());
tes.setAutoVolume(side, autoVol);
}
@Override
@OnlyIn(Dist.CLIENT)
public void handleClient(BlockPos pos, BlockSide side, ScreenBlockEntity tes, IPayloadContext ctx) {
tes.setAutoVolume(side, autoVol);
}
}

View File

@@ -0,0 +1,62 @@
package net.montoyo.wd.controls.builtin;
import net.minecraft.core.BlockPos;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.api.distmarker.OnlyIn;
import net.neoforged.neoforge.network.handling.IPayloadContext;
import net.montoyo.wd.controls.ScreenControl;
import net.montoyo.wd.core.MissingPermissionException;
import net.montoyo.wd.entity.ScreenBlockEntity;
import net.montoyo.wd.utilities.data.BlockSide;
import net.montoyo.wd.utilities.math.Vector2i;
import java.util.function.Function;
public class ClickControl extends ScreenControl {
public static final ResourceLocation id = ResourceLocation.fromNamespaceAndPath("webdisplays", "click");
public enum ControlType {
CLICK, MOVE, DOWN, UP
}
ControlType type;
Vector2i coord;
public ClickControl(ControlType type, Vector2i coord) {
this(type, coord, -1);
this.type = type;
}
public ClickControl(ControlType type, Vector2i coord, int button) {
super(id);
this.coord = coord;
}
public ClickControl(FriendlyByteBuf buf) {
super(id);
type = ControlType.values()[buf.readByte()];
coord = new Vector2i(buf);
}
@Override
public void write(FriendlyByteBuf buf) {
buf.writeByte(type.ordinal());
coord.writeTo(buf);
}
@Override
public void handleServer(BlockPos pos, BlockSide side, ScreenBlockEntity tes, IPayloadContext ctx, Function<Integer, Boolean> permissionChecker) throws MissingPermissionException {
throw new RuntimeException("Cannot call click control on server");
}
@Override
@OnlyIn(Dist.CLIENT)
public void handleClient(BlockPos pos, BlockSide side, ScreenBlockEntity tes, IPayloadContext ctx) {
if (coord != null)
tes.handleMouseEvent(side, ClickControl.ControlType.MOVE, coord, -1);
tes.handleMouseEvent(side, type, coord, 1);
}
}

View File

@@ -0,0 +1,62 @@
package net.montoyo.wd.controls.builtin;
import net.minecraft.core.BlockPos;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.api.distmarker.OnlyIn;
import net.neoforged.neoforge.network.handling.IPayloadContext;
import net.montoyo.wd.controls.ScreenControl;
import net.montoyo.wd.core.JSServerRequest;
import net.montoyo.wd.core.MissingPermissionException;
import net.montoyo.wd.entity.ScreenBlockEntity;
import net.montoyo.wd.utilities.data.BlockSide;
import java.util.function.Function;
public class JSRequestControl extends ScreenControl {
public static final ResourceLocation id = ResourceLocation.fromNamespaceAndPath("webdisplays", "js_req");
int reqId;
JSServerRequest reqType;
Object[] data;
public JSRequestControl(int reqId, JSServerRequest reqType, Object[] data) {
super(id);
this.reqId = reqId;
this.reqType = reqType;
this.data = data;
}
public JSRequestControl(FriendlyByteBuf buf) {
super(id);
reqId = buf.readInt();
reqType = JSServerRequest.fromID(buf.readByte());
if (reqType != null)
data = reqType.deserialize(buf);
}
@Override
public void write(FriendlyByteBuf buf) {
buf.writeInt(reqId);
buf.writeByte(reqType.ordinal());
if (!reqType.serialize(buf, data))
throw new RuntimeException("Could not serialize CTRL_JS_REQUEST " + reqType);
}
@Override
public void handleServer(BlockPos pos, BlockSide side, ScreenBlockEntity tes, IPayloadContext ctx, Function<Integer, Boolean> permissionChecker) throws MissingPermissionException {
ServerPlayer player = (ServerPlayer) ctx.player();
// if (reqType == null || data == null) Log.warning("Caught invalid JS request from player %s (UUID %s)", player.getName(), player.getGameProfile().getId().toString());
// else tes.handleJSRequest(player, side, reqId, reqType, data);
}
@Override
@OnlyIn(Dist.CLIENT)
public void handleClient(BlockPos pos, BlockSide side, ScreenBlockEntity tes, IPayloadContext ctx) {
throw new RuntimeException("TODO");
}
}

View File

@@ -0,0 +1,54 @@
package net.montoyo.wd.controls.builtin;
import net.minecraft.core.BlockPos;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.api.distmarker.OnlyIn;
import net.neoforged.neoforge.network.handling.IPayloadContext;
import net.montoyo.wd.controls.ScreenControl;
import net.montoyo.wd.core.MissingPermissionException;
import net.montoyo.wd.core.ScreenRights;
import net.montoyo.wd.entity.ScreenBlockEntity;
import net.montoyo.wd.utilities.data.BlockSide;
import java.util.function.Function;
public class KeyTypedControl extends ScreenControl {
public static final ResourceLocation id = ResourceLocation.fromNamespaceAndPath("webdisplays", "type");
String text;
BlockPos soundPos;
public KeyTypedControl(String text, BlockPos soundPos) {
super(id);
this.text = text;
this.soundPos = soundPos;
}
public KeyTypedControl(FriendlyByteBuf buf) {
super(id);
text = buf.readUtf();
soundPos = buf.readBlockPos();
}
@Override
public void write(FriendlyByteBuf buf) {
buf.writeUtf(text);
buf.writeBlockPos(soundPos);
}
@Override
public void handleServer(BlockPos pos, BlockSide side, ScreenBlockEntity tes, IPayloadContext ctx, Function<Integer, Boolean> permissionChecker) throws MissingPermissionException {
ServerPlayer player = (ServerPlayer) ctx.player();
checkPerms(ScreenRights.INTERACT, permissionChecker, player);
tes.type(side, text, soundPos, player);
}
@Override
@OnlyIn(Dist.CLIENT)
public void handleClient(BlockPos pos, BlockSide side, ScreenBlockEntity tes, IPayloadContext ctx) {
tes.type(side, text, soundPos);
}
}

View File

@@ -0,0 +1,79 @@
package net.montoyo.wd.controls.builtin;
import net.minecraft.core.BlockPos;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.api.distmarker.OnlyIn;
import net.neoforged.neoforge.network.handling.IPayloadContext;
import net.montoyo.wd.controls.ScreenControl;
import net.montoyo.wd.core.MissingPermissionException;
import net.montoyo.wd.entity.ScreenBlockEntity;
import net.montoyo.wd.utilities.data.BlockSide;
import net.montoyo.wd.utilities.math.Vector2i;
import java.util.function.Function;
public class LaserControl extends ScreenControl {
public static final ResourceLocation id = ResourceLocation.fromNamespaceAndPath("webdisplays", "laser");
public enum ControlType {
MOVE, DOWN, UP
}
ControlType type;
Vector2i coord;
int button;
public LaserControl(ControlType type, Vector2i coord) {
this(type, coord, -1);
}
public LaserControl(ControlType type, Vector2i coord, int button) {
super(id);
this.type = type;
this.coord = coord;
this.button = button;
}
public LaserControl(FriendlyByteBuf buf) {
super(id);
type = ControlType.values()[buf.readByte()];
if (!type.equals(ControlType.UP))
coord = new Vector2i(buf);
if (!type.equals(ControlType.MOVE))
button = buf.readInt();
}
@Override
public void write(FriendlyByteBuf buf) {
buf.writeByte(type.ordinal());
if (coord != null) coord.writeTo(buf);
if (type != ControlType.MOVE) buf.writeInt(button);
}
@Override
public void handleServer(BlockPos pos, BlockSide side, ScreenBlockEntity tes, IPayloadContext ctx, Function<Integer, Boolean> permissionChecker) throws MissingPermissionException {
// feel like this makes sense, but I wanna get opinions first
// checkPerms(ScreenRights.INTERACT, permissionChecker, (ServerPlayer) ctx.player());
ServerPlayer sender = (ServerPlayer) ctx.player();
switch (type) {
case UP -> tes.laserUp(side, sender, button);
case DOWN -> tes.laserDownMove(side, sender, coord, true, button);
case MOVE -> tes.laserDownMove(side, sender, coord, false, button);
}
}
@Override
@OnlyIn(Dist.CLIENT)
public void handleClient(BlockPos pos, BlockSide side, ScreenBlockEntity tes, IPayloadContext ctx) {
if (coord != null)
tes.handleMouseEvent(side, ClickControl.ControlType.MOVE, coord, -1);
switch (type) {
case UP -> tes.handleMouseEvent(side, ClickControl.ControlType.UP, coord, button);
case DOWN -> tes.handleMouseEvent(side, ClickControl.ControlType.DOWN, coord, button);
}
}
}

View File

@@ -0,0 +1,126 @@
package net.montoyo.wd.controls.builtin;
import net.minecraft.core.BlockPos;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.item.ItemStack;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.api.distmarker.OnlyIn;
import net.neoforged.neoforge.network.handling.IPayloadContext;
import net.montoyo.wd.controls.ScreenControl;
import net.montoyo.wd.core.MissingPermissionException;
import net.montoyo.wd.core.ScreenRights;
import net.montoyo.wd.entity.ScreenData;
import net.montoyo.wd.entity.ScreenBlockEntity;
import net.montoyo.wd.utilities.data.BlockSide;
import java.util.function.Function;
/**
* TODO: I'm considering merging this with {@link ModifyFriendListControl} to make ManageScreenControl
*/
@Deprecated
public class ManageRightsAndUpdgradesControl extends ScreenControl {
public static final ResourceLocation id = ResourceLocation.fromNamespaceAndPath("webdisplays", "mod_rights_upgrades");
public enum ControlType {
RIGHTS, UPGRADES
}
ControlType type;
boolean adding;
ItemStack toRemove;
private int friendRights;
private int otherRights;
public ManageRightsAndUpdgradesControl(boolean adding, ItemStack toRemove) {
super(id);
this.adding = adding;
type = ControlType.UPGRADES;
this.toRemove = toRemove;
}
public ManageRightsAndUpdgradesControl(int friendRights, int otherRights) {
super(id);
type = ControlType.RIGHTS;
this.friendRights = friendRights;
this.otherRights = otherRights;
}
public ManageRightsAndUpdgradesControl(FriendlyByteBuf buf) {
super(id);
type = ControlType.values()[buf.readByte()];
switch (type) {
case UPGRADES -> {
adding = buf.readBoolean();
toRemove = ItemStack.STREAM_CODEC.decode((RegistryFriendlyByteBuf) buf);
}
case RIGHTS -> {
friendRights = buf.readInt();
otherRights = buf.readInt();
}
}
}
@Override
public void write(FriendlyByteBuf buf) {
buf.writeByte(type.ordinal());
switch (type) {
case UPGRADES -> {
buf.writeBoolean(adding);
ItemStack.STREAM_CODEC.encode((RegistryFriendlyByteBuf) buf, toRemove);
}
case RIGHTS -> {
buf.writeInt(friendRights);
buf.writeInt(otherRights);
}
}
}
@Override
public void handleServer(BlockPos pos, BlockSide side, ScreenBlockEntity tes, IPayloadContext ctx, Function<Integer, Boolean> permissionChecker) throws MissingPermissionException {
ServerPlayer player = (ServerPlayer) ctx.player();
switch (type) {
case UPGRADES -> {
checkPerms(ScreenRights.MANAGE_UPGRADES, permissionChecker, player);
if (adding)
throw new RuntimeException("Cannot add an upgrade from the client");
else tes.removeUpgrade(side, toRemove, player);
}
case RIGHTS -> {
ScreenData scr = tes.getScreen(side);
int fr = scr.owner.uuid.equals(player.getGameProfile().getId()) ? friendRights : scr.friendRights;
int or = (scr.rightsFor(player) & ScreenRights.MANAGE_OTHER_RIGHTS) == 0 ? scr.otherRights : otherRights;
if(scr.friendRights != fr || scr.otherRights != or)
tes.setRights(player, side, fr, or);
}
}
}
@Override
@OnlyIn(Dist.CLIENT)
public void handleClient(BlockPos pos, BlockSide side, ScreenBlockEntity tes, IPayloadContext ctx) {
ServerPlayer player = (ServerPlayer) ctx.player();
switch (type) {
case UPGRADES -> {
if (adding)
tes.addUpgrade(side, toRemove, player, true);
else tes.removeUpgrade(side, toRemove, player);
}
case RIGHTS -> {
ScreenData scr = tes.getScreen(side);
int fr = friendRights;
int or = otherRights;
if(scr.friendRights != fr || scr.otherRights != or)
tes.setRights(player, side, fr, or);
}
}
}
}

View File

@@ -0,0 +1,56 @@
package net.montoyo.wd.controls.builtin;
import net.minecraft.core.BlockPos;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.api.distmarker.OnlyIn;
import net.neoforged.neoforge.network.handling.IPayloadContext;
import net.montoyo.wd.controls.ScreenControl;
import net.montoyo.wd.core.MissingPermissionException;
import net.montoyo.wd.core.ScreenRights;
import net.montoyo.wd.entity.ScreenBlockEntity;
import net.montoyo.wd.utilities.data.BlockSide;
import net.montoyo.wd.utilities.serialization.NameUUIDPair;
import java.util.function.Function;
public class ModifyFriendListControl extends ScreenControl {
public static final ResourceLocation id = ResourceLocation.fromNamespaceAndPath("webdisplays", "mod_friend_list");
boolean adding;
NameUUIDPair friend;
public ModifyFriendListControl(NameUUIDPair pair, boolean adding) {
super(id);
this.adding = adding;
this.friend = pair;
}
public ModifyFriendListControl(FriendlyByteBuf buf) {
super(id);
adding = buf.readBoolean();
friend = new NameUUIDPair(buf);
}
@Override
public void write(FriendlyByteBuf buf) {
buf.writeBoolean(adding);
friend.writeTo(buf);
}
@Override
public void handleServer(BlockPos pos, BlockSide side, ScreenBlockEntity tes, IPayloadContext ctx, Function<Integer, Boolean> permissionChecker) throws MissingPermissionException {
ServerPlayer player = (ServerPlayer) ctx.player();
checkPerms(ScreenRights.MANAGE_FRIEND_LIST, permissionChecker, player);
if (adding) tes.addFriend(player, side, friend);
else tes.removeFriend(player, side, friend);
}
@Override
@OnlyIn(Dist.CLIENT)
public void handleClient(BlockPos pos, BlockSide side, ScreenBlockEntity tes, IPayloadContext ctx) {
throw new RuntimeException("TODO");
}
}

View File

@@ -0,0 +1,47 @@
package net.montoyo.wd.controls.builtin;
import net.minecraft.core.BlockPos;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.api.distmarker.OnlyIn;
import net.neoforged.neoforge.network.handling.IPayloadContext;
import net.montoyo.wd.controls.ScreenControl;
import net.montoyo.wd.core.MissingPermissionException;
import net.montoyo.wd.entity.ScreenBlockEntity;
import net.montoyo.wd.utilities.data.BlockSide;
import net.montoyo.wd.utilities.serialization.NameUUIDPair;
import java.util.function.Function;
public class OwnerControl extends ScreenControl {
public static final ResourceLocation id = ResourceLocation.fromNamespaceAndPath("webdisplays", "set_owner");
NameUUIDPair owner;
public OwnerControl(NameUUIDPair pair) {
super(id);
this.owner = pair;
}
public OwnerControl(FriendlyByteBuf buf) {
super(id);
owner = new NameUUIDPair(buf);
}
@Override
public void write(FriendlyByteBuf buf) {
owner.writeTo(buf);
}
@Override
public void handleServer(BlockPos pos, BlockSide side, ScreenBlockEntity tes, IPayloadContext ctx, Function<Integer, Boolean> permissionChecker) throws MissingPermissionException {
throw new RuntimeException("Cannot handle ownership theft packet from server");
}
@Override
@OnlyIn(Dist.CLIENT)
public void handleClient(BlockPos pos, BlockSide side, ScreenBlockEntity tes, IPayloadContext ctx) {
tes.getScreen(side).owner = owner;
}
}

View File

@@ -0,0 +1,75 @@
package net.montoyo.wd.controls.builtin;
import net.minecraft.core.BlockPos;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.api.distmarker.OnlyIn;
import net.neoforged.neoforge.network.handling.IPayloadContext;
import net.montoyo.wd.controls.ScreenControl;
import net.montoyo.wd.core.MissingPermissionException;
import net.montoyo.wd.core.ScreenRights;
import net.montoyo.wd.entity.ScreenBlockEntity;
import net.montoyo.wd.utilities.data.BlockSide;
import net.montoyo.wd.utilities.data.Rotation;
import net.montoyo.wd.utilities.math.Vector2i;
import java.util.function.Function;
public class ScreenModifyControl extends ScreenControl {
public static final ResourceLocation id = ResourceLocation.fromNamespaceAndPath("webdisplays", "mod_screen");
public enum ControlType {
RESOLUTION, ROTATION
}
ControlType type;
Vector2i res;
Rotation rotation;
public ScreenModifyControl(Vector2i res) {
super(id);
this.type = ControlType.RESOLUTION;
this.res = res;
}
public ScreenModifyControl(Rotation rotation) {
super(id);
this.type = ControlType.ROTATION;
this.rotation = rotation;
}
public ScreenModifyControl(FriendlyByteBuf buf) {
super(id);
type = ControlType.values()[buf.readByte()];
if (type.equals(ControlType.RESOLUTION))
res = new Vector2i(buf);
else rotation = Rotation.values()[buf.readByte()];
}
@Override
public void write(FriendlyByteBuf buf) {
buf.writeByte(type.ordinal());
if (res != null) res.writeTo(buf);
else if (rotation != null) buf.writeByte(rotation.ordinal());
}
@Override
public void handleServer(BlockPos pos, BlockSide side, ScreenBlockEntity tes, IPayloadContext ctx, Function<Integer, Boolean> permissionChecker) throws MissingPermissionException {
checkPerms(ScreenRights.MODIFY_SCREEN, permissionChecker, (ServerPlayer) ctx.player());
switch (type) {
case RESOLUTION -> tes.setResolution(side, res);
case ROTATION -> tes.setRotation(side, rotation);
}
}
@Override
@OnlyIn(Dist.CLIENT)
public void handleClient(BlockPos pos, BlockSide side, ScreenBlockEntity tes, IPayloadContext ctx) {
switch (type) {
case RESOLUTION -> tes.setResolution(side, res);
case ROTATION -> tes.setRotation(side, rotation);
}
}
}

View File

@@ -0,0 +1,64 @@
package net.montoyo.wd.controls.builtin;
import net.minecraft.core.BlockPos;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.api.distmarker.OnlyIn;
import net.neoforged.neoforge.network.handling.IPayloadContext;
import net.montoyo.wd.controls.ScreenControl;
import net.montoyo.wd.core.MissingPermissionException;
import net.montoyo.wd.core.ScreenRights;
import net.montoyo.wd.entity.ScreenBlockEntity;
import net.montoyo.wd.utilities.data.BlockSide;
import net.montoyo.wd.utilities.math.Vector3i;
import java.util.function.Function;
public class SetURLControl extends ScreenControl {
public static final ResourceLocation id = ResourceLocation.fromNamespaceAndPath("webdisplays", "set_url");
String url;
Vector3i remoteLocation;
public SetURLControl(String url, Vector3i remoteLocation) {
super(id);
this.url = url;
this.remoteLocation = remoteLocation;
}
public SetURLControl(FriendlyByteBuf buf) {
super(id);
url = buf.readUtf();
if (buf.readBoolean()) remoteLocation = new Vector3i(buf);
}
@Override
public void write(FriendlyByteBuf buf) {
buf.writeUtf(url);
buf.writeBoolean(remoteLocation != null);
if (remoteLocation != null) remoteLocation.writeTo(buf);
}
@Override
public void handleServer(BlockPos pos, BlockSide side, ScreenBlockEntity tes, IPayloadContext ctx, Function<Integer, Boolean> permissionChecker) throws MissingPermissionException {
// TODO: deal with remote
checkPerms(ScreenRights.CHANGE_URL, permissionChecker, (ServerPlayer) ctx.player());
try {
tes.setScreenURL(side, url);
} catch (Throwable err) {
err.printStackTrace();
}
}
@Override
@OnlyIn(Dist.CLIENT)
public void handleClient(BlockPos pos, BlockSide side, ScreenBlockEntity tes, IPayloadContext ctx) {
try {
tes.setScreenURL(side, url);
} catch (Throwable err) {
err.printStackTrace();
}
}
}

View File

@@ -0,0 +1,48 @@
package net.montoyo.wd.controls.builtin;
import net.minecraft.core.BlockPos;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.neoforged.api.distmarker.Dist;
import net.neoforged.api.distmarker.OnlyIn;
import net.neoforged.neoforge.network.handling.IPayloadContext;
import net.montoyo.wd.WebDisplays;
import net.montoyo.wd.controls.ScreenControl;
import net.montoyo.wd.core.MissingPermissionException;
import net.montoyo.wd.entity.ScreenBlockEntity;
import net.montoyo.wd.utilities.data.BlockSide;
import java.util.function.Function;
public class TurnOffControl extends ScreenControl {
public static final ResourceLocation id = ResourceLocation.fromNamespaceAndPath("webdisplays", "deactivate");
public static final TurnOffControl INSTANCE = new TurnOffControl();
public TurnOffControl() {
super(id);
}
@Override
public void write(FriendlyByteBuf buf) {
}
@Override
public void handleServer(BlockPos pos, BlockSide side, ScreenBlockEntity tes, IPayloadContext ctx, Function<Integer, Boolean> permissionChecker) throws MissingPermissionException {
throw new RuntimeException("Cannot handle deactivation packet from server");
}
@Override
@OnlyIn(Dist.CLIENT)
public void handleClient(BlockPos pos, BlockSide side, ScreenBlockEntity tes, IPayloadContext ctx) {
if (side != null) {
WebDisplays.PROXY.closeGui(pos, side);
tes.disableScreen(side);
} else {
for (BlockSide value : BlockSide.values()) {
WebDisplays.PROXY.closeGui(pos, value);
tes.disableScreen(value);
}
}
}
}

View File

@@ -0,0 +1,91 @@
/*
* Copyright (C) 2019 BARBOTIN Nicolas
*/
package net.montoyo.wd.core;
import java.util.List;
import java.util.Map;
// TODO: bring this back when SSR is implemented
public class CCArguments implements IComputerArgs {
private final Object[] args;
public CCArguments(Object[] args) {
this.args = args;
}
@Override
public String checkString(int i) {
checkIndex(i, "string");
Object obj = args[i];
if(!(obj instanceof String))
throw typeError(i, "string", obj);
return (String) obj;
}
@Override
public int checkInteger(int i) {
checkIndex(i, "number");
Object obj = args[i];
int ret;
if(obj instanceof Integer)
ret = (int) obj;
else if(obj instanceof Double)
ret = ((Double) obj).intValue();
else if(obj instanceof Float)
ret = ((Float) obj).intValue();
else
throw typeError(i, "number", obj);
return ret;
}
@Override
public Map checkTable(int i) {
checkIndex(i, "table");
Object obj = args[i];
if(!(obj instanceof Map))
throw typeError(i, "table", args[i]);
return (Map) obj;
}
private void checkIndex(int idx, String want) {
if(idx < 0 || idx >= args.length)
typeError(idx, want, null);
}
private static IllegalArgumentException typeError(int idx, String want, Object got) {
return new IllegalArgumentException("bad argument #" + (idx + 1) + " (" + want + " expected, got " + luaTypeName(got) + ")");
}
private static String luaTypeName(Object obj) {
if(obj == null)
return "nil";
Class<?> cls = obj.getClass();
if(cls == Boolean.class || cls == Boolean.TYPE)
return "boolean";
else if(cls == Integer.class || cls == Integer.TYPE || cls == Double.class || cls == Double.TYPE || cls == Float.class || cls == Float.TYPE)
return "number";
else if(cls == String.class)
return "string";
else if(Map.class.isAssignableFrom(cls))
return "table";
else
return cls.getSimpleName();
}
@Override
public int count() {
return args.length;
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright (C) 2019 BARBOTIN Nicolas
*/
package net.montoyo.wd.core;
/*
import net.montoyo.wd.entity.TileEntityCCInterface;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@Optional.Interface(iface = "dan200.computercraft.api.peripheral.IPeripheralProvider", modid = "computercraft")
public class CCPeripheralProvider implements IPeripheralProvider {
private CCPeripheralProvider() {
}
@Optional.Method(modid = "computercraft")
@Nullable
@Override
public IPeripheral getPeripheral(@Nonnull World world, @Nonnull BlockPos pos, @Nonnull EnumFacing f) {
TileEntity te = world.getTileEntity(pos);
return (te instanceof TileEntityCCInterface) ? ((TileEntityCCInterface) te) : null;
}
@Optional.Method(modid = "computercraft")
public static void register() {
ComputerCraftAPI.registerPeripheralProvider(new CCPeripheralProvider());
}
}
*/

View File

@@ -0,0 +1,37 @@
/*
* Copyright (C) 2018 BARBOTIN Nicolas
*/
package net.montoyo.wd.core;
import net.minecraft.world.item.ItemStack;
import net.montoyo.wd.registry.ItemRegistry;
public enum CraftComponent {
STONEKEY("stonekey", "StoneKey"),
UPGRADE("upgrade", "Upgrade"),
PERIPHERAL("peripheral", "Peripheral"),
BATCELL("batcell", "BatCell"),
BATPACK("batpack", "BatPack"),
LASERDIODE("laserdiode", "LaserDiode"),
BACKLIGHT("backlight", "Backlight"),
EXTCARD("extcard", "ExtCard"),
BADEXTCARD("badextcard", "BadExtCard");
private final String name;
private final String wikiName;
CraftComponent(String n, String wikiName) {
name = n;
this.wikiName = wikiName;
}
@Override
public String toString() {
return name;
}
public ItemStack makeItemStack() {
return new ItemStack(ItemRegistry.getComputerCraftItem(ordinal()).get(), 1);
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright (C) 2019 BARBOTIN Nicolas
*/
package net.montoyo.wd.core;
import net.minecraft.util.StringRepresentable;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.montoyo.wd.entity.KeyboardBlockEntity;
import net.montoyo.wd.entity.RemoteControlBlockEntity;
import net.montoyo.wd.entity.RedstoneControlBlockEntity;
import net.montoyo.wd.entity.ServerBlockEntity;
import net.montoyo.wd.registry.BlockRegistry;
import org.jetbrains.annotations.NotNull;
import java.util.function.Supplier;
public enum DefaultPeripheral implements StringRepresentable {
KEYBOARD("keyboard", "Keyboard", KeyboardBlockEntity::new, BlockRegistry.KEYBOARD_BLOCK), //WITH FACING (< 3)
// CC_INTERFACE("ccinterface", "ComputerCraft_Interface", TileEntityCCInterface.class),
// OC_INTERFACE("cointerface", "OpenComputers_Interface", TileEntityOCInterface.class),
REMOTE_CONTROLLER("remotectrl", "Remote_Controller", RemoteControlBlockEntity::new , BlockRegistry.REMOTE_CONTROLLER_BLOCK), //WITHOUT FACING (>= 3)
REDSTONE_CONTROLLER("redstonectrl", "Redstone_Controller", RedstoneControlBlockEntity::new , BlockRegistry.REDSTONE_CONTROL_BLOCK),
SERVER("server", "Server", ServerBlockEntity::new, BlockRegistry.SERVER_BLOCK);
private final String name;
private final String wikiName;
private final BlockEntityType.BlockEntitySupplier<? extends BlockEntity> teClass;
private final Supplier<? extends Block> bClass;
DefaultPeripheral(String name, String wname, BlockEntityType.BlockEntitySupplier<? extends BlockEntity> factory, Supplier<? extends Block> supplier) {
this.name = name;
wikiName = wname;
teClass = factory;
bClass = supplier;
}
public Supplier<? extends Block> getBlockClass() {
return bClass;
}
public static DefaultPeripheral fromMetadata(int meta) {
if((meta & 3) == 3)
return values()[(((meta >> 2) & 3) | 4) - 1]; //Without facing
else
return values()[meta & 3]; //With facing
}
public BlockEntityType.BlockEntitySupplier<? extends BlockEntity> getTEClass() {
return teClass;
}
public boolean hasFacing() {
return ordinal() < 3;
}
public int toMetadata(int facing) {
int ret = ordinal();
if(ret < 3) //With facing
ret |= facing << 2;
else //Without facing
ret = (((ret + 1) & 3) << 2) | 3;
return ret;
}
@Override
public @NotNull String getSerializedName() {
return "default_peripheral_" + name;
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright (C) 2018 BARBOTIN Nicolas
*/
package net.montoyo.wd.core;
import net.minecraft.world.item.ItemStack;
import net.montoyo.wd.item.ItemUpgrade;
public enum DefaultUpgrade {
LASERMOUSE("lasermouse", "LaserMouse"),
REDINPUT("redinput", "RedInput"),
REDOUTPUT("redoutput", "RedOutput"),
GPS("gps", "GPS");
public final String name;
public final String wikiName;
DefaultUpgrade(String n, String wn) {
name = n;
wikiName = wn;
}
@Override
public String toString() {
return name;
}
protected static boolean matches(ItemStack stack, DefaultUpgrade upgrade) {
if (stack.getItem() instanceof ItemUpgrade upgrade1)
return upgrade1.type == upgrade;
return false;
}
public boolean matchesLaserMouse(ItemStack is) {
return matches(is, LASERMOUSE);
}
public boolean matchesRedInput(ItemStack is) {
return matches(is, REDINPUT);
}
public boolean matchesRedOutput(ItemStack is) {
return matches(is, REDOUTPUT);
}
public boolean matchesGps(ItemStack is) {
return matches(is, GPS);
}
}

View File

@@ -0,0 +1,11 @@
/*
* Copyright (C) 2018 BARBOTIN Nicolas
*/
package net.montoyo.wd.core;
public enum HasAdvancement {
DONT_KNOW,
YES,
NO
}

View File

@@ -0,0 +1,14 @@
/*
* Copyright (C) 2019 BARBOTIN Nicolas
*/
package net.montoyo.wd.core;
import java.util.Map;
public interface IComputerArgs {
String checkString(int i);
int checkInteger(int i);
Map checkTable(int i);
int count();
}

View File

@@ -0,0 +1,15 @@
/*
* Copyright (C) 2018 BARBOTIN Nicolas
*/
package net.montoyo.wd.core;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
import net.montoyo.wd.utilities.data.BlockSide;
import net.montoyo.wd.utilities.math.Vector3i;
public interface IPeripheral {
boolean connect(Level world, BlockPos blockPos, BlockState blockState, Vector3i screenPos, BlockSide screenSide);
}

Some files were not shown because too many files have changed in this diff Show More