3 class_name DialogicTimeline
5 ## Resource that defines a list of events.
6 ## It can store them as text and load them from text too.
9 var events_processed := false
12 ## Method used for printing timeline resources identifiably
13 func _to_string() -> String:
14 return "[DialogicTimeline:{file}]".format({"file":resource_path})
18 func get_event(index:int) -> Variant:
19 if index >= len(events):
24 ## Parses the lines as seperate events and insert them in an array,
25 ## so they can be converted to DialogicEvent's when processed later
26 func from_text(text:String) -> void:
27 events = text.split('\n', true)
28 events_processed = false
31 ## Stores all events in their text format and returns them as a string
32 func as_text() -> String:
37 for idx in range(0, len(events)):
38 var event: DialogicEvent = events[idx]
40 if event.event_name == 'End Branch':
45 for i in event.empty_lines_above:
46 result += "\t".repeat(indent)+"\n"
47 result += "\t".repeat(indent)+event.event_node_as_text.replace('\n', "\n"+"\t".repeat(indent)) + "\n"
48 if event.can_contain_events:
54 result += str(event)+"\n"
56 result.trim_suffix('\n')
58 return result.strip_edges()
61 ## Method that loads all the event resources from the strings, if it wasn't done before
62 func process() -> void:
63 if typeof(events[0]) == TYPE_STRING:
64 events_processed = false
66 # if the timeline is already processed
69 event.event_node_ready = true
72 var event_cache := DialogicResourceUtil.get_event_cache()
73 var end_event := DialogicEndBranchEvent.new()
76 var processed_events := []
78 # this is needed to add an end branch event even to empty conditions/choices
79 var prev_was_opener := false
84 while idx < len(lines)-1:
87 # make sure we are using the string version, in case this was already converted
89 if typeof(lines[idx]) == TYPE_STRING:
92 line = lines[idx].event_node_as_text
94 ## Ignore empty lines, but record them in @empty_lines
95 var line_stripped: String = line.strip_edges(true, false)
96 if line_stripped.is_empty():
100 ## Add an end event if the indent is smaller then previously
101 var indent: String = line.substr(0,len(line)-len(line_stripped))
102 if len(indent) < len(prev_indent):
103 for i in range(len(prev_indent)-len(indent)):
104 processed_events.append(end_event.duplicate())
105 ## Add an end event if the indent is the same but the previous was an opener
106 ## (so for example choice that is empty)
107 if prev_was_opener and len(indent) <= len(prev_indent):
108 processed_events.append(end_event.duplicate())
112 ## Now we process the event into a resource
113 ## by checking on each event if it recognizes this string
114 var event_content: String = line_stripped
115 var event: DialogicEvent
116 for i in event_cache:
117 if i._test_event_string(event_content):
118 event = i.duplicate()
121 event.empty_lines_above = empty_lines
122 # add the following lines until the event says it's full or there is an empty line
123 while !event.is_string_full_event(event_content):
125 if idx == len(lines):
128 var following_line_stripped: String = lines[idx].strip_edges(true, false)
130 if following_line_stripped.is_empty():
133 event_content += "\n"+following_line_stripped
135 event._load_from_string(event_content)
136 event.event_node_as_text = event_content
138 processed_events.append(event)
139 prev_was_opener = event.can_contain_events
142 if !prev_indent.is_empty():
143 for i in range(len(prev_indent)):
144 processed_events.append(end_event.duplicate())
146 events = processed_events
147 events_processed = true
150 ## This method makes sure that all events in a timeline are correctly reset
151 func clean() -> void:
152 if not events_processed:
155 # This is necessary because otherwise INTERNAL GODOT ONESHOT CONNECTIONS
156 # are disconnected before they can disconnect themselves.
157 await Engine.get_main_loop().process_frame
159 for event:DialogicEvent in events:
160 for con_in in event.get_incoming_connections():
161 con_in.signal.disconnect(con_in.callable)
163 for sig in event.get_signal_list():
164 for con_out in event.get_signal_connection_list(sig.name):
165 con_out.signal.disconnect(con_out.callable)