Giter Club home page Giter Club logo

starrailwarpobserve's People

Contributors

tremblingmoenew avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

Forkers

sunjunps

starrailwarpobserve's Issues

关于 GachaMetaData

这个 issue 假定该仓库的主人是 UIGF 群的 Haruhikage。如果不是的话,那这个 issue 可能跑题了。

image

生成 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 中了

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.