chore: release version 2.3.5 and refactor lobby

Bump export_presets.cfg version to 2.3.5. Update CHANGELOG_DRAFT.md.
Refactor lobby.gd into LobbyChat, LobbyMainMenu, LobbyRoomList, LobbyRoom.
Move Nakama config to environment variables in nakama_manager.gd.
Derive auth_manager.gd encryption key from OS.get_unique_id().sha256_text().
Remove Steam email auth fallback. Require auth ticket.
Make GachaManager.pull() async in gacha_panel.gd.
Remove dummy wallet seeding. Add store_type to IAP payload.
Validate IAP receipts server-side in economy.lua.
Register gacha module in main.lua.
Clean backend_service.gd stubs.
Fix featured_banners type safety in gacha_manager.gd. Guards non-array responses.
Move tiles_armagedon_a1.res to assets/models/meshes/. Fix import fallback_path.
This commit is contained in:
2026-05-22 12:08:11 +08:00
parent 8430d1054e
commit decdb74ade
356 changed files with 27438 additions and 1630 deletions
+5 -17
View File
@@ -22,8 +22,8 @@ var auth_mode: AuthMode = AuthMode.GUEST
const SESSION_FILE := "user://auth_session.dat"
const CREDENTIALS_FILE := "user://auth_credentials.dat"
# Encryption key for session storage (replace with your own!)
const ENCRYPTION_KEY := "tekton_secret_key_change_me_123"
# Encryption key for session storage (device-specific)
var ENCRYPTION_KEY: String = OS.get_unique_id().sha256_text()
func _ready() -> void:
# Try to restore session on startup
@@ -368,7 +368,7 @@ func login_with_steam() -> bool:
return true
func _authenticate_steam_with_fallback(steamworks: Node) -> NakamaSession:
# Try proper Steam ticket auth first
# Proper Steam ticket auth
var auth_ticket = steamworks.get_auth_session_ticket()
if not auth_ticket.is_empty():
print("[AuthManager] Got Steam auth ticket, authenticating with Nakama...")
@@ -376,22 +376,10 @@ func _authenticate_steam_with_fallback(steamworks: Node) -> NakamaSession:
if not session.is_exception():
return session
print("[AuthManager] Steam ticket auth failed: %s" % session.get_exception().message)
print("[AuthManager] Falling back to Steam ID custom auth (dev mode)...")
# Fallback: use Steam ID + username to create an email-style account (works without publisher key)
var steam_id = str(steamworks.get_steam_user_id())
var steam_name = steamworks.get_steam_user_name()
if steam_id == "0" or steam_id.is_empty():
return null
# Derive email and password from Steam credentials
var email = steam_name.to_lower().replace(" ", "_") + "@steam.local"
var password = steam_name # Default password = Steam username
var username = steam_name
print("[AuthManager] Using Steam email auth: %s (%s)" % [email, username])
var fallback_session: NakamaSession = await NakamaManager.client.authenticate_email_async(email, password, username, true)
return fallback_session
print("[AuthManager] Steam auth ticket is empty.")
return null
# =============================================================================
# Account Linking (Convert Guest to Full Account)
+34 -34
View File
@@ -27,44 +27,44 @@ func _load_data() -> void:
func pull(banner_id: String, count: int) -> Array:
if data.is_empty(): _load_data()
var banner: Dictionary = data.get("banners", {}).get(banner_id, {})
if banner.is_empty():
push_error("[GachaManager] Unknown banner: " + banner_id)
if not NakamaManager.session:
push_error("[GachaManager] Not authenticated")
return []
var currency: String = banner.get("currency", "star")
var cost_key: String = "pull_%d_cost" % count
var cost: int = banner.get(cost_key, banner.get("pull_1_cost", 999) * count)
var pity_at: int = banner.get("pity_at", 90)
# Deduct currency
var bal: int = UserProfileManager.wallet.get(currency, 0)
if bal < cost:
push_warning("[GachaManager] Not enough %s (have %d need %d)" % [currency, bal, cost])
var payload = JSON.stringify({
"banner_id": banner_id,
"count": count
})
var result = await NakamaManager.client.rpc_async(
NakamaManager.session,
"perform_gacha_pull",
payload
)
if result.is_exception():
var msg = result.get_exception().message
push_error("[GachaManager] Gacha pull failed: " + msg)
return []
UserProfileManager.wallet[currency] = bal - cost
UserProfileManager.save_wallet()
var results: Array = []
for _i in range(count):
var pity: int = pity_counters.get(banner_id, 0)
var rarity: String
if pity + 1 >= pity_at:
rarity = "real_prize"
pity_counters[banner_id] = 0
else:
rarity = _roll_rarity(banner.get("rates", {}))
pity_counters[banner_id] = (pity + 1) if rarity != "real_prize" else 0
var item_id: String = _pick_from_pool(rarity, banner_id)
var item_data: Dictionary = _resolve_item(item_id, rarity)
results.append({
"id": item_id,
"rarity": rarity,
"name": item_data.get("name", item_id)
})
var parsed = JSON.parse_string(result.payload)
if typeof(parsed) != TYPE_DICTIONARY or not parsed.has("results"):
return []
var results: Array = parsed.get("results", [])
if parsed.has("new_pity"):
pity_counters[banner_id] = int(parsed.new_pity)
if UserProfileManager.has_method("_reload_wallet"):
await UserProfileManager._reload_wallet()
for res in results:
var item_id = res.get("id", "")
var rarity = res.get("rarity", "")
_grant_item(item_id, rarity)
pull_completed.emit(results)
return results
+4 -2
View File
@@ -360,7 +360,8 @@ func fetch_shop_catalog() -> void:
if payload and payload.has("catalog"):
shop_catalog = payload.catalog
if payload.has("featured_banners"):
featured_banners = payload.get("featured_banners", [])
var _banners = payload.get("featured_banners", [])
featured_banners = _banners if typeof(_banners) == TYPE_ARRAY else []
emit_signal("profile_updated")
## Admin-only: grants a large amount of gold via a server-authoritative RPC.
@@ -384,7 +385,8 @@ func buy_currency(package_id: String) -> bool:
var payload = JSON.stringify({
"package_id": package_id,
"idempotency_key": str(randi()) + "_" + str(Time.get_ticks_usec()),
"receipt": "mock_receipt_for_now"
"receipt": "mock_receipt_for_now",
"store_type": "test"
})
var result = await NakamaManager.client.rpc_async(