diff --git a/game/plugin/areas/src/Area.kt b/game/plugin/areas/src/Area.kt new file mode 100644 index 00000000..6efdae06 --- /dev/null +++ b/game/plugin/areas/src/Area.kt @@ -0,0 +1,27 @@ +package org.apollo.game.plugins.area + +import org.apollo.game.model.Position +import org.apollo.game.plugins.api.Position.component1 +import org.apollo.game.plugins.api.Position.component2 +import org.apollo.game.plugins.api.Position.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 + +} + +internal class RectangularArea(private val x: IntRange, private val y: IntRange, private 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 + } + +} diff --git a/game/plugin/areas/src/AreaAction.kt b/game/plugin/areas/src/AreaAction.kt new file mode 100644 index 00000000..85d8141a --- /dev/null +++ b/game/plugin/areas/src/AreaAction.kt @@ -0,0 +1,51 @@ +package org.apollo.game.plugins.area + +import org.apollo.game.model.Position +import org.apollo.game.model.entity.Player + +/** + * A set of actions to execute when a player enters, moves inside, or exits a specific area of the world. + */ +internal class AreaAction(val entrance: AreaListener, val inside: AreaListener, val exit: AreaListener) + +/** + * A function that is invoked when a player enters, moves inside of, or exits an [Area]. + */ +typealias AreaListener = Player.(Position) -> Unit + +/** + * Registers an [AreaAction] for the specified [Area] using the builder. + */ +fun action(name: String, area: Area, builder: AreaActionBuilder.() -> Unit) { + actions += AreaActionBuilder(name, area).apply(builder).build() +} + +/** + * Registers an [AreaAction] for the specified [Area] using the builder. + * + * @param predicate The predicate that determines whether or not the given [Position] is inside the [Area]. + */ +fun action(name: String, predicate: (Position) -> Boolean, builder: AreaActionBuilder.() -> Unit) { + val area = object : Area { + override fun contains(position: Position): Boolean = predicate(position) + } + + action(name, area, builder) +} + +/** + * Registers an [AreaAction] for the specified [Area] using the builder. + * + * @param x The `x` coordinate range, both ends inclusive. + * @param y The `y` coordinate range, both ends inclusive. + */ +fun action(name: String, x: IntRange, y: IntRange, height: Int = 0, builder: AreaActionBuilder.() -> Unit) { + val area = RectangularArea(x, y, height) + + action(name, area, builder) +} + +/** + * The [Set] of ([Area], [AreaAction]) [Pair]s. + */ +internal val actions = mutableSetOf>() diff --git a/game/plugin/areas/src/AreaActionBuilder.kt b/game/plugin/areas/src/AreaActionBuilder.kt new file mode 100644 index 00000000..00ba8eb6 --- /dev/null +++ b/game/plugin/areas/src/AreaActionBuilder.kt @@ -0,0 +1,42 @@ +package org.apollo.game.plugins.area + +/** + * A builder for ([Area], [AreaAction]) [Pair]s. + */ +class AreaActionBuilder internal constructor(val name: String, val area: Area) { + + private var entrance: AreaListener = { } + + private var inside: AreaListener = { } + + private var exit: AreaListener = { } + + /** + * Places the contents of this builder into an ([Area], [AreaAction]) [Pair]. + */ + internal fun build(): Pair { + return Pair(area, AreaAction(entrance, inside, exit)) + } + + /** + * The [listener] to execute when a player enters the associated [Area]. + */ + fun entrance(listener: AreaListener) { + this.entrance = listener + } + + /** + * The [listener] to execute when a player moves around inside the associated [Area]. + */ + fun inside(listener: AreaListener) { + this.inside = listener + } + + /** + * The [listener] to execute when a player moves exits the associated [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 similarity index 100% rename from game/plugin/areas/src/areas.plugin.kts rename to game/plugin/areas/src/Areas.plugin.kts diff --git a/game/plugin/areas/src/action.kt b/game/plugin/areas/src/action.kt deleted file mode 100644 index 5b965603..00000000 --- a/game/plugin/areas/src/action.kt +++ /dev/null @@ -1,18 +0,0 @@ -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 deleted file mode 100644 index a924ca44..00000000 --- a/game/plugin/areas/src/area.kt +++ /dev/null @@ -1,94 +0,0 @@ -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.Position.component1 -import org.apollo.game.plugins.api.Position.component2 -import org.apollo.game.plugins.api.Position.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/test/AreaActionTests.kt b/game/plugin/areas/test/AreaActionTests.kt new file mode 100644 index 00000000..0fbd5e1b --- /dev/null +++ b/game/plugin/areas/test/AreaActionTests.kt @@ -0,0 +1,59 @@ +import org.apollo.game.model.Position +import org.apollo.game.model.entity.Player +import org.apollo.game.plugin.testing.junit.ApolloTestingExtension +import org.apollo.game.plugin.testing.junit.api.annotations.TestMock +import org.apollo.game.plugins.area.action +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith + +@ExtendWith(ApolloTestingExtension::class) +class AreaActionTests { + + @TestMock + lateinit var player: Player + + @Test + fun `entrance action is triggered when a player enters the area`() { + var triggered = false + val position = Position(3222, 3222) + + action("entrance_test_action", predicate = { it == player.position }) { + triggered = true + } + + player.position = position + + assertTrue(triggered) { "entrance_test_action was not triggered." } + } + + @Test + fun `inside action is triggered when a player moves inside an area`() { + player.position = Position(3222, 3222) + var triggered = false + + action("inside_test_action", x = 3220..3224, y = 3220..3224) { + triggered = true + } + + player.position = Position(3223, 3222) + + assertTrue(triggered) { "inside_test_action was not triggered." } + } + + @Test + fun `exit action is triggered when a player exits the area`() { + player.position = Position(3222, 3222) + + var triggered = false + + action("exit_test_action", predicate = { it == player.position }) { + triggered = true + } + + player.position = Position(3221, 3221) + + assertTrue(triggered) { "exit_test_action was not triggered." } + } + +} \ No newline at end of file