fix detached rifts being placed after door gets destroyed

change ExplosionMixin Overwrite to Inject
This commit is contained in:
CreepyCre 2021-06-18 18:16:09 +02:00
parent f8f8d00f98
commit e60e20bbd9
7 changed files with 205 additions and 136 deletions

View file

@ -0,0 +1,43 @@
package org.dimdev.dimdoors.api.block;
import net.minecraft.block.BlockState;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.entity.Entity;
import net.minecraft.fluid.FluidState;
import net.minecraft.util.Pair;
import net.minecraft.util.TypedActionResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import java.util.function.Consumer;
/**
* Only works in cases where {@link net.minecraft.block.AbstractBlock#getStateForNeighborUpdate AbstractBlock#getStateForNeighborUpdate} returns an air {@link BlockState}
*/
public interface CustomBreakBlock {
TypedActionResult<Pair<BlockState, Consumer<BlockEntity>>> customBreakBlock(World world, BlockPos pos, BlockState blockState, Entity breakingEntity);
/*
If this causes any issue mixin into FluidState instead.
Also remember to remove the access wideners.
*/
class HackyFluidState extends FluidState {
private final BlockState blockState;
private final Consumer<BlockEntity> blockEntityConsumer;
public HackyFluidState(BlockState blockState, Consumer<BlockEntity> blockEntityConsumer) {
super(blockState.getFluidState().getFluid(), blockState.getFluidState().getEntries(), blockState.getFluidState().codec);
this.blockState = blockState;
this.blockEntityConsumer = blockEntityConsumer;
}
@Override
public BlockState getBlockState() {
return blockState;
}
public Consumer<BlockEntity> getBlockEntityConsumer() {
return blockEntityConsumer;
}
}
}

View file

@ -0,0 +1,11 @@
package org.dimdev.dimdoors.api.block;
import net.minecraft.block.BlockState;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.util.ActionResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
public interface ExplosionConvertibleBlock {
ActionResult explode(World world, BlockPos pos, BlockState state, BlockEntity blockEntity);
}

View file

@ -2,16 +2,23 @@ package org.dimdev.dimdoors.block.door;
import net.fabricmc.fabric.api.event.player.PlayerBlockBreakEvents;
import net.minecraft.block.*;
import net.minecraft.block.piston.PistonBehavior;
import net.minecraft.util.Pair;
import net.minecraft.util.TypedActionResult;
import net.minecraft.world.explosion.Explosion;
import org.apache.logging.log4j.Level;
import org.dimdev.dimdoors.DimensionalDoorsInitializer;
import org.dimdev.dimdoors.api.block.CustomBreakBlock;
import org.dimdev.dimdoors.api.block.ExplosionConvertibleBlock;
import org.dimdev.dimdoors.api.util.math.MathUtil;
import org.dimdev.dimdoors.api.util.math.TransformationMatrix3d;
import org.dimdev.dimdoors.block.CoordinateTransformerBlock;
import org.dimdev.dimdoors.block.DetachedRiftBlock;
import org.dimdev.dimdoors.block.ModBlocks;
import org.dimdev.dimdoors.block.RiftProvider;
import org.dimdev.dimdoors.block.entity.DetachedRiftBlockEntity;
import org.dimdev.dimdoors.block.entity.EntranceRiftBlockEntity;
import org.dimdev.dimdoors.block.entity.RiftData;
import org.jetbrains.annotations.Nullable;
import net.minecraft.block.entity.BlockEntity;
@ -35,7 +42,9 @@ import net.minecraft.world.event.GameEvent;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
public class DimensionalDoorBlock extends WaterLoggableDoorBlock implements RiftProvider<EntranceRiftBlockEntity>, CoordinateTransformerBlock {
import java.util.function.Consumer;
public class DimensionalDoorBlock extends WaterLoggableDoorBlock implements RiftProvider<EntranceRiftBlockEntity>, CoordinateTransformerBlock, ExplosionConvertibleBlock, CustomBreakBlock {
public DimensionalDoorBlock(Settings settings) {
super(settings);
}
@ -99,6 +108,11 @@ public class DimensionalDoorBlock extends WaterLoggableDoorBlock implements Rift
createDetachedRift(world, pos, world.getBlockState(pos));
}
/*
TODO: rewrite so it can only be used from the lower door block.
I fear this method may be called twice otherwise.
~CreepyCre
*/
public void createDetachedRift(World world, BlockPos pos, BlockState state) {
DoubleBlockHalf doubleBlockHalf = state.get(HALF);
BlockPos blockPos = pos;
@ -224,4 +238,29 @@ public class DimensionalDoorBlock extends WaterLoggableDoorBlock implements Rift
}
});
}
@Override
public ActionResult explode(World world, BlockPos pos, BlockState state, BlockEntity blockEntity) {
if (blockEntity == null) {
return ActionResult.PASS;
}
createDetachedRift(world, pos, state);
return ActionResult.SUCCESS;
}
@Override
public PistonBehavior getPistonBehavior(BlockState state) {
return state.get(HALF) == DoubleBlockHalf.LOWER ? PistonBehavior.BLOCK : super.getPistonBehavior(state);
}
@Override
public TypedActionResult<Pair<BlockState, Consumer<BlockEntity>>> customBreakBlock(World world, BlockPos pos, BlockState blockState, Entity breakingEntity) {
if (blockState.get(HALF) != DoubleBlockHalf.LOWER) {
return TypedActionResult.pass(null);
}
RiftData data = ((EntranceRiftBlockEntity) world.getBlockEntity(pos)).getData();
return TypedActionResult.success(new Pair<>(ModBlocks.DETACHED_RIFT.getDefaultState().with(WATERLOGGED, blockState.get(WATERLOGGED)), blockEntity -> {
((EntranceRiftBlockEntity) blockEntity).setData(data);
}));
}
}

View file

@ -1,154 +1,39 @@
package org.dimdev.dimdoors.mixin;
import com.mojang.datafixers.util.Pair;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import it.unimi.dsi.fastutil.objects.ObjectListIterator;
import net.minecraft.block.AbstractFireBlock;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.entity.Entity;
import net.minecraft.entity.damage.DamageSource;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.loot.context.LootContext;
import net.minecraft.loot.context.LootContextParameters;
import net.minecraft.particle.ParticleTypes;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.sound.SoundCategory;
import net.minecraft.sound.SoundEvents;
import net.minecraft.util.ActionResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;
import net.minecraft.world.explosion.Explosion;
import net.minecraft.world.explosion.ExplosionBehavior;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dimdev.dimdoors.block.RiftProvider;
import org.dimdev.dimdoors.block.door.DimensionalDoorBlock;
import org.jetbrains.annotations.Nullable;
import org.dimdev.dimdoors.api.block.ExplosionConvertibleBlock;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Mutable;
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.callback.CallbackInfo;
import java.util.*;
import static org.dimdev.dimdoors.block.ModBlocks.DETACHED_RIFT;
import java.util.stream.Collectors;
@Mixin(Explosion.class)
public class ExplosionMixin {
private static final Logger LOGGER = LogManager.getLogger();
@Shadow
private static final int field_30960 = 16;
@Shadow
private boolean createFire;
@Shadow
private Explosion.DestructionType destructionType;
@Shadow
private Random random;
@Shadow
private World world;
@Shadow
private double x;
@Shadow
private double y;
@Shadow
private double z;
@Shadow
@Nullable
private Entity entity;
@Shadow
private float power;
@Shadow
private DamageSource damageSource;
@Shadow
private ExplosionBehavior behavior;
@Shadow
@Mutable @Shadow
private List<BlockPos> affectedBlocks;
@Shadow
private Map<PlayerEntity, Vec3d> affectedPlayers;
@Shadow
private static void tryMergeStack(ObjectArrayList<Pair<ItemStack, BlockPos>> stacks, ItemStack stack, BlockPos pos) {
}
/**
* @author - MalekiRe
*/
@Overwrite
public void affectWorld(boolean particles) {
if (this.world.isClient) {
this.world.playSound(this.x, this.y, this.z, SoundEvents.ENTITY_GENERIC_EXPLODE, SoundCategory.BLOCKS, 4.0F, (1.0F + (this.world.random.nextFloat() - this.world.random.nextFloat()) * 0.2F) * 0.7F, false);
}
boolean bl = this.destructionType != Explosion.DestructionType.NONE;
if (particles) {
if (!(this.power < 2.0F) && bl) {
this.world.addParticle(ParticleTypes.EXPLOSION_EMITTER, this.x, this.y, this.z, 1.0D, 0.0D, 0.0D);
} else {
this.world.addParticle(ParticleTypes.EXPLOSION, this.x, this.y, this.z, 1.0D, 0.0D, 0.0D);
@Inject(method = "affectWorld", at = @At(value = "INVOKE", target = "Ljava/util/Collections;shuffle(Ljava/util/List;Ljava/util/Random;)V", ordinal = 0, shift = At.Shift.AFTER))
private void handleExplosionConvertibleBlocks(boolean b1, CallbackInfo ci) {
this.affectedBlocks = this.affectedBlocks.stream().filter(blockPos -> {
BlockState state = this.world.getBlockState(blockPos);
Block block = state.getBlock();
if (!(block instanceof ExplosionConvertibleBlock)) {
return true;
}
}
if (bl) {
ObjectArrayList<Pair<ItemStack, BlockPos>> objectArrayList = new ObjectArrayList();
Collections.shuffle(this.affectedBlocks, this.world.random);
for (BlockPos blockPos : this.affectedBlocks) {
BlockState blockState = this.world.getBlockState(blockPos);
Block block = blockState.getBlock();
if (!blockState.isAir()) {
BlockPos blockPos2 = blockPos.toImmutable();
this.world.getProfiler().push("explosion_blocks");
if (block.shouldDropItemsOnExplosion((Explosion) (Object) this) && this.world instanceof ServerWorld) {
//TODO: Change this to work with trapdoors as well, when we implement trapdoors.
//This is the only changed code
if (block instanceof DimensionalDoorBlock) {
LOGGER.log(Level.INFO, "Creating Detached Rift From Explosion of Door");
((DimensionalDoorBlock) block).createDetachedRift(this.world, blockPos2);
continue;
}
else if(world.getBlockState(blockPos2).getBlock() == DETACHED_RIFT) {
continue;
}
//Normal code below
BlockEntity blockEntity = blockState.hasBlockEntity() ? this.world.getBlockEntity(blockPos) : null;
LootContext.Builder builder = (new LootContext.Builder((ServerWorld) this.world)).random(this.world.random).parameter(LootContextParameters.ORIGIN, Vec3d.ofCenter(blockPos)).parameter(LootContextParameters.TOOL, ItemStack.EMPTY).optionalParameter(LootContextParameters.BLOCK_ENTITY, blockEntity).optionalParameter(LootContextParameters.THIS_ENTITY, this.entity);
if (this.destructionType == Explosion.DestructionType.DESTROY) {
builder.parameter(LootContextParameters.EXPLOSION_RADIUS, this.power);
}
blockState.getDroppedStacks(builder).forEach((stack) -> {
tryMergeStack(objectArrayList, stack, blockPos2);
});
}
this.world.setBlockState(blockPos, Blocks.AIR.getDefaultState(), 3);
block.onDestroyedByExplosion(this.world, blockPos, (Explosion) (Object) this);
this.world.getProfiler().pop();
}
}
ObjectListIterator var12 = objectArrayList.iterator();
while(var12.hasNext()) {
Pair<ItemStack, BlockPos> pair = (Pair)var12.next();
Block.dropStack(this.world, (BlockPos)pair.getSecond(), (ItemStack)pair.getFirst());
}
}
if (this.createFire) {
Iterator var11 = this.affectedBlocks.iterator();
while(var11.hasNext()) {
BlockPos blockPos3 = (BlockPos)var11.next();
if (this.random.nextInt(3) == 0 && this.world.getBlockState(blockPos3).isAir() && this.world.getBlockState(blockPos3.down()).isOpaqueFullCube(this.world, blockPos3.down())) {
this.world.setBlockState(blockPos3, AbstractFireBlock.getState(this.world, blockPos3));
}
}
}
ActionResult result = ((ExplosionConvertibleBlock) block).explode(this.world, blockPos, state, state.hasBlockEntity() ? this.world.getBlockEntity(blockPos) : null);
return result == ActionResult.PASS;
}).collect(Collectors.toList());
}
}

View file

@ -0,0 +1,85 @@
package org.dimdev.dimdoors.mixin;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.entity.BlockEntity;
import net.minecraft.entity.Entity;
import net.minecraft.fluid.FluidState;
import net.minecraft.util.Pair;
import net.minecraft.util.TypedActionResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
import org.dimdev.dimdoors.api.block.CustomBreakBlock;
import org.jetbrains.annotations.Nullable;
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.ModifyVariable;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
import java.util.function.Consumer;
@Mixin(World.class)
public abstract class WorldMixin {
/*
I thought about redirecting the entire break method to be handled by the block itself,
but I am not quite sure what that would mean for compatibility with other mixins,
since then a large part of the method would need to be canceled. This is rather hacky, but it should fulfill the purpose best
~CreepyCre
*/
@ModifyVariable(method = "Lnet/minecraft/world/World;breakBlock(Lnet/minecraft/util/math/BlockPos;ZLnet/minecraft/entity/Entity;I)Z",
at = @At(value = "INVOKE_ASSIGN",
target = "Lnet/minecraft/world/World;getFluidState(Lnet/minecraft/util/math/BlockPos;)Lnet/minecraft/fluid/FluidState;",
ordinal = 0))
private FluidState replaceFluidStateWithCustomHackyFluidState(FluidState original, BlockPos pos, boolean drop, @Nullable Entity breakingEntity, int maxUpdateDepth) {
World world = (World) (Object) this;
BlockState blockState = world.getBlockState(pos);
Block block = blockState.getBlock();
if (!(block instanceof CustomBreakBlock)) {
return original;
}
TypedActionResult<Pair<BlockState, Consumer<BlockEntity>>> result = ((CustomBreakBlock) block).customBreakBlock(world, pos, blockState, breakingEntity);
if (!result.getResult().isAccepted()) {
return original;
}
Pair<BlockState, Consumer<BlockEntity>> pair = result.getValue();
return new CustomBreakBlock.HackyFluidState(pair.getLeft(), pair.getRight());
}
@Inject(method = "Lnet/minecraft/world/World;breakBlock(Lnet/minecraft/util/math/BlockPos;ZLnet/minecraft/entity/Entity;I)Z",
locals = LocalCapture.CAPTURE_FAILHARD,
at = @At(value = "INVOKE",
target = "Lnet/minecraft/world/World;setBlockState(Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;II)Z",
ordinal = 0))
private void applyBlockEntityModification(BlockPos pos, boolean drop, Entity breakingEntity, int maxUpdateDepth, CallbackInfoReturnable<Boolean> cir, BlockState blockState, FluidState fluidState) {
if (!(fluidState instanceof CustomBreakBlock.HackyFluidState)) {
return;
}
Consumer<BlockEntity> blockEntityConsumer = ((CustomBreakBlock.HackyFluidState) fluidState).getBlockEntityConsumer();
if (blockEntityConsumer == null) {
return;
}
BlockEntity blockEntity = ((World) (Object) this).getBlockEntity(pos);
if (blockEntity != null) {
blockEntityConsumer.accept(blockEntity);
}
}
/*
This is where I'd inject if it turns out the method used above does actually have an issue
*/
// @Inject(method = "Lnet/minecraft/world/World;breakBlock(Lnet/minecraft/util/math/BlockPos;ZLnet/minecraft/entity/Entity;I)Z",
// cancellable = true,
// at = @At(
// value = "INVOKE",
// target = "Lnet/minecraft/world/WorldAccess;syncWorldEvent(ILnet/minecraft/util/math/BlockPos;I)V",
// ordinal = 0,
// shift = At.Shift.BY,
// by = 2
// )
// )
}

View file

@ -10,3 +10,8 @@ accessible method net/minecraft/entity/Entity setRotation (FF)V
# for MutableBlockEntityType
extendable class net/minecraft/block/entity/BlockEntityType$BlockEntityFactory
# for HackyFluidState
extendable class net/minecraft/fluid/FluidState
accessible field net/minecraft/state/State codec Lcom/mojang/serialization/MapCodec;
#Lnet/minecraft/state/State;codec:Lcom/mojang/serialization/MapCodec;

View file

@ -15,6 +15,7 @@
"ServerPlayerEntityMixin",
"ServerPlayerInteractionManagerMixin",
"StructurePoolMixin",
"WorldMixin",
"accessor.BuiltinBiomesAccessor",
"accessor.ChunkGeneratorAccessor",
"accessor.DefaultParticleTypeAccessor",
@ -33,9 +34,9 @@
"client.ExtendedClientPlayNetworkHandlerMixin",
"client.GameRendererMixin",
"client.InGameHudMixin",
"client.PostProcessShaderMixin",
"client.WorldRendererMixin",
"client.accessor.RenderLayerAccessor",
"client.PostProcessShaderMixin"
"client.accessor.RenderLayerAccessor"
],
"injectors": {
"defaultRequire": 1