Files
cultivation-world-simulator/tools/map_creator/main.py
2025-12-03 22:38:53 +08:00

315 lines
12 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 os
import csv
import json
import glob
from flask import Flask, render_template, jsonify, request, send_from_directory
app = Flask(__name__)
# --- 配置路径 ---
BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "../../"))
ASSETS_DIR = os.path.join(BASE_DIR, "assets")
CONFIG_DIR = os.path.join(BASE_DIR, "static", "game_configs")
OUTPUT_DIR = os.path.dirname(__file__)
# 地图尺寸
MAP_WIDTH = 70
MAP_HEIGHT = 50
@app.route('/')
def index():
return render_template('index.html')
@app.route('/tiles/<path:filename>')
def serve_tile_image(filename):
return send_from_directory(os.path.join(ASSETS_DIR, "tiles"), filename)
@app.route('/sects/<path:filename>')
def serve_sect_image(filename):
return send_from_directory(os.path.join(ASSETS_DIR, "sects"), filename)
@app.route('/cities/<path:filename>')
def serve_city_image(filename):
return send_from_directory(os.path.join(ASSETS_DIR, "cities"), filename)
# 显式定义的区域-地形映射表
# Key: 区域名称, Value: {"t": tile_name, "type": "tile" | "sect" | "city"}
REGION_TILE_MAP = {
# --- 普通区域 (Normal Regions) ---
"平原地带": {"t": "plain", "type": "tile"},
"西域流沙": {"t": "desert", "type": "tile"},
"南疆蛮荒": {"t": "rainforest", "type": "tile"},
"极北冰原": {"t": "glacier", "type": "tile"},
"无边碧海": {"t": "sea", "type": "tile"},
"天河奔流": {"t": "water", "type": "tile"},
"青峰山脉": {"t": "mountain", "type": "tile"},
"万丈雪峰": {"t": "snow_mountain", "type": "tile"},
"碧野千里": {"t": "grassland", "type": "tile"},
"青云林海": {"t": "forest", "type": "tile"},
"炎狱火山": {"t": "volcano", "type": "tile"},
"沃土良田": {"t": "farm", "type": "tile"},
"幽冥毒泽": {"t": "swamp", "type": "tile"},
"十万大山": {"t": "mountain", "type": "tile"},
"紫竹幽境": {"t": "bamboo", "type": "tile"},
"凛霜荒原": {"t": "tundra", "type": "tile"},
"碎星戈壁": {"t": "gobi", "type": "tile"},
"蓬莱遗岛": {"t": "island", "type": "tile"},
# --- 城市区域 (City Regions) ---
"青云城": {"t": "青云城", "type": "city"},
"沙月城": {"t": "沙月城", "type": "city"},
"翠林城": {"t": "翠林城", "type": "city"},
"揽月城": {"t": "揽月城", "type": "city"},
"沧澜城": {"t": "沧澜城", "type": "city"},
# --- 洞府遗迹 (Cultivate Regions) ---
"太白金府": {"t": "cave", "type": "tile"},
"青木洞天": {"t": "cave", "type": "tile"},
"玄水秘境": {"t": "cave", "type": "tile"},
"离火洞府": {"t": "cave", "type": "tile"},
"厚土玄宫": {"t": "cave", "type": "tile"},
"古越遗迹": {"t": "ruin", "type": "tile"},
"沧海遗迹": {"t": "ruin", "type": "tile"},
}
def get_default_tile(name, type_tag, all_tiles, all_sect_tiles, all_city_tiles):
"""根据区域名称和类型查找默认 Tile"""
# 1. 查表 (精确匹配)
if name in REGION_TILE_MAP:
return REGION_TILE_MAP[name]
# 2. 宗门:尝试匹配 Sect 图片
if type_tag == 'sect':
# 尝试直接匹配宗门名
if name in all_sect_tiles:
return {"t": name, "type": "sect"}
# 尝试部分匹配
for t in all_sect_tiles:
if t in name or name in t:
return {"t": t, "type": "sect"}
return {"t": "mountain", "type": "tile"} # 默认建在山上
# 3. 城市
if type_tag == 'city':
if name in all_city_tiles:
return {"t": name, "type": "city"}
return {"t": "city", "type": "tile"}
# 4. 包含特定关键词的兜底逻辑 (针对未在表中的新区域)
name_lower = name.lower()
if '' in name_lower or '' in name_lower or '秘境' in name_lower:
return {"t": "cave", "type": "tile"}
if '遗迹' in name_lower:
return {"t": "ruin", "type": "tile"}
# 默认
return {"t": "plain", "type": "tile"}
@app.route('/api/init')
def init_data():
"""初始化数据读取Tiles列表和Region配置"""
# 1. 获取所有 Tile 图片名称
tile_files = glob.glob(os.path.join(ASSETS_DIR, "tiles", "*.png"))
# 过滤切片 (name_0.png)
tiles = [os.path.splitext(os.path.basename(f))[0] for f in tile_files if not os.path.splitext(os.path.basename(f))[0][-2:] in ['_0', '_1', '_2', '_3']]
tiles.sort()
# 2. 获取所有 Sect 图片名称
sect_files = glob.glob(os.path.join(ASSETS_DIR, "sects", "*.png"))
sect_tiles = [os.path.splitext(os.path.basename(f))[0] for f in sect_files if not os.path.splitext(os.path.basename(f))[0][-2:] in ['_0', '_1', '_2', '_3']]
sect_tiles.sort()
# 3. 获取所有 City 图片名称 (保留扩展名映射)
city_files = glob.glob(os.path.join(ASSETS_DIR, "cities", "*.*"))
# 过滤非图片
city_files = [f for f in city_files if f.lower().endswith(('.png', '.jpg', '.jpeg'))]
city_tiles_map = {} # name -> filename
city_tiles = []
for f in city_files:
name = os.path.splitext(os.path.basename(f))[0]
if name[-2:] in ['_0', '_1', '_2', '_3']: continue
filename = os.path.basename(f)
city_tiles.append(name)
city_tiles_map[name] = filename
city_tiles.sort()
# 4. 读取 sect.csv 建立 sect_id -> sect_name 映射
sect_id_to_name = {}
sect_csv_path = os.path.join(CONFIG_DIR, "sect.csv")
if os.path.exists(sect_csv_path):
with open(sect_csv_path, 'r', encoding='utf-8-sig') as f:
reader = csv.reader(f)
rows = list(reader)
data_rows = rows[2:] if len(rows) > 2 else []
for row in data_rows:
if len(row) >= 2:
try:
sid = int(row[0])
sname = row[1]
sect_id_to_name[sid] = sname
except ValueError:
continue
# 5. 读取 Region 配置
regions = []
def parse_csv(filename, id_col, name_col, type_tag, sect_id_col=None):
path = os.path.join(CONFIG_DIR, filename)
if not os.path.exists(path):
print(f"Warning: {path} not found")
return
with open(path, 'r', encoding='utf-8-sig') as f:
reader = csv.reader(f)
rows = list(reader)
# 跳过前两行 (header 和 description)
data_rows = rows[2:] if len(rows) > 2 else []
for row in data_rows:
if len(row) <= max(id_col, name_col): continue
try:
r_id = int(row[id_col])
name = row[name_col]
# 简单的 hash 颜色生成
color_hash = hash(f"{type_tag}_{r_id}") & 0xFFFFFF
color = f"#{color_hash:06x}"
# 对于 sect 类型,使用 sect_id 查找对应的宗门名称
bind_name = name
if type_tag == 'sect' and sect_id_col is not None and len(row) > sect_id_col:
try:
sid = int(row[sect_id_col])
bind_name = sect_id_to_name.get(sid, name)
except ValueError:
pass
# 计算默认绑定 Tile
bind_info = get_default_tile(bind_name, type_tag, tiles, sect_tiles, city_tiles)
regions.append({
"id": r_id,
"name": name,
"type": type_tag,
"color": color,
"bindTile": bind_info["t"],
"bindTileType": bind_info["type"]
})
except ValueError:
continue
# 读取四种配置
# normal_region.csv: id=0, name=1
parse_csv("normal_region.csv", 0, 1, "normal")
# sect_region.csv: id=0, name=1, sect_id=3
parse_csv("sect_region.csv", 0, 1, "sect", sect_id_col=3)
# cultivate_region.csv: id=0, name=1
parse_csv("cultivate_region.csv", 0, 1, "cultivate")
# city_region.csv: id=0, name=1
parse_csv("city_region.csv", 0, 1, "city")
# 排序优先级normal > sect > cultivate > city > 其他
def sort_priority(r):
if r['type'] == 'normal': return 0
if r['type'] == 'sect': return 1
if r['type'] == 'cultivate': return 2
if r['type'] == 'city': return 3
return 4
regions.sort(key=lambda x: (sort_priority(x), x['id']))
# 3. 尝试读取现有的地图数据
saved_map = load_map_data()
return jsonify({
"width": MAP_WIDTH,
"height": MAP_HEIGHT,
"tiles": tiles,
"sectTiles": sect_tiles,
"cityTiles": city_tiles,
"cityTilesMap": city_tiles_map,
"regions": regions,
"savedMap": saved_map
})
@app.route('/api/save', methods=['POST'])
def save_map():
data = request.json
grid = data.get('grid', []) # list of {x, y, t, r}
tile_csv_path = os.path.join(OUTPUT_DIR, "tile_map.csv")
region_csv_path = os.path.join(OUTPUT_DIR, "region_map.csv")
try:
# 初始化二维数组 (Matrix)
# MAP_HEIGHT 行, MAP_WIDTH 列
tile_matrix = [["plain" for _ in range(MAP_WIDTH)] for _ in range(MAP_HEIGHT)]
region_matrix = [[-1 for _ in range(MAP_WIDTH)] for _ in range(MAP_HEIGHT)]
# 填充数据
for cell in grid:
x, y = cell['x'], cell['y']
if 0 <= x < MAP_WIDTH and 0 <= y < MAP_HEIGHT:
tile_matrix[y][x] = cell['t']
if cell.get('r') is not None:
region_matrix[y][x] = cell['r']
# 保存 Tile Map (矩阵形式)
with open(tile_csv_path, 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerows(tile_matrix)
# 保存 Region Map (矩阵形式)
with open(region_csv_path, 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerows(region_matrix)
return jsonify({"status": "success", "message": "Map saved successfully (Matrix Format)"})
except Exception as e:
return jsonify({"status": "error", "message": str(e)}), 500
def load_map_data():
"""读取矩阵格式 CSV 并重建 Grid 状态"""
tile_csv_path = os.path.join(OUTPUT_DIR, "tile_map.csv")
region_csv_path = os.path.join(OUTPUT_DIR, "region_map.csv")
loaded_data = {} # key: "x,y", value: {t: ..., r: ...}
# 读取 Tile Matrix
if os.path.exists(tile_csv_path):
with open(tile_csv_path, 'r', encoding='utf-8') as f:
reader = csv.reader(f)
for y, row in enumerate(reader):
if y >= MAP_HEIGHT: break
for x, val in enumerate(row):
if x >= MAP_WIDTH: break
key = f"{x},{y}"
loaded_data[key] = {"t": val}
# 读取 Region Matrix
if os.path.exists(region_csv_path):
with open(region_csv_path, 'r', encoding='utf-8') as f:
reader = csv.reader(f)
for y, row in enumerate(reader):
if y >= MAP_HEIGHT: break
for x, val in enumerate(row):
if x >= MAP_WIDTH: break
try:
rid = int(val)
if rid != -1:
key = f"{x},{y}"
if key not in loaded_data:
loaded_data[key] = {"t": "plain"} # 默认
loaded_data[key]["r"] = rid
except ValueError:
continue
return loaded_data
if __name__ == '__main__':
print(f"Starting Map Creator at http://127.0.0.1:5000")
print(f"Assets Dir: {ASSETS_DIR}")
app.run(debug=True, port=5000)