diff --git a/game-source/WebSockets.gd b/game-source/WebSockets.gd index 9090089..4aa3c98 100644 --- a/game-source/WebSockets.gd +++ b/game-source/WebSockets.gd @@ -1,6 +1,7 @@ extends Node class_name WebSocketClient +@export var base_url: String = "ws://localhost:8000/ws" @export var handshake_headers : PackedStringArray @export var supported_protocols : PackedStringArray @export var tls_trusted_certificate : X509Certificate @@ -29,7 +30,7 @@ func connect_to_url(url) -> int: func send(message) -> int: - print(socket.get_ready_state()) + print("Yooo " + str(socket.get_ready_state())) if typeof(message) == TYPE_STRING: return socket.send_text(message) return socket.send(var_to_bytes(message)) @@ -45,6 +46,7 @@ func get_message() -> Variant: func close(code := 1000, reason := "") -> void: + print("Socket closed!") socket.close(code, reason) last_state = socket.get_ready_state() @@ -70,10 +72,6 @@ func poll() -> void: connection_closed.emit() while socket.get_ready_state() == socket.STATE_OPEN and socket.get_available_packet_count(): message_received.emit(get_message()) - - -func _ready(): - connect_to_url("ws://localhost:8000/ws") - + func _process(delta): poll() diff --git a/game-source/assets/Illustration95.png b/game-source/assets/Illustration95.png index 8a60264..10f290c 100644 Binary files a/game-source/assets/Illustration95.png and b/game-source/assets/Illustration95.png differ diff --git a/game-source/assets/box.png b/game-source/assets/box.png deleted file mode 100644 index 2570ab4..0000000 Binary files a/game-source/assets/box.png and /dev/null differ diff --git a/game-source/assets/lil_guy.png b/game-source/assets/lil_guy.png deleted file mode 100644 index dbfac59..0000000 Binary files a/game-source/assets/lil_guy.png and /dev/null differ diff --git a/game-source/assets/lil_guy.png.import b/game-source/assets/lil_guy.png.import deleted file mode 100644 index 639c993..0000000 --- a/game-source/assets/lil_guy.png.import +++ /dev/null @@ -1,34 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://cf7mjmudnjchn" -path="res://.godot/imported/lil_guy.png-5b8522e296156f74a537c04ff5d11eca.ctex" -metadata={ -"vram_texture": false -} - -[deps] - -source_file="res://assets/lil_guy.png" -dest_files=["res://.godot/imported/lil_guy.png-5b8522e296156f74a537c04ff5d11eca.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 diff --git a/game-source/assets/main_menu.png b/game-source/assets/main_menu.png deleted file mode 100644 index 146ee27..0000000 Binary files a/game-source/assets/main_menu.png and /dev/null differ diff --git a/game-source/assets/main_menu.png.import b/game-source/assets/main_menu.png.import deleted file mode 100644 index 03042d8..0000000 --- a/game-source/assets/main_menu.png.import +++ /dev/null @@ -1,34 +0,0 @@ -[remap] - -importer="texture" -type="CompressedTexture2D" -uid="uid://oc0ftdvgh6n0" -path="res://.godot/imported/main_menu.png-0132c610c849c9ef08be85042da72793.ctex" -metadata={ -"vram_texture": false -} - -[deps] - -source_file="res://assets/main_menu.png" -dest_files=["res://.godot/imported/main_menu.png-0132c610c849c9ef08be85042da72793.ctex"] - -[params] - -compress/mode=0 -compress/high_quality=false -compress/lossy_quality=0.7 -compress/hdr_compression=1 -compress/normal_map=0 -compress/channel_pack=0 -mipmaps/generate=false -mipmaps/limit=-1 -roughness/mode=0 -roughness/src_normal="" -process/fix_alpha_border=true -process/premult_alpha=false -process/normal_map_invert_y=false -process/hdr_as_srgb=false -process/hdr_clamp_exposure=false -process/size_limit=0 -detect_3d/compress_to=1 diff --git a/game-source/assets/player.png b/game-source/assets/player.png new file mode 100644 index 0000000..10f290c Binary files /dev/null and b/game-source/assets/player.png differ diff --git a/game-source/assets/box.png.import b/game-source/assets/player.png.import similarity index 69% rename from game-source/assets/box.png.import rename to game-source/assets/player.png.import index d455528..9df1a89 100644 --- a/game-source/assets/box.png.import +++ b/game-source/assets/player.png.import @@ -2,16 +2,16 @@ importer="texture" type="CompressedTexture2D" -uid="uid://c6jm42o02gsfr" -path="res://.godot/imported/box.png-52cc4e41fddadef836f9ad4dbe7936fa.ctex" +uid="uid://cn58l1sya667a" +path="res://.godot/imported/player.png-be2216fcaabb5c62aa2466cd9a5726a8.ctex" metadata={ "vram_texture": false } [deps] -source_file="res://assets/box.png" -dest_files=["res://.godot/imported/box.png-52cc4e41fddadef836f9ad4dbe7936fa.ctex"] +source_file="res://assets/player.png" +dest_files=["res://.godot/imported/player.png-be2216fcaabb5c62aa2466cd9a5726a8.ctex"] [params] diff --git a/game-source/project.godot b/game-source/project.godot index ea3ff37..be852db 100644 --- a/game-source/project.godot +++ b/game-source/project.godot @@ -48,6 +48,11 @@ up={ , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194320,"key_label":0,"unicode":0,"echo":false,"script":null) ] } +Use={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":70,"key_label":0,"unicode":102,"echo":false,"script":null) +] +} [rendering] diff --git a/game-source/scenes/box/box.gd b/game-source/scenes/box/box.gd new file mode 100644 index 0000000..8a3e9b4 --- /dev/null +++ b/game-source/scenes/box/box.gd @@ -0,0 +1,35 @@ +extends CharacterBody2D + + +@export var fetch_url: String = "https://via.placeholder.com/100" + +func _ready(): + position = position.snapped(Vector2.ONE * 100) + position += Vector2.ONE * 100 / 2 + # Create an HTTP request node and connect its completion signal. + var http_request = HTTPRequest.new() + add_child(http_request) + http_request.request_completed.connect(self._http_request_completed) + + # Perform the HTTP request. The URL below returns a PNG image as of writing. + var error = http_request.request(fetch_url) + if error != OK: + push_error("An error occurred in the HTTP request.") + +# Called when the HTTP request is completed. +func _http_request_completed(result, response_code, headers, body): + if result != HTTPRequest.RESULT_SUCCESS: + push_error("Image couldn't be downloaded. Try a different image.") + + var image = Image.new() + var error = image.load_png_from_buffer(body) + if error != OK: + push_error("Couldn't load the image.") + + var texture = ImageTexture.create_from_image(image) + + # Display the image in a TextureRect node. + var texture_rect = TextureRect.new() + add_child(texture_rect) + texture_rect.texture = texture + texture_rect.position = Vector2(0.5,0.5)*100 diff --git a/game-source/scenes/box/box.tscn b/game-source/scenes/box/box.tscn index 1354980..e704a3c 100644 --- a/game-source/scenes/box/box.tscn +++ b/game-source/scenes/box/box.tscn @@ -1,14 +1,15 @@ -[gd_scene load_steps=2 format=3 uid="uid://cv6rvnsd8jc4u"] +[gd_scene load_steps=3 format=3 uid="uid://cv6rvnsd8jc4u"] -[ext_resource type="Texture2D" uid="uid://c6jm42o02gsfr" path="res://assets/box.png" id="1_blfj8"] +[ext_resource type="Script" path="res://scenes/box/box.gd" id="1_so5sa"] + +[sub_resource type="RectangleShape2D" id="RectangleShape2D_pwdsl"] +size = Vector2(100, 100) [node name="CharacterBody2D" type="CharacterBody2D"] +script = ExtResource("1_so5sa") [node name="Box" type="Sprite2D" parent="."] position = Vector2(33, 52) -texture = ExtResource("1_blfj8") -[node name="CollisionPolygon2D" type="CollisionPolygon2D" parent="."] -visible = false -position = Vector2(36, 52) -polygon = PackedVector2Array(16, -124.8, 16, -122.9, 29, -111.9, 29, -109.4, 40.2, -85, 41.1, -85, 51, -69.6, 51, -59, 48.7, -59, 47, -57.9, 47, -57, 49.1, -24, 47.8, -24, 45.8, -8, 45, -8, 45, -4, 43, -4, 36, 5, 33.2, 5, 12, 7.2, 12, 5.89999, -13, 4.10001, -13, 4.89999, -80, 8.10001, -80, 6.10001, -93, -1.89999, -93, -4.60001, -95.4, -11, -98.1, -11, -102.1, -17, -105.2, -17, -115, -19.4, -115, -21.3, -122, -31, -123.6, -31, -149, -46.9, -149, -54, -150, -91, -150, -128, -148.6, -128, -137.6, -135, -127.2, -135, -120, -135.8, -120, -137.2) +[node name="CollisionShape2D" type="CollisionShape2D" parent="."] +shape = SubResource("RectangleShape2D_pwdsl") diff --git a/game-source/scenes/main/main.gd b/game-source/scenes/main/main.gd index 093d424..72543d3 100644 --- a/game-source/scenes/main/main.gd +++ b/game-source/scenes/main/main.gd @@ -1,14 +1,10 @@ extends Node2D -@export var on = true @export var tile_size = 100 func _process(delta): queue_redraw() func _draw(): - if !on: - pass - var camera: CharacterBody2D = get_tree().current_scene.find_child('PlayerCharacter') var size = get_viewport_rect().size / 2 diff --git a/game-source/scenes/main/main.tscn b/game-source/scenes/main/main.tscn index a3372c6..443c5f6 100644 --- a/game-source/scenes/main/main.tscn +++ b/game-source/scenes/main/main.tscn @@ -1,7 +1,9 @@ -[gd_scene load_steps=3 format=3 uid="uid://2t2nbl4hrw8b"] +[gd_scene load_steps=5 format=3 uid="uid://2t2nbl4hrw8b"] [ext_resource type="PackedScene" uid="uid://xqoiyxrn10js" path="res://scenes/player_character/player_character.tscn" id="1_1c02b"] [ext_resource type="Script" path="res://scenes/main/main.gd" id="1_47q2m"] +[ext_resource type="PackedScene" uid="uid://cv6rvnsd8jc4u" path="res://scenes/box/box.tscn" id="3_2p8op"] +[ext_resource type="Script" path="res://WebSockets.gd" id="3_uxet7"] [node name="GameLevel" type="Node2D"] script = ExtResource("1_47q2m") @@ -9,3 +11,11 @@ script = ExtResource("1_47q2m") [node name="PlayerCharacter" parent="." instance=ExtResource("1_1c02b")] position = Vector2(577, 325) scale = Vector2(2.9, 2.9) + +[node name="WebSocketClient" type="Node" parent="."] +script = ExtResource("3_uxet7") + +[node name="CharacterBody2D" parent="." instance=ExtResource("3_2p8op")] +position = Vector2(256, 168) + +[node name="OtherObjects" type="Node2D" parent="."] diff --git a/game-source/scenes/player_character/player_character.gd b/game-source/scenes/player_character/player_character.gd index 0aec7e5..735fed4 100644 --- a/game-source/scenes/player_character/player_character.gd +++ b/game-source/scenes/player_character/player_character.gd @@ -1,9 +1,19 @@ extends CharacterBody2D - @export var tile_size: float = 100 @export var is_moving: bool = false +@onready var wsClient: WebSocketClient = get_tree().current_scene.find_child('WebSocketClient') +@onready var container: VBoxContainer = get_tree().current_scene.find_child('CanvasLayer').find_child('SpawnListScroll').find_child('SpawnListContainer') +@onready var inventoryContainer: VBoxContainer = get_tree().current_scene.find_child('CanvasLayer').find_child('Inventory').find_child('InventoryList') + +@onready var OtherObjects: Node2D = get_tree().current_scene.find_child('OtherObjects') +@onready var BoxObject = preload('res://scenes/box/box.gd') + +@onready var uuid_util = preload('res://uuid.gd') +signal player_did_move(new_pos: Vector2) + + var inputs = { "right": Vector2.RIGHT, "left": Vector2.LEFT, @@ -18,13 +28,152 @@ var inputs_rev = { Vector2.DOWN: "down" } +var inventory = {} +var selectedItem = {} +var selectedTile = Vector2.RIGHT * tile_size +var playerInfo = {} + +const spawnList = { + "hydrogen": "5652c78a-8bfe-452a-ac29-53746cabfa40", + "oxygen": "5585b1f1-8be4-498f-990c-46fadf68425f", + "nitrogen": "33fc0e3a-fc48-4785-82fa-4a5d93bf0917", + "calcium": "dee34cee-7ef5-46ce-b6d2-e7915669f21c", + "iron": "b1951823-bf0f-47c2-82ae-9053443bda9b", + "aluminium": "4941ce02-5992-4de3-9ead-23d9e7de5f0c", + "uranium": "43375a4b-5950-4341-9d20-1b5086ddf9db", + "sodium": "c0025eb6-15e1-4d0b-b9de-f08617ace76a", + "chlorine": "45327e56-9ac9-46a2-8624-bd5c20d251f4", + "light": "d9f02822-6f1b-4d99-8c71-ceba6af0f1e3", + "time": "078e8865-0c37-466b-a219-b7122a0613cd", + "silicon": "e59c2fbe-8975-4d20-a406-5a98df7d2455", + "water": "1df74708-1124-4c05-a3f4-8ce2d14702a6", + "salt": "a8bced32-f63a-4903-8932-a6d81e36c04f", + "air": "2d88a7d2-5f23-4ca9-8c12-1550ac36490a", + "dirt": "76952fb5-aa9d-4eda-a79a-8086ff35f5f1" +} + func _ready(): + + playerInfo = { + "username": "Player-" + uuid_util.v4(), + "uuid": uuid_util.v4() + } + position = position.snapped(Vector2.ONE * tile_size) position += Vector2.ONE * tile_size / 2 + + print(position) + print(position + selectedTile) + + for i in spawnList: + var btn = Button.new() + btn.text = i; + btn.connect("pressed", spawnButtonPress.bind(spawnList[i])) + container.add_child(btn) + + wsClient.connect_to_url("wss://hack.djpiper28.co.uk/ws/") + await wsClient.connected_to_server + wsClient.send(JSON.stringify({ + "initialised": false, + "player": playerInfo, + "coordinates": [position.x, position.y] + })) + + var rec_coords = JSON.parse_string(await wsClient.message_received) + position.x = rec_coords["coordinates"][0] + position.y = rec_coords["coordinates"][1] + +func redrawInventory(): + for n in inventoryContainer.get_children(): + n.remove_child(n) + n.queue_free() + + for k in inventory.keys(): + var lab = Button.new() + lab.text = inventory[k].name + " x" + str(inventory[k].amount) + lab.connect("pressed", itemSelectButton.bind(k, inventory[k].name)) + inventoryContainer.add_child(lab) + +func spawnButtonPress(uuid): + print("added " + uuid) + + if uuid in inventory: + inventory[uuid] = {"name": spawnList.find_key(uuid), "amount": inventory[uuid].amount + 1} + else: + inventory[uuid] = {"name": spawnList.find_key(uuid), "amount": 1} + + redrawInventory() + +func itemSelectButton(uuid, name): + selectedItem = {"id": uuid, "name": name} + print("New selected Item ", selectedItem) func _input(event): + if !event.is_action("down") and !event.is_action("Use") and ! event.is_action("up") and !event.is_action("left") and ! event.is_action("right"): + return + var vec = Input.get_vector("left", "right", "up", "down") + + print(inventory[selectedItem.id]) + if event.is_action("Use"): + print("gdhlgjhs") + if inventory[selectedItem.id].amount > 0 and !selectedItem.has("id"): + wsClient.send(JSON.stringify( + { + "initialised": true, + "player": playerInfo, + "action": { + "kind": 0, + "kube": { + "id": selectedItem.id, + "name": selectedItem.name, + }, + "coordinates": [selectedTile.x, selectedTile.y] + }, + "coordinates": [position.x, position.y] + } + )) + + var ser_res = JSON.parse_string(await wsClient.message_received) + print("Response from move: " + JSON.stringify(ser_res)) + + print(selectedItem) + if inventory[selectedItem.id].amount - 1 <= 0: + inventory.erase(selectedItem.id) + selectedItem = { } + else: + inventory[selectedItem.id].amount -= 1 + redrawInventory() + + if vec.length() == 1: + print(inputs_rev[vec]) + selectedTile = vec * tile_size position += vec*100 + emit_signal('player_did_move', position) $AnimationPlayer.play(inputs_rev[vec]) + +func make_player_state(make_player_state:Vector2): + return JSON.stringify({ + "initialised": true, + "player": playerInfo, + "coordinates": [make_player_state.x, make_player_state.y] + }) + +func _on_player_did_move(new_pos): + wsClient.send(make_player_state(new_pos)) + var ser_res = JSON.parse_string(await wsClient.message_received) + print("Response from move: " + JSON.stringify(ser_res)) + + for i in ser_res["grid"]["spaces"]: + if i["contains"].has("Player"): + pass + elif i["contains"].has("Kube"): + var obj = BoxObject.instance() + obj.transform.origin = Vector2(i["contains"]["coordinate"][0], i["contains"]["coordinate"][1]) + obj.fetch_url("https://hack.djpiper28.co.uk/cache/kubeImageById/" + i["contains"]["Kube"]["uuid"]) + OtherObjects.add_child(obj) + pass + + diff --git a/game-source/scenes/player_character/player_character.tscn b/game-source/scenes/player_character/player_character.tscn index 6406c42..9a9780e 100644 --- a/game-source/scenes/player_character/player_character.tscn +++ b/game-source/scenes/player_character/player_character.tscn @@ -1,7 +1,7 @@ -[gd_scene load_steps=8 format=3 uid="uid://xqoiyxrn10js"] +[gd_scene load_steps=9 format=3 uid="uid://xqoiyxrn10js"] [ext_resource type="Script" path="res://scenes/player_character/player_character.gd" id="1_rmbqt"] -[ext_resource type="Texture2D" uid="uid://bdap4f6w37byq" path="res://assets/Illustration95.png" id="2_0gsa3"] +[ext_resource type="Texture2D" uid="uid://cn58l1sya667a" path="res://assets/player.png" id="2_fja5p"] [sub_resource type="RectangleShape2D" id="RectangleShape2D_nrvy8"] size = Vector2(14, 15.5) @@ -15,14 +15,14 @@ tracks/0/path = NodePath("../Sprite2D:frame") tracks/0/interp = 1 tracks/0/loop_wrap = true tracks/0/keys = { -"times": PackedFloat32Array(0.00177135, 0.297301, 0.529481, 0.858887), +"times": PackedFloat32Array(0.00177135, 0.256913, 0.49717, 0.834654), "transitions": PackedFloat32Array(1, 1, 1, 1), "update": 1, "values": [0, 1, 2, 0] } -[sub_resource type="Animation" id="Animation_sndo4"] -resource_name = "up" +[sub_resource type="Animation" id="Animation_ok6dn"] +resource_name = "left" tracks/0/type = "value" tracks/0/imported = false tracks/0/enabled = true @@ -30,10 +30,10 @@ tracks/0/path = NodePath("../Sprite2D:frame") tracks/0/interp = 1 tracks/0/loop_wrap = true tracks/0/keys = { -"times": PackedFloat32Array(0.00553417, 0.15295, 0.32358), +"times": PackedFloat32Array(0, 0.3, 0.6), "transitions": PackedFloat32Array(1, 1, 1), "update": 1, -"values": [3, 4, 5] +"values": [9, 10, 10] } [sub_resource type="Animation" id="Animation_v72g0"] @@ -45,15 +45,31 @@ tracks/0/path = NodePath("../Sprite2D:frame") tracks/0/interp = 1 tracks/0/loop_wrap = true tracks/0/keys = { -"times": PackedFloat32Array(0.0161553, 0.319068, 0.69064), +"times": PackedFloat32Array(0.0161553, 0.319068, 0.6), "transitions": PackedFloat32Array(1, 1, 1), "update": 1, "values": [6, 7, 8] } +[sub_resource type="Animation" id="Animation_sndo4"] +resource_name = "up" +tracks/0/type = "value" +tracks/0/imported = false +tracks/0/enabled = true +tracks/0/path = NodePath("../Sprite2D:frame") +tracks/0/interp = 1 +tracks/0/loop_wrap = true +tracks/0/keys = { +"times": PackedFloat32Array(0.00149534, 0.286231, 0.586104, 0.860271), +"transitions": PackedFloat32Array(1, 1, 1, 1), +"update": 1, +"values": [3, 4, 5, 3] +} + [sub_resource type="AnimationLibrary" id="AnimationLibrary_hbknw"] _data = { "down": SubResource("Animation_17yri"), +"left": SubResource("Animation_ok6dn"), "right": SubResource("Animation_v72g0"), "up": SubResource("Animation_sndo4") } @@ -62,14 +78,14 @@ _data = { script = ExtResource("1_rmbqt") [node name="RayCast2D" type="RayCast2D" parent="."] -scale = Vector2(1, 1) target_position = Vector2(0, 19) [node name="Sprite2D" type="Sprite2D" parent="."] scale = Vector2(0.458, 0.368) -texture = ExtResource("2_0gsa3") +texture = ExtResource("2_fja5p") hframes = 3 -vframes = 3 +vframes = 4 +frame = 7 [node name="Camera2D" type="Camera2D" parent="."] position_smoothing_enabled = true @@ -91,3 +107,35 @@ root_node = NodePath("../RayCast2D") libraries = { "": SubResource("AnimationLibrary_hbknw") } + +[node name="AnimationPlayer2" type="AnimationPlayer" parent="AnimationPlayer"] +root_node = NodePath("../RayCast2D") +libraries = { +"": SubResource("AnimationLibrary_hbknw") +} + +[node name="AnimationPlayer" type="AnimationPlayer" parent="AnimationPlayer/AnimationPlayer2"] +root_node = NodePath("../RayCast2D") +libraries = { +"": SubResource("AnimationLibrary_hbknw") +} + +[node name="CanvasLayer" type="CanvasLayer" parent="."] + +[node name="SpawnListScroll" type="ScrollContainer" parent="CanvasLayer"] +offset_top = -1.0 +offset_right = 116.0 +offset_bottom = 131.0 + +[node name="SpawnListContainer" type="VBoxContainer" parent="CanvasLayer/SpawnListScroll"] +layout_mode = 2 + +[node name="Inventory" type="ScrollContainer" parent="CanvasLayer"] +offset_left = 421.0 +offset_right = 576.0 +offset_bottom = 60.0 + +[node name="InventoryList" type="VBoxContainer" parent="CanvasLayer/Inventory"] +layout_mode = 2 + +[connection signal="player_did_move" from="." to="." method="_on_player_did_move"] diff --git a/game-source/uuid.gd b/game-source/uuid.gd new file mode 100644 index 0000000..2235991 --- /dev/null +++ b/game-source/uuid.gd @@ -0,0 +1,115 @@ +# Note: The code might not be as pretty it could be, since it's written +# in a way that maximizes performance. Methods are inlined and loops are avoided. +extends Node + +const BYTE_MASK: int = 0b11111111 + +static func uuidbin(): + randomize() + # 16 random bytes with the bytes on index 6 and 8 modified + return [ + randi() & BYTE_MASK, randi() & BYTE_MASK, randi() & BYTE_MASK, randi() & BYTE_MASK, + randi() & BYTE_MASK, randi() & BYTE_MASK, ((randi() & BYTE_MASK) & 0x0f) | 0x40, randi() & BYTE_MASK, + ((randi() & BYTE_MASK) & 0x3f) | 0x80, randi() & BYTE_MASK, randi() & BYTE_MASK, randi() & BYTE_MASK, + randi() & BYTE_MASK, randi() & BYTE_MASK, randi() & BYTE_MASK, randi() & BYTE_MASK, + ] + +static func uuidbinrng(rng: RandomNumberGenerator): + rng.randomize() + return [ + rng.randi() & BYTE_MASK, rng.randi() & BYTE_MASK, rng.randi() & BYTE_MASK, rng.randi() & BYTE_MASK, + rng.randi() & BYTE_MASK, rng.randi() & BYTE_MASK, ((rng.randi() & BYTE_MASK) & 0x0f) | 0x40, rng.randi() & BYTE_MASK, + ((rng.randi() & BYTE_MASK) & 0x3f) | 0x80, rng.randi() & BYTE_MASK, rng.randi() & BYTE_MASK, rng.randi() & BYTE_MASK, + rng.randi() & BYTE_MASK, rng.randi() & BYTE_MASK, rng.randi() & BYTE_MASK, rng.randi() & BYTE_MASK, + ] + +static func v4(): + # 16 random bytes with the bytes on index 6 and 8 modified + var b = uuidbin() + + return '%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x' % [ + # low + b[0], b[1], b[2], b[3], + + # mid + b[4], b[5], + + # hi + b[6], b[7], + + # clock + b[8], b[9], + + # clock + b[10], b[11], b[12], b[13], b[14], b[15] + ] + +static func v4_rng(rng: RandomNumberGenerator): + # 16 random bytes with the bytes on index 6 and 8 modified + var b = uuidbinrng(rng) + + return '%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x' % [ + # low + b[0], b[1], b[2], b[3], + + # mid + b[4], b[5], + + # hi + b[6], b[7], + + # clock + b[8], b[9], + + # clock + b[10], b[11], b[12], b[13], b[14], b[15] + ] + +var _uuid: Array + +func _init(rng := RandomNumberGenerator.new()) -> void: + _uuid = uuidbinrng(rng) + +func as_array() -> Array: + return _uuid.duplicate() + +func as_dict(big_endian := true) -> Dictionary: + if big_endian: + return { + "low" : (_uuid[0] << 24) + (_uuid[1] << 16) + (_uuid[2] << 8 ) + _uuid[3], + "mid" : (_uuid[4] << 8 ) + _uuid[5], + "hi" : (_uuid[6] << 8 ) + _uuid[7], + "clock": (_uuid[8] << 8 ) + _uuid[9], + "node" : (_uuid[10] << 40) + (_uuid[11] << 32) + (_uuid[12] << 24) + (_uuid[13] << 16) + (_uuid[14] << 8 ) + _uuid[15] + } + else: + return { + "low" : _uuid[0] + (_uuid[1] << 8 ) + (_uuid[2] << 16) + (_uuid[3] << 24), + "mid" : _uuid[4] + (_uuid[5] << 8 ), + "hi" : _uuid[6] + (_uuid[7] << 8 ), + "clock": _uuid[8] + (_uuid[9] << 8 ), + "node" : _uuid[10] + (_uuid[11] << 8 ) + (_uuid[12] << 16) + (_uuid[13] << 24) + (_uuid[14] << 32) + (_uuid[15] << 40) + } + +func as_string() -> String: + return '%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x' % [ + # low + _uuid[0], _uuid[1], _uuid[2], _uuid[3], + + # mid + _uuid[4], _uuid[5], + + # hi + _uuid[6], _uuid[7], + + # clock + _uuid[8], _uuid[9], + + # node + _uuid[10], _uuid[11], _uuid[12], _uuid[13], _uuid[14], _uuid[15] + ] + +func is_equal(other) -> bool: + # Godot Engine compares Array recursively + # There's no need for custom comparison here. + return _uuid == other._uuid