package net.taehui.twilight.awilight import net.taehui.twilight.BaseCompiler import net.taehui.twilight.Component import net.taehui.twilight.Computing import net.taehui.twilight.JSON import net.taehui.twilight.Utility.getDistance import net.taehui.twilight.note.BaseNote import net.taehui.twilight.note.InputNote import net.taehui.twilight.note.JudgedNoteData import net.taehui.twilight.note.JudgedNoteData.ID import org.apache.commons.lang3.time.StopWatch import java.nio.file.Files import java.nio.file.Path import java.util.* import java.util.concurrent.ConcurrentLinkedQueue import kotlin.math.abs class DefaultCompute(noteID: String, val avatar: AwilightAvatar, val handlerID: String, val site: AwilightSite) : Computing(noteID) { val ioAvatarIDs = mutableListOf<String>() val pendingIOAvatarIDs = mutableListOf<String>() private val sentIOAvatarIDs = mutableListOf<String>() val modeComponentValue = ModeComponent() val comment: CommentOuterClass.Comment.Builder = CommentOuterClass.Comment.newBuilder().setDate("2.0.0") private val handlingNotes = mutableListOf<BaseNote>() val notes = mutableListOf<BaseNote>() val waitAudioNoteMap = TreeSet<Double>() val waitMediaNoteMap = TreeSet<Double>() val waitBPMMap = TreeMap<Double, Double>() val netDrawings = mutableListOf<EventOuterClass.Event.NetDrawing>() private val r = Random() private lateinit var noteFileData: ByteArray var setStop = false var stand = 0 var isF = false private var hitPoints = 1.0 private var isFailed = false private var lastJudged = Component.Judged.NOT private var band = 0 private var highestBand = 0 private var point = 1.0 private var savedPoint = 0.0 private var totalPoint = 0.0 private var savedStand = 0.0 private var earlyValue = 0 private var lateValue = 0 val twilightCompiledIOQueue = ConcurrentLinkedQueue<JSON.TwilightCompiledIO>() fun stop() { setStop = true avatar.send(EventOuterClass.Event.EventID.COMPILED, object { val siteID = site.siteID val handlerID = this@DefaultCompute.handlerID @JvmField val isCompiled = false }) } private fun setNoteJudged(inputNote: BaseNote, judged: Component.Judged) { inputNote.judged = judged if (sentIOAvatarIDs.isNotEmpty()) { avatar.send( EventOuterClass.Event.newBuilder().apply { eventID = EventOuterClass.Event.EventID.IO_JUDGE qwilightIOJudge = EventOuterClass.Event.QwilightIOJudge.newBuilder().apply { handlerID = this@DefaultCompute.handlerID addAllAvatarIDs(sentIOAvatarIDs) noteID = inputNote.noteID this.judged = judged.value }.build() } ) } } private fun setNoteFailed(note: BaseNote, setValidJudgedNotes: Boolean) { note.isFailed = true if (sentIOAvatarIDs.isNotEmpty()) { avatar.send( EventOuterClass.Event.newBuilder().apply { eventID = EventOuterClass.Event.EventID.IO_NOTE_VISIBILITY qwilightIONoteVisibility = EventOuterClass.Event.QwilightIONoteVisibility.newBuilder().apply { handlerID = this@DefaultCompute.handlerID addAllAvatarIDs(sentIOAvatarIDs) noteID = note.noteID this.setValidJudgedNotes = setValidJudgedNotes setNoteFailed = true }.build() } ) } } private fun wipeNotes(note: BaseNote, setValidJudgedNotes: Boolean) { handlingNotes.remove(note) if (note.hasStand() && sentIOAvatarIDs.isNotEmpty()) { avatar.send( EventOuterClass.Event.newBuilder().apply { eventID = EventOuterClass.Event.EventID.IO_NOTE_VISIBILITY qwilightIONoteVisibility = EventOuterClass.Event.QwilightIONoteVisibility.newBuilder().apply { handlerID = this@DefaultCompute.handlerID addAllAvatarIDs(sentIOAvatarIDs) this.noteID = note.noteID this.setValidJudgedNotes = setValidJudgedNotes setNoteFailed = false }.build() } ) } } fun handleCompile(noteFilePath: Path) { noteFileData = Files.readAllBytes(noteFilePath) BaseCompiler.handleCompile(this, noteFileData) avatar.send(EventOuterClass.Event.EventID.COMPILED, object { val siteID = site.siteID val handlerID = this@DefaultCompute.handlerID @JvmField val isCompiled = true }) } fun handleNotes(w: Double) { modeComponentValue.bpm = bpm comment.levyingMultiplier = modeComponentValue.getMultiplier() comment.levyingAudioMultiplier = modeComponentValue.audioMultiplier val inputCount = Component.INPUT_COUNTS[inputMode.value] val drawingComponent = EventOuterClass.Event.DrawingComponent.newBuilder().apply { this.p2BuiltLength = (InputNote.NOTE_LENGTH * inputCount).toFloat() this.judgmentMainPosition = Component.STANDARD_HEIGHT.toFloat() }.build() var handlingID = 0 var loopingCounter = -Component.LEVYING_WAIT val valueComponent = Component(levyingBPM, 60) var logicalY = 0.0 val endNoteID = notes.size var handledNotYet = true val inputMillisMap = DoubleArray(53) val waitBPMQueue = LinkedList<Map.Entry<Double, Double>>(waitBPMMap.entries) val rawInputQueue = LinkedList<Int>() val inputMap = BooleanArray(53) val inputCoolMillisMap = DoubleArray(53) val loopingHandler = StopWatch.createStarted() while (!setStop) { fun onNoteJudged(judgedNote: BaseNote) { val judged = judgedNote.judged if (judged == Component.Judged.LOWEST) { band = 0 } else { highestBand = (++band).coerceAtLeast(highestBand) } when (judged) { Component.Judged.HIGHEST -> comment.highestJudgment += 1 Component.Judged.HIGHER -> comment.higherJudgment += 1 Component.Judged.HIGH -> comment.highJudgment += 1 Component.Judged.LOW -> comment.lowJudgment += 1 Component.Judged.LOWER -> comment.lowerJudgment += 1 Component.Judged.LOWEST -> { comment.lowestJudgment += 1 isFailed = true } else -> Unit } lastJudged = judged point = Component.POINT_MAP[judged.value] .let { savedPoint += it; savedPoint } / Component.POINT_MAP[Component.Judged.HIGHEST.value].let { totalPoint += it; totalPoint } stand = (1000000 * ((Component.STAND_MAP[judged.value]) .let { savedStand += it; savedStand }) / (totalNotes * Component.STAND_MAP[Component.Judged.HIGHEST.value])).toInt() hitPoints += (if (judged != Component.Judged.LOWEST) hitPointsValue else 1.5 * hitPoints + 0.5) * Component.HIT_POINTS_MAP[judged.value] hitPoints = hitPoints.coerceIn(0.0, 1.0) comment.addPaints( CommentOuterClass.PaintEvent.newBuilder().setWait(loopingCounter).setHitPoints(hitPoints) .setStand(stand) .setBand(band) .setPoint(point).build() ) } fun setJudgmentMeter( loopingCounter: Double, input: Int, judgmentMeter: Double, judgmentAssist: Component.JudgmentAssist ) { comment.addJudgmentMeters( CommentOuterClass.JudgmentMeterEvent.newBuilder().setJudgmentMeter(judgmentMeter) .setWait(loopingCounter - judgmentMeter).setAssist(judgmentAssist.ordinal).build() ) if (judgmentMeter > 0.0) { ++lateValue } else if (judgmentMeter < 0.0) { ++earlyValue } if (sentIOAvatarIDs.isNotEmpty()) { avatar.send( EventOuterClass.Event.newBuilder().apply { eventID = EventOuterClass.Event.EventID.IO_JUDGMENT_METER qwilightIOJudgmentMeter = EventOuterClass.Event.QwilightIOJudgmentMeter.newBuilder().apply { addAllAvatarIDs(sentIOAvatarIDs) handlerID = this@DefaultCompute.handlerID this.judgmentMeter = judgmentMeter this.input = input this.assist = judgmentAssist.ordinal }.build() } ) } } fun handleJudged(judgedNote: BaseNote, judgedNoteData: JudgedNoteData?, loopingCounter: Double) { judgedNoteData?.let { val input = judgedNote.input val judged = it.judged when (it.valueID) { ID.NOTE_JUDGMENT -> { setNoteJudged(judgedNote, judged) wipeNotes(judgedNote, false) setJudgmentMeter(loopingCounter, input, it.judgmentMeter, Component.JudgmentAssist.DEFAULT) onNoteJudged(judgedNote) } ID.TRAP_NOTE_JUDGMENT -> { setNoteJudged(judgedNote, judged) wipeNotes(judgedNote, false) onNoteJudged(judgedNote) } ID.QUIT_LONG_NOTE_JUDGMENT, ID.AUTO_LONG_NOTE_JUDGMENT -> { setNoteJudged(judgedNote, judged) wipeNotes(judgedNote, false) setJudgmentMeter( loopingCounter, input, it.judgmentMeter, Component.JudgmentAssist.LONG_NOTE_UP ) onNoteJudged(judgedNote) } ID.LEVY_LONG_NOTE_JUDGMENT -> { if (judged == Component.Judged.LOWEST) { setNoteFailed(judgedNote, true) } setNoteJudged(judgedNote, judged) setJudgmentMeter(loopingCounter, input, it.judgmentMeter, Component.JudgmentAssist.DEFAULT) onNoteJudged(judgedNote) } ID.FAILED_LONG_NOTE_JUDGMENT -> { setNoteFailed(judgedNote, false) setNoteJudged(judgedNote, Component.Judged.LOWEST) onNoteJudged(judgedNote) } else -> Unit } } } fun sendCallNetEvent(avatarNetStatus: EventOuterClass.Event.AvatarNetStatus) { avatar.send(EventOuterClass.Event.newBuilder().apply { this.eventID = EventOuterClass.Event.EventID.CALL_NET this.qwilightCallNet = EventOuterClass.Event.QwilightCallNet.newBuilder().apply { this.siteID = site.siteID this.handlerID = this@DefaultCompute.handlerID this.hitPointsMode = modeComponentValue.hitPointsMode this.avatarNetStatus = avatarNetStatus this.stand = stand this.band = band this.point = point this.hitPoints = hitPoints this.isFailed = this@DefaultCompute.isFailed this.lastJudged = this@DefaultCompute.lastJudged.value if (avatarNetStatus != EventOuterClass.Event.AvatarNetStatus.Default) { this.title = this@DefaultCompute.title this.artist = this@DefaultCompute.artist this.genre = this@DefaultCompute.genre this.level = this@DefaultCompute.level this.levelText = this@DefaultCompute.levelText this.wantLevelID = "" val modeComponentValue = this@DefaultCompute.modeComponentValue val comment = this@DefaultCompute.comment this.autoMode = modeComponentValue.autoMode this.noteSaltMode = modeComponentValue.noteSaltMode this.audioMultiplier = comment.levyingAudioMultiplier this.faintNoteMode = modeComponentValue.faintNoteMode this.judgmentMode = modeComponentValue.judgmentMode this.noteMobilityMode = modeComponentValue.noteMobilityMode this.inputFavorMode = modeComponentValue.inputFavorMode this.longNoteMode = modeComponentValue.longNoteMode this.noteModifyMode = modeComponentValue.noteModifyMode this.bpmMode = modeComponentValue.bpmMode this.waveMode = modeComponentValue.waveMode this.setNoteMode = modeComponentValue.setNoteMode this.lowestJudgmentConditionMode = modeComponentValue.lowestJudgmentConditionMode this.totalNotes = this@DefaultCompute.totalNotes this.judgmentStage = this@DefaultCompute.judgmentStage this.hitPointsValue = this@DefaultCompute.hitPointsValue this.highestInputCount = this@DefaultCompute.highestInputCount this.length = this@DefaultCompute.length this.bpm = this@DefaultCompute.bpm this.multiplier = comment.levyingMultiplier this.inputMode = this@DefaultCompute.inputMode.value addData(comment.build().toByteString()) } else { this.drawingComponent = drawingComponent this.addAllDrawings(this@DefaultCompute.netDrawings) } }.build() }) isFailed = false } val lastLoopingCounter = loopingCounter loopingCounter += valueComponent.millisLoopUnit if (loopingCounter > length + Component.QUIT_WAIT && handledNotYet) { handledNotYet = false if (!isF) { sendCallNetEvent(EventOuterClass.Event.AvatarNetStatus.Clear) } } while (true) { val twilightCompiledIO = twilightCompiledIOQueue.poll() if (twilightCompiledIO != null) { if (twilightCompiledIO.isCompiled) { ioAvatarIDs.add(twilightCompiledIO.avatarID) pendingIOAvatarIDs.add(twilightCompiledIO.avatarID) } else { ioAvatarIDs.remove(twilightCompiledIO.avatarID) pendingIOAvatarIDs.remove(twilightCompiledIO.avatarID) sentIOAvatarIDs.remove(twilightCompiledIO.avatarID) } } else { break } } // 인공지능 for (i in inputCount downTo 1) { val powValue0 = 125.0 * r.nextGaussian(0.0, 1 - w) val powValue1 = abs(r.nextGaussian(0.0, w) * valueComponent.millisLoopUnit) inputCoolMillisMap[i] = 0.0.coerceAtLeast(inputCoolMillisMap[i] - valueComponent.millisLoopUnit) if (inputCoolMillisMap[i] == 0.0) { if (inputMap[i]) { rawInputQueue.offer(-i) inputMap[i] = false } else { handlingNotes .filter { it.hasStand() && it.input == i && it.judged == Component.Judged.NOT } .minByOrNull { it.wait } ?.let { if (it.wait + inputMillisMap[i] <= loopingCounter) { rawInputQueue.offer(i) inputMap[i] = true inputCoolMillisMap[i] += it.longWait + powValue0 inputMillisMap[i] = powValue0 } } } } val highestJudgmentMillis0 = Component.getJudgmentMillis( Component.Judged.HIGHEST, judgmentStage, 0, Component.JudgmentAssist.DEFAULT ) - valueComponent.millisLoopUnit val highestJudgmentMillis1 = Component.getJudgmentMillis( Component.Judged.HIGHEST, judgmentStage, 1, Component.JudgmentAssist.DEFAULT ) if (inputMillisMap[i] < highestJudgmentMillis0) { inputMillisMap[i] = inputMillisMap[i] + powValue1 } else if (highestJudgmentMillis1 < inputMillisMap[i]) { inputMillisMap[i] = inputMillisMap[i] - powValue1 } } // 렌더링 스레드 netDrawings.clear() for (i in inputMap.size - 1 downTo 1) { if (inputMap[i]) { netDrawings.add(EventOuterClass.Event.NetDrawing.newBuilder().apply { this.drawingVariety = EventOuterClass.Event.NetDrawing.Variety.Main this.param = Component.NET_DRAWINGS[inputMode.value]!![i] - 128 this.position0 = (i - 1) * InputNote.NOTE_LENGTH this.position1 = 0.0 this.length = InputNote.NOTE_LENGTH this.height = Component.STANDARD_HEIGHT }.build()) } } handlingNotes.forEach { it.paint(this) } if (pendingIOAvatarIDs.isNotEmpty()) { avatar.send( EventOuterClass.Event.EventID.LEVY_IO, object { val avatarIDs = pendingIOAvatarIDs val handlerID = this@DefaultCompute.handlerID val levyingWait = loopingCounter @JvmField val isF = this@DefaultCompute.isF val multiplier = modeComponentValue.getMultiplier() val audioMultiplier = modeComponentValue.audioMultiplier val ioMillis = System.currentTimeMillis() }, comment.build().toByteString() ) sentIOAvatarIDs.addAll(pendingIOAvatarIDs) pendingIOAvatarIDs.clear() } while (endNoteID > handlingID) { val note = notes[handlingID] if (note.isClose(loopingCounter, logicalY)) { handlingNotes.add(note) ++handlingID } else { break } } val distance = getDistance( valueComponent, waitBPMQueue, lastLoopingCounter, loopingCounter ) logicalY -= distance for (i in handlingNotes.size - 1 downTo 0) { fun handleAutoJudged(note: BaseNote): Boolean { return when (note.autoJudge(loopingCounter)?.valueID) { ID.NOT -> true else -> false } } val handlingNote = handlingNotes[i] handlingNote.doMove(distance) if (handleAutoJudged(handlingNote)) { wipeNotes(handlingNote, false) } else { if (handlingNote.hasStand()) { if (handlingNote.isFailedAsTooLate( loopingCounter, judgmentStage ) && handlingNote.judged == Component.Judged.NOT ) { setNoteJudged(handlingNote, Component.Judged.LOWEST) if (handlingNote.longWait > 0.0) { setNoteFailed(handlingNote, true) } else { wipeNotes(handlingNote, false) } onNoteJudged(handlingNote) } if (handlingNote.judged != Component.Judged.NOT && handlingNote.isTooLong( loopingCounter, judgmentStage ) ) { if (!handlingNote.isFailed) { if (isAutoLongNote) { setNoteJudged(handlingNote, handlingNote.judged) onNoteJudged(handlingNote) } else { setNoteJudged(handlingNote, Component.Judged.LOWER) onNoteJudged(handlingNote) } } wipeNotes(handlingNote, isAutoLongNote) } } } } while (true) { val rawInput = rawInputQueue.poll() if (rawInput != null) { val isInput = rawInput > 0 val absInput = abs(rawInput) comment.addInputs( CommentOuterClass.InputEvent.newBuilder().setInput(rawInput).setWait(loopingCounter).build() ) if (sentIOAvatarIDs.isNotEmpty()) { avatar.send( EventOuterClass.Event.newBuilder().apply { eventID = EventOuterClass.Event.EventID.IO_INPUT qwilightIOInput = EventOuterClass.Event.QwilightIOInput.newBuilder().apply { handlerID = this@DefaultCompute.handlerID addAllAvatarIDs(sentIOAvatarIDs) this.input = rawInput power = UByte.MAX_VALUE.toInt() }.build() } ) } var wasHandled = false handlingNotes .filter { it.input == absInput && !it.isFailed && it.hasStand() && (!isInput || it.judged == Component.Judged.NOT) } .minByOrNull { it.wait } ?.let { val judgedNoteData = it.judge(rawInput, loopingCounter, judgmentStage, isAutoLongNote) if (judgedNoteData != null) { handleJudged(it, judgedNoteData, loopingCounter) } wasHandled = true } if (!wasHandled) { handlingNotes .minByOrNull { it.wait } ?.let { if (it.input == absInput && !it.isFailed && !it.hasStand()) { val judgedNoteData = it.judge(rawInput, loopingCounter, judgmentStage, isAutoLongNote) if (judgedNoteData != null) { handleJudged(it, judgedNoteData, loopingCounter) wasHandled = true } } } } } else { break } } if (hitPoints == 0.0 && !isF) { isF = true sendCallNetEvent(EventOuterClass.Event.AvatarNetStatus.Failed) isFailed = false } if (!isF) { sendCallNetEvent(EventOuterClass.Event.AvatarNetStatus.Default) } val toLoop = loopingCounter - (loopingHandler.time - Component.LEVYING_WAIT) if (toLoop > 0) { Thread.sleep(toLoop.toLong()) } } if (ioAvatarIDs.isNotEmpty()) { avatar.send( EventOuterClass.Event.EventID.IO_QUIT, object { val handlerID = this@DefaultCompute.handlerID val avatarIDs = ioAvatarIDs @JvmField val isBanned = false } ) } } }