1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-01-09 00:40:29 +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.
*/

View File

@ -13,7 +13,13 @@ import dan200.computercraft.shared.computer.core.ServerContext
import net.minecraft.core.BlockPos
import net.minecraft.gametest.framework.*
import net.minecraft.server.MinecraftServer
import net.minecraft.server.level.ServerLevel
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.LoggerFactory
import java.io.File
@ -173,4 +179,23 @@ object TestHooks {
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.lifecycle.v1.ServerLifecycleEvents;
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.resources.ResourceLocation;
@ -26,6 +27,7 @@ public class TestMod implements ModInitializer, ClientModInitializer {
ServerLifecycleEvents.SERVER_STARTED.addPhaseOrdering(Event.DEFAULT_PHASE, phase);
ServerLifecycleEvents.SERVER_STARTED.register(phase, TestHooks::onServerStarted);
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);
}

View File

@ -12,6 +12,7 @@ import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.RegisterCommandsEvent;
import net.minecraftforge.event.RegisterGameTestsEvent;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.event.level.BlockEvent;
import net.minecraftforge.event.server.ServerStartedEvent;
import net.minecraftforge.eventbus.api.EventPriority;
import net.minecraftforge.fml.DistExecutor;
@ -26,6 +27,10 @@ public class TestMod {
var bus = MinecraftForge.EVENT_BUS;
bus.addListener(EventPriority.LOW, (ServerStartedEvent e) -> TestHooks.onServerStarted(e.getServer()));
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);
var modBus = FMLJavaModLoadingContext.get().getModEventBus();