From 9a8ed0d7a9099b6e17fe325e8f4d93950ecdb921 Mon Sep 17 00:00:00 2001 From: Gary Tierney Date: Sat, 24 Jun 2017 17:40:08 +0100 Subject: [PATCH] Port the walkto and following plugins to Kotlin --- .../java/org/apollo/game/model/Direction.java | 2 +- .../game/model/entity/path/Heuristic.java | 2 +- .../game/plugin/kotlin/KotlinPluginScript.kt | 79 +++++++++++-------- game/src/main/kotlin/stub.kt | 10 ++- game/src/plugins/entity/following/meta.toml | 8 ++ .../plugins/entity/following/src/following.kt | 52 ++++++++++++ .../entity/following/src/following.plugin.kts | 11 +++ .../plugins/entity/player-action/meta.toml | 8 ++ .../entity/player-action/src/player_action.kt | 35 ++++++++ .../src/player_action.plugin.kts | 18 +++++ game/src/plugins/entity/walk-to/meta.toml | 4 + .../src/plugins/entity/walk-to/src/walk_to.kt | 59 ++++++++++++++ 12 files changed, 252 insertions(+), 36 deletions(-) create mode 100644 game/src/plugins/entity/following/meta.toml create mode 100644 game/src/plugins/entity/following/src/following.kt create mode 100644 game/src/plugins/entity/following/src/following.plugin.kts create mode 100644 game/src/plugins/entity/player-action/meta.toml create mode 100644 game/src/plugins/entity/player-action/src/player_action.kt create mode 100644 game/src/plugins/entity/player-action/src/player_action.plugin.kts create mode 100644 game/src/plugins/entity/walk-to/meta.toml create mode 100644 game/src/plugins/entity/walk-to/src/walk_to.kt diff --git a/game/src/main/java/org/apollo/game/model/Direction.java b/game/src/main/java/org/apollo/game/model/Direction.java index dc03c558..d1a13ffb 100644 --- a/game/src/main/java/org/apollo/game/model/Direction.java +++ b/game/src/main/java/org/apollo/game/model/Direction.java @@ -85,7 +85,7 @@ public enum Direction { int deltaX = next.getX() - current.getX(); int deltaY = next.getY() - current.getY(); - return fromDeltas(deltaX, deltaY); + return fromDeltas(Integer.signum(deltaX), Integer.signum(deltaY)); } /** diff --git a/game/src/main/java/org/apollo/game/model/entity/path/Heuristic.java b/game/src/main/java/org/apollo/game/model/entity/path/Heuristic.java index 468c65bd..45390aaf 100644 --- a/game/src/main/java/org/apollo/game/model/entity/path/Heuristic.java +++ b/game/src/main/java/org/apollo/game/model/entity/path/Heuristic.java @@ -7,7 +7,7 @@ import org.apollo.game.model.Position; * * @author Major */ -abstract class Heuristic { +public abstract class Heuristic { /** * Estimates the value for this heuristic. 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 e186a700..f3b9cf3b 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 @@ -6,6 +6,8 @@ import org.apollo.game.message.handler.MessageHandler 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.EventListener +import org.apollo.game.model.event.PlayerEvent import org.apollo.game.plugin.PluginContext import org.apollo.net.message.Message import kotlin.reflect.KClass @@ -18,19 +20,18 @@ abstract class KotlinPluginScript(private var world: World, val context: PluginC var startListener: (World) -> Unit = { _ -> }; var stopListener: (World) -> Unit = { _ -> }; - protected fun on(type: () -> KClass): KotlinMessageHandler { - return KotlinMessageHandler(world, context, type.invoke()) - } + fun on(type: () -> KClass) = KotlinMessageHandler(world, context, type.invoke()) - protected fun on_command(command: String, privileges: PrivilegeLevel): KotlinCommandHandler { - return KotlinCommandHandler(world, command, privileges) - } + fun on_player_event(type: () -> KClass) = KotlinPlayerEventHandler(world, type.invoke()) - protected fun start(callback: (World) -> Unit) { + fun on_command(command: String, privileges: PrivilegeLevel) = KotlinCommandHandler(world, command, privileges) + + + fun start(callback: (World) -> Unit) { this.startListener = callback } - protected fun stop(callback: (World) -> Unit) { + fun stop(callback: (World) -> Unit) { this.stopListener = callback } @@ -43,45 +44,59 @@ abstract class KotlinPluginScript(private var world: World, val context: PluginC } } -class KotlinMessageHandler(val world: World, val context: PluginContext, val type: KClass) : MessageHandler(world) { +interface KotlinPlayerHandlerProxyTrait { - override fun handle(player: Player, message: T) { - if (message.predicate()) { - message.function(player) - } - } + var callback: S.(Player) -> Unit + var predicate: S.() -> Boolean - var function: T.(Player) -> Unit = { _ -> } - - var predicate: T.() -> Boolean = { true } - - fun where(predicate: T.() -> Boolean): KotlinMessageHandler { + fun where(predicate: S.() -> Boolean): KotlinPlayerHandlerProxyTrait { this.predicate = predicate return this } - fun then(function: T.(Player) -> Unit) { - this.function = function - this.context.addMessageHandler(type.java, this) + fun then(callback: S.(Player) -> Unit) { + this.callback = callback + this.register() } + fun register() + + fun handleProxy(player: Player, subject: S) { + if (subject.predicate()) { + subject.callback(player) + } + } +} + +class KotlinPlayerEventHandler(val world: World, val type: KClass) : + KotlinPlayerHandlerProxyTrait, EventListener { + + 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) + } -class KotlinCommandListener(val level: PrivilegeLevel, val function: Command.(Player) -> Unit) : CommandListener(level) { +class KotlinMessageHandler(val world: World, val context: PluginContext, val type: KClass) : + KotlinPlayerHandlerProxyTrait, MessageHandler(world) { - override fun execute(player: Player, command: Command) { - function.invoke(command, player) - } + 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) } -class KotlinCommandHandler(val world : World, val command: String, val privileges: PrivilegeLevel) { +class KotlinCommandHandler(val world: World, val command: String, privileges: PrivilegeLevel) : + KotlinPlayerHandlerProxyTrait, CommandListener(privileges) { - var function: Command.(Player) -> Unit = { _ -> } + override var callback: Command.(Player) -> Unit = {} + override var predicate: Command.() -> Boolean = { true } - fun then(function: Command.(Player) -> Unit) { - this.function = function - world.commandDispatcher.register(command, KotlinCommandListener(privileges, function)) - } + 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 a7e212c7..70dcb9db 100644 --- a/game/src/main/kotlin/stub.kt +++ b/game/src/main/kotlin/stub.kt @@ -6,16 +6,22 @@ * required to resolve references within plugin code. */ +import org.apollo.game.message.handler.MessageHandlerChainSet 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.plugin.kotlin.KotlinCommandHandler -import org.apollo.game.plugin.kotlin.KotlinMessageHandler +import org.apollo.game.model.event.PlayerEvent +import org.apollo.game.plugin.kotlin.* import org.apollo.net.message.Message import kotlin.reflect.KClass fun on(type: () -> KClass): KotlinMessageHandler { null!! } +fun on_player_event(type: () -> KClass): KotlinPlayerEventHandler { + null!! +} fun on_command(command: String, privileges: PrivilegeLevel): KotlinCommandHandler { null!! diff --git a/game/src/plugins/entity/following/meta.toml b/game/src/plugins/entity/following/meta.toml new file mode 100644 index 00000000..0e2e8e4d --- /dev/null +++ b/game/src/plugins/entity/following/meta.toml @@ -0,0 +1,8 @@ +name = "following" +package = "org.apollo.game.plugin.entity" +authors = [ "Gary Tierney" ] +dependencies = [ "walkto", "command_utilities", "player_action"] + +[config] +srcDir = "src/" +testDir = "test/" \ No newline at end of file diff --git a/game/src/plugins/entity/following/src/following.kt b/game/src/plugins/entity/following/src/following.kt new file mode 100644 index 00000000..e208229f --- /dev/null +++ b/game/src/plugins/entity/following/src/following.kt @@ -0,0 +1,52 @@ +package org.apollo.plugin.entity.following + +import org.apollo.game.action.Action +import org.apollo.game.model.Direction +import org.apollo.game.model.Position +import org.apollo.game.model.entity.Mob +import org.apollo.game.model.entity.Player +import org.apollo.net.message.Message +import org.apollo.plugin.entity.walkto.walkBehind +import org.apollo.plugin.entity.walkto.walkTo + +class FollowAction(player: Player, val target: Player) : Action(0, true, player) { + var lastPosition: Position? = null + + companion object { + fun start(player: Player, target: Player, message: Message? = null) { + player.startAction(FollowAction(player, target)) + message?.terminate() + } + } + + override fun execute() { + if (!target.isActive) { + stop() + return + } + + mob.interactingMob = target + + if (target.position == lastPosition) { + return + } + + val distance = mob.position.getDistance(target.position) + if (distance >= 15) { + stop() + return + } + + if (mob.position == target.position) { + val directions = Direction.NESW + val directionOffset = (Math.random() * directions.size).toInt() + + mob.walkTo(target.position.step(1, directions[directionOffset])) + return + } + + mob.walkBehind(target) + lastPosition = target.position + } + +} \ No newline at end of file diff --git a/game/src/plugins/entity/following/src/following.plugin.kts b/game/src/plugins/entity/following/src/following.plugin.kts new file mode 100644 index 00000000..6037e24d --- /dev/null +++ b/game/src/plugins/entity/following/src/following.plugin.kts @@ -0,0 +1,11 @@ +import com.google.common.primitives.Ints +import org.apollo.game.message.impl.PlayerActionMessage +import org.apollo.game.model.entity.setting.PrivilegeLevel +import org.apollo.plugin.entity.following.FollowAction + +on_player_event { PlayerActionEvent::class } + .where { action == PlayerActionType.FOLLOW } + .then { + FollowAction.start(it, target) + terminate() + } \ No newline at end of file diff --git a/game/src/plugins/entity/player-action/meta.toml b/game/src/plugins/entity/player-action/meta.toml new file mode 100644 index 00000000..89eabadd --- /dev/null +++ b/game/src/plugins/entity/player-action/meta.toml @@ -0,0 +1,8 @@ +name = "player_action" +package = "org.apollo.game.plugin.entity" +authors = [ "Gary Tierney" ] +dependencies = [] + +[config] +srcDir = "src/" +testDir = "test/" \ No newline at end of file diff --git a/game/src/plugins/entity/player-action/src/player_action.kt b/game/src/plugins/entity/player-action/src/player_action.kt new file mode 100644 index 00000000..bfec0e6d --- /dev/null +++ b/game/src/plugins/entity/player-action/src/player_action.kt @@ -0,0 +1,35 @@ +import org.apollo.game.message.impl.SetPlayerActionMessage +import org.apollo.game.model.entity.Player +import org.apollo.game.model.event.PlayerEvent +import java.util.* + +enum class PlayerActionType(val displayName: String, val slot: Int, val primary: Boolean = true) { + ATTACK("Attack", 2), + CHALLENGE("Challenge", 2), + FOLLOW("Follow", 4), + TRADE("Trade with", 5) +} + +class PlayerActionEvent(player: Player, val target: Player, val action: PlayerActionType) : PlayerEvent(player) + +private val playerActionsMap = mutableMapOf>() +private val Player.actions: EnumSet + get() = playerActionsMap.computeIfAbsent(this, { EnumSet.noneOf(PlayerActionType::class.java) }) + +fun Player.enableAction(action: PlayerActionType) { + send(SetPlayerActionMessage(action.displayName, action.slot, action.primary)) + actions.add(action) +} + +fun Player.disableAction(action: PlayerActionType) { + send(SetPlayerActionMessage("null", action.slot, action.primary)) + actions.remove(action) +} + +fun Player.actionEnabled(action: PlayerActionType): Boolean { + return actions.contains(action) +} + +fun Player.actionAt(slot: Int): PlayerActionType? { + return actions.find { it.slot == slot } +} \ No newline at end of file diff --git a/game/src/plugins/entity/player-action/src/player_action.plugin.kts b/game/src/plugins/entity/player-action/src/player_action.plugin.kts new file mode 100644 index 00000000..c165f30a --- /dev/null +++ b/game/src/plugins/entity/player-action/src/player_action.plugin.kts @@ -0,0 +1,18 @@ +import org.apollo.game.message.impl.PlayerActionMessage +import org.apollo.game.model.event.impl.LoginEvent + +on { PlayerActionMessage::class } + .then { + val action = it.actionAt(option) + if (action != null) { + it.world.submit(PlayerActionEvent(it, it.world.playerRepository[index], action)) + } + + terminate() + } + +on_player_event { LoginEvent::class } + .then { + it.enableAction(PlayerActionType.FOLLOW) + it.enableAction(PlayerActionType.TRADE) + } \ No newline at end of file diff --git a/game/src/plugins/entity/walk-to/meta.toml b/game/src/plugins/entity/walk-to/meta.toml new file mode 100644 index 00000000..7a7b0ff8 --- /dev/null +++ b/game/src/plugins/entity/walk-to/meta.toml @@ -0,0 +1,4 @@ +name = "walkto" +package = "org.apollo.plugin.entity.walkto" +authors = ["Gary Tierney"] +dependencies = [] \ No newline at end of file diff --git a/game/src/plugins/entity/walk-to/src/walk_to.kt b/game/src/plugins/entity/walk-to/src/walk_to.kt new file mode 100644 index 00000000..3fda1dba --- /dev/null +++ b/game/src/plugins/entity/walk-to/src/walk_to.kt @@ -0,0 +1,59 @@ +package org.apollo.plugin.entity.walkto + +import org.apollo.game.model.Direction +import org.apollo.game.model.Position +import org.apollo.game.model.entity.* +import org.apollo.game.model.entity.obj.GameObject +import org.apollo.game.model.entity.path.SimplePathfindingAlgorithm + +private fun bounds(target: Entity): Pair = when (target) { + is GameObject -> { + val orientation = Direction.WNES[target.orientation] + val rotated = (orientation == Direction.WEST || orientation == Direction.EAST) + + val width = if (rotated) target.definition.length else target.definition.width + val height = if (rotated) target.definition.width else target.definition.length + + Pair(width, height) + } + is Npc -> Pair(target.definition.size, target.definition.size) + is Player -> Pair(1, 1) + else -> error("Invalid entity type") +} + +fun Mob.walkTo(target: Entity, positioningDirection: Direction? = null) { + val (sourceWidth, sourceHeight) = bounds(target) + val (targetWidth, targetHeight) = bounds(target) + + val direction = positioningDirection ?: Direction.between(position, target.position) + val dx = direction.deltaX() + val dy = direction.deltaY() + + val targetX = if (dx <= 0) target.position.x else target.position.x + targetWidth - 1 + val targetY = if (dy <= 0) target.position.y else target.position.y + targetHeight - 1 + val offsetX = if (dx < 0) -sourceWidth else if (dx > 0) 1 else 0 + val offsetY = if (dy < 0) -sourceHeight else if (dy > 0) 1 else 0 + + walkTo(Position(targetX + offsetX, targetY + offsetY, position.height)) +} + +fun Mob.walkBehind(target: Mob) { + walkTo(target, target.lastDirection.opposite()) +} + +fun Mob.walkTo(target: Position, positionPredicate: (Position) -> Boolean = { true }) { + if (position == target) { + return + } + + val pathfinder = SimplePathfindingAlgorithm(world.collisionManager) + val path = pathfinder.find(position, target) + + for (step in path) { + if (!positionPredicate.invoke(step)) { + return + } + + walkingQueue.addStep(step) + } +} \ No newline at end of file