tremblingmoenew / starrailwarpobserve Goto Github PK
View Code? Open in Web Editor NEW星穹铁道跃迁观测工具(Star Rail Warp Observe, STARWO), a Honkai: Star Rail warp(gacha) log export tool
Home Page: https://starwo.dodocotales.cc
License: MIT License
星穹铁道跃迁观测工具(Star Rail Warp Observe, STARWO), a Honkai: Star Rail warp(gacha) log export tool
Home Page: https://starwo.dodocotales.cc
License: MIT License
这个 issue 假定该仓库的主人是 UIGF 群的 Haruhikage。如果不是的话,那这个 issue 可能跑题了。
生成 Gacha Metadata 资料库呢,图的就是一个新鲜。Dimbreath 的资料库是最新鲜的,但也是最原始的资料。
我这边用 Swift 搞了个 GachaMetaDB JSON 生成脚本,估计你能用上。
生成的内容范例(节选):
{
"1001" : {
"id" : 1001,
"l10nMap" : {
"de-de" : "7. März",
"en-us" : "March 7th",
"es-es" : "Siete de Marzo",
"fr-fr" : "March 7th",
"id-id" : "March 7th",
"ja-jp" : "三月なのか",
"ko-kr" : "Mar. 7th",
"pt-pt" : "7 de Março",
"ru-ru" : "Март 7",
"th-th" : "March 7th",
"vi-vn" : "March 7th",
"zh-cn" : "三月七",
"zh-tw" : "三月七"
},
"nameTextMapHash" : -531793651,
"rank" : 4
},
"24004" : {
"id" : 24004,
"l10nMap" : {
"de-de" : "Unermüdliche Berechnung",
"en-us" : "Eternal Calculus",
"es-es" : "Cálculo interminable",
"fr-fr" : "Calcul éternel",
"id-id" : "Eternal Calculus",
"ja-jp" : "絶え間ない演算",
"ko-kr" : "멈추지 않는 연산",
"pt-pt" : "Cálculo Ininterrupto",
"ru-ru" : "Беспрерывные вычисления",
"th-th" : "Eternal Calculus",
"vi-vn" : "Ngoại Suy Vô Tận",
"zh-cn" : "不息的演算",
"zh-tw" : "不息的演算"
},
"nameTextMapHash" : 330597463,
"rank" : 5
}
}
原始脚本,直接在终端机里面跑 然后用 pipeline 输出到 JSON 即可。
// (c) 2023 and onwards Pizza Studio (GPL v3.0 License).
// ====================
// This code is released under the GPL v3.0 License (SPDX-License-Identifier: GPL-3.0)
import Foundation
// MARK: - ProtagonistDetector
public enum ProtagonistDetector {
case ofCaelus
case ofStelle
// MARK: Lifecycle
public init?(rawValue: Int) {
switch rawValue {
case 8001, 8003, 8005, 8007, 8009, 8011, 8013, 8015: self = .ofCaelus
case 8002, 8004, 8006, 8008, 8010, 8012, 8014, 8016: self = .ofStelle
default: return nil
}
}
public init?(against target: GachaItemMeta) {
guard let map = target.l10nMap,
map.description.contains(#"{NICKNAME}"#)
else { return nil }
self = (target.id % 2 == 0) ? .ofStelle : .ofCaelus
}
}
// MARK: - GachaDictLang
public enum GachaDictLang: String, CaseIterable, Sendable, Identifiable {
case langCHS
case langCHT
case langDE
case langEN
case langES
case langFR
case langID
case langJP
case langKR
case langPT
case langRU
case langTH
case langVI
// MARK: Public
public static let tableStelle = [
"de-de": "Stella",
"en-us": "Stelle",
"es-es": "Estela",
"fr-fr": "Stelle",
"id-id": "Stelle",
"ja-jp": "星",
"ko-kr": "스텔레",
"pt-pt": "Stelle",
"ru-ru": "Стелла",
"th-th": "Stelle",
"vi-vn": "Stelle",
"zh-cn": "星",
"zh-tw": "星",
]
public static let tableCaelus = [
"de-de": "Caelus",
"en-us": "Caelus",
"es-es": "Caelus",
"fr-fr": "Caelus",
"id-id": "Caelus",
"ja-jp": "穹",
"ko-kr": "카일루스",
"pt-pt": "Caelus",
"ru-ru": "Келус",
"th-th": "Caelus",
"vi-vn": "Caelus",
"zh-cn": "穹",
"zh-tw": "穹",
]
public var id: String { langID }
public var langID: String {
switch self {
case .langCHS: "zh-cn"
case .langCHT: "zh-tw"
case .langDE: "de-de"
case .langEN: "en-us"
case .langES: "es-es"
case .langFR: "fr-fr"
case .langID: "id-id"
case .langJP: "ja-jp"
case .langKR: "ko-kr"
case .langPT: "pt-pt"
case .langRU: "ru-ru"
case .langTH: "th-th"
case .langVI: "vi-vn"
}
}
public var filename: String {
rawValue.replacingOccurrences(of: "lang", with: "TextMap").appending(".json")
}
public var url: URL! {
URL(string: """
https://raw.githubusercontent.com/Dimbreath/StarRailData/master/TextMap/\(filename)
""")
}
}
// MARK: - QualityType
public enum QualityType: String, Codable {
case v5sp = "QUALITY_ORANGE_SP"
case v5 = "QUALITY_ORANGE"
case v4 = "QUALITY_PURPLE"
case v3 = "QUALITY_BLUE"
case v2 = "QUALITY_GREEN"
case v1 = "QUALITY_GRAY"
// MARK: Internal
var asrank: Int {
switch self {
case .v5, .v5sp: return 5
case .v4: return 4
case .v3: return 3
case .v2: return 2
case .v1: return 1
}
}
}
// MARK: - NameHashUnit
public struct NameHashUnit: Codable {
public enum CodingKeys: String, CodingKey {
case hash = "Hash"
}
public let hash: Int
}
// MARK: - GachaItemMeta
public class GachaItemMeta: Codable {
// MARK: Lifecycle
public init(id: Int, rank: Int, nameTextMapHash: Int) {
self.id = id
self.rank = rank
self.nameTextMapHash = nameTextMapHash
}
// MARK: Public
public let id: Int
public let rank: Int
public let nameTextMapHash: Int
public var l10nMap: [String: String]?
public var isCharacter: Bool {
id <= 9999
}
}
// MARK: - RawItemFetchModelProtocol
public protocol RawItemFetchModelProtocol {
var id: Int { get }
var nameTextMapHash: Int { get }
var rarity: Int { get }
}
// MARK: - AvatarRawItem
public class AvatarRawItem: Codable, RawItemFetchModelProtocol {
// MARK: Lifecycle
public required init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(Int.self, forKey: .id)
self.nameTextMapHash = (try container.decode(NameHashUnit.self, forKey: .nameTextMapHash)).hash
let rawRarityText = try container.decode(String.self, forKey: .rarity)
self.rarity = Int(rawRarityText.last?.description ?? "3") ?? 3
}
// MARK: Public
public enum CodingKeys: String, CodingKey {
case id = "AvatarID"
case nameTextMapHash = "AvatarName"
case rarity = "Rarity"
}
public let id: Int
public let nameTextMapHash: Int
public let rarity: Int
}
// MARK: - WeaponRawItem
public class WeaponRawItem: Codable, RawItemFetchModelProtocol {
// MARK: Lifecycle
public required init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(Int.self, forKey: .id)
self.nameTextMapHash = (try container.decode(NameHashUnit.self, forKey: .nameTextMapHash)).hash
let rawRarityText = try container.decode(String.self, forKey: .rarity)
self.rarity = Int(rawRarityText.last?.description ?? "3") ?? 3
}
// MARK: Public
public enum CodingKeys: String, CodingKey {
case id = "EquipmentID"
case nameTextMapHash = "EquipmentName"
case rarity = "Rarity"
}
public let id: Int
public let nameTextMapHash: Int
public let rarity: Int
}
extension RawItemFetchModelProtocol {
public func toGachaItemMeta() -> GachaItemMeta {
.init(id: id, rank: rarity, nameTextMapHash: nameTextMapHash)
}
}
let urlHeader = """
https://raw.githubusercontent.com/Dimbreath/StarRailData/master/ExcelOutput/
"""
let urlAvatarJSON = URL(string: urlHeader + "AvatarConfig.json")!
let urlWeaponJSON = URL(string: urlHeader + "EquipmentConfig.json")!
func fetchAvatars() async throws -> [GachaItemMeta] {
let (data, _) = try await URLSession.shared.data(from: urlAvatarJSON)
let response = try JSONDecoder().decode([String: AvatarRawItem].self, from: data)
return response.map { $0.value.toGachaItemMeta() }
}
func fetchWeapons() async throws -> [GachaItemMeta] {
let (data, _) = try await URLSession.shared.data(from: urlWeaponJSON)
let response = try JSONDecoder().decode([String: WeaponRawItem].self, from: data)
return response.map { $0.value.toGachaItemMeta() }
}
let items = try await withThrowingTaskGroup(of: [GachaItemMeta].self, returning: [GachaItemMeta].self) { taskGroup in
taskGroup.addTask { try await fetchAvatars() }
taskGroup.addTask { try await fetchWeapons() }
var images = [GachaItemMeta]()
for try await result in taskGroup {
images.append(contentsOf: result)
}
return images
}
let neededHashIDs = Set<String>(items.map(\.nameTextMapHash.description))
// MARK: - Get translations from AnimeGameData
let dictAll = try await withThrowingTaskGroup(
of: (subDict: [String: String], lang: GachaDictLang).self,
returning: [String: [String: String]].self
) { taskGroup in
GachaDictLang.allCases.forEach { locale in
taskGroup.addTask {
let (data, _) = try await URLSession.shared.data(from: locale.url)
var dict = try JSONDecoder().decode([String: String].self, from: data)
let keysToRemove = Set<String>(dict.keys).subtracting(neededHashIDs)
keysToRemove.forEach { dict.removeValue(forKey: $0) }
if locale == .langJP {
dict.keys.forEach { theKey in
guard dict[theKey]?.contains("{RUBY") ?? false else { return }
if let rawStrToHandle = dict[theKey], rawStrToHandle.contains("{") {
dict[theKey] = rawStrToHandle.replacingOccurrences(
of: #"\{RUBY.*?\}"#,
with: "",
options: .regularExpression
)
}
}
}
return (subDict: dict, lang: locale)
}
}
var results = [String: [String: String]]()
for try await result in taskGroup {
results[result.lang.langID] = result.subDict
}
return results
}
// MARK: - Apply translations
items.forEach { currentItem in
GachaDictLang.allCases.forEach { localeID in
let hashKey = currentItem.nameTextMapHash.description
guard let dict = dictAll[localeID.langID]?[hashKey] else { return }
if currentItem.l10nMap == nil { currentItem.l10nMap = [:] }
currentItem.l10nMap?[localeID.langID] = dict
}
}
// MARK: - Prepare Dictionary.
var dict: [String: GachaItemMeta] = [:]
items.forEach { item in
guard let desc = item.l10nMap?.description,
!desc.contains("测试")
else { return }
let key = item.id.description
protagonistName: switch ProtagonistDetector(against: item) {
case .ofCaelus: item.l10nMap = GachaDictLang.tableCaelus
case .ofStelle: item.l10nMap = GachaDictLang.tableStelle
default: break protagonistName
}
dict[key] = item
}
let encoder = JSONEncoder()
encoder.outputFormatting = [.sortedKeys, .prettyPrinted]
let encoded = String(data: try encoder.encode(dict), encoding: .utf8)
print(encoded ?? "Error happened.")
NSLog("All Tasks Done.")
这个脚本还可以弄成机器人,监视 Dimbreath 的仓库变化,一有变化就立刻生成新的资料且自动开 PR。
星穹铁道的抽卡抓包和原神的返回比较类似,感觉可以套用在原神抽卡交换格式上的经验,指定类似UIGF的UIGSR(或者其他名字!可以再议!)的交换格式,希望能趁相关工具还不多的时候团结一心联合起来!
目前星穹铁道还没出现paimon.moe这样的pompom.moe之类的东西(哦不,已经有了,pom-pom.moe),希望这次能制定一个让全世界各地工具开发者都用得上的格式(毕竟原神的交换格式基本上就分成了paimon.moe和UIGF两套)
为了避免在每个项目中都引用一遍对太多开发者造成打扰,我把目前所有使用了的项目全都记录在 https://github.com/LaoshuBaby/HSR-UIG/issues/1 中了
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.