class_name CurrencyGenerator extends Node signal purchase_completed(amount: int, total_owned: int, total_purchased: int, total_cost: BigNumber) signal purchase_failed(requested_amount: int, required_cost: BigNumber, available_currency: BigNumber) signal production_tick(amount: BigNumber, cycle_count: int, total_owned: int) const HUGE_COST_EXPONENT: int = 1000000 @export var currency: GameState.CurrencyType = GameState.CurrencyType.gold @export var data: CurrencyGeneratorData @export var is_automatic: bool = true @export var press_buys_generator: bool = true @export var run_multiplier: float = 1.0 ## Manual click fallback when no data is assigned. @export var click_mantissa: float = 1.0 @export var click_exponent: int = 0 var owned: int = 0 var purchased_count: int = 0 var cycle_progress_seconds: float = 0.0 func _ready() -> void: cycle_progress_seconds = 0.0 func _process(delta: float) -> void: if not is_automatic: return if data == null or owned <= 0: return var cycle_seconds: float = maxf(data.initial_time, 0.0001) cycle_progress_seconds += maxf(delta, 0.0) if cycle_progress_seconds < cycle_seconds: return var completed_cycles: int = floori(cycle_progress_seconds / cycle_seconds) cycle_progress_seconds -= cycle_seconds * float(completed_cycles) _grant_cycle_income(completed_cycles) func _grant_cycle_income(cycle_count: int) -> void: if data == null or cycle_count <= 0: return var per_cycle: float = data.production_per_cycle(owned, run_multiplier, purchased_count) if per_cycle <= 0.0: return var produced: BigNumber = BigNumber.from_float(per_cycle).multiply(BigNumber.from_float(float(cycle_count))) _add_currency(produced) production_tick.emit(produced, cycle_count, owned) func get_next_cost() -> BigNumber: return get_cost_for_amount(1) func get_cost_for_amount(amount: int = 1) -> BigNumber: if data == null: return BigNumber.from_float(0.0) var safe_amount: int = maxi(amount, 1) var raw_cost: float = data.cost_for_amount(owned, safe_amount) return _float_to_big_number(raw_cost) func can_buy(amount: int = 1) -> bool: var cost: BigNumber = get_cost_for_amount(amount) var current: BigNumber = _get_currency_amount() return current.is_greater_than(cost) or current.is_equal_to(cost) func buy(amount: int = 1) -> bool: if data == null: return false var safe_amount: int = maxi(amount, 1) var total_cost: BigNumber = get_cost_for_amount(safe_amount) if not _spend_currency(total_cost): purchase_failed.emit(safe_amount, total_cost, _get_currency_amount()) return false owned += safe_amount purchased_count += safe_amount purchase_completed.emit(safe_amount, owned, purchased_count, total_cost) return true func buy_max() -> int: if data == null: return 0 var available_currency: float = _big_number_to_float(_get_currency_amount()) var estimated_max: int = data.max_affordable(owned, available_currency) if estimated_max <= 0: return 0 # Keep max-buy accurate by validating affordability with BigNumber comparisons. var low: int = 1 var high: int = estimated_max var best: int = 0 while low <= high: var mid: int = floori((float(low) + float(high)) * 0.5) if can_buy(mid): best = mid low = mid + 1 else: high = mid - 1 if best <= 0: return 0 return best if buy(best) else 0 func _on_pressed() -> void: if data != null and press_buys_generator: buy(1) return _add_currency(BigNumber.new(click_mantissa, click_exponent)) func grant_currency(amount: BigNumber) -> void: _add_currency(amount) func _add_currency(amount: BigNumber) -> void: match currency: GameState.CurrencyType.gold: GameState.add_gold(amount) GameState.CurrencyType.gems: GameState.add_gems(amount) _: push_error("Unknown currency type in CurrencyGenerator._add_currency") func _spend_currency(cost: BigNumber) -> bool: match currency: GameState.CurrencyType.gold: return GameState.spend_gold(cost) GameState.CurrencyType.gems: return GameState.spend_gems(cost) _: push_error("Unknown currency type in CurrencyGenerator._spend_currency") return false func _get_currency_amount() -> BigNumber: match currency: GameState.CurrencyType.gold: return GameState.gold GameState.CurrencyType.gems: return GameState.gems _: return BigNumber.from_float(0.0) func _float_to_big_number(value: float) -> BigNumber: if value <= 0.0: return BigNumber.from_float(0.0) if not is_finite(value): return BigNumber.new(1.0, HUGE_COST_EXPONENT) return BigNumber.from_float(value) func _big_number_to_float(value: BigNumber) -> float: if value.mantissa <= 0.0: return 0.0 if value.exponent > 308: return INF if value.exponent < -308: return 0.0 return value.mantissa * pow(10.0, float(value.exponent))