| … | … | @@ -28,11 +28,13 @@ A Godot plugin and example project for loading assets from the [hypercore-protoc |
| 28 | 28 | - [x] Refactor chat system to be more general purpose (not just texts) |
| 29 | 29 | - [x] Basic demo moving blobs around on a map |
| 30 | 30 | - [x] Place usernames in a hyperdrive's json file along with an image for your blob |
| 31 | - [ ] Create custom maps from an image and a JSON file | |
| 31 | - [x] Create custom maps from an image and a JSON file | |
| 32 | 32 | - [ ] Auto-discover which maps are active |
| 33 | 33 | - [ ] Enable code to work in Godot Web in Agregore |
| 34 | 34 | - [ ] Use new [JavaScriptObject](https://godotengine.org/article/godot-web-progress-report-9) API from Godot 4 |
| 35 | 35 | - [ ] Enable code to work in Godot Web in regular browsers |
| 36 | - [ ] Update to latest hypercore protocol stuff | |
| 37 | - [ ] Add multi-writer for hyperdrives | |
| 36 | 38 | - [ ] Load gltf scene from hyper |
| 37 | 39 | - [ ] Load VRM avatar from hyper (social multiplayer) [VRM stuff](https://github.com/V-Sekai/godot-vrm/blob/godot3/addons/vrm/import_vrm.gd) |
| 38 | 40 | - [ ] Folder sync (From `hyper://` to fs, from fs to `hyper://`) |
| … | … | @@ -39,6 +39,11 @@ _global_script_classes=[ { |
| 39 | 39 | "language": "GDScript", |
| 40 | 40 | "path": "res://src/HyperRequest/HyperUploader.gd" |
| 41 | 41 | }, { |
| 42 | "base": "HyperGossip", | |
| 43 | "class": "ListGossip", | |
| 44 | "language": "GDScript", | |
| 45 | "path": "res://src/multiplayer/ListGossip.gd" | |
| 46 | }, { | |
| 42 | 47 | "base": "Reference", |
| 43 | 48 | "class": "WAT", |
| 44 | 49 | "language": "GDScript", |
| … | … | @@ -56,6 +61,7 @@ _global_script_class_icons={ |
| 56 | 61 | "HyperGossip": "", |
| 57 | 62 | "HyperRequest": "", |
| 58 | 63 | "HyperUploader": "", |
| 64 | "ListGossip": "", | |
| 59 | 65 | "WAT": "", |
| 60 | 66 | "WATTest": "" |
| 61 | 67 | } |
| … | … | @@ -5,7 +5,7 @@ |
| 5 | 5 | [ext_resource path="res://src/MultiplayerDemo/FlyBalls.gd" type="Script" id=3] |
| 6 | 6 | [ext_resource path="res://src/HyperGateway/HyperGateway.gd" type="Script" id=4] |
| 7 | 7 | [ext_resource path="res://src/multiplayer/HyperGossip.gd" type="Script" id=5] |
| 8 | [ext_resource path="res://src/MultiplayerDemo/LevelInfo.tscn" type="PackedScene" id=6] | |
| 8 | [ext_resource path="res://src/multiplayer/InfoLoader.tscn" type="PackedScene" id=6] | |
| 9 | 9 | [ext_resource path="res://src/MultiplayerDemo/background.png" type="Texture" id=7] |
| 10 | 10 | |
| 11 | 11 | [node name="FlyBalls" type="Node2D"] |
| … | … | @@ -26,13 +26,13 @@ script = ExtResource( 2 ) |
| 26 | 26 | |
| 27 | 27 | [node name="HyperGossip" type="Node" parent="."] |
| 28 | 28 | script = ExtResource( 5 ) |
| 29 | extension_name = "flyballs-v1" | |
| 30 | 29 | |
| 31 | 30 | [node name="HyperGateway" type="Node" parent="."] |
| 32 | 31 | script = ExtResource( 4 ) |
| 33 | 32 | autoStart = false |
| 34 | 33 | |
| 35 | 34 | [node name="LevelInfo" parent="." instance=ExtResource( 6 )] |
| 35 | profile_file = "level.json" | |
| 36 | 36 | autoload_image = true |
| 37 | 37 | |
| 38 | 38 | [connection signal="moved" from="Player" to="." method="_on_Player_moved"] |
| … | … | @@ -1,143 +0,0 @@ |
| 1 | extends Node | |
| 2 | ||
| 3 | var URLParser = preload("res://src/utils/URLParser.gd") | |
| 4 | var parser = URLParser.new() | |
| 5 | ||
| 6 | signal info(title, image) | |
| 7 | signal no_info() | |
| 8 | signal image(path) | |
| 9 | signal updated() | |
| 10 | signal resolved(url) | |
| 11 | signal error(err) | |
| 12 | ||
| 13 | export var url = 'hyper://multiplayer-level/' | |
| 14 | export var cache_folder: String = 'user://hyper-file-cache/' | |
| 15 | export var level_file: String = 'level.json' | |
| 16 | ||
| 17 | export var autoload = false | |
| 18 | export var autoload_image = false | |
| 19 | ||
| 20 | const DEFAULT_TITLE = 'New Level' | |
| 21 | ||
| 22 | var info_cache = null | |
| 23 | var has_loaded = false | |
| 24 | var picture_path = '' | |
| 25 | ||
| 26 | func _ready(): | |
| 27 | var dir = Directory.new() | |
| 28 | var err = dir.make_dir_recursive(cache_folder) | |
| 29 | if err != OK: | |
| 30 | printerr("Unable to create cache directory ", err) | |
| 31 | ||
| 32 | print("Level info setup") | |
| 33 | if autoload: | |
| 34 | load_info() | |
| 35 | ||
| 36 | func get_title(): | |
| 37 | if info_cache == null: return DEFAULT_TITLE | |
| 38 | if !info_cache.has('title'): return DEFAULT_TITLE | |
| 39 | return info_cache.title | |
| 40 | ||
| 41 | func set_title(title): | |
| 42 | update_info({ | |
| 43 | "title": title | |
| 44 | }) | |
| 45 | ||
| 46 | func update_info(updates): | |
| 47 | var data = {} | |
| 48 | ||
| 49 | if info_cache != null: | |
| 50 | for key in info_cache.keys(): | |
| 51 | data[key] = info_cache[key] | |
| 52 | ||
| 53 | for key in updates.keys(): | |
| 54 | data[key] = info_cache[key] | |
| 55 | ||
| 56 | var json = JSON.print(data) | |
| 57 | var info_url = url + level_file | |
| 58 | ||
| 59 | $updateInfoRequest.request(info_url, [], true, HTTPClient.METHOD_PUT, json) | |
| 60 | ||
| 61 | func load_info(): | |
| 62 | var info_url = url + level_file | |
| 63 | $loadInfoRequest.request(info_url) | |
| 64 | ||
| 65 | func resolve_url(): | |
| 66 | var url_record = url + '.well-known/hyper' | |
| 67 | $resolveURLRequest.request(url_record) | |
| 68 | ||
| 69 | func download_image(url: String): | |
| 70 | print('Loading image: ', url) | |
| 71 | var file_name = url.get_file() | |
| 72 | var key = parser.parse(url).host | |
| 73 | # Save the picture with the profile key prefixed to it | |
| 74 | picture_path = cache_folder + key + '-' + file_name | |
| 75 | $loadImageRequest.download_file = picture_path | |
| 76 | $loadImageRequest.request(url) | |
| 77 | ||
| 78 | func upload_image(path: String): | |
| 79 | var picture_name = path.get_file() | |
| 80 | var picture_url = url + picture_name | |
| 81 | ||
| 82 | $uploader.upload_file(picture_url, path) | |
| 83 | ||
| 84 | func _on_loadInfoRequest_request_completed(_result, response_code, _headers, body): | |
| 85 | print("Loaded info", _result, " ", response_code) | |
| 86 | if response_code != 200: | |
| 87 | printerr('Cannot load existing level info: ', response_code) | |
| 88 | emit_signal("no_info") | |
| 89 | return | |
| 90 | ||
| 91 | var parsed = JSON.parse(body.get_string_from_utf8()) | |
| 92 | if parsed.error != OK: | |
| 93 | printerr(parsed.error_string) | |
| 94 | emit_signal("no_info") | |
| 95 | return | |
| 96 | ||
| 97 | info_cache = parsed.result | |
| 98 | ||
| 99 | var title = info_cache.title | |
| 100 | var image = info_cache.image | |
| 101 | ||
| 102 | emit_signal("info", title, image) | |
| 103 | ||
| 104 | if image != null and autoload_image: | |
| 105 | download_image(image) | |
| 106 | ||
| 107 | func _on_uploader_uploaded(resolved_url, response_code): | |
| 108 | if response_code != 200: | |
| 109 | printerr('Unable to upload file ') | |
| 110 | return | |
| 111 | ||
| 112 | if !url || (url.length() == 0): | |
| 113 | printerr("Unable to load Link header for image.") | |
| 114 | return | |
| 115 | ||
| 116 | update_info({ | |
| 117 | "image": resolved_url | |
| 118 | }) | |
| 119 | ||
| 120 | download_image(resolved_url) | |
| 121 | ||
| 122 | func _on_loadImageRequest_request_completed(result, response_code, _headers, body): | |
| 123 | if result != OK or response_code != 200: | |
| 124 | printerr('Unable to load image: ', result, ": ", body.get_string_from_utf8()) | |
| 125 | return | |
| 126 | ||
| 127 | print(result, ' Image got downloaded: ', picture_path) | |
| 128 | ||
| 129 | emit_signal('image', picture_path) | |
| 130 | ||
| 131 | func _on_updateInfoRequest_request_completed(_result, _response_code, _headers, _body): | |
| 132 | emit_signal("updated") | |
| 133 | ||
| 134 | func _on_resolveURLRequest_request_completed(result, response_code, _headers, body: PoolByteArray): | |
| 135 | if result != OK or response_code != 200: | |
| 136 | printerr('Unable to load final profile URL') | |
| 137 | return | |
| 138 | ||
| 139 | var lines = body.get_string_from_utf8() | |
| 140 | var url = lines.split('\n')[0] | |
| 141 | if !url.ends_with('/'): | |
| 142 | url += '/' | |
| 143 | emit_signal('resolved', url) |
| … | … | @@ -1,29 +0,0 @@ |
| 1 | [gd_scene load_steps=4 format=2] | |
| 2 | ||
| 3 | [ext_resource path="res://src/MultiplayerDemo/LevelInfo.gd" type="Script" id=1] | |
| 4 | [ext_resource path="res://src/HyperRequest/HyperRequest.gd" type="Script" id=2] | |
| 5 | [ext_resource path="res://src/HyperRequest/HyperUploader.gd" type="Script" id=3] | |
| 6 | ||
| 7 | [node name="LevelInfo" type="Node"] | |
| 8 | script = ExtResource( 1 ) | |
| 9 | ||
| 10 | [node name="loadInfoRequest" type="HTTPRequest" parent="."] | |
| 11 | script = ExtResource( 2 ) | |
| 12 | ||
| 13 | [node name="loadImageRequest" type="HTTPRequest" parent="."] | |
| 14 | script = ExtResource( 2 ) | |
| 15 | ||
| 16 | [node name="updateInfoRequest" type="HTTPRequest" parent="."] | |
| 17 | script = ExtResource( 2 ) | |
| 18 | ||
| 19 | [node name="resolveURLRequest" type="HTTPRequest" parent="."] | |
| 20 | script = ExtResource( 2 ) | |
| 21 | ||
| 22 | [node name="uploader" type="Node" parent="."] | |
| 23 | script = ExtResource( 3 ) | |
| 24 | ||
| 25 | [connection signal="request_completed" from="loadInfoRequest" to="." method="_on_loadInfoRequest_request_completed"] | |
| 26 | [connection signal="request_completed" from="loadImageRequest" to="." method="_on_loadImageRequest_request_completed"] | |
| 27 | [connection signal="request_completed" from="updateInfoRequest" to="." method="_on_updateInfoRequest_request_completed"] | |
| 28 | [connection signal="request_completed" from="resolveURLRequest" to="." method="_on_resolveURLRequest_request_completed"] | |
| 29 | [connection signal="uploaded" from="uploader" to="." method="_on_uploader_uploaded"] |
| … | … | @@ -46,6 +46,9 @@ func _on_HyperGateway_started_gateway(_pid): |
| 46 | 46 | func _on_StartButton_pressed(): |
| 47 | 47 | print("Starting next step") |
| 48 | 48 | show_loader() |
| 49 | $LevelInfo.update({ | |
| 50 | "title": get_title() | |
| 51 | }) | |
| 49 | 52 | $LevelInfo.resolve_url() |
| 50 | 53 | |
| 51 | 54 | func _on_LevelInfo_error(err): |
| … | … | @@ -63,13 +66,13 @@ func _on_LevelInfo_image(path): |
| 63 | 66 | |
| 64 | 67 | $Form/ImageChooser.icon = texture |
| 65 | 68 | |
| 66 | func _on_LevelInfo_info(title: String, image: String): | |
| 67 | print("Loaded level info:", {"title": title, "image": image}) | |
| 69 | func _on_LevelInfo_info(info): | |
| 70 | print("Loaded level info:", info) | |
| 68 | 71 | $Form/StartButton.disabled = false |
| 69 | $Form/TitleInput.text = title | |
| 72 | $Form/TitleInput.text = info.title | |
| 70 | 73 | |
| 71 | if image != null && image.length() != 0: | |
| 72 | load_image(image) | |
| 74 | if info.has('image') && info.image.length() != 0: | |
| 75 | load_image(info.image) | |
| 73 | 76 | else: hide_loader() |
| 74 | 77 | |
| 75 | 78 | func _on_LevelInfo_updated(): |
| … | … | @@ -2,7 +2,7 @@ |
| 2 | 2 | |
| 3 | 3 | [ext_resource path="res://src/MultiplayerDemo/LoadLevel.gd" type="Script" id=1] |
| 4 | 4 | [ext_resource path="res://src/HyperGateway/HyperGateway.gd" type="Script" id=2] |
| 5 | [ext_resource path="res://src/MultiplayerDemo/LevelInfo.tscn" type="PackedScene" id=3] | |
| 5 | [ext_resource path="res://src/multiplayer/InfoLoader.tscn" type="PackedScene" id=4] | |
| 6 | 6 | |
| 7 | 7 | [node name="LoadLevel" type="VBoxContainer"] |
| 8 | 8 | anchor_right = 1.0 |
| … | … | @@ -79,7 +79,12 @@ access = 2 |
| 79 | 79 | current_dir = "/home/mauve/programming/godot/hypergodot" |
| 80 | 80 | current_path = "/home/mauve/programming/godot/hypergodot/" |
| 81 | 81 | |
| 82 | [node name="LevelInfo" parent="." instance=ExtResource( 3 )] | |
| 82 | [node name="LevelInfo" parent="." instance=ExtResource( 4 )] | |
| 83 | url = "hyper://multiplayer-level/" | |
| 84 | profile_file = "level.json" | |
| 85 | defaults = { | |
| 86 | "title": "New Level" | |
| 87 | } | |
| 83 | 88 | |
| 84 | 89 | [connection signal="pressed" from="Form/ImageChooser" to="." method="_on_ImageChooser_pressed"] |
| 85 | 90 | [connection signal="pressed" from="Form/StartButton" to="." method="_on_StartButton_pressed"] |
| … | … | @@ -14,7 +14,7 @@ func _ready(): |
| 14 | 14 | func load_profile(): |
| 15 | 15 | print("Initial profile load") |
| 16 | 16 | $PlayerProfile.url = profile_url |
| 17 | $PlayerProfile.load_profile() | |
| 17 | $PlayerProfile.load_info() | |
| 18 | 18 | |
| 19 | 19 | func hide_loader(): |
| 20 | 20 | $Loader.hide() |
| … | … | @@ -47,6 +47,9 @@ func _on_HyperGateway_started_gateway(_pid): |
| 47 | 47 | func _on_StartButton_pressed(): |
| 48 | 48 | print("Starting next step") |
| 49 | 49 | show_loader() |
| 50 | $PlayerProfile.update_info({ | |
| 51 | "username": get_username() | |
| 52 | }) | |
| 50 | 53 | $PlayerProfile.resolve_url() |
| 51 | 54 | |
| 52 | 55 | func _on_PlayerProfile_error(err): |
| … | … | @@ -64,13 +67,13 @@ func _on_PlayerProfile_image(path): |
| 64 | 67 | |
| 65 | 68 | $Form/ImageChooser.icon = texture |
| 66 | 69 | |
| 67 | func _on_PlayerProfile_info(username: String, image: String): | |
| 68 | print("Loaded player profile:", {"username": username, "image": image}) | |
| 70 | func _on_PlayerProfile_info(info): | |
| 71 | print("Loaded player profile:", info) | |
| 69 | 72 | $Form/StartButton.disabled = false |
| 70 | $Form/UsernameInput.text = username | |
| 73 | $Form/UsernameInput.text = info.username | |
| 71 | 74 | |
| 72 | if image != null && image.length() != 0: | |
| 73 | load_image(image) | |
| 75 | if info.has('image') && info.image.length() != 0: | |
| 76 | load_image(info.image) | |
| 74 | 77 | else: hide_loader() |
| 75 | 78 | |
| 76 | 79 | func _on_PlayerProfile_updated(): |
| … | … | @@ -2,7 +2,7 @@ |
| 2 | 2 | |
| 3 | 3 | [ext_resource path="res://src/MultiplayerDemo/LoadProfile.gd" type="Script" id=1] |
| 4 | 4 | [ext_resource path="res://src/HyperGateway/HyperGateway.gd" type="Script" id=2] |
| 5 | [ext_resource path="res://src/MultiplayerDemo/PlayerProfile.tscn" type="PackedScene" id=3] | |
| 5 | [ext_resource path="res://src/multiplayer/InfoLoader.tscn" type="PackedScene" id=3] | |
| 6 | 6 | |
| 7 | 7 | [node name="LoadProfile" type="VBoxContainer"] |
| 8 | 8 | anchor_right = 1.0 |
| … | … | @@ -67,6 +67,10 @@ script = ExtResource( 2 ) |
| 67 | 67 | autoStart = false |
| 68 | 68 | |
| 69 | 69 | [node name="PlayerProfile" parent="." instance=ExtResource( 3 )] |
| 70 | url = "hyper://flyballs-profile/" | |
| 71 | defaults = { | |
| 72 | "username": "anonymous" | |
| 73 | } | |
| 70 | 74 | |
| 71 | 75 | [node name="FileDialog" type="FileDialog" parent="."] |
| 72 | 76 | margin_top = 536.0 |
| … | … | @@ -87,7 +91,4 @@ current_path = "/home/mauve/programming/godot/hypergodot/" |
| 87 | 91 | [connection signal="error" from="PlayerProfile" to="." method="_on_PlayerProfile_error"] |
| 88 | 92 | [connection signal="image" from="PlayerProfile" to="." method="_on_PlayerProfile_image"] |
| 89 | 93 | [connection signal="info" from="PlayerProfile" to="." method="_on_PlayerProfile_info"] |
| 90 | [connection signal="no_info" from="PlayerProfile" to="." method="_on_PlayerProfile_no_info"] | |
| 91 | [connection signal="resolved" from="PlayerProfile" to="." method="_on_PlayerProfile_resolved"] | |
| 92 | [connection signal="updated" from="PlayerProfile" to="." method="_on_PlayerProfile_updated"] | |
| 93 | 94 | [connection signal="file_selected" from="FileDialog" to="." method="_on_FileDialog_file_selected"] |
| … | … | @@ -10,5 +10,5 @@ func _on_LoadProfile_profile_ready(url): |
| 10 | 10 | game.spawn_gateway = false |
| 11 | 11 | add_child(game) |
| 12 | 12 | |
| 13 | func _on_HyperGateway_started_gateway(pid): | |
| 13 | func _on_HyperGateway_started_gateway(_pid): | |
| 14 | 14 | $LoadProfile.load_profile() |
| … | … | @@ -1,144 +0,0 @@ |
| 1 | extends Node | |
| 2 | ||
| 3 | var URLParser = preload("res://src/utils/URLParser.gd") | |
| 4 | var parser = URLParser.new() | |
| 5 | ||
| 6 | signal info(username, image) | |
| 7 | signal no_info() | |
| 8 | signal image(path) | |
| 9 | signal updated() | |
| 10 | signal resolved(url) | |
| 11 | signal error(err) | |
| 12 | ||
| 13 | export var url = 'hyper://blog.mauve.moe/' | |
| 14 | export var cache_folder: String = 'user://hyper-file-cache/' | |
| 15 | export var profile_file: String = 'profile.json' | |
| 16 | ||
| 17 | export var autoload = false | |
| 18 | export var autoload_image = false | |
| 19 | ||
| 20 | const DEFAULT_USERNAME = 'Anonymous' | |
| 21 | ||
| 22 | var info_cache = null | |
| 23 | var has_loaded = false | |
| 24 | var picture_path = '' | |
| 25 | ||
| 26 | func _ready(): | |
| 27 | var dir = Directory.new() | |
| 28 | var err = dir.make_dir_recursive(cache_folder) | |
| 29 | if err != OK: | |
| 30 | printerr("Unable to create cache directory ", err) | |
| 31 | ||
| 32 | print("Profile setup") | |
| 33 | if autoload: | |
| 34 | load_profile() | |
| 35 | ||
| 36 | func get_username(): | |
| 37 | if info_cache == null: return DEFAULT_USERNAME | |
| 38 | if !info_cache.has('username'): return DEFAULT_USERNAME | |
| 39 | return info_cache.username | |
| 40 | ||
| 41 | func set_username(username): | |
| 42 | update_info({ | |
| 43 | "username": username | |
| 44 | }) | |
| 45 | ||
| 46 | func update_info(updates): | |
| 47 | var data = {} | |
| 48 | ||
| 49 | if info_cache != null: | |
| 50 | for key in info_cache.keys(): | |
| 51 | data[key] = info_cache[key] | |
| 52 | ||
| 53 | for key in updates.keys(): | |
| 54 | data[key] = info_cache[key] | |
| 55 | ||
| 56 | var json = JSON.print(data) | |
| 57 | var info_url = url + profile_file | |
| 58 | ||
| 59 | $updateInfoRequest.request(info_url, [], true, HTTPClient.METHOD_PUT, json) | |
| 60 | ||
| 61 | func load_profile(): | |
| 62 | var info_url = url + profile_file | |
| 63 | $loadInfoRequest.request(info_url) | |
| 64 | ||
| 65 | func resolve_url(): | |
| 66 | var url_record = url + '.well-known/hyper' | |
| 67 | $resolveURLRequest.request(url_record) | |
| 68 | ||
| 69 | func download_image(url: String): | |
| 70 | print('Loading image: ', url) | |
| 71 | var file_name = url.get_file() | |
| 72 | var key = parser.parse(url).host | |
| 73 | # Save the picture with the profile key prefixed to it | |
| 74 | picture_path = cache_folder + key + '-' + file_name | |
| 75 | $loadImageRequest.download_file = picture_path | |
| 76 | $loadImageRequest.request(url) | |
| 77 | ||
| 78 | func upload_image(path: String): | |
| 79 | var picture_name = path.get_file() | |
| 80 | var picture_url = url + picture_name | |
| 81 | ||
| 82 | $uploader.upload_file(picture_url, path) | |
| 83 | ||
| 84 | func _on_loadInfoRequest_request_completed(_result, response_code, _headers, body): | |
| 85 | print("Loaded info", _result, " ", response_code) | |
| 86 | if response_code != 200: | |
| 87 | printerr('Cannot load existing profile info: ', response_code) | |
| 88 | emit_signal("no_info") | |
| 89 | return | |
| 90 | ||
| 91 | var parsed = JSON.parse(body.get_string_from_utf8()) | |
| 92 | if parsed.error != OK: | |
| 93 | printerr(parsed.error_string) | |
| 94 | emit_signal("no_info") | |
| 95 | return | |
| 96 | ||
| 97 | info_cache = parsed.result | |
| 98 | ||
| 99 | var username = info_cache.username | |
| 100 | var image = info_cache.image | |
| 101 | ||
| 102 | emit_signal("info", username, image) | |
| 103 | ||
| 104 | if image != null and autoload_image: | |
| 105 | download_image(image) | |
| 106 | ||
| 107 | func _on_uploader_uploaded(resolved_url, response_code): | |
| 108 | if response_code != 200: | |
| 109 | printerr('Unable to upload file ') | |
| 110 | return | |
| 111 | ||
| 112 | if !url || (url.length() == 0): | |
| 113 | printerr("Unable to load Link header for image.") | |
| 114 | return | |
| 115 | ||
| 116 | update_info({ | |
| 117 | "username": get_username(), | |
| 118 | "image": resolved_url | |
| 119 | }) | |
| 120 | ||
| 121 | download_image(resolved_url) | |
| 122 | ||
| 123 | func _on_loadImageRequest_request_completed(result, response_code, _headers, body): | |
| 124 | if result != OK or response_code != 200: | |
| 125 | printerr('Unable to load image: ', result, ": ", body.get_string_from_utf8()) | |
| 126 | return | |
| 127 | ||
| 128 | print(result, ' Image got downloaded: ', picture_path) | |
| 129 | ||
| 130 | emit_signal('image', picture_path) | |
| 131 | ||
| 132 | func _on_updateInfoRequest_request_completed(_result, _response_code, _headers, _body): | |
| 133 | emit_signal("updated") | |
| 134 | ||
| 135 | func _on_resolveURLRequest_request_completed(result, response_code, _headers, body: PoolByteArray): | |
| 136 | if result != OK or response_code != 200: | |
| 137 | printerr('Unable to load final profile URL') | |
| 138 | return | |
| 139 | ||
| 140 | var lines = body.get_string_from_utf8() | |
| 141 | var url = lines.split('\n')[0] | |
| 142 | if !url.ends_with('/'): | |
| 143 | url += '/' | |
| 144 | emit_signal('resolved', url) |
| … | … | @@ -1,29 +0,0 @@ |
| 1 | [gd_scene load_steps=4 format=2] | |
| 2 | ||
| 3 | [ext_resource path="res://src/MultiplayerDemo/PlayerProfile.gd" type="Script" id=1] | |
| 4 | [ext_resource path="res://src/HyperRequest/HyperRequest.gd" type="Script" id=2] | |
| 5 | [ext_resource path="res://src/HyperRequest/HyperUploader.gd" type="Script" id=3] | |
| 6 | ||
| 7 | [node name="PlayerProfile" type="Node"] | |
| 8 | script = ExtResource( 1 ) | |
| 9 | ||
| 10 | [node name="loadInfoRequest" type="HTTPRequest" parent="."] | |
| 11 | script = ExtResource( 2 ) | |
| 12 | ||
| 13 | [node name="loadImageRequest" type="HTTPRequest" parent="."] | |
| 14 | script = ExtResource( 2 ) | |
| 15 | ||
| 16 | [node name="updateInfoRequest" type="HTTPRequest" parent="."] | |
| 17 | script = ExtResource( 2 ) | |
| 18 | ||
| 19 | [node name="resolveURLRequest" type="HTTPRequest" parent="."] | |
| 20 | script = ExtResource( 2 ) | |
| 21 | ||
| 22 | [node name="uploader" type="Node" parent="."] | |
| 23 | script = ExtResource( 3 ) | |
| 24 | ||
| 25 | [connection signal="request_completed" from="loadInfoRequest" to="." method="_on_loadInfoRequest_request_completed"] | |
| 26 | [connection signal="request_completed" from="loadImageRequest" to="." method="_on_loadImageRequest_request_completed"] | |
| 27 | [connection signal="request_completed" from="updateInfoRequest" to="." method="_on_updateInfoRequest_request_completed"] | |
| 28 | [connection signal="request_completed" from="resolveURLRequest" to="." method="_on_resolveURLRequest_request_completed"] | |
| 29 | [connection signal="uploaded" from="uploader" to="." method="_on_uploader_uploaded"] |
| … | … | @@ -6,7 +6,7 @@ export var has_profile = false |
| 6 | 6 | func load_profile(profile_url): |
| 7 | 7 | has_profile = true |
| 8 | 8 | $PlayerProfile.url = profile_url |
| 9 | $PlayerProfile.load_profile() | |
| 9 | $PlayerProfile.load_info() | |
| 10 | 10 | |
| 11 | 11 | func set_texture(texture): |
| 12 | 12 | $Sprite.texture = texture |
| … | … | @@ -18,28 +18,28 @@ func _on_VisibilityNotifier2D_screen_exited(): |
| 18 | 18 | position.x = viewport.x / 2 |
| 19 | 19 | position.y = viewport.y / 2 |
| 20 | 20 | |
| 21 | func set_radius(radius): | |
| 22 | _set_sprite_radius(radius) | |
| 23 | _set_colission_radius(radius) | |
| 24 | _set_visibility_radius(radius) | |
| 21 | func set_radius(new_radius): | |
| 22 | _set_sprite_radius(new_radius) | |
| 23 | _set_colission_radius(new_radius) | |
| 24 | _set_visibility_radius(new_radius) | |
| 25 | 25 | |
| 26 | func _set_sprite_radius(radius): | |
| 26 | func _set_sprite_radius(new_radius): | |
| 27 | 27 | var sprite = $Sprite |
| 28 | 28 | var rect = sprite.get_rect() |
| 29 | 29 | var xCurrent = rect.size.x |
| 30 | 30 | var yCurrent = rect.size.y |
| 31 | 31 | |
| 32 | var xScale = (radius * 2) / xCurrent | |
| 33 | var yScale = (radius * 2) / yCurrent | |
| 32 | var xScale = (new_radius * 2) / xCurrent | |
| 33 | var yScale = (new_radius * 2) / yCurrent | |
| 34 | 34 | |
| 35 | 35 | sprite.scale.x = xScale |
| 36 | 36 | sprite.scale.y = yScale |
| 37 | 37 | |
| 38 | func _set_colission_radius(radius): | |
| 39 | $CollisionShape2D.shape.radius = radius | |
| 38 | func _set_colission_radius(new_radius): | |
| 39 | $CollisionShape2D.shape.radius = new_radius | |
| 40 | 40 | |
| 41 | func _set_visibility_radius(radius): | |
| 42 | var rect = Rect2(-radius, -radius, radius*2, radius*2) | |
| 41 | func _set_visibility_radius(new_radius): | |
| 42 | var rect = Rect2(-new_radius, -new_radius, new_radius*2, new_radius*2) | |
| 43 | 43 | $VisibilityNotifier2D.rect = rect |
| 44 | 44 | |
| 45 | 45 | func _on_PlayerProfile_image(path): |
| … | … | @@ -48,3 +48,4 @@ func _on_PlayerProfile_image(path): |
| 48 | 48 | image.load(path) |
| 49 | 49 | texture.create_from_image(image) |
| 50 | 50 | set_texture(texture) |
| 51 |
| … | … | @@ -2,7 +2,7 @@ |
| 2 | 2 | |
| 3 | 3 | [ext_resource path="res://src/MultiplayerDemo/icon.png" type="Texture" id=1] |
| 4 | 4 | [ext_resource path="res://src/MultiplayerDemo/PlayerSprite.gd" type="Script" id=2] |
| 5 | [ext_resource path="res://src/MultiplayerDemo/PlayerProfile.tscn" type="PackedScene" id=3] | |
| 5 | [ext_resource path="res://src/multiplayer/InfoLoader.tscn" type="PackedScene" id=3] | |
| 6 | 6 | |
| 7 | 7 | [sub_resource type="ParticlesMaterial" id=1] |
| 8 | 8 | flag_disable_z = true |
| … | … | @@ -41,7 +41,6 @@ shape = SubResource( 2 ) |
| 41 | 41 | rect = Rect2( -40, -40, 80, 80 ) |
| 42 | 42 | |
| 43 | 43 | [node name="PlayerProfile" parent="." instance=ExtResource( 3 )] |
| 44 | autoload_image = true | |
| 45 | 44 | |
| 46 | 45 | [connection signal="screen_exited" from="VisibilityNotifier2D" to="." method="_on_VisibilityNotifier2D_screen_exited"] |
| 47 | 46 | [connection signal="image" from="PlayerProfile" to="." method="_on_PlayerProfile_image"] |
| … | … | @@ -0,0 +1,143 @@ |
| 1 | extends Node | |
| 2 | ||
| 3 | var URLParser = preload("res://src/utils/URLParser.gd") | |
| 4 | var parser = URLParser.new() | |
| 5 | ||
| 6 | signal info(info) | |
| 7 | signal no_info() | |
| 8 | signal image(path) | |
| 9 | signal updated() | |
| 10 | signal resolved(url) | |
| 11 | signal error(err) | |
| 12 | ||
| 13 | export var url = 'hyper://blog.mauve.moe/' | |
| 14 | export var cache_folder: String = 'user://hyper-file-cache/' | |
| 15 | export var profile_file: String = 'profile.json' | |
| 16 | ||
| 17 | export var autoload = false | |
| 18 | export var autoload_image = false | |
| 19 | export var defaults = {} | |
| 20 | ||
| 21 | var info_cache = null | |
| 22 | var has_loaded = false | |
| 23 | var picture_path = '' | |
| 24 | ||
| 25 | func _ready(): | |
| 26 | var dir = Directory.new() | |
| 27 | var err = dir.make_dir_recursive(cache_folder) | |
| 28 | if err != OK: | |
| 29 | printerr("Unable to create cache directory ", err) | |
| 30 | ||
| 31 | print("Profile setup") | |
| 32 | if autoload: | |
| 33 | load_info() | |
| 34 | ||
| 35 | func get(field): | |
| 36 | if info_cache != null and info_cache.has(field): | |
| 37 | return info_cache[field] | |
| 38 | if defaults.has(field): | |
| 39 | return defaults.get(field) | |
| 40 | ||
| 41 | func update_info(updates): | |
| 42 | var data = {} | |
| 43 | ||
| 44 | if info_cache != null: | |
| 45 | for key in info_cache.keys(): | |
| 46 | data[key] = info_cache[key] | |
| 47 | else: | |
| 48 | for key in defaults: | |
| 49 | data[key] = defaults[key] | |
| 50 | ||
| 51 | for key in updates.keys(): | |
| 52 | data[key] = updates[key] | |
| 53 | ||
| 54 | var json = JSON.print(data) | |
| 55 | var info_url = url + profile_file | |
| 56 | ||
| 57 | $updateInfoRequest.request(info_url, [], true, HTTPClient.METHOD_PUT, json) | |
| 58 | ||
| 59 | info_cache = data | |
| 60 | ||
| 61 | func load_info(): | |
| 62 | var info_url = url + profile_file | |
| 63 | $loadInfoRequest.request(info_url) | |
| 64 | ||
| 65 | func resolve_url(): | |
| 66 | var url_record = url + '.well-known/hyper' | |
| 67 | $resolveURLRequest.request(url_record) | |
| 68 | ||
| 69 | func download_image(image_url: String): | |
| 70 | print('Loading image: ', image_url) | |
| 71 | var file_name = image_url.get_file() | |
| 72 | var key = parser.parse(image_url).host | |
| 73 | # Save the picture with the profile key prefixed to it | |
| 74 | picture_path = cache_folder + key + '-' + file_name | |
| 75 | $loadImageRequest.download_file = picture_path | |
| 76 | $loadImageRequest.request(image_url) | |
| 77 | ||
| 78 | func upload_image(path: String): | |
| 79 | var picture_name = path.get_file() | |
| 80 | var picture_url = url + picture_name | |
| 81 | ||
| 82 | $uploader.upload_file(picture_url, path) | |
| 83 | ||
| 84 | func _on_loadInfoRequest_request_completed(_result, response_code, _headers, body): | |
| 85 | print("Loaded info", _result, " ", response_code) | |
| 86 | if response_code != 200: | |
| 87 | printerr('Cannot load existing profile info: ', response_code) | |
| 88 | emit_signal("no_info") | |
| 89 | return | |
| 90 | ||
| 91 | var parsed = JSON.parse(body.get_string_from_utf8()) | |
| 92 | if parsed.error != OK: | |
| 93 | printerr(parsed.error_string) | |
| 94 | emit_signal("no_info") | |
| 95 | return | |
| 96 | ||
| 97 | info_cache = parsed.result | |
| 98 | ||
| 99 | var image = info_cache.image | |
| 100 | ||
| 101 | emit_signal("info", info_cache) | |
| 102 | ||
| 103 | if image != null and autoload_image: | |
| 104 | download_image(image) | |
| 105 | ||
| 106 | func _on_uploader_uploaded(resolved_url, response_code): | |
| 107 | if response_code != 200: | |
| 108 | printerr('Unable to upload file ') | |
| 109 | return | |
| 110 | ||
| 111 | if !url || (url.length() == 0): | |
| 112 | printerr("Unable to load Link header for image.") | |
| 113 | return | |
| 114 | ||
| 115 | update_info({ | |
| 116 | "image": resolved_url | |
| 117 | }) | |
| 118 | ||
| 119 | download_image(resolved_url) | |
| 120 | ||
| 121 | func _on_loadImageRequest_request_completed(result, response_code, _headers, body): | |
| 122 | if result != OK or response_code != 200: | |
| 123 | printerr('Unable to load image: ', result, ": ", body.get_string_from_utf8()) | |
| 124 | return | |
| 125 | ||
| 126 | print(result, ' Image got downloaded: ', picture_path) | |
| 127 | ||
| 128 | emit_signal('image', picture_path) | |
| 129 | ||
| 130 | func _on_updateInfoRequest_request_completed(_result, _response_code, _headers, _body): | |
| 131 | emit_signal("updated") | |
| 132 | ||
| 133 | func _on_resolveURLRequest_request_completed(result, response_code, _headers, body: PoolByteArray): | |
| 134 | if result != OK or response_code != 200: | |
| 135 | printerr('Unable to load final profile URL') | |
| 136 | return | |
| 137 | ||
| 138 | var lines = body.get_string_from_utf8() | |
| 139 | var resolved_url = lines.split('\n')[0] | |
| 140 | if !resolved_url.ends_with('/'): | |
| 141 | resolved_url += '/' | |
| 142 | ||
| 143 | emit_signal('resolved', resolved_url) |
| … | … | @@ -0,0 +1,29 @@ |
| 1 | [gd_scene load_steps=4 format=2] | |
| 2 | ||
| 3 | [ext_resource path="res://src/multiplayer/InfoLoader.gd" type="Script" id=1] | |
| 4 | [ext_resource path="res://src/HyperRequest/HyperRequest.gd" type="Script" id=2] | |
| 5 | [ext_resource path="res://src/HyperRequest/HyperUploader.gd" type="Script" id=3] | |
| 6 | ||
| 7 | [node name="InfoLoader" type="Node"] | |
| 8 | script = ExtResource( 1 ) | |
| 9 | ||
| 10 | [node name="loadInfoRequest" type="HTTPRequest" parent="."] | |
| 11 | script = ExtResource( 2 ) | |
| 12 | ||
| 13 | [node name="loadImageRequest" type="HTTPRequest" parent="."] | |
| 14 | script = ExtResource( 2 ) | |
| 15 | ||
| 16 | [node name="updateInfoRequest" type="HTTPRequest" parent="."] | |
| 17 | script = ExtResource( 2 ) | |
| 18 | ||
| 19 | [node name="resolveURLRequest" type="HTTPRequest" parent="."] | |
| 20 | script = ExtResource( 2 ) | |
| 21 | ||
| 22 | [node name="uploader" type="Node" parent="."] | |
| 23 | script = ExtResource( 3 ) | |
| 24 | ||
| 25 | [connection signal="request_completed" from="loadInfoRequest" to="." method="_on_loadInfoRequest_request_completed"] | |
| 26 | [connection signal="request_completed" from="loadImageRequest" to="." method="_on_loadImageRequest_request_completed"] | |
| 27 | [connection signal="request_completed" from="updateInfoRequest" to="." method="_on_updateInfoRequest_request_completed"] | |
| 28 | [connection signal="request_completed" from="resolveURLRequest" to="." method="_on_resolveURLRequest_request_completed"] | |
| 29 | [connection signal="uploaded" from="uploader" to="." method="_on_uploader_uploaded"] |
| … | … | @@ -0,0 +1,48 @@ |
| 1 | extends HyperGossip | |
| 2 | ||
| 3 | signal list_items(items) | |
| 4 | ||
| 5 | class_name ListGossip | |
| 6 | ||
| 7 | export var id_key = 'url' | |
| 8 | export var items_event = 'items' | |
| 9 | ||
| 10 | # Enables you to gossip a list of items, useful for game lobbies | |
| 11 | var items = [] | |
| 12 | ||
| 13 | func _ready(): | |
| 14 | ._ready() | |
| 15 | connect("event", self, "_on_event_list_gossip") | |
| 16 | ||
| 17 | func add_item(item): | |
| 18 | if !item.has(id_key): | |
| 19 | printerr('Attempted to gossip item without an id key:', id_key, ' ', item) | |
| 20 | return | |
| 21 | ||
| 22 | if !has_item(item): | |
| 23 | items.append(item) | |
| 24 | broadcast_items() | |
| 25 | ||
| 26 | func has_item(item): | |
| 27 | for existing in items: | |
| 28 | if existing[id_key] == item[id_key]: | |
| 29 | return true | |
| 30 | return false | |
| 31 | ||
| 32 | func broadcast_items(): | |
| 33 | emit_signal('list_items', items) | |
| 34 | broadcast_event(items_event, {'items': items}) | |
| 35 | ||
| 36 | func _on_event_list_gossip(event, data, _from): | |
| 37 | if event != items_event: return | |
| 38 | if !data.has('items'): | |
| 39 | printerr('Gossip without items list', data) | |
| 40 | return | |
| 41 | var hasNew = false | |
| 42 | for item in data.items: | |
| 43 | if !has_item(item): | |
| 44 | hasNew = true | |
| 45 | items.append(item) | |
| 46 | ||
| 47 | if hasNew: broadcast_items() | |
| 48 |