diff --git a/modules/assets/combat/maps/0-2/map.json b/modules/assets/combat/maps/0-2/map.json index 8c9dd6ccd..789936184 100644 --- a/modules/assets/combat/maps/0-2/map.json +++ b/modules/assets/combat/maps/0-2/map.json @@ -1,110 +1,29 @@ [ - { - "x": 491, - "y": 81, - "width": 46, - "height": 46 - }, - { - "x": 758, - "y": 79, - "width": 46, - "height": 46 - }, { "type": "CommandPost", - "x": 918, - "y": 100, - "width": 70, - "height": 70 + "x": 595, + "y": 637, + "width": 75, + "height": 75 }, { "type": "Heliport", - "x": 390, - "y": 184, - "width": 46, - "height": 46 - }, - { - "x": 628, - "y": 169, - "width": 46, - "height": 46 - }, - { - "x": 765, - "y": 262, - "width": 46, - "height": 46 + "x": 252, + "y": 628, + "width": 75, + "height": 75 }, { - "x": 1002, - "y": 267, - "width": 46, - "height": 46 - }, - { - "x": 302, - "y": 299, - "width": 46, - "height": 46 - }, - { - "type": "Heliport", - "x": 502, - "y": 362, - "width": 46, - "height": 46 - }, - { - "type": "Heliport", - "x": 886, - "y": 382, - "width": 46, - "height": 46 - }, - { - "x": 313, - "y": 463, - "width": 46, - "height": 46 - }, - { - "x": 469, - "y": 559, - "width": 46, - "height": 46 - }, - { - "x": 762, - "y": 508, - "width": 46, - "height": 46 - }, - { - "type": "Heliport", - "x": 246, - "y": 640, - "width": 46, - "height": 46 + "x": 479, + "y": 111, + "width": 75, + "height": 75 }, { "type": "CommandPost", - "x": 599, - "y": 630, - "width": 83, - "height": 83 - }, - { - "x": 220, - "y": 846, - "width": 46, - "height": 46 - }, - { - "x": 476, - "y": 766, - "width": 46, - "height": 46 + "x": 887, + "y": 142, + "width": 75, + "height": 75 } -] \ No newline at end of file +] diff --git a/modules/assets/combat/maps/0-2/map.png b/modules/assets/combat/maps/0-2/map.png index 207e28f14..ab6945997 100644 Binary files a/modules/assets/combat/maps/0-2/map.png and b/modules/assets/combat/maps/0-2/map.png differ diff --git a/modules/core/src/main/kotlin/com/waicool20/wai2k/game/GFL.kt b/modules/core/src/main/kotlin/com/waicool20/wai2k/game/GFL.kt index 5562f83b3..105bb153a 100644 --- a/modules/core/src/main/kotlin/com/waicool20/wai2k/game/GFL.kt +++ b/modules/core/src/main/kotlin/com/waicool20/wai2k/game/GFL.kt @@ -25,8 +25,12 @@ import com.waicool20.wai2k.events.DollDropEvent import com.waicool20.wai2k.events.EventBus import com.waicool20.wai2k.script.ScriptRunner import com.waicool20.wai2k.util.loggerFor +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.launch import java.util.concurrent.TimeUnit -import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicReference import kotlin.concurrent.thread object GFL { @@ -36,39 +40,43 @@ object GFL { class LogcatListener(val scriptRunner: ScriptRunner, val device: AndroidDevice) { private val logger = loggerFor() - private val isRunning = AtomicBoolean(false) - private var process: Process? = null + private val currentThread = AtomicReference(null) + private var currentProcess: Process? = null + private val r = + Regex("(\\d\\d-\\d\\d) (\\d\\d:\\d\\d:\\d\\d.\\d{3})\\s+(\\d+)\\s+(\\d+)\\s+([VDIWEFS])\\s+(\\w+)\\s+: (.*)\$") + fun start() { - if (!isRunning.compareAndSet(false, true)) return thread(isDaemon = true, name = "Logcat Listener [${device.serial}]") { - while (isRunning.get()) { - if (process == null || process?.isAlive == false) { - if (device.isConnected()) { - ADB.execute("-s", device.serial, "logcat", "-c") // Clear logs - process = ADB.execute("-s", device.serial, "logcat", "Unity:V", "*:S") - } else { - TimeUnit.SECONDS.sleep(5) - } + while (!Thread.interrupted()) { + if (!device.isConnected()) { + TimeUnit.SECONDS.sleep(5) + continue } + ADB.execute("-s", device.serial, "logcat", "-c") // Clear logs + val process = ADB.execute("-s", device.serial, "logcat", "Unity:V", "*:S") try { - process?.inputStream?.bufferedReader()?.use { reader -> - reader.forEachLine { - if (!isRunning.get()) process?.destroy() - onNewLine(it) - } - } + process.inputStream.bufferedReader().forEachLine(::onNewLine) } catch (e: Exception) { // Ignore } } - isRunning.set(false) - } + currentProcess?.destroyForcibly() + currentThread.set(null) + }.also { currentThread.set(it) } } fun stop() { - isRunning.compareAndSet(true, false) + currentThread.get()?.interrupt() + currentProcess?.destroyForcibly() } + private val _lines = MutableSharedFlow( + replay = 1, + extraBufferCapacity = 2048, + onBufferOverflow = BufferOverflow.DROP_OLDEST + ) + val lines = _lines.asSharedFlow() + private val gunCheckRegex = Regex(".*加载热更资源包名.+character(.+)\\.ab.*") private var gotGun = false @@ -77,6 +85,12 @@ object GFL { !gotGun && l.contains("预制物GetNewGun") -> gotGun = true gotGun -> checkForGun(l) } + scriptRunner.sessionScope.launch { + val match = r.matchEntire(l) ?: return@launch + // date, time, pid, tid, level, tag, msg + val (_, _, _, _, _, _, msg) = match.destructured + _lines.emit(msg) + } } private fun checkForGun(l: String) { diff --git a/modules/core/src/main/kotlin/com/waicool20/wai2k/script/ScriptComponent.kt b/modules/core/src/main/kotlin/com/waicool20/wai2k/script/ScriptComponent.kt index ccf1247d0..aff044315 100644 --- a/modules/core/src/main/kotlin/com/waicool20/wai2k/script/ScriptComponent.kt +++ b/modules/core/src/main/kotlin/com/waicool20/wai2k/script/ScriptComponent.kt @@ -25,6 +25,8 @@ import com.waicool20.wai2k.config.Wai2kPersist import com.waicool20.wai2k.config.Wai2kProfile import com.waicool20.wai2k.game.location.GameLocation import com.waicool20.wai2k.util.Ocr +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.first interface ScriptComponent { val scriptRunner: ScriptRunner @@ -38,4 +40,55 @@ interface ScriptComponent { val scope get() = scriptRunner.sessionScope val sessionId get() = scriptRunner.sessionId val elapsedTime get() = scriptRunner.elapsedTime + + + /** + * Searches the ADB logs for a certain string, returns false if the op times out or + * does not find the regex in the logs + */ + suspend fun waitForLog( + str: String, + timeout: Long = Long.MAX_VALUE, + fn: suspend () -> Unit = {} + ): Boolean { + val job = scriptRunner.sessionScope.launch { + delay(250) + while (coroutineContext.isActive) fn() + } + try { + withTimeout(timeout) { + scriptRunner.logcatListener!!.lines.first { it.contains(str) } + } + return true + } catch (e: TimeoutCancellationException) { + return false + } finally { + job.cancel() + } + } + + /** + * Searches the ADB logs for a certain regex string, returns false if the op times out or + * does not find the regex in the logs + */ + suspend fun waitForLog( + regex: Regex, + timeout: Long = Long.MAX_VALUE, + fn: suspend () -> Unit = {} + ): Boolean { + val job = scriptRunner.sessionScope.launch { + delay(250) + while (coroutineContext.isActive) fn() + } + try { + withTimeout(timeout) { + scriptRunner.logcatListener!!.lines.first { regex.matchEntire(it) != null } + } + return true + } catch (e: TimeoutCancellationException) { + return false + } finally { + job.cancel() + } + } } diff --git a/modules/core/src/main/kotlin/com/waicool20/wai2k/script/ScriptRunner.kt b/modules/core/src/main/kotlin/com/waicool20/wai2k/script/ScriptRunner.kt index dc7e5be12..44158af32 100644 --- a/modules/core/src/main/kotlin/com/waicool20/wai2k/script/ScriptRunner.kt +++ b/modules/core/src/main/kotlin/com/waicool20/wai2k/script/ScriptRunner.kt @@ -68,7 +68,8 @@ class ScriptRunner( private val logger = loggerFor() private var _device: AndroidDevice? = null - private var logcatListener: GFL.LogcatListener? = null + var logcatListener: GFL.LogcatListener? = null + private set private var _config = config private var _profile = profile diff --git a/modules/core/src/main/kotlin/com/waicool20/wai2k/script/modules/combat/MapRunner.kt b/modules/core/src/main/kotlin/com/waicool20/wai2k/script/modules/combat/MapRunner.kt index b39577ca3..bda0d72fb 100644 --- a/modules/core/src/main/kotlin/com/waicool20/wai2k/script/modules/combat/MapRunner.kt +++ b/modules/core/src/main/kotlin/com/waicool20/wai2k/script/modules/combat/MapRunner.kt @@ -32,11 +32,13 @@ import com.waicool20.wai2k.game.CombatMap import com.waicool20.wai2k.game.Echelon import com.waicool20.wai2k.game.GFL import com.waicool20.wai2k.game.MapRunnerRegions -import com.waicool20.wai2k.game.location.LocationId import com.waicool20.wai2k.script.ScriptComponent import com.waicool20.wai2k.script.ScriptTimeOutException import com.waicool20.wai2k.util.* -import kotlinx.coroutines.* +import kotlinx.coroutines.TimeoutCancellationException +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive import org.reflections.Reflections import java.awt.Color import java.awt.image.BufferedImage @@ -539,36 +541,28 @@ abstract class MapRunner( */ protected suspend fun handleBattleResults() { logger.info("Battle ended, clicking through battle results") - val location = if (this@MapRunner is EventMapRunner) { + if (this@MapRunner is EventMapRunner) { logger.info("Waiting for event menu") - locations.getValue(LocationId.EVENT) } else { logger.info("Waiting for combat menu") - locations.getValue(LocationId.COMBAT_MENU) } - try { - withTimeout(60000) { - while (!location.isInRegion(region)) { - repeat(Random.nextInt(2, 4)) { - mapRunnerRegions.battleEndClick.click() - delay(50) - } - endTurn() - } + waitForLog("MissionSelectionController:Start()") { + repeat(Random.nextInt(2, 4)) { + mapRunnerRegions.battleEndClick.click() + delay(50) } - } catch (e: TimeoutCancellationException) { - throw ScriptTimeOutException("Waiting to exit battle", e) - } finally { - EventBus.publish( - SortieDoneEvent( - profile.combat.map, - if (this is CorpseDragging) profile.combat.draggers else emptyList(), - sessionId, - elapsedTime - ) - ) + delay(500) } + delay(5000) + EventBus.publish( + SortieDoneEvent( + profile.combat.map, + if (this is CorpseDragging) profile.combat.draggers else emptyList(), + sessionId, + elapsedTime + ) + ) } protected suspend fun terminateMission(incrementSorties: Boolean = true) { @@ -700,10 +694,10 @@ abstract class MapRunner( } private suspend fun endTurn() { - mapRunnerRegions.endBattle.clickWhile { - ocr.readText(this, threshold = 0.73).contains("end", true) + waitForLog("Dequeue:Mission/endTurn") { + mapRunnerRegions.endBattle.click() + delay(200) } - region.waitHas(FT("ok.png"), 1000)?.click() } /** diff --git a/modules/core/src/main/kotlin/com/waicool20/wai2k/script/modules/combat/maps/Map0_2.kt b/modules/core/src/main/kotlin/com/waicool20/wai2k/script/modules/combat/maps/Map0_2.kt index 054352fc2..7184ed7ae 100644 --- a/modules/core/src/main/kotlin/com/waicool20/wai2k/script/modules/combat/maps/Map0_2.kt +++ b/modules/core/src/main/kotlin/com/waicool20/wai2k/script/modules/combat/maps/Map0_2.kt @@ -45,10 +45,10 @@ class Map0_2(scriptComponent: ScriptComponent) : HomographyMapRunner(scriptCompo delay((900 * gameState.delayCoefficient).roundToLong()) //Wait to settle gameState.requiresMapInit = false } - val rEchelons = deployEchelons(nodes[14], nodes[13]) + val rEchelons = deployEchelons(nodes[0], nodes[1]) mapRunnerRegions.startOperation.click(); yield() waitForGNKSplash() - resupplyEchelons(rEchelons + nodes[13]) + resupplyEchelons(rEchelons + nodes[1]) planPath() waitForTurnEnd(5) handleBattleResults() @@ -56,15 +56,15 @@ class Map0_2(scriptComponent: ScriptComponent) : HomographyMapRunner(scriptCompo private suspend fun planPath() { logger.info("Selecting echelon at command post") - nodes[14].findRegion().click() + nodes[0].findRegion().click() enterPlanningMode() - logger.info("Selecting ${nodes[0]}") - nodes[0].findRegion().click() - logger.info("Selecting ${nodes[2]}") - nodes[2].findRegion().click(); yield() + nodes[2].findRegion().click() + + logger.info("Selecting ${nodes[3]}") + nodes[3].findRegion().click(); yield() logger.info("Executing plan") mapRunnerRegions.executePlan.click()