diff --git a/game/plugin/api/src/org/apollo/game/plugins/api/position.kt b/game/plugin/api/src/org/apollo/game/plugins/api/position.kt new file mode 100644 index 00000000..ffd3534d --- /dev/null +++ b/game/plugin/api/src/org/apollo/game/plugins/api/position.kt @@ -0,0 +1,9 @@ +package org.apollo.game.plugins.api + +import org.apollo.game.model.Position + +// Support destructuring a Position into its components. + +operator fun Position.component1(): Int = x +operator fun Position.component2(): Int = y +operator fun Position.component3(): Int = height 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) + } + } + } diff --git a/game/src/main/kotlin/org/apollo/game/plugin/kotlin/KotlinPluginScript.kt b/game/src/main/kotlin/org/apollo/game/plugin/kotlin/KotlinPluginScript.kt index 357b17ef..a1b55151 100644 --- a/game/src/main/kotlin/org/apollo/game/plugin/kotlin/KotlinPluginScript.kt +++ b/game/src/main/kotlin/org/apollo/game/plugin/kotlin/KotlinPluginScript.kt @@ -7,6 +7,7 @@ import org.apollo.game.message.impl.ButtonMessage import org.apollo.game.model.World import org.apollo.game.model.entity.Player import org.apollo.game.model.entity.setting.PrivilegeLevel +import org.apollo.game.model.event.Event import org.apollo.game.model.event.EventListener import org.apollo.game.model.event.PlayerEvent import org.apollo.game.plugin.PluginContext @@ -15,103 +16,151 @@ import kotlin.reflect.KClass import kotlin.script.templates.ScriptTemplateDefinition @ScriptTemplateDefinition( - scriptFilePattern = ".*\\.plugin\\.kts" + scriptFilePattern = ".*\\.plugin\\.kts" ) abstract class KotlinPluginScript(private var world: World, val context: PluginContext) { - var startListener: (World) -> Unit = { _ -> }; - var stopListener: (World) -> Unit = { _ -> }; + var startListener: (World) -> Unit = { _ -> } + var stopListener: (World) -> Unit = { _ -> } - /** - * Create a new [MessageHandler]. - */ - fun on(type: () -> KClass) = KotlinMessageHandler(world, context, type.invoke()) + /** + * Creates a [MessageHandler]. + */ + fun on(type: () -> KClass) = KotlinMessageHandler(world, context, type.invoke()) - /** - * Create a new [EventListener] for a type of [PlayerEvent]. - */ - fun on_player_event(type: () -> KClass) = KotlinPlayerEventHandler(world, type.invoke()) + /** + * Create an [EventListener] for a [PlayerEvent]. + */ + fun on_player_event(type: () -> KClass) = KotlinPlayerEventHandler(world, type.invoke()) - /** - * Create a new [CommandHandler] for the given _command_ name, which only players with a [PrivelegeLevel] - * of _privileges_ and above can use. - */ - fun on_command(command: String, privileges: PrivilegeLevel) = KotlinCommandHandler(world, command, privileges) + /** + * Create an [EventListener] for an [Event]. + */ + fun on_event(type: () -> KClass) = KotlinEventHandler(world, type.invoke()) - /** - * Create a new [ButtonMessage] [MessageHandler] for the given _button_ id. - */ - fun on_button(button: Int) = on { ButtonMessage::class }.where { widgetId == button } + /** + * Create a [CommandListener] for the given [command] name, which only players with a [PrivilegeLevel] + * of [privileges] and above can use. + */ + fun on_command(command: String, privileges: PrivilegeLevel) = KotlinCommandHandler(world, command, privileges) - fun start(callback: (World) -> Unit) { - this.startListener = callback - } + /** + * Create a [ButtonMessage] [MessageHandler] for the given [id]. + */ + fun on_button(id: Int) = on { ButtonMessage::class }.where { widgetId == id } - fun stop(callback: (World) -> Unit) { - this.stopListener = callback - } + fun start(callback: (World) -> Unit) { + this.startListener = callback + } - fun doStart(world: World) { - this.startListener.invoke(world) - } + fun stop(callback: (World) -> Unit) { + this.stopListener = callback + } + + fun doStart(world: World) { + this.startListener.invoke(world) + } + + fun doStop(world: World) { + this.stopListener.invoke(world) + } - fun doStop(world: World) { - this.stopListener.invoke(world) - } } +/** + * A proxy interface for any handler that operates on [Player]s. + */ interface KotlinPlayerHandlerProxyTrait { - var callback: S.(Player) -> Unit - var predicate: S.() -> Boolean + var callback: S.(Player) -> Unit + var predicate: S.() -> Boolean - fun where(predicate: S.() -> Boolean): KotlinPlayerHandlerProxyTrait { - this.predicate = predicate - return this - } + fun register() - fun then(callback: S.(Player) -> Unit) { - this.callback = callback - this.register() - } + fun where(predicate: S.() -> Boolean): KotlinPlayerHandlerProxyTrait { + this.predicate = predicate + return this + } - fun register() + fun then(callback: S.(Player) -> Unit) { + this.callback = callback + this.register() + } + + + fun handleProxy(player: Player, subject: S) { + if (subject.predicate()) { + subject.callback(player) + } + } - fun handleProxy(player: Player, subject: S) { - if (subject.predicate()) { - subject.callback(player) - } - } } +/** + * A handler for [PlayerEvent]s. + */ class KotlinPlayerEventHandler(val world: World, val type: KClass) : - KotlinPlayerHandlerProxyTrait, EventListener { + KotlinPlayerHandlerProxyTrait, EventListener { - override var callback: T.(Player) -> Unit = {} - override var predicate: T.() -> Boolean = { true } + override var callback: T.(Player) -> Unit = {} + override var predicate: T.() -> Boolean = { true } - override fun handle(event: T) = handleProxy(event.player, event) - override fun register() = world.listenFor(type.java, this) + override fun handle(event: T) = handleProxy(event.player, event) + override fun register() = world.listenFor(type.java, this) } +/** + * A handler for [Event]s. + */ +class KotlinEventHandler(val world: World, val type: KClass) : EventListener { + + private var callback: S.() -> Unit = {} + private var predicate: S.() -> Boolean = { true } + + fun where(predicate: S.() -> Boolean): KotlinEventHandler { + this.predicate = predicate + return this + } + + fun then(callback: S.() -> Unit) { + this.callback = callback + this.register() + } + + override fun handle(event: S) { + if (event.predicate()) { + event.callback() + } + } + + fun register() = world.listenFor(type.java, this) + +} + +/** + * A handler for [Message]s. + */ class KotlinMessageHandler(val world: World, val context: PluginContext, val type: KClass) : - KotlinPlayerHandlerProxyTrait, MessageHandler(world) { + KotlinPlayerHandlerProxyTrait, MessageHandler(world) { - override var callback: T.(Player) -> Unit = {} - override var predicate: T.() -> Boolean = { true } + override var callback: T.(Player) -> Unit = {} + override var predicate: T.() -> Boolean = { true } - override fun handle(player: Player, message: T) = handleProxy(player, message) - override fun register() = context.addMessageHandler(type.java, this) + override fun handle(player: Player, message: T) = handleProxy(player, message) + override fun register() = context.addMessageHandler(type.java, this) } +/** + * A handler for [Command]s. + */ class KotlinCommandHandler(val world: World, val command: String, privileges: PrivilegeLevel) : - KotlinPlayerHandlerProxyTrait, CommandListener(privileges) { + KotlinPlayerHandlerProxyTrait, CommandListener(privileges) { - override var callback: Command.(Player) -> Unit = {} - override var predicate: Command.() -> Boolean = { true } + override var callback: Command.(Player) -> Unit = {} + override var predicate: Command.() -> Boolean = { true } - override fun execute(player: Player, command: Command) = handleProxy(player, command) - override fun register() = world.commandDispatcher.register(command, this) + override fun execute(player: Player, command: Command) = handleProxy(player, command) + override fun register() = world.commandDispatcher.register(command, this) } diff --git a/game/src/main/kotlin/stub.kt b/game/src/main/kotlin/stub.kt index c236c3a3..0f49676b 100644 --- a/game/src/main/kotlin/stub.kt +++ b/game/src/main/kotlin/stub.kt @@ -7,19 +7,19 @@ */ import org.apollo.game.command.Command -import org.apollo.game.message.handler.MessageHandlerChainSet import org.apollo.game.message.impl.ButtonMessage import org.apollo.game.model.World -import org.apollo.game.model.area.RegionRepository -import org.apollo.game.model.entity.* import org.apollo.game.model.entity.setting.PrivilegeLevel +import org.apollo.game.model.event.Event import org.apollo.game.model.event.PlayerEvent -import org.apollo.game.plugin.kotlin.* +import org.apollo.game.plugin.kotlin.KotlinEventHandler +import org.apollo.game.plugin.kotlin.KotlinPlayerHandlerProxyTrait import org.apollo.net.message.Message import kotlin.reflect.KClass fun on(type: () -> KClass): KotlinPlayerHandlerProxyTrait = null!! fun on_player_event(type: () -> KClass): KotlinPlayerHandlerProxyTrait = null!! +fun on_event(type: () -> KClass): KotlinEventHandler = null!! fun on_command(command: String, privileges: PrivilegeLevel): KotlinPlayerHandlerProxyTrait = null!! fun on_button(button: Int): KotlinPlayerHandlerProxyTrait = null!!