From 0c64ddb7c62ae08c8cb14df44efcaf042cab467d Mon Sep 17 00:00:00 2001 From: Joshua Bemenderfer Date: Tue, 7 Apr 2020 11:12:48 -0400 Subject: [PATCH] Initial commit. --- .gitignore | 1 + Main.gd | 13 +++ Main.tscn | 11 +++ Services/CLIService.gd | 18 ++++ Services/ConsoleService.gd | 14 +++ Services/NetworkService.gd | 49 +++++++++++ Services/Old/ConnectionService.gd | 74 ++++++++++++++++ Services/Old/ModService.gd | 122 ++++++++++++++++++++++++++ Services/PackageService.gd | 81 +++++++++++++++++ Services/TaskService.gd | 24 +++++ export_presets.cfg | 26 ++++++ packages/base/base.tscn | 6 ++ packages/base/main.gd | 14 +++ packages/vest_mainmenu/main.gd | 14 +++ packages/vest_mainmenu/main_menu.gd | 15 ++++ packages/vest_mainmenu/main_menu.tscn | 51 +++++++++++ packages/voxel_mesher/godot_headers | 1 + project.godot | 28 ++++++ 18 files changed, 562 insertions(+) create mode 100644 .gitignore create mode 100644 Main.gd create mode 100644 Main.tscn create mode 100644 Services/CLIService.gd create mode 100644 Services/ConsoleService.gd create mode 100644 Services/NetworkService.gd create mode 100644 Services/Old/ConnectionService.gd create mode 100644 Services/Old/ModService.gd create mode 100644 Services/PackageService.gd create mode 100644 Services/TaskService.gd create mode 100644 export_presets.cfg create mode 100644 packages/base/base.tscn create mode 100644 packages/base/main.gd create mode 100644 packages/vest_mainmenu/main.gd create mode 100644 packages/vest_mainmenu/main_menu.gd create mode 100644 packages/vest_mainmenu/main_menu.tscn create mode 160000 packages/voxel_mesher/godot_headers create mode 100644 project.godot diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..37f05f2 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.import diff --git a/Main.gd b/Main.gd new file mode 100644 index 0000000..0dcb461 --- /dev/null +++ b/Main.gd @@ -0,0 +1,13 @@ +extends Node + +onready var CLIService = $"/root/CLIService" +onready var ConsoleService = $"/root/ConsoleService" +onready var PackageService = $"/root/PackageService" + +# Called when the node enters the scene tree for the first time. +func _ready(): + CLIService.parse_arguments() + + PackageService.discover_packages() + PackageService.load_packages() + PackageService.init_packages() diff --git a/Main.tscn b/Main.tscn new file mode 100644 index 0000000..0025597 --- /dev/null +++ b/Main.tscn @@ -0,0 +1,11 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://Main.gd" type="Script" id=1] + +[node name="Main" type="Node"] +script = ExtResource( 1 ) + +[node name="Scene" type="Node" parent="."] + +[node name="Packages" type="Node" parent="."] +[connection signal="script_changed" from="." to="." method="_on_Main_script_changed"] diff --git a/Services/CLIService.gd b/Services/CLIService.gd new file mode 100644 index 0000000..d549189 --- /dev/null +++ b/Services/CLIService.gd @@ -0,0 +1,18 @@ +extends Node + +var _args = { + +} + +func parse_arguments(): + var args = Array(OS.get_cmdline_args()) + for arg in args: + arg = arg.trim_prefix('--') + var split_arg = arg.split('=') + _args[split_arg[0]] = split_arg[1] + +func get(arg): + if arg in _args: + return _args[arg] + else: + return null diff --git a/Services/ConsoleService.gd b/Services/ConsoleService.gd new file mode 100644 index 0000000..6314c1e --- /dev/null +++ b/Services/ConsoleService.gd @@ -0,0 +1,14 @@ +extends Node + +signal on_log; +signal on_error; + +var log_messages = [] + +func log(message): + log_messages.append(String(message)) + emit_signal('on_log', log_messages) + +func error(message): + log_messages.append("Error: " + String(message)) + emit_signal('on_error', log_messages) diff --git a/Services/NetworkService.gd b/Services/NetworkService.gd new file mode 100644 index 0000000..9570e2d --- /dev/null +++ b/Services/NetworkService.gd @@ -0,0 +1,49 @@ +extends Node + +signal on_client_connected; +signal on_server_connected; +signal on_server_disconnected; + +var NETWORK_MODE = "client" +var local_server_pid = null +var server_hash = null + +func _ready(): + get_tree().connect("network_peer_connected", self, "server_on_peer_connected") + get_tree().connect("connected_to_server", self, "client_on_connection_succeeded") + get_tree().connect("connection_failed", self, "client_on_connection_failed") + self.connect("tree_exiting", self, "cleanup_local_server") + +func create_local_server(): + local_server_pid = OS.execute(OS.get_executable_path(), ["--mode=server", "--port=9801"], false) + +func cleanup_local_server(): + if local_server_pid: + OS.kill(local_server_pid) + +func get_local_connection(): + return {"host": "127.0.0.1", "port": 9801} + +func init_server(port: int): + NETWORK_MODE = "server" + var peer = NetworkedMultiplayerENet.new() + peer.create_server(port, 256) + get_tree().set_network_peer(peer) + + +func init_client(host: String, port: int): + NETWORK_MODE = "client" + server_hash = (host+String(port)).md5_text() + var peer = NetworkedMultiplayerENet.new() + peer.create_client(host, port) + get_tree().set_network_peer(peer) + +func server_on_peer_connected(id: int): + emit_signal("on_client_connected", id) + +func client_on_connection_succeeded(): + emit_signal("on_server_connected"); + +func client_on_connection_failed(): + emit_signal("on_server_disconnected", "Connection Failed") + diff --git a/Services/Old/ConnectionService.gd b/Services/Old/ConnectionService.gd new file mode 100644 index 0000000..ff90cc2 --- /dev/null +++ b/Services/Old/ConnectionService.gd @@ -0,0 +1,74 @@ +extends Node + +var ConsoleService +var NetworkService + +func _ready(): + NetworkService = $"/root/NetworkService" + ConsoleService = $"/root/ConsoleService" + NetworkService.connect("on_client_connected", self, "on_client_connected") + NetworkService.connect("on_server_connected", self, "on_server_connected") + NetworkService.connect("on_server_disconnected", self, "on_server_disconnected") + +func on_client_connected(id: int): + if NetworkService.NETWORK_MODE == 'server': + var server_mods = get_local_mods() + rpc_id(id, "compute_needed_mods", server_mods) + +func get_local_mods(): + var md5_hashes = [] + for mod_desc in ModService.mod_arr: + if not mod_desc.md5: continue + md5_hashes.push_back(mod_desc.md5) + return md5_hashes + +remote func send_mods_to_client(id: int, needed_mods: Array): + var ModService = $"/root/ModService" + for mod_desc in ModService.mod_arr: + if not needed_mods.has(mod_desc.md5): + continue + if mod_desc.type != 'zip': + continue + var mod_file = File.new() + mod_file.open(mod_desc.path, 1) + var file_length = mod_file.get_len() + var bytes = mod_file.get_buffer(file_length) + ConsoleService.log("Sending mod "+mod_desc.name+" with size: "+String(bytes.size()/1000000)+"MB") + rpc_id(id, "receive_mod", mod_desc.name, bytes) + +remote func compute_needed_mods(server_mods: Array): + var needed_mods_md5 = server_mods + var dir = Directory.new() + var err = dir.make_dir_recursive('user://servers/'+NetworkService.server_hash+'/mods') + var mod_dir_exists = dir.open('user://servers/'+NetworkService.server_hash+'/mods') + dir.list_dir_begin() + + while true: + var file = dir.get_next() + var basename = file.get_basename() + if file == "": + break + if file.ends_with(".zip") and server_mods.has(basename): + needed_mods_md5.erase(basename) + + dir.list_dir_end() + + rpc_id(1, 'send_mods_to_client', get_tree().get_network_unique_id(), needed_mods_md5) + +remote func receive_mod(name: String, bytes: PoolByteArray): + ConsoleService.log("Receiving mod "+name+" with size: "+String(bytes.size()/1000000)+"MB") + var file_path = 'user://servers/'+NetworkService.server_hash+'/mods/'+name+'.zip' + var dir_path = file_path.get_base_dir() + var mod_file = File.new() + var dir = Directory.new() + var err = dir.make_dir_recursive(dir_path) + mod_file.open(file_path, 2) + mod_file.store_buffer(bytes) + mod_file.close() + dir.rename(file_path, dir_path+'/'+mod_file.get_md5(file_path)+'.zip') + +func on_server_connected(): + pass + +func on_server_disconnected(): + pass diff --git a/Services/Old/ModService.gd b/Services/Old/ModService.gd new file mode 100644 index 0000000..6b09191 --- /dev/null +++ b/Services/Old/ModService.gd @@ -0,0 +1,122 @@ +extends Node + +var ConsoleService + +var data_path = "." +var mod_dict = {} +var mod_arr = [] +var mod_instances = {} + +func _ready(): + ConsoleService = $"/root/ConsoleService" + +# Locate all mods that can be loaded for the given game. +func find_local_mods(): + var dir = Directory.new() + + var mod_dir_exists = dir.open(data_path + "/mods") + if (mod_dir_exists != 0): + dir.change_dir(data_path) + dir.make_dir("mods") + dir.change_dir(data_path + "/mods") + + dir.list_dir_begin() + + while true: + var file = dir.get_next() + var basename = file.get_basename() + if file == "": + break + elif file.ends_with(".zip"): + var mod_desc = { + "name": basename, + "path": data_path + "/mods/" + file, + "md5": File.new().get_md5(data_path + "/mods/" + file), + "type": "zip" + } + mod_arr.append(mod_desc) + mod_dict[mod_desc.name] = mod_desc + elif dir.file_exists(basename+"/main.gd"): + var mod_desc = { + "name": basename, + "path": data_path + "/mods/" + file, + "md5": null, + "type": "folder" + } + mod_arr.append(mod_desc) + mod_dict[mod_desc.name] = mod_desc + + dir.list_dir_end() + +# Prepare initial instances of mods for preprocessing. Used to determine mod dependencies and configuration +# Prior to final loading and initialization. +func preload_instances(): + if mod_arr.size() == 0: + ConsoleService.error("[ModService] No mods found (Run find_local_mods and download_server_mods first)") + return + + for mod_desc in mod_arr: + mod_instances[mod_desc.name] = _get_mod_instance(mod_desc) + if !mod_instances[mod_desc.name]: + ConsoleService.error("[ModService] Failed to load mod '"+mod_desc.name+"'") + +func compute_dependency_graph(): + if mod_instances.size() == 0: + ConsoleService.error("[ModService] Mods have not been preloaded (Run preload_instances first)") + return + + var resolved = []; # Properly ordered mod list. + for mod_desc in mod_arr: + _resolve_dependency(mod_desc, resolved, []) + + return resolved + +func _resolve_dependency(mod_desc: Dictionary, resolved: Array, unresolved: Array): + if mod_desc in resolved: + return + # Seen is used to prevent + unresolved.append(mod_desc) + if not "dependencies" in mod_instances[mod_desc.name]: + resolved.append(mod_desc) + unresolved.erase(mod_desc) + return + + var deps = mod_instances[mod_desc.name].dependencies + for dep in deps: + var dep_desc = mod_dict[dep] + if not dep_desc in resolved: + if dep_desc in unresolved: + ConsoleService.error("[ModService] Circular dependency between '"+mod_desc.name+"' <-> '"+dep+"'") + assert(false) + if not dep in mod_instances: + ConsoleService.error("[ModService] Mod '"+mod_desc.name+"' requires mod '"+dep+"' to be installed, but it is not") + assert(false) + _resolve_dependency(dep_desc, resolved, unresolved) + resolved.append(mod_desc) + unresolved.erase(mod_desc) + +func load_mods(): + if mod_arr.size() == 0: + print("[ModService] No mods found (Run find_mods first)") + return + + for mod_desc in mod_arr: + var mod_instance = _get_mod_instance(mod_desc) + mod_instances[mod_desc.name] = mod_instance + if !mod_instance: + ConsoleService.log("[ModService] Failed to load mod '"+mod_desc.name+"'") + continue + + $"/root/Main/Mods".add_child(mod_instance) + if mod_instance.has_method('init'): + mod_instance.init() + +func _get_mod_instance(mod_desc: Dictionary): + if mod_desc.type == 'zip': + ProjectSettings.load_resource_pack(mod_desc.path, true) + + var mod_script = load("res://mods/"+mod_desc.name+"/main.gd"); + if !mod_script: + return + var mod_instance = mod_script.new() + return mod_instance diff --git a/Services/PackageService.gd b/Services/PackageService.gd new file mode 100644 index 0000000..a3e9cd3 --- /dev/null +++ b/Services/PackageService.gd @@ -0,0 +1,81 @@ +extends Node + +onready var ConsoleService = $"/root/ConsoleService" + +var package_dict = {} +var package_load_order = [] + +# Locate all packages that can be loaded. +func discover_packages(): + var dir = Directory.new() + dir.open("res://packages") + dir.list_dir_begin() + + while true: + var package_name = dir.get_next() + if package_name == "": break + if not dir.current_is_dir() or package_name == "." or package_name == "..": continue + + var package_script = load("res://packages/"+package_name+"/main.gd"); + if !package_script: continue + + var package = { + "name": package_script.package.name if package_script.package.has("name") else package_name, + "version": package_script.package.version if package_script.package.has("version") else "1.0.0", + "dependencies": package_script.package.dependencies if package_script.package.has("dependencies") else [], + "_script": package_script + } + package_dict[package.name] = package + + dir.list_dir_end() + _compute_dependency_graph() + +func load_packages(): + for package_name in package_load_order: + var package = package_dict[package_name] + package._instance = package._script.new() + if !package._instance: + ConsoleService.log("[PackageService] Failed to load package '"+package.name+"'") + continue + +func init_packages(): + for package_name in package_load_order: + var package = package_dict[package_name] + if not package.has("_instance"): + ConsoleService.log("[PackageService] Package '"+package.name+"' has not been loaded. Please load before initializing.") + continue + $"/root/Main/Packages".add_child(package._instance) + if package._instance.has_method('init'): + package._instance.init() + +func _compute_dependency_graph(): + if package_dict.size() == 0: + ConsoleService.error("[PackageService] packages have not been preloaded (Run preload_instances first)") + return + + var resolved = []; # Properly ordered package list. + for package in package_dict.values(): + _resolve_dependency(package, resolved, []) + + package_load_order = resolved + +func _resolve_dependency(package: Dictionary, resolved: Array, unresolved: Array): + if package.name in resolved: + return + # Unresolved is used to prevent recursive lookups. + unresolved.append(package.name) + + for dependency_name in package.dependencies: + if dependency_name in resolved: continue + if dependency_name in unresolved: + ConsoleService.error("[PackageService] Circular dependency between '"+package.name+"' <-> '"+dependency_name+"'") + assert(false) + if not package_dict.has(dependency_name): + ConsoleService.error("[PackageService] package '"+package.name+"' requires package '"+dependency_name+"' to be installed, but it is not") + assert(false) + + var dependency = package_dict[dependency_name] + _resolve_dependency(dependency, resolved, unresolved) + + resolved.append(package.name) + unresolved.erase(package.name) diff --git a/Services/TaskService.gd b/Services/TaskService.gd new file mode 100644 index 0000000..32d67ee --- /dev/null +++ b/Services/TaskService.gd @@ -0,0 +1,24 @@ +extends Node + +var tasks = {} + +func start(name: String, message: String): + var task = Task.new(name, message) + if not tasks[task.name]: tasks[task.name] = [] + tasks[task.name].append(task) + emit_signal('started:'+name, task) + +func complete(name: String): + if not tasks[name]: return + var task = tasks[name].pop_front() + if tasks[name].size() == 0: + tasks.erase(name) + emit_signal('completed:'+name, task) + +class Task: + var name = '' + var message = '' + + func _init(_name: String, _message: String): + name = _name + message = _message diff --git a/export_presets.cfg b/export_presets.cfg new file mode 100644 index 0000000..e1a1b8f --- /dev/null +++ b/export_presets.cfg @@ -0,0 +1,26 @@ +[preset.0] + +name="Linux/X11" +platform="Linux/X11" +runnable=true +custom_features="" +export_filter="scenes" +export_files=PoolStringArray( "res://mods/base/base.tscn" ) +include_filter="" +exclude_filter="" +export_path="" +patch_list=PoolStringArray( ) +script_export_mode=0 +script_encryption_key="" + +[preset.0.options] + +texture_format/bptc=false +texture_format/s3tc=true +texture_format/etc=false +texture_format/etc2=false +texture_format/no_bptc_fallbacks=true +binary_format/64_bits=true +binary_format/embed_pck=false +custom_template/release="" +custom_template/debug="" diff --git a/packages/base/base.tscn b/packages/base/base.tscn new file mode 100644 index 0000000..7d31b3f --- /dev/null +++ b/packages/base/base.tscn @@ -0,0 +1,6 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://packages/base/main.gd" type="Script" id=1] + +[node name="Node" type="Node"] +script = ExtResource( 1 ) diff --git a/packages/base/main.gd b/packages/base/main.gd new file mode 100644 index 0000000..4b78827 --- /dev/null +++ b/packages/base/main.gd @@ -0,0 +1,14 @@ +extends Node + +onready var ConsoleService = $"/root/ConsoleService" + +const package = { + "name": "base", + "version": "0.1.0", + "dependencies": [ + "vest_mainmenu" + ] +} + +func init(): + ConsoleService.log("[Base] Mod Init") diff --git a/packages/vest_mainmenu/main.gd b/packages/vest_mainmenu/main.gd new file mode 100644 index 0000000..7938391 --- /dev/null +++ b/packages/vest_mainmenu/main.gd @@ -0,0 +1,14 @@ +extends Node + +onready var ConsoleService = $"/root/ConsoleService" + +const package = { + "name": "vest_mainmenu", + "version": "0.1.0" +} + +func init(): + ConsoleService.log("[Vest Mainmenu] Mod Init") + var scene_root = $"/root/Main/Scene" + var main_menu = preload("main_menu.tscn") + scene_root.add_child(main_menu.instance()); diff --git a/packages/vest_mainmenu/main_menu.gd b/packages/vest_mainmenu/main_menu.gd new file mode 100644 index 0000000..38d78c3 --- /dev/null +++ b/packages/vest_mainmenu/main_menu.gd @@ -0,0 +1,15 @@ +extends Node + +# Called when the node enters the scene tree for the first time. +func _ready(): + var NetworkService = $"/root/NetworkService" + if NetworkService.NETWORK_MODE == 'server': + $"TabContainer/Singleplayer".queue_free() + + var ConsoleService = $"/root/ConsoleService" + ConsoleService.connect("on_log", self, "update_log") + ConsoleService.connect("on_error", self, "update_log") + $"TabContainer/Logs/LogList".text = PoolStringArray(ConsoleService.log_messages).join("\n") + +func update_log(log_messages): + $"TabContainer/Logs/LogList".text = PoolStringArray(log_messages).join("\n") diff --git a/packages/vest_mainmenu/main_menu.tscn b/packages/vest_mainmenu/main_menu.tscn new file mode 100644 index 0000000..bdffa24 --- /dev/null +++ b/packages/vest_mainmenu/main_menu.tscn @@ -0,0 +1,51 @@ +[gd_scene load_steps=2 format=2] + +[ext_resource path="res://packages/vest_mainmenu/main_menu.gd" type="Script" id=1] + +[node name="Node" type="Node"] +script = ExtResource( 1 ) + +[node name="TabContainer" type="TabContainer" parent="."] +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_left = 50.0 +margin_top = 49.1426 +margin_right = -50.0 +margin_bottom = -50.8574 +tab_align = 0 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Logs" type="Control" parent="TabContainer"] +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_left = 4.0 +margin_top = 32.0 +margin_right = -4.0 +margin_bottom = -4.0 + +[node name="LogList" type="RichTextLabel" parent="TabContainer/Logs"] +anchor_right = 1.0 +anchor_bottom = 1.0 +scroll_following = true +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Singleplayer" type="Control" parent="TabContainer"] +visible = false +anchor_right = 1.0 +anchor_bottom = 1.0 +margin_left = 4.0 +margin_top = 32.0 +margin_right = -4.0 +margin_bottom = -4.0 + +[node name="Start Game" type="Button" parent="TabContainer/Singleplayer"] +margin_right = 12.0 +margin_bottom = 20.0 +text = "Start Game" +__meta__ = { +"_edit_use_anchors_": false +} diff --git a/packages/voxel_mesher/godot_headers b/packages/voxel_mesher/godot_headers new file mode 160000 index 0000000..ddf67cc --- /dev/null +++ b/packages/voxel_mesher/godot_headers @@ -0,0 +1 @@ +Subproject commit ddf67cc7b8274c5fb77a71c828bab2991f1ee12a diff --git a/project.godot b/project.godot new file mode 100644 index 0000000..a5a1e71 --- /dev/null +++ b/project.godot @@ -0,0 +1,28 @@ +; Engine configuration file. +; It's best edited using the editor UI and not directly, +; since the parameters that go here are not all obvious. +; +; Format: +; [section] ; section goes between [] +; param=value ; assign values to parameters + +config_version=4 + +_global_script_classes=[ ] +_global_script_class_icons={ + +} + +[application] + +config/name="Vest" +run/main_scene="res://Main.tscn" +config/use_custom_user_dir=true +config/custom_user_dir_name="Vest" + +[autoload] + +NetworkService="*res://Services/NetworkService.gd" +CLIService="*res://Services/CLIService.gd" +ConsoleService="*res://Services/ConsoleService.gd" +PackageService="*res://Services/PackageService.gd"