| … | … | @@ -0,0 +1,143 @@ |
| 1 | extends Node | |
| 2 | ||
| 3 | class_name HTTPEventSource | |
| 4 | ||
| 5 | const URLParser = preload("./utils/urlparser.gd") | |
| 6 | ||
| 7 | signal event(data, event, id) | |
| 8 | signal response_started(statusCode, responseHeaders) | |
| 9 | ||
| 10 | var http = HTTPClient.new() | |
| 11 | var urlParser = URLParser.new() | |
| 12 | ||
| 13 | func cancel_request(): | |
| 14 | http.close() | |
| 15 | pass | |
| 16 | ||
| 17 | func 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 | ||
| 48 | func _emit_started(statusCode, responseHeaders): | |
| 49 | print("Emitting started") | |
| 50 | emit_signal("response_started", statusCode, responseHeaders) | |
| 51 | ||
| 52 | func _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") | |
| 136 | pass | |
| 137 | ||
| 138 | func _emitEvent(params): | |
| 139 | emit_signal("event", params.data, params.name, params.id) | |
| 140 | pass | |
| 141 | ||
| 142 | func _exit_tree(): | |
| 143 | cancel_request() |
| … | … | @@ -0,0 +1,227 @@ |
| 1 | extends Node | |
| 2 | ||
| 3 | const URLParser = preload("../utils/urlparser.gd") | |
| 4 | ||
| 5 | class_name HyperGateway | |
| 6 | ||
| 7 | signal starting_gateway() | |
| 8 | signal started_gateway(pid) | |
| 9 | ||
| 10 | signal stopping_gateway() | |
| 11 | signal stopped_gateway() | |
| 12 | ||
| 13 | signal started_download(version) | |
| 14 | signal finished_download(version) | |
| 15 | ||
| 16 | export var writable: bool = true | |
| 17 | export var persist: bool = true | |
| 18 | ||
| 19 | export var autoStart: bool = true | |
| 20 | export var autoDownload: bool = true | |
| 21 | export var autoUpdate: bool = false | |
| 22 | ||
| 23 | const DEFAULT_PORT = 4973 | |
| 24 | ||
| 25 | export var port: int = DEFAULT_PORT | |
| 26 | export var storageDirectory: String = "user://gateway-data/" | |
| 27 | export var repositoryName: String = "rangermauve/hyper-gateway" | |
| 28 | export 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 | ||
| 33 | var processPID : int = 0 | |
| 34 | var latestInfoRequest = HTTPRequest.new() | |
| 35 | var downloadFileRequest = HTTPRequest.new() | |
| 36 | var downloadingVersion = null | |
| 37 | var downloadThread = null | |
| 38 | ||
| 39 | func _exit_tree(): | |
| 40 | cleanupGateway() | |
| 41 | ||
| 42 | func _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. | |
| 47 | func _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 | ||
| 58 | func start(): | |
| 59 | autoStart = true | |
| 60 | if autoDownload: | |
| 61 | ensureDownloaded() | |
| 62 | else: ensureSetup() | |
| 63 | ||
| 64 | func stop(): | |
| 65 | cleanupGateway() | |
| 66 | ||
| 67 | func 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 | ||
| 81 | func 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 | ||
| 110 | func ensureSetup(): | |
| 111 | if getIsGatewayRunning(): return | |
| 112 | setupGateway() | |
| 113 | ||
| 114 | func getIsGatewayRunning() -> bool: | |
| 115 | if processPID != 0: | |
| 116 | return true; | |
| 117 | else: | |
| 118 | return false; | |
| 119 | ||
| 120 | func 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 | ||
| 127 | func 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 | ||
| 137 | func saveCurrentVersion(version): | |
| 138 | var file = File.new() | |
| 139 | file.open(getVersionFilePath(), File.WRITE) | |
| 140 | file.store_string(version) | |
| 141 | file.close() | |
| 142 | ||
| 143 | func getVersionFilePath(): | |
| 144 | return storageDirectory + 'version.txt' | |
| 145 | ||
| 146 | func getExecutiblePath(): | |
| 147 | var executibleName = getExecutibleName() | |
| 148 | return storageDirectory + executibleName | |
| 149 | ||
| 150 | func 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 | ||
| 156 | func hasBinary(): | |
| 157 | var version = getCurrentVersion() | |
| 158 | return version && version.length() | |
| 159 | ||
| 160 | func ensureDownloaded(): | |
| 161 | if autoUpdate || !hasBinary(): | |
| 162 | checkLatestVersion() | |
| 163 | elif autoStart: | |
| 164 | ensureSetup() | |
| 165 | ||
| 166 | func checkLatestVersion(): | |
| 167 | var url = latestURL() | |
| 168 | print('Getting latest version from ' + url) | |
| 169 | latestInfoRequest.request(url, [], true, HTTPClient.METHOD_GET) | |
| 170 | ||
| 171 | func _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 | ||
| 187 | func 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 | ||
| 209 | func _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 | ||
| 223 | func repoURL(): | |
| 224 | return "https://api.github.com/repos/" + repositoryName + "/" | |
| 225 | ||
| 226 | func latestURL(): | |
| 227 | return repoURL() + "releases/latest" |
| … | … | @@ -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"] | |
| 6 | script = ExtResource( 1 ) |
| … | … | @@ -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 |
| … | … | @@ -0,0 +1,140 @@ |
| 1 | extends Node | |
| 2 | ||
| 3 | class_name HyperGossip | |
| 4 | ||
| 5 | const LRU = preload("../utils/lru.gd") | |
| 6 | ||
| 7 | signal listening(extension_name) | |
| 8 | signal event(type, data, from) | |
| 9 | signal peers(peers) | |
| 10 | ||
| 11 | export var url = "hyper://blog.mauve.moe/" | |
| 12 | export var extension_name = "hyper-gossip-v1" | |
| 13 | ||
| 14 | var peerListRequest = HyperRequest.new() | |
| 15 | var eventSource = HyperEventSource.new() | |
| 16 | var broadcastRequest = HyperRequest.new() | |
| 17 | ||
| 18 | var seen_messages = LRU.new() | |
| 19 | var requestQueue = [] | |
| 20 | var isRequesting = false | |
| 21 | ||
| 22 | func _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 | ||
| 33 | func start_listening(): | |
| 34 | load_peers() | |
| 35 | eventSource.request(_get_extension_listen_url()) | |
| 36 | ||
| 37 | func _on_events_started(_statusCode, _responseHeaders): | |
| 38 | emit_signal("listening", extension_name) | |
| 39 | ||
| 40 | func _enqueue_broadcast_request(reqURL, body): | |
| 41 | if isRequesting: | |
| 42 | requestQueue.push_back({"reqURL": reqURL, "body": body}) | |
| 43 | else: _perform_broadcast_request(reqURL, body) | |
| 44 | ||
| 45 | func _perform_broadcast_request(reqURL, body): | |
| 46 | isRequesting = true | |
| 47 | broadcastRequest.request(reqURL, [], false, HTTPClient.METHOD_POST, body) | |
| 48 | ||
| 49 | func _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 | ||
| 55 | func rebroadcast(event): | |
| 56 | var reqURL = _get_extension_url() | |
| 57 | var body = JSON.print(event) | |
| 58 | _enqueue_broadcast_request(reqURL, body) | |
| 59 | ||
| 60 | func 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 | ||
| 65 | func 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 | ||
| 70 | func load_peers(): | |
| 71 | var reqURL = _get_extension_url() | |
| 72 | peerListRequest.request(reqURL) | |
| 73 | ||
| 74 | func _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 | ||
| 83 | func _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 | ||
| 97 | func _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 | ||
| 114 | func _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 | ||
| 130 | func _get_extension_url_to_peer(peer): | |
| 131 | return url + '$/extensions/' + extension_name + '/' + peer | |
| 132 | ||
| 133 | func _get_extension_url(): | |
| 134 | return url + '$/extensions/' + extension_name | |
| 135 | ||
| 136 | func _get_extension_listen_url(): | |
| 137 | return url + '$/extensions/' | |
| 138 | ||
| 139 | func _make_id(): | |
| 140 | return str(randi()) + str(randi()) |
| … | … | @@ -0,0 +1,21 @@ |
| 1 | extends HTTPEventSource | |
| 2 | ||
| 3 | class_name HyperEventSource | |
| 4 | ||
| 5 | signal loading_data(url) | |
| 6 | ||
| 7 | export var serverPrefix = "http://127.0.0.1:4973/hyper/" | |
| 8 | ||
| 9 | var watching = false | |
| 10 | ||
| 11 | func 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) | |
| 21 | pass |
| … | … | @@ -0,0 +1,34 @@ |
| 1 | extends HTTPRequest | |
| 2 | ||
| 3 | class_name HyperRequest | |
| 4 | ||
| 5 | signal loading_data(url) | |
| 6 | ||
| 7 | export var serverPrefix = "http://127.0.0.1:4973/hyper/" | |
| 8 | ||
| 9 | var watching = false | |
| 10 | ||
| 11 | func 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) |
| … | … | @@ -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"] | |
| 6 | script = ExtResource( 1 ) |
| … | … | @@ -0,0 +1,132 @@ |
| 1 | extends Node | |
| 2 | ||
| 3 | class_name HyperUploader | |
| 4 | ||
| 5 | # Final URL of upload along with status code | |
| 6 | signal uploaded(url, status) | |
| 7 | signal error(e) | |
| 8 | ||
| 9 | var URLParser = preload("../utils/urlparser.gd") | |
| 10 | ||
| 11 | export var serverPrefix = "http://127.0.0.1:4973/hyper/" | |
| 12 | ||
| 13 | # TODO: Actually use threads | |
| 14 | # export var use_threads = true | |
| 15 | ||
| 16 | var request: HTTPClient = HTTPClient.new() | |
| 17 | var parser = URLParser.new() | |
| 18 | ||
| 19 | func cancel_request(): | |
| 20 | request.close() | |
| 21 | ||
| 22 | func _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 | ||
| 44 | func 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 | ||
| 90 | func _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 | ||
| 97 | func _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 | ||
| 126 | func _exit_tree(): | |
| 127 | cancel_request() | |
| 128 | ||
| 129 | func _emit_err(e): | |
| 130 | printerr('Error uploadng file: ', e) | |
| 131 | emit_signal('error', e) | |
| 132 | return e |
| … | … | @@ -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 | |
| 5 | var values = {} | |
| 6 | const DEFAULT_SIZE = 512 | |
| 7 | ||
| 8 | var max_size = DEFAULT_SIZE | |
| 9 | ||
| 10 | func _init(new_max = DEFAULT_SIZE): | |
| 11 | max_size = new_max | |
| 12 | ||
| 13 | func has(value): | |
| 14 | return value in values | |
| 15 | pass | |
| 16 | ||
| 17 | func track(value): | |
| 18 | var time = OS.get_system_time_msecs() | |
| 19 | ||
| 20 | values[value] = time | |
| 21 | pass | |
| 22 | ||
| 23 | func is_full(): | |
| 24 | return size() > max_size | |
| 25 | ||
| 26 | func size(): | |
| 27 | return values.size() | |
| 28 | ||
| 29 | func clear_oldest(): | |
| 30 | var oldest = get_oldest() | |
| 31 | values.erase(oldest) | |
| 32 | pass | |
| 33 | ||
| 34 | func 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 |
| … | … | @@ -0,0 +1,34 @@ |
| 1 | var urlParseRegex = RegEx.new() | |
| 2 | ||
| 3 | const DEFAULT_PORTS = { | |
| 4 | "http": 80, | |
| 5 | "https": 443 | |
| 6 | } | |
| 7 | ||
| 8 | func _init(): | |
| 9 | urlParseRegex.compile("([^:]+)://([^/]+)(/?.*)") | |
| 10 | ||
| 11 | func 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 | } |
| … | … | @@ -1,17 +1,124 @@ |
| 1 | extends Spatial | |
| 1 | extends Node | |
| 2 | 2 | |
| 3 | signal player_request_join | |
| 3 | # TODO : Make Debug UI dynamic and not hardcoded in the core Hyper scripts. | |
| 4 | onready var hyperdebugui : Control = $HyperGodotDebugUI | |
| 5 | onready var hyperdebugui_gateway_startstop_button : Button = $HyperGodotDebugUI/HypercoreDebugContainer/GatewayStartStopButton | |
| 4 | 6 | |
| 7 | onready var gateway : HyperGateway = $HyperGodot/HyperGateway | |
| 8 | onready var gossip : HyperGossip = $HyperGodot/HyperGossip | |
| 5 | 9 | |
| 6 | # Declare member variables here. Examples: | |
| 7 | # var a = 2 | |
| 8 | # var b = "text" | |
| 10 | onready var lGatewayStatus : Control = hyperdebugui.find_node("GatewayStatus_Value") | |
| 11 | onready var lGossipURL : Control = hyperdebugui.find_node("GossipURL_Value") | |
| 12 | ||
| 13 | onready var player_local : RigidBody = $Players/PlayerLocal | |
| 14 | ||
| 15 | const knownPlayers = {} | |
| 16 | ||
| 17 | const EVENT_PLAYER_POSITION = 'player_position' | |
| 18 | ||
| 19 | var RemotePlayer = preload("res://player/ball/player_ball_remote.tscn") | |
| 9 | 20 | |
| 10 | 21 | |
| 11 | 22 | # Called when the node enters the scene tree for the first time. |
| 12 | 23 | func _ready(): |
| 24 | # gossip = get_tree().get_current_scene().get_node("HyperGodot").get_node("HyperGossip") | |
| 13 | 25 | pass |
| 26 | ||
| 27 | func _perform_setup(): | |
| 28 | gossip.start_listening() | |
| 29 | ||
| 30 | func 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 | ||
| 40 | func _on_HyperGossip_listening(_extension_name): | |
| 41 | print('Started listening on events') | |
| 42 | # Tell everyone we exist! | |
| 43 | _broadcast_player() | |
| 44 | ||
| 45 | func _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 | ||
| 50 | func _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 | ||
| 64 | func _on_HyperGateway_stopped_gateway(): | |
| 65 | updateGatewayStatus(0) | |
| 66 | ||
| 67 | func 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 | ||
| 78 | func _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 | ||
| 110 | func _on_remote_player_moved(positionData, id): | |
| 111 | # print('Moving player', id, " ", positionData) | |
| 112 | var remotePlayer = get_player_object(id) | |
| 14 | 113 | |
| 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 | ) |
| … | … | @@ -1,4 +1,4 @@ |
| 1 | [gd_scene load_steps=10 format=2] | |
| 1 | [gd_scene load_steps=14 format=2] | |
| 2 | 2 | |
| 3 | 3 | [ext_resource path="res://assets/levels/level0/assets_level_0.tscn" type="PackedScene" id=1] |
| 4 | 4 | [ext_resource path="res://levels/spawn_point/spawn_point.tscn" type="PackedScene" id=2] |
| … | … | @@ -9,24 +9,29 @@ |
| 9 | 9 | [ext_resource path="res://player/camera/player_camera.tscn" type="PackedScene" id=7] |
| 10 | 10 | [ext_resource path="res://player/camera/player_camera.gd" type="Script" id=8] |
| 11 | 11 | [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] | |
| 12 | 16 | |
| 13 | [node name="Spatial" type="Spatial"] | |
| 17 | [node name="Spatial" type="Node"] | |
| 14 | 18 | script = ExtResource( 3 ) |
| 15 | 19 | |
| 16 | 20 | [node name="level_0" parent="." instance=ExtResource( 1 )] |
| 17 | 21 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 1.178, 1.877, -3.673 ) |
| 18 | 22 | |
| 19 | [node name="player_ball" parent="." instance=ExtResource( 4 )] | |
| 20 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -0.733258, 2.26544, -8.91449 ) | |
| 23 | [node name="Players" type="Node" parent="."] | |
| 21 | 24 | |
| 22 | [node name="WorldEnvironment" type="WorldEnvironment" parent="."] | |
| 23 | environment = ExtResource( 9 ) | |
| 25 | [node name="PlayerLocal" parent="Players" instance=ExtResource( 4 )] | |
| 26 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, -0.733258, 2.26544, -8.91449 ) | |
| 24 | 27 | |
| 25 | [node name="player_camera" parent="." instance=ExtResource( 7 )] | |
| 28 | [node name="PlayerLocalCamera" parent="Players" instance=ExtResource( 7 )] | |
| 26 | 29 | pause_mode = 2 |
| 27 | environment = ExtResource( 9 ) | |
| 28 | 30 | script = ExtResource( 8 ) |
| 29 | player_ball = NodePath("../player_ball") | |
| 31 | player_ball = NodePath("../PlayerLocal") | |
| 32 | ||
| 33 | [node name="WorldEnvironment" type="WorldEnvironment" parent="."] | |
| 34 | environment = ExtResource( 9 ) | |
| 30 | 35 | |
| 31 | 36 | [node name="spawn_point" parent="." instance=ExtResource( 2 )] |
| 32 | 37 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 11.446, 6.01065, -19.7949 ) |
| … | … | @@ -38,12 +43,32 @@ light_bake_mode = 0 |
| 38 | 43 | shadow_enabled = true |
| 39 | 44 | shadow_bias = -0.267 |
| 40 | 45 | |
| 41 | [node name="special_input" type="Spatial" parent="."] | |
| 46 | [node name="special_input" type="Node" parent="."] | |
| 42 | 47 | pause_mode = 2 |
| 43 | transform = Transform( 1, 0, 0, 0, 1, 0, 0, 0, 1, 17.8105, 5.36633, -12.0799 ) | |
| 44 | 48 | script = ExtResource( 5 ) |
| 45 | 49 | |
| 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 )] | |
| 47 | 55 | |
| 48 | 56 | [node name="CameraTween" type="Tween" parent="."] |
| 49 | 57 | pause_mode = 2 |
| 58 | ||
| 59 | [node name="HyperGodot" type="Node" parent="."] | |
| 60 | ||
| 61 | [node name="HyperGateway" type="Node" parent="HyperGodot"] | |
| 62 | script = ExtResource( 10 ) | |
| 63 | autoStart = false | |
| 64 | ||
| 65 | [node name="HyperGossip" type="Node" parent="HyperGodot"] | |
| 66 | script = 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"] |
| … | … | @@ -2,5 +2,5 @@ |
| 2 | 2 | |
| 3 | 3 | [ext_resource path="res://main.gd" type="Script" id=1] |
| 4 | 4 | |
| 5 | [node name="Spatial" type="Spatial"] | |
| 5 | [node name="Spatial" type="Node"] | |
| 6 | 6 | script = ExtResource( 1 ) |
| … | … | @@ -7,6 +7,8 @@ enum BallPhysicsMode { |
| 7 | 7 | PHYSICS_MAX |
| 8 | 8 | } |
| 9 | 9 | |
| 10 | signal reset_ball() | |
| 11 | ||
| 10 | 12 | var rolling_force : float = 10.0 |
| 11 | 13 | var rolling_force_max : float = 50.0 |
| 12 | 14 | |
| … | … | @@ -14,25 +16,33 @@ var linear_force : float = 2.0 |
| 14 | 16 | |
| 15 | 17 | var freefly_force : float = 3.0 |
| 16 | 18 | |
| 19 | onready var ballInitialOrigin : Vector3 = self.translation | |
| 20 | ||
| 17 | 21 | var ball_physics_mode : int = BallPhysicsMode.ENGINE_PHYSICS_LINEAR |
| 18 | 22 | var bFirstPersonCamera = false |
| 19 | 23 | |
| 20 | 24 | var lVelocity : Label |
| 21 | 25 | var lAngularVelocity : Label |
| 22 | 26 | var lBallPhysicsMode : Label |
| 23 | var sldBallFrictionSlider : Slider | |
| 24 | 27 | |
| 25 | 28 | func _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") | |
| 30 | 32 | |
| 31 | 33 | updateBallPhysicsMode() |
| 32 | 34 | |
| 33 | 35 | func _process(_delta: float) -> void: |
| 34 | 36 | pass |
| 35 | 37 | |
| 38 | func 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 | ||
| 36 | 46 | func updateBallPhysicsMode() -> void: |
| 37 | 47 | if(ball_physics_mode == BallPhysicsMode.FREE_FLY): |
| 38 | 48 | lBallPhysicsMode.text = "Free Fly" |
| … | … | @@ -105,6 +115,7 @@ func _physics_process(delta: float) -> void: |
| 105 | 115 | self.translate( Vector3(0, 0, freefly_force) * delta) |
| 106 | 116 | else: |
| 107 | 117 | linear_velocity.z += (linear_force * delta); |
| 118 | ||
| 108 | 119 | if Input.is_action_pressed("ball_roll_left"): |
| 109 | 120 | if(ball_physics_mode == BallPhysicsMode.ENGINE_PHYSICS_ROLL): |
| 110 | 121 | 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: |
| 119 | 130 | self.translate( Vector3(freefly_force, 0, 0) * delta) |
| 120 | 131 | else: |
| 121 | 132 | 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) | |
| 122 | 140 | |
| 123 | 141 | #if Input.is_action_just_pressed("jump") and $FloorCheck.is_colliding(): |
| 124 | 142 | # apply_impulse(Vector3(), Vector3.UP*1000) |
| … | … | @@ -126,3 +144,10 @@ func _physics_process(delta: float) -> void: |
| 126 | 144 | # Update Debug UI |
| 127 | 145 | lVelocity.text = String(self.linear_velocity) |
| 128 | 146 | lAngularVelocity.text = String(self.angular_velocity) |
| 147 | ||
| 148 | ||
| 149 | func _on_PlayerLocal_reset_ball() -> void: | |
| 150 | resetBall() | |
| 151 | ||
| 152 | func _on_special_input_reset_ball() -> void: | |
| 153 | resetBall() |
| … | … | @@ -9,7 +9,7 @@ rough = true |
| 9 | 9 | |
| 10 | 10 | [sub_resource type="SphereShape" id=1] |
| 11 | 11 | |
| 12 | [node name="Spatial" type="RigidBody"] | |
| 12 | [node name="PlayerBall" type="RigidBody"] | |
| 13 | 13 | physics_material_override = SubResource( 2 ) |
| 14 | 14 | script = ExtResource( 2 ) |
| 15 | 15 |
| … | … | @@ -0,0 +1,10 @@ |
| 1 | extends "res://player/ball/player_ball.gd" | |
| 2 | ||
| 3 | func 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 | ||
| 9 | func _physics_process(_delta): | |
| 10 | pass |
| … | … | @@ -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 )] | |
| 7 | script = ExtResource( 2 ) |
| … | … | @@ -5,12 +5,10 @@ onready var ball = get_node(player_ball) |
| 5 | 5 | onready var offset = ball.to_local(global_transform.origin) |
| 6 | 6 | |
| 7 | 7 | var fCameraGoalFoV : float |
| 8 | var lCameraAngle : Label | |
| 9 | 8 | var tween : Tween |
| 10 | 9 | |
| 11 | 10 | func _ready(): |
| 12 | 11 | fCameraGoalFoV = fov |
| 13 | lCameraAngle = get_tree().get_current_scene().get_node("DebugUI").get_node("CameraAngle_Real") | |
| 14 | 12 | tween = get_tree().get_current_scene().get_node("CameraTween") |
| 15 | 13 | |
| 16 | 14 | func _process(_delta): |
| … | … | @@ -8,6 +8,52 @@ |
| 8 | 8 | |
| 9 | 9 | config_version=4 |
| 10 | 10 | |
| 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 | ||
| 11 | 57 | [application] |
| 12 | 58 | |
| 13 | 59 | config/name="Marbles of Violence" |
| … | … | @@ -41,6 +87,16 @@ ball_roll_down={ |
| 41 | 87 | "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) |
| 42 | 88 | ] |
| 43 | 89 | } |
| 90 | ball_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 | } | |
| 95 | ball_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 | } | |
| 44 | 100 | ball_cycle_physics_mode={ |
| 45 | 101 | "deadzone": 0.5, |
| 46 | 102 | "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) |
| … | … | @@ -1,12 +1,13 @@ |
| 1 | extends Spatial | |
| 1 | extends Node | |
| 2 | 2 | |
| 3 | 3 | var bDebugPaused : bool = false |
| 4 | 4 | |
| 5 | signal reset_ball() | |
| 6 | ||
| 5 | 7 | func _ready(): |
| 6 | 8 | bDebugPaused = false |
| 7 | 9 | get_tree().paused = false |
| 8 | 10 | |
| 9 | # Called every frame. 'delta' is the elapsed time since the previous frame. | |
| 10 | 11 | func _process(_delta): |
| 11 | 12 | if Input.is_action_just_pressed("debug_pause"): |
| 12 | 13 | bDebugPaused = !bDebugPaused |
| … | … | @@ -17,4 +18,5 @@ func _process(_delta): |
| 17 | 18 | if Input.is_action_just_pressed("ui_cancel"): |
| 18 | 19 | get_tree().quit() |
| 19 | 20 | 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") |
| … | … | @@ -0,0 +1,30 @@ |
| 1 | [gd_scene format=2] | |
| 2 | ||
| 3 | [node name="Control" type="Control"] | |
| 4 | anchor_right = 1.0 | |
| 5 | anchor_bottom = 1.0 | |
| 6 | __meta__ = { | |
| 7 | "_edit_use_anchors_": false | |
| 8 | } | |
| 9 | ||
| 10 | [node name="PlayerName" type="Label" parent="."] | |
| 11 | margin_left = 39.0 | |
| 12 | margin_top = 27.0 | |
| 13 | margin_right = 178.0 | |
| 14 | margin_bottom = 41.0 | |
| 15 | text = "Player Name :" | |
| 16 | align = 2 | |
| 17 | __meta__ = { | |
| 18 | "_edit_use_anchors_": false | |
| 19 | } | |
| 20 | ||
| 21 | [node name="PlayerName_Value" type="LineEdit" parent="."] | |
| 22 | margin_left = 186.0 | |
| 23 | margin_top = 27.0 | |
| 24 | margin_right = 306.0 | |
| 25 | margin_bottom = 51.0 | |
| 26 | focus_mode = 1 | |
| 27 | placeholder_text = "Marble Man" | |
| 28 | __meta__ = { | |
| 29 | "_edit_use_anchors_": false | |
| 30 | } |
| … | … | @@ -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] | |
| 4 | 2 | |
| 5 | 3 | [node name="Control" type="Control"] |
| 6 | 4 | anchor_right = 1.0 |
| … | … | @@ -9,69 +7,67 @@ __meta__ = { |
| 9 | 7 | "_edit_use_anchors_": false |
| 10 | 8 | } |
| 11 | 9 | |
| 12 | [node name="BallVelocity" type="Label" parent="."] | |
| 13 | margin_left = 39.0 | |
| 14 | margin_top = 52.0 | |
| 15 | margin_right = 178.0 | |
| 16 | margin_bottom = 66.0 | |
| 10 | [node name="BallInfoContainer" type="GridContainer" parent="."] | |
| 11 | margin_left = 24.0 | |
| 12 | margin_top = 62.0 | |
| 13 | margin_right = 629.0 | |
| 14 | margin_bottom = 285.0 | |
| 15 | columns = 2 | |
| 16 | __meta__ = { | |
| 17 | "_edit_use_anchors_": false | |
| 18 | } | |
| 19 | ||
| 20 | [node name="BallVelocity" type="Label" parent="BallInfoContainer"] | |
| 21 | margin_right = 139.0 | |
| 22 | margin_bottom = 14.0 | |
| 17 | 23 | text = "Ball Velocity :" |
| 18 | 24 | align = 2 |
| 25 | __meta__ = { | |
| 26 | "_edit_use_anchors_": false | |
| 27 | } | |
| 19 | 28 | |
| 20 | [node name="BallVelocity_Value" type="Label" parent="."] | |
| 21 | margin_left = 186.0 | |
| 22 | margin_top = 52.0 | |
| 23 | margin_right = 640.0 | |
| 24 | margin_bottom = 66.0 | |
| 29 | [node name="BallVelocity_Value" type="Label" parent="BallInfoContainer"] | |
| 30 | margin_left = 143.0 | |
| 31 | margin_right = 143.0 | |
| 32 | margin_bottom = 14.0 | |
| 25 | 33 | __meta__ = { |
| 26 | 34 | "_edit_use_anchors_": false |
| 27 | 35 | } |
| 28 | 36 | |
| 29 | [node name="BallAngularVelocity" type="Label" parent="."] | |
| 30 | margin_left = 39.0 | |
| 31 | margin_top = 77.0 | |
| 32 | margin_right = 178.0 | |
| 33 | margin_bottom = 91.0 | |
| 37 | [node name="BallAngularVelocity" type="Label" parent="BallInfoContainer"] | |
| 38 | margin_top = 18.0 | |
| 39 | margin_right = 139.0 | |
| 40 | margin_bottom = 32.0 | |
| 34 | 41 | text = "Ball Angular Velocity :" |
| 35 | 42 | align = 2 |
| 36 | ||
| 37 | [node name="BallAngularVelocity_Value" type="Label" parent="."] | |
| 38 | margin_left = 186.0 | |
| 39 | margin_top = 77.0 | |
| 40 | margin_right = 640.0 | |
| 41 | margin_bottom = 91.0 | |
| 42 | 43 | __meta__ = { |
| 43 | 44 | "_edit_use_anchors_": false |
| 44 | 45 | } |
| 45 | 46 | |
| 46 | [node name="BallPhysicsMode" type="Label" parent="."] | |
| 47 | margin_left = 39.0 | |
| 48 | margin_top = 102.0 | |
| 49 | margin_right = 178.0 | |
| 50 | margin_bottom = 116.0 | |
| 51 | text = "Ball Physics Mode :" | |
| 52 | align = 2 | |
| 47 | [node name="BallAngularVelocity_Value" type="Label" parent="BallInfoContainer"] | |
| 48 | margin_left = 143.0 | |
| 49 | margin_top = 18.0 | |
| 50 | margin_right = 143.0 | |
| 51 | margin_bottom = 32.0 | |
| 53 | 52 | __meta__ = { |
| 54 | 53 | "_edit_use_anchors_": false |
| 55 | 54 | } |
| 56 | 55 | |
| 57 | [node name="BallPhysicsMode_Value" type="Label" parent="."] | |
| 58 | margin_left = 186.0 | |
| 59 | margin_top = 102.0 | |
| 60 | margin_right = 640.0 | |
| 61 | margin_bottom = 116.0 | |
| 56 | [node name="BallPhysicsMode" type="Label" parent="BallInfoContainer"] | |
| 57 | margin_top = 36.0 | |
| 58 | margin_right = 139.0 | |
| 59 | margin_bottom = 50.0 | |
| 60 | text = "Ball Physics Mode :" | |
| 61 | align = 2 | |
| 62 | 62 | __meta__ = { |
| 63 | 63 | "_edit_use_anchors_": false |
| 64 | 64 | } |
| 65 | 65 | |
| 66 | [node name="BallFrictionSlider" type="HSlider" parent="."] | |
| 67 | margin_left = 1347.0 | |
| 68 | margin_top = 55.0 | |
| 69 | margin_right = 1595.0 | |
| 70 | margin_bottom = 71.0 | |
| 71 | theme = SubResource( 1 ) | |
| 72 | max_value = 1.0 | |
| 73 | step = 0.1 | |
| 74 | scrollable = false | |
| 66 | [node name="BallPhysicsMode_Value" type="Label" parent="BallInfoContainer"] | |
| 67 | margin_left = 143.0 | |
| 68 | margin_top = 36.0 | |
| 69 | margin_right = 143.0 | |
| 70 | margin_bottom = 50.0 | |
| 75 | 71 | __meta__ = { |
| 76 | 72 | "_edit_use_anchors_": false |
| 77 | 73 | } |
| … | … | @@ -0,0 +1,28 @@ |
| 1 | extends Control | |
| 2 | ||
| 3 | signal toggle_hypergateway_startstop(start) | |
| 4 | ||
| 5 | var hypergateway : HyperGateway | |
| 6 | ||
| 7 | ||
| 8 | func _ready(): | |
| 9 | # Find Potential HyperGateway Node | |
| 10 | hypergateway = get_tree().root.find_node("HyperGateway") | |
| 11 | if(hypergateway): | |
| 12 | hypergateway.start() | |
| 13 | ||
| 14 | func _process(_delta): | |
| 15 | hypergateway = get_tree().root.find_node("HyperGateway") | |
| 16 | if(hypergateway): | |
| 17 | hypergateway.start() | |
| 18 | ||
| 19 | ||
| 20 | func _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() |
| … | … | @@ -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"] | |
| 6 | anchor_right = 1.0 | |
| 7 | anchor_bottom = 1.0 | |
| 8 | script = ExtResource( 1 ) | |
| 9 | __meta__ = { | |
| 10 | "_edit_use_anchors_": false | |
| 11 | } | |
| 12 | ||
| 13 | [node name="HypercoreDebugContainer" type="GridContainer" parent="."] | |
| 14 | margin_left = 1052.0 | |
| 15 | margin_top = 62.0 | |
| 16 | margin_right = 1591.0 | |
| 17 | margin_bottom = 285.0 | |
| 18 | columns = 2 | |
| 19 | __meta__ = { | |
| 20 | "_edit_use_anchors_": false | |
| 21 | } | |
| 22 | ||
| 23 | [node name="GatewayStartStopButton" type="Button" parent="HypercoreDebugContainer"] | |
| 24 | margin_right = 105.0 | |
| 25 | margin_bottom = 20.0 | |
| 26 | text = "Start Gateway" | |
| 27 | __meta__ = { | |
| 28 | "_edit_use_anchors_": false | |
| 29 | } | |
| 30 | ||
| 31 | [node name="Label" type="Label" parent="HypercoreDebugContainer"] | |
| 32 | margin_left = 109.0 | |
| 33 | margin_top = 3.0 | |
| 34 | margin_right = 187.0 | |
| 35 | margin_bottom = 17.0 | |
| 36 | __meta__ = { | |
| 37 | "_edit_use_anchors_": false | |
| 38 | } | |
| 39 | ||
| 40 | [node name="GatewayStatus" type="Label" parent="HypercoreDebugContainer"] | |
| 41 | margin_top = 24.0 | |
| 42 | margin_right = 105.0 | |
| 43 | margin_bottom = 38.0 | |
| 44 | text = "Gateway Status :" | |
| 45 | align = 2 | |
| 46 | __meta__ = { | |
| 47 | "_edit_use_anchors_": false | |
| 48 | } | |
| 49 | ||
| 50 | [node name="GatewayStatus_Value" type="Label" parent="HypercoreDebugContainer"] | |
| 51 | margin_left = 109.0 | |
| 52 | margin_top = 24.0 | |
| 53 | margin_right = 187.0 | |
| 54 | margin_bottom = 38.0 | |
| 55 | text = "Not Running" | |
| 56 | __meta__ = { | |
| 57 | "_edit_use_anchors_": false | |
| 58 | } | |
| 59 | ||
| 60 | [node name="GossipURL" type="Label" parent="HypercoreDebugContainer"] | |
| 61 | margin_top = 42.0 | |
| 62 | margin_right = 105.0 | |
| 63 | margin_bottom = 56.0 | |
| 64 | text = "Gossip URL :" | |
| 65 | align = 2 | |
| 66 | __meta__ = { | |
| 67 | "_edit_use_anchors_": false | |
| 68 | } | |
| 69 | ||
| 70 | [node name="GossipURL_Value" type="Label" parent="HypercoreDebugContainer"] | |
| 71 | margin_left = 109.0 | |
| 72 | margin_top = 42.0 | |
| 73 | margin_right = 187.0 | |
| 74 | margin_bottom = 56.0 | |
| 75 | text = "N/A" | |
| 76 | ||
| 77 | [connection signal="button_up" from="HypercoreDebugContainer/GatewayStartStopButton" to="." method="_on_GatewayStartStopButton_button_up"] |