Fresh start: replace with naxIO/netfox-cs-sample foundation

Complete replacement of the tactical-shooter project with the
netfox-cs-sample (MIT) — a CS 1.6 inspired multiplayer FPS built
with Godot 4 and netfox.

## What's new
- Full CS-style gameplay: teams (T/CT), rounds, economy, buy menu
- 6 weapons: Knife, Glock, USP, AK-47, M4A1, AWP
- Bomb plant/defuse with 2 bombsites
- Flashbang & smoke grenades
- Proper netfox rollback netcode at 64 tick
- Network popup UI for host/join
- HUD, crosshair, round timer, scoreboard
- All netfox singletons registered as autoloads (works in exported builds)

## Architecture
- Listen-server (host from client, no dedicated server binary)
- Multiplayer-fps game lives at examples/multiplayer-fps/
- Netfox addons registered as autoloads for exported build compat
- Godot 4.7 with Forward+ renderer

## Removed
- Old headless-server architecture (client_main, server_main, player.gd, etc.)
- Custom netfox bootstrap with ENet fallback
- Old ChaffGames FPS template (2,420 lines, 844 KB)
- SimulationServer GDExtension stub
- Godot-jolt physics (netfox sample uses default Godot physics)
- Duplicate weapon_data.gd, anti_cheat.gd, round_manager.gd, etc.
- Server browser API Python venv (87 MB)
- test_range map and modular assets

## Preserved
- Git history
- Server config at config/default_server_config.cfg
- Windows export preset
- Build directory (gitignored)

Co-authored-by: naxIO <naxIO@users.noreply.github.com>
This commit is contained in:
2026-07-02 20:55:20 -04:00
parent ce39b237c3
commit b0c83af092
4416 changed files with 57418 additions and 902676 deletions
+61
View File
@@ -0,0 +1,61 @@
[gd_scene format=3 uid="uid://4cj8dmy01ewy"]
[node name="comparison-popup" type="Window"]
initial_position = 1
size = Vector2i(384, 256)
[node name="Panel" type="Panel" parent="."]
custom_minimum_size = Vector2(384, 256)
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="VBoxContainer" type="VBoxContainer" parent="Panel"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="GridContainer" type="GridContainer" parent="Panel/VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
columns = 2
[node name="Got Label" type="Label" parent="Panel/VBoxContainer/GridContainer"]
layout_mode = 2
text = "Got
"
[node name="Expected Label" type="Label" parent="Panel/VBoxContainer/GridContainer"]
layout_mode = 2
text = "Expected"
[node name="Got" type="TextEdit" parent="Panel/VBoxContainer/GridContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="Expected" type="TextEdit" parent="Panel/VBoxContainer/GridContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="Tools" type="HBoxContainer" parent="Panel/VBoxContainer"]
layout_mode = 2
alignment = 1
[node name="Reset Button" type="Button" parent="Panel/VBoxContainer/Tools"]
unique_name_in_owner = true
layout_mode = 2
text = "Reset"
[node name="OK Button" type="Button" parent="Panel/VBoxContainer/Tools"]
unique_name_in_owner = true
layout_mode = 2
text = "OK"
+40
View File
@@ -0,0 +1,40 @@
[gd_scene format=3 uid="uid://dhly5ta07dd6r"]
[node name="message-popup" type="Window"]
initial_position = 1
size = Vector2i(384, 256)
[node name="Panel" type="Panel" parent="."]
custom_minimum_size = Vector2(384, 256)
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="VBoxContainer" type="VBoxContainer" parent="Panel"]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="Contents" type="TextEdit" parent="Panel/VBoxContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_vertical = 3
[node name="Tools" type="HBoxContainer" parent="Panel/VBoxContainer"]
layout_mode = 2
alignment = 1
[node name="Reset Button" type="Button" parent="Panel/VBoxContainer/Tools"]
unique_name_in_owner = true
layout_mode = 2
text = "Reset"
[node name="OK Button" type="Button" parent="Panel/VBoxContainer/Tools"]
unique_name_in_owner = true
layout_mode = 2
text = "OK"
+292
View File
@@ -0,0 +1,292 @@
@tool
extends Panel
var visibility_popup: VestUI.VisibilityPopup
@onready var _tree := %Tree as Tree
@onready var _spinner := %Spinner as Control
@onready var _spinner_icon := %"Spinner Icon" as TextureRect
@onready var _spinner_label := %"Spinner Label" as Label
@onready var _animation_player := $PanelContainer/Spinner/AnimationPlayer as AnimationPlayer
var _results: VestResult.Suite = null
var _render_results: VestResult.Suite = null
var _search_string: String = ""
signal on_collapse_changed()
func get_results() -> VestResult.Suite:
return _results
func set_results(results: VestResult.Suite) -> void:
if _results != results:
_results = results
_render()
func get_search_string() -> String:
return _search_string
func set_search_string(search_string: String) -> void:
if _search_string != search_string:
_search_string = search_string
_render()
func clear() -> void:
set_results(null)
func set_spinner(text: String, icon: Texture2D = null, animated: bool = true) -> void:
clear()
_spinner_icon.texture = icon
_spinner_label.text = text
_spinner.show()
if animated:
_animation_player.play("spin")
else:
_animation_player.stop()
func collapse() -> void:
var root := _tree.get_root()
if not root: return
for item in root.get_children():
item.set_collapsed_recursive(true)
func expand() -> void:
var root := _tree.get_root()
if not root: return
for item in root.get_children():
item.set_collapsed_recursive(false)
func toggle_collapsed() -> void:
if is_any_collapsed():
expand()
else:
collapse()
func is_any_collapsed() -> bool:
var at := _tree.get_root()
var queue := [] as Array[TreeItem]
while at:
queue.append_array(at.get_children())
if at.collapsed:
return true
at = queue.pop_back()
return false
func _ready():
_tree.item_collapsed.connect(func(_item):
on_collapse_changed.emit()
)
func _clear():
_tree.clear()
for connection in _tree.item_activated.get_connections():
connection["signal"].disconnect(connection["callable"])
_spinner.hide()
func _render():
_clear()
if _results != null:
_render_results = VestResult.Suite._from_wire(_results._to_wire()) # HACK: Duplicate
_filter_visibility(_render_results)
_filter_search(_render_results, _search_string)
_render_result(_render_results, _tree)
func _filter_visibility(results: VestResult.Suite) -> void:
var cases_to_remove := []
var suites_to_remove := []
for case in results.cases:
if not _check_visibility(case):
cases_to_remove.append(case)
for subsuite in results.subsuites:
if not _check_visibility(subsuite):
suites_to_remove.append(subsuite)
else:
_filter_visibility(subsuite)
for case in cases_to_remove:
results.cases.erase(case)
for subsuite in suites_to_remove:
results.subsuites.erase(subsuite)
func _filter_search(results: VestResult.Suite, needle: String) -> void:
if not needle:
# Search string empty, do nothing
return
var scores := {}
var parents := {}
var at := results
var queue: Array[VestResult.Suite] = []
var leaves: Array[VestResult.Suite] = []
# Calculate scores
while at:
for subsuite in at.subsuites:
queue.append(subsuite)
parents[subsuite] = at
if at.subsuites.is_empty():
leaves.append(at)
scores[at] = VestUI.fuzzy_score(needle, at.suite.name)
for case in at.cases:
scores[case] = maxf(
VestUI.fuzzy_score(needle, case.case.description),
VestUI.fuzzy_score(needle, case.case.method_name + "()")
)
at = queue.pop_back() as VestResult.Suite
# Propagate best scores from leaves
for leaf in leaves:
at = leaf
# Calculate best score for leaf
var best_score := scores[at] as float
for case in at.cases:
best_score = maxf(best_score, scores[case])
# Propagate upwards in tree
while at:
scores[at] = maxf(scores[at], best_score)
best_score = maxf(best_score, scores[at])
at = parents.get(at, null)
# Remove results that don't match the search string
at = results
queue.clear()
while at:
at.cases.sort_custom(func(a, b): return scores[a] > scores[b])
at.subsuites.sort_custom(func(a, b): return scores[a] > scores[b])
queue.append_array(at.subsuites)
at = queue.pop_back()
func _check_visibility(what: Variant) -> bool:
if what is VestResult.Case:
var case := what as VestResult.Case
return visibility_popup.get_visibility_for(case.status)
elif what is VestResult.Suite:
var suite := what as VestResult.Suite
for status in suite.get_unique_statuses():
if visibility_popup.get_visibility_for(status):
return true
return false
else:
push_warning("Checking visibility for unknown item: %s" % [what])
return true
func _render_result(what: Object, tree: Tree, parent: TreeItem = null):
if what is VestResult.Suite:
var item := tree.create_item(parent)
item.set_text(0, what.suite.name)
item.set_text(1, what.get_aggregate_status_string().capitalize())
item.set_icon(0, VestUI.get_status_icon(what))
item.set_icon_max_width(0, VestUI.get_icon_size())
tree.item_activated.connect(func():
if tree.get_selected() == item:
_navigate(what.suite.definition_file, what.suite.definition_line)
)
for subsuite in what.subsuites:
_render_result(subsuite, tree, item)
for case in what.cases:
_render_result(case, tree, item)
elif what is VestResult.Case:
var item := tree.create_item(parent)
item.set_text(0, what.case.description)
item.set_text(1, what.get_status_string().capitalize())
item.collapsed = what.status == VestResult.TEST_PASS
item.set_icon(0, VestUI.get_status_icon(what))
item.set_icon_max_width(0, VestUI.get_icon_size())
_render_data(what, tree, item)
tree.item_activated.connect(func():
if tree.get_selected() == item:
_navigate(what.case.definition_file, what.case.definition_line)
)
else:
push_error("Rendering unknown object: %s" % [what])
func _render_data(case: VestResult.Case, tree: Tree, parent: TreeItem):
var data := case.data.duplicate()
for message in case.messages:
var item := tree.create_item(parent)
item.set_text(0, message)
tree.item_activated.connect(func():
if tree.get_selected() == item:
# TODO: popup_dialog()
add_child(VestMessagePopup.of(message).window)
)
if data == null or data.is_empty():
return
if data.has("messages"):
var header_item := tree.create_item(parent)
header_item.set_text(0, "Messages")
for message in data["messages"]:
tree.create_item(header_item).set_text(0, message)
data.erase("messages")
if data.has("benchmarks"):
var header_item := tree.create_item(parent)
header_item.set_text(0, "Benchmarks")
for benchmark in data["benchmarks"]:
var benchmark_item = tree.create_item(header_item)
benchmark_item.set_text(0, benchmark["name"])
if benchmark.has("duration"): benchmark_item.set_text(1, benchmark["duration"])
for measurement in benchmark.keys():
if measurement == "name": continue
var measurement_item := tree.create_item(benchmark_item)
measurement_item.set_text(0, str(measurement).capitalize())
measurement_item.set_text(1, str(benchmark[measurement]))
data.erase("benchmarks")
if data.has("expect") and data.has("got"):
var header_item := tree.create_item(parent)
header_item.set_text(0, "Got:")
header_item.set_text(1, "Expected:")
var got_string := JSON.stringify(data["got"], " ")
var expect_string := JSON.stringify(data["expect"], " ")
var comparison_item := tree.create_item(header_item)
comparison_item.set_text(0, got_string)
comparison_item.set_text(1, expect_string)
tree.item_activated.connect(func():
# TODO: popup_dialog()
if tree.get_selected() in [header_item, comparison_item]:
add_child(VestComparisonPopup.of(expect_string, got_string).window)
)
data.erase("got")
data.erase("expect")
for key in data:
var item := tree.create_item(parent)
item.set_text(0, var_to_str(key))
item.set_text(1, var_to_str(data[key]))
func _navigate(file: String, line: int):
Vest._get_editor_interface().edit_script(load(file), line)
+1
View File
@@ -0,0 +1 @@
uid://brlr0bv7le1uq
+76
View File
@@ -0,0 +1,76 @@
[gd_scene load_steps=5 format=3 uid="uid://lqudrgj64q3x"]
[ext_resource type="Script" path="res://addons/vest/ui/results-panel.gd" id="1_ujqyc"]
[ext_resource type="Texture2D" uid="uid://dwhlyf5eyiect" path="res://addons/vest/icons/debug.svg" id="2_1y2jb"]
[sub_resource type="Animation" id="Animation_wkibd"]
resource_name = "spin"
loop_mode = 1
step = 0.0416667
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Spinner Icon:modulate")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 0.5, 1),
"transitions": PackedFloat32Array(-2, -2, -2),
"update": 0,
"values": [Color(1, 1, 1, 1), Color(1, 1, 1, 0.501961), Color(1, 1, 1, 1)]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_ne2nn"]
_data = {
"spin": SubResource("Animation_wkibd")
}
[node name="Results Panel" type="Panel"]
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
size_flags_vertical = 3
script = ExtResource("1_ujqyc")
[node name="PanelContainer" type="PanelContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
[node name="Tree" type="Tree" parent="PanelContainer"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
columns = 2
hide_root = true
[node name="Spinner" type="HBoxContainer" parent="PanelContainer"]
unique_name_in_owner = true
visible = false
layout_mode = 2
size_flags_horizontal = 4
size_flags_vertical = 4
[node name="Spinner Icon" type="TextureRect" parent="PanelContainer/Spinner"]
unique_name_in_owner = true
layout_mode = 2
texture = ExtResource("2_1y2jb")
expand_mode = 3
stretch_mode = 5
[node name="Spinner Label" type="Label" parent="PanelContainer/Spinner"]
unique_name_in_owner = true
layout_mode = 2
text = "Waiting for results..."
[node name="AnimationPlayer" type="AnimationPlayer" parent="PanelContainer/Spinner"]
autoplay = "spin"
libraries = {
"": SubResource("AnimationLibrary_ne2nn")
}
+57
View File
@@ -0,0 +1,57 @@
extends RefCounted
class_name VestComparisonPopup
var window: Window
var expected: TextEdit
var got: TextEdit
var ok_button: Button
var reset_button: Button
var _expected: String = ""
var _got: String = ""
# For some reason, when instantiating in Godot 4.0.4, the script doesn't attach, even though it's
# added in the editor.
#
# This means that _ready doesn't run, and set_contents isn't recognized.
#
# The workaround is this weird pattern where the class is instantiated with a reference to the
# scriptless scene ( Window ), and takes over.
# NOTE: This class may be rewritten, as we went from supporting 4.0.4 to 4.1.4
static func of(p_expected: String, p_got: String):
var popup_scene := (load("res://addons/vest/ui/comparison-popup.tscn") as PackedScene).instantiate(PackedScene.GEN_EDIT_STATE_MAIN)
var popup := VestComparisonPopup.new(popup_scene)
popup.set_contents(p_expected, p_got)
return popup
func set_contents(p_expected: String, p_got: String):
_expected = p_expected
_got = p_got
reset()
func reset():
expected.text = _expected
got.text = _got
func close():
window.queue_free()
free()
func _init(p_window: Window):
p_window.title = "vest - Comparison"
p_window.position = (DisplayServer.screen_get_size(-1) - p_window.size) / 2
window = p_window
ok_button = window.get_node("%OK Button")
reset_button = window.get_node("%Reset Button")
expected = window.get_node("%Expected")
got = window.get_node("%Got")
window.close_requested.connect(func(): close())
ok_button.pressed.connect(func(): window.queue_free())
reset_button.pressed.connect(func(): reset())
@@ -0,0 +1 @@
uid://dscuovb44m4t6
+51
View File
@@ -0,0 +1,51 @@
extends RefCounted
class_name VestMessagePopup
var window: Window
var contents: TextEdit
var ok_button: Button
var reset_button: Button
var _text: String = ""
# For some reason, when instantiating in Godot 4.0.4, the script doesn't attach, even though it's
# added in the editor.
#
# This means that _ready doesn't run, and set_contents isn't recognized.
#
# The workaround is this weird pattern where the class is instantiated with a reference to the
# scriptless scene ( Window ), and takes over.
# NOTE: This class may be rewritten, as we went from supporting 4.0.4 to 4.1.4
static func of(text: String):
var popup_scene := (load("res://addons/vest/ui/message-popup.tscn") as PackedScene).instantiate(PackedScene.GEN_EDIT_STATE_MAIN)
var popup := VestMessagePopup.new(popup_scene)
popup.set_contents(text)
return popup
func set_contents(text: String):
_text = text
contents.text = _text
func reset():
contents.text = _text
func close():
window.queue_free()
free()
func _init(p_window: Window):
p_window.title = "vest - Message"
p_window.position = (DisplayServer.screen_get_size(-1) - p_window.size) / 2
window = p_window
ok_button = window.get_node("%OK Button")
reset_button = window.get_node("%Reset Button")
contents = window.get_node("%Contents")
window.close_requested.connect(func(): close())
ok_button.pressed.connect(func(): window.queue_free())
reset_button.pressed.connect(func(): reset())
+1
View File
@@ -0,0 +1 @@
uid://dw33d1tq6738
+225
View File
@@ -0,0 +1,225 @@
@tool
extends Control
class_name VestUI
const VisibilityPopup := preload("res://addons/vest/ui/visibility-popup.gd")
const ResultsPanel := preload("res://addons/vest/ui/results-panel.gd")
@onready var run_all_button := %"Run All Button" as Button
@onready var debug_button := %"Debug Button" as Button
@onready var run_on_save_button := %"Run on Save Button" as Button
@onready var filter_results_button := %"Filter Results Button" as Button
@onready var visibility_popup := %"Visibility Popup" as VisibilityPopup
@onready var clear_button := %"Clear Button" as Button
@onready var expand_toggle_button := %"Expand Toggle Button" as Button
@onready var search_button := %"Search Button" as Button
@onready var search_input := %"Search Input" as LineEdit
@onready var refresh_mixins_button := %"Refresh Mixins Button" as Button
@onready var results_panel := %"Results Panel" as ResultsPanel
@onready var summary_label := %"Tests Summary Label" as Label
@onready var summary_icon := %"Test Summary Icon" as TextureRect
@onready var glob_line_edit := %"Glob LineEdit" as LineEdit
@onready var run_summary := %"Run Summary" as Control
@onready var progress_indicator := %"Progress Indicator" as Control
@onready var progress_animator := $"VBoxContainer/Bottom Line/Progress Indicator/Control/AnimationPlayer" as AnimationPlayer
var _run_on_save: bool = false
var _results: VestResult.Suite = null
static var _icon_size := 16
static var _instance: VestUI
static func get_icon_size() -> int:
return _icon_size
static func _get_ui() -> VestUI:
return _instance
func handle_resource_saved(resource: Resource):
if not resource is Script or not visible:
return
if _run_on_save:
run_all()
func run_all(is_debug: bool = false):
Vest._register_scene_tree(get_tree())
var runner := VestDaemonRunner.new()
runner.on_partial_result.connect(func(results):
results_panel.set_results(results)
)
var test_glob := glob_line_edit.text
Vest.__.LocalSettings.test_glob = test_glob
Vest.__.LocalSettings.flush()
results_panel.set_spinner("Waiting for results...", Vest.Icons.debug)
progress_indicator.show()
progress_animator.play("spin")
run_summary.hide()
var test_start := Vest.time()
var results: VestResult.Suite
if not is_debug:
results = await runner.run_glob(glob_line_edit.text)
else:
results = await runner.with_debug().run_glob(glob_line_edit.text)
var test_duration := Vest.time() - test_start
# Render individual results
ingest_results(results, test_duration)
func run_script(script: Script, is_debug: bool = false, only_mode: int = Vest.__.ONLY_AUTO) -> void:
if not get_tree():
push_warning("UI has no tree!")
Vest._register_scene_tree(get_tree())
var runner := VestDaemonRunner.new()
results_panel.set_spinner("Waiting for results...", Vest.Icons.debug)
progress_indicator.show()
progress_animator.play("spin")
run_summary.hide()
var test_start := Vest.time()
var results: VestResult.Suite
if not is_debug:
results = await runner.run_script(script, only_mode)
else:
results = await runner.with_debug().run_script(script, only_mode)
var test_duration := Vest.time() - test_start
# Render individual results
ingest_results(results, test_duration)
func ingest_results(results: VestResult.Suite, duration: float = -1.) -> void:
clear_results()
_results = results
if results:
results_panel.set_results(results)
_render_summary(results, duration)
else:
results_panel.set_spinner("Test run failed!", Vest.Icons.result_fail, false)
func clear_results():
results_panel.clear()
summary_label.text = ""
summary_icon.visible = false
func _ready():
_icon_size = int(16. * Vest._get_editor_scale())
results_panel.visibility_popup = visibility_popup
run_all_button.pressed.connect(run_all)
run_on_save_button.toggled.connect(func(toggled):
_run_on_save = toggled
)
clear_button.pressed.connect(clear_results)
refresh_mixins_button.pressed.connect(func(): VestMixins.refresh())
glob_line_edit.text = Vest.__.LocalSettings.test_glob
glob_line_edit.text_changed.connect(func(text: String):
Vest.__.LocalSettings.test_glob = text
)
debug_button.pressed.connect(func(): run_all(true))
filter_results_button.pressed.connect(func():
visibility_popup.position = filter_results_button.get_screen_position() + Vector2.RIGHT * filter_results_button.size.x
visibility_popup.show()
)
visibility_popup.on_change.connect(func():
clear_results()
results_panel.set_results(_results)
if visibility_popup.is_empty():
filter_results_button.icon = Vest.Icons.hidden
else:
filter_results_button.icon = Vest.Icons.visible
)
results_panel.on_collapse_changed.connect(func():
if (results_panel.is_any_collapsed()):
expand_toggle_button.icon = Vest.Icons.expand
else:
expand_toggle_button.icon = Vest.Icons.collapse
)
expand_toggle_button.pressed.connect(func():
results_panel.toggle_collapsed()
)
search_button.pressed.connect(func():
search_input.show()
search_input.grab_focus()
)
search_input.focus_exited.connect(func():
if not search_input.text and get_viewport().gui_get_focus_owner() != search_button:
search_input.hide()
, CONNECT_DEFERRED)
search_input.text_changed.connect(func(text: String):
results_panel.set_search_string(text)
)
_instance = self
func _render_summary(results: VestResult.Suite, test_duration: float):
progress_indicator.hide()
run_summary.show()
if test_duration > 0:
summary_label.text = "Ran %d tests in %s" % [results.size(), VestUI.format_duration(test_duration)]
else:
summary_label.text = "Ran %d tests" % [results.size()]
summary_icon.visible = true
summary_icon.texture = VestUI.get_status_icon(results)
summary_icon.custom_minimum_size = Vector2i.ONE * VestUI.get_icon_size() # TODO: Check
static func get_status_icon(what: Variant) -> Texture2D:
if what is VestResult.Suite:
return get_status_icon(what.get_aggregate_status())
elif what is VestResult.Case:
if what.data.has("benchmarks"):
if what.status == VestResult.TEST_FAIL:
return Vest.Icons.benchmark_fail
else:
return Vest.Icons.benchmark
else:
return get_status_icon(what.status)
elif what is int:
match(what):
VestResult.TEST_VOID: return Vest.Icons.result_void
VestResult.TEST_TODO: return Vest.Icons.result_todo
VestResult.TEST_SKIP: return Vest.Icons.result_skip
VestResult.TEST_FAIL: return Vest.Icons.result_fail
VestResult.TEST_PASS: return Vest.Icons.result_pass
return null
static func format_duration(duration: float) -> String:
if duration > 60.:
return "%.2fmin" % [duration / 60.]
elif duration > 1.:
return "%.2fs" % duration
elif duration > 0.001:
return "%.2fms" % [duration * 1000.]
else:
return "%.2fµs" % [duration * 1000_000.0]
static func fuzzy_score(needle: String, haystack: String) -> float:
var ineedle := needle.to_lower()
var ihaystack := haystack.to_lower()
return ineedle.similarity(ihaystack) + float(ineedle.is_subsequence_of(ihaystack))
static func fuzzy_match(needle: String, haystack: String) -> bool:
return fuzzy_score(needle, haystack) > 0.0
static func fuzzy_sorter(needle: String) -> Callable:
return func(a, b):
return fuzzy_score(needle, a) < fuzzy_score(needle, b)
+1
View File
@@ -0,0 +1 @@
uid://i36by1fk3oss
+232
View File
@@ -0,0 +1,232 @@
[gd_scene load_steps=16 format=3 uid="uid://bp8g7j7774mwi"]
[ext_resource type="Script" path="res://addons/vest/ui/vest-ui.gd" id="1_cct3x"]
[ext_resource type="Texture2D" uid="uid://r4y6ihamgino" path="res://addons/vest/icons/run.svg" id="2_q3lni"]
[ext_resource type="PackedScene" uid="uid://lqudrgj64q3x" path="res://addons/vest/ui/results-panel.tscn" id="2_r41u0"]
[ext_resource type="Texture2D" uid="uid://dwhlyf5eyiect" path="res://addons/vest/icons/debug.svg" id="3_mcbjt"]
[ext_resource type="Texture2D" uid="uid://ctdipwc8sklwo" path="res://addons/vest/icons/clear.svg" id="4_rjham"]
[ext_resource type="Texture2D" uid="uid://dt75n55vr4kq0" path="res://addons/vest/icons/refresh.svg" id="5_k7hxl"]
[ext_resource type="Texture2D" uid="uid://bmqjme87oq3xx" path="res://addons/vest/icons/pass.svg" id="6_mef67"]
[ext_resource type="Texture2D" uid="uid://c711af0y1s2ct" path="res://addons/vest/icons/run-save.svg" id="6_wy3v7"]
[ext_resource type="Texture2D" uid="uid://bpyua1ic4p47h" path="res://addons/vest/icons/visibility.svg" id="6_xkbs5"]
[ext_resource type="Texture2D" uid="uid://sx0233b14cll" path="res://addons/vest/icons/expand.svg" id="9_hg4sy"]
[ext_resource type="Texture2D" uid="uid://cl6im4a1uj6nq" path="res://addons/vest/icons/search.svg" id="10_2uogy"]
[ext_resource type="Texture2D" uid="uid://csfyamlc15vp0" path="res://addons/vest/icons/spinner.svg" id="10_7lh4k"]
[ext_resource type="PackedScene" uid="uid://dbgbcglqh42ya" path="res://addons/vest/ui/visibility-popup.tscn" id="13_72bup"]
[sub_resource type="Animation" id="Animation_cyqg3"]
resource_name = "spin"
loop_mode = 1
step = 0.0416667
tracks/0/type = "value"
tracks/0/imported = false
tracks/0/enabled = true
tracks/0/path = NodePath("Spinner:rotation")
tracks/0/interp = 1
tracks/0/loop_wrap = true
tracks/0/keys = {
"times": PackedFloat32Array(0, 1),
"transitions": PackedFloat32Array(0.5, 1),
"update": 0,
"values": [0.0, 6.28319]
}
[sub_resource type="AnimationLibrary" id="AnimationLibrary_ed1ow"]
_data = {
"spin": SubResource("Animation_cyqg3")
}
[node name="Vest UI" type="Control"]
custom_minimum_size = Vector2(0, 144)
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_cct3x")
[node name="VBoxContainer" type="VBoxContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
size_flags_horizontal = 3
[node name="Results Panel" parent="VBoxContainer" instance=ExtResource("2_r41u0")]
unique_name_in_owner = true
layout_mode = 2
[node name="Bottom Line" type="HBoxContainer" parent="VBoxContainer"]
layout_mode = 2
[node name="Buttons" type="HBoxContainer" parent="VBoxContainer/Bottom Line"]
layout_mode = 2
[node name="Run All Button" type="Button" parent="VBoxContainer/Bottom Line/Buttons"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Run tests"
text = " "
icon = ExtResource("2_q3lni")
icon_alignment = 1
expand_icon = true
[node name="Debug Button" type="Button" parent="VBoxContainer/Bottom Line/Buttons"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Debug tests"
text = " "
icon = ExtResource("3_mcbjt")
icon_alignment = 1
expand_icon = true
[node name="Clear Button" type="Button" parent="VBoxContainer/Bottom Line/Buttons"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Clear results"
text = " "
icon = ExtResource("4_rjham")
icon_alignment = 1
expand_icon = true
[node name="Run on Save Button" type="Button" parent="VBoxContainer/Bottom Line/Buttons"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Run on Save"
toggle_mode = true
text = " "
icon = ExtResource("6_wy3v7")
icon_alignment = 1
expand_icon = true
[node name="Filter Results Button" type="Button" parent="VBoxContainer/Bottom Line/Buttons"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Filter results"
text = " "
icon = ExtResource("6_xkbs5")
icon_alignment = 1
expand_icon = true
[node name="Visibility Popup" parent="VBoxContainer/Bottom Line/Buttons/Filter Results Button" instance=ExtResource("13_72bup")]
unique_name_in_owner = true
visible = false
[node name="Search Button" type="Button" parent="VBoxContainer/Bottom Line/Buttons"]
unique_name_in_owner = true
layout_mode = 2
text = " "
icon = ExtResource("10_2uogy")
icon_alignment = 1
expand_icon = true
[node name="Search Input" type="LineEdit" parent="VBoxContainer/Bottom Line/Buttons"]
unique_name_in_owner = true
visible = false
custom_minimum_size = Vector2(192, 0)
layout_mode = 2
placeholder_text = "Search..."
[node name="Expand Toggle Button" type="Button" parent="VBoxContainer/Bottom Line/Buttons"]
unique_name_in_owner = true
layout_mode = 2
text = " "
icon = ExtResource("9_hg4sy")
icon_alignment = 1
expand_icon = true
[node name="VSeparator3" type="VSeparator" parent="VBoxContainer/Bottom Line"]
layout_mode = 2
[node name="Run Summary" type="HBoxContainer" parent="VBoxContainer/Bottom Line"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
[node name="Test Summary Icon" type="TextureRect" parent="VBoxContainer/Bottom Line/Run Summary"]
unique_name_in_owner = true
visible = false
custom_minimum_size = Vector2(16, 16)
layout_mode = 2
size_flags_vertical = 4
texture = ExtResource("6_mef67")
expand_mode = 5
stretch_mode = 4
[node name="Tests Summary Label" type="Label" parent="VBoxContainer/Bottom Line/Run Summary"]
unique_name_in_owner = true
custom_minimum_size = Vector2(220, 0)
layout_mode = 2
[node name="Progress Indicator" type="HBoxContainer" parent="VBoxContainer/Bottom Line"]
unique_name_in_owner = true
visible = false
layout_mode = 2
size_flags_horizontal = 3
[node name="Control" type="Control" parent="VBoxContainer/Bottom Line/Progress Indicator"]
custom_minimum_size = Vector2(32, 0)
layout_mode = 2
[node name="Spinner" type="TextureRect" parent="VBoxContainer/Bottom Line/Progress Indicator/Control"]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -16.0
offset_top = -15.5
offset_right = 16.0
offset_bottom = 16.5
grow_horizontal = 2
grow_vertical = 2
rotation = 2.9185
pivot_offset = Vector2(16, 16)
texture = ExtResource("10_7lh4k")
expand_mode = 5
stretch_mode = 5
flip_h = true
[node name="AnimationPlayer" type="AnimationPlayer" parent="VBoxContainer/Bottom Line/Progress Indicator/Control"]
autoplay = "spin"
libraries = {
"": SubResource("AnimationLibrary_ed1ow")
}
[node name="Label" type="Label" parent="VBoxContainer/Bottom Line/Progress Indicator"]
layout_mode = 2
text = "Running tests..."
[node name="VSeparator2" type="VSeparator" parent="VBoxContainer/Bottom Line"]
layout_mode = 2
[node name="Glob" type="HBoxContainer" parent="VBoxContainer/Bottom Line"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_stretch_ratio = 3.0
[node name="Glob Label" type="Label" parent="VBoxContainer/Bottom Line/Glob"]
layout_mode = 2
text = "Test glob:"
[node name="Glob LineEdit" type="LineEdit" parent="VBoxContainer/Bottom Line/Glob"]
unique_name_in_owner = true
layout_mode = 2
size_flags_horizontal = 3
text = "res://tests/*.test.gd"
[node name="VSeparator4" type="VSeparator" parent="VBoxContainer/Bottom Line"]
layout_mode = 2
[node name="Refresh Mixins Button" type="Button" parent="VBoxContainer/Bottom Line"]
unique_name_in_owner = true
layout_mode = 2
tooltip_text = "Refresh Mixins"
text = " "
icon = ExtResource("5_k7hxl")
icon_alignment = 1
expand_icon = true
+70
View File
@@ -0,0 +1,70 @@
@tool
extends PopupPanel
@onready var _container := %"Statuses Container" as Control
@onready var _toggle_button := %"Toggle Button" as Button
var _visibilities: Dictionary = {}
signal on_change()
func get_visibility_for(status: int) -> bool:
return _visibilities.get(status, true)
func is_empty() -> bool:
# Return true if all visibilites are set to false
for status in _visibilities:
if _visibilities[status]:
return false
return true
func _init() -> void:
# Default visibility to true
for status in range(VestResult.TEST_MAX):
_visibilities[status] = true
func _ready() -> void:
_render()
_toggle_button.pressed.connect(_toggle_all)
func _toggle_all() -> void:
var visibility := is_empty()
for status in _visibilities.keys():
_visibilities[status] = visibility
_render()
on_change.emit()
func _render() -> void:
if _container.get_child_count() != _visibilities.size():
# Remove children
for child in _container.get_children():
child.queue_free()
for status in _visibilities.keys():
var checkbox := CheckBox.new()
checkbox.toggle_mode = true
checkbox.text = VestResult.get_status_string(status).capitalize()
checkbox.icon = VestUI.get_status_icon(status)
checkbox.expand_icon = true
checkbox.pressed.connect(func():
_visibilities[status] = not _visibilities[status]
_render()
on_change.emit()
)
_container.add_child(checkbox)
# Update checkbox statuses
for idx in range(_container.get_child_count()):
var status := _visibilities.keys()[idx] as int
var checkbox := _container.get_child(idx) as CheckBox
checkbox.set_pressed_no_signal(_visibilities[status])
# Update toggle button
if is_empty():
_toggle_button.text = "All"
_toggle_button.icon = Vest.Icons.visible
else:
_toggle_button.text = "None"
_toggle_button.icon = Vest.Icons.hidden
+1
View File
@@ -0,0 +1 @@
uid://hnnddmijt1mg
+31
View File
@@ -0,0 +1,31 @@
[gd_scene load_steps=4 format=3 uid="uid://dbgbcglqh42ya"]
[ext_resource type="Script" path="res://addons/vest/ui/visibility-popup.gd" id="1_21m7j"]
[ext_resource type="Texture2D" uid="uid://buuq6qptg3vll" path="res://addons/vest/icons/hidden.svg" id="2_urx7m"]
[sub_resource type="Theme" id="Theme_l01ra"]
Button/colors/icon_normal_color = Color(1, 1, 1, 0.501961)
Button/colors/icon_pressed_color = Color(1, 1, 1, 1)
[node name="Visibility Popup" type="PopupPanel"]
size = Vector2i(128, 214)
visible = true
theme = SubResource("Theme_l01ra")
script = ExtResource("1_21m7j")
[node name="Buttons Container" type="VBoxContainer" parent="."]
offset_left = 4.0
offset_top = 4.0
offset_right = 124.0
offset_bottom = 210.0
[node name="Statuses Container" type="VBoxContainer" parent="Buttons Container"]
unique_name_in_owner = true
layout_mode = 2
[node name="Toggle Button" type="Button" parent="Buttons Container"]
unique_name_in_owner = true
layout_mode = 2
text = "None"
icon = ExtResource("2_urx7m")
expand_icon = true