2025-11-11 15:09:52 +08:00

445 lines
20 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import os
import csv
from io import StringIO
# function: read_text_file
def read_text_file(file_path):
"""尝试用不同编码读取文本文件"""
encodings = ['utf-8', 'gbk', 'gb2312', 'latin-1']
for encoding in encodings:
try:
with open(file_path, 'r', encoding=encoding) as file:
return file.read()
except UnicodeDecodeError:
continue
except Exception as e:
print(f"用编码 {encoding} 读取时出错: {e}")
return None
# function: parse_csv_to_dict
def parse_csv_to_dict(csv_str):
"""将CSV字符串解析为字典列表"""
string_io = StringIO(csv_str)
csv_reader = csv.DictReader(string_io)
data = []
for row in csv_reader:
data.append(dict(row)) # 转换为普通字典
return data
def has_content(s):
return s is not None and len(s) > 0
# function: calc_story_end_idx
def calc_story_range_list(csv_data):
story_ranges = []
for i, item_data in enumerate(csv_data):
if has_content(item_data['关卡']):
story_ranges.append(i)
story_ranges.append(len(csv_data))
story_range_list = []
for i in range(len(story_ranges)-1):
story_range_list.append({'start': story_ranges[i], 'end': story_ranges[i+1]})
return story_range_list
# functino: make_story_data
def make_story_data(csv_data, start_idx, end_idx):
start_csv_line = csv_data[start_idx]
story_data = {
'id': start_csv_line['ID'],
'level': start_csv_line['关卡'],
'task_title': start_csv_line['聊天任务标题'],
'story_type': start_csv_line['剧情分类'],
'B1_add_heart': start_csv_line['分支1好感度'],
'B1_reward': start_csv_line['分支1奖励'],
'B1_scripts': [],
'B2_add_heart': start_csv_line['分支2好感度'],
'B2_reward': start_csv_line['分支2奖励'],
'B2_scripts': [],
'B3_add_heart': start_csv_line['分支3好感度'],
'B3_reward': start_csv_line['分支3奖励'],
'B3_scripts': [],
}
idx = 0
for i in range(start_idx, end_idx):
csv_line = csv_data[i]
# 分支1
story_data['B1_scripts'].append({'idx': idx, 'name': csv_line['分支1角色'], 'script': csv_line['分支1台词']})
# if csv_line['分支1角色'] != '' and csv_line['分支1台词'] != '':
# story_data['B1_scripts'].append({'idx': idx, 'name': csv_line['分支1角色'], 'script': csv_line['分支1台词']})
# 分支2
story_data['B2_scripts'].append({'idx': idx, 'name': csv_line['分支2角色'], 'script': csv_line['分支2台词']})
# if csv_line['分支2角色'] != '' and csv_line['分支2台词'] != '':
# story_data['B2_scripts'].append({'idx': idx, 'name': csv_line['分支2角色'], 'script': csv_line['分支2台词']})
# 分支3
story_data['B3_scripts'].append({'idx': idx, 'name': csv_line['分支3角色'], 'script': csv_line['分支3台词']})
# if csv_line['分支3角色'] != '' and csv_line['分支3台词'] != '':
# story_data['B3_scripts'].append({'idx': idx, 'name': csv_line['分支3角色'], 'script': csv_line['分支3台词']})
idx = idx + 1
return story_data
# functino: make_language_key
g_language_strs = {}
def make_language_key(idx, str):
key = "ST%s"%(idx)
g_language_strs[key] = str
return key
# functino: get_language_name_key
g_actor_info_dict = [
{'NameKey': 'player', 'Img': 'player', 'Name': '玩家'},
{'NameKey': 'actor_nvshen', 'Img': 'HeroDraw/Npc_01', 'Name': '女神'},
{'NameKey': 'actor_weier', 'Img': 'HeroDraw/Npc_02', 'Name': '薇儿'},
{'NameKey': 'actor_xifu', 'Img': 'HeroDraw/Npc_03', 'Name': '希芙'},
{'NameKey': 'actor_furuiya', 'Img': 'HeroDraw/Npc_04', 'Name': '芙蕾雅'},
{'NameKey': 'actor_yideng', 'Img': 'HeroDraw/Npc_05', 'Name': '伊登'},
{'NameKey': 'actor_haila', 'Img': 'HeroDraw/Npc_06', 'Name': '海拉'},
{'NameKey': 'actor_waerjili', 'Img': 'HeroDraw/Npc_07', 'Name': '瓦尔基里'}
]
def get_actor_info(name):
for value in g_actor_info_dict:
if value['Name'] == name:
return value
print("[get_actor_info] no find actor info name: " + name)
return None
# functino: create_story_cfg
def create_story_cfg(story_id, story_type, story_selection_texts, story_selection_next,
story_force_guide_group, story_condition):
return {
'Id': int(story_id),
'StoryId': int(story_id),
'PreStoryId': 0,
'Type': int(story_type),
'SelectionTexts': story_selection_texts,
'SelectionNext': story_selection_next,
'NeedBg': False,
'HideUi': False,
'Transition': False,
'TransitionType': 3,
'Skip': False,
'PlayInterval': 5,
'ProgramControl': True,
'NeedSave': len(story_selection_texts) > 0,
'ForceGuideGroup': story_force_guide_group,
'Condition': story_condition,
'Group': '',
'DataCheck': ''
}
# function: create_stroyPerformCfg, act_type: 'RoleMovein', 'RoleSpeaking', 'RoleMoveout'
def create_stroyPerformCfg(script_info_list, story_id, story_type, start_idx, end_idx):
cfgs = []
idx = 0
if start_idx <= end_idx:
for script_info in script_info_list[start_idx:end_idx+1]:
if has_content(script_info['script']):
idx += 1
story_perform_id = "%s%d" % (story_id, idx)
SpeakSite = 1
SpeakId = make_language_key(story_perform_id, script_info['script'])
LeftName = ''
LeftImg = ''
LeftAct = ''
RightName = ''
RightImg = ''
RightAct = ''
actor_info = get_actor_info(script_info['name'])
if actor_info == None:
print("ERR: not find actor name, story_id=" + story_id)
if actor_info['NameKey'] == 'player':
SpeakSite = 2
LeftName = ''
LeftImg = ''
LeftAct = 'RoleMoveout'
RightName = actor_info['NameKey']
RightImg = actor_info['Img']
RightAct = 'RoleMovein'
else:
SpeakSite = 1
LeftName = actor_info['NameKey']
LeftImg = actor_info['Img']
LeftAct = 'RoleMovein'
RightName = ''
RightImg = ''
RightAct = 'RoleMoveout'
cfgs.append({
'Id': story_perform_id, 'StoryId': story_id, 'Type': 0, 'LeftAct': LeftAct, 'LeftActBgTime': 0,
'RightAct': RightAct, 'RightActBgTime': 0, 'LeftName': LeftName, 'LeftNameColour': 'title02', 'LeftImg': LeftImg,
'RightName': RightName, 'RightNameColour': 'title01', 'RightImg': RightImg, 'SpeakSite': SpeakSite, 'SpeakId': SpeakId, 'SpeakTime': 0,
'WordTime': 10, 'Camp': '', 'CfgId': '', 'Dir': '', 'DirStartTime': '',
'AniName': '', 'AniStartTime': '', 'Eff': '', 'EffStartTime': '', 'OverTime': 0
})
# 如果是对话框对话,就要在首尾添加入场配置和退场配置
if story_type == 0:
story_perform_id = "%s%d" % (story_id, 0)
LeftName = cfgs[0]['LeftName']
LeftImg = cfgs[0]['LeftImg']
RightName = cfgs[0]['RightName']
RightImg = cfgs[0]['RightImg']
cfgs.insert(0, {
'Id': story_perform_id, 'StoryId': story_id, 'Type': 0, 'LeftAct': 'RoleMovein', 'LeftActBgTime': 0,
'RightAct': 'RoleMovein', 'RightActBgTime': 0, 'LeftName': LeftName, 'LeftNameColour': 'title02', 'LeftImg': LeftImg,
'RightName': RightName, 'RightNameColour': 'title01', 'RightImg': RightImg, 'SpeakSite': 0, 'SpeakId': '', 'SpeakTime': 0,
'WordTime': 10, 'Camp': '', 'CfgId': '', 'Dir': '', 'DirStartTime': '',
'AniName': '', 'AniStartTime': '', 'Eff': '', 'EffStartTime': '', 'OverTime': 0
})
story_perform_id = "%s%d" % (story_id, len(cfgs))
LeftName = cfgs[-1]['LeftName']
LeftImg = cfgs[-1]['LeftImg']
RightName = cfgs[-1]['RightName']
RightImg = cfgs[-1]['RightImg']
cfgs.append({
'Id': story_perform_id, 'StoryId': story_id, 'Type': 0, 'LeftAct': 'RoleMoveout', 'LeftActBgTime': 0,
'RightAct': 'RoleMoveout', 'RightActBgTime': 0, 'LeftName': LeftName, 'LeftNameColour': 'title02', 'LeftImg': LeftImg,
'RightName': RightName, 'RightNameColour': 'title01', 'RightImg': RightImg, 'SpeakSite': 0, 'SpeakId': '', 'SpeakTime': 0,
'WordTime': 10, 'Camp': '', 'CfgId': '', 'Dir': '', 'DirStartTime': '',
'AniName': '', 'AniStartTime': '', 'Eff': '', 'EffStartTime': '', 'OverTime': 0
})
for i, cfg_item in enumerate(cfgs):
if cfg_item == None:
print("----cfg_item is None-----")
print(cfg_item)
return cfgs
# functino: make_story_cfg
def make_story_cfg(story_cfgs, stroyPerformCfgV2_cfgs, story_data):
ID = story_data['id']
level = story_data['level']
story_type = 0
if story_data['story_type'] == '对话':
story_type = 0
elif story_data['story_type'] == '聊天':
story_type = 1
story_force_guide_group = 0
story_condition = ''
has_multi_options = False
for i in range(len(story_data['B1_scripts'])):
if has_content(story_data['B1_scripts'][i]['script']) and has_content(story_data['B2_scripts'][i]['script']) and has_content(story_data['B3_scripts'][i]['script']):
story_id = "%s" % (ID)
story1_id = "%s01" % (ID)
story2_id = "%s02" % (ID)
story3_id = "%s03" % (ID)
story1_opt_1 = make_language_key(story1_id, story_data['B1_scripts'][i]['script'])
story1_opt_2 = make_language_key(story2_id, story_data['B2_scripts'][i]['script'])
story1_opt_3 = make_language_key(story3_id, story_data['B3_scripts'][i]['script'])
story1_selection_texts = "%s;%s;%s" % (story1_opt_1, story1_opt_2, story1_opt_3)
story1_selection_next = "%s;%s;%s" % (story1_id, story2_id, story3_id)
story_cfgs.append(create_story_cfg(story_id, story_type, story1_selection_texts, story1_selection_next, story_force_guide_group, story_condition))
story_cfgs.append(create_story_cfg(story1_id, story_type, '', '', 0, ''))
story_cfgs.append(create_story_cfg(story2_id, story_type, '', '', 0, ''))
story_cfgs.append(create_story_cfg(story3_id, story_type, '', '', 0, ''))
stroyPerformCfgV2_cfgs.extend(create_stroyPerformCfg(story_data['B1_scripts'], story_id, story_type, 0, i-1))
stroyPerformCfgV2_cfgs.extend(create_stroyPerformCfg(story_data['B1_scripts'], story1_id, story_type, i+1, len(story_data['B1_scripts'])-1))
stroyPerformCfgV2_cfgs.extend(create_stroyPerformCfg(story_data['B2_scripts'], story2_id, story_type, i+1, len(story_data['B2_scripts'])-1))
stroyPerformCfgV2_cfgs.extend(create_stroyPerformCfg(story_data['B3_scripts'], story3_id, story_type, i+1, len(story_data['B3_scripts'])-1))
has_multi_options = True
break
if has_content(story_data['B1_scripts'][i]['script']) and has_content(story_data['B2_scripts'][i]['script']) and has_content(story_data['B3_scripts'][i]['script']) == False:
story_id = "%s" % (ID)
story1_id = "%s01" % (ID)
story2_id = "%s02" % (ID)
story1_opt_1 = make_language_key(story1_id, story_data['B1_scripts'][i]['script'])
story1_opt_2 = make_language_key(story2_id, story_data['B2_scripts'][i]['script'])
story1_selection_texts = "%s;%s" % (story1_opt_1, story1_opt_2)
story1_selection_next = "%s;%s" % (story1_id, story2_id)
story_cfgs.append(create_story_cfg(story_id, story_type, story1_selection_texts, story1_selection_next, story_force_guide_group, story_condition))
story_cfgs.append(create_story_cfg(story1_id, story_type, '', '', 0, ''))
story_cfgs.append(create_story_cfg(story2_id, story_type, '', '', 0, ''))
stroyPerformCfgV2_cfgs.extend(create_stroyPerformCfg(story_data['B1_scripts'], story_id, story_type, 0, i-1))
stroyPerformCfgV2_cfgs.extend(create_stroyPerformCfg(story_data['B1_scripts'], story1_id, story_type, i+1, len(story_data['B1_scripts'])-1))
stroyPerformCfgV2_cfgs.extend(create_stroyPerformCfg(story_data['B2_scripts'], story2_id, story_type, i+1, len(story_data['B2_scripts'])-1))
has_multi_options = True
break
if has_multi_options == False:
story_id = "%s" % (ID)
story_cfgs.append(create_story_cfg(story_id, story_type, '', '', story_force_guide_group, story_condition))
stroyPerformCfgV2_cfgs.extend(create_stroyPerformCfg(story_data['B1_scripts'], story_id, story_type, 0, len(story_data['B1_scripts'])-1))
# function:
# generate_StoryCfgV2
def generate_StoryCfgV2(out_path, data):
with open(out_path+'/StoryCfgV2.csv', 'w', newline='', encoding='utf-8') as file:
fieldnames = ['序列ID', '触发剧情ID', '前置剧情', '剧情类型0是对话其他ChatNpcId',
'选项显示', '继续下一个剧情', '是否底图', '是否隐藏UI',
'是否转场', '转场类型', '是否可跳过', '剧情播放间隔时间s',
'是否需要程序驱动', '是否存盘', '强制引导', '触发条件', '同组', '数据判断']
row1 = ['Id', 'StoryId', 'PreStoryId', 'Type',
'SelectionTexts', 'SelectionNext', 'NeedBg', 'HideUi',
'Transition', 'TransitionType', 'Skip', 'PlayInterval',
'ProgramControl', 'NeedSave', 'ForceGuideGroup', 'Condition', 'Group', 'DataCheck']
row2 = ['int', 'int', 'int', 'int',
'list', 'list', 'bool', 'bool',
'bool', 'int', 'bool', 'int',
'bool', 'bool', 'int', 'list', 'list', 'list']
csv_data = []
csv_data.append(fieldnames)
csv_data.append(row1)
csv_data.append(row2)
for i, data_item in enumerate(data):
csv_data.append(data_item.values())
writer = csv.writer(file)
writer.writerows(csv_data)
#print("\n--- StoryCfgV2.csv ---")
#print(csv_data)
# generate_StroyPerformCfgV2
def generate_StroyPerformCfgV2(out_path, data):
with open(out_path+'/StroyPerformCfgV2.csv', 'w', newline='', encoding='utf-8') as file:
fieldnames = ['序列ID', '剧情ID', '类型', '左侧行为',
'左侧行为开始时间', '右侧行为', '右侧行为时间', '左侧姓名',
'左侧姓名颜色', '左侧半身像', '右侧姓名', '右侧姓名颜色',
'阵营', '配置ID', '面向', '面向开始时间',
'动作名字', '动作开始时间', '特效ID', '特效开始时间', '结束时间']
row1 = ['Id', 'StoryId', 'Type', 'LeftAct',
'LeftActBgTime', 'RightAct', 'RightActBgTime', 'LeftName',
'LeftNameColour', 'LeftImg', 'RightName', 'RightNameColour',
'RightImg', 'SpeakSite', 'SpeakId', 'SpeakTime',
'WordTime', 'Camp', 'CfgId', 'Dir',
'DirStartTime', 'AniName', 'AniStartTime', 'Eff', 'EffStartTime', 'OverTime']
row2 = ['int', 'int', 'int', 'string',
'int', 'string', 'int', 'string',
'string', 'string', 'string', 'string',
'string', 'int', 'string', 'int',
'int', 'list', 'list', 'list',
'list', 'list', 'list', 'list', 'list', 'int']
csv_data = []
csv_data.append(fieldnames)
csv_data.append(row1)
csv_data.append(row2)
print("\n--- StroyPerformCfgV2.csv ---")
for i, data_item in enumerate(data):
if data_item == None:
print("data_item: None " + str(i))
csv_data.append(data_item.values())
writer = csv.writer(file)
writer.writerows(csv_data)
# generate_LanguagePackageV2_cn
def generate_LanguagePackageV2_cn(out_path, data):
with open(out_path+'/../../../Assets/Content/Config/LanguagePackageV2_cn.csv', 'w', newline='', encoding='utf-8') as file:
fieldnames = ['key', '内容']
row1 = ['key', 'Language']
row2 = ['string', 'string']
csv_data = []
csv_data.append(fieldnames)
csv_data.append(row1)
csv_data.append(row2)
for actor_info in g_actor_info_dict:
csv_data.append([actor_info['NameKey'], actor_info['Name']])
for key, value in data.items():
csv_data.append([key, value])
writer = csv.writer(file)
writer.writerows(csv_data)
print("\n--- LanguagePackageV2_cn.csv ---")
#print(csv_data)
# functino: generate_config
def generate_config(story_data_list, out_path):
StoryCfgV2_lines = []
StroyPerformCfgV2_lines = []
LanguagePackage_cn_lines = []
for i, story_data in enumerate(story_data_list):
make_story_cfg(StoryCfgV2_lines, StroyPerformCfgV2_lines, story_data)
generate_StoryCfgV2(out_path, StoryCfgV2_lines)
generate_StroyPerformCfgV2(out_path, StroyPerformCfgV2_lines)
generate_LanguagePackageV2_cn(out_path, g_language_strs)
# main入口
if __name__ == "__main__":
g_script_dir = os.path.dirname(os.path.abspath(__file__))
g_input_dir = os.path.join(g_script_dir, 'input')
g_out_dir = os.path.join(g_script_dir, 'output')
g_input_dir = g_input_dir.replace('\\', '/')
g_out_dir = g_out_dir.replace('\\', '/')
print('g_input_dir: ' + g_input_dir)
print('g_out_dir: ' + g_out_dir)
g_story_data_list = []
for filename in os.listdir(g_input_dir):
if filename.endswith('.csv'):
file_path = os.path.join(g_input_dir, filename)
file_content = read_text_file(file_path)
if file_content is not None:
print(f"=== {filename} ===")
# 解析csv数据为Dict
csv_data = parse_csv_to_dict(file_content)
# 把csv数据分隔为一段段剧情并生成剧情数据列表story_data_list
story_range_list = calc_story_range_list(csv_data)
for i, story_range in enumerate(story_range_list):
story_data = make_story_data(csv_data, story_range['start'], story_range['end'])
g_story_data_list.append(story_data)
print("--- story_data ---")
print(story_data)
print()
# 根据story_data_list生成配置文件
generate_config(g_story_data_list, g_out_dir)
else:
print(f"无法读取文件: {filename}")