Skip to content

Commit

Permalink
Create basepoint
Browse files Browse the repository at this point in the history
  • Loading branch information
yochrisbolton committed Apr 1, 2020
0 parents commit 9612d9c
Show file tree
Hide file tree
Showing 8 changed files with 466 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .import/icon.png-487276ed1e3a0c39cad0279d744ee560.md5
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
source_md5="47313fa4c47a9963fddd764e1ec6e4a8"
dest_md5="2ded9e7f9060e2b530aab678b135fc5b"

Binary file not shown.
101 changes: 101 additions & 0 deletions Scenes/Window.tscn
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
[gd_scene load_steps=4 format=2]

[sub_resource type="StyleBoxFlat" id=1]
bg_color = Color( 0.0823529, 0.0823529, 0.0823529, 1 )

[sub_resource type="GDScript" id=3]
script/source = "extends Control


# Declare member variables here. Examples:
# var a = 2
# var b = \"text\"


# Called when the node enters the scene tree for the first time.
func _ready():
pass # Replace with function body.


# Called every frame. 'delta' is the elapsed time since the previous frame.
#func _process(delta):
# pass
"

[sub_resource type="StyleBoxFlat" id=2]
bg_color = Color( 0.117647, 0.12549, 0.141176, 1 )

[node name="Window" type="Control"]
anchor_right = 1.0
anchor_bottom = 1.0
__meta__ = {
"_edit_lock_": true,
"_edit_use_anchors_": false
}

[node name="Frame" type="Panel" parent="."]
anchor_right = 1.0
anchor_bottom = 1.0
custom_styles/panel = SubResource( 1 )
__meta__ = {
"_edit_lock_": true
}

[node name="Container" type="Control" parent="Frame"]
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
margin_left = -288.0
margin_top = -116.0
margin_right = 288.0
margin_bottom = 252.0
script = SubResource( 3 )
__meta__ = {
"_edit_use_anchors_": false
}

[node name="Dialogue" type="Panel" parent="Frame/Container"]
anchor_right = 1.0
anchor_bottom = 1.0
margin_bottom = -188.0
custom_styles/panel = SubResource( 2 )
__meta__ = {
"_edit_use_anchors_": false
}

[node name="Name" type="Label" parent="Frame/Container/Dialogue"]
margin_left = 16.0
margin_top = 16.0
margin_right = 56.0
margin_bottom = 30.0
text = "Name"
__meta__ = {
"_edit_use_anchors_": false
}

[node name="Text" type="RichTextLabel" parent="Frame/Container/Dialogue"]
margin_left = 16.0
margin_top = 48.0
margin_right = 560.0
margin_bottom = 168.0
text = "Test text, look how amazing this all is!!"
__meta__ = {
"_edit_use_anchors_": false
}

[node name="Options" type="Control" parent="Frame/Container"]
anchor_right = 1.0
anchor_bottom = 1.0
margin_top = 182.0
__meta__ = {
"_edit_use_anchors_": false
}

[node name="Template" type="Button" parent="Frame/Container/Options"]
anchor_top = 0.5
anchor_right = 1.0
anchor_bottom = 0.5
margin_top = -83.0
margin_bottom = -43.0
text = "Very Real Text"
289 changes: 289 additions & 0 deletions Scripts/WhiskersParser.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,289 @@
class_name WhiskersParser

var data : Dictionary
var current_block : Dictionary
var format_dictionary : Dictionary = {} setget set_format_dictionary
var default_base_instance : Object # Default base instance defined at _init method
var base_instance : Object # Object used as a base instance when running expressions

func _init(base_instance : Object = null):
default_base_instance = base_instance

if not base_instance:
print("[WARN]: no base_instance for calling expressions.")

static func open_whiskers(file_path : String) -> Dictionary:
var file = File.new()

var error = file.open(file_path, File.READ)
if error:
print("[ERROR]: couldn't open file at %s. Error number %s." % [file_path, error])
return {}

var dialogue_data = parse_json(file.get_as_text())
file.close()

if not dialogue_data is Dictionary:
print("[ERROR]: failed to parse whiskers file. Is it a valid exported whiskers file?")
return {}

return dialogue_data

static func parse_whiskers(data : Dictionary) -> Dictionary:
return data

func start_dialogue(dialogue_data : Dictionary, custom_base_instance : Object = null) -> Dictionary:
if not dialogue_data.has("Start"):
print("[ERROR]: not a valid whiskers data, it has not the key Start.")
return {}

base_instance = custom_base_instance if custom_base_instance else default_base_instance
data = dialogue_data
current_block = generate_block(data.Start.connects_to.front())

return current_block

func end_dialogue() -> void:
data = {}
current_block = {}
base_instance = default_base_instance

func next(selected_option_key : String = "") -> Dictionary:
if not data:
print("[WARN]: trying to call next() on a finalized dialogue.")
return {}

if current_block.is_final:
# It is a final block, but it could be connected to more than an END node, we have to process them
process_block(current_block)
end_dialogue()
return {}

var next_block = {}

handle_expressions(current_block.expressions)

# DEALING WITH OPTIONS
if selected_option_key:
# Generate a block containing all the nodes that this options is connected with
var option_block = generate_block(selected_option_key)
if option_block.empty(): return {}

next_block = process_block(option_block)

elif not current_block.options.empty():
print("[WARN]: no option was passed as argument, but there was options available. This could cause an infinite loop. Use wisely.")

else:
next_block = process_block(current_block)

current_block = next_block

return current_block

func process_block(block : Dictionary) -> Dictionary:
var next_block = {}

handle_expressions(block.expressions)

if not block.dialogue.empty():
next_block = generate_block(block.dialogue.key)
elif not block.jump.empty():
next_block = handle_jump(block.jump)
elif not block.condition.empty():
next_block = handle_condition(block.condition)

return next_block

func handle_expressions(expressions_array : Array) -> Array:
if expressions_array.empty(): return []

var results = []
var expression = Expression.new()

for dic in expressions_array:
results.append(execute_expression(dic.logic))

return results

func handle_condition(condition : Dictionary) -> Dictionary:
var result = execute_expression(condition.logic)
var next_block = {}

if not result is bool:
print("[ERROR]: the expression used as input for a condition node should return a boolean, but it is returning %s instead." % result)
return {}

if result:
if not "End" in condition.goes_to_key.if_true: # If a condition node goest to an end node, then we have to end the dialogue
next_block = generate_block(condition.goes_to_key.if_true)
else:
if not "End" in condition.goes_to_key.if_false:
next_block = generate_block(condition.goes_to_key.if_false)

return next_block

func handle_jump(jump) -> Dictionary:
# Get the matching node to wich we are going
var jumped_to = generate_block(jump.goes_to_key)
var next_block = {}

# If this node has expressions that it is connected to, than we want to execute them
handle_expressions(jumped_to.expressions)

if not jumped_to.dialogue.empty():
next_block = generate_block(jumped_to.dialogue.key)
elif not jumped_to.jump.empty():
next_block = handle_jump(jumped_to.jump)
elif not jumped_to.condition.empty():
next_block = handle_condition(jumped_to.condition)
elif not jumped_to.options.empty():
next_block = jumped_to

return next_block

func execute_expression(expression_text : String):
var expression = Expression.new()
var result = null

var error = expression.parse(expression_text)
if error:
print("[ERROR]: unable to parse expression %s. Error: %s." % [expression_text, error])
else:
result = expression.execute([], base_instance, true)
if expression.has_execute_failed():
print("[ERROR]: unable to execute expression %s." % expression_text)

return result

# A block is a Dictionary containing a node and every node it is connected to, by type and it's informations.
func generate_block(node_key : String) -> Dictionary:
if not data.has(node_key):
print("[ERROR]: trying to create block from inexisting node %s. Aborting.", node_key)
return {}

# Block template
var block = {
key = node_key,
options = [], # key, text
expressions = [], # key, logic
dialogue = {}, # key, text
condition = {}, # key, logic, goes_to_key["true"], goes_to_key["false"]
jump = {}, # key, id, goes_to_key
is_final = false
}

if "Dialogue" in node_key:
block.text = data[node_key].text.format(format_dictionary)

if "Jump" in node_key:
for key in data:
if "Jump" in key and data[node_key].text == data[key].text and node_key != key:
block = generate_block(data[key].connects_to[0])
break

if "Condition" in node_key: # this isn't very DRY
block.condition = process_condition(node_key)
block = process_block(block)

# For each key of the connected nodes we put it on the block
for connected_node_key in data[node_key].connects_to:
if "Dialogue" in connected_node_key:
if not block.dialogue.empty(): # It doesn't make sense to connect two dialogue nodes
print("[WARN]: more than one Dialogue node connected. Defaulting to the first, key: %s, text: %s." % [block.dialogue.key, block.text])
continue

var dialogue = {
key = connected_node_key,
}
block.dialogue = dialogue

elif "Option" in connected_node_key:
var option = {
key = connected_node_key,
text = data[connected_node_key].text,
}
block.options.append(option)

elif "Expression" in connected_node_key:
var expression = {
key = connected_node_key,
logic = data[connected_node_key].logic
}
block.expressions.append(expression)

elif "Condition" in connected_node_key:
if not block.condition.empty(): # It also doesn't make sense to connect two Condition nodes
print("[WARN]: more than one Condition node connected. Defaulting to the first, key: %s." % block.condition.key)
continue

block.condition = process_condition(connected_node_key)

var parse_condition = handle_condition(block.condition)

if 'Option' in parse_condition.key:
var option = {
key = parse_condition.key,
text = data[parse_condition.key].text,
}
block.options.append(option)

elif "Jump" in connected_node_key:
if not block.jump.empty():
print("[WARN]: more than one Jump node connected. Defaulting to the first, key: %s, id: %d." % [connected_node_key, block.jump.id])
continue

# Just like with the Expression node a linear search is needed to find the matching jump node.
var match_key : String
for key in data:
if "Jump" in key and data[connected_node_key].text == data[key].text and connected_node_key != key:
match_key = key
break

if not match_key:
print("[ERROR]: no other node with the id %s was found. Aborting." % data[connected_node_key].text)
return {}

var jump = {
key = connected_node_key,
id = data[connected_node_key].text,
goes_to_key = match_key
}
block.jump = jump

var jump_options = handle_jump(block.jump)
if not jump_options.options.empty():
for option in jump_options.options:
block.options.append(option)

elif "End" in connected_node_key and not "Jump" in node_key:
block.is_final = true

current_block = block
return current_block

func process_condition(passed_key : String) -> Dictionary:
# Sadly the only way to find the Expression node that serves as input is to make a linear search
var input_logic : String
for key in data:
if "Expression" in key and data[key].connects_to.front() == passed_key:
input_logic = data[key].logic
break

if not input_logic:
print("[ERROR]: no input for the condition node %s was found." % passed_key)
return {}

var condition = {
key = passed_key,
logic = input_logic,
goes_to_key = {
if_true = data[passed_key].conditions["true"],
if_false = data[passed_key].conditions["false"]
}
}

return condition

func set_format_dictionary(value : Dictionary) -> void:
format_dictionary = value
Loading

0 comments on commit 9612d9c

Please sign in to comment.