diff --git a/scripts/generate-battle-map.py b/scripts/generate-battle-map.py index dbf2dd4..4c25b64 100644 --- a/scripts/generate-battle-map.py +++ b/scripts/generate-battle-map.py @@ -1,6 +1,7 @@ from __future__ import annotations import ast +import math import random import re from pathlib import Path @@ -11,10 +12,15 @@ from PIL import Image, ImageDraw, ImageFilter ROOT = Path(__file__).resolve().parents[1] SCENARIO_PATH = ROOT / "src" / "game" / "data" / "scenario.ts" OUTPUT_PATH = ROOT / "src" / "assets" / "images" / "battle" / "first-battle-map.png" -TILE = 128 +BASE_TILE = 128 +TILE = 224 SEED = 314159 +def s(value: float) -> int: + return max(1, round(value * TILE / BASE_TILE)) + + def load_first_battle_map() -> tuple[int, int, list[list[str]]]: source = SCENARIO_PATH.read_text(encoding="utf-8") width_match = re.search(r"firstBattleMap:\s*BattleMap\s*=\s*\{\s*width:\s*(\d+)", source) @@ -68,13 +74,13 @@ def draw_base(width: int, height: int, terrain: list[list[str]], rng: random.Ran ground_noise = Image.new("RGBA", image.size, (0, 0, 0, 0)) noise_draw = ImageDraw.Draw(ground_noise) - for _ in range(width * height * 12): + for _ in range(width * height * 42): px = rng.randrange(width * TILE) py = rng.randrange(height * TILE) - radius = rng.randint(2, 9) + radius = rng.randint(s(2), s(11)) color = jitter(rng.choice(palettes["plain"]), 16, rng) noise_draw.ellipse((px - radius, py - radius, px + radius, py + radius), fill=(*color, rng.randint(18, 42))) - ground_noise = ground_noise.filter(ImageFilter.GaussianBlur(1.2)) + ground_noise = ground_noise.filter(ImageFilter.GaussianBlur(s(1.2))) image = Image.alpha_composite(image.convert("RGBA"), ground_noise).convert("RGB") draw = ImageDraw.Draw(image) @@ -95,14 +101,14 @@ def draw_base(width: int, height: int, terrain: list[list[str]], rng: random.Ran "cliff": 82, }[kind] tint_draw.rectangle(tile_box(x, y), fill=(*jitter(base, 8, rng), alpha)) - for _ in range(20): + for _ in range(64): px = x * TILE + rng.randrange(TILE) py = y * TILE + rng.randrange(TILE) - radius = rng.randint(1, 3) + radius = rng.randint(s(1), s(3)) color = jitter(rng.choice(palettes[kind]), 14, rng) draw.ellipse((px - radius, py - radius, px + radius, py + radius), fill=color) - tint = tint.filter(ImageFilter.GaussianBlur(18)) + tint = tint.filter(ImageFilter.GaussianBlur(s(18))) image = Image.alpha_composite(image.convert("RGBA"), tint).convert("RGB") return image @@ -112,12 +118,17 @@ def draw_grass_detail(draw: ImageDraw.ImageDraw, terrain: list[list[str]], rng: for x, kind in enumerate(row): if kind not in {"plain", "camp", "village"}: continue - for _ in range(18): - px = x * TILE + rng.randint(4, TILE - 4) - py = y * TILE + rng.randint(4, TILE - 4) - length = rng.randint(5, 13) + for _ in range(52): + px = x * TILE + rng.randint(s(4), TILE - s(4)) + py = y * TILE + rng.randint(s(4), TILE - s(4)) + length = rng.randint(s(5), s(15)) color = rng.choice([(70, 116, 55), (126, 116, 54), (62, 94, 47), (148, 130, 68)]) - draw.line((px, py, px + rng.randint(-3, 3), py - length), fill=color, width=1) + draw.line((px, py, px + rng.randint(-s(3), s(3)), py - length), fill=color, width=s(1)) + for _ in range(14): + px = x * TILE + rng.randint(s(8), TILE - s(8)) + py = y * TILE + rng.randint(s(8), TILE - s(8)) + color = jitter(rng.choice([(121, 105, 58), (83, 102, 57), (151, 128, 71)]), 9, rng) + draw.ellipse((px - s(3), py - s(2), px + s(4), py + s(2)), fill=color) def draw_roads(draw: ImageDraw.ImageDraw, terrain: list[list[str]], rng: random.Random) -> None: @@ -141,21 +152,29 @@ def draw_roads(draw: ImageDraw.ImageDraw, terrain: list[list[str]], rng: random. for dx, dy in connections: end_x = cx + dx * TILE // 2 end_y = cy + dy * TILE // 2 - wobble = rng.randint(-7, 7) + wobble = rng.randint(-s(8), s(8)) if dx: points = [(cx, cy + wobble), ((cx + end_x) // 2, cy - wobble), (end_x, end_y + wobble)] elif dy: points = [(cx + wobble, cy), (cx - wobble, (cy + end_y) // 2), (end_x + wobble, end_y)] else: - points = [(cx - 28, cy), (cx, cy + 14), (cx + 28, cy - 8)] - draw.line(points, fill=shadow, width=42, joint="curve") - draw.line(points, fill=road, width=35, joint="curve") - draw.line(points, fill=highlight, width=6, joint="curve") + points = [(cx - s(34), cy), (cx, cy + s(16)), (cx + s(34), cy - s(10))] + draw.line(points, fill=shadow, width=s(50), joint="curve") + draw.line(points, fill=road, width=s(41), joint="curve") + draw.line(points, fill=highlight, width=s(7), joint="curve") - for _ in range(20): - px = x * TILE + rng.randint(10, TILE - 10) - py = y * TILE + rng.randint(10, TILE - 10) - draw.ellipse((px - 2, py - 1, px + 2, py + 1), fill=jitter((122, 96, 62), 10, rng)) + for _ in range(52): + px = x * TILE + rng.randint(s(10), TILE - s(10)) + py = y * TILE + rng.randint(s(10), TILE - s(10)) + draw.ellipse((px - s(2), py - s(1), px + s(3), py + s(2)), fill=jitter((122, 96, 62), 10, rng)) + for _ in range(7): + px = x * TILE + rng.randint(s(18), TILE - s(18)) + py = y * TILE + rng.randint(s(18), TILE - s(18)) + draw.line( + (px - s(14), py + rng.randint(-s(4), s(4)), px + s(16), py + rng.randint(-s(4), s(4))), + fill=jitter((102, 79, 52), 8, rng), + width=s(2) + ) def draw_rivers(draw: ImageDraw.ImageDraw, terrain: list[list[str]], rng: random.Random) -> None: @@ -170,32 +189,36 @@ def draw_rivers(draw: ImageDraw.ImageDraw, terrain: list[list[str]], rng: random east = neighbor_is(terrain, x + 1, y, {"river"}) west = neighbor_is(terrain, x - 1, y, {"river"}) - water = [(left + 10, top + 10), (right - 8, top + 2), (right - 12, bottom - 8), (left + 4, bottom - 2)] + water = [(left + s(10), top + s(10)), (right - s(8), top + s(2)), (right - s(12), bottom - s(8)), (left + s(4), bottom - s(2))] if north: - water[0] = (left + 8, top - 8) - water[1] = (right - 4, top - 8) + water[0] = (left + s(8), top - s(8)) + water[1] = (right - s(4), top - s(8)) if south: - water[2] = (right - 10, bottom + 8) - water[3] = (left + 8, bottom + 8) + water[2] = (right - s(10), bottom + s(8)) + water[3] = (left + s(8), bottom + s(8)) draw.polygon(water, fill=(43, 94, 107)) - draw.polygon([(left + 3, top + 2), (right - 3, top + 0), (right - 8, top + 18), (left + 8, top + 20)], fill=(69, 117, 119)) - for _ in range(10): - sx = rng.randint(left + 12, right - 12) - sy = rng.randint(top + 15, bottom - 15) - draw.arc((sx - 18, sy - 5, sx + 18, sy + 9), 190, 340, fill=(135, 177, 175), width=1) + draw.polygon([(left + s(3), top + s(2)), (right - s(3), top), (right - s(8), top + s(18)), (left + s(8), top + s(20))], fill=(69, 117, 119)) + for _ in range(24): + sx = rng.randint(left + s(12), right - s(12)) + sy = rng.randint(top + s(15), bottom - s(15)) + draw.arc((sx - s(18), sy - s(5), sx + s(18), sy + s(9)), 190, 340, fill=(135, 177, 175), width=s(1)) + for _ in range(8): + sx = rng.randint(left + s(12), right - s(12)) + sy = rng.randint(top + s(18), bottom - s(18)) + draw.line((sx - s(22), sy, sx + s(24), sy + rng.randint(-s(5), s(5))), fill=(55, 125, 137), width=s(2)) for side, connected in ((left, west), (right, east)): if not connected: - draw.line((side, top + 6, side, bottom - 6), fill=(83, 87, 62), width=6) + draw.line((side, top + s(6), side, bottom - s(6)), fill=(83, 87, 62), width=s(6)) if not north: - draw.line((left + 5, top, right - 5, top), fill=(91, 91, 60), width=6) + draw.line((left + s(5), top, right - s(5), top), fill=(91, 91, 60), width=s(6)) if not south: - draw.line((left + 4, bottom, right - 6, bottom), fill=(80, 83, 57), width=7) - for _ in range(5): - rx = rng.randint(left + 8, right - 8) - ry = rng.randint(top + 8, bottom - 8) - draw.ellipse((rx - 5, ry - 3, rx + 6, ry + 4), fill=(85, 92, 80)) + draw.line((left + s(4), bottom, right - s(6), bottom), fill=(80, 83, 57), width=s(7)) + for _ in range(12): + rx = rng.randint(left + s(8), right - s(8)) + ry = rng.randint(top + s(8), bottom - s(8)) + draw.ellipse((rx - s(5), ry - s(3), rx + s(6), ry + s(4)), fill=(85, 92, 80)) if not (north or south or east or west): - draw.ellipse((cx - 24, cy - 20, cx + 24, cy + 20), fill=(44, 92, 103)) + draw.ellipse((cx - s(24), cy - s(20), cx + s(24), cy + s(20)), fill=(44, 92, 103)) def draw_forests(draw: ImageDraw.ImageDraw, terrain: list[list[str]], rng: random.Random) -> None: @@ -204,20 +227,25 @@ def draw_forests(draw: ImageDraw.ImageDraw, terrain: list[list[str]], rng: rando if kind != "forest": continue left, top, right, bottom = tile_box(x, y) - for _ in range(13): - px = rng.randint(left + 12, right - 12) - py = rng.randint(top + 14, bottom - 12) + for _ in range(28): + px = rng.randint(left + s(12), right - s(12)) + py = rng.randint(top + s(14), bottom - s(12)) trunk = jitter((78, 54, 34), 8, rng) - draw.rectangle((px - 3, py + 7, px + 3, py + 23), fill=trunk) - for radius, color in ((22, (36, 74, 39)), (17, (54, 96, 42)), (11, (81, 120, 53))): - ox = rng.randint(-4, 4) - oy = rng.randint(-5, 4) + draw.rectangle((px - s(3), py + s(7), px + s(3), py + s(23)), fill=trunk) + for radius, color in ((24, (36, 74, 39)), (18, (54, 96, 42)), (12, (81, 120, 53))): + radius = s(radius) + ox = rng.randint(-s(4), s(4)) + oy = rng.randint(-s(5), s(4)) draw.ellipse((px - radius + ox, py - radius + oy, px + radius + ox, py + radius + oy), fill=jitter(color, 8, rng)) - draw.ellipse((px - 14, py - 18, px + 4, py - 2), fill=(103, 132, 56)) - for _ in range(10): + draw.ellipse((px - s(14), py - s(18), px + s(4), py - s(2)), fill=(103, 132, 56)) + for _ in range(28): px = rng.randint(left, right) py = rng.randint(top, bottom) - draw.ellipse((px - 3, py - 3, px + 3, py + 3), fill=(24, 54, 31)) + draw.ellipse((px - s(3), py - s(3), px + s(3), py + s(3)), fill=(24, 54, 31)) + for _ in range(6): + sx = rng.randint(left + s(20), right - s(20)) + sy = rng.randint(top + s(20), bottom - s(20)) + draw.arc((sx - s(26), sy - s(14), sx + s(28), sy + s(16)), 185, 335, fill=(22, 49, 29), width=s(2)) def draw_hills_and_cliffs(draw: ImageDraw.ImageDraw, terrain: list[list[str]], rng: random.Random) -> None: @@ -227,29 +255,33 @@ def draw_hills_and_cliffs(draw: ImageDraw.ImageDraw, terrain: list[list[str]], r continue left, top, right, bottom = tile_box(x, y) if kind == "hill": - for _ in range(5): - cx = rng.randint(left + 22, right - 22) - cy = rng.randint(top + 22, bottom - 22) - rx = rng.randint(20, 38) - ry = rng.randint(10, 24) + for _ in range(10): + cx = rng.randint(left + s(22), right - s(22)) + cy = rng.randint(top + s(22), bottom - s(22)) + rx = rng.randint(s(20), s(42)) + ry = rng.randint(s(10), s(25)) draw.ellipse((cx - rx, cy - ry, cx + rx, cy + ry), fill=jitter((126, 113, 80), 12, rng)) - draw.arc((cx - rx, cy - ry, cx + rx, cy + ry), 180, 350, fill=(176, 160, 112), width=2) - draw.arc((cx - rx, cy - ry, cx + rx, cy + ry), 20, 160, fill=(72, 69, 52), width=2) + draw.arc((cx - rx, cy - ry, cx + rx, cy + ry), 180, 350, fill=(176, 160, 112), width=s(2)) + draw.arc((cx - rx, cy - ry, cx + rx, cy + ry), 20, 160, fill=(72, 69, 52), width=s(2)) + for _ in range(12): + px = rng.randint(left + s(10), right - s(10)) + py = rng.randint(top + s(10), bottom - s(10)) + draw.line((px - s(11), py, px + s(13), py + rng.randint(-s(3), s(3))), fill=(89, 84, 62), width=s(1)) continue draw.rectangle((left, top, right, bottom), fill=jitter((55, 52, 45), 5, rng)) - for _ in range(12): - ax = rng.randint(left - 8, right - 12) - ay = rng.randint(top + 2, bottom - 8) + for _ in range(24): + ax = rng.randint(left - s(8), right - s(12)) + ay = rng.randint(top + s(2), bottom - s(8)) points = [ - (ax, ay + rng.randint(12, 26)), - (ax + rng.randint(14, 31), ay), - (ax + rng.randint(32, 58), ay + rng.randint(18, 40)), - (ax + rng.randint(4, 18), ay + rng.randint(42, 68)), + (ax, ay + rng.randint(s(12), s(26))), + (ax + rng.randint(s(14), s(31)), ay), + (ax + rng.randint(s(32), s(58)), ay + rng.randint(s(18), s(40))), + (ax + rng.randint(s(4), s(18)), ay + rng.randint(s(42), s(68))), ] draw.polygon(points, fill=jitter((75, 70, 58), 15, rng)) - draw.line(points + [points[0]], fill=(37, 37, 34), width=2) - draw.line((left + 8, top + 5, right - 8, bottom - 8), fill=(116, 105, 77), width=3) + draw.line(points + [points[0]], fill=(37, 37, 34), width=s(2)) + draw.line((left + s(8), top + s(5), right - s(8), bottom - s(8)), fill=(116, 105, 77), width=s(3)) def draw_structures(draw: ImageDraw.ImageDraw, terrain: list[list[str]], rng: random.Random) -> None: @@ -260,64 +292,78 @@ def draw_structures(draw: ImageDraw.ImageDraw, terrain: list[list[str]], rng: ra left, top, right, bottom = tile_box(x, y) cx, cy = tile_center(x, y) if kind == "village": - draw_hut(draw, cx - 22, cy - 14, rng) - draw_hut(draw, cx + 22, cy + 12, rng) - draw.line((left + 12, bottom - 22, right - 12, bottom - 22), fill=(91, 66, 39), width=5) - draw.rectangle((cx - 7, cy + 22, cx + 8, cy + 45), fill=(208, 165, 65)) + draw_hut(draw, cx - s(24), cy - s(16), rng) + draw_hut(draw, cx + s(24), cy + s(14), rng) + draw.line((left + s(12), bottom - s(22), right - s(12), bottom - s(22)), fill=(91, 66, 39), width=s(5)) + draw.rectangle((cx - s(7), cy + s(22), cx + s(8), cy + s(45)), fill=(208, 165, 65)) + draw.line((cx - s(50), cy + s(45), cx + s(52), cy + s(35)), fill=(79, 61, 42), width=s(2)) continue if kind == "camp": - for ox, oy, color in ((-24, 5, (179, 129, 50)), (22, 0, (209, 174, 84))): - draw.polygon([(cx + ox, cy + oy - 32), (cx + ox - 28, cy + oy + 22), (cx + ox + 28, cy + oy + 22)], fill=color) - draw.line((cx + ox, cy + oy - 32, cx + ox, cy + oy + 22), fill=(88, 58, 32), width=3) - draw.line((cx + ox - 28, cy + oy + 22, cx + ox + 28, cy + oy + 22), fill=(92, 61, 35), width=4) - draw.line((left + 12, top + 20, right - 12, top + 10), fill=(105, 81, 45), width=4) - draw_banner(draw, right - 28, top + 28, (216, 171, 57)) + for ox, oy, color in ((-27, 6, (179, 129, 50)), (24, 0, (209, 174, 84))): + ox = s(ox) + oy = s(oy) + draw.polygon([(cx + ox, cy + oy - s(34)), (cx + ox - s(31), cy + oy + s(24)), (cx + ox + s(31), cy + oy + s(24))], fill=color) + draw.line((cx + ox, cy + oy - s(34), cx + ox, cy + oy + s(24)), fill=(88, 58, 32), width=s(3)) + draw.line((cx + ox - s(31), cy + oy + s(24), cx + ox + s(31), cy + oy + s(24)), fill=(92, 61, 35), width=s(4)) + draw.line((cx + ox - s(17), cy + oy + s(10), cx + ox + s(15), cy + oy + s(10)), fill=(225, 189, 92), width=s(2)) + draw.line((left + s(12), top + s(20), right - s(12), top + s(10)), fill=(105, 81, 45), width=s(4)) + draw_banner(draw, right - s(28), top + s(28), (216, 171, 57)) continue - draw.rectangle((left + 12, top + 22, right - 12, bottom - 18), outline=(73, 55, 42), width=8) - draw.rectangle((left + 20, top + 30, right - 20, bottom - 26), outline=(119, 88, 60), width=5) - draw.rectangle((cx - 18, bottom - 50, cx + 18, bottom - 18), fill=(63, 48, 39)) - for tx, ty in ((left + 16, top + 20), (right - 16, top + 20), (left + 16, bottom - 18), (right - 16, bottom - 18)): - draw.rectangle((tx - 10, ty - 10, tx + 10, ty + 10), fill=(93, 70, 52)) - draw_banner(draw, cx + 26, top + 28, (213, 171, 54)) + draw.rectangle((left + s(12), top + s(22), right - s(12), bottom - s(18)), outline=(73, 55, 42), width=s(8)) + draw.rectangle((left + s(20), top + s(30), right - s(20), bottom - s(26)), outline=(119, 88, 60), width=s(5)) + draw.rectangle((cx - s(18), bottom - s(50), cx + s(18), bottom - s(18)), fill=(63, 48, 39)) + for tx, ty in ((left + s(16), top + s(20)), (right - s(16), top + s(20)), (left + s(16), bottom - s(18)), (right - s(16), bottom - s(18))): + draw.rectangle((tx - s(10), ty - s(10), tx + s(10), ty + s(10)), fill=(93, 70, 52)) + draw.line((left + s(18), cy, right - s(18), cy), fill=(83, 62, 48), width=s(3)) + draw_banner(draw, cx + s(26), top + s(28), (213, 171, 54)) def draw_hut(draw: ImageDraw.ImageDraw, x: int, y: int, rng: random.Random) -> None: - draw.rectangle((x - 22, y - 2, x + 22, y + 26), fill=jitter((106, 74, 45), 8, rng)) - draw.polygon([(x - 31, y), (x, y - 28), (x + 31, y)], fill=jitter((151, 106, 48), 8, rng)) - draw.line((x - 30, y, x + 30, y), fill=(84, 56, 34), width=4) - draw.rectangle((x - 7, y + 8, x + 6, y + 26), fill=(55, 39, 31)) + draw.rectangle((x - s(22), y - s(2), x + s(22), y + s(26)), fill=jitter((106, 74, 45), 8, rng)) + draw.polygon([(x - s(31), y), (x, y - s(28)), (x + s(31), y)], fill=jitter((151, 106, 48), 8, rng)) + draw.line((x - s(30), y, x + s(30), y), fill=(84, 56, 34), width=s(4)) + draw.rectangle((x - s(7), y + s(8), x + s(6), y + s(26)), fill=(55, 39, 31)) + draw.line((x - s(20), y + s(8), x + s(20), y + s(8)), fill=(137, 93, 52), width=s(2)) def draw_banner(draw: ImageDraw.ImageDraw, x: int, y: int, color: tuple[int, int, int]) -> None: - draw.line((x, y - 22, x, y + 30), fill=(68, 48, 31), width=3) - draw.polygon([(x, y - 18), (x + 23, y - 12), (x, y + 2)], fill=color) - draw.line((x, y - 18, x + 23, y - 12, x, y + 2), fill=(96, 66, 28), width=1) + draw.line((x, y - s(22), x, y + s(30)), fill=(68, 48, 31), width=s(3)) + draw.polygon([(x, y - s(18)), (x + s(23), y - s(12)), (x, y + s(2))], fill=color) + draw.line((x, y - s(18), x + s(23), y - s(12), x, y + s(2)), fill=(96, 66, 28), width=s(1)) def draw_shadows_and_light(image: Image.Image, rng: random.Random) -> Image.Image: width, height = image.size shade = Image.new("RGBA", image.size, (0, 0, 0, 0)) draw = ImageDraw.Draw(shade) - for _ in range(150): - x = rng.randint(-80, width) - y = rng.randint(-60, height) - rx = rng.randint(45, 150) - ry = rng.randint(20, 90) + for _ in range(260): + x = rng.randint(-s(80), width) + y = rng.randint(-s(60), height) + rx = rng.randint(s(45), s(170)) + ry = rng.randint(s(20), s(95)) draw.ellipse((x - rx, y - ry, x + rx, y + ry), fill=(0, 0, 0, rng.randint(5, 16))) - shade = shade.filter(ImageFilter.GaussianBlur(18)) + shade = shade.filter(ImageFilter.GaussianBlur(s(18))) result = Image.alpha_composite(image.convert("RGBA"), shade) glow = Image.new("RGBA", image.size, (0, 0, 0, 0)) gdraw = ImageDraw.Draw(glow) gdraw.ellipse((-width * 0.22, -height * 0.22, width * 0.82, height * 0.62), fill=(255, 213, 135, 28)) - glow = glow.filter(ImageFilter.GaussianBlur(80)) + glow = glow.filter(ImageFilter.GaussianBlur(s(80))) result = Image.alpha_composite(result, glow) + mask_size = (320, max(1, round(320 * height / width))) + vignette_mask = Image.new("L", mask_size, 0) + pixels = vignette_mask.load() + cx = (mask_size[0] - 1) / 2 + cy = (mask_size[1] - 1) / 2 + max_distance = math.hypot(cx, cy) + for py in range(mask_size[1]): + for px in range(mask_size[0]): + distance = math.hypot(px - cx, py - cy) / max_distance + pixels[px, py] = clamp(round(max(0, distance - 0.45) / 0.55 * 68)) + vignette_mask = vignette_mask.resize(image.size, Image.Resampling.BICUBIC).filter(ImageFilter.GaussianBlur(s(18))) vignette = Image.new("RGBA", image.size, (0, 0, 0, 0)) - vdraw = ImageDraw.Draw(vignette) - for i in range(80): - alpha = int((i / 79) ** 2 * 75) - vdraw.rectangle((i * width / 160, i * height / 160, width - i * width / 160, height - i * height / 160), outline=(0, 0, 0, alpha), width=4) + vignette.putalpha(vignette_mask) result = Image.alpha_composite(result, vignette) return result.convert("RGB") @@ -327,10 +373,10 @@ def add_tile_seams(image: Image.Image, width: int, height: int) -> Image.Image: draw = ImageDraw.Draw(overlay) for x in range(1, width): px = x * TILE - draw.line((px, 0, px, height * TILE), fill=(14, 19, 14, 5), width=1) + draw.line((px, 0, px, height * TILE), fill=(14, 19, 14, 10), width=s(1)) for y in range(1, height): py = y * TILE - draw.line((0, py, width * TILE, py), fill=(14, 19, 14, 5), width=1) + draw.line((0, py, width * TILE, py), fill=(14, 19, 14, 10), width=s(1)) return Image.alpha_composite(image.convert("RGBA"), overlay).convert("RGB") diff --git a/src/assets/images/battle/first-battle-map.png b/src/assets/images/battle/first-battle-map.png index d667298..3a6df29 100644 Binary files a/src/assets/images/battle/first-battle-map.png and b/src/assets/images/battle/first-battle-map.png differ