Files
CosmicClash/Game/scripts/ranked_profile_state.gd
T
2026-09-01 21:49:48 +01:00

70 lines
2.8 KiB
GDScript

class_name RankedProfileState
extends RefCounted
# Read-only server projection. The client deliberately stores no tier bands
# or rating formula: it displays the backend's committed view verbatim after
# validating the shape and numeric safety of the response.
var available := false
var rating := 0.0
var rd := 0.0
var volatility := 0.0
var ranked_games := 0
var tier := ""
var provisional := false
var season_id := ""
var season_ends_at_unix := 0
var error_message := ""
func apply(payload: Dictionary) -> bool:
var required := ["rating", "rd", "volatility", "ranked_games", "tier", "provisional"]
for key in required:
if not payload.has(key):
return _reject("Profile response is missing " + key)
if not (payload["rating"] is int or payload["rating"] is float) or not (payload["rd"] is int or payload["rd"] is float) or not (payload["volatility"] is int or payload["volatility"] is float) or not (payload["ranked_games"] is int or payload["ranked_games"] is float) or not payload["tier"] is String or not payload["provisional"] is bool:
return _reject("Profile response contains invalid types")
var next_rating := float(payload["rating"])
var next_rd := float(payload["rd"])
var next_volatility := float(payload["volatility"])
var next_games := int(payload["ranked_games"])
var next_tier := String(payload["tier"])
if not is_finite(next_rating) or not is_finite(next_rd) or not is_finite(next_volatility) or next_rating < 0.0 or next_rd < 0.0 or next_volatility < 0.0 or next_games < 0 or next_tier.is_empty():
return _reject("Profile response contains invalid values")
rating = next_rating
rd = next_rd
volatility = next_volatility
ranked_games = next_games
tier = next_tier
provisional = bool(payload["provisional"])
season_id = String(payload.get("season_id", ""))
season_ends_at_unix = 0
if payload.has("season_ends_at") and payload["season_ends_at"] is String and not String(payload["season_ends_at"]).is_empty():
season_ends_at_unix = maxi(0, int(Time.get_unix_time_from_datetime_string(String(payload["season_ends_at"]))))
available = true
error_message = ""
return true
func set_error(reason: String) -> void:
available = false
error_message = reason
func display_text(now_unix: int = -1) -> String:
if not available:
return error_message if not error_message.is_empty() else "Ranked profile unavailable"
var status := "Provisional" if provisional else tier
var text := "%s · %d ranked game%s" % [status, ranked_games, "" if ranked_games == 1 else "s"]
if season_ends_at_unix > 0:
var current_unix := int(Time.get_unix_time_from_system()) if now_unix < 0 else now_unix
var remaining_days := maxi(0, int(ceil(float(season_ends_at_unix - current_unix) / 86400.0)))
text += " · Season ends in %dd" % remaining_days
return text
func _reject(reason: String) -> bool:
available = false
error_message = reason
return false