update map

This commit is contained in:
bridge
2025-10-09 01:05:34 +08:00
parent 3095f18303
commit 2e2b1a0dae
8 changed files with 136 additions and 29 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 49 KiB

View File

@@ -7,7 +7,7 @@ from src.classes.avatar import Avatar, Gender
from .theme import COLORS
from .fonts import create_font, get_region_font as _get_region_font_cached
from .assets import load_tile_images, load_avatar_images, load_sect_images
from .assets import load_tile_images, load_avatar_images, load_sect_images, load_region_images
from .rendering import (
draw_map,
draw_region_labels,
@@ -65,6 +65,7 @@ class Front:
self.tile_images = load_tile_images(self.pygame, self.tile_size)
self.sect_images = load_sect_images(self.pygame, self.tile_size)
self.region_images = load_region_images(self.pygame, self.tile_size)
self.male_avatars, self.female_avatars = load_avatar_images(self.pygame, self.tile_size)
self.avatar_images: Dict[str, object] = {}
self._assign_avatar_images()
@@ -132,8 +133,9 @@ class Front:
self.margin,
STATUS_BAR_HEIGHT,
)
# 底图后叠加宗门总部图层2x2
from .rendering import draw_sect_headquarters
# 底图后叠加小区域整图2x2/3x3再绘制宗门总部避免被覆盖
from .rendering import draw_sect_headquarters, draw_small_regions
draw_small_regions(pygame, self.screen, self.world, self.region_images, self.tile_images, self.tile_size, self.margin, STATUS_BAR_HEIGHT)
draw_sect_headquarters(pygame, self.screen, self.world, self.sect_images, self.tile_size, self.margin, STATUS_BAR_HEIGHT)
hovered_region = draw_region_labels(
pygame,

View File

@@ -70,6 +70,34 @@ def load_sect_images(pygame_mod, tile_size: int):
return images
__all__ = ["load_tile_images", "load_avatar_images", "load_sect_images"]
def load_region_images(pygame_mod, tile_size: int) -> Dict[str, Dict[int, object]]:
"""
加载小区域整图:按名称加载 assets/regions/<name>.png。
为兼容 2x2 和 3x3分别生成两种缩放版本
- key 2 -> (tile_size*2, tile_size*2)
- key 3 -> (tile_size*3, tile_size*3)
返回结构: { name: {2: surf2x2, 3: surf3x3} }
"""
results: Dict[str, Dict[int, object]] = {}
base_dir = Path("assets/regions")
if base_dir.exists():
for filename in base_dir.iterdir():
if filename.suffix.lower() != ".png" or filename.name == "original.png":
continue
try:
image = pygame_mod.image.load(str(filename))
except pygame_mod.error:
continue
name_key = filename.stem
variants: Dict[int, object] = {}
for n in (2, 3):
w = tile_size * n
h = tile_size * n
variants[n] = pygame_mod.transform.scale(image, (w, h))
results[name_key] = variants
return results
__all__ = ["load_tile_images", "load_avatar_images", "load_sect_images", "load_region_images"]

View File

@@ -66,10 +66,72 @@ def draw_sect_headquarters(pygame_mod, screen, world, sect_images: dict, ts: int
screen.blit(image, (x_px, y_px))
def _is_small_square_region(region) -> int:
"""
若为 2x2 或 3x3 的矩形/正方形区域返回边长2或3否则返回0。
"""
try:
nw = tuple(map(int, str(getattr(region, "north_west_cor", "0,0")).split(",")))
se = tuple(map(int, str(getattr(region, "south_east_cor", "0,0")).split(",")))
except Exception:
return 0
if getattr(region, "shape", None) is None:
return 0
shape_name = getattr(region.shape, "name", "")
if shape_name not in ("RECTANGLE", "SQUARE"):
return 0
width = se[0] - nw[0] + 1
height = se[1] - nw[1] + 1
if width == height and width in (2, 3):
return width
return 0
def draw_small_regions(pygame_mod, screen, world, region_images: dict, tile_images: dict, ts: int, m: int, top_offset: int = 0):
"""
使用整图绘制 2x2 / 3x3 的小区域:
- 优先按名称从 region_images 中取 n×n 的整图n 为 2 或 3
- 若没有整图,则将现有 tile 图裁切/合成为一张,避免重复边框
"""
for region in world.map.regions.values():
n = _is_small_square_region(region)
if n == 0:
continue
# 仅对 2x2 生效3x3 不覆盖(保持每格一张图)
if n != 2:
continue
try:
nw = tuple(map(int, str(getattr(region, "north_west_cor", "0,0")).split(",")))
except Exception:
continue
x_px = m + nw[0] * ts
y_px = m + top_offset + nw[1] * ts
name_key = str(getattr(region, "name", ""))
variants = region_images.get(name_key)
if variants and variants.get(n):
screen.blit(variants[n], (x_px, y_px))
continue
# 回退:直接将该区域左上角 tile 的贴图放大为 n×n 覆盖(只用一张图,而不是四/九张)
try:
tile = world.map.get_tile(nw[0], nw[1])
base_image = tile_images.get(tile.type)
except Exception:
base_image = None
if base_image is not None:
scaled = pygame_mod.transform.scale(base_image, (ts * n, ts * n))
screen.blit(scaled, (x_px, y_px))
else:
# 最后兜底:淡色块
tmp = pygame_mod.Surface((ts * n, ts * n), pygame_mod.SRCALPHA)
tmp.fill((255, 255, 255, 24))
screen.blit(tmp, (x_px, y_px))
def calculate_font_size_by_area(tile_size: int, area: int) -> int:
base = int(tile_size * 1.1)
growth = int(max(0, min(24, (area ** 0.5))))
return max(16, min(40, base + growth))
size = base + growth - 7 # 再降低2个字号
return max(16, min(40, size))
def draw_region_labels(pygame_mod, screen, colors, world, get_region_font, tile_size: int, margin: int, top_offset: int = 0, outline_px: int = 2):
@@ -101,8 +163,9 @@ def draw_region_labels(pygame_mod, screen, colors, world, get_region_font, tile_
name = getattr(region, "name", None)
if not name:
continue
# 以“区域最下缘的中点”为锚点(优先放在区域下方)
if getattr(region, "cors", None):
# 小区域(面积<=9例如2x2/3x3标签放在底部大区域放在中心
use_bottom = getattr(region, "area", 0) <= 9
if use_bottom and getattr(region, "cors", None):
bottom_y = max(y for _, y in region.cors)
xs_on_bottom = [x for x, y in region.cors if y == bottom_y]
if xs_on_bottom:
@@ -111,14 +174,12 @@ def draw_region_labels(pygame_mod, screen, colors, world, get_region_font, tile_
anchor_cx_tile = (left_x + right_x) / 2.0
else:
anchor_cx_tile = float(region.center_loc[0])
screen_cx = int(m + anchor_cx_tile * ts + ts // 2)
screen_cy = int(m + top_offset + (bottom_y + 1) * ts + 2)
else:
# 兜底使用中心点
anchor_cx_tile = float(region.center_loc[0])
bottom_y = int(region.center_loc[1])
screen_cx = int(m + anchor_cx_tile * ts + ts // 2)
# 锚点Y取区域底边像素的下一行再加少量间距
screen_cy = int(m + top_offset + (bottom_y + 1) * ts + 2)
# 居中放置
screen_cx = int(m + float(region.center_loc[0]) * ts + ts // 2)
screen_cy = int(m + top_offset + float(region.center_loc[1]) * ts)
font_size = calculate_font_size_by_area(tile_size, region.area)
region_font = get_region_font(font_size)
text_surface = region_font.render(str(name), True, colors["text"])

View File

@@ -82,10 +82,25 @@ def add_sect_headquarters(game_map: Map, enabled_sects: list[Sect]):
base_w, base_h = BASE_W, BASE_H
size_w = se[0] - nw[0]
size_h = se[1] - nw[1]
nw_x = max(0, min(game_map.width - 1, _scale_x(nw[0], game_map.width)))
nw_y = max(0, min(game_map.height - 1, _scale_y(nw[1], game_map.height)))
se_x = max(nw_x, min(game_map.width - 1, nw_x + size_w))
se_y = max(nw_y, min(game_map.height - 1, nw_y + size_h))
# 初步缩放坐标
nw_x = _scale_x(nw[0], game_map.width)
nw_y = _scale_y(nw[1], game_map.height)
se_x = nw_x + size_w
se_y = nw_y + size_h
# 边界修正:确保 2x2 或 1x2 等固定尺寸完整在图内
if se_x >= game_map.width:
shift = se_x - (game_map.width - 1)
nw_x -= shift
se_x -= shift
if se_y >= game_map.height:
shift = se_y - (game_map.height - 1)
nw_y -= shift
se_y -= shift
# 最终夹紧
nw_x = max(0, min(game_map.width - 1, nw_x))
nw_y = max(0, min(game_map.height - 1, nw_y))
se_x = max(nw_x, min(game_map.width - 1, se_x))
se_y = max(nw_y, min(game_map.height - 1, se_y))
region = SectRegion(
id=400 + sect.id,
name=hq_name,
@@ -213,11 +228,11 @@ def _create_2x2_wuxing_caves(game_map: Map):
"""创建2*2的五行洞府区域"""
# 五行洞府配置:金木水火土
wuxing_caves = [
{"name": "太白金府", "base_x": _scale_x(26, game_map.width), "base_y": _scale_y(12, game_map.height), "element": EssenceType.GOLD, "description": "青峰山脉深处的金行洞府"},
{"name": "太白金府", "base_x": _scale_x(24, game_map.width), "base_y": _scale_y(12, game_map.height), "element": EssenceType.GOLD, "description": "青峰山脉深处的金行洞府"},
{"name": "青木洞天", "base_x": _scale_x(48, game_map.width), "base_y": _scale_y(18, game_map.height), "element": EssenceType.WOOD, "description": "青云林海中的木行洞府"},
{"name": "玄水秘境", "base_x": _scale_x(67, game_map.width), "base_y": _scale_y(25, game_map.height), "element": EssenceType.WATER, "description": "无边碧海深处的水行洞府"},
{"name": "离火洞府", "base_x": _scale_x(48, game_map.width), "base_y": _scale_y(33, game_map.height), "element": EssenceType.FIRE, "description": "炎狱火山旁的火行洞府"},
{"name": "厚土玄宫", "base_x": _scale_x(30, game_map.width), "base_y": _scale_y(16, game_map.height), "element": EssenceType.EARTH, "description": "青峰山脉的土行洞府"}
{"name": "厚土玄宫", "base_x": _scale_x(32, game_map.width), "base_y": _scale_y(16, game_map.height), "element": EssenceType.EARTH, "description": "青峰山脉的土行洞府"}
]
for cave in wuxing_caves:
@@ -281,6 +296,12 @@ def _scale_loaded_regions(game_map: Map) -> None:
new_nw_y = max(0, min(height - 1, _scale_y(nw_y, height)))
new_se_x = max(new_nw_x, min(width - 1, _scale_x(se_x, width)))
new_se_y = max(new_nw_y, min(height - 1, _scale_y(se_y, height)))
# 夹紧到地图范围
new_nw_x = max(0, min(width - 1, new_nw_x))
new_se_x = max(new_nw_x, min(width - 1, new_se_x))
new_nw_y = max(0, min(height - 1, new_nw_y))
new_se_y = max(new_nw_y, min(height - 1, new_se_y))
params = {
"id": region.id,
"name": region.name,

View File

@@ -150,14 +150,9 @@ def parse_llm_response(res: str) -> dict:
pass
# 3) 整体 json5 兜底
try:
obj = json5.loads(res)
if isinstance(obj, dict):
return obj
except Exception:
pass
obj = json5.loads(res)
return obj
raise ValueError("无法从LLM响应中解析出有效的JSON字典对象")
def get_prompt_and_call_llm(template_path: Path, infos: dict, mode="normal") -> str:
"""

View File

@@ -1,9 +1,9 @@
id,name,desc,shape,north-west-cor,south-east-cor,root_type,root_density
"ID必须以2开头",,,,,,,
201,太白金府,青峰山脉深处的金行洞府,金精气凝,刀剑鸣音不绝,乃金系修士的最高圣地。,square,"26,12","27,13",,10
201,太白金府,青峰山脉深处的金行洞府,金精气凝,刀剑鸣音不绝,乃金系修士的最高圣地。,square,"24,12","25,13",,10
202,青木洞天,青云林海中的木行洞府,生机盎然,灵药遍地,乃木系修士的最高圣地。,square,"48,18","49,19",,10
203,玄水秘境,无边碧海深处的水行洞府,碧波万里,水精凝神,乃水系修士的最高圣地。,square,"67,25","68,26",,10
204,离火洞府,炎狱火山旁的火行洞府,烈焰冲天,真火精纯,乃火系修士的最高圣地。,square,"48,33","49,34",,10
205,厚土玄宫,青峰山脉的土行洞府,厚德载物,山岳共鸣,乃土系修士的最高圣地。,square,"30,16","31,17",,10
205,厚土玄宫,青峰山脉的土行洞府,厚德载物,山岳共鸣,乃土系修士的最高圣地。,square,"32,16","33,17",,10
206,古越遗迹,雨林深处的上古遗迹,古藤缠绕,木行灵气与金行灵气交融。蕴藏古老功法与灵药配方。,square,"25,40","26,41",,8
207,沧海遗迹,沉没在海中的远古文明遗迹,水行灵气浓郁,潮汐间偶有宝物现世。,square,"66,47","67,48",,9
1 id name desc shape north-west-cor south-east-cor root_type root_density
2 ID必须以2开头
3 201 太白金府 青峰山脉深处的金行洞府,金精气凝,刀剑鸣音不绝,乃金系修士的最高圣地。 square 26,12 24,12 27,13 25,13 10
4 202 青木洞天 青云林海中的木行洞府,生机盎然,灵药遍地,乃木系修士的最高圣地。 square 48,18 49,19 10
5 203 玄水秘境 无边碧海深处的水行洞府,碧波万里,水精凝神,乃水系修士的最高圣地。 square 67,25 68,26 10
6 204 离火洞府 炎狱火山旁的火行洞府,烈焰冲天,真火精纯,乃火系修士的最高圣地。 square 48,33 49,34 10
7 205 厚土玄宫 青峰山脉的土行洞府,厚德载物,山岳共鸣,乃土系修士的最高圣地。 square 30,16 32,16 31,17 33,17 10
8 206 古越遗迹 雨林深处的上古遗迹,古藤缠绕,木行灵气与金行灵气交融。蕴藏古老功法与灵药配方。 square 25,40 26,41 8
9 207 沧海遗迹 沉没在海中的远古文明遗迹,水行灵气浓郁,潮汐间偶有宝物现世。 square 66,47 67,48 9