refactor map
This commit is contained in:
@@ -19,27 +19,19 @@ class Map():
|
||||
# key: region.id, value: list[(x, y)]
|
||||
self.region_cors: dict[int, list[tuple[int, int]]] = {}
|
||||
|
||||
# 加载所有region数据到Map中
|
||||
self._load_regions()
|
||||
|
||||
def _load_regions(self):
|
||||
"""从配置文件加载所有区域数据到Map实例中"""
|
||||
# 延迟导入避免循环导入
|
||||
from src.classes.region import regions_by_id, regions_by_name
|
||||
from src.classes.region import normal_regions_by_id, normal_regions_by_name
|
||||
from src.classes.region import cultivate_regions_by_id, cultivate_regions_by_name
|
||||
from src.classes.region import city_regions_by_id, city_regions_by_name
|
||||
# 区域字典,由外部加载器 (load_map.py) 填充
|
||||
self.regions = {}
|
||||
self.region_names = {}
|
||||
self.sect_regions = {}
|
||||
|
||||
self.regions = regions_by_id
|
||||
self.region_names = regions_by_name
|
||||
self.normal_regions = normal_regions_by_id
|
||||
self.normal_region_names = normal_regions_by_name
|
||||
self.cultivate_regions = cultivate_regions_by_id
|
||||
self.cultivate_region_names = cultivate_regions_by_name
|
||||
self.city_regions = city_regions_by_id
|
||||
self.city_region_names = city_regions_by_name
|
||||
# 宗门总部区域集合(由地图生成阶段注入),保持与其他区域一致的“提前维护”策略
|
||||
self.update_sect_regions()
|
||||
# 这些分类字典可能暂时不再自动维护,或者需要 load_map.py 手动维护
|
||||
# 为了兼容性,先初始化为空
|
||||
self.normal_regions = {}
|
||||
self.normal_region_names = {}
|
||||
self.cultivate_regions = {}
|
||||
self.cultivate_region_names = {}
|
||||
self.city_regions = {}
|
||||
self.city_region_names = {}
|
||||
|
||||
def update_sect_regions(self) -> None:
|
||||
"""根据当前 self.regions 动态刷新宗门总部区域字典。"""
|
||||
@@ -91,19 +83,24 @@ class Map():
|
||||
def get_info(self, detailed: bool = False) -> dict:
|
||||
"""
|
||||
返回地图信息(dict)。
|
||||
- detailed=False:各类区域返回名称列表
|
||||
- detailed=True:各类区域返回详细信息字符串列表
|
||||
"""
|
||||
# 动态分类(因为现在没有自动分类字典了)
|
||||
# 或者我们简单点,不分类返回,只返回总览?
|
||||
# 为了保持接口不变,我们可以现场过滤。
|
||||
|
||||
from src.classes.region import NormalRegion, CultivateRegion, CityRegion, SectRegion
|
||||
|
||||
def filter_regions(cls):
|
||||
return {rid: r for rid, r in self.regions.items() if isinstance(r, cls)}
|
||||
|
||||
def build_regions_info(regions_dict) -> list[str]:
|
||||
if detailed:
|
||||
return [r.get_detailed_info() for r in regions_dict.values()]
|
||||
return [r.get_info() for r in regions_dict.values()]
|
||||
|
||||
return {
|
||||
"修炼区域(可以修炼以增进修为)": build_regions_info(self.cultivate_regions),
|
||||
"普通区域(可以狩猎或采集)": build_regions_info(self.normal_regions),
|
||||
"城市区域(可以交易)": build_regions_info(self.city_regions),
|
||||
"宗门总部(宗门弟子可在此进行疗伤等操作)": build_regions_info(self.sect_regions),
|
||||
"修炼区域(可以修炼以增进修为)": build_regions_info(filter_regions(CultivateRegion)),
|
||||
"普通区域(可以狩猎或采集)": build_regions_info(filter_regions(NormalRegion)),
|
||||
"城市区域(可以交易)": build_regions_info(filter_regions(CityRegion)),
|
||||
"宗门总部(宗门弟子可在此进行疗伤等操作)": build_regions_info(filter_regions(SectRegion)),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -14,15 +14,10 @@ from src.classes.sect import sects_by_name
|
||||
def get_tiles_from_shape(shape: 'Shape', north_west_cor: str, south_east_cor: str) -> list[tuple[int, int]]:
|
||||
"""
|
||||
根据形状和两个角点坐标,计算出对应的所有坐标点
|
||||
|
||||
Args:
|
||||
shape: 区域形状
|
||||
north_west_cor: 西北角坐标,格式: "x,y"
|
||||
south_east_cor: 东南角坐标,格式: "x,y"
|
||||
|
||||
Returns:
|
||||
所有坐标点的列表
|
||||
"""
|
||||
if not north_west_cor or not south_east_cor:
|
||||
return []
|
||||
|
||||
nw_coords = tuple(map(int, north_west_cor.split(',')))
|
||||
se_coords = tuple(map(int, south_east_cor.split(',')))
|
||||
|
||||
@@ -32,66 +27,46 @@ def get_tiles_from_shape(shape: 'Shape', north_west_cor: str, south_east_cor: st
|
||||
coordinates = []
|
||||
|
||||
if shape == Shape.SQUARE or shape == Shape.RECTANGLE:
|
||||
# 正方形和长方形:填充整个矩形区域
|
||||
for x in range(min_x, max_x + 1):
|
||||
for y in range(min_y, max_y + 1):
|
||||
coordinates.append((x, y))
|
||||
|
||||
elif shape == Shape.MEANDERING:
|
||||
# 蜿蜒形状(如河流):创建一条从西北到东南的蜿蜒路径
|
||||
# 计算河流的宽度(根据距离动态调整)
|
||||
distance_x = max_x - min_x
|
||||
distance_y = max_y - min_y
|
||||
total_distance = max(distance_x, distance_y)
|
||||
|
||||
# 河流宽度:距离越长,河流越宽
|
||||
if total_distance < 10:
|
||||
width = 1
|
||||
elif total_distance < 30:
|
||||
width = 2
|
||||
else:
|
||||
width = 3
|
||||
if total_distance < 10: width = 1
|
||||
elif total_distance < 30: width = 2
|
||||
else: width = 3
|
||||
|
||||
# 生成中心路径点
|
||||
path_points = []
|
||||
if distance_x >= distance_y:
|
||||
# 主要沿X轴方向流动
|
||||
for x in range(min_x, max_x + 1):
|
||||
# 计算对应的y坐标,添加一些蜿蜒效果
|
||||
progress = (x - min_x) / max(distance_x, 1)
|
||||
base_y = min_y + int(progress * distance_y)
|
||||
|
||||
# 添加蜿蜒效果:使用简单的正弦波
|
||||
import math
|
||||
wave_amplitude = min(3, distance_y // 4) if distance_y > 0 else 0
|
||||
wave_y = int(wave_amplitude * math.sin(progress * math.pi * 2))
|
||||
y = max(min_y, min(max_y, base_y + wave_y))
|
||||
|
||||
path_points.append((x, y))
|
||||
else:
|
||||
# 主要沿Y轴方向流动
|
||||
for y in range(min_y, max_y + 1):
|
||||
progress = (y - min_y) / max(distance_y, 1)
|
||||
base_x = min_x + int(progress * distance_x)
|
||||
|
||||
# 添加蜿蜒效果
|
||||
import math
|
||||
wave_amplitude = min(3, distance_x // 4) if distance_x > 0 else 0
|
||||
wave_x = int(wave_amplitude * math.sin(progress * math.pi * 2))
|
||||
x = max(min_x, min(max_x, base_x + wave_x))
|
||||
|
||||
path_points.append((x, y))
|
||||
|
||||
# 为每个路径点添加宽度
|
||||
for px, py in path_points:
|
||||
for dx in range(-width//2, width//2 + 1):
|
||||
for dy in range(-width//2, width//2 + 1):
|
||||
nx, ny = px + dx, py + dy
|
||||
# 确保在边界内
|
||||
if min_x <= nx <= max_x and min_y <= ny <= max_y:
|
||||
coordinates.append((nx, ny))
|
||||
|
||||
# 去重并排序
|
||||
return sorted(list(set(coordinates)))
|
||||
|
||||
|
||||
@@ -99,34 +74,36 @@ def get_tiles_from_shape(shape: 'Shape', north_west_cor: str, south_east_cor: st
|
||||
class Region(ABC):
|
||||
"""
|
||||
区域抽象基类
|
||||
理想中,一些地块应当在一起组成一个区域。
|
||||
比如,某山;某湖、江、海;某森林;某平原;某城市;
|
||||
一些分布,比如物产,按照Region来分布。
|
||||
再比如,灵气,应当也是按照region分布的。
|
||||
默认,一个region内部的属性,是共通的。
|
||||
同时,NPC应当对Region有观测和认知。
|
||||
"""
|
||||
id: int
|
||||
name: str
|
||||
desc: str
|
||||
shape: 'Shape'
|
||||
north_west_cor: str # 西北角坐标,格式: "x,y"
|
||||
south_east_cor: str # 东南角坐标,格式: "x,y"
|
||||
|
||||
# 这些字段将在__post_init__中设置
|
||||
cors: list[tuple[int, int]] = field(init=False) # 存储所有坐标点
|
||||
# 可选/默认值,因为现在主要通过外部传入 cors 初始化
|
||||
shape: 'Shape' = field(default=None)
|
||||
north_west_cor: str = ""
|
||||
south_east_cor: str = ""
|
||||
|
||||
# 核心坐标数据
|
||||
cors: list[tuple[int, int]] = field(default_factory=list)
|
||||
|
||||
# 计算字段
|
||||
center_loc: tuple[int, int] = field(init=False)
|
||||
area: int = field(init=False)
|
||||
|
||||
def __post_init__(self):
|
||||
"""初始化计算字段"""
|
||||
# 先计算所有坐标点
|
||||
self.cors = get_tiles_from_shape(self.shape, self.north_west_cor, self.south_east_cor)
|
||||
if self.shape is None:
|
||||
self.shape = Shape.SQUARE # 默认值
|
||||
|
||||
# 如果 cors 为空且提供了旧版坐标字符串,尝试计算 (兼容性)
|
||||
if not self.cors and self.north_west_cor and self.south_east_cor:
|
||||
self.cors = get_tiles_from_shape(self.shape, self.north_west_cor, self.south_east_cor)
|
||||
|
||||
# 基于坐标点计算面积
|
||||
self.area = len(self.cors)
|
||||
|
||||
# 计算中心位置:选取落在区域格点集合中的、最接近几何中心的点
|
||||
# 计算中心位置
|
||||
if self.cors:
|
||||
avg_x = sum(coord[0] for coord in self.cors) // len(self.cors)
|
||||
avg_y = sum(coord[1] for coord in self.cors) // len(self.cors)
|
||||
@@ -138,13 +115,13 @@ class Region(ABC):
|
||||
return (p[0] - avg_x) ** 2 + (p[1] - avg_y) ** 2
|
||||
self.center_loc = min(self.cors, key=_dist2)
|
||||
else:
|
||||
# 如果没有坐标点,使用边界框中心作为fallback
|
||||
nw_coords = tuple(map(int, self.north_west_cor.split(',')))
|
||||
se_coords = tuple(map(int, self.south_east_cor.split(',')))
|
||||
self.center_loc = (
|
||||
(nw_coords[0] + se_coords[0]) // 2,
|
||||
(nw_coords[1] + se_coords[1]) // 2
|
||||
)
|
||||
# Fallback
|
||||
if self.north_west_cor and self.south_east_cor:
|
||||
nw = list(map(int, self.north_west_cor.split(',')))
|
||||
se = list(map(int, self.south_east_cor.split(',')))
|
||||
self.center_loc = ((nw[0]+se[0])//2, (nw[1]+se[1])//2)
|
||||
else:
|
||||
self.center_loc = (0, 0)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self.id)
|
||||
@@ -156,25 +133,18 @@ class Region(ABC):
|
||||
|
||||
@abstractmethod
|
||||
def get_region_type(self) -> str:
|
||||
"""返回区域类型的字符串表示"""
|
||||
pass
|
||||
|
||||
def get_hover_info(self) -> list[str]:
|
||||
"""
|
||||
返回用于前端悬浮提示的多行信息(基础信息)。
|
||||
子类可扩展更多领域信息。
|
||||
"""
|
||||
return [
|
||||
f"区域: {self.name}",
|
||||
f"描述: {self.desc}",
|
||||
]
|
||||
|
||||
def get_info(self) -> str:
|
||||
# 简版:仅返回名称
|
||||
return self.name
|
||||
|
||||
def get_detailed_info(self) -> str:
|
||||
# 基类暂无更多结构化信息,详细版返回名称+描述
|
||||
return f"{self.name} - {self.desc}"
|
||||
|
||||
def get_structured_info(self) -> dict:
|
||||
@@ -188,57 +158,33 @@ class Region(ABC):
|
||||
|
||||
|
||||
class Shape(Enum):
|
||||
"""
|
||||
区域形状类型
|
||||
"""
|
||||
SQUARE = "square" # 正方形
|
||||
RECTANGLE = "rectangle" # 长方形
|
||||
MEANDERING = "meandering" # 蜿蜒的(如河流)
|
||||
SQUARE = "square"
|
||||
RECTANGLE = "rectangle"
|
||||
MEANDERING = "meandering"
|
||||
|
||||
@classmethod
|
||||
def from_str(cls, shape_str: str) -> 'Shape':
|
||||
"""
|
||||
从字符串创建Shape实例
|
||||
|
||||
Args:
|
||||
shape_str: 形状的字符串表示,如 "square", "rectangle", "meandering"
|
||||
|
||||
Returns:
|
||||
对应的Shape枚举值
|
||||
|
||||
Raises:
|
||||
ValueError: 如果字符串不匹配任何已知的形状类型
|
||||
"""
|
||||
if not shape_str: return cls.SQUARE
|
||||
for shape in cls:
|
||||
if shape.value == shape_str:
|
||||
return shape
|
||||
raise ValueError(f"Unknown shape type: {shape_str}")
|
||||
return cls.SQUARE
|
||||
|
||||
|
||||
@dataclass
|
||||
class NormalRegion(Region):
|
||||
"""
|
||||
普通区域 - 平原、大河之类的,没有灵气或灵气很低
|
||||
包含该区域分布的动植物物种信息
|
||||
"""
|
||||
animal_ids: list[int] = field(default_factory=list) # 该区域分布的动物物种IDs
|
||||
plant_ids: list[int] = field(default_factory=list) # 该区域分布的植物物种IDs
|
||||
"""普通区域"""
|
||||
animal_ids: list[int] = field(default_factory=list)
|
||||
plant_ids: list[int] = field(default_factory=list)
|
||||
|
||||
# 这些字段将在__post_init__中设置
|
||||
animals: list[Animal] = field(init=False, default_factory=list) # 该区域的动物实例
|
||||
plants: list[Plant] = field(init=False, default_factory=list) # 该区域的植物实例
|
||||
animals: list[Animal] = field(init=False, default_factory=list)
|
||||
plants: list[Plant] = field(init=False, default_factory=list)
|
||||
|
||||
def __post_init__(self):
|
||||
"""初始化动植物实例"""
|
||||
# 先调用父类的__post_init__
|
||||
super().__post_init__()
|
||||
|
||||
# 加载动物实例
|
||||
for animal_id in self.animal_ids:
|
||||
if animal_id in animals_by_id:
|
||||
self.animals.append(animals_by_id[animal_id])
|
||||
|
||||
# 加载植物实例
|
||||
for plant_id in self.plant_ids:
|
||||
if plant_id in plants_by_id:
|
||||
self.plants.append(plants_by_id[plant_id])
|
||||
@@ -247,24 +193,14 @@ class NormalRegion(Region):
|
||||
return "normal"
|
||||
|
||||
def get_species_info(self) -> str:
|
||||
"""获取该区域动植物物种的描述信息"""
|
||||
info_parts = []
|
||||
if self.animals:
|
||||
animal_infos = [animal.get_info() for animal in self.animals]
|
||||
info_parts.extend(animal_infos)
|
||||
|
||||
info_parts.extend([a.get_info() for a in self.animals])
|
||||
if self.plants:
|
||||
plant_infos = [plant.get_info() for plant in self.plants]
|
||||
info_parts.extend(plant_infos)
|
||||
|
||||
info_parts.extend([p.get_info() for p in self.plants])
|
||||
return "; ".join(info_parts) if info_parts else "暂无特色物种"
|
||||
|
||||
def _get_species_brief(self) -> str:
|
||||
"""
|
||||
简要物种信息:仅名字与境界,用于在名称后括号展示。
|
||||
例:"灵兔(练气)、青云鹿(练气)、暗影豹(筑基)"
|
||||
若无物种,返回空串。
|
||||
"""
|
||||
briefs: list[str] = []
|
||||
if self.animals:
|
||||
briefs.extend([f"{a.name}({a.realm.value})" for a in self.animals])
|
||||
@@ -281,7 +217,6 @@ class NormalRegion(Region):
|
||||
return f"{self.name}({brief})" if brief else self.name
|
||||
|
||||
def get_detailed_info(self) -> str:
|
||||
# 名称后追加物种简要;正文仍保留原来的详细物种描述
|
||||
brief = self._get_species_brief()
|
||||
name_with_brief = f"{self.name}({brief})" if brief else self.name
|
||||
species_info = self.get_species_info()
|
||||
@@ -302,12 +237,10 @@ class NormalRegion(Region):
|
||||
|
||||
@property
|
||||
def is_huntable(self) -> bool:
|
||||
# 如果该区域有动物,则可以狩猎
|
||||
return len(self.animals) > 0
|
||||
|
||||
@property
|
||||
def is_harvestable(self) -> bool:
|
||||
# 如果该区域有植物,则可以采集
|
||||
return len(self.plants) > 0
|
||||
|
||||
def get_structured_info(self) -> dict:
|
||||
@@ -320,18 +253,13 @@ class NormalRegion(Region):
|
||||
|
||||
@dataclass
|
||||
class CultivateRegion(Region):
|
||||
"""
|
||||
修炼区域 - 有灵气的区域,可以修炼
|
||||
"""
|
||||
essence_type: EssenceType # 最高灵气类型
|
||||
essence_density: int # 最高灵气密度
|
||||
essence: Essence = field(init=False) # 灵气对象,根据 essence_type 和 essence_density 生成
|
||||
"""修炼区域"""
|
||||
essence_type: EssenceType = EssenceType.GOLD # 默认值避免 dataclass 继承错误
|
||||
essence_density: int = 0
|
||||
essence: Essence = field(init=False)
|
||||
|
||||
def __post_init__(self):
|
||||
# 先调用父类的 __post_init__
|
||||
super().__post_init__()
|
||||
|
||||
# 创建灵气对象,主要灵气类型设置为指定密度,其他类型设置为0
|
||||
essence_density_dict = {essence_type: 0 for essence_type in EssenceType}
|
||||
essence_density_dict[self.essence_type] = self.essence_density
|
||||
self.essence = Essence(essence_density_dict)
|
||||
@@ -358,7 +286,7 @@ class CultivateRegion(Region):
|
||||
info = super().get_structured_info()
|
||||
info["type_name"] = "修炼区域"
|
||||
info["essence"] = {
|
||||
"type": str(self.essence_type), # EssenceType.__str__ 已经返回中文名
|
||||
"type": str(self.essence_type),
|
||||
"density": self.essence_density
|
||||
}
|
||||
return info
|
||||
@@ -366,20 +294,13 @@ class CultivateRegion(Region):
|
||||
|
||||
@dataclass
|
||||
class CityRegion(Region):
|
||||
"""
|
||||
城市区域 - 不能修炼,但会有特殊操作
|
||||
"""
|
||||
|
||||
"""城市区域"""
|
||||
def get_region_type(self) -> str:
|
||||
return "city"
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"城市区域:{self.name} - {self.desc}"
|
||||
|
||||
def get_hover_info(self) -> list[str]:
|
||||
# 城市区域暂时仅展示基础信息
|
||||
return super().get_hover_info()
|
||||
|
||||
def get_info(self) -> str:
|
||||
return self.name
|
||||
|
||||
@@ -393,10 +314,6 @@ class CityRegion(Region):
|
||||
|
||||
|
||||
def _normalize_region_name(name: str) -> str:
|
||||
"""
|
||||
将诸如 "太白金府(金行灵气:10)" 归一化为 "太白金府":
|
||||
去除常见括号及其中附加信息,并裁剪空白。
|
||||
"""
|
||||
s = str(name).strip()
|
||||
brackets = [("(", ")"), ("(", ")"), ("[", "]"), ("【", "】"), ("「", "」"), ("『", "』"), ("<", ">"), ("《", "》")]
|
||||
for left, right in brackets:
|
||||
@@ -412,15 +329,9 @@ def _normalize_region_name(name: str) -> str:
|
||||
|
||||
def resolve_region(world, region: Union[Region, str]) -> Region:
|
||||
"""
|
||||
解析字符串或 Region 为当前 world.map 中的 Region 实例:
|
||||
- 字符串:先精确匹配;失败则做归一化再匹配;再做“唯一包含”匹配;最后尝试按宗门名解析宗门总部区域
|
||||
- Region:若 world.map.regions 中存在同 id 的实例,则返回映射后的当前实例,否则原样返回
|
||||
|
||||
Raises:
|
||||
ValueError: 未知区域名或名称不唯一
|
||||
TypeError: 不支持的类型
|
||||
解析字符串或 Region 为当前 world.map 中的 Region 实例
|
||||
"""
|
||||
from typing import Dict # 局部导入以避免潜在循环
|
||||
from typing import Dict
|
||||
|
||||
if isinstance(region, str):
|
||||
region_name = region
|
||||
@@ -438,7 +349,7 @@ def resolve_region(world, region: Union[Region, str]) -> Region:
|
||||
if r2 is not None:
|
||||
return r2
|
||||
|
||||
# 3) 唯一包含匹配(当且仅当候选唯一时)
|
||||
# 3) 唯一包含匹配
|
||||
candidates = [name for name in by_name.keys() if name and (name in region_name or (normalized and name in normalized))]
|
||||
if len(candidates) == 1:
|
||||
return by_name[candidates[0]]
|
||||
@@ -451,7 +362,6 @@ def resolve_region(world, region: Union[Region, str]) -> Region:
|
||||
if len(matched) == 1:
|
||||
return matched[0]
|
||||
|
||||
# 失败:抛出明确错误提示
|
||||
if candidates:
|
||||
sample = ", ".join(candidates[:5])
|
||||
raise ValueError(f"区域名不唯一: {region_name},候选: {sample}")
|
||||
@@ -464,86 +374,3 @@ def resolve_region(world, region: Union[Region, str]) -> Region:
|
||||
return region
|
||||
|
||||
raise TypeError(f"不支持的region类型: {type(region).__name__}")
|
||||
|
||||
|
||||
T = TypeVar('T', NormalRegion, CultivateRegion, CityRegion)
|
||||
|
||||
def _load_regions(region_type: Type[T], config_name: str) -> tuple[dict[int, T], dict[str, T]]:
|
||||
"""
|
||||
通用的区域加载函数
|
||||
|
||||
Args:
|
||||
region_type: 区域类型 (NormalRegion, CultivateRegion, CityRegion)
|
||||
config_name: 配置文件名 ("normal_region", "cultivate_region", "city_region")
|
||||
|
||||
Returns:
|
||||
(按ID索引的字典, 按名称索引的字典)
|
||||
"""
|
||||
regions_by_id: dict[int, T] = {}
|
||||
regions_by_name: dict[str, T] = {}
|
||||
|
||||
region_df = game_configs[config_name]
|
||||
for row in region_df:
|
||||
# 构建基础参数
|
||||
base_params = {
|
||||
"id": get_int(row, "id"),
|
||||
"name": get_str(row, "name"),
|
||||
"desc": get_str(row, "desc"),
|
||||
"shape": Shape.from_str(get_str(row, "shape")),
|
||||
"north_west_cor": get_str(row, "north-west-cor"),
|
||||
"south_east_cor": get_str(row, "south-east-cor")
|
||||
}
|
||||
|
||||
# 如果是修炼区域,添加额外参数
|
||||
if region_type == CultivateRegion:
|
||||
base_params["essence_type"] = EssenceType.from_str(get_str(row, "root_type"))
|
||||
base_params["essence_density"] = get_int(row, "root_density")
|
||||
|
||||
# 如果是普通区域,添加动植物ID参数
|
||||
elif region_type == NormalRegion:
|
||||
base_params["animal_ids"] = get_list_int(row, "animal_ids")
|
||||
base_params["plant_ids"] = get_list_int(row, "plant_ids")
|
||||
|
||||
region = region_type(**base_params)
|
||||
regions_by_id[region.id] = region
|
||||
regions_by_name[region.name] = region
|
||||
|
||||
return regions_by_id, regions_by_name
|
||||
|
||||
|
||||
def load_all_regions() -> tuple[
|
||||
dict[int, Union[NormalRegion, CultivateRegion, CityRegion]],
|
||||
dict[str, Union[NormalRegion, CultivateRegion, CityRegion]]
|
||||
]:
|
||||
"""
|
||||
统一加载所有类型的区域数据
|
||||
返回: (按ID索引的字典, 按名称索引的字典)
|
||||
"""
|
||||
all_regions_by_id: dict[int, Union[NormalRegion, CultivateRegion, CityRegion]] = {}
|
||||
all_regions_by_name: dict[str, Union[NormalRegion, CultivateRegion, CityRegion]] = {}
|
||||
|
||||
# 加载普通区域
|
||||
normal_by_id, normal_by_name = _load_regions(NormalRegion, "normal_region")
|
||||
all_regions_by_id.update(normal_by_id)
|
||||
all_regions_by_name.update(normal_by_name)
|
||||
|
||||
# 加载修炼区域
|
||||
cultivate_by_id, cultivate_by_name = _load_regions(CultivateRegion, "cultivate_region")
|
||||
all_regions_by_id.update(cultivate_by_id)
|
||||
all_regions_by_name.update(cultivate_by_name)
|
||||
|
||||
# 加载城市区域
|
||||
city_by_id, city_by_name = _load_regions(CityRegion, "city_region")
|
||||
all_regions_by_id.update(city_by_id)
|
||||
all_regions_by_name.update(city_by_name)
|
||||
|
||||
return all_regions_by_id, all_regions_by_name
|
||||
|
||||
|
||||
# 从配表加载所有区域数据
|
||||
regions_by_id, regions_by_name = load_all_regions()
|
||||
|
||||
# 分别加载各类型区域数据
|
||||
normal_regions_by_id, normal_regions_by_name = _load_regions(NormalRegion, "normal_region")
|
||||
cultivate_regions_by_id, cultivate_regions_by_name = _load_regions(CultivateRegion, "cultivate_region")
|
||||
city_regions_by_id, city_regions_by_name = _load_regions(CityRegion, "city_region")
|
||||
|
||||
@@ -10,7 +10,7 @@ class SectRegion(Region):
|
||||
宗门总部区域:仅用于显示宗门总部的名称与描述。
|
||||
无额外操作或属性。
|
||||
"""
|
||||
sect_name: str
|
||||
sect_name: str = ""
|
||||
sect_id: int = -1
|
||||
image_path: str | None = None
|
||||
|
||||
|
||||
@@ -1,468 +0,0 @@
|
||||
from src.classes.map import Map
|
||||
from src.classes.tile import TileType
|
||||
from src.classes.essence import Essence, EssenceType
|
||||
from src.classes.sect_region import SectRegion
|
||||
from src.classes.region import Shape
|
||||
from src.classes.sect import Sect
|
||||
from src.utils.df import game_configs, get_str, get_int
|
||||
|
||||
BASE_W = 70
|
||||
BASE_H = 50
|
||||
|
||||
def _scale_x(x: int, width: int) -> int:
|
||||
# 将以 BASE_W 为参考的 x 坐标按比例缩放到当前 width
|
||||
return int(round(x / BASE_W * width))
|
||||
|
||||
def _scale_y(y: int, height: int) -> int:
|
||||
# 将以 BASE_H 为参考的 y 坐标按比例缩放到当前 height
|
||||
return int(round(y / BASE_H * height))
|
||||
|
||||
def _scaled_range_x(x0: int, x1_exclusive: int, width: int) -> range:
|
||||
sx0 = max(0, min(width, _scale_x(x0, width)))
|
||||
sx1 = max(sx0, min(width, _scale_x(x1_exclusive, width)))
|
||||
return range(sx0, sx1)
|
||||
|
||||
def _scaled_range_y(y0: int, y1_exclusive: int, height: int) -> range:
|
||||
sy0 = max(0, min(height, _scale_y(y0, height)))
|
||||
sy1 = max(sy0, min(height, _scale_y(y1_exclusive, height)))
|
||||
return range(sy0, sy1)
|
||||
|
||||
|
||||
def create_cultivation_world_map() -> Map:
|
||||
"""
|
||||
创建修仙世界地图(按 0.8 比例缩小格子数量,保持构造不变)
|
||||
基准尺寸: 70x50 -> 实际尺寸: 56x40(约减少 36% 的 tile 数量)
|
||||
西部大漠,南部雨林,北边冰原,最东部和最南部海洋
|
||||
横向大河从大漠东部流向东南入海
|
||||
北方纵向山脉
|
||||
"""
|
||||
# 将总格子数在宽高两个维度各缩小到 0.8 倍
|
||||
width = int(round(BASE_W * 0.8))
|
||||
height = int(round(BASE_H * 0.8))
|
||||
game_map = Map(width=width, height=height)
|
||||
|
||||
# 缩放从配置中加载的区域到新地图尺度
|
||||
_scale_loaded_regions(game_map)
|
||||
# 创建基础地形
|
||||
_create_base_terrain(game_map)
|
||||
|
||||
# 创建区域
|
||||
_assign_regions_to_tiles(game_map)
|
||||
|
||||
return game_map
|
||||
|
||||
def add_sect_headquarters(game_map: Map, enabled_sects: list[Sect]):
|
||||
"""
|
||||
根据已启用的宗门列表,为其添加总部区域(1x1,hover仅显示名称与描述)。
|
||||
若未启用(列表中无该宗门),则不添加对应总部。
|
||||
"""
|
||||
# 为九个宗门设计坐标(根据地图地形大势和叙事):
|
||||
# 仅登记矩形区域的西北角与东南角(现在合二为一)
|
||||
locs: dict[str, tuple[tuple[int, int], tuple[int, int]]] = {
|
||||
"明心剑宗": ((36, 10), (36, 10)),
|
||||
"百兽宗": ((22, 22), (22, 22)),
|
||||
"水镜宗": ((58, 22), (58, 22)),
|
||||
"冥王宗": ((66, 8), (66, 8)),
|
||||
"朱勾宗": ((48, 8), (48, 8)),
|
||||
"合欢宗": ((62, 40), (62, 40)),
|
||||
"镇魂宗": ((30, 46), (30, 46)),
|
||||
"幽魂噬影宗":((44, 38), (44, 38)),
|
||||
"千帆城": ((60, 28), (60, 28)),
|
||||
"妙化宗": ((42, 6), (42, 6)), # 北部冰原边缘
|
||||
"回玄宗": ((52, 18), (52, 18)), # 东北部森林边缘
|
||||
"不夜城": ((28, 4), (28, 4)), # 极北冰原
|
||||
"天行健宗": ((38, 25), (38, 25)), # 中部平原,浩然峰
|
||||
"噬魔宗": ((10, 30), (10, 30)), # 西部大漠深处
|
||||
}
|
||||
|
||||
# 从 sect_region.csv 读取(按 sect_id 对齐):sect_name、headquarter_name、headquarter_desc
|
||||
sect_region_df = game_configs["sect_region"]
|
||||
hq_by_id: dict[int, tuple[str, str, str]] = {
|
||||
get_int(row, "sect_id"): (
|
||||
get_str(row, "sect_name"),
|
||||
get_str(row, "headquarter_name"),
|
||||
get_str(row, "headquarter_desc"),
|
||||
)
|
||||
for row in sect_region_df
|
||||
}
|
||||
# 坐标字典按 sect.name 提供,转换为按 sect.id 对齐
|
||||
id_to_coords: dict[int, tuple[tuple[int, int], tuple[int, int]]] = {
|
||||
s.id: locs[s.name] for s in enabled_sects if s.name in locs
|
||||
}
|
||||
|
||||
for sect in enabled_sects:
|
||||
coords = id_to_coords.get(sect.id)
|
||||
if coords is None:
|
||||
continue
|
||||
nw, se = coords
|
||||
# 名称与描述:优先使用 sect_region.csv;若为空则回退到 sect.headquarter / sect
|
||||
hq_name = getattr(sect.headquarter, "name", sect.name) or sect.name
|
||||
hq_desc = getattr(sect.headquarter, "desc", sect.desc) or sect.desc
|
||||
csv_entry = hq_by_id.get(sect.id)
|
||||
sect_name_for_region = sect.name
|
||||
if csv_entry is not None:
|
||||
sect_name_csv, csv_name, csv_desc = csv_entry
|
||||
if csv_name:
|
||||
hq_name = csv_name
|
||||
if csv_desc:
|
||||
hq_desc = csv_desc
|
||||
if sect_name_csv:
|
||||
sect_name_for_region = sect_name_csv
|
||||
|
||||
# 缩放坐标
|
||||
nw_x = _scale_x(nw[0], game_map.width)
|
||||
nw_y = _scale_y(nw[1], game_map.height)
|
||||
|
||||
# 边界修正
|
||||
nw_x = max(0, min(game_map.width - 1, nw_x))
|
||||
nw_y = max(0, min(game_map.height - 1, nw_y))
|
||||
|
||||
se_x, se_y = nw_x, nw_y
|
||||
|
||||
region = SectRegion(
|
||||
id=400 + sect.id,
|
||||
name=hq_name,
|
||||
desc=hq_desc,
|
||||
shape=Shape.RECTANGLE,
|
||||
north_west_cor=f"{nw_x},{nw_y}",
|
||||
south_east_cor=f"{se_x},{se_y}",
|
||||
sect_name=sect_name_for_region,
|
||||
sect_id=sect.id,
|
||||
image_path=str(getattr(sect.headquarter, "image", None)),
|
||||
)
|
||||
game_map.regions[region.id] = region
|
||||
game_map.region_names[region.name] = region
|
||||
# 刷新 Map 内部的宗门区域缓存
|
||||
game_map.update_sect_regions()
|
||||
|
||||
# 设置 Tile
|
||||
if game_map.is_in_bounds(nw_x, nw_y):
|
||||
game_map.tiles[(nw_x, nw_y)].type = TileType.SECT
|
||||
|
||||
# 添加完成后,重新分配到 tiles
|
||||
_assign_regions_to_tiles(game_map)
|
||||
|
||||
def _create_base_terrain(game_map: Map):
|
||||
"""创建基础地形"""
|
||||
width, height = game_map.width, game_map.height
|
||||
|
||||
# 先创建默认平原
|
||||
for x in range(width):
|
||||
for y in range(height):
|
||||
game_map.create_tile(x, y, TileType.PLAIN)
|
||||
|
||||
# 西部大漠 (x: 0-18)
|
||||
for x in _scaled_range_x(0, 19, width):
|
||||
for y in range(height):
|
||||
game_map.tiles[(x, y)].type = TileType.DESERT
|
||||
|
||||
# 南部雨林 (y: 35-49)
|
||||
for x in range(width):
|
||||
for y in _scaled_range_y(35, BASE_H, height):
|
||||
if game_map.tiles[(x, y)].type != TileType.DESERT:
|
||||
game_map.tiles[(x, y)].type = TileType.RAINFOREST
|
||||
|
||||
# 北边冰原 (y: 0-8)
|
||||
for x in range(width):
|
||||
for y in _scaled_range_y(0, 9, height):
|
||||
if game_map.tiles[(x, y)].type != TileType.DESERT:
|
||||
game_map.tiles[(x, y)].type = TileType.GLACIER
|
||||
|
||||
# 最东部海洋 (x: 65-69)
|
||||
for x in _scaled_range_x(65, BASE_W, width):
|
||||
for y in range(height):
|
||||
game_map.tiles[(x, y)].type = TileType.SEA
|
||||
|
||||
# 最南部海洋 (y: 46-49)
|
||||
for x in range(width):
|
||||
for y in _scaled_range_y(46, BASE_H, height):
|
||||
game_map.tiles[(x, y)].type = TileType.SEA
|
||||
|
||||
# 横向大河:从大漠东部(18,25)比例位置流向东南入海,河流更宽
|
||||
river_tiles = _calculate_wide_river_tiles(game_map)
|
||||
for x, y in river_tiles:
|
||||
if game_map.is_in_bounds(x, y):
|
||||
game_map.tiles[(x, y)].type = TileType.WATER
|
||||
|
||||
# 北方纵向山脉 (x: 28-32, y: 5-20)
|
||||
for x in _scaled_range_x(28, 33, width):
|
||||
for y in _scaled_range_y(5, 21, height):
|
||||
if game_map.tiles[(x, y)].type == TileType.PLAIN:
|
||||
game_map.tiles[(x, y)].type = TileType.MOUNTAIN
|
||||
|
||||
# 添加其他地形类型
|
||||
_add_other_terrains(game_map)
|
||||
|
||||
def _calculate_wide_river_tiles(game_map: Map):
|
||||
"""计算宽阔大河的所有水域tiles(按比例缩放)"""
|
||||
w, h = game_map.width, game_map.height
|
||||
river_tiles = []
|
||||
|
||||
# 按比例的关键节点
|
||||
start_x = _scale_x(18, w)
|
||||
start_y = _scale_y(25, h)
|
||||
phase_split_x = _scale_x(40, w)
|
||||
end1_x = _scale_x(65, w)
|
||||
end1_y = _scale_y(46, h)
|
||||
end2_x = _scale_x(68, w)
|
||||
end2_y = _scale_y(48, h)
|
||||
|
||||
center_path = []
|
||||
x, y = start_x, start_y
|
||||
|
||||
while x < end1_x and y < end1_y:
|
||||
center_path.append((x, y))
|
||||
if x < phase_split_x:
|
||||
x += 1
|
||||
if y < _scale_y(35, h):
|
||||
y += 1
|
||||
else:
|
||||
x += max(1, int(round(w / BASE_W * 2))) # 按比例放大步幅
|
||||
y += 1
|
||||
|
||||
while x < end2_x and y < end2_y:
|
||||
center_path.append((x, y))
|
||||
x += 1
|
||||
y += 1
|
||||
|
||||
for i, (cx, cy) in enumerate(center_path):
|
||||
width_sel = 1 if i < len(center_path) // 3 else 2 if i < 2 * len(center_path) // 3 else 3
|
||||
for dx in range(-width_sel // 2, width_sel // 2 + 1):
|
||||
for dy in range(-width_sel // 2, width_sel // 2 + 1):
|
||||
river_tiles.append((cx + dx, cy + dy))
|
||||
|
||||
return list(set(river_tiles))
|
||||
|
||||
def _create_cities(game_map: Map):
|
||||
"""创建城市区域"""
|
||||
cities = [
|
||||
{"name": "青云城", "base_x": _scale_x(34, game_map.width), "base_y": _scale_y(21, game_map.height), "description": "繁华都市的中心"},
|
||||
{"name": "沙月城", "base_x": _scale_x(14, game_map.width), "base_y": _scale_y(19, game_map.height), "description": "沙漠绿洲中的贸易重镇"},
|
||||
{"name": "翠林城", "base_x": _scale_x(54, game_map.width), "base_y": _scale_y(14, game_map.height), "description": "森林深处的修仙重镇"}
|
||||
]
|
||||
|
||||
for city in cities:
|
||||
x, y = city["base_x"], city["base_y"]
|
||||
if game_map.is_in_bounds(x, y):
|
||||
game_map.tiles[(x, y)].type = TileType.CITY
|
||||
|
||||
def _create_wuxing_caves(game_map: Map):
|
||||
"""创建五行洞府区域"""
|
||||
# 五行洞府配置:金木水火土
|
||||
wuxing_caves = [
|
||||
{"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(32, game_map.width), "base_y": _scale_y(16, game_map.height), "element": EssenceType.EARTH, "description": "青峰山脉的土行洞府"}
|
||||
]
|
||||
|
||||
for cave in wuxing_caves:
|
||||
x, y = cave["base_x"], cave["base_y"]
|
||||
if game_map.is_in_bounds(x, y):
|
||||
game_map.tiles[(x, y)].type = TileType.CAVE
|
||||
|
||||
def _create_ruins(game_map: Map):
|
||||
"""创建遗迹区域"""
|
||||
ruins = [
|
||||
{"name": "古越遗迹", "base_x": _scale_x(25, game_map.width), "base_y": _scale_y(40, game_map.height), "description": "雨林深处的上古遗迹"},
|
||||
{"name": "沧海遗迹", "base_x": _scale_x(66, game_map.width), "base_y": _scale_y(47, game_map.height), "description": "沉没在海中的远古文明遗迹"}
|
||||
]
|
||||
|
||||
for ruin in ruins:
|
||||
x, y = ruin["base_x"], ruin["base_y"]
|
||||
if game_map.is_in_bounds(x, y):
|
||||
game_map.tiles[(x, y)].type = TileType.RUINS
|
||||
|
||||
def _scale_loaded_regions(game_map: Map) -> None:
|
||||
"""按比例缩放从 CSV 加载到 Map 的区域坐标。
|
||||
- 小型 2x2(或 1x2/2x1)保持尺寸不变,仅移动锚点
|
||||
- 其他矩形/正方形区域按比例缩放两角
|
||||
- 蜿蜒区域按比例缩放其包围框
|
||||
"""
|
||||
new_regions: dict[int, object] = {}
|
||||
new_region_names: dict[str, object] = {}
|
||||
for region in list(game_map.regions.values()):
|
||||
try:
|
||||
nw_x, nw_y = map(int, str(region.north_west_cor).split(","))
|
||||
se_x, se_y = map(int, str(region.south_east_cor).split(","))
|
||||
except Exception:
|
||||
# 坐标异常的保持原样
|
||||
new_regions[region.id] = region
|
||||
new_region_names[region.name] = region
|
||||
continue
|
||||
width = game_map.width
|
||||
height = game_map.height
|
||||
cls = region.__class__
|
||||
shape = region.shape
|
||||
# 计算尺寸(包含端点,故差值为尺寸-1)
|
||||
span_w = max(0, se_x - nw_x)
|
||||
span_h = max(0, se_y - nw_y)
|
||||
if shape.name in ("RECTANGLE", "SQUARE") and span_w <= 1 and span_h <= 1:
|
||||
# 小区域(如 2x2 或更小)缩放后塌缩为 1x1
|
||||
new_nw_x = max(0, min(width - 1, _scale_x(nw_x, width)))
|
||||
new_nw_y = max(0, min(height - 1, _scale_y(nw_y, height)))
|
||||
# 强制变为 1x1
|
||||
new_se_x = new_nw_x
|
||||
new_se_y = new_nw_y
|
||||
else:
|
||||
# 一般区域按比例缩放两角
|
||||
new_nw_x = max(0, min(width - 1, _scale_x(nw_x, width)))
|
||||
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,
|
||||
"desc": region.desc,
|
||||
"shape": shape,
|
||||
"north_west_cor": f"{new_nw_x},{new_nw_y}",
|
||||
"south_east_cor": f"{new_se_x},{new_se_y}",
|
||||
}
|
||||
# 附带子类特有字段
|
||||
# 普通区域需要保留物种ID列表,避免缩放重建后丢失物种信息
|
||||
if hasattr(region, "animal_ids"):
|
||||
params["animal_ids"] = list(getattr(region, "animal_ids") or [])
|
||||
if hasattr(region, "plant_ids"):
|
||||
params["plant_ids"] = list(getattr(region, "plant_ids") or [])
|
||||
if hasattr(region, "essence_type"):
|
||||
params["essence_type"] = getattr(region, "essence_type")
|
||||
if hasattr(region, "essence_density"):
|
||||
params["essence_density"] = getattr(region, "essence_density")
|
||||
# SectRegion 透传特有字段
|
||||
if hasattr(region, "sect_name"):
|
||||
params["sect_name"] = getattr(region, "sect_name")
|
||||
if hasattr(region, "sect_id"):
|
||||
params["sect_id"] = getattr(region, "sect_id")
|
||||
if hasattr(region, "image_path"):
|
||||
params["image_path"] = getattr(region, "image_path")
|
||||
new_region = cls(**params) # 重新构建以刷新 cors/area/center
|
||||
new_regions[new_region.id] = new_region
|
||||
new_region_names[new_region.name] = new_region
|
||||
game_map.regions = new_regions
|
||||
game_map.region_names = new_region_names
|
||||
|
||||
def _add_other_terrains(game_map: Map):
|
||||
"""添加其他地形类型到合适位置(按比例缩放区域范围)"""
|
||||
w, h = game_map.width, game_map.height
|
||||
# 草原 (中部区域)
|
||||
for x in _scaled_range_x(20, 40, w):
|
||||
for y in _scaled_range_y(12, 25, h):
|
||||
if game_map.tiles[(x, y)].type == TileType.PLAIN:
|
||||
game_map.tiles[(x, y)].type = TileType.GRASSLAND
|
||||
|
||||
# 森林 (东中部区域)
|
||||
for x in _scaled_range_x(40, 60, w):
|
||||
for y in _scaled_range_y(10, 30, h):
|
||||
if game_map.tiles[(x, y)].type == TileType.PLAIN:
|
||||
game_map.tiles[(x, y)].type = TileType.FOREST
|
||||
|
||||
# 雪山 (北部山脉附近)
|
||||
for x in _scaled_range_x(25, 35, w):
|
||||
for y in _scaled_range_y(2, 8, h):
|
||||
if game_map.tiles[(x, y)].type == TileType.GLACIER:
|
||||
game_map.tiles[(x, y)].type = TileType.SNOW_MOUNTAIN
|
||||
|
||||
# 火山 (单独区域)
|
||||
for x in _scaled_range_x(52, 55, w):
|
||||
for y in _scaled_range_y(32, 35, h):
|
||||
if game_map.tiles[(x, y)].type == TileType.PLAIN:
|
||||
game_map.tiles[(x, y)].type = TileType.VOLCANO
|
||||
|
||||
# 创建城市区域
|
||||
_create_cities(game_map)
|
||||
|
||||
# 创建五行洞府区域
|
||||
_create_wuxing_caves(game_map)
|
||||
|
||||
# 创建遗迹区域
|
||||
_create_ruins(game_map)
|
||||
|
||||
# 农田 (城市附近,改为不与草原重叠的区域)
|
||||
for x in _scaled_range_x(33, 38, w):
|
||||
for y in _scaled_range_y(25, 30, h):
|
||||
if game_map.tiles[(x, y)].type == TileType.PLAIN:
|
||||
game_map.tiles[(x, y)].type = TileType.FARM
|
||||
|
||||
# 沼泽 (河流附近的低洼地区,避开雨林区域)
|
||||
for x in _scaled_range_x(42, 46, w):
|
||||
for y in _scaled_range_y(30, 34, h):
|
||||
if game_map.tiles[(x, y)].type == TileType.PLAIN:
|
||||
game_map.tiles[(x, y)].type = TileType.SWAMP
|
||||
|
||||
def _assign_regions_to_tiles(game_map: Map):
|
||||
"""将区域分配给地图中的tiles"""
|
||||
# 初始化所有tiles的region为None
|
||||
for x in range(game_map.width):
|
||||
for y in range(game_map.height):
|
||||
game_map.tiles[(x, y)].region = None
|
||||
|
||||
# 遍历所有region,为对应的坐标分配正确的region
|
||||
# 现在从map实例获取region信息,而不是直接从region.py导入
|
||||
for region in game_map.regions.values():
|
||||
for coord_x, coord_y in region.cors:
|
||||
# 确保坐标在地图范围内
|
||||
if game_map.is_in_bounds(coord_x, coord_y):
|
||||
game_map.tiles[(coord_x, coord_y)].region = region
|
||||
|
||||
# 归并“最终归属”的坐标集合到 Map.region_cors,并回写到各 Region
|
||||
final_region_cors: dict[int, list[tuple[int, int]]] = {}
|
||||
for x in range(game_map.width):
|
||||
for y in range(game_map.height):
|
||||
tile = game_map.tiles[(x, y)]
|
||||
r = tile.region
|
||||
if r is None:
|
||||
continue
|
||||
rid = r.id
|
||||
if rid not in final_region_cors:
|
||||
final_region_cors[rid] = []
|
||||
final_region_cors[rid].append((x, y))
|
||||
|
||||
# 写入 Map 级缓存
|
||||
game_map.region_cors = final_region_cors
|
||||
|
||||
# 将最终坐标集合回写到各 Region 的 cors/area/center_loc
|
||||
for rid, region in list(game_map.regions.items()):
|
||||
cors = final_region_cors.get(rid, [])
|
||||
# 去重并排序,保证稳定性
|
||||
cors = sorted(set(cors))
|
||||
region.cors = cors
|
||||
region.area = len(cors)
|
||||
# 计算新的中心点(若无格点则保持原中心)
|
||||
if cors:
|
||||
region.center_loc = game_map.get_center_locs(cors)
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 创建地图
|
||||
cultivation_map = create_cultivation_world_map()
|
||||
print(f"修仙世界地图创建完成!尺寸: {cultivation_map.width}x{cultivation_map.height}")
|
||||
|
||||
# 统计各地形类型
|
||||
terrain_count = {}
|
||||
regions_count = {}
|
||||
for x in range(cultivation_map.width):
|
||||
for y in range(cultivation_map.height):
|
||||
tile = cultivation_map.get_tile(x, y)
|
||||
tile_type = tile.type.value
|
||||
if tile_type not in terrain_count:
|
||||
terrain_count[tile_type] = 0
|
||||
terrain_count[tile_type] += 1
|
||||
|
||||
region = cultivation_map.get_region(x, y)
|
||||
if region.name not in regions_count:
|
||||
regions_count[region.name] = 0
|
||||
regions_count[region.name] += 1
|
||||
|
||||
print("各地形类型分布:")
|
||||
for terrain_type, count in terrain_count.items():
|
||||
print(f" {terrain_type}: {count}个地块")
|
||||
|
||||
print("\n各区域分布:")
|
||||
for region_name, count in regions_count.items():
|
||||
print(f" {region_name}: {count}个地块")
|
||||
140
src/run/load_map.py
Normal file
140
src/run/load_map.py
Normal file
@@ -0,0 +1,140 @@
|
||||
import os
|
||||
import csv
|
||||
from src.classes.map import Map
|
||||
from src.classes.tile import TileType
|
||||
from src.classes.region import Region, Shape, NormalRegion, CultivateRegion, CityRegion
|
||||
from src.classes.sect_region import SectRegion
|
||||
from src.utils.df import game_configs, get_str, get_int
|
||||
from src.classes.essence import EssenceType
|
||||
|
||||
# 静态配置路径
|
||||
CONFIG_DIR = os.path.join(os.path.dirname(__file__), "../../static/game_configs")
|
||||
|
||||
def load_cultivation_world_map() -> Map:
|
||||
"""
|
||||
从静态 CSV 文件加载修仙世界地图。
|
||||
读取: tile_map.csv, region_map.csv
|
||||
以及: normal/city/cultivate/sect_region.csv
|
||||
"""
|
||||
tile_csv = os.path.join(CONFIG_DIR, "tile_map.csv")
|
||||
region_csv = os.path.join(CONFIG_DIR, "region_map.csv")
|
||||
|
||||
if not os.path.exists(tile_csv) or not os.path.exists(region_csv):
|
||||
raise FileNotFoundError(f"Map data files not found in {CONFIG_DIR}")
|
||||
|
||||
# 1. 读取 Tile Map 以确定尺寸
|
||||
with open(tile_csv, 'r', encoding='utf-8') as f:
|
||||
tile_rows = list(csv.reader(f))
|
||||
|
||||
height = len(tile_rows)
|
||||
width = len(tile_rows[0]) if height > 0 else 0
|
||||
|
||||
game_map = Map(width=width, height=height)
|
||||
|
||||
# 2. 填充 Tile Type
|
||||
for y, row in enumerate(tile_rows):
|
||||
for x, tile_name in enumerate(row):
|
||||
if x < width:
|
||||
try:
|
||||
# TileType 通常是大写,兼容 CSV 可能存的小写
|
||||
t_type = TileType[tile_name.upper()]
|
||||
except KeyError:
|
||||
# 兼容性处理:如果是 sect 名称,映射为山地或平原
|
||||
# 编辑器可能把 sect 名称也存进来了
|
||||
# 这里简单处理为 MOUNTAIN,或者根据需要扩展 TileType
|
||||
t_type = TileType.MOUNTAIN if "宗" in tile_name else TileType.PLAIN
|
||||
|
||||
game_map.create_tile(x, y, t_type)
|
||||
|
||||
# 3. 读取 Region Map 并聚合坐标
|
||||
# region_coords: { region_id: [(x, y), ...] }
|
||||
region_coords = {}
|
||||
|
||||
with open(region_csv, 'r', encoding='utf-8') as f:
|
||||
region_rows = list(csv.reader(f))
|
||||
|
||||
for y, row in enumerate(region_rows):
|
||||
if y >= height: break
|
||||
for x, val in enumerate(row):
|
||||
if x >= width: break
|
||||
try:
|
||||
rid = int(val)
|
||||
if rid != -1:
|
||||
if rid not in region_coords:
|
||||
region_coords[rid] = []
|
||||
region_coords[rid].append((x, y))
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
# 4. 加载 Region 元数据并创建对象
|
||||
_load_and_assign_regions(game_map, region_coords)
|
||||
|
||||
# 5. 更新缓存
|
||||
game_map.update_sect_regions()
|
||||
|
||||
return game_map
|
||||
|
||||
def _load_and_assign_regions(game_map: Map, region_coords: dict[int, list[tuple[int, int]]]):
|
||||
"""
|
||||
读取各 region.csv,创建 Region 对象,并分配给 Map 和 Tile
|
||||
"""
|
||||
|
||||
# 辅助函数:处理 Region 数据
|
||||
def process_region_config(df, cls, type_tag):
|
||||
for row in df:
|
||||
rid = get_int(row, "id")
|
||||
|
||||
if rid not in region_coords:
|
||||
continue
|
||||
|
||||
cors = region_coords[rid]
|
||||
|
||||
# 构建参数
|
||||
params = {
|
||||
"id": rid,
|
||||
"name": get_str(row, "name"),
|
||||
"desc": get_str(row, "desc"),
|
||||
"cors": cors,
|
||||
}
|
||||
|
||||
# 特有字段处理
|
||||
if type_tag == "normal":
|
||||
params["animal_ids"] = _parse_list(get_str(row, "animal_ids"))
|
||||
params["plant_ids"] = _parse_list(get_str(row, "plant_ids"))
|
||||
elif type_tag == "cultivate":
|
||||
params["essence_type"] = EssenceType.from_str(get_str(row, "root_type"))
|
||||
params["essence_density"] = get_int(row, "root_density")
|
||||
elif type_tag == "sect":
|
||||
params["sect_id"] = get_int(row, "sect_id")
|
||||
params["sect_name"] = get_str(row, "name") # 假设驻地名即区域名
|
||||
# image_path 暂不处理或需要另寻来源
|
||||
|
||||
# 实例化
|
||||
try:
|
||||
region_obj = cls(**params)
|
||||
game_map.regions[rid] = region_obj
|
||||
game_map.region_names[region_obj.name] = region_obj
|
||||
|
||||
# 写入 Map 缓存 (region_cors)
|
||||
game_map.region_cors[rid] = cors
|
||||
|
||||
# 绑定到 Tiles
|
||||
for rx, ry in cors:
|
||||
if game_map.is_in_bounds(rx, ry):
|
||||
game_map.tiles[(rx, ry)].region = region_obj
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error creating region {rid}: {e}")
|
||||
|
||||
# 执行加载
|
||||
process_region_config(game_configs["normal_region"], NormalRegion, "normal")
|
||||
process_region_config(game_configs["city_region"], CityRegion, "city")
|
||||
process_region_config(game_configs["cultivate_region"], CultivateRegion, "cultivate")
|
||||
process_region_config(game_configs["sect_region"], SectRegion, "sect")
|
||||
|
||||
def _parse_list(s: str) -> list[int]:
|
||||
if not s: return []
|
||||
try:
|
||||
return [int(x.strip()) for x in s.split(",") if x.strip()]
|
||||
except:
|
||||
return []
|
||||
@@ -17,7 +17,7 @@ sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
|
||||
from src.sim.simulator import Simulator
|
||||
from src.classes.world import World
|
||||
from src.classes.calendar import Month, Year, create_month_stamp
|
||||
from src.run.create_map import create_cultivation_world_map, add_sect_headquarters
|
||||
from src.run.load_map import load_cultivation_world_map
|
||||
from src.sim.new_avatar import make_avatars as _new_make, create_avatar_from_request
|
||||
from src.utils.config import CONFIG
|
||||
from src.classes.sect import sects_by_id
|
||||
@@ -209,7 +209,7 @@ def serialize_phenomenon(phenomenon) -> Optional[dict]:
|
||||
def init_game():
|
||||
"""初始化游戏世界,逻辑复用自 src/run/run.py"""
|
||||
print("正在初始化游戏世界...")
|
||||
game_map = create_cultivation_world_map()
|
||||
game_map = load_cultivation_world_map()
|
||||
world = World(map=game_map, month_stamp=create_month_stamp(Year(100), Month.JANUARY))
|
||||
sim = Simulator(world)
|
||||
|
||||
@@ -224,9 +224,6 @@ def init_game():
|
||||
random.shuffle(pool)
|
||||
existed_sects = pool[:needed_sects]
|
||||
|
||||
if existed_sects:
|
||||
add_sect_headquarters(world.map, existed_sects)
|
||||
|
||||
# 创建角色
|
||||
# 注意:这里直接调用 new_avatar 的 make_avatars,避免循环导入
|
||||
all_avatars = _new_make(world, count=CONFIG.game.init_npc_num, current_month_stamp=world.month_stamp, existed_sects=existed_sects)
|
||||
|
||||
@@ -13,7 +13,7 @@ from src.classes.event import Event
|
||||
from src.classes.sect import sects_by_id, Sect
|
||||
from src.classes.relation import Relation
|
||||
from src.sim.simulator import Simulator
|
||||
from src.run.create_map import create_cultivation_world_map, add_sect_headquarters
|
||||
from src.run.load_map import load_cultivation_world_map
|
||||
from src.utils.config import CONFIG
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ def load_game(save_path: Optional[Path] = None) -> Tuple[World, Simulator, List[
|
||||
f"游戏时间: {meta.get('game_time', 'unknown')})")
|
||||
|
||||
# 重建地图(地图本身不变,只需重建宗门总部位置)
|
||||
game_map = create_cultivation_world_map()
|
||||
game_map = load_cultivation_world_map()
|
||||
|
||||
# 读取世界数据
|
||||
world_data = save_data.get("world", {})
|
||||
@@ -65,9 +65,6 @@ def load_game(save_path: Optional[Path] = None) -> Tuple[World, Simulator, List[
|
||||
existed_sect_ids = world_data.get("existed_sect_ids", [])
|
||||
existed_sects = [sects_by_id[sid] for sid in existed_sect_ids if sid in sects_by_id]
|
||||
|
||||
# 在地图上添加宗门总部
|
||||
add_sect_headquarters(game_map, existed_sects)
|
||||
|
||||
# 第一阶段:重建所有Avatar(不含relations)
|
||||
avatars_data = save_data.get("avatars", [])
|
||||
all_avatars = {}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
id,name,desc,shape,north-west-cor,south-east-cor
|
||||
"ID必须以3开头",,,,
|
||||
301,青云城,繁华都市,人烟稠密,商贾云集。此地是交易天材地宝、寻找机缘的重要场所。,square,"34,21","35,22"
|
||||
302,沙月城,沙漠绿洲中的贸易重镇,各路商队在此集结,是修士补给和交流的重要据点。,square,"14,19","15,20"
|
||||
303,翠林城,森林深处的修仙重镇,众多修士在此栖居,是修炼和炼宝的理想之地。,square,"54,14","55,15"
|
||||
id,name,desc
|
||||
ID必须以3开头,,
|
||||
301,青云城,繁华都市,人烟稠密,商贾云集。此地是交易天材地宝、寻找机缘的重要场所。
|
||||
302,沙月城,沙漠绿洲中的贸易重镇,各路商队在此集结,是修士补给和交流的重要据点。
|
||||
303,翠林城,森林深处的修仙重镇,众多修士在此栖居,是修炼和炼宝的理想之地。
|
||||
|
||||
|
@@ -1,9 +1,9 @@
|
||||
id,name,desc,shape,north-west-cor,south-east-cor,root_type,root_density
|
||||
"ID必须以2开头",,,,,,,
|
||||
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,"32,16","33,17",土,10
|
||||
206,古越遗迹,雨林深处的上古遗迹,古藤缠绕,木行灵气与金行灵气交融。蕴藏古老功法与灵药配方。,square,"25,40","26,41",木,8
|
||||
207,沧海遗迹,沉没在海中的远古文明遗迹,水行灵气浓郁,潮汐间偶有宝物现世。,square,"66,47","67,48",水,9
|
||||
id,name,desc,root_type,root_density
|
||||
ID必须以2开头,,,,
|
||||
201,太白金府,青峰山脉深处的金行洞府,金精气凝,刀剑鸣音不绝,乃金系修士的最高圣地。,金,10
|
||||
202,青木洞天,青云林海中的木行洞府,生机盎然,灵药遍地,乃木系修士的最高圣地。,木,10
|
||||
203,玄水秘境,无边碧海深处的水行洞府,碧波万里,水精凝神,乃水系修士的最高圣地。,水,10
|
||||
204,离火洞府,炎狱火山旁的火行洞府,烈焰冲天,真火精纯,乃火系修士的最高圣地。,火,10
|
||||
205,厚土玄宫,青峰山脉的土行洞府,厚德载物,山岳共鸣,乃土系修士的最高圣地。,土,10
|
||||
206,古越遗迹,雨林深处的上古遗迹,古藤缠绕,木行灵气与金行灵气交融。蕴藏古老功法与灵药配方。,木,8
|
||||
207,沧海遗迹,沉没在海中的远古文明遗迹,水行灵气浓郁,潮汐间偶有宝物现世。,水,9
|
||||
|
||||
|
@@ -1,20 +1,20 @@
|
||||
id,name,desc,shape,north-west-cor,south-east-cor,animal_ids,plant_ids
|
||||
"ID必须以1开头",,,,,"该区域分布的动物物种IDs","该区域分布的植物物种IDs"
|
||||
101,平原地带,地势平坦,灵气平和。是初学修炼者打基础和建立宗门的理想之地。,rectangle,"19,9","64,34",3,
|
||||
102,西域流沙,茫茫大漠,黄沙漫天。此地气候干燥,日夜温差极大,是沙漠商队的必经之路。,rectangle,"0,0","18,49",,3
|
||||
103,南疆蛮荒,古木参天,藤蔓缠绕。此地森林茂密,野兽众多,是采集药材和狩猎的危险之地。,rectangle,"19,35","64,45",4,
|
||||
104,极北冰原,千里冰封,万年不化。此地严寒刺骨,风雪交加,只有最坚韧的冒险者才能在此生存。,rectangle,"19,0","64,8",,4
|
||||
105,无边碧海,浩瀚无垠,波涛汹涌。此地风浪险恶,暗礁密布,是海商和渔民的挑战之海。,rectangle,"65,0","69,49",5,
|
||||
106,天河奔流,一江春水向东流,奔腾不息入东海。此河贯穿东西,水流湍急,是重要的交通要道。,meandering,"18,25","67,47",,5
|
||||
107,青峰山脉,连绵起伏,直插云霄。此地山势险峻,多有奇石异洞,是探险者寻宝的热门之地。,rectangle,"28,5","32,20",6,
|
||||
108,万丈雪峰,雪峰皑皑,寒风刺骨。此地终年积雪,山路崎岖,是登山者的终极挑战。,rectangle,"25,2","34,7",,6
|
||||
109,碧野千里,芳草萋萋,一望无际。此地水草丰美,牛羊成群,是游牧民族的天然牧场。,rectangle,"20,12","39,24",7,
|
||||
110,青云林海,古树参天,绿意盎然。此地森林广袤,物产丰富,是伐木工和猎人的主要活动区域。,rectangle,"40,10","59,29",,7
|
||||
111,炎狱火山,烈焰冲天,岩浆奔流。此地火山活跃,地热丰富,是铁匠锻造的理想之地,但也极其危险。,square,"52,32","54,34",8,
|
||||
112,沃土良田,土地肥沃,五谷丰登。此地土壤深厚,雨水充沛,是农民耕种的黄金宝地。,rectangle,"33,25","37,29",,8
|
||||
113,幽冥毒泽,终年被五色瘴气笼罩,毒虫遍地。凡人入之即化为白骨,唯有修习毒功者视此处为无上洞天。,rectangle,"42,30","45,33",9,
|
||||
114,十万大山,苍茫群山连绵不绝,乃是妖族祖地。山势险峻,道路难行。,
|
||||
115,紫竹幽境,紫竹成林,灵气清冽。风过林间若奏仙乐,在此静修可涤荡心魔,感悟天地自然之道。,
|
||||
116,凛霜荒原,寸草不生,冻土千尺。此地生机绝灭,却蕴含着极致的阴寒灵气,偶有万年玄冰出世。,
|
||||
117,碎星戈壁,飞沙走石,狂风如刀。传说曾有星辰陨落于此,至今仍残存着狂暴的星辰之力与天外陨铁。,
|
||||
118,蓬莱遗岛,孤悬海外,云雾缭绕。岛上灵泉喷涌,奇花异草遍布,灵气浓郁远超内陆,是海外散修向往之地。,
|
||||
id,name,desc,animal_ids,plant_ids
|
||||
ID必须以1开头,,,该区域分布的动物物种IDs,该区域分布的植物物种IDs
|
||||
101,平原地带,地势平坦,灵气平和。是初学修炼者打基础和建立宗门的理想之地。,3.0,
|
||||
102,西域流沙,茫茫大漠,黄沙漫天。此地气候干燥,日夜温差极大,是沙漠商队的必经之路。,,3.0
|
||||
103,南疆蛮荒,古木参天,藤蔓缠绕。此地森林茂密,野兽众多,是采集药材和狩猎的危险之地。,4.0,
|
||||
104,极北冰原,千里冰封,万年不化。此地严寒刺骨,风雪交加,只有最坚韧的冒险者才能在此生存。,,4.0
|
||||
105,无边碧海,浩瀚无垠,波涛汹涌。此地风浪险恶,暗礁密布,是海商和渔民的挑战之海。,5.0,
|
||||
106,天河奔流,一江春水向东流,奔腾不息入东海。此河贯穿东西,水流湍急,是重要的交通要道。,,5.0
|
||||
107,青峰山脉,连绵起伏,直插云霄。此地山势险峻,多有奇石异洞,是探险者寻宝的热门之地。,6.0,
|
||||
108,万丈雪峰,雪峰皑皑,寒风刺骨。此地终年积雪,山路崎岖,是登山者的终极挑战。,,6.0
|
||||
109,碧野千里,芳草萋萋,一望无际。此地水草丰美,牛羊成群,是游牧民族的天然牧场。,7.0,
|
||||
110,青云林海,古树参天,绿意盎然。此地森林广袤,物产丰富,是伐木工和猎人的主要活动区域。,,7.0
|
||||
111,炎狱火山,烈焰冲天,岩浆奔流。此地火山活跃,地热丰富,是铁匠锻造的理想之地,但也极其危险。,8.0,
|
||||
112,沃土良田,土地肥沃,五谷丰登。此地土壤深厚,雨水充沛,是农民耕种的黄金宝地。,,8.0
|
||||
113,幽冥毒泽,终年被五色瘴气笼罩,毒虫遍地。凡人入之即化为白骨,唯有修习毒功者视此处为无上洞天。,9.0,
|
||||
114,十万大山,苍茫群山连绵不绝,乃是妖族祖地。山势险峻,道路难行。,,
|
||||
115,紫竹幽境,紫竹成林,灵气清冽。风过林间若奏仙乐,在此静修可涤荡心魔,感悟天地自然之道。,,
|
||||
116,凛霜荒原,寸草不生,冻土千尺。此地生机绝灭,却蕴含着极致的阴寒灵气,偶有万年玄冰出世。,,
|
||||
117,碎星戈壁,飞沙走石,狂风如刀。传说曾有星辰陨落于此,至今仍残存着狂暴的星辰之力与天外陨铁。,,
|
||||
118,蓬莱遗岛,孤悬海外,云雾缭绕。岛上灵泉喷涌,奇花异草遍布,灵气浓郁远超内陆,是海外散修向往之地。,,
|
||||
|
||||
|
@@ -1,16 +1,16 @@
|
||||
sect_id,sect_name,headquarter_name,headquarter_desc
|
||||
"与 sect.csv 的 id 对应","宗门名","宗门驻地名称","宗门驻地描述"
|
||||
1,明心剑宗,连霞山,连霞山坐忘峰下,是名门仙山的主峰。山上设有弟子修炼之地,环境清幽。
|
||||
2,百兽宗,玄灵洞,玄灵洞位于十万大山深处,洞府依托一座千丈高的灵山开凿而成,洞口宽达百丈,形似猛兽张口。洞内纵横交错,共有九十九层洞天,每层都驯养着不同品阶的灵兽妖兽。
|
||||
3,水镜宗,碧波湖,水镜宗建于碧波湖心的三座仙岛之上,湖水清澈如镜,终年波澜不兴。三岛之间以水晶长桥相连,宫殿楼阁皆为水晶琉璃所筑,通体透明,倒映湖中,虚实难辨。
|
||||
4,冥王宗,七鬼角,冥王宗位于七鬼角群岛上,宗门四面临海,周围暗礁密布,巨浪狂潮起落之间,在暗礁周围生成无数令人望而生畏的暗流漩涡。群岛周围千万年来投入了多少凶魂厉鬼,海浪起啸之时,万鬼齐哭,遮天蔽日。
|
||||
5,朱勾宗,明玉山,明玉山通体由罕见的明玉石构成,山体呈现出诡异的青白色泽,在月光下更是泛着幽冷的光芒。山中宫殿楼阁皆为玉石所筑,看似华丽却处处暗藏杀机。殿宇之间机关重重,暗道密布。
|
||||
6,合欢宗,桃花岛,合欢宗坐落于东海桃花岛上,岛上四季桃花盛开,永不凋零,花瓣随风飘舞,铺满整座仙岛。宗门建筑以粉红色调为主,雕梁画栋,极尽奢华。殿宇楼阁之间云雾缭绕,隐约可见成双成对的弟子在其间修炼。
|
||||
7,镇魂宗,落魂海,位于极南海上。镇魂海常年有妖兽作乱,海域凶险。宗门建筑立于海中礁岛之上,以镇魂钟为中心,钟声可震慑方圆千里之内的妖魂。
|
||||
8,幽魂噬影宗,鬼门湖,鬼门湖位于原始森林深处,重重大山围拢的平原地带。参天巨木、缠绕藤蔓、终日浮游不散的瘴气,还有因宗门秘法而生就的层层迷雾,将这里与外界完全隔离,透不进一点光来。
|
||||
9,千帆城,天星海,千帆城建于天星海中央的巨型浮岛之上,整座城池由无数法器拼接而成,城外海面上停泊着数以千计的法器灵舟,帆樯如林,蔚为壮观。城中高塔林立,每座塔顶都有炼器炉火日夜不息,烟柱冲天。
|
||||
10,妙化宗,心园,四季如春,百花不谢。园内亭台楼阁错落,丝竹之声终日不绝,看似人间仙境,实则暗藏无数音杀禁制。
|
||||
11,回玄宗,诸隐山,诸隐山云雾缭绕,山势晦暗不明。整座山脉被无数重叠的禁制大阵覆盖,一步一景,一步一险,若无通形令牌,外人踏入半步便会迷失其中。
|
||||
12,不夜城,大千光极城,常年笼罩在绚丽的极光之下。城池由万年寒冰与玄铁铸就,在漫天极光的映照下流光溢彩,宛如神迹,是极寒之地唯一的温暖所在。
|
||||
13,天行健宗,浩然峰,位于通玄界中腹,山势雄奇挺拔,直插云霄。峰顶常年白云浩渺,紫气东来,书声琅琅之声可传十里,是天下浩然正气汇聚之地。
|
||||
14,噬魔宗,陷空山,位于极西之地的险恶山脉,山势如犬牙交错,穷山恶水。山中常有血雾弥漫,怪石嶙峋,随处可见被吸干精血的兽骨枯尸,令人闻风丧胆。
|
||||
id,name,desc,sect_id
|
||||
ID以4开头(400+sect_id),宗门驻地名称,宗门驻地描述,对应宗门ID
|
||||
401,连霞山,连霞山坐忘峰下,是名门仙山的主峰。山上设有弟子修炼之地,环境清幽。,1
|
||||
402,玄灵洞,玄灵洞位于十万大山深处,洞府依托一座千丈高的灵山开凿而成,洞口宽达百丈,形似猛兽张口。洞内纵横交错,共有九十九层洞天,每层都驯养着不同品阶的灵兽妖兽。,2
|
||||
403,碧波湖,水镜宗建于碧波湖心的三座仙岛之上,湖水清澈如镜,终年波澜不兴。三岛之间以水晶长桥相连,宫殿楼阁皆为水晶琉璃所筑,通体透明,倒映湖中,虚实难辨。,3
|
||||
404,七鬼角,冥王宗位于七鬼角群岛上,宗门四面临海,周围暗礁密布,巨浪狂潮起落之间,在暗礁周围生成无数令人望而生畏的暗流漩涡。群岛周围千万年来投入了多少凶魂厉鬼,海浪起啸之时,万鬼齐哭,遮天蔽日。,4
|
||||
405,明玉山,明玉山通体由罕见的明玉石构成,山体呈现出诡异的青白色泽,在月光下更是泛着幽冷的光芒。山中宫殿楼阁皆为玉石所筑,看似华丽却处处暗藏杀机。殿宇之间机关重重,暗道密布。,5
|
||||
406,桃花岛,合欢宗坐落于东海桃花岛上,岛上四季桃花盛开,永不凋零,花瓣随风飘舞,铺满整座仙岛。宗门建筑以粉红色调为主,雕梁画栋,极尽奢华。殿宇楼阁之间云雾缭绕,隐约可见成双成对的弟子在其间修炼。,6
|
||||
407,落魂海,位于极南海上。镇魂海常年有妖兽作乱,海域凶险。宗门建筑立于海中礁岛之上,以镇魂钟为中心,钟声可震慑方圆千里之内的妖魂。,7
|
||||
408,鬼门湖,鬼门湖位于原始森林深处,重重大山围拢的平原地带。参天巨木、缠绕藤蔓、终日浮游不散的瘴气,还有因宗门秘法而生就的层层迷雾,将这里与外界完全隔离,透不进一点光来。,8
|
||||
409,天星海,千帆城建于天星海中央的巨型浮岛之上,整座城池由无数法器拼接而成,城外海面上停泊着数以千计的法器灵舟,帆樯如林,蔚为壮观。城中高塔林立,每座塔顶都有炼器炉火日夜不息,烟柱冲天。,9
|
||||
410,心园,四季如春,百花不谢。园内亭台楼阁错落,丝竹之声终日不绝,看似人间仙境,实则暗藏无数音杀禁制。,10
|
||||
411,诸隐山,诸隐山云雾缭绕,山势晦暗不明。整座山脉被无数重叠的禁制大阵覆盖,一步一景,一步一险,若无通形令牌,外人踏入半步便会迷失其中。,11
|
||||
412,大千光极城,常年笼罩在绚丽的极光之下。城池由万年寒冰与玄铁铸就,在漫天极光的映照下流光溢彩,宛如神迹,是极寒之地唯一的温暖所在。,12
|
||||
413,浩然峰,位于通玄界中腹,山势雄奇挺拔,直插云霄。峰顶常年白云浩渺,紫气东来,书声琅琅之声可传十里,是天下浩然正气汇聚之地。,13
|
||||
414,陷空山,位于极西之地的险恶山脉,山势如犬牙交错,穷山恶水。山中常有血雾弥漫,怪石嶙峋,随处可见被吸干精血的兽骨枯尸,令人闻风丧胆。,14
|
||||
|
||||
|
Reference in New Issue
Block a user