Newer
Older
Twilight / src / main / kotlin / net / taehui / twilight / system / AbilitySystem.kt
@Taehui Taehui on 28 Nov 9 KB v1.0-SNAPSHOT
package net.taehui.twilight.system

import com.fasterxml.jackson.databind.ObjectMapper
import net.taehui.twilight.JSON
import net.taehui.twilight.Logger
import net.taehui.twilight.TwilightComponent
import net.taehui.twilight.Utility
import org.apache.commons.io.FileUtils
import org.apache.commons.io.FilenameUtils
import org.apache.hc.client5.http.classic.methods.HttpGet
import org.apache.hc.client5.http.impl.classic.BasicHttpClientResponseHandler
import org.apache.hc.client5.http.impl.classic.HttpClients
import org.jsoup.Jsoup
import java.io.IOException
import java.nio.file.Files

object AbilitySystem : Logger {
    class DefaultAbility {
        var abilityFiles = emptyArray<AbilityFile>()
        var abilityWww = emptyArray<String>()
        var abilityMap = emptyMap<String, Double>()

        class AbilityFile {
            var fileName = ""
            var abilityClassVariety = AbilityClassSystem.AbilityClassVariety.ABILITY_CLASS_5K
        }
    }

    class AbilityMap<T> {
        val hashMap128 = mutableMapOf<String, T>()
        val hashMap256 = mutableMapOf<String, T>()

        fun wipe() {
            hashMap128.clear()
            hashMap256.clear()
        }
    }

    private val noteIDAbilityMap = arrayOf<AbilityMap<Double>>(AbilityMap(), AbilityMap(), AbilityMap())
    private val noteIDAbilityIDsMap = AbilityMap<MutableCollection<String>>()
    private val abilityNameAbilityIDsMap = mutableMapOf<String, MutableCollection<String>>()
    var isLoading = false

    fun loadAbility() {
        isLoading = true
        logFuture {
            try {
                val jm = ObjectMapper()
                val defaultAbility = jm.readValue(
                    TwilightComponent.ABILITY_ENTRY_PATH.resolve("Default.json").toFile(),
                    DefaultAbility::class.java
                )
                if (Configure.mode.getAbility) {
                    defaultAbility.abilityWww.forEach { target ->
                        try {
                            val o = Jsoup.connect(target).get()
                            var dataValue = o.selectXpath("/html/head/meta[@name='bmstable']")
                            if (dataValue.isEmpty()) {
                                dataValue = o.selectXpath("/html/body/meta[@name='bmstable']")
                            }
                            if (dataValue.isEmpty()) {
                                dataValue = o.selectXpath("/html/head/body/meta[@name='bmstable']")
                            }
                            if (!dataValue.isEmpty()) {
                                val www = dataValue[0].attr("content")
                                HttpClients.createDefault().use {
                                    fun doModifyDataValue(target: String, dataValue: String): String {
                                        return if (!Utility.isValidWww(dataValue)) {
                                            if (target.substring(target.lastIndexOf('/'))
                                                    .contains(".") || target.endsWith("/")
                                            ) {
                                                "$target/../$dataValue"
                                            } else {
                                                "$target/$dataValue"
                                            }
                                        } else {
                                            dataValue
                                        }
                                    }

                                    val text = it.execute(
                                        HttpGet(doModifyDataValue(target, www)),
                                        BasicHttpClientResponseHandler()
                                    )
                                    val abilityTable = text.byteInputStream().use { src ->
                                        ObjectMapper().readValue(src, JSON.BMSTable::class.java)
                                    }
                                    Files.writeString(
                                        TwilightComponent.ABILITY_ENTRY_PATH.resolve("#" + abilityTable.name + ".json"),
                                        text
                                    )
                                    Files.writeString(
                                        TwilightComponent.ABILITY_ENTRY_PATH.resolve(abilityTable.name + ".json"),
                                        it.execute(
                                            HttpGet(doModifyDataValue(target, abilityTable.data_url)),
                                            BasicHttpClientResponseHandler()
                                        )
                                    )
                                    logInfo("Saved Ability (${abilityTable.name})")
                                }
                            }
                        } catch (e: IOException) {
                            logFault(e)
                        }
                    }
                }
                try {
                    val abilityMap = defaultAbility.abilityMap
                    noteIDAbilityMap[AbilityClassSystem.AbilityClassVariety.ABILITY_CLASS_5K.ordinal].wipe()
                    noteIDAbilityMap[AbilityClassSystem.AbilityClassVariety.ABILITY_CLASS_7K.ordinal].wipe()
                    noteIDAbilityIDsMap.wipe()
                    Files.list(TwilightComponent.ABILITY_ENTRY_PATH).use { abilityFilePaths ->
                        abilityFilePaths.filter { it.fileName.toString().startsWith("#") }.sorted(
                            Comparator.comparingInt { abilityFilePath ->
                                defaultAbility.abilityFiles.indexOfFirst {
                                    it.fileName == abilityFilePath.fileName.toString()
                                }
                            }
                        ).forEach { abilityFilePath ->
                            val abilityFileName = abilityFilePath.fileName.toString()
                            val abilityClassVariety = defaultAbility.abilityFiles.find {
                                it.fileName == abilityFileName
                            }?.abilityClassVariety
                            if (abilityClassVariety != null) {
                                val noteIDAbilityMap = noteIDAbilityMap[abilityClassVariety.ordinal]
                                val abilityTable = jm.readValue(abilityFilePath.toFile(), JSON.BMSTable::class.java)
                                jm.readValue(
                                    TwilightComponent.ABILITY_ENTRY_PATH.resolve(abilityTable.name + ".json")
                                        .toFile(),
                                    Array<JSON.BMSTableData>::class.java
                                ).forEach { abilityTableData ->
                                    val abilityID = abilityTable.symbol + abilityTableData.level
                                    val ability = abilityMap[abilityID] ?: 0.0
                                    if (ability > 0.0) {
                                        if (abilityTableData.md5.isNotEmpty()) {
                                            noteIDAbilityMap.hashMap128.putIfAbsent(abilityTableData.md5, ability)
                                            noteIDAbilityIDsMap.hashMap128.computeIfAbsent(
                                                abilityTableData.md5
                                            ) { mutableListOf() }.add(abilityID)
                                        }
                                        if (abilityTableData.sha256.isNotEmpty()) {
                                            noteIDAbilityMap.hashMap256.putIfAbsent(abilityTableData.sha256, ability)
                                            noteIDAbilityIDsMap.hashMap128.computeIfAbsent(
                                                abilityTableData.sha256
                                            ) { mutableListOf() }.add(abilityID)
                                        }
                                    }
                                    abilityNameAbilityIDsMap.computeIfAbsent(
                                        FilenameUtils.removeExtension(
                                            abilityFileName.substring(abilityFileName.indexOf('#') + 1)
                                        )
                                    ) {
                                        mutableListOf()
                                    }.add(abilityID)
                                }
                                logInfo("Loaded Ability (${abilityTable.name})")
                            }
                        }
                    }
                } catch (e: IOException) {
                    logFault(e)
                }
                try {
                    DB.learnAbility()
                    logInfo("Learned Ability")
                } catch (e: Throwable) {
                    logFault(e)
                }
            } finally {
                isLoading = false
            }
        }
    }

    fun getAbility(
        abilityClassVariety: AbilityClassSystem.AbilityClassVariety,
        noteID128: String,
        noteID256: String
    ): Double {
        val abilityHashMap = noteIDAbilityMap[abilityClassVariety.ordinal]
        return abilityHashMap.hashMap128.getOrDefault(noteID128, abilityHashMap.hashMap256.getOrDefault(noteID256, 0.0))
    }

    fun getAbilityIDs(
        noteID128: String,
        noteID256: String
    ): Collection<String> {
        return noteIDAbilityIDsMap.hashMap128.getOrDefault(
            noteID128,
            noteIDAbilityIDsMap.hashMap256.getOrDefault(noteID256, emptyList())
        )
    }

    fun getAbilityIDs(
        abilityName: String
    ): Collection<String> {
        return abilityNameAbilityIDsMap.getOrDefault(abilityName, emptyList())
    }

    fun getAbilityNames(): Collection<String> {
        return abilityNameAbilityIDsMap.keys
    }
}