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 org.apache.commons.io.FilenameUtils import java.io.IOException import java.nio.file.Files import java.util.* import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.CopyOnWriteArrayList import java.util.stream.Stream object LevelSystem : Logger { class LevelGroup { var avatarID = "" var levelItems = emptyArray<LevelItem>() class LevelItem : Comparable<LevelItem> { override fun compareTo(other: LevelItem): Int { val levelData = level.compareTo(other.level) if (levelData != 0) { return levelData } var levelTextData = getValue(levelText).compareTo(getValue(other.levelText)) if (levelTextData != 0) { return levelTextData } levelTextData = levelText.compareTo(other.levelText) return if (levelTextData != 0) { levelTextData } else title.compareTo(other.title) } var levelID = "" var noteID = "" var title = "" var comment = "" var levelText = "" var level = 0 var stand: IntArray? = null var point: DoubleArray? = null var band: IntArray? = null var judgments: Array<IntArray>? = null var autoMode: IntArray? = null var noteSaltMode: IntArray? = null var audioMultiplier: DoubleArray? = null var faintNoteMode: IntArray? = null var judgmentMode: IntArray? = null var hitPointsMode: IntArray? = null var noteMobilityMode: IntArray? = null var longNoteMode: IntArray? = null var inputFavorMode: IntArray? = null var noteModifyMode: IntArray? = intArrayOf(0) var bpmMode: IntArray? = intArrayOf(0) var waveMode: IntArray? = intArrayOf(0) var setNoteMode: IntArray? = intArrayOf(0) var lowestJudgmentConditionMode: IntArray? = null var allowPause = true fun isSatisfy(qwilightWwwLevel: JSON.QwilightWwwLevel): Boolean { return judgments?.let { it.indices.all { i -> isSatisfy(it[i], qwilightWwwLevel.judgments[i]) } } != false && isSatisfy(stand, qwilightWwwLevel.stand) && isSatisfy(point, qwilightWwwLevel.point) && isSatisfy(band, qwilightWwwLevel.band) && isSatisfyMode(autoMode, qwilightWwwLevel.autoMode) && isSatisfyMode(noteSaltMode, qwilightWwwLevel.noteSaltMode) && isSatisfyMode(faintNoteMode, qwilightWwwLevel.faintNoteMode) && isSatisfyMode(judgmentMode, qwilightWwwLevel.judgmentMode) && isSatisfyMode(hitPointsMode, qwilightWwwLevel.hitPointsMode) && isSatisfyMode(noteMobilityMode, qwilightWwwLevel.noteMobilityMode) && isSatisfyMode(longNoteMode, qwilightWwwLevel.longNoteMode) && isSatisfyMode(inputFavorMode, qwilightWwwLevel.inputFavorMode) && isSatisfyMode(noteModifyMode, qwilightWwwLevel.noteModifyMode) && isSatisfyMode(bpmMode, qwilightWwwLevel.bpmMode) && isSatisfyMode(waveMode, qwilightWwwLevel.waveMode) && isSatisfyMode(setNoteMode, qwilightWwwLevel.setNoteMode) && isSatisfyMode(lowestJudgmentConditionMode, qwilightWwwLevel.lowestJudgmentConditionMode) } companion object { fun getValue(data: String): Int { return try { data.replace("\\D".toRegex(), "").toInt() } catch (e: NumberFormatException) { 0 } } fun isSatisfy(data: IntArray?, target: Int): Boolean { return if (data != null) { val lowestData = data[0] val highestData = data[1] (lowestData == -1 || lowestData <= target) && (highestData == -1 || target <= highestData) } else { true } } fun isSatisfy(data: DoubleArray?, target: Double): Boolean { return if (data != null) { val lowestData = data[0] val highestData = data[1] (lowestData == -1.0 || lowestData <= target) && (highestData == -1.0 || target <= highestData) } else { true } } fun isSatisfyMode(data: IntArray?, target: Int): Boolean { return data?.any { it == target } ?: true } } } } private val avatarIDLevelGroupMap = ConcurrentHashMap<String, MutableList<LevelGroup>>() private val levelNameLevelGroupMap = ConcurrentHashMap<String, LevelGroup>() private val avatarIDLevelNameMap = ConcurrentHashMap<String, MutableList<String>>() private val levelIDDrawingMap = ConcurrentHashMap<String, ByteArray>() val levelNoteIDs = CopyOnWriteArrayList<String>() var isLoading = false fun loadLevel() { isLoading = true try { avatarIDLevelGroupMap.clear() levelNameLevelGroupMap.clear() avatarIDLevelNameMap.clear() levelIDDrawingMap.clear() levelNoteIDs.clear() Files.list(TwilightComponent.LEVEL_ENTRY_PATH).use { it.forEach { levelFilePath -> if (!Files.isDirectory(levelFilePath)) { try { val jm = ObjectMapper() val levelGroup = jm.readValue(levelFilePath.toFile().absoluteFile, LevelGroup::class.java) levelGroup.levelItems.map { data -> data.noteID.split("/".toRegex()).toTypedArray() }.forEach { noteIDs -> levelNoteIDs.addAll(listOf(*noteIDs)) } Arrays.sort(levelGroup.levelItems) val avatarID = levelGroup.avatarID val levelName = FilenameUtils.removeExtension(levelFilePath.fileName.toString()) avatarIDLevelGroupMap.computeIfAbsent(avatarID) { mutableListOf() }.add(levelGroup) levelNameLevelGroupMap[levelName] = levelGroup avatarIDLevelNameMap.computeIfAbsent(avatarID) { mutableListOf() }.add(levelName) levelGroup.levelItems.forEach { levelItem -> val levelID = levelItem.levelID try { levelIDDrawingMap[levelID] = Files.readAllBytes( TwilightComponent.LEVEL_ENTRY_PATH.resolve(levelName) .resolve("$levelID.png") ) } catch (_: IOException) { } } logInfo("Loaded Level (${levelFilePath.fileName})") jm.writerWithDefaultPrettyPrinter().writeValue(levelFilePath.toFile().absoluteFile, levelGroup) logInfo("Saved Level (${levelFilePath.fileName})") } catch (e: IOException) { logFault(e) } } } } } catch (e: IOException) { logFault(e) } finally { isLoading = false } } val avatars: Array<Any> get() = avatarIDLevelGroupMap.keys.filter { it.isNotEmpty() }.map { object { val avatarID = it val avatarName = DB.getAvatarName(it) } }.sortedBy { it.avatarName }.toTypedArray() fun getLevelNames(avatarID: String): Array<String> { return avatarIDLevelNameMap[avatarID]?.sorted()?.toTypedArray() ?: emptyArray() } fun getLevelGroup(levelName: String): LevelGroup? { return levelNameLevelGroupMap[levelName] } fun getLevelIDDrawing(levelID: String): ByteArray? { return levelIDDrawingMap[levelID] } fun getSatisfiedLevelItems(qwilightWwwLevel: JSON.QwilightWwwLevel): Stream<LevelGroup.LevelItem> { return levelNameLevelGroupMap.values.stream().flatMap { Arrays.stream(it.levelItems) } .filter { it.noteID == qwilightWwwLevel.noteID && it.isSatisfy( qwilightWwwLevel ) } } fun getLevelItem(levelID: String): LevelGroup.LevelItem? { return levelNameLevelGroupMap.values.stream().flatMap { Arrays.stream(it.levelItems) } .filter { it.levelID == levelID }.toList().randomOrNull() } }