diff --git a/src/generated/resources/assets/anvilcraft/blockstates/redstone_wire.json b/src/generated/resources/assets/anvilcraft/blockstates/redstone_wire.json index 6d55633853..38dab8467c 100644 --- a/src/generated/resources/assets/anvilcraft/blockstates/redstone_wire.json +++ b/src/generated/resources/assets/anvilcraft/blockstates/redstone_wire.json @@ -189,6 +189,15 @@ "north": "corner" } }, + { + "apply": { + "model": "anvilcraft:block/redstone_wire/north/side_corner_sp_0" + }, + "when": { + "attachment": "north", + "north": "corner_sp" + } + }, { "apply": { "model": "anvilcraft:block/redstone_wire/north/up_0" @@ -306,6 +315,15 @@ "north": "corner" } }, + { + "apply": { + "model": "anvilcraft:block/redstone_wire/south/side_corner_sp_0" + }, + "when": { + "attachment": "south", + "north": "corner_sp" + } + }, { "apply": { "model": "anvilcraft:block/redstone_wire/south/up_0" @@ -423,6 +441,15 @@ "north": "corner" } }, + { + "apply": { + "model": "anvilcraft:block/redstone_wire/west/side_corner_sp_0" + }, + "when": { + "attachment": "west", + "north": "corner_sp" + } + }, { "apply": { "model": "anvilcraft:block/redstone_wire/west/up_0" @@ -540,6 +567,15 @@ "north": "corner" } }, + { + "apply": { + "model": "anvilcraft:block/redstone_wire/east/side_corner_sp_0" + }, + "when": { + "attachment": "east", + "north": "corner_sp" + } + }, { "apply": { "model": "anvilcraft:block/redstone_wire/east/up_0" diff --git a/src/generated/resources/assets/anvilcraft/models/block/redstone_wire/east/side_corner_sp_0.json b/src/generated/resources/assets/anvilcraft/models/block/redstone_wire/east/side_corner_sp_0.json new file mode 100644 index 0000000000..19380426bb --- /dev/null +++ b/src/generated/resources/assets/anvilcraft/models/block/redstone_wire/east/side_corner_sp_0.json @@ -0,0 +1,136 @@ +{ + "ambientocclusion": false, + "elements": [ + { + "faces": { + "east": { + "cullface": "east", + "rotation": 180, + "texture": "#0", + "uv": [ + 11.0, + 0.0, + 5.0, + 8.0 + ] + }, + "north": { + "rotation": 180, + "texture": "#0", + "uv": [ + 5.0, + 8.0, + 6.0, + 0.0 + ] + }, + "south": { + "texture": "#0", + "uv": [ + 10.0, + 0.0, + 11.0, + 8.0 + ] + }, + "up": { + "cullface": "up", + "rotation": 270, + "texture": "#0", + "uv": [ + 5.0, + 0.0, + 11.0, + 1.0 + ] + }, + "west": { + "texture": "#0" + } + }, + "from": [ + 15, + 8, + 5 + ], + "to": [ + 16, + 16, + 11 + ] + }, + { + "faces": { + "east": { + "rotation": 180, + "texture": "#1", + "tintindex": 0, + "uv": [ + 6.0, + 0.0, + 10.0, + 9.0 + ] + }, + "north": { + "rotation": 180, + "texture": "#1", + "tintindex": 0, + "uv": [ + 6.0, + 0.0, + 7.0, + 9.0 + ] + }, + "south": { + "texture": "#1", + "tintindex": 0, + "uv": [ + 6.0, + 0.0, + 7.0, + 9.0 + ] + }, + "up": { + "cullface": "up", + "rotation": 270, + "texture": "#1", + "tintindex": 0, + "uv": [ + 6.0, + 0.0, + 10.0, + 1.0 + ] + }, + "west": { + "texture": "#1", + "tintindex": 0, + "uv": [ + 6.0, + 0.0, + 10.0, + 9.0 + ] + } + }, + "from": [ + 14, + 8, + 6 + ], + "to": [ + 15.99, + 17, + 10 + ] + } + ], + "textures": { + "0": "anvilcraft:block/redstone_wire_line", + "1": "anvilcraft:block/redstone_wire_line_overlay", + "particle": "anvilcraft:block/redstone_wire_line" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/anvilcraft/models/block/redstone_wire/north/side_corner_sp_0.json b/src/generated/resources/assets/anvilcraft/models/block/redstone_wire/north/side_corner_sp_0.json new file mode 100644 index 0000000000..7fb7008a7d --- /dev/null +++ b/src/generated/resources/assets/anvilcraft/models/block/redstone_wire/north/side_corner_sp_0.json @@ -0,0 +1,130 @@ +{ + "ambientocclusion": false, + "elements": [ + { + "faces": { + "east": { + "texture": "#0", + "uv": [ + 10.0, + 0.0, + 11.0, + 8.0 + ] + }, + "north": { + "cullface": "north", + "rotation": 180, + "texture": "#0", + "uv": [ + 11.0, + 0.0, + 5.0, + 8.0 + ] + }, + "south": { + "texture": "#0" + }, + "up": { + "cullface": "up", + "rotation": 180, + "texture": "#0" + }, + "west": { + "rotation": 180, + "texture": "#0", + "uv": [ + 5.0, + 8.0, + 6.0, + 0.0 + ] + } + }, + "from": [ + 5, + 8, + 0 + ], + "to": [ + 11, + 16, + 1 + ] + }, + { + "faces": { + "east": { + "texture": "#1", + "tintindex": 0, + "uv": [ + 6.0, + 0.0, + 7.0, + 9.0 + ] + }, + "north": { + "rotation": 180, + "texture": "#1", + "tintindex": 0, + "uv": [ + 6.0, + 0.0, + 10.0, + 9.0 + ] + }, + "south": { + "texture": "#1", + "tintindex": 0, + "uv": [ + 6.0, + 0.0, + 10.0, + 9.0 + ] + }, + "up": { + "cullface": "up", + "rotation": 180, + "texture": "#1", + "tintindex": 0, + "uv": [ + 6.0, + 0.0, + 10.0, + 1.0 + ] + }, + "west": { + "rotation": 180, + "texture": "#1", + "tintindex": 0, + "uv": [ + 6.0, + 0.0, + 7.0, + 9.0 + ] + } + }, + "from": [ + 6, + 8, + 0.01 + ], + "to": [ + 10, + 17, + 2 + ] + } + ], + "textures": { + "0": "anvilcraft:block/redstone_wire_line", + "1": "anvilcraft:block/redstone_wire_line_overlay", + "particle": "anvilcraft:block/redstone_wire_line" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/anvilcraft/models/block/redstone_wire/south/side_corner_sp_0.json b/src/generated/resources/assets/anvilcraft/models/block/redstone_wire/south/side_corner_sp_0.json new file mode 100644 index 0000000000..c7d1a7476a --- /dev/null +++ b/src/generated/resources/assets/anvilcraft/models/block/redstone_wire/south/side_corner_sp_0.json @@ -0,0 +1,134 @@ +{ + "ambientocclusion": false, + "elements": [ + { + "faces": { + "east": { + "rotation": 180, + "texture": "#0", + "uv": [ + 5.0, + 8.0, + 6.0, + 0.0 + ] + }, + "north": { + "texture": "#0" + }, + "south": { + "cullface": "south", + "rotation": 180, + "texture": "#0", + "uv": [ + 11.0, + 0.0, + 5.0, + 8.0 + ] + }, + "up": { + "cullface": "up", + "texture": "#0", + "uv": [ + 5.0, + 0.0, + 11.0, + 1.0 + ] + }, + "west": { + "texture": "#0", + "uv": [ + 10.0, + 0.0, + 11.0, + 8.0 + ] + } + }, + "from": [ + 5, + 8, + 15 + ], + "to": [ + 11, + 16, + 16 + ] + }, + { + "faces": { + "east": { + "rotation": 180, + "texture": "#1", + "tintindex": 0, + "uv": [ + 6.0, + 0.0, + 7.0, + 9.0 + ] + }, + "north": { + "texture": "#1", + "tintindex": 0, + "uv": [ + 6.0, + 0.0, + 10.0, + 9.0 + ] + }, + "south": { + "rotation": 180, + "texture": "#1", + "tintindex": 0, + "uv": [ + 6.0, + 0.0, + 10.0, + 9.0 + ] + }, + "up": { + "cullface": "up", + "texture": "#1", + "tintindex": 0, + "uv": [ + 6.0, + 0.0, + 10.0, + 1.0 + ] + }, + "west": { + "texture": "#1", + "tintindex": 0, + "uv": [ + 6.0, + 0.0, + 7.0, + 9.0 + ] + } + }, + "from": [ + 6, + 8, + 14 + ], + "to": [ + 10, + 17, + 15.99 + ] + } + ], + "textures": { + "0": "anvilcraft:block/redstone_wire_line", + "1": "anvilcraft:block/redstone_wire_line_overlay", + "particle": "anvilcraft:block/redstone_wire_line" + } +} \ No newline at end of file diff --git a/src/generated/resources/assets/anvilcraft/models/block/redstone_wire/west/side_corner_sp_0.json b/src/generated/resources/assets/anvilcraft/models/block/redstone_wire/west/side_corner_sp_0.json new file mode 100644 index 0000000000..7ec3ba9e42 --- /dev/null +++ b/src/generated/resources/assets/anvilcraft/models/block/redstone_wire/west/side_corner_sp_0.json @@ -0,0 +1,136 @@ +{ + "ambientocclusion": false, + "elements": [ + { + "faces": { + "east": { + "texture": "#0" + }, + "north": { + "texture": "#0", + "uv": [ + 10.0, + 0.0, + 11.0, + 8.0 + ] + }, + "south": { + "rotation": 180, + "texture": "#0", + "uv": [ + 5.0, + 8.0, + 6.0, + 0.0 + ] + }, + "up": { + "cullface": "up", + "rotation": 90, + "texture": "#0", + "uv": [ + 5.0, + 0.0, + 11.0, + 1.0 + ] + }, + "west": { + "cullface": "west", + "rotation": 180, + "texture": "#0", + "uv": [ + 11.0, + 0.0, + 5.0, + 8.0 + ] + } + }, + "from": [ + 0, + 8, + 5 + ], + "to": [ + 1, + 16, + 11 + ] + }, + { + "faces": { + "east": { + "texture": "#1", + "tintindex": 0, + "uv": [ + 6.0, + 0.0, + 10.0, + 9.0 + ] + }, + "north": { + "texture": "#1", + "tintindex": 0, + "uv": [ + 6.0, + 0.0, + 7.0, + 9.0 + ] + }, + "south": { + "rotation": 180, + "texture": "#1", + "tintindex": 0, + "uv": [ + 6.0, + 0.0, + 7.0, + 9.0 + ] + }, + "up": { + "cullface": "up", + "rotation": 90, + "texture": "#1", + "tintindex": 0, + "uv": [ + 6.0, + 0.0, + 10.0, + 1.0 + ] + }, + "west": { + "rotation": 180, + "texture": "#1", + "tintindex": 0, + "uv": [ + 6.0, + 0.0, + 10.0, + 9.0 + ] + } + }, + "from": [ + 0.01, + 8, + 6 + ], + "to": [ + 2, + 17, + 10 + ] + } + ], + "textures": { + "0": "anvilcraft:block/redstone_wire_line", + "1": "anvilcraft:block/redstone_wire_line_overlay", + "particle": "anvilcraft:block/redstone_wire_line" + } +} \ No newline at end of file diff --git a/src/main/java/dev/dubhe/anvilcraft/anvil/TimeWarpPlayerBehavior.java b/src/main/java/dev/dubhe/anvilcraft/anvil/TimeWarpPlayerBehavior.java index f61f2874db..b1981bdab5 100644 --- a/src/main/java/dev/dubhe/anvilcraft/anvil/TimeWarpPlayerBehavior.java +++ b/src/main/java/dev/dubhe/anvilcraft/anvil/TimeWarpPlayerBehavior.java @@ -5,6 +5,7 @@ import dev.dubhe.anvilcraft.block.CorruptedBeaconBlock; import dev.dubhe.anvilcraft.init.block.ModBlocks; import dev.dubhe.anvilcraft.init.entity.ModDamageTypes; +import dev.dubhe.anvilcraft.util.CauldronUtil; import net.minecraft.core.BlockPos; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; @@ -17,12 +18,19 @@ public class TimeWarpPlayerBehavior implements IAnvilBehavior { @Override public boolean handle(Level level, BlockPos hitBlockPos, BlockState hitBlockState, float fallDistance, AnvilEvent.OnLand event) { if (!(level instanceof ServerLevel serverLevel)) return false; - BlockState below = level.getBlockState(hitBlockPos.below()); - if (!below.is(ModBlocks.CORRUPTED_BEACON)) return false; - if (!below.getValue(CorruptedBeaconBlock.LIT)) return false; - List players = serverLevel.getPlayers(player -> player.getOnPos().equals(hitBlockPos)); + boolean activeBeacon = CauldronUtil.getBottomPositions(hitBlockPos, hitBlockState).stream() + .map(level::getBlockState) + .anyMatch(TimeWarpPlayerBehavior::isActiveCorruptedBeacon); + if (!activeBeacon) return false; + List players = serverLevel.getPlayers( + player -> CauldronUtil.isEntityInside(hitBlockPos, hitBlockState, player) + ); if (players.isEmpty()) return false; for (ServerPlayer player : players) player.hurt(ModDamageTypes.lostInTime(level), Float.MAX_VALUE); return true; } + + private static boolean isActiveCorruptedBeacon(BlockState state) { + return state.is(ModBlocks.CORRUPTED_BEACON) && state.getValue(CorruptedBeaconBlock.LIT); + } } diff --git a/src/main/java/dev/dubhe/anvilcraft/api/block/ICauldronGeometry.java b/src/main/java/dev/dubhe/anvilcraft/api/block/ICauldronGeometry.java new file mode 100644 index 0000000000..32557ad155 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/api/block/ICauldronGeometry.java @@ -0,0 +1,16 @@ +package dev.dubhe.anvilcraft.api.block; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.AABB; + +import java.util.List; + +/** + * Describes the interior and supporting blocks of a non-standard cauldron. + */ +public interface ICauldronGeometry { + AABB getCauldronInnerArea(BlockPos pos, BlockState state); + + List getCauldronBottomPositions(BlockPos pos, BlockState state); +} diff --git a/src/main/java/dev/dubhe/anvilcraft/api/block/IDamagingHeater.java b/src/main/java/dev/dubhe/anvilcraft/api/block/IDamagingHeater.java new file mode 100644 index 0000000000..36003ae5fe --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/api/block/IDamagingHeater.java @@ -0,0 +1,10 @@ +package dev.dubhe.anvilcraft.api.block; + +import net.minecraft.world.level.block.state.BlockState; + +/** + * A heater that damages entities standing on it or in a cauldron above it. + */ +public interface IDamagingHeater { + boolean isActive(BlockState state); +} diff --git a/src/main/java/dev/dubhe/anvilcraft/api/entity/IAnvilCraftEntityExtension.java b/src/main/java/dev/dubhe/anvilcraft/api/entity/IAnvilCraftEntityExtension.java new file mode 100644 index 0000000000..9754241fac --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/api/entity/IAnvilCraftEntityExtension.java @@ -0,0 +1,28 @@ +package dev.dubhe.anvilcraft.api.entity; + +import dev.dubhe.anvilcraft.util.GravityType; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.phys.Vec3; + +public interface IAnvilCraftEntityExtension { + default GravityType anvilcraft$getGravityType() { + return null; + } + + default Vec3 anvilcraft$getAdditionalGravity(double baseGravity) { + return Vec3.ZERO; + } + + default boolean anvilcraft$isMagnetized() { + return false; + } + + default boolean anvilcraft$canCollisionCraft() { + return true; + } + + default boolean anvilcraft$acceptMagnetization(Player player, ItemStack stack) { + return false; + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/api/entity/IEntityCauldron.java b/src/main/java/dev/dubhe/anvilcraft/api/entity/IEntityCauldron.java new file mode 100644 index 0000000000..8233eef739 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/api/entity/IEntityCauldron.java @@ -0,0 +1,12 @@ +package dev.dubhe.anvilcraft.api.entity; + +import dev.dubhe.anvilcraft.api.fluid.IFluidHandlerHolder; + +public interface IEntityCauldron extends IFluidHandlerHolder { + default boolean anvilcraft$isIgnited() { + return false; + } + + default void anvilcraft$setIgnited(boolean ignited) { + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/block/BurningHeaterBlock.java b/src/main/java/dev/dubhe/anvilcraft/block/BurningHeaterBlock.java index ff9e8e183e..b24b65ed9b 100644 --- a/src/main/java/dev/dubhe/anvilcraft/block/BurningHeaterBlock.java +++ b/src/main/java/dev/dubhe/anvilcraft/block/BurningHeaterBlock.java @@ -1,15 +1,15 @@ package dev.dubhe.anvilcraft.block; import com.mojang.serialization.MapCodec; +import dev.dubhe.anvilcraft.api.block.IDamagingHeater; import dev.dubhe.anvilcraft.api.hammer.IHammerRemovable; import dev.dubhe.anvilcraft.block.entity.BurningHeaterBlockEntity; import dev.dubhe.anvilcraft.init.block.ModBlockEntities; -import dev.dubhe.anvilcraft.init.entity.ModDamageTypes; +import dev.dubhe.anvilcraft.util.HeaterUtil; import net.minecraft.core.BlockPos; import net.minecraft.world.Containers; import net.minecraft.world.InteractionResult; import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.Level; @@ -26,7 +26,7 @@ import net.neoforged.neoforge.items.ItemStackHandler; import org.jetbrains.annotations.Nullable; -public class BurningHeaterBlock extends BaseEntityBlock implements IHammerRemovable { +public class BurningHeaterBlock extends BaseEntityBlock implements IHammerRemovable, IDamagingHeater { /** * 燃烧等级:0=熄灭,1=阴燃(0-300s),2=点燃(≥300s) */ @@ -118,11 +118,12 @@ public int getAnalogOutputSignal(BlockState blockState, Level level, BlockPos po @Override public void stepOn(Level level, BlockPos pos, BlockState state, Entity entity) { - if (state.getValue(LEVEL) > 0 - && !entity.isSteppingCarefully() - && entity instanceof LivingEntity) { - entity.hurt(ModDamageTypes.heaterBurn(level), 4.0F); - } + HeaterUtil.hurtEntity(level, state, entity); super.stepOn(level, pos, state, entity); } + + @Override + public boolean isActive(BlockState state) { + return state.getValue(LEVEL) > 0; + } } diff --git a/src/main/java/dev/dubhe/anvilcraft/block/HeaterBlock.java b/src/main/java/dev/dubhe/anvilcraft/block/HeaterBlock.java index 32639fe6e4..dfd63c0f1f 100644 --- a/src/main/java/dev/dubhe/anvilcraft/block/HeaterBlock.java +++ b/src/main/java/dev/dubhe/anvilcraft/block/HeaterBlock.java @@ -1,15 +1,14 @@ package dev.dubhe.anvilcraft.block; import com.mojang.serialization.MapCodec; +import dev.dubhe.anvilcraft.api.block.IDamagingHeater; import dev.dubhe.anvilcraft.api.hammer.IHammerRemovable; import dev.dubhe.anvilcraft.api.power.IPowerComponent; import dev.dubhe.anvilcraft.block.entity.HeaterBlockEntity; import dev.dubhe.anvilcraft.init.block.ModBlockEntities; -import dev.dubhe.anvilcraft.init.block.ModBlocks; -import dev.dubhe.anvilcraft.init.entity.ModDamageTypes; +import dev.dubhe.anvilcraft.util.HeaterUtil; import net.minecraft.core.BlockPos; import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.item.context.BlockPlaceContext; import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.Level; @@ -29,7 +28,7 @@ import net.minecraft.world.phys.shapes.VoxelShape; import org.jetbrains.annotations.Nullable; -public class HeaterBlock extends BaseEntityBlock implements IHammerRemovable { +public class HeaterBlock extends BaseEntityBlock implements IHammerRemovable, IDamagingHeater { public static final VoxelShape SHAPE = Shapes.or(Block.box(0, 2, 0, 16, 16, 16), Block.box(1, 0, 1, 15, 2, 15)); public static final BooleanProperty POWERED = BlockStateProperties.POWERED; public static final BooleanProperty OVERLOAD = IPowerComponent.OVERLOAD; @@ -95,16 +94,15 @@ public BlockEntityTicker getTicker( @Override public void stepOn(Level level, BlockPos pos, BlockState state, Entity entity) { - if (state.is(ModBlocks.HEATER.get()) - && !state.getValue(POWERED) - && !state.getValue(OVERLOAD) - && !entity.isSteppingCarefully() - && entity instanceof LivingEntity) { - entity.hurt(ModDamageTypes.heaterBurn(level), 4.0F); - } + HeaterUtil.hurtEntity(level, state, entity); super.stepOn(level, pos, state, entity); } + @Override + public boolean isActive(BlockState state) { + return !state.getValue(POWERED) && !state.getValue(OVERLOAD); + } + @Override public VoxelShape getShape( diff --git a/src/main/java/dev/dubhe/anvilcraft/block/LargeCauldronBlock.java b/src/main/java/dev/dubhe/anvilcraft/block/LargeCauldronBlock.java index 3698d357b7..9f66cb5189 100644 --- a/src/main/java/dev/dubhe/anvilcraft/block/LargeCauldronBlock.java +++ b/src/main/java/dev/dubhe/anvilcraft/block/LargeCauldronBlock.java @@ -1,5 +1,6 @@ package dev.dubhe.anvilcraft.block; +import dev.dubhe.anvilcraft.api.block.ICauldronGeometry; import dev.dubhe.anvilcraft.api.hammer.IHammerRemovable; import dev.dubhe.anvilcraft.block.entity.LargeCauldronBlockEntity; import dev.dubhe.anvilcraft.block.item.SimpleMultiPartBlockItem; @@ -44,13 +45,14 @@ import org.jetbrains.annotations.Nullable; import java.util.EnumMap; +import java.util.List; import java.util.Map; import static dev.dubhe.anvilcraft.block.PropelPiston.createTickerHelper; public class LargeCauldronBlock extends SimpleMultiPartBlock - implements MultiPartBlockEntity, IHammerRemovable { + implements MultiPartBlockEntity, IHammerRemovable, ICauldronGeometry { public static final EnumProperty HALF = EnumProperty.create("half", Cube3x3PartHalf.class); private static final double WALL_THICKNESS = 0.25; private static final double BOTTOM_WALL_MIN_Y = 0.5; @@ -97,6 +99,27 @@ public LargeCauldronBlock getMultiBlock() { return this; } + @Override + public AABB getCauldronInnerArea(BlockPos pos, BlockState state) { + BlockPos mainPos = this.getMainPartPos(pos, state); + return new AABB( + mainPos.getX() - 0.75, + mainPos.getY() - 0.5, + mainPos.getZ() - 0.75, + mainPos.getX() + 1.75, + mainPos.getY() + 1.75, + mainPos.getZ() + 1.75 + ); + } + + @Override + public List getCauldronBottomPositions(BlockPos pos, BlockState state) { + BlockPos center = this.getMainPartPos(pos, state).below(2); + return BlockPos.betweenClosedStream(center.offset(-1, 0, -1), center.offset(1, 0, 1)) + .map(BlockPos::immutable) + .toList(); + } + @Override public @Nullable BlockEntity newBlockEntity(BlockPos pos, BlockState state) { return this.createBlockEntity(pos, state); @@ -120,7 +143,11 @@ public BlockEntityTicker getTicker( @Override public VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, CollisionContext context) { - return SHAPES.get(state.getValue(HALF)); + Cube3x3PartHalf part = state.getValue(HALF); + if (part.getOffsetY() == 2 && context.isHoldingItem(ModBlocks.GIANT_ANVIL.asItem())) { + return Shapes.block(); + } + return SHAPES.get(part); } @Override diff --git a/src/main/java/dev/dubhe/anvilcraft/block/MagnetBlock.java b/src/main/java/dev/dubhe/anvilcraft/block/MagnetBlock.java index 4f42ec42d2..b1bf7cbc04 100644 --- a/src/main/java/dev/dubhe/anvilcraft/block/MagnetBlock.java +++ b/src/main/java/dev/dubhe/anvilcraft/block/MagnetBlock.java @@ -1,6 +1,7 @@ package dev.dubhe.anvilcraft.block; import dev.dubhe.anvilcraft.AnvilCraft; +import dev.dubhe.anvilcraft.api.entity.IAnvilCraftEntityExtension; import dev.dubhe.anvilcraft.api.hammer.IHammerRemovable; import dev.dubhe.anvilcraft.entity.AnimateAscendingBlockEntity; import dev.dubhe.anvilcraft.init.block.ModBlockTags; @@ -109,6 +110,7 @@ private void attract(BlockState state, Level level, BlockPos magnetPos) { List entities = level.getEntitiesOfClass(FallingBlockEntity.class, new AABB(currentPos)); for (FallingBlockEntity entity : entities) { + if (entity instanceof IAnvilCraftEntityExtension) continue; BlockState state2 = entity.getBlockState(); if (state2.is(BlockTags.ANVIL) && !state2.is(ModBlockTags.NON_MAGNETIC)) { level.destroyBlock(magnetPos.below(), true); diff --git a/src/main/java/dev/dubhe/anvilcraft/block/RedstoneWireBlock.java b/src/main/java/dev/dubhe/anvilcraft/block/RedstoneWireBlock.java index 72404a953f..5d6b0dbffd 100644 --- a/src/main/java/dev/dubhe/anvilcraft/block/RedstoneWireBlock.java +++ b/src/main/java/dev/dubhe/anvilcraft/block/RedstoneWireBlock.java @@ -54,6 +54,7 @@ public class RedstoneWireBlock extends Block implements IHammerRemovable { private static final Map DOT_SHAPES = new EnumMap<>(Direction.class); private static final Map> SIDE_SHAPES = new EnumMap<>(Direction.class); private static final Map> CORNER_SHAPES = new EnumMap<>(Direction.class); + private static final Map> CORNER_SP_SHAPES = new EnumMap<>(Direction.class); private static final Map> UP_SHAPES = new EnumMap<>(Direction.class); static { @@ -63,15 +64,18 @@ public class RedstoneWireBlock extends Block implements IHammerRemovable { DOT_SHAPES.put(attachment, transformedBox(attachment, north, 4.0, 0.0, 4.0, 12.0, 2.5, 12.0)); List sides = new ArrayList<>(4); List corners = new ArrayList<>(4); + List specialCorners = new ArrayList<>(4); List ups = new ArrayList<>(4); for (int index = 0; index < 4; index++) { Direction tangent = getLocalDirection(attachment, index); sides.add(transformedBox(attachment, tangent, 5.0, 0.0, 0.0, 11.0, 2.0, 8.0)); corners.add(transformedBox(attachment, tangent, 5.0, 0.0, -2.0, 11.0, 2.0, 8.0)); + specialCorners.add(transformedBox(attachment, tangent, 5.0, 0.0, -1.0, 11.0, 2.0, 8.0)); ups.add(transformedBox(attachment, tangent, 5.0, 1.0, -0.1, 11.0, 18.0, 2.0)); } SIDE_SHAPES.put(attachment, List.copyOf(sides)); CORNER_SHAPES.put(attachment, List.copyOf(corners)); + CORNER_SP_SHAPES.put(attachment, List.copyOf(specialCorners)); UP_SHAPES.put(attachment, List.copyOf(ups)); } } @@ -124,12 +128,15 @@ protected VoxelShape getShape(BlockState state, BlockGetter level, BlockPos pos, VoxelShape shape = state.getValue(DOT) ? DOT_SHAPES.get(attachment) : Shapes.empty(); for (int index = 0; index < 4; index++) { ConnectionType side = state.getValue(CONNECTION_PROPERTIES.get(index)); - if (side.isConnected() && side != ConnectionType.CORNER) { + if (side.isConnected() && side != ConnectionType.CORNER && side != ConnectionType.CORNER_SP) { shape = Shapes.or(shape, SIDE_SHAPES.get(attachment).get(index)); } if (side == ConnectionType.CORNER) { shape = Shapes.or(shape, CORNER_SHAPES.get(attachment).get(index)); } + if (side == ConnectionType.CORNER_SP) { + shape = Shapes.or(shape, CORNER_SP_SHAPES.get(attachment).get(index)); + } if (side == ConnectionType.UP) { shape = Shapes.or(shape, UP_SHAPES.get(attachment).get(index)); } @@ -180,17 +187,21 @@ protected boolean isSignalSource(BlockState state) { public boolean canConnectRedstone( BlockState state, BlockGetter level, BlockPos pos, @Nullable Direction direction ) { - if (direction == null || !direction.getAxis().isHorizontal()) { + Direction terminalDirection; + if (direction == null) { + // 原版红石粉检查斜向下方的方块时不带方向;此时只暴露侧面导线的向上开放断口。 + terminalDirection = Direction.UP; + } else if (!direction.getAxis().isHorizontal()) { return false; + } else { + terminalDirection = direction.getOpposite(); } - Direction terminalDirection = direction.getOpposite(); int index = getLocalIndex(state.getValue(ATTACHMENT), terminalDirection); if (index < 0 || !state.getValue(CONNECTION_PROPERTIES.get(index)).isConnected()) { return false; } - Connection[] cached = RedstoneWireNetworkManager.getConnections(level, pos); // 内部接线端只负责连通网络;仅开放端点应被外部元件视为红石接口。 - return (cached == null ? findConnection(level, pos, state, index) : cached[index]) == null; + return this.isOpenTerminal(level, pos, state, index); } @Override @@ -231,6 +242,38 @@ private boolean hasWireConnection(BlockGetter level, BlockPos pos, BlockState st return (cached == null ? findConnection(level, pos, state, index) : cached[index]) != null; } + private boolean isOpenTerminal(BlockGetter level, BlockPos pos, BlockState state, int index) { + Connection[] cached = RedstoneWireNetworkManager.getConnections(level, pos); + return (cached == null ? findConnection(level, pos, state, index) : cached[index]) == null; + } + + /** 供原版红石粉采样斜下方导线的非粉线信号,避免该特殊连接只改变外观。 */ + public static int getUpwardDustSignal(BlockGetter level, BlockPos dustPos) { + int power = 0; + for (Direction towardWire : Direction.Plane.HORIZONTAL) { + BlockPos wirePos = dustPos.relative(towardWire).below(); + BlockState wireState = level.getBlockState(wirePos); + if (!(wireState.getBlock() instanceof RedstoneWireBlock wire)) { + continue; + } + Direction attachment = towardWire.getOpposite(); + if (wireState.getValue(ATTACHMENT) != attachment) { + continue; + } + int index = getLocalIndex(attachment, Direction.UP); + if (index < 0 + || !wireState.getValue(CONNECTION_PROPERTIES.get(index)).isConnected() + || !hasUpwardDustConnection(level, wirePos, wireState, index) + || !wire.isOpenTerminal(level, wirePos, wireState, index)) { + continue; + } + power = Math.max(power, RedstoneWireNetworkManager.getNonDustPower( + level, wirePos, wireState.getValue(POWER) + )); + } + return power; + } + /** 根据当前世界重新计算指定导线的四向外观状态。 */ BlockState connectionState(BlockGetter level, BlockPos pos, BlockState oldState) { return this.connectionState(level, pos, oldState, findConnections(level, pos, oldState)); @@ -249,8 +292,11 @@ BlockState connectionState( int second = -1; for (int index = 0; index < 4; index++) { Connection connection = connections[index]; - ConnectionType side = getConnection(level, pos, result, index, connection); - result = result.setValue(CONNECTION_PROPERTIES.get(index), side); + EnumProperty property = CONNECTION_PROPERTIES.get(index); + ConnectionType side = getConnection( + level, pos, result, index, connection, oldState.getValue(property).isConnected() + ); + result = result.setValue(property, side); if (side.isConnected()) { if (first < 0) { first = index; @@ -284,12 +330,21 @@ private static BlockState emptyState(BlockState state) { } private static ConnectionType getConnection( - BlockGetter level, BlockPos pos, BlockState state, int index, @Nullable Connection connection + BlockGetter level, + BlockPos pos, + BlockState state, + int index, + @Nullable Connection connection, + boolean wasConnected ) { if (connection != null) { // 导线到导线的几何关系决定 SIDE、CORNER 或 UP,应优先于普通红石接口外观。 return connection.side(); } + if (wasConnected && hasUpwardDustConnection(level, pos, state, index)) { + // 原版粉线通过 canConnectRedstone(null) 吸引到这个断口,使用专用短拐角避免模型插入粉线。 + return ConnectionType.CORNER_SP; + } Direction tangent = getLocalDirection(state.getValue(ATTACHMENT), index); BlockPos adjacentPos = pos.relative(tangent); BlockState adjacent = level.getBlockState(adjacentPos); @@ -308,6 +363,24 @@ private static boolean canAttachTo(BlockGetter level, BlockPos pos, BlockState s || state.is(Blocks.REDSTONE_WALL_TORCH); } + /** 侧面导线向上断开时,检查支撑方块顶面的原版红石粉斜角连接。 */ + private static boolean hasUpwardDustConnection(BlockGetter level, BlockPos pos, BlockState state, int index) { + Direction attachment = state.getValue(ATTACHMENT); + if (!attachment.getAxis().isHorizontal() || getLocalDirection(attachment, index) != Direction.UP) { + return false; + } + return level.getBlockState(pos.relative(attachment).above()).is(Blocks.REDSTONE_WIRE); + } + + /** 返回开放端点实际对应的外部方块位置;斜角红石粉位于支撑方块的顶面。 */ + static BlockPos terminalTarget(BlockGetter level, BlockPos pos, BlockState state, Direction tangent) { + int index = getLocalIndex(state.getValue(ATTACHMENT), tangent); + if (index >= 0 && hasUpwardDustConnection(level, pos, state, index)) { + return pos.relative(state.getValue(ATTACHMENT)).above(); + } + return pos.relative(tangent); + } + /** * 查找指定局部方向上与当前端点重合或可爬升相连的自定义导线。 * @@ -672,7 +745,9 @@ public enum ConnectionType implements StringRepresentable { /** 沿前方完整方块的侧面向上爬升。 */ UP("up", true), /** 绕同一支撑方块的边缘连接到另一个附着面。 */ - CORNER("corner", true); + CORNER("corner", true), + /** 向上的开放断口连接支撑方块顶面的原版红石粉。 */ + CORNER_SP("corner_sp", true); private final String name; @Getter diff --git a/src/main/java/dev/dubhe/anvilcraft/block/RedstoneWireNetworkManager.java b/src/main/java/dev/dubhe/anvilcraft/block/RedstoneWireNetworkManager.java index 09230eea82..6b30b45bcc 100644 --- a/src/main/java/dev/dubhe/anvilcraft/block/RedstoneWireNetworkManager.java +++ b/src/main/java/dev/dubhe/anvilcraft/block/RedstoneWireNetworkManager.java @@ -460,7 +460,10 @@ private void recompute(Network network) { for (int index = 0; index < network.terminalWires.size(); index++) { BlockPos wirePos = BlockPos.of(network.terminalWires.getLong(index)); Direction tangent = Direction.values()[network.terminalDirections.getByte(index)]; - BlockPos inputPos = wirePos.relative(tangent); + BlockState wireState = this.level.getBlockState(wirePos); + BlockPos inputPos = wireState.getBlock() instanceof RedstoneWireBlock + ? RedstoneWireBlock.terminalTarget(this.level, wirePos, wireState, tangent) + : wirePos.relative(tangent); BlockState inputState = this.level.getBlockState(inputPos); int inputPower = this.level.getSignal(inputPos, tangent); totalPower = Math.max(totalPower, inputPower); @@ -512,7 +515,11 @@ private void notifyTerminalChanges(Network network) { long source = network.terminalWires.getLong(index); BlockPos sourcePos = BlockPos.of(source); Direction tangent = Direction.values()[network.terminalDirections.getByte(index)]; - long target = sourcePos.relative(tangent).asLong(); + BlockState sourceState = this.level.getBlockState(sourcePos); + BlockPos targetPos = sourceState.getBlock() instanceof RedstoneWireBlock + ? RedstoneWireBlock.terminalTarget(this.level, sourcePos, sourceState, tangent) + : sourcePos.relative(tangent); + long target = targetPos.asLong(); byte mask = (byte) (excludedFaces.get(target) | 1 << tangent.getOpposite().ordinal()); excludedFaces.put(target, mask); // 多个端点指向同一方块时只需任选一个真实导线作为邻居变化来源。 diff --git a/src/main/java/dev/dubhe/anvilcraft/block/entity/BaseLaserBlockEntity.java b/src/main/java/dev/dubhe/anvilcraft/block/entity/BaseLaserBlockEntity.java index 145b38b3bb..79e7527ef5 100644 --- a/src/main/java/dev/dubhe/anvilcraft/block/entity/BaseLaserBlockEntity.java +++ b/src/main/java/dev/dubhe/anvilcraft/block/entity/BaseLaserBlockEntity.java @@ -404,15 +404,16 @@ public void resetLaserStateAfterMove() { public void setRemoved() { super.setRemoved(); if (this.level == null) return; + if (this.level.isClientSide()) { + CacheableBERenderingPipeline.getInstance().blockRemoved(this); + return; + } if (this.irradiateBlockPos == null) return; if (!this.level.isLoaded(this.irradiateBlockPos)) return; BlockEntity targetBe = this.level.getBlockEntity(this.irradiateBlockPos); if (targetBe instanceof BaseLaserBlockEntity irradiateBlockEntity) { irradiateBlockEntity.onCancelingIrradiation(this); } - if (this.level.isClientSide()) { - CacheableBERenderingPipeline.getInstance().update(this); - } } public float getLaserOffset() { diff --git a/src/main/java/dev/dubhe/anvilcraft/block/entity/BurningHeaterBlockEntity.java b/src/main/java/dev/dubhe/anvilcraft/block/entity/BurningHeaterBlockEntity.java index e370e2594a..2326f185bd 100644 --- a/src/main/java/dev/dubhe/anvilcraft/block/entity/BurningHeaterBlockEntity.java +++ b/src/main/java/dev/dubhe/anvilcraft/block/entity/BurningHeaterBlockEntity.java @@ -181,39 +181,16 @@ public void tick(Level level, BlockPos pos, BlockState state) { /** * 消耗燃烧时间(用于铁砧合成) * - *

直接从燃料槽扣除物品来抵消耗,避免 tryConsumeFuel 下个 tick 立刻补满导致消耗不可见。 - * 同时强制向客户端同步,让 tooltip/Jade 显示最新值。

+ *

从当前缓存中精确扣除燃烧时间,并立即更新燃烧状态和客户端显示。

* * @param ticks 消耗的tick数 */ public void consumeBurnTime(int ticks) { - int remaining = ticks; - - // 1. 优先从燃料槽中直接扣除物品 - while (remaining > 0) { - ItemStack fuel = this.itemHandler.getStackInSlot(0); - int burnTimePerItem = getItemBurnTime(fuel); - if (burnTimePerItem <= 0) break; - - int itemsToConsume = (remaining + burnTimePerItem - 1) / burnTimePerItem; - itemsToConsume = Math.min(itemsToConsume, fuel.getCount()); - if (itemsToConsume <= 0) break; - - remaining -= itemsToConsume * burnTimePerItem; - this.itemHandler.extractItem(0, itemsToConsume, false); - if (fuel.hasCraftingRemainingItem() && this.itemHandler.getStackInSlot(0).isEmpty()) { - this.itemHandler.setStackInSlot(0, fuel.getCraftingRemainingItem()); - } - } - - // 2. 物品不够的部分从 burnTime 缓冲区扣除 - if (remaining > 0) { - this.burnTime = Math.max(0, this.burnTime - remaining); - } - + if (ticks <= 0) return; + this.burnTime = Math.max(0, this.burnTime - ticks); setChanged(); - // 强制向客户端同步,确保 tooltip/Jade 显示最新值 if (level != null && !level.isClientSide()) { + updateBurningState(level, worldPosition, getBlockState()); level.sendBlockUpdated(worldPosition, getBlockState(), getBlockState(), 3); level.updateNeighbourForOutputSignal(worldPosition, getBlockState().getBlock()); } diff --git a/src/main/java/dev/dubhe/anvilcraft/block/entity/LargeCauldronBlockEntity.java b/src/main/java/dev/dubhe/anvilcraft/block/entity/LargeCauldronBlockEntity.java index 461b10283c..d39497b7f0 100644 --- a/src/main/java/dev/dubhe/anvilcraft/block/entity/LargeCauldronBlockEntity.java +++ b/src/main/java/dev/dubhe/anvilcraft/block/entity/LargeCauldronBlockEntity.java @@ -27,7 +27,6 @@ import dev.dubhe.anvilcraft.block.state.Cube3x3PartHalf; import dev.dubhe.anvilcraft.init.block.ModBlocks; import dev.dubhe.anvilcraft.init.block.ModFluidTags; -import dev.dubhe.anvilcraft.init.entity.ModDamageTypes; import dev.dubhe.anvilcraft.init.item.ModItemTags; import dev.dubhe.anvilcraft.init.recipe.ModRecipeTriggers; import dev.dubhe.anvilcraft.init.recipe.ModRecipeTypes; @@ -36,7 +35,7 @@ import dev.dubhe.anvilcraft.recipe.anvil.predicate.block.HasAnvil; import dev.dubhe.anvilcraft.recipe.anvil.predicate.block.HasCauldron; import dev.dubhe.anvilcraft.recipe.anvil.wrap.ItemCompressRecipe; -import dev.dubhe.anvilcraft.recipe.anvil.wrap.SuperHeatingRecipe; +import dev.dubhe.anvilcraft.util.CauldronUtil; import dev.dubhe.anvilcraft.util.FireReforgingUtil; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; @@ -186,7 +185,7 @@ public static void serverTick( entity.absorbFluidSources(level); entity.refreshIgnited(); entity.applyFluidEffects((ServerLevel) level); - entity.hurtEntitiesInside((ServerLevel) level); + entity.hurtEntitiesInsideFromCampfire((ServerLevel) level); entity.reforgeItemsInLava(level); } @@ -481,7 +480,6 @@ public boolean handleGiantAnvilImpact(AnvilEvent.OnLand event) { for (int slot = 0; slot < main.output.getSlots(); slot++) { if (!main.output.getStackInSlot(slot).isEmpty()) initialOutputSlots.add(slot); } - Set fuelCharged = new HashSet<>(); int processed = 0; boolean madeProgress; do { @@ -492,21 +490,17 @@ public boolean handleGiantAnvilImpact(AnvilEvent.OnLand event) { List candidates = helpers.isEmpty() ? List.of(slotPos.below()) : orderedHelpers(slotPos, helpers, base); - RecipeAttempt attempt = main.tryProcessItemGroup( + RecipeExecution execution = main.tryProcessItemGroup( serverLevel, base, candidates, event.getEntity(), slot, false, - fuelCharged, allowItemCompression ); - if (!attempt.execution().executed()) continue; - if (level.getBlockState(attempt.helper()).is(ModBlocks.BURNING_HEATER)) { - fuelCharged.add(attempt.helper()); - } - if (attempt.execution().damageAnvil()) event.setAnvilDamage(true); + if (!execution.executed()) continue; + if (execution.damageAnvil()) event.setAnvilDamage(true); processed++; madeProgress = true; } @@ -518,21 +512,17 @@ public boolean handleGiantAnvilImpact(AnvilEvent.OnLand event) { for (int slot : initialOutputSlots) { if (processed >= MAX_PROCESS_EFFICIENCY) break; if (main.output.getStackInSlot(slot).isEmpty()) continue; - RecipeAttempt attempt = main.tryProcessItemGroup( + RecipeExecution execution = main.tryProcessItemGroup( serverLevel, base, centerCandidates, event.getEntity(), slot, true, - fuelCharged, allowItemCompression ); - if (!attempt.execution().executed()) continue; - if (level.getBlockState(attempt.helper()).is(ModBlocks.BURNING_HEATER)) { - fuelCharged.add(attempt.helper()); - } - if (attempt.execution().damageAnvil()) event.setAnvilDamage(true); + if (!execution.executed()) continue; + if (execution.damageAnvil()) event.setAnvilDamage(true); processed++; } @@ -542,19 +532,15 @@ public boolean handleGiantAnvilImpact(AnvilEvent.OnLand event) { processed++; continue; } - RecipeAttempt attempt = main.tryProcessFluidRecipe( + RecipeExecution execution = main.tryProcessFluidRecipe( serverLevel, base, centerCandidates, event.getEntity(), - fuelCharged, allowItemCompression ); - if (!attempt.execution().executed()) break; - if (level.getBlockState(attempt.helper()).is(ModBlocks.BURNING_HEATER)) { - fuelCharged.add(attempt.helper()); - } - if (attempt.execution().damageAnvil()) event.setAnvilDamage(true); + if (!execution.executed()) break; + if (execution.damageAnvil()) event.setAnvilDamage(true); processed++; } } @@ -603,14 +589,13 @@ private boolean tryProcessFluidMixingRecipe(ServerLevel level) { return false; } - private RecipeAttempt tryProcessItemGroup( + private RecipeExecution tryProcessItemGroup( ServerLevel level, BlockPos base, List candidates, Entity anvil, int slot, boolean outputSource, - Set fuelCharged, boolean allowItemCompression ) { for (BlockPos helper : candidates) { @@ -633,7 +618,6 @@ private RecipeAttempt tryProcessItemGroup( anvil, slot, outputSource, - fuelCharged.contains(helper), allowItemCompression ); } finally { @@ -641,29 +625,25 @@ private RecipeAttempt tryProcessItemGroup( this.processingOutput = false; this.processingOutputInputs.clear(); } - if (execution.executed()) return new RecipeAttempt(execution, helper); + if (execution.executed()) return execution; } - return RecipeAttempt.EMPTY; + return RecipeExecution.EMPTY; } - private RecipeAttempt tryProcessFluidRecipe( + private RecipeExecution tryProcessFluidRecipe( ServerLevel level, BlockPos base, List candidates, Entity anvil, - Set fuelCharged, boolean allowItemCompression ) { for (BlockPos helper : candidates) { Vec3 contextPos = new Vec3(helper.getX() + 0.5, base.getY() + 1.0, helper.getZ() + 0.5); InWorldRecipeContext context = new InWorldRecipeContext(level, contextPos, anvil); - if (fuelCharged.contains(helper)) { - context.put(SuperHeatingRecipe.ConsumeFuel.FUEL_CONSUMED, true); - } RecipeExecution execution = triggerOneRecipe(level, context, ItemStack.EMPTY, allowItemCompression); - if (execution.executed()) return new RecipeAttempt(execution, helper); + if (execution.executed()) return execution; } - return RecipeAttempt.EMPTY; + return RecipeExecution.EMPTY; } private RecipeExecution triggerRecipeGroup( @@ -672,7 +652,6 @@ private RecipeExecution triggerRecipeGroup( Entity anvil, int slot, boolean outputSource, - boolean fuelConsumed, boolean allowItemCompression ) { IItemHandler source = outputSource ? this.output : this.input; @@ -686,9 +665,6 @@ private RecipeExecution triggerRecipeGroup( ItemStack processingInput = source.getStackInSlot(slot); if (processingInput.isEmpty()) break; InWorldRecipeContext context = new InWorldRecipeContext(level, contextPos, anvil); - if (fuelConsumed || executed) { - context.put(SuperHeatingRecipe.ConsumeFuel.FUEL_CONSUMED, true); - } RecipeExecution current = triggerOneRecipe( level, context, @@ -1219,39 +1195,26 @@ private void refreshIgnited() { } private AABB contentArea() { - return new AABB( - this.worldPosition.getX() - 0.75, - this.worldPosition.getY() - 0.5, - this.worldPosition.getZ() - 0.75, - this.worldPosition.getX() + 1.75, - this.worldPosition.getY() + 1.75, - this.worldPosition.getZ() + 1.75 - ); + return CauldronUtil.getInnerArea(this.worldPosition, this.getBlockState()); } - private void hurtEntitiesInside(ServerLevel level) { + private void hurtEntitiesInsideFromCampfire(ServerLevel level) { BlockPos base = this.worldPosition.below(); boolean normalCampfire = false; boolean soulCampfire = false; - boolean heater = false; for (int slot = 0; slot < FOOTPRINT_OFFSETS.length; slot++) { BlockState state = level.getBlockState(positionForFootprint(base, slot).below()); if (CampfireBlock.isLitCampfire(state)) { soulCampfire |= state.is(Blocks.SOUL_CAMPFIRE); normalCampfire |= state.is(Blocks.CAMPFIRE); } - heater |= state.is(ModBlocks.HEATER) && !state.getValue(HeaterBlock.OVERLOAD); } - if (!normalCampfire && !soulCampfire && !heater) return; + if (!normalCampfire && !soulCampfire) return; AABB inside = this.contentArea(); for (LivingEntity living : level.getEntitiesOfClass(LivingEntity.class, inside)) { if (living.fireImmune() || living.isSteppingCarefully()) continue; - if (heater) { - living.hurt(ModDamageTypes.heaterBurn(level), 4.0F); - } else { - living.hurt(level.damageSources().inFire(), soulCampfire ? 2.0F : 1.0F); - } + living.hurt(level.damageSources().inFire(), soulCampfire ? 2.0F : 1.0F); } } @@ -1393,10 +1356,7 @@ private static List copyFluids(List fluids) { } private record RecipeExecution(boolean executed, boolean damageAnvil) { - } - - private record RecipeAttempt(RecipeExecution execution, @Nullable BlockPos helper) { - private static final RecipeAttempt EMPTY = new RecipeAttempt(new RecipeExecution(false, false), null); + private static final RecipeExecution EMPTY = new RecipeExecution(false, false); } public record RecipePreview( diff --git a/src/main/java/dev/dubhe/anvilcraft/block/entity/LensBlockEntity.java b/src/main/java/dev/dubhe/anvilcraft/block/entity/LensBlockEntity.java index 462e7e97a5..7cdf6cd5b0 100644 --- a/src/main/java/dev/dubhe/anvilcraft/block/entity/LensBlockEntity.java +++ b/src/main/java/dev/dubhe/anvilcraft/block/entity/LensBlockEntity.java @@ -71,6 +71,7 @@ public void onIrradiated(BaseLaserBlockEntity source) { @Override public void onCancelingIrradiation(BaseLaserBlockEntity source) { + if (!this.irradiateSelfLaserBlockSet.contains(source)) return; super.onCancelingIrradiation(source); this.enabled = !this.irradiateSelfLaserBlockSet.isEmpty(); } diff --git a/src/main/java/dev/dubhe/anvilcraft/block/entity/RubyPrismBlockEntity.java b/src/main/java/dev/dubhe/anvilcraft/block/entity/RubyPrismBlockEntity.java index db714e00ad..74efa7f3d8 100644 --- a/src/main/java/dev/dubhe/anvilcraft/block/entity/RubyPrismBlockEntity.java +++ b/src/main/java/dev/dubhe/anvilcraft/block/entity/RubyPrismBlockEntity.java @@ -40,6 +40,7 @@ protected int getBaseLaserLevel() { @Override public void onCancelingIrradiation(BaseLaserBlockEntity baseLaserBlockEntity) { + if (!irradiateSelfLaserBlockSet.contains(baseLaserBlockEntity)) return; super.onCancelingIrradiation(baseLaserBlockEntity); enabled = !irradiateSelfLaserBlockSet.isEmpty(); } diff --git a/src/main/java/dev/dubhe/anvilcraft/data/generator/RedstoneWireBlockStateGenerator.java b/src/main/java/dev/dubhe/anvilcraft/data/generator/RedstoneWireBlockStateGenerator.java index cdda3e6f69..7f42537748 100644 --- a/src/main/java/dev/dubhe/anvilcraft/data/generator/RedstoneWireBlockStateGenerator.java +++ b/src/main/java/dev/dubhe/anvilcraft/data/generator/RedstoneWireBlockStateGenerator.java @@ -50,6 +50,13 @@ public static void generate( .condition(RedstoneWireBlock.ATTACHMENT, attachment) .condition(property, ConnectionType.CORNER) .end(); + if (RedstoneWireBlock.getLocalDirection(attachment, index) == Direction.UP) { + ModelFile sideCornerSp = sideCornerSpModel(provider, attachment, index); + multipart.part().modelFile(sideCornerSp).addModel() + .condition(RedstoneWireBlock.ATTACHMENT, attachment) + .condition(property, ConnectionType.CORNER_SP) + .end(); + } } multipart.part().modelFile(up).addModel() .condition(RedstoneWireBlock.ATTACHMENT, attachment) @@ -141,6 +148,32 @@ private static BlockModelBuilder sideCornerModel( return model; } + /** 生成墙面向上断口连接支撑方块顶面红石粉时使用的专用短拐角。 */ + private static BlockModelBuilder sideCornerSpModel( + RegistrumBlockstateProvider provider, Direction attachment, int index + ) { + Direction tangent = RedstoneWireBlock.getLocalDirection(attachment, index); + BlockModelBuilder model = model(provider, attachment, "side_corner_sp_" + index) + .texture("0", AnvilCraft.of("block/redstone_wire_line")) + .texture("1", AnvilCraft.of("block/redstone_wire_line_overlay")) + .texture("particle", AnvilCraft.of("block/redstone_wire_line")); + addBoxWithRotatedUvs(model, attachment, tangent, 5.0, 0.0, 0.0, 11.0, 1.0, 8.0, List.of( + face(Direction.NORTH, "#0", 5, 0, 11, 1, 0, false, Direction.NORTH), + face(Direction.EAST, "#0", 10, 0, 11, 8, 90, false, null), + face(Direction.WEST, "#0", 5, 8, 6, 0, 90, false, null), + face(Direction.UP, "#0", 5, 0, 11, 8), + face(Direction.DOWN, "#0", 11, 0, 5, 8, 0, false, Direction.DOWN) + )); + addBoxWithRotatedUvs(model, attachment, tangent, 6.0, 0.01, -1.0, 10.0, 2.0, 8.0, List.of( + face(Direction.NORTH, "#1", 6, 0, 10, 1, 0, true, Direction.NORTH), + face(Direction.EAST, "#1", 6, 0, 7, 9, 90, true, null), + face(Direction.WEST, "#1", 6, 0, 7, 9, 90, true, null), + face(Direction.UP, "#1", 6, 0, 10, 9, 0, true, null), + face(Direction.DOWN, "#1", 6, 0, 10, 9, 0, true, null) + )); + return model; + } + /** 生成导线沿前方完整方块侧面向上爬升的竖直部分。 */ private static BlockModelBuilder upModel( RegistrumBlockstateProvider provider, Direction attachment, int index diff --git a/src/main/java/dev/dubhe/anvilcraft/event/FallingBlockCollisionEventListener.java b/src/main/java/dev/dubhe/anvilcraft/event/FallingBlockCollisionEventListener.java index 6770a93ba0..4eed7f4d9e 100644 --- a/src/main/java/dev/dubhe/anvilcraft/event/FallingBlockCollisionEventListener.java +++ b/src/main/java/dev/dubhe/anvilcraft/event/FallingBlockCollisionEventListener.java @@ -42,6 +42,7 @@ public class FallingBlockCollisionEventListener { @SubscribeEvent public static void anvilCollisionCraft(AnvilEvent.CollisionBlock event) { + if (event.isCanceled()) return; Vec3 entityPos = event.getEntity().position(); Level level = event.getLevel(); BlockPos pos = event.getPos(); diff --git a/src/main/java/dev/dubhe/anvilcraft/event/LivingEntityEventListener.java b/src/main/java/dev/dubhe/anvilcraft/event/LivingEntityEventListener.java index a15cf8fcf4..abc89279e7 100644 --- a/src/main/java/dev/dubhe/anvilcraft/event/LivingEntityEventListener.java +++ b/src/main/java/dev/dubhe/anvilcraft/event/LivingEntityEventListener.java @@ -7,6 +7,7 @@ import dev.dubhe.anvilcraft.init.item.ModAmulets; import dev.dubhe.anvilcraft.init.recipe.ModRecipeTypes; import dev.dubhe.anvilcraft.recipe.transform.MobTransformWithItemRecipe; +import dev.dubhe.anvilcraft.util.CauldronUtil; import net.minecraft.tags.EntityTypeTags; import net.minecraft.world.InteractionHand; import net.minecraft.world.damagesource.DamageTypes; @@ -64,6 +65,7 @@ public static void onChangeTarget(LivingChangeTargetEvent event) { @SubscribeEvent public static void onTick(EntityTickEvent.Post event) { + CauldronUtil.hurtFromHeaterBelow(event.getEntity()); Mob entity = Util.castSafely(event.getEntity(), Mob.class).orElse(null); if (entity == null) return; if (!(entity.getTarget() instanceof Player player)) return; diff --git a/src/main/java/dev/dubhe/anvilcraft/event/anvil/AnvilEventListener.java b/src/main/java/dev/dubhe/anvilcraft/event/anvil/AnvilEventListener.java index ec3847502f..2a373c1e3b 100644 --- a/src/main/java/dev/dubhe/anvilcraft/event/anvil/AnvilEventListener.java +++ b/src/main/java/dev/dubhe/anvilcraft/event/anvil/AnvilEventListener.java @@ -13,6 +13,7 @@ import dev.dubhe.anvilcraft.block.NeoforgeBlock; import dev.dubhe.anvilcraft.block.TranscendenceAnvilBlock; import dev.dubhe.anvilcraft.block.entity.LargeCauldronBlockEntity; +import dev.dubhe.anvilcraft.block.multipart.AbstractMultiPartBlock; import dev.dubhe.anvilcraft.entity.FallingGiantAnvilEntity; import dev.dubhe.anvilcraft.init.block.ModBlocks; import dev.dubhe.anvilcraft.init.recipe.ModRecipeTriggers; @@ -74,22 +75,25 @@ public static void onLand(AnvilEvent.OnLand event) { if (event.getEntity() instanceof FallingGiantAnvilEntity && hitBlockState.is(ModBlocks.LARGE_CAULDRON)) { LargeCauldronBlockEntity cauldron = LargeCauldronBlockEntity.getMain(level, hitBlockPos, hitBlockState); - if (cauldron != null && cauldron.handleGiantAnvilImpact(event)) return; + if (cauldron != null && cauldron.handleGiantAnvilImpact(event)) { + handleBehaviors(level, hitBlockPos, hitBlockState, event); + return; + } + } + BlockPos breakBlockPos = hitBlockPos; + if (hitBlockState.getBlock() instanceof AbstractMultiPartBlock multiPartBlock) { + BlockPos mainPartPos = multiPartBlock.getMainPartPos(hitBlockPos, hitBlockState); + if (level.getBlockState(mainPartPos).is(multiPartBlock)) breakBlockPos = mainPartPos; } - BlockPos belowPos = hitBlockPos.below(); - BlockState hitBelowState = level.getBlockState(belowPos); - if (hitBelowState.is(Blocks.STONECUTTER)) { - brokeBlock(level, hitBlockPos, event); + BlockState breakBlockState = level.getBlockState(breakBlockPos); + if (hasStonecutterBelow(level, breakBlockPos, breakBlockState)) { + brokeBlock(level, breakBlockPos, event); return; } if (!ProceduralProcessStepManager.checkAnyMatches(event)) { handleNeoAnvilRecipe(event); } - for (IAnvilBehavior behavior : IAnvilBehavior.findMatching(hitBlockState)) { - if (behavior.handle(level, hitBlockPos, hitBlockState, event.getFallDistance(), event)) { - return; - } - } + if (handleBehaviors(level, hitBlockPos, hitBlockState, event)) return; if (blockState.is(ModBlocks.NEOFORGE)) { if (event.getFallDistance() > 1) { if (level.random.nextDouble() < 0.01) { @@ -99,6 +103,18 @@ public static void onLand(AnvilEvent.OnLand event) { } } + private static boolean handleBehaviors( + Level level, + BlockPos hitBlockPos, + BlockState hitBlockState, + AnvilEvent.OnLand event + ) { + for (IAnvilBehavior behavior : IAnvilBehavior.findMatching(hitBlockState)) { + if (behavior.handle(level, hitBlockPos, hitBlockState, event.getFallDistance(), event)) return true; + } + return false; + } + public static void handleNeoAnvilRecipe(AnvilEvent.OnLand event) { Level level = event.getLevel(); if (!(level instanceof ServerLevel serverLevel)) return; @@ -117,6 +133,29 @@ public static void handleNeoAnvilRecipe(AnvilEvent.OnLand event) { } } + private static boolean hasStonecutterBelow(Level level, BlockPos pos, BlockState state) { + if (state.getBlock() instanceof AbstractMultiPartBlock multiPartBlock) { + return hasStonecutterBelowAnyPart(level, pos, state, multiPartBlock); + } + return level.getBlockState(pos.below()).is(Blocks.STONECUTTER); + } + + private static

> boolean hasStonecutterBelowAnyPart( + Level level, + BlockPos mainPartPos, + BlockState mainPartState, + AbstractMultiPartBlock

multiPartBlock + ) { + for (P part : multiPartBlock.getParts()) { + BlockPos partPos = mainPartPos.offset(multiPartBlock.offsetFrom(mainPartState, part)); + if (level.getBlockState(partPos).is(multiPartBlock) + && level.getBlockState(partPos.below()).is(Blocks.STONECUTTER)) { + return true; + } + } + return false; + } + private static void brokeBlock(Level level, BlockPos pos, AnvilEvent.OnLand event) { if (!(level instanceof ServerLevel serverLevel)) return; BlockState state = level.getBlockState(pos); diff --git a/src/main/java/dev/dubhe/anvilcraft/init/ModAnvilBehaviors.java b/src/main/java/dev/dubhe/anvilcraft/init/ModAnvilBehaviors.java index 159c46e2f0..d2cbe14b14 100644 --- a/src/main/java/dev/dubhe/anvilcraft/init/ModAnvilBehaviors.java +++ b/src/main/java/dev/dubhe/anvilcraft/init/ModAnvilBehaviors.java @@ -24,7 +24,7 @@ import dev.dubhe.anvilcraft.block.SugarBlock; import dev.dubhe.anvilcraft.init.block.ModBlockTags; import dev.dubhe.anvilcraft.init.block.ModBlocks; -import net.minecraft.world.level.block.AbstractCauldronBlock; +import net.minecraft.tags.BlockTags; import net.minecraft.world.level.block.Blocks; import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.common.EventBusSubscriber; @@ -51,7 +51,7 @@ public static void register(AnvilBehaviorRegisterEvent event) { event.registerBehavior(state -> state.getBlock() instanceof GunpowderBlock, new GunpowderBlockBehavior()); event.registerBehavior(state -> state.is(ModBlocks.IMPACT_PILE), new ImpactPileBehavior()); event.registerBehavior(state -> state.getBlock() instanceof SugarBlock, new SugarBlockBehavior()); - event.registerBehavior(state -> state.getBlock() instanceof AbstractCauldronBlock, new TimeWarpPlayerBehavior()); + event.registerBehavior(state -> state.is(BlockTags.CAULDRONS), new TimeWarpPlayerBehavior()); event.registerBehavior(Blocks.BEACON, new BeaconConversionBehavior()); event.registerBehavior(ModBlocks.OVERHEATED_EMBER_METAL_BLOCK.get(), new TranscendiumBehavior()); event.registerBehavior( diff --git a/src/main/java/dev/dubhe/anvilcraft/mixin/EntityMixin.java b/src/main/java/dev/dubhe/anvilcraft/mixin/EntityMixin.java index cb11795f56..d9274fe5d2 100644 --- a/src/main/java/dev/dubhe/anvilcraft/mixin/EntityMixin.java +++ b/src/main/java/dev/dubhe/anvilcraft/mixin/EntityMixin.java @@ -3,6 +3,7 @@ import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import dev.anvilcraft.lib.v2.util.Util; +import dev.dubhe.anvilcraft.api.entity.IAnvilCraftEntityExtension; import dev.dubhe.anvilcraft.api.event.AnvilEvent; import dev.dubhe.anvilcraft.api.event.EntityThroughPortalEvent; import dev.dubhe.anvilcraft.api.injection.entity.IEntityExtension; @@ -244,6 +245,10 @@ public abstract class EntityMixin implements IEntityExtension { Optional entityOp = Util.castSafely(this, FallingBlockEntity.class); if (entityOp.isEmpty() || !this.horizontalCollision) return; FallingBlockEntity self = entityOp.get(); + if (self instanceof IAnvilCraftEntityExtension extension + && !extension.anvilcraft$canCollisionCraft()) { + return; + } BlockPos blockPos = BlockPos.containing(this.position.add(anvil$beforeBoundingMovement .scale(0.55 / anvil$beforeBoundingMovement.length()) .multiply(1, 0, 1))); diff --git a/src/main/java/dev/dubhe/anvilcraft/mixin/ExplosionMixin.java b/src/main/java/dev/dubhe/anvilcraft/mixin/ExplosionMixin.java index 1d1297bd74..ee9c6cee14 100644 --- a/src/main/java/dev/dubhe/anvilcraft/mixin/ExplosionMixin.java +++ b/src/main/java/dev/dubhe/anvilcraft/mixin/ExplosionMixin.java @@ -13,6 +13,7 @@ import dev.dubhe.anvilcraft.recipe.anvil.collision.BlockTransform; import it.unimi.dsi.fastutil.objects.ObjectListIterator; import net.minecraft.core.BlockPos; +import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.world.item.ItemStack; import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.Explosion; @@ -34,6 +35,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Set; @Mixin(Explosion.class) abstract class ExplosionMixin implements IExplosionExtension { @@ -93,6 +95,8 @@ private void finalizeExplosion( @Share("isExplosionBlockTransformed") LocalBooleanRef isExplosionBlockTransformed, @Local(ordinal = 0) BlockPos pos ) { + // The shared local is reused for every ray iteration in explode(). + isExplosionBlockTransformed.set(false); Block block = level.getBlockState(pos).getBlock(); ArrayList blockTransforms = new ArrayList<>(this.anvilcraft$blockTransformMap.get(block)); if (blockTransforms.isEmpty()) return; @@ -138,8 +142,14 @@ private void finalizeExplosion( @Override public void anvilcraft$setBlockTransformExplosion(Collection blockTransformExplosions) { for (BlockTransform blockTransform : blockTransformExplosions) { - for (BlockState state : blockTransform.inputBlock().getStatesCache()) { - Block block = state.getBlock(); + Set inputBlocks = new HashSet<>(); + blockTransform.inputBlock().getBlocks().unwrap().ifLeft(tag -> + BuiltInRegistries.BLOCK.getTagOrEmpty(tag).forEach(holder -> inputBlocks.add(holder.value())) + ); + blockTransform.inputBlock().getBlocks().unwrap().ifRight(holders -> + holders.forEach(holder -> inputBlocks.add(holder.value())) + ); + for (Block block : inputBlocks) { this.anvilcraft$blockTransformMap.put(block, blockTransform); } } diff --git a/src/main/java/dev/dubhe/anvilcraft/mixin/RedstoneWireBlockMixin.java b/src/main/java/dev/dubhe/anvilcraft/mixin/RedstoneWireBlockMixin.java index b8e893dea7..4052f26a51 100644 --- a/src/main/java/dev/dubhe/anvilcraft/mixin/RedstoneWireBlockMixin.java +++ b/src/main/java/dev/dubhe/anvilcraft/mixin/RedstoneWireBlockMixin.java @@ -1,14 +1,20 @@ package dev.dubhe.anvilcraft.mixin; import dev.dubhe.anvilcraft.api.injection.tooltip.ITooltipProviderExtension; +import dev.dubhe.anvilcraft.block.RedstoneWireBlock; import net.minecraft.ChatFormatting; +import net.minecraft.core.BlockPos; import net.minecraft.network.chat.Component; +import net.minecraft.world.level.Level; import net.minecraft.world.level.block.RedStoneWireBlock; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.properties.IntegerProperty; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import java.util.ArrayList; import java.util.List; @@ -26,4 +32,11 @@ public abstract class RedstoneWireBlockMixin implements ITooltipProviderExtensio lines.add(Component.translatable("tooltip.anvilcraft.redstone.power", state.getValue(POWER)).withStyle(ChatFormatting.GRAY)); return lines; } + + @Inject(method = "calculateTargetStrength", at = @At("RETURN"), cancellable = true) + private void anvilcraft$includeUpwardWireSignal( + Level level, BlockPos pos, CallbackInfoReturnable callback + ) { + callback.setReturnValue(Math.max(callback.getReturnValue(), RedstoneWireBlock.getUpwardDustSignal(level, pos))); + } } diff --git a/src/main/java/dev/dubhe/anvilcraft/recipe/anvil/predicate/block/HasCauldron.java b/src/main/java/dev/dubhe/anvilcraft/recipe/anvil/predicate/block/HasCauldron.java index 100788b90c..348cb72526 100644 --- a/src/main/java/dev/dubhe/anvilcraft/recipe/anvil/predicate/block/HasCauldron.java +++ b/src/main/java/dev/dubhe/anvilcraft/recipe/anvil/predicate/block/HasCauldron.java @@ -9,6 +9,7 @@ import dev.anvilcraft.lib.v2.recipe.util.InWorldRecipeContext; import dev.anvilcraft.lib.v2.util.MathUtil; import dev.dubhe.anvilcraft.api.block.IIgnitableCauldron; +import dev.dubhe.anvilcraft.api.entity.IEntityCauldron; import dev.dubhe.anvilcraft.api.fluid.IFluidHandlerHolder; import dev.dubhe.anvilcraft.api.fluid.LargeCauldronFluidHandler; import dev.dubhe.anvilcraft.block.entity.LargeCauldronBlockEntity; @@ -26,11 +27,13 @@ import net.minecraft.resources.ResourceLocation; import net.minecraft.tags.BlockTags; import net.minecraft.tags.TagKey; +import net.minecraft.world.entity.Entity; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.properties.IntegerProperty; import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.Vec3; import net.neoforged.neoforge.fluids.FluidStack; import net.neoforged.neoforge.fluids.capability.IFluidHandler; @@ -120,7 +123,8 @@ public boolean test(InWorldRecipeContext context) { // 不是锅 否决 BlockPos pos = BlockPos.containing(context.getPos().add(this.offset())); BlockCache cache = context.computeIfAbsent(BlockCache.BLOCK_CACHE); - if (!cache.getBlockState(pos).is(BlockTags.CAULDRONS)) return false; + IEntityCauldron entityCauldron = findEntityCauldron(context, pos); + if (!cache.getBlockState(pos).is(BlockTags.CAULDRONS) && entityCauldron == null) return false; if (cache.getBlockEntity(pos) instanceof LargeCauldronBlockEntity cauldron) { if (this.consume() > LargeCauldronFluidHandler.TANK_CAPACITY @@ -129,26 +133,28 @@ public boolean test(InWorldRecipeContext context) { } // 消耗/产生比锅容量大 否决 - double capacity = HasCauldron.getCapacity(cache, pos); + double capacity = HasCauldron.getCapacity(cache, pos, entityCauldron); if (this.consume() > capacity || this.produce() > capacity) return false; // 锅中流体检查不通过 否决 - ResourceLocation curFluid = HasCauldron.getCurFluid(cache, pos); + ResourceLocation curFluid = HasCauldron.getCurFluid(cache, pos, entityCauldron); if (this.hasCheck() && !this.matchesFluid(curFluid)) return false; // 如果锅必须为可点燃锅 if (this.ignited) { - // 锅不为可点燃锅 否决 - if (!(cache.getBlockState(pos).getBlock() instanceof IIgnitableCauldron cauldron)) return false; - // 锅未点燃 否决 - if (!cauldron.isIgnited(cache, pos)) return false; + if (entityCauldron != null) { + if (!entityCauldron.anvilcraft$isIgnited()) return false; + } else { + if (!(cache.getBlockState(pos).getBlock() instanceof IIgnitableCauldron cauldron)) return false; + if (!cauldron.isIgnited(cache, pos)) return false; + } } // 不消耗且不产生 通过 if (this.consume() == 0 && this.produce() == 0) return true; // 消耗量大于存量 否决 - double cur = HasCauldron.getCur(cache, pos); + double cur = HasCauldron.getCur(cache, pos, entityCauldron); if (this.consume() > cur) return false; // 最终总量超出容量 否决 @@ -176,7 +182,8 @@ public void accept(InWorldRecipeContext context) { if (context.getLevel().getRandom().nextFloat() > this.chance()) return; if (this.fluid().equals(EMPTY) && !HasCauldron.isNotEmpty(this.transform())) return; - double cur = HasCauldron.getCur(cache, pos); + IEntityCauldron entityCauldron = findEntityCauldron(context, pos); + double cur = HasCauldron.getCur(cache, pos, entityCauldron); double afterConsume = cur - this.consume(); double amount = afterConsume + this.produce(); @@ -184,9 +191,9 @@ public void accept(InWorldRecipeContext context) { if (!HasCauldron.isNotEmpty(newFluid)) newFluid = this.fluid(); if (!HasCauldron.isNotEmpty(newFluid)) return; if (amount > 0 && HasCauldron.isNotEmpty(newFluid)) { - HasCauldron.applyFluid(cache, pos, newFluid, amount, this.ignited); + HasCauldron.applyFluid(cache, pos, newFluid, amount, this.ignited, entityCauldron); } else { - HasCauldron.applyEmpty(cache, pos); + HasCauldron.applyEmpty(cache, pos, entityCauldron); } context.putAcceptor(BlockCache.BLOCK_CACHE.location(), BlockCache.DEFAULT_ACCEPTOR); @@ -250,9 +257,12 @@ public boolean matchesFluid(ResourceLocation curFluid) { } public static double getCapacity(BlockCache cache, BlockPos pos) { - return cache.getBlockEntity(pos) instanceof IFluidHandlerHolder holder - ? holder.getFluidHandler().getTankCapacity(0) - : 1000; + return getCapacity(cache, pos, null); + } + + private static double getCapacity(BlockCache cache, BlockPos pos, @Nullable IEntityCauldron entityCauldron) { + IFluidHandler handler = getFluidHandler(cache, pos, entityCauldron); + return handler == null ? 1000 : handler.getTankCapacity(0); } /** @@ -262,11 +272,22 @@ public static double getCapacity(BlockCache cache, BlockPos pos) { */ @SuppressWarnings("deprecation") public static ResourceLocation getCurFluid(BlockCache cache, BlockPos pos) { - return cache.getBlockEntity(pos) instanceof IFluidHandlerHolder holder - ? holder.getFluidHandler().getFluidInTank(0).getFluidHolder().getKey().location() - : cache.getBlockState(pos).getBlock() instanceof IIgnitableCauldron cauldron - ? cauldron.getFluid(cache, pos).builtInRegistryHolder().key().location() - : WrapUtils.cauldron2Fluid(cache.getBlockState(pos).getBlock()); + return getCurFluid(cache, pos, null); + } + + private static ResourceLocation getCurFluid( + BlockCache cache, + BlockPos pos, + @Nullable IEntityCauldron entityCauldron + ) { + IFluidHandler handler = getFluidHandler(cache, pos, entityCauldron); + if (handler != null) { + FluidStack stack = handler.getFluidInTank(0); + return stack.isEmpty() ? EMPTY : stack.getFluidHolder().getKey().location(); + } + return cache.getBlockState(pos).getBlock() instanceof IIgnitableCauldron cauldron + ? cauldron.getFluid(cache, pos).builtInRegistryHolder().key().location() + : WrapUtils.cauldron2Fluid(cache.getBlockState(pos).getBlock()); } /** @@ -275,7 +296,12 @@ public static ResourceLocation getCurFluid(BlockCache cache, BlockPos pos) { * @return 炼药锅方块 */ public static double getCur(BlockCache cache, BlockPos pos) { - if (cache.getBlockEntity(pos) instanceof IFluidHandlerHolder holder) return holder.getFluidHandler().getFluidInTank(0).getAmount(); + return getCur(cache, pos, null); + } + + private static double getCur(BlockCache cache, BlockPos pos, @Nullable IEntityCauldron entityCauldron) { + IFluidHandler handler = getFluidHandler(cache, pos, entityCauldron); + if (handler != null) return handler.getFluidInTank(0).getAmount(); BlockState state = cache.getBlockState(pos); if (state.is(Blocks.CAULDRON)) return 0.0; IntegerProperty property = CauldronUtil.LEVEL_4; @@ -289,8 +315,12 @@ public static double getCur(BlockCache cache, BlockPos pos) { } public static void applyEmpty(BlockCache cache, BlockPos pos) { - if (cache.getBlockEntity(pos) instanceof IFluidHandlerHolder holder) { - IFluidHandler handler = holder.getFluidHandler(); + applyEmpty(cache, pos, null); + } + + private static void applyEmpty(BlockCache cache, BlockPos pos, @Nullable IEntityCauldron entityCauldron) { + IFluidHandler handler = getFluidHandler(cache, pos, entityCauldron); + if (handler != null) { handler.drain(Integer.MAX_VALUE, IFluidHandler.FluidAction.EXECUTE); } else { cache.setBlock(pos, Blocks.CAULDRON); @@ -298,11 +328,23 @@ public static void applyEmpty(BlockCache cache, BlockPos pos) { } public static void applyFluid(BlockCache cache, BlockPos pos, ResourceLocation fluid, double mb, boolean ignited) { + applyFluid(cache, pos, fluid, mb, ignited, null); + } + + private static void applyFluid( + BlockCache cache, + BlockPos pos, + ResourceLocation fluid, + double mb, + boolean ignited, + @Nullable IEntityCauldron entityCauldron + ) { if (cache.getBlockState(pos).getBlock() instanceof IIgnitableCauldron cauldron) { if (cauldron.isIgnited(cache, pos)) ignited = true; } - if (cache.getBlockEntity(pos) instanceof IFluidHandlerHolder holder) { - IFluidHandler handler = holder.getFluidHandler(); + if (entityCauldron != null && entityCauldron.anvilcraft$isIgnited()) ignited = true; + IFluidHandler handler = getFluidHandler(cache, pos, entityCauldron); + if (handler != null) { FluidStack fluidInTank = handler.getFluidInTank(0); Fluid target = BuiltInRegistries.FLUID.get(fluid); if (fluidInTank.is(target)) { @@ -334,6 +376,35 @@ public static void applyFluid(BlockCache cache, BlockPos pos, ResourceLocation f if (cache.getBlockState(pos).getBlock() instanceof IIgnitableCauldron cauldron) { if (cauldron.isIgnited(cache, pos) != ignited) cauldron.setIgnited(cache, pos, ignited); } + if (entityCauldron != null && entityCauldron.anvilcraft$isIgnited() != ignited) { + entityCauldron.anvilcraft$setIgnited(ignited); + } + } + + private static @Nullable IFluidHandler getFluidHandler( + BlockCache cache, + BlockPos pos, + @Nullable IEntityCauldron entityCauldron + ) { + if (entityCauldron != null) return entityCauldron.getFluidHandler(); + return cache.getBlockEntity(pos) instanceof IFluidHandlerHolder holder ? holder.getFluidHandler() : null; + } + + private static @Nullable IEntityCauldron findEntityCauldron(InWorldRecipeContext context, BlockPos pos) { + Vec3 center = pos.getCenter(); + IEntityCauldron closest = null; + double closestDistance = Double.POSITIVE_INFINITY; + for (Entity entity : context.getLevel().getEntitiesOfClass( + Entity.class, + new AABB(pos).inflate(0.0625), + entity -> entity.isAlive() && entity instanceof IEntityCauldron + )) { + double distance = entity.getBoundingBox().getCenter().distanceToSqr(center); + if (distance >= closestDistance) continue; + closest = (IEntityCauldron) entity; + closestDistance = distance; + } + return closest; } /** diff --git a/src/main/java/dev/dubhe/anvilcraft/recipe/anvil/wrap/SuperHeatingRecipe.java b/src/main/java/dev/dubhe/anvilcraft/recipe/anvil/wrap/SuperHeatingRecipe.java index 08c8cbf6e4..ad90302126 100644 --- a/src/main/java/dev/dubhe/anvilcraft/recipe/anvil/wrap/SuperHeatingRecipe.java +++ b/src/main/java/dev/dubhe/anvilcraft/recipe/anvil/wrap/SuperHeatingRecipe.java @@ -291,7 +291,7 @@ public record ConsumeFuel() implements IRecipeOutcome { public static final int FUEL_COST_TICKS = 240 * 20; /** - * 标记一次铁砧落地事件中是否已消耗过燃料,防止多个物品匹配时重复消耗 + * 标记当前配方上下文是否已消耗过燃料,避免同一结果被重复接受 */ public static final InWorldRecipeData FUEL_CONSUMED = InWorldRecipeData.of(AnvilCraft.of("super_heating_fuel_consumed"), false); @@ -328,4 +328,4 @@ public StreamCodec streamCodec() { } } } -} \ No newline at end of file +} diff --git a/src/main/java/dev/dubhe/anvilcraft/util/AccelerateManager.java b/src/main/java/dev/dubhe/anvilcraft/util/AccelerateManager.java index f9aba9c8cd..b3ef02d384 100644 --- a/src/main/java/dev/dubhe/anvilcraft/util/AccelerateManager.java +++ b/src/main/java/dev/dubhe/anvilcraft/util/AccelerateManager.java @@ -1,5 +1,6 @@ package dev.dubhe.anvilcraft.util; +import dev.dubhe.anvilcraft.api.entity.IAnvilCraftEntityExtension; import dev.dubhe.anvilcraft.api.power.IPowerComponent; import dev.dubhe.anvilcraft.block.AccelerationRingBlock; import dev.dubhe.anvilcraft.block.entity.AccelerationRingBlockEntity; @@ -67,8 +68,12 @@ private static boolean isActiveAccelerationRing(BlockState state) { public static boolean canBeAccelerated(Entity entity) { return entity instanceof FallingBlockEntity fallingBlockEntity - && fallingBlockEntity.getBlockState().is(BlockTags.ANVIL) - && !fallingBlockEntity.getBlockState().is(ModBlockTags.NON_MAGNETIC) + && (fallingBlockEntity.getBlockState().is(BlockTags.ANVIL) + || entity instanceof IAnvilCraftEntityExtension extension + && extension.anvilcraft$isMagnetized()) + && (!(entity instanceof IAnvilCraftEntityExtension extension) + ? !fallingBlockEntity.getBlockState().is(ModBlockTags.NON_MAGNETIC) + : extension.anvilcraft$isMagnetized()) || entity instanceof Projectile || (entity instanceof Player player && isPlayerCanBeAccelerated(player)); } @@ -170,7 +175,10 @@ public static Vec3 limitAnvilSpeed(Entity entity, Vec3 movement) { } private static boolean isAnvil(Entity entity) { - if (entity instanceof FallingBlockEntity falling) return falling.getBlockState().is(BlockTags.ANVIL); + if (entity instanceof FallingBlockEntity falling) { + return falling.getBlockState().is(BlockTags.ANVIL) + || entity instanceof IAnvilCraftEntityExtension extension && extension.anvilcraft$isMagnetized(); + } return entity instanceof RailgunAnvilEntity railgun && railgun.getBlockState().is(BlockTags.ANVIL); } diff --git a/src/main/java/dev/dubhe/anvilcraft/util/CauldronUtil.java b/src/main/java/dev/dubhe/anvilcraft/util/CauldronUtil.java index 0c1d695aab..68731ee865 100644 --- a/src/main/java/dev/dubhe/anvilcraft/util/CauldronUtil.java +++ b/src/main/java/dev/dubhe/anvilcraft/util/CauldronUtil.java @@ -1,7 +1,11 @@ package dev.dubhe.anvilcraft.util; +import dev.dubhe.anvilcraft.api.block.ICauldronGeometry; import dev.dubhe.anvilcraft.block.Layered4LevelCauldronBlock; import net.minecraft.core.BlockPos; +import net.minecraft.tags.BlockTags; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.AbstractCauldronBlock; import net.minecraft.world.level.block.Block; @@ -9,10 +13,12 @@ import net.minecraft.world.level.block.LayeredCauldronBlock; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.properties.IntegerProperty; +import net.minecraft.world.phys.AABB; import net.neoforged.neoforge.fluids.CauldronFluidContent; import net.neoforged.neoforge.fluids.FluidStack; import net.neoforged.neoforge.fluids.capability.IFluidHandler; +import java.util.List; import java.util.Optional; /** @@ -235,4 +241,32 @@ public static boolean compatibleForFill(BlockState state, Block cauldronContent, if (fillLevel <= 0) return true; return remainSpaceFor(state, cauldronContent) >= fillLevel; } + + public static AABB getInnerArea(BlockPos pos, BlockState state) { + if (state.getBlock() instanceof ICauldronGeometry geometry) { + return geometry.getCauldronInnerArea(pos, state); + } + return new AABB(pos); + } + + public static List getBottomPositions(BlockPos pos, BlockState state) { + if (state.getBlock() instanceof ICauldronGeometry geometry) { + return geometry.getCauldronBottomPositions(pos, state); + } + return List.of(pos.below()); + } + + public static boolean isEntityInside(BlockPos pos, BlockState state, Entity entity) { + return getInnerArea(pos, state).contains(entity.position()); + } + + public static void hurtFromHeaterBelow(Entity entity) { + if (!(entity instanceof LivingEntity) || entity.level().isClientSide()) return; + BlockPos pos = entity.blockPosition(); + BlockState state = entity.level().getBlockState(pos); + if (!state.is(BlockTags.CAULDRONS) || !isEntityInside(pos, state, entity)) return; + for (BlockPos bottomPos : getBottomPositions(pos, state)) { + if (HeaterUtil.hurtEntity(entity.level(), entity.level().getBlockState(bottomPos), entity)) return; + } + } } diff --git a/src/main/java/dev/dubhe/anvilcraft/util/GravityManager.java b/src/main/java/dev/dubhe/anvilcraft/util/GravityManager.java index c2e38ab023..7a414be46b 100644 --- a/src/main/java/dev/dubhe/anvilcraft/util/GravityManager.java +++ b/src/main/java/dev/dubhe/anvilcraft/util/GravityManager.java @@ -1,6 +1,7 @@ package dev.dubhe.anvilcraft.util; import dev.dubhe.anvilcraft.AnvilCraft; +import dev.dubhe.anvilcraft.api.entity.IAnvilCraftEntityExtension; import dev.dubhe.anvilcraft.block.BlackHoleBlock; import dev.dubhe.anvilcraft.block.WhiteHoleBlock; import dev.dubhe.anvilcraft.entity.LevitatingBlockEntity; @@ -107,6 +108,10 @@ public static void applyNetworkSync( } public static GravityType getGravityType(Entity entity) { + if (entity instanceof IAnvilCraftEntityExtension extension) { + GravityType supplied = extension.anvilcraft$getGravityType(); + if (supplied != null) return supplied; + } if (entity instanceof ItemEntity itemEntity) { var item = itemEntity.getItem(); if (item.is(ModItems.NEGATIVE_MATTER_NUGGET.get()) @@ -139,11 +144,16 @@ public static Vec3 getGravityVector(Entity entity) { } public static Vec3 getGravityVector(Entity entity, double baseGravity) { - return GravitySourceManager.calculateGravityVector( + Vec3 gravity = GravitySourceManager.calculateGravityVector( entity.level(), entity.getBoundingBox().getCenter(), Math.abs(baseGravity) - ).scale(getGravityType(entity).getScalar()); + ); + if (entity instanceof IAnvilCraftEntityExtension extension) { + Vec3 additional = extension.anvilcraft$getAdditionalGravity(Math.abs(baseGravity)); + if (isFinite(additional)) gravity = gravity.add(additional); + } + return gravity.scale(getGravityType(entity).getScalar()); } /** @@ -191,11 +201,11 @@ public static Vec3 getNetGravityVectorForFallingBlock(Level level, Vec3 pos, Gra } public static Vec3 getNetGravityVectorForFallingBlock(Entity entity) { - return getNetGravityVectorForFallingBlock( - entity.level(), - entity.getBoundingBox().getCenter(), - getGravityType(entity) - ); + GravityType gravityType = getGravityType(entity); + double scalar = gravityType.getScalar(); + double baseGravity = 0.04 * getDimensionGravity(entity.level()); + Vec3 baseGravityVector = new Vec3(0, -baseGravity * scalar, 0); + return baseGravityVector.add(getGravityVector(entity, 0.04)); } public static void registerDimensionGravity(ResourceKey dimension, double gravity) { diff --git a/src/main/java/dev/dubhe/anvilcraft/util/HeaterUtil.java b/src/main/java/dev/dubhe/anvilcraft/util/HeaterUtil.java new file mode 100644 index 0000000000..8963adeec6 --- /dev/null +++ b/src/main/java/dev/dubhe/anvilcraft/util/HeaterUtil.java @@ -0,0 +1,21 @@ +package dev.dubhe.anvilcraft.util; + +import dev.dubhe.anvilcraft.api.block.IDamagingHeater; +import dev.dubhe.anvilcraft.init.entity.ModDamageTypes; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.state.BlockState; + +public class HeaterUtil { + public static boolean hurtEntity(Level level, BlockState state, Entity entity) { + if (!(state.getBlock() instanceof IDamagingHeater heater) + || !heater.isActive(state) + || entity.isSteppingCarefully() + || !(entity instanceof LivingEntity)) { + return false; + } + entity.hurt(ModDamageTypes.heaterBurn(level), 4.0F); + return true; + } +} diff --git a/src/main/java/dev/dubhe/anvilcraft/util/MagnetUtil.java b/src/main/java/dev/dubhe/anvilcraft/util/MagnetUtil.java index 89fc6ebe03..8efa61d499 100644 --- a/src/main/java/dev/dubhe/anvilcraft/util/MagnetUtil.java +++ b/src/main/java/dev/dubhe/anvilcraft/util/MagnetUtil.java @@ -1,6 +1,7 @@ package dev.dubhe.anvilcraft.util; import dev.dubhe.anvilcraft.AnvilCraft; +import dev.dubhe.anvilcraft.api.entity.IAnvilCraftEntityExtension; import dev.dubhe.anvilcraft.api.event.UseMagnetEvent; import dev.dubhe.anvilcraft.block.MagnetBlock; import dev.dubhe.anvilcraft.entity.MagnetizedNodeEntity; @@ -43,6 +44,21 @@ public static InteractionResult placeMagnetizedNode(Item item, UseOnContext cont if (player == null) return InteractionResult.PASS; if (!player.isShiftKeyDown()) return InteractionResult.PASS; BlockPos pos = context.getClickedPos(); + if (player.isShiftKeyDown()) { + AABB entityArea = new AABB(pos).inflate(0.08D); + for (Entity entity : level.getEntities( + player, + entityArea, + candidate -> candidate instanceof IAnvilCraftEntityExtension + && candidate.isAlive() + )) { + IAnvilCraftEntityExtension extension = (IAnvilCraftEntityExtension) entity; + if (extension.anvilcraft$acceptMagnetization(player, context.getItemInHand())) { + player.getCooldowns().addCooldown(item, 5); + return InteractionResult.sidedSuccess(level.isClientSide()); + } + } + } BlockState blockState = level.getBlockState(pos); if (blockState.isAir()) return InteractionResult.PASS; double maxY = blockState.getCollisionShape(level, pos).max(Direction.Axis.Y, 0.5, 0.5); diff --git a/src/main/resources/assets/anvilcraft/models/block/redstone_wire_side_corner_sp.json b/src/main/resources/assets/anvilcraft/models/block/redstone_wire_side_corner_sp.json new file mode 100644 index 0000000000..9995725f40 --- /dev/null +++ b/src/main/resources/assets/anvilcraft/models/block/redstone_wire_side_corner_sp.json @@ -0,0 +1,36 @@ +{ + "format_version": "1.9.0", + "credit": "Made by XeKr with Blockbench", + "ambientocclusion": false, + "textures": { + "0": "anvilcraft:block/redstone_wire_line", + "1": "anvilcraft:block/redstone_wire_line_overlay", + "particle": "anvilcraft:block/redstone_wire_line" + }, + "elements": [ + { + "from": [5, 0, 0], + "to": [11, 1, 8], + "rotation": {"angle": 0, "axis": "y", "origin": [6, 0, 5]}, + "faces": { + "north": {"uv": [5, 0, 11, 1], "texture": "#0", "cullface": "north"}, + "east": {"uv": [10, 0, 11, 8], "rotation": 90, "texture": "#0"}, + "west": {"uv": [5, 8, 6, 0], "rotation": 90, "texture": "#0"}, + "up": {"uv": [5, 0, 11, 8], "texture": "#0"}, + "down": {"uv": [11, 0, 5, 8], "texture": "#0", "cullface": "down"} + } + }, + { + "from": [6, 0.01, -1], + "to": [10, 2, 8], + "rotation": {"angle": 0, "axis": "y", "origin": [6, 1, 5]}, + "faces": { + "north": {"uv": [6, 0, 10, 1], "texture": "#1", "cullface": "north", "tintindex": 0}, + "east": {"uv": [6, 0, 7, 9], "rotation": 90, "texture": "#1", "tintindex": 0}, + "west": {"uv": [6, 0, 7, 9], "rotation": 90, "texture": "#1", "tintindex": 0}, + "up": {"uv": [6, 0, 10, 9], "texture": "#1", "tintindex": 0}, + "down": {"uv": [6, 0, 10, 9], "texture": "#1", "tintindex": 0} + } + } + ] +} diff --git a/src/main/resources/assets/anvilcraft/models/block/redstone_wire_up.json b/src/main/resources/assets/anvilcraft/models/block/redstone_wire_up.json index 2c92691dfe..a7bc5ce9c3 100644 --- a/src/main/resources/assets/anvilcraft/models/block/redstone_wire_up.json +++ b/src/main/resources/assets/anvilcraft/models/block/redstone_wire_up.json @@ -9,7 +9,7 @@ }, "elements": [ { - "from": [5, 1, -0.1], + "from": [5, 1, 0], "to": [11, 17, 1], "rotation": {"angle": 0, "axis": "x", "origin": [8, 9, 8]}, "faces": {