190 lines
5.2 KiB
GDScript
190 lines
5.2 KiB
GDScript
extends RefCounted
|
|
class_name MessageCodeParser
|
|
|
|
const tag_if_start = "{{if "
|
|
const tag_if_start_len = 5
|
|
|
|
const tag_if_end = "}}"
|
|
const tag_if_end_len = 2
|
|
|
|
const cond_sep = ": "
|
|
const cond_sep_len = 2
|
|
|
|
const operators = [" >= ", " <= ", " != ", " > ", " < ", " = "]
|
|
|
|
|
|
|
|
func process(source_text: String, variables: Dictionary) -> Array:
|
|
var result = ""
|
|
|
|
var message_list = parse(source_text, variables)
|
|
for msg in message_list:
|
|
if msg is String:
|
|
result += msg
|
|
|
|
elif (msg is Dictionary) and (msg["condition"] in variables):
|
|
var var_value = variables[msg["condition"]]
|
|
var message_approved = false
|
|
match msg["operator"]:
|
|
"=":
|
|
message_approved = (var_value == msg["value"])
|
|
"!=":
|
|
message_approved = (var_value != msg["value"])
|
|
">":
|
|
message_approved = (var_value > msg["value"])
|
|
">=":
|
|
message_approved = (var_value >= msg["value"])
|
|
"<":
|
|
message_approved = (var_value < msg["value"])
|
|
"<=":
|
|
message_approved = (var_value <= msg["value"])
|
|
_:
|
|
message_approved = false
|
|
if message_approved:
|
|
result += msg["text"]
|
|
|
|
# Find pause points
|
|
var text_segments = result.split("<pause>")
|
|
if (not text_segments is PackedStringArray) or (text_segments.size() <= 1):
|
|
return [result, [1.0]] # 1.0 means one text with 100% of characters
|
|
|
|
|
|
# Calculate total length of text
|
|
# This text is potentially BB code, and therefore must be properly
|
|
# parsed since text progression is based only on visible characters
|
|
# For this we need a RichTextLabel to parse the BBCode, but it doesn't
|
|
# have to be added anyhere
|
|
var bbcode_parser = RichTextLabel.new()
|
|
bbcode_parser.bbcode_enabled = true
|
|
bbcode_parser.text = ""
|
|
var charcount_per_segment = []
|
|
|
|
result = ""
|
|
for item in text_segments:
|
|
result += item
|
|
bbcode_parser.text += item
|
|
var characters_up_to_here = bbcode_parser.text.length() #get_total_character_count()
|
|
|
|
charcount_per_segment.append(characters_up_to_here)
|
|
var total_characters = float(bbcode_parser.text.length())
|
|
|
|
bbcode_parser.queue_free()
|
|
|
|
var percentages = []
|
|
for charcount in charcount_per_segment:
|
|
percentages.append(float(charcount) / total_characters)
|
|
|
|
return [result, percentages]
|
|
|
|
|
|
func parse(source_text: String, variables: Dictionary) -> Array:
|
|
# variables is a Dictionary mapping "variable name" to a value (int, float or String)
|
|
# result is an Array of text segments, where each segment is in the format
|
|
# segment = "String" ---> means text is not conditional
|
|
# segment = {
|
|
# "condition": "variable name",
|
|
# "operator": "=" | "!=" | ">" | ">=" | "<" | "<="
|
|
# "value": <value> or "variable name",
|
|
# "text": "text"
|
|
# }
|
|
var result = []
|
|
|
|
var s_tmp = source_text
|
|
var tag_pos = s_tmp.find("{{if")
|
|
while tag_pos > -1:
|
|
var text_before = s_tmp.left(tag_pos)
|
|
var text_after = s_tmp.substr(tag_pos + tag_if_start_len)
|
|
|
|
var tag_endpos = text_after.find(tag_if_end)
|
|
|
|
# if we don't have a closing tag, this is malformatted syntax and we
|
|
# just assume it's not meant to be parsed
|
|
if tag_endpos == -1:
|
|
break
|
|
|
|
var text_cond = text_after.left(tag_endpos)
|
|
text_after = text_after.substr(tag_endpos + tag_if_end_len)
|
|
|
|
# Now we have:
|
|
# text_before -> text before the start tag
|
|
# text_cond -> everything between both tags (not including)
|
|
# text_after -> text after the end tag
|
|
# both tags are not included anywhere
|
|
|
|
|
|
result.append(_replace_variables(text_before, variables))
|
|
result.append(_parse_condition(text_cond, variables))
|
|
|
|
s_tmp = text_after
|
|
tag_pos = s_tmp.find("{{if")
|
|
|
|
result.append(_replace_variables(s_tmp, variables))
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
func _parse_condition(text: String, variables: Dictionary):
|
|
# Returns a segment to be appended into the array in parse()
|
|
var sep_pos = text.find(cond_sep)
|
|
if sep_pos == -1:
|
|
return text
|
|
|
|
var text_before = text.left(sep_pos)
|
|
var text_after = text.substr(sep_pos + cond_sep_len)
|
|
|
|
|
|
var cond_terms = text_before
|
|
for op in operators:
|
|
if op in text_before:
|
|
# Split text (e.g. "variable >= 5")
|
|
cond_terms = text_before.split(op, false, 2)
|
|
if cond_terms.size() < 2:
|
|
cond_terms = text_before
|
|
break
|
|
|
|
var value = cond_terms[1]
|
|
if value.is_valid_int():
|
|
value = value.to_int()
|
|
elif value.is_valid_float():
|
|
value = value.to_float()
|
|
|
|
return {
|
|
"condition": cond_terms[0],
|
|
"operator": op.strip_edges(),
|
|
"value": value,
|
|
"text": _replace_variables(text_after, variables),
|
|
}
|
|
|
|
# If no operator was found, cond_terms remains String
|
|
|
|
|
|
# Boolean check (e.g. "{{if variable: Text here!}}
|
|
if cond_terms is String:
|
|
return {
|
|
"condition": cond_terms,
|
|
# Boolean check is implemented as "value != 0"
|
|
"operator": "!=",
|
|
"value": 0,
|
|
"text": _replace_variables(text_after, variables),
|
|
}
|
|
|
|
else:
|
|
return ""
|
|
|
|
func _replace_variables(text, variables):
|
|
var s_tmp = text
|
|
for var_name in variables:
|
|
var var_tag = "<<%s>>" % var_name
|
|
if var_tag in s_tmp:
|
|
var var_value = variables[var_name]
|
|
# Converts 15.0 into 15 for elegant printing
|
|
if (var_value is float) and (var_value == floor(var_value)):
|
|
var_value = str(int(var_value))
|
|
else:
|
|
var_value = str(var_value)
|
|
|
|
s_tmp = s_tmp.replace(var_tag, var_value)
|
|
return s_tmp
|