All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.korlibs.event.InputGamepad.kt Maven / Gradle / Ivy

There is a newer version: 4.0.10
Show newest version
package korlibs.event

import korlibs.datastructure.*
import korlibs.datastructure.iterators.*
import korlibs.memory.*
import korlibs.io.util.*
import korlibs.math.geom.*
import kotlin.jvm.*
import kotlin.math.*

enum class GameStick(val id: Int) {
    LEFT(0), RIGHT(1);

    companion object {
        val STICKS = values()
    }
}

/**
 * ```
 *             ____________________________              __
 *            / [__L2__]          [__R2__] \               |
 *           / [__ L1 __]        [__ R1 __] \              | Front Triggers
 *        __/________________________________\__         __|
 *       /                                  _   \          |
 *      /      /\           ___            (N)   \         |
 *     /       ||      __  |SYS| __     _       _ \        | Main Pad
 *    |    <===DP===> |SE|      |ST|   (W) -|- (E) |       |
 *     \       ||    ___          ___       _     /        |
 *     /\      \/   /   \        /   \     (S)   /\      __|
 *    /  \________ | LS  | ____ |  RS | ________/  \       |
 *   |         /  \ \___/ /    \ \___/ /  \         |      | Control Sticks
 *   |        /    \_____/      \_____/    \        |    __|
 *   |       /                              \       |
 *    \_____/                                \_____/
 *
 *        |________|______|    |______|___________|
 *          D-Pad    Left       Right   Action Pad
 *         UP/DOWN   Stick      Stick
 *        LEFT/RIGHT LX/LY/L3   RX/RY/R3
 *                 |_____________|
 *                     Menu Pad
 * ```
 */
enum class GameButton {
    /** D-PAD LEFT */ LEFT, /** D-PAD RIGHT */ RIGHT,
    /** D-PAD UP */ UP, /** D-PAD DOWN */ DOWN,
    /** XBox: A, Playstation: Cross */ BUTTON_SOUTH,
    /** XBox: B, Playstation: Circle */ BUTTON_EAST,
    /** XBox: X, Playstation: Square */ BUTTON_WEST,
    /** XBox: Y, Playstation: Triangle */ BUTTON_NORTH,
    /** SELECT OR BACK */ SELECT,
    /** START OR FORWARD */ START,
    /** SYSTEM OR MENU */ SYSTEM,
    /** Left shoulder */ L1, /** Right shoulder */  R1,
    /** Left trigger (pressure 0.0-1.0) */ L2, /** Right trigger (pressure 0.0-1.0) */  R2,
    /** Left thumbstick */ L3, /** Right thumbstick */ R3,
    /** Left stick X: -1=left, +1=right */ LX, /** Left stick Y: -1=down, +1=up */ LY,
    /** Right stick X: -1=left, +1=right */ RX, /** Right stick Y: -1=down, +1=up */ RY,
    /** Generic button 4 */ BUTTON4,
    /** Generic button 5 */ BUTTON5,
    /** Generic button 6 */ BUTTON6,
    /** Generic button 7 */ BUTTON7,
    /** Generic button 8 */ BUTTON8,
    /** Record button */ RECORD,
    /** Internal use DPAD-X */ DPADX,
    /** Internal use DPAD-Y */ DPADY
    ;

    val index: Int get() = ordinal
    val bitMask: Int get() = 1 shl ordinal

    val isXbox: Boolean get() = name.contains("xbox", ignoreCase = true) || name.contains("microsoft", ignoreCase = true) || name.contains("xinput", ignoreCase = true)
    val isPlaystation: Boolean get() = name.contains("dualsense", ignoreCase = true) || name.contains("dualshock", ignoreCase = true) || name.contains("sony", ignoreCase = true)

    companion object {
        val BUTTONS = values()
        const val MAX = 32

        val LEFT_SHOULDER get() = L1
        val RIGHT_SHOULDER get() = R1

        val LEFT_TRIGGER get() = L2
        val RIGHT_TRIGGER get() = R2

        val LEFT_THUMB get() = L3
        val RIGHT_THUMB get() = R3

        val BACK get() = SELECT
        val FORWARD get() = START

        val XBOX_A get() = BUTTON_SOUTH
        val XBOX_B get() = BUTTON_EAST
        val XBOX_X get() = BUTTON_WEST
        val XBOX_Y get() = BUTTON_NORTH

        val PS_CROSS get() = BUTTON_SOUTH
        val PS_CIRCLE get() = BUTTON_EAST
        val PS_SQUARE get() = BUTTON_WEST
        val PS_TRIANGLE get() = BUTTON_NORTH
    }
}

class GamepadInfoEmitter(val dispatcher: EventListener) {
    private val gamepadPrevConnected = BooleanArray(GamepadInfo.MAX_CONTROLLERS)
    private val gamePadUpdateEvent = GamePadUpdateEvent()
    private val gamePadConnectionEvent = GamePadConnectionEvent()
    private val tempInts = IntArrayList()

    fun dispatchGamepadUpdateStart() {
        gamePadUpdateEvent.gamepads.fastForEach { it.connected = false }
        gamePadUpdateEvent.gamepadsLength = 0
    }
    fun dispatchGamepadUpdateAdd(info: GamepadInfo) {
        val index = gamePadUpdateEvent.gamepadsLength++
        val pad = gamePadUpdateEvent.gamepads[index]
        pad.copyFrom(info)
        pad.connected = true
        pad.index = index
    }
    /**
     * Triggers an update envent and potential CONNECTED/DISCONNECTED events.
     *
     * Returns a list of disconnected gamepads.
     */
    fun dispatchGamepadUpdateEnd(out: IntArrayList = tempInts): IntArrayList {
        out.clear()
        gamePadUpdateEvent.gamepads.fastForEach {
            if (gamepadPrevConnected[it.index] != it.connected) {
                gamepadPrevConnected[it.index] = it.connected
                out.add(it.index)
                // Clear stuff
                if (!it.connected) {
                    it.name = null
                }
                dispatcher.dispatch(gamePadConnectionEvent.apply {
                    this.type = GamePadConnectionEvent.Type.fromConnected(it.connected)
                    this.gamepad = it.index
                })
            }
        }
        dispatcher.dispatch(gamePadUpdateEvent)
        return out
    }
}

// http://blog.teamtreehouse.com/wp-content/uploads/2014/03/standardgamepad.png
class GamepadInfo(
    var index: Int = 0,
    var connected: Boolean = false,
    var name: String? = null,
    var rawButtons: FloatArray = FloatArray(GameButton.MAX),
    var batteryLevel: Float = 1f,
    var name2: String = DEFAULT_NAME2,
    var batteryStatus: BatteryStatus = BatteryStatus.UNKNOWN,
) {
    enum class BatteryStatus { CHARGING, DISCHARGING, FULL, UNKNOWN }

    companion object {
        val DEFAULT_NAME2 = "Wireless Controller"
        const val MAX_CONTROLLERS = 4
        //const val MAX_CONTROLLERS = 8

        const val DEAD_RANGE = 0.06f

        fun withoutDeadRange(value: Float, margin: Float = DEAD_RANGE, apply: Boolean = true): Float {
            if (apply && value.absoluteValue < margin) return 0f
            return value
        }
    }

    val fullName: String get() = "${name ?: "unknown"} - $name2"

    fun copyFrom(that: GamepadInfo) {
        this.index = that.index
        this.name = that.name
        this.name2 = that.name2
        this.connected = that.connected
        this.batteryLevel = that.batteryLevel
        this.batteryStatus = that.batteryStatus
        arraycopy(that.rawButtons, 0, this.rawButtons, 0, min(this.rawButtons.size, that.rawButtons.size))
    }

    val up: Boolean get() = this[GameButton.UP] != 0f
    val down: Boolean get() = this[GameButton.DOWN] != 0f
    val left: Boolean get() = this[GameButton.LEFT] != 0f
    val right: Boolean get() = this[GameButton.RIGHT] != 0f
    val start: Boolean get() = this[GameButton.START] != 0f
    val select: Boolean get() = this[GameButton.SELECT] != 0f
    val system: Boolean get() = this[GameButton.SYSTEM] != 0f

    val north: Boolean get() = this[GameButton.BUTTON_NORTH] != 0f
    val west: Boolean get() = this[GameButton.BUTTON_WEST] != 0f
    val east: Boolean get() = this[GameButton.BUTTON_EAST] != 0f
    val south: Boolean get() = this[GameButton.BUTTON_SOUTH] != 0f

    val lx: Float get() = this[GameButton.LX]
    val ly: Float get() = this[GameButton.LY]
    val rx: Float get() = this[GameButton.RX]
    val ry: Float get() = this[GameButton.RY]

    val l1: Boolean get() = this[GameButton.L1] != 0f
    val l2: Float get() = this[GameButton.L2]
    val l3: Boolean get() = this[GameButton.L3] != 0f

    val r1: Boolean get() = this[GameButton.R1] != 0f
    val r2: Float get() = this[GameButton.R2]
    val r3: Boolean get() = this[GameButton.R3] != 0f

    operator fun get(button: GameButton): Float = rawButtons[button.index].toFloat()
    operator fun get(stick: GameStick): Point = Point(getX(stick), getY(stick))
    fun getX(stick: GameStick) = when (stick) {
        GameStick.LEFT -> get(GameButton.LX)
        GameStick.RIGHT -> get(GameButton.RX)
    }
    fun getY(stick: GameStick) = when (stick) {
        GameStick.LEFT -> get(GameButton.LY)
        GameStick.RIGHT -> get(GameButton.RY)
    }
    fun toStringEx(includeButtons: Boolean = true): String = buildString {
        append("Gamepad[$index][$fullName]")
        if (includeButtons) {
            append("[")
            var count = 0
            for (button in GameButton.values()) {
                val value = this@GamepadInfo[button]
                if (value != 0f) {
                    if (count > 0) append(",")
                    append("$button=${value.niceStr}")
                    count++
                }
            }
            append("]")
        }
    }
    override fun toString(): String = toStringEx(includeButtons = false)
}

data class GamePadConnectionEvent(
    override var type: Type = Type.CONNECTED,
    var gamepad: Int = 0
) : Event(), TEvent {

    enum class Type : EventType {
        CONNECTED, DISCONNECTED;
        companion object {
            val ALL = values()
            fun fromConnected(connected: Boolean): Type = if (connected) CONNECTED else DISCONNECTED
        }
    }

    fun copyFrom(other: GamePadConnectionEvent) {
        this.type = other.type
        this.gamepad = other.gamepad
    }
}

@Suppress("ArrayInDataClass")
data class GamePadUpdateEvent @JvmOverloads constructor(
    var gamepadsLength: Int = 0,
    val gamepads: Array = Array(GamepadInfo.MAX_CONTROLLERS) { GamepadInfo(it) },
) : Event(), TEvent {
    override val type: EventType get() = GamePadUpdateEvent
    companion object : EventType

    fun copyFrom(that: GamePadUpdateEvent) {
        this.gamepadsLength = that.gamepadsLength
        for (n in 0 until gamepads.size) {
            this.gamepads[n].copyFrom(that.gamepads[n])
        }
    }

    override fun toString(): String = "GamePadUpdateEvent(${gamepads.filter { it.connected }.map { it.toStringEx() }})"
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy