| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412 |
- #!/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()
|