1022 lines
40 KiB
GDScript
1022 lines
40 KiB
GDScript
@tool
|
|
extends HSplitContainer
|
|
|
|
var folderStructure
|
|
|
|
var timelineFolderBreakdown:Dictionary = {}
|
|
var characterFolderBreakdown:Dictionary = {}
|
|
var definitionFolderBreakdown:Dictionary = {}
|
|
var themeFolderBreakdown:Dictionary = {}
|
|
var definitionsFile = {}
|
|
var flatDefinitionsFile = {}
|
|
|
|
var conversionRootFolder = "res://converted-dialogic"
|
|
|
|
var contents
|
|
|
|
var conversionReady = false
|
|
|
|
var varSubsystemInstalled = false
|
|
var anchorNames = {}
|
|
var prefixCharacters = false
|
|
|
|
func refresh():
|
|
pass
|
|
|
|
func _on_verify_pressed():
|
|
|
|
%OutputLog.text = ""
|
|
|
|
if FileAccess.file_exists("res://dialogic/settings.cfg"):
|
|
%OutputLog.text += "[√] Dialogic 1.x data [color=green]found![/color]\r\n"
|
|
|
|
if FileAccess.file_exists("res://dialogic/definitions.json"):
|
|
%OutputLog.text += "[√] Dialogic 1.x definitions [color=green]found![/color]\r\n"
|
|
else:
|
|
%OutputLog.text += "[X] Dialogic 1.x definitions [color=red]not found![/color]\r\n"
|
|
%OutputLog.text += "Please copy the res://dialogic folder from your Dialogic 1.x project into this project and try again.\r\n"
|
|
return
|
|
|
|
if FileAccess.file_exists("res://dialogic/settings.cfg"):
|
|
%OutputLog.text += "[√] Dialogic 1.x settings [color=green]found![/color]\r\n"
|
|
else:
|
|
%OutputLog.text += "[X] Dialogic 1.x settings [color=red]not found![/color]\r\n"
|
|
%OutputLog.text += "Please copy the res://dialogic folder from your Dialogic 1.x project into this project and try again.\r\n"
|
|
return
|
|
|
|
%OutputLog.text += "\r\n"
|
|
|
|
%OutputLog.text += "Verifying data:\r\n"
|
|
var file := FileAccess.open("res://dialogic/folder_structure.json", FileAccess.READ)
|
|
var fileContent = file.get_as_text()
|
|
var json_object = JSON.new()
|
|
|
|
var error = json_object.parse(fileContent)
|
|
|
|
if error == OK:
|
|
folderStructure = json_object.get_data()
|
|
#print(folderStructure)
|
|
else:
|
|
print("JSON Parse Error: ", json_object.get_error_message(), " in ", error, " at line ", json_object.get_error_line())
|
|
%OutputLog.text += "Dialogic 1.x folder structure [color=red]could not[/color] be read!\r\n"
|
|
%OutputLog.text += "Please check the output log for the error the JSON parser encountered.\r\n"
|
|
return
|
|
#folderStructure = json_object.get_data()
|
|
|
|
%OutputLog.text += "Dialogic 1.x folder structure read successfully!\r\n"
|
|
|
|
#I'm going to build a new, simpler tree here, as the folder structure is too complicated
|
|
|
|
|
|
recursive_search("Timeline", folderStructure["folders"]["Timelines"], "/")
|
|
recursive_search("Character", folderStructure["folders"]["Characters"], "/")
|
|
recursive_search("Definition", folderStructure["folders"]["Definitions"], "/")
|
|
recursive_search("Theme", folderStructure["folders"]["Themes"], "/")
|
|
|
|
|
|
%OutputLog.text += "Timelines found: " + str(timelineFolderBreakdown.size()) + "\r\n"
|
|
%OutputLog.text += "Characters found: " + str(characterFolderBreakdown.size()) + "\r\n"
|
|
%OutputLog.text += "Definitions found: " + str(definitionFolderBreakdown.size()) + "\r\n"
|
|
%OutputLog.text += "Themes found: " + str(themeFolderBreakdown.size()) + "\r\n"
|
|
|
|
%OutputLog.text += "\r\n"
|
|
%OutputLog.text += "Verifying count of JSON files for match with folder structure:\r\n"
|
|
|
|
var timelinesDirectory = list_files_in_directory("res://dialogic/timelines")
|
|
if timelinesDirectory.size() == timelineFolderBreakdown.size():
|
|
%OutputLog.text += "Timeline files found: [color=green]" + str(timelinesDirectory.size()) + "[/color]\r\n"
|
|
else:
|
|
%OutputLog.text += "Timeline files found: [color=red]" + str(timelinesDirectory.size()) + "[/color]\r\n"
|
|
%OutputLog.text += "[color=yellow]There may be an issue, please check in Dialogic 1.x to make sure that is correct![/color]\r\n"
|
|
|
|
var characterDirectory = list_files_in_directory("res://dialogic/characters")
|
|
if characterDirectory.size() == characterFolderBreakdown.size():
|
|
%OutputLog.text += "Character files found: [color=green]" + str(characterDirectory.size()) + "[/color]\r\n"
|
|
else:
|
|
%OutputLog.text += "Character files found: [color=red]" + str(characterDirectory.size()) + "[/color]\r\n"
|
|
%OutputLog.text += "[color=yellow]There may be an issue, please check in Dialogic 1.x to make sure that is correct![/color]\r\n"
|
|
|
|
|
|
file = FileAccess.open("res://dialogic/definitions.json",FileAccess.READ)
|
|
fileContent = file.get_as_text()
|
|
json_object = JSON.new()
|
|
|
|
error = json_object.parse(fileContent)
|
|
|
|
if error == OK:
|
|
definitionsFile = json_object.get_data()
|
|
#print(folderStructure)
|
|
else:
|
|
print("JSON Parse Error: ", json_object.get_error_message(), " in ", error, " at line ", json_object.get_error_line())
|
|
%OutputLog.text += "Dialogic 1.x definitions file [color=red]could not[/color] be read!\r\n"
|
|
%OutputLog.text += "Please check the output log for the error the JSON parser encountered.\r\n"
|
|
return
|
|
|
|
|
|
|
|
for variable in definitionsFile["variables"]:
|
|
var varPath = definitionFolderBreakdown[variable["id"]]
|
|
var variableInfo = {}
|
|
variableInfo["type"] = "variable"
|
|
variableInfo["path"] = varPath
|
|
variableInfo["name"] = variable["name"]
|
|
variableInfo["value"] = variable["value"]
|
|
definitionFolderBreakdown[variable["id"]] = variableInfo
|
|
|
|
for variable in definitionsFile["glossary"]:
|
|
var varPath = definitionFolderBreakdown[variable["id"]]
|
|
var variableInfo = {}
|
|
variableInfo["type"] = "glossary"
|
|
variableInfo["path"] = varPath
|
|
variableInfo["name"] = variable["name"]
|
|
variableInfo["text"] = variable["text"]
|
|
variableInfo["title"] = variable["title"]
|
|
variableInfo["extra"] = variable["extra"]
|
|
variableInfo["glossary_type"] = variable["type"]
|
|
definitionFolderBreakdown[variable["id"]] = variableInfo
|
|
|
|
if (definitionsFile["glossary"].size() + definitionsFile["variables"].size()) == definitionFolderBreakdown.size():
|
|
%OutputLog.text += "Definitions found: [color=green]" + str((definitionsFile["glossary"].size() + definitionsFile["variables"].size())) + "[/color]\r\n"
|
|
%OutputLog.text += " • Glossaries found: " + str(definitionsFile["glossary"].size()) + "\r\n"
|
|
%OutputLog.text += " • Variables found: " + str(definitionsFile["variables"].size()) + "\r\n"
|
|
else:
|
|
%OutputLog.text += "Definition files found: [color=red]" + str(definitionsFile.size()) + "[/color]\r\n"
|
|
%OutputLog.text += "[color=yellow]There may be an issue, please check in Dialogic 1.x to make sure that is correct![/color]\r\n"
|
|
|
|
var themeDirectory = list_files_in_directory("res://dialogic/themes")
|
|
if themeDirectory.size() == themeFolderBreakdown.size():
|
|
%OutputLog.text += "Theme files found: [color=green]" + str(themeDirectory.size()) + "[/color]\r\n"
|
|
else:
|
|
%OutputLog.text += "Theme files found: [color=red]" + str(themeDirectory.size()) + "[/color]\r\n"
|
|
%OutputLog.text += "[color=yellow]There may be an issue, please check in Dialogic 1.x to make sure that is correct![/color]\r\n"
|
|
|
|
# dirty check for the variable subsystem, as properly calling has subsystem is complicated currently
|
|
varSubsystemInstalled = file.file_exists("res://addons/dialogic/Events/Variable/event_variable.gd")
|
|
|
|
if !varSubsystemInstalled:
|
|
%OutputLog.text += "[color=yellow]Variable subsystem is not present in this Dialogic! Variables will not be converted![/color]"
|
|
|
|
%OutputLog.text += "\r\n"
|
|
|
|
%OutputLog.text += "Initial integrity check completed!\r\n"
|
|
|
|
|
|
|
|
if DirAccess.dir_exists_absolute(conversionRootFolder):
|
|
%OutputLog.text += "[color=yellow]Conversion folder already exists, coverting will overwrite existing files.[/color]\r\n"
|
|
else:
|
|
%OutputLog.text += conversionRootFolder
|
|
%OutputLog.text += "Folders are being created in " + conversionRootFolder + ". Converted files will be located there.\r\n"
|
|
var directory = DirAccess.open("res://")
|
|
directory.make_dir(conversionRootFolder)
|
|
directory.open(conversionRootFolder)
|
|
directory.make_dir("characters")
|
|
directory.make_dir("timelines")
|
|
directory.make_dir("themes")
|
|
|
|
conversionReady = true
|
|
$RightPanel/Begin.disabled = false
|
|
|
|
else:
|
|
%OutputLog.text += "[X] Dialogic 1.x data [color=red]not found![/color]\r\n"
|
|
%OutputLog.text += "Please copy the res://dialogic folder from your Dialogic 1.x project into this project and try again.\r\n"
|
|
|
|
|
|
func list_files_in_directory(path):
|
|
var files = []
|
|
var dir = DirAccess.open(path)
|
|
dir.list_dir_begin()
|
|
|
|
while true:
|
|
var file = dir.get_next()
|
|
if file == "":
|
|
break
|
|
elif not file.begins_with("."):
|
|
if file.ends_with(".json") || file.ends_with(".cfg"):
|
|
files.append(file)
|
|
|
|
dir.list_dir_end()
|
|
return files
|
|
|
|
func recursive_search(currentCheck, currentDictionary, currentFolder):
|
|
for structureFile in currentDictionary["files"]:
|
|
match currentCheck:
|
|
"Timeline": timelineFolderBreakdown[structureFile] = characterNameConversion(currentFolder, false)
|
|
"Character": characterFolderBreakdown[structureFile] = characterNameConversion(currentFolder,false)
|
|
"Definition": definitionFolderBreakdown[structureFile] = characterNameConversion(currentFolder, false)
|
|
"Theme": themeFolderBreakdown[structureFile] = characterNameConversion(currentFolder, false)
|
|
|
|
for structureFolder in currentDictionary["folders"]:
|
|
recursive_search(currentCheck, currentDictionary["folders"][structureFolder], currentFolder + structureFolder + "/")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _on_begin_pressed():
|
|
%OutputLog.text += "-----------------------------------------\r\n"
|
|
%OutputLog.text += "Beginning file conversion:\r\n"
|
|
%OutputLog.text += "\r\n"
|
|
|
|
#Variable conversion needs to be first, to build the lookup table for new style
|
|
#Character conversion needs to be before timelines, so the character names are available
|
|
convertVariables()
|
|
convertCharacters()
|
|
convertTimelines()
|
|
convertGlossaries()
|
|
convertThemes()
|
|
convertSettings()
|
|
|
|
%OutputLog.text += "All conversions complete!\r\n"
|
|
%OutputLog.text += "\r\nPlease check to make sure your timelines all look good. After that, you can remove the /addons/dialogic/Events/Converter folder, as it is no longer needed.\r\n\r\n"
|
|
%OutputLog.text += "Please be aware, Godot may take some time on the next project load to reload all of the Characters and Timelines. This is normal, and should only happen the one time."
|
|
|
|
|
|
func convertTimelines():
|
|
%OutputLog.text += "Converting timelines: \r\n"
|
|
for item in timelineFolderBreakdown:
|
|
var folderPath = timelineFolderBreakdown[item]
|
|
%OutputLog.text += "Timeline " + folderPath + item +": "
|
|
var jsonData = {}
|
|
var file := FileAccess.open("res://dialogic/timelines/" + item, FileAccess.READ)
|
|
var fileContent = file.get_as_text()
|
|
var json_object = JSON.new()
|
|
|
|
var error = json_object.parse(fileContent)
|
|
|
|
if error == OK:
|
|
contents = json_object.get_data()
|
|
var fileName = contents["metadata"]["name"]
|
|
%OutputLog.text += "Name: " + fileName + ", " + str(contents["events"].size()) + " timeline events"
|
|
|
|
|
|
if not DirAccess.dir_exists_absolute(conversionRootFolder + "/timelines" + folderPath):
|
|
var directory = DirAccess.open(conversionRootFolder + "/timelines")
|
|
|
|
var progresiveDirectory = ""
|
|
for pathItem in folderPath.split('/'):
|
|
directory.open(conversionRootFolder + "/timelines" + progresiveDirectory)
|
|
if pathItem!= "":
|
|
progresiveDirectory += "/" + pathItem
|
|
if !directory.dir_exists(conversionRootFolder + "/timelines" + progresiveDirectory):
|
|
directory.make_dir(conversionRootFolder + "/timelines" + progresiveDirectory)
|
|
|
|
#just double check because sometimes its making double slashes at the final filename
|
|
if folderPath.right(1) == "/":
|
|
folderPath = folderPath.left(-1)
|
|
# we will save it as an intermediary file first, then on second pass cleanup make it the .dtl
|
|
var newFilePath = conversionRootFolder + "/timelines" + folderPath + "/" + fileName + ".cnv"
|
|
|
|
file = FileAccess.open(newFilePath, FileAccess.WRITE)
|
|
|
|
# update the new location so we know where second pass items are
|
|
|
|
timelineFolderBreakdown[item] = newFilePath
|
|
|
|
|
|
var processedEvents = 0
|
|
|
|
|
|
var depth = []
|
|
for event in contents["events"]:
|
|
processedEvents += 1
|
|
var eventLine = ""
|
|
|
|
for i in depth:
|
|
eventLine += " "
|
|
|
|
if "dialogic_" in event["event_id"]:
|
|
match event["event_id"]:
|
|
"dialogic_001":
|
|
#Text Event
|
|
var has_character:bool = false
|
|
if event['character'] != "" && event['character']:
|
|
has_character = true
|
|
eventLine += characterFolderBreakdown[event['character']]['searchable_name']
|
|
|
|
if event['portrait'] != "":
|
|
eventLine += "(" + event['portrait'] + ")"
|
|
|
|
eventLine += ": "
|
|
if '\n' in event['text']:
|
|
var splitCount = 0
|
|
var split = event['text'].split('\n')
|
|
for splitItem in split:
|
|
if has_character == false && splitItem.find(' ') > 0 && splitItem.find(':') > 0 && (splitItem.find(' ') > splitItem.find(':')):
|
|
splitItem = splitItem.insert("\\", splitItem.find(':'))
|
|
if splitCount == 0:
|
|
file.store_line(eventLine + splitItem + "\\")
|
|
else:
|
|
file.store_line(splitItem + "\\")
|
|
splitCount += 1
|
|
else:
|
|
var text_line = variableNameConversion(event['text'])
|
|
if has_character == false && text_line.find(' ') > 0 && text_line.find(':') > 0 && (text_line.find(' ') > text_line.find(':')):
|
|
text_line = text_line.insert("\\", text_line.find(':'))
|
|
file.store_string(eventLine + text_line)
|
|
"dialogic_002":
|
|
# Character event
|
|
|
|
#For some reason this is loading as a float, and the match is failing. so hard casting as string
|
|
var eventType:String = str(event['type'])
|
|
|
|
match eventType:
|
|
"0":
|
|
if event['character'] != "":
|
|
eventLine += "Join "
|
|
eventLine += characterFolderBreakdown[event['character']]['searchable_name']
|
|
if (event['portrait'] != ""):
|
|
eventLine += " (" + event['portrait'] + ") "
|
|
|
|
for i in event['position']:
|
|
if event['position'][i] == true:
|
|
#1.x uses positions 0-4, while the default 2.0 scene uses positions 1-5
|
|
eventLine += str(i.to_int() + 1)
|
|
|
|
if (event['animation'] != "[Default]" && event['animation'] != "") || ('z_index' in event) || ('mirror_portrait' in event):
|
|
# Note: due to Anima changes, animations will be converted into a default. Times and wait will be perserved
|
|
eventLine += " ["
|
|
if (event['animation'] != "[Default]" && event['animation'] != ""):
|
|
eventLine += " [animation=\"Instant In Or Out\" "
|
|
eventLine += "length=\"" + str(event['animation_length']) + "\""
|
|
if "animation_wait" in event:
|
|
eventLine += " wait=\"true\""
|
|
|
|
if 'z_index' in event:
|
|
if event['z_index'] != 0:
|
|
eventLine += ' z-index="' + str(event['z_index']) + '"'
|
|
if 'mirror_portrait' in event:
|
|
if event['mirror_portrait']:
|
|
eventLine += ' mirrored="true"'
|
|
|
|
eventLine += "]"
|
|
|
|
file.store_string(eventLine)
|
|
else:
|
|
eventLine += "# Character join event that did not have a selected character"
|
|
file.store_string(eventLine)
|
|
"2":
|
|
if event['character'] != "":
|
|
if event['character'] != "[All]":
|
|
|
|
eventLine += "Update "
|
|
eventLine += characterFolderBreakdown[event['character']]['searchable_name']
|
|
if 'portrait' in event:
|
|
if (event['portrait'] != "") && (event['portrait'] != "(Don't change)"):
|
|
eventLine += " (" + event['portrait'] + ") "
|
|
|
|
var positionCheck = false
|
|
if 'position' in event:
|
|
for i in event['position']:
|
|
|
|
if event['position'][i] == true:
|
|
positionCheck = true
|
|
eventLine += str(i.to_int() + 1)
|
|
|
|
if !positionCheck:
|
|
eventLine += " 0"
|
|
|
|
if (event['animation'] != "[Default]" && event['animation'] != "") || ('z_index' in event) || ('mirror_portrait' in event):
|
|
# Note: due to Anima changes, animations will be converted into a default. Times and wait will be perserved
|
|
eventLine += " ["
|
|
if (event['animation'] != "[Default]" && event['animation'] != ""):
|
|
eventLine += "animation=\"Heartbeat\" "
|
|
eventLine += "length=\"" + str(event['animation_length']) + "\""
|
|
if "animation_wait" in event:
|
|
eventLine += " wait=\"true\""
|
|
if "animation_repeat" in event:
|
|
eventLine += " repeat=\"" + str(event['animation_repeat']) + "\""
|
|
if 'z_index' in event: if 'z_index' in event:
|
|
if event['z_index'] != 0:
|
|
eventLine += ' z-index="' + str(event['z_index']) + '"'
|
|
if 'mirror_portrait' in event:
|
|
if event['mirror_portrait']:
|
|
eventLine += ' mirrored="true"'
|
|
eventLine += "]"
|
|
|
|
file.store_string(eventLine)
|
|
else:
|
|
file.store_string(eventLine + "# Update and Leave All not currently implemented")
|
|
else:
|
|
eventLine += "# Character Update event that did not have a selected character"
|
|
file.store_string(eventLine)
|
|
"1":
|
|
if event['character'] != "":
|
|
|
|
eventLine += "Leave "
|
|
if event['character'] == "[All]":
|
|
eventLine += "--All--"
|
|
else:
|
|
eventLine += characterFolderBreakdown[event['character']]['searchable_name']
|
|
|
|
if event['animation'] != "[Default]" && event['animation'] != "":
|
|
# Note: due to Anima changes, animations will be converted into a default. Times and wait will be perserved
|
|
eventLine += " [animation=\"Instant In Or Out\" "
|
|
eventLine += "length=\"" + str(event['animation_length']) + "\""
|
|
if "animation_wait" in event:
|
|
eventLine += " wait=\"true\""
|
|
eventLine += "]"
|
|
file.store_string(eventLine)
|
|
else:
|
|
eventLine += " # Character Update event that did not have a selected character"
|
|
file.store_string(eventLine)
|
|
_:
|
|
file.store_string("# failed" + str(event['type']))
|
|
|
|
|
|
"dialogic_010":
|
|
# Question event
|
|
# With the change in 2.0, the root of the Question block is simply text event
|
|
if event['character'] != "" && event['character']:
|
|
eventLine += characterFolderBreakdown[event['character']]['name']
|
|
if event['portrait'] != "":
|
|
eventLine += "(" + event['portrait'] + ")"
|
|
|
|
eventLine += ": "
|
|
if '\n' in event['question']:
|
|
var splitCount = 0
|
|
var split = event['text'].split('\n')
|
|
for splitItem in split:
|
|
if splitCount == 0:
|
|
file.store_line(eventLine + splitItem + "\\")
|
|
else:
|
|
file.store_line(splitItem + "\\")
|
|
splitCount += 1
|
|
else:
|
|
file.store_string(eventLine + event['question'])
|
|
|
|
#depth.push_front("question")
|
|
|
|
"dialogic_011":
|
|
#Choice event
|
|
|
|
#Choice's in 1.x have depth, but they do not have matching End Nodes as Questions and Conditionals do
|
|
if depth.size() > 0:
|
|
if depth[0] == "choice":
|
|
#reset the tabs for this choice to be one tree up
|
|
|
|
if depth.size() == 1:
|
|
eventLine = ""
|
|
else:
|
|
for i in (depth.size() - 1):
|
|
eventLine = " "
|
|
|
|
else:
|
|
#for the next line we want to add a depth
|
|
|
|
depth.push_front("choice")
|
|
|
|
|
|
eventLine += "- "
|
|
eventLine += event['choice']
|
|
|
|
if 'value' in event:
|
|
if event['value'] != "":
|
|
var valueLookup = variableNameConversion("[" + definitionFolderBreakdown[event['definition']]['path'] + definitionFolderBreakdown[event['definition']]['name'] + "]" )
|
|
|
|
eventLine += " [if "
|
|
eventLine += valueLookup
|
|
if event['condition'] != "":
|
|
eventLine += " " + event['condition']
|
|
else:
|
|
#default is true, so it may not store it
|
|
eventLine += " =="
|
|
|
|
# weird line due to missing type casts on String in current Godot 4 alpha
|
|
if event['value'] == str(event['value'].to_int()):
|
|
eventLine += " " + event['value']
|
|
else:
|
|
eventLine += " \"" + event['value'] + "\""
|
|
|
|
eventLine += "]"
|
|
|
|
|
|
file.store_string(eventLine)
|
|
#print("choice node")
|
|
#print ("bracnh depth now" + str(depth))
|
|
"dialogic_012":
|
|
#If event
|
|
var valueLookup = variableNameConversion("[" + definitionFolderBreakdown[event['definition']]['path'] + definitionFolderBreakdown[event['definition']]['name'] + "]" )
|
|
|
|
eventLine += "if "
|
|
eventLine += valueLookup
|
|
if event['condition'] != "":
|
|
eventLine += " " + event['condition']
|
|
else:
|
|
#default is true, so it may not store it
|
|
eventLine += " =="
|
|
|
|
# weird line due to missing type casts on String in current Godot 4 alpha
|
|
if event['value'] == str(event['value'].to_int()):
|
|
eventLine += " " + event['value']
|
|
else:
|
|
eventLine += " \"" + event['value'] + "\""
|
|
|
|
eventLine += ":"
|
|
file.store_string(eventLine)
|
|
#print("if branch node")
|
|
depth.push_front("condition")
|
|
|
|
#print ("bracnh depth now" + str(depth))
|
|
"dialogic_013":
|
|
#End Branch event
|
|
# doesnt actually make any lines, just adjusts the tab depth
|
|
#print("end branch node")
|
|
|
|
|
|
var _popped = depth.pop_front()
|
|
#print ("bracnh depth now" + str(depth))
|
|
"dialogic_014":
|
|
#Set Value event
|
|
if varSubsystemInstalled:
|
|
|
|
|
|
eventLine += "VAR "
|
|
eventLine += variableNameConversion("[" + definitionFolderBreakdown[event['definition']]['path'] + definitionFolderBreakdown[event['definition']]['name'] + "]" )
|
|
eventLine += " = "
|
|
|
|
if "set_random" in event:
|
|
if event['set_random'] == true:
|
|
eventLine += "[random=\"True\""
|
|
if "random_lower_limit" in event:
|
|
eventLine += " min=\"" + str(event['random_lower_limit']) + "\""
|
|
if "random_upper_limit" in event:
|
|
eventLine += " max=\"" + str(event['random_upper_limit']) + "\""
|
|
|
|
eventLine += "]"
|
|
else:
|
|
eventLine += event['set_value']
|
|
else:
|
|
eventLine += event['set_value']
|
|
|
|
file.store_string(eventLine)
|
|
else:
|
|
file.store_string(eventLine + "# Set variable function. Variables subsystem is disabled")
|
|
"dialogic_015":
|
|
#Label event
|
|
file.store_string(eventLine + "[label name=\"" + event['name'] +"\"]")
|
|
anchorNames[event['id']] = event['name']
|
|
"dialogic_016":
|
|
#Goto event
|
|
# Dialogic 1.x only allowed jumping to labels in the same timeline
|
|
# But since it is stored as a ID reference, we will have to get it on the second pass
|
|
|
|
file.store_string(eventLine + "[jump label=<" + event['anchor_id'] +">]")
|
|
#file.store_string(eventLine + "# jump label, just a comment for testing")
|
|
"dialogic_020":
|
|
#Change Timeline event
|
|
# we will need to come back to this one on second pass, since we may not know the new path yet
|
|
|
|
file.store_string(eventLine + "[jump timeline=<" + event['change_timeline'] +">]")
|
|
#file.store_string(eventLine + "# jump timeline, just a comment for testing")
|
|
"dialogic_021":
|
|
#Change Background event
|
|
#in Dialogic 1.x, fade default value was 1. its now 0, so we need to always write one here
|
|
var time: float = 1.0
|
|
if "fade_duration" in event:
|
|
time = event['fade_duration']
|
|
file.store_string(eventLine + "[background arg=\"" + event['background'] +"\" fade=\"" + str(time) + "\"]")
|
|
"dialogic_022":
|
|
#Close Dialog event
|
|
file.store_string(eventLine + "[end_timeline]")
|
|
"dialogic_023":
|
|
#Wait event
|
|
|
|
eventLine += "[wait time=\"" + str(event['wait_seconds']) + "\""
|
|
if !("hide_dialogbox" in event) || (event['hide_dialogbox'] == true):
|
|
eventLine += ' hide_text="true"'
|
|
else:
|
|
eventLine += ' hide_text="false"'
|
|
eventLine += "]"
|
|
file.store_string(eventLine)
|
|
"dialogic_024":
|
|
#Change Theme event
|
|
file.store_string(eventLine + '[style name="<' + event['set_theme'] + '>"]')
|
|
"dialogic_025":
|
|
#Set Glossary event
|
|
file.store_string(eventLine + "# Set Glossary event, not currently implemented")
|
|
"dialogic_026":
|
|
#Save event
|
|
if event['use_default_slot']:
|
|
file.store_string(eventLine + "[save slot=\"Default\"]")
|
|
else:
|
|
file.store_string(eventLine + "[save slot=\"" + event['custom_slot'] + "\"]")
|
|
|
|
"dialogic_030":
|
|
#Audio event
|
|
eventLine += "[sound"
|
|
eventLine += " path=\"" + event['file'] + "\""
|
|
eventLine += " volume=\"" + str(event['volume']) + "\""
|
|
eventLine += " bus=\"" + event['audio_bus'] + "\"]"
|
|
file.store_string(eventLine)
|
|
"dialogic_031":
|
|
#Background Music event
|
|
eventLine += "[music"
|
|
eventLine += " path=\"" + event['file'] + "\""
|
|
eventLine += " volume=\"" + str(event['volume']) + "\""
|
|
eventLine += " fade=\"" + str(event['fade_length']) + "\""
|
|
eventLine += " bus=\"" + event['audio_bus'] + "\""
|
|
eventLine += " loop=\"true\"]"
|
|
file.store_string(eventLine)
|
|
"dialogic_040":
|
|
#Emit Signal event
|
|
file.store_string(eventLine + "[signal arg=\"" + event['emit_signal'] +"\"]")
|
|
"dialogic_041":
|
|
#Change Scene event
|
|
file.store_string(eventLine + "# Change scene event is deprecated. Scene called was: " + event['change_scene'])
|
|
"dialogic_042":
|
|
#Call Node event
|
|
eventLine += "[call_node path=\"" + event['call_node']['target_node_path'] + "\" "
|
|
eventLine += "method=\"" + event['call_node']['method_name'] + "\" "
|
|
eventLine += "args=\"["
|
|
for arg in event['call_node']['arguments']:
|
|
eventLine += "\"" + arg + "\", "
|
|
|
|
#remove the last comma and space
|
|
eventLine = eventLine.left(-2)
|
|
|
|
eventLine += "]\"]"
|
|
file.store_string(eventLine)
|
|
_:
|
|
file.store_string(eventLine + "# unimplemented Dialogic control with unknown number")
|
|
|
|
|
|
|
|
|
|
else:
|
|
var returnString = CustomEventConverter.convertCustomEvent(event)
|
|
if returnString != "":
|
|
file.store_string(eventLine + returnString)
|
|
else:
|
|
eventLine += "# Custom event: "
|
|
eventLine += str(event)
|
|
eventLine = eventLine.replace("{", "*")
|
|
eventLine = eventLine.replace("}", "*")
|
|
|
|
file.store_string(eventLine)
|
|
|
|
file.store_string("\r\n\r\n")
|
|
|
|
|
|
|
|
%OutputLog.text += "Processed events: " + str(processedEvents) + "\r\n"
|
|
else:
|
|
%OutputLog.text += "[color=red]There was a problem parsing this file![/color]\r\n"
|
|
|
|
%OutputLog.text += "\r\n"
|
|
|
|
#second pass
|
|
for item in timelineFolderBreakdown:
|
|
%OutputLog.text += "Verifying file: " + timelineFolderBreakdown[item] + "\r\n"
|
|
|
|
var oldFile = FileAccess.open(timelineFolderBreakdown[item], FileAccess.READ)
|
|
var newFile = FileAccess.open(timelineFolderBreakdown[item].replace(".cnv", ".dtl"), FileAccess.WRITE)
|
|
|
|
var regex = RegEx.new()
|
|
regex.compile('(<.*?>)')
|
|
#var result = regex.search_all(oldText)
|
|
|
|
|
|
var whitespaceCount = 0
|
|
while oldFile.get_position() < oldFile.get_length():
|
|
var line = oldFile.get_line()
|
|
|
|
if line.length() == 0:
|
|
#clean up any extra whitespace so theres only one line betwen each command
|
|
whitespaceCount += 1
|
|
if whitespaceCount < 2:
|
|
newFile.store_string("\r\n\r\n")
|
|
else:
|
|
whitespaceCount = 0
|
|
|
|
var result = regex.search_all(line)
|
|
if result:
|
|
for res in result:
|
|
var r_string = res.get_string()
|
|
var newString = r_string.substr(1,r_string.length()-2)
|
|
|
|
if "timeline" in line:
|
|
newString = "\"" + timelineFolderBreakdown[newString].replace(".cnv", ".dtl") + "\""
|
|
if "label" in line:
|
|
newString = "\"" + anchorNames[newString] + "\""
|
|
if "style" in line:
|
|
var prev = newString
|
|
var config = ConfigFile.new()
|
|
var error = config.load("res://dialogic/themes/" + newString)
|
|
if error == OK:
|
|
if config.has_section_key("settings", "name"):
|
|
newString = config.get_value("settings", "name")
|
|
|
|
if newString == prev:
|
|
newString = "DefaultTheme"
|
|
|
|
line = line.replace(r_string,newString)
|
|
newFile.store_string(line)
|
|
else:
|
|
newFile.store_string(line)
|
|
|
|
oldFile = null
|
|
|
|
var fileDirectory = timelineFolderBreakdown[item].replace(timelineFolderBreakdown[item].split("/")[-1], "")
|
|
var dir = DirAccess.open(fileDirectory)
|
|
dir.remove(timelineFolderBreakdown[item])
|
|
|
|
%OutputLog.text += "Completed conversion of file: " + timelineFolderBreakdown[item].replace(".cnv", ".dtl") + "\r\n"
|
|
|
|
#print(item)
|
|
|
|
|
|
|
|
func convertCharacters():
|
|
%OutputLog.text += "Converting characters: \r\n"
|
|
for item in characterFolderBreakdown:
|
|
var original_file = item
|
|
var folderPath = characterFolderBreakdown[item]
|
|
%OutputLog.text += "Character " + folderPath + item +": "
|
|
var jsonData = {}
|
|
var file := FileAccess.open("res://dialogic/characters/" + item, FileAccess.READ)
|
|
var fileContent = file.get_as_text()
|
|
var json_object = JSON.new()
|
|
|
|
var error = json_object.parse(fileContent)
|
|
|
|
if error == OK:
|
|
contents = json_object.get_data()
|
|
var fileName = contents["name"]
|
|
%OutputLog.text += "Name: " + fileName + "\r\n"
|
|
|
|
if ("[" in fileName) || ("]" in fileName) || ("?" in fileName):
|
|
%OutputLog.text += " [color=yellow]Stripping invalid characters from file name![/color]\r\n"
|
|
fileName = characterNameConversion(fileName)
|
|
|
|
|
|
|
|
|
|
if not DirAccess.dir_exists_absolute(conversionRootFolder + "/characters" + folderPath):
|
|
var directory = DirAccess.open(conversionRootFolder + "/characters")
|
|
|
|
var progresiveDirectory = ""
|
|
for pathItem in folderPath.split('/'):
|
|
directory.open(conversionRootFolder + "/characters" + progresiveDirectory)
|
|
if pathItem!= "":
|
|
progresiveDirectory += "/" + pathItem
|
|
if !directory.dir_exists(conversionRootFolder + "/characters" + progresiveDirectory):
|
|
directory.make_dir(conversionRootFolder + "/characters" + progresiveDirectory)
|
|
|
|
#add the prefix if the prefix option is enabled
|
|
if prefixCharacters:
|
|
var prefix = ""
|
|
for level in folderPath.split('/'):
|
|
if level != "":
|
|
prefix += level.left(2) + "-"
|
|
fileName = prefix + fileName
|
|
# using the resource constructor for this one
|
|
|
|
var current_character = DialogicCharacter.new()
|
|
current_character.resource_path = conversionRootFolder + "/characters" + folderPath + "/" + fileName + ".dch"
|
|
# Everything needs to be in exact order
|
|
|
|
current_character.color = Color(contents["color"].right(6))
|
|
var customInfoDict = {}
|
|
customInfoDict["sound_moods"] = {}
|
|
customInfoDict["theme"] = ""
|
|
current_character.custom_info = customInfoDict
|
|
current_character.description = varNameStripSpecial(contents["description"])
|
|
if contents["display_name"] == "":
|
|
current_character.display_name = varNameStripSpecial(contents["name"])
|
|
else:
|
|
current_character.display_name = varNameStripSpecial(contents["display_name"])
|
|
current_character.mirror = contents["mirror_portraits"]
|
|
#current_character.name = varNameStripSpecial(contents["name"])
|
|
current_character.nicknames = []
|
|
current_character.offset = Vector2(0,0)
|
|
|
|
var portraitsList = {}
|
|
var firstPortrait = ""
|
|
for portrait in contents['portraits']:
|
|
var portraitData = {}
|
|
if portrait['path'] != "":
|
|
if ".tscn" in portrait['path']:
|
|
portraitData['scene'] = portrait['path']
|
|
else:
|
|
portraitData['image'] = portrait['path']
|
|
|
|
#use the global offset, scale, and mirror setting from the origianl character file
|
|
portraitData['offset'] = Vector2(contents['offset_x'], contents['offset_y'])
|
|
portraitData['scale'] = contents['scale'] / 100
|
|
portraitData['mirror'] = contents['mirror_portraits']
|
|
|
|
#discard it if there's an empty Default, so it doesn't throw an error
|
|
if !((portrait['name'] == "Default") && (portrait['path'] == "")) && !((portrait['name'] == "") && (portrait['path'] == "")):
|
|
portraitsList[portrait['name']] = portraitData
|
|
if firstPortrait == "":
|
|
firstPortrait = portrait['name']
|
|
|
|
|
|
|
|
current_character.portraits = portraitsList
|
|
if firstPortrait != "":
|
|
current_character.default_portrait = firstPortrait
|
|
current_character.scale = 1.0
|
|
|
|
ResourceSaver.save(current_character, current_character.resource_path)
|
|
|
|
# Before we're finished here, update the folder breakdown so it has the proper character name
|
|
var infoDict = {}
|
|
infoDict["original_file"] = original_file
|
|
infoDict["path"] = characterFolderBreakdown[item]
|
|
infoDict["name"] = fileName
|
|
var name_breakdown:Array = ( folderPath + fileName).split("/")
|
|
name_breakdown.reverse()
|
|
infoDict["name_breakdown"] = name_breakdown
|
|
|
|
infoDict["searchable_name"] = infoDict["name_breakdown"][0]
|
|
|
|
characterFolderBreakdown[item] = infoDict
|
|
|
|
%OutputLog.text += "\r\n"
|
|
else:
|
|
%OutputLog.text += "[color=red]There was a problem parsing this file![/color]\r\n"
|
|
|
|
# Second pass, shorten all of the paths, so they match the character dictionary in Dialogic itself
|
|
|
|
# Temporarily need an array to be able to sort
|
|
var sorting_array:Array = []
|
|
|
|
for item in characterFolderBreakdown:
|
|
sorting_array.append(characterFolderBreakdown[item])
|
|
|
|
sorting_array.sort_custom(func(a, b):return a['name_breakdown'].count("/") < b['name_breakdown'].count("/"))
|
|
|
|
var clean_search_path:bool = false
|
|
var depth = 1
|
|
|
|
while !clean_search_path:
|
|
var interim_array:Array = []
|
|
clean_search_path = true
|
|
|
|
for i in sorting_array.size():
|
|
if sorting_array.filter(func(val): return val['searchable_name'] == sorting_array[i]['searchable_name']).size() > 1:
|
|
clean_search_path = false
|
|
var replace_dict:Dictionary = sorting_array[i]
|
|
replace_dict["searchable_name"] = replace_dict["name_breakdown"][depth] + "/" + replace_dict["searchable_name"]
|
|
interim_array.append(replace_dict)
|
|
else:
|
|
interim_array.append(sorting_array[i])
|
|
depth += 1
|
|
sorting_array = interim_array
|
|
|
|
characterFolderBreakdown.clear()
|
|
|
|
for item in sorting_array:
|
|
if item["searchable_name"].count(" ") > 0:
|
|
item["searchable_name"] = '"' + item["searchable_name"] + '"'
|
|
characterFolderBreakdown[item['original_file']] = item
|
|
%OutputLog.text += "Final character name for " + item['original_file'] + ": " + item['searchable_name'] + "\r\n"
|
|
|
|
%OutputLog.text += "\r\n"
|
|
|
|
func convertVariables():
|
|
%OutputLog.text += "Converting variables: \r\n"
|
|
|
|
var convertedVariables = 0
|
|
# Creating a file with a format identical to how the variables are stored in project settings
|
|
if varSubsystemInstalled:
|
|
var newVariableDictionary = {}
|
|
for varItem in definitionFolderBreakdown:
|
|
if "type" in definitionFolderBreakdown[varItem]:
|
|
if definitionFolderBreakdown[varItem]["type"] == "variable":
|
|
if definitionFolderBreakdown[varItem]["path"] == "/":
|
|
newVariableDictionary[varNameStripSpecial(definitionFolderBreakdown[varItem]["name"])] = definitionFolderBreakdown[varItem]["value"]
|
|
flatDefinitionsFile[varNameStripSpecial(definitionFolderBreakdown[varItem]["name"])] = varItem
|
|
convertedVariables += 1
|
|
else:
|
|
# I will fill this one in later, need to figure out the recursion for it
|
|
var dictRef = newVariableDictionary
|
|
var flatNameBuilder = ""
|
|
|
|
for pathItem in varNameStripSpecial(definitionFolderBreakdown[varItem]["path"]).split("/"):
|
|
|
|
if pathItem != "":
|
|
if pathItem in dictRef:
|
|
dictRef = dictRef[pathItem]
|
|
flatNameBuilder += pathItem + "."
|
|
else:
|
|
dictRef[pathItem] = {}
|
|
dictRef = dictRef[pathItem]
|
|
flatNameBuilder += pathItem + "."
|
|
|
|
dictRef[varNameStripSpecial(definitionFolderBreakdown[varItem]["name"])] = definitionFolderBreakdown[varItem]["value"]
|
|
convertedVariables +=1
|
|
var flatName = flatNameBuilder + varNameStripSpecial(definitionFolderBreakdown[varItem]["name"])
|
|
flatDefinitionsFile[flatName] = varItem
|
|
|
|
|
|
ProjectSettings.set_setting('dialogic/variables', null)
|
|
ProjectSettings.set_setting('dialogic/variables', newVariableDictionary)
|
|
ProjectSettings.save()
|
|
|
|
#rebuild the data in the other tabs, so it doesnt override it
|
|
find_parent('SettingsEditor').refresh()
|
|
%OutputLog.text += str(convertedVariables) + " variables converted, and saved to project!\r\n"
|
|
else:
|
|
%OutputLog.text += "[color=yellow]Variable subsystem is not present! Variables were not converted![/color]\r\n"
|
|
|
|
|
|
%OutputLog.text += "\r\n"
|
|
|
|
|
|
func convertGlossaries():
|
|
%OutputLog.text += "Converting glossaries: [color=red]not currently implemented[/color] \r\n"
|
|
|
|
%OutputLog.text += "\r\n"
|
|
|
|
func convertThemes():
|
|
%OutputLog.text += "Converting themes: [color=red]not currently implemented[/color] \r\n"
|
|
|
|
%OutputLog.text += "\r\n"
|
|
|
|
func varNameStripSpecial(oldVariable):
|
|
# This is to remove special characters from variable names
|
|
# Since in code variables are accessed by Dialogic.VAR.path.to.variable, characters not usable in Godot paths have to be removed
|
|
var newVariable = oldVariable
|
|
newVariable = newVariable.replace(" ", "_")
|
|
newVariable = newVariable.replace(".", "_")
|
|
newVariable = newVariable.replace("-", "_")
|
|
|
|
|
|
return(newVariable)
|
|
|
|
func variableNameConversion(oldText):
|
|
var newText = oldText
|
|
var regex = RegEx.new()
|
|
regex.compile('(\\[.*?\\])')
|
|
var result = regex.search_all(oldText)
|
|
if result:
|
|
for res in result:
|
|
var r_string = res.get_string()
|
|
var newString = res.get_string()
|
|
newString = newString.replace("[", "")
|
|
newString = newString.replace("]", "")
|
|
if newString[0] == '/':
|
|
newString = newString.right(-1)
|
|
|
|
newString = varNameStripSpecial(newString)
|
|
newString = newString.replace("/", ".")
|
|
|
|
|
|
|
|
if newString in flatDefinitionsFile:
|
|
newString = "{" + newString + "}"
|
|
newText = newText.replace(r_string, newString)
|
|
|
|
|
|
|
|
return(newText)
|
|
|
|
func characterNameConversion(oldText:String, filter_forward_slash:bool = true):
|
|
#as some characters aren't supported in filenames, we need to convert both the filenames, and references to them
|
|
#Most character name restrictions are only restrictions of Windows, but we are going to enforce it for Dialogic due to node and regex complexities for platform uniformity
|
|
#We're also restricting the special characters used for other designations
|
|
var newText = oldText
|
|
newText = newText.replace("<","[")
|
|
newText = newText.replace(">","]")
|
|
newText = newText.replace(":","-")
|
|
newText = newText.replace("\\","/")
|
|
if filter_forward_slash:
|
|
newText = newText.replace("/","-")
|
|
newText = newText.replace("|","-")
|
|
newText = newText.replace("*","@")
|
|
newText = newText.replace("?","0")
|
|
newText = newText.replace('"',"'")
|
|
|
|
#These ones as they are the delimiter for portraits
|
|
newText = newText.replace("(","[")
|
|
newText = newText.replace(")","]")
|
|
|
|
|
|
return newText
|
|
|
|
func convertSettings():
|
|
%OutputLog.text += "Converting other settings: \r\n"
|
|
%OutputLog.text += "[color=yellow]Note! Most original settings can't be converted.[/color] \r\n"
|
|
|
|
|
|
var config = ConfigFile.new()
|
|
|
|
var err = config.load("res://dialogic/settings.cfg")
|
|
if err != OK:
|
|
%OutputLog.text += "[color=red]Dialogic 1.x Settings file could not be loaded![/color] \r\n"
|
|
return
|
|
|
|
ProjectSettings.set_setting('dialogic/text/autocolor_names', config.get_value("dialog", "auto_color_names", true))
|
|
ProjectSettings.set_setting('dialogic/choices/autofocus_first', config.get_value("input", "autofocus_choices", false))
|
|
ProjectSettings.set_setting('dialogic/choices/delay', config.get_value("input", "delay_after_options", 0.2))
|
|
ProjectSettings.save()
|
|
|
|
|
|
|
|
func _on_check_box_toggled(button_pressed):
|
|
prefixCharacters = button_pressed
|
|
%OutputLog.text += "\r\n\r\nToggling this will add a prefix to all character filenames, which will have letters from each folder depth they are in. Characters in the root folder will have no prefix. \r\n"
|