config_manager.py 165 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305
  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. class SkillConfigImporter:
  31. """技能配置表导入工具类"""
  32. def __init__(self, excel_dir=None):
  33. # 获取当前脚本所在目录
  34. if excel_dir:
  35. self.script_dir = Path(excel_dir)
  36. else:
  37. self.script_dir = Path(__file__).parent
  38. self.excel_file = self.script_dir / "局外技能配置表.xlsx"
  39. self.json_file = self.script_dir.parent / "skill_config.json"
  40. # 默认配置结构
  41. self.default_config = {
  42. "skillTypes": [],
  43. "skillGroups": [],
  44. "totalGroups": 12,
  45. "skillsPerGroup": 3
  46. }
  47. def read_excel_config(self):
  48. """从Excel文件读取技能配置"""
  49. if not self.excel_file.exists():
  50. print(f"错误: Excel文件不存在: {self.excel_file}")
  51. return None
  52. try:
  53. # 读取所有工作表
  54. excel_data = pd.read_excel(self.excel_file, sheet_name=None)
  55. print(f"找到工作表: {list(excel_data.keys())}")
  56. config = self.default_config.copy()
  57. # 读取技能类型配置
  58. if "技能类型配置" in excel_data:
  59. skill_types = self.parse_skill_types(excel_data["技能类型配置"])
  60. config["skillTypes"] = skill_types
  61. print(f"读取到 {len(skill_types)} 个技能类型")
  62. elif "JSON技能类型配置" in excel_data:
  63. skill_types = self.parse_skill_types(excel_data["JSON技能类型配置"])
  64. config["skillTypes"] = skill_types
  65. print(f"读取到 {len(skill_types)} 个技能类型")
  66. # 读取技能组配置
  67. if "技能组配置" in excel_data:
  68. skill_groups = self.parse_skill_groups(excel_data["技能组配置"])
  69. config["skillGroups"] = skill_groups
  70. config["totalGroups"] = len(skill_groups)
  71. print(f"读取到 {len(skill_groups)} 个技能组")
  72. elif "JSON技能组配置" in excel_data:
  73. skill_groups = self.parse_skill_groups(excel_data["JSON技能组配置"])
  74. config["skillGroups"] = skill_groups
  75. config["totalGroups"] = len(skill_groups)
  76. print(f"读取到 {len(skill_groups)} 个技能组")
  77. # 读取基本配置
  78. if "基本配置" in excel_data:
  79. basic_config = self.parse_basic_config(excel_data["基本配置"])
  80. config.update(basic_config)
  81. elif "JSON基本配置" in excel_data:
  82. basic_config = self.parse_basic_config(excel_data["JSON基本配置"])
  83. config.update(basic_config)
  84. return config
  85. except Exception as e:
  86. print(f"读取Excel文件失败: {e}")
  87. return None
  88. def parse_skill_types(self, df):
  89. """解析技能类型配置"""
  90. skill_types = []
  91. # 读取所有数据行(pandas已经自动处理了标题行)
  92. for index, row in df.iterrows():
  93. # 检查是否为空行
  94. if pd.isna(row.iloc[0]):
  95. continue
  96. try:
  97. skill_type = {
  98. "id": int(row.iloc[0]) if not pd.isna(row.iloc[0]) else 0,
  99. "name": str(row.iloc[1]) if not pd.isna(row.iloc[1]) else "",
  100. "displayName": str(row.iloc[2]) if not pd.isna(row.iloc[2]) else "",
  101. "nameTemplate": str(row.iloc[3]) if not pd.isna(row.iloc[3]) else "",
  102. "type": str(row.iloc[4]) if not pd.isna(row.iloc[4]) else ""
  103. }
  104. skill_types.append(skill_type)
  105. except Exception as e:
  106. print(f"解析技能类型第 {index+1} 行失败: {e}")
  107. continue
  108. return skill_types
  109. def parse_skill_groups(self, df):
  110. """解析技能组配置"""
  111. skill_groups = []
  112. # 读取所有数据行(pandas已经自动处理了标题行)
  113. for index, row in df.iterrows():
  114. # 检查是否为空行
  115. if pd.isna(row.iloc[0]):
  116. continue
  117. try:
  118. skill_group = {
  119. "group": int(row.iloc[0]) if not pd.isna(row.iloc[0]) else 1,
  120. "effectPercent": int(row.iloc[1]) if not pd.isna(row.iloc[1]) else 0,
  121. "diamondCost": int(row.iloc[2]) if not pd.isna(row.iloc[2]) else 0
  122. }
  123. skill_groups.append(skill_group)
  124. except Exception as e:
  125. print(f"解析技能组第 {index+1} 行失败: {e}")
  126. continue
  127. return skill_groups
  128. def parse_basic_config(self, df):
  129. """解析基本配置"""
  130. basic_config = {}
  131. # 假设基本配置是纵向格式:配置项名称 | 数值
  132. for index, row in df.iterrows():
  133. if index == 0: # 跳过标题行
  134. continue
  135. # 检查是否为空行
  136. if pd.isna(row.iloc[0]):
  137. continue
  138. try:
  139. param_name = str(row.iloc[0]).strip()
  140. param_value = row.iloc[1]
  141. if param_name == "总组数" or param_name == "totalGroups":
  142. basic_config["totalGroups"] = int(param_value) if not pd.isna(param_value) else 12
  143. elif param_name == "每组技能数" or param_name == "skillsPerGroup":
  144. basic_config["skillsPerGroup"] = int(param_value) if not pd.isna(param_value) else 3
  145. except Exception as e:
  146. print(f"解析基本配置第 {index+1} 行失败: {e}")
  147. continue
  148. return basic_config
  149. def validate_config(self, config):
  150. """验证配置数据"""
  151. errors = []
  152. # 验证技能类型
  153. if not config.get("skillTypes"):
  154. errors.append("技能类型配置为空")
  155. else:
  156. for i, skill_type in enumerate(config["skillTypes"]):
  157. if not skill_type.get("name"):
  158. errors.append(f"技能类型 {i+1} 缺少名称")
  159. if not skill_type.get("type"):
  160. errors.append(f"技能类型 {i+1} 缺少类型标识")
  161. # 验证技能组
  162. if not config.get("skillGroups"):
  163. errors.append("技能组配置为空")
  164. else:
  165. groups = [sg["group"] for sg in config["skillGroups"]]
  166. if len(groups) != len(set(groups)):
  167. errors.append("技能组编号存在重复")
  168. # 验证基本配置
  169. if config.get("totalGroups", 0) <= 0:
  170. errors.append("总组数必须大于0")
  171. if config.get("skillsPerGroup", 0) <= 0:
  172. errors.append("每组技能数必须大于0")
  173. return errors
  174. def backup_json(self):
  175. """备份当前JSON文件(已禁用)"""
  176. # 不再创建备份文件,直接返回成功
  177. return True
  178. def save_json_config(self, config):
  179. """保存配置到JSON文件"""
  180. try:
  181. with open(self.json_file, 'w', encoding='utf-8') as f:
  182. json.dump(config, f, ensure_ascii=False, indent=2)
  183. print(f"配置已保存到: {self.json_file}")
  184. return True
  185. except Exception as e:
  186. print(f"保存JSON文件失败: {e}")
  187. return False
  188. def import_config(self):
  189. """执行配置导入"""
  190. print("=== 技能配置导入工具 ===")
  191. print(f"Excel文件: {self.excel_file}")
  192. print(f"JSON文件: {self.json_file}")
  193. print()
  194. # 读取Excel配置
  195. config = self.read_excel_config()
  196. if not config:
  197. print("读取Excel配置失败")
  198. return False
  199. # 验证配置
  200. errors = self.validate_config(config)
  201. if errors:
  202. print("配置验证失败:")
  203. for error in errors:
  204. print(f" - {error}")
  205. return False
  206. print("配置验证通过")
  207. # 显示配置摘要
  208. print(f"\n配置摘要:")
  209. print(f" 技能类型数量: {len(config['skillTypes'])}")
  210. print(f" 技能组数量: {len(config['skillGroups'])}")
  211. print(f" 总组数: {config['totalGroups']}")
  212. print(f" 每组技能数: {config['skillsPerGroup']}")
  213. # 备份原文件
  214. if not self.backup_json():
  215. print("备份失败,是否继续?(y/n): ", end="")
  216. # 在GUI环境中,我们默认继续
  217. print("y (自动继续)")
  218. # 保存新配置
  219. if self.save_json_config(config):
  220. print("\n配置导入成功!")
  221. return True
  222. else:
  223. print("\n配置导入失败!")
  224. return False
  225. class ConfigManagerGUI:
  226. def __init__(self):
  227. self.root = tk.Tk()
  228. self.root.title("游戏配置管理工具")
  229. self.root.geometry("1000x700")
  230. self.root.resizable(True, True)
  231. # 配置文件路径 - 动态获取项目根目录
  232. # 从当前脚本位置向上查找项目根目录
  233. current_dir = Path(__file__).parent
  234. self.project_root = current_dir.parent.parent.parent.parent # 从excel目录向上4级到项目根目录
  235. self.excel_dir = current_dir
  236. # 手动配置变量
  237. self.selected_json_path = None
  238. self.format_type = 'horizontal' # 默认为横向表格
  239. self.multi_sheet = False
  240. self.sheet_names = []
  241. self.param_types = {}
  242. # 当前选择的配置映射
  243. self.current_mapping = None
  244. # 移除json_config_path,现在使用selected_json_path
  245. self.param_types = {}
  246. # 默认配置值
  247. self.default_config = {
  248. 'baseSpeed': 60,
  249. 'maxReflectionRandomness': 0.2,
  250. 'antiTrapTimeWindow': 5.0,
  251. 'antiTrapHitThreshold': 5,
  252. 'deflectionAttemptThreshold': 3,
  253. 'antiTrapDeflectionMultiplier': 3.0,
  254. 'FIRE_COOLDOWN': 0.05,
  255. 'ballRadius': 10,
  256. 'gravityScale': 0,
  257. 'linearDamping': 0,
  258. 'angularDamping': 0,
  259. 'colliderGroup': 2,
  260. 'colliderTag': 1,
  261. 'friction': 0,
  262. 'restitution': 1,
  263. 'safeDistance': 50,
  264. 'edgeOffset': 20,
  265. 'sensor': False
  266. }
  267. self.selected_files = []
  268. self.config_data = {}
  269. self.setup_ui()
  270. self.load_current_config()
  271. def setup_ui(self):
  272. """设置用户界面"""
  273. # 主框架
  274. main_frame = ttk.Frame(self.root, padding="10")
  275. main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
  276. # 配置根窗口的网格权重
  277. self.root.columnconfigure(0, weight=1)
  278. self.root.rowconfigure(0, weight=1)
  279. main_frame.columnconfigure(1, weight=1)
  280. main_frame.rowconfigure(2, weight=1)
  281. # 标题
  282. title_label = ttk.Label(main_frame, text="游戏配置管理工具",
  283. font=("Arial", 16, "bold"))
  284. title_label.grid(row=0, column=0, columnspan=3, pady=(0, 20))
  285. # 左侧面板 - 文件选择
  286. file_types_text = "Excel/CSV配置文件选择" if PANDAS_AVAILABLE else "CSV配置文件选择"
  287. left_frame = ttk.LabelFrame(main_frame, text=file_types_text, padding="10")
  288. left_frame.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(0, 10))
  289. left_frame.columnconfigure(0, weight=1)
  290. left_frame.rowconfigure(3, weight=1)
  291. # 文件浏览按钮
  292. browse_frame = ttk.Frame(left_frame)
  293. browse_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=(0, 10))
  294. browse_frame.columnconfigure(0, weight=1)
  295. browse_text = "浏览Excel/CSV文件" if PANDAS_AVAILABLE else "浏览CSV文件"
  296. ttk.Button(browse_frame, text=browse_text,
  297. command=self.browse_files).grid(row=0, column=0, sticky=(tk.W, tk.E))
  298. ttk.Button(browse_frame, text="扫描项目目录",
  299. command=self.scan_project_files).grid(row=0, column=1, padx=(10, 0))
  300. # JSON文件选择区域
  301. json_frame = ttk.LabelFrame(left_frame, text="JSON输出路径选择", padding="5")
  302. json_frame.grid(row=1, column=0, sticky=(tk.W, tk.E), pady=(10, 0))
  303. json_frame.columnconfigure(1, weight=1)
  304. ttk.Label(json_frame, text="输出路径:").grid(row=0, column=0, sticky=tk.W)
  305. self.json_path_var = tk.StringVar()
  306. self.json_path_entry = ttk.Entry(json_frame, textvariable=self.json_path_var, state='readonly')
  307. self.json_path_entry.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(5, 0))
  308. ttk.Button(json_frame, text="浏览", command=self.browse_json_path).grid(row=0, column=2, padx=(5, 0))
  309. # 配置选项
  310. config_frame = ttk.LabelFrame(left_frame, text="配置选项", padding="5")
  311. config_frame.grid(row=2, column=0, sticky=(tk.W, tk.E), pady=(10, 0))
  312. # 表格格式选择
  313. ttk.Label(config_frame, text="表格格式:").grid(row=0, column=0, sticky=tk.W)
  314. self.format_var = tk.StringVar(value="horizontal")
  315. format_frame = ttk.Frame(config_frame)
  316. format_frame.grid(row=0, column=1, sticky=tk.W, padx=(5, 0))
  317. ttk.Radiobutton(format_frame, text="横向", variable=self.format_var, value="horizontal").pack(side=tk.LEFT)
  318. ttk.Radiobutton(format_frame, text="纵向", variable=self.format_var, value="vertical").pack(side=tk.LEFT, padx=(10, 0))
  319. # 多工作表选项
  320. self.multi_sheet_var = tk.BooleanVar()
  321. ttk.Checkbutton(config_frame, text="多工作表文件", variable=self.multi_sheet_var,
  322. command=self.on_multi_sheet_change).grid(row=1, column=0, columnspan=2, sticky=tk.W, pady=(5, 0))
  323. # 单个JSON选项
  324. self.single_json_var = tk.BooleanVar(value=True)
  325. ttk.Checkbutton(config_frame, text="单个json", variable=self.single_json_var,
  326. command=self.on_single_json_change).grid(row=2, column=0, columnspan=2, sticky=tk.W, pady=(5, 0))
  327. # 文件列表
  328. list_frame = ttk.Frame(left_frame)
  329. list_frame.grid(row=3, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
  330. list_frame.columnconfigure(0, weight=1)
  331. list_frame.rowconfigure(0, weight=1)
  332. self.file_listbox = tk.Listbox(list_frame, selectmode=tk.MULTIPLE, height=15)
  333. scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.file_listbox.yview)
  334. self.file_listbox.configure(yscrollcommand=scrollbar.set)
  335. self.file_listbox.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
  336. scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
  337. # 文件操作按钮
  338. file_btn_frame = ttk.Frame(left_frame)
  339. file_btn_frame.grid(row=4, column=0, sticky=(tk.W, tk.E), pady=(10, 0))
  340. ttk.Button(file_btn_frame, text="预览选中文件",
  341. command=self.preview_selected_files).pack(side=tk.LEFT)
  342. ttk.Button(file_btn_frame, text="清空选择",
  343. command=self.clear_selection).pack(side=tk.LEFT, padx=(10, 0))
  344. # 右侧面板 - 配置预览和操作
  345. right_frame = ttk.LabelFrame(main_frame, text="配置预览与操作", padding="10")
  346. right_frame.grid(row=1, column=1, sticky=(tk.W, tk.E, tk.N, tk.S))
  347. right_frame.columnconfigure(0, weight=1)
  348. right_frame.rowconfigure(0, weight=1)
  349. # 配置预览文本框
  350. self.preview_text = scrolledtext.ScrolledText(right_frame, height=20, width=50)
  351. self.preview_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10))
  352. # 操作按钮
  353. btn_frame = ttk.Frame(right_frame)
  354. btn_frame.grid(row=1, column=0, sticky=(tk.W, tk.E))
  355. ttk.Button(btn_frame, text="导入配置",
  356. command=self.import_config).pack(side=tk.LEFT)
  357. ttk.Button(btn_frame, text="导入技能配置",
  358. command=self.import_skill_config).pack(side=tk.LEFT, padx=(10, 0))
  359. ttk.Button(btn_frame, text="备份当前配置",
  360. command=self.backup_config).pack(side=tk.LEFT, padx=(10, 0))
  361. ttk.Button(btn_frame, text="恢复默认配置",
  362. command=self.restore_default_config).pack(side=tk.LEFT, padx=(10, 0))
  363. # 敌人配置专用按钮
  364. enemy_btn_frame = ttk.Frame(right_frame)
  365. enemy_btn_frame.grid(row=2, column=0, sticky=(tk.W, tk.E), pady=(10, 0))
  366. ttk.Button(enemy_btn_frame, text="修复敌人JSON",
  367. command=self.fix_enemies_json).pack(side=tk.LEFT)
  368. ttk.Button(enemy_btn_frame, text="自动导入敌人配置",
  369. command=self.auto_import_enemies_from_excel).pack(side=tk.LEFT, padx=(10, 0))
  370. # 底部状态栏
  371. self.status_var = tk.StringVar()
  372. self.status_var.set("就绪")
  373. status_bar = ttk.Label(main_frame, textvariable=self.status_var,
  374. relief=tk.SUNKEN, anchor=tk.W)
  375. status_bar.grid(row=2, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(10, 0))
  376. # 绑定事件
  377. self.file_listbox.bind('<<ListboxSelect>>', self.on_file_select)
  378. def browse_files(self):
  379. """浏览Excel/CSV文件"""
  380. if PANDAS_AVAILABLE:
  381. title = "选择Excel/CSV配置文件"
  382. filetypes = [("Excel文件", "*.xlsx *.xls"), ("CSV文件", "*.csv"), ("文本文件", "*.txt"), ("所有文件", "*.*")]
  383. else:
  384. title = "选择CSV配置文件"
  385. filetypes = [("CSV文件", "*.csv"), ("文本文件", "*.txt"), ("所有文件", "*.*")]
  386. files = filedialog.askopenfilenames(
  387. title=title,
  388. filetypes=filetypes,
  389. initialdir=str(self.excel_dir)
  390. )
  391. if files:
  392. self.file_listbox.delete(0, tk.END)
  393. for file in files:
  394. self.file_listbox.insert(tk.END, file)
  395. self.status_var.set(f"已选择 {len(files)} 个文件")
  396. def scan_project_files(self):
  397. """扫描项目目录中的Excel/CSV文件"""
  398. self.status_var.set("正在扫描项目目录...")
  399. def scan_thread():
  400. config_files = []
  401. # 扫描常见的配置目录
  402. scan_dirs = [
  403. self.project_root / "assets/resources/data",
  404. self.project_root / "assets/resources/config",
  405. self.project_root / "assets/excel",
  406. self.project_root / "config",
  407. self.project_root / "data",
  408. self.project_root
  409. ]
  410. # 根据pandas可用性选择扫描的文件类型
  411. patterns = ['*.xlsx', '*.xls', '*.csv', '*.txt'] if PANDAS_AVAILABLE else ['*.csv', '*.txt']
  412. for scan_dir in scan_dirs:
  413. if scan_dir.exists():
  414. for pattern in patterns:
  415. config_files.extend(scan_dir.rglob(pattern))
  416. # 更新UI
  417. self.root.after(0, self.update_file_list, config_files)
  418. threading.Thread(target=scan_thread, daemon=True).start()
  419. def browse_json_path(self):
  420. """浏览JSON输出路径"""
  421. # 根据用户选择的选项组合决定选择文件还是目录
  422. multi_sheet = self.multi_sheet_var.get()
  423. single_json = self.single_json_var.get()
  424. if multi_sheet and not single_json:
  425. # 多工作表文件且不是单个JSON:输出到目录(每个sheet一个JSON文件)
  426. path = filedialog.askdirectory(
  427. title="选择JSON输出目录",
  428. initialdir=str(self.project_root / "assets/resources/data")
  429. )
  430. else:
  431. # 其他情况:输出单个JSON文件
  432. path = filedialog.askopenfilename(
  433. title="选择JSON输出文件",
  434. filetypes=[("JSON文件", "*.json"), ("所有文件", "*.*")],
  435. initialdir=str(self.project_root / "assets/resources/data")
  436. )
  437. if path:
  438. self.json_path_var.set(path)
  439. self.selected_json_path = Path(path)
  440. self.status_var.set(f"已选择输出路径: {Path(path).name}")
  441. def on_multi_sheet_change(self):
  442. """多工作表选项变化处理"""
  443. # 清空当前选择的JSON路径,让用户重新选择
  444. self.json_path_var.set("")
  445. def on_single_json_change(self):
  446. """单个JSON选项变化处理"""
  447. # 清空当前选择的JSON路径,让用户重新选择
  448. self.json_path_var.set("")
  449. self.selected_json_path = None
  450. if self.multi_sheet_var.get():
  451. self.status_var.set("多工作表模式:请选择输出目录")
  452. else:
  453. self.status_var.set("单工作表模式:请选择输出JSON文件")
  454. def update_file_list(self, files):
  455. """更新文件列表"""
  456. self.file_listbox.delete(0, tk.END)
  457. for file in files:
  458. self.file_listbox.insert(tk.END, str(file))
  459. file_type_text = "Excel/CSV文件" if PANDAS_AVAILABLE else "CSV文件"
  460. self.status_var.set(f"找到 {len(files)} 个{file_type_text}")
  461. def on_file_select(self, event):
  462. """文件选择事件处理"""
  463. selection = self.file_listbox.curselection()
  464. if selection:
  465. self.selected_files = [self.file_listbox.get(i) for i in selection]
  466. # 根据选择的文件设置配置映射
  467. self._set_config_mapping_for_files()
  468. self.preview_selected_files()
  469. self.status_var.set(f"已选择 {len(selection)} 个文件")
  470. def _set_config_mapping_for_files(self):
  471. """根据选择的文件设置配置映射"""
  472. if not self.selected_files:
  473. return
  474. # 获取第一个文件名来判断配置类型
  475. first_file = Path(self.selected_files[0])
  476. filename = first_file.name.lower()
  477. # 根据文件名设置配置映射
  478. if '武器' in filename or 'weapon' in filename:
  479. self.current_mapping = {
  480. 'format_type': 'horizontal',
  481. 'param_types': {
  482. 'ID': str,
  483. '名称': str,
  484. '伤害': int,
  485. '射程': int,
  486. '射速': float,
  487. '弹药容量': int,
  488. '重装时间': float,
  489. '解锁等级': int,
  490. '价格': int,
  491. '描述': str,
  492. '最大等级': int,
  493. # 英文字段支持
  494. 'weaponId': str,
  495. 'name': str,
  496. 'damage': int,
  497. 'range': int,
  498. 'fireRate': float,
  499. 'ammoCapacity': int,
  500. 'reloadTime': float,
  501. 'unlockLevel': int,
  502. 'price': int,
  503. 'description': str,
  504. 'maxLevel': int
  505. }
  506. }
  507. elif '敌人' in filename or 'enemy' in filename:
  508. self.current_mapping = {
  509. 'format_type': 'multi_sheet',
  510. 'multi_sheet': True,
  511. 'param_types': {
  512. # 基础配置字段
  513. '敌人ID': ('id', str),
  514. '敌人名称': ('name', str),
  515. '敌人类型': ('type', str),
  516. '生命值': ('stats.health', int),
  517. '最大生命值': ('stats.maxHealth', int),
  518. '防御力': ('stats.defense', int),
  519. '移动速度': ('stats.speed', float),
  520. # 战斗配置字段
  521. '攻击伤害': ('combat.attackDamage', int),
  522. '攻击范围': ('combat.attackRange', int),
  523. '攻击速度': ('combat.attackSpeed', float),
  524. '能否格挡': ('combat.canBlock', bool),
  525. '格挡几率': ('combat.blockChance', float),
  526. '格挡伤害减免': ('combat.blockDamageReduction', float),
  527. '攻击冷却': ('combat.attackCooldown', float),
  528. '攻击类型': ('combat.attackType', str),
  529. '攻击延迟': ('combat.attackDelay', float),
  530. '武器类型': ('combat.weaponType', str),
  531. '投射物类型': ('combat.projectileType', str),
  532. '投射物速度': ('combat.projectileSpeed', float),
  533. # 移动配置字段
  534. '移动模式': ('movement.pattern', str),
  535. '巡逻范围': ('movement.patrolRange', int),
  536. '追击范围': ('movement.chaseRange', int),
  537. '旋转速度': ('movement.rotationSpeed', float),
  538. '移动类型': ('movement.moveType', str),
  539. '摆动幅度': ('movement.swingAmplitude', float),
  540. '摆动频率': ('movement.swingFrequency', float),
  541. '速度变化': ('movement.speedVariation', float),
  542. # 视觉配置字段
  543. '精灵路径': ('visualConfig.spritePath', str),
  544. '缩放比例': ('visualConfig.scale', float),
  545. '动画速度': ('visualConfig.animationSpeed', float),
  546. '水平翻转': ('visualConfig.flipX', bool),
  547. '待机动画': ('visualConfig.animations.idle', str),
  548. '行走动画': ('visualConfig.animations.walk', str),
  549. '攻击动画': ('visualConfig.animations.attack', str),
  550. '死亡动画': ('visualConfig.animations.death', str),
  551. '武器道具': ('visualConfig.weaponProp', str),
  552. # 音频配置字段
  553. '攻击音效': ('audioConfig.attackSound', str),
  554. '死亡音效': ('audioConfig.deathSound', str),
  555. '受击音效': ('audioConfig.hitSound', str),
  556. '行走音效': ('audioConfig.walkSound', str),
  557. '格挡音效': ('audioConfig.blockSound', str),
  558. '隐身音效': ('audioConfig.stealthSound', str),
  559. '护甲破碎音效': ('audioConfig.armorBreakSound', str),
  560. '引信音效': ('audioConfig.fuseSound', str),
  561. # 特殊能力配置字段
  562. '能力类型': ('specialAbilities.type', str),
  563. '伤害': ('specialAbilities.damage', int),
  564. '范围': ('specialAbilities.range', float),
  565. '冷却时间': ('specialAbilities.cooldown', float),
  566. # BOSS配置字段
  567. '是否BOSS': ('bossConfig.isBoss', bool),
  568. '阶段数': ('bossConfig.phases', int),
  569. '狂暴阈值': ('bossConfig.enrageThreshold', float),
  570. '狂暴伤害倍数': ('bossConfig.enrageDamageMultiplier', float),
  571. '狂暴速度倍数': ('bossConfig.enrageSpeedMultiplier', float),
  572. # 英文字段支持
  573. 'id': ('id', str),
  574. 'name': ('name', str),
  575. 'type': ('type', str),
  576. 'health': ('stats.health', int),
  577. 'maxHealth': ('stats.maxHealth', int),
  578. 'defense': ('stats.defense', int),
  579. 'speed': ('stats.speed', float),
  580. 'attackDamage': ('combat.attackDamage', int),
  581. 'attackRange': ('combat.attackRange', int),
  582. 'attackSpeed': ('combat.attackSpeed', float)
  583. }
  584. }
  585. elif '技能' in filename or 'skill' in filename:
  586. self.current_mapping = {
  587. 'format_type': 'horizontal',
  588. 'param_types': {
  589. 'ID': str,
  590. '名称': str,
  591. '伤害': int,
  592. '冷却时间': float,
  593. '消耗': int,
  594. '描述': str,
  595. # 英文字段支持
  596. 'skillId': str,
  597. 'skillName': str,
  598. 'damage': int,
  599. 'cooldown': float,
  600. 'cost': int,
  601. 'description': str
  602. }
  603. }
  604. elif '关卡' in filename or 'level' in filename:
  605. self.current_mapping = {
  606. 'format_type': 'horizontal',
  607. 'param_types': {
  608. 'ID': str,
  609. '名称': str,
  610. '敌人列表': str,
  611. '波数': int,
  612. '难度': int,
  613. # 英文字段支持
  614. 'levelId': str,
  615. 'name': str,
  616. 'enemies': str,
  617. 'waves': int,
  618. 'difficulty': int
  619. }
  620. }
  621. elif 'ballcontroller' in filename or '球控制' in filename or '标准配置表' in filename:
  622. # BallController配置映射(纵向格式:参数名-数值)
  623. self.current_mapping = {
  624. 'format_type': 'vertical',
  625. 'param_types': {
  626. 'baseSpeed': float,
  627. 'maxReflectionRandomness': float,
  628. 'antiTrapTimeWindow': float,
  629. 'antiTrapHitThreshold': int,
  630. 'deflectionAttemptThreshold': int,
  631. 'antiTrapDeflectionMultiplier': float,
  632. 'FIRE_COOLDOWN': float,
  633. 'ballRadius': float,
  634. 'gravityScale': float,
  635. 'linearDamping': float,
  636. 'angularDamping': float,
  637. 'colliderGroup': int,
  638. 'colliderTag': int,
  639. 'friction': float,
  640. 'restitution': float,
  641. 'safeDistance': float,
  642. 'edgeOffset': float,
  643. 'sensor': bool,
  644. 'maxAttempts': int
  645. }
  646. }
  647. else:
  648. # 默认配置映射
  649. self.current_mapping = {
  650. 'format_type': 'horizontal',
  651. 'param_types': {}
  652. }
  653. print(f"为文件 {filename} 设置配置映射: {self.current_mapping['format_type']} 格式")
  654. print(f"支持的参数类型: {list(self.current_mapping['param_types'].keys())}")
  655. def preview_selected_files(self):
  656. """预览选中的文件"""
  657. selection = self.file_listbox.curselection()
  658. if not selection:
  659. messagebox.showwarning("警告", "请先选择要预览的文件")
  660. return
  661. self.preview_text.delete(1.0, tk.END)
  662. self.config_data = {}
  663. for i in selection:
  664. file_path = Path(self.file_listbox.get(i))
  665. self.preview_text.insert(tk.END, f"=== {file_path.name} ===\n")
  666. try:
  667. # 根据文件扩展名和pandas可用性选择读取方法
  668. if file_path.suffix.lower() in ['.xlsx', '.xls']:
  669. if PANDAS_AVAILABLE:
  670. file_config = self.read_excel_config(file_path)
  671. else:
  672. self.preview_text.insert(tk.END, "Excel文件需要pandas库支持,请安装: pip install pandas openpyxl\n\n")
  673. continue
  674. elif file_path.suffix.lower() in ['.csv', '.txt']:
  675. file_config = self.read_csv_config(file_path)
  676. else:
  677. self.preview_text.insert(tk.END, "不支持的文件格式\n\n")
  678. continue
  679. if file_config:
  680. # 检查file_config的类型,如果是列表则特殊处理
  681. if isinstance(file_config, list):
  682. # 对于关卡配置等返回列表的情况
  683. self.preview_text.insert(tk.END, f"找到 {len(file_config)} 个配置项:\n")
  684. for i, item in enumerate(file_config):
  685. self.preview_text.insert(tk.END, f" 配置项 {i+1}: {item}\n")
  686. # 将列表数据存储到config_data中
  687. self.config_data[file_path.stem] = file_config
  688. else:
  689. # 原有的字典处理逻辑
  690. self.config_data.update(file_config)
  691. # 显示预览
  692. self.preview_text.insert(tk.END, f"找到 {len(file_config)} 个配置参数:\n")
  693. for key, value in file_config.items():
  694. self.preview_text.insert(tk.END, f" {key}: {value}\n")
  695. else:
  696. self.preview_text.insert(tk.END, "未找到有效的配置数据\n")
  697. except Exception as e:
  698. self.preview_text.insert(tk.END, f"读取失败: {str(e)}\n")
  699. self.preview_text.insert(tk.END, "\n")
  700. # 显示合并后的配置
  701. if self.config_data:
  702. self.preview_text.insert(tk.END, "=== 合并后的配置 ===\n")
  703. self.preview_text.insert(tk.END, json.dumps(self.config_data, indent=2, ensure_ascii=False))
  704. self.status_var.set(f"预览完成,共 {len(self.config_data)} 个配置参数")
  705. def read_excel_config(self, file_path):
  706. """读取Excel配置文件"""
  707. config = {}
  708. if not PANDAS_AVAILABLE:
  709. print("错误: pandas未安装,无法读取Excel文件")
  710. return config
  711. try:
  712. # 根据用户选择的"多工作表文件"选项决定处理方式
  713. if self.multi_sheet_var.get():
  714. # 多工作表模式:读取所有工作表
  715. print(f"多工作表模式处理文件: {file_path.name}")
  716. # 获取所有工作表名称(避免缓存问题)
  717. try:
  718. # 先尝试读取第一个工作表来获取所有工作表名称
  719. excel_file = pd.ExcelFile(file_path)
  720. sheet_names = excel_file.sheet_names
  721. excel_file.close() # 关闭文件句柄
  722. print(f"发现工作表: {sheet_names}")
  723. except Exception as e:
  724. print(f"获取工作表名称失败: {e}")
  725. return config
  726. all_sheets_data = {}
  727. for sheet_name in sheet_names:
  728. try:
  729. # 每次都重新读取文件,避免缓存
  730. df = pd.read_excel(file_path, sheet_name=sheet_name, engine='openpyxl')
  731. all_sheets_data[sheet_name] = df
  732. print(f"成功读取工作表: {sheet_name}, 数据行数: {len(df)}")
  733. # 打印第一行数据用于调试
  734. if len(df) > 0 and sheet_name == '敌人基础配置':
  735. print(f"敌人基础配置第一行数据: {df.iloc[0].to_dict()}")
  736. except Exception as e:
  737. print(f"读取工作表 {sheet_name} 时出错: {e}")
  738. # 根据工作表内容判断配置类型
  739. if any('关卡' in name for name in sheet_names):
  740. config = self.parse_level_multi_sheet_data(all_sheets_data, file_path.name)
  741. elif any('技能' in name for name in sheet_names):
  742. config = self.parse_skill_multi_sheet_data(all_sheets_data, file_path.name)
  743. elif any('敌人' in name for name in sheet_names):
  744. config = self.parse_enemy_multi_sheet_data(all_sheets_data, file_path.name)
  745. elif any('武器' in name for name in sheet_names):
  746. config = self.parse_weapon_multi_sheet_data(all_sheets_data, file_path.name)
  747. else:
  748. # 通用多工作表处理
  749. config = self.parse_multi_sheet_data(all_sheets_data, file_path.name)
  750. else:
  751. # 单工作表模式:读取第一个工作表或指定工作表
  752. print(f"单工作表模式处理文件: {file_path.name}")
  753. sheet_name = None
  754. if self.current_mapping and 'sheet_name' in self.current_mapping:
  755. sheet_name = self.current_mapping['sheet_name']
  756. # 读取Excel文件
  757. if sheet_name:
  758. df = pd.read_excel(file_path, sheet_name=sheet_name)
  759. print(f"读取指定工作表: {sheet_name}, 数据行数: {len(df)}")
  760. else:
  761. df = pd.read_excel(file_path)
  762. print(f"读取默认工作表, 数据行数: {len(df)}")
  763. config = self.parse_config_data(df, file_path.name)
  764. except Exception as e:
  765. print(f"读取Excel文件 {file_path.name} 时出错: {e}")
  766. return config
  767. def read_csv_config(self, file_path):
  768. """读取CSV配置文件"""
  769. config = {}
  770. try:
  771. # 如果pandas可用,优先使用pandas读取CSV(支持更多格式)
  772. if PANDAS_AVAILABLE:
  773. try:
  774. df = pd.read_csv(file_path)
  775. config = self.parse_config_data(df, file_path.name)
  776. except:
  777. # 如果pandas读取失败,回退到原始CSV读取方法
  778. config = self.read_csv_fallback(file_path)
  779. else:
  780. # 如果pandas不可用,直接使用原始CSV读取方法
  781. config = self.read_csv_fallback(file_path)
  782. except Exception as e:
  783. print(f"读取文件 {file_path.name} 时出错: {e}")
  784. return config
  785. def read_csv_fallback(self, file_path):
  786. """原始CSV读取方法(不依赖pandas)"""
  787. config = {}
  788. try:
  789. with open(file_path, 'r', encoding='utf-8') as f:
  790. reader = csv.reader(f)
  791. for row_num, row in enumerate(reader, 1):
  792. if len(row) < 2:
  793. continue
  794. param_name = row[0].strip()
  795. param_value = row[1].strip()
  796. # 跳过标题行
  797. if param_name in ['参数名', 'parameter', 'name']:
  798. continue
  799. # 检查参数是否有效
  800. if param_name in self.param_types:
  801. try:
  802. param_type = self.param_types[param_name]
  803. if param_type == bool:
  804. config[param_name] = param_value.lower() in ['true', '1', 'yes', 'on']
  805. else:
  806. config[param_name] = param_type(param_value)
  807. except (ValueError, TypeError):
  808. continue
  809. except Exception as e:
  810. print(f"读取CSV文件 {file_path.name} 时出错: {e}")
  811. return config
  812. def parse_level_multi_sheet_data(self, all_sheets_data, filename):
  813. """解析关卡配置的多工作表数据"""
  814. config = []
  815. try:
  816. # 获取各个工作表的数据(支持中英文工作表名)
  817. basic_config = None
  818. for sheet_name in ['关卡基础配置', 'Level Config', 'levels', '关卡配置']:
  819. if sheet_name in all_sheets_data:
  820. basic_config = all_sheets_data[sheet_name]
  821. break
  822. weapon_config = None
  823. for sheet_name in ['关卡武器配置', 'Level Weapon Config', 'level_weapons', '武器配置']:
  824. if sheet_name in all_sheets_data:
  825. weapon_config = all_sheets_data[sheet_name]
  826. break
  827. wave_config = None
  828. for sheet_name in ['关卡波次配置', 'Wave Config', 'waves', '波次配置']:
  829. if sheet_name in all_sheets_data:
  830. wave_config = all_sheets_data[sheet_name]
  831. break
  832. enemy_config = None
  833. for sheet_name in ['敌人详细配置', 'Enemy Detail Config', 'enemy_details', '敌人配置']:
  834. if sheet_name in all_sheets_data:
  835. enemy_config = all_sheets_data[sheet_name]
  836. break
  837. if basic_config is None:
  838. print("错误: 未找到关卡基础配置工作表(支持的工作表名: 关卡基础配置, Level Config, levels, 关卡配置)")
  839. return config
  840. # 处理每个关卡
  841. for _, basic_row in basic_config.iterrows():
  842. if pd.isna(basic_row['关卡ID']):
  843. continue
  844. level_id = str(basic_row['关卡ID'])
  845. # 获取武器配置
  846. available_weapons = []
  847. if weapon_config is not None:
  848. weapon_rows = weapon_config[weapon_config['关卡ID'] == level_id]
  849. for _, weapon_row in weapon_rows.iterrows():
  850. if pd.notna(weapon_row['可用武器']):
  851. weapons_str = str(weapon_row['可用武器'])
  852. # 支持多种分隔符
  853. import re
  854. weapons = re.split(r'[、,,;;]', weapons_str)
  855. available_weapons.extend([w.strip() for w in weapons if w.strip()])
  856. level_data = {
  857. 'levelId': level_id,
  858. 'name': str(basic_row['关卡名称']) if pd.notna(basic_row['关卡名称']) else '',
  859. 'scene': str(basic_row['场景']) if pd.notna(basic_row['场景']) else 'grassland',
  860. 'description': str(basic_row['描述']) if pd.notna(basic_row['描述']) else '',
  861. 'backgroundImage': str(basic_row['关卡背景图路径']) if pd.notna(basic_row['关卡背景图路径']) else 'images/LevelBackground/BG1',
  862. 'availableWeapons': available_weapons,
  863. 'coinReward': int(basic_row['钞票奖励']) if pd.notna(basic_row['钞票奖励']) else 100,
  864. 'diamondReward': int(basic_row['钻石奖励']) if pd.notna(basic_row['钻石奖励']) else 0,
  865. 'timeLimit': 300, # 默认值
  866. 'difficulty': 'normal', # 默认值
  867. 'healthMultiplier': float(basic_row['生命倍数']) if pd.notna(basic_row['生命倍数']) else 1.0, # 从Excel读取生命倍数
  868. 'waves': []
  869. }
  870. # 获取该关卡的波次配置
  871. if wave_config is not None:
  872. level_waves = wave_config[wave_config['关卡ID'] == level_id]
  873. for _, wave_row in level_waves.iterrows():
  874. wave_id = int(wave_row['波次ID']) if pd.notna(wave_row['波次ID']) else 1
  875. # 获取该波次的敌人配置
  876. wave_enemies = []
  877. if enemy_config is not None:
  878. wave_enemy_data = enemy_config[
  879. (enemy_config['关卡ID'] == level_id) &
  880. (enemy_config['波次ID'] == wave_id)
  881. ]
  882. for _, enemy_row in wave_enemy_data.iterrows():
  883. enemy_type = str(enemy_row['敌人类型']) if pd.notna(enemy_row['敌人类型']) else '普通僵尸'
  884. enemy_data = {
  885. 'enemyType': enemy_type,
  886. 'count': int(enemy_row['数量']) if pd.notna(enemy_row['数量']) else 1,
  887. 'spawnInterval': float(enemy_row['生成间隔']) if pd.notna(enemy_row['生成间隔']) else 2.0,
  888. 'spawnDelay': float(enemy_row['生成延迟']) if pd.notna(enemy_row['生成延迟']) else 0.0,
  889. 'characteristics': str(enemy_row['特征描述']) if pd.notna(enemy_row['特征描述']) else ''
  890. }
  891. wave_enemies.append(enemy_data)
  892. wave_data = {
  893. 'waveId': wave_id,
  894. 'enemies': wave_enemies
  895. }
  896. level_data['waves'].append(wave_data)
  897. config.append(level_data)
  898. print(f"处理关卡: {level_id}, 波次数: {len(level_data['waves'])}")
  899. print(f"成功解析关卡配置,共 {len(config)} 个关卡")
  900. except Exception as e:
  901. print(f"解析关卡多工作表数据时出错: {e}")
  902. import traceback
  903. traceback.print_exc()
  904. return config
  905. def parse_skill_multi_sheet_data(self, all_sheets_data, filename):
  906. """解析技能配置的多工作表数据"""
  907. skills_config = []
  908. try:
  909. # 检查是否为局外技能配置表格式(包含技能类型配置、技能组配置、基本配置)
  910. if '技能类型配置' in all_sheets_data and '技能组配置' in all_sheets_data and '基本配置' in all_sheets_data:
  911. print("检测到局外技能配置表格式,使用集成的技能配置导入工具处理")
  912. # 使用集成的SkillConfigImporter处理
  913. try:
  914. skill_importer = SkillConfigImporter(self.excel_dir)
  915. success = skill_importer.import_config()
  916. if success:
  917. print("技能配置导入成功!")
  918. # 返回成功标识,让调用者知道已经处理完成
  919. return [{'status': 'imported', 'message': '技能配置已成功导入到skill_config.json'}]
  920. else:
  921. print("技能配置导入失败")
  922. return [{'status': 'error', 'message': '技能配置导入失败'}]
  923. except Exception as e:
  924. print(f"技能配置导入过程中出错: {e}")
  925. return [{'status': 'error', 'message': f'技能配置导入出错: {e}'}]
  926. return skills_config
  927. # 获取技能信息表数据(支持中英文工作表名)
  928. skill_info = None
  929. for sheet_name in ['技能信息表', 'Skill Config', 'skills', '技能配置']:
  930. if sheet_name in all_sheets_data:
  931. skill_info = all_sheets_data[sheet_name]
  932. break
  933. if skill_info is None:
  934. print("错误: 未找到技能信息表工作表(支持的工作表名: 技能信息表, Skill Config, skills, 技能配置)")
  935. return skills_config
  936. # 处理每个技能(支持中英文字段名)
  937. for _, skill_row in skill_info.iterrows():
  938. # 获取技能ID(支持中英文字段名)
  939. skill_id_field = None
  940. for field in ['id', '技能ID', 'skill_id', 'ID']:
  941. if field in skill_row and pd.notna(skill_row[field]):
  942. skill_id_field = field
  943. break
  944. if skill_id_field is None:
  945. continue
  946. def get_skill_field_value(row, cn_field, en_fields, default_value, value_type=str):
  947. """获取技能字段值,支持中英文字段名"""
  948. for field in [cn_field] + (en_fields if isinstance(en_fields, list) else [en_fields]):
  949. if field in row and pd.notna(row[field]):
  950. try:
  951. return value_type(row[field])
  952. except (ValueError, TypeError):
  953. continue
  954. return default_value
  955. skill_data = {
  956. 'id': str(skill_row[skill_id_field]),
  957. 'name': get_skill_field_value(skill_row, '技能名称', ['name', 'skill_name'], ''),
  958. 'description': get_skill_field_value(skill_row, '技能描述', ['description', 'desc'], ''),
  959. 'iconPath': get_skill_field_value(skill_row, '图标路径', ['iconPath', 'icon_path', 'icon'], ''),
  960. 'maxLevel': get_skill_field_value(skill_row, '最大等级', ['maxLevel', 'max_level'], 5, int),
  961. 'currentLevel': get_skill_field_value(skill_row, '当前等级', ['currentLevel', 'current_level'], 0, int),
  962. 'priceReduction': get_skill_field_value(skill_row, '价格减少', ['priceReduction', 'price_reduction'], 0.0, float),
  963. 'critChanceIncrease': get_skill_field_value(skill_row, '暴击几率增加', ['critChanceIncrease', 'crit_chance_increase'], 0.0, float),
  964. 'critDamageBonus': get_skill_field_value(skill_row, '暴击伤害加成', ['critDamageBonus', 'crit_damage_bonus'], 0.0, float),
  965. 'healthIncrease': get_skill_field_value(skill_row, '生命值增加', ['healthIncrease', 'health_increase'], 0.0, float),
  966. 'multiShotChance': get_skill_field_value(skill_row, '多重射击几率', ['multiShotChance', 'multi_shot_chance'], 0.0, float),
  967. 'energyGainIncrease': get_skill_field_value(skill_row, '能量加成', ['energyGainIncrease', 'energy_gain_increase'], 0.0, float),
  968. 'ballSpeedIncrease': get_skill_field_value(skill_row, '速度提升', ['ballSpeedIncrease', 'ball_speed_increase'], 0.0, float)
  969. }
  970. skills_config.append(skill_data)
  971. print(f"处理技能: {skill_data['id']} - {skill_data['name']}")
  972. print(f"成功解析技能配置,共 {len(skills_config)} 个技能")
  973. except Exception as e:
  974. print(f"解析技能多工作表数据时出错: {e}")
  975. import traceback
  976. traceback.print_exc()
  977. return skills_config
  978. def parse_enemy_multi_sheet_data(self, all_sheets_data, filename):
  979. """解析敌人配置的多工作表数据"""
  980. enemies_config = []
  981. try:
  982. # 获取敌人基础配置数据(支持中英文工作表名)
  983. enemy_info = None
  984. for sheet_name in ['敌人基础配置', 'Enemy Config', 'enemies', '敌人配置']:
  985. if sheet_name in all_sheets_data:
  986. enemy_info = all_sheets_data[sheet_name]
  987. break
  988. if enemy_info is None:
  989. print("错误: 未找到敌人基础配置工作表(支持的工作表名: 敌人基础配置, Enemy Config, enemies, 敌人配置)")
  990. return enemies_config
  991. # 获取其他配置工作表
  992. combat_sheet = all_sheets_data.get('战斗配置')
  993. movement_sheet = all_sheets_data.get('移动配置')
  994. visual_sheet = all_sheets_data.get('视觉配置')
  995. audio_sheet = all_sheets_data.get('音频配置')
  996. special_sheet = all_sheets_data.get('特殊能力配置')
  997. boss_sheet = all_sheets_data.get('BOSS配置')
  998. def get_field_value(row, cn_field, en_fields, default_value, value_type=str):
  999. """获取字段值,支持中英文字段名"""
  1000. for field in [cn_field] + (en_fields if isinstance(en_fields, list) else [en_fields]):
  1001. if field in row and pd.notna(row[field]):
  1002. try:
  1003. return value_type(row[field])
  1004. except (ValueError, TypeError):
  1005. continue
  1006. return default_value
  1007. def find_row_by_id(sheet, enemy_id):
  1008. """根据敌人ID查找对应行"""
  1009. if sheet is None:
  1010. return None
  1011. for _, row in sheet.iterrows():
  1012. if str(row.get('敌人ID', '')) == enemy_id:
  1013. return row
  1014. return None
  1015. # 处理每个敌人(支持中英文字段名)
  1016. for _, enemy_row in enemy_info.iterrows():
  1017. # 获取敌人ID(支持中英文字段名)
  1018. enemy_id_field = None
  1019. for field in ['id', '敌人ID', 'enemy_id', 'ID']:
  1020. if field in enemy_row and pd.notna(enemy_row[field]):
  1021. enemy_id_field = field
  1022. break
  1023. if enemy_id_field is None:
  1024. continue
  1025. enemy_id = str(enemy_row[enemy_id_field])
  1026. # 基础配置(不包含攻击伤害,攻击伤害从战斗配置表获取)
  1027. enemy_data = {
  1028. 'id': enemy_id,
  1029. 'name': get_field_value(enemy_row, '敌人名称', ['name', 'enemy_name'], ''),
  1030. 'type': get_field_value(enemy_row, '敌人类型', ['type', 'enemy_type'], 'basic'),
  1031. 'health': get_field_value(enemy_row, '生命值', ['health', 'hp'], 100, int),
  1032. 'speed': get_field_value(enemy_row, '移动速度', ['speed', 'move_speed'], 1.0, float),
  1033. 'attackRange': get_field_value(enemy_row, '攻击范围', ['attackRange', 'attack_range', 'range'], 1.0, float),
  1034. 'attackSpeed': get_field_value(enemy_row, '攻击速度', ['attackSpeed', 'attack_speed'], 1.0, float),
  1035. 'defense': get_field_value(enemy_row, '防御力', ['defense'], 0, int),
  1036. 'attackDamage': 10 # 默认值,将从战斗配置表覆盖
  1037. }
  1038. # 获取战斗配置
  1039. combat_row = find_row_by_id(combat_sheet, enemy_id)
  1040. if combat_row is not None:
  1041. enemy_data.update({
  1042. 'attackDamage': get_field_value(combat_row, '攻击伤害', ['attackDamage', 'attack'], 10, int),
  1043. 'attackType': get_field_value(combat_row, '攻击类型', ['attackType'], 'melee'),
  1044. 'attackDelay': get_field_value(combat_row, '攻击延迟', ['attackDelay'], 0.5, float),
  1045. 'weaponType': get_field_value(combat_row, '武器类型', ['weaponType'], 'none'),
  1046. 'projectileType': get_field_value(combat_row, '投射物类型', ['projectileType'], 'none'),
  1047. 'projectileSpeed': get_field_value(combat_row, '投射物速度', ['projectileSpeed'], 100, float),
  1048. 'canBlock': get_field_value(combat_row, '能否格挡', ['canBlock'], False, bool),
  1049. 'blockChance': get_field_value(combat_row, '格挡几率', ['blockChance'], 0.0, float),
  1050. 'blockDamageReduction': get_field_value(combat_row, '格挡伤害减免', ['blockDamageReduction'], 0.5, float)
  1051. })
  1052. # 获取移动配置
  1053. movement_row = find_row_by_id(movement_sheet, enemy_id)
  1054. if movement_row is not None:
  1055. enemy_data.update({
  1056. 'movementPattern': get_field_value(movement_row, '移动模式', ['movementPattern'], 'walk_forward'),
  1057. 'patrolRange': get_field_value(movement_row, '巡逻范围', ['patrolRange'], 100, int),
  1058. 'chaseRange': get_field_value(movement_row, '追击范围', ['chaseRange'], 200, int),
  1059. 'rotationSpeed': get_field_value(movement_row, '旋转速度', ['rotationSpeed'], 180, float),
  1060. 'moveType': get_field_value(movement_row, '移动类型', ['moveType'], 'straight'),
  1061. 'swingAmplitude': get_field_value(movement_row, '摆动幅度', ['swingAmplitude'], 0.0, float),
  1062. 'swingFrequency': get_field_value(movement_row, '摆动频率', ['swingFrequency'], 0.0, float),
  1063. 'speedVariation': get_field_value(movement_row, '速度变化', ['speedVariation'], 0.1, float)
  1064. })
  1065. # 获取视觉配置
  1066. visual_row = find_row_by_id(visual_sheet, enemy_id)
  1067. if visual_row is not None:
  1068. enemy_data.update({
  1069. 'spritePath': get_field_value(visual_row, '精灵路径', ['spritePath'], f'enemies/{enemy_id}'),
  1070. 'scale': get_field_value(visual_row, '缩放比例', ['scale'], 1.0, float),
  1071. 'animationSpeed': get_field_value(visual_row, '动画速度', ['animationSpeed'], 1.0, float),
  1072. 'flipX': get_field_value(visual_row, '水平翻转', ['flipX'], False, bool),
  1073. 'idleAnimation': get_field_value(visual_row, '待机动画', ['idleAnimation'], 'idle'),
  1074. 'walkAnimation': get_field_value(visual_row, '行走动画', ['walkAnimation'], 'walk'),
  1075. 'attackAnimation': get_field_value(visual_row, '攻击动画', ['attackAnimation'], 'attack'),
  1076. 'deathAnimation': get_field_value(visual_row, '死亡动画', ['deathAnimation'], 'dead'),
  1077. 'weaponProp': get_field_value(visual_row, '武器道具', ['weaponProp'], '')
  1078. })
  1079. # 获取音频配置
  1080. audio_row = find_row_by_id(audio_sheet, enemy_id)
  1081. if audio_row is not None:
  1082. enemy_data.update({
  1083. 'attackSound': get_field_value(audio_row, '攻击音效', ['attackSound'], 'enemy_attack'),
  1084. 'deathSound': get_field_value(audio_row, '死亡音效', ['deathSound'], 'enemy_death'),
  1085. 'hitSound': get_field_value(audio_row, '受击音效', ['hitSound'], 'enemy_hit'),
  1086. 'walkSound': get_field_value(audio_row, '行走音效', ['walkSound'], ''),
  1087. 'blockSound': get_field_value(audio_row, '格挡音效', ['blockSound'], ''),
  1088. 'stealthSound': get_field_value(audio_row, '隐身音效', ['stealthSound'], ''),
  1089. 'armorBreakSound': get_field_value(audio_row, '护甲破碎音效', ['armorBreakSound'], ''),
  1090. 'fuseSound': get_field_value(audio_row, '引信音效', ['fuseSound'], '')
  1091. })
  1092. # 获取特殊能力配置
  1093. special_abilities = []
  1094. if special_sheet is not None:
  1095. for _, special_row in special_sheet.iterrows():
  1096. if str(special_row.get('敌人ID', '')) == enemy_id:
  1097. ability_type = get_field_value(special_row, '能力类型', ['abilityType'], '')
  1098. if ability_type: # 只添加有效的特殊能力
  1099. special_abilities.append({
  1100. 'type': ability_type,
  1101. 'damage': get_field_value(special_row, '伤害', ['damage'], 0, int),
  1102. 'range': get_field_value(special_row, '范围', ['range'], 0, float),
  1103. 'cooldown': get_field_value(special_row, '冷却时间', ['cooldown'], 0, float)
  1104. })
  1105. if special_abilities:
  1106. enemy_data['specialAbilities'] = special_abilities
  1107. # 获取BOSS配置
  1108. boss_row = find_row_by_id(boss_sheet, enemy_id)
  1109. if boss_row is not None:
  1110. is_boss = get_field_value(boss_row, '是否BOSS', ['isBoss'], False, bool)
  1111. if is_boss:
  1112. enemy_data['bossConfig'] = {
  1113. 'isBoss': True,
  1114. 'phases': get_field_value(boss_row, '阶段数', ['phases'], 1, int),
  1115. 'enrageThreshold': get_field_value(boss_row, '狂暴阈值', ['enrageThreshold'], 0.3, float),
  1116. 'enrageDamageMultiplier': get_field_value(boss_row, '狂暴伤害倍数', ['enrageDamageMultiplier'], 1.5, float),
  1117. 'enrageSpeedMultiplier': get_field_value(boss_row, '狂暴速度倍数', ['enrageSpeedMultiplier'], 1.3, float)
  1118. }
  1119. # 转换为嵌套结构
  1120. nested_enemy_data = {
  1121. 'id': enemy_data['id'],
  1122. 'name': enemy_data['name'],
  1123. 'type': enemy_data['type'],
  1124. 'stats': {
  1125. 'health': enemy_data.get('health', 100),
  1126. 'maxHealth': enemy_data.get('health', 100),
  1127. 'defense': enemy_data.get('defense', 0),
  1128. 'speed': enemy_data.get('speed', 50)
  1129. },
  1130. 'movement': {
  1131. 'pattern': enemy_data.get('movementPattern', 'direct'),
  1132. 'speed': enemy_data.get('speed', 50),
  1133. 'patrolRange': enemy_data.get('patrolRange', 100),
  1134. 'chaseRange': enemy_data.get('chaseRange', 200),
  1135. 'rotationSpeed': enemy_data.get('rotationSpeed', 180),
  1136. 'moveType': enemy_data.get('moveType', 'straight'),
  1137. 'swingAmplitude': enemy_data.get('swingAmplitude', 0.0),
  1138. 'swingFrequency': enemy_data.get('swingFrequency', 0.0),
  1139. 'speedVariation': enemy_data.get('speedVariation', 0.1)
  1140. },
  1141. 'combat': {
  1142. 'attackDamage': enemy_data.get('attackDamage', 10),
  1143. 'attackRange': enemy_data.get('attackRange', 100),
  1144. 'attackSpeed': enemy_data.get('attackSpeed', 1.0),
  1145. 'canBlock': enemy_data.get('canBlock', False),
  1146. 'blockChance': enemy_data.get('blockChance', 0.0),
  1147. 'blockDamageReduction': enemy_data.get('blockDamageReduction', 0.5),
  1148. 'attackCooldown': enemy_data.get('attackCooldown', 1.0),
  1149. 'attackType': enemy_data.get('attackType', 'melee'),
  1150. 'attackDelay': enemy_data.get('attackDelay', 0.5),
  1151. 'weaponType': enemy_data.get('weaponType', 'none'),
  1152. 'projectileType': enemy_data.get('projectileType', 'none'),
  1153. 'projectileSpeed': enemy_data.get('projectileSpeed', 100.0)
  1154. },
  1155. 'visualConfig': {
  1156. 'spritePath': enemy_data.get('spritePath', f'Animation/EnemyAni/001'),
  1157. 'scale': enemy_data.get('scale', 1.0),
  1158. 'animationSpeed': enemy_data.get('animationSpeed', 1.0),
  1159. 'flipX': enemy_data.get('flipX', False),
  1160. 'tint': enemy_data.get('tint', '#FFFFFF'),
  1161. 'animations': {
  1162. 'idle': 'idle',
  1163. 'walk': 'walk',
  1164. 'attack': 'attack',
  1165. 'death': 'dead'
  1166. },
  1167. 'weaponProp': enemy_data.get('weaponProp', '')
  1168. },
  1169. 'audioConfig': {
  1170. 'attackSound': enemy_data.get('attackSound', 'enemy_attack'),
  1171. 'deathSound': enemy_data.get('deathSound', 'enemy_death'),
  1172. 'hitSound': enemy_data.get('hitSound', 'enemy_hit'),
  1173. 'walkSound': enemy_data.get('walkSound', ''),
  1174. 'blockSound': enemy_data.get('blockSound', ''),
  1175. 'stealthSound': enemy_data.get('stealthSound', ''),
  1176. 'armorBreakSound': enemy_data.get('armorBreakSound', ''),
  1177. 'fuseSound': enemy_data.get('fuseSound', ''),
  1178. 'volume': enemy_data.get('volume', 1.0)
  1179. }
  1180. }
  1181. # 添加特殊能力配置
  1182. if 'specialAbilities' in enemy_data:
  1183. nested_enemy_data['specialAbilities'] = enemy_data['specialAbilities']
  1184. # 添加BOSS配置
  1185. if 'bossConfig' in enemy_data:
  1186. nested_enemy_data['bossConfig'] = enemy_data['bossConfig']
  1187. enemies_config.append(nested_enemy_data)
  1188. print(f"处理敌人: {enemy_data['id']} - {enemy_data['name']}")
  1189. print(f"敌人 {enemy_data['id']} 的生命值: {nested_enemy_data['stats']['health']}")
  1190. print(f"成功解析敌人配置,共 {len(enemies_config)} 个敌人")
  1191. except Exception as e:
  1192. print(f"解析敌人多工作表数据时出错: {e}")
  1193. import traceback
  1194. traceback.print_exc()
  1195. return enemies_config
  1196. def parse_multi_sheet_data(self, all_sheets_data, filename):
  1197. """解析通用多工作表数据"""
  1198. # 处理武器配置表的多工作表
  1199. if '方块武器配置表' in filename:
  1200. return self.parse_weapon_multi_sheet_data(all_sheets_data, filename)
  1201. # 处理敌人配置表的多工作表
  1202. if '敌人配置表' in filename or '敌人' in filename:
  1203. return self.parse_enemy_multi_sheet_data(all_sheets_data, filename)
  1204. # 这里可以添加其他多工作表文件的处理逻辑
  1205. # 目前只返回第一个工作表的数据
  1206. if all_sheets_data:
  1207. first_sheet_name = list(all_sheets_data.keys())[0]
  1208. first_sheet_data = all_sheets_data[first_sheet_name]
  1209. return self.parse_config_data(first_sheet_data, filename)
  1210. return []
  1211. def parse_config_data(self, df, filename):
  1212. """解析配置数据(支持多种格式,需要pandas)"""
  1213. config = {}
  1214. if not PANDAS_AVAILABLE or not self.current_mapping:
  1215. return config
  1216. format_type = self.current_mapping['format_type']
  1217. try:
  1218. if format_type == 'vertical':
  1219. # 纵向表格:参数名在第一列,值在第二/三列
  1220. for _, row in df.iterrows():
  1221. param_name = str(row.iloc[0]).strip()
  1222. # 跳过标题行和无效行
  1223. if param_name in ['参数名', 'parameter', 'name', 'nan', '球控制器参数'] or param_name == 'nan':
  1224. continue
  1225. # 检查参数是否有效
  1226. param_types = self.current_mapping.get('param_types', {})
  1227. if param_name in param_types:
  1228. try:
  1229. # 优先使用第3列(默认值),如果不存在则使用第2列
  1230. param_value = row.iloc[2] if len(row) > 2 and not pd.isna(row.iloc[2]) else row.iloc[1]
  1231. if pd.isna(param_value):
  1232. continue
  1233. param_type = param_types[param_name]
  1234. if param_type == bool:
  1235. config[param_name] = str(param_value).lower() in ['true', '1', 'yes', 'on']
  1236. else:
  1237. config[param_name] = param_type(param_value)
  1238. except (ValueError, TypeError, IndexError):
  1239. continue
  1240. elif format_type == 'horizontal':
  1241. # 横向表格:第一行是参数名(列名),数据从第0行开始
  1242. print(f"横向表格解析: 总行数={len(df)}")
  1243. print(f"表格列名: {list(df.columns)}")
  1244. # 打印前几行数据用于调试
  1245. for i in range(min(3, len(df))):
  1246. print(f"第{i}行数据: {df.iloc[i].to_dict()}")
  1247. # 检查第0行是否为有效数据行
  1248. # 如果第0行第一个单元格是描述性文字或空值,则跳过
  1249. data_start_row = 0
  1250. if len(df) > 0:
  1251. first_cell = str(df.iloc[0, 0]).strip().lower()
  1252. # 跳过描述行、空行或标题行
  1253. if (first_cell in ['唯一标识符', '描述', 'description', 'desc', 'nan', ''] or
  1254. first_cell == df.columns[0].lower()):
  1255. data_start_row = 1
  1256. print(f"跳过第0行(描述行或标题行): {first_cell}")
  1257. print(f"数据起始行: {data_start_row}")
  1258. # 解析多行数据(如敌人配置、武器配置、关卡配置等)
  1259. config_list = []
  1260. for i in range(data_start_row, len(df)):
  1261. row_config = {}
  1262. row_has_data = False
  1263. param_types = self.current_mapping.get('param_types', {})
  1264. print(f"可用的参数类型: {list(param_types.keys())}")
  1265. print(f"当前配置映射: {self.current_mapping}")
  1266. print(f"DataFrame列名: {list(df.columns)}")
  1267. # 如果参数类型为空,尝试从DataFrame列名自动推断
  1268. if not param_types:
  1269. print("参数类型为空,尝试自动推断...")
  1270. for col_name in df.columns:
  1271. col_str = str(col_name).strip()
  1272. if col_str and col_str != 'nan':
  1273. # 根据列名推断数据类型
  1274. if any(keyword in col_str for keyword in ['ID', 'id', '编号']):
  1275. param_types[col_str] = str
  1276. elif any(keyword in col_str for keyword in ['名称', 'name', '名字']):
  1277. param_types[col_str] = str
  1278. elif any(keyword in col_str for keyword in ['伤害', 'damage', '攻击', 'attack']):
  1279. param_types[col_str] = int
  1280. elif any(keyword in col_str for keyword in ['射程', 'range', '范围']):
  1281. param_types[col_str] = int
  1282. elif any(keyword in col_str for keyword in ['射速', 'fireRate', '频率']):
  1283. param_types[col_str] = float
  1284. elif any(keyword in col_str for keyword in ['价格', 'price', '金币', 'cost']):
  1285. param_types[col_str] = int
  1286. elif any(keyword in col_str for keyword in ['等级', 'level', 'Level']):
  1287. param_types[col_str] = int
  1288. elif any(keyword in col_str for keyword in ['时间', 'time', 'Time']):
  1289. param_types[col_str] = float
  1290. elif any(keyword in col_str for keyword in ['描述', 'description', '说明']):
  1291. param_types[col_str] = str
  1292. else:
  1293. # 默认为字符串类型
  1294. param_types[col_str] = str
  1295. # 更新配置映射
  1296. if param_types:
  1297. self.current_mapping['param_types'] = param_types
  1298. print(f"自动推断的参数类型: {param_types}")
  1299. for col_idx, col_name in enumerate(df.columns):
  1300. param_name = str(col_name).strip()
  1301. print(f"检查列名: '{param_name}' 是否在参数类型中")
  1302. if param_name in param_types:
  1303. try:
  1304. param_value = df.iloc[i, col_idx]
  1305. if pd.isna(param_value) or str(param_value).strip() == '':
  1306. print(f"跳过空值: {param_name} = {param_value}")
  1307. continue
  1308. param_type = param_types[param_name]
  1309. if param_type == bool:
  1310. row_config[param_name] = str(param_value).lower() in ['true', '1', 'yes', 'on']
  1311. else:
  1312. row_config[param_name] = param_type(param_value)
  1313. row_has_data = True
  1314. print(f"成功转换字段: {param_name} = {param_value} ({param_type})")
  1315. except (ValueError, TypeError, IndexError) as e:
  1316. print(f"转换字段 {param_name} 时出错: {e}")
  1317. continue
  1318. else:
  1319. print(f"字段 '{param_name}' 不在参数类型定义中,跳过")
  1320. if row_config and row_has_data: # 只添加非空且有有效数据的配置
  1321. config_list.append(row_config)
  1322. print(f"成功解析第{i}行,包含{len(row_config)}个字段")
  1323. else:
  1324. print(f"跳过第{i}行(无有效数据)")
  1325. print(f"总共解析出{len(config_list)}个有效配置项")
  1326. # 对于横向表格,返回配置列表
  1327. config = {'items': config_list}
  1328. except Exception as e:
  1329. print(f"解析文件 {filename} 时出错: {e}")
  1330. return config
  1331. def parse_weapon_multi_sheet_data(self, all_sheets_data, filename):
  1332. """解析武器配置表的多工作表数据"""
  1333. weapons_config = {'weapons': []}
  1334. try:
  1335. # 解析武器基础配置工作表(支持中英文工作表名)
  1336. base_sheet = None
  1337. for sheet_name in ['武器基础配置', 'Weapon Config', 'weapons', '武器配置']:
  1338. if sheet_name in all_sheets_data:
  1339. base_sheet = all_sheets_data[sheet_name]
  1340. break
  1341. if base_sheet is not None:
  1342. # 设置武器配置的映射
  1343. old_mapping = self.current_mapping
  1344. self.current_mapping = {
  1345. 'format_type': 'horizontal',
  1346. 'param_types': {
  1347. 'ID': str,
  1348. '名称': str,
  1349. '类型': str,
  1350. '稀有度': str,
  1351. '权重': int,
  1352. '伤害': int,
  1353. '射速': float,
  1354. '射程': int,
  1355. '子弹速度': int,
  1356. # 方块价格配置字段
  1357. '基础每格成本': int,
  1358. 'I形状成本': int,
  1359. 'H-I形状成本': int,
  1360. 'L形状成本': int,
  1361. 'S形状成本': int,
  1362. 'D-T形状成本': int,
  1363. # 英文字段支持
  1364. 'id': str,
  1365. 'name': str,
  1366. 'type': str,
  1367. 'rarity': str,
  1368. 'weight': int,
  1369. 'damage': int,
  1370. 'fireRate': float,
  1371. 'range': int,
  1372. 'bulletSpeed': int,
  1373. 'baseCost': int,
  1374. 'I_shape_cost': int,
  1375. 'HI_shape_cost': int,
  1376. 'L_shape_cost': int,
  1377. 'S_shape_cost': int,
  1378. 'DT_shape_cost': int
  1379. }
  1380. }
  1381. base_config = self.parse_config_data(base_sheet, filename)
  1382. if 'items' in base_config:
  1383. weapons_config['weapons'] = base_config['items']
  1384. print(f"成功解析武器基础配置,共{len(base_config['items'])}个武器")
  1385. # 恢复原来的映射
  1386. self.current_mapping = old_mapping
  1387. # 解析武器升级费用配置工作表(新增支持)
  1388. upgrade_cost_sheet = None
  1389. print(f"可用的工作表: {list(all_sheets_data.keys())}")
  1390. for sheet_name in ['武器升级费用配置', 'Weapon Upgrade Cost', 'upgrade_costs', '升级费用']:
  1391. if sheet_name in all_sheets_data:
  1392. upgrade_cost_sheet = all_sheets_data[sheet_name]
  1393. print(f"找到升级费用配置工作表: {sheet_name}")
  1394. break
  1395. if upgrade_cost_sheet is None:
  1396. print("未找到升级费用配置工作表")
  1397. if upgrade_cost_sheet is not None:
  1398. # 直接处理升级费用数据(横向表格格式)
  1399. print(f"开始处理升级费用配置,工作表行数: {len(upgrade_cost_sheet)}")
  1400. upgrade_cost_data = []
  1401. # 检查第一行是否为表头
  1402. first_row = upgrade_cost_sheet.iloc[0] if len(upgrade_cost_sheet) > 0 else None
  1403. is_header = False
  1404. if first_row is not None:
  1405. first_cell = str(first_row.iloc[0]).strip() if len(first_row) > 0 else ""
  1406. # 如果第一列是"武器ID"等表头文字,则认为是表头
  1407. if first_cell in ['武器ID', 'ID', 'weapon_id', 'weaponId']:
  1408. is_header = True
  1409. print(f"检测到表头行,第一列内容: {first_cell}")
  1410. for index, row in upgrade_cost_sheet.iterrows():
  1411. if is_header and index == 0: # 只有确认是表头时才跳过第一行
  1412. print(f"跳过表头行 {index}")
  1413. continue
  1414. # 支持多种武器ID字段名:武器ID、ID、weapon_id等
  1415. weapon_id = None
  1416. for id_field in ['武器ID', 'ID', 'weapon_id', 'weaponId']:
  1417. if id_field in row and pd.notna(row[id_field]):
  1418. weapon_id = row[id_field]
  1419. break
  1420. # 如果没有找到命名字段,则使用第一列
  1421. if weapon_id is None:
  1422. weapon_id = row.iloc[0] if len(row) > 0 else None
  1423. print(f"处理行 {index}: weapon_id = {weapon_id}")
  1424. if weapon_id and str(weapon_id).strip(): # 确保武器ID不为空
  1425. upgrade_levels = {}
  1426. # 从第5列开始是等级1-10的费用(E列到N列),从第15列开始是等级1-10的伤害(O列到X列)
  1427. for level in range(1, 11):
  1428. cost_col_index = 4 + (level - 1) # 等级1费用在第5列(索引4),等级2在第6列(索引5),以此类推
  1429. damage_col_index = 14 + (level - 1) # 等级1伤害在第15列(索引14),等级2在第16列(索引15),以此类推
  1430. level_config = {}
  1431. # 处理费用
  1432. if cost_col_index < len(row):
  1433. cost = row.iloc[cost_col_index]
  1434. if cost and str(cost).strip() and str(cost) != 'nan':
  1435. try:
  1436. level_config['cost'] = int(float(cost))
  1437. print(f" 等级 {level} 费用: {cost}")
  1438. except (ValueError, TypeError):
  1439. print(f" 等级 {level} 费用解析失败: {cost}")
  1440. # 处理伤害
  1441. if damage_col_index < len(row):
  1442. damage = row.iloc[damage_col_index]
  1443. if damage and str(damage).strip() and str(damage) != 'nan':
  1444. try:
  1445. level_config['damage'] = int(float(damage))
  1446. print(f" 等级 {level} 伤害: {damage}")
  1447. except (ValueError, TypeError):
  1448. print(f" 等级 {level} 伤害解析失败: {damage}")
  1449. # 只有当有费用或伤害数据时才添加等级配置
  1450. if level_config:
  1451. upgrade_levels[str(level)] = level_config
  1452. if upgrade_levels: # 只有当有升级费用数据时才添加
  1453. upgrade_cost_data.append({
  1454. 'weapon_id': str(weapon_id).strip(),
  1455. 'levels': upgrade_levels
  1456. })
  1457. print(f"为武器 {weapon_id} 添加了 {len(upgrade_levels)} 个等级的升级费用")
  1458. else:
  1459. print(f"武器 {weapon_id} 没有有效的升级费用数据")
  1460. # 将升级费用配置合并到原始武器数据中(在转换之前)
  1461. print(f"总共解析到 {len(upgrade_cost_data)} 个武器的升级费用配置")
  1462. # 检查数据结构,支持两种格式:items 或 weapons
  1463. weapon_list = None
  1464. if 'items' in weapons_config:
  1465. weapon_list = weapons_config['items']
  1466. print(f"使用items字段,开始合并升级费用配置到 {len(weapon_list)} 个武器")
  1467. elif 'weapons' in weapons_config:
  1468. weapon_list = weapons_config['weapons']
  1469. print(f"使用weapons字段,开始合并升级费用配置到 {len(weapon_list)} 个武器")
  1470. else:
  1471. print("weapons_config中没有items或weapons字段")
  1472. if weapon_list:
  1473. for weapon in weapon_list:
  1474. # 支持两种字段名格式:ID(原始数据)和id(转换后数据)
  1475. weapon_id = weapon.get('ID', '') or weapon.get('id', '')
  1476. if weapon_id:
  1477. # 查找对应的升级费用配置
  1478. matching_upgrade = None
  1479. for upgrade_data in upgrade_cost_data:
  1480. if upgrade_data['weapon_id'] == weapon_id:
  1481. matching_upgrade = upgrade_data
  1482. break
  1483. if matching_upgrade:
  1484. weapon['upgradeConfig'] = {
  1485. 'maxLevel': 10,
  1486. 'levels': matching_upgrade['levels']
  1487. }
  1488. print(f"✓ 为武器 {weapon_id} 添加了升级费用配置")
  1489. else:
  1490. print(f"✗ 武器 {weapon_id} 未找到匹配的升级费用配置")
  1491. # 解析旧版升级配置工作表(向后兼容)
  1492. upgrade_sheet = None
  1493. for sheet_name in ['升级配置', 'Upgrade Config', 'upgrades', '武器升级']:
  1494. if sheet_name in all_sheets_data:
  1495. upgrade_sheet = all_sheets_data[sheet_name]
  1496. break
  1497. if upgrade_sheet is not None:
  1498. upgrade_config = self.parse_config_data(upgrade_sheet, filename)
  1499. # 将升级配置合并到对应的武器中
  1500. if 'items' in upgrade_config:
  1501. upgrade_items = upgrade_config['items']
  1502. # 为每个武器添加升级配置
  1503. for weapon in weapons_config['weapons']:
  1504. weapon_id = weapon.get('ID')
  1505. if weapon_id and 'upgradeConfig' not in weapon: # 如果还没有升级配置
  1506. # 查找对应的升级配置
  1507. weapon_upgrades = [item for item in upgrade_items if item.get('ID') == weapon_id]
  1508. if weapon_upgrades:
  1509. # 构建升级配置结构
  1510. upgrade_config_struct = {
  1511. 'maxLevel': weapon.get('最大等级', 10),
  1512. 'levels': {}
  1513. }
  1514. # 添加每个等级的升级配置
  1515. for upgrade in weapon_upgrades:
  1516. level = upgrade.get('等级', 1)
  1517. if level:
  1518. upgrade_config_struct['levels'][str(level)] = {
  1519. 'cost': upgrade.get('升级费用', 25),
  1520. 'damageIncrease': upgrade.get('伤害增加', 1)
  1521. }
  1522. weapon['upgradeConfig'] = upgrade_config_struct
  1523. # 解析游戏内成本配置工作表(新增支持)
  1524. cost_config_sheet = None
  1525. print(f"查找游戏内成本配置工作表...")
  1526. for sheet_name in ['游戏内成本配置', 'In Game Cost Config', 'cost_config', '成本配置']:
  1527. if sheet_name in all_sheets_data:
  1528. cost_config_sheet = all_sheets_data[sheet_name]
  1529. print(f"找到游戏内成本配置工作表: {sheet_name}")
  1530. break
  1531. if cost_config_sheet is None:
  1532. print("未找到游戏内成本配置工作表")
  1533. if cost_config_sheet is not None:
  1534. # 直接处理游戏内成本配置数据(横向表格格式)
  1535. print(f"开始处理游戏内成本配置,工作表行数: {len(cost_config_sheet)}")
  1536. cost_config_data = []
  1537. # 检查第一行是否为表头
  1538. first_row = cost_config_sheet.iloc[0] if len(cost_config_sheet) > 0 else None
  1539. is_header = False
  1540. if first_row is not None:
  1541. first_cell = str(first_row.iloc[0]).strip() if len(first_row) > 0 else ""
  1542. # 如果第一列是"武器ID"等表头文字,则认为是表头
  1543. if first_cell in ['武器ID', 'ID', 'weapon_id', 'weaponId']:
  1544. is_header = True
  1545. print(f"检测到表头行,第一列内容: {first_cell}")
  1546. for index, row in cost_config_sheet.iterrows():
  1547. if is_header and index == 0: # 只有确认是表头时才跳过第一行
  1548. print(f"跳过表头行 {index}")
  1549. continue
  1550. # 支持多种武器ID字段名:武器ID、ID、weapon_id等
  1551. weapon_id = None
  1552. for id_field in ['武器ID', 'ID', 'weapon_id', 'weaponId']:
  1553. if id_field in row and pd.notna(row[id_field]):
  1554. weapon_id = row[id_field]
  1555. break
  1556. # 如果没有找到命名字段,则使用第一列
  1557. if weapon_id is None:
  1558. weapon_id = row.iloc[0] if len(row) > 0 else None
  1559. print(f"处理行 {index}: weapon_id = {weapon_id}")
  1560. if weapon_id and str(weapon_id).strip(): # 确保武器ID不为空
  1561. cost_config = {}
  1562. # 读取基础每格成本
  1563. base_cost_fields = ['基础每格成本', 'baseCost', '基础成本', '武器基础售价']
  1564. for field in base_cost_fields:
  1565. if field in row and pd.notna(row[field]):
  1566. try:
  1567. cost_config['baseCost'] = int(float(row[field]))
  1568. print(f" 基础每格成本: {cost_config['baseCost']}")
  1569. break
  1570. except (ValueError, TypeError):
  1571. continue
  1572. # 读取各形状成本
  1573. shape_costs = {}
  1574. shape_fields = {
  1575. 'I': ['I形状', 'I形状成本', 'I_shape_cost', 'I'],
  1576. 'H-I': ['H-I形状', 'H-I形状成本', 'HI_shape_cost', 'H-I'],
  1577. 'L': ['L形状', 'L形状成本', 'L_shape_cost', 'L'],
  1578. 'S': ['S形状', 'S形状成本', 'S_shape_cost', 'S'],
  1579. 'D-T': ['D-T形状', 'D-T形状成本', 'DT_shape_cost', 'D-T']
  1580. }
  1581. for shape, field_names in shape_fields.items():
  1582. for field_name in field_names:
  1583. if field_name in row and pd.notna(row[field_name]):
  1584. try:
  1585. shape_costs[shape] = int(float(row[field_name]))
  1586. print(f" {shape}形状成本: {shape_costs[shape]}")
  1587. break
  1588. except (ValueError, TypeError):
  1589. continue
  1590. if shape_costs:
  1591. cost_config['shapeCosts'] = shape_costs
  1592. if cost_config: # 只有当有成本配置数据时才添加
  1593. cost_config_data.append({
  1594. 'weapon_id': str(weapon_id).strip(),
  1595. 'costConfig': cost_config
  1596. })
  1597. print(f"为武器 {weapon_id} 添加了游戏内成本配置")
  1598. else:
  1599. print(f"武器 {weapon_id} 没有有效的游戏内成本配置数据")
  1600. # 将游戏内成本配置合并到原始武器数据中
  1601. print(f"总共解析到 {len(cost_config_data)} 个武器的游戏内成本配置")
  1602. weapon_list = None
  1603. if 'items' in weapons_config:
  1604. weapon_list = weapons_config['items']
  1605. print(f"使用items字段,开始合并游戏内成本配置到 {len(weapon_list)} 个武器")
  1606. elif 'weapons' in weapons_config:
  1607. weapon_list = weapons_config['weapons']
  1608. print(f"使用weapons字段,开始合并游戏内成本配置到 {len(weapon_list)} 个武器")
  1609. else:
  1610. print("weapons_config中没有items或weapons字段")
  1611. if weapon_list:
  1612. for weapon in weapon_list:
  1613. # 支持两种字段名格式:ID(原始数据)和id(转换后数据)
  1614. weapon_id = weapon.get('ID', '') or weapon.get('id', '')
  1615. if weapon_id:
  1616. # 查找对应的游戏内成本配置
  1617. matching_cost = None
  1618. for cost_data in cost_config_data:
  1619. if cost_data['weapon_id'] == weapon_id:
  1620. matching_cost = cost_data
  1621. break
  1622. if matching_cost:
  1623. # 直接将成本配置字段添加到武器数据中
  1624. weapon.update(matching_cost['costConfig'])
  1625. print(f"✓ 为武器 {weapon_id} 添加了游戏内成本配置: {matching_cost['costConfig']}")
  1626. else:
  1627. print(f"✗ 武器 {weapon_id} 未找到匹配的游戏内成本配置")
  1628. print(f"武器配置解析完成,共{len(weapons_config['weapons'])}个武器")
  1629. return weapons_config
  1630. except Exception as e:
  1631. print(f"解析武器配置表时出错: {e}")
  1632. import traceback
  1633. traceback.print_exc()
  1634. return {'weapons': []}
  1635. def clear_selection(self):
  1636. """清空选择"""
  1637. self.file_listbox.selection_clear(0, tk.END)
  1638. self.preview_text.delete(1.0, tk.END)
  1639. self.config_data = {}
  1640. self.status_var.set("已清空选择")
  1641. self.load_current_config()
  1642. def load_current_config(self):
  1643. """加载当前配置"""
  1644. try:
  1645. if not self.selected_json_path:
  1646. self.preview_text.insert(tk.END, "请先选择JSON输出路径\n")
  1647. return
  1648. json_path = Path(self.selected_json_path)
  1649. if json_path.exists():
  1650. with open(json_path, 'r', encoding='utf-8') as f:
  1651. current_config = json.load(f)
  1652. self.preview_text.insert(tk.END, f"=== 当前配置 ({json_path.name}) ===\n")
  1653. # 根据配置类型显示不同的预览格式
  1654. if self.format_var.get() == 'horizontal':
  1655. # 横向表格配置(如敌人、武器)
  1656. if 'enemies' in current_config:
  1657. self.preview_text.insert(tk.END, f"敌人配置 ({len(current_config['enemies'])} 个):\n")
  1658. for i, enemy in enumerate(current_config['enemies'][:5]): # 只显示前5个
  1659. self.preview_text.insert(tk.END, f" {i+1}. {enemy.get('name', enemy.get('id', 'Unknown'))}\n")
  1660. if len(current_config['enemies']) > 5:
  1661. self.preview_text.insert(tk.END, f" ... 还有 {len(current_config['enemies']) - 5} 个\n")
  1662. if 'weapons' in current_config:
  1663. self.preview_text.insert(tk.END, f"\n武器配置 ({len(current_config['weapons'])} 个):\n")
  1664. for i, weapon in enumerate(current_config['weapons'][:5]): # 只显示前5个
  1665. self.preview_text.insert(tk.END, f" {i+1}. {weapon.get('name', weapon.get('id', 'Unknown'))}\n")
  1666. if len(current_config['weapons']) > 5:
  1667. self.preview_text.insert(tk.END, f" ... 还有 {len(current_config['weapons']) - 5} 个\n")
  1668. else:
  1669. # 纵向表格配置(如BallController)
  1670. self.preview_text.insert(tk.END, json.dumps(current_config, indent=2, ensure_ascii=False))
  1671. file_hint = "Excel/CSV文件" if PANDAS_AVAILABLE else "CSV文件"
  1672. self.preview_text.insert(tk.END, f"\n\n请选择{file_hint}进行配置导入...\n")
  1673. else:
  1674. self.preview_text.insert(tk.END, "配置文件不存在\n")
  1675. except Exception as e:
  1676. self.preview_text.insert(tk.END, f"加载当前配置失败: {e}\n")
  1677. def backup_config(self):
  1678. """备份当前配置"""
  1679. try:
  1680. if not self.selected_json_path:
  1681. messagebox.showwarning("警告", "请先选择JSON输出路径")
  1682. return
  1683. json_path = Path(self.selected_json_path)
  1684. if not json_path.exists():
  1685. messagebox.showwarning("警告", "配置文件不存在")
  1686. return
  1687. backup_path = json_path.parent / f"ballController_backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
  1688. with open(json_path, 'r', encoding='utf-8') as f:
  1689. content = f.read()
  1690. with open(backup_path, 'w', encoding='utf-8') as f:
  1691. f.write(content)
  1692. messagebox.showinfo("成功", f"配置已备份到:\n{backup_path}")
  1693. self.status_var.set(f"配置已备份")
  1694. except Exception as e:
  1695. messagebox.showerror("错误", f"备份失败: {e}")
  1696. def import_config(self):
  1697. """导入配置到JSON文件"""
  1698. if not self.config_data:
  1699. messagebox.showwarning("警告", "没有配置数据可导入")
  1700. return
  1701. if not self.selected_json_path:
  1702. messagebox.showwarning("警告", "请先选择JSON输出路径")
  1703. return
  1704. try:
  1705. print(f"开始导入配置...")
  1706. print(f"配置数据: {self.config_data}")
  1707. print(f"输出路径: {self.selected_json_path}")
  1708. print(f"选中文件: {self.selected_files}")
  1709. # 优先使用配置映射中的格式类型,如果没有则使用GUI设置
  1710. format_type = self.current_mapping.get('format_type', self.format_var.get()) if hasattr(self, 'current_mapping') and self.current_mapping else self.format_var.get()
  1711. print(f"格式类型: {format_type}")
  1712. print(f"配置映射格式: {self.current_mapping.get('format_type') if hasattr(self, 'current_mapping') and self.current_mapping else 'None'}")
  1713. print(f"GUI格式设置: {self.format_var.get()}")
  1714. if format_type == 'vertical':
  1715. # 处理纵向表格(如BallController)
  1716. print("开始处理纵向表格配置...")
  1717. self._import_vertical_config()
  1718. elif format_type == 'horizontal':
  1719. # 处理横向表格(如武器配置)
  1720. print("开始处理横向表格配置...")
  1721. self._import_horizontal_config()
  1722. elif format_type == 'multi_sheet':
  1723. # 处理多工作表格式(如敌人配置)
  1724. print("开始处理多工作表配置...")
  1725. self._import_multi_sheet_config() # 使用专门的多工作表处理逻辑
  1726. else:
  1727. raise ValueError(f"未知的格式类型: {format_type}")
  1728. except Exception as e:
  1729. import traceback
  1730. error_details = traceback.format_exc()
  1731. print(f"导入配置失败,详细错误信息:")
  1732. print(error_details)
  1733. messagebox.showerror("错误", f"导入配置失败: {str(e)}\n\n详细错误信息已输出到控制台,请查看。")
  1734. def import_skill_config(self):
  1735. """导入技能配置到skill_config.json"""
  1736. try:
  1737. # 查找局外技能配置表.xlsx文件
  1738. excel_dir = Path(self.excel_dir)
  1739. skill_excel_file = excel_dir / "局外技能配置表.xlsx"
  1740. if not skill_excel_file.exists():
  1741. messagebox.showwarning("警告", f"未找到技能配置文件: {skill_excel_file}")
  1742. return
  1743. # 使用SkillConfigImporter导入配置
  1744. importer = SkillConfigImporter(str(excel_dir))
  1745. success, message = importer.import_config()
  1746. if success:
  1747. messagebox.showinfo("成功", message)
  1748. self.status_var.set("技能配置导入成功")
  1749. # 更新预览文本
  1750. self.preview_text.delete(1.0, tk.END)
  1751. self.preview_text.insert(tk.END, f"技能配置导入成功\n\n{message}")
  1752. else:
  1753. messagebox.showerror("错误", f"技能配置导入失败: {message}")
  1754. self.status_var.set("技能配置导入失败")
  1755. except Exception as e:
  1756. import traceback
  1757. error_details = traceback.format_exc()
  1758. print(f"导入技能配置失败,详细错误信息:")
  1759. print(error_details)
  1760. messagebox.showerror("错误", f"导入技能配置失败: {str(e)}\n\n详细错误信息已输出到控制台,请查看。")
  1761. def _import_vertical_config(self):
  1762. """导入纵向表格配置"""
  1763. # 读取现有JSON配置
  1764. if self.selected_json_path.exists():
  1765. with open(self.selected_json_path, 'r', encoding='utf-8') as f:
  1766. current_config = json.load(f)
  1767. else:
  1768. current_config = self.default_config.copy()
  1769. # 合并配置
  1770. updated_count = 0
  1771. for key, value in self.config_data.items():
  1772. if key in current_config and current_config[key] != value:
  1773. current_config[key] = value
  1774. updated_count += 1
  1775. elif key not in current_config:
  1776. current_config[key] = value
  1777. updated_count += 1
  1778. # 写入更新后的配置
  1779. with open(self.selected_json_path, 'w', encoding='utf-8') as f:
  1780. json.dump(current_config, f, indent=2, ensure_ascii=False)
  1781. messagebox.showinfo("成功", f"配置导入成功!\n更新了 {updated_count} 个参数")
  1782. self.status_var.set("配置导入成功")
  1783. # 刷新预览
  1784. self.clear_selection()
  1785. def _import_horizontal_config(self):
  1786. """导入横向表格配置"""
  1787. print(f"横向表格配置导入开始...")
  1788. print(f"配置数据结构: {list(self.config_data.keys()) if self.config_data else 'None'}")
  1789. # 检查是否是关卡配置(目录类型)
  1790. is_level_config = str(self.selected_json_path).endswith('levels') or self.selected_json_path.is_dir()
  1791. # 对于关卡配置,支持多种数据格式
  1792. if is_level_config:
  1793. if isinstance(self.config_data, list):
  1794. # 多工作表数据格式:直接是关卡数据列表
  1795. print("检测到关卡配置的多工作表数据格式")
  1796. items = None # 不需要items字段
  1797. elif 'items' in self.config_data:
  1798. # 传统的items数据格式
  1799. print("检测到关卡配置的传统items数据格式")
  1800. items = self.config_data['items']
  1801. elif isinstance(self.config_data, dict) and len(self.config_data) == 1:
  1802. # 检查是否是包装在单个键中的配置数据
  1803. key = list(self.config_data.keys())[0]
  1804. wrapped_data = self.config_data[key]
  1805. print(f"检测到包装的关卡配置数据,键名: {key}")
  1806. if isinstance(wrapped_data, list):
  1807. print("提取到关卡配置的多工作表数据格式")
  1808. self.config_data = wrapped_data # 更新配置数据为实际内容
  1809. items = None
  1810. elif isinstance(wrapped_data, dict) and 'items' in wrapped_data:
  1811. print("提取到关卡配置的传统items数据格式")
  1812. self.config_data = wrapped_data
  1813. items = wrapped_data['items']
  1814. else:
  1815. print(f"错误: 包装数据格式不正确: {type(wrapped_data)}")
  1816. error_msg = f"关卡配置数据格式错误:包装数据不是列表或包含items的字典\n\n包装键: {key}\n数据类型: {type(wrapped_data)}"
  1817. messagebox.showwarning("警告", error_msg)
  1818. self.preview_text.insert(tk.END, f"\n=== 错误信息 ===\n")
  1819. self.preview_text.insert(tk.END, f"关卡配置数据格式错误:包装数据不是列表或包含items的字典\n")
  1820. self.preview_text.insert(tk.END, f"包装键: {key}\n数据类型: {type(wrapped_data)}\n")
  1821. return
  1822. else:
  1823. print(f"错误: 关卡配置数据格式不正确")
  1824. print(f"实际配置数据: {self.config_data}")
  1825. print(f"配置数据类型: {type(self.config_data)}")
  1826. print(f"配置数据键: {list(self.config_data.keys()) if isinstance(self.config_data, dict) else 'Not a dict'}")
  1827. error_msg = f"关卡配置数据格式错误:需要多工作表数据或items字段\n\n数据类型: {type(self.config_data)}\n数据内容: {str(self.config_data)[:200]}..."
  1828. messagebox.showwarning("警告", error_msg)
  1829. # 同时在GUI中显示错误信息
  1830. self.preview_text.insert(tk.END, f"\n=== 错误信息 ===\n")
  1831. self.preview_text.insert(tk.END, f"关卡配置数据格式错误:需要多工作表数据或items字段\n")
  1832. self.preview_text.insert(tk.END, f"数据类型: {type(self.config_data)}\n")
  1833. self.preview_text.insert(tk.END, f"数据内容: {str(self.config_data)[:500]}...\n")
  1834. return
  1835. else:
  1836. # 其他配置类型:检查数据格式
  1837. if isinstance(self.config_data, dict):
  1838. # 检查是否是多工作表数据格式(如:{'技能配置表': [...]})
  1839. if len(self.config_data) == 1:
  1840. key = list(self.config_data.keys())[0]
  1841. data_content = self.config_data[key]
  1842. if isinstance(data_content, list):
  1843. print(f"检测到多工作表数据格式,键名: {key}")
  1844. items = data_content
  1845. elif isinstance(data_content, dict) and 'items' in data_content:
  1846. print(f"检测到包装的items数据格式,键名: {key}")
  1847. items = data_content['items']
  1848. else:
  1849. print(f"错误: 无法识别的数据格式")
  1850. error_msg = f"配置数据格式错误:无法识别的数据结构\n\n键名: {key}\n数据类型: {type(data_content)}"
  1851. messagebox.showwarning("警告", error_msg)
  1852. return
  1853. elif 'items' in self.config_data:
  1854. print("检测到传统items数据格式")
  1855. items = self.config_data['items']
  1856. else:
  1857. print(f"错误: 配置数据格式不正确")
  1858. print(f"实际配置数据: {self.config_data}")
  1859. print(f"配置数据类型: {type(self.config_data)}")
  1860. print(f"配置数据键: {list(self.config_data.keys())}")
  1861. error_msg = f"配置数据格式错误:需要items字段或多工作表数据格式\n\n数据类型: {type(self.config_data)}\n可用键: {list(self.config_data.keys())}"
  1862. messagebox.showwarning("警告", error_msg)
  1863. # 同时在GUI中显示错误信息
  1864. self.preview_text.insert(tk.END, f"\n=== 错误信息 ===\n")
  1865. self.preview_text.insert(tk.END, f"配置数据格式错误:需要items字段或多工作表数据格式\n")
  1866. self.preview_text.insert(tk.END, f"数据类型: {type(self.config_data)}\n")
  1867. self.preview_text.insert(tk.END, f"可用键: {list(self.config_data.keys())}\n")
  1868. return
  1869. elif isinstance(self.config_data, list):
  1870. print("检测到直接列表数据格式")
  1871. items = self.config_data
  1872. else:
  1873. print(f"错误: 不支持的数据类型: {type(self.config_data)}")
  1874. error_msg = f"不支持的数据类型: {type(self.config_data)}"
  1875. messagebox.showwarning("警告", error_msg)
  1876. return
  1877. # 验证数据有效性
  1878. if items is not None:
  1879. print(f"配置项数量: {len(items) if items else 0}")
  1880. if not items:
  1881. messagebox.showwarning("警告", "没有有效的配置项")
  1882. return
  1883. elif isinstance(self.config_data, list):
  1884. print(f"关卡配置数量: {len(self.config_data)}")
  1885. if not self.config_data:
  1886. messagebox.showwarning("警告", "没有有效的关卡配置")
  1887. return
  1888. # 打印前几个配置项用于调试
  1889. if items is not None:
  1890. for i, item in enumerate(items[:3]):
  1891. print(f"配置项{i}: {item}")
  1892. elif isinstance(self.config_data, list):
  1893. for i, item in enumerate(self.config_data[:3]):
  1894. print(f"关卡配置{i}: {item.get('levelId', 'Unknown')} - {item.get('name', 'Unknown')}")
  1895. # 读取现有JSON配置
  1896. print(f"JSON配置文件路径: {self.selected_json_path}")
  1897. # 检查是否是关卡配置(目录类型)
  1898. if is_level_config:
  1899. print("处理关卡配置目录...")
  1900. # 关卡配置是目录,确保目录存在
  1901. if not self.selected_json_path.exists():
  1902. print(f"创建关卡配置目录: {self.selected_json_path}")
  1903. self.selected_json_path.mkdir(parents=True, exist_ok=True)
  1904. current_config = {}
  1905. else:
  1906. # 普通JSON文件配置
  1907. if self.selected_json_path.exists():
  1908. print("读取现有JSON配置文件...")
  1909. with open(self.selected_json_path, 'r', encoding='utf-8') as f:
  1910. current_config = json.load(f)
  1911. else:
  1912. print("创建新的JSON配置...")
  1913. current_config = {}
  1914. # 根据用户选择的处理模式和数据类型处理
  1915. if not self.selected_files:
  1916. print("错误: 没有选中的文件")
  1917. messagebox.showerror("错误", "没有选中的文件")
  1918. return
  1919. filename = Path(self.selected_files[0]).name
  1920. print(f"处理文件: {filename}")
  1921. # 获取用户选择的处理模式
  1922. is_multi_sheet = self.multi_sheet_var.get()
  1923. is_single_json = self.single_json_var.get()
  1924. print(f"处理模式 - 多工作表: {is_multi_sheet}, 单个JSON: {is_single_json}")
  1925. # 根据数据类型和用户选择的模式进行处理
  1926. if is_level_config:
  1927. # 关卡配置:始终为目录模式,每个关卡一个JSON文件
  1928. print("处理关卡配置...")
  1929. # 关卡配置:为每个关卡创建单独的JSON文件
  1930. levels_dir = self.selected_json_path
  1931. print(f"关卡配置目录: {levels_dir}")
  1932. try:
  1933. # 检查并创建目录
  1934. if not levels_dir.exists():
  1935. print(f"创建关卡配置目录: {levels_dir}")
  1936. levels_dir.mkdir(parents=True, exist_ok=True)
  1937. else:
  1938. print(f"关卡配置目录已存在: {levels_dir}")
  1939. # 测试目录写入权限
  1940. test_file = levels_dir / "test_permission.tmp"
  1941. try:
  1942. with open(test_file, 'w', encoding='utf-8') as f:
  1943. f.write("test")
  1944. test_file.unlink() # 删除测试文件
  1945. except PermissionError:
  1946. messagebox.showerror("错误", f"没有写入权限到目录: {levels_dir}\n请检查目录权限或以管理员身份运行")
  1947. return
  1948. # 检查数据格式:多工作表数据 vs 传统items数据
  1949. if isinstance(self.config_data, list):
  1950. # 新的多工作表数据格式:直接是关卡数据列表
  1951. print("使用多工作表数据格式")
  1952. level_configs = self.config_data
  1953. elif 'items' in self.config_data:
  1954. # 传统的items数据格式:需要转换
  1955. print("使用传统items数据格式")
  1956. level_configs = []
  1957. for item in items:
  1958. level_data = self._convert_level_data(item)
  1959. if level_data:
  1960. level_configs.append(level_data)
  1961. else:
  1962. print(f"错误: 未知的关卡配置数据格式: {type(self.config_data)}")
  1963. messagebox.showerror("错误", "关卡配置数据格式错误")
  1964. return
  1965. # 保存关卡配置文件
  1966. updated_count = 0
  1967. failed_count = 0
  1968. for level_data in level_configs:
  1969. try:
  1970. if level_data and 'levelId' in level_data and level_data['levelId']:
  1971. level_id = level_data['levelId']
  1972. level_file = levels_dir / f"{level_id}.json"
  1973. print(f"保存关卡配置: {level_file}")
  1974. with open(level_file, 'w', encoding='utf-8') as f:
  1975. json.dump(level_data, f, indent=2, ensure_ascii=False)
  1976. updated_count += 1
  1977. else:
  1978. print(f"跳过无效的关卡数据: {level_data}")
  1979. failed_count += 1
  1980. except Exception as e:
  1981. print(f"保存关卡配置时出错: {e}")
  1982. failed_count += 1
  1983. continue
  1984. if updated_count > 0:
  1985. message = f"关卡配置导入成功!\n更新了 {updated_count} 个关卡文件"
  1986. if failed_count > 0:
  1987. message += f"\n跳过了 {failed_count} 个无效配置"
  1988. messagebox.showinfo("成功", message)
  1989. self.status_var.set("关卡配置导入成功")
  1990. else:
  1991. messagebox.showerror("错误", "没有成功导入任何关卡配置\n请检查Excel文件格式和数据")
  1992. self.status_var.set("关卡配置导入失败")
  1993. except Exception as e:
  1994. error_msg = f"关卡配置导入失败: {str(e)}"
  1995. print(error_msg)
  1996. messagebox.showerror("错误", error_msg)
  1997. self.status_var.set("关卡配置导入失败")
  1998. self.clear_selection()
  1999. return
  2000. else:
  2001. # 非关卡配置:根据用户选择的模式和数据内容进行处理
  2002. print("处理非关卡配置...")
  2003. # 根据数据类型自动识别配置类型
  2004. config_type = self._detect_config_type()
  2005. print(f"检测到配置类型: {config_type}")
  2006. if config_type == 'unknown':
  2007. print(f"警告: 无法识别的配置类型")
  2008. messagebox.showwarning("警告", f"无法识别配置类型\n请检查Excel文件的工作表名称或数据格式")
  2009. return
  2010. # 根据配置类型处理数据
  2011. if config_type == 'enemy':
  2012. print("处理敌人配置...")
  2013. if 'enemies' not in current_config:
  2014. current_config['enemies'] = []
  2015. updated_enemies = []
  2016. for i, item in enumerate(items):
  2017. print(f"转换敌人数据 {i+1}/{len(items)}: {item}")
  2018. enemy_data = self._convert_enemy_data(item)
  2019. if enemy_data:
  2020. updated_enemies.append(enemy_data)
  2021. print(f"成功转换敌人数据: {enemy_data['id']}")
  2022. else:
  2023. print(f"跳过无效的敌人数据: {item}")
  2024. print(f"总共转换了 {len(updated_enemies)} 个敌人配置")
  2025. current_config['enemies'] = updated_enemies
  2026. elif config_type == 'weapon':
  2027. print("处理武器配置...")
  2028. if 'weapons' not in current_config:
  2029. current_config['weapons'] = []
  2030. updated_weapons = []
  2031. for i, item in enumerate(items):
  2032. print(f"转换武器数据 {i+1}/{len(items)}: {item}")
  2033. weapon_data = self._convert_weapon_data(item)
  2034. if weapon_data:
  2035. updated_weapons.append(weapon_data)
  2036. print(f"成功转换武器数据: {weapon_data.get('id', 'Unknown')}")
  2037. else:
  2038. print(f"跳过无效的武器数据: {item}")
  2039. print(f"总共转换了 {len(updated_weapons)} 个武器配置")
  2040. current_config['weapons'] = updated_weapons
  2041. elif config_type == 'skill':
  2042. print("处理技能配置...")
  2043. if 'skills' not in current_config:
  2044. current_config['skills'] = []
  2045. updated_skills = []
  2046. for i, item in enumerate(items):
  2047. print(f"转换技能数据 {i+1}/{len(items)}: {item}")
  2048. skill_data = self._convert_skill_data(item)
  2049. if skill_data:
  2050. updated_skills.append(skill_data)
  2051. print(f"成功转换技能数据: {skill_data.get('id', 'Unknown')}")
  2052. else:
  2053. print(f"跳过无效的技能数据: {item}")
  2054. print(f"总共转换了 {len(updated_skills)} 个技能配置")
  2055. current_config['skills'] = updated_skills
  2056. else:
  2057. # 通用配置处理
  2058. print(f"处理通用配置类型: {config_type}")
  2059. # 直接使用items数据
  2060. current_config['items'] = items
  2061. # 写入更新后的配置(关卡配置已在前面处理,跳过)
  2062. if not is_level_config:
  2063. try:
  2064. print(f"写入配置文件: {self.selected_json_path}")
  2065. print(f"配置内容预览: {str(current_config)[:200]}...")
  2066. with open(self.selected_json_path, 'w', encoding='utf-8') as f:
  2067. json.dump(current_config, f, indent=2, ensure_ascii=False)
  2068. print("配置文件写入成功")
  2069. messagebox.showinfo("成功", f"配置导入成功!\n更新了 {len(items)} 个配置项")
  2070. self.status_var.set("配置导入成功")
  2071. except Exception as e:
  2072. print(f"写入配置文件失败: {e}")
  2073. messagebox.showerror("错误", f"写入配置文件失败: {str(e)}")
  2074. return
  2075. # 刷新预览
  2076. self.clear_selection()
  2077. def _import_multi_sheet_config(self):
  2078. """导入多工作表配置(直接使用解析后的数据,不再通过_convert_enemy_data转换)"""
  2079. print(f"多工作表配置导入开始...")
  2080. print(f"配置数据结构: {list(self.config_data.keys()) if self.config_data else 'None'}")
  2081. try:
  2082. # 读取现有JSON配置
  2083. if self.selected_json_path.exists():
  2084. with open(self.selected_json_path, 'r', encoding='utf-8') as f:
  2085. current_config = json.load(f)
  2086. else:
  2087. current_config = self.default_config.copy()
  2088. # 直接使用解析后的多工作表数据
  2089. if isinstance(self.config_data, list):
  2090. # 如果config_data是列表(如敌人配置),直接使用
  2091. print(f"使用解析后的敌人配置数据,共 {len(self.config_data)} 个敌人")
  2092. current_config['enemies'] = self.config_data
  2093. # 添加元数据
  2094. current_config['metadata'] = {
  2095. 'last_updated': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
  2096. 'totalEnemies': len(self.config_data),
  2097. 'conversionMethod': 'excel_multi_sheet_direct'
  2098. }
  2099. elif isinstance(self.config_data, dict):
  2100. # 如果config_data是字典,合并到current_config
  2101. print(f"合并字典格式的配置数据")
  2102. current_config.update(self.config_data)
  2103. # 写入更新后的配置
  2104. print(f"写入配置文件: {self.selected_json_path}")
  2105. print(f"配置内容预览: {str(current_config)[:200]}...")
  2106. with open(self.selected_json_path, 'w', encoding='utf-8') as f:
  2107. json.dump(current_config, f, indent=2, ensure_ascii=False)
  2108. print("多工作表配置文件写入成功")
  2109. messagebox.showinfo("成功", f"多工作表配置导入成功!\n更新了配置数据")
  2110. self.status_var.set("多工作表配置导入成功")
  2111. except Exception as e:
  2112. print(f"导入多工作表配置失败: {e}")
  2113. import traceback
  2114. traceback.print_exc()
  2115. messagebox.showerror("错误", f"导入多工作表配置失败: {str(e)}")
  2116. return
  2117. # 刷新预览
  2118. self.clear_selection()
  2119. def _detect_config_type(self):
  2120. """根据数据内容和工作表名称自动识别配置类型"""
  2121. try:
  2122. # 检查是否是多工作表数据
  2123. if self.multi_sheet_var.get() and hasattr(self, 'config_data') and isinstance(self.config_data, dict):
  2124. # 多工作表模式:根据工作表名称判断
  2125. if '敌人基础配置' in self.config_data or '敌人配置' in str(self.config_data.keys()):
  2126. return 'enemy'
  2127. elif '技能信息表' in self.config_data or '技能配置' in str(self.config_data.keys()):
  2128. return 'skill'
  2129. elif '武器配置' in str(self.config_data.keys()) or '武器信息' in self.config_data:
  2130. return 'weapon'
  2131. elif any('关卡' in str(key) for key in self.config_data.keys()):
  2132. return 'level'
  2133. # 检查数据内容中的字段来判断类型
  2134. if hasattr(self, 'config_data'):
  2135. sample_data = None
  2136. # 获取样本数据
  2137. if isinstance(self.config_data, dict) and 'items' in self.config_data:
  2138. items = self.config_data['items']
  2139. if items and len(items) > 0:
  2140. sample_data = items[0]
  2141. elif isinstance(self.config_data, list) and len(self.config_data) > 0:
  2142. sample_data = self.config_data[0]
  2143. if sample_data and isinstance(sample_data, dict):
  2144. # 根据字段名判断配置类型
  2145. fields = set(sample_data.keys())
  2146. # 敌人配置特征字段
  2147. enemy_fields = {'enemyId', 'name', 'hp', 'attack', 'defense', 'speed'}
  2148. if enemy_fields.intersection(fields):
  2149. return 'enemy'
  2150. # 技能配置特征字段
  2151. skill_fields = {'skillId', 'skillName', 'damage', 'cooldown', 'description'}
  2152. if skill_fields.intersection(fields):
  2153. return 'skill'
  2154. # 武器配置特征字段
  2155. weapon_fields = {'weaponId', 'weaponName', 'damage', 'range', 'fireRate'}
  2156. if weapon_fields.intersection(fields):
  2157. return 'weapon'
  2158. # 关卡配置特征字段
  2159. level_fields = {'levelId', 'name', 'enemies', 'waves'}
  2160. if level_fields.intersection(fields):
  2161. return 'level'
  2162. # 如果无法通过数据内容判断,尝试通过文件名判断(作为后备方案)
  2163. if hasattr(self, 'selected_files') and self.selected_files:
  2164. filename = Path(self.selected_files[0]).name.lower()
  2165. if '敌人' in filename:
  2166. return 'enemy'
  2167. elif '技能' in filename:
  2168. return 'skill'
  2169. elif '武器' in filename:
  2170. return 'weapon'
  2171. elif '关卡' in filename:
  2172. return 'level'
  2173. return 'unknown'
  2174. except Exception as e:
  2175. print(f"配置类型检测出错: {e}")
  2176. return 'unknown'
  2177. def _convert_enemy_data(self, item):
  2178. """转换敌人数据格式 - 支持嵌套结构"""
  2179. try:
  2180. print(f"开始转换敌人数据: {item}")
  2181. # 支持中英文字段名
  2182. enemy_id = item.get('id', item.get('敌人ID', ''))
  2183. enemy_name = item.get('name', item.get('敌人名称', ''))
  2184. enemy_type = item.get('type', item.get('敌人类型', ''))
  2185. print(f"敌人ID: {enemy_id}, 敌人名称: {enemy_name}, 类型: {enemy_type}")
  2186. # 检查必要字段
  2187. if not enemy_id:
  2188. print(f"跳过无效敌人数据: 缺少敌人ID - {item}")
  2189. return None
  2190. # 获取基础属性
  2191. health = item.get('health', item.get('生命值', 100))
  2192. speed = item.get('speed', item.get('移动速度', 50))
  2193. attack_damage = item.get('attackDamage', item.get('攻击伤害', 10))
  2194. attack_range = item.get('range', item.get('攻击范围', 100))
  2195. attack_speed = item.get('attackSpeed', item.get('攻击速度', 1.0))
  2196. defense = item.get('defense', item.get('防御力', 0))
  2197. # 调试信息
  2198. print(f"转换敌人 {enemy_id} 的属性: health={health}, speed={speed}, attackDamage={attack_damage}, defense={defense}")
  2199. print(f"原始数据中的字段: {list(item.keys())}")
  2200. if '生命值' in item:
  2201. print(f"找到生命值字段,值为: {item['生命值']}")
  2202. if 'health' in item:
  2203. print(f"找到health字段,值为: {item['health']}")
  2204. # 获取格挡相关属性
  2205. can_block = item.get('canBlock', item.get('可格挡', False))
  2206. block_chance = item.get('blockChance', item.get('格挡概率', 0.0))
  2207. block_damage_reduction = item.get('blockDamageReduction', item.get('格挡伤害减免', 0.5))
  2208. # 获取移动相关属性
  2209. movement_pattern = item.get('movementPattern', item.get('移动模式', 'direct'))
  2210. patrol_range = item.get('patrolRange', item.get('巡逻范围', 100))
  2211. chase_range = item.get('chaseRange', item.get('追击范围', 200))
  2212. # 获取视觉配置
  2213. # 敌人ID到动画编号的映射
  2214. enemy_to_ani = {
  2215. 'normal_zombie': '001',
  2216. 'roadblock_zombie': '002',
  2217. 'wandering_zombie': '003',
  2218. 'mage_zombie': '004',
  2219. 'archer_zombie': '005',
  2220. 'stealth_zombie': '006',
  2221. 'bucket_zombie': '007',
  2222. 'barrel_zombie': '008',
  2223. 'boss1_gatekeeper': '009',
  2224. 'boss2_gravedigger': '010',
  2225. 'boss3_cyborg': '011'
  2226. }
  2227. ani_num = enemy_to_ani.get(enemy_id, '001')
  2228. default_sprite_path = f'Animation/EnemyAni/{ani_num}'
  2229. sprite_path = item.get('spritePath', item.get('精灵路径', default_sprite_path))
  2230. scale = item.get('scale', item.get('缩放', 1.0))
  2231. # 获取音效配置
  2232. attack_sound = item.get('attackSound', item.get('攻击音效', 'enemy_attack'))
  2233. death_sound = item.get('deathSound', item.get('死亡音效', 'enemy_death'))
  2234. # 构建嵌套结构的配置
  2235. result = {
  2236. 'id': enemy_id,
  2237. 'name': enemy_name,
  2238. 'type': enemy_type,
  2239. 'stats': {
  2240. 'health': health,
  2241. 'maxHealth': health,
  2242. 'defense': defense,
  2243. 'speed': speed
  2244. },
  2245. 'movement': {
  2246. 'pattern': movement_pattern,
  2247. 'speed': speed,
  2248. 'patrolRange': patrol_range,
  2249. 'chaseRange': chase_range,
  2250. 'rotationSpeed': item.get('rotationSpeed', item.get('旋转速度', 180)),
  2251. 'moveType': item.get('moveType', item.get('移动类型', 'straight')),
  2252. 'swingAmplitude': item.get('swingAmplitude', item.get('摆动幅度', 0.0)),
  2253. 'swingFrequency': item.get('swingFrequency', item.get('摆动频率', 0.0)),
  2254. 'speedVariation': item.get('speedVariation', item.get('速度变化', 0.1))
  2255. },
  2256. 'combat': {
  2257. 'attackDamage': attack_damage,
  2258. 'attackRange': attack_range,
  2259. 'attackSpeed': attack_speed,
  2260. 'canBlock': can_block,
  2261. 'blockChance': block_chance,
  2262. 'blockDamageReduction': block_damage_reduction,
  2263. 'attackCooldown': 1.0 / attack_speed if attack_speed > 0 else 1.0,
  2264. 'attackType': item.get('attackType', item.get('攻击类型', 'melee')),
  2265. 'attackDelay': item.get('attackDelay', item.get('攻击延迟', 0.5)),
  2266. 'weaponType': item.get('weaponType', item.get('武器类型', 'none')),
  2267. 'projectileType': item.get('projectileType', item.get('投射物类型', 'none')),
  2268. 'projectileSpeed': item.get('projectileSpeed', item.get('投射物速度', 100))
  2269. },
  2270. 'visualConfig': {
  2271. 'spritePath': sprite_path,
  2272. 'scale': scale,
  2273. 'animationSpeed': item.get('animationSpeed', item.get('动画速度', 1.0)),
  2274. 'flipX': item.get('flipX', item.get('水平翻转', False)),
  2275. 'tint': item.get('tint', item.get('着色', '#FFFFFF')),
  2276. 'animations': {
  2277. 'idle': item.get('idleAnimation', item.get('待机动画', 'idle')),
  2278. 'walk': item.get('walkAnimation', item.get('行走动画', 'walk')),
  2279. 'attack': item.get('attackAnimation', item.get('攻击动画', 'attack')),
  2280. 'death': item.get('deathAnimation', item.get('死亡动画', 'dead'))
  2281. },
  2282. 'weaponProp': item.get('weaponProp', item.get('武器道具', ''))
  2283. },
  2284. 'audioConfig': {
  2285. 'attackSound': attack_sound,
  2286. 'deathSound': death_sound,
  2287. 'hitSound': item.get('hitSound', item.get('受击音效', 'enemy_hit')),
  2288. 'walkSound': item.get('walkSound', item.get('行走音效', '')),
  2289. 'blockSound': item.get('blockSound', item.get('格挡音效', '')),
  2290. 'stealthSound': item.get('stealthSound', item.get('隐身音效', '')),
  2291. 'armorBreakSound': item.get('armorBreakSound', item.get('护甲破碎音效', '')),
  2292. 'fuseSound': item.get('fuseSound', item.get('引信音效', '')),
  2293. 'volume': item.get('volume', item.get('音量', 1.0))
  2294. }
  2295. }
  2296. # 添加特殊能力配置
  2297. special_abilities = item.get('specialAbilities', [])
  2298. if special_abilities:
  2299. result['specialAbilities'] = special_abilities
  2300. # 添加BOSS配置
  2301. boss_config = item.get('bossConfig', {})
  2302. if boss_config and boss_config.get('isBoss', False):
  2303. result['bossConfig'] = boss_config
  2304. # 根据敌人类型添加特殊配置
  2305. result = self._add_enemy_special_config(result, enemy_type, item)
  2306. print(f"成功转换敌人数据: {enemy_id}")
  2307. return result
  2308. except Exception as e:
  2309. print(f"转换敌人数据失败: {e} - 数据: {item}")
  2310. return None
  2311. def _add_enemy_special_config(self, result, enemy_type, item):
  2312. """根据敌人类型添加特殊配置"""
  2313. try:
  2314. # 根据敌人类型添加特殊能力和配置
  2315. special_abilities = []
  2316. if enemy_type == 'stealth_zombie':
  2317. # 隐身僵尸特殊配置
  2318. special_abilities.append({
  2319. 'type': 'stealth',
  2320. 'duration': item.get('stealthDuration', item.get('隐身持续时间', 3.0)),
  2321. 'cooldown': item.get('stealthCooldown', item.get('隐身冷却时间', 8.0)),
  2322. 'alpha': item.get('stealthAlpha', item.get('隐身透明度', 0.3))
  2323. })
  2324. result['stealthConfig'] = {
  2325. 'canStealth': True,
  2326. 'stealthDuration': item.get('stealthDuration', item.get('隐身持续时间', 3.0)),
  2327. 'stealthCooldown': item.get('stealthCooldown', item.get('隐身冷却时间', 8.0)),
  2328. 'stealthAlpha': item.get('stealthAlpha', item.get('隐身透明度', 0.3))
  2329. }
  2330. elif enemy_type == 'bucket_zombie':
  2331. # 铁桶僵尸特殊配置
  2332. result['armorConfig'] = {
  2333. 'hasArmor': True,
  2334. 'armorHealth': item.get('armorHealth', item.get('护甲血量', 50)),
  2335. 'armorDefense': item.get('armorDefense', item.get('护甲防御', 5)),
  2336. 'armorSprite': item.get('armorSprite', item.get('护甲精灵', 'bucket_armor'))
  2337. }
  2338. elif enemy_type == 'explosive_zombie':
  2339. # 火药桶僵尸特殊配置
  2340. special_abilities.append({
  2341. 'type': 'explosion',
  2342. 'damage': item.get('explosionDamage', item.get('爆炸伤害', 30)),
  2343. 'radius': item.get('explosionRadius', item.get('爆炸半径', 80)),
  2344. 'triggerOnDeath': True
  2345. })
  2346. result['explosiveConfig'] = {
  2347. 'explosionDamage': item.get('explosionDamage', item.get('爆炸伤害', 30)),
  2348. 'explosionRadius': item.get('explosionRadius', item.get('爆炸半径', 80)),
  2349. 'fuseTime': item.get('fuseTime', item.get('引爆时间', 2.0)),
  2350. 'chainExplosion': item.get('chainExplosion', item.get('连锁爆炸', False))
  2351. }
  2352. elif enemy_type == 'ranged_enemy':
  2353. # 远程敌人特殊配置
  2354. result['rangedConfig'] = {
  2355. 'projectileType': item.get('projectileType', item.get('弹药类型', 'bullet')),
  2356. 'projectileSpeed': item.get('projectileSpeed', item.get('弹药速度', 100)),
  2357. 'projectileDamage': item.get('projectileDamage', item.get('弹药伤害', item.get('attack', item.get('攻击力', 10)))),
  2358. 'fireRate': item.get('fireRate', item.get('射速', 1.0)),
  2359. 'accuracy': item.get('accuracy', item.get('精度', 0.9))
  2360. }
  2361. elif 'boss' in enemy_type.lower():
  2362. # BOSS特殊配置
  2363. special_abilities.extend([
  2364. {
  2365. 'type': 'charge_attack',
  2366. 'damage': item.get('chargeDamage', item.get('冲锋伤害', item.get('attack', item.get('攻击力', 10)) * 2)),
  2367. 'range': item.get('chargeRange', item.get('冲锋范围', 150)),
  2368. 'cooldown': item.get('chargeCooldown', item.get('冲锋冷却', 8.0))
  2369. },
  2370. {
  2371. 'type': 'area_attack',
  2372. 'damage': item.get('areaDamage', item.get('范围伤害', item.get('attack', item.get('攻击力', 10)) * 1.5)),
  2373. 'radius': item.get('areaRadius', item.get('范围半径', 100)),
  2374. 'cooldown': item.get('areaCooldown', item.get('范围攻击冷却', 12.0))
  2375. }
  2376. ])
  2377. result['bossConfig'] = {
  2378. 'isBoss': True,
  2379. 'phases': item.get('phases', item.get('阶段数', 1)),
  2380. 'enrageThreshold': item.get('enrageThreshold', item.get('狂暴阈值', 0.3)),
  2381. 'enrageDamageMultiplier': item.get('enrageDamageMultiplier', item.get('狂暴伤害倍数', 1.5)),
  2382. 'enrageSpeedMultiplier': item.get('enrageSpeedMultiplier', item.get('狂暴速度倍数', 1.3))
  2383. }
  2384. # 添加特殊能力到结果中
  2385. if special_abilities:
  2386. result['specialAbilities'] = special_abilities
  2387. return result
  2388. except Exception as e:
  2389. print(f"添加特殊配置失败: {e}")
  2390. return result
  2391. def _convert_weapon_data(self, item):
  2392. """转换武器数据格式"""
  2393. try:
  2394. print(f"开始转换武器数据: {item}")
  2395. # 支持中英文字段名
  2396. weapon_id = item.get('id', item.get('ID', ''))
  2397. weapon_name = item.get('name', item.get('名称', ''))
  2398. print(f"武器ID: {weapon_id}, 武器名称: {weapon_name}")
  2399. # 检查必要字段
  2400. if not weapon_id:
  2401. print(f"跳过无效武器数据: 缺少武器ID - {item}")
  2402. return None
  2403. # 获取基础属性
  2404. damage = item.get('damage', item.get('伤害', 10))
  2405. fire_rate = item.get('fireRate', item.get('射速', 1.0))
  2406. weapon_range = item.get('range', item.get('射程', 100))
  2407. bullet_speed = item.get('bulletSpeed', item.get('子弹速度', 100))
  2408. weapon_type = item.get('type', item.get('类型', ''))
  2409. rarity = item.get('rarity', item.get('稀有度', ''))
  2410. weight = item.get('weight', item.get('权重', 1))
  2411. # 根据武器ID推断武器类型和稀有度(如果为空)
  2412. if not weapon_type:
  2413. if 'shotgun' in weapon_id or 'cactus' in weapon_id:
  2414. weapon_type = 'shotgun'
  2415. elif 'bomb' in weapon_id or 'pepper' in weapon_id:
  2416. weapon_type = 'explosive'
  2417. elif 'missile' in weapon_id:
  2418. weapon_type = 'homing_missile'
  2419. elif 'boomerang' in weapon_id:
  2420. weapon_type = 'boomerang'
  2421. elif 'saw' in weapon_id:
  2422. weapon_type = 'ricochet_piercing'
  2423. elif 'carrot' in weapon_id:
  2424. weapon_type = 'piercing'
  2425. else:
  2426. weapon_type = 'single_shot'
  2427. if not rarity:
  2428. if damage >= 60:
  2429. rarity = 'epic'
  2430. elif damage >= 40:
  2431. rarity = 'rare'
  2432. elif damage >= 25:
  2433. rarity = 'uncommon'
  2434. else:
  2435. rarity = 'common'
  2436. # 根据稀有度设置权重
  2437. if weight == 1: # 默认权重,需要根据稀有度调整
  2438. rarity_weights = {'common': 30, 'uncommon': 20, 'rare': 15, 'epic': 8}
  2439. weight = rarity_weights.get(rarity, 20)
  2440. result = {
  2441. 'id': weapon_id,
  2442. 'name': weapon_name,
  2443. 'type': weapon_type,
  2444. 'rarity': rarity,
  2445. 'weight': weight,
  2446. 'stats': {
  2447. 'damage': damage,
  2448. 'fireRate': fire_rate,
  2449. 'range': weapon_range,
  2450. 'bulletSpeed': min(bullet_speed, 50) # 限制子弹速度
  2451. },
  2452. 'bulletConfig': self._generate_bullet_config(weapon_id, weapon_type, damage, weapon_range),
  2453. 'visualConfig': self._generate_visual_config(weapon_id, weapon_name)
  2454. }
  2455. # 如果原始数据中有upgradeConfig,则添加到结果中
  2456. if 'upgradeConfig' in item:
  2457. result['upgradeConfig'] = item['upgradeConfig']
  2458. print(f"为武器 {weapon_id} 保留了升级配置: {item['upgradeConfig']}")
  2459. else:
  2460. print(f"武器 {weapon_id} 没有升级配置数据")
  2461. # 处理方块价格配置(inGameCostConfig)
  2462. # 优先使用从游戏内成本配置工作表导入的数据
  2463. if 'baseCost' in item or 'shapeCosts' in item:
  2464. in_game_cost_config = {
  2465. 'baseCost': item.get('baseCost', 10),
  2466. 'shapeCosts': item.get('shapeCosts', {})
  2467. }
  2468. print(f"为武器 {weapon_id} 使用Excel导入的方块价格配置: {in_game_cost_config}")
  2469. else:
  2470. # 如果没有导入数据,则生成默认配置
  2471. in_game_cost_config = self._generate_in_game_cost_config(item, weapon_id, rarity)
  2472. print(f"为武器 {weapon_id} 生成默认方块价格配置: {in_game_cost_config}")
  2473. if in_game_cost_config:
  2474. result['inGameCostConfig'] = in_game_cost_config
  2475. print(f"成功转换武器数据: {result}")
  2476. return result
  2477. except Exception as e:
  2478. print(f"转换武器数据失败: {e} - 数据: {item}")
  2479. return None
  2480. def _generate_bullet_config(self, weapon_id, weapon_type, damage, weapon_range):
  2481. """生成子弹配置"""
  2482. # 基础配置模板
  2483. base_config = {
  2484. 'count': {'type': 'single', 'amount': 1, 'spreadAngle': 0, 'burstCount': 1, 'burstDelay': 0},
  2485. 'trajectory': {'type': 'straight', 'speed': 200, 'gravity': 0, 'arcHeight': 0, 'homingStrength': 0, 'homingDelay': 0},
  2486. 'hitEffects': [{'type': 'normal_damage', 'priority': 1, 'params': {'damage': damage}}],
  2487. 'lifecycle': {'type': 'hit_destroy', 'maxLifetime': 5.0, 'penetration': 1, 'ricochetCount': 0, 'returnToOrigin': False},
  2488. 'visual': {'bulletPrefab': f'bullets/{weapon_id.title()}Bullet', 'hitEffect': 'Animation/WeaponTx/tx0002/tx0002', 'trailEffect': None, 'muzzleFlash': 'Animation/WeaponTx/tx0002/tx0002'}
  2489. }
  2490. # 根据武器类型调整配置
  2491. if weapon_type == 'shotgun':
  2492. base_config['count'] = {'type': 'spread', 'amount': 5, 'spreadAngle': 30, 'burstCount': 1, 'burstDelay': 0}
  2493. base_config['lifecycle']['type'] = 'range_limit'
  2494. base_config['lifecycle']['maxRange'] = weapon_range * 2
  2495. elif weapon_type == 'piercing':
  2496. base_config['hitEffects'] = [{'type': 'pierce_damage', 'priority': 1, 'params': {'damage': damage, 'pierceCount': 999}}]
  2497. base_config['lifecycle'] = {'type': 'range_limit', 'maxLifetime': 5.0, 'penetration': 999, 'ricochetCount': 0, 'returnToOrigin': False, 'maxRange': weapon_range * 2}
  2498. base_config['visual']['trailEffect'] = 'Animation/WeaponTx/tx0001/tx0001'
  2499. elif weapon_type == 'explosive':
  2500. base_config['trajectory']['type'] = 'arc'
  2501. base_config['hitEffects'] = [{'type': 'explosion', 'priority': 1, 'params': {'damage': damage + 20, 'radius': 100, 'delay': 0.1}}]
  2502. base_config['lifecycle']['type'] = 'ground_impact'
  2503. base_config['visual']['hitEffect'] = 'Animation/WeaponTx/tx0007/tx0007'
  2504. elif weapon_type == 'homing_missile':
  2505. base_config['trajectory'] = {'type': 'homing', 'speed': 20, 'gravity': 0.2, 'arcHeight': 100, 'homingStrength': 0.8, 'homingDelay': 0.3}
  2506. base_config['hitEffects'] = [{'type': 'explosion', 'priority': 1, 'params': {'damage': damage, 'radius': 150, 'delay': 0}}]
  2507. base_config['lifecycle']['type'] = 'target_impact'
  2508. elif weapon_type == 'boomerang':
  2509. base_config['trajectory'] = {'type': 'homing', 'speed': 15, 'gravity': 0, 'homingStrength': 0.5, 'homingDelay': 0.3}
  2510. base_config['hitEffects'] = [{'type': 'pierce_damage', 'priority': 1, 'params': {'damage': damage, 'pierceCount': 999}}]
  2511. base_config['lifecycle'] = {'type': 'return_trip', 'maxLifetime': 10.0, 'penetration': 999, 'ricochetCount': 0, 'returnToOrigin': True, 'returnDelay': 1.0}
  2512. elif weapon_type == 'ricochet_piercing':
  2513. base_config['hitEffects'] = [
  2514. {'type': 'ricochet_damage', 'priority': 1, 'params': {'damage': damage, 'ricochetCount': 2, 'ricochetAngle': 45}},
  2515. {'type': 'pierce_damage', 'priority': 2, 'params': {'damage': damage, 'pierceCount': 3}}
  2516. ]
  2517. base_config['lifecycle'] = {'type': 'ricochet_counter', 'maxLifetime': 8.0, 'penetration': 3, 'ricochetCount': 3, 'returnToOrigin': False}
  2518. return base_config
  2519. def _generate_in_game_cost_config(self, item, weapon_id, rarity):
  2520. """从Excel表中读取方块价格配置"""
  2521. try:
  2522. # 从游戏内成本配置表中读取价格配置
  2523. cost_config = self._load_in_game_cost_config(weapon_id)
  2524. if cost_config:
  2525. base_cost_per_grid = cost_config.get('基础每格成本', 10)
  2526. shape_costs = {
  2527. 'I': cost_config.get('I形状', base_cost_per_grid * 2),
  2528. 'H-I': cost_config.get('H-I形状', base_cost_per_grid * 2),
  2529. 'L': cost_config.get('L形状', base_cost_per_grid * 3),
  2530. 'S': cost_config.get('S形状', base_cost_per_grid * 4),
  2531. 'D-T': cost_config.get('D-T形状', base_cost_per_grid * 4)
  2532. }
  2533. print(f"为武器 {weapon_id} 从Excel表中读取方块价格配置")
  2534. else:
  2535. # 如果在Excel表中找不到配置,使用默认值
  2536. print(f"警告:武器 {weapon_id} 在游戏内成本配置表中未找到,使用默认配置")
  2537. base_cost_per_grid = 10
  2538. shape_costs = {
  2539. 'I': 20,
  2540. 'H-I': 20,
  2541. 'L': 30,
  2542. 'S': 40,
  2543. 'D-T': 40
  2544. }
  2545. return {
  2546. 'baseCost': base_cost_per_grid,
  2547. 'shapeCosts': shape_costs
  2548. }
  2549. except Exception as e:
  2550. print(f"生成方块价格配置失败: {e} - 武器: {weapon_id}")
  2551. # 返回默认配置
  2552. return {
  2553. 'baseCost': 10,
  2554. 'shapeCosts': {
  2555. 'I': 20,
  2556. 'H-I': 20,
  2557. 'L': 30,
  2558. 'S': 40,
  2559. 'D-T': 40
  2560. }
  2561. }
  2562. def _load_in_game_cost_config(self, weapon_id):
  2563. """从Excel表中加载指定武器的游戏内成本配置"""
  2564. try:
  2565. excel_file = self.script_dir / "方块武器配置" / "方块武器配置表.xlsx"
  2566. if not excel_file.exists():
  2567. print(f"Excel文件不存在: {excel_file}")
  2568. return None
  2569. # 读取游戏内成本配置工作表
  2570. excel_data = pd.read_excel(excel_file, sheet_name="游戏内成本配置")
  2571. # 查找指定武器ID的配置
  2572. for index, row in excel_data.iterrows():
  2573. if index == 0: # 跳过表头
  2574. continue
  2575. # 获取武器ID(支持多种字段名)
  2576. current_weapon_id = None
  2577. for id_field in ['武器ID', 'ID', 'weapon_id', 'weaponId']:
  2578. if id_field in row and pd.notna(row[id_field]):
  2579. current_weapon_id = str(row[id_field]).strip()
  2580. break
  2581. # 如果没有找到命名字段,使用第一列
  2582. if current_weapon_id is None:
  2583. current_weapon_id = str(row.iloc[0]).strip() if len(row) > 0 else None
  2584. if current_weapon_id == weapon_id:
  2585. # 构建成本配置
  2586. cost_config = {}
  2587. # 读取基础每格成本
  2588. base_cost_fields = ['基础每格成本', 'baseCost', '基础成本', '武器基础售价']
  2589. for field in base_cost_fields:
  2590. if field in row and pd.notna(row[field]):
  2591. try:
  2592. cost_config['基础每格成本'] = int(float(row[field]))
  2593. break
  2594. except (ValueError, TypeError):
  2595. continue
  2596. # 读取各形状成本
  2597. shape_fields = {
  2598. 'I形状': ['I形状', 'I形状成本', 'I_shape_cost', 'I'],
  2599. 'H-I形状': ['H-I形状', 'H-I形状成本', 'HI_shape_cost', 'H-I'],
  2600. 'L形状': ['L形状', 'L形状成本', 'L_shape_cost', 'L'],
  2601. 'S形状': ['S形状', 'S形状成本', 'S_shape_cost', 'S'],
  2602. 'D-T形状': ['D-T形状', 'D-T形状成本', 'DT_shape_cost', 'D-T']
  2603. }
  2604. for shape_key, field_names in shape_fields.items():
  2605. for field_name in field_names:
  2606. if field_name in row and pd.notna(row[field_name]):
  2607. try:
  2608. cost_config[shape_key] = int(float(row[field_name]))
  2609. break
  2610. except (ValueError, TypeError):
  2611. continue
  2612. return cost_config if cost_config else None
  2613. print(f"未找到武器 {weapon_id} 的游戏内成本配置")
  2614. return None
  2615. except Exception as e:
  2616. print(f"加载游戏内成本配置失败: {e}")
  2617. return None
  2618. def _generate_visual_config(self, weapon_id, weapon_name):
  2619. """生成视觉配置"""
  2620. # 根据武器ID生成图片编号
  2621. weapon_sprite_map = {
  2622. 'pea_shooter': '001-1',
  2623. 'sharp_carrot': '002',
  2624. 'saw_grass': '003',
  2625. 'watermelon_bomb': '007',
  2626. 'boomerang_plant': '004',
  2627. 'hot_pepper': '005',
  2628. 'cactus_shotgun': '008',
  2629. 'okra_missile': '006',
  2630. 'mace_club': '009'
  2631. }
  2632. sprite_id = weapon_sprite_map.get(weapon_id, '001')
  2633. return {
  2634. 'weaponSprites': {
  2635. 'I': f'images/PlantsSprite/{sprite_id}',
  2636. 'H-I': f'images/PlantsSprite/{sprite_id}',
  2637. 'L': f'images/PlantsSprite/{sprite_id}',
  2638. 'S': f'images/PlantsSprite/{sprite_id}',
  2639. 'D-T': f'images/PlantsSprite/{sprite_id}'
  2640. },
  2641. 'fireSound': f'audio/{weapon_id}_shot'
  2642. }
  2643. def _convert_skill_data(self, item):
  2644. """转换技能数据格式"""
  2645. try:
  2646. # 支持多种字段名格式(中文和英文)
  2647. skill_id = item.get('id', item.get('技能ID', ''))
  2648. skill_name = item.get('name', item.get('技能名称', ''))
  2649. print(f"正在转换技能数据: {skill_id} - {skill_name}")
  2650. # 检查必要字段
  2651. if not skill_id:
  2652. print(f"跳过无效技能数据: 缺少技能ID - {item}")
  2653. return None
  2654. result = {
  2655. 'id': skill_id,
  2656. 'name': skill_name,
  2657. 'description': item.get('description', item.get('技能描述', '')),
  2658. 'iconPath': item.get('iconPath', item.get('图标路径', '')),
  2659. 'maxLevel': item.get('maxLevel', item.get('最大等级', 1)),
  2660. 'currentLevel': item.get('currentLevel', item.get('当前等级', 0)),
  2661. 'priceReduction': item.get('priceReduction', item.get('价格减少', 0.0)),
  2662. 'critChanceIncrease': item.get('critChanceIncrease', item.get('暴击几率增加', 0.0)),
  2663. 'critDamageBonus': item.get('critDamageBonus', item.get('暴击伤害加成', 0.0)),
  2664. 'healthIncrease': item.get('healthIncrease', item.get('生命值增加', 0.0)),
  2665. 'multiShotChance': item.get('multiShotChance', item.get('多重射击几率', 0.0)),
  2666. 'energyGainIncrease': item.get('energyGainIncrease', item.get('能量加成', 0.0)),
  2667. 'ballSpeedIncrease': item.get('ballSpeedIncrease', item.get('速度提升', 0.0))
  2668. }
  2669. print(f"成功转换技能数据: {result}")
  2670. return result
  2671. except Exception as e:
  2672. print(f"转换技能数据失败: {e} - 数据: {item}")
  2673. return None
  2674. def _convert_level_data(self, item):
  2675. """转换关卡数据格式"""
  2676. try:
  2677. # 如果item已经是完整的关卡数据结构(来自多工作表解析),直接返回
  2678. if isinstance(item, dict) and 'waves' in item:
  2679. return item
  2680. # 处理传统的单行数据格式
  2681. # 处理可用武器字符串,转换为数组(支持中英文字段名)
  2682. available_weapons = []
  2683. weapons_field = item.get('weapons', item.get('可用武器', ''))
  2684. if weapons_field:
  2685. weapons_str = str(weapons_field)
  2686. # 支持逗号、顿号、分号等多种分隔符
  2687. for separator in ['、', ',', ',', ';', ';']:
  2688. if separator in weapons_str:
  2689. available_weapons = [weapon.strip() for weapon in weapons_str.split(separator)]
  2690. break
  2691. # 如果没有找到分隔符,则作为单个武器处理
  2692. if not available_weapons:
  2693. available_weapons = [weapons_str.strip()]
  2694. # 获取关卡ID,用于读取现有配置(支持中英文字段名)
  2695. level_id = str(item.get('levelId', item.get('关卡ID', '')))
  2696. # 尝试读取现有的关卡配置文件,保留waves数据
  2697. existing_waves = []
  2698. existing_data = {}
  2699. if level_id:
  2700. try:
  2701. levels_dir = self.project_root / "assets/resources/data/levels"
  2702. level_file = levels_dir / f"{level_id}.json"
  2703. if level_file.exists():
  2704. with open(level_file, 'r', encoding='utf-8') as f:
  2705. existing_data = json.load(f)
  2706. existing_waves = existing_data.get('waves', [])
  2707. print(f"保留现有关卡 {level_id} 的 {len(existing_waves)} 个波次数据")
  2708. except Exception as e:
  2709. print(f"读取现有关卡配置时出错: {e}")
  2710. # 构建新的关卡数据,保留现有的waves(支持中英文字段名)
  2711. level_data = {
  2712. "levelId": level_id,
  2713. "name": str(item.get('name', item.get('关卡名称', existing_data.get('name', '')))),
  2714. "scene": str(item.get('scene', item.get('场景', existing_data.get('scene', '')))),
  2715. "description": str(item.get('description', item.get('描述', existing_data.get('description', '')))),
  2716. "backgroundImage": str(item.get('backgroundImage', item.get('关卡背景图路径', existing_data.get('backgroundImage', 'images/LevelBackground/BG1')))),
  2717. "weapons": available_weapons if available_weapons else existing_data.get('weapons', existing_data.get('availableWeapons', [])),
  2718. "timeLimit": int(item.get('timeLimit', item.get('时间限制', existing_data.get('timeLimit', 300)))),
  2719. "difficulty": str(item.get('difficulty', item.get('难度', existing_data.get('difficulty', 'normal')))),
  2720. "healthMultiplier": float(item.get('healthMultiplier', item.get('生命倍数', existing_data.get('healthMultiplier', 1.0)))),
  2721. "waves": existing_waves # 保留现有的waves数据
  2722. }
  2723. # 添加可选字段(如果存在,支持中英文字段名)
  2724. coin_reward = item.get('coinReward', item.get('钞票奖励'))
  2725. if coin_reward is not None:
  2726. level_data["coinReward"] = int(coin_reward)
  2727. elif 'coinReward' in existing_data:
  2728. level_data["coinReward"] = existing_data['coinReward']
  2729. diamond_reward = item.get('diamondReward', item.get('钻石奖励'))
  2730. if diamond_reward is not None:
  2731. level_data["diamondReward"] = int(diamond_reward)
  2732. elif 'diamondReward' in existing_data:
  2733. level_data["diamondReward"] = existing_data['diamondReward']
  2734. return level_data
  2735. except Exception as e:
  2736. print(f"转换关卡数据时出错: {e}")
  2737. return None
  2738. def fix_enemies_json(self):
  2739. """修复enemies.json文件 - 删除attack字段"""
  2740. try:
  2741. # 获取enemies.json路径
  2742. enemies_json_path = Path(self.json_path_var.get()) if self.json_path_var.get() else None
  2743. if not enemies_json_path or not enemies_json_path.exists():
  2744. # 尝试默认路径
  2745. enemies_json_path = Path(os.path.dirname(__file__)).parent / "enemies.json"
  2746. if not enemies_json_path.exists():
  2747. messagebox.showerror("错误", "未找到enemies.json文件")
  2748. return False
  2749. # 备份原文件
  2750. backup_path = f"{enemies_json_path}.backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
  2751. with open(enemies_json_path, 'r', encoding='utf-8') as src:
  2752. with open(backup_path, 'w', encoding='utf-8') as dst:
  2753. dst.write(src.read())
  2754. # 读取JSON文件
  2755. with open(enemies_json_path, 'r', encoding='utf-8') as f:
  2756. data = json.load(f)
  2757. # 递归删除attack字段
  2758. def remove_attack_fields(obj):
  2759. if isinstance(obj, dict):
  2760. if 'attack' in obj:
  2761. del obj['attack']
  2762. for value in obj.values():
  2763. remove_attack_fields(value)
  2764. elif isinstance(obj, list):
  2765. for item in obj:
  2766. remove_attack_fields(item)
  2767. remove_attack_fields(data)
  2768. # 写回文件
  2769. with open(enemies_json_path, 'w', encoding='utf-8') as f:
  2770. json.dump(data, f, ensure_ascii=False, indent=2)
  2771. messagebox.showinfo("成功", f"已修复enemies.json文件\n备份文件: {backup_path}\n已删除所有attack字段")
  2772. return True
  2773. except Exception as e:
  2774. messagebox.showerror("错误", f"修复enemies.json失败: {str(e)}")
  2775. return False
  2776. def auto_import_enemies_from_excel(self):
  2777. """自动从Excel导入敌人配置"""
  2778. try:
  2779. # 查找敌人配置表.xlsx
  2780. excel_dir = Path(os.path.dirname(__file__))
  2781. excel_file = excel_dir / "敌人配置表.xlsx"
  2782. if not excel_file.exists():
  2783. messagebox.showerror("错误", f"未找到敌人配置表.xlsx文件: {excel_file}")
  2784. return False
  2785. # 读取Excel文件
  2786. if not PANDAS_AVAILABLE:
  2787. messagebox.showerror("错误", "pandas未安装,无法读取Excel文件")
  2788. return False
  2789. all_sheets = pd.read_excel(excel_file, sheet_name=None)
  2790. # 解析敌人配置
  2791. enemies_config = self.parse_enemy_multi_sheet_data(all_sheets, "敌人配置表.xlsx")
  2792. if not enemies_config:
  2793. messagebox.showerror("错误", "未能解析到任何敌人配置")
  2794. return False
  2795. # 获取输出路径
  2796. enemies_json_path = Path(self.json_path_var.get()) if self.json_path_var.get() else None
  2797. if not enemies_json_path:
  2798. enemies_json_path = excel_dir.parent / "enemies.json"
  2799. # 备份现有文件
  2800. if enemies_json_path.exists():
  2801. backup_path = f"{enemies_json_path}.backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
  2802. with open(enemies_json_path, 'r', encoding='utf-8') as src:
  2803. with open(backup_path, 'w', encoding='utf-8') as dst:
  2804. dst.write(src.read())
  2805. # 写入新配置
  2806. with open(enemies_json_path, 'w', encoding='utf-8') as f:
  2807. json.dump(enemies_config, f, ensure_ascii=False, indent=2)
  2808. messagebox.showinfo("成功", f"已成功导入{len(enemies_config)}个敌人配置到: {enemies_json_path}")
  2809. return True
  2810. except Exception as e:
  2811. messagebox.showerror("错误", f"自动导入敌人配置失败: {str(e)}")
  2812. return False
  2813. def restore_default_config(self):
  2814. """恢复默认配置"""
  2815. result = messagebox.askyesno("确认", "确定要恢复默认配置吗?\n当前配置将被覆盖!")
  2816. if not result:
  2817. return
  2818. try:
  2819. if not self.selected_json_path:
  2820. messagebox.showwarning("警告", "请先选择JSON输出路径")
  2821. return
  2822. json_path = Path(self.selected_json_path)
  2823. # 备份当前配置
  2824. if json_path.exists():
  2825. backup_path = json_path.parent / f"ballController_backup_before_default_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
  2826. with open(json_path, 'r', encoding='utf-8') as f:
  2827. content = f.read()
  2828. with open(backup_path, 'w', encoding='utf-8') as f:
  2829. f.write(content)
  2830. # 写入默认配置
  2831. with open(json_path, 'w', encoding='utf-8') as f:
  2832. json.dump(self.default_config, f, indent=2, ensure_ascii=False)
  2833. messagebox.showinfo("成功", "已恢复默认配置")
  2834. self.status_var.set("已恢复默认配置")
  2835. # 刷新预览
  2836. self.clear_selection()
  2837. except Exception as e:
  2838. messagebox.showerror("错误", f"恢复默认配置失败: {e}")
  2839. def run(self):
  2840. """运行应用"""
  2841. self.root.mainloop()
  2842. def main():
  2843. """主函数"""
  2844. try:
  2845. app = ConfigManagerGUI()
  2846. app.run()
  2847. except Exception as e:
  2848. print(f"启动应用失败: {e}")
  2849. input("按回车键退出...")
  2850. if __name__ == "__main__":
  2851. main()