Переглянути джерело

Merge branch 'master' of https://github.com/okamstudio/godot

Juan Linietsky 10 роки тому
батько
коміт
9783f6fc96

+ 2 - 2
demos/misc/tween/main.gd

@@ -112,8 +112,8 @@ func reset_tween():
 		tween.interpolate_property(sprite, "transform/rot", 360, 0, 2, state.trans, state.eases, 2)
 	
 	if get_node("modes/callback").is_pressed():
-		tween.interpolate_callback(self, "on_callback", 0.5, "0.5 second's after")
-		tween.interpolate_callback(self, "on_callback", 1.2, "1.2 second's after")
+		tween.interpolate_callback(self, 0.5, "on_callback", "0.5 second's after")
+		tween.interpolate_callback(self, 0.2, "on_callback", "1.2 second's after")
 	
 	if get_node("modes/follow").is_pressed():
 		follow.show()

+ 1 - 0
drivers/SCsub

@@ -7,6 +7,7 @@ Export('env')
 
 SConscript('unix/SCsub');
 SConscript('alsa/SCsub');
+SConscript('pulseaudio/SCsub');
 SConscript('windows/SCsub');
 SConscript('gles2/SCsub');
 SConscript('gles1/SCsub');

+ 5 - 0
drivers/pulseaudio/SCsub

@@ -0,0 +1,5 @@
+Import('env')
+
+env.add_source_files(env.drivers_sources,"*.cpp")
+
+Export('env')

+ 194 - 0
drivers/pulseaudio/audio_driver_pulseaudio.cpp

@@ -0,0 +1,194 @@
+/*************************************************************************/
+/*  audio_driver_alsa.cpp                                                */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                    http://www.godotengine.org                         */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                 */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+#include "audio_driver_pulseaudio.h"
+
+#ifdef PULSEAUDIO_ENABLED
+
+#include <pulse/error.h>
+
+#include "globals.h"
+
+Error AudioDriverPulseAudio::init() {
+
+    active = false;
+    thread_exited = false;
+    exit_thread = false;
+	pcm_open = false;
+	samples_in = NULL;
+	samples_out = NULL;
+
+	mix_rate = 44100;
+	output_format = OUTPUT_STEREO;
+	channels = 2;
+
+    pa_sample_spec spec;
+    spec.format = PA_SAMPLE_S16LE;
+    spec.channels = channels;
+    spec.rate = mix_rate;
+
+    int error_code;
+    pulse = pa_simple_new(NULL,         // default server
+                          "Godot",      // application name
+                          PA_STREAM_PLAYBACK,
+                          NULL,         // default device
+                          "Sound",      // stream description
+                          &spec,
+                          NULL,         // use default channel map
+                          NULL,         // use default buffering attributes
+                          &error_code
+                          );
+
+    if (pulse == NULL) {
+
+        fprintf(stderr, "PulseAudio ERR: %s\n", pa_strerror(error_code));\
+        ERR_FAIL_COND_V(pulse == NULL, ERR_CANT_OPEN);
+    }
+
+    int latency = GLOBAL_DEF("audio/output_latency", 25);
+    buffer_size = nearest_power_of_2(latency * mix_rate / 1000);
+
+    samples_in = memnew_arr(int32_t, buffer_size * channels);
+    samples_out = memnew_arr(int16_t, buffer_size * channels);
+
+    mutex = Mutex::create();
+    thread = Thread::create(AudioDriverPulseAudio::thread_func, this);
+
+	return OK;
+}
+
+void AudioDriverPulseAudio::thread_func(void* p_udata) {
+
+    AudioDriverPulseAudio* ad = (AudioDriverPulseAudio*)p_udata;
+
+	while (!ad->exit_thread) {
+
+		if (!ad->active) {
+
+            for (unsigned int i=0; i < ad->buffer_size * ad->channels; i++) {
+
+				ad->samples_out[i] = 0;
+            }
+
+		} else {
+
+			ad->lock();
+
+			ad->audio_server_process(ad->buffer_size, ad->samples_in);
+
+			ad->unlock();
+
+            for (unsigned int i=0; i < ad->buffer_size * ad->channels;i ++) {
+
+                ad->samples_out[i] = ad->samples_in[i] >> 16;
+			}
+        }
+
+        // pa_simple_write always consumes the entire buffer
+
+        int error_code;
+        int byte_size = ad->buffer_size * sizeof(int16_t) * ad->channels;
+        if (pa_simple_write(ad->pulse, ad->samples_out, byte_size, &error_code) < 0) {
+
+            // can't recover here
+            fprintf(stderr, "PulseAudio failed and can't recover: %s\n", pa_strerror(error_code));
+            ad->active = false;
+            ad->exit_thread = true;
+            break;
+        }
+    }
+
+    ad->thread_exited = true;
+}
+
+void AudioDriverPulseAudio::start() {
+
+	active = true;
+}
+
+int AudioDriverPulseAudio::get_mix_rate() const {
+
+	return mix_rate;
+}
+
+AudioDriverSW::OutputFormat AudioDriverPulseAudio::get_output_format() const {
+
+	return output_format;
+}
+
+void AudioDriverPulseAudio::lock() {
+
+	if (!thread || !mutex)
+		return;
+	mutex->lock();
+}
+
+void AudioDriverPulseAudio::unlock() {
+
+	if (!thread || !mutex)
+		return;
+	mutex->unlock();
+}
+
+void AudioDriverPulseAudio::finish() {
+
+	if (!thread)
+		return;
+
+	exit_thread = true;
+	Thread::wait_to_finish(thread);
+
+    if (pulse)
+        pa_simple_free(pulse);
+
+	if (samples_in) {
+		memdelete_arr(samples_in);
+		memdelete_arr(samples_out);
+	};
+
+	memdelete(thread);
+    if (mutex) {
+		memdelete(mutex);
+        mutex = NULL;
+    }
+
+	thread = NULL;
+}
+
+AudioDriverPulseAudio::AudioDriverPulseAudio() {
+
+	mutex = NULL;
+    thread = NULL;
+    pulse = NULL;
+}
+
+AudioDriverPulseAudio::~AudioDriverPulseAudio() {
+
+}
+
+#endif

+ 79 - 0
drivers/pulseaudio/audio_driver_pulseaudio.h

@@ -0,0 +1,79 @@
+/*************************************************************************/
+/*  audio_driver_pulseaudio.h                                            */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                    http://www.godotengine.org                         */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                 */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+#include "servers/audio/audio_server_sw.h"
+
+#ifdef PULSEAUDIO_ENABLED
+
+#include "core/os/thread.h"
+#include "core/os/mutex.h"
+
+#include <pulse/simple.h>
+
+class AudioDriverPulseAudio : public AudioDriverSW {
+
+	Thread* thread;
+	Mutex* mutex;
+
+    pa_simple* pulse;
+
+	int32_t* samples_in;
+	int16_t* samples_out;
+
+	static void thread_func(void* p_udata);
+
+	unsigned int mix_rate;
+	OutputFormat output_format;
+
+    unsigned int buffer_size;
+	int channels;
+
+	bool active;
+	bool thread_exited;
+	mutable bool exit_thread;
+	bool pcm_open;
+
+public:
+
+	const char* get_name() const {
+        return "PulseAudio";
+	};
+
+	virtual Error init();
+	virtual void start();
+	virtual int get_mix_rate() const;
+	virtual OutputFormat get_output_format() const;
+	virtual void lock();
+	virtual void unlock();
+	virtual void finish();
+
+    AudioDriverPulseAudio();
+    ~AudioDriverPulseAudio();
+};
+
+#endif

+ 1 - 0
main/main.cpp

@@ -147,6 +147,7 @@ void Main::print_help(const char* p_binary) {
 			OS::get_singleton()->print(", ");
 		OS::get_singleton()->print("%s",OS::get_singleton()->get_audio_driver_name(i));
 	}
+    OS::get_singleton()->print(")\n");
 	OS::get_singleton()->print("\t-rthread <mode>\t : Render Thread Mode ('unsafe', 'safe', 'separate).");
 	OS::get_singleton()->print(")\n");
 	OS::get_singleton()->print("\t-s,-script [script] : Run a script.\n");	

+ 1 - 1
modules/gdscript/gd_script.cpp

@@ -1440,8 +1440,8 @@ GDInstance* GDScript::_create_instance(const Variant** p_args,int p_argcount,Obj
 
 	if (err.error!=Variant::CallError::CALL_OK) {
 		instance->script=Ref<GDScript>();
+		instance->owner->set_script_instance(NULL);
 		instances.erase(p_owner);
-		memdelete(instance);
 		ERR_FAIL_COND_V(err.error!=Variant::CallError::CALL_OK, NULL); //error consrtucting
 	}
 

+ 8 - 0
platform/x11/detect.py

@@ -114,6 +114,14 @@ def configure(env):
 	
 	env.Append(CPPFLAGS=['-DOPENGL_ENABLED','-DGLEW_ENABLED'])
 	env.Append(CPPFLAGS=["-DALSA_ENABLED"])
+
+        if not os.system("pkg-config --exists libpulse-simple"):
+            print("Enabling PulseAudio")
+            env.Append(CPPFLAGS=["-DPULSEAUDIO_ENABLED"])
+            env.ParseConfig('pkg-config --cflags --libs libpulse-simple')
+        else:
+            print("PulseAudio development libraries not found, disabling driver")
+
 	env.Append(CPPFLAGS=['-DX11_ENABLED','-DUNIX_ENABLED','-DGLES2_ENABLED','-DGLES1_ENABLED','-DGLES_OVER_GL'])
 	env.Append(LIBS=['GL', 'GLU', 'pthread','asound','z']) #TODO detect linux/BSD!
 	#env.Append(CPPFLAGS=['-DMPC_FIXED_POINT'])

+ 16 - 0
platform/x11/os_x11.cpp

@@ -74,6 +74,18 @@ OS::VideoMode OS_X11::get_default_video_mode() const {
 	return OS::VideoMode(800,600,false);
 }
 
+int OS_X11::get_audio_driver_count() const {
+
+    return AudioDriverManagerSW::get_driver_count();
+}
+
+const char *OS_X11::get_audio_driver_name(int p_driver) const {
+
+    AudioDriverSW* driver = AudioDriverManagerSW::get_driver(p_driver);
+    ERR_FAIL_COND_V( !driver, "" );
+    return AudioDriverManagerSW::get_driver(p_driver)->get_name();
+}
+
 void OS_X11::initialize(const VideoMode& p_desired,int p_video_driver,int p_audio_driver) {
 
 	last_button_state=0;
@@ -1450,6 +1462,10 @@ OS_X11::OS_X11() {
 	AudioDriverManagerSW::add_driver(&driver_alsa);
 #endif
 
+#ifdef PULSEAUDIO_ENABLED
+	AudioDriverManagerSW::add_driver(&driver_pulseaudio);
+#endif
+
 	minimized = false;
 	xim_style=NULL;
 	mouse_mode=MOUSE_MODE_VISIBLE;

+ 9 - 1
platform/x11/os_x11.h

@@ -43,6 +43,7 @@
 #include "servers/spatial_sound_2d/spatial_sound_2d_server_sw.h"
 #include "drivers/rtaudio/audio_driver_rtaudio.h"
 #include "drivers/alsa/audio_driver_alsa.h"
+#include "drivers/pulseaudio/audio_driver_pulseaudio.h"
 #include "servers/physics_2d/physics_2d_server_sw.h"
 
 #include <X11/keysym.h>
@@ -129,6 +130,10 @@ class OS_X11 : public OS_Unix {
 	AudioDriverALSA driver_alsa;
 #endif
 
+#ifdef PULSEAUDIO_ENABLED
+	AudioDriverPulseAudio driver_pulseaudio;
+#endif
+
 	enum {
 		JOYSTICKS_MAX = 8,
 		MAX_JOY_AXIS = 32768, // I've no idea
@@ -160,7 +165,10 @@ protected:
 	virtual int get_video_driver_count() const;
 	virtual const char * get_video_driver_name(int p_driver) const;	
 	virtual VideoMode get_default_video_mode() const;
-	
+
+    virtual int get_audio_driver_count() const;
+    virtual const char * get_audio_driver_name(int p_driver) const;
+
 	virtual void initialize(const VideoMode& p_desired,int p_video_driver,int p_audio_driver);	
 	virtual void finalize();
 

+ 120 - 75
scene/animation/tween.cpp

@@ -124,32 +124,34 @@ void Tween::_bind_methods() {
 	ObjectTypeDB::bind_method(_MD("get_tween_process_mode"),&Tween::get_tween_process_mode);
 
 	ObjectTypeDB::bind_method(_MD("start"),&Tween::start );
-	ObjectTypeDB::bind_method(_MD("reset","node","key"),&Tween::reset );
+	ObjectTypeDB::bind_method(_MD("reset","object","key"),&Tween::reset );
 	ObjectTypeDB::bind_method(_MD("reset_all"),&Tween::reset_all );
-	ObjectTypeDB::bind_method(_MD("stop","node","key"),&Tween::stop );
+	ObjectTypeDB::bind_method(_MD("stop","object","key"),&Tween::stop );
 	ObjectTypeDB::bind_method(_MD("stop_all"),&Tween::stop_all );
-	ObjectTypeDB::bind_method(_MD("resume","node","key"),&Tween::resume );
+	ObjectTypeDB::bind_method(_MD("resume","object","key"),&Tween::resume );
 	ObjectTypeDB::bind_method(_MD("resume_all"),&Tween::resume_all );
-	ObjectTypeDB::bind_method(_MD("remove","node","key"),&Tween::remove );
+	ObjectTypeDB::bind_method(_MD("remove","object","key"),&Tween::remove );
 	ObjectTypeDB::bind_method(_MD("remove_all"),&Tween::remove_all );
 	ObjectTypeDB::bind_method(_MD("seek","time"),&Tween::seek );
 	ObjectTypeDB::bind_method(_MD("tell"),&Tween::tell );
 	ObjectTypeDB::bind_method(_MD("get_runtime"),&Tween::get_runtime );
 
-	ObjectTypeDB::bind_method(_MD("interpolate_property","node","property","initial_val","final_val","times_in_sec","trans_type","ease_type","delay"),&Tween::interpolate_property, DEFVAL(0) );
-	ObjectTypeDB::bind_method(_MD("interpolate_method","node","method","initial_val","final_val","times_in_sec","trans_type","ease_type","delay"),&Tween::interpolate_method, DEFVAL(0) );
-	ObjectTypeDB::bind_method(_MD("interpolate_callback","node","callback","times_in_sec","args"),&Tween::interpolate_callback, DEFVAL(Variant()) );
-	ObjectTypeDB::bind_method(_MD("follow_property","node","property","initial_val","target","target_property","times_in_sec","trans_type","ease_type","delay"),&Tween::follow_property, DEFVAL(0) );
-	ObjectTypeDB::bind_method(_MD("follow_method","node","method","initial_val","target","target_method","times_in_sec","trans_type","ease_type","delay"),&Tween::follow_method, DEFVAL(0) );
-	ObjectTypeDB::bind_method(_MD("targeting_property","node","property","initial","initial_val","final_val","times_in_sec","trans_type","ease_type","delay"),&Tween::targeting_property, DEFVAL(0) );
-	ObjectTypeDB::bind_method(_MD("targeting_method","node","method","initial","initial_method","final_val","times_in_sec","trans_type","ease_type","delay"),&Tween::targeting_method, DEFVAL(0) );
+	ObjectTypeDB::bind_method(_MD("interpolate_property","object","property","initial_val","final_val","times_in_sec","trans_type","ease_type","delay"),&Tween::interpolate_property, DEFVAL(0) );
+	ObjectTypeDB::bind_method(_MD("interpolate_method","object","method","initial_val","final_val","times_in_sec","trans_type","ease_type","delay"),&Tween::interpolate_method, DEFVAL(0) );
+	ObjectTypeDB::bind_method(_MD("interpolate_callback","object","times_in_sec","callback","args"),&Tween::interpolate_callback, DEFVAL(Variant()) );
+	ObjectTypeDB::bind_method(_MD("follow_property","object","property","initial_val","target","target_property","times_in_sec","trans_type","ease_type","delay"),&Tween::follow_property, DEFVAL(0) );
+	ObjectTypeDB::bind_method(_MD("follow_method","object","method","initial_val","target","target_method","times_in_sec","trans_type","ease_type","delay"),&Tween::follow_method, DEFVAL(0) );
+	ObjectTypeDB::bind_method(_MD("targeting_property","object","property","initial","initial_val","final_val","times_in_sec","trans_type","ease_type","delay"),&Tween::targeting_property, DEFVAL(0) );
+	ObjectTypeDB::bind_method(_MD("targeting_method","object","method","initial","initial_method","final_val","times_in_sec","trans_type","ease_type","delay"),&Tween::targeting_method, DEFVAL(0) );
 
-	ADD_SIGNAL( MethodInfo("tween_start", PropertyInfo( Variant::OBJECT,"node"), PropertyInfo( Variant::STRING,"key")) );
-	ADD_SIGNAL( MethodInfo("tween_step", PropertyInfo( Variant::OBJECT,"node"), PropertyInfo( Variant::STRING,"key"), PropertyInfo( Variant::REAL,"elapsed"), PropertyInfo( Variant::OBJECT,"value")) );
-	ADD_SIGNAL( MethodInfo("tween_complete", PropertyInfo( Variant::OBJECT,"node"), PropertyInfo( Variant::STRING,"key")) );
+	ADD_SIGNAL( MethodInfo("tween_start", PropertyInfo( Variant::OBJECT,"object"), PropertyInfo( Variant::STRING,"key")) );
+	ADD_SIGNAL( MethodInfo("tween_step", PropertyInfo( Variant::OBJECT,"object"), PropertyInfo( Variant::STRING,"key"), PropertyInfo( Variant::REAL,"elapsed"), PropertyInfo( Variant::OBJECT,"value")) );
+	ADD_SIGNAL( MethodInfo("tween_complete", PropertyInfo( Variant::OBJECT,"object"), PropertyInfo( Variant::STRING,"key")) );
 
 	ADD_PROPERTY( PropertyInfo( Variant::INT, "playback/process_mode", PROPERTY_HINT_ENUM, "Fixed,Idle"), _SCS("set_tween_process_mode"), _SCS("get_tween_process_mode"));
-	//ADD_PROPERTY( PropertyInfo( Variant::BOOL, "activate"), _SCS("set_active"), _SCS("is_active"));
+
+	BIND_CONSTANT(TWEEN_PROCESS_FIXED);
+	BIND_CONSTANT(TWEEN_PROCESS_IDLE);
 
 	BIND_CONSTANT(TRANS_LINEAR);
 	BIND_CONSTANT(TRANS_SINE);
@@ -181,19 +183,19 @@ Variant& Tween::_get_initial_val(InterpolateData& p_data) {
 		case TARGETING_PROPERTY:
 		case TARGETING_METHOD: {
 
-				Node *node = get_node(p_data.target);
-				ERR_FAIL_COND_V(node == NULL,p_data.initial_val);
+				Object *object = ObjectDB::get_instance(p_data.target_id);
+				ERR_FAIL_COND_V(object == NULL,p_data.initial_val);
 
 				static Variant initial_val;
 				if(p_data.type == TARGETING_PROPERTY) {
 
 					bool valid = false;
-					initial_val = node->get(p_data.target_key, &valid);
+					initial_val = object->get(p_data.target_key, &valid);
 					ERR_FAIL_COND_V(!valid,p_data.initial_val);
 				} else {
 
 					Variant::CallError error;
-					initial_val = node->call(p_data.target_key, NULL, 0, error);
+					initial_val = object->call(p_data.target_key, NULL, 0, error);
 					ERR_FAIL_COND_V(error.error != Variant::CallError::CALL_OK,p_data.initial_val);
 				}
 				return initial_val;
@@ -213,7 +215,7 @@ Variant& Tween::_get_delta_val(InterpolateData& p_data) {
 		case FOLLOW_PROPERTY:
 		case FOLLOW_METHOD: {
 
-				Node *target = get_node(p_data.target);
+				Object *target = ObjectDB::get_instance(p_data.target_id);
 				ERR_FAIL_COND_V(target == NULL,p_data.initial_val);
 
 				Variant final_val;
@@ -264,6 +266,11 @@ Variant Tween::_run_equation(InterpolateData& p_data) {
 
 	switch(initial_val.get_type())
 	{
+
+	case Variant::BOOL:
+		result = ((int) _run_equation(p_data.trans_type, p_data.ease_type, p_data.elapsed - p_data.delay, (int) initial_val, (int) delta_val, p_data.times_in_sec)) >= 0.5;
+		break;
+
 	case Variant::INT:
 		result = (int) _run_equation(p_data.trans_type, p_data.ease_type, p_data.elapsed - p_data.delay, (int) initial_val, (int) delta_val, p_data.times_in_sec);
 		break;
@@ -409,7 +416,7 @@ Variant Tween::_run_equation(InterpolateData& p_data) {
 
 bool Tween::_apply_tween_value(InterpolateData& p_data, Variant& value) {
 
-	Object *object = get_node(p_data.path);
+	Object *object = ObjectDB::get_instance(p_data.id);
 	ERR_FAIL_COND_V(object == NULL, false);
 
 	switch(p_data.type) {
@@ -452,6 +459,7 @@ void Tween::_tween_process(float p_delta) {
 		return;
 	p_delta *= speed_scale;
 
+	pending_update ++;
 	// if repeat and all interpolates was finished then reset all interpolates
 	if(repeat) {
 		bool all_finished = true;
@@ -476,7 +484,7 @@ void Tween::_tween_process(float p_delta) {
 		if(!data.active || data.finish)
 			continue;
 
-		Object *object = get_node(data.path);
+		Object *object = ObjectDB::get_instance(data.id);
 		if(object == NULL)
 			continue;
 
@@ -523,6 +531,7 @@ void Tween::_tween_process(float p_delta) {
 		if(data.finish)
 			emit_signal("tween_complete",object,data.key);
 	}
+	pending_update --;
 }
 
 void Tween::set_tween_process_mode(TweenProcessMode p_mode) {
@@ -598,16 +607,17 @@ bool Tween::start() {
 	return true;
 }
 
-bool Tween::reset(Node *p_node, String p_key) {
+bool Tween::reset(Object *p_object, String p_key) {
 
+	pending_update ++;
 	for(List<InterpolateData>::Element *E=interpolates.front();E;E=E->next()) {
 
 		InterpolateData& data = E->get();
-		Node *node = get_node(data.path);
-		if(node == NULL)
+		Object *object = ObjectDB::get_instance(data.id);
+		if(object == NULL)
 			continue;
 
-		if(node == p_node && data.key == p_key) {
+		if(object == p_object && data.key == p_key) {
 
 			data.elapsed = 0;
 			data.finish = false;
@@ -615,11 +625,13 @@ bool Tween::reset(Node *p_node, String p_key) {
 				_apply_tween_value(data, data.initial_val);
 		}
 	}
+	pending_update --;
 	return true;
 }
 
 bool Tween::reset_all() {
 
+	pending_update ++;
 	for(List<InterpolateData>::Element *E=interpolates.front();E;E=E->next()) {
 
 		InterpolateData& data = E->get();
@@ -628,20 +640,23 @@ bool Tween::reset_all() {
 		if(data.delay == 0)
 			_apply_tween_value(data, data.initial_val);
 	}
+	pending_update --;
 	return true;
 }
 
-bool Tween::stop(Node *p_node, String p_key) {
+bool Tween::stop(Object *p_object, String p_key) {
 
+	pending_update ++;
 	for(List<InterpolateData>::Element *E=interpolates.front();E;E=E->next()) {
 
 		InterpolateData& data = E->get();
-		Node *node = get_node(data.path);
-		if(node == NULL)
+		Object *object = ObjectDB::get_instance(data.id);
+		if(object == NULL)
 			continue;
-		if(node == p_node && data.key == p_key)
+		if(object == p_object && data.key == p_key)
 			data.active = false;
 	}
+	pending_update --;
 	return true;
 }
 
@@ -650,28 +665,32 @@ bool Tween::stop_all() {
 	set_active(false);
 	_set_process(false);
 
+	pending_update ++;
 	for(List<InterpolateData>::Element *E=interpolates.front();E;E=E->next()) {
 
 		InterpolateData& data = E->get();
 		data.active = false;
 	}
+	pending_update --;
 	return true;
 }
 
-bool Tween::resume(Node *p_node, String p_key) {
+bool Tween::resume(Object *p_object, String p_key) {
 
 	set_active(true);
 	_set_process(true);
 
+	pending_update ++;
 	for(List<InterpolateData>::Element *E=interpolates.front();E;E=E->next()) {
 
 		InterpolateData& data = E->get();
-		Node *node = get_node(data.path);
-		if(node == NULL)
+		Object *object = ObjectDB::get_instance(data.id);
+		if(object == NULL)
 			continue;
-		if(node == p_node && data.key == p_key)
+		if(object == p_object && data.key == p_key)
 			data.active = true;
 	}
+	pending_update --;
 	return true;
 }
 
@@ -680,23 +699,26 @@ bool Tween::resume_all() {
 	set_active(true);
 	_set_process(true);
 
+	pending_update ++;
 	for(List<InterpolateData>::Element *E=interpolates.front();E;E=E->next()) {
 
 		InterpolateData& data = E->get();
 		data.active = true;
 	}
+	pending_update --;
 	return true;
 }
 
-bool Tween::remove(Node *p_node, String p_key) {
+bool Tween::remove(Object *p_object, String p_key) {
 
+	ERR_FAIL_COND_V(pending_update != 0, false);
 	for(List<InterpolateData>::Element *E=interpolates.front();E;E=E->next()) {
 
 		InterpolateData& data = E->get();
-		Node *node = get_node(data.path);
-		if(node == NULL)
+		Object *object = ObjectDB::get_instance(data.id);
+		if(object == NULL)
 			continue;
-		if(node == p_node && data.key == p_key) {
+		if(object == p_object && data.key == p_key) {
 			interpolates.erase(E);
 			return true;
 		}
@@ -706,6 +728,7 @@ bool Tween::remove(Node *p_node, String p_key) {
 
 bool Tween::remove_all() {
 
+	ERR_FAIL_COND_V(pending_update != 0, false);
 	set_active(false);
 	_set_process(false);
 	interpolates.clear();
@@ -714,6 +737,7 @@ bool Tween::remove_all() {
 
 bool Tween::seek(real_t p_time) {
 
+	pending_update ++;
 	for(List<InterpolateData>::Element *E=interpolates.front();E;E=E->next()) {
 
 		InterpolateData& data = E->get();
@@ -744,11 +768,13 @@ bool Tween::seek(real_t p_time) {
 
 		_apply_tween_value(data, result);
 	}
+	pending_update --;
 	return true;
 }
 
 real_t Tween::tell() const {
 
+	pending_update ++;
 	real_t pos = 0;
 	for(const List<InterpolateData>::Element *E=interpolates.front();E;E=E->next()) {
 
@@ -756,11 +782,13 @@ real_t Tween::tell() const {
 		if(data.elapsed > pos)
 			pos = data.elapsed;
 	}
+	pending_update --;
 	return pos;
 }
 
 real_t Tween::get_runtime() const {
 
+	pending_update ++;
 	real_t runtime = 0;
 	for(const List<InterpolateData>::Element *E=interpolates.front();E;E=E->next()) {
 
@@ -769,6 +797,7 @@ real_t Tween::get_runtime() const {
 		if(t > runtime)
 			runtime = t;
 	}
+	pending_update --;
 	return runtime;
 }
 
@@ -779,6 +808,12 @@ bool Tween::_calc_delta_val(const Variant& p_initial_val, const Variant& p_final
 	Variant& delta_val = p_delta_val;
 
 	switch(initial_val.get_type()) {
+
+		case Variant::BOOL:
+			//delta_val = p_final_val;
+			delta_val = (int) p_final_val - (int) p_initial_val;
+			break;
+
 		case Variant::INT:
 			delta_val = (int) final_val - (int) initial_val;
 			break;
@@ -873,7 +908,7 @@ bool Tween::_calc_delta_val(const Variant& p_initial_val, const Variant& p_final
 	return true;
 }
 
-bool Tween::interpolate_property(Node *p_node
+bool Tween::interpolate_property(Object *p_object
 	, String p_property
 	, Variant p_initial_val
 	, Variant p_final_val
@@ -882,11 +917,12 @@ bool Tween::interpolate_property(Node *p_node
 	, EaseType p_ease_type
 	, real_t p_delay
 ) {
+	ERR_FAIL_COND_V(pending_update != 0, false);
 	// convert INT to REAL is better for interpolaters
 	if(p_initial_val.get_type() == Variant::INT) p_initial_val = p_initial_val.operator real_t();
 	if(p_final_val.get_type() == Variant::INT) p_final_val = p_final_val.operator real_t();
 
-	ERR_FAIL_COND_V(p_node == NULL, false);
+	ERR_FAIL_COND_V(p_object == NULL, false);
 	ERR_FAIL_COND_V(p_initial_val.get_type() != p_final_val.get_type(), false);
 	ERR_FAIL_COND_V(p_times_in_sec <= 0, false);
 	ERR_FAIL_COND_V(p_trans_type < 0 || p_trans_type >= TRANS_COUNT, false);
@@ -894,7 +930,7 @@ bool Tween::interpolate_property(Node *p_node
 	ERR_FAIL_COND_V(p_delay < 0, false);
 
 	bool prop_valid = false;
-	p_node->get(p_property,&prop_valid);
+	p_object->get(p_property,&prop_valid);
 	ERR_FAIL_COND_V(!prop_valid, false);
 
 	InterpolateData data;
@@ -903,7 +939,7 @@ bool Tween::interpolate_property(Node *p_node
 	data.finish = false;
 	data.elapsed = 0;
 
-	data.path = p_node->get_path();
+	data.id = p_object->get_instance_ID();
 	data.key = p_property;
 	data.initial_val = p_initial_val;
 	data.final_val = p_final_val;
@@ -919,7 +955,7 @@ bool Tween::interpolate_property(Node *p_node
 	return true;
 }
 
-bool Tween::interpolate_method(Node *p_node
+bool Tween::interpolate_method(Object *p_object
 	, String p_method
 	, Variant p_initial_val
 	, Variant p_final_val
@@ -928,18 +964,19 @@ bool Tween::interpolate_method(Node *p_node
 	, EaseType p_ease_type
 	, real_t p_delay
 ) {
+	ERR_FAIL_COND_V(pending_update != 0, false);
 	// convert INT to REAL is better for interpolaters
 	if(p_initial_val.get_type() == Variant::INT) p_initial_val = p_initial_val.operator real_t();
 	if(p_final_val.get_type() == Variant::INT) p_final_val = p_final_val.operator real_t();
 
-	ERR_FAIL_COND_V(p_node == NULL, false);
+	ERR_FAIL_COND_V(p_object == NULL, false);
 	ERR_FAIL_COND_V(p_initial_val.get_type() != p_final_val.get_type(), false);
 	ERR_FAIL_COND_V(p_times_in_sec <= 0, false);
 	ERR_FAIL_COND_V(p_trans_type < 0 || p_trans_type >= TRANS_COUNT, false);
 	ERR_FAIL_COND_V(p_ease_type < 0 || p_ease_type >= EASE_COUNT, false);
 	ERR_FAIL_COND_V(p_delay < 0, false);
 
-	ERR_FAIL_COND_V(!p_node->has_method(p_method), false);
+	ERR_FAIL_COND_V(!p_object->has_method(p_method), false);
 
 	InterpolateData data;
 	data.active = true;
@@ -947,7 +984,7 @@ bool Tween::interpolate_method(Node *p_node
 	data.finish = false;
 	data.elapsed = 0;
 
-	data.path = p_node->get_path();
+	data.id = p_object->get_instance_ID();
 	data.key = p_method;
 	data.initial_val = p_initial_val;
 	data.final_val = p_final_val;
@@ -963,16 +1000,17 @@ bool Tween::interpolate_method(Node *p_node
 	return true;
 }
 
-bool Tween::interpolate_callback(Node *p_node
-	, String p_callback
+bool Tween::interpolate_callback(Object *p_object
 	, real_t p_times_in_sec
+	, String p_callback
 	, Variant p_arg
 ) {
 
-	ERR_FAIL_COND_V(p_node == NULL, false);
+	ERR_FAIL_COND_V(pending_update != 0, false);
+	ERR_FAIL_COND_V(p_object == NULL, false);
 	ERR_FAIL_COND_V(p_times_in_sec < 0, false);
 
-	ERR_FAIL_COND_V(!p_node->has_method(p_callback), false);
+	ERR_FAIL_COND_V(!p_object->has_method(p_callback), false);
 
 	InterpolateData data;
 	data.active = true;
@@ -980,30 +1018,33 @@ bool Tween::interpolate_callback(Node *p_node
 	data.finish = false;
 	data.elapsed = 0;
 
-	data.path = p_node->get_path();
+	data.id = p_object->get_instance_ID();
 	data.key = p_callback;
 	data.times_in_sec = p_times_in_sec;
 	data.delay = 0;
 	data.arg = p_arg;
 
+	pending_update ++;
 	interpolates.push_back(data);
+	pending_update --;
 	return true;
 }
 
-bool Tween::follow_property(Node *p_node
+bool Tween::follow_property(Object *p_object
 	, String p_property
 	, Variant p_initial_val
-	, Node *p_target
+	, Object *p_target
 	, String p_target_property
 	, real_t p_times_in_sec
 	, TransitionType p_trans_type
 	, EaseType p_ease_type
 	, real_t p_delay
 ) {
+	ERR_FAIL_COND_V(pending_update != 0, false);
 	// convert INT to REAL is better for interpolaters
 	if(p_initial_val.get_type() == Variant::INT) p_initial_val = p_initial_val.operator real_t();
 
-	ERR_FAIL_COND_V(p_node == NULL, false);
+	ERR_FAIL_COND_V(p_object == NULL, false);
 	ERR_FAIL_COND_V(p_target == NULL, false);
 	ERR_FAIL_COND_V(p_times_in_sec <= 0, false);
 	ERR_FAIL_COND_V(p_trans_type < 0 || p_trans_type >= TRANS_COUNT, false);
@@ -1011,7 +1052,7 @@ bool Tween::follow_property(Node *p_node
 	ERR_FAIL_COND_V(p_delay < 0, false);
 
 	bool prop_valid = false;
-	p_node->get(p_property,&prop_valid);
+	p_object->get(p_property,&prop_valid);
 	ERR_FAIL_COND_V(!prop_valid, false);
 
 	bool target_prop_valid = false;
@@ -1028,10 +1069,10 @@ bool Tween::follow_property(Node *p_node
 	data.finish = false;
 	data.elapsed = 0;
 
-	data.path = p_node->get_path();
+	data.id = p_object->get_instance_ID();
 	data.key = p_property;
 	data.initial_val = p_initial_val;
-	data.target = p_target->get_path();
+	data.target_id = p_target->get_instance_ID();
 	data.target_key = p_target_property;
 	data.times_in_sec = p_times_in_sec;
 	data.trans_type = p_trans_type;
@@ -1042,27 +1083,28 @@ bool Tween::follow_property(Node *p_node
 	return true;
 }
 
-bool Tween::follow_method(Node *p_node
+bool Tween::follow_method(Object *p_object
 	, String p_method
 	, Variant p_initial_val
-	, Node *p_target
+	, Object *p_target
 	, String p_target_method
 	, real_t p_times_in_sec
 	, TransitionType p_trans_type
 	, EaseType p_ease_type
 	, real_t p_delay
 ) {
+	ERR_FAIL_COND_V(pending_update != 0, false);
 	// convert INT to REAL is better for interpolaters
 	if(p_initial_val.get_type() == Variant::INT) p_initial_val = p_initial_val.operator real_t();
 
-	ERR_FAIL_COND_V(p_node == NULL, false);
+	ERR_FAIL_COND_V(p_object == NULL, false);
 	ERR_FAIL_COND_V(p_target == NULL, false);
 	ERR_FAIL_COND_V(p_times_in_sec <= 0, false);
 	ERR_FAIL_COND_V(p_trans_type < 0 || p_trans_type >= TRANS_COUNT, false);
 	ERR_FAIL_COND_V(p_ease_type < 0 || p_ease_type >= EASE_COUNT, false);
 	ERR_FAIL_COND_V(p_delay < 0, false);
 
-	ERR_FAIL_COND_V(!p_node->has_method(p_method), false);
+	ERR_FAIL_COND_V(!p_object->has_method(p_method), false);
 	ERR_FAIL_COND_V(!p_target->has_method(p_target_method), false);
 
 	Variant::CallError error;
@@ -1079,10 +1121,10 @@ bool Tween::follow_method(Node *p_node
 	data.finish = false;
 	data.elapsed = 0;
 
-	data.path = p_node->get_path();
+	data.id = p_object->get_instance_ID();
 	data.key = p_method;
 	data.initial_val = p_initial_val;
-	data.target = p_target->get_path();
+	data.target_id = p_target->get_instance_ID();
 	data.target_key = p_target_method;
 	data.times_in_sec = p_times_in_sec;
 	data.trans_type = p_trans_type;
@@ -1093,9 +1135,9 @@ bool Tween::follow_method(Node *p_node
 	return true;
 }
 
-bool Tween::targeting_property(Node *p_node
+bool Tween::targeting_property(Object *p_object
 	, String p_property
-	, Node *p_initial
+	, Object *p_initial
 	, String p_initial_property
 	, Variant p_final_val
 	, real_t p_times_in_sec
@@ -1103,10 +1145,11 @@ bool Tween::targeting_property(Node *p_node
 	, EaseType p_ease_type
 	, real_t p_delay
 ) {
+	ERR_FAIL_COND_V(pending_update != 0, false);
 	// convert INT to REAL is better for interpolaters
 	if(p_final_val.get_type() == Variant::INT) p_final_val = p_final_val.operator real_t();
 
-	ERR_FAIL_COND_V(p_node == NULL, false);
+	ERR_FAIL_COND_V(p_object == NULL, false);
 	ERR_FAIL_COND_V(p_initial == NULL, false);
 	ERR_FAIL_COND_V(p_times_in_sec <= 0, false);
 	ERR_FAIL_COND_V(p_trans_type < 0 || p_trans_type >= TRANS_COUNT, false);
@@ -1114,7 +1157,7 @@ bool Tween::targeting_property(Node *p_node
 	ERR_FAIL_COND_V(p_delay < 0, false);
 
 	bool prop_valid = false;
-	p_node->get(p_property,&prop_valid);
+	p_object->get(p_property,&prop_valid);
 	ERR_FAIL_COND_V(!prop_valid, false);
 
 	bool initial_prop_valid = false;
@@ -1131,9 +1174,9 @@ bool Tween::targeting_property(Node *p_node
 	data.finish = false;
 	data.elapsed = 0;
 
-	data.path = p_node->get_path();
+	data.id = p_object->get_instance_ID();
 	data.key = p_property;
-	data.target = p_initial->get_path();
+	data.target_id = p_initial->get_instance_ID();
 	data.target_key = p_initial_property;
 	data.initial_val = initial_val;
 	data.final_val = p_final_val;
@@ -1150,9 +1193,9 @@ bool Tween::targeting_property(Node *p_node
 }
 
 
-bool Tween::targeting_method(Node *p_node
+bool Tween::targeting_method(Object *p_object
 	, String p_method
-	, Node *p_initial
+	, Object *p_initial
 	, String p_initial_method
 	, Variant p_final_val
 	, real_t p_times_in_sec
@@ -1160,17 +1203,18 @@ bool Tween::targeting_method(Node *p_node
 	, EaseType p_ease_type
 	, real_t p_delay
 ) {
+	ERR_FAIL_COND_V(pending_update != 0, false);
 	// convert INT to REAL is better for interpolaters
 	if(p_final_val.get_type() == Variant::INT) p_final_val = p_final_val.operator real_t();
 
-	ERR_FAIL_COND_V(p_node == NULL, false);
+	ERR_FAIL_COND_V(p_object == NULL, false);
 	ERR_FAIL_COND_V(p_initial == NULL, false);
 	ERR_FAIL_COND_V(p_times_in_sec <= 0, false);
 	ERR_FAIL_COND_V(p_trans_type < 0 || p_trans_type >= TRANS_COUNT, false);
 	ERR_FAIL_COND_V(p_ease_type < 0 || p_ease_type >= EASE_COUNT, false);
 	ERR_FAIL_COND_V(p_delay < 0, false);
 
-	ERR_FAIL_COND_V(!p_node->has_method(p_method), false);
+	ERR_FAIL_COND_V(!p_object->has_method(p_method), false);
 	ERR_FAIL_COND_V(!p_initial->has_method(p_initial_method), false);
 
 	Variant::CallError error;
@@ -1187,9 +1231,9 @@ bool Tween::targeting_method(Node *p_node
 	data.finish = false;
 	data.elapsed = 0;
 
-	data.path = p_node->get_path();
+	data.id = p_object->get_instance_ID();
 	data.key = p_method;
-	data.target = p_initial->get_path();
+	data.target_id = p_initial->get_instance_ID();
 	data.target_key = p_initial_method;
 	data.initial_val = initial_val;
 	data.final_val = p_final_val;
@@ -1213,6 +1257,7 @@ Tween::Tween() {
 	active=false;
 	repeat=false;
 	speed_scale=1;
+	pending_update=0;
 }
 
 Tween::~Tween() {

+ 24 - 21
scene/animation/tween.h

@@ -54,15 +54,17 @@ public:
         TRANS_CIRC,
         TRANS_BOUNCE,
         TRANS_BACK,
-	TRANS_COUNT,
+
+		TRANS_COUNT,
     };
         
     enum EaseType {
         EASE_IN,
         EASE_OUT,
         EASE_IN_OUT,
-	EASE_OUT_IN,
-	EASE_COUNT,
+		EASE_OUT_IN,
+
+		EASE_COUNT,
     };
 
 private:
@@ -82,12 +84,12 @@ private:
 		InterpolateType type;
 		bool finish;
 		real_t elapsed;
-		NodePath path;
+		ObjectID id;
 		StringName key;
 		Variant initial_val;
 		Variant delta_val;
 		Variant final_val;
-		NodePath target;
+		ObjectID target_id;
 		StringName target_key;
 		real_t times_in_sec;
 		TransitionType trans_type;
@@ -102,6 +104,7 @@ private:
 	bool active;
 	bool repeat;
 	float speed_scale;
+	mutable int pending_update;
 
 	List<InterpolateData> interpolates;
 
@@ -142,20 +145,20 @@ public:
 	float get_speed() const;
 
 	bool start();
-	bool reset(Node *p_node, String p_key);
+	bool reset(Object *p_node, String p_key);
 	bool reset_all();
-	bool stop(Node *p_node, String p_key);
+	bool stop(Object *p_node, String p_key);
 	bool stop_all();
-	bool resume(Node *p_node, String p_key);
+	bool resume(Object *p_node, String p_key);
 	bool resume_all();
-	bool remove(Node *p_node, String p_key);
+	bool remove(Object *p_node, String p_key);
 	bool remove_all();
 
 	bool seek(real_t p_time);
 	real_t tell() const;
 	real_t get_runtime() const;
 
-	bool interpolate_property(Node *p_node
+	bool interpolate_property(Object *p_node
 		, String p_property
 		, Variant p_initial_val
 		, Variant p_final_val
@@ -165,7 +168,7 @@ public:
 		, real_t p_delay = 0
 	);
 
-	bool interpolate_method(Node *p_node
+	bool interpolate_method(Object *p_node
 		, String p_method
 		, Variant p_initial_val
 		, Variant p_final_val
@@ -175,16 +178,16 @@ public:
 		, real_t p_delay = 0
 	);
 
-	bool interpolate_callback(Node *p_node
-		, String p_callback
+	bool interpolate_callback(Object *p_node
 		, real_t p_times_in_sec
+		, String p_callback
 		, Variant p_arg = Variant()
 	);
 
-	bool follow_property(Node *p_node
+	bool follow_property(Object *p_node
 		, String p_property
 		, Variant p_initial_val
-		, Node *p_target
+		, Object *p_target
 		, String p_target_property
 		, real_t p_times_in_sec
 		, TransitionType p_trans_type
@@ -192,10 +195,10 @@ public:
 		, real_t p_delay = 0
 	);
 
-	bool follow_method(Node *p_node
+	bool follow_method(Object *p_node
 		, String p_method
 		, Variant p_initial_val
-		, Node *p_target
+		, Object *p_target
 		, String p_target_method
 		, real_t p_times_in_sec
 		, TransitionType p_trans_type
@@ -203,9 +206,9 @@ public:
 		, real_t p_delay = 0
 	);
 
-	bool targeting_property(Node *p_node
+	bool targeting_property(Object *p_node
 		, String p_property
-		, Node *p_initial
+		, Object *p_initial
 		, String p_initial_property
 		, Variant p_final_val
 		, real_t p_times_in_sec
@@ -214,9 +217,9 @@ public:
 		, real_t p_delay = 0
 	);
 
-	bool targeting_method(Node *p_node
+	bool targeting_method(Object *p_node
 		, String p_method
-		, Node *p_initial
+		, Object *p_initial
 		, String p_initial_method
 		, Variant p_final_val
 		, real_t p_times_in_sec

+ 0 - 21
scene/gui/text_edit.cpp

@@ -1921,27 +1921,6 @@ void TextEdit::_input_event(const InputEvent& p_input_event) {
 
                 } break;
 
-                case KEY_K:{
-                    if (!k.mod.command || k.mod.shift || k.mod.alt) {
-                        scancode_handled=false;
-                        break;
-                    }
-                    else {
-                        if (selection.active) {
-                            int ini = selection.from_line;
-                            int end = selection.to_line;
-                            for (int i=ini; i<= end; i++)
-                            {
-                                _insert_text(i,0,"#");
-                            }
-                        }
-                        else{
-                            _insert_text(cursor.line,0,"#");
-                        }
-                        update();
-                    }
-                    break;}
-
             case KEY_U:{
                 if (!k.mod.command || k.mod.shift || k.mod.alt) {
                     scancode_handled=false;

+ 1 - 1
tools/editor/plugins/script_editor_plugin.cpp

@@ -1599,7 +1599,7 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) {
 	edit_menu->get_popup()->add_item("Move Down",EDIT_MOVE_LINE_DOWN,KEY_MASK_ALT|KEY_DOWN);
 	edit_menu->get_popup()->add_item("Indent Left",EDIT_INDENT_LEFT,KEY_MASK_ALT|KEY_LEFT);
 	edit_menu->get_popup()->add_item("Indent Right",EDIT_INDENT_RIGHT,KEY_MASK_ALT|KEY_RIGHT);
-	edit_menu->get_popup()->add_item("Toggle Comment",EDIT_TOGGLE_COMMENT,KEY_MASK_CMD|KEY_SLASH);
+	edit_menu->get_popup()->add_item("Toggle Comment",EDIT_TOGGLE_COMMENT,KEY_MASK_CMD|KEY_K);
 	edit_menu->get_popup()->add_item("Clone Down",EDIT_CLONE_DOWN,KEY_MASK_CMD|KEY_B);
 	edit_menu->get_popup()->add_separator();
 	edit_menu->get_popup()->add_item("Complete Symbol",EDIT_COMPLETE,KEY_MASK_CMD|KEY_SPACE);

+ 42 - 9
tools/editor/scene_tree_dock.cpp

@@ -212,17 +212,50 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) {
 			if (!_validate_no_foreign())
 				break;
 
-			Node * node=scene_tree->get_selected();
-			ERR_FAIL_COND(!node->get_parent());
-			int current_pos = node->get_index();
-			int next_pos = current_pos + ((p_tool==TOOL_MOVE_DOWN)?1:-1);
+			bool MOVING_DOWN = (p_tool == TOOL_MOVE_DOWN);
+			bool MOVING_UP = !MOVING_DOWN;
 
-			if (next_pos< 0 || next_pos>=node->get_parent()->get_child_count())
-				break; // invalid position
+			Node *common_parent = scene_tree->get_selected()->get_parent();
+			List<Node*> selection = editor_selection->get_selected_node_list();
+			selection.sort_custom<Node::Comparator>();  // sort by index
+			if (MOVING_DOWN)
+				selection.invert();
+
+			int lowest_id = common_parent->get_child_count() - 1;
+			int highest_id = 0;
+			for (List<Node*>::Element *E = selection.front(); E; E = E->next()) {
+				int index = E->get()->get_index();
+
+				if (index > highest_id) highest_id = index;
+				if (index < lowest_id) lowest_id = index;
+
+				if (E->get()->get_parent() != common_parent)
+					common_parent = NULL;
+			}
+
+			if (!common_parent || (MOVING_DOWN && highest_id >= common_parent->get_child_count() - MOVING_DOWN) || (MOVING_UP && lowest_id == 0))
+				break; // one or more nodes can not be moved
+
+			if (selection.size() == 1) editor_data->get_undo_redo().create_action("Move Node In Parent");
+			if (selection.size() > 1) editor_data->get_undo_redo().create_action("Move Nodes In Parent");
+
+			for (int i = 0; i < selection.size(); i++) {
+				Node *top_node = selection[i];
+				Node *bottom_node = selection[selection.size() - 1 - i];
+				 
+				ERR_FAIL_COND(!top_node->get_parent());
+				ERR_FAIL_COND(!bottom_node->get_parent());
+
+				int top_node_pos = top_node->get_index();
+				int bottom_node_pos = bottom_node->get_index();
+
+				int top_node_pos_next = top_node_pos + (MOVING_DOWN ? 1 : -1);
+				int bottom_node_pos_next = bottom_node_pos + (MOVING_DOWN ? 1 : -1);
+
+				editor_data->get_undo_redo().add_do_method(top_node->get_parent(), "move_child", top_node, top_node_pos_next);
+				editor_data->get_undo_redo().add_undo_method(bottom_node->get_parent(), "move_child", bottom_node, bottom_node_pos);
+			}
 
-			editor_data->get_undo_redo().create_action("Move Node In Parent");
-			editor_data->get_undo_redo().add_do_method(node->get_parent(),"move_child",node,next_pos);
-			editor_data->get_undo_redo().add_undo_method(node->get_parent(),"move_child",node,current_pos);
 			editor_data->get_undo_redo().commit_action();
 
 		} break;