Compare commits

...
17 Commits
Author SHA1 Message Date
Worive fd7d5c269f Bound compressed packet decoding (#58) 2026-11-13 18:20:48 +01:00
UltraProdigy 89dac36ebc ci: run build and test on release branches 2026-11-09 02:19:02 -05:00
Nikolay S. 18bac36c10 Deprecate index-based field selection (#52) 2026-11-01 14:15:34 +01:00
Jarnexis a9643dba1c Add initial Ukrainian (uk_UA) localization for CodeChickenCore (#55) 2026-09-19 20:15:54 +02:00
Martin Robertz 31d26db5b4 update+gradle+bs+deps+bs+gitignore
fix curse auto uploader
2026-09-17 22:32:16 +02:00
Algent 2077a8da86 Fix missing colour tint on block particles (#57) 2026-09-14 10:51:15 +02:00
Ryan Nasers ba757e4237 Fixing silly read write issue (#56) 2026-09-02 16:59:45 +02:00
DrParadox7 b738e8e073 NPE check on onChunkUnload (#51) 2026-08-21 17:39:04 +02:00
Jason Mitchell a385d29ab3 Additional thread saftey for CCL (#50) 2026-07-22 15:28:54 -07:00
Jason Mitchell 60d44f3c7b Skip MR class files (#49) 2026-06-16 10:35:36 -07:00
Ranzu 6209831d08 Revert #47 (#48) 2026-06-07 19:36:40 +00:00
Ranzu 7ae8b15810 Convert colors to ColorUtils (#47) 2026-06-07 19:17:10 +02:00
Alexdoru 08f3e4d42a intern superclass and interfaces of ClassHeirachyManager data (#46) 2026-05-24 15:19:20 +02:00
Martin Robertz 9bd4c63b00 update gradle+deps+bs 2026-03-10 19:37:26 +01:00
Ranzu 6e90633d31 Added color override helper (#43) 2026-03-10 13:59:10 +01:00
danyadevandAlexdoru f02dbc956c optimize ClassHeirachyManager (#42)
Co-authored-by: Alexdoru <[email protected]>
2026-03-05 17:41:52 +00:00
boubou19 63f36ca383 Reapply "Fix ItemStack packet id limited by short" (#23) (#37) (#41) 2025-12-07 16:17:57 +00:00
33 changed files with 852 additions and 137 deletions

No files matched your search

+2 -2
View File
@@ -3,9 +3,9 @@ name: Build and test
on: on:
pull_request: pull_request:
branches: [ master, main ] branches: [ master, main, release/** ]
push: push:
branches: [ master, main ] branches: [ master, main, release/** ]
jobs: jobs:
build-and-test: build-and-test:
+3
View File
@@ -27,3 +27,6 @@ whitelist.json
*.iws *.iws
src/main/resources/mixins.*.json src/main/resources/mixins.*.json
*.bat *.bat
.DS_Store
*.sh
+7
View File
@@ -6,3 +6,10 @@ minecraft {
} }
} }
} }
test {
useJUnitPlatform()
testLogging {
events "passed", "skipped", "failed"
}
}
+3
View File
@@ -1,4 +1,7 @@
// Add your dependencies here // Add your dependencies here
dependencies { dependencies {
testImplementation(platform('org.junit:junit-bom:5.14.1'))
testImplementation('org.junit.jupiter:junit-jupiter')
testRuntimeOnly('org.junit.platform:junit-platform-launcher')
} }
+35 -3
View File
@@ -39,9 +39,33 @@ remoteMappings = https\://raw.githubusercontent.com/MinecraftForge/FML/1.7.10/co
# `./gradlew runClient --username=AnotherPlayer`, or configuring this command in your IDE. # `./gradlew runClient --username=AnotherPlayer`, or configuring this command in your IDE.
developmentEnvironmentUserName = Developer developmentEnvironmentUserName = Developer
# Enables using modern Java syntax (up to version 17) via Jabel, while still targeting JVM 8. # Enables modern Java syntax support. Valid values:
# See https://github.com/bsideup/jabel for details on how this works. # - false: No modern syntax, Java 8 only
enableModernJavaSyntax = true # - jabel: Jabel syntax-only support, compiles to J8 bytecode
# - jvmDowngrader: Full modern Java via JVM Downgrader (syntax + stdlib APIs)
# - modern: Native modern Java bytecode, no downgrading
enableModernJavaSyntax = jabel
# If set, ignores the above setting and compiles with the given toolchain. This may cause unexpected issues,
# and should *not* be used in most situations. -1 disables this.
# forceToolchainVersion = -1
# Target JVM version for JVM Downgrader bytecode downgrading.
# Only used when enableModernJavaSyntax = jvmDowngrader
# downgradeTargetVersion = 8
# Comma-separated list of Java versions for multi-release jar support (JVM Downgrader only).
# Classes will be available in META-INF/versions/N/ for each version N in this list.
# Default: "21,25" (J25+ gets native classes, J21-24 gets partial downgrade, J8-20 gets full downgrade).
# jvmDowngraderMultiReleaseVersions = 21,25
# Specifies how JVM Downgrader API stubs are provided. Options:
# - shade: Shade minimized stubs into the jar
# - gtnhlib: GTNHLib provides stubs at runtime (adds version constraint)
# - external: Another dependency provides stubs (no constraint, no warning)
# - (empty): Warning reminding you to configure stubs
# Note: 'shade' option requires you to verify license compliance, see: https://github.com/unimined/JvmDowngrader/blob/main/LICENSE.md
# jvmDowngraderStubsProvider =
# Enables injecting missing generics into the decompiled source code for a better coding experience. # Enables injecting missing generics into the decompiled source code for a better coding experience.
# Turns most publicly visible List, Map, etc. into proper List<E>, Map<K, V> types. # Turns most publicly visible List, Map, etc. into proper List<E>, Map<K, V> types.
@@ -156,6 +180,11 @@ modrinthRelations =
# Leave this empty if you don't want to publish on CurseForge. # Leave this empty if you don't want to publish on CurseForge.
curseForgeProjectId = 746279 curseForgeProjectId = 746279
# Comma-separated supported CurseForge environments: client, server (case-insensitive).
# Use client or server for mods that only support one environment. Defaults to client,server.
# These tags describe where the mod can run, not whether installation is required on both sides.
curseForgeEnvironments = client,server
# The project's relations on CurseForge. You can use this to refer to other projects on CurseForge. # The project's relations on CurseForge. You can use this to refer to other projects on CurseForge.
# Syntax: type1:name1;type2:name2;... # Syntax: type1:name1;type2:name2;...
# Where type can be one of [requiredDependency, embeddedLibrary, optionalDependency, tool, incompatible], # Where type can be one of [requiredDependency, embeddedLibrary, optionalDependency, tool, incompatible],
@@ -203,3 +232,6 @@ curseForgeRelations =
# This is meant to be set in $HOME/.gradle/gradle.properties. # This is meant to be set in $HOME/.gradle/gradle.properties.
# ideaCheckSpotlessOnBuild = true # ideaCheckSpotlessOnBuild = true
# Non-GTNH properties
org.gradle.configuration-cache = true
org.gradle.parallel = true
+12
View File
@@ -0,0 +1,12 @@
#This file is generated by updateDaemonJvm
toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/539ea72837c6244075f8ee037babc29e/redirect
toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/876dbbf00bf4ef6190f7da074cee903a/redirect
toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/539ea72837c6244075f8ee037babc29e/redirect
toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/876dbbf00bf4ef6190f7da074cee903a/redirect
toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/9064f22734b42ac03e85df04b632e167/redirect
toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/4d0277c49991306ac78048580fe34bcf/redirect
toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/539ea72837c6244075f8ee037babc29e/redirect
toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/876dbbf00bf4ef6190f7da074cee903a/redirect
toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/a9fa3b809e484a1e1ac4308a5dda661a/redirect
toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/aa99913fa6d767d899ffc96082da01ca/redirect
toolchainVersion=25
Binary file not shown.
+3 -1
View File
@@ -1,7 +1,9 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip
networkTimeout=10000 networkTimeout=10000
retries=0
retryBackOffMs=500
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
Vendored
+5 -8
View File
@@ -1,7 +1,7 @@
#!/bin/sh #!/bin/sh
# #
# Copyright © 2015-2021 the original authors. # Copyright © 2015 the original authors.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@@ -20,7 +20,7 @@
############################################################################## ##############################################################################
# #
# Gradle start up script for POSIX generated by Gradle. # gradlew start up script for POSIX generated by Gradle.
# #
# Important for running: # Important for running:
# #
@@ -29,7 +29,7 @@
# bash, then to run this script, type that shell name before the whole # bash, then to run this script, type that shell name before the whole
# command line, like: # command line, like:
# #
# ksh Gradle # ksh gradlew
# #
# Busybox and similar reduced shells will NOT work, because this script # Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features: # requires all of these POSIX shell features:
@@ -57,7 +57,7 @@
# Darwin, MinGW, and NonStop. # Darwin, MinGW, and NonStop.
# #
# (3) This script is generated from the Groovy template # (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project. # within the Gradle project.
# #
# You can find Gradle at https://github.com/gradle/gradle/. # You can find Gradle at https://github.com/gradle/gradle/.
@@ -114,7 +114,6 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;; NONSTOP* ) nonstop=true ;;
esac esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM. # Determine the Java command to use to start the JVM.
@@ -172,7 +171,6 @@ fi
# For Cygwin or MSYS, switch paths to Windows format before running java # For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" ) JAVACMD=$( cygpath --unix "$JAVACMD" )
@@ -212,8 +210,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
set -- \ set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \ "-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
org.gradle.wrapper.GradleWrapperMain \
"$@" "$@"
# Stop when "xargs" is not available. # Stop when "xargs" is not available.
Vendored
+12 -24
View File
@@ -19,12 +19,12 @@
@if "%DEBUG%"=="" @echo off @if "%DEBUG%"=="" @echo off
@rem ########################################################################## @rem ##########################################################################
@rem @rem
@rem Gradle startup script for Windows @rem gradlew startup script for Windows
@rem @rem
@rem ########################################################################## @rem ##########################################################################
@rem Set local scope for the variables with windows NT shell @rem Set local scope for the variables, and ensure extensions are enabled
if "%OS%"=="Windows_NT" setlocal setlocal EnableExtensions
set DIRNAME=%~dp0 set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=. if "%DIRNAME%"=="" set DIRNAME=.
@@ -51,7 +51,7 @@ echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2 echo location of your Java installation. 1>&2
goto fail "%COMSPEC%" /c exit 1
:findJavaFromJavaHome :findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=% set JAVA_HOME=%JAVA_HOME:"=%
@@ -65,30 +65,18 @@ echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2 echo location of your Java installation. 1>&2
goto fail "%COMSPEC%" /c exit 1
:execute :execute
@rem Setup the command line @rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle @rem Execute gradlew
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* @rem endlocal doesn't take effect until after the line is parsed and variables are expanded
@rem which allows us to clear the local environment before executing the java command
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
:end :exitWithErrorLevel
@rem End local scope for the variables with windows NT shell @rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
if %ERRORLEVEL% equ 0 goto mainEnd "%COMSPEC%" /c exit %ERRORLEVEL%
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
+1 -1
View File
@@ -17,7 +17,7 @@ pluginManagement {
} }
plugins { plugins {
id 'com.gtnewhorizons.gtnhsettingsconvention' version '1.0.48' id 'com.gtnewhorizons.gtnhsettingsconvention' version '2.0.30'
} }
@@ -116,6 +116,7 @@ public class ClassDiscoverer {
while (zipEntries.hasMoreElements()) { while (zipEntries.hasMoreElements()) {
ZipEntry zipentry = zipEntries.nextElement(); ZipEntry zipentry = zipEntries.nextElement();
String fullname = zipentry.getName().replace('\\', '/'); String fullname = zipentry.getName().replace('\\', '/');
if (fullname.startsWith("META-INF/versions/")) continue;
int pos = fullname.lastIndexOf('/'); int pos = fullname.lastIndexOf('/');
String name = pos == -1 ? fullname : fullname.substring(pos + 1); String name = pos == -1 ? fullname : fullname.substring(pos + 1);
if (!zipentry.isDirectory() && matcher.matches(name)) { if (!zipentry.isDirectory() && matcher.matches(name)) {
@@ -2,12 +2,16 @@ package codechicken.core;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiScreen; import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.resources.IReloadableResourceManager;
import net.minecraft.client.resources.IResourceManager;
import net.minecraft.client.resources.IResourceManagerReloadListener;
import net.minecraft.network.NetworkManager; import net.minecraft.network.NetworkManager;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.util.EnumChatFormatting; import net.minecraft.util.EnumChatFormatting;
import net.minecraft.world.World; import net.minecraft.world.World;
import codechicken.core.internal.CCCEventHandler; import codechicken.core.internal.CCCEventHandler;
import codechicken.lib.colour.LocalizedColours;
import cpw.mods.fml.common.FMLCommonHandler; import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.ModContainer; import cpw.mods.fml.common.ModContainer;
import cpw.mods.fml.relauncher.Side; import cpw.mods.fml.relauncher.Side;
@@ -65,4 +69,16 @@ public class ClientUtils extends CommonUtils {
.replace("Supporters:", EnumChatFormatting.AQUA + "Supporters:"); .replace("Supporters:", EnumChatFormatting.AQUA + "Supporters:");
GuiModListScroll.register(mod); GuiModListScroll.register(mod);
} }
@SideOnly(Side.CLIENT)
public static void registerLocalizedColourReloadListener() {
((IReloadableResourceManager) mc().getResourceManager())
.registerReloadListener(new IResourceManagerReloadListener() {
@Override
public void onResourceManagerReload(IResourceManager resourceManager) {
LocalizedColours.reloadLocalizedColours();
}
});
}
} }
@@ -36,6 +36,7 @@ import org.lwjgl.input.Keyboard;
import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL11;
import codechicken.core.launch.CodeChickenCorePlugin; import codechicken.core.launch.CodeChickenCorePlugin;
import codechicken.lib.colour.LocalizedColours;
import codechicken.lib.gui.GuiDraw; import codechicken.lib.gui.GuiDraw;
import codechicken.lib.vec.Rectangle4i; import codechicken.lib.vec.Rectangle4i;
import cpw.mods.fml.client.GuiModList; import cpw.mods.fml.client.GuiModList;
@@ -148,10 +149,11 @@ public class GuiModListScroll {
double dy = scroll % (height + 20); double dy = scroll % (height + 20);
GL11.glPushMatrix(); GL11.glPushMatrix();
GL11.glTranslated(0, -dy, 0); GL11.glTranslated(0, -dy, 0);
GuiDraw.fontRenderer.drawSplitString(description, x1, y1draw, x2 - x1, 0xDDDDDD); GuiDraw.fontRenderer.drawSplitString(description, x1, y1draw, x2 - x1, LocalizedColours.MOD_DESCRIPTION_TEXT);
if (needsScroll) { if (needsScroll) {
GL11.glTranslated(0, height + 20, 0); GL11.glTranslated(0, height + 20, 0);
GuiDraw.fontRenderer.drawSplitString(description, x1, y1draw, x2 - x1, 0xDDDDDD); GuiDraw.fontRenderer
.drawSplitString(description, x1, y1draw, x2 - x1, LocalizedColours.MOD_DESCRIPTION_TEXT);
} }
GL11.glPopMatrix(); GL11.glPopMatrix();
@@ -89,6 +89,22 @@ public class ReflectionManager {
} }
} }
/**
* Set a field of the object {@code instance} of type {@code class1} to {@code value}.
*
* @deprecated According to {@link Class#getDeclaredFields()}, the array of fields has no defined order, therefore
* selecting a field based on its index will result in JVM-specific behaviour. Use
* {@link #setField(Class, Object, String, Object)} instead.
*
* @param class1 The class which defines the field in question.
* @param instance The object being altered - must be an instance of {@code class1}
* @param fieldindex The index of the field being set.
* @param value The value to set the field to.
* @throws IllegalArgumentException If {@code instance} is not an instance of {@code class1}, or if {@code value}
* doesn't match the type of the field.
* @throws IllegalAccessException If unable to write to the field.
*/
@Deprecated
public static void setField(Class<?> class1, Object instance, int fieldindex, Object value) public static void setField(Class<?> class1, Object instance, int fieldindex, Object value)
throws IllegalArgumentException, IllegalAccessException { throws IllegalArgumentException, IllegalAccessException {
Field field = class1.getDeclaredFields()[fieldindex]; Field field = class1.getDeclaredFields()[fieldindex];
@@ -179,6 +195,21 @@ public class ReflectionManager {
return null; return null;
} }
/**
* Get a field of the object {@code instance} of type {@code class1}.
*
* @deprecated According to {@link Class#getDeclaredFields()}, the array of fields has no defined order, therefore
* selecting a field based on its index will result in JVM-specific behaviour. Use
* {@link #getField(Class, Class, Object, String)} instead.
*
* @param class1 The class which defines the field in question.
* @param fieldType The type of the field being read.
* @param instance The object being read from - must be an instance of {@code class1}
* @param fieldIndex The index of the field being read.
* @throws IllegalArgumentException If {@code instance} is not an instance of {@code class1}.
* @throws IllegalAccessException If unable to read from the field.
*/
@Deprecated
public static <T> T getField(Class<?> class1, Class<T> fieldType, Object instance, int fieldIndex) public static <T> T getField(Class<?> class1, Class<T> fieldType, Object instance, int fieldIndex)
throws IllegalArgumentException, IllegalAccessException { throws IllegalArgumentException, IllegalAccessException {
Field field = class1.getDeclaredFields()[fieldIndex]; Field field = class1.getDeclaredFields()[fieldIndex];
@@ -65,6 +65,7 @@ public class CodeChickenCoreModContainer extends DummyModContainer {
public void init(FMLInitializationEvent event) { public void init(FMLInitializationEvent event) {
if (event.getSide().isClient()) { if (event.getSide().isClient()) {
ClientUtils.enhanceSupportersList("CodeChickenCore"); ClientUtils.enhanceSupportersList("CodeChickenCore");
ClientUtils.registerLocalizedColourReloadListener();
FMLCommonHandler.instance().bus().register(new CCCEventHandler()); FMLCommonHandler.instance().bus().register(new CCCEventHandler());
MinecraftForge.EVENT_BUS.register(new CCCEventHandler()); MinecraftForge.EVENT_BUS.register(new CCCEventHandler());
@@ -6,6 +6,8 @@ import net.minecraft.util.ResourceLocation;
import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL11;
import codechicken.lib.colour.LocalizedColours;
public class GuiCCButton extends GuiWidget { public class GuiCCButton extends GuiWidget {
public String text; public String text;
@@ -73,7 +75,8 @@ public class GuiCCButton extends GuiWidget {
} }
public int getTextColour(int mousex, int mousey) { public int getTextColour(int mousex, int mousey) {
return !isEnabled ? 0xFFA0A0A0 : pointInside(mousex, mousey) ? 0xFFFFFFA0 : 0xFFE0E0E0; return !isEnabled ? LocalizedColours.BUTTON_TEXT_DISABLED
: pointInside(mousex, mousey) ? LocalizedColours.BUTTON_TEXT_HOVER : LocalizedColours.BUTTON_TEXT;
} }
public GuiCCButton setActionCommand(String string) { public GuiCCButton setActionCommand(String string) {
@@ -5,6 +5,8 @@ import net.minecraft.util.ChatAllowedCharacters;
import org.lwjgl.input.Keyboard; import org.lwjgl.input.Keyboard;
import codechicken.lib.colour.LocalizedColours;
public class GuiCCTextField extends GuiWidget { public class GuiCCTextField extends GuiWidget {
private String text; private String text;
@@ -120,8 +122,8 @@ public class GuiCCTextField extends GuiWidget {
} }
public void drawBackground() { public void drawBackground() {
drawRect(x - 1, y - 1, x + width + 1, y + height + 1, 0xffa0a0a0); drawRect(x - 1, y - 1, x + width + 1, y + height + 1, LocalizedColours.TEXT_FIELD_BORDER);
drawRect(x, y, x + width, y + height, 0xff000000); drawRect(x, y, x + width, y + height, LocalizedColours.TEXT_FIELD_BACKGROUND);
} }
public String getDrawText() { public String getDrawText() {
@@ -135,7 +137,7 @@ public class GuiCCTextField extends GuiWidget {
} }
public int getTextColour() { public int getTextColour() {
return isEnabled ? 0xe0e0e0 : 0x707070; return isEnabled ? LocalizedColours.TEXT_FIELD_TEXT : LocalizedColours.TEXT_FIELD_TEXT_DISABLED;
} }
public GuiCCTextField setMaxStringLength(int i) { public GuiCCTextField setMaxStringLength(int i) {
@@ -3,6 +3,7 @@ package codechicken.core.gui;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.Rectangle; import java.awt.Rectangle;
import codechicken.lib.colour.LocalizedColours;
import codechicken.lib.math.MathHelper; import codechicken.lib.math.MathHelper;
import codechicken.lib.vec.Rectangle4i; import codechicken.lib.vec.Rectangle4i;
@@ -184,28 +185,29 @@ public abstract class GuiScrollPane extends GuiWidget {
} }
public void drawBackground(float frame) { public void drawBackground(float frame) {
drawRect(x, y, x + width, y + height, 0xff000000); drawRect(x, y, x + width, y + height, LocalizedColours.SCROLL_PANE_BACKGROUND);
} }
public abstract void drawContent(int mx, int my, float frame); public abstract void drawContent(int mx, int my, float frame);
public void drawOverlay(float frame) { public void drawOverlay(float frame) {
// outlines // outlines
drawRect(x, y - 1, x + width, y, 0xffa0a0a0); // top drawRect(x, y - 1, x + width, y, LocalizedColours.SCROLL_PANE_OVERLAY_TOP);
drawRect(x, y + height, x + width, y + height + 1, 0xffa0a0a0); // bottom drawRect(x, y + height, x + width, y + height + 1, LocalizedColours.SCROLL_PANE_OVERLAY_BOTTOM);
drawRect(x - 1, y - 1, x, y + height + 1, 0xffa0a0a0); // left drawRect(x - 1, y - 1, x, y + height + 1, LocalizedColours.SCROLL_PANE_OVERLAY_LEFT);
drawRect(x + width, y - 1, x + width + 1, y + height + 1, 0xffa0a0a0); // right drawRect(x + width, y - 1, x + width + 1, y + height + 1, LocalizedColours.SCROLL_PANE_OVERLAY_RIGHT);
} }
public void drawScrollbar(float frame) { public void drawScrollbar(float frame) {
Rectangle r = scrollbarBounds(); Rectangle r = scrollbarBounds();
drawRect(r.x, r.y, r.x + r.width, r.y + r.height, 0xFF8B8B8B); // corners drawRect(r.x, r.y, r.x + r.width, r.y + r.height, LocalizedColours.SCROLLBAR_CORNERS);
drawRect(r.x, r.y, r.x + r.width - 1, r.y + r.height - 1, 0xFFF0F0F0); // topleft up drawRect(r.x, r.y, r.x + r.width - 1, r.y + r.height - 1, LocalizedColours.SCROLLBAR_TOP_LEFT);
drawRect(r.x + 1, r.y + 1, r.x + r.width, r.y + r.height, 0xFF555555); // bottom right down drawRect(r.x + 1, r.y + 1, r.x + r.width, r.y + r.height, LocalizedColours.SCROLLBAR_BOTTOM_RIGHT);
drawRect(r.x + 1, r.y + 1, r.x + r.width - 1, r.y + r.height - 1, 0xFFC6C6C6); // scrollbar drawRect(r.x + 1, r.y + 1, r.x + r.width - 1, r.y + r.height - 1, LocalizedColours.SCROLLBAR_FILL);
int algn = scrollbarGuideAlignment(); int algn = scrollbarGuideAlignment();
if (algn != 0) drawRect(algn > 0 ? r.x + r.width : r.x - 1, y, r.x, y + height, 0xFF808080); // lineguide if (algn != 0)
drawRect(algn > 0 ? r.x + r.width : r.x - 1, y, r.x, y + height, LocalizedColours.SCROLLBAR_GUIDE);
} }
} }
@@ -2,12 +2,12 @@ package codechicken.lib.asm;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import net.minecraft.launchwrapper.IClassTransformer; import net.minecraft.launchwrapper.IClassTransformer;
import net.minecraft.launchwrapper.Launch; import net.minecraft.launchwrapper.Launch;
import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassReader;
import org.objectweb.asm.tree.ClassNode;
import cpw.mods.fml.common.asm.transformers.deobf.FMLDeobfuscatingRemapper; import cpw.mods.fml.common.asm.transformers.deobf.FMLDeobfuscatingRemapper;
@@ -33,8 +33,8 @@ public class ClassHeirachyManager implements IClassTransformer {
} }
private static ClassInfo declareASM(String name, byte[] bytes) { private static ClassInfo declareASM(String name, byte[] bytes) {
final ClassNode node = ASMHelper.createClassNode(bytes, ClassReader.SKIP_CODE); final ClassReader classReader = new ClassReader(bytes);
final ClassInfo classInfo = new ClassInfo(node); final ClassInfo classInfo = new ClassInfo(classReader);
superclasses.put(name, classInfo); superclasses.put(name, classInfo);
return classInfo; return classInfo;
} }
@@ -108,6 +108,7 @@ public class ClassHeirachyManager implements IClassTransformer {
private static class ClassInfo { private static class ClassInfo {
public static final ClassInfo OBJECT = new ClassInfo(); public static final ClassInfo OBJECT = new ClassInfo();
private static final Map<String, String> STRING_POOL = new ConcurrentHashMap<>(256);
public final String superclass; public final String superclass;
public final String[] interfaces; public final String[] interfaces;
@@ -118,19 +119,23 @@ public class ClassHeirachyManager implements IClassTransformer {
this.interfaces = null; this.interfaces = null;
} }
public ClassInfo(ClassNode node) { public ClassInfo(ClassReader classReader) {
if ("java/lang/Object".equals(node.superName)) { final String superName = classReader.getSuperName();
final String[] interfaces = classReader.getInterfaces();
if (superName == null || "java/lang/Object".equals(superName)) {
superclass = "java.lang.Object"; superclass = "java.lang.Object";
parent = OBJECT; parent = OBJECT;
} else { } else {
superclass = toKey(node.superName); superclass = pool(toKey(superName));
} }
if (node.interfaces.isEmpty()) {
interfaces = null; if (interfaces == null || interfaces.length == 0) {
this.interfaces = null;
} else { } else {
interfaces = new String[node.interfaces.size()]; this.interfaces = new String[interfaces.length];
for (int i = 0; i < node.interfaces.size(); i++) { for (int i = 0; i < interfaces.length; i++) {
interfaces[i] = toKey(node.interfaces.get(i)); this.interfaces[i] = pool(toKey(interfaces[i]));
} }
} }
} }
@@ -145,7 +150,7 @@ public class ClassHeirachyManager implements IClassTransformer {
this.superclass = "java.lang.Object"; this.superclass = "java.lang.Object";
this.parent = OBJECT; this.parent = OBJECT;
} else { } else {
this.superclass = toKey(superclass.getName()); this.superclass = pool(toKey(superclass.getName()));
} }
} }
final Class<?>[] interfaces = aclass.getInterfaces(); final Class<?>[] interfaces = aclass.getInterfaces();
@@ -154,7 +159,7 @@ public class ClassHeirachyManager implements IClassTransformer {
} else { } else {
this.interfaces = new String[interfaces.length]; this.interfaces = new String[interfaces.length];
for (int i = 0; i < interfaces.length; i++) { for (int i = 0; i < interfaces.length; i++) {
this.interfaces[i] = toKey(interfaces[i].getName()); this.interfaces[i] = pool(toKey(interfaces[i].getName()));
} }
} }
} }
@@ -169,5 +174,9 @@ public class ClassHeirachyManager implements IClassTransformer {
this.parent = parentInfo; this.parent = parentInfo;
return parentInfo.hasSuper(superclass); return parentInfo.hasSuper(superclass);
} }
private static String pool(String s) {
return STRING_POOL.computeIfAbsent(s, Function.identity());
}
} }
} }
@@ -0,0 +1,86 @@
package codechicken.lib.colour;
import net.minecraft.util.StatCollector;
public final class LocalizedColours {
private static final String KEY_PREFIX = "codechickencore.color.";
public static int BUTTON_TEXT_DISABLED = 0xFFA0A0A0;
public static int BUTTON_TEXT_HOVER = 0xFFFFFFA0;
public static int BUTTON_TEXT = 0xFFE0E0E0;
public static int TEXT_FIELD_BORDER = 0xFFA0A0A0;
public static int TEXT_FIELD_BACKGROUND = 0xFF000000;
public static int TEXT_FIELD_TEXT = 0xE0E0E0;
public static int TEXT_FIELD_TEXT_DISABLED = 0x707070;
public static int SCROLL_PANE_BACKGROUND = 0xFF000000;
public static int SCROLL_PANE_OVERLAY_TOP = 0xFFA0A0A0;
public static int SCROLL_PANE_OVERLAY_BOTTOM = 0xFFA0A0A0;
public static int SCROLL_PANE_OVERLAY_LEFT = 0xFFA0A0A0;
public static int SCROLL_PANE_OVERLAY_RIGHT = 0xFFA0A0A0;
public static int SCROLLBAR_CORNERS = 0xFF8B8B8B;
public static int SCROLLBAR_TOP_LEFT = 0xFFF0F0F0;
public static int SCROLLBAR_BOTTOM_RIGHT = 0xFF555555;
public static int SCROLLBAR_FILL = 0xFFC6C6C6;
public static int SCROLLBAR_GUIDE = 0xFF808080;
public static int TOOLTIP_BG_START = 0xF0100010;
public static int TOOLTIP_BG_END = 0xF0100010;
public static int TOOLTIP_BORDER_START = 0x505000FF;
public static int TOOLTIP_BORDER_END = 0x5028007F;
public static int TOOLTIP_TEXT = 0xFFFFFFFF;
public static int ITEM_QUANTITY_TEXT = 0xFFFFFF;
public static int MOD_DESCRIPTION_TEXT = 0xDDDDDD;
private LocalizedColours() {}
public static void reloadLocalizedColours() {
BUTTON_TEXT_DISABLED = getLocalizedColor("buttonTextDisabled", BUTTON_TEXT_DISABLED);
BUTTON_TEXT_HOVER = getLocalizedColor("buttonTextHover", BUTTON_TEXT_HOVER);
BUTTON_TEXT = getLocalizedColor("buttonText", BUTTON_TEXT);
TEXT_FIELD_BORDER = getLocalizedColor("textFieldBorder", TEXT_FIELD_BORDER);
TEXT_FIELD_BACKGROUND = getLocalizedColor("textFieldBackground", TEXT_FIELD_BACKGROUND);
TEXT_FIELD_TEXT = getLocalizedColor("textFieldText", TEXT_FIELD_TEXT);
TEXT_FIELD_TEXT_DISABLED = getLocalizedColor("textFieldTextDisabled", TEXT_FIELD_TEXT_DISABLED);
SCROLL_PANE_BACKGROUND = getLocalizedColor("background", SCROLL_PANE_BACKGROUND);
SCROLL_PANE_OVERLAY_TOP = getLocalizedColor("overlayTop", SCROLL_PANE_OVERLAY_TOP);
SCROLL_PANE_OVERLAY_BOTTOM = getLocalizedColor("overlayBottom", SCROLL_PANE_OVERLAY_BOTTOM);
SCROLL_PANE_OVERLAY_LEFT = getLocalizedColor("overlayLeft", SCROLL_PANE_OVERLAY_LEFT);
SCROLL_PANE_OVERLAY_RIGHT = getLocalizedColor("overlayRight", SCROLL_PANE_OVERLAY_RIGHT);
SCROLLBAR_CORNERS = getLocalizedColor("scrollbarCorners", SCROLLBAR_CORNERS);
SCROLLBAR_TOP_LEFT = getLocalizedColor("scrollbarTopLeft", SCROLLBAR_TOP_LEFT);
SCROLLBAR_BOTTOM_RIGHT = getLocalizedColor("scrollbarBottomRight", SCROLLBAR_BOTTOM_RIGHT);
SCROLLBAR_FILL = getLocalizedColor("scrollbarFill", SCROLLBAR_FILL);
SCROLLBAR_GUIDE = getLocalizedColor("scrollbarGuide", SCROLLBAR_GUIDE);
TOOLTIP_BG_START = getLocalizedColor("tooltipBgStart", TOOLTIP_BG_START);
TOOLTIP_BG_END = getLocalizedColor("tooltipBgEnd", TOOLTIP_BG_END);
TOOLTIP_BORDER_START = getLocalizedColor("tooltipBorderStart", TOOLTIP_BORDER_START);
TOOLTIP_BORDER_END = getLocalizedColor("tooltipBorderEnd", TOOLTIP_BORDER_END);
TOOLTIP_TEXT = getLocalizedColor("tooltipText", TOOLTIP_TEXT);
ITEM_QUANTITY_TEXT = getLocalizedColor("itemQuantityText", ITEM_QUANTITY_TEXT);
MOD_DESCRIPTION_TEXT = getLocalizedColor("modDescriptionText", MOD_DESCRIPTION_TEXT);
}
/**
* Resolve a optional localized ARGB color from lang files. Expected value format: AARRGGBB (for example FF555555).
*/
private static int getLocalizedColor(String key, int defaultColor) {
String fullKey = KEY_PREFIX + key;
if (!StatCollector.canTranslate(fullKey)) {
return defaultColor;
}
String raw = StatCollector.translateToLocal(fullKey);
String hex = raw.trim();
if (hex.startsWith("0x") || hex.startsWith("0X")) {
hex = hex.substring(2);
} else if (hex.startsWith("#")) {
hex = hex.substring(1);
}
try {
return (int) Long.parseLong(hex, 16);
} catch (NumberFormatException ignored) {
return defaultColor;
}
}
}
+20 -3
View File
@@ -18,6 +18,7 @@ import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12; import org.lwjgl.opengl.GL12;
import codechicken.lib.colour.LocalizedColours;
import codechicken.lib.math.MathHelper; import codechicken.lib.math.MathHelper;
import codechicken.lib.render.CCRenderState; import codechicken.lib.render.CCRenderState;
@@ -162,7 +163,15 @@ public class GuiDraw {
} }
public static void drawMultilineTip(FontRenderer font, int x, int y, List<String> list) { public static void drawMultilineTip(FontRenderer font, int x, int y, List<String> list) {
drawMultilineTip(font, x, y, list, 0xf0100010, 0xf0100010, 0x505000ff, 0x5028007F); drawMultilineTip(
font,
x,
y,
list,
LocalizedColours.TOOLTIP_BG_START,
LocalizedColours.TOOLTIP_BG_END,
LocalizedColours.TOOLTIP_BORDER_START,
LocalizedColours.TOOLTIP_BORDER_END);
} }
public static void drawMultilineTip(FontRenderer font, int x, int y, List<String> list, int bgStart, int bgEnd, public static void drawMultilineTip(FontRenderer font, int x, int y, List<String> list, int bgStart, int bgEnd,
@@ -201,7 +210,7 @@ public class GuiDraw {
line.draw(x, y); line.draw(x, y);
y += line.getSize().height; y += line.getSize().height;
} else { } else {
font.drawStringWithShadow(s, x, y, -1); font.drawStringWithShadow(s, x, y, LocalizedColours.TOOLTIP_TEXT);
y += s.endsWith(TOOLTIP_LINESPACE) ? 12 : 10; y += s.endsWith(TOOLTIP_LINESPACE) ? 12 : 10;
} }
} }
@@ -215,7 +224,15 @@ public class GuiDraw {
} }
public static void drawTooltipBox(int x, int y, int w, int h) { public static void drawTooltipBox(int x, int y, int w, int h) {
drawTooltipBox(x, y, w, h, 0xf0100010, 0xf0100010, 0x505000ff, 0x5028007F); drawTooltipBox(
x,
y,
w,
h,
LocalizedColours.TOOLTIP_BG_START,
LocalizedColours.TOOLTIP_BG_END,
LocalizedColours.TOOLTIP_BORDER_START,
LocalizedColours.TOOLTIP_BORDER_END);
} }
public static void drawTooltipBox(int x, int y, int w, int h, int bgStart, int bgEnd, int borderStart, public static void drawTooltipBox(int x, int y, int w, int h, int bgStart, int bgEnd, int borderStart,
@@ -12,7 +12,12 @@ import codechicken.lib.vec.BlockCoord;
public class SimpleBrightnessModel implements CCRenderState.IVertexOperation { public class SimpleBrightnessModel implements CCRenderState.IVertexOperation {
public static final int operationIndex = CCRenderState.registerOperation(); public static final int operationIndex = CCRenderState.registerOperation();
public static SimpleBrightnessModel instance = new SimpleBrightnessModel(); private static final ThreadLocal<SimpleBrightnessModel> instances = ThreadLocal
.withInitial(SimpleBrightnessModel::new);
public static SimpleBrightnessModel instance() {
return instances.get();
}
public IBlockAccess access; public IBlockAccess access;
public BlockCoord pos = new BlockCoord(); public BlockCoord pos = new BlockCoord();
@@ -2,6 +2,7 @@ package codechicken.lib.packet;
import java.util.EnumMap; import java.util.EnumMap;
import java.util.List; import java.util.List;
import java.util.zip.DataFormatException;
import java.util.zip.Deflater; import java.util.zip.Deflater;
import java.util.zip.Inflater; import java.util.zip.Inflater;
@@ -50,6 +51,8 @@ import io.netty.util.AttributeKey;
public final class PacketCustom implements MCDataInput, MCDataOutput { public final class PacketCustom implements MCDataInput, MCDataOutput {
private static final int MAX_DECOMPRESSED_SIZE = 2 * 1024 * 1024;
public static interface ICustomPacketHandler { public static interface ICustomPacketHandler {
} }
@@ -190,7 +193,7 @@ public final class PacketCustom implements MCDataInput, MCDataOutput {
byteBuf = payload; byteBuf = payload;
type = byteBuf.readUnsignedByte(); type = byteBuf.readUnsignedByte();
if (type > 0x80) decompress(); if (type > 0x80) decompressPayload();
type &= 0x7F; type &= 0x7F;
} }
@@ -204,44 +207,61 @@ public final class PacketCustom implements MCDataInput, MCDataOutput {
byteBuf.writeByte(type); byteBuf.writeByte(type);
} }
/** private void decompressPayload() {
* Decompresses the remaining ByteBuf (after type has been read) using Snappy if (byteBuf.readableBytes() < 4) throw new IllegalArgumentException("Missing compressed packet length");
*/ int declaredLength = byteBuf.readInt();
private void decompress() { if (declaredLength < 0 || declaredLength > MAX_DECOMPRESSED_SIZE)
throw new IllegalArgumentException("Invalid decompressed packet length: " + declaredLength);
Inflater inflater = new Inflater(); Inflater inflater = new Inflater();
ByteBuf decodedPayload = null;
try { try {
int len = byteBuf.readInt(); byte[] compressedInput = new byte[byteBuf.readableBytes()];
ByteBuf out = Unpooled.buffer(len); byteBuf.readBytes(compressedInput);
inflater.setInput(byteBuf.array(), byteBuf.readerIndex(), byteBuf.readableBytes()); inflater.setInput(compressedInput);
inflater.inflate(out.array()); decodedPayload = Unpooled.buffer(Math.min(declaredLength, 8192), declaredLength);
out.writerIndex(len); byte[] inflatedChunk = new byte[Math.min(declaredLength + 1, 8192)];
byteBuf = out; while (!inflater.finished()) {
} catch (Exception e) { int inflatedBytes = inflater.inflate(inflatedChunk);
throw new RuntimeException(e); if (inflatedBytes > declaredLength - decodedPayload.writerIndex())
throw new IllegalArgumentException("Decompressed packet exceeds declared length");
if (inflatedBytes == 0 && !inflater.finished())
throw new IllegalArgumentException("Incomplete or dictionary-dependent compressed packet");
decodedPayload.writeBytes(inflatedChunk, 0, inflatedBytes);
}
if (decodedPayload.writerIndex() != declaredLength || inflater.getRemaining() != 0)
throw new IllegalArgumentException(
"Compressed packet does not match declared length or has trailing data");
byteBuf = decodedPayload;
decodedPayload = null;
} catch (DataFormatException e) {
throw new IllegalArgumentException("Invalid compressed packet", e);
} finally { } finally {
if (decodedPayload != null) decodedPayload.release();
inflater.end(); inflater.end();
} }
} }
/** private void compressPayload() {
* Compresses the payload ByteBuf after the type byte if ((byteBuf.getUnsignedByte(0) & 0x80) != 0) return;
*/
private void do_compress() { int payloadLength = byteBuf.writerIndex() - 1;
if (payloadLength > MAX_DECOMPRESSED_SIZE)
throw new IllegalArgumentException("Packet payload exceeds 2 MiB decompressed limit: " + payloadLength);
Deflater deflater = new Deflater(); Deflater deflater = new Deflater();
try { try {
byteBuf.readerIndex(1); byteBuf.readerIndex(1);
int len = byteBuf.readableBytes(); deflater.setInput(byteBuf.array(), byteBuf.readerIndex(), payloadLength);
deflater.setInput(byteBuf.array(), byteBuf.readerIndex(), len);
deflater.finish(); deflater.finish();
ByteBuf out = Unpooled.buffer(len + 5); ByteBuf compressedPayload = Unpooled.buffer(payloadLength + 5);
int clen = deflater.deflate(out.array(), 5, len); int compressedLength = deflater.deflate(compressedPayload.array(), 5, payloadLength);
if (clen >= len - 5 || !deflater.finished()) // not worth compressing, gets larger if (compressedLength >= payloadLength - 5 || !deflater.finished()) return;
return;
out.setByte(0, type | 0x80); compressedPayload.setByte(0, type | 0x80);
out.setInt(1, len); compressedPayload.setInt(1, payloadLength);
out.writerIndex(clen + 5); compressedPayload.writerIndex(compressedLength + 5);
byteBuf = out; byteBuf = compressedPayload;
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} finally { } finally {
@@ -351,9 +371,9 @@ public final class PacketCustom implements MCDataInput, MCDataOutput {
public PacketCustom writeItemStack(ItemStack stack, boolean large) { public PacketCustom writeItemStack(ItemStack stack, boolean large) {
if (stack == null) { if (stack == null) {
writeShort(-1); writeInt(-1);
} else { } else {
writeShort(Item.getIdFromItem(stack.getItem())); writeInt(Item.getIdFromItem(stack.getItem()));
if (large) writeInt(stack.stackSize); if (large) writeInt(stack.stackSize);
else writeByte(stack.stackSize); else writeByte(stack.stackSize);
writeShort(stack.getItemDamage()); writeShort(stack.getItemDamage());
@@ -448,7 +468,7 @@ public final class PacketCustom implements MCDataInput, MCDataOutput {
public ItemStack readItemStack(boolean large) { public ItemStack readItemStack(boolean large) {
ItemStack item = null; ItemStack item = null;
short itemID = readShort(); int itemID = readInt();
if (itemID >= 0) { if (itemID >= 0) {
int stackSize = large ? readInt() : readByte(); int stackSize = large ? readInt() : readByte();
@@ -466,7 +486,7 @@ public final class PacketCustom implements MCDataInput, MCDataOutput {
public FluidStack readFluidStack() { public FluidStack readFluidStack() {
Fluid fluid = FluidRegistry.getFluid(readShort()); Fluid fluid = FluidRegistry.getFluid(readShort());
if (fluid == null) fluid = FluidRegistry.WATER; if (fluid == null) return null;
return new FluidStack(fluid, readVarInt(), readNBTTagCompound()); return new FluidStack(fluid, readVarInt(), readNBTTagCompound());
} }
@@ -474,7 +494,7 @@ public final class PacketCustom implements MCDataInput, MCDataOutput {
public FMLProxyPacket toPacket() { public FMLProxyPacket toPacket() {
if (incoming()) throw new IllegalStateException("Tried to write an incoming packet"); if (incoming()) throw new IllegalStateException("Tried to write an incoming packet");
if (byteBuf.readableBytes() > 32000 || (type & 0x80) != 0) do_compress(); if (byteBuf.readableBytes() > 32000 || (type & 0x80) != 0) compressPayload();
// FML packet impl returns the whole of the backing array, copy used portion of array to another ByteBuf // FML packet impl returns the whole of the backing array, copy used portion of array to another ByteBuf
return new FMLProxyPacket(byteBuf.copy(), channel); return new FMLProxyPacket(byteBuf.copy(), channel);
@@ -1,6 +1,7 @@
package codechicken.lib.render; package codechicken.lib.render;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.OpenGlHelper; import net.minecraft.client.renderer.OpenGlHelper;
@@ -38,9 +39,9 @@ public class CCRenderState {
return instances.get(); return instances.get();
} }
private static int nextOperationIndex; private static volatile int nextOperationIndex;
public static int registerOperation() { public static synchronized int registerOperation() {
return nextOperationIndex++; return nextOperationIndex++;
} }
@@ -87,15 +88,52 @@ public class CCRenderState {
int operationID(); int operationID();
} }
private static ArrayList<VertexAttribute<?>> vertexAttributes = new ArrayList<>(); public static final class AttributeKey<T> {
private static int registerVertexAttribute(VertexAttribute<?> attr) { private static final HashMap<String, AttributeKey<?>> nameMap = new HashMap<>();
vertexAttributes.add(attr); private static final ArrayList<AttributeKey<?>> attributeKeys = new ArrayList<>();
return vertexAttributes.size() - 1; private static int nextLegacyId;
public final String name;
public final int attributeKeyIndex;
public final int operationIndex;
private VertexAttribute<T> canonical;
private AttributeKey(String name) {
this.name = name;
this.attributeKeyIndex = attributeKeys.size();
this.operationIndex = registerOperation();
attributeKeys.add(this);
nameMap.put(name, this);
}
public static synchronized <T> AttributeKey<T> create(String name) {
if (nameMap.containsKey(name))
throw new IllegalArgumentException("Duplicate registration of attribute with name: " + name);
return new AttributeKey<>(name);
}
static synchronized <T> AttributeKey<T> createLegacy() {
return new AttributeKey<>("legacy#" + nextLegacyId++);
}
synchronized void setCanonical(VertexAttribute<T> attr) {
if (canonical == null) canonical = attr;
}
static synchronized AttributeKey<?> get(int index) {
return attributeKeys.get(index);
}
} }
private static final AttributeKey<Vector3[]> NORMAL_KEY = AttributeKey.create("normal");
private static final AttributeKey<int[]> COLOUR_KEY = AttributeKey.create("colour");
private static final AttributeKey<int[]> LIGHTING_KEY = AttributeKey.create("lighting");
private static final AttributeKey<int[]> SIDE_KEY = AttributeKey.create("side");
private static final AttributeKey<LC[]> LC_KEY = AttributeKey.create("lc");
public static VertexAttribute<?> getAttribute(int index) { public static VertexAttribute<?> getAttribute(int index) {
return vertexAttributes.get(index); return AttributeKey.get(index).canonical;
} }
/** /**
@@ -106,14 +144,26 @@ public class CCRenderState {
*/ */
public abstract static class VertexAttribute<T> implements IVertexOperation { public abstract static class VertexAttribute<T> implements IVertexOperation {
public final int attributeIndex = registerVertexAttribute(this); public final int attributeIndex;
private final int operationIndex = registerOperation(); private final int operationIndex;
/** /**
* Set to true when the attrute is part of the pipeline. Should only be managed by CCRenderState when * Set to true when the attrute is part of the pipeline. Should only be managed by CCRenderState when
* constructing the pipeline * constructing the pipeline
*/ */
public boolean active = false; public boolean active = false;
protected VertexAttribute(AttributeKey<T> key) {
attributeIndex = key.attributeKeyIndex;
operationIndex = key.operationIndex;
key.setCanonical(this);
}
/** @deprecated keyless attributes get a per-construction index that is threadsafe */
@Deprecated
protected VertexAttribute() {
this(AttributeKey.createLegacy());
}
/** /**
* Construct a new array for storage of vertex attrutes in a model * Construct a new array for storage of vertex attrutes in a model
*/ */
@@ -189,7 +239,7 @@ public class CCRenderState {
return instances.get().lightingAttrib; return instances.get().lightingAttrib;
} }
public VertexAttribute<Vector3[]> normalAttrib = new VertexAttribute<>() { public VertexAttribute<Vector3[]> normalAttrib = new VertexAttribute<>(NORMAL_KEY) {
private Vector3[] normalRef; private Vector3[] normalRef;
@@ -217,7 +267,7 @@ public class CCRenderState {
else state.setNormalInstance(Rotation.axes[state.side]); else state.setNormalInstance(Rotation.axes[state.side]);
} }
}; };
public VertexAttribute<int[]> colourAttrib = new VertexAttribute<>() { public VertexAttribute<int[]> colourAttrib = new VertexAttribute<>(COLOUR_KEY) {
private int[] colourRef; private int[] colourRef;
@@ -239,7 +289,7 @@ public class CCRenderState {
else state.setColourInstance(state.baseColour); else state.setColourInstance(state.baseColour);
} }
}; };
public VertexAttribute<int[]> lightingAttrib = new VertexAttribute<>() { public VertexAttribute<int[]> lightingAttrib = new VertexAttribute<>(LIGHTING_KEY) {
private int[] colourRef; private int[] colourRef;
@@ -265,7 +315,7 @@ public class CCRenderState {
state.setColourInstance(ColourRGBA.multiply(state.colour, colourRef[state.vertexIndex])); state.setColourInstance(ColourRGBA.multiply(state.colour, colourRef[state.vertexIndex]));
} }
}; };
public VertexAttribute<int[]> sideAttrib = new VertexAttribute<>() { public VertexAttribute<int[]> sideAttrib = new VertexAttribute<>(SIDE_KEY) {
private int[] sideRef; private int[] sideRef;
@@ -292,7 +342,7 @@ public class CCRenderState {
/** /**
* Uses the position of the lightmatrix to compute LC if not provided * Uses the position of the lightmatrix to compute LC if not provided
*/ */
public VertexAttribute<LC[]> lightCoordAttrib = new VertexAttribute<>() { public VertexAttribute<LC[]> lightCoordAttrib = new VertexAttribute<>(LC_KEY) {
private LC[] lcRef; private LC[] lcRef;
private final Vector3 vec = new Vector3(); // for computation private final Vector3 vec = new Vector3(); // for computation
@@ -1,5 +1,7 @@
package codechicken.lib.render; package codechicken.lib.render;
import java.util.Arrays;
import net.minecraft.client.particle.EffectRenderer; import net.minecraft.client.particle.EffectRenderer;
import net.minecraft.client.particle.EntityFX; import net.minecraft.client.particle.EntityFX;
import net.minecraft.client.renderer.Tessellator; import net.minecraft.client.renderer.Tessellator;
@@ -11,6 +13,9 @@ import codechicken.lib.vec.Vector3;
public class EntityDigIconFX extends EntityFX { public class EntityDigIconFX extends EntityFX {
/** Tint that leaves the particle colour untouched, for materials that don't colour their texture. */
public static final int NO_TINT = 0xFFFFFF;
public EntityDigIconFX(World world, double x, double y, double z, double dx, double dy, double dz, IIcon icon) { public EntityDigIconFX(World world, double x, double y, double z, double dx, double dy, double dz, IIcon icon) {
super(world, x, y, z, dx, dy, dz); super(world, x, y, z, dx, dy, dz);
particleIcon = icon; particleIcon = icon;
@@ -19,6 +24,18 @@ public class EntityDigIconFX extends EntityFX {
particleScale /= 2.0F; particleScale /= 2.0F;
} }
/**
* Multiplies the particle colour by an 0xRRGGBB tint, the equivalent of vanilla
* EntityDiggingFX.applyColourMultiplier. Materials that draw a greyscale texture and colour it at render time
* (leaves, grass tops) need this, or their particles come out flat grey.
*/
public EntityDigIconFX applyColourMultiplier(int colour) {
particleRed *= (colour >> 16 & 255) / 255F;
particleGreen *= (colour >> 8 & 255) / 255F;
particleBlue *= (colour & 255) / 255F;
return this;
}
@Override @Override
public int getFXLayer() { public int getFXLayer() {
return 1; return 1;
@@ -90,8 +107,24 @@ public class EntityDigIconFX extends EntityFX {
f9); f9);
} }
/**
* @deprecated the particle is never tinted, so a material that colours a greyscale texture at render time (leaves,
* grass tops) breaks into grey particles. Use
* {@link #addBlockHitEffects(World, Cuboid6, int, IIcon, int, EffectRenderer)}, passing
* {@link #NO_TINT} for materials whose texture is already coloured.
*/
@Deprecated
public static void addBlockHitEffects(World world, Cuboid6 bounds, int side, IIcon icon, public static void addBlockHitEffects(World world, Cuboid6 bounds, int side, IIcon icon,
EffectRenderer effectRenderer) { EffectRenderer effectRenderer) {
addBlockHitEffects(world, bounds, side, icon, NO_TINT, effectRenderer);
}
/**
* Spawns a hit particle on side of bounds, tinted by colour (0xRRGGBB, {@link #NO_TINT} for none). The caller
* resolves the tint for the side it hit, so per-face materials like grass can tint only the faces that need it.
*/
public static void addBlockHitEffects(World world, Cuboid6 bounds, int side, IIcon icon, int colour,
EffectRenderer effectRenderer) {
float border = 0.1F; float border = 0.1F;
Vector3 diff = bounds.max.copy().subtract(bounds.min).add(-2 * border); Vector3 diff = bounds.max.copy().subtract(bounds.min).add(-2 * border);
diff.x *= world.rand.nextDouble(); diff.x *= world.rand.nextDouble();
@@ -106,13 +139,34 @@ public class EntityDigIconFX extends EntityFX {
if (side == 4) diff.x = bounds.min.x - border; if (side == 4) diff.x = bounds.min.x - border;
if (side == 5) diff.x = bounds.max.x + border; if (side == 5) diff.x = bounds.max.x + border;
effectRenderer.addEffect( EntityDigIconFX fx = new EntityDigIconFX(world, pos.x, pos.y, pos.z, 0, 0, 0, icon);
new EntityDigIconFX(world, pos.x, pos.y, pos.z, 0, 0, 0, icon).multiplyVelocity(0.2F) effectRenderer.addEffect(fx.applyColourMultiplier(colour).multiplyVelocity(0.2F).multipleParticleScaleBy(0.6F));
.multipleParticleScaleBy(0.6F));
} }
/**
* @deprecated the particles are never tinted, so a material that colours a greyscale texture at render time
* (leaves, grass tops) breaks into grey particles. Use
* {@link #addBlockDestroyEffects(World, Cuboid6, IIcon[], int[], EffectRenderer)}, passing
* {@link #NO_TINT} for the faces whose texture is already coloured.
*/
@Deprecated
public static void addBlockDestroyEffects(World world, Cuboid6 bounds, IIcon[] icons, public static void addBlockDestroyEffects(World world, Cuboid6 bounds, IIcon[] icons,
EffectRenderer effectRenderer) { EffectRenderer effectRenderer) {
int[] colours = new int[icons.length];
Arrays.fill(colours, NO_TINT);
addBlockDestroyEffects(world, bounds, icons, colours, effectRenderer);
}
/**
* Spawns break particles filling bounds, each drawn from a random face. colours holds the 0xRRGGBB tint for the
* matching entry of icons ({@link #NO_TINT} for none) and must be the same length, so a particle is tinted by the
* face it was taken from.
*/
public static void addBlockDestroyEffects(World world, Cuboid6 bounds, IIcon[] icons, int[] colours,
EffectRenderer effectRenderer) {
if (icons.length != colours.length)
throw new IllegalArgumentException("icons and colours must have the same length");
Vector3 diff = bounds.max.copy().subtract(bounds.min); Vector3 diff = bounds.max.copy().subtract(bounds.min);
Vector3 center = bounds.min.copy().add(bounds.max).multiply(0.5); Vector3 center = bounds.min.copy().add(bounds.max).multiply(0.5);
Vector3 density = diff.copy().multiply(4); Vector3 density = diff.copy().multiply(4);
@@ -121,19 +175,20 @@ public class EntityDigIconFX extends EntityFX {
density.z = Math.ceil(density.z); density.z = Math.ceil(density.z);
for (int i = 0; i < density.x; ++i) for (int j = 0; j < density.y; ++j) for (int k = 0; k < density.z; ++k) { for (int i = 0; i < density.x; ++i) for (int j = 0; j < density.y; ++j) for (int k = 0; k < density.z; ++k) {
double x = bounds.min.x + (i + 0.5) * diff.x / density.x; double px = bounds.min.x + (i + 0.5) * diff.x / density.x;
double y = bounds.min.y + (j + 0.5) * diff.y / density.y; double py = bounds.min.y + (j + 0.5) * diff.y / density.y;
double z = bounds.min.z + (k + 0.5) * diff.z / density.z; double pz = bounds.min.z + (k + 0.5) * diff.z / density.z;
effectRenderer.addEffect( int iconSide = world.rand.nextInt(icons.length);
new EntityDigIconFX( EntityDigIconFX fx = new EntityDigIconFX(
world, world,
x, px,
y, py,
z, pz,
x - center.x, px - center.x,
y - center.y, py - center.y,
z - center.z, pz - center.z,
icons[world.rand.nextInt(icons.length)])); icons[iconSide]);
effectRenderer.addEffect(fx.applyColourMultiplier(colours[iconSide]));
} }
} }
} }
@@ -6,6 +6,8 @@ import net.minecraft.item.ItemStack;
import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL11;
import codechicken.lib.colour.LocalizedColours;
public class FontUtils { public class FontUtils {
public static FontRenderer fontRenderer = Minecraft.getMinecraft().fontRenderer; public static FontRenderer fontRenderer = Minecraft.getMinecraft().fontRenderer;
@@ -53,7 +55,7 @@ public class FontUtils {
GL11.glPushMatrix(); GL11.glPushMatrix();
GL11.glTranslated(x + 16 - swidth, y + 16 - sheight, 0); GL11.glTranslated(x + 16 - swidth, y + 16 - sheight, 0);
GL11.glScaled(scale, scale, 1); GL11.glScaled(scale, scale, 1);
fontRenderer.drawStringWithShadow(quantity, 0, 0, 0xFFFFFF); fontRenderer.drawStringWithShadow(quantity, 0, 0, LocalizedColours.ITEM_QUANTITY_TEXT);
GL11.glPopMatrix(); GL11.glPopMatrix();
GL11.glEnable(GL11.GL_LIGHTING); GL11.glEnable(GL11.GL_LIGHTING);
GL11.glEnable(GL11.GL_DEPTH_TEST); GL11.glEnable(GL11.GL_DEPTH_TEST);
@@ -55,7 +55,10 @@ public class WorldExtensionManager {
public void onChunkUnLoad(ChunkEvent.Unload event) { public void onChunkUnLoad(ChunkEvent.Unload event) {
if (event.getChunk() instanceof EmptyChunk) return; if (event.getChunk() instanceof EmptyChunk) return;
for (WorldExtension extension : worldMap.get(event.world)) extension.unloadChunk(event.getChunk()); WorldExtension[] extensions = worldMap.get(event.world);
if (extensions == null) return;
for (WorldExtension extension : extensions) extension.unloadChunk(event.getChunk());
if (event.world.isRemote) removeChunk(event.world, event.getChunk()); if (event.world.isRemote) removeChunk(event.world, event.getChunk());
} }
@@ -1 +1,26 @@
codechickencore.update=Version %s of %s is available codechickencore.update=Version %s of %s is available
codechickencore.color.buttonTextDisabled=FFA0A0A0
codechickencore.color.buttonTextHover=FFFFFFA0
codechickencore.color.buttonText=FFE0E0E0
codechickencore.color.textFieldBorder=FFA0A0A0
codechickencore.color.textFieldBackground=FF000000
codechickencore.color.textFieldText=E0E0E0
codechickencore.color.textFieldTextDisabled=707070
codechickencore.color.background=FF000000
codechickencore.color.overlayTop=FFA0A0A0
codechickencore.color.overlayBottom=FFA0A0A0
codechickencore.color.overlayLeft=FFA0A0A0
codechickencore.color.overlayRight=FFA0A0A0
codechickencore.color.scrollbarCorners=FF8B8B8B
codechickencore.color.scrollbarTopLeft=FFF0F0F0
codechickencore.color.scrollbarBottomRight=FF555555
codechickencore.color.scrollbarFill=FFC6C6C6
codechickencore.color.scrollbarGuide=FF808080
codechickencore.color.tooltipBgStart=F0100010
codechickencore.color.tooltipBgEnd=F0100010
codechickencore.color.tooltipBorderStart=505000FF
codechickencore.color.tooltipBorderEnd=5028007F
codechickencore.color.tooltipText=FFFFFFFF
codechickencore.color.itemQuantityText=FFFFFF
codechickencore.color.modDescriptionText=DDDDDD
@@ -0,0 +1 @@
codechickencore.update=Доступна версія %s для %s
@@ -0,0 +1,218 @@
package codechicken.lib.packet;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import java.io.ByteArrayOutputStream;
import java.util.Arrays;
import java.util.zip.Deflater;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import cpw.mods.fml.common.network.internal.FMLProxyPacket;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
public class PacketCustomTest {
private static final int MAX_DECOMPRESSED_SIZE = 2 * 1024 * 1024;
@Test
public void rejectsLargeDeclarationWithoutCompressedData() {
assertRejected(Unpooled.buffer(5).writeByte(0x81).writeInt(8 * 1024 * 1024));
}
@ParameterizedTest
@ValueSource(ints = { -1, Integer.MIN_VALUE, MAX_DECOMPRESSED_SIZE + 1, Integer.MAX_VALUE })
public void rejectsInvalidDeclaredLengths(int length) {
assertRejected(Unpooled.buffer(5).writeByte(0x81).writeInt(length));
}
@Test
public void rejectsMissingCompressedDataWithinLimit() {
assertRejected(Unpooled.buffer(5).writeByte(0x81).writeInt(MAX_DECOMPRESSED_SIZE));
}
@Test
public void rejectsCompleteStreamAboveDecodeLimit() {
assertRejected(compressed(new byte[MAX_DECOMPRESSED_SIZE + 1]));
}
@Test
public void rejectsTruncatedLengthHeader() {
assertRejected(Unpooled.buffer(4).writeByte(0x81).writeMedium(10));
}
@Test
public void rejectsMismatchedInflatedLengths() {
byte[] data = new byte[16384];
assertRejected(compressed(data).setInt(1, data.length - 1));
assertRejected(compressed(data).setInt(1, data.length + 1));
assertRejected(compressed(data).setInt(1, 0));
}
@Test
public void rejectsTruncatedStreamEvenWhenAllOutputWasProduced() {
ByteBuf payload = compressed(new byte[16384]);
payload.writerIndex(payload.writerIndex() - 1);
assertRejected(payload);
}
@Test
public void rejectsCorruptStream() {
ByteBuf payload = compressed(new byte[16384]);
payload.setByte(payload.writerIndex() - 1, payload.getByte(payload.writerIndex() - 1) ^ 1);
assertRejected(payload);
}
@Test
public void rejectsDictionaryDependentStream() {
assertRejected(compressed(new byte[16384], new byte[] { 1, 2, 3 }));
}
@Test
public void rejectsTrailingCompressedData() {
assertRejected(compressed(new byte[16384]).writeByte(0));
}
@ParameterizedTest
@ValueSource(ints = { 0, 1, 8192, 8193, MAX_DECOMPRESSED_SIZE })
public void decodesCompleteStreams(int length) {
byte[] data = new byte[length];
Arrays.fill(data, (byte) 42);
assertDecoded(compressed(data), data);
}
@Test
public void decodesSlicedAndDirectBuffers() {
byte[] data = new byte[16384];
Arrays.fill(data, (byte) 42);
ByteBuf payload = compressed(data);
ByteBuf prefixed = Unpooled.buffer().writeLong(-1).writeBytes(payload, 0, payload.readableBytes());
ByteBuf direct = Unpooled.directBuffer().writeBytes(payload);
payload.release();
try {
assertDecoded(prefixed.slice(8, prefixed.readableBytes() - 8).retain(), data);
assertDecoded(direct, data);
} finally {
prefixed.release();
}
}
@Test
public void preservesUncompressedPackets() {
ByteBuf payload = Unpooled.buffer().writeByte(7).writeInt(42);
try {
PacketCustom packet = new PacketCustom(payload);
assertEquals(7, packet.getType());
assertEquals(42, packet.readInt());
assertEquals(0, packet.getByteBuf().readableBytes());
} finally {
payload.release();
}
}
@Test
public void roundTripsOutgoingCompression() {
for (int length : new int[] { 16, 64, 40000, MAX_DECOMPRESSED_SIZE }) {
byte[] data = new byte[length];
Arrays.fill(data, (byte) 42);
PacketCustom outgoing = new PacketCustom("PacketTest", 7);
FMLProxyPacket wire = null;
try {
outgoing.writeByteArray(data);
if (length <= 64) outgoing.compress();
wire = outgoing.toPacket();
assertEquals(length == 16 ? 7 : 0x87, wire.payload().getUnsignedByte(0));
assertDecoded(wire.payload().retain(), data);
} finally {
if (wire != null) wire.payload().release();
outgoing.getByteBuf().release();
}
}
}
@ParameterizedTest
@ValueSource(booleans = { false, true })
public void repeatedSerializationPreservesCompressedPayload(boolean forceCompression) {
byte[] data = new byte[40000];
Arrays.fill(data, (byte) 42);
PacketCustom outgoing = new PacketCustom("PacketTest", 7).writeByteArray(data);
if (forceCompression) outgoing.compress();
try {
for (int serialization = 0; serialization < 3; serialization++) {
FMLProxyPacket wire = outgoing.toPacket();
try {
assertEquals(0x87, wire.payload().getUnsignedByte(0));
assertDecoded(wire.payload().retain(), data);
} finally {
wire.payload().release();
}
}
} finally {
outgoing.getByteBuf().release();
}
}
@Test
public void rejectsOutgoingPayloadAboveDecodeLimit() {
PacketCustom outgoing = new PacketCustom("PacketTest", 7);
try {
outgoing.getByteBuf().writeZero(MAX_DECOMPRESSED_SIZE + 1);
assertThrows(IllegalArgumentException.class, outgoing::toPacket);
} finally {
outgoing.getByteBuf().release();
}
}
private static ByteBuf compressed(byte[] data) {
return compressed(data, null);
}
private static ByteBuf compressed(byte[] data, byte[] dictionary) {
Deflater deflater = new Deflater();
try {
if (dictionary != null) deflater.setDictionary(dictionary);
deflater.setInput(data);
deflater.finish();
ByteArrayOutputStream compressed = new ByteArrayOutputStream();
byte[] scratch = new byte[8192];
while (!deflater.finished()) {
int count = deflater.deflate(scratch);
compressed.write(scratch, 0, count);
}
return Unpooled.buffer().writeByte(0x87).writeInt(data.length).writeBytes(compressed.toByteArray());
} finally {
deflater.end();
}
}
private static void assertDecoded(ByteBuf payload, byte[] expected) {
PacketCustom packet = null;
try {
packet = new PacketCustom(payload);
assertEquals(7, packet.getType());
assertEquals(expected.length, packet.getByteBuf().readableBytes());
byte[] actual = new byte[expected.length];
packet.getByteBuf().readBytes(actual);
assertArrayEquals(expected, actual);
} finally {
if (packet != null && packet.getByteBuf() != payload) packet.getByteBuf().release();
payload.release();
}
}
private static void assertRejected(ByteBuf payload) {
try {
assertThrows(IllegalArgumentException.class, () -> {
PacketCustom packet = new PacketCustom(payload);
if (packet.getByteBuf() != payload) packet.getByteBuf().release();
});
} finally {
payload.release();
}
}
}
@@ -0,0 +1,102 @@
package codechicken.lib.render;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Test;
import codechicken.lib.vec.Vector3;
public class CCRenderStateThreadingTest {
private static int[] attribIndices() {
return new int[] { CCRenderState.normalAttrib().attributeIndex, CCRenderState.colourAttrib().attributeIndex,
CCRenderState.lightingAttrib().attributeIndex, CCRenderState.sideAttrib().attributeIndex,
CCRenderState.lightCoordAttrib().attributeIndex };
}
@Test
public void attributeIndicesStableAcrossThreads() throws Exception {
int[] main = attribIndices();
assertEquals(5, Arrays.stream(main).distinct().count());
ExecutorService pool = Executors.newFixedThreadPool(8);
try {
List<Future<int[]>> futures = new ArrayList<>();
for (int i = 0; i < 8; i++) futures.add(pool.submit(CCRenderStateThreadingTest::attribIndices));
for (Future<int[]> f : futures) assertArrayEquals(main, f.get(10, TimeUnit.SECONDS));
} finally {
pool.shutdownNow();
}
}
@Test
public void modelBakedOnOneThreadIsReadableOnAnother() throws Exception {
ExecutorService bakeThread = Executors.newSingleThreadExecutor();
ExecutorService renderThread = Executors.newSingleThreadExecutor();
try {
CCModel model = bakeThread.submit(() -> {
CCModel m = CCModel.quadModel(4);
m.verts[0] = new Vertex5(0, 1, 0, 0, 0);
m.verts[1] = new Vertex5(0, 1, 1, 0, 1);
m.verts[2] = new Vertex5(1, 1, 1, 1, 1);
m.verts[3] = new Vertex5(1, 1, 0, 1, 0);
return m.computeNormals();
}).get(10, TimeUnit.SECONDS);
int side = renderThread.submit(() -> {
assertTrue(
model.hasAttribute(CCRenderState.normalAttrib()),
"normals baked on another thread not visible under this thread's attributeIndex");
Vector3[] normals = model.getAttributes(CCRenderState.normalAttrib());
assertNotNull(normals);
return CCModel.findSide(normals[0]);
}).get(10, TimeUnit.SECONDS);
assertEquals(1, side);
assertEquals(1, CCModel.findSide(model.getAttributes(CCRenderState.normalAttrib())[0]));
} finally {
bakeThread.shutdownNow();
renderThread.shutdownNow();
}
}
@Test
public void concurrentFirstUseKeepsIndicesConsistent() throws Exception {
int threads = 16;
CyclicBarrier barrier = new CyclicBarrier(threads);
ExecutorService pool = Executors.newFixedThreadPool(threads);
try {
List<Future<int[]>> futures = new ArrayList<>();
for (int i = 0; i < threads; i++) futures.add(pool.submit(() -> {
barrier.await(10, TimeUnit.SECONDS);
return attribIndices();
}));
int[] expected = attribIndices();
for (Future<int[]> f : futures) assertArrayEquals(expected, f.get(10, TimeUnit.SECONDS));
} finally {
pool.shutdownNow();
}
}
@Test
public void getAttributeReturnsCanonicalForIndex() {
int normalIndex = CCRenderState.normalAttrib().attributeIndex;
CCRenderState.VertexAttribute<?> canonical = CCRenderState.getAttribute(normalIndex);
assertNotNull(canonical);
assertEquals(normalIndex, canonical.attributeIndex);
assertSame(canonical, CCRenderState.getAttribute(normalIndex));
}
}
@@ -0,0 +1,20 @@
package codechicken.lib.render;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import net.minecraft.util.IIcon;
import org.junit.jupiter.api.Test;
public class EntityDigIconFXTest {
@Test
public void rejectsMismatchedIconAndColourCountsBeforeSpawning() {
IllegalArgumentException exception = assertThrows(
IllegalArgumentException.class,
() -> EntityDigIconFX.addBlockDestroyEffects(null, null, new IIcon[1], new int[0], null));
assertEquals("icons and colours must have the same length", exception.getMessage());
}
}