diff --git a/game/src/main/java/org/apollo/game/model/entity/Player.java b/game/src/main/java/org/apollo/game/model/entity/Player.java index 2a323b10..76c06db9 100644 --- a/game/src/main/java/org/apollo/game/model/entity/Player.java +++ b/game/src/main/java/org/apollo/game/model/entity/Player.java @@ -28,6 +28,7 @@ import org.apollo.game.model.entity.attr.AttributeDefinition; import org.apollo.game.model.entity.attr.AttributeMap; import org.apollo.game.model.entity.attr.AttributePersistence; import org.apollo.game.model.entity.attr.NumericalAttribute; +import org.apollo.game.model.entity.attr.BooleanAttribute; import org.apollo.game.model.entity.obj.DynamicGameObject; import org.apollo.game.model.entity.setting.MembershipStatus; import org.apollo.game.model.entity.setting.PrivacyState; @@ -943,6 +944,22 @@ public final class Player extends Mob { this.withdrawingNotes = withdrawingNotes; } + /** + * Ban the player. + */ + public void ban() { + attributes.set("banned", new BooleanAttribute(true)); + } + + /** + * Sets the mute status of a player. + * + * @param muted Whether the player is muted. + */ + public void setMuted(boolean muted) { + attributes.set("muted", new BooleanAttribute(muted)); + } + @Override public void shout(String message, boolean chatOnly) { blockSet.add(SynchronizationBlock.createForceChatBlock(chatOnly ? message : '~' + message)); 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 1a9541a3..e186a700 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 @@ -1,10 +1,12 @@ package org.apollo.game.plugin.kotlin +import org.apollo.game.command.Command +import org.apollo.game.command.CommandListener 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.plugin.PluginContext -import org.apollo.game.scheduling.ScheduledTask import org.apollo.net.message.Message import kotlin.reflect.KClass import kotlin.script.templates.ScriptTemplateDefinition @@ -20,6 +22,10 @@ abstract class KotlinPluginScript(private var world: World, val context: PluginC return KotlinMessageHandler(world, context, type.invoke()) } + protected fun on_command(command: String, privileges: PrivilegeLevel): KotlinCommandHandler { + return KotlinCommandHandler(world, command, privileges) + } + protected fun start(callback: (World) -> Unit) { this.startListener = callback } @@ -37,7 +43,6 @@ abstract class KotlinPluginScript(private var world: World, val context: PluginC } } - class KotlinMessageHandler(val world: World, val context: PluginContext, val type: KClass) : MessageHandler(world) { override fun handle(player: Player, message: T) { @@ -61,3 +66,22 @@ class KotlinMessageHandler(val world: World, val context: PluginCon } } + +class KotlinCommandListener(val level: PrivilegeLevel, val function: Command.(Player) -> Unit) : CommandListener(level) { + + override fun execute(player: Player, command: Command) { + function.invoke(command, player) + } + +} + +class KotlinCommandHandler(val world : World, val command: String, val privileges: PrivilegeLevel) { + + var function: Command.(Player) -> Unit = { _ -> } + + fun then(function: Command.(Player) -> Unit) { + this.function = function + world.commandDispatcher.register(command, KotlinCommandListener(privileges, function)) + } + +} diff --git a/game/src/main/kotlin/stub.kt b/game/src/main/kotlin/stub.kt index 8c583704..a7e212c7 100644 --- a/game/src/main/kotlin/stub.kt +++ b/game/src/main/kotlin/stub.kt @@ -7,8 +7,9 @@ */ import org.apollo.game.model.World -import org.apollo.game.plugin.PluginContext -import org.apollo.game.plugin.kotlin.* +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.net.message.Message import kotlin.reflect.KClass @@ -16,6 +17,10 @@ fun on(type: () -> KClass): KotlinMessageHandler { null!! } +fun on_command(command: String, privileges: PrivilegeLevel): KotlinCommandHandler { + null!! +} + fun start(callback: (World) -> Unit) { } diff --git a/game/src/plugins/cmd/meta.toml b/game/src/plugins/cmd/meta.toml new file mode 100644 index 00000000..7a255f39 --- /dev/null +++ b/game/src/plugins/cmd/meta.toml @@ -0,0 +1,13 @@ +name = "Chat commands" +package = "org.apollo.game.plugin.cmd" +dependencies = [ "command_utilities" ] +authors = [ + "Graham", + "Major", + "lare96", + "cubeee" +] + +[config] +srcDir = "src/" +testDir = "test/" \ No newline at end of file diff --git a/game/src/plugins/cmd/src/animate-cmd.plugin.kts b/game/src/plugins/cmd/src/animate-cmd.plugin.kts new file mode 100644 index 00000000..cab21041 --- /dev/null +++ b/game/src/plugins/cmd/src/animate-cmd.plugin.kts @@ -0,0 +1,17 @@ +import com.google.common.primitives.Ints +import org.apollo.game.model.Animation +import org.apollo.game.model.entity.setting.PrivilegeLevel + +on_command("animate", PrivilegeLevel.MODERATOR) + .then { player -> + val invalidSyntax = "Invalid syntax - ::animate [animation-id]" + if(valid_arg_length(arguments, 1, player, invalidSyntax)) { + val id = Ints.tryParse(arguments[0]) + if (id == null) { + player.sendMessage(invalidSyntax) + return@then + } + + player.playAnimation(Animation(id)) + } + } \ No newline at end of file diff --git a/game/src/plugins/cmd/src/bank-cmd.plugin.kts b/game/src/plugins/cmd/src/bank-cmd.plugin.kts new file mode 100644 index 00000000..181a1194 --- /dev/null +++ b/game/src/plugins/cmd/src/bank-cmd.plugin.kts @@ -0,0 +1,5 @@ +import org.apollo.game.model.entity.setting.PrivilegeLevel + +// Opens the player's bank if they are an administrator. +on_command("bank", PrivilegeLevel.ADMINISTRATOR) + .then { player -> player.openBank() } \ No newline at end of file diff --git a/game/src/plugins/cmd/src/item-cmd.plugin.kts b/game/src/plugins/cmd/src/item-cmd.plugin.kts new file mode 100644 index 00000000..52eafb18 --- /dev/null +++ b/game/src/plugins/cmd/src/item-cmd.plugin.kts @@ -0,0 +1,39 @@ +import com.google.common.primitives.Ints +import org.apollo.cache.def.ItemDefinition +import org.apollo.game.model.entity.setting.PrivilegeLevel + +on_command("item", PrivilegeLevel.ADMINISTRATOR) + .then { player -> + val invalidSyntax = "Invalid syntax - ::item [id] [amount]" + if (!valid_arg_length(arguments, 1..2, player, invalidSyntax)) { + return@then + } + + val id = Ints.tryParse(arguments[0]) + if (id == null) { + player.sendMessage(invalidSyntax) + return@then + } + + var amount = 1 + if (arguments.size == 2) { + val amt = Ints.tryParse(arguments[1]) + if (amt == null) { + player.sendMessage(invalidSyntax) + return@then + } + amount = amt + } + + if (id < 0 || id >= ItemDefinition.count()) { + player.sendMessage("The item id you specified is out of bounds!") + return@then + } + + if (amount < 0) { + player.sendMessage("The amount you specified is out of bounds!") + return@then + } + + player.inventory.add(id, amount) + } \ No newline at end of file diff --git a/game/src/plugins/cmd/src/lookup.plugin.kts b/game/src/plugins/cmd/src/lookup.plugin.kts new file mode 100644 index 00000000..a599c0f4 --- /dev/null +++ b/game/src/plugins/cmd/src/lookup.plugin.kts @@ -0,0 +1,66 @@ +import com.google.common.primitives.Ints +import org.apollo.cache.def.ItemDefinition +import org.apollo.cache.def.NpcDefinition +import org.apollo.cache.def.ObjectDefinition +import org.apollo.game.model.entity.setting.PrivilegeLevel + +on_command("iteminfo", PrivilegeLevel.ADMINISTRATOR) + .then { player -> + val invalidSyntax = "Invalid syntax - ::npcinfo [npc id]" + if (!valid_arg_length(arguments, 1, player, invalidSyntax)) { + return@then + } + + val id = Ints.tryParse(arguments[0]) + if (id == null) { + player.sendMessage(invalidSyntax) + return@then + } + + val definition = ItemDefinition.lookup(id) + val members = if (definition.isMembersOnly) "members" else "not members" + + player.sendMessage("Item $id is called ${definition.name}, is $members only, and has a " + + "team of ${definition.team}.") + player.sendMessage("Its description is \"${definition.description}\".") + } + +on_command("npcinfo", PrivilegeLevel.ADMINISTRATOR) + .then { player -> + val invalidSyntax = "Invalid syntax - ::npcinfo [npc id]" + if (!valid_arg_length(arguments, 1, player, invalidSyntax)) { + return@then + } + + val id = Ints.tryParse(arguments[0]) + if (id == null) { + player.sendMessage(invalidSyntax) + return@then + } + + val definition = NpcDefinition.lookup(id) + val isCombative = if (definition.hasCombatLevel()) "has a combat level of ${definition.combatLevel}" else + "does not have a combat level" + + player.sendMessage("Npc $id is called ${definition.name} and $isCombative.") + player.sendMessage("Its description is \"${definition.description}\".") + } + +on_command("objectinfo", PrivilegeLevel.ADMINISTRATOR) + .then { player -> + val invalidSyntax = "Invalid syntax - ::objectinfo [object id]" + if (!valid_arg_length(arguments, 1, player, invalidSyntax)) { + return@then + } + + val id = Ints.tryParse(arguments[0]) + if (id == null) { + player.sendMessage(invalidSyntax) + return@then + } + + val definition = ObjectDefinition.lookup(id) + player.sendMessage("Object $id is called ${definition.name} and its description is " + + "\"${definition.description}\".") + player.sendMessage("Its width is ${definition.width} and its length is ${definition.length}.") + } \ No newline at end of file diff --git a/game/src/plugins/cmd/src/messaging-cmd.plugin.kts b/game/src/plugins/cmd/src/messaging-cmd.plugin.kts new file mode 100644 index 00000000..883d7a51 --- /dev/null +++ b/game/src/plugins/cmd/src/messaging-cmd.plugin.kts @@ -0,0 +1,11 @@ +import org.apollo.game.model.entity.setting.PrivilegeLevel + +on_command("broadcast", PrivilegeLevel.ADMINISTRATOR) + .then { player -> + val message = arguments.joinToString(" ") + val broadcast = "[Broadcast] ${player.username.capitalize()}: $message" + + player.world.playerRepository.forEach { other -> + other.sendMessage(broadcast) + } + } diff --git a/game/src/plugins/cmd/src/punish-cmd.plugin.kts b/game/src/plugins/cmd/src/punish-cmd.plugin.kts new file mode 100644 index 00000000..619d4674 --- /dev/null +++ b/game/src/plugins/cmd/src/punish-cmd.plugin.kts @@ -0,0 +1,60 @@ + +import org.apollo.game.model.entity.Player +import org.apollo.game.model.entity.setting.PrivilegeLevel + +/** + * Adds a command to mute a player. Admins cannot be muted. + */ +on_command("mute", PrivilegeLevel.MODERATOR) + .then { player -> + val name = arguments.joinToString(" ") + val targetPlayer = player.world.getPlayer(name) + + if (validate(player, targetPlayer)) { + targetPlayer.isMuted = true + player.sendMessage("You have just unmuted ${targetPlayer.username}.") + } + } + +/** + * Adds a command to unmute a player. + */ +on_command("unmute", PrivilegeLevel.MODERATOR) + .then { player -> + val name = arguments.joinToString(" ") + val targetPlayer = player.world.getPlayer(name) + + if (validate(player, targetPlayer)) { + targetPlayer.isMuted = false + player.sendMessage("You have just unmuted ${targetPlayer.username}.") + } + } + +/** + * Adds a command to ban a player. Admins cannot be banned. + */ +on_command("ban", PrivilegeLevel.ADMINISTRATOR) + .then { player -> + val name = arguments.joinToString(" ") + val targetPlayer = player.world.getPlayer(name) + + if (validate(player, targetPlayer)) { + targetPlayer.ban() + targetPlayer.logout() // TODO force logout + player.sendMessage("You have just banned ${targetPlayer.username}.") + } + } + +/** + * Ensures the player isn't null, and that they aren't an Administrator. + */ +fun validate(player: Player, targetPlayer: Player?): Boolean { + if (targetPlayer == null) { + player.sendMessage("That player does not exist.") + return false + } else if (targetPlayer.privilegeLevel == PrivilegeLevel.ADMINISTRATOR) { + player.sendMessage("You cannot perform this action on Administrators.") + return false + } + return true +} \ No newline at end of file diff --git a/game/src/plugins/cmd/src/skill-cmd.plugin.kts b/game/src/plugins/cmd/src/skill-cmd.plugin.kts new file mode 100644 index 00000000..ade6e6c0 --- /dev/null +++ b/game/src/plugins/cmd/src/skill-cmd.plugin.kts @@ -0,0 +1,86 @@ + +import com.google.common.primitives.Doubles +import com.google.common.primitives.Ints +import org.apollo.game.model.entity.Skill +import org.apollo.game.model.entity.SkillSet +import org.apollo.game.model.entity.setting.PrivilegeLevel + +/** + * Maximises the player's skill set. + */ +on_command("max", PrivilegeLevel.ADMINISTRATOR) + .then { player -> + val skills = player.skillSet + + for (skill in 0 until skills.size()) { + skills.addExperience(skill, SkillSet.MAXIMUM_EXP) + } + } + +/** + * Levels the specified skill to the specified level, optionally updating the current level as well. + */ +on_command("level", PrivilegeLevel.ADMINISTRATOR) + .then { player -> + val invalidSyntax = "Invalid syntax - ::level [skill-id] [level] " + if (arguments.size !in 2..3) { + player.sendMessage(invalidSyntax) + return@then + } + + val skillId = Ints.tryParse(arguments[0]) + if (skillId == null) { + player.sendMessage(invalidSyntax) + return@then + } + val level = Ints.tryParse(arguments[1]) + if (level == null) { + player.sendMessage(invalidSyntax) + return@then + } + + if (skillId !in 0..20 || level !in 1..99) { + player.sendMessage(invalidSyntax) + return@then + } + + val experience = SkillSet.getExperienceForLevel(level).toDouble() + var current = level + + if (arguments.size == 3 && arguments[2] == "old") { + val skill = player.skillSet.getSkill(skillId) + current = skill.currentLevel + } + + player.skillSet.setSkill(skillId, Skill(experience, current, level)) + } + +/** + * Adds the specified amount of experience to the specified skill. + */ +on_command("xp", PrivilegeLevel.ADMINISTRATOR) + .then { player -> + val invalidSyntax = "Invalid syntax - ::xp [skill-id] [experience]" + if (arguments.size != 2) { + player.sendMessage(invalidSyntax) + return@then + } + + val skillId = Ints.tryParse(arguments[0]) + if (skillId == null) { + player.sendMessage(invalidSyntax) + return@then + } + val experience = Doubles.tryParse(arguments[1]) + if (experience == null) { + player.sendMessage(invalidSyntax) + return@then + } + + if (skillId !in 0..20 || experience <= 0) { + player.sendMessage("Invalid syntax - ::xp [skill-id] [experience]") + return@then + } + + player.skillSet.addExperience(skillId, experience) + } \ No newline at end of file diff --git a/game/src/plugins/cmd/src/spawn-cmd.plugin.kts b/game/src/plugins/cmd/src/spawn-cmd.plugin.kts new file mode 100644 index 00000000..7f124d89 --- /dev/null +++ b/game/src/plugins/cmd/src/spawn-cmd.plugin.kts @@ -0,0 +1,110 @@ +import com.google.common.primitives.Ints +import org.apollo.game.model.Position +import org.apollo.game.model.entity.Npc +import org.apollo.game.model.entity.setting.PrivilegeLevel + +/** + * An array of npcs that cannot be spawned. + */ +val blacklist: IntArray = intArrayOf() + +/** + * Spawns a non-blacklisted npc in the specified position, or the player's position if both 'x' and + * 'y' are not supplied. + */ +on_command("spawn", PrivilegeLevel.ADMINISTRATOR) + .then { player -> + val invalidSyntax = "Invalid syntax - ::spawn [npc id] [optional-x] [optional-y] [optional-z]" + if (arguments.size !in intArrayOf(1, 3, 4)) { + player.sendMessage(invalidSyntax) + return@then + } + + val id = Ints.tryParse(arguments[0]) + if (id == null) { + player.sendMessage(invalidSyntax) + return@then + } + + if (id in blacklist) { + player.sendMessage("Sorry, npc $id is blacklisted!") + return@then + } + + val position: Position? + if (arguments.size == 1) { + position = player.position + } else { + var height = player.position.height + if (arguments.size == 4) { + val h = Ints.tryParse(arguments[3]) + if (h == null) { + player.sendMessage(invalidSyntax) + return@then + } + height = h + } + position = Position(arguments[1].toInt(), arguments[2].toInt(), height) + } + + player.world.register(Npc(player.world, id, position)) + } + +/** + * Mass spawns npcs around the player. + */ +on_command("mass", PrivilegeLevel.ADMINISTRATOR) + .then { player -> + val invalidSyntax = "Invalid syntax - ::mass [npc id] [range (1-5)]" + if (arguments.size != 2) { + player.sendMessage(invalidSyntax) + return@then + } + + val id = Ints.tryParse(arguments[0]) + if (id == null) { + player.sendMessage(invalidSyntax) + return@then + } + + val range = Ints.tryParse(arguments[1]) + if (range == null) { + player.sendMessage(invalidSyntax) + return@then + } + + if (id < 0 || range !in 1..5) { + player.sendMessage(invalidSyntax) + return@then + } + + if (id in blacklist) { + player.sendMessage("Sorry, npc $id is blacklisted!") + return@then + } + + val centerPosition = player.position + + val minX = centerPosition.x - range + val minY = centerPosition.y - range + val maxX = centerPosition.x + range + val maxY = centerPosition.y + range + val z = centerPosition.height + + for (x in minX..maxX) { + for (y in minY..maxY) { + player.world.register(Npc(player.world, id, Position(x, y, z))) + } + } + + player.sendMessage("Mass spawning npcs with id $id.") + } + +/** + * Unregisters all npcs from the world npc repository. + */ +on_command("clearnpcs", PrivilegeLevel.ADMINISTRATOR) + .then { player -> + player.world.npcRepository.forEach { npc -> player.world.unregister(npc) } + player.sendMessage("Unregistered all npcs from the world.") + } \ No newline at end of file diff --git a/game/src/plugins/cmd/src/teleport-cmd.plugin.kts b/game/src/plugins/cmd/src/teleport-cmd.plugin.kts new file mode 100644 index 00000000..75490b9f --- /dev/null +++ b/game/src/plugins/cmd/src/teleport-cmd.plugin.kts @@ -0,0 +1,48 @@ +import com.google.common.primitives.Ints +import org.apollo.game.model.Position +import org.apollo.game.model.entity.setting.PrivilegeLevel + +/** + * Sends the player's position. + */ +on_command("pos", PrivilegeLevel.MODERATOR) + .then { player -> + player.sendMessage("You are at: ${player.position}.") + } + +/** + * Teleports the player to the specified position. + */ +on_command("tele", PrivilegeLevel.ADMINISTRATOR) + .then { player -> + val invalidSyntax = "Invalid syntax - ::tele [x] [y] [optional-z]" + if (!valid_arg_length(arguments, 2..3, player, invalidSyntax)) { + return@then + } + + val x = Ints.tryParse(arguments[0]) + if (x == null) { + player.sendMessage(invalidSyntax) + return@then + } + + val y = Ints.tryParse(arguments[1]) + if (y == null) { + player.sendMessage(invalidSyntax) + return@then + } + + var z = player.position.height + if (arguments.size == 3) { + val plane = Ints.tryParse(arguments[2]) + if (plane == null) { + player.sendMessage(invalidSyntax) + return@then + } + z = plane + } + + if (z in 0..4) { + player.teleport(Position(x, y, z)) + } + } \ No newline at end of file diff --git a/game/src/plugins/stub/stub.kt b/game/src/plugins/stub/stub.kt index 8c583704..f82da254 100644 --- a/game/src/plugins/stub/stub.kt +++ b/game/src/plugins/stub/stub.kt @@ -16,6 +16,10 @@ fun on(type: () -> KClass): KotlinMessageHandler { null!! } +fun on_command(command: String, privileges: PrivilegeLevel): KotlinCommandHandler { + null!! +} + fun start(callback: (World) -> Unit) { } diff --git a/game/src/plugins/util/command/meta.toml b/game/src/plugins/util/command/meta.toml new file mode 100644 index 00000000..2d962919 --- /dev/null +++ b/game/src/plugins/util/command/meta.toml @@ -0,0 +1,5 @@ +name = "command utilities" +package = "org.apollo.game.plugins.util" + +[config] +srcDir = "src/" \ No newline at end of file diff --git a/game/src/plugins/util/command/src/command.kt b/game/src/plugins/util/command/src/command.kt new file mode 100644 index 00000000..7746d948 --- /dev/null +++ b/game/src/plugins/util/command/src/command.kt @@ -0,0 +1,20 @@ +import org.apollo.game.model.entity.Player + +/** + * Checks whether the amount of arguments provided is correct, sending the player the specified + * message if not. + */ +fun valid_arg_length(args: Array, length: IntRange, player: Player, message: String): Boolean { + val valid = length.contains(args.size) + if (!valid) { + player.sendMessage(message) + } + return valid +} + +/** + * Checks whether the amount of arguments provided is correct, sending the player the specified + * message if not. + */ +fun valid_arg_length(args: Array, length: Int, player: Player, message: String) + = valid_arg_length(args, IntRange(length, length), player, message) \ No newline at end of file