2026-08-02 19:01:16 -04:00
|
|
|
extends SceneTree
|
|
|
|
|
|
|
|
|
|
const FishCatchType = preload("res://fish/fish_catch.gd")
|
|
|
|
|
const FishExperienceType = preload("res://fish/fish_experience.gd")
|
|
|
|
|
const FishQualityType = preload("res://fish/fish_quality.gd")
|
|
|
|
|
const PlayerExperienceType = preload(
|
|
|
|
|
"res://progression/player_experience.gd"
|
|
|
|
|
)
|
|
|
|
|
const Catalog: FishPool = preload("res://fish/pools/fish_catalog.tres")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _initialize() -> void:
|
|
|
|
|
call_deferred("_run")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _run() -> void:
|
|
|
|
|
_validate_level_curve()
|
|
|
|
|
_validate_catch_awards()
|
|
|
|
|
_validate_player_experience_state()
|
|
|
|
|
_validate_save_migration()
|
|
|
|
|
print("Player experience validation: PASS")
|
|
|
|
|
quit()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _validate_level_curve() -> void:
|
|
|
|
|
assert(PlayerExperienceType.experience_required_for_next_level(1) == 100)
|
|
|
|
|
assert(PlayerExperienceType.experience_required_for_next_level(2) == 130)
|
|
|
|
|
assert(PlayerExperienceType.experience_required_for_next_level(3) == 170)
|
|
|
|
|
assert(PlayerExperienceType.total_experience_for_level(1) == 0)
|
|
|
|
|
assert(PlayerExperienceType.total_experience_for_level(2) == 100)
|
|
|
|
|
assert(PlayerExperienceType.total_experience_for_level(3) == 230)
|
|
|
|
|
assert(PlayerExperienceType.total_experience_for_level(4) == 400)
|
|
|
|
|
assert(PlayerExperienceType.level_for_total_experience(99) == 1)
|
|
|
|
|
assert(PlayerExperienceType.level_for_total_experience(100) == 2)
|
|
|
|
|
assert(PlayerExperienceType.level_for_total_experience(399) == 3)
|
|
|
|
|
assert(PlayerExperienceType.level_for_total_experience(400) == 4)
|
|
|
|
|
assert(
|
|
|
|
|
is_equal_approx(
|
|
|
|
|
PlayerExperienceType.progress_for_total_experience(50),
|
|
|
|
|
0.5,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _validate_catch_awards() -> void:
|
|
|
|
|
var minimum_boring: FishCatch = _make_catch(
|
|
|
|
|
FishQualityType.Tier.BORING,
|
|
|
|
|
false,
|
|
|
|
|
)
|
|
|
|
|
assert(
|
|
|
|
|
FishExperienceType.calculate_catch_experience(
|
|
|
|
|
minimum_boring,
|
|
|
|
|
false,
|
|
|
|
|
0,
|
|
|
|
|
) == 50
|
|
|
|
|
)
|
|
|
|
|
var maximum_shiny: FishCatch = _make_catch(
|
|
|
|
|
FishQualityType.Tier.SHINY,
|
|
|
|
|
true,
|
|
|
|
|
)
|
|
|
|
|
var boring_mask: int = FishQualityType.bit_for(
|
|
|
|
|
FishQualityType.Tier.BORING
|
|
|
|
|
)
|
|
|
|
|
assert(
|
|
|
|
|
FishExperienceType.calculate_catch_experience(
|
|
|
|
|
maximum_shiny,
|
|
|
|
|
true,
|
|
|
|
|
boring_mask,
|
|
|
|
|
) == 46
|
|
|
|
|
)
|
|
|
|
|
var almost_mastered: int = (
|
|
|
|
|
FishQualityType.ALL_TIERS_MASK
|
|
|
|
|
& ~FishQualityType.bit_for(FishQualityType.Tier.SHINY)
|
|
|
|
|
)
|
|
|
|
|
assert(
|
|
|
|
|
FishExperienceType.calculate_catch_experience(
|
|
|
|
|
maximum_shiny,
|
|
|
|
|
true,
|
|
|
|
|
almost_mastered,
|
|
|
|
|
) == 146
|
|
|
|
|
)
|
|
|
|
|
assert(
|
|
|
|
|
FishExperienceType.calculate_catch_experience(
|
|
|
|
|
maximum_shiny,
|
|
|
|
|
true,
|
|
|
|
|
FishQualityType.ALL_TIERS_MASK,
|
|
|
|
|
) == 31
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _validate_player_experience_state() -> void:
|
|
|
|
|
var experience := PlayerExperienceType.new()
|
|
|
|
|
root.add_child(experience)
|
|
|
|
|
var awards: Array[Array] = []
|
|
|
|
|
experience.experience_awarded.connect(
|
|
|
|
|
func(
|
|
|
|
|
amount: int,
|
|
|
|
|
previous_total: int,
|
|
|
|
|
new_total: int,
|
|
|
|
|
previous_level: int,
|
|
|
|
|
new_level: int,
|
|
|
|
|
) -> void:
|
|
|
|
|
awards.append([
|
|
|
|
|
amount,
|
|
|
|
|
previous_total,
|
|
|
|
|
new_total,
|
|
|
|
|
previous_level,
|
|
|
|
|
new_level,
|
|
|
|
|
])
|
|
|
|
|
)
|
|
|
|
|
assert(experience.get_level() == 1)
|
|
|
|
|
assert(experience.award_experience(125))
|
|
|
|
|
assert(experience.get_total_experience() == 125)
|
|
|
|
|
assert(experience.get_level() == 2)
|
|
|
|
|
assert(experience.get_experience_in_level() == 25)
|
|
|
|
|
assert(awards.size() == 1)
|
|
|
|
|
assert(awards[0] == [125, 0, 125, 1, 2])
|
|
|
|
|
assert(experience.restore_total_experience(400))
|
|
|
|
|
assert(experience.get_level() == 4)
|
|
|
|
|
assert(not experience.restore_total_experience(-1))
|
|
|
|
|
experience.queue_free()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _validate_save_migration() -> void:
|
|
|
|
|
var manager := PlayerSaveManager.new()
|
|
|
|
|
root.add_child(manager)
|
|
|
|
|
var version_five: Dictionary = {
|
|
|
|
|
"save_version": 5,
|
|
|
|
|
"wallet": {"balance": 0},
|
|
|
|
|
"collection": {
|
|
|
|
|
"discovered_fish_ids": [],
|
|
|
|
|
"discovered_quality_masks": {},
|
|
|
|
|
},
|
|
|
|
|
"inventory": {"next_catch_sequence": 1, "catches": []},
|
|
|
|
|
"bag": {"items": []},
|
|
|
|
|
"hotbar": {"selected_slot": 0, "slots": []},
|
|
|
|
|
"upgrades": {"reel_speed_level": 0, "barrier_power_level": 0},
|
|
|
|
|
"cooler": {"capacity_level": 0},
|
|
|
|
|
"art": {"unlock_mask": 0},
|
|
|
|
|
}
|
|
|
|
|
var migrated: Dictionary = manager.call(
|
|
|
|
|
"_migrate_save",
|
|
|
|
|
version_five,
|
|
|
|
|
5,
|
|
|
|
|
)
|
2026-08-03 00:43:43 -04:00
|
|
|
assert(int(migrated.get("save_version", -1)) == 7)
|
2026-08-02 19:01:16 -04:00
|
|
|
var experience_data: Dictionary = migrated.get("experience", {})
|
|
|
|
|
assert(int(experience_data.get("total_experience", -1)) == 0)
|
|
|
|
|
var world_data: Dictionary = migrated.get("world", {})
|
|
|
|
|
assert(is_equal_approx(float(world_data.get("time_hours", -1.0)), 8.0))
|
2026-08-03 00:43:43 -04:00
|
|
|
assert(
|
|
|
|
|
PlayerJobService.validate_save_data(migrated.get("jobs", {}))
|
|
|
|
|
)
|
2026-08-02 19:01:16 -04:00
|
|
|
manager.queue_free()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func _make_catch(quality: int, maximum_weight: bool) -> FishCatch:
|
|
|
|
|
var fish: FishData = Catalog.get_fish_by_id(&"bluegill")
|
|
|
|
|
assert(fish != null)
|
|
|
|
|
var fish_catch := FishCatchType.new()
|
|
|
|
|
fish_catch.fish = fish
|
|
|
|
|
fish_catch.fish_id = fish.id
|
|
|
|
|
fish_catch.catch_id = StringName("bluegill:xp-%d-%s" % [
|
|
|
|
|
quality,
|
|
|
|
|
"max" if maximum_weight else "min",
|
|
|
|
|
])
|
|
|
|
|
fish_catch.catch_sequence = 1
|
|
|
|
|
fish_catch.weight_lb = (
|
|
|
|
|
fish.get_maximum_weight()
|
|
|
|
|
if maximum_weight
|
|
|
|
|
else fish.get_minimum_weight()
|
|
|
|
|
)
|
|
|
|
|
fish_catch.display_scale = fish.get_display_scale_for_weight(
|
|
|
|
|
fish_catch.weight_lb
|
|
|
|
|
)
|
|
|
|
|
fish_catch.quality = quality
|
|
|
|
|
fish_catch.sale_value = FishQualityType.apply_sale_value(
|
|
|
|
|
fish.get_sale_value_for_weight(fish_catch.weight_lb),
|
|
|
|
|
quality,
|
|
|
|
|
)
|
|
|
|
|
return fish_catch
|