Implement new listener dsl for plugins

This commit is contained in:
Major
2019-07-22 02:34:28 +01:00
parent 11cee7e0f2
commit 0405639ed1
13 changed files with 457 additions and 118 deletions
@@ -0,0 +1,29 @@
package org.apollo.game.plugin.kotlin
import org.apollo.game.command.Command
import org.apollo.game.command.CommandListener
import org.apollo.game.model.World
import org.apollo.game.model.entity.Player
import org.apollo.game.model.entity.setting.PrivilegeLevel
/**
* A handler for [Command]s.
*/
class KotlinCommandHandler(
val world: World,
val command: String,
privileges: PrivilegeLevel
) : KotlinPlayerHandlerProxyTrait<Command>, CommandListener(privileges) {
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)
}
}
@@ -0,0 +1,35 @@
package org.apollo.game.plugin.kotlin
import org.apollo.game.message.handler.MessageHandler
import org.apollo.game.model.World
import org.apollo.game.model.entity.Player
import org.apollo.game.plugin.PluginContext
import org.apollo.net.message.Message
import kotlin.reflect.KClass
class KotlinMessageHandler<T : ListenableContext, F : Message>(
world: World,
private val listenable: MessageListenable<T, F>,
private val callback: T.() -> Unit
) : MessageHandler<F>(world) {
override fun handle(player: Player, message: F) {
val context = listenable.from(player, message)
context.callback()
}
}
/**
* A handler for [Message]s.
*/
@Deprecated("To be removed")
class OldKotlinMessageHandler<T : Message>(val world: World, val context: PluginContext, val type: KClass<T>) :
KotlinPlayerHandlerProxyTrait<T>, MessageHandler<T>(world) {
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)
}
@@ -0,0 +1,31 @@
package org.apollo.game.plugin.kotlin
import org.apollo.game.model.entity.Player
/**
* A proxy interface for any handler that operates on [Player]s.
*/
@Deprecated("To be removed")
interface KotlinPlayerHandlerProxyTrait<S : Any> {
var callback: S.(Player) -> Unit
var predicate: S.() -> Boolean
fun register()
fun where(predicate: S.() -> Boolean): KotlinPlayerHandlerProxyTrait<S> {
this.predicate = predicate
return this
}
fun then(callback: S.(Player) -> Unit) {
this.callback = callback
this.register()
}
fun handleProxy(player: Player, subject: S) {
if (subject.predicate()) {
subject.callback(player)
}
}
}
@@ -1,157 +1,112 @@
package org.apollo.game.plugin.kotlin
import kotlin.reflect.KClass
import kotlin.script.experimental.annotations.KotlinScript
import org.apollo.game.command.Command
import org.apollo.game.command.CommandListener
import org.apollo.game.message.handler.MessageHandler
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
import org.apollo.net.message.Message
import kotlin.reflect.KClass
import kotlin.script.experimental.annotations.KotlinScript
@KotlinScript("Apollo Plugin Script", fileExtension = "plugin.kts")
abstract class KotlinPluginScript(private var world: World, val context: PluginContext) {
var startListener: (World) -> Unit = { _ -> }
var stopListener: (World) -> Unit = { _ -> }
abstract class KotlinPluginScript(var world: World, val context: PluginContext) {
/**
* Creates a [MessageHandler].
*/
fun <T : Message> on(type: () -> KClass<T>) = KotlinMessageHandler(world, context, type.invoke())
private var startListener: (World) -> Unit = { _ -> }
/**
* Create an [EventListener] for a [PlayerEvent].
*/
fun <T : PlayerEvent> on_player_event(type: () -> KClass<T>) = KotlinPlayerEventHandler(world, type.invoke())
private var stopListener: (World) -> Unit = { _ -> }
/**
* Create an [EventListener] for an [Event].
*/
fun <T : Event> on_event(type: () -> KClass<T>) = KotlinEventHandler(world, type.invoke())
fun <T : ListenableContext, F : Any> on(listenable: Listenable<T, F>, callback: T.() -> Unit) {
// Smart-casting/type-inference is completely broken in this function in intelliJ, so assign to otherwise
// pointless `l` values for now.
return when (listenable) {
is MessageListenable -> {
@Suppress("UNCHECKED_CAST")
val l = listenable as MessageListenable<T, Message>
val handler = KotlinMessageHandler(world, l, callback)
context.addMessageHandler(l.type.java, handler)
}
is PlayerEventListenable -> {
@Suppress("UNCHECKED_CAST")
val l = listenable as PlayerEventListenable<T, PlayerEvent>
world.listenFor(l.type.java) { event ->
val context = l.from(event)
context.callback()
}
}
is EventListenable -> {
@Suppress("UNCHECKED_CAST")
val l = listenable as EventListenable<T, Event>
world.listenFor(l.type.java) { event ->
val context = l.from(event)
context.callback()
}
}
}
}
/**
* 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 on_command(command: String, privileges: PrivilegeLevel): KotlinCommandHandler { // TODO what to do with this?
return KotlinCommandHandler(world, command, privileges)
}
/**
* Creates a [MessageHandler].
*/
@Deprecated("Use new on(Type) listener")
fun <T : Message> on(type: () -> KClass<T>): OldKotlinMessageHandler<T> {
return OldKotlinMessageHandler(world, context, type())
}
/**
* Create an [EventListener] for a [PlayerEvent].
*/
@Deprecated("Use new on(Type) listener")
fun <T : PlayerEvent> on_player_event(type: () -> KClass<T>): OldKotlinPlayerEventHandler<T> {
return OldKotlinPlayerEventHandler(world, type())
}
/**
* Create an [EventListener] for an [Event].
*/
@Deprecated("Use new on(Type) listener")
fun <T : Event> on_event(type: () -> KClass<T>): OldKotlinEventHandler<T> {
return OldKotlinEventHandler(world, type())
}
/**
* Create a [ButtonMessage] [MessageHandler] for the given [id].
*/
fun on_button(id: Int) = on { ButtonMessage::class }.where { widgetId == id }
@Deprecated("Use new on(Type) listener")
fun on_button(id: Int): KotlinPlayerHandlerProxyTrait<ButtonMessage> {
return on { ButtonMessage::class }.where { widgetId == id }
}
fun start(callback: (World) -> Unit) {
this.startListener = callback
startListener = callback
}
fun stop(callback: (World) -> Unit) {
this.stopListener = callback
stopListener = callback
}
fun doStart(world: World) {
this.startListener.invoke(world)
startListener(world)
}
fun doStop(world: World) {
this.stopListener.invoke(world)
}
}
/**
* A proxy interface for any handler that operates on [Player]s.
*/
interface KotlinPlayerHandlerProxyTrait<S : Any> {
var callback: S.(Player) -> Unit
var predicate: S.() -> Boolean
fun register()
fun where(predicate: S.() -> Boolean): KotlinPlayerHandlerProxyTrait<S> {
this.predicate = predicate
return this
stopListener(world)
}
fun then(callback: S.(Player) -> Unit) {
this.callback = callback
this.register()
}
fun handleProxy(player: Player, subject: S) {
if (subject.predicate()) {
subject.callback(player)
}
}
}
/**
* A handler for [PlayerEvent]s.
*/
class KotlinPlayerEventHandler<T : PlayerEvent>(val world: World, val type: KClass<T>) :
KotlinPlayerHandlerProxyTrait<T>, EventListener<T> {
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)
}
/**
* A handler for [Event]s.
*/
class KotlinEventHandler<S : Event>(val world: World, val type: KClass<S>) : EventListener<S> {
private var callback: S.() -> Unit = {}
private var predicate: S.() -> Boolean = { true }
fun where(predicate: S.() -> Boolean): KotlinEventHandler<S> {
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<T : Message>(val world: World, val context: PluginContext, val type: KClass<T>) :
KotlinPlayerHandlerProxyTrait<T>, MessageHandler<T>(world) {
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)
}
/**
* A handler for [Command]s.
*/
class KotlinCommandHandler(val world: World, val command: String, privileges: PrivilegeLevel) :
KotlinPlayerHandlerProxyTrait<Command>, CommandListener(privileges) {
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)
}
}
@@ -0,0 +1,32 @@
package org.apollo.game.plugin.kotlin
import org.apollo.game.model.entity.Player
import org.apollo.game.model.event.Event
import org.apollo.game.model.event.PlayerEvent
import org.apollo.net.message.Message
import kotlin.reflect.KClass
/**
* A game occurrence that can be listened to.
*/
sealed class Listenable<T : ListenableContext, F : Any> {
abstract val type: KClass<F>
}
abstract class EventListenable<T : ListenableContext, F : Event> : Listenable<T, F>() {
abstract fun from(event: F): T
}
abstract class MessageListenable<T : ListenableContext, F : Message> : Listenable<T, F>() {
abstract fun from(player: Player, message: F): T
}
abstract class PlayerEventListenable<T : ListenableContext, F : PlayerEvent> : EventListenable<T, F>() {
abstract fun from(player: Player, event: F): T
override fun from(event: F): T {
return from(event.player, event)
}
}
@@ -0,0 +1,6 @@
package org.apollo.game.plugin.kotlin
/**
* Contains contextual information for a [Listenable].
*/
interface ListenableContext
@@ -0,0 +1,34 @@
package org.apollo.game.plugin.kotlin
import org.apollo.game.model.World
import org.apollo.game.model.event.Event
import org.apollo.game.model.event.EventListener
import kotlin.reflect.KClass
/**
* A handler for [Event]s.
*/
@Deprecated("To be removed")
class OldKotlinEventHandler<S : Event>(val world: World, val type: KClass<S>) : EventListener<S> {
private var callback: S.() -> Unit = {}
private var predicate: S.() -> Boolean = { true }
fun where(predicate: S.() -> Boolean): OldKotlinEventHandler<S> {
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)
}
@@ -0,0 +1,21 @@
package org.apollo.game.plugin.kotlin
import org.apollo.game.model.World
import org.apollo.game.model.entity.Player
import org.apollo.game.model.event.EventListener
import org.apollo.game.model.event.PlayerEvent
import kotlin.reflect.KClass
/**
* A handler for [PlayerEvent]s.
*/
@Deprecated("To be removed")
class OldKotlinPlayerEventHandler<T : PlayerEvent>(val world: World, val type: KClass<T>) :
KotlinPlayerHandlerProxyTrait<T>, EventListener<T> {
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)
}
@@ -0,0 +1,33 @@
package org.apollo.game.plugin.kotlin.action
import org.apollo.game.model.entity.Player
import org.apollo.game.plugin.kotlin.KotlinPluginScript
import org.apollo.game.plugin.kotlin.ListenableContext
import org.apollo.game.plugin.kotlin.MessageListenable
import org.apollo.net.message.Message
/**
* Registers a listener for an action event that uses the given [option] (case-insensitive).
*
* ```
* on(PlayerAction, option = "Trade") {
* player.sendMessage("Sending trade request...")
* }
* ```
*/
inline fun <T : ActionListenableContext, reified F : Message> KotlinPluginScript.on(
listenable: MessageListenable<T, F>,
option: String,
crossinline callback: T.() -> Unit
) {
on(listenable) {
if (this.option.equals(option, ignoreCase = true)) {
callback()
}
}
}
interface ActionListenableContext : ListenableContext {
val option: String
val player: Player
}
@@ -0,0 +1,14 @@
package org.apollo.game.plugin.kotlin.action.obj
import org.apollo.game.model.entity.obj.GameObject
/**
* An object that can be interacted with.
*/
interface InteractiveObject {
val id: Int
fun instanceOf(other: GameObject): Boolean // TODO alternative name?
}
@@ -0,0 +1,79 @@
package org.apollo.game.plugin.kotlin.action.obj
import org.apollo.cache.def.ObjectDefinition
import org.apollo.game.message.handler.MessageHandler
import org.apollo.game.message.impl.ObjectActionMessage
import org.apollo.game.model.World
import org.apollo.game.model.entity.EntityType
import org.apollo.game.model.entity.Player
import org.apollo.game.model.entity.obj.GameObject
import org.apollo.game.plugin.kotlin.KotlinPluginScript
import org.apollo.game.plugin.kotlin.action.on
@Deprecated("example function, remove")
fun KotlinPluginScript.x() {
on(ObjectAction, option = "Trade", objects = listOf()) {
}
on(ObjectAction, option = "Trade") {
}
}
/**
* Registers a listener for [ObjectActionMessage]s that occur on any of the given [InteractiveObject]s using the
* given [option] (case-insensitive).
*
* ```
* on(ObjectAction, option = "Open", objects = DOORS.toList()) {
* player.sendMessage("You open the door")
* }
* ```
*/
fun <T : InteractiveObject> KotlinPluginScript.on(
listenable: ObjectActionListenable,
option: String,
objects: List<T>,
callback: ObjectAction<T>.() -> Unit
) {
if (objects.isEmpty()) {
on(listenable) {
if (this.option.equals(option, ignoreCase = true)) {
@Suppress("UNCHECKED_CAST") (this as ObjectAction<T>)
callback()
}
}
} else {
val handler = ObjectActionMessageHandler(world, listenable, objects, option, callback)
context.addMessageHandler(listenable.type.java, handler)
}
}
/**
* A [MessageHandler]
*/
class ObjectActionMessageHandler<T : InteractiveObject>(
world: World,
private val listenable: ObjectActionListenable,
private val objects: List<T>,
private val option: String,
private val callback: ObjectAction<T>.() -> Unit
) : MessageHandler<ObjectActionMessage>(world) {
override fun handle(player: Player, message: ObjectActionMessage) {
val def = ObjectDefinition.lookup(message.id)
val selectedAction = def.menuActions[message.option]
val obj = player.world.regionRepository
.fromPosition(message.position)
.getEntities<GameObject>(message.position, EntityType.DYNAMIC_OBJECT, EntityType.STATIC_OBJECT)
.first { it.definition == def }
if (option.equals(selectedAction, ignoreCase = true) && objects.any { it.instanceOf(obj) }) {
val context = listenable.from(player, message, objects)
context.callback()
}
}
}
@@ -0,0 +1,53 @@
package org.apollo.game.plugin.kotlin.action.obj
import org.apollo.cache.def.ObjectDefinition
import org.apollo.game.message.impl.ObjectActionMessage
import org.apollo.game.model.entity.EntityType
import org.apollo.game.model.entity.Player
import org.apollo.game.model.entity.obj.GameObject
import org.apollo.game.plugin.kotlin.action.ActionListenableContext
/**
* An interaction between a [Player] and an [interactive] [GameObject].
*/
class ObjectAction<T : InteractiveObject?>(
override val option: String,
override val player: Player,
val target: GameObject,
val interactive: T
) : ActionListenableContext {
companion object : ObjectActionListenable() {
override fun from(player: Player, message: ObjectActionMessage): ObjectAction<*> {
val def = ObjectDefinition.lookup(message.id)
val selectedAction = def.menuActions[message.option]
val obj = player.world.regionRepository
.fromPosition(message.position)
.getEntities<GameObject>(message.position, EntityType.DYNAMIC_OBJECT, EntityType.STATIC_OBJECT)
.first { it.definition == def }
return ObjectAction(selectedAction, player, obj, null)
}
override val type = ObjectActionMessage::class
override fun <T : InteractiveObject> from(
player: Player,
other: ObjectActionMessage,
objects: List<T>
): ObjectAction<T> {
val def = ObjectDefinition.lookup(other.id)
val selectedAction = def.menuActions[other.option]
val obj = player.world.regionRepository
.fromPosition(other.position)
.getEntities<GameObject>(other.position, EntityType.DYNAMIC_OBJECT, EntityType.STATIC_OBJECT)
.first { it.definition == def }
return ObjectAction(selectedAction, player, obj, objects.first { it.instanceOf(obj) })
}
}
}
@@ -0,0 +1,17 @@
package org.apollo.game.plugin.kotlin.action.obj
import org.apollo.game.message.impl.ObjectActionMessage
import org.apollo.game.model.entity.Player
import org.apollo.game.plugin.kotlin.MessageListenable
abstract class ObjectActionListenable : MessageListenable<ObjectAction<*>, ObjectActionMessage>() {
override val type = ObjectActionMessage::class
abstract fun <T : InteractiveObject> from(
player: Player,
other: ObjectActionMessage,
objects: List<T>
): ObjectAction<T>
}