147 lines
4.0 KiB
GDScript
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
|