Skip to content

Commit 397d9b2

Browse files
authored
Merge pull request #366 from apollo-rsps/kotlin-experiments-areas
Port the area actions plugin
2 parents 6e3ddff + ce1bcba commit 397d9b2

File tree

7 files changed

+274
-75
lines changed

7 files changed

+274
-75
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package org.apollo.game.plugins.api
2+
3+
import org.apollo.game.model.Position
4+
5+
// Support destructuring a Position into its components.
6+
7+
operator fun Position.component1(): Int = x
8+
operator fun Position.component2(): Int = y
9+
operator fun Position.component3(): Int = height

game/plugin/areas/build.gradle

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
plugin {
2+
name = "Area listeners"
3+
description = "Enables plugins to listen on mobs entering, moving inside of, or leaving a rectangular area."
4+
authors = ["Major"]
5+
dependencies = ["api"]
6+
}

game/plugin/areas/src/action.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package org.apollo.game.plugins.area
2+
3+
/**
4+
* Defines an area action using the DSL.
5+
*/
6+
fun action(@Suppress("UNUSED_PARAMETER") name: String, builder: ActionBuilder.() -> Unit) {
7+
val listener = ActionBuilder()
8+
builder(listener)
9+
10+
actions.add(listener.build())
11+
}
12+
13+
/**
14+
* The [Set] of ([Area], [AreaAction]) [Pair]s.
15+
*/
16+
val actions = mutableSetOf<Pair<Area, AreaAction>>()
17+
18+
class AreaAction(val entrance: AreaListener, val inside: AreaListener, val exit: AreaListener)

game/plugin/areas/src/area.kt

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package org.apollo.game.plugins.area
2+
3+
import org.apollo.game.model.Position
4+
import org.apollo.game.model.entity.Player
5+
import org.apollo.game.plugins.api.component1
6+
import org.apollo.game.plugins.api.component2
7+
import org.apollo.game.plugins.api.component3
8+
9+
/**
10+
* An area in the game world.
11+
*/
12+
interface Area {
13+
14+
/**
15+
* Returns whether or not the specified [Position] is inside this [Area].
16+
*/
17+
operator fun contains(position: Position): Boolean
18+
19+
}
20+
21+
private class RectangularArea(val x: IntRange, val y: IntRange, val height: Int) : Area {
22+
23+
override operator fun contains(position: Position): Boolean {
24+
val (x, y, z) = position
25+
return x in this.x && y in this.y && z == height
26+
}
27+
28+
}
29+
30+
/**
31+
* A typealias for a function that is invoked when a player enters, moves inside of, or exits an [Area].
32+
*/
33+
internal typealias AreaListener = Player.(Position) -> Unit
34+
35+
/**
36+
* A builder for ([Area], [AreaAction]) [Pair]s.
37+
*/
38+
class ActionBuilder {
39+
40+
private var area: Area? = null
41+
42+
private var entrance: AreaListener = { }
43+
44+
private var inside: AreaListener = { }
45+
46+
private var exit: AreaListener = { }
47+
48+
/**
49+
* Places the contents of this builder into an ([Area], [AreaAction]) [Pair].
50+
*/
51+
fun build(): Pair<Area, AreaAction> {
52+
val area = area ?: throw UnsupportedOperationException("Area must be specified.")
53+
return Pair(area, AreaAction(entrance, inside, exit))
54+
}
55+
56+
/**
57+
* Sets the [Area] to listen for movement in.
58+
*/
59+
fun area(contains: (Position) -> Boolean) {
60+
this.area = object : Area {
61+
override fun contains(position: Position): Boolean = contains.invoke(position)
62+
}
63+
}
64+
65+
/**
66+
* Sets the [Area] to listen for movement in. Note that [IntRange]s are (inclusive, _exclusive_), i.e. the upper
67+
* bound is exclusive.
68+
*/
69+
fun area(x: IntRange, y: IntRange, height: Int = 0) {
70+
this.area = RectangularArea(x, y, height)
71+
}
72+
73+
/**
74+
* Executes the specified [listener] when a player enters the related [Area].
75+
*/
76+
fun entrance(listener: AreaListener) {
77+
this.entrance = listener
78+
}
79+
80+
/**
81+
* Executes the specified [listener] when a player moves around inside the related [Area].
82+
*/
83+
fun inside(listener: AreaListener) {
84+
this.inside = listener
85+
}
86+
87+
/**
88+
* Executes the specified [listener] when a player moves exits the related [Area].
89+
*/
90+
fun exit(listener: AreaListener) {
91+
this.exit = listener
92+
}
93+
94+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
2+
import org.apollo.game.model.entity.Player
3+
import org.apollo.game.model.event.impl.MobPositionUpdateEvent
4+
import org.apollo.game.plugins.area.actions
5+
6+
/**
7+
* Intercepts the [MobPositionUpdateEvent] and invokes area actions if necessary.
8+
*/
9+
on_event { MobPositionUpdateEvent::class }
10+
.where { mob is Player }
11+
.then {
12+
for ((area, action) in actions) {
13+
if (mob.position in area) {
14+
if (next in area) {
15+
action.inside(mob as Player, next)
16+
} else {
17+
action.exit(mob as Player, next)
18+
}
19+
} else if (next in area) {
20+
action.entrance(mob as Player, next)
21+
}
22+
}
23+
}

game/src/main/kotlin/org/apollo/game/plugin/kotlin/KotlinPluginScript.kt

Lines changed: 120 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import org.apollo.game.message.impl.ButtonMessage
77
import org.apollo.game.model.World
88
import org.apollo.game.model.entity.Player
99
import org.apollo.game.model.entity.setting.PrivilegeLevel
10+
import org.apollo.game.model.event.Event
1011
import org.apollo.game.model.event.EventListener
1112
import org.apollo.game.model.event.PlayerEvent
1213
import org.apollo.game.plugin.PluginContext
@@ -15,103 +16,151 @@ import kotlin.reflect.KClass
1516
import kotlin.script.templates.ScriptTemplateDefinition
1617

1718
@ScriptTemplateDefinition(
18-
scriptFilePattern = ".*\\.plugin\\.kts"
19+
scriptFilePattern = ".*\\.plugin\\.kts"
1920
)
2021
abstract class KotlinPluginScript(private var world: World, val context: PluginContext) {
21-
var startListener: (World) -> Unit = { _ -> };
22-
var stopListener: (World) -> Unit = { _ -> };
23-
24-
/**
25-
* Create a new [MessageHandler].
26-
*/
27-
fun <T : Message> on(type: () -> KClass<T>) = KotlinMessageHandler<T>(world, context, type.invoke())
28-
29-
/**
30-
* Create a new [EventListener] for a type of [PlayerEvent].
31-
*/
32-
fun <T : PlayerEvent> on_player_event(type: () -> KClass<T>) = KotlinPlayerEventHandler(world, type.invoke())
33-
34-
/**
35-
* Create a new [CommandHandler] for the given _command_ name, which only players with a [PrivelegeLevel]
36-
* of _privileges_ and above can use.
37-
*/
38-
fun on_command(command: String, privileges: PrivilegeLevel) = KotlinCommandHandler(world, command, privileges)
39-
40-
/**
41-
* Create a new [ButtonMessage] [MessageHandler] for the given _button_ id.
42-
*/
43-
fun on_button(button: Int) = on { ButtonMessage::class }.where { widgetId == button }
44-
45-
fun start(callback: (World) -> Unit) {
46-
this.startListener = callback
47-
}
48-
49-
fun stop(callback: (World) -> Unit) {
50-
this.stopListener = callback
51-
}
52-
53-
fun doStart(world: World) {
54-
this.startListener.invoke(world)
55-
}
56-
57-
fun doStop(world: World) {
58-
this.stopListener.invoke(world)
59-
}
22+
var startListener: (World) -> Unit = { _ -> }
23+
var stopListener: (World) -> Unit = { _ -> }
24+
25+
/**
26+
* Creates a [MessageHandler].
27+
*/
28+
fun <T : Message> on(type: () -> KClass<T>) = KotlinMessageHandler(world, context, type.invoke())
29+
30+
/**
31+
* Create an [EventListener] for a [PlayerEvent].
32+
*/
33+
fun <T : PlayerEvent> on_player_event(type: () -> KClass<T>) = KotlinPlayerEventHandler(world, type.invoke())
34+
35+
/**
36+
* Create an [EventListener] for an [Event].
37+
*/
38+
fun <T : Event> on_event(type: () -> KClass<T>) = KotlinEventHandler(world, type.invoke())
39+
40+
/**
41+
* Create a [CommandListener] for the given [command] name, which only players with a [PrivilegeLevel]
42+
* of [privileges] and above can use.
43+
*/
44+
fun on_command(command: String, privileges: PrivilegeLevel) = KotlinCommandHandler(world, command, privileges)
45+
46+
/**
47+
* Create a [ButtonMessage] [MessageHandler] for the given [id].
48+
*/
49+
fun on_button(id: Int) = on { ButtonMessage::class }.where { widgetId == id }
50+
51+
fun start(callback: (World) -> Unit) {
52+
this.startListener = callback
53+
}
54+
55+
fun stop(callback: (World) -> Unit) {
56+
this.stopListener = callback
57+
}
58+
59+
fun doStart(world: World) {
60+
this.startListener.invoke(world)
61+
}
62+
63+
fun doStop(world: World) {
64+
this.stopListener.invoke(world)
65+
}
66+
6067
}
6168

69+
/**
70+
* A proxy interface for any handler that operates on [Player]s.
71+
*/
6272
interface KotlinPlayerHandlerProxyTrait<S : Any> {
6373

64-
var callback: S.(Player) -> Unit
65-
var predicate: S.() -> Boolean
74+
var callback: S.(Player) -> Unit
75+
var predicate: S.() -> Boolean
76+
77+
fun register()
78+
79+
fun where(predicate: S.() -> Boolean): KotlinPlayerHandlerProxyTrait<S> {
80+
this.predicate = predicate
81+
return this
82+
}
6683

67-
fun where(predicate: S.() -> Boolean): KotlinPlayerHandlerProxyTrait<S> {
68-
this.predicate = predicate
69-
return this
70-
}
84+
fun then(callback: S.(Player) -> Unit) {
85+
this.callback = callback
86+
this.register()
87+
}
7188

72-
fun then(callback: S.(Player) -> Unit) {
73-
this.callback = callback
74-
this.register()
75-
}
7689

77-
fun register()
90+
fun handleProxy(player: Player, subject: S) {
91+
if (subject.predicate()) {
92+
subject.callback(player)
93+
}
94+
}
7895

79-
fun handleProxy(player: Player, subject: S) {
80-
if (subject.predicate()) {
81-
subject.callback(player)
82-
}
83-
}
8496
}
8597

98+
/**
99+
* A handler for [PlayerEvent]s.
100+
*/
86101
class KotlinPlayerEventHandler<T : PlayerEvent>(val world: World, val type: KClass<T>) :
87-
KotlinPlayerHandlerProxyTrait<T>, EventListener<T> {
102+
KotlinPlayerHandlerProxyTrait<T>, EventListener<T> {
103+
104+
override var callback: T.(Player) -> Unit = {}
105+
override var predicate: T.() -> Boolean = { true }
106+
107+
override fun handle(event: T) = handleProxy(event.player, event)
108+
override fun register() = world.listenFor(type.java, this)
109+
110+
}
111+
112+
/**
113+
* A handler for [Event]s.
114+
*/
115+
class KotlinEventHandler<S : Event>(val world: World, val type: KClass<S>) : EventListener<S> {
116+
117+
private var callback: S.() -> Unit = {}
118+
private var predicate: S.() -> Boolean = { true }
119+
120+
fun where(predicate: S.() -> Boolean): KotlinEventHandler<S> {
121+
this.predicate = predicate
122+
return this
123+
}
124+
125+
fun then(callback: S.() -> Unit) {
126+
this.callback = callback
127+
this.register()
128+
}
88129

89-
override var callback: T.(Player) -> Unit = {}
90-
override var predicate: T.() -> Boolean = { true }
130+
override fun handle(event: S) {
131+
if (event.predicate()) {
132+
event.callback()
133+
}
134+
}
91135

92-
override fun handle(event: T) = handleProxy(event.player, event)
93-
override fun register() = world.listenFor(type.java, this)
136+
fun register() = world.listenFor(type.java, this)
94137

95138
}
96139

140+
/**
141+
* A handler for [Message]s.
142+
*/
97143
class KotlinMessageHandler<T : Message>(val world: World, val context: PluginContext, val type: KClass<T>) :
98-
KotlinPlayerHandlerProxyTrait<T>, MessageHandler<T>(world) {
144+
KotlinPlayerHandlerProxyTrait<T>, MessageHandler<T>(world) {
99145

100-
override var callback: T.(Player) -> Unit = {}
101-
override var predicate: T.() -> Boolean = { true }
146+
override var callback: T.(Player) -> Unit = {}
147+
override var predicate: T.() -> Boolean = { true }
102148

103-
override fun handle(player: Player, message: T) = handleProxy(player, message)
104-
override fun register() = context.addMessageHandler(type.java, this)
149+
override fun handle(player: Player, message: T) = handleProxy(player, message)
150+
override fun register() = context.addMessageHandler(type.java, this)
105151

106152
}
107153

154+
/**
155+
* A handler for [Command]s.
156+
*/
108157
class KotlinCommandHandler(val world: World, val command: String, privileges: PrivilegeLevel) :
109-
KotlinPlayerHandlerProxyTrait<Command>, CommandListener(privileges) {
158+
KotlinPlayerHandlerProxyTrait<Command>, CommandListener(privileges) {
110159

111-
override var callback: Command.(Player) -> Unit = {}
112-
override var predicate: Command.() -> Boolean = { true }
160+
override var callback: Command.(Player) -> Unit = {}
161+
override var predicate: Command.() -> Boolean = { true }
113162

114-
override fun execute(player: Player, command: Command) = handleProxy(player, command)
115-
override fun register() = world.commandDispatcher.register(command, this)
163+
override fun execute(player: Player, command: Command) = handleProxy(player, command)
164+
override fun register() = world.commandDispatcher.register(command, this)
116165

117166
}

0 commit comments

Comments
 (0)