Files
cultivation-world-simulator/src/front/rendering.py
2025-10-04 23:10:37 +08:00

258 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import math
from typing import List, Optional, Tuple, Callable
from src.classes.avatar import Avatar, Gender
from src.classes.tile import TileType
from src.utils.text_wrap import wrap_text
from src.classes.relation import Relation
# 顶部状态栏高度(像素)
STATUS_BAR_HEIGHT = 32
def draw_grid(pygame_mod, screen, colors, map_obj, ts: int, m: int, top_offset: int = 0):
grid_color = colors["grid"]
for gx in range(map_obj.width + 1):
start_pos = (m + gx * ts, m + top_offset)
end_pos = (m + gx * ts, m + top_offset + map_obj.height * ts)
pygame_mod.draw.line(screen, grid_color, start_pos, end_pos, 1)
for gy in range(map_obj.height + 1):
start_pos = (m, m + top_offset + gy * ts)
end_pos = (m + map_obj.width * ts, m + top_offset + gy * ts)
pygame_mod.draw.line(screen, grid_color, start_pos, end_pos, 1)
def draw_map(pygame_mod, screen, colors, world, tile_images, ts: int, m: int, top_offset: int = 0):
map_obj = world.map
for y in range(map_obj.height):
for x in range(map_obj.width):
tile = map_obj.get_tile(x, y)
tile_image = tile_images.get(tile.type)
if tile_image:
pos = (m + x * ts, m + top_offset + y * ts)
screen.blit(tile_image, pos)
else:
color = (80, 80, 80)
rect = pygame_mod.Rect(m + x * ts, m + top_offset + y * ts, ts, ts)
pygame_mod.draw.rect(screen, color, rect)
draw_grid(pygame_mod, screen, colors, map_obj, ts, m, top_offset)
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))
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):
ts = tile_size
m = margin
mouse_x, mouse_y = pygame_mod.mouse.get_pos()
from src.classes.region import regions_by_id
hovered_region = None
for region in regions_by_id.values():
name = getattr(region, "name", None)
if not name:
continue
center_x, center_y = region.center_loc
screen_x = m + center_x * ts + ts // 2
screen_y = m + top_offset + center_y * ts + ts // 2
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"])
border_surface = region_font.render(str(name), True, colors.get("text_border", (24, 24, 24)))
text_w = text_surface.get_width()
text_h = text_surface.get_height()
x = int(screen_x - text_w / 2)
y = int(screen_y - text_h / 2)
if (x <= mouse_x <= x + text_w and y <= mouse_y <= y + text_h):
hovered_region = region
# 多方向描边
if outline_px > 0:
for dx in (-outline_px, 0, outline_px):
for dy in (-outline_px, 0, outline_px):
if dx == 0 and dy == 0:
continue
screen.blit(border_surface, (x + dx, y + dy))
screen.blit(text_surface, (x, y))
return hovered_region
def avatar_center_pixel(avatar: Avatar, tile_size: int, margin: int, top_offset: int = 0) -> Tuple[int, int]:
px = margin + avatar.pos_x * tile_size + tile_size // 2
py = margin + top_offset + avatar.pos_y * tile_size + tile_size // 2
return px, py
def draw_avatars_and_pick_hover(
pygame_mod,
screen,
colors,
simulator,
avatar_images,
tile_size: int,
margin: int,
get_display_center: Optional[Callable[[Avatar, int, int], Tuple[float, float]]] = None,
top_offset: int = 0,
) -> Optional[Avatar]:
mouse_x, mouse_y = pygame_mod.mouse.get_pos()
hovered = None
min_dist = float("inf")
for avatar_id, avatar in simulator.world.avatar_manager.avatars.items():
if get_display_center is not None:
cx_f, cy_f = get_display_center(avatar, tile_size, margin)
cx, cy = int(cx_f), int(cy_f)
else:
cx, cy = avatar_center_pixel(avatar, tile_size, margin)
cy += top_offset
avatar_image = avatar_images.get(avatar_id)
if avatar_image:
image_rect = avatar_image.get_rect()
image_x = cx - image_rect.width // 2
image_y = cy - image_rect.height // 2
screen.blit(avatar_image, (image_x, image_y))
if image_rect.collidepoint(mouse_x - image_x, mouse_y - image_y):
hovered = avatar
min_dist = 0
else:
radius = max(8, tile_size // 3)
pygame_mod.draw.circle(screen, colors["avatar"], (cx, cy), radius)
dist = math.hypot(mouse_x - cx, mouse_y - cy)
if dist <= radius and dist < min_dist:
hovered = avatar
min_dist = dist
return hovered
def draw_tooltip(pygame_mod, screen, colors, lines: List[str], mouse_x: int, mouse_y: int, font, min_width: Optional[int] = None):
padding = 6
spacing = 2
surf_lines = [font.render(t, True, colors["text"]) for t in lines]
width = max(s.get_width() for s in surf_lines) + padding * 2
if min_width is not None:
width = max(width, min_width)
height = sum(s.get_height() for s in surf_lines) + padding * 2 + spacing * (len(surf_lines) - 1)
x = mouse_x + 12
y = mouse_y + 12
screen_w, screen_h = screen.get_size()
if x + width > screen_w:
x = mouse_x - width - 12
if y + height > screen_h:
y = mouse_y - height - 12
bg_rect = pygame_mod.Rect(x, y, width, height)
pygame_mod.draw.rect(screen, colors["tooltip_bg"], bg_rect, border_radius=6)
pygame_mod.draw.rect(screen, colors["tooltip_bd"], bg_rect, 1, border_radius=6)
cursor_y = y + padding
for s in surf_lines:
screen.blit(s, (x + padding, cursor_y))
cursor_y += s.get_height() + spacing
def draw_tooltip_for_avatar(pygame_mod, screen, colors, font, avatar: Avatar):
lines = [
f"{avatar.name}",
f"性别: {avatar.gender}",
f"年龄: {avatar.age}",
f"阵营: {avatar.alignment}",
f"境界: {str(avatar.cultivation_progress)}",
f"HP: {avatar.hp}",
f"MP: {avatar.mp}",
f"灵根: {str(avatar.root)}",
f"功法: {avatar.technique.name}{avatar.technique.attribute}·{avatar.technique.grade.value}",
f"个性: {', '.join([persona.name for persona in avatar.personas])}",
f"位置: ({avatar.pos_x}, {avatar.pos_y})",
]
lines.append(f"灵石: {str(avatar.magic_stone)}")
if avatar.items:
lines.append("物品:")
for item, quantity in avatar.items.items():
lines.append(f" {item.name} x{quantity}")
else:
lines.append("")
lines.append("物品: 无")
if avatar.thinking:
lines.append("")
lines.append("思考:")
thinking_lines = wrap_text(avatar.thinking, 28)
lines.extend(thinking_lines)
if getattr(avatar, "objective", None):
lines.append("")
lines.append("目标:")
objective_lines = wrap_text(avatar.objective, 28)
lines.extend(objective_lines)
# 关系信息
relations_list = [f"{other.name}({str(relation)})" for other, relation in getattr(avatar, "relations", {}).items()]
lines.append("")
if relations_list:
lines.append("关系:")
for s in relations_list[:6]:
lines.append(f" {s}")
else:
lines.append("关系: 无")
draw_tooltip(pygame_mod, screen, colors, lines, *pygame_mod.mouse.get_pos(), font, min_width=260)
def draw_tooltip_for_region(pygame_mod, screen, colors, font, region, mouse_x: int, mouse_y: int):
if region is None:
return
lines = [
f"区域: {region.name}",
f"描述: {region.desc}",
]
from src.classes.region import CultivateRegion, NormalRegion
if isinstance(region, CultivateRegion):
stars = "" * region.essence_density + "" * (10 - region.essence_density)
lines.append(f"主要灵气: {region.essence_type} {stars}")
elif isinstance(region, NormalRegion):
species_info = region.get_species_info()
if species_info and species_info != "暂无特色物种":
lines.append("物种分布:")
for species in species_info.split("; "):
lines.append(f" {species}")
else:
lines.append("物种分布: 暂无特色物种")
draw_tooltip(pygame_mod, screen, colors, lines, mouse_x, mouse_y, font)
def draw_operation_guide(pygame_mod, screen, colors, font, margin: int, auto_step: bool):
auto_status = "" if auto_step else ""
guide_text = f"A:自动步进({auto_status}) SPACE:单步 ESC:退出"
guide_surf = font.render(guide_text, True, colors["status_text"])
x_pos = margin + 8
screen.blit(guide_surf, (x_pos, 8))
return guide_surf.get_width()
def draw_year_month_info(pygame_mod, screen, colors, font, margin: int, guide_width: int, world):
year = int(world.month_stamp.get_year())
month_num = world.month_stamp.get_month().value
ym_text = f"{year}{month_num:02d}"
ym_surf = font.render(ym_text, True, colors["status_text"])
x_pos = margin + guide_width + 8 * 3
screen.blit(ym_surf, (x_pos, 8))
def draw_status_bar(pygame_mod, screen, colors, font, margin: int, world, auto_step: bool):
status_y = 8
status_height = STATUS_BAR_HEIGHT
status_rect = pygame_mod.Rect(0, 0, screen.get_width(), status_height)
pygame_mod.draw.rect(screen, colors["status_bg"], status_rect)
pygame_mod.draw.line(screen, colors["status_border"],
(0, status_height), (screen.get_width(), status_height), 2)
guide_w = draw_operation_guide(pygame_mod, screen, colors, font, margin, auto_step)
draw_year_month_info(pygame_mod, screen, colors, font, margin, guide_w, world)
__all__ = [
"draw_map",
"draw_region_labels",
"draw_avatars_and_pick_hover",
"draw_tooltip_for_avatar",
"draw_tooltip_for_region",
"draw_status_bar",
"STATUS_BAR_HEIGHT",
]