212 lines
7.1 KiB
GDScript
212 lines
7.1 KiB
GDScript
extends Node
|
|
|
|
class UnlockGoalRequirement extends RefCounted:
|
|
var currency: GameState.CurrencyType
|
|
var amount: BigNumber
|
|
|
|
func _init(req_currency: GameState.CurrencyType, req_amount: BigNumber) -> void:
|
|
currency = req_currency
|
|
amount = req_amount
|
|
|
|
class UnlockGoal extends RefCounted:
|
|
var id: StringName
|
|
var target_generator_id: StringName
|
|
var requirements: Array[UnlockGoalRequirement]
|
|
|
|
func _init(goal_id: StringName, target_id: StringName, goal_requirements: Array[UnlockGoalRequirement]) -> void:
|
|
id = goal_id
|
|
target_generator_id = target_id
|
|
requirements = goal_requirements
|
|
|
|
const GOALS_FILE_PATH: String = "res://generator-unlock-goals/generator_unlock_goals.json"
|
|
const SCHEMA_VERSION: int = 1
|
|
|
|
var _goals: Array[UnlockGoal] = []
|
|
var _known_generator_ids: Dictionary = {}
|
|
var _warned_unknown_target_ids: Dictionary = {}
|
|
|
|
func _ready() -> void:
|
|
_collect_known_generator_ids()
|
|
_load_goals()
|
|
GameState.currency_changed.connect(_on_currency_changed)
|
|
_evaluate_all_goals()
|
|
|
|
func _on_currency_changed(_changed_currency: GameState.CurrencyType, _new_amount: BigNumber) -> void:
|
|
_evaluate_all_goals()
|
|
|
|
func _evaluate_all_goals() -> void:
|
|
if _goals.is_empty():
|
|
return
|
|
|
|
for goal in _goals:
|
|
_evaluate_goal(goal)
|
|
|
|
func _evaluate_goal(goal: UnlockGoal) -> bool:
|
|
if _is_goal_completed(goal):
|
|
return false
|
|
if not _can_resolve_target(goal.target_generator_id):
|
|
return false
|
|
|
|
for requirement in goal.requirements:
|
|
var total_amount: BigNumber = _get_total_currency_amount(requirement.currency)
|
|
if total_amount.is_less_than(requirement.amount):
|
|
return false
|
|
|
|
GameState.set_generator_unlocked(goal.target_generator_id, true)
|
|
GameState.set_generator_available(goal.target_generator_id, true)
|
|
print("[UnlockSystem] Goal completed: %s -> %s" % [String(goal.id), String(goal.target_generator_id)])
|
|
return true
|
|
|
|
func _is_goal_completed(goal: UnlockGoal) -> bool:
|
|
return (
|
|
GameState.is_generator_unlocked(goal.target_generator_id)
|
|
and GameState.is_generator_available(goal.target_generator_id)
|
|
)
|
|
|
|
func _can_resolve_target(target_generator_id: StringName) -> bool:
|
|
var target_key: String = String(target_generator_id)
|
|
if target_key.is_empty():
|
|
return false
|
|
|
|
if _known_generator_ids.has(target_key) or GameState.generator_states.has(target_key):
|
|
return true
|
|
|
|
if not _warned_unknown_target_ids.has(target_key):
|
|
_warned_unknown_target_ids[target_key] = true
|
|
push_warning("Unlock goal target generator not found in scene/state: '%s'" % target_key)
|
|
return false
|
|
|
|
func _load_goals() -> void:
|
|
_goals.clear()
|
|
|
|
if not FileAccess.file_exists(GOALS_FILE_PATH):
|
|
push_warning("Unlock goals file not found: %s" % GOALS_FILE_PATH)
|
|
return
|
|
|
|
var file: FileAccess = FileAccess.open(GOALS_FILE_PATH, FileAccess.READ)
|
|
if file == null:
|
|
push_warning("Unable to open unlock goals file: %s" % GOALS_FILE_PATH)
|
|
return
|
|
|
|
var parsed: Variant = JSON.parse_string(file.get_as_text())
|
|
if not (parsed is Dictionary):
|
|
push_warning("Unlock goals root must be a Dictionary: %s" % GOALS_FILE_PATH)
|
|
return
|
|
|
|
var root: Dictionary = parsed
|
|
var version: int = int(root.get("version", SCHEMA_VERSION))
|
|
if version != SCHEMA_VERSION:
|
|
push_warning("Unlock goals schema version %d is different from expected %d" % [version, SCHEMA_VERSION])
|
|
|
|
var raw_goals: Variant = root.get("goals", [])
|
|
if not (raw_goals is Array):
|
|
push_warning("Unlock goals file has invalid 'goals' field (expected Array)")
|
|
return
|
|
|
|
var seen_goal_ids: Dictionary = {}
|
|
for raw_goal in raw_goals:
|
|
var goal: UnlockGoal = _parse_goal(raw_goal)
|
|
if goal == null:
|
|
continue
|
|
|
|
var goal_key: String = String(goal.id)
|
|
if seen_goal_ids.has(goal_key):
|
|
push_warning("Duplicate unlock goal id skipped: '%s'" % goal_key)
|
|
continue
|
|
|
|
seen_goal_ids[goal_key] = true
|
|
_goals.append(goal)
|
|
|
|
if _goals.is_empty():
|
|
push_warning("Unlock goals loaded, but no valid entries were found.")
|
|
|
|
func _parse_goal(raw_goal: Variant) -> UnlockGoal:
|
|
if not (raw_goal is Dictionary):
|
|
push_warning("Skipping unlock goal: entry is not a Dictionary")
|
|
return null
|
|
|
|
var goal_dict: Dictionary = raw_goal
|
|
var goal_id: String = String(goal_dict.get("id", "")).strip_edges()
|
|
var target_id: String = String(goal_dict.get("target_generator_id", "")).strip_edges()
|
|
|
|
if goal_id.is_empty():
|
|
push_warning("Skipping unlock goal: missing 'id'")
|
|
return null
|
|
if target_id.is_empty():
|
|
push_warning("Skipping unlock goal '%s': missing 'target_generator_id'" % goal_id)
|
|
return null
|
|
|
|
var raw_requirements: Variant = goal_dict.get("requirements", [])
|
|
if not (raw_requirements is Array):
|
|
push_warning("Skipping unlock goal '%s': 'requirements' must be an Array" % goal_id)
|
|
return null
|
|
|
|
var parsed_requirements: Array[UnlockGoalRequirement] = []
|
|
for raw_requirement in raw_requirements:
|
|
var requirement: UnlockGoalRequirement = _parse_requirement(raw_requirement, goal_id)
|
|
if requirement != null:
|
|
parsed_requirements.append(requirement)
|
|
|
|
if parsed_requirements.is_empty():
|
|
push_warning("Skipping unlock goal '%s': no valid requirements" % goal_id)
|
|
return null
|
|
|
|
return UnlockGoal.new(StringName(goal_id), StringName(target_id), parsed_requirements)
|
|
|
|
func _parse_requirement(raw_requirement: Variant, goal_id: String) -> UnlockGoalRequirement:
|
|
if not (raw_requirement is Dictionary):
|
|
push_warning("Skipping requirement in goal '%s': entry is not a Dictionary" % goal_id)
|
|
return null
|
|
|
|
var requirement_dict: Dictionary = raw_requirement
|
|
var currency_value: int = _parse_currency(requirement_dict.get("currency", ""))
|
|
if currency_value < 0:
|
|
push_warning("Skipping requirement in goal '%s': unknown currency '%s'" % [goal_id, String(requirement_dict.get("currency", ""))])
|
|
return null
|
|
|
|
var raw_amount: Variant = requirement_dict.get("amount", {})
|
|
if not (raw_amount is Dictionary):
|
|
push_warning("Skipping requirement in goal '%s': 'amount' must be a Dictionary" % goal_id)
|
|
return null
|
|
|
|
var amount: BigNumber = BigNumber.deserialize(raw_amount)
|
|
if amount.mantissa < 0.0:
|
|
push_warning("Skipping requirement in goal '%s': amount must be non-negative" % goal_id)
|
|
return null
|
|
|
|
return UnlockGoalRequirement.new(currency_value, amount)
|
|
|
|
func _parse_currency(raw_currency: Variant) -> int:
|
|
if raw_currency is int:
|
|
var currency_int: int = raw_currency
|
|
if currency_int == GameState.CurrencyType.gold or currency_int == GameState.CurrencyType.gems:
|
|
return currency_int
|
|
return -1
|
|
|
|
var currency_text: String = String(raw_currency).to_lower().strip_edges()
|
|
match currency_text:
|
|
"gold":
|
|
return GameState.CurrencyType.gold
|
|
"gems", "gem":
|
|
return GameState.CurrencyType.gems
|
|
_:
|
|
return -1
|
|
|
|
func _get_total_currency_amount(currency: GameState.CurrencyType) -> BigNumber:
|
|
return GameState.get_total_currency_acquired(currency)
|
|
|
|
func _collect_known_generator_ids() -> void:
|
|
_known_generator_ids.clear()
|
|
|
|
var generator_nodes: Array[Node] = find_children("*", "CurrencyGenerator", true, false)
|
|
for generator_node in generator_nodes:
|
|
var generator: CurrencyGenerator = generator_node as CurrencyGenerator
|
|
if generator == null:
|
|
continue
|
|
|
|
var generator_key: String = String(generator.get_generator_id())
|
|
if generator_key.is_empty():
|
|
continue
|
|
|
|
_known_generator_ids[generator_key] = true
|