1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-01-10 01:10:30 +00:00

Add a test for exploding turtles

There's been a couple of bug reports in the past where the game would
crash if a turtle is destroyed while breaking a block (typically due to
the block exploding). This commit adds a test, to ensure that this is
handled gracefully.

I'm not entirely sure this is testing the right thing. Looking at the
issues in question, it doesn't look like I ever managed to reproduce the
bug. However, it's hopefully at least a quick sanity test to check we
never break this case.
This commit is contained in:
Jonathan Coates 2024-08-14 22:41:31 +01:00
parent bb97c465d9
commit 87dfad026e
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
5 changed files with 185 additions and 0 deletions

View File

@ -693,6 +693,20 @@ class Turtle_Test {
} }
} }
/**
* Tests a turtle can break a block that explodes, causing the turtle itself to explode.
*
* This attempts to test [#585](https://github.com/cc-tweaked/CC-Tweaked/issues/585) and other similar issues. It's
* not clear if this is a good test case, as that bug does not seem reliably reproducible, but it's at least a good
* sanity check.
*/
@GameTest
fun Breaks_exploding_block(context: GameTestHelper) = context.sequence {
thenOnComputer { turtle.dig(Optional.empty()) }
thenIdle(2)
thenExecute { context.assertItemEntityPresent(ModRegistry.Items.TURTLE_NORMAL.get(), BlockPos(2, 2, 2), 1.0) }
}
/** /**
* Render turtles as an item. * Render turtles as an item.
*/ */

View File

@ -13,7 +13,13 @@ import dan200.computercraft.shared.computer.core.ServerContext
import net.minecraft.core.BlockPos import net.minecraft.core.BlockPos
import net.minecraft.gametest.framework.* import net.minecraft.gametest.framework.*
import net.minecraft.server.MinecraftServer import net.minecraft.server.MinecraftServer
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.level.GameRules import net.minecraft.world.level.GameRules
import net.minecraft.world.level.Level
import net.minecraft.world.level.LevelAccessor
import net.minecraft.world.level.block.Blocks
import net.minecraft.world.level.block.state.BlockState
import net.minecraft.world.phys.Vec3
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.io.File import java.io.File
@ -173,4 +179,23 @@ object TestHooks {
throw RuntimeException(e) throw RuntimeException(e)
} }
} }
/**
* Adds a hook that makes breaking a bone block spawn an explosion.
*
* It would be more Correct to register a custom block, but that's quite a lot of work, and doesn't seem worth it
* for test code.
*
* See also [Turtle_Test.Breaks_exploding_block].
*/
@JvmStatic
fun onBeforeDestroyBlock(level: LevelAccessor, pos: BlockPos, state: BlockState): Boolean {
if (state.block === Blocks.BONE_BLOCK && level is ServerLevel) {
val explosionPos = Vec3.atCenterOf(pos)
level.explode(null, explosionPos.x, explosionPos.y, explosionPos.z, 4.0f, Level.ExplosionInteraction.TNT)
return true
}
return false
}
} }

View File

@ -0,0 +1,139 @@
{
DataVersion: 3465,
size: [5, 5, 5],
data: [
{pos: [0, 0, 0], state: "minecraft:obsidian"},
{pos: [0, 0, 1], state: "minecraft:obsidian"},
{pos: [0, 0, 2], state: "minecraft:obsidian"},
{pos: [0, 0, 3], state: "minecraft:obsidian"},
{pos: [0, 0, 4], state: "minecraft:obsidian"},
{pos: [1, 0, 0], state: "minecraft:obsidian"},
{pos: [1, 0, 1], state: "minecraft:obsidian"},
{pos: [1, 0, 2], state: "minecraft:obsidian"},
{pos: [1, 0, 3], state: "minecraft:obsidian"},
{pos: [1, 0, 4], state: "minecraft:obsidian"},
{pos: [2, 0, 0], state: "minecraft:obsidian"},
{pos: [2, 0, 1], state: "minecraft:obsidian"},
{pos: [2, 0, 2], state: "minecraft:obsidian"},
{pos: [2, 0, 3], state: "minecraft:obsidian"},
{pos: [2, 0, 4], state: "minecraft:obsidian"},
{pos: [3, 0, 0], state: "minecraft:obsidian"},
{pos: [3, 0, 1], state: "minecraft:obsidian"},
{pos: [3, 0, 2], state: "minecraft:obsidian"},
{pos: [3, 0, 3], state: "minecraft:obsidian"},
{pos: [3, 0, 4], state: "minecraft:obsidian"},
{pos: [4, 0, 0], state: "minecraft:obsidian"},
{pos: [4, 0, 1], state: "minecraft:obsidian"},
{pos: [4, 0, 2], state: "minecraft:obsidian"},
{pos: [4, 0, 3], state: "minecraft:obsidian"},
{pos: [4, 0, 4], state: "minecraft:obsidian"},
{pos: [0, 1, 0], state: "minecraft:barrier"},
{pos: [0, 1, 1], state: "minecraft:barrier"},
{pos: [0, 1, 2], state: "minecraft:barrier"},
{pos: [0, 1, 3], state: "minecraft:barrier"},
{pos: [0, 1, 4], state: "minecraft:barrier"},
{pos: [1, 1, 0], state: "minecraft:barrier"},
{pos: [1, 1, 1], state: "minecraft:air"},
{pos: [1, 1, 2], state: "minecraft:air"},
{pos: [1, 1, 3], state: "minecraft:air"},
{pos: [1, 1, 4], state: "minecraft:barrier"},
{pos: [2, 1, 0], state: "minecraft:barrier"},
{pos: [2, 1, 1], state: "minecraft:air"},
{pos: [2, 1, 2], state: "computercraft:turtle_normal{facing:south,waterlogged:false}", nbt: {ComputerId: 1, Fuel: 0, Items: [], Label: "turtle_test.breaks_exploding_block", LeftUpgrade: "minecraft:diamond_pickaxe", LeftUpgradeNbt: {Tag: {Damage: 0}}, On: 1b, Owner: {LowerId: -5670393268852517359L, Name: "Player172", UpperId: 3578583684139923613L}, Slot: 0, id: "computercraft:turtle_normal"}},
{pos: [2, 1, 3], state: "minecraft:bone_block{axis:y}"},
{pos: [2, 1, 4], state: "minecraft:barrier"},
{pos: [3, 1, 0], state: "minecraft:barrier"},
{pos: [3, 1, 1], state: "minecraft:air"},
{pos: [3, 1, 2], state: "minecraft:air"},
{pos: [3, 1, 3], state: "minecraft:air"},
{pos: [3, 1, 4], state: "minecraft:barrier"},
{pos: [4, 1, 0], state: "minecraft:barrier"},
{pos: [4, 1, 1], state: "minecraft:barrier"},
{pos: [4, 1, 2], state: "minecraft:barrier"},
{pos: [4, 1, 3], state: "minecraft:barrier"},
{pos: [4, 1, 4], state: "minecraft:barrier"},
{pos: [0, 2, 0], state: "minecraft:barrier"},
{pos: [0, 2, 1], state: "minecraft:barrier"},
{pos: [0, 2, 2], state: "minecraft:air"},
{pos: [0, 2, 3], state: "minecraft:barrier"},
{pos: [0, 2, 4], state: "minecraft:barrier"},
{pos: [1, 2, 0], state: "minecraft:barrier"},
{pos: [1, 2, 1], state: "minecraft:air"},
{pos: [1, 2, 2], state: "minecraft:air"},
{pos: [1, 2, 3], state: "minecraft:air"},
{pos: [1, 2, 4], state: "minecraft:barrier"},
{pos: [2, 2, 0], state: "minecraft:barrier"},
{pos: [2, 2, 1], state: "minecraft:air"},
{pos: [2, 2, 2], state: "minecraft:air"},
{pos: [2, 2, 3], state: "minecraft:air"},
{pos: [2, 2, 4], state: "minecraft:barrier"},
{pos: [3, 2, 0], state: "minecraft:barrier"},
{pos: [3, 2, 1], state: "minecraft:air"},
{pos: [3, 2, 2], state: "minecraft:air"},
{pos: [3, 2, 3], state: "minecraft:air"},
{pos: [3, 2, 4], state: "minecraft:barrier"},
{pos: [4, 2, 0], state: "minecraft:barrier"},
{pos: [4, 2, 1], state: "minecraft:barrier"},
{pos: [4, 2, 2], state: "minecraft:barrier"},
{pos: [4, 2, 3], state: "minecraft:barrier"},
{pos: [4, 2, 4], state: "minecraft:barrier"},
{pos: [0, 3, 0], state: "minecraft:air"},
{pos: [0, 3, 1], state: "minecraft:air"},
{pos: [0, 3, 2], state: "minecraft:air"},
{pos: [0, 3, 3], state: "minecraft:air"},
{pos: [0, 3, 4], state: "minecraft:air"},
{pos: [1, 3, 0], state: "minecraft:air"},
{pos: [1, 3, 1], state: "minecraft:air"},
{pos: [1, 3, 2], state: "minecraft:air"},
{pos: [1, 3, 3], state: "minecraft:air"},
{pos: [1, 3, 4], state: "minecraft:air"},
{pos: [2, 3, 0], state: "minecraft:air"},
{pos: [2, 3, 1], state: "minecraft:air"},
{pos: [2, 3, 2], state: "minecraft:air"},
{pos: [2, 3, 3], state: "minecraft:air"},
{pos: [2, 3, 4], state: "minecraft:air"},
{pos: [3, 3, 0], state: "minecraft:air"},
{pos: [3, 3, 1], state: "minecraft:air"},
{pos: [3, 3, 2], state: "minecraft:air"},
{pos: [3, 3, 3], state: "minecraft:air"},
{pos: [3, 3, 4], state: "minecraft:air"},
{pos: [4, 3, 0], state: "minecraft:air"},
{pos: [4, 3, 1], state: "minecraft:air"},
{pos: [4, 3, 2], state: "minecraft:air"},
{pos: [4, 3, 3], state: "minecraft:air"},
{pos: [4, 3, 4], state: "minecraft:air"},
{pos: [0, 4, 0], state: "minecraft:air"},
{pos: [0, 4, 1], state: "minecraft:air"},
{pos: [0, 4, 2], state: "minecraft:air"},
{pos: [0, 4, 3], state: "minecraft:air"},
{pos: [0, 4, 4], state: "minecraft:air"},
{pos: [1, 4, 0], state: "minecraft:air"},
{pos: [1, 4, 1], state: "minecraft:air"},
{pos: [1, 4, 2], state: "minecraft:air"},
{pos: [1, 4, 3], state: "minecraft:air"},
{pos: [1, 4, 4], state: "minecraft:air"},
{pos: [2, 4, 0], state: "minecraft:air"},
{pos: [2, 4, 1], state: "minecraft:air"},
{pos: [2, 4, 2], state: "minecraft:air"},
{pos: [2, 4, 3], state: "minecraft:air"},
{pos: [2, 4, 4], state: "minecraft:air"},
{pos: [3, 4, 0], state: "minecraft:air"},
{pos: [3, 4, 1], state: "minecraft:air"},
{pos: [3, 4, 2], state: "minecraft:air"},
{pos: [3, 4, 3], state: "minecraft:air"},
{pos: [3, 4, 4], state: "minecraft:air"},
{pos: [4, 4, 0], state: "minecraft:air"},
{pos: [4, 4, 1], state: "minecraft:air"},
{pos: [4, 4, 2], state: "minecraft:air"},
{pos: [4, 4, 3], state: "minecraft:air"},
{pos: [4, 4, 4], state: "minecraft:air"}
],
entities: [],
palette: [
"minecraft:obsidian",
"minecraft:barrier",
"minecraft:bone_block{axis:y}",
"minecraft:air",
"computercraft:turtle_normal{facing:south,waterlogged:false}"
]
}

View File

@ -14,6 +14,7 @@ import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.fabricmc.fabric.api.event.Event; import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
import net.fabricmc.fabric.api.event.player.PlayerBlockBreakEvents;
import net.minecraft.gametest.framework.GameTestRegistry; import net.minecraft.gametest.framework.GameTestRegistry;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
@ -26,6 +27,7 @@ public class TestMod implements ModInitializer, ClientModInitializer {
ServerLifecycleEvents.SERVER_STARTED.addPhaseOrdering(Event.DEFAULT_PHASE, phase); ServerLifecycleEvents.SERVER_STARTED.addPhaseOrdering(Event.DEFAULT_PHASE, phase);
ServerLifecycleEvents.SERVER_STARTED.register(phase, TestHooks::onServerStarted); ServerLifecycleEvents.SERVER_STARTED.register(phase, TestHooks::onServerStarted);
CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> CCTestCommand.register(dispatcher)); CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> CCTestCommand.register(dispatcher));
PlayerBlockBreakEvents.BEFORE.register((level, player, pos, state, blockEntity) -> !TestHooks.onBeforeDestroyBlock(level, pos, state));
TestHooks.loadTests(GameTestRegistry::register); TestHooks.loadTests(GameTestRegistry::register);
} }

View File

@ -12,6 +12,7 @@ import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.RegisterCommandsEvent; import net.minecraftforge.event.RegisterCommandsEvent;
import net.minecraftforge.event.RegisterGameTestsEvent; import net.minecraftforge.event.RegisterGameTestsEvent;
import net.minecraftforge.event.TickEvent; import net.minecraftforge.event.TickEvent;
import net.minecraftforge.event.level.BlockEvent;
import net.minecraftforge.event.server.ServerStartedEvent; import net.minecraftforge.event.server.ServerStartedEvent;
import net.minecraftforge.eventbus.api.EventPriority; import net.minecraftforge.eventbus.api.EventPriority;
import net.minecraftforge.fml.DistExecutor; import net.minecraftforge.fml.DistExecutor;
@ -26,6 +27,10 @@ public class TestMod {
var bus = MinecraftForge.EVENT_BUS; var bus = MinecraftForge.EVENT_BUS;
bus.addListener(EventPriority.LOW, (ServerStartedEvent e) -> TestHooks.onServerStarted(e.getServer())); bus.addListener(EventPriority.LOW, (ServerStartedEvent e) -> TestHooks.onServerStarted(e.getServer()));
bus.addListener((RegisterCommandsEvent e) -> CCTestCommand.register(e.getDispatcher())); bus.addListener((RegisterCommandsEvent e) -> CCTestCommand.register(e.getDispatcher()));
bus.addListener((BlockEvent.BreakEvent e) -> {
if (TestHooks.onBeforeDestroyBlock(e.getLevel(), e.getPos(), e.getState())) e.setCanceled(true);
});
DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> TestMod::onInitializeClient); DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> TestMod::onInitializeClient);
var modBus = FMLJavaModLoadingContext.get().getModEventBus(); var modBus = FMLJavaModLoadingContext.get().getModEventBus();