2026-10-18 20:14:27 +03:00
|
|
|
extends SceneTree
|
|
|
|
|
const E = preload("res://world/owniverse/math/exact.gd")
|
|
|
|
|
const Controller = preload("res://ui/achievements/achievement_controller.gd")
|
|
|
|
|
var failures := 0
|
|
|
|
|
class MockWorld extends RefCounted:
|
|
|
|
|
var state := {"time": {"year": 0}, "stats": {"total_amount": {}}}
|
|
|
|
|
var presentation := {"revision": 0}
|
|
|
|
|
class MockStage extends Control:
|
|
|
|
|
const PAGE_WIDTH = 1080
|
|
|
|
|
var owniverse = MockWorld.new()
|
|
|
|
|
var screens: Dictionary
|
|
|
|
|
var stage_page_scroller: ScrollContainer
|
|
|
|
|
func _initialize() -> void:
|
|
|
|
|
call_deferred("run")
|
|
|
|
|
func check(value: bool, message: String) -> void:
|
|
|
|
|
if not value:
|
|
|
|
|
failures += 1
|
|
|
|
|
push_error(message)
|
|
|
|
|
func node(parent: Node, child: Node, label: String) -> Node:
|
|
|
|
|
child.name = label
|
|
|
|
|
parent.add_child(child)
|
|
|
|
|
return child
|
|
|
|
|
func run() -> void:
|
|
|
|
|
check(load("res://stage/stage.gd") != null, "Stage integration compiles")
|
2026-10-18 20:32:20 +03:00
|
|
|
var scene: PackedScene = load("res://world/world.tscn")
|
|
|
|
|
var scene_state: SceneState = scene.get_state()
|
|
|
|
|
for language in ["English", "Russian"]:
|
|
|
|
|
var connected := false
|
|
|
|
|
for i in scene_state.get_connection_count():
|
|
|
|
|
if str(scene_state.get_connection_source(i)) == "Stage/MainMenu/Language/" + language:
|
|
|
|
|
connected = scene_state.get_connection_signal(i) == &"pressed" and str(scene_state.get_connection_target(i)) == "Stage/MainMenu" and scene_state.get_connection_method(i) == &"_on_change_locale_pressed" and scene_state.get_connection_binds(i) == ["en" if language == "English" else "ru"]
|
|
|
|
|
check(connected, language + " button connects to locale handler")
|
2026-10-18 20:14:27 +03:00
|
|
|
var stage := MockStage.new()
|
|
|
|
|
root.add_child(stage)
|
|
|
|
|
var world = node(stage, Control.new(), "World")
|
|
|
|
|
stage.screens = {"World": world}
|
|
|
|
|
stage.stage_page_scroller = node(world, ScrollContainer.new(), "PageScroller")
|
|
|
|
|
var pages = node(stage.stage_page_scroller, HBoxContainer.new(), "HBoxContainer")
|
|
|
|
|
for names in [["ForcePage", "ForceHeader"], ["ResourcePage", "ResourceHeader"], ["StarPage", "StarHeader"], ["StructurePage", "StructureHeader"], ["LifePage", "LifeHeader"]]:
|
|
|
|
|
node(node(pages, VBoxContainer.new(), names[0]), HBoxContainer.new(), names[1])
|
|
|
|
|
var screen = node(stage, Control.new(), "Achievements")
|
2026-10-18 20:32:20 +03:00
|
|
|
var title = node(node(screen, Control.new(), "Header"), TextureRect.new(), "Title")
|
2026-10-18 20:14:27 +03:00
|
|
|
node(node(screen, ScrollContainer.new(), "AchievementList"), VBoxContainer.new(), "AchievementItems")
|
|
|
|
|
var controller = Controller.new()
|
|
|
|
|
controller.progress_path = "/tmp/owniverse-achievements-test-%s.cfg" % OS.get_process_id()
|
|
|
|
|
stage.add_child(controller)
|
|
|
|
|
controller.initialize(stage)
|
|
|
|
|
check(controller.definitions.size() == 72, "All 72 enabled achievements loaded")
|
|
|
|
|
check(not controller.definitions.has("a95"), "Ignored achievement excluded")
|
|
|
|
|
controller.update(0.0)
|
|
|
|
|
check(controller.queue.is_empty() and controller.current.is_empty(), "Initially visible achievements do not notify")
|
|
|
|
|
stage.owniverse.state.stats.total_amount.H = E.of("5E4")
|
|
|
|
|
stage.owniverse.presentation.revision += 1
|
|
|
|
|
controller.update(0.0)
|
|
|
|
|
check(controller.progress.a13.unlocked, "Cumulative hydrogen threshold unlocks")
|
|
|
|
|
check(controller.progress.a14.visible and not controller.progress.a14.unlocked, "Opening threshold reveals next achievement")
|
|
|
|
|
controller.queue.clear()
|
|
|
|
|
controller._enqueue("a14", "g")
|
|
|
|
|
controller._enqueue("a14", "c")
|
|
|
|
|
check(controller.queue.size() == 1 and controller.queue[0].type == "c", "Pending reveal upgrades to completion")
|
|
|
|
|
controller.queue.clear()
|
|
|
|
|
controller.current = {}
|
|
|
|
|
stage.owniverse.state.time.year = E.of("1E12")
|
|
|
|
|
stage.owniverse.state.stats.total_amount.H = E.of("1E24")
|
|
|
|
|
stage.owniverse.state.stats.total_amount.Star = E.of("9E16")
|
|
|
|
|
stage.owniverse.presentation.revision += 1
|
|
|
|
|
controller.update(0.0)
|
|
|
|
|
check(controller.progress.a4.unlocked and controller.progress.a20.unlocked and controller.progress.a71.unlocked, "Exact large thresholds unlock")
|
|
|
|
|
check(controller.current.type == "c", "Immediate completion emits only completed notification")
|
|
|
|
|
var count: int = controller.queue.size()
|
|
|
|
|
controller.update(0.0)
|
|
|
|
|
check(controller.queue.size() == count, "Repeated update does not duplicate events")
|
|
|
|
|
world.hide()
|
|
|
|
|
var time: float = controller.elapsed
|
|
|
|
|
controller.update(5.0)
|
|
|
|
|
check(controller.elapsed == time, "Menu pauses notification")
|
|
|
|
|
world.show()
|
|
|
|
|
controller.update(3.4)
|
|
|
|
|
check(controller.current.is_empty() and not controller.panel.visible, "Notification expires")
|
|
|
|
|
for header in controller.headers: check(header.modulate.a == 1.0, "Indicators restored")
|
|
|
|
|
controller.update(0.0)
|
|
|
|
|
var remaining: int = controller.queue.size()
|
|
|
|
|
var click := InputEventMouseButton.new()
|
|
|
|
|
click.button_index = MOUSE_BUTTON_LEFT
|
|
|
|
|
click.pressed = true
|
|
|
|
|
controller._on_notification_input(click)
|
|
|
|
|
check(controller.current.is_empty() and not controller.panel.visible, "Click dismisses notification")
|
|
|
|
|
check(controller.queue.size() == remaining, "Click preserves pending notifications")
|
|
|
|
|
for header in controller.headers: check(header.modulate.a == 1.0, "Click restores indicators")
|
|
|
|
|
stage.owniverse = MockWorld.new()
|
|
|
|
|
controller.update(0.0)
|
|
|
|
|
check(controller.progress.a71.unlocked, "New universe preserves unlocked achievements")
|
|
|
|
|
controller.progress.clear()
|
|
|
|
|
controller._load_progress()
|
|
|
|
|
check(controller.progress.a71.unlocked, "Progress survives reload")
|
|
|
|
|
# Every enabled definition must be reachable through its actual compiled rule.
|
|
|
|
|
for id in controller.definitions:
|
|
|
|
|
var rule: Dictionary = controller.conditions[id].achieve
|
|
|
|
|
var source := {"time": {"year": 0}, "stats": {"total_amount": {}}}
|
|
|
|
|
if rule.type == "age": source.time.year = E.subtract(rule.amount, 1)
|
|
|
|
|
else: source.stats.total_amount[rule.id] = E.subtract(rule.amount, 1)
|
|
|
|
|
check(not controller._matches(rule, source), id + " stays locked below threshold")
|
|
|
|
|
if rule.type == "age": source.time.year = rule.amount
|
|
|
|
|
else: source.stats.total_amount[rule.id] = rule.amount
|
|
|
|
|
check(controller._matches(rule, source), id + " completes at exact threshold")
|
|
|
|
|
var global: Node = root.get_node("Global")
|
|
|
|
|
var previous_locale: String = global.Locale
|
|
|
|
|
global.Locale = "ru"
|
|
|
|
|
controller.update(0.0)
|
|
|
|
|
check(controller.status.text.begins_with("Открыто"), "Status localizes")
|
2026-10-18 20:32:20 +03:00
|
|
|
check(title.texture == Controller.TITLES.ru, "Russian title selected")
|
|
|
|
|
check(controller.items.a71.event_label.text.contains("90 квадриллионов"), "Russian achievement text selected")
|
|
|
|
|
global.Locale = "en"
|
|
|
|
|
controller.update(0.0)
|
|
|
|
|
check(controller.status.text.begins_with("Unlocked"), "English status selected")
|
|
|
|
|
check(title.texture == Controller.TITLES.en, "English title selected")
|
|
|
|
|
check(controller.items.a71.event_label.text.contains("90 quadrillion"), "English achievement text selected")
|
|
|
|
|
controller.current = {"id": "a71", "type": "c"}
|
|
|
|
|
controller._display_event(controller.current)
|
|
|
|
|
check(controller.notification_item.event_label.text.contains("90 quadrillion"), "English notification text selected")
|
|
|
|
|
global.Locale = "ru"
|
|
|
|
|
controller.update(0.0)
|
|
|
|
|
check(controller.notification_item.event_label.text.contains("90 квадриллионов"), "Active notification changes language")
|
2026-10-18 20:14:27 +03:00
|
|
|
global.Locale = previous_locale
|
|
|
|
|
var path: String = controller.progress_path
|
|
|
|
|
stage.free()
|
|
|
|
|
DirAccess.remove_absolute(path)
|
|
|
|
|
print("Achievement checks: ", "PASS" if failures == 0 else "FAIL")
|
|
|
|
|
quit(1 if failures else 0)
|