You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							388 lines
						
					
					
						
							14 KiB
						
					
					
				
			
		
		
	
	
							388 lines
						
					
					
						
							14 KiB
						
					
					
				| extends Node | |
|  | |
| ## Exposed and safe to use methods for Dialogic | |
| ## See documentation under 'https://github.com/coppolaemilio/dialogic' or in the editor: | |
|  | |
| ## ### /!\ ### | |
| ## Do not use methods from other classes as it could break the plugin's integrity | |
| ## ### /!\ ### | |
|  | |
| ## Trying to follow this documentation convention: https://github.com/godotengine/godot/pull/41095 | |
| class_name Dialogic | |
|  | |
|  | |
| ## Refactor the start function for 2.0 there should be a cleaner way to do it :) | |
|  | |
| ## Starts the dialog for the given timeline and returns a Dialog node. | |
| ## You must then add it manually to the scene to display the dialog. | |
| ## | |
| ## Example: | |
| ## var new_dialog = Dialogic.start('Your Timeline Name Here') | |
| ## add_child(new_dialog) | |
| ## | |
| ## This is similar to using the editor: | |
| ## you can drag and drop the scene located at /addons/dialogic/Dialog.tscn  | |
| ## and set the current timeline via the inspector. | |
| ## | |
| ## @param timeline				The timeline to load. You can provide the timeline name or the filename. | |
| ##								If you leave it empty, it will try to load from current data | |
| ##								In that case, you should do  Dialogic.load() or Dialogic.import() before. | |
| ## @param default_timeline		If timeline == '' and no valid data was found, this will be loaded. | |
| ## @param dialog_scene_path		If you made a custom Dialog scene or moved it from its default path, you can specify its new path here. | |
| ## @param debug_mode			Debug is disabled by default but can be enabled if needed. | |
| ## @param use_canvas_instead	Create the Dialog inside a canvas layer to make it show up regardless of the camera 2D/3D situation. | |
| ## @returns						A Dialog node to be added into the scene tree. | |
| static func start(timeline: String = '', default_timeline: String ='', dialog_scene_path: String="res://addons/dialogic/Nodes/DialogNode.tscn", debug_mode: bool=false, use_canvas_instead=true): | |
| 	var dialog_scene = load(dialog_scene_path) | |
| 	var dialog_node = null | |
| 	var canvas_dialog_node = null | |
| 	var returned_dialog_node = null | |
| 	 | |
| 	if use_canvas_instead: | |
| 		var canvas_dialog_script = load("res://addons/dialogic/Nodes/canvas_dialog_node.gd") | |
| 		canvas_dialog_node = canvas_dialog_script.new() | |
| 		canvas_dialog_node.set_dialog_node_scene(dialog_scene) | |
| 		dialog_node = canvas_dialog_node.dialog_node | |
| 	else: | |
| 		dialog_node = dialog_scene.instance() | |
| 	 | |
| 	dialog_node.debug_mode = debug_mode | |
| 	 | |
| 	returned_dialog_node = dialog_node if not canvas_dialog_node else canvas_dialog_node | |
| 	 | |
| 	## 1. Case: A slot has been loaded OR data has been imported | |
| 	if timeline == '': | |
| 		if (Engine.get_main_loop().has_meta('last_dialog_state')  | |
| 			and not Engine.get_main_loop().get_meta('last_dialog_state').empty() | |
| 			and not Engine.get_main_loop().get_meta('last_dialog_state').get('timeline', '').empty()): | |
| 		 | |
| 			dialog_node.resume_state_from_info(Engine.get_main_loop().get_meta('last_dialog_state')) | |
| 			return returned_dialog_node | |
| 		 | |
| 		## The loaded data isn't complete | |
| 		elif (Engine.get_main_loop().has_meta('current_timeline') | |
| 			and not Engine.get_main_loop().get_meta('current_timeline').empty()): | |
| 				timeline = Engine.get_main_loop().get_meta('current_timeline') | |
| 		 | |
| 		## Else load the default timeline | |
| 		else: | |
| 			timeline = default_timeline | |
| 	 | |
| 	## 2. Case: A specific timeline should be started | |
| 	 | |
| 	# check if it's a file name | |
| 	if timeline.ends_with('.json'): | |
| 		for t in DialogicUtil.get_timeline_list(): | |
| 			if t['file'] == timeline: | |
| 				dialog_node.timeline = t['file'] | |
| 				return returned_dialog_node | |
| 		# No file found. Show error | |
| 		dialog_node.dialog_script = { | |
| 			"events":[ | |
| 				{"event_id":'dialogic_001', | |
| 				"character":"", | |
| 				"portrait":"", | |
| 				"text":"[Dialogic Error] Loading dialog [color=red]" + timeline + "[/color]. It seems like the timeline doesn't exists. Maybe the name is wrong?" | |
| 			}] | |
| 		} | |
| 		return returned_dialog_node | |
| 	 | |
| 	# else get the file from the name | |
| 	var timeline_file = _get_timeline_file_from_name(timeline) | |
| 	if timeline_file: | |
| 		dialog_node.timeline = timeline_file | |
| 		return returned_dialog_node | |
| 	 | |
| 	# Just in case everything else fails. | |
| 	return returned_dialog_node | |
|  | |
|  | |
|  | |
| ################################################################################ | |
| ## 						BUILT-IN SAVING/LOADING | |
| ################################################################################ | |
|  | |
| ## Loads the given slot | |
| static func load(slot_name: String = ''): | |
| 	_load_from_slot(slot_name) | |
| 	Engine.get_main_loop().set_meta('current_save_slot', slot_name) | |
|  | |
|  | |
| ## Saves the current definitions and the latest added dialog nodes state info. | |
| ##  | |
| ## @param slot_name		The name of the save slot. To load this save you have to specify the same | |
| ##						If the slot folder doesn't exist it will be created.  | |
| ##						Leaving this empty will use the last loaded save slot. | |
| static func save(slot_name: String = '', is_autosave = false) -> void: | |
| 	# check if to save (if this is a autosave) | |
| 	if is_autosave and not get_autosave(): | |
| 		return | |
| 	 | |
| 	# gather the info | |
| 	var current_dialog_info = {} | |
| 	if has_current_dialog_node(): | |
| 		current_dialog_info = Engine.get_main_loop().get_meta('latest_dialogic_node').get_current_state_info() | |
| 	 | |
| 	var game_state = {} | |
| 	if Engine.get_main_loop().has_meta('game_state'): | |
| 		game_state = Engine.get_main_loop().get_meta('game_state') | |
| 	 | |
| 	var save_data = { | |
| 		'game_state': game_state, | |
| 		'dialog_state': current_dialog_info | |
| 		} | |
| 	 | |
| 	# save the information | |
| 	_save_state_and_definitions(slot_name, save_data) | |
|  | |
|  | |
| ## Returns an array with the names of all available slots. | |
| static func get_slot_names() -> Array: | |
| 	return DialogicResources.get_saves_folders() | |
|  | |
|  | |
| ## Will permanently erase the data in the given save_slot. | |
| ##  | |
| ## @param slot_name		The name of the slot folder. | |
| static func erase_slot(slot_name: String) -> void: | |
| 	DialogicResources.remove_save_folder(slot_name) | |
|  | |
|  | |
| ## Whether a save can be performed | |
| ## | |
| ## @returns				True if a save can be performed; otherwise False | |
| static func has_current_dialog_node() -> bool: | |
| 	return Engine.get_main_loop().has_meta('latest_dialogic_node') and is_instance_valid(Engine.get_main_loop().get_meta('latest_dialogic_node')) | |
|  | |
|  | |
| ## Resets the state and definitions of the given save slot | |
| ## | |
| ## By default this will also LOAD that reseted save | |
| static func reset_saves(slot_name: String = '', reload:= true) -> void: | |
| 	DialogicResources.reset_save(slot_name) | |
| 	if reload: _load_from_slot(slot_name) | |
|  | |
|  | |
| ## Returns the currently loaded save slot | |
| static func get_current_slot(): | |
| 	if Engine.get_main_loop().has_meta('current_save_slot'): | |
| 		return Engine.get_main_loop().get_meta('current_save_slot') | |
| 	else: | |
| 		return '' | |
|  | |
| ## +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | |
| ## 						EXPORT / IMPORT | |
| ## +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | |
|  | |
| # this returns a dictionary with the DEFINITIONS, the GAME STATE and the DIALOG STATE | |
| static func export(dialog_node = null) -> Dictionary: | |
| 	# gather the data | |
| 	var current_dialog_info = {} | |
| 	if dialog_node == null and has_current_dialog_node(): | |
| 		dialog_node = Engine.get_main_loop().get_meta('latest_dialogic_node') | |
| 	if dialog_node: | |
| 		current_dialog_info = dialog_node.get_current_state_info() | |
| 	 | |
| 	# return it | |
| 	return { | |
| 		'definitions': _get_definitions(), | |
| 		'state': Engine.get_main_loop().get_meta('game_state'), | |
| 		'dialog_state': current_dialog_info | |
| 	} | |
|  | |
|  | |
| # this loads a dictionary with GAME STATE, DEFINITIONS and DIALOG_STATE  | |
| static func import(data: Dictionary) -> void: | |
| 	## Tell the future we want to use the imported data | |
| 	Engine.get_main_loop().set_meta('current_save_lot', '/') | |
| 	 | |
| 	# load the data | |
| 	Engine.get_main_loop().set_meta('definitions', data['definitions']) | |
| 	Engine.get_main_loop().set_meta('game_state', data['state']) | |
| 	Engine.get_main_loop().set_meta('last_dialog_state', data.get('dialog_state', null)) | |
| 	set_current_timeline(get_saved_state_general_key('timeline')) | |
|  | |
|  | |
| ## +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | |
| ## 						DEFINITIONS | |
| ## +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | |
|  | |
| # sets the value of the value definition with the given name | |
| static func set_variable(name: String, value): | |
| 	var exists = false | |
| 	for d in _get_definitions()['variables']: | |
| 		if d['name'] == name: | |
| 			d['value'] = str(value) | |
| 			exists = true | |
| 	if exists == false: | |
| 		# TODO it would be great to automatically generate that missing variable here so they don't | |
| 		# have to create it from the editor.  | |
| 		print("[Dialogic] Warning! the variable [" + name + "] doesn't exists. Create it from the Dialogic editor.") | |
| 	return value | |
|  | |
| # returns the value of the value definition with the given name | |
| static func get_variable(name: String, default = null): | |
| 	for d in _get_definitions()['variables']: | |
| 		if d['name'] == name: | |
| 			return d['value'] | |
| 	print("[Dialogic] Warning! the variable [" + name + "] doesn't exists.") | |
| 	return default | |
|  | |
|  | |
| ## +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | |
| ## 						GAME STATE | |
| ## +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | |
| # the game state is a global dictionary that can be used to store custom data | |
| # these functions should be renamed in 2.0! These names are outdated. | |
|  | |
| # this sets a value in the GAME STATE dictionary | |
| static func get_saved_state_general_key(key: String, default = '') -> String: | |
| 	if not Engine.get_main_loop().has_meta('game_state'): | |
| 		return default | |
| 	if key in Engine.get_main_loop().get_meta('game_state').keys(): | |
| 		return Engine.get_main_loop().get_meta('game_state')[key] | |
| 	else: | |
| 		return default | |
|  | |
|  | |
| # this gets a value from the GAME STATE dictionary | |
| static func set_saved_state_general_key(key: String, value) -> void: | |
| 	if not Engine.get_main_loop().has_meta('game_state'): | |
| 		Engine.get_main_loop().set_meta('game_state', {}) | |
| 	Engine.get_main_loop().get_meta('game_state')[key] = str(value) | |
| 	save('', true) | |
|  | |
|  | |
|  | |
| ################################################################################ | |
| ## 					COULD BE USED | |
| ################################################################################ | |
| # these are old things, that have little use. | |
|  | |
| static func get_autosave() -> bool: | |
| 	if Engine.get_main_loop().has_meta('autoload'): | |
| 		return Engine.get_main_loop().get_meta('autoload') | |
| 	return true | |
|  | |
|  | |
| static func set_autosave(autoload): | |
| 	Engine.get_main_loop().set_meta('autoload', autoload) | |
|  | |
|  | |
| static func set_current_timeline(timeline): | |
| 	Engine.get_main_loop().set_meta('current_timeline', timeline) | |
| 	return timeline | |
|  | |
|  | |
| static func get_current_timeline(): | |
| 	var timeline | |
| 	timeline = Engine.get_main_loop().get_meta('current_timeline') | |
| 	if timeline == null: | |
| 		timeline = '' | |
| 	return timeline | |
|  | |
| ################################################################################ | |
| ## 					NOT TO BE USED FROM OUTSIDE | |
| ################################################################################ | |
| ## this loads the saves definitions and returns the saves state_info ditionary | |
| static func _load_from_slot(slot_name: String = '') -> Dictionary: | |
| 	Engine.get_main_loop().set_meta('definitions', DialogicResources.get_saved_definitions(slot_name)) | |
| 	 | |
| 	var state_info = DialogicResources.get_saved_state_info(slot_name) | |
| 	Engine.get_main_loop().set_meta('last_dialog_state', state_info.get('dialog_state', null)) | |
| 	Engine.get_main_loop().set_meta('game_state', state_info.get('game_state', null)) | |
| 	 | |
| 	return state_info.get('dialog_state', {}) | |
|  | |
|  | |
| ## this saves the current definitions and the given state info into the save folder @save_name | |
| static func _save_state_and_definitions(save_name: String, state_info: Dictionary) -> void: | |
| 	DialogicResources.save_definitions(save_name, _get_definitions()) | |
| 	DialogicResources.save_state_info(save_name, state_info) | |
|  | |
|  | |
|  | |
| static func _get_definitions() -> Dictionary: | |
| 	var definitions | |
| 	if Engine.get_main_loop().has_meta('definitions'): | |
| 		definitions = Engine.get_main_loop().get_meta('definitions') | |
| 	else: | |
| 		definitions = DialogicResources.get_default_definitions() | |
| 		Engine.get_main_loop().set_meta('definitions', definitions) | |
| 	return definitions | |
|  | |
|  | |
| # used by the DialogNode | |
| static func set_glossary_from_id(id: String, title: String, text: String, extra:String) -> void: | |
| 	var target_def: Dictionary; | |
| 	for d in _get_definitions()['glossary']: | |
| 		if d['id'] == id: | |
| 			target_def = d; | |
| 	if target_def != null: | |
| 		if title and title != "[No Change]": | |
| 			target_def['title'] = title | |
| 		if text and text != "[No Change]": | |
| 			target_def['text'] = text | |
| 		if extra and extra != "[No Change]": | |
| 			target_def['extra'] = extra | |
|  | |
| # used by the DialogNode | |
| static func set_variable_from_id(id: String, value: String, operation: String) -> void: | |
| 	var target_def: Dictionary; | |
| 	for d in _get_definitions()['variables']: | |
| 		if d['id'] == id: | |
| 			target_def = d; | |
| 	if target_def != null: | |
| 		var converted_set_value = value | |
| 		var converted_target_value = target_def['value'] | |
| 		var is_number = converted_set_value.is_valid_float() and converted_target_value.is_valid_float() | |
| 		if is_number: | |
| 			converted_set_value = float(value) | |
| 			converted_target_value = float(target_def['value']) | |
| 		var result = target_def['value'] | |
| 		# Do nothing for -, * and / operations on string | |
| 		match operation: | |
| 			'=': | |
| 				result = converted_set_value | |
| 			'+': | |
| 				result = converted_target_value + converted_set_value | |
| 			'-': | |
| 				if is_number: | |
| 					result = converted_target_value - converted_set_value | |
| 			'*': | |
| 				if is_number: | |
| 					result = converted_target_value * converted_set_value | |
| 			'/': | |
| 				if is_number: | |
| 					result = converted_target_value / converted_set_value | |
| 		target_def['value'] = str(result) | |
|  | |
| # tries to find the path of a given timeline  | |
| static func _get_timeline_file_from_name(timeline_name_path: String) -> String: | |
| 	var timelines = DialogicUtil.get_full_resource_folder_structure()['folders']['Timelines'] | |
| 	var parts = timeline_name_path.split('/', false) | |
| 	if parts.size() > 1: | |
| 		var current_data | |
| 		var current_depth = 0 | |
| 		for p in parts: | |
| 			if current_depth == 0: | |
| 				# Starting the crawl | |
| 				current_data = timelines['folders'][p] | |
| 			elif current_depth == parts.size() - 1: | |
| 				# The final destination | |
| 				for t in DialogicUtil.get_timeline_list(): | |
| 					for f in current_data['files']: | |
| 						if t['file'] == f && t['name'] == p: | |
| 							return t['file'] | |
| 							 | |
| 			else: | |
| 				# Still going deeper | |
| 				current_data = current_data['folders'][p] | |
| 			current_depth += 1 | |
| 	else: | |
| 		# Searching for any timeline that could match that name | |
| 		for t in DialogicUtil.get_timeline_list(): | |
| 			if parts.size(): | |
| 				if t['name'] == parts[0]: | |
| 					return t['file'] | |
| 	return ''
 | |
| 
 |