Files
TheTree_mock/scripts/economy/ledger_backed_resource_state.gd

147 lines
4.0 KiB
GDScript

class_name LedgerBackedResourceState
extends RefCounted
## Construction/operating adapter that keeps Gold authoritative in the ledger
## while preserving the generic resource-state contract used by services.
const PLAYER_CASH := &"PLAYER_CASH"
var _state: Variant
var _economy: EconomyService
var _counter_account: StringName
var _expense_category: StringName
var _refund_category: StringName
var _source_entity_id: StringName
var _minute_provider: Callable
func _init(
state: Variant,
economy: EconomyService,
counter_account: StringName,
expense_category: StringName,
refund_category: StringName,
source_entity_id: StringName,
minute_provider: Callable = Callable()
) -> void:
_state = state
_economy = economy
_counter_account = counter_account
_expense_category = expense_category
_refund_category = refund_category
_source_entity_id = source_entity_id
_minute_provider = minute_provider
func get_resource(resource_name: StringName) -> int:
if resource_name == &"gold":
return _economy.get_balance(PLAYER_CASH)
return int(_state.call(&"get_resource", resource_name))
func can_afford(cost: Dictionary) -> bool:
for raw_name: Variant in cost:
var resource_name := StringName(raw_name)
var amount := int(cost[raw_name])
if amount < 0 or get_resource(resource_name) < amount:
return false
return true
func spend(cost: Dictionary) -> bool:
if not can_afford(cost):
return false
var deltas: Dictionary = {}
for raw_name: Variant in cost:
deltas[StringName(raw_name)] = -int(cost[raw_name])
return apply_resource_deltas(deltas)
func apply_resource_deltas(deltas: Dictionary) -> bool:
if not _validate_deltas(deltas):
return false
var before := create_snapshot()
var non_gold: Dictionary = {}
var gold_delta := 0
for raw_name: Variant in deltas:
var resource_name := StringName(raw_name)
if resource_name == &"gold":
gold_delta += int(deltas[raw_name])
else:
non_gold[resource_name] = int(deltas[raw_name])
if not non_gold.is_empty() and not bool(_state.call(&"apply_resource_deltas", non_gold)):
return false
if gold_delta != 0:
var result: CommandResult
if gold_delta < 0:
result = _economy.transfer(
PLAYER_CASH,
_counter_account,
-gold_delta,
_current_minute(),
_expense_category,
_source_entity_id
)
else:
result = _economy.transfer(
_counter_account,
PLAYER_CASH,
gold_delta,
_current_minute(),
_refund_category,
_source_entity_id
)
if not result.is_success():
restore_snapshot(before)
return false
_sync_gold_mirror()
return true
func create_snapshot() -> Dictionary:
return {
&"state": _state.call(&"create_snapshot"),
&"economy": _economy.create_snapshot(),
}
func restore_snapshot(snapshot: Dictionary) -> bool:
if not snapshot.get(&"state", null) is Dictionary \
or not snapshot.get(&"economy", null) is Dictionary:
return false
var previous_state: Dictionary = _state.call(&"create_snapshot")
var previous_economy: Dictionary = _economy.create_snapshot()
if not _economy.restore_snapshot(snapshot[&"economy"]):
return false
if not bool(_state.call(&"restore_snapshot", snapshot[&"state"])):
_economy.restore_snapshot(previous_economy)
_state.call(&"restore_snapshot", previous_state)
return false
if int(_state.call(&"get_resource", &"gold")) != _economy.get_balance(PLAYER_CASH):
_economy.restore_snapshot(previous_economy)
_state.call(&"restore_snapshot", previous_state)
return false
return true
func _validate_deltas(deltas: Dictionary) -> bool:
var next_values: Dictionary = {}
for raw_name: Variant in deltas:
var resource_name := StringName(raw_name)
var next_value := int(next_values.get(resource_name, get_resource(resource_name))) + int(deltas[raw_name])
if next_value < 0:
return false
next_values[resource_name] = next_value
return true
func _sync_gold_mirror() -> void:
_state.call(&"set_resource", &"gold", _economy.get_balance(PLAYER_CASH))
func _current_minute() -> int:
if _minute_provider.is_valid():
return maxi(int(_minute_provider.call()), 0)
return _economy.current_minute