def chop_terrain(img, cross_img=None):
    parts = img.chop_grid(TERRAIN_PARTS)
    parts['empty'] = BLANK_TILE
    if cross_img is not None:
        parts.update(cross_img.chop_grid((
            ('cross/nw',),
            ('cross/ne',),
            )))
    else:
        parts['cross/nw'] = image2.stack((parts['inner/nw'], parts['inner/se']))
        parts['cross/ne'] = image2.stack((parts['inner/ne'], parts['inner/sw']))
    return parts
 def get_top(side):
     return image2.stack((
         black[ds[0]],
         wall_top[ds[1]],
         wall_top[ds[2]],
         ramp['top/%s' % side],
         ))
 def get_front(z, side):
     front_desc = calc_front_desc(ds)
     ramp_front = ramp['front/%s/z%d' % (side, z)]
     if front_desc is not None:
         return image2.stack((wall_front['%s/z%d' % (front_desc, z)], ramp_front))
     else:
         return ramp_front
def do_cave_entrance(tiles, cave_bottom_dct):
    wall_top = chop_cave_top(tiles('lpc-cave-walls2.png'))
    wall_front = chop_cave_front(tiles('lpc-cave-walls2.png'))
    door_front = chop_cave_entrance(tiles('lpc-cave-walls2.png'))
    black = chop_black()
    cave_bottom = cave_bottom_dct['cccc']

    bb = BLOCK.child().shape('solid')

    for code in iter_codes(3):
        nw, ne, se, sw = code
        ds = dissect(code, 3)
        name = ''.join(str(x) for x in code)
        terrain = ''.join(('g' if x == 1 else 'c') for x in code)

        def get_front(z, side):
            front_desc = calc_front_desc(ds)
            door_tile = door_front['%s/z%d' % (side, z)]
            if front_desc is not None:
                base = wall_front['%s/z%d' % (front_desc, z)]
                if side == 'left':
                    base = base.modify(blank_right)
                elif side == 'right':
                    base = base.modify(blank_left)
                return image2.stack((base, door_tile))
            else:
                return door_tile

        top = image2.stack((
            black[ds[0]],
            wall_top[ds[1]],
            wall_top[ds[2]],
            ))

        if ne == 2 and se == 1:
            bb.new('cave_entrance/x0/z0/%s/c%s' % (terrain, name)) \
                    .front(get_front(0, 'left')) \
                    .bottom(cave_bottom_dct[terrain])
            bb.new('cave_entrance/x0/z1/c%s' % name) \
                    .front(get_front(1, 'left')) \
                    .top(top)

        if code == (2, 2, 1, 1):
            bb.new('cave_entrance/x1/z0/%s/c%s' % (terrain, name)) \
                    .shape('floor') \
                    .front(door_front['center/z0']) \
                    .bottom(cave_bottom_dct[terrain])
            bb.new('cave_entrance/x1/z1/c%s' % name) \
                    .shape('empty') \
                    .front(door_front['center/z1']) \
                    .top(top)

        if nw == 2 and sw == 1:
            bb.new('cave_entrance/x2/z0/%s/c%s' % (terrain, name)) \
                    .front(get_front(0, 'right')) \
                    .bottom(cave_bottom_dct[terrain])
            bb.new('cave_entrance/x2/z1/c%s' % name) \
                    .front(get_front(1, 'right')) \
                    .top(top)
 def get_front(z, side):
     front_desc = calc_front_desc(ds)
     door_tile = door_front['%s/z%d' % (side, z)]
     if front_desc is not None:
         base = wall_front['%s/z%d' % (front_desc, z)]
         if side == 'left':
             base = base.modify(blank_right)
         elif side == 'right':
             base = base.modify(blank_left)
         return image2.stack((base, door_tile))
     else:
         return door_tile
def iter_terrain_floor(layers):
    letters = [l.name for l in layers]
    for code in iter_codes(len(layers)):
        name = ''.join(layers[i].name for i in code)

        parts = []
        for i in range(len(layers)):
            l = layers[i]
            if l.is_base:
                key = describe(tuple(x >= i for x in code))
            else:
                key = describe(tuple(x == i for x in code))
            parts.append(l.dct[key])
        img = image2.stack(parts)
        yield FloorInfo(name, img)
    def get_image(self):
        img_size = (self.size[0], self.size[1] + self.size[2])
        px_size = tuple(x * TILE_SIZE for x in img_size)

        # part.base is relative to the 0,0,0 corner, so part.base[1] may be as
        # low as -self.size[2] * TILE_SIZE.
        y_off = self.size[2] * TILE_SIZE

        layers = []
        for p in self.parts:
            bx, by = p.base
            layers.append(p.img.pad(px_size, offset=(bx, by + y_off)))

        if len(layers) > 0:
            result = image2.stack(layers)
        else:
            result = image2.Image(size=px_size)
        return result.raw().raw()
def interior_blocks(basename, img, shape='empty'):
    parts = img.chop(INTERIOR_PARTS, unit=TILE_SIZE // 2)

    def variations(k, vs):
        f = lambda x: (('0', 'inner'), ('1', 'full')) if x == '*' else (('', x),)

        for ak, av in f(vs[0]):
            for bk, bv in f(vs[1]):
                for ck, cv in f(vs[2]):
                    for dk, dv in f(vs[3]):
                        extra_k = ak + bk + ck + dk
                        local_k = k + '/' + extra_k if extra_k else k
                        yield (local_k, (av, bv, cv, dv))

    # Build up the map that describes how to build each tile from parts.
    PART_MAP_FULL = {}
    def add(base, parts_str):
        for k, v in variations(base, parts_str.split()):
            PART_MAP_FULL[k] = v

    # Naming convention: <edges>/<corners>
    #
    # <edges> is [n][s][w][e] depending on which edges are filled.
    #
    # <corners> is [01] for each corner with both connected edges filled
    # (order: nw, sw, se, ne).  The digit is 0 if the subtile in that position
    # is 'inner' (for situations where there is not a tile of the same group in
    # that direction), and 1 if the subtile is 'full' (there is a tile).
    #
    # If <corners> is empty, then the slash is omitted.

    #               'NW    SW    SE    NE   '
    add('spot',     'outer outer outer outer')

    add('n',        'vert  outer outer vert ')
    add('w',        'horiz horiz outer outer')
    add('s',        'outer vert  vert  outer')
    add('e',        'outer outer horiz horiz')

    add('nw',       '*     horiz outer vert ')
    add('sw',       'horiz *     vert  outer')
    add('se',       'outer vert  *     horiz')
    add('ne',       'vert  outer horiz *    ')
    add('ns',       'vert  vert  vert  vert ')
    add('we',       'horiz horiz horiz horiz')

    add('nsw',      '*     *     vert  vert ')
    add('swe',      'horiz *     *     horiz')
    add('nse',      'vert  vert  *     *    ')
    add('nwe',      '*     horiz horiz *    ')

    add('nswe',     '*     *     *     *    ')

    full_parts = {}
    HALF = TILE_SIZE // 2
    for k, (nw, sw, se, ne) in PART_MAP_FULL.items():
        full_parts[k] = image2.stack((
                parts['nw/' + nw].pad(TILE_SIZE, offset=(0, 0)),
                parts['sw/' + sw].pad(TILE_SIZE, offset=(0, HALF)),
                parts['se/' + se].pad(TILE_SIZE, offset=(HALF, HALF)),
                parts['ne/' + ne].pad(TILE_SIZE, offset=(HALF, 0)),
                )).with_unit(TILE_SIZE)

    b = BLOCK.prefixed(basename) \
            .shape(shape)
    b.new(full_parts.keys()) \
            .bottom(full_parts)
    return b
def init():
    tiles = loader('tiles', unit=TILE_SIZE)

    def mk_layer(letter, img_name, is_base=True):
        return Layer(letter, chop_terrain(tiles(img_name)), is_base)

    layer_dct = {
            'g': mk_layer('g', 'lpc-grassalt-with-variants.png'),
            'm': mk_layer('m', 'lpc-base-tiles/dirt.png'),
            'c': mk_layer('c', 'lpc-base-tiles/dirt2.png'),
            #'s': mk_layer('s', 'TODO'),
            'a': mk_layer('a', 'lpc-base-tiles/lavarock.png'),

            'w': mk_layer('w', 'lpc-base-tiles/water.png', is_base=False),
            'l': mk_layer('l', 'lpc-base-tiles/lava.png', is_base=False),
            'p': mk_layer('p', 'lpc-base-tiles/holemid.png', is_base=False),
            }

    order = 'mca wlp g s'

    def collect_layers(letters):
        return [layer_dct[l] for l in order if l in letters]

    # Base terrain
    floor_bb = BLOCK.prefixed('terrain').shape('floor')
    empty_bb = BLOCK.prefixed('terrain').shape('empty')
    seen = set()
    for letters in ('gc', 'gw'):
        for name, img in iter_terrain_floor(collect_layers(letters)):
            # Avoid duplicate blocks
            if name in seen:
                continue
            seen.add(name)

            bb = floor_bb if 'w' not in letters else empty_bb
            bb.new(name).bottom(img)
    
    # Variants
    for layer in layer_dct.values():
        name = layer.name * 4
        for i in range(4):
            key = 'full/v%d' % i if i > 0 else 'full'
            floor_bb.new('%s/v%d' % (name, i)).bottom(layer.dct[key])


    # Hilltop edges
    grass_top = chop_terrain(tiles('cave-top-grass.png'), tiles('cave-top-grass-cross.png'))
    for code in iter_codes(2):
        d = describe(tuple(x == 0 for x in code))
        blk = floor_bb.new('gggg/e%s' % ''.join(str(x) for x in code)).bottom(grass_top[d])
        if code == (1, 1, 1, 1):
            blk.shape('empty')

    # Cave top (for ramp tops inside caves)
    cave_top = chop_terrain(tiles('lpc-cave-top2.png'), tiles('lpc-cave-top2-cross.png'))
    for code in iter_codes(2):
        d = describe(tuple(x == 0 for x in code))
        blk = floor_bb.new('cccc/e%s' % ''.join(str(x) for x in code)).bottom(cave_top[d])
        if code == (1, 1, 1, 1):
            blk.shape('empty')

    # Terrain with cave walls
    for floor in iter_terrain_floor(collect_layers('gc')):
        for cave in iter_cave_z0(tiles('lpc-cave-walls2.png')):
            BLOCK.new('terrain/%s/c%s' % (floor.name, cave.name)) \
                    .bottom(floor.bottom) \
                    .front(cave.front) \
                    .shape('floor' if cave.clear else 'solid')

    # Cave walls (z1 part)
    cave_top_dct = chop_cave_top(tiles('lpc-cave-walls2.png'))
    cave_front_dct = chop_cave_front(tiles('lpc-cave-walls2.png'))
    black_dct = chop_black()

    for code in iter_codes(3):
        ds = dissect(code, 3)
        name = ''.join(str(x) for x in code)

        front_desc = calc_front_desc(ds)
        front = cave_front_dct['%s/z1' % front_desc] if front_desc is not None else None

        top = image2.stack((
            black_dct[ds[0]],
            cave_top_dct[ds[1]],
            cave_top_dct[ds[2]],
            ))

        clear = name in ('1111', '2222')
        BLOCK.new('cave_z1/c%s' % name) \
                .shape('empty' if clear else 'solid') \
                .top(top).front(front)

    # Ramps
    cave_bottom_dct = dict(iter_terrain_floor(collect_layers('gc')))
    do_cave_ramps(tiles, cave_bottom_dct)
    do_cave_entrance(tiles, cave_bottom_dct)
def do_cave_ramps(tiles, cave_bottom_dct):
    ramp = tiles('cave-stair.png').chop_grid(CAVE_RAMP_PARTS)
    wall_top = chop_cave_top(tiles('lpc-cave-walls2.png'))
    wall_front = chop_cave_front(tiles('lpc-cave-walls2.png'))
    black = chop_black()
    cave_bottom = cave_bottom_dct['cccc']

    bb = BLOCK.child().shape('solid')

    for code in iter_codes(3):
        nw, ne, se, sw = code
        ds = dissect(code, 3)
        name = ''.join(str(x) for x in code)
        terrain = ''.join(('g' if x == 1 else 'c') for x in code)

        def get_front(z, side):
            front_desc = calc_front_desc(ds)
            ramp_front = ramp['front/%s/z%d' % (side, z)]
            if front_desc is not None:
                return image2.stack((wall_front['%s/z%d' % (front_desc, z)], ramp_front))
            else:
                return ramp_front

        def get_top(side):
            return image2.stack((
                black[ds[0]],
                wall_top[ds[1]],
                wall_top[ds[2]],
                ramp['top/%s' % side],
                ))

        if ne == 0 and se != 0:
            # Front left
            if terrain != 'cccc':
                bb.new('ramp/xy01/z0/cccc/c%s' % name).front(get_front(0, 'left')) \
                        .bottom(cave_bottom)
            bb.new('ramp/xy01/z0/%s/c%s' % (terrain, name)).front(get_front(0, 'left')) \
                    .bottom(cave_bottom_dct[terrain])
            bb.new('ramp/xy01/z1/c%s' % name).front(get_front(1, 'left')) \
                    .top(get_top('front/left/%s' % ('corner' if sw != 0 else 'edge')))

        if nw == 0 and sw != 0:
            # Front right
            if terrain != 'cccc':
                bb.new('ramp/xy21/z0/cccc/c%s' % name).front(get_front(0, 'right')) \
                        .bottom(cave_bottom)
            bb.new('ramp/xy21/z0/%s/c%s' % (terrain, name)).front(get_front(0, 'right')) \
                    .bottom(cave_bottom_dct[terrain])
            bb.new('ramp/xy21/z1/c%s' % name).front(get_front(1, 'right')) \
                    .top(get_top('front/right/%s' % ('corner' if se != 0 else 'edge')))

        def normal_front(z):
            front_desc = calc_front_desc(ds)
            if front_desc is not None:
                return wall_front['%s/z%d' % (front_desc, z)]
            else:
                return None

        if se == 0:
            # Back right
            if terrain != 'cccc':
                bb.new('ramp/xy00/z0/cccc/c%s' % name).front(normal_front(0)) \
                        .bottom(cave_bottom)
            bb.new('ramp/xy00/z0/%s/c%s' % (terrain, name)).front(normal_front(0)) \
                    .bottom(cave_bottom_dct[terrain])
            bb.new('ramp/xy00/z1/c%s' % name).front(normal_front(1)) \
                    .top(get_top('back/left'))

        if sw == 0:
            # Back left
            if terrain != 'cccc':
                bb.new('ramp/xy20/z0/cccc/c%s' % name).front(normal_front(0)) \
                        .bottom(cave_bottom)
            bb.new('ramp/xy20/z0/%s/c%s' % (terrain, name)).front(normal_front(0)) \
                    .bottom(cave_bottom_dct[terrain])
            bb.new('ramp/xy20/z1/c%s' % name).front(normal_front(1)) \
                    .top(get_top('back/right'))

        if sw == 0 and se == 0:
            # Back center
            if terrain != 'cccc':
                bb.new('ramp/xy10/z0/cccc/c%s' % name).front(normal_front(0)) \
                        .bottom(cave_bottom)
            bb.new('ramp/xy10/z0/%s/c%s' % (terrain, name)).front(normal_front(0)) \
                    .bottom(cave_bottom_dct[terrain])
            bb.new('ramp/xy10/z1/c%s' % name).front(normal_front(1)) \
                    .top(get_top('back/center/dirt2'))

    bb_ramp = BLOCK.child().shape('ramp_n')
    for terrain in ('grass', 'dirt', 'dirt2'):
        bottom = cave_bottom if terrain != 'grass' else cave_bottom_dct['gggg']

        bb_ramp.new('ramp/%s/z0' % terrain) \
                .bottom(image2.stack((
                    bottom,
                    ramp['ramp/%s/3' % terrain],
                    ))) \
                .back(ramp['ramp/%s/2' % terrain])
        bb_ramp.new('ramp/%s/z1' % terrain) \
                .bottom(ramp['ramp/%s/1' % terrain]) \
                .back(ramp['ramp/%s/0' % terrain])

        BLOCK.new('ramp/%s/cap0' % terrain) \
                .shape('floor') \
                .bottom(ramp['ramp/%s/cap0' % terrain])
        BLOCK.new('ramp/%s/cap1' % terrain) \
                .shape('empty') \
                .bottom(ramp['ramp/%s/cap1' % terrain])
def add_cross(parts):
    parts['cross/nw'] = image2.stack((parts['inner/nw'], parts['inner/se']))
    parts['cross/ne'] = image2.stack((parts['inner/ne'], parts['inner/sw']))