Add an RZ Coin slot machine
Adds a spend-coins-for-a-random-reward mini-game reachable from the shop. Backend mirrors the store's existing atomic/idempotent purchase pattern (SpinAsync in CreditStore.cs) with a server-authoritative, weighted reward roll and a rolling per-user spin-rate guard; odds are served from GET /api/credits/spin-config so the client never hardcodes numbers that could drift from the real payout table. The mockup's "Streak Protector" reward was swapped for a real Exam Simulation Token since streaks aren't currently a persisted, protectable concept. Also fixes two latent bugs surfaced while building this: - credits_service.gd's operation-id builder referenced a nonexistent AuthState.user_id (should be .username), throwing on every credit action. - _post() silently no-op'd with no signal when signed out, which combined with a GDScript closure gotcha (lambdas capture outer locals by value, so reassigning inside one never reached the caller) meant a spin attempted while unauthenticated could hang forever instead of surfacing an error.
This commit is contained in:
@@ -3,6 +3,8 @@ extends Node
|
||||
signal credits_changed
|
||||
signal credits_failed(message: String)
|
||||
signal store_operation_completed(message: String)
|
||||
signal spin_resolved(reward_key: String, reward_label: String, coins_won: int)
|
||||
signal spin_config_loaded
|
||||
|
||||
const EndpointConfig := preload("res://scripts/autoload/api_endpoint_config.gd")
|
||||
const DEFAULT_URL := "https://us3q.ranaze.com/api/credits"
|
||||
@@ -12,10 +14,13 @@ var exam_tokens: int = 0
|
||||
var qbank_reset_tokens: int = 0
|
||||
var bank_cycle: int = 0
|
||||
var products: Array = []
|
||||
var spin_cost := 50
|
||||
var spin_rewards: Array = []
|
||||
var loaded := false
|
||||
var loading := false
|
||||
var request: HTTPRequest
|
||||
var action_request: HTTPRequest
|
||||
var spin_config_request: HTTPRequest
|
||||
var request_token := ""
|
||||
var api_url := DEFAULT_URL
|
||||
var pending_store_action := ""
|
||||
@@ -33,6 +38,10 @@ func _ready() -> void:
|
||||
action_request.timeout = 20.0
|
||||
action_request.request_completed.connect(_on_action_completed)
|
||||
add_child(action_request)
|
||||
spin_config_request = HTTPRequest.new()
|
||||
spin_config_request.timeout = 20.0
|
||||
spin_config_request.request_completed.connect(_on_spin_config_completed)
|
||||
add_child(spin_config_request)
|
||||
AuthState.auth_changed.connect(_on_auth_changed)
|
||||
|
||||
|
||||
@@ -83,8 +92,33 @@ func reset_qbank() -> void:
|
||||
_post("/qbank-reset", {"confirmation": "RESET_QBANK", "operationId": _operation_id("reset")})
|
||||
|
||||
|
||||
func spin() -> void:
|
||||
pending_store_action = "spin"
|
||||
_post("/spin", {"operationId": _operation_id("spin")})
|
||||
|
||||
|
||||
func fetch_spin_config() -> void:
|
||||
if not AuthState.can_send_authenticated_request():
|
||||
return
|
||||
var headers := PackedStringArray(["Authorization: Bearer %s" % AuthState.access_token])
|
||||
spin_config_request.request(api_url + "/spin-config", headers, HTTPClient.METHOD_GET)
|
||||
|
||||
|
||||
func _on_spin_config_completed(result: int, response_code: int, _headers: PackedStringArray, body: PackedByteArray) -> void:
|
||||
if result != HTTPRequest.RESULT_SUCCESS or response_code < 200 or response_code >= 300:
|
||||
return
|
||||
var parsed: Variant = JSON.parse_string(body.get_string_from_utf8())
|
||||
if typeof(parsed) != TYPE_DICTIONARY:
|
||||
return
|
||||
spin_cost = int(parsed.get("spinCost", spin_cost))
|
||||
spin_rewards = parsed.get("rewards", spin_rewards)
|
||||
spin_config_loaded.emit()
|
||||
|
||||
|
||||
func _post(path: String, payload: Dictionary) -> void:
|
||||
if not AuthState.can_send_authenticated_request():
|
||||
pending_store_action = ""
|
||||
credits_failed.emit("You're signed out. Sign back in and try again.")
|
||||
return
|
||||
var headers := PackedStringArray([
|
||||
"Authorization: Bearer %s" % AuthState.access_token,
|
||||
@@ -101,6 +135,16 @@ func _on_action_completed(result: int, response_code: int, _headers: PackedStrin
|
||||
credits_failed.emit("The Rz Coin Store could not complete that action.")
|
||||
return
|
||||
if response_code >= 200 and response_code < 300 and typeof(parsed) == TYPE_DICTIONARY:
|
||||
if pending_store_action == "spin":
|
||||
var status: Variant = parsed.get("status", {})
|
||||
if typeof(status) == TYPE_DICTIONARY:
|
||||
apply_status(status)
|
||||
pending_store_action = ""
|
||||
spin_resolved.emit(
|
||||
str(parsed.get("rewardKey", "")),
|
||||
str(parsed.get("rewardLabel", "")),
|
||||
int(parsed.get("coinsWon", 0)))
|
||||
return
|
||||
apply_status(parsed)
|
||||
if pending_store_action == "reset":
|
||||
ProfileService.reset_qbank_eligibility()
|
||||
@@ -118,7 +162,7 @@ func _on_action_completed(result: int, response_code: int, _headers: PackedStrin
|
||||
|
||||
|
||||
func _operation_id(prefix: String) -> String:
|
||||
return "%s:%s:%d" % [prefix, str(AuthState.user_id), Time.get_ticks_usec()]
|
||||
return "%s:%s:%d" % [prefix, str(AuthState.username), Time.get_ticks_usec()]
|
||||
|
||||
|
||||
func _on_auth_changed() -> void:
|
||||
@@ -128,6 +172,7 @@ func _on_auth_changed() -> void:
|
||||
qbank_reset_tokens = 0
|
||||
bank_cycle = 0
|
||||
products = []
|
||||
spin_rewards = []
|
||||
loaded = false
|
||||
loading = false
|
||||
if request.get_http_client_status() != HTTPClient.STATUS_DISCONNECTED:
|
||||
|
||||
Reference in New Issue
Block a user