Skip to content

Commit

Permalink
GDScript: Fix Inspector "Add Input" button, improve tweening, add pri…
Browse files Browse the repository at this point in the history
…nt in chat, fix entry signals, add export vars (#184)

* GDScript: Improve tweening and add print in chat

* GDScript: Fix Inspector "Add Input" button

* GDScript and visual scripting: Fix custom entry creation dialog

* GDScript: Sync exposed @export vars with SpaceObject space vars

* Add an inspector for script object variables
aaronfranke authored May 30, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent e30e030 commit 9273b4c
Showing 23 changed files with 382 additions and 62 deletions.
Original file line number Diff line number Diff line change
@@ -6,6 +6,10 @@ signal inspector_category_visibility_changed(new_visibility: bool)
@export var expand_speed: float = 10.0
@export var properties: Node

## The text to show as a tooltip when hovering.
## Use this instead of Godot's tooltip_text property.
@export var hover_tooltip_text: String = ""

var _is_category_visible: bool = false
var _properties_child: Control
var _plus_texture: TextureRect
@@ -55,3 +59,17 @@ func set_category_visible(new_is_visible):

func _on_toggle_button_pressed():
set_category_visible(not _is_category_visible)


func _on_hoverable_button_mouse_entered() -> void:
if hover_tooltip_text == "":
return
GameUI.set_hover_tooltip_text(hover_tooltip_text)


func _on_hoverable_button_mouse_exited() -> void:
GameUI.hide_hover_tooltip_text()


func _on_hoverable_button_pressed() -> void:
GameUI.hide_hover_tooltip_text()
27 changes: 27 additions & 0 deletions mirror-godot-app/creator/selection/inspector/inspector.gd
Original file line number Diff line number Diff line change
@@ -15,6 +15,7 @@ const _ENVIRONMENT_CATEGORY = preload("res://creator/selection/inspector/categor
const _LIGHT_CATEGORY = preload("res://creator/selection/inspector/categories/inspector_light.tscn")
const _PHYSICS_CATEGORY = preload("res://creator/selection/inspector/categories/inspector_physics.tscn")
const _VISIBILITY_CATEGORY = preload("res://creator/selection/inspector/categories/inspector_visibility.tscn")
const _SCRIPT_OBJECT_VARS_CATEGORY = preload("res://creator/selection/inspector/script/inspector_script_object_vars.tscn")
const _SCRIPT_INSTANCE_CATEGORY = preload("res://creator/selection/inspector/script/inspector_script_instance.tscn")
const _MODEL_NODES_CATEGORY = preload("res://creator/selection/inspector/nodes/inspector_model_nodes.tscn")
const _EXTRA_NODE_CATEGORY = preload("res://creator/selection/inspector/nodes/inspector_extra_node.tscn")
@@ -43,6 +44,7 @@ var _deletion_target_category: InspectorCategoryBase

@onready var _categories: VBoxContainer = _tab_cont.get_node(^"Properties/MarginContainer/Categories")
@onready var _script_main_vbox: VBoxContainer = _tab_cont.get_node(^"Scripting/MarginContainer/VBoxContainer")
@onready var _script_obj_vars: Control = _script_main_vbox.get_node(^"ScriptObjectVars")
@onready var _script_instances: VBoxContainer = _script_main_vbox.get_node(^"ScriptInstances")
@onready var _script_add_button: Button = _script_main_vbox.get_node(^"AddScriptButton")
@onready var _model_nodes_main_vbox: VBoxContainer = _tab_cont.get_node(^"Nodes/MarginContainer/VBoxContainer")
@@ -189,6 +191,7 @@ func inspect_nodes(new_nodes: Array[Node], force_rebuild: bool = false) -> void:
# The first step is to delete old categories if they exist.
_remove_old_category_children(_categories)
_remove_old_category_children(_script_instances)
_remove_old_category_children(_script_obj_vars)
_remove_old_category_children(_model_nodes)
# Update a few misc things.
_button_sound.refresh()
@@ -278,6 +281,7 @@ func _setup_new_categories(target_nodes: Array[Node]) -> void:
prim_model_cat.request_convert_to_local.connect(_on_request_convert_prim_model_to_local.bind(target_node))
target_node.scripts_changed.connect(_on_scripts_changed)
_setup_script_instances(target_node)
_setup_script_obj_vars(target_node)
_script_main_vbox.show()
_tab_cont.tabs_visible = true
_setup_extra_model_nodes(target_node)
@@ -287,6 +291,7 @@ func _setup_new_categories(target_nodes: Array[Node]) -> void:
_tab_cont.current_tab = 1
target_node.scripts_changed.connect(_on_scripts_changed)
_setup_script_instances(target_node)
_setup_script_obj_vars(target_node)
_script_main_vbox.show()
else:
_tab_cont.current_tab = 0
@@ -390,6 +395,28 @@ func _setup_script_instances(space_object_or_global_scripts: Node) -> void:
_setup_script_instance(script_instance)


func _setup_script_obj_vars(space_object_or_global_scripts: Node) -> void:
# Set up script object variables inspector, if it has any.
var object_variables: Dictionary
if space_object_or_global_scripts.has_meta(&"MirrorScriptObjectVariables"):
object_variables = space_object_or_global_scripts.get_meta(&"MirrorScriptObjectVariables")
if object_variables.is_empty():
if not _is_any_script_instance_gdscript(space_object_or_global_scripts):
return
var obj_var_cat = _SCRIPT_OBJECT_VARS_CATEGORY.instantiate()
obj_var_cat.setup(space_object_or_global_scripts, Util.can_local_user_edit_scripts())
_script_obj_vars.add_child(obj_var_cat)
obj_var_cat.setup_object_vars(object_variables)


func _is_any_script_instance_gdscript(space_object_or_global_scripts: Node) -> bool:
var script_instances: Array[ScriptInstance] = space_object_or_global_scripts.get_script_instances()
for script_inst in script_instances:
if script_inst is GDScriptInstance:
return true
return false


func _setup_script_instance(script_instance: ScriptInstance) -> void:
var cat: InspectorCategoryBase = _setup_category(script_instance.target_node, _SCRIPT_INSTANCE_CATEGORY, "", _script_instances)
cat.setup(script_instance)
5 changes: 5 additions & 0 deletions mirror-godot-app/creator/selection/inspector/inspector.tscn
Original file line number Diff line number Diff line change
@@ -92,6 +92,11 @@ metadata/_edit_layout_mode = 1
[node name="VBoxContainer" type="VBoxContainer" parent="VBoxContainer/TabContainer/Scripting/MarginContainer"]
layout_mode = 2

[node name="ScriptObjectVars" type="MarginContainer" parent="VBoxContainer/TabContainer/Scripting/MarginContainer/VBoxContainer"]
layout_mode = 2
theme_override_constants/margin_top = 0
theme_override_constants/margin_bottom = 8

[node name="ScriptInstances" type="VBoxContainer" parent="VBoxContainer/TabContainer/Scripting/MarginContainer/VBoxContainer"]
layout_mode = 2
size_flags_horizontal = 3
Original file line number Diff line number Diff line change
@@ -56,11 +56,8 @@ func _setup_parameter(parameter_name: String, parameter_data: Array) -> void:


func _on_parameter_changed(value, which: Control) -> void:
var parameters_for_entry: Dictionary = _target_script_instance.entry_parameters[_entry_id]
var param_name = which.label_text
var param_data = parameters_for_entry[param_name]
param_data[1] = value
_target_script_instance.apply_inspector_parameter_values()
var param_name: String = which.label_text
_target_script_instance.set_inspector_parameter_input_value(_entry_id, param_name, value)
_target_script_instance.script_instance_changed()


@@ -69,12 +66,7 @@ func _on_toggle_button_inspector_category_visibility_changed(new_visibility: boo


func _on_create_parameter(parameter_port_array: Array) -> void:
for entry_block in _target_script_instance.script_builder.entry_blocks:
if entry_block.entry_id == _entry_id:
entry_block.parameters.create_inspector_parameter(parameter_port_array)
entry_block.reset_entry_output_ports()
break
_target_script_instance.sync_script_inst_params_with_script_data()
_target_script_instance.create_inspector_parameter_input(_entry_id, parameter_port_array)
_target_script_instance.script_data_contents_changed()
refresh_inspected_nodes.emit()

Original file line number Diff line number Diff line change
@@ -7,6 +7,9 @@
[node name="InspectorScriptEntryInputs" instance=ExtResource("1_amxtn")]
script = ExtResource("2_adill")

[node name="ToggleButton" parent="CategoryTitle" index="0" node_paths=PackedStringArray("properties")]
properties = NodePath("../../Properties")

[node name="Text" parent="CategoryTitle/ToggleButton/Name" index="3"]
text = ""

Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
extends InspectorCategoryBase


const _TRASH_BUTTON = preload("res://creator/selection/inspector/script/entry_input_trash_button.tscn")

var target_node: Node # SpaceObject or SpaceGlobalScripts
var _is_editable: bool = false
var _object_vars: Dictionary
var _property_list: Control


## Must run before _ready()
func setup(target_object: Node, is_editable: bool) -> void:
target_node = target_object
_property_list = $Properties/MarginContainer/PropertyList
_is_editable = is_editable


func setup_object_vars(object_vars: Dictionary) -> void:
_object_vars = object_vars
for obj_var_name in _object_vars:
_setup_object_var(obj_var_name, _object_vars[obj_var_name])
if _property_list.get_child_count() < 5:
if _property_list.get_child_count() != 0:
$CategoryTitle/ToggleButton.hover_tooltip_text = ""
set_visible_to_maximum_size()


func _setup_object_var(obj_var_name: String, obj_var_value: Variant) -> void:
var obj_var_type: int = typeof(obj_var_value)
if obj_var_type == TYPE_NIL or not obj_var_type in ScriptParameterCreationMenu.INSPECTOR_PRIMITIVE_SCENES:
return
var obj_var_scene = ScriptParameterCreationMenu.INSPECTOR_PRIMITIVE_SCENES[obj_var_type].instantiate()
obj_var_scene.label_text = obj_var_name
obj_var_scene.reset_value = Serialization.type_convert_any(_get_default_value_of_obj_var(obj_var_name), obj_var_type)
# Be careful, the order matters here! Value editors with setters
# that use onready vars can only be used after adding as a child,
# and then we need to refresh if a refresh method exists.
_property_list.add_child(obj_var_scene)
obj_var_scene.current_value = obj_var_value
if obj_var_scene.has_method(&"refresh"):
obj_var_scene.refresh()
obj_var_scene.value_changed.connect(_on_object_var_changed.bind(obj_var_scene))
if _is_editable and GameplaySettings.script_show_add_inspector_input:
var trash_button: Node = _TRASH_BUTTON.instantiate()
obj_var_scene.add_child(trash_button)
trash_button.pressed.connect(_on_delete_object_var.bind(obj_var_name))


func _on_object_var_changed(value, which: Control) -> void:
var obj_var_name: String = which.label_text
Zone.script_network_sync.set_variable_on_node(target_node, obj_var_name, value)


func _on_delete_object_var(obj_var_name: String) -> void:
Zone.script_network_sync.set_variable_on_node(target_node, obj_var_name, null)
for child in _property_list.get_children():
child.cleanup_and_delete()
_property_list.remove_child(child)
var vars = target_node.get_meta(&"MirrorScriptObjectVariables")
vars.erase(obj_var_name)
setup_object_vars(vars)


func _get_default_value_of_obj_var(obj_var_name: String) -> Variant:
var script_instances: Array[ScriptInstance] = target_node.get_script_instances()
for script_inst in script_instances:
if script_inst.has_method(&"get_default_value_of_exposed_variable"):
var def = script_inst.get_default_value_of_exposed_variable(obj_var_name)
if def != null:
return def
return null
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[gd_scene load_steps=3 format=3 uid="uid://bhbojmr3nuwrc"]

[ext_resource type="PackedScene" uid="uid://dkxqj3l0xm8uw" path="res://creator/selection/inspector/categories/inspector_category_base.tscn" id="1_ov2tq"]
[ext_resource type="Script" path="res://creator/selection/inspector/script/inspector_script_object_vars.gd" id="2_abj1i"]

[node name="InspectorScriptObjectVars" instance=ExtResource("1_ov2tq")]
script = ExtResource("2_abj1i")

[node name="ToggleButton" parent="CategoryTitle" index="0" node_paths=PackedStringArray("properties")]
properties = NodePath("../../Properties")
hover_tooltip_text = "Use @export in GDScript"

[node name="Text" parent="CategoryTitle/ToggleButton/Name" index="3"]
text = "OBJECT VARIABLES"

[connection signal="mouse_entered" from="CategoryTitle/ToggleButton" to="CategoryTitle/ToggleButton" method="_on_hoverable_button_mouse_entered"]
[connection signal="mouse_exited" from="CategoryTitle/ToggleButton" to="CategoryTitle/ToggleButton" method="_on_hoverable_button_mouse_exited"]
[connection signal="pressed" from="CategoryTitle/ToggleButton" to="CategoryTitle/ToggleButton" method="_on_hoverable_button_pressed"]
1 change: 1 addition & 0 deletions mirror-godot-app/script/editor/abstract_script_editor.gd
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@ extends Control

signal request_save_script_as_asset(script_instance: ScriptInstance)
signal request_show_entry_creation_dialog(target_node: Node)
signal request_show_custom_entry_creation_dialog(target_node: Node)
signal request_toggle_variable_editor()
signal request_script_editor_visibility(is_visible: bool)
signal request_track_recently_used_space_script(script_instance: ScriptInstance)
Original file line number Diff line number Diff line change
@@ -13,22 +13,39 @@ func _ready() -> void:

func populate_and_show(target_node: Node) -> void:
title = "Create Script Entry"
_entry_creation_menu.populate_selection_tree(target_node)
_entry_creation_menu.populate_selection_tree(target_node, false)
GameUI.grab_input_lock(self)
popup_centered()
_entry_creation_menu.focus_search_bar()


func populate_and_show_for_custom(target_node: Node) -> void:
_entry_creation_menu.populate_selection_tree(target_node, true)
_show_custom_entry_menu()
GameUI.grab_input_lock(self)
_entry_creation_menu.focus_search_bar()


func _on_confirmed() -> void:
var signal_dict = _entry_creation_menu.get_desired_signal_signature()
if signal_dict == null:
title = "Create Entry With Custom Signal"
popup_centered()
_show_custom_entry_menu()
return
var block_dict: Dictionary = signal_dict.duplicate(false)
create_entry_block.emit(block_dict)


func _show_custom_entry_menu() -> void:
title = "Create Entry With Custom Signal"
popup_centered()
_entry_creation_menu.size = Vector2.ZERO
size = Vector2i.ZERO
await get_tree().process_frame
popup_centered()
_entry_creation_menu.size = Vector2.ZERO
size = Vector2i.ZERO


func _on_script_entry_creation_menu_confirmed() -> void:
hide()
_on_confirmed()
Original file line number Diff line number Diff line change
@@ -21,15 +21,18 @@ func setup(signal_tree_populator: ScriptEntrySignalTreePopulator) -> void:
_signal_selection.setup(signal_tree_populator)


func populate_selection_tree(target_node: Node) -> void:
func populate_selection_tree(target_node: Node, for_custom: bool) -> void:
_target_node = target_node
_custom_signal_parameters.clear()
_add_input_button.show()
_custom_signal_name.text = ""
_signal_parameters_label.text = "Signal Inputs:"
_signal_selection.populate_selection_tree(target_node)
_signal_selection.show()
_custom_signal.hide()
if for_custom:
_show_custom_entry_menu()
else:
_signal_selection.populate_selection_tree(target_node)
_signal_selection.show()
_custom_signal.hide()


func focus_search_bar() -> void:
@@ -43,14 +46,17 @@ func get_desired_signal_signature():
return _get_custom_signal_signature()


func _show_custom_entry_menu() -> void:
_signal_selection.hide()
_custom_signal.show()


func _get_selected_signal_signature():
var signal_dict = _signal_selection.get_selected_signal()
if signal_dict == null:
return null
if String(signal_dict["signal"]) == "custom_signal":
_signal_selection.hide()
_custom_signal.show()
_add_input_button.show()
_show_custom_entry_menu()
return null
return signal_dict

@@ -68,10 +74,14 @@ func _get_custom_signal_signature():
if not existing_signature.is_empty():
existing_signature = existing_signature.duplicate()
existing_signature["path"] = "self"
existing_signature["type"] = "entry"
return existing_signature
var ret = {
"entry_id": "self_" + signal_name + "_" + str(randi() % 1000000),
"name": "On " + signal_name.capitalize(),
"path": "self",
"signal": signal_name,
"type": "entry",
}
if not _custom_signal_parameters.is_empty():
ret["signalParameters"] = _custom_signal_parameters.duplicate(true)
4 changes: 4 additions & 0 deletions mirror-godot-app/script/editor/script_editor.gd
Original file line number Diff line number Diff line change
@@ -227,6 +227,10 @@ func _on_request_show_entry_creation_dialog(target_node: Node) -> void:
_script_entry_creation_dialog.populate_and_show(target_node)


func _on_request_show_custom_entry_creation_dialog(target_node: Node) -> void:
_script_entry_creation_dialog.populate_and_show_for_custom(target_node)


func _on_script_entry_creation_dialog_create_entry_block(block_json: Dictionary) -> void:
if _gd_script_editor.visible:
if block_json["type"] == "entry":
2 changes: 2 additions & 0 deletions mirror-godot-app/script/editor/script_editor.tscn
Original file line number Diff line number Diff line change
@@ -41,11 +41,13 @@ script = ExtResource("7_w8ott")

[connection signal="request_save_script_as_asset" from="VisualScriptEditor" to="." method="_on_request_save_script_as_asset"]
[connection signal="request_script_editor_visibility" from="VisualScriptEditor" to="." method="set_visual_script_editor_visibility"]
[connection signal="request_show_custom_entry_creation_dialog" from="VisualScriptEditor" to="." method="_on_request_show_custom_entry_creation_dialog"]
[connection signal="request_show_entry_creation_dialog" from="VisualScriptEditor" to="." method="_on_request_show_entry_creation_dialog"]
[connection signal="request_toggle_variable_editor" from="VisualScriptEditor" to="." method="_on_request_toggle_variable_editor"]
[connection signal="request_track_recently_used_space_script" from="VisualScriptEditor" to="." method="_on_request_track_recently_used_space_script"]
[connection signal="request_save_script_as_asset" from="GDScriptEditor" to="." method="_on_request_save_script_as_asset"]
[connection signal="request_script_editor_visibility" from="GDScriptEditor" to="." method="set_gd_script_editor_visibility"]
[connection signal="request_show_custom_entry_creation_dialog" from="GDScriptEditor" to="." method="_on_request_show_custom_entry_creation_dialog"]
[connection signal="request_show_entry_creation_dialog" from="GDScriptEditor" to="." method="_on_request_show_entry_creation_dialog"]
[connection signal="request_toggle_variable_editor" from="GDScriptEditor" to="." method="_on_request_toggle_variable_editor"]
[connection signal="request_track_recently_used_space_script" from="GDScriptEditor" to="." method="_on_request_track_recently_used_space_script"]
15 changes: 15 additions & 0 deletions mirror-godot-app/script/gd/gdscript_entry.gd
Original file line number Diff line number Diff line change
@@ -174,6 +174,21 @@ func _value_to_gdscript_literal(value: Variant, type_enum: int) -> String:
return str(value)


func connect_entry_signal(script_instance_object: Object, inst_entry_parameters: Dictionary) -> void:
# Trivial case: No inspector parameter inputs, no bindv needed.
if inst_entry_parameters.is_empty():
entry_node.connect(entry_signal, Callable(script_instance_object, function_name))
return
# If the user added inspector parameters via "Add Input", we need to bind them to the signal.
var insp_inputs: Array = []
if inst_entry_parameters.has(entry_id):
var params_for_entry: Dictionary = inst_entry_parameters[entry_id]
for insp_param_name in params_for_entry:
var insp_param_data: Array = params_for_entry[insp_param_name]
insp_inputs.append(insp_param_data[1])
entry_node.connect(entry_signal, Callable(script_instance_object, function_name).bindv(insp_inputs))


static func create_node_for_entry_signal(entry_signal: String) -> Node:
if entry_signal == "timeout":
var timer: Timer = Timer.new()
95 changes: 79 additions & 16 deletions mirror-godot-app/script/gd/gdscript_instance.gd
Original file line number Diff line number Diff line change
@@ -7,16 +7,16 @@ signal gdscript_compile_success()

const _EMPTY_SCRIPT: String = """# Welcome to The Mirror-flavored GDScript!
# This is like normal GDScript, but you must not write class\u200B_name or ext\u200Bends.
# You can print to the notification area using `Notify.info(title, message)`.
# You may use functions and variables just like you would in normal GDScript.
# Use `target_object` to refer to the object (SpaceObject or global) the script
# is attached to instead of `self`, as `self` refers to the script itself.
# The Mirror provides support for multiple scripts per object, you can use
# members of SpaceObject the same way you use inherited members in Godot.
# For example, `print(position)` will print a SpaceObject's position.
# You can print to the notification area using `Notify.info(title, message)`.
# For example, `Notify.info("pos", str(position))` will print a SpaceObject's position.
# Use `target_object` to refer to the object (SpaceObject or global) the script
# is attached to instead of `self`, as `self` refers to the script itself.
# Called when a SpaceObject finishes loading.
# Called when a SpaceObject and script finish loading.
func _ready() -> void:
pass # Replace with function body.
@@ -26,9 +26,7 @@ func _process(delta: float) -> void:
pass # Replace with function body.
"""

const _SCRIPT_PREPROCESS_PREFIX: String = """extends Object
signal tmusergdscript_runtime_error(error_str: String, frame_index: int, line_num: int, func_name: String)
const _SCRIPT_PREPROCESS_PREFIX: String = """extends TMUserGDScriptBase
func get_object_variable(variable_name: String) -> Variant:
@@ -40,10 +38,12 @@ func has_object_variable(variable_name: String) -> bool:
func set_object_variable(variable_name: String, variable_value: Variant) -> void:
if variable_name in self:
set(variable_name, variable_value)
Mirror.set_object_variable(target_object, variable_name, variable_value)
func tween_object_variable(variable_name: String, to_value: Variant, duration: float, trans: Tween.TransitionType, easing: Tween.EaseType) -> void:
func tween_object_variable(variable_name: String, to_value: Variant, duration: float, trans: Tween.TransitionType = Tween.TRANS_LINEAR, easing: Tween.EaseType = Tween.EASE_IN_OUT) -> void:
Mirror.tween_object_variable(target_object, variable_name, to_value, duration, trans, easing)
@@ -66,7 +66,12 @@ static var _SCRIPT_TEXT_DENYLIST: Array[RegEx] = [
RegEx.create_from_string("\\bDirAccess\\b")
]

static var _EXPOSE_VAR_REGEX: RegEx = RegEx.create_from_string("@export var ([_a-zA-Z][_a-zA-Z0-9]{0,30})\\b[^=\\n]*(= )?([^=\\n]*)")
static var _EXPRESSION = Expression.new()

var _entries: Array[GDScriptEntry] = []
var _exposed_var_names: PackedStringArray = []
var _exposed_var_default_values: Dictionary = {}
var _source_code: String
var gdscript_code: TMUserGDScript = TMUserGDScript.new()
var script_instance_object: Object
@@ -155,6 +160,12 @@ func get_default_value_of_entry_inspector_parameter(entry_id: String, parameter_
return null


func get_default_value_of_exposed_variable(variable_name: String) -> Variant:
if _exposed_var_default_values.has(variable_name):
return _exposed_var_default_values[variable_name]
return null


func get_entry_line_numbers() -> PackedInt32Array:
var ret := PackedInt32Array()
for entry in _entries:
@@ -179,7 +190,12 @@ func set_source_code(source_code: String) -> void:


func create_entry(entry_json: Dictionary) -> void:
var new_function_name: String = entry_json["name"].to_snake_case()
var new_function_name: String
if entry_json.has("name"):
new_function_name = entry_json["name"].to_snake_case()
else:
new_function_name = "on_" + entry_json["signal"]
entry_json["function"] = new_function_name
for entry in _entries:
if entry.function_name == new_function_name:
return # We already have an entry for this, no need to make a new entry.
@@ -210,6 +226,15 @@ func _sync_entry_params_with_gdscript_code() -> void:
script_entries_changed.emit()


func create_inspector_parameter_input(entry_id: String, parameter_port_array: Array) -> void:
for gdscript_entry in _entries:
if gdscript_entry.entry_id == entry_id:
gdscript_entry.entry_parameters.create_inspector_parameter(parameter_port_array)
_source_code = gdscript_entry.sync_gdscript_code_with_entry(_source_code)
break
sync_script_inst_params_with_script_data()


## Ensure the script instance entry inspector parameters match the
## signature of the script's data. Since a script may be used by
## multiple objects, parameters may get out of sync without this code.
@@ -222,10 +247,10 @@ func sync_script_inst_params_with_script_data() -> void:
entry_parameters[entry_id] = new_params
if entry_id in old_entry_parameters:
var old_params: Dictionary = old_entry_parameters[entry_id]
for old_key in old_params:
if old_key in new_params:
var old_param_array: Array = old_params[old_key]
var new_param_array: Array = new_params[old_key]
for param_key in old_params:
if param_key in new_params:
var old_param_array: Array = old_params[param_key]
var new_param_array: Array = new_params[param_key]
new_param_array[1] = old_param_array[1]
entry_parameters.sort()
apply_inspector_parameter_values()
@@ -256,20 +281,28 @@ func _preprocess_and_apply_code() -> void:
error_message_dict["line"] -= _SCRIPT_PREPROCESS_LINE_COUNT
gdscript_compile_error.emit(error_code, error_messages)
return
_update_exposed_variables()
gdscript_compile_success.emit()
if not can_execute():
# Don't even init the script if it can't execute.
# Someone could put malicious code inside func _init().
return
# Instantiate the successfully loaded script.
script_instance_object = gdscript_code.new()
_sync_exposed_variables_with_spaceobj_spacevars()
# Connect the signals.
script_instance_object.load_exposed_vars.connect(_on_load_exposed_vars)
script_instance_object.save_exposed_vars.connect(_on_save_exposed_vars)
script_instance_object.tmusergdscript_runtime_error.connect(_on_tmusergdscript_runtime_error)
for entry in _entries:
entry.entry_node.connect(entry.entry_signal, Callable(script_instance_object, entry.function_name))
entry.connect_entry_signal(script_instance_object, entry_parameters)
# Supplementary entry callbacks. Keep this in sync with GDScript CodeEdit load_entry_connection_decoration.
if target_node is SpaceObject:
if _source_code.contains("func _ready("):
target_node.setup_done.connect(Callable(script_instance_object, &"_ready"))
if target_node._is_setup:
script_instance_object.call(&"_ready")
else:
target_node.setup_done.connect(Callable(script_instance_object, &"_ready"))
if _source_code.contains("func _physics_process("):
Zone.physics_process_every_frame.connect(Callable(script_instance_object, &"_physics_process"))
if _source_code.contains("func _process("):
@@ -295,6 +328,36 @@ func _sync_gdscript_code_with_entries() -> void:
_source_code = entry.sync_gdscript_code_with_entry(_source_code)


func _sync_exposed_variables_with_spaceobj_spacevars() -> void:
#_update_exposed_variables()
for var_name in _exposed_var_names:
if Mirror.has_object_variable(target_node, var_name):
script_instance_object.set(var_name, Mirror.get_object_variable(target_node, var_name))
else:
Mirror.set_object_variable(target_node, var_name, script_instance_object.get(var_name))


func _on_load_exposed_vars() -> void:
for var_name in _exposed_var_names:
script_instance_object.set(var_name, Mirror.get_object_variable(target_node, var_name))


func _on_save_exposed_vars() -> void:
for var_name in _exposed_var_names:
Mirror.set_object_variable(target_node, var_name, script_instance_object.get(var_name))


func _update_exposed_variables() -> void:
_exposed_var_names.clear()
var matches: Array[RegExMatch] = _EXPOSE_VAR_REGEX.search_all(_source_code)
for mat in matches:
var var_name: String = mat.get_string(1)
_exposed_var_names.append(var_name)
if mat.get_group_count() >= 3:
_EXPRESSION.parse(mat.get_string(3))
_exposed_var_default_values[var_name] = _EXPRESSION.execute()


## This method handles runtime error messages similar to VisualScriptInstance's `_on_block_message` method.
func _on_tmusergdscript_runtime_error(error_str: String, frame_index: int, line_num: int, func_name: String) -> void:
line_num -= _SCRIPT_PREPROCESS_LINE_COUNT
50 changes: 48 additions & 2 deletions mirror-godot-app/script/gd/mirror_singleton.gd
Original file line number Diff line number Diff line change
@@ -3,6 +3,28 @@ class_name Mirror
extends Object


# Misc.
static func get_friendly_name(value: Variant) -> String:
if value is SpaceObject:
return value.get_space_object_name()
elif value is Player:
return value.get_player_name()
elif value is Node:
return value.name
elif value == null:
return "<null>"
elif not is_instance_valid(value):
return "<invalid instance>"
else:
# For non-Node objects, str() is the best we can do.
return str(value)


static func print_in_chat(attached_object: Object, message: String, range_radius: float = INF) -> void:
GameUI.chat_ui.send_message_from_object(attached_object, message, range_radius)


# Global variables.
static func get_global_variable(variable_name: String) -> Variant:
return Zone.script_network_sync.get_global_variable(variable_name)

@@ -15,10 +37,11 @@ static func set_global_variable(variable_name: String, variable_value: Variant)
Zone.script_network_sync.set_global_variable(variable_name, variable_value)


static func tween_global_variable(variable_name: String, to_value: Variant, duration: float, trans: Tween.TransitionType, easing: Tween.EaseType) -> void:
static func tween_global_variable(variable_name: String, to_value: Variant, duration: float, trans: Tween.TransitionType = Tween.TRANS_LINEAR, easing: Tween.EaseType = Tween.EASE_IN_OUT) -> void:
Zone.script_network_sync.tween_global_variable(variable_name, to_value, duration, trans, easing)


# Object variables.
static func get_object_variable(variable_object: Object, variable_name: String) -> Variant:
var object_variables = variable_object.get_meta(&"MirrorScriptObjectVariables")
return TMDataUtil.get_variable_by_json_path_string(object_variables, variable_name)
@@ -45,8 +68,31 @@ static func set_object_variable(variable_object: Object, variable_name: String,
Zone.script_network_sync.object_variable_changed.emit(variable_object, variable_name, variable_value)


static func tween_object_variable(variable_node: Node, variable_name: String, to_value: Variant, duration: float, trans: Tween.TransitionType, easing: Tween.EaseType) -> void:
static func tween_object_variable(variable_node: Node, variable_name: String, to_value: Variant, duration: float, trans: Tween.TransitionType = Tween.TRANS_LINEAR, easing: Tween.EaseType = Tween.EASE_IN_OUT) -> void:
# Note: Unlike setting a variable, this does not apply immediately.
# Since tweening does not have an effect on the same frame anyway,
# we can afford to wait for the server to only do a synced tween.
Zone.script_network_sync.tween_variable_on_node(variable_node, variable_name, to_value, duration, trans, easing)


# Object properties.
static func get_object_property(property_object: Object, property_name: StringName) -> Variant:
return property_object.get(property_name)


static func has_object_property(property_object: Object, property_name: StringName) -> bool:
return property_name in property_object


static func set_object_property(property_object: Object, property_name: StringName, property_value: Variant) -> void:
property_object.set(property_name, property_value)


static func tween_object_property(property_object: Object, property_name: StringName, to_value: Variant, duration: float, trans: Tween.TransitionType = Tween.TRANS_LINEAR, easing: Tween.EaseType = Tween.EASE_IN_OUT) -> void:
if not property_object is Node:
Notify.error("Cannot tween object property", "The target object is not a node, but tweening properties is only supported on nodes for now.")
return
# Note: Unlike setting a property, this does not apply immediately.
# Since tweening does not have an effect on the same frame anyway,
# we can afford to wait for the server to only do a synced tween.
Zone.script_network_sync.tween_property_on_node(property_object, property_name, to_value, duration, trans, easing)
6 changes: 6 additions & 0 deletions mirror-godot-app/script/gd/tmusergdscript_base.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class_name TMUserGDScriptBase extends Object


signal load_exposed_vars()
signal save_exposed_vars()
signal tmusergdscript_runtime_error(error_str: String, frame_index: int, line_num: int, func_name: String)
20 changes: 14 additions & 6 deletions mirror-godot-app/script/network_sync.gd
Original file line number Diff line number Diff line change
@@ -314,7 +314,7 @@ func _set_global_variables_network(variables: Dictionary) -> void:
received_variable_data_change.emit()


func tween_global_variable(variable_name: String, to_value: Variant, duration: float, trans: Tween.TransitionType, easing: Tween.EaseType) -> void:
func tween_global_variable(variable_name: String, to_value: Variant, duration: float, trans: Tween.TransitionType = Tween.TRANS_LINEAR, easing: Tween.EaseType = Tween.EASE_IN_OUT) -> void:
_net_queue_tweened_global_variables[variable_name] = [to_value, duration, trans, easing]


@@ -338,7 +338,7 @@ func _tween_global_variables_network(tweened_variables: Dictionary) -> void:


## Tween a user variable we store inside of a Dictionary using MethodTweener.
func _tween_variable_in_dict(variables_dict: Dictionary, variable_name: String, from_value: Variant, to_value: Variant, duration: float, trans: Tween.TransitionType, easing: Tween.EaseType) -> void:
func _tween_variable_in_dict(variables_dict: Dictionary, variable_name: String, from_value: Variant, to_value: Variant, duration: float, trans: Tween.TransitionType = Tween.TRANS_LINEAR, easing: Tween.EaseType = Tween.EASE_IN_OUT) -> void:
var split_path: PackedStringArray = TMDataUtil.split_json_path_string(variable_name)
var method_callable: Callable = _tween_variable_callback_method.bind(variables_dict, split_path)
var tween: Tween = get_tree().create_tween()
@@ -412,7 +412,7 @@ func _set_property_on_node(node: Node, property: StringName, value: Variant) ->
node.set(property, value)


func tween_property_on_node(node: Node, property: StringName, to_value: Variant, duration: float, trans: Tween.TransitionType, easing: Tween.EaseType) -> void:
func tween_property_on_node(node: Node, property: StringName, to_value: Variant, duration: float, trans: Tween.TransitionType = Tween.TRANS_LINEAR, easing: Tween.EaseType = Tween.EASE_IN_OUT) -> void:
if to_value is Object:
Notify.error("Tween Failed", "Tweening an object is not a sensible operation. Aborting.")
return
@@ -446,7 +446,7 @@ func _tween_properties_on_nodes_network(nodes_tweened_properties: Dictionary) ->
_save_tween_results_for_later(node_path, node_tweened_properties, _set_queue_properties_on_nodes)


func _tween_property_on_node(node: Node, property: StringName, to_value: Variant, duration: float, trans: Tween.TransitionType, easing: Tween.EaseType) -> void:
func _tween_property_on_node(node: Node, property: StringName, to_value: Variant, duration: float, trans: Tween.TransitionType = Tween.TRANS_LINEAR, easing: Tween.EaseType = Tween.EASE_IN_OUT) -> void:
if not ScriptPropertyRegistration.has_registered_property(property):
return
var tween: Tween = get_tree().create_tween()
@@ -508,13 +508,21 @@ func _set_variable_on_node(node: Node, variable_name: String, variable_value: Va
node.set_meta(&"MirrorScriptObjectVariables", {})
var object_variables: Dictionary = node.get_meta(&"MirrorScriptObjectVariables")
TMDataUtil.set_variable_by_json_path_string(object_variables, variable_name, variable_value)
# If this is an exposed script variable, set it in the script.
if node.has_method(&"get_script_instances") and Zone.is_host():
var script_instances: Array[ScriptInstance] = node.get_script_instances()
for script_inst in script_instances:
if script_inst is GDScriptInstance and is_instance_valid(script_inst.script_instance_object):
var obj = script_inst.script_instance_object
if variable_name in obj:
obj.set(variable_name, variable_value)
# Keep track of all node variables ever set for use with the variable editor.
var node_path: NodePath = node.get_path()
var node_variables_in_all: Dictionary = _all_set_variables_on_nodes.get_or_add(node_path, {})
TMDataUtil.set_variable_by_json_path_string(node_variables_in_all, variable_name, variable_value)


func tween_variable_on_node(node: Node, variable: String, to_value: Variant, duration: float, trans: Tween.TransitionType, easing: Tween.EaseType) -> void:
func tween_variable_on_node(node: Node, variable: String, to_value: Variant, duration: float, trans: Tween.TransitionType = Tween.TRANS_LINEAR, easing: Tween.EaseType = Tween.EASE_IN_OUT) -> void:
if to_value is Object:
Notify.error("Tween Failed", "Tweening an Object value is not a sensible operation. Aborting.")
return
@@ -549,7 +557,7 @@ func _tween_variables_on_nodes_network(nodes_tweened_variables: Dictionary) -> v
received_variable_data_change.emit()


func _tween_variable_on_node(node: Node, variable_name: String, to_value: Variant, duration: float, trans: Tween.TransitionType, easing: Tween.EaseType) -> void:
func _tween_variable_on_node(node: Node, variable_name: String, to_value: Variant, duration: float, trans: Tween.TransitionType = Tween.TRANS_LINEAR, easing: Tween.EaseType = Tween.EASE_IN_OUT) -> void:
if not node.has_meta(&"MirrorScriptObjectVariables"):
node.set_meta(&"MirrorScriptObjectVariables", {})
var object_variables: Dictionary = node.get_meta(&"MirrorScriptObjectVariables")
18 changes: 16 additions & 2 deletions mirror-godot-app/script/script_instance.gd
Original file line number Diff line number Diff line change
@@ -87,8 +87,11 @@ func is_script_instance_setup() -> bool:

func setup(node: Node, script_inst_dict: Dictionary) -> void:
target_node = node
var script_entity_data: Dictionary = await Net.script_client.get_script_entity(script_id)
setup_script_entity_data(script_entity_data)
var script_entity_data: Dictionary = Net.script_client.get_script_entity(script_id)
# If it's empty, we don't have the data yet. The setup_script_entity_data
# function will be ran later when we receive the data.
if not script_entity_data.is_empty():
setup_script_entity_data(script_entity_data)


func setup_script_instance_data(script_inst_dict: Dictionary) -> void:
@@ -145,6 +148,17 @@ func update_script_entity_data_from_network(script_entity_data: Dictionary) -> v


# Parameter methods.
func create_inspector_parameter_input(entry_id: String, parameter_port_array: Array) -> void:
assert(false, "This method must be overridden in a derived class.")


func set_inspector_parameter_input_value(entry_id: String, param_name: String, new_value: Variant) -> void:
var parameters_for_entry: Dictionary = entry_parameters[entry_id]
var param_data: Array = parameters_for_entry[param_name]
param_data[1] = new_value
apply_inspector_parameter_values()


func _load_entry_parameters_from_json(entry_parameter_json: Dictionary) -> void:
assert(entry_parameters.is_empty(), "This method expects to only be called once. If calling multiple times is needed, add support for that.")
entry_parameters = entry_parameter_json.duplicate(true)
14 changes: 1 addition & 13 deletions mirror-godot-app/script/visual/blocks/misc/get_friendly_name.gd
Original file line number Diff line number Diff line change
@@ -4,19 +4,7 @@ extends ScriptBlock
func evaluate() -> void:
evaluate_inputs()
var value = inputs[0].value
if value is SpaceObject:
outputs[0].value = value.get_space_object_name()
elif value is Player:
outputs[0].value = value.get_player_name()
elif value is Node:
outputs[0].value = value.name
elif value == null:
outputs[0].value = "<null>"
elif not is_instance_valid(value):
outputs[0].value = "<invalid instance>"
else:
# For non-Node objects, str() is the best we can do.
outputs[0].value = str(value)
outputs[0].value = Mirror.get_friendly_name(value)


func get_script_block_type() -> String:
2 changes: 2 additions & 0 deletions mirror-godot-app/script/visual/blocks/script_block.gd
Original file line number Diff line number Diff line change
@@ -109,6 +109,8 @@ func _setup_base(block_json: Dictionary) -> void:
graph_position = Serialization.array_to_vector2(block_json["position"])
if block_json.has("name"):
graph_name = block_json["name"]
elif block_json.has("signal"):
graph_name = "On " + String(block_json["signal"]).capitalize()
else:
graph_name = String(block_json["type"]).capitalize()

Original file line number Diff line number Diff line change
@@ -112,7 +112,7 @@ func _on_confirmed():
var desired_block_json: Dictionary = _script_block_creation_menu.get_desired_block_json()
if desired_block_json.is_empty():
return
if desired_block_json["type"] == "entry" and not desired_block_json.has("signal"):
if desired_block_json["type"] == "entry" and desired_block_json["signal"] == &"custom_signal":
request_show_entry_creation_dialog.emit()
return
create_block.emit(desired_block_json)
Original file line number Diff line number Diff line change
@@ -148,7 +148,7 @@ func _on_request_block_creation(constraint: int, data_type: int, index: int, fro

func _on_request_entry_creation(where: Vector2) -> void:
_creation_position = where
request_show_entry_creation_dialog.emit(_script_instance.target_node)
request_show_custom_entry_creation_dialog.emit(_script_instance.target_node)


func _on_request_input_value_edit(graph_node: ScriptBlockGraphNode, input_port: ScriptBlock.ScriptBlockInputPort) -> void:
9 changes: 9 additions & 0 deletions mirror-godot-app/script/visual/visual_script_instance.gd
Original file line number Diff line number Diff line change
@@ -137,6 +137,15 @@ func _create_event_node(entry_block: ScriptBlockEntryBase) -> void:
entry_block.entry_node = event_node


func create_inspector_parameter_input(entry_id: String, parameter_port_array: Array) -> void:
for entry_block in script_builder.entry_blocks:
if entry_block.entry_id == entry_id:
entry_block.parameters.create_inspector_parameter(parameter_port_array)
entry_block.reset_entry_output_ports()
break
sync_script_inst_params_with_script_data()


## Ensure the script instance entry inspector parameters match the
## signature of the script's data. Since a script may be used by
## multiple objects, parameters may get out of sync without this code.

0 comments on commit 9273b4c

Please sign in to comment.