]> Untitled Git - wolf-seeking-sheep.git/blob - addons/dialogic/Modules/Character/node_portrait_container.gd
Initial Godot project with Dialogic 2.0-Alpha-17
[wolf-seeking-sheep.git] / addons / dialogic / Modules / Character / node_portrait_container.gd
1 @tool
2 class_name DialogicNode_PortraitContainer
3 extends Control
4
5 ## Node that defines a position for dialogic portraits and how to display portraits at that position.
6
7 enum PositionModes {
8         POSITION, ## This container can be joined/moved to with the Character Event
9         SPEAKER,  ## This container is joined/left automatically based on the speaker.
10         }
11
12 @export var mode := PositionModes.POSITION
13
14 @export_subgroup('Mode: Position')
15 ## The position this node corresponds to.
16 @export var container_ids: PackedStringArray = ["1"]
17
18
19 @export_subgroup('Mode: Speaker')
20 ## Can be used to use a different portrait.
21 ## E.g. "Faces/" would mean instead of "happy" it will use portrait "Faces/happy"
22 @export var portrait_prefix := ''
23
24 @export_subgroup('Portrait Placement')
25 enum SizeModes {
26         KEEP, ## The height and width of the container have no effect, only the origin.
27         FIT_STRETCH, ## The portrait will be fitted into the container, ignoring it's aspect ratio and the character/portrait scale.
28         FIT_IGNORE_SCALE, ## The portrait will be fitted into the container, ignoring the character/portrait scale, but preserving the aspect ratio.
29         FIT_SCALE_HEIGHT ## Recommended. The portrait will be scaled to fit the container height. A character/portrait scale of 100% means 100% container height. Aspect ratio will be preserved.
30         }
31 ## Defines how to affect the scale of the portrait
32 @export var size_mode: SizeModes = SizeModes.FIT_SCALE_HEIGHT :
33         set(mode):
34                 size_mode = mode
35                 _update_debug_portrait_transform()
36
37 ## If true, portraits will be mirrored in this position.
38 @export var mirrored := false :
39         set(mirror):
40                 mirrored = mirror
41                 _update_debug_portrait_scene()
42
43
44 @export_group('Origin', 'origin')
45 enum OriginAnchors {TOP_LEFT, TOP_CENTER, TOP_RIGHT, LEFT_MIDDLE, CENTER, RIGHT_MIDDLE, BOTTOM_LEFT, BOTTOM_CENTER, BOTTOM_RIGHT}
46 ## The portrait will be placed relative to this point in the container.
47 @export var origin_anchor: OriginAnchors = OriginAnchors.BOTTOM_CENTER :
48         set(anchor):
49                 origin_anchor = anchor
50                 _update_debug_origin()
51
52 ## An offset to apply to the origin. Rarely useful.
53 @export var origin_offset := Vector2() :
54         set(offset):
55                 origin_offset = offset
56                 _update_debug_origin()
57
58 enum PivotModes {AT_ORIGIN, PERCENTAGE, PIXELS}
59 ## Usually you want to rotate or scale around the portrait origin.
60 ## For the moments where that is not the case, set the mode to PERCENTAGE or PIXELS and use [member pivot_value].
61 @export var pivot_mode: PivotModes = PivotModes.AT_ORIGIN
62 ## Only has an effect when [member pivot_mode] is not AT_ORIGIN. Meaning depends on whether [member pivot_mode] is PERCENTAGE or PIXELS.
63 @export var pivot_value := Vector2()
64
65 @export_group('Debug', 'debug')
66 ## A character that will be displayed in the editor, useful for getting the right size.
67 @export var debug_character: DialogicCharacter = null:
68         set(character):
69                 debug_character = character
70                 _update_debug_portrait_scene()
71 @export var debug_character_portrait := "":
72         set(portrait):
73                 debug_character_portrait = portrait
74                 _update_debug_portrait_scene()
75
76 var debug_character_holder_node: Node2D = null
77 var debug_character_scene_node: Node = null
78 var debug_origin: Sprite2D = null
79 var default_portrait_scene: String = DialogicUtil.get_module_path('Character').path_join("default_portrait.tscn")
80 # Used if no debug character is specified
81 var default_debug_character := load(DialogicUtil.get_module_path('Character').path_join("preview_character.tres"))
82
83 var ignore_resize := false
84
85
86 func _ready() -> void:
87         match mode:
88                 PositionModes.POSITION:
89                         add_to_group('dialogic_portrait_con_position')
90                 PositionModes.SPEAKER:
91                         add_to_group('dialogic_portrait_con_speaker')
92
93         if Engine.is_editor_hint():
94                 resized.connect(_update_debug_origin)
95
96                 if !ProjectSettings.get_setting('dialogic/portraits/default_portrait', '').is_empty():
97                         default_portrait_scene = ProjectSettings.get_setting('dialogic/portraits/default_portrait', '')
98
99                 debug_origin = Sprite2D.new()
100                 add_child(debug_origin)
101                 debug_origin.texture = load("res://addons/dialogic/Editor/Images/Dropdown/default.svg")
102
103                 _update_debug_origin()
104                 _update_debug_portrait_scene()
105         else:
106                 resized.connect(update_portrait_transforms)
107
108
109 ################################################################################
110 ##                                              MAIN METHODS
111 ################################################################################
112
113 func update_portrait_transforms() -> void:
114         if ignore_resize:
115                 return
116
117         match pivot_mode:
118                 PivotModes.AT_ORIGIN:
119                         pivot_offset = _get_origin_position()
120                 PivotModes.PERCENTAGE:
121                         pivot_offset = size*pivot_value
122                 PivotModes.PIXELS:
123                         pivot_offset = pivot_value
124
125         for child in get_children():
126                 DialogicUtil.autoload().Portraits._update_character_transform(child)
127
128
129 ## Returns a Rect2 with the position as the position and the scale as the size.
130 func get_local_portrait_transform(portrait_rect:Rect2, character_scale:=1.0) -> Rect2:
131         var transform := Rect2()
132         transform.position = _get_origin_position()
133
134         # Mode that ignores the containers size
135         if size_mode == SizeModes.KEEP:
136                 transform.size = Vector2(1,1) * character_scale
137
138         # Mode that makes sure neither height nor width go out of container
139         elif size_mode == SizeModes.FIT_IGNORE_SCALE:
140                 if size.x/size.y < portrait_rect.size.x/portrait_rect.size.y:
141                         transform.size = Vector2(1,1) * size.x/portrait_rect.size.x
142                 else:
143                         transform.size = Vector2(1,1) * size.y/portrait_rect.size.y
144
145         # Mode that stretches the portrait to fill the whole container
146         elif size_mode == SizeModes.FIT_STRETCH:
147                 transform.size = size/portrait_rect.size
148
149         # Mode that size the character so 100% size fills the height
150         elif size_mode == SizeModes.FIT_SCALE_HEIGHT:
151                 transform.size = Vector2(1,1) * size.y / portrait_rect.size.y*character_scale
152
153         return transform
154
155
156 ## Returns the current origin position
157 func _get_origin_position(rect_size = null) -> Vector2:
158         if rect_size == null:
159                 rect_size = size
160         return rect_size * Vector2(origin_anchor%3 / 2.0, floor(origin_anchor/3.0) / 2.0) + origin_offset
161
162
163 func is_container(id:Variant) -> bool:
164         return str(id) in container_ids
165
166 #region DEBUG METHODS
167 ################################################################################
168 ### USE THIS TO DEBUG THE POSITIONS
169 #func _draw():
170         #draw_rect(Rect2(Vector2(), size), Color(1, 0.3098039329052, 1), false, 2)
171         #draw_string(get_theme_default_font(),get_theme_default_font().get_string_size(container_ids[0], HORIZONTAL_ALIGNMENT_LEFT, 1, get_theme_default_font_size()) , container_ids[0], HORIZONTAL_ALIGNMENT_CENTER)
172 #
173 #func _process(delta:float) -> void:
174         #queue_redraw()
175
176
177 ## Loads the debug_character with the debug_character_portrait
178 ## Creates a holder node and applies mirror
179 func _update_debug_portrait_scene() -> void:
180         if !Engine.is_editor_hint():
181                 return
182         if is_instance_valid(debug_character_holder_node):
183                 for child in get_children():
184                         if child != debug_origin:
185                                 child.free()
186
187         # Get character
188         var character := _get_debug_character()
189         if not character is DialogicCharacter or character.portraits.is_empty():
190                 return
191
192         # Determine portrait
193         var debug_portrait := debug_character_portrait
194         if debug_portrait.is_empty():
195                 debug_portrait = character.default_portrait
196         if mode == PositionModes.SPEAKER and !portrait_prefix.is_empty():
197                 if portrait_prefix+debug_portrait in character.portraits:
198                         debug_portrait = portrait_prefix+debug_portrait
199         if not debug_portrait in character.portraits:
200                 debug_portrait = character.default_portrait
201
202         var portrait_info: Dictionary = character.get_portrait_info(debug_portrait)
203
204         # Determine scene
205         var portrait_scene_path: String = portrait_info.get('scene', default_portrait_scene)
206         if portrait_scene_path.is_empty():
207                 portrait_scene_path = default_portrait_scene
208
209         debug_character_scene_node = load(portrait_scene_path).instantiate()
210
211         if !is_instance_valid(debug_character_scene_node):
212                 return
213
214         # Load portrait
215         DialogicUtil.apply_scene_export_overrides(debug_character_scene_node, character.portraits[debug_portrait].get('export_overrides', {}))
216         debug_character_scene_node._update_portrait(character, debug_portrait)
217
218         # Add character node
219         if !is_instance_valid(debug_character_holder_node):
220                 debug_character_holder_node = Node2D.new()
221                 add_child(debug_character_holder_node)
222
223         # Add portrait node
224         debug_character_holder_node.add_child(debug_character_scene_node)
225         move_child(debug_character_holder_node, 0)
226         debug_character_scene_node._set_mirror(character.mirror != mirrored != portrait_info.get('mirror', false))
227
228         _update_debug_portrait_transform()
229
230
231 ## Set's the size and position of the holder and scene node
232 ## according to the size_mode
233 func _update_debug_portrait_transform() -> void:
234         if !Engine.is_editor_hint() or !is_instance_valid(debug_character_scene_node) or !is_instance_valid(debug_origin):
235                 return
236         var character := _get_debug_character()
237         var portrait_info := character.get_portrait_info(debug_character_portrait)
238         var transform := get_local_portrait_transform(debug_character_scene_node._get_covered_rect(), character.scale*portrait_info.get('scale', 1))
239         debug_character_holder_node.position = transform.position
240         debug_character_scene_node.position = portrait_info.get('offset', Vector2())+character.offset
241
242         debug_character_holder_node.scale = transform.size
243
244
245 ## Updates the debug origins position. Also calls _update_debug_portrait_transform()
246 func _update_debug_origin() -> void:
247         if !Engine.is_editor_hint() or !is_instance_valid(debug_origin):
248                 return
249         debug_origin.position = _get_origin_position()
250         _update_debug_portrait_transform()
251
252
253
254 ## Returns the debug character or the default debug character
255 func _get_debug_character() -> DialogicCharacter:
256         return debug_character if debug_character != null else default_debug_character
257
258 #endregion