Updates nested object uniquification expectations based on Blender export ordering.

extends ColorRect

const GOLDEN_GLTF_SCENE: PackedScene = preload("res://gltf/45545-relax-name-sanitization.gltf")

## These constants are the prefix part of the node names used in the golden glTF file.
# Prefix for "basic" nodes, no import hints, no animation, etc...
const GLTF_PREFIX_BASIC: String = "Basic_"

# Prefix for nodes that have an import hint.
const GLTF_PREFIX_HINTED: String = "Hinted_"

# Prefix for nodes that have an associated animation.
const GLTF_PREFIX_ANIMATED: String = "Animated_"

# The suffix for animations (all animations in the golden glTF file use the Blender default suffix
# "Action" and the looping import hint "-loop".
const GLTF_SUFFIX_ANIMATION: String = "Action-loop"

## These constants are the infix part of the node names used in the golden glTF file.
const GLTF_ALPHANUMERIC: String = "Alphanumeric123"
# The last three symbols are:
# https://en.wiktionary.org/wiki/%E3%82%B4#Japanese
# https://en.wiktionary.org/wiki/%E3%83%89#Japanese
# https://en.wiktionary.org/wiki/%E3%83%84#Japanese
const GLTF_KATAKANA: String = "Katakana_ゴドツ"
# The last two symbols are:
# https://emojipedia.org/grinning-face
# https://emojipedia.org/robot/
const GLTF_EMOJI: String = "Emoji_😀🤖"
const GLTF_ALLOWED_SYMBOLS: String = "AllowedSymbols_!#$%^&*()-=[]{}';<>,~"
const GLTF_DISALLOWED_NONCLASHING: String = "Disallowed_Non-clashing_.:@\"/"
const GLTF_DISALLOWED_CLASHING_1: String = "Disallowed_Clashing_."
const GLTF_DISALLOWED_CLASHING_2: String = "Disallowed_Clashing_@"
const GLTF_DISALLOWED_CLASHING_DEEP_TREE_1: String = "Disallowed_Clashing_Deep_Tree_@"
const GLTF_DISALLOWED_CLASHING_DEEP_TREE_2: String = "Disallowed_Clashing_Deep_Tree_@@"
const GLTF_DISALLOWED_CLASHING_DEEP_TREE_3: String = "Disallowed_Clashing_Deep_Tree_@@@"

var output: BoxContainer
var test_scene: Node
var test_animation_player: AnimationPlayer

func _ready():
	output = $ScrollContainer/OutputWindow
	test_scene = GOLDEN_GLTF_SCENE.instance()
	test_animation_player = test_scene.get_node_or_null("AnimationPlayer")
	if not test_animation_player:
		_add_line("ERROR: imported glTF scene has no AnimationPlayer node!", Color.orangered)
		print("FAIL: imported glTF scene has no AnimationPlayer so no tests will be run.")
		return

	var passed_tests: Array = []
	var failed_tests: Array = []

	for method_name in _find_tests():
		_add_test_header(method_name)
		var result: bool = self.call(method_name)
		_add_line(" ")
		if result:
			passed_tests.push_back(method_name)
		else:
			failed_tests.push_back(method_name)

	if failed_tests:
		print("FAIL: %d failed tests:" % len(failed_tests))
		for test in failed_tests:
			print("\t%s" % test)
		print("")
		print("Animations:")
		for animation_name in test_animation_player.get_animation_list():
			print("\t%s" % animation_name)
	else:
		print("PASS: Passed all %d tests" % len(passed_tests))

func _find_tests() -> Array:
	var ret: Array = []
	for method in get_method_list():
		var method_name: String = method.get("name")
		if method_name.begins_with("_test_"):
			ret.push_back(method_name)
	ret.sort()
	return ret

func _add_test_header(method_name: String) -> void:
	_add_line(method_name, Color.royalblue)

func _add_result(succeeded: bool, message: String) -> void:
	var prefix: String = "  [OK] " if succeeded else "  [FAIL] "
	_add_line(prefix + message, Color.forestgreen if succeeded else Color.orangered)

func _add_line(text: String, font_color: Color = Color.black) -> void:
	var line: Label = Label.new()
	line.text = text
	line.add_theme_font_size_override("font_size", 22)
	line.add_theme_color_override("font_color", font_color)
	output.add_child(line)
	print(text)

func _check_basic(original: String, expected: String) -> bool:
	var node: Node = test_scene.get_node_or_null(expected)
	if not node:
		_add_result(
			false,
			"Missing expected Godot node '%s' for glTF node '%s'" % [expected, original])
		return false
	_add_result(true, "Found expected node '%s'" % expected)
	return true

func _test_basic_alphanumeric_is_unchanged() -> bool:
	var original: String = GLTF_PREFIX_BASIC + GLTF_ALPHANUMERIC
	var expected: String = original
	return _check_basic(original, expected)

func _test_basic_allowed_symbols_is_unchanged() -> bool:
	var original: String = GLTF_PREFIX_BASIC + GLTF_ALLOWED_SYMBOLS
	var expected: String = original
	return _check_basic(original, expected)

func _test_basic_katakana_is_unchanged() -> bool:
	var original: String = GLTF_PREFIX_BASIC + GLTF_KATAKANA
	var expected: String = original
	return _check_basic(original, expected)

func _test_basic_emoji_is_unchanged() -> bool:
	var original: String = GLTF_PREFIX_BASIC + GLTF_EMOJI
	var expected: String = original
	return _check_basic(original, expected)

func _test_basic_disallowed_characters_are_stripped() -> bool:
	var original: String = GLTF_PREFIX_BASIC + GLTF_DISALLOWED_NONCLASHING
	var expected: String = GLTF_PREFIX_BASIC + "Disallowed_Non-clashing_"
	return _check_basic(original, expected)

func _test_basic_disallowed_clashing_nodes_are_uniquified() -> bool:
	var original: String = GLTF_PREFIX_BASIC + GLTF_DISALLOWED_CLASHING_1
	var expected: String = GLTF_PREFIX_BASIC + "Disallowed_Clashing_"
	if not _check_basic(original, expected):
		return false

	original = GLTF_PREFIX_BASIC + GLTF_DISALLOWED_CLASHING_2
	expected = GLTF_PREFIX_BASIC + "Disallowed_Clashing_2"
	return _check_basic(original, expected)

func _test_basic_disallowed_nested_nodes_are_uniquified() -> bool:
	# Note: Blender 9.2 serializes in prefix order, so the leaf child will be named first.
	var original: String = GLTF_PREFIX_BASIC + GLTF_DISALLOWED_CLASHING_DEEP_TREE_1
	var expected_root: String = GLTF_PREFIX_BASIC + "Disallowed_Clashing_Deep_Tree_3"
	if not _check_basic(original, expected_root):
		return false

	original = GLTF_PREFIX_BASIC + GLTF_DISALLOWED_CLASHING_DEEP_TREE_2
	var expected_mid = expected_root + "/" + GLTF_PREFIX_BASIC + "Disallowed_Clashing_Deep_Tree_2"
	if not _check_basic(original, expected_mid):
		return false

	original = GLTF_PREFIX_BASIC + GLTF_DISALLOWED_CLASHING_DEEP_TREE_3
	var expected_leaf = expected_mid + "/" + GLTF_PREFIX_BASIC + "Disallowed_Clashing_Deep_Tree_"
	return _check_basic(original, expected_leaf)

# Verifies that a node exists with the expected name and that it has a StaticBody3D child.
func _check_hinted_col(original: String, expected: String) -> bool:
	var node: Node = test_scene.get_node_or_null(expected)
	if not node:
		_add_result(
			false,
			"Missing expected Godot node '%s' for glTF node '%s'" % [expected, original])
		return false

	var collision_body: StaticBody3D = node.get_node_or_null("static_collision")
	if not collision_body:
		_add_result(
			false,
			"Missing 'static_collision' child for Godot node '%s' (glTF '%s')" % [
				expected, original])
		return false

	# This test assumes that if the static_collision node was created the -col hint was properly
	# handled (as that behavior should be tested elsewhere).
	_add_result(true, "Expected node '%s' contains a static_collision StaticBody3D" % expected)
	return true

func _test_hinted_alphanumeric_is_unchanged() -> bool:
	var original: String = GLTF_PREFIX_HINTED + GLTF_ALPHANUMERIC
	var expected: String = original
	return _check_hinted_col(original, expected)

func _test_hinted_allowed_symbols_is_unchanged() -> bool:
	var original: String = GLTF_PREFIX_HINTED + GLTF_ALLOWED_SYMBOLS
	var expected: String = original
	return _check_hinted_col(original, expected)

func _test_hinted_katakana_is_unchanged() -> bool:
	var original: String = GLTF_PREFIX_HINTED + GLTF_KATAKANA
	var expected: String = original
	return _check_hinted_col(original, expected)

func _test_hinted_emoji_is_unchanged() -> bool:
	var original: String = GLTF_PREFIX_HINTED + GLTF_EMOJI
	var expected: String = original
	return _check_hinted_col(original, expected)

func _test_hinted_disallowed_characters_are_stripped() -> bool:
	var original: String = GLTF_PREFIX_HINTED + GLTF_DISALLOWED_NONCLASHING
	var expected: String = GLTF_PREFIX_HINTED + "Disallowed_Non-clashing_"
	return _check_hinted_col(original, expected)

func _test_hinted_disallowed_clashing_nodes_are_uniquified() -> bool:
	var original: String = GLTF_PREFIX_HINTED + GLTF_DISALLOWED_CLASHING_1
	var expected: String = GLTF_PREFIX_HINTED + "Disallowed_Clashing_"
	if not _check_hinted_col(original, expected):
		return false

	original = GLTF_PREFIX_HINTED + GLTF_DISALLOWED_CLASHING_2
	expected = GLTF_PREFIX_HINTED + "Disallowed_Clashing_2"
	return _check_hinted_col(original, expected)

func _test_hinted_disallowed_nested_nodes_are_uniquified() -> bool:
	var original: String = GLTF_PREFIX_HINTED + GLTF_DISALLOWED_CLASHING_DEEP_TREE_1
	var expected_root: String = GLTF_PREFIX_HINTED + "Disallowed_Clashing_Deep_Tree_3"
	if not _check_hinted_col(original, expected_root):
		return false

	original = GLTF_PREFIX_HINTED + GLTF_DISALLOWED_CLASHING_DEEP_TREE_2
	var expected_mid = expected_root + "/" + GLTF_PREFIX_HINTED + "Disallowed_Clashing_Deep_Tree_2"
	if not _check_hinted_col(original, expected_mid):
		return false

	original = GLTF_PREFIX_HINTED + GLTF_DISALLOWED_CLASHING_DEEP_TREE_3
	var expected_leaf = expected_mid + "/" + GLTF_PREFIX_HINTED + "Disallowed_Clashing_Deep_Tree_"
	return _check_hinted_col(original, expected_leaf)

# Verifies that a node exists with the expected name and that an associated animation exists.
func _check_animated(original: String, expected: String, expected_animation: String = "") -> bool:
	var node: Node = test_scene.get_node_or_null(expected)
	if not node:
		_add_result(
			false,
			"Missing expected Godot node '%s' for glTF node '%s'" % [expected, original])
		return false

	if expected_animation.is_empty():
		expected_animation = expected + GLTF_SUFFIX_ANIMATION
	var animation: Animation = test_animation_player.get_animation(expected_animation)
	if not animation:
		_add_result(
			false,
			"Missing expected animation '%s' for Godot node '%s' (glTF '%s')" % [
				expected_animation, expected, original])
		return false

	var track_count: int = animation.get_track_count()
	if track_count != 1:
		_add_result(
			false,
			"Track count %d != 1 on animation '%s' for Godot node '%s' (glTF '%s')" % [
				track_count, expected_animation, expected, original])
		return false

	var track_type: int = animation.track_get_type(0)
	if track_type != Animation.TYPE_TRANSFORM:
		_add_result(
			false,
			"Unexpected track type %d on animation '%s' for Godot node '%s' (glTF '%s')" % [
				track_type, expected_animation, expected, original])
		return false

	var track_path: NodePath = animation.track_get_path(0)
	var expected_track_path = NodePath(expected)
	if track_path != expected_track_path:
		_add_result(
			false,
			("Incorrect track_path '%s' (expected '%s') on animation '%s' for Godot node '%s' \
				 (glTF '%s')" % [
					track_path, expected_track_path, expected_animation, expected, original]))
		return false

	# This test assumes that if the static_collision node was created the -col hint was properly
	# handled (as that behavior should be tested elsewhere).
	_add_result(true, "Expected node '%s' has an associated animation" % expected)
	return true

func _test_animated_alphanumeric_is_unchanged() -> bool:
	var original: String = GLTF_PREFIX_ANIMATED + GLTF_ALPHANUMERIC
	var expected: String = original
	return _check_animated(original, expected)

func _test_animated_allowed_symbols_is_unchanged() -> bool:
	var original: String = GLTF_PREFIX_ANIMATED + GLTF_ALLOWED_SYMBOLS
	var expected_node: String = original
	var expected_animation: String = original.replace(",", "").replace("[", "")
	expected_animation += GLTF_SUFFIX_ANIMATION
	return _check_animated(original, expected_node, expected_animation)

func _test_animated_katakana_is_unchanged() -> bool:
	var original: String = GLTF_PREFIX_ANIMATED + GLTF_KATAKANA
	var expected: String = original
	return _check_animated(original, expected)

func _test_animated_emoji_is_unchanged() -> bool:
	var original: String = GLTF_PREFIX_ANIMATED + GLTF_EMOJI
	var expected: String = original
	return _check_animated(original, expected)

func _test_animated_disallowed_characters_are_stripped() -> bool:
	var original: String = GLTF_PREFIX_ANIMATED + GLTF_DISALLOWED_NONCLASHING
	var expected: String = GLTF_PREFIX_ANIMATED + "Disallowed_Non-clashing_"
	return _check_animated(original, expected)

func _test_animated_disallowed_clashing_nodes_are_uniquified() -> bool:
	var original: String = GLTF_PREFIX_ANIMATED + GLTF_DISALLOWED_CLASHING_1
	var expected: String = GLTF_PREFIX_ANIMATED + "Disallowed_Clashing_"
	if not _check_animated(original, expected):
		return false

	original = GLTF_PREFIX_ANIMATED + GLTF_DISALLOWED_CLASHING_2
	expected = GLTF_PREFIX_ANIMATED + "Disallowed_Clashing_2"
	return _check_animated(original, expected)

func _test_animated_disallowed_nested_nodes_are_uniquified() -> bool:
	var original: String = GLTF_PREFIX_ANIMATED + GLTF_DISALLOWED_CLASHING_DEEP_TREE_1
	var expected_root: String = GLTF_PREFIX_ANIMATED + "Disallowed_Clashing_Deep_Tree_3"
	if not _check_animated(original, expected_root):
		return false

	original = GLTF_PREFIX_ANIMATED + GLTF_DISALLOWED_CLASHING_DEEP_TREE_2
	var expected_mid = (expected_root + "/" +
		GLTF_PREFIX_ANIMATED + "Disallowed_Clashing_Deep_Tree_2")
	if not _check_animated(original, expected_mid):
		return false

	original = GLTF_PREFIX_ANIMATED + GLTF_DISALLOWED_CLASHING_DEEP_TREE_3
	var expected_leaf = (expected_mid + "/" +
		GLTF_PREFIX_ANIMATED + "Disallowed_Clashing_Deep_Tree_")
	return _check_animated(original, expected_leaf)

extends ColorRect

const GOLDEN_GLTF_SCENE: PackedScene = preload("res://gltf/45545-relax-name-sanitization.gltf")

## These constants are the prefix part of the node names used in the golden glTF file.
# Prefix for "basic" nodes, no import hints, no animation, etc...
const GLTF_PREFIX_BASIC: String = "Basic_"

# Prefix for nodes that have an import hint.
const GLTF_PREFIX_HINTED: String = "Hinted_"

# Prefix for nodes that have an associated animation.
const GLTF_PREFIX_ANIMATED: String = "Animated_"

# The suffix for animations (all animations in the golden glTF file use the Blender default suffix
# "Action" and the looping import hint "-loop".
const GLTF_SUFFIX_ANIMATION: String = "Action-loop"

## These constants are the infix part of the node names used in the golden glTF file.
const GLTF_ALPHANUMERIC: String = "Alphanumeric123"
# The last three symbols are:
# https://en.wiktionary.org/wiki/%E3%82%B4#Japanese
# https://en.wiktionary.org/wiki/%E3%83%89#Japanese
# https://en.wiktionary.org/wiki/%E3%83%84#Japanese
const GLTF_KATAKANA: String = "Katakana_ゴドツ"
# The last two symbols are:
# https://emojipedia.org/grinning-face
# https://emojipedia.org/robot/
const GLTF_EMOJI: String = "Emoji_😀🤖"
const GLTF_ALLOWED_SYMBOLS: String = "AllowedSymbols_!#$%^&*()-=[]{}';<>,~"
const GLTF_DISALLOWED_NONCLASHING: String = "Disallowed_Non-clashing_.:@\"/"
const GLTF_DISALLOWED_CLASHING_1: String = "Disallowed_Clashing_."
const GLTF_DISALLOWED_CLASHING_2: String = "Disallowed_Clashing_@"
const GLTF_DISALLOWED_CLASHING_DEEP_TREE_1: String = "Disallowed_Clashing_Deep_Tree_@"
const GLTF_DISALLOWED_CLASHING_DEEP_TREE_2: String = "Disallowed_Clashing_Deep_Tree_@@"
const GLTF_DISALLOWED_CLASHING_DEEP_TREE_3: String = "Disallowed_Clashing_Deep_Tree_@@@"

var output: BoxContainer
var test_scene: Node
var test_animation_player: AnimationPlayer

func _ready():
	output = $ScrollContainer/OutputWindow
	test_scene = GOLDEN_GLTF_SCENE.instance()
	test_animation_player = test_scene.get_node_or_null("AnimationPlayer")
	if not test_animation_player:
		_add_line("ERROR: imported glTF scene has no AnimationPlayer node!", Color.orangered)
		print("FAIL: imported glTF scene has no AnimationPlayer so no tests will be run.")
		return

	var passed_tests: Array = []
	var failed_tests: Array = []

	for method_name in _find_tests():
		_add_test_header(method_name)
		var result: bool = self.call(method_name)
		_add_line(" ")
		if result:
			passed_tests.push_back(method_name)
		else:
			failed_tests.push_back(method_name)

	if failed_tests:
		print("FAIL: %d failed tests:" % len(failed_tests))
		for test in failed_tests:
			print("\t%s" % test)
		print("")
		print("Animations:")
		for animation_name in test_animation_player.get_animation_list():
			print("\t%s" % animation_name)
	else:
		print("PASS: Passed all %d tests" % len(passed_tests))

func _find_tests() -> Array:
	var ret: Array = []
	for method in get_method_list():
		var method_name: String = method.get("name")
		if method_name.begins_with("_test_"):
			ret.push_back(method_name)
	ret.sort()
	return ret

func _add_test_header(method_name: String) -> void:
	_add_line(method_name, Color.royalblue)

func _add_result(succeeded: bool, message: String) -> void:
	var prefix: String = "  [OK] " if succeeded else "  [FAIL] "
	_add_line(prefix + message, Color.forestgreen if succeeded else Color.orangered)

func _add_line(text: String, font_color: Color = Color.black) -> void:
	var line: Label = Label.new()
	line.text = text
	line.add_theme_font_size_override("font_size", 22)
	line.add_theme_color_override("font_color", font_color)
	output.add_child(line)
	print(text)

func _check_basic(original: String, expected: String) -> bool:
	var node: Node = test_scene.get_node_or_null(expected)
	if not node:
		_add_result(
			false,
			"Missing expected Godot node '%s' for glTF node '%s'" % [expected, original])
		return false
	_add_result(true, "Found expected node '%s'" % expected)
	return true

func _test_basic_alphanumeric_is_unchanged() -> bool:
	var original: String = GLTF_PREFIX_BASIC + GLTF_ALPHANUMERIC
	var expected: String = original
	return _check_basic(original, expected)

func _test_basic_allowed_symbols_is_unchanged() -> bool:
	var original: String = GLTF_PREFIX_BASIC + GLTF_ALLOWED_SYMBOLS
	var expected: String = original
	return _check_basic(original, expected)

func _test_basic_katakana_is_unchanged() -> bool:
	var original: String = GLTF_PREFIX_BASIC + GLTF_KATAKANA
	var expected: String = original
	return _check_basic(original, expected)

func _test_basic_emoji_is_unchanged() -> bool:
	var original: String = GLTF_PREFIX_BASIC + GLTF_EMOJI
	var expected: String = original
	return _check_basic(original, expected)

func _test_basic_disallowed_characters_are_stripped() -> bool:
	var original: String = GLTF_PREFIX_BASIC + GLTF_DISALLOWED_NONCLASHING
	var expected: String = GLTF_PREFIX_BASIC + "Disallowed_Non-clashing_"
	return _check_basic(original, expected)

func _test_basic_disallowed_clashing_nodes_are_uniquified() -> bool:
	var original: String = GLTF_PREFIX_BASIC + GLTF_DISALLOWED_CLASHING_1
	var expected: String = GLTF_PREFIX_BASIC + "Disallowed_Clashing_"
	if not _check_basic(original, expected):
		return false

	original = GLTF_PREFIX_BASIC + GLTF_DISALLOWED_CLASHING_2
	expected = GLTF_PREFIX_BASIC + "Disallowed_Clashing_2"
	return _check_basic(original, expected)

func _test_basic_disallowed_nested_nodes_are_uniquified() -> bool:
	var original: String = GLTF_PREFIX_BASIC + GLTF_DISALLOWED_CLASHING_DEEP_TREE_1
	var expected_root: String = GLTF_PREFIX_BASIC + "Disallowed_Clashing_Deep_Tree_"
	if not _check_basic(original, expected_root):
		return false

	original = GLTF_PREFIX_BASIC + GLTF_DISALLOWED_CLASHING_DEEP_TREE_2
	var expected_mid = expected_root + "/" + GLTF_PREFIX_BASIC + "Disallowed_Clashing_Deep_Tree_2"
	if not _check_basic(original, expected_mid):
		return false

	original = GLTF_PREFIX_BASIC + GLTF_DISALLOWED_CLASHING_DEEP_TREE_3
	var expected_leaf = expected_mid + "/" + GLTF_PREFIX_BASIC + "Disallowed_Clashing_Deep_Tree_3"
	return _check_basic(original, expected_leaf)

# Verifies that a node exists with the expected name and that it has a StaticBody3D child.
func _check_hinted_col(original: String, expected: String) -> bool:
	var node: Node = test_scene.get_node_or_null(expected)
	if not node:
		_add_result(
			false,
			"Missing expected Godot node '%s' for glTF node '%s'" % [expected, original])
		return false

	var collision_body: StaticBody3D = node.get_node_or_null("static_collision")
	if not collision_body:
		_add_result(
			false,
			"Missing 'static_collision' child for Godot node '%s' (glTF '%s')" % [
				expected, original])
		return false

	# This test assumes that if the static_collision node was created the -col hint was properly
	# handled (as that behavior should be tested elsewhere).
	_add_result(true, "Expected node '%s' contains a static_collision StaticBody3D" % expected)
	return true

func _test_hinted_alphanumeric_is_unchanged() -> bool:
	var original: String = GLTF_PREFIX_HINTED + GLTF_ALPHANUMERIC
	var expected: String = original
	return _check_hinted_col(original, expected)

func _test_hinted_allowed_symbols_is_unchanged() -> bool:
	var original: String = GLTF_PREFIX_HINTED + GLTF_ALLOWED_SYMBOLS
	var expected: String = original
	return _check_hinted_col(original, expected)

func _test_hinted_katakana_is_unchanged() -> bool:
	var original: String = GLTF_PREFIX_HINTED + GLTF_KATAKANA
	var expected: String = original
	return _check_hinted_col(original, expected)

func _test_hinted_emoji_is_unchanged() -> bool:
	var original: String = GLTF_PREFIX_HINTED + GLTF_EMOJI
	var expected: String = original
	return _check_hinted_col(original, expected)

func _test_hinted_disallowed_characters_are_stripped() -> bool:
	var original: String = GLTF_PREFIX_HINTED + GLTF_DISALLOWED_NONCLASHING
	var expected: String = GLTF_PREFIX_HINTED + "Disallowed_Non-clashing_"
	return _check_hinted_col(original, expected)

func _test_hinted_disallowed_clashing_nodes_are_uniquified() -> bool:
	var original: String = GLTF_PREFIX_HINTED + GLTF_DISALLOWED_CLASHING_1
	var expected: String = GLTF_PREFIX_HINTED + "Disallowed_Clashing_"
	if not _check_hinted_col(original, expected):
		return false

	original = GLTF_PREFIX_HINTED + GLTF_DISALLOWED_CLASHING_2
	expected = GLTF_PREFIX_HINTED + "Disallowed_Clashing_2"
	return _check_hinted_col(original, expected)

func _test_hinted_disallowed_nested_nodes_are_uniquified() -> bool:
	var original: String = GLTF_PREFIX_HINTED + GLTF_DISALLOWED_CLASHING_DEEP_TREE_1
	var expected_root: String = GLTF_PREFIX_HINTED + "Disallowed_Clashing_Deep_Tree_"
	if not _check_hinted_col(original, expected_root):
		return false

	original = GLTF_PREFIX_HINTED + GLTF_DISALLOWED_CLASHING_DEEP_TREE_2
	var expected_mid = expected_root + "/" + GLTF_PREFIX_HINTED + "Disallowed_Clashing_Deep_Tree_2"
	if not _check_hinted_col(original, expected_mid):
		return false

	original = GLTF_PREFIX_HINTED + GLTF_DISALLOWED_CLASHING_DEEP_TREE_3
	var expected_leaf = expected_mid + "/" + GLTF_PREFIX_HINTED + "Disallowed_Clashing_Deep_Tree_3"
	return _check_hinted_col(original, expected_leaf)

# Verifies that a node exists with the expected name and that an associated animation exists.
func _check_animated(original: String, expected: String, expected_animation: String = "") -> bool:
	var node: Node = test_scene.get_node_or_null(expected)
	if not node:
		_add_result(
			false,
			"Missing expected Godot node '%s' for glTF node '%s'" % [expected, original])
		return false

	if expected_animation.is_empty():
		expected_animation = expected + GLTF_SUFFIX_ANIMATION
	var animation: Animation = test_animation_player.get_animation(expected_animation)
	if not animation:
		_add_result(
			false,
			"Missing expected animation '%s' for Godot node '%s' (glTF '%s')" % [
				expected_animation, expected, original])
		return false

	var track_count: int = animation.get_track_count()
	if track_count != 1:
		_add_result(
			false,
			"Track count %d != 1 on animation '%s' for Godot node '%s' (glTF '%s')" % [
				track_count, expected_animation, expected, original])
		return false

	var track_type: int = animation.track_get_type(0)
	if track_type != Animation.TYPE_TRANSFORM:
		_add_result(
			false,
			"Unexpected track type %d on animation '%s' for Godot node '%s' (glTF '%s')" % [
				track_type, expected_animation, expected, original])
		return false

	var track_path: NodePath = animation.track_get_path(0)
	var expected_track_path = NodePath(expected)
	if track_path != expected_track_path:
		_add_result(
			false,
			("Incorrect track_path '%s' (expected '%s') on animation '%s' for Godot node '%s' \
				 (glTF '%s')" % [
					track_path, expected_track_path, expected_animation, expected, original]))
		return false

	# This test assumes that if the static_collision node was created the -col hint was properly
	# handled (as that behavior should be tested elsewhere).
	_add_result(true, "Expected node '%s' has an associated animation" % expected)
	return true

func _test_animated_alphanumeric_is_unchanged() -> bool:
	var original: String = GLTF_PREFIX_ANIMATED + GLTF_ALPHANUMERIC
	var expected: String = original
	return _check_animated(original, expected)

func _test_animated_allowed_symbols_is_unchanged() -> bool:
	var original: String = GLTF_PREFIX_ANIMATED + GLTF_ALLOWED_SYMBOLS
	var expected_node: String = original
	var expected_animation: String = original.replace(",", "").replace("[", "")
	expected_animation += GLTF_SUFFIX_ANIMATION
	return _check_animated(original, expected_node, expected_animation)

func _test_animated_katakana_is_unchanged() -> bool:
	var original: String = GLTF_PREFIX_ANIMATED + GLTF_KATAKANA
	var expected: String = original
	return _check_animated(original, expected)

func _test_animated_emoji_is_unchanged() -> bool:
	var original: String = GLTF_PREFIX_ANIMATED + GLTF_EMOJI
	var expected: String = original
	return _check_animated(original, expected)

func _test_animated_disallowed_characters_are_stripped() -> bool:
	var original: String = GLTF_PREFIX_ANIMATED + GLTF_DISALLOWED_NONCLASHING
	var expected: String = GLTF_PREFIX_ANIMATED + "Disallowed_Non-clashing_"
	return _check_animated(original, expected)

func _test_animated_disallowed_clashing_nodes_are_uniquified() -> bool:
	var original: String = GLTF_PREFIX_ANIMATED + GLTF_DISALLOWED_CLASHING_1
	var expected: String = GLTF_PREFIX_ANIMATED + "Disallowed_Clashing_"
	if not _check_animated(original, expected):
		return false

	original = GLTF_PREFIX_ANIMATED + GLTF_DISALLOWED_CLASHING_2
	expected = GLTF_PREFIX_ANIMATED + "Disallowed_Clashing_2"
	return _check_animated(original, expected)

func _test_animated_disallowed_nested_nodes_are_uniquified() -> bool:
	var original: String = GLTF_PREFIX_ANIMATED + GLTF_DISALLOWED_CLASHING_DEEP_TREE_1
	var expected_root: String = GLTF_PREFIX_ANIMATED + "Disallowed_Clashing_Deep_Tree_"
	if not _check_animated(original, expected_root):
		return false

	original = GLTF_PREFIX_ANIMATED + GLTF_DISALLOWED_CLASHING_DEEP_TREE_2
	var expected_mid = (expected_root + "/" +
		GLTF_PREFIX_ANIMATED + "Disallowed_Clashing_Deep_Tree_2")
	if not _check_animated(original, expected_mid):
		return false

	original = GLTF_PREFIX_ANIMATED + GLTF_DISALLOWED_CLASHING_DEEP_TREE_3
	var expected_leaf = (expected_mid + "/" +
		GLTF_PREFIX_ANIMATED + "Disallowed_Clashing_Deep_Tree_3")
	return _check_animated(original, expected_leaf)


basic add/rm block functionality

basic add/rm block functionality

extends Spatial

var puzzleScn = preload("res://puzzle.scn")
var cursorScn = preload("res://cursor.scn")

var cursorPos = Vector3(1,6,0)
var cursor
var puzzle = puzzleScn.instance()
var puzzleMan = puzzle.puzzleMan
var gridMan
var selectedBlock = null

var gui = {
	add=Button.new(),
	rm=Button.new(),
	save_pzl=Button.new(),
	load_pzl=Button.new()
}
var gui_text = {
	add="Add Block",
	rm="Remove Block",
	save_pzl="Save",
	load_pzl="Load"
}

func cursor_move(dir):
	var tween = Tween.new()
	var N = 2
	add_child(tween)
	tween.interpolate_method( cursor, "set_global_transform", \
		cursor.get_global_transform(), cursor.get_global_transform().translated(dir * N), \
		0.25, Tween.TRANS_EXPO, Tween.EASE_OUT )
	tween.start()
	cursorPos += dir
	selectedBlock = gridMan.get_block(cursorPos / 2)

func _input(ev):
	if ev.type == InputEvent.KEY:
		if ev.is_pressed():
			if Input.is_action_pressed("ui_up"):
				cursor_move(Vector3(0,1,0))
			if Input.is_action_pressed("ui_down"):
				cursor_move(Vector3(0,-1,0))
			if Input.is_action_pressed("ui_left"):
				cursor_move(Vector3(1,0,0))
			if Input.is_action_pressed("ui_right"):
				cursor_move(Vector3(-1,0,0))
			if Input.is_action_pressed("ui_page_up"):
				cursor_move(Vector3(0,0,1))
			if Input.is_action_pressed("ui_page_down"):
				cursor_move(Vector3(0,0,-1))
			if Input.is_action_pressed("ui_accept"):
				delete_block()

func delete_block():
	if not selectedBlock == null:
		gridMan.remove_block(selectedBlock)
	selectedBlock = null

func cursor_action():
	if not selectedBlock == null:
		print("CHILDREN:",gridMan.get_child_count())
		print("CHILDREN:",gridMan.get_child_count())
		delete_block()
		selectedBlock = null

func add_block():
	if gridMan.get_block(cursorPos) != null:
		return

	var pickled = preload("PuzzleManager.gd").PickledBlock.new()
	pickled.blockPos = cursorPos * 2
	pickled.name = 300

	pickled.setBlockClass( preload("PuzzleManager.gd").BLOCK_PAIR) \
					.setTextureName("Red")

	var n = pickled.toNode()

	gridMan.add_block(n)
	print(n.name, gridMan.get_node(n.name))
	gridMan.print_tree()
	gridMan.get_node(n.name) \
			.set_translation(pickled.blockPos)

func _ready():
	gridMan = puzzle.get_node("GridView/GridMan")
	cursor = cursorScn.instance()
	puzzle.mainPuzzle = false
	puzzle.time.on = false
	puzzle.set_as_toplevel(true)

	var pos = Vector2(10, 10)
	for k in gui.keys():
		pos.y += 45
		gui[k].set_theme(preload("res://themes/MainTheme.thm"))
		gui[k].set_text(gui_text[k])
		gui[k].set_pos(pos)
		add_child(gui[k]);
	gui.add.connect("pressed", self, "add_block")
	gui.rm.connect("pressed", self, "delete_block")

	gridMan.add_child(cursor)

	add_child(puzzle)
	cursor.set_owner(puzzle)

	set_process_input(true)

extends Spatial

var cursorPos = Vector3(1,0,0)
var cursor
var puzzleMan
var puzzle
var gridMan
var selectedBlock = null

var gui = {
	add=Button.new(),
	rm=Button.new(),
	save_pzl=Button.new(),
	load_pzl=Button.new()
}
var gui_text = {
	add="Add Block",
	rm="Remove Block",
	save_pzl="Save",
	load_pzl="Load"
}

var puzzleScn = preload("res://puzzle.scn")
var cursorScn = preload("res://cursor.scn")

func cursor_move(dir):
	var tween = Tween.new()
	var N = 2
	add_child(tween)
	tween.interpolate_method( cursor, "set_global_transform", \
		cursor.get_global_transform(), cursor.get_global_transform().translated(dir * N), \
		0.25, Tween.TRANS_EXPO, Tween.EASE_OUT )
	tween.start()
	cursorPos += dir
	selectedBlock = gridMan.get_block(cursorPos)

func _input(ev):
	if ev.type == InputEvent.KEY:
		if ev.is_pressed():
			if Input.is_action_pressed("ui_up"):
				cursor_move(Vector3(0,1,0))
			if Input.is_action_pressed("ui_down"):
				cursor_move(Vector3(0,-1,0))
			if Input.is_action_pressed("ui_left"):
				cursor_move(Vector3(1,0,0))
			if Input.is_action_pressed("ui_right"):
				cursor_move(Vector3(-1,0,0))
			if Input.is_action_pressed("ui_page_up"):
				cursor_move(Vector3(0,0,1))
			if Input.is_action_pressed("ui_page_down"):
				cursor_move(Vector3(0,0,-1))
			if Input.is_action_pressed("ui_accept"):
				cursor_action()

func cursor_action():
	if not selectedBlock == null:
		print("CHILDREN:",gridMan.get_child_count())
		gridMan.remove_block(selectedBlock)
		print("CHILDREN:",gridMan.get_child_count())

		selectedBlock = null

func _ready():
	var pMan = puzzleScn.instance()
	gridMan = pMan.get_node("GridView/GridMan")
	cursor = cursorScn.instance()
	pMan.mainPuzzle = false
	pMan.time.on = false
	puzzleMan = pMan.puzzleMan
	pMan.set_as_toplevel(true)

	var pos = Vector2(10, 10)
	for k in gui.keys():
		pos.y += 45
		gui[k].set_theme(preload("res://themes/MainTheme.thm"))
		gui[k].set_text(gui_text[k])
		gui[k].set_pos(pos)
		add_child(gui[k]);

	gridMan.add_child(cursor)
	add_child(pMan)
	cursor.set_owner(pMan)

	set_process_input(true)


Add reponsive section implementation

Add reponsive section implementation

tool
extends Control

var ResponsiveSizing = preload("ResponsiveSizing.gd")
var sizingNodes = [ ]
var parentControl

func _ready():
	refreshParent()
	refreshSizings()
	processParentResizing()

# Function to refresh the parentControl with the parent Control node of the current node
# NOTE - This function should be manually called if this node is either unparented / reparented
func refreshParent():
	parentControl = get_parent()
	if parentControl != null && parentControl extends Control:
		parentControl.connect("resized", self, "processParentResizing")
	else:
		parentControl = null

# Dynamically chooses the Responsive Sizing node that better relates to the current size of the
# parent Control and applies it
func processParentResizing():
	if parentControl != null:
		var parentSize = parentControl.get_size()
		for sizingNode in sizingNodes:
			if sizingNode.isEligible(parentSize):
				sizingNode.applyTo(self)
				break

# Function to refresh the sizingNodes array with all the Responsive Sizing children
# NOTE - This function should be manually called if the children of the current node are changed
#        (e.g. add or remove Responsive Sizing children)
func refreshSizings():
	sizingNodes.clear()
	for child in get_children():
		if child extends ResponsiveSizing:
			sizingNodes.append(child)
	sizingNodes.sort_custom(self, "sortSizingNodes")

# Sorts the Responsive Sizing children nodes from largest to smallest (prioritizing width)
func sortSizingNodes(node1, node2):
	var nodeSize1 = node1.get_size()
	var nodeSize2 = node2.get_size()
	if nodeSize1.width != nodeSize2.width:
		return nodeSize1.width > nodeSize2.width
	return nodeSize1.height > nodeSize2.height

tool
extends Control

func _ready():
	pass


comment on "times_jumped" variable

comment on "times_jumped" variable

extends RigidBody2D

# Character Demo, written by Juan Linietsky.
#
# Implementation of a 2D Character controller.
# This implementation uses the physics engine for
# controlling a character, in a very similar way
# than a 3D character controller would be implemented.
#
# Using the physics engine for this has the main
# advantages:
# -Easy to write.
# -Interaction with other physics-based objects is free
# -Only have to deal with the object linear velocity, not position
# -All collision/area framework available
#
# But also has the following disadvantages:
#
# -Objects may bounce a little bit sometimes
# -Going up ramps sends the chracter flying up, small hack is needed.
# -A ray collider is needed to avoid sliding down on ramps and
#   undesiderd bumps, small steps and rare numerical precision errors.
#   (another alternative may be to turn on friction when the character is not moving).
# -Friction cant be used, so floor velocity must be considered
#  for moving platforms.

# Member variables
var anim = ""
var siding_left = false
var jumping = false
var stopping_jump = false
var shooting = false

var WALK_ACCEL = 800.0
var WALK_DEACCEL = 800.0
var WALK_MAX_VELOCITY = 200.0
var AIR_ACCEL = 200.0
var AIR_DEACCEL = 200.0
var JUMP_VELOCITY = 480
var STOP_JUMP_FORCE = 900.0

var MAX_FLOOR_AIRBORNE_TIME = 0.15

var airborne_time = 1e20
var shoot_time = 1e20

var MAX_SHOOT_POSE_TIME = 0.3

var bullet = preload("res://src/actors/player/bullet.tscn")

var floor_h_velocity = 0.0
var enemy

#func beam_to(spawner):

#	var spawnerpos = get_node(spawner).get_global_pos()
#	print("Beam to active!")
#	self.set_pos(spawnerpos)

func _integrate_forces(s):
	var lv = s.get_linear_velocity()
	var step = s.get_step()

	var new_anim = anim
	var new_siding_left = siding_left

	# Get the controls
	var move_left = Input.is_action_pressed("move_left")
	var move_right = Input.is_action_pressed("move_right")
	var jump = Input.is_action_pressed("jump")
	var shoot = Input.is_action_pressed("shoot")
	var spawn = Input.is_action_pressed("spawn")

	if spawn:
		var e = enemy.instance()
		var p = get_pos()
		p.y = p.y - 100
		e.set_pos(p)
		get_parent().add_child(e)

	# Deapply prev floor velocity
	lv.x -= floor_h_velocity
	floor_h_velocity = 0.0

	# Find the floor (a contact with upwards facing collision normal)
	var found_floor = false
	var floor_index = -1

	for x in range(s.get_contact_count()):
		var ci = s.get_contact_local_normal(x)
		if (ci.dot(Vector2(0, -1)) > 0.6):
			found_floor = true
			floor_index = x

	# A good idea when impementing characters of all kinds,
	# compensates for physics imprecission, as well as human reaction delay.
	if (shoot and not shooting):
		shoot_time = 0
		var bi = bullet.instance()
		var ss
		if (siding_left):
			ss = -1.0
		else:
			ss = 1.0
		var pos = get_pos() + get_node("bullet_shoot").get_pos()*Vector2(ss, 1.0)

		bi.set_pos(pos)
		get_parent().add_child(bi)

		bi.set_linear_velocity(Vector2(800.0*ss, -80))
		get_node("sprite/smoke").set_emitting(true)
		get_node("sfx").play("shoot")
		PS2D.body_add_collision_exception(bi.get_rid(), get_rid()) # Make bullet and this not collide
	else:
		shoot_time += step

	if (found_floor):
		airborne_time = 0.0
	else:
		airborne_time += step # Time it spent in the air

	var on_floor = airborne_time < MAX_FLOOR_AIRBORNE_TIME

	# Process jump
	if (jumping):
		if (lv.y > 0):
			# Set off the jumping flag if going down
			jumping = false

		elif (not jump):
			stopping_jump = true

		if (stopping_jump):
			lv.y += STOP_JUMP_FORCE*step

	if (on_floor):
		# Process logic when character is on floor
		if (move_left and not move_right):
			if (lv.x > -WALK_MAX_VELOCITY):
				lv.x -= WALK_ACCEL*step
		elif (move_right and not move_left):
			if (lv.x < WALK_MAX_VELOCITY):
				lv.x += WALK_ACCEL*step
		else:
			var xv = abs(lv.x)
			xv -= WALK_DEACCEL*step
			if (xv < 0):
				xv = 0
			lv.x = sign(lv.x)*xv

		# Check jump
		if (not jumping and jump):
			lv.y = -JUMP_VELOCITY
			jumping = true
			stopping_jump = false
			get_node("sfx").play("jump")

			# THIS IS FOR FUTURE USE - Its a statistics thing for players to see like "Hey you only jumped 20 times during the whole game..."
			# Whatever use it is, i think its a fun element to talk about :D
			# I don't care often about the "use" of things... just having it for fun is good enough ;)

			global.times_jumped = global.times_jumped +1
			print(global.times_jumped)

		# Check siding
		if (lv.x < 0 and move_left):
			new_siding_left = true
		elif (lv.x > 0 and move_right):
			new_siding_left = false
		if (jumping):
			new_anim = "jumping"
		elif (abs(lv.x) < 0.1):
			if (shoot_time < MAX_SHOOT_POSE_TIME):
				new_anim = "idle_weapon"
			else:
				new_anim = "idle"
		else:
			if (shoot_time < MAX_SHOOT_POSE_TIME):
				new_anim = "run_weapon"
			else:
				new_anim = "run"
	else:
		# Process logic when the character is in the air
		if (move_left and not move_right):
			if (lv.x > -WALK_MAX_VELOCITY):
				lv.x -= AIR_ACCEL*step
		elif (move_right and not move_left):
			if (lv.x < WALK_MAX_VELOCITY):
				lv.x += AIR_ACCEL*step
		else:
			var xv = abs(lv.x)
			xv -= AIR_DEACCEL*step
			if (xv < 0):
				xv = 0
			lv.x = sign(lv.x)*xv

		if (lv.y < 0):
			if (shoot_time < MAX_SHOOT_POSE_TIME):
				new_anim = "jumping_weapon"
			else:
				new_anim = "jumping"
		else:
			if (shoot_time < MAX_SHOOT_POSE_TIME):
				new_anim = "falling_weapon"

			else:
				new_anim = "falling"

	# Update siding
	if (new_siding_left != siding_left):
		if (new_siding_left):
			get_node("sprite").set_scale(Vector2(-1, 1))
		else:
			get_node("sprite").set_scale(Vector2(1, 1))

		siding_left = new_siding_left

	# Change animation
	if (new_anim != anim):
		anim = new_anim
		get_node("anim").play(anim)

	shooting = shoot

	# Apply floor velocity
	if (found_floor):
		floor_h_velocity = s.get_contact_collider_velocity_at_pos(floor_index).x
		lv.x += floor_h_velocity

	# Finally, apply gravity and set back the linear velocity
	lv += s.get_total_gravity()*step
	s.set_linear_velocity(lv)

func _ready():
	enemy = ResourceLoader.load("res://scenes/scn3-forest/enemy.tscn")

#	if !Globals.has_singleton("Facebook"):
#		return
#	var Facebook = Globals.get_singleton("Facebook")
#	var link = Globals.get("facebook/link")
#	var icon = Globals.get("facebook/icon")
#	var msg = "I just sneezed on your wall! Beat my score and Stop the Running nose!"
#	var title = "I just sneezed on your wall!"
#	Facebook.post("feed", msg, title, link, icon)

extends RigidBody2D

# Character Demo, written by Juan Linietsky.
#
# Implementation of a 2D Character controller.
# This implementation uses the physics engine for
# controlling a character, in a very similar way
# than a 3D character controller would be implemented.
#
# Using the physics engine for this has the main
# advantages:
# -Easy to write.
# -Interaction with other physics-based objects is free
# -Only have to deal with the object linear velocity, not position
# -All collision/area framework available
#
# But also has the following disadvantages:
#
# -Objects may bounce a little bit sometimes
# -Going up ramps sends the chracter flying up, small hack is needed.
# -A ray collider is needed to avoid sliding down on ramps and
#   undesiderd bumps, small steps and rare numerical precision errors.
#   (another alternative may be to turn on friction when the character is not moving).
# -Friction cant be used, so floor velocity must be considered
#  for moving platforms.

# Member variables
var anim = ""
var siding_left = false
var jumping = false
var stopping_jump = false
var shooting = false

var WALK_ACCEL = 800.0
var WALK_DEACCEL = 800.0
var WALK_MAX_VELOCITY = 200.0
var AIR_ACCEL = 200.0
var AIR_DEACCEL = 200.0
var JUMP_VELOCITY = 480
var STOP_JUMP_FORCE = 900.0

var MAX_FLOOR_AIRBORNE_TIME = 0.15

var airborne_time = 1e20
var shoot_time = 1e20

var MAX_SHOOT_POSE_TIME = 0.3

var bullet = preload("res://src/actors/player/bullet.tscn")

var floor_h_velocity = 0.0
var enemy

#func beam_to(spawner):

#	var spawnerpos = get_node(spawner).get_global_pos()
#	print("Beam to active!")
#	self.set_pos(spawnerpos)

func _integrate_forces(s):
	var lv = s.get_linear_velocity()
	var step = s.get_step()

	var new_anim = anim
	var new_siding_left = siding_left

	# Get the controls
	var move_left = Input.is_action_pressed("move_left")
	var move_right = Input.is_action_pressed("move_right")
	var jump = Input.is_action_pressed("jump")
	var shoot = Input.is_action_pressed("shoot")
	var spawn = Input.is_action_pressed("spawn")

	if spawn:
		var e = enemy.instance()
		var p = get_pos()
		p.y = p.y - 100
		e.set_pos(p)
		get_parent().add_child(e)

	# Deapply prev floor velocity
	lv.x -= floor_h_velocity
	floor_h_velocity = 0.0

	# Find the floor (a contact with upwards facing collision normal)
	var found_floor = false
	var floor_index = -1

	for x in range(s.get_contact_count()):
		var ci = s.get_contact_local_normal(x)
		if (ci.dot(Vector2(0, -1)) > 0.6):
			found_floor = true
			floor_index = x

	# A good idea when impementing characters of all kinds,
	# compensates for physics imprecission, as well as human reaction delay.
	if (shoot and not shooting):
		shoot_time = 0
		var bi = bullet.instance()
		var ss
		if (siding_left):
			ss = -1.0
		else:
			ss = 1.0
		var pos = get_pos() + get_node("bullet_shoot").get_pos()*Vector2(ss, 1.0)

		bi.set_pos(pos)
		get_parent().add_child(bi)

		bi.set_linear_velocity(Vector2(800.0*ss, -80))
		get_node("sprite/smoke").set_emitting(true)
		get_node("sfx").play("shoot")
		PS2D.body_add_collision_exception(bi.get_rid(), get_rid()) # Make bullet and this not collide
	else:
		shoot_time += step

	if (found_floor):
		airborne_time = 0.0
	else:
		airborne_time += step # Time it spent in the air

	var on_floor = airborne_time < MAX_FLOOR_AIRBORNE_TIME

	# Process jump
	if (jumping):
		if (lv.y > 0):
			# Set off the jumping flag if going down
			jumping = false

		elif (not jump):
			stopping_jump = true

		if (stopping_jump):
			lv.y += STOP_JUMP_FORCE*step

	if (on_floor):
		# Process logic when character is on floor
		if (move_left and not move_right):
			if (lv.x > -WALK_MAX_VELOCITY):
				lv.x -= WALK_ACCEL*step
		elif (move_right and not move_left):
			if (lv.x < WALK_MAX_VELOCITY):
				lv.x += WALK_ACCEL*step
		else:
			var xv = abs(lv.x)
			xv -= WALK_DEACCEL*step
			if (xv < 0):
				xv = 0
			lv.x = sign(lv.x)*xv

		# Check jump
		if (not jumping and jump):
			lv.y = -JUMP_VELOCITY
			jumping = true
			stopping_jump = false
			get_node("sfx").play("jump")
			global.times_jumped = global.times_jumped +1
			print(global.times_jumped)

		# Check siding
		if (lv.x < 0 and move_left):
			new_siding_left = true
		elif (lv.x > 0 and move_right):
			new_siding_left = false
		if (jumping):
			new_anim = "jumping"
		elif (abs(lv.x) < 0.1):
			if (shoot_time < MAX_SHOOT_POSE_TIME):
				new_anim = "idle_weapon"
			else:
				new_anim = "idle"
		else:
			if (shoot_time < MAX_SHOOT_POSE_TIME):
				new_anim = "run_weapon"
			else:
				new_anim = "run"
	else:
		# Process logic when the character is in the air
		if (move_left and not move_right):
			if (lv.x > -WALK_MAX_VELOCITY):
				lv.x -= AIR_ACCEL*step
		elif (move_right and not move_left):
			if (lv.x < WALK_MAX_VELOCITY):
				lv.x += AIR_ACCEL*step
		else:
			var xv = abs(lv.x)
			xv -= AIR_DEACCEL*step
			if (xv < 0):
				xv = 0
			lv.x = sign(lv.x)*xv

		if (lv.y < 0):
			if (shoot_time < MAX_SHOOT_POSE_TIME):
				new_anim = "jumping_weapon"
			else:
				new_anim = "jumping"
		else:
			if (shoot_time < MAX_SHOOT_POSE_TIME):
				new_anim = "falling_weapon"

			else:
				new_anim = "falling"

	# Update siding
	if (new_siding_left != siding_left):
		if (new_siding_left):
			get_node("sprite").set_scale(Vector2(-1, 1))
		else:
			get_node("sprite").set_scale(Vector2(1, 1))

		siding_left = new_siding_left

	# Change animation
	if (new_anim != anim):
		anim = new_anim
		get_node("anim").play(anim)

	shooting = shoot

	# Apply floor velocity
	if (found_floor):
		floor_h_velocity = s.get_contact_collider_velocity_at_pos(floor_index).x
		lv.x += floor_h_velocity

	# Finally, apply gravity and set back the linear velocity
	lv += s.get_total_gravity()*step
	s.set_linear_velocity(lv)

func _ready():
	enemy = ResourceLoader.load("res://scenes/scn3-forest/enemy.tscn")

#	if !Globals.has_singleton("Facebook"):
#		return
#	var Facebook = Globals.get_singleton("Facebook")
#	var link = Globals.get("facebook/link")
#	var icon = Globals.get("facebook/icon")
#	var msg = "I just sneezed on your wall! Beat my score and Stop the Running nose!"
#	var title = "I just sneezed on your wall!"
#	Facebook.post("feed", msg, title, link, icon)


Fix set window size on startup.

Fix set window size on startup.

# main node to start game with, holds background, does ship spawing
extends Node2D

func _ready():
	print ("reset everything - new game")

	# set screen width
	OS.window_size = Vector2(settings.game['display_width'], settings.game['display_height'])

	# add camera
	var camera_scn = load(global.scene_path_camera)
	var camera_node = camera_scn.instance()
	add_child(camera_node, true)

	call_deferred("initialize")

	# when client starts, clear the game
	network_manager.connect("connected_as_client", self , "clear_game")

	#get_node(global.scene_tree_game).clear_game()

func initialize():
	for i in range(1): spawn_enemy()

	#for i in range(1): spawn_tower()

	#for i in range(2): spawn_pickup()

	for i in range(2): spawn_object(global.scene_path_asteroid, "objects")
	for i in range(4): spawn_object(global.scene_path_comet, "objects")

	# Background node
	# player_manager node

# client > spawn_player(input) > spawn_object > rpc spawn_player(network)
#												 server spawn_object > rpc spawn_object
#																		client spawn_object

remote func spawn_player(processor, device_details):
	print ("game > spawn_player > ", processor)
#	if get_tree().has_meta("network_peer") and processor == "Network" and device_details[0] == get_tree().get_network_unique_id():
#		print("skip this? processor: ", processor , " networkid: ", get_tree().get_network_unique_id())

	var player_node = spawn_object(global.scene_path_player, "ships", "", processor != "Network")

	print ("playername ", player_node.get_name())

	player_node.get_node("processor_selector").set_processor(processor)
	player_node.get_node("processor_selector").set_processor_details(device_details)

	if get_tree().has_meta("network_peer"):
		# only local players are net_work_masters
		if processor == 'Input':
			var selfPeerID = get_tree().get_network_unique_id()
			#player_node.set_name(player_node.get_name() +" "+ str(selfPeerID))
			player_node.set_network_master(selfPeerID) # Will be explained later
			if !get_tree().is_network_server():
				print ("game > spawn_player > not master!")
				rpc("spawn_player", "Network", [selfPeerID, player_node.get_name()])
		elif processor == 'Network':
			player_node.set_network_master(device_details[0]) # Will be explained later

	return player_node

func spawn_enemy():
	return spawn_object(global.scene_path_enemy, "ships")

func spawn_tower():
	return spawn_object(global.scene_path_tower, "ships")

func spawn_pickup():
	return spawn_object(global.scene_path_pickup, "objects")

# spawns an object in to the game
# can be path or packedScene
remote func spawn_object(scn, group, name = "", propagate = true):
	print ("spawn_object ", scn, " in " , group)
	var scn_instance
	var scn_path
#	if not scn is PackedScene:
	scn_instance = load(scn)
	scn_path = scn
#
#	else:
#		scn_instance = scn
#		scn_path = scn.resource_path

	var node = scn_instance.instance()
	# name should only be set, when propagete is false
	if name != "":
		node.set_name(name)
	get_node("set/" + group).add_child(node, true)
	node.scene_path = scn_path

	if(propagate):
		if network_manager.is_server():
			rpc("spawn_object", scn_path, group, node.get_name())

	return node

func clear_game():
	for group in object_locator.objects_registered:
		for i in range(object_locator.objects_registered[group].size()-1, -1, -1):
			var obj = object_locator.objects_registered[group][i]
		#for obj in object_locator.objects_registered[group]:
			#var obj = object_locator.objects_registered[group][o]
			#print ("objects_registered - group: ", group, " i ", i, " val ",  object_locator.objects_registered[group])
			var destroyable = interface.is_destroyable(obj)
			object_locator.free_object(obj)
			if destroyable :
				destroyable.destroy(null, true)
			else:
				obj.queue_free()

	# http://www.gamefromscratch.com/post/2015/02/23/Godot-Engine-Tutorial-Part-6-Multiple-Scenes-and-Global-Variables.aspx
#
#	func _ready():
#	   #On load set the current scene to the last scene available
#	   currentScene = get_tree().get_root().get_child(get_tree().get_root().get_child_count() -1)
#	   #Demonstrate setting a global variable.
#	   Globals.set("MAX_POWER_LEVEL",9000)
#
#	# create a function to switch between scenes
#	func setScene(scene):
#	   #clean up the current scene
#	   currentScene.queue_free()
#	   #load the file passed in as the param "scene"
#	   var s = ResourceLoader.load(scene)
#	   #create an instance of our scene
#	   currentScene = s.instance()
#	   # add scene to root
#	   get_tree().get_root().add_child(currentScene)
#

# main node to start game with, holds background, does ship spawing
extends Node2D

func _ready():
	print ("reset everything - new game")

	# set screen width
	# this is strange
	# get_tree().get_root().size = Vector2(settings.game['display_width'], settings.game['display_height'])

	# add camera
	var camera_scn = load(global.scene_path_camera)
	var camera_node = camera_scn.instance()
	add_child(camera_node, true)

	call_deferred("initialize")

	# when client starts, clear the game
	network_manager.connect("connected_as_client", self , "clear_game")

	#get_node(global.scene_tree_game).clear_game()

func initialize():
	for i in range(1): spawn_enemy()

	#for i in range(1): spawn_tower()

	#for i in range(2): spawn_pickup()

	for i in range(2): spawn_object(global.scene_path_asteroid, "objects")
	for i in range(4): spawn_object(global.scene_path_comet, "objects")

	# Background node
	# player_manager node

# client > spawn_player(input) > spawn_object > rpc spawn_player(network)
#												 server spawn_object > rpc spawn_object
#																		client spawn_object

remote func spawn_player(processor, device_details):
	print ("game > spawn_player > ", processor)
#	if get_tree().has_meta("network_peer") and processor == "Network" and device_details[0] == get_tree().get_network_unique_id():
#		print("skip this? processor: ", processor , " networkid: ", get_tree().get_network_unique_id())

	var player_node = spawn_object(global.scene_path_player, "ships", "", processor != "Network")

	print ("playername ", player_node.get_name())

	player_node.get_node("processor_selector").set_processor(processor)
	player_node.get_node("processor_selector").set_processor_details(device_details)

	if get_tree().has_meta("network_peer"):
		# only local players are net_work_masters
		if processor == 'Input':
			var selfPeerID = get_tree().get_network_unique_id()
			#player_node.set_name(player_node.get_name() +" "+ str(selfPeerID))
			player_node.set_network_master(selfPeerID) # Will be explained later
			if !get_tree().is_network_server():
				print ("game > spawn_player > not master!")
				rpc("spawn_player", "Network", [selfPeerID, player_node.get_name()])
		elif processor == 'Network':
			player_node.set_network_master(device_details[0]) # Will be explained later

	return player_node

func spawn_enemy():
	return spawn_object(global.scene_path_enemy, "ships")

func spawn_tower():
	return spawn_object(global.scene_path_tower, "ships")

func spawn_pickup():
	return spawn_object(global.scene_path_pickup, "objects")

# spawns an object in to the game
# can be path or packedScene
remote func spawn_object(scn, group, name = "", propagate = true):
	print ("spawn_object ", scn, " in " , group)
	var scn_instance
	var scn_path
#	if not scn is PackedScene:
	scn_instance = load(scn)
	scn_path = scn
#
#	else:
#		scn_instance = scn
#		scn_path = scn.resource_path

	var node = scn_instance.instance()
	# name should only be set, when propagete is false
	if name != "":
		node.set_name(name)
	get_node("set/" + group).add_child(node, true)
	node.scene_path = scn_path

	if(propagate):
		if network_manager.is_server():
			rpc("spawn_object", scn_path, group, node.get_name())

	return node

func clear_game():
	for group in object_locator.objects_registered:
		for i in range(object_locator.objects_registered[group].size()-1, -1, -1):
			var obj = object_locator.objects_registered[group][i]
		#for obj in object_locator.objects_registered[group]:
			#var obj = object_locator.objects_registered[group][o]
			#print ("objects_registered - group: ", group, " i ", i, " val ",  object_locator.objects_registered[group])
			var destroyable = interface.is_destroyable(obj)
			object_locator.free_object(obj)
			if destroyable :
				destroyable.destroy(null, true)
			else:
				obj.queue_free()

	# http://www.gamefromscratch.com/post/2015/02/23/Godot-Engine-Tutorial-Part-6-Multiple-Scenes-and-Global-Variables.aspx
#
#	func _ready():
#	   #On load set the current scene to the last scene available
#	   currentScene = get_tree().get_root().get_child(get_tree().get_root().get_child_count() -1)
#	   #Demonstrate setting a global variable.
#	   Globals.set("MAX_POWER_LEVEL",9000)
#
#	# create a function to switch between scenes
#	func setScene(scene):
#	   #clean up the current scene
#	   currentScene.queue_free()
#	   #load the file passed in as the param "scene"
#	   var s = ResourceLoader.load(scene)
#	   #create an instance of our scene
#	   currentScene = s.instance()
#	   # add scene to root
#	   get_tree().get_root().add_child(currentScene)
#


Decreased level exit detection range

Decreased level exit detection range

extends "res://scripts/moving_object.gd"

var player_id
var is_playing = false
var is_alive = true
var attack_range = 100
var attack_width = PI * 0.33
var attack_strength = 1
var attack_cooldown = 0.4
var is_attack_on_cooldown = false
var is_attacking = false
var blast

var target_cone
var target_cone_vector = [0, 0]
var target_cone_angle = 0.0

var panel
var hp_cap = 16
var attack_move_velocity = 20
var normal_velocity = 200

var EXIT_THRESHOLD = 35
var LEVEL_EXIT_THRESHOLD = 8

func _init(bag, player_id).(bag):
    self.bag = bag
    self.player_id = player_id
    self.velocity = 200
    self.attack_move_velocity = 20
    self.normal_velocity = 200
    self.hp = 10
    self.max_hp = 10
    self.score = 0
    self.hit_protection = true
    self.invulnerability_period = 1
    self.avatar = preload("res://scenes/player/player.xscn").instance()
    self.body_part_head = self.avatar.get_node('head')
    self.hat = self.body_part_head.get_node('hat')
    self.body_part_body = self.avatar.get_node('body')
    self.body_part_footer = self.avatar.get_node('footer')
    self.target_cone = self.avatar.get_node('attack_cone')
    self.animations = self.avatar.get_node('body_animations')
    self.hit_particles = self.avatar.get_node('hitparticles')
    self.blast = self.avatar.get_node('blast_animations')

    if not self.bag.input.arcade:
        self.bind_gamepad(player_id)
    else:
        self.bind_arcade(player_id)
    self.panel = self.bag.hud.bind_player_panel(player_id)
    self.hat.set_frame(player_id)
    self.update_bars()

    self.sounds['hit'] = 'player_hit'
    self.sounds['die'] = 'player_die'
    self.sounds['attack1'] = 'player_attack1'
    self.sounds['attack2'] = 'player_attack2'

func bind_arcade(player):
    var arcade = self.bag.input.devices['arcade']
    if player == 0:
        arcade.register_handler(preload("res://scripts/input/handlers/player_enter_game_arcade.gd").new(self.bag, self, 14))
        arcade.register_handler(preload("res://scripts/input/handlers/player_enter_game_arcade.gd").new(self.bag, self, 0))
        arcade.register_handler(preload("res://scripts/input/handlers/player_move_arcade.gd").new(self.bag, self, 1, 13, -1))
        arcade.register_handler(preload("res://scripts/input/handlers/player_move_arcade.gd").new(self.bag, self, 1, 12, 1))
        arcade.register_handler(preload("res://scripts/input/handlers/player_move_arcade.gd").new(self.bag, self, 0, 11, -1))
        arcade.register_handler(preload("res://scripts/input/handlers/player_move_arcade.gd").new(self.bag, self, 0, 10, 1))
        arcade.register_handler(preload("res://scripts/input/handlers/player_cone_arcade.gd").new(self.bag, self, 1, 13, -1))
        arcade.register_handler(preload("res://scripts/input/handlers/player_cone_arcade.gd").new(self.bag, self, 1, 12, 1))
        arcade.register_handler(preload("res://scripts/input/handlers/player_cone_arcade.gd").new(self.bag, self, 0, 11, -1))
        arcade.register_handler(preload("res://scripts/input/handlers/player_cone_arcade.gd").new(self.bag, self, 0, 10, 1))
        arcade.register_handler(preload("res://scripts/input/handlers/player_attack_arcade.gd").new(self.bag, self, 15))
        arcade.register_handler(preload("res://scripts/input/handlers/player_attack_arcade.gd").new(self.bag, self, 16))
        arcade.register_handler(preload("res://scripts/input/handlers/player_attack_arcade.gd").new(self.bag, self, 17))
        arcade.register_handler(preload("res://scripts/input/handlers/player_attack_arcade.gd").new(self.bag, self, 18))
        arcade.register_handler(preload("res://scripts/input/handlers/player_attack_arcade.gd").new(self.bag, self, 19))
        arcade.register_handler(preload("res://scripts/input/handlers/player_attack_arcade.gd").new(self.bag, self, 20))
    else:
        arcade.register_handler(preload("res://scripts/input/handlers/player_enter_game_arcade.gd").new(self.bag, self, 0))
        arcade.register_handler(preload("res://scripts/input/handlers/player_move_axis_arcade.gd").new(self.bag, self, 0, 0))
        arcade.register_handler(preload("res://scripts/input/handlers/player_move_axis_arcade.gd").new(self.bag, self, 1, 1))
        arcade.register_handler(preload("res://scripts/input/handlers/player_cone_axis_arcade.gd").new(self.bag, self, 0, 0))
        arcade.register_handler(preload("res://scripts/input/handlers/player_cone_axis_arcade.gd").new(self.bag, self, 1, 1))
        arcade.register_handler(preload("res://scripts/input/handlers/player_attack_arcade.gd").new(self.bag, self, 1))
        arcade.register_handler(preload("res://scripts/input/handlers/player_attack_arcade.gd").new(self.bag, self, 2))
        arcade.register_handler(preload("res://scripts/input/handlers/player_attack_arcade.gd").new(self.bag, self, 3))
        arcade.register_handler(preload("res://scripts/input/handlers/player_attack_arcade.gd").new(self.bag, self, 4))
        arcade.register_handler(preload("res://scripts/input/handlers/player_attack_arcade.gd").new(self.bag, self, 5))
        arcade.register_handler(preload("res://scripts/input/handlers/player_attack_arcade.gd").new(self.bag, self, 6))

func bind_gamepad(id):
    var gamepad = self.bag.input.devices['pad' + str(id)]
    gamepad.register_handler(preload("res://scripts/input/handlers/player_enter_game_gamepad.gd").new(self.bag, self))
    gamepad.register_handler(preload("res://scripts/input/handlers/player_move_axis.gd").new(self.bag, self, 0))
    gamepad.register_handler(preload("res://scripts/input/handlers/player_move_axis.gd").new(self.bag, self, 1))
    gamepad.register_handler(preload("res://scripts/input/handlers/player_cone_gamepad.gd").new(self.bag, self, Globals.get("platform_input/xbox_right_stick_x"), 0))
    gamepad.register_handler(preload("res://scripts/input/handlers/player_cone_gamepad.gd").new(self.bag, self, Globals.get("platform_input/xbox_right_stick_y"), 1))
    gamepad.register_handler(preload("res://scripts/input/handlers/player_attack_gamepad.gd").new(self.bag, self))

func bind_keyboard_and_mouse():
    var keyboard = self.bag.input.devices['keyboard']
    var mouse = self.bag.input.devices['mouse']
    keyboard.register_handler(preload("res://scripts/input/handlers/player_enter_game_keyboard.gd").new(self.bag, self))
    keyboard.register_handler(preload("res://scripts/input/handlers/player_move_key.gd").new(self.bag, self, 1, KEY_W, -1))
    keyboard.register_handler(preload("res://scripts/input/handlers/player_move_key.gd").new(self.bag, self, 1, KEY_S, 1))
    keyboard.register_handler(preload("res://scripts/input/handlers/player_move_key.gd").new(self.bag, self, 0, KEY_A, -1))
    keyboard.register_handler(preload("res://scripts/input/handlers/player_move_key.gd").new(self.bag, self, 0, KEY_D, 1))
    mouse.register_handler(preload("res://scripts/input/handlers/player_cone_mouse.gd").new(self.bag, self))
    mouse.register_handler(preload("res://scripts/input/handlers/player_attack_mouse.gd").new(self.bag, self))

func enter_game():
    self.is_playing = true
    self.spawn(self.bag.room_loader.get_spawn_position('initial' + str(self.player_id)))
    self.panel.show()

func spawn(position):
    self.is_alive = true
    .spawn(position)

func die():
    self.is_alive = false
    self.panel.hide()
    .die()
    if not self.bag.players.is_living_player_in_game():
        self.bag.sample_player.play('game_over')
        self.bag.action_controller.end_game()

func remove_from_game():
    self.panel.hide()
    self.is_processing = false
    self.despawn()
    self.reset()

func process(delta):
    self.adjust_attack_cone()
    .process(delta)
    self.check_doors()
    self.handle_items()
    self.process_attack()

func process_attack():
    if self.is_attacking && not self.is_attack_on_cooldown:
        self.velocity = self.attack_move_velocity
        self.attack()

func modify_position(delta):
    .modify_position(delta)
    self.flip(self.target_cone_vector[0])
    self.handle_animations()

func handle_animations():
    if not self.animations.is_playing():
        if abs(self.movement_vector[0]) > self.AXIS_THRESHOLD || abs(self.movement_vector[1]) > self.AXIS_THRESHOLD:
            self.animations.play('run')
        else:
            self.animations.play('idle')
    else:
        if self.animations.get_current_animation() == 'idle' && (abs(self.movement_vector[0]) > self.AXIS_THRESHOLD || abs(self.movement_vector[1]) > self.AXIS_THRESHOLD):
            self.animations.play('run')
        elif self.animations.get_current_animation() == 'run' && abs(self.movement_vector[0]) < self.AXIS_THRESHOLD && abs(self.movement_vector[1]) < self.AXIS_THRESHOLD:
            self.animations.play('idle')

func respawn():
    if self.is_alive || not self.is_playing:
        return
    self.panel.reset()
    self.reset()
    self.enter_game()

func handle_items():
    var items = self.bag.items.get_items_near_object(self)
    for item in items:
        if item.power_up_type == 0:
            self.get_fat(item.power_up_amount)
        else:
            self.get_power(item.power_up_amount)
        self.score = self.score + item.score
        item.pick()
        self.update_bars()

func adjust_attack_cone():
    #if abs(self.target_cone_vector[0]) < self.AXIS_THRESHOLD || abs(self.target_cone_vector[1]) < self.AXIS_THRESHOLD:
    #    return
    if self.target_cone_vector[0] == 0 && self.target_cone_vector[1] == 0:
        self.target_cone.hide()
        return

    self.target_cone.show()

    self.target_cone_angle = -atan2(self.target_cone_vector[1], self.target_cone_vector[0]) - PI/2
    self.target_cone.set_rot(self.target_cone_angle)

func attack():
    if self.is_attack_on_cooldown:
        return

    var enemies
    var random_attack = 'attack'+ str(1 + randi() % 2)

    self.is_attack_on_cooldown = true

    if not self.animations.get_current_animation() == 'attack1' and not self.animations.get_current_animation() == 'attack2' :
        self.animations.play(random_attack)
        self.play_sound(random_attack)
        self.blast.play('blast')
    elif not self.animations.is_playing():
        if self.animations.get_current_animation() == 'attack1':
            self.animations.play('attack2')
            self.play_sound('attack2')
        else:
            self.animations.play('attack1')
            self.play_sound('attack1')
        self.blast.play('blast')

    enemies = self.bag.enemies.get_enemies_near_object(self, self.attack_range, self.target_cone_vector, self.attack_width)
    for enemy in enemies:
        if enemy.will_die(self.attack_strength):
            self.score += enemy.score
            self.update_bars()

        enemy.recieve_damage(self.attack_strength)
        enemy.push_back(self)
    self.bag.timers.set_timeout(self.attack_cooldown, self, "attack_cooled_down")

func get_power(amount):
    self.get_pure_power(amount)

    self.hp -= amount
    self.max_hp -= amount
    self.update_bars()
    if self.hp <= 0:
        self.die()

func get_pure_power(amount):
    self.attack_strength += amount
    if self.attack_strength >= 16:
        self.attack_strength = 16
    self.update_bars()

func get_fat(amount):
    self.hp += amount
    self.max_hp += amount
    if self.max_hp > self.hp_cap:
        self.max_hp = self.hp_cap
    if self.hp > self.max_hp:
        self.hp = self.max_hp
    self.update_bars()

func check_colisions():
    return

func check_doors():
    if not self.bag.game_state.doors_open:
        return;

    var door_coords
    var new_coords = [0, 0]
    var cell = self.bag.game_state.current_cell
    if cell.north != null:
        door_coords = self.bag.room_loader.door_definitions['north'][1]
        new_coords[0] = door_coords[0] + 8 + self.bag.room_loader.side_offset
        new_coords[1] = door_coords[1] + 0 + self.bag.room_loader.top_offset
        if self.check_exit(new_coords, cell.north, Vector2(26, -15)):
            self.bag.players.move_to_entry_position('south')
            return
    if cell.south != null:
        door_coords = self.bag.room_loader.door_definitions['south'][1]
        new_coords[0] = door_coords[0] + 8 + self.bag.room_loader.side_offset
        new_coords[1] = door_coords[1] + 9 + self.bag.room_loader.top_offset
        if self.check_exit(new_coords, cell.south, Vector2(26, 40)):
            self.bag.players.move_to_entry_position('north')
            return
    if cell.east != null:
        door_coords = self.bag.room_loader.door_definitions['east'][1]
        new_coords[0] = door_coords[0] + 19 + self.bag.room_loader.side_offset
        new_coords[1] = door_coords[1] + 4 + self.bag.room_loader.top_offset
        if self.check_exit(new_coords, cell.east, Vector2(40, 0)):
            self.bag.players.move_to_entry_position('west')
            return
    if cell.west != null:
        door_coords = self.bag.room_loader.door_definitions['west'][1]
        new_coords[0] = door_coords[0] + 0 + self.bag.room_loader.side_offset
        new_coords[1] = door_coords[1] + 4 + self.bag.room_loader.top_offset
        if self.check_exit(new_coords, cell.west, Vector2(-10, 0)):
            self.bag.players.move_to_entry_position('east')
            return

    self.check_level_exit()

func check_exit(door_coords, cell, door_offset):
    var exit_area = self.bag.room_loader.translate_position(Vector2(door_coords[0] + self.bag.room_loader.side_offset, door_coords[1]))
    exit_area = exit_area + door_offset
    var distance = self.calculate_distance(exit_area)
    if distance < self.EXIT_THRESHOLD:
        self.bag.map.switch_to_cell(cell)
        return true
    return false

func check_level_exit():
    var exit_area
    var distance
    for exit in self.bag.game_state.current_room.exits:
        exit_area = self.bag.room_loader.translate_position(Vector2(exit[0] + self.bag.room_loader.side_offset, exit[1]  + self.bag.room_loader.top_offset))
        distance = self.calculate_distance(exit_area)
        if distance < self.LEVEL_EXIT_THRESHOLD:
            self.bag.action_controller.next_level(exit[2])

func move_to_entry_position(name):
    var entry_position_id = self.bag.players.get_first_free_entry_position_num(self.player_id)
    var entry_position
    entry_position = self.bag.room_loader.get_spawn_position(name + str(entry_position_id))
    self.avatar.set_pos(entry_position)

func update_bars():
    self.panel.update_bar(self.panel.fat_bar, self.hp - 1, 0)
    self.panel.update_bar(self.panel.power_bar, self.attack_strength - 1, 3)
    self.panel.update_points(self.score)

func set_hp(hp):
    .set_hp(hp)
    self.update_bars()

func recieve_damage(damage):
    if self.is_invulnerable:
        return

    self.bag.camera.shake()
    .recieve_damage(damage)

func reset():
    self.attack_strength = 1
    self.hp = 10
    self.max_hp = 10
    self.target_cone_vector = [0, 0]
    self.target_cone_angle = 0.0
    self.is_playing = false
    self.is_alive = true
    self.movement_vector = [0, 0]
    self.score = 0
    self.is_attack_on_cooldown = false
    self.is_attacking = false
    self.update_bars()

func attack_cooled_down():
    self.is_attack_on_cooldown = false
    self.velocity = self.normal_velocity

extends "res://scripts/moving_object.gd"

var player_id
var is_playing = false
var is_alive = true
var attack_range = 100
var attack_width = PI * 0.33
var attack_strength = 1
var attack_cooldown = 0.4
var is_attack_on_cooldown = false
var is_attacking = false
var blast

var target_cone
var target_cone_vector = [0, 0]
var target_cone_angle = 0.0

var panel
var hp_cap = 16
var attack_move_velocity = 20
var normal_velocity = 200

var EXIT_THRESHOLD = 35

func _init(bag, player_id).(bag):
    self.bag = bag
    self.player_id = player_id
    self.velocity = 200
    self.attack_move_velocity = 20
    self.normal_velocity = 200
    self.hp = 10
    self.max_hp = 10
    self.score = 0
    self.hit_protection = true
    self.invulnerability_period = 1
    self.avatar = preload("res://scenes/player/player.xscn").instance()
    self.body_part_head = self.avatar.get_node('head')
    self.hat = self.body_part_head.get_node('hat')
    self.body_part_body = self.avatar.get_node('body')
    self.body_part_footer = self.avatar.get_node('footer')
    self.target_cone = self.avatar.get_node('attack_cone')
    self.animations = self.avatar.get_node('body_animations')
    self.hit_particles = self.avatar.get_node('hitparticles')
    self.blast = self.avatar.get_node('blast_animations')

    if not self.bag.input.arcade:
        self.bind_gamepad(player_id)
    else:
        self.bind_arcade(player_id)
    self.panel = self.bag.hud.bind_player_panel(player_id)
    self.hat.set_frame(player_id)
    self.update_bars()

    self.sounds['hit'] = 'player_hit'
    self.sounds['die'] = 'player_die'
    self.sounds['attack1'] = 'player_attack1'
    self.sounds['attack2'] = 'player_attack2'

func bind_arcade(player):
    var arcade = self.bag.input.devices['arcade']
    if player == 0:
        arcade.register_handler(preload("res://scripts/input/handlers/player_enter_game_arcade.gd").new(self.bag, self, 14))
        arcade.register_handler(preload("res://scripts/input/handlers/player_enter_game_arcade.gd").new(self.bag, self, 0))
        arcade.register_handler(preload("res://scripts/input/handlers/player_move_arcade.gd").new(self.bag, self, 1, 13, -1))
        arcade.register_handler(preload("res://scripts/input/handlers/player_move_arcade.gd").new(self.bag, self, 1, 12, 1))
        arcade.register_handler(preload("res://scripts/input/handlers/player_move_arcade.gd").new(self.bag, self, 0, 11, -1))
        arcade.register_handler(preload("res://scripts/input/handlers/player_move_arcade.gd").new(self.bag, self, 0, 10, 1))
        arcade.register_handler(preload("res://scripts/input/handlers/player_cone_arcade.gd").new(self.bag, self, 1, 13, -1))
        arcade.register_handler(preload("res://scripts/input/handlers/player_cone_arcade.gd").new(self.bag, self, 1, 12, 1))
        arcade.register_handler(preload("res://scripts/input/handlers/player_cone_arcade.gd").new(self.bag, self, 0, 11, -1))
        arcade.register_handler(preload("res://scripts/input/handlers/player_cone_arcade.gd").new(self.bag, self, 0, 10, 1))
        arcade.register_handler(preload("res://scripts/input/handlers/player_attack_arcade.gd").new(self.bag, self, 15))
        arcade.register_handler(preload("res://scripts/input/handlers/player_attack_arcade.gd").new(self.bag, self, 16))
        arcade.register_handler(preload("res://scripts/input/handlers/player_attack_arcade.gd").new(self.bag, self, 17))
        arcade.register_handler(preload("res://scripts/input/handlers/player_attack_arcade.gd").new(self.bag, self, 18))
        arcade.register_handler(preload("res://scripts/input/handlers/player_attack_arcade.gd").new(self.bag, self, 19))
        arcade.register_handler(preload("res://scripts/input/handlers/player_attack_arcade.gd").new(self.bag, self, 20))
    else:
        arcade.register_handler(preload("res://scripts/input/handlers/player_enter_game_arcade.gd").new(self.bag, self, 0))
        arcade.register_handler(preload("res://scripts/input/handlers/player_move_axis_arcade.gd").new(self.bag, self, 0, 0))
        arcade.register_handler(preload("res://scripts/input/handlers/player_move_axis_arcade.gd").new(self.bag, self, 1, 1))
        arcade.register_handler(preload("res://scripts/input/handlers/player_cone_axis_arcade.gd").new(self.bag, self, 0, 0))
        arcade.register_handler(preload("res://scripts/input/handlers/player_cone_axis_arcade.gd").new(self.bag, self, 1, 1))
        arcade.register_handler(preload("res://scripts/input/handlers/player_attack_arcade.gd").new(self.bag, self, 1))
        arcade.register_handler(preload("res://scripts/input/handlers/player_attack_arcade.gd").new(self.bag, self, 2))
        arcade.register_handler(preload("res://scripts/input/handlers/player_attack_arcade.gd").new(self.bag, self, 3))
        arcade.register_handler(preload("res://scripts/input/handlers/player_attack_arcade.gd").new(self.bag, self, 4))
        arcade.register_handler(preload("res://scripts/input/handlers/player_attack_arcade.gd").new(self.bag, self, 5))
        arcade.register_handler(preload("res://scripts/input/handlers/player_attack_arcade.gd").new(self.bag, self, 6))

func bind_gamepad(id):
    var gamepad = self.bag.input.devices['pad' + str(id)]
    gamepad.register_handler(preload("res://scripts/input/handlers/player_enter_game_gamepad.gd").new(self.bag, self))
    gamepad.register_handler(preload("res://scripts/input/handlers/player_move_axis.gd").new(self.bag, self, 0))
    gamepad.register_handler(preload("res://scripts/input/handlers/player_move_axis.gd").new(self.bag, self, 1))
    gamepad.register_handler(preload("res://scripts/input/handlers/player_cone_gamepad.gd").new(self.bag, self, Globals.get("platform_input/xbox_right_stick_x"), 0))
    gamepad.register_handler(preload("res://scripts/input/handlers/player_cone_gamepad.gd").new(self.bag, self, Globals.get("platform_input/xbox_right_stick_y"), 1))
    gamepad.register_handler(preload("res://scripts/input/handlers/player_attack_gamepad.gd").new(self.bag, self))

func bind_keyboard_and_mouse():
    var keyboard = self.bag.input.devices['keyboard']
    var mouse = self.bag.input.devices['mouse']
    keyboard.register_handler(preload("res://scripts/input/handlers/player_enter_game_keyboard.gd").new(self.bag, self))
    keyboard.register_handler(preload("res://scripts/input/handlers/player_move_key.gd").new(self.bag, self, 1, KEY_W, -1))
    keyboard.register_handler(preload("res://scripts/input/handlers/player_move_key.gd").new(self.bag, self, 1, KEY_S, 1))
    keyboard.register_handler(preload("res://scripts/input/handlers/player_move_key.gd").new(self.bag, self, 0, KEY_A, -1))
    keyboard.register_handler(preload("res://scripts/input/handlers/player_move_key.gd").new(self.bag, self, 0, KEY_D, 1))
    mouse.register_handler(preload("res://scripts/input/handlers/player_cone_mouse.gd").new(self.bag, self))
    mouse.register_handler(preload("res://scripts/input/handlers/player_attack_mouse.gd").new(self.bag, self))

func enter_game():
    self.is_playing = true
    self.spawn(self.bag.room_loader.get_spawn_position('initial' + str(self.player_id)))
    self.panel.show()

func spawn(position):
    self.is_alive = true
    .spawn(position)

func die():
    self.is_alive = false
    self.panel.hide()
    .die()
    if not self.bag.players.is_living_player_in_game():
        self.bag.sample_player.play('game_over')
        self.bag.action_controller.end_game()

func remove_from_game():
    self.panel.hide()
    self.is_processing = false
    self.despawn()
    self.reset()

func process(delta):
    self.adjust_attack_cone()
    .process(delta)
    self.check_doors()
    self.handle_items()
    self.process_attack()

func process_attack():
    if self.is_attacking && not self.is_attack_on_cooldown:
        self.velocity = self.attack_move_velocity
        self.attack()

func modify_position(delta):
    .modify_position(delta)
    self.flip(self.target_cone_vector[0])
    self.handle_animations()

func handle_animations():
    if not self.animations.is_playing():
        if abs(self.movement_vector[0]) > self.AXIS_THRESHOLD || abs(self.movement_vector[1]) > self.AXIS_THRESHOLD:
            self.animations.play('run')
        else:
            self.animations.play('idle')
    else:
        if self.animations.get_current_animation() == 'idle' && (abs(self.movement_vector[0]) > self.AXIS_THRESHOLD || abs(self.movement_vector[1]) > self.AXIS_THRESHOLD):
            self.animations.play('run')
        elif self.animations.get_current_animation() == 'run' && abs(self.movement_vector[0]) < self.AXIS_THRESHOLD && abs(self.movement_vector[1]) < self.AXIS_THRESHOLD:
            self.animations.play('idle')

func respawn():
    if self.is_alive || not self.is_playing:
        return
    self.panel.reset()
    self.reset()
    self.enter_game()

func handle_items():
    var items = self.bag.items.get_items_near_object(self)
    for item in items:
        if item.power_up_type == 0:
            self.get_fat(item.power_up_amount)
        else:
            self.get_power(item.power_up_amount)
        self.score = self.score + item.score
        item.pick()
        self.update_bars()

func adjust_attack_cone():
    #if abs(self.target_cone_vector[0]) < self.AXIS_THRESHOLD || abs(self.target_cone_vector[1]) < self.AXIS_THRESHOLD:
    #    return
    if self.target_cone_vector[0] == 0 && self.target_cone_vector[1] == 0:
        self.target_cone.hide()
        return

    self.target_cone.show()

    self.target_cone_angle = -atan2(self.target_cone_vector[1], self.target_cone_vector[0]) - PI/2
    self.target_cone.set_rot(self.target_cone_angle)

func attack():
    if self.is_attack_on_cooldown:
        return

    var enemies
    var random_attack = 'attack'+ str(1 + randi() % 2)

    self.is_attack_on_cooldown = true

    if not self.animations.get_current_animation() == 'attack1' and not self.animations.get_current_animation() == 'attack2' :
        self.animations.play(random_attack)
        self.play_sound(random_attack)
        self.blast.play('blast')
    elif not self.animations.is_playing():
        if self.animations.get_current_animation() == 'attack1':
            self.animations.play('attack2')
            self.play_sound('attack2')
        else:
            self.animations.play('attack1')
            self.play_sound('attack1')
        self.blast.play('blast')

    enemies = self.bag.enemies.get_enemies_near_object(self, self.attack_range, self.target_cone_vector, self.attack_width)
    for enemy in enemies:
        if enemy.will_die(self.attack_strength):
            self.score += enemy.score
            self.update_bars()

        enemy.recieve_damage(self.attack_strength)
        enemy.push_back(self)
    self.bag.timers.set_timeout(self.attack_cooldown, self, "attack_cooled_down")

func get_power(amount):
    self.get_pure_power(amount)

    self.hp -= amount
    self.max_hp -= amount
    self.update_bars()
    if self.hp <= 0:
        self.die()

func get_pure_power(amount):
    self.attack_strength += amount
    if self.attack_strength >= 16:
        self.attack_strength = 16
    self.update_bars()

func get_fat(amount):
    self.hp += amount
    self.max_hp += amount
    if self.max_hp > self.hp_cap:
        self.max_hp = self.hp_cap
    if self.hp > self.max_hp:
        self.hp = self.max_hp
    self.update_bars()

func check_colisions():
    return

func check_doors():
    if not self.bag.game_state.doors_open:
        return;

    var door_coords
    var new_coords = [0, 0]
    var cell = self.bag.game_state.current_cell
    if cell.north != null:
        door_coords = self.bag.room_loader.door_definitions['north'][1]
        new_coords[0] = door_coords[0] + 8 + self.bag.room_loader.side_offset
        new_coords[1] = door_coords[1] + 0 + self.bag.room_loader.top_offset
        if self.check_exit(new_coords, cell.north, Vector2(26, -15)):
            self.bag.players.move_to_entry_position('south')
            return
    if cell.south != null:
        door_coords = self.bag.room_loader.door_definitions['south'][1]
        new_coords[0] = door_coords[0] + 8 + self.bag.room_loader.side_offset
        new_coords[1] = door_coords[1] + 9 + self.bag.room_loader.top_offset
        if self.check_exit(new_coords, cell.south, Vector2(26, 40)):
            self.bag.players.move_to_entry_position('north')
            return
    if cell.east != null:
        door_coords = self.bag.room_loader.door_definitions['east'][1]
        new_coords[0] = door_coords[0] + 19 + self.bag.room_loader.side_offset
        new_coords[1] = door_coords[1] + 4 + self.bag.room_loader.top_offset
        if self.check_exit(new_coords, cell.east, Vector2(40, 0)):
            self.bag.players.move_to_entry_position('west')
            return
    if cell.west != null:
        door_coords = self.bag.room_loader.door_definitions['west'][1]
        new_coords[0] = door_coords[0] + 0 + self.bag.room_loader.side_offset
        new_coords[1] = door_coords[1] + 4 + self.bag.room_loader.top_offset
        if self.check_exit(new_coords, cell.west, Vector2(-10, 0)):
            self.bag.players.move_to_entry_position('east')
            return

    self.check_level_exit()

func check_exit(door_coords, cell, door_offset):
    var exit_area = self.bag.room_loader.translate_position(Vector2(door_coords[0] + self.bag.room_loader.side_offset, door_coords[1]))
    exit_area = exit_area + door_offset
    var distance = self.calculate_distance(exit_area)
    if distance < self.EXIT_THRESHOLD:
        self.bag.map.switch_to_cell(cell)
        return true
    return false

func check_level_exit():
    var exit_area
    var distance
    for exit in self.bag.game_state.current_room.exits:
        exit_area = self.bag.room_loader.translate_position(Vector2(exit[0] + self.bag.room_loader.side_offset, exit[1]  + self.bag.room_loader.top_offset))
        distance = self.calculate_distance(exit_area)
        if distance < self.EXIT_THRESHOLD:
            self.bag.action_controller.next_level(exit[2])

func move_to_entry_position(name):
    var entry_position_id = self.bag.players.get_first_free_entry_position_num(self.player_id)
    var entry_position
    entry_position = self.bag.room_loader.get_spawn_position(name + str(entry_position_id))
    self.avatar.set_pos(entry_position)

func update_bars():
    self.panel.update_bar(self.panel.fat_bar, self.hp - 1, 0)
    self.panel.update_bar(self.panel.power_bar, self.attack_strength - 1, 3)
    self.panel.update_points(self.score)

func set_hp(hp):
    .set_hp(hp)
    self.update_bars()

func recieve_damage(damage):
    if self.is_invulnerable:
        return

    self.bag.camera.shake()
    .recieve_damage(damage)

func reset():
    self.attack_strength = 1
    self.hp = 10
    self.max_hp = 10
    self.target_cone_vector = [0, 0]
    self.target_cone_angle = 0.0
    self.is_playing = false
    self.is_alive = true
    self.movement_vector = [0, 0]
    self.score = 0
    self.is_attack_on_cooldown = false
    self.is_attacking = false
    self.update_bars()

func attack_cooled_down():
    self.is_attack_on_cooldown = false
    self.velocity = self.normal_velocity


better layout

better layout

extends Spatial

const NO_ERRORS = "STATUS NOMINAL"

var puzzle
var puzzleMan
var DataMan = preload("res://scripts/DataManager.gd")

var gridMan
var prevBlocks = [] # keeps track of prevous color for pairs by layer
var blockColors = preload("res://scripts/PuzzleManager.gd").blockColors
var id

var saveDir = OS.get_data_dir() + "/PuzzleSaves"

var glyphIx = 0

var fd
var gui = [
	["save_pzl", Button.new()],
	["load_pzl", Button.new()],
	["remove_layer", Button.new()],
	["random_layer", Button.new()],
	["class_toggle", {
		optionButt=OptionButton.new(),
		values=["WILD", "PAIR"],
		# const BLOCK_LASER	= 0
		# const BLOCK_WILD	= 1
		# const BLOCK_PAIR	= 2
		# const BLOCK_GOAL	= 3
		# const BLOCK_BLOCK   = 4
		value="PAIR"
	}],
	["color_toggle", {
		optionButt=OptionButton.new(),
		values=blockColors,
		value="Blue"
	}],
	["action_toggle", {
		optionButt=OptionButton.new(),
		values=["Add", "Remove", "Replace"],
		value="Add"
	}],
	["status", Label.new()],
	["test_pzl", Button.new()]
]
var action_ix = gui.size() - 2 - 1
var color_ix = gui.size() -  2 - 2
var class_ix = gui.size() -  2 - 3
var status_ix = gui.size() - 2

# gross style, cannot , but clean enough
func shouldAddNeighbor():
	return gui[action_ix][1].value == "Add"

func shouldReplaceSelf():
	return gui[action_ix][1].value == "Replace"

func shouldRemoveSelf():
	return gui[action_ix][1].value == "Remove"

# called in AbstractBlock to add a new block
func addBlock(pos):
	var curColor = gui[color_ix][1].value
	var selected = gui[class_ix][1].value
	var b = puzzleMan.PickledBlock.new() \
		.setName(id) \
		.setBlockPos(pos) \
		.setBlockClass(gui[class_ix][1].optionButt.get_selected() + 1)
		# VERY FRAGILE INDEX STUFF
	var layer = puzzleMan.calcBlockLayerVec(pos)
	if layer == 0 and not selected == "GOAL":
		return

	if selected == "LZR" or selected == "GOAL":
		print("CANNOT MK ", str(selected))
		return

	if selected == "WILD":
		b.setTextureName(curColor)
	# must be a paired block

	else:
		id += 1

		# expand prevblocks index
		while prevBlocks.size() <= layer:
			var d = {}
			for k in blockColors:
				d[k] = null
			prevBlocks.append(d)

		var pb = prevBlocks[layer][curColor]

		if pb != null:
			var prevName = pb.toNode().name
			var pbNode = gridMan.get_node(prevName)

			b.setPairName(pb.name)
			pb.setPairName(b.name)

			b.setTextureName(pb.textureName)

			gridMan.remove_block(pb)
			gridMan.addPickledBlock(pb)
			prevBlocks[layer][curColor] = null
		else:
			prevBlocks[layer][curColor] = b
			glyphIx += 1
			glyphIx %= 3
			b.setTextureName(curColor + str(glyphIx + 1))

		gui[status_ix][1].set_text(getPrevBlockErrors())

	var block = gridMan.addPickledBlock(b)
	var v = 0.01
	block.set_scale(Vector3(v,v,v))
	block.scaleTweenNode(1, 0.25, Tween.TRANS_QUART).start()
	return b

func getPrevBlockErrors():
	# hahaha, so bad
	var missing = "STATUS: "
	# check every layer
	for l in range(prevBlocks.size()):
		# check every color
		for k in prevBlocks[l].keys():
			var missed = false
			if prevBlocks[l][k] != null:
				# one message about the current layer
				if not missed:
					missing += " L" + str(l) + " "
					missed = true
				missing += k.to_upper() + " " # bad way of constructing strings
	if missing == "":
		missing += "NOMINAL"
	return missing

func changeValue(ix, togg):
	togg.value = togg.values[ix]

func _ready():
	# lights!
	get_tree().get_root().add_child(preload( "res://puzzleView.scn" ).instance())
	puzzle = preload( "res://puzzle.scn" ).instance()
	puzzle.mainPuzzle = false

	# hide and disable timer
	puzzle.time.on = false;
	puzzle.time.val = ''

	puzzle.set_as_toplevel(true)
	get_tree().get_root().add_child(puzzle)

	gridMan = puzzle.get_node("GridView/GridMan")
	id = gridMan.puzzle.shape.size()

	puzzleMan = puzzle.puzzleMan

	set_process_input(true)

	var theme = preload("res://themes/MainTheme.thm")
	fd = initLoadSaveDialog(self, get_tree(), saveDir)

	var y = 0
	var x = 60 # these are unrelated
	for control in gui:
		if control[0].rfind('_toggle') > 0:
			x += 150
			var togg = control[1]
			var e = togg.optionButt
			e.set_pos(Vector2(x, 45))
			e.set_theme(theme)
			add_child(e)

			# index of items needed
			e.connect("item_selected", self, "changeValue", [togg])
			for i in range(togg.values.size()):
				e.add_item(togg.values[i], i)
				if togg.value == togg.values[i]:
					e.select(i)
				# e.add_icon_item(togg.values[i], i)

		else:
			y += 45
			var e = control[1]
			e.set_pos(Vector2(10, y))
			e.set_theme(theme)
			if e extends Button:
				e.set_text(control[0])
			if control[0] == 'save_pzl' :
				e.connect('pressed', self, 'showSaveDialog', [fd, self])
			if control[0] == 'load_pzl' :
				e.connect('pressed', self, 'showLoadDialog', [fd, self])
			if control[0] == 'status':
				control[1].set_text('STATUS: NOMINAL')
			add_child(e)

func puzzleSave():
	var f = fd.get_current_path()
	if f == null or f == "":
		return
	if getPrevBlockErrors() != NO_ERRORS:
		gui[status_ix][1].set_text(getPrevBlockErrors() + " SAVING DISABLED! BANG HEAD ON KEYBOARD ")
		return
	print("SAVING TO ", f)
	DataMan.savePuzzle( f, gridMan.puzzle )

func puzzleLoad():
	var f = fd.get_current_path()
	if f == null or f == "":
		return
	print("LOADING FROM ", f)
	prevBlocks = []
	gridMan.set_puzzle(DataMan.loadPuzzle( f ))

static func showLoadDialog(fileD, parent):
	if fileD.is_connected("confirmed", parent, "puzzleSave"):
		fileD.disconnect("confirmed", parent, "puzzleSave")
	fileD.connect("confirmed", parent, "puzzleLoad")
	fileD.set_mode(FileDialog.MODE_OPEN_FILE)

	fileD.popup_centered()

static func showSaveDialog(fileD, parent):
	if fileD.is_connected("confirmed", parent, "puzzleLoad"):
		fileD.disconnect("confirmed", parent, "puzzleLoad")
	fileD.connect("confirmed", parent, "puzzleSave")
	fileD.set_mode(FileDialog.MODE_SAVE_FILE)

	fileD.popup_centered()

static func initLoadSaveDialog(parent, tree, saveDir):
	# setup text in the file dialog
	var fileDialog = load("res://fileDialog.scn").instance()
	fileDialog.set_title("Select Puzzle Filename")
	fileDialog.set_access(FileDialog.ACCESS_FILESYSTEM)
	fileDialog.add_filter("*.pzl ; Anttris Puzzle")

	Directory.new().make_dir_recursive(saveDir)
	fileDialog.set_current_dir(saveDir)
	print("Saves in ", saveDir)

	# MainTheme leaks on bottom
	var dialogTheme = Theme.new()
	dialogTheme.copy_default_theme()
	fileDialog.set_theme(dialogTheme)

	# work even if game is paused
	fileDialog.set_pause_mode(PAUSE_MODE_PROCESS)

	# unpause if user cancels
	fileDialog.connect("popup_hide", tree, "set_pause", [false])
	fileDialog.connect("hide", tree, "set_pause", [false])
	fileDialog.connect("about_to_show", tree, "set_pause", [true])
	fileDialog.hide()

	parent.add_child(fileDialog)
	fileDialog.set_owner(parent)
	return fileDialog

extends Spatial

const NO_ERRORS = "STATUS NOMINAL"

var puzzle
var puzzleMan
var DataMan = preload("res://scripts/DataManager.gd")

var gridMan
var prevBlocks = [] # keeps track of prevous color for pairs by layer
var blockColors = preload("res://scripts/PuzzleManager.gd").blockColors
var id

var saveDir = OS.get_data_dir() + "/PuzzleSaves"

var glyphIx = 0

var fd
var gui = [
	["status", Label.new()], # plz keep me as first element, referenced as gui[0] later
	["save_pzl", Button.new()],
	["load_pzl", Button.new()],
	["remove_layer", Button.new()],
	["random_layer", Button.new()],
	["class_toggle", {
		optionButt=OptionButton.new(),
		values=["WILD", "PAIR"],
		# const BLOCK_LASER	= 0
		# const BLOCK_WILD	= 1
		# const BLOCK_PAIR	= 2
		# const BLOCK_GOAL	= 3
		# const BLOCK_BLOCK   = 4
		value="PAIR"
	}],
	["color_toggle", {
		optionButt=OptionButton.new(),
		values=blockColors,
		value="Blue"
	}],
	["action_toggle", {
		optionButt=OptionButton.new(),
		values=["Add", "Remove", "Replace"],
		value="Add"
	}],
	["test_pzl", Button.new()],
]
var action_ix = gui.size() - 1 - 1
var color_ix = gui.size() - 1 - 2
var class_ix = gui.size() - 1 - 3

# gross style, cannot , but clean enough
func shouldAddNeighbor():
	return gui[action_ix][1].value == "Add"

func shouldReplaceSelf():
	return gui[action_ix][1].value == "Replace"

func shouldRemoveSelf():
	return gui[action_ix][1].value == "Remove"

# called in AbstractBlock to add a new block
func addBlock(pos):
	var curColor = gui[color_ix][1].value
	var selected = gui[class_ix][1].value
	var b = puzzleMan.PickledBlock.new() \
		.setName(id) \
		.setBlockPos(pos) \
		.setBlockClass(gui[class_ix][1].optionButt.get_selected() + 1)
		# VERY FRAGILE INDEX STUFF
	var layer = puzzleMan.calcBlockLayerVec(pos)
	if layer == 0 and not selected == "GOAL":
		return

	if selected == "LZR" or selected == "GOAL":
		print("CANNOT MK ", str(selected))
		return

	if selected == "WILD":
		b.setTextureName(curColor)
	# must be a paired block

	else:
		id += 1

		# expand prevblocks index
		while prevBlocks.size() <= layer:
			var d = {}
			for k in blockColors:
				d[k] = null
			prevBlocks.append(d)

		var pb = prevBlocks[layer][curColor]

		if pb != null:
			var prevName = pb.toNode().name
			var pbNode = gridMan.get_node(prevName)

			b.setPairName(pb.name)
			pb.setPairName(b.name)

			b.setTextureName(pb.textureName)

			gridMan.remove_block(pb)
			gridMan.addPickledBlock(pb)
			prevBlocks[layer][curColor] = null
		else:
			prevBlocks[layer][curColor] = b
			glyphIx += 1
			glyphIx %= 3
			b.setTextureName(curColor + str(glyphIx + 1))

		gui[0][1].set_text(getPrevBlockErrors())

	var block = gridMan.addPickledBlock(b)
	var v = 0.01
	block.set_scale(Vector3(v,v,v))
	block.scaleTweenNode(1, 0.25, Tween.TRANS_QUART).start()
	return b

func getPrevBlockErrors():
	# hahaha, so bad
	var missing = "STATUS: "
	# check every layer
	for l in range(prevBlocks.size()):
		# check every color
		for k in prevBlocks[l].keys():
			var missed = false
			if prevBlocks[l][k] != null:
				# one message about the current layer
				if not missed:
					missing += " L" + str(l) + " "
					missed = true
				missing += k.to_upper() + " " # bad way of constructing strings
	if missing == "":
		missing += "NOMINAL"
	return missing

func changeValue(ix, togg):
	togg.value = togg.values[ix]

func _ready():
	# lights!
	get_tree().get_root().add_child(preload( "res://puzzleView.scn" ).instance())
	puzzle = preload( "res://puzzle.scn" ).instance()
	puzzle.mainPuzzle = false

	# hide and disable timer
	puzzle.time.on = false;
	puzzle.time.val = ''

	puzzle.set_as_toplevel(true)
	get_tree().get_root().add_child(puzzle)

	gridMan = puzzle.get_node("GridView/GridMan")
	id = gridMan.puzzle.shape.size()

	puzzleMan = puzzle.puzzleMan

	set_process_input(true)

	var theme = preload("res://themes/MainTheme.thm")
	fd = initLoadSaveDialog(self, get_tree(), saveDir)

	var y = 0
	for control in gui:
		y += 45
		if control[0].rfind('_toggle') > 0:
			var togg = control[1]
			var e = togg.optionButt
			e.set_pos(Vector2(10, y))
			e.set_theme(theme)
			add_child(e)

			# index of items needed
			e.connect("item_selected", self, "changeValue", [togg])
			for i in range(togg.values.size()):
				e.add_item(togg.values[i], i)
				if togg.value == togg.values[i]:
					e.select(i)
				# e.add_icon_item(togg.values[i], i)

		else:
			var e = control[1]
			e.set_pos(Vector2(10, y))
			e.set_theme(theme)
			if e extends Button:
				e.set_text(control[0])
			if control[0] == 'save_pzl' :
				e.connect('pressed', self, 'showSaveDialog', [fd, self])
			if control[0] == 'load_pzl' :
				e.connect('pressed', self, 'showLoadDialog', [fd, self])
			if control[0] == 'status':
				control[1].set_text('STATUS: NOMINAL')
			add_child(e)

func puzzleSave():
	var f = fd.get_current_path()
	if f == null or f == "":
		return
	if getPrevBlockErrors() != NO_ERRORS:
		gui[0][1].set_text(getPrevBlockErrors() + " SAVING DISABLED! BANG HEAD ON KEYBOARD ")
		return
	print("SAVING TO ", f)
	DataMan.savePuzzle( f, gridMan.puzzle )

func puzzleLoad():
	var f = fd.get_current_path()
	if f == null or f == "":
		return
	print("LOADING FROM ", f)
	prevBlocks = []
	gridMan.set_puzzle(DataMan.loadPuzzle( f ))

static func showLoadDialog(fileD, parent):
	if fileD.is_connected("confirmed", parent, "puzzleSave"):
		fileD.disconnect("confirmed", parent, "puzzleSave")
	fileD.connect("confirmed", parent, "puzzleLoad")
	fileD.set_mode(FileDialog.MODE_OPEN_FILE)

	fileD.popup_centered()

static func showSaveDialog(fileD, parent):
	if fileD.is_connected("confirmed", parent, "puzzleLoad"):
		fileD.disconnect("confirmed", parent, "puzzleLoad")
	fileD.connect("confirmed", parent, "puzzleSave")
	fileD.set_mode(FileDialog.MODE_SAVE_FILE)

	fileD.popup_centered()

static func initLoadSaveDialog(parent, tree, saveDir):
	# setup text in the file dialog
	var fileDialog = load("res://fileDialog.scn").instance()
	fileDialog.set_title("Select Puzzle Filename")
	fileDialog.set_access(FileDialog.ACCESS_FILESYSTEM)
	fileDialog.add_filter("*.pzl ; Anttris Puzzle")

	Directory.new().make_dir_recursive(saveDir)
	fileDialog.set_current_dir(saveDir)
	print("Saves in ", saveDir)

	# MainTheme leaks on bottom
	var dialogTheme = Theme.new()
	dialogTheme.copy_default_theme()
	fileDialog.set_theme(dialogTheme)

	# work even if game is paused
	fileDialog.set_pause_mode(PAUSE_MODE_PROCESS)

	# unpause if user cancels
	fileDialog.connect("popup_hide", tree, "set_pause", [false])
	fileDialog.connect("hide", tree, "set_pause", [false])
	fileDialog.connect("about_to_show", tree, "set_pause", [true])
	fileDialog.hide()

	parent.add_child(fileDialog)
	fileDialog.set_owner(parent)
	return fileDialog