diff --git a/addons/steam_api/LICENSE b/addons/steam_api/LICENSE new file mode 100644 index 0000000..da9abfc --- /dev/null +++ b/addons/steam_api/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2021 Sam Murray + +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. + diff --git a/addons/steam_api/examples/example.gd b/addons/steam_api/examples/example.gd new file mode 100644 index 0000000..f8acf08 --- /dev/null +++ b/addons/steam_api/examples/example.gd @@ -0,0 +1,27 @@ +extends Node2D + +func _ready(): + print(Steam.is_init()) + + print(Steam.user.get_steam_id().get_account_id()) + + Steam.clear_achievement("gator_god") + Steam.set_achievement("gator_god") + + + Steam.set_leaderboard_score("High Score", 982, Steam.LeaderboardUploadScoreMethod.KeepBest, PoolIntArray([5, 5, 3])) + + for i in Steam.user_stats.get_num_achievements(): + print(Steam.user_stats.get_achievement_name(i)) + + var scores = yield(Steam.get_leaderboard_scores("High Score", 0, 10, Steam.LeaderboardDataRequest.Global, 3), "done") + print(scores) + + Steam.friends.connect("game_overlay_activated", self, "_on_game_overlay_activated") + Steam.friends.activate_game_overlay_to_web_page("https://steamcommunity.com/") + Steam.friends.activate_game_overlay_to_store(1435470, Steam.OverlayToStoreFlag.AddToCart) + + Steam.friends.set_rich_presence("steam_display", "cactus") + +func _on_game_overlay_activated(active:bool) -> void: + $game_overlay_activated.text = str(active) diff --git a/addons/steam_api/examples/example.tscn b/addons/steam_api/examples/example.tscn new file mode 100644 index 0000000..08cd608 --- /dev/null +++ b/addons/steam_api/examples/example.tscn @@ -0,0 +1,16 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://addons/steam_api/examples/example.gd" type="Script" id=1] + +[node name="Node2D" type="Node2D"] +script = ExtResource( 1 ) + +[node name="game_overlay_activated" type="Label" parent="."] +margin_left = 362.0 +margin_top = 231.0 +margin_right = 402.0 +margin_bottom = 245.0 +text = "0" +__meta__ = { +"_edit_use_anchors_": false +} diff --git a/addons/steam_api/libsteam_api.dylib b/addons/steam_api/libsteam_api.dylib new file mode 100644 index 0000000..8d3c5ee Binary files /dev/null and b/addons/steam_api/libsteam_api.dylib differ diff --git a/addons/steam_api/libsteam_api.so b/addons/steam_api/libsteam_api.so new file mode 100644 index 0000000..8bf6762 Binary files /dev/null and b/addons/steam_api/libsteam_api.so differ diff --git a/addons/steam_api/libsteamsdk-godot.dylib b/addons/steam_api/libsteamsdk-godot.dylib new file mode 100644 index 0000000..c52b84f Binary files /dev/null and b/addons/steam_api/libsteamsdk-godot.dylib differ diff --git a/addons/steam_api/libsteamsdk-godot.so b/addons/steam_api/libsteamsdk-godot.so new file mode 100644 index 0000000..4a8552c Binary files /dev/null and b/addons/steam_api/libsteamsdk-godot.so differ diff --git a/addons/steam_api/plugin.cfg b/addons/steam_api/plugin.cfg new file mode 100644 index 0000000..6cfacda --- /dev/null +++ b/addons/steam_api/plugin.cfg @@ -0,0 +1,7 @@ +[plugin] + +name="SteamAPI" +description="" +author="samsface" +version="" +script="plugin.gd" diff --git a/addons/steam_api/plugin.gd b/addons/steam_api/plugin.gd new file mode 100644 index 0000000..acb9f57 --- /dev/null +++ b/addons/steam_api/plugin.gd @@ -0,0 +1,26 @@ +tool +extends EditorPlugin + +var setup:Control + +func _enter_tree(): + setup = load("res://addons/steam_api/setup.tscn").instance() + add_control_to_container(CONTAINER_PROJECT_SETTING_TAB_RIGHT, setup) + + setup.connect("disable", self, "_on_disabled") + _on_disabled(setup.settings.disable) + +func _on_disabled(value:bool) -> void: + remove_autoload_singleton("Steam") + + if value: + print("disable steam integration") + add_autoload_singleton("Steam", "res://addons/steam_api/steam_i.gd") + else: + print("enable steam integration") + add_autoload_singleton("Steam", "res://addons/steam_api/steam.gd") + +func disable_plugin(): + remove_autoload_singleton("Steam") + remove_control_from_container(CONTAINER_PROJECT_SETTING_TAB_RIGHT, setup) + setup.queue_free() diff --git a/addons/steam_api/readme.md b/addons/steam_api/readme.md new file mode 100644 index 0000000..5428aa6 --- /dev/null +++ b/addons/steam_api/readme.md @@ -0,0 +1,65 @@ +# ๐Ÿš‚ Godot-Steam-API + +> **Godot Steam integration without rebuilding Godot.** Supports ๐Ÿ’ฐWindows, ๐ŸงLinux & ๐ŸMacOS (x86_x64/arm64). + +## ๐Ÿ Getting Started +1. Download this plugin through Godot's asset library https://godotengine.org/asset-library/asset/1020 +2. There should now be a SteamAPI tab in your Project Settings, follow the instructions to configure the SteamAPI for your game *(Takes a few seconds)*. + +Now you can use the following functions: +```gdscript +# check steam integration is working and enabled, useful if you publish to multiple stores +Steam.is_init() + +# achievements +# check if player already unlocked this achievement +var has_alredy_unlocked_achievement:bool = Steam.get_achievement("gator_god") +# give the player this achievement, benign if they already have it +Steam.set_achievement("gator_god") +# clear this achievement (only for debugging) +Steam.clear_achievement("gator_god") + +# leaderboards +Steam.set_leaderboard_score("High Scores", 1000) +# Get the first 10 global high scores +var top_10_global_scores = yield(Steam.get_leaderboard_scores("High Scores", 0, 10), "done") +# Get just the current user's high score +var players_score = yield(Steam.get_leaderboard_scores("High Scores", 0, 0, Steam.LeaderboardDataRequest.GlobalAroundUser), "done") +# Get the current user's high score and the two scores infront and behind +var player_rivals_score = yield(Steam.get_leaderboard_scores("High Scores", -1, 1, Steam.LeaderboardDataRequest.GlobalAroundUser), "done") + +# rich presence +# Read the docs: https://partner.steamgames.com/doc/features/enhancedrichpresence as this call has lots of hidden magic +# and use this tool to test: https://steamcommunity.com/dev/testrichpresence +Steam.friends.set_rich_presence("status", "cactus") +Steam.friends.clear_rich_presence() + +# overlay +Steam.friends.connect("game_overlay_activated", self, "_on_game_overlay_activated") +Steam.friends.activate_game_overlay_to_web_page("https://steamcommunity.com/") +Steam.friends.activate_game_overlay_to_store(1435470, Steam.OverlayToStoreFlag.AddToCart) +``` + +## ๐Ÿ“„ Documentation + +This plugin follows the structure and naming of the offical C++ SteamSDK almost verbatim. +The only major difference is I use snake_case to better integrate with Godot. +That is to say, you can just rely on the offical docs: https://partner.steamgames.com/doc/api. + +## ๐Ÿงช Testing an exported build + +Steam integration doesn't work with exported builds straight away. You'll have to either: +- Upload your build to Steam or +- Create a file in the same directory as your exported build named `steam_appid.txt` with just your steam app id in it. Though do not upload this file as part of your build. + +## ๐Ÿ˜ถโ€๐ŸŒซ๏ธ Can I publish to Itch.io still? + +Yes. There's a switch in `Project Settings > Steam API` that disables the integration, i.e. all calls on the API just do nothing and return/yield null. However it is up to the caller to then safely deal with the null values returned by the API in disabled mode. + + +## ๐Ÿค” Troubleshooting + +- Did you follow the instruction in the project settings Steam tab? +- Is Steam running? +- Have you published your acheivments in the SteamWorks console? *Publishing isn't releasing the game, I mean the button that publishes your Store page.* +- Tried uninstalling and reinstalling the plugin ๐Ÿ˜…? diff --git a/addons/steam_api/settings.gd b/addons/steam_api/settings.gd new file mode 100644 index 0000000..c835fa6 --- /dev/null +++ b/addons/steam_api/settings.gd @@ -0,0 +1,5 @@ +extends Resource + +export(bool) var disable:bool +export(String) var app_id:String +export(String) var sdk_dir:String diff --git a/addons/steam_api/settings.tres b/addons/steam_api/settings.tres new file mode 100644 index 0000000..5eeabb0 --- /dev/null +++ b/addons/steam_api/settings.tres @@ -0,0 +1,9 @@ +[gd_resource type="Resource" load_steps=2 format=2] + +[ext_resource path="res://addons/steam_api/settings.gd" type="Script" id=1] + +[resource] +script = ExtResource( 1 ) +disable = false +app_id = "1993830" +sdk_dir = "C:\\Apps\\steamworks_sdk_153a\\sdk" diff --git a/addons/steam_api/setup.gd b/addons/steam_api/setup.gd new file mode 100644 index 0000000..e6b6dfa --- /dev/null +++ b/addons/steam_api/setup.gd @@ -0,0 +1,95 @@ +tool +extends Control + +signal disable + +var settings +var settings_path = "res://addons/steam_api/settings.tres" + +func _ready(): + settings = load(settings_path) + $panel/container/app_id/input.text = settings.app_id + $panel/container/sdk_dir/input.text = settings.sdk_dir + +func copy_file_from_steam_sdk_(base_path:String, file_name:String) -> void: + print(settings.sdk_dir + base_path + file_name) + if not File.new().file_exists("res://addons/steam_api/%s" % file_name): + var res := Directory.new().copy(settings.sdk_dir + base_path + file_name, "res://addons/steam_api/%s" % file_name) + if res != OK: + popup_error("Failed to copy %s. Steam integration will not work. Did you set the 'SDK Directory' correctly?" % file_name) + +static func exe_path_() -> String: + return OS.get_executable_path().get_base_dir() + +func write_steam_app_id_file_() -> void: + var f := File.new() + var res := f.open(exe_path_() + "/steam_appid.txt", File.WRITE) + if res == OK: + f.store_string(str(settings.app_id)) + f.close() + else: + popup_error("Failed to write steamapp_id.txt to \"{exe_path}\". Steam integration will not work. For debug builds, steam needs a text file with just your steam_app_id named 'steam_appid.txt' next to your exe in '{exe_path}'. Try write the file manually.".format({"exe_path": exe_path_()})) + +func steam_appid_file_has_correct_id_() -> bool: + var file := File.new() + var res := file.open(exe_path_() + "/steam_appid.txt", File.READ) + if res == OK: + return str(settings.app_id) == file.get_line() + else: + return false + +func debug_setup_() -> void: + copy_file_from_steam_sdk_("/redistributable_bin/linux64/", "libsteam_api.so") + copy_file_from_steam_sdk_("/redistributable_bin/win64/", "steam_api64.dll") + copy_file_from_steam_sdk_("/redistributable_bin/osx/", "libsteam_api.dylib") + + if not steam_appid_file_has_correct_id_(): + write_steam_app_id_file_() + +func _on_SDK_instructions_meta_clicked(meta): + if meta == "open_sdk_page": + OS.shell_open("https://partner.steamgames.com/downloads/steamworks_sdk.zip") + +func popup_error(message): + $panel/warning.popup_centered_clamped(Vector2(400, 100)) + $panel/warning/container/error_text.text = message + push_error(message) + +func _on_FindSDK_pressed(): + _ready() + debug_setup_() + check_status() + +func _on_SteamAPI_visibility_changed(): + check_status() + +func check_status(): + var file = File.new() + var status = "[color=green]Done![/color]" + var missing = [] + if not file.file_exists("res://addons/steam_api/libsteam_api.so"): missing.push_back("linux") + if not file.file_exists("res://addons/steam_api/steam_api64.dll"): missing.push_back("windows") + if not file.file_exists("res://addons/steam_api/libsteam_api.dylib"): missing.push_back("osx") + if missing.size() > 0: + if missing.size() == 3: + status = "[color=red]No files imported[/color]" + else: + status = "[color=#ffa500]Partially complete, missing: %s[/color]" % String(missing) + status = "[center]%s[/center]" % status + $panel/container/sdk_status/status.bbcode_text = status + +func _on_app_id_focus_exited(): + save_settings() + +func _on_sdk_dir_focus_exited(): + save_settings() + +func save_settings(): + settings.app_id = $panel/container/app_id/input.text + settings.sdk_dir = $panel/container/sdk_dir/input.text + ResourceSaver.save(settings_path, settings) + +func _on_disable_pressed(): + settings.disable = $panel/container/disable/input.pressed + save_settings() + emit_signal("disable", settings.disable) diff --git a/addons/steam_api/setup.tscn b/addons/steam_api/setup.tscn new file mode 100644 index 0000000..2b6a62f --- /dev/null +++ b/addons/steam_api/setup.tscn @@ -0,0 +1,213 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://addons/steam_api/setup.gd" type="Script" id=1] + +[node name="SteamAPI" type="ScrollContainer"] +anchor_right = 1.0 +anchor_bottom = 1.0 +script = ExtResource( 1 ) +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="panel" type="MarginContainer" parent="."] +margin_right = 1024.0 +margin_bottom = 600.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="Label" type="Label" parent="panel"] +margin_top = 293.0 +margin_right = 1024.0 +margin_bottom = 307.0 + +[node name="container" type="VBoxContainer" parent="panel"] +margin_right = 1024.0 +margin_bottom = 600.0 + +[node name="SDK instructions" type="RichTextLabel" parent="panel/container"] +margin_right = 1024.0 +margin_bottom = 135.0 +rect_clip_content = false +bbcode_enabled = true +bbcode_text = "[color=red]This plugin requires the Steam SDK, you must already be a Steam partner to obtain the SDK (Valve's rules, not mine sorry).[/color] + +[b]Setup:[/b] +1. Enter your Steam App ID [color=gray](the number next to your Steam title on the Steamworks page or the number in the store page url)[/color]. +2. Download the Steam SDK at [url=open_sdk_page]https://partner.steamgames.com/downloads/steamworks_sdk.zip[/url]. +3. Unzip the Steam SDK into any directory on your computer [color=gray](you will not need the SDK once setup is complete)[/color]. +4. Enter the directory path where you unzipped the Steam SDK [color=gray](e.g. \"C:\\Users\\gaben\\Downloads\\sdk\")[/color]. +5. Click \"Import from SDK Directory\". +" +text = "This plugin requires the Steam SDK, you must already be a Steam partner to obtain the SDK (Valve's rules, not mine sorry). + +Setup: +1. Enter your Steam App ID (the number next to your Steam title on the Steamworks page or the number in the store page url). +2. Download the Steam SDK at https://partner.steamgames.com/downloads/steamworks_sdk.zip. +3. Unzip the Steam SDK into any directory on your computer (you will not need the SDK once setup is complete). +4. Enter the directory path where you unzipped the Steam SDK (e.g. \"C:\\Users\\gaben\\Downloads\\sdk\"). +5. Click \"Import from SDK Directory\". +" +fit_content_height = true +scroll_active = false + +[node name="disable" type="HBoxContainer" parent="panel/container"] +margin_top = 139.0 +margin_right = 1024.0 +margin_bottom = 163.0 +size_flags_horizontal = 3 + +[node name="title" type="Label" parent="panel/container/disable"] +margin_right = 510.0 +margin_bottom = 24.0 +rect_min_size = Vector2( 0, 24 ) +size_flags_horizontal = 3 +text = "Disable Steam Integration (all Steam calls will return or yield null)" +valign = 1 + +[node name="input" type="CheckBox" parent="panel/container/disable"] +margin_left = 514.0 +margin_right = 1024.0 +margin_bottom = 24.0 +size_flags_horizontal = 3 + +[node name="app_id" type="HBoxContainer" parent="panel/container"] +margin_top = 167.0 +margin_right = 1024.0 +margin_bottom = 191.0 +size_flags_horizontal = 3 + +[node name="title" type="Label" parent="panel/container/app_id"] +margin_right = 510.0 +margin_bottom = 24.0 +rect_min_size = Vector2( 0, 24 ) +size_flags_horizontal = 3 +text = "Steam App ID (must be a valid id in your steam library)" +valign = 1 + +[node name="input" type="LineEdit" parent="panel/container/app_id"] +margin_left = 514.0 +margin_right = 1024.0 +margin_bottom = 24.0 +size_flags_horizontal = 3 +text = "1435470" +placeholder_text = "1435470" +caret_blink = true +caret_blink_speed = 0.5 + +[node name="sdk_dir" type="HBoxContainer" parent="panel/container"] +margin_top = 195.0 +margin_right = 1024.0 +margin_bottom = 219.0 +size_flags_horizontal = 3 + +[node name="title" type="Label" parent="panel/container/sdk_dir"] +margin_right = 510.0 +margin_bottom = 24.0 +rect_min_size = Vector2( 0, 24 ) +size_flags_horizontal = 3 +text = "SDK Directory" +valign = 1 + +[node name="input" type="LineEdit" parent="panel/container/sdk_dir"] +margin_left = 514.0 +margin_right = 1024.0 +margin_bottom = 24.0 +size_flags_horizontal = 3 +text = "/home/sam/work/godot-steam-api/lib/steam-sdk" +placeholder_text = "C:\\Users\\gaben\\Downloads\\sdk" +caret_blink = true +caret_blink_speed = 0.5 + +[node name="HSeparator" type="HSeparator" parent="panel/container"] +margin_top = 223.0 +margin_right = 1024.0 +margin_bottom = 227.0 + +[node name="sdk_status" type="HBoxContainer" parent="panel/container"] +margin_top = 231.0 +margin_right = 1024.0 +margin_bottom = 255.0 + +[node name="FindSDK" type="Button" parent="panel/container/sdk_status"] +margin_right = 482.0 +margin_bottom = 24.0 +size_flags_horizontal = 3 +text = "Import from SDK Directory" + +[node name="VSeparator" type="VSeparator" parent="panel/container/sdk_status"] +margin_left = 486.0 +margin_right = 490.0 +margin_bottom = 24.0 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="title" type="Label" parent="panel/container/sdk_status"] +margin_left = 494.0 +margin_right = 537.0 +margin_bottom = 24.0 +rect_min_size = Vector2( 0, 24 ) +text = "Status:" +valign = 1 + +[node name="status" type="RichTextLabel" parent="panel/container/sdk_status"] +margin_left = 541.0 +margin_right = 1024.0 +margin_bottom = 24.0 +rect_min_size = Vector2( 0, 24 ) +size_flags_horizontal = 3 +bbcode_enabled = true +bbcode_text = "[center][color=red]No files imported[/color][/center]" +text = "No files imported" +fit_content_height = true +scroll_active = false + +[node name="warning" type="PopupDialog" parent="panel"] +margin_right = 1024.0 +margin_bottom = 600.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 + +[node name="container" type="VBoxContainer" parent="panel/warning"] +anchor_right = 1.0 +anchor_bottom = 1.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Label" type="RichTextLabel" parent="panel/warning/container"] +margin_right = 1024.0 +margin_bottom = 15.0 +size_flags_horizontal = 3 +size_flags_vertical = 4 +bbcode_enabled = true +bbcode_text = "[center][b]Error[/b][/center]" +text = "Error" +fit_content_height = true +scroll_active = false +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="error_text" type="Label" parent="panel/warning/container"] +margin_top = 19.0 +margin_right = 1024.0 +margin_bottom = 600.0 +size_flags_horizontal = 3 +size_flags_vertical = 3 +align = 1 +valign = 1 +autowrap = true +__meta__ = { +"_edit_use_anchors_": false +} + +[connection signal="visibility_changed" from="." to="." method="_on_SteamAPI_visibility_changed"] +[connection signal="meta_clicked" from="panel/container/SDK instructions" to="." method="_on_SDK_instructions_meta_clicked"] +[connection signal="pressed" from="panel/container/disable/input" to="." method="_on_disable_pressed"] +[connection signal="focus_exited" from="panel/container/app_id/input" to="." method="_on_app_id_focus_exited"] +[connection signal="focus_exited" from="panel/container/sdk_dir/input" to="." method="_on_sdk_dir_focus_exited"] +[connection signal="pressed" from="panel/container/sdk_status/FindSDK" to="." method="_on_FindSDK_pressed"] diff --git a/addons/steam_api/steam.gd b/addons/steam_api/steam.gd new file mode 100644 index 0000000..bb5e09f --- /dev/null +++ b/addons/steam_api/steam.gd @@ -0,0 +1,22 @@ +extends SteamI + +func _ready() -> void: + var settings = load("res://addons/steam_api/settings.tres") + api_ = SteamAPI.new() + if not api_ or not api_.init(): + api_ = null + push_error("Failed to init steam api. Is Steam running? Is the plugin activated? Did you do the setup in ProjectSettings > SteamAPI") + if OS.has_feature("standalone"): + push_error("You're running an exported build. Steam integration will only work if the game is launched by Steam. Either upload this build to Steam or create a file in the same directory as your exported build named 'steamapp_id.txt' with just your steam app id in it.") + + return + + user = User.new(SteamUser.new()) + user_stats = UserStats.new(SteamUserStats.new()) + user_stats.request_current_stats() + friends = Friends.new(SteamFriends.new()) + +#warning-ignore:unused_argument +func _process(delta:float) -> void: + if api_: + api_.run_callbacks() diff --git a/addons/steam_api/steam_api.gdnlib b/addons/steam_api/steam_api.gdnlib new file mode 100644 index 0000000..fff58ae --- /dev/null +++ b/addons/steam_api/steam_api.gdnlib @@ -0,0 +1,20 @@ +[general] + +singleton=false +load_once=true +symbol_prefix="godot_" +reloadable=true + +[entry] + +OSX.64="res://addons/steam_api/libsteamsdk-godot.dylib" +Windows.64="res://addons/steam_api/steamsdk-godot.dll" +X11.64="res://addons/steam_api/libsteamsdk-godot.so" +Server="res://addons/steam_api/libsteamsdk-godot.so" + +[dependencies] + +OSX.64=[ "res://addons/steam_api/libsteam_api.dylib" ] +Windows.64=[ "res://addons/steam_api/steam_api64.dll" ] +X11.64=[ "res://addons/steam_api/libsteam_api.so" ] +Server=[ "res://addons/steam_api/libsteam_api.so" ] diff --git a/addons/steam_api/steam_api.gdns b/addons/steam_api/steam_api.gdns new file mode 100644 index 0000000..d690cab --- /dev/null +++ b/addons/steam_api/steam_api.gdns @@ -0,0 +1,9 @@ +[gd_resource type="NativeScript" load_steps=2 format=2] + +[ext_resource path="res://addons/steam_api/steam_api.gdnlib" type="GDNativeLibrary" id=1] + +[resource] +resource_name = "steam_api" +class_name = "SteamAPI" +library = ExtResource( 1 ) +script_class_name = "SteamAPI" diff --git a/addons/steam_api/steam_api64.dll b/addons/steam_api/steam_api64.dll new file mode 100644 index 0000000..e1ca692 Binary files /dev/null and b/addons/steam_api/steam_api64.dll differ diff --git a/addons/steam_api/steam_callback.gdns b/addons/steam_api/steam_callback.gdns new file mode 100644 index 0000000..59987e7 --- /dev/null +++ b/addons/steam_api/steam_callback.gdns @@ -0,0 +1,9 @@ +[gd_resource type="NativeScript" load_steps=2 format=2] + +[ext_resource path="res://addons/steam_api/steam_api.gdnlib" type="GDNativeLibrary" id=1] + +[resource] +resource_name = "steam_callback" +class_name = "SteamCallback" +library = ExtResource( 1 ) +script_class_name = "SteamCallback" diff --git a/addons/steam_api/steam_find_leaderboard_result.gdns b/addons/steam_api/steam_find_leaderboard_result.gdns new file mode 100644 index 0000000..649a6b2 --- /dev/null +++ b/addons/steam_api/steam_find_leaderboard_result.gdns @@ -0,0 +1,9 @@ +[gd_resource type="NativeScript" load_steps=2 format=2] + +[ext_resource path="res://addons/steam_api/steam_api.gdnlib" type="GDNativeLibrary" id=1] + +[resource] +resource_name = "steam_find_leaderboard_result" +class_name = "SteamLeaderboardFindResult" +library = ExtResource( 1 ) +script_class_name = "SteamLeaderboardFindResult" diff --git a/addons/steam_api/steam_friends.gdns b/addons/steam_api/steam_friends.gdns new file mode 100644 index 0000000..a8b9d23 --- /dev/null +++ b/addons/steam_api/steam_friends.gdns @@ -0,0 +1,10 @@ +[gd_resource type="NativeScript" load_steps=2 format=2] + +[ext_resource path="res://addons/steam_api/steam_api.gdnlib" type="GDNativeLibrary" id=1] + +[resource] +resource_name = "steam_friends" +class_name = "SteamFriends" +library = ExtResource( 1 ) +script_class_name = "SteamFriends" + diff --git a/addons/steam_api/steam_i.gd b/addons/steam_api/steam_i.gd new file mode 100644 index 0000000..13ff9f9 --- /dev/null +++ b/addons/steam_api/steam_i.gd @@ -0,0 +1,230 @@ +extends Node +class_name SteamI + +signal leaderboard_scores + +enum LeaderboardUploadScoreMethod { + None = 0, + KeepBest = 1, + ForceUpdate = 2, +} + +enum LeaderboardDataRequest { + Global = 0, + GlobalAroundUser = 1, + Friends = 2, + Users = 3 +} + +enum OverlayToStoreFlag { + None = 0, + AddToCart = 1, + AddToCartAndShow = 2 +} + +enum LeaderboardSortMethod { + None = 0, + Ascending = 1, + Descending = 2 +} + +enum LeaderboardDisplayType { + None = 0, + Numeric = 1, + TimeSeconds = 2, + TimeMilliSeconds = 3 +} + +class Callback: + signal done + +class Proxy_: + signal keep_alive_hack_ + + var object_to_proxy_ + + func _init(object_to_proxy) -> void: + object_to_proxy_ = object_to_proxy + + func call_(func_name, args := []): + if not object_to_proxy_: + return + + return object_to_proxy_.callv(func_name, args) + + func callback_(func_name, args := []): + if not object_to_proxy_: + var result = Callback.new() + result.call_deferred("emit_signal", "done", null) + call_deferred("emit_signal", "keep_alive_hack_", result) + return result + + return object_to_proxy_.callv(func_name, args) + +class User extends Proxy_: + func _init(o).(o) -> void: + pass + + func get_steam_id() -> SteamId: + return call_("get_steam_id") + +class UserStats extends Proxy_: + func _init(o).(o) -> void: + pass + + func set_achievement(achievement_api_name:String) -> bool: + return call_("set_achievement", [achievement_api_name]) + + func clear_achievement(achievement_api_name:String) -> bool: + return call_("clear_achievement", [achievement_api_name]) + + func get_achievement(achievement_api_name:String) -> bool: + return call_("get_achievement", [achievement_api_name]) + + func get_num_achievements() -> int: + return call_("get_num_achievements") + + func get_achievement_name(idx:int) -> String: + return call_("get_achievement_name", [idx]) + + func request_current_stats() -> bool: + return call_("request_current_stats") + + func store_stats() -> bool: + return call_("store_stats") + + func find_leaderboard(leaderboard_name:String): + return callback_("find_leaderboard", [leaderboard_name]) + + func find_or_create_leaderboard(leaderboard_name:String, sort_method:int, display_type:int): + return callback_("find_or_create_leaderboard", [leaderboard_name, sort_method, display_type]) + + func upload_leaderboard_score(leaderboard, method:int, score:int, details:PoolIntArray): + return callback_("upload_leaderboard_score", [leaderboard, method, score, details]) + + func download_leaderboard_entries(leaderboard, data_request:int, begin:int, end:int): + return callback_("download_leaderboard_entries", [leaderboard, data_request, begin, end]) + + func get_downloaded_leaderboard_entry(leaderboard, index:int, max_details:int): + return callback_("get_downloaded_leaderboard_entry", [leaderboard, index, max_details]) + +class Friends extends Proxy_: + signal game_overlay_activated + + func _init(o).(o) -> void: + if o: + o.connect("game_overlay_activated", self, "_on_game_overlay_activated") + + func _on_game_overlay_activated(active:bool) -> void: + emit_signal("game_overlay_activated", active) + + func request_user_information(steam_user_id, require_name_only:bool) -> bool: + return call_("request_user_information", [steam_user_id, require_name_only]) + + func get_friend_persona_name(steam_user_id) -> String: + return call_("get_friend_persona_name", [steam_user_id]) + + func activate_game_overlay_to_web_page(url:String) -> void: + call_("activate_game_overlay_to_web_page", [url]) + + func activate_game_overlay_to_store(app_id:int, where:int) -> void: + call_("activate_game_overlay_to_store", [app_id, where]) + + func set_rich_presence(key:String, value:String) -> bool: + return call_("set_rich_presence", [key, value]) + + func clear_rich_presence() -> void: + call_("clear_rich_presence") + +var api_ +var user:User = User.new(null) +var user_stats:UserStats = UserStats.new(null) +var friends:Friends = Friends.new(null) + +func is_init() -> bool: + if api_: + return api_.is_init() + return false + +func set_achievement(name:String) -> void: + user_stats.set_achievement(name) + user_stats.store_stats() + +func get_achievement(name:String) -> bool: + return user_stats.get_achievement(name) + +func clear_achievement(name:String) -> void: + user_stats.clear_achievement(name) + user_stats.store_stats() + +func set_leaderboard_score(leaderboard_name:String, score:int, method:int = LeaderboardUploadScoreMethod.KeepBest, details:PoolIntArray = PoolIntArray()) -> void: + var find_leaderboard_result = yield(user_stats.find_leaderboard(leaderboard_name), "done") + if not find_leaderboard_result: + return + + if not find_leaderboard_result.get_leaderboard_found(): + return + + var leaderboard = find_leaderboard_result.get_leaderboard() + if not leaderboard: + return + + yield(user_stats.upload_leaderboard_score(leaderboard, method, score, details), "done") + +func get_leaderboard_scores(leaderboard_name:String, begin:int, end:int, method:int = LeaderboardDataRequest.Global, max_details:int = 0): + var callback := Callback.new() + get_leaderboard_scores_(leaderboard_name, begin, end, method, max_details, callback) + return callback + +func get_leaderboard_scores_(leaderboard_name:String, begin:int, end:int, method:int, max_details, callback:Callback) -> Callback: + var res := [] + + var user_stats_ = user_stats.object_to_proxy_ + var friends_ = friends.object_to_proxy_ + + if not user_stats_: + return callback.emit_signal("done", res) + + if not friends_: + return callback.emit_signal("done", res) + + var find_leaderboard_result = yield(user_stats_.find_leaderboard(leaderboard_name), "done") + if not find_leaderboard_result.get_leaderboard_found(): + return callback.emit_signal("done", res) + + var leaderboard = find_leaderboard_result.get_leaderboard() + if not leaderboard: + return callback.emit_signal("done", res) + + var download = yield(user_stats_.download_leaderboard_entries(leaderboard, method, begin, end), "done") + if not download: + return callback.emit_signal("done", res) + + var entries = download.get_entries() + if not entries: + return callback.emit_signal("done", res) + + for i in download.get_entry_count(): + var entry = user_stats_.get_downloaded_leaderboard_entry(entries, i, max_details) + if not entry: + continue + + var score := {} + score["global_rank"] = entry.get_global_rank() + score["score"] = entry.get_score() + score["details"] = entry.get_details() + + if not friends_.request_user_information(entry.get_steam_id_user(), true): + score["persona_name"] = friends_.get_friend_persona_name(entry.get_steam_id_user()) + else: + score["persona_name"] = "Frank" + + res.push_back(score) + + return callback.emit_signal("done", res) + +func activate_game_overlay_to_web_page(url:String) -> void: + friends.activate_game_overlay_to_web_page(url) + +func activate_game_overlay_to_store(app_id:int, where:int = 0) -> void: + friends.activate_game_overlay_to_store(app_id, where) diff --git a/addons/steam_api/steam_id.gdns b/addons/steam_api/steam_id.gdns new file mode 100644 index 0000000..31ddc77 --- /dev/null +++ b/addons/steam_api/steam_id.gdns @@ -0,0 +1,10 @@ +[gd_resource type="NativeScript" load_steps=2 format=2] + +[ext_resource path="res://addons/steam_api/steam_api.gdnlib" type="GDNativeLibrary" id=1] + +[resource] +resource_name = "steam_id" +class_name = "SteamId" +library = ExtResource( 1 ) +script_class_name = "SteamId" + diff --git a/addons/steam_api/steam_leaderboard.gdns b/addons/steam_api/steam_leaderboard.gdns new file mode 100644 index 0000000..12b1eb9 --- /dev/null +++ b/addons/steam_api/steam_leaderboard.gdns @@ -0,0 +1,10 @@ +[gd_resource type="NativeScript" load_steps=2 format=2] + +[ext_resource path="res://addons/steam_api/steam_api.gdnlib" type="GDNativeLibrary" id=1] + +[resource] +resource_name = "steam_leaderboard" +class_name = "SteamLeaderboard" +library = ExtResource( 1 ) +script_class_name = "SteamLeaderboard" + diff --git a/addons/steam_api/steam_leaderboard_entries.gdns b/addons/steam_api/steam_leaderboard_entries.gdns new file mode 100644 index 0000000..1a8164f --- /dev/null +++ b/addons/steam_api/steam_leaderboard_entries.gdns @@ -0,0 +1,10 @@ +[gd_resource type="NativeScript" load_steps=2 format=2] + +[ext_resource path="res://addons/steam_api/steam_api.gdnlib" type="GDNativeLibrary" id=1] + +[resource] +resource_name = "steam_leaderboard_entries" +class_name = "SteamLeaderboardEntries" +library = ExtResource( 1 ) +script_class_name = "SteamLeaderboardEntries" + diff --git a/addons/steam_api/steam_leaderboard_entry.gdns b/addons/steam_api/steam_leaderboard_entry.gdns new file mode 100644 index 0000000..04ef859 --- /dev/null +++ b/addons/steam_api/steam_leaderboard_entry.gdns @@ -0,0 +1,10 @@ +[gd_resource type="NativeScript" load_steps=2 format=2] + +[ext_resource path="res://addons/steam_api/steam_api.gdnlib" type="GDNativeLibrary" id=1] + +[resource] +resource_name = "steam_leaderboard_entry" +class_name = "SteamLeaderboardEntry" +library = ExtResource( 1 ) +script_class_name = "SteamLeaderboardEntry" + diff --git a/addons/steam_api/steam_leaderboard_scores_downloaded.gdns b/addons/steam_api/steam_leaderboard_scores_downloaded.gdns new file mode 100644 index 0000000..6740054 --- /dev/null +++ b/addons/steam_api/steam_leaderboard_scores_downloaded.gdns @@ -0,0 +1,9 @@ +[gd_resource type="NativeScript" load_steps=2 format=2] + +[ext_resource path="res://addons/steam_api/steam_api.gdnlib" type="GDNativeLibrary" id=1] + +[resource] +resource_name = "steam_leaderboard_scores_downloaded" +class_name = "SteamLeaderboardScoresDownloaded" +library = ExtResource( 1 ) +script_class_name = "SteamLeaderboardScoresDownloaded" diff --git a/addons/steam_api/steam_leaderboard_uploaded.gdns b/addons/steam_api/steam_leaderboard_uploaded.gdns new file mode 100644 index 0000000..26d4626 --- /dev/null +++ b/addons/steam_api/steam_leaderboard_uploaded.gdns @@ -0,0 +1,9 @@ +[gd_resource type="NativeScript" load_steps=2 format=2] + +[ext_resource path="res://addons/steam_api/steam_api.gdnlib" type="GDNativeLibrary" id=1] + +[resource] +resource_name = "steam_leaderboard_score_uploaded" +class_name = "SteamLeaderboardScoreUploaded" +library = ExtResource( 1 ) +script_class_name = "SteamLeaderboardScoreUploaded" diff --git a/addons/steam_api/steam_user.gdns b/addons/steam_api/steam_user.gdns new file mode 100644 index 0000000..82b7d9c --- /dev/null +++ b/addons/steam_api/steam_user.gdns @@ -0,0 +1,10 @@ +[gd_resource type="NativeScript" load_steps=2 format=2] + +[ext_resource path="res://addons/steam_api/steam_api.gdnlib" type="GDNativeLibrary" id=1] + +[resource] +resource_name = "steam_user" +class_name = "SteamUser" +library = ExtResource( 1 ) +script_class_name = "SteamUser" + diff --git a/addons/steam_api/steam_user_stats.gdns b/addons/steam_api/steam_user_stats.gdns new file mode 100644 index 0000000..ca17d73 --- /dev/null +++ b/addons/steam_api/steam_user_stats.gdns @@ -0,0 +1,9 @@ +[gd_resource type="NativeScript" load_steps=2 format=2] + +[ext_resource path="res://addons/steam_api/steam_api.gdnlib" type="GDNativeLibrary" id=1] + +[resource] +resource_name = "steam_user_stats" +class_name = "SteamUserStats" +library = ExtResource( 1 ) +script_class_name = "SteamUserStats" diff --git a/addons/steam_api/steamsdk-godot.dll b/addons/steam_api/steamsdk-godot.dll new file mode 100644 index 0000000..762bf55 Binary files /dev/null and b/addons/steam_api/steamsdk-godot.dll differ diff --git a/project.godot b/project.godot index 4ca04d2..9767ea0 100644 --- a/project.godot +++ b/project.godot @@ -48,6 +48,71 @@ _global_script_classes=[ { "class": "SaveDict", "language": "GDScript", "path": "res://src/class/SaveDict.gd" +}, { +"base": "", +"class": "SteamAPI", +"language": "NativeScript", +"path": "res://addons/steam_api/steam_api.gdns" +}, { +"base": "", +"class": "SteamCallback", +"language": "NativeScript", +"path": "res://addons/steam_api/steam_callback.gdns" +}, { +"base": "", +"class": "SteamFriends", +"language": "NativeScript", +"path": "res://addons/steam_api/steam_friends.gdns" +}, { +"base": "Node", +"class": "SteamI", +"language": "GDScript", +"path": "res://addons/steam_api/steam_i.gd" +}, { +"base": "", +"class": "SteamId", +"language": "NativeScript", +"path": "res://addons/steam_api/steam_id.gdns" +}, { +"base": "", +"class": "SteamLeaderboard", +"language": "NativeScript", +"path": "res://addons/steam_api/steam_leaderboard.gdns" +}, { +"base": "", +"class": "SteamLeaderboardEntries", +"language": "NativeScript", +"path": "res://addons/steam_api/steam_leaderboard_entries.gdns" +}, { +"base": "", +"class": "SteamLeaderboardEntry", +"language": "NativeScript", +"path": "res://addons/steam_api/steam_leaderboard_entry.gdns" +}, { +"base": "", +"class": "SteamLeaderboardFindResult", +"language": "NativeScript", +"path": "res://addons/steam_api/steam_find_leaderboard_result.gdns" +}, { +"base": "", +"class": "SteamLeaderboardScoreUploaded", +"language": "NativeScript", +"path": "res://addons/steam_api/steam_leaderboard_uploaded.gdns" +}, { +"base": "", +"class": "SteamLeaderboardScoresDownloaded", +"language": "NativeScript", +"path": "res://addons/steam_api/steam_leaderboard_scores_downloaded.gdns" +}, { +"base": "", +"class": "SteamUser", +"language": "NativeScript", +"path": "res://addons/steam_api/steam_user.gdns" +}, { +"base": "", +"class": "SteamUserStats", +"language": "NativeScript", +"path": "res://addons/steam_api/steam_user_stats.gdns" } ] _global_script_class_icons={ "Box": "", @@ -57,7 +122,20 @@ _global_script_class_icons={ "OnOff": "", "PaletteSwap": "", "Player": "", -"SaveDict": "" +"SaveDict": "", +"SteamAPI": "", +"SteamCallback": "", +"SteamFriends": "", +"SteamI": "", +"SteamId": "", +"SteamLeaderboard": "", +"SteamLeaderboardEntries": "", +"SteamLeaderboardEntry": "", +"SteamLeaderboardFindResult": "", +"SteamLeaderboardScoreUploaded": "", +"SteamLeaderboardScoresDownloaded": "", +"SteamUser": "", +"SteamUserStats": "" } [application] @@ -89,6 +167,7 @@ MenuOptions="*res://src/menu/options/MenuOptions.tscn" MenuRemap="*res://src/menu/options/MenuRemap.tscn" Music="*res://src/autoload/Music.tscn" Audio="*res://src/autoload/Audio.tscn" +Steam="*res://addons/steam_api/steam.gd" [display] @@ -98,6 +177,10 @@ window/size/fullscreen=true window/stretch/mode="2d" window/stretch/aspect="keep" +[editor_plugins] + +enabled=PoolStringArray( "res://addons/steam_api/plugin.cfg" ) + [input] ui_accept={ diff --git a/src/autoload/Shared.gd b/src/autoload/Shared.gd index bb3a1a3..c0e13ec 100644 --- a/src/autoload/Shared.gd +++ b/src/autoload/Shared.gd @@ -130,10 +130,9 @@ func change_scene(): Cam.reset_zoom() BG.set_colors(0) - emit_signal("scene_changed") - save_data() + try_achievement() func toggle_fullscreen(): OS.window_fullscreen = !OS.window_fullscreen @@ -396,3 +395,38 @@ func load_keys(path := "user://keys.tres"): for e in r.dict[a]: InputMap.action_add_event(a, e) + +### Steam ### + +func try_achievement(): + if !Steam.is_init(): return + + var map := "" + + match csfn: + "res://src/map/worlds/1/0_hub.tscn": + map = "grass1" + "res://src/map/worlds/2/0_hub.tscn": + map = "stone1" + "res://src/map/worlds/3/0_hub.tscn": + map = "cacti1" + "res://src/map/worlds/2A/0_hub.tscn": + map = "snow1" + + "res://src/map/worlds/3A/0_hub.tscn": + map = "grass2" + "res://src/map/worlds/2B/0_hub.tscn": + map = "stone2" + "res://src/map/worlds/3B/0_hub.tscn": + map = "cacti2" + "res://src/map/worlds/2C/0_hub.tscn": + map = "snow2" + + if map != "": + Steam.set_achievement(map) + + if gem_count > 49: + Steam.set_achievement("gem50") + + if csfn == "res://src/menu/Ending.tscn" and save_time < 3600: + Steam.set_achievement("speedrun")