| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196 |
- #!/usr/bin/env python3
- # -*- coding: utf-8 -*-
- """
- 武器配置管理器
- 从config_manager.py中提取的武器相关配置管理功能
- 支持从Excel读取武器配置并与现有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文件")
- # 新增:统一环境变量大小写不敏感控制
- CASE_INSENSITIVE = str(os.environ.get('CFG_CASE_INSENSITIVE', '1')).strip().lower() in ('1', 'true', 'yes', 'on')
- class WeaponConfigManager:
- """武器配置管理器"""
-
- 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 / "weapons.json"
- else:
- self.json_file = Path(json_file_path)
-
- print(f"Excel文件路径: {self.excel_file}")
- print(f"JSON文件路径: {self.json_file}")
-
- # 武器配置映射
- self.weapon_mapping = {
- 'format_type': 'horizontal',
- 'param_types': {
- 'ID': str,
- '名称': str,
- '类型': str,
- '权重': int,
- '伤害': int,
- '射速': float,
- '射程': int,
- '子弹速度': int,
- '稀有度伤害倍率': str, # 逗号分隔的数组字符串
- '解锁条件关卡': int, # 新增解锁条件关卡字段
- # 方块价格配置字段
- '基础每格成本': int,
- 'I形状成本': int,
- 'H-I形状成本': int,
- 'L形状成本': int,
- 'S形状成本': int,
- 'D-T形状成本': int,
- # 英文字段支持
- 'id': str,
- 'name': str,
- 'type': str,
- 'weight': int,
- 'damage': int,
- 'fireRate': float,
- 'range': int,
- 'bulletSpeed': int,
- 'rarityDamageMultipliers': str, # 逗号分隔的数组字符串
- 'unlockLevel': int, # 英文版解锁条件关卡字段
- 'baseCost': int,
- 'I_shape_cost': int,
- 'HI_shape_cost': int,
- 'L_shape_cost': int,
- 'S_shape_cost': int,
- 'DT_shape_cost': int
- }
- }
-
- 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配置,包含 {len(config.get('weapons', []))} 个武器")
- return config
- else:
- print(f"JSON文件不存在,将创建新配置: {self.json_file}")
- return {'weapons': [], 'blockSizes': []}
- except Exception as e:
- print(f"加载JSON配置失败: {e}")
- return {'weapons': [], 'blockSizes': []}
-
- 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:
- # 读取所有工作表
- all_sheets = pd.read_excel(self.excel_file, sheet_name=None)
- print(f"成功读取Excel文件,包含工作表: {list(all_sheets.keys())}")
- return all_sheets
- except Exception as e:
- raise Exception(f"读取Excel文件失败: {e}")
-
- def parse_weapon_multi_sheet_data(self, all_sheets_data):
- """改造:多工作表解析支持大小写不敏感的表名匹配"""
- weapons_config = {'weapons': []}
- try:
- # 构建工作表名映射(大小写不敏感)
- sheet_keys_map = {str(k).strip().lower(): k for k in all_sheets_data.keys()} if CASE_INSENSITIVE else {}
- def get_sheet(possible_names):
- for name in possible_names:
- if CASE_INSENSITIVE:
- actual = sheet_keys_map.get(str(name).strip().lower())
- if actual is not None:
- return all_sheets_data.get(actual)
- else:
- if name in all_sheets_data:
- return all_sheets_data.get(name)
- return None
- # 基础配置
- base_sheet = get_sheet(['武器基础配置', 'Weapon Config', 'weapons', '武器配置'])
- if base_sheet is not None:
- base_config = self.parse_config_data(base_sheet)
- if 'items' in base_config:
- weapons_config['weapons'] = base_config['items']
- print(f"成功解析武器基础配置,共{len(base_config['items'])}个武器")
- # 升级费用配置
- upgrade_cost_sheet = get_sheet(['武器升级费用配置', 'Weapon Upgrade Cost', 'upgrade_costs', '升级费用'])
- if upgrade_cost_sheet is not None:
- print("找到升级费用配置工作表")
- self._parse_upgrade_cost_data(upgrade_cost_sheet, weapons_config['weapons'])
- # 游戏内成本配置
- cost_sheet = get_sheet(['游戏内成本配置', 'In Game Cost', 'cost_config', '成本配置'])
- if cost_sheet is not None:
- print("找到游戏内成本配置工作表")
- self._parse_cost_config_data(cost_sheet, weapons_config['weapons'])
- # 方块形状配置
- block_shape_sheet = get_sheet(['方块形状配置', 'Block Shape Config', 'block_shapes', '形状配置'])
- if block_shape_sheet is not None:
- print("找到方块形状配置工作表")
- weapons_config['blockSizes'] = self._parse_block_shape_data(block_shape_sheet)
- return weapons_config
- except Exception as e:
- print(f"解析武器配置失败: {e}")
- return {'weapons': []}
- # 新增:行值读取助手,支持大小写不敏感的列名
- def _get_row_value(self, row, candidates):
- try:
- idx_map = {str(col).strip().lower(): col for col in row.index}
- for name in candidates:
- if CASE_INSENSITIVE:
- actual = idx_map.get(str(name).strip().lower())
- if actual is not None and actual in row and pd.notna(row[actual]):
- return row[actual]
- else:
- if name in row and pd.notna(row[name]):
- return row[name]
- return None
- except Exception:
- return None
- def parse_config_data(self, df):
- """解析配置数据"""
- try:
- items = []
-
- # 检查第一行是否为表头
- first_row = df.iloc[0] if len(df) > 0 else None
- is_header = False
- if first_row is not None:
- first_cell = str(first_row.iloc[0]).strip() if len(first_row) > 0 else ""
- first_cell_ci = first_cell.lower() if CASE_INSENSITIVE else first_cell
- if first_cell_ci in ['武器id', 'id', 'weapon_id', 'weaponid']:
- is_header = True
- print(f"检测到表头行,第一列内容: {first_cell}")
-
- for index, row in df.iterrows():
- if is_header and index == 0: # 跳过表头
- continue
-
- # 转换行数据为字典
- item = {}
- param_types_ci = {str(k).strip().lower(): v for k, v in self.weapon_mapping['param_types'].items()}
- for col_index, value in enumerate(row):
- if col_index < len(df.columns):
- raw_col = df.columns[col_index]
- col_name = str(raw_col).strip()
- col_key = col_name.lower() if CASE_INSENSITIVE else col_name
- if pd.notna(value) and str(value).strip():
- param_type = (param_types_ci.get(col_key) if CASE_INSENSITIVE else self.weapon_mapping['param_types'].get(col_name, str))
- try:
- if param_type == int:
- item[col_key] = int(float(value))
- elif param_type == float:
- item[col_key] = float(value)
- else:
- if (col_key in ['稀有度伤害倍率', 'raritydamagemultipliers']) or (col_name in ['稀有度伤害倍率', 'rarityDamageMultipliers']):
- multipliers_str = str(value).strip()
- if multipliers_str:
- try:
- multipliers = [float(x.strip()) for x in multipliers_str.split(',') if x.strip()]
- item[col_key] = multipliers
- except ValueError:
- item[col_key] = [1.0, 1.5, 2.25, 8.0]
- else:
- item[col_key] = [1.0, 1.5, 2.25, 8.0]
- else:
- item[col_key] = str(value).strip()
- except (ValueError, TypeError):
- if (col_key in ['稀有度伤害倍率', 'raritydamagemultipliers']) or (col_name in ['稀有度伤害倍率', 'rarityDamageMultipliers']):
- item[col_key] = [1.0, 1.5, 2.25, 8.0]
- else:
- item[col_key] = str(value).strip()
-
- # 检查是否有有效的武器ID
- weapon_id = item.get('ID') or item.get('id') or item.get('武器ID') or item.get('武器id')
- if weapon_id and str(weapon_id).strip():
- items.append(item)
-
- return {'items': items}
-
- except Exception as e:
- print(f"解析配置数据失败: {e}")
- return {'items': []}
-
- def _parse_upgrade_cost_data(self, upgrade_cost_sheet, weapons_list):
- """从升级费用工作表中解析升级配置并合并到武器列表中(列名大小写不敏感)"""
- try:
- print(f"开始处理升级费用配置,工作表行数: {len(upgrade_cost_sheet)}")
- upgrade_cost_data = []
-
- # 检查第一行是否为表头
- first_row = upgrade_cost_sheet.iloc[0] if len(upgrade_cost_sheet) > 0 else None
- is_header = False
- if first_row is not None:
- first_cell = str(first_row.iloc[0]).strip() if len(first_row) > 0 else ""
- first_cell_ci = first_cell.lower() if CASE_INSENSITIVE else first_cell
- if first_cell_ci in ['武器id', 'id', 'weapon_id', 'weaponid']:
- is_header = True
- print(f"检测到表头行,第一列内容: {first_cell}")
-
- for index, row in upgrade_cost_sheet.iterrows():
- if is_header and index == 0: # 跳过表头
- continue
-
- # 支持多种武器ID字段名(大小写不敏感)
- weapon_id = self._get_row_value(row, ['武器ID', 'ID', 'weapon_id', 'weaponId'])
- if weapon_id is None:
- weapon_id = row.iloc[0] if len(row) > 0 else None
-
- if weapon_id and str(weapon_id).strip():
- upgrade_levels = {}
- # 从第5列开始是等级1-10的费用,从第15列开始是等级1-10的伤害
- for level in range(1, 11):
- cost_col_index = 4 + (level - 1)
- damage_col_index = 14 + (level - 1)
-
- level_config = {}
-
- # 处理费用
- if cost_col_index < len(row):
- cost = row.iloc[cost_col_index]
- if cost and str(cost).strip() and str(cost) != 'nan':
- try:
- level_config['cost'] = int(float(cost))
- except (ValueError, TypeError):
- pass
-
- # 处理伤害
- if damage_col_index < len(row):
- damage = row.iloc[damage_col_index]
- if damage and str(damage).strip() and str(damage) != 'nan':
- try:
- level_config['damage'] = int(float(damage))
- except (ValueError, TypeError):
- pass
-
- if level_config:
- upgrade_levels[str(level)] = level_config
-
- if upgrade_levels:
- upgrade_cost_data.append({
- 'weapon_id': str(weapon_id).strip(),
- 'levels': upgrade_levels
- })
-
- # 将升级费用配置合并到武器数据中(ID对比大小写不敏感)
- for weapon in weapons_list:
- weapon_id = weapon.get('ID', '') or weapon.get('id', '')
- if weapon_id:
- matching_upgrade = None
- for upgrade_data in upgrade_cost_data:
- if str(upgrade_data['weapon_id']).strip().lower() == str(weapon_id).strip().lower():
- matching_upgrade = upgrade_data
- break
-
- if matching_upgrade:
- weapon['upgradeConfig'] = {
- 'maxLevel': 10,
- 'levels': matching_upgrade['levels']
- }
- print(f"✓ 为武器 {weapon_id} 添加了升级费用配置")
-
- except Exception as e:
- print(f"解析升级费用配置失败: {e}")
-
- def _parse_cost_config_data(self, cost_sheet, weapons_list):
- """解析游戏内成本配置数据(列名大小写不敏感)"""
- try:
- print(f"开始处理游戏内成本配置,工作表行数: {len(cost_sheet)}")
-
- # 检查第一行是否为表头
- first_row = cost_sheet.iloc[0] if len(cost_sheet) > 0 else None
- is_header = False
- if first_row is not None:
- first_cell = str(first_row.iloc[0]).strip() if len(first_row) > 0 else ""
- first_cell_ci = first_cell.lower() if CASE_INSENSITIVE else first_cell
- if first_cell_ci in ['武器id', 'id', 'weapon_id', 'weaponid']:
- is_header = True
-
- for index, row in cost_sheet.iterrows():
- if is_header and index == 0: # 跳过表头
- continue
-
- # 获取武器ID(大小写不敏感)
- weapon_id = self._get_row_value(row, ['武器ID', 'ID', 'weapon_id', 'weaponId'])
- if weapon_id is None:
- weapon_id = str(row.iloc[0]).strip() if len(row) > 0 else None
- else:
- weapon_id = str(weapon_id).strip()
-
- if weapon_id:
- # 查找对应的武器并添加成本配置
- for weapon in weapons_list:
- w_id = (weapon.get('ID', '') or weapon.get('id', '')).strip()
- if w_id and w_id.lower() == weapon_id.lower():
- # 构建成本配置
- base_cost = 5 # 默认基础成本
- shape_costs = {}
-
- # 读取基础成本(大小写不敏感)
- val = self._get_row_value(row, ['武器基础售价', 'baseCost', '基础成本'])
- if val is not None:
- try:
- base_cost = int(float(val))
- except (ValueError, TypeError):
- pass
-
- # 读取各形状成本(大小写不敏感)
- shape_fields = {
- 'I': ['I形状成本', 'I形状', 'I_shape', 'I'],
- 'H-I': ['H-I形状成本', 'H-I形状', 'HI_shape', 'H-I'],
- 'L': ['L形状成本', 'L形状', 'L_shape', 'L'],
- 'S': ['S形状成本', 'S形状', 'S_shape', 'S'],
- 'D-T': ['D-T形状成本', 'D-T形状', 'DT_shape', 'D-T'],
- 'L2': ['L2形状成本', 'L2形状', 'L2_shape', 'L2'],
- 'L3': ['L3形状成本', 'L3形状', 'L3_shape', 'L3'],
- 'L4': ['L4形状成本', 'L4形状', 'L4_shape', 'L4'],
- 'F-S': ['F-S形状成本', 'F-S形状', 'FS_shape', 'F-S'],
- 'T': ['T形状成本', 'T形状', 'T_shape', 'T']
- }
-
- for shape_key, field_names in shape_fields.items():
- sval = self._get_row_value(row, field_names)
- if sval is not None:
- try:
- shape_costs[shape_key] = int(float(sval))
- except (ValueError, TypeError):
- pass
-
- weapon['inGameCostConfig'] = {
- 'baseCost': base_cost,
- 'shapeCosts': shape_costs
- }
- print(f"✓ 为武器 {weapon_id} 添加了游戏内成本配置")
- break
-
- except Exception as e:
- print(f"解析游戏内成本配置失败: {e}")
-
- def _parse_block_shape_data(self, block_shape_sheet):
- """解析方块形状配置数据(列名大小写不敏感)"""
- try:
- block_shapes = []
-
- # 检查第一行是否为表头
- first_row = block_shape_sheet.iloc[0] if len(block_shape_sheet) > 0 else None
- is_header = False
- if first_row is not None:
- first_cell = str(first_row.iloc[0]).strip() if len(first_row) > 0 else ""
- first_cell_ci = first_cell.lower() if CASE_INSENSITIVE else first_cell
- if first_cell_ci in ['id', '形状id', 'shape_id']:
- is_header = True
- print(f"检测到方块形状配置表头行,第一列内容: {first_cell}")
-
- for index, row in block_shape_sheet.iterrows():
- if is_header and index == 0: # 跳过表头
- continue
-
- # 获取方块形状数据(大小写不敏感)
- shape_id = self._get_row_value(row, ['ID', 'id', '形状ID', 'shape_id'])
- if shape_id is None:
- shape_id = str(row.iloc[0]).strip() if len(row) > 0 else None
- else:
- shape_id = str(shape_id).strip()
-
- shape_name = self._get_row_value(row, ['名称', 'name', 'Name', '形状名称'])
- if shape_name is None and len(row) > 1:
- shape_name = str(row.iloc[1]).strip() if pd.notna(row.iloc[1]) else None
- else:
- shape_name = str(shape_name).strip() if shape_name is not None else None
-
- shape_matrix = self._get_row_value(row, ['形状矩阵', 'shape', 'matrix', '矩阵'])
- if shape_matrix is None and len(row) > 2:
- shape_matrix = str(row.iloc[2]).strip() if pd.notna(row.iloc[2]) else None
- else:
- shape_matrix = str(shape_matrix).strip() if shape_matrix is not None else None
-
- grid_count = self._get_row_value(row, ['占用格数', 'gridCount', 'grid_count', '格数'])
- if grid_count is None and len(row) > 3:
- try:
- grid_count = int(float(row.iloc[3])) if pd.notna(row.iloc[3]) else None
- except (ValueError, TypeError):
- grid_count = None
- else:
- try:
- grid_count = int(float(grid_count)) if grid_count is not None else None
- except (ValueError, TypeError):
- grid_count = None
-
- cost_multiplier = self._get_row_value(row, ['成本倍数', 'costMultiplier', 'cost_multiplier', '倍数'])
- if cost_multiplier is None and len(row) > 4:
- try:
- cost_multiplier = float(row.iloc[4]) if pd.notna(row.iloc[4]) else None
- except (ValueError, TypeError):
- cost_multiplier = None
- else:
- try:
- cost_multiplier = float(cost_multiplier) if cost_multiplier is not None else None
- except (ValueError, TypeError):
- cost_multiplier = None
-
- description = self._get_row_value(row, ['描述', 'description', 'Description', '说明'])
- if description is None and len(row) > 5:
- description = str(row.iloc[5]).strip() if pd.notna(row.iloc[5]) else None
- else:
- description = str(description).strip() if description is not None else None
-
- # 如果有有效的形状ID,则创建形状配置
- if shape_id:
- # 解析形状矩阵
- shape_array = self._parse_shape_matrix(shape_matrix)
-
- block_shape = {
- "id": shape_id,
- "name": shape_name or shape_id,
- "shape": shape_array,
- "gridCount": grid_count or len([cell for row in shape_array for cell in row if cell == 1]),
- "costMultiplier": cost_multiplier or grid_count or 1,
- "description": description or f"{shape_name or shape_id}形状"
- }
-
- block_shapes.append(block_shape)
- print(f"✓ 添加方块形状配置: {shape_id} ({shape_name})")
-
- return block_shapes
-
- except Exception as e:
- print(f"解析方块形状配置失败: {e}")
- return []
-
- def _parse_shape_matrix(self, shape_matrix_str):
- """解析形状矩阵字符串为二维数组"""
- try:
- if not shape_matrix_str:
- return [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
-
- shape_matrix_str = str(shape_matrix_str).strip()
-
- # 尝试解析JSON格式的矩阵字符串,如 "[0, 1, 0, 0], [1, 1, 1, 0], [0, 0, 0, 0], [0, 0, 0, 0]"
- if '[' in shape_matrix_str and ']' in shape_matrix_str:
- try:
- # 添加外层方括号使其成为有效的JSON数组
- json_str = '[' + shape_matrix_str + ']'
- import json
- shape_array = json.loads(json_str)
-
- # 确保是4x4矩阵
- while len(shape_array) < 4:
- shape_array.append([0, 0, 0, 0])
-
- for i in range(len(shape_array)):
- if len(shape_array[i]) < 4:
- shape_array[i].extend([0] * (4 - len(shape_array[i])))
- shape_array[i] = shape_array[i][:4]
-
- return shape_array[:4]
- except (json.JSONDecodeError, ValueError) as e:
- print(f"JSON解析失败: {e}, 尝试其他解析方式")
-
- # 按换行符分割行(原有逻辑保留作为备用)
- lines = shape_matrix_str.split('\n')
- shape_array = []
-
- for line in lines:
- line = line.strip()
- if line:
- # 将每个字符转换为数字
- row = [int(char) for char in line if char in '01']
- # 确保每行有4个元素
- while len(row) < 4:
- row.append(0)
- shape_array.append(row[:4]) # 只取前4个元素
-
- # 确保有4行
- while len(shape_array) < 4:
- shape_array.append([0, 0, 0, 0])
-
- return shape_array[:4] # 只取前4行
-
- except Exception as e:
- print(f"解析形状矩阵失败: {e}, 使用默认矩阵")
- return [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
-
- def _parse_rarity_weights_data(self, rarity_weights_sheet):
- """解析稀有度权重配置数据"""
- try:
- print(f"开始处理稀有度权重配置,工作表行数: {len(rarity_weights_sheet)}")
- rarity_weights = {}
-
- # 检查第一行是否为表头
- first_row = rarity_weights_sheet.iloc[0] if len(rarity_weights_sheet) > 0 else None
- is_header = False
- if first_row is not None:
- first_cell = str(first_row.iloc[0]).strip() if len(first_row) > 0 else ""
- if first_cell in ['稀有度', 'Rarity', 'rarity', '等级']:
- is_header = True
- print(f"检测到表头行,第一列内容: {first_cell}")
-
- for index, row in rarity_weights_sheet.iterrows():
- if is_header and index == 0: # 跳过表头
- continue
-
- # 获取稀有度名称
- rarity_name = None
- for field in ['稀有度', 'Rarity', 'rarity', '等级']:
- if field in row and pd.notna(row[field]):
- rarity_name = str(row[field]).strip().lower()
- break
-
- if rarity_name is None and len(row) > 0:
- rarity_name = str(row.iloc[0]).strip().lower() if pd.notna(row.iloc[0]) else None
-
- # 获取权重值
- weight = None
- for field in ['权重', 'Weight', 'weight', '值']:
- if field in row and pd.notna(row[field]):
- try:
- weight = int(float(row[field]))
- break
- except (ValueError, TypeError):
- pass
-
- if weight is None and len(row) > 1:
- try:
- weight = int(float(row.iloc[1])) if pd.notna(row.iloc[1]) else None
- except (ValueError, TypeError):
- pass
-
- # 映射中文稀有度名称到英文
- rarity_mapping = {
- '普通': 'common',
- '稀有': 'uncommon',
- '史诗': 'rare',
- '传说': 'epic',
- 'common': 'common',
- 'uncommon': 'uncommon',
- 'rare': 'rare',
- 'epic': 'epic'
- }
-
- if rarity_name and weight is not None:
- mapped_rarity = rarity_mapping.get(rarity_name, rarity_name)
- rarity_weights[mapped_rarity] = weight
- print(f"✓ 添加稀有度权重配置: {mapped_rarity} = {weight}")
-
- return rarity_weights
-
- except Exception as e:
- print(f"解析稀有度权重配置失败: {e}")
- return {}
-
- def _parse_rarity_damage_multipliers_data(self, rarity_damage_multipliers_sheet):
- """解析稀有度伤害倍率配置数据"""
- try:
- print(f"开始处理稀有度伤害倍率配置,工作表行数: {len(rarity_damage_multipliers_sheet)}")
- damage_multipliers = []
-
- # 检查第一行是否为表头
- first_row = rarity_damage_multipliers_sheet.iloc[0] if len(rarity_damage_multipliers_sheet) > 0 else None
- is_header = False
- if first_row is not None:
- first_cell = str(first_row.iloc[0]).strip() if len(first_row) > 0 else ""
- if first_cell in ['配置项', 'Config Item', 'config_item', '等级', 'Level', 'level', '稀有度等级']:
- is_header = True
- print(f"检测到表头行,第一列内容: {first_cell}")
-
- for index, row in rarity_damage_multipliers_sheet.iterrows():
- if is_header and index == 0: # 跳过表头
- continue
-
- # 获取配置项名称
- config_item = None
- for field in ['配置项', 'Config Item', 'config_item']:
- if field in row and pd.notna(row[field]):
- config_item = str(row[field]).strip()
- break
-
- if config_item is None and len(row) > 0:
- config_item = str(row.iloc[0]).strip() if pd.notna(row.iloc[0]) else None
-
- # 如果找到rarityDamageMultipliers配置项
- if config_item == 'rarityDamageMultipliers':
- # 获取值字段
- multipliers_str = None
- for field in ['值', 'Value', 'value']:
- if field in row and pd.notna(row[field]):
- multipliers_str = str(row[field]).strip()
- break
-
- if multipliers_str is None and len(row) > 1:
- multipliers_str = str(row.iloc[1]).strip() if pd.notna(row.iloc[1]) else None
-
- if multipliers_str:
- try:
- # 解析逗号分隔的数组字符串
- multipliers_list = [float(x.strip()) for x in multipliers_str.split(',')]
- damage_multipliers = multipliers_list
- print(f"✓ 解析稀有度伤害倍率配置: {damage_multipliers}")
- break
- except (ValueError, TypeError) as e:
- print(f"解析倍率数组失败: {e}, 字符串: {multipliers_str}")
-
- # 如果没有找到配置或解析失败,尝试旧格式解析
- if not damage_multipliers:
- print("未找到新格式配置,尝试解析旧格式...")
- level_multipliers = {}
-
- for index, row in rarity_damage_multipliers_sheet.iterrows():
- if is_header and index == 0: # 跳过表头
- continue
-
- # 获取稀有度等级
- level = None
- for field in ['等级', 'Level', 'level', '稀有度等级']:
- if field in row and pd.notna(row[field]):
- try:
- level_str = str(row[field]).strip()
- if level_str.startswith('等级'):
- level = int(level_str.replace('等级', ''))
- else:
- level = int(float(row[field]))
- break
- except (ValueError, TypeError):
- pass
-
- if level is None and len(row) > 0:
- try:
- first_cell = str(row.iloc[0]).strip()
- if first_cell.startswith('等级'):
- level = int(first_cell.replace('等级', ''))
- else:
- level = int(float(row.iloc[0])) if pd.notna(row.iloc[0]) else None
- except (ValueError, TypeError):
- pass
-
- # 获取伤害倍率
- multiplier = None
- for field in ['伤害倍率', 'Damage Multiplier', 'multiplier', '倍率', '值', 'Value', 'value']:
- if field in row and pd.notna(row[field]):
- try:
- multiplier = float(row[field])
- break
- except (ValueError, TypeError):
- pass
-
- if multiplier is None and len(row) > 1:
- try:
- multiplier = float(row.iloc[1]) if pd.notna(row.iloc[1]) else None
- except (ValueError, TypeError):
- pass
-
- if level is not None and multiplier is not None:
- level_multipliers[level] = multiplier
- print(f"✓ 添加稀有度伤害倍率配置: 等级{level} = {multiplier}倍")
-
- # 将字典转换为按等级排序的数组
- if level_multipliers:
- max_level = max(level_multipliers.keys())
- for i in range(max_level + 1):
- if i in level_multipliers:
- damage_multipliers.append(level_multipliers[i])
- else:
- # 使用默认值
- default_multipliers = [1, 1.5, 2.25, 8]
- if i < len(default_multipliers):
- damage_multipliers.append(default_multipliers[i])
- else:
- damage_multipliers.append(1)
-
- # 如果仍然没有数据,使用默认值
- if not damage_multipliers:
- damage_multipliers = [1, 1.5, 2.25, 8]
- print("使用默认稀有度伤害倍率配置")
-
- return damage_multipliers
-
- except Exception as e:
- print(f"解析稀有度伤害倍率配置失败: {e}")
- return [1, 1.5, 2.25, 8] # 返回默认值
-
- def merge_weapon_configs(self, existing_config, excel_config):
- """合并现有JSON配置和Excel配置"""
- try:
- print("开始合并武器配置...")
-
- # 创建现有武器的映射表(按ID索引)
- existing_weapons_map = {}
- for weapon in existing_config.get('weapons', []):
- weapon_id = weapon.get('id')
- if weapon_id:
- existing_weapons_map[weapon_id] = weapon
-
- print(f"现有武器数量: {len(existing_weapons_map)}")
- print(f"Excel武器数量: {len(excel_config.get('weapons', []))}")
-
- # 处理Excel中的武器数据
- merged_weapons = []
- for excel_weapon in excel_config.get('weapons', []):
- weapon_id = excel_weapon.get('ID') or excel_weapon.get('id')
- if not weapon_id:
- continue
-
- # 转换Excel数据为标准格式
- converted_weapon = self._convert_weapon_data(
- excel_weapon,
- existing_weapons_map.get(weapon_id)
- )
-
- if converted_weapon:
- merged_weapons.append(converted_weapon)
- print(f"✓ 处理武器: {weapon_id}")
-
- # 添加Excel中没有但现有配置中存在的武器
- excel_weapon_ids = {w.get('ID') or w.get('id') for w in excel_config.get('weapons', [])}
- for weapon_id, existing_weapon in existing_weapons_map.items():
- if weapon_id not in excel_weapon_ids:
- merged_weapons.append(existing_weapon)
- print(f"✓ 保留现有武器: {weapon_id}")
-
- # 构建最终配置
- merged_config = existing_config.copy()
- merged_config['weapons'] = merged_weapons
-
- # 合并方块形状配置
- if 'blockSizes' in excel_config:
- merged_config['blockSizes'] = excel_config['blockSizes']
- print(f"✓ 更新方块形状配置,共{len(excel_config['blockSizes'])}个形状")
-
- # 保留重要的全局配置字段
- global_config_fields = ['rarityWeights', 'rarityDamageMultipliers']
- for field in global_config_fields:
- if field in existing_config:
- merged_config[field] = existing_config[field]
- print(f"✓ 保留全局配置: {field}")
-
- print(f"合并完成,最终武器数量: {len(merged_weapons)}")
- return merged_config
-
- except Exception as e:
- print(f"合并武器配置失败: {e}")
- return existing_config
-
- def _convert_weapon_data(self, item, existing_weapon=None):
- """转换武器数据格式"""
- try:
- # 支持中英文字段名
- weapon_id = item.get('id', item.get('ID', ''))
- weapon_name = item.get('name', item.get('名称', ''))
-
- if not weapon_id:
- print(f"跳过无效武器数据: 缺少武器ID - {item}")
- return None
-
- # 获取基础属性
- damage = item.get('damage', item.get('伤害', 10))
- fire_rate = item.get('fireRate', item.get('射速', 1.0))
- weapon_range = item.get('range', item.get('射程', 100))
- bullet_speed = item.get('bulletSpeed', item.get('子弹速度', 100))
- weapon_type = item.get('type', item.get('类型', ''))
- weight = item.get('weight', item.get('权重', 1))
- unlock_level = item.get('unlockLevel', item.get('解锁条件关卡', item.get('解锁条件关卡(通关)', item.get('解锁条件关卡(通关)', 1)))) # 兼容带括号的列名
- # 强制转换为整数,避免字符串进入JSON
- try:
- if unlock_level is None or str(unlock_level).strip() == '' or str(unlock_level).strip().lower() == 'nan':
- unlock_level = 1
- else:
- unlock_level = int(float(str(unlock_level).strip()))
- except Exception:
- unlock_level = 1
- rarity_damage_multipliers = item.get('rarityDamageMultipliers', item.get('稀有度伤害倍率', [1.0, 1.5, 2.25, 8.0]))
- # 确保是数组格式
- if not isinstance(rarity_damage_multipliers, list):
- rarity_damage_multipliers = [1.0, 1.5, 2.25, 8.0]
-
- # 推断武器类型(如果为空)
- if not weapon_type:
- weapon_type = self._infer_weapon_type(weapon_id)
-
- # 设置默认权重(如果为空)
- if weight == 1:
- weight = 20 # 默认权重
-
- # 构建基础武器配置
- result = {
- 'id': weapon_id,
- 'name': weapon_name,
- 'type': weapon_type,
- 'weight': weight,
- 'unlockLevel': unlock_level, # 添加解锁条件关卡到结果中
- 'rarityDamageMultipliers': rarity_damage_multipliers,
- 'stats': {
- 'damage': damage,
- 'fireRate': fire_rate,
- 'range': weapon_range,
- 'bulletSpeed': min(bullet_speed, 50) # 限制子弹速度
- }
- }
-
- # 如果有现有武器配置,保留其bulletConfig和visualConfig
- if existing_weapon:
- if 'bulletConfig' in existing_weapon:
- result['bulletConfig'] = existing_weapon['bulletConfig']
- print(f"为武器 {weapon_id} 保留现有的bulletConfig")
- else:
- result['bulletConfig'] = self._generate_bullet_config(weapon_id, weapon_type, damage, weapon_range)
-
- if 'visualConfig' in existing_weapon:
- result['visualConfig'] = existing_weapon['visualConfig']
- print(f"为武器 {weapon_id} 保留现有的visualConfig")
- else:
- result['visualConfig'] = self._generate_visual_config(weapon_id, weapon_name)
- else:
- # 生成默认配置
- result['bulletConfig'] = self._generate_bullet_config(weapon_id, weapon_type, damage, weapon_range)
- result['visualConfig'] = self._generate_visual_config(weapon_id, weapon_name)
-
- # 添加升级配置(如果Excel中有)
- if 'upgradeConfig' in item:
- result['upgradeConfig'] = item['upgradeConfig']
- print(f"为武器 {weapon_id} 添加升级配置")
-
- # 添加游戏内成本配置(如果Excel中有)
- if 'inGameCostConfig' in item:
- result['inGameCostConfig'] = item['inGameCostConfig']
- print(f"为武器 {weapon_id} 添加游戏内成本配置")
-
- return result
-
- except Exception as e:
- print(f"转换武器数据失败: {e} - 数据: {item}")
- return None
-
- def _infer_weapon_type(self, weapon_id):
- """根据武器ID推断武器类型"""
- weapon_id = str(weapon_id).lower()
- if 'shotgun' in weapon_id or 'cactus' in weapon_id:
- return 'shotgun'
- elif 'bomb' in weapon_id or 'pepper' in weapon_id:
- return 'explosive'
- elif 'missile' in weapon_id:
- return 'homing_missile'
- elif 'boomerang' in weapon_id:
- return 'boomerang'
- elif 'saw' in weapon_id:
- return 'ricochet_piercing'
- elif 'carrot' in weapon_id:
- return 'piercing'
- else:
- return 'single_shot'
-
- def _generate_bullet_config(self, weapon_id, weapon_type, damage, weapon_range):
- """生成子弹配置"""
- # 基础配置模板
- base_config = {
- 'count': {'type': 'single', 'amount': 1, 'spreadAngle': 0, 'burstCount': 1, 'burstDelay': 0},
- 'trajectory': {'type': 'straight', 'speed': 200, 'gravity': 0, 'arcHeight': 0, 'homingStrength': 0, 'homingDelay': 0},
- 'hitEffects': [{'type': 'normal_damage', 'priority': 1, 'damage': damage}],
- 'lifecycle': {'type': 'hit_destroy', 'maxLifetime': 5.0, 'penetration': 1, 'ricochetCount': 0, 'returnToOrigin': False},
- 'visual': {
- 'bulletImages': f'images/PlantsSprite/{sprite_id}',
- 'hitEffect': 'Animation/WeaponTx/tx0002/tx0002',
- 'trailEffect': True
- }
- }
-
- # 根据武器类型调整配置
- if weapon_type == 'shotgun':
- base_config['count'] = {'type': 'spread', 'amount': 5, 'spreadAngle': 30, 'burstCount': 1, 'burstDelay': 0}
- base_config['lifecycle']['type'] = 'range_limit'
- base_config['lifecycle']['maxRange'] = weapon_range * 2
- elif weapon_type == 'piercing':
- base_config['hitEffects'] = [{'type': 'pierce_damage', 'priority': 1, 'damage': damage, 'pierceCount': 999}]
- base_config['lifecycle'] = {'type': 'range_limit', 'maxLifetime': 5.0, 'penetration': 999, 'ricochetCount': 0, 'returnToOrigin': False, 'maxRange': weapon_range * 2}
- elif weapon_type == 'explosive':
- base_config['trajectory']['type'] = 'arc'
- base_config['hitEffects'] = [{'type': 'explosion', 'priority': 1, 'damage': damage + 20, 'radius': 100, 'delay': 0.1}]
- base_config['lifecycle']['type'] = 'ground_impact'
- base_config['visual']['hitEffect'] = 'Animation/WeaponTx/tx0007/tx0007'
- base_config['visual']['explosionEffect'] = 'Animation/WeaponTx/tx0007/tx0007'
-
- return base_config
-
- def _generate_visual_config(self, weapon_id, weapon_name):
- """生成视觉配置"""
- # 根据武器ID生成图片编号
- weapon_sprite_map = {
- 'pea_shooter': '001-1',
- 'sharp_carrot': '002',
- 'saw_grass': '003',
- 'watermelon_bomb': '007',
- 'boomerang_plant': '004',
- 'hot_pepper': '005',
- 'cactus_shotgun': '008',
- 'okra_missile': '006',
- 'mace_club': '009'
- }
-
- sprite_id = weapon_sprite_map.get(weapon_id, '001')
-
- return {
- 'weaponSprites': f'images/PlantsSprite/{sprite_id}',
- 'fireSound': f'audio/{weapon_id}_shot'
- }
-
- 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_weapon_config(self):
- """导入武器配置的主方法"""
- try:
- print("开始导入武器配置...")
-
- # 1. 加载现有JSON配置
- existing_config = self.load_existing_json_config()
-
- # 2. 读取Excel配置
- excel_sheets = self.read_excel_config()
-
- # 3. 解析Excel数据
- excel_config = self.parse_weapon_multi_sheet_data(excel_sheets)
-
- # 4. 合并配置
- merged_config = self.merge_weapon_configs(existing_config, excel_config)
-
- # 5. 备份现有配置
- self.backup_json_config()
-
- # 6. 保存新配置
- if self.save_json_config(merged_config):
- print("武器配置导入成功!")
- return True
- else:
- print("武器配置保存失败!")
- return False
-
- except Exception as e:
- print(f"导入武器配置失败: {e}")
- return False
- def sync_json_to_excel(self):
- """将JSON配置同步到Excel文件"""
- try:
- print("开始将JSON配置同步到Excel文件...")
-
- # 导入生成器模块
- from generate_excel_from_json import WeaponExcelGenerator
-
- # 创建Excel生成器
- generator = WeaponExcelGenerator(
- json_file_path=str(self.json_file),
- excel_output_path=str(self.excel_file)
- )
-
- # 生成Excel文件
- success = generator.generate_excel_file()
-
- if success:
- print("✓ JSON配置已成功同步到Excel文件")
- return True
- else:
- print("✗ JSON配置同步到Excel文件失败")
- return False
-
- except Exception as e:
- print(f"同步JSON到Excel失败: {e}")
- return False
-
- def sync_excel_to_json(self):
- """将Excel配置同步到JSON文件"""
- try:
- print("开始将Excel配置同步到JSON文件...")
-
- # 使用现有的导入方法
- success = self.import_weapon_config()
-
- if success:
- print("✓ Excel配置已成功同步到JSON文件")
- return True
- else:
- print("✗ Excel配置同步到JSON文件失败")
- return False
-
- except Exception as e:
- print(f"同步Excel到JSON失败: {e}")
- return False
-
- def show_sync_menu(self):
- """显示同步菜单"""
- while True:
- print("\n武器配置同步工具")
- print("=" * 50)
- print("1. 从JSON同步到Excel (推荐)")
- print("2. 从Excel同步到JSON")
- print("3. 查看文件状态")
- print("4. 退出")
- print("=" * 50)
-
- choice = input("请选择操作 (1-4): ").strip()
-
- if choice == '1':
- print("\n正在从JSON同步到Excel...")
- success = self.sync_json_to_excel()
- if success:
- print("🎉 同步完成!Excel文件已更新")
- else:
- print("❌ 同步失败!")
-
- elif choice == '2':
- print("\n正在从Excel同步到JSON...")
- success = self.sync_excel_to_json()
- if success:
- print("🎉 同步完成!JSON文件已更新")
- else:
- print("❌ 同步失败!")
-
- elif choice == '3':
- self.show_file_status()
-
- elif choice == '4':
- print("\n再见!")
- break
-
- else:
- print("\n❌ 无效选择,请重新输入")
-
- def show_file_status(self):
- """显示文件状态"""
- print("\n文件状态信息")
- print("-" * 30)
-
- # JSON文件状态
- if self.json_file.exists():
- json_mtime = datetime.fromtimestamp(self.json_file.stat().st_mtime)
- print(f"✓ JSON文件: {self.json_file}")
- print(f" 最后修改: {json_mtime.strftime('%Y-%m-%d %H:%M:%S')}")
-
- try:
- with open(self.json_file, 'r', encoding='utf-8') as f:
- config = json.load(f)
- weapon_count = len(config.get('weapons', []))
- print(f" 武器数量: {weapon_count}")
- except Exception as e:
- print(f" 读取失败: {e}")
- else:
- print(f"❌ JSON文件不存在: {self.json_file}")
-
- print()
-
- # Excel文件状态
- if self.excel_file.exists():
- excel_mtime = datetime.fromtimestamp(self.excel_file.stat().st_mtime)
- print(f"✓ Excel文件: {self.excel_file}")
- print(f" 最后修改: {excel_mtime.strftime('%Y-%m-%d %H:%M:%S')}")
-
- try:
- if PANDAS_AVAILABLE:
- sheets = pd.read_excel(self.excel_file, sheet_name=None)
- print(f" 工作表数量: {len(sheets)}")
- print(f" 工作表名称: {list(sheets.keys())}")
- else:
- print(" 无法读取详细信息 (pandas未安装)")
- except Exception as e:
- print(f" 读取失败: {e}")
- else:
- print(f"❌ Excel文件不存在: {self.excel_file}")
- def main():
- """主函数"""
- print("武器配置管理器")
- print("=" * 50)
-
- # 创建武器配置管理器
- manager = WeaponConfigManager()
-
- # 检查命令行参数
- import sys
- if len(sys.argv) > 1:
- if sys.argv[1] == '--sync-excel-to-json':
- print("自动执行:从Excel同步到JSON...")
- success = manager.sync_excel_to_json()
- if success:
- print("🎉 同步完成!JSON文件已更新")
- else:
- print("❌ 同步失败!")
- return
- elif sys.argv[1] == '--sync-json-to-excel':
- print("自动执行:从JSON同步到Excel...")
- success = manager.sync_json_to_excel()
- if success:
- print("🎉 同步完成!Excel文件已更新")
- else:
- print("❌ 同步失败!")
- return
-
- # 显示同步菜单
- manager.show_sync_menu()
- if __name__ == "__main__":
- main()
|