Răsfoiți Sursa

-Support for changing fonts
-Detect when free() might crash the project and throw error
-fixed 2D Bounce in physics (3d still broken)
-renamed “on_top” property to “behind_parent”, which makes more sense, old on_top remains there for compatibility but is invisible.
-large amount of fixes

Juan Linietsky 11 ani în urmă
părinte
comite
9f33134c93
47 a modificat fișierele cu 1483 adăugiri și 151 ștergeri
  1. 7 0
      core/bind/core_bind.cpp
  2. 2 0
      core/bind/core_bind.h
  3. 1 0
      core/io/resource_format_binary.cpp
  4. 2 1
      core/message_queue.cpp
  5. 52 2
      core/object.cpp
  6. 6 2
      core/object.h
  7. 5 0
      core/os/os.cpp
  8. 1 0
      core/os/os.h
  9. 87 87
      demos/2d/platformer/player.xml
  10. 13 12
      demos/2d/platformer/stage.xml
  11. 6 0
      drivers/unix/os_unix.cpp
  12. 1 0
      drivers/unix/os_unix.h
  13. 0 1
      modules/SCsub
  14. 1 1
      modules/gdscript/gd_functions.cpp
  15. 13 0
      modules/gdscript/gd_script.cpp
  16. 1 1
      platform/android/AndroidManifest.xml.template
  17. 0 5
      platform/android/java/ant.properties
  18. 2 1
      platform/android/java/src/com/android/godot/Godot.java
  19. 83 0
      platform/android/java/src/com/android/godot/GodotPaymentV3.java
  20. 71 0
      platform/android/java/src/com/android/godot/payments/ConsumeTask.java
  21. 79 0
      platform/android/java/src/com/android/godot/payments/HandlePurchaseTask.java
  22. 42 0
      platform/android/java/src/com/android/godot/payments/PaymentsCache.java
  23. 151 0
      platform/android/java/src/com/android/godot/payments/PaymentsManager.java
  24. 121 0
      platform/android/java/src/com/android/godot/payments/PurchaseTask.java
  25. 97 0
      platform/android/java/src/com/android/godot/payments/ValidateTask.java
  26. 39 0
      platform/android/java/src/com/android/godot/utils/Crypt.java
  27. 54 0
      platform/android/java/src/com/android/godot/utils/CustomSSLSocketFactory.java
  28. 206 0
      platform/android/java/src/com/android/godot/utils/HttpRequester.java
  29. 58 0
      platform/android/java/src/com/android/godot/utils/RequestParams.java
  30. 144 0
      platform/android/java/src/com/android/vending/billing/IInAppBillingService.aidl
  31. 7 2
      platform/android/java_glue.cpp
  32. 9 2
      platform/iphone/app_delegate.mm
  33. 0 1
      platform/iphone/detect.py
  34. 13 12
      platform/iphone/gl_view.mm
  35. 25 0
      platform/iphone/in_app_store.mm
  36. 0 1
      platform/isim/detect.py
  37. 15 10
      scene/2d/canvas_item.cpp
  38. 6 3
      scene/2d/canvas_item.h
  39. 2 0
      scene/gui/control.cpp
  40. 2 0
      scene/main/node.h
  41. 13 2
      servers/physics_2d/body_pair_2d_sw.cpp
  42. 2 0
      servers/physics_2d/body_pair_2d_sw.h
  43. 17 1
      tools/editor/code_editor.cpp
  44. 3 1
      tools/editor/code_editor.h
  45. 8 0
      tools/editor/editor_node.cpp
  46. 8 2
      tools/editor/editor_settings.cpp
  47. 8 1
      tools/editor/property_editor.cpp

+ 7 - 0
core/bind/core_bind.cpp

@@ -246,6 +246,12 @@ Error _OS::kill(int p_pid) {
 	return OS::get_singleton()->kill(p_pid);
 }
 
+int _OS::get_process_ID() const {
+
+	return OS::get_singleton()->get_process_ID();
+};
+
+
 bool _OS::has_environment(const String& p_var) const {
 
 	return OS::get_singleton()->has_environment(p_var);
@@ -561,6 +567,7 @@ void _OS::_bind_methods() {
 	ObjectTypeDB::bind_method(_MD("execute","path","arguments","blocking"),&_OS::execute);
 	ObjectTypeDB::bind_method(_MD("kill","pid"),&_OS::kill);
 	ObjectTypeDB::bind_method(_MD("shell_open","uri"),&_OS::shell_open);
+	ObjectTypeDB::bind_method(_MD("get_process_ID"),&_OS::get_process_ID);
 
 	ObjectTypeDB::bind_method(_MD("get_environment","environment"),&_OS::get_environment);
 	ObjectTypeDB::bind_method(_MD("has_environment","environment"),&_OS::has_environment);

+ 2 - 0
core/bind/core_bind.h

@@ -117,6 +117,8 @@ public:
 	Error kill(int p_pid);
 	Error shell_open(String p_uri);
 
+	int get_process_ID() const;
+
 	bool has_environment(const String& p_var) const;
 	String get_environment(const String& p_var) const;
 

+ 1 - 0
core/io/resource_format_binary.cpp

@@ -949,6 +949,7 @@ String ResourceInteractiveLoaderBinary::recognize(FileAccess *p_f) {
 
 	} else if (header[0]!='R' || header[1]!='S' || header[2]!='R' || header[3]!='C') {
 		//not normal
+		error=ERR_FILE_UNRECOGNIZED;
 		return "";
 	}
 

+ 2 - 1
core/message_queue.cpp

@@ -378,11 +378,12 @@ void MessageQueue::flush() {
 			}
 
 		}
-		message->~Message();
 
 		read_pos+=sizeof(Message);
 		if (message->type!=TYPE_NOTIFICATION)
 			read_pos+=sizeof(Variant)*message->args;
+		message->~Message();
+
 		_THREAD_SAFE_UNLOCK_
 
 	}

+ 52 - 2
core/object.cpp

@@ -33,6 +33,30 @@
 #include "message_queue.h"
 #include "core_string_names.h"
 #include "translation.h"
+
+#ifdef DEBUG_ENABLED
+
+struct _ObjectDebugLock {
+
+	Object *obj;
+
+	_ObjectDebugLock(Object *p_obj) {
+		obj=p_obj;
+		obj->_lock_index.ref();
+	}
+	~_ObjectDebugLock() {
+		obj->_lock_index.unref();
+	}
+};
+
+#define OBJ_DEBUG_LOCK _ObjectDebugLock _debug_lock(this);
+
+#else
+
+#define OBJ_DEBUG_LOCK
+
+#endif
+
 Array convert_property_list(const List<PropertyInfo> * p_list) {
 
 	Array va;
@@ -562,13 +586,22 @@ void Object::call_multilevel(const StringName& p_method,const Variant** p_args,i
 			ERR_FAIL();
 			return;
 		}
+
+
+		if (_lock_index.get()>1) {
+			ERR_EXPLAIN("Object is locked and can't be freed.");
+			ERR_FAIL();
+			return;
+		}
 #endif
+
 		//must be here, must be before everything,
 		memdelete(this);
 		return;
 	}
 
 	//Variant ret;
+	OBJ_DEBUG_LOCK
 
 	Variant::CallError error;
 
@@ -594,6 +627,7 @@ void Object::call_multilevel_reversed(const StringName& p_method,const Variant**
 	MethodBind *method=ObjectTypeDB::get_method(get_type_name(),p_method);
 
 	Variant::CallError error;
+	OBJ_DEBUG_LOCK
 
 	if (method) {
 
@@ -813,6 +847,15 @@ Variant Object::call(const StringName& p_method,const Variant** p_args,int p_arg
 			ERR_EXPLAIN("Can't 'free' a reference.");
 			ERR_FAIL_V(Variant());
 		}
+
+		if (_lock_index.get()>1) {
+			r_error.argument=0;
+			r_error.error=Variant::CallError::CALL_ERROR_INVALID_METHOD;
+			ERR_EXPLAIN("Object is locked and can't be freed.");
+			ERR_FAIL_V(Variant());
+
+		}
+
 #endif
 		//must be here, must be before everything,
 		memdelete(this);
@@ -821,7 +864,7 @@ Variant Object::call(const StringName& p_method,const Variant** p_args,int p_arg
 	}
 
 	Variant ret;
-
+	OBJ_DEBUG_LOCK
 	if (script_instance) {
 		ret = script_instance->call(p_method,p_args,p_argcount,r_error);
 		//force jumptable
@@ -902,7 +945,7 @@ void Object::set_script(const RefPtr& p_script) {
 	Ref<Script> s(script);
 
 	if (!s.is_null() && s->can_instance() ) {
-		
+		OBJ_DEBUG_LOCK
 		script_instance = s->instance_create(this);
 
 	}
@@ -1066,6 +1109,8 @@ void Object::emit_signal(const StringName& p_name,VARIANT_ARG_DECLARE) {
 
 	int ssize = slot_map.size();
 
+	OBJ_DEBUG_LOCK
+
 	for(int i=0;i<ssize;i++) {
 
 		const Connection &c = slot_map.getv(i).conn;
@@ -1536,6 +1581,11 @@ Object::Object() {
 	_edited=false;
 #endif
 
+#ifdef DEBUG_ENABLED
+	_lock_index.init(1);
+#endif
+
+
 
 }
 

+ 6 - 2
core/object.h

@@ -331,7 +331,9 @@ public:
 		Connection(const Variant& p_variant);
 	};
 private:
-
+#ifdef DEBUG_ENABLED
+friend class _ObjectDebugLock;
+#endif
 friend bool predelete_handler(Object*);
 friend void postinitialize_handler(Object*);
 
@@ -365,7 +367,9 @@ friend void postinitialize_handler(Object*);
 
 	HashMap< StringName, Signal, StringNameHasher> signal_map;
 	List<Connection> connections;
-
+#ifdef DEBUG_ENABLED
+	SafeRefCount _lock_index;
+#endif
 	bool _block_signals;
 	int _predelete_ok;
 	Set<Object*> change_receptors;

+ 5 - 0
core/os/os.cpp

@@ -124,6 +124,11 @@ String OS::get_executable_path() const {
 	return _execpath;
 }
 
+int OS::get_process_ID() const {
+
+	return -1;
+};
+
 uint64_t OS::get_frames_drawn() {
 
 	return frames_drawn;

+ 1 - 0
core/os/os.h

@@ -162,6 +162,7 @@ public:
 	virtual String get_executable_path() const;
 	virtual Error execute(const String& p_path, const List<String>& p_arguments,bool p_blocking,ProcessID *r_child_id=NULL,String* r_pipe=NULL,int *r_exitcode=NULL)=0;
 	virtual Error kill(const ProcessID& p_pid)=0;
+	virtual int get_process_ID() const;
 
 	virtual Error shell_open(String p_uri);
 	virtual Error set_cwd(const String& p_cwd);

Fișier diff suprimat deoarece este prea mare
+ 87 - 87
demos/2d/platformer/player.xml


Fișier diff suprimat deoarece este prea mare
+ 13 - 12
demos/2d/platformer/stage.xml


+ 6 - 0
drivers/unix/os_unix.cpp

@@ -333,6 +333,12 @@ Error OS_Unix::kill(const ProcessID& p_pid) {
 	return ret?ERR_INVALID_PARAMETER:OK;
 }
 
+int OS_Unix::get_process_ID() const {
+
+	return getpid();
+};
+
+
 bool OS_Unix::has_environment(const String& p_var) const {
 
 	return getenv(p_var.utf8().get_data())!=NULL;

+ 1 - 0
drivers/unix/os_unix.h

@@ -98,6 +98,7 @@ public:
 
 	virtual Error execute(const String& p_path, const List<String>& p_arguments,bool p_blocking,ProcessID *r_child_id=NULL,String* r_pipe=NULL,int *r_exitcode=NULL);
 	virtual Error kill(const ProcessID& p_pid);
+	virtual int get_process_ID() const;
 
 	virtual bool has_environment(const String& p_var) const;
 	virtual String get_environment(const String& p_var) const;

+ 0 - 1
modules/SCsub

@@ -10,7 +10,6 @@ env.modules_sources=[
 #env.add_source_files(env.modules_sources,"*.cpp")
 Export('env')
 
-
 for x in env.module_list:
 	if (x in env.disabled_modules):
 		continue

+ 1 - 1
modules/gdscript/gd_functions.cpp

@@ -1095,7 +1095,7 @@ MethodInfo GDFunctions::get_info(Function p_func) {
 			return mi;
 		} break;
 		case MATH_RAND: {
-			MethodInfo mi("rand");
+			MethodInfo mi("randi");
 			mi.return_val.type=Variant::INT;
 			return mi;
 		} break;

+ 13 - 0
modules/gdscript/gd_script.cpp

@@ -635,6 +635,19 @@ Variant GDFunction::call(GDInstance *p_instance,const Variant **p_args, int p_ar
 								err.argument-=1;
 							}
 						}
+					} if (methodstr=="free") {
+
+						if (err.error==Variant::CallError::CALL_ERROR_INVALID_METHOD) {
+
+							if (base->is_ref()) {
+								err_text="Attempted to free a reference.";
+								break;
+							} else if (base->get_type()==Variant::OBJECT) {
+
+								err_text="Attempted to free a locked object (calling or emitting).";
+								break;
+							}
+						}
 					}
 					err_text=_get_call_error(err,"function '"+methodstr+"' in base '"+basestr+"'",(const Variant**)argptrs);
 					break;

+ 1 - 1
platform/android/AndroidManifest.xml.template

@@ -11,7 +11,7 @@
                   android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
                   android:launchMode="singleTask"
                   android:screenOrientation="landscape"
-                  android:configChanges="orientation|keyboardHidden">
+                  android:configChanges="orientation|keyboardHidden|screenSize|smallestScreenSize">
                                                                                                   
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />

+ 0 - 5
platform/android/java/ant.properties

@@ -15,8 +15,3 @@
 #  'key.alias' for the name of the key to use.
 # The password will be asked during the build when you use the 'release' target.
 
-key.store=/home/luis/Downloads/carnavalguachin.keystore
-key.alias=momoselacome
-
-key.store.password=12345678
-key.alias.password=12345678

+ 2 - 1
platform/android/java/src/com/android/godot/Godot.java

@@ -135,7 +135,7 @@ public class Godot extends Activity implements SensorEventListener
 	};
 	public ResultCallback result_callback;
 
-	private PaymentsManager mPaymentsManager;
+	private PaymentsManager mPaymentsManager = null;
 
 	@Override protected void onActivityResult (int requestCode, int resultCode, Intent data) {
 		if(requestCode == PaymentsManager.REQUEST_CODE_FOR_PURCHASE){
@@ -168,6 +168,7 @@ public class Godot extends Activity implements SensorEventListener
 	
 	@Override protected void onCreate(Bundle icicle) {
 
+		System.out.printf("** GODOT ACTIVITY CREATED HERE ***\n");
 
 		super.onCreate(icicle);
 		_self = this;

+ 83 - 0
platform/android/java/src/com/android/godot/GodotPaymentV3.java

@@ -0,0 +1,83 @@
+package com.android.godot;
+
+
+import android.app.Activity;
+
+
+public class GodotPaymentV3 extends Godot.SingletonBase {
+
+	private Godot activity;
+
+	private Integer purchaseCallbackId = 0;
+
+	private String accessToken;
+	
+	private String purchaseValidationUrlPrefix;
+
+	public void purchase( String _sku) {
+		final String sku = _sku;
+		activity.getPaymentsManager().setBaseSingleton(this);
+		activity.runOnUiThread(new Runnable() {
+			@Override
+			public void run() {
+				activity.getPaymentsManager().requestPurchase(sku);				
+			}
+		});
+	};
+
+
+    static public Godot.SingletonBase initialize(Activity p_activity) {
+
+        return new GodotPaymentV3(p_activity);
+    }
+
+	
+	public GodotPaymentV3(Activity p_activity) {
+
+		registerClass("GodotPayments", new String[] {"purchase", "setPurchaseCallbackId", "setPurchaseValidationUrlPrefix"});
+		activity=(Godot) p_activity;
+	}
+
+
+	
+	public void callbackSuccess(){
+        GodotLib.callobject(purchaseCallbackId, "purchase_success", new Object[]{});
+	}
+	
+	public void callbackFail(){
+        GodotLib.callobject(purchaseCallbackId, "purchase_fail", new Object[]{});
+	}
+	
+	public void callbackCancel(){
+		GodotLib.callobject(purchaseCallbackId, "purchase_cancel", new Object[]{});
+	}
+	
+	public int getPurchaseCallbackId() {
+		return purchaseCallbackId;
+	}
+
+	public void setPurchaseCallbackId(int purchaseCallbackId) {
+		this.purchaseCallbackId = purchaseCallbackId;
+	}
+
+
+
+	public String getPurchaseValidationUrlPrefix(){
+		return this.purchaseValidationUrlPrefix ;
+	}
+
+	public void setPurchaseValidationUrlPrefix(String url){
+		this.purchaseValidationUrlPrefix = url;
+	}
+
+
+	public String getAccessToken() {
+		return accessToken;
+	}
+
+
+	public void setAccessToken(String accessToken) {
+		this.accessToken = accessToken;
+	}
+	
+}

+ 71 - 0
platform/android/java/src/com/android/godot/payments/ConsumeTask.java

@@ -0,0 +1,71 @@
+package com.android.godot.payments;
+
+import com.android.vending.billing.IInAppBillingService;
+
+import android.content.Context;
+import android.os.AsyncTask;
+import android.os.RemoteException;
+import android.util.Log;
+
+abstract public class ConsumeTask {
+
+	private Context context;
+	
+	private IInAppBillingService mService;
+	public ConsumeTask(IInAppBillingService mService, Context context ){
+		this.context = context;
+		this.mService = mService;
+	}
+	
+
+	public void consume(final String sku){
+//		Log.d("XXX", "Consuming product " + sku);
+		PaymentsCache pc = new PaymentsCache(context);
+		Boolean isBlocked = pc.getConsumableFlag("block", sku);
+		String _token = pc.getConsumableValue("token", sku);
+//		Log.d("XXX", "token " + _token);		
+		if(!isBlocked && _token == null){
+//			_token = "inapp:"+context.getPackageName()+":android.test.purchased";
+//			Log.d("XXX", "Consuming product " + sku + " with token " + _token);
+		}else if(!isBlocked){
+//			Log.d("XXX", "It is not blocked ¿?");
+			return;
+		}else if(_token == null){
+//			Log.d("XXX", "No token available");
+			this.error("No token for sku:" + sku);
+			return;
+		}
+		final String token = _token;
+		new AsyncTask<String, String, String>(){
+
+			@Override
+			protected String doInBackground(String... params) {
+				try {
+//					Log.d("XXX", "Requesting to release item.");
+					int response = mService.consumePurchase(3, context.getPackageName(), token);
+//					Log.d("XXX", "release response code: " + response);
+					if(response == 0 || response == 8){
+						return null;
+					}
+				} catch (RemoteException e) {
+					return e.getMessage();
+					
+				}
+				return "Some error";
+			}
+			
+			protected void onPostExecute(String param){
+				if(param == null){
+					success();
+				}else{
+					error(param);
+				}
+			}
+			
+		}.execute();
+	}
+	
+	abstract protected void success();
+	abstract protected void error(String message);
+	
+}

+ 79 - 0
platform/android/java/src/com/android/godot/payments/HandlePurchaseTask.java

@@ -0,0 +1,79 @@
+package com.android.godot.payments;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import com.android.godot.GodotLib;
+import com.android.godot.utils.Crypt;
+import com.android.vending.billing.IInAppBillingService;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender.SendIntentException;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+abstract public class HandlePurchaseTask {
+
+	private Activity context;
+	
+	public HandlePurchaseTask(Activity context ){
+		this.context = context;
+	}
+	
+	
+	public void handlePurchaseRequest(int resultCode, Intent data){
+//		Log.d("XXX", "Handling purchase response");
+//		int responseCode = data.getIntExtra("RESPONSE_CODE", 0);
+		PaymentsCache pc = new PaymentsCache(context);
+		
+		String purchaseData = data.getStringExtra("INAPP_PURCHASE_DATA");
+//		String dataSignature = data.getStringExtra("INAPP_DATA_SIGNATURE");
+		
+		if (resultCode == Activity.RESULT_OK) {
+			
+			try {
+				Log.d("SARLANGA", purchaseData);
+				
+				
+				JSONObject jo = new JSONObject(purchaseData);
+//				String sku = jo.getString("productId");
+//				alert("You have bought the " + sku + ". Excellent choice, aventurer!");
+//				String orderId = jo.getString("orderId");
+//				String packageName = jo.getString("packageName");
+				String productId = jo.getString("productId");
+//				Long purchaseTime = jo.getLong("purchaseTime");
+//				Integer state = jo.getInt("purchaseState");
+				String developerPayload = jo.getString("developerPayload");
+				String purchaseToken = jo.getString("purchaseToken");
+				
+				if(! pc.getConsumableValue("validation_hash", productId).equals(developerPayload) ) {
+					error("Untrusted callback");
+					return;
+				}
+				
+				pc.setConsumableValue("ticket", productId, purchaseData);
+				pc.setConsumableFlag("block", productId, true);
+				pc.setConsumableValue("token", productId, purchaseToken);
+				
+				success(purchaseToken, productId);
+				return;
+			}	catch (JSONException e) {
+				error(e.getMessage());
+			}
+		}else if( resultCode == Activity.RESULT_CANCELED){
+			canceled();
+		}
+	}
+
+	abstract protected void success(String purchaseToken, String sku);
+	abstract protected void error(String message);
+	abstract protected void canceled();
+
+	
+}

+ 42 - 0
platform/android/java/src/com/android/godot/payments/PaymentsCache.java

@@ -0,0 +1,42 @@
+package com.android.godot.payments;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+public class PaymentsCache {
+	
+	public Context context;
+
+	public PaymentsCache(Context context){
+		this.context = context;
+	}
+	
+	
+	public void setConsumableFlag(String set, String sku, Boolean flag){
+		SharedPreferences sharedPref = context.getSharedPreferences("consumables_" + set, Context.MODE_PRIVATE); 
+	    SharedPreferences.Editor editor = sharedPref.edit();
+	    editor.putBoolean(sku, flag);
+	    editor.commit();
+}
+
+	public boolean getConsumableFlag(String set, String sku){
+	    SharedPreferences sharedPref = context.getSharedPreferences(
+	    		"consumables_" + set, Context.MODE_PRIVATE);
+	    return sharedPref.getBoolean(sku, false);
+	}
+
+
+	public void setConsumableValue(String set, String sku, String value){
+		SharedPreferences sharedPref = context.getSharedPreferences("consumables_" + set, Context.MODE_PRIVATE); 
+	    SharedPreferences.Editor editor = sharedPref.edit();
+	    editor.putString(sku, value);
+	    editor.commit();
+	}
+
+	public String getConsumableValue(String set, String sku){
+	    SharedPreferences sharedPref = context.getSharedPreferences(
+	    		"consumables_" + set, Context.MODE_PRIVATE);
+	    return sharedPref.getString(sku, null);
+	}
+
+}

+ 151 - 0
platform/android/java/src/com/android/godot/payments/PaymentsManager.java

@@ -0,0 +1,151 @@
+package com.android.godot.payments;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+
+import com.android.godot.Godot;
+import com.android.godot.GodotPaymentV3;
+import com.android.vending.billing.IInAppBillingService;
+
+public class PaymentsManager {
+
+	public static final int BILLING_RESPONSE_RESULT_OK = 0;
+
+	
+	public static final int REQUEST_CODE_FOR_PURCHASE = 0x1001;
+	
+	
+	private Activity activity;
+	IInAppBillingService mService;
+
+	
+	public void setActivity(Activity activity){
+		this.activity = activity;
+	}
+
+	public static PaymentsManager createManager(Activity activity){
+		PaymentsManager manager = new PaymentsManager(activity);
+		return manager;
+	}
+	
+	private PaymentsManager(Activity activity){
+		this.activity = activity;
+	}
+	
+	public PaymentsManager initService(){
+		activity.bindService(
+				new Intent("com.android.vending.billing.InAppBillingService.BIND"), 
+				mServiceConn, 
+				Context.BIND_AUTO_CREATE);
+		return this;
+	}
+
+	public void destroy(){
+		if (mService != null) {
+	        activity.unbindService(mServiceConn);
+	    }  
+	}
+	
+	ServiceConnection mServiceConn = new ServiceConnection() {
+	    @Override
+	    public void onServiceDisconnected(ComponentName name) {
+	    	mService = null;
+	    }
+
+	    @Override
+	    public void onServiceConnected(ComponentName name, 
+	    		IBinder service) {
+		mService = IInAppBillingService.Stub.asInterface(service);
+	    }
+	};
+	
+	public void requestPurchase(String sku){
+		new PurchaseTask(mService, Godot.getInstance()) {
+			
+			@Override
+			protected void error(String message) {
+				godotPaymentV3.callbackFail();
+				
+			}
+			
+			@Override
+			protected void canceled() {
+				godotPaymentV3.callbackCancel();
+			}
+		}.purchase(sku);
+
+	}
+
+	public void processPurchaseResponse(int resultCode, Intent data) {
+		new HandlePurchaseTask(activity){
+
+			@Override
+			protected void success(String purchaseToken, String sku) {
+				validatePurchase(purchaseToken, sku);
+			}
+
+			@Override
+			protected void error(String message) {
+				godotPaymentV3.callbackFail();
+				
+			}
+
+			@Override
+			protected void canceled() {
+				godotPaymentV3.callbackCancel();
+				
+			}}.handlePurchaseRequest(resultCode, data);
+	}
+	
+	public void validatePurchase(String purchaseToken, final String sku){
+		
+		new ValidateTask(activity, godotPaymentV3){
+
+			@Override
+			protected void success() {
+				
+				new ConsumeTask(mService, activity) {
+					
+					@Override
+					protected void success() {
+						godotPaymentV3.callbackSuccess();
+						
+					}
+					
+					@Override
+					protected void error(String message) {
+						godotPaymentV3.callbackFail();
+						
+					}
+				}.consume(sku);
+				
+			}
+
+			@Override
+			protected void error(String message) {
+				godotPaymentV3.callbackFail();
+				
+			}
+
+			@Override
+			protected void canceled() {
+				godotPaymentV3.callbackCancel();
+				
+			}
+		}.validatePurchase(sku);
+	}
+	
+	private GodotPaymentV3 godotPaymentV3;
+	
+	public void setBaseSingleton(GodotPaymentV3 godotPaymentV3) {
+		this.godotPaymentV3 = godotPaymentV3;
+		
+	}
+
+
+}
+

+ 121 - 0
platform/android/java/src/com/android/godot/payments/PurchaseTask.java

@@ -0,0 +1,121 @@
+package com.android.godot.payments;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import com.android.godot.GodotLib;
+import com.android.godot.utils.Crypt;
+import com.android.vending.billing.IInAppBillingService;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender.SendIntentException;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+abstract public class PurchaseTask {
+
+	private Activity context;
+	
+	private IInAppBillingService mService;
+	public PurchaseTask(IInAppBillingService mService, Activity context ){
+		this.context = context;
+		this.mService = mService;
+	}
+	
+
+	private boolean isLooping = false;
+	
+	public void purchase(final String sku){
+//		Log.d("XXX", "Starting purchase");
+		PaymentsCache pc = new PaymentsCache(context);
+		Boolean isBlocked = pc.getConsumableFlag("block", sku);
+//		if(isBlocked){
+//			Log.d("XXX", "Is awaiting payment confirmation");
+//			error("Awaiting payment confirmation");
+//			return;
+//		}
+		final String hash = Crypt.createRandomHash() + Crypt.createRandomHash();
+
+		Bundle buyIntentBundle;
+		try {
+			buyIntentBundle = mService.getBuyIntent(3, context.getApplicationContext().getPackageName(), sku, "inapp", hash  );
+		} catch (RemoteException e) {
+//			Log.d("XXX", "Error: " + e.getMessage());
+			error(e.getMessage());
+			return;
+		}
+		Object rc = buyIntentBundle.get("RESPONSE_CODE");
+		int responseCode = 0;
+		if(rc == null){
+			responseCode = PaymentsManager.BILLING_RESPONSE_RESULT_OK;
+		}else if( rc instanceof Integer){
+			responseCode = ((Integer)rc).intValue();
+		}else if( rc instanceof Long){
+			responseCode = (int)((Long)rc).longValue();
+		}
+//		Log.d("XXX", "Buy intent response code: " + responseCode);
+		if(responseCode == 1 || responseCode == 3 || responseCode == 4){
+			canceled();
+			return ;
+		}
+		if(responseCode == 7){
+			new ConsumeTask(mService, context) {
+				
+				@Override
+				protected void success() {
+//					Log.d("XXX", "Product was erroniously purchased!");
+					if(isLooping){
+//						Log.d("XXX", "It is looping");
+						error("Error while purchasing product");
+						return;
+					}
+					isLooping=true;
+					PurchaseTask.this.purchase(sku);
+					
+				}
+				
+				@Override
+				protected void error(String message) {
+					PurchaseTask.this.error(message);
+					
+				}
+			}.consume(sku);
+			return;
+		}
+		
+		
+		PendingIntent pendingIntent = buyIntentBundle.getParcelable("BUY_INTENT");
+		pc.setConsumableValue("validation_hash", sku, hash);
+		try {
+			if(context == null){
+//				Log.d("XXX", "No context!");
+			}
+			if(pendingIntent == null){
+//				Log.d("XXX", "No pending intent");
+			}
+//			Log.d("XXX", "Starting activity for purchase!");
+			context.startIntentSenderForResult(
+					pendingIntent.getIntentSender(),
+					PaymentsManager.REQUEST_CODE_FOR_PURCHASE, 
+					new Intent(), 
+					Integer.valueOf(0), Integer.valueOf(0),
+					   Integer.valueOf(0));
+		} catch (SendIntentException e) {
+			error(e.getMessage());
+		}
+		
+		
+		
+	}
+
+	abstract protected void error(String message);
+	abstract protected void canceled();
+
+	
+}

+ 97 - 0
platform/android/java/src/com/android/godot/payments/ValidateTask.java

@@ -0,0 +1,97 @@
+package com.android.godot.payments;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import com.android.godot.Godot;
+import com.android.godot.GodotLib;
+import com.android.godot.GodotPaymentV3;
+import com.android.godot.utils.Crypt;
+import com.android.godot.utils.HttpRequester;
+import com.android.godot.utils.RequestParams;
+import com.android.vending.billing.IInAppBillingService;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender.SendIntentException;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+abstract public class ValidateTask {
+
+	private Activity context;
+	private GodotPaymentV3 godotPaymentsV3;
+	public ValidateTask(Activity context, GodotPaymentV3 godotPaymentsV3){
+		this.context = context;
+		this.godotPaymentsV3 = godotPaymentsV3;
+	}
+	
+	public void validatePurchase(final String sku){
+		new AsyncTask<String, String, String>(){
+
+			
+			private ProgressDialog dialog;
+
+			@Override
+			protected void onPreExecute(){
+				dialog = ProgressDialog.show(context, null, "Please wait...");
+			}
+			
+			@Override
+			protected String doInBackground(String... params) {
+				PaymentsCache pc = new PaymentsCache(context);
+				String url = godotPaymentsV3.getPurchaseValidationUrlPrefix();
+				RequestParams param = new RequestParams();
+				param.setUrl(url);
+				param.put("ticket", pc.getConsumableValue("ticket", sku));
+				param.put("purchaseToken", pc.getConsumableValue("token", sku));
+				param.put("sku", sku);
+//				Log.d("XXX", "Haciendo request a " + url);
+//				Log.d("XXX", "ticket: " + pc.getConsumableValue("ticket", sku));
+//				Log.d("XXX", "purchaseToken: " + pc.getConsumableValue("token", sku));
+//				Log.d("XXX", "sku: " + sku);
+				param.put("package", context.getApplicationContext().getPackageName());
+				HttpRequester requester = new HttpRequester();
+				String jsonResponse = requester.post(param);
+//				Log.d("XXX", "Validation response:\n"+jsonResponse);
+				return jsonResponse;
+			}
+			
+			@Override
+			protected void onPostExecute(String response){
+				if(dialog != null){
+					dialog.dismiss();
+				}
+				JSONObject j;
+				try {
+					j = new JSONObject(response);
+					if(j.getString("status").equals("OK")){
+						success();
+						return;
+					}else if(j.getString("status") != null){
+						error(j.getString("message"));
+					}else{
+						error("Connection error");
+					}
+				} catch (JSONException e) {
+					error(e.getMessage());
+				}catch (Exception e){
+					error(e.getMessage());
+				}
+
+				
+			}
+			
+		}.execute();
+	}
+	abstract protected void success();
+	abstract protected void error(String message);
+	abstract protected void canceled();
+
+	
+}

+ 39 - 0
platform/android/java/src/com/android/godot/utils/Crypt.java

@@ -0,0 +1,39 @@
+package com.android.godot.utils;
+
+import java.security.MessageDigest;
+import java.util.Random;
+
+public class Crypt {
+
+	public static String md5(String input){
+        try {
+            // Create MD5 Hash
+            MessageDigest digest = java.security.MessageDigest.getInstance("MD5");
+            digest.update(input.getBytes());
+            byte messageDigest[] = digest.digest();
+            
+            // Create Hex String
+            StringBuffer hexString = new StringBuffer();
+            for (int i=0; i<messageDigest.length; i++)
+                hexString.append(Integer.toHexString(0xFF & messageDigest[i]));
+            return hexString.toString();
+            
+        } catch (Exception e) {
+            e.printStackTrace();
+        }   
+        return "";
+	}
+	
+	public static String createRandomHash(){
+		return md5(Long.toString(createRandomLong()));
+	}
+	
+	public static long createAbsRandomLong(){
+		return Math.abs(createRandomLong());
+	}
+	
+	public static long createRandomLong(){
+		Random r = new Random();
+		return r.nextLong();
+	}
+}

+ 54 - 0
platform/android/java/src/com/android/godot/utils/CustomSSLSocketFactory.java

@@ -0,0 +1,54 @@
+package com.android.godot.utils;
+import java.io.IOException;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+import org.apache.http.conn.ssl.SSLSocketFactory;
+
+
+/**
+ * 
+ * @author Luis Linietsky <[email protected]>
+ */
+public class CustomSSLSocketFactory extends SSLSocketFactory {
+    SSLContext sslContext = SSLContext.getInstance("TLS");
+
+    public CustomSSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
+        super(truststore);
+
+        TrustManager tm = new X509TrustManager() {
+            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+            }
+
+            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+            }
+
+            public X509Certificate[] getAcceptedIssuers() {
+                return null;
+            }
+        };
+
+        sslContext.init(null, new TrustManager[] { tm }, null);
+    }
+
+    @Override
+    public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
+        return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
+    }
+
+    @Override
+    public Socket createSocket() throws IOException {
+        return sslContext.getSocketFactory().createSocket();
+    }
+}

+ 206 - 0
platform/android/java/src/com/android/godot/utils/HttpRequester.java

@@ -0,0 +1,206 @@
+package com.android.godot.utils;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.security.KeyStore;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpVersion;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.conn.scheme.PlainSocketFactory;
+import org.apache.http.conn.scheme.Scheme;
+import org.apache.http.conn.scheme.SchemeRegistry;
+import org.apache.http.conn.ssl.SSLSocketFactory;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.params.BasicHttpParams;
+import org.apache.http.params.HttpConnectionParams;
+import org.apache.http.params.HttpParams;
+import org.apache.http.params.HttpProtocolParams;
+import org.apache.http.protocol.HTTP;
+import org.apache.http.util.EntityUtils;
+
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.util.Log;
+
+/**
+ * 
+ * @author Luis Linietsky <[email protected]>
+ */
+public class HttpRequester {
+	
+	private Context context;
+	private static final int TTL = 600000; // 10 minutos 
+	private long cttl=0;
+	
+	public HttpRequester(){
+//		Log.d("XXX", "Creando http request sin contexto");
+	}
+	
+	public HttpRequester(Context context){
+		this.context=context;
+//		Log.d("XXX", "Creando http request con contexto");
+	}
+	
+	public String post(RequestParams params){
+	    HttpPost httppost = new HttpPost(params.getUrl());
+        try {
+			httppost.setEntity(new UrlEncodedFormEntity(params.toPairsList()));
+			return request(httppost);
+		} catch (UnsupportedEncodingException e) {
+			return null;
+		}
+	}
+	
+	public String get(RequestParams params){
+		String response = getResponseFromCache(params.getUrl());
+		if(response == null){
+//			Log.d("XXX", "Cache miss!");
+		    HttpGet httpget = new HttpGet(params.getUrl());
+		    long timeInit = new Date().getTime();
+		    response = request(httpget);
+		    long delay = new Date().getTime() - timeInit;
+		    Log.d("com.app11tt.android.utils.HttpRequest::get(url)", "Url: " + params.getUrl() + " downloaded in " + String.format("%.03f", delay/1000.0f) + " seconds");
+		    if(response == null || response.length() == 0){
+		    	response = "";
+		    }else{
+		    	saveResponseIntoCache(params.getUrl(), response);
+		    } 
+		}
+		Log.d("XXX", "Req: " + params.getUrl());
+		Log.d("XXX", "Resp: " + response);
+	    return response;
+	}
+	
+	private String request(HttpUriRequest request){
+//		Log.d("XXX", "Haciendo request a: " + request.getURI() );
+		Log.d("PPP", "Haciendo request a: " + request.getURI() );
+		long init = new Date().getTime();
+		HttpClient httpclient = getNewHttpClient();
+		HttpParams httpParameters = httpclient.getParams();
+		HttpConnectionParams.setConnectionTimeout(httpParameters, 0);
+		HttpConnectionParams.setSoTimeout(httpParameters, 0);
+		HttpConnectionParams.setTcpNoDelay(httpParameters, true);
+	    try {
+	        HttpResponse response = httpclient.execute(request);
+	        Log.d("PPP", "Fin de request (" + (new Date().getTime() - init) + ") a: " + request.getURI() );
+//	        Log.d("XXX1", "Status:" + response.getStatusLine().toString());
+	        if(response.getStatusLine().getStatusCode() == 200){
+	        	String strResponse = EntityUtils.toString(response.getEntity());
+//	        	Log.d("XXX2", strResponse);
+	        	return strResponse;
+	        }else{
+	        	Log.d("XXX3", "Response status code:" + response.getStatusLine().getStatusCode() + "\n" + EntityUtils.toString(response.getEntity()));
+	        	return null;
+	        }
+	        
+	    } catch (ClientProtocolException e) {
+	    	Log.d("XXX3", e.getMessage());
+	    } catch (IOException e) {
+	    	Log.d("XXX4", e.getMessage());
+	    }
+		return null;
+	}
+	
+	private HttpClient getNewHttpClient() {
+	    try {
+	        KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
+	        trustStore.load(null, null);
+
+	        SSLSocketFactory sf = new CustomSSLSocketFactory(trustStore);
+	        sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
+
+	        HttpParams params = new BasicHttpParams();
+	        HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
+	        HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);
+
+	        SchemeRegistry registry = new SchemeRegistry();
+	        registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
+	        registry.register(new Scheme("https", sf, 443));
+
+	        ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry);
+
+	        return new DefaultHttpClient(ccm, params);
+	    } catch (Exception e) {
+	        return new DefaultHttpClient();
+	    }
+	}
+	
+	private static String convertStreamToString(InputStream is) {
+	    BufferedReader reader = new BufferedReader(new InputStreamReader(is));
+	    StringBuilder sb = new StringBuilder();
+	    String line = null;
+	    try {
+	        while ((line = reader.readLine()) != null) {
+	            sb.append((line + "\n"));
+	        }
+	    } catch (IOException e) {
+	        e.printStackTrace();
+	    } finally {
+	        try {
+	            is.close(); 
+	        } catch (IOException e) {
+	            e.printStackTrace(); 
+	        }
+	    }
+	    return sb.toString();
+	}
+
+	public void saveResponseIntoCache(String request, String response){
+		if(context == null){
+//			Log.d("XXX", "No context, cache failed!");
+			return;
+		}
+        SharedPreferences sharedPref = context.getSharedPreferences("http_get_cache", Context.MODE_PRIVATE); 
+        SharedPreferences.Editor editor = sharedPref.edit();
+        editor.putString("request_" + Crypt.md5(request), response);
+        editor.putLong("request_" + Crypt.md5(request) + "_ttl", new Date().getTime() + getTtl());
+        editor.commit();
+	}
+	
+	
+	public String getResponseFromCache(String request){
+		if(context == null){
+			Log.d("XXX", "No context, cache miss");
+			return null;
+		}
+        SharedPreferences sharedPref = context.getSharedPreferences( "http_get_cache", Context.MODE_PRIVATE);
+        long ttl = getResponseTtl(request);
+        if(ttl == 0l || (new Date().getTime() - ttl) > 0l){
+        	Log.d("XXX", "Cache invalid ttl:" + ttl + " vs now:" + new Date().getTime());
+        	return null;
+        }
+        return sharedPref.getString("request_" + Crypt.md5(request), null);
+	}
+
+	public long getResponseTtl(String request){
+        SharedPreferences sharedPref = context.getSharedPreferences(
+        		"http_get_cache", Context.MODE_PRIVATE); 
+        return sharedPref.getLong("request_" + Crypt.md5(request) + "_ttl", 0l);
+	}
+
+	public long getTtl() {
+		return cttl > 0 ? cttl : TTL;
+	}
+
+	public void setTtl(long ttl) {
+		this.cttl = (ttl*1000) + new Date().getTime();
+	}
+	
+}

+ 58 - 0
platform/android/java/src/com/android/godot/utils/RequestParams.java

@@ -0,0 +1,58 @@
+package com.android.godot.utils;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+
+import org.apache.http.NameValuePair;
+import org.apache.http.message.BasicNameValuePair;
+
+/**
+ * 
+ * @author Luis Linietsky <[email protected]>
+ */
+public class RequestParams {
+
+	private HashMap<String,String> params;
+	private String url;
+	
+	public RequestParams(){
+		params = new HashMap<String,String>();
+	}
+	
+	public void put(String key, String value){
+		params.put(key, value);
+	}
+	
+	public String get(String key){
+		return params.get(key);
+	}
+	
+	public void remove(Object key){
+		params.remove(key);
+	}
+	
+	public boolean has(String key){
+		return params.containsKey(key);
+	}
+	
+	public List<NameValuePair> toPairsList(){
+		List<NameValuePair>  fields = new ArrayList<NameValuePair>();
+
+		for(String key : params.keySet()){
+			fields.add(new BasicNameValuePair(key, this.get(key)));
+		}
+		return fields;
+	}
+
+	public String getUrl() {
+		return url;
+	}
+
+	public void setUrl(String url) {
+		this.url = url;
+	}
+
+	
+}

+ 144 - 0
platform/android/java/src/com/android/vending/billing/IInAppBillingService.aidl

@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.vending.billing;
+
+import android.os.Bundle;
+
+/**
+ * InAppBillingService is the service that provides in-app billing version 3 and beyond.
+ * This service provides the following features:
+ * 1. Provides a new API to get details of in-app items published for the app including
+ *    price, type, title and description.
+ * 2. The purchase flow is synchronous and purchase information is available immediately
+ *    after it completes.
+ * 3. Purchase information of in-app purchases is maintained within the Google Play system
+ *    till the purchase is consumed.
+ * 4. An API to consume a purchase of an inapp item. All purchases of one-time
+ *    in-app items are consumable and thereafter can be purchased again.
+ * 5. An API to get current purchases of the user immediately. This will not contain any
+ *    consumed purchases.
+ *
+ * All calls will give a response code with the following possible values
+ * RESULT_OK = 0 - success
+ * RESULT_USER_CANCELED = 1 - user pressed back or canceled a dialog
+ * RESULT_BILLING_UNAVAILABLE = 3 - this billing API version is not supported for the type requested
+ * RESULT_ITEM_UNAVAILABLE = 4 - requested SKU is not available for purchase
+ * RESULT_DEVELOPER_ERROR = 5 - invalid arguments provided to the API
+ * RESULT_ERROR = 6 - Fatal error during the API action
+ * RESULT_ITEM_ALREADY_OWNED = 7 - Failure to purchase since item is already owned
+ * RESULT_ITEM_NOT_OWNED = 8 - Failure to consume since item is not owned
+ */
+interface IInAppBillingService {
+    /**
+     * Checks support for the requested billing API version, package and in-app type.
+     * Minimum API version supported by this interface is 3.
+     * @param apiVersion the billing version which the app is using
+     * @param packageName the package name of the calling app
+     * @param type type of the in-app item being purchased "inapp" for one-time purchases
+     *        and "subs" for subscription.
+     * @return RESULT_OK(0) on success, corresponding result code on failures
+     */
+    int isBillingSupported(int apiVersion, String packageName, String type);
+
+    /**
+     * Provides details of a list of SKUs
+     * Given a list of SKUs of a valid type in the skusBundle, this returns a bundle
+     * with a list JSON strings containing the productId, price, title and description.
+     * This API can be called with a maximum of 20 SKUs.
+     * @param apiVersion billing API version that the Third-party is using
+     * @param packageName the package name of the calling app
+     * @param skusBundle bundle containing a StringArrayList of SKUs with key "ITEM_ID_LIST"
+     * @return Bundle containing the following key-value pairs
+     *         "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on
+     *              failure as listed above.
+     *         "DETAILS_LIST" with a StringArrayList containing purchase information
+     *              in JSON format similar to:
+     *              '{ "productId" : "exampleSku", "type" : "inapp", "price" : "$5.00",
+     *                 "title : "Example Title", "description" : "This is an example description" }'
+     */
+    Bundle getSkuDetails(int apiVersion, String packageName, String type, in Bundle skusBundle);
+
+    /**
+     * Returns a pending intent to launch the purchase flow for an in-app item by providing a SKU,
+     * the type, a unique purchase token and an optional developer payload.
+     * @param apiVersion billing API version that the app is using
+     * @param packageName package name of the calling app
+     * @param sku the SKU of the in-app item as published in the developer console
+     * @param type the type of the in-app item ("inapp" for one-time purchases
+     *        and "subs" for subscription).
+     * @param developerPayload optional argument to be sent back with the purchase information
+     * @return Bundle containing the following key-value pairs
+     *         "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on
+     *              failure as listed above.
+     *         "BUY_INTENT" - PendingIntent to start the purchase flow
+     *
+     * The Pending intent should be launched with startIntentSenderForResult. When purchase flow
+     * has completed, the onActivityResult() will give a resultCode of OK or CANCELED.
+     * If the purchase is successful, the result data will contain the following key-value pairs
+     *         "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on
+     *              failure as listed above.
+     *         "INAPP_PURCHASE_DATA" - String in JSON format similar to
+     *              '{"orderId":"12999763169054705758.1371079406387615",
+     *                "packageName":"com.example.app",
+     *                "productId":"exampleSku",
+     *                "purchaseTime":1345678900000,
+     *                "purchaseToken" : "122333444455555",
+     *                "developerPayload":"example developer payload" }'
+     *         "INAPP_DATA_SIGNATURE" - String containing the signature of the purchase data that
+     *                                  was signed with the private key of the developer
+     *                                  TODO: change this to app-specific keys.
+     */
+    Bundle getBuyIntent(int apiVersion, String packageName, String sku, String type,
+        String developerPayload);
+
+    /**
+     * Returns the current SKUs owned by the user of the type and package name specified along with
+     * purchase information and a signature of the data to be validated.
+     * This will return all SKUs that have been purchased in V3 and managed items purchased using
+     * V1 and V2 that have not been consumed.
+     * @param apiVersion billing API version that the app is using
+     * @param packageName package name of the calling app
+     * @param type the type of the in-app items being requested
+     *        ("inapp" for one-time purchases and "subs" for subscription).
+     * @param continuationToken to be set as null for the first call, if the number of owned
+     *        skus are too many, a continuationToken is returned in the response bundle.
+     *        This method can be called again with the continuation token to get the next set of
+     *        owned skus.
+     * @return Bundle containing the following key-value pairs
+     *         "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on
+     *              failure as listed above.
+     *         "INAPP_PURCHASE_ITEM_LIST" - StringArrayList containing the list of SKUs
+     *         "INAPP_PURCHASE_DATA_LIST" - StringArrayList containing the purchase information
+     *         "INAPP_DATA_SIGNATURE_LIST"- StringArrayList containing the signatures
+     *                                      of the purchase information
+     *         "INAPP_CONTINUATION_TOKEN" - String containing a continuation token for the
+     *                                      next set of in-app purchases. Only set if the
+     *                                      user has more owned skus than the current list.
+     */
+    Bundle getPurchases(int apiVersion, String packageName, String type, String continuationToken);
+
+    /**
+     * Consume the last purchase of the given SKU. This will result in this item being removed
+     * from all subsequent responses to getPurchases() and allow re-purchase of this item.
+     * @param apiVersion billing API version that the app is using
+     * @param packageName package name of the calling app
+     * @param purchaseToken token in the purchase information JSON that identifies the purchase
+     *        to be consumed
+     * @return 0 if consumption succeeded. Appropriate error values for failures.
+     */
+    int consumePurchase(int apiVersion, String packageName, String purchaseToken);
+}

+ 7 - 2
platform/android/java_glue.cpp

@@ -440,7 +440,8 @@ public:
 			case Variant::STRING: {
 
 				jobject o = env->CallObjectMethodA(instance,E->get().method,v);
-				String singname = env->GetStringUTFChars((jstring)o, NULL );
+				String str = env->GetStringUTFChars((jstring)o, NULL );
+				ret=str;
 			} break;
 			case Variant::STRING_ARRAY: {
 
@@ -665,11 +666,12 @@ JNIEXPORT void JNICALL Java_com_android_godot_GodotLib_initialize(JNIEnv * env,
 
 
 	initialized=true;
-	_godot_instance=activity;
 
 	JavaVM *jvm;
 	env->GetJavaVM(&jvm);
 
+	_godot_instance=env->NewGlobalRef(activity);
+//	_godot_instance=activity;
 
 	__android_log_print(ANDROID_LOG_INFO,"godot","***************** HELLO FROM JNI!!!!!!!!");
 
@@ -816,8 +818,10 @@ static void _initialize_java_modules() {
 		jmethodID getClassLoader = env->GetMethodID(activityClass,"getClassLoader", "()Ljava/lang/ClassLoader;");
 
 		jobject cls = env->CallObjectMethod(_godot_instance, getClassLoader);
+		//cls=env->NewGlobalRef(cls);
 
 		jclass classLoader = env->FindClass("java/lang/ClassLoader");
+		//classLoader=(jclass)env->NewGlobalRef(classLoader);
 
 		jmethodID findClass = env->GetMethodID(classLoader, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
 
@@ -835,6 +839,7 @@ static void _initialize_java_modules() {
 				ERR_EXPLAIN("Couldn't find singleton for class: "+m);
 				ERR_CONTINUE(!singletonClass);
 			}
+			//singletonClass=(jclass)env->NewGlobalRef(singletonClass);
 
 			__android_log_print(ANDROID_LOG_INFO,"godot","****^*^*?^*^*class data %x",singletonClass);
 			jmethodID initialize = env->GetStaticMethodID(singletonClass, "initialize", "(Landroid/app/Activity;)Lcom/android/godot/Godot$SingletonBase;");

+ 9 - 2
platform/iphone/app_delegate.mm

@@ -108,8 +108,15 @@ static int frame_count = 0;
 		if ([[UIDevice currentDevice]respondsToSelector:@selector(identifierForVendor)]) {
 			uuid = [UIDevice currentDevice].identifierForVendor.UUIDString;
 		}else{
-			// return [UIDevice currentDevice]. uniqueIdentifier
-			uuid = [[UIDevice currentDevice] performSelector:@selector(uniqueIdentifier)];
+
+			// before iOS 6, so just generate an identifier and store it
+			uuid = [[NSUserDefaults standardUserDefaults] objectForKey:@"identiferForVendor"];
+			if( !uuid ) {
+				CFUUIDRef cfuuid = CFUUIDCreate(NULL);
+				uuid = (__bridge_transfer NSString*)CFUUIDCreateString(NULL, cfuuid);
+				CFRelease(cfuuid);
+				[[NSUserDefaults standardUserDefaults] setObject:uuid forKey:@"identifierForVendor"];
+			}
 		}
 
 		OSIPhone::get_singleton()->set_unique_ID(String::utf8([uuid UTF8String]));

+ 0 - 1
platform/iphone/detect.py

@@ -37,7 +37,6 @@ def get_flags():
 		('tools', 'yes'),
 		('nedmalloc', 'no'),
 		('webp', 'yes'),
-		('module_FacebookScorer_ios_enabled', 'no'),
 	]
 
 

+ 13 - 12
platform/iphone/gl_view.mm

@@ -64,25 +64,22 @@ bool _play_video(String p_path) {
 	
 	p_path = Globals::get_singleton()->globalize_path(p_path);
 
-	// NSString *file_path = [NSString stringWithCString:p_path.utf8().get_data() encoding:NSASCIIStringEncoding];
 	NSString* file_path = [[[NSString alloc] initWithUTF8String:p_path.utf8().get_data()] autorelease];
 	NSURL *file_url = [NSURL fileURLWithPath:file_path];
 		
 	_instance.moviePlayerController = [[MPMoviePlayerController alloc] initWithContentURL:file_url];
 	_instance.moviePlayerController.controlStyle = MPMovieControlStyleNone;
-	[_instance.moviePlayerController setScalingMode:MPMovieScalingModeAspectFit];
-	// [_instance.moviePlayerController setScalingMode:MPMovieScalingModeAspectFill];
+	// [_instance.moviePlayerController setScalingMode:MPMovieScalingModeAspectFit];
+	[_instance.moviePlayerController setScalingMode:MPMovieScalingModeAspectFill];
 	
 	[[NSNotificationCenter defaultCenter] addObserver:_instance
                    selector:@selector(moviePlayBackDidFinish:)
                    name:MPMoviePlayerPlaybackDidFinishNotification
                    object:_instance.moviePlayerController];
-
-	[[_instance window] makeKeyAndVisible];
-	_instance.backgroundWindow = [[UIApplication sharedApplication] keyWindow];
-	[_instance.moviePlayerController.view setFrame:_instance.backgroundWindow.frame];
+	
+	[_instance.moviePlayerController.view setFrame:_instance.bounds];
 	_instance.moviePlayerController.view.userInteractionEnabled = NO;
-	[_instance.backgroundWindow addSubview:_instance.moviePlayerController.view];
+	[_instance addSubview:_instance.moviePlayerController.view];
 	[_instance.moviePlayerController play];
 
 	return true;
@@ -484,6 +481,14 @@ static void clear_touches() {
 	return self;
 }
 
+// -(BOOL)automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers {
+//     return YES;
+// }
+
+// - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation{
+//     return YES;
+// }
+
 // Stop animating and release resources when they are no longer needed.
 - (void)dealloc
 {
@@ -510,8 +515,4 @@ static void clear_touches() {
     _stop_video();
 }
 
-- (void)handleTap:(UITapGestureRecognizer *)gesture {
-    NSLog(@"Gesture\n");
-}
-
 @end

+ 25 - 0
platform/iphone/in_app_store.mm

@@ -167,6 +167,31 @@ Error InAppStore::request_product_info(Variant p_params) {
 			ret["type"] = "purchase";
 			ret["result"] = "ok";
 			ret["product_id"] = pid;
+            
+            if([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0){
+                
+                NSURL *receiptFileURL = nil;
+                NSBundle *bundle = [NSBundle mainBundle];
+                if ([bundle respondsToSelector:@selector(appStoreReceiptURL)]) {
+                    
+                    // Get the transaction receipt file path location in the app bundle.
+                    receiptFileURL = [bundle appStoreReceiptURL];
+                    
+                    // Read in the contents of the transaction file.
+                    ret["receipt"] = receiptFileURL;
+                    
+                } else {
+                    // Fall back to deprecated transaction receipt,
+                    // which is still available in iOS 7.
+                    
+                    // Use SKPaymentTransaction's transactionReceipt.
+                    ret["receipt"] = transaction.transactionReceipt;
+                }
+                
+            }else{
+                ret["receipt"] = transaction.transactionReceipt;
+            }
+            
 			InAppStore::get_singleton()->_post_event(ret);
 			[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
 		} break;

+ 0 - 1
platform/isim/detect.py

@@ -38,7 +38,6 @@ def get_flags():
 		('tools', 'yes'),
 		('nedmalloc', 'no'),
 		('webp', 'yes'),
-		('module_FacebookScorer_ios_enabled', 'no'),
 	]
 
 

+ 15 - 10
scene/2d/canvas_item.cpp

@@ -715,17 +715,18 @@ bool CanvasItem::is_block_transform_notify_enabled() const {
 	return block_transform_notify;
 }
 
-void CanvasItem::set_on_top(bool p_on_top) {
+void CanvasItem::set_draw_behind_parent(bool p_enable) {
 
-	if (on_top==p_on_top)
+	if (behind==p_enable)
 		return;
-	on_top=p_on_top;
-	VisualServer::get_singleton()->canvas_item_set_on_top(canvas_item,on_top);
+	behind=p_enable;
+	VisualServer::get_singleton()->canvas_item_set_on_top(canvas_item,!behind);
+
 }
 
-bool CanvasItem::is_on_top() const {
+bool CanvasItem::is_draw_behind_parent_enabled() const{
 
-	return on_top;
+	return behind;
 }
 
 
@@ -764,8 +765,11 @@ void CanvasItem::_bind_methods() {
 	ObjectTypeDB::bind_method(_MD("set_self_opacity","self_opacity"),&CanvasItem::set_self_opacity);
 	ObjectTypeDB::bind_method(_MD("get_self_opacity"),&CanvasItem::get_self_opacity);
 
-	ObjectTypeDB::bind_method(_MD("set_on_top","on_top"),&CanvasItem::set_on_top);
-	ObjectTypeDB::bind_method(_MD("is_on_top"),&CanvasItem::is_on_top);
+	ObjectTypeDB::bind_method(_MD("set_draw_behind_parent","enabe"),&CanvasItem::set_draw_behind_parent);
+	ObjectTypeDB::bind_method(_MD("is_draw_behind_parent_enabled"),&CanvasItem::is_draw_behind_parent_enabled);
+
+	ObjectTypeDB::bind_method(_MD("_set_on_top","on_top"),&CanvasItem::_set_on_top);
+	ObjectTypeDB::bind_method(_MD("_is_on_top"),&CanvasItem::_is_on_top);
 
 	//ObjectTypeDB::bind_method(_MD("get_transform"),&CanvasItem::get_transform);
 
@@ -796,7 +800,8 @@ void CanvasItem::_bind_methods() {
 	ADD_PROPERTY( PropertyInfo(Variant::BOOL,"visibility/visible"), _SCS("_set_visible_"),_SCS("_is_visible_") );
 	ADD_PROPERTY( PropertyInfo(Variant::REAL,"visibility/opacity",PROPERTY_HINT_RANGE, "0,1,0.01"), _SCS("set_opacity"),_SCS("get_opacity") );
 	ADD_PROPERTY( PropertyInfo(Variant::REAL,"visibility/self_opacity",PROPERTY_HINT_RANGE, "0,1,0.01"), _SCS("set_self_opacity"),_SCS("get_self_opacity") );
-	ADD_PROPERTY( PropertyInfo(Variant::BOOL,"visibility/on_top"), _SCS("set_on_top"),_SCS("is_on_top") );
+	ADD_PROPERTY( PropertyInfo(Variant::BOOL,"visibility/behind_parent"), _SCS("set_draw_behind_parent"),_SCS("is_draw_behind_parent_enabled") );
+	ADD_PROPERTY( PropertyInfo(Variant::BOOL,"visibility/on_top",PROPERTY_HINT_NONE,"",0), _SCS("_set_on_top"),_SCS("_is_on_top") ); //compatibility
 
 	ADD_PROPERTYNZ( PropertyInfo(Variant::INT,"visibility/blend_mode",PROPERTY_HINT_ENUM, "Mix,Add,Sub,Mul"), _SCS("set_blend_mode"),_SCS("get_blend_mode") );
 	//exporting these two things doesn't really make much sense i think
@@ -859,7 +864,7 @@ CanvasItem::CanvasItem() : xform_change(this) {
 	first_draw=false;
 	blend_mode=BLEND_MODE_MIX;
 	drawing=false;
-	on_top=true;
+	behind=false;
 	block_transform_notify=false;
 	viewport=NULL;
 	canvas_layer=NULL;

+ 6 - 3
scene/2d/canvas_item.h

@@ -77,7 +77,7 @@ private:
 	bool pending_children_sort;
 	bool drawing;
 	bool block_transform_notify;
-	bool on_top;
+	bool behind;
 
 	mutable Matrix32 global_transform;
 	mutable bool global_invalid;
@@ -102,6 +102,9 @@ private:
 
 	void _notify_transform(CanvasItem *p_node);
 
+	void _set_on_top(bool p_on_top) { set_draw_behind_parent(!p_on_top); }
+	bool _is_on_top() const { return !is_draw_behind_parent_enabled(); }
+
 protected:
 
 
@@ -175,8 +178,8 @@ public:
 	void set_as_toplevel(bool p_toplevel);
 	bool is_set_as_toplevel() const;
 
-	void set_on_top(bool p_on_top);
-	bool is_on_top() const;
+	void set_draw_behind_parent(bool p_enable);
+	bool is_draw_behind_parent_enabled() const;
 
 	CanvasItem *get_parent_item() const;
 

+ 2 - 0
scene/gui/control.cpp

@@ -351,11 +351,13 @@ void Control::_notification(int p_notification) {
 
 				window->tooltip_timer = memnew( Timer );
 				add_child(window->tooltip_timer);
+				window->tooltip_timer->force_parent_owned();
 				window->tooltip_timer->set_wait_time( GLOBAL_DEF("display/tooltip_delay",0.7));
 				window->tooltip_timer->connect("timeout",this,"_window_show_tooltip");
 				window->tooltip=NULL;
 				window->tooltip_popup = memnew( TooltipPanel );
 				add_child(window->tooltip_popup);
+				window->tooltip_popup->force_parent_owned();
 				window->tooltip_label = memnew( TooltipLabel );
 				window->tooltip_popup->add_child(window->tooltip_label);
 				window->tooltip_popup->set_as_toplevel(true);

+ 2 - 0
scene/main/node.h

@@ -264,6 +264,8 @@ public:
 	static void set_human_readable_collision_renaming(bool p_enabled);
 	static void init_node_hrcr();
 
+	void force_parent_owned() { data.parent_owned=true; } //hack to avoid duplicate nodes
+
 	/* CANVAS */
 
 	Node();

+ 13 - 2
servers/physics_2d/body_pair_2d_sw.cpp

@@ -375,6 +375,18 @@ bool BodyPair2DSW::setup(float p_step) {
 		}
 
 #endif
+
+
+		c.bounce=MAX(A->get_bounce(),B->get_bounce());
+		if (c.bounce) {
+
+			Vector2 crA( -A->get_angular_velocity() * c.rA.y, A->get_angular_velocity() * c.rA.x );
+			Vector2 crB( -B->get_angular_velocity() * c.rB.y, B->get_angular_velocity() * c.rB.x );
+			Vector2 dv = B->get_linear_velocity() + crB - A->get_linear_velocity() - crA;
+			c.bounce = c.bounce * dv.dot(c.normal);
+		}
+
+
 	}
 
 	return true;
@@ -420,8 +432,7 @@ void BodyPair2DSW::solve(float p_step) {
 		A->apply_bias_impulse(c.rA,-jb);
 		B->apply_bias_impulse(c.rB, jb);
 
-		real_t bounce=MAX(A->get_bounce(),B->get_bounce());
-		real_t jn = -(bounce + vn)*c.mass_normal;
+		real_t jn = -(c.bounce + vn)*c.mass_normal;
 		real_t jnOld = c.acc_normal_impulse;
 		c.acc_normal_impulse = MAX(jnOld + jn, 0.0f);
 

+ 2 - 0
servers/physics_2d/body_pair_2d_sw.h

@@ -66,6 +66,8 @@ class BodyPair2DSW : public Constraint2DSW {
 		bool active;
 		Vector2 rA,rB;
 		bool reused;
+		float bounce;
+
 	};
 
 	Vector2 offset_B; //use local A coordinates to avoid numerical issues on collision detection

+ 17 - 1
tools/editor/code_editor.cpp

@@ -510,6 +510,20 @@ void CodeTextEditor::set_error(const String& p_error) {
 
 }
 
+void CodeTextEditor::_update_font() {
+
+	String editor_font = EditorSettings::get_singleton()->get("text_editor/font");
+	if (editor_font!="") {
+		Ref<Font> fnt = ResourceLoader::load(editor_font);
+		if (fnt.is_valid()) {
+			text_editor->add_font_override("font",fnt);
+			return;
+		}
+	}
+
+	text_editor->add_font_override("font",get_font("source","Fonts"));
+}
+
 void CodeTextEditor::_text_changed_idle_timeout() {
 
 
@@ -527,8 +541,9 @@ void CodeTextEditor::_bind_methods() {
 
 	ObjectTypeDB::bind_method("_line_col_changed",&CodeTextEditor::_line_col_changed);
 	ObjectTypeDB::bind_method("_text_changed",&CodeTextEditor::_text_changed);
+	ObjectTypeDB::bind_method("_update_font",&CodeTextEditor::_update_font);
 	ObjectTypeDB::bind_method("_text_changed_idle_timeout",&CodeTextEditor::_text_changed_idle_timeout);
-    ObjectTypeDB::bind_method("_complete_request",&CodeTextEditor::_complete_request);
+	ObjectTypeDB::bind_method("_complete_request",&CodeTextEditor::_complete_request);
 }
 
 CodeTextEditor::CodeTextEditor() {
@@ -571,4 +586,5 @@ CodeTextEditor::CodeTextEditor() {
 	text_editor->set_completion(true,cs);
 	idle->connect("timeout", this,"_text_changed_idle_timeout");
 
+	EditorSettings::get_singleton()->connect("settings_changed",this,"_update_font");
 }

+ 3 - 1
tools/editor/code_editor.h

@@ -131,7 +131,9 @@ class CodeTextEditor : public Control {
 
 	Label *error;
 
-    void _complete_request(const String& p_request,int p_line);
+	void _update_font();
+
+	void _complete_request(const String& p_request,int p_line);
 protected:
 
 	void set_error(const String& p_error);

+ 8 - 0
tools/editor/editor_node.cpp

@@ -3263,6 +3263,14 @@ EditorNode::EditorNode() {
 	editor_register_icons(theme);
 	editor_register_fonts(theme);
 
+	String global_font = EditorSettings::get_singleton()->get("global/font");
+	if (global_font!="") {
+		Ref<Font> fnt = ResourceLoader::load(global_font);
+		if (fnt.is_valid()) {
+			theme->set_default_theme_font(fnt);
+		}
+	}
+
 	Ref<StyleBoxTexture> focus_sbt=memnew( StyleBoxTexture );
 	focus_sbt->set_texture(theme->get_icon("EditorFocus","EditorIcons"));
 	for(int i=0;i<4;i++) {

+ 8 - 2
tools/editor/editor_settings.cpp

@@ -384,6 +384,9 @@ void EditorSettings::_load_defaults() {
 
 	_THREAD_SAFE_METHOD_
 
+	set("global/font","");
+	hints["global/font"]=PropertyInfo(Variant::STRING,"global/font",PROPERTY_HINT_GLOBAL_FILE,"*.fnt");
+
 	set("text_editor/background_color",Color::html("3b000000"));
 	set("text_editor/text_color",Color::html("aaaaaa"));
 	set("text_editor/text_selected_color",Color::html("000000"));
@@ -398,6 +401,9 @@ void EditorSettings::_load_defaults() {
 	set("text_editor/idle_parse_delay",2);
 	set("text_editor/create_signal_callbacks",true);
 	set("text_editor/autosave_interval_seconds",60);
+	set("text_editor/font","");
+	hints["text_editor/font"]=PropertyInfo(Variant::STRING,"text_editor/font",PROPERTY_HINT_GLOBAL_FILE,"*.fnt");
+
 
 	set("3d_editor/default_fov",45.0);
 	set("3d_editor/default_z_near",0.1);
@@ -429,9 +435,9 @@ void EditorSettings::_load_defaults() {
 
 	set("import/pvrtc_texture_tool","");
 #ifdef WINDOWS_ENABLED
-	hints["import/pvrtc_texture_tool"]=PropertyInfo(Variant::STRING,"import/pvrtc_texture_tool",PROPERTY_HINT_FILE,"*.exe");
+	hints["import/pvrtc_texture_tool"]=PropertyInfo(Variant::STRING,"import/pvrtc_texture_tool",PROPERTY_HINT_GLOBAL_FILE,"*.exe");
 #else
-	hints["import/pvrtc_texture_tool"]=PropertyInfo(Variant::STRING,"import/pvrtc_texture_tool",PROPERTY_HINT_FILE,"");
+	hints["import/pvrtc_texture_tool"]=PropertyInfo(Variant::STRING,"import/pvrtc_texture_tool",PROPERTY_HINT_GLOBAL_FILE,"");
 #endif
 	set("PVRTC/fast_conversion",false);
 

+ 8 - 1
tools/editor/property_editor.cpp

@@ -909,7 +909,14 @@ void CustomPropertyEditor::_action_pressed(int p_which) {
 						Vector<String> extensions=hint_text.split(",");
 						for(int i=0;i<extensions.size();i++) {
 
-							file->add_filter("*."+extensions[i]+" ; "+extensions[i].to_upper() );
+							String filter = extensions[i];
+							if (filter.begins_with("."))
+								filter="*"+extensions[i];
+							else if (!filter.begins_with("*"))
+								filter="*."+extensions[i];
+
+
+							file->add_filter(filter+" ; "+extensions[i].to_upper() );
 
 						}
 					}

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff