Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
/src/.godot
/src/android/
/src/addons/godot-sqlite
/src/setup_downloads/
31 changes: 20 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,35 @@ Client is the interface used to connect and interact with the virtual world of O

## Development Quick Start

### 1: Download source code
### Download source code
For cloning this project on Linux:
```bash
# Clone this repository.
git clone --recurse-submodules https://github.com/OpenMinerva/client
```

### 2: Import project into Godot.
2.a: Open your copy of the [Godot Engine](https://godotengine.org/).
### Setup the environment
This repository does not ship some required addons in order to prevent unnecessary weight in the git history. Instead there is a script `/src/setup_environment.sh` (Linux only) that can be executed to automatically download the addons. Windows users will need to install these addons manually for now.

2.b: Click on "Import" near the top of the application window.
For simplicity, here is a list of the missing addons:
- [godot-sqlite](https://github.com/2shady4u/godot-sqlite) - Tagged `v4.8` - [Download](https://github.com/2shady4u/godot-sqlite/releases/download/v4.8/addons.zip)

2.c: Navigate to the location where the source code was downloaded to.

2.d: Navigate to the subdirectory `/src`.

2.e: Select `project.godot` to import this project.
Download these addons into the `/src/addons/` folder of this repository.
Example:
```
/src
/addons
/godot-sqlite
/discord-rpc-gd
/rpc-await
```

### 3: Open
Double click on the added entry.
### Import project into Godot.
- Open your copy of the [Godot Engine](https://godotengine.org/).
- Click on "Import" near the top of the application window.
- Navigate to the location where the source code was downloaded to.
- Navigate to the sub-directory `/src`.
- Select `project.godot` to import this project.

## Contributing
See [CONTRIBUTING.md](https://github.com/OpenMinerva/client/blob/alpha/CONTRIBUTING.md) for guidelines, and information.
3 changes: 2 additions & 1 deletion src/project.godot
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ HTTP="*uid://d3cnfdwjxopsx"
FileDrop="*uid://btgqgfpx0av4m"
Events="*uid://c656spc3ppdlw"
StateManager="*uid://b5vvw77bmn4yc"
Database="*uid://cbm5uyffux8ca"
OAuth2Client="*uid://c4b880pwgfrty"
UrlParser="*uid://budprjmmpally"

Expand All @@ -54,7 +55,7 @@ window/stretch/aspect="expand"

[editor_plugins]

enabled=PackedStringArray("res://addons/Gizmo3DScript/plugin.cfg", "res://addons/discord-rpc-gd/plugin.cfg", "res://addons/godot-oauth2client/plugin.cfg", "res://addons/godot-urlparser/plugin.cfg", "res://addons/rpc-await/plugin.cfg")
enabled=PackedStringArray("res://addons/Gizmo3DScript/plugin.cfg", "res://addons/discord-rpc-gd/plugin.cfg", "res://addons/godot-oauth2client/plugin.cfg", "res://addons/godot-sqlite/plugin.cfg", "res://addons/godot-urlparser/plugin.cfg", "res://addons/rpc-await/plugin.cfg")

[gui]

Expand Down
7 changes: 2 additions & 5 deletions src/scenes/managers/app/network.gd
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ var _heartbeats = { }
@onready var scene_m = get_node("../SceneManager")


func start_server(port: int = 0, root_scene: Enum.BaseLevel = Enum.BaseLevel.GRID) -> bool:
func start_server(port: int = 0, root_scene: Enum.BaseLevel = Enum.BaseLevel.GRID, scene_dir: String = "") -> bool:
GlobalLogger.log("Starting a new server.")

# Get an available port. If port was defined, force that port or fail.
Expand Down Expand Up @@ -68,10 +68,7 @@ func start_server(port: int = 0, root_scene: Enum.BaseLevel = Enum.BaseLevel.GRI
_session_db.set(_scene, _create_instance_db_entry(_mp_api, _scene, Enum.PrivacyLevel.INVITE, port, "host"))

# Create server root scene.
if root_scene:
scene_m.set_master_root_from_program(_scene, root_scene)
else:
scene_m.set_master_root_from_program(_scene, Enum.BaseLevel.GRID)
scene_m.set_master_root_from_program(_scene, root_scene, scene_dir)

scene_m.start_master_scene(_scene)
scene_m.set_active_session(_scene)
Expand Down
12 changes: 10 additions & 2 deletions src/scenes/managers/app/scene.gd
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func destroy_master_scene(id: String):
return


func set_master_root_from_program(id: String, scene_type: Enum.BaseLevel) -> void:
func set_master_root_from_program(id: String, scene_type: Enum.BaseLevel, scene_dir: String = "") -> void:
var _scene = get_master_scene(id)

var _root_scene: String = _get_scene_by_type(scene_type)
Expand All @@ -72,7 +72,15 @@ func set_master_root_from_program(id: String, scene_type: Enum.BaseLevel) -> voi
await get_tree().process_frame

# Use spawnable system to read the TSCN file, and instantiate it into the multiplayer instance.
await spawnable_file_handling.load_spawnable(_root_scene)
if scene_type == Enum.BaseLevel.CUSTOM:
if scene_dir == "":
GlobalLogger.log("Tried to load a custom scene, but there was not a directiory!", Enum.LogLevel.WARNING)
_root_scene = _get_scene_by_type(Enum.BaseLevel.GRID)
await spawnable_file_handling.load_spawnable(_root_scene)
else:
await spawnable_file_handling.load_spawnable(scene_dir)
else:
await spawnable_file_handling.load_spawnable(_root_scene)

# Remove the "root" node of the world, and instead parent all nodes under the true instance root.
# HACK: Force reparent the children of the node to the world root.
Expand Down
2 changes: 1 addition & 1 deletion src/scenes/managers/app/scene.gd.uid
Original file line number Diff line number Diff line change
@@ -1 +1 @@
uid://3ylijxenr1bg
uid://c82mspg5iieer
8 changes: 3 additions & 5 deletions src/scripts/enum.gd
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ enum BaseLevel {
HOME = 1,
EMPTY = 2,
GRID = 3,
CUSTOM = 4,
}
enum PrivacyLevel {
INVITE = 0,
Expand All @@ -35,11 +36,8 @@ enum LogLevel {
ERROR = 3,
}
enum SpawnableType {
EMPTY = 0,
BOX = 1,
RIGIDBODY = 2,
CAPSULE = 3,
MODEL = 10,
ITEM = 0,
WORLD = 1,
}

const Settings = {
Expand Down
64 changes: 50 additions & 14 deletions src/scripts/spawnable_file_handling.gd
Original file line number Diff line number Diff line change
Expand Up @@ -15,37 +15,47 @@ func _ready() -> void:
return


func save_spawnable(root: Node, name_override: String = "") -> void:
func save_spawnable(root: Node, type: Enum.SpawnableType = Enum.SpawnableType.ITEM, name_override: String = "") -> void:
# Take a node path from the scene, and save that node to the file
# In order to save nodes using the ResourceSaver, we need to make all nodes that are a child of the root have the owner of the root.
# FIXME: When saving the node scene, The FileManager should be used entirely.
# ResourceLoader / ResourceSaver should not be called from here.
var _spawnable_name: String = ""

# TODO: Get hash of file, and use that hash for filename on disk.
if name_override != "":
_spawnable_name = name_override
elif root.has_meta("pretty_name") == true:
_spawnable_name = root.get_meta("pretty_name")
else:
_spawnable_name = "Untitled"

# Duplicate the node, this is so we can make modifications to it (if required to)
# This duplicate does not touch the scene tree.
root = root.duplicate()

var original_owners = _get_node_ownership(root)
_set_temporary_ownership_recursive(root, root)

# Externalize assets makes it so that networking things are consistent.
_externalize_assets(root)

# Remove PlayerControllers.
_remove_invalid_nodes(root)

var packed_scene = _create_packed_scene(root)
var file_path = FileManager._current_path() + "/" + _spawnable_name + ".tscn"
var _data_hash: String = var_to_str(packed_scene).sha256_text()

_externalize_assets(root, _data_hash)

var file_path = FileManager._current_path() + _data_hash + ".tscn"
ResourceSaver.save(packed_scene, file_path)

# Add spawnable to the database.
var _database_row: Dictionary = {
"hash": _data_hash,
"name": _spawnable_name,
"directory": file_path,
"creation_date": int(Time.get_unix_time_from_system()),
"type": type,
}
Database.set_spawnable(_data_hash, _database_row)

# I have no idea what ownership is in terms of nodes are right now, so put it back as to not break anything
_restore_ownership(root, original_owners)

Expand Down Expand Up @@ -141,6 +151,11 @@ func load_spawnable(path: String) -> void:
return


func _add_spawnable_asset_relation(spawnable_hash: String, asset_hash: String) -> void:
Database.set_spawnable_asset_rel(spawnable_hash, asset_hash)
return


func _flatten_resource(resource: Resource):
var _resource_props = resource.get_property_list()
var _response: Dictionary = { "properties": [], "class": "" }
Expand All @@ -155,22 +170,43 @@ func _flatten_resource(resource: Resource):
return _response


func _externalize_assets(root: Node) -> Node:
func _externalize_assets(root: Node, spawnable_hash: String) -> Node:
# TODO: This way of externalizing assets functions, but is not what is intended. I need to create a thorough walk-through of a node hierarchy and individually get all resources of that node. Right now this seems to just do a surface level extraction.
for property in root.get_property_list():
var value = root.get(property.name)

if value is Resource and value.resource_path.is_empty():
# TODO: Hash the files to use as names?
var external_path = "user://spawnables_assets/%d.res" % randi()
ResourceSaver.save(value, external_path)
# TODO: Throw this onto a separate thread.
var _data_hash: String = var_to_str(value).sha256_text()
var _data_size: int
var _external_path = "user://spawnables_assets/%s.res" % _data_hash

ResourceSaver.save(value, _external_path)

var external_resource = load(external_path)
external_resource.take_over_path(external_path)
var external_resource = load(_external_path)
external_resource.take_over_path(_external_path)

root.set(property.name, external_resource)

if Database.get_asset(_data_hash) == { }:
# Add to the database if we do not already have the asset in the database.

# Get the file size of the asset
var _file = FileAccess.open(_external_path, FileAccess.READ)
_data_size = _file.get_length()
_file.close()

var _database_row: Dictionary = {
"hash": _data_hash,
"directory": _external_path,
"size": _data_size,
}
Database.set_asset(_data_hash, _database_row)

_add_spawnable_asset_relation(spawnable_hash, _data_hash)

for child in root.get_children():
_externalize_assets(child)
_externalize_assets(child, spawnable_hash)

return root

Expand Down
2 changes: 1 addition & 1 deletion src/scripts/spawnable_file_handling.gd.uid
Original file line number Diff line number Diff line change
@@ -1 +1 @@
uid://4rhvu1oluj66
uid://bl8yv02xhnx6s
Loading