2026-04-17 16:04:33 +00:00
|
|
|
import json
|
2026-05-15 16:38:36 +00:00
|
|
|
import pygame
|
|
|
|
|
from scripts.models import Tile
|
2026-04-24 16:45:31 +00:00
|
|
|
|
2026-05-15 16:38:36 +00:00
|
|
|
AUTOTILE_MAP: dict[tuple[tuple[int, int], ...], int] = {
|
2026-04-17 16:04:33 +00:00
|
|
|
tuple(sorted([(1, 0), (0, 1)])): 0,
|
|
|
|
|
tuple(sorted([(1, 0), (0, 1), (-1, 0)])): 1,
|
|
|
|
|
tuple(sorted([(-1, 0), (0, 1)])): 2,
|
|
|
|
|
tuple(sorted([(-1, 0), (0, -1), (0, 1)])): 3,
|
|
|
|
|
tuple(sorted([(-1, 0), (0, -1)])): 4,
|
|
|
|
|
tuple(sorted([(-1, 0), (0, -1), (1, 0)])): 5,
|
|
|
|
|
tuple(sorted([(1, 0), (0, -1)])): 6,
|
|
|
|
|
tuple(sorted([(1, 0), (0, -1), (0, 1)])): 7,
|
|
|
|
|
tuple(sorted([(1, 0), (-1, 0), (0, 1), (0, -1)])): 8,
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-15 16:38:36 +00:00
|
|
|
NEIGHBOR_OFFSETS: list[tuple[int, int]] = [(-1, 0), (-1, -1), (0, -1), (1, -1), (1, 0), (0, 0), (-1, 1), (0, 1), (1, 1)]
|
|
|
|
|
PHYSICS_TILES: set[str] = {'grass', 'stone'}
|
|
|
|
|
AUTOTILE_TYPES: set[str] = {'grass', 'stone'}
|
2026-04-24 16:45:31 +00:00
|
|
|
|
2026-02-06 17:51:57 +00:00
|
|
|
class Tilemap:
|
2026-05-15 16:38:36 +00:00
|
|
|
def __init__(self, game, tile_size: int = 16) -> None:
|
2026-02-06 17:51:57 +00:00
|
|
|
self.game = game
|
2026-05-11 08:37:37 +00:00
|
|
|
self.tile_size: int = tile_size
|
2026-05-15 16:38:36 +00:00
|
|
|
self.tilemap: dict[str, Tile] = {}
|
|
|
|
|
self.offgrid_tiles: list[Tile] = []
|
|
|
|
|
|
|
|
|
|
def extract(self, id_pairs: list[tuple[str, int]], keep: bool = False) -> list[dict]:
|
|
|
|
|
matches: list[dict] = []
|
|
|
|
|
for tile in self.offgrid_tiles.copy():
|
|
|
|
|
if (tile.type, tile.variant) in id_pairs:
|
|
|
|
|
matches.append(tile.to_dict())
|
|
|
|
|
if not keep:
|
|
|
|
|
self.offgrid_tiles.remove(tile)
|
|
|
|
|
|
|
|
|
|
for loc in list(self.tilemap):
|
|
|
|
|
tilemap_tile = self.tilemap[loc]
|
|
|
|
|
if (tilemap_tile.type, tilemap_tile.variant) in id_pairs:
|
|
|
|
|
end_match = tilemap_tile.to_dict()
|
|
|
|
|
end_match["pos"] = [end_match['pos'][0] * self.tile_size, end_match['pos'][1] * self.tile_size]
|
|
|
|
|
|
|
|
|
|
matches.append(end_match)
|
|
|
|
|
|
|
|
|
|
if not keep:
|
|
|
|
|
del self.tilemap[loc]
|
|
|
|
|
return matches
|
|
|
|
|
|
|
|
|
|
def tiles_around(self, pos: tuple[float, float]) -> list[Tile]:
|
|
|
|
|
tiles: list[Tile] = []
|
|
|
|
|
tile_location: tuple[int, int] = (int(pos[0] // self.tile_size), int(pos[1] // self.tile_size))
|
2026-02-06 17:51:57 +00:00
|
|
|
for offset in NEIGHBOR_OFFSETS:
|
2026-05-15 16:38:36 +00:00
|
|
|
check_location: str = str(tile_location[0] + offset[0]) + ';' + str(tile_location[1] + offset[1])
|
2026-02-06 17:51:57 +00:00
|
|
|
if check_location in self.tilemap:
|
|
|
|
|
tiles.append(self.tilemap[check_location])
|
|
|
|
|
return tiles
|
2026-05-15 16:38:36 +00:00
|
|
|
|
|
|
|
|
def save(self, path: str) -> None:
|
|
|
|
|
data: dict = {
|
|
|
|
|
'tilemap': {
|
|
|
|
|
k: tile.to_dict() for k, tile in self.tilemap.items()
|
|
|
|
|
},
|
|
|
|
|
'tile_size': self.tile_size,
|
|
|
|
|
'offgrid': [
|
|
|
|
|
tile.to_dict() for tile in self.offgrid_tiles
|
|
|
|
|
],
|
|
|
|
|
}
|
|
|
|
|
with open(path, 'w') as f:
|
|
|
|
|
json.dump(data, f)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def load(self, path: str) -> None:
|
|
|
|
|
with open(path, 'r') as f:
|
|
|
|
|
map_data = json.load(f)
|
|
|
|
|
|
|
|
|
|
self.tile_size: int = map_data['tile_size']
|
|
|
|
|
|
|
|
|
|
# Grid-Tiles: Dict -> Tile-Objekte
|
|
|
|
|
self.tilemap: dict[str, Tile] = {}
|
|
|
|
|
for key, tile_data in map_data['tilemap'].items():
|
|
|
|
|
self.tilemap[key] = Tile.from_dict(tile_data)
|
|
|
|
|
|
|
|
|
|
# Offgrid-Tiles: Dict -> Tile-Objekte
|
|
|
|
|
self.offgrid_tiles: list[Tile] = [
|
|
|
|
|
Tile.from_dict(t) for t in map_data['offgrid']
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
def physics_rects_around(self, pos: tuple[float, float]) -> list[pygame.Rect]:
|
|
|
|
|
rects: list[pygame.Rect] = []
|
2026-02-06 17:51:57 +00:00
|
|
|
for tile in self.tiles_around(pos):
|
2026-05-15 16:38:36 +00:00
|
|
|
if tile.type in PHYSICS_TILES:
|
|
|
|
|
rects.append(pygame.Rect(tile.pos[0] * self.tile_size, tile.pos[1] * self.tile_size, self.tile_size, self.tile_size))
|
2026-02-06 17:51:57 +00:00
|
|
|
return rects
|
2026-05-15 16:38:36 +00:00
|
|
|
|
|
|
|
|
def autotile(self):
|
2026-04-24 16:45:31 +00:00
|
|
|
for loc in self.tilemap:
|
2026-05-15 16:38:36 +00:00
|
|
|
tile: Tile = self.tilemap[loc]
|
|
|
|
|
neighbors: set[tuple[int, int]] = set()
|
2026-04-24 16:45:31 +00:00
|
|
|
for shift in [(1, 0), (-1, 0), (0, -1), (0, 1)]:
|
2026-05-15 16:38:36 +00:00
|
|
|
check_loc: str = str(tile.pos[0] + shift[0]) + ';' + str(tile.pos[1] + shift[1])
|
2026-04-24 16:45:31 +00:00
|
|
|
if check_loc in self.tilemap:
|
2026-05-15 16:38:36 +00:00
|
|
|
if self.tilemap[check_loc].type == tile.type:
|
2026-04-24 16:45:31 +00:00
|
|
|
neighbors.add(shift)
|
2026-05-15 16:38:36 +00:00
|
|
|
neighbors: tuple[tuple[int, int], ...] = tuple(sorted(neighbors))
|
|
|
|
|
if (tile.type in AUTOTILE_TYPES) and (neighbors in AUTOTILE_MAP):
|
|
|
|
|
tile.variant = AUTOTILE_MAP[neighbors]
|
|
|
|
|
|
|
|
|
|
def render(self, surface: pygame.Surface, offset: tuple[float, float] = (0, 0)) -> None:
|
|
|
|
|
for tile in self.offgrid_tiles:
|
|
|
|
|
surface.blit(
|
|
|
|
|
self.game.assets[tile.type][tile.variant],
|
|
|
|
|
(tile.pos[0] - offset[0], tile.pos[1] - offset[1])
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
for x in range(offset[0] // self.tile_size,
|
|
|
|
|
(offset[0] + surface.get_width()) // self.tile_size + 1):
|
|
|
|
|
for y in range(offset[1] // self.tile_size,
|
|
|
|
|
(offset[1] + surface.get_height()) // self.tile_size + 1):
|
|
|
|
|
location_key: str = str(x) + ';' + str(y)
|
|
|
|
|
if location_key in self.tilemap:
|
|
|
|
|
tile: Tile = self.tilemap[location_key]
|
|
|
|
|
surface.blit(
|
|
|
|
|
self.game.assets[tile.type][tile.variant],
|
|
|
|
|
(
|
|
|
|
|
tile.pos[0] * self.tile_size - offset[0],
|
|
|
|
|
tile.pos[1] * self.tile_size - offset[1])
|
|
|
|
|
)
|