-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
2537e61
commit 9641703
Showing
1,936 changed files
with
37,716 additions
and
3,765 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
# GDQuest Sparkly Bag Utils | ||
|
||
Is a collection of utilities dealing with repeating patterns that we discovered in time. | ||
|
||
They're not necessarily related to each other, and some are generic while others are very specific. | ||
|
||
The collection includes: | ||
|
||
- A post import script for GLTF resources that cleans up any inconsistencies with naming conventions and adds support for `AnimatableBody3D` convention via the `-anim` suffix. | ||
- A utility library called `SparklyBagUtils`. | ||
|
||
## ✗ WARNING | ||
|
||
> Compatible: Godot `>= v4.0` | ||
## ✓ Install | ||
|
||
### Manual | ||
|
||
1. Copy the contents of this folder into `res://addons/gdquest_sparkly_bag/`. | ||
1. Profit. | ||
|
||
### gd-plug | ||
|
||
1. Install **gd-plug** using the Godot Asset Library. | ||
1. Save the following code into the file `res://plug.gd` (create the file if necessary): | ||
|
||
```gdscript | ||
#!/usr/bin/env -S godot --headless --script | ||
extends "res://addons/gd-plug/plug.gd" | ||
func _plugging() -> void: | ||
plug( | ||
"[email protected]:GDQuest/godot-addons.git", | ||
{include = ["addons/gdquest_sparkly_bag"]} | ||
) | ||
``` | ||
|
||
1. On Linux, make the `res://plug.gd` script executable with `chmod +x plug.gd`. | ||
1. Using the command line, run `./plug.gd install` or `godot --headless --script plug.gd install`. |
22 changes: 22 additions & 0 deletions
22
addons/gdquest_sparkly_bag/autoloads/reset_net_injector.gd
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
extends Node | ||
|
||
var reset_net_y_global_position := -10.0 | ||
|
||
|
||
func _ready() -> void: | ||
var scene := get_tree().current_scene | ||
if scene is Node3D: | ||
var reset_net := Area3D.new() | ||
reset_net.area_entered.connect(_on_node_entered) | ||
reset_net.body_entered.connect(_on_node_entered) | ||
|
||
var collision_shape := CollisionShape3D.new() | ||
collision_shape.shape = WorldBoundaryShape3D.new() | ||
reset_net.add_child(collision_shape) | ||
scene.add_child(reset_net) | ||
reset_net.global_position.y = reset_net_y_global_position | ||
|
||
|
||
func _on_node_entered(node: Node3D) -> void: | ||
if node.has_method("reset"): | ||
node.reset() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
@tool | ||
extends EditorScenePostImport | ||
|
||
const Utils := preload("sparkly_bag_utils.gd") | ||
|
||
const SUFFIXES = ["-anim", "-col"] | ||
const AABB_SIZE := "aabb_size" | ||
|
||
|
||
func _post_import(scene: Node) -> Object: | ||
for node in scene.find_children("*"): | ||
if node.name.ends_with("-anim") and node is MeshInstance3D: | ||
node.create_convex_collision() | ||
var animatable_body := AnimatableBody3D.new() | ||
animatable_body.name = "AnimatableBody3D" | ||
animatable_body.sync_to_physics = false | ||
for child in node.get_children(): | ||
# BUG: where the collision shape doesn't get transformed if `sync_to_physics = true` | ||
child.replace_by(animatable_body) | ||
child.free() | ||
node.name = node.name.replace("-anim", "") | ||
|
||
elif node.name.ends_with("-rigid"): | ||
node.name = node.name.replace("-rigid", "") | ||
|
||
elif node is AnimationPlayer: | ||
for animation_name in node.get_animation_list(): | ||
var animation_library: AnimationLibrary = node.get_animation_library("") | ||
var animation: Animation = node.get_animation(animation_name) | ||
if animation_name.ends_with("-noimp"): | ||
animation_library.remove_animation(animation_name) | ||
continue | ||
|
||
for track_index in range(animation.get_track_count()): | ||
var path := animation.track_get_path(track_index) | ||
var clean_path := "" | ||
for name_index in range(path.get_name_count()): | ||
for suffix in SUFFIXES: | ||
var path_name := path.get_name(name_index) | ||
if path_name.ends_with(suffix): | ||
clean_path = clean_path.path_join(path_name.replace(suffix, "")) | ||
break | ||
|
||
if not clean_path.is_empty(): | ||
animation.track_set_path(track_index, clean_path) | ||
|
||
if node is MeshInstance3D: | ||
var aabb: AABB = node.mesh.get_aabb() | ||
for index in range(node.mesh.get_surface_count()): | ||
var material_file_name: StringName = ( | ||
"%s.tres" % node.mesh.get("surface_%d/name" % index).to_snake_case() | ||
) | ||
var found := Utils.fs_find(material_file_name) | ||
if found.return_code != Utils.ReturnCode.OK: | ||
return scene | ||
|
||
if found.is_empty(): | ||
var message := ( | ||
"[ScenePostImport:WARN] Missing material `%s` for `%s`" | ||
% [material_file_name, node.name] | ||
) | ||
print(message) | ||
else: | ||
for path: String in found.result: | ||
var material := load(path) | ||
if material is ShaderMaterial: | ||
material.set_shader_parameter(AABB_SIZE, aabb.size) | ||
node.mesh.surface_set_material(index, material) | ||
var message := ( | ||
"[ScenePostImport:INFO] Material found @ `%s` for `%s`" | ||
% [path, node.name] | ||
) | ||
print(message) | ||
break | ||
return scene |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
class Inner: | ||
signal finished | ||
|
||
var _count := 0 | ||
|
||
func _init(count: int) -> void: | ||
_count = count | ||
|
||
func check() -> void: | ||
_count -= 1 | ||
if _count == 0: | ||
finished.emit() | ||
|
||
|
||
static func parallel(coroutines: Array[Callable]) -> Array: | ||
var results := [] | ||
var inner := Inner.new(coroutines.size()) | ||
for coroutine in coroutines.map( | ||
func(coroutine: Callable) -> Callable: return func() -> void: | ||
results.push_back(await coroutine.call()) | ||
inner.check() | ||
): | ||
coroutine.call() | ||
await inner.finished | ||
return results | ||
|
||
|
||
static func sequence(coroutines: Array[Callable]) -> Array: | ||
var result := [] | ||
for coroutine in coroutines: | ||
result.push_back(await coroutine.call()) | ||
return result |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
const SEP := "/" | ||
|
||
enum ReturnCode { OK, FAIL, WARN, SKIP, EXE_NOT_FOUND = 127, CODE_NOT_FOUND } | ||
|
||
|
||
class Result: | ||
var return_code := ReturnCode.OK | ||
var result: Variant = null | ||
|
||
func _init(result: Variant = null) -> void: | ||
self.result = result | ||
|
||
|
||
static func fs_find(pattern: String = "*", path: String = "res://", do_include_hidden := true, do_fail := true) -> Result: | ||
const TAG := { ReturnCode.FAIL: "FAIL", ReturnCode.SKIP: "SKIP" } | ||
|
||
var result: Result = Result.new([]) | ||
var is_file := not pattern.ends_with(SEP) | ||
pattern = pattern.rstrip(SEP) | ||
|
||
var dir := DirAccess.open(path) | ||
dir.include_hidden = do_include_hidden | ||
|
||
if DirAccess.get_open_error() != OK: | ||
result.return_code = ReturnCode.FAIL if do_fail else ReturnCode.SKIP | ||
printerr("%s: could not open [%s]" % [TAG[result.return_code], path]) | ||
return result | ||
|
||
if dir.list_dir_begin() != OK: | ||
result.return_code = ReturnCode.FAIL if do_fail else ReturnCode.SKIP | ||
printerr("%s: could not list contents of [%s]" % [TAG[result.return_code], path]) | ||
return result | ||
|
||
path = dir.get_next() | ||
while path.is_valid_filename(): | ||
var new_path: String = dir.get_current_dir().path_join(path) | ||
if dir.current_is_dir(): | ||
if not is_file and (path == pattern or new_path.match(pattern)): | ||
result.result.push_back(new_path) | ||
result.result += fs_find(pattern, new_path).result | ||
elif path == pattern or new_path.match(pattern): | ||
result.result.push_back(new_path) | ||
path = dir.get_next() | ||
return result | ||
|
||
|
||
static func fs_remove_dir(base_path: String) -> void: | ||
if not DirAccess.dir_exists_absolute(base_path): | ||
return | ||
|
||
var found := fs_find("*", base_path) | ||
for path: String in found.result: | ||
DirAccess.remove_absolute(path) | ||
|
||
found = fs_find("*/", base_path) | ||
found.result.reverse() | ||
for path in found.result: | ||
DirAccess.remove_absolute(path) | ||
DirAccess.remove_absolute(base_path) | ||
|
||
|
||
static func fs_copy_dir(from_path: String, to_path: String, ignore: Array[String] = []) -> ReturnCode: | ||
var dir := DirAccess.open(from_path) | ||
from_path = dir.get_current_dir() | ||
var from_base_path := from_path.get_base_dir() | ||
var found := fs_find("*", from_path) | ||
if found.return_code != ReturnCode.OK: | ||
return found.return_code | ||
|
||
for file_path: String in found.result: | ||
if ignore.any(func(p: String) -> bool: return file_path.match(p)): | ||
continue | ||
var destination_file_path := file_path.replace(from_base_path, to_path) | ||
var destination_dir_path := destination_file_path.get_base_dir() | ||
DirAccess.make_dir_recursive_absolute(destination_dir_path) | ||
DirAccess.copy_absolute(file_path, destination_file_path) | ||
return found.return_code | ||
|
||
|
||
static func os_execute(exe: String, args: Array, do_read_stderr := true) -> ReturnCode: | ||
var output := [] | ||
var return_code := OS.execute(exe, args, output, do_read_stderr) | ||
for line in output: | ||
print_rich(line) | ||
|
||
var is_fail := ( | ||
not return_code in ReturnCode.values() | ||
or output.any(func(s: String) -> bool: return "FAIL" in s) | ||
) | ||
return ReturnCode.FAIL if is_fail else (return_code as ReturnCode) | ||
|
||
|
||
static func os_parse_user_args(help_description := [], supported_args := []) -> Result: | ||
var result := Result.new({args = {}}) | ||
|
||
const ARG_HELP := ["-h", "--help", "Show this help message."] | ||
supported_args = [ARG_HELP] + supported_args | ||
|
||
var is_arg_predicate := func(s: String) -> bool: return s.begins_with("-") | ||
var arg_to_help := func(a: Array) -> String: | ||
var args := a.filter(is_arg_predicate) | ||
var help_message := a.filter(func(s: String) -> bool: return not s.begins_with("-")) | ||
return " %s" % "\n\t".join([" ".join(args), "".join(help_message)]) | ||
var help_message := help_description + ["Arguments"] + supported_args.map(arg_to_help) | ||
result.result.help_message = "\n".join(help_message) | ||
|
||
result.result.user_args = OS.get_cmdline_user_args() | ||
if not ARG_HELP.filter(func(a: String) -> bool: return a in result.result.user_args).is_empty(): | ||
print_rich(result.result.help_message) | ||
return result | ||
|
||
var flat_supported_args := flatten(supported_args).filter(is_arg_predicate) | ||
var unknown_args: Array[String] = [] | ||
for arg: String in result.result.user_args: | ||
var parts := arg.split("=") | ||
var key := parts[0] | ||
if key in flat_supported_args: | ||
result.result.args[key] = parts[1] if parts.size() == 2 else null | ||
else: | ||
unknown_args.push_back(key) | ||
|
||
if not unknown_args.is_empty(): | ||
var message := [ | ||
"Unknown command-line arguments %s." % str(unknown_args), | ||
"Supported arguments %s." % str(flat_supported_args), | ||
] | ||
push_warning(" ".join(message)) | ||
result.return_code = ReturnCode.WARN | ||
return result | ||
|
||
|
||
static func flatten_unique(array: Array) -> Array: | ||
var result := {} | ||
for key in flatten(array): | ||
result[key] = null | ||
return result.keys() | ||
|
||
|
||
static func flatten(array: Array) -> Array: | ||
return array.reduce(func(acc: Array, xs: Array) -> Array: return acc + xs, []) | ||
|
Binary file not shown.
Oops, something went wrong.