| … | … | @@ -2,3 +2,4 @@ |
| 2 | 2 | *.translation |
| 3 | 3 | export_presets.cfg |
| 4 | 4 | .mono/ |
| 5 | addons/ |
| … | … | @@ -2,7 +2,7 @@ |
| 2 | 2 | |
| 3 | 3 | A Godot plugin and example project for loading assets from the [hypercore-protocol](https://hypercore-protocol.org/). |
| 4 | 4 | |
| 5 | *WIP:* This is a work an progress and isn't ready for use yet. Feel free to follow for progress, though. | |
| 5 | *WIP:* This is a work an progress and may have breaking changes. Feel free to follow for progress, though. | |
| 6 | 6 | |
| 7 | 7 | ## How It Works: |
| 8 | 8 | |
| … | … | @@ -22,8 +22,16 @@ A Godot plugin and example project for loading assets from the [hypercore-protoc |
| 22 | 22 | - [ ] Examples for all the methods exposed by the gateway. |
| 23 | 23 | - [x] GET/PUT text files |
| 24 | 24 | - [x] Watch for changes |
| 25 | - [ ] Basic chat app (WIP | |
| 25 | - [x] Basic chat app | |
| 26 | - [x] Load latest hyper-gateway dynamically on boot | |
| 26 | 27 | - [ ] Basic Multiplayer demo using extension messages |
| 28 | - [x] Refactor chat system to be more general purpose (not just texts) | |
| 29 | - [x] Basic demo moving blobs around on a map | |
| 30 | - [ ] Place usernames in a hyperdrive's json file along with an image for your blob | |
| 31 | - [ ] Create custom maps from an image and a JSON file | |
| 32 | - [ ] Auto-discover which maps are active | |
| 33 | - [ ] Enable code to work in Godot Web in Agregore | |
| 34 | - [ ] Enable code to work in Godot Web in regular browsers | |
| 27 | 35 | - [ ] Load gltf scene from hyper |
| 28 | 36 | - [ ] Load VRM avatar from hyper (social multiplayer) [VRM stuff](https://github.com/V-Sekai/godot-vrm/blob/godot3/addons/vrm/import_vrm.gd) |
| 29 | 37 | - [ ] Folder sync (From `hyper://` to fs, from fs to `hyper://`) |
| … | … | @@ -0,0 +1,5 @@ |
| 1 | { | |
| 2 | "failed": [ | |
| 3 | ||
| 4 | ] | |
| 5 | } |
| … | … | @@ -24,18 +24,47 @@ _global_script_classes=[ { |
| 24 | 24 | "language": "GDScript", |
| 25 | 25 | "path": "res://src/HyperGateway/HyperGateway.gd" |
| 26 | 26 | }, { |
| 27 | "base": "Node", | |
| 28 | "class": "HyperGossip", | |
| 29 | "language": "GDScript", | |
| 30 | "path": "res://src/multiplayer/HyperGossip.gd" | |
| 31 | }, { | |
| 27 | 32 | "base": "HTTPRequest", |
| 28 | 33 | "class": "HyperRequest", |
| 29 | 34 | "language": "GDScript", |
| 30 | 35 | "path": "res://src/HyperRequest/HyperRequest.gd" |
| 36 | }, { | |
| 37 | "base": "Reference", | |
| 38 | "class": "WAT", | |
| 39 | "language": "GDScript", | |
| 40 | "path": "res://addons/WAT/namespace.gd" | |
| 41 | }, { | |
| 42 | "base": "Node", | |
| 43 | "class": "WATTest", | |
| 44 | "language": "GDScript", | |
| 45 | "path": "res://addons/WAT/test/test.gd" | |
| 31 | 46 | } ] |
| 32 | 47 | _global_script_class_icons={ |
| 33 | 48 | "HTTPEventSource": "", |
| 34 | 49 | "HyperEventSource": "", |
| 35 | 50 | "HyperGateway": "", |
| 36 | "HyperRequest": "" | |
| 51 | "HyperGossip": "", | |
| 52 | "HyperRequest": "", | |
| 53 | "WAT": "", | |
| 54 | "WATTest": "" | |
| 37 | 55 | } |
| 38 | 56 | |
| 57 | [WAT] | |
| 58 | ||
| 59 | Test_Directory="res://" | |
| 60 | Results_Directory="res://" | |
| 61 | Test_Metadata_Directory="res://" | |
| 62 | Tags=PoolStringArray( ) | |
| 63 | Window_Size=Vector2( 1280, 720 ) | |
| 64 | Minimize_Window_When_Running_Tests=false | |
| 65 | Port=6008 | |
| 66 | Display=8 | |
| 67 | ||
| 39 | 68 | [application] |
| 40 | 69 | |
| 41 | 70 | config/name="HyperGodot" |
| … | … | @@ -48,6 +77,43 @@ window/size/width=666 |
| 48 | 77 | window/size/height=666 |
| 49 | 78 | window/dpi/allow_hidpi=true |
| 50 | 79 | |
| 80 | [editor_plugins] | |
| 81 | ||
| 82 | enabled=PoolStringArray( "res://addons/WAT/plugin.cfg" ) | |
| 83 | ||
| 84 | [input] | |
| 85 | ||
| 86 | left={ | |
| 87 | "deadzone": 0.5, | |
| 88 | "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":65,"unicode":0,"echo":false,"script":null) | |
| 89 | , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777231,"unicode":0,"echo":false,"script":null) | |
| 90 | ] | |
| 91 | } | |
| 92 | right={ | |
| 93 | "deadzone": 0.5, | |
| 94 | "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":68,"unicode":0,"echo":false,"script":null) | |
| 95 | , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777233,"unicode":0,"echo":false,"script":null) | |
| 96 | ] | |
| 97 | } | |
| 98 | up={ | |
| 99 | "deadzone": 0.5, | |
| 100 | "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":87,"unicode":0,"echo":false,"script":null) | |
| 101 | , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777232,"unicode":0,"echo":false,"script":null) | |
| 102 | ] | |
| 103 | } | |
| 104 | down={ | |
| 105 | "deadzone": 0.5, | |
| 106 | "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,"unicode":0,"echo":false,"script":null) | |
| 107 | , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777234,"unicode":0,"echo":false,"script":null) | |
| 108 | ] | |
| 109 | } | |
| 110 | click={ | |
| 111 | "deadzone": 0.5, | |
| 112 | "events": [ Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"button_mask":0,"position":Vector2( 0, 0 ),"global_position":Vector2( 0, 0 ),"factor":1.0,"button_index":1,"pressed":false,"doubleclick":false,"script":null) | |
| 113 | , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":32,"unicode":0,"echo":false,"script":null) | |
| 114 | ] | |
| 115 | } | |
| 116 | ||
| 51 | 117 | [physics] |
| 52 | 118 | |
| 53 | 119 | common/enable_pause_aware_picking=true |
| … | … | @@ -0,0 +1,7 @@ |
| 1 | <?xml version="1.0" ?> | |
| 2 | <testsuites failures="0" name="TestXML" tests="1" time="0.019"> | |
| 3 | <testsuite name="Hyper Gossip" failures="0" tests="1" time="0"> | |
| 4 | <testcase name=" create gateway and connect" time="0"></testcase> | |
| 5 | </testsuite> | |
| 6 | ||
| 7 | </testsuites> |
| … | … | @@ -5,6 +5,7 @@ class_name HTTPEventSource |
| 5 | 5 | const URLParser = preload('./utils/URLParser.gd') |
| 6 | 6 | |
| 7 | 7 | signal event(data, event, id) |
| 8 | signal response_started(statusCode, responseHeaders) | |
| 8 | 9 | |
| 9 | 10 | var http = HTTPClient.new() |
| 10 | 11 | var urlParser = URLParser.new() |
| … | … | @@ -36,7 +37,7 @@ func request( |
| 36 | 37 | "request_data": request_data |
| 37 | 38 | } |
| 38 | 39 | |
| 39 | print('About to request', parsedURL, params) | |
| 40 | print('About to request', parsedURL) | |
| 40 | 41 | |
| 41 | 42 | var thread = Thread.new() |
| 42 | 43 | |
| … | … | @@ -44,14 +45,24 @@ func request( |
| 44 | 45 | |
| 45 | 46 | pass |
| 46 | 47 | |
| 48 | func _emit_started(statusCode, responseHeaders): | |
| 49 | print("Emitting started") | |
| 50 | emit_signal("response_started", statusCode, responseHeaders) | |
| 51 | ||
| 47 | 52 | func _runRequest(params): |
| 48 | 53 | var err = http.connect_to_host(params.host, params.port, params.use_ssl, params.verify_host) |
| 49 | 54 | |
| 55 | if err != OK: | |
| 56 | printerr('Error connecting to EventSource server') | |
| 57 | ||
| 50 | 58 | print('Connected to host') |
| 51 | 59 | |
| 52 | 60 | # Wait to establish a connection to the host |
| 53 | 61 | while http.get_status() == HTTPClient.STATUS_CONNECTING or http.get_status() == HTTPClient.STATUS_RESOLVING: |
| 54 | http.poll() | |
| 62 | err = http.poll() | |
| 63 | if err != OK: | |
| 64 | printerr('Unable to connect to EventSource') | |
| 65 | return | |
| 55 | 66 | OS.delay_msec(100) |
| 56 | 67 | |
| 57 | 68 | print('Able to connect, requesting') |
| … | … | @@ -59,9 +70,16 @@ func _runRequest(params): |
| 59 | 70 | # Start the request |
| 60 | 71 | err = http.request(params.method, params.path, params.headers, params.request_data) |
| 61 | 72 | |
| 73 | if err != OK: | |
| 74 | printerr('Unable to request EventSource') | |
| 75 | return | |
| 76 | ||
| 62 | 77 | # Wait for request to get processed and to get the body |
| 63 | 78 | while (http.get_status() == HTTPClient.STATUS_REQUESTING): |
| 64 | http.poll() | |
| 79 | err = http.poll() | |
| 80 | if err != OK: | |
| 81 | printerr('Unable to request to EventSource') | |
| 82 | return | |
| 65 | 83 | OS.delay_msec(100) |
| 66 | 84 | |
| 67 | 85 | if !http.has_response(): |
| … | … | @@ -72,10 +90,10 @@ func _runRequest(params): |
| 72 | 90 | |
| 73 | 91 | var statusCode = http.get_response_code() |
| 74 | 92 | var responseHeaders = http.get_response_headers() |
| 75 | ||
| 93 | ||
| 76 | 94 | print('Got response ', statusCode) |
| 77 | 95 | |
| 78 | call_deferred("emit_signal", "response_started", statusCode, responseHeaders) | |
| 96 | call_deferred("_emit_started", statusCode, responseHeaders) | |
| 79 | 97 | |
| 80 | 98 | var buffer = "" |
| 81 | 99 | var lastChunkEmpty = false |
| … | … | @@ -10,16 +10,19 @@ signal started_gateway(pid) |
| 10 | 10 | signal started_download(version) |
| 11 | 11 | signal finished_download(version) |
| 12 | 12 | |
| 13 | export var writable = true | |
| 14 | export var persist = true | |
| 13 | export var writable: bool = true | |
| 14 | export var persist: bool = true | |
| 15 | 15 | |
| 16 | export var autoStart = true | |
| 17 | export var autoDownload = true | |
| 18 | export var autoUpdate = false | |
| 16 | export var autoStart: bool = true | |
| 17 | export var autoDownload: bool = true | |
| 18 | export var autoUpdate: bool = false | |
| 19 | 19 | |
| 20 | export var storageDirectory = "user://gateway-data/" | |
| 21 | export var repositoryName = "rangermauve/hyper-gateway" | |
| 22 | export var tagStart = "v2" | |
| 20 | const DEFAULT_PORT = 4973 | |
| 21 | ||
| 22 | export var port: int = DEFAULT_PORT | |
| 23 | export var storageDirectory: String = "user://gateway-data/" | |
| 24 | export var repositoryName: String = "rangermauve/hyper-gateway" | |
| 25 | export var tagStart: String = "v2" | |
| 23 | 26 | |
| 24 | 27 | ## TODO: Use this tag if the `latest` release doesn't match the tag? |
| 25 | 28 | # export var defaultTag = "v2.1.3" |
| … | … | @@ -69,6 +72,10 @@ func setupGateway(): |
| 69 | 72 | if !persist: args.append('--no-persist') |
| 70 | 73 | if writable: args.append('--writable') |
| 71 | 74 | |
| 75 | if port != DEFAULT_PORT: | |
| 76 | args.append('--port') | |
| 77 | args.append(String(port)) | |
| 78 | ||
| 72 | 79 | var resolvedStorageDirectory = ProjectSettings.globalize_path(storageDirectory) |
| 73 | 80 | args.append('--storage-location') |
| 74 | 81 | args.append(resolvedStorageDirectory) |
| … | … | @@ -18,5 +18,4 @@ func request( |
| 18 | 18 | var toLoad = url.replace("hyper://", serverPrefix) |
| 19 | 19 | emit_signal('loading_data', url) |
| 20 | 20 | |
| 21 | print(method, ' ', toLoad) | |
| 22 | 21 | return .request(toLoad, custom_headers, ssl_validate_domain, method, request_data) |
| … | … | @@ -0,0 +1,11 @@ |
| 1 | extends Area2D | |
| 2 | ||
| 3 | var initialSpeed = 600 | |
| 4 | var velocity = Vector2(initialSpeed, 0) | |
| 5 | ||
| 6 | func _physics_process(delta): | |
| 7 | position += velocity * delta | |
| 8 | ||
| 9 | # Destroy the bullet once it leaves the screen | |
| 10 | func _on_VisibilityNotifier2D_screen_exited(): | |
| 11 | queue_free() |
| … | … | @@ -0,0 +1,22 @@ |
| 1 | [gd_scene load_steps=4 format=2] | |
| 2 | ||
| 3 | [ext_resource path="res://src/MultiplayerDemo/icon.png" type="Texture" id=1] | |
| 4 | [ext_resource path="res://src/MultiplayerDemo/Bullet.gd" type="Script" id=2] | |
| 5 | ||
| 6 | [sub_resource type="CircleShape2D" id=1] | |
| 7 | radius = 4.0 | |
| 8 | ||
| 9 | [node name="Bullet" type="Area2D"] | |
| 10 | script = ExtResource( 2 ) | |
| 11 | ||
| 12 | [node name="Sprite" type="Sprite" parent="."] | |
| 13 | scale = Vector2( 0.01, 0.01 ) | |
| 14 | texture = ExtResource( 1 ) | |
| 15 | ||
| 16 | [node name="CollisionShape2D" type="CollisionShape2D" parent="."] | |
| 17 | shape = SubResource( 1 ) | |
| 18 | ||
| 19 | [node name="VisibilityNotifier2D" type="VisibilityNotifier2D" parent="."] | |
| 20 | rect = Rect2( -4, -4, 8, 8 ) | |
| 21 | ||
| 22 | [connection signal="screen_exited" from="VisibilityNotifier2D" to="." method="_on_VisibilityNotifier2D_screen_exited"] |
| … | … | @@ -0,0 +1,70 @@ |
| 1 | extends Node2D | |
| 2 | ||
| 3 | var PlayerSprite = preload("res://src/MultiplayerDemo/PlayerSprite.tscn") | |
| 4 | ||
| 5 | export var url: String = "hyper://blog.mauve.moe/" | |
| 6 | ||
| 7 | onready var gossip = $HyperGossip | |
| 8 | onready var player = $Player | |
| 9 | onready var gateway = $HyperGateway | |
| 10 | ||
| 11 | const POSITION = 'position' | |
| 12 | ||
| 13 | const knownPlayers = {} | |
| 14 | ||
| 15 | func _ready(): | |
| 16 | gateway.start() | |
| 17 | ||
| 18 | func get_player_object(id): | |
| 19 | if knownPlayers.has(id): return knownPlayers[id] | |
| 20 | ||
| 21 | var playerSprite = PlayerSprite.instance() | |
| 22 | knownPlayers[id] = playerSprite | |
| 23 | ||
| 24 | _broadcast_player() | |
| 25 | ||
| 26 | return playerSprite | |
| 27 | ||
| 28 | func _broadcast_player(): | |
| 29 | var position = player.position | |
| 30 | var rotation = player.rotation_degrees | |
| 31 | var data = { | |
| 32 | "position": { | |
| 33 | "x": position.x, | |
| 34 | "y": position.y | |
| 35 | }, | |
| 36 | "rotation": rotation | |
| 37 | } | |
| 38 | gossip.broadcast_event(POSITION, data) | |
| 39 | ||
| 40 | func _on_remote_player_moved(positionData, id): | |
| 41 | var remotePlayer = get_player_object(id) | |
| 42 | var rotation_degrees = positionData.rotation | |
| 43 | var raw_position = positionData.position | |
| 44 | remotePlayer.rotation_degrees = rotation_degrees | |
| 45 | remotePlayer.position.x = raw_position.x | |
| 46 | remotePlayer.position.y = raw_position.y | |
| 47 | ||
| 48 | func _on_Player_shoot(Bullet, direction, location): | |
| 49 | var bullet = Bullet.instance() | |
| 50 | add_child(bullet) | |
| 51 | bullet.rotation = direction | |
| 52 | bullet.position = location | |
| 53 | bullet.velocity = bullet.velocity.rotated(direction) | |
| 54 | ||
| 55 | func _on_HyperGateway_started_gateway(_pid): | |
| 56 | gossip.url = url | |
| 57 | gossip.start_listening() | |
| 58 | ||
| 59 | func _on_HyperGossip_listening(_extension_name): | |
| 60 | print('Started listening on events') | |
| 61 | # Tell everyone we exist! | |
| 62 | _broadcast_player() | |
| 63 | ||
| 64 | func _on_HyperGossip_event(type, data, from): | |
| 65 | if type == POSITION: | |
| 66 | _on_remote_player_moved(data, from) | |
| 67 | ||
| 68 | func _on_Player_moved(_position, _rotation, _velocity): | |
| 69 | # TODO: Don't broadcast too often to avoid flooding the system | |
| 70 | _broadcast_player() |
| … | … | @@ -0,0 +1,28 @@ |
| 1 | [gd_scene load_steps=6 format=2] | |
| 2 | ||
| 3 | [ext_resource path="res://src/MultiplayerDemo/PlayerSprite.tscn" type="PackedScene" id=1] | |
| 4 | [ext_resource path="res://src/MultiplayerDemo/Player.gd" type="Script" id=2] | |
| 5 | [ext_resource path="res://src/MultiplayerDemo/FlyBalls.gd" type="Script" id=3] | |
| 6 | [ext_resource path="res://src/HyperGateway/HyperGateway.gd" type="Script" id=4] | |
| 7 | [ext_resource path="res://src/multiplayer/HyperGossip.gd" type="Script" id=5] | |
| 8 | ||
| 9 | [node name="FlyBalls" type="Node2D"] | |
| 10 | script = ExtResource( 3 ) | |
| 11 | ||
| 12 | [node name="Player" parent="." instance=ExtResource( 1 )] | |
| 13 | position = Vector2( 333, 333 ) | |
| 14 | script = ExtResource( 2 ) | |
| 15 | ||
| 16 | [node name="HyperGossip" type="Node" parent="."] | |
| 17 | script = ExtResource( 5 ) | |
| 18 | extension_name = "flyballs-v1" | |
| 19 | ||
| 20 | [node name="HyperGateway" type="Node" parent="."] | |
| 21 | script = ExtResource( 4 ) | |
| 22 | autoStart = false | |
| 23 | ||
| 24 | [connection signal="moved" from="Player" to="." method="_on_Player_moved"] | |
| 25 | [connection signal="shoot" from="Player" to="." method="_on_Player_shoot"] | |
| 26 | [connection signal="event" from="HyperGossip" to="." method="_on_HyperGossip_event"] | |
| 27 | [connection signal="listening" from="HyperGossip" to="." method="_on_HyperGossip_listening"] | |
| 28 | [connection signal="started_gateway" from="HyperGateway" to="." method="_on_HyperGateway_started_gateway"] |
| … | … | @@ -0,0 +1,39 @@ |
| 1 | extends "./PlayerSprite.gd" | |
| 2 | ||
| 3 | var Bullet = preload("res://src/MultiplayerDemo/Bullet.tscn") | |
| 4 | ||
| 5 | export (int) var speed = 200 | |
| 6 | ||
| 7 | signal shoot(bullet, direction, location) | |
| 8 | signal moved(position, rotation, velocity) | |
| 9 | ||
| 10 | var velocity = Vector2() | |
| 11 | ||
| 12 | func proccess_input(): | |
| 13 | var hasMoved = false | |
| 14 | look_at(get_global_mouse_position()) | |
| 15 | velocity = Vector2() | |
| 16 | if Input.is_action_pressed("right"): | |
| 17 | hasMoved = true | |
| 18 | velocity.x += 1 | |
| 19 | if Input.is_action_pressed("left"): | |
| 20 | hasMoved = true | |
| 21 | velocity.x -= 1 | |
| 22 | if Input.is_action_pressed("down"): | |
| 23 | hasMoved = true | |
| 24 | velocity.y += 1 | |
| 25 | if Input.is_action_pressed("up"): | |
| 26 | hasMoved = true | |
| 27 | velocity.y -= 1 | |
| 28 | ||
| 29 | velocity = velocity.normalized() * speed | |
| 30 | ||
| 31 | if Input.is_action_just_pressed("click"): | |
| 32 | emit_signal("shoot", Bullet, rotation, position) | |
| 33 | ||
| 34 | return hasMoved | |
| 35 | ||
| 36 | func _physics_process(_delta): | |
| 37 | var hasMoved = proccess_input() | |
| 38 | velocity = move_and_slide(velocity) | |
| 39 | if hasMoved: emit_signal("moved", position, rotation, velocity) |
| … | … | @@ -0,0 +1,6 @@ |
| 1 | extends KinematicBody2D | |
| 2 | ||
| 3 | func _on_VisibilityNotifier2D_screen_exited(): | |
| 4 | var viewport = get_viewport_rect().size | |
| 5 | position.x = viewport.x / 2 | |
| 6 | position.y = viewport.y / 2 |
| … | … | @@ -0,0 +1,42 @@ |
| 1 | [gd_scene load_steps=5 format=2] | |
| 2 | ||
| 3 | [ext_resource path="res://src/MultiplayerDemo/icon.png" type="Texture" id=1] | |
| 4 | [ext_resource path="res://src/MultiplayerDemo/PlayerSprite.gd" type="Script" id=2] | |
| 5 | ||
| 6 | [sub_resource type="ParticlesMaterial" id=1] | |
| 7 | flag_disable_z = true | |
| 8 | direction = Vector3( -1, 0, 0 ) | |
| 9 | gravity = Vector3( 0, 0, 0 ) | |
| 10 | initial_velocity = 60.0 | |
| 11 | orbit_velocity = 0.0 | |
| 12 | orbit_velocity_random = 0.0 | |
| 13 | radial_accel_random = 0.14 | |
| 14 | angle = 45.0 | |
| 15 | angle_random = 1.0 | |
| 16 | scale = 0.02 | |
| 17 | scale_random = 0.01 | |
| 18 | ||
| 19 | [sub_resource type="CircleShape2D" id=2] | |
| 20 | radius = 30.0 | |
| 21 | ||
| 22 | [node name="PlayerSprite" type="KinematicBody2D"] | |
| 23 | script = ExtResource( 2 ) | |
| 24 | ||
| 25 | [node name="Particles2D" type="Particles2D" parent="."] | |
| 26 | amount = 32 | |
| 27 | lifetime = 3.0 | |
| 28 | local_coords = false | |
| 29 | process_material = SubResource( 1 ) | |
| 30 | texture = ExtResource( 1 ) | |
| 31 | ||
| 32 | [node name="Sprite" type="Sprite" parent="."] | |
| 33 | scale = Vector2( 0.1, 0.1 ) | |
| 34 | texture = ExtResource( 1 ) | |
| 35 | ||
| 36 | [node name="CollisionShape2D" type="CollisionShape2D" parent="."] | |
| 37 | shape = SubResource( 2 ) | |
| 38 | ||
| 39 | [node name="VisibilityNotifier2D" type="VisibilityNotifier2D" parent="."] | |
| 40 | rect = Rect2( -40, -40, 80, 80 ) | |
| 41 | ||
| 42 | [connection signal="screen_exited" from="VisibilityNotifier2D" to="." method="_on_VisibilityNotifier2D_screen_exited"] |
| … | … | @@ -0,0 +1,34 @@ |
| 1 | [remap] | |
| 2 | ||
| 3 | importer="texture" | |
| 4 | type="StreamTexture" | |
| 5 | path="res://.import/icon.png-f5f6ad6f85377ad8c63e3aa6b4372897.stex" | |
| 6 | metadata={ | |
| 7 | "vram_texture": false | |
| 8 | } | |
| 9 | ||
| 10 | [deps] | |
| 11 | ||
| 12 | source_file="res://src/MultiplayerDemo/icon.png" | |
| 13 | dest_files=[ "res://.import/icon.png-f5f6ad6f85377ad8c63e3aa6b4372897.stex" ] | |
| 14 | ||
| 15 | [params] | |
| 16 | ||
| 17 | compress/mode=0 | |
| 18 | compress/lossy_quality=0.7 | |
| 19 | compress/hdr_mode=0 | |
| 20 | compress/bptc_ldr=0 | |
| 21 | compress/normal_map=0 | |
| 22 | flags/repeat=0 | |
| 23 | flags/filter=true | |
| 24 | flags/mipmaps=false | |
| 25 | flags/anisotropic=false | |
| 26 | flags/srgb=2 | |
| 27 | process/fix_alpha_border=true | |
| 28 | process/premult_alpha=false | |
| 29 | process/HDR_as_SRGB=false | |
| 30 | process/invert_color=false | |
| 31 | stream=false | |
| 32 | size_limit=0 | |
| 33 | detect_3d=true | |
| 34 | svg/scale=1.0 |
| … | … | @@ -0,0 +1,139 @@ |
| 1 | extends Node | |
| 2 | ||
| 3 | class_name HyperGossip | |
| 4 | ||
| 5 | const LRU = preload('res://src/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 | eventSource.connect("event", self, "_on_extesnion_event") | |
| 30 | eventSource.connect("response_started", self, "_on_events_started") | |
| 31 | ||
| 32 | func start_listening(): | |
| 33 | load_peers() | |
| 34 | eventSource.request(_get_extension_listen_url()) | |
| 35 | ||
| 36 | func _on_events_started(_statusCode, _responseHeaders): | |
| 37 | emit_signal("listening", extension_name) | |
| 38 | ||
| 39 | func _enqueue_broadcast_request(reqURL, body): | |
| 40 | if isRequesting: | |
| 41 | requestQueue.push_back({"reqURL": reqURL, "body": body}) | |
| 42 | else: _perform_broadcast_request(reqURL, body) | |
| 43 | ||
| 44 | func _perform_broadcast_request(reqURL, body): | |
| 45 | isRequesting = true | |
| 46 | broadcastRequest.request(reqURL, [], false, HTTPClient.METHOD_POST, body) | |
| 47 | ||
| 48 | func _on_broadcast_completed(_result, _response_code, _headers, _body): | |
| 49 | isRequesting = false | |
| 50 | if requestQueue.size() == 0: return | |
| 51 | var next = requestQueue.pop_front() | |
| 52 | _perform_broadcast_request(next.reqURL, next.body) | |
| 53 | ||
| 54 | func rebroadcast(event): | |
| 55 | var reqURL = _get_extension_url() | |
| 56 | var body = JSON.print(event) | |
| 57 | _enqueue_broadcast_request(reqURL, body) | |
| 58 | ||
| 59 | func broadcast_event(type: String, data): | |
| 60 | var reqURL = _get_extension_url() | |
| 61 | var body = JSON.print(_generateEvent(type, data)) | |
| 62 | _enqueue_broadcast_request(reqURL, body) | |
| 63 | ||
| 64 | func send_to_peer(peer, type, data): | |
| 65 | var reqURL = _get_extension_url_to_peer(peer) | |
| 66 | var body = JSON.print(_generateEvent(type, data)) | |
| 67 | _enqueue_broadcast_request(reqURL, body) | |
| 68 | ||
| 69 | func load_peers(): | |
| 70 | var reqURL = _get_extension_url() | |
| 71 | peerListRequest.request(reqURL) | |
| 72 | ||
| 73 | func _generateEvent(type, data): | |
| 74 | var id = _make_id() | |
| 75 | seen_messages.track(id) | |
| 76 | return { | |
| 77 | "id": id, | |
| 78 | "type": type, | |
| 79 | "data": data | |
| 80 | } | |
| 81 | ||
| 82 | func _handle_event(event): | |
| 83 | var type = event.type | |
| 84 | var data = event.data | |
| 85 | var id = event.id | |
| 86 | var from = event.from | |
| 87 | ||
| 88 | if seen_messages.has(id): | |
| 89 | return | |
| 90 | ||
| 91 | seen_messages.track(id) | |
| 92 | ||
| 93 | emit_signal("event", type, data, from) | |
| 94 | rebroadcast(event) | |
| 95 | ||
| 96 | func _on_event(data, event, id): | |
| 97 | # Ignore events for other extensions | |
| 98 | if event != extension_name: return | |
| 99 | ||
| 100 | var parsed = JSON.parse(data) | |
| 101 | ||
| 102 | if parsed.error != OK: | |
| 103 | printerr("Unable to parse EventSource content " + parsed.error_line + "\n" + data) | |
| 104 | return | |
| 105 | ||
| 106 | var result = parsed.result | |
| 107 | if !("from" in result): | |
| 108 | result.from = id | |
| 109 | ||
| 110 | _handle_event(result) | |
| 111 | ||
| 112 | ||
| 113 | func _on_peer_list(_result, response_code, _headers, body): | |
| 114 | var text = body.get_string_from_utf8() | |
| 115 | if response_code != 200: | |
| 116 | printerr('Error listing peers ', response_code, " ", text) | |
| 117 | return | |
| 118 | ||
| 119 | var parsed = JSON.parse(text) | |
| 120 | ||
| 121 | if parsed.error != OK: | |
| 122 | printerr("Unable to parse Peer List" + String(parsed.error_line)) | |
| 123 | return | |
| 124 | ||
| 125 | var peers = parsed.result | |
| 126 | ||
| 127 | emit_signal("peers", peers) | |
| 128 | ||
| 129 | func _get_extension_url_to_peer(peer): | |
| 130 | return url + '$/extensions/' + extension_name + '/' + peer | |
| 131 | ||
| 132 | func _get_extension_url(): | |
| 133 | return url + '$/extensions/' + extension_name | |
| 134 | ||
| 135 | func _get_extension_listen_url(): | |
| 136 | return url + '$/extensions/' | |
| 137 | ||
| 138 | func _make_id(): | |
| 139 | return str(randi()) + str(randi()) |
| … | … | @@ -0,0 +1,4 @@ |
| 1 | extends WAT.Test | |
| 2 | ||
| 3 | func test_create_gateway_and_connect() -> void: | |
| 4 | asserts.auto_pass("TODO") |