| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554 |
- #!/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:
- # 读取所有工作表,不指定header以保留所有行
- excel_data = pd.read_excel(self.excel_path, sheet_name=None, header=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 = {}
- # 从第1行开始处理(跳过第0行标题)
- for i in range(1, len(df)):
- row = df.iloc[i]
-
- 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 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 = {
- 'sprite_path': str(row.iloc[1]) if not pd.isna(row.iloc[1]) else '',
- 'scale': float(row.iloc[2]) if not pd.isna(row.iloc[2]) else 1.0,
- 'animation_speed': float(row.iloc[3]) if not pd.isna(row.iloc[3]) else 1.0,
- 'flip_horizontal': bool(row.iloc[4]) if not pd.isna(row.iloc[4]) else False,
- 'animations': {
- 'idle': str(row.iloc[5]) if not pd.isna(row.iloc[5]) else 'idle',
- 'walk': str(row.iloc[6]) if not pd.isna(row.iloc[6]) else 'walk',
- 'attack': str(row.iloc[7]) if not pd.isna(row.iloc[7]) else 'attack',
- 'death': str(row.iloc[8]) if not pd.isna(row.iloc[8]) else 'dead'
- }
- }
-
- # 武器道具(可选)
- if len(row) > 9 and not pd.isna(row.iloc[9]):
- weapon_prop = str(row.iloc[9]).strip()
- if weapon_prop:
- config['weapon_prop'] = weapon_prop
-
- visual_config[enemy_id] = config
-
- print(f"解析视觉配置: {len(visual_config)} 个敌人")
- return visual_config
-
- def _parse_audio_config(self, df):
- """解析音频配置"""
- if df is None:
- return {}
-
- audio_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 = {
- 'attack_sound': str(row.iloc[1]) if not pd.isna(row.iloc[1]) else '',
- 'death_sound': str(row.iloc[2]) if not pd.isna(row.iloc[2]) else '',
- 'hit_sound': str(row.iloc[3]) if not pd.isna(row.iloc[3]) else '',
- 'walk_sound': str(row.iloc[4]) if not pd.isna(row.iloc[4]) else ''
- }
-
- audio_config[enemy_id] = config
-
- print(f"解析音频配置: {len(audio_config)} 个敌人")
- return audio_config
-
- def _parse_special_abilities(self, df):
- """解析特殊能力配置"""
- if df is None:
- return {}
-
- abilities_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
-
- ability_type = str(row.iloc[1]).strip() if not pd.isna(row.iloc[1]) else ''
- if not ability_type:
- # 没有特殊能力的敌人
- if enemy_id not in abilities_config:
- abilities_config[enemy_id] = []
- continue
-
- ability = {
- 'type': ability_type,
- 'damage': int(row.iloc[2]) if not pd.isna(row.iloc[2]) else 0,
- 'range': int(row.iloc[3]) if not pd.isna(row.iloc[3]) else 0,
- 'cooldown': int(row.iloc[4]) if not pd.isna(row.iloc[4]) else 0
- }
-
- if enemy_id not in abilities_config:
- abilities_config[enemy_id] = []
- abilities_config[enemy_id].append(ability)
-
- print(f"解析特殊能力配置: {len(abilities_config)} 个敌人")
- return abilities_config
-
- def _parse_boss_config(self, df):
- """解析BOSS配置"""
- if df is None:
- return {}
-
- boss_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 = {
- 'is_boss': bool(row.iloc[1]) if not pd.isna(row.iloc[1]) else False,
- 'phases': int(row.iloc[2]) if not pd.isna(row.iloc[2]) else 1,
- 'rage_threshold': float(row.iloc[3]) if not pd.isna(row.iloc[3]) else 0.3,
- 'rage_damage_multiplier': float(row.iloc[4]) if not pd.isna(row.iloc[4]) else 1.0,
- 'rage_speed_multiplier': float(row.iloc[5]) if not pd.isna(row.iloc[5]) else 1.0
- }
-
- boss_config[enemy_id] = config
-
- print(f"解析BOSS配置: {len(boss_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', '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' in self.excel_data and enemy_id in self.excel_data['special']:
- abilities_data = self.excel_data['special'][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()
|