Initial commit.

This commit is contained in:
Joshua Bemenderfer 2020-04-07 11:12:48 -04:00
commit 0c64ddb7c6
18 changed files with 562 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.import

13
Main.gd Normal file
View File

@ -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()

11
Main.tscn Normal file
View File

@ -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"]

18
Services/CLIService.gd Normal file
View File

@ -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

View File

@ -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)

View File

@ -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")

View File

@ -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

122
Services/Old/ModService.gd Normal file
View File

@ -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

View File

@ -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)

24
Services/TaskService.gd Normal file
View File

@ -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

26
export_presets.cfg Normal file
View File

@ -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=""

6
packages/base/base.tscn Normal file
View File

@ -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 )

14
packages/base/main.gd Normal file
View File

@ -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")

View File

@ -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());

View File

@ -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")

View File

@ -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
}

@ -0,0 +1 @@
Subproject commit ddf67cc7b8274c5fb77a71c828bab2991f1ee12a

28
project.godot Normal file
View File

@ -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"