|
|
@@ -119,7 +119,9 @@ class ConfigManagerGUI:
|
|
|
'金币奖励': int,
|
|
|
'钻石奖励': int
|
|
|
},
|
|
|
- 'format_type': 'horizontal'
|
|
|
+ 'format_type': 'horizontal',
|
|
|
+ 'multi_sheet': True, # 标记为多工作表文件
|
|
|
+ 'sheet_names': ['关卡基础配置', '波次配置', '敌人配置', '敌人名称映射'] # 需要读取的工作表
|
|
|
},
|
|
|
'技能配置表.xlsx': {
|
|
|
'json_path': self.project_root / "assets/resources/data/skill.json",
|
|
|
@@ -387,12 +389,22 @@ class ConfigManagerGUI:
|
|
|
continue
|
|
|
|
|
|
if file_config:
|
|
|
- self.config_data.update(file_config)
|
|
|
-
|
|
|
- # 显示预览
|
|
|
- self.preview_text.insert(tk.END, f"找到 {len(file_config)} 个配置参数:\n")
|
|
|
- for key, value in file_config.items():
|
|
|
- self.preview_text.insert(tk.END, f" {key}: {value}\n")
|
|
|
+ # 检查file_config的类型,如果是列表则特殊处理
|
|
|
+ if isinstance(file_config, list):
|
|
|
+ # 对于关卡配置等返回列表的情况
|
|
|
+ self.preview_text.insert(tk.END, f"找到 {len(file_config)} 个配置项:\n")
|
|
|
+ for i, item in enumerate(file_config):
|
|
|
+ self.preview_text.insert(tk.END, f" 配置项 {i+1}: {item}\n")
|
|
|
+ # 将列表数据存储到config_data中
|
|
|
+ self.config_data[file_path.stem] = file_config
|
|
|
+ else:
|
|
|
+ # 原有的字典处理逻辑
|
|
|
+ self.config_data.update(file_config)
|
|
|
+
|
|
|
+ # 显示预览
|
|
|
+ self.preview_text.insert(tk.END, f"找到 {len(file_config)} 个配置参数:\n")
|
|
|
+ for key, value in file_config.items():
|
|
|
+ self.preview_text.insert(tk.END, f" {key}: {value}\n")
|
|
|
else:
|
|
|
self.preview_text.insert(tk.END, "未找到有效的配置数据\n")
|
|
|
|
|
|
@@ -417,17 +429,38 @@ class ConfigManagerGUI:
|
|
|
return config
|
|
|
|
|
|
try:
|
|
|
- # 检查是否有指定的工作表名称
|
|
|
- sheet_name = None
|
|
|
- if self.current_mapping and 'sheet_name' in self.current_mapping:
|
|
|
- sheet_name = self.current_mapping['sheet_name']
|
|
|
-
|
|
|
- # 读取Excel文件
|
|
|
- if sheet_name:
|
|
|
- df = pd.read_excel(file_path, sheet_name=sheet_name)
|
|
|
+ # 检查是否为多工作表文件
|
|
|
+ if self.current_mapping and self.current_mapping.get('multi_sheet', False):
|
|
|
+ # 处理多工作表文件
|
|
|
+ sheet_names = self.current_mapping.get('sheet_names', [])
|
|
|
+ all_sheets_data = {}
|
|
|
+
|
|
|
+ for sheet_name in sheet_names:
|
|
|
+ try:
|
|
|
+ df = pd.read_excel(file_path, sheet_name=sheet_name)
|
|
|
+ all_sheets_data[sheet_name] = df
|
|
|
+ print(f"成功读取工作表: {sheet_name}, 数据行数: {len(df)}")
|
|
|
+ except Exception as e:
|
|
|
+ print(f"读取工作表 {sheet_name} 时出错: {e}")
|
|
|
+
|
|
|
+ # 对于关卡配置,需要特殊处理多工作表数据
|
|
|
+ if '关卡配置表' in file_path.name:
|
|
|
+ config = self.parse_level_multi_sheet_data(all_sheets_data, file_path.name)
|
|
|
+ else:
|
|
|
+ # 其他多工作表文件的处理逻辑
|
|
|
+ config = self.parse_multi_sheet_data(all_sheets_data, file_path.name)
|
|
|
else:
|
|
|
- df = pd.read_excel(file_path)
|
|
|
- config = self.parse_config_data(df, file_path.name)
|
|
|
+ # 处理单工作表文件
|
|
|
+ sheet_name = None
|
|
|
+ if self.current_mapping and 'sheet_name' in self.current_mapping:
|
|
|
+ sheet_name = self.current_mapping['sheet_name']
|
|
|
+
|
|
|
+ # 读取Excel文件
|
|
|
+ if sheet_name:
|
|
|
+ df = pd.read_excel(file_path, sheet_name=sheet_name)
|
|
|
+ else:
|
|
|
+ df = pd.read_excel(file_path)
|
|
|
+ config = self.parse_config_data(df, file_path.name)
|
|
|
|
|
|
except Exception as e:
|
|
|
print(f"读取Excel文件 {file_path.name} 时出错: {e}")
|
|
|
@@ -489,6 +522,103 @@ class ConfigManagerGUI:
|
|
|
|
|
|
return config
|
|
|
|
|
|
+ def parse_level_multi_sheet_data(self, all_sheets_data, filename):
|
|
|
+ """解析关卡配置的多工作表数据"""
|
|
|
+ config = []
|
|
|
+
|
|
|
+ try:
|
|
|
+ # 获取各个工作表的数据
|
|
|
+ basic_config = all_sheets_data.get('关卡基础配置')
|
|
|
+ wave_config = all_sheets_data.get('波次配置')
|
|
|
+ enemy_config = all_sheets_data.get('敌人配置')
|
|
|
+ enemy_mapping = all_sheets_data.get('敌人名称映射')
|
|
|
+
|
|
|
+ if basic_config is None:
|
|
|
+ print("错误: 未找到关卡基础配置工作表")
|
|
|
+ return config
|
|
|
+
|
|
|
+ # 创建敌人名称映射字典
|
|
|
+ enemy_name_map = {}
|
|
|
+ if enemy_mapping is not None:
|
|
|
+ for _, row in enemy_mapping.iterrows():
|
|
|
+ if pd.notna(row['中文名称']) and pd.notna(row['英文ID']):
|
|
|
+ enemy_name_map[row['中文名称']] = row['英文ID']
|
|
|
+
|
|
|
+ # 处理每个关卡
|
|
|
+ for _, basic_row in basic_config.iterrows():
|
|
|
+ if pd.isna(basic_row['关卡ID']):
|
|
|
+ continue
|
|
|
+
|
|
|
+ level_id = str(basic_row['关卡ID'])
|
|
|
+ level_data = {
|
|
|
+ 'levelId': level_id,
|
|
|
+ 'name': str(basic_row['关卡名称']) if pd.notna(basic_row['关卡名称']) else '',
|
|
|
+ 'scene': str(basic_row['场景']) if pd.notna(basic_row['场景']) else 'grassland',
|
|
|
+ 'description': str(basic_row['描述']) if pd.notna(basic_row['描述']) else '',
|
|
|
+ 'availableWeapons': str(basic_row['可用武器']).split(', ') if pd.notna(basic_row['可用武器']) else [],
|
|
|
+ 'timeLimit': 300, # 默认值
|
|
|
+ 'difficulty': 'normal', # 默认值
|
|
|
+ 'healthMultiplier': 1.0, # 默认值
|
|
|
+ 'waves': []
|
|
|
+ }
|
|
|
+
|
|
|
+ # 获取该关卡的波次配置
|
|
|
+ if wave_config is not None:
|
|
|
+ level_waves = wave_config[wave_config['关卡ID'] == level_id]
|
|
|
+
|
|
|
+ for _, wave_row in level_waves.iterrows():
|
|
|
+ wave_id = int(wave_row['波次ID']) if pd.notna(wave_row['波次ID']) else 1
|
|
|
+
|
|
|
+ # 获取该波次的敌人配置
|
|
|
+ wave_enemies = []
|
|
|
+ if enemy_config is not None:
|
|
|
+ wave_enemy_data = enemy_config[
|
|
|
+ (enemy_config['关卡ID'] == level_id) &
|
|
|
+ (enemy_config['波次ID'] == wave_id)
|
|
|
+ ]
|
|
|
+
|
|
|
+ for _, enemy_row in wave_enemy_data.iterrows():
|
|
|
+ enemy_type = str(enemy_row['敌人类型']) if pd.notna(enemy_row['敌人类型']) else '普通僵尸'
|
|
|
+ # 使用映射表转换敌人名称
|
|
|
+ enemy_id = enemy_name_map.get(enemy_type, enemy_type)
|
|
|
+
|
|
|
+ enemy_data = {
|
|
|
+ 'enemyType': enemy_id,
|
|
|
+ 'count': int(enemy_row['数量']) if pd.notna(enemy_row['数量']) else 1,
|
|
|
+ 'spawnInterval': float(enemy_row['生成间隔']) if pd.notna(enemy_row['生成间隔']) else 2.0,
|
|
|
+ 'spawnDelay': float(enemy_row['生成延迟']) if pd.notna(enemy_row['生成延迟']) else 0.0,
|
|
|
+ 'characteristics': str(enemy_row['特性']) if pd.notna(enemy_row['特性']) else ''
|
|
|
+ }
|
|
|
+ wave_enemies.append(enemy_data)
|
|
|
+
|
|
|
+ wave_data = {
|
|
|
+ 'waveId': wave_id,
|
|
|
+ 'enemies': wave_enemies
|
|
|
+ }
|
|
|
+ level_data['waves'].append(wave_data)
|
|
|
+
|
|
|
+ config.append(level_data)
|
|
|
+ print(f"处理关卡: {level_id}, 波次数: {len(level_data['waves'])}")
|
|
|
+
|
|
|
+ print(f"成功解析关卡配置,共 {len(config)} 个关卡")
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ print(f"解析关卡多工作表数据时出错: {e}")
|
|
|
+ import traceback
|
|
|
+ traceback.print_exc()
|
|
|
+
|
|
|
+ return config
|
|
|
+
|
|
|
+ def parse_multi_sheet_data(self, all_sheets_data, filename):
|
|
|
+ """解析通用多工作表数据"""
|
|
|
+ # 这里可以添加其他多工作表文件的处理逻辑
|
|
|
+ # 目前只返回第一个工作表的数据
|
|
|
+ if all_sheets_data:
|
|
|
+ first_sheet_name = list(all_sheets_data.keys())[0]
|
|
|
+ first_sheet_data = all_sheets_data[first_sheet_name]
|
|
|
+ return self.parse_config_data(first_sheet_data, filename)
|
|
|
+ return []
|
|
|
+
|
|
|
def parse_config_data(self, df, filename):
|
|
|
"""解析配置数据(支持多种格式,需要pandas)"""
|
|
|
config = {}
|
|
|
@@ -736,21 +866,89 @@ class ConfigManagerGUI:
|
|
|
print(f"横向表格配置导入开始...")
|
|
|
print(f"配置数据结构: {list(self.config_data.keys()) if self.config_data else 'None'}")
|
|
|
|
|
|
- if 'items' not in self.config_data:
|
|
|
- print(f"错误: 配置数据中缺少'items'字段")
|
|
|
- print(f"实际配置数据: {self.config_data}")
|
|
|
- messagebox.showwarning("警告", "横向表格数据格式错误:缺少'items'字段")
|
|
|
- return
|
|
|
+ # 检查是否是关卡配置(目录类型)
|
|
|
+ is_level_config = str(self.json_config_path).endswith('levels')
|
|
|
|
|
|
- items = self.config_data['items']
|
|
|
- print(f"配置项数量: {len(items) if items else 0}")
|
|
|
- if not items:
|
|
|
- messagebox.showwarning("警告", "没有有效的配置项")
|
|
|
- return
|
|
|
+ # 对于关卡配置,支持多种数据格式
|
|
|
+ if is_level_config:
|
|
|
+ if isinstance(self.config_data, list):
|
|
|
+ # 多工作表数据格式:直接是关卡数据列表
|
|
|
+ print("检测到关卡配置的多工作表数据格式")
|
|
|
+ items = None # 不需要items字段
|
|
|
+ elif 'items' in self.config_data:
|
|
|
+ # 传统的items数据格式
|
|
|
+ print("检测到关卡配置的传统items数据格式")
|
|
|
+ items = self.config_data['items']
|
|
|
+ elif isinstance(self.config_data, dict) and len(self.config_data) == 1:
|
|
|
+ # 检查是否是包装在单个键中的配置数据
|
|
|
+ key = list(self.config_data.keys())[0]
|
|
|
+ wrapped_data = self.config_data[key]
|
|
|
+ print(f"检测到包装的关卡配置数据,键名: {key}")
|
|
|
+ if isinstance(wrapped_data, list):
|
|
|
+ print("提取到关卡配置的多工作表数据格式")
|
|
|
+ self.config_data = wrapped_data # 更新配置数据为实际内容
|
|
|
+ items = None
|
|
|
+ elif isinstance(wrapped_data, dict) and 'items' in wrapped_data:
|
|
|
+ print("提取到关卡配置的传统items数据格式")
|
|
|
+ self.config_data = wrapped_data
|
|
|
+ items = wrapped_data['items']
|
|
|
+ else:
|
|
|
+ print(f"错误: 包装数据格式不正确: {type(wrapped_data)}")
|
|
|
+ error_msg = f"关卡配置数据格式错误:包装数据不是列表或包含items的字典\n\n包装键: {key}\n数据类型: {type(wrapped_data)}"
|
|
|
+ messagebox.showwarning("警告", error_msg)
|
|
|
+ self.preview_text.insert(tk.END, f"\n=== 错误信息 ===\n")
|
|
|
+ self.preview_text.insert(tk.END, f"关卡配置数据格式错误:包装数据不是列表或包含items的字典\n")
|
|
|
+ self.preview_text.insert(tk.END, f"包装键: {key}\n数据类型: {type(wrapped_data)}\n")
|
|
|
+ return
|
|
|
+ else:
|
|
|
+ print(f"错误: 关卡配置数据格式不正确")
|
|
|
+ print(f"实际配置数据: {self.config_data}")
|
|
|
+ print(f"配置数据类型: {type(self.config_data)}")
|
|
|
+ print(f"配置数据键: {list(self.config_data.keys()) if isinstance(self.config_data, dict) else 'Not a dict'}")
|
|
|
+ error_msg = f"关卡配置数据格式错误:需要多工作表数据或items字段\n\n数据类型: {type(self.config_data)}\n数据内容: {str(self.config_data)[:200]}..."
|
|
|
+ messagebox.showwarning("警告", error_msg)
|
|
|
+ # 同时在GUI中显示错误信息
|
|
|
+ self.preview_text.insert(tk.END, f"\n=== 错误信息 ===\n")
|
|
|
+ self.preview_text.insert(tk.END, f"关卡配置数据格式错误:需要多工作表数据或items字段\n")
|
|
|
+ self.preview_text.insert(tk.END, f"数据类型: {type(self.config_data)}\n")
|
|
|
+ self.preview_text.insert(tk.END, f"数据内容: {str(self.config_data)[:500]}...\n")
|
|
|
+ return
|
|
|
+ else:
|
|
|
+ # 其他配置类型必须有items字段
|
|
|
+ if 'items' not in self.config_data:
|
|
|
+ print(f"错误: 配置数据中缺少'items'字段")
|
|
|
+ print(f"实际配置数据: {self.config_data}")
|
|
|
+ print(f"配置数据类型: {type(self.config_data)}")
|
|
|
+ print(f"配置数据键: {list(self.config_data.keys()) if isinstance(self.config_data, dict) else 'Not a dict'}")
|
|
|
+ error_msg = f"横向表格数据格式错误:缺少'items'字段\n\n数据类型: {type(self.config_data)}\n可用键: {list(self.config_data.keys()) if isinstance(self.config_data, dict) else 'Not a dict'}"
|
|
|
+ messagebox.showwarning("警告", error_msg)
|
|
|
+ # 同时在GUI中显示错误信息
|
|
|
+ self.preview_text.insert(tk.END, f"\n=== 错误信息 ===\n")
|
|
|
+ self.preview_text.insert(tk.END, f"横向表格数据格式错误:缺少'items'字段\n")
|
|
|
+ self.preview_text.insert(tk.END, f"数据类型: {type(self.config_data)}\n")
|
|
|
+ self.preview_text.insert(tk.END, f"可用键: {list(self.config_data.keys()) if isinstance(self.config_data, dict) else 'Not a dict'}\n")
|
|
|
+ return
|
|
|
+ items = self.config_data['items']
|
|
|
+
|
|
|
+ # 验证数据有效性
|
|
|
+ if items is not None:
|
|
|
+ print(f"配置项数量: {len(items) if items else 0}")
|
|
|
+ if not items:
|
|
|
+ messagebox.showwarning("警告", "没有有效的配置项")
|
|
|
+ return
|
|
|
+ elif isinstance(self.config_data, list):
|
|
|
+ print(f"关卡配置数量: {len(self.config_data)}")
|
|
|
+ if not self.config_data:
|
|
|
+ messagebox.showwarning("警告", "没有有效的关卡配置")
|
|
|
+ return
|
|
|
|
|
|
# 打印前几个配置项用于调试
|
|
|
- for i, item in enumerate(items[:3]):
|
|
|
- print(f"配置项{i}: {item}")
|
|
|
+ if items is not None:
|
|
|
+ for i, item in enumerate(items[:3]):
|
|
|
+ print(f"配置项{i}: {item}")
|
|
|
+ elif isinstance(self.config_data, list):
|
|
|
+ for i, item in enumerate(self.config_data[:3]):
|
|
|
+ print(f"关卡配置{i}: {item.get('levelId', 'Unknown')} - {item.get('name', 'Unknown')}")
|
|
|
|
|
|
# 读取现有JSON配置
|
|
|
print(f"JSON配置文件路径: {self.json_config_path}")
|
|
|
@@ -866,15 +1064,32 @@ class ConfigManagerGUI:
|
|
|
messagebox.showerror("错误", f"没有写入权限到目录: {levels_dir}\n请检查目录权限或以管理员身份运行")
|
|
|
return
|
|
|
|
|
|
- # 将Excel数据转换为JSON格式并保存为单独文件
|
|
|
+ # 检查数据格式:多工作表数据 vs 传统items数据
|
|
|
+ if isinstance(self.config_data, list):
|
|
|
+ # 新的多工作表数据格式:直接是关卡数据列表
|
|
|
+ print("使用多工作表数据格式")
|
|
|
+ level_configs = self.config_data
|
|
|
+ elif 'items' in self.config_data:
|
|
|
+ # 传统的items数据格式:需要转换
|
|
|
+ print("使用传统items数据格式")
|
|
|
+ level_configs = []
|
|
|
+ for item in items:
|
|
|
+ level_data = self._convert_level_data(item)
|
|
|
+ if level_data:
|
|
|
+ level_configs.append(level_data)
|
|
|
+ else:
|
|
|
+ print(f"错误: 未知的关卡配置数据格式: {type(self.config_data)}")
|
|
|
+ messagebox.showerror("错误", "关卡配置数据格式错误")
|
|
|
+ return
|
|
|
+
|
|
|
+ # 保存关卡配置文件
|
|
|
updated_count = 0
|
|
|
failed_count = 0
|
|
|
|
|
|
- for item in items:
|
|
|
+ for level_data in level_configs:
|
|
|
try:
|
|
|
- level_data = self._convert_level_data(item)
|
|
|
- if level_data and '关卡ID' in item and item['关卡ID']:
|
|
|
- level_id = item['关卡ID']
|
|
|
+ if level_data and 'levelId' in level_data and level_data['levelId']:
|
|
|
+ level_id = level_data['levelId']
|
|
|
level_file = levels_dir / f"{level_id}.json"
|
|
|
|
|
|
print(f"保存关卡配置: {level_file}")
|
|
|
@@ -882,7 +1097,7 @@ class ConfigManagerGUI:
|
|
|
json.dump(level_data, f, indent=2, ensure_ascii=False)
|
|
|
updated_count += 1
|
|
|
else:
|
|
|
- print(f"跳过无效的关卡数据: {item}")
|
|
|
+ print(f"跳过无效的关卡数据: {level_data}")
|
|
|
failed_count += 1
|
|
|
except Exception as e:
|
|
|
print(f"保存关卡配置时出错: {e}")
|
|
|
@@ -1028,24 +1243,61 @@ class ConfigManagerGUI:
|
|
|
def _convert_level_data(self, item):
|
|
|
"""转换关卡数据格式"""
|
|
|
try:
|
|
|
+ # 如果item已经是完整的关卡数据结构(来自多工作表解析),直接返回
|
|
|
+ if isinstance(item, dict) and 'waves' in item:
|
|
|
+ return item
|
|
|
+
|
|
|
+ # 处理传统的单行数据格式
|
|
|
# 处理可用武器字符串,转换为数组
|
|
|
available_weapons = []
|
|
|
if '可用武器' in item and item['可用武器']:
|
|
|
weapons_str = str(item['可用武器'])
|
|
|
available_weapons = [weapon.strip() for weapon in weapons_str.split(',')]
|
|
|
|
|
|
- return {
|
|
|
- "levelId": str(item.get('关卡ID', '')),
|
|
|
- "name": str(item.get('关卡名称', '')),
|
|
|
- "scene": str(item.get('场景', '')),
|
|
|
- "description": str(item.get('描述', '')),
|
|
|
- "weapons": available_weapons,
|
|
|
- "timeLimit": int(item.get('时间限制', 300)),
|
|
|
- "difficulty": str(item.get('难度', 'normal')),
|
|
|
- "healthMultiplier": float(item.get('生命倍数', 1.0)),
|
|
|
- "coinReward": int(item.get('金币奖励', 0)),
|
|
|
- "diamondReward": int(item.get('钻石奖励', 0))
|
|
|
+ # 获取关卡ID,用于读取现有配置
|
|
|
+ level_id = str(item.get('关卡ID', ''))
|
|
|
+
|
|
|
+ # 尝试读取现有的关卡配置文件,保留waves数据
|
|
|
+ existing_waves = []
|
|
|
+ existing_data = {}
|
|
|
+ if level_id:
|
|
|
+ try:
|
|
|
+ levels_dir = self.project_root / "assets/resources/data/levels"
|
|
|
+ level_file = levels_dir / f"{level_id}.json"
|
|
|
+ if level_file.exists():
|
|
|
+ with open(level_file, 'r', encoding='utf-8') as f:
|
|
|
+ existing_data = json.load(f)
|
|
|
+ existing_waves = existing_data.get('waves', [])
|
|
|
+ print(f"保留现有关卡 {level_id} 的 {len(existing_waves)} 个波次数据")
|
|
|
+ except Exception as e:
|
|
|
+ print(f"读取现有关卡配置时出错: {e}")
|
|
|
+
|
|
|
+ # 构建新的关卡数据,保留现有的waves
|
|
|
+ level_data = {
|
|
|
+ "levelId": level_id,
|
|
|
+ "name": str(item.get('关卡名称', existing_data.get('name', ''))),
|
|
|
+ "scene": str(item.get('场景', existing_data.get('scene', ''))),
|
|
|
+ "description": str(item.get('描述', existing_data.get('description', ''))),
|
|
|
+ "weapons": available_weapons if available_weapons else existing_data.get('weapons', existing_data.get('availableWeapons', [])),
|
|
|
+ "timeLimit": int(item.get('时间限制', existing_data.get('timeLimit', 300))),
|
|
|
+ "difficulty": str(item.get('难度', existing_data.get('difficulty', 'normal'))),
|
|
|
+ "healthMultiplier": float(item.get('生命倍数', existing_data.get('healthMultiplier', 1.0))),
|
|
|
+ "waves": existing_waves # 保留现有的waves数据
|
|
|
}
|
|
|
+
|
|
|
+ # 添加可选字段(如果存在)
|
|
|
+ if '金币奖励' in item:
|
|
|
+ level_data["coinReward"] = int(item.get('金币奖励', 0))
|
|
|
+ elif 'coinReward' in existing_data:
|
|
|
+ level_data["coinReward"] = existing_data['coinReward']
|
|
|
+
|
|
|
+ if '钻石奖励' in item:
|
|
|
+ level_data["diamondReward"] = int(item.get('钻石奖励', 0))
|
|
|
+ elif 'diamondReward' in existing_data:
|
|
|
+ level_data["diamondReward"] = existing_data['diamondReward']
|
|
|
+
|
|
|
+ return level_data
|
|
|
+
|
|
|
except Exception as e:
|
|
|
print(f"转换关卡数据时出错: {e}")
|
|
|
return None
|