fix bug
This commit is contained in:
@@ -2,11 +2,15 @@
|
||||
颜色系统
|
||||
统一管理游戏中各种等级、稀有度的颜色方案
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import Protocol
|
||||
|
||||
|
||||
class ColorGradable(Protocol):
|
||||
"""支持颜色分级的协议"""
|
||||
|
||||
@property
|
||||
def color_rgb(self) -> tuple[int, int, int]:
|
||||
"""返回RGB颜色值"""
|
||||
@@ -15,35 +19,43 @@ class ColorGradable(Protocol):
|
||||
|
||||
# ==================== 通用颜色定义 ====================
|
||||
|
||||
|
||||
class Color:
|
||||
"""预定义的颜色常量"""
|
||||
|
||||
# 基础颜色
|
||||
WHITE = (255, 255, 255)
|
||||
BLACK = (0, 0, 0)
|
||||
|
||||
|
||||
# 品质颜色 - 从低到高
|
||||
COMMON_WHITE = (255, 255, 255) # 普通/白色
|
||||
UNCOMMON_GREEN = (50, 205, 50) # 不凡/绿色
|
||||
RARE_BLUE = (74, 144, 226) # 稀有/蓝色
|
||||
EPIC_PURPLE = (147, 112, 219) # 史诗/紫色
|
||||
LEGENDARY_GOLD = (255, 215, 0) # 传说/金色
|
||||
COMMON_WHITE = (255, 255, 255) # 普通/白色
|
||||
UNCOMMON_GREEN = (50, 205, 50) # 不凡/绿色
|
||||
RARE_BLUE = (74, 144, 226) # 稀有/蓝色
|
||||
EPIC_PURPLE = (147, 112, 219) # 史诗/紫色
|
||||
LEGENDARY_GOLD = (255, 215, 0) # 传说/金色
|
||||
|
||||
|
||||
# ==================== 辅助函数 ====================
|
||||
|
||||
|
||||
COLOR_TAG_PATTERN = re.compile(
|
||||
r"<color:(\d{1,3}),(\d{1,3}),(\d{1,3})>(.*?)</color>", re.DOTALL
|
||||
)
|
||||
|
||||
|
||||
def get_color_from_mapping(
|
||||
grade_value: object,
|
||||
color_mapping: dict,
|
||||
default_color: tuple[int, int, int] = Color.COMMON_WHITE
|
||||
default_color: tuple[int, int, int] = Color.COMMON_WHITE,
|
||||
) -> tuple[int, int, int]:
|
||||
"""
|
||||
从映射字典中获取颜色
|
||||
|
||||
|
||||
Args:
|
||||
grade_value: 等级对象
|
||||
color_mapping: 等级到颜色的映射字典
|
||||
default_color: 默认颜色
|
||||
|
||||
|
||||
Returns:
|
||||
RGB颜色元组
|
||||
"""
|
||||
@@ -54,11 +66,11 @@ def format_colored_text(text: str, color_rgb: tuple[int, int, int]) -> str:
|
||||
"""
|
||||
将文本格式化为带颜色标记的字符串,供前端渲染使用
|
||||
格式:<color:R,G,B>text</color>
|
||||
|
||||
|
||||
Args:
|
||||
text: 要着色的文本
|
||||
color_rgb: RGB颜色元组
|
||||
|
||||
|
||||
Returns:
|
||||
带颜色标记的文本字符串
|
||||
"""
|
||||
@@ -66,6 +78,51 @@ def format_colored_text(text: str, color_rgb: tuple[int, int, int]) -> str:
|
||||
return f"<color:{r},{g},{b}>{text}</color>"
|
||||
|
||||
|
||||
def rgb_to_hex(color_rgb: tuple[int, int, int]) -> str:
|
||||
"""RGB 整数元组转 16 进制字符串(#rrggbb)"""
|
||||
r, g, b = color_rgb
|
||||
return f"#{r:02x}{g:02x}{b:02x}"
|
||||
|
||||
|
||||
def split_colored_segments(text: str) -> list[dict[str, str]]:
|
||||
"""
|
||||
将包含 <color> 标签的文本拆分为 segments,
|
||||
每个 segment 结构:{"text": "...", "color": "#rrggbb"}(color 可选)。
|
||||
"""
|
||||
segments: list[dict[str, str]] = []
|
||||
last_index = 0
|
||||
|
||||
for match in COLOR_TAG_PATTERN.finditer(text):
|
||||
start, end = match.span()
|
||||
if start > last_index:
|
||||
plain = text[last_index:start]
|
||||
if plain:
|
||||
segments.append({"text": plain})
|
||||
|
||||
r, g, b, content = match.groups()
|
||||
color_hex = rgb_to_hex((int(r), int(g), int(b)))
|
||||
segments.append({"text": content, "color": color_hex})
|
||||
last_index = end
|
||||
|
||||
if last_index < len(text):
|
||||
trailing = text[last_index:]
|
||||
if trailing:
|
||||
segments.append({"text": trailing})
|
||||
|
||||
if not segments:
|
||||
segments.append({"text": text})
|
||||
|
||||
return segments
|
||||
|
||||
|
||||
def serialize_hover_lines(lines: list[str]) -> list[list[dict[str, str]]]:
|
||||
"""将 hover 信息行转换为 segment 列表,供前端直接渲染颜色。"""
|
||||
serialized: list[list[dict[str, str]]] = []
|
||||
for line in lines:
|
||||
serialized.append(split_colored_segments(line or ""))
|
||||
return serialized
|
||||
|
||||
|
||||
# ==================== 颜色方案映射 ====================
|
||||
|
||||
# 装备等级颜色方案(普通-宝物-法宝)
|
||||
|
||||
@@ -2,7 +2,8 @@ import sys
|
||||
import os
|
||||
import asyncio
|
||||
from contextlib import asynccontextmanager
|
||||
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
|
||||
from typing import List
|
||||
from fastapi import FastAPI, WebSocket, WebSocketDisconnect, HTTPException, Query
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
import uvicorn
|
||||
@@ -17,6 +18,8 @@ from src.run.create_map import create_cultivation_world_map, add_sect_headquarte
|
||||
from src.sim.new_avatar import make_avatars as _new_make
|
||||
from src.utils.config import CONFIG
|
||||
from src.classes.sect import sects_by_id
|
||||
from src.classes.color import serialize_hover_lines
|
||||
from src.classes.event import Event
|
||||
import random
|
||||
|
||||
# 全局游戏实例
|
||||
@@ -48,6 +51,46 @@ class ConnectionManager:
|
||||
|
||||
manager = ConnectionManager()
|
||||
|
||||
|
||||
def serialize_events_for_client(events: List[Event]) -> List[dict]:
|
||||
"""将事件转换为前端可用的结构。"""
|
||||
serialized: List[dict] = []
|
||||
for idx, event in enumerate(events):
|
||||
month_stamp = getattr(event, "month_stamp", None)
|
||||
stamp_int = None
|
||||
year = None
|
||||
month = None
|
||||
if month_stamp is not None:
|
||||
try:
|
||||
stamp_int = int(month_stamp)
|
||||
except Exception:
|
||||
stamp_int = None
|
||||
try:
|
||||
year = int(month_stamp.get_year())
|
||||
except Exception:
|
||||
year = None
|
||||
try:
|
||||
month_obj = month_stamp.get_month()
|
||||
month = int(getattr(month_obj, "value", month_obj))
|
||||
except Exception:
|
||||
month = None
|
||||
|
||||
related_raw = getattr(event, "related_avatars", None) or []
|
||||
related_ids = [str(a) for a in related_raw if a is not None]
|
||||
|
||||
serialized.append({
|
||||
"id": getattr(event, "event_id", None) or f"{stamp_int or 'evt'}-{idx}",
|
||||
"text": str(event),
|
||||
"content": getattr(event, "content", ""),
|
||||
"year": year,
|
||||
"month": month,
|
||||
"month_stamp": stamp_int,
|
||||
"related_avatar_ids": related_ids,
|
||||
"is_major": bool(getattr(event, "is_major", False)),
|
||||
"is_story": bool(getattr(event, "is_story", False)),
|
||||
})
|
||||
return serialized
|
||||
|
||||
def init_game():
|
||||
"""初始化游戏世界,逻辑复用自 src/run/run.py"""
|
||||
print("正在初始化游戏世界...")
|
||||
@@ -98,7 +141,7 @@ async def game_loop():
|
||||
"type": "tick",
|
||||
"year": int(world.month_stamp.get_year()),
|
||||
"month": world.month_stamp.get_month().value,
|
||||
"events": [str(e) for e in events],
|
||||
"events": serialize_events_for_client(events),
|
||||
# 暂时只发前 50 个角色的位置更新,减少数据量
|
||||
"avatars": [
|
||||
{
|
||||
@@ -212,12 +255,21 @@ def get_state():
|
||||
except Exception as e:
|
||||
return {"step": 3, "error": str(e)}
|
||||
|
||||
recent_events = []
|
||||
try:
|
||||
event_manager = getattr(world, "event_manager", None)
|
||||
if event_manager:
|
||||
recent_events = serialize_events_for_client(event_manager.get_recent_events(limit=50))
|
||||
except Exception:
|
||||
recent_events = []
|
||||
|
||||
return {
|
||||
"status": "ok",
|
||||
"year": y,
|
||||
"month": m,
|
||||
"avatar_count": len(world.avatar_manager.avatars),
|
||||
"avatars": av_list
|
||||
"avatars": av_list,
|
||||
"events": recent_events
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
@@ -285,6 +337,43 @@ async def step_world():
|
||||
"events_sample": [str(e) for e in events[:5]]
|
||||
}
|
||||
|
||||
@app.get("/api/hover")
|
||||
def get_hover_info(
|
||||
target_type: str = Query(alias="type"),
|
||||
target_id: str = Query(alias="id")
|
||||
):
|
||||
world = game_instance.get("world")
|
||||
if world is None:
|
||||
raise HTTPException(status_code=503, detail="World not initialized")
|
||||
|
||||
target = None
|
||||
if target_type == "avatar":
|
||||
target = world.avatar_manager.avatars.get(target_id)
|
||||
elif target_type == "region":
|
||||
if world.map and hasattr(world.map, "regions"):
|
||||
regions = world.map.regions
|
||||
target = regions.get(target_id)
|
||||
if target is None:
|
||||
try:
|
||||
target = regions.get(int(target_id))
|
||||
except (ValueError, TypeError):
|
||||
target = None
|
||||
else:
|
||||
raise HTTPException(status_code=400, detail="Unsupported target type")
|
||||
|
||||
if target is None:
|
||||
raise HTTPException(status_code=404, detail="Target not found")
|
||||
if not hasattr(target, "get_hover_info"):
|
||||
raise HTTPException(status_code=422, detail="Target has no hover info")
|
||||
|
||||
lines = target.get_hover_info() or []
|
||||
return {
|
||||
"id": target_id,
|
||||
"type": target_type,
|
||||
"name": getattr(target, "name", target_id),
|
||||
"lines": serialize_hover_lines([str(line) for line in lines]),
|
||||
}
|
||||
|
||||
def start():
|
||||
"""启动服务的入口函数"""
|
||||
# 改为 8002 端口
|
||||
|
||||
Reference in New Issue
Block a user