Compare commits

..
20 Commits
Author SHA1 Message Date
LexManos 664a2c06ad 1.19.4 RB 2 2023-09-21 11:52:10 -07:00
Paint_Ninjaandmalte0811 e85295a837 Fix Level leak in debug HUD (#9700)
Co-authored-by: malte0811 <[email protected]>
2023-09-02 17:02:03 -07:00
Paint_Ninjaanddhyces 21582ef641 Keep order of sources in PackRepository (#9703)
Co-authored-by: dhyces <[email protected]>
2023-09-02 16:21:21 -07:00
Paint_Ninja 020ec3d51b Fix DelegatingPackResources searching resource path twice (#9698) 2023-08-30 14:10:38 -07:00
Jonathan Colmenares 774b79e609 Fix parameter names in IForgeDimensionSpecialEffects.adjustLightmapColors to better describe what they are. (#9657) 2023-07-17 09:45:29 -07:00
Jonathan Colmenares ee6022b006 Add mods button to pause menu (#9654) 2023-07-17 08:41:11 -07:00
brandon3055 37efd2b8ae Fixed FluidUtil#tryFillContainer returning invalid result when simulating #9358 2023-07-15 10:52:26 -07:00
Random832 cf3d42ef38 Fix conflicting keybindings not having the correct click count set. #9360 2023-07-15 10:44:29 -07:00
RealMangoRage 03a505a3f2 Fixed AdvancementsScreen.java to render next/previous page buttons now. (#9650) 2023-07-14 19:22:33 -07:00
ChampionAsh5357 bdc856edf5 Properly Handle Fluid Updates while in a Boat Fix #9256 (#9428) 2023-07-14 19:06:07 -07:00
Silver_David 6eebea0896 New hook in IClientBlockExtensions to handle enabling tinting on breaking particles. #9446 2023-07-14 16:08:51 -07:00
Technici4n 9218e08955 Add EmptyEnergyStorage (#9487) 2023-07-14 11:34:12 -07:00
Paint_Ninja 7006fe4f29 Support IPv6 address compression for logged IPs 2023-07-14 11:30:57 -07:00
Paint_Ninja 301e7252b0 Add option to advertise dedicated servers to LAN. (#9502) 2023-07-14 07:07:36 -07:00
ChampionAsh5357 2b5852072c Add particle description data provider (#9510) 2023-07-13 19:35:07 -07:00
bconlon 0b38373a45 AlterGroundEvent for modifying block placement performed by AlterGroundDecorator (#9636) 2023-07-13 17:30:45 -07:00
Paint_Ninja 07e538f3dd Improve logging for server connections (#9622) 2023-07-11 11:04:45 -07:00
cech12 b39e300c1b Fix placing fluids into waterlogged blocks with FluidUtil::tryPlaceFluid. To behave more like vanilla MC-127110 (#9519) 2023-07-11 11:04:26 -07:00
LexManos 50db51a366 Add proper duplicate mod error message that includes the mod id(s) and mod files. (#9474) 2023-06-20 14:18:14 -07:00
LexManos df80acc49d Make 1.20.x the main TC branch. 2023-06-20 14:07:42 -07:00
48 changed files with 1193 additions and 65 deletions

No files matched your search

+1 -1
View File
@@ -38,7 +38,7 @@ project {
params {
text("docker_jdk_version", "17", label = "Gradle version", description = "The version of the JDK to use during execution of tasks in a JDK.", display = ParameterDisplay.HIDDEN, allowEmpty = false)
text("docker_gradle_version", "7.5.1", label = "Gradle version", description = "The version of Gradle to use during execution of Gradle tasks.", display = ParameterDisplay.HIDDEN, allowEmpty = false)
text("git_main_branch", "1.19.x", label = "Git Main Branch", description = "The git main or default branch to use in VCS operations.", display = ParameterDisplay.HIDDEN, allowEmpty = false)
text("git_main_branch", "1.20.x", label = "Git Main Branch", description = "The git main or default branch to use in VCS operations.", display = ParameterDisplay.HIDDEN, allowEmpty = false)
text("git_branch_spec", """
+:refs/heads/(main*)
+:refs/heads/(master*)
+6 -2
View File
@@ -27,8 +27,8 @@ plugins {
id 'net.minecraftforge.gradleutils' version '2.+'
id 'eclipse'
id 'de.undercouch.download' version '5.1.3'
id 'net.minecraftforge.gradle.patcher' version '[5.1.50,5.2.0)' apply false
id 'net.minecraftforge.gradle.mcp' version '[5.1.50,5.2.0)' apply false
id 'net.minecraftforge.gradle.patcher' version '[5.1.77,5.2.0)' apply false
id 'net.minecraftforge.gradle.mcp' version '[5.1.77,5.2.0)' apply false
}
Util.init() //Init all our extension methods!
@@ -884,6 +884,10 @@ project(':forge') {
apply plugin: 'net.minecraftforge.gradle.patcher'
apply plugin: 'org.cadixdev.licenser'
applyPatches {
level 'WARNING'
}
sourceSets {
main {
java {
+3 -2
View File
@@ -10,8 +10,9 @@ Forge is a free, open-source modding API all of your favourite mods use!
| Version | Support |
|---------| ------------- |
| 1.19.x | Active |
| 1.18.x | LTS |
| 1.20.x | Active |
| 1.19.4 | Legacy |
| 1.19.2 | Legacy |
* [Download]
* [Forum]
@@ -11,11 +11,17 @@ import net.minecraftforge.forgespi.language.IModInfo;
import org.apache.maven.artifact.versioning.ArtifactVersion;
import org.slf4j.Logger;
import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.*;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.joining;
import static net.minecraftforge.fml.loading.LogMarkers.LOADING;
public class UniqueModListBuilder
{
@@ -66,28 +72,35 @@ public class UniqueModListBuilder
// Its theoretically possible that some mod has somehow moved an id to a secondary place, thus causing a dupe.
// We can't handle this
final List<IModInfo> dupedMods = modIds.values().stream()
final List<String> dupedModErrors = modIds.values().stream()
.filter(modInfos -> modInfos.size() > 1)
.map(modInfos -> modInfos.get(0))
.toList();
.map(mods -> String.format("\tMod ID: '%s' from mod files: %s",
mods.get(0).getModId(),
mods.stream()
.map(modInfo -> modInfo.getOwningFile().getFile().getFileName()).collect(joining(", "))
)).toList();
if (!dupedMods.isEmpty()) {
final List<EarlyLoadingException.ExceptionData> duplicateModErrors = dupedMods.stream()
.map(dm -> new EarlyLoadingException.ExceptionData("fml.modloading.dupedmod", dm, Objects.toString(dm)))
.toList();
throw new EarlyLoadingException("Duplicate mods found", null, duplicateModErrors);
if (!dupedModErrors.isEmpty()) {
LOGGER.error(LOADING, "Found duplicate mods:\n{}", dupedModErrors.stream().collect(joining("\n")));
throw new EarlyLoadingException("Duplicate mods found", null, dupedModErrors.stream()
.map(s -> new EarlyLoadingException.ExceptionData(s))
.toList());
}
final List<ModFile> dupedLibs = versionedLibIds.values().stream()
.filter(modFiles -> modFiles.size() > 1)
.map(modFiles -> modFiles.get(0))
.toList();
if (!dupedLibs.isEmpty()) {
final List<EarlyLoadingException.ExceptionData> duplicateLibErrors = dupedLibs.stream()
.map(dm -> new EarlyLoadingException.ExceptionData("fml.modloading.dupedlib.versioned", dm, Objects.toString(dm)))
.toList();
throw new EarlyLoadingException("Duplicate plugins or libraries found", null, duplicateLibErrors);
final List<String> dupedLibErrors = versionedLibIds.values().stream()
.filter(modFiles -> modFiles.size() > 1)
.map(mods -> String.format("\tLibrary: '%s' from files: %s",
getModId(mods.get(0)),
mods.stream()
.map(modFile -> modFile.getFileName()).collect(joining(", "))
)).toList();
if (!dupedLibErrors.isEmpty()) {
LOGGER.error(LOADING, "Found duplicate plugins or libraries:\n{}", dupedLibErrors.stream().collect(joining("\n")));
throw new EarlyLoadingException("Duplicate plugins or libraries found", null, dupedLibErrors.stream()
.map(s -> new EarlyLoadingException.ExceptionData(s))
.toList());
}
// Collect unique mod files by module name. This will be used for deduping purposes
@@ -12,6 +12,15 @@
private static final Set<String> f_90811_ = Sets.newHashSet();
public static final String f_167805_ = "key.categories.movement";
public static final String f_167806_ = "key.categories.misc";
@@ -41,7 +_,7 @@
private int f_90818_;
public static void m_90835_(InputConstants.Key p_90836_) {
- KeyMapping keymapping = f_90810_.get(p_90836_);
+ for (KeyMapping keymapping : f_90810_.getAll(p_90836_))
if (keymapping != null) {
++keymapping.f_90818_;
}
@@ -49,7 +_,7 @@
}
@@ -0,0 +1,11 @@
--- a/net/minecraft/client/gui/screens/ConnectScreen.java
+++ b/net/minecraft/client/gui/screens/ConnectScreen.java
@@ -72,6 +_,8 @@
}
if (!optional.isPresent()) {
+ ConnectScreen.f_95683_.error("Couldn't connect to server: Unknown host \"{}\"", p_249536_.m_171863_());
+ net.minecraftforge.network.DualStackUtils.logInitialPreferences();
p_251955_.execute(() -> {
p_251955_.m_91152_(new DisconnectedScreen(ConnectScreen.this.f_95686_, CommonComponents.f_130661_, ConnectScreen.f_169260_));
});
@@ -0,0 +1,10 @@
--- a/net/minecraft/client/gui/screens/PauseScreen.java
+++ b/net/minecraft/client/gui/screens/PauseScreen.java
@@ -83,6 +_,7 @@
} else {
gridlayout$rowhelper.m_264139_(this.m_262456_(f_262254_, SocialInteractionsScreen::new));
}
+ gridlayout$rowhelper.m_264108_(Button.m_253074_(Component.m_237115_("fml.menu.mods"), button -> this.f_96541_.m_91152_(new net.minecraftforge.client.gui.ModListScreen(this))).m_252780_(f_262268_).m_253136_(), 2);
Component component = this.f_96541_.m_91090_() ? f_262217_ : f_262246_;
this.f_252482_ = gridlayout$rowhelper.m_264108_(Button.m_253074_(component, (p_275864_) -> {
@@ -34,7 +34,7 @@
this.f_97334_.m_104401_(advancementtab.m_97182_(), true);
break;
}
@@ -98,6 +_,11 @@
@@ -98,9 +_,15 @@
int i = (this.f_96543_ - 252) / 2;
int j = (this.f_96544_ - 140) / 2;
this.m_7333_(p_97361_);
@@ -46,6 +46,10 @@
this.m_97373_(p_97361_, p_97362_, p_97363_, i, j);
this.m_97356_(p_97361_, i, j);
this.m_97381_(p_97361_, p_97362_, p_97363_, i, j);
+ super.m_86412_(p_97361_, p_97362_, p_97363_, p_97364_);
}
public boolean m_7979_(double p_97347_, double p_97348_, int p_97349_, double p_97350_, double p_97351_) {
@@ -138,10 +_,12 @@
RenderSystem.m_157456_(0, f_97330_);
@@ -1,5 +1,14 @@
--- a/net/minecraft/client/particle/TerrainParticle.java
+++ b/net/minecraft/client/particle/TerrainParticle.java
@@ -28,7 +_,7 @@
this.f_107227_ = 0.6F;
this.f_107228_ = 0.6F;
this.f_107229_ = 0.6F;
- if (!p_172458_.m_60713_(Blocks.f_50440_)) {
+ if (net.minecraftforge.client.extensions.common.IClientBlockExtensions.of(p_172458_).areBreakingParticlesTinted(p_172458_, p_172451_, p_172459_)) {
int i = Minecraft.m_91087_().m_91298_().m_92577_(p_172458_, p_172451_, p_172459_, 0);
this.f_107227_ *= (float)(i >> 16 & 255) / 255.0F;
this.f_107228_ *= (float)(i >> 8 & 255) / 255.0F;
@@ -69,7 +_,13 @@
public static class Provider implements ParticleProvider<BlockParticleOption> {
public Particle m_6966_(BlockParticleOption p_108304_, ClientLevel p_108305_, double p_108306_, double p_108307_, double p_108308_, double p_108309_, double p_108310_, double p_108311_) {
@@ -9,21 +9,13 @@
this.f_120088_.setSoTimeout(5000);
this.f_120088_.joinGroup(this.f_120087_);
}
@@ -87,7 +_,19 @@
@@ -87,7 +_,11 @@
String s = LanServerPinger.m_120111_(p_120097_);
String s1 = LanServerPinger.m_120116_(p_120097_);
if (s1 != null) {
- s1 = p_120098_.getHostAddress() + ":" + s1;
+ if (net.minecraftforge.network.DualStackUtils.checkIPv6(p_120098_)) {
+ final String ip;
+
+ // compress to short form if enabled in config
+ if (net.minecraftforge.common.ForgeConfig.CLIENT.compressLanIPv6Addresses.get())
+ ip = com.google.common.net.InetAddresses.toAddrString(p_120098_);
+ else
+ ip = p_120098_.getHostAddress();
+
+ s1 = "[" + ip + "]:" + s1;
+ s1 = "[" + com.google.common.net.InetAddresses.toAddrString(p_120098_) + "]:" + s1;
+ } else {
+ s1 = p_120098_.getHostAddress() + ":" + s1;
+ }
@@ -1,6 +1,6 @@
--- a/net/minecraft/client/server/LanServerPinger.java
+++ b/net/minecraft/client/server/LanServerPinger.java
@@ -16,7 +_,7 @@
@@ -13,7 +_,7 @@
public class LanServerPinger extends Thread {
private static final AtomicInteger f_120101_ = new AtomicInteger(0);
private static final Logger f_120102_ = LogUtils.getLogger();
@@ -9,7 +9,7 @@
public static final int f_174975_ = 4445;
private static final long f_174976_ = 1500L;
private final String f_120103_;
@@ -39,7 +_,7 @@
@@ -36,7 +_,7 @@
while(!this.isInterrupted() && this.f_120105_) {
try {
@@ -1,5 +1,14 @@
--- a/net/minecraft/server/dedicated/DedicatedServer.java
+++ b/net/minecraft/server/dedicated/DedicatedServer.java
@@ -68,6 +_,8 @@
private MinecraftServerGui f_139605_;
@Nullable
private final TextFilterClient f_139606_;
+ @Nullable
+ private net.minecraft.client.server.LanServerPinger dediLanPinger;
public DedicatedServer(Thread p_214789_, LevelStorageSource.LevelStorageAccess p_214790_, PackRepository p_214791_, WorldStem p_214792_, DedicatedServerSettings p_214793_, DataFixer p_214794_, Services p_214795_, ChunkProgressListenerFactory p_214796_) {
super(p_214789_, p_214790_, p_214791_, p_214792_, Proxy.NO_PROXY, p_214794_, p_214795_, p_214796_);
@@ -79,6 +_,7 @@
public boolean m_7038_() throws IOException {
Thread thread = new Thread("Server console handler") {
@@ -22,7 +31,7 @@
if (dedicatedserverproperties.f_139744_ != null) {
this.m_129900_().m_46170_(GameRules.f_46153_).m_46246_(dedicatedserverproperties.f_139744_, this);
}
@@ -183,12 +_,13 @@
@@ -183,12 +_,18 @@
thread1.start();
}
@@ -33,7 +42,34 @@
}
- return true;
+ if (net.minecraftforge.common.ForgeConfig.SERVER.advertiseDedicatedServerToLan.get()) {
+ this.dediLanPinger = new net.minecraft.client.server.LanServerPinger(this.m_129916_(), String.valueOf(this.m_7448_()));
+ this.dediLanPinger.start();
+ }
+
+ return net.minecraftforge.server.ServerLifecycleHooks.handleServerStarting(this);
}
}
@@ -263,6 +_,10 @@
this.f_139601_.m_7530_();
}
+ if (this.dediLanPinger != null) {
+ this.dediLanPinger.interrupt();
+ this.dediLanPinger = null;
+ }
}
public void m_5703_(BooleanSupplier p_139661_) {
@@ -492,6 +_,10 @@
public void m_7041_() {
super.m_7041_();
+ if (this.dediLanPinger != null) {
+ this.dediLanPinger.interrupt();
+ this.dediLanPinger = null;
+ }
Util.m_137580_();
SkullBlockEntity.m_196704_();
}
@@ -15,6 +15,16 @@
this.m_10055_();
} else if (this.f_10019_ == ServerLoginPacketListenerImpl.State.DELAY_ACCEPT) {
ServerPlayer serverplayer = this.f_10018_.m_6846_().m_11259_(this.f_10021_.getId());
@@ -138,7 +_,8 @@
}
public String m_10056_() {
- return this.f_10021_ != null ? this.f_10021_ + " (" + this.f_10013_.m_129523_() + ")" : String.valueOf((Object)this.f_10013_.m_129523_());
+ final String addressString = net.minecraftforge.network.DualStackUtils.getAddressString(this.f_10013_.m_129523_());
+ return this.f_10021_ != null ? this.f_10021_ + " (" + addressString + ")" : addressString;
}
public void m_5990_(ServerboundHelloPacket p_10047_) {
@@ -147,14 +_,14 @@
GameProfile gameprofile = this.f_10018_.m_236731_();
if (gameprofile != null && p_10047_.f_238040_().equalsIgnoreCase(gameprofile.getName())) {
@@ -5,7 +5,7 @@
public PackRepository(RepositorySource... p_251886_) {
- this.f_10497_ = ImmutableSet.copyOf(p_251886_);
+ this.f_10497_ = new java.util.HashSet<>(List.of(p_251886_)); //Forge: This needs to be a mutable set, so that we can add to it later on.
+ this.f_10497_ = new java.util.LinkedHashSet<>(List.of(p_251886_)); //Forge: This needs to be a mutable set, so that we can add to it later on.
}
public void m_10506_() {
@@ -8,7 +8,15 @@
public PlayerList(MinecraftServer p_203842_, LayeredRegistryAccess<RegistryLayer> p_251844_, PlayerDataStorage p_203844_, int p_203845_) {
this.f_11195_ = p_203842_;
@@ -168,6 +_,7 @@
@@ -161,13 +_,14 @@
p_11263_.m_143425_(serverlevel1);
String s1 = "local";
if (p_11262_.m_129523_() != null) {
- s1 = p_11262_.m_129523_().toString();
+ s1 = net.minecraftforge.network.DualStackUtils.getAddressString(p_11262_.m_129523_());
}
f_11188_.info("{}[{}] logged in with entity id {} at ({}, {}, {})", p_11263_.m_7755_().getString(), s1, p_11263_.m_19879_(), p_11263_.m_20185_(), p_11263_.m_20186_(), p_11263_.m_20189_());
LevelData leveldata = serverlevel1.m_6106_();
p_11263_.m_143427_(compoundtag);
ServerGamePacketListenerImpl servergamepacketlistenerimpl = new ServerGamePacketListenerImpl(this.f_11195_, p_11262_, p_11263_);
@@ -160,7 +160,7 @@
this.m_5496_(soundtype.m_56776_(), soundtype.m_56773_() * 0.15F, soundtype.m_56774_());
}
}
@@ -1074,19 +_,22 @@
@@ -1074,31 +_,38 @@
public void m_5844_() {
if (this.m_6069_()) {
@@ -188,6 +188,24 @@
}
void m_20074_() {
Entity entity = this.m_20202_();
+ java.util.function.BooleanSupplier updateFluidHeight = () -> this.m_204031_(FluidTags.f_13131_, 0.014D);
if (entity instanceof Boat boat) {
if (!boat.m_5842_()) {
this.f_19798_ = false;
- return;
+ updateFluidHeight = () -> {
+ this.updateFluidHeightAndDoFluidPushing(state -> this.shouldUpdateFluidWhileBoating(state, boat));
+ return false;
+ };
}
}
- if (this.m_204031_(FluidTags.f_13131_, 0.014D)) {
+ if (updateFluidHeight.getAsBoolean()) {
if (!this.f_19798_ && !this.f_19803_) {
this.m_5841_();
}
@@ -1115,6 +_,7 @@
private void m_20323_() {
this.f_19800_ = this.m_204029_(FluidTags.f_13131_);
@@ -414,19 +432,24 @@
});
}
@@ -2861,9 +_,17 @@
@@ -2861,9 +_,22 @@
this.f_19859_ = this.m_146908_();
}
+ @Deprecated // Forge: Use no parameter version instead, only for vanilla Tags
+ @Deprecated // Forge: Use predicate version instead, only for vanilla Tags
public boolean m_204031_(TagKey<Fluid> p_204032_, double p_204033_) {
+ this.updateFluidHeightAndDoFluidPushing();
+ this.updateFluidHeightAndDoFluidPushing(com.google.common.base.Predicates.alwaysTrue());
+ if(p_204032_ == FluidTags.f_13131_) return this.isInFluidType(net.minecraftforge.common.ForgeMod.WATER_TYPE.get());
+ else if (p_204032_ == FluidTags.f_13132_) return this.isInFluidType(net.minecraftforge.common.ForgeMod.LAVA_TYPE.get());
+ else return false;
+ }
+
+ @Deprecated(forRemoval = true, since = "1.19.4")
+ public void updateFluidHeightAndDoFluidPushing() {
+ this.updateFluidHeightAndDoFluidPushing(com.google.common.base.Predicates.alwaysTrue());
+ }
+
+ public void updateFluidHeightAndDoFluidPushing(Predicate<FluidState> shouldUpdate) {
if (this.m_146899_()) {
- return false;
+ return;
@@ -446,7 +469,7 @@
FluidState fluidstate = this.f_19853_.m_6425_(blockpos$mutableblockpos);
- if (fluidstate.m_205070_(p_204032_)) {
+ net.minecraftforge.fluids.FluidType fluidType = fluidstate.getFluidType();
+ if (!fluidType.isAir()) {
+ if (!fluidType.isAir() && shouldUpdate.test(fluidstate)) {
double d1 = (double)((float)i2 + fluidstate.m_76155_(this.f_19853_, blockpos$mutableblockpos));
if (d1 >= aabb.f_82289_) {
flag1 = true;
@@ -0,0 +1,11 @@
--- a/net/minecraft/world/level/levelgen/feature/treedecorators/AlterGroundDecorator.java
+++ b/net/minecraft/world/level/levelgen/feature/treedecorators/AlterGroundDecorator.java
@@ -72,7 +_,7 @@
for(int i = 2; i >= -3; --i) {
BlockPos blockpos = p_225975_.m_6630_(i);
if (Feature.m_65788_(p_225974_.m_226058_(), blockpos)) {
- p_225974_.m_226061_(blockpos, this.f_69303_.m_213972_(p_225974_.m_226067_(), p_225975_));
+ p_225974_.m_226061_(blockpos, net.minecraftforge.event.ForgeEventFactory.alterGround(p_225974_.m_226058_(), p_225974_.m_226067_(), blockpos, this.f_69303_.m_213972_(p_225974_.m_226067_(), p_225975_)));
break;
}
@@ -0,0 +1,12 @@
{
"textures": [
"minecraft:generic_7",
"minecraft:generic_6",
"minecraft:generic_5",
"minecraft:generic_4",
"minecraft:generic_3",
"minecraft:generic_2",
"minecraft:generic_1",
"minecraft:generic_0"
]
}
@@ -0,0 +1,5 @@
{
"textures": [
"minecraft:drip_hang"
]
}
@@ -0,0 +1,30 @@
{
"textures": [
"minecraft:sga_a",
"minecraft:sga_b",
"minecraft:sga_c",
"minecraft:sga_d",
"minecraft:sga_e",
"minecraft:sga_f",
"minecraft:sga_g",
"minecraft:sga_h",
"minecraft:sga_i",
"minecraft:sga_j",
"minecraft:sga_k",
"minecraft:sga_l",
"minecraft:sga_m",
"minecraft:sga_n",
"minecraft:sga_o",
"minecraft:sga_p",
"minecraft:sga_q",
"minecraft:sga_r",
"minecraft:sga_s",
"minecraft:sga_t",
"minecraft:sga_u",
"minecraft:sga_v",
"minecraft:sga_w",
"minecraft:sga_x",
"minecraft:sga_y",
"minecraft:sga_z"
]
}
@@ -0,0 +1,8 @@
{
"textures": [
"minecraft:splash_0",
"minecraft:splash_1",
"minecraft:splash_2",
"minecraft:splash_3"
]
}
@@ -67,15 +67,15 @@ public interface IForgeDimensionSpecialEffects
* Allows for manipulating the coloring of the lightmap texture.
* Will be called for each 16*16 combination of sky/block light values.
*
* @param level The current level (client-side).
* @param partialTicks Progress between ticks.
* @param skyDarken Current darkness of the sky.
* @param skyLight Sky light brightness factor.
* @param blockLight Block light brightness factor.
* @param pixelX X-coordinate of the lightmap texture.
* @param pixelY Y-coordinate of the lightmap texture.
* @param colors The color values that will be used: [r, g, b].
* @param level The current level (client-side).
* @param partialTicks Progress between ticks.
* @param skyDarken Current darkness of the sky (can be used to calculate sky light).
* @param blockLightRedFlicker Block light flicker factor (red color) (can be used to calculate block light).
* @param skyLight Sky light brightness (accounting for sky darkness).
* @param pixelX X-coordinate of the lightmap texture (block).
* @param pixelY Y-coordinate of the lightmap texture (sky).
* @param colors The color values that will be used: [r, g, b].
* @see LightTexture#updateLightTexture(float)
*/
default void adjustLightmapColors(ClientLevel level, float partialTicks, float skyDarken, float skyLight, float blockLight, int pixelX, int pixelY, Vector3f colors) {}
default void adjustLightmapColors(ClientLevel level, float partialTicks, float skyDarken, float blockLightRedFlicker, float skyLight, int pixelX, int pixelY, Vector3f colors) {}
}
@@ -5,6 +5,7 @@
package net.minecraftforge.client.extensions.common;
import net.minecraft.client.multiplayer.ClientLevel;
import net.minecraft.client.particle.ParticleEngine;
import net.minecraft.core.BlockPos;
import net.minecraft.world.effect.MobEffects;
@@ -14,6 +15,7 @@ import net.minecraft.world.item.enchantment.EnchantmentHelper;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.material.Material;
import net.minecraft.world.phys.HitResult;
@@ -111,4 +113,17 @@ public interface IClientBlockExtensions
}
return originalColor;
}
/**
* Returns true if the breaking particles created from the {@link BlockState} passed should be tinted with biome colors.
*
* @param state The state of this block
* @param level The level the particles are spawning in
* @param pos The position of the block
* @return {@code true} if the particles should be tinted.
*/
default boolean areBreakingParticlesTinted(BlockState state, ClientLevel level, BlockPos pos)
{
return !state.is(Blocks.GRASS_BLOCK);
}
}
@@ -496,6 +496,13 @@ public class ForgeGui extends Gui
}
}
@Override
public void clearCache()
{
super.clearCache();
this.debugOverlay.clearChunkCache();
}
protected void renderRecordOverlay(int width, int height, float partialTick, PoseStack pStack)
{
if (overlayMessageTime > 0)
@@ -27,6 +27,7 @@ public class KeyMappingLookup
}
}
@Deprecated(forRemoval = true)
@Nullable
public KeyMapping get(InputConstants.Key keyCode)
{
@@ -59,6 +60,16 @@ public class KeyMappingLookup
return null;
}
/**
* Returns all active keys associated with the given key code and the active
* modifiers and conflict context.
*
* This could probably be renamed to something like getActiveMappings, but
* that would be a breaking change.
*
* @param keyCode the key being pressed
* @return the list of key mappings
*/
public List<KeyMapping> getAll(InputConstants.Key keyCode)
{
List<KeyMapping> matchingBindings = new ArrayList<KeyMapping>();
@@ -67,7 +78,11 @@ public class KeyMappingLookup
Collection<KeyMapping> bindings = bindingsMap.get(keyCode);
if (bindings != null)
{
matchingBindings.addAll(bindings);
for (KeyMapping binding : bindings)
{
if (binding.isConflictContextAndModifierActive())
matchingBindings.add(binding);
}
}
}
return matchingBindings;
@@ -30,6 +30,8 @@ public class ForgeConfig {
public final ConfigValue<String> permissionHandler;
public final BooleanValue advertiseDedicatedServerToLan;
Server(ForgeConfigSpec.Builder builder) {
builder.comment("Server configuration settings")
.push("server");
@@ -69,6 +71,11 @@ public class ForgeConfig {
.translation("forge.configgui.permissionHandler")
.define("permissionHandler", "forge:default_handler");
advertiseDedicatedServerToLan = builder
.comment("Set this to true to enable advertising the dedicated server to local LAN clients so that it shows up in the Multiplayer screen automatically.")
.translation("forge.configgui.advertiseDedicatedServerToLan")
.define("advertiseDedicatedServerToLan", true);
builder.pop();
}
}
@@ -123,6 +130,7 @@ public class ForgeConfig {
public final BooleanValue useCombinedDepthStencilAttachment;
@Deprecated(since = "1.20.1", forRemoval = true) // Config option ignored.
public final BooleanValue compressLanIPv6Addresses;
Client(ForgeConfigSpec.Builder builder) {
@@ -152,7 +160,7 @@ public class ForgeConfig {
.define("useCombinedDepthStencilAttachment", false);
compressLanIPv6Addresses = builder
.comment("When enabled, Forge will convert discovered 'Open to LAN' IPv6 addresses to their more compact, compressed representation")
.comment("[DEPRECATED] [NOW IN COMMON CONFIG] When enabled, Forge will convert discovered 'Open to LAN' IPv6 addresses to their more compact, compressed representation")
.translation("forge.configgui.compressLanIPv6Addresses")
.define("compressLanIPv6Addresses", true);
@@ -10,6 +10,7 @@ import net.minecraftforge.client.ClientCommandHandler;
import net.minecraftforge.eventbus.api.BusBuilder;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.fml.loading.FMLEnvironment;
import net.minecraftforge.network.DualStackUtils;
import net.minecraftforge.versions.forge.ForgeVersion;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -42,6 +43,7 @@ public class MinecraftForge
UsernameCache.load();
TierSortingRegistry.init();
if (FMLEnvironment.dist == Dist.CLIENT) ClientCommandHandler.init();
DualStackUtils.initialise();
}
/*
@@ -0,0 +1,238 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.common.data;
import com.google.common.base.Preconditions;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import net.minecraft.Util;
import net.minecraft.client.particle.ParticleEngine;
import net.minecraft.client.particle.ParticleProvider;
import net.minecraft.core.particles.ParticleType;
import net.minecraft.data.CachedOutput;
import net.minecraft.data.DataProvider;
import net.minecraft.data.PackOutput;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.PackType;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.registries.ForgeRegistries;
import org.jetbrains.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;
/**
* A data provider for {@link net.minecraft.client.particle.ParticleDescription}s.
*
* <p>To use this provider, extend this class and implement {@link #addDescriptions()}.
* Then, register an instance using {@link net.minecraft.data.DataGenerator#addProvider(boolean, Factory)}
* via the {@link net.minecraftforge.data.event.GatherDataEvent} on the {@linkplain FMLJavaModLoadingContext#getModEventBus() mod event bus}.
*
* <p>A description can be added to a {@link ParticleType} which uses a {@linkplain #sprite(ParticleType, ResourceLocation) sprite}
* or {@linkplain #spriteSet(ParticleType, Iterable) sprite set}.
*
* <pre>{@code
* @Override
* protected void addDescriptions() {
* // Single sprite
* this.sprite(ParticleTypes.DRIPPING_LAVA, new ResourceLocation("drip_hang"));
*
* // Multiple sprites
* this.spriteSet(ParticleTypes.CLOUD, new ResourceLocation("generic"), 8, true);
* }
* }</pre>
*
* <p>A particle description holds a list of textures used when rendering the
* particle to the screen. All registered particle descriptions are stitched
* together into a texture atlas called {@link net.minecraft.client.renderer.texture.TextureAtlas#LOCATION_PARTICLES}.
* A {@link ParticleType} whose particle uses the texture atlas, typically via
* the {@link net.minecraft.client.particle.ParticleRenderType}, can then reference
* the necessary texture during rendering.
*
* <p>Particles with a particle description must have their particle providers
* attached to a {@link ParticleType} as a {@linkplain net.minecraftforge.client.event.RegisterParticleProvidersEvent#registerSprite(ParticleType, ParticleProvider.Sprite) sprite}
* or {@linkplain net.minecraftforge.client.event.RegisterParticleProvidersEvent#registerSpriteSet(ParticleType, ParticleEngine.SpriteParticleRegistration) sprite set}
* consumer.
*
* @see DataProvider
* @see net.minecraft.client.particle.ParticleDescription
*/
public abstract class ParticleDescriptionProvider implements DataProvider {
private final PackOutput.PathProvider particlesPath;
@VisibleForTesting
protected final ExistingFileHelper fileHelper;
@VisibleForTesting
protected final Map<ResourceLocation, List<String>> descriptions;
/**
* Creates an instance of the data provider.
*
* @param output the expected root directory the data generator outputs to
* @param fileHelper the helper used to validate a texture's existence
*/
protected ParticleDescriptionProvider(PackOutput output, ExistingFileHelper fileHelper) {
this.particlesPath = output.createPathProvider(PackOutput.Target.RESOURCE_PACK, "particles");
this.fileHelper = fileHelper;
this.descriptions = new HashMap<>();
}
/**
* Registers the particle descriptions generated by {@link #sprite(ParticleType, ResourceLocation)}
* or one of the {@link #spriteSet(ParticleType, Iterable) sprite set methods}.
*/
protected abstract void addDescriptions();
/**
* Creates a new particle description that contains a single texture for the
* associated {@link ParticleType}.
*
* <p>Particle types with this description should be attached to a particle provider
* via {@link net.minecraftforge.client.event.RegisterParticleProvidersEvent#registerSprite(ParticleType, ParticleProvider.Sprite)}.
*
* @param type the particle type the textures are applied
* for
* @param texture the texture to render for the particle type
* @throws NullPointerException if the particle type is not registered
* @throws IllegalArgumentException if a texture does not have an associated PNG
* file, or the particle type has already been
* provided
*/
protected void sprite(ParticleType<?> type, ResourceLocation texture) {
this.spriteSet(type, texture);
}
/**
* Creates a new particle description that contains multiple textures for the
* associated {@link ParticleType}. The textures are generated from a common
* name and appended with a number representing the state of the animation.
*
* <pre>{@code
* minecraft:generic_0
* minecraft:generic_1
* minecraft:generic_2
* // ...
* }</pre>
*
* <p>Particle types with this description should be attached to a particle provider
* via {@link net.minecraftforge.client.event.RegisterParticleProvidersEvent#registerSpriteSet(ParticleType, ParticleEngine.SpriteParticleRegistration)}.
*
* @param type the particle type the textures are applied
* for
* @param baseName the common name of all the textures
* @param numOfTextures the number of textures within the set
* @param reverse when {@code true}, the textures will be
* listed in descending order
* @throws NullPointerException if the particle type is not registered
* @throws IllegalArgumentException if a texture does not have an associated PNG
* file, or the particle type has already been
* provided
*/
protected void spriteSet(ParticleType<?> type, ResourceLocation baseName, int numOfTextures, boolean reverse) {
Preconditions.checkArgument(numOfTextures > 0, "The number of textures to generate must be positive");
this.spriteSet(type, () -> new Iterator<>() {
private int counter = 0;
@Override
public boolean hasNext() {
return this.counter < numOfTextures;
}
@Override
public ResourceLocation next() {
var texture = baseName.withSuffix("_" + (reverse ? numOfTextures - this.counter - 1 : this.counter));
this.counter++;
return texture;
}
});
}
/**
* Creates a new particle description that contains multiple textures for the
* associated {@link ParticleType}. The textures are passed as varargs with
* at least one texture present.
*
* <p>Particle types with this description should be attached to a particle provider
* via {@link net.minecraftforge.client.event.RegisterParticleProvidersEvent#registerSpriteSet(ParticleType, ParticleEngine.SpriteParticleRegistration)}.
*
* @param type the particle type the textures are applied
* for
* @param texture the first texture in the description
* @param textures a list of subsequent textures to render for
* the particle type
* @throws NullPointerException if the particle type is not registered
* @throws IllegalArgumentException if a texture does not have an associated PNG
* file, or the particle type has already been
* provided
*/
protected void spriteSet(ParticleType<?> type, ResourceLocation texture, ResourceLocation... textures) {
this.spriteSet(type, Stream.concat(Stream.of(texture), Arrays.stream(textures))::iterator);
}
/**
* Creates a new particle description that contains multiple textures for the
* associated {@link ParticleType}. The textures are passed as an iterable.
*
* <p>Particle types with this description should be attached to a particle provider
* via {@link net.minecraftforge.client.event.RegisterParticleProvidersEvent#registerSpriteSet(ParticleType, ParticleEngine.SpriteParticleRegistration)}.
*
* @param type the particle type the textures are applied
* for
* @param textures a list of textures to render for the
* particle type
* @throws NullPointerException if the particle type is not registered
* @throws IllegalArgumentException if there are no textures provided, a texture
* does not have an associated PNG file, or
* the particle type has already been provided
*/
protected void spriteSet(ParticleType<?> type, Iterable<ResourceLocation> textures) {
// Make sure particle type is registered
var particle = Preconditions.checkNotNull(ForgeRegistries.PARTICLE_TYPES.getKey(type), "The particle type is not registered");
// Validate textures
List<String> desc = new ArrayList<>();
for (var texture : textures) {
Preconditions.checkArgument(this.fileHelper.exists(texture, PackType.CLIENT_RESOURCES, ".png", "textures/particle"),
"Texture '%s' does not exist in any known resource pack", texture);
desc.add(texture.toString());
}
Preconditions.checkArgument(desc.size() > 0, "The particle type '%s' must have one texture", particle);
// Insert into map
if (this.descriptions.putIfAbsent(particle, desc) != null) {
throw new IllegalArgumentException(String.format("The particle type '%s' already has a description associated with it", particle));
}
}
@Override
public CompletableFuture<?> run(CachedOutput cache) {
this.addDescriptions();
return CompletableFuture.allOf(
this.descriptions.entrySet().stream().map(entry -> {
// Map entries to the description format
var textures = new JsonArray();
entry.getValue().forEach(textures::add);
return DataProvider.saveStable(cache,
Util.make(new JsonObject(), obj -> obj.add("textures", textures)),
this.particlesPath.json(entry.getKey())
);
}).toArray(CompletableFuture[]::new)
);
}
@Override
public String getName() {
return "Particle Descriptions";
}
}
@@ -5,6 +5,7 @@
package net.minecraftforge.common.extensions;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.vehicle.Boat;
import net.minecraft.world.level.material.FluidState;
import net.minecraftforge.fluids.FluidType;
@@ -37,4 +38,17 @@ public interface IForgeBoat
{
return type.supportsBoating(self());
}
/**
* When {@code false}, the fluid will no longer update its height value while
* within a boat while it is not within a fluid ({@link Boat#isUnderWater()}.
*
* @param state the state of the fluid the rider is within
* @param rider the rider of the boat
* @return {@code true} if the fluid height should be updated, {@code false} otherwise
*/
default boolean shouldUpdateFluidWhileRiding(FluidState state, Entity rider)
{
return state.shouldUpdateWhileBoating(self(), rider);
}
}
@@ -13,6 +13,7 @@ import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
import net.minecraft.world.entity.boss.enderdragon.EnderDragon;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.vehicle.Boat;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.entity.Entity;
@@ -424,4 +425,17 @@ public interface IForgeEntity extends ICapabilitySerializable<CompoundTag>
{
return false;
}
/**
* When {@code false}, the fluid will no longer update its height value while
* within a boat while it is not within a fluid ({@link Boat#isUnderWater()}.
*
* @param state the state of the fluid the rider is within
* @param boat the boat the rider is within that is not inside a fluid
* @return {@code true} if the fluid height should be updated, {@code false} otherwise
*/
default boolean shouldUpdateFluidWhileBoating(FluidState state, Boat boat)
{
return boat.shouldUpdateFluidWhileRiding(state, self());
}
}
@@ -6,6 +6,7 @@
package net.minecraftforge.common.extensions;
import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.vehicle.Boat;
@@ -94,6 +95,20 @@ public interface IForgeFluid
return getFluidType().supportsBoating(state, boat);
}
/**
* When {@code false}, the fluid will no longer update its height value while
* within a boat while it is not within a fluid ({@link Boat#isUnderWater()}.
*
* @param state the state of the fluid the rider is within
* @param boat the boat the rider is within that is not inside a fluid
* @param rider the rider of the boat
* @return {@code true} if the fluid height should be updated, {@code false} otherwise
*/
default boolean shouldUpdateWhileBoating(FluidState state, Boat boat, Entity rider)
{
return getFluidType().shouldUpdateWhileBoating(state, boat, rider);
}
/**
* Gets the path type of this fluid when an entity is pathfinding. When
* {@code null}, uses vanilla behavior.
@@ -6,6 +6,7 @@
package net.minecraftforge.common.extensions;
import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.vehicle.Boat;
@@ -88,6 +89,19 @@ public interface IForgeFluidState
return self().getType().supportsBoating(self(), boat);
}
/**
* When {@code false}, the fluid will no longer update its height value while
* within a boat while it is not within a fluid ({@link Boat#isUnderWater()}.
*
* @param boat the boat the rider is within that is not inside a fluid
* @param rider the rider of the boat
* @return {@code true} if the fluid height should be updated, {@code false} otherwise
*/
default boolean shouldUpdateWhileBoating(Boat boat, Entity rider)
{
return self().getType().shouldUpdateWhileBoating(self(), boat, rider);
}
/**
* Gets the path type of this fluid when an entity is pathfinding. When
* {@code null}, uses vanilla behavior.
@@ -0,0 +1,58 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.energy;
/**
* Implementation of {@link IEnergyStorage} that cannot store, receive, or provide energy.
* Use the {@link #INSTANCE}, don't instantiate. Example:
* <pre>{@code
* ItemStack stack = ...;
* IEnergyStorage storage = stack.getCapability(ForgeCapabilities.ENERGY).orElse(EmptyEnergyStorage.INSTANCE);
* // Use storage without checking whether it's present.
* }</pre>
*/
public class EmptyEnergyStorage implements IEnergyStorage
{
public static final EmptyEnergyStorage INSTANCE = new EmptyEnergyStorage();
protected EmptyEnergyStorage() {}
@Override
public int receiveEnergy(int maxReceive, boolean simulate)
{
return 0;
}
@Override
public int extractEnergy(int maxExtract, boolean simulate)
{
return 0;
}
@Override
public int getEnergyStored()
{
return 0;
}
@Override
public int getMaxEnergyStored()
{
return 0;
}
@Override
public boolean canExtract()
{
return false;
}
@Override
public boolean canReceive()
{
return false;
}
}
@@ -29,6 +29,7 @@ import net.minecraft.world.DifficultyInstance;
import net.minecraft.world.entity.MobCategory;
import net.minecraft.world.entity.projectile.Projectile;
import net.minecraft.world.entity.projectile.ThrownEnderpearl;
import net.minecraft.world.level.LevelSimulatedReader;
import net.minecraft.world.level.biome.MobSpawnSettings;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
@@ -125,6 +126,7 @@ import net.minecraftforge.event.entity.player.PlayerWakeUpEvent;
import net.minecraftforge.event.entity.player.SleepingLocationCheckEvent;
import net.minecraftforge.event.entity.player.SleepingTimeCheckEvent;
import net.minecraftforge.event.furnace.FurnaceFuelBurnTimeEvent;
import net.minecraftforge.event.level.AlterGroundEvent;
import net.minecraftforge.event.level.BlockEvent;
import net.minecraftforge.event.level.BlockEvent.BlockToolModificationEvent;
import net.minecraftforge.event.level.BlockEvent.CreateFluidSourceEvent;
@@ -680,6 +682,13 @@ public class ForgeEventFactory
return event;
}
public static BlockState alterGround(LevelSimulatedReader level, RandomSource random, BlockPos pos, BlockState altered)
{
AlterGroundEvent event = new AlterGroundEvent(level, random, pos, altered);
MinecraftForge.EVENT_BUS.post(event);
return event.getNewAlteredState();
}
public static void fireChunkTicketLevelUpdated(ServerLevel level, long chunkPos, int oldTicketLevel, int newTicketLevel, @Nullable ChunkHolder chunkHolder)
{
if (oldTicketLevel != newTicketLevel)
@@ -0,0 +1,79 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.event.level;
import net.minecraft.core.BlockPos;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.LevelSimulatedReader;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.levelgen.feature.treedecorators.TreeDecorator;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.Cancelable;
import net.minecraftforge.eventbus.api.Event;
import org.jetbrains.annotations.ApiStatus;
/**
* This event is fired when {@link net.minecraft.world.level.levelgen.feature.treedecorators.AlterGroundDecorator#placeBlockAt(TreeDecorator.Context, BlockPos)}
* attempts to alter a ground block when generating a feature. An example of this would be large spruce trees converting grass blocks into podzol.
* <p>
* This event is not {@linkplain Cancelable cancellable}.
* <p>
* This event is fired on the {@linkplain MinecraftForge#EVENT_BUS main Forge event bus}
* only on the {@linkplain net.minecraftforge.fml.LogicalSide#SERVER logical server}.
*/
public class AlterGroundEvent extends Event {
private final LevelSimulatedReader level;
private final RandomSource random;
private final BlockPos pos;
private final BlockState originalAltered;
private BlockState newAltered;
@ApiStatus.Internal
public AlterGroundEvent(LevelSimulatedReader level, RandomSource random, BlockPos pos, BlockState altered) {
super();
this.level = level;
this.random = random;
this.pos = pos;
this.originalAltered = altered;
this.newAltered = altered;
}
public LevelSimulatedReader getLevel() {
return this.level;
}
public RandomSource getRandom() {
return this.random;
}
/**
* {@return the position of the block that will be altered}
*/
public BlockPos getPos() {
return this.pos;
}
/**
* {@return the original block state that would be placed by the ground decorator}
*/
public BlockState getOriginalAlteredState() {
return this.originalAltered;
}
/**
* {@return the new block state to be placed by the ground decorator}
*/
public BlockState getNewAlteredState() {
return this.newAltered;
}
/**
* @param newAltered the new block state to be placed by the ground decorator
*/
public void setNewAlteredState(BlockState newAltered) {
this.newAltered = newAltered;
}
}
@@ -355,6 +355,20 @@ public class FluidType
return this.supportsBoating(boat);
}
/**
* When {@code false}, the fluid will no longer update its height value while
* within a boat while it is not within a fluid ({@link Boat#isUnderWater()}.
*
* @param state the state of the fluid the rider is within
* @param boat the boat the rider is within that is not inside a fluid
* @param rider the rider of the boat
* @return {@code true} if the fluid height should be updated, {@code false} otherwise
*/
public boolean shouldUpdateWhileBoating(FluidState state, Boat boat, Entity rider)
{
return !this.supportsBoating(state, boat);
}
/**
* Returns whether the entity can ride in this vehicle under the fluid.
*
@@ -147,7 +147,9 @@ public class FluidUtil
}
else
{
containerFluidHandler.fill(simulatedTransfer, IFluidHandler.FluidAction.SIMULATE);
// We are acting on a COPY of the stack, so performing changes on the source is acceptable even if we are simulating.
// We need to perform the change otherwise the call to getContainer() will be incorrect.
containerFluidHandler.fill(simulatedTransfer, IFluidHandler.FluidAction.EXECUTE);
}
ItemStack resultContainer = containerFluidHandler.getContainer();
@@ -15,8 +15,6 @@ import net.minecraftforge.fluids.FluidType;
import net.minecraftforge.fluids.FluidUtil;
import net.minecraftforge.fluids.capability.templates.VoidFluidHandler;
import net.minecraftforge.fluids.capability.IFluidHandler.FluidAction;
/**
* Wrapper around any block, only accounts for fluid placement, otherwise the block acts a void.
* If the block in question inherits from the Forge implementations,
@@ -73,11 +71,11 @@ public class BlockWrapper extends VoidFluidHandler
BlockState state = world.getBlockState(blockPos);
if (liquidContainer.canPlaceLiquid(world, blockPos, state, resource.getFluid()))
{
//If we are executing try to actually fill the container, if it failed return that we failed
if (action.simulate() || liquidContainer.placeLiquid(world, blockPos, state, resource.getFluid().getFluidType().getStateForPlacement(world, blockPos, resource)))
if (action.execute())
{
return FluidType.BUCKET_VOLUME;
liquidContainer.placeLiquid(world, blockPos, state, resource.getFluid().getFluidType().getStateForPlacement(world, blockPos, resource));
}
return FluidType.BUCKET_VOLUME;
}
}
return 0;
@@ -5,12 +5,21 @@
package net.minecraftforge.network;
import com.mojang.logging.LogUtils;
import com.google.common.net.InetAddresses;
import net.minecraft.client.multiplayer.resolver.ResolvedServerAddress;
import net.minecraft.client.multiplayer.resolver.ServerAddress;
import net.minecraft.client.multiplayer.resolver.ServerNameResolver;
import net.minecraft.util.HttpUtil;
import org.jetbrains.annotations.ApiStatus;
import org.slf4j.Logger;
import javax.annotation.Nullable;
import java.net.Inet4Address;
import net.minecraftforge.common.ForgeConfig;
import javax.annotation.Nullable;
import java.net.SocketAddress;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
@@ -19,6 +28,19 @@ import java.util.Optional;
public class DualStackUtils
{
private static final String INITIAL_PREFER_IPv4_STACK = System.getProperty("java.net.preferIPv4Stack") == null ? "false" : System.getProperty("java.net.preferIPv4Stack");
private static final String INITIAL_PREFER_IPv6_ADDRESSES = System.getProperty("java.net.preferIPv6Addresses") == null ? "false" : System.getProperty("java.net.preferIPv6Addresses");
private static final Logger LOGGER = LogUtils.getLogger();
/**
* Called by {@link net.minecraftforge.common.MinecraftForge} to load this class so that the initial network
* property constants are set before any of the other methods in this class are called. This is so we can
* distinguish what Java's read once on JVM start vs what we've set for Netty.
*/
@ApiStatus.Internal
public static void initialise() {}
/**
* Resolve the address and see if Java and the OS return an IPv6 or IPv4 one, then let Netty know
* accordingly (it doesn't understand the {@code java.net.preferIPv6Addresses=system} property).
@@ -45,14 +67,49 @@ public class DualStackUtils
*/
public static boolean checkIPv6(final InetAddress inetAddress)
{
if (inetAddress instanceof Inet6Address)
// only log debug messages if we're not in the server pinger thread, as otherwise it's unclear which IP
// corresponds to which server as soon as you have more than one server in the multiplayer server list
final String currentThreadName = Thread.currentThread().getName();
final boolean shouldLogDebug = !currentThreadName.contains("Server Pinger #");
if (inetAddress instanceof Inet6Address addr)
{
if (shouldLogDebug)
LOGGER.debug("Detected IPv6 address: \"" + addr.getHostAddress() + "\"");
System.setProperty("java.net.preferIPv4Stack", "false");
System.setProperty("java.net.preferIPv6Addresses", "true");
return true;
}
else if (inetAddress instanceof Inet4Address addr)
{
if (shouldLogDebug)
LOGGER.debug("Detected IPv4 address: \"" + addr.getHostAddress() + "\"");
System.setProperty("java.net.preferIPv4Stack", "true");
System.setProperty("java.net.preferIPv6Addresses", "false");
return false;
}
else
{
if (shouldLogDebug) {
final String addr = inetAddress == null ? "null" : "\"" + inetAddress.getHostAddress() + "\"";
LOGGER.debug("Unable to determine IP version of address: " + addr);
}
if (INITIAL_PREFER_IPv4_STACK.equalsIgnoreCase("false") && INITIAL_PREFER_IPv6_ADDRESSES.equalsIgnoreCase("true"))
{
if (shouldLogDebug)
LOGGER.debug("Assuming IPv6 as Java was explicitly told to prefer it...");
System.setProperty("java.net.preferIPv4Stack", "false");
System.setProperty("java.net.preferIPv6Addresses", "true");
return true;
}
if (shouldLogDebug)
LOGGER.debug("Assuming IPv4...");
System.setProperty("java.net.preferIPv4Stack", "true");
System.setProperty("java.net.preferIPv6Addresses", "false");
return false;
@@ -81,8 +138,43 @@ public class DualStackUtils
}
}
/**
* Used for the "Open to LAN" feature.
* @return The multicast group to use for LAN discovery - IPv6 if available, IPv4 otherwise.
*/
public static String getMulticastGroup() {
if (checkIPv6(getLocalAddress())) return "FF75:230::60";
else return "224.0.2.60";
}
/**
* Logs the initial values of the {@code java.net.preferIPv4Stack} and {@code java.net.preferIPv6Addresses} system
* properties that Java has read on JVM start. Useful for debugging hostname lookup failures.
*/
public static void logInitialPreferences() {
LOGGER.debug("Initial IPv4 stack preference: " + INITIAL_PREFER_IPv4_STACK);
LOGGER.debug("Initial IPv6 addresses preference: " + INITIAL_PREFER_IPv6_ADDRESSES);
}
/**
* {@link SocketAddress#toString()} but with IPv6 address compression support
*/
public static String getAddressString(final SocketAddress address) {
if (address instanceof final InetSocketAddress inetAddress) {
String formatted;
if (inetAddress.isUnresolved()) {
formatted = inetAddress.getHostName() + "/<unresolved>";
} else {
formatted = InetAddresses.toAddrString(inetAddress.getAddress());
if (inetAddress.getAddress() instanceof Inet6Address)
formatted = '[' + formatted + ']';
formatted = '/' + formatted;
}
return formatted + ':' + inetAddress.getPort();
}
return address.toString();
}
}
@@ -103,7 +103,7 @@ public class DelegatingPackResources extends AbstractPackResources
{
IoSupplier<InputStream> ioSupplier = pack.getResource(type, location);
if (ioSupplier != null)
return pack.getResource(type, location);
return ioSupplier;
}
return null;
+3 -1
View File
@@ -4,4 +4,6 @@
# checkAndFixSAS will populate all overrides as nessasary. Prefixing added lines with \t
# checkAndFixSAS also supports names using . so simplest way to find the correct line to add is to get the AT line from the bot
# and remove the access modifier.
#==================================================================================================================================
# You need to run the setup task again after running checkAndFixSAS for genPatches to pick up the changes.
#==================================================================================================================================
net/minecraft/client/server/LanServerPinger
@@ -0,0 +1,27 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.debug;
import net.minecraft.world.level.block.Blocks;
import net.minecraftforge.event.level.AlterGroundEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
@Mod("alter_ground_event_test")
@Mod.EventBusSubscriber
public class AlterGroundEventTest {
public static final boolean ENABLE = true;
@SubscribeEvent
public static void onAlterGround(AlterGroundEvent event)
{
if (ENABLE) {
if (event.getOriginalAlteredState().is(Blocks.PODZOL)) {
event.setNewAlteredState(Blocks.REDSTONE_BLOCK.defaultBlockState());
}
}
}
}
@@ -15,6 +15,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -34,6 +35,7 @@ import net.minecraft.DetectedVersion;
import net.minecraft.Util;
import net.minecraft.advancements.critereon.InventoryChangeTrigger;
import net.minecraft.core.*;
import net.minecraft.core.particles.ParticleTypes;
import net.minecraft.core.registries.Registries;
import net.minecraft.data.CachedOutput;
import net.minecraft.data.PackOutput;
@@ -169,6 +171,7 @@ public class DataGeneratorTest
gen.addProvider(event.includeClient(), itemModels);
gen.addProvider(event.includeClient(), new BlockStates(packOutput, itemModels.existingFileHelper));
gen.addProvider(event.includeClient(), new SoundDefinitions(packOutput, event.getExistingFileHelper()));
gen.addProvider(event.includeClient(), new ParticleDescriptions(packOutput, event.getExistingFileHelper()));
gen.addProvider(event.includeServer(), new Recipes(packOutput));
gen.addProvider(event.includeServer(), new Tags(packOutput, lookupProvider, event.getExistingFileHelper()));
@@ -1120,6 +1123,80 @@ public class DataGeneratorTest
}
}
private static class ParticleDescriptions extends ParticleDescriptionProvider {
public ParticleDescriptions(PackOutput output, ExistingFileHelper fileHelper) {
super(output, fileHelper);
}
@Override
protected void addDescriptions() {
this.sprite(ParticleTypes.DRIPPING_LAVA, new ResourceLocation("drip_hang"));
this.spriteSet(ParticleTypes.CLOUD, new ResourceLocation("generic"), 8, true);
this.spriteSet(ParticleTypes.FISHING,
new ResourceLocation("splash_0"),
new ResourceLocation("splash_1"),
new ResourceLocation("splash_2"),
new ResourceLocation("splash_3")
);
this.spriteSet(ParticleTypes.ENCHANT, () -> new Iterator<>() {
private final ResourceLocation base = new ResourceLocation("sga");
private char suffix = 'a';
@Override
public boolean hasNext() {
return this.suffix <= 'z';
}
@Override
public ResourceLocation next() {
return this.base.withSuffix("_" + this.suffix++);
}
});
}
@Override
public CompletableFuture<?> run(CachedOutput cache) {
return super.run(cache).thenRun(this::validateResults);
}
private void validateResults() {
var errors = Stream.of(ParticleTypes.DRIPPING_LAVA, ParticleTypes.CLOUD, ParticleTypes.FISHING, ParticleTypes.ENCHANT)
.map(ForgeRegistries.PARTICLE_TYPES::getKey).map(particle -> {
try (var resource = this.fileHelper.getResource(particle, PackType.CLIENT_RESOURCES, ".json", "particles").openAsReader()) {
var existingTextures = GSON.fromJson(resource, JsonObject.class).get("textures").getAsJsonArray();
var generatedTextures = this.descriptions.get(particle);
// Check texture size
if (existingTextures.size() != generatedTextures.size()) {
LOGGER.error("%s had a different number of sprites, expected %s, actual %s", particle, existingTextures.size(), generatedTextures.size());
return particle;
}
boolean error = false;
for (int i = 0; i < generatedTextures.size(); ++i) {
if (!existingTextures.get(i).getAsString().equals(generatedTextures.get(i))) {
LOGGER.error("%s index %s: expected %s, actual %s", particle, i, existingTextures.get(i).getAsString(), generatedTextures.get(i));
error = true;
}
}
return error ? particle : null;
} catch (IOException e) {
throw new RuntimeException(e);
}
}).filter(Objects::nonNull).toList();
if (!errors.isEmpty()) {
throw new AssertionError(String.format("Validation errors found in %s; see above for details", errors.stream().reduce("", (str, rl) -> str + ", " + rl, (str1, str2) -> str1 + ", " + str2)));
}
}
}
private static <T extends ModelBuilder<T>> List<String> testModelResults(Map<ResourceLocation, T> models, ExistingFileHelper existingFileHelper, Set<ResourceLocation> toIgnore) {
List<String> ret = new ArrayList<>();
models.forEach((loc, model) -> {
@@ -38,6 +38,7 @@ public class FluidUtilTest
private static void runTests(FMLCommonSetupEvent commonSetupEvent)
{
test_tryEmptyContainer();
test_tryFillContainer();
test_tryEmptyContainerAndStow_stackable();
LogManager.getLogger().info("FluidUtilTest ok!");
@@ -70,6 +71,35 @@ public class FluidUtilTest
checkItemStack(sourceStack, Items.WATER_BUCKET, 2); // Apparently the stack is not supposed to be modified
}
/**
* Ensures that tryFillContainer doesn't change the target fluid handler when simulating.
* Ant that the result of the simulated transfver is valid.
* Regression test for the root cause of <a href="https://github.com/MinecraftForge/MinecraftForge/issues/6796">issue #6796</a>.
*/
private static void test_tryFillContainer()
{
var targetStack = new ItemStack(Items.BUCKET, 2);
var sourceTank = new FluidTank(10000);
sourceTank.setFluid(new FluidStack(Fluids.WATER, 5000));
// Simulate is not supposed to modify anything
var simulateResult = FluidUtil.tryFillContainer(targetStack, sourceTank, 1000, null, false);
if (!simulateResult.isSuccess())
throw new AssertionError("Failed to transfer.");
checkItemStack(simulateResult.getResult(), Items.WATER_BUCKET, 1);
// Tank and stack shouldn't be modified
checkItemStack(targetStack, Items.BUCKET, 2);
checkFluidStack(sourceTank.getFluid(), Fluids.WATER, 5000);
// Execute should modify
var executeResult = FluidUtil.tryFillContainer(targetStack, sourceTank, 1000, null, true);
if (!executeResult.isSuccess())
throw new AssertionError("Failed to transfer.");
checkItemStack(executeResult.getResult(), Items.WATER_BUCKET, 1);
checkFluidStack(sourceTank.getFluid(), Fluids.WATER, 4000);
checkItemStack(targetStack, Items.BUCKET, 2);
}
/**
* Ensures that tryEmptyContainerAndStow doesn't duplicate fluids in the target when the container is stackable.
* Regression test for <a href="https://github.com/MinecraftForge/MinecraftForge/issues/6796">issue #6796</a>.
@@ -0,0 +1,55 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.debug;
import com.mojang.blaze3d.platform.InputConstants;
import net.minecraft.client.KeyMapping;
import net.minecraft.client.Minecraft;
import net.minecraft.network.chat.Component;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.Items;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.event.RegisterKeyMappingsEvent;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.lifecycle.FMLConstructModEvent;
@Mod("keymapping_test")
public class KeyMappingTest
{
@Mod.EventBusSubscriber(modid = "keymapping_test", value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.MOD)
public static class ClientStuff {
// these are two separate keys to stand in for keys added by different
// mods that each do something similar with a held item from the
// respective mod, so the user wants them on the same physical key.
static KeyMapping stickKey = new KeyMapping("stick_key", InputConstants.KEY_BACKSLASH, KeyMapping.CATEGORY_MISC);
static KeyMapping rockKey = new KeyMapping("rock_key", InputConstants.KEY_BACKSLASH, KeyMapping.CATEGORY_MISC);
@SubscribeEvent
public static void init(FMLConstructModEvent event) {
MinecraftForge.EVENT_BUS.addListener(ClientStuff::tick);
}
@SubscribeEvent
public static void initKeys(RegisterKeyMappingsEvent event) {
event.register(stickKey);
event.register(rockKey);
}
public static void tick(TickEvent.ClientTickEvent event) {
if(event.phase != TickEvent.Phase.START) return;
if(stickKey.consumeClick()) {
Player player = Minecraft.getInstance().player;
if(player != null && player.getMainHandItem().is(Items.STICK))
player.sendSystemMessage(Component.literal("stick found!"));
}
if(rockKey.consumeClick()) {
Player player = Minecraft.getInstance().player;
if(player != null && player.getMainHandItem().is(Items.COBBLESTONE))
player.sendSystemMessage(Component.literal("rock found!"));
}
}
}
}
@@ -0,0 +1,135 @@
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
*/
package net.minecraftforge.debug.item;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.chat.Component;
import net.minecraft.world.InteractionHand;
import net.minecraft.world.InteractionResultHolder;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.item.CreativeModeTabs;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.ClipContext;
import net.minecraft.world.level.Level;
import net.minecraftforge.common.capabilities.ICapabilityProvider;
import net.minecraftforge.event.CreativeModeTabEvent;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.fluids.FluidActionResult;
import net.minecraftforge.fluids.FluidStack;
import net.minecraftforge.fluids.FluidType;
import net.minecraftforge.fluids.FluidUtil;
import net.minecraftforge.fluids.capability.templates.FluidHandlerItemStackSimple;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.registries.DeferredRegister;
import net.minecraftforge.registries.ForgeRegistries;
import net.minecraftforge.registries.RegistryObject;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicReference;
@Mod(CustomFluidContainerTest.MODID)
public class CustomFluidContainerTest
{
public static final String MODID = "custom_fluid_container_test";
public static final DeferredRegister<Item> ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, MODID);
public static final boolean ENABLED = true;
public static final RegistryObject<Item> CUSTOM_FLUID_CONTAINER = ITEMS.register("custom_fluid_container", () -> new CustomFluidContainer((new Item.Properties()).stacksTo(1)));
public CustomFluidContainerTest()
{
if (ENABLED)
{
IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus();
ITEMS.register(modEventBus);
modEventBus.addListener(this::addCreative);
}
}
private void addCreative(CreativeModeTabEvent.BuildContents event)
{
if (event.getTab() == CreativeModeTabs.INGREDIENTS)
event.accept(CUSTOM_FLUID_CONTAINER);
}
/**
* A custom fluid container item with a capacity of a vanilla bucket which uses the FluidUtil functionalities to pickup and place fluids.
*/
private static class CustomFluidContainer extends Item
{
public CustomFluidContainer(Properties properties)
{
super(properties);
}
@Override
@Nonnull
public Component getName(@Nonnull ItemStack itemStack) {
AtomicReference<String> name = new AtomicReference<>("Custom Fluid Container");
FluidUtil.getFluidHandler(itemStack).ifPresent(fluidHandler ->
{
FluidStack fluidStack = fluidHandler.getFluidInTank(0);
if (fluidStack.isEmpty())
{
name.set(name.get() + " (empty)");
}
else
{
name.set(name.get() + " (" + fluidStack.getFluid().getFluidType().getDescription().getString() + ")");
}
});
return Component.literal(name.get());
}
@Override
public InteractionResultHolder<ItemStack> use(Level level, Player player, InteractionHand hand)
{
var itemStack = player.getItemInHand(hand);
var result = new AtomicReference<FluidActionResult>();
FluidUtil.getFluidHandler(itemStack).ifPresent(fluidHandler ->
{
var fluidStack = fluidHandler.getFluidInTank(0);
if (fluidStack.isEmpty())
{
var blockHitResult = getPlayerPOVHitResult(level, player, ClipContext.Fluid.SOURCE_ONLY);
result.set(FluidUtil.tryPickUpFluid(itemStack, player, level, blockHitResult.getBlockPos(), blockHitResult.getDirection()));
}
else
{
var blockHitResult = getPlayerPOVHitResult(level, player, ClipContext.Fluid.NONE);
//try to place fluid in hit block (waterlogging, fill tank, ...). When no success try the block on the hit side.
for (BlockPos pos : Arrays.asList(blockHitResult.getBlockPos(), blockHitResult.getBlockPos().relative(blockHitResult.getDirection())))
{
result.set(FluidUtil.tryPlaceFluid(player, level, hand, pos, itemStack, fluidStack));
if (result.get().isSuccess())
{
break;
}
}
}
});
if (result.get() != null && result.get().isSuccess())
{
return InteractionResultHolder.sidedSuccess(result.get().getResult(), level.isClientSide());
}
return super.use(level, player, hand);
}
@Override
public ICapabilityProvider initCapabilities(@Nonnull ItemStack stack, @Nullable CompoundTag nbt)
{
return new FluidHandlerItemStackSimple(stack, FluidType.BUCKET_VOLUME);
}
}
}
+6
View File
@@ -293,6 +293,12 @@ modId="render_level_stages_test"
modId="trade_with_villager_event_test"
[[mods]]
modId="chunk_event_load_new_chunk_test"
[[mods]]
modId="alter_ground_event_test"
[[mods]]
modId="custom_fluid_container_test"
[[mods]]
modId="keymapping_test"
# ADD ABOVE THIS LINE
@@ -0,0 +1,6 @@
{
"parent": "item/generated",
"textures": {
"layer0": "item/bucket"
}
}