mirror of
https://github.com/MinecraftForge/MinecraftForge.git
synced 2026-11-14 09:05:17 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
16dde4d551 | ||
|
|
60661350c6 | ||
|
|
7ee4a44c75 | ||
|
|
f22894cf70 | ||
|
|
a3fce36942 | ||
|
|
549dbe7df4 | ||
|
|
5505e472aa | ||
|
|
c2c7f9b656 | ||
|
|
fd8a0f9c8b | ||
|
|
0ac873e296 | ||
|
|
38b5b678cb | ||
|
|
9a2bc1e7c2 | ||
|
|
95b49bedbc | ||
|
|
7c36115e3d | ||
|
|
c037e6aca1 | ||
|
|
e8f1b7ed6d | ||
|
|
5da16e6719 | ||
|
|
f0a82d79a6 | ||
|
|
2b3a4137e1 | ||
|
|
640c206117 | ||
|
|
59a7bbdb57 | ||
|
|
be7ad14b4b | ||
|
|
c700b4ea9d | ||
|
|
dffa3dd4c9 | ||
|
|
4b934809c4 | ||
|
|
ddf76db9b3 | ||
|
|
64d6b4f76a | ||
|
|
58012bfb17 | ||
|
|
0cad6b14d9 | ||
|
|
8848294210 | ||
|
|
0e28336fc0 | ||
|
|
968fa9eeb5 | ||
|
|
43768ea328 | ||
|
|
0e829630da | ||
|
|
0467d71466 | ||
|
|
115eb5e4c8 | ||
|
|
a7345372fd | ||
|
|
bd714b2f76 |
No files matched your search
@@ -140,7 +140,7 @@ abstract class CheckPatches extends CheckTask {
|
||||
final nextLineChange = i + 1 < lines.size() && lines[i + 1].startsWithAny('+','-')
|
||||
|
||||
if (!prevLineChange && !nextLineChange) {
|
||||
reporter.report("Patch contains white space change in valid hunk at line ${i + 1}, file: $patchPath", false)
|
||||
reporter.report("Patch contains white space change in valid hunk at line ${i + 1}, file: $patchPath \n$prevLine\n$line\n${lines[i+1]}", false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+8
-5
@@ -180,6 +180,7 @@ patcher {
|
||||
|
||||
forge_client_test {
|
||||
parent runs.forge_client
|
||||
source sourceSets.test
|
||||
}
|
||||
|
||||
forge_server {
|
||||
@@ -188,6 +189,7 @@ patcher {
|
||||
|
||||
forge_server_test {
|
||||
parent runs.forge_server
|
||||
source sourceSets.test
|
||||
}
|
||||
|
||||
forge_server_gametest {
|
||||
@@ -196,6 +198,7 @@ patcher {
|
||||
|
||||
forge_server_gametest_test {
|
||||
parent runs.forge_server_gametest
|
||||
source sourceSets.test
|
||||
}
|
||||
|
||||
forge_data {
|
||||
@@ -211,6 +214,7 @@ patcher {
|
||||
}
|
||||
|
||||
forge_data_test {
|
||||
source sourceSets.test
|
||||
args '--launchTarget', 'forge_dev_data',
|
||||
'--mod', '.+',
|
||||
'--all',
|
||||
@@ -221,7 +225,6 @@ patcher {
|
||||
'--assetsDir', downloadAssets.output,
|
||||
'--assetIndex', "{asset_index}"
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -480,10 +483,10 @@ tasks.register('createServerShimClasspath', BundleList) {
|
||||
tasks.register('createServerShimConfig') {
|
||||
ext.output = file('build/libs/bootstrap-shim.properties')
|
||||
doLast {
|
||||
def cfg = new CleanProperties()
|
||||
cfg."Main-Class" = 'net.minecraftforge.bootstrap.ForgeBootstrap'
|
||||
cfg."Java-Version" = '17'
|
||||
cfg."Arguments" = "--launchTarget forge_server"
|
||||
var cfg = new CleanProperties()
|
||||
cfg['Main-Class'] = 'net.minecraftforge.bootstrap.ForgeBootstrap'
|
||||
cfg['Java-Version'] = '21'
|
||||
cfg['Arguments'] = '--launchTarget forge_server'
|
||||
cfg.store(output)
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -2,8 +2,8 @@
|
||||
|
||||
MinecraftForge
|
||||
=============
|
||||
[][Download]
|
||||
[][Download]
|
||||
[][Download] [][Discord] [][Patreon]
|
||||
|
||||
Forge is a free, open-source modding API all of your favourite mods use!
|
||||
|
||||
@@ -16,8 +16,7 @@ import net.minecraftforge.api.distmarker.Dist;
|
||||
*
|
||||
* @see Dist
|
||||
*/
|
||||
public enum LogicalSide
|
||||
{
|
||||
public enum LogicalSide {
|
||||
/**
|
||||
* The logical client of the Minecraft game, which interfaces with the player's inputs and renders the player's
|
||||
* viewpoint.
|
||||
@@ -27,6 +26,7 @@ public enum LogicalSide
|
||||
* @see Dist#CLIENT
|
||||
*/
|
||||
CLIENT,
|
||||
|
||||
/**
|
||||
* The logical server of the Minecraft game, responsible for connecting to clients and running the simulation logic
|
||||
* on the level.
|
||||
@@ -41,16 +41,14 @@ public enum LogicalSide
|
||||
/**
|
||||
* {@return if this logical side is the server}
|
||||
*/
|
||||
public boolean isServer()
|
||||
{
|
||||
public boolean isServer() {
|
||||
return !isClient();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return if the logical side is the client}
|
||||
*/
|
||||
public boolean isClient()
|
||||
{
|
||||
public boolean isClient() {
|
||||
return this == CLIENT;
|
||||
}
|
||||
}
|
||||
@@ -48,8 +48,8 @@ public class VersionChecker {
|
||||
BETA(),
|
||||
BETA_OUTDATED(6, true, true);
|
||||
|
||||
final int sheetOffset;
|
||||
final boolean draw, animated;
|
||||
private final int sheetOffset;
|
||||
private final boolean draw, animated;
|
||||
|
||||
Status() {
|
||||
this(0, false, false);
|
||||
@@ -72,6 +72,10 @@ public class VersionChecker {
|
||||
public boolean isAnimated() {
|
||||
return animated;
|
||||
}
|
||||
|
||||
public boolean isOutdated() {
|
||||
return this == OUTDATED || this == BETA_OUTDATED;
|
||||
}
|
||||
}
|
||||
|
||||
public record CheckResult(VersionChecker.Status status, ComparableVersion target, Map<ComparableVersion, String> changes, String url) {}
|
||||
@@ -211,7 +215,7 @@ public class VersionChecker {
|
||||
LOGGER.warn("Failed to process update information", e);
|
||||
status = FAILED;
|
||||
}
|
||||
results.put(mod, new CheckResult(status, target, changes, display_url));
|
||||
RESULTS.put(mod, new CheckResult(status, target, changes, display_url));
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
@@ -226,10 +230,10 @@ public class VersionChecker {
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static Map<IModInfo, CheckResult> results = new ConcurrentHashMap<>();
|
||||
private static final Map<IModInfo, CheckResult> RESULTS = new ConcurrentHashMap<>();
|
||||
private static final CheckResult PENDING_CHECK = new CheckResult(PENDING, null, null, null);
|
||||
|
||||
public static CheckResult getResult(IModInfo mod) {
|
||||
return results.getOrDefault(mod, PENDING_CHECK);
|
||||
return RESULTS.getOrDefault(mod, PENDING_CHECK);
|
||||
}
|
||||
}
|
||||
@@ -67,7 +67,9 @@ public class ConfigFileTypeHandler {
|
||||
}
|
||||
|
||||
private boolean setupConfigFile(final ModConfig modConfig, final Path file, final ConfigFormat<?> conf) throws IOException {
|
||||
Files.createDirectories(file.getParent());
|
||||
if (!Files.isDirectory(file.getParent())) {
|
||||
Files.createDirectories(file.getParent());
|
||||
}
|
||||
Path p = defaultConfigPath.resolve(modConfig.getFileName());
|
||||
if (Files.exists(p)) {
|
||||
LOGGER.info(CONFIG, "Loading default config file from path {}", p);
|
||||
|
||||
+14
@@ -10,6 +10,8 @@ import cpw.mods.jarhandling.SecureJar;
|
||||
import cpw.mods.modlauncher.api.LamdbaExceptionUtils;
|
||||
import cpw.mods.modlauncher.api.NamedPath;
|
||||
import cpw.mods.modlauncher.serviceapi.ITransformerDiscoveryService;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -20,6 +22,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public class ModDirTransformerDiscoverer implements ITransformerDiscoveryService {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private static final Set<String> SERVICES = Set.of(
|
||||
@@ -76,4 +79,15 @@ public class ModDirTransformerDiscoverer implements ITransformerDiscoveryService
|
||||
.filter(SERVICES::contains)
|
||||
.forEach(s -> found.add(new NamedPath(s, path)));
|
||||
}
|
||||
|
||||
public static boolean isServiceProvider(Path path) {
|
||||
var jar = SecureJar.from(path);
|
||||
|
||||
for (var providers : jar.moduleDataProvider().descriptor().provides()) {
|
||||
if (SERVICES.contains(providers.service()))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -57,11 +57,17 @@ public class RuntimeDistCleaner implements ILaunchPluginService
|
||||
throw new RuntimeException("Attempted to load class "+ classNode.name + " for invalid dist "+ DIST);
|
||||
}
|
||||
|
||||
if (!FMLEnvironment.production && hasOnlyInWithModAnnotation(classNode.visibleAnnotations))
|
||||
{
|
||||
LOGGER.error(DISTXFORM, "Attempted to load class {} with @Mod and @OnlyIn/@OnlyIns annotations", classNode.name);
|
||||
throw new RuntimeException("Found @OnlyIn on @Mod class "+ classNode.name + " - this is not allowed as it causes crashes. Remove the OnlyIn and consider setting clientSideOnly=true in the root of your mods.toml instead");
|
||||
}
|
||||
|
||||
if (classNode.interfaces != null )
|
||||
{
|
||||
unpack(classNode.visibleAnnotations).stream()
|
||||
.filter(ann->Objects.equals(ann.desc, ONLYIN))
|
||||
.filter(ann->ann.values.indexOf("_interface") != -1)
|
||||
.filter(ann-> ann.values.contains("_interface"))
|
||||
.filter(ann->!Objects.equals(((String[])ann.values.get(ann.values.indexOf("value") + 1))[1], DIST))
|
||||
.map(ann -> ((Type)ann.values.get(ann.values.indexOf("_interface") + 1)).getInternalName())
|
||||
.forEach(intf -> {
|
||||
@@ -137,22 +143,78 @@ public class RuntimeDistCleaner implements ILaunchPluginService
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static List<AnnotationNode> unpack(final List<AnnotationNode> anns) {
|
||||
if (anns == null) return Collections.emptyList();
|
||||
List<AnnotationNode> ret = anns.stream().filter(ann->Objects.equals(ann.desc, ONLYIN)).collect(Collectors.toList());
|
||||
anns.stream().filter(ann->Objects.equals(ann.desc, ONLYINS) && ann.values != null)
|
||||
.map( ann -> (List<AnnotationNode>)ann.values.get(ann.values.indexOf("value") + 1))
|
||||
.filter(v -> v != null)
|
||||
.forEach(v -> v.forEach(ret::add));
|
||||
return ret;
|
||||
private static List<AnnotationNode> unpack(final List<AnnotationNode> anns)
|
||||
{
|
||||
if (anns == null)
|
||||
{
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<AnnotationNode> unpacked = new ArrayList<>();
|
||||
|
||||
for (var annotationNode : anns)
|
||||
{
|
||||
if (Objects.equals(annotationNode.desc, ONLYINS))
|
||||
{
|
||||
unpacked.add(annotationNode);
|
||||
|
||||
if (annotationNode.values != null)
|
||||
{
|
||||
List<AnnotationNode> subNodes = (List<AnnotationNode>) annotationNode.values.get(annotationNode.values.indexOf("value") + 1);
|
||||
|
||||
if (subNodes != null)
|
||||
{
|
||||
unpacked.addAll(subNodes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return unpacked;
|
||||
}
|
||||
|
||||
private boolean remove(final List<AnnotationNode> anns, final String side)
|
||||
{
|
||||
return unpack(anns).stream().
|
||||
filter(ann->Objects.equals(ann.desc, ONLYIN)).
|
||||
filter(ann->ann.values.indexOf("_interface") == -1).
|
||||
anyMatch(ann -> !Objects.equals(((String[])ann.values.get(ann.values.indexOf("value")+1))[1], side));
|
||||
var onlyIns = unpack(anns);
|
||||
|
||||
for (var onlyIn : onlyIns)
|
||||
{
|
||||
if (!Objects.equals(onlyIn.desc, ONLYINS) || onlyIn.values.contains("_interface"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!Objects.equals(((String[])onlyIn.values.get(onlyIn.values.indexOf("value") + 1))[1], side))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean hasOnlyInWithModAnnotation(final List<AnnotationNode> anns)
|
||||
{
|
||||
if (anns == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var foundModAnnotation = false;
|
||||
var foundOnlyIn = false;
|
||||
|
||||
for (var ann : anns)
|
||||
{
|
||||
if (ann.desc.equals("Lnet/minecraftforge/fml/common/Mod;"))
|
||||
{
|
||||
foundModAnnotation = true;
|
||||
} else if (Objects.equals(ann.desc, ONLYIN) || Objects.equals(ann.desc, ONLYINS))
|
||||
{
|
||||
foundOnlyIn = true;
|
||||
}
|
||||
}
|
||||
|
||||
return foundModAnnotation && foundOnlyIn;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
||||
+8
-1
@@ -8,7 +8,6 @@ package net.minecraftforge.fml.loading.moddiscovery;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import cpw.mods.jarhandling.JarMetadata;
|
||||
import cpw.mods.jarhandling.SecureJar;
|
||||
import net.minecraftforge.fml.loading.EarlyLoadingException;
|
||||
import net.minecraftforge.fml.loading.LogMarkers;
|
||||
import net.minecraftforge.forgespi.language.IConfigurable;
|
||||
import net.minecraftforge.forgespi.language.IModFileInfo;
|
||||
@@ -19,6 +18,7 @@ import net.minecraftforge.forgespi.locating.IModProvider;
|
||||
import net.minecraftforge.forgespi.locating.ModFileLoadingException;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -37,6 +37,11 @@ public abstract class AbstractModProvider implements IModProvider {
|
||||
protected static final String MODS_TOML = "META-INF/mods.toml";
|
||||
|
||||
protected IModLocator.ModFileOrException createMod(Path path) {
|
||||
return createMod(path, false);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected IModLocator.ModFileOrException createMod(Path path, boolean ignoreUnknown) {
|
||||
var mjm = new ModJarMetadata();
|
||||
var sj = SecureJar.from(
|
||||
jar -> jar.moduleDataProvider().findFile(MODS_TOML).isPresent() ? mjm : JarMetadata.from(jar, path),
|
||||
@@ -58,6 +63,8 @@ public abstract class AbstractModProvider implements IModProvider {
|
||||
} else if (type != null) {
|
||||
LOGGER.debug(LogMarkers.SCAN, "Found {} mod of type {}: {}", JarFile.MANIFEST_NAME, type, path);
|
||||
mod = new ModFile(sj, this, this::manifestParser, type);
|
||||
} else if (ignoreUnknown) {
|
||||
return null;
|
||||
} else
|
||||
return new IModLocator.ModFileOrException(null, new ModFileLoadingException("Invalid mod file found " + path));
|
||||
|
||||
|
||||
+6
-2
@@ -8,6 +8,7 @@ package net.minecraftforge.fml.loading.moddiscovery;
|
||||
import com.mojang.logging.LogUtils;
|
||||
|
||||
import net.minecraftforge.fml.loading.LogMarkers;
|
||||
import net.minecraftforge.fml.loading.ModDirTransformerDiscoverer;
|
||||
import net.minecraftforge.forgespi.locating.IModLocator;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
@@ -71,8 +72,11 @@ public class ClasspathLocator extends AbstractModProvider implements IModLocator
|
||||
}
|
||||
|
||||
var ret = new ArrayList<ModFileOrException>();
|
||||
for (var path : claimed)
|
||||
ret.add(createMod(path));
|
||||
for (var path : claimed) {
|
||||
// Filter out anything found by the ServiceLoader
|
||||
if (!ModDirTransformerDiscoverer.isServiceProvider(path))
|
||||
ret.add(createMod(path));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
+16
-5
@@ -16,6 +16,7 @@ import org.slf4j.Logger;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@@ -42,11 +43,21 @@ public class ModsFolderLocator extends AbstractModProvider implements IModLocato
|
||||
public List<IModLocator.ModFileOrException> scanMods() {
|
||||
LOGGER.debug(LogMarkers.SCAN, "Scanning mods dir {} for mods", this.modFolder);
|
||||
var excluded = ModDirTransformerDiscoverer.allExcluded();
|
||||
try (var stream = Files.list(this.modFolder)) {
|
||||
return stream.filter(path -> !excluded.contains(path) && path.getFileName().toString().toLowerCase(Locale.ROOT).endsWith(SUFFIX))
|
||||
.sorted(Comparator.comparing(path -> path.getFileName().toString().toLowerCase(Locale.ROOT)))
|
||||
.map(this::createMod)
|
||||
.toList();
|
||||
try {
|
||||
var ret = new ArrayList<IModLocator.ModFileOrException>();
|
||||
var files = Files.list(this.modFolder).toList();
|
||||
for (var file : files) {
|
||||
var name = file.getFileName().toString().toLowerCase(Locale.ROOT);
|
||||
if (excluded.contains(file) || !name.endsWith(SUFFIX))
|
||||
continue;
|
||||
var mod = this.createMod(file, true);
|
||||
if (mod == null) {
|
||||
LOGGER.debug(LogMarkers.SCAN, "Found unknown jar file {} ignoring", file);
|
||||
} else {
|
||||
ret.add(mod);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
} catch (IOException e) {
|
||||
return sneak(e);
|
||||
}
|
||||
|
||||
+12
@@ -6,6 +6,7 @@
|
||||
package net.minecraftforge.fml.javafmlmod;
|
||||
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import net.minecraftforge.fml.Logging;
|
||||
import net.minecraftforge.fml.ModContainer;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
@@ -19,6 +20,7 @@ import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.objectweb.asm.Type;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -32,6 +34,7 @@ public class AutomaticEventSubscriber {
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
private static final Type AUTO_SUBSCRIBER = Type.getType(EventBusSubscriber.class);
|
||||
private static final Type MOD_TYPE = Type.getType(Mod.class);
|
||||
private static final Type ONLY_IN_TYPE = Type.getType(OnlyIn.class);
|
||||
|
||||
public static void inject(ModContainer mod, ModFileScanData scanData, ClassLoader loader) {
|
||||
if (scanData == null) return;
|
||||
@@ -41,6 +44,11 @@ public class AutomaticEventSubscriber {
|
||||
.filter(data -> AUTO_SUBSCRIBER.equals(data.annotationType()))
|
||||
.toList();
|
||||
|
||||
var onlyIns = FMLEnvironment.production ? Collections.emptySet() : scanData.getAnnotations().stream()
|
||||
.filter(data -> ONLY_IN_TYPE.equals(data.annotationType()))
|
||||
.map(data -> data.clazz().getClassName())
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
var modids = scanData.getAnnotations().stream()
|
||||
.filter(data -> MOD_TYPE.equals(data.annotationType()))
|
||||
.collect(Collectors.toMap(a -> a.clazz().getClassName(), a -> (String)a.annotationData().get("value")));
|
||||
@@ -49,6 +57,10 @@ public class AutomaticEventSubscriber {
|
||||
var defaultBus = new EnumData(null, "FORGE");
|
||||
|
||||
for (var data : targets) {
|
||||
if (!FMLEnvironment.production && onlyIns.contains(data.clazz().getClassName())) {
|
||||
throw new RuntimeException("Found @OnlyIn on @EventBusSubscriber class " + data.clazz().getClassName() + " - this is not allowed as it causes crashes. Remove the OnlyIn and set value=Dist.CLIENT in the EventBusSubscriber annotation instead");
|
||||
}
|
||||
|
||||
var modId = modids.getOrDefault(data.clazz().getClassName(), mod.getModId());
|
||||
modId = value(data, "modid", modId);
|
||||
|
||||
|
||||
+5
-2
@@ -2,7 +2,7 @@ plugins {
|
||||
id 'eclipse'
|
||||
id 'idea'
|
||||
id 'maven-publish'
|
||||
id 'net.minecraftforge.gradle' version '[6.0.16,6.2)'
|
||||
id 'net.minecraftforge.gradle' version '[6.0.24,6.2)'
|
||||
}
|
||||
|
||||
version = mod_version
|
||||
@@ -31,6 +31,9 @@ minecraft {
|
||||
// Use non-default mappings at your own risk. They may not always work.
|
||||
// Simply re-run your setup task after changing the mappings to update your workspace.
|
||||
mappings channel: mapping_channel, version: mapping_version
|
||||
|
||||
// Tell FG to not automtically create the reobf tasks, as we now use Official mappings at runtime, If you don't use them at dev time then you'll have to fix your reobf yourself.
|
||||
reobf = false
|
||||
|
||||
// When true, this property will have all Eclipse/IntelliJ IDEA run configurations run the "prepareX" task for the given run configuration before launching the game.
|
||||
// In most cases, it is not necessary to enable.
|
||||
@@ -139,7 +142,7 @@ dependencies {
|
||||
// http://www.gradle.org/docs/current/userguide/artifact_dependencies_tutorial.html
|
||||
// http://www.gradle.org/docs/current/userguide/dependency_management.html
|
||||
|
||||
// Hack fix for now, force jopt-simple to be exactly 5.0.4 because Mojang ships that version, but some transtive dependencies request 6.0+
|
||||
// Hack fix for now, force jopt-simple to be exactly 5.0.4 because Mojang ships that version, but some transitive dependencies request 6.0+
|
||||
implementation('net.sf.jopt-simple:jopt-simple:5.0.4') { version { strictly '5.0.4' } }
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
--- a/com/mojang/blaze3d/pipeline/RenderTarget.java
|
||||
+++ b/com/mojang/blaze3d/pipeline/RenderTarget.java
|
||||
@@ -113,11 +_,19 @@
|
||||
GlStateManager._bindTexture(this.colorTextureId);
|
||||
GlStateManager._texParameter(3553, 10242, 33071);
|
||||
GlStateManager._texParameter(3553, 10243, 33071);
|
||||
+ if (!stencilEnabled)
|
||||
@@ -106,7 +_,10 @@
|
||||
GlStateManager._texParameter(3553, 34892, 0);
|
||||
GlStateManager._texParameter(3553, 10242, 33071);
|
||||
GlStateManager._texParameter(3553, 10243, 33071);
|
||||
+ if (!stencilEnabled)
|
||||
GlStateManager._texImage2D(3553, 0, 6402, this.width, this.height, 0, 6402, 5126, null);
|
||||
+ else
|
||||
+ GlStateManager._texImage2D(3553, 0, org.lwjgl.opengl.GL30.GL_DEPTH32F_STENCIL8, this.width, this.height, 0, org.lwjgl.opengl.GL30.GL_DEPTH_STENCIL, org.lwjgl.opengl.GL30.GL_FLOAT_32_UNSIGNED_INT_24_8_REV, null);
|
||||
}
|
||||
|
||||
this.setFilterMode(9728, true);
|
||||
@@ -116,8 +_,13 @@
|
||||
GlStateManager._texImage2D(3553, 0, 32856, this.width, this.height, 0, 6408, 5121, null);
|
||||
+ else
|
||||
+ GlStateManager._texImage2D(3553, 0, org.lwjgl.opengl.GL30.GL_DEPTH32F_STENCIL8, this.width, this.height, 0, org.lwjgl.opengl.GL30.GL_DEPTH_STENCIL, org.lwjgl.opengl.GL30.GL_FLOAT_32_UNSIGNED_INT_24_8_REV, null);
|
||||
GlStateManager._glBindFramebuffer(36160, this.frameBufferId);
|
||||
GlStateManager._glFramebufferTexture2D(36160, 36064, 3553, this.colorTextureId, 0);
|
||||
- if (this.useDepth) {
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
--- a/com/mojang/blaze3d/vertex/VertexFormat.java
|
||||
+++ b/com/mojang/blaze3d/vertex/VertexFormat.java
|
||||
@@ -114,6 +_,13 @@
|
||||
@@ -12,7 +_,7 @@
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
-public class VertexFormat {
|
||||
+public class VertexFormat implements net.minecraftforge.client.extensions.IForgeVertexFormat {
|
||||
private final ImmutableList<VertexFormatElement> elements;
|
||||
private final ImmutableMap<String, VertexFormatElement> elementMapping;
|
||||
private final IntList offsets = new IntArrayList();
|
||||
@@ -113,6 +_,9 @@
|
||||
|
||||
return vertexbuffer;
|
||||
}
|
||||
|
||||
+
|
||||
+ public ImmutableMap<String, VertexFormatElement> getElementMapping() { return elementMapping; }
|
||||
+ public int getOffset(int index) { return offsets.getInt(index); }
|
||||
+ public boolean hasPosition() { return elements.stream().anyMatch(e -> e.getUsage() == VertexFormatElement.Usage.POSITION); }
|
||||
+ public boolean hasNormal() { return elements.stream().anyMatch(e -> e.getUsage() == VertexFormatElement.Usage.NORMAL); }
|
||||
+ public boolean hasColor() { return elements.stream().anyMatch(e -> e.getUsage() == VertexFormatElement.Usage.COLOR); }
|
||||
+ public boolean hasUV(int which) { return elements.stream().anyMatch(e -> e.getUsage() == VertexFormatElement.Usage.UV && e.getIndex() == which); }
|
||||
+
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public static enum IndexType {
|
||||
SHORT(5123, 2),
|
||||
@@ -9,6 +9,15 @@
|
||||
private final SubtitleOverlay subtitleOverlay;
|
||||
private final SpectatorGui spectatorGui;
|
||||
private final PlayerTabOverlay tabList;
|
||||
@@ -320,7 +_,7 @@
|
||||
Window window = this.minecraft.getWindow();
|
||||
int i = Mth.floor(this.minecraft.mouseHandler.xpos() * (double)window.getGuiScaledWidth() / (double)window.getScreenWidth());
|
||||
int j = Mth.floor(this.minecraft.mouseHandler.ypos() * (double)window.getGuiScaledHeight() / (double)window.getScreenHeight());
|
||||
- this.chat.render(p_329202_, this.tickCount, i, j, false);
|
||||
+ net.minecraftforge.client.ForgeHooksClient.onCustomizeChatEvent(p_329202_, this.chat, window, i, j, this.tickCount);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -444,6 +_,8 @@
|
||||
|
||||
for (MobEffectInstance mobeffectinstance : Ordering.natural().reverse().sortedCopy(collection)) {
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
list.add("");
|
||||
boolean flag = this.minecraft.getSingleplayerServer() != null;
|
||||
list.add(
|
||||
@@ -176,7 +_,12 @@
|
||||
@@ -176,11 +_,18 @@
|
||||
+ (this.renderNetworkCharts ? " visible" : " hidden")
|
||||
);
|
||||
list.add("For help: press F3 + Q");
|
||||
@@ -79,10 +79,16 @@
|
||||
+ protected void drawGameInformation(GuiGraphics p_281525_) {
|
||||
+ List<String> list = this.getGameInformation();
|
||||
+ list.addAll(this.getOverlayHelp());
|
||||
+ net.minecraftforge.client.ForgeHooksClient.onCustomizeDebugEvent(p_281525_, minecraft.getWindow(), minecraft.getFrameTime(), list, true);
|
||||
+ this.renderLines(p_281525_, list, true);
|
||||
}
|
||||
|
||||
protected void drawSystemInformation(GuiGraphics p_281261_) {
|
||||
List<String> list = this.getSystemInformation();
|
||||
+ net.minecraftforge.client.ForgeHooksClient.onCustomizeDebugEvent(p_281261_, minecraft.getWindow(), minecraft.getFrameTime(), list, false);
|
||||
this.renderLines(p_281261_, list, false);
|
||||
}
|
||||
|
||||
@@ -529,6 +_,7 @@
|
||||
list.add("");
|
||||
list.add(ChatFormatting.UNDERLINE + "Targeted Entity");
|
||||
|
||||
+9
-9
@@ -146,19 +146,11 @@
|
||||
|
||||
this.refreshSearchResults();
|
||||
} else {
|
||||
@@ -642,7 +_,7 @@
|
||||
@@ -642,7 +_,15 @@
|
||||
public void render(GuiGraphics p_283000_, int p_281317_, int p_282770_, float p_281295_) {
|
||||
super.render(p_283000_, p_281317_, p_282770_, p_281295_);
|
||||
|
||||
- for (CreativeModeTab creativemodetab : CreativeModeTabs.tabs()) {
|
||||
+ for (CreativeModeTab creativemodetab : currentPage.getVisibleTabs()) {
|
||||
if (this.checkTabHovering(p_283000_, creativemodetab, p_281317_, p_282770_)) {
|
||||
break;
|
||||
}
|
||||
@@ -653,7 +_,15 @@
|
||||
&& this.isHovering(this.destroyItemSlot.x, this.destroyItemSlot.y, 16, 16, (double)p_281317_, (double)p_282770_)) {
|
||||
p_283000_.renderTooltip(this.font, TRASH_SLOT_TOOLTIP, p_281317_, p_282770_);
|
||||
}
|
||||
+ if (this.pages.size() != 1) {
|
||||
+ Component page = Component.literal(String.format("%d / %d", this.pages.indexOf(this.currentPage) + 1, this.pages.size()));
|
||||
+ p_283000_.pose().pushPose();
|
||||
@@ -166,6 +158,14 @@
|
||||
+ p_283000_.drawString(font, page.getVisualOrderText(), leftPos + (imageWidth / 2) - (font.width(page) / 2), topPos - 44, -1);
|
||||
+ p_283000_.pose().popPose();
|
||||
+ }
|
||||
+
|
||||
+ for (CreativeModeTab creativemodetab : currentPage.getVisibleTabs()) {
|
||||
if (this.checkTabHovering(p_283000_, creativemodetab, p_281317_, p_282770_)) {
|
||||
break;
|
||||
}
|
||||
@@ -654,6 +_,7 @@
|
||||
p_283000_.renderTooltip(this.font, TRASH_SLOT_TOOLTIP, p_281317_, p_282770_);
|
||||
}
|
||||
|
||||
+ com.mojang.blaze3d.systems.RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F);
|
||||
this.renderTooltip(p_283000_, p_281317_, p_282770_);
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
--- a/net/minecraft/client/model/geom/ModelLayers.java
|
||||
+++ b/net/minecraft/client/model/geom/ModelLayers.java
|
||||
@@ -228,11 +_,13 @@
|
||||
}
|
||||
|
||||
public static ModelLayerLocation createSignModelName(WoodType p_171292_) {
|
||||
- return createLocation("sign/" + p_171292_.name(), "main");
|
||||
+ ResourceLocation location = new ResourceLocation(p_171292_.name());
|
||||
+ return new ModelLayerLocation(new ResourceLocation(location.getNamespace(), "sign/" + location.getPath()), "main");
|
||||
}
|
||||
|
||||
public static ModelLayerLocation createHangingSignModelName(WoodType p_252225_) {
|
||||
- return createLocation("hanging_sign/" + p_252225_.name(), "main");
|
||||
+ ResourceLocation location = new ResourceLocation(p_252225_.name());
|
||||
+ return new ModelLayerLocation(new ResourceLocation(location.getNamespace(), "hanging_sign/" + location.getPath()), "main");
|
||||
}
|
||||
|
||||
public static Stream<ModelLayerLocation> getKnownLocations() {
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
|
||||
@Override
|
||||
public void handleCustomPayload(ClientboundCustomPayloadPacket p_298103_) {
|
||||
+ if (net.minecraftforge.common.ForgeHooks.onCustomPayload(p_298103_, this.connection)) return;
|
||||
+ if (net.minecraftforge.common.ForgeHooks.onCustomPayload(p_298103_.payload(), this.connection)) return;
|
||||
CustomPacketPayload custompacketpayload = p_298103_.payload();
|
||||
if (!(custompacketpayload instanceof DiscardedPayload)) {
|
||||
PacketUtils.ensureRunningOnSameThread(p_298103_, this, this.minecraft);
|
||||
@@ -31,13 +31,13 @@
|
||||
0.0F,
|
||||
1000.0F,
|
||||
- 21000.0F
|
||||
+ 21000.0f
|
||||
+ net.minecraftforge.client.ForgeHooksClient.getGuiFarPlane()
|
||||
);
|
||||
RenderSystem.setProjectionMatrix(matrix4f, VertexSorting.ORTHOGRAPHIC_Z);
|
||||
Matrix4fStack matrix4fstack = RenderSystem.getModelViewStack();
|
||||
matrix4fstack.pushMatrix();
|
||||
- matrix4fstack.translation(0.0F, 0.0F, -11000.0F);
|
||||
+ matrix4fstack.translation(0.0F, 0.0F, 1000F - net.minecraftforge.client.ForgeHooksClient.getGuiFarPlane());
|
||||
+ matrix4fstack.translation(0.0F, 0.0F, 10000F - net.minecraftforge.client.ForgeHooksClient.getGuiFarPlane());
|
||||
RenderSystem.applyModelViewMatrix();
|
||||
Lighting.setupFor3DItems();
|
||||
GuiGraphics guigraphics = new GuiGraphics(this.minecraft, this.renderBuffers.bufferSource());
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
if (iteminhandrenderer$handrenderselection.renderOffHand) {
|
||||
float f6 = interactionhand == InteractionHand.OFF_HAND ? f : 0.0F;
|
||||
float f7 = 1.0F - Mth.lerp(p_109315_, this.oOffHandHeight, this.offHandHeight);
|
||||
+ if (!net.minecraftforge.client.ForgeHooksClient.renderSpecificFirstPersonHand(InteractionHand.MAIN_HAND, p_109316_, p_109317_, p_109319_, p_109315_, f1, f6, f7, this.mainHandItem))
|
||||
+ if (!net.minecraftforge.client.ForgeHooksClient.renderSpecificFirstPersonHand(InteractionHand.OFF_HAND, p_109316_, p_109317_, p_109319_, p_109315_, f1, f6, f7, this.offHandItem))
|
||||
this.renderArmWithItem(p_109318_, p_109315_, f1, InteractionHand.OFF_HAND, f6, this.offHandItem, f7, p_109316_, p_109317_, p_109319_);
|
||||
}
|
||||
|
||||
|
||||
+9
-31
@@ -1,5 +1,14 @@
|
||||
--- a/net/minecraft/client/renderer/entity/layers/ElytraLayer.java
|
||||
+++ b/net/minecraft/client/renderer/entity/layers/ElytraLayer.java
|
||||
@@ -23,7 +_,7 @@
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
-public class ElytraLayer<T extends LivingEntity, M extends EntityModel<T>> extends RenderLayer<T, M> {
|
||||
+public class ElytraLayer<T extends LivingEntity, M extends EntityModel<T>> extends RenderLayer<T, M> implements net.minecraftforge.client.extensions.IForgeElytraLayer<T> {
|
||||
public static final ResourceLocation WINGS_LOCATION = new ResourceLocation("textures/entity/elytra.png");
|
||||
private final ElytraModel<T> elytraModel;
|
||||
|
||||
@@ -45,7 +_,7 @@
|
||||
float p_116960_
|
||||
) {
|
||||
@@ -22,34 +31,3 @@
|
||||
}
|
||||
|
||||
p_116951_.pushPose();
|
||||
@@ -68,5 +_,30 @@
|
||||
this.elytraModel.renderToBuffer(p_116951_, vertexconsumer, p_116953_, OverlayTexture.NO_OVERLAY, 1.0F, 1.0F, 1.0F, 1.0F);
|
||||
p_116951_.popPose();
|
||||
}
|
||||
+ }
|
||||
+
|
||||
+ /**
|
||||
+ * Determines if the ElytraLayer should render.
|
||||
+ * ItemStack and Entity are provided for modder convenience,
|
||||
+ * For example, using the same ElytraLayer for multiple custom Elytra.
|
||||
+ *
|
||||
+ * @param stack The Elytra ItemStack
|
||||
+ * @param entity The entity being rendered.
|
||||
+ * @return If the ElytraLayer should render.
|
||||
+ */
|
||||
+ public boolean shouldRender(ItemStack stack, T entity) {
|
||||
+ return stack.getItem() == Items.ELYTRA;
|
||||
+ }
|
||||
+
|
||||
+ /**
|
||||
+ * Gets the texture to use with this ElytraLayer.
|
||||
+ * This assumes the vanilla Elytra model.
|
||||
+ *
|
||||
+ * @param stack The Elytra ItemStack.
|
||||
+ * @param entity The entity being rendered.
|
||||
+ * @return The texture.
|
||||
+ */
|
||||
+ public ResourceLocation getElytraTexture(ItemStack stack, T entity) {
|
||||
+ return WINGS_LOCATION;
|
||||
}
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
--- a/net/minecraft/client/renderer/entity/layers/HumanoidArmorLayer.java
|
||||
+++ b/net/minecraft/client/renderer/entity/layers/HumanoidArmorLayer.java
|
||||
@@ -66,6 +_,7 @@
|
||||
if (armoritem.getEquipmentSlot() == p_117122_) {
|
||||
this.getParentModel().copyPropertiesTo(p_117124_);
|
||||
this.setPartVisibility(p_117124_, p_117122_);
|
||||
+ net.minecraft.client.model.Model model = getArmorModelHook(p_117121_, itemstack, p_117122_, p_117124_);
|
||||
boolean flag = this.usesInnerModel(p_117122_);
|
||||
ArmorMaterial armormaterial = armoritem.getMaterial().value();
|
||||
int i = itemstack.is(ItemTags.DYEABLE) ? DyedItemColor.getOrDefault(itemstack, -6265536) : -1;
|
||||
@@ -83,17 +_,17 @@
|
||||
f1 = 1.0F;
|
||||
f2 = 1.0F;
|
||||
}
|
||||
-
|
||||
- this.renderModel(p_117119_, p_117120_, p_117123_, p_117124_, f, f1, f2, armormaterial$layer.texture(flag));
|
||||
+ var texture = net.minecraftforge.client.ForgeHooksClient.getArmorTexture(p_117121_, itemstack, p_117122_, armormaterial$layer, flag);
|
||||
+ this.renderModel(p_117119_, p_117120_, p_117123_, model, f, f1, f2, texture);
|
||||
}
|
||||
|
||||
ArmorTrim armortrim = itemstack.get(DataComponents.TRIM);
|
||||
if (armortrim != null) {
|
||||
- this.renderTrim(armoritem.getMaterial(), p_117119_, p_117120_, p_117123_, armortrim, p_117124_, flag);
|
||||
+ this.renderTrim(armoritem.getMaterial(), p_117119_, p_117120_, p_117123_, armortrim, model, flag);
|
||||
}
|
||||
|
||||
if (itemstack.hasFoil()) {
|
||||
- this.renderGlint(p_117119_, p_117120_, p_117123_, p_117124_);
|
||||
+ this.renderGlint(p_117119_, p_117120_, p_117123_, model);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -122,11 +_,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
+ private void renderModel(PoseStack p_289664_, MultiBufferSource p_289689_, int p_289681_, A p_289658_, float p_289678_, float p_289674_, float p_289693_, ResourceLocation p_328978_) {
|
||||
+ renderModel(p_289664_, p_289689_, p_289681_, (net.minecraft.client.model.Model)p_289658_, p_289678_, p_289674_, p_289693_, p_328978_);
|
||||
+ }
|
||||
+
|
||||
private void renderModel(
|
||||
PoseStack p_289664_,
|
||||
MultiBufferSource p_289689_,
|
||||
int p_289681_,
|
||||
- A p_289658_,
|
||||
+ net.minecraft.client.model.Model p_289658_,
|
||||
float p_289678_,
|
||||
float p_289674_,
|
||||
float p_289693_,
|
||||
@@ -136,8 +_,12 @@
|
||||
p_289658_.renderToBuffer(p_289664_, vertexconsumer, p_289681_, OverlayTexture.NO_OVERLAY, p_289678_, p_289674_, p_289693_, 1.0F);
|
||||
}
|
||||
|
||||
+ private void renderTrim(Holder<ArmorMaterial> p_331988_, PoseStack p_289687_, MultiBufferSource p_289643_, int p_289683_, ArmorTrim p_289692_, A p_289663_, boolean p_289651_) {
|
||||
+ renderTrim(p_331988_, p_289687_, p_289643_, p_289683_, p_289692_, (net.minecraft.client.model.Model)p_289663_, p_289651_);
|
||||
+ }
|
||||
+
|
||||
private void renderTrim(
|
||||
- Holder<ArmorMaterial> p_331988_, PoseStack p_289687_, MultiBufferSource p_289643_, int p_289683_, ArmorTrim p_289692_, A p_289663_, boolean p_289651_
|
||||
+ Holder<ArmorMaterial> p_331988_, PoseStack p_289687_, MultiBufferSource p_289643_, int p_289683_, ArmorTrim p_289692_, net.minecraft.client.model.Model p_289663_, boolean p_289651_
|
||||
) {
|
||||
TextureAtlasSprite textureatlassprite = this.armorTrimAtlas.getSprite(p_289651_ ? p_289692_.innerTexture(p_331988_) : p_289692_.outerTexture(p_331988_));
|
||||
VertexConsumer vertexconsumer = textureatlassprite.wrap(p_289643_.getBuffer(Sheets.armorTrimsSheet(p_289692_.pattern().value().decal())));
|
||||
@@ -145,6 +_,10 @@
|
||||
}
|
||||
|
||||
private void renderGlint(PoseStack p_289673_, MultiBufferSource p_289654_, int p_289649_, A p_289659_) {
|
||||
+ renderGlint(p_289673_, p_289654_, p_289649_, (net.minecraft.client.model.Model)p_289659_);
|
||||
+ }
|
||||
+
|
||||
+ private void renderGlint(PoseStack p_289673_, MultiBufferSource p_289654_, int p_289649_, net.minecraft.client.model.Model p_289659_) {
|
||||
p_289659_.renderToBuffer(p_289673_, p_289654_.getBuffer(RenderType.armorEntityGlint()), p_289649_, OverlayTexture.NO_OVERLAY, 1.0F, 1.0F, 1.0F, 1.0F);
|
||||
}
|
||||
|
||||
@@ -154,5 +_,12 @@
|
||||
|
||||
private boolean usesInnerModel(EquipmentSlot p_117129_) {
|
||||
return p_117129_ == EquipmentSlot.LEGS;
|
||||
+ }
|
||||
+
|
||||
+ /**
|
||||
+ * Hook to allow item-sensitive armor model. for HumanoidArmorLayer.
|
||||
+ */
|
||||
+ protected net.minecraft.client.model.Model getArmorModelHook(T entity, ItemStack itemStack, EquipmentSlot slot, A model) {
|
||||
+ return net.minecraftforge.client.ForgeHooksClient.getArmorModel(entity, itemStack, slot, model);
|
||||
}
|
||||
}
|
||||
@@ -9,10 +9,11 @@
|
||||
this.socket.setSoTimeout(5000);
|
||||
this.socket.joinGroup(this.pingGroup);
|
||||
}
|
||||
@@ -89,6 +_,11 @@
|
||||
@@ -88,7 +_,11 @@
|
||||
String s = LanServerPinger.parseMotd(p_120097_);
|
||||
String s1 = LanServerPinger.parseAddress(p_120097_);
|
||||
if (s1 != null) {
|
||||
s1 = p_120098_.getHostAddress() + ":" + s1;
|
||||
- s1 = p_120098_.getHostAddress() + ":" + s1;
|
||||
+ if (net.minecraftforge.network.DualStackUtils.checkIPv6(p_120098_)) {
|
||||
+ s1 = "[" + com.google.common.net.InetAddresses.toAddrString(p_120098_) + "]:" + s1;
|
||||
+ } else {
|
||||
|
||||
@@ -35,12 +35,16 @@
|
||||
}
|
||||
|
||||
return this;
|
||||
@@ -435,5 +_,20 @@
|
||||
@@ -435,5 +_,24 @@
|
||||
@Override
|
||||
public HolderLookup.RegistryLookup<T> asLookup() {
|
||||
return this.lookup;
|
||||
+ }
|
||||
+
|
||||
+ public boolean isIntrusive() {
|
||||
+ return this.unregisteredIntrusiveHolders != null;
|
||||
+ }
|
||||
+
|
||||
+ private static final Set<ResourceLocation> KNOWN = new java.util.LinkedHashSet<>();
|
||||
+ public static Set<ResourceLocation> getKnownRegistries() {
|
||||
+ return java.util.Collections.unmodifiableSet(KNOWN);
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
--- a/net/minecraft/network/Connection.java
|
||||
+++ b/net/minecraft/network/Connection.java
|
||||
@@ -107,9 +_,12 @@
|
||||
@@ -107,9 +_,17 @@
|
||||
private volatile Component delayedDisconnect;
|
||||
@Nullable
|
||||
BandwidthDebugMonitor bandwidthDebugMonitor;
|
||||
+ private java.util.function.Consumer<Connection> activationHandler;
|
||||
+ private final net.minecraftforge.common.util.PacketLogger packetLogger;
|
||||
+ private final net.minecraftforge.common.util.PacketLogger packetLogger = new net.minecraftforge.common.util.PacketLogger(this);
|
||||
+ private ProtocolInfo<?> outboundProtocol = null;
|
||||
+ private ProtocolInfo<?> inboundProtocol = null;
|
||||
|
||||
public Connection(PacketFlow p_129482_) {
|
||||
this.receiving = p_129482_;
|
||||
+ this.packetLogger = new net.minecraftforge.common.util.PacketLogger(this);
|
||||
+ if (this.receiving == PacketFlow.SERVERBOUND)
|
||||
+ this.inboundProtocol = INITIAL_PROTOCOL;
|
||||
+ else
|
||||
+ this.outboundProtocol = INITIAL_PROTOCOL;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -29,14 +34,23 @@
|
||||
genericsFtw(p_129488_, packetlistener);
|
||||
} catch (RunningOnDifferentThreadException runningondifferentthreadexception) {
|
||||
} catch (RejectedExecutionException rejectedexecutionexception) {
|
||||
@@ -249,6 +_,7 @@
|
||||
p_326044_ -> p_326044_.pipeline().addAfter("encoder", "unbundler", packetbundleunpacker)
|
||||
);
|
||||
}
|
||||
+ unconfiguredpipelinehandler$outboundconfigurationtask = unconfiguredpipelinehandler$outboundconfigurationtask.andThen(f -> this.protocol = p_329145_.id());
|
||||
|
||||
boolean flag = p_329145_.id() == ConnectionProtocol.LOGIN;
|
||||
syncAfterConfigurationChange(this.channel.writeAndFlush(unconfiguredpipelinehandler$outboundconfigurationtask.andThen(p_326048_ -> this.sendLoginDisconnect = flag)));
|
||||
@@ -218,6 +_,7 @@
|
||||
if (p_333271_.flow() != this.getReceiving()) {
|
||||
throw new IllegalStateException("Invalid inbound protocol: " + p_333271_.id());
|
||||
} else {
|
||||
+ this.inboundProtocol = p_333271_;
|
||||
this.packetListener = p_330962_;
|
||||
this.disconnectListener = null;
|
||||
UnconfiguredPipelineHandler.InboundConfigurationTask unconfiguredpipelinehandler$inboundconfigurationtask = UnconfiguredPipelineHandler.setupInboundProtocol(
|
||||
@@ -241,6 +_,8 @@
|
||||
} else {
|
||||
UnconfiguredPipelineHandler.OutboundConfigurationTask unconfiguredpipelinehandler$outboundconfigurationtask = UnconfiguredPipelineHandler.setupOutboundProtocol(
|
||||
p_329145_
|
||||
+ ).andThen(
|
||||
+ f -> this.outboundProtocol = p_329145_
|
||||
);
|
||||
BundlerInfo bundlerinfo = p_329145_.bundlerInfo();
|
||||
if (bundlerinfo != null) {
|
||||
@@ -290,7 +_,8 @@
|
||||
this.disconnectListener = p_330656_;
|
||||
this.runOnceConnected(p_326042_ -> {
|
||||
@@ -81,7 +95,7 @@
|
||||
new Bootstrap().group(LOCAL_WORKER_GROUP.get()).handler(new ChannelInitializer<Channel>() {
|
||||
@Override
|
||||
protected void initChannel(Channel p_332618_) {
|
||||
@@ -616,6 +_,15 @@
|
||||
@@ -616,6 +_,22 @@
|
||||
|
||||
public float getAverageSentPackets() {
|
||||
return this.averageSentPackets;
|
||||
@@ -91,9 +105,16 @@
|
||||
+ return this.channel;
|
||||
+ }
|
||||
+
|
||||
+ private ConnectionProtocol protocol = INITIAL_PROTOCOL.id();
|
||||
+ public ConnectionProtocol getProtocol() {
|
||||
+ return protocol;
|
||||
+ return outboundProtocol != null ? outboundProtocol.id() : inboundProtocol.id();
|
||||
+ }
|
||||
+
|
||||
+ public ProtocolInfo<?> getInboundProtocolInfo() {
|
||||
+ return this.inboundProtocol;
|
||||
+ }
|
||||
+
|
||||
+ public ProtocolInfo<?> getOutputboundProtocolInfo() {
|
||||
+ return this.outboundProtocol;
|
||||
}
|
||||
|
||||
public void setBandwidthLogger(LocalSampleLogger p_333554_) {
|
||||
@@ -0,0 +1,11 @@
|
||||
--- a/net/minecraft/network/FriendlyByteBuf.java
|
||||
+++ b/net/minecraft/network/FriendlyByteBuf.java
|
||||
@@ -1589,4 +_,8 @@
|
||||
public boolean release(int p_130347_) {
|
||||
return this.source.release(p_130347_);
|
||||
}
|
||||
+
|
||||
+ public FriendlyByteBuf wrap(ByteBuf data) {
|
||||
+ return new FriendlyByteBuf(data);
|
||||
+ }
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
--- a/net/minecraft/network/RegistryFriendlyByteBuf.java
|
||||
+++ b/net/minecraft/network/RegistryFriendlyByteBuf.java
|
||||
@@ -16,6 +_,11 @@
|
||||
return this.registryAccess;
|
||||
}
|
||||
|
||||
+ @Override
|
||||
+ public RegistryFriendlyByteBuf wrap(ByteBuf data) {
|
||||
+ return new RegistryFriendlyByteBuf(data, this.registryAccess);
|
||||
+ }
|
||||
+
|
||||
public static Function<ByteBuf, RegistryFriendlyByteBuf> decorator(RegistryAccess p_336066_) {
|
||||
return p_328649_ -> new RegistryFriendlyByteBuf(p_328649_, p_336066_);
|
||||
}
|
||||
+16
-16
@@ -1,20 +1,20 @@
|
||||
--- a/net/minecraft/network/protocol/common/ClientboundCustomPayloadPacket.java
|
||||
+++ b/net/minecraft/network/protocol/common/ClientboundCustomPayloadPacket.java
|
||||
@@ -32,7 +_,7 @@
|
||||
import net.minecraft.network.protocol.common.custom.WorldGenAttemptDebugPayload;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
-public record ClientboundCustomPayloadPacket(CustomPacketPayload payload) implements Packet<ClientCommonPacketListener> {
|
||||
+public record ClientboundCustomPayloadPacket(CustomPacketPayload payload) implements Packet<ClientCommonPacketListener>, net.minecraftforge.network.ICustomPacket<ClientboundCustomPayloadPacket> {
|
||||
@@ -35,7 +_,7 @@
|
||||
public record ClientboundCustomPayloadPacket(CustomPacketPayload payload) implements Packet<ClientCommonPacketListener> {
|
||||
private static final int MAX_PAYLOAD_SIZE = 1048576;
|
||||
public static final StreamCodec<RegistryFriendlyByteBuf, ClientboundCustomPayloadPacket> GAMEPLAY_STREAM_CODEC = CustomPacketPayload.<RegistryFriendlyByteBuf>codec(
|
||||
p_330149_ -> DiscardedPayload.codec(p_330149_, 1048576),
|
||||
@@ -77,4 +_,8 @@
|
||||
public void handle(ClientCommonPacketListener p_299773_) {
|
||||
p_299773_.handleCustomPayload(this);
|
||||
}
|
||||
+
|
||||
+ @Override public @org.jetbrains.annotations.Nullable FriendlyByteBuf getInternalData() { return payload instanceof DiscardedPayload dc ? dc.data() : null; }
|
||||
+ @Override public ResourceLocation getName() { return payload.type().id(); }
|
||||
+ @Override public int getIndex() { return 0; }
|
||||
}
|
||||
- p_330149_ -> DiscardedPayload.codec(p_330149_, 1048576),
|
||||
+ p_330149_ -> net.minecraftforge.common.ForgeHooks.getCustomPayloadCodec(p_330149_, 1048576),
|
||||
Util.make(
|
||||
Lists.newArrayList(
|
||||
new CustomPacketPayload.TypeAndCodec<>(BrandPayload.TYPE, BrandPayload.STREAM_CODEC),
|
||||
@@ -64,7 +_,7 @@
|
||||
)
|
||||
.map(ClientboundCustomPayloadPacket::new, ClientboundCustomPayloadPacket::payload);
|
||||
public static final StreamCodec<FriendlyByteBuf, ClientboundCustomPayloadPacket> CONFIG_STREAM_CODEC = CustomPacketPayload.<FriendlyByteBuf>codec(
|
||||
- p_331803_ -> DiscardedPayload.codec(p_331803_, 1048576),
|
||||
+ p_331803_ -> net.minecraftforge.common.ForgeHooks.getCustomPayloadCodec(p_331803_, 1048576),
|
||||
List.of(new CustomPacketPayload.TypeAndCodec<>(BrandPayload.TYPE, BrandPayload.STREAM_CODEC))
|
||||
)
|
||||
.map(ClientboundCustomPayloadPacket::new, ClientboundCustomPayloadPacket::payload);
|
||||
+7
-16
@@ -1,20 +1,11 @@
|
||||
--- a/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java
|
||||
+++ b/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java
|
||||
@@ -12,7 +_,7 @@
|
||||
import net.minecraft.network.protocol.common.custom.DiscardedPayload;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
-public record ServerboundCustomPayloadPacket(CustomPacketPayload payload) implements Packet<ServerCommonPacketListener> {
|
||||
+public record ServerboundCustomPayloadPacket(CustomPacketPayload payload) implements Packet<ServerCommonPacketListener>, net.minecraftforge.network.ICustomPacket<ServerboundCustomPayloadPacket> {
|
||||
@@ -15,7 +_,7 @@
|
||||
public record ServerboundCustomPayloadPacket(CustomPacketPayload payload) implements Packet<ServerCommonPacketListener> {
|
||||
private static final int MAX_PAYLOAD_SIZE = 32767;
|
||||
public static final StreamCodec<FriendlyByteBuf, ServerboundCustomPayloadPacket> STREAM_CODEC = CustomPacketPayload.<FriendlyByteBuf>codec(
|
||||
p_335203_ -> DiscardedPayload.codec(p_335203_, 32767),
|
||||
@@ -29,4 +_,8 @@
|
||||
public void handle(ServerCommonPacketListener p_297991_) {
|
||||
p_297991_.handleCustomPayload(this);
|
||||
}
|
||||
+
|
||||
+ @Override public @org.jetbrains.annotations.Nullable FriendlyByteBuf getInternalData() { return payload instanceof DiscardedPayload dc ? dc.data() : null; }
|
||||
+ @Override public ResourceLocation getName() { return payload.type().id(); }
|
||||
+ @Override public int getIndex() { return 0; }
|
||||
}
|
||||
- p_335203_ -> DiscardedPayload.codec(p_335203_, 32767),
|
||||
+ p_335203_ -> net.minecraftforge.common.ForgeHooks.getCustomPayloadCodec(p_335203_, 32767),
|
||||
Util.make(Lists.newArrayList(new CustomPacketPayload.TypeAndCodec<>(BrandPayload.TYPE, BrandPayload.STREAM_CODEC)), p_334419_ -> {
|
||||
})
|
||||
)
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
--- a/net/minecraft/network/protocol/common/custom/DiscardedPayload.java
|
||||
+++ b/net/minecraft/network/protocol/common/custom/DiscardedPayload.java
|
||||
@@ -4,18 +_,24 @@
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
-public record DiscardedPayload(ResourceLocation id) implements CustomPacketPayload {
|
||||
+public record DiscardedPayload(ResourceLocation id, @org.jetbrains.annotations.Nullable FriendlyByteBuf data) implements CustomPacketPayload {
|
||||
public static <T extends FriendlyByteBuf> StreamCodec<T, DiscardedPayload> codec(ResourceLocation p_328904_, int p_334650_) {
|
||||
return CustomPacketPayload.codec((p_330619_, p_329210_) -> {
|
||||
+ if (p_330619_.data != null) {
|
||||
+ p_329210_.writeBytes(p_330619_.data.slice());
|
||||
+ }
|
||||
}, p_333509_ -> {
|
||||
int i = p_333509_.readableBytes();
|
||||
if (i >= 0 && i <= p_334650_) {
|
||||
- p_333509_.skipBytes(i);
|
||||
- return new DiscardedPayload(p_328904_);
|
||||
+ return new DiscardedPayload(p_328904_, new FriendlyByteBuf(p_333509_.readBytes(i)));
|
||||
} else {
|
||||
throw new IllegalArgumentException("Payload may not be larger than " + p_334650_ + " bytes");
|
||||
}
|
||||
});
|
||||
+ }
|
||||
+
|
||||
+ public DiscardedPayload(ResourceLocation id) {
|
||||
+ this(id, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
--- a/net/minecraft/network/protocol/login/ClientboundCustomQueryPacket.java
|
||||
+++ b/net/minecraft/network/protocol/login/ClientboundCustomQueryPacket.java
|
||||
@@ -8,7 +_,7 @@
|
||||
import net.minecraft.network.protocol.login.custom.DiscardedQueryPayload;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
-public record ClientboundCustomQueryPacket(int transactionId, CustomQueryPayload payload) implements Packet<ClientLoginPacketListener> {
|
||||
+public record ClientboundCustomQueryPacket(int transactionId, CustomQueryPayload payload) implements Packet<ClientLoginPacketListener>, net.minecraftforge.network.ICustomPacket<ClientboundCustomQueryPacket> {
|
||||
public static final StreamCodec<FriendlyByteBuf, ClientboundCustomQueryPacket> STREAM_CODEC = Packet.codec(
|
||||
ClientboundCustomQueryPacket::write, ClientboundCustomQueryPacket::new
|
||||
);
|
||||
@@ -25,8 +_,7 @@
|
||||
private static DiscardedQueryPayload readUnknownPayload(ResourceLocation p_299981_, FriendlyByteBuf p_297706_) {
|
||||
int i = p_297706_.readableBytes();
|
||||
if (i >= 0 && i <= 1048576) {
|
||||
- p_297706_.skipBytes(i);
|
||||
- return new DiscardedQueryPayload(p_299981_);
|
||||
+ return new DiscardedQueryPayload(p_299981_, new FriendlyByteBuf(p_297706_.readBytes(i)));
|
||||
} else {
|
||||
throw new IllegalArgumentException("Payload may not be larger than 1048576 bytes");
|
||||
}
|
||||
@@ -46,4 +_,8 @@
|
||||
public void handle(ClientLoginPacketListener p_134754_) {
|
||||
p_134754_.handleCustomQuery(this);
|
||||
}
|
||||
+
|
||||
+ @Override public int getIndex() { return transactionId(); }
|
||||
+ @Override public ResourceLocation getName() { return this.payload.id(); }
|
||||
+ @org.jetbrains.annotations.Nullable @Override public FriendlyByteBuf getInternalData() { return ((DiscardedQueryPayload)this.payload).data(); }
|
||||
}
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
--- a/net/minecraft/network/protocol/login/ServerboundCustomQueryAnswerPacket.java
|
||||
+++ b/net/minecraft/network/protocol/login/ServerboundCustomQueryAnswerPacket.java
|
||||
@@ -8,7 +_,7 @@
|
||||
import net.minecraft.network.protocol.login.custom.CustomQueryAnswerPayload;
|
||||
import net.minecraft.network.protocol.login.custom.DiscardedQueryAnswerPayload;
|
||||
|
||||
-public record ServerboundCustomQueryAnswerPacket(int transactionId, @Nullable CustomQueryAnswerPayload payload) implements Packet<ServerLoginPacketListener> {
|
||||
+public record ServerboundCustomQueryAnswerPacket(int transactionId, @Nullable CustomQueryAnswerPayload payload) implements Packet<ServerLoginPacketListener>, net.minecraftforge.network.ICustomPacket<ServerboundCustomQueryAnswerPacket> {
|
||||
public static final StreamCodec<FriendlyByteBuf, ServerboundCustomQueryAnswerPacket> STREAM_CODEC = Packet.codec(
|
||||
ServerboundCustomQueryAnswerPacket::write, ServerboundCustomQueryAnswerPacket::read
|
||||
);
|
||||
@@ -26,8 +_,7 @@
|
||||
private static CustomQueryAnswerPayload readUnknownPayload(FriendlyByteBuf p_299934_) {
|
||||
int i = p_299934_.readableBytes();
|
||||
if (i >= 0 && i <= 1048576) {
|
||||
- p_299934_.skipBytes(i);
|
||||
- return DiscardedQueryAnswerPayload.INSTANCE;
|
||||
+ return new DiscardedQueryAnswerPayload(new FriendlyByteBuf(p_299934_.readBytes(i)));
|
||||
} else {
|
||||
throw new IllegalArgumentException("Payload may not be larger than 1048576 bytes");
|
||||
}
|
||||
@@ -46,4 +_,8 @@
|
||||
public void handle(ServerLoginPacketListener p_298492_) {
|
||||
p_298492_.handleCustomQueryPacket(this);
|
||||
}
|
||||
+
|
||||
+ @Nullable @Override public FriendlyByteBuf getInternalData() { return this.payload instanceof DiscardedQueryAnswerPayload dc ? dc.data() : null; }
|
||||
+ @Override public net.minecraft.resources.ResourceLocation getName() { return net.minecraftforge.network.NetworkInitialization.LOGIN_NAME; }
|
||||
+ @Override public int getIndex() { return transactionId(); }
|
||||
}
|
||||
+8
-3
@@ -1,21 +1,26 @@
|
||||
--- a/net/minecraft/network/protocol/login/custom/DiscardedQueryAnswerPayload.java
|
||||
+++ b/net/minecraft/network/protocol/login/custom/DiscardedQueryAnswerPayload.java
|
||||
@@ -2,10 +_,17 @@
|
||||
@@ -2,10 +_,22 @@
|
||||
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
|
||||
-public record DiscardedQueryAnswerPayload() implements CustomQueryAnswerPayload {
|
||||
+public record DiscardedQueryAnswerPayload(@javax.annotation.Nullable FriendlyByteBuf data) implements CustomQueryAnswerPayload {
|
||||
+public record DiscardedQueryAnswerPayload(
|
||||
+ @javax.annotation.Nullable FriendlyByteBuf data,
|
||||
+ java.util.function.Consumer<FriendlyByteBuf> encoder
|
||||
+) implements CustomQueryAnswerPayload {
|
||||
public static final DiscardedQueryAnswerPayload INSTANCE = new DiscardedQueryAnswerPayload();
|
||||
|
||||
+ public DiscardedQueryAnswerPayload() {
|
||||
+ this(null);
|
||||
+ this(null, buf -> {});
|
||||
+ }
|
||||
+
|
||||
@Override
|
||||
public void write(FriendlyByteBuf p_299186_) {
|
||||
+ if (this.data != null) {
|
||||
+ p_299186_.writeBytes(this.data.slice());
|
||||
+ } else {
|
||||
+ encoder.accept(p_299186_);
|
||||
+ }
|
||||
}
|
||||
}
|
||||
+9
-7
@@ -1,23 +1,25 @@
|
||||
--- a/net/minecraft/network/protocol/login/custom/DiscardedQueryPayload.java
|
||||
+++ b/net/minecraft/network/protocol/login/custom/DiscardedQueryPayload.java
|
||||
@@ -3,12 +_,18 @@
|
||||
@@ -3,9 +_,21 @@
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
-public record DiscardedQueryPayload(ResourceLocation id) implements CustomQueryPayload {
|
||||
+public record DiscardedQueryPayload(ResourceLocation id, @org.jetbrains.annotations.Nullable FriendlyByteBuf data) implements CustomQueryPayload {
|
||||
+public record DiscardedQueryPayload(ResourceLocation id,
|
||||
+ @org.jetbrains.annotations.Nullable FriendlyByteBuf data,
|
||||
+ java.util.function.Consumer<net.minecraft.network.FriendlyByteBuf> encoder
|
||||
+) implements CustomQueryPayload {
|
||||
+ public DiscardedQueryPayload(ResourceLocation id) {
|
||||
+ this(id, null);
|
||||
+ this(id, null, buf -> {});
|
||||
+ }
|
||||
+
|
||||
@Override
|
||||
public void write(FriendlyByteBuf p_299949_) {
|
||||
+ if (this.data != null) {
|
||||
+ p_299949_.writeBytes(this.data.slice());
|
||||
+ } else {
|
||||
+ encoder.accept(p_299949_);
|
||||
+ }
|
||||
}
|
||||
|
||||
- @Override
|
||||
public ResourceLocation id() {
|
||||
return this.id;
|
||||
}
|
||||
@Override
|
||||
@@ -1,10 +1,12 @@
|
||||
--- a/net/minecraft/network/syncher/EntityDataSerializers.java
|
||||
+++ b/net/minecraft/network/syncher/EntityDataSerializers.java
|
||||
@@ -121,15 +_,16 @@
|
||||
@@ -120,16 +_,18 @@
|
||||
public static final EntityDataSerializer<Quaternionf> QUATERNION = EntityDataSerializer.forValueType(ByteBufCodecs.QUATERNIONF);
|
||||
|
||||
public static void registerSerializer(EntityDataSerializer<?> p_135051_) {
|
||||
+ int id =
|
||||
SERIALIZERS.add(p_135051_);
|
||||
+ if (SERIALIZERS.add(p_135051_) >= 256) throw new RuntimeException("Vanilla DataSerializer ID limit exceeded");
|
||||
+ if (id >= 256) throw new RuntimeException("Vanilla DataSerializer ID limit exceeded");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -75,6 +75,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
@@ -738,6 +_,7 @@
|
||||
p_8648_.tickCount++;
|
||||
this.getProfiler().push(() -> BuiltInRegistries.ENTITY_TYPE.getKey(p_8648_.getType()).toString());
|
||||
profilerfiller.incrementCounter("tickNonPassenger");
|
||||
+ if (p_8648_.canUpdate())
|
||||
p_8648_.tick();
|
||||
this.getProfiler().pop();
|
||||
|
||||
@@ -755,6 +_,7 @@
|
||||
ProfilerFiller profilerfiller = this.getProfiler();
|
||||
profilerfiller.push(() -> BuiltInRegistries.ENTITY_TYPE.getKey(p_8664_.getType()).toString());
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
@Override
|
||||
public void die(DamageSource p_9035_) {
|
||||
+ if (net.minecraftforge.common.ForgeHooks.onLivingDeath(this, p_9035_)) return;
|
||||
+ if (net.minecraftforge.event.ForgeEventFactory.onLivingDeath(this, p_9035_)) return;
|
||||
this.gameEvent(GameEvent.ENTITY_DIE);
|
||||
boolean flag = this.level().getGameRules().getBoolean(GameRules.RULE_SHOWDEATHMESSAGES);
|
||||
if (flag) {
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
|
||||
@Override
|
||||
public void handleCustomPayload(ServerboundCustomPayloadPacket p_300164_) {
|
||||
+ net.minecraftforge.common.ForgeHooks.onCustomPayload(p_300164_, this.connection);
|
||||
+ net.minecraftforge.common.ForgeHooks.onCustomPayload(p_300164_.payload(), this.connection);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+17
@@ -37,3 +37,20 @@
|
||||
PlayerChatMessage playerchatmessage1 = playerchatmessage.withUnsignedContent(component).filter(p_296589_.mask());
|
||||
this.broadcastChatMessage(playerchatmessage1);
|
||||
});
|
||||
@@ -1516,7 +_,7 @@
|
||||
|
||||
@Override
|
||||
public void onInteraction(InteractionHand p_143682_, Vec3 p_143683_) {
|
||||
- this.performInteraction(p_143682_, (p_143686_, p_143687_, p_143688_) -> p_143687_.interactAt(p_143686_, p_143683_, p_143688_));
|
||||
+ this.performInteraction(p_143682_, (p_143686_, p_143687_, p_143688_) -> net.minecraftforge.common.ForgeHooks.onInteractEntityAt(p_143687_, p_143686_, p_143683_, p_143688_));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1797,6 +_,7 @@
|
||||
|
||||
@Override
|
||||
public void handleCustomPayload(ServerboundCustomPayloadPacket p_329963_) {
|
||||
+ net.minecraftforge.common.ForgeHooks.onCustomPayload(p_329963_.payload(), this.connection);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -220,7 +220,7 @@
|
||||
}
|
||||
|
||||
public void die(DamageSource p_21014_) {
|
||||
+ if (net.minecraftforge.common.ForgeHooks.onLivingDeath(this, p_21014_)) return;
|
||||
+ if (net.minecraftforge.event.ForgeEventFactory.onLivingDeath(this, p_21014_)) return;
|
||||
if (!this.isRemoved() && !this.dead) {
|
||||
Entity entity = p_21014_.getEntity();
|
||||
LivingEntity livingentity = this.getKillCredit();
|
||||
@@ -252,7 +252,7 @@
|
||||
this.dropExperience();
|
||||
+
|
||||
+ var drops = captureDrops(null);
|
||||
+ if (!net.minecraftforge.common.ForgeHooks.onLivingDrops(this, p_21192_, drops, i, lastHurtByPlayerTime > 0)) {
|
||||
+ if (!net.minecraftforge.event.ForgeEventFactory.onLivingDrops(this, p_21192_, drops, i, lastHurtByPlayerTime > 0)) {
|
||||
+ drops.forEach(e -> level().addFreshEntity(e));
|
||||
+ }
|
||||
}
|
||||
@@ -439,7 +439,7 @@
|
||||
|
||||
@Override
|
||||
public void tick() {
|
||||
+ if (net.minecraftforge.common.ForgeHooks.onLivingTick(this)) return;
|
||||
+ if (net.minecraftforge.event.ForgeEventFactory.onLivingTick(this)) return;
|
||||
super.tick();
|
||||
this.updatingUsingItem();
|
||||
this.updateSwimAmount();
|
||||
@@ -586,7 +586,7 @@
|
||||
Item item = this.useItem.getItem();
|
||||
- return item.getUseAnimation(this.useItem) != UseAnim.BLOCK ? false : item.getUseDuration(this.useItem) - this.useItemRemaining >= 5;
|
||||
+ boolean canBlock = this.useItem.canPerformAction(net.minecraftforge.common.ToolActions.SHIELD_BLOCK);
|
||||
+ return canBlock ? false : item.getUseDuration(this.useItem) - this.useItemRemaining >= 5;
|
||||
+ return !canBlock ? false : item.getUseDuration(this.useItem) - this.useItemRemaining >= 5;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
--- a/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java
|
||||
+++ b/net/minecraft/world/entity/ai/goal/target/HurtByTargetGoal.java
|
||||
@@ -84,6 +_,10 @@
|
||||
}
|
||||
|
||||
mob = (Mob)iterator.next();
|
||||
+ // Fix NPE if modders do things vanilla doesn't expect. https://github.com/MinecraftForge/MinecraftForge/issues/7853
|
||||
+ if (this.mob.getLastHurtByMob() == null)
|
||||
+ return;
|
||||
+
|
||||
if (this.mob != mob
|
||||
&& mob.getTarget() == null
|
||||
&& (!(this.mob instanceof TamableAnimal) || ((TamableAnimal)this.mob).getOwner() == ((TamableAnimal)mob).getOwner())
|
||||
@@ -73,21 +73,3 @@
|
||||
if (!this.isSilent()) {
|
||||
p_219160_.levelEvent(null, 1026, this.blockPosition(), 0);
|
||||
}
|
||||
@@ -487,7 +_,7 @@
|
||||
}
|
||||
|
||||
public static boolean getSpawnAsBabyOdds(RandomSource p_219163_) {
|
||||
- return p_219163_.nextFloat() < 0.05F;
|
||||
+ return p_219163_.nextFloat() < net.minecraftforge.common.ForgeConfig.SERVER.zombieBabyChance.get();
|
||||
}
|
||||
|
||||
protected void handleAttributes(float p_34340_) {
|
||||
@@ -510,7 +_,7 @@
|
||||
}
|
||||
|
||||
protected void randomizeReinforcementsChance() {
|
||||
- this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(this.random.nextDouble() * 0.1F);
|
||||
+ this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(this.random.nextDouble() * net.minecraftforge.common.ForgeConfig.SERVER.zombieBaseSummonChance.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -5,7 +5,7 @@
|
||||
CriteriaTriggers.TRADE.trigger((ServerPlayer)this.tradingPlayer, this, p_35274_.getResult());
|
||||
}
|
||||
+
|
||||
+ net.minecraftforge.common.MinecraftForge.EVENT_BUS.post(new net.minecraftforge.event.entity.player.TradeWithVillagerEvent(this.tradingPlayer, p_35274_, this));
|
||||
+ net.minecraftforge.event.ForgeEventFactory.onPlayerTradeWithVillager(this.tradingPlayer, p_35274_, this);
|
||||
}
|
||||
|
||||
protected abstract void rewardTradeXp(MerchantOffer p_35299_);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
public InteractionResult mobInteract(Player p_35472_, InteractionHand p_35473_) {
|
||||
ItemStack itemstack = p_35472_.getItemInHand(p_35473_);
|
||||
- if (itemstack.is(Items.VILLAGER_SPAWN_EGG) || !this.isAlive() || this.isTrading() || this.isSleeping()) {
|
||||
+ if (itemstack.getItem() != Items.VILLAGER_SPAWN_EGG && this.isAlive() && !this.isTrading() && !this.isSleeping() && !p_35472_.isSecondaryUseActive()) {
|
||||
+ if (itemstack.is(Items.VILLAGER_SPAWN_EGG) || !this.isAlive() || this.isTrading() || this.isSleeping() || p_35472_.isSecondaryUseActive()) {
|
||||
return super.mobInteract(p_35472_, p_35473_);
|
||||
} else if (this.isBaby()) {
|
||||
this.setUnhappy();
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
|
||||
@Override
|
||||
public void die(DamageSource p_36152_) {
|
||||
+ if (net.minecraftforge.common.ForgeHooks.onLivingDeath(this, p_36152_)) return;
|
||||
+ if (net.minecraftforge.event.ForgeEventFactory.onLivingDeath(this, p_36152_)) return;
|
||||
super.die(p_36152_);
|
||||
this.reapplyPosition();
|
||||
if (!this.isSpectator()) {
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
--- a/net/minecraft/world/item/ArmorMaterial.java
|
||||
+++ b/net/minecraft/world/item/ArmorMaterial.java
|
||||
@@ -56,5 +_,9 @@
|
||||
public boolean dyeable() {
|
||||
return this.dyeable;
|
||||
}
|
||||
+
|
||||
+ public String getSuffix() {
|
||||
+ return this.suffix;
|
||||
+ }
|
||||
}
|
||||
}
|
||||
@@ -16,30 +16,18 @@
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
@@ -104,5 +_,26 @@
|
||||
@@ -104,5 +_,14 @@
|
||||
@Override
|
||||
public String getSerializedName() {
|
||||
return this.name;
|
||||
+ }
|
||||
+
|
||||
+
|
||||
+ public net.minecraft.tags.TagKey<Item> getTag() {
|
||||
+ return tag;
|
||||
+ }
|
||||
+
|
||||
+ @Nullable
|
||||
+ public static DyeColor getColor(ItemStack stack) {
|
||||
+ if (stack.getItem() instanceof DyeItem dye) {
|
||||
+ return dye.getDyeColor();
|
||||
+ }
|
||||
+
|
||||
+ for (int x = 0; x < BLACK.getId(); x++) {
|
||||
+ var color = byId(x);
|
||||
+ if (stack.is(color.getTag())) {
|
||||
+ return color;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return null;
|
||||
+ return net.minecraftforge.common.ForgeHooks.getDyeColorFromItemStack(stack);
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,27 @@
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@@ -111,6 +_,7 @@
|
||||
@@ -100,17 +_,25 @@
|
||||
return this.builtInRegistryHolder;
|
||||
}
|
||||
|
||||
+ @Nullable
|
||||
+ private DataComponentMap builtComponents = null;
|
||||
+
|
||||
public DataComponentMap components() {
|
||||
- return this.components;
|
||||
+ if (builtComponents == null) {
|
||||
+ builtComponents = net.minecraftforge.common.ForgeHooks.gatherItemComponents(this, components);
|
||||
+ }
|
||||
+
|
||||
+ return builtComponents;
|
||||
}
|
||||
|
||||
public int getDefaultMaxStackSize() {
|
||||
- return this.components.getOrDefault(DataComponents.MAX_STACK_SIZE, 1);
|
||||
+ return builtComponents == null ? this.components.getOrDefault(DataComponents.MAX_STACK_SIZE, 1) : this.builtComponents.getOrDefault(DataComponents.MAX_STACK_SIZE, 1);
|
||||
}
|
||||
|
||||
public void onUseTick(Level p_41428_, LivingEntity p_41429_, ItemStack p_41430_, int p_41431_) {
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
|
||||
public boolean hasMix(ItemStack p_43509_, ItemStack p_43510_) {
|
||||
- return !this.isContainer(p_43509_) ? false : this.hasContainerMix(p_43509_, p_43510_) || this.hasPotionMix(p_43509_, p_43510_);
|
||||
+ return !mix(p_43509_, p_43510_).isEmpty();
|
||||
+ return !mix(p_43510_, p_43509_).isEmpty();
|
||||
}
|
||||
|
||||
+ /** @deprecated Forge: use hasMix(ItemStack, ItemStack)*/
|
||||
@@ -61,11 +61,11 @@
|
||||
}
|
||||
|
||||
public ItemStack mix(ItemStack p_43530_, ItemStack p_43531_) {
|
||||
+ if (p_43530_.isEmpty() || p_43530_.getCount() != 1) return ItemStack.EMPTY;
|
||||
+ if (p_43531_.isEmpty()) return ItemStack.EMPTY;
|
||||
+ if (p_43531_.isEmpty() || p_43531_.getCount() != 1) return ItemStack.EMPTY;
|
||||
+ if (p_43530_.isEmpty()) return ItemStack.EMPTY;
|
||||
+
|
||||
+ for (var recipe : recipes) {
|
||||
+ ItemStack output = recipe.getOutput(p_43530_, p_43531_);
|
||||
+ ItemStack output = recipe.getOutput(p_43531_, p_43530_);
|
||||
+ if (!output.isEmpty()) {
|
||||
+ return output;
|
||||
+ }
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
&& (!enchantment.isTreasureOnly() || p_44820_)
|
||||
&& enchantment.isDiscoverable()
|
||||
- && (flag || enchantment.canEnchant(p_44819_) && enchantment.isPrimaryItem(p_44819_))) {
|
||||
+ && ((flag && enchantment.isAllowedOnBooks()) || enchantment.canApplyAtEnchatingTable(p_44819_))) {
|
||||
+ && ((flag && enchantment.isAllowedOnBooks()) || enchantment.canApplyAtEnchantingTable(p_44819_))) {
|
||||
for (int i = enchantment.getMaxLevel(); i > enchantment.getMinLevel() - 1; i--) {
|
||||
if (p_44818_ >= enchantment.getMinCost(i) && p_44818_ <= enchantment.getMaxCost(i)) {
|
||||
list.add(new EnchantmentInstance(enchantment, i));
|
||||
@@ -10,10 +10,11 @@
|
||||
}
|
||||
|
||||
boolean flag1 = spawndata.getEntityToSpawn().size() == 1 && spawndata.getEntityToSpawn().contains("id", 8);
|
||||
- if (flag1) {
|
||||
- ((Mob)entity).finalizeSpawn(p_151312_, p_151312_.getCurrentDifficultyAt(entity.blockPosition()), MobSpawnType.SPAWNER, null);
|
||||
+ // Forge: Patch in FinalizeSpawn for spawners so it may be fired unconditionally, instead of only when vanilla normally would trigger it.
|
||||
+ var event = net.minecraftforge.event.ForgeEventFactory.onFinalizeSpawnSpawner(mob, p_151312_, p_151312_.getCurrentDifficultyAt(entity.blockPosition()), null, compoundtag, this);
|
||||
if (flag1) {
|
||||
- ((Mob)entity).finalizeSpawn(p_151312_, p_151312_.getCurrentDifficultyAt(entity.blockPosition()), MobSpawnType.SPAWNER, null);
|
||||
+ if (!event.isCanceled() && flag1) {
|
||||
+ ((Mob)entity).finalizeSpawn(p_151312_, event.getDifficulty(), MobSpawnType.SPAWNER, null);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,17 @@
|
||||
BiomeGenerationSettings.CODEC.forGetter(p_220548_ -> p_220548_.generationSettings),
|
||||
MobSpawnSettings.CODEC.forGetter(p_220546_ -> p_220546_.mobSettings)
|
||||
)
|
||||
@@ -58,8 +_,10 @@
|
||||
)
|
||||
public static final PerlinSimplexNoise BIOME_INFO_NOISE = new PerlinSimplexNoise(new WorldgenRandom(new LegacyRandomSource(2345L)), ImmutableList.of(0));
|
||||
private static final int TEMPERATURE_CACHE_SIZE = 1024;
|
||||
+ /** @deprecated Redirected to {@link #getModifiedClimateSettings()} by field_to_method.js */
|
||||
private final Biome.ClimateSettings climateSettings;
|
||||
private final BiomeGenerationSettings generationSettings;
|
||||
+ /** @deprecated Redirected to {@link #getModifiedSpecialEffects()} by field_to_method.js */
|
||||
private final MobSpawnSettings mobSettings;
|
||||
private final BiomeSpecialEffects specialEffects;
|
||||
private final ThreadLocal<Long2FloatLinkedOpenHashMap> temperatureCache = ThreadLocal.withInitial(() -> Util.make(() -> {
|
||||
@@ -71,12 +_,14 @@
|
||||
long2floatlinkedopenhashmap.defaultReturnValue(Float.NaN);
|
||||
return long2floatlinkedopenhashmap;
|
||||
@@ -44,7 +55,7 @@
|
||||
}
|
||||
|
||||
public int getFogColor() {
|
||||
@@ -251,6 +_,29 @@
|
||||
@@ -251,6 +_,31 @@
|
||||
|
||||
public Optional<Music> getBackgroundMusic() {
|
||||
return this.specialEffects.getBackgroundMusic();
|
||||
@@ -61,6 +72,7 @@
|
||||
+
|
||||
+ /**
|
||||
+ * {@return The biome's climate settings, with modifications if called after modifiers are applied in server init.}
|
||||
+ * Note: This method is called by our field_to_method coremod. Which redirects {@link #climateSettings} to this method at load time.
|
||||
+ */
|
||||
+ public ClimateSettings getModifiedClimateSettings() {
|
||||
+ return this.modifiableBiomeInfo().get().climateSettings();
|
||||
@@ -68,6 +80,7 @@
|
||||
+
|
||||
+ /**
|
||||
+ * {@return The biome's client effects, with modifications if called after modifiers are applied in server init.}
|
||||
+ * Note: This methods is called by our field_to_method coremod. Which redirects {@link #specialEffects} to this method at load time.
|
||||
+ */
|
||||
+ public BiomeSpecialEffects getModifiedSpecialEffects() {
|
||||
+ return this.modifiableBiomeInfo().get().effects();
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
switch (railshape) {
|
||||
case NORTH_SOUTH:
|
||||
if (p_55223_) {
|
||||
@@ -110,18 +_,18 @@
|
||||
@@ -110,10 +_,10 @@
|
||||
|
||||
protected boolean isSameRailWithPower(Level p_55226_, BlockPos p_55227_, boolean p_55228_, int p_55229_, RailShape p_55230_) {
|
||||
BlockState blockstate = p_55226_.getBlockState(p_55227_);
|
||||
@@ -47,9 +47,8 @@
|
||||
if (p_55230_ != RailShape.EAST_WEST
|
||||
|| railshape != RailShape.NORTH_SOUTH && railshape != RailShape.ASCENDING_NORTH && railshape != RailShape.ASCENDING_SOUTH) {
|
||||
if (p_55230_ != RailShape.NORTH_SOUTH
|
||||
|| railshape != RailShape.EAST_WEST && railshape != RailShape.ASCENDING_EAST && railshape != RailShape.ASCENDING_WEST) {
|
||||
- if (!blockstate.getValue(POWERED)) {
|
||||
+ if (isActivatorRail() == other.isActivatorRail()) {
|
||||
@@ -121,7 +_,7 @@
|
||||
if (!blockstate.getValue(POWERED)) {
|
||||
return false;
|
||||
} else {
|
||||
- return p_55226_.hasNeighborSignal(p_55227_) ? true : this.findPoweredRailSignal(p_55226_, p_55227_, blockstate, p_55228_, p_55229_ + 1);
|
||||
|
||||
@@ -13,15 +13,14 @@
|
||||
int i = p_222538_.getValue(AGE);
|
||||
if (i < 7) {
|
||||
p_222538_ = p_222538_.setValue(AGE, Integer.valueOf(i + 1));
|
||||
@@ -90,7 +_,7 @@
|
||||
Direction direction = Direction.Plane.HORIZONTAL.getRandomDirection(p_222541_);
|
||||
BlockPos blockpos = p_222540_.relative(direction);
|
||||
BlockState blockstate = p_222539_.getBlockState(blockpos.below());
|
||||
- if (p_222539_.getBlockState(blockpos).isAir() && (blockstate.is(Blocks.FARMLAND) || blockstate.is(BlockTags.DIRT))) {
|
||||
+ if (p_222539_.getBlockState(blockpos).isAir() && blockstate.canSustainPlant(p_222539_, blockpos.below(), Direction.UP, this)) {
|
||||
Registry<Block> registry = p_222539_.registryAccess().registryOrThrow(Registries.BLOCK);
|
||||
Optional<Block> optional = registry.getOptional(this.fruit);
|
||||
Optional<Block> optional1 = registry.getOptional(this.attachedStem);
|
||||
@@ -100,6 +_,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
+ net.minecraftforge.common.ForgeHooks.onCropsGrowPost(p_222539_, p_222540_, p_222538_);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -132,5 +_,10 @@
|
||||
@Override
|
||||
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> p_57040_) {
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
--- a/net/minecraft/world/level/chunk/ChunkGenerator.java
|
||||
+++ b/net/minecraft/world/level/chunk/ChunkGenerator.java
|
||||
@@ -81,7 +_,7 @@
|
||||
public abstract class ChunkGenerator {
|
||||
public static final Codec<ChunkGenerator> CODEC = BuiltInRegistries.CHUNK_GENERATOR.byNameCodec().dispatchStable(ChunkGenerator::codec, Function.identity());
|
||||
protected final BiomeSource biomeSource;
|
||||
- private final Supplier<List<FeatureSorter.StepFeatureData>> featuresPerStep;
|
||||
+ private final net.minecraftforge.common.util.ClearableLazy<List<FeatureSorter.StepFeatureData>> featuresPerStep;
|
||||
private final Function<Holder<Biome>, BiomeGenerationSettings> generationSettingsGetter;
|
||||
|
||||
public ChunkGenerator(BiomeSource p_256133_) {
|
||||
@@ -91,9 +_,14 @@
|
||||
public ChunkGenerator(BiomeSource p_255838_, Function<Holder<Biome>, BiomeGenerationSettings> p_256216_) {
|
||||
this.biomeSource = p_255838_;
|
||||
this.generationSettingsGetter = p_256216_;
|
||||
- this.featuresPerStep = Suppliers.memoize(
|
||||
+ this.featuresPerStep = net.minecraftforge.common.util.ClearableLazy.concurrentOf(
|
||||
() -> FeatureSorter.buildFeaturesPerStep(List.copyOf(p_255838_.possibleBiomes()), p_223216_ -> p_256216_.apply(p_223216_).features(), true)
|
||||
);
|
||||
+ }
|
||||
+
|
||||
+ @org.jetbrains.annotations.ApiStatus.Internal
|
||||
+ public void refreshFeaturesPerStep() {
|
||||
+ featuresPerStep.invalidate();
|
||||
}
|
||||
|
||||
public void validate() {
|
||||
+2
-4
@@ -1,5 +1,3 @@
|
||||
import org.gradle.internal.os.OperatingSystem
|
||||
|
||||
pluginManagement {
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
@@ -27,7 +25,7 @@ dependencyResolutionManagement {
|
||||
libs {
|
||||
library('forgespi', 'net.minecraftforge:forgespi:7.1.4') // Needs modlauncher
|
||||
library('modlauncher', 'net.minecraftforge:modlauncher:10.2.1') // Needs securemodules
|
||||
library('securemodules', 'net.minecraftforge:securemodules:2.2.14') // Needs unsafe
|
||||
library('securemodules', 'net.minecraftforge:securemodules:2.2.19') // Needs unsafe
|
||||
library('unsafe', 'net.minecraftforge:unsafe:0.9.2')
|
||||
library('accesstransformers', 'net.minecraftforge:accesstransformers:8.2.0')
|
||||
library('coremods', 'net.minecraftforge:coremods:5.1.6')
|
||||
@@ -45,7 +43,7 @@ dependencyResolutionManagement {
|
||||
library('jimfs', 'com.google.jimfs:jimfs:1.3.0')
|
||||
bundle('jimfs', ['guava', 'failureaccess'])
|
||||
|
||||
version('bootstrap', '2.1.0')
|
||||
version('bootstrap', '2.1.3')
|
||||
library('bootstrap', 'net.minecraftforge', 'bootstrap' ).versionRef('bootstrap') // Needs modlauncher
|
||||
library('bootstrap-api', 'net.minecraftforge', 'bootstrap-api' ).versionRef('bootstrap')
|
||||
library('bootstrap-dev', 'net.minecraftforge', 'bootstrap-dev' ).versionRef('bootstrap')
|
||||
|
||||
@@ -28,6 +28,7 @@ import net.minecraft.client.color.block.BlockColors;
|
||||
import net.minecraft.client.color.item.ItemColors;
|
||||
import net.minecraft.client.gui.Font;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.gui.components.ChatComponent;
|
||||
import net.minecraft.client.gui.components.LerpingBossEvent;
|
||||
import net.minecraft.client.gui.components.toasts.Toast;
|
||||
import net.minecraft.client.gui.screens.ConfirmScreen;
|
||||
@@ -98,6 +99,7 @@ import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.inventory.tooltip.TooltipComponent;
|
||||
import net.minecraft.world.item.ArmorMaterial;
|
||||
import net.minecraft.world.item.ItemDisplayContext;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.crafting.RecipeManager;
|
||||
@@ -246,10 +248,10 @@ public class ForgeHooksClient {
|
||||
}
|
||||
|
||||
public static float getGuiFarPlane() {
|
||||
// 1000 units for the overlay background,
|
||||
// 11000 units for the overlay background,
|
||||
// and 10000 units for each layered Screen,
|
||||
|
||||
return 1000.0F + 10000.0F * (1 + guiLayers.size());
|
||||
return 11000.0F + 10000.0F * (1 + guiLayers.size());
|
||||
}
|
||||
|
||||
public static boolean onClientPauseChangePre(boolean pause) {
|
||||
@@ -260,9 +262,9 @@ public class ForgeHooksClient {
|
||||
MinecraftForge.EVENT_BUS.post(new ClientPauseChangeEvent.Post(pause));
|
||||
}
|
||||
|
||||
public static String getArmorTexture(Entity entity, ItemStack armor, String _default, EquipmentSlot slot, String type) {
|
||||
String result = armor.getItem().getArmorTexture(armor, entity, slot, type);
|
||||
return result != null ? result : _default;
|
||||
public static ResourceLocation getArmorTexture(Entity entity, ItemStack armor, EquipmentSlot slot, ArmorMaterial.Layer layer, boolean inner) {
|
||||
var result = armor.getItem().getArmorTexture(armor, entity, slot, layer, inner);
|
||||
return result != null ? result : layer.texture(inner);
|
||||
}
|
||||
|
||||
public static boolean onDrawHighlight(LevelRenderer context, Camera camera, HitResult target, float partialTick, Matrix4f ctxPos, MultiBufferSource bufferSource) {
|
||||
@@ -382,7 +384,7 @@ public class ForgeHooksClient {
|
||||
guiLayers.forEach(layer -> {
|
||||
// Prevent the background layers from thinking the mouse is over their controls and showing them as highlighted.
|
||||
drawScreenInternal(layer, guiGraphics, Integer.MAX_VALUE, Integer.MAX_VALUE, partialTick);
|
||||
guiGraphics.pose().translate(0,0,2000);
|
||||
guiGraphics.pose().translate(0, 0, 10000);
|
||||
});
|
||||
drawScreenInternal(screen, guiGraphics, mouseX, mouseY, partialTick);
|
||||
guiGraphics.pose().popPose();
|
||||
@@ -530,6 +532,25 @@ public class ForgeHooksClient {
|
||||
return evt;
|
||||
}
|
||||
|
||||
public static void onCustomizeChatEvent(GuiGraphics guiGraphics, ChatComponent chat, Window window, int mouseX, int mouseY, int tickCount) {
|
||||
var minecraft = Minecraft.getInstance();
|
||||
minecraft.getProfiler().push("chat");
|
||||
CustomizeGuiOverlayEvent.Chat evt = new CustomizeGuiOverlayEvent.Chat(window, guiGraphics, minecraft.getFrameTime(), 0, chat.getHeight() - 40);
|
||||
MinecraftForge.EVENT_BUS.post(evt);
|
||||
guiGraphics.pose().pushPose();
|
||||
// We give the absolute Y position of the chat component in the event and account for the chat component's own offsetting here.
|
||||
guiGraphics.pose().translate(evt.getPosX(), (evt.getPosY() - chat.getHeight() + 40) / chat.getScale(), 0.0D);
|
||||
chat.render(guiGraphics, tickCount, mouseX, mouseY, false);
|
||||
guiGraphics.pose().popPose();
|
||||
minecraft.getProfiler().pop();
|
||||
}
|
||||
|
||||
public static void onCustomizeDebugEvent(GuiGraphics guiGraphics, Window window, float partialTick, List<String> text, boolean isLeft) {
|
||||
var evt = new CustomizeGuiOverlayEvent.DebugText(window, guiGraphics, partialTick, text,
|
||||
isLeft ? CustomizeGuiOverlayEvent.DebugText.Side.Left : CustomizeGuiOverlayEvent.DebugText.Side.Right);
|
||||
MinecraftForge.EVENT_BUS.post(evt);
|
||||
}
|
||||
|
||||
public static void onClientChangeGameType(PlayerInfo info, GameType currentGameMode, GameType newGameMode) {
|
||||
if (currentGameMode != newGameMode) {
|
||||
ClientPlayerChangeGameTypeEvent evt = new ClientPlayerChangeGameTypeEvent(info, currentGameMode, newGameMode);
|
||||
|
||||
@@ -14,7 +14,7 @@ import net.minecraft.client.renderer.RenderType;
|
||||
* be the same as {@code entity}.
|
||||
*/
|
||||
public record RenderTypeGroup(RenderType block, RenderType entity, RenderType entityFabulous) {
|
||||
public static RenderTypeGroup EMPTY = new RenderTypeGroup(null, null, null);
|
||||
public static final RenderTypeGroup EMPTY = new RenderTypeGroup(null, null, null);
|
||||
|
||||
public RenderTypeGroup {
|
||||
if ((block == null) != (entity == null) || (block == null) != (entityFabulous == null))
|
||||
|
||||
@@ -15,6 +15,7 @@ import net.minecraftforge.fml.LogicalSide;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Fired when an overlay is about to be rendered to the screen to allow the user to modify it.
|
||||
@@ -133,31 +134,46 @@ public abstract class CustomizeGuiOverlayEvent extends Event
|
||||
*/
|
||||
public static class DebugText extends CustomizeGuiOverlayEvent
|
||||
{
|
||||
private final ArrayList<String> left;
|
||||
private final ArrayList<String> right;
|
||||
private final List<String> text;
|
||||
|
||||
private final Side side;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public DebugText(Window window, GuiGraphics guiGraphics, float partialTick, ArrayList<String> left, ArrayList<String> right)
|
||||
public DebugText(Window window, GuiGraphics guiGraphics, float partialTick, List<String> text, Side side)
|
||||
{
|
||||
super(window, guiGraphics, partialTick);
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
this.text = text;
|
||||
this.side = side;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the modifiable list of text to render on the left side
|
||||
* @return the modifiable list of text to render on the side
|
||||
*/
|
||||
public ArrayList<String> getLeft()
|
||||
public List<String> getText()
|
||||
{
|
||||
return left;
|
||||
return this.text;
|
||||
}
|
||||
|
||||
@Deprecated(forRemoval = true, since = "1.20.6")
|
||||
public List<String> getLeft() {
|
||||
return Side.Left.equals(this.side) ? this.text : new ArrayList<>();
|
||||
}
|
||||
|
||||
@Deprecated(forRemoval = true, since = "1.20.6")
|
||||
public List<String> getRight() {
|
||||
return Side.Right.equals(this.side) ? this.text : new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the modifiable list of text to render on the right side
|
||||
* @return the side of the text getting rendered.
|
||||
*/
|
||||
public ArrayList<String> getRight()
|
||||
{
|
||||
return right;
|
||||
public Side getSide() {
|
||||
return this.side;
|
||||
}
|
||||
|
||||
public enum Side {
|
||||
Left,
|
||||
Right
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,11 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.eventbus.api.Cancelable;
|
||||
import net.minecraftforge.fml.ModLoader;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import com.mojang.blaze3d.audio.Channel;
|
||||
@@ -50,31 +55,46 @@ import net.minecraft.world.entity.decoration.ItemFrame;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraftforge.client.event.sound.PlaySoundSourceEvent;
|
||||
import net.minecraftforge.client.event.sound.PlayStreamingSourceEvent;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.event.entity.player.PlayerInteractEvent;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.ModLoader;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public final class ForgeEventFactoryClient {
|
||||
private static final ModLoader ML = ModLoader.get();
|
||||
|
||||
private ForgeEventFactoryClient() {}
|
||||
|
||||
/**
|
||||
* Post an event to the {@link MinecraftForge#EVENT_BUS}
|
||||
* @return true if the event is {@link Cancelable} and has been canceled
|
||||
*/
|
||||
private static boolean post(Event e) {
|
||||
return MinecraftForge.EVENT_BUS.post(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Post an event to the {@link MinecraftForge#EVENT_BUS}, then return the event object
|
||||
* @return the event object passed in and possibly modified by listeners
|
||||
*/
|
||||
private static <E extends Event> E fire(E e) {
|
||||
MinecraftForge.EVENT_BUS.post(e);
|
||||
post(e);
|
||||
return e;
|
||||
}
|
||||
|
||||
/**
|
||||
* Post an event to the {@link ModLoader#get()} event bus
|
||||
*/
|
||||
private static <T extends Event & IModBusEvent> void postModBus(T e) {
|
||||
ML.postEvent(e);
|
||||
}
|
||||
|
||||
public static void onGatherLayers(Map<EntityType<?>, EntityRenderer<?>> renderers, Map<Model, EntityRenderer<? extends Player>> playerRenderers, Context context) {
|
||||
// TODO: Why is this a ModLoader event...
|
||||
ModLoader.get().postEvent(new EntityRenderersEvent.AddLayers(renderers, playerRenderers, context));
|
||||
postModBus(new EntityRenderersEvent.AddLayers(renderers, playerRenderers, context));
|
||||
}
|
||||
|
||||
public static void onRegisterShaders(ResourceProvider resourceProvider, List<Pair<ShaderInstance, Consumer<ShaderInstance>>> shaderList) {
|
||||
// TODO: Why i this a ModLoader event...
|
||||
ModLoader.get().postEvent(new RegisterShadersEvent(resourceProvider, shaderList));
|
||||
// TODO: Why is this a ModLoader event...
|
||||
postModBus(new RegisterShadersEvent(resourceProvider, shaderList));
|
||||
}
|
||||
|
||||
public static void onScreenMouseReleased(boolean[] state, Screen screen, double mouseX, double mouseY, int button) {
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.client.extensions;
|
||||
|
||||
import net.minecraft.client.renderer.entity.layers.ElytraLayer;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.Items;
|
||||
|
||||
public interface IForgeElytraLayer<T> {
|
||||
/**
|
||||
* Determines if the ElytraLayer should render.
|
||||
* ItemStack and Entity are provided for modder convenience,
|
||||
* For example, using the same ElytraLayer for multiple custom Elytra.
|
||||
*
|
||||
* @param stack The Elytra ItemStack
|
||||
* @param entity The entity being rendered.
|
||||
* @return If the ElytraLayer should render.
|
||||
*/
|
||||
default boolean shouldRender(ItemStack stack, T entity) {
|
||||
return stack.getItem() == Items.ELYTRA;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the texture to use with this ElytraLayer.
|
||||
* This assumes the vanilla Elytra model.
|
||||
*
|
||||
* @param stack The Elytra ItemStack.
|
||||
* @param entity The entity being rendered.
|
||||
* @return The texture.
|
||||
*/
|
||||
default ResourceLocation getElytraTexture(ItemStack stack, T entity) {
|
||||
return ElytraLayer.WINGS_LOCATION;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.client.extensions;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.mojang.blaze3d.vertex.VertexFormat;
|
||||
import com.mojang.blaze3d.vertex.VertexFormatElement;
|
||||
|
||||
/**
|
||||
* Extension interface for {@link VertexFormat}.
|
||||
*/
|
||||
public interface IForgeVertexFormat {
|
||||
ImmutableList<VertexFormatElement> getElements();
|
||||
|
||||
default boolean hasPosition() {
|
||||
return getElements().stream().anyMatch(e -> e.getUsage() == VertexFormatElement.Usage.POSITION);
|
||||
}
|
||||
|
||||
default boolean hasNormal() {
|
||||
return getElements().stream().anyMatch(e -> e.getUsage() == VertexFormatElement.Usage.NORMAL);
|
||||
}
|
||||
|
||||
default boolean hasColor() {
|
||||
return getElements().stream().anyMatch(e -> e.getUsage() == VertexFormatElement.Usage.COLOR);
|
||||
}
|
||||
|
||||
default boolean hasUV(int which) {
|
||||
for (var e : getElements()) {
|
||||
if (e.getUsage() == VertexFormatElement.Usage.UV && e.getIndex() == which) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -16,14 +16,13 @@ import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import net.minecraft.client.gui.narration.NarrationElementOutput;
|
||||
import net.minecraftforge.client.ConfigScreenHandler;
|
||||
import net.minecraftforge.client.gui.widget.ModListWidget;
|
||||
import net.minecraftforge.client.gui.widget.ScrollPanel;
|
||||
import net.minecraftforge.fml.loading.moddiscovery.ModFileInfo;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.maven.artifact.versioning.ComparableVersion;
|
||||
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
@@ -56,10 +55,11 @@ import net.minecraftforge.forgespi.language.IModInfo;
|
||||
import net.minecraft.locale.Language;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.chat.Style;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
public class ModListScreen extends Screen {
|
||||
private static String stripControlCodes(String value) { return net.minecraft.util.StringUtil.stripColor(value); }
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
private enum SortType implements Comparator<IModInfo> {
|
||||
NORMAL,
|
||||
A_TO_Z{ @Override protected int compare(String name1, String name2){ return name1.compareTo(name2); }},
|
||||
@@ -229,7 +229,7 @@ public class ModListScreen extends Screen {
|
||||
listWidth = Math.max(listWidth,getFontRenderer().width(mod.getDisplayName()) + 10);
|
||||
listWidth = Math.max(listWidth,getFontRenderer().width(MavenVersionStringHelper.artifactVersionToString(mod.getVersion())) + 5);
|
||||
}
|
||||
listWidth = Math.max(Math.min(listWidth, width/3), 100);
|
||||
listWidth = Math.max(Math.min(listWidth, width / 3), 100);
|
||||
listWidth += listWidth % NUM_BUTTONS != 0 ? (NUM_BUTTONS - listWidth % NUM_BUTTONS) : 0;
|
||||
|
||||
int modInfoWidth = this.width - this.listWidth - (PADDING * 3);
|
||||
@@ -237,14 +237,23 @@ public class ModListScreen extends Screen {
|
||||
int y = this.height - 20 - PADDING;
|
||||
int fullButtonHeight = PADDING + 20 + PADDING;
|
||||
|
||||
doneButton = Button.builder(Component.translatable("gui.done"), b -> ModListScreen.this.onClose()).bounds(((listWidth + PADDING + this.width - doneButtonWidth) / 2), y, doneButtonWidth, 20).build();
|
||||
openModsFolderButton = Button.builder(Component.translatable("fml.menu.mods.openmodsfolder"), b -> Util.getPlatform().openFile(FMLPaths.MODSDIR.get().toFile())).bounds(6, y, this.listWidth, 20).build();
|
||||
doneButton = Button.builder(Component.translatable("gui.done"), b -> ModListScreen.this.onClose())
|
||||
.bounds(((listWidth + PADDING + this.width - doneButtonWidth) / 2), y, doneButtonWidth, 20)
|
||||
.build();
|
||||
|
||||
openModsFolderButton = Button.builder(Component.translatable("fml.menu.mods.openmodsfolder"), b -> Util.getPlatform().openFile(FMLPaths.MODSDIR.get().toFile()))
|
||||
.bounds(6, y, this.listWidth, 20)
|
||||
.build();
|
||||
|
||||
y -= 20 + PADDING;
|
||||
configButton = Button.builder(Component.translatable("fml.menu.mods.config"), b -> ModListScreen.this.displayModConfig()).bounds(6, y, this.listWidth, 20).build();
|
||||
configButton = Button.builder(Component.translatable("fml.menu.mods.config"), b -> ModListScreen.this.displayModConfig())
|
||||
.bounds(6, y, this.listWidth, 20)
|
||||
.build();
|
||||
|
||||
y -= 14 + PADDING;
|
||||
search = new EditBox(getFontRenderer(), PADDING + 1, y, listWidth - 2, 14, Component.translatable("fml.menu.mods.search"));
|
||||
|
||||
this.modList = new ModListWidget(this, listWidth, fullButtonHeight, search.getY() - getFontRenderer().lineHeight*4 - PADDING);
|
||||
this.modList = new ModListWidget(this, listWidth, fullButtonHeight, search.getY() - getFontRenderer().lineHeight * 4 - PADDING);
|
||||
this.modList.setX(6);
|
||||
this.modInfo = new InfoPanel(this.minecraft, modInfoWidth, this.height - PADDING - fullButtonHeight, PADDING);
|
||||
|
||||
@@ -261,11 +270,20 @@ public class ModListScreen extends Screen {
|
||||
|
||||
final int width = listWidth / NUM_BUTTONS;
|
||||
int x = PADDING;
|
||||
addRenderableWidget(SortType.NORMAL.button = Button.builder(SortType.NORMAL.getButtonText(), b -> resortMods(SortType.NORMAL)).bounds(x, PADDING, width - BUTTON_MARGIN, 20).build());
|
||||
addRenderableWidget(SortType.NORMAL.button = Button.builder(SortType.NORMAL.getButtonText(), b -> resortMods(SortType.NORMAL))
|
||||
.bounds(x, PADDING, width - BUTTON_MARGIN, 20)
|
||||
.build());
|
||||
|
||||
x += width + BUTTON_MARGIN;
|
||||
addRenderableWidget(SortType.A_TO_Z.button = Button.builder(SortType.A_TO_Z.getButtonText(), b -> resortMods(SortType.A_TO_Z)).bounds(x, PADDING, width - BUTTON_MARGIN, 20).build());
|
||||
addRenderableWidget(SortType.A_TO_Z.button = Button.builder(SortType.A_TO_Z.getButtonText(), b -> resortMods(SortType.A_TO_Z))
|
||||
.bounds(x, PADDING, width - BUTTON_MARGIN, 20)
|
||||
.build());
|
||||
|
||||
x += width + BUTTON_MARGIN;
|
||||
addRenderableWidget(SortType.Z_TO_A.button = Button.builder(SortType.Z_TO_A.getButtonText(), b -> resortMods(SortType.Z_TO_A)).bounds(x, PADDING, width - BUTTON_MARGIN, 20).build());
|
||||
addRenderableWidget(SortType.Z_TO_A.button = Button.builder(SortType.Z_TO_A.getButtonText(), b -> resortMods(SortType.Z_TO_A))
|
||||
.bounds(x, PADDING, width - BUTTON_MARGIN, 20)
|
||||
.build());
|
||||
|
||||
resortMods(SortType.NORMAL);
|
||||
updateCache();
|
||||
}
|
||||
@@ -275,7 +293,9 @@ public class ModListScreen extends Screen {
|
||||
return;
|
||||
|
||||
try {
|
||||
ConfigScreenHandler.getScreenFactoryFor(selected.getInfo()).map(f->f.apply(this.minecraft, this)).ifPresent(newScreen -> this.minecraft.setScreen(newScreen));
|
||||
ConfigScreenHandler.getScreenFactoryFor(selected.getInfo())
|
||||
.map(f -> f.apply(this.minecraft, this))
|
||||
.ifPresent(newScreen -> this.minecraft.setScreen(newScreen));
|
||||
} catch (final Exception e) {
|
||||
LOGGER.error("There was a critical issue trying to build the config GUI for {}", selected.getInfo().getModId(), e);
|
||||
}
|
||||
@@ -295,7 +315,10 @@ public class ModListScreen extends Screen {
|
||||
mods.sort(sortType);
|
||||
modList.refreshList();
|
||||
if (selected != null) {
|
||||
selected = modList.children().stream().filter(e -> e.getInfo() == selected.getInfo()).findFirst().orElse(null);
|
||||
selected = modList.children().stream()
|
||||
.filter(e -> e.getInfo() == selected.getInfo())
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
updateCache();
|
||||
}
|
||||
sorted = true;
|
||||
@@ -303,7 +326,9 @@ public class ModListScreen extends Screen {
|
||||
}
|
||||
|
||||
public <T extends ObjectSelectionList.Entry<T>> void buildModList(Consumer<T> modListViewConsumer, Function<IModInfo, T> newEntry) {
|
||||
mods.forEach(mod->modListViewConsumer.accept(newEntry.apply(mod)));
|
||||
for (IModInfo mod : mods) {
|
||||
modListViewConsumer.accept(newEntry.apply(mod));
|
||||
}
|
||||
}
|
||||
|
||||
private void reloadMods() {
|
||||
@@ -405,12 +430,12 @@ public class ModListScreen extends Screen {
|
||||
lines.add(ForgeI18n.parseMessage("fml.menu.mods.info.authors", authors)));
|
||||
selectedMod.getConfig().getConfigElement("displayURL").ifPresent(displayURL ->
|
||||
lines.add(ForgeI18n.parseMessage("fml.menu.mods.info.displayurl", displayURL)));
|
||||
if (selectedMod.getOwningFile() == null || selectedMod.getOwningFile().getMods().size()==1)
|
||||
if (selectedMod.getOwningFile() == null || selectedMod.getOwningFile().getMods().size() == 1)
|
||||
lines.add(ForgeI18n.parseMessage("fml.menu.mods.info.nochildmods"));
|
||||
else
|
||||
lines.add(ForgeI18n.parseMessage("fml.menu.mods.info.childmods", selectedMod.getOwningFile().getMods().stream().map(IModInfo::getDisplayName).collect(Collectors.joining(","))));
|
||||
|
||||
if (vercheck.status() == VersionChecker.Status.OUTDATED || vercheck.status() == VersionChecker.Status.BETA_OUTDATED)
|
||||
if (vercheck.status().isOutdated())
|
||||
lines.add(ForgeI18n.parseMessage("fml.menu.mods.info.updateavailable", vercheck.url() == null ? "" : vercheck.url()));
|
||||
lines.add(ForgeI18n.parseMessage("fml.menu.mods.info.license", ((ModFileInfo) selectedMod.getOwningFile()).getLicense()));
|
||||
lines.add(null);
|
||||
@@ -426,7 +451,7 @@ public class ModListScreen extends Screen {
|
||||
}
|
||||
*/
|
||||
|
||||
if ((vercheck.status() == VersionChecker.Status.OUTDATED || vercheck.status() == VersionChecker.Status.BETA_OUTDATED) && !vercheck.changes().isEmpty()) {
|
||||
if ((vercheck.status().isOutdated()) && !vercheck.changes().isEmpty()) {
|
||||
lines.add(null);
|
||||
lines.add(ForgeI18n.parseMessage("fml.menu.mods.info.changelogheader"));
|
||||
for (Entry<ComparableVersion, String> entry : vercheck.changes().entrySet()) {
|
||||
|
||||
@@ -26,7 +26,7 @@ public class ModListWidget extends ObjectSelectionList<ModListWidget.ModEntry>
|
||||
private static final ResourceLocation VERSION_CHECK_ICONS = new ResourceLocation(ForgeVersion.MOD_ID, "textures/gui/version_check_icons.png");
|
||||
private final int listWidth;
|
||||
|
||||
private ModListScreen parent;
|
||||
private final ModListScreen parent;
|
||||
|
||||
public ModListWidget(ModListScreen parent, int listWidth, int top, int bottom)
|
||||
{
|
||||
|
||||
@@ -91,8 +91,8 @@ public class ClientModLoader {
|
||||
public static VersionChecker.Status checkForUpdates() {
|
||||
boolean anyOutdated = ModList.get().getMods().stream()
|
||||
.map(VersionChecker::getResult)
|
||||
.map(result -> result.status())
|
||||
.anyMatch(status -> status == VersionChecker.Status.OUTDATED || status == VersionChecker.Status.BETA_OUTDATED);
|
||||
.map(VersionChecker.CheckResult::status)
|
||||
.anyMatch(VersionChecker.Status::isOutdated);
|
||||
return anyOutdated ? VersionChecker.Status.OUTDATED : null;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
|
||||
import net.minecraftforge.common.ForgeConfigSpec.BooleanValue;
|
||||
import net.minecraftforge.common.ForgeConfigSpec.DoubleValue;
|
||||
import net.minecraftforge.common.ForgeConfigSpec.ConfigValue;
|
||||
|
||||
public class ForgeConfig {
|
||||
@@ -23,9 +22,6 @@ public class ForgeConfig {
|
||||
|
||||
public final BooleanValue fullBoundingBoxLadders;
|
||||
|
||||
public final DoubleValue zombieBaseSummonChance;
|
||||
public final DoubleValue zombieBabyChance;
|
||||
|
||||
public final ConfigValue<String> permissionHandler;
|
||||
|
||||
public final BooleanValue advertiseDedicatedServerToLan;
|
||||
@@ -52,18 +48,6 @@ public class ForgeConfig {
|
||||
.worldRestart()
|
||||
.define("fullBoundingBoxLadders", false);
|
||||
|
||||
zombieBaseSummonChance = builder
|
||||
.comment("Base zombie summoning spawn chance. Allows changing the bonus zombie summoning mechanic.")
|
||||
.translation("forge.configgui.zombieBaseSummonChance")
|
||||
.worldRestart()
|
||||
.defineInRange("zombieBaseSummonChance", 0.1D, 0.0D, 1.0D);
|
||||
|
||||
zombieBabyChance = builder
|
||||
.comment("Chance that a zombie (or subclass) is a baby. Allows changing the zombie spawning mechanic.")
|
||||
.translation("forge.configgui.zombieBabyChance")
|
||||
.worldRestart()
|
||||
.defineInRange("zombieBabyChance", 0.05D, 0.0D, 1.0D);
|
||||
|
||||
permissionHandler = builder
|
||||
.comment("The permission handler used by the server. Defaults to forge:default_handler if no such handler with that name is registered.")
|
||||
.translation("forge.configgui.permissionHandler")
|
||||
@@ -103,9 +87,6 @@ public class ForgeConfig {
|
||||
|
||||
public final BooleanValue useCombinedDepthStencilAttachment;
|
||||
|
||||
@Deprecated(since = "1.20.1", forRemoval = true) // Config option ignored.
|
||||
public final BooleanValue compressLanIPv6Addresses;
|
||||
|
||||
public final BooleanValue calculateAllNormals;
|
||||
|
||||
public final BooleanValue stabilizeDirectionGetNearest;
|
||||
@@ -137,11 +118,6 @@ public class ForgeConfig {
|
||||
.translation("forge.configgui.useCombinedDepthStencilAttachment")
|
||||
.define("useCombinedDepthStencilAttachment", false);
|
||||
|
||||
compressLanIPv6Addresses = builder
|
||||
.comment("[DEPRECATED] Does nothing anymore, IPv6 addresses will be compressed always")
|
||||
.translation("forge.configgui.compressLanIPv6Addresses")
|
||||
.define("compressLanIPv6Addresses", true);
|
||||
|
||||
calculateAllNormals = builder
|
||||
.comment("During block model baking, manually calculates the normal for all faces.",
|
||||
"This was the default behavior of forge between versions 31.0 and 47.1.",
|
||||
|
||||
@@ -49,9 +49,11 @@ import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.HolderLookup;
|
||||
import net.minecraft.core.HolderSet;
|
||||
import net.minecraft.core.NonNullList;
|
||||
import net.minecraft.core.component.DataComponentMap;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.core.particles.ParticleTypes;
|
||||
import net.minecraft.network.Connection;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||
import net.minecraft.network.chat.ClickEvent;
|
||||
import net.minecraft.network.chat.Component;
|
||||
@@ -100,9 +102,14 @@ import net.minecraft.world.item.context.UseOnContext;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.nbt.ListTag;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.PacketFlow;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
import net.minecraft.network.protocol.common.custom.DiscardedPayload;
|
||||
import net.minecraft.network.syncher.EntityDataSerializer;
|
||||
import net.minecraft.network.protocol.game.ClientGamePacketListener;
|
||||
import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket;
|
||||
import net.minecraft.network.protocol.login.ClientboundCustomQueryPacket;
|
||||
import net.minecraft.network.protocol.login.ServerboundCustomQueryAnswerPacket;
|
||||
import net.minecraft.stats.Stats;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraft.world.level.block.entity.FurnaceBlockEntity;
|
||||
@@ -114,7 +121,6 @@ import net.minecraft.world.level.biome.BiomeGenerationSettings;
|
||||
import net.minecraft.world.level.biome.MobSpawnSettings;
|
||||
import net.minecraftforge.common.crafting.conditions.ConditionCodec;
|
||||
import net.minecraftforge.common.crafting.conditions.ICondition;
|
||||
import net.minecraftforge.common.crafting.conditions.ICondition.IContext;
|
||||
import net.minecraftforge.common.crafting.ingredients.IIngredientSerializer;
|
||||
import net.minecraftforge.common.util.BlockSnapshot;
|
||||
import net.minecraftforge.common.util.BrainBuilder;
|
||||
@@ -125,6 +131,7 @@ import net.minecraftforge.entity.IEntityAdditionalSpawnData;
|
||||
import net.minecraftforge.event.AnvilUpdateEvent;
|
||||
import net.minecraftforge.event.BuildCreativeModeTabContentsEvent;
|
||||
import net.minecraftforge.event.ForgeEventFactory;
|
||||
import net.minecraftforge.event.GatherComponentsEvent;
|
||||
import net.minecraftforge.event.GrindstoneEvent;
|
||||
import net.minecraftforge.event.ModMismatchEvent;
|
||||
import net.minecraftforge.event.ServerChatEvent;
|
||||
@@ -155,15 +162,17 @@ import net.minecraftforge.event.level.NoteBlockEvent;
|
||||
import net.minecraftforge.event.network.CustomPayloadEvent;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fluids.FluidType;
|
||||
import net.minecraftforge.fml.LogicalSide;
|
||||
import net.minecraftforge.fml.ModList;
|
||||
import net.minecraftforge.fml.ModLoader;
|
||||
import net.minecraftforge.fml.config.ConfigTracker;
|
||||
import net.minecraftforge.fml.util.thread.EffectiveSide;
|
||||
import net.minecraftforge.network.ConnectionType;
|
||||
import net.minecraftforge.network.ICustomPacket;
|
||||
import net.minecraftforge.network.ForgePayload;
|
||||
import net.minecraftforge.network.NetworkContext;
|
||||
import net.minecraftforge.network.NetworkDirection;
|
||||
import net.minecraftforge.network.NetworkInitialization;
|
||||
import net.minecraftforge.network.NetworkProtocol;
|
||||
import net.minecraftforge.network.NetworkRegistry;
|
||||
import net.minecraftforge.network.packets.SpawnEntity;
|
||||
import net.minecraftforge.resource.ResourcePackLoader;
|
||||
@@ -224,10 +233,6 @@ public final class ForgeHooks {
|
||||
return brainBuilder.makeBrain(dynamic);
|
||||
}
|
||||
|
||||
public static boolean onLivingTick(LivingEntity entity) {
|
||||
return MinecraftForge.EVENT_BUS.post(new LivingTickEvent(entity));
|
||||
}
|
||||
|
||||
public static boolean onLivingAttack(LivingEntity entity, DamageSource src, float amount) {
|
||||
return entity instanceof Player || !MinecraftForge.EVENT_BUS.post(new LivingAttackEvent(entity, src, amount));
|
||||
}
|
||||
@@ -250,12 +255,11 @@ public final class ForgeHooks {
|
||||
return (MinecraftForge.EVENT_BUS.post(event) ? 0 : event.getAmount());
|
||||
}
|
||||
|
||||
public static boolean onLivingDeath(LivingEntity entity, DamageSource src) {
|
||||
return MinecraftForge.EVENT_BUS.post(new LivingDeathEvent(entity, src));
|
||||
}
|
||||
|
||||
public static boolean onLivingDrops(LivingEntity entity, DamageSource source, Collection<ItemEntity> drops, int lootingLevel, boolean recentlyHit) {
|
||||
return MinecraftForge.EVENT_BUS.post(new LivingDropsEvent(entity, source, drops, lootingLevel, recentlyHit));
|
||||
public static InteractionResult onInteractEntityAt(Entity entity, Player player, Vec3 vec3d, InteractionHand hand) {
|
||||
var ret = ForgeEventFactory.onEntityInteractSpecific(player, entity, hand, vec3d);
|
||||
if (ret.isCanceled())
|
||||
return ret.getCancellationResult();
|
||||
return entity.interactAt(player, vec3d, hand);
|
||||
}
|
||||
|
||||
public static int getLootingLevel(Entity target, @Nullable Entity killer, @Nullable DamageSource cause) {
|
||||
@@ -776,7 +780,7 @@ public final class ForgeHooks {
|
||||
@SuppressWarnings("deprecation")
|
||||
public static synchronized void updateBurns() {
|
||||
VANILLA_BURNS.clear();
|
||||
FurnaceBlockEntity.getFuel().entrySet().forEach(e -> VANILLA_BURNS.put(ForgeRegistries.ITEMS.getDelegateOrThrow(e.getKey()), e.getValue()));
|
||||
FurnaceBlockEntity.getFuel().forEach((k, v) -> VANILLA_BURNS.put(ForgeRegistries.ITEMS.getDelegateOrThrow(k), v));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1121,18 +1125,51 @@ public final class ForgeHooks {
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static boolean onCustomPayload(final ICustomPacket<?> packet, final Connection connection) {
|
||||
if (packet.getDirection().getReceptionSide() != EffectiveSide.get()) {
|
||||
connection.disconnect(Component.literal("Illegal packet received, terminating connection"));
|
||||
return false;
|
||||
}
|
||||
public static <B extends FriendlyByteBuf> StreamCodec<B, ? extends CustomPacketPayload> getCustomPayloadCodec(ResourceLocation id, int max) {
|
||||
var channel = NetworkRegistry.findTarget(id);
|
||||
if (channel == null)
|
||||
return DiscardedPayload.codec(id, max);
|
||||
|
||||
var context = new CustomPayloadEvent.Context(connection, packet.getDirection());
|
||||
return onCustomPayload(new CustomPayloadEvent(packet, context));
|
||||
return StreamCodec.<B, ForgePayload>ofMember(
|
||||
(value, buf) -> {
|
||||
value.encoder().accept(buf);
|
||||
},
|
||||
(buf) -> {
|
||||
int len = buf.readableBytes();
|
||||
if (len < 0 || len > max)
|
||||
throw new IllegalArgumentException("Payload may not be larger then " + max + " bytes");
|
||||
return ForgePayload.create(id, buf.wrap(buf.readBytes(len)));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static boolean onCustomPayload(CustomPacketPayload payload, Connection connection) {
|
||||
var context = new CustomPayloadEvent.Context(connection);
|
||||
return onCustomPayload(new CustomPayloadEvent(payload.type().id(), payload, context, 0));
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static boolean onCustomPayload(ClientboundCustomQueryPacket packet, Connection connection) {
|
||||
var context = new CustomPayloadEvent.Context(connection);
|
||||
return onCustomPayload(new CustomPayloadEvent(packet.payload().id(), packet.payload(), context, packet.transactionId()));
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static boolean onCustomPayload(ServerboundCustomQueryAnswerPacket packet, Connection connection) {
|
||||
var context = new CustomPayloadEvent.Context(connection);
|
||||
return onCustomPayload(new CustomPayloadEvent(NetworkInitialization.LOGIN_NAME, packet.payload(), context, packet.transactionId()));
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static boolean onCustomPayload(CustomPayloadEvent event) {
|
||||
var connection = event.getSource().getConnection();
|
||||
var expectedSide = connection.getReceiving() == PacketFlow.CLIENTBOUND ? LogicalSide.CLIENT : LogicalSide.SERVER;
|
||||
if (expectedSide != EffectiveSide.get()) {
|
||||
connection.disconnect(Component.literal("Illegal packet received, terminating connection"));
|
||||
return false;
|
||||
}
|
||||
|
||||
var channel = NetworkRegistry.findTarget(event.getChannel());
|
||||
if (channel != null && channel.dispatch(event))
|
||||
return true;
|
||||
@@ -1160,12 +1197,7 @@ public final class ForgeHooks {
|
||||
if (!(entity instanceof IEntityAdditionalSpawnData add))
|
||||
throw new IllegalArgumentException(entity.getClass() + " is not an instance of " + IEntityAdditionalSpawnData.class);
|
||||
|
||||
var play = NetworkInitialization.PLAY;
|
||||
var msg = new SpawnEntity(entity);
|
||||
var data = play.toBuffer(msg);
|
||||
@SuppressWarnings("unchecked")
|
||||
var pkt = (Packet<ClientGamePacketListener>)NetworkDirection.PLAY_TO_CLIENT.buildPacket(data, play.getName()).getThis();
|
||||
return pkt;
|
||||
return NetworkDirection.PLAY_TO_CLIENT.buildPacket(NetworkInitialization.PLAY, new SpawnEntity(entity));
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
@@ -1261,4 +1293,23 @@ public final class ForgeHooks {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static DyeColor getDyeColorFromItemStack(ItemStack stack) {
|
||||
if (stack.getItem() instanceof DyeItem dye)
|
||||
return dye.getDyeColor();
|
||||
|
||||
for (int x = 0; x < DyeColor.BLACK.getId(); x++) {
|
||||
var color = DyeColor.byId(x);
|
||||
if (stack.is(color.getTag())) {
|
||||
return color;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static DataComponentMap gatherItemComponents(Item item, DataComponentMap dataComponents) {
|
||||
return DataComponentMap.composite(dataComponents, ForgeEventFactory.gatherItemComponentsEvent(item, dataComponents).getDataComponentMap());
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ public interface IForgeEnchantment {
|
||||
* This applies specifically to applying at the enchanting table. The other method {@link #canEnchant(ItemStack)}
|
||||
* applies for <i>all possible</i> enchantments.
|
||||
*/
|
||||
default boolean canApplyAtEnchatingTable(ItemStack stack) {
|
||||
return stack.canApplyAtEnchantingTable(null);
|
||||
default boolean canApplyAtEnchantingTable(ItemStack stack) {
|
||||
return stack.canApplyAtEnchantingTable(self());
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import net.minecraft.world.item.crafting.RecipeType;
|
||||
import net.minecraft.world.InteractionResult;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraftforge.common.ForgeHooks;
|
||||
import net.minecraftforge.common.ToolAction;
|
||||
@@ -326,11 +327,12 @@ public interface IForgeItem {
|
||||
* @param stack ItemStack for the equipped armor
|
||||
* @param entity The entity wearing the armor
|
||||
* @param slot The slot the armor is in
|
||||
* @param type The subtype, can be null or "overlay"
|
||||
* @param layer The layer of the armor being rendered. Typically want to use {@alin ArmorMaterial.Layer#getSuffix()}.
|
||||
* @param inner Weither or not to use the inner texture layer.
|
||||
* @return Path of texture to bind, or null to use default
|
||||
*/
|
||||
@Nullable
|
||||
default String getArmorTexture(ItemStack stack, Entity entity, EquipmentSlot slot, String type) {
|
||||
default ResourceLocation getArmorTexture(ItemStack stack, Entity entity, EquipmentSlot slot, ArmorMaterial.Layer layer, boolean inner) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
package net.minecraftforge.common.util;
|
||||
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public interface ClearableLazy<T> extends Lazy<T> {
|
||||
void invalidate();
|
||||
|
||||
/**
|
||||
* Constructs a lazy-initialized object
|
||||
* @param supplier The supplier for the value, to be called the first time the value is needed.
|
||||
*/
|
||||
static <T> ClearableLazy<T> of(@NotNull Supplier<T> supplier) {
|
||||
return new ClearableLazy.Fast<T>(supplier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a thread-safe lazy-initialized object
|
||||
* @param supplier The supplier for the value, to be called the first time the value is needed.
|
||||
*/
|
||||
static <T> ClearableLazy<T> concurrentOf(@NotNull Supplier<T> supplier) {
|
||||
return new ClearableLazy.Concurrent<>(supplier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Non-thread-safe implementation.
|
||||
*/
|
||||
final class Fast<T> implements ClearableLazy<T> {
|
||||
private final Supplier<T> supplier;
|
||||
private T instance;
|
||||
|
||||
private Fast(Supplier<T> supplier) {
|
||||
this.supplier = supplier;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public final T get() {
|
||||
if (instance == null) {
|
||||
instance = supplier.get();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidate() {
|
||||
this.instance = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Thread-safe implementation.
|
||||
*/
|
||||
final class Concurrent<T> implements ClearableLazy<T> {
|
||||
private final Object lock = new Object();
|
||||
private final Supplier<T> supplier;
|
||||
private volatile T instance;
|
||||
|
||||
private Concurrent(Supplier<T> supplier) {
|
||||
this.supplier = supplier;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public final T get()
|
||||
{
|
||||
var ret = instance;
|
||||
if (ret == null) {
|
||||
synchronized (lock) {
|
||||
if (instance == null) {
|
||||
return instance = supplier.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidate() {
|
||||
synchronized (lock) {
|
||||
this.instance = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,34 +16,41 @@ import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class LogicalSidedProvider<T>
|
||||
{
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
public class LogicalSidedProvider<T> {
|
||||
public static final LogicalSidedProvider<BlockableEventLoop<? super TickTask>> WORKQUEUE = new LogicalSidedProvider<>(Supplier::get, Supplier::get);
|
||||
public static final LogicalSidedProvider<Optional<Level>> CLIENTWORLD = new LogicalSidedProvider<>((c)-> Optional.of(c.get().level), (s)->Optional.empty());
|
||||
|
||||
public T get(LogicalSide side) {
|
||||
return side == LogicalSide.CLIENT ? clientSide.apply(client) : serverSide.apply(server);
|
||||
}
|
||||
|
||||
public T get(boolean client) {
|
||||
return get(client ? LogicalSide.CLIENT : LogicalSide.SERVER);
|
||||
}
|
||||
|
||||
// =========================================
|
||||
// Everything below this is internal
|
||||
// =========================================
|
||||
private static Supplier<Minecraft> client;
|
||||
private static Supplier<MinecraftServer> server;
|
||||
|
||||
// INTERNAL, DO NOT CALL
|
||||
public static void setClient(Supplier<Minecraft> client)
|
||||
{
|
||||
@ApiStatus.Internal
|
||||
public static void setClient(Supplier<Minecraft> client) {
|
||||
LogicalSidedProvider.client = client;
|
||||
}
|
||||
public static void setServer(Supplier<MinecraftServer> server)
|
||||
{
|
||||
|
||||
@ApiStatus.Internal
|
||||
public static void setServer(Supplier<MinecraftServer> server) {
|
||||
LogicalSidedProvider.server = server;
|
||||
}
|
||||
|
||||
private LogicalSidedProvider(Function<Supplier<Minecraft>, T> clientSide, Function<Supplier<MinecraftServer>, T> serverSide)
|
||||
{
|
||||
private LogicalSidedProvider(Function<Supplier<Minecraft>, T> clientSide, Function<Supplier<MinecraftServer>, T> serverSide) {
|
||||
this.clientSide = clientSide;
|
||||
this.serverSide = serverSide;
|
||||
}
|
||||
|
||||
private final Function<Supplier<Minecraft>, T> clientSide;
|
||||
private final Function<Supplier<MinecraftServer>, T> serverSide;
|
||||
|
||||
public T get(final LogicalSide side) {
|
||||
return side==LogicalSide.CLIENT ? clientSide.apply(client) : serverSide.apply(server);
|
||||
}
|
||||
}
|
||||
@@ -12,11 +12,15 @@ import org.apache.logging.log4j.MarkerManager;
|
||||
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.minecraft.network.Connection;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.PacketListener;
|
||||
import net.minecraft.network.ProtocolInfo;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.PacketFlow;
|
||||
import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket;
|
||||
import net.minecraft.network.protocol.common.ServerboundCustomPayloadPacket;
|
||||
import net.minecraft.network.protocol.configuration.ClientboundFinishConfigurationPacket;
|
||||
import net.minecraftforge.network.ICustomPacket;
|
||||
import net.minecraft.network.protocol.login.ClientboundCustomQueryPacket;
|
||||
import net.minecraft.network.protocol.login.ServerboundCustomQueryAnswerPacket;
|
||||
|
||||
/*
|
||||
* This is a utility class that just dumps packets to the logger.
|
||||
@@ -35,20 +39,44 @@ public class PacketLogger {
|
||||
}
|
||||
|
||||
public void send(Packet<?> packet) {
|
||||
common(connection.getReceiving(), connection.getSending(), packet);
|
||||
common(connection.getReceiving(), connection.getSending(), connection.getOutputboundProtocolInfo(), packet);
|
||||
}
|
||||
|
||||
public void recv(Packet<?> packet) {
|
||||
common(connection.getReceiving(), connection.getReceiving(), packet);
|
||||
common(connection.getReceiving(), connection.getReceiving(), connection.getInboundProtocolInfo(), packet);
|
||||
}
|
||||
|
||||
private void common(PacketFlow side, PacketFlow flow, Packet<?> packet) {
|
||||
private void common(PacketFlow side, PacketFlow flow, ProtocolInfo<?> protocol, Packet<?> packet) {
|
||||
if (!enabled) return;
|
||||
if (packet instanceof ClientboundFinishConfigurationPacket)
|
||||
enabled = false;
|
||||
|
||||
if (packet instanceof ICustomPacket custom && custom.getInternalData() != null) {
|
||||
LOGGER.info(MARKER, "{} {} {}\n{}", side(side), dir(flow), packet.getClass().getName(), HexDumper.dump(custom.getInternalData()));
|
||||
String channel = null;
|
||||
String hex = null;
|
||||
switch (packet) {
|
||||
case ClientboundCustomPayloadPacket custom -> {
|
||||
channel = custom.payload().type().id().toString();
|
||||
hex = hex(protocol, packet);
|
||||
}
|
||||
case ServerboundCustomPayloadPacket custom -> {
|
||||
channel = custom.payload().type().id().toString();
|
||||
hex = hex(protocol, packet);
|
||||
}
|
||||
case ClientboundCustomQueryPacket custom -> {
|
||||
channel = custom.payload().id().toString() + " id " + custom.transactionId();
|
||||
hex = hex(protocol, packet);
|
||||
}
|
||||
case ServerboundCustomQueryAnswerPacket custom -> {
|
||||
channel = packet.getClass().getName() + " id " + custom.transactionId();
|
||||
hex = hex(protocol, packet);
|
||||
}
|
||||
default -> {}
|
||||
}
|
||||
|
||||
if (channel != null && hex != null) {
|
||||
LOGGER.info(MARKER, "{} {} {} {}\n{}", side(side), dir(flow), packet.getClass().getName(), channel, hex);
|
||||
} else if (channel != null) {
|
||||
LOGGER.info(MARKER, "{} {} {} {}", side(side), dir(flow), packet.getClass().getName(), channel);
|
||||
} else {
|
||||
LOGGER.info(MARKER, "{} {} {}", side(side), dir(flow), packet.getClass().getName());
|
||||
}
|
||||
@@ -60,4 +88,11 @@ public class PacketLogger {
|
||||
private static String dir(PacketFlow flow) {
|
||||
return flow == PacketFlow.CLIENTBOUND ? "S->C" : "C->S";
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private static <T extends PacketListener> String hex(ProtocolInfo protocol, Packet packet) {
|
||||
var buf = Unpooled.buffer();
|
||||
protocol.codec().encode(buf, packet);
|
||||
return HexDumper.dump(buf);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
package net.minecraftforge.event;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -13,6 +14,13 @@ import java.util.Optional;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import net.minecraft.core.component.DataComponentMap;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.eventbus.api.Cancelable;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.ModLoader;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -74,6 +82,7 @@ import net.minecraft.world.entity.SpawnGroupData;
|
||||
import net.minecraft.world.entity.animal.Animal;
|
||||
import net.minecraft.world.entity.item.ItemEntity;
|
||||
import net.minecraft.world.entity.monster.Zombie;
|
||||
import net.minecraft.world.entity.npc.AbstractVillager;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.entity.projectile.FishingHook;
|
||||
import net.minecraft.world.entity.projectile.Projectile;
|
||||
@@ -87,6 +96,7 @@ import net.minecraft.world.item.TooltipFlag;
|
||||
import net.minecraft.world.item.alchemy.PotionBrewing.Builder;
|
||||
import net.minecraft.world.item.context.UseOnContext;
|
||||
import net.minecraft.world.item.crafting.RecipeType;
|
||||
import net.minecraft.world.item.trading.MerchantOffer;
|
||||
import net.minecraft.world.level.BaseSpawner;
|
||||
import net.minecraft.world.level.ChunkPos;
|
||||
import net.minecraft.world.level.Explosion;
|
||||
@@ -111,7 +121,6 @@ import net.minecraft.world.level.storage.loot.LootTable;
|
||||
import net.minecraft.world.phys.BlockHitResult;
|
||||
import net.minecraft.world.phys.HitResult;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.common.ToolAction;
|
||||
import net.minecraftforge.common.capabilities.CapabilityDispatcher;
|
||||
import net.minecraftforge.common.capabilities.ICapabilityProvider;
|
||||
@@ -136,10 +145,13 @@ import net.minecraftforge.event.entity.living.LivingAttackEvent;
|
||||
import net.minecraftforge.event.entity.living.LivingBreatheEvent;
|
||||
import net.minecraftforge.event.entity.living.LivingChangeTargetEvent;
|
||||
import net.minecraftforge.event.entity.living.LivingConversionEvent;
|
||||
import net.minecraftforge.event.entity.living.LivingDeathEvent;
|
||||
import net.minecraftforge.event.entity.living.LivingDestroyBlockEvent;
|
||||
import net.minecraftforge.event.entity.living.LivingDropsEvent;
|
||||
import net.minecraftforge.event.entity.living.LivingDrownEvent;
|
||||
import net.minecraftforge.event.entity.living.LivingEntityUseItemEvent;
|
||||
import net.minecraftforge.event.entity.living.LivingEquipmentChangeEvent;
|
||||
import net.minecraftforge.event.entity.living.LivingEvent;
|
||||
import net.minecraftforge.event.entity.living.LivingExperienceDropEvent;
|
||||
import net.minecraftforge.event.entity.living.LivingFallEvent;
|
||||
import net.minecraftforge.event.entity.living.LivingHealEvent;
|
||||
@@ -152,7 +164,6 @@ import net.minecraftforge.event.entity.living.MobSpawnEvent.AllowDespawn;
|
||||
import net.minecraftforge.event.entity.living.MobSpawnEvent.PositionCheck;
|
||||
import net.minecraftforge.event.entity.living.MobSpawnEvent.SpawnPlacementCheck;
|
||||
import net.minecraftforge.event.entity.living.ShieldBlockEvent;
|
||||
import net.minecraftforge.event.entity.living.LivingChangeTargetEvent.ILivingTargetType;
|
||||
import net.minecraftforge.event.entity.living.ZombieEvent.SummonAidEvent;
|
||||
import net.minecraftforge.event.entity.player.AdvancementEvent.AdvancementEarnEvent;
|
||||
import net.minecraftforge.event.entity.player.AdvancementEvent.AdvancementProgressEvent;
|
||||
@@ -178,6 +189,7 @@ import net.minecraftforge.event.entity.player.PlayerWakeUpEvent;
|
||||
import net.minecraftforge.event.entity.player.PlayerXpEvent;
|
||||
import net.minecraftforge.event.entity.player.SleepingLocationCheckEvent;
|
||||
import net.minecraftforge.event.entity.player.SleepingTimeCheckEvent;
|
||||
import net.minecraftforge.event.entity.player.TradeWithVillagerEvent;
|
||||
import net.minecraftforge.event.furnace.FurnaceFuelBurnTimeEvent;
|
||||
import net.minecraftforge.event.level.AlterGroundEvent;
|
||||
import net.minecraftforge.event.level.BlockEvent;
|
||||
@@ -198,27 +210,37 @@ import net.minecraftforge.event.level.SleepFinishedTimeEvent;
|
||||
import net.minecraftforge.event.network.ChannelRegistrationChangeEvent;
|
||||
import net.minecraftforge.event.network.ConnectionStartEvent;
|
||||
import net.minecraftforge.event.network.GatherLoginConfigurationTasksEvent;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.eventbus.api.Event.Result;
|
||||
import net.minecraftforge.fml.LogicalSide;
|
||||
import net.minecraftforge.fml.ModLoader;
|
||||
import net.minecraftforge.fml.event.IModBusEvent;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public final class ForgeEventFactory {
|
||||
private static final ModLoader ML = ModLoader.get();
|
||||
|
||||
private ForgeEventFactory() {}
|
||||
|
||||
/**
|
||||
* Post an event to the {@link MinecraftForge#EVENT_BUS}
|
||||
* @return true if the event is {@link Cancelable} and has been canceled
|
||||
*/
|
||||
private static boolean post(Event e) {
|
||||
return MinecraftForge.EVENT_BUS.post(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Post an event to the {@link MinecraftForge#EVENT_BUS}, then return the event object
|
||||
* @return the event object passed in and possibly modified by listeners
|
||||
*/
|
||||
private static <E extends Event> E fire(E e) {
|
||||
post(e);
|
||||
return e;
|
||||
}
|
||||
|
||||
/**
|
||||
* Post an event to the {@link ModLoader#get()} event bus
|
||||
*/
|
||||
private static <T extends Event & IModBusEvent> void postModBus(T e) {
|
||||
ModLoader.get().postEvent(e);
|
||||
ML.postEvent(e);
|
||||
}
|
||||
|
||||
public static boolean onMultiBlockPlace(@Nullable Entity entity, List<BlockSnapshot> blockSnapshots, Direction direction) {
|
||||
@@ -327,9 +349,9 @@ public final class ForgeEventFactory {
|
||||
boolean cancel = post(event);
|
||||
|
||||
if (!cancel)
|
||||
mob.finalizeSpawn(level, event.getDifficulty(), event.getSpawnType(), event.getSpawnData());
|
||||
return mob.finalizeSpawn(level, event.getDifficulty(), event.getSpawnType(), event.getSpawnData());
|
||||
|
||||
return cancel ? null : event.getSpawnData();
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -340,9 +362,7 @@ public final class ForgeEventFactory {
|
||||
*/
|
||||
@Nullable
|
||||
public static MobSpawnEvent.FinalizeSpawn onFinalizeSpawnSpawner(Mob mob, ServerLevelAccessor level, DifficultyInstance difficulty, @Nullable SpawnGroupData spawnData, @Nullable CompoundTag spawnTag, BaseSpawner spawner) {
|
||||
var event = new MobSpawnEvent.FinalizeSpawn(mob, level, mob.getX(), mob.getY(), mob.getZ(), difficulty, MobSpawnType.SPAWNER, spawnData, spawnTag, spawner);
|
||||
boolean cancel = post(event);
|
||||
return cancel ? null : event;
|
||||
return fire(new MobSpawnEvent.FinalizeSpawn(mob, level, mob.getX(), mob.getY(), mob.getZ(), difficulty, MobSpawnType.SPAWNER, spawnData, spawnTag, spawner));
|
||||
}
|
||||
|
||||
public static Result canEntityDespawn(Mob entity, ServerLevelAccessor level) {
|
||||
@@ -822,6 +842,7 @@ public final class ForgeEventFactory {
|
||||
LevelEvent.PotentialSpawns event = new LevelEvent.PotentialSpawns(level, category, pos, oldList);
|
||||
if (post(event))
|
||||
return WeightedRandomList.create();
|
||||
//System.out.println("List: " + oldList.unwrap() + " " + event.getSpawnerDataList());
|
||||
return WeightedRandomList.create(event.getSpawnerDataList());
|
||||
}
|
||||
|
||||
@@ -909,6 +930,10 @@ public final class ForgeEventFactory {
|
||||
post(new EntityEvent.EnteringSection(entity, packedOldPos, packedNewPos));
|
||||
}
|
||||
|
||||
public static boolean onLivingTick(LivingEntity entity) {
|
||||
return post(new LivingEvent.LivingTickEvent(entity));
|
||||
}
|
||||
|
||||
public static LivingFallEvent onLivingFall(LivingEntity entity, float distance, float damageMultiplier) {
|
||||
return fire(new LivingFallEvent(entity, distance, damageMultiplier));
|
||||
}
|
||||
@@ -925,6 +950,14 @@ public final class ForgeEventFactory {
|
||||
return fire(new LivingKnockBackEvent(target, strength, ratioX, ratioZ));
|
||||
}
|
||||
|
||||
public static boolean onLivingDeath(LivingEntity entity, DamageSource src) {
|
||||
return post(new LivingDeathEvent(entity, src));
|
||||
}
|
||||
|
||||
public static boolean onLivingDrops(LivingEntity entity, DamageSource source, Collection<ItemEntity> drops, int lootingLevel, boolean recentlyHit) {
|
||||
return post(new LivingDropsEvent(entity, source, drops, lootingLevel, recentlyHit));
|
||||
}
|
||||
|
||||
public static void onLeftClickEmpty(Player player) {
|
||||
post(new PlayerInteractEvent.LeftClickEmpty(player));
|
||||
}
|
||||
@@ -1053,4 +1086,12 @@ public final class ForgeEventFactory {
|
||||
public static AnvilRepairEvent onAnvilRepair(Player player, @NotNull ItemStack output, @NotNull ItemStack left, @NotNull ItemStack right) {
|
||||
return fire(new AnvilRepairEvent(player, left, right, output));
|
||||
}
|
||||
|
||||
public static void onPlayerTradeWithVillager(Player player, MerchantOffer offer, AbstractVillager villager) {
|
||||
post(new TradeWithVillagerEvent(player, offer, villager));
|
||||
}
|
||||
|
||||
public static GatherComponentsEvent.Item gatherItemComponentsEvent(Item item, DataComponentMap dataComponents) {
|
||||
return fire(new GatherComponentsEvent.Item(item, dataComponents));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package net.minecraftforge.event;
|
||||
|
||||
import net.minecraft.core.component.DataComponentMap;
|
||||
import net.minecraft.core.component.DataComponentType;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
Where all the events fore Gathering Components will exist.
|
||||
*/
|
||||
|
||||
public abstract class GatherComponentsEvent extends Event {
|
||||
private final DataComponentMap.Builder components = DataComponentMap.builder();
|
||||
private final DataComponentMap originalComponents;
|
||||
private final Object owner;
|
||||
|
||||
protected GatherComponentsEvent(Object owner, DataComponentMap originalComponents) {
|
||||
this.originalComponents = originalComponents;
|
||||
this.owner = owner;
|
||||
}
|
||||
|
||||
public <T> void register(DataComponentType<T> componentType, @Nullable T value) {
|
||||
components.set(componentType, value);
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public DataComponentMap getDataComponentMap() {
|
||||
return components.build();
|
||||
}
|
||||
|
||||
public DataComponentMap getOriginalComponentMap() {
|
||||
return originalComponents;
|
||||
}
|
||||
|
||||
public Object getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to get additional Components for any {@link net.minecraft.world.item.Item}
|
||||
*
|
||||
* Fired once for every {@link net.minecraft.world.item.Item} instance, only once, Lazily.
|
||||
*
|
||||
* Recursion is not supported. Cant call {@link net.minecraft.world.item.Item#components()} as that would cause a loop.
|
||||
*
|
||||
* References in {@link net.minecraft.world.item.Items} may not be valid at the current time.
|
||||
*/
|
||||
public static class Item extends GatherComponentsEvent {
|
||||
public Item(net.minecraft.world.item.Item item, DataComponentMap dataComponents) {
|
||||
super(item, dataComponents);
|
||||
}
|
||||
|
||||
@Override
|
||||
public net.minecraft.world.item.Item getOwner() {
|
||||
return (net.minecraft.world.item.Item) super.getOwner();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,8 @@ package net.minecraftforge.event.entity.living;
|
||||
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraftforge.common.ForgeHooks;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.event.ForgeEventFactory;
|
||||
import net.minecraftforge.eventbus.api.Cancelable;
|
||||
import net.minecraft.world.damagesource.DamageSource;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
@@ -20,7 +20,7 @@ import net.minecraft.world.entity.LivingEntity;
|
||||
* {@link Player#die(DamageSource)}, and
|
||||
* {@link ServerPlayer#die(DamageSource)}. <br>
|
||||
* <br>
|
||||
* This event is fired via the {@link ForgeHooks#onLivingDeath(LivingEntity, DamageSource)}.<br>
|
||||
* This event is fired via the {@link ForgeEventFactory#onLivingDeath(LivingEntity, DamageSource)}.<br>
|
||||
* <br>
|
||||
* {@link #source} contains the DamageSource that caused the entity to die. <br>
|
||||
* <br>
|
||||
|
||||
@@ -10,8 +10,8 @@ import java.util.Collection;
|
||||
import net.minecraft.world.damagesource.DamageSource;
|
||||
import net.minecraft.world.entity.item.ItemEntity;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraftforge.common.ForgeHooks;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.event.ForgeEventFactory;
|
||||
import net.minecraftforge.eventbus.api.Cancelable;
|
||||
|
||||
/**
|
||||
@@ -19,7 +19,7 @@ import net.minecraftforge.eventbus.api.Cancelable;
|
||||
* This event is fired whenever an Entity dies and drops items in
|
||||
* {@link LivingEntity#die(DamageSource)}.<br>
|
||||
* <br>
|
||||
* This event is fired via the {@link ForgeHooks#onLivingDrops(LivingEntity, DamageSource, Collection, int, boolean)} .<br>
|
||||
* This event is fired via the {@link ForgeEventFactory#onLivingDrops(LivingEntity, DamageSource, Collection, int, boolean)} .<br>
|
||||
* <br>
|
||||
* {@link #source} contains the DamageSource that caused the drop to occur.<br>
|
||||
* {@link #drops} contains the ArrayList of EntityItems that will be dropped.<br>
|
||||
|
||||
@@ -8,6 +8,7 @@ package net.minecraftforge.event.entity.living;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraftforge.common.ForgeHooks;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.event.ForgeEventFactory;
|
||||
import net.minecraftforge.eventbus.api.Cancelable;
|
||||
import net.minecraft.world.entity.LivingEntity;
|
||||
import net.minecraftforge.event.entity.EntityEvent;
|
||||
@@ -40,7 +41,7 @@ public class LivingEvent extends EntityEvent
|
||||
/**
|
||||
* LivingUpdateEvent is fired when a LivingEntity is ticked in {@link LivingEntity#tick()}. <br>
|
||||
* <br>
|
||||
* This event is fired via the {@link ForgeHooks#onLivingTick(LivingEntity)}.<br>
|
||||
* This event is fired via the {@link ForgeEventFactory#onLivingTick(LivingEntity)}.<br>
|
||||
* <br>
|
||||
* This event is {@link Cancelable}.<br>
|
||||
* If this event is canceled, the Entity does not update.<br>
|
||||
|
||||
@@ -15,10 +15,8 @@ import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.network.ServerGamePacketListenerImpl;
|
||||
import net.minecraft.network.Connection;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.util.thread.BlockableEventLoop;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.network.ICustomPacket;
|
||||
import net.minecraftforge.network.NetworkDirection;
|
||||
import net.minecraftforge.network.ForgePayload;
|
||||
import net.minecraftforge.common.util.LogicalSidedProvider;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -28,27 +26,30 @@ import java.util.concurrent.CompletableFuture;
|
||||
// But also expose it as a generic listener event for anyone who cares about it but is outside out channel control system.
|
||||
public class CustomPayloadEvent extends Event {
|
||||
private final ResourceLocation channel;
|
||||
private final FriendlyByteBuf payload;
|
||||
private final Object payload;
|
||||
private final FriendlyByteBuf data;
|
||||
private final Context source;
|
||||
private final int loginIndex;
|
||||
|
||||
public CustomPayloadEvent(ResourceLocation channel, FriendlyByteBuf payload, Context source, int loginIndex) {
|
||||
public CustomPayloadEvent(ResourceLocation channel, Object payload, Context source, int loginIndex) {
|
||||
this.channel = channel;
|
||||
this.payload = payload;
|
||||
this.data = payload instanceof ForgePayload forge ? forge.data() : null;
|
||||
this.source = source;
|
||||
this.loginIndex = loginIndex;
|
||||
}
|
||||
|
||||
public CustomPayloadEvent(ICustomPacket<?> payload, Context source) {
|
||||
this(payload.getName(), payload.getInternalData(), source, payload.getIndex());
|
||||
}
|
||||
|
||||
public ResourceLocation getChannel() {
|
||||
return this.channel;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public FriendlyByteBuf getPayload() {
|
||||
return payload;
|
||||
return data;
|
||||
}
|
||||
|
||||
public Object getPayloadObject() {
|
||||
return this.payload;
|
||||
}
|
||||
|
||||
public int getLoginIndex() {
|
||||
@@ -67,31 +68,23 @@ public class CustomPayloadEvent extends Event {
|
||||
* The {@link Connection} for this message.
|
||||
*/
|
||||
private final Connection connection;
|
||||
|
||||
/**
|
||||
* The {@link NetworkDirection} this message has been received on.
|
||||
*/
|
||||
private final NetworkDirection networkDirection;
|
||||
private final boolean client;
|
||||
|
||||
private boolean packetHandled;
|
||||
|
||||
public Context(Connection connection, NetworkDirection networkDirection) {
|
||||
public Context(Connection connection) {
|
||||
this.connection = connection;
|
||||
this.networkDirection = networkDirection;
|
||||
this.client = connection.getReceiving() == PacketFlow.CLIENTBOUND;
|
||||
}
|
||||
|
||||
public boolean isClientSide() {
|
||||
return getConnection().getReceiving() == PacketFlow.CLIENTBOUND;
|
||||
return client;
|
||||
}
|
||||
|
||||
public boolean isServerSide() {
|
||||
return !isClientSide();
|
||||
}
|
||||
|
||||
public NetworkDirection getDirection() {
|
||||
return networkDirection;
|
||||
}
|
||||
|
||||
public Connection getConnection() {
|
||||
return connection;
|
||||
}
|
||||
@@ -109,7 +102,8 @@ public class CustomPayloadEvent extends Event {
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> enqueueWork(Runnable runnable) {
|
||||
BlockableEventLoop<?> executor = LogicalSidedProvider.WORKQUEUE.get(getDirection().getReceptionSide());
|
||||
var executor = LogicalSidedProvider.WORKQUEUE.get(isClientSide());
|
||||
|
||||
// Must check ourselves as Minecraft will sometimes delay tasks even when they are received on the client thread
|
||||
// Same logic as ThreadTaskExecutor#runImmediately without the join
|
||||
if (!executor.isSameThread())
|
||||
|
||||
@@ -7,9 +7,15 @@ package net.minecraftforge.network;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.logging.log4j.Marker;
|
||||
import org.apache.logging.log4j.MarkerManager;
|
||||
|
||||
import net.minecraft.network.Connection;
|
||||
import net.minecraft.network.ConnectionProtocol;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.PacketFlow;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
@@ -17,6 +23,8 @@ import net.minecraftforge.event.network.CustomPayloadEvent;
|
||||
import net.minecraftforge.network.packets.LoginWrapper;
|
||||
|
||||
public abstract class Channel<MSG> {
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
private static final Marker MARKER = MarkerManager.getMarker("CHANNEL");
|
||||
protected final NetworkInstance instance;
|
||||
|
||||
protected Channel(NetworkInstance instance) {
|
||||
@@ -27,6 +35,15 @@ public abstract class Channel<MSG> {
|
||||
return instance.getChannelName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the channel name to use for the specified packet
|
||||
* This typically is the main channel's name, but in some cases
|
||||
* you want to specify a custom one. If your channel has {@link NetworkInstance#addChild(ResourceLocation) children}
|
||||
*/
|
||||
ResourceLocation getName(MSG packet) {
|
||||
return getName();
|
||||
}
|
||||
|
||||
public int getProtocolVersion() {
|
||||
return instance.getNetworkProtocolVersion();
|
||||
}
|
||||
@@ -35,32 +52,26 @@ public abstract class Channel<MSG> {
|
||||
* Returns true if the channel is present in the given connection.
|
||||
*/
|
||||
public boolean isRemotePresent(Connection connection) {
|
||||
return NetworkContext.get(connection).getRemoteChannels().contains(getName());
|
||||
return instance.isRemotePresent(connection);
|
||||
}
|
||||
|
||||
public abstract FriendlyByteBuf toBuffer(MSG message);
|
||||
public abstract void encode(FriendlyByteBuf out, MSG message);
|
||||
|
||||
// Package private so we can call from ourselves.
|
||||
Packet<?> toVanillaPacket(Connection connection, MSG message) {
|
||||
protected Packet<?> toVanillaPacket(Connection connection, MSG message) {
|
||||
var protocol = connection.getProtocol();
|
||||
var serverbound = connection.getSending() == PacketFlow.SERVERBOUND;
|
||||
var data = toBuffer(message);
|
||||
var handler = switch (protocol) {
|
||||
case LOGIN -> NetworkProtocol.LOGIN;
|
||||
case CONFIGURATION -> NetworkProtocol.CONFIGURATION;
|
||||
case PLAY -> NetworkProtocol.PLAY;
|
||||
default -> throw new IllegalStateException("Unsupported protocol " + protocol.name() + " in Forge Networking Channel");
|
||||
};
|
||||
|
||||
if (protocol == ConnectionProtocol.LOGIN) {
|
||||
// Login Protocol C->S packets do not contain the plugin channel name. As they are meant to be replies.
|
||||
// So fuck it lets just wrap in our own packet that DOES include the channel name
|
||||
if (serverbound) {
|
||||
if (this != NetworkInitialization.LOGIN)
|
||||
return NetworkInitialization.LOGIN.toVanillaPacket(connection, new LoginWrapper(getName(), data));
|
||||
else
|
||||
return NetworkDirection.LOGIN_TO_SERVER.buildPacket(data, getName()).getThis();
|
||||
}
|
||||
return NetworkDirection.LOGIN_TO_CLIENT.buildPacket(data, getName()).getThis();
|
||||
} else if (protocol == ConnectionProtocol.PLAY || protocol == ConnectionProtocol.CONFIGURATION) {
|
||||
var dir = serverbound ? NetworkDirection.PLAY_TO_SERVER : NetworkDirection.PLAY_TO_CLIENT;
|
||||
return dir.buildPacket(data, getName()).getThis();
|
||||
} else
|
||||
throw new IllegalStateException("Unsupported protocol " + protocol.name() + " in Forge Networking Channel");
|
||||
// Login Protocol C->S packets do not contain the plugin channel name. As they are meant to be replies.
|
||||
// So fuck it lets just wrap in our own packet that DOES include the channel name
|
||||
if (protocol == ConnectionProtocol.LOGIN && this != NetworkInitialization.LOGIN && connection.getSending() == PacketFlow.SERVERBOUND)
|
||||
return NetworkInitialization.LOGIN.toVanillaPacket(connection, new LoginWrapper(this, message));
|
||||
|
||||
return handler.buildPacket(connection.getSending(), this, message);
|
||||
}
|
||||
|
||||
public void send(MSG msg, Connection connection) {
|
||||
@@ -79,13 +90,30 @@ public abstract class Channel<MSG> {
|
||||
* @param <MSG> The type of the message
|
||||
*/
|
||||
public void send(MSG msg, PacketDistributor.PacketTarget target) {
|
||||
target.send(target.direction().buildPacket(toBuffer(msg), getName()).getThis());
|
||||
target.send(target.direction().buildPacket(this, msg));
|
||||
}
|
||||
|
||||
public void reply(MSG msg, CustomPayloadEvent.Context context) {
|
||||
send(msg, context.getConnection());
|
||||
}
|
||||
|
||||
protected void validate(Object id, Connection con, NetworkProtocol<?> protocol, PacketFlow direction, boolean sending) {
|
||||
var actualD = sending ? con.getSending() : con.getReceiving();
|
||||
var actualP = con.getProtocol();
|
||||
var expectedP = protocol == null ? actualP : protocol.toVanilla();
|
||||
var expectedD = direction == null ? actualD : direction;
|
||||
|
||||
if (expectedP != actualP || expectedD != actualD) {
|
||||
var error = "Illegal packet " + (sending ? "sent" : "received") + ", terminating connection. " + id + " expected " +
|
||||
expectedD + " " + expectedP + " but was " +
|
||||
actualD + " " + actualP;
|
||||
|
||||
LOGGER.error(MARKER, error);
|
||||
con.disconnect(Component.literal(error));
|
||||
throw new IllegalStateException(error);
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public static interface VersionTest {
|
||||
public static final VersionTest ACCEPT_MISSING = (status, version) -> status == Status.MISSING;
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.network;
|
||||
|
||||
public interface ChannelBuildable<T> {
|
||||
/*
|
||||
* This will build the entire channel, locking any future modifications from happening.
|
||||
* This should be the LAST call in your builder chain and can only be called once.
|
||||
*/
|
||||
Channel<T> build();
|
||||
}
|
||||
@@ -14,8 +14,10 @@ import java.util.function.Supplier;
|
||||
|
||||
import io.netty.util.AttributeKey;
|
||||
import net.minecraft.network.Connection;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraftforge.network.Channel.VersionTest;
|
||||
import net.minecraftforge.network.payload.PayloadConnection;
|
||||
|
||||
/**
|
||||
* Builder for constructing impl channels using a builder style API.
|
||||
@@ -27,6 +29,7 @@ public class ChannelBuilder {
|
||||
private Channel.VersionTest serverAcceptedVersions;
|
||||
private Map<AttributeKey<?>, Function<Connection, ?>> attributes = new HashMap<>();
|
||||
private Consumer<Connection> connectionHandler;
|
||||
private boolean registerSelf = true;
|
||||
|
||||
/**
|
||||
* Creates a new channel builder, The name of the channel must be unique.
|
||||
@@ -171,21 +174,17 @@ public class ChannelBuilder {
|
||||
* @return the {@link NetworkInstance}
|
||||
*/
|
||||
private NetworkInstance createNetworkInstance() {
|
||||
if (NetworkRegistry.lock) {
|
||||
NetworkRegistry.LOGGER.error(NetworkRegistry.NETREGISTRY, "Attempted to register channel {} even though registry phase is over", name);
|
||||
throw new IllegalArgumentException("Registration of impl channels is locked");
|
||||
}
|
||||
if (NetworkRegistry.instances.containsKey(name)) {
|
||||
NetworkRegistry.LOGGER.error(NetworkRegistry.NETREGISTRY, "NetworkDirection channel {} already registered.", name);
|
||||
throw new IllegalArgumentException("NetworkDirection Channel {"+ name +"} already registered");
|
||||
}
|
||||
|
||||
var networkInstance = new NetworkInstance(name, networkProtocolVersion,
|
||||
var instance = new NetworkInstance(name, networkProtocolVersion,
|
||||
getClientAcceptedVersions(), getServerAcceptedVersions(),
|
||||
attributes, connectionHandler
|
||||
);
|
||||
NetworkRegistry.instances.put(name, networkInstance);
|
||||
return networkInstance;
|
||||
|
||||
NetworkRegistry.register(instance);
|
||||
|
||||
if (registerSelf)
|
||||
instance.addChild(name);
|
||||
|
||||
return instance;
|
||||
|
||||
}
|
||||
|
||||
@@ -203,7 +202,7 @@ public class ChannelBuilder {
|
||||
* @return A new {@link SimpleChannel}
|
||||
*/
|
||||
public SimpleChannel simpleChannel() {
|
||||
return new SimpleChannel(createNetworkInstance());
|
||||
return channel(SimpleChannel::new);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -211,6 +210,23 @@ public class ChannelBuilder {
|
||||
* @return A new {@link EventNetworkChannel}
|
||||
*/
|
||||
public EventNetworkChannel eventNetworkChannel() {
|
||||
return new EventNetworkChannel(createNetworkInstance());
|
||||
return channel(EventNetworkChannel::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a new {@link PayloadChannel} with this builder's configuration.
|
||||
* @return A new {@link PayloadConnection PayloadConnection<CustomPacketPayload>}
|
||||
*/
|
||||
public PayloadConnection<CustomPacketPayload> payloadChannel() {
|
||||
this.registerSelf = false;
|
||||
return channel(PayloadChannel::builder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers this channel with the {@link NetworkManager} and calls the supplied Function.
|
||||
* This is meant to allow modders to build their own Channel implementations.
|
||||
*/
|
||||
public <C> C channel(Function<NetworkInstance, C> factory) {
|
||||
return factory.apply(createNetworkInstance());
|
||||
}
|
||||
}
|
||||
@@ -6,10 +6,11 @@
|
||||
package net.minecraftforge.network;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.minecraft.ResourceLocationException;
|
||||
import net.minecraft.network.Connection;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraftforge.event.ForgeEventFactory;
|
||||
import net.minecraftforge.event.network.ChannelRegistrationChangeEvent;
|
||||
@@ -19,44 +20,70 @@ import org.jetbrains.annotations.ApiStatus;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
@ApiStatus.Internal // TODO: Decide what I want to have public, Right now it should just be add/remove channels.
|
||||
public class ChannelListManager {
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
static final EventNetworkChannel REGISTER = ChannelBuilder
|
||||
.named("minecraft:register")
|
||||
.optional()
|
||||
.eventNetworkChannel()
|
||||
.addListener(ChannelListManager::registerListener);
|
||||
static final EventNetworkChannel UNREGISTER = ChannelBuilder
|
||||
.named("minecraft:unregister")
|
||||
.optional()
|
||||
.eventNetworkChannel()
|
||||
.addListener(ChannelListManager::unregisterListener);
|
||||
|
||||
private static void registerListener(CustomPayloadEvent evt) {
|
||||
var ctx = evt.getSource();
|
||||
updateFrom(ctx, evt.getPayload(), ChannelRegistrationChangeEvent.Type.REGISTER);
|
||||
public static final ResourceLocation NAME = new ResourceLocation("forge", "channel_registration");
|
||||
|
||||
static final Channel<CustomPacketPayload> CHANNEL = ChannelBuilder
|
||||
.named(NAME)
|
||||
.optional()
|
||||
.payloadChannel()
|
||||
.any()
|
||||
.bidirectional()
|
||||
.add(Register.TYPE, Register.CODEC, ChannelListManager::register)
|
||||
.add(Unregister.TYPE, Unregister.CODEC, ChannelListManager::unregister)
|
||||
.build();
|
||||
|
||||
private record Register(List<String> channels) implements CustomPacketPayload {
|
||||
private static Type<Register> TYPE = CustomPacketPayload.createType("minecraft:register");
|
||||
private static StreamCodec<FriendlyByteBuf, Register> CODEC = StreamCodec.of(
|
||||
(buf, v) -> encode(buf, v.channels),
|
||||
buf -> new Register(decode(buf))
|
||||
);
|
||||
|
||||
@Override
|
||||
public Type<Register> type() {
|
||||
return TYPE;
|
||||
}
|
||||
}
|
||||
|
||||
private record Unregister(List<String> channels) implements CustomPacketPayload {
|
||||
private static Type<Unregister> TYPE = CustomPacketPayload.createType("minecraft:unregister");
|
||||
private static StreamCodec<FriendlyByteBuf, Unregister> CODEC = StreamCodec.of(
|
||||
(buf, v) -> encode(buf, v.channels),
|
||||
buf -> new Unregister(decode(buf))
|
||||
);
|
||||
|
||||
@Override
|
||||
public Type<Unregister> type() {
|
||||
return TYPE;
|
||||
}
|
||||
}
|
||||
|
||||
private static void register(Register payload, CustomPayloadEvent.Context ctx) {
|
||||
updateFrom(ctx, payload.channels(), ChannelRegistrationChangeEvent.Type.REGISTER);
|
||||
ctx.setPacketHandled(true);
|
||||
// Send the client's channels to the server whenever asked. If we're in the login state then our login wrapper will unwrap for us.
|
||||
if (ctx.isClientSide())
|
||||
addChannels(ctx.getConnection());
|
||||
}
|
||||
|
||||
private static void unregisterListener(CustomPayloadEvent evt) {
|
||||
var ctx = evt.getSource();
|
||||
updateFrom(ctx, evt.getPayload(), ChannelRegistrationChangeEvent.Type.UNREGISTER);
|
||||
private static void unregister(Unregister payload, CustomPayloadEvent.Context ctx) {
|
||||
updateFrom(ctx, payload.channels(), ChannelRegistrationChangeEvent.Type.UNREGISTER);
|
||||
ctx.setPacketHandled(true);
|
||||
}
|
||||
|
||||
public static void addChannels(Connection connection) {
|
||||
addChannels(connection, NetworkRegistry.buildChannelVersions().keySet().stream()
|
||||
.filter(rl -> !"minecraft".equals(rl.getNamespace()))
|
||||
.toList()
|
||||
);
|
||||
addChannels(connection, NetworkRegistry.buildRegisterList());
|
||||
}
|
||||
|
||||
public static void addChannels(Connection connection, ResourceLocation... channels) {
|
||||
@@ -65,12 +92,14 @@ public class ChannelListManager {
|
||||
|
||||
public static void addChannels(Connection connection, Collection<ResourceLocation> channels) {
|
||||
var list = NetworkContext.get(connection);
|
||||
var toSend = new HashSet<ResourceLocation>();
|
||||
var toSend = new HashSet<String>();
|
||||
for (var channel : channels) {
|
||||
if (list.sentChannels.add(channel))
|
||||
toSend.add(channel);
|
||||
toSend.add(channel.toString());
|
||||
}
|
||||
sendChannels(REGISTER, connection, toSend);
|
||||
|
||||
if (!toSend.isEmpty())
|
||||
CHANNEL.send(new Register(toSend.stream().sorted().toList()), connection);
|
||||
}
|
||||
|
||||
public static void removeChannels(Connection connection, ResourceLocation... channels) {
|
||||
@@ -79,31 +108,27 @@ public class ChannelListManager {
|
||||
|
||||
public static void removeChannels(Connection connection, Collection<ResourceLocation> channels) {
|
||||
var list = NetworkContext.get(connection);
|
||||
var toSend = new HashSet<ResourceLocation>();
|
||||
var toSend = new HashSet<String>();
|
||||
for (var channel : channels) {
|
||||
if (list.sentChannels.remove(channel))
|
||||
toSend.add(channel);
|
||||
toSend.add(channel.toString());
|
||||
}
|
||||
sendChannels(UNREGISTER, connection, toSend);
|
||||
|
||||
if (!toSend.isEmpty())
|
||||
CHANNEL.send(new Unregister(toSend.stream().toList()), connection);
|
||||
}
|
||||
|
||||
private static void sendChannels(EventNetworkChannel channel, Connection connection, Collection<ResourceLocation> channels) {
|
||||
if (channels.isEmpty())
|
||||
return;
|
||||
|
||||
var buf = new FriendlyByteBuf(Unpooled.buffer());
|
||||
private static void encode(FriendlyByteBuf buf, List<String> channels) {
|
||||
for (var c : channels) {
|
||||
buf.writeBytes(c.toString().getBytes(StandardCharsets.UTF_8));
|
||||
buf.writeByte(0);
|
||||
}
|
||||
|
||||
channel.send(buf, connection);
|
||||
}
|
||||
|
||||
private static void updateFrom(CustomPayloadEvent.Context source, FriendlyByteBuf buffer, final ChannelRegistrationChangeEvent.Type changeType) {
|
||||
private static List<String> decode(FriendlyByteBuf buffer) {
|
||||
byte[] data = new byte[Math.max(buffer.readableBytes(), 0)];
|
||||
buffer.readBytes(data);
|
||||
var channels = new HashSet<String>();
|
||||
var channels = new ArrayList<String>();
|
||||
|
||||
int last = 0;
|
||||
for (int cur = 0; cur < data.length; cur++) {
|
||||
@@ -117,6 +142,10 @@ public class ChannelListManager {
|
||||
if (last < data.length)
|
||||
channels.add(new String(data, last, data.length - last, StandardCharsets.UTF_8));
|
||||
|
||||
return Collections.unmodifiableList(channels);
|
||||
}
|
||||
|
||||
private static void updateFrom(CustomPayloadEvent.Context source, List<String> channels, final ChannelRegistrationChangeEvent.Type changeType) {
|
||||
var changed = new HashSet<ResourceLocation>();
|
||||
for (var channel : channels) {
|
||||
// It also says nothing about the format of channels so ignore bad channels.
|
||||
@@ -142,7 +171,7 @@ public class ChannelListManager {
|
||||
if (fire) {
|
||||
var target = NetworkRegistry.findTarget(channel);
|
||||
if (target != null)
|
||||
target.registrationChange(changeType == ChannelRegistrationChangeEvent.Type.REGISTER);
|
||||
target.registrationChange(channel, changeType == ChannelRegistrationChangeEvent.Type.REGISTER);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ public class EventNetworkChannel extends Channel<FriendlyByteBuf> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public FriendlyByteBuf toBuffer(FriendlyByteBuf message) {
|
||||
return message;
|
||||
public void encode(FriendlyByteBuf out, FriendlyByteBuf message) {
|
||||
out.writeBytes(message.slice());
|
||||
}
|
||||
}
|
||||
@@ -73,7 +73,7 @@ public class ForgePacketHandler {
|
||||
}
|
||||
|
||||
void handleLoginWrapper(LoginWrapper msg, CustomPayloadEvent.Context ctx) {
|
||||
ForgeHooks.onCustomPayload(new CustomPayloadEvent(msg.channel(), msg.data(), ctx, -1));
|
||||
ForgeHooks.onCustomPayload(new CustomPayloadEvent(msg.name(), ForgePayload.create(msg.name(), msg.data()), ctx, -1));
|
||||
}
|
||||
|
||||
void handleClientAck(Acknowledge msg, CustomPayloadEvent.Context ctx) {
|
||||
@@ -96,7 +96,7 @@ public class ForgePacketHandler {
|
||||
nctx.modList.putAll(list.mods());
|
||||
|
||||
if (ctx.isClientSide()) {
|
||||
NetworkInitialization.PLAY.send(ModVersions.create(), ctx.getConnection());
|
||||
NetworkInitialization.CONFIG.send(ModVersions.create(), ctx.getConnection());
|
||||
/*
|
||||
* Set the modded marker on the channel so we know we got packets
|
||||
* TODO: NETWORKING: Move modded marker to it's own handler? Its here just because this is the first packet sent from the server to the client
|
||||
@@ -121,14 +121,14 @@ public class ForgePacketHandler {
|
||||
nctx.mismatchData = new NetworkMismatchData(invalid.mismatched(), invalid.missing(), invalid.fromServer(), nctx.getModList());
|
||||
|
||||
if (ctx.isServerSide())
|
||||
NetworkInitialization.PLAY.reply(new MismatchData(nctx.mismatchData), ctx);
|
||||
NetworkInitialization.CONFIG.reply(new MismatchData(nctx.mismatchData), ctx);
|
||||
|
||||
ctx.getConnection().disconnect(Component.literal("Connection closed - mismatched mod channel list"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx.isClientSide())
|
||||
NetworkInitialization.PLAY.send(new ChannelVersions(), ctx.getConnection());
|
||||
NetworkInitialization.CONFIG.send(new ChannelVersions(), ctx.getConnection());
|
||||
else
|
||||
nctx.finishTask(ChannelVersionsTask.TYPE);
|
||||
|
||||
@@ -162,7 +162,7 @@ public class ForgePacketHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
NetworkInitialization.PLAY.reply(new Acknowledge(list.token()), ctx);
|
||||
NetworkInitialization.CONFIG.reply(new Acknowledge(list.token()), ctx);
|
||||
|
||||
this.registriesToReceive = new HashSet<>(list.normal());
|
||||
this.registrySnapshots.clear();
|
||||
@@ -187,7 +187,7 @@ public class ForgePacketHandler {
|
||||
if (!continueHandshake)
|
||||
LOGGER.error(MARKER, "Connection closed, not continuing handshake");
|
||||
else
|
||||
NetworkInitialization.PLAY.reply(new Acknowledge(msg.token()), ctx);
|
||||
NetworkInitialization.CONFIG.reply(new Acknowledge(msg.token()), ctx);
|
||||
}
|
||||
|
||||
private boolean handleRegistryLoading(CustomPayloadEvent.Context ctx) {
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.network;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
import net.minecraft.network.protocol.login.custom.CustomQueryAnswerPayload;
|
||||
import net.minecraft.network.protocol.login.custom.CustomQueryPayload;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public record ForgePayload (
|
||||
ResourceLocation id,
|
||||
@Nullable
|
||||
FriendlyByteBuf data,
|
||||
Consumer<FriendlyByteBuf> encoder
|
||||
) implements CustomPacketPayload, CustomQueryPayload, CustomQueryAnswerPayload {
|
||||
public static ForgePayload create(ResourceLocation id, FriendlyByteBuf data) {
|
||||
return new ForgePayload(id, data, buf -> buf.writeBytes(data.slice()));
|
||||
}
|
||||
|
||||
public static ForgePayload create(ResourceLocation id, Consumer<FriendlyByteBuf> encoder) {
|
||||
return new ForgePayload(id, null, encoder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(FriendlyByteBuf buf) {
|
||||
encoder.accept(buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type<? extends CustomPacketPayload> type() {
|
||||
return new Type<>(this.id());
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.network;
|
||||
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
public interface ICustomPacket<T extends Packet<?>> {
|
||||
/**
|
||||
* Returns a unsafe reference to this packet's internal data.
|
||||
* Any modifications to this buffer will be reflected in the main buffer.
|
||||
*/
|
||||
@Nullable
|
||||
FriendlyByteBuf getInternalData();
|
||||
|
||||
ResourceLocation getName();
|
||||
|
||||
int getIndex();
|
||||
|
||||
default NetworkDirection getDirection() {
|
||||
return NetworkDirection.directionFor(this.getClass());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
default T getThis() {
|
||||
return (T)this;
|
||||
}
|
||||
}
|
||||
@@ -5,88 +5,21 @@
|
||||
|
||||
package net.minecraftforge.network;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ReferenceArrayMap;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket;
|
||||
import net.minecraft.network.protocol.common.ServerboundCustomPayloadPacket;
|
||||
import net.minecraft.network.protocol.common.custom.DiscardedPayload;
|
||||
import net.minecraft.network.protocol.PacketFlow;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.protocol.login.ClientboundCustomQueryPacket;
|
||||
import net.minecraft.network.protocol.login.ServerboundCustomQueryAnswerPacket;
|
||||
import net.minecraft.network.protocol.login.custom.DiscardedQueryAnswerPayload;
|
||||
import net.minecraft.network.protocol.login.custom.DiscardedQueryPayload;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraftforge.fml.LogicalSide;
|
||||
import net.minecraft.network.PacketListener;
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public enum NetworkDirection {
|
||||
PLAY_TO_SERVER(LogicalSide.CLIENT, ServerboundCustomPayloadPacket.class, 1, NetworkDirection::playServerbound),
|
||||
PLAY_TO_CLIENT(LogicalSide.SERVER, ClientboundCustomPayloadPacket.class, 0, NetworkDirection::playClientbound),
|
||||
LOGIN_TO_SERVER(LogicalSide.CLIENT, ServerboundCustomQueryAnswerPacket.class, 3, NetworkDirection::loginServerbound),
|
||||
LOGIN_TO_CLIENT(LogicalSide.SERVER, ClientboundCustomQueryPacket.class, 2, NetworkDirection::loginClientbound);
|
||||
public record NetworkDirection<B extends FriendlyByteBuf>(NetworkProtocol<B> protocol, PacketFlow direction) {
|
||||
public static final NetworkDirection<RegistryFriendlyByteBuf> PLAY_TO_CLIENT = new NetworkDirection<>(NetworkProtocol.PLAY, PacketFlow.CLIENTBOUND);
|
||||
public static final NetworkDirection<RegistryFriendlyByteBuf> PLAY_TO_SERVER = new NetworkDirection<>(NetworkProtocol.PLAY, PacketFlow.SERVERBOUND);
|
||||
public static final NetworkDirection<FriendlyByteBuf> LOGIN_TO_CLIENT = new NetworkDirection<>(NetworkProtocol.LOGIN, PacketFlow.CLIENTBOUND);
|
||||
public static final NetworkDirection<FriendlyByteBuf> LOGIN_TO_SERVER = new NetworkDirection<>(NetworkProtocol.LOGIN, PacketFlow.SERVERBOUND);
|
||||
public static final NetworkDirection<FriendlyByteBuf> CONFIGURATION_TO_CLIENT = new NetworkDirection<>(NetworkProtocol.CONFIGURATION, PacketFlow.CLIENTBOUND);
|
||||
public static final NetworkDirection<FriendlyByteBuf> CONFIGURATION_TO_SERVER = new NetworkDirection<>(NetworkProtocol.CONFIGURATION, PacketFlow.SERVERBOUND);
|
||||
|
||||
private final LogicalSide logicalSide;
|
||||
private final Class<? extends Packet> packetClass;
|
||||
private final int otherWay;
|
||||
private final Factory factory;
|
||||
|
||||
private static final Reference2ReferenceArrayMap<Class<? extends Packet>, NetworkDirection> packetLookup = new Reference2ReferenceArrayMap<>();
|
||||
private static final NetworkDirection[] values = values();
|
||||
|
||||
static {
|
||||
for (var value : values)
|
||||
packetLookup.put(value.getPacketClass(), value);
|
||||
}
|
||||
|
||||
private NetworkDirection(LogicalSide logicalSide, Class<? extends Packet> clazz, int i, Factory factory) {
|
||||
this.logicalSide = logicalSide;
|
||||
this.packetClass = clazz;
|
||||
this.otherWay = i;
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
private Class<? extends Packet> getPacketClass() {
|
||||
return packetClass;
|
||||
}
|
||||
|
||||
public static <T extends ICustomPacket<?>> NetworkDirection directionFor(Class<T> customPacket) {
|
||||
return packetLookup.get(customPacket);
|
||||
}
|
||||
|
||||
public NetworkDirection reply() {
|
||||
return values[this.otherWay];
|
||||
}
|
||||
|
||||
public LogicalSide getOriginationSide() {
|
||||
return logicalSide;
|
||||
}
|
||||
|
||||
public LogicalSide getReceptionSide() {
|
||||
return reply().logicalSide;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends Packet<?>> ICustomPacket<T> buildPacket(FriendlyByteBuf data, ResourceLocation channelName) {
|
||||
return this.factory.create(data, 0, channelName);
|
||||
}
|
||||
|
||||
private interface Factory<T extends Packet<?>> {
|
||||
ICustomPacket<T> create(FriendlyByteBuf data, Integer index, ResourceLocation channelName);
|
||||
}
|
||||
|
||||
private static ServerboundCustomPayloadPacket playServerbound(FriendlyByteBuf data, Integer index, ResourceLocation channelName) {
|
||||
return new ServerboundCustomPayloadPacket(new DiscardedPayload(channelName, data));
|
||||
}
|
||||
|
||||
private static ClientboundCustomPayloadPacket playClientbound(FriendlyByteBuf data, Integer index, ResourceLocation channelName) {
|
||||
return new ClientboundCustomPayloadPacket(new DiscardedPayload(channelName, data));
|
||||
}
|
||||
|
||||
private static ServerboundCustomQueryAnswerPacket loginServerbound(FriendlyByteBuf data, Integer index, ResourceLocation channelName) {
|
||||
return new ServerboundCustomQueryAnswerPacket(index, new DiscardedQueryAnswerPayload(data));
|
||||
}
|
||||
|
||||
private static ClientboundCustomQueryPacket loginClientbound(FriendlyByteBuf data, Integer index, ResourceLocation channelName) {
|
||||
return new ClientboundCustomQueryPacket(index, new DiscardedQueryPayload(channelName, data));
|
||||
public <T extends PacketListener, MSG> Packet<T> buildPacket(Channel<MSG> channel, MSG packet) {
|
||||
return this.protocol().buildPacket(this.direction(), channel, packet);
|
||||
}
|
||||
}
|
||||
@@ -5,113 +5,79 @@
|
||||
|
||||
package net.minecraftforge.network;
|
||||
|
||||
import io.netty.util.AttributeKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraftforge.event.network.CustomPayloadEvent;
|
||||
import net.minecraftforge.network.packets.*;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.logging.log4j.Marker;
|
||||
import org.apache.logging.log4j.MarkerManager;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
|
||||
import io.netty.util.AttributeKey;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraftforge.network.packets.Acknowledge;
|
||||
import net.minecraftforge.network.packets.ChannelVersions;
|
||||
import net.minecraftforge.network.packets.LoginWrapper;
|
||||
import net.minecraftforge.network.packets.OpenContainer;
|
||||
import net.minecraftforge.network.packets.RegistryList;
|
||||
import net.minecraftforge.network.packets.RegistryData;
|
||||
import net.minecraftforge.network.packets.ConfigData;
|
||||
import net.minecraftforge.network.packets.MismatchData;
|
||||
import net.minecraftforge.network.packets.ModVersions;
|
||||
import net.minecraftforge.network.packets.SpawnEntity;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public class NetworkInitialization {
|
||||
public final class NetworkInitialization {
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
private static final Marker MARKER = MarkerManager.getMarker("FORGE_NETWORK");
|
||||
public static final ResourceLocation LOGIN_NAME = new ResourceLocation("forge", "login");
|
||||
public static final ResourceLocation HANDSHAKE_NAME = new ResourceLocation("forge", "handshake");
|
||||
public static final ResourceLocation PLAY_NAME = new ResourceLocation("forge", "play");
|
||||
private static final ResourceLocation HANDSHAKE_NAME = new ResourceLocation("forge", "handshake");
|
||||
//private static final ResourceLocation PLAY_NAME = new ResourceLocation("forge", "network");
|
||||
public static final AttributeKey<ForgePacketHandler> CONTEXT = AttributeKey.newInstance(HANDSHAKE_NAME.toString());
|
||||
|
||||
public static SimpleChannel LOGIN = ChannelBuilder
|
||||
public static final SimpleChannel LOGIN = ChannelBuilder
|
||||
.named(LOGIN_NAME)
|
||||
.optional()
|
||||
.networkProtocolVersion(0)
|
||||
.attribute(CONTEXT, ForgePacketHandler::new) // Shared across all of our channels
|
||||
.simpleChannel()
|
||||
.login()
|
||||
.serverbound()
|
||||
.add(LoginWrapper.class, LoginWrapper.STREAM_CODEC, ctx(ForgePacketHandler::handleLoginWrapper))
|
||||
.build();
|
||||
|
||||
.messageBuilder(LoginWrapper.class)
|
||||
.decoder(LoginWrapper::decode)
|
||||
.encoder(LoginWrapper::encode)
|
||||
.consumerNetworkThread(CONTEXT, ForgePacketHandler::handleLoginWrapper)
|
||||
.add();
|
||||
|
||||
public static SimpleChannel PLAY = ChannelBuilder
|
||||
public static final SimpleChannel CONFIG = ChannelBuilder
|
||||
.named(HANDSHAKE_NAME)
|
||||
.optional()
|
||||
.networkProtocolVersion(0)
|
||||
.attribute(CONTEXT, ForgePacketHandler::new)
|
||||
.simpleChannel()
|
||||
.configuration()
|
||||
.serverbound()
|
||||
.add(Acknowledge.class, Acknowledge.STREAM_CODEC, ctx(ForgePacketHandler::handleClientAck))
|
||||
.bidirectional()
|
||||
.add(ModVersions.class, ModVersions.STREAM_CODEC, ctx(ForgePacketHandler::handleModVersions))
|
||||
.add(ChannelVersions.class, ChannelVersions.STREAM_CODEC, ctx(ForgePacketHandler::handleChannelVersions))
|
||||
.clientbound()
|
||||
.add(RegistryList.class, RegistryList.STREAM_CODEC, ctx(ForgePacketHandler::handleRegistryList))
|
||||
.add(RegistryData.class, RegistryData.STREAM_CODEC, ctx(ForgePacketHandler::handleRegistryData))
|
||||
.add(ConfigData.class, ConfigData.STREAM_CODEC, ctx(ForgePacketHandler::handleConfigSync))
|
||||
.add(MismatchData.class, MismatchData.STREAM_CODEC, ctx(ForgePacketHandler::handleModMismatchData))
|
||||
.play() // TODO: Move to it's own channel, so that we can keep the core handshake channel clean/simple and thus not need to bump the version ever As it is the one responsible for validating versions
|
||||
.clientbound()
|
||||
.addMain(SpawnEntity.class, SpawnEntity.STREAM_CODEC, SpawnEntity::handle)
|
||||
.addMain(OpenContainer.class, OpenContainer.STREAM_CODEC, OpenContainer::handle)
|
||||
.build();
|
||||
|
||||
.messageBuilder(Acknowledge.class, NetworkDirection.PLAY_TO_SERVER)
|
||||
.decoder(Acknowledge::decode)
|
||||
.encoder(Acknowledge::encode)
|
||||
.consumerNetworkThread(CONTEXT, ForgePacketHandler::handleClientAck)
|
||||
.add()
|
||||
|
||||
.messageBuilder(ModVersions.class)
|
||||
.decoder(ModVersions::decode)
|
||||
.encoder(ModVersions::encode)
|
||||
.consumerNetworkThread(CONTEXT, ForgePacketHandler::handleModVersions)
|
||||
.add()
|
||||
|
||||
.messageBuilder(ChannelVersions.class)
|
||||
.decoder(ChannelVersions::decode)
|
||||
.encoder(ChannelVersions::encode)
|
||||
.consumerNetworkThread(CONTEXT, ForgePacketHandler::handleChannelVersions)
|
||||
.add()
|
||||
|
||||
.messageBuilder(RegistryList.class, NetworkDirection.PLAY_TO_CLIENT)
|
||||
.decoder(RegistryList::decode)
|
||||
.encoder(RegistryList::encode)
|
||||
.consumerNetworkThread(CONTEXT, ForgePacketHandler::handleRegistryList)
|
||||
.add()
|
||||
|
||||
.messageBuilder(RegistryData.class, NetworkDirection.PLAY_TO_CLIENT)
|
||||
.decoder(RegistryData::decode)
|
||||
.encoder(RegistryData::encode)
|
||||
.consumerNetworkThread(CONTEXT, ForgePacketHandler::handleRegistryData)
|
||||
.add()
|
||||
|
||||
.messageBuilder(ConfigData.class, NetworkDirection.PLAY_TO_CLIENT)
|
||||
.decoder(ConfigData::decode)
|
||||
.encoder(ConfigData::encode)
|
||||
.consumerNetworkThread(CONTEXT, ForgePacketHandler::handleConfigSync)
|
||||
.add()
|
||||
|
||||
.messageBuilder(MismatchData.class, NetworkDirection.PLAY_TO_CLIENT)
|
||||
.decoder(MismatchData::decode)
|
||||
.encoder(MismatchData::encode)
|
||||
.consumerNetworkThread(CONTEXT, ForgePacketHandler::handleModMismatchData)
|
||||
.add()
|
||||
|
||||
.messageBuilder(SpawnEntity.class, NetworkDirection.PLAY_TO_CLIENT)
|
||||
.decoder(SpawnEntity::decode)
|
||||
.encoder(SpawnEntity::encode)
|
||||
.consumerMainThread(SpawnEntity::handle)
|
||||
.add()
|
||||
|
||||
.messageBuilder(OpenContainer.class)
|
||||
.decoder(OpenContainer::decode)
|
||||
.encoder(OpenContainer::encode)
|
||||
.consumerMainThread(OpenContainer::handle)
|
||||
.add();
|
||||
public static final SimpleChannel PLAY = CONFIG;;
|
||||
|
||||
public static void init() {
|
||||
for (var channel : new Channel[]{ LOGIN, PLAY, ChannelListManager.REGISTER, ChannelListManager.UNREGISTER})
|
||||
for (var channel : new Channel[]{ LOGIN, CONFIG, PLAY, ChannelListManager.CHANNEL})
|
||||
LOGGER.debug(MARKER, "Registering Network {} v{}", channel.getName(), channel.getProtocolVersion());
|
||||
}
|
||||
|
||||
public static int getVersion() {
|
||||
return PLAY.getProtocolVersion();
|
||||
return CONFIG.getProtocolVersion();
|
||||
}
|
||||
|
||||
private interface Handler<MSG> {
|
||||
void handle(ForgePacketHandler handler, MSG msg, CustomPayloadEvent.Context ctx);
|
||||
}
|
||||
|
||||
private static <MSG> BiConsumer<MSG, CustomPayloadEvent.Context> ctx(Handler<MSG> handler) {
|
||||
return (msg, ctx) -> {
|
||||
var inst = ctx.getConnection().channel().attr(CONTEXT).get();
|
||||
handler.handle(inst, msg, ctx);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,9 @@ import net.minecraftforge.eventbus.api.IEventBus;
|
||||
import net.minecraftforge.eventbus.api.IEventListener;
|
||||
import net.minecraftforge.network.Channel.VersionTest;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
@@ -27,8 +29,10 @@ import io.netty.util.AttributeKey;
|
||||
* as the internal API and {@link Channel} as the public.
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
public class NetworkInstance {
|
||||
private final IEventBus networkEventBus; // TODO: Evaluate why we even use event bus for this...
|
||||
public final class NetworkInstance {
|
||||
// We use an event bus here so that we don't have to have a handle(event) public function on Channel.
|
||||
// Should this be changed so that modders can fire other channel's handlers?
|
||||
private final IEventBus networkEventBus;
|
||||
private final ResourceLocation channelName;
|
||||
private final int networkProtocolVersion;
|
||||
final VersionTest clientAcceptedVersions;
|
||||
@@ -36,6 +40,7 @@ public class NetworkInstance {
|
||||
final Map<AttributeKey<?>, Function<Connection, ?>> attributes;
|
||||
final Consumer<Connection> channelHandler;
|
||||
final ServerStatusPing.ChannelData pingData;
|
||||
private final Set<ResourceLocation> ids = new HashSet<>();
|
||||
|
||||
NetworkInstance(ResourceLocation channelName, int networkProtocolVersion,
|
||||
VersionTest clientAcceptedVersions, VersionTest serverAcceptedVersions,
|
||||
@@ -71,6 +76,16 @@ public class NetworkInstance {
|
||||
return event.getSource().getPacketHandled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers another name that will have its CustomPayloadEvents redirected to this channel.
|
||||
* Like the main name, this must be unique across all channels.
|
||||
*/
|
||||
public NetworkInstance addChild(ResourceLocation name) {
|
||||
NetworkRegistry.register(this, name);
|
||||
this.ids.add(name);
|
||||
return this;
|
||||
}
|
||||
|
||||
ResourceLocation getChannelName() {
|
||||
return channelName;
|
||||
}
|
||||
@@ -79,7 +94,12 @@ public class NetworkInstance {
|
||||
return networkProtocolVersion;
|
||||
}
|
||||
|
||||
void registrationChange(boolean registered) {
|
||||
void registrationChange(ResourceLocation name, boolean registered) {
|
||||
// TODO: Expose to listeners?
|
||||
}
|
||||
|
||||
boolean isRemotePresent(Connection con) {
|
||||
var channels = NetworkContext.get(con).getRemoteChannels();
|
||||
return channels.containsAll(ids);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.network;
|
||||
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.PacketFlow;
|
||||
import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket;
|
||||
import net.minecraft.network.protocol.common.ServerboundCustomPayloadPacket;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import net.minecraft.network.ConnectionProtocol;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.PacketListener;
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf;
|
||||
import net.minecraft.network.protocol.login.ClientboundCustomQueryPacket;
|
||||
import net.minecraft.network.protocol.login.ServerboundCustomQueryAnswerPacket;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
public class NetworkProtocol<B extends FriendlyByteBuf> {
|
||||
public static final NetworkProtocol<RegistryFriendlyByteBuf> PLAY = new NetworkProtocol<>(ConnectionProtocol.PLAY);
|
||||
public static final NetworkProtocol<FriendlyByteBuf> LOGIN = new NetworkProtocol<>(ConnectionProtocol.LOGIN);
|
||||
public static final NetworkProtocol<FriendlyByteBuf> CONFIGURATION = new NetworkProtocol<>(ConnectionProtocol.CONFIGURATION);
|
||||
|
||||
private final ConnectionProtocol protocol;
|
||||
|
||||
private NetworkProtocol(ConnectionProtocol protocol) {
|
||||
this.protocol = protocol;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.protocol.name();
|
||||
}
|
||||
|
||||
public ConnectionProtocol toVanilla() {
|
||||
return this.protocol;
|
||||
}
|
||||
|
||||
public <T extends PacketListener, MSG> Packet<T> buildPacket(PacketFlow direction, Channel<MSG> channel, MSG packet) {
|
||||
return buildPacket(direction, channel.getName(packet), buf -> channel.encode(buf, packet));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T extends PacketListener> Packet<T> buildPacket(PacketFlow direction, ResourceLocation name, Consumer<B> encoder) {
|
||||
var payload = ForgePayload.create(name, (Consumer<FriendlyByteBuf>)encoder);
|
||||
|
||||
switch (this.protocol) {
|
||||
case PLAY, CONFIGURATION:
|
||||
if (direction == PacketFlow.CLIENTBOUND)
|
||||
return (Packet<T>)new ClientboundCustomPayloadPacket(payload);
|
||||
else
|
||||
return (Packet<T>)new ServerboundCustomPayloadPacket(payload);
|
||||
|
||||
case LOGIN:
|
||||
if (direction == PacketFlow.CLIENTBOUND)
|
||||
return (Packet<T>)new ClientboundCustomQueryPacket(0, payload);
|
||||
else
|
||||
return (Packet<T>)new ServerboundCustomQueryAnswerPacket(0, payload);
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid protocol, shouldn't be possible as this is a private class..");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,25 +41,28 @@ public class NetworkRegistry {
|
||||
static final Logger LOGGER = LogManager.getLogger();
|
||||
static final Marker NETREGISTRY = MarkerManager.getMarker("NETREGISTRY");
|
||||
|
||||
static Map<ResourceLocation, NetworkInstance> instances = Collections.synchronizedMap(new HashMap<>());
|
||||
private static Map<ResourceLocation, NetworkInstance> instances = Collections.synchronizedMap(new HashMap<>());
|
||||
private static Map<ResourceLocation, NetworkInstance> byName = Collections.synchronizedMap(new HashMap<>());
|
||||
|
||||
public static boolean acceptsVanillaClientConnections() {
|
||||
return (instances.isEmpty() || listRejectedVanillaMods(n -> n.clientAcceptedVersions).isEmpty()) && DataPackRegistriesHooks.getSyncedCustomRegistries().isEmpty();
|
||||
return listRejectedVanillaMods(n -> n.clientAcceptedVersions).isEmpty() && DataPackRegistriesHooks.getSyncedCustomRegistries().isEmpty();
|
||||
}
|
||||
|
||||
public static boolean canConnectToVanillaServer() {
|
||||
return instances.isEmpty() || listRejectedVanillaMods(n -> n.serverAcceptedVersions).isEmpty();
|
||||
return listRejectedVanillaMods(n -> n.serverAcceptedVersions).isEmpty();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static NetworkInstance findTarget(ResourceLocation resourceLocation) {
|
||||
return instances.get(resourceLocation);
|
||||
return byName.get(resourceLocation);
|
||||
}
|
||||
|
||||
static Map<ResourceLocation, ServerStatusPing.ChannelData> buildChannelVersionsForListPing() {
|
||||
return instances.entrySet().stream()
|
||||
.filter(p -> !p.getKey().getNamespace().equals("forge"))
|
||||
.collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue().pingData));
|
||||
var ret = new HashMap<ResourceLocation, ServerStatusPing.ChannelData>();
|
||||
for (var channel : instances.values()) {
|
||||
ret.put(channel.getChannelName(), channel.pingData);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static List<String> listRejectedVanillaMods(Function<NetworkInstance, VersionTest> testFunction) {
|
||||
@@ -86,7 +89,8 @@ public class NetworkRegistry {
|
||||
|
||||
Set<ResourceLocation> missing = new HashSet<>();
|
||||
Map<ResourceLocation, NetworkMismatchData.Version> results = new HashMap<>();
|
||||
instances.forEach((name, net) -> {
|
||||
for (var net : instances.values()) {
|
||||
var name = net.getChannelName();
|
||||
VersionTest test = fromClient ? net.clientAcceptedVersions : net.serverAcceptedVersions;
|
||||
|
||||
var status = VersionTest.Status.MISSING;
|
||||
@@ -105,7 +109,7 @@ public class NetworkRegistry {
|
||||
else
|
||||
results.put(name, new NetworkMismatchData.Version(Integer.toString(version), Integer.toString(net.getNetworkProtocolVersion())));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!results.isEmpty() || !missing.isEmpty()) {
|
||||
LOGGER.error(NETREGISTRY, "Channels [{}] rejected their {} side version number",
|
||||
@@ -176,7 +180,45 @@ public class NetworkRegistry {
|
||||
|
||||
public static Map<ResourceLocation, Integer> buildChannelVersions() {
|
||||
var ret = new Object2IntOpenHashMap<ResourceLocation>(instances.size());
|
||||
instances.forEach((k, v) -> ret.put(k, v.getNetworkProtocolVersion()));
|
||||
for (var net : instances.values()) {
|
||||
ret.put(net.getChannelName(), net.getNetworkProtocolVersion());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static List<ResourceLocation> buildRegisterList() {
|
||||
var ret = new ArrayList<ResourceLocation>(byName.keySet().size());
|
||||
for (var name : byName.keySet())
|
||||
if (!"minecraft".equals(name.getNamespace()))
|
||||
ret.add(name);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void register(NetworkInstance instance, ResourceLocation name) {
|
||||
checkLock(instance);
|
||||
if (NetworkRegistry.byName.containsKey(name))
|
||||
error("Payload name " + name + " already registered.");
|
||||
|
||||
byName.put(name, instance);
|
||||
}
|
||||
|
||||
static void register(NetworkInstance instance) {
|
||||
checkLock(instance);
|
||||
|
||||
var name = instance.getChannelName();
|
||||
if (NetworkRegistry.instances.containsKey(name))
|
||||
error("Channel " + name + " already registered.");
|
||||
|
||||
instances.put(name, instance);
|
||||
}
|
||||
|
||||
private static void checkLock(NetworkInstance instance) {
|
||||
if (NetworkRegistry.lock)
|
||||
error("Attempted to register channel " + instance.getChannelName() + " even though registry phase is over");
|
||||
}
|
||||
|
||||
private static void error(String message) {
|
||||
NetworkRegistry.LOGGER.error(NetworkRegistry.NETREGISTRY, message);
|
||||
throw new IllegalArgumentException(message);
|
||||
}
|
||||
}
|
||||
@@ -29,55 +29,63 @@ import org.jetbrains.annotations.Nullable;
|
||||
*
|
||||
* @param <T>
|
||||
*/
|
||||
public record PacketDistributor<T>(BiFunction<PacketDistributor<T>, T, Consumer<Packet<?>>> functor, NetworkDirection direction) {
|
||||
public record PacketDistributor<T>(BiFunction<PacketDistributor<T>, T, Consumer<Packet<?>>> functor, NetworkDirection<?> direction) {
|
||||
/**
|
||||
* Send to the player specified in the Supplier
|
||||
* <br/>
|
||||
* {@link #with(T)} Player
|
||||
*/
|
||||
public static final PacketDistributor<ServerPlayer> PLAYER = new PacketDistributor<>(PacketDistributor::playerConsumer);
|
||||
|
||||
/**
|
||||
* Send to everyone in the dimension specified in the Supplier
|
||||
* <br/>
|
||||
* {@link #with(T)} DimensionType
|
||||
*/
|
||||
public static final PacketDistributor<ResourceKey<Level>> DIMENSION = new PacketDistributor<>(PacketDistributor::playerListDimConsumer);
|
||||
|
||||
/**
|
||||
* Send to everyone near the {@link TargetPoint} specified in the Supplier
|
||||
* <br/>
|
||||
* {@link #with(T)} TargetPoint
|
||||
*/
|
||||
public static final PacketDistributor<TargetPoint> NEAR = new PacketDistributor<>(PacketDistributor::playerListPointConsumer);
|
||||
|
||||
/**
|
||||
* Send to everyone
|
||||
* <br/>
|
||||
* {@link #noArg()}
|
||||
*/
|
||||
public static final PacketDistributor<Void> ALL = new PacketDistributor<>(PacketDistributor::playerListAll);
|
||||
|
||||
/**
|
||||
* Send to the server (CLIENT to SERVER)
|
||||
* <br/>
|
||||
* {@link #noArg()}
|
||||
*/
|
||||
public static final PacketDistributor<Void> SERVER = new PacketDistributor<>(PacketDistributor::clientToServer, NetworkDirection.PLAY_TO_SERVER);
|
||||
|
||||
/**
|
||||
* Send to all tracking the Entity in the Supplier
|
||||
* <br/>
|
||||
* {@link #with(T)} Entity
|
||||
*/
|
||||
public static final PacketDistributor<Entity> TRACKING_ENTITY = new PacketDistributor<>(PacketDistributor::trackingEntity);
|
||||
|
||||
/**
|
||||
* Send to all tracking the Entity and Player in the Supplier
|
||||
* <br/>
|
||||
* {@link #with(T)} Entity
|
||||
*/
|
||||
public static final PacketDistributor<Entity> TRACKING_ENTITY_AND_SELF = new PacketDistributor<>(PacketDistributor::trackingEntityAndSelf);
|
||||
|
||||
/**
|
||||
* Send to all tracking the Chunk in the Supplier
|
||||
* <br/>
|
||||
* {@link #with(T)} Chunk
|
||||
*/
|
||||
public static final PacketDistributor<LevelChunk> TRACKING_CHUNK = new PacketDistributor<>(PacketDistributor::trackingChunk);
|
||||
|
||||
/**
|
||||
* Send to the supplied list of NetworkManager instances in the Supplier
|
||||
* <br/>
|
||||
@@ -99,7 +107,7 @@ public record PacketDistributor<T>(BiFunction<PacketDistributor<T>, T, Consumer<
|
||||
*
|
||||
* @see Channel#send(Object, PacketTarget)
|
||||
*/
|
||||
public record PacketTarget(Consumer<Packet<?>> packetConsumer, NetworkDirection direction) {
|
||||
public record PacketTarget(Consumer<Packet<?>> packetConsumer, NetworkDirection<?> direction) {
|
||||
public void send(Packet<?> packet) {
|
||||
packetConsumer.accept(packet);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
/*
|
||||
* Copyright (c) Forge Development LLC and contributors
|
||||
* SPDX-License-Identifier: LGPL-2.1-only
|
||||
*/
|
||||
|
||||
package net.minecraftforge.network;
|
||||
|
||||
import io.netty.util.AttributeKey;
|
||||
import net.minecraft.network.Connection;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.PacketFlow;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
|
||||
import net.minecraft.network.protocol.common.custom.CustomPacketPayload.Type;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraftforge.event.network.CustomPayloadEvent;
|
||||
import net.minecraftforge.event.network.CustomPayloadEvent.Context;
|
||||
import net.minecraftforge.network.simple.handler.SimplePacket;
|
||||
import net.minecraftforge.network.payload.PayloadConnection;
|
||||
import net.minecraftforge.network.payload.PayloadFlow;
|
||||
import net.minecraftforge.network.payload.PayloadProtocol;
|
||||
import net.minecraftforge.network.payload.handler.PayloadHandlerFlow;
|
||||
import net.minecraftforge.network.payload.handler.PayloadHandlerProtocol;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.logging.log4j.Marker;
|
||||
import org.apache.logging.log4j.MarkerManager;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
final class PayloadChannel extends Channel<CustomPacketPayload> {
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
private static final Marker MARKER = MarkerManager.getMarker("PAYLOAD_CHANNEL");
|
||||
private final Map<ResourceLocation, Message<?, ?>> payloads;
|
||||
|
||||
@ApiStatus.Internal
|
||||
private PayloadChannel(Builder builder) {
|
||||
super(builder.instance);
|
||||
builder.instance.addListener(this::onPacketReceived);
|
||||
this.payloads = Collections.unmodifiableMap(builder.payloads);
|
||||
}
|
||||
|
||||
static PayloadConnection<CustomPacketPayload> builder(NetworkInstance instance) {
|
||||
return new Builder(instance);
|
||||
}
|
||||
|
||||
private final static class Builder implements PayloadConnection<CustomPacketPayload> {
|
||||
private final NetworkInstance instance;
|
||||
private boolean built = false;
|
||||
private final Map<ResourceLocation, Message<?, ?>> payloads = new HashMap<>();
|
||||
|
||||
private Builder(NetworkInstance instance) {
|
||||
this.instance = instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <NEWBUF extends FriendlyByteBuf, NEWBASE extends CustomPacketPayload> PayloadProtocol<NEWBUF, NEWBASE> protocol(NetworkProtocol<NEWBUF> protocol) {
|
||||
return new Protocol<>(new BuilderContext<NEWBUF, NEWBASE>(this).protocol(protocol));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <NEWBUF extends FriendlyByteBuf, CTX, NEWBASE extends CustomPacketPayload & SimplePacket<CTX>> PayloadHandlerProtocol<NEWBUF, NEWBASE> protocol(AttributeKey<CTX> context, NetworkProtocol<NEWBUF> protocol) {
|
||||
return new HandlerProtocol<>(context, new BuilderContext<NEWBUF, NEWBASE>(this).protocol(protocol));
|
||||
}
|
||||
|
||||
private void add(Message<?, ?> msg) {
|
||||
if (this.payloads.put(msg.type().id(), msg) != null)
|
||||
throw new IllegalArgumentException("Packet with type " + msg.type().id() + " already registered");
|
||||
|
||||
this.instance.addChild(msg.type().id());
|
||||
}
|
||||
|
||||
private void checkBuilt() {
|
||||
if (built)
|
||||
throw new IllegalStateException("PayloadChannel builder is fully built, can not modify it any more");
|
||||
}
|
||||
|
||||
public PayloadChannel build() {
|
||||
checkBuilt();
|
||||
return new PayloadChannel(this);
|
||||
}
|
||||
|
||||
private record BuilderContext<BUF extends FriendlyByteBuf, BASE>(Builder builder, @Nullable NetworkProtocol<BUF> protocol, @Nullable PacketFlow flow) {
|
||||
public BuilderContext(Builder builder) {
|
||||
this(builder, null, null);
|
||||
}
|
||||
|
||||
public <NEWBUF extends FriendlyByteBuf> BuilderContext<NEWBUF, BASE> protocol(NetworkProtocol<NEWBUF> protocol) {
|
||||
return new BuilderContext<>(builder, protocol, flow);
|
||||
}
|
||||
|
||||
private BuilderContext<BUF, BASE> flow(@Nullable PacketFlow flow) {
|
||||
return new BuilderContext<>(builder, protocol, flow);
|
||||
}
|
||||
}
|
||||
private record Protocol<BUF extends FriendlyByteBuf, BASE extends CustomPacketPayload>(BuilderContext<BUF, BASE> ctx) implements ProtocolFactory<BUF, BASE>, PayloadProtocol<BUF, BASE> {
|
||||
@Override
|
||||
public PayloadFlow<BUF, BASE> flow(@Nullable PacketFlow flow) {
|
||||
return new Flow<>(ctx.flow(flow));
|
||||
}
|
||||
}
|
||||
|
||||
private record HandlerProtocol<BUF extends FriendlyByteBuf, CTX, BASE extends CustomPacketPayload & SimplePacket<CTX>>(
|
||||
AttributeKey<CTX> key,
|
||||
BuilderContext<BUF, BASE> ctx
|
||||
) implements ProtocolFactory<BUF, BASE>, PayloadHandlerProtocol<BUF, BASE> {
|
||||
@Override
|
||||
public PayloadHandlerFlow<BUF, BASE> flow(@Nullable PacketFlow flow) {
|
||||
return new HandlerFlow<>(key, ctx.flow(flow));
|
||||
}
|
||||
}
|
||||
|
||||
private record Flow<BUF extends FriendlyByteBuf, BASE extends CustomPacketPayload>(BuilderContext<BUF, BASE> ctx) implements ProtocolFactory<BUF, BASE>, PayloadFlow<BUF, BASE> {
|
||||
@Override
|
||||
public <MSG extends BASE> Flow<BUF, BASE> add(Type<MSG> type, StreamCodec<BUF, MSG> codec, BiConsumer<MSG, Context> consumer) {
|
||||
ctx.builder().add(new Message<>(type, ctx.protocol(), ctx.flow(), codec, consumer));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayloadFlow<BUF, BASE> flow(@Nullable PacketFlow flow) {
|
||||
return new Flow<>(ctx.flow(flow));
|
||||
}
|
||||
}
|
||||
|
||||
private record HandlerFlow<BUF extends FriendlyByteBuf, CTX, BASE extends CustomPacketPayload & SimplePacket<CTX>>(
|
||||
AttributeKey<CTX> key,
|
||||
BuilderContext<BUF, BASE> ctx
|
||||
) implements ProtocolFactory<BUF, BASE>, PayloadHandlerFlow<BUF, BASE> {
|
||||
@Override
|
||||
public <MSG extends BASE> PayloadHandlerFlow<BUF, BASE> add(Type<MSG> type, StreamCodec<BUF, MSG> codec) {
|
||||
return this.add(type, codec, (msg, ctx) -> {
|
||||
var inst = ctx.getConnection().channel().attr(key).get();
|
||||
ctx.setPacketHandled(msg.handle(inst, ctx));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public <MSG extends BASE> PayloadHandlerFlow<BUF, BASE> addMain(Type<MSG> type, StreamCodec<BUF, MSG> codec) {
|
||||
return this.add(type, codec, (msg, ctx) -> {
|
||||
ctx.enqueueWork(() -> {
|
||||
var inst = ctx.getConnection().channel().attr(key).get();
|
||||
msg.handle(inst, ctx);
|
||||
});
|
||||
ctx.setPacketHandled(true);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public <MSG extends BASE> PayloadHandlerFlow<BUF, BASE> add(Type<MSG> type, StreamCodec<BUF, MSG> codec, BiConsumer<MSG, Context> consumer) {
|
||||
ctx.builder().add(new Message<>(type, ctx.protocol(), ctx.flow(), codec, consumer));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayloadHandlerFlow<BUF, BASE> flow(@Nullable PacketFlow flow) {
|
||||
return new HandlerFlow<>(key, ctx.flow(flow));
|
||||
}
|
||||
}
|
||||
|
||||
private interface ProtocolFactory<BUF extends FriendlyByteBuf, BASE extends CustomPacketPayload> extends PayloadConnection<BASE>, ChannelBuildable<CustomPacketPayload> {
|
||||
BuilderContext<BUF, BASE> ctx();
|
||||
|
||||
@Override
|
||||
default <NEWBUF extends FriendlyByteBuf, NEWBASE extends CustomPacketPayload> PayloadProtocol<NEWBUF, NEWBASE> protocol(NetworkProtocol<NEWBUF> protocol) {
|
||||
return ctx().builder.protocol(protocol);
|
||||
}
|
||||
|
||||
@Override
|
||||
default <NEWBUF extends FriendlyByteBuf, CTX, NEWBASE extends SimplePacket<CTX> & CustomPacketPayload> PayloadHandlerProtocol<NEWBUF, NEWBASE> protocol(AttributeKey<CTX> context, NetworkProtocol<NEWBUF> protocol) {
|
||||
return ctx().builder.protocol(context, protocol);
|
||||
}
|
||||
|
||||
@Override
|
||||
default Channel<CustomPacketPayload> build() {
|
||||
return ctx().builder.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private record Message<MSG extends CustomPacketPayload, BUF extends FriendlyByteBuf>(
|
||||
Type<MSG> type,
|
||||
@Nullable
|
||||
NetworkProtocol<BUF> protocol,
|
||||
@Nullable
|
||||
PacketFlow direction,
|
||||
StreamCodec<BUF, MSG> codec,
|
||||
BiConsumer<MSG, CustomPayloadEvent.Context> consumer
|
||||
){ };
|
||||
|
||||
private Message<CustomPacketPayload, FriendlyByteBuf> get(ResourceLocation type, boolean send) {
|
||||
@SuppressWarnings("unchecked")
|
||||
var msg = (Message<CustomPacketPayload, FriendlyByteBuf>)payloads.get(type);
|
||||
|
||||
if (msg == null)
|
||||
error((send ? "Attemped to send" : "Received") + " invalid message " + type + " on channel " + getName());
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
private void error(String message) {
|
||||
LOGGER.error(MARKER, message);
|
||||
throw new IllegalArgumentException(message);
|
||||
}
|
||||
|
||||
private void onPacketReceived(CustomPayloadEvent event) {
|
||||
var data = event.getPayload();
|
||||
if (data == null)
|
||||
error("Received empty payload on channel " + getName() + " login index " + event.getLoginIndex() + " for payload type " + event.getChannel());
|
||||
|
||||
var msg = get(event.getChannel(), false);
|
||||
this.validate(event.getChannel(), event.getSource().getConnection(), msg.protocol(), msg.direction(), false);
|
||||
|
||||
var pkt = msg.codec().decode(data);
|
||||
msg.consumer().accept(pkt, event.getSource());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Packet<?> toVanillaPacket(Connection con, CustomPacketPayload message) {
|
||||
var msg = get(message.type().id(), true);
|
||||
this.validate(message.type().id(), con, msg.protocol(), msg.direction(), true);
|
||||
return super.toVanillaPacket(con, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
ResourceLocation getName(CustomPacketPayload packet) {
|
||||
return packet.type().id();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(FriendlyByteBuf out, CustomPacketPayload message) {
|
||||
var msg = get(message.type().id(), true);
|
||||
msg.codec().encode(out, message);
|
||||
}
|
||||
}
|
||||
@@ -5,14 +5,25 @@
|
||||
|
||||
package net.minecraftforge.network;
|
||||
|
||||
import io.netty.util.AttributeKey;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
|
||||
import net.minecraft.network.Connection;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.PacketFlow;
|
||||
import net.minecraftforge.event.network.CustomPayloadEvent;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import net.minecraftforge.event.network.CustomPayloadEvent.Context;
|
||||
import net.minecraftforge.network.simple.handler.SimpleHandlerFlow;
|
||||
import net.minecraftforge.network.simple.handler.SimpleHandlerProtocol;
|
||||
import net.minecraftforge.network.simple.handler.SimplePacket;
|
||||
import net.minecraftforge.network.simple.SimpleBuildable;
|
||||
import net.minecraftforge.network.simple.SimpleConnection;
|
||||
import net.minecraftforge.network.simple.SimpleFlow;
|
||||
import net.minecraftforge.network.simple.SimpleProtocol;
|
||||
import org.apache.commons.lang3.function.TriConsumer;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
@@ -21,16 +32,13 @@ import org.apache.logging.log4j.MarkerManager;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.util.AttributeKey;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectMap;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class SimpleChannel extends Channel<Object> {
|
||||
public class SimpleChannel extends Channel<Object> implements SimpleConnection<Object> {
|
||||
private static final Logger LOGGER = LogManager.getLogger();
|
||||
private static final Marker MARKER = MarkerManager.getMarker("SIMPLE_CHANNEL");
|
||||
private boolean built = false;
|
||||
|
||||
@ApiStatus.Internal
|
||||
SimpleChannel(NetworkInstance instance) {
|
||||
@@ -38,13 +46,23 @@ public class SimpleChannel extends Channel<Object> {
|
||||
instance.addListener(this::networkEventListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <NEWBUF extends FriendlyByteBuf, NEWBASE> SimpleProtocol<NEWBUF, NEWBASE> protocol(NetworkProtocol<NEWBUF> protocol) {
|
||||
return new Protocol<>(new SimpleContext<NEWBUF, NEWBASE>(this).protocol(protocol));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <NEWBUF extends FriendlyByteBuf, CTX, NEWBASE extends SimplePacket<CTX>> SimpleHandlerProtocol<NEWBUF, NEWBASE> protocol(AttributeKey<CTX> context, NetworkProtocol<NEWBUF> protocol) {
|
||||
return new HandlerProtocol<>(context, new SimpleContext<NEWBUF, NEWBASE>(this).protocol(protocol));
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a new MessageBuilder, using the next available discriminator.
|
||||
*
|
||||
* @param type Type of message
|
||||
* @param <M> Type of type
|
||||
*/
|
||||
public <M> MessageBuilder<M> messageBuilder(final Class<M> type) {
|
||||
public <M> MessageBuilder<M, FriendlyByteBuf> messageBuilder(Class<M> type) {
|
||||
return messageBuilder(type, nextIndex());
|
||||
}
|
||||
|
||||
@@ -55,8 +73,19 @@ public class SimpleChannel extends Channel<Object> {
|
||||
* @param discriminator Manually configured discriminator, Must be a positive number.
|
||||
* @param <M> Type of type
|
||||
*/
|
||||
public <M> MessageBuilder<M> messageBuilder(final Class<M> type, int discriminator) {
|
||||
return messageBuilder(type, discriminator, null);
|
||||
public <M> MessageBuilder<M, FriendlyByteBuf> messageBuilder(Class<M> type, int discriminator) {
|
||||
return messageBuilder(type, discriminator, (NetworkProtocol<FriendlyByteBuf>)null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a new MessageBuilder, using the next available discriminator.
|
||||
*
|
||||
* @param type Type of message
|
||||
* @param protocol The protocol that this packet is allowed to be sent/received in, Use enforce strict state handling to prevent spoofing.
|
||||
* @param <M> Type of type
|
||||
*/
|
||||
public <M, B extends FriendlyByteBuf> MessageBuilder<M, B> messageBuilder(Class<M> type, NetworkProtocol<B> protocol) {
|
||||
return messageBuilder(type, nextIndex(), protocol);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,8 +96,21 @@ public class SimpleChannel extends Channel<Object> {
|
||||
* enforce strict sided handling to prevent spoofing.
|
||||
* @param <M> Type of type
|
||||
*/
|
||||
public <M> MessageBuilder<M> messageBuilder(final Class<M> type, NetworkDirection direction) {
|
||||
return messageBuilder(type, nextIndex(), direction);
|
||||
public <M, B extends FriendlyByteBuf> MessageBuilder<M, B> messageBuilder(Class<M> type, NetworkDirection<B> direction) {
|
||||
return messageBuilder(type, nextIndex(), direction.protocol()).direction(direction.direction());
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a new MessageBuilder, using the next available discriminator.
|
||||
*
|
||||
* @param type Type of message
|
||||
* @param discriminator Manually configured discriminator, Must be a positive number.
|
||||
* @param protocol The protocol that this packet is allowed to be sent/received in, Use enforce strict state handling to prevent spoofing.
|
||||
* @param <M> Type of type
|
||||
*/
|
||||
public <M, B extends FriendlyByteBuf> MessageBuilder<M, B> messageBuilder(Class<M> type, int discriminator, NetworkProtocol<B> protocol) {
|
||||
checkBuilt();
|
||||
return new MessageBuilder<>(this, type, discriminator, protocol);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,41 +122,68 @@ public class SimpleChannel extends Channel<Object> {
|
||||
* enforce strict sided handling to prevent spoofing.
|
||||
* @param <M> Type of type
|
||||
*/
|
||||
public <M> MessageBuilder<M> messageBuilder(final Class<M> type, int discriminator, NetworkDirection direction) {
|
||||
return new MessageBuilder<>(this, type, discriminator, direction);
|
||||
public <M, B extends FriendlyByteBuf> MessageBuilder<M, B> messageBuilder(Class<M> type, int discriminator, NetworkDirection<B> direction) {
|
||||
return messageBuilder(type, discriminator, direction.protocol()).direction(direction.direction());
|
||||
}
|
||||
|
||||
public static class MessageBuilder<MSG> {
|
||||
/**
|
||||
* Finishes off the builder side of the SimpleChanel. This prevents adding more messages to this channel.
|
||||
*/
|
||||
public SimpleChannel build() {
|
||||
checkBuilt();
|
||||
this.built = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
protected void checkBuilt() {
|
||||
if (built)
|
||||
throw new IllegalStateException("SimpleChannel builder is fully built, can not modify it any more");
|
||||
}
|
||||
|
||||
public static class MessageBuilder<MSG, BUF extends FriendlyByteBuf> {
|
||||
private final SimpleChannel channel;
|
||||
private final Class<MSG> type;
|
||||
private final int id;
|
||||
private final Optional<NetworkDirection> direction;
|
||||
private final NetworkProtocol<BUF> protocol;
|
||||
|
||||
private BiConsumer<MSG, FriendlyByteBuf> encoder;
|
||||
private Function<FriendlyByteBuf, MSG> decoder;
|
||||
private BiConsumer<MSG, BUF> encoder;
|
||||
private Function<BUF, MSG> decoder;
|
||||
private BiConsumer<MSG, CustomPayloadEvent.Context> consumer;
|
||||
private PacketFlow direction;
|
||||
|
||||
private MessageBuilder(final SimpleChannel channel, final Class<MSG> type, int id, @Nullable NetworkDirection networkDirection) {
|
||||
private MessageBuilder(SimpleChannel channel, Class<MSG> type, int id, @Nullable NetworkProtocol<BUF> protocol) {
|
||||
this.channel = channel;
|
||||
this.type = type;
|
||||
this.id = id;
|
||||
this.direction = Optional.ofNullable(networkDirection);
|
||||
this.protocol = protocol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the direction that this packet is allowed to be sent/received.
|
||||
* Use to enforce strict sided handling to prevent spoofing.
|
||||
*
|
||||
* @param direction The direction this packet is allowed on.
|
||||
* @return This message builder, for chaining.
|
||||
*/
|
||||
public MessageBuilder<MSG, BUF> direction(PacketFlow direction) {
|
||||
this.direction = direction;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the message encoder, which writes this message to a {@link FriendlyByteBuf}.
|
||||
* <p>
|
||||
* The encoder is called <em>immediately</em> {@linkplain #send(PacketDistributor.PacketTarget, Object) when the
|
||||
* The encoder is called <em>immediately</em> {@linkplain #send(Object, PacketDistributor.PacketTarget)} when the
|
||||
* packet is sent}. This means encoding typically occurs on the main server/client thread rather than on the
|
||||
* network thread.
|
||||
* <p>
|
||||
* However, this behaviour should not be relied on, and the encoder should try to be thread-safe and not
|
||||
* However, this behavior should not be relied on, and the encoder should try to be thread-safe and not
|
||||
* interact with the current game state.
|
||||
*
|
||||
* @param encoder The message encoder.
|
||||
* @return This message builder, for chaining.
|
||||
*/
|
||||
public MessageBuilder<MSG> encoder(BiConsumer<MSG, FriendlyByteBuf> encoder) {
|
||||
public MessageBuilder<MSG, BUF> encoder(BiConsumer<MSG, BUF> encoder) {
|
||||
this.encoder = encoder;
|
||||
return this;
|
||||
}
|
||||
@@ -123,17 +192,50 @@ public class SimpleChannel extends Channel<Object> {
|
||||
* Set the message decoder, which reads the message from a {@link FriendlyByteBuf}.
|
||||
* <p>
|
||||
* The decoder is called when the message is received on the network thread. The decoder should not attempt to
|
||||
* access or mutate any game state, deferring that until the {@linkplain #consumer(ToBooleanBiFunction) the
|
||||
* access or mutate any game state, deferring that until the {@linkplain #consumer(BiConsumer) the
|
||||
* message is handled}.
|
||||
*
|
||||
* @param decoder The message decoder.
|
||||
* @return The message builder, for chaining.
|
||||
*/
|
||||
public MessageBuilder<MSG> decoder(Function<FriendlyByteBuf, MSG> decoder) {
|
||||
public MessageBuilder<MSG, BUF> decoder(Function<BUF, MSG> decoder) {
|
||||
this.decoder = decoder;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the StreamCodec to be used for marshaling the message object to and from a {@link FriendlyByteBuf}
|
||||
* <p>
|
||||
* This has all the same limitations of {@link #encoder(BiConsumer)} and {@link #decoder(Function)} as it is equivalent of calling those functions
|
||||
* with the codec's encode and decode functions.
|
||||
*
|
||||
* @param codec The codec to use.
|
||||
* @return The message builder, for chaining.
|
||||
*/
|
||||
public MessageBuilder<MSG, BUF> codec(StreamCodec<BUF, MSG> codec) {
|
||||
return this.encoder((msg, buf) -> codec.encode(buf, msg))
|
||||
.decoder(codec::decode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the message consumer, which is called once a message has been decoded. This accepts the decoded message
|
||||
* object and the message's context.
|
||||
* <p>
|
||||
* The consumer is called on the network thread, and so should not interact with most game state by default.
|
||||
* {@link CustomPayloadEvent.Context#enqueueWork(Runnable)} can be used to handle the message on the main server or
|
||||
* client thread. Alternatively one can use {@link #consumerMainThread(BiConsumer)} to run the handler on the
|
||||
* main thread.
|
||||
*
|
||||
* @param consumer The message consumer.
|
||||
* @return The message builder, for chaining.
|
||||
*
|
||||
* @see #consumerMainThread(BiConsumer)
|
||||
*/
|
||||
public MessageBuilder<MSG, BUF> consumer(BiConsumer<MSG, CustomPayloadEvent.Context> consumer) {
|
||||
this.consumer = consumer;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the message consumer, which is called once a message has been decoded. This accepts the decoded message
|
||||
* object and the message's context.
|
||||
@@ -148,9 +250,8 @@ public class SimpleChannel extends Channel<Object> {
|
||||
*
|
||||
* @see #consumerMainThread(BiConsumer)
|
||||
*/
|
||||
public MessageBuilder<MSG> consumerNetworkThread(BiConsumer<MSG, CustomPayloadEvent.Context> consumer) {
|
||||
this.consumer = consumer;
|
||||
return this;
|
||||
public MessageBuilder<MSG, BUF> consumerNetworkThread(BiConsumer<MSG, CustomPayloadEvent.Context> consumer) {
|
||||
return this.consumer(consumer);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -160,7 +261,7 @@ public class SimpleChannel extends Channel<Object> {
|
||||
* <p>
|
||||
* The consumer is called on the network thread, and so should not interact with most game state by default.
|
||||
* {@link CustomPayloadEvent.Context#enqueueWork(Runnable)} can be used to handle the message on the main server or
|
||||
* client thread. Alternatively one can use {@link #consumerMainThread(TriConsumer)} to run the handler on the
|
||||
* client thread. Alternatively one can use {@link #consumerMainThread(BiConsumer)} to run the handler on the
|
||||
* main thread.
|
||||
*
|
||||
* @param consumer The message consumer.
|
||||
@@ -168,7 +269,7 @@ public class SimpleChannel extends Channel<Object> {
|
||||
*
|
||||
* @see #consumerMainThread(BiConsumer)
|
||||
*/
|
||||
public <C> MessageBuilder<MSG> consumerNetworkThread(AttributeKey<C> key, TriConsumer<C, MSG, CustomPayloadEvent.Context> consumer) {
|
||||
public <C> MessageBuilder<MSG, BUF> consumerNetworkThread(AttributeKey<C> key, TriConsumer<C, MSG, CustomPayloadEvent.Context> consumer) {
|
||||
return consumerNetworkThread((msg, ctx) -> {
|
||||
var inst = ctx.getConnection().channel().attr(key).get();
|
||||
consumer.accept(inst, msg, ctx);
|
||||
@@ -180,7 +281,7 @@ public class SimpleChannel extends Channel<Object> {
|
||||
*
|
||||
* @see #consumerNetworkThread(BiConsumer)
|
||||
*/
|
||||
public MessageBuilder<MSG> consumerNetworkThread(ToBooleanBiFunction<MSG, CustomPayloadEvent.Context> handler) {
|
||||
public MessageBuilder<MSG, BUF> consumerNetworkThread(ToBooleanBiFunction<MSG, CustomPayloadEvent.Context> handler) {
|
||||
this.consumer = (msg, ctx) -> ctx.setPacketHandled(handler.applyAsBool(msg, ctx));
|
||||
return this;
|
||||
}
|
||||
@@ -190,7 +291,7 @@ public class SimpleChannel extends Channel<Object> {
|
||||
*
|
||||
* @see #consumerNetworkThread(AttributeKey,TriConsumer)
|
||||
*/
|
||||
public <C> MessageBuilder<MSG> consumerNetworkThread(AttributeKey<C> key, ToBooleanTriFunction<C, MSG, CustomPayloadEvent.Context> handler) {
|
||||
public <C> MessageBuilder<MSG, BUF> consumerNetworkThread(AttributeKey<C> key, ToBooleanTriFunction<C, MSG, CustomPayloadEvent.Context> handler) {
|
||||
return consumerNetworkThread((msg, ctx) -> {
|
||||
var inst = ctx.getConnection().channel().attr(key).get();
|
||||
return handler.applyAsBool(inst, msg, ctx);
|
||||
@@ -209,7 +310,7 @@ public class SimpleChannel extends Channel<Object> {
|
||||
*
|
||||
* @see #consumerNetworkThread(BiConsumer)
|
||||
*/
|
||||
public MessageBuilder<MSG> consumerMainThread(BiConsumer<MSG, CustomPayloadEvent.Context> consumer) {
|
||||
public MessageBuilder<MSG, BUF> consumerMainThread(BiConsumer<MSG, CustomPayloadEvent.Context> consumer) {
|
||||
this.consumer = (msg, context) -> {
|
||||
var ctx = context;
|
||||
ctx.enqueueWork(() -> consumer.accept(msg, context));
|
||||
@@ -231,7 +332,7 @@ public class SimpleChannel extends Channel<Object> {
|
||||
*
|
||||
* @see #consumerNetworkThread(BiConsumer)
|
||||
*/
|
||||
public <C> MessageBuilder<MSG> consumerMainThread(AttributeKey<C> key, TriConsumer<C, MSG, CustomPayloadEvent.Context> consumer) {
|
||||
public <C> MessageBuilder<MSG, BUF> consumerMainThread(AttributeKey<C> key, TriConsumer<C, MSG, CustomPayloadEvent.Context> consumer) {
|
||||
this.consumer = (msg, ctx) -> {
|
||||
ctx.enqueueWork(() -> {
|
||||
var inst = ctx.getConnection().channel().attr(key).get();
|
||||
@@ -257,10 +358,15 @@ public class SimpleChannel extends Channel<Object> {
|
||||
* @return The attached SimpleChannel to facilitate chaining.
|
||||
*/
|
||||
public SimpleChannel add() {
|
||||
channel.checkBuilt();
|
||||
|
||||
if (this.id < 0)
|
||||
throw new IllegalStateException("Failed to register SimpleChannel message, Invalid ID " + this.id + ": " + this.type.getName());
|
||||
|
||||
var msg = new Message<>(this.id, this.type, this.direction, this.encoder, this.decoder, this.consumer);
|
||||
var msg = new Message<MSG, BUF>(
|
||||
this.id, this.type, this.protocol, this.direction,
|
||||
this.encoder, this.decoder, this.consumer
|
||||
);
|
||||
|
||||
if (channel.byId.containsKey(msg.index()))
|
||||
throw new IllegalStateException("Failed to register SimpleChannel message, ID " + msg.index() + " already claimed: " + msg.type().getName());
|
||||
@@ -269,28 +375,152 @@ public class SimpleChannel extends Channel<Object> {
|
||||
|
||||
channel.byId.put(msg.index(), msg);
|
||||
channel.byType.put(msg.type(), msg);
|
||||
|
||||
return this.channel;
|
||||
}
|
||||
}
|
||||
|
||||
private record SimpleContext<BUF extends FriendlyByteBuf, BASE>(SimpleChannel channel, @Nullable NetworkProtocol<BUF> protocol, @Nullable PacketFlow flow) {
|
||||
public SimpleContext(SimpleChannel channel) {
|
||||
this(channel, null, null);
|
||||
}
|
||||
|
||||
public <NEWBUF extends FriendlyByteBuf> SimpleContext<NEWBUF, BASE> protocol(NetworkProtocol<NEWBUF> protocol) {
|
||||
return new SimpleContext<>(channel, protocol, flow);
|
||||
}
|
||||
|
||||
private SimpleContext<BUF, BASE> flow(@Nullable PacketFlow flow) {
|
||||
return new SimpleContext<>(channel, protocol, flow);
|
||||
}
|
||||
}
|
||||
|
||||
private record Protocol<BUF extends FriendlyByteBuf, BASE>(SimpleContext<BUF, BASE> ctx) implements ProtocolFactory<BUF, BASE>, SimpleProtocol<BUF, BASE> {
|
||||
@Override
|
||||
public SimpleFlow<BUF, BASE> flow(@Nullable PacketFlow flow) {
|
||||
return new Flow<>(ctx.flow(flow));
|
||||
}
|
||||
}
|
||||
|
||||
private record HandlerProtocol<BUF extends FriendlyByteBuf, CTX, BASE extends SimplePacket<CTX>>(
|
||||
AttributeKey<CTX> key,
|
||||
SimpleContext<BUF, BASE> ctx
|
||||
) implements ProtocolFactory<BUF, BASE>, SimpleHandlerProtocol<BUF, BASE> {
|
||||
@Override
|
||||
public SimpleHandlerFlow<BUF, BASE> flow(@Nullable PacketFlow flow) {
|
||||
return new HandlerFlow<>(key, ctx.flow(flow));
|
||||
}
|
||||
}
|
||||
|
||||
private record Flow<BUF extends FriendlyByteBuf, BASE>(SimpleContext<BUF, BASE> ctx) implements ProtocolFactory<BUF, BASE>, SimpleFlow<BUF, BASE> {
|
||||
@Override
|
||||
public <MSG extends BASE> Flow<BUF, BASE> add(Class<MSG> type, StreamCodec<BUF, MSG> codec, BiConsumer<MSG, Context> handler) {
|
||||
ctx.channel.messageBuilder(type, ctx.protocol)
|
||||
.codec(codec)
|
||||
.consumer(handler)
|
||||
.direction(ctx.flow())
|
||||
.add();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SimpleFlow<BUF, BASE> flow(@Nullable PacketFlow flow) {
|
||||
return new Flow<>(ctx.flow(flow));
|
||||
}
|
||||
}
|
||||
|
||||
private record HandlerFlow<BUF extends FriendlyByteBuf, CTX, BASE extends SimplePacket<CTX>>(
|
||||
AttributeKey<CTX> key,
|
||||
SimpleContext<BUF, BASE> ctx
|
||||
) implements ProtocolFactory<BUF, BASE>, SimpleHandlerFlow<BUF, BASE> {
|
||||
@Override
|
||||
public <MSG extends BASE> SimpleHandlerFlow<BUF, BASE> add(Class<MSG> type, StreamCodec<BUF, MSG> codec) {
|
||||
return this.add(type, codec, (msg, ctx) -> {
|
||||
var inst = ctx.getConnection().channel().attr(key).get();
|
||||
ctx.setPacketHandled(msg.handle(inst, ctx));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public <MSG extends BASE> SimpleHandlerFlow<BUF, BASE> addMain(Class<MSG> type, StreamCodec<BUF, MSG> codec) {
|
||||
return this.add(type, codec, (msg, ctx) -> {
|
||||
ctx.enqueueWork(() -> {
|
||||
var inst = ctx.getConnection().channel().attr(key).get();
|
||||
msg.handle(inst, ctx);
|
||||
});
|
||||
ctx.setPacketHandled(true);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public <MSG extends BASE> SimpleHandlerFlow<BUF, BASE> add(Class<MSG> type, StreamCodec<BUF, MSG> codec, BiConsumer<MSG, Context> handler) {
|
||||
ctx.channel.messageBuilder(type, ctx.protocol)
|
||||
.codec(codec)
|
||||
.consumer(handler)
|
||||
.direction(ctx.flow())
|
||||
.add();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SimpleHandlerFlow<BUF, BASE> flow(@Nullable PacketFlow flow) {
|
||||
return new HandlerFlow<>(key, ctx.flow(flow));
|
||||
}
|
||||
}
|
||||
|
||||
private interface ProtocolFactory<BUF extends FriendlyByteBuf, BASE> extends SimpleConnection<BASE>, SimpleBuildable {
|
||||
SimpleContext<BUF, BASE> ctx();
|
||||
|
||||
@Override
|
||||
default <NEWBUF extends FriendlyByteBuf, NEWBASE> SimpleProtocol<NEWBUF, NEWBASE> protocol(NetworkProtocol<NEWBUF> protocol) {
|
||||
return ctx().channel.protocol(protocol);
|
||||
}
|
||||
|
||||
@Override
|
||||
default <NEWBUF extends FriendlyByteBuf, CTX, NEWBASE extends SimplePacket<CTX>> SimpleHandlerProtocol<NEWBUF, NEWBASE> protocol(AttributeKey<CTX> context, NetworkProtocol<NEWBUF> protocol) {
|
||||
return ctx().channel.protocol(context, protocol);
|
||||
}
|
||||
|
||||
@Override
|
||||
default SimpleChannel build() {
|
||||
return ctx().channel.build();
|
||||
}
|
||||
}
|
||||
|
||||
/* ===================================================================================
|
||||
* INTERNAL BELOW THIS
|
||||
* ===================================================================================
|
||||
*/
|
||||
private int lastIndex = 0;
|
||||
private Int2ObjectMap<Message<?>> byId = new Int2ObjectArrayMap<>();
|
||||
private Object2ObjectMap<Class<?>, Message<?>> byType = new Object2ObjectArrayMap<>();
|
||||
protected int lastIndex = 0;
|
||||
protected Int2ObjectMap<Message<?, ?>> byId = new Int2ObjectArrayMap<>();
|
||||
protected Object2ObjectMap<Class<?>, Message<?, ?>> byType = new Object2ObjectArrayMap<>();
|
||||
|
||||
private record Message<MSG>(
|
||||
protected record Message<MSG, BUF extends FriendlyByteBuf>(
|
||||
int index,
|
||||
Class<MSG> type,
|
||||
Optional<NetworkDirection> direction,
|
||||
BiConsumer<MSG, FriendlyByteBuf> encoder,
|
||||
Function<FriendlyByteBuf, MSG> decoder,
|
||||
NetworkProtocol<BUF> protocol,
|
||||
PacketFlow direction,
|
||||
BiConsumer<MSG, BUF> encoder,
|
||||
Function<BUF, MSG> decoder,
|
||||
BiConsumer<MSG, CustomPayloadEvent.Context> consumer
|
||||
){ };
|
||||
|
||||
private int nextIndex() {
|
||||
private Message<Object, FriendlyByteBuf> get(int id) {
|
||||
@SuppressWarnings("unchecked")
|
||||
var msg = (Message<Object, FriendlyByteBuf>)byId.get(id);
|
||||
if (msg == null)
|
||||
error("Received invalid discriminator " + id + " on channel " + getName());
|
||||
return msg;
|
||||
}
|
||||
|
||||
private Message<Object, FriendlyByteBuf> get(Object type) {
|
||||
@SuppressWarnings("unchecked")
|
||||
var msg = (Message<Object, FriendlyByteBuf>)byType.get(type.getClass());
|
||||
if (msg == null)
|
||||
error("Attemped to send invalid message " + type + " on channel " + getName());
|
||||
return msg;
|
||||
}
|
||||
|
||||
protected int nextIndex() {
|
||||
while (byId.containsKey(lastIndex)) {
|
||||
lastIndex++;
|
||||
if (lastIndex < 0)
|
||||
@@ -306,46 +536,29 @@ public class SimpleChannel extends Channel<Object> {
|
||||
return;
|
||||
}
|
||||
|
||||
int id = data.readVarInt();
|
||||
var msg = byId.get(id);
|
||||
if (msg == null) {
|
||||
LOGGER.error(MARKER, "Received invalid discriminator {} on channel {}", id, getName());
|
||||
return;
|
||||
}
|
||||
|
||||
var con = event.getSource().getConnection();
|
||||
var dir = event.getSource().getDirection();
|
||||
|
||||
if (msg.direction.isPresent() && msg.direction.get() != dir) {
|
||||
var error = "Illegal packet received, terminating connection. " + msg.type().getName() + " expexted " + msg.direction.get() + " but was " + dir;
|
||||
LOGGER.error(MARKER, error);
|
||||
con.disconnect(Component.literal(error));
|
||||
throw new IllegalStateException(error);
|
||||
}
|
||||
|
||||
decodeAndDispatch(data, event.getSource(), msg);
|
||||
var msg = get(data.readVarInt());
|
||||
super.validate(msg.type.getName(), event.getSource().getConnection(), msg.protocol(), msg.direction(), false);
|
||||
var pkt = msg.decoder().apply(data);
|
||||
msg.consumer().accept(pkt, event.getSource());
|
||||
}
|
||||
|
||||
// Yay generics
|
||||
private static <MSG> void decodeAndDispatch(FriendlyByteBuf data, CustomPayloadEvent.Context ctx, Message<MSG> msg) {
|
||||
var message = msg.decoder().apply(data);
|
||||
msg.consumer.accept(message, ctx);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public FriendlyByteBuf toBuffer(Object message) {
|
||||
var msg = byType.get(message.getClass());
|
||||
protected Packet<?> toVanillaPacket(Connection con, Object message) {
|
||||
var msg = get(message);
|
||||
super.validate(message.getClass().getName(), con, msg.protocol(), msg.direction(), true);
|
||||
return super.toVanillaPacket(con, message);
|
||||
}
|
||||
|
||||
if (msg == null) {
|
||||
LOGGER.error(MARKER, "Attempted to send invalid message {} on channel {}", message.getClass().getName(), getName());
|
||||
throw new IllegalArgumentException("Invalid message " + message.getClass().getName());
|
||||
}
|
||||
var ret = new FriendlyByteBuf(Unpooled.buffer());
|
||||
ret.writeVarInt(msg.index());
|
||||
@Override
|
||||
public void encode(FriendlyByteBuf out, Object message) {
|
||||
var msg = get(message);
|
||||
out.writeVarInt(msg.index());
|
||||
if (msg.encoder() != null)
|
||||
((BiConsumer<Object, FriendlyByteBuf>)msg.encoder()).accept(message, ret);
|
||||
msg.encoder().accept(message, out);
|
||||
}
|
||||
|
||||
return ret;
|
||||
private void error(String message) {
|
||||
LOGGER.error(MARKER, message);
|
||||
throw new IllegalArgumentException(message);
|
||||
}
|
||||
}
|
||||
@@ -6,12 +6,15 @@
|
||||
package net.minecraftforge.network.packets;
|
||||
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
|
||||
/*
|
||||
* A simple packet used to acknowledge that the client has received and processed a message sent by the server.
|
||||
* The token can be anything, ideally it would an identifier sent from the server.
|
||||
*/
|
||||
public record Acknowledge(int token) {
|
||||
public static StreamCodec<FriendlyByteBuf, Acknowledge> STREAM_CODEC = StreamCodec.ofMember(Acknowledge::encode, Acknowledge::decode);
|
||||
|
||||
public void encode(FriendlyByteBuf buf) {
|
||||
buf.writeVarInt(token);
|
||||
}
|
||||
|
||||
@@ -11,19 +11,22 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraftforge.network.NetworkRegistry;
|
||||
|
||||
public record ChannelVersions(Map<ResourceLocation, @NotNull Integer> channels) {
|
||||
public static StreamCodec<FriendlyByteBuf, ChannelVersions> STREAM_CODEC = StreamCodec.ofMember(ChannelVersions::encode, ChannelVersions::decode);
|
||||
|
||||
public ChannelVersions() {
|
||||
this(NetworkRegistry.buildChannelVersions());
|
||||
}
|
||||
|
||||
public static ChannelVersions decode(FriendlyByteBuf buf) {
|
||||
return new ChannelVersions(buf.readMap(Object2IntOpenHashMap::new, FriendlyByteBuf::readResourceLocation, FriendlyByteBuf::readVarInt));
|
||||
return new ChannelVersions(buf.readMap(Object2IntOpenHashMap::new, FriendlyByteBuf::readResourceLocation, FriendlyByteBuf::readVarInt));
|
||||
}
|
||||
|
||||
public void encode(FriendlyByteBuf buf) {
|
||||
buf.writeMap(channels, FriendlyByteBuf::writeResourceLocation, FriendlyByteBuf::writeVarInt);
|
||||
buf.writeMap(channels, FriendlyByteBuf::writeResourceLocation, FriendlyByteBuf::writeVarInt);
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,11 @@
|
||||
package net.minecraftforge.network.packets;
|
||||
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
|
||||
public record ConfigData(String name, byte[] data) {
|
||||
public static final StreamCodec<FriendlyByteBuf, ConfigData> STREAM_CODEC = StreamCodec.ofMember(ConfigData::encode, ConfigData::decode);
|
||||
|
||||
public void encode(final FriendlyByteBuf buf) {
|
||||
buf.writeUtf(this.name());
|
||||
buf.writeByteArray(this.data());
|
||||
|
||||
@@ -5,20 +5,52 @@
|
||||
|
||||
package net.minecraftforge.network.packets;
|
||||
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.codec.StreamCodec;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraftforge.network.Channel;
|
||||
|
||||
public record LoginWrapper(ResourceLocation channel, FriendlyByteBuf data) {
|
||||
public static LoginWrapper decode(FriendlyByteBuf buf) {
|
||||
var channel = buf.readResourceLocation();
|
||||
var len = buf.readVarInt();
|
||||
var data = new FriendlyByteBuf(buf.readBytes(len));
|
||||
return new LoginWrapper(channel, data);
|
||||
public class LoginWrapper {
|
||||
public static final StreamCodec<FriendlyByteBuf, LoginWrapper> STREAM_CODEC = StreamCodec.ofMember(LoginWrapper::encode, LoginWrapper::new);
|
||||
private final ResourceLocation name;
|
||||
private FriendlyByteBuf data;
|
||||
private final Channel<Object> channel;
|
||||
private final Object packet;
|
||||
|
||||
public <MSG> LoginWrapper(Channel<MSG> channel, MSG packet) {
|
||||
this(channel.getName(), null, channel, packet);
|
||||
}
|
||||
|
||||
private LoginWrapper(FriendlyByteBuf buf) {
|
||||
this(buf.readResourceLocation(), buf.wrap(buf.readBytes(buf.readVarInt())), null, null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private LoginWrapper(ResourceLocation name, FriendlyByteBuf data, Channel<?> channel, Object packet) {
|
||||
this.name = name;
|
||||
this.data = data;
|
||||
this.channel = (Channel<Object>)channel;
|
||||
this.packet = packet;
|
||||
}
|
||||
|
||||
public void encode(FriendlyByteBuf buf) {
|
||||
buf.writeResourceLocation(channel);
|
||||
buf.writeResourceLocation(name);
|
||||
|
||||
if (data == null) {
|
||||
data = buf.wrap(Unpooled.buffer());
|
||||
((Channel<Object>)channel).encode(data, packet);
|
||||
}
|
||||
|
||||
buf.writeVarInt(data.readableBytes());
|
||||
buf.writeBytes(data.slice());
|
||||
}
|
||||
|
||||
public ResourceLocation name() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public FriendlyByteBuf data() {
|
||||
return this.data;
|
||||
}
|
||||
}
|
||||
Loaded 100 of 136 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user