/*
* Copyright 2012 Benjamin Glatzel <benjamin.glatzel@me.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.terasology.model.structures;
import com.google.common.collect.Lists;
import org.terasology.logic.world.BlockUpdate;
import org.terasology.logic.world.WorldProvider;
import org.terasology.teraspout.TeraBlock;
import java.util.HashMap;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* A collection of actual blocks and their relative positions, usually _not_ absolute positions in an existing world
* Can also store a specific position that the collection should be oriented around rather than always a specific corner
* Useful for for blueprints and other things that apply a particular set of expectations on a spot in a world
*
* @author Rasmus 'Cervator' Praestholm <cervator@gmail.com>
*/
public class BlockCollection {
private static Logger logger = Logger.getLogger(BlockCollection.class.getName());
// TODO: Can this integrate better with BlockSelection, rather than need its keyset constructed into a BlockSelection for utility?
/**
* Map of what blocks are in which positions
*/
private final HashMap<BlockPosition, TeraBlock> _blocks = new HashMap<BlockPosition, TeraBlock>();
/**
* A specific position to use for attaching to a spot in the world - for a tree this could be the bottom trunk block.
*/
private BlockPosition _attachPos = new BlockPosition(0, 0, 0);
/**
* Simple return of the BlockPositions this collection holds in the form of a BlockSelection
*
* @return A BlockSelection containing the positions within this collection
*/
public BlockSelection positions() {
return new BlockSelection(_blocks.keySet());
}
/**
* Get the Block that matches the given position
*
* @param pos The position we care about
* @return The block at the position
*/
public TeraBlock getBlock(BlockPosition pos) {
return _blocks.get(pos);
}
/**
* Return the main collection
*
* @return The Position-Block map
*/
public HashMap<BlockPosition, TeraBlock> getBlocks() {
return _blocks;
}
/**
* Simply forwards to the main build method including itself as the BlockCollection to draw blocks from
*
* @param provider The world to build the collection in
* @param position The position to build the collection at (using the collection's attachment position)
* @return A BlockSelection containing the final positions the blocks were built at
*/
public BlockSelection build(WorldProvider provider, BlockPosition position) {
return build(provider, position, this);
}
/**
* Builds and returns only the blocks in the collection that matches the blockName
*
* @param provider The world to build the collection in
* @param position The position to build the collection at (using the collection's attachment position)
* @param blockName The name of the blocks we want to filter by
* @return The BlockSelection for the built blocks matching the filter
*/
public BlockSelection buildWithFilter(WorldProvider provider, BlockPosition position, String blockName) {
BlockCollection filteredBlocks = filter(blockName);
return build(provider, position, filteredBlocks);
}
/**
* Create the contents of the collection at a specific position in the world, returning the absolute BlockPositions.
* This can thus be used to for instance build a blueprint, then optionally sort out what was built where so different
* block types can be made reactive to subsequent input (say a PortalComponent storing what's frame and what's portal)
*
* @param provider The world to build the collection in
* @param position The position to build the collection at (using the collection's attachment position)
* @param buildingBlocks The BlockCollection we are using to build with, which could be filtered or the main one here
* @return A BlockSelection containing the final positions the blocks were built at
*/
public BlockSelection build(WorldProvider provider, BlockPosition position, BlockCollection buildingBlocks) {
BlockSelection result = new BlockSelection();
logger.log(Level.INFO, "Going to build this collection into the world at " + position + ", attaching at relative " + _attachPos);
//System.out.println(toString());
List<BlockUpdate> updates = Lists.newArrayListWithCapacity(buildingBlocks.getBlocks().size());
for (BlockPosition pos : buildingBlocks.getBlocks().keySet()) {
//System.out.println("Processing block " + getBlock(pos) + " relative position " + pos);
int x = position.x + pos.x - _attachPos.x;
int y = position.y + pos.y - _attachPos.y;
int z = position.z + pos.z - _attachPos.z;
//System.out.println("This block is being placed at " + x + "," + y + "," + z);
// TODO: Fix this up for concurrency
provider.setBlock(x, y, z, buildingBlocks.getBlocks().get(pos), provider.getBlock(x, y, z));
result.add(new BlockPosition(x, y, z));
}
return result;
}
/**
* Returns a filtered BlockCollection only including Blocks matching the supplied name
*
* @param blockName The name of the Block we're interested in
* @return A BlockCollection only containing the interesting blocks
*/
public BlockCollection filter(String blockName) {
BlockCollection filtered = new BlockCollection();
for (BlockPosition pos : _blocks.keySet()) {
TeraBlock b = _blocks.get(pos);
if (b.getTitle().equals(blockName)) {
//System.out.println("Block " + b + " matches the filter for " + blockName + " so adding it");
filtered.addBlock(pos, b);
}
}
return filtered;
}
/**
* TODO: Can this be used by build to not duplicate code? Maybe a static version that takes a BlockCollection param?
* Calculates a localized version of the BlockSelection in this Collection's map as per a given position
*
* @param localPos The local position we're going to localize against
* @return The localized BlockSelection
*/
public BlockSelection getLocalizedSelection(BlockPosition localPos) {
BlockSelection result = new BlockSelection();
logger.log(Level.INFO, "Going to localize this BlockCollection to position" + localPos);
for (BlockPosition pos : _blocks.keySet()) {
//System.out.println("Processing block " + getBlock(pos) + " relative position " + pos);
int x = localPos.x + pos.x - _attachPos.x;
int y = localPos.y + pos.y - _attachPos.y;
int z = localPos.z + pos.z - _attachPos.z;
//System.out.println("Localized to " + x + "," + y + "," + z);
result.add(new BlockPosition(x, y, z));
}
return result;
}
public void addBlock(BlockPosition pos, TeraBlock b) {
_blocks.put(pos, b);
}
public BlockPosition getAttachPos() {
return _attachPos;
}
public void setAttachPos(BlockPosition pos) {
_attachPos = pos;
}
/**
* Returns the width (x) of the backing BlockSelection's widest point (horizontally measured, not diagonally)
*
* @return int holding calculated width or -1 if there are no elements
*/
public int calcWidth() {
return positions().calcWidth();
}
/**
* Returns the height (y) of the backing BlockCollection's highest point (vertically measured, not diagonally)
*
* @return int holding calculated height or -1 if there are no elements
*/
public int calcHeight() {
return positions().calcHeight();
}
/**
* Returns the depth (z) of the backing BlockCollection's deepest point (horizontally measured, not diagonally)
*
* @return int holding calculated depth or -1 if there are no elements
*/
public int calcDepth() {
return positions().calcDepth();
}
@Override
public String toString() {
String result = "[[";
for (BlockPosition pos : _blocks.keySet()) {
result += pos + "-" + getBlock(pos) + "],[";
}
result = result.substring(0, result.length() - 2) + "],attachPos:" + _attachPos;
return result;
}
}