Decouples core systems from centralized globals in favor of catalogue-based architecture and data-driven content. Key changes: - Prestige: node-based buff tech tree, graph panel, progress bar - Research: multi-worker system, per-generator research, xp generation - Ascension: meta-currency layer via multi-currency prestige resets - Buffs: refactored as generator-independent via catalogues - UI: currency panel, edge-scrolling camera + zoom, current-goal panel - Alchemy Tower: crafting building with recipe/cost system - Testing: 7 test suites (ascension, prestige, research, goals) - Content: migrated from hardcoded idles/ to gym format (tiny_sword)
319 lines
11 KiB
GDScript
319 lines
11 KiB
GDScript
extends Node
|
|
|
|
var passed: int = 0
|
|
var failed: int = 0
|
|
var _game_root: Node = null
|
|
|
|
|
|
func _ready() -> void:
|
|
await run()
|
|
|
|
|
|
func run() -> void:
|
|
print("\n=== TEST: Ascension Buff System ===\n")
|
|
TestUtils.set_test_name("ascension")
|
|
|
|
_test_node_resource()
|
|
_test_node_validation()
|
|
_test_node_parent_checking()
|
|
_test_catalogue_queries()
|
|
_test_purchase_flow()
|
|
_test_prestige_does_not_reset_prestige_buffs()
|
|
_test_multiplier_computation()
|
|
_test_save_load_roundtrip()
|
|
|
|
_print_summary()
|
|
|
|
|
|
#region Node Resource Tests
|
|
func _test_node_resource() -> void:
|
|
print("--- Node Resource ---")
|
|
|
|
var node: PrestigeBuffNode = PrestigeBuffNode.new()
|
|
node.id = &"test_node"
|
|
node.display_name = "Test Node"
|
|
node.effect_type = PrestigeBuffNode.EffectType.CURRENCY_PRODUCTION_MULTIPLIER
|
|
node.effect_value = 2.0
|
|
node.cost_mantissa = 5.0
|
|
node.cost_exponent = 2 # cost = 5e2 = 500
|
|
|
|
var cost: BigNumber = node.get_cost()
|
|
TestUtils.assert_equals(5.0, cost.mantissa, "cost mantissa matches")
|
|
TestUtils.assert_equals(2, cost.exponent, "cost exponent matches")
|
|
TestUtils.assert_false(node.has_parents(), "node with no parents returns false")
|
|
|
|
|
|
func _test_node_validation() -> void:
|
|
print("--- Node Validation ---")
|
|
|
|
var valid: PrestigeBuffNode = PrestigeBuffNode.new()
|
|
valid.id = &"valid_id"
|
|
valid.cost_mantissa = 1.0
|
|
TestUtils.assert_true(valid.is_valid(), "node with id and positive cost is valid")
|
|
|
|
var no_id: PrestigeBuffNode = PrestigeBuffNode.new()
|
|
no_id.cost_mantissa = 1.0
|
|
TestUtils.assert_false(no_id.is_valid(), "node with empty id is invalid")
|
|
|
|
var zero_cost: PrestigeBuffNode = PrestigeBuffNode.new()
|
|
zero_cost.id = &"has_id"
|
|
zero_cost.cost_mantissa = 0.0
|
|
TestUtils.assert_false(zero_cost.is_valid(), "node with zero cost is invalid")
|
|
|
|
|
|
func _test_node_parent_checking() -> void:
|
|
print("--- Parent Checking ---")
|
|
|
|
var unlocked: Dictionary = {}
|
|
unlocked[&"parent_a"] = true
|
|
unlocked[&"parent_b"] = false
|
|
|
|
var node: PrestigeBuffNode = PrestigeBuffNode.new()
|
|
node.id = &"child"
|
|
node.parent_ids = [&"parent_a"]
|
|
|
|
TestUtils.assert_true(node.all_parents_unlocked(unlocked), "all parents unlocked when all are true")
|
|
|
|
node.parent_ids = [&"parent_a", &"parent_b"]
|
|
TestUtils.assert_false(node.all_parents_unlocked(unlocked), "not all parents unlocked when one is false")
|
|
|
|
node.parent_ids = [&"nonexistent"]
|
|
TestUtils.assert_false(node.all_parents_unlocked(unlocked), "missing parent treated as not unlocked")
|
|
#endregion
|
|
|
|
|
|
#region Catalogue Tests
|
|
func _test_catalogue_queries() -> void:
|
|
print("--- Catalogue Queries ---")
|
|
|
|
var root: PrestigeBuffNode = _make_node(&"root", [], 0, 0.0)
|
|
var child_a: PrestigeBuffNode = _make_node(&"child_a", [&"root"], 1, 0.0)
|
|
var child_b: PrestigeBuffNode = _make_node(&"child_b", [&"root"], 1, 1.0)
|
|
var grandchild: PrestigeBuffNode = _make_node(&"grandchild", [&"child_a", &"child_b"], 2, 0.0)
|
|
|
|
var cat: PrestigeBuffCatalogue = PrestigeBuffCatalogue.new()
|
|
cat.nodes = [root, child_a, child_b, grandchild]
|
|
|
|
TestUtils.assert_equals(root, cat.get_node_by_id(&"root"), "get_node_by_id finds root")
|
|
TestUtils.assert_equals(null, cat.get_node_by_id(&"nonexistent"), "get_node_by_id returns null for unknown id")
|
|
|
|
var ids: Array[StringName] = cat.get_all_ids()
|
|
TestUtils.assert_equals(4, ids.size(), "get_all_ids returns 4 ids")
|
|
|
|
var roots: Array[PrestigeBuffNode] = cat.get_root_nodes()
|
|
TestUtils.assert_equals(1, roots.size(), "one root node")
|
|
TestUtils.assert_equals(&"root", roots[0].id, "root node id is 'root'")
|
|
|
|
var children: Array[PrestigeBuffNode] = cat.get_children_of(&"root")
|
|
TestUtils.assert_equals(2, children.size(), "root has 2 children")
|
|
|
|
var grandkids: Array[PrestigeBuffNode] = cat.get_children_of(&"child_a")
|
|
TestUtils.assert_equals(1, grandkids.size(), "child_a has 1 child")
|
|
#endregion
|
|
|
|
|
|
#region Purchase Flow Tests
|
|
func _test_purchase_flow() -> void:
|
|
print("--- Purchase Flow ---")
|
|
|
|
var scene: PackedScene = load("res://docs/gyms/tiny_sword/tiny_sword.tscn")
|
|
_game_root = scene.instantiate()
|
|
add_child(_game_root)
|
|
await _wait()
|
|
|
|
var gs: LevelGameState = _game_root.find_child("LevelGameState")
|
|
if gs == null:
|
|
print("[ERROR] LevelGameState not found")
|
|
return
|
|
|
|
# Give ascension currency for purchasing
|
|
gs.add_currency_by_id(&"ascension", BigNumber.from_float(100.0))
|
|
await _wait()
|
|
|
|
# Verify catalogue has our two nodes
|
|
var cat: PrestigeBuffCatalogue = gs.prestige_buff_catalogue
|
|
TestUtils.assert_not_null(cat, "prestige_buff_catalogue is set")
|
|
if cat == null:
|
|
return
|
|
|
|
TestUtils.assert_true(cat.nodes.size() >= 2, "catalogue has at least 2 nodes")
|
|
|
|
# Verify nodes initialized in state
|
|
TestUtils.assert_false(gs.is_prestige_buff_unlocked(&"gold_boost"), "gold_boost starts locked")
|
|
TestUtils.assert_false(gs.is_prestige_buff_unlocked(&"farm_boost"), "farm_boost starts locked")
|
|
|
|
# Check can_purchase (both are root nodes with no parents, should be purchaseable)
|
|
TestUtils.assert_true(gs.can_purchase_prestige_buff(&"gold_boost"), "gold_boost is purchasable")
|
|
TestUtils.assert_true(gs.can_purchase_prestige_buff(&"farm_boost"), "farm_boost is purchasable")
|
|
|
|
# Check cost
|
|
var cost: BigNumber = gs.get_prestige_buff_cost(&"gold_boost")
|
|
TestUtils.assert_greater_than(cost.mantissa, 0.0, "gold_boost cost > 0")
|
|
|
|
# Purchase gold_boost
|
|
var purchased: bool = gs.purchase_prestige_buff(&"gold_boost")
|
|
TestUtils.assert_true(purchased, "purchase_prestige_buff returns true")
|
|
TestUtils.assert_true(gs.is_prestige_buff_unlocked(&"gold_boost"), "gold_boost is now unlocked")
|
|
TestUtils.assert_false(gs.can_purchase_prestige_buff(&"gold_boost"), "cannot repurchase already unlocked node")
|
|
|
|
# farm_boost should still be available (both are roots, independent)
|
|
TestUtils.assert_true(gs.can_purchase_prestige_buff(&"farm_boost"), "farm_boost is still purchasable")
|
|
|
|
# Verify available buffs
|
|
var available: Array[PrestigeBuffNode] = gs.get_available_prestige_buffs()
|
|
var found_farm: bool = false
|
|
var found_gold: bool = false
|
|
for node in available:
|
|
if node.id == &"farm_boost":
|
|
found_farm = true
|
|
if node.id == &"gold_boost":
|
|
found_gold = true
|
|
TestUtils.assert_true(found_farm, "farm_boost appears in available buffs")
|
|
TestUtils.assert_false(found_gold, "gold_boost does NOT appear in available buffs (already unlocked)")
|
|
|
|
# Test cannot purchase with insufficient currency
|
|
gs._prestige_buff_unlocked.clear()
|
|
gs._initialize_prestige_buffs()
|
|
# Drain ascension currency
|
|
var bal: BigNumber = gs.get_currency_amount_by_id(&"ascension")
|
|
gs.spend_currency_by_id(&"ascension", bal)
|
|
TestUtils.assert_false(gs.can_purchase_prestige_buff(&"gold_boost"), "cannot purchase with no currency")
|
|
|
|
# Restore currency, re-unlock gold_boost for later tests
|
|
gs.add_currency_by_id(&"ascension", BigNumber.from_float(100.0))
|
|
gs.purchase_prestige_buff(&"gold_boost")
|
|
await _wait()
|
|
#endregion
|
|
|
|
|
|
func _test_prestige_does_not_reset_prestige_buffs() -> void:
|
|
print("--- Prestige Does Not Reset Prestige ---")
|
|
|
|
if _game_root == null:
|
|
return
|
|
|
|
var gs: LevelGameState = _game_root.find_child("LevelGameState")
|
|
if gs == null:
|
|
return
|
|
|
|
# gold_boost should be unlocked from previous test
|
|
TestUtils.assert_true(gs.is_prestige_buff_unlocked(&"gold_boost"), "gold_boost unlocked before prestige")
|
|
|
|
# Give enough currency to prestige
|
|
gs.add_currency_by_id(&"gold", BigNumber.new(1.0, 10)) # 1e10 gold
|
|
await _wait()
|
|
|
|
var pm: PrestigeManager = _game_root.find_child("PrestigeManager") as PrestigeManager
|
|
if pm == null:
|
|
print("[WARN] PrestigeManager not found; skipping prestige test")
|
|
return
|
|
|
|
# Perform prestige if possible
|
|
if pm.can_prestige():
|
|
pm.perform_prestige()
|
|
await _wait()
|
|
|
|
# After prestige, gold_boost should STILL be unlocked
|
|
TestUtils.assert_true(gs.is_prestige_buff_unlocked(&"gold_boost"), "gold_boost still unlocked after prestige")
|
|
else:
|
|
# Manual simulation: call reset_for_prestige directly and verify ascension survives
|
|
gs.reset_for_prestige(true, false, [&"ascension"])
|
|
await _wait()
|
|
TestUtils.assert_true(gs.is_prestige_buff_unlocked(&"gold_boost"), "gold_boost still unlocked after reset_for_prestige")
|
|
#endregion
|
|
|
|
|
|
func _test_multiplier_computation() -> void:
|
|
print("--- Multiplier Computation ---")
|
|
|
|
if _game_root == null:
|
|
return
|
|
|
|
var gs: LevelGameState = _game_root.find_child("LevelGameState")
|
|
if gs == null:
|
|
return
|
|
|
|
# gold_boost is already unlocked (from purchase test). Effect type 1 = GENERATOR_PRODUCTION_MULTIPLIER
|
|
var mult: float = gs.get_prestige_buff_multiplier(PrestigeBuffNode.EffectType.CURRENCY_PRODUCTION_MULTIPLIER, &"goldmine")
|
|
TestUtils.assert_greater_than(mult, 1.0, "multiplier for goldmine is > 1.0 with gold_boost unlocked")
|
|
|
|
# farm_boost is NOT unlocked, multiplier for farm should be 1.0
|
|
var farm_mult: float = gs.get_prestige_buff_multiplier(PrestigeBuffNode.EffectType.CURRENCY_PRODUCTION_MULTIPLIER, &"farm")
|
|
TestUtils.assert_equals(1.0, farm_mult, "multiplier for farm is 1.0 (farm_boost not unlocked)")
|
|
|
|
# Unlock farm_boost and check again
|
|
gs.add_currency_by_id(&"ascension", BigNumber.from_float(100.0))
|
|
gs.purchase_prestige_buff(&"farm_boost")
|
|
await _wait()
|
|
|
|
var farm_mult2: float = gs.get_prestige_buff_multiplier(PrestigeBuffNode.EffectType.CURRENCY_PRODUCTION_MULTIPLIER, &"farm")
|
|
TestUtils.assert_greater_than(farm_mult2, 1.0, "multiplier for farm is > 1.0 after unlocking farm_boost")
|
|
|
|
# Check that unrelated effect type returns 1.0
|
|
var unrelated: float = gs.get_prestige_buff_multiplier(PrestigeBuffNode.EffectType.RESEARCH_XP_MULTIPLIER, &"")
|
|
TestUtils.assert_equals(1.0, unrelated, "unrelated effect type returns 1.0")
|
|
#endregion
|
|
|
|
|
|
func _test_save_load_roundtrip() -> void:
|
|
print("--- Save/Load Roundtrip ---")
|
|
|
|
if _game_root == null:
|
|
return
|
|
|
|
var gs: LevelGameState = _game_root.find_child("LevelGameState")
|
|
if gs == null:
|
|
return
|
|
|
|
# Both gold_boost and farm_boost should be unlocked
|
|
TestUtils.assert_true(gs.is_prestige_buff_unlocked(&"gold_boost"), "gold_boost unlocked before save")
|
|
TestUtils.assert_true(gs.is_prestige_buff_unlocked(&"farm_boost"), "farm_boost unlocked before save")
|
|
|
|
# Save
|
|
gs.save_game()
|
|
await _wait()
|
|
|
|
# Clear state manually to simulate fresh load
|
|
gs._prestige_buff_unlocked.clear()
|
|
TestUtils.assert_false(gs.is_prestige_buff_unlocked(&"gold_boost"), "gold_boost false after clear")
|
|
TestUtils.assert_false(gs.is_prestige_buff_unlocked(&"farm_boost"), "farm_boost false after clear")
|
|
|
|
# Load
|
|
gs.load_game()
|
|
await _wait()
|
|
|
|
# Both should be restored
|
|
TestUtils.assert_true(gs.is_prestige_buff_unlocked(&"gold_boost"), "gold_boost restored after load")
|
|
TestUtils.assert_true(gs.is_prestige_buff_unlocked(&"farm_boost"), "farm_boost restored after load")
|
|
#endregion
|
|
|
|
|
|
#region Helpers
|
|
func _make_node(id: StringName, parent_ids: Array[StringName], tier: int, x_pos: float) -> PrestigeBuffNode:
|
|
var n: PrestigeBuffNode = PrestigeBuffNode.new()
|
|
n.id = id
|
|
n.display_name = String(id)
|
|
n.parent_ids = parent_ids
|
|
n.tier = tier
|
|
n.x_position = x_pos
|
|
n.cost_mantissa = 1.0
|
|
return n
|
|
|
|
|
|
func _wait() -> void:
|
|
await get_tree().create_timer(0.3).timeout
|
|
|
|
|
|
func _print_summary() -> void:
|
|
TestUtils.print_result()
|
|
passed = TestUtils.get_passed()
|
|
failed = TestUtils.get_failed()
|
|
|
|
|
|
func get_passed() -> int:
|
|
return passed
|
|
|
|
|
|
func get_failed() -> int:
|
|
return failed
|