package net.taehui.twilight.awilight import net.taehui.CommentClass import net.taehui.EventClass 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(val avatar: AwilightAvatar, val handlerID: String, val site: AwilightSite) : Computing() { val ioAvatarIDs = mutableListOf<String>() val pendingIOAvatarIDs = mutableListOf<String>() private val sentIOAvatarIDs = mutableListOf<String>() val modeComponentValue = ModeComponent() val comment: CommentClass.Comment.Builder = CommentClass.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<EventClass.Event.NetDrawing>() private val r = Random() private lateinit var noteFileContents: ByteArray var setStop = false var targetStand = 0 var isF = false private var targetHitPoints = 1.0 private var isFailed = false private var lastJudged = Component.Judged.NOT private var targetBand = 0 private var highestBand = 0 private var targetPoint = 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(EventClass.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( EventClass.Event.newBuilder().apply { eventID = EventClass.Event.EventID.IO_JUDGE qwilightIOJudge = EventClass.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( EventClass.Event.newBuilder().apply { eventID = EventClass.Event.EventID.IO_NOTE_VISIBILITY qwilightIONoteVisibility = EventClass.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( EventClass.Event.newBuilder().apply { eventID = EventClass.Event.EventID.IO_NOTE_VISIBILITY qwilightIONoteVisibility = EventClass.Event.QwilightIONoteVisibility.newBuilder().apply { handlerID = this@DefaultCompute.handlerID addAllAvatarIDs(sentIOAvatarIDs) this.noteID = note.noteID this.setValidJudgedNotes = setValidJudgedNotes setNoteFailed = false }.build() } ) } } fun handleCompile(noteFilePath: Path) { noteFileContents = Files.readAllBytes(noteFilePath) BaseCompiler.handleCompile(this, noteFileContents) avatar.send(EventClass.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.getPaidMultiplier() comment.levyingAudioMultiplier = modeComponentValue.audioMultiplier val inputCount = Component.INPUT_COUNTS[inputMode.value] val drawingComponent = EventClass.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) { targetBand = 0 } else { highestBand = (++targetBand).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 targetPoint = Component.POINT_MAP[judged.value] .let { savedPoint += it; savedPoint } / Component.POINT_MAP[Component.Judged.HIGHEST.value].let { totalPoint += it; totalPoint } targetStand = (1000000 * ((Component.STAND_MAP[judged.value]) .let { savedStand += it; savedStand }) / (totalNotes * Component.STAND_MAP[Component.Judged.HIGHEST.value])).toInt() targetHitPoints += (if (judged != Component.Judged.LOWEST) hitPointsValue else 1.5 * targetHitPoints + 0.5) * Component.HIT_POINTS_MAP[judged.value] targetHitPoints = targetHitPoints.coerceIn(0.0, 1.0) comment.addPaints( CommentClass.PaintEvent.newBuilder().setWait(loopingCounter).setHitPoints(targetHitPoints) .setStand(targetStand) .setBand(targetBand) .setPoint(targetPoint).build() ) } fun setJudgmentMeter( loopingCounter: Double, input: Int, judgmentMeter: Double, judgmentAssist: Component.JudgmentAssist ) { comment.addJudgmentMeters( CommentClass.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( EventClass.Event.newBuilder().apply { eventID = EventClass.Event.EventID.IO_JUDGMENT_METER qwilightIOJudgmentMeter = EventClass.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: EventClass.Event.AvatarNetStatus) { avatar.send(EventClass.Event.newBuilder().apply { this.eventID = EventClass.Event.EventID.CALL_NET this.qwilightCallNet = EventClass.Event.QwilightCallNet.newBuilder().apply { this.siteID = site.siteID this.handlerID = this@DefaultCompute.handlerID this.hitPointsMode = modeComponentValue.hitPointsMode this.avatarNetStatus = avatarNetStatus this.stand = targetStand this.highestBand = this@DefaultCompute.highestBand this.point = targetPoint this.hitPoints = targetHitPoints this.isFailed = this@DefaultCompute.isFailed this.lastJudged = this@DefaultCompute.lastJudged.value if (avatarNetStatus != EventClass.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(EventClass.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(EventClass.Event.NetDrawing.newBuilder().apply { this.drawingVariety = EventClass.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( EventClass.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.getPaidMultiplier() 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( CommentClass.InputEvent.newBuilder().setInput(rawInput).setWait(loopingCounter).build() ) if (sentIOAvatarIDs.isNotEmpty()) { avatar.send( EventClass.Event.newBuilder().apply { eventID = EventClass.Event.EventID.IO_INPUT qwilightIOInput = EventClass.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 (targetHitPoints == 0.0 && !isF) { isF = true sendCallNetEvent(EventClass.Event.AvatarNetStatus.Failed) isFailed = false } if (!isF) { sendCallNetEvent(EventClass.Event.AvatarNetStatus.Default) } val toLoop = loopingCounter - (loopingHandler.time - Component.LEVYING_WAIT) if (toLoop > 0) { Thread.sleep(toLoop.toLong()) } } if (ioAvatarIDs.isNotEmpty()) { avatar.send( EventClass.Event.EventID.IO_QUIT, object { val handlerID = this@DefaultCompute.handlerID val avatarIDs = ioAvatarIDs @JvmField val isBanned = false } ) } } }