1
This commit is contained in:
1
_tmp_sdd_yarn_1
Submodule
1
_tmp_sdd_yarn_1
Submodule
Submodule _tmp_sdd_yarn_1 added at 49f5c0d139
@@ -1,10 +1,11 @@
|
|||||||
package com.straice;
|
package com.straice;
|
||||||
|
|
||||||
|
import com.straice.smoothdoors.client.anim.SddAnimator;
|
||||||
import net.fabricmc.api.ClientModInitializer;
|
import net.fabricmc.api.ClientModInitializer;
|
||||||
|
|
||||||
public class TemplateModClient implements ClientModInitializer {
|
public class TemplateModClient implements ClientModInitializer {
|
||||||
@Override
|
@Override
|
||||||
public void onInitializeClient() {
|
public void onInitializeClient() {
|
||||||
// This entrypoint is suitable for setting up client-specific logic, such as rendering.
|
SddAnimator.initClientHooks();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package com.straice.mixin.client;
|
||||||
|
|
||||||
|
import com.straice.smoothdoors.client.anim.SddAnimator;
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
|
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||||
|
import net.minecraft.client.renderer.block.ModelBlockRenderer;
|
||||||
|
import net.minecraft.client.resources.model.BakedModel;
|
||||||
|
import net.minecraft.util.RandomSource;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.world.level.BlockAndTintGetter;
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
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.callback.CallbackInfo;
|
||||||
|
|
||||||
|
@Mixin(ModelBlockRenderer.class)
|
||||||
|
public class BlockModelRendererMixin {
|
||||||
|
|
||||||
|
@Inject(
|
||||||
|
method = "tesselateBlock",
|
||||||
|
at = @At("HEAD"),
|
||||||
|
cancellable = true,
|
||||||
|
require = 0
|
||||||
|
)
|
||||||
|
private void sdd$tesselateBlock(BlockAndTintGetter world, BakedModel model, BlockState state, BlockPos pos,
|
||||||
|
PoseStack matrices, VertexConsumer vertices, boolean cull, RandomSource random,
|
||||||
|
long seed, int overlay, CallbackInfo ci) {
|
||||||
|
if (SddAnimator.shouldHideInChunk(pos, state)) {
|
||||||
|
ci.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(
|
||||||
|
method = "tesselateWithAO",
|
||||||
|
at = @At("HEAD"),
|
||||||
|
cancellable = true,
|
||||||
|
require = 0
|
||||||
|
)
|
||||||
|
private void sdd$tesselateWithAo(BlockAndTintGetter world, BakedModel model, BlockState state, BlockPos pos,
|
||||||
|
PoseStack matrices, VertexConsumer vertices, boolean cull, RandomSource random,
|
||||||
|
long seed, int overlay, CallbackInfo ci) {
|
||||||
|
if (SddAnimator.shouldHideInChunk(pos, state)) {
|
||||||
|
ci.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(
|
||||||
|
method = "tesselateWithoutAO",
|
||||||
|
at = @At("HEAD"),
|
||||||
|
cancellable = true,
|
||||||
|
require = 0
|
||||||
|
)
|
||||||
|
private void sdd$tesselateWithoutAo(BlockAndTintGetter world, BakedModel model, BlockState state, BlockPos pos,
|
||||||
|
PoseStack matrices, VertexConsumer vertices, boolean cull, RandomSource random,
|
||||||
|
long seed, int overlay, CallbackInfo ci) {
|
||||||
|
if (SddAnimator.shouldHideInChunk(pos, state)) {
|
||||||
|
ci.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.straice.mixin.client;
|
||||||
|
|
||||||
|
import com.straice.smoothdoors.client.anim.SddAnimator;
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
|
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||||
|
import net.minecraft.client.renderer.block.BlockRenderDispatcher;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.util.RandomSource;
|
||||||
|
import net.minecraft.world.level.BlockAndTintGetter;
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
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.callback.CallbackInfo;
|
||||||
|
|
||||||
|
@Mixin(BlockRenderDispatcher.class)
|
||||||
|
public class BlockRenderManagerMixin {
|
||||||
|
|
||||||
|
@Inject(
|
||||||
|
method = "renderBatched",
|
||||||
|
at = @At("HEAD"),
|
||||||
|
cancellable = true,
|
||||||
|
require = 0
|
||||||
|
)
|
||||||
|
private void sdd$renderBatched(BlockState state, BlockPos pos, BlockAndTintGetter world,
|
||||||
|
PoseStack matrices, VertexConsumer vertexConsumer,
|
||||||
|
boolean cull, RandomSource random,
|
||||||
|
CallbackInfo ci) {
|
||||||
|
if (SddAnimator.shouldHideInChunk(pos, state)) {
|
||||||
|
ci.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.straice.mixin.client;
|
||||||
|
|
||||||
|
import com.straice.smoothdoors.client.anim.SddAnimator;
|
||||||
|
import net.minecraft.client.renderer.chunk.RenderChunkRegion;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.world.level.block.Blocks;
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
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.callback.CallbackInfoReturnable;
|
||||||
|
|
||||||
|
@Mixin(RenderChunkRegion.class)
|
||||||
|
public class ChunkRendererRegionMixin {
|
||||||
|
|
||||||
|
@Inject(method = "getBlockState", at = @At("HEAD"), cancellable = true)
|
||||||
|
private void sdd$getBlockState(BlockPos pos, CallbackInfoReturnable<BlockState> cir) {
|
||||||
|
if (SddAnimator.isAnimatingAt(pos)) {
|
||||||
|
cir.setReturnValue(Blocks.AIR.defaultBlockState());
|
||||||
|
cir.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.straice.mixin.client;
|
||||||
|
|
||||||
|
import com.straice.smoothdoors.client.anim.SddAnimator;
|
||||||
|
import net.minecraft.client.multiplayer.ClientLevel;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
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.callback.CallbackInfo;
|
||||||
|
|
||||||
|
@Mixin(ClientLevel.class)
|
||||||
|
public class ClientWorldMixin {
|
||||||
|
|
||||||
|
@Inject(method = "sendBlockUpdated", at = @At("TAIL"))
|
||||||
|
private void sdd$updateListeners(BlockPos pos, BlockState oldState, BlockState newState, int flags, CallbackInfo ci) {
|
||||||
|
SddAnimator.onBlockUpdate(pos, oldState, newState);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,4 +12,4 @@ public class ExampleClientMixin {
|
|||||||
private void init(CallbackInfo info) {
|
private void init(CallbackInfo info) {
|
||||||
// This code is injected into the start of Minecraft.run()V
|
// This code is injected into the start of Minecraft.run()V
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package com.straice.smoothdoors.client.anim;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
|
import com.mojang.math.Axis;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.core.Direction;
|
||||||
|
import net.minecraft.world.level.BlockGetter;
|
||||||
|
import net.minecraft.world.level.block.DoorBlock;
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
import net.minecraft.world.level.block.state.properties.DoorHingeSide;
|
||||||
|
import net.minecraft.world.phys.AABB;
|
||||||
|
import net.minecraft.world.phys.shapes.CollisionContext;
|
||||||
|
|
||||||
|
final class DoorAnimation {
|
||||||
|
|
||||||
|
private DoorAnimation() {}
|
||||||
|
|
||||||
|
static void apply(BlockState state, float angleDeg, PoseStack matrices, BlockGetter world) {
|
||||||
|
if (world == null) return;
|
||||||
|
|
||||||
|
Direction facing = state.getValue(DoorBlock.FACING);
|
||||||
|
DoorHingeSide hinge = state.getValue(DoorBlock.HINGE);
|
||||||
|
|
||||||
|
Direction hingeSide = (hinge == DoorHingeSide.RIGHT)
|
||||||
|
? facing.getClockWise()
|
||||||
|
: facing.getCounterClockWise();
|
||||||
|
|
||||||
|
AABB bb = state.getShape(world, BlockPos.ZERO, CollisionContext.empty()).bounds();
|
||||||
|
|
||||||
|
float cx = (float) ((bb.minX + bb.maxX) * 0.5);
|
||||||
|
float cz = (float) ((bb.minZ + bb.maxZ) * 0.5);
|
||||||
|
|
||||||
|
float pivotX = cx;
|
||||||
|
float pivotZ = cz;
|
||||||
|
|
||||||
|
switch (hingeSide) {
|
||||||
|
case EAST -> pivotX = (float) bb.maxX;
|
||||||
|
case WEST -> pivotX = (float) bb.minX;
|
||||||
|
case SOUTH -> pivotZ = (float) bb.maxZ;
|
||||||
|
case NORTH -> pivotZ = (float) bb.minZ;
|
||||||
|
default -> {
|
||||||
|
pivotX = cx;
|
||||||
|
pivotZ = cz;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float thickness = (facing == Direction.NORTH || facing == Direction.SOUTH)
|
||||||
|
? (float) (bb.maxZ - bb.minZ)
|
||||||
|
: (float) (bb.maxX - bb.minX);
|
||||||
|
float halfT = thickness * 0.5f;
|
||||||
|
|
||||||
|
float dX = 0.0f;
|
||||||
|
float dZ = 0.0f;
|
||||||
|
switch (facing) {
|
||||||
|
case NORTH -> dZ = halfT;
|
||||||
|
case SOUTH -> dZ = -halfT;
|
||||||
|
case WEST -> dX = halfT;
|
||||||
|
case EAST -> dX = -halfT;
|
||||||
|
default -> {
|
||||||
|
dX = 0.0f;
|
||||||
|
dZ = 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double rad = Math.toRadians(angleDeg);
|
||||||
|
float cos = (float) Math.cos(rad);
|
||||||
|
float sin = (float) Math.sin(rad);
|
||||||
|
float rotDX = dX * cos - dZ * sin;
|
||||||
|
float rotDZ = dX * sin + dZ * cos;
|
||||||
|
float shiftX = dX - rotDX;
|
||||||
|
float shiftZ = dZ - rotDZ;
|
||||||
|
|
||||||
|
matrices.translate(shiftX, 0.0f, shiftZ);
|
||||||
|
matrices.translate(pivotX, 0.0f, pivotZ);
|
||||||
|
matrices.mulPose(Axis.YP.rotationDegrees(angleDeg));
|
||||||
|
matrices.translate(-pivotX, 0.0f, -pivotZ);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,374 @@
|
|||||||
|
package com.straice.smoothdoors.client.anim;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
|
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||||
|
import com.mojang.math.Axis;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.renderer.ItemBlockRenderTypes;
|
||||||
|
import net.minecraft.client.renderer.MultiBufferSource;
|
||||||
|
import net.minecraft.client.renderer.RenderType;
|
||||||
|
import net.minecraft.client.renderer.block.ModelBlockRenderer;
|
||||||
|
import net.minecraft.client.renderer.block.model.BakedQuad;
|
||||||
|
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
|
||||||
|
import net.minecraft.client.resources.model.BakedModel;
|
||||||
|
import net.minecraft.core.Direction;
|
||||||
|
import net.minecraft.util.RandomSource;
|
||||||
|
import net.minecraft.world.level.block.FenceGateBlock;
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Proxy;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.EnumMap;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
final class FenceGateAnimation {
|
||||||
|
|
||||||
|
private static final float POST_EDGE = 2.0f / 16.0f;
|
||||||
|
private static final float EDGE_EPS = 1.0e-4f;
|
||||||
|
|
||||||
|
private static final Method RENDER_TYPE_ONE_ARG;
|
||||||
|
private static final Method RENDER_TYPE_TWO_ARG;
|
||||||
|
private static final Method QUAD_LIGHT_EMISSION;
|
||||||
|
private static final Constructor<BakedQuad> QUAD_CTOR_5;
|
||||||
|
private static final Constructor<BakedQuad> QUAD_CTOR_6;
|
||||||
|
|
||||||
|
static {
|
||||||
|
Method oneArg = null;
|
||||||
|
Method twoArg = null;
|
||||||
|
Method lightEmission = null;
|
||||||
|
Constructor<BakedQuad> ctor5 = null;
|
||||||
|
Constructor<BakedQuad> ctor6 = null;
|
||||||
|
try {
|
||||||
|
oneArg = ItemBlockRenderTypes.class.getMethod("getRenderType", BlockState.class);
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
// Older API.
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
twoArg = ItemBlockRenderTypes.class.getMethod("getRenderType", BlockState.class, boolean.class);
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
// Newer API.
|
||||||
|
}
|
||||||
|
if (oneArg == null) {
|
||||||
|
oneArg = findRenderTypeMethod(BlockState.class);
|
||||||
|
}
|
||||||
|
if (twoArg == null) {
|
||||||
|
twoArg = findRenderTypeMethod(BlockState.class, boolean.class);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
lightEmission = BakedQuad.class.getMethod("getLightEmission");
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
// Older quad API.
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
ctor5 = BakedQuad.class.getConstructor(int[].class, int.class, Direction.class, TextureAtlasSprite.class, boolean.class);
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
// Newer quad API.
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
ctor6 = BakedQuad.class.getConstructor(int[].class, int.class, Direction.class, TextureAtlasSprite.class, boolean.class, int.class);
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
// Older quad API.
|
||||||
|
}
|
||||||
|
RENDER_TYPE_ONE_ARG = oneArg;
|
||||||
|
RENDER_TYPE_TWO_ARG = twoArg;
|
||||||
|
QUAD_LIGHT_EMISSION = lightEmission;
|
||||||
|
QUAD_CTOR_5 = ctor5;
|
||||||
|
QUAD_CTOR_6 = ctor6;
|
||||||
|
}
|
||||||
|
|
||||||
|
private FenceGateAnimation() {}
|
||||||
|
|
||||||
|
static void render(BlockState state, float angleDeg, PoseStack matrices,
|
||||||
|
MultiBufferSource consumers, int light, int overlay) {
|
||||||
|
Minecraft mc = Minecraft.getInstance();
|
||||||
|
if (mc.level == null) return;
|
||||||
|
|
||||||
|
Direction facing = state.getValue(FenceGateBlock.FACING);
|
||||||
|
boolean leftRightIsX = facing.getAxis() == Direction.Axis.Z;
|
||||||
|
|
||||||
|
BakedModel baseModel = mc.getBlockRenderer().getBlockModel(state);
|
||||||
|
FenceGateModels models = splitFenceGateModels(state, baseModel, leftRightIsX);
|
||||||
|
if (models.isEmpty()) return;
|
||||||
|
|
||||||
|
int tint = mc.getBlockColors().getColor(state, null, null, 0);
|
||||||
|
float r = ((tint >> 16) & 0xFF) / 255.0f;
|
||||||
|
float g = ((tint >> 8) & 0xFF) / 255.0f;
|
||||||
|
float b = (tint & 0xFF) / 255.0f;
|
||||||
|
|
||||||
|
RenderType renderType = getRenderTypeCompat(state);
|
||||||
|
VertexConsumer consumer = consumers.getBuffer(renderType);
|
||||||
|
ModelBlockRenderer renderer = mc.getBlockRenderer().getModelRenderer();
|
||||||
|
|
||||||
|
if (models.posts != null) {
|
||||||
|
renderer.renderModel(matrices.last(), consumer, state, models.posts, r, g, b, light, overlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
float leftPivotX = leftRightIsX ? (1.0f / 16.0f) : 0.5f;
|
||||||
|
float leftPivotZ = leftRightIsX ? 0.5f : (1.0f / 16.0f);
|
||||||
|
float rightPivotX = leftRightIsX ? (15.0f / 16.0f) : 0.5f;
|
||||||
|
float rightPivotZ = leftRightIsX ? 0.5f : (15.0f / 16.0f);
|
||||||
|
|
||||||
|
float leftAngle = -angleDeg;
|
||||||
|
float rightAngle = angleDeg;
|
||||||
|
|
||||||
|
if (models.left != null) {
|
||||||
|
matrices.pushPose();
|
||||||
|
matrices.translate(leftPivotX, 0.0f, leftPivotZ);
|
||||||
|
matrices.mulPose(Axis.YP.rotationDegrees(leftAngle));
|
||||||
|
matrices.translate(-leftPivotX, 0.0f, -leftPivotZ);
|
||||||
|
renderer.renderModel(matrices.last(), consumer, state, models.left, r, g, b, light, overlay);
|
||||||
|
matrices.popPose();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (models.right != null) {
|
||||||
|
matrices.pushPose();
|
||||||
|
matrices.translate(rightPivotX, 0.0f, rightPivotZ);
|
||||||
|
matrices.mulPose(Axis.YP.rotationDegrees(rightAngle));
|
||||||
|
matrices.translate(-rightPivotX, 0.0f, -rightPivotZ);
|
||||||
|
renderer.renderModel(matrices.last(), consumer, state, models.right, r, g, b, light, overlay);
|
||||||
|
matrices.popPose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FenceGateModels splitFenceGateModels(BlockState state, BakedModel model, boolean leftRightIsX) {
|
||||||
|
List<BakedQuad> posts = new ArrayList<>();
|
||||||
|
List<BakedQuad> left = new ArrayList<>();
|
||||||
|
List<BakedQuad> right = new ArrayList<>();
|
||||||
|
|
||||||
|
for (Direction dir : Direction.values()) {
|
||||||
|
addFenceGateQuads(state, model, dir, leftRightIsX, posts, left, right);
|
||||||
|
}
|
||||||
|
addFenceGateQuads(state, model, null, leftRightIsX, posts, left, right);
|
||||||
|
|
||||||
|
return new FenceGateModels(
|
||||||
|
posts.isEmpty() ? null : buildStaticModel(model, buildFaces(posts), model.useAmbientOcclusion(), model.getParticleIcon()),
|
||||||
|
left.isEmpty() ? null : buildStaticModel(model, buildFaces(left), model.useAmbientOcclusion(), model.getParticleIcon()),
|
||||||
|
right.isEmpty() ? null : buildStaticModel(model, buildFaces(right), model.useAmbientOcclusion(), model.getParticleIcon())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FaceBuckets buildFaces(List<BakedQuad> quads) {
|
||||||
|
EnumMap<Direction, List<BakedQuad>> faces = new EnumMap<>(Direction.class);
|
||||||
|
List<BakedQuad> unculled = new ArrayList<>();
|
||||||
|
|
||||||
|
for (BakedQuad quad : quads) {
|
||||||
|
Direction face = quad.getDirection();
|
||||||
|
if (face == null) {
|
||||||
|
unculled.add(quad);
|
||||||
|
} else {
|
||||||
|
faces.computeIfAbsent(face, k -> new ArrayList<>()).add(quad);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FaceBuckets(faces, unculled);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addFenceGateQuads(BlockState state, BakedModel model, Direction dir, boolean leftRightIsX,
|
||||||
|
List<BakedQuad> posts, List<BakedQuad> left, List<BakedQuad> right) {
|
||||||
|
RandomSource random = RandomSource.create(42L);
|
||||||
|
List<BakedQuad> quads = model.getQuads(state, dir, random);
|
||||||
|
if (quads.isEmpty()) return;
|
||||||
|
|
||||||
|
for (BakedQuad quad : quads) {
|
||||||
|
FenceGateSection section = classifyFenceGateQuad(quad, leftRightIsX);
|
||||||
|
switch (section) {
|
||||||
|
case POSTS -> posts.add(quad);
|
||||||
|
case LEFT -> {
|
||||||
|
left.add(quad);
|
||||||
|
left.add(reverseQuad(quad));
|
||||||
|
}
|
||||||
|
case RIGHT -> {
|
||||||
|
right.add(quad);
|
||||||
|
right.add(reverseQuad(quad));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FenceGateSection classifyFenceGateQuad(BakedQuad quad, boolean leftRightIsX) {
|
||||||
|
int[] data = quad.getVertices();
|
||||||
|
if (data.length < 8) return FenceGateSection.LEFT;
|
||||||
|
|
||||||
|
int stride = data.length / 4;
|
||||||
|
float min = Float.POSITIVE_INFINITY;
|
||||||
|
float max = Float.NEGATIVE_INFINITY;
|
||||||
|
int coordOffset = leftRightIsX ? 0 : 2;
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
int base = i * stride + coordOffset;
|
||||||
|
float coord = Float.intBitsToFloat(data[base]);
|
||||||
|
min = Math.min(min, coord);
|
||||||
|
max = Math.max(max, coord);
|
||||||
|
}
|
||||||
|
|
||||||
|
float scale = (max > 1.001f) ? 16.0f : 1.0f;
|
||||||
|
float postEdge = POST_EDGE * scale;
|
||||||
|
float edgeEps = EDGE_EPS * scale;
|
||||||
|
float one = 1.0f * scale;
|
||||||
|
|
||||||
|
if (max <= postEdge + edgeEps || min >= (one - postEdge) - edgeEps) {
|
||||||
|
return FenceGateSection.POSTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
float center = (min + max) * 0.5f;
|
||||||
|
return center <= (0.5f * scale) ? FenceGateSection.LEFT : FenceGateSection.RIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum FenceGateSection { POSTS, LEFT, RIGHT }
|
||||||
|
|
||||||
|
private static BakedQuad reverseQuad(BakedQuad quad) {
|
||||||
|
int[] data = quad.getVertices();
|
||||||
|
int stride = data.length / 4;
|
||||||
|
int[] flipped = new int[data.length];
|
||||||
|
|
||||||
|
for (int v = 0; v < 4; v++) {
|
||||||
|
int src = (3 - v) * stride;
|
||||||
|
int dst = v * stride;
|
||||||
|
System.arraycopy(data, src, flipped, dst, stride);
|
||||||
|
}
|
||||||
|
|
||||||
|
Direction face = quad.getDirection();
|
||||||
|
Direction opposite = (face == null) ? null : face.getOpposite();
|
||||||
|
return newQuadCompat(flipped, quad.getTintIndex(), opposite, quad.getSprite(), quad.isShade(), quad);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RenderType getRenderTypeCompat(BlockState state) {
|
||||||
|
try {
|
||||||
|
if (RENDER_TYPE_TWO_ARG != null) {
|
||||||
|
return (RenderType) RENDER_TYPE_TWO_ARG.invoke(null, state, true);
|
||||||
|
}
|
||||||
|
if (RENDER_TYPE_ONE_ARG != null) {
|
||||||
|
return (RenderType) RENDER_TYPE_ONE_ARG.invoke(null, state);
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
throw new IllegalStateException("Failed to resolve ItemBlockRenderTypes#getRenderType", e);
|
||||||
|
}
|
||||||
|
throw new IllegalStateException("No compatible ItemBlockRenderTypes#getRenderType found");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Method findRenderTypeMethod(Class<?>... params) {
|
||||||
|
for (Method method : ItemBlockRenderTypes.class.getDeclaredMethods()) {
|
||||||
|
if (!Modifier.isStatic(method.getModifiers())) continue;
|
||||||
|
if (!RenderType.class.isAssignableFrom(method.getReturnType())) continue;
|
||||||
|
Class<?>[] types = method.getParameterTypes();
|
||||||
|
if (types.length != params.length) continue;
|
||||||
|
boolean match = true;
|
||||||
|
for (int i = 0; i < types.length; i++) {
|
||||||
|
if (types[i] != params[i]) {
|
||||||
|
match = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (match) {
|
||||||
|
method.setAccessible(true);
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class FenceGateModels {
|
||||||
|
final BakedModel posts;
|
||||||
|
final BakedModel left;
|
||||||
|
final BakedModel right;
|
||||||
|
|
||||||
|
FenceGateModels(BakedModel posts, BakedModel left, BakedModel right) {
|
||||||
|
this.posts = posts;
|
||||||
|
this.left = left;
|
||||||
|
this.right = right;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isEmpty() {
|
||||||
|
return posts == null && left == null && right == null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BakedModel buildStaticModel(BakedModel base, FaceBuckets buckets,
|
||||||
|
boolean useAo, TextureAtlasSprite particleIcon) {
|
||||||
|
return (BakedModel) Proxy.newProxyInstance(
|
||||||
|
BakedModel.class.getClassLoader(),
|
||||||
|
new Class<?>[]{BakedModel.class},
|
||||||
|
(proxy, method, args) -> {
|
||||||
|
String name = method.getName();
|
||||||
|
switch (name) {
|
||||||
|
case "getQuads" -> {
|
||||||
|
Direction face = (Direction) args[1];
|
||||||
|
if (face == null) return buckets.unculled;
|
||||||
|
List<BakedQuad> quads = buckets.faces.get(face);
|
||||||
|
return (quads == null) ? List.of() : quads;
|
||||||
|
}
|
||||||
|
case "useAmbientOcclusion" -> {
|
||||||
|
return useAo;
|
||||||
|
}
|
||||||
|
case "isGui3d" -> {
|
||||||
|
return base.isGui3d();
|
||||||
|
}
|
||||||
|
case "usesBlockLight" -> {
|
||||||
|
return base.usesBlockLight();
|
||||||
|
}
|
||||||
|
case "isCustomRenderer" -> {
|
||||||
|
return invokeBoolean(base, "isCustomRenderer", false);
|
||||||
|
}
|
||||||
|
case "getParticleIcon" -> {
|
||||||
|
return particleIcon;
|
||||||
|
}
|
||||||
|
case "getTransforms" -> {
|
||||||
|
return base.getTransforms();
|
||||||
|
}
|
||||||
|
case "getOverrides", "overrides" -> {
|
||||||
|
return method.invoke(base, args);
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
return method.invoke(base, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean invokeBoolean(Object target, String methodName, boolean fallback) {
|
||||||
|
try {
|
||||||
|
Method method = target.getClass().getMethod(methodName);
|
||||||
|
Object result = method.invoke(target);
|
||||||
|
return (result instanceof Boolean) ? (Boolean) result : fallback;
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class FaceBuckets {
|
||||||
|
final EnumMap<Direction, List<BakedQuad>> faces;
|
||||||
|
final List<BakedQuad> unculled;
|
||||||
|
|
||||||
|
FaceBuckets(EnumMap<Direction, List<BakedQuad>> faces, List<BakedQuad> unculled) {
|
||||||
|
this.faces = faces;
|
||||||
|
this.unculled = unculled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BakedQuad newQuadCompat(int[] vertices, int tintIndex, Direction face,
|
||||||
|
TextureAtlasSprite sprite, boolean shade, BakedQuad source) {
|
||||||
|
int lightEmission = 0;
|
||||||
|
if (QUAD_LIGHT_EMISSION != null) {
|
||||||
|
try {
|
||||||
|
lightEmission = (int) QUAD_LIGHT_EMISSION.invoke(source);
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
lightEmission = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (QUAD_CTOR_6 != null) {
|
||||||
|
return QUAD_CTOR_6.newInstance(vertices, tintIndex, face, sprite, shade, lightEmission);
|
||||||
|
}
|
||||||
|
if (QUAD_CTOR_5 != null) {
|
||||||
|
return QUAD_CTOR_5.newInstance(vertices, tintIndex, face, sprite, shade);
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
throw new IllegalStateException("Failed to construct BakedQuad", e);
|
||||||
|
}
|
||||||
|
throw new IllegalStateException("No compatible BakedQuad constructor found");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,469 @@
|
|||||||
|
package com.straice.smoothdoors.client.anim;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
|
import com.straice.smoothdoors.config.SddConfig;
|
||||||
|
import com.straice.smoothdoors.config.SddConfigManager;
|
||||||
|
import com.straice.smoothdoors.util.ConnectedUtil;
|
||||||
|
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||||
|
import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
import net.minecraft.client.renderer.LevelRenderer;
|
||||||
|
import net.minecraft.client.renderer.MultiBufferSource;
|
||||||
|
import net.minecraft.client.renderer.texture.OverlayTexture;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.core.SectionPos;
|
||||||
|
import net.minecraft.util.Mth;
|
||||||
|
import net.minecraft.world.level.BlockAndTintGetter;
|
||||||
|
import net.minecraft.world.level.block.Block;
|
||||||
|
import net.minecraft.world.level.block.DoorBlock;
|
||||||
|
import net.minecraft.world.level.block.FenceGateBlock;
|
||||||
|
import net.minecraft.world.level.block.TrapDoorBlock;
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
import net.minecraft.world.level.block.state.properties.DoorHingeSide;
|
||||||
|
import net.minecraft.world.level.block.state.properties.DoubleBlockHalf;
|
||||||
|
import net.minecraft.world.phys.Vec3;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
|
||||||
|
public final class SddAnimator {
|
||||||
|
|
||||||
|
private static final Minecraft MC = Minecraft.getInstance();
|
||||||
|
|
||||||
|
// Base duration (seconds) at speed = 1.0x
|
||||||
|
private static final float BASE_DURATION_S = 0.35f;
|
||||||
|
private static final double END_GRACE_TICKS = 2.0;
|
||||||
|
|
||||||
|
private static final Long2ObjectOpenHashMap<Anim> ANIMS = new Long2ObjectOpenHashMap<>();
|
||||||
|
private static boolean hooksInit = false;
|
||||||
|
|
||||||
|
private SddAnimator() {}
|
||||||
|
|
||||||
|
/** Llamar desde SmoothDoubleDoorsClient#onInitializeClient(). */
|
||||||
|
public static void initClientHooks() {
|
||||||
|
if (hooksInit) return;
|
||||||
|
hooksInit = true;
|
||||||
|
WorldRenderEvents.AFTER_ENTITIES.register(context -> {
|
||||||
|
if (MC.level == null || ANIMS.isEmpty()) return;
|
||||||
|
float tickDelta = MC.getFrameTime();
|
||||||
|
PoseStack matrices = context.matrixStack();
|
||||||
|
MultiBufferSource consumers = context.consumers();
|
||||||
|
if (matrices == null || consumers == null) return;
|
||||||
|
Vec3 camPos = context.camera().getPosition();
|
||||||
|
renderAll(tickDelta, matrices, consumers, camPos);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// === API usada por mixins ===
|
||||||
|
|
||||||
|
/** Oculta el bloque vanilla mientras animamos (evita doble puerta). */
|
||||||
|
public static boolean shouldHideInChunk(BlockPos pos, BlockState state) {
|
||||||
|
synchronized (ANIMS) {
|
||||||
|
if (isHiddenByAnim(pos)) return true;
|
||||||
|
|
||||||
|
if (state.getBlock() instanceof DoorBlock && state.hasProperty(DoorBlock.HALF)) {
|
||||||
|
DoubleBlockHalf half = state.getValue(DoorBlock.HALF);
|
||||||
|
BlockPos other = (half == DoubleBlockHalf.LOWER) ? pos.above() : pos.below();
|
||||||
|
return isHiddenByAnim(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isAnimatingAt(BlockPos pos) {
|
||||||
|
synchronized (ANIMS) {
|
||||||
|
return isHiddenByAnim(pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Se llama cuando cambia el bloque (abrir/cerrar). */
|
||||||
|
public static void onBlockUpdate(BlockPos pos, BlockState oldState, BlockState newState) {
|
||||||
|
if (MC.level == null) return;
|
||||||
|
|
||||||
|
Kind kind = kindOf(oldState, newState);
|
||||||
|
if (kind == null) {
|
||||||
|
synchronized (ANIMS) {
|
||||||
|
ANIMS.remove(pos.asLong());
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isKindBlock(kind, newState)) {
|
||||||
|
removeKindAt(pos, kind);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DOOR: si llega update de la mitad UPPER, arrancamos desde abajo usando el estado superior.
|
||||||
|
if (kind == Kind.DOOR && newState.hasProperty(DoorBlock.HALF)) {
|
||||||
|
if (newState.getValue(DoorBlock.HALF) == DoubleBlockHalf.UPPER) {
|
||||||
|
BlockPos lowerPos = pos.below();
|
||||||
|
if (isAnimatingAt(lowerPos) || isAnimatingAt(pos)) return;
|
||||||
|
|
||||||
|
boolean oldOpen = isOpen(oldState);
|
||||||
|
boolean newOpen = isOpen(newState);
|
||||||
|
if (oldOpen == newOpen) return;
|
||||||
|
|
||||||
|
SddConfig cfg = SddConfigManager.get();
|
||||||
|
if (!isAnimationEnabled(cfg, kind)) {
|
||||||
|
removeKindAt(pos, kind);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float speed = speedFor(cfg, kind);
|
||||||
|
if (speed <= 0.001f) speed = 1.0f;
|
||||||
|
|
||||||
|
long startTick = MC.level.getGameTime();
|
||||||
|
BlockState lowerNew = MC.level.getBlockState(lowerPos);
|
||||||
|
if (!lowerNew.hasProperty(DoorBlock.HALF)) {
|
||||||
|
lowerNew = newState;
|
||||||
|
}
|
||||||
|
if (lowerNew.hasProperty(DoorBlock.OPEN)) {
|
||||||
|
lowerNew = lowerNew.setValue(DoorBlock.OPEN, newOpen);
|
||||||
|
}
|
||||||
|
lowerNew = lowerNew.setValue(DoorBlock.HALF, DoubleBlockHalf.LOWER);
|
||||||
|
startDoorAnimBothHalves(lowerPos, lowerNew, oldOpen, newOpen, speed, startTick);
|
||||||
|
startPairedDoorAnimIfAny(lowerPos, lowerNew, oldOpen, newOpen, speed, startTick, cfg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean oldOpen = isOpen(oldState);
|
||||||
|
boolean newOpen = isOpen(newState);
|
||||||
|
if (oldOpen == newOpen) return;
|
||||||
|
|
||||||
|
SddConfig cfg = SddConfigManager.get();
|
||||||
|
if (!isAnimationEnabled(cfg, kind)) {
|
||||||
|
removeKindAt(pos, kind);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float speed = speedFor(cfg, kind);
|
||||||
|
if (speed <= 0.001f) speed = 1.0f;
|
||||||
|
|
||||||
|
long startTick = MC.level.getGameTime();
|
||||||
|
|
||||||
|
if (kind == Kind.DOOR) {
|
||||||
|
if (isAnimatingAt(pos) || isAnimatingAt(pos.above())) return;
|
||||||
|
startDoorAnimBothHalves(pos, newState, oldOpen, newOpen, speed, startTick);
|
||||||
|
startPairedDoorAnimIfAny(pos, newState, oldOpen, newOpen, speed, startTick, cfg);
|
||||||
|
} else {
|
||||||
|
BlockState base = forceClosedModel(newState, kind);
|
||||||
|
synchronized (ANIMS) {
|
||||||
|
ANIMS.put(pos.asLong(), new Anim(pos, base, kind, oldOpen, newOpen, speed, startTick));
|
||||||
|
}
|
||||||
|
requestRerender(pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Render ===
|
||||||
|
|
||||||
|
private static void renderAll(float tickDelta, PoseStack matrices, MultiBufferSource consumers, Vec3 camPos) {
|
||||||
|
if (MC.level == null || ANIMS.isEmpty()) return;
|
||||||
|
|
||||||
|
long worldTick = MC.level.getGameTime();
|
||||||
|
double nowTick = worldTick + (double) Mth.clamp(tickDelta, 0.0f, 1.0f);
|
||||||
|
|
||||||
|
Anim[] snapshot;
|
||||||
|
synchronized (ANIMS) {
|
||||||
|
snapshot = ANIMS.values().toArray(new Anim[0]);
|
||||||
|
}
|
||||||
|
for (Anim a : snapshot) {
|
||||||
|
float t = a.progress01(nowTick);
|
||||||
|
renderOne(a, t, camPos, matrices, consumers);
|
||||||
|
}
|
||||||
|
cleanupFinished(nowTick);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void cleanupFinished(double nowTick) {
|
||||||
|
synchronized (ANIMS) {
|
||||||
|
var it = ANIMS.long2ObjectEntrySet().iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
var entry = it.next();
|
||||||
|
Anim anim = entry.getValue();
|
||||||
|
if (anim.hasEnded(nowTick) && anim.hideVanilla) {
|
||||||
|
anim.hideVanilla = false;
|
||||||
|
requestRerender(anim.pos);
|
||||||
|
}
|
||||||
|
if (anim.isFinished(nowTick)) {
|
||||||
|
it.remove();
|
||||||
|
requestRerender(anim.pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void startPairedDoorAnimIfAny(BlockPos lowerPos, BlockState lowerState,
|
||||||
|
boolean oldOpen, boolean newOpen,
|
||||||
|
float speed, long startTick, SddConfig cfg) {
|
||||||
|
if (MC.level == null) return;
|
||||||
|
if (!cfg.connectDoors) return;
|
||||||
|
|
||||||
|
BlockPos lower = ConnectedUtil.getDoorLowerPos(lowerPos, lowerState);
|
||||||
|
BlockState lowerNow = MC.level.getBlockState(lower);
|
||||||
|
if (!(lowerNow.getBlock() instanceof DoorBlock)) return;
|
||||||
|
|
||||||
|
BlockPos mateLower = ConnectedUtil.findPairedDoorLower(MC.level, lower, lowerNow);
|
||||||
|
if (mateLower == null) return;
|
||||||
|
if (isHiddenByAnim(mateLower) || isHiddenByAnim(mateLower.above())) return;
|
||||||
|
|
||||||
|
BlockState mateLowerState = MC.level.getBlockState(mateLower);
|
||||||
|
if (!(mateLowerState.getBlock() instanceof DoorBlock)) return;
|
||||||
|
if (mateLowerState.hasProperty(DoorBlock.OPEN)) {
|
||||||
|
mateLowerState = mateLowerState.setValue(DoorBlock.OPEN, newOpen);
|
||||||
|
}
|
||||||
|
mateLowerState = mateLowerState.setValue(DoorBlock.HALF, DoubleBlockHalf.LOWER);
|
||||||
|
|
||||||
|
startDoorAnimBothHalves(mateLower, mateLowerState, oldOpen, newOpen, speed, startTick);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void renderOne(Anim anim, float t, Vec3 camPos, PoseStack matrices,
|
||||||
|
MultiBufferSource consumers) {
|
||||||
|
if (MC.level == null) return;
|
||||||
|
|
||||||
|
BlockPos pos = anim.pos;
|
||||||
|
BlockState state = anim.baseState;
|
||||||
|
|
||||||
|
matrices.pushPose();
|
||||||
|
|
||||||
|
// Coordenadas relativas a camara (requerido por WorldRenderEvents consumidores)
|
||||||
|
matrices.translate(
|
||||||
|
pos.getX() - camPos.x,
|
||||||
|
pos.getY() - camPos.y,
|
||||||
|
pos.getZ() - camPos.z
|
||||||
|
);
|
||||||
|
|
||||||
|
float eased = easeInOut(t);
|
||||||
|
float angleDeg = lerpAngleDeg(anim.fromOpen, anim.toOpen, eased, anim.kind, state);
|
||||||
|
|
||||||
|
int light = LevelRenderer.getLightColor((BlockAndTintGetter) MC.level, pos);
|
||||||
|
if (anim.kind == Kind.FENCE_GATE) {
|
||||||
|
FenceGateAnimation.render(state, angleDeg, matrices, consumers, light, OverlayTexture.NO_OVERLAY);
|
||||||
|
} else {
|
||||||
|
applyTransform(anim.kind, state, angleDeg, matrices);
|
||||||
|
renderBlockAsEntity(state, matrices, consumers, light, OverlayTexture.NO_OVERLAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
matrices.popPose();
|
||||||
|
}
|
||||||
|
|
||||||
|
// === DOOR (dos mitades) ===
|
||||||
|
|
||||||
|
private static void startDoorAnimBothHalves(BlockPos lowerPos, BlockState lowerNewState, boolean oldOpen, boolean newOpen,
|
||||||
|
float speed, long startTick) {
|
||||||
|
BlockState baseLower = forceClosedModel(
|
||||||
|
lowerNewState.setValue(DoorBlock.HALF, DoubleBlockHalf.LOWER),
|
||||||
|
Kind.DOOR
|
||||||
|
);
|
||||||
|
|
||||||
|
BlockPos upperPos = lowerPos.above();
|
||||||
|
BlockState baseUpper = baseLower.setValue(DoorBlock.HALF, DoubleBlockHalf.UPPER);
|
||||||
|
|
||||||
|
Anim aLower = new Anim(lowerPos, baseLower, Kind.DOOR, oldOpen, newOpen, speed, startTick);
|
||||||
|
Anim aUpper = new Anim(upperPos, baseUpper, Kind.DOOR, oldOpen, newOpen, speed, startTick);
|
||||||
|
|
||||||
|
synchronized (ANIMS) {
|
||||||
|
ANIMS.put(lowerPos.asLong(), aLower);
|
||||||
|
ANIMS.put(upperPos.asLong(), aUpper);
|
||||||
|
}
|
||||||
|
requestRerender(lowerPos);
|
||||||
|
requestRerender(upperPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void removeKindAt(BlockPos pos, Kind kind) {
|
||||||
|
synchronized (ANIMS) {
|
||||||
|
ANIMS.remove(pos.asLong());
|
||||||
|
if (kind == Kind.DOOR) {
|
||||||
|
ANIMS.remove(pos.above().asLong());
|
||||||
|
ANIMS.remove(pos.below().asLong());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Angulos y transforms ===
|
||||||
|
|
||||||
|
private static float lerpAngleDeg(boolean fromOpen, boolean toOpen, float t, Kind kind, BlockState state) {
|
||||||
|
float a = angleFor(kind, state, fromOpen);
|
||||||
|
float b = angleFor(kind, state, toOpen);
|
||||||
|
return Mth.lerp(t, a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float angleFor(Kind kind, BlockState state, boolean open) {
|
||||||
|
if (!open) return 0.0f;
|
||||||
|
|
||||||
|
return switch (kind) {
|
||||||
|
case DOOR -> {
|
||||||
|
DoorHingeSide hinge = state.getValue(DoorBlock.HINGE);
|
||||||
|
yield (hinge == DoorHingeSide.RIGHT) ? -90.0f : 90.0f;
|
||||||
|
}
|
||||||
|
case TRAPDOOR -> 90.0f;
|
||||||
|
case FENCE_GATE -> 90.0f;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void applyTransform(Kind kind, BlockState state, float angleDeg, PoseStack matrices) {
|
||||||
|
switch (kind) {
|
||||||
|
case DOOR -> DoorAnimation.apply(state, angleDeg, matrices, MC.level);
|
||||||
|
case TRAPDOOR -> TrapdoorAnimation.apply(state, angleDeg, matrices, MC.level);
|
||||||
|
case FENCE_GATE -> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void renderBlockAsEntity(BlockState state, PoseStack matrices,
|
||||||
|
MultiBufferSource consumers, int light, int overlay) {
|
||||||
|
MC.getBlockRenderer().renderSingleBlock(state, matrices, consumers, light, overlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Utilidades ===
|
||||||
|
|
||||||
|
private static BlockState forceClosedModel(BlockState s, Kind kind) {
|
||||||
|
return switch (kind) {
|
||||||
|
case DOOR -> s.setValue(DoorBlock.OPEN, false);
|
||||||
|
case TRAPDOOR -> s.setValue(TrapDoorBlock.OPEN, false);
|
||||||
|
case FENCE_GATE -> s.setValue(FenceGateBlock.OPEN, false);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isKindBlock(Kind kind, BlockState state) {
|
||||||
|
Block b = state.getBlock();
|
||||||
|
return switch (kind) {
|
||||||
|
case DOOR -> b instanceof DoorBlock;
|
||||||
|
case TRAPDOOR -> b instanceof TrapDoorBlock;
|
||||||
|
case FENCE_GATE -> b instanceof FenceGateBlock;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void requestRerender(BlockPos pos) {
|
||||||
|
if (MC.levelRenderer == null || MC.level == null) return;
|
||||||
|
BlockState st = MC.level.getBlockState(pos);
|
||||||
|
MC.levelRenderer.blockChanged(MC.level, pos, st, st, 0);
|
||||||
|
MC.levelRenderer.setBlocksDirty(
|
||||||
|
pos.getX(), pos.getY(), pos.getZ(),
|
||||||
|
pos.getX(), pos.getY(), pos.getZ()
|
||||||
|
);
|
||||||
|
MC.levelRenderer.setSectionDirty(
|
||||||
|
SectionPos.blockToSectionCoord(pos.getX()),
|
||||||
|
SectionPos.blockToSectionCoord(pos.getY()),
|
||||||
|
SectionPos.blockToSectionCoord(pos.getZ())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isOpen(BlockState s) {
|
||||||
|
Block b = s.getBlock();
|
||||||
|
if (b instanceof DoorBlock) return s.getValue(DoorBlock.OPEN);
|
||||||
|
if (b instanceof TrapDoorBlock) return s.getValue(TrapDoorBlock.OPEN);
|
||||||
|
if (b instanceof FenceGateBlock) return s.getValue(FenceGateBlock.OPEN);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Kind kindOf(BlockState oldState, BlockState newState) {
|
||||||
|
Block b = newState.getBlock();
|
||||||
|
if (b instanceof DoorBlock) return Kind.DOOR;
|
||||||
|
if (b instanceof TrapDoorBlock) return Kind.TRAPDOOR;
|
||||||
|
if (b instanceof FenceGateBlock) return Kind.FENCE_GATE;
|
||||||
|
|
||||||
|
b = oldState.getBlock();
|
||||||
|
if (b instanceof DoorBlock) return Kind.DOOR;
|
||||||
|
if (b instanceof TrapDoorBlock) return Kind.TRAPDOOR;
|
||||||
|
if (b instanceof FenceGateBlock) return Kind.FENCE_GATE;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isAnimationEnabled(SddConfig cfg, Kind kind) {
|
||||||
|
return switch (kind) {
|
||||||
|
case DOOR -> cfg.animateDoors;
|
||||||
|
case TRAPDOOR -> cfg.animateTrapdoors;
|
||||||
|
case FENCE_GATE -> getBool(cfg, "animateFenceGates", false);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float speedFor(SddConfig cfg, Kind kind) {
|
||||||
|
return switch (kind) {
|
||||||
|
case DOOR -> cfg.doorSpeed;
|
||||||
|
case TRAPDOOR -> cfg.trapdoorSpeed;
|
||||||
|
case FENCE_GATE -> getFloat(cfg, "fenceGateSpeed", 1.0f);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float easeInOut(float t) {
|
||||||
|
return t * t * (3.0f - 2.0f * t);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isHiddenByAnim(BlockPos pos) {
|
||||||
|
Anim anim = ANIMS.get(pos.asLong());
|
||||||
|
return anim != null && anim.hideVanilla;
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Reflect helpers (por si el campo no existe) ===
|
||||||
|
|
||||||
|
private static boolean getBool(Object obj, String field, boolean def) {
|
||||||
|
try {
|
||||||
|
Field f = obj.getClass().getDeclaredField(field);
|
||||||
|
f.setAccessible(true);
|
||||||
|
Object v = f.get(obj);
|
||||||
|
return (v instanceof Boolean) ? (Boolean) v : def;
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float getFloat(Object obj, String field, float def) {
|
||||||
|
try {
|
||||||
|
Field f = obj.getClass().getDeclaredField(field);
|
||||||
|
f.setAccessible(true);
|
||||||
|
Object v = f.get(obj);
|
||||||
|
if (v instanceof Float) return (Float) v;
|
||||||
|
if (v instanceof Double) return ((Double) v).floatValue();
|
||||||
|
if (v instanceof Number) return ((Number) v).floatValue();
|
||||||
|
return def;
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Tipos ===
|
||||||
|
|
||||||
|
public enum Kind { DOOR, TRAPDOOR, FENCE_GATE }
|
||||||
|
|
||||||
|
private static final class Anim {
|
||||||
|
final BlockPos pos;
|
||||||
|
final BlockState baseState;
|
||||||
|
final Kind kind;
|
||||||
|
final boolean fromOpen;
|
||||||
|
final boolean toOpen;
|
||||||
|
boolean hideVanilla = true;
|
||||||
|
|
||||||
|
final double startTick;
|
||||||
|
final double durationTicks;
|
||||||
|
|
||||||
|
Anim(BlockPos pos, BlockState baseState, Kind kind, boolean fromOpen, boolean toOpen,
|
||||||
|
float speed, long startTick) {
|
||||||
|
this.pos = pos;
|
||||||
|
this.baseState = baseState;
|
||||||
|
this.kind = kind;
|
||||||
|
this.fromOpen = fromOpen;
|
||||||
|
this.toOpen = toOpen;
|
||||||
|
|
||||||
|
double durS = BASE_DURATION_S / speed;
|
||||||
|
if (durS < 0.05) durS = 0.05;
|
||||||
|
|
||||||
|
this.startTick = (double) startTick;
|
||||||
|
this.durationTicks = Math.max(1.0, durS * 20.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
float progress01(double nowTick) {
|
||||||
|
double t = (nowTick - startTick) / durationTicks;
|
||||||
|
if (t <= 0.0) return 0.0f;
|
||||||
|
if (t >= 1.0) return 1.0f;
|
||||||
|
return (float) t;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean hasEnded(double nowTick) {
|
||||||
|
return (nowTick - startTick) >= durationTicks;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isFinished(double nowTick) {
|
||||||
|
return (nowTick - startTick) >= (durationTicks + END_GRACE_TICKS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package com.straice.smoothdoors.client.anim;
|
||||||
|
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
|
import com.mojang.math.Axis;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.core.Direction;
|
||||||
|
import net.minecraft.world.level.BlockGetter;
|
||||||
|
import net.minecraft.world.level.block.TrapDoorBlock;
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
import net.minecraft.world.level.block.state.properties.Half;
|
||||||
|
import net.minecraft.world.phys.AABB;
|
||||||
|
import net.minecraft.world.phys.shapes.CollisionContext;
|
||||||
|
import net.minecraft.world.phys.shapes.VoxelShape;
|
||||||
|
|
||||||
|
final class TrapdoorAnimation {
|
||||||
|
|
||||||
|
private TrapdoorAnimation() {}
|
||||||
|
|
||||||
|
static void apply(BlockState state, float baseAngleDeg, PoseStack matrices, BlockGetter world) {
|
||||||
|
if (world == null) return;
|
||||||
|
|
||||||
|
Direction facing = state.getValue(TrapDoorBlock.FACING);
|
||||||
|
Half half = state.getValue(TrapDoorBlock.HALF);
|
||||||
|
|
||||||
|
VoxelShape collShape = state.getCollisionShape(world, BlockPos.ZERO, CollisionContext.empty());
|
||||||
|
AABB bb = collShape.isEmpty()
|
||||||
|
? state.getShape(world, BlockPos.ZERO, CollisionContext.empty()).bounds()
|
||||||
|
: collShape.bounds();
|
||||||
|
|
||||||
|
Direction hingeSide = facing.getOpposite();
|
||||||
|
|
||||||
|
float pivotX = (float) ((bb.minX + bb.maxX) * 0.5);
|
||||||
|
float pivotZ = (float) ((bb.minZ + bb.maxZ) * 0.5);
|
||||||
|
switch (hingeSide) {
|
||||||
|
case NORTH -> pivotZ = (float) bb.minZ;
|
||||||
|
case SOUTH -> pivotZ = (float) bb.maxZ;
|
||||||
|
case WEST -> pivotX = (float) bb.minX;
|
||||||
|
case EAST -> pivotX = (float) bb.maxX;
|
||||||
|
default -> {
|
||||||
|
pivotX = (float) ((bb.minX + bb.maxX) * 0.5);
|
||||||
|
pivotZ = (float) ((bb.minZ + bb.maxZ) * 0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
float pivotY = (half == Half.TOP) ? (float) bb.maxY : (float) bb.minY;
|
||||||
|
|
||||||
|
float angle;
|
||||||
|
switch (hingeSide) {
|
||||||
|
case NORTH, EAST -> angle = -baseAngleDeg;
|
||||||
|
case SOUTH, WEST -> angle = baseAngleDeg;
|
||||||
|
default -> angle = baseAngleDeg;
|
||||||
|
}
|
||||||
|
if (half == Half.TOP) angle = -angle;
|
||||||
|
|
||||||
|
float thickness = (float) (bb.maxY - bb.minY);
|
||||||
|
float halfT = thickness > 0.0f ? thickness * 0.5f : (3.0f / 32.0f);
|
||||||
|
float dY = (half == Half.TOP) ? -halfT : halfT;
|
||||||
|
|
||||||
|
double rad = Math.toRadians(angle);
|
||||||
|
float cos = (float) Math.cos(rad);
|
||||||
|
float sin = (float) Math.sin(rad);
|
||||||
|
|
||||||
|
float shiftX = 0.0f;
|
||||||
|
float shiftY = 0.0f;
|
||||||
|
float shiftZ = 0.0f;
|
||||||
|
if (hingeSide == Direction.NORTH || hingeSide == Direction.SOUTH) {
|
||||||
|
float rotY = dY * cos;
|
||||||
|
float rotZ = dY * sin;
|
||||||
|
shiftY = dY - rotY;
|
||||||
|
shiftZ = -rotZ;
|
||||||
|
} else {
|
||||||
|
float rotX = -dY * sin;
|
||||||
|
float rotY = dY * cos;
|
||||||
|
shiftX = -rotX;
|
||||||
|
shiftY = dY - rotY;
|
||||||
|
}
|
||||||
|
|
||||||
|
matrices.translate(shiftX, shiftY, shiftZ);
|
||||||
|
matrices.translate(pivotX, pivotY, pivotZ);
|
||||||
|
if (hingeSide == Direction.NORTH || hingeSide == Direction.SOUTH) {
|
||||||
|
matrices.mulPose(Axis.XP.rotationDegrees(angle));
|
||||||
|
} else {
|
||||||
|
matrices.mulPose(Axis.ZP.rotationDegrees(angle));
|
||||||
|
}
|
||||||
|
matrices.translate(-pivotX, -pivotY, -pivotZ);
|
||||||
|
matrices.translate(-shiftX, -shiftY, -shiftZ);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package com.straice.smoothdoors.mixin.client;
|
||||||
|
|
||||||
|
import com.straice.smoothdoors.client.anim.SddAnimator;
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
|
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||||
|
import net.minecraft.client.renderer.block.ModelBlockRenderer;
|
||||||
|
import net.minecraft.client.resources.model.BakedModel;
|
||||||
|
import net.minecraft.util.RandomSource;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.world.level.BlockAndTintGetter;
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
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.callback.CallbackInfo;
|
||||||
|
|
||||||
|
@Mixin(ModelBlockRenderer.class)
|
||||||
|
public class BlockModelRendererMixin {
|
||||||
|
|
||||||
|
@Inject(
|
||||||
|
method = "tesselateBlock",
|
||||||
|
at = @At("HEAD"),
|
||||||
|
cancellable = true,
|
||||||
|
require = 0
|
||||||
|
)
|
||||||
|
private void sdd$tesselateBlock(BlockAndTintGetter world, BakedModel model, BlockState state, BlockPos pos,
|
||||||
|
PoseStack matrices, VertexConsumer vertices, boolean cull, RandomSource random,
|
||||||
|
long seed, int overlay, CallbackInfo ci) {
|
||||||
|
if (SddAnimator.shouldHideInChunk(pos, state)) {
|
||||||
|
ci.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(
|
||||||
|
method = "tesselateWithAO",
|
||||||
|
at = @At("HEAD"),
|
||||||
|
cancellable = true,
|
||||||
|
require = 0
|
||||||
|
)
|
||||||
|
private void sdd$tesselateWithAo(BlockAndTintGetter world, BakedModel model, BlockState state, BlockPos pos,
|
||||||
|
PoseStack matrices, VertexConsumer vertices, boolean cull, RandomSource random,
|
||||||
|
long seed, int overlay, CallbackInfo ci) {
|
||||||
|
if (SddAnimator.shouldHideInChunk(pos, state)) {
|
||||||
|
ci.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(
|
||||||
|
method = "tesselateWithoutAO",
|
||||||
|
at = @At("HEAD"),
|
||||||
|
cancellable = true,
|
||||||
|
require = 0
|
||||||
|
)
|
||||||
|
private void sdd$tesselateWithoutAo(BlockAndTintGetter world, BakedModel model, BlockState state, BlockPos pos,
|
||||||
|
PoseStack matrices, VertexConsumer vertices, boolean cull, RandomSource random,
|
||||||
|
long seed, int overlay, CallbackInfo ci) {
|
||||||
|
if (SddAnimator.shouldHideInChunk(pos, state)) {
|
||||||
|
ci.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.straice.smoothdoors.mixin.client;
|
||||||
|
|
||||||
|
import com.straice.smoothdoors.client.anim.SddAnimator;
|
||||||
|
import com.mojang.blaze3d.vertex.PoseStack;
|
||||||
|
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||||
|
import net.minecraft.client.renderer.block.BlockRenderDispatcher;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.util.RandomSource;
|
||||||
|
import net.minecraft.world.level.BlockAndTintGetter;
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
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.callback.CallbackInfo;
|
||||||
|
|
||||||
|
@Mixin(BlockRenderDispatcher.class)
|
||||||
|
public class BlockRenderManagerMixin {
|
||||||
|
|
||||||
|
@Inject(
|
||||||
|
method = "renderBatched",
|
||||||
|
at = @At("HEAD"),
|
||||||
|
cancellable = true,
|
||||||
|
require = 0
|
||||||
|
)
|
||||||
|
private void sdd$renderBatched(BlockState state, BlockPos pos, BlockAndTintGetter world,
|
||||||
|
PoseStack matrices, VertexConsumer vertexConsumer,
|
||||||
|
boolean cull, RandomSource random,
|
||||||
|
CallbackInfo ci) {
|
||||||
|
if (SddAnimator.shouldHideInChunk(pos, state)) {
|
||||||
|
ci.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.straice.smoothdoors.mixin.client;
|
||||||
|
|
||||||
|
import com.straice.smoothdoors.client.anim.SddAnimator;
|
||||||
|
import net.minecraft.client.renderer.chunk.RenderChunkRegion;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.world.level.block.Blocks;
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
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.callback.CallbackInfoReturnable;
|
||||||
|
|
||||||
|
@Mixin(RenderChunkRegion.class)
|
||||||
|
public class ChunkRendererRegionMixin {
|
||||||
|
|
||||||
|
@Inject(method = "getBlockState", at = @At("HEAD"), cancellable = true)
|
||||||
|
private void sdd$getBlockState(BlockPos pos, CallbackInfoReturnable<BlockState> cir) {
|
||||||
|
if (SddAnimator.isAnimatingAt(pos)) {
|
||||||
|
cir.setReturnValue(Blocks.AIR.defaultBlockState());
|
||||||
|
cir.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.straice.smoothdoors.mixin.client;
|
||||||
|
|
||||||
|
import com.straice.smoothdoors.client.anim.SddAnimator;
|
||||||
|
import net.minecraft.client.multiplayer.ClientLevel;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
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.callback.CallbackInfo;
|
||||||
|
|
||||||
|
@Mixin(ClientLevel.class)
|
||||||
|
public class ClientWorldMixin {
|
||||||
|
|
||||||
|
@Inject(method = "sendBlockUpdated", at = @At("TAIL"))
|
||||||
|
private void sdd$updateListeners(BlockPos pos, BlockState oldState, BlockState newState, int flags, CallbackInfo ci) {
|
||||||
|
SddAnimator.onBlockUpdate(pos, oldState, newState);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
{
|
{
|
||||||
"required": true,
|
"required": true,
|
||||||
"package": "com.straice.mixin.client",
|
"package": "com.straice.smoothdoors.mixin.client",
|
||||||
"compatibilityLevel": "JAVA_17",
|
"compatibilityLevel": "JAVA_17",
|
||||||
"client": [
|
"client": [
|
||||||
"ExampleClientMixin"
|
"BlockModelRendererMixin",
|
||||||
|
"BlockRenderManagerMixin",
|
||||||
|
"ChunkRendererRegionMixin",
|
||||||
|
"ClientWorldMixin"
|
||||||
],
|
],
|
||||||
"injectors": {
|
"injectors": {
|
||||||
"defaultRequire": 1
|
"defaultRequire": 1
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import net.fabricmc.api.ModInitializer;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.straice.smoothdoors.config.SddConfigManager;
|
||||||
|
|
||||||
public class TemplateMod implements ModInitializer {
|
public class TemplateMod implements ModInitializer {
|
||||||
public static final String MOD_ID = "template-mod";
|
public static final String MOD_ID = "template-mod";
|
||||||
|
|
||||||
@@ -15,10 +17,7 @@ public class TemplateMod implements ModInitializer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onInitialize() {
|
public void onInitialize() {
|
||||||
// This code runs as soon as Minecraft is in a mod-load-ready state.
|
SddConfigManager.load();
|
||||||
// However, some things (like resources) may still be uninitialized.
|
LOGGER.info("Smooth Double Doors loaded");
|
||||||
// Proceed with mild caution.
|
|
||||||
|
|
||||||
LOGGER.info("Hello Fabric world!");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
20
src/main/java/com/straice/smoothdoors/config/SddConfig.java
Normal file
20
src/main/java/com/straice/smoothdoors/config/SddConfig.java
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package com.straice.smoothdoors.config;
|
||||||
|
|
||||||
|
public class SddConfig {
|
||||||
|
public boolean connectDoors = true;
|
||||||
|
public boolean redstoneDoubleDoors = true;
|
||||||
|
|
||||||
|
public boolean connectTrapdoors = false;
|
||||||
|
public boolean redstoneDoubleTrapdoors = false;
|
||||||
|
|
||||||
|
public boolean connectFenceGates = false;
|
||||||
|
public boolean redstoneDoubleFenceGates = false;
|
||||||
|
|
||||||
|
public boolean animateDoors = true;
|
||||||
|
public boolean animateTrapdoors = true;
|
||||||
|
public boolean animateFenceGates = true;
|
||||||
|
|
||||||
|
public float doorSpeed = 1.0f;
|
||||||
|
public float trapdoorSpeed = 1.0f;
|
||||||
|
public float fenceGateSpeed = 1.0f;
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.straice.smoothdoors.config;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import net.fabricmc.loader.api.FabricLoader;
|
||||||
|
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
public final class SddConfigManager {
|
||||||
|
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
|
||||||
|
private static final Path PATH = FabricLoader.getInstance().getConfigDir().resolve("smooth-double-doors.json");
|
||||||
|
|
||||||
|
private static SddConfig config = new SddConfig();
|
||||||
|
|
||||||
|
private SddConfigManager() {}
|
||||||
|
|
||||||
|
public static SddConfig get() { return config; }
|
||||||
|
|
||||||
|
public static void load() {
|
||||||
|
try {
|
||||||
|
if (Files.exists(PATH)) {
|
||||||
|
String json = Files.readString(PATH);
|
||||||
|
SddConfig read = GSON.fromJson(json, SddConfig.class);
|
||||||
|
if (read != null) config = read;
|
||||||
|
} else {
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
// si falla, usamos defaults
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void save() {
|
||||||
|
try {
|
||||||
|
Files.createDirectories(PATH.getParent());
|
||||||
|
Files.writeString(PATH, GSON.toJson(config));
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package com.straice.smoothdoors.mixin;
|
||||||
|
|
||||||
|
import com.straice.smoothdoors.config.SddConfigManager;
|
||||||
|
import com.straice.smoothdoors.util.ConnectedUtil;
|
||||||
|
import com.straice.smoothdoors.util.SyncGuard;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.world.InteractionResult;
|
||||||
|
import net.minecraft.world.entity.player.Player;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
import net.minecraft.world.level.block.Block;
|
||||||
|
import net.minecraft.world.level.block.DoorBlock;
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
import net.minecraft.world.InteractionHand;
|
||||||
|
import net.minecraft.world.phys.BlockHitResult;
|
||||||
|
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.callback.CallbackInfo;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||||
|
|
||||||
|
@Mixin(DoorBlock.class)
|
||||||
|
public class DoorBlockMixin {
|
||||||
|
|
||||||
|
private void sdd$handleUse(BlockState state, Level level, BlockPos pos) {
|
||||||
|
if (level.isClientSide()) return;
|
||||||
|
if (!SddConfigManager.get().connectDoors) return;
|
||||||
|
if (SyncGuard.isIn()) return;
|
||||||
|
|
||||||
|
BlockPos lower = ConnectedUtil.getDoorLowerPos(pos, state);
|
||||||
|
BlockState lowerNow = level.getBlockState(lower);
|
||||||
|
if (!lowerNow.hasProperty(DoorBlock.OPEN)) return;
|
||||||
|
|
||||||
|
boolean openNow = lowerNow.getValue(DoorBlock.OPEN);
|
||||||
|
BlockPos mateLower = ConnectedUtil.findPairedDoorLower(level, lower, lowerNow);
|
||||||
|
if (mateLower == null) return;
|
||||||
|
|
||||||
|
SyncGuard.run(() -> ConnectedUtil.setDoorOpen(level, mateLower, openNow));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "use", at = @At("TAIL"))
|
||||||
|
private void sdd$use(BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand,
|
||||||
|
BlockHitResult hit, CallbackInfoReturnable<InteractionResult> cir) {
|
||||||
|
sdd$handleUse(state, level, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sdd$handleNeighborUpdate(BlockState state, Level level, BlockPos pos) {
|
||||||
|
if (level.isClientSide()) return;
|
||||||
|
if (!SddConfigManager.get().connectDoors) return;
|
||||||
|
if (!SddConfigManager.get().redstoneDoubleDoors) return;
|
||||||
|
if (SyncGuard.isIn()) return;
|
||||||
|
|
||||||
|
BlockPos lower = ConnectedUtil.getDoorLowerPos(pos, state);
|
||||||
|
BlockState lowerNow = level.getBlockState(lower);
|
||||||
|
if (!lowerNow.hasProperty(DoorBlock.OPEN)) return;
|
||||||
|
|
||||||
|
boolean openNow = lowerNow.getValue(DoorBlock.OPEN);
|
||||||
|
BlockPos mateLower = ConnectedUtil.findPairedDoorLower(level, lower, lowerNow);
|
||||||
|
if (mateLower == null) return;
|
||||||
|
|
||||||
|
SyncGuard.run(() -> ConnectedUtil.setDoorOpen(level, mateLower, openNow));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "neighborChanged", at = @At("TAIL"))
|
||||||
|
private void sdd$neighborChanged(BlockState state, Level level, BlockPos pos, Block block,
|
||||||
|
BlockPos fromPos, boolean notify, CallbackInfo ci) {
|
||||||
|
sdd$handleNeighborUpdate(state, level, pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package com.straice.smoothdoors.mixin;
|
||||||
|
|
||||||
|
import com.straice.smoothdoors.config.SddConfigManager;
|
||||||
|
import com.straice.smoothdoors.util.ConnectedUtil;
|
||||||
|
import com.straice.smoothdoors.util.SyncGuard;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.world.InteractionResult;
|
||||||
|
import net.minecraft.world.entity.player.Player;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
import net.minecraft.world.level.block.Block;
|
||||||
|
import net.minecraft.world.level.block.FenceGateBlock;
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
import net.minecraft.world.InteractionHand;
|
||||||
|
import net.minecraft.world.phys.BlockHitResult;
|
||||||
|
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.callback.CallbackInfo;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||||
|
|
||||||
|
@Mixin(FenceGateBlock.class)
|
||||||
|
public class FenceGateBlockMixin {
|
||||||
|
|
||||||
|
private void sdd$handleUse(BlockState state, Level level, BlockPos pos) {
|
||||||
|
if (level.isClientSide()) return;
|
||||||
|
if (!SddConfigManager.get().connectFenceGates) return;
|
||||||
|
if (SyncGuard.isIn()) return;
|
||||||
|
|
||||||
|
BlockState now = level.getBlockState(pos);
|
||||||
|
if (!now.hasProperty(FenceGateBlock.OPEN)) return;
|
||||||
|
|
||||||
|
boolean openNow = now.getValue(FenceGateBlock.OPEN);
|
||||||
|
BlockPos mate = ConnectedUtil.findPairedFenceGate(level, pos, now);
|
||||||
|
if (mate == null) return;
|
||||||
|
|
||||||
|
SyncGuard.run(() -> ConnectedUtil.setFenceGateOpen(level, mate, openNow));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "use", at = @At("TAIL"))
|
||||||
|
private void sdd$use(BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand,
|
||||||
|
BlockHitResult hit, CallbackInfoReturnable<InteractionResult> cir) {
|
||||||
|
sdd$handleUse(state, level, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sdd$handleNeighborUpdate(BlockState state, Level level, BlockPos pos) {
|
||||||
|
if (level.isClientSide()) return;
|
||||||
|
if (!SddConfigManager.get().connectFenceGates) return;
|
||||||
|
if (!SddConfigManager.get().redstoneDoubleFenceGates) return;
|
||||||
|
if (SyncGuard.isIn()) return;
|
||||||
|
|
||||||
|
BlockState now = level.getBlockState(pos);
|
||||||
|
if (!now.hasProperty(FenceGateBlock.OPEN)) return;
|
||||||
|
|
||||||
|
boolean openNow = now.getValue(FenceGateBlock.OPEN);
|
||||||
|
BlockPos mate = ConnectedUtil.findPairedFenceGate(level, pos, now);
|
||||||
|
if (mate == null) return;
|
||||||
|
|
||||||
|
SyncGuard.run(() -> ConnectedUtil.setFenceGateOpen(level, mate, openNow));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "neighborChanged", at = @At("TAIL"))
|
||||||
|
private void sdd$neighborChanged(BlockState state, Level level, BlockPos pos, Block block,
|
||||||
|
BlockPos fromPos, boolean notify, CallbackInfo ci) {
|
||||||
|
sdd$handleNeighborUpdate(state, level, pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package com.straice.smoothdoors.mixin;
|
||||||
|
|
||||||
|
import com.straice.smoothdoors.config.SddConfigManager;
|
||||||
|
import com.straice.smoothdoors.util.ConnectedUtil;
|
||||||
|
import com.straice.smoothdoors.util.SyncGuard;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.world.InteractionResult;
|
||||||
|
import net.minecraft.world.entity.player.Player;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
import net.minecraft.world.level.block.Block;
|
||||||
|
import net.minecraft.world.level.block.TrapDoorBlock;
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
import net.minecraft.world.InteractionHand;
|
||||||
|
import net.minecraft.world.phys.BlockHitResult;
|
||||||
|
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.callback.CallbackInfo;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||||
|
|
||||||
|
@Mixin(TrapDoorBlock.class)
|
||||||
|
public class TrapdoorBlockMixin {
|
||||||
|
|
||||||
|
private void sdd$handleUse(BlockState state, Level level, BlockPos pos) {
|
||||||
|
if (level.isClientSide()) return;
|
||||||
|
if (!SddConfigManager.get().connectTrapdoors) return;
|
||||||
|
if (SyncGuard.isIn()) return;
|
||||||
|
|
||||||
|
BlockState now = level.getBlockState(pos);
|
||||||
|
if (!now.hasProperty(TrapDoorBlock.OPEN)) return;
|
||||||
|
|
||||||
|
boolean openNow = now.getValue(TrapDoorBlock.OPEN);
|
||||||
|
BlockPos mate = ConnectedUtil.findPairedTrapdoor(level, pos, now);
|
||||||
|
if (mate == null) return;
|
||||||
|
|
||||||
|
SyncGuard.run(() -> ConnectedUtil.setTrapdoorOpen(level, mate, openNow));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "use", at = @At("TAIL"))
|
||||||
|
private void sdd$use(BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand,
|
||||||
|
BlockHitResult hit, CallbackInfoReturnable<InteractionResult> cir) {
|
||||||
|
sdd$handleUse(state, level, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sdd$handleNeighborUpdate(BlockState state, Level level, BlockPos pos) {
|
||||||
|
if (level.isClientSide()) return;
|
||||||
|
if (!SddConfigManager.get().connectTrapdoors) return;
|
||||||
|
if (!SddConfigManager.get().redstoneDoubleTrapdoors) return;
|
||||||
|
if (SyncGuard.isIn()) return;
|
||||||
|
|
||||||
|
BlockState now = level.getBlockState(pos);
|
||||||
|
if (!now.hasProperty(TrapDoorBlock.OPEN)) return;
|
||||||
|
|
||||||
|
boolean openNow = now.getValue(TrapDoorBlock.OPEN);
|
||||||
|
BlockPos mate = ConnectedUtil.findPairedTrapdoor(level, pos, now);
|
||||||
|
if (mate == null) return;
|
||||||
|
|
||||||
|
SyncGuard.run(() -> ConnectedUtil.setTrapdoorOpen(level, mate, openNow));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Inject(method = "neighborChanged", at = @At("TAIL"))
|
||||||
|
private void sdd$neighborChanged(BlockState state, Level level, BlockPos pos, Block block,
|
||||||
|
BlockPos fromPos, boolean notify, CallbackInfo ci) {
|
||||||
|
sdd$handleNeighborUpdate(state, level, pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
125
src/main/java/com/straice/smoothdoors/util/ConnectedUtil.java
Normal file
125
src/main/java/com/straice/smoothdoors/util/ConnectedUtil.java
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
package com.straice.smoothdoors.util;
|
||||||
|
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.core.Direction;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
import net.minecraft.world.level.block.Block;
|
||||||
|
import net.minecraft.world.level.block.DoorBlock;
|
||||||
|
import net.minecraft.world.level.block.FenceGateBlock;
|
||||||
|
import net.minecraft.world.level.block.TrapDoorBlock;
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
import net.minecraft.world.level.block.state.properties.DoorHingeSide;
|
||||||
|
import net.minecraft.world.level.block.state.properties.DoubleBlockHalf;
|
||||||
|
import net.minecraft.world.level.block.state.properties.Half;
|
||||||
|
|
||||||
|
public final class ConnectedUtil {
|
||||||
|
private ConnectedUtil() {}
|
||||||
|
|
||||||
|
// ===== DOORS =====
|
||||||
|
|
||||||
|
public static BlockPos getDoorLowerPos(BlockPos pos, BlockState state) {
|
||||||
|
DoubleBlockHalf half = state.getValue(DoorBlock.HALF);
|
||||||
|
return half == DoubleBlockHalf.LOWER ? pos : pos.below();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BlockPos findPairedDoorLower(Level world, BlockPos lowerPos, BlockState lowerState) {
|
||||||
|
if (!(lowerState.getBlock() instanceof DoorBlock)) return null;
|
||||||
|
|
||||||
|
Direction facing = lowerState.getValue(DoorBlock.FACING);
|
||||||
|
DoorHingeSide hinge = lowerState.getValue(DoorBlock.HINGE);
|
||||||
|
|
||||||
|
Direction side = (hinge == DoorHingeSide.LEFT)
|
||||||
|
? facing.getClockWise()
|
||||||
|
: facing.getCounterClockWise();
|
||||||
|
|
||||||
|
BlockPos otherLower = lowerPos.relative(side);
|
||||||
|
BlockState other = world.getBlockState(otherLower);
|
||||||
|
|
||||||
|
if (other.getBlock() != lowerState.getBlock()) return null;
|
||||||
|
if (!(other.getBlock() instanceof DoorBlock)) return null;
|
||||||
|
if (other.getValue(DoorBlock.HALF) != DoubleBlockHalf.LOWER) return null;
|
||||||
|
if (other.getValue(DoorBlock.FACING) != facing) return null;
|
||||||
|
if (other.getValue(DoorBlock.HINGE) == hinge) return null;
|
||||||
|
|
||||||
|
return otherLower;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setDoorOpen(Level world, BlockPos doorLower, boolean open) {
|
||||||
|
BlockState lower = world.getBlockState(doorLower);
|
||||||
|
if (!(lower.getBlock() instanceof DoorBlock)) return;
|
||||||
|
|
||||||
|
BlockPos upperPos = doorLower.above();
|
||||||
|
BlockState upper = world.getBlockState(upperPos);
|
||||||
|
|
||||||
|
world.setBlock(doorLower, lower.setValue(DoorBlock.OPEN, open), Block.UPDATE_ALL);
|
||||||
|
|
||||||
|
if (upper.getBlock() == lower.getBlock()) {
|
||||||
|
world.setBlock(upperPos, upper.setValue(DoorBlock.OPEN, open), Block.UPDATE_ALL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== TRAPDOORS =====
|
||||||
|
|
||||||
|
public static BlockPos findPairedTrapdoor(Level world, BlockPos pos, BlockState state) {
|
||||||
|
if (!(state.getBlock() instanceof TrapDoorBlock)) return null;
|
||||||
|
|
||||||
|
Direction facing = state.getValue(TrapDoorBlock.FACING);
|
||||||
|
Half half = state.getValue(TrapDoorBlock.HALF);
|
||||||
|
|
||||||
|
BlockPos left = pos.relative(facing.getCounterClockWise());
|
||||||
|
BlockPos right = pos.relative(facing.getClockWise());
|
||||||
|
|
||||||
|
BlockPos mate = matchTrapdoor(world, state, left, facing, half);
|
||||||
|
if (mate != null) return mate;
|
||||||
|
return matchTrapdoor(world, state, right, facing, half);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BlockPos matchTrapdoor(Level world, BlockState a, BlockPos candidate, Direction facing, Half half) {
|
||||||
|
BlockState b = world.getBlockState(candidate);
|
||||||
|
if (b.getBlock() != a.getBlock()) return null;
|
||||||
|
if (!(b.getBlock() instanceof TrapDoorBlock)) return null;
|
||||||
|
|
||||||
|
if (b.getValue(TrapDoorBlock.FACING) != facing) return null;
|
||||||
|
if (b.getValue(TrapDoorBlock.HALF) != half) return null;
|
||||||
|
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setTrapdoorOpen(Level world, BlockPos pos, boolean open) {
|
||||||
|
BlockState st = world.getBlockState(pos);
|
||||||
|
if (!(st.getBlock() instanceof TrapDoorBlock)) return;
|
||||||
|
|
||||||
|
world.setBlock(pos, st.setValue(TrapDoorBlock.OPEN, open), Block.UPDATE_ALL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== FENCE GATES =====
|
||||||
|
|
||||||
|
public static BlockPos findPairedFenceGate(Level world, BlockPos pos, BlockState state) {
|
||||||
|
if (!(state.getBlock() instanceof FenceGateBlock)) return null;
|
||||||
|
|
||||||
|
Direction facing = state.getValue(FenceGateBlock.FACING);
|
||||||
|
|
||||||
|
BlockPos left = pos.relative(facing.getCounterClockWise());
|
||||||
|
BlockPos right = pos.relative(facing.getClockWise());
|
||||||
|
|
||||||
|
BlockPos mate = matchFenceGate(world, state, left, facing);
|
||||||
|
if (mate != null) return mate;
|
||||||
|
return matchFenceGate(world, state, right, facing);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BlockPos matchFenceGate(Level world, BlockState a, BlockPos candidate, Direction facing) {
|
||||||
|
BlockState b = world.getBlockState(candidate);
|
||||||
|
if (b.getBlock() != a.getBlock()) return null;
|
||||||
|
if (!(b.getBlock() instanceof FenceGateBlock)) return null;
|
||||||
|
|
||||||
|
if (b.getValue(FenceGateBlock.FACING) != facing) return null;
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setFenceGateOpen(Level world, BlockPos pos, boolean open) {
|
||||||
|
BlockState st = world.getBlockState(pos);
|
||||||
|
if (!(st.getBlock() instanceof FenceGateBlock)) return;
|
||||||
|
|
||||||
|
world.setBlock(pos, st.setValue(FenceGateBlock.OPEN, open), Block.UPDATE_ALL);
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/main/java/com/straice/smoothdoors/util/SyncGuard.java
Normal file
16
src/main/java/com/straice/smoothdoors/util/SyncGuard.java
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package com.straice.smoothdoors.util;
|
||||||
|
|
||||||
|
public final class SyncGuard {
|
||||||
|
private static final ThreadLocal<Boolean> IN = ThreadLocal.withInitial(() -> false);
|
||||||
|
|
||||||
|
private SyncGuard() {}
|
||||||
|
|
||||||
|
public static boolean isIn() { return IN.get(); }
|
||||||
|
|
||||||
|
public static void run(Runnable r) {
|
||||||
|
if (isIn()) return;
|
||||||
|
IN.set(true);
|
||||||
|
try { r.run(); }
|
||||||
|
finally { IN.set(false); }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
{
|
{
|
||||||
"required": true,
|
"required": true,
|
||||||
"package": "com.straice.mixin",
|
"package": "com.straice.smoothdoors.mixin",
|
||||||
"compatibilityLevel": "JAVA_17",
|
"compatibilityLevel": "JAVA_17",
|
||||||
"mixins": [
|
"mixins": [
|
||||||
"DoorBlockMixin",
|
"DoorBlockMixin",
|
||||||
"ExampleMixin"
|
"FenceGateBlockMixin",
|
||||||
|
"TrapdoorBlockMixin"
|
||||||
],
|
],
|
||||||
"injectors": {
|
"injectors": {
|
||||||
"defaultRequire": 1
|
"defaultRequire": 1
|
||||||
|
|||||||
Reference in New Issue
Block a user