config_manager.py 66 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. BallController配置管理工具 - 简化版
  5. 图形界面版本,不依赖pandas,支持浏览文件并选择Excel/CSV表格进行配置导入
  6. 功能:
  7. 1. 图形界面浏览项目文件
  8. 2. 多选Excel/CSV配置表格
  9. 3. 预览配置内容
  10. 4. 一键导入配置到JSON
  11. 5. 配置备份和恢复
  12. 作者: AI Assistant
  13. 日期: 2024
  14. """
  15. import tkinter as tk
  16. from tkinter import ttk, filedialog, messagebox, scrolledtext
  17. import json
  18. import os
  19. import csv
  20. from datetime import datetime
  21. from pathlib import Path
  22. import threading
  23. # 尝试导入pandas,如果失败则使用纯CSV模式
  24. try:
  25. import pandas as pd
  26. PANDAS_AVAILABLE = True
  27. except ImportError:
  28. PANDAS_AVAILABLE = False
  29. print("警告: pandas未安装,将使用纯CSV模式(不支持Excel文件)")
  30. # 导入武器配置管理器
  31. try:
  32. from weapon_config_manager import WeaponConfigManager
  33. WEAPON_MANAGER_AVAILABLE = True
  34. except ImportError:
  35. WEAPON_MANAGER_AVAILABLE = False
  36. print("警告: weapon_config_manager.py 未找到,武器配置功能将不可用")
  37. try:
  38. from enemy_config_manager import EnemyConfigManager
  39. ENEMY_MANAGER_AVAILABLE = True
  40. except ImportError:
  41. ENEMY_MANAGER_AVAILABLE = False
  42. print("警告: enemy_config_manager.py 未找到,敌人配置功能将不可用")
  43. try:
  44. from level_config_manager import LevelConfigManager
  45. LEVEL_MANAGER_AVAILABLE = True
  46. except ImportError:
  47. LEVEL_MANAGER_AVAILABLE = False
  48. print("警告: level_config_manager.py 未找到,关卡配置功能将不可用")
  49. # 导入新的配置管理器模块
  50. try:
  51. from ball_controller_config_manager import BallControllerConfigManager
  52. BALL_CONTROLLER_MANAGER_AVAILABLE = True
  53. except ImportError:
  54. BALL_CONTROLLER_MANAGER_AVAILABLE = False
  55. print("警告: ball_controller_config_manager.py 未找到,球控制器配置功能将不可用")
  56. try:
  57. from skill_config_manager import SkillConfigManager
  58. SKILL_CONFIG_MANAGER_AVAILABLE = True
  59. except ImportError:
  60. SKILL_CONFIG_MANAGER_AVAILABLE = False
  61. print("警告: skill_config_manager.py 未找到,局外技能配置功能将不可用")
  62. try:
  63. from game_skill_config_manager import GameSkillConfigManager
  64. GAME_SKILL_MANAGER_AVAILABLE = True
  65. except ImportError:
  66. GAME_SKILL_MANAGER_AVAILABLE = False
  67. print("警告: game_skill_config_manager.py 未找到,游戏内技能配置功能将不可用")
  68. try:
  69. from shop_config_manager import ShopConfigManager
  70. SHOP_MANAGER_AVAILABLE = True
  71. except ImportError:
  72. SHOP_MANAGER_AVAILABLE = False
  73. print("警告: shop_config_manager.py 未找到,商店配置功能将不可用")
  74. try:
  75. from ball_price_config_manager import BallPriceConfigManager
  76. BALL_PRICE_MANAGER_AVAILABLE = True
  77. except ImportError:
  78. BALL_PRICE_MANAGER_AVAILABLE = False
  79. print("警告: ball_price_config_manager.py 未找到,小球价格配置功能将不可用")
  80. def parse_skill_types(self, df):
  81. """解析技能类型配置"""
  82. skill_types = []
  83. # 读取所有数据行(pandas已经自动处理了标题行)
  84. for index, row in df.iterrows():
  85. # 检查是否为空行
  86. if pd.isna(row.iloc[0]):
  87. continue
  88. try:
  89. skill_type = {
  90. "id": int(row.iloc[0]) if not pd.isna(row.iloc[0]) else 0,
  91. "name": str(row.iloc[1]) if not pd.isna(row.iloc[1]) else "",
  92. "displayName": str(row.iloc[2]) if not pd.isna(row.iloc[2]) else "",
  93. "nameTemplate": str(row.iloc[3]) if not pd.isna(row.iloc[3]) else "",
  94. "type": str(row.iloc[4]) if not pd.isna(row.iloc[4]) else ""
  95. }
  96. skill_types.append(skill_type)
  97. except Exception as e:
  98. print(f"解析技能类型第 {index+1} 行失败: {e}")
  99. continue
  100. return skill_types
  101. def parse_skill_groups(self, df):
  102. """解析技能组配置"""
  103. skill_groups = []
  104. # 读取所有数据行(pandas已经自动处理了标题行)
  105. for index, row in df.iterrows():
  106. # 检查是否为空行
  107. if pd.isna(row.iloc[0]):
  108. continue
  109. try:
  110. skill_group = {
  111. "group": int(row.iloc[0]) if not pd.isna(row.iloc[0]) else 1,
  112. "effectPercent": int(row.iloc[1]) if not pd.isna(row.iloc[1]) else 0,
  113. "diamondCost": int(row.iloc[2]) if not pd.isna(row.iloc[2]) else 0
  114. }
  115. skill_groups.append(skill_group)
  116. except Exception as e:
  117. print(f"解析技能组第 {index+1} 行失败: {e}")
  118. continue
  119. return skill_groups
  120. def validate_config(self, config):
  121. """验证配置数据"""
  122. errors = []
  123. # 验证技能类型
  124. if not config.get("skillTypes"):
  125. errors.append("技能类型配置为空")
  126. else:
  127. for i, skill_type in enumerate(config["skillTypes"]):
  128. if not skill_type.get("name"):
  129. errors.append(f"技能类型 {i+1} 缺少名称")
  130. if not skill_type.get("type"):
  131. errors.append(f"技能类型 {i+1} 缺少类型标识")
  132. # 验证技能组
  133. if not config.get("skillGroups"):
  134. errors.append("技能组配置为空")
  135. else:
  136. groups = [sg["group"] for sg in config["skillGroups"]]
  137. if len(groups) != len(set(groups)):
  138. errors.append("技能组编号存在重复")
  139. # 验证基本配置
  140. if config.get("totalGroups", 0) <= 0:
  141. errors.append("总组数必须大于0")
  142. if config.get("skillsPerGroup", 0) <= 0:
  143. errors.append("每组技能数必须大于0")
  144. return errors
  145. def backup_json(self):
  146. """备份当前JSON文件(已禁用)"""
  147. # 不再创建备份文件,直接返回成功
  148. return True
  149. def save_json_config(self, config):
  150. """保存配置到JSON文件"""
  151. try:
  152. with open(self.json_file, 'w', encoding='utf-8') as f:
  153. json.dump(config, f, ensure_ascii=False, indent=2)
  154. print(f"配置已保存到: {self.json_file}")
  155. return True
  156. except Exception as e:
  157. print(f"保存JSON文件失败: {e}")
  158. return False
  159. class ConfigManagerGUI:
  160. def __init__(self):
  161. self.root = tk.Tk()
  162. self.root.title("游戏配置管理工具")
  163. self.root.geometry("1000x700")
  164. self.root.resizable(True, True)
  165. # 配置文件路径 - 动态获取项目根目录
  166. # 从当前脚本位置向上查找项目根目录
  167. current_dir = Path(__file__).parent
  168. self.project_root = current_dir.parent.parent.parent.parent # 从excel目录向上4级到项目根目录
  169. self.excel_dir = current_dir
  170. # 手动配置变量
  171. self.selected_json_path = None
  172. self.format_type = 'horizontal' # 默认为横向表格
  173. self.multi_sheet = False
  174. self.sheet_names = []
  175. self.param_types = {}
  176. # 当前选择的配置映射
  177. self.current_mapping = None
  178. # 移除json_config_path,现在使用selected_json_path
  179. self.param_types = {}
  180. # 默认配置值
  181. self.default_config = {
  182. 'baseSpeed': 60,
  183. 'maxReflectionRandomness': 0.2,
  184. 'antiTrapTimeWindow': 5.0,
  185. 'antiTrapHitThreshold': 5,
  186. 'deflectionAttemptThreshold': 3,
  187. 'antiTrapDeflectionMultiplier': 3.0,
  188. 'FIRE_COOLDOWN': 0.05,
  189. 'ballRadius': 10,
  190. 'gravityScale': 0,
  191. 'linearDamping': 0,
  192. 'angularDamping': 0,
  193. 'colliderGroup': 2,
  194. 'colliderTag': 1,
  195. 'friction': 0,
  196. 'restitution': 1,
  197. 'safeDistance': 50,
  198. 'edgeOffset': 20,
  199. 'sensor': False
  200. }
  201. self.selected_files = []
  202. self.config_data = {}
  203. # 初始化配置管理器类引用
  204. self.weapon_config_manager_class = WeaponConfigManager if WEAPON_MANAGER_AVAILABLE else None
  205. self.enemy_config_manager_class = EnemyConfigManager if ENEMY_MANAGER_AVAILABLE else None
  206. self.level_config_manager_class = LevelConfigManager if LEVEL_MANAGER_AVAILABLE else None
  207. self.ball_controller_config_manager_class = BallControllerConfigManager if BALL_CONTROLLER_MANAGER_AVAILABLE else None
  208. self.skill_config_manager_class = SkillConfigManager if SKILL_CONFIG_MANAGER_AVAILABLE else None
  209. self.game_skill_config_manager_class = GameSkillConfigManager if GAME_SKILL_MANAGER_AVAILABLE else None
  210. self.shop_config_manager_class = ShopConfigManager if SHOP_MANAGER_AVAILABLE else None
  211. self.ball_price_config_manager_class = BallPriceConfigManager if BALL_PRICE_MANAGER_AVAILABLE else None
  212. # 初始化新的配置管理器模块
  213. self.init_config_managers()
  214. self.setup_ui()
  215. self.load_current_config()
  216. def setup_ui(self):
  217. """设置用户界面"""
  218. # 主框架
  219. main_frame = ttk.Frame(self.root, padding="10")
  220. main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
  221. # 配置根窗口的网格权重
  222. self.root.columnconfigure(0, weight=1)
  223. self.root.rowconfigure(0, weight=1)
  224. main_frame.columnconfigure(1, weight=1)
  225. main_frame.rowconfigure(2, weight=1)
  226. # 标题
  227. title_label = ttk.Label(main_frame, text="游戏配置管理工具",
  228. font=("Arial", 16, "bold"))
  229. title_label.grid(row=0, column=0, columnspan=3, pady=(0, 20))
  230. # 左侧面板 - 文件选择
  231. file_types_text = "Excel/CSV配置文件选择" if PANDAS_AVAILABLE else "CSV配置文件选择"
  232. left_frame = ttk.LabelFrame(main_frame, text=file_types_text, padding="10")
  233. left_frame.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(0, 10))
  234. left_frame.columnconfigure(0, weight=1)
  235. left_frame.rowconfigure(3, weight=1)
  236. # 文件浏览按钮
  237. browse_frame = ttk.Frame(left_frame)
  238. browse_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=(0, 10))
  239. browse_frame.columnconfigure(0, weight=1)
  240. browse_text = "浏览Excel/CSV文件" if PANDAS_AVAILABLE else "浏览CSV文件"
  241. ttk.Button(browse_frame, text=browse_text,
  242. command=self.browse_files).grid(row=0, column=0, sticky=(tk.W, tk.E))
  243. ttk.Button(browse_frame, text="扫描项目目录",
  244. command=self.scan_project_files).grid(row=0, column=1, padx=(10, 0))
  245. # JSON文件选择区域
  246. json_frame = ttk.LabelFrame(left_frame, text="JSON输出路径选择", padding="5")
  247. json_frame.grid(row=1, column=0, sticky=(tk.W, tk.E), pady=(10, 0))
  248. json_frame.columnconfigure(1, weight=1)
  249. ttk.Label(json_frame, text="输出路径:").grid(row=0, column=0, sticky=tk.W)
  250. self.json_path_var = tk.StringVar()
  251. self.json_path_entry = ttk.Entry(json_frame, textvariable=self.json_path_var, state='readonly')
  252. self.json_path_entry.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(5, 0))
  253. ttk.Button(json_frame, text="浏览", command=self.browse_json_path).grid(row=0, column=2, padx=(5, 0))
  254. # 配置选项
  255. config_frame = ttk.LabelFrame(left_frame, text="配置选项", padding="5")
  256. config_frame.grid(row=2, column=0, sticky=(tk.W, tk.E), pady=(10, 0))
  257. # 表格格式选择
  258. ttk.Label(config_frame, text="表格格式:").grid(row=0, column=0, sticky=tk.W)
  259. self.format_var = tk.StringVar(value="horizontal")
  260. format_frame = ttk.Frame(config_frame)
  261. format_frame.grid(row=0, column=1, sticky=tk.W, padx=(5, 0))
  262. ttk.Radiobutton(format_frame, text="横向", variable=self.format_var, value="horizontal").pack(side=tk.LEFT)
  263. ttk.Radiobutton(format_frame, text="纵向", variable=self.format_var, value="vertical").pack(side=tk.LEFT, padx=(10, 0))
  264. # 多工作表选项
  265. self.multi_sheet_var = tk.BooleanVar()
  266. ttk.Checkbutton(config_frame, text="多工作表文件", variable=self.multi_sheet_var,
  267. command=self.on_multi_sheet_change).grid(row=1, column=0, columnspan=2, sticky=tk.W, pady=(5, 0))
  268. # 单个JSON选项
  269. self.single_json_var = tk.BooleanVar(value=True)
  270. ttk.Checkbutton(config_frame, text="单个json", variable=self.single_json_var,
  271. command=self.on_single_json_change).grid(row=2, column=0, columnspan=2, sticky=tk.W, pady=(5, 0))
  272. # 文件列表
  273. list_frame = ttk.Frame(left_frame)
  274. list_frame.grid(row=3, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
  275. list_frame.columnconfigure(0, weight=1)
  276. list_frame.rowconfigure(0, weight=1)
  277. self.file_listbox = tk.Listbox(list_frame, selectmode=tk.MULTIPLE, height=15)
  278. scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.file_listbox.yview)
  279. self.file_listbox.configure(yscrollcommand=scrollbar.set)
  280. self.file_listbox.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
  281. scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
  282. # 文件操作按钮
  283. file_btn_frame = ttk.Frame(left_frame)
  284. file_btn_frame.grid(row=4, column=0, sticky=(tk.W, tk.E), pady=(10, 0))
  285. ttk.Button(file_btn_frame, text="预览选中文件",
  286. command=self.preview_selected_files).pack(side=tk.LEFT)
  287. ttk.Button(file_btn_frame, text="清空选择",
  288. command=self.clear_selection).pack(side=tk.LEFT, padx=(10, 0))
  289. # 右侧面板 - 配置预览和操作
  290. right_frame = ttk.LabelFrame(main_frame, text="配置预览与操作", padding="10")
  291. right_frame.grid(row=1, column=1, sticky=(tk.W, tk.E, tk.N, tk.S))
  292. right_frame.columnconfigure(0, weight=1)
  293. right_frame.rowconfigure(0, weight=1)
  294. # 配置预览文本框
  295. self.preview_text = scrolledtext.ScrolledText(right_frame, height=20, width=50)
  296. self.preview_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10))
  297. # 操作按钮 - 移除了导入配置、备份当前配置、恢复默认配置按钮
  298. btn_frame = ttk.Frame(right_frame)
  299. btn_frame.grid(row=1, column=0, sticky=(tk.W, tk.E))
  300. # 球控制器配置按钮
  301. ball_controller_btn_frame = ttk.Frame(right_frame)
  302. ball_controller_btn_frame.grid(row=2, column=0, sticky=(tk.W, tk.E), pady=(10, 0))
  303. if BALL_CONTROLLER_MANAGER_AVAILABLE:
  304. ttk.Button(ball_controller_btn_frame, text="导入球控制器配置",
  305. command=self.import_ball_controller_config).pack(side=tk.LEFT)
  306. else:
  307. ttk.Label(ball_controller_btn_frame, text="球控制器配置管理器不可用",
  308. foreground="red").pack(side=tk.LEFT)
  309. # 局外技能配置按钮
  310. skill_config_btn_frame = ttk.Frame(right_frame)
  311. skill_config_btn_frame.grid(row=3, column=0, sticky=(tk.W, tk.E), pady=(10, 0))
  312. if SKILL_CONFIG_MANAGER_AVAILABLE:
  313. ttk.Button(skill_config_btn_frame, text="导入局外技能配置",
  314. command=self.import_skill_config_new).pack(side=tk.LEFT)
  315. else:
  316. ttk.Label(skill_config_btn_frame, text="局外技能配置管理器不可用",
  317. foreground="red").pack(side=tk.LEFT)
  318. # 游戏内技能配置按钮
  319. game_skill_btn_frame = ttk.Frame(right_frame)
  320. game_skill_btn_frame.grid(row=4, column=0, sticky=(tk.W, tk.E), pady=(10, 0))
  321. if GAME_SKILL_MANAGER_AVAILABLE:
  322. ttk.Button(game_skill_btn_frame, text="导入游戏内技能配置",
  323. command=self.import_game_skill_config).pack(side=tk.LEFT)
  324. else:
  325. ttk.Label(game_skill_btn_frame, text="游戏内技能配置管理器不可用",
  326. foreground="red").pack(side=tk.LEFT)
  327. # 武器配置专用按钮
  328. weapon_btn_frame = ttk.Frame(right_frame)
  329. weapon_btn_frame.grid(row=5, column=0, sticky=(tk.W, tk.E), pady=(10, 0))
  330. if WEAPON_MANAGER_AVAILABLE:
  331. ttk.Button(weapon_btn_frame, text="导入武器配置",
  332. command=self.import_weapon_config).pack(side=tk.LEFT)
  333. else:
  334. ttk.Label(weapon_btn_frame, text="武器配置管理器不可用",
  335. foreground="red").pack(side=tk.LEFT)
  336. # 敌人配置专用按钮
  337. enemy_btn_frame = ttk.Frame(right_frame)
  338. enemy_btn_frame.grid(row=6, column=0, sticky=(tk.W, tk.E), pady=(10, 0))
  339. if ENEMY_MANAGER_AVAILABLE:
  340. ttk.Button(enemy_btn_frame, text="导入敌人配置",
  341. command=self.import_enemy_config).pack(side=tk.LEFT)
  342. else:
  343. ttk.Label(enemy_btn_frame, text="敌人配置管理器不可用",
  344. foreground="red").pack(side=tk.LEFT)
  345. # 关卡配置专用按钮
  346. level_btn_frame = ttk.Frame(right_frame)
  347. level_btn_frame.grid(row=7, column=0, sticky=(tk.W, tk.E), pady=(10, 0))
  348. if LEVEL_MANAGER_AVAILABLE:
  349. ttk.Button(level_btn_frame, text="导入关卡配置",
  350. command=self.import_level_config).pack(side=tk.LEFT)
  351. else:
  352. ttk.Label(level_btn_frame, text="关卡配置管理器不可用",
  353. foreground="red").pack(side=tk.LEFT)
  354. # 商店配置专用按钮
  355. shop_btn_frame = ttk.Frame(right_frame)
  356. shop_btn_frame.grid(row=8, column=0, sticky=(tk.W, tk.E), pady=(10, 0))
  357. if SHOP_MANAGER_AVAILABLE:
  358. ttk.Button(shop_btn_frame, text="导入商店配置",
  359. command=self.import_shop_config).pack(side=tk.LEFT)
  360. else:
  361. ttk.Label(shop_btn_frame, text="商店配置管理器不可用",
  362. foreground="red").pack(side=tk.LEFT)
  363. # 小球价格配置专用按钮
  364. ball_price_btn_frame = ttk.Frame(right_frame)
  365. ball_price_btn_frame.grid(row=9, column=0, sticky=(tk.W, tk.E), pady=(10, 0))
  366. if BALL_PRICE_MANAGER_AVAILABLE:
  367. ttk.Button(ball_price_btn_frame, text="导入小球价格配置",
  368. command=self.import_ball_price_config).pack(side=tk.LEFT)
  369. else:
  370. ttk.Label(ball_price_btn_frame, text="小球价格配置管理器不可用",
  371. foreground="red").pack(side=tk.LEFT)
  372. # 底部状态栏
  373. self.status_var = tk.StringVar()
  374. self.status_var.set("就绪")
  375. status_bar = ttk.Label(main_frame, textvariable=self.status_var,
  376. relief=tk.SUNKEN, anchor=tk.W)
  377. status_bar.grid(row=5, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(10, 0))
  378. # 绑定事件
  379. self.file_listbox.bind('<<ListboxSelect>>', self.on_file_select)
  380. def browse_files(self):
  381. """浏览Excel/CSV文件"""
  382. if PANDAS_AVAILABLE:
  383. title = "选择Excel/CSV配置文件"
  384. filetypes = [("Excel文件", "*.xlsx *.xls"), ("CSV文件", "*.csv"), ("文本文件", "*.txt"), ("所有文件", "*.*")]
  385. else:
  386. title = "选择CSV配置文件"
  387. filetypes = [("CSV文件", "*.csv"), ("文本文件", "*.txt"), ("所有文件", "*.*")]
  388. files = filedialog.askopenfilenames(
  389. title=title,
  390. filetypes=filetypes,
  391. initialdir=str(self.excel_dir)
  392. )
  393. if files:
  394. self.file_listbox.delete(0, tk.END)
  395. for file in files:
  396. self.file_listbox.insert(tk.END, file)
  397. self.status_var.set(f"已选择 {len(files)} 个文件")
  398. def scan_project_files(self):
  399. """扫描项目目录中的Excel/CSV文件"""
  400. self.status_var.set("正在扫描项目目录...")
  401. def scan_thread():
  402. config_files = []
  403. # 扫描常见的配置目录
  404. scan_dirs = [
  405. self.project_root / "assets/resources/data",
  406. self.project_root / "assets/resources/config",
  407. self.project_root / "assets/excel",
  408. self.project_root / "config",
  409. self.project_root / "data",
  410. self.project_root
  411. ]
  412. # 根据pandas可用性选择扫描的文件类型
  413. patterns = ['*.xlsx', '*.xls', '*.csv', '*.txt'] if PANDAS_AVAILABLE else ['*.csv', '*.txt']
  414. for scan_dir in scan_dirs:
  415. if scan_dir.exists():
  416. for pattern in patterns:
  417. config_files.extend(scan_dir.rglob(pattern))
  418. # 更新UI
  419. self.root.after(0, self.update_file_list, config_files)
  420. threading.Thread(target=scan_thread, daemon=True).start()
  421. def browse_json_path(self):
  422. """浏览JSON输出路径"""
  423. # 根据用户选择的选项组合决定选择文件还是目录
  424. multi_sheet = self.multi_sheet_var.get()
  425. single_json = self.single_json_var.get()
  426. if multi_sheet and not single_json:
  427. # 多工作表文件且不是单个JSON:输出到目录(每个sheet一个JSON文件)
  428. path = filedialog.askdirectory(
  429. title="选择JSON输出目录",
  430. initialdir=str(self.project_root / "assets/resources/data")
  431. )
  432. else:
  433. # 其他情况:输出单个JSON文件
  434. path = filedialog.askopenfilename(
  435. title="选择JSON输出文件",
  436. filetypes=[("JSON文件", "*.json"), ("所有文件", "*.*")],
  437. initialdir=str(self.project_root / "assets/resources/data")
  438. )
  439. if path:
  440. self.json_path_var.set(path)
  441. self.selected_json_path = Path(path)
  442. self.status_var.set(f"已选择输出路径: {Path(path).name}")
  443. def on_multi_sheet_change(self):
  444. """多工作表选项变化处理"""
  445. # 清空当前选择的JSON路径,让用户重新选择
  446. self.json_path_var.set("")
  447. def on_single_json_change(self):
  448. """单个JSON选项变化处理"""
  449. # 清空当前选择的JSON路径,让用户重新选择
  450. self.json_path_var.set("")
  451. self.selected_json_path = None
  452. if self.multi_sheet_var.get():
  453. self.status_var.set("多工作表模式:请选择输出目录")
  454. else:
  455. self.status_var.set("单工作表模式:请选择输出JSON文件")
  456. def update_file_list(self, files):
  457. """更新文件列表"""
  458. self.file_listbox.delete(0, tk.END)
  459. for file in files:
  460. self.file_listbox.insert(tk.END, str(file))
  461. file_type_text = "Excel/CSV文件" if PANDAS_AVAILABLE else "CSV文件"
  462. self.status_var.set(f"找到 {len(files)} 个{file_type_text}")
  463. def on_file_select(self, event):
  464. """文件选择事件处理"""
  465. selection = self.file_listbox.curselection()
  466. if selection:
  467. self.selected_files = [self.file_listbox.get(i) for i in selection]
  468. # 根据选择的文件设置配置映射
  469. self._set_config_mapping_for_files()
  470. self.preview_selected_files()
  471. self.status_var.set(f"已选择 {len(selection)} 个文件")
  472. def preview_selected_files(self):
  473. """预览选择的文件内容"""
  474. if not self.selected_files:
  475. self.preview_text.delete(1.0, tk.END)
  476. return
  477. preview_content = ""
  478. for file_path in self.selected_files:
  479. try:
  480. file_path_obj = Path(file_path)
  481. preview_content += f"文件: {file_path_obj.name}\n"
  482. preview_content += f"路径: {file_path}\n"
  483. if PANDAS_AVAILABLE and file_path.endswith(('.xlsx', '.xls')):
  484. # Excel文件预览
  485. try:
  486. xl_file = pd.ExcelFile(file_path)
  487. preview_content += f"工作表: {', '.join(xl_file.sheet_names)}\n"
  488. # 预览第一个工作表的前几行
  489. first_sheet = xl_file.sheet_names[0]
  490. df = pd.read_excel(file_path, sheet_name=first_sheet, nrows=5)
  491. preview_content += f"\n{first_sheet} 预览 (前5行):\n"
  492. preview_content += df.to_string(index=False) + "\n"
  493. except Exception as e:
  494. preview_content += f"Excel预览失败: {e}\n"
  495. elif file_path.endswith(('.csv', '.txt')):
  496. # CSV/TXT文件预览
  497. try:
  498. with open(file_path, 'r', encoding='utf-8') as f:
  499. lines = [f.readline().strip() for _ in range(5)]
  500. preview_content += "文件预览 (前5行):\n"
  501. preview_content += "\n".join(lines) + "\n"
  502. except Exception as e:
  503. preview_content += f"文件预览失败: {e}\n"
  504. preview_content += "-" * 50 + "\n\n"
  505. except Exception as e:
  506. preview_content += f"处理文件 {file_path} 时出错: {e}\n\n"
  507. # 更新预览文本框
  508. self.preview_text.delete(1.0, tk.END)
  509. self.preview_text.insert(1.0, preview_content)
  510. def _set_config_mapping_for_files(self):
  511. """根据选择的文件设置配置映射"""
  512. if not self.selected_files:
  513. return
  514. # 获取第一个文件名来判断配置类型
  515. first_file = Path(self.selected_files[0])
  516. filename = first_file.name.lower()
  517. # 根据文件名设置配置映射
  518. if '武器' in filename or 'weapon' in filename:
  519. self.current_mapping = {
  520. 'format_type': 'horizontal',
  521. 'param_types': {
  522. 'ID': str,
  523. '名称': str,
  524. '伤害': int,
  525. '射程': int,
  526. '射速': float,
  527. '弹药容量': int,
  528. '重装时间': float,
  529. '解锁等级': int,
  530. '价格': int,
  531. '描述': str,
  532. '最大等级': int,
  533. # 英文字段支持
  534. 'weaponId': str,
  535. 'name': str,
  536. 'damage': int,
  537. 'range': int,
  538. 'fireRate': float,
  539. 'ammoCapacity': int,
  540. 'reloadTime': float,
  541. 'unlockLevel': int,
  542. 'price': int,
  543. 'description': str,
  544. 'maxLevel': int
  545. }
  546. }
  547. elif '敌人' in filename or 'enemy' in filename:
  548. self.current_mapping = {
  549. 'format_type': 'multi_sheet',
  550. 'multi_sheet': True,
  551. 'param_types': {
  552. # 基础配置字段
  553. '敌人ID': ('id', str),
  554. '敌人名称': ('name', str),
  555. '敌人类型': ('type', str),
  556. '生命值': ('stats.health', int),
  557. '最大生命值': ('stats.maxHealth', int),
  558. '防御力': ('stats.defense', int),
  559. '移动速度': ('stats.speed', float),
  560. # 战斗配置字段
  561. '攻击伤害': ('combat.attackDamage', int),
  562. '攻击范围': ('combat.attackRange', int),
  563. '攻击速度': ('combat.attackSpeed', float),
  564. '能否格挡': ('combat.canBlock', bool),
  565. '格挡几率': ('combat.blockChance', float),
  566. '格挡伤害减免': ('combat.blockDamageReduction', float),
  567. '攻击冷却': ('combat.attackCooldown', float),
  568. '攻击类型': ('combat.attackType', str),
  569. '攻击延迟': ('combat.attackDelay', float),
  570. '武器类型': ('combat.weaponType', str),
  571. '投射物类型': ('combat.projectileType', str),
  572. '投射物速度': ('combat.projectileSpeed', float),
  573. # 移动配置字段
  574. '移动模式': ('movement.pattern', str),
  575. '巡逻范围': ('movement.patrolRange', int),
  576. '旋转速度': ('movement.rotationSpeed', float),
  577. '移动类型': ('movement.moveType', str),
  578. '摆动幅度': ('movement.swingAmplitude', float),
  579. '摆动频率': ('movement.swingFrequency', float),
  580. '速度变化': ('movement.speedVariation', float),
  581. # 视觉配置字段
  582. '精灵路径': ('visualConfig.spritePath', str),
  583. '缩放比例': ('visualConfig.scale', float),
  584. '动画速度': ('visualConfig.animationSpeed', float),
  585. '水平翻转': ('visualConfig.flipX', bool),
  586. '待机动画': ('visualConfig.animations.idle', str),
  587. '行走动画': ('visualConfig.animations.walk', str),
  588. '攻击动画': ('visualConfig.animations.attack', str),
  589. '死亡动画': ('visualConfig.animations.death', str),
  590. '武器道具': ('visualConfig.weaponProp', str),
  591. # 音频配置字段
  592. '攻击音效': ('audioConfig.attackSound', str),
  593. '死亡音效': ('audioConfig.deathSound', str),
  594. '受击音效': ('audioConfig.hitSound', str),
  595. '行走音效': ('audioConfig.walkSound', str),
  596. '格挡音效': ('audioConfig.blockSound', str),
  597. '隐身音效': ('audioConfig.stealthSound', str),
  598. '护甲破碎音效': ('audioConfig.armorBreakSound', str),
  599. '引信音效': ('audioConfig.fuseSound', str),
  600. # 特殊能力配置字段
  601. '能力类型': ('specialAbilities.type', str),
  602. '伤害': ('specialAbilities.damage', int),
  603. '范围': ('specialAbilities.range', float),
  604. '冷却时间': ('specialAbilities.cooldown', float),
  605. # BOSS配置字段
  606. '是否BOSS': ('bossConfig.isBoss', bool),
  607. '阶段数': ('bossConfig.phases', int),
  608. '狂暴阈值': ('bossConfig.enrageThreshold', float),
  609. '狂暴伤害倍数': ('bossConfig.enrageDamageMultiplier', float),
  610. '狂暴速度倍数': ('bossConfig.enrageSpeedMultiplier', float),
  611. # 英文字段支持
  612. 'id': ('id', str),
  613. 'name': ('name', str),
  614. 'type': ('type', str),
  615. 'health': ('stats.health', int),
  616. 'maxHealth': ('stats.maxHealth', int),
  617. 'defense': ('stats.defense', int),
  618. 'speed': ('stats.speed', float),
  619. 'attackDamage': ('combat.attackDamage', int),
  620. 'attackRange': ('combat.attackRange', int),
  621. 'attackSpeed': ('combat.attackSpeed', float)
  622. }
  623. }
  624. elif '技能' in filename or 'skill' in filename:
  625. self.current_mapping = {
  626. 'format_type': 'horizontal',
  627. 'param_types': {
  628. 'ID': str,
  629. '名称': str,
  630. '伤害': int,
  631. '冷却时间': float,
  632. '消耗': int,
  633. '描述': str,
  634. # 英文字段支持
  635. 'skillId': str,
  636. 'skillName': str,
  637. 'damage': int,
  638. 'cooldown': float,
  639. 'cost': int,
  640. 'description': str
  641. }
  642. }
  643. elif '关卡' in filename or 'level' in filename:
  644. self.current_mapping = {
  645. 'format_type': 'horizontal',
  646. 'param_types': {
  647. 'ID': str,
  648. '名称': str,
  649. '敌人列表': str,
  650. '波数': int,
  651. '难度': int,
  652. # 英文字段支持
  653. 'levelId': str,
  654. 'name': str,
  655. 'enemies': str,
  656. 'waves': int,
  657. 'difficulty': int
  658. }
  659. }
  660. elif 'ballcontroller' in filename or '球控制' in filename or '标准配置表' in filename:
  661. # BallController配置映射(纵向格式:参数名-数值)
  662. self.current_mapping = {
  663. 'format_type': 'vertical',
  664. 'param_types': {
  665. 'baseSpeed': float,
  666. 'maxReflectionRandomness': float,
  667. 'antiTrapTimeWindow': float,
  668. 'antiTrapHitThreshold': int,
  669. 'deflectionAttemptThreshold': int,
  670. 'antiTrapDeflectionMultiplier': float,
  671. 'FIRE_COOLDOWN': float,
  672. 'ballRadius': float,
  673. 'gravityScale': float,
  674. 'linearDamping': float,
  675. 'angularDamping': float,
  676. 'colliderGroup': int,
  677. 'colliderTag': int,
  678. 'friction': float,
  679. 'restitution': float,
  680. 'safeDistance': float,
  681. 'edgeOffset': float,
  682. 'sensor': bool,
  683. 'maxAttempts': int
  684. }
  685. }
  686. else:
  687. # 默认配置映射
  688. self.current_mapping = {
  689. 'format_type': 'horizontal',
  690. 'param_types': {}
  691. }
  692. print(f"为文件 {filename} 设置配置映射: {self.current_mapping['format_type']} 格式")
  693. print(f"支持的参数类型: {list(self.current_mapping['param_types'].keys())}")
  694. def clear_selection(self):
  695. """清空选择"""
  696. self.file_listbox.selection_clear(0, tk.END)
  697. self.preview_text.delete(1.0, tk.END)
  698. self.config_data = {}
  699. self.status_var.set("已清空选择")
  700. self.load_current_config()
  701. def load_current_config(self):
  702. """加载当前配置"""
  703. try:
  704. if not self.selected_json_path:
  705. self.preview_text.insert(tk.END, "请先选择JSON输出路径\n")
  706. return
  707. json_path = Path(self.selected_json_path)
  708. if json_path.exists():
  709. with open(json_path, 'r', encoding='utf-8') as f:
  710. current_config = json.load(f)
  711. self.preview_text.insert(tk.END, f"=== 当前配置 ({json_path.name}) ===\n")
  712. # 根据配置类型显示不同的预览格式
  713. if self.format_var.get() == 'horizontal':
  714. # 横向表格配置(如敌人、武器)
  715. if 'enemies' in current_config:
  716. self.preview_text.insert(tk.END, f"敌人配置 ({len(current_config['enemies'])} 个):\n")
  717. for i, enemy in enumerate(current_config['enemies'][:5]): # 只显示前5个
  718. self.preview_text.insert(tk.END, f" {i+1}. {enemy.get('name', enemy.get('id', 'Unknown'))}\n")
  719. if len(current_config['enemies']) > 5:
  720. self.preview_text.insert(tk.END, f" ... 还有 {len(current_config['enemies']) - 5} 个\n")
  721. if 'weapons' in current_config:
  722. self.preview_text.insert(tk.END, f"\n武器配置 ({len(current_config['weapons'])} 个):\n")
  723. for i, weapon in enumerate(current_config['weapons'][:5]): # 只显示前5个
  724. self.preview_text.insert(tk.END, f" {i+1}. {weapon.get('name', weapon.get('id', 'Unknown'))}\n")
  725. if len(current_config['weapons']) > 5:
  726. self.preview_text.insert(tk.END, f" ... 还有 {len(current_config['weapons']) - 5} 个\n")
  727. else:
  728. # 纵向表格配置(如BallController)
  729. self.preview_text.insert(tk.END, json.dumps(current_config, indent=2, ensure_ascii=False))
  730. file_hint = "Excel/CSV文件" if PANDAS_AVAILABLE else "CSV文件"
  731. self.preview_text.insert(tk.END, f"\n\n请选择{file_hint}进行配置导入...\n")
  732. else:
  733. self.preview_text.insert(tk.END, "配置文件不存在\n")
  734. except Exception as e:
  735. self.preview_text.insert(tk.END, f"加载当前配置失败: {e}\n")
  736. def import_weapon_config(self):
  737. """导入武器配置"""
  738. try:
  739. if not WEAPON_MANAGER_AVAILABLE:
  740. messagebox.showerror("错误", "武器配置管理器不可用,请检查weapon_config_manager.py文件是否存在")
  741. return
  742. # 创建武器配置管理器
  743. excel_dir = Path(self.excel_dir)
  744. weapon_excel_file = excel_dir / "方块武器配置" / "方块武器配置表.xlsx"
  745. weapon_json_file = excel_dir.parent / "weapons.json"
  746. if not weapon_excel_file.exists():
  747. messagebox.showwarning("警告", f"未找到武器配置文件: {weapon_excel_file}")
  748. return
  749. # 使用WeaponConfigManager导入配置
  750. weapon_manager = WeaponConfigManager(
  751. excel_file_path=str(weapon_excel_file),
  752. json_file_path=str(weapon_json_file)
  753. )
  754. # 在后台线程中执行导入
  755. def import_thread():
  756. try:
  757. success = weapon_manager.import_weapon_config()
  758. # 在主线程中更新UI
  759. def update_ui():
  760. if success:
  761. messagebox.showinfo("成功", "武器配置导入成功!")
  762. self.status_var.set("武器配置导入成功")
  763. # 更新预览文本
  764. self.preview_text.delete(1.0, tk.END)
  765. self.preview_text.insert(tk.END, "武器配置导入成功\n\n配置已从Excel文件合并到JSON文件中")
  766. else:
  767. messagebox.showerror("错误", "武器配置导入失败,请查看控制台输出")
  768. self.status_var.set("武器配置导入失败")
  769. self.root.after(0, update_ui)
  770. except Exception as e:
  771. def show_error():
  772. import traceback
  773. error_details = traceback.format_exc()
  774. print(f"导入武器配置失败,详细错误信息:")
  775. print(error_details)
  776. messagebox.showerror("错误", f"导入武器配置失败: {str(e)}\n\n详细错误信息已输出到控制台,请查看。")
  777. self.status_var.set("武器配置导入失败")
  778. self.root.after(0, show_error)
  779. # 启动导入线程
  780. self.status_var.set("正在导入武器配置...")
  781. thread = threading.Thread(target=import_thread)
  782. thread.daemon = True
  783. thread.start()
  784. except Exception as e:
  785. import traceback
  786. error_details = traceback.format_exc()
  787. print(f"启动武器配置导入失败,详细错误信息:")
  788. print(error_details)
  789. messagebox.showerror("错误", f"启动武器配置导入失败: {str(e)}\n\n详细错误信息已输出到控制台,请查看。")
  790. def import_enemy_config(self):
  791. """导入敌人配置"""
  792. try:
  793. if not ENEMY_MANAGER_AVAILABLE:
  794. messagebox.showerror("错误", "敌人配置管理器不可用,请检查enemy_config_manager.py文件是否存在")
  795. return
  796. # 创建敌人配置管理器
  797. excel_dir = Path(self.excel_dir)
  798. enemy_excel_file = excel_dir / "敌人配置表.xlsx"
  799. enemy_json_file = excel_dir.parent / "enemies.json"
  800. if not enemy_excel_file.exists():
  801. messagebox.showwarning("警告", f"未找到敌人配置文件: {enemy_excel_file}")
  802. return
  803. # 使用EnemyConfigManager导入配置
  804. enemy_manager = EnemyConfigManager(
  805. excel_path=str(enemy_excel_file),
  806. json_path=str(enemy_json_file)
  807. )
  808. # 在后台线程中执行导入
  809. def import_thread():
  810. try:
  811. success = enemy_manager.import_config()
  812. # 在主线程中更新UI
  813. def update_ui():
  814. if success:
  815. messagebox.showinfo("成功", "敌人配置导入成功!")
  816. self.status_var.set("敌人配置导入成功")
  817. # 更新预览文本
  818. self.preview_text.delete(1.0, tk.END)
  819. self.preview_text.insert(tk.END, "敌人配置导入成功\n\n配置已从Excel文件合并到JSON文件中")
  820. else:
  821. messagebox.showerror("错误", "敌人配置导入失败,请查看控制台输出")
  822. self.status_var.set("敌人配置导入失败")
  823. self.root.after(0, update_ui)
  824. except Exception as e:
  825. def show_error():
  826. import traceback
  827. error_details = traceback.format_exc()
  828. print(f"导入敌人配置失败,详细错误信息:")
  829. print(error_details)
  830. messagebox.showerror("错误", f"导入敌人配置失败: {str(e)}\n\n详细错误信息已输出到控制台,请查看。")
  831. self.status_var.set("敌人配置导入失败")
  832. self.root.after(0, show_error)
  833. # 启动导入线程
  834. self.status_var.set("正在导入敌人配置...")
  835. thread = threading.Thread(target=import_thread)
  836. thread.daemon = True
  837. thread.start()
  838. except Exception as e:
  839. import traceback
  840. error_details = traceback.format_exc()
  841. print(f"启动敌人配置导入失败,详细错误信息:")
  842. print(error_details)
  843. messagebox.showerror("错误", f"启动敌人配置导入失败: {str(e)}\n\n详细错误信息已输出到控制台,请查看。")
  844. def import_level_config(self):
  845. """导入关卡配置"""
  846. try:
  847. if not LEVEL_MANAGER_AVAILABLE:
  848. messagebox.showerror("错误", "关卡配置管理器不可用,请检查level_config_manager.py文件是否存在")
  849. return
  850. # 创建关卡配置管理器
  851. excel_dir = Path(self.excel_dir)
  852. level_excel_file = excel_dir / "关卡配置" / "关卡配置表.xlsx"
  853. levels_dir = excel_dir.parent / "levels"
  854. if not level_excel_file.exists():
  855. messagebox.showwarning("警告", f"未找到关卡配置文件: {level_excel_file}")
  856. return
  857. if not levels_dir.exists():
  858. messagebox.showwarning("警告", f"未找到关卡配置目录: {levels_dir}")
  859. return
  860. # 使用LevelConfigManager导入配置
  861. level_manager = LevelConfigManager(
  862. excel_path=str(level_excel_file),
  863. levels_dir=str(levels_dir)
  864. )
  865. # 在后台线程中执行导入
  866. def import_thread():
  867. try:
  868. success = level_manager.import_from_excel()
  869. # 在主线程中更新UI
  870. def update_ui():
  871. if success:
  872. messagebox.showinfo("成功", "关卡配置导入成功!")
  873. self.status_var.set("关卡配置导入成功")
  874. # 更新预览文本
  875. self.preview_text.delete(1.0, tk.END)
  876. self.preview_text.insert(tk.END, "关卡配置导入成功\n\n配置已从Excel文件合并到各个关卡JSON文件中")
  877. else:
  878. messagebox.showerror("错误", "关卡配置导入失败,请查看控制台输出")
  879. self.status_var.set("关卡配置导入失败")
  880. self.root.after(0, update_ui)
  881. except Exception as e:
  882. def show_error():
  883. import traceback
  884. error_details = traceback.format_exc()
  885. print(f"导入关卡配置失败,详细错误信息:")
  886. print(error_details)
  887. messagebox.showerror("错误", f"导入关卡配置失败: {str(e)}\n\n详细错误信息已输出到控制台,请查看。")
  888. self.status_var.set("关卡配置导入失败")
  889. self.root.after(0, show_error)
  890. # 启动导入线程
  891. self.status_var.set("正在导入关卡配置...")
  892. thread = threading.Thread(target=import_thread)
  893. thread.daemon = True
  894. thread.start()
  895. except Exception as e:
  896. import traceback
  897. error_details = traceback.format_exc()
  898. print(f"启动关卡配置导入失败,详细错误信息:")
  899. print(error_details)
  900. messagebox.showerror("错误", f"启动关卡配置导入失败: {str(e)}\n\n详细错误信息已输出到控制台,请查看。")
  901. def import_ball_controller_config(self):
  902. """导入球控制器配置"""
  903. try:
  904. if not BALL_CONTROLLER_MANAGER_AVAILABLE:
  905. messagebox.showerror("错误", "球控制器配置管理器不可用,请检查ball_controller_config_manager.py文件是否存在")
  906. return
  907. # 创建球控制器配置管理器
  908. excel_dir = Path(self.excel_dir)
  909. ball_controller_excel_file = excel_dir / "BallController标准配置表.xlsx"
  910. ball_controller_json_file = excel_dir.parent / "ballController.json"
  911. if not ball_controller_excel_file.exists():
  912. messagebox.showwarning("警告", f"未找到球控制器配置文件: {ball_controller_excel_file}")
  913. return
  914. # 使用BallControllerConfigManager导入配置
  915. ball_controller_manager = BallControllerConfigManager(
  916. excel_file_path=str(ball_controller_excel_file),
  917. json_file_path=str(ball_controller_json_file)
  918. )
  919. # 在后台线程中执行导入
  920. def import_thread():
  921. try:
  922. success = ball_controller_manager.import_ball_controller_config()
  923. # 在主线程中更新UI
  924. def update_ui():
  925. if success:
  926. messagebox.showinfo("成功", "球控制器配置导入成功!")
  927. self.status_var.set("球控制器配置导入成功")
  928. # 更新预览文本
  929. self.preview_text.delete(1.0, tk.END)
  930. self.preview_text.insert(tk.END, "球控制器配置导入成功\n\n配置已从Excel文件合并到JSON文件中")
  931. else:
  932. messagebox.showerror("错误", "球控制器配置导入失败,请查看控制台输出")
  933. self.status_var.set("球控制器配置导入失败")
  934. self.root.after(0, update_ui)
  935. except Exception as e:
  936. def show_error():
  937. import traceback
  938. error_details = traceback.format_exc()
  939. print(f"导入球控制器配置失败,详细错误信息:")
  940. print(error_details)
  941. messagebox.showerror("错误", f"导入球控制器配置失败: {str(e)}\n\n详细错误信息已输出到控制台,请查看。")
  942. self.status_var.set("球控制器配置导入失败")
  943. self.root.after(0, show_error)
  944. # 启动导入线程
  945. self.status_var.set("正在导入球控制器配置...")
  946. thread = threading.Thread(target=import_thread)
  947. thread.daemon = True
  948. thread.start()
  949. except Exception as e:
  950. import traceback
  951. error_details = traceback.format_exc()
  952. print(f"启动球控制器配置导入失败,详细错误信息:")
  953. print(error_details)
  954. messagebox.showerror("错误", f"启动球控制器配置导入失败: {str(e)}\n\n详细错误信息已输出到控制台,请查看。")
  955. def import_skill_config_new(self):
  956. """导入局外技能配置"""
  957. try:
  958. if not SKILL_CONFIG_MANAGER_AVAILABLE:
  959. messagebox.showerror("错误", "局外技能配置管理器不可用,请检查skill_config_manager.py文件是否存在")
  960. return
  961. # 创建局外技能配置管理器
  962. excel_dir = Path(self.excel_dir)
  963. skill_excel_file = excel_dir / "局外技能配置表.xlsx"
  964. skill_json_file = excel_dir.parent / "skill_config.json"
  965. if not skill_excel_file.exists():
  966. messagebox.showwarning("警告", f"未找到局外技能配置文件: {skill_excel_file}")
  967. return
  968. # 使用SkillConfigManager导入配置
  969. skill_manager = SkillConfigManager(
  970. excel_file_path=str(skill_excel_file),
  971. json_file_path=str(skill_json_file)
  972. )
  973. # 在后台线程中执行导入
  974. def import_thread():
  975. try:
  976. success = skill_manager.import_skill_config()
  977. # 在主线程中更新UI
  978. def update_ui():
  979. if success:
  980. messagebox.showinfo("成功", "局外技能配置导入成功!")
  981. self.status_var.set("局外技能配置导入成功")
  982. # 更新预览文本
  983. self.preview_text.delete(1.0, tk.END)
  984. self.preview_text.insert(tk.END, "局外技能配置导入成功\n\n配置已从Excel文件合并到JSON文件中")
  985. else:
  986. messagebox.showerror("错误", "局外技能配置导入失败,请查看控制台输出")
  987. self.status_var.set("局外技能配置导入失败")
  988. self.root.after(0, update_ui)
  989. except Exception as e:
  990. def show_error():
  991. import traceback
  992. error_details = traceback.format_exc()
  993. print(f"导入局外技能配置失败,详细错误信息:")
  994. print(error_details)
  995. messagebox.showerror("错误", f"导入局外技能配置失败: {str(e)}\n\n详细错误信息已输出到控制台,请查看。")
  996. self.status_var.set("局外技能配置导入失败")
  997. self.root.after(0, show_error)
  998. # 启动导入线程
  999. self.status_var.set("正在导入局外技能配置...")
  1000. thread = threading.Thread(target=import_thread)
  1001. thread.daemon = True
  1002. thread.start()
  1003. except Exception as e:
  1004. import traceback
  1005. error_details = traceback.format_exc()
  1006. print(f"启动局外技能配置导入失败,详细错误信息:")
  1007. print(error_details)
  1008. messagebox.showerror("错误", f"启动局外技能配置导入失败: {str(e)}\n\n详细错误信息已输出到控制台,请查看。")
  1009. def import_game_skill_config(self):
  1010. """导入游戏内技能配置"""
  1011. try:
  1012. if not GAME_SKILL_MANAGER_AVAILABLE:
  1013. messagebox.showerror("错误", "游戏内技能配置管理器不可用,请检查game_skill_config_manager.py文件是否存在")
  1014. return
  1015. # 创建游戏内技能配置管理器
  1016. excel_dir = Path(self.excel_dir)
  1017. game_skill_excel_file = excel_dir / "技能配置表.xlsx"
  1018. game_skill_json_file = excel_dir.parent / "skill.json"
  1019. if not game_skill_excel_file.exists():
  1020. messagebox.showwarning("警告", f"未找到游戏内技能配置文件: {game_skill_excel_file}")
  1021. return
  1022. # 使用GameSkillConfigManager导入配置
  1023. game_skill_manager = GameSkillConfigManager(
  1024. excel_file_path=str(game_skill_excel_file),
  1025. json_file_path=str(game_skill_json_file)
  1026. )
  1027. # 在后台线程中执行导入
  1028. def import_thread():
  1029. try:
  1030. success = game_skill_manager.import_game_skill_config()
  1031. # 在主线程中更新UI
  1032. def update_ui():
  1033. if success:
  1034. messagebox.showinfo("成功", "游戏内技能配置导入成功!")
  1035. self.status_var.set("游戏内技能配置导入成功")
  1036. # 更新预览文本
  1037. self.preview_text.delete(1.0, tk.END)
  1038. self.preview_text.insert(tk.END, "游戏内技能配置导入成功\n\n配置已从Excel文件合并到JSON文件中")
  1039. else:
  1040. messagebox.showerror("错误", "游戏内技能配置导入失败,请查看控制台输出")
  1041. self.status_var.set("游戏内技能配置导入失败")
  1042. self.root.after(0, update_ui)
  1043. except Exception as e:
  1044. def show_error():
  1045. import traceback
  1046. error_details = traceback.format_exc()
  1047. print(f"导入游戏内技能配置失败,详细错误信息:")
  1048. print(error_details)
  1049. messagebox.showerror("错误", f"导入游戏内技能配置失败: {str(e)}\n\n详细错误信息已输出到控制台,请查看。")
  1050. self.status_var.set("游戏内技能配置导入失败")
  1051. self.root.after(0, show_error)
  1052. # 启动导入线程
  1053. self.status_var.set("正在导入游戏内技能配置...")
  1054. thread = threading.Thread(target=import_thread)
  1055. thread.daemon = True
  1056. thread.start()
  1057. except Exception as e:
  1058. import traceback
  1059. error_details = traceback.format_exc()
  1060. print(f"启动游戏内技能配置导入失败,详细错误信息:")
  1061. print(error_details)
  1062. messagebox.showerror("错误", f"启动游戏内技能配置导入失败: {str(e)}\n\n详细错误信息已输出到控制台,请查看。")
  1063. def import_shop_config(self):
  1064. """导入商店配置"""
  1065. try:
  1066. if not SHOP_MANAGER_AVAILABLE:
  1067. messagebox.showerror("错误", "商店配置管理器不可用,请检查shop_config_manager.py文件是否存在")
  1068. return
  1069. # 创建商店配置管理器
  1070. excel_dir = Path(self.excel_dir)
  1071. shop_excel_file = excel_dir / "商店配置" / "商店配置表.xlsx"
  1072. shop_json_file = excel_dir.parent / "shop.json"
  1073. if not shop_excel_file.exists():
  1074. messagebox.showwarning("警告", f"未找到商店配置文件: {shop_excel_file}")
  1075. return
  1076. # 使用ShopConfigManager导入配置
  1077. shop_manager = ShopConfigManager(
  1078. excel_file_path=str(shop_excel_file),
  1079. json_file_path=str(shop_json_file)
  1080. )
  1081. # 在后台线程中执行导入
  1082. def import_thread():
  1083. try:
  1084. success = shop_manager.import_config()
  1085. # 在主线程中更新UI
  1086. def update_ui():
  1087. if success:
  1088. messagebox.showinfo("成功", "商店配置导入成功!")
  1089. self.status_var.set("商店配置导入成功")
  1090. # 更新预览文本
  1091. self.preview_text.delete(1.0, tk.END)
  1092. self.preview_text.insert(tk.END, "商店配置导入成功\n\n配置已从Excel文件合并到JSON文件中")
  1093. else:
  1094. messagebox.showerror("错误", "商店配置导入失败,请查看控制台输出")
  1095. self.status_var.set("商店配置导入失败")
  1096. self.root.after(0, update_ui)
  1097. except Exception as e:
  1098. def show_error():
  1099. import traceback
  1100. error_details = traceback.format_exc()
  1101. print(f"导入商店配置失败,详细错误信息:")
  1102. print(error_details)
  1103. messagebox.showerror("错误", f"导入商店配置失败: {str(e)}\n\n详细错误信息已输出到控制台,请查看。")
  1104. self.status_var.set("商店配置导入失败")
  1105. self.root.after(0, show_error)
  1106. # 启动导入线程
  1107. self.status_var.set("正在导入商店配置...")
  1108. thread = threading.Thread(target=import_thread)
  1109. thread.daemon = True
  1110. thread.start()
  1111. except Exception as e:
  1112. import traceback
  1113. error_details = traceback.format_exc()
  1114. print(f"启动商店配置导入失败,详细错误信息:")
  1115. print(error_details)
  1116. messagebox.showerror("错误", f"启动商店配置导入失败: {str(e)}\n\n详细错误信息已输出到控制台,请查看。")
  1117. def import_ball_price_config(self):
  1118. """导入小球价格配置"""
  1119. try:
  1120. if not BALL_PRICE_MANAGER_AVAILABLE:
  1121. messagebox.showerror("错误", "小球价格配置管理器不可用,请检查ball_price_config_manager.py文件是否存在")
  1122. return
  1123. # 创建小球价格配置管理器
  1124. excel_dir = Path(self.excel_dir)
  1125. ball_price_excel_file = excel_dir / "小球价格配置" / "小球价格配置表.xlsx"
  1126. ball_price_json_file = excel_dir.parent / "ball_price_config.json"
  1127. if not ball_price_excel_file.exists():
  1128. messagebox.showwarning("警告", f"未找到小球价格配置文件: {ball_price_excel_file}")
  1129. return
  1130. # 使用BallPriceConfigManager导入配置
  1131. ball_price_manager = BallPriceConfigManager(
  1132. excel_file_path=str(ball_price_excel_file),
  1133. json_file_path=str(ball_price_json_file)
  1134. )
  1135. # 在后台线程中执行导入
  1136. def import_thread():
  1137. try:
  1138. success = ball_price_manager.import_config()
  1139. # 在主线程中更新UI
  1140. def update_ui():
  1141. if success:
  1142. messagebox.showinfo("成功", "小球价格配置导入成功!")
  1143. self.status_var.set("小球价格配置导入成功")
  1144. # 更新预览文本
  1145. self.preview_text.delete(1.0, tk.END)
  1146. self.preview_text.insert(tk.END, "小球价格配置导入成功\n\n配置已从Excel文件合并到JSON文件中")
  1147. else:
  1148. messagebox.showerror("错误", "小球价格配置导入失败,请查看控制台输出")
  1149. self.status_var.set("小球价格配置导入失败")
  1150. self.root.after(0, update_ui)
  1151. except Exception as e:
  1152. def show_error():
  1153. import traceback
  1154. error_details = traceback.format_exc()
  1155. print(f"导入小球价格配置失败,详细错误信息:")
  1156. print(error_details)
  1157. messagebox.showerror("错误", f"导入小球价格配置失败: {str(e)}\n\n详细错误信息已输出到控制台,请查看。")
  1158. self.status_var.set("小球价格配置导入失败")
  1159. self.root.after(0, show_error)
  1160. # 启动导入线程
  1161. self.status_var.set("正在导入小球价格配置...")
  1162. thread = threading.Thread(target=import_thread)
  1163. thread.daemon = True
  1164. thread.start()
  1165. except Exception as e:
  1166. import traceback
  1167. error_details = traceback.format_exc()
  1168. print(f"启动小球价格配置导入失败,详细错误信息:")
  1169. print(error_details)
  1170. messagebox.showerror("错误", f"启动小球价格配置导入失败: {str(e)}\n\n详细错误信息已输出到控制台,请查看。")
  1171. def init_config_managers(self):
  1172. """初始化新的配置管理器模块"""
  1173. try:
  1174. # 初始化武器配置管理器
  1175. if self.weapon_config_manager_class:
  1176. self.weapon_config_manager = self.weapon_config_manager_class()
  1177. print("武器配置管理器初始化成功")
  1178. else:
  1179. self.weapon_config_manager = None
  1180. print("武器配置管理器不可用")
  1181. # 初始化敌人配置管理器
  1182. if self.enemy_config_manager_class:
  1183. self.enemy_config_manager = self.enemy_config_manager_class()
  1184. print("敌人配置管理器初始化成功")
  1185. else:
  1186. self.enemy_config_manager = None
  1187. print("敌人配置管理器不可用")
  1188. # 初始化关卡配置管理器
  1189. if self.level_config_manager_class:
  1190. self.level_config_manager = self.level_config_manager_class()
  1191. print("关卡配置管理器初始化成功")
  1192. else:
  1193. self.level_config_manager = None
  1194. print("关卡配置管理器不可用")
  1195. # 初始化球控制器配置管理器
  1196. if self.ball_controller_config_manager_class:
  1197. self.ball_controller_config_manager = self.ball_controller_config_manager_class()
  1198. print("球控制器配置管理器初始化成功")
  1199. else:
  1200. self.ball_controller_config_manager = None
  1201. print("球控制器配置管理器不可用")
  1202. # 初始化技能配置管理器
  1203. if self.skill_config_manager_class:
  1204. self.skill_config_manager = self.skill_config_manager_class()
  1205. print("技能配置管理器初始化成功")
  1206. else:
  1207. self.skill_config_manager = None
  1208. print("技能配置管理器不可用")
  1209. # 初始化游戏内技能配置管理器
  1210. if self.game_skill_config_manager_class:
  1211. self.game_skill_config_manager = self.game_skill_config_manager_class()
  1212. print("游戏内技能配置管理器初始化成功")
  1213. else:
  1214. self.game_skill_config_manager = None
  1215. print("游戏内技能配置管理器不可用")
  1216. except Exception as e:
  1217. print(f"配置管理器初始化失败: {e}")
  1218. def run(self):
  1219. """运行应用"""
  1220. self.root.mainloop()
  1221. def main():
  1222. """主函数"""
  1223. try:
  1224. app = ConfigManagerGUI()
  1225. app.run()
  1226. except Exception as e:
  1227. print(f"启动应用失败: {e}")
  1228. input("按回车键退出...")
  1229. if __name__ == "__main__":
  1230. main()