134 lines
4.5 KiB
GDScript
134 lines
4.5 KiB
GDScript
extends Node
|
|
## GachaManager — Autoload singleton
|
|
## Handles pull logic, pity counter, and fragment reward dispatch.
|
|
|
|
const DATA_PATH = "res://assets/data/gacha_data.json"
|
|
|
|
var data: Dictionary = {}
|
|
|
|
# pity_counters[banner_id] = int
|
|
var pity_counters: Dictionary = {"star": 0, "gold": 0}
|
|
|
|
signal pull_completed(results: Array) # Array of {id, rarity, name}
|
|
|
|
func _ready() -> void:
|
|
_load_data()
|
|
|
|
func _load_data() -> void:
|
|
var f := FileAccess.open(DATA_PATH, FileAccess.READ)
|
|
if f:
|
|
var parsed = JSON.parse_string(f.get_as_text())
|
|
if parsed is Dictionary:
|
|
data = parsed
|
|
else:
|
|
push_error("[GachaManager] Could not open gacha_data.json")
|
|
|
|
# ─── Public API ──────────────────────────────────────────────────────────────
|
|
|
|
func pull(banner_id: String, count: int) -> Array:
|
|
if data.is_empty(): _load_data()
|
|
|
|
if not NakamaManager.session:
|
|
push_error("[GachaManager] Not authenticated")
|
|
return []
|
|
|
|
var payload = JSON.stringify({
|
|
"banner_id": banner_id,
|
|
"count": count
|
|
})
|
|
|
|
var result = await BackendService.perform_gacha_pull(banner_id, count)
|
|
|
|
if result.get("success", false) == false:
|
|
var msg = str(result.get("error", "Unknown error"))
|
|
push_error("[GachaManager] Gacha pull failed: " + msg)
|
|
return []
|
|
|
|
var parsed = result.get("data", {})
|
|
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
|
|
|
|
func get_pity(banner_id: String) -> int:
|
|
return pity_counters.get(banner_id, 0)
|
|
|
|
func get_balance(banner_id: String) -> int:
|
|
var banner: Dictionary = data.get("banners", {}).get(banner_id, {})
|
|
var currency: String = banner.get("currency", "star")
|
|
return UserProfileManager.wallet.get(currency, 0)
|
|
|
|
# ─── Internal ─────────────────────────────────────────────────────────────────
|
|
|
|
func _roll_rarity(rates: Dictionary) -> String:
|
|
var roll := randf()
|
|
var cumulative := 0.0
|
|
for rarity in ["real_prize", "rare", "uncommon", "common"]:
|
|
cumulative += rates.get(rarity, 0.0)
|
|
if roll <= cumulative:
|
|
return rarity
|
|
return "common"
|
|
|
|
func _pick_from_pool(rarity: String, _banner_id: String) -> String:
|
|
var pool: Array = data.get("pools", {}).get(rarity, [])
|
|
if pool.is_empty(): return ""
|
|
return pool[randi() % pool.size()]
|
|
|
|
func _resolve_item(item_id: String, rarity: String) -> Dictionary:
|
|
if rarity == "real_prize":
|
|
return data.get("real_prize_catalog", {}).get(item_id, {})
|
|
return data.get("fragments", {}).get(item_id, {})
|
|
|
|
func _grant_item(item_id: String, rarity: String) -> void:
|
|
if rarity == "real_prize":
|
|
# Add skin directly to inventory
|
|
if not UserProfileManager.inventory.has(item_id):
|
|
UserProfileManager.inventory.append(item_id)
|
|
else:
|
|
# Add fragment count
|
|
var frags: Dictionary = UserProfileManager.fragments
|
|
frags[item_id] = frags.get(item_id, 0) + 1
|
|
UserProfileManager.fragments = frags
|
|
|
|
# ─── Crafting ─────────────────────────────────────────────────────────────────
|
|
|
|
func get_all_recipes() -> Dictionary:
|
|
return data.get("craft_recipes", {})
|
|
|
|
func can_craft(recipe_id: String) -> bool:
|
|
var recipe: Dictionary = data.get("craft_recipes", {}).get(recipe_id, {})
|
|
if recipe.is_empty(): return false
|
|
var cost: Dictionary = recipe.get("cost", {})
|
|
for frag_id in cost:
|
|
var needed: int = cost[frag_id]
|
|
var have: int = UserProfileManager.fragments.get(frag_id, 0)
|
|
if have < needed: return false
|
|
return true
|
|
|
|
func craft(recipe_id: String) -> bool:
|
|
if not can_craft(recipe_id): return false
|
|
var recipe: Dictionary = data.get("craft_recipes", {}).get(recipe_id, {})
|
|
var cost: Dictionary = recipe.get("cost", {})
|
|
for frag_id in cost:
|
|
UserProfileManager.fragments[frag_id] -= cost[frag_id]
|
|
var result_id: String = recipe.get("result_id", "")
|
|
if not result_id.is_empty():
|
|
if not UserProfileManager.inventory.has(result_id):
|
|
UserProfileManager.inventory.append(result_id)
|
|
UserProfileManager.save_wallet()
|
|
return true
|