Add addons

This commit is contained in:
Joey Yakimowich-Payne 2023-01-03 16:53:53 -07:00
commit 3367bad24f
No known key found for this signature in database
GPG key ID: 6BFE655FA5ABD1E1
527 changed files with 36197 additions and 0 deletions

View file

@ -0,0 +1,15 @@
extends DialogicAnimation
func animate():
var tween = (node.create_tween() as Tween)
node.scale = Vector2()
node.modulate.a = 0
tween.set_ease(Tween.EASE_IN_OUT)
tween.set_trans(Tween.TRANS_LINEAR)
tween.set_parallel()
tween.tween_property(node, 'scale', Vector2(1,1), time).set_trans(Tween.TRANS_ELASTIC).set_ease(Tween.EASE_OUT)
tween.tween_property(node, 'modulate:a', 1.0, time)
tween.finished.connect(emit_signal.bind('finished_once'))

View file

@ -0,0 +1,15 @@
extends DialogicAnimation
func animate():
var tween = (node.create_tween() as Tween)
node.scale = Vector2(1,1)
node.modulate.a = 1
tween.set_ease(Tween.EASE_IN_OUT)
tween.set_trans(Tween.TRANS_LINEAR)
tween.set_parallel()
tween.tween_property(node, 'scale', Vector2(), time).set_trans(Tween.TRANS_ELASTIC).set_ease(Tween.EASE_IN)
tween.tween_property(node, 'modulate:a', 0.0, time)
tween.finished.connect(emit_signal.bind('finished_once'))

View file

@ -0,0 +1,14 @@
extends DialogicAnimation
func animate():
var tween = (node.create_tween() as Tween)
node.position.y = orig_pos.y + node.get_viewport().size.y/5
node.modulate.a = 0
tween.set_ease(Tween.EASE_IN_OUT)
tween.set_trans(Tween.TRANS_SINE)
tween.set_parallel()
tween.tween_property(node, 'position', orig_pos, time)
tween.tween_property(node, 'modulate:a', 1.0, time)
tween.finished.connect(emit_signal.bind('finished_once'))

View file

@ -0,0 +1,12 @@
extends DialogicAnimation
func animate():
var tween = (node.create_tween() as Tween)
tween.set_ease(Tween.EASE_IN_OUT)
tween.set_trans(Tween.TRANS_SINE)
tween.set_parallel()
tween.tween_property(node, 'position:y', orig_pos.y + node.get_viewport().size.y/5, time)
tween.tween_property(node, 'modulate:a', 0.0, time)
tween.finished.connect(emit_signal.bind('finished_once'))

View file

@ -0,0 +1,11 @@
extends DialogicAnimation
func animate():
var tween = (node.create_tween() as Tween)
#tween.set_parallel(true)
#node.scale = Vector2(1,1)*1.2
tween.tween_property(node, 'scale', Vector2(1,1)*1.2, time*0.5).set_trans(Tween.TRANS_ELASTIC).set_ease(Tween.EASE_OUT)
#tween.tween_property(node, 'scale', Vector2(1,1)*1.1, time*0.5).set_trans(Tween.TRANS_BACK).set_ease(Tween.EASE_OUT)
tween.tween_property(node, 'scale', Vector2(1,1), time*0.5).set_trans(Tween.TRANS_BOUNCE).set_ease(Tween.EASE_OUT)
tween.finished.connect(emit_signal.bind('finished_once'))

View file

@ -0,0 +1,5 @@
extends DialogicAnimation
func animate():
await node.get_tree().process_frame
emit_signal('finished')

View file

@ -0,0 +1,17 @@
extends DialogicAnimation
func animate():
var tween = (node.create_tween() as Tween)
tween.set_parallel(true)
tween.set_trans(Tween.TRANS_SINE)
tween.set_ease(Tween.EASE_OUT)
tween.tween_property(node, 'scale', Vector2(1,1)*1.1, time*0.3)
tween.tween_property(node, 'rotation', -0.1, time*0.1).set_delay(time*0.2)
tween.tween_property(node, 'rotation', 0.1, time*0.1).set_delay(time*0.3)
tween.tween_property(node, 'rotation', -0.1, time*0.1).set_delay(time*0.4)
tween.tween_property(node, 'rotation', 0.1, time*0.1).set_delay(time*0.5)
tween.tween_property(node, 'rotation', -0.1, time*0.1).set_delay(time*0.6)
tween.chain().tween_property(node, 'scale', Vector2(1,1), time*0.3)
tween.parallel().tween_property(node, 'rotation', 0.0, time*0.3)
tween.finished.connect(emit_signal.bind('finished_once'))

View file

@ -0,0 +1,47 @@
class_name DialogicAnimation
extends Node
## Class that can be used to animate portraits. Can be extended to create animations.
signal finished_once
signal finished
## Set at runtime, will be the node to animate.
var node :Node
## Set at runtime, will be the length of the animation.
var time : float
## Set at runtime, will be the position at which to end the animation.
var end_position : Vector2
## Set at runtime. The position the node started at.
var orig_pos : Vector2
## Used to repeate the animation for a number of times.
var repeats : int
func _ready():
connect('finished_once', finished_one_loop)
## To be overridden. Do the actual animating/tweening in here.
## Use the properties [node], [time], [end_position], [orig_pos].
func animate():
pass
func finished_one_loop():
repeats -= 1
if repeats > 0:
animate()
elif repeats == 0:
emit_signal("finished")
func pause():
if node:
node.process_mode = Node.PROCESS_MODE_DISABLED
func resume():
if node:
node.process_mode = Node.PROCESS_MODE_INHERIT

View file

@ -0,0 +1,48 @@
@tool
class_name DialogicDefaultPortrait # TODO: Question should this have a class_name? To be extended or what?
extends Node
## Default portrait scene.
## Stores the character that this scene displays.
var character: DialogicCharacter
## Stores the name of the current portrait.
var portrait: String
## Function to accept and use the extra data, if the custom portrait wants to accept it
func _set_extra_data(data: String) -> void:
pass
## This function can be overridden. Defaults to true, if not overridden!
func _should_do_portrait_update(character:DialogicCharacter, portrait:String) -> bool:
return true
## If the custom portrait accepts a change, then accept it here
func _update_portrait(passed_character:DialogicCharacter, passed_portrait:String) -> void:
if passed_portrait == "" or not passed_portrait in passed_character.portraits.keys():
passed_portrait = passed_character['default_portrait']
portrait = passed_portrait
if passed_character != null:
if character == null or character != passed_character:
character = passed_character
var path :String = character.portraits[portrait].get('image', '')
$Portrait.texture = null
if !path.is_empty(): $Portrait.texture = load(path)
$Portrait.centered = false
$Portrait.position = $Portrait.get_rect().size * Vector2(-0.5, -1)
## If implemented, this is called when the mirror changes
func _set_mirror(mirror:bool) -> void:
$Portrait.flip_h = mirror
## If implemented, this is used by the editor for the "full view" mode
func _get_covered_rect() -> Rect2:
if $Portrait.texture == null:
return Rect2()
return Rect2($Portrait.position, $Portrait.get_rect().size)

View file

@ -0,0 +1,14 @@
[gd_scene load_steps=3 format=3 uid="uid://c4yp65n7ju1br"]
[ext_resource type="Script" path="res://addons/dialogic/Events/Character/default_portrait.gd" id="1_wn77n"]
[sub_resource type="Texture2D" id="Texture2D_sny5f"]
resource_local_to_scene = false
resource_name = ""
[node name="DefaultPortrait" type="Node2D"]
script = ExtResource("1_wn77n")
[node name="Portrait" type="Sprite2D" parent="."]
texture = SubResource("Texture2D_sny5f")
centered = false

View file

@ -0,0 +1,471 @@
@tool
class_name DialogicCharacterEvent
extends DialogicEvent
## Event that allows to manipulate character portraits.
enum PortraitModes {VisualNovel, RPG}
enum ActionTypes {Join, Leave, Update}
### Settings
## The type of action of this event (JOIN/LEAVE/UPDATE). See [ActionTypes].
var action_type : int = ActionTypes.Join
## The character that will join/leave/update.
var character : DialogicCharacter = null
## For Join/Update, this will be the portrait of the character that is shown.
## Not used on Leave.
## If empty, the default portrait will be used.
var portrait: String = ""
## The index of the position this character should move to
var position: int = 1
## Path to an animation script (extending DialogicAnimation).
## On Join/Leave empty (default) will fallback to the animations set in the settings.
## On Update empty will mean no animation.
var animation_name: String = ""
## Length of the animation.
var animation_length: float = 0.5
## How often the animation is repeated. Only for Update events.
var animation_repeats: int = 1
## If true, the events waits for the animation to finish before the next event starts.
var animation_wait: bool = false
## For Update only. If bigger then 0, the portrait will tween to the
## new position (if changed) in this time (in seconds).
var position_move_time: float = 0.0
## The z_index that the portrait should have.
var z_index: int = 0
## If true, the portrait will be set to mirrored.
var mirrored: bool = false
## If set, will be passed to the portrait scene.
var extra_data: String = ""
### Helpers
## Indicates if the z_index should be updated.
var _update_zindex: bool = false
## Used to set the character resource from the unique name identifier and vice versa
var _character_from_directory: String:
get:
if _character_from_directory == '--All--':
return '--All--'
for item in _character_directory.keys():
if _character_directory[item]['resource'] == character:
return item
break
return ""
set(value):
_character_from_directory = value
if value in _character_directory.keys():
character = _character_directory[value]['resource']
else:
character = null
## Used by [_character_from_directory]
var _character_directory: Dictionary = {}
################################################################################
## EXECUTION
################################################################################
func _execute() -> void:
match action_type:
ActionTypes.Join:
if character:
if !dialogic.Portraits.is_character_joined(character):
var n = dialogic.Portraits.add_portrait(character, portrait, position, mirrored, z_index, extra_data)
if n:
if animation_name.is_empty():
animation_name = DialogicUtil.get_project_setting('dialogic/animations/join_default',
get_script().resource_path.get_base_dir().path_join('DefaultAnimations/fade_in_up.gd'))
animation_length = DialogicUtil.get_project_setting('dialogic/animations/join_default_length', 0.5)
animation_wait = DialogicUtil.get_project_setting('dialogic/animations/join_default_wait', true)
if animation_name:
var anim:DialogicAnimation = dialogic.Portraits.animate_portrait(character, animation_name, animation_length, animation_repeats)
if animation_wait:
dialogic.current_state = Dialogic.states.ANIMATING
await anim.finished
dialogic.current_state = Dialogic.states.IDLE
else:
dialogic.Portraits.change_portrait(character, portrait)
if animation_name.is_empty():
animation_length = DialogicUtil.get_project_setting('dialogic/animations/join_default_length', 0.5)
dialogic.Portraits.move_portrait(character, position, z_index, false, animation_length)
ActionTypes.Leave:
if _character_from_directory == '--All--':
if animation_name.is_empty():
animation_name = DialogicUtil.get_project_setting('dialogic/animations/leave_default',
get_script().resource_path.get_base_dir().path_join('DefaultAnimations/fade_out_down.gd'))
animation_length = DialogicUtil.get_project_setting('dialogic/animations/leave_default_length', 0.5)
animation_wait = DialogicUtil.get_project_setting('dialogic/animations/leave_default_wait', true)
if animation_name:
for chara in dialogic.Portraits.get_joined_characters():
var anim = dialogic.Portraits.animate_portrait(chara, animation_name, animation_length, animation_repeats)
anim.finished.connect(dialogic.Portraits.remove_portrait.bind(chara))
if animation_wait:
dialogic.current_state = Dialogic.states.ANIMATING
await anim.finished
dialogic.current_state = Dialogic.states.IDLE
else:
for chara in dialogic.Portraits.get_joined_characters():
dialogic.Portraits.remove_portrait(chara)
elif character:
if dialogic.Portraits.is_character_joined(character):
if animation_name.is_empty():
animation_name = DialogicUtil.get_project_setting('dialogic/animations/leave_default',
get_script().resource_path.get_base_dir().path_join('DefaultAnimations/fade_out_down.gd'))
animation_length = DialogicUtil.get_project_setting('dialogic/animations/leave_default_length', 0.5)
animation_wait = DialogicUtil.get_project_setting('dialogic/animations/leave_default_wait', true)
if animation_name:
var anim = dialogic.Portraits.animate_portrait(character, animation_name, animation_length, animation_repeats)
anim.finished.connect(dialogic.Portraits.remove_portrait.bind(character))
if animation_wait:
dialogic.current_state = Dialogic.states.ANIMATING
await anim.finished
dialogic.current_state = Dialogic.states.IDLE
else:
dialogic.Portraits.remove_portrait(character)
ActionTypes.Update:
if character:
if dialogic.Portraits.is_character_joined(character):
dialogic.Portraits.change_portrait(character, portrait, mirrored, z_index, _update_zindex, extra_data)
if position != 0:
dialogic.Portraits.move_portrait(character, position, z_index, _update_zindex, position_move_time)
if animation_name:
var anim = dialogic.Portraits.animate_portrait(character, animation_name, animation_length, animation_repeats)
if animation_wait:
dialogic.current_state = Dialogic.states.ANIMATING
await anim.finished
dialogic.current_state = Dialogic.states.IDLE
finish()
################################################################################
## INITIALIZE
################################################################################
func _init() -> void:
event_name = "Character"
set_default_color('Color2')
event_category = Category.Main
event_sorting_index = 2
continue_at_end = true
expand_by_default = false
func _get_icon() -> Resource:
return load(self.get_script().get_path().get_base_dir().path_join('icon_character.png'))
################################################################################
## SAVING/LOADING
################################################################################
func to_text() -> String:
var result_string := ""
match action_type:
ActionTypes.Join: result_string += "Join "
ActionTypes.Leave: result_string += "Leave "
ActionTypes.Update: result_string += "Update "
print('to_string')
print('character ', character)
print('characterfrom ', _character_from_directory)
if character or _character_from_directory == '--All--':
if action_type == ActionTypes.Leave and _character_from_directory == '--All--':
result_string += "--All--"
else:
var name := ""
for path in _character_directory.keys():
if _character_directory[path]['resource'] == character:
name = path
break
if name.count(" ") > 0:
name = '"' + name + '"'
result_string += name
if !portrait.is_empty() and action_type != ActionTypes.Leave:
result_string+= " ("+portrait+")"
if position and action_type != ActionTypes.Leave:
result_string += " "+str(position)
if animation_name != "" or z_index != 0 or mirrored != false or position_move_time != 0.0 or extra_data != "":
result_string += " ["
if animation_name:
result_string += 'animation="'+DialogicUtil.pretty_name(animation_name)+'"'
if animation_length != 0.5:
result_string += ' length="'+str(animation_length)+'"'
if animation_wait:
result_string += ' wait="'+str(animation_wait)+'"'
if animation_repeats != 1:
result_string += ' repeat="'+str(animation_repeats)+'"'
if z_index != 0:
result_string += ' z-index="' + str(z_index) + '"'
if mirrored:
result_string += ' mirrored="' + str(mirrored) + '"'
if position_move_time != 0:
result_string += ' move_time="' + str(position_move_time) + '"'
if extra_data != "":
result_string += ' extra_data="' + extra_data + '"'
result_string += "]"
return result_string
func from_text(string:String) -> void:
if Engine.is_editor_hint() == false:
_character_directory = Dialogic.character_directory
else:
_character_directory = self.get_meta("editor_character_directory")
var regex := RegEx.new()
# Reference regex without Godot escapes: (?<type>Join|Update|Leave)\s*(")?(?<name>(?(2)[^"\n]*|[^(: \n]*))(?(2)"|)(\W*\((?<portrait>.*)\))?(\s*(?<position>\d))?(\s*\[(?<shortcode>.*)\])?
regex.compile("(?<type>Join|Update|Leave)\\s*(\")?(?<name>(?(2)[^\"\\n]*|[^(: \\n]*))(?(2)\"|)(\\W*\\((?<portrait>.*)\\))?(\\s*(?<position>\\d))?(\\s*\\[(?<shortcode>.*)\\])?")
var result := regex.search(string)
match result.get_string('type'):
"Join":
action_type = ActionTypes.Join
"Leave":
action_type = ActionTypes.Leave
"Update":
action_type = ActionTypes.Update
if result.get_string('name').strip_edges():
if action_type == ActionTypes.Leave and result.get_string('name').strip_edges() == "--All--":
_character_from_directory = '--All--'
else:
var name := result.get_string('name').strip_edges()
if _character_directory != null:
if _character_directory.size() > 0:
character = null
if _character_directory.has(name):
character = _character_directory[name]['resource']
else:
name = name.replace('"', "")
# First do a full search to see if more of the path is there then necessary:
for character in _character_directory:
if name in _character_directory[character]['full_path']:
character = _character_directory[character]['resource']
break
# If it doesn't exist, we'll consider it a guest and create a temporary character
if character == null:
if Engine.is_editor_hint() == false:
character = DialogicCharacter.new()
character.display_name = name
var entry:Dictionary = {}
entry['resource'] = character
entry['full_path'] = "runtime://" + name
Dialogic.character_directory[name] = entry
if result.get_string('portrait').strip_edges():
portrait = result.get_string('portrait').strip_edges()
if result.get_string('position'):
position = result.get_string('position').to_int()
elif action_type == ActionTypes.Update:
# Override the normal default if it's an Update
position = 0
if result.get_string('shortcode'):
var shortcode_params = parse_shortcode_parameters(result.get_string('shortcode'))
animation_name = shortcode_params.get('animation', '')
if animation_name != "":
if !animation_name.ends_with('.gd'):
animation_name = guess_animation_file(animation_name)
if !animation_name.ends_with('.gd'):
printerr("[Dialogic] Couldn't identify animation '"+animation_name+"'.")
animation_name = ""
var animLength = shortcode_params.get('length', '0.5').to_float()
if typeof(animLength) == TYPE_FLOAT:
animation_length = animLength
else:
animation_length = animLength.to_float()
animation_wait = DialogicUtil.str_to_bool(shortcode_params.get('wait', 'false'))
#repeat is supported on Update, the other two should not be checking this
if action_type == ActionTypes.Update:
animation_repeats = shortcode_params.get('repeat', 1).to_int()
position_move_time = shortcode_params.get('move_time', 0.0)
#move time is only supported on Update, but it isnt part of the animations so its separate
if action_type == ActionTypes.Update:
if typeof(shortcode_params.get('move_time', 0)) == TYPE_STRING:
position_move_time = shortcode_params.get('move_time', 0.0).to_float()
if typeof(shortcode_params.get('z-index', 0)) == TYPE_STRING:
z_index = shortcode_params.get('z-index', 0).to_int()
_update_zindex = true
mirrored = DialogicUtil.str_to_bool(shortcode_params.get('mirrored', 'false'))
extra_data = shortcode_params.get('extra_data', "")
func is_valid_event(string:String) -> bool:
if string.begins_with("Join ") or string.begins_with("Leave ") or string.begins_with("Update "):
return true
return false
################################################################################
## EDITOR REPRESENTATION
################################################################################
func build_event_editor() -> void:
add_header_edit('action_type', ValueType.FixedOptionSelector, '', '', {
'selector_options': [
{
'label': 'Join',
'value': ActionTypes.Join,
'icon': load("res://addons/dialogic/Editor/Images/Dropdown/join.svg")
},
{
'label': 'Leave',
'value': ActionTypes.Leave,
'icon': load("res://addons/dialogic/Editor/Images/Dropdown/leave.svg")
},
{
'label': 'Update',
'value': ActionTypes.Update,
'icon': load("res://addons/dialogic/Editor/Images/Dropdown/update.svg")
}
]
})
add_header_edit('_character_from_directory', ValueType.ComplexPicker, '', '',
{'empty_text' : 'Character',
'file_extension' : '.dch',
'suggestions_func' : get_character_suggestions,
'icon' : load("res://addons/dialogic/Editor/Images/Resources/character.svg")})
add_header_button('', _on_character_edit_pressed, 'Edit character', ["Edit", "EditorIcons"], 'character != null and _character_from_directory != "--All--"')
add_header_edit('portrait', ValueType.ComplexPicker, '', '',
{'empty_text' : 'Default',
'suggestions_func' : get_portrait_suggestions,
'icon' : load("res://addons/dialogic/Editor/Images/Resources/portrait.svg")},
'character != null and !has_no_portraits() and action_type != %s' %ActionTypes.Leave)
add_header_edit('position', ValueType.Integer, ' at position', '', {},
'character != null and !has_no_portraits() and action_type != %s' %ActionTypes.Leave)
# Body
add_body_edit('animation_name', ValueType.ComplexPicker, 'Animation:', '',
{'suggestions_func' : get_animation_suggestions,
'editor_icon' : ["Animation", "EditorIcons"],
'placeholder' : 'Default',
'enable_pretty_name' : true},
'should_show_animation_options()')
add_body_edit('animation_length', ValueType.Float, 'Length:', '', {},
'should_show_animation_options() and !animation_name.is_empty()')
add_body_edit('animation_wait', ValueType.Bool, 'Wait for animation to finish:', '', {},
'should_show_animation_options() and !animation_name.is_empty()')
add_body_edit('animation_repeats', ValueType.Integer, 'Repeat:', '', {},
'should_show_animation_options() and !animation_name.is_empty() and action_type == %s)' %ActionTypes.Update)
add_body_edit('z_index', ValueType.Integer, 'portrait z-index:', "",{},
'action_type != %s' %ActionTypes.Leave)
add_body_edit('mirrored', ValueType.Bool, 'mirrored:', "",{},
'action_type != %s' %ActionTypes.Leave)
add_body_edit('position_move_time', ValueType.Float, 'Transiton time to change position:', '', {},
'action_type == %s' %ActionTypes.Update)
func should_show_animation_options() -> bool:
return (character != null and !character.portraits.is_empty()) or _character_from_directory == '--All--'
func has_no_portraits() -> bool:
return character and character.portraits.is_empty()
func get_character_suggestions(search_text:String) -> Dictionary:
var suggestions := {}
#override the previous _character_directory with the meta, specifically for searching otherwise new nodes wont work
_character_directory = Engine.get_meta('dialogic_character_directory')
var icon = load("res://addons/dialogic/Editor/Images/Resources/character.svg")
suggestions['(No one)'] = {'value':'', 'editor_icon':["GuiRadioUnchecked", "EditorIcons"]}
if action_type == ActionTypes.Leave:
suggestions['ALL'] = {'value':'--All--', 'tooltip':'All currently joined characters leave', 'editor_icon':["GuiEllipsis", "EditorIcons"]}
for resource in _character_directory.keys():
suggestions[resource] = {'value': resource, 'tooltip': _character_directory[resource]['full_path'], 'icon': icon.duplicate()}
return suggestions
func get_portrait_suggestions(search_text:String) -> Dictionary:
var suggestions := {}
var icon = load("res://addons/dialogic/Editor/Images/Resources/portrait.svg")
if action_type == ActionTypes.Update:
suggestions["Don't Change"] = {'value':'', 'editor_icon':["GuiRadioUnchecked", "EditorIcons"]}
if action_type == ActionTypes.Join:
suggestions["Default portrait"] = {'value':'', 'editor_icon':["GuiRadioUnchecked", "EditorIcons"]}
if character != null:
for portrait in character.portraits:
suggestions[portrait] = {'value':portrait, 'icon':icon.duplicate()}
return suggestions
func get_animation_suggestions(search_text:String) -> Dictionary:
var suggestions := {}
match action_type:
ActionTypes.Join, ActionTypes.Leave:
suggestions['Default'] = {'value':"", 'editor_icon':["GuiRadioUnchecked", "EditorIcons"]}
ActionTypes.Update:
suggestions['None'] = {'value':"", 'editor_icon':["GuiRadioUnchecked", "EditorIcons"]}
for anim in list_animations():
match action_type:
ActionTypes.Join:
if '_in' in anim.get_file():
suggestions[DialogicUtil.pretty_name(anim)] = {'value':anim, 'editor_icon':["Animation", "EditorIcons"]}
ActionTypes.Leave:
if '_out' in anim.get_file():
suggestions[DialogicUtil.pretty_name(anim)] = {'value':anim, 'editor_icon':["Animation", "EditorIcons"]}
ActionTypes.Update:
if not ('_in' in anim.get_file() or '_out' in anim.get_file()):
suggestions[DialogicUtil.pretty_name(anim)] = {'value':anim, 'editor_icon':["Animation", "EditorIcons"]}
continue
return suggestions
func list_animations() -> Array:
var list := DialogicUtil.listdir(get_script().resource_path.get_base_dir().path_join('DefaultAnimations'), true, false, true)
list.append_array(DialogicUtil.listdir(DialogicUtil.get_project_setting('dialogic/animations/custom_folder', 'res://addons/dialogic_additions/Animations'), true, false, true))
return list
func guess_animation_file(animation_name: String) -> String:
for file in list_animations():
if DialogicUtil.pretty_name(animation_name) == DialogicUtil.pretty_name(file):
return file
return animation_name
func _on_character_edit_pressed() -> void:
var editor_manager := _editor_node.find_parent('EditorsManager')
if editor_manager:
editor_manager.edit_resource(character)

View file

@ -0,0 +1,111 @@
@tool
class_name DialogicPositionEvent
extends DialogicEvent
## Event that allows moving of positions (and characters that are on that position).
## Requires the Portraits subsystem to be present!
enum ActionTypes {SetRelative, SetAbsolute, Reset, ResetAll}
### Settings
## The type of action: SetRelative, SetAbsolute, Reset, ResetAll
var action_type := ActionTypes.SetRelative
## The position that should be affected
var position: int = 0
## A vector representing a relative change or an absolute position (for SetRelative and SetAbsolute)
var vector: Vector2 = Vector2()
## The time the tweening will take.
var movement_time: float = 0
################################################################################
## EXECUTE
################################################################################
func _execute() -> void:
# If for some someone sets it to 0, ignore it entirely. 0 position is used for Update to indicate no change.
if action_type != ActionTypes.ResetAll and position == 0:
finish()
return
match action_type:
ActionTypes.SetRelative:
dialogic.Portraits.move_portrait_position(position, vector, true, movement_time)
ActionTypes.SetAbsolute:
dialogic.Portraits.move_portrait_position(position, vector, false, movement_time)
ActionTypes.ResetAll:
dialogic.Portraits.reset_portrait_positions(movement_time)
ActionTypes.Reset:
dialogic.Portraits.reset_portrait_position(position, movement_time)
finish()
##############################################file_path##################################
## INITIALIZE
################################################################################
func _init() -> void:
event_name = "position"
set_default_color('Color2')
event_category = Category.Main
event_sorting_index = 2
continue_at_end = true
expand_by_default = false
func _get_icon() -> Resource:
return load(self.get_script().get_path().get_base_dir().path_join('icon_position.png'))
################################################################################
## SAVING/LOADING
################################################################################
func get_shortcode() -> String:
return "update_position"
func get_shortcode_parameters() -> Dictionary:
return {
#param_name : property_info
"action" : {"sproperty": "action_type", "default": ActionTypes.SetRelative,
"suggestions": func(): return {"Set Relative":{'value':'0'}, "Set Absolute":{'value':'1'}, "Reset":{'value':'2'}, "Reset All":{'value':'3'}}},
"position" : {"property": "position", "default": 0},
"vector" : {"property": "vector", "default": Vector2()},
"time" : {"property": "movement_time", "default": 0},
}
################################################################################
## EDITOR REPRESENTATION
################################################################################
func build_event_editor():
add_header_edit('action_type', ValueType.FixedOptionSelector, '', '', {
'selector_options': [
{
'label': 'Change',
'value': ActionTypes.SetRelative,
},
{
'label': 'Set',
'value': ActionTypes.SetAbsolute,
},
{
'label': 'Reset',
'value': ActionTypes.Reset,
},
{
'label': 'Reset All',
'value': ActionTypes.ResetAll,
}
]
})
add_header_edit("position", ValueType.Integer, "position", '', {},
'action_type != ActionTypes.ResetAll')
add_header_label('to (absolute)', 'action_type == ActionTypes.SetAbsolute')
add_header_label('by (relative)', 'action_type == ActionTypes.SetRelative')
add_header_edit("vector", ValueType.Vector2, "", '', {},
'action_type != ActionTypes.Reset and action_type != ActionTypes.ResetAll')
add_body_edit("movement_time", ValueType.Float, "AnimationTime:", "(0 for instant)")

View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dn5jx2ucynfio"
path="res://.godot/imported/icon.png-a6ef7c3eeb0fb100c7d0b0c505ea4b6f.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/dialogic/Events/Character/icon.png"
dest_files=["res://.godot/imported/icon.png-a6ef7c3eeb0fb100c7d0b0c505ea4b6f.ctex"]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/bptc_ldr=0
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 569 B

View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b1i3ttk36kghm"
path="res://.godot/imported/icon_character.png-93ea754ecc6f43d01c1116ffee9f80e3.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/dialogic/Events/Character/icon_character.png"
dest_files=["res://.godot/imported/icon_character.png-93ea754ecc6f43d01c1116ffee9f80e3.ctex"]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/bptc_ldr=0
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,017 B

View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cu6cj55m6dr78"
path="res://.godot/imported/icon_position.png-18fc8e57ed28c84781c862b6ca4492f7.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/dialogic/Events/Character/icon_position.png"
dest_files=["res://.godot/imported/icon_position.png-18fc8e57ed28c84781c862b6ca4492f7.ctex"]
[params]
compress/mode=0
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/bptc_ldr=0
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

View file

@ -0,0 +1,14 @@
@tool
extends DialogicIndexer
func _get_events() -> Array:
return [this_folder.path_join('event_character.gd'), this_folder.path_join('event_position.gd')]
func _get_subsystems() -> Array:
return [{'name':'Portraits', 'script':this_folder.path_join('subsystem_portraits.gd')}]
func _get_settings_pages() -> Array:
return [this_folder.path_join('settings_portraits.tscn')]

View file

@ -0,0 +1,6 @@
class_name DialogicNode_PortraitHolder
extends CanvasLayer
func _ready():
add_to_group('dialogic_portrait_holder')

View file

@ -0,0 +1,9 @@
class_name DialogicNode_PortraitPosition
extends Marker2D
## Used to identify positions in the [DialogicCharacterEvent].
@export var position_index = 0
func _ready() -> void:
add_to_group('dialogic_portrait_position')

View file

@ -0,0 +1,88 @@
@tool
extends HBoxContainer
var current_animation_path = ""
func _ready():
%JoinDefault.get_suggestions_func = get_join_animation_suggestions
%JoinDefault.enable_pretty_name = true
%LeaveDefault.get_suggestions_func = get_leave_animation_suggestions
%LeaveDefault.enable_pretty_name = true
func refresh():
%CustomAnimationsFolderOpener.icon = get_theme_icon("Folder", "EditorIcons")
%CustomAnimationsFolder.text = DialogicUtil.get_project_setting('dialogic/animations/custom_folder', 'res://addons/dialogic_additions/Animations')
%PortraitMode.select(DialogicUtil.get_project_setting('dialogic/portrait_mode', 0))
%JoinDefault.resource_icon = get_theme_icon("Animation", "EditorIcons")
%LeaveDefault.resource_icon = get_theme_icon("Animation", "EditorIcons")
%JoinDefault.set_value(DialogicUtil.pretty_name(DialogicUtil.get_project_setting('dialogic/animations/join_default',
get_script().resource_path.get_base_dir().path_join('DefaultAnimations/fade_in_up.gd'))))
%LeaveDefault.set_value(DialogicUtil.get_project_setting('dialogic/animations/leave_default',
get_script().resource_path.get_base_dir().path_join('DefaultAnimations/fade_out_down.gd')))
%JoinDefaultLength.set_value(DialogicUtil.get_project_setting('dialogic/animations/join_default_length', 0.5))
%LeaveDefaultLength.set_value(DialogicUtil.get_project_setting('dialogic/animations/leave_default_length', 0.5))
%LeaveDefaultWait.button_pressed = DialogicUtil.get_project_setting('dialogic/animations/leave_default_wait', true)
%JoinDefaultWait.button_pressed = DialogicUtil.get_project_setting('dialogic/animations/join_default_wait', true)
func _on_LeaveDefault_value_changed(property_name, value):
ProjectSettings.set_setting('dialogic/animations/leave_default', value)
ProjectSettings.save()
func _on_JoinDefault_value_changed(property_name, value):
ProjectSettings.set_setting('dialogic/animations/join_default', value)
ProjectSettings.save()
func _on_JoinDefaultLength_value_changed(value):
ProjectSettings.set_setting('dialogic/animations/join_default_length', value)
ProjectSettings.save()
func _on_LeaveDefaultLength_value_changed(value):
ProjectSettings.set_setting('dialogic/animations/leave_default_length', value)
ProjectSettings.save()
func _on_JoinDefaultWait_toggled(button_pressed):
ProjectSettings.set_setting('dialogic/animations/join_default_wait', button_pressed)
ProjectSettings.save()
func _on_LeaveDefaultWait_toggled(button_pressed):
ProjectSettings.set_setting('dialogic/animations/leave_default_wait', button_pressed)
ProjectSettings.save()
func get_join_animation_suggestions(search_text:String) -> Dictionary:
var suggestions = {}
for anim in list_animations():
if '_in' in anim.get_file():
suggestions[DialogicUtil.pretty_name(anim)] = {'value':anim, 'icon':get_theme_icon('Animation', 'EditorIcons')}
return suggestions
func get_leave_animation_suggestions(search_text:String) -> Dictionary:
var suggestions = {}
for anim in list_animations():
if '_out' in anim.get_file():
suggestions[DialogicUtil.pretty_name(anim)] = {'value':anim, 'icon':get_theme_icon('Animation', 'EditorIcons')}
return suggestions
func list_animations() -> Array:
var list = DialogicUtil.listdir(get_script().resource_path.get_base_dir().path_join('DefaultAnimations'), true, false, true)
list.append_array(DialogicUtil.listdir(DialogicUtil.get_project_setting('dialogic/animations/custom_folder', 'res://addons/dialogic_additions/Animations'), true, false, true))
return list
func _on_CustomAnimationsFolderOpener_pressed():
find_parent('EditorView').godot_file_dialog(custom_anims_folder_selected, '', EditorFileDialog.FILE_MODE_OPEN_DIR, 'Select custom animation folder')
func custom_anims_folder_selected(path):
%CustomAnimationsFolder.text = path
ProjectSettings.set_setting('dialogic/animations/custom_folder', path)
ProjectSettings.save()
func _on_PortraitMode_item_selected(index):
ProjectSettings.set_setting('dialogic/portrait_mode', index)
ProjectSettings.save()

View file

@ -0,0 +1,232 @@
[gd_scene load_steps=7 format=3]
[ext_resource type="Script" path="res://addons/dialogic/Events/Character/settings_portraits.gd" id="2"]
[ext_resource type="StyleBox" uid="uid://dmsjhgv22dns8" path="res://addons/dialogic/Editor/Common/TitleBgStylebox.tres" id="2_i6l15"]
[ext_resource type="PackedScene" uid="uid://dpwhshre1n4t6" path="res://addons/dialogic/Editor/Events/Fields/ComplexPicker.tscn" id="3"]
[ext_resource type="Script" path="res://addons/dialogic/Editor/Settings/HintLabelStylingScript.gd" id="3_jknrx"]
[sub_resource type="Image" id="Image_t2ykh"]
data = {
"data": PackedByteArray(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0),
"format": "LumAlpha8",
"height": 16,
"mipmaps": false,
"width": 16
}
[sub_resource type="ImageTexture" id="2"]
image = SubResource("Image_t2ykh")
[node name="Portraits" type="HBoxContainer"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
script = ExtResource("2")
[node name="General" type="VBoxContainer" parent="."]
layout_mode = 2
offset_right = 650.0
offset_bottom = 648.0
size_flags_horizontal = 3
[node name="Title" type="Label" parent="General"]
layout_mode = 2
offset_right = 650.0
offset_bottom = 36.0
theme_override_styles/normal = ExtResource("2_i6l15")
text = "Portrait Settings"
[node name="HBoxContainer" type="HBoxContainer" parent="General"]
layout_mode = 2
offset_top = 40.0
offset_right = 650.0
offset_bottom = 71.0
[node name="Label" type="Label" parent="General/HBoxContainer"]
layout_mode = 2
offset_top = 2.0
offset_right = 460.0
offset_bottom = 28.0
size_flags_horizontal = 3
text = "Portraits mode"
[node name="PortraitMode" type="OptionButton" parent="General/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
offset_left = 464.0
offset_right = 650.0
offset_bottom = 31.0
item_count = 2
selected = 0
popup/item_0/text = "Visual Novel Mode"
popup/item_0/id = 0
popup/item_1/text = "RPG Mode"
popup/item_1/id = 1
[node name="Info" type="Label" parent="General"]
layout_mode = 2
offset_top = 75.0
offset_right = 650.0
offset_bottom = 205.0
text = "Visual Novel: Multiple characters can show up in different positions at the same time. They have to be joined and leaved manually.
RPG: Only the character that speaks is visible (always in the same location). Does not have to be joined/left manually. Uses only position_idx 1."
autowrap_mode = 3
script = ExtResource("3_jknrx")
[node name="VSeparator" type="VSeparator" parent="."]
layout_mode = 2
offset_left = 654.0
offset_right = 658.0
offset_bottom = 648.0
[node name="Animations" type="VBoxContainer" parent="."]
layout_mode = 2
offset_left = 662.0
offset_right = 1152.0
offset_bottom = 648.0
size_flags_horizontal = 3
size_flags_stretch_ratio = 0.5
[node name="Title" type="Label" parent="Animations"]
layout_mode = 2
offset_right = 490.0
offset_bottom = 36.0
theme_override_styles/normal = ExtResource("2_i6l15")
text = "Animations
"
[node name="HBoxContainer" type="HBoxContainer" parent="Animations"]
layout_mode = 2
offset_top = 40.0
offset_right = 490.0
offset_bottom = 71.0
[node name="Label2" type="Label" parent="Animations/HBoxContainer"]
layout_mode = 2
offset_top = 2.0
offset_right = 229.0
offset_bottom = 28.0
size_flags_horizontal = 3
text = "Custom anims folder"
[node name="CustomAnimationsFolder" type="LineEdit" parent="Animations/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
offset_left = 233.0
offset_right = 462.0
offset_bottom = 31.0
size_flags_horizontal = 3
caret_blink = true
[node name="CustomAnimationsFolderOpener" type="Button" parent="Animations/HBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
offset_left = 466.0
offset_right = 490.0
offset_bottom = 31.0
icon = SubResource("2")
[node name="HSeparator" type="HSeparator" parent="Animations"]
layout_mode = 2
offset_top = 75.0
offset_right = 490.0
offset_bottom = 79.0
[node name="Title2" type="Label" parent="Animations"]
layout_mode = 2
offset_top = 83.0
offset_right = 490.0
offset_bottom = 119.0
theme_override_styles/normal = ExtResource("2_i6l15")
text = "Default Animations
"
[node name="DefaultIn" type="HBoxContainer" parent="Animations"]
layout_mode = 2
offset_top = 123.0
offset_right = 490.0
offset_bottom = 156.0
[node name="Label3" type="Label" parent="Animations/DefaultIn"]
layout_mode = 2
offset_top = 3.0
offset_right = 107.0
offset_bottom = 29.0
size_flags_horizontal = 3
text = "Default Join"
[node name="JoinDefault" parent="Animations/DefaultIn" instance=ExtResource("3")]
unique_name_in_owner = true
layout_mode = 2
anchors_preset = 0
anchor_right = 0.0
anchor_bottom = 0.0
offset_left = 111.0
offset_right = 311.0
offset_bottom = 33.0
[node name="JoinDefaultLength" type="SpinBox" parent="Animations/DefaultIn"]
unique_name_in_owner = true
layout_mode = 2
offset_left = 315.0
offset_right = 398.0
offset_bottom = 33.0
step = 0.1
[node name="JoinDefaultWait" type="CheckButton" parent="Animations/DefaultIn"]
unique_name_in_owner = true
layout_mode = 2
offset_left = 402.0
offset_right = 490.0
offset_bottom = 33.0
text = "Wait:"
[node name="DefaultOut" type="HBoxContainer" parent="Animations"]
layout_mode = 2
offset_top = 160.0
offset_right = 490.0
offset_bottom = 193.0
[node name="Label4" type="Label" parent="Animations/DefaultOut"]
layout_mode = 2
offset_top = 3.0
offset_right = 107.0
offset_bottom = 29.0
size_flags_horizontal = 3
text = "Default Leave"
[node name="LeaveDefault" parent="Animations/DefaultOut" instance=ExtResource("3")]
unique_name_in_owner = true
layout_mode = 2
anchors_preset = 0
anchor_right = 0.0
anchor_bottom = 0.0
offset_left = 111.0
offset_right = 311.0
offset_bottom = 33.0
[node name="LeaveDefaultLength" type="SpinBox" parent="Animations/DefaultOut"]
unique_name_in_owner = true
layout_mode = 2
offset_left = 315.0
offset_right = 398.0
offset_bottom = 33.0
step = 0.1
[node name="LeaveDefaultWait" type="CheckButton" parent="Animations/DefaultOut"]
unique_name_in_owner = true
layout_mode = 2
offset_left = 402.0
offset_right = 490.0
offset_bottom = 33.0
text = "Wait:"
[connection signal="item_selected" from="General/HBoxContainer/PortraitMode" to="." method="_on_PortraitMode_item_selected"]
[connection signal="pressed" from="Animations/HBoxContainer/CustomAnimationsFolderOpener" to="." method="_on_CustomAnimationsFolderOpener_pressed"]
[connection signal="value_changed" from="Animations/DefaultIn/JoinDefault" to="." method="_on_JoinDefault_value_changed"]
[connection signal="value_changed" from="Animations/DefaultIn/JoinDefaultLength" to="." method="_on_JoinDefaultLength_value_changed"]
[connection signal="toggled" from="Animations/DefaultIn/JoinDefaultWait" to="." method="_on_JoinDefaultWait_toggled"]
[connection signal="value_changed" from="Animations/DefaultOut/LeaveDefault" to="." method="_on_LeaveDefault_value_changed"]
[connection signal="value_changed" from="Animations/DefaultOut/LeaveDefaultLength" to="." method="_on_LeaveDefaultLength_value_changed"]
[connection signal="toggled" from="Animations/DefaultOut/LeaveDefaultWait" to="." method="_on_LeaveDefaultWait_toggled"]

View file

@ -0,0 +1,306 @@
extends DialogicSubsystem
## Subsystem that manages portraits and portrait positions.
## The default portrait scene.
var default_portrait_scene = load(get_script().resource_path.get_base_dir().path_join('default_portrait.tscn'))
## Temporarily stores the default positions.
var _default_positions: Dictionary = {}
## Stores the current positions.
var current_positions: Dictionary = {}
## A reference to the current [DialogicNode_PortraitHolder].
var _portrait_holder_reference: Node = null
####################################################################################################
## STATE
####################################################################################################
func clear_game_state():
for character in dialogic.current_state_info.get('portraits', {}).keys():
remove_portrait(load(character))
dialogic.current_state_info['portraits'] = {}
func load_game_state():
for character_path in dialogic.current_state_info['portraits']:
add_portrait(
load(character_path),
dialogic.current_state_info['portraits'][character_path].portrait, dialogic.current_state_info['portraits'][character_path].position_index,
false)
func pause() -> void:
for portrait in dialogic.current_state_info['portraits'].values():
if portrait.has('animation_node'):
portrait.animation_node.pause()
func resume() -> void:
for portrait in dialogic.current_state_info['portraits'].values():
if portrait.has('animation_node'):
portrait.animation_node.resume()
####################################################################################################
## MAIN METHODS
####################################################################################################
## Joins a character and then calls change_portrait().
func add_portrait(character:DialogicCharacter, portrait:String, position_idx:int, mirrored: bool = false, z_index: int = 0, extra_data:String = "") -> Node:
var character_node = null
if portrait.is_empty():
portrait = character.default_portrait
if not character:
print_debug('[DialogicError] Cannot call add_portrait() with null character.')
return null
if not portrait in character.portraits:
print_debug("[DialogicError] Tried joining ",character.display_name, " wit not-existing portrait '", portrait, "'. Will use default portrait instead.")
portrait = character.default_portrait
if portrait.is_empty():
print_debug("[DialogicError] Character ",character.display_name, " has no default portrait to use.")
return null
check_positions_and_holder()
character_node = Node2D.new()
character_node.name = character.get_character_name()
character_node.position = current_positions[position_idx]
character_node.z_index = z_index
character_node.set_meta("position", position_idx)
_portrait_holder_reference.add_child(character_node)
if character_node:
dialogic.current_state_info['portraits'][character.resource_path] = {'portrait':portrait, 'node':character_node, 'position_index':position_idx}
if portrait:
change_portrait(character, portrait, mirrored, z_index, false, extra_data)
return character_node
## Changes the portrait of a character. Only works with characters that joined previously.
func change_portrait(character:DialogicCharacter, portrait:String, mirrored:bool = false, z_index: int = 0, update_zindex:bool = false, extra_data:String = "") -> void:
if not character or not is_character_joined(character):
print_debug('[DialogicError] Cannot change portrait of null/not joined character.')
return null
if portrait.is_empty():
portrait = character.default_portrait
if not portrait in character.portraits.keys():
print_debug('[Dialogic] Change to not-existing portrait will be ignored!')
return
var char_node :Node = dialogic.current_state_info.portraits[character.resource_path].node
if update_zindex:
char_node.z_index = z_index
# path to the scene to use
var scene_path :String = character.portraits[portrait].get('scene', '')
var portrait_node = null
# check if the scene is the same as the currently loaded scene
if (char_node.get_child_count() and
character.portraits[dialogic.current_state_info['portraits'][character.resource_path]['portrait']].get('scene', '') == scene_path and
# also check if the scene supports changing to the given portrait
(!char_node.get_child(0).has_method('_should_do_portrait_update') or char_node.get_child(0)._should_do_portrait_update(character, portrait))):
portrait_node = char_node.get_child(0)
else:
# remove previous portrait
if char_node.get_child_count():
char_node.get_child(0).queue_free()
if scene_path.is_empty():
portrait_node = default_portrait_scene.instantiate()
else:
var p = load(scene_path)
if p:
portrait_node = p.instantiate()
else:
push_error('Dialogic: Portrait node "' + str(scene_path) + '" for character [' + character.display_name + '] could not be loaded. Your portrait might not show up on the screen.')
if portrait_node:
portrait_node.position = character.offset + character.portraits[portrait].get('offset', Vector2())
# ignore the character scale on custom portraits that have 'ignore_char_scale' set to true
if scene_path.is_empty() or !character.portraits[portrait].get('ignore_char_scale', false):
portrait_node.scale = Vector2(1,1)*character.scale * character.portraits[portrait].get('scale', 1)
else:
portrait_node.scale = Vector2(1,1)*character.portraits[portrait].get('scale', 1)
for property in character.portraits[portrait].get('export_overrides', {}).keys():
portrait_node.set(property, str_to_var(character.portraits[portrait]['export_overrides'][property]))
if portrait_node.has_method('_update_portrait'):
portrait_node._update_portrait(character, portrait)
if portrait_node.has_method('_set_mirror'):
portrait_node._set_mirror(mirrored)
if portrait_node.has_method('_set_extra_data'):
portrait_node._set_extra_data(extra_data)
if !portrait_node.is_inside_tree():
char_node.add_child(portrait_node)
dialogic.current_state_info['portraits'][character.resource_path]['portrait'] = portrait
## Animates the portrait of the given character with the given animation.
func animate_portrait(character:DialogicCharacter, animation_path:String, length:float, repeats = 1) -> DialogicAnimation:
if not character or not is_character_joined(character):
print_debug('[DialogicError] Cannot animate portrait of null/not joined character.')
return null
var char_node = dialogic.current_state_info['portraits'][character.resource_path].node
if dialogic.current_state_info['portraits'][character.resource_path].get('animation_node', null):
if is_instance_valid(dialogic.current_state_info['portraits'][character.resource_path].animation_node):
dialogic.current_state_info['portraits'][character.resource_path].animation_node.queue_free()
var anim_script = load(animation_path)
var anim_node = Node.new()
anim_node.set_script(anim_script)
anim_node = (anim_node as DialogicAnimation)
anim_node.node = char_node
anim_node.orig_pos = char_node.position
anim_node.end_position = char_node.position
anim_node.time = length
anim_node.repeats = repeats
add_child(anim_node)
anim_node.animate()
dialogic.current_state_info['portraits'][character.resource_path]['animation_node'] = anim_node
return anim_node
## Moves the portrait of the given character to the given positions. Also allows updating the z_index.
## TODO: Question, why the z_index is set here and in change_portrait and doesn't have it's own method.
func move_portrait(character:DialogicCharacter, position_idx:int, z_index:int = 0, update_zindex:bool = false, time:float = 0.0):
if not character or not is_character_joined(character):
print_debug('[DialogicError] Cannot move portrait of null/not joined character.')
return null
var char_node = dialogic.current_state_info.portraits[character.resource_path].node
if update_zindex:
char_node.z_index = z_index
char_node.set_meta('position', position_idx)
if time == 0.0:
char_node.position = current_positions[position_idx]
else:
var tween = char_node.create_tween()
tween.tween_property(char_node, "position", current_positions[position_idx], time)
dialogic.current_state_info.portraits[character.resource_path].position_index = position_idx
func remove_portrait(character:DialogicCharacter) -> void:
dialogic.current_state_info['portraits'][character.resource_path].node.queue_free()
dialogic.current_state_info['portraits'].erase(character.resource_path)
## Creates additional positions either from timeline or at runtime
## If it's an existing position, will move that position to the coordinates instead
## There's no need to actually remove them once added, but saves will need to track position updates as well, so the whole current_positions array will need to be saved
## This will always be an absolute value for new positions, existing positions will be updated as absolute values by this
func add_portrait_position(position_number: int, position:Vector2) -> void:
check_positions_and_holder()
if position_number in current_positions:
move_portrait_position(position_number, position)
else:
# Add to both current and default positions
_default_positions[position_number] = position
current_positions[position_number] = position
func reset_portrait_positions(time:float = 0.0) -> void:
for position in current_positions:
move_portrait_position(position, _default_positions[position], false, time)
func reset_portrait_position(position:int, time:float = 0.0) -> void:
move_portrait_position(position, _default_positions[position], false, time)
func move_portrait_position(position_number: int, vector:Vector2, relative:bool = false, time:float = 0.0) -> void:
check_positions_and_holder()
if !position_number in current_positions:
if !relative:
add_portrait_position(position_number, vector)
else:
print_debug('[DialogicError] Cannot move non-existent position. (Use SetAbsolute to create a new position)')
return null
if !relative:
current_positions[position_number] = vector
else:
current_positions[position_number] += vector
for child in _portrait_holder_reference.get_children():
if child.get_meta('position') == position_number:
if time != 0.0:
var tween = child.create_tween()
tween.tween_property(child, "position", current_positions[position_number], time)
else:
child.position = current_positions[position_number]
####################################################################################################
## HELPERS
####################################################################################################
func is_character_joined(character:DialogicCharacter) -> bool:
return character.resource_path in dialogic.current_state_info['portraits']
func get_joined_characters() -> Array:
var chars = []
for char_path in dialogic.current_state_info.get('portraits', {}).keys():
chars.append(load(char_path))
return chars
func update_rpg_portrait_mode(character:DialogicCharacter = null, portrait:String = "") -> void:
if DialogicUtil.get_project_setting('dialogic/portrait_mode', 0) == DialogicCharacterEvent.PortraitModes.RPG:
if !Dialogic.current_state_info.has('rpg_last_portrait'):
Dialogic.current_state_info['rpg_last_portraits'] = {}
if character != null:
if portrait == "":
if Dialogic.current_state_info['rpg_last_portraits'].has(character.resource_path):
portrait = Dialogic.current_state_info['rpg_last_portraits'][character.resource_path]
else:
portrait = character.default_portrait
Dialogic.current_state_info['rpg_last_portraits'][character.resource_path] = portrait
var char_joined = false
for joined_character in dialogic.current_state_info.portraits:
if not character or (joined_character != character.resource_path):
var AnimationName = DialogicUtil.get_project_setting('dialogic/animations/leave_default',
get_script().resource_path.get_base_dir().path_join('DefaultAnimations/fade_out_down.gd'))
var AnimationLength = DialogicUtil.get_project_setting('dialogic/animations/leave_default_length', 0.5)
var anim = animate_portrait(load(joined_character), AnimationName, AnimationLength)
anim.finished.connect(remove_portrait.bind(load(joined_character)))
else:
char_joined = true
if (not char_joined) and character and portrait in character.portraits:
var AnimationName = DialogicUtil.get_project_setting('dialogic/animations/join_default',
get_script().resource_path.get_base_dir().path_join('DefaultAnimations/fade_in_up.gd'))
var AnimationLength = DialogicUtil.get_project_setting('dialogic/animations/join_default_length', 0.5)
add_portrait(character, portrait, 1, false)
var anim = animate_portrait(character, AnimationName, AnimationLength)
# makes sure positions are listed and can be accessed
func check_positions_and_holder() -> void:
if _portrait_holder_reference == null and len(get_tree().get_nodes_in_group('dialogic_portrait_holder')) == 0:
assert(false, '[Dialogic] If you want to display portraits, you need a PortraitHolder scene!')
else:
if _portrait_holder_reference == null:
_portrait_holder_reference = get_tree().get_first_node_in_group('dialogic_portrait_holder')
if _default_positions.size() == 0:
for node in get_tree().get_nodes_in_group('dialogic_portrait_position'):
_default_positions[node['position_index']] = node['position']
if current_positions.size() == 0:
current_positions = _default_positions.duplicate()