EventContextAttributeReader.kt

package io.github.lishangbu.avalon.game.battle.engine.core.runtime.support

import io.github.lishangbu.avalon.game.battle.engine.core.event.EventContext

/**
 * `EventContext.attributes` 的轻量类型化读取器。
 *
 * 设计意图:
 * - 把 `Map<String, Any?>` 上常见的字符串、布尔值、数值、Map 读取逻辑集中到一处;
 * - 避免条件解释器和动作执行器到处手写 `as?`、`toString()`、字符串数值解析;
 * - 为 battle attribute key 常量化之后的“按类型读取”提供统一入口。
 *
 * 说明:
 * - 这里只处理 battle 引擎执行期最常见的几类读取,不做重量级 schema 校验;
 * - 对于布尔值,兼容 `Boolean`、`String("true"/"false")`、`Number(0/非0)` 三类输入;
 * - 对于数值,兼容 `Number` 与可解析的字符串;
 * - 读取失败时返回 `null`,由调用方决定是否提供默认值。
 */
object EventContextAttributeReader {
    fun readString(
        key: String,
        context: EventContext,
    ): String? = readString(key, context.attributes)

    fun readString(
        key: String,
        attributes: Map<String, Any?>,
    ): String? = attributes[key]?.toString()

    fun readBoolean(
        key: String,
        context: EventContext,
    ): Boolean? = readBoolean(key, context.attributes)

    fun readBoolean(
        key: String,
        attributes: Map<String, Any?>,
    ): Boolean? =
        when (val value = attributes[key]) {
            null -> {
                null
            }

            is Boolean -> {
                value
            }

            is String -> {
                when {
                    value.equals("true", ignoreCase = true) -> true
                    value.equals("false", ignoreCase = true) -> false
                    else -> null
                }
            }

            is Number -> {
                value.toInt() != 0
            }

            else -> {
                null
            }
        }

    fun readInt(
        key: String,
        context: EventContext,
    ): Int? = readInt(key, context.attributes)

    fun readInt(
        key: String,
        attributes: Map<String, Any?>,
    ): Int? =
        when (val value = attributes[key]) {
            null -> null
            is Int -> value
            is Number -> value.toInt()
            is String -> value.toIntOrNull()
            else -> null
        }

    fun readDouble(
        key: String,
        context: EventContext,
    ): Double? = readDouble(key, context.attributes)

    fun readDouble(
        key: String,
        attributes: Map<String, Any?>,
    ): Double? =
        when (val value = attributes[key]) {
            null -> null
            is Double -> value
            is Number -> value.toDouble()
            is String -> value.toDoubleOrNull()
            else -> null
        }

    fun readMap(
        key: String,
        context: EventContext,
    ): Map<*, *>? = readMap(key, context.attributes)

    fun readMap(
        key: String,
        attributes: Map<String, Any?>,
    ): Map<*, *>? = attributes[key] as? Map<*, *>
}