| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448 |
- #!/usr/bin/env python3
- # -*- coding: utf-8 -*-
- """
- BallController配置管理工具 - 简化版
- 图形界面版本,不依赖pandas,支持浏览文件并选择Excel/CSV表格进行配置导入
- 功能:
- 1. 图形界面浏览项目文件
- 2. 多选Excel/CSV配置表格
- 3. 预览配置内容
- 4. 一键导入配置到JSON
- 5. 配置备份和恢复
- 作者: AI Assistant
- 日期: 2024
- """
- import tkinter as tk
- from tkinter import ttk, filedialog, messagebox, scrolledtext
- import json
- import os
- import csv
- from datetime import datetime
- from pathlib import Path
- import threading
- # 尝试导入pandas,如果失败则使用纯CSV模式
- try:
- import pandas as pd
- PANDAS_AVAILABLE = True
- except ImportError:
- PANDAS_AVAILABLE = False
- print("警告: pandas未安装,将使用纯CSV模式(不支持Excel文件)")
- class SkillConfigImporter:
- """技能配置表导入工具类"""
- def __init__(self, excel_dir=None):
- # 获取当前脚本所在目录
- if excel_dir:
- self.script_dir = Path(excel_dir)
- else:
- self.script_dir = Path(__file__).parent
- self.excel_file = self.script_dir / "局外技能配置表.xlsx"
- self.json_file = self.script_dir.parent / "skill_config.json"
-
- # 默认配置结构
- self.default_config = {
- "skillTypes": [],
- "skillGroups": [],
- "totalGroups": 12,
- "skillsPerGroup": 3
- }
-
- def read_excel_config(self):
- """从Excel文件读取技能配置"""
- if not self.excel_file.exists():
- print(f"错误: Excel文件不存在: {self.excel_file}")
- return None
-
- try:
- # 读取所有工作表
- excel_data = pd.read_excel(self.excel_file, sheet_name=None)
- print(f"找到工作表: {list(excel_data.keys())}")
-
- config = self.default_config.copy()
-
- # 读取技能类型配置
- if "技能类型配置" in excel_data:
- skill_types = self.parse_skill_types(excel_data["技能类型配置"])
- config["skillTypes"] = skill_types
- print(f"读取到 {len(skill_types)} 个技能类型")
- elif "JSON技能类型配置" in excel_data:
- skill_types = self.parse_skill_types(excel_data["JSON技能类型配置"])
- config["skillTypes"] = skill_types
- print(f"读取到 {len(skill_types)} 个技能类型")
-
- # 读取技能组配置
- if "技能组配置" in excel_data:
- skill_groups = self.parse_skill_groups(excel_data["技能组配置"])
- config["skillGroups"] = skill_groups
- config["totalGroups"] = len(skill_groups)
- print(f"读取到 {len(skill_groups)} 个技能组")
- elif "JSON技能组配置" in excel_data:
- skill_groups = self.parse_skill_groups(excel_data["JSON技能组配置"])
- config["skillGroups"] = skill_groups
- config["totalGroups"] = len(skill_groups)
- print(f"读取到 {len(skill_groups)} 个技能组")
-
- # 读取基本配置
- if "基本配置" in excel_data:
- basic_config = self.parse_basic_config(excel_data["基本配置"])
- config.update(basic_config)
- elif "JSON基本配置" in excel_data:
- basic_config = self.parse_basic_config(excel_data["JSON基本配置"])
- config.update(basic_config)
-
- return config
-
- except Exception as e:
- print(f"读取Excel文件失败: {e}")
- return None
-
- def parse_skill_types(self, df):
- """解析技能类型配置"""
- skill_types = []
-
- # 读取所有数据行(pandas已经自动处理了标题行)
- for index, row in df.iterrows():
-
- # 检查是否为空行
- if pd.isna(row.iloc[0]):
- continue
-
- try:
- skill_type = {
- "id": int(row.iloc[0]) if not pd.isna(row.iloc[0]) else 0,
- "name": str(row.iloc[1]) if not pd.isna(row.iloc[1]) else "",
- "displayName": str(row.iloc[2]) if not pd.isna(row.iloc[2]) else "",
- "nameTemplate": str(row.iloc[3]) if not pd.isna(row.iloc[3]) else "",
- "type": str(row.iloc[4]) if not pd.isna(row.iloc[4]) else ""
- }
- skill_types.append(skill_type)
- except Exception as e:
- print(f"解析技能类型第 {index+1} 行失败: {e}")
- continue
-
- return skill_types
-
- def parse_skill_groups(self, df):
- """解析技能组配置"""
- skill_groups = []
-
- # 读取所有数据行(pandas已经自动处理了标题行)
- for index, row in df.iterrows():
-
- # 检查是否为空行
- if pd.isna(row.iloc[0]):
- continue
-
- try:
- skill_group = {
- "group": int(row.iloc[0]) if not pd.isna(row.iloc[0]) else 1,
- "effectPercent": int(row.iloc[1]) if not pd.isna(row.iloc[1]) else 0,
- "diamondCost": int(row.iloc[2]) if not pd.isna(row.iloc[2]) else 0
- }
- skill_groups.append(skill_group)
- except Exception as e:
- print(f"解析技能组第 {index+1} 行失败: {e}")
- continue
-
- return skill_groups
-
- def parse_basic_config(self, df):
- """解析基本配置"""
- basic_config = {}
-
- # 假设基本配置是纵向格式:配置项名称 | 数值
- for index, row in df.iterrows():
- if index == 0: # 跳过标题行
- continue
-
- # 检查是否为空行
- if pd.isna(row.iloc[0]):
- continue
-
- try:
- param_name = str(row.iloc[0]).strip()
- param_value = row.iloc[1]
-
- if param_name == "总组数" or param_name == "totalGroups":
- basic_config["totalGroups"] = int(param_value) if not pd.isna(param_value) else 12
- elif param_name == "每组技能数" or param_name == "skillsPerGroup":
- basic_config["skillsPerGroup"] = int(param_value) if not pd.isna(param_value) else 3
-
- except Exception as e:
- print(f"解析基本配置第 {index+1} 行失败: {e}")
- continue
-
- return basic_config
-
- def validate_config(self, config):
- """验证配置数据"""
- errors = []
-
- # 验证技能类型
- if not config.get("skillTypes"):
- errors.append("技能类型配置为空")
- else:
- for i, skill_type in enumerate(config["skillTypes"]):
- if not skill_type.get("name"):
- errors.append(f"技能类型 {i+1} 缺少名称")
- if not skill_type.get("type"):
- errors.append(f"技能类型 {i+1} 缺少类型标识")
-
- # 验证技能组
- if not config.get("skillGroups"):
- errors.append("技能组配置为空")
- else:
- groups = [sg["group"] for sg in config["skillGroups"]]
- if len(groups) != len(set(groups)):
- errors.append("技能组编号存在重复")
-
- # 验证基本配置
- if config.get("totalGroups", 0) <= 0:
- errors.append("总组数必须大于0")
- if config.get("skillsPerGroup", 0) <= 0:
- errors.append("每组技能数必须大于0")
-
- return errors
-
- def backup_json(self):
- """备份当前JSON文件(已禁用)"""
- # 不再创建备份文件,直接返回成功
- return True
-
- def save_json_config(self, config):
- """保存配置到JSON文件"""
- try:
- with open(self.json_file, 'w', encoding='utf-8') as f:
- json.dump(config, f, ensure_ascii=False, indent=2)
- print(f"配置已保存到: {self.json_file}")
- return True
- except Exception as e:
- print(f"保存JSON文件失败: {e}")
- return False
-
- def import_config(self):
- """执行配置导入"""
- print("=== 技能配置导入工具 ===")
- print(f"Excel文件: {self.excel_file}")
- print(f"JSON文件: {self.json_file}")
- print()
-
- # 读取Excel配置
- config = self.read_excel_config()
- if not config:
- print("读取Excel配置失败")
- return False
-
- # 验证配置
- errors = self.validate_config(config)
- if errors:
- print("配置验证失败:")
- for error in errors:
- print(f" - {error}")
- return False
-
- print("配置验证通过")
-
- # 显示配置摘要
- print(f"\n配置摘要:")
- print(f" 技能类型数量: {len(config['skillTypes'])}")
- print(f" 技能组数量: {len(config['skillGroups'])}")
- print(f" 总组数: {config['totalGroups']}")
- print(f" 每组技能数: {config['skillsPerGroup']}")
-
- # 备份原文件
- if not self.backup_json():
- print("备份失败,是否继续?(y/n): ", end="")
- # 在GUI环境中,我们默认继续
- print("y (自动继续)")
-
- # 保存新配置
- if self.save_json_config(config):
- print("\n配置导入成功!")
- return True
- else:
- print("\n配置导入失败!")
- return False
- class ConfigManagerGUI:
- def __init__(self):
- self.root = tk.Tk()
- self.root.title("游戏配置管理工具")
- self.root.geometry("1000x700")
- self.root.resizable(True, True)
-
- # 配置文件路径 - 动态获取项目根目录
- # 从当前脚本位置向上查找项目根目录
- current_dir = Path(__file__).parent
- self.project_root = current_dir.parent.parent.parent.parent # 从excel目录向上4级到项目根目录
- self.excel_dir = current_dir
-
- # 手动配置变量
- self.selected_json_path = None
- self.format_type = 'horizontal' # 默认为横向表格
- self.multi_sheet = False
- self.sheet_names = []
- self.param_types = {}
-
- # 当前选择的配置映射
- self.current_mapping = None
- # 移除json_config_path,现在使用selected_json_path
- self.param_types = {}
-
- # 默认配置值
- self.default_config = {
- 'baseSpeed': 60,
- 'maxReflectionRandomness': 0.2,
- 'antiTrapTimeWindow': 5.0,
- 'antiTrapHitThreshold': 5,
- 'deflectionAttemptThreshold': 3,
- 'antiTrapDeflectionMultiplier': 3.0,
- 'FIRE_COOLDOWN': 0.05,
- 'ballRadius': 10,
- 'gravityScale': 0,
- 'linearDamping': 0,
- 'angularDamping': 0,
- 'colliderGroup': 2,
- 'colliderTag': 1,
- 'friction': 0,
- 'restitution': 1,
- 'safeDistance': 50,
- 'edgeOffset': 20,
- 'sensor': False
- }
-
- self.selected_files = []
- self.config_data = {}
-
- self.setup_ui()
- self.load_current_config()
-
- def setup_ui(self):
- """设置用户界面"""
- # 主框架
- main_frame = ttk.Frame(self.root, padding="10")
- main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
-
- # 配置根窗口的网格权重
- self.root.columnconfigure(0, weight=1)
- self.root.rowconfigure(0, weight=1)
- main_frame.columnconfigure(1, weight=1)
- main_frame.rowconfigure(2, weight=1)
-
- # 标题
- title_label = ttk.Label(main_frame, text="游戏配置管理工具",
- font=("Arial", 16, "bold"))
- title_label.grid(row=0, column=0, columnspan=3, pady=(0, 20))
-
- # 左侧面板 - 文件选择
- file_types_text = "Excel/CSV配置文件选择" if PANDAS_AVAILABLE else "CSV配置文件选择"
- left_frame = ttk.LabelFrame(main_frame, text=file_types_text, padding="10")
- left_frame.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(0, 10))
- left_frame.columnconfigure(0, weight=1)
- left_frame.rowconfigure(3, weight=1)
-
- # 文件浏览按钮
- browse_frame = ttk.Frame(left_frame)
- browse_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=(0, 10))
- browse_frame.columnconfigure(0, weight=1)
-
- browse_text = "浏览Excel/CSV文件" if PANDAS_AVAILABLE else "浏览CSV文件"
- ttk.Button(browse_frame, text=browse_text,
- command=self.browse_files).grid(row=0, column=0, sticky=(tk.W, tk.E))
- ttk.Button(browse_frame, text="扫描项目目录",
- command=self.scan_project_files).grid(row=0, column=1, padx=(10, 0))
-
- # JSON文件选择区域
- json_frame = ttk.LabelFrame(left_frame, text="JSON输出路径选择", padding="5")
- json_frame.grid(row=1, column=0, sticky=(tk.W, tk.E), pady=(10, 0))
- json_frame.columnconfigure(1, weight=1)
-
- ttk.Label(json_frame, text="输出路径:").grid(row=0, column=0, sticky=tk.W)
- self.json_path_var = tk.StringVar()
- self.json_path_entry = ttk.Entry(json_frame, textvariable=self.json_path_var, state='readonly')
- self.json_path_entry.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=(5, 0))
- ttk.Button(json_frame, text="浏览", command=self.browse_json_path).grid(row=0, column=2, padx=(5, 0))
-
- # 配置选项
- config_frame = ttk.LabelFrame(left_frame, text="配置选项", padding="5")
- config_frame.grid(row=2, column=0, sticky=(tk.W, tk.E), pady=(10, 0))
-
- # 表格格式选择
- ttk.Label(config_frame, text="表格格式:").grid(row=0, column=0, sticky=tk.W)
- self.format_var = tk.StringVar(value="horizontal")
- format_frame = ttk.Frame(config_frame)
- format_frame.grid(row=0, column=1, sticky=tk.W, padx=(5, 0))
- ttk.Radiobutton(format_frame, text="横向", variable=self.format_var, value="horizontal").pack(side=tk.LEFT)
- ttk.Radiobutton(format_frame, text="纵向", variable=self.format_var, value="vertical").pack(side=tk.LEFT, padx=(10, 0))
-
- # 多工作表选项
- self.multi_sheet_var = tk.BooleanVar()
- ttk.Checkbutton(config_frame, text="多工作表文件", variable=self.multi_sheet_var,
- command=self.on_multi_sheet_change).grid(row=1, column=0, columnspan=2, sticky=tk.W, pady=(5, 0))
-
- # 单个JSON选项
- self.single_json_var = tk.BooleanVar(value=True)
- ttk.Checkbutton(config_frame, text="单个json", variable=self.single_json_var,
- command=self.on_single_json_change).grid(row=2, column=0, columnspan=2, sticky=tk.W, pady=(5, 0))
-
- # 文件列表
- list_frame = ttk.Frame(left_frame)
- list_frame.grid(row=3, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
- list_frame.columnconfigure(0, weight=1)
- list_frame.rowconfigure(0, weight=1)
-
- self.file_listbox = tk.Listbox(list_frame, selectmode=tk.MULTIPLE, height=15)
- scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.file_listbox.yview)
- self.file_listbox.configure(yscrollcommand=scrollbar.set)
-
- self.file_listbox.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
- scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
-
- # 文件操作按钮
- file_btn_frame = ttk.Frame(left_frame)
- file_btn_frame.grid(row=4, column=0, sticky=(tk.W, tk.E), pady=(10, 0))
-
- ttk.Button(file_btn_frame, text="预览选中文件",
- command=self.preview_selected_files).pack(side=tk.LEFT)
- ttk.Button(file_btn_frame, text="清空选择",
- command=self.clear_selection).pack(side=tk.LEFT, padx=(10, 0))
-
- # 右侧面板 - 配置预览和操作
- right_frame = ttk.LabelFrame(main_frame, text="配置预览与操作", padding="10")
- right_frame.grid(row=1, column=1, sticky=(tk.W, tk.E, tk.N, tk.S))
- right_frame.columnconfigure(0, weight=1)
- right_frame.rowconfigure(0, weight=1)
-
- # 配置预览文本框
- self.preview_text = scrolledtext.ScrolledText(right_frame, height=20, width=50)
- self.preview_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10))
-
- # 操作按钮
- btn_frame = ttk.Frame(right_frame)
- btn_frame.grid(row=1, column=0, sticky=(tk.W, tk.E))
-
- ttk.Button(btn_frame, text="导入配置",
- command=self.import_config).pack(side=tk.LEFT)
- ttk.Button(btn_frame, text="导入技能配置",
- command=self.import_skill_config).pack(side=tk.LEFT, padx=(10, 0))
- ttk.Button(btn_frame, text="备份当前配置",
- command=self.backup_config).pack(side=tk.LEFT, padx=(10, 0))
- ttk.Button(btn_frame, text="恢复默认配置",
- command=self.restore_default_config).pack(side=tk.LEFT, padx=(10, 0))
-
- # 敌人配置专用按钮
- enemy_btn_frame = ttk.Frame(right_frame)
- enemy_btn_frame.grid(row=2, column=0, sticky=(tk.W, tk.E), pady=(10, 0))
-
- ttk.Button(enemy_btn_frame, text="修复敌人JSON",
- command=self.fix_enemies_json).pack(side=tk.LEFT)
- ttk.Button(enemy_btn_frame, text="自动导入敌人配置",
- command=self.auto_import_enemies_from_excel).pack(side=tk.LEFT, padx=(10, 0))
-
- # 底部状态栏
- self.status_var = tk.StringVar()
- self.status_var.set("就绪")
- status_bar = ttk.Label(main_frame, textvariable=self.status_var,
- relief=tk.SUNKEN, anchor=tk.W)
- status_bar.grid(row=2, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(10, 0))
-
- # 绑定事件
- self.file_listbox.bind('<<ListboxSelect>>', self.on_file_select)
-
- def browse_files(self):
- """浏览Excel/CSV文件"""
- if PANDAS_AVAILABLE:
- title = "选择Excel/CSV配置文件"
- filetypes = [("Excel文件", "*.xlsx *.xls"), ("CSV文件", "*.csv"), ("文本文件", "*.txt"), ("所有文件", "*.*")]
- else:
- title = "选择CSV配置文件"
- filetypes = [("CSV文件", "*.csv"), ("文本文件", "*.txt"), ("所有文件", "*.*")]
-
- files = filedialog.askopenfilenames(
- title=title,
- filetypes=filetypes,
- initialdir=str(self.excel_dir)
- )
-
- if files:
- self.file_listbox.delete(0, tk.END)
- for file in files:
- self.file_listbox.insert(tk.END, file)
- self.status_var.set(f"已选择 {len(files)} 个文件")
-
- def scan_project_files(self):
- """扫描项目目录中的Excel/CSV文件"""
- self.status_var.set("正在扫描项目目录...")
-
- def scan_thread():
- config_files = []
-
- # 扫描常见的配置目录
- scan_dirs = [
- self.project_root / "assets/resources/data",
- self.project_root / "assets/resources/config",
- self.project_root / "assets/excel",
- self.project_root / "config",
- self.project_root / "data",
- self.project_root
- ]
-
- # 根据pandas可用性选择扫描的文件类型
- patterns = ['*.xlsx', '*.xls', '*.csv', '*.txt'] if PANDAS_AVAILABLE else ['*.csv', '*.txt']
-
- for scan_dir in scan_dirs:
- if scan_dir.exists():
- for pattern in patterns:
- config_files.extend(scan_dir.rglob(pattern))
-
- # 更新UI
- self.root.after(0, self.update_file_list, config_files)
-
- threading.Thread(target=scan_thread, daemon=True).start()
-
- def browse_json_path(self):
- """浏览JSON输出路径"""
- # 根据用户选择的选项组合决定选择文件还是目录
- multi_sheet = self.multi_sheet_var.get()
- single_json = self.single_json_var.get()
-
- if multi_sheet and not single_json:
- # 多工作表文件且不是单个JSON:输出到目录(每个sheet一个JSON文件)
- path = filedialog.askdirectory(
- title="选择JSON输出目录",
- initialdir=str(self.project_root / "assets/resources/data")
- )
- else:
- # 其他情况:输出单个JSON文件
- path = filedialog.askopenfilename(
- title="选择JSON输出文件",
- filetypes=[("JSON文件", "*.json"), ("所有文件", "*.*")],
- initialdir=str(self.project_root / "assets/resources/data")
- )
-
- if path:
- self.json_path_var.set(path)
- self.selected_json_path = Path(path)
- self.status_var.set(f"已选择输出路径: {Path(path).name}")
-
- def on_multi_sheet_change(self):
- """多工作表选项变化处理"""
- # 清空当前选择的JSON路径,让用户重新选择
- self.json_path_var.set("")
-
- def on_single_json_change(self):
- """单个JSON选项变化处理"""
- # 清空当前选择的JSON路径,让用户重新选择
- self.json_path_var.set("")
- self.selected_json_path = None
-
- if self.multi_sheet_var.get():
- self.status_var.set("多工作表模式:请选择输出目录")
- else:
- self.status_var.set("单工作表模式:请选择输出JSON文件")
-
- def update_file_list(self, files):
- """更新文件列表"""
- self.file_listbox.delete(0, tk.END)
- for file in files:
- self.file_listbox.insert(tk.END, str(file))
-
- file_type_text = "Excel/CSV文件" if PANDAS_AVAILABLE else "CSV文件"
- self.status_var.set(f"找到 {len(files)} 个{file_type_text}")
-
- def on_file_select(self, event):
- """文件选择事件处理"""
- selection = self.file_listbox.curselection()
- if selection:
- self.selected_files = [self.file_listbox.get(i) for i in selection]
- # 根据选择的文件设置配置映射
- self._set_config_mapping_for_files()
- self.preview_selected_files()
- self.status_var.set(f"已选择 {len(selection)} 个文件")
-
-
- def _set_config_mapping_for_files(self):
- """根据选择的文件设置配置映射"""
- if not self.selected_files:
- return
-
- # 获取第一个文件名来判断配置类型
- first_file = Path(self.selected_files[0])
- filename = first_file.name.lower()
-
- # 根据文件名设置配置映射
- if '武器' in filename or 'weapon' in filename:
- self.current_mapping = {
- 'format_type': 'horizontal',
- 'param_types': {
- 'ID': str,
- '名称': str,
- '伤害': int,
- '射程': int,
- '射速': float,
- '弹药容量': int,
- '重装时间': float,
- '解锁等级': int,
- '价格': int,
- '描述': str,
- '最大等级': int,
- # 英文字段支持
- 'weaponId': str,
- 'name': str,
- 'damage': int,
- 'range': int,
- 'fireRate': float,
- 'ammoCapacity': int,
- 'reloadTime': float,
- 'unlockLevel': int,
- 'price': int,
- 'description': str,
- 'maxLevel': int
- }
- }
- elif '敌人' in filename or 'enemy' in filename:
- self.current_mapping = {
- 'format_type': 'multi_sheet',
- 'multi_sheet': True,
- 'param_types': {
- # 基础配置字段
- '敌人ID': ('id', str),
- '敌人名称': ('name', str),
- '敌人类型': ('type', str),
- '生命值': ('stats.health', int),
- '最大生命值': ('stats.maxHealth', int),
- '防御力': ('stats.defense', int),
- '移动速度': ('stats.speed', float),
-
- # 战斗配置字段
- '攻击伤害': ('combat.attackDamage', int),
- '攻击范围': ('combat.attackRange', int),
- '攻击速度': ('combat.attackSpeed', float),
- '能否格挡': ('combat.canBlock', bool),
- '格挡几率': ('combat.blockChance', float),
- '格挡伤害减免': ('combat.blockDamageReduction', float),
- '攻击冷却': ('combat.attackCooldown', float),
- '攻击类型': ('combat.attackType', str),
- '攻击延迟': ('combat.attackDelay', float),
- '武器类型': ('combat.weaponType', str),
- '投射物类型': ('combat.projectileType', str),
- '投射物速度': ('combat.projectileSpeed', float),
-
- # 移动配置字段
- '移动模式': ('movement.pattern', str),
- '巡逻范围': ('movement.patrolRange', int),
- '追击范围': ('movement.chaseRange', int),
- '旋转速度': ('movement.rotationSpeed', float),
- '移动类型': ('movement.moveType', str),
- '摆动幅度': ('movement.swingAmplitude', float),
- '摆动频率': ('movement.swingFrequency', float),
- '速度变化': ('movement.speedVariation', float),
-
- # 视觉配置字段
- '精灵路径': ('visualConfig.spritePath', str),
- '缩放比例': ('visualConfig.scale', float),
- '动画速度': ('visualConfig.animationSpeed', float),
- '水平翻转': ('visualConfig.flipX', bool),
- '待机动画': ('visualConfig.animations.idle', str),
- '行走动画': ('visualConfig.animations.walk', str),
- '攻击动画': ('visualConfig.animations.attack', str),
- '死亡动画': ('visualConfig.animations.death', str),
- '武器道具': ('visualConfig.weaponProp', str),
-
- # 音频配置字段
- '攻击音效': ('audioConfig.attackSound', str),
- '死亡音效': ('audioConfig.deathSound', str),
- '受击音效': ('audioConfig.hitSound', str),
- '行走音效': ('audioConfig.walkSound', str),
- '格挡音效': ('audioConfig.blockSound', str),
- '隐身音效': ('audioConfig.stealthSound', str),
- '护甲破碎音效': ('audioConfig.armorBreakSound', str),
- '引信音效': ('audioConfig.fuseSound', str),
-
- # 特殊能力配置字段
- '能力类型': ('specialAbilities.type', str),
- '伤害': ('specialAbilities.damage', int),
- '范围': ('specialAbilities.range', float),
- '冷却时间': ('specialAbilities.cooldown', float),
-
- # BOSS配置字段
- '是否BOSS': ('bossConfig.isBoss', bool),
- '阶段数': ('bossConfig.phases', int),
- '狂暴阈值': ('bossConfig.enrageThreshold', float),
- '狂暴伤害倍数': ('bossConfig.enrageDamageMultiplier', float),
- '狂暴速度倍数': ('bossConfig.enrageSpeedMultiplier', float),
-
- # 英文字段支持
- 'id': ('id', str),
- 'name': ('name', str),
- 'type': ('type', str),
- 'health': ('stats.health', int),
- 'maxHealth': ('stats.maxHealth', int),
- 'defense': ('stats.defense', int),
- 'speed': ('stats.speed', float),
- 'attackDamage': ('combat.attackDamage', int),
- 'attackRange': ('combat.attackRange', int),
- 'attackSpeed': ('combat.attackSpeed', float)
- }
- }
- elif '技能' in filename or 'skill' in filename:
- self.current_mapping = {
- 'format_type': 'horizontal',
- 'param_types': {
- 'ID': str,
- '名称': str,
- '伤害': int,
- '冷却时间': float,
- '消耗': int,
- '描述': str,
- # 英文字段支持
- 'skillId': str,
- 'skillName': str,
- 'damage': int,
- 'cooldown': float,
- 'cost': int,
- 'description': str
- }
- }
- elif '关卡' in filename or 'level' in filename:
- self.current_mapping = {
- 'format_type': 'horizontal',
- 'param_types': {
- 'ID': str,
- '名称': str,
- '敌人列表': str,
- '波数': int,
- '难度': int,
- # 英文字段支持
- 'levelId': str,
- 'name': str,
- 'enemies': str,
- 'waves': int,
- 'difficulty': int
- }
- }
- elif 'ballcontroller' in filename or '球控制' in filename or '标准配置表' in filename:
- # BallController配置映射(纵向格式:参数名-数值)
- self.current_mapping = {
- 'format_type': 'vertical',
- 'param_types': {
- 'baseSpeed': float,
- 'maxReflectionRandomness': float,
- 'antiTrapTimeWindow': float,
- 'antiTrapHitThreshold': int,
- 'deflectionAttemptThreshold': int,
- 'antiTrapDeflectionMultiplier': float,
- 'FIRE_COOLDOWN': float,
- 'ballRadius': float,
- 'gravityScale': float,
- 'linearDamping': float,
- 'angularDamping': float,
- 'colliderGroup': int,
- 'colliderTag': int,
- 'friction': float,
- 'restitution': float,
- 'safeDistance': float,
- 'edgeOffset': float,
- 'sensor': bool,
- 'maxAttempts': int
- }
- }
- else:
- # 默认配置映射
- self.current_mapping = {
- 'format_type': 'horizontal',
- 'param_types': {}
- }
-
- print(f"为文件 {filename} 设置配置映射: {self.current_mapping['format_type']} 格式")
- print(f"支持的参数类型: {list(self.current_mapping['param_types'].keys())}")
-
- def preview_selected_files(self):
- """预览选中的文件"""
- selection = self.file_listbox.curselection()
- if not selection:
- messagebox.showwarning("警告", "请先选择要预览的文件")
- return
-
- self.preview_text.delete(1.0, tk.END)
- self.config_data = {}
-
- for i in selection:
- file_path = Path(self.file_listbox.get(i))
- self.preview_text.insert(tk.END, f"=== {file_path.name} ===\n")
-
- try:
- # 根据文件扩展名和pandas可用性选择读取方法
- if file_path.suffix.lower() in ['.xlsx', '.xls']:
- if PANDAS_AVAILABLE:
- file_config = self.read_excel_config(file_path)
- else:
- self.preview_text.insert(tk.END, "Excel文件需要pandas库支持,请安装: pip install pandas openpyxl\n\n")
- continue
- elif file_path.suffix.lower() in ['.csv', '.txt']:
- file_config = self.read_csv_config(file_path)
- else:
- self.preview_text.insert(tk.END, "不支持的文件格式\n\n")
- continue
-
- if file_config:
- # 检查file_config的类型,如果是列表则特殊处理
- if isinstance(file_config, list):
- # 对于关卡配置等返回列表的情况
- self.preview_text.insert(tk.END, f"找到 {len(file_config)} 个配置项:\n")
- for i, item in enumerate(file_config):
- self.preview_text.insert(tk.END, f" 配置项 {i+1}: {item}\n")
- # 将列表数据存储到config_data中
- self.config_data[file_path.stem] = file_config
- else:
- # 原有的字典处理逻辑
- self.config_data.update(file_config)
-
- # 显示预览
- self.preview_text.insert(tk.END, f"找到 {len(file_config)} 个配置参数:\n")
- for key, value in file_config.items():
- self.preview_text.insert(tk.END, f" {key}: {value}\n")
- else:
- self.preview_text.insert(tk.END, "未找到有效的配置数据\n")
-
- except Exception as e:
- self.preview_text.insert(tk.END, f"读取失败: {str(e)}\n")
-
- self.preview_text.insert(tk.END, "\n")
-
- # 显示合并后的配置
- if self.config_data:
- self.preview_text.insert(tk.END, "=== 合并后的配置 ===\n")
- self.preview_text.insert(tk.END, json.dumps(self.config_data, indent=2, ensure_ascii=False))
-
- self.status_var.set(f"预览完成,共 {len(self.config_data)} 个配置参数")
-
- def read_excel_config(self, file_path):
- """读取Excel配置文件"""
- config = {}
-
- if not PANDAS_AVAILABLE:
- print("错误: pandas未安装,无法读取Excel文件")
- return config
-
- try:
- # 根据用户选择的"多工作表文件"选项决定处理方式
- if self.multi_sheet_var.get():
- # 多工作表模式:读取所有工作表
- print(f"多工作表模式处理文件: {file_path.name}")
-
- # 获取所有工作表名称(避免缓存问题)
- try:
- # 先尝试读取第一个工作表来获取所有工作表名称
- excel_file = pd.ExcelFile(file_path)
- sheet_names = excel_file.sheet_names
- excel_file.close() # 关闭文件句柄
- print(f"发现工作表: {sheet_names}")
- except Exception as e:
- print(f"获取工作表名称失败: {e}")
- return config
-
- all_sheets_data = {}
- for sheet_name in sheet_names:
- try:
- # 每次都重新读取文件,避免缓存
- df = pd.read_excel(file_path, sheet_name=sheet_name, engine='openpyxl')
- all_sheets_data[sheet_name] = df
- print(f"成功读取工作表: {sheet_name}, 数据行数: {len(df)}")
- # 打印第一行数据用于调试
- if len(df) > 0 and sheet_name == '敌人基础配置':
- print(f"敌人基础配置第一行数据: {df.iloc[0].to_dict()}")
- except Exception as e:
- print(f"读取工作表 {sheet_name} 时出错: {e}")
-
- # 根据工作表内容判断配置类型
- if any('关卡' in name for name in sheet_names):
- config = self.parse_level_multi_sheet_data(all_sheets_data, file_path.name)
- elif any('技能' in name for name in sheet_names):
- config = self.parse_skill_multi_sheet_data(all_sheets_data, file_path.name)
- elif any('敌人' in name for name in sheet_names):
- config = self.parse_enemy_multi_sheet_data(all_sheets_data, file_path.name)
- elif any('武器' in name for name in sheet_names):
- config = self.parse_weapon_multi_sheet_data(all_sheets_data, file_path.name)
- else:
- # 通用多工作表处理
- config = self.parse_multi_sheet_data(all_sheets_data, file_path.name)
- else:
- # 单工作表模式:读取第一个工作表或指定工作表
- print(f"单工作表模式处理文件: {file_path.name}")
-
- sheet_name = None
- if self.current_mapping and 'sheet_name' in self.current_mapping:
- sheet_name = self.current_mapping['sheet_name']
-
- # 读取Excel文件
- if sheet_name:
- df = pd.read_excel(file_path, sheet_name=sheet_name)
- print(f"读取指定工作表: {sheet_name}, 数据行数: {len(df)}")
- else:
- df = pd.read_excel(file_path)
- print(f"读取默认工作表, 数据行数: {len(df)}")
-
- config = self.parse_config_data(df, file_path.name)
-
- except Exception as e:
- print(f"读取Excel文件 {file_path.name} 时出错: {e}")
-
- return config
-
- def read_csv_config(self, file_path):
- """读取CSV配置文件"""
- config = {}
-
- try:
- # 如果pandas可用,优先使用pandas读取CSV(支持更多格式)
- if PANDAS_AVAILABLE:
- try:
- df = pd.read_csv(file_path)
- config = self.parse_config_data(df, file_path.name)
- except:
- # 如果pandas读取失败,回退到原始CSV读取方法
- config = self.read_csv_fallback(file_path)
- else:
- # 如果pandas不可用,直接使用原始CSV读取方法
- config = self.read_csv_fallback(file_path)
-
- except Exception as e:
- print(f"读取文件 {file_path.name} 时出错: {e}")
-
- return config
-
- def read_csv_fallback(self, file_path):
- """原始CSV读取方法(不依赖pandas)"""
- config = {}
-
- try:
- with open(file_path, 'r', encoding='utf-8') as f:
- reader = csv.reader(f)
- for row_num, row in enumerate(reader, 1):
- if len(row) < 2:
- continue
-
- param_name = row[0].strip()
- param_value = row[1].strip()
-
- # 跳过标题行
- if param_name in ['参数名', 'parameter', 'name']:
- continue
-
- # 检查参数是否有效
- if param_name in self.param_types:
- try:
- param_type = self.param_types[param_name]
- if param_type == bool:
- config[param_name] = param_value.lower() in ['true', '1', 'yes', 'on']
- else:
- config[param_name] = param_type(param_value)
- except (ValueError, TypeError):
- continue
- except Exception as e:
- print(f"读取CSV文件 {file_path.name} 时出错: {e}")
-
- return config
-
- def parse_level_multi_sheet_data(self, all_sheets_data, filename):
- """解析关卡配置的多工作表数据"""
- config = []
-
- try:
- # 获取各个工作表的数据(支持中英文工作表名)
- basic_config = None
- for sheet_name in ['关卡基础配置', 'Level Config', 'levels', '关卡配置']:
- if sheet_name in all_sheets_data:
- basic_config = all_sheets_data[sheet_name]
- break
-
- weapon_config = None
- for sheet_name in ['关卡武器配置', 'Level Weapon Config', 'level_weapons', '武器配置']:
- if sheet_name in all_sheets_data:
- weapon_config = all_sheets_data[sheet_name]
- break
-
- wave_config = None
- for sheet_name in ['关卡波次配置', 'Wave Config', 'waves', '波次配置']:
- if sheet_name in all_sheets_data:
- wave_config = all_sheets_data[sheet_name]
- break
-
- enemy_config = None
- for sheet_name in ['敌人详细配置', 'Enemy Detail Config', 'enemy_details', '敌人配置']:
- if sheet_name in all_sheets_data:
- enemy_config = all_sheets_data[sheet_name]
- break
-
- if basic_config is None:
- print("错误: 未找到关卡基础配置工作表(支持的工作表名: 关卡基础配置, Level Config, levels, 关卡配置)")
- return config
-
- # 处理每个关卡
- for _, basic_row in basic_config.iterrows():
- if pd.isna(basic_row['关卡ID']):
- continue
-
- level_id = str(basic_row['关卡ID'])
-
- # 获取武器配置
- available_weapons = []
- if weapon_config is not None:
- weapon_rows = weapon_config[weapon_config['关卡ID'] == level_id]
- for _, weapon_row in weapon_rows.iterrows():
- if pd.notna(weapon_row['可用武器']):
- weapons_str = str(weapon_row['可用武器'])
- # 支持多种分隔符
- import re
- weapons = re.split(r'[、,,;;]', weapons_str)
- available_weapons.extend([w.strip() for w in weapons if w.strip()])
-
- level_data = {
- 'levelId': level_id,
- 'name': str(basic_row['关卡名称']) if pd.notna(basic_row['关卡名称']) else '',
- 'scene': str(basic_row['场景']) if pd.notna(basic_row['场景']) else 'grassland',
- 'description': str(basic_row['描述']) if pd.notna(basic_row['描述']) else '',
- 'backgroundImage': str(basic_row['关卡背景图路径']) if pd.notna(basic_row['关卡背景图路径']) else 'images/LevelBackground/BG1',
- 'availableWeapons': available_weapons,
- 'coinReward': int(basic_row['钞票奖励']) if pd.notna(basic_row['钞票奖励']) else 100,
- 'diamondReward': int(basic_row['钻石奖励']) if pd.notna(basic_row['钻石奖励']) else 0,
- 'timeLimit': 300, # 默认值
- 'difficulty': 'normal', # 默认值
- 'healthMultiplier': float(basic_row['生命倍数']) if pd.notna(basic_row['生命倍数']) else 1.0, # 从Excel读取生命倍数
- 'waves': []
- }
-
- # 获取该关卡的波次配置
- if wave_config is not None:
- level_waves = wave_config[wave_config['关卡ID'] == level_id]
-
- for _, wave_row in level_waves.iterrows():
- wave_id = int(wave_row['波次ID']) if pd.notna(wave_row['波次ID']) else 1
-
- # 获取该波次的敌人配置
- wave_enemies = []
- if enemy_config is not None:
- wave_enemy_data = enemy_config[
- (enemy_config['关卡ID'] == level_id) &
- (enemy_config['波次ID'] == wave_id)
- ]
-
- for _, enemy_row in wave_enemy_data.iterrows():
- enemy_type = str(enemy_row['敌人类型']) if pd.notna(enemy_row['敌人类型']) else '普通僵尸'
-
- enemy_data = {
- 'enemyType': enemy_type,
- 'count': int(enemy_row['数量']) if pd.notna(enemy_row['数量']) else 1,
- 'spawnInterval': float(enemy_row['生成间隔']) if pd.notna(enemy_row['生成间隔']) else 2.0,
- 'spawnDelay': float(enemy_row['生成延迟']) if pd.notna(enemy_row['生成延迟']) else 0.0,
- 'characteristics': str(enemy_row['特征描述']) if pd.notna(enemy_row['特征描述']) else ''
- }
- wave_enemies.append(enemy_data)
-
- wave_data = {
- 'waveId': wave_id,
- 'enemies': wave_enemies
- }
- level_data['waves'].append(wave_data)
-
- config.append(level_data)
- print(f"处理关卡: {level_id}, 波次数: {len(level_data['waves'])}")
-
- print(f"成功解析关卡配置,共 {len(config)} 个关卡")
-
- except Exception as e:
- print(f"解析关卡多工作表数据时出错: {e}")
- import traceback
- traceback.print_exc()
-
- return config
-
- def parse_skill_multi_sheet_data(self, all_sheets_data, filename):
- """解析技能配置的多工作表数据"""
- skills_config = []
-
- try:
- # 检查是否为局外技能配置表格式(包含技能类型配置、技能组配置、基本配置)
- if '技能类型配置' in all_sheets_data and '技能组配置' in all_sheets_data and '基本配置' in all_sheets_data:
- print("检测到局外技能配置表格式,使用集成的技能配置导入工具处理")
-
- # 使用集成的SkillConfigImporter处理
- try:
- skill_importer = SkillConfigImporter(self.excel_dir)
- success = skill_importer.import_config()
- if success:
- print("技能配置导入成功!")
- # 返回成功标识,让调用者知道已经处理完成
- return [{'status': 'imported', 'message': '技能配置已成功导入到skill_config.json'}]
- else:
- print("技能配置导入失败")
- return [{'status': 'error', 'message': '技能配置导入失败'}]
- except Exception as e:
- print(f"技能配置导入过程中出错: {e}")
- return [{'status': 'error', 'message': f'技能配置导入出错: {e}'}]
-
- return skills_config
-
- # 获取技能信息表数据(支持中英文工作表名)
- skill_info = None
- for sheet_name in ['技能信息表', 'Skill Config', 'skills', '技能配置']:
- if sheet_name in all_sheets_data:
- skill_info = all_sheets_data[sheet_name]
- break
-
- if skill_info is None:
- print("错误: 未找到技能信息表工作表(支持的工作表名: 技能信息表, Skill Config, skills, 技能配置)")
- return skills_config
-
- # 处理每个技能(支持中英文字段名)
- for _, skill_row in skill_info.iterrows():
- # 获取技能ID(支持中英文字段名)
- skill_id_field = None
- for field in ['id', '技能ID', 'skill_id', 'ID']:
- if field in skill_row and pd.notna(skill_row[field]):
- skill_id_field = field
- break
-
- if skill_id_field is None:
- continue
-
- def get_skill_field_value(row, cn_field, en_fields, default_value, value_type=str):
- """获取技能字段值,支持中英文字段名"""
- for field in [cn_field] + (en_fields if isinstance(en_fields, list) else [en_fields]):
- if field in row and pd.notna(row[field]):
- try:
- return value_type(row[field])
- except (ValueError, TypeError):
- continue
- return default_value
-
- skill_data = {
- 'id': str(skill_row[skill_id_field]),
- 'name': get_skill_field_value(skill_row, '技能名称', ['name', 'skill_name'], ''),
- 'description': get_skill_field_value(skill_row, '技能描述', ['description', 'desc'], ''),
- 'iconPath': get_skill_field_value(skill_row, '图标路径', ['iconPath', 'icon_path', 'icon'], ''),
- 'maxLevel': get_skill_field_value(skill_row, '最大等级', ['maxLevel', 'max_level'], 5, int),
- 'currentLevel': get_skill_field_value(skill_row, '当前等级', ['currentLevel', 'current_level'], 0, int),
- 'priceReduction': get_skill_field_value(skill_row, '价格减少', ['priceReduction', 'price_reduction'], 0.0, float),
- 'critChanceIncrease': get_skill_field_value(skill_row, '暴击几率增加', ['critChanceIncrease', 'crit_chance_increase'], 0.0, float),
- 'critDamageBonus': get_skill_field_value(skill_row, '暴击伤害加成', ['critDamageBonus', 'crit_damage_bonus'], 0.0, float),
- 'healthIncrease': get_skill_field_value(skill_row, '生命值增加', ['healthIncrease', 'health_increase'], 0.0, float),
- 'multiShotChance': get_skill_field_value(skill_row, '多重射击几率', ['multiShotChance', 'multi_shot_chance'], 0.0, float),
- 'energyGainIncrease': get_skill_field_value(skill_row, '能量加成', ['energyGainIncrease', 'energy_gain_increase'], 0.0, float),
- 'ballSpeedIncrease': get_skill_field_value(skill_row, '速度提升', ['ballSpeedIncrease', 'ball_speed_increase'], 0.0, float)
- }
-
- skills_config.append(skill_data)
- print(f"处理技能: {skill_data['id']} - {skill_data['name']}")
-
- print(f"成功解析技能配置,共 {len(skills_config)} 个技能")
-
- except Exception as e:
- print(f"解析技能多工作表数据时出错: {e}")
- import traceback
- traceback.print_exc()
-
- return skills_config
-
- def parse_enemy_multi_sheet_data(self, all_sheets_data, filename):
- """解析敌人配置的多工作表数据"""
- enemies_config = []
-
- try:
- # 获取敌人基础配置数据(支持中英文工作表名)
- enemy_info = None
- for sheet_name in ['敌人基础配置', 'Enemy Config', 'enemies', '敌人配置']:
- if sheet_name in all_sheets_data:
- enemy_info = all_sheets_data[sheet_name]
- break
-
- if enemy_info is None:
- print("错误: 未找到敌人基础配置工作表(支持的工作表名: 敌人基础配置, Enemy Config, enemies, 敌人配置)")
- return enemies_config
-
- # 获取其他配置工作表
- combat_sheet = all_sheets_data.get('战斗配置')
- movement_sheet = all_sheets_data.get('移动配置')
- visual_sheet = all_sheets_data.get('视觉配置')
- audio_sheet = all_sheets_data.get('音频配置')
- special_sheet = all_sheets_data.get('特殊能力配置')
- boss_sheet = all_sheets_data.get('BOSS配置')
-
- def get_field_value(row, cn_field, en_fields, default_value, value_type=str):
- """获取字段值,支持中英文字段名"""
- for field in [cn_field] + (en_fields if isinstance(en_fields, list) else [en_fields]):
- if field in row and pd.notna(row[field]):
- try:
- return value_type(row[field])
- except (ValueError, TypeError):
- continue
- return default_value
-
- def find_row_by_id(sheet, enemy_id):
- """根据敌人ID查找对应行"""
- if sheet is None:
- return None
- for _, row in sheet.iterrows():
- if str(row.get('敌人ID', '')) == enemy_id:
- return row
- return None
-
- # 处理每个敌人(支持中英文字段名)
- for _, enemy_row in enemy_info.iterrows():
- # 获取敌人ID(支持中英文字段名)
- enemy_id_field = None
- for field in ['id', '敌人ID', 'enemy_id', 'ID']:
- if field in enemy_row and pd.notna(enemy_row[field]):
- enemy_id_field = field
- break
-
- if enemy_id_field is None:
- continue
-
- enemy_id = str(enemy_row[enemy_id_field])
-
- # 基础配置(不包含攻击伤害,攻击伤害从战斗配置表获取)
- enemy_data = {
- 'id': enemy_id,
- 'name': get_field_value(enemy_row, '敌人名称', ['name', 'enemy_name'], ''),
- 'type': get_field_value(enemy_row, '敌人类型', ['type', 'enemy_type'], 'basic'),
- 'health': get_field_value(enemy_row, '生命值', ['health', 'hp'], 100, int),
- 'speed': get_field_value(enemy_row, '移动速度', ['speed', 'move_speed'], 1.0, float),
- 'attackRange': get_field_value(enemy_row, '攻击范围', ['attackRange', 'attack_range', 'range'], 1.0, float),
- 'attackSpeed': get_field_value(enemy_row, '攻击速度', ['attackSpeed', 'attack_speed'], 1.0, float),
- 'defense': get_field_value(enemy_row, '防御力', ['defense'], 0, int),
- 'attackDamage': 10 # 默认值,将从战斗配置表覆盖
- }
-
- # 获取战斗配置
- combat_row = find_row_by_id(combat_sheet, enemy_id)
- if combat_row is not None:
- enemy_data.update({
- 'attackDamage': get_field_value(combat_row, '攻击伤害', ['attackDamage', 'attack'], 10, int),
- 'attackType': get_field_value(combat_row, '攻击类型', ['attackType'], 'melee'),
- 'attackDelay': get_field_value(combat_row, '攻击延迟', ['attackDelay'], 0.5, float),
- 'weaponType': get_field_value(combat_row, '武器类型', ['weaponType'], 'none'),
- 'projectileType': get_field_value(combat_row, '投射物类型', ['projectileType'], 'none'),
- 'projectileSpeed': get_field_value(combat_row, '投射物速度', ['projectileSpeed'], 100, float),
- 'canBlock': get_field_value(combat_row, '能否格挡', ['canBlock'], False, bool),
- 'blockChance': get_field_value(combat_row, '格挡几率', ['blockChance'], 0.0, float),
- 'blockDamageReduction': get_field_value(combat_row, '格挡伤害减免', ['blockDamageReduction'], 0.5, float)
- })
-
- # 获取移动配置
- movement_row = find_row_by_id(movement_sheet, enemy_id)
- if movement_row is not None:
- enemy_data.update({
- 'movementPattern': get_field_value(movement_row, '移动模式', ['movementPattern'], 'walk_forward'),
- 'patrolRange': get_field_value(movement_row, '巡逻范围', ['patrolRange'], 100, int),
- 'chaseRange': get_field_value(movement_row, '追击范围', ['chaseRange'], 200, int),
- 'rotationSpeed': get_field_value(movement_row, '旋转速度', ['rotationSpeed'], 180, float),
- 'moveType': get_field_value(movement_row, '移动类型', ['moveType'], 'straight'),
- 'swingAmplitude': get_field_value(movement_row, '摆动幅度', ['swingAmplitude'], 0.0, float),
- 'swingFrequency': get_field_value(movement_row, '摆动频率', ['swingFrequency'], 0.0, float),
- 'speedVariation': get_field_value(movement_row, '速度变化', ['speedVariation'], 0.1, float)
- })
-
- # 获取视觉配置
- visual_row = find_row_by_id(visual_sheet, enemy_id)
- if visual_row is not None:
- enemy_data.update({
- 'spritePath': get_field_value(visual_row, '精灵路径', ['spritePath'], f'enemies/{enemy_id}'),
- 'scale': get_field_value(visual_row, '缩放比例', ['scale'], 1.0, float),
- 'animationSpeed': get_field_value(visual_row, '动画速度', ['animationSpeed'], 1.0, float),
- 'flipX': get_field_value(visual_row, '水平翻转', ['flipX'], False, bool),
- 'idleAnimation': get_field_value(visual_row, '待机动画', ['idleAnimation'], 'idle'),
- 'walkAnimation': get_field_value(visual_row, '行走动画', ['walkAnimation'], 'walk'),
- 'attackAnimation': get_field_value(visual_row, '攻击动画', ['attackAnimation'], 'attack'),
- 'deathAnimation': get_field_value(visual_row, '死亡动画', ['deathAnimation'], 'dead'),
- 'weaponProp': get_field_value(visual_row, '武器道具', ['weaponProp'], '')
- })
-
- # 获取音频配置
- audio_row = find_row_by_id(audio_sheet, enemy_id)
- if audio_row is not None:
- enemy_data.update({
- 'attackSound': get_field_value(audio_row, '攻击音效', ['attackSound'], 'enemy_attack'),
- 'deathSound': get_field_value(audio_row, '死亡音效', ['deathSound'], 'enemy_death'),
- 'hitSound': get_field_value(audio_row, '受击音效', ['hitSound'], 'enemy_hit'),
- 'walkSound': get_field_value(audio_row, '行走音效', ['walkSound'], ''),
- 'blockSound': get_field_value(audio_row, '格挡音效', ['blockSound'], ''),
- 'stealthSound': get_field_value(audio_row, '隐身音效', ['stealthSound'], ''),
- 'armorBreakSound': get_field_value(audio_row, '护甲破碎音效', ['armorBreakSound'], ''),
- 'fuseSound': get_field_value(audio_row, '引信音效', ['fuseSound'], '')
- })
-
- # 获取特殊能力配置
- special_abilities = []
- if special_sheet is not None:
- for _, special_row in special_sheet.iterrows():
- if str(special_row.get('敌人ID', '')) == enemy_id:
- ability_type = get_field_value(special_row, '能力类型', ['abilityType'], '')
- if ability_type: # 只添加有效的特殊能力
- special_abilities.append({
- 'type': ability_type,
- 'damage': get_field_value(special_row, '伤害', ['damage'], 0, int),
- 'range': get_field_value(special_row, '范围', ['range'], 0, float),
- 'cooldown': get_field_value(special_row, '冷却时间', ['cooldown'], 0, float)
- })
- if special_abilities:
- enemy_data['specialAbilities'] = special_abilities
-
- # 获取BOSS配置
- boss_row = find_row_by_id(boss_sheet, enemy_id)
- if boss_row is not None:
- is_boss = get_field_value(boss_row, '是否BOSS', ['isBoss'], False, bool)
- if is_boss:
- enemy_data['bossConfig'] = {
- 'isBoss': True,
- 'phases': get_field_value(boss_row, '阶段数', ['phases'], 1, int),
- 'enrageThreshold': get_field_value(boss_row, '狂暴阈值', ['enrageThreshold'], 0.3, float),
- 'enrageDamageMultiplier': get_field_value(boss_row, '狂暴伤害倍数', ['enrageDamageMultiplier'], 1.5, float),
- 'enrageSpeedMultiplier': get_field_value(boss_row, '狂暴速度倍数', ['enrageSpeedMultiplier'], 1.3, float)
- }
-
- # 转换为嵌套结构
- nested_enemy_data = {
- 'id': enemy_data['id'],
- 'name': enemy_data['name'],
- 'type': enemy_data['type'],
- 'stats': {
- 'health': enemy_data.get('health', 100),
- 'maxHealth': enemy_data.get('health', 100),
- 'defense': enemy_data.get('defense', 0),
- 'speed': enemy_data.get('speed', 50)
- },
- 'movement': {
- 'pattern': enemy_data.get('movementPattern', 'direct'),
- 'speed': enemy_data.get('speed', 50),
- 'patrolRange': enemy_data.get('patrolRange', 100),
- 'chaseRange': enemy_data.get('chaseRange', 200),
- 'rotationSpeed': enemy_data.get('rotationSpeed', 180),
- 'moveType': enemy_data.get('moveType', 'straight'),
- 'swingAmplitude': enemy_data.get('swingAmplitude', 0.0),
- 'swingFrequency': enemy_data.get('swingFrequency', 0.0),
- 'speedVariation': enemy_data.get('speedVariation', 0.1)
- },
- 'combat': {
- 'attackDamage': enemy_data.get('attackDamage', 10),
- 'attackRange': enemy_data.get('attackRange', 100),
- 'attackSpeed': enemy_data.get('attackSpeed', 1.0),
- 'canBlock': enemy_data.get('canBlock', False),
- 'blockChance': enemy_data.get('blockChance', 0.0),
- 'blockDamageReduction': enemy_data.get('blockDamageReduction', 0.5),
- 'attackCooldown': enemy_data.get('attackCooldown', 1.0),
- 'attackType': enemy_data.get('attackType', 'melee'),
- 'attackDelay': enemy_data.get('attackDelay', 0.5),
- 'weaponType': enemy_data.get('weaponType', 'none'),
- 'projectileType': enemy_data.get('projectileType', 'none'),
- 'projectileSpeed': enemy_data.get('projectileSpeed', 100.0)
- },
- 'visualConfig': {
- 'spritePath': enemy_data.get('spritePath', f'Animation/EnemyAni/001'),
- 'scale': enemy_data.get('scale', 1.0),
- 'animationSpeed': enemy_data.get('animationSpeed', 1.0),
- 'flipX': enemy_data.get('flipX', False),
- 'tint': enemy_data.get('tint', '#FFFFFF'),
- 'animations': {
- 'idle': 'idle',
- 'walk': 'walk',
- 'attack': 'attack',
- 'death': 'dead'
- },
- 'weaponProp': enemy_data.get('weaponProp', '')
- },
- 'audioConfig': {
- 'attackSound': enemy_data.get('attackSound', 'enemy_attack'),
- 'deathSound': enemy_data.get('deathSound', 'enemy_death'),
- 'hitSound': enemy_data.get('hitSound', 'enemy_hit'),
- 'walkSound': enemy_data.get('walkSound', ''),
- 'blockSound': enemy_data.get('blockSound', ''),
- 'stealthSound': enemy_data.get('stealthSound', ''),
- 'armorBreakSound': enemy_data.get('armorBreakSound', ''),
- 'fuseSound': enemy_data.get('fuseSound', ''),
- 'volume': enemy_data.get('volume', 1.0)
- }
- }
-
- # 添加特殊能力配置
- if 'specialAbilities' in enemy_data:
- nested_enemy_data['specialAbilities'] = enemy_data['specialAbilities']
-
- # 添加BOSS配置
- if 'bossConfig' in enemy_data:
- nested_enemy_data['bossConfig'] = enemy_data['bossConfig']
-
- enemies_config.append(nested_enemy_data)
- print(f"处理敌人: {enemy_data['id']} - {enemy_data['name']}")
- print(f"敌人 {enemy_data['id']} 的生命值: {nested_enemy_data['stats']['health']}")
-
- print(f"成功解析敌人配置,共 {len(enemies_config)} 个敌人")
-
- except Exception as e:
- print(f"解析敌人多工作表数据时出错: {e}")
- import traceback
- traceback.print_exc()
-
- return enemies_config
-
- def parse_multi_sheet_data(self, all_sheets_data, filename):
- """解析通用多工作表数据"""
- # 处理武器配置表的多工作表
- if '方块武器配置表' in filename:
- return self.parse_weapon_multi_sheet_data(all_sheets_data, filename)
-
- # 处理敌人配置表的多工作表
- if '敌人配置表' in filename or '敌人' in filename:
- return self.parse_enemy_multi_sheet_data(all_sheets_data, filename)
-
- # 这里可以添加其他多工作表文件的处理逻辑
- # 目前只返回第一个工作表的数据
- if all_sheets_data:
- first_sheet_name = list(all_sheets_data.keys())[0]
- first_sheet_data = all_sheets_data[first_sheet_name]
- return self.parse_config_data(first_sheet_data, filename)
- return []
-
- def parse_config_data(self, df, filename):
- """解析配置数据(支持多种格式,需要pandas)"""
- config = {}
-
- if not PANDAS_AVAILABLE or not self.current_mapping:
- return config
-
- format_type = self.current_mapping['format_type']
-
- try:
- if format_type == 'vertical':
- # 纵向表格:参数名在第一列,值在第二/三列
- for _, row in df.iterrows():
- param_name = str(row.iloc[0]).strip()
-
- # 跳过标题行和无效行
- if param_name in ['参数名', 'parameter', 'name', 'nan', '球控制器参数'] or param_name == 'nan':
- continue
-
- # 检查参数是否有效
- param_types = self.current_mapping.get('param_types', {})
- if param_name in param_types:
- try:
- # 优先使用第3列(默认值),如果不存在则使用第2列
- param_value = row.iloc[2] if len(row) > 2 and not pd.isna(row.iloc[2]) else row.iloc[1]
-
- if pd.isna(param_value):
- continue
-
- param_type = param_types[param_name]
- if param_type == bool:
- config[param_name] = str(param_value).lower() in ['true', '1', 'yes', 'on']
- else:
- config[param_name] = param_type(param_value)
- except (ValueError, TypeError, IndexError):
- continue
-
- elif format_type == 'horizontal':
- # 横向表格:第一行是参数名(列名),数据从第0行开始
- print(f"横向表格解析: 总行数={len(df)}")
- print(f"表格列名: {list(df.columns)}")
-
- # 打印前几行数据用于调试
- for i in range(min(3, len(df))):
- print(f"第{i}行数据: {df.iloc[i].to_dict()}")
-
- # 检查第0行是否为有效数据行
- # 如果第0行第一个单元格是描述性文字或空值,则跳过
- data_start_row = 0
- if len(df) > 0:
- first_cell = str(df.iloc[0, 0]).strip().lower()
- # 跳过描述行、空行或标题行
- if (first_cell in ['唯一标识符', '描述', 'description', 'desc', 'nan', ''] or
- first_cell == df.columns[0].lower()):
- data_start_row = 1
- print(f"跳过第0行(描述行或标题行): {first_cell}")
-
- print(f"数据起始行: {data_start_row}")
-
- # 解析多行数据(如敌人配置、武器配置、关卡配置等)
- config_list = []
- for i in range(data_start_row, len(df)):
- row_config = {}
- row_has_data = False
-
- param_types = self.current_mapping.get('param_types', {})
- print(f"可用的参数类型: {list(param_types.keys())}")
- print(f"当前配置映射: {self.current_mapping}")
- print(f"DataFrame列名: {list(df.columns)}")
-
- # 如果参数类型为空,尝试从DataFrame列名自动推断
- if not param_types:
- print("参数类型为空,尝试自动推断...")
- for col_name in df.columns:
- col_str = str(col_name).strip()
- if col_str and col_str != 'nan':
- # 根据列名推断数据类型
- if any(keyword in col_str for keyword in ['ID', 'id', '编号']):
- param_types[col_str] = str
- elif any(keyword in col_str for keyword in ['名称', 'name', '名字']):
- param_types[col_str] = str
- elif any(keyword in col_str for keyword in ['伤害', 'damage', '攻击', 'attack']):
- param_types[col_str] = int
- elif any(keyword in col_str for keyword in ['射程', 'range', '范围']):
- param_types[col_str] = int
- elif any(keyword in col_str for keyword in ['射速', 'fireRate', '频率']):
- param_types[col_str] = float
- elif any(keyword in col_str for keyword in ['价格', 'price', '金币', 'cost']):
- param_types[col_str] = int
- elif any(keyword in col_str for keyword in ['等级', 'level', 'Level']):
- param_types[col_str] = int
- elif any(keyword in col_str for keyword in ['时间', 'time', 'Time']):
- param_types[col_str] = float
- elif any(keyword in col_str for keyword in ['描述', 'description', '说明']):
- param_types[col_str] = str
- else:
- # 默认为字符串类型
- param_types[col_str] = str
-
- # 更新配置映射
- if param_types:
- self.current_mapping['param_types'] = param_types
- print(f"自动推断的参数类型: {param_types}")
-
- for col_idx, col_name in enumerate(df.columns):
- param_name = str(col_name).strip()
- print(f"检查列名: '{param_name}' 是否在参数类型中")
- if param_name in param_types:
- try:
- param_value = df.iloc[i, col_idx]
- if pd.isna(param_value) or str(param_value).strip() == '':
- print(f"跳过空值: {param_name} = {param_value}")
- continue
-
- param_type = param_types[param_name]
- if param_type == bool:
- row_config[param_name] = str(param_value).lower() in ['true', '1', 'yes', 'on']
- else:
- row_config[param_name] = param_type(param_value)
- row_has_data = True
- print(f"成功转换字段: {param_name} = {param_value} ({param_type})")
- except (ValueError, TypeError, IndexError) as e:
- print(f"转换字段 {param_name} 时出错: {e}")
- continue
- else:
- print(f"字段 '{param_name}' 不在参数类型定义中,跳过")
-
- if row_config and row_has_data: # 只添加非空且有有效数据的配置
- config_list.append(row_config)
- print(f"成功解析第{i}行,包含{len(row_config)}个字段")
- else:
- print(f"跳过第{i}行(无有效数据)")
-
- print(f"总共解析出{len(config_list)}个有效配置项")
-
- # 对于横向表格,返回配置列表
- config = {'items': config_list}
-
- except Exception as e:
- print(f"解析文件 {filename} 时出错: {e}")
-
- return config
-
- def parse_weapon_multi_sheet_data(self, all_sheets_data, filename):
- """解析武器配置表的多工作表数据"""
- weapons_config = {'weapons': []}
-
- try:
- # 解析武器基础配置工作表(支持中英文工作表名)
- base_sheet = None
- for sheet_name in ['武器基础配置', 'Weapon Config', 'weapons', '武器配置']:
- if sheet_name in all_sheets_data:
- base_sheet = all_sheets_data[sheet_name]
- break
-
- if base_sheet is not None:
- # 设置武器配置的映射
- old_mapping = self.current_mapping
- self.current_mapping = {
- 'format_type': 'horizontal',
- 'param_types': {
- 'ID': str,
- '名称': str,
- '类型': str,
- '稀有度': str,
- '权重': int,
- '伤害': int,
- '射速': float,
- '射程': int,
- '子弹速度': int,
- # 方块价格配置字段
- '基础每格成本': int,
- 'I形状成本': int,
- 'H-I形状成本': int,
- 'L形状成本': int,
- 'S形状成本': int,
- 'D-T形状成本': int,
- # 英文字段支持
- 'id': str,
- 'name': str,
- 'type': str,
- 'rarity': str,
- 'weight': int,
- 'damage': int,
- 'fireRate': float,
- 'range': int,
- 'bulletSpeed': int,
- 'baseCost': int,
- 'I_shape_cost': int,
- 'HI_shape_cost': int,
- 'L_shape_cost': int,
- 'S_shape_cost': int,
- 'DT_shape_cost': int
- }
- }
-
- base_config = self.parse_config_data(base_sheet, filename)
- if 'items' in base_config:
- weapons_config['weapons'] = base_config['items']
- print(f"成功解析武器基础配置,共{len(base_config['items'])}个武器")
-
- # 恢复原来的映射
- self.current_mapping = old_mapping
-
- # 解析武器升级费用配置工作表(新增支持)
- upgrade_cost_sheet = None
- print(f"可用的工作表: {list(all_sheets_data.keys())}")
- for sheet_name in ['武器升级费用配置', 'Weapon Upgrade Cost', 'upgrade_costs', '升级费用']:
- if sheet_name in all_sheets_data:
- upgrade_cost_sheet = all_sheets_data[sheet_name]
- print(f"找到升级费用配置工作表: {sheet_name}")
- break
-
- if upgrade_cost_sheet is None:
- print("未找到升级费用配置工作表")
-
- if upgrade_cost_sheet is not None:
- # 直接处理升级费用数据(横向表格格式)
- print(f"开始处理升级费用配置,工作表行数: {len(upgrade_cost_sheet)}")
- upgrade_cost_data = []
-
- # 检查第一行是否为表头
- first_row = upgrade_cost_sheet.iloc[0] if len(upgrade_cost_sheet) > 0 else None
- is_header = False
- if first_row is not None:
- first_cell = str(first_row.iloc[0]).strip() if len(first_row) > 0 else ""
- # 如果第一列是"武器ID"等表头文字,则认为是表头
- if first_cell in ['武器ID', 'ID', 'weapon_id', 'weaponId']:
- is_header = True
- print(f"检测到表头行,第一列内容: {first_cell}")
-
- for index, row in upgrade_cost_sheet.iterrows():
- if is_header and index == 0: # 只有确认是表头时才跳过第一行
- print(f"跳过表头行 {index}")
- continue
-
- # 支持多种武器ID字段名:武器ID、ID、weapon_id等
- weapon_id = None
- for id_field in ['武器ID', 'ID', 'weapon_id', 'weaponId']:
- if id_field in row and pd.notna(row[id_field]):
- weapon_id = row[id_field]
- break
-
- # 如果没有找到命名字段,则使用第一列
- if weapon_id is None:
- weapon_id = row.iloc[0] if len(row) > 0 else None
-
- print(f"处理行 {index}: weapon_id = {weapon_id}")
- if weapon_id and str(weapon_id).strip(): # 确保武器ID不为空
- upgrade_levels = {}
- # 从第5列开始是等级1-10的费用(E列到N列),从第15列开始是等级1-10的伤害(O列到X列)
- for level in range(1, 11):
- cost_col_index = 4 + (level - 1) # 等级1费用在第5列(索引4),等级2在第6列(索引5),以此类推
- damage_col_index = 14 + (level - 1) # 等级1伤害在第15列(索引14),等级2在第16列(索引15),以此类推
-
- level_config = {}
-
- # 处理费用
- if cost_col_index < len(row):
- cost = row.iloc[cost_col_index]
- if cost and str(cost).strip() and str(cost) != 'nan':
- try:
- level_config['cost'] = int(float(cost))
- print(f" 等级 {level} 费用: {cost}")
- except (ValueError, TypeError):
- print(f" 等级 {level} 费用解析失败: {cost}")
-
- # 处理伤害
- if damage_col_index < len(row):
- damage = row.iloc[damage_col_index]
- if damage and str(damage).strip() and str(damage) != 'nan':
- try:
- level_config['damage'] = int(float(damage))
- print(f" 等级 {level} 伤害: {damage}")
- except (ValueError, TypeError):
- print(f" 等级 {level} 伤害解析失败: {damage}")
-
- # 只有当有费用或伤害数据时才添加等级配置
- if level_config:
- upgrade_levels[str(level)] = level_config
-
- if upgrade_levels: # 只有当有升级费用数据时才添加
- upgrade_cost_data.append({
- 'weapon_id': str(weapon_id).strip(),
- 'levels': upgrade_levels
- })
- print(f"为武器 {weapon_id} 添加了 {len(upgrade_levels)} 个等级的升级费用")
- else:
- print(f"武器 {weapon_id} 没有有效的升级费用数据")
-
- # 将升级费用配置合并到原始武器数据中(在转换之前)
- print(f"总共解析到 {len(upgrade_cost_data)} 个武器的升级费用配置")
- # 检查数据结构,支持两种格式:items 或 weapons
- weapon_list = None
- if 'items' in weapons_config:
- weapon_list = weapons_config['items']
- print(f"使用items字段,开始合并升级费用配置到 {len(weapon_list)} 个武器")
- elif 'weapons' in weapons_config:
- weapon_list = weapons_config['weapons']
- print(f"使用weapons字段,开始合并升级费用配置到 {len(weapon_list)} 个武器")
- else:
- print("weapons_config中没有items或weapons字段")
-
- if weapon_list:
- for weapon in weapon_list:
- # 支持两种字段名格式:ID(原始数据)和id(转换后数据)
- weapon_id = weapon.get('ID', '') or weapon.get('id', '')
- if weapon_id:
- # 查找对应的升级费用配置
- matching_upgrade = None
- for upgrade_data in upgrade_cost_data:
- if upgrade_data['weapon_id'] == weapon_id:
- matching_upgrade = upgrade_data
- break
-
- if matching_upgrade:
- weapon['upgradeConfig'] = {
- 'maxLevel': 10,
- 'levels': matching_upgrade['levels']
- }
- print(f"✓ 为武器 {weapon_id} 添加了升级费用配置")
- else:
- print(f"✗ 武器 {weapon_id} 未找到匹配的升级费用配置")
-
- # 解析旧版升级配置工作表(向后兼容)
- upgrade_sheet = None
- for sheet_name in ['升级配置', 'Upgrade Config', 'upgrades', '武器升级']:
- if sheet_name in all_sheets_data:
- upgrade_sheet = all_sheets_data[sheet_name]
- break
-
- if upgrade_sheet is not None:
- upgrade_config = self.parse_config_data(upgrade_sheet, filename)
-
- # 将升级配置合并到对应的武器中
- if 'items' in upgrade_config:
- upgrade_items = upgrade_config['items']
-
- # 为每个武器添加升级配置
- for weapon in weapons_config['weapons']:
- weapon_id = weapon.get('ID')
- if weapon_id and 'upgradeConfig' not in weapon: # 如果还没有升级配置
- # 查找对应的升级配置
- weapon_upgrades = [item for item in upgrade_items if item.get('ID') == weapon_id]
- if weapon_upgrades:
- # 构建升级配置结构
- upgrade_config_struct = {
- 'maxLevel': weapon.get('最大等级', 10),
- 'levels': {}
- }
-
- # 添加每个等级的升级配置
- for upgrade in weapon_upgrades:
- level = upgrade.get('等级', 1)
- if level:
- upgrade_config_struct['levels'][str(level)] = {
- 'cost': upgrade.get('升级费用', 25),
- 'damageIncrease': upgrade.get('伤害增加', 1)
- }
-
- weapon['upgradeConfig'] = upgrade_config_struct
-
- # 解析视觉配置工作表(新增支持)
- visual_config_sheet = None
- print(f"查找视觉配置工作表...")
- for sheet_name in ['视觉配置', 'Visual Config', 'visual_config', '武器视觉配置']:
- if sheet_name in all_sheets_data:
- visual_config_sheet = all_sheets_data[sheet_name]
- print(f"找到视觉配置工作表: {sheet_name}")
- break
-
- if visual_config_sheet is None:
- print("未找到视觉配置工作表")
-
- if visual_config_sheet is not None:
- # 直接处理视觉配置数据(横向表格格式)
- print(f"开始处理视觉配置,工作表行数: {len(visual_config_sheet)}")
- visual_config_data = []
-
- # 检查第一行是否为表头
- first_row = visual_config_sheet.iloc[0] if len(visual_config_sheet) > 0 else None
- is_header = False
- if first_row is not None:
- first_cell = str(first_row.iloc[0]).strip() if len(first_row) > 0 else ""
- # 如果第一列是"武器ID"等表头文字,则认为是表头
- if first_cell in ['武器ID', 'ID', 'weapon_id', 'weaponId']:
- is_header = True
- print(f"检测到表头行,第一列内容: {first_cell}")
-
- for index, row in visual_config_sheet.iterrows():
- if is_header and index == 0: # 只有确认是表头时才跳过第一行
- print(f"跳过表头行 {index}")
- continue
-
- # 支持多种武器ID字段名:武器ID、ID、weapon_id等
- weapon_id = None
- for id_field in ['武器ID', 'ID', 'weapon_id', 'weaponId']:
- if id_field in row and pd.notna(row[id_field]):
- weapon_id = row[id_field]
- break
-
- # 如果没有找到命名字段,则使用第一列
- if weapon_id is None:
- weapon_id = row.iloc[0] if len(row) > 0 else None
-
- print(f"处理行 {index}: weapon_id = {weapon_id}")
- if weapon_id and str(weapon_id).strip(): # 确保武器ID不为空
- visual_config = {}
-
- # 读取武器精灵配置
- weapon_sprites = {}
- sprite_fields = {
- 'I': ['I形状精灵', 'I_sprite', 'I形状图片', 'I'],
- 'H-I': ['H-I形状精灵', 'HI_sprite', 'H-I形状图片', 'H-I'],
- 'L': ['L形状精灵', 'L_sprite', 'L形状图片', 'L'],
- 'S': ['S形状精灵', 'S_sprite', 'S形状图片', 'S'],
- 'D-T': ['D-T形状精灵', 'DT_sprite', 'D-T形状图片', 'D-T']
- }
-
- for shape, field_names in sprite_fields.items():
- for field_name in field_names:
- if field_name in row and pd.notna(row[field_name]):
- sprite_path = str(row[field_name]).strip()
- if sprite_path:
- weapon_sprites[shape] = sprite_path
- print(f" {shape}形状精灵: {sprite_path}")
- break
-
- if weapon_sprites:
- visual_config['weaponSprites'] = weapon_sprites
-
- # 读取开火音效
- fire_sound_fields = ['开火音效', 'fireSound', '射击音效', '音效']
- for field in fire_sound_fields:
- if field in row and pd.notna(row[field]):
- fire_sound = str(row[field]).strip()
- if fire_sound:
- visual_config['fireSound'] = fire_sound
- print(f" 开火音效: {fire_sound}")
- break
-
- # 读取拖尾特效
- trail_effect_fields = ['拖尾特效', 'trailEffect', '尾迹特效', '特效']
- for field in trail_effect_fields:
- if field in row and pd.notna(row[field]):
- trail_effect = str(row[field]).strip()
- if trail_effect and trail_effect.lower() != 'null':
- visual_config['trailEffect'] = trail_effect
- print(f" 拖尾特效: {trail_effect}")
- break
-
- if visual_config: # 只有当有视觉配置数据时才添加
- visual_config_data.append({
- 'weapon_id': str(weapon_id).strip(),
- 'visualConfig': visual_config
- })
- print(f"为武器 {weapon_id} 添加了视觉配置")
- else:
- print(f"武器 {weapon_id} 没有有效的视觉配置数据")
-
- # 将视觉配置合并到原始武器数据中
- print(f"总共解析到 {len(visual_config_data)} 个武器的视觉配置")
- weapon_list = None
- if 'items' in weapons_config:
- weapon_list = weapons_config['items']
- print(f"使用items字段,开始合并视觉配置到 {len(weapon_list)} 个武器")
- elif 'weapons' in weapons_config:
- weapon_list = weapons_config['weapons']
- print(f"使用weapons字段,开始合并视觉配置到 {len(weapon_list)} 个武器")
- else:
- print("weapons_config中没有items或weapons字段")
-
- if weapon_list:
- for weapon in weapon_list:
- # 支持两种字段名格式:ID(原始数据)和id(转换后数据)
- weapon_id = weapon.get('ID', '') or weapon.get('id', '')
- if weapon_id:
- # 查找对应的视觉配置
- matching_visual = None
- for visual_data in visual_config_data:
- if visual_data['weapon_id'] == weapon_id:
- matching_visual = visual_data
- break
-
- if matching_visual:
- weapon['visualConfig'] = matching_visual['visualConfig']
- print(f"✓ 为武器 {weapon_id} 添加了视觉配置: {matching_visual['visualConfig']}")
- else:
- print(f"✗ 武器 {weapon_id} 未找到匹配的视觉配置")
-
- # 解析游戏内成本配置工作表(新增支持)
- cost_config_sheet = None
- print(f"查找游戏内成本配置工作表...")
- for sheet_name in ['游戏内成本配置', 'In Game Cost Config', 'cost_config', '成本配置']:
- if sheet_name in all_sheets_data:
- cost_config_sheet = all_sheets_data[sheet_name]
- print(f"找到游戏内成本配置工作表: {sheet_name}")
- break
-
- if cost_config_sheet is None:
- print("未找到游戏内成本配置工作表")
-
- if cost_config_sheet is not None:
- # 直接处理游戏内成本配置数据(横向表格格式)
- print(f"开始处理游戏内成本配置,工作表行数: {len(cost_config_sheet)}")
- cost_config_data = []
-
- # 检查第一行是否为表头
- first_row = cost_config_sheet.iloc[0] if len(cost_config_sheet) > 0 else None
- is_header = False
- if first_row is not None:
- first_cell = str(first_row.iloc[0]).strip() if len(first_row) > 0 else ""
- # 如果第一列是"武器ID"等表头文字,则认为是表头
- if first_cell in ['武器ID', 'ID', 'weapon_id', 'weaponId']:
- is_header = True
- print(f"检测到表头行,第一列内容: {first_cell}")
-
- for index, row in cost_config_sheet.iterrows():
- if is_header and index == 0: # 只有确认是表头时才跳过第一行
- print(f"跳过表头行 {index}")
- continue
-
- # 支持多种武器ID字段名:武器ID、ID、weapon_id等
- weapon_id = None
- for id_field in ['武器ID', 'ID', 'weapon_id', 'weaponId']:
- if id_field in row and pd.notna(row[id_field]):
- weapon_id = row[id_field]
- break
-
- # 如果没有找到命名字段,则使用第一列
- if weapon_id is None:
- weapon_id = row.iloc[0] if len(row) > 0 else None
-
- print(f"处理行 {index}: weapon_id = {weapon_id}")
- if weapon_id and str(weapon_id).strip(): # 确保武器ID不为空
- cost_config = {}
-
- # 读取基础每格成本
- base_cost_fields = ['基础每格成本', 'baseCost', '基础成本', '武器基础售价']
- for field in base_cost_fields:
- if field in row and pd.notna(row[field]):
- try:
- cost_config['baseCost'] = int(float(row[field]))
- print(f" 基础每格成本: {cost_config['baseCost']}")
- break
- except (ValueError, TypeError):
- continue
-
- # 读取各形状成本
- shape_costs = {}
- shape_fields = {
- 'I': ['I形状', 'I形状成本', 'I_shape_cost', 'I'],
- 'H-I': ['H-I形状', 'H-I形状成本', 'HI_shape_cost', 'H-I'],
- 'L': ['L形状', 'L形状成本', 'L_shape_cost', 'L'],
- 'S': ['S形状', 'S形状成本', 'S_shape_cost', 'S'],
- 'D-T': ['D-T形状', 'D-T形状成本', 'DT_shape_cost', 'D-T']
- }
-
- for shape, field_names in shape_fields.items():
- for field_name in field_names:
- if field_name in row and pd.notna(row[field_name]):
- try:
- shape_costs[shape] = int(float(row[field_name]))
- print(f" {shape}形状成本: {shape_costs[shape]}")
- break
- except (ValueError, TypeError):
- continue
-
- if shape_costs:
- cost_config['shapeCosts'] = shape_costs
-
- if cost_config: # 只有当有成本配置数据时才添加
- cost_config_data.append({
- 'weapon_id': str(weapon_id).strip(),
- 'costConfig': cost_config
- })
- print(f"为武器 {weapon_id} 添加了游戏内成本配置")
- else:
- print(f"武器 {weapon_id} 没有有效的游戏内成本配置数据")
-
- # 将游戏内成本配置合并到原始武器数据中
- print(f"总共解析到 {len(cost_config_data)} 个武器的游戏内成本配置")
- weapon_list = None
- if 'items' in weapons_config:
- weapon_list = weapons_config['items']
- print(f"使用items字段,开始合并游戏内成本配置到 {len(weapon_list)} 个武器")
- elif 'weapons' in weapons_config:
- weapon_list = weapons_config['weapons']
- print(f"使用weapons字段,开始合并游戏内成本配置到 {len(weapon_list)} 个武器")
- else:
- print("weapons_config中没有items或weapons字段")
-
- if weapon_list:
- for weapon in weapon_list:
- # 支持两种字段名格式:ID(原始数据)和id(转换后数据)
- weapon_id = weapon.get('ID', '') or weapon.get('id', '')
- if weapon_id:
- # 查找对应的游戏内成本配置
- matching_cost = None
- for cost_data in cost_config_data:
- if cost_data['weapon_id'] == weapon_id:
- matching_cost = cost_data
- break
-
- if matching_cost:
- # 直接将成本配置字段添加到武器数据中
- weapon.update(matching_cost['costConfig'])
- print(f"✓ 为武器 {weapon_id} 添加了游戏内成本配置: {matching_cost['costConfig']}")
- else:
- print(f"✗ 武器 {weapon_id} 未找到匹配的游戏内成本配置")
-
- print(f"武器配置解析完成,共{len(weapons_config['weapons'])}个武器")
- return weapons_config
-
- except Exception as e:
- print(f"解析武器配置表时出错: {e}")
- import traceback
- traceback.print_exc()
- return {'weapons': []}
-
- def clear_selection(self):
- """清空选择"""
- self.file_listbox.selection_clear(0, tk.END)
- self.preview_text.delete(1.0, tk.END)
- self.config_data = {}
- self.status_var.set("已清空选择")
- self.load_current_config()
-
- def load_current_config(self):
- """加载当前配置"""
- try:
- if not self.selected_json_path:
- self.preview_text.insert(tk.END, "请先选择JSON输出路径\n")
- return
-
- json_path = Path(self.selected_json_path)
- if json_path.exists():
- with open(json_path, 'r', encoding='utf-8') as f:
- current_config = json.load(f)
-
- self.preview_text.insert(tk.END, f"=== 当前配置 ({json_path.name}) ===\n")
-
- # 根据配置类型显示不同的预览格式
- if self.format_var.get() == 'horizontal':
- # 横向表格配置(如敌人、武器)
- if 'enemies' in current_config:
- self.preview_text.insert(tk.END, f"敌人配置 ({len(current_config['enemies'])} 个):\n")
- for i, enemy in enumerate(current_config['enemies'][:5]): # 只显示前5个
- self.preview_text.insert(tk.END, f" {i+1}. {enemy.get('name', enemy.get('id', 'Unknown'))}\n")
- if len(current_config['enemies']) > 5:
- self.preview_text.insert(tk.END, f" ... 还有 {len(current_config['enemies']) - 5} 个\n")
-
- if 'weapons' in current_config:
- self.preview_text.insert(tk.END, f"\n武器配置 ({len(current_config['weapons'])} 个):\n")
- for i, weapon in enumerate(current_config['weapons'][:5]): # 只显示前5个
- self.preview_text.insert(tk.END, f" {i+1}. {weapon.get('name', weapon.get('id', 'Unknown'))}\n")
- if len(current_config['weapons']) > 5:
- self.preview_text.insert(tk.END, f" ... 还有 {len(current_config['weapons']) - 5} 个\n")
- else:
- # 纵向表格配置(如BallController)
- self.preview_text.insert(tk.END, json.dumps(current_config, indent=2, ensure_ascii=False))
-
- file_hint = "Excel/CSV文件" if PANDAS_AVAILABLE else "CSV文件"
- self.preview_text.insert(tk.END, f"\n\n请选择{file_hint}进行配置导入...\n")
- else:
- self.preview_text.insert(tk.END, "配置文件不存在\n")
- except Exception as e:
- self.preview_text.insert(tk.END, f"加载当前配置失败: {e}\n")
-
- def backup_config(self):
- """备份当前配置"""
- try:
- if not self.selected_json_path:
- messagebox.showwarning("警告", "请先选择JSON输出路径")
- return
-
- json_path = Path(self.selected_json_path)
- if not json_path.exists():
- messagebox.showwarning("警告", "配置文件不存在")
- return
-
- backup_path = json_path.parent / f"ballController_backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
-
- with open(json_path, 'r', encoding='utf-8') as f:
- content = f.read()
-
- with open(backup_path, 'w', encoding='utf-8') as f:
- f.write(content)
-
- messagebox.showinfo("成功", f"配置已备份到:\n{backup_path}")
- self.status_var.set(f"配置已备份")
-
- except Exception as e:
- messagebox.showerror("错误", f"备份失败: {e}")
-
- def import_config(self):
- """导入配置到JSON文件"""
- if not self.config_data:
- messagebox.showwarning("警告", "没有配置数据可导入")
- return
-
- if not self.selected_json_path:
- messagebox.showwarning("警告", "请先选择JSON输出路径")
- return
-
- try:
- print(f"开始导入配置...")
- print(f"配置数据: {self.config_data}")
- print(f"输出路径: {self.selected_json_path}")
- print(f"选中文件: {self.selected_files}")
-
- # 优先使用配置映射中的格式类型,如果没有则使用GUI设置
- 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()
- print(f"格式类型: {format_type}")
- print(f"配置映射格式: {self.current_mapping.get('format_type') if hasattr(self, 'current_mapping') and self.current_mapping else 'None'}")
- print(f"GUI格式设置: {self.format_var.get()}")
-
- if format_type == 'vertical':
- # 处理纵向表格(如BallController)
- print("开始处理纵向表格配置...")
- self._import_vertical_config()
- elif format_type == 'horizontal':
- # 处理横向表格(如武器配置)
- print("开始处理横向表格配置...")
- self._import_horizontal_config()
- elif format_type == 'multi_sheet':
- # 处理多工作表格式(如敌人配置)
- print("开始处理多工作表配置...")
- self._import_multi_sheet_config() # 使用专门的多工作表处理逻辑
- else:
- raise ValueError(f"未知的格式类型: {format_type}")
-
- except Exception as e:
- import traceback
- error_details = traceback.format_exc()
- print(f"导入配置失败,详细错误信息:")
- print(error_details)
- messagebox.showerror("错误", f"导入配置失败: {str(e)}\n\n详细错误信息已输出到控制台,请查看。")
-
- def import_skill_config(self):
- """导入技能配置到skill_config.json"""
- try:
- # 查找局外技能配置表.xlsx文件
- excel_dir = Path(self.excel_dir)
- skill_excel_file = excel_dir / "局外技能配置表.xlsx"
-
- if not skill_excel_file.exists():
- messagebox.showwarning("警告", f"未找到技能配置文件: {skill_excel_file}")
- return
-
- # 使用SkillConfigImporter导入配置
- importer = SkillConfigImporter(str(excel_dir))
- success, message = importer.import_config()
-
- if success:
- messagebox.showinfo("成功", message)
- self.status_var.set("技能配置导入成功")
- # 更新预览文本
- self.preview_text.delete(1.0, tk.END)
- self.preview_text.insert(tk.END, f"技能配置导入成功\n\n{message}")
- else:
- messagebox.showerror("错误", f"技能配置导入失败: {message}")
- self.status_var.set("技能配置导入失败")
-
- except Exception as e:
- import traceback
- error_details = traceback.format_exc()
- print(f"导入技能配置失败,详细错误信息:")
- print(error_details)
- messagebox.showerror("错误", f"导入技能配置失败: {str(e)}\n\n详细错误信息已输出到控制台,请查看。")
-
- def _import_vertical_config(self):
- """导入纵向表格配置"""
- # 读取现有JSON配置
- if self.selected_json_path.exists():
- with open(self.selected_json_path, 'r', encoding='utf-8') as f:
- current_config = json.load(f)
- else:
- current_config = self.default_config.copy()
-
- # 合并配置
- updated_count = 0
- for key, value in self.config_data.items():
- if key in current_config and current_config[key] != value:
- current_config[key] = value
- updated_count += 1
- elif key not in current_config:
- current_config[key] = value
- updated_count += 1
-
- # 写入更新后的配置
- with open(self.selected_json_path, 'w', encoding='utf-8') as f:
- json.dump(current_config, f, indent=2, ensure_ascii=False)
-
- messagebox.showinfo("成功", f"配置导入成功!\n更新了 {updated_count} 个参数")
- self.status_var.set("配置导入成功")
-
- # 刷新预览
- self.clear_selection()
-
- def _import_horizontal_config(self):
- """导入横向表格配置"""
- print(f"横向表格配置导入开始...")
- print(f"配置数据结构: {list(self.config_data.keys()) if self.config_data else 'None'}")
-
- # 检查是否是关卡配置(目录类型)
- is_level_config = str(self.selected_json_path).endswith('levels') or self.selected_json_path.is_dir()
-
- # 对于关卡配置,支持多种数据格式
- if is_level_config:
- if isinstance(self.config_data, list):
- # 多工作表数据格式:直接是关卡数据列表
- print("检测到关卡配置的多工作表数据格式")
- items = None # 不需要items字段
- elif 'items' in self.config_data:
- # 传统的items数据格式
- print("检测到关卡配置的传统items数据格式")
- items = self.config_data['items']
- elif isinstance(self.config_data, dict) and len(self.config_data) == 1:
- # 检查是否是包装在单个键中的配置数据
- key = list(self.config_data.keys())[0]
- wrapped_data = self.config_data[key]
- print(f"检测到包装的关卡配置数据,键名: {key}")
- if isinstance(wrapped_data, list):
- print("提取到关卡配置的多工作表数据格式")
- self.config_data = wrapped_data # 更新配置数据为实际内容
- items = None
- elif isinstance(wrapped_data, dict) and 'items' in wrapped_data:
- print("提取到关卡配置的传统items数据格式")
- self.config_data = wrapped_data
- items = wrapped_data['items']
- else:
- print(f"错误: 包装数据格式不正确: {type(wrapped_data)}")
- error_msg = f"关卡配置数据格式错误:包装数据不是列表或包含items的字典\n\n包装键: {key}\n数据类型: {type(wrapped_data)}"
- messagebox.showwarning("警告", error_msg)
- self.preview_text.insert(tk.END, f"\n=== 错误信息 ===\n")
- self.preview_text.insert(tk.END, f"关卡配置数据格式错误:包装数据不是列表或包含items的字典\n")
- self.preview_text.insert(tk.END, f"包装键: {key}\n数据类型: {type(wrapped_data)}\n")
- return
- else:
- print(f"错误: 关卡配置数据格式不正确")
- print(f"实际配置数据: {self.config_data}")
- print(f"配置数据类型: {type(self.config_data)}")
- print(f"配置数据键: {list(self.config_data.keys()) if isinstance(self.config_data, dict) else 'Not a dict'}")
- error_msg = f"关卡配置数据格式错误:需要多工作表数据或items字段\n\n数据类型: {type(self.config_data)}\n数据内容: {str(self.config_data)[:200]}..."
- messagebox.showwarning("警告", error_msg)
- # 同时在GUI中显示错误信息
- self.preview_text.insert(tk.END, f"\n=== 错误信息 ===\n")
- self.preview_text.insert(tk.END, f"关卡配置数据格式错误:需要多工作表数据或items字段\n")
- self.preview_text.insert(tk.END, f"数据类型: {type(self.config_data)}\n")
- self.preview_text.insert(tk.END, f"数据内容: {str(self.config_data)[:500]}...\n")
- return
- else:
- # 其他配置类型:检查数据格式
- if isinstance(self.config_data, dict):
- # 检查是否是多工作表数据格式(如:{'技能配置表': [...]})
- if len(self.config_data) == 1:
- key = list(self.config_data.keys())[0]
- data_content = self.config_data[key]
- if isinstance(data_content, list):
- print(f"检测到多工作表数据格式,键名: {key}")
- items = data_content
- elif isinstance(data_content, dict) and 'items' in data_content:
- print(f"检测到包装的items数据格式,键名: {key}")
- items = data_content['items']
- else:
- print(f"错误: 无法识别的数据格式")
- error_msg = f"配置数据格式错误:无法识别的数据结构\n\n键名: {key}\n数据类型: {type(data_content)}"
- messagebox.showwarning("警告", error_msg)
- return
- elif 'items' in self.config_data:
- print("检测到传统items数据格式")
- items = self.config_data['items']
- else:
- print(f"错误: 配置数据格式不正确")
- print(f"实际配置数据: {self.config_data}")
- print(f"配置数据类型: {type(self.config_data)}")
- print(f"配置数据键: {list(self.config_data.keys())}")
- error_msg = f"配置数据格式错误:需要items字段或多工作表数据格式\n\n数据类型: {type(self.config_data)}\n可用键: {list(self.config_data.keys())}"
- messagebox.showwarning("警告", error_msg)
- # 同时在GUI中显示错误信息
- self.preview_text.insert(tk.END, f"\n=== 错误信息 ===\n")
- self.preview_text.insert(tk.END, f"配置数据格式错误:需要items字段或多工作表数据格式\n")
- self.preview_text.insert(tk.END, f"数据类型: {type(self.config_data)}\n")
- self.preview_text.insert(tk.END, f"可用键: {list(self.config_data.keys())}\n")
- return
- elif isinstance(self.config_data, list):
- print("检测到直接列表数据格式")
- items = self.config_data
- else:
- print(f"错误: 不支持的数据类型: {type(self.config_data)}")
- error_msg = f"不支持的数据类型: {type(self.config_data)}"
- messagebox.showwarning("警告", error_msg)
- return
-
- # 验证数据有效性
- if items is not None:
- print(f"配置项数量: {len(items) if items else 0}")
- if not items:
- messagebox.showwarning("警告", "没有有效的配置项")
- return
- elif isinstance(self.config_data, list):
- print(f"关卡配置数量: {len(self.config_data)}")
- if not self.config_data:
- messagebox.showwarning("警告", "没有有效的关卡配置")
- return
-
- # 打印前几个配置项用于调试
- if items is not None:
- for i, item in enumerate(items[:3]):
- print(f"配置项{i}: {item}")
- elif isinstance(self.config_data, list):
- for i, item in enumerate(self.config_data[:3]):
- print(f"关卡配置{i}: {item.get('levelId', 'Unknown')} - {item.get('name', 'Unknown')}")
-
- # 读取现有JSON配置
- print(f"JSON配置文件路径: {self.selected_json_path}")
-
- # 检查是否是关卡配置(目录类型)
- if is_level_config:
- print("处理关卡配置目录...")
- # 关卡配置是目录,确保目录存在
- if not self.selected_json_path.exists():
- print(f"创建关卡配置目录: {self.selected_json_path}")
- self.selected_json_path.mkdir(parents=True, exist_ok=True)
- current_config = {}
- else:
- # 普通JSON文件配置
- if self.selected_json_path.exists():
- print("读取现有JSON配置文件...")
- with open(self.selected_json_path, 'r', encoding='utf-8') as f:
- current_config = json.load(f)
- else:
- print("创建新的JSON配置...")
- current_config = {}
-
- # 根据用户选择的处理模式和数据类型处理
- if not self.selected_files:
- print("错误: 没有选中的文件")
- messagebox.showerror("错误", "没有选中的文件")
- return
-
- filename = Path(self.selected_files[0]).name
- print(f"处理文件: {filename}")
-
- # 获取用户选择的处理模式
- is_multi_sheet = self.multi_sheet_var.get()
- is_single_json = self.single_json_var.get()
- print(f"处理模式 - 多工作表: {is_multi_sheet}, 单个JSON: {is_single_json}")
-
- # 根据数据类型和用户选择的模式进行处理
- if is_level_config:
- # 关卡配置:始终为目录模式,每个关卡一个JSON文件
- print("处理关卡配置...")
- # 关卡配置:为每个关卡创建单独的JSON文件
- levels_dir = self.selected_json_path
- print(f"关卡配置目录: {levels_dir}")
-
- try:
- # 检查并创建目录
- if not levels_dir.exists():
- print(f"创建关卡配置目录: {levels_dir}")
- levels_dir.mkdir(parents=True, exist_ok=True)
- else:
- print(f"关卡配置目录已存在: {levels_dir}")
-
- # 测试目录写入权限
- test_file = levels_dir / "test_permission.tmp"
- try:
- with open(test_file, 'w', encoding='utf-8') as f:
- f.write("test")
- test_file.unlink() # 删除测试文件
- except PermissionError:
- messagebox.showerror("错误", f"没有写入权限到目录: {levels_dir}\n请检查目录权限或以管理员身份运行")
- return
-
- # 检查数据格式:多工作表数据 vs 传统items数据
- if isinstance(self.config_data, list):
- # 新的多工作表数据格式:直接是关卡数据列表
- print("使用多工作表数据格式")
- level_configs = self.config_data
- elif 'items' in self.config_data:
- # 传统的items数据格式:需要转换
- print("使用传统items数据格式")
- level_configs = []
- for item in items:
- level_data = self._convert_level_data(item)
- if level_data:
- level_configs.append(level_data)
- else:
- print(f"错误: 未知的关卡配置数据格式: {type(self.config_data)}")
- messagebox.showerror("错误", "关卡配置数据格式错误")
- return
-
- # 保存关卡配置文件
- updated_count = 0
- failed_count = 0
-
- for level_data in level_configs:
- try:
- if level_data and 'levelId' in level_data and level_data['levelId']:
- level_id = level_data['levelId']
- level_file = levels_dir / f"{level_id}.json"
-
- print(f"保存关卡配置: {level_file}")
- with open(level_file, 'w', encoding='utf-8') as f:
- json.dump(level_data, f, indent=2, ensure_ascii=False)
- updated_count += 1
- else:
- print(f"跳过无效的关卡数据: {level_data}")
- failed_count += 1
- except Exception as e:
- print(f"保存关卡配置时出错: {e}")
- failed_count += 1
- continue
-
- if updated_count > 0:
- message = f"关卡配置导入成功!\n更新了 {updated_count} 个关卡文件"
- if failed_count > 0:
- message += f"\n跳过了 {failed_count} 个无效配置"
- messagebox.showinfo("成功", message)
- self.status_var.set("关卡配置导入成功")
- else:
- messagebox.showerror("错误", "没有成功导入任何关卡配置\n请检查Excel文件格式和数据")
- self.status_var.set("关卡配置导入失败")
-
- except Exception as e:
- error_msg = f"关卡配置导入失败: {str(e)}"
- print(error_msg)
- messagebox.showerror("错误", error_msg)
- self.status_var.set("关卡配置导入失败")
-
- self.clear_selection()
- return
-
- else:
- # 非关卡配置:根据用户选择的模式和数据内容进行处理
- print("处理非关卡配置...")
-
- # 根据数据类型自动识别配置类型
- config_type = self._detect_config_type()
- print(f"检测到配置类型: {config_type}")
-
- if config_type == 'unknown':
- print(f"警告: 无法识别的配置类型")
- messagebox.showwarning("警告", f"无法识别配置类型\n请检查Excel文件的工作表名称或数据格式")
- return
-
- # 根据配置类型处理数据
- if config_type == 'enemy':
- print("处理敌人配置...")
- if 'enemies' not in current_config:
- current_config['enemies'] = []
-
- updated_enemies = []
- for i, item in enumerate(items):
- print(f"转换敌人数据 {i+1}/{len(items)}: {item}")
- enemy_data = self._convert_enemy_data(item)
- if enemy_data:
- updated_enemies.append(enemy_data)
- print(f"成功转换敌人数据: {enemy_data['id']}")
- else:
- print(f"跳过无效的敌人数据: {item}")
-
- print(f"总共转换了 {len(updated_enemies)} 个敌人配置")
- current_config['enemies'] = updated_enemies
-
- elif config_type == 'weapon':
- print("处理武器配置...")
- if 'weapons' not in current_config:
- current_config['weapons'] = []
-
- updated_weapons = []
- for i, item in enumerate(items):
- print(f"转换武器数据 {i+1}/{len(items)}: {item}")
- weapon_data = self._convert_weapon_data(item)
- if weapon_data:
- updated_weapons.append(weapon_data)
- print(f"成功转换武器数据: {weapon_data.get('id', 'Unknown')}")
- else:
- print(f"跳过无效的武器数据: {item}")
-
- print(f"总共转换了 {len(updated_weapons)} 个武器配置")
- current_config['weapons'] = updated_weapons
-
- elif config_type == 'skill':
- print("处理技能配置...")
- if 'skills' not in current_config:
- current_config['skills'] = []
-
- updated_skills = []
- for i, item in enumerate(items):
- print(f"转换技能数据 {i+1}/{len(items)}: {item}")
- skill_data = self._convert_skill_data(item)
- if skill_data:
- updated_skills.append(skill_data)
- print(f"成功转换技能数据: {skill_data.get('id', 'Unknown')}")
- else:
- print(f"跳过无效的技能数据: {item}")
-
- print(f"总共转换了 {len(updated_skills)} 个技能配置")
- current_config['skills'] = updated_skills
-
- else:
- # 通用配置处理
- print(f"处理通用配置类型: {config_type}")
- # 直接使用items数据
- current_config['items'] = items
-
- # 写入更新后的配置(关卡配置已在前面处理,跳过)
- if not is_level_config:
- try:
- print(f"写入配置文件: {self.selected_json_path}")
- print(f"配置内容预览: {str(current_config)[:200]}...")
-
- with open(self.selected_json_path, 'w', encoding='utf-8') as f:
- json.dump(current_config, f, indent=2, ensure_ascii=False)
-
- print("配置文件写入成功")
- messagebox.showinfo("成功", f"配置导入成功!\n更新了 {len(items)} 个配置项")
- self.status_var.set("配置导入成功")
-
- except Exception as e:
- print(f"写入配置文件失败: {e}")
- messagebox.showerror("错误", f"写入配置文件失败: {str(e)}")
- return
-
- # 刷新预览
- self.clear_selection()
-
- def _import_multi_sheet_config(self):
- """导入多工作表配置(直接使用解析后的数据,不再通过_convert_enemy_data转换)"""
- print(f"多工作表配置导入开始...")
- print(f"配置数据结构: {list(self.config_data.keys()) if self.config_data else 'None'}")
-
- try:
- # 读取现有JSON配置
- if self.selected_json_path.exists():
- with open(self.selected_json_path, 'r', encoding='utf-8') as f:
- current_config = json.load(f)
- else:
- current_config = self.default_config.copy()
-
- # 直接使用解析后的多工作表数据
- if isinstance(self.config_data, list):
- # 如果config_data是列表(如敌人配置),直接使用
- print(f"使用解析后的敌人配置数据,共 {len(self.config_data)} 个敌人")
- current_config['enemies'] = self.config_data
-
- # 添加元数据
- current_config['metadata'] = {
- 'last_updated': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
- 'totalEnemies': len(self.config_data),
- 'conversionMethod': 'excel_multi_sheet_direct'
- }
-
- elif isinstance(self.config_data, dict):
- # 如果config_data是字典,合并到current_config
- print(f"合并字典格式的配置数据")
- current_config.update(self.config_data)
-
- # 写入更新后的配置
- print(f"写入配置文件: {self.selected_json_path}")
- print(f"配置内容预览: {str(current_config)[:200]}...")
-
- with open(self.selected_json_path, 'w', encoding='utf-8') as f:
- json.dump(current_config, f, indent=2, ensure_ascii=False)
-
- print("多工作表配置文件写入成功")
- messagebox.showinfo("成功", f"多工作表配置导入成功!\n更新了配置数据")
- self.status_var.set("多工作表配置导入成功")
-
- except Exception as e:
- print(f"导入多工作表配置失败: {e}")
- import traceback
- traceback.print_exc()
- messagebox.showerror("错误", f"导入多工作表配置失败: {str(e)}")
- return
-
- # 刷新预览
- self.clear_selection()
-
- def _detect_config_type(self):
- """根据数据内容和工作表名称自动识别配置类型"""
- try:
- # 检查是否是多工作表数据
- if self.multi_sheet_var.get() and hasattr(self, 'config_data') and isinstance(self.config_data, dict):
- # 多工作表模式:根据工作表名称判断
- if '敌人基础配置' in self.config_data or '敌人配置' in str(self.config_data.keys()):
- return 'enemy'
- elif '技能信息表' in self.config_data or '技能配置' in str(self.config_data.keys()):
- return 'skill'
- elif '武器配置' in str(self.config_data.keys()) or '武器信息' in self.config_data:
- return 'weapon'
- elif any('关卡' in str(key) for key in self.config_data.keys()):
- return 'level'
-
- # 检查数据内容中的字段来判断类型
- if hasattr(self, 'config_data'):
- sample_data = None
-
- # 获取样本数据
- if isinstance(self.config_data, dict) and 'items' in self.config_data:
- items = self.config_data['items']
- if items and len(items) > 0:
- sample_data = items[0]
- elif isinstance(self.config_data, list) and len(self.config_data) > 0:
- sample_data = self.config_data[0]
-
- if sample_data and isinstance(sample_data, dict):
- # 根据字段名判断配置类型
- fields = set(sample_data.keys())
-
- # 敌人配置特征字段
- enemy_fields = {'enemyId', 'name', 'hp', 'attack', 'defense', 'speed'}
- if enemy_fields.intersection(fields):
- return 'enemy'
-
- # 技能配置特征字段
- skill_fields = {'skillId', 'skillName', 'damage', 'cooldown', 'description'}
- if skill_fields.intersection(fields):
- return 'skill'
-
- # 武器配置特征字段
- weapon_fields = {'weaponId', 'weaponName', 'damage', 'range', 'fireRate'}
- if weapon_fields.intersection(fields):
- return 'weapon'
-
- # 关卡配置特征字段
- level_fields = {'levelId', 'name', 'enemies', 'waves'}
- if level_fields.intersection(fields):
- return 'level'
-
- # 如果无法通过数据内容判断,尝试通过文件名判断(作为后备方案)
- if hasattr(self, 'selected_files') and self.selected_files:
- filename = Path(self.selected_files[0]).name.lower()
- if '敌人' in filename:
- return 'enemy'
- elif '技能' in filename:
- return 'skill'
- elif '武器' in filename:
- return 'weapon'
- elif '关卡' in filename:
- return 'level'
-
- return 'unknown'
-
- except Exception as e:
- print(f"配置类型检测出错: {e}")
- return 'unknown'
-
- def _convert_enemy_data(self, item):
- """转换敌人数据格式 - 支持嵌套结构"""
- try:
- print(f"开始转换敌人数据: {item}")
- # 支持中英文字段名
- enemy_id = item.get('id', item.get('敌人ID', ''))
- enemy_name = item.get('name', item.get('敌人名称', ''))
- enemy_type = item.get('type', item.get('敌人类型', ''))
- print(f"敌人ID: {enemy_id}, 敌人名称: {enemy_name}, 类型: {enemy_type}")
-
- # 检查必要字段
- if not enemy_id:
- print(f"跳过无效敌人数据: 缺少敌人ID - {item}")
- return None
-
- # 获取基础属性
- health = item.get('health', item.get('生命值', 100))
- speed = item.get('speed', item.get('移动速度', 50))
- attack_damage = item.get('attackDamage', item.get('攻击伤害', 10))
- attack_range = item.get('range', item.get('攻击范围', 100))
- attack_speed = item.get('attackSpeed', item.get('攻击速度', 1.0))
- defense = item.get('defense', item.get('防御力', 0))
-
- # 调试信息
- print(f"转换敌人 {enemy_id} 的属性: health={health}, speed={speed}, attackDamage={attack_damage}, defense={defense}")
- print(f"原始数据中的字段: {list(item.keys())}")
- if '生命值' in item:
- print(f"找到生命值字段,值为: {item['生命值']}")
- if 'health' in item:
- print(f"找到health字段,值为: {item['health']}")
-
- # 获取格挡相关属性
- can_block = item.get('canBlock', item.get('可格挡', False))
- block_chance = item.get('blockChance', item.get('格挡概率', 0.0))
- block_damage_reduction = item.get('blockDamageReduction', item.get('格挡伤害减免', 0.5))
-
- # 获取移动相关属性
- movement_pattern = item.get('movementPattern', item.get('移动模式', 'direct'))
- patrol_range = item.get('patrolRange', item.get('巡逻范围', 100))
- chase_range = item.get('chaseRange', item.get('追击范围', 200))
-
- # 获取视觉配置
- # 敌人ID到动画编号的映射
- enemy_to_ani = {
- 'normal_zombie': '001',
- 'roadblock_zombie': '002',
- 'wandering_zombie': '003',
- 'mage_zombie': '004',
- 'archer_zombie': '005',
- 'stealth_zombie': '006',
- 'bucket_zombie': '007',
- 'barrel_zombie': '008',
- 'boss1_gatekeeper': '009',
- 'boss2_gravedigger': '010',
- 'boss3_cyborg': '011'
- }
- ani_num = enemy_to_ani.get(enemy_id, '001')
- default_sprite_path = f'Animation/EnemyAni/{ani_num}'
- sprite_path = item.get('spritePath', item.get('精灵路径', default_sprite_path))
- scale = item.get('scale', item.get('缩放', 1.0))
-
- # 获取音效配置
- attack_sound = item.get('attackSound', item.get('攻击音效', 'enemy_attack'))
- death_sound = item.get('deathSound', item.get('死亡音效', 'enemy_death'))
-
- # 构建嵌套结构的配置
- result = {
- 'id': enemy_id,
- 'name': enemy_name,
- 'type': enemy_type,
- 'stats': {
- 'health': health,
- 'maxHealth': health,
- 'defense': defense,
- 'speed': speed
- },
- 'movement': {
- 'pattern': movement_pattern,
- 'speed': speed,
- 'patrolRange': patrol_range,
- 'chaseRange': chase_range,
- 'rotationSpeed': item.get('rotationSpeed', item.get('旋转速度', 180)),
- 'moveType': item.get('moveType', item.get('移动类型', 'straight')),
- 'swingAmplitude': item.get('swingAmplitude', item.get('摆动幅度', 0.0)),
- 'swingFrequency': item.get('swingFrequency', item.get('摆动频率', 0.0)),
- 'speedVariation': item.get('speedVariation', item.get('速度变化', 0.1))
- },
- 'combat': {
- 'attackDamage': attack_damage,
- 'attackRange': attack_range,
- 'attackSpeed': attack_speed,
- 'canBlock': can_block,
- 'blockChance': block_chance,
- 'blockDamageReduction': block_damage_reduction,
- 'attackCooldown': 1.0 / attack_speed if attack_speed > 0 else 1.0,
- 'attackType': item.get('attackType', item.get('攻击类型', 'melee')),
- 'attackDelay': item.get('attackDelay', item.get('攻击延迟', 0.5)),
- 'weaponType': item.get('weaponType', item.get('武器类型', 'none')),
- 'projectileType': item.get('projectileType', item.get('投射物类型', 'none')),
- 'projectileSpeed': item.get('projectileSpeed', item.get('投射物速度', 100))
- },
- 'visualConfig': {
- 'spritePath': sprite_path,
- 'scale': scale,
- 'animationSpeed': item.get('animationSpeed', item.get('动画速度', 1.0)),
- 'flipX': item.get('flipX', item.get('水平翻转', False)),
- 'tint': item.get('tint', item.get('着色', '#FFFFFF')),
- 'animations': {
- 'idle': item.get('idleAnimation', item.get('待机动画', 'idle')),
- 'walk': item.get('walkAnimation', item.get('行走动画', 'walk')),
- 'attack': item.get('attackAnimation', item.get('攻击动画', 'attack')),
- 'death': item.get('deathAnimation', item.get('死亡动画', 'dead'))
- },
- 'weaponProp': item.get('weaponProp', item.get('武器道具', ''))
- },
- 'audioConfig': {
- 'attackSound': attack_sound,
- 'deathSound': death_sound,
- 'hitSound': item.get('hitSound', item.get('受击音效', 'enemy_hit')),
- 'walkSound': item.get('walkSound', item.get('行走音效', '')),
- 'blockSound': item.get('blockSound', item.get('格挡音效', '')),
- 'stealthSound': item.get('stealthSound', item.get('隐身音效', '')),
- 'armorBreakSound': item.get('armorBreakSound', item.get('护甲破碎音效', '')),
- 'fuseSound': item.get('fuseSound', item.get('引信音效', '')),
- 'volume': item.get('volume', item.get('音量', 1.0))
- }
- }
-
- # 添加特殊能力配置
- special_abilities = item.get('specialAbilities', [])
- if special_abilities:
- result['specialAbilities'] = special_abilities
-
- # 添加BOSS配置
- boss_config = item.get('bossConfig', {})
- if boss_config and boss_config.get('isBoss', False):
- result['bossConfig'] = boss_config
-
- # 根据敌人类型添加特殊配置
- result = self._add_enemy_special_config(result, enemy_type, item)
-
- print(f"成功转换敌人数据: {enemy_id}")
- return result
- except Exception as e:
- print(f"转换敌人数据失败: {e} - 数据: {item}")
- return None
-
- def _add_enemy_special_config(self, result, enemy_type, item):
- """根据敌人类型添加特殊配置"""
- try:
- # 根据敌人类型添加特殊能力和配置
- special_abilities = []
-
- if enemy_type == 'stealth_zombie':
- # 隐身僵尸特殊配置
- special_abilities.append({
- 'type': 'stealth',
- 'duration': item.get('stealthDuration', item.get('隐身持续时间', 3.0)),
- 'cooldown': item.get('stealthCooldown', item.get('隐身冷却时间', 8.0)),
- 'alpha': item.get('stealthAlpha', item.get('隐身透明度', 0.3))
- })
- result['stealthConfig'] = {
- 'canStealth': True,
- 'stealthDuration': item.get('stealthDuration', item.get('隐身持续时间', 3.0)),
- 'stealthCooldown': item.get('stealthCooldown', item.get('隐身冷却时间', 8.0)),
- 'stealthAlpha': item.get('stealthAlpha', item.get('隐身透明度', 0.3))
- }
-
- elif enemy_type == 'bucket_zombie':
- # 铁桶僵尸特殊配置
- result['armorConfig'] = {
- 'hasArmor': True,
- 'armorHealth': item.get('armorHealth', item.get('护甲血量', 50)),
- 'armorDefense': item.get('armorDefense', item.get('护甲防御', 5)),
- 'armorSprite': item.get('armorSprite', item.get('护甲精灵', 'bucket_armor'))
- }
-
- elif enemy_type == 'explosive_zombie':
- # 火药桶僵尸特殊配置
- special_abilities.append({
- 'type': 'explosion',
- 'damage': item.get('explosionDamage', item.get('爆炸伤害', 30)),
- 'radius': item.get('explosionRadius', item.get('爆炸半径', 80)),
- 'triggerOnDeath': True
- })
- result['explosiveConfig'] = {
- 'explosionDamage': item.get('explosionDamage', item.get('爆炸伤害', 30)),
- 'explosionRadius': item.get('explosionRadius', item.get('爆炸半径', 80)),
- 'fuseTime': item.get('fuseTime', item.get('引爆时间', 2.0)),
- 'chainExplosion': item.get('chainExplosion', item.get('连锁爆炸', False))
- }
-
- elif enemy_type == 'ranged_enemy':
- # 远程敌人特殊配置
- result['rangedConfig'] = {
- 'projectileType': item.get('projectileType', item.get('弹药类型', 'bullet')),
- 'projectileSpeed': item.get('projectileSpeed', item.get('弹药速度', 100)),
- 'projectileDamage': item.get('projectileDamage', item.get('弹药伤害', item.get('attack', item.get('攻击力', 10)))),
- 'fireRate': item.get('fireRate', item.get('射速', 1.0)),
- 'accuracy': item.get('accuracy', item.get('精度', 0.9))
- }
-
- elif 'boss' in enemy_type.lower():
- # BOSS特殊配置
- special_abilities.extend([
- {
- 'type': 'charge_attack',
- 'damage': item.get('chargeDamage', item.get('冲锋伤害', item.get('attack', item.get('攻击力', 10)) * 2)),
- 'range': item.get('chargeRange', item.get('冲锋范围', 150)),
- 'cooldown': item.get('chargeCooldown', item.get('冲锋冷却', 8.0))
- },
- {
- 'type': 'area_attack',
- 'damage': item.get('areaDamage', item.get('范围伤害', item.get('attack', item.get('攻击力', 10)) * 1.5)),
- 'radius': item.get('areaRadius', item.get('范围半径', 100)),
- 'cooldown': item.get('areaCooldown', item.get('范围攻击冷却', 12.0))
- }
- ])
- result['bossConfig'] = {
- 'isBoss': True,
- 'phases': item.get('phases', item.get('阶段数', 1)),
- 'enrageThreshold': item.get('enrageThreshold', item.get('狂暴阈值', 0.3)),
- 'enrageDamageMultiplier': item.get('enrageDamageMultiplier', item.get('狂暴伤害倍数', 1.5)),
- 'enrageSpeedMultiplier': item.get('enrageSpeedMultiplier', item.get('狂暴速度倍数', 1.3))
- }
-
- # 添加特殊能力到结果中
- if special_abilities:
- result['specialAbilities'] = special_abilities
-
- return result
-
- except Exception as e:
- print(f"添加特殊配置失败: {e}")
- return result
-
- def _convert_weapon_data(self, item):
- """转换武器数据格式"""
- try:
- print(f"开始转换武器数据: {item}")
- # 支持中英文字段名
- weapon_id = item.get('id', item.get('ID', ''))
- weapon_name = item.get('name', item.get('名称', ''))
- print(f"武器ID: {weapon_id}, 武器名称: {weapon_name}")
-
- # 检查必要字段
- if not weapon_id:
- print(f"跳过无效武器数据: 缺少武器ID - {item}")
- return None
-
- # 获取基础属性
- damage = item.get('damage', item.get('伤害', 10))
- fire_rate = item.get('fireRate', item.get('射速', 1.0))
- weapon_range = item.get('range', item.get('射程', 100))
- bullet_speed = item.get('bulletSpeed', item.get('子弹速度', 100))
- weapon_type = item.get('type', item.get('类型', ''))
- rarity = item.get('rarity', item.get('稀有度', ''))
- weight = item.get('weight', item.get('权重', 1))
-
- # 根据武器ID推断武器类型和稀有度(如果为空)
- if not weapon_type:
- if 'shotgun' in weapon_id or 'cactus' in weapon_id:
- weapon_type = 'shotgun'
- elif 'bomb' in weapon_id or 'pepper' in weapon_id:
- weapon_type = 'explosive'
- elif 'missile' in weapon_id:
- weapon_type = 'homing_missile'
- elif 'boomerang' in weapon_id:
- weapon_type = 'boomerang'
- elif 'saw' in weapon_id:
- weapon_type = 'ricochet_piercing'
- elif 'carrot' in weapon_id:
- weapon_type = 'piercing'
- else:
- weapon_type = 'single_shot'
-
- if not rarity:
- if damage >= 60:
- rarity = 'epic'
- elif damage >= 40:
- rarity = 'rare'
- elif damage >= 25:
- rarity = 'uncommon'
- else:
- rarity = 'common'
-
- # 根据稀有度设置权重
- if weight == 1: # 默认权重,需要根据稀有度调整
- rarity_weights = {'common': 30, 'uncommon': 20, 'rare': 15, 'epic': 8}
- weight = rarity_weights.get(rarity, 20)
-
- result = {
- 'id': weapon_id,
- 'name': weapon_name,
- 'type': weapon_type,
- 'rarity': rarity,
- 'weight': weight,
- 'stats': {
- 'damage': damage,
- 'fireRate': fire_rate,
- 'range': weapon_range,
- 'bulletSpeed': min(bullet_speed, 50) # 限制子弹速度
- },
- 'bulletConfig': self._generate_bullet_config(weapon_id, weapon_type, damage, weapon_range, item),
- 'visualConfig': self._generate_visual_config(weapon_id, weapon_name, item)
- }
-
- # 如果原始数据中有upgradeConfig,则添加到结果中
- if 'upgradeConfig' in item:
- result['upgradeConfig'] = item['upgradeConfig']
- print(f"为武器 {weapon_id} 保留了升级配置: {item['upgradeConfig']}")
- else:
- print(f"武器 {weapon_id} 没有升级配置数据")
-
- # 处理方块价格配置(inGameCostConfig)
- # 优先使用从游戏内成本配置工作表导入的数据
- if 'baseCost' in item or 'shapeCosts' in item:
- in_game_cost_config = {
- 'baseCost': item.get('baseCost', 10),
- 'shapeCosts': item.get('shapeCosts', {})
- }
- print(f"为武器 {weapon_id} 使用Excel导入的方块价格配置: {in_game_cost_config}")
- else:
- # 如果没有导入数据,则生成默认配置
- in_game_cost_config = self._generate_in_game_cost_config(item, weapon_id, rarity)
- print(f"为武器 {weapon_id} 生成默认方块价格配置: {in_game_cost_config}")
-
- if in_game_cost_config:
- result['inGameCostConfig'] = in_game_cost_config
-
- print(f"成功转换武器数据: {result}")
- return result
- except Exception as e:
- print(f"转换武器数据失败: {e} - 数据: {item}")
- return None
-
- def _generate_bullet_config(self, weapon_id, weapon_type, damage, weapon_range, item=None):
- """生成子弹配置"""
- # 从item中获取trailEffect,如果没有则使用默认值
- trail_effect = None
- if item and 'visualConfig' in item and 'trailEffect' in item['visualConfig']:
- trail_effect = item['visualConfig']['trailEffect']
- print(f"为武器 {weapon_id} 在bulletConfig.visual中使用Excel中的拖尾特效: {trail_effect}")
-
- # 基础配置模板
- base_config = {
- 'count': {'type': 'single', 'amount': 1, 'spreadAngle': 0, 'burstCount': 1, 'burstDelay': 0},
- 'trajectory': {'type': 'straight', 'speed': 200, 'gravity': 0, 'arcHeight': 0, 'homingStrength': 0, 'homingDelay': 0},
- 'hitEffects': [{'type': 'normal_damage', 'priority': 1, 'params': {'damage': damage}}],
- 'lifecycle': {'type': 'hit_destroy', 'maxLifetime': 5.0, 'penetration': 1, 'ricochetCount': 0, 'returnToOrigin': False},
- 'visual': {'bulletPrefab': f'bullets/{weapon_id.title()}Bullet', 'hitEffect': 'Animation/WeaponTx/tx0002/tx0002', 'trailEffect': trail_effect, 'muzzleFlash': 'Animation/WeaponTx/tx0002/tx0002'}
- }
-
- # 根据武器类型调整配置
- if weapon_type == 'shotgun':
- base_config['count'] = {'type': 'spread', 'amount': 5, 'spreadAngle': 30, 'burstCount': 1, 'burstDelay': 0}
- base_config['lifecycle']['type'] = 'range_limit'
- base_config['lifecycle']['maxRange'] = weapon_range * 2
- elif weapon_type == 'piercing':
- base_config['hitEffects'] = [{'type': 'pierce_damage', 'priority': 1, 'params': {'damage': damage, 'pierceCount': 999}}]
- base_config['lifecycle'] = {'type': 'range_limit', 'maxLifetime': 5.0, 'penetration': 999, 'ricochetCount': 0, 'returnToOrigin': False, 'maxRange': weapon_range * 2}
- base_config['visual']['trailEffect'] = 'Animation/WeaponTx/tx0001/tx0001'
- elif weapon_type == 'explosive':
- base_config['trajectory']['type'] = 'arc'
- base_config['hitEffects'] = [{'type': 'explosion', 'priority': 1, 'params': {'damage': damage + 20, 'radius': 100, 'delay': 0.1}}]
- base_config['lifecycle']['type'] = 'ground_impact'
- base_config['visual']['hitEffect'] = 'Animation/WeaponTx/tx0007/tx0007'
- elif weapon_type == 'homing_missile':
- base_config['trajectory'] = {'type': 'homing', 'speed': 20, 'gravity': 0.2, 'arcHeight': 100, 'homingStrength': 0.8, 'homingDelay': 0.3}
- base_config['hitEffects'] = [{'type': 'explosion', 'priority': 1, 'params': {'damage': damage, 'radius': 150, 'delay': 0}}]
- base_config['lifecycle']['type'] = 'target_impact'
- elif weapon_type == 'boomerang':
- base_config['trajectory'] = {'type': 'homing', 'speed': 15, 'gravity': 0, 'homingStrength': 0.5, 'homingDelay': 0.3}
- base_config['hitEffects'] = [{'type': 'pierce_damage', 'priority': 1, 'params': {'damage': damage, 'pierceCount': 999}}]
- base_config['lifecycle'] = {'type': 'return_trip', 'maxLifetime': 10.0, 'penetration': 999, 'ricochetCount': 0, 'returnToOrigin': True, 'returnDelay': 1.0}
- elif weapon_type == 'ricochet_piercing':
- base_config['hitEffects'] = [
- {'type': 'ricochet_damage', 'priority': 1, 'params': {'damage': damage, 'ricochetCount': 2, 'ricochetAngle': 45}},
- {'type': 'pierce_damage', 'priority': 2, 'params': {'damage': damage, 'pierceCount': 3}}
- ]
- base_config['lifecycle'] = {'type': 'ricochet_counter', 'maxLifetime': 8.0, 'penetration': 3, 'ricochetCount': 3, 'returnToOrigin': False}
-
- return base_config
-
- def _generate_in_game_cost_config(self, item, weapon_id, rarity):
- """从Excel表中读取方块价格配置"""
- try:
- # 从游戏内成本配置表中读取价格配置
- cost_config = self._load_in_game_cost_config(weapon_id)
-
- if cost_config:
- base_cost_per_grid = cost_config.get('基础每格成本', 10)
- shape_costs = {
- 'I': cost_config.get('I形状', base_cost_per_grid * 2),
- 'H-I': cost_config.get('H-I形状', base_cost_per_grid * 2),
- 'L': cost_config.get('L形状', base_cost_per_grid * 3),
- 'S': cost_config.get('S形状', base_cost_per_grid * 4),
- 'D-T': cost_config.get('D-T形状', base_cost_per_grid * 4)
- }
- print(f"为武器 {weapon_id} 从Excel表中读取方块价格配置")
- else:
- # 如果在Excel表中找不到配置,使用默认值
- print(f"警告:武器 {weapon_id} 在游戏内成本配置表中未找到,使用默认配置")
- base_cost_per_grid = 10
- shape_costs = {
- 'I': 20,
- 'H-I': 20,
- 'L': 30,
- 'S': 40,
- 'D-T': 40
- }
-
- return {
- 'baseCost': base_cost_per_grid,
- 'shapeCosts': shape_costs
- }
-
- except Exception as e:
- print(f"生成方块价格配置失败: {e} - 武器: {weapon_id}")
- # 返回默认配置
- return {
- 'baseCost': 10,
- 'shapeCosts': {
- 'I': 20,
- 'H-I': 20,
- 'L': 30,
- 'S': 40,
- 'D-T': 40
- }
- }
-
- def _load_in_game_cost_config(self, weapon_id):
- """从Excel表中加载指定武器的游戏内成本配置"""
- try:
- excel_file = self.script_dir / "方块武器配置" / "方块武器配置表.xlsx"
- if not excel_file.exists():
- print(f"Excel文件不存在: {excel_file}")
- return None
-
- # 读取游戏内成本配置工作表
- excel_data = pd.read_excel(excel_file, sheet_name="游戏内成本配置")
-
- # 查找指定武器ID的配置
- for index, row in excel_data.iterrows():
- if index == 0: # 跳过表头
- continue
-
- # 获取武器ID(支持多种字段名)
- current_weapon_id = None
- for id_field in ['武器ID', 'ID', 'weapon_id', 'weaponId']:
- if id_field in row and pd.notna(row[id_field]):
- current_weapon_id = str(row[id_field]).strip()
- break
-
- # 如果没有找到命名字段,使用第一列
- if current_weapon_id is None:
- current_weapon_id = str(row.iloc[0]).strip() if len(row) > 0 else None
-
- if current_weapon_id == weapon_id:
- # 构建成本配置
- cost_config = {}
-
- # 读取基础每格成本
- base_cost_fields = ['基础每格成本', 'baseCost', '基础成本', '武器基础售价']
- for field in base_cost_fields:
- if field in row and pd.notna(row[field]):
- try:
- cost_config['基础每格成本'] = int(float(row[field]))
- break
- except (ValueError, TypeError):
- continue
-
- # 读取各形状成本
- shape_fields = {
- 'I形状': ['I形状', 'I形状成本', 'I_shape_cost', 'I'],
- 'H-I形状': ['H-I形状', 'H-I形状成本', 'HI_shape_cost', 'H-I'],
- 'L形状': ['L形状', 'L形状成本', 'L_shape_cost', 'L'],
- 'S形状': ['S形状', 'S形状成本', 'S_shape_cost', 'S'],
- 'D-T形状': ['D-T形状', 'D-T形状成本', 'DT_shape_cost', 'D-T']
- }
-
- for shape_key, field_names in shape_fields.items():
- for field_name in field_names:
- if field_name in row and pd.notna(row[field_name]):
- try:
- cost_config[shape_key] = int(float(row[field_name]))
- break
- except (ValueError, TypeError):
- continue
-
- return cost_config if cost_config else None
-
- print(f"未找到武器 {weapon_id} 的游戏内成本配置")
- return None
-
- except Exception as e:
- print(f"加载游戏内成本配置失败: {e}")
- return None
-
- def _generate_visual_config(self, weapon_id, weapon_name, item=None):
- """生成视觉配置"""
- # 根据武器ID生成图片编号
- weapon_sprite_map = {
- 'pea_shooter': '001-1',
- 'sharp_carrot': '002',
- 'saw_grass': '003',
- 'watermelon_bomb': '007',
- 'boomerang_plant': '004',
- 'hot_pepper': '005',
- 'cactus_shotgun': '008',
- 'okra_missile': '006',
- 'mace_club': '009'
- }
-
- sprite_id = weapon_sprite_map.get(weapon_id, '001')
-
- visual_config = {
- 'weaponSprites': {
- 'I': f'images/PlantsSprite/{sprite_id}',
- 'H-I': f'images/PlantsSprite/{sprite_id}',
- 'L': f'images/PlantsSprite/{sprite_id}',
- 'S': f'images/PlantsSprite/{sprite_id}',
- 'D-T': f'images/PlantsSprite/{sprite_id}'
- },
- 'fireSound': f'audio/{weapon_id}_shot'
- }
-
- # 从item中获取trailEffect,如果有的话添加到visualConfig中
- if item and 'visualConfig' in item and 'trailEffect' in item['visualConfig']:
- trail_effect = item['visualConfig']['trailEffect']
- if trail_effect and trail_effect != 'NaN' and str(trail_effect).strip():
- visual_config['trailEffect'] = trail_effect
- print(f"为武器 {weapon_id} 在visualConfig中添加拖尾特效: {trail_effect}")
-
- return visual_config
-
- def _convert_skill_data(self, item):
- """转换技能数据格式"""
- try:
- # 支持多种字段名格式(中文和英文)
- skill_id = item.get('id', item.get('技能ID', ''))
- skill_name = item.get('name', item.get('技能名称', ''))
- print(f"正在转换技能数据: {skill_id} - {skill_name}")
-
- # 检查必要字段
- if not skill_id:
- print(f"跳过无效技能数据: 缺少技能ID - {item}")
- return None
-
- result = {
- 'id': skill_id,
- 'name': skill_name,
- 'description': item.get('description', item.get('技能描述', '')),
- 'iconPath': item.get('iconPath', item.get('图标路径', '')),
- 'maxLevel': item.get('maxLevel', item.get('最大等级', 1)),
- 'currentLevel': item.get('currentLevel', item.get('当前等级', 0)),
- 'priceReduction': item.get('priceReduction', item.get('价格减少', 0.0)),
- 'critChanceIncrease': item.get('critChanceIncrease', item.get('暴击几率增加', 0.0)),
- 'critDamageBonus': item.get('critDamageBonus', item.get('暴击伤害加成', 0.0)),
- 'healthIncrease': item.get('healthIncrease', item.get('生命值增加', 0.0)),
- 'multiShotChance': item.get('multiShotChance', item.get('多重射击几率', 0.0)),
- 'energyGainIncrease': item.get('energyGainIncrease', item.get('能量加成', 0.0)),
- 'ballSpeedIncrease': item.get('ballSpeedIncrease', item.get('速度提升', 0.0))
- }
- print(f"成功转换技能数据: {result}")
- return result
- except Exception as e:
- print(f"转换技能数据失败: {e} - 数据: {item}")
- return None
-
- def _convert_level_data(self, item):
- """转换关卡数据格式"""
- try:
- # 如果item已经是完整的关卡数据结构(来自多工作表解析),直接返回
- if isinstance(item, dict) and 'waves' in item:
- return item
-
- # 处理传统的单行数据格式
- # 处理可用武器字符串,转换为数组(支持中英文字段名)
- available_weapons = []
- weapons_field = item.get('weapons', item.get('可用武器', ''))
- if weapons_field:
- weapons_str = str(weapons_field)
- # 支持逗号、顿号、分号等多种分隔符
- for separator in ['、', ',', ',', ';', ';']:
- if separator in weapons_str:
- available_weapons = [weapon.strip() for weapon in weapons_str.split(separator)]
- break
- # 如果没有找到分隔符,则作为单个武器处理
- if not available_weapons:
- available_weapons = [weapons_str.strip()]
-
- # 获取关卡ID,用于读取现有配置(支持中英文字段名)
- level_id = str(item.get('levelId', item.get('关卡ID', '')))
-
- # 尝试读取现有的关卡配置文件,保留waves数据
- existing_waves = []
- existing_data = {}
- if level_id:
- try:
- levels_dir = self.project_root / "assets/resources/data/levels"
- level_file = levels_dir / f"{level_id}.json"
- if level_file.exists():
- with open(level_file, 'r', encoding='utf-8') as f:
- existing_data = json.load(f)
- existing_waves = existing_data.get('waves', [])
- print(f"保留现有关卡 {level_id} 的 {len(existing_waves)} 个波次数据")
- except Exception as e:
- print(f"读取现有关卡配置时出错: {e}")
-
- # 构建新的关卡数据,保留现有的waves(支持中英文字段名)
- level_data = {
- "levelId": level_id,
- "name": str(item.get('name', item.get('关卡名称', existing_data.get('name', '')))),
- "scene": str(item.get('scene', item.get('场景', existing_data.get('scene', '')))),
- "description": str(item.get('description', item.get('描述', existing_data.get('description', '')))),
- "backgroundImage": str(item.get('backgroundImage', item.get('关卡背景图路径', existing_data.get('backgroundImage', 'images/LevelBackground/BG1')))),
- "weapons": available_weapons if available_weapons else existing_data.get('weapons', existing_data.get('availableWeapons', [])),
- "timeLimit": int(item.get('timeLimit', item.get('时间限制', existing_data.get('timeLimit', 300)))),
- "difficulty": str(item.get('difficulty', item.get('难度', existing_data.get('difficulty', 'normal')))),
- "healthMultiplier": float(item.get('healthMultiplier', item.get('生命倍数', existing_data.get('healthMultiplier', 1.0)))),
- "waves": existing_waves # 保留现有的waves数据
- }
-
- # 添加可选字段(如果存在,支持中英文字段名)
- coin_reward = item.get('coinReward', item.get('钞票奖励'))
- if coin_reward is not None:
- level_data["coinReward"] = int(coin_reward)
- elif 'coinReward' in existing_data:
- level_data["coinReward"] = existing_data['coinReward']
-
- diamond_reward = item.get('diamondReward', item.get('钻石奖励'))
- if diamond_reward is not None:
- level_data["diamondReward"] = int(diamond_reward)
- elif 'diamondReward' in existing_data:
- level_data["diamondReward"] = existing_data['diamondReward']
-
- return level_data
-
- except Exception as e:
- print(f"转换关卡数据时出错: {e}")
- return None
-
- def fix_enemies_json(self):
- """修复enemies.json文件 - 删除attack字段"""
- try:
- # 获取enemies.json路径
- enemies_json_path = Path(self.json_path_var.get()) if self.json_path_var.get() else None
- if not enemies_json_path or not enemies_json_path.exists():
- # 尝试默认路径
- enemies_json_path = Path(os.path.dirname(__file__)).parent / "enemies.json"
- if not enemies_json_path.exists():
- messagebox.showerror("错误", "未找到enemies.json文件")
- return False
-
- # 备份原文件
- backup_path = f"{enemies_json_path}.backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
- with open(enemies_json_path, 'r', encoding='utf-8') as src:
- with open(backup_path, 'w', encoding='utf-8') as dst:
- dst.write(src.read())
-
- # 读取JSON文件
- with open(enemies_json_path, 'r', encoding='utf-8') as f:
- data = json.load(f)
-
- # 递归删除attack字段
- def remove_attack_fields(obj):
- if isinstance(obj, dict):
- if 'attack' in obj:
- del obj['attack']
- for value in obj.values():
- remove_attack_fields(value)
- elif isinstance(obj, list):
- for item in obj:
- remove_attack_fields(item)
-
- remove_attack_fields(data)
-
- # 写回文件
- with open(enemies_json_path, 'w', encoding='utf-8') as f:
- json.dump(data, f, ensure_ascii=False, indent=2)
-
- messagebox.showinfo("成功", f"已修复enemies.json文件\n备份文件: {backup_path}\n已删除所有attack字段")
- return True
-
- except Exception as e:
- messagebox.showerror("错误", f"修复enemies.json失败: {str(e)}")
- return False
-
- def auto_import_enemies_from_excel(self):
- """自动从Excel导入敌人配置"""
- try:
- # 查找敌人配置表.xlsx
- excel_dir = Path(os.path.dirname(__file__))
- excel_file = excel_dir / "敌人配置表.xlsx"
-
- if not excel_file.exists():
- messagebox.showerror("错误", f"未找到敌人配置表.xlsx文件: {excel_file}")
- return False
-
- # 读取Excel文件
- if not PANDAS_AVAILABLE:
- messagebox.showerror("错误", "pandas未安装,无法读取Excel文件")
- return False
-
- all_sheets = pd.read_excel(excel_file, sheet_name=None)
-
- # 解析敌人配置
- enemies_config = self.parse_enemy_multi_sheet_data(all_sheets, "敌人配置表.xlsx")
-
- if not enemies_config:
- messagebox.showerror("错误", "未能解析到任何敌人配置")
- return False
-
- # 获取输出路径
- enemies_json_path = Path(self.json_path_var.get()) if self.json_path_var.get() else None
- if not enemies_json_path:
- enemies_json_path = excel_dir.parent / "enemies.json"
-
- # 备份现有文件
- if enemies_json_path.exists():
- backup_path = f"{enemies_json_path}.backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
- with open(enemies_json_path, 'r', encoding='utf-8') as src:
- with open(backup_path, 'w', encoding='utf-8') as dst:
- dst.write(src.read())
-
- # 写入新配置
- with open(enemies_json_path, 'w', encoding='utf-8') as f:
- json.dump(enemies_config, f, ensure_ascii=False, indent=2)
-
- messagebox.showinfo("成功", f"已成功导入{len(enemies_config)}个敌人配置到: {enemies_json_path}")
- return True
-
- except Exception as e:
- messagebox.showerror("错误", f"自动导入敌人配置失败: {str(e)}")
- return False
-
- def restore_default_config(self):
- """恢复默认配置"""
- result = messagebox.askyesno("确认", "确定要恢复默认配置吗?\n当前配置将被覆盖!")
- if not result:
- return
-
- try:
- if not self.selected_json_path:
- messagebox.showwarning("警告", "请先选择JSON输出路径")
- return
-
- json_path = Path(self.selected_json_path)
-
- # 备份当前配置
- if json_path.exists():
- backup_path = json_path.parent / f"ballController_backup_before_default_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
- with open(json_path, 'r', encoding='utf-8') as f:
- content = f.read()
- with open(backup_path, 'w', encoding='utf-8') as f:
- f.write(content)
-
- # 写入默认配置
- with open(json_path, 'w', encoding='utf-8') as f:
- json.dump(self.default_config, f, indent=2, ensure_ascii=False)
-
- messagebox.showinfo("成功", "已恢复默认配置")
- self.status_var.set("已恢复默认配置")
-
- # 刷新预览
- self.clear_selection()
-
- except Exception as e:
- messagebox.showerror("错误", f"恢复默认配置失败: {e}")
-
- def run(self):
- """运行应用"""
- self.root.mainloop()
- def main():
- """主函数"""
- try:
- app = ConfigManagerGUI()
- app.run()
- except Exception as e:
- print(f"启动应用失败: {e}")
- input("按回车键退出...")
- if __name__ == "__main__":
- main()
|