Skip to content
This repository has been archived by the owner on Feb 27, 2024. It is now read-only.

Commit

Permalink
Implement ChangeBlockEvent.Pre for modded liquids. Fixes #892.
Browse files Browse the repository at this point in the history
A little explanation about this addition. For starters, we now modify
the Forge added base classes for custom liquids, but as many know, we
cannot mix into other mod code for implementors of IFluidBlock that do
not extend BlockFluidBase/Classic/Finite etc. This remains fully
implemented for the cases of mod blocks that do extend those classes,
and not override the methods themselves. Due to the nature of how the
injections work however, a bug with Mixin's refmap generation was
discovered, leading to a required addition to the legacy-long-forgotten
extraSrg.srg file. It now contains a searge mapping for both
BlockFluidFinite and BlockFluidClassic #updateTick method since
previously, the refmap would point to
net/minecraft/block/Block;updateTick().

This has been minimally tested with some mod fluids and a sponge audit
to ensure that mixin application succeeds.

To keep up to date with the Mixin issue, it can be referenced here:
SpongePowered/Mixin#326. As soon as the issue is resolved, the
extraSrg.srg file can be removed from the project.

Signed-off-by: Gabriel Harris-Rouquette <[email protected]>
  • Loading branch information
gabizou committed Jun 5, 2019
1 parent c90a85d commit 0fb00f4
Show file tree
Hide file tree
Showing 7 changed files with 585 additions and 1 deletion.
2 changes: 1 addition & 1 deletion SpongeCommon
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,9 @@ reobf {
extraFiles 'extraSrg.srg'
}
}
mixin {
extraSrgFile("searge", file('extraSrg.srg'))
}

uploadArchives {
repositories {
Expand Down
2 changes: 2 additions & 0 deletions extraSrg.srg
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
MD: net/minecraft/server/management/PlayerList/initializeConnectionToPlayer (Lnet/minecraft/network/NetworkManager;Lnet/minecraft/entity/player/EntityPlayerMP;Lnet/minecraft/network/NetHandlerPlayServer;)V net/minecraft/server/management/PlayerList/func_72355_a (Lnet/minecraft/network/NetworkManager;Lnet/minecraft/entity/player/EntityPlayerMP;Lnet/minecraft/network/NetHandlerPlayServer;)V
MD: net/minecraftforge/fluids/BlockFluidClassic/updateTick (Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/state/IBlockState;Ljava/util/Random;)V net/minecraftforge/fluids/BlockFluidClassic/func_180650_b (Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/state/IBlockState;Ljava/util/Random;)V
MD: net/minecraftforge/fluids/BlockFluidFinite/updateTick (Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/state/IBlockState;Ljava/util/Random;)V net/minecraftforge/fluids/BlockFluidFinite/func_180650_b (Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/state/IBlockState;Ljava/util/Random;)V
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* This file is part of Sponge, licensed under the MIT License (MIT).
*
* Copyright (c) SpongePowered <https://www.spongepowered.org>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.spongepowered.mod.mixin.core.forge.fluids;

import net.minecraft.block.Block;
import net.minecraft.block.properties.PropertyInteger;
import net.minecraft.block.state.IBlockState;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockAccess;
import net.minecraftforge.fluids.BlockFluidBase;
import org.spongepowered.api.event.block.ChangeBlockEvent;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
import org.spongepowered.common.event.SpongeCommonEventFactory;
import org.spongepowered.common.interfaces.block.IMixinBlock;
import org.spongepowered.common.interfaces.world.IMixinWorld;
import org.spongepowered.common.interfaces.world.IMixinWorldServer;
import org.spongepowered.mod.mixin.core.block.MixinBlock;

import java.util.Map;

@Mixin(value = BlockFluidBase.class)
public abstract class MixinBlockFluidBase extends MixinBlock implements IMixinBlock {

@Shadow @Final public static PropertyInteger LEVEL;
@Shadow protected int tickRate;

@Redirect(method = "canDisplace",
remap = false,
at = @At(
value = "INVOKE",
target = "Ljava/util/Map;get(Ljava/lang/Object;)Ljava/lang/Object;",
remap = false
)
)
private Object getDisplacementWithSponge(Map map, Object key, IBlockAccess world, BlockPos pos) {
if (!(world instanceof IMixinWorld) || ((IMixinWorld) world).isFake()) {
return map.get(key);
}
if (!((Boolean) map.get(key))) {
return Boolean.FALSE;
}
ChangeBlockEvent.Pre event = SpongeCommonEventFactory.callChangeBlockEventPre((IMixinWorldServer) world, pos);
if (event.isCancelled()) {
return Boolean.FALSE;
}
return Boolean.TRUE;
}

@Inject(method = "canDisplace",
cancellable = true,
remap = false,
locals = LocalCapture.CAPTURE_FAILSOFT,
at = @At(
value = "INVOKE",
target = "Lnet/minecraftforge/fluids/BlockFluidBase;getDensity(Lnet/minecraft/world/IBlockAccess;Lnet/minecraft/util/math/BlockPos;)I",
remap = false
)
)
private void onSpongeInjectFailEvent(IBlockAccess world, BlockPos pos, CallbackInfoReturnable<Boolean> cir, IBlockState state, Block block) {
if (!(world instanceof IMixinWorld) || ((IMixinWorld) world).isFake()) {
return;
}
ChangeBlockEvent.Pre event = SpongeCommonEventFactory.callChangeBlockEventPre((IMixinWorldServer) world, pos);
if (event.isCancelled()) {
cir.setReturnValue(false);
}
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/*
* This file is part of Sponge, licensed under the MIT License (MIT).
*
* Copyright (c) SpongePowered <https://www.spongepowered.org>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.spongepowered.mod.mixin.core.forge.fluids;

import net.minecraft.block.state.IBlockState;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.IBlockAccess;
import net.minecraft.world.World;
import net.minecraftforge.common.util.Constants;
import net.minecraftforge.fluids.BlockFluidBase;
import net.minecraftforge.fluids.BlockFluidClassic;
import org.spongepowered.api.block.BlockSnapshot;
import org.spongepowered.api.data.Transaction;
import org.spongepowered.api.event.CauseStackManager;
import org.spongepowered.api.event.block.ChangeBlockEvent;
import org.spongepowered.api.event.cause.EventContextKeys;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.common.block.BlockUtil;
import org.spongepowered.common.event.ShouldFire;
import org.spongepowered.common.event.SpongeCommonEventFactory;
import org.spongepowered.common.event.tracking.IPhaseState;
import org.spongepowered.common.event.tracking.PhaseContext;
import org.spongepowered.common.event.tracking.PhaseTracker;
import org.spongepowered.common.interfaces.world.IMixinWorld;
import org.spongepowered.common.interfaces.world.IMixinWorldServer;

import java.util.Random;
import java.util.function.BiConsumer;

import javax.annotation.Nullable;

@Mixin(value = BlockFluidClassic.class)
public abstract class MixinBlockFluidClassic extends MixinBlockFluidBase {

@Override
public BiConsumer<CauseStackManager.StackFrame, IMixinWorldServer> getTickFrameModifier() {
return (frame, world) -> frame.addContext(EventContextKeys.LIQUID_FLOW, (org.spongepowered.api.world.World) world);
}

@Inject(method = "updateTick",
at = @At("HEAD"),
cancellable = true
)
private void onUpdateTickCheckSpongePre(World world, BlockPos pos, IBlockState state, Random rand, CallbackInfo ci) {
if (!((IMixinWorld) world).isFake() && ShouldFire.CHANGE_BLOCK_EVENT_PRE) {
if (SpongeCommonEventFactory.callChangeBlockEventPre((IMixinWorldServer) world, pos).isCancelled()) {
ci.cancel();
}
}
}

@Nullable private Boolean isPreCancelled;

/**
* @author gabizou - June 4th, 2019 - 1.12.2
* @reason In the event the injection above does cancel, we need to duck out of the rest of the method.
* Otherwise, the above injection never went through, the {@link BlockFluidBase#canDisplace(IBlockAccess, BlockPos)}
* ends up getting it's injection thrown anyways.
*
* @param world The world
* @param pos the position being flown into
* @param state The state
* @param rand The rando
* @param ci The callback info
*/
@Inject(
method = "updateTick",
at = @At(
value = "INVOKE",
target = "Lnet/minecraftforge/fluids/BlockFluidClassic;canDisplace(Lnet/minecraft/world/IBlockAccess;Lnet/minecraft/util/math/BlockPos;)Z",
remap = false
),
cancellable = true
)
private void onCheckDisplaceIfPreAlreadyCancelled(World world, BlockPos pos, IBlockState state, Random rand, CallbackInfo ci) {
if (this.isPreCancelled == Boolean.TRUE) {
ci.cancel();
// And reset the field so we don't leak...
this.isPreCancelled = null;
}
}

// Capture Fluids flowing into other blocks
@SuppressWarnings({"unchecked", "DefaultAnnotationParam"})
@Redirect(
method = "flowIntoBlock",
remap = false,
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/world/World;setBlockState(Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/state/IBlockState;)Z",
remap = true
)
)
private boolean afterCanFlowInto(World targetWorld, BlockPos targetPos, IBlockState newLiquidState, World world, BlockPos pos, int meta) {
if (((IMixinWorld) targetWorld).isFake() || !ShouldFire.CHANGE_BLOCK_EVENT_BREAK) { // Check if we even need to fire.
return world.setBlockState(targetPos, newLiquidState, Constants.BlockFlags.DEFAULT);
}

final IBlockState existing = targetWorld.getBlockState(targetPos);
// Do not call events when just flowing into air or same liquid
//noinspection RedundantCast
if (!existing.getBlock().isAir(existing, targetWorld, targetPos) && !(existing.getMaterial().isLiquid() || existing.getBlock() == (BlockFluidClassic) (Object) this)) {
ChangeBlockEvent.Break event = SpongeCommonEventFactory.callChangeBlockEventModifyLiquidBreak(world, pos, newLiquidState);

Transaction<BlockSnapshot> transaction = event.getTransactions().get(0);
if (event.isCancelled() || !transaction.isValid()) {
// We need to clear any drops here because this is called after {@link #displaceIfPossible} and since it was true, well
// there's the likelyhood that there's a dropped item
final PhaseContext<?> currentContext = PhaseTracker.getInstance().getCurrentContext();
((IPhaseState) currentContext.state).processCancelledTransaction(currentContext, transaction, transaction.getOriginal());
return false;
}

// Transaction modified?
if (transaction.getCustom().isPresent()) {
return targetWorld.setBlockState(targetPos, BlockUtil.toNative(transaction.getFinal().getState()), Constants.BlockFlags.DEFAULT);
}
}

return targetWorld.setBlockState(targetPos, newLiquidState, Constants.BlockFlags.DEFAULT);
}

}
Loading

0 comments on commit 0fb00f4

Please sign in to comment.