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 } }