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.
167 lines
4.6 KiB
167 lines
4.6 KiB
@tool
|
|
extends Resource
|
|
class_name DialogicTimeline
|
|
|
|
## Resource that defines a list of events.
|
|
## It can store them as text and load them from text too.
|
|
|
|
var events: Array = []
|
|
var events_processed: bool = false
|
|
|
|
|
|
## Method used for printing timeline resources identifiably
|
|
func _to_string() -> String:
|
|
return "[DialogicTimeline:{file}]".format({"file":resource_path})
|
|
|
|
|
|
## Helper method
|
|
func get_event(index:int) -> Variant:
|
|
if index >= len(events):
|
|
return null
|
|
return events[index]
|
|
|
|
|
|
## Parses the lines as seperate events and insert them in an array,
|
|
## so they can be converted to DialogicEvent's when processed later
|
|
func from_text(text:String) -> void:
|
|
events = text.split('\n', true)
|
|
events_processed = false
|
|
|
|
|
|
## Stores all events in their text format and returns them as a string
|
|
func as_text() -> String:
|
|
var result: String = ""
|
|
|
|
if events_processed:
|
|
var indent := 0
|
|
for idx in range(0, len(events)):
|
|
var event: DialogicEvent = events[idx]
|
|
|
|
if event.event_name == 'End Branch':
|
|
indent -= 1
|
|
continue
|
|
|
|
if event != null:
|
|
for i in event.empty_lines_above:
|
|
result += "\t".repeat(indent)+"\n"
|
|
result += "\t".repeat(indent)+event.event_node_as_text.replace('\n', "\n"+"\t".repeat(indent)) + "\n"
|
|
if event.can_contain_events:
|
|
indent += 1
|
|
if indent < 0:
|
|
indent = 0
|
|
else:
|
|
for event in events:
|
|
result += str(event)+"\n"
|
|
|
|
result.trim_suffix('\n')
|
|
|
|
return result.strip_edges()
|
|
|
|
|
|
## Method that loads all the event resources from the strings, if it wasn't done before
|
|
func process() -> void:
|
|
if typeof(events[0]) == TYPE_STRING:
|
|
events_processed = false
|
|
|
|
# if the timeline is already processed
|
|
if events_processed:
|
|
for event in events:
|
|
event.event_node_ready = true
|
|
return
|
|
|
|
var event_cache := DialogicResourceUtil.get_event_cache()
|
|
var end_event := DialogicEndBranchEvent.new()
|
|
|
|
var prev_indent := ""
|
|
var processed_events := []
|
|
|
|
# this is needed to add an end branch event even to empty conditions/choices
|
|
var prev_was_opener := false
|
|
|
|
var lines := events
|
|
var idx := -1
|
|
var empty_lines := 0
|
|
while idx < len(lines)-1:
|
|
idx += 1
|
|
|
|
# make sure we are using the string version, in case this was already converted
|
|
var line := ""
|
|
if typeof(lines[idx]) == TYPE_STRING:
|
|
line = lines[idx]
|
|
else:
|
|
line = lines[idx].event_node_as_text
|
|
|
|
## Ignore empty lines, but record them in @empty_lines
|
|
var line_stripped: String = line.strip_edges(true, false)
|
|
if line_stripped.is_empty():
|
|
empty_lines += 1
|
|
continue
|
|
|
|
## Add an end event if the indent is smaller then previously
|
|
var indent: String = line.substr(0,len(line)-len(line_stripped))
|
|
if len(indent) < len(prev_indent):
|
|
for i in range(len(prev_indent)-len(indent)):
|
|
processed_events.append(end_event.duplicate())
|
|
## Add an end event if the indent is the same but the previous was an opener
|
|
## (so for example choice that is empty)
|
|
if prev_was_opener and len(indent) <= len(prev_indent):
|
|
processed_events.append(end_event.duplicate())
|
|
|
|
prev_indent = indent
|
|
|
|
## Now we process the event into a resource
|
|
## by checking on each event if it recognizes this string
|
|
var event_content: String = line_stripped
|
|
var event: DialogicEvent
|
|
for i in event_cache:
|
|
if i._test_event_string(event_content):
|
|
event = i.duplicate()
|
|
break
|
|
|
|
event.empty_lines_above = empty_lines
|
|
# add the following lines until the event says it's full or there is an empty line
|
|
while !event.is_string_full_event(event_content):
|
|
idx += 1
|
|
if idx == len(lines):
|
|
break
|
|
|
|
var following_line_stripped: String = lines[idx].strip_edges(true, false)
|
|
|
|
if following_line_stripped.is_empty():
|
|
break
|
|
|
|
event_content += "\n"+following_line_stripped
|
|
|
|
event._load_from_string(event_content)
|
|
event.event_node_as_text = event_content
|
|
|
|
processed_events.append(event)
|
|
prev_was_opener = event.can_contain_events
|
|
empty_lines = 0
|
|
|
|
if !prev_indent.is_empty():
|
|
for i in range(len(prev_indent)):
|
|
processed_events.append(end_event.duplicate())
|
|
|
|
events = processed_events
|
|
events_processed = true
|
|
|
|
|
|
## This method makes sure that all events in a timeline are correctly reset
|
|
func clean() -> void:
|
|
if not events_processed:
|
|
return
|
|
reference()
|
|
# This is necessary because otherwise INTERNAL GODOT ONESHOT CONNECTIONS
|
|
# are disconnected before they can disconnect themselves.
|
|
await Engine.get_main_loop().process_frame
|
|
|
|
for event:DialogicEvent in events:
|
|
for con_in in event.get_incoming_connections():
|
|
con_in.signal.disconnect(con_in.callable)
|
|
|
|
for sig in event.get_signal_list():
|
|
for con_out in event.get_signal_connection_list(sig.name):
|
|
con_out.signal.disconnect(con_out.callable)
|
|
unreference()
|