Merge pull request #41340 from naithar/feature/pluggable-ios-modules-3.2

[iOS] [3.2] iOS Plugins
This commit is contained in:
Rémi Verschelde
2021-01-15 16:13:45 +01:00
committed by GitHub
47 changed files with 1449 additions and 463 deletions

18
modules/arkit/arkit.gdip Normal file
View File

@@ -0,0 +1,18 @@
[config]
name="ARKit"
binary="arkit_lib.a"
initialization="register_arkit_types"
deinitialization="unregister_arkit_types"
[dependencies]
linked=[]
embedded=[]
system=["AVFoundation.framework", "ARKit.framework"]
capabilities=["arkit"]
files=[]
[plist]
NSCameraUsageDescription="Device camera is used for some functionality"

View File

@@ -1,5 +1,5 @@
/*************************************************************************/
/* register_types.cpp */
/* arkit_module.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,7 +28,7 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include "register_types.h"
#include "arkit_module.h"
#include "arkit_interface.h"

View File

@@ -1,5 +1,5 @@
/*************************************************************************/
/* register_types.h */
/* arkit_module.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */

View File

@@ -5,17 +5,7 @@ Import("env_modules")
env_camera = env_modules.Clone()
if env["platform"] == "iphone":
# (iOS) Enable module support
env_camera.Append(CCFLAGS=["-fmodules", "-fcxx-modules"])
# (iOS) Build as separate static library
modules_sources = []
env_camera.add_source_files(modules_sources, "register_types.cpp")
env_camera.add_source_files(modules_sources, "camera_ios.mm")
mod_lib = env_modules.add_library("#bin/libgodot_camera_module" + env["LIBSUFFIX"], modules_sources)
elif env["platform"] == "windows":
if env["platform"] == "windows":
env_camera.add_source_files(env.modules_sources, "register_types.cpp")
env_camera.add_source_files(env.modules_sources, "camera_win.cpp")

View File

@@ -1,5 +1,5 @@
def can_build(env, platform):
return platform == "iphone" or platform == "osx" or platform == "windows"
return platform == "osx" or platform == "windows"
def configure(env):

View File

@@ -33,9 +33,6 @@
#if defined(WINDOWS_ENABLED)
#include "camera_win.h"
#endif
#if defined(IPHONE_ENABLED)
#include "camera_ios.h"
#endif
#if defined(OSX_ENABLED)
#include "camera_osx.h"
#endif
@@ -44,9 +41,6 @@ void register_camera_types() {
#if defined(WINDOWS_ENABLED)
CameraServer::make_default<CameraWindows>();
#endif
#if defined(IPHONE_ENABLED)
CameraServer::make_default<CameraIOS>();
#endif
#if defined(OSX_ENABLED)
CameraServer::make_default<CameraOSX>();
#endif

View File

@@ -0,0 +1,15 @@
#!/usr/bin/env python
Import("env")
Import("env_modules")
env_camera = env_modules.Clone()
# (iOS) Enable module support
env_camera.Append(CCFLAGS=["-fmodules", "-fcxx-modules"])
# (iOS) Build as separate static library
modules_sources = []
env_camera.add_source_files(modules_sources, "*.cpp")
env_camera.add_source_files(modules_sources, "*.mm")
mod_lib = env_modules.add_library("#bin/libgodot_camera_module" + env["LIBSUFFIX"], modules_sources)

View File

@@ -0,0 +1,18 @@
[config]
name="Camera"
binary="camera_lib.a"
initialization="register_camera_types"
deinitialization="unregister_camera_types"
[dependencies]
linked=[]
embedded=[]
system=["AVFoundation.framework"]
capabilities=[]
files=[]
[plist]
NSCameraUsageDescription="Device camera is used for some functionality"

View File

@@ -0,0 +1,40 @@
/*************************************************************************/
/* camera_module.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* 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 "camera_module.h"
#include "camera_ios.h"
void register_camera_types() {
CameraServer::make_default<CameraIOS>();
}
void unregister_camera_types() {
}

View File

@@ -0,0 +1,32 @@
/*************************************************************************/
/* camera_module.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* 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. */
/*************************************************************************/
void register_camera_types();
void unregister_camera_types();

View File

@@ -0,0 +1,6 @@
def can_build(env, platform):
return platform == "iphone"
def configure(env):
pass

15
modules/gamecenter/SCsub Normal file
View File

@@ -0,0 +1,15 @@
#!/usr/bin/env python
Import("env")
Import("env_modules")
env_gamecenter = env_modules.Clone()
# (iOS) Enable module support
env_gamecenter.Append(CCFLAGS=["-fmodules", "-fcxx-modules"])
# (iOS) Build as separate static library
modules_sources = []
env_gamecenter.add_source_files(modules_sources, "*.cpp")
env_gamecenter.add_source_files(modules_sources, "*.mm")
mod_lib = env_modules.add_library("#bin/libgodot_gamecenter_module" + env["LIBSUFFIX"], modules_sources)

View File

@@ -0,0 +1,6 @@
def can_build(env, platform):
return platform == "iphone"
def configure(env):
pass

View File

@@ -0,0 +1,72 @@
/*************************************************************************/
/* game_center.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* 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. */
/*************************************************************************/
#ifndef GAME_CENTER_H
#define GAME_CENTER_H
#include "core/object.h"
class GameCenter : public Object {
GDCLASS(GameCenter, Object);
static GameCenter *instance;
static void _bind_methods();
List<Variant> pending_events;
bool authenticated;
void return_connect_error(const char *p_error_description);
public:
Error authenticate();
bool is_authenticated();
Error post_score(Variant p_score);
Error award_achievement(Variant p_params);
void reset_achievements();
void request_achievements();
void request_achievement_descriptions();
Error show_game_center(Variant p_params);
Error request_identity_verification_signature();
void game_center_closed();
int get_pending_event_count();
Variant pop_pending_event();
static GameCenter *get_singleton();
GameCenter();
~GameCenter();
};
#endif

View File

@@ -0,0 +1,385 @@
/*************************************************************************/
/* game_center.mm */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* 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 "game_center.h"
#import "game_center_delegate.h"
#import "platform/iphone/app_delegate.h"
#import "platform/iphone/view_controller.h"
#import <GameKit/GameKit.h>
GameCenter *GameCenter::instance = NULL;
GodotGameCenterDelegate *gameCenterDelegate = nil;
void GameCenter::_bind_methods() {
ClassDB::bind_method(D_METHOD("authenticate"), &GameCenter::authenticate);
ClassDB::bind_method(D_METHOD("is_authenticated"), &GameCenter::is_authenticated);
ClassDB::bind_method(D_METHOD("post_score"), &GameCenter::post_score);
ClassDB::bind_method(D_METHOD("award_achievement"), &GameCenter::award_achievement);
ClassDB::bind_method(D_METHOD("reset_achievements"), &GameCenter::reset_achievements);
ClassDB::bind_method(D_METHOD("request_achievements"), &GameCenter::request_achievements);
ClassDB::bind_method(D_METHOD("request_achievement_descriptions"), &GameCenter::request_achievement_descriptions);
ClassDB::bind_method(D_METHOD("show_game_center"), &GameCenter::show_game_center);
ClassDB::bind_method(D_METHOD("request_identity_verification_signature"), &GameCenter::request_identity_verification_signature);
ClassDB::bind_method(D_METHOD("get_pending_event_count"), &GameCenter::get_pending_event_count);
ClassDB::bind_method(D_METHOD("pop_pending_event"), &GameCenter::pop_pending_event);
};
Error GameCenter::authenticate() {
//if this class isn't available, game center isn't implemented
if ((NSClassFromString(@"GKLocalPlayer")) == nil) {
return ERR_UNAVAILABLE;
}
GKLocalPlayer *player = [GKLocalPlayer localPlayer];
ERR_FAIL_COND_V(![player respondsToSelector:@selector(authenticateHandler)], ERR_UNAVAILABLE);
UIViewController *root_controller = [[UIApplication sharedApplication] delegate].window.rootViewController;
ERR_FAIL_COND_V(!root_controller, FAILED);
// This handler is called several times. First when the view needs to be shown, then again
// after the view is cancelled or the user logs in. Or if the user's already logged in, it's
// called just once to confirm they're authenticated. This is why no result needs to be specified
// in the presentViewController phase. In this case, more calls to this function will follow.
_weakify(root_controller);
_weakify(player);
player.authenticateHandler = (^(UIViewController *controller, NSError *error) {
_strongify(root_controller);
_strongify(player);
if (controller) {
[root_controller presentViewController:controller animated:YES completion:nil];
} else {
Dictionary ret;
ret["type"] = "authentication";
if (player.isAuthenticated) {
ret["result"] = "ok";
ret["player_id"] = [player.playerID UTF8String];
GameCenter::get_singleton()->authenticated = true;
} else {
ret["result"] = "error";
ret["error_code"] = (int64_t)error.code;
ret["error_description"] = [error.localizedDescription UTF8String];
GameCenter::get_singleton()->authenticated = false;
};
pending_events.push_back(ret);
};
});
return OK;
};
bool GameCenter::is_authenticated() {
return authenticated;
};
Error GameCenter::post_score(Variant p_score) {
Dictionary params = p_score;
ERR_FAIL_COND_V(!params.has("score") || !params.has("category"), ERR_INVALID_PARAMETER);
float score = params["score"];
String category = params["category"];
NSString *cat_str = [[NSString alloc] initWithUTF8String:category.utf8().get_data()];
GKScore *reporter = [[GKScore alloc] initWithLeaderboardIdentifier:cat_str];
reporter.value = score;
ERR_FAIL_COND_V([GKScore respondsToSelector:@selector(reportScores)], ERR_UNAVAILABLE);
[GKScore reportScores:@[ reporter ]
withCompletionHandler:^(NSError *error) {
Dictionary ret;
ret["type"] = "post_score";
if (error == nil) {
ret["result"] = "ok";
} else {
ret["result"] = "error";
ret["error_code"] = (int64_t)error.code;
ret["error_description"] = [error.localizedDescription UTF8String];
};
pending_events.push_back(ret);
}];
return OK;
};
Error GameCenter::award_achievement(Variant p_params) {
Dictionary params = p_params;
ERR_FAIL_COND_V(!params.has("name") || !params.has("progress"), ERR_INVALID_PARAMETER);
String name = params["name"];
float progress = params["progress"];
NSString *name_str = [[NSString alloc] initWithUTF8String:name.utf8().get_data()];
GKAchievement *achievement = [[GKAchievement alloc] initWithIdentifier:name_str];
ERR_FAIL_COND_V(!achievement, FAILED);
ERR_FAIL_COND_V([GKAchievement respondsToSelector:@selector(reportAchievements)], ERR_UNAVAILABLE);
achievement.percentComplete = progress;
achievement.showsCompletionBanner = NO;
if (params.has("show_completion_banner")) {
achievement.showsCompletionBanner = params["show_completion_banner"] ? YES : NO;
}
[GKAchievement reportAchievements:@[ achievement ]
withCompletionHandler:^(NSError *error) {
Dictionary ret;
ret["type"] = "award_achievement";
if (error == nil) {
ret["result"] = "ok";
} else {
ret["result"] = "error";
ret["error_code"] = (int64_t)error.code;
};
pending_events.push_back(ret);
}];
return OK;
};
void GameCenter::request_achievement_descriptions() {
[GKAchievementDescription loadAchievementDescriptionsWithCompletionHandler:^(NSArray *descriptions, NSError *error) {
Dictionary ret;
ret["type"] = "achievement_descriptions";
if (error == nil) {
ret["result"] = "ok";
PoolStringArray names;
PoolStringArray titles;
PoolStringArray unachieved_descriptions;
PoolStringArray achieved_descriptions;
PoolIntArray maximum_points;
Array hidden;
Array replayable;
for (NSUInteger i = 0; i < [descriptions count]; i++) {
GKAchievementDescription *description = [descriptions objectAtIndex:i];
const char *str = [description.identifier UTF8String];
names.push_back(String::utf8(str != NULL ? str : ""));
str = [description.title UTF8String];
titles.push_back(String::utf8(str != NULL ? str : ""));
str = [description.unachievedDescription UTF8String];
unachieved_descriptions.push_back(String::utf8(str != NULL ? str : ""));
str = [description.achievedDescription UTF8String];
achieved_descriptions.push_back(String::utf8(str != NULL ? str : ""));
maximum_points.push_back(description.maximumPoints);
hidden.push_back(description.hidden == YES);
replayable.push_back(description.replayable == YES);
}
ret["names"] = names;
ret["titles"] = titles;
ret["unachieved_descriptions"] = unachieved_descriptions;
ret["achieved_descriptions"] = achieved_descriptions;
ret["maximum_points"] = maximum_points;
ret["hidden"] = hidden;
ret["replayable"] = replayable;
} else {
ret["result"] = "error";
ret["error_code"] = (int64_t)error.code;
};
pending_events.push_back(ret);
}];
};
void GameCenter::request_achievements() {
[GKAchievement loadAchievementsWithCompletionHandler:^(NSArray *achievements, NSError *error) {
Dictionary ret;
ret["type"] = "achievements";
if (error == nil) {
ret["result"] = "ok";
PoolStringArray names;
PoolRealArray percentages;
for (NSUInteger i = 0; i < [achievements count]; i++) {
GKAchievement *achievement = [achievements objectAtIndex:i];
const char *str = [achievement.identifier UTF8String];
names.push_back(String::utf8(str != NULL ? str : ""));
percentages.push_back(achievement.percentComplete);
}
ret["names"] = names;
ret["progress"] = percentages;
} else {
ret["result"] = "error";
ret["error_code"] = (int64_t)error.code;
};
pending_events.push_back(ret);
}];
};
void GameCenter::reset_achievements() {
[GKAchievement resetAchievementsWithCompletionHandler:^(NSError *error) {
Dictionary ret;
ret["type"] = "reset_achievements";
if (error == nil) {
ret["result"] = "ok";
} else {
ret["result"] = "error";
ret["error_code"] = (int64_t)error.code;
};
pending_events.push_back(ret);
}];
};
Error GameCenter::show_game_center(Variant p_params) {
ERR_FAIL_COND_V(!NSProtocolFromString(@"GKGameCenterControllerDelegate"), FAILED);
Dictionary params = p_params;
GKGameCenterViewControllerState view_state = GKGameCenterViewControllerStateDefault;
if (params.has("view")) {
String view_name = params["view"];
if (view_name == "default") {
view_state = GKGameCenterViewControllerStateDefault;
} else if (view_name == "leaderboards") {
view_state = GKGameCenterViewControllerStateLeaderboards;
} else if (view_name == "achievements") {
view_state = GKGameCenterViewControllerStateAchievements;
} else if (view_name == "challenges") {
view_state = GKGameCenterViewControllerStateChallenges;
} else {
return ERR_INVALID_PARAMETER;
}
}
GKGameCenterViewController *controller = [[GKGameCenterViewController alloc] init];
ERR_FAIL_COND_V(!controller, FAILED);
UIViewController *root_controller = [[UIApplication sharedApplication] delegate].window.rootViewController;
ERR_FAIL_COND_V(!root_controller, FAILED);
controller.gameCenterDelegate = gameCenterDelegate;
controller.viewState = view_state;
if (view_state == GKGameCenterViewControllerStateLeaderboards) {
controller.leaderboardIdentifier = nil;
if (params.has("leaderboard_name")) {
String name = params["leaderboard_name"];
NSString *name_str = [[NSString alloc] initWithUTF8String:name.utf8().get_data()];
controller.leaderboardIdentifier = name_str;
}
}
[root_controller presentViewController:controller animated:YES completion:nil];
return OK;
};
Error GameCenter::request_identity_verification_signature() {
ERR_FAIL_COND_V(!is_authenticated(), ERR_UNAUTHORIZED);
GKLocalPlayer *player = [GKLocalPlayer localPlayer];
[player generateIdentityVerificationSignatureWithCompletionHandler:^(NSURL *publicKeyUrl, NSData *signature, NSData *salt, uint64_t timestamp, NSError *error) {
Dictionary ret;
ret["type"] = "identity_verification_signature";
if (error == nil) {
ret["result"] = "ok";
ret["public_key_url"] = [publicKeyUrl.absoluteString UTF8String];
ret["signature"] = [[signature base64EncodedStringWithOptions:0] UTF8String];
ret["salt"] = [[salt base64EncodedStringWithOptions:0] UTF8String];
ret["timestamp"] = timestamp;
ret["player_id"] = [player.playerID UTF8String];
} else {
ret["result"] = "error";
ret["error_code"] = (int64_t)error.code;
ret["error_description"] = [error.localizedDescription UTF8String];
};
pending_events.push_back(ret);
}];
return OK;
};
void GameCenter::game_center_closed() {
Dictionary ret;
ret["type"] = "show_game_center";
ret["result"] = "ok";
pending_events.push_back(ret);
}
int GameCenter::get_pending_event_count() {
return pending_events.size();
};
Variant GameCenter::pop_pending_event() {
Variant front = pending_events.front()->get();
pending_events.pop_front();
return front;
};
GameCenter *GameCenter::get_singleton() {
return instance;
};
GameCenter::GameCenter() {
ERR_FAIL_COND(instance != NULL);
instance = this;
authenticated = false;
gameCenterDelegate = [[GodotGameCenterDelegate alloc] init];
};
GameCenter::~GameCenter() {
if (gameCenterDelegate) {
gameCenterDelegate = nil;
}
}

View File

@@ -0,0 +1,35 @@
/*************************************************************************/
/* game_center_delegate.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* 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. */
/*************************************************************************/
#import <GameKit/GameKit.h>
@interface GodotGameCenterDelegate : NSObject <GKGameCenterControllerDelegate>
@end

View File

@@ -0,0 +1,45 @@
/*************************************************************************/
/* game_center_delegate.mm */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* 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. */
/*************************************************************************/
#import "game_center_delegate.h"
#include "game_center.h"
@implementation GodotGameCenterDelegate
- (void)gameCenterViewControllerDidFinish:(GKGameCenterViewController *)gameCenterViewController {
//[gameCenterViewController dismissViewControllerAnimated:YES completion:^{GameCenter::get_singleton()->game_center_closed();}];//version for signaling when overlay is completely gone
if (GameCenter::get_singleton()) {
GameCenter::get_singleton()->game_center_closed();
}
[gameCenterViewController dismissViewControllerAnimated:YES completion:nil];
}
@end

View File

@@ -0,0 +1,48 @@
/*************************************************************************/
/* game_center_module.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* 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 "game_center_module.h"
#include "core/engine.h"
#include "game_center.h"
GameCenter *game_center;
void register_gamecenter_types() {
game_center = memnew(GameCenter);
Engine::get_singleton()->add_singleton(Engine::Singleton("GameCenter", game_center));
}
void unregister_gamecenter_types() {
if (game_center) {
memdelete(game_center);
}
}

View File

@@ -0,0 +1,32 @@
/*************************************************************************/
/* game_center_module.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* 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. */
/*************************************************************************/
void register_gamecenter_types();
void unregister_gamecenter_types();

View File

@@ -0,0 +1,17 @@
[config]
name="GameCenter"
binary="gamecenter_lib.a"
initialization="register_gamecenter_types"
deinitialization="unregister_gamecenter_types"
[dependencies]
linked=[]
embedded=[]
system=["GameKit.framework"]
capabilities=["gamekit"]
files=[]
[plist]

15
modules/icloud/SCsub Normal file
View File

@@ -0,0 +1,15 @@
#!/usr/bin/env python
Import("env")
Import("env_modules")
env_icloud = env_modules.Clone()
# (iOS) Enable module support
env_icloud.Append(CCFLAGS=["-fmodules", "-fcxx-modules"])
# (iOS) Build as separate static library
modules_sources = []
env_icloud.add_source_files(modules_sources, "*.cpp")
env_icloud.add_source_files(modules_sources, "*.mm")
mod_lib = env_modules.add_library("#bin/libgodot_icloud_module" + env["LIBSUFFIX"], modules_sources)

6
modules/icloud/config.py Normal file
View File

@@ -0,0 +1,6 @@
def can_build(env, platform):
return platform == "iphone"
def configure(env):
pass

View File

@@ -0,0 +1,17 @@
[config]
name="iCloud"
binary="icloud_lib.a"
initialization="register_icloud_types"
deinitialization="unregister_icloud_types"
[dependencies]
linked=[]
embedded=[]
system=[]
capabilities=[]
files=[]
[plist]

61
modules/icloud/icloud.h Normal file
View File

@@ -0,0 +1,61 @@
/*************************************************************************/
/* icloud.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* 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. */
/*************************************************************************/
#ifndef ICLOUD_H
#define ICLOUD_H
#include "core/object.h"
class ICloud : public Object {
GDCLASS(ICloud, Object);
static ICloud *instance;
static void _bind_methods();
List<Variant> pending_events;
public:
Error remove_key(Variant p_param);
Variant set_key_values(Variant p_param);
Variant get_key_value(Variant p_param);
Error synchronize_key_values();
Variant get_all_key_values();
int get_pending_event_count();
Variant pop_pending_event();
static ICloud *get_singleton();
ICloud();
~ICloud();
};
#endif

347
modules/icloud/icloud.mm Normal file
View File

@@ -0,0 +1,347 @@
/*************************************************************************/
/* icloud.mm */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* 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 "icloud.h"
#import "platform/iphone/app_delegate.h"
#import <Foundation/Foundation.h>
ICloud *ICloud::instance = NULL;
void ICloud::_bind_methods() {
ClassDB::bind_method(D_METHOD("remove_key"), &ICloud::remove_key);
ClassDB::bind_method(D_METHOD("set_key_values"), &ICloud::set_key_values);
ClassDB::bind_method(D_METHOD("get_key_value"), &ICloud::get_key_value);
ClassDB::bind_method(D_METHOD("synchronize_key_values"), &ICloud::synchronize_key_values);
ClassDB::bind_method(D_METHOD("get_all_key_values"), &ICloud::get_all_key_values);
ClassDB::bind_method(D_METHOD("get_pending_event_count"), &ICloud::get_pending_event_count);
ClassDB::bind_method(D_METHOD("pop_pending_event"), &ICloud::pop_pending_event);
};
int ICloud::get_pending_event_count() {
return pending_events.size();
};
Variant ICloud::pop_pending_event() {
Variant front = pending_events.front()->get();
pending_events.pop_front();
return front;
};
ICloud *ICloud::get_singleton() {
return instance;
};
//convert from apple's abstract type to godot's abstract type....
Variant nsobject_to_variant(NSObject *object) {
if ([object isKindOfClass:[NSString class]]) {
const char *str = [(NSString *)object UTF8String];
return String::utf8(str != NULL ? str : "");
} else if ([object isKindOfClass:[NSData class]]) {
PoolByteArray ret;
NSData *data = (NSData *)object;
if ([data length] > 0) {
ret.resize([data length]);
{
PoolByteArray::Write w = ret.write();
copymem(w.ptr(), [data bytes], [data length]);
}
}
return ret;
} else if ([object isKindOfClass:[NSArray class]]) {
Array result;
NSArray *array = (NSArray *)object;
for (unsigned int i = 0; i < [array count]; ++i) {
NSObject *value = [array objectAtIndex:i];
result.push_back(nsobject_to_variant(value));
}
return result;
} else if ([object isKindOfClass:[NSDictionary class]]) {
Dictionary result;
NSDictionary *dic = (NSDictionary *)object;
NSArray *keys = [dic allKeys];
int count = [keys count];
for (int i = 0; i < count; ++i) {
NSObject *k = [keys objectAtIndex:i];
NSObject *v = [dic objectForKey:k];
result[nsobject_to_variant(k)] = nsobject_to_variant(v);
}
return result;
} else if ([object isKindOfClass:[NSNumber class]]) {
//Every type except numbers can reliably identify its type. The following is comparing to the *internal* representation, which isn't guaranteed to match the type that was used to create it, and is not advised, particularly when dealing with potential platform differences (ie, 32/64 bit)
//To avoid errors, we'll cast as broadly as possible, and only return int or float.
//bool, char, int, uint, longlong -> int
//float, double -> float
NSNumber *num = (NSNumber *)object;
if (strcmp([num objCType], @encode(BOOL)) == 0) {
return Variant((int)[num boolValue]);
} else if (strcmp([num objCType], @encode(char)) == 0) {
return Variant((int)[num charValue]);
} else if (strcmp([num objCType], @encode(int)) == 0) {
return Variant([num intValue]);
} else if (strcmp([num objCType], @encode(unsigned int)) == 0) {
return Variant((int)[num unsignedIntValue]);
} else if (strcmp([num objCType], @encode(long long)) == 0) {
return Variant((int)[num longValue]);
} else if (strcmp([num objCType], @encode(float)) == 0) {
return Variant([num floatValue]);
} else if (strcmp([num objCType], @encode(double)) == 0) {
return Variant((float)[num doubleValue]);
} else {
return Variant();
}
} else if ([object isKindOfClass:[NSDate class]]) {
//this is a type that icloud supports...but how did you submit it in the first place?
//I guess this is a type that *might* show up, if you were, say, trying to make your game
//compatible with existing cloud data written by another engine's version of your game
WARN_PRINT("NSDate unsupported, returning null Variant");
return Variant();
} else if ([object isKindOfClass:[NSNull class]] or object == nil) {
return Variant();
} else {
WARN_PRINT("Trying to convert unknown NSObject type to Variant");
return Variant();
}
}
NSObject *variant_to_nsobject(Variant v) {
if (v.get_type() == Variant::STRING) {
return [[NSString alloc] initWithUTF8String:((String)v).utf8().get_data()];
} else if (v.get_type() == Variant::REAL) {
return [NSNumber numberWithDouble:(double)v];
} else if (v.get_type() == Variant::INT) {
return [NSNumber numberWithLongLong:(long)(int)v];
} else if (v.get_type() == Variant::BOOL) {
return [NSNumber numberWithBool:BOOL((bool)v)];
} else if (v.get_type() == Variant::DICTIONARY) {
NSMutableDictionary *result = [[NSMutableDictionary alloc] init];
Dictionary dic = v;
Array keys = dic.keys();
for (int i = 0; i < keys.size(); ++i) {
NSString *key = [[NSString alloc] initWithUTF8String:((String)(keys[i])).utf8().get_data()];
NSObject *value = variant_to_nsobject(dic[keys[i]]);
if (key == NULL || value == NULL) {
return NULL;
}
[result setObject:value forKey:key];
}
return result;
} else if (v.get_type() == Variant::ARRAY) {
NSMutableArray *result = [[NSMutableArray alloc] init];
Array arr = v;
for (int i = 0; i < arr.size(); ++i) {
NSObject *value = variant_to_nsobject(arr[i]);
if (value == NULL) {
//trying to add something unsupported to the array. cancel the whole array
return NULL;
}
[result addObject:value];
}
return result;
} else if (v.get_type() == Variant::POOL_BYTE_ARRAY) {
PoolByteArray arr = v;
PoolByteArray::Read r = arr.read();
NSData *result = [NSData dataWithBytes:r.ptr() length:arr.size()];
return result;
}
WARN_PRINT(String("Could not add unsupported type to iCloud: '" + Variant::get_type_name(v.get_type()) + "'").utf8().get_data());
return NULL;
}
Error ICloud::remove_key(Variant p_param) {
String param = p_param;
NSString *key = [[NSString alloc] initWithUTF8String:param.utf8().get_data()];
NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore];
if (![[store dictionaryRepresentation] objectForKey:key]) {
return ERR_INVALID_PARAMETER;
}
[store removeObjectForKey:key];
return OK;
}
//return an array of the keys that could not be set
Variant ICloud::set_key_values(Variant p_params) {
Dictionary params = p_params;
Array keys = params.keys();
Array error_keys;
for (int i = 0; i < keys.size(); ++i) {
String variant_key = keys[i];
Variant variant_value = params[variant_key];
NSString *key = [[NSString alloc] initWithUTF8String:variant_key.utf8().get_data()];
if (key == NULL) {
error_keys.push_back(variant_key);
continue;
}
NSObject *value = variant_to_nsobject(variant_value);
if (value == NULL) {
error_keys.push_back(variant_key);
continue;
}
NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore];
[store setObject:value forKey:key];
}
return error_keys;
}
Variant ICloud::get_key_value(Variant p_param) {
String param = p_param;
NSString *key = [[NSString alloc] initWithUTF8String:param.utf8().get_data()];
NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore];
if (![[store dictionaryRepresentation] objectForKey:key]) {
return Variant();
}
Variant result = nsobject_to_variant([[store dictionaryRepresentation] objectForKey:key]);
return result;
}
Variant ICloud::get_all_key_values() {
Dictionary result;
NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore];
NSDictionary *store_dictionary = [store dictionaryRepresentation];
NSArray *keys = [store_dictionary allKeys];
int count = [keys count];
for (int i = 0; i < count; ++i) {
NSString *k = [keys objectAtIndex:i];
NSObject *v = [store_dictionary objectForKey:k];
const char *str = [k UTF8String];
if (str != NULL) {
result[String::utf8(str)] = nsobject_to_variant(v);
}
}
return result;
}
Error ICloud::synchronize_key_values() {
NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore];
BOOL result = [store synchronize];
if (result == YES) {
return OK;
} else {
return FAILED;
}
}
/*
Error ICloud::initial_sync() {
//you sometimes have to write something to the store to get it to download new data. go apple!
NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore];
if ([store boolForKey:@"isb"])
{
[store setBool:NO forKey:@"isb"];
}
else
{
[store setBool:YES forKey:@"isb"];
}
return synchronize();
}
*/
ICloud::ICloud() {
ERR_FAIL_COND(instance != NULL);
instance = this;
//connected = false;
[[NSNotificationCenter defaultCenter]
addObserverForName:NSUbiquitousKeyValueStoreDidChangeExternallyNotification
object:[NSUbiquitousKeyValueStore defaultStore]
queue:nil
usingBlock:^(NSNotification *notification) {
NSDictionary *userInfo = [notification userInfo];
NSInteger change = [[userInfo objectForKey:NSUbiquitousKeyValueStoreChangeReasonKey] integerValue];
Dictionary ret;
ret["type"] = "key_value_changed";
//PoolStringArray result_keys;
//Array result_values;
Dictionary keyValues;
String reason = "";
if (change == NSUbiquitousKeyValueStoreServerChange) {
reason = "server";
} else if (change == NSUbiquitousKeyValueStoreInitialSyncChange) {
reason = "initial_sync";
} else if (change == NSUbiquitousKeyValueStoreQuotaViolationChange) {
reason = "quota_violation";
} else if (change == NSUbiquitousKeyValueStoreAccountChange) {
reason = "account";
}
ret["reason"] = reason;
NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore];
NSArray *keys = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangedKeysKey];
for (NSString *key in keys) {
const char *str = [key UTF8String];
if (str == NULL) {
continue;
}
NSObject *object = [store objectForKey:key];
//figure out what kind of object it is
Variant value = nsobject_to_variant(object);
keyValues[String::utf8(str)] = value;
}
ret["changed_values"] = keyValues;
pending_events.push_back(ret);
}];
}
ICloud::~ICloud() {}

View File

@@ -0,0 +1,48 @@
/*************************************************************************/
/* icloud_module.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* 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 "icloud_module.h"
#include "core/engine.h"
#include "icloud.h"
ICloud *icloud;
void register_icloud_types() {
icloud = memnew(ICloud);
Engine::get_singleton()->add_singleton(Engine::Singleton("ICloud", icloud));
}
void unregister_icloud_types() {
if (icloud) {
memdelete(icloud);
}
}

View File

@@ -0,0 +1,32 @@
/*************************************************************************/
/* icloud_module.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* 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. */
/*************************************************************************/
void register_icloud_types();
void unregister_icloud_types();

15
modules/inappstore/SCsub Normal file
View File

@@ -0,0 +1,15 @@
#!/usr/bin/env python
Import("env")
Import("env_modules")
env_inappstore = env_modules.Clone()
# (iOS) Enable module support
env_inappstore.Append(CCFLAGS=["-fmodules", "-fcxx-modules"])
# (iOS) Build as separate static library
modules_sources = []
env_inappstore.add_source_files(modules_sources, "*.cpp")
env_inappstore.add_source_files(modules_sources, "*.mm")
mod_lib = env_modules.add_library("#bin/libgodot_inappstore_module" + env["LIBSUFFIX"], modules_sources)

View File

@@ -0,0 +1,6 @@
def can_build(env, platform):
return platform == "iphone"
def configure(env):
pass

View File

@@ -0,0 +1,78 @@
/*************************************************************************/
/* in_app_store.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* 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. */
/*************************************************************************/
#ifndef IN_APP_STORE_H
#define IN_APP_STORE_H
#include "core/object.h"
#ifdef __OBJC__
@class GodotProductsDelegate;
@class GodotTransactionsObserver;
typedef GodotProductsDelegate InAppStoreProductDelegate;
typedef GodotTransactionsObserver InAppStoreTransactionObserver;
#else
typedef void InAppStoreProductDelegate;
typedef void InAppStoreTransactionObserver;
#endif
class InAppStore : public Object {
GDCLASS(InAppStore, Object);
static InAppStore *instance;
static void _bind_methods();
List<Variant> pending_events;
InAppStoreProductDelegate *products_request_delegate;
InAppStoreTransactionObserver *transactions_observer;
public:
Error request_product_info(Variant p_params);
Error restore_purchases();
Error purchase(Variant p_params);
int get_pending_event_count();
Variant pop_pending_event();
void finish_transaction(String product_id);
void set_auto_finish_transaction(bool b);
void _post_event(Variant p_event);
void _record_purchase(String product_id);
static InAppStore *get_singleton();
InAppStore();
~InAppStore();
};
#endif

View File

@@ -0,0 +1,414 @@
/*************************************************************************/
/* in_app_store.mm */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* 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 "in_app_store.h"
#import <Foundation/Foundation.h>
#import <StoreKit/StoreKit.h>
InAppStore *InAppStore::instance = NULL;
@interface SKProduct (LocalizedPrice)
@property(nonatomic, readonly) NSString *localizedPrice;
@end
//----------------------------------//
// SKProduct extension
//----------------------------------//
@implementation SKProduct (LocalizedPrice)
- (NSString *)localizedPrice {
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
[numberFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
[numberFormatter setLocale:self.priceLocale];
NSString *formattedString = [numberFormatter stringFromNumber:self.price];
return formattedString;
}
@end
@interface GodotProductsDelegate : NSObject <SKProductsRequestDelegate>
@property(nonatomic, strong) NSMutableArray *loadedProducts;
@property(nonatomic, strong) NSMutableArray *pendingRequests;
- (void)performRequestWithProductIDs:(NSSet *)productIDs;
- (Error)purchaseProductWithProductID:(NSString *)productID;
- (void)reset;
@end
@implementation GodotProductsDelegate
- (instancetype)init {
self = [super init];
if (self) {
[self godot_commonInit];
}
return self;
}
- (void)godot_commonInit {
self.loadedProducts = [NSMutableArray new];
self.pendingRequests = [NSMutableArray new];
}
- (void)performRequestWithProductIDs:(NSSet *)productIDs {
SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:productIDs];
request.delegate = self;
[self.pendingRequests addObject:request];
[request start];
}
- (Error)purchaseProductWithProductID:(NSString *)productID {
SKProduct *product = nil;
NSLog(@"searching for product!");
if (self.loadedProducts) {
for (SKProduct *storedProduct in self.loadedProducts) {
if ([storedProduct.productIdentifier isEqualToString:productID]) {
product = storedProduct;
break;
}
}
}
if (!product) {
return ERR_INVALID_PARAMETER;
}
NSLog(@"product found!");
SKPayment *payment = [SKPayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addPayment:payment];
NSLog(@"purchase sent!");
return OK;
}
- (void)reset {
[self.loadedProducts removeAllObjects];
[self.pendingRequests removeAllObjects];
}
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error {
[self.pendingRequests removeObject:request];
Dictionary ret;
ret["type"] = "product_info";
ret["result"] = "error";
ret["error"] = String::utf8([error.localizedDescription UTF8String]);
InAppStore::get_singleton()->_post_event(ret);
}
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
[self.pendingRequests removeObject:request];
NSArray *products = response.products;
[self.loadedProducts addObjectsFromArray:products];
Dictionary ret;
ret["type"] = "product_info";
ret["result"] = "ok";
PoolStringArray titles;
PoolStringArray descriptions;
PoolRealArray prices;
PoolStringArray ids;
PoolStringArray localized_prices;
PoolStringArray currency_codes;
for (NSUInteger i = 0; i < [products count]; i++) {
SKProduct *product = [products objectAtIndex:i];
const char *str = [product.localizedTitle UTF8String];
titles.push_back(String::utf8(str != NULL ? str : ""));
str = [product.localizedDescription UTF8String];
descriptions.push_back(String::utf8(str != NULL ? str : ""));
prices.push_back([product.price doubleValue]);
ids.push_back(String::utf8([product.productIdentifier UTF8String]));
localized_prices.push_back(String::utf8([product.localizedPrice UTF8String]));
currency_codes.push_back(String::utf8([[[product priceLocale] objectForKey:NSLocaleCurrencyCode] UTF8String]));
}
ret["titles"] = titles;
ret["descriptions"] = descriptions;
ret["prices"] = prices;
ret["ids"] = ids;
ret["localized_prices"] = localized_prices;
ret["currency_codes"] = currency_codes;
PoolStringArray invalid_ids;
for (NSString *ipid in response.invalidProductIdentifiers) {
invalid_ids.push_back(String::utf8([ipid UTF8String]));
}
ret["invalid_ids"] = invalid_ids;
InAppStore::get_singleton()->_post_event(ret);
}
@end
@interface GodotTransactionsObserver : NSObject <SKPaymentTransactionObserver>
@property(nonatomic, assign) BOOL shouldAutoFinishTransactions;
@property(nonatomic, strong) NSMutableDictionary *pendingTransactions;
- (void)finishTransactionWithProductID:(NSString *)productID;
- (void)reset;
@end
@implementation GodotTransactionsObserver
- (instancetype)init {
self = [super init];
if (self) {
[self godot_commonInit];
}
return self;
}
- (void)godot_commonInit {
self.pendingTransactions = [NSMutableDictionary new];
}
- (void)finishTransactionWithProductID:(NSString *)productID {
SKPaymentTransaction *transaction = self.pendingTransactions[productID];
if (transaction) {
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
self.pendingTransactions[productID] = nil;
}
- (void)reset {
[self.pendingTransactions removeAllObjects];
}
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions {
printf("transactions updated!\n");
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchased: {
printf("status purchased!\n");
String pid = String::utf8([transaction.payment.productIdentifier UTF8String]);
String transactionId = String::utf8([transaction.transactionIdentifier UTF8String]);
InAppStore::get_singleton()->_record_purchase(pid);
Dictionary ret;
ret["type"] = "purchase";
ret["result"] = "ok";
ret["product_id"] = pid;
ret["transaction_id"] = transactionId;
NSData *receipt = nil;
int sdk_version = [[[UIDevice currentDevice] systemVersion] intValue];
NSBundle *bundle = [NSBundle mainBundle];
// Get the transaction receipt file path location in the app bundle.
NSURL *receiptFileURL = [bundle appStoreReceiptURL];
// Read in the contents of the transaction file.
receipt = [NSData dataWithContentsOfURL:receiptFileURL];
NSString *receipt_to_send = nil;
if (receipt != nil) {
receipt_to_send = [receipt base64EncodedStringWithOptions:0];
}
Dictionary receipt_ret;
receipt_ret["receipt"] = String::utf8(receipt_to_send != nil ? [receipt_to_send UTF8String] : "");
receipt_ret["sdk"] = sdk_version;
ret["receipt"] = receipt_ret;
InAppStore::get_singleton()->_post_event(ret);
if (self.shouldAutoFinishTransactions) {
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
} else {
self.pendingTransactions[transaction.payment.productIdentifier] = transaction;
}
} break;
case SKPaymentTransactionStateFailed: {
printf("status transaction failed!\n");
String pid = String::utf8([transaction.payment.productIdentifier UTF8String]);
Dictionary ret;
ret["type"] = "purchase";
ret["result"] = "error";
ret["product_id"] = pid;
ret["error"] = String::utf8([transaction.error.localizedDescription UTF8String]);
InAppStore::get_singleton()->_post_event(ret);
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
} break;
case SKPaymentTransactionStateRestored: {
printf("status transaction restored!\n");
String pid = String::utf8([transaction.originalTransaction.payment.productIdentifier UTF8String]);
InAppStore::get_singleton()->_record_purchase(pid);
Dictionary ret;
ret["type"] = "restore";
ret["result"] = "ok";
ret["product_id"] = pid;
InAppStore::get_singleton()->_post_event(ret);
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
} break;
default: {
printf("status default %i!\n", (int)transaction.transactionState);
} break;
}
}
}
@end
void InAppStore::_bind_methods() {
ClassDB::bind_method(D_METHOD("request_product_info"), &InAppStore::request_product_info);
ClassDB::bind_method(D_METHOD("restore_purchases"), &InAppStore::restore_purchases);
ClassDB::bind_method(D_METHOD("purchase"), &InAppStore::purchase);
ClassDB::bind_method(D_METHOD("get_pending_event_count"), &InAppStore::get_pending_event_count);
ClassDB::bind_method(D_METHOD("pop_pending_event"), &InAppStore::pop_pending_event);
ClassDB::bind_method(D_METHOD("finish_transaction"), &InAppStore::finish_transaction);
ClassDB::bind_method(D_METHOD("set_auto_finish_transaction"), &InAppStore::set_auto_finish_transaction);
}
Error InAppStore::request_product_info(Variant p_params) {
Dictionary params = p_params;
ERR_FAIL_COND_V(!params.has("product_ids"), ERR_INVALID_PARAMETER);
PoolStringArray pids = params["product_ids"];
printf("************ request product info! %i\n", pids.size());
NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:pids.size()];
for (int i = 0; i < pids.size(); i++) {
printf("******** adding %ls to product list\n", pids[i].c_str());
NSString *pid = [[NSString alloc] initWithUTF8String:pids[i].utf8().get_data()];
[array addObject:pid];
}
NSSet *products = [[NSSet alloc] initWithArray:array];
[products_request_delegate performRequestWithProductIDs:products];
return OK;
}
Error InAppStore::restore_purchases() {
printf("restoring purchases!\n");
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
return OK;
}
Error InAppStore::purchase(Variant p_params) {
ERR_FAIL_COND_V(![SKPaymentQueue canMakePayments], ERR_UNAVAILABLE);
if (![SKPaymentQueue canMakePayments]) {
return ERR_UNAVAILABLE;
}
printf("purchasing!\n");
Dictionary params = p_params;
ERR_FAIL_COND_V(!params.has("product_id"), ERR_INVALID_PARAMETER);
NSString *pid = [[NSString alloc] initWithUTF8String:String(params["product_id"]).utf8().get_data()];
return [products_request_delegate purchaseProductWithProductID:pid];
}
int InAppStore::get_pending_event_count() {
return pending_events.size();
}
Variant InAppStore::pop_pending_event() {
Variant front = pending_events.front()->get();
pending_events.pop_front();
return front;
}
void InAppStore::_post_event(Variant p_event) {
pending_events.push_back(p_event);
}
void InAppStore::_record_purchase(String product_id) {
String skey = "purchased/" + product_id;
NSString *key = [[NSString alloc] initWithUTF8String:skey.utf8().get_data()];
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:key];
[[NSUserDefaults standardUserDefaults] synchronize];
}
InAppStore *InAppStore::get_singleton() {
return instance;
}
InAppStore::InAppStore() {
ERR_FAIL_COND(instance != NULL);
instance = this;
products_request_delegate = [[GodotProductsDelegate alloc] init];
transactions_observer = [[GodotTransactionsObserver alloc] init];
[[SKPaymentQueue defaultQueue] addTransactionObserver:transactions_observer];
}
void InAppStore::finish_transaction(String product_id) {
NSString *prod_id = [NSString stringWithCString:product_id.utf8().get_data() encoding:NSUTF8StringEncoding];
[transactions_observer finishTransactionWithProductID:prod_id];
}
void InAppStore::set_auto_finish_transaction(bool b) {
transactions_observer.shouldAutoFinishTransactions = b;
}
InAppStore::~InAppStore() {
[products_request_delegate reset];
[transactions_observer reset];
products_request_delegate = nil;
[[SKPaymentQueue defaultQueue] removeTransactionObserver:transactions_observer];
transactions_observer = nil;
}

View File

@@ -0,0 +1,48 @@
/*************************************************************************/
/* in_app_store_module.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* 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 "in_app_store_module.h"
#include "core/engine.h"
#include "in_app_store.h"
InAppStore *store_kit;
void register_inappstore_types() {
store_kit = memnew(InAppStore);
Engine::get_singleton()->add_singleton(Engine::Singleton("InAppStore", store_kit));
}
void unregister_inappstore_types() {
if (store_kit) {
memdelete(store_kit);
}
}

View File

@@ -0,0 +1,32 @@
/*************************************************************************/
/* in_app_store_module.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* 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. */
/*************************************************************************/
void register_inappstore_types();
void unregister_inappstore_types();

View File

@@ -0,0 +1,17 @@
[config]
name="InAppStore"
binary="inappstore_lib.a"
initialization="register_inappstore_types"
deinitialization="unregister_inappstore_types"
[dependencies]
linked=[]
embedded=[]
system=["StoreKit.framework"]
capabilities=[]
files=[]
[plist]