public SimulateBatch( BlockSpell spell, Location center, int radius, int yRadius, MaterialAndData birth, Material death, Set<Integer> liveCounts, Set<Integer> birthCounts) { super(spell); this.blockSpell = spell; this.mage = spell.getMage(); this.yRadius = yRadius; this.radius = radius; this.center = center.clone(); this.birthMaterial = birth; this.deathMaterial = death; this.powerSimMaterial = birthMaterial; this.powerSimMaterialBackup = new MaterialAndData(deathMaterial); mapIntegers(liveCounts, this.liveCounts); mapIntegers(birthCounts, this.birthCounts); this.world = center.getWorld(); includeCommands = false; x = 0; y = 0; z = 0; r = 0; state = SimulationState.SCANNING_COMMAND; updatingIndex = 0; }
@Override public int process(int maxBlocks) { int processedBlocks = 0; if (state == SimulationState.SCANNING_COMMAND) { // Process the casting command block first, and only if specially configured to do so. if (includeCommands && castCommandBlock != null) { // We are going to rely on the block toggling to kick this back to life when the chunk // reloads, so for now just bail and hope the timing works out. if (!castCommandBlock.getChunk().isLoaded()) { finish(); // TODO: Maybe Scatter-shot and register all 6 surrounding power blocks for reload toggle. // Can't really do it without the chunk being loaded though, so hrm. return processedBlocks; } // Check for death since activation (e.g. during delay period) if (castCommandBlock.getType() != Material.COMMAND) { die(); finish(); return processedBlocks; } // Check for power blocks for (BlockFace powerFace : POWER_FACES) { Block checkForPower = castCommandBlock.getRelative(powerFace); if (checkForPower.getType() == POWER_MATERIAL) { if (commandReload) { controller.unregisterAutomata(checkForPower); } powerSimMaterial.modify(checkForPower); commandPowered = true; } } if (!commandPowered) { die(); finish(); return processedBlocks; } // Make this a normal block so the sim will process it // this also serves to reset the command block for the next tick, if it lives. birthMaterial.modify(castCommandBlock); } processedBlocks++; state = SimulationState.SCANNING; } while (state == SimulationState.SCANNING && processedBlocks <= maxBlocks) { if (!simulateBlocks(x, y, z)) { return processedBlocks; } y++; if (y > yRadius) { y = 0; if (x < radius) { x++; } else { z--; if (z < 0) { r++; z = r; x = 0; } } } if (r > radius) { state = SimulationState.UPDATING; } } while (state == SimulationState.UPDATING && processedBlocks <= maxBlocks) { int deadIndex = updatingIndex; if (deadIndex >= 0 && deadIndex < deadBlocks.size()) { Block killBlock = deadBlocks.get(deadIndex); if (!killBlock.getChunk().isLoaded()) { killBlock.getChunk().load(); return processedBlocks; } if (birthMaterial.is(killBlock)) { registerForUndo(killBlock); killBlock.setType(deathMaterial); } else { // If this block was destroyed while we were processing, // avoid spawning a random birth block. // This tries to make it so automata don't "cheat" when // getting destroyed. A bit hacky though, I'm not about // to re-simulate... if (bornBlocks.size() > 0) { bornBlocks.remove(bornBlocks.size() - 1); } } processedBlocks++; } int bornIndex = updatingIndex - deadBlocks.size(); if (bornIndex >= 0 && bornIndex < bornBlocks.size()) { Block birthBlock = bornBlocks.get(bornIndex); if (!birthBlock.getChunk().isLoaded()) { birthBlock.getChunk().load(); return processedBlocks; } registerForUndo(birthBlock); birthMaterial.modify(birthBlock); } updatingIndex++; if (updatingIndex >= deadBlocks.size() + bornBlocks.size()) { state = SimulationState.COMMAND_SEARCH; // Wait at least a tick before re-populating the command block. return maxBlocks; } } // Each of the following states will end in this tick, to give the // MC sim time to register power updates. if (state == SimulationState.COMMAND_SEARCH) { if (includeCommands && potentialCommandBlocks.size() > 0) { switch (targetMode) { case HUNT: Collections.sort(potentialCommandBlocks); break; case FLEE: Collections.sort(potentialCommandBlocks); break; default: Collections.shuffle(potentialCommandBlocks); break; } // Find a valid block for the command powerTargetBlock = null; commandTargetBlock = null; Block backupBlock = null; while (commandTargetBlock == null && potentialCommandBlocks.size() > 0) { Block block = potentialCommandBlocks.remove(0).getBlock(); if (block != null && birthMaterial.is(block)) { // If we're powering the block, look for one with a powerable neighbor. if (!commandPowered) { commandTargetBlock = block; } else { backupBlock = block; BlockFace powerFace = findPowerLocation(block, powerSimMaterial); if (powerFace != null) { commandTargetBlock = block; } } } } // If we didn't find any powerable blocks, but we did find at least one valid sim block // just use that one. if (commandTargetBlock == null) commandTargetBlock = backupBlock; // Search for a power block if (commandTargetBlock != null) { // First try and replace a live cell BlockFace powerDirection = findPowerLocation(commandTargetBlock, powerSimMaterial); // Next try to replace a dead cell, which will affect the simulation outcome // but this is perhaps better than it dying? if (powerDirection == null) { if (DEBUG) { controller .getLogger() .info("Had to fall back to backup location, pattern may diverge"); } powerDirection = findPowerLocation(commandTargetBlock, powerSimMaterialBackup); } // If it's *still* not valid, search for something breakable. if (powerDirection == null) { for (BlockFace face : POWER_FACES) { if (blockSpell.isDestructible(commandTargetBlock.getRelative(face))) { if (DEBUG) { controller .getLogger() .info( "Had to fall back to destructible location, pattern may diverge and may destroy blocks"); } powerDirection = face; break; } } } if (powerDirection != null) { powerTargetBlock = commandTargetBlock.getRelative(powerDirection); } } } if (DEBUG) { if (commandTargetBlock != null) { controller .getLogger() .info( "MOVED: " + commandTargetBlock.getLocation().toVector().subtract(center.toVector())); } } state = SimulationState.COMMON_RESET_REDSTONE; return processedBlocks; } if (state == SimulationState.COMMON_RESET_REDSTONE) { if (includeCommands && commandTargetBlock != null) { DeprecatedUtils.setData(commandTargetBlock, (byte) 0); } if (includeCommands && powerTargetBlock != null) { DeprecatedUtils.setData(powerTargetBlock, (byte) 0); } state = SimulationState.COMMAND_UPDATE; return processedBlocks; } if (state == SimulationState.COMMAND_UPDATE) { if (includeCommands) { if (commandTargetBlock != null) { if (!commandTargetBlock.getChunk().isLoaded()) { commandTargetBlock.getChunk().load(); return processedBlocks; } commandTargetBlock.setType(Material.COMMAND); BlockState commandData = commandTargetBlock.getState(); if (castCommand != null && commandData != null && commandData instanceof CommandBlock) { CommandBlock copyCommand = (CommandBlock) commandData; copyCommand.setCommand(castCommand); copyCommand.setName(commandName); copyCommand.update(); // Also move the mage Location newLocation = commandTargetBlock.getLocation(); newLocation.setPitch(center.getPitch()); newLocation.setYaw(center.getYaw()); mage.setLocation(newLocation); } else { commandTargetBlock = null; } } else { die(); } } powerDelayTicks = POWER_DELAY_TICKS; state = SimulationState.COMMAND_POWER; return processedBlocks; } if (state == SimulationState.COMMAND_POWER) { // Continue to power the command block if (commandPowered && powerTargetBlock != null && includeCommands) { // Wait a bit before powering for redstone signals to reset if (powerDelayTicks > 0) { powerDelayTicks--; return processedBlocks; } if (powerTargetBlock != null) { powerTargetBlock.setType(POWER_MATERIAL); if (commandReload) { String automataName = commandName; if (automataName == null || automataName.length() <= 1) { automataName = controller.getMessages().get("automata.default_name"); } controller.registerAutomata(powerTargetBlock, automataName, "automata.awaken"); } } } state = SimulationState.FINISHED; return processedBlocks; } if (state == SimulationState.FINISHED) { finish(); } return processedBlocks; }