OverviewFilesCommitsIssuesDocsReleasesPeers

hypergodot commits / 4a81d83

Add player profiles with hyperdrives
4a81d83e28ef2e5151766d87540cf8eb12d36f83
by mauve <ranger@mauve.moe> · 2021-10-18T23:47:05.000Z

Changed files (11) +306 -149

README.md +1 -1
……@@ -27,7 +27,7 @@ A Godot plugin and example project for loading assets from the [hypercore-protoc
2727- [ ] Basic Multiplayer demo using extension messages
2828 - [x] Refactor chat system to be more general purpose (not just texts)
2929 - [x] Basic demo moving blobs around on a map
30 - [ ] Place usernames in a hyperdrive's json file along with an image for your blob
30 - [x] Place usernames in a hyperdrive's json file along with an image for your blob
3131 - [ ] Create custom maps from an image and a JSON file
3232 - [ ] Auto-discover which maps are active
3333- [ ] Enable code to work in Godot Web in Agregore
project.godot +1 -1
……@@ -74,7 +74,7 @@ Display=8
7474[application]
7575
7676config/name="HyperGodot"
77run/main_scene="res://src/HyperLoad/HyperLoad.tscn"
77run/main_scene="res://src/MultiplayerDemo/MultiplayerDemo.tscn"
7878config/icon="res://icon.png"
7979
8080[display]
src/MultiplayerDemo/FlyBalls.gd +12 -1
……@@ -5,6 +5,8 @@ var RemoteBullet = preload("res://src/MultiplayerDemo/RemoteBullet.tscn")
55
66#export var url: String = "hyper://blog.mauve.moe/"
77export var url: String = "hyper://70338a94d7415990ab4a3160cf98a5d940243d7fa24dd297352e66b52d3ff841/"
8export var profile : String = ""
9export var spawn_gateway = true
810
911onready var gossip = $HyperGossip
1012onready var player = $Player
……@@ -16,7 +18,8 @@ const SHOOT = 'shoot'
1618const knownPlayers = {}
1719
1820func _ready():
19 gateway.start()
21 if spawn_gateway: gateway.start()
22 elif profile.length() != 0: player.load_profile(profile)
2023
2124func get_player_object(id):
2225 if knownPlayers.has(id): return knownPlayers[id]
……@@ -34,6 +37,7 @@ func _broadcast_player():
3437 var rotation = player.rotation_degrees
3538 var velocity = player.velocity
3639 var data = {
40 "profile": profile,
3741 "position": {
3842 "x": position.x,
3943 "y": position.y
……@@ -49,6 +53,7 @@ func _broadcast_player():
4953
5054func _broadcast_shoot(direction,location):
5155 var data = {
56 "profile": profile,
5257 "direction": direction,
5358 "position": {
5459 "x": location.x,
……@@ -61,6 +66,10 @@ func _broadcast_shoot(direction,location):
6166func _on_remote_player_moved(positionData, id):
6267 # print('Moving player', id, " ", positionData)
6368 var remotePlayer = get_player_object(id)
69
70 if positionData.has('profile') and positionData.profile.length() != 0:
71 if !remotePlayer.has_profile: remotePlayer.load_profile(positionData.profile)
72
6473 var rotation_degrees = positionData.rotation
6574 var raw_position = positionData.position
6675 var raw_velocity = positionData.velocity
……@@ -95,6 +104,8 @@ func _on_Player_shoot(Bullet, direction, location):
95104 _broadcast_shoot(direction, location)
96105
97106func _on_HyperGateway_started_gateway(_pid):
107 if profile.length() != 0: player.load_profile(profile)
108
98109 gossip.url = url
99110 gossip.start_listening()
100111
src/MultiplayerDemo/LoadProfile.gd +40 -116
……@@ -1,19 +1,20 @@
11extends "res://src/ResizeRoot.gd"
22
3signal profile_ready(url)
3# TODO: Better error dialogs
4# TODO: Better UX for updating the username
45
5export var profile_URL: String = 'hyper://flyballs-profile/'
6export var profile_file: String = 'profile.json'
7export var cache_folder: String = 'user://hyper-file-cache/'
6signal profile_ready(url)
87
9var picture_name = ""
10var picture_path = ""
8export var profile_url = 'hyper://flyballs-profile/'
9export var spawn_gateway = true
1110
1211func _ready():
13 var dir = Directory.new()
14 var err = dir.make_dir_recursive(cache_folder)
15 if err != OK:
16 printerr("Unable to create cache directory ", err)
12 if spawn_gateway: $HyperGateway.start()
13
14func load_profile():
15 print("Initial profile load")
16 $PlayerProfile.url = profile_url
17 $PlayerProfile.load_profile()
1718
1819func hide_loader():
1920 $Loader.hide()
……@@ -23,142 +24,65 @@ func show_loader():
2324 $Loader.show()
2425 $Form.hide()
2526
26func load_url():
27 var url_record = profile_URL + '.well-known/hyper'
28 $LoadURLRequest.request(url_record)
29
30func update_profile(data):
31 var json = JSON.print(data)
32 var info_url = profile_URL + profile_file
33
34 $UpdateProfileRequest.request(info_url, [], true, HTTPClient.METHOD_PUT, json)
35
3627func get_username():
3728 return $Form/UsernameInput.text
3829
39func load_profile():
40 var info_url = profile_URL + profile_file
41 $ListInfoRequest.request(info_url)
42
4330func load_image(image: String):
4431 print('Loading image')
4532 show_loader()
46 var file_name = image.get_file()
47 picture_path = cache_folder + file_name
48 # TODO: Save file_path somewhere?
49 $LoadImageRequest.download_file = picture_path
50 $LoadImageRequest.request(image)
33 $PlayerProfile.download_image(image)
5134
5235func _on_ImageChooser_pressed():
36 print('Choosing image')
5337 $FileDialog.popup()
5438
5539func _on_FileDialog_file_selected(path: String):
40 print('Selected image, uploading:', path)
5641 show_loader()
57
58 picture_name = path.get_file()
59 var picture_url = profile_URL + picture_name
60
61 $HyperUploader.upload_file(picture_url, path)
42 $PlayerProfile.upload_image(path)
6243
6344func _on_HyperGateway_started_gateway(_pid):
6445 load_profile()
6546
66func _on_ListInfoRequest_request_completed(_result, response_code, _headers, body):
67 if response_code != 200:
68 printerr('Cannot load existing profile info: ', response_code)
69 hide_loader()
70 return
71
72 var parsed = JSON.parse(body.get_string_from_utf8())
73 if parsed.error != OK:
74 printerr(parsed.error_string)
75 hide_loader()
76 return
77
78 $Form/StartButton.disabled = false
79
80 $Form/UsernameInput.text = parsed.result.username
47func _on_StartButton_pressed():
48 print("Starting next step")
49 show_loader()
50 $PlayerProfile.resolve_url()
8151
82 var image = parsed.result.image
83 if image != null:
84 load_image(image)
85 else: hide_loader()
52func _on_PlayerProfile_error(err):
53 # TODO: Show an error dialog?
54 print("Error interacting with profile:", err)
8655
87func _on_HyperUploader_uploaded(url, response_code):
88 if response_code != 200:
89 printerr('Unable to upload file ')
90 hide_loader()
91 return
92
93 if !url || (url.length() == 0):
94 printerr("Unable to load Link header for image.")
95 hide_loader()
96 return
97
98 update_profile({
99 "username": get_username(),
100 "image": url
101 })
102
103 load_image(url)
104
105func _on_UploadFileRequest_request_completed(_result, response_code, headers, body):
106 print('Uploaded picture')
107 if response_code != 200:
108 printerr('Unable to upload: ', body.get_string_from_utf8())
109 return
110
111 var regex = RegEx.new()
112 regex.compile("link: <(.+)>")
113 var final_url = ''
114
115 for header in headers:
116 var result = regex.search(header)
117 final_url = result.get_string(1)
118
119 if !final_url || (final_url.size() == 0):
120 printerr("Unable to load Link header for image. ", headers)
121 hide_loader()
122 return
123
124 update_profile({
125 "username": get_username(),
126 "image": final_url
127 })
128
129func _on_LoadImageRequest_request_completed(result, response_code, _headers, body):
56func _on_PlayerProfile_image(path):
57 print("Image loaded, setting texture")
13058 hide_loader()
131 if result != OK or response_code != 200:
132 printerr('Unable to load image: ', result, ": ", body.get_string_from_utf8())
133 return
134
135 print(result, ' Image got downloaded: ', picture_path)
136
13759 var texture = ImageTexture.new()
13860 var image = Image.new()
139 image.load(picture_path)
61 image.load(path)
14062 texture.create_from_image(image)
14163 texture.set_size_override(Vector2(64, 64))
14264
14365 $Form/ImageChooser.icon = texture
14466
145func _on_UpdateProfileRequest_request_completed(_result, _response_code, _headers, _body):
146 hide_loader()
67func _on_PlayerProfile_info(username: String, image: String):
68 print("Loaded player profile:", {"username": username, "image": image})
14769 $Form/StartButton.disabled = false
70 $Form/UsernameInput.text = username
14871
149func _on_StartButton_pressed():
150 show_loader()
151 load_url()
72 if image != null && image.length() != 0:
73 load_image(image)
74 else: hide_loader()
15275
153func _on_LoadURLRequest_request_completed(result, response_code, _headers, body: PoolByteArray):
154 if result != OK or response_code != 200:
155 printerr('Unable to load final profile URL')
156 return
76func _on_PlayerProfile_updated():
77 print('Updated player profile"')
78 hide_loader()
79 $Form/StartButton.disabled = false
15780
158 var lines = body.get_string_from_utf8()
159 var url = lines.split('\n')[0]
160 if !url.ends_with('/'):
161 url += '/'
81func _on_PlayerProfile_resolved(url):
16282 print('Profile ready')
16383 print(url)
16484 emit_signal('profile_ready', url)
85
86func _on_PlayerProfile_no_info():
87 print('No existing profile info')
88 hide_loader()
src/MultiplayerDemo/LoadProfile.tscn +14 -29
……@@ -1,9 +1,8 @@
1[gd_scene load_steps=5 format=2]
1[gd_scene load_steps=4 format=2]
22
33[ext_resource path="res://src/MultiplayerDemo/LoadProfile.gd" type="Script" id=1]
44[ext_resource path="res://src/HyperGateway/HyperGateway.gd" type="Script" id=2]
5[ext_resource path="res://src/HyperRequest/HyperRequest.gd" type="Script" id=3]
6[ext_resource path="res://src/HyperRequest/HyperUploader.gd" type="Script" id=4]
5[ext_resource path="res://src/MultiplayerDemo/PlayerProfile.tscn" type="PackedScene" id=3]
76
87[node name="LoadProfile" type="VBoxContainer"]
98anchor_right = 1.0
……@@ -15,18 +14,19 @@ __meta__ = {
1514
1615[node name="Loader" type="CenterContainer" parent="."]
1716margin_right = 666.0
18margin_bottom = 331.0
17margin_bottom = 666.0
1918size_flags_horizontal = 3
2019size_flags_vertical = 3
2120
2221[node name="Label" type="Label" parent="Loader"]
2322margin_left = 308.0
24margin_top = 158.0
23margin_top = 326.0
2524margin_right = 357.0
26margin_bottom = 172.0
25margin_bottom = 340.0
2726text = "Loading"
2827
2928[node name="Form" type="VBoxContainer" parent="."]
29visible = false
3030margin_top = 335.0
3131margin_right = 666.0
3232margin_bottom = 666.0
……@@ -64,24 +64,9 @@ text = "Start"
6464
6565[node name="HyperGateway" type="Node" parent="."]
6666script = ExtResource( 2 )
67autoStart = false
6768
68[node name="UploadFileRequest" type="HTTPRequest" parent="."]
69script = ExtResource( 3 )
70
71[node name="ListInfoRequest" type="HTTPRequest" parent="."]
72script = ExtResource( 3 )
73
74[node name="LoadImageRequest" type="HTTPRequest" parent="."]
75script = ExtResource( 3 )
76
77[node name="LoadURLRequest" type="HTTPRequest" parent="."]
78script = ExtResource( 3 )
79
80[node name="UpdateProfileRequest" type="HTTPRequest" parent="."]
81script = ExtResource( 3 )
82
83[node name="HyperUploader" type="Node" parent="."]
84script = ExtResource( 4 )
69[node name="PlayerProfile" parent="." instance=ExtResource( 3 )]
8570
8671[node name="FileDialog" type="FileDialog" parent="."]
8772margin_top = 536.0
……@@ -99,10 +84,10 @@ current_path = "/home/mauve/programming/godot/hypergodot/"
9984[connection signal="pressed" from="Form/ImageChooser" to="." method="_on_ImageChooser_pressed"]
10085[connection signal="pressed" from="Form/StartButton" to="." method="_on_StartButton_pressed"]
10186[connection signal="started_gateway" from="HyperGateway" to="." method="_on_HyperGateway_started_gateway"]
102[connection signal="request_completed" from="UploadFileRequest" to="." method="_on_UploadFileRequest_request_completed"]
103[connection signal="request_completed" from="ListInfoRequest" to="." method="_on_ListInfoRequest_request_completed"]
104[connection signal="request_completed" from="LoadImageRequest" to="." method="_on_LoadImageRequest_request_completed"]
105[connection signal="request_completed" from="LoadURLRequest" to="." method="_on_LoadURLRequest_request_completed"]
106[connection signal="request_completed" from="UpdateProfileRequest" to="." method="_on_UpdateProfileRequest_request_completed"]
107[connection signal="uploaded" from="HyperUploader" to="." method="_on_HyperUploader_uploaded"]
87[connection signal="error" from="PlayerProfile" to="." method="_on_PlayerProfile_error"]
88[connection signal="image" from="PlayerProfile" to="." method="_on_PlayerProfile_image"]
89[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"]
10893[connection signal="file_selected" from="FileDialog" to="." method="_on_FileDialog_file_selected"]
src/MultiplayerDemo/MultiplayerDemo.gd +14 -0
……@@ -0,0 +1,14 @@
1extends "res://src/ResizeRoot.gd"
2
3var FlyBalls = preload("res://src/MultiplayerDemo/FlyBalls.tscn")
4
5func _on_LoadProfile_profile_ready(url):
6 remove_child($LoadProfile)
7
8 var game = FlyBalls.instance()
9 game.profile = url
10 game.spawn_gateway = false
11 add_child(game)
12
13func _on_HyperGateway_started_gateway(pid):
14 $LoadProfile.load_profile()
src/MultiplayerDemo/MultiplayerDemo.tscn +32 -0
……@@ -0,0 +1,32 @@
1[gd_scene load_steps=4 format=2]
2
3[ext_resource path="res://src/MultiplayerDemo/MultiplayerDemo.gd" type="Script" id=1]
4[ext_resource path="res://src/MultiplayerDemo/LoadProfile.tscn" type="PackedScene" id=2]
5[ext_resource path="res://src/HyperGateway/HyperGateway.gd" type="Script" id=3]
6
7[node name="MultiplayerDemo" type="VBoxContainer"]
8anchor_right = 1.0
9anchor_bottom = 1.0
10margin_right = -617.0
11margin_bottom = -652.0
12size_flags_horizontal = 3
13size_flags_vertical = 3
14script = ExtResource( 1 )
15__meta__ = {
16"_edit_use_anchors_": false
17}
18
19[node name="LoadProfile" parent="." instance=ExtResource( 2 )]
20anchor_right = 0.0
21anchor_bottom = 0.0
22margin_right = 49.0
23margin_bottom = 14.0
24size_flags_horizontal = 3
25size_flags_vertical = 3
26spawn_gateway = false
27
28[node name="HyperGateway" type="Node" parent="."]
29script = ExtResource( 3 )
30
31[connection signal="profile_ready" from="LoadProfile" to="." method="_on_LoadProfile_profile_ready"]
32[connection signal="started_gateway" from="HyperGateway" to="." method="_on_HyperGateway_started_gateway"]
src/MultiplayerDemo/PlayerProfile.gd +144 -0
……@@ -0,0 +1,144 @@
1extends Node
2
3var URLParser = preload("res://src/utils/URLParser.gd")
4var parser = URLParser.new()
5
6signal info(username, image)
7signal no_info()
8signal image(path)
9signal updated()
10signal resolved(url)
11signal error(err)
12
13export var url = 'hyper://blog.mauve.moe/'
14export var cache_folder: String = 'user://hyper-file-cache/'
15export var profile_file: String = 'profile.json'
16
17export var autoload = false
18export var autoload_image = false
19
20const DEFAULT_USERNAME = 'Anonymous'
21
22var info_cache = null
23var has_loaded = false
24var picture_path = ''
25
26func _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
36func 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
41func set_username(username):
42 update_info({
43 "username": username
44 })
45
46func 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
61func load_profile():
62 var info_url = url + profile_file
63 $loadInfoRequest.request(info_url)
64
65func resolve_url():
66 var url_record = url + '.well-known/hyper'
67 $resolveURLRequest.request(url_record)
68
69func 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
78func 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
84func _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
107func _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
123func _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
132func _on_updateInfoRequest_request_completed(_result, _response_code, _headers, _body):
133 emit_signal("updated")
134
135func _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)
src/MultiplayerDemo/PlayerProfile.tscn +29 -0
……@@ -0,0 +1,29 @@
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"]
8script = ExtResource( 1 )
9
10[node name="loadInfoRequest" type="HTTPRequest" parent="."]
11script = ExtResource( 2 )
12
13[node name="loadImageRequest" type="HTTPRequest" parent="."]
14script = ExtResource( 2 )
15
16[node name="updateInfoRequest" type="HTTPRequest" parent="."]
17script = ExtResource( 2 )
18
19[node name="resolveURLRequest" type="HTTPRequest" parent="."]
20script = ExtResource( 2 )
21
22[node name="uploader" type="Node" parent="."]
23script = 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"]
src/MultiplayerDemo/PlayerSprite.gd +13 -0
……@@ -1,6 +1,12 @@
11extends KinematicBody2D
22
33export var radius: float = 30 setget set_radius
4export var has_profile = false
5
6func load_profile(profile_url):
7 has_profile = true
8 $PlayerProfile.url = profile_url
9 $PlayerProfile.load_profile()
410
511func set_texture(texture):
612 $Sprite.texture = texture
……@@ -35,3 +41,10 @@ func _set_colission_radius(radius):
3541func _set_visibility_radius(radius):
3642 var rect = Rect2(-radius, -radius, radius*2, radius*2)
3743 $VisibilityNotifier2D.rect = rect
44
45func _on_PlayerProfile_image(path):
46 var texture = ImageTexture.new()
47 var image = Image.new()
48 image.load(path)
49 texture.create_from_image(image)
50 set_texture(texture)
src/MultiplayerDemo/PlayerSprite.tscn +6 -1
……@@ -1,7 +1,8 @@
1[gd_scene load_steps=5 format=2]
1[gd_scene load_steps=6 format=2]
22
33[ext_resource path="res://src/MultiplayerDemo/icon.png" type="Texture" id=1]
44[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]
56
67[sub_resource type="ParticlesMaterial" id=1]
78flag_disable_z = true
……@@ -39,4 +40,8 @@ shape = SubResource( 2 )
3940[node name="VisibilityNotifier2D" type="VisibilityNotifier2D" parent="."]
4041rect = Rect2( -40, -40, 80, 80 )
4142
43[node name="PlayerProfile" parent="." instance=ExtResource( 3 )]
44autoload_image = true
45
4246[connection signal="screen_exited" from="VisibilityNotifier2D" to="." method="_on_VisibilityNotifier2D_screen_exited"]
47[connection signal="image" from="PlayerProfile" to="." method="_on_PlayerProfile_image"]