#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 游戏内技能配置管理器 处理技能配置表.xlsx中的技能信息,与现有skill.json配置合并 支持技能参数配置和技能信息表的读取与处理 """ import json import os from pathlib import Path from datetime import datetime try: import pandas as pd PANDAS_AVAILABLE = True except ImportError: PANDAS_AVAILABLE = False print("警告: pandas未安装,无法处理Excel文件") class GameSkillConfigManager: """游戏内技能配置管理器""" def __init__(self, excel_file_path=None, json_file_path=None): """初始化游戏内技能配置管理器 Args: excel_file_path: Excel配置文件路径 json_file_path: JSON配置文件路径 """ self.script_dir = Path(__file__).parent # 设置默认路径 if excel_file_path is None: self.excel_file = self.script_dir / "技能配置表.xlsx" else: self.excel_file = Path(excel_file_path) if json_file_path is None: self.json_file = self.script_dir.parent / "skill.json" else: self.json_file = Path(json_file_path) print(f"Excel文件路径: {self.excel_file}") print(f"JSON文件路径: {self.json_file}") # 技能参数映射 self.skill_param_mapping = { 'skillId': 'id', 'skillName': 'name', 'description': 'description', 'icon': 'iconPath', 'maxLevel': 'maxLevel', 'currentLevel': 'currentLevel', 'priceReduction': 'priceReduction', 'critChanceIncrease': 'critChanceIncrease', 'critDamageBonus': 'critDamageBonus', 'healthIncrease': 'healthIncrease', 'multiShotChance': 'multiShotChance', 'energyBonus': 'energyBonus', 'speedIncrease': 'speedIncrease' } # 默认配置结构 self.default_config = { "skills": [] } def load_existing_json_config(self): """加载现有的JSON配置文件""" try: if self.json_file.exists(): with open(self.json_file, 'r', encoding='utf-8') as f: config = json.load(f) print(f"成功加载现有JSON配置") return config else: print(f"JSON文件不存在,将创建新配置: {self.json_file}") return self.default_config.copy() except Exception as e: print(f"加载JSON配置失败: {e}") return self.default_config.copy() def read_excel_config(self): """读取Excel配置文件""" if not PANDAS_AVAILABLE: raise Exception("pandas未安装,无法读取Excel文件") if not self.excel_file.exists(): raise Exception(f"Excel文件不存在: {self.excel_file}") try: # 读取所有工作表 excel_data = pd.read_excel(self.excel_file, sheet_name=None) print(f"成功读取Excel文件,包含工作表: {list(excel_data.keys())}") return excel_data except Exception as e: raise Exception(f"读取Excel文件失败: {e}") def parse_skill_params(self, df): """解析技能参数配置""" skill_params = {} try: for index, row in df.iterrows(): # 跳过表头,但允许有效的参数名 if index == 0 and str(row.iloc[0]).strip() in ['技能ID', 'skillId', '参数名', 'paramName']: continue # 检查是否为有效数据行 if pd.isna(row.iloc[0]) or str(row.iloc[0]).strip() == '': continue param_name = str(row.iloc[0]).strip() data_type = str(row.iloc[1]).strip() if pd.notna(row.iloc[1]) else 'string' default_value = row.iloc[2] if pd.notna(row.iloc[2]) else '' valid_range = str(row.iloc[3]).strip() if pd.notna(row.iloc[3]) else '' description = str(row.iloc[4]).strip() if pd.notna(row.iloc[4]) else '' skill_params[param_name] = { 'dataType': data_type, 'defaultValue': default_value, 'validRange': valid_range, 'description': description } print(f"解析到 {len(skill_params)} 个技能参数") return skill_params except Exception as e: print(f"解析技能参数失败: {e}") return {} def parse_skill_info(self, df): """解析技能信息表""" skills = [] try: for index, row in df.iterrows(): # 跳过表头,但允许有效的技能ID if index == 0 and str(row.iloc[0]).strip() in ['技能ID', 'skillId', 'ID']: continue # 检查是否为有效数据行 if pd.isna(row.iloc[0]) or str(row.iloc[0]).strip() == '': continue skill = { 'id': str(row.iloc[0]).strip() if pd.notna(row.iloc[0]) else '', 'name': str(row.iloc[1]).strip() if pd.notna(row.iloc[1]) else '', 'description': str(row.iloc[2]).strip() if pd.notna(row.iloc[2]) else '', 'iconPath': str(row.iloc[3]).strip() if pd.notna(row.iloc[3]) else '', 'maxLevel': int(row.iloc[4]) if pd.notna(row.iloc[4]) else 5, 'currentLevel': int(row.iloc[5]) if pd.notna(row.iloc[5]) else 0, 'levelEffects': {} } # 解析技能效果参数,保持原有JSON格式 effect_arrays = {} descriptions = [] # 获取基础效果值 base_effects = {} if pd.notna(row.iloc[6]) and float(row.iloc[6]) != 0: # 价格减少 base_effects['priceReduction'] = float(row.iloc[6]) if pd.notna(row.iloc[7]) and float(row.iloc[7]) != 0: # 暴击几率增加 base_effects['critChanceIncrease'] = float(row.iloc[7]) if pd.notna(row.iloc[8]) and float(row.iloc[8]) != 0: # 暴击伤害加成 base_effects['critDamageBonus'] = float(row.iloc[8]) if pd.notna(row.iloc[9]) and float(row.iloc[9]) != 0: # 生命值增加 base_effects['healthIncrease'] = float(row.iloc[9]) if pd.notna(row.iloc[10]) and float(row.iloc[10]) != 0: # 多重射击几率 base_effects['multiShotChance'] = float(row.iloc[10]) if pd.notna(row.iloc[11]) and float(row.iloc[11]) != 0: # 能量加成 base_effects['energyBonus'] = float(row.iloc[11]) if pd.notna(row.iloc[12]) and float(row.iloc[12]) != 0: # 速度提升 base_effects['speedIncrease'] = float(row.iloc[12]) # 生成每个效果的数组格式(包含0级) for effect_name, base_value in base_effects.items(): effect_array = [0] # 0级效果为0 for level in range(1, skill['maxLevel'] + 1): effect_array.append(base_value * level) effect_arrays[effect_name] = effect_array # 生成描述数组 for level in range(1, skill['maxLevel'] + 1): desc = skill['description'] # 替换描述中的占位符,根据实际效果值计算 if '{value}' in desc: # 找到第一个有效的效果值来替换占位符 replacement_value = None for effect_name, effect_array in effect_arrays.items(): if len(effect_array) > level and effect_array[level] > 0: replacement_value = int(effect_array[level] * 100) break if replacement_value is not None: desc = desc.replace('{value}', str(replacement_value)) else: # 如果没有找到有效值,使用基础效果值 for effect_name, base_value in base_effects.items(): replacement_value = int(base_value * level * 100) desc = desc.replace('{value}', str(replacement_value)) break descriptions.append(desc) # 设置levelEffects为对象格式 skill['levelEffects'] = effect_arrays.copy() if descriptions: skill['levelEffects']['descriptions'] = descriptions skills.append(skill) print(f"解析到 {len(skills)} 个技能") return skills except Exception as e: print(f"解析技能信息失败: {e}") return [] def parse_game_skill_data(self, excel_data): """解析游戏技能配置数据""" try: config = self.default_config.copy() skill_params = {} # 读取技能参数配置 if "技能参数配置" in excel_data: skill_params = self.parse_skill_params(excel_data["技能参数配置"]) # 读取技能信息表 if "技能信息表" in excel_data: skills = self.parse_skill_info(excel_data["技能信息表"]) config["skills"] = skills return config, skill_params except Exception as e: print(f"解析游戏技能配置数据失败: {e}") return self.default_config.copy(), {} def validate_config(self, config): """验证配置数据""" try: # 检查必要字段 if 'skills' not in config: print("错误: 缺少 skills 字段") return False if not isinstance(config['skills'], list): print("错误: skills 必须是列表") return False # 检查每个技能的必要字段 required_skill_fields = ['id', 'name', 'description', 'iconPath', 'maxLevel', 'currentLevel', 'levelEffects'] for i, skill in enumerate(config['skills']): for field in required_skill_fields: if field not in skill: print(f"警告: 技能 {i} 缺少字段 {field}") return False print("配置验证通过") return True except Exception as e: print(f"配置验证失败: {e}") return False def merge_skill_configs(self, existing_config, excel_config): """合并现有配置和Excel配置""" try: # 创建合并后的配置 merged_config = existing_config.copy() if 'skills' not in merged_config: merged_config['skills'] = [] # 创建现有技能的ID映射 existing_skills_map = {} for skill in merged_config['skills']: if 'id' in skill: existing_skills_map[skill['id']] = skill # 更新或添加技能 updated_count = 0 added_count = 0 for excel_skill in excel_config.get('skills', []): skill_id = excel_skill.get('id') if not skill_id: continue if skill_id in existing_skills_map: # 更新现有技能,保留currentLevel等用户数据 existing_skill = existing_skills_map[skill_id] # 保存原有的currentLevel original_current_level = existing_skill.get('currentLevel', 0) # 更新基本信息 existing_skill['name'] = excel_skill.get('name', existing_skill.get('name', '')) existing_skill['description'] = excel_skill.get('description', existing_skill.get('description', '')) existing_skill['iconPath'] = excel_skill.get('iconPath', existing_skill.get('iconPath', '')) existing_skill['maxLevel'] = excel_skill.get('maxLevel', existing_skill.get('maxLevel', 5)) # 更新levelEffects,保持对象格式 if 'levelEffects' in excel_skill: existing_skill['levelEffects'] = excel_skill['levelEffects'] # 恢复原有的currentLevel(用户进度) existing_skill['currentLevel'] = original_current_level updated_count += 1 else: # 添加新技能 merged_config['skills'].append(excel_skill) added_count += 1 print(f"技能配置合并完成: 更新 {updated_count} 个技能,新增 {added_count} 个技能") return merged_config except Exception as e: print(f"合并配置失败: {e}") return existing_config def backup_json_config(self): """备份现有JSON配置""" try: if self.json_file.exists(): timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") backup_file = self.json_file.parent / f"{self.json_file.stem}_backup_{timestamp}.json" with open(self.json_file, 'r', encoding='utf-8') as src: with open(backup_file, 'w', encoding='utf-8') as dst: dst.write(src.read()) print(f"配置已备份到: {backup_file}") return backup_file else: print("JSON文件不存在,无需备份") return None except Exception as e: print(f"备份配置失败: {e}") return None def save_json_config(self, config): """保存配置到JSON文件""" try: # 确保目录存在 self.json_file.parent.mkdir(parents=True, exist_ok=True) with open(self.json_file, 'w', encoding='utf-8') as f: json.dump(config, f, ensure_ascii=False, indent=2) print(f"配置已保存到: {self.json_file}") return True except Exception as e: print(f"保存JSON文件失败: {e}") return False def import_game_skill_config(self): """导入游戏技能配置的主方法""" try: print("开始导入游戏技能配置...") # 1. 加载现有JSON配置 existing_config = self.load_existing_json_config() # 2. 读取Excel配置 excel_data = self.read_excel_config() # 3. 解析Excel数据 excel_config, skill_params = self.parse_game_skill_data(excel_data) # 4. 验证配置 if not self.validate_config(excel_config): print("配置验证失败,使用默认配置") excel_config = self.default_config.copy() # 5. 合并配置 merged_config = self.merge_skill_configs(existing_config, excel_config) # 6. 备份现有配置 self.backup_json_config() # 7. 保存新配置 if self.save_json_config(merged_config): print("游戏技能配置导入成功!") return True, "游戏技能配置导入成功" else: print("游戏技能配置保存失败!") return False, "游戏技能配置保存失败" except Exception as e: error_msg = f"导入游戏技能配置失败: {e}" print(error_msg) return False, error_msg def main(): """主函数""" print("游戏内技能配置管理器") print("=" * 50) # 创建游戏技能配置管理器 manager = GameSkillConfigManager() # 导入配置 success, message = manager.import_game_skill_config() if success: print(f"\n✓ {message}") else: print(f"\n✗ {message}") if __name__ == "__main__": main()