#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 敌人配置管理器 负责从Excel文件读取敌人配置并与现有JSON配置合并 只修改策划配置的值,保持JSON中其他字段不变 作者: AI Assistant 日期: 2024 """ import json import os from pathlib import Path from datetime import datetime import copy try: import pandas as pd PANDAS_AVAILABLE = True except ImportError: PANDAS_AVAILABLE = False print("警告: pandas未安装,敌人配置管理器将不可用") class EnemyConfigManager: """敌人配置管理器类""" def __init__(self, excel_path=None, json_path=None): """初始化敌人配置管理器""" if not PANDAS_AVAILABLE: raise ImportError("pandas未安装,无法使用敌人配置管理器") # 设置文件路径 self.script_dir = Path(__file__).parent self.excel_path = Path(excel_path) if excel_path else self.script_dir / "敌人配置表.xlsx" self.json_path = Path(json_path) if json_path else self.script_dir.parent / "enemies.json" # 存储配置数据 self.excel_data = {} self.existing_json = [] self.merged_config = [] print(f"敌人配置管理器初始化完成") print(f"Excel文件: {self.excel_path}") print(f"JSON文件: {self.json_path}") def load_existing_json(self): """加载现有的JSON配置""" if self.json_path.exists(): try: with open(self.json_path, 'r', encoding='utf-8') as f: self.existing_json = json.load(f) print(f"成功加载现有JSON配置,包含 {len(self.existing_json)} 个敌人") return True except Exception as e: print(f"加载JSON配置失败: {e}") self.existing_json = [] return False else: print("JSON文件不存在,将创建新配置") self.existing_json = [] return True def read_excel_data(self): """从Excel文件读取敌人配置数据""" if not self.excel_path.exists(): raise FileNotFoundError(f"Excel文件不存在: {self.excel_path}") try: # 读取所有工作表 excel_data = pd.read_excel(self.excel_path, sheet_name=None) print(f"找到工作表: {list(excel_data.keys())}") # 解析各个工作表 self.excel_data = { 'basic': self._parse_basic_config(excel_data.get('敌人基础配置')), 'combat': self._parse_combat_config(excel_data.get('战斗配置')), 'movement': self._parse_movement_config(excel_data.get('移动配置')), 'visual': self._parse_visual_config(excel_data.get('视觉配置')), 'audio': self._parse_audio_config(excel_data.get('音频配置')), 'special': self._parse_special_abilities(excel_data.get('特殊能力配置')), 'boss': self._parse_boss_config(excel_data.get('BOSS配置')) } print("Excel数据解析完成") return True except Exception as e: print(f"读取Excel文件失败: {e}") return False def _parse_basic_config(self, df): """解析敌人基础配置""" if df is None: return {} config = {} for index, row in df.iterrows(): if index == 0: # 跳过标题行 continue enemy_id = str(row.iloc[0]).strip() if pd.isna(row.iloc[0]) or enemy_id == '': continue config[enemy_id] = { 'name': str(row.iloc[1]) if not pd.isna(row.iloc[1]) else '', 'type': str(row.iloc[2]) if not pd.isna(row.iloc[2]) else '', 'health': int(row.iloc[3]) if not pd.isna(row.iloc[3]) else 100, 'maxHealth': int(row.iloc[4]) if not pd.isna(row.iloc[4]) else 100, 'defense': int(row.iloc[5]) if not pd.isna(row.iloc[5]) else 0, 'speed': float(row.iloc[6]) if not pd.isna(row.iloc[6]) else 30.0, 'dropEnergy': int(row.iloc[7]) if not pd.isna(row.iloc[7]) else 1 } print(f"解析基础配置: {len(config)} 个敌人") return config def _parse_combat_config(self, df): """解析战斗配置""" if df is None: return {} config = {} for index, row in df.iterrows(): if index == 0: # 跳过标题行 continue enemy_id = str(row.iloc[0]).strip() if pd.isna(row.iloc[0]) or enemy_id == '': continue config[enemy_id] = { 'attackDamage': int(row.iloc[1]) if not pd.isna(row.iloc[1]) else 1, 'attackRange': float(row.iloc[2]) if not pd.isna(row.iloc[2]) else 1.0, 'attackSpeed': float(row.iloc[3]) if not pd.isna(row.iloc[3]) else 1.0, 'canBlock': bool(row.iloc[4]) if not pd.isna(row.iloc[4]) else False, 'blockChance': float(row.iloc[5]) if not pd.isna(row.iloc[5]) else 0.0, 'blockDamageReduction': float(row.iloc[6]) if not pd.isna(row.iloc[6]) else 0.5, 'attackCooldown': float(row.iloc[7]) if not pd.isna(row.iloc[7]) else 1.0, 'attackType': str(row.iloc[8]) if not pd.isna(row.iloc[8]) else 'melee', 'attackDelay': float(row.iloc[9]) if not pd.isna(row.iloc[9]) else 1.0, 'weaponType': str(row.iloc[10]) if not pd.isna(row.iloc[10]) else 'none', 'projectileType': str(row.iloc[11]) if not pd.isna(row.iloc[11]) else 'none', 'projectileSpeed': float(row.iloc[12]) if not pd.isna(row.iloc[12]) else 100.0 } print(f"解析战斗配置: {len(config)} 个敌人") return config def _parse_movement_config(self, df): """解析移动配置""" if df is None: return {} config = {} for index, row in df.iterrows(): if index == 0: # 跳过标题行 continue enemy_id = str(row.iloc[0]).strip() if pd.isna(row.iloc[0]) or enemy_id == '': continue config[enemy_id] = { 'pattern': str(row.iloc[1]) if not pd.isna(row.iloc[1]) else 'direct', 'speed': float(row.iloc[2]) if not pd.isna(row.iloc[2]) else 30.0, 'patrolRange': int(row.iloc[3]) if not pd.isna(row.iloc[3]) else 100, 'chaseRange': int(row.iloc[4]) if not pd.isna(row.iloc[4]) else 200, 'rotationSpeed': float(row.iloc[5]) if not pd.isna(row.iloc[5]) else 180.0, 'moveType': str(row.iloc[6]) if not pd.isna(row.iloc[6]) else 'straight', 'swingAmplitude': float(row.iloc[7]) if not pd.isna(row.iloc[7]) else 0.0, 'swingFrequency': float(row.iloc[8]) if not pd.isna(row.iloc[8]) else 0.0, 'speedVariation': float(row.iloc[9]) if not pd.isna(row.iloc[9]) else 0.1 } print(f"解析移动配置: {len(config)} 个敌人") return config def _parse_visual_config(self, df): """解析视觉配置""" if df is None: return {} visual_config = {} for _, row in df.iterrows(): enemy_id = str(row.get('敌人ID', '')).strip() if not enemy_id or enemy_id == '敌人ID': continue config = { 'sprite_path': str(row.get('精灵路径', '')).strip(), 'scale': float(row.get('缩放比例', 1.0)), 'animation_speed': float(row.get('动画速度', 1.0)), 'flip_horizontal': bool(row.get('水平翻转', False)), 'animations': { 'idle': str(row.get('待机动画', 'idle')).strip(), 'walk': str(row.get('行走动画', 'walk')).strip(), 'attack': str(row.get('攻击动画', 'attack')).strip(), 'death': str(row.get('死亡动画', 'dead')).strip() } } # 武器道具(可选) weapon_prop = str(row.get('武器道具', '')).strip() if weapon_prop: config['weapon_prop'] = weapon_prop visual_config[enemy_id] = config return visual_config def _parse_audio_config(self, df): """解析音频配置""" if df is None: return {} audio_config = {} for _, row in df.iterrows(): enemy_id = str(row.get('敌人ID', '')).strip() if not enemy_id or enemy_id == '敌人ID': continue config = { 'attack_sound': str(row.get('攻击音效', '')).strip(), 'death_sound': str(row.get('死亡音效', '')).strip(), 'hit_sound': str(row.get('受击音效', '')).strip(), 'walk_sound': str(row.get('行走音效', '')).strip() } # 可选音效 optional_sounds = { 'block_sound': '格挡音效', 'stealth_sound': '隐身音效', 'armor_break_sound': '护甲破碎音效', 'fuse_sound': '引信音效' } for key, col_name in optional_sounds.items(): sound = str(row.get(col_name, '')).strip() if sound: config[key] = sound audio_config[enemy_id] = config return audio_config def _parse_special_abilities(self, df): """解析特殊能力配置""" if df is None: return {} abilities_config = {} for _, row in df.iterrows(): enemy_id = str(row.get('敌人ID', '')).strip() if not enemy_id or enemy_id == '敌人ID': continue ability_type = str(row.get('能力类型', '')).strip() if not ability_type: # 没有特殊能力的敌人 if enemy_id not in abilities_config: abilities_config[enemy_id] = [] continue ability = { 'type': ability_type, 'damage': int(row.get('伤害', 0)), 'range': int(row.get('范围', 0)), 'cooldown': int(row.get('冷却时间', 0)) } if enemy_id not in abilities_config: abilities_config[enemy_id] = [] abilities_config[enemy_id].append(ability) return abilities_config def _parse_boss_config(self, df): """解析BOSS配置""" if df is None: return {} boss_config = {} for _, row in df.iterrows(): enemy_id = str(row.get('敌人ID', '')).strip() if not enemy_id or enemy_id == '敌人ID': continue config = { 'is_boss': bool(row.get('是否BOSS', False)), 'phases': int(row.get('阶段数', 1)), 'rage_threshold': float(row.get('狂暴阈值', 0.3)), 'rage_damage_multiplier': float(row.get('狂暴伤害倍数', 1.0)), 'rage_speed_multiplier': float(row.get('狂暴速度倍数', 1.0)) } boss_config[enemy_id] = config return boss_config def merge_configurations(self): """合并Excel配置和现有JSON配置""" print("开始合并配置...") # 创建敌人ID到JSON索引的映射 json_enemy_map = {} for i, enemy in enumerate(self.existing_json): json_enemy_map[enemy.get('id', '')] = i # 获取所有Excel中的敌人ID excel_enemy_ids = set() for config_type in ['basic', 'combat', 'movement', 'visual', 'audio', 'special_abilities', 'boss']: if config_type in self.excel_data: excel_enemy_ids.update(self.excel_data[config_type].keys()) # 复制现有JSON作为基础 self.merged_config = copy.deepcopy(self.existing_json) # 处理每个敌人 for enemy_id in excel_enemy_ids: if enemy_id in json_enemy_map: # 更新现有敌人 json_index = json_enemy_map[enemy_id] self._update_enemy_config(self.merged_config[json_index], enemy_id) print(f"更新敌人配置: {enemy_id}") else: # 创建新敌人(如果需要) new_enemy = self._create_new_enemy_config(enemy_id) if new_enemy: self.merged_config.append(new_enemy) print(f"创建新敌人配置: {enemy_id}") print(f"配置合并完成,共 {len(self.merged_config)} 个敌人") def _update_enemy_config(self, enemy_config, enemy_id): """更新单个敌人的配置""" # 更新基础配置 if 'basic' in self.excel_data and enemy_id in self.excel_data['basic']: basic_data = self.excel_data['basic'][enemy_id] if 'name' in basic_data: enemy_config['name'] = basic_data['name'] if 'type' in basic_data: enemy_config['type'] = basic_data['type'] # 更新stats部分 if 'stats' not in enemy_config: enemy_config['stats'] = {} stats_mapping = { 'health': 'health', 'maxHealth': 'maxHealth', 'defense': 'defense', 'speed': 'speed', 'dropEnergy': 'dropEnergy' } for excel_key, json_key in stats_mapping.items(): if excel_key in basic_data: enemy_config['stats'][json_key] = basic_data[excel_key] # 更新战斗配置 if 'combat' in self.excel_data and enemy_id in self.excel_data['combat']: combat_data = self.excel_data['combat'][enemy_id] if 'combat' not in enemy_config: enemy_config['combat'] = {} combat_mapping = { 'attackDamage': 'attackDamage', 'attackRange': 'attackRange', 'attackSpeed': 'attackSpeed', 'canBlock': 'canBlock', 'blockChance': 'blockChance', 'blockDamageReduction': 'blockDamageReduction', 'attackCooldown': 'attackCooldown', 'attackType': 'attackType', 'attackDelay': 'attackDelay', 'weaponType': 'weaponType', 'projectileType': 'projectileType', 'projectileSpeed': 'projectileSpeed' } for excel_key, json_key in combat_mapping.items(): if excel_key in combat_data: enemy_config['combat'][json_key] = combat_data[excel_key] # 更新移动配置 if 'movement' in self.excel_data and enemy_id in self.excel_data['movement']: movement_data = self.excel_data['movement'][enemy_id] if 'movement' not in enemy_config: enemy_config['movement'] = {} movement_mapping = { 'pattern': 'pattern', 'speed': 'speed', 'patrolRange': 'patrolRange', 'chaseRange': 'chaseRange', 'rotationSpeed': 'rotationSpeed', 'moveType': 'moveType', 'swingAmplitude': 'swingAmplitude', 'swingFrequency': 'swingFrequency', 'speedVariation': 'speedVariation' } for excel_key, json_key in movement_mapping.items(): if excel_key in movement_data: enemy_config['movement'][json_key] = movement_data[excel_key] # 更新视觉配置 if 'visual' in self.excel_data and enemy_id in self.excel_data['visual']: visual_data = self.excel_data['visual'][enemy_id] if 'visual' not in enemy_config: enemy_config['visual'] = {} # 直接映射视觉配置 for key, value in visual_data.items(): enemy_config['visual'][key] = value # 更新音频配置 if 'audio' in self.excel_data and enemy_id in self.excel_data['audio']: audio_data = self.excel_data['audio'][enemy_id] if 'audio' not in enemy_config: enemy_config['audio'] = {} # 直接映射音频配置 for key, value in audio_data.items(): enemy_config['audio'][key] = value # 更新特殊能力配置 if 'special_abilities' in self.excel_data and enemy_id in self.excel_data['special_abilities']: abilities_data = self.excel_data['special_abilities'][enemy_id] if 'special_abilities' not in enemy_config: enemy_config['special_abilities'] = [] # 替换特殊能力列表 enemy_config['special_abilities'] = abilities_data # 更新BOSS配置 if 'boss' in self.excel_data and enemy_id in self.excel_data['boss']: boss_data = self.excel_data['boss'][enemy_id] if 'boss' not in enemy_config: enemy_config['boss'] = {} # 直接映射BOSS配置 for key, value in boss_data.items(): enemy_config['boss'][key] = value def _create_new_enemy_config(self, enemy_id): """创建新的敌人配置(基于Excel数据)""" # 这里可以根据需要实现创建新敌人的逻辑 # 暂时返回None,只更新现有敌人 return None def backup_json(self): """备份原始JSON文件""" if not self.json_path.exists(): return True try: timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") backup_path = self.json_path.parent / f"{self.json_path.stem}_backup_{timestamp}.json" with open(self.json_path, 'r', encoding='utf-8') as src: with open(backup_path, 'w', encoding='utf-8') as dst: dst.write(src.read()) print(f"备份文件已创建: {backup_path}") return True except Exception as e: print(f"备份失败: {e}") return False def save_merged_config(self): """保存合并后的配置到JSON文件""" try: # 确保目录存在 self.json_path.parent.mkdir(parents=True, exist_ok=True) with open(self.json_path, 'w', encoding='utf-8') as f: json.dump(self.merged_config, f, ensure_ascii=False, indent=2) print(f"配置已保存到: {self.json_path}") return True except Exception as e: print(f"保存配置失败: {e}") return False def import_config(self): """执行完整的配置导入流程""" print("=== 敌人配置导入开始 ===") try: # 1. 加载现有JSON配置 if not self.load_existing_json(): print("加载现有JSON配置失败") return False # 2. 读取Excel数据 if not self.read_excel_data(): print("读取Excel数据失败") return False # 3. 合并配置 self.merge_configurations() # 4. 备份原文件 if not self.backup_json(): print("备份失败,但继续执行") # 5. 保存新配置 if not self.save_merged_config(): print("保存配置失败") return False print("=== 敌人配置导入完成 ===") return True except Exception as e: print(f"配置导入过程中发生错误: {e}") return False # 测试函数 def test_enemy_config_manager(): """测试敌人配置管理器""" try: manager = EnemyConfigManager() success = manager.import_config() if success: print("敌人配置导入测试成功") else: print("敌人配置导入测试失败") return success except Exception as e: print(f"测试失败: {e}") return False if __name__ == "__main__": test_enemy_config_manager()