User:JdavisBro/Maps

From Chicory: A Colorful Tale Wiki

Full Maps of Picnic based on the ingame map.

Layer 0[edit | edit source]

Layer 1[edit | edit source]

Layer 2[edit | edit source]

Code[edit | edit source]

Python code to generate the maps. Requires pillow. Extract the sprMap_outline_thick, sprMap_diag, sprMap_vert, and sprMap_water sprites with UndertaleModTool to script dir/sprites/ Move level_data from the game to script dir/ Run script

import base64
import json
import zlib
from pathlib import Path

from PIL import Image

sprdir = Path("sprites/")

def get_msquare_ims(name: str) -> list[Image.Image]:
    return [Image.open(sprdir / name.format(i)) for i in range(15)]

# outline = get_msquare_ims("sprMap_outline_{}.png")
outline_thick = get_msquare_ims("sprMap_outline_thick_{}.png")
# outline_thick2 = get_msquare_ims("sprMap_outline_thick1_{}.png") # not used i guess
# outline_thick3 = get_msquare_ims("sprMap_outline_thick11_{}.png")
diag = get_msquare_ims("sprMap_diag_{}.png")
vert = get_msquare_ims("sprMap_vert_{}.png")
water = get_msquare_ims("sprMap_water_{}.png")

with Path("level_data").open() as f:
    level_data = json.load(f)

GEO_SIZE = (81,46)

def geo(data: str) -> list[list[int]]:
    hex = zlib.decompress(base64.b64decode(data)).hex()
    data = [int(v + hex[i+1], base=16) for i, v in enumerate(hex) if i % 2 == 0]
    assert(len(data) == GEO_SIZE[0] * GEO_SIZE[1])
    data_list = []
    i = 0
    for y in range(GEO_SIZE[1]):
        data_list.append([])
        for x in range(GEO_SIZE[0]):
            data_list[-1].append(data[i])
            i += 1
    return data_list

# Map Making

def render_tag(im: Image, geo: list[list[int]], tag: int, msquare: list[Image.Image]) -> Image:
    size = msquare[0].size
    for y in range(len(geo)-1):
        for x in range(len(geo[y])-1):
            a, b, c, d = geo[y][x], geo[y][x+1], geo[y+1][x+1], geo[y+1][x]
            val = int(a == tag) + (int(b == tag) << 1) + (int(c == tag) << 2) + (int(d == tag) << 3)
            if val > 0:
                im.paste(msquare[val-1], (x*size[0], y*size[1]), msquare[val-1])

TILE_SIZE = (128, 72)

def do_level(screen: str) -> Image:
    im = Image.new("RGBA", (1920, 1080), (0,0,0,0))
    level = geo(level_data[screen]["geo"])
    minn = 0
    for y in range(len(level)):
        for x in range(len(level[y])):
            minn = min(minn, level[y][x])
            if level[y][x] > 128:
                minn = min(minn, level[y][x] - 255)
            if level[y][x] == 3:
                level[y][x] = 2
    render_tag(im, level, 5, diag) # unwalkable
    render_tag(im, level, 6, water) # water
    render_tag(im, level, 2, vert) # non swim
    render_tag(im, level, 2, outline_thick)
    render_tag(im, level, 4, outline_thick)
    t = 0
    while t >= minn:
        render_tag(im, level, t, outline_thick)
        t -= 1
    t = 255
    while t >= 255 + minn:
        render_tag(im, level, t, outline_thick)
        t -= 1
    return im

def main():
    min_x = {0: 0, 1: 0, 2: 0}
    max_x = {0: 0, 1: 0, 2: 0}
    min_y = {0: 0, 1: 0, 2: 0}
    max_y = {0: 0, 1: 0, 2: 0}
    tiles = {0: {}, 1: {}, 2: {}}
    for level in level_data:
        print(f"Making {level}")
        z, x, y = [int(i) for i in level.split("_")]
        if z > 3:
            continue
        if y == -99:
            continue
        tiles[z][f"{x}_{y}"] = do_level(level).resize(TILE_SIZE, Image.Resampling.NEAREST)
        min_x[z] = min(min_x[z], x)
        max_x[z] = max(max_x[z], x)
        min_y[z] = min(min_y[z], y)
        max_y[z] = max(max_y[z], y)
    for z in range(3):
        print(f"Saving Layer {z}")
        im_size = ((max_x[z] - min_x[z] + 1) * TILE_SIZE[0], (max_y[z] - min_y[z] + 1) * TILE_SIZE[1])
        tile_offset = (min_x[z] * -1 * TILE_SIZE[0], min_y[z] * -1 * TILE_SIZE[1])
        im = Image.new("RGBA", im_size)
        for level in tiles[z]:
            x, y = [int(i) for i in level.split("_")]
            im.paste(tiles[z][level], (tile_offset[0] + x * TILE_SIZE[0], tile_offset[1] + y * TILE_SIZE[1]))
        im.save(f"map{z}.png")

if __name__ == "__main__":
    main()