cleaner null pair check

cleaner null pair check

extends RigidBody

var name_int
var name
var pairName
var selected = false
var blockPos
var blockLayer
var blockType
const far_away_corner = Vector3(110, 110, 110)

var editor

static func nameToNodeName(n):
	return "block" + str(n)

func setName(n):
	name_int = n
	name = nameToNodeName(n)
	set_name(nameToNodeName(n))
	return self

func setPairName(n):
	pairName = nameToNodeName(n)
	return self

func setBlockLayer(n):
	blockLayer = n
	return self

func getBlockLayer():
	return blockLayer

func setBlockType( blockT ):
	blockType = blockT
	return self

func getBlockType():
	return blockType

func setSelected(sel):
	selected = sel
	return self

func forceClick(click_normal):
	if editor != null:
		if editor.shouldAddNeighbor():
			addNeighbor(editor, click_normal)
			return
		if editor.shouldReplaceSelf() or editor.shouldRemoveSelf():
			# lasers cannot be removed by the editor
			if blockType == get_parent().get_parent().get_parent().puzzleMan.BLOCK_LASER:
				return

			# maybe replace self
			if editor.shouldReplaceSelf():
				addNeighbor(editor, 0 * click_normal)
			if pairName != null:
				var pair = get_parent().get_node(pairName)
				if pair != null:
					pair.request_remove()
			request_remove()
			return
	else:
		activate()
		get_parent().clickBlock( name )

func addNeighbor(editor, click_normal):
	var t = get_parent().get_parent().get_transform().inverse()
	editor.addBlock(blockPos + t * click_normal)

# catch clicks/taps
func _input_event( camera, ev, click_pos, click_normal, shape_idx ):
	if ((ev.type==InputEvent.MOUSE_BUTTON and ev.button_index==BUTTON_LEFT)
	or (ev.type==InputEvent.SCREEN_TOUCH)):
		if (get_parent().get_parent().active and ev.is_pressed()):
			forceClick(click_normal)

# returns this block's pairNode or null
func pairActivate():
	selected = true

	# is my pair Nil?
	if not get_parent().has_node(str(pairName)):
		scaleTweenNode(0.9, 0.2, Tween.TRANS_EXPO).start()
		return null

	# get my pair node
	var pairNode = get_parent().get_node(pairName)

	# enlarge if the other guy's unselected
	if not pairNode.selected:
		scaleTweenNode(1.1, 0.2, Tween.TRANS_EXPO).start()
	return pairNode

# returns a tween node that uniformly changes the object's scale
# call start() on the returned object
func scaleTweenNode(scale, time=1, transType=Tween.TRANS_BOUNCE):
	var tweenNode = newTweenNode()
	tweenNode.interpolate_method( self, "set_scale", \
		self.get_scale(), Vector3(scale, scale, scale), \
		time, transType, Tween.EASE_OUT )

	return tweenNode

# adds a tween transition node to the tree
func newTweenNode():
	var tween = Tween.new()
	add_child(tween)
	return tween

func request_remove(node=null, key=null):
	var n = scaleTweenNode(0.001, 0.25, Tween.TRANS_QUART)
	n.start()
	n.connect("tween_complete", get_parent(), "remove_block", [self])

func _ready():
	if get_tree().get_root().has_node("EditorSpatial"):
		editor = get_tree().get_root().get_node("EditorSpatial")
	set_ray_pickable(true)

extends RigidBody

var name_int
var name
var pairName
var selected = false
var blockPos
var blockLayer
var blockType
const far_away_corner = Vector3(110, 110, 110)

var editor

static func nameToNodeName(n):
	return "block" + str(n)

func setName(n):
	name_int = n
	name = nameToNodeName(n)
	set_name(nameToNodeName(n))
	return self

func setPairName(n):
	pairName = nameToNodeName(n)
	return self

func setBlockLayer(n):
	blockLayer = n
	return self

func getBlockLayer():
	return blockLayer

func setBlockType( blockT ):
	blockType = blockT
	return self

func getBlockType():
	return blockType

func setSelected(sel):
	selected = sel
	return self

func forceClick(click_normal):
	if editor != null:
		if editor.shouldAddNeighbor():
			addNeighbor(editor, click_normal)
			return
		if editor.shouldReplaceSelf() or editor.shouldRemoveSelf():
			# lasers cannot be removed by the editor
			if blockType == get_parent().get_parent().get_parent().puzzleMan.BLOCK_LASER:
				return

			# maybe replace self
			if editor.shouldReplaceSelf():
				addNeighbor(editor, 0 * click_normal)
			if pairName != null:
				var pair = get_parent().get_node(pairName)
				if pair != null:
					pair.request_remove()
			request_remove()
			return
	else:
		activate()
		get_parent().clickBlock( name )

func addNeighbor(editor, click_normal):
	var t = get_parent().get_parent().get_transform().inverse()
	editor.addBlock(blockPos + t * click_normal)

# catch clicks/taps
func _input_event( camera, ev, click_pos, click_normal, shape_idx ):
	if ((ev.type==InputEvent.MOUSE_BUTTON and ev.button_index==BUTTON_LEFT)
	or (ev.type==InputEvent.SCREEN_TOUCH)):
		if (get_parent().get_parent().active and ev.is_pressed()):
			forceClick(click_normal)

# returns this block's pairNode or null
func pairActivate():
	selected = true

	# is my pair Nil?
	if pairName == null or get_parent() == null or not get_parent().has_node(pairName):
		scaleTweenNode(0.9, 0.2, Tween.TRANS_EXPO).start()
		return null

	# get my pair node
	var pairNode = get_parent().get_node(pairName)

	# enlarge if the other guy's unselected
	if not pairNode.selected:
		scaleTweenNode(1.1, 0.2, Tween.TRANS_EXPO).start()
	return pairNode

# returns a tween node that uniformly changes the object's scale
# call start() on the returned object
func scaleTweenNode(scale, time=1, transType=Tween.TRANS_BOUNCE):
	var tweenNode = newTweenNode()
	tweenNode.interpolate_method( self, "set_scale", \
		self.get_scale(), Vector3(scale, scale, scale), \
		time, transType, Tween.EASE_OUT )

	return tweenNode

# adds a tween transition node to the tree
func newTweenNode():
	var tween = Tween.new()
	add_child(tween)
	return tween

func request_remove(node=null, key=null):
	var n = scaleTweenNode(0.001, 0.25, Tween.TRANS_QUART)
	n.start()
	n.connect("tween_complete", get_parent(), "remove_block", [self])

func _ready():
	if get_tree().get_root().has_node("EditorSpatial"):
		editor = get_tree().get_root().get_node("EditorSpatial")
	set_ray_pickable(true)


static function for scene switch-aroo

static function for scene switch-aroo

# This script manages all of the GUI elements.
# It switches between the states of the GUI allowing
# the user to start games, edit the options or connect
# to a multiplayer game.

extends Node

# network variables
var network

# State variables.
var menuOn
var splashTween

# Timers.
var timer
var initialWait  = 0.5
var splashTimer  = 2.0
var warningTimer = 5.0

# GUI pieces.
var Splash_Team5
var Splash_Warning
var Menu_Main
var Menu_Chooser
var Menu_MP
var Menu_HostGame
var Menu_JoinGame
var Menu_Options

# Menu state values. Used to switch between them.
var MENU_INIT			= 0
var MENU_SPLASHTEAM5	= 1
var MENU_SPLASHWARNING	= 2
var MENU_MAIN			= 3
var MENU_CHOOSER		= 4
var MENU_MP				= 5
var MENU_HOSTGAME		= 6
var MENU_JOINGAME		= 7
var MENU_OPTIONS		= 8

# Main Menu Theme
var samplePlayer = StreamPlayer.new()
var songs = [load("res://main_theme_antris.ogg")]

# Loader dialog
var saveDir
var fileDialog

func skipTitle(skip):
	if skip:
		initialWait  = 0.01
		splashTimer  = 0.01
		warningTimer = 0.01

# Function to be called once for setup.
func _ready():
	# Initial setup.
	menuOn = MENU_INIT
	timer = 0.0
	set_process( true )
	set_process_input( true )

	# Setup the splash fader tween.
	splashTween = Tween.new()
	get_node( "SplashFader" ).add_child( splashTween )
	splashTween.interpolate_method( get_node( "SplashFader" ), "set_opacity", 1.0, 0.0, 1.0, Tween.TRANS_CUBIC, Tween.EASE_IN_OUT )

	# Gather all of the menus.
	Splash_Team5 = get_node( "SplashTeam5" )
	Splash_Warning = get_node( "SplashWarning" )
	Menu_Main = get_node( "MainMenu" )
	Menu_Chooser = get_node( "ChooserMenu" )
	Menu_MP = get_node( "Multiplayer" )
	Menu_HostGame = get_node( "HostGame" )
	Menu_JoinGame = get_node( "JoinGame" )
	Menu_Options = get_node( "OptionsMenu" )

	Globals.set("Network", load("res://scripts/Network.gd").new())
	network = Globals.get("Network")

	var scn = load("res://networkProxy.scn")
	add_child(scn.instance())
	network.root = get_tree().get_root()

	# Load the config.
	var config = preload( "res://scripts/DataManager.gd" ).new().loadConfig()

	get_node("OptionsMenu/Panel/OnlineName/LineEdit").set_text( config.name )
	get_node("OptionsMenu/Panel/SoundVolume/SoundSlider").set_value( config.soundvolume )
	get_node("OptionsMenu/Panel/MusicVolume/MusicSlider").set_value( config.musicvolume )

	network.port = config.portnumber

	# Prepare puzzle loader dialog
	saveDir = OS.get_data_dir() + "/PuzzleSaves"
	fileDialog = preload("Editor.gd").initLoadSaveDialog(self, get_tree(), saveDir)

	# Main Menu Theme
	Globals.set("StreamPlayer", samplePlayer)
	add_child(samplePlayer)
	samplePlayer.set_stream(songs[0])
	samplePlayer.play()

# Function to update the GUI.
func _process( delta ):
	# Handle the initial wait.
	if( menuOn == MENU_INIT ):
		if( timer > initialWait ):
			menuOn = MENU_SPLASHTEAM5
			timer = 0.0
			Splash_Team5.guiIn()

	# Handle the Team5 splash screen.
	if( menuOn == MENU_SPLASHTEAM5 ):
		if( timer > splashTimer ):
			menuOn = MENU_SPLASHWARNING
			timer = 0.0
			Splash_Team5.guiOut()
			Splash_Warning.guiIn()

	# Handle the warning splash screen.
	if( menuOn == MENU_SPLASHWARNING ):
		if( timer > warningTimer ):
			menuOn = MENU_MAIN
			timer = 0.0
			Splash_Warning.guiOut()
			Menu_Main.guiIn()
			splashTween.start()

	# Increase the timer each frame.
	timer += delta

# Handle the input events.
func _input( ev ):
	# Handle skip splash screens.
	if( !ev.is_pressed() and ev.type==InputEvent.MOUSE_BUTTON and ev.button_index==BUTTON_LEFT ):
		if( menuOn == MENU_SPLASHTEAM5 || menuOn == MENU_SPLASHWARNING ):
			timer = 999.0;

	# Handle exit.
	if( ev.is_action( "ui_cancel" ) ):
		exit()

func exit():
	print( "Exiting" )
	OS.get_main_loop().quit()

func _on_Exit_pressed():
	exit()

# TODO save to ConfigFile, load this on beginning
func _on_Options_pressed():
	Menu_Main.guiOut()
	Menu_Options.guiIn()
	timer = 0.0
	menuOn = MENU_OPTIONS
	get_node("OptionsMenu/Panel/PortField/LineEdit").set_text(str(network.port))

func _on_Cancel_pressed():
	Menu_Options.guiOut()
	Menu_Main.guiIn()
	timer = 0.0
	menuOn = MENU_MAIN

func _on_SaveQuit_pressed():
	# Save the options.

	var config = { name = get_node("OptionsMenu/Panel/OnlineName/LineEdit").get_text()
				  , soundvolume = get_node("OptionsMenu/Panel/SoundVolume/SoundSlider").get_value()
				  , musicvolume = get_node("OptionsMenu/Panel/MusicVolume/MusicSlider").get_value()
				  , portnumber = get_node("OptionsMenu/Panel/PortField/LineEdit").get_text()
				  }

	preload( "res://scripts/DataManager.gd" ).new().saveConfig( config )

	var field = get_node("OptionsMenu/Panel/PortField/LineEdit")
	network.setPort(field.get_text())
	network.setPort(field.get_text())
	_on_Cancel_pressed()

func _on_SP_pressed():
	Menu_Main.guiOut()
	Menu_Chooser.guiIn()
	timer = 0.0
	menuOn = MENU_CHOOSER

func _on_MainMenu_pressed():
	Menu_Chooser.guiOut()
	Menu_Main.guiIn()
	timer = 0.0
	menuOn = MENU_MAIN

func _on_MainMenuMP_pressed():
	Menu_MP.guiOut()
	Menu_Main.guiIn()
	timer = 0.0
	menuOn = MENU_MAIN

func _on_MP_pressed():
	Menu_Main.guiOut()
	Menu_MP.guiIn()
	timer = 0.0
	menuOn = MENU_MP

func _on_MainMenuHG_pressed():
	Menu_HostGame.guiOut()
	Menu_Main.guiIn()
	timer = 0.0
	menuOn = MENU_MAIN
	network.disconnect()

func _on_HostGame_pressed():
	Menu_MP.guiOut()
	Menu_HostGame.guiIn()
	timer = 0.0
	menuOn = MENU_HOSTGAME

	if !network.isHost and !network.isNetwork:
		print("calling!")
		network.host(network.port)
		get_node("HostGame/Panel/Waiting").set_text("Waiting for player to join on port " + str(network.port) + "...")

func _on_JoinGame_pressed():
	Menu_MP.guiOut()
	Menu_JoinGame.guiIn()
	timer = 0.0
	menuOn = MENU_JOINGAME

func _on_MainMenuJG_pressed():
	Menu_JoinGame.guiOut()
	Menu_Main.guiIn()
	timer = 0.0
	menuOn = MENU_MAIN
	if (network.isClient):
		network.disconnect()

func _on_RandomPuzzle_pressed(puzzle=null):
	gotoPuzzleScene(get_tree().get_root(), false, puzzle)

static func gotoPuzzleScene(root, networked=false, puzzle=null):
	var p = load( "res://puzzle.scn" ).instance()
	p.mainPuzzle = networked

	if puzzle != null:
		p.generateRandom = false
		p.get_node("GridView/GridMan").set_puzzle(puzzle)
		p.get_node("GridView/GridMan").setupCam()

	# called on idle time
	goto_scene(root.get_tree(), [load("res://puzzleView.scn").instance(), p])

static func goto_scene(tree, scenes, freeAll=false):
	var root = tree.get_root()
	root.print_tree()
	if freeAll:
		for child in root.get_children():
			child.queue_free()
	else:
		root.get_child( root.get_child_count() - 1 ).queue_free()
	for scn in scenes:
		scn.set_owner(root)
		root.call_deferred("add_child", scn )

func _on_Join_pressed():
	var IPPanel = get_node("JoinGame/Panel/IPAddress")
	var ip = IPPanel.get_text();

	if (ip.empty()):
		return

	if !network.isClient:
		network.connectTo(ip, network.port)

func _on_Editor_pressed():
	# called on idle time
	goto_scene(get_tree(), [load("res://editor.scn").instance()])

func _on_LoadPuzzle_pressed():
	preload("Editor.gd").showLoadDialog(fileDialog, self)

# called by the fileDialog
func puzzleLoad():
	var f = fileDialog.get_current_path()
	if f == null or f == "":
		return
	print("LOADING FROM ", f)
	_on_RandomPuzzle_pressed(preload("DataManager.gd").loadPuzzle( f ))

# This script manages all of the GUI elements.
# It switches between the states of the GUI allowing
# the user to start games, edit the options or connect
# to a multiplayer game.

extends Node

# network variables
var network

# State variables.
var menuOn
var splashTween

# Timers.
var timer
var initialWait  = 0.5
var splashTimer  = 2.0
var warningTimer = 5.0

# GUI pieces.
var Splash_Team5
var Splash_Warning
var Menu_Main
var Menu_Chooser
var Menu_MP
var Menu_HostGame
var Menu_JoinGame
var Menu_Options

# Menu state values. Used to switch between them.
var MENU_INIT			= 0
var MENU_SPLASHTEAM5	= 1
var MENU_SPLASHWARNING	= 2
var MENU_MAIN			= 3
var MENU_CHOOSER		= 4
var MENU_MP				= 5
var MENU_HOSTGAME		= 6
var MENU_JOINGAME		= 7
var MENU_OPTIONS		= 8

# Main Menu Theme
var samplePlayer = StreamPlayer.new()
var songs = [load("res://main_theme_antris.ogg")]

# Loader dialog
var saveDir
var fileDialog

func skipTitle(skip):
	if skip:
		initialWait  = 0.01
		splashTimer  = 0.01
		warningTimer = 0.01

# Function to be called once for setup.
func _ready():
	# Initial setup.
	menuOn = MENU_INIT
	timer = 0.0
	set_process( true )
	set_process_input( true )

	# Setup the splash fader tween.
	splashTween = Tween.new()
	get_node( "SplashFader" ).add_child( splashTween )
	splashTween.interpolate_method( get_node( "SplashFader" ), "set_opacity", 1.0, 0.0, 1.0, Tween.TRANS_CUBIC, Tween.EASE_IN_OUT )

	# Gather all of the menus.
	Splash_Team5 = get_node( "SplashTeam5" )
	Splash_Warning = get_node( "SplashWarning" )
	Menu_Main = get_node( "MainMenu" )
	Menu_Chooser = get_node( "ChooserMenu" )
	Menu_MP = get_node( "Multiplayer" )
	Menu_HostGame = get_node( "HostGame" )
	Menu_JoinGame = get_node( "JoinGame" )
	Menu_Options = get_node( "OptionsMenu" )

	Globals.set("Network", load("res://scripts/Network.gd").new())
	network = Globals.get("Network")

	var scn = load("res://networkProxy.scn")
	add_child(scn.instance())
	network.root = get_tree().get_root()

	# Load the config.
	var config = preload( "res://scripts/DataManager.gd" ).new().loadConfig()

	get_node("OptionsMenu/Panel/OnlineName/LineEdit").set_text( config.name )
	get_node("OptionsMenu/Panel/SoundVolume/SoundSlider").set_value( config.soundvolume )
	get_node("OptionsMenu/Panel/MusicVolume/MusicSlider").set_value( config.musicvolume )

	network.port = config.portnumber

	# Prepare puzzle loader dialog
	saveDir = OS.get_data_dir() + "/PuzzleSaves"
	fileDialog = preload("Editor.gd").initLoadSaveDialog(self, get_tree(), saveDir)

	# Main Menu Theme
	Globals.set("StreamPlayer", samplePlayer)
	add_child(samplePlayer)
	samplePlayer.set_stream(songs[0])
	samplePlayer.play()

# Function to update the GUI.
func _process( delta ):
	# Handle the initial wait.
	if( menuOn == MENU_INIT ):
		if( timer > initialWait ):
			menuOn = MENU_SPLASHTEAM5
			timer = 0.0
			Splash_Team5.guiIn()

	# Handle the Team5 splash screen.
	if( menuOn == MENU_SPLASHTEAM5 ):
		if( timer > splashTimer ):
			menuOn = MENU_SPLASHWARNING
			timer = 0.0
			Splash_Team5.guiOut()
			Splash_Warning.guiIn()

	# Handle the warning splash screen.
	if( menuOn == MENU_SPLASHWARNING ):
		if( timer > warningTimer ):
			menuOn = MENU_MAIN
			timer = 0.0
			Splash_Warning.guiOut()
			Menu_Main.guiIn()
			splashTween.start()

	# Increase the timer each frame.
	timer += delta

# Handle the input events.
func _input( ev ):
	# Handle skip splash screens.
	if( !ev.is_pressed() and ev.type==InputEvent.MOUSE_BUTTON and ev.button_index==BUTTON_LEFT ):
		if( menuOn == MENU_SPLASHTEAM5 || menuOn == MENU_SPLASHWARNING ):
			timer = 999.0;

	# Handle exit.
	if( ev.is_action( "ui_cancel" ) ):
		exit()

func exit():
	print( "Exiting" )
	OS.get_main_loop().quit()

func _on_Exit_pressed():
	exit()

# TODO save to ConfigFile, load this on beginning
func _on_Options_pressed():
	Menu_Main.guiOut()
	Menu_Options.guiIn()
	timer = 0.0
	menuOn = MENU_OPTIONS
	get_node("OptionsMenu/Panel/PortField/LineEdit").set_text(str(network.port))

func _on_Cancel_pressed():
	Menu_Options.guiOut()
	Menu_Main.guiIn()
	timer = 0.0
	menuOn = MENU_MAIN

func _on_SaveQuit_pressed():
	# Save the options.

	var config = { name = get_node("OptionsMenu/Panel/OnlineName/LineEdit").get_text()
				  , soundvolume = get_node("OptionsMenu/Panel/SoundVolume/SoundSlider").get_value()
				  , musicvolume = get_node("OptionsMenu/Panel/MusicVolume/MusicSlider").get_value()
				  , portnumber = get_node("OptionsMenu/Panel/PortField/LineEdit").get_text()
				  }

	preload( "res://scripts/DataManager.gd" ).new().saveConfig( config )

	var field = get_node("OptionsMenu/Panel/PortField/LineEdit")
	network.setPort(field.get_text())
	network.setPort(field.get_text())
	_on_Cancel_pressed()

func _on_SP_pressed():
	Menu_Main.guiOut()
	Menu_Chooser.guiIn()
	timer = 0.0
	menuOn = MENU_CHOOSER

func _on_MainMenu_pressed():
	Menu_Chooser.guiOut()
	Menu_Main.guiIn()
	timer = 0.0
	menuOn = MENU_MAIN

func _on_MainMenuMP_pressed():
	Menu_MP.guiOut()
	Menu_Main.guiIn()
	timer = 0.0
	menuOn = MENU_MAIN

func _on_MP_pressed():
	Menu_Main.guiOut()
	Menu_MP.guiIn()
	timer = 0.0
	menuOn = MENU_MP

func _on_MainMenuHG_pressed():
	Menu_HostGame.guiOut()
	Menu_Main.guiIn()
	timer = 0.0
	menuOn = MENU_MAIN
	network.disconnect()

func _on_HostGame_pressed():
	Menu_MP.guiOut()
	Menu_HostGame.guiIn()
	timer = 0.0
	menuOn = MENU_HOSTGAME

	if !network.isHost and !network.isNetwork:
		print("calling!")
		network.host(network.port)
		get_node("HostGame/Panel/Waiting").set_text("Waiting for player to join on port " + str(network.port) + "...")

func _on_JoinGame_pressed():
	Menu_MP.guiOut()
	Menu_JoinGame.guiIn()
	timer = 0.0
	menuOn = MENU_JOINGAME

func _on_MainMenuJG_pressed():
	Menu_JoinGame.guiOut()
	Menu_Main.guiIn()
	timer = 0.0
	menuOn = MENU_MAIN
	if (network.isClient):
		network.disconnect()

func _on_RandomPuzzle_pressed(puzzle=null):
	gotoPuzzleScene(get_tree().get_root(), false, puzzle)

static func gotoPuzzleScene(root, networked=false, puzzle=null):
	root.get_child( root.get_child_count() - 1 ).queue_free()
	root.add_child( preload( "res://puzzleView.scn" ).instance() )
	var p = preload( "res://puzzle.scn" ).instance()
	p.mainPuzzle = networked

	if puzzle != null:
		p.generateRandom = false
		p.get_node("GridView/GridMan").set_puzzle(puzzle)
		p.get_node("GridView/GridMan").setupCam()

	root.add_child( p )

func _on_Join_pressed():
	var IPPanel = get_node("JoinGame/Panel/IPAddress")
	var ip = IPPanel.get_text();

	if (ip.empty()):
		return

	if !network.isClient:
		network.connectTo(ip, network.port)

func _on_Editor_pressed():
	var root = get_tree().get_root()
	root.get_child( root.get_child_count() - 1 ).queue_free()
	root.add_child( ResourceLoader.load( "res://editor.scn" ).instance() )

func _on_LoadPuzzle_pressed():
	preload("Editor.gd").showLoadDialog(fileDialog, self)

# called by the fileDialog
func puzzleLoad():
	var f = fileDialog.get_current_path()
	if f == null or f == "":
		return
	print("LOADING FROM ", f)
	_on_RandomPuzzle_pressed(preload("DataManager.gd").loadPuzzle( f ))


beaten and won game over popups

beaten and won game over popups

extends Spatial

# Is there a better way to do this?
const BLOCK_LASER	= 0
const BLOCK_WILD	= 1
const BLOCK_PAIR	= 2
const BLOCK_GOAL	= 3
const BLOCK_BLOCK   = 4

# Block selection handling.
var offClick = false
var selectedBlocks = []

# Puzzle vars.
var puzzle
var puzzleLoaded = false
var blockNodes = {}

# Beam stuff.
const beamScn = preload( "res://blocks/block.scn" )
const Beam = preload("res://scripts/Blocks/Beam.gd")

# sounds
var samplePlayer = SamplePlayer.new()

func addPickledBlock(block):
	var b = block.toNode()

	# TODO  any special treatment for the wild blocks?
	# TODO  keep track of puzzle.lasers ? How to?
	blockNodes[block.blockPos] = b

	if puzzleLoaded:
		# keep pickled block
		puzzle.shape[block.blockPos] = block

		# keep track of puzzle.pairCounts
		var layer = calcBlockLayerVec(b.blockPos)
		if b.getBlockType() == 2:
			while puzzle.pairCount.size() <= layer:
				puzzle.pairCount.append(0)
			puzzle.puzzleLayers = puzzle.pairCount.size() - 1
			puzzle.pairCount[layer] += 0.5

	add_child(b)
	return b

func get_block(pos):
	if blockNodes.has(pos):
		return blockNodes[pos]
	else:
		return null

# unused key argument needed for the tween_complete signal
func remove_block(block, key=null):
	var block_node = blockNodes[block.blockPos]

	puzzle.shape[block_node.blockPos] = null
	blockNodes[block_node.blockPos] = null

	if block_node == null:
		return

	for child in block_node.get_children():
		block_node.remove_and_delete_child(child)
	remove_and_delete_child(block_node)

# Sets the puzzle for this GridMan.
func set_puzzle(puzz):
	# verify puzzle
	if puzz == null:
		print("INVALID PUZZLE")
		return

	# delete all current nodes
	if puzzle != null:
		for pos in puzzle.shape:
			remove_block(puzzle.shape[pos])
	puzzleLoaded = false

	# Store the puzzle.
	puzzle = puzz

		# # I can do my own counting!
	# needed for adding blocks in the editor
	for k in puzzle.shape:
		# Create a block node, add it to the tree
		addPickledBlock(puzzle.shape[k])
	puzzleLoaded = true

# Clears any selected blocks. WE SHOULD FIX THIS, THERE CAN ONLY BE ONE BLOCK SELECTED AT ANY ONE TIME, NO NEED FOR AN ARRAY!
func clearSelection():
	for bl in selectedBlocks:
		var blo = get_node( bl )
		blo.setSelected(false)
		blo.scaleTweenNode(1.0).start()
	selectedBlocks = []

# Add the selected block to the selected list. WE SHOULD FIX THIS, IT ONLY NEEDS TO STORE IT, NOT AN ARRAY!
func addSelected(bl):
	selectedBlocks.append(bl)

# Used for the multiplayer mode to force click a block on their side.
func forceClickBlock( pos ):
	blockNodes[pos].forceClick()

func clickBlock( name ):
	#now check if that was the second block we picked. If it was, we want to
	#unselect the blocks again
	if (offClick):
		offClick = false
		addSelected(name)
		clearSelection()
	else:
		offClick = true;
		addSelected(name)

# Calculates the layer that a block is on.
# COPY OF FUNCTION IN PUZZLEMAN, IS THERE A BETTER WAY TO DO THIS?!
func calcBlockLayer( x, y, z ):
	return max( max( abs( x ), abs( y ) ), abs( z ) )
func calcBlockLayerVec( pos ):
	return max( max( abs( pos.x ), abs( pos.y ) ), abs( pos.z ) )

func beatenWithScore(othersScore):
	print( "BEATEN!" )
	var pauseMenu = get_tree().get_root().get_node( "Spatial" ).get_node( "Camera" ).pauseMenu
	pauseMenu.set_text("GAME OVER\nYou've been beaten with time: " + othersScore)
	pauseMenu.popup_centered()

func win():
	print( "GAME OVER!" )
	var pauseMenu = get_tree().get_root().get_node( "Spatial" ).get_node( "Camera" ).pauseMenu
	pauseMenu.set_text("GAME OVER\nYou win with time: " + get_parent().get_parent().time.val)
	pauseMenu.popup_centered()

# Handles keeping track of pairs being removed.
func popPair( pos ):
	var blayer = calcBlockLayerVec( pos )
	puzzle.pairCount[blayer] -= 1

	if( puzzle.pairCount[blayer] == 0 ):
		print("LAYER CLEARED")
		if blayer == 1:
			win()

		for b in puzzle.shape:
			if not ( puzzle.shape[b] == null ):
				if calcBlockLayerVec( b ) == blayer:
					if blockNodes[b].getBlockType() == BLOCK_LASER:
						blockNodes[b].forceActivate()

		# Fire beams.
		var beamNum = 0
		for l in range( puzzle.lasers.size() ):
			if calcBlockLayerVec( puzzle.lasers[l][0] ) == blayer:
				# Firin mah lazerz!
				var beam = beamScn.instance()

				beam.set_name( str(blayer) + "_beam_" + str(beamNum) )
				beamNum += 1
				beam.set_script( Beam )

				add_child( beam )
				beam.fire( puzzle.lasers[l][0], puzzle.lasers[l][1] )

				samplePlayer.play( "soundslikewillem_hitting_slinky" )

func _init():
	# Load sound
	samplePlayer.set_voice_count(10)
	samplePlayer.set_sample_library(ResourceLoader.load("new_samplelibrary.xml"))
	print("GridMan initialized")

func _ready():
	setupCam()

func setupCam():
	var cam = get_tree().get_root().get_node( "Spatial/Camera" )
	if cam == null or puzzle == null:
		return
	var totalSize = ( puzzle.puzzleLayers * 2 + 1 )
	cam.distance.val = 4.5 * totalSize
	cam.distance.min_ = 3 * totalSize
	cam.distance.max_ = 10 * totalSize
	cam.recalculate_camera()

extends Spatial

# Is there a better way to do this?
const BLOCK_LASER	= 0
const BLOCK_WILD	= 1
const BLOCK_PAIR	= 2
const BLOCK_GOAL	= 3
const BLOCK_BLOCK   = 4

# Block selection handling.
var offClick = false
var selectedBlocks = []

# Puzzle vars.
var puzzle
var puzzleLoaded = false
var blockNodes = {}

# Beam stuff.
const beamScn = preload( "res://blocks/block.scn" )
const Beam = preload("res://scripts/Blocks/Beam.gd")

# sounds
var samplePlayer = SamplePlayer.new()

func addPickledBlock(block):
	var b = block.toNode()

	# TODO  any special treatment for the wild blocks?
	# TODO  keep track of puzzle.lasers ? How to?
	blockNodes[block.blockPos] = b

	if puzzleLoaded:
		# keep pickled block
		puzzle.shape[block.blockPos] = block

		# keep track of puzzle.pairCounts
		var layer = calcBlockLayerVec(b.blockPos)
		if b.getBlockType() == 2:
			while puzzle.pairCount.size() <= layer:
				puzzle.pairCount.append(0)
			puzzle.puzzleLayers = puzzle.pairCount.size() - 1
			puzzle.pairCount[layer] += 0.5

	add_child(b)
	return b

func get_block(pos):
	if blockNodes.has(pos):
		return blockNodes[pos]
	else:
		return null

# unused key argument needed for the tween_complete signal
func remove_block(block, key=null):
	var block_node = blockNodes[block.blockPos]

	puzzle.shape[block_node.blockPos] = null
	blockNodes[block_node.blockPos] = null

	if block_node == null:
		return

	for child in block_node.get_children():
		block_node.remove_and_delete_child(child)
	remove_and_delete_child(block_node)

# Sets the puzzle for this GridMan.
func set_puzzle(puzz):
	# verify puzzle
	if puzz == null:
		print("INVALID PUZZLE")
		return

	# delete all current nodes
	if puzzle != null:
		for pos in puzzle.shape:
			remove_block(puzzle.shape[pos])
	puzzleLoaded = false

	# Store the puzzle.
	puzzle = puzz

		# # I can do my own counting!
	# needed for adding blocks in the editor
	for k in puzzle.shape:
		# Create a block node, add it to the tree
		addPickledBlock(puzzle.shape[k])
	puzzleLoaded = true

# Clears any selected blocks. WE SHOULD FIX THIS, THERE CAN ONLY BE ONE BLOCK SELECTED AT ANY ONE TIME, NO NEED FOR AN ARRAY!
func clearSelection():
	for bl in selectedBlocks:
		var blo = get_node( bl )
		blo.setSelected(false)
		blo.scaleTweenNode(1.0).start()
	selectedBlocks = []

# Add the selected block to the selected list. WE SHOULD FIX THIS, IT ONLY NEEDS TO STORE IT, NOT AN ARRAY!
func addSelected(bl):
	selectedBlocks.append(bl)

# Used for the multiplayer mode to force click a block on their side.
func forceClickBlock( pos ):
	blockNodes[pos].forceClick()

func clickBlock( name ):
	#now check if that was the second block we picked. If it was, we want to
	#unselect the blocks again
	if (offClick):
		offClick = false
		addSelected(name)
		clearSelection()
	else:
		offClick = true;
		addSelected(name)

# Calculates the layer that a block is on.
# COPY OF FUNCTION IN PUZZLEMAN, IS THERE A BETTER WAY TO DO THIS?!
func calcBlockLayer( x, y, z ):
	return max( max( abs( x ), abs( y ) ), abs( z ) )
func calcBlockLayerVec( pos ):
	return max( max( abs( pos.x ), abs( pos.y ) ), abs( pos.z ) )

# Handles keeping track of pairs being removed.
func popPair( pos ):
	var blayer = calcBlockLayerVec( pos )
	puzzle.pairCount[blayer] -= 1

	if( puzzle.pairCount[blayer] == 0 ):
		print("LAYER CLEARED")
		if blayer == 1:
			print( "GAME OVER!" )
			var pauseMenu = get_tree().get_root().get_node( "Spatial" ).get_node( "Camera" ).pauseMenu
			pauseMenu.set_text("GAME OVER\nSCORE:1,000,000")
			# TODO set timeout!!!
			pauseMenu.popup_centered()

		for b in puzzle.shape:
			if not ( puzzle.shape[b] == null ):
				if calcBlockLayerVec( b ) == blayer:
					if blockNodes[b].getBlockType() == BLOCK_LASER:
						blockNodes[b].forceActivate()

		# Fire beams.
		var beamNum = 0
		for l in range( puzzle.lasers.size() ):
			if calcBlockLayerVec( puzzle.lasers[l][0] ) == blayer:
				# Firin mah lazerz!
				var beam = beamScn.instance()

				beam.set_name( str(blayer) + "_beam_" + str(beamNum) )
				beamNum += 1
				beam.set_script( Beam )

				add_child( beam )
				beam.fire( puzzle.lasers[l][0], puzzle.lasers[l][1] )

				samplePlayer.play( "soundslikewillem_hitting_slinky" )

func _init():
	# Load sound
	samplePlayer.set_voice_count(10)
	samplePlayer.set_sample_library(ResourceLoader.load("new_samplelibrary.xml"))
	print("GridMan initialized")

func _ready():
	setupCam()

func setupCam():
	var cam = get_tree().get_root().get_node( "Spatial/Camera" )
	if cam == null or puzzle == null:
		return
	var totalSize = ( puzzle.puzzleLayers * 2 + 1 )
	cam.distance.val = 4.5 * totalSize
	cam.distance.min_ = 3 * totalSize
	cam.distance.max_ = 10 * totalSize
	cam.recalculate_camera()


Fix incorrect comment in hierarchical FSM demo script

This closes #433.

extends Node
# Base interface for a generic state machine.
# It handles initializing, setting the machine active or not
# delegating _physics_process, _input calls to the State nodes,
# and changing the current/active state.
# See the PlayerV2 scene for an example on how to use it.

signal state_changed(current_state)

# You should set a starting node from the inspector or on the node that inherits
# from this state machine interface. If you don't, the game will default to
# the first state in the state machine's children.
export(NodePath) var start_state
var states_map = {}

var states_stack = []
var current_state = null
var _active = false setget set_active

func _ready():
	if not start_state:
		start_state = get_child(0).get_path()
	for child in get_children():
		child.connect("finished", self, "_change_state")
	initialize(start_state)

func initialize(initial_state):
	set_active(true)
	states_stack.push_front(get_node(initial_state))
	current_state = states_stack[0]
	current_state.enter()

func set_active(value):
	_active = value
	set_physics_process(value)
	set_process_input(value)
	if not _active:
		states_stack = []
		current_state = null

func _input(event):
	current_state.handle_input(event)

func _physics_process(delta):
	current_state.update(delta)

func _on_animation_finished(anim_name):
	if not _active:
		return
	current_state._on_animation_finished(anim_name)

func _change_state(state_name):
	if not _active:
		return
	current_state.exit()

	if state_name == "previous":
		states_stack.pop_front()
	else:
		states_stack[0] = states_map[state_name]

	current_state = states_stack[0]
	emit_signal("state_changed", current_state)

	if state_name != "previous":
		current_state.enter()

extends Node
# Base interface for a generic state machine.
# It handles initializing, setting the machine active or not
# delegating _physics_process, _input calls to the State nodes,
# and changing the current/active state.
# See the PlayerV2 scene for an example on how to use it.

signal state_changed(current_state)

# You must set a starting node from the inspector or on
# the node that inherits from this state machine interface.
# If you don't the game will crash (on purpose, so you won't
# forget to initialize the state machine).
export(NodePath) var start_state
var states_map = {}

var states_stack = []
var current_state = null
var _active = false setget set_active

func _ready():
	if not start_state:
		start_state = get_child(0).get_path()
	for child in get_children():
		child.connect("finished", self, "_change_state")
	initialize(start_state)

func initialize(initial_state):
	set_active(true)
	states_stack.push_front(get_node(initial_state))
	current_state = states_stack[0]
	current_state.enter()

func set_active(value):
	_active = value
	set_physics_process(value)
	set_process_input(value)
	if not _active:
		states_stack = []
		current_state = null

func _input(event):
	current_state.handle_input(event)

func _physics_process(delta):
	current_state.update(delta)

func _on_animation_finished(anim_name):
	if not _active:
		return
	current_state._on_animation_finished(anim_name)

func _change_state(state_name):
	if not _active:
		return
	current_state.exit()

	if state_name == "previous":
		states_stack.pop_front()
	else:
		states_stack[0] = states_map[state_name]

	current_state = states_stack[0]
	emit_signal("state_changed", current_state)

	if state_name != "previous":
		current_state.enter()


No change

No change

extends Node2D

# member variables here, example:
# var a=2
# var b="textvar"

func _ready():
	# Initalization here
	#var value = get_node("/root/global").getSavedHighScore()
	get_node("/root/global").saveHighScore()

	#get_node("/root/global").savedHighScore = "NO NO NO"
	#get_node("/root/global").savedHighTime = "NO NO NO"
	get_node("/root/global").savedHighDifficulty = "NO NO NO"
	get_node("/root/global").savedHighName = "NO NO NO"

	get_node("/root/global").loadHighScore()

	get_node("Panel").get_node("Label 1").set_text(get_node("/root/global").savedHighScore)
	get_node("Panel").get_node("Label 2").set_text(get_node("/root/global").savedHighTime)
	get_node("Panel").get_node("Label 3").set_text(get_node("/root/global").savedHighDifficulty)
	get_node("Panel").get_node("Label 4").set_text(get_node("/root/global").savedHighName)

extends Node2D

# member variables here, example:
# var a=2
# var b="textvar"

func _ready():
	# Initalization here
	#var value = get_node("/root/global").getSavedHighScore()
	get_node("/root/global").saveHighScore()

	get_node("/root/global").savedHighScore = "NO NO NO"
	get_node("/root/global").savedHighTime = "NO NO NO"
	get_node("/root/global").savedHighDifficulty = "NO NO NO"
	get_node("/root/global").savedHighName = "NO NO NO"

	get_node("/root/global").loadHighScore()

	get_node("Panel").get_node("Label 1").set_text(get_node("/root/global").savedHighScore)
	get_node("Panel").get_node("Label 2").set_text(get_node("/root/global").savedHighTime)
	get_node("Panel").get_node("Label 3").set_text(get_node("/root/global").savedHighDifficulty)
	get_node("Panel").get_node("Label 4").set_text(get_node("/root/global").savedHighName)


Fixed style issues in IK Demo. Added a bit of static typing hints to the LookAt IK file to better fit the rest of the scripts

tool
extends Spatial

export(NodePath) var skeleton_path setget _set_skeleton_path
export(String) var bone_name = ""
export(int, "_process", "_physics_process", "_notification", "none") var update_mode = 0 setget _set_update
export(int, "X-up", "Y-up", "Z-up") var look_at_axis = 1
export(bool) var use_our_rotation_x = false
export(bool) var use_our_rotation_y = false
export(bool) var use_our_rotation_z = false
export(bool) var use_negative_our_rot = false
export(Vector3) var additional_rotation = Vector3()
export(bool) var position_using_additional_bone = false
export(String) var additional_bone_name = ""
export(float) var additional_bone_length = 1
export(bool) var debug_messages = false

var skeleton_to_use: Skeleton = null
var first_call: bool = true
var _editor_indicator: Spatial = null

func _ready():
	set_process(false)
	set_physics_process(false)
	set_notify_transform(false)

	if update_mode == 0:
		set_process(true)
	elif update_mode == 1:
		set_physics_process(true)
	elif update_mode == 2:
		set_notify_transform(true)
	else:
		if debug_messages == true:
			print (name, " - IK_LookAt: Unknown update mode. NOT updating skeleton")

	if Engine.editor_hint == true:
		_setup_for_editor()

func _process(_delta):
	update_skeleton()

func _physics_process(_delta):
	update_skeleton()

func _notification(what):
	if what == NOTIFICATION_TRANSFORM_CHANGED:
		update_skeleton()

func update_skeleton():
	# NOTE: Because get_node doesn't work in _ready, we need to skip
	# a call before doing anything.
	if first_call == true:
		first_call = false
		if skeleton_to_use == null:
			_set_skeleton_path(skeleton_path)

	# If we do not have a skeleton and/or we're not supposed to update, then return.
	if skeleton_to_use == null:
		return
	if update_mode >= 3:
		return

	# Get the bone
	var bone : int = skeleton_to_use.find_bone(bone_name)

	# If no bone is found (-1), then return (and optionally print an error)
	if bone == -1:
		if debug_messages == true:
			print (name, " - IK_LookAt: No bone in skeleton found with name [", bone_name, "]!")
		return

	# get the bone's rest position
	var rest = skeleton_to_use.get_bone_global_pose(bone)

	# Convert our position relative to the skeleton's transform
	var target_pos = skeleton_to_use.global_transform.xform_inv(global_transform.origin)

	# Call helper's look_at function with the chosen up axis.
	if look_at_axis == 0:
		rest = rest.looking_at(target_pos, Vector3(1, 0, 0))
	elif look_at_axis == 1:
		rest = rest.looking_at(target_pos, Vector3(0, 1, 0))
	elif look_at_axis == 2:
		rest = rest.looking_at(target_pos, Vector3(0, 0, 1))
	else:
		rest = rest.looking_at(target_pos, Vector3(0, 1, 0))
		if debug_messages == true:
			print (name, " - IK_LookAt: Unknown look_at_axis value!")

	# Get our rotation euler, and the bone's rotation euler
	var rest_euler = rest.basis.get_euler()
	var self_euler = global_transform.basis.orthonormalized().get_euler()

	# If we using negative rotation, we flip our rotation euler
	if use_negative_our_rot == true:
		self_euler = -self_euler

	# Apply our rotation euler, if wanted/required
	if use_our_rotation_x == true:
		rest_euler.x = self_euler.x
	if use_our_rotation_y == true:
		rest_euler.y = self_euler.y
	if use_our_rotation_z == true:
		rest_euler.z = self_euler.z

	# Rotate the bone by the (potentially) changed euler angle(s)
	rest.basis = Basis(rest_euler)

	# If we have additional rotation, then rotate it by the local rotation vectors
	if additional_rotation != Vector3.ZERO:
		rest.basis = rest.basis.rotated(rest.basis.x, deg2rad(additional_rotation.x))
		rest.basis = rest.basis.rotated(rest.basis.y, deg2rad(additional_rotation.y))
		rest.basis = rest.basis.rotated(rest.basis.z, deg2rad(additional_rotation.z))

	if position_using_additional_bone:
		var additional_bone_id = skeleton_to_use.find_bone(additional_bone_name)
		var additional_bone_pos = skeleton_to_use.get_bone_global_pose(additional_bone_id)
		rest.origin = additional_bone_pos.origin - additional_bone_pos.basis.z.normalized() * additional_bone_length

	# Finally, apply the bone rotation to the skeleton
	skeleton_to_use.set_bone_global_pose_override(bone, rest, 1.0, true)

func _setup_for_editor():
	# So we can see the target in the editor, let's create a mesh instance,
	# Add it as our child, and name it
	_editor_indicator = MeshInstance.new()
	add_child(_editor_indicator)
	_editor_indicator.name = "(EditorOnly) Visual indicator"

	# We need to make a mesh for the mesh instance.
	# The code below makes a small sphere mesh
	var indicator_mesh = SphereMesh.new()
	indicator_mesh.radius = 0.1
	indicator_mesh.height = 0.2
	indicator_mesh.radial_segments = 8
	indicator_mesh.rings = 4

	# The mesh needs a material (unless we want to use the defualt one).
	# Let's create a material and use the EditorGizmoTexture to texture it.
	var indicator_material = SpatialMaterial.new()
	indicator_material.flags_unshaded = true
	indicator_material.albedo_texture = preload("editor_gizmo_texture.png")
	indicator_material.albedo_color = Color(1, 0.5, 0, 1)
	indicator_mesh.material = indicator_material
	_editor_indicator.mesh = indicator_mesh

func _set_update(new_value):
	update_mode = new_value

	# Set all of our processes to false
	set_process(false)
	set_physics_process(false)
	set_notify_transform(false)

	# Based on the value of upate, change how we handle updating the skeleton
	if update_mode == 0:
		set_process(true)
		if debug_messages == true:
			print (name, " - IK_LookAt: updating skeleton using _process...")
	elif update_mode == 1:
		set_physics_process(true)
		if debug_messages == true:
			print (name, " - IK_LookAt: updating skeleton using _physics_process...")
	elif update_mode == 2:
		set_notify_transform(true)
		if debug_messages == true:
			print (name, " - IK_LookAt: updating skeleton using _notification...")
	else:
		if debug_messages == true:
			print (name, " - IK_LookAt: NOT updating skeleton due to unknown update method...")

func _set_skeleton_path(new_value):
	# Because get_node doesn't work in the first call, we just want to assign instead
	# This is to get around a issue with NodePaths exposed to the editor
	if first_call == true:
		skeleton_path = new_value
		return

	# Assign skeleton_path to whatever value is passed
	skeleton_path = new_value

	if skeleton_path == null:
		if debug_messages == true:
			print (name, " - IK_LookAt: No Nodepath selected for skeleton_path!")
		return

	# Get the node at that location, if there is one
	var temp = get_node(skeleton_path)
	if temp != null:
		# If the node has the method "find_bone" then we can assume it is (likely) a skeleton
		if temp.has_method("find_bone") == true:
			skeleton_to_use = temp
			if debug_messages == true:
				print (name, " - IK_LookAt: attached to (new) skeleton")
		# If not, then it's (likely) not a skeleton
		else:
			skeleton_to_use = null
			if debug_messages == true:
				print (name, " - IK_LookAt: skeleton_path does not point to a skeleton!")
	else:
		if debug_messages == true:
			print (name, " - IK_LookAt: No Nodepath selected for skeleton_path!")

tool
extends Spatial

export(NodePath) var skeleton_path setget _set_skeleton_path
export(String) var bone_name = ""
export(int, "_process", "_physics_process", "_notification", "none") var update_mode = 0 setget _set_update
export(int, "X-up", "Y-up", "Z-up") var look_at_axis = 1
export(bool) var use_our_rotation_x = false
export(bool) var use_our_rotation_y = false
export(bool) var use_our_rotation_z = false
export(bool) var use_negative_our_rot = false
export(Vector3) var additional_rotation = Vector3()
export (bool) var position_using_additional_bone = false
export (String) var additional_bone_name = ""
export (float) var additional_bone_length = 1
export(bool) var debug_messages = false

var skeleton_to_use
var first_call = true
var _editor_indicator = null

func _ready():
	set_process(false)
	set_physics_process(false)
	set_notify_transform(false)

	if update_mode == 0:
		set_process(true)
	elif update_mode == 1:
		set_physics_process(true)
	elif update_mode == 2:
		set_notify_transform(true)
	else:
		if debug_messages == true:
			print (name, " - IK_LookAt: Unknown update mode. NOT updating skeleton")

	if Engine.editor_hint == true:
		_setup_for_editor()

func _process(_delta):
	update_skeleton()

func _physics_process(_delta):
	update_skeleton()

func _notification(what):
	if what == NOTIFICATION_TRANSFORM_CHANGED:
		update_skeleton()

func update_skeleton():
	# NOTE: Because get_node doesn't work in _ready, we need to skip
	# a call before doing anything.
	if first_call == true:
		first_call = false
		if skeleton_to_use == null:
			_set_skeleton_path(skeleton_path)

	# If we do not have a skeleton and/or we're not supposed to update, then return.
	if skeleton_to_use == null:
		return
	if update_mode >= 3:
		return

	# Get the bone
	var bone = skeleton_to_use.find_bone(bone_name)

	# If no bone is found (-1), then return (and optionally print an error)
	if bone == -1:
		if debug_messages == true:
			print (name, " - IK_LookAt: No bone in skeleton found with name [", bone_name, "]!")
		return

	# get the bone's rest position
	var rest = skeleton_to_use.get_bone_global_pose(bone)

	# Convert our position relative to the skeleton's transform
	var target_pos = skeleton_to_use.global_transform.xform_inv(global_transform.origin)

	# Call helper's look_at function with the chosen up axis.
	if look_at_axis == 0:
		rest = rest.looking_at(target_pos, Vector3(1, 0, 0))
	elif look_at_axis == 1:
		rest = rest.looking_at(target_pos, Vector3(0, 1, 0))
	elif look_at_axis == 2:
		rest = rest.looking_at(target_pos, Vector3(0, 0, 1))
	else:
		rest = rest.looking_at(target_pos, Vector3(0, 1, 0))
		if debug_messages == true:
			print (name, " - IK_LookAt: Unknown look_at_axis value!")

	# Get our rotation euler, and the bone's rotation euler
	var rest_euler = rest.basis.get_euler()
	var self_euler = global_transform.basis.orthonormalized().get_euler()

	# If we using negative rotation, we flip our rotation euler
	if use_negative_our_rot == true:
		self_euler = -self_euler

	# Apply our rotation euler, if wanted/required
	if use_our_rotation_x == true:
		rest_euler.x = self_euler.x
	if use_our_rotation_y == true:
		rest_euler.y = self_euler.y
	if use_our_rotation_z == true:
		rest_euler.z = self_euler.z

	# Rotate the bone by the (potentially) changed euler angle(s)
	rest.basis = Basis(rest_euler)

	# If we have additional rotation, then rotate it by the local rotation vectors
	if additional_rotation != Vector3.ZERO:
		rest.basis = rest.basis.rotated(rest.basis.x, deg2rad(additional_rotation.x))
		rest.basis = rest.basis.rotated(rest.basis.y, deg2rad(additional_rotation.y))
		rest.basis = rest.basis.rotated(rest.basis.z, deg2rad(additional_rotation.z))

	if position_using_additional_bone:
		var additional_bone_id = skeleton_to_use.find_bone(additional_bone_name)
		var additional_bone_pos = skeleton_to_use.get_bone_global_pose(additional_bone_id)
		rest.origin = additional_bone_pos.origin - additional_bone_pos.basis.z.normalized() * additional_bone_length

	# Finally, apply the bone rotation to the skeleton
	skeleton_to_use.set_bone_global_pose_override(bone, rest, 1.0, true)

func _setup_for_editor():
	# So we can see the target in the editor, let's create a mesh instance,
	# Add it as our child, and name it
	_editor_indicator = MeshInstance.new()
	add_child(_editor_indicator)
	_editor_indicator.name = "(EditorOnly) Visual indicator"

	# We need to make a mesh for the mesh instance.
	# The code below makes a small sphere mesh
	var indicator_mesh = SphereMesh.new()
	indicator_mesh.radius = 0.1
	indicator_mesh.height = 0.2
	indicator_mesh.radial_segments = 8
	indicator_mesh.rings = 4

	# The mesh needs a material (unless we want to use the defualt one).
	# Let's create a material and use the EditorGizmoTexture to texture it.
	var indicator_material = SpatialMaterial.new()
	indicator_material.flags_unshaded = true
	indicator_material.albedo_texture = preload("editor_gizmo_texture.png")
	indicator_material.albedo_color = Color(1, 0.5, 0, 1)
	indicator_mesh.material = indicator_material
	_editor_indicator.mesh = indicator_mesh

func _set_update(new_value):
	update_mode = new_value

	# Set all of our processes to false
	set_process(false)
	set_physics_process(false)
	set_notify_transform(false)

	# Based on the value of upate, change how we handle updating the skeleton
	if update_mode == 0:
		set_process(true)
		if debug_messages == true:
			print (name, " - IK_LookAt: updating skeleton using _process...")
	elif update_mode == 1:
		set_physics_process(true)
		if debug_messages == true:
			print (name, " - IK_LookAt: updating skeleton using _physics_process...")
	elif update_mode == 2:
		set_notify_transform(true)
		if debug_messages == true:
			print (name, " - IK_LookAt: updating skeleton using _notification...")
	else:
		if debug_messages == true:
			print (name, " - IK_LookAt: NOT updating skeleton due to unknown update method...")

func _set_skeleton_path(new_value):
	# Because get_node doesn't work in the first call, we just want to assign instead
	# This is to get around a issue with NodePaths exposed to the editor
	if first_call == true:
		skeleton_path = new_value
		return

	# Assign skeleton_path to whatever value is passed
	skeleton_path = new_value

	if skeleton_path == null:
		if debug_messages == true:
			print (name, " - IK_LookAt: No Nodepath selected for skeleton_path!")
		return

	# Get the node at that location, if there is one
	var temp = get_node(skeleton_path)
	if temp != null:
		# If the node has the method "find_bone" then we can assume it is (likely) a skeleton
		if temp.has_method("find_bone") == true:
			skeleton_to_use = temp
			if debug_messages == true:
				print (name, " - IK_LookAt: attached to (new) skeleton")
		# If not, then it's (likely) not a skeleton
		else:
			skeleton_to_use = null
			if debug_messages == true:
				print (name, " - IK_LookAt: skeleton_path does not point to a skeleton!")
	else:
		if debug_messages == true:
			print (name, " - IK_LookAt: No Nodepath selected for skeleton_path!")


Re add is_player property

Re add is_player property

extends Node2D

################################################################################
### Vars

# Gameplay
onready var tilemap   = self.get_node("TileMap")
enum TILES {TILE_EMPTY, TILE_WALL}

export(NodePath) var parent_block_path = null
export(Vector2)  var position_on_parent = Vector2(0, 0)
export(bool)     var is_player = false

var parent_block = null
var child_blocks = []

# Movement animation
var is_moving = false
var move_vector = Vector2(0.0, 0.0)
var previous_parent = null
var previous_position_on_parent = self.position_on_parent

var current_move_time = 0

# Drawing
onready var viewport  = self.get_node("SelfViewport")
onready var self_rect = self.get_self_rect()

################################################################################
### Setup

func _ready():
	# Add to block group
	self.add_to_group("blocks")

	# Setup viewport
	viewport.set_world_2d(self.get_world_2d())
	viewport.set_rect(self.self_rect)
	#viewport.set_size_override(true, self.self_rect.size)
	var transform = Matrix32(0, -self.self_rect.pos)
	viewport.set_canvas_transform(transform)

	self.set_fixed_process(true)

################################################################################
### Parents and Siblings

func find_children():
	var all_blocks = get_tree().get_nodes_in_group("blocks")
	var child_blocks = []
	for b in all_blocks:
		if b.parent_block == self:
			child_blocks.append(b)
	return child_blocks

func set_child_blocks():
	self.child_blocks = self.find_children()

func find_parent_block():
	self.parent_block = self.get_node(self.parent_block_path)
	self.previous_parent = self.parent_block

################################################################################
### Block position
# Represents a square in the world

class BlockPosition:
	var block = null
	var position = Vector2(0, 0)

	func _init(b, p):
		self.block    = b
		self.position = p

	func get_tile_id():
		return self.block.tilemap.get_cell(self.position.x, self.position.y)

	func adjacent(move_vect):
		return adjacent_recurse(move_vect, self.block)

	func adjacent_recurse(move_vect, original):
		var newpos = self.position + move_vect

		if newpos.x < 0:
			newpos.x = Constants.block_size - 1
		elif newpos.x >= Constants.block_size:
			newpos.x = 0
		elif newpos.y < 0:
			newpos.y = Constants.block_size - 1
		elif newpos.y >= Constants.block_size:
			newpos.y = 0
		else:
			return get_script().new(self.block, newpos)

		# We left the block

		if self.block.parent_block == original:
			return null

		var newsquare = self.block.own_position().adjacent_recurse(move_vect, original)

		if newsquare == null:
			return null

		var newblock = newsquare.block_at()

		if newblock == null:
			return null

		return get_script().new(newblock, newpos)

	func is_underlying_walkable():
		return self.get_tile_id() == TILE_EMPTY

	func block_at():
		for b in self.block.child_blocks:
			if b.position_on_parent == self.position:
				return b
		return null

	func is_empty():
		if not self.is_underlying_walkable():
			return false

		if not self.block_at() == null:
			return false

		return true

func own_position():
	return BlockPosition.new(parent_block, position_on_parent)

func adjacent_blocks():
	return adjacent_blocks_with_displacement().keys()

func adjacent_blocks_with_displacement():
	var adjacents = {}
	var a = null

	a = self.own_position().adjacent("left")
	if not a == null:
		var b = a.block_at()
		if not b == null:
			adjacents[b] = Vector2(-1, 0)

	a = self.own_position().adjacent("right")
	if not a == null:
		var b = a.block_at()
		if not b == null:
			adjacents[b] = Vector2(1, 0)

	a = self.own_position().adjacent("up")
	if not a == null:
		var b = a.block_at()
		if not b == null:
			adjacents[b] = Vector2(0, -1)

	a = self.own_position().adjacent("down")
	if not a == null:
		var b = a.block_at()
		if not b == null:
			adjacents[b] = Vector2(0, 1)

	return adjacents

################################################################################
### Drawing

func get_self_rect():
	var pos = tilemap.get_global_pos()
	# TODO: scale?
	var s = Constants.block_size * tilemap.get_cell_size()
	return Rect2(pos, s)

func visual_position_on_parent():
	if self.is_moving:
		var t = easing_function(self.current_move_time / Constants.move_duration)

		return interpolate(t, self.position_on_parent - self.move_vector, self.position_on_parent)
	else:
		return self.position_on_parent

func draw_self_texture_on(block, position):
	var texture = self.viewport.get_render_target_texture()

	var to_parent_transform = block.get_global_transform() * self.get_global_transform().affine_inverse()

	var cell_size = self.tilemap.get_cell_size() # TODO: self?
	var drawrect = Rect2(position, cell_size)

	self.draw_set_transform_matrix(to_parent_transform)
	self.draw_texture_rect(texture, drawrect, false)

func _draw(): # TODO: think about clearing the render target every frame
	# Border
	# var s = Constants.block_size * tilemap.get_cell_size().x
	# var grey = Color(0.5, 0.5, 0.5)
	# self.draw_line(Vector2(0, 0), Vector2(0, s), grey, 1)
	# self.draw_line(Vector2(0, s), Vector2(s, s), grey, 1)
	# self.draw_line(Vector2(s, s), Vector2(s, 0), grey, 1)
	# self.draw_line(Vector2(s, 0), Vector2(0, 0), grey, 1)

	if self.is_moving:
		var t = easing_function(self.current_move_time / Constants.move_duration)

		var newpos = self.parent_block.tilemap.map_to_world(self.position_on_parent)
		var oldpos = self.parent_block.tilemap.map_to_world(self.position_on_parent - self.move_vector)

		var pos = interpolate(t, oldpos, newpos)
		self.draw_self_texture_on(self.parent_block, pos)

		if not self.previous_parent == self.parent_block:
			var newpos = self.previous_parent.tilemap.map_to_world(self.previous_position_on_parent + self.move_vector)
			var oldpos = self.previous_parent.tilemap.map_to_world(self.previous_position_on_parent)

			var pos = interpolate(t, oldpos, newpos)
			self.draw_self_texture_on(self.previous_parent, pos)
	else:
		var worldcoords = self.parent_block.tilemap.map_to_world(self.position_on_parent)
		self.draw_self_texture_on(self.parent_block, worldcoords)

func interpolate(t, x1, x2):
	return (1-t)*x1 + t*x2

func easing_function(t):
	var u0 = 0
	var u1 = 0.2
	var u2 = 0.95
	var u3 = 1
	return u0 * (1-t*t*t) + 3*u1*(1-t*t)*t + 3*u2*(1-t)*t*t + u3*t*t*t*t

func _fixed_process(delta):
	if self.is_moving:
		self.current_move_time += delta
		if self.current_move_time > Constants.move_duration:
			self.is_moving = false
			self.current_move_time = 0

		self.update()

################################################################################
### Movement

func try_move(move_vect):
	if self.is_moving:
		return

	var a = self.own_position().adjacent(move_vect)
	if a == null:
		return

	if a.is_empty():
		do_move(move_vect, a)

func do_move(move_vect, new_square):
	self.is_moving = true
	self.move_vector = move_vect

	self.previous_parent = self.parent_block
	self.previous_position_on_parent = self.position_on_parent

	self.parent_block = new_square.block
	self.position_on_parent = new_square.position

	if not self.parent_block == self.previous_parent:
		self.previous_parent.child_blocks.erase(self)
		self.parent_block.child_blocks.append(self)

extends Node2D

################################################################################
### Vars

# Gameplay
onready var tilemap   = self.get_node("TileMap")
enum TILES {TILE_EMPTY, TILE_WALL}

export(NodePath) var parent_block_path = null
export(Vector2)  var position_on_parent = Vector2(0, 0)

var parent_block = null
var child_blocks = []

# Movement animation
var is_moving = false
var move_vector = Vector2(0.0, 0.0)
var previous_parent = null
var previous_position_on_parent = self.position_on_parent

var current_move_time = 0

# Drawing
onready var viewport  = self.get_node("SelfViewport")
onready var self_rect = self.get_self_rect()

################################################################################
### Setup

func _ready():
	# Add to block group
	self.add_to_group("blocks")

	# Setup viewport
	viewport.set_world_2d(self.get_world_2d())
	viewport.set_rect(self.self_rect)
	#viewport.set_size_override(true, self.self_rect.size)
	var transform = Matrix32(0, -self.self_rect.pos)
	viewport.set_canvas_transform(transform)

	self.set_fixed_process(true)

################################################################################
### Parents and Siblings

func find_children():
	var all_blocks = get_tree().get_nodes_in_group("blocks")
	var child_blocks = []
	for b in all_blocks:
		if b.parent_block == self:
			child_blocks.append(b)
	return child_blocks

func set_child_blocks():
	self.child_blocks = self.find_children()

func find_parent_block():
	self.parent_block = self.get_node(self.parent_block_path)
	self.previous_parent = self.parent_block

################################################################################
### Block position
# Represents a square in the world

class BlockPosition:
	var block = null
	var position = Vector2(0, 0)

	func _init(b, p):
		self.block    = b
		self.position = p

	func get_tile_id():
		return self.block.tilemap.get_cell(self.position.x, self.position.y)

	func adjacent(move_vect):
		return adjacent_recurse(move_vect, self.block)

	func adjacent_recurse(move_vect, original):
		var newpos = self.position + move_vect

		if newpos.x < 0:
			newpos.x = Constants.block_size - 1
		elif newpos.x >= Constants.block_size:
			newpos.x = 0
		elif newpos.y < 0:
			newpos.y = Constants.block_size - 1
		elif newpos.y >= Constants.block_size:
			newpos.y = 0
		else:
			return get_script().new(self.block, newpos)

		# We left the block

		if self.block.parent_block == original:
			return null

		var newsquare = self.block.own_position().adjacent_recurse(move_vect, original)

		if newsquare == null:
			return null

		var newblock = newsquare.block_at()

		if newblock == null:
			return null

		return get_script().new(newblock, newpos)

	func is_underlying_walkable():
		return self.get_tile_id() == TILE_EMPTY

	func block_at():
		for b in self.block.child_blocks:
			if b.position_on_parent == self.position:
				return b
		return null

	func is_empty():
		if not self.is_underlying_walkable():
			return false

		if not self.block_at() == null:
			return false

		return true

func own_position():
	return BlockPosition.new(parent_block, position_on_parent)

func adjacent_blocks():
	return adjacent_blocks_with_displacement().keys()

func adjacent_blocks_with_displacement():
	var adjacents = {}
	var a = null

	a = self.own_position().adjacent("left")
	if not a == null:
		var b = a.block_at()
		if not b == null:
			adjacents[b] = Vector2(-1, 0)

	a = self.own_position().adjacent("right")
	if not a == null:
		var b = a.block_at()
		if not b == null:
			adjacents[b] = Vector2(1, 0)

	a = self.own_position().adjacent("up")
	if not a == null:
		var b = a.block_at()
		if not b == null:
			adjacents[b] = Vector2(0, -1)

	a = self.own_position().adjacent("down")
	if not a == null:
		var b = a.block_at()
		if not b == null:
			adjacents[b] = Vector2(0, 1)

	return adjacents

################################################################################
### Drawing

func get_self_rect():
	var pos = tilemap.get_global_pos()
	# TODO: scale?
	var s = Constants.block_size * tilemap.get_cell_size()
	return Rect2(pos, s)

func visual_position_on_parent():
	if self.is_moving:
		var t = easing_function(self.current_move_time / Constants.move_duration)

		return interpolate(t, self.position_on_parent - self.move_vector, self.position_on_parent)
	else:
		return self.position_on_parent

func draw_self_texture_on(block, position):
	var texture = self.viewport.get_render_target_texture()

	var to_parent_transform = block.get_global_transform() * self.get_global_transform().affine_inverse()

	var cell_size = self.tilemap.get_cell_size() # TODO: self?
	var drawrect = Rect2(position, cell_size)

	self.draw_set_transform_matrix(to_parent_transform)
	self.draw_texture_rect(texture, drawrect, false)

func _draw(): # TODO: think about clearing the render target every frame
	# Border
	# var s = Constants.block_size * tilemap.get_cell_size().x
	# var grey = Color(0.5, 0.5, 0.5)
	# self.draw_line(Vector2(0, 0), Vector2(0, s), grey, 1)
	# self.draw_line(Vector2(0, s), Vector2(s, s), grey, 1)
	# self.draw_line(Vector2(s, s), Vector2(s, 0), grey, 1)
	# self.draw_line(Vector2(s, 0), Vector2(0, 0), grey, 1)

	if self.is_moving:
		var t = easing_function(self.current_move_time / Constants.move_duration)

		var newpos = self.parent_block.tilemap.map_to_world(self.position_on_parent)
		var oldpos = self.parent_block.tilemap.map_to_world(self.position_on_parent - self.move_vector)

		var pos = interpolate(t, oldpos, newpos)
		self.draw_self_texture_on(self.parent_block, pos)

		if not self.previous_parent == self.parent_block:
			var newpos = self.previous_parent.tilemap.map_to_world(self.previous_position_on_parent + self.move_vector)
			var oldpos = self.previous_parent.tilemap.map_to_world(self.previous_position_on_parent)

			var pos = interpolate(t, oldpos, newpos)
			self.draw_self_texture_on(self.previous_parent, pos)
	else:
		var worldcoords = self.parent_block.tilemap.map_to_world(self.position_on_parent)
		self.draw_self_texture_on(self.parent_block, worldcoords)

func interpolate(t, x1, x2):
	return (1-t)*x1 + t*x2

func easing_function(t):
	var u0 = 0
	var u1 = 0.2
	var u2 = 0.95
	var u3 = 1
	return u0 * (1-t*t*t) + 3*u1*(1-t*t)*t + 3*u2*(1-t)*t*t + u3*t*t*t*t

func _fixed_process(delta):
	if self.is_moving:
		self.current_move_time += delta
		if self.current_move_time > Constants.move_duration:
			self.is_moving = false
			self.current_move_time = 0

		self.update()

################################################################################
### Movement

func try_move(move_vect):
	if self.is_moving:
		return

	var a = self.own_position().adjacent(move_vect)
	if a == null:
		return

	if a.is_empty():
		do_move(move_vect, a)

func do_move(move_vect, new_square):
	self.is_moving = true
	self.move_vector = move_vect

	self.previous_parent = self.parent_block
	self.previous_position_on_parent = self.position_on_parent

	self.parent_block = new_square.block
	self.position_on_parent = new_square.position

	if not self.parent_block == self.previous_parent:
		self.previous_parent.child_blocks.erase(self)
		self.parent_block.child_blocks.append(self)


fix : resume button grabs focus when menu opened with keyboard but not with clicking/touching

extends CanvasLayer

signal pause
signal restart
signal menu

var enabled = true
var open = false
onready var animation = get_node("animation")
onready var button = get_node("pause_button")
onready var menu_screen = get_node("menu_screen")
onready var buttons = menu_screen.get_node("buttons")

func _ready():
	set_process_input(true)

func _input(event):
	if !open && event.is_action_released("start"):
		handle_pause()
		buttons.get_node("resume_button").grab_focus()

func set_enabled(enabled):
	self.enabled = enabled
	menu_screen.set_hidden(!enabled)
	button.set_hidden(!enabled)

func _on_animation_finished():
	if animation.get_current_animation() == "open":
		open = !open

func handle_pause():
	if !animation.is_playing():
		if !open:
			animation.play("open")
			emit_signal("pause")
			get_tree().set_pause(true)
			menu_screen.reset()
		else:
			animation.play_backwards("open")
			get_tree().set_pause(false)
			menu_screen.stop()

func _on_pause_button_released():
	if !open:
		handle_pause()

func _on_restart_button_pressed():
	if open:
		emit_signal("restart")

func _on_resume_button_pressed():
	if open:
		handle_pause()
		buttons.get_node("resume_button").release_focus()

func _on_menu_button_pressed():
	if open:
		emit_signal("menu")

extends CanvasLayer

signal pause
signal restart
signal menu

var enabled = true
var open = false
onready var animation = get_node("animation")
onready var button = get_node("pause_button")
onready var menu_screen = get_node("menu_screen")
onready var buttons = menu_screen.get_node("buttons")

func _ready():
	set_process_input(true)

func _input(event):
	if !open && event.is_action_released("start"):
		handle_pause()

func set_enabled(enabled):
	self.enabled = enabled
	menu_screen.set_hidden(!enabled)
	button.set_hidden(!enabled)

func _on_animation_finished():
	if animation.get_current_animation() == "open":
		open = !open

func handle_pause():
	if !animation.is_playing():
		if !open:
			buttons.get_node("resume_button").grab_focus()
			animation.play("open")
			emit_signal("pause")
			get_tree().set_pause(true)
			menu_screen.reset()
		else:
			animation.play_backwards("open")
			get_tree().set_pause(false)
			menu_screen.stop()

func _on_pause_button_released():
	if !open:
		handle_pause()

func _on_restart_button_pressed():
	if open:
		emit_signal("restart")

func _on_resume_button_pressed():
	if open:
		handle_pause()

func _on_menu_button_pressed():
	if open:
		emit_signal("menu")


Fix sorting function

Fix sorting function

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):
	if node1.minimumParentSize.width != node2.minimumParentSize.width:
		return node1.minimumParentSize.width > node2.minimumParentSize.width
	return node1.minimumParentSize.height > node2.minimumParentSize.height

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


Add another user. Mock chris

Add another user. Mock chris

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

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

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

	for i in range(2): spawn_enemy()

	for i in range(1): spawn_tower()

	for i in range(2): spawn_pickup()

	# Background node
	# player_manager node

func spawn_player(processor, device_details):
	var player_scn = load(global.scene_path_player)
	var player_node = player_scn.instance()
	get_node("ships").add_child(player_node, true)

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

func spawn_enemy():
	var scn = load(global.scene_path_enemy)
	var node = scn.instance()
	get_node("ships").add_child(node, true)

func spawn_tower():
	var scn = load(global.scene_path_tower)
	var node = scn.instance()
	get_node("ships").add_child(node, true)

func spawn_pickup():
	var scn = load(global.scene_path_pickup)
	var node = scn.instance()
	get_node("objects").add_child(node, true)

	# 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")

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

	for i in range(1): spawn_enemy()

	for i in range(1): spawn_tower()

	for i in range(2): spawn_pickup()

	# Background node
	# player_manager node

func spawn_player(processor, device_details):
	var player_scn = load(global.scene_path_player)
	var player_node = player_scn.instance()
	get_node("ships").add_child(player_node, true)

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

func spawn_enemy():
	var scn = load(global.scene_path_enemy)
	var node = scn.instance()
	get_node("ships").add_child(node, true)

func spawn_tower():
	var scn = load(global.scene_path_tower)
	var node = scn.instance()
	get_node("ships").add_child(node, true)

func spawn_pickup():
	var scn = load(global.scene_path_pickup)
	var node = scn.instance()
	get_node("objects").add_child(node, true)

	# 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)
#


Removed random boss size dropoff

Removed random boss size dropoff

# GAME class
# This class handles the core game loop.
# It generates new random bosses as the player defeats them,
# and also respawns the player when dead.

extends Node

# As long as this is true, new bosses (and players) will be spawned
var playing = false

# Strong and weak references to last spawned boss
# Weak reference is nessecary to check when the boss has been destroyed
var last_boss
var last_boss_wr

# Values for current and target fore- and background colors
# Used for slowly fading into new colors
var bgcol = Color(0,0,0)
var target_bgcol = Color(0,0,0)
var fgcol = Color(0,0,0)
var target_fgcol = Color(0,0,0)

# Number of bosses spawned, and how many layers the next boss should have
var bossnum = 0
var bossdepth = 2

# Those values every game in the known universe have
var score = 0
var lives = 3

var score_mult = 1
var score_mult_timer = 0
const score_mult_time = 4

# List of Nikolaj-curaged regexes for the boss generator to use
# The letters a, b and c will each be replaced by a random number from 0 to the highest poly degree of the boss
var regex_list = [
#"a*b*",
".*",
#"(a|c)*b",
#".*a.*",
#"c*(aa|bb)*c*",
#"b.*a",
#"(c.b)*",
#"ba*b.*",
#"c*ba*",
#"c*",
#"a*b*c*"
]

# Starts the party
func start_game():
	playing = true
	gen_boss()

func _ready():
	set_process_input(true)
	set_process(true)
	start_game()

func _input(event):
	if event.type == InputEvent.KEY:
		if event.scancode == KEY_R and event.pressed == true:
			if last_boss != null:
				last_boss.queue_free()

func _process(delta):
	bgcol = bgcol.linear_interpolate(target_bgcol,delta * 3)
	fgcol = fgcol.linear_interpolate(target_fgcol,delta * 0.5)
	#get_node("Background/Polygon2D").set_color(bgcol)
	get_node("Background").set_modulate(bgcol)

	if score_mult_timer > 0:
		score_mult_timer -= delta
		if score_mult_timer <= 0:
			score_mult = 1

	if playing:
		if OS.get_time_scale() < 1:
			OS.set_time_scale(min(OS.get_time_scale() + delta, 1))
			if OS.get_time_scale() >= 1 and !has_node("Player") and lives > 0:
				var p = preload("res://objects/Player.tscn").instance()
				add_child(p)
				p.set_global_pos(Vector2(360,600))
				lives-=1

		if !last_boss_wr.get_ref():
			#No boss, spawn a new one
			if bossdepth < 4:
				bossdepth += 1
			gen_boss()
			#also increase player health
			var player = get_node("Player")
			if player != null:
				player.health = floor(lerp(player.health,player.MAX_HEALTH,0.5))

# Generates regexes using the regex list and some info about the boss
func random_regex(size, larg):
	var string = regex_list[abs(randi()) % regex_list.size()]
	var base = randi();
	string = string.replace("a",str(int(base+0)%int(larg)))
	string = string.replace("b",str(int(base+1)%int(larg)))
	string = string.replace("c",str(int(base+2)%int(larg)))
	return string

func add_score(amount):
	score += amount * score_mult
	score_mult += 1
	#score_mult_timer = score_mult_time

# Spawns a boss from a boss design object
func spawn_boss(design):
	var boss_instance = preload("res://objects/Boss.tscn").instance()
	last_boss = boss_instance
	last_boss_wr = weakref(boss_instance)

	boss_instance.design = design

	target_bgcol = design.start_color.linear_interpolate(Color(0,0,0), 0.8)
	target_fgcol = design.end_color.linear_interpolate(Color(0,0,0), 0)

	add_child(boss_instance)
	boss_instance.set_pos(Vector2(360,360))

	bossnum += 1

	playing = true

# Generates a random boss design
func gen_boss():
	var design = preload("res://scripts/datatypes/BossDesign.gd").new()

	randomize() # Randomize random seed

	var layer_count = 4 #floor(rand_range(3,5)) + floor(bossdepth / 2) # bossdepth # floor(rand_range(3,5))

	var layers = []
	var bullettypes = []
	var largest = 3;
	for i in range(0,layer_count):
		var l = floor(rand_range(3,6));
		if (l>largest):
			largest = l;
		layers.append(l)
		bullettypes.append(floor(rand_range(0,5)))
		#bullettypes.append(2)
	design.layers = layers
	design.bullettypes = bullettypes

	design.regex = random_regex(1 + (abs(randi())%3), largest)

	design.base_size = layer_count * 20
	design.size_dropoff = 0.6

	design.base_health = 20 + (2.5 * bossnum)
	#TODO: Health and health dropoff (Should be based on difficulty, and probably affected by the total amount of boss parts)

	var speed = 1

	var min_base_rot = 0.5
	var max_base_rot = 1.5
	var min_rot_inc = 0.05
	var max_rot_inc = 0.15

	var rot_speed_focus = rand_range(0,1)
	design.base_rot_speed = lerp(min_base_rot, max_base_rot, rot_speed_focus) * speed
	design.rot_speed_inc = lerp(min_rot_inc, max_rot_inc, 1 - rot_speed_focus) * PI * speed

	if randi() % 2 == 0: design.base_rot_speed = -design.base_rot_speed
	if randi() % 2 == 0: design.rot_speed_inc = -design.rot_speed_inc

	var boss_start_col = Color(rand_range(0,1),rand_range(0,1),rand_range(0,1))
	var boss_end_col = Color(rand_range(0,1),rand_range(0,1),rand_range(0,1))

	design.start_color = boss_start_col
	design.end_color = boss_end_col

	spawn_boss(design)

# Unused broken regex generator
func gen_regex(depth, layers):
	print(depth)
	if depth == 0:
		return str(floor(rand_range(0, layers[depth])))

	var option = floor(rand_range(0,3))
	if option == 0:
		return "(" + gen_regex(depth - 1, layers) + ")*"
	if option == 1:
		return "(" + gen_regex(depth - 1, layers) + ")+(" + gen_regex(depth - 1, layers) + ")"
	if option == 2:
		return "(" + gen_regex(depth - 1, layers) + ")(" + gen_regex(depth - 1, layers) + ")"

# GAME class
# This class handles the core game loop.
# It generates new random bosses as the player defeats them,
# and also respawns the player when dead.

extends Node

# As long as this is true, new bosses (and players) will be spawned
var playing = false

# Strong and weak references to last spawned boss
# Weak reference is nessecary to check when the boss has been destroyed
var last_boss
var last_boss_wr

# Values for current and target fore- and background colors
# Used for slowly fading into new colors
var bgcol = Color(0,0,0)
var target_bgcol = Color(0,0,0)
var fgcol = Color(0,0,0)
var target_fgcol = Color(0,0,0)

# Number of bosses spawned, and how many layers the next boss should have
var bossnum = 0
var bossdepth = 2

# Those values every game in the known universe have
var score = 0
var lives = 3

var score_mult = 1
var score_mult_timer = 0
const score_mult_time = 4

# List of Nikolaj-curaged regexes for the boss generator to use
# The letters a, b and c will each be replaced by a random number from 0 to the highest poly degree of the boss
var regex_list = [
#"a*b*",
".*",
#"(a|c)*b",
#".*a.*",
#"c*(aa|bb)*c*",
#"b.*a",
#"(c.b)*",
#"ba*b.*",
#"c*ba*",
#"c*",
#"a*b*c*"
]

# Starts the party
func start_game():
	playing = true
	gen_boss()

func _ready():
	set_process_input(true)
	set_process(true)
	start_game()

func _input(event):
	if event.type == InputEvent.KEY:
		if event.scancode == KEY_R and event.pressed == true:
			if last_boss != null:
				last_boss.queue_free()

func _process(delta):
	bgcol = bgcol.linear_interpolate(target_bgcol,delta * 3)
	fgcol = fgcol.linear_interpolate(target_fgcol,delta * 0.5)
	#get_node("Background/Polygon2D").set_color(bgcol)
	get_node("Background").set_modulate(bgcol)

	if score_mult_timer > 0:
		score_mult_timer -= delta
		if score_mult_timer <= 0:
			score_mult = 1

	if playing:
		if OS.get_time_scale() < 1:
			OS.set_time_scale(min(OS.get_time_scale() + delta, 1))
			if OS.get_time_scale() >= 1 and !has_node("Player") and lives > 0:
				var p = preload("res://objects/Player.tscn").instance()
				add_child(p)
				p.set_global_pos(Vector2(360,600))
				lives-=1

		if !last_boss_wr.get_ref():
			#No boss, spawn a new one
			if bossdepth < 4:
				bossdepth += 1
			gen_boss()
			#also increase player health
			var player = get_node("Player")
			if player != null:
				player.health = floor(lerp(player.health,player.MAX_HEALTH,0.5))

# Generates regexes using the regex list and some info about the boss
func random_regex(size, larg):
	var string = regex_list[abs(randi()) % regex_list.size()]
	var base = randi();
	string = string.replace("a",str(int(base+0)%int(larg)))
	string = string.replace("b",str(int(base+1)%int(larg)))
	string = string.replace("c",str(int(base+2)%int(larg)))
	return string

func add_score(amount):
	score += amount * score_mult
	score_mult += 1
	#score_mult_timer = score_mult_time

# Spawns a boss from a boss design object
func spawn_boss(design):
	var boss_instance = preload("res://objects/Boss.tscn").instance()
	last_boss = boss_instance
	last_boss_wr = weakref(boss_instance)

	boss_instance.design = design

	target_bgcol = design.start_color.linear_interpolate(Color(0,0,0), 0.8)
	target_fgcol = design.end_color.linear_interpolate(Color(0,0,0), 0)

	add_child(boss_instance)
	boss_instance.set_pos(Vector2(360,360))

	bossnum += 1

	playing = true

# Generates a random boss design
func gen_boss():
	var design = preload("res://scripts/datatypes/BossDesign.gd").new()

	randomize() # Randomize random seed

	var layer_count = 4 #floor(rand_range(3,5)) + floor(bossdepth / 2) # bossdepth # floor(rand_range(3,5))

	var layers = []
	var bullettypes = []
	var largest = 3;
	for i in range(0,layer_count):
		var l = floor(rand_range(3,6));
		if (l>largest):
			largest = l;
		layers.append(l)
		bullettypes.append(floor(rand_range(0,5)))
		#bullettypes.append(2)
	design.layers = layers
	design.bullettypes = bullettypes

	design.regex = random_regex(1 + (abs(randi())%3), largest)

	design.base_size = layer_count * 20
	design.size_dropoff = rand_range(0.5,0.8)

	design.base_health = 20 + (2.5 * bossnum)
	#TODO: Health and health dropoff (Should be based on difficulty, and probably affected by the total amount of boss parts)

	var speed = 1

	var min_base_rot = 0.5
	var max_base_rot = 1.5
	var min_rot_inc = 0.05
	var max_rot_inc = 0.15

	var rot_speed_focus = rand_range(0,1)
	design.base_rot_speed = lerp(min_base_rot, max_base_rot, rot_speed_focus) * speed
	design.rot_speed_inc = lerp(min_rot_inc, max_rot_inc, 1 - rot_speed_focus) * PI * speed

	if randi() % 2 == 0: design.base_rot_speed = -design.base_rot_speed
	if randi() % 2 == 0: design.rot_speed_inc = -design.rot_speed_inc

	var boss_start_col = Color(rand_range(0,1),rand_range(0,1),rand_range(0,1))
	var boss_end_col = Color(rand_range(0,1),rand_range(0,1),rand_range(0,1))

	design.start_color = boss_start_col
	design.end_color = boss_end_col

	spawn_boss(design)

# Unused broken regex generator
func gen_regex(depth, layers):
	print(depth)
	if depth == 0:
		return str(floor(rand_range(0, layers[depth])))

	var option = floor(rand_range(0,3))
	if option == 0:
		return "(" + gen_regex(depth - 1, layers) + ")*"
	if option == 1:
		return "(" + gen_regex(depth - 1, layers) + ")+(" + gen_regex(depth - 1, layers) + ")"
	if option == 2:
		return "(" + gen_regex(depth - 1, layers) + ")(" + gen_regex(depth - 1, layers) + ")"


portal fix

portal fix

extends Navigation2D

# Member variables
const SPEED = 180.0
const TALK_DISTANCE = 50

export var scale_enabled = false
export var scale_factor = 500

var npc_clicked = null
var dialogue_running = null

var item_held = null

var begin = Vector2()
var end = Vector2()
var path = []

var left_corner = Vector2(110,1000)
var right_corner = Vector2(1748,1000)
var middle = Vector2(1000,1000)

var mouseOver = null

func _process(delta):

	if (path.size() > 1):
		var to_walk = delta*SPEED

		if !get_node("player/AnimationPlayer").is_playing():
			get_node("player/AnimationPlayer").play("walk")
		scale_player()

		while(to_walk > 0 and path.size() >= 2):
			var pfrom = path[path.size() - 1]
			var pto = path[path.size() - 2]
			var d = pfrom.distance_to(pto)
			if (d <= to_walk):
				path.remove(path.size() - 1)
				to_walk -= d
			else:
				path[path.size() - 1] = pfrom.linear_interpolate(pto, to_walk/d)
				to_walk = 0

		var atpos = path[path.size() - 1]
		get_node("player").set_pos(atpos)

		if (path.size() < 2):
			path = []
			get_node("player/AnimationPlayer").seek(0.0, true)
			get_node("player/AnimationPlayer").stop_all()
			set_process(false)
			var npc_near = check_npc_near()
			if (npc_near != null && npc_clicked != null) && (npc_clicked == npc_near) && (dialogue_running == false):
					npc_clicked.show_dialogue()

			if get_node("player/Area2D").get_overlapping_areas().size() > 0:
				get_node("player/Area2D").get_overlapping_areas()[0].teleport()
	else:
		set_process(false)

func check_npc_clicked(clickPos):
	var character = get_node("player")
	for npc in get_children():
		if npc.is_in_group("npc_dialogue"):
			var npc_x = npc.get_pos().x
			var npc_y = npc.get_pos().y
			var npc_width = npc.get_region_rect().size.x
			var npc_height = npc.get_region_rect().size.y
			if (clickPos.x > (npc_x - npc_width/2) && clickPos.x < (npc_x + npc_width/2)):
				if (clickPos.y > (npc_y - npc_height/2) && clickPos.y < (npc_y + npc_height/2)):
					return npc
#			npc.hide_dialogue()
	return null

func check_npc_near():
	var character = get_node("player")
	for npc in get_children():
		if npc.is_in_group("npc_dialogue"):
			var npc_x = npc.get_pos().x
			var npc_y = npc.get_pos().y
			var npc_width = npc.get_region_rect().size.x
			var npc_height = npc.get_region_rect().size.y
			if character.get_pos().x > (npc_x - npc_width/2 - TALK_DISTANCE) && character.get_pos().x < (npc_x + npc_width/2 + TALK_DISTANCE):
				if character.get_pos().y > (npc_y - npc_height/2 - TALK_DISTANCE) && character.get_pos().y < (npc_y + npc_height/2 + TALK_DISTANCE):
					return npc
	return null

func _update_path():
	var p = get_simple_path(begin, end, true)
	path = Array(p) # Vector2array too complex to use, convert to regular array
	path.invert()

	set_process(true)

func _input(event):
	if (event.type == InputEvent.MOUSE_BUTTON and event.pressed and event.button_index == 1 and !dialogue_running):
		begin = get_node("player").get_pos()

		npc_clicked = check_npc_clicked(event.pos - get_pos())

		# Mouse to local navigation coordinates
		end = event.pos - get_pos()
		_update_path()

func scale_player():
	if scale_enabled:
		var scale = log(get_node("player").get_pos().y/scale_factor)
		get_node("player").set_scale(Vector2(scale, scale))

func set_playerPos():
	if(transition.get_direction() == 0):
		get_node("player").set_pos(right_corner)
	if(transition.get_direction() == 1):
		get_node("player").set_pos(left_corner)
	if(transition.get_direction() == 2):
		get_node("player").set_pos(middle)
	else:
		return

func _ready():
	SPEED = game_state.player_speed
	set_playerPos()
	scale_player()
	dialogue_running = false
	for npc in get_children():
		if npc.is_in_group("npc_dialogue"):
			npc.hide_dialogue()
	if game_state.end:
		for portal in get_children():
			if portal.is_in_group("portal"):
				portal.hide()
		npc_clicked = get_node("end")
		get_node("end").show_dialogue()
	set_process_input(true)

# when pressing the key, collect it #
func _on_Key_pressed():
	item_held = "sprites/key.png"
	get_node("Key").hide()

func npc_bubble_clicked():
	if get_node("npc_bubble/text_interface_engine")._buffer.size() > 0:
		#get_node("npc_bubble/text_interface_engine").set_buff_speed(1.0)
		get_node("npc_bubble/text_interface_engine").set_turbomode(true)
	else:
		if npc_clicked.counter >= npc_clicked.npc_text.size():
			npc_clicked.hide_dialogue()
			if npc_clicked == get_node("end"):
				for portal in get_children():
						if portal.is_in_group("portal"):
							portal.show()
		else:
			npc_clicked.show_dialogue()

extends Navigation2D

# Member variables
const SPEED = 180.0
const TALK_DISTANCE = 50

export var scale_enabled = false
export var scale_factor = 500

var npc_clicked = null
var dialogue_running = null

var item_held = null

var begin = Vector2()
var end = Vector2()
var path = []

var left_corner = Vector2(110,1000)
var right_corner = Vector2(1748,1000)
var middle = Vector2(1000,1000)

var mouseOver = null

func _process(delta):

	if (path.size() > 1):
		var to_walk = delta*SPEED

		if !get_node("player/AnimationPlayer").is_playing():
			get_node("player/AnimationPlayer").play("walk")
		scale_player()

		while(to_walk > 0 and path.size() >= 2):
			var pfrom = path[path.size() - 1]
			var pto = path[path.size() - 2]
			var d = pfrom.distance_to(pto)
			if (d <= to_walk):
				path.remove(path.size() - 1)
				to_walk -= d
			else:
				path[path.size() - 1] = pfrom.linear_interpolate(pto, to_walk/d)
				to_walk = 0

		var atpos = path[path.size() - 1]
		get_node("player").set_pos(atpos)

		if (path.size() < 2):
			path = []
			get_node("player/AnimationPlayer").seek(0.0, true)
			get_node("player/AnimationPlayer").stop_all()
			set_process(false)
			var npc_near = check_npc_near()
			if (npc_near != null && npc_clicked != null) && (npc_clicked == npc_near) && (dialogue_running == false):
					npc_clicked.show_dialogue()

			if get_node("player/Area2D").get_overlapping_areas().size() > 0 && game_state.end == true:
				get_node("player/Area2D").get_overlapping_areas()[0].teleport()
	else:
		set_process(false)

func check_npc_clicked(clickPos):
	var character = get_node("player")
	for npc in get_children():
		if npc.is_in_group("npc_dialogue"):
			var npc_x = npc.get_pos().x
			var npc_y = npc.get_pos().y
			var npc_width = npc.get_region_rect().size.x
			var npc_height = npc.get_region_rect().size.y
			if (clickPos.x > (npc_x - npc_width/2) && clickPos.x < (npc_x + npc_width/2)):
				if (clickPos.y > (npc_y - npc_height/2) && clickPos.y < (npc_y + npc_height/2)):
					return npc
#			npc.hide_dialogue()
	return null

func check_npc_near():
	var character = get_node("player")
	for npc in get_children():
		if npc.is_in_group("npc_dialogue"):
			var npc_x = npc.get_pos().x
			var npc_y = npc.get_pos().y
			var npc_width = npc.get_region_rect().size.x
			var npc_height = npc.get_region_rect().size.y
			if character.get_pos().x > (npc_x - npc_width/2 - TALK_DISTANCE) && character.get_pos().x < (npc_x + npc_width/2 + TALK_DISTANCE):
				if character.get_pos().y > (npc_y - npc_height/2 - TALK_DISTANCE) && character.get_pos().y < (npc_y + npc_height/2 + TALK_DISTANCE):
					return npc
	return null

func _update_path():
	var p = get_simple_path(begin, end, true)
	path = Array(p) # Vector2array too complex to use, convert to regular array
	path.invert()

	set_process(true)

func _input(event):
	if (event.type == InputEvent.MOUSE_BUTTON and event.pressed and event.button_index == 1 and !dialogue_running):
		begin = get_node("player").get_pos()

		npc_clicked = check_npc_clicked(event.pos - get_pos())

		# Mouse to local navigation coordinates
		end = event.pos - get_pos()
		_update_path()

func scale_player():
	if scale_enabled:
		var scale = log(get_node("player").get_pos().y/scale_factor)
		get_node("player").set_scale(Vector2(scale, scale))

func set_playerPos():
	if(transition.get_direction() == 0):
		get_node("player").set_pos(right_corner)
	if(transition.get_direction() == 1):
		get_node("player").set_pos(left_corner)
	if(transition.get_direction() == 2):
		get_node("player").set_pos(middle)
	else:
		return

func _ready():
	SPEED = game_state.player_speed
	set_playerPos()
	scale_player()
	dialogue_running = false
	for npc in get_children():
		if npc.is_in_group("npc_dialogue"):
			npc.hide_dialogue()
	if game_state.end:
		for portal in get_children():
			if portal.is_in_group("portal"):
				portal.hide()
		npc_clicked = get_node("end")
		get_node("end").show_dialogue()
	set_process_input(true)

# when pressing the key, collect it #
func _on_Key_pressed():
	item_held = "sprites/key.png"
	get_node("Key").hide()

func npc_bubble_clicked():
	if get_node("npc_bubble/text_interface_engine")._buffer.size() > 0:
		#get_node("npc_bubble/text_interface_engine").set_buff_speed(1.0)
		get_node("npc_bubble/text_interface_engine").set_turbomode(true)
	else:
		if npc_clicked.counter >= npc_clicked.npc_text.size():
			npc_clicked.hide_dialogue()
			if npc_clicked == get_node("end"):
				for portal in get_children():
						if portal.is_in_group("portal"):
							portal.show()
		else:
			npc_clicked.show_dialogue()


emission color default red

emission color default red

extends "AbstractBlock.gd"

var mat
var laserExtent = Vector3()

# color
func setTexture(color=Color(0.5, 0, 0)):
	var mat = load("res://materials/block_Laser.mtl")
	mat.set_parameter(FixedMaterial.PARAM_EMISSION, Color(0.2, 0.1, 0.1))
	self.get_node("MeshInstance").set_material_override(mat)

	return self

# sets light emission color
func setColor(col):
	mat.set_parameter(FixedMaterial.PARAM_EMISSION, col)

func setExtent(laserExtent):
	 self.laserExtent = laserExtent

# Force a laser to set off at the end of a layer.
func forceActivate():
	scaleTweenNode(0, 0.5, Tween.TRANS_ELASTIC).start()

# Can't manually activate a laser.
func activate():
	return

extends "AbstractBlock.gd"

var mat
var laserExtent = Vector3()

# color
func setTexture(color=Color(0.5, 0, 0)):
	var mat = load("res://materials/block_Laser.mtl")
	mat.set_parameter(FixedMaterial.PARAM_EMISSION, color)
	self.get_node("MeshInstance").set_material_override(mat)

	return self

# sets light emission color
func setColor(col):
	mat.set_parameter(FixedMaterial.PARAM_EMISSION, col)

func setExtent(laserExtent):
	 self.laserExtent = laserExtent

# Force a laser to set off at the end of a layer.
func forceActivate():
	scaleTweenNode(0, 0.5, Tween.TRANS_ELASTIC).start()

# Can't manually activate a laser.
func activate():
	return