Newer
Older
Twilight / src / main / kotlin / net / taehui / twilight / awilight / DefaultCompute.kt
@Taehui Taehui on 12 Nov 25 KB 2023-11-12 오후 9:22
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
                }
            )
        }
    }
}