Pular para o conteúdo principal

dungeon_party_lobby_artifact

Dungeon Party Lobby Artifact — misc entry.

Entry Information

PropertyValue
Entry IDdungeon_party_lobby_artifact
TypeMisc
ColorBlue
Iconfa6-solid:users

Description

Unified artifact storing all persistent dungeon data: - Per-dungeon stats (rooms, instances created, best time, completions) - Per-player stats (total runs, completions, best time) Follows the NumericalStorage/RPGCore architectural pattern: single artifact entry with encapsulated load/save and short caching. Profile-aware via [ProfilesAPI] for per-profile storage support. / @Singleton @Entry( "dungeon_artifact", "Unified Dungeon Data Artifact", Colors.ORANGE, "fa6-solid:database" ) @Tags("dungeon_artifact", "artifact") class DungeonArtifactEntry( override val id: String = "", override val name: String = "", @Help("Unique artifact identifier") override val artifactId: String = "dungeon_data", ) : ArtifactEntry { // ── Caching ────────────────────────────────────────────────────────── private class Cache { var cachedData: DungeonArtifactData? = null var lastCacheTime: Long = 0 val cacheDurationMs = 500 } companion object { private val CACHES = ConcurrentHashMap<String, Cache>() } private val dataCache: Cache get() = CACHES.computeIfAbsent(if (id.isEmpty()) artifactId else id) { Cache() } // ── Profile-aware storage key ──────────────────────────────────────── private fun getStorageKey(uuid: UUID): String { return DungeonProfilesReflection.getStorageKey(uuid) } // ── Dungeon Stats ──────────────────────────────────────────────────── /** Get stats for a specific dungeon. @param dungeonId The dungeon instance entry ID. @return The dungeon stats, or default if not found. / fun getDungeonStats(dungeonId: String): DungeonStats { val data = load() return data.dungeonStats[dungeonId] ?: DungeonStats() } /** Update stats for a specific dungeon. @param dungeonId The dungeon instance entry ID. @param block Mutation block applied to the current stats. / fun updateDungeonStats(dungeonId: String, block: (DungeonStats) -> DungeonStats) { update { data -> val current = data.dungeonStats[dungeonId] ?: DungeonStats() data.dungeonStats[dungeonId] = block(current) } } // ── Player Stats ───────────────────────────────────────────────────── /** Get stats for a specific player. @param uuid The player UUID. @return The player's dungeon stats, or default if not found. / fun getPlayerStats(uuid: UUID): PlayerDungeonStats { val data = load() return data.playerStats[getStorageKey(uuid)] ?: PlayerDungeonStats() } /** Update stats for a specific player. @param uuid The player UUID. @param block Mutation block applied to the current stats. / fun updatePlayerStats(uuid: UUID, block: (PlayerDungeonStats) -> PlayerDungeonStats) { update { data -> val key = getStorageKey(uuid) val current = data.playerStats[key] ?: PlayerDungeonStats() data.playerStats[key] = block(current) } } /** Get all player stats (for leaderboard etc). / fun getAllPlayerStats(): Map<String, PlayerDungeonStats> { val data = load() return data.playerStats } // ── Batch update ───────────────────────────────────────────────────── /** Optimized batch update: loads data once, applies the block, saves once. / fun update(block: (DungeonArtifactData) -> Unit) { val data = load() block(data) save(data) } // ── Load / Save ────────────────────────────────────────────────────── private fun load(): DungeonArtifactData { val cache = dataCache val now = System.currentTimeMillis() if (cache.cachedData != null && now - cache.lastCacheTime < cache.cacheDurationMs) { return cache.cachedData!! } val assetManager = get<AssetManager>(AssetManager::class.java) val exists = runBlocking { runCatching { assetManager.containsAsset(this@DungeonArtifactEntry) }.getOrDefault(false) } val content = if (exists) { runCatching { runBlocking { assetManager.fetchStringAsset(this@DungeonArtifactEntry) } }.getOrNull() } else { null } val data = if (content.isNullOrBlank()) { DungeonArtifactData() } else { runCatching { val obj = JsonParser.parseString(content).asJsonObject val dungeonStats = mutableMapOf<String, DungeonStats>() obj.getAsJsonObject("dungeon_stats")?.entrySet()?.forEach { (id, statsJson) -> val s = statsJson.asJsonObject dungeonStats[id] = DungeonStats( rooms = s.getAsJsonArray("rooms")?.map { it.asString } ?: emptyList(), instancesCreated = s.get("instances_created")?.asInt ?: 0, bestTimeMillis = s.get("best_time_millis")?.asLong, timesCompleted = s.get("times_completed")?.asInt ?: 0, ) } val playerStats = mutableMapOf<String, PlayerDungeonStats>() obj.getAsJsonObject("player_stats")?.entrySet()?.forEach { (key, statsJson) -> val s = statsJson.asJsonObject playerStats[key] = PlayerDungeonStats( totalRuns = s.get("total_runs")?.asInt ?: 0, totalCompletions = s.get("total_completions")?.asInt ?: 0, bestTimeMillis = s.get("best_time_millis")?.asLong, ) } DungeonArtifactData(dungeonStats, playerStats) }.getOrDefault(DungeonArtifactData()) } cache.cachedData = data cache.lastCacheTime = now return data } private fun save(data: DungeonArtifactData) { val assetManager = get<AssetManager>(AssetManager::class.java) val dungeonStatsJson = JsonObject().apply { data.dungeonStats.forEach { (id, stats) -> val s = JsonObject().apply { val roomsArray = com.google.gson.JsonArray() stats.rooms.forEach { roomsArray.add(it) } add("rooms", roomsArray) addProperty("instances_created", stats.instancesCreated) stats.bestTimeMillis?.let { addProperty("best_time_millis", it) } addProperty("times_completed", stats.timesCompleted) } add(id, s) } } val playerStatsJson = JsonObject().apply { data.playerStats.forEach { (key, stats) -> val s = JsonObject().apply { addProperty("total_runs", stats.totalRuns) addProperty("total_completions", stats.totalCompletions) stats.bestTimeMillis?.let { addProperty("best_time_millis", it) } } add(key, s) } } val root = JsonObject().apply { add("dungeon_stats", dungeonStatsJson) add("player_stats", playerStatsJson) } runBlocking { assetManager.storeStringAsset( this@DungeonArtifactEntry, root.toString() ) } val cache = dataCache cache.cachedData = data cache.lastCacheTime = System.currentTimeMillis() } } // ───────────────────────────────────────────────────────────────────────────── // Data Models // ───────────────────────────────────────────────────────────────────────────── /** Root data structure for the unified dungeon artifact. @property dungeonStats Per-dungeon statistics, keyed by dungeon entry ID. @property playerStats Per-player statistics, keyed by storage key (UUID or profile key). / data class DungeonArtifactData( val dungeonStats: MutableMap<String, DungeonStats> = mutableMapOf(), val playerStats: MutableMap<String, PlayerDungeonStats> = mutableMapOf(), ) /** Statistics for a single dungeon definition. @property rooms List of room IDs in this dungeon. @property instancesCreated Total number of instances ever created. @property bestTimeMillis Best completion time in milliseconds (null if never completed). @property timesCompleted Total number of completions. / data class DungeonStats( val rooms: List<String> = emptyList(), val instancesCreated: Int = 0, val bestTimeMillis: Long? = null, val timesCompleted: Int = 0, ) /** Per-player dungeon statistics. @property totalRuns Total number of dungeon runs started. @property totalCompletions Total number of successful completions. @property bestTimeMillis Personal best completion time in milliseconds. / data class PlayerDungeonStats( val totalRuns: Int = 0, val totalCompletions: Int = 0, val bestTimeMillis: Long? = null, ) // ───────────────────────────────────────────────────────────────────────────── // Runtime Artifacts (Kept Separate) // ───────────────────────────────────────────────────────────────────────────── /** Artifact storing runtime dungeon instance data including resolved rooms. This is transient and only persists for crash recovery / reload. / @Entry("active_dungeon_instance_artifact", "Active Dungeon Instance Artifact", Colors.BLUE, "fa6-solid:server") @Tags("active_dungeon_instance_artifact") @Serializable class ActiveDungeonInstanceArtifactEntry( override val id: String = "", override val name: String = "", override val artifactId: String = "", val instances: Map<String, ActiveInstanceData> = emptyMap(), ) : ArtifactEntry @Serializable data class ActiveInstanceData( val worldName: String = "", val players: List<String> = emptyList(), val remainingSeconds: Int = 0, val lobbyIndex: Int? = null, ) /** Runtime representation of a room inside an instance world. / @Serializable data class DungeonRoomArtifact( val roomId: String = "", val instanceWorldName: String = "", val region: Region3i = Region3i(), ) /** Artifact storing temporary lobby data for the party menu.

Fields

FieldTypeDescription
artifactIdString
lobbiesMap<String, PartyLobbyData>

Usage Example

- entry: dungeon_party_lobby_artifact
name: "Dungeon Party Lobby Artifact"
artifactId: ""
lobbies: ""