package speedytools.serverside.ingametester; import net.minecraft.block.Block; import net.minecraft.command.IEntitySelector; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.init.Blocks; import net.minecraft.server.MinecraftServer; import net.minecraft.tileentity.TileEntity; import net.minecraft.util.AxisAlignedBB; import net.minecraft.util.BlockPos; import net.minecraft.world.EnumSkyBlock; import net.minecraft.world.WorldServer; import net.minecraft.world.WorldType; import net.minecraft.world.chunk.Chunk; import net.minecraft.world.chunk.storage.ExtendedBlockStorage; import net.minecraft.world.gen.structure.StructureBoundingBox; import net.minecraft.world.storage.WorldInfo; import org.objenesis.Objenesis; import org.objenesis.ObjenesisStd; import speedytools.common.selections.VoxelSelection; import speedytools.serverside.worldmanipulation.WorldFragment; import speedytools.serverside.worldmanipulation.WorldHistory; import speedytools.serverside.worldmanipulation.WorldSelectionUndo; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; /** * User: The Grey Ghost * Date: 13/06/2014 WorldSelectionUndo, and WorldFragment * * * Test plan: * Test of WorldSelectionUndo: Tests all permutations of makePermanent() and undoChanges() for five blocks: // 1) place A, B, C, D, E (the voxels in A, B, C, D, E have a binary pattern 0..31 so that every combination of overlapping A, B, C, D, E voxels is present) // 2) remove each one, one step at a time, by either makePermanent() or undoChanges(), and check that the result at each step matches a // straight-forward placement. eg // after makePermanent(C) and undoChanges(B), the result matches the world after placing A, D, then E. // performs every permutation: // a) order of action removal = 5! = 120 // b) removal method = 2^5 = 32 // So a total of 120 * 32 = 40,000 or so. * Test of WorldHistory: see method comments */ public class WorldSelectionUndoTest { public void runWorldHistoryTests() { final int TEST_WORLD_X_SIZE = 64; final int TEST_WORLD_Z_SIZE = 32; WorldServerTest worldServer1 = WorldServerTest.createDummyInstance(TEST_WORLD_X_SIZE, TEST_WORLD_Z_SIZE); WorldServerTest worldServer2 = WorldServerTest.createDummyInstance(TEST_WORLD_X_SIZE, TEST_WORLD_Z_SIZE); EntityPlayerMPTest entityPlayerMPTest1 = EntityPlayerMPTest.createDummyInstance(); EntityPlayerMPTest entityPlayerMPTest2 = EntityPlayerMPTest.createDummyInstance(); final int ACTION_COUNT = 5; ArrayList<InGameTester.TestRegions> testRegions = new ArrayList<InGameTester.TestRegions>(); final int XORIGIN = 0; final int YORIGIN = 0; final int ZORIGIN = 0; final int XSIZE = 8; final int YSIZE = 1; final int ZSIZE = 4; for (int i = 0; i < ACTION_COUNT; ++i) { testRegions.add(new InGameTester.TestRegions(XORIGIN, YORIGIN, ZORIGIN + i * ZSIZE, XSIZE, YSIZE, ZSIZE, true)); } InGameTester.TestRegions allRegions = new InGameTester.TestRegions(XORIGIN, YORIGIN, ZORIGIN + ACTION_COUNT * ZSIZE, XSIZE, YSIZE, ZSIZE, true); WorldFragment worldFragmentBlank = new WorldFragment(allRegions.xSize, allRegions.ySize, allRegions.zSize); worldFragmentBlank.readFromWorld(worldServer1, allRegions.testRegionInitialiser.getX(), allRegions.testRegionInitialiser.getY(), allRegions.testRegionInitialiser.getZ(), null); ArrayList<WorldFragment> sourceFragments = new ArrayList<WorldFragment>(); for (int i = 0; i < ACTION_COUNT; ++i) { InGameTester.TestRegions testRegion = testRegions.get(i); WorldFragment worldFragment = new WorldFragment(testRegion.xSize, testRegion.ySize, testRegion.zSize); for (int j = 0; j < (2 << ACTION_COUNT); ++j) { // make binary pattern for all combinations of actions int wx = testRegion.sourceRegion.getX() + j / testRegion.zSize; int wy = testRegion.sourceRegion.getY(); int wz = testRegion.sourceRegion.getZ() + j % testRegion.zSize; Chunk chunk = worldServer1.getChunkFromChunkCoords(wx >> 4, wz >> 4); boolean successful = WorldFragment.setBlockIDWithMetadata(chunk, wx, wy, wz, (0 == (j & (1 << i))) ? 0 : Block.getIdFromBlock(Blocks.wool), i+1); } VoxelSelection voxelSelection = InGameTester.selectAllNonAir(worldServer1, testRegion.sourceRegion, testRegion.xSize, testRegion.ySize, testRegion.zSize); worldFragment.readFromWorld(worldServer1, testRegion.sourceRegion.getX(), testRegion.sourceRegion.getY(), testRegion.sourceRegion.getZ(), voxelSelection); sourceFragments.add(worldFragment); } // first test: // 1) Do four writeToWorldWithUndo for the player, check that performUndo does them in correct order with the correct undolist. // Check that the last performUndo does nothing (returns false) final int MAX_HISTORY_DEPTH_TEST_1 = 8; final int MAX_HISTORY_PER_PLAYER = 100; WorldHistory worldHistory = new WorldHistory(MAX_HISTORY_DEPTH_TEST_1, MAX_HISTORY_PER_PLAYER); ArrayList<WorldSelectionUndo> worldSelectionUndos = new ArrayList<WorldSelectionUndo>(); for (int i = 0; i < ACTION_COUNT; ++i) { worldHistory.writeToWorldWithUndo(entityPlayerMPTest1, worldServer1, sourceFragments.get(i), allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), allRegions.testOutputRegion.getZ()); worldSelectionUndos.add(new WorldSelectionUndo()); worldSelectionUndos.get(i).writeToWorld(worldServer2, sourceFragments.get(i), allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), allRegions.testOutputRegion.getZ()); // printTestRegionSlice("Assemble" + i, worldServer1, worldServer2, allRegions, 0); } for (int i = ACTION_COUNT - 1; i >= 0; --i) { boolean retval = worldHistory.performComplexUndo(entityPlayerMPTest1, worldServer1); assert (retval); List<WorldSelectionUndo> empty = new LinkedList<WorldSelectionUndo>(); worldSelectionUndos.get(i).undoChanges(worldServer2, empty); // printTestRegionSlice("Disassemble" + i, worldServer1, worldServer2, allRegions, 0); retval = compareTestWorldServers(worldServer1, worldServer2, allRegions, true); assert (retval); } boolean retval = worldHistory.performComplexUndo(entityPlayerMPTest1, worldServer1); assert (!retval); // second test: // 2) Intersperse two different WorldServers and two different players. Verify that the performUndo calls in correct order worldFragmentBlank.writeToWorld(worldServer1, allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), allRegions.testOutputRegion.getZ(), null); worldFragmentBlank.writeToWorld(worldServer2, allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), allRegions.testOutputRegion.getZ(), null); worldHistory.writeToWorldWithUndo(entityPlayerMPTest1, worldServer1, sourceFragments.get(0), allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), allRegions.testOutputRegion.getZ()); worldHistory.writeToWorldWithUndo(entityPlayerMPTest1, worldServer2, sourceFragments.get(1), allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), allRegions.testOutputRegion.getZ()); worldHistory.writeToWorldWithUndo(entityPlayerMPTest2, worldServer1, sourceFragments.get(2), allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), allRegions.testOutputRegion.getZ()); worldHistory.writeToWorldWithUndo(entityPlayerMPTest2, worldServer2, sourceFragments.get(3), allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), allRegions.testOutputRegion.getZ()); WorldServerTest worldServer1b = WorldServerTest.createDummyInstance(TEST_WORLD_X_SIZE, TEST_WORLD_Z_SIZE); WorldServerTest worldServer2b = WorldServerTest.createDummyInstance(TEST_WORLD_X_SIZE, TEST_WORLD_Z_SIZE); worldSelectionUndos.get(0).writeToWorld(worldServer1b, sourceFragments.get(0), allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), allRegions.testOutputRegion.getZ()); worldSelectionUndos.get(1).writeToWorld(worldServer2b, sourceFragments.get(1), allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), allRegions.testOutputRegion.getZ()); worldSelectionUndos.get(2).writeToWorld(worldServer1b, sourceFragments.get(2), allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), allRegions.testOutputRegion.getZ()); worldSelectionUndos.get(3).writeToWorld(worldServer2b, sourceFragments.get(3), allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), allRegions.testOutputRegion.getZ()); assert compareTestWorldServers(worldServer1, worldServer1b, allRegions, true) && compareTestWorldServers(worldServer2, worldServer2b, allRegions, true); List<WorldSelectionUndo> laterLayers = new LinkedList<WorldSelectionUndo>(); worldHistory.performComplexUndo(entityPlayerMPTest1, worldServer2); laterLayers.add(worldSelectionUndos.get(3)); worldSelectionUndos.get(1).undoChanges(worldServer2b, laterLayers); assert compareTestWorldServers(worldServer1, worldServer1b, allRegions, true) && compareTestWorldServers(worldServer2, worldServer2b, allRegions, true); worldHistory.performComplexUndo(entityPlayerMPTest2, worldServer1); laterLayers.clear(); // laterLayers.add(worldSelectionUndos.get(3)); worldSelectionUndos.get(2).undoChanges(worldServer1b, laterLayers); assert compareTestWorldServers(worldServer1, worldServer1b, allRegions, true) && compareTestWorldServers(worldServer2, worldServer2b, allRegions, true); worldHistory.performComplexUndo(entityPlayerMPTest1, worldServer1); laterLayers.clear(); // laterLayers.add(worldSelectionUndos.get(3)); worldSelectionUndos.get(0).undoChanges(worldServer1b, laterLayers); assert compareTestWorldServers(worldServer1, worldServer1b, allRegions, true) && compareTestWorldServers(worldServer2, worldServer2b, allRegions, true); worldHistory.performComplexUndo(entityPlayerMPTest2, worldServer2); laterLayers.clear(); // laterLayers.add(worldSelectionUndos.get(3)); worldSelectionUndos.get(3).undoChanges(worldServer2b, laterLayers); assert compareTestWorldServers(worldServer1, worldServer1b, allRegions, true) && compareTestWorldServers(worldServer2, worldServer2b, allRegions, true); // third test: 3) Delete one of the WorldServers and verify that the performUndo doesn't crash, correctly undoes the remaining change, and further undoes return false worldFragmentBlank.writeToWorld(worldServer1, allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), allRegions.testOutputRegion.getZ(), null); worldFragmentBlank.writeToWorld(worldServer1b, allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), allRegions.testOutputRegion.getZ(), null); WorldServerTest worldServer3 = WorldServerTest.createDummyInstance(TEST_WORLD_X_SIZE, TEST_WORLD_Z_SIZE); WorldServerTest worldServer4 = WorldServerTest.createDummyInstance(TEST_WORLD_X_SIZE, TEST_WORLD_Z_SIZE); worldHistory.writeToWorldWithUndo(entityPlayerMPTest1, worldServer3, sourceFragments.get(0), allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), allRegions.testOutputRegion.getZ()); worldHistory.writeToWorldWithUndo(entityPlayerMPTest1, worldServer1, sourceFragments.get(1), allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), allRegions.testOutputRegion.getZ()); worldHistory.writeToWorldWithUndo(entityPlayerMPTest1, worldServer4, sourceFragments.get(2), allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), allRegions.testOutputRegion.getZ()); worldSelectionUndos.get(0).writeToWorld(worldServer1b, sourceFragments.get(1), allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), allRegions.testOutputRegion.getZ()); assert compareTestWorldServers(worldServer1, worldServer1b, allRegions, true); worldServer3 = null; worldServer4 = null; assert worldHistory.performComplexUndo(entityPlayerMPTest1, worldServer1); List<WorldSelectionUndo> empty = new LinkedList<WorldSelectionUndo>(); worldSelectionUndos.get(0).undoChanges(worldServer1b, empty); assert compareTestWorldServers(worldServer1, worldServer1b, allRegions, true); assert !worldHistory.performComplexUndo(entityPlayerMPTest1, worldServer1); // 4th test: 4) Delete one of the EntityPlayerMP and verify that it doesn't affect the other player's undoes worldFragmentBlank.writeToWorld(worldServer1, allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), allRegions.testOutputRegion.getZ(), null); worldFragmentBlank.writeToWorld(worldServer1b, allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), allRegions.testOutputRegion.getZ(), null); worldHistory.writeToWorldWithUndo(entityPlayerMPTest2, worldServer1, sourceFragments.get(0), allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), allRegions.testOutputRegion.getZ()); worldHistory.writeToWorldWithUndo(entityPlayerMPTest1, worldServer1, sourceFragments.get(1), allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), allRegions.testOutputRegion.getZ()); worldHistory.writeToWorldWithUndo(entityPlayerMPTest1, worldServer1, sourceFragments.get(2), allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), allRegions.testOutputRegion.getZ()); worldHistory.writeToWorldWithUndo(entityPlayerMPTest2, worldServer1, sourceFragments.get(3), allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), allRegions.testOutputRegion.getZ()); worldHistory.writeToWorldWithUndo(entityPlayerMPTest1, worldServer1, sourceFragments.get(4), allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), allRegions.testOutputRegion.getZ()); worldSelectionUndos.get(0).writeToWorld(worldServer1b, sourceFragments.get(0), allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), allRegions.testOutputRegion.getZ()); worldSelectionUndos.get(1).writeToWorld(worldServer1b, sourceFragments.get(1), allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), allRegions.testOutputRegion.getZ()); worldSelectionUndos.get(2).writeToWorld(worldServer1b, sourceFragments.get(2), allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), allRegions.testOutputRegion.getZ()); worldSelectionUndos.get(3).writeToWorld(worldServer1b, sourceFragments.get(3), allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), allRegions.testOutputRegion.getZ()); worldSelectionUndos.get(4).writeToWorld(worldServer1b, sourceFragments.get(4), allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), allRegions.testOutputRegion.getZ()); assert compareTestWorldServers(worldServer1b, worldServer1, allRegions, true); // printTestRegionSlice("init", worldServer1b, worldServer1, allRegions, 0); entityPlayerMPTest2 = null; assert worldHistory.performComplexUndo(entityPlayerMPTest1, worldServer1); // printTestRegionSlice("WorldHistory.performUndo", worldServer1b, worldServer1, allRegions, 0); worldSelectionUndos.get(4).undoChanges(worldServer1b, empty); // printTestRegionSlice("worldSelectionUndos.get(4).undoChanges",worldServer1b, worldServer1, allRegions, 0); assert compareTestWorldServers(worldServer1b, worldServer1, allRegions, true); assert worldHistory.performComplexUndo(entityPlayerMPTest1, worldServer1); // printTestRegionSlice("WorldHistory.performUndo", worldServer1b, worldServer1, allRegions, 0); laterLayers.clear(); laterLayers.add(worldSelectionUndos.get(3)); worldSelectionUndos.get(2).undoChanges(worldServer1b, laterLayers); // printTestRegionSlice("worldSelectionUndos.get(2).undoChanges", worldServer1b, worldServer1, allRegions, 0); assert compareTestWorldServers(worldServer1b, worldServer1, allRegions, true); assert worldHistory.performComplexUndo(entityPlayerMPTest1, worldServer1); worldSelectionUndos.get(1).undoChanges(worldServer1b, laterLayers); assert compareTestWorldServers(worldServer1b, worldServer1, allRegions, true); assert !worldHistory.performComplexUndo(entityPlayerMPTest1, worldServer1); // 5th test: // 5) Initialise with a small history depth, add multiple from different players, and verify that they are deleted oldest first, leaving each player with at least one. // add a WorldServer and delete it and verify that it doesn't occupy space. worldFragmentBlank.writeToWorld(worldServer1, allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), allRegions.testOutputRegion.getZ(), null); worldFragmentBlank.writeToWorld(worldServer1b, allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), allRegions.testOutputRegion.getZ(), null); entityPlayerMPTest2 = EntityPlayerMPTest.createDummyInstance(); EntityPlayerMPTest entityPlayerMPTest3 = EntityPlayerMPTest.createDummyInstance(); worldHistory = new WorldHistory(3, MAX_HISTORY_PER_PLAYER); worldServer2 = WorldServerTest.createDummyInstance(TEST_WORLD_X_SIZE, TEST_WORLD_Z_SIZE); worldHistory.writeToWorldWithUndo(entityPlayerMPTest1, worldServer1, sourceFragments.get(0), allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), allRegions.testOutputRegion.getZ()); // printTestRegionSlice("writeToWorldWithUndo:1", worldServer1b, worldServer1, allRegions, 0); // worldHistory.printUndoStackYSlice(worldServer1, allRegions.testOutputRegion, allRegions.xSize, 0, allRegions.zSize); worldHistory.writeToWorldWithUndo(entityPlayerMPTest2, worldServer1, sourceFragments.get(1), allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), allRegions.testOutputRegion.getZ()); worldHistory.writeToWorldWithUndo(entityPlayerMPTest1, worldServer2, sourceFragments.get(1), allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), allRegions.testOutputRegion.getZ()); worldHistory.removeWorldServer(worldServer2); worldServer2 = null; // printTestRegionSlice("writeToWorldWithUndo:2", worldServer1b, worldServer1, allRegions, 0); // worldHistory.printUndoStackYSlice(worldServer1, allRegions.testOutputRegion, allRegions.xSize, 0, allRegions.zSize); // test that 0 is not pushed because server2 was removed worldHistory.writeToWorldWithUndo(entityPlayerMPTest2, worldServer1, sourceFragments.get(2), allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), allRegions.testOutputRegion.getZ()); // printTestRegionSlice("writeToWorldWithUndo:3", worldServer1b, worldServer1, allRegions, 0); // worldHistory.printUndoStackYSlice(worldServer1, allRegions.testOutputRegion, allRegions.xSize, 0, allRegions.zSize); // test that oldest player 2 is pushed not 1 worldHistory.writeToWorldWithUndo(entityPlayerMPTest3, worldServer1, sourceFragments.get(3), allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), allRegions.testOutputRegion.getZ()); worldHistory.removePlayer(entityPlayerMPTest3); entityPlayerMPTest3 = null; // printTestRegionSlice("writeToWorldWithUndo:4", worldServer1b, worldServer1, allRegions, 0); // worldHistory.printUndoStackYSlice(worldServer1, allRegions.testOutputRegion, allRegions.xSize, 0, allRegions.zSize); // test that 0 is not pushed because player3 was removed worldHistory.writeToWorldWithUndo(entityPlayerMPTest2, worldServer1, sourceFragments.get(4), allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), allRegions.testOutputRegion.getZ()); // in summary: should see 0U, 1P, 2U, 3P, 4U // printTestRegionSlice("writeToWorldWithUndo:5", worldServer1b, worldServer1, allRegions, 0); // worldHistory.printUndoStackYSlice(worldServer1, allRegions.testOutputRegion, allRegions.xSize, 0, allRegions.zSize); // printTestRegionSlice("worldHistory.writeToWorldWithUndo)", worldServer1b, worldServer1, allRegions, 0); worldSelectionUndos.get(0).writeToWorld(worldServer1b, sourceFragments.get(0), allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), allRegions.testOutputRegion.getZ()); // printTestRegionSlice("worldSelectionUndos.get(0)", worldServer1b, worldServer1, allRegions, 0); worldSelectionUndos.get(1).writeToWorld(worldServer1b, sourceFragments.get(1), allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), allRegions.testOutputRegion.getZ()); // printTestRegionSlice("worldSelectionUndos.get(1)", worldServer1b, worldServer1, allRegions, 0); worldSelectionUndos.get(2).writeToWorld(worldServer1b, sourceFragments.get(2), allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), allRegions.testOutputRegion.getZ()); // printTestRegionSlice("worldSelectionUndos.get(2)", worldServer1b, worldServer1, allRegions, 0); worldSelectionUndos.get(3).writeToWorld(worldServer1b, sourceFragments.get(3), allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), allRegions.testOutputRegion.getZ()); // printTestRegionSlice("worldSelectionUndos.get(3)", worldServer1b, worldServer1, allRegions, 0); worldSelectionUndos.get(4).writeToWorld(worldServer1b, sourceFragments.get(4), allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), allRegions.testOutputRegion.getZ()); // printTestRegionSlice("worldSelectionUndos.get(4)", worldServer1b, worldServer1, allRegions, 0); assert compareTestWorldServers(worldServer1b, worldServer1, allRegions, true); assert worldHistory.performComplexUndo(entityPlayerMPTest2, worldServer1); // printTestRegionSlice("performComplexUndo", worldServer1b, worldServer1, allRegions, 0); laterLayers.clear(); worldSelectionUndos.get(4).undoChanges(worldServer1b, laterLayers); assert compareTestWorldServers(worldServer1b, worldServer1, allRegions, true); assert worldHistory.performComplexUndo(entityPlayerMPTest2, worldServer1); laterLayers.add(worldSelectionUndos.get(3)); worldSelectionUndos.get(2).undoChanges(worldServer1b, laterLayers); assert compareTestWorldServers(worldServer1b, worldServer1, allRegions, true); assert worldHistory.performComplexUndo(entityPlayerMPTest1, worldServer1); laterLayers.add(worldSelectionUndos.get(1)); worldSelectionUndos.get(0).undoChanges(worldServer1b, laterLayers); assert compareTestWorldServers(worldServer1b, worldServer1, allRegions, true); assert !worldHistory.performComplexUndo(entityPlayerMPTest1, worldServer1); assert !worldHistory.performComplexUndo(entityPlayerMPTest2, worldServer1); } // returns true if the testOutputRegion in both worldServers is identical public boolean compareTestWorldServers(WorldServerTest worldServerExpected, WorldServerTest worldServerActual, InGameTester.TestRegions testRegions, boolean printOnCompareFail) { WorldFragment worldFragmentExpectedOutcome = new WorldFragment(testRegions.xSize, testRegions.ySize, testRegions.zSize); worldFragmentExpectedOutcome.readFromWorld(worldServerExpected, testRegions.testOutputRegion.getX(), testRegions.testOutputRegion.getY(), testRegions.testOutputRegion.getZ(), null); WorldFragment worldFragmentActualOutcome = new WorldFragment(testRegions.xSize, testRegions.ySize, testRegions.zSize); worldFragmentActualOutcome.readFromWorld(worldServerActual, testRegions.testOutputRegion.getX(), testRegions.testOutputRegion.getY(), testRegions.testOutputRegion.getZ(), null); boolean retval = WorldFragment.areFragmentsEqual(worldFragmentExpectedOutcome, worldFragmentActualOutcome); if (!retval && printOnCompareFail) { printFragments(worldFragmentExpectedOutcome, worldFragmentActualOutcome, WorldFragment.lastCompareFailY); } return retval; } // print a side-by-side comparison of a single y slice of the two world Fragments public void printFragments(WorldFragment worldFragmentExpected, WorldFragment worldFragmentActual, int y) { System.out.println("Expected : Actual for y-slice " + y); for (int x = 0; x < worldFragmentExpected.getxCount(); ++x) { for (int z = 0; z < worldFragmentExpected.getzCount(); ++z) { System.out.print(worldFragmentExpected.getMetadata(x, y, z) + " "); } System.out.print(": "); for (int z = 0; z < worldFragmentActual.getzCount(); ++z) { System.out.print(worldFragmentActual.getMetadata(x, y, z) + " "); } System.out.println(); } } // prints a slice from two world Server test regions public void printTestRegionSlice(String title, WorldServerTest worldServerExpected, WorldServerTest worldServerActual, InGameTester.TestRegions testRegions, int y) { WorldFragment worldFragmentExpectedOutcome = new WorldFragment(testRegions.xSize, testRegions.ySize, testRegions.zSize); worldFragmentExpectedOutcome.readFromWorld(worldServerExpected, testRegions.testOutputRegion.getX(), testRegions.testOutputRegion.getY(), testRegions.testOutputRegion.getZ(), null); WorldFragment worldFragmentActualOutcome = new WorldFragment(testRegions.xSize, testRegions.ySize, testRegions.zSize); worldFragmentActualOutcome.readFromWorld(worldServerActual, testRegions.testOutputRegion.getX(), testRegions.testOutputRegion.getY(), testRegions.testOutputRegion.getZ(), null); System.out.println(title); printFragments(worldFragmentExpectedOutcome, worldFragmentActualOutcome, y); } public void runWorldSelectionUndoTest() { final int TEST_WORLD_X_SIZE = 64; final int TEST_WORLD_Z_SIZE = 32; WorldServerTest worldServer = WorldServerTest.createDummyInstance(TEST_WORLD_X_SIZE, TEST_WORLD_Z_SIZE); final int ACTION_COUNT = 5; ArrayList<InGameTester.TestRegions> testRegions = new ArrayList<InGameTester.TestRegions>(); final int XORIGIN = 0; final int YORIGIN = 0; final int ZORIGIN = 0; final int XSIZE = 8; final int YSIZE = 1; final int ZSIZE = 4; for (int i = 0; i < ACTION_COUNT; ++i) { testRegions.add(new InGameTester.TestRegions(XORIGIN, YORIGIN, ZORIGIN + i * ZSIZE, XSIZE, YSIZE, ZSIZE, true)); } InGameTester.TestRegions allRegions = new InGameTester.TestRegions(XORIGIN, YORIGIN, ZORIGIN + ACTION_COUNT * ZSIZE, XSIZE, YSIZE, ZSIZE, true); int permutations = 1; for (int i = ACTION_COUNT; i > 1; --i) { permutations *= i; } int permutationOrder[][] = new int[permutations][ACTION_COUNT]; // the order to perform the actions in. second []: [][0] = 1st action, [][1] = 2nd action, etc for (int perm = 0; perm < permutations; ++perm) { int temp = perm; boolean taken[] = new boolean[ACTION_COUNT]; for (int i = 0; i < ACTION_COUNT; ++i) { int skip = temp % (ACTION_COUNT - i); temp /= (ACTION_COUNT - i); int idx = 0; while (skip > 0 || taken[idx]) { if (!taken[idx]) { --skip; } ++idx; } taken[idx] = true; permutationOrder[perm][i] = idx; } } WorldFragment worldFragmentBlank = new WorldFragment(allRegions.xSize, allRegions.ySize, allRegions.zSize); worldFragmentBlank.readFromWorld(worldServer, allRegions.testRegionInitialiser.getX(), allRegions.testRegionInitialiser.getY(), allRegions.testRegionInitialiser.getZ(), null); ArrayList<WorldFragment> sourceFragments = new ArrayList<WorldFragment>(); for (int i = 0; i < ACTION_COUNT; ++i) { InGameTester.TestRegions testRegion = testRegions.get(i); WorldFragment worldFragment = new WorldFragment(testRegion.xSize, testRegion.ySize, testRegion.zSize); for (int j = 0; j < (2 << ACTION_COUNT); ++j) { // make binary pattern for all combinations of actions int wx = testRegion.sourceRegion.getX() + j / testRegion.zSize; int wy = testRegion.sourceRegion.getY(); int wz = testRegion.sourceRegion.getZ() + j % testRegion.zSize; Chunk chunk = worldServer.getChunkFromChunkCoords(wx >> 4, wz >> 4); boolean successful = WorldFragment.setBlockIDWithMetadata(chunk, wx, wy, wz, (0 == (j & (1 << i))) ? 0 : Block.getIdFromBlock(Blocks.wool), i+1); } VoxelSelection voxelSelection = InGameTester.selectAllNonAir(worldServer, testRegion.sourceRegion, testRegion.xSize, testRegion.ySize, testRegion.zSize); worldFragment.readFromWorld(worldServer, testRegion.sourceRegion.getX(), testRegion.sourceRegion.getY(), testRegion.sourceRegion.getZ(), voxelSelection); sourceFragments.add(worldFragment); } ArrayList<WorldSelectionUndo> worldSelectionUndos = new ArrayList<WorldSelectionUndo>(); for (int i = 0; i < ACTION_COUNT; ++i) { worldSelectionUndos.add(new WorldSelectionUndo()); } for (int perm = 0; perm < permutations; ++perm) { System.out.print(perm + "; "); System.out.flush(); for (int placeOrUndo = 0; placeOrUndo < (1 << ACTION_COUNT); ++placeOrUndo) { // mask used for placing or undoing each action, one bit per action boolean debugPrint = false; final int DEBUG_PERM = -1; final int DEBUG_PLACEORUNDO = 6; final int DEBUG_XPOS = 1; final int DEBUG_ZPOS = 3; if (perm == DEBUG_PERM && placeOrUndo == DEBUG_PLACEORUNDO) { debugPrint = true; // breakpoint here } // start by placing all actions, then undoing or permanenting them one by one, checking the match after each step worldFragmentBlank.writeToWorld(worldServer, allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), allRegions.testOutputRegion.getZ(), null); boolean actionIncluded[] = new boolean[ACTION_COUNT]; ArrayList<WorldSelectionUndo> layersLeft = new ArrayList<WorldSelectionUndo>(); for (int i = 0; i < ACTION_COUNT; ++i) { worldSelectionUndos.get(i).writeToWorld(worldServer, sourceFragments.get(i), allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), allRegions.testOutputRegion.getZ()); layersLeft.add(worldSelectionUndos.get(i)); actionIncluded[i] = true; } if (debugPrint) { String states = "worldSelectionUndos[" + DEBUG_XPOS + ", 0, " + DEBUG_ZPOS + "]:"; for (int k = 0; k < ACTION_COUNT; ++k) { Integer savedValue = worldSelectionUndos.get(k).getStoredMetadata(DEBUG_XPOS + allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), DEBUG_ZPOS + allRegions.testOutputRegion.getZ()); states += (savedValue == null) ? "-" : savedValue; states += " "; } System.out.println(states); } // undo or permanent the changes one by one for (int j = 0; j < ACTION_COUNT; ++j) { int whichAction = permutationOrder[perm][j]; boolean makeThisActionPermanent = (0 == (placeOrUndo & (1 << j))); ArrayList<WorldSelectionUndo> subsequentLayers = new ArrayList<WorldSelectionUndo>(); LinkedList<WorldSelectionUndo> precedingLayers = new LinkedList<WorldSelectionUndo>(); WorldSelectionUndo thisLayer = worldSelectionUndos.get(whichAction); boolean preceding = true; for (WorldSelectionUndo eachLayer : layersLeft) { if (eachLayer == thisLayer) { preceding = false; } else { if (preceding) { precedingLayers.addFirst(eachLayer); } else { subsequentLayers.add(eachLayer); } } } if (makeThisActionPermanent) { thisLayer.makePermanent(worldServer, precedingLayers);//, subsequentLayers); } else { thisLayer.undoChanges(worldServer, subsequentLayers); actionIncluded[whichAction] = false; } layersLeft.remove(thisLayer); if (debugPrint) { String states = "after undo/place [" + DEBUG_XPOS + ", 0, " + DEBUG_ZPOS + "]:"; for (int k = 0; k < ACTION_COUNT; ++k) { Integer savedValue = worldSelectionUndos.get(k).getStoredMetadata(DEBUG_XPOS + allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), DEBUG_ZPOS + allRegions.testOutputRegion.getZ()); states += (savedValue == null) ? "-" : savedValue; states += " "; } System.out.println(states); } // create expected outcome at this step = placing all fragments which are still included worldFragmentBlank.writeToWorld(worldServer, allRegions.expectedOutcome.getX(), allRegions.expectedOutcome.getY(), allRegions.expectedOutcome.getZ(), null); for (int i = 0; i < ACTION_COUNT; ++i) { if (actionIncluded[i]) { sourceFragments.get(i).writeToWorld(worldServer, allRegions.expectedOutcome.getX(), allRegions.expectedOutcome.getY(), allRegions.expectedOutcome.getZ(), null); } } WorldFragment worldFragmentExpectedOutcome = new WorldFragment(allRegions.xSize, allRegions.ySize, allRegions.zSize); worldFragmentExpectedOutcome.readFromWorld(worldServer, allRegions.expectedOutcome.getX(), allRegions.expectedOutcome.getY(), allRegions.expectedOutcome.getZ(), null); WorldFragment worldFragmentActualOutcome = new WorldFragment(allRegions.xSize, allRegions.ySize, allRegions.zSize); worldFragmentActualOutcome.readFromWorld(worldServer, allRegions.testOutputRegion.getX(), allRegions.testOutputRegion.getY(), allRegions.testOutputRegion.getZ(), null); boolean retval = WorldFragment.areFragmentsEqual(worldFragmentActualOutcome, worldFragmentExpectedOutcome); if (!retval) { String errorString = "comparison failed for perm=" + perm + " ["; for (int k = 0; k < ACTION_COUNT; ++k) { errorString += permutationOrder[perm][k] + " "; } errorString += "]; placeOrUndo=" + placeOrUndo + "; steps performed=" + j; System.out.println(errorString); System.out.print("Operations: "); for (int k = 0; k <= j; ++k) { System.out.print(permutationOrder[perm][k] + 1 + "-" + ((0 == (placeOrUndo & (1 << k))) ? "P " : "U ")); } System.out.println(); System.out.print("Actions Included:"); for (int k = 0; k < ACTION_COUNT; ++k) { if (actionIncluded[k]) System.out.print(k+1 + ", "); } System.out.println(); System.out.println("Expected : Actual"); for (int x = 0; x < worldFragmentActualOutcome.getxCount(); ++x) { for (int z = 0; z < worldFragmentExpectedOutcome.getzCount(); ++z) { System.out.print(worldFragmentExpectedOutcome.getMetadata(x, 0, z) + " "); } System.out.print(": "); for (int z = 0; z < worldFragmentActualOutcome.getzCount(); ++z) { System.out.print(worldFragmentActualOutcome.getMetadata(x, 0, z) + " "); } System.out.println(); } assert false: errorString; } } } } System.out.println(); } public static class WorldServerTest extends WorldServer { public static WorldServerTest createDummyInstance(int xBlockCount, int zBlockCount) { Objenesis objenesis = new ObjenesisStd(); WorldServerTest worldServerTest = (WorldServerTest) objenesis.newInstance(WorldServerTest.class); worldServerTest.initialise(((xBlockCount-1) >> 4) + 1, ((zBlockCount-1) >> 4) + 1); return worldServerTest; } public WorldServerTest() { super(null, null, null, 0, null); } public void initialise(int i_xChunkCount, int i_zChunkCount) { xChunkCount = i_xChunkCount; zChunkCount = i_zChunkCount; chunks = new ChunkTest[xChunkCount][zChunkCount]; for (int cx = 0; cx < xChunkCount; ++cx) { for (int cz = 0; cz < zChunkCount; ++cz) { chunks[cx][cz] = new ChunkTest(this); } } } @Override public TileEntity getTileEntity(BlockPos blockPos) {return null;} @Override public List getEntitiesWithinAABB(Class par1Class, AxisAlignedBB par2AxisAlignedBB) {return new ArrayList();} @Override public Chunk getChunkFromChunkCoords(int cx, int cz) { assert(cx >= 0 && cx < xChunkCount); assert(cz >= 0 && cz < zChunkCount); return chunks[cx][cz]; } @Override public WorldType getWorldType() {return WorldType.DEFAULT;} @Override public WorldInfo getWorldInfo() { return MinecraftServer.getServer().getEntityWorld().getWorldInfo(); } @Override public List func_175712_a(StructureBoundingBox p_175712_1_, boolean p_175712_2_) {return null;} // get ticking blocks @Override public void func_180497_b(BlockPos pos, Block p_180497_2_, int p_180497_3_, int p_180497_4_) {} // schedule block tick @Override public void notifyNeighborsRespectDebug(BlockPos pos, Block blockType) { } ChunkTest chunks[][]; int xChunkCount; int zChunkCount; } public static class ChunkTest extends Chunk { public ChunkTest(WorldServerTest worldServerTest) { super(worldServerTest, 0, 0); } @Override public void generateSkylightMap() {return;} @Override public void generateHeightMap() {return;} @Override public int getLightFor(EnumSkyBlock enumSkyBlock, BlockPos pos) { int i = pos.getX() & 15; int j = pos.getY(); int k = pos.getZ() & 15; ExtendedBlockStorage extendedblockstorage = this.getBlockStorageArray()[j >> 4]; if (extendedblockstorage == null) { return (this.canSeeSky(pos) ? enumSkyBlock.defaultLightValue : 0); } else { if (enumSkyBlock == EnumSkyBlock.SKY) { return (extendedblockstorage.getExtSkylightValue(i, j & 15, k)); } else { return (enumSkyBlock == EnumSkyBlock.BLOCK ? extendedblockstorage.getExtBlocklightValue(i, j & 15, k) : enumSkyBlock.defaultLightValue); } } } } public static class EntityPlayerMPTest extends EntityPlayerMP { public static EntityPlayerMPTest createDummyInstance() { Objenesis objenesis = new ObjenesisStd(); EntityPlayerMPTest entityPlayerMPTest = (EntityPlayerMPTest) objenesis.newInstance(EntityPlayerMPTest.class); entityPlayerMPTest.setEntityId(++nextID); // needed for hashcode unique // entityPlayerMPTest.initialise(((xBlockCount-1) >> 4) + 1, ((zBlockCount-1) >> 4) + 1); return entityPlayerMPTest; } public EntityPlayerMPTest() { super(null, null, null, null); } private static int nextID = 0; } }