diff --git a/game/plugin/areas/build.gradle b/game/plugin/areas/build.gradle new file mode 100644 index 00000000..4a0d8ff9 --- /dev/null +++ b/game/plugin/areas/build.gradle @@ -0,0 +1,6 @@ +plugin { + name = "Area listeners" + description = "Enables plugins to listen on mobs entering, moving inside of, or leaving a rectangular area." + authors = ["Major"] + dependencies = ["api"] +} \ No newline at end of file diff --git a/game/plugin/areas/src/action.kt b/game/plugin/areas/src/action.kt new file mode 100644 index 00000000..5b965603 --- /dev/null +++ b/game/plugin/areas/src/action.kt @@ -0,0 +1,18 @@ +package org.apollo.game.plugins.area + +/** + * Defines an area action using the DSL. + */ +fun action(@Suppress("UNUSED_PARAMETER") name: String, builder: ActionBuilder.() -> Unit) { + val listener = ActionBuilder() + builder(listener) + + actions.add(listener.build()) +} + +/** + * The [Set] of ([Area], [AreaAction]) [Pair]s. + */ +val actions = mutableSetOf>() + +class AreaAction(val entrance: AreaListener, val inside: AreaListener, val exit: AreaListener) \ No newline at end of file diff --git a/game/plugin/areas/src/area.kt b/game/plugin/areas/src/area.kt new file mode 100644 index 00000000..3c53fb1c --- /dev/null +++ b/game/plugin/areas/src/area.kt @@ -0,0 +1,94 @@ +package org.apollo.game.plugins.area + +import org.apollo.game.model.Position +import org.apollo.game.model.entity.Player +import org.apollo.game.plugins.api.component1 +import org.apollo.game.plugins.api.component2 +import org.apollo.game.plugins.api.component3 + +/** + * An area in the game world. + */ +interface Area { + + /** + * Returns whether or not the specified [Position] is inside this [Area]. + */ + operator fun contains(position: Position): Boolean + +} + +private class RectangularArea(val x: IntRange, val y: IntRange, val height: Int) : Area { + + override operator fun contains(position: Position): Boolean { + val (x, y, z) = position + return x in this.x && y in this.y && z == height + } + +} + +/** + * A typealias for a function that is invoked when a player enters, moves inside of, or exits an [Area]. + */ +internal typealias AreaListener = Player.(Position) -> Unit + +/** + * A builder for ([Area], [AreaAction]) [Pair]s. + */ +class ActionBuilder { + + private var area: Area? = null + + private var entrance: AreaListener = { } + + private var inside: AreaListener = { } + + private var exit: AreaListener = { } + + /** + * Places the contents of this builder into an ([Area], [AreaAction]) [Pair]. + */ + fun build(): Pair { + val area = area ?: throw UnsupportedOperationException("Area must be specified.") + return Pair(area, AreaAction(entrance, inside, exit)) + } + + /** + * Sets the [Area] to listen for movement in. + */ + fun area(contains: (Position) -> Boolean) { + this.area = object : Area { + override fun contains(position: Position): Boolean = contains.invoke(position) + } + } + + /** + * Sets the [Area] to listen for movement in. Note that [IntRange]s are (inclusive, _exclusive_), i.e. the upper + * bound is exclusive. + */ + fun area(x: IntRange, y: IntRange, height: Int = 0) { + this.area = RectangularArea(x, y, height) + } + + /** + * Executes the specified [listener] when a player enters the related [Area]. + */ + fun entrance(listener: AreaListener) { + this.entrance = listener + } + + /** + * Executes the specified [listener] when a player moves around inside the related [Area]. + */ + fun inside(listener: AreaListener) { + this.inside = listener + } + + /** + * Executes the specified [listener] when a player moves exits the related [Area]. + */ + fun exit(listener: AreaListener) { + this.exit = listener + } + +} \ No newline at end of file diff --git a/game/plugin/areas/src/areas.plugin.kts b/game/plugin/areas/src/areas.plugin.kts new file mode 100644 index 00000000..e029b199 --- /dev/null +++ b/game/plugin/areas/src/areas.plugin.kts @@ -0,0 +1,23 @@ + +import org.apollo.game.model.entity.Player +import org.apollo.game.model.event.impl.MobPositionUpdateEvent +import org.apollo.game.plugins.area.actions + +/** + * Intercepts the [MobPositionUpdateEvent] and invokes area actions if necessary. + */ +on_event { MobPositionUpdateEvent::class } + .where { mob is Player } + .then { + for ((area, action) in actions) { + if (mob.position in area) { + if (next in area) { + action.inside(mob as Player, next) + } else { + action.exit(mob as Player, next) + } + } else if (next in area) { + action.entrance(mob as Player, next) + } + } + }