279 lines
10 KiB
Python
279 lines
10 KiB
Python
"""
|
|
DeCliff filter contributed by Minecraft Forums user "DrRomz"
|
|
|
|
Originally posted here:
|
|
http://www.minecraftforum.net/topic/13807-mcedit-minecraft-world-editor-compatible-with-mc-beta-18/page__st__3940__p__7648793#entry7648793
|
|
"""
|
|
from numpy import zeros, array
|
|
import itertools
|
|
from pymclevel import alphaMaterials
|
|
am = alphaMaterials
|
|
|
|
# Consider below materials when determining terrain height
|
|
blocks = [
|
|
am.Stone,
|
|
am.Grass,
|
|
am.Dirt,
|
|
am.Bedrock,
|
|
am.Sand,
|
|
am.Sandstone,
|
|
am.Clay,
|
|
am.Gravel,
|
|
am.GoldOre,
|
|
am.IronOre,
|
|
am.CoalOre,
|
|
am.LapisLazuliOre,
|
|
am.DiamondOre,
|
|
am.RedstoneOre,
|
|
am.RedstoneOreGlowing,
|
|
am.Netherrack,
|
|
am.SoulSand,
|
|
am.Glowstone
|
|
]
|
|
terrainBlocktypes = [b.ID for b in blocks]
|
|
terrainBlockmask = zeros((256,), dtype='bool')
|
|
|
|
# Truth table used to calculate terrain height
|
|
# trees, leaves, etc. sit on top of terrain
|
|
terrainBlockmask[terrainBlocktypes] = True
|
|
|
|
inputs = (
|
|
# Option to limit change to raise_cliff_floor / lower_cliff_top
|
|
# Default is to adjust both and meet somewhere in the middle
|
|
("Raise/Lower", ("Both", "Lower Only", "Raise Only")),
|
|
)
|
|
|
|
|
|
#
|
|
# Calculate the maximum adjustment that can be made from
|
|
# cliff_pos in direction dir (-1/1) keeping terain at most
|
|
# maxstep blocks away from previous column
|
|
def maxadj(heightmap, slice_no, cliff_pos, dir, pushup, maxstep, slice_width):
|
|
ret = 0
|
|
if dir < 0:
|
|
if cliff_pos < 2:
|
|
return 0
|
|
end = 0
|
|
else:
|
|
if cliff_pos > slice_width - 2:
|
|
return 0
|
|
end = slice_width - 1
|
|
|
|
for cur_pos in range(cliff_pos, end, dir):
|
|
if pushup:
|
|
ret = ret + \
|
|
max([0, maxstep - dir * heightmap[slice_no, cur_pos] + \
|
|
dir * heightmap[slice_no, cur_pos + dir]])
|
|
else:
|
|
ret = ret + \
|
|
min([0, -maxstep + dir * heightmap[slice_no, cur_pos] - \
|
|
dir * heightmap[slice_no, cur_pos + dir]])
|
|
|
|
return ret
|
|
|
|
|
|
#
|
|
# Raise/lower column at cliff face by adj and decrement change as we move away
|
|
# from the face. Each level will be at most maxstep blocks from those beside it.
|
|
#
|
|
# This function dosn't actually change anything, but just sets array 'new'
|
|
# with the desired height.
|
|
def adjheight(orig, new, slice_no, cliff_pos, dir, adj, can_adj, maxstep, slice_width):
|
|
cur_adj = adj
|
|
prev = 0
|
|
done_adj = 0
|
|
|
|
if dir < 0:
|
|
end = 1
|
|
else:
|
|
end = slice_width - 1
|
|
|
|
if adj == 0 or can_adj == 0:
|
|
for cur_pos in range(cliff_pos, end, dir):
|
|
new[slice_no, cur_pos] = orig[slice_no, cur_pos]
|
|
else:
|
|
|
|
for cur_pos in range(cliff_pos, end, dir):
|
|
if adj > 0:
|
|
done_adj = done_adj + \
|
|
max([0, maxstep - orig[slice_no, cur_pos] + \
|
|
orig[slice_no, cur_pos + dir]])
|
|
|
|
if orig[slice_no, cur_pos] - \
|
|
orig[slice_no, cur_pos + dir] > 0:
|
|
cur_adj = max([0, cur_adj - orig[slice_no, cur_pos] + \
|
|
orig[slice_no, cur_pos + dir]])
|
|
prev = adj - cur_adj
|
|
else:
|
|
done_adj = done_adj + \
|
|
min([0, -maxstep + \
|
|
orig[slice_no, cur_pos] - \
|
|
orig[slice_no, cur_pos + dir]])
|
|
if orig[slice_no, cur_pos] - \
|
|
orig[slice_no, cur_pos + dir] > 0:
|
|
cur_adj = min([0, cur_adj + orig[slice_no, cur_pos] - orig[slice_no, cur_pos + dir]])
|
|
prev = adj - cur_adj
|
|
new[slice_no, cur_pos] = max([0, orig[slice_no, cur_pos] + cur_adj])
|
|
if cur_adj != 0 and \
|
|
abs(prev) < abs(int(adj * done_adj / can_adj)):
|
|
cur_adj = cur_adj + (prev - int(adj * done_adj / can_adj))
|
|
prev = int(adj * done_adj / can_adj)
|
|
|
|
new[slice_no, end] = orig[slice_no, end]
|
|
|
|
|
|
def perform(level, box, options):
|
|
if box.volume > 16000000:
|
|
raise ValueError("Volume too big for this filter method!")
|
|
|
|
RLOption = options["Raise/Lower"]
|
|
schema = level.extractSchematic(box)
|
|
schema.removeEntitiesInBox(schema.bounds)
|
|
schema.removeTileEntitiesInBox(schema.bounds)
|
|
|
|
terrainBlocks = terrainBlockmask[schema.Blocks]
|
|
|
|
coords = terrainBlocks.nonzero()
|
|
|
|
# Swap values around so long edge of selected rectangle is first
|
|
# - the long edge is assumed to run parallel to the cliff face
|
|
# and we want to process slices perpendicular to the face
|
|
# heightmap will have x,z (or z,x) index with highest ground level
|
|
if schema.Width > schema.Length:
|
|
heightmap = zeros((schema.Width, schema.Length), dtype='float32')
|
|
heightmap[coords[0], coords[1]] = coords[2]
|
|
newHeightmap = zeros((schema.Width, schema.Length), dtype='uint16')
|
|
slice_count = schema.Width
|
|
slice_width = schema.Length
|
|
else:
|
|
heightmap = zeros((schema.Length, schema.Width), dtype='float32')
|
|
heightmap[coords[1], coords[0]] = coords[2]
|
|
newHeightmap = zeros((schema.Length, schema.Width), dtype='uint16')
|
|
slice_count = schema.Length
|
|
slice_width = schema.Width
|
|
|
|
nonTerrainBlocks = ~terrainBlocks
|
|
nonTerrainBlocks &= schema.Blocks != 0
|
|
|
|
for slice_no in range(0, slice_count):
|
|
|
|
cliff_height = 0
|
|
# determine pos and height of cliff in this slice
|
|
for cur_pos in range(0, slice_width - 1):
|
|
if abs(heightmap[slice_no, cur_pos] - \
|
|
heightmap[slice_no, cur_pos + 1]) > abs(cliff_height):
|
|
cliff_height = \
|
|
heightmap[slice_no, cur_pos] - \
|
|
heightmap[slice_no, cur_pos + 1]
|
|
cliff_pos = cur_pos
|
|
|
|
if abs(cliff_height) < 2:
|
|
# nothing to adjust - just copy heightmap to newHightmap
|
|
adjheight(heightmap, newHeightmap, slice_no, 0, 1, 0, 1, 1, slice_width)
|
|
continue
|
|
|
|
# Try to keep adjusted columns within 1 column of their neighbours
|
|
# but ramp up to 4 blocks up/down on each column when needed
|
|
for max_step in range(1, 4):
|
|
|
|
can_left = maxadj(heightmap, slice_no, cliff_pos, -1, cliff_height < 0, max_step, slice_width)
|
|
can_right = maxadj(heightmap, slice_no, cliff_pos + 1, 1, cliff_height > 0, max_step, slice_width)
|
|
|
|
if can_right < 0 and RLOption == "Raise Only":
|
|
can_right = 0
|
|
if can_right > 0 and RLOption == "Lower Only":
|
|
can_right = 0
|
|
if can_left < 0 and RLOption == "Raise Only":
|
|
can_left = 0
|
|
if can_left > 0 and RLOption == "Lower Only":
|
|
can_left = 0
|
|
|
|
if cliff_height < 0 and can_right - can_left < cliff_height:
|
|
if abs(can_left) > abs(can_right):
|
|
adj_left = -1 * (cliff_height - max([int(cliff_height / 2), can_right]))
|
|
adj_right = cliff_height + adj_left
|
|
else:
|
|
adj_right = cliff_height - max([int(cliff_height / 2), -can_left])
|
|
adj_left = -1 * (cliff_height - adj_right + 1)
|
|
else:
|
|
if cliff_height > 0 and can_right - can_left > cliff_height:
|
|
if abs(can_left) > abs(can_right):
|
|
adj_left = -1 * (cliff_height - min([int(cliff_height / 2), can_right]))
|
|
adj_right = cliff_height + adj_left
|
|
else:
|
|
adj_right = cliff_height - min([int(cliff_height / 2), -can_left]) - 1
|
|
adj_left = -1 * (cliff_height - adj_right)
|
|
else:
|
|
adj_right = 0
|
|
adj_left = 0
|
|
continue
|
|
break
|
|
|
|
adjheight(heightmap, newHeightmap, slice_no, cliff_pos, -1, adj_left, can_left, max_step, slice_width)
|
|
adjheight(heightmap, newHeightmap, slice_no, cliff_pos + 1, 1, adj_right, can_right, max_step, slice_width)
|
|
|
|
# OK, newHeightMap has new height for each column
|
|
# so it's just a matter of moving everything up/down
|
|
for x, z in itertools.product(xrange(1, schema.Width - 1), xrange(1, schema.Length - 1)):
|
|
|
|
if schema.Width > schema.Length:
|
|
oh = heightmap[x, z]
|
|
nh = newHeightmap[x, z]
|
|
else:
|
|
oh = heightmap[z, x]
|
|
nh = newHeightmap[z, x]
|
|
|
|
delta = nh - oh
|
|
|
|
column = array(schema.Blocks[x, z])
|
|
# Keep bottom 5 blocks, so we don't loose bedrock
|
|
keep = min([5, nh])
|
|
|
|
Waterdepth = 0
|
|
# Detect Water on top
|
|
if column[oh + 1:oh + 2] == am.Water.ID or \
|
|
column[oh + 1:oh + 2] == am.Ice.ID:
|
|
for cur_pos in range(oh + 1, schema.Height):
|
|
if column[cur_pos:cur_pos + 1] != am.Water.ID and \
|
|
column[cur_pos:cur_pos + 1] != am.Ice.ID: break
|
|
Waterdepth = Waterdepth + 1
|
|
|
|
if delta == 0:
|
|
column[oh:] = schema.Blocks[x, z, oh:]
|
|
|
|
if delta < 0:
|
|
# Moving column down
|
|
column[keep:delta] = schema.Blocks[x, z, keep - delta:]
|
|
column[delta:] = am.Air.ID
|
|
if Waterdepth > 0:
|
|
# Avoid steping small lakes, etc on cliff top
|
|
# replace with dirt 'n grass
|
|
column[nh:nh + 1] = am.Grass.ID
|
|
column[nh + 1:nh + 1 + delta] = am.Air.ID
|
|
if delta > 0:
|
|
# Moving column up
|
|
column[keep + delta:] = schema.Blocks[x, z, keep:-delta]
|
|
# Put stone in gap at the bottom
|
|
column[keep:keep + delta] = am.Stone.ID
|
|
|
|
if Waterdepth > 0:
|
|
if Waterdepth > delta:
|
|
# Retain Ice
|
|
if column[nh + Waterdepth:nh + Waterdepth + 1] == am.Ice.ID:
|
|
column[nh + Waterdepth - delta:nh + 1 + Waterdepth - delta] = \
|
|
am.Ice.ID
|
|
column[nh + 1 + Waterdepth - delta:nh + 1 + Waterdepth] = am.Air.ID
|
|
else:
|
|
if Waterdepth < delta - 2:
|
|
column[nh:nh + 1] = am.Grass.ID
|
|
column[nh + 1:nh + 1 + Waterdepth] = am.Air.ID
|
|
else:
|
|
# Beach at the edge
|
|
column[nh - 4:nh - 2] = am.Sandstone.ID
|
|
column[nh - 2:nh + 1] = am.Sand.ID
|
|
column[nh + 1:nh + 1 + Waterdepth] = am.Air.ID
|
|
|
|
schema.Blocks[x, z] = column
|
|
|
|
level.copyBlocksFrom(schema, schema.bounds, box.origin)
|