OverviewFilesCommitsIssuesDocsReleasesPeers

godot-marble_madness_demo commits / 3f8d835

test commit for multiplayer
3f8d835e0acd7ff9645b9f7025a39bba343a4e4c
by Dresk Spidflisk <dreskspidflisk@gmail.com> · 2022-02-03T19:51:58.000Z

Changed files (27) +1443 -77

hypergodot/http_event_source.gd +143 -0
……@@ -0,0 +1,143 @@
1extends Node
2
3class_name HTTPEventSource
4
5const URLParser = preload("./utils/urlparser.gd")
6
7signal event(data, event, id)
8signal response_started(statusCode, responseHeaders)
9
10var http = HTTPClient.new()
11var urlParser = URLParser.new()
12
13func cancel_request():
14 http.close()
15 pass
16
17func request(
18 url,
19 custom_headers = [],
20 ssl_validate_domain=true,
21 method =0,
22 request_data=""
23):
24 var headers = ["Accept: text/event-stream"]
25 headers.append_array(custom_headers)
26 var parsedURL = urlParser.parse(url)
27 var use_ssl = parsedURL.protocol == 'https'
28
29 var params = {
30 "headers": headers,
31 "host": parsedURL.host,
32 "port": parsedURL.port,
33 "path": parsedURL.path,
34 "use_ssl": use_ssl,
35 "verify_host": use_ssl && ssl_validate_domain,
36 "method": method,
37 "request_data": request_data
38 }
39
40 print('About to request', parsedURL)
41
42 var thread = Thread.new()
43
44 thread.start(self,"_runRequest", params)
45
46 pass
47
48func _emit_started(statusCode, responseHeaders):
49 print("Emitting started")
50 emit_signal("response_started", statusCode, responseHeaders)
51
52func _runRequest(params):
53 var err = http.connect_to_host(params.host, params.port, params.use_ssl, params.verify_host)
54
55 if err != OK:
56 printerr('Error connecting to EventSource server')
57
58 print('Connected to host')
59
60 # Wait to establish a connection to the host
61 while http.get_status() == HTTPClient.STATUS_CONNECTING or http.get_status() == HTTPClient.STATUS_RESOLVING:
62 err = http.poll()
63 if err != OK:
64 printerr('Unable to connect to EventSource')
65 return
66 OS.delay_msec(100)
67
68 print('Able to connect, requesting')
69
70 # Start the request
71 err = http.request(params.method, params.path, params.headers, params.request_data)
72
73 if err != OK:
74 printerr('Unable to request EventSource')
75 return
76
77 # Wait for request to get processed and to get the body
78 while (http.get_status() == HTTPClient.STATUS_REQUESTING):
79 err = http.poll()
80 if err != OK:
81 printerr('Unable to request to EventSource')
82 return
83 OS.delay_msec(100)
84
85 if !http.has_response():
86 print('No Response')
87 http.close()
88 call_deferred("emit_signal", "response_finished")
89 return
90
91 var statusCode = http.get_response_code()
92 var responseHeaders = http.get_response_headers()
93
94 print('Got response ', statusCode)
95
96 call_deferred("_emit_started", statusCode, responseHeaders)
97
98 var buffer = ""
99 var lastChunkEmpty = false
100 var currentEvent = {
101 "event": "message",
102 "data": "",
103 "id": ""
104 }
105
106 while http.get_status() == HTTPClient.STATUS_BODY:
107 http.poll()
108 var chunk = http.read_response_body_chunk().get_string_from_utf8()
109 if(chunk.length() == 0):
110 var delay = 800 if lastChunkEmpty else 200
111 lastChunkEmpty = true
112 OS.delay_usec(delay)
113 continue
114 lastChunkEmpty = false
115 buffer = buffer + chunk
116
117 while buffer.find("\n") != -1:
118 var line = buffer.substr(0, buffer.find("\n"))
119 buffer = buffer.substr(buffer.find("\n") + 1)
120
121 if line.find("data:") == 0: currentEvent["data"] += line.trim_prefix("data:")
122 if line.find("id:") == 0: currentEvent["id"] = line.trim_prefix("id:")
123 if line.find("event:") == 0: currentEvent["event"] = line.trim_prefix("event:")
124
125 var isEmpty = line.length() == 0
126 if isEmpty:
127 call_deferred("emit_signal", "event", currentEvent.data, currentEvent.event, currentEvent.id)
128 currentEvent = {
129 "event": "message",
130 "data": "",
131 "id": ""
132 }
133 http.close()
134 print('Done!')
135 call_deferred("emit_signal", "response_finished")
136pass
137
138func _emitEvent(params):
139 emit_signal("event", params.data, params.name, params.id)
140 pass
141
142func _exit_tree():
143 cancel_request()
hypergodot/hyper_gateway/hyper_gateway.gd +227 -0
……@@ -0,0 +1,227 @@
1extends Node
2
3const URLParser = preload("../utils/urlparser.gd")
4
5class_name HyperGateway
6
7signal starting_gateway()
8signal started_gateway(pid)
9
10signal stopping_gateway()
11signal stopped_gateway()
12
13signal started_download(version)
14signal finished_download(version)
15
16export var writable: bool = true
17export var persist: bool = true
18
19export var autoStart: bool = true
20export var autoDownload: bool = true
21export var autoUpdate: bool = false
22
23const DEFAULT_PORT = 4973
24
25export var port: int = DEFAULT_PORT
26export var storageDirectory: String = "user://gateway-data/"
27export var repositoryName: String = "rangermauve/hyper-gateway"
28export var tagStart: String = "v2"
29
30## TODO: Use this tag if the `latest` release doesn't match the tag?
31# export var defaultTag = "v2.1.3"
32
33var processPID : int = 0
34var latestInfoRequest = HTTPRequest.new()
35var downloadFileRequest = HTTPRequest.new()
36var downloadingVersion = null
37var downloadThread = null
38
39func _exit_tree():
40 cleanupGateway()
41
42func _notification(what):
43 if what == MainLoop.NOTIFICATION_WM_QUIT_REQUEST:
44 cleanupGateway()
45
46# Called when the node enters the scene tree for the first time.
47func _ready():
48 ensureStorageLocation()
49 add_child(latestInfoRequest)
50 latestInfoRequest.connect("request_completed", self, "_on_got_latest")
51
52 add_child(downloadFileRequest)
53 downloadFileRequest.connect("request_completed", self, "_on_finished_download")
54
55 if autoStart:
56 start()
57
58func start():
59 autoStart = true
60 if autoDownload:
61 ensureDownloaded()
62 else: ensureSetup()
63
64func stop():
65 cleanupGateway()
66
67func cleanupGateway() -> int:
68 if processPID == 0:
69 print('Gateway already closed ' + str(processPID))
70 return OK
71
72 print('Cleaning up gateway ' + str(processPID))
73 emit_signal("stopping_gateway")
74 var err = OS.kill(processPID)
75 OS.delay_msec(2000)
76 if err == OK:
77 processPID = 0
78 emit_signal("stopped_gateway")
79 return err
80
81func setupGateway():
82 emit_signal("starting_gateway")
83 var executiblePath = getExecutiblePath()
84 var path = ProjectSettings.globalize_path(executiblePath)
85
86 var args = ["run"]
87 if !persist: args.append('--no-persist')
88 if writable: args.append('--writable')
89
90 if port != DEFAULT_PORT:
91 args.append('--port')
92 args.append(String(port))
93
94 var resolvedStorageDirectory = ProjectSettings.globalize_path(storageDirectory)
95 args.append('--storage-location')
96 args.append(resolvedStorageDirectory)
97
98 print("Starting gateway from ",path, " with args ", args)
99
100 # Start the hyper-gateway in a process
101 processPID = OS.execute(path, args, false)
102
103 # Sleep for a bit while the gateway loads
104 # TODO: Find a better indicator like polling the HTTP server socket
105 OS.delay_msec(2000)
106
107 print("Started gateway at " + str(processPID))
108 emit_signal("started_gateway", processPID)
109
110func ensureSetup():
111 if getIsGatewayRunning(): return
112 setupGateway()
113
114func getIsGatewayRunning() -> bool:
115 if processPID != 0:
116 return true;
117 else:
118 return false;
119
120func getExecutibleName():
121 var name = OS.get_name()
122 if name == 'X11': return "hyper-gateway-linux"
123 if name == 'OSX': return "hyper-gateway-macos"
124 if name == 'Windows': return "hyper-gateway-windows.exe"
125 push_error("Can not start gateway, invalid operating system " + name)
126
127func getCurrentVersion():
128 var file = File.new()
129 var err = file.open(getVersionFilePath(), File.READ)
130 if err != OK:
131 printerr('No existing binary version found', err)
132 return ""
133 var version = file.get_as_text()
134 file.close()
135 return version
136
137func saveCurrentVersion(version):
138 var file = File.new()
139 file.open(getVersionFilePath(), File.WRITE)
140 file.store_string(version)
141 file.close()
142
143func getVersionFilePath():
144 return storageDirectory + 'version.txt'
145
146func getExecutiblePath():
147 var executibleName = getExecutibleName()
148 return storageDirectory + executibleName
149
150func ensureStorageLocation():
151 var dir = Directory.new()
152 var err = dir.make_dir_recursive(storageDirectory)
153 if err != OK:
154 printerr("Unable to create storage directory ", err)
155
156func hasBinary():
157 var version = getCurrentVersion()
158 return version && version.length()
159
160func ensureDownloaded():
161 if autoUpdate || !hasBinary():
162 checkLatestVersion()
163 elif autoStart:
164 ensureSetup()
165
166func checkLatestVersion():
167 var url = latestURL()
168 print('Getting latest version from ' + url)
169 latestInfoRequest.request(url, [], true, HTTPClient.METHOD_GET)
170
171func _on_got_latest(_result, response_code, _headers, body):
172 print('Latest info response', response_code)
173 var parsed = JSON.parse(body.get_string_from_utf8())
174 if parsed.error != OK:
175 printerr("Unable to parse latest version from GitHub:", parsed.error_string)
176 return
177 var info = parsed.result
178 var newVersion = info.tag_name
179
180 print('Latest version:', newVersion)
181
182 var existing = getCurrentVersion()
183
184 if newVersion != existing: downloadLatest(info.assets, newVersion)
185 elif autoStart: ensureSetup()
186
187func downloadLatest(assets, version):
188 var toDownload = null
189 var executibleName = getExecutibleName()
190 for asset in assets:
191 if asset.name != executibleName: continue
192 toDownload = asset
193 if !toDownload:
194 printerr("Unable to find valid executible in latest release")
195 return
196
197 var fromURL = toDownload.browser_download_url
198 var toPath = getExecutiblePath()
199
200 print('Downloading latest binary:',fromURL)
201
202 downloadFileRequest.set_download_file(toPath)
203
204 downloadingVersion = version
205 emit_signal("started_download", version)
206
207 downloadFileRequest.request(fromURL, [], true,HTTPClient.METHOD_GET)
208
209func _on_finished_download(_result, response_code, _headers, _body):
210 var executiblePath = getExecutiblePath()
211 var path = ProjectSettings.globalize_path(executiblePath)
212 var os = OS.get_name()
213
214 # Mark file as executible if not on Windows
215 if os != "Windows":
216 OS.execute("chmod", ["+x", path])
217
218 print("Finished download ", response_code)
219 saveCurrentVersion(downloadingVersion)
220 emit_signal("finished_download", downloadingVersion)
221 if autoStart: ensureSetup()
222
223func repoURL():
224 return "https://api.github.com/repos/" + repositoryName + "/"
225
226func latestURL():
227 return repoURL() + "releases/latest"
hypergodot/hyper_gateway/hyper_gateway.tscn +6 -0
……@@ -0,0 +1,6 @@
1[gd_scene load_steps=2 format=2]
2
3[ext_resource path="res://hypergodot/hyper_gateway/hyper_gateway.gd" type="Script" id=1]
4
5[node name="HyperGateway" type="Node"]
6script = ExtResource( 1 )
hypergodot/hyper_gossip/InfoLoader.gd +143 -0
……@@ -0,0 +1,143 @@
1extends Node
2
3var URLParser = preload("res://src/utils/URLParser.gd")
4var parser = URLParser.new()
5
6signal info(info)
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
19export var defaults = {}
20
21var info_cache = null
22var has_loaded = false
23var picture_path = ''
24
25func _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
35func 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
41func 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
61func load_info():
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(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
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 image = info_cache.image
100
101 emit_signal("info", info_cache)
102
103 if image != null and autoload_image:
104 download_image(image)
105
106func _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
121func _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
130func _on_updateInfoRequest_request_completed(_result, _response_code, _headers, _body):
131 emit_signal("updated")
132
133func _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)
hypergodot/hyper_gossip/InfoLoader.tscn +29 -0
……@@ -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"]
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"]
hypergodot/hyper_gossip/ListGossip.gd +48 -0
……@@ -0,0 +1,48 @@
1extends HyperGossip
2
3signal list_items(items)
4
5class_name ListGossip
6
7export var id_key = 'url'
8export var items_event = 'items'
9
10# Enables you to gossip a list of items, useful for game lobbies
11var items = []
12
13func _ready():
14 ._ready()
15 connect("event", self, "_on_event_list_gossip")
16
17func 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
26func has_item(item):
27 for existing in items:
28 if existing[id_key] == item[id_key]:
29 return true
30 return false
31
32func broadcast_items():
33 emit_signal('list_items', items)
34 broadcast_event(items_event, {'items': items})
35
36func _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
hypergodot/hyper_gossip/hyper_gossip.gd +140 -0
……@@ -0,0 +1,140 @@
1extends Node
2
3class_name HyperGossip
4
5const LRU = preload("../utils/lru.gd")
6
7signal listening(extension_name)
8signal event(type, data, from)
9signal peers(peers)
10
11export var url = "hyper://blog.mauve.moe/"
12export var extension_name = "hyper-gossip-v1"
13
14var peerListRequest = HyperRequest.new()
15var eventSource = HyperEventSource.new()
16var broadcastRequest = HyperRequest.new()
17
18var seen_messages = LRU.new()
19var requestQueue = []
20var isRequesting = false
21
22func _ready():
23 add_child(peerListRequest)
24 peerListRequest.connect("request_completed", self, "_on_peer_list")
25
26 add_child(broadcastRequest)
27 broadcastRequest.connect("request_completed", self, "_on_broadcast_completed")
28
29 add_child(eventSource)
30 eventSource.connect("event", self, "_on_event")
31 eventSource.connect("response_started", self, "_on_events_started")
32
33func start_listening():
34 load_peers()
35 eventSource.request(_get_extension_listen_url())
36
37func _on_events_started(_statusCode, _responseHeaders):
38 emit_signal("listening", extension_name)
39
40func _enqueue_broadcast_request(reqURL, body):
41 if isRequesting:
42 requestQueue.push_back({"reqURL": reqURL, "body": body})
43 else: _perform_broadcast_request(reqURL, body)
44
45func _perform_broadcast_request(reqURL, body):
46 isRequesting = true
47 broadcastRequest.request(reqURL, [], false, HTTPClient.METHOD_POST, body)
48
49func _on_broadcast_completed(_result, _response_code, _headers, _body):
50 isRequesting = false
51 if requestQueue.size() == 0: return
52 var next = requestQueue.pop_front()
53 _perform_broadcast_request(next.reqURL, next.body)
54
55func rebroadcast(event):
56 var reqURL = _get_extension_url()
57 var body = JSON.print(event)
58 _enqueue_broadcast_request(reqURL, body)
59
60func broadcast_event(type: String, data):
61 var reqURL = _get_extension_url()
62 var body = JSON.print(_generateEvent(type, data))
63 _enqueue_broadcast_request(reqURL, body)
64
65func send_to_peer(peer, type, data):
66 var reqURL = _get_extension_url_to_peer(peer)
67 var body = JSON.print(_generateEvent(type, data))
68 _enqueue_broadcast_request(reqURL, body)
69
70func load_peers():
71 var reqURL = _get_extension_url()
72 peerListRequest.request(reqURL)
73
74func _generateEvent(type, data):
75 var id = _make_id()
76 seen_messages.track(id)
77 return {
78 "id": id,
79 "type": type,
80 "data": data
81 }
82
83func _handle_event(event):
84 var type = event.type
85 var data = event.data
86 var id = event.id
87 var from = event.from
88
89 if seen_messages.has(id):
90 return
91
92 seen_messages.track(id)
93
94 emit_signal("event", type, data, from)
95 rebroadcast(event)
96
97func _on_event(data, event, id):
98 # Ignore events for other extensions
99 if event != extension_name: return
100
101 var parsed = JSON.parse(data)
102
103 if parsed.error != OK:
104 printerr("Unable to parse EventSource content " + parsed.error_line + "\n" + data)
105 return
106
107 var result = parsed.result
108 if !("from" in result):
109 result.from = id
110
111 _handle_event(result)
112
113
114func _on_peer_list(_result, response_code, _headers, body):
115 var text = body.get_string_from_utf8()
116 if response_code != 200:
117 printerr('Error listing peers ', response_code, " ", text)
118 return
119
120 var parsed = JSON.parse(text)
121
122 if parsed.error != OK:
123 printerr("Unable to parse Peer List" + String(parsed.error_line))
124 return
125
126 var peers = parsed.result
127
128 emit_signal("peers", peers)
129
130func _get_extension_url_to_peer(peer):
131 return url + '$/extensions/' + extension_name + '/' + peer
132
133func _get_extension_url():
134 return url + '$/extensions/' + extension_name
135
136func _get_extension_listen_url():
137 return url + '$/extensions/'
138
139func _make_id():
140 return str(randi()) + str(randi())
hypergodot/hyper_request/hyper_event_source.gd +21 -0
……@@ -0,0 +1,21 @@
1extends HTTPEventSource
2
3class_name HyperEventSource
4
5signal loading_data(url)
6
7export var serverPrefix = "http://127.0.0.1:4973/hyper/"
8
9var watching = false
10
11func request(
12 url,
13 custom_headers=PoolStringArray( ),
14 ssl_validate_domain=false,
15 method=HTTPClient.METHOD_GET, request_data=""
16):
17 assert(url.begins_with("hyper://"))
18 var toLoad = url.replace("hyper://", serverPrefix)
19 emit_signal('loading_data', url)
20 .request(toLoad, custom_headers, ssl_validate_domain, method, request_data)
21pass
hypergodot/hyper_request/hyper_request.gd +34 -0
……@@ -0,0 +1,34 @@
1extends HTTPRequest
2
3class_name HyperRequest
4
5signal loading_data(url)
6
7export var serverPrefix = "http://127.0.0.1:4973/hyper/"
8
9var watching = false
10
11func request(
12 url,
13 custom_headers=PoolStringArray( ),
14 ssl_validate_domain=false,
15 method=HTTPClient.METHOD_GET, request_data=""
16):
17 assert(url.begins_with("hyper://"))
18 var toLoad = url.replace("hyper://", serverPrefix)
19 emit_signal('loading_data', url)
20
21 return .request(toLoad, custom_headers, ssl_validate_domain, method, request_data)
22
23# Only available in 3.3
24#func request_raw(
25# url: String,
26# custom_headers : PoolStringArray=PoolStringArray(),
27# ssl_validate_domain: bool =true,
28# method=0,
29# request_data_raw: PoolByteArray=PoolByteArray( )
30#):
31# assert(url.begins_with("hyper://"))
32# var toLoad = url.replace("hyper://", serverPrefix)
33#
34# return .request_raw(toLoad, custom_headers, ssl_validate_domain, method, request_data_raw)
hypergodot/hyper_request/hyper_request.tscn +6 -0
……@@ -0,0 +1,6 @@
1[gd_scene load_steps=2 format=2]
2
3[ext_resource path="res://hypergodot/hyper_request/hyper_request.gd" type="Script" id=1]
4
5[node name="HyperRequest" type="HTTPRequest"]
6script = ExtResource( 1 )
hypergodot/hyper_request/hyper_uploader.gd +132 -0
……@@ -0,0 +1,132 @@
1extends Node
2
3class_name HyperUploader
4
5# Final URL of upload along with status code
6signal uploaded(url, status)
7signal error(e)
8
9var URLParser = preload("../utils/urlparser.gd")
10
11export var serverPrefix = "http://127.0.0.1:4973/hyper/"
12
13# TODO: Actually use threads
14# export var use_threads = true
15
16var request: HTTPClient = HTTPClient.new()
17var parser = URLParser.new()
18
19func cancel_request():
20 request.close()
21
22func _connect_to_gateway():
23 var parsed = parser.parse(serverPrefix)
24 var port = parsed.port
25 var host = parsed.host
26 var use_ssl = parsed.ssl
27
28 print('Connecting to gateway for upload: ', host, ":", port)
29 var err = request.connect_to_host(host, port, use_ssl, true)
30
31 if err != OK:
32 return err
33
34 # Wait to establish a connection to the host
35 while request.get_status() == HTTPClient.STATUS_CONNECTING or request.get_status() == HTTPClient.STATUS_RESOLVING:
36 err = request.poll()
37 if err != OK:
38 printerr('Unable to connect to EventSource')
39 return err
40 OS.delay_msec(100)
41
42 return OK
43
44func upload_file(url, filePath):
45 # Connect to gateway
46 _connect_to_gateway()
47 # Open file
48 var file = File.new()
49 var err = file.open(filePath, File.READ)
50
51 if err != OK:
52 printerr('Unable to open file for upload')
53 return _emit_err(err)
54
55 var parsedServer = parser.parse(serverPrefix)
56 var parsedURL = parser.parse(url)
57
58 var final_url = parsedServer.path + parsedURL.host + parsedURL.path
59
60 print('PUT ', final_url, " <- ", filePath)
61 err = _perform_file_upload(final_url, file)
62
63 file.close()
64
65 if err != OK:
66 printerr("Unable to make file upload request")
67 return _emit_err(err)
68
69 err = _wait_for_finish()
70
71 if err != OK:
72 return _emit_err(err)
73
74 var statusCode = request.get_response_code()
75 var headers = request.get_response_headers()
76
77 var regex = RegEx.new()
78 regex.compile("link: <(.+)>")
79 var resolve_url = ''
80
81 for header in headers:
82 var result = regex.search(header)
83 if !result: continue
84 resolve_url = result.get_string(1)
85
86 emit_signal('uploaded', resolve_url, statusCode)
87
88 return OK
89
90func _perform_file_upload(absolute_url, file: File):
91 var length = file.get_len()
92 var buffer = file.get_buffer(length)
93 var headers = []
94
95 return request.request_raw(HTTPClient.METHOD_PUT, absolute_url, headers, buffer)
96
97func _wait_for_finish():
98 print('Waiting to finish upload')
99 # Wait for request to get processed and to get the body
100 while (request.get_status() == HTTPClient.STATUS_REQUESTING):
101 var err = request.poll()
102 if err != OK:
103 printerr('Error getting request from upload')
104 return err
105 OS.delay_msec(100)
106
107 if !request.has_response():
108 printerr('No Response from upload')
109 request.close()
110 return ERR_CANT_OPEN
111
112 print("Loading upload response")
113
114 while request.get_status() == HTTPClient.STATUS_BODY:
115 var err = request.poll()
116 if err != OK:
117 printerr('Error loading body from upload')
118 return err
119 print(request.read_response_body_chunk().get_string_from_utf8())
120 OS.delay_usec(100)
121
122 print('Finished loading upload response')
123
124 return OK
125
126func _exit_tree():
127 cancel_request()
128
129func _emit_err(e):
130 printerr('Error uploadng file: ', e)
131 emit_signal('error', e)
132 return e
hypergodot/utils/lru.gd +42 -0
……@@ -0,0 +1,42 @@
1# Least Recently Used Cache
2## Use this if you want to keep track of a set, but with a limit
3
4# Map from value to time last used
5var values = {}
6const DEFAULT_SIZE = 512
7
8var max_size = DEFAULT_SIZE
9
10func _init(new_max = DEFAULT_SIZE):
11 max_size = new_max
12
13func has(value):
14 return value in values
15 pass
16
17func track(value):
18 var time = OS.get_system_time_msecs()
19
20 values[value] = time
21 pass
22
23func is_full():
24 return size() > max_size
25
26func size():
27 return values.size()
28
29func clear_oldest():
30 var oldest = get_oldest()
31 values.erase(oldest)
32 pass
33
34func get_oldest():
35 var oldestTime = OS.get_system_time_msecs()
36 var oldestValue = null
37 for value in values.keys():
38 var valueTime = values[value]
39 if valueTime < oldestTime:
40 oldestTime = valueTime
41 oldestValue = value
42 return oldestValue
hypergodot/utils/urlparser.gd +34 -0
……@@ -0,0 +1,34 @@
1var urlParseRegex = RegEx.new()
2
3const DEFAULT_PORTS = {
4 "http": 80,
5 "https": 443
6}
7
8func _init():
9 urlParseRegex.compile("([^:]+)://([^/]+)(/?.*)")
10
11func parse(url):
12 var parsed = urlParseRegex.search(url)
13
14 if !parsed: return null
15
16 var protocol = parsed.get_string(1).to_lower()
17 var raw_host = parsed.get_string(2)
18 var path = parsed.get_string(3)
19 var host = raw_host
20 var port = DEFAULT_PORTS[protocol] if DEFAULT_PORTS.has(protocol) else 0
21 var ssl = protocol == "https"
22
23 if ":" in raw_host:
24 var split = raw_host.split(':')
25 host = split[0]
26 port = split[1].to_int()
27
28 return {
29 "protocol": protocol,
30 "host": host,
31 "port": port,
32 "path": path,
33 "ssl": ssl,
34 }
levels/level_script.gd +115 -8
……@@ -1,17 +1,124 @@
1extends Spatial
1extends Node
22
3signal player_request_join
3# TODO : Make Debug UI dynamic and not hardcoded in the core Hyper scripts.
4onready var hyperdebugui : Control = $HyperGodotDebugUI
5onready var hyperdebugui_gateway_startstop_button : Button = $HyperGodotDebugUI/HypercoreDebugContainer/GatewayStartStopButton
46
7onready var gateway : HyperGateway = $HyperGodot/HyperGateway
8onready var gossip : HyperGossip = $HyperGodot/HyperGossip
59
6# Declare member variables here. Examples:
7# var a = 2
8# var b = "text"
10onready var lGatewayStatus : Control = hyperdebugui.find_node("GatewayStatus_Value")
11onready var lGossipURL : Control = hyperdebugui.find_node("GossipURL_Value")
12
13onready var player_local : RigidBody = $Players/PlayerLocal
14
15const knownPlayers = {}
16
17const EVENT_PLAYER_POSITION = 'player_position'
18
19var RemotePlayer = preload("res://player/ball/player_ball_remote.tscn")
920
1021
1122# Called when the node enters the scene tree for the first time.
1223func _ready():
24 # gossip = get_tree().get_current_scene().get_node("HyperGodot").get_node("HyperGossip")
1325 pass
26
27func _perform_setup():
28 gossip.start_listening()
29
30func updateGatewayStatus(_pid : int) -> void:
31 if gateway.getIsGatewayRunning():
32 lGatewayStatus.text = "Listening on Port " + String(gateway.port) + " (PID " + String(_pid) + ")"
33 lGossipURL.text = gossip.url
34 hyperdebugui_gateway_startstop_button.text = "Stop Gateway"
35 else:
36 lGatewayStatus.text = "Not Running"
37 lGossipURL.text = "N/A"
38 hyperdebugui_gateway_startstop_button.text = "Start Gateway"
39
40func _on_HyperGossip_listening(_extension_name):
41 print('Started listening on events')
42 # Tell everyone we exist!
43 _broadcast_player()
44
45func _on_HyperGossip_event(type, data, from):
46 # print("Event ", type, " ", from)
47 if type == EVENT_PLAYER_POSITION:
48 _on_remote_player_moved(data, from)
49
50func _on_HyperGateway_started_gateway(_pid : int):
51 if !gateway:
52 gateway = get_tree().get_current_scene().get_node("HyperGodot").get_node("HyperGateway")
53 if !gossip:
54 gossip = get_tree().get_current_scene().get_node("HyperGodot").get_node("HyperGossip")
55 if !hyperdebugui:
56 hyperdebugui = get_tree().get_current_scene().get_node("HyperGodotDebugUI")
57 if !hyperdebugui_gateway_startstop_button:
58 hyperdebugui_gateway_startstop_button = hyperdebugui.find_node("GatewayStartStopButton")
59 if !lGatewayStatus:
60 lGatewayStatus = hyperdebugui.find_node("GatewayStatus_Value")
61 updateGatewayStatus(_pid)
62 _perform_setup()
63
64func _on_HyperGateway_stopped_gateway():
65 updateGatewayStatus(0)
66
67func get_player_object(id):
68 if knownPlayers.has(id): return knownPlayers[id]
69
70 var remotePlayer = RemotePlayer.instance()
71 knownPlayers[id] = remotePlayer
72
73 add_child(remotePlayer)
74 _broadcast_player()
75
76 return remotePlayer
77
78func _broadcast_player():
79 var translation : Vector3 = player_local.translation
80 var rotation : Vector3 = player_local.rotation_degrees
81 var velocity_linear : Vector3 = player_local.linear_velocity
82 var velocity_angular : Vector3 = player_local.angular_velocity
83
84 var data = {
85 #"profile": profile,
86 "translation": {
87 "x": translation.x,
88 "y": translation.y,
89 "z": translation.z
90 },
91 "rotation": {
92 "x": rotation.x,
93 "y": rotation.y,
94 "z": rotation.z
95 },
96 "velocity_linear": {
97 "x": velocity_linear.x,
98 "y": velocity_linear.y,
99 "z": velocity_linear.z
100 },
101 "velocity_angular": {
102 "x": velocity_angular.x,
103 "y": velocity_angular.y,
104 "z": velocity_angular.z
105 }
106 }
107 print('Broadcasting', data)
108 gossip.broadcast_event(EVENT_PLAYER_POSITION, data)
109
110func _on_remote_player_moved(positionData, id):
111 # print('Moving player', id, " ", positionData)
112 var remotePlayer = get_player_object(id)
14113
15# Called every frame. 'delta' is the elapsed time since the previous frame.
16#func _process(delta):
17# pass
114 var translation = positionData.translation
115 var rotation = positionData.rotation
116 var velocity_linear = positionData.velocity_linear
117 var velocity_angular = positionData.velocity_angular
118
119 remotePlayer.update_position(
120 translation,
121 rotation,
122 velocity_linear,
123 velocity_angular
124 )
levels/scenes/level_0.tscn +37 -12
……@@ -1,4 +1,4 @@
1[gd_scene load_steps=10 format=2]
1[gd_scene load_steps=14 format=2]
22
33[ext_resource path="res://assets/levels/level0/assets_level_0.tscn" type="PackedScene" id=1]
44[ext_resource path="res://levels/spawn_point/spawn_point.tscn" type="PackedScene" id=2]
……@@ -9,24 +9,29 @@
99[ext_resource path="res://player/camera/player_camera.tscn" type="PackedScene" id=7]
1010[ext_resource path="res://player/camera/player_camera.gd" type="Script" id=8]
1111[ext_resource path="res://assets/env/cam_env.tres" type="Environment" id=9]
12[ext_resource path="res://hypergodot/hyper_gateway/hyper_gateway.gd" type="Script" id=10]
13[ext_resource path="res://hypergodot/hyper_gossip/hyper_gossip.gd" type="Script" id=11]
14[ext_resource path="res://ui/debug/hypercore/ui_debug_hypercore.tscn" type="PackedScene" id=12]
15[ext_resource path="res://ui/PlayerConfig.tscn" type="PackedScene" id=13]
1216
13[node name="Spatial" type="Spatial"]
17[node name="Spatial" type="Node"]
1418script = ExtResource( 3 )
1519
1620[node name="level_0" parent="." instance=ExtResource( 1 )]
1721transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 1.178, 1.877, -3.673 )
1822
19[node name="player_ball" parent="." instance=ExtResource( 4 )]
20transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -0.733258, 2.26544, -8.91449 )
23[node name="Players" type="Node" parent="."]
2124
22[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
23environment = ExtResource( 9 )
25[node name="PlayerLocal" parent="Players" instance=ExtResource( 4 )]
26transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -0.733258, 2.26544, -8.91449 )
2427
25[node name="player_camera" parent="." instance=ExtResource( 7 )]
28[node name="PlayerLocalCamera" parent="Players" instance=ExtResource( 7 )]
2629pause_mode = 2
27environment = ExtResource( 9 )
2830script = ExtResource( 8 )
29player_ball = NodePath("../player_ball")
31player_ball = NodePath("../PlayerLocal")
32
33[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
34environment = ExtResource( 9 )
3035
3136[node name="spawn_point" parent="." instance=ExtResource( 2 )]
3237transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 11.446, 6.01065, -19.7949 )
……@@ -38,12 +43,32 @@ light_bake_mode = 0
3843shadow_enabled = true
3944shadow_bias = -0.267
4045
41[node name="special_input" type="Spatial" parent="."]
46[node name="special_input" type="Node" parent="."]
4247pause_mode = 2
43transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 17.8105, 5.36633, -12.0799 )
4448script = ExtResource( 5 )
4549
46[node name="DebugUI" parent="." instance=ExtResource( 6 )]
50[node name="PlayerConfigUI" parent="." instance=ExtResource( 13 )]
51
52[node name="BallDebugUI" parent="." instance=ExtResource( 6 )]
53
54[node name="HyperGodotDebugUI" parent="." instance=ExtResource( 12 )]
4755
4856[node name="CameraTween" type="Tween" parent="."]
4957pause_mode = 2
58
59[node name="HyperGodot" type="Node" parent="."]
60
61[node name="HyperGateway" type="Node" parent="HyperGodot"]
62script = ExtResource( 10 )
63autoStart = false
64
65[node name="HyperGossip" type="Node" parent="HyperGodot"]
66script = ExtResource( 11 )
67
68[connection signal="reset_ball" from="Players/PlayerLocal" to="Players/PlayerLocal" method="_on_PlayerLocal_reset_ball"]
69[connection signal="reset_ball" from="special_input" to="Players/PlayerLocal" method="_on_special_input_reset_ball"]
70[connection signal="toggle_hypergateway_startstop" from="HyperGodotDebugUI" to="HyperGodot/HyperGateway" method="_on_HyperGodotDebugUI_toggle_hypergateway_startstop"]
71[connection signal="started_gateway" from="HyperGodot/HyperGateway" to="." method="_on_HyperGateway_started_gateway"]
72[connection signal="stopped_gateway" from="HyperGodot/HyperGateway" to="." method="_on_HyperGateway_stopped_gateway"]
73[connection signal="event" from="HyperGodot/HyperGossip" to="." method="_on_HyperGossip_event"]
74[connection signal="listening" from="HyperGodot/HyperGossip" to="." method="_on_HyperGossip_listening"]
main.tscn +1 -1
……@@ -2,5 +2,5 @@
22
33[ext_resource path="res://main.gd" type="Script" id=1]
44
5[node name="Spatial" type="Spatial"]
5[node name="Spatial" type="Node"]
66script = ExtResource( 1 )
player/ball/player_ball.gd +30 -5
……@@ -7,6 +7,8 @@ enum BallPhysicsMode {
77 PHYSICS_MAX
88}
99
10signal reset_ball()
11
1012var rolling_force : float = 10.0
1113var rolling_force_max : float = 50.0
1214
……@@ -14,25 +16,33 @@ var linear_force : float = 2.0
1416
1517var freefly_force : float = 3.0
1618
19onready var ballInitialOrigin : Vector3 = self.translation
20
1721var ball_physics_mode : int = BallPhysicsMode.ENGINE_PHYSICS_LINEAR
1822var bFirstPersonCamera = false
1923
2024var lVelocity : Label
2125var lAngularVelocity : Label
2226var lBallPhysicsMode : Label
23var sldBallFrictionSlider : Slider
2427
2528func _ready():
26 lVelocity = get_tree().get_current_scene().get_node("DebugUI").get_node("BallVelocity_Value")
27 lAngularVelocity = get_tree().get_current_scene().get_node("DebugUI").get_node("BallAngularVelocity_Value")
28 lBallPhysicsMode = get_tree().get_current_scene().get_node("DebugUI").get_node("BallPhysicsMode_Value")
29 sldBallFrictionSlider = get_tree().get_current_scene().get_node("DebugUI").get_node("BallFrictionSlider")
29 lVelocity = get_tree().get_current_scene().get_node("BallDebugUI").find_node("BallVelocity_Value")
30 lAngularVelocity = get_tree().get_current_scene().get_node("BallDebugUI").find_node("BallAngularVelocity_Value")
31 lBallPhysicsMode = get_tree().get_current_scene().get_node("BallDebugUI").find_node("BallPhysicsMode_Value")
3032
3133 updateBallPhysicsMode()
3234
3335func _process(_delta: float) -> void:
3436 pass
3537
38func resetBall() -> void:
39 self.angular_velocity = Vector3(0, 0, 0)
40 self.linear_velocity = Vector3(0, 0, 0)
41 self.rotation = Vector3(0, 0, 0)
42 ball_physics_mode = BallPhysicsMode.ENGINE_PHYSICS_LINEAR
43 updateBallPhysicsMode()
44 self.translation = ballInitialOrigin
45
3646func updateBallPhysicsMode() -> void:
3747 if(ball_physics_mode == BallPhysicsMode.FREE_FLY):
3848 lBallPhysicsMode.text = "Free Fly"
……@@ -105,6 +115,7 @@ func _physics_process(delta: float) -> void:
105115 self.translate( Vector3(0, 0, freefly_force) * delta)
106116 else:
107117 linear_velocity.z += (linear_force * delta);
118
108119 if Input.is_action_pressed("ball_roll_left"):
109120 if(ball_physics_mode == BallPhysicsMode.ENGINE_PHYSICS_ROLL):
110121 angular_velocity.z = clamp(angular_velocity.z + rolling_force*delta, rolling_force_max * -1, rolling_force_max)
……@@ -119,6 +130,13 @@ func _physics_process(delta: float) -> void:
119130 self.translate( Vector3(freefly_force, 0, 0) * delta)
120131 else:
121132 linear_velocity.x += (linear_force * delta);
133
134 if Input.is_action_pressed("ball_fly_up"):
135 if(ball_physics_mode == BallPhysicsMode.FREE_FLY):
136 self.translate( Vector3(0, freefly_force, 0) * delta)
137 elif Input.is_action_pressed("ball_fly_down"):
138 if(ball_physics_mode == BallPhysicsMode.FREE_FLY):
139 self.translate( Vector3(0, -freefly_force, 0) * delta)
122140
123141 #if Input.is_action_just_pressed("jump") and $FloorCheck.is_colliding():
124142 # apply_impulse(Vector3(), Vector3.UP*1000)
……@@ -126,3 +144,10 @@ func _physics_process(delta: float) -> void:
126144 # Update Debug UI
127145 lVelocity.text = String(self.linear_velocity)
128146 lAngularVelocity.text = String(self.angular_velocity)
147
148
149func _on_PlayerLocal_reset_ball() -> void:
150 resetBall()
151
152func _on_special_input_reset_ball() -> void:
153 resetBall()
player/ball/player_ball.tscn +1 -1
……@@ -9,7 +9,7 @@ rough = true
99
1010[sub_resource type="SphereShape" id=1]
1111
12[node name="Spatial" type="RigidBody"]
12[node name="PlayerBall" type="RigidBody"]
1313physics_material_override = SubResource( 2 )
1414script = ExtResource( 2 )
1515
player/ball/player_ball_remote.gd +10 -0
……@@ -0,0 +1,10 @@
1extends "res://player/ball/player_ball.gd"
2
3func update_position(new_translation, new_rotation, new_velocity_linear, new_velocity_angular):
4 self.translation = new_translation
5 self.rotation = new_rotation
6 self.linear_velocity = new_velocity_linear
7 self.angular_velocity = new_velocity_angular
8
9func _physics_process(_delta):
10 pass
player/ball/player_ball_remote.tscn +7 -0
……@@ -0,0 +1,7 @@
1[gd_scene load_steps=3 format=2]
2
3[ext_resource path="res://player/ball/player_ball.tscn" type="PackedScene" id=1]
4[ext_resource path="res://player/ball/player_ball_remote.gd" type="Script" id=2]
5
6[node name="PlayerBallRemote" instance=ExtResource( 1 )]
7script = ExtResource( 2 )
player/camera/player_camera.gd +0 -2
……@@ -5,12 +5,10 @@ onready var ball = get_node(player_ball)
55onready var offset = ball.to_local(global_transform.origin)
66
77var fCameraGoalFoV : float
8var lCameraAngle : Label
98var tween : Tween
109
1110func _ready():
1211 fCameraGoalFoV = fov
13 lCameraAngle = get_tree().get_current_scene().get_node("DebugUI").get_node("CameraAngle_Real")
1412 tween = get_tree().get_current_scene().get_node("CameraTween")
1513
1614func _process(_delta):
project.godot +56 -0
……@@ -8,6 +8,52 @@
88
99config_version=4
1010
11_global_script_classes=[ {
12"base": "Node",
13"class": "HTTPEventSource",
14"language": "GDScript",
15"path": "res://hypergodot/http_event_source.gd"
16}, {
17"base": "HTTPEventSource",
18"class": "HyperEventSource",
19"language": "GDScript",
20"path": "res://hypergodot/hyper_request/hyper_event_source.gd"
21}, {
22"base": "Node",
23"class": "HyperGateway",
24"language": "GDScript",
25"path": "res://hypergodot/hyper_gateway/hyper_gateway.gd"
26}, {
27"base": "Node",
28"class": "HyperGossip",
29"language": "GDScript",
30"path": "res://hypergodot/hyper_gossip/hyper_gossip.gd"
31}, {
32"base": "HTTPRequest",
33"class": "HyperRequest",
34"language": "GDScript",
35"path": "res://hypergodot/hyper_request/hyper_request.gd"
36}, {
37"base": "Node",
38"class": "HyperUploader",
39"language": "GDScript",
40"path": "res://hypergodot/hyper_request/hyper_uploader.gd"
41}, {
42"base": "HyperGossip",
43"class": "ListGossip",
44"language": "GDScript",
45"path": "res://hypergodot/hyper_gossip/ListGossip.gd"
46} ]
47_global_script_class_icons={
48"HTTPEventSource": "",
49"HyperEventSource": "",
50"HyperGateway": "",
51"HyperGossip": "",
52"HyperRequest": "",
53"HyperUploader": "",
54"ListGossip": ""
55}
56
1157[application]
1258
1359config/name="Marbles of Violence"
……@@ -41,6 +87,16 @@ ball_roll_down={
4187"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":83,"physical_scancode":0,"unicode":0,"echo":false,"script":null)
4288 ]
4389}
90ball_fly_up={
91"deadzone": 0.5,
92"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":81,"physical_scancode":0,"unicode":0,"echo":false,"script":null)
93 ]
94}
95ball_fly_down={
96"deadzone": 0.5,
97"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":69,"physical_scancode":0,"unicode":0,"echo":false,"script":null)
98 ]
99}
44100ball_cycle_physics_mode={
45101"deadzone": 0.5,
46102"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":88,"physical_scancode":0,"unicode":0,"echo":false,"script":null)
special_input.gd +5 -3
……@@ -1,12 +1,13 @@
1extends Spatial
1extends Node
22
33var bDebugPaused : bool = false
44
5signal reset_ball()
6
57func _ready():
68 bDebugPaused = false
79 get_tree().paused = false
810
9# Called every frame. 'delta' is the elapsed time since the previous frame.
1011func _process(_delta):
1112 if Input.is_action_just_pressed("debug_pause"):
1213 bDebugPaused = !bDebugPaused
……@@ -17,4 +18,5 @@ func _process(_delta):
1718 if Input.is_action_just_pressed("ui_cancel"):
1819 get_tree().quit()
1920 if Input.is_action_just_pressed("restart_level"):
20 get_tree().reload_current_scene()
21 # get_tree().reload_current_scene()
22 emit_signal("reset_ball")
ui/PlayerConfig.tscn +30 -0
……@@ -0,0 +1,30 @@
1[gd_scene format=2]
2
3[node name="Control" type="Control"]
4anchor_right = 1.0
5anchor_bottom = 1.0
6__meta__ = {
7"_edit_use_anchors_": false
8}
9
10[node name="PlayerName" type="Label" parent="."]
11margin_left = 39.0
12margin_top = 27.0
13margin_right = 178.0
14margin_bottom = 41.0
15text = "Player Name :"
16align = 2
17__meta__ = {
18"_edit_use_anchors_": false
19}
20
21[node name="PlayerName_Value" type="LineEdit" parent="."]
22margin_left = 186.0
23margin_top = 27.0
24margin_right = 306.0
25margin_bottom = 51.0
26focus_mode = 1
27placeholder_text = "Marble Man"
28__meta__ = {
29"_edit_use_anchors_": false
30}
ui/debug/ball/ui_debug_ball.tscn +41 -45
……@@ -1,6 +1,4 @@
1[gd_scene load_steps=2 format=2]
2
3[sub_resource type="Theme" id=1]
1[gd_scene format=2]
42
53[node name="Control" type="Control"]
64anchor_right = 1.0
……@@ -9,69 +7,67 @@ __meta__ = {
97"_edit_use_anchors_": false
108}
119
12[node name="BallVelocity" type="Label" parent="."]
13margin_left = 39.0
14margin_top = 52.0
15margin_right = 178.0
16margin_bottom = 66.0
10[node name="BallInfoContainer" type="GridContainer" parent="."]
11margin_left = 24.0
12margin_top = 62.0
13margin_right = 629.0
14margin_bottom = 285.0
15columns = 2
16__meta__ = {
17"_edit_use_anchors_": false
18}
19
20[node name="BallVelocity" type="Label" parent="BallInfoContainer"]
21margin_right = 139.0
22margin_bottom = 14.0
1723text = "Ball Velocity :"
1824align = 2
25__meta__ = {
26"_edit_use_anchors_": false
27}
1928
20[node name="BallVelocity_Value" type="Label" parent="."]
21margin_left = 186.0
22margin_top = 52.0
23margin_right = 640.0
24margin_bottom = 66.0
29[node name="BallVelocity_Value" type="Label" parent="BallInfoContainer"]
30margin_left = 143.0
31margin_right = 143.0
32margin_bottom = 14.0
2533__meta__ = {
2634"_edit_use_anchors_": false
2735}
2836
29[node name="BallAngularVelocity" type="Label" parent="."]
30margin_left = 39.0
31margin_top = 77.0
32margin_right = 178.0
33margin_bottom = 91.0
37[node name="BallAngularVelocity" type="Label" parent="BallInfoContainer"]
38margin_top = 18.0
39margin_right = 139.0
40margin_bottom = 32.0
3441text = "Ball Angular Velocity :"
3542align = 2
36
37[node name="BallAngularVelocity_Value" type="Label" parent="."]
38margin_left = 186.0
39margin_top = 77.0
40margin_right = 640.0
41margin_bottom = 91.0
4243__meta__ = {
4344"_edit_use_anchors_": false
4445}
4546
46[node name="BallPhysicsMode" type="Label" parent="."]
47margin_left = 39.0
48margin_top = 102.0
49margin_right = 178.0
50margin_bottom = 116.0
51text = "Ball Physics Mode :"
52align = 2
47[node name="BallAngularVelocity_Value" type="Label" parent="BallInfoContainer"]
48margin_left = 143.0
49margin_top = 18.0
50margin_right = 143.0
51margin_bottom = 32.0
5352__meta__ = {
5453"_edit_use_anchors_": false
5554}
5655
57[node name="BallPhysicsMode_Value" type="Label" parent="."]
58margin_left = 186.0
59margin_top = 102.0
60margin_right = 640.0
61margin_bottom = 116.0
56[node name="BallPhysicsMode" type="Label" parent="BallInfoContainer"]
57margin_top = 36.0
58margin_right = 139.0
59margin_bottom = 50.0
60text = "Ball Physics Mode :"
61align = 2
6262__meta__ = {
6363"_edit_use_anchors_": false
6464}
6565
66[node name="BallFrictionSlider" type="HSlider" parent="."]
67margin_left = 1347.0
68margin_top = 55.0
69margin_right = 1595.0
70margin_bottom = 71.0
71theme = SubResource( 1 )
72max_value = 1.0
73step = 0.1
74scrollable = false
66[node name="BallPhysicsMode_Value" type="Label" parent="BallInfoContainer"]
67margin_left = 143.0
68margin_top = 36.0
69margin_right = 143.0
70margin_bottom = 50.0
7571__meta__ = {
7672"_edit_use_anchors_": false
7773}
ui/debug/hypercore/ui_debug_hypercore.gd +28 -0
……@@ -0,0 +1,28 @@
1extends Control
2
3signal toggle_hypergateway_startstop(start)
4
5var hypergateway : HyperGateway
6
7
8func _ready():
9 # Find Potential HyperGateway Node
10 hypergateway = get_tree().root.find_node("HyperGateway")
11 if(hypergateway):
12 hypergateway.start()
13
14func _process(_delta):
15 hypergateway = get_tree().root.find_node("HyperGateway")
16 if(hypergateway):
17 hypergateway.start()
18
19
20func _on_GatewayStartStopButton_button_up():
21 if(!hypergateway):
22 # TODO : Make this find a potential HyperGateway node based on classtype, and not assuming scene root node name is Spatial.
23 hypergateway = get_tree().root.get_node("Spatial").find_node("HyperGateway")
24 if(hypergateway):
25 if( hypergateway.getIsGatewayRunning() ):
26 hypergateway.stop()
27 else:
28 hypergateway.start()
ui/debug/hypercore/ui_debug_hypercore.tscn +77 -0
……@@ -0,0 +1,77 @@
1[gd_scene load_steps=2 format=2]
2
3[ext_resource path="res://ui/debug/hypercore/ui_debug_hypercore.gd" type="Script" id=1]
4
5[node name="Control" type="Control"]
6anchor_right = 1.0
7anchor_bottom = 1.0
8script = ExtResource( 1 )
9__meta__ = {
10"_edit_use_anchors_": false
11}
12
13[node name="HypercoreDebugContainer" type="GridContainer" parent="."]
14margin_left = 1052.0
15margin_top = 62.0
16margin_right = 1591.0
17margin_bottom = 285.0
18columns = 2
19__meta__ = {
20"_edit_use_anchors_": false
21}
22
23[node name="GatewayStartStopButton" type="Button" parent="HypercoreDebugContainer"]
24margin_right = 105.0
25margin_bottom = 20.0
26text = "Start Gateway"
27__meta__ = {
28"_edit_use_anchors_": false
29}
30
31[node name="Label" type="Label" parent="HypercoreDebugContainer"]
32margin_left = 109.0
33margin_top = 3.0
34margin_right = 187.0
35margin_bottom = 17.0
36__meta__ = {
37"_edit_use_anchors_": false
38}
39
40[node name="GatewayStatus" type="Label" parent="HypercoreDebugContainer"]
41margin_top = 24.0
42margin_right = 105.0
43margin_bottom = 38.0
44text = "Gateway Status :"
45align = 2
46__meta__ = {
47"_edit_use_anchors_": false
48}
49
50[node name="GatewayStatus_Value" type="Label" parent="HypercoreDebugContainer"]
51margin_left = 109.0
52margin_top = 24.0
53margin_right = 187.0
54margin_bottom = 38.0
55text = "Not Running"
56__meta__ = {
57"_edit_use_anchors_": false
58}
59
60[node name="GossipURL" type="Label" parent="HypercoreDebugContainer"]
61margin_top = 42.0
62margin_right = 105.0
63margin_bottom = 56.0
64text = "Gossip URL :"
65align = 2
66__meta__ = {
67"_edit_use_anchors_": false
68}
69
70[node name="GossipURL_Value" type="Label" parent="HypercoreDebugContainer"]
71margin_left = 109.0
72margin_top = 42.0
73margin_right = 187.0
74margin_bottom = 56.0
75text = "N/A"
76
77[connection signal="button_up" from="HypercoreDebugContainer/GatewayStartStopButton" to="." method="_on_GatewayStartStopButton_button_up"]