diff --git a/build.gradle.kts b/build.gradle.kts index 4e0fcce..6d60124 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -27,6 +27,7 @@ group = "dev.znci" version = "1.0-SNAPSHOT" repositories { + mavenLocal() mavenCentral() maven("https://repo.papermc.io/repository/maven-public/") { name = "papermc-repo" @@ -34,6 +35,7 @@ repositories { maven("https://oss.sonatype.org/content/groups/public/") { name = "sonatype" } + maven("https://jitpack.io") } dependencies { @@ -42,8 +44,13 @@ dependencies { implementation("org.luaj:luaj-jse:3.0.1") implementation("net.luckperms:api:5.4") implementation("org.jetbrains.kotlin:kotlin-reflect") + implementation("dev.znci:twine:v1.0.4") + implementation("org.reflections:reflections:0.10.2") + testImplementation(kotlin("test")) testImplementation("org.junit.jupiter:junit-jupiter:5.12.2") + testImplementation("org.mockbukkit.mockbukkit:mockbukkit-v1.21:4.45.1") + testImplementation("org.mockito.kotlin:mockito-kotlin:5.4.0") } val targetJavaVersion = 21 @@ -69,7 +76,6 @@ tasks.withType().configureEach { named("main") { moduleName.set("Rocket Java API") includes.from("Rocket.md") - } } } @@ -87,4 +93,8 @@ tasks.withType(xyz.jpenilla.runtask.task.AbstractRun::class) { tasks.runServer { minecraftVersion("1.21.4") jvmArgs("-Dcom.mojang.eula.agree=true") +} + +tasks.test { + useJUnitPlatform() } \ No newline at end of file diff --git a/src/main/kotlin/dev/znci/rocket/Rocket.kt b/src/main/kotlin/dev/znci/rocket/Rocket.kt index c8e3f63..3f704ea 100644 --- a/src/main/kotlin/dev/znci/rocket/Rocket.kt +++ b/src/main/kotlin/dev/znci/rocket/Rocket.kt @@ -20,13 +20,22 @@ import dev.znci.rocket.i18n.LocaleManager import dev.znci.rocket.scripting.ScriptManager import dev.znci.rocket.scripting.events.EventListener import dev.znci.rocket.scripting.GlobalInitializer +import dev.znci.rocket.scripting.annotations.Global import org.bukkit.plugin.java.JavaPlugin +import org.reflections.Reflections import java.io.File -class Rocket : JavaPlugin() { +open class Rocket : JavaPlugin() { + companion object { + lateinit var instance: Rocket + private set + } + private var defaultLocale: String = "en_GB" override fun onEnable() { + instance = this + // Create the plugin data folder saveDefaultConfig() @@ -62,12 +71,10 @@ class Rocket : JavaPlugin() { EventListener.registerAllEvents() // Register globals - val globalInitialized = GlobalInitializer.init() - if (globalInitialized) { - logger.info("Globals successfully initialized") - } else { - logger.severe("Globals failed to initialize") - } + GlobalInitializer.registerAll() + + // Automatically load all scripts in the scripts folder + ScriptManager.loadScripts() logger.info("Rocket plugin enabled") } diff --git a/src/main/kotlin/dev/znci/rocket/i18n/LocaleManager.kt b/src/main/kotlin/dev/znci/rocket/i18n/LocaleManager.kt index 3329041..ebdedf9 100644 --- a/src/main/kotlin/dev/znci/rocket/i18n/LocaleManager.kt +++ b/src/main/kotlin/dev/znci/rocket/i18n/LocaleManager.kt @@ -121,7 +121,7 @@ object LocaleManager { val formattedMessage = try { message.format(*formatArgs.map { it.toString() }.toTypedArray()) - } catch (e: Exception) { + } catch (_: Exception) { message } diff --git a/src/main/kotlin/dev/znci/rocket/scripting/GlobalInitializer.kt b/src/main/kotlin/dev/znci/rocket/scripting/GlobalInitializer.kt index cdaf3ce..9a59a6a 100644 --- a/src/main/kotlin/dev/znci/rocket/scripting/GlobalInitializer.kt +++ b/src/main/kotlin/dev/znci/rocket/scripting/GlobalInitializer.kt @@ -1,9 +1,32 @@ +/** + * Copyright 2025 znci + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package dev.znci.rocket.scripting +import dev.znci.rocket.Rocket +import dev.znci.rocket.scripting.annotations.Global +import dev.znci.rocket.scripting.api.RocketError +import dev.znci.rocket.scripting.globals.tables.LuaCommands +import dev.znci.rocket.scripting.globals.tables.LuaHTTPClient import dev.znci.rocket.scripting.globals.tables.LuaLocations import dev.znci.rocket.scripting.globals.tables.LuaPlayers -import dev.znci.rocket.scripting.globals.tables.SimpleTest +import dev.znci.rocket.scripting.globals.tables.LuaServer import dev.znci.rocket.scripting.globals.values.TestValue +import dev.znci.twine.TwineValueBase +import org.bukkit.plugin.java.JavaPlugin +import org.reflections.Reflections /** * The `GlobalInitializer` object is responsible for initializing and registering global objects @@ -12,19 +35,23 @@ import dev.znci.rocket.scripting.globals.values.TestValue */ object GlobalInitializer { /** - * Initializes the global objects and registers them with the `ScriptManager`. + * Uses reflection to find all classes marked with `@Global`. * - * It returns `true` upon successful registration of these objects. - * - * @return `true` if the global objects were successfully initialized and registered. + * If a non-TwineValueBase value is marked with `@Global`, it throws an error. */ - fun init(): Boolean { - ScriptManager.registerGlobal(SimpleTest()) - ScriptManager.registerGlobal(TestValue()) - ScriptManager.registerGlobal(LuaPlayers()) - ScriptManager.registerGlobal(LuaLocations()) - //ScriptManager.registerGlobal(GamemodeEnum()) + fun registerAll() { + val reflections = Reflections("dev.znci.rocket") + val globalClasses = reflections.getTypesAnnotatedWith(Global::class.java) + + globalClasses.forEach { globalClass -> + if (TwineValueBase::class.java.isAssignableFrom(globalClass)) { + val instance = globalClass.getDeclaredConstructor().newInstance() as TwineValueBase + ScriptManager.registerGlobal(instance) + } else { + throw RocketError("@Global annotated class does not extend TwineValueBase.") + } + } - return true + Rocket.instance.logger.info { "Successfully registered ${globalClasses.size} globals." } } } \ No newline at end of file diff --git a/src/main/kotlin/dev/znci/rocket/scripting/PlayerManager.kt b/src/main/kotlin/dev/znci/rocket/scripting/PlayerManager.kt deleted file mode 100644 index f50d9cd..0000000 --- a/src/main/kotlin/dev/znci/rocket/scripting/PlayerManager.kt +++ /dev/null @@ -1,256 +0,0 @@ -/** - * Copyright 2025 znci - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dev.znci.rocket.scripting - -import dev.znci.rocket.scripting.globals.tables.LuaLocation.Companion.fromBukkit -import dev.znci.rocket.scripting.globals.tables.toBukkitLocation -import dev.znci.rocket.scripting.util.defineProperty -import dev.znci.rocket.util.MessageFormatter -import net.kyori.adventure.text.Component -import net.kyori.adventure.title.Title -import net.kyori.adventure.title.TitlePart -import org.bukkit.Bukkit -import org.bukkit.GameMode -import org.bukkit.entity.Player -import org.luaj.vm2.LuaTable -import org.luaj.vm2.LuaValue -import org.luaj.vm2.lib.OneArgFunction -import org.luaj.vm2.lib.TwoArgFunction -import org.luaj.vm2.lib.ZeroArgFunction -import java.time.Duration - -object PlayerManager { - - /** - * Returns a LuaTable with general and management information about the player - * - * - * @param player The player to get information from - * @return LuaTable - */ - fun getPlayerTable(player: Player): LuaTable { - val table = LuaTable() - - // methods should be defined before properties - table.set("send", object : OneArgFunction() { - override fun call(message: LuaValue): LuaValue { - val messageComponent = MessageFormatter.formatMessage(message.tojstring()) - player.sendMessage(messageComponent) - return LuaValue.TRUE - } - }) - - table.set("sendActionBar", object : OneArgFunction() { - override fun call(message: LuaValue): LuaValue { - val messageComponent = MessageFormatter.formatMessage(message.tojstring()) - player.sendActionBar(messageComponent) - return LuaValue.TRUE - } - }) - - table.set("sendTitle", object : TwoArgFunction() { - override fun call(message: LuaValue, timeTable: LuaValue): LuaValue { - val messageComponent = MessageFormatter.formatMessage(message.tojstring()) - val times = Title.Times.times( - Duration.ofMillis(timeTable.get("fadeIn").tolong() * 50), - Duration.ofMillis(timeTable.get("stay").tolong() * 50), - Duration.ofMillis(timeTable.get("fadeOut").tolong() * 50) - ) - player.sendTitlePart(TitlePart.TITLE, messageComponent) - player.sendTitlePart(TitlePart.TIMES, times) - return LuaValue.TRUE - } - }) - - table.set("sendSubtitle", object : TwoArgFunction() { - override fun call(message: LuaValue, timeTable: LuaValue): LuaValue { - val messageComponent = MessageFormatter.formatMessage(message.tojstring()) - val times = Title.Times.times( - Duration.ofMillis(timeTable.get("fadeIn").tolong() * 50), - Duration.ofMillis(timeTable.get("stay").tolong() * 50), - Duration.ofMillis(timeTable.get("fadeOut").tolong() * 50) - ) - player.sendTitlePart(TitlePart.SUBTITLE, messageComponent) - player.sendTitlePart(TitlePart.TIMES, times) - return LuaValue.TRUE - } - }) - - table.set("setPlayerTime", object : TwoArgFunction() { - override fun call(value: LuaValue, relative: LuaValue): LuaValue { - player.setPlayerTime(value.tolong(), relative.toboolean()) - return LuaValue.TRUE - } - }) - - table.set("addPermission", object : OneArgFunction() { - override fun call(value: LuaValue): LuaValue { - player.addAttachment(Bukkit.getPluginManager().getPlugin("Rocket")!!) - .setPermission(value.tojstring(), true) - return LuaValue.TRUE - } - }) - - table.set("op", object : ZeroArgFunction() { - override fun call(): LuaValue { - player.isOp = true - return LuaValue.TRUE - } - }) - - table.set("deop", object : ZeroArgFunction() { - override fun call(): LuaValue { - player.isOp = false - return LuaValue.TRUE - } - }) - - table.set("teleport", object : OneArgFunction() { - override fun call(value: LuaValue): LuaValue { - if (value is LuaTable) { - try { - val bukkitLocation = value.toBukkitLocation() - player.teleport(bukkitLocation) - return LuaValue.TRUE - } catch (e: Exception) { - return LuaValue.FALSE - } - } - return LuaValue.FALSE - } - }) - - table.set("hasPermission", object : OneArgFunction() { - override fun call(value: LuaValue): LuaValue { - return LuaValue.valueOf( - PermissionsManager.hasPermission(player, value.tojstring()) - ) - } - }) - - table.set("isInGroup", object : OneArgFunction() { - override fun call(value: LuaValue): LuaValue { - return LuaValue.valueOf( - PermissionsManager.isPlayerInGroup(player, value.tojstring()) - ) - } - }) - - // read-only properties - defineProperty(table, "name", { LuaValue.valueOf(player.name) }) - defineProperty(table, "uuid", { LuaValue.valueOf(player.uniqueId.toString()) }) - defineProperty(table, "world", { LuaValue.valueOf(player.world.name) }) - defineProperty(table, "ip", { LuaValue.valueOf(player.address?.hostString) }) - defineProperty(table, "isFlying", { LuaValue.valueOf(player.isFlying) }) - defineProperty(table, "isSneaking", { LuaValue.valueOf(player.isSneaking) }) - defineProperty(table, "isSprinting", { LuaValue.valueOf(player.isSprinting) }) - defineProperty(table, "isBlocking", { LuaValue.valueOf(player.isBlocking) }) - defineProperty(table, "isSleeping", { LuaValue.valueOf(player.isSleeping) }) - val block = player.getTargetBlockExact(100) - if (block != null) { - defineProperty(table, "targetBlockType", { LuaValue.valueOf(block.type.toString()) }) - defineProperty(table, "targetBlockLocation", { fromBukkit(block.location) }) - defineProperty(table, "targetBlockLightLevel", { LuaValue.valueOf(block.lightLevel.toDouble()) }) - defineProperty(table, "targetBlockTemperature", { LuaValue.valueOf(block.temperature) }) - defineProperty(table, "targetBlockHumidity", { LuaValue.valueOf(block.humidity) }) - } - // writable properties - defineProperty( - table, "health", - getter = { LuaValue.valueOf(player.health) }, - setter = { value -> player.health = value.todouble() }, - validator = { value -> value.isnumber() && value.todouble() >= 0 } - ) - - defineProperty( - table, "foodLevel", - getter = { LuaValue.valueOf(player.foodLevel) }, - setter = { value -> player.foodLevel = value.toint() }, - validator = { value -> value.isint() && value.toint() >= 0 && value.toint() <= 20 } - ) - - defineProperty( - table, "gameMode", - getter = { LuaValue.valueOf(player.gameMode.toString()) }, - setter = { value -> player.gameMode = GameMode.valueOf(value.tojstring()) }, - validator = { value -> - value.isstring() && listOf("SURVIVAL", "CREATIVE", "ADVENTURE", "SPECTATOR").contains(value.tojstring()) - } - ) - - defineProperty( - table, "xp", - getter = { LuaValue.valueOf(player.exp.toDouble()) }, - setter = { value -> player.exp = value.tofloat() }, - validator = { value -> value.isnumber() && value.todouble() >= 0 && value.todouble() <= 1 } - ) - - defineProperty( - table, "level", - getter = { LuaValue.valueOf(player.level) }, - setter = { value -> player.level = value.toint() }, - validator = { value -> value.isint() && value.toint() >= 0 } - ) - - defineProperty( - table, "location", - getter = { fromBukkit(player.location) }, - setter = { value -> - player.teleport(value.toBukkitLocation()) - }, - validator = { value -> - value is LuaTable && runCatching { value.toBukkitLocation() }.isSuccess - } - ) - - defineProperty( - table, "isOp", - getter = { LuaValue.valueOf(player.isOp) }, - setter = { value -> player.isOp = value.toboolean() }, - validator = { value -> value.isboolean() } - ) - - defineProperty( - table, "saturation", - getter = { LuaValue.valueOf(player.saturation.toDouble()) }, - setter = { value -> player.saturation = value.tofloat() }, - validator = { value -> value.isnumber() && value.todouble() >= 0 } - ) - - defineProperty( - table, "exhaustion", - getter = { LuaValue.valueOf(player.exhaustion.toDouble()) }, - setter = { value -> player.exhaustion = value.tofloat() }, - validator = { value -> value.isnumber() && value.todouble() >= 0 } - ) - - defineProperty( - table, "displayName", - getter = { LuaValue.valueOf(player.displayName().toString()) }, - setter = { value -> player.displayName(Component.text(value.tojstring())) }, - validator = { value -> value.isstring() } - ) - - defineProperty( - table, "tabListName", - getter = { LuaValue.valueOf(player.playerListName().toString()) }, - setter = { value -> player.playerListName(Component.text(value.tojstring())) }, - validator = { value -> value.isstring() } - ) - - return table - } -} \ No newline at end of file diff --git a/src/main/kotlin/dev/znci/rocket/scripting/ScriptManager.kt b/src/main/kotlin/dev/znci/rocket/scripting/ScriptManager.kt index 3fc86a6..6b2d04f 100644 --- a/src/main/kotlin/dev/znci/rocket/scripting/ScriptManager.kt +++ b/src/main/kotlin/dev/znci/rocket/scripting/ScriptManager.kt @@ -16,11 +16,11 @@ package dev.znci.rocket.scripting import dev.znci.rocket.scripting.api.RocketError -import dev.znci.rocket.scripting.api.RocketLuaValue -import dev.znci.rocket.scripting.api.RocketProperty -import dev.znci.rocket.scripting.api.RocketTable -import dev.znci.rocket.scripting.api.RocketValueBase -import dev.znci.rocket.scripting.classes.Command +import dev.znci.rocket.scripting.classes.CommandReference +import dev.znci.twine.TwineLuaValue +import dev.znci.twine.TwineProperty +import dev.znci.twine.TwineTable +import dev.znci.twine.TwineValueBase import org.bukkit.event.Event import java.io.File import org.luaj.vm2.Globals @@ -40,13 +40,6 @@ object ScriptManager { * This is used to load and execute Lua code with a standard Lua environment. */ private val globals: Globals = JsePlatform.standardGlobals() - - /** - * A list of global values (properties and tables) that have been registered for Lua scripting. - * These globals are made available to Lua scripts during their execution. - */ - private var enabledGlobals: MutableList = mutableListOf() - /** * The folder where scripts are located. * This can be set to a custom folder to load Lua scripts from a specific directory. @@ -63,7 +56,13 @@ object ScriptManager { * A map of enabled commands by their names. * It associates command names with their respective command executors. */ - val enabledCommands = mutableMapOf() + val enabledCommands = mutableMapOf() + + /** + * A list of global values (properties and tables) that have been registered for Lua scripting. + * These globals are made available to Lua scripts during their execution. + */ + var enabledGlobals: MutableList = mutableListOf() /** * Sets the folder where scripts are located. @@ -84,8 +83,7 @@ object ScriptManager { scriptsFolder.walkTopDown().forEach { file -> if (file.isFile && !file.startsWith("-")) { val content = file.readText() - - println(content) + runScript(content) } } } @@ -102,7 +100,7 @@ object ScriptManager { scriptsFolder.walkTopDown().forEach { file -> if (file.isFile) { if (includeDisabled && file.startsWith("-")) return@forEach - list.add(file.path.removePrefix("plugins/rocket/scripts/")) + list.add(file.path.removePrefix("plugins/rocket/scripts/").removePrefix("plugins\\rocket\\scripts\\")) } } return list @@ -121,8 +119,8 @@ object ScriptManager { val scriptResult = globals.load(text, "script", globals) scriptResult.call() - } catch (error: LuaError) { - return error.message + } catch (e: LuaError) { + return e.message } return "" @@ -134,7 +132,7 @@ object ScriptManager { * @param valueName The name of the global value to retrieve. * @return The global value if found, or `null` if it is not registered. */ - private fun getGlobalByTableName(valueName: String): RocketValueBase? { + private fun getGlobalByTableName(valueName: String): TwineValueBase? { return enabledGlobals.find { it.valueName == valueName } } @@ -144,7 +142,7 @@ object ScriptManager { * @param global The global value to register. * @throws RocketError If a global with the same table name is already registered. */ - fun registerGlobal(global: RocketValueBase) { + fun registerGlobal(global: TwineValueBase) { if (getGlobalByTableName(global.valueName) != null) { throw RocketError("A global of the same table name ('${global.valueName}') is already registered.") } @@ -158,8 +156,7 @@ object ScriptManager { * @param global The global value to unregister. * @throws RocketError If no global with the given table name is registered. */ - @Suppress("unused") // TODO: more management of globals by the end-user - private fun unregisterGlobal(global: RocketValueBase) { + fun unregisterGlobal(global: TwineValueBase) { if (getGlobalByTableName(global.valueName) == null) { throw RocketError("A global with the table name ('${global.valueName}') is not registered and cannot be unregistered.") } @@ -173,15 +170,14 @@ object ScriptManager { * * @param table The Lua table to which the globals will be applied. */ - private fun applyGlobals(table: LuaTable) { + fun applyGlobals(table: LuaTable) { enabledGlobals.forEach { - println("Processing: ${it::class.qualifiedName}") when (it) { - is RocketTable -> { + is TwineTable -> { table.set(it.valueName, it.table) } - is RocketProperty -> { - table.set(it.valueName, RocketLuaValue.valueOf(it.value)) + is TwineProperty -> { + table.set(it.valueName, TwineLuaValue.valueOf(it.value)) } } } diff --git a/src/test/kotlin/dev/znci/rocket/scripting/api/RocketTableTest.kt b/src/main/kotlin/dev/znci/rocket/scripting/annotations/Global.kt similarity index 73% rename from src/test/kotlin/dev/znci/rocket/scripting/api/RocketTableTest.kt rename to src/main/kotlin/dev/znci/rocket/scripting/annotations/Global.kt index ffc0703..cf3a4dc 100644 --- a/src/test/kotlin/dev/znci/rocket/scripting/api/RocketTableTest.kt +++ b/src/main/kotlin/dev/znci/rocket/scripting/annotations/Global.kt @@ -1,7 +1,3 @@ -package dev.znci.rocket.scripting.api - -import org.junit.jupiter.api.* - /** * Copyright 2025 znci * @@ -9,7 +5,7 @@ import org.junit.jupiter.api.* * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -17,9 +13,8 @@ import org.junit.jupiter.api.* * See the License for the specific language governing permissions and * limitations under the License. */ +package dev.znci.rocket.scripting.annotations -class RocketTableTest { - @BeforeEach - fun setUp() { - } -} \ No newline at end of file +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +annotation class Global \ No newline at end of file diff --git a/src/main/kotlin/dev/znci/rocket/scripting/api/RocketEnum.kt b/src/main/kotlin/dev/znci/rocket/scripting/api/RocketEnum.kt deleted file mode 100644 index 3be9016..0000000 --- a/src/main/kotlin/dev/znci/rocket/scripting/api/RocketEnum.kt +++ /dev/null @@ -1,44 +0,0 @@ -package dev.znci.rocket.scripting.api - -import org.luaj.vm2.LuaTable -import org.luaj.vm2.LuaValue -import kotlin.reflect.KClass - -/** - * Copyright 2025 znci - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -class RocketEnum(name: String) : RocketTable(name) { - fun toLuaTable(enum: Enum<*>): LuaTable { - val table = this.table - for (enumConstant in enum.javaClass.enumConstants) { - table.set(enumConstant.name, RocketLuaValue(LuaValue.valueOf(enumConstant.ordinal))) - } - return table - } - - fun fromLuaTable(luaTable: LuaTable, enum: KClass): Enum<*> { - val enumConstants = enum.java.enumConstants - for (i in 0 until luaTable.length()) { - val name = luaTable[i + 1].toString() - for (enumConstant in enumConstants as Array>) { - if (enumConstant.name == name) { - return enumConstant - } - } - } - throw RocketError("Enum constant not found") - } -} \ No newline at end of file diff --git a/src/main/kotlin/dev/znci/rocket/scripting/api/RocketLuaValue.kt b/src/main/kotlin/dev/znci/rocket/scripting/api/RocketLuaValue.kt deleted file mode 100644 index d0beff8..0000000 --- a/src/main/kotlin/dev/znci/rocket/scripting/api/RocketLuaValue.kt +++ /dev/null @@ -1,68 +0,0 @@ -package dev.znci.rocket.scripting.api - -import org.luaj.vm2.LuaValue - -/** - * A wrapper class for `LuaValue` that provides additional functionality and type safety. - * This class extends `LuaValue` and delegates method calls to the wrapped `luaValue` instance. - */ -open class RocketLuaValue(val luaValue: LuaValue = LuaValue.TRUE) : LuaValue() { - - /** - * Returns the type of the wrapped `LuaValue`. - * - * @return The integer type code of the `LuaValue`. - */ - override fun type(): Int { - return luaValue.type() - } - - /** - * Returns the type name of the wrapped `LuaValue`. - * - * @return The name of the Lua type as a string. - */ - override fun typename(): String? { - return luaValue.typename() - } - - companion object { - /** - * Represents a `nil` Lua value. - */ - val NIL = RocketLuaValue(LuaValue.NIL) - - /** - * Represents a `true` Lua value. - */ - val TRUE = RocketLuaValue(LuaValue.TRUE) - - /** - * Represents a `false` Lua value. - */ - val FALSE = RocketLuaValue(LuaValue.FALSE) - - /** - * Converts a given Kotlin value to a `RocketLuaValue` instance. - * - * @param value The value to be converted. - * @return A corresponding `RocketLuaValue` representing the input value. - */ - fun valueOf(value: Any?): RocketLuaValue { - return when (value) { - is String -> RocketLuaValue(LuaValue.valueOf(value)) - is Boolean -> RocketLuaValue(LuaValue.valueOf(value)) - is Int -> RocketLuaValue(LuaValue.valueOf(value)) - is Double -> RocketLuaValue(LuaValue.valueOf(value)) - is RocketTable -> RocketLuaValue(value.table) - is LuaValue -> RocketLuaValue(value) - else -> { - throw RocketError("Unsupported type: ${value?.javaClass?.simpleName ?: "null"}") - NIL - } - } - } - } - - override fun toString(): String = luaValue.toString() -} \ No newline at end of file diff --git a/src/main/kotlin/dev/znci/rocket/scripting/api/RocketNative.kt b/src/main/kotlin/dev/znci/rocket/scripting/api/RocketNative.kt deleted file mode 100644 index d538e1e..0000000 --- a/src/main/kotlin/dev/znci/rocket/scripting/api/RocketNative.kt +++ /dev/null @@ -1,238 +0,0 @@ -package dev.znci.rocket.scripting.api - -import dev.znci.rocket.scripting.api.annotations.RocketNativeFunction -import dev.znci.rocket.scripting.api.annotations.RocketNativeProperty -import org.luaj.vm2.LuaTable -import org.luaj.vm2.LuaValue -import org.luaj.vm2.Varargs -import org.luaj.vm2.lib.ThreeArgFunction -import org.luaj.vm2.lib.TwoArgFunction -import org.luaj.vm2.lib.VarArgFunction -import java.util.ArrayList -import kotlin.reflect.* -import kotlin.reflect.full.findAnnotation -import kotlin.reflect.full.functions -import kotlin.reflect.full.memberProperties -import kotlin.reflect.full.primaryConstructor - -/** - * Abstract class RocketNative serves as a bridge between Kotlin and Lua, allowing functions and properties - * to be dynamically registered in a Lua table. - * - * Code is written as Kotlin, and is converted to Lua if the appropriate function/property has the correct annotation. - * - * Functions with the {@code RocketNativeFunction} annotation will be registered, and properties with the {@code RocketNativeProperty} annotation. - */ -@Suppress("unused") -abstract class RocketNative( - /** The name of the Lua table/property for this object. */ - override var valueName: String -) : RocketTable(valueName) { - - /** - * Initializes the RocketNative instance by registering its functions and properties. - */ - init { - registerFunctions(this.table) - registerProperties(this.table) - } - - /** - * Registers functions annotated with {@code RocketNativeFunction} into the Lua table. - * - * @param table The LuaTable instance to register functions to. - */ - private fun registerFunctions(table: LuaTable) { - this::class.functions.forEach { function -> - if (function.findAnnotation() == null) { - return@forEach - } - val annotation = function.findAnnotation() - var annotatedFunctionName = annotation?.name ?: function.name - - if (annotatedFunctionName == "INHERIT_FROM_DEFINITION" ) { - annotatedFunctionName = function.name - } - - table.set(annotatedFunctionName, object : VarArgFunction() { - override fun invoke(args: Varargs): Varargs { - return try { - val kotlinArgs = args.toKotlinArgs(function) - val result = function.call(this@RocketNative, *kotlinArgs) - result.toLuaValue() - } catch (e: Exception) { - error("Error calling ${function.name}: ${e.message}") - } - } - }) - } - } - - /** - * Registers properties annotated with {@code RocketNativeProperty} into the Lua table. - * - * @param table The LuaTable instance to register properties to. - */ - private fun registerProperties(table: LuaTable) { - val properties = this::class.memberProperties - .mapNotNull { prop -> - prop.findAnnotation()?.let { annotation -> - val customName = annotation.name.takeIf { it != "INHERIT_FROM_DEFINITION" } ?: prop.name - customName to prop - } - }.toMap() - - val metatable = LuaTable() - - // Handle property getting - metatable.set("__index", object : TwoArgFunction() { - override fun call(self: LuaValue, key: LuaValue): LuaValue { - val prop = properties[key.tojstring()] as? KProperty<*> - ?: return error("No property '${key.tojstring()}'") - - return try { - val value = prop.getter.call(this@RocketNative) - value.toLuaValue() - } catch (e: Exception) { - error("Error getting '${prop.name}': ${e.message}") - } - } - }) - - // Handle property setting - metatable.set("__newindex", object : ThreeArgFunction() { - override fun call(self: LuaValue, key: LuaValue, value: LuaValue): LuaValue { - val prop = properties[key.tojstring()] as? KMutableProperty<*> - ?: return error("No property '${key.tojstring()}'") - - return try { - prop.setter.call(this@RocketNative, value.toKotlinValue(prop.returnType.classifier)) - TRUE - } catch (e: Exception) { - error("Error setting '${prop.name}': ${e.message}") - } - } - }) - - table.setmetatable(metatable) - } - - /** - * Converts Lua arguments to Kotlin arguments based on function parameter types. - * - * @param func The function whose parameters should be converted. - * @return An array of Kotlin compatible arguments. - */ - private fun Varargs.toKotlinArgs(func: KFunction<*>): Array { - val params = func.parameters.drop(1) // Skip `this` - return params.mapIndexed { index, param -> - this.arg(index + 1).let { arg -> - if (arg.istable()) { - arg.checktable().toClass(func) - } else { - arg.toKotlinValue(param.type.classifier) - } - } - }.toTypedArray() - } - - /** - * Converts a LuaValue to a Kotlin compatible value based on its type. - * - * @param type The expected Kotlin type. - * @return The converted value in Kotlin. - */ - private fun LuaValue.toKotlinValue(type: KClassifier?): Any? { - return when (type) { - String::class -> if (isnil()) null else tojstring() - Boolean::class -> toboolean() - Int::class -> toint() - Double::class -> todouble() - Float::class -> tofloat() - Long::class -> tolong() - else -> this - } - } - - /** - * Converts a Kotlin value to a LuaValue. - * - * @return The corresponding LuaValue. - */ - private fun Any?.toLuaValue(): LuaValue { - return when (this) { - is String -> LuaValue.valueOf(this) - is Boolean -> LuaValue.valueOf(this) - is Int -> LuaValue.valueOf(this) - is Double -> LuaValue.valueOf(this) - is Float -> LuaValue.valueOf(this.toDouble()) - is Long -> LuaValue.valueOf(this.toDouble()) - is RocketTable -> { - val table = this.table - set("__javaClass", TableSetOptions(getter = { LuaValue.valueOf(javaClass.name) })) - table - } - is RocketLuaValue -> { - throw RocketError("RocketLuaValue should not be used as a return type.") - } - is ArrayList<*> -> { - val table = LuaTable() - this.forEachIndexed { index, value -> - table.set(index + 1, value.toLuaValue()) - } - table - } - is Enum<*> -> { - val enumClass = this::class - val enumTable = RocketEnum(enumClass.simpleName!!) - enumTable.toLuaTable(this) - } - else -> { - throw RocketError("Unsupported type: ${this?.javaClass?.simpleName ?: "null"}") - } - } - } - - /** - * Converts a LuaTable into a given class. - */ - private fun LuaTable.toClass(func: KFunction<*>): Any { // XXX: stupid trick to also return enums. - try { - var className = get("__javaClass").tojstring() - if (className == "nil") { - var classes = func.parameters.map { it.type.classifier } - classes = classes.drop(1) - val tableProps = keys().asSequence().map { it.tojstring() }.toList() - for (clazz in classes) { - val constructor = (clazz as KClass<*>).primaryConstructor - if (constructor != null) { - val constructorProps = constructor.parameters.map { it.name } - if (constructorProps.containsAll(tableProps)) { - className = clazz.qualifiedName!! - break - } - } - } - - } - val clazz = Class.forName(className).kotlin - - // if enum class - if (clazz.java.isEnum) { - val renum = RocketEnum(clazz.simpleName!!) - return renum.fromLuaTable(this, clazz) - } else { - val constructor = - clazz.primaryConstructor - ?: throw IllegalArgumentException("No primary constructor found for $className") - val args = constructor.parameters.map { param -> - val value = get(param.name) - value.toKotlinValue(param.type.classifier) - }.toTypedArray() - return constructor.call(*args) as RocketTable - } - } catch (e: Exception) { - throw e - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/dev/znci/rocket/scripting/api/RocketProperty.kt b/src/main/kotlin/dev/znci/rocket/scripting/api/RocketProperty.kt deleted file mode 100644 index ba83eab..0000000 --- a/src/main/kotlin/dev/znci/rocket/scripting/api/RocketProperty.kt +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright 2025 znci - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dev.znci.rocket.scripting.api - -/** - * Represents a property in the Rocket framework, extending the `RocketValueBase` class. - * This class provides a basic structure for holding a property with a `valueName` and a `value`. - * It is used to represent properties that can be accessed and modified (getters/setters). - * - * @param valueName The name of the property. - */ -open class RocketProperty( - override var valueName: String, -) : RocketValueBase(valueName) { - /** - * The value of the property. - * This is a generic any field that can hold any type of data. - * It must be assigned before use. - */ - lateinit var value: Any -} \ No newline at end of file diff --git a/src/main/kotlin/dev/znci/rocket/scripting/api/RocketTable.kt b/src/main/kotlin/dev/znci/rocket/scripting/api/RocketTable.kt deleted file mode 100644 index 202e94a..0000000 --- a/src/main/kotlin/dev/znci/rocket/scripting/api/RocketTable.kt +++ /dev/null @@ -1,105 +0,0 @@ -/** - * Copyright 2025 znci - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dev.znci.rocket.scripting.api - -import org.luaj.vm2.LuaTable -import org.luaj.vm2.LuaValue -import org.luaj.vm2.lib.ThreeArgFunction -import org.luaj.vm2.lib.TwoArgFunction - -/** - * Data class that provides the options for setting properties on a `LuaTable`. - * It provides functionality for defining a getter, setter, and validator for properties in a table. - * - * @param getter The getter function for retrieving the property's value. It returns a `LuaValue` and can be `null`. - * @param setter The setter function for setting the property's value. It takes a `LuaValue` and can be `null`. - * @param validator The validator function that checks the validity of the value before setting it. It returns a `Boolean` and can be `null`. - */ -data class TableSetOptions( - val getter: (() -> LuaValue)?, - val setter: ((LuaValue) -> Unit)? = null, - val validator: ((LuaValue) -> Boolean)? = null -) - -/** - * Represents a table, which is a wrapper around a `LuaTable`. - * This class provides functionality for managing Lua properties and exposing them with custom getter, setter, and validation logic. - * It allows you to set properties dynamically on the table, with custom logic for getting, setting, and validating property values. - * - * @param valueName The name of the table, used for specifying the name of the table if it becomes a global. - */ -open class RocketTable( - override var valueName: String -): RocketValueBase(valueName) { - /** - * The internal `LuaTable` instance that holds the table's data. - */ - val table: LuaTable = LuaTable() - - - /** - * Sets a property on the table with custom getter, setter, and validator options. - * - * @param propertyName The name of the property to set on the table. - * @param options The options that define how the property should behave, including getter, setter, and validator. - */ - fun set( - propertyName: String, - options: TableSetOptions - ) { - // FIXME: this null chuck is redundant - val meta = table.getmetatable() ?: LuaTable() - val indexFunction = meta.get("__index") as? TwoArgFunction - val newIndexFunction = meta.get("__newindex") as? ThreeArgFunction - - // Define the getter - if (options.getter != null) { - meta.set("__index", object : TwoArgFunction() { - override fun call(table: LuaValue, key: LuaValue): LuaValue { - if (key.tojstring() == propertyName) { - return options.getter() - } - return indexFunction?.call(table, key) ?: NIL - } - }) - } - - // Define the setter - meta.set("__newindex", object : ThreeArgFunction() { - override fun call(table: LuaValue, key: LuaValue, value: LuaValue): LuaValue { - if (key.tojstring() == propertyName) { - if (options.setter != null) { - if (options.validator != null && !options.validator(value)) { - error("Invalid input for '$propertyName', got ${value.typename()} (value: ${value.tojstring()})") - } - options.setter(value) - } else { - return NIL - } - } else { - newIndexFunction?.call(table, key, value) - } - return NIL - } - }) - - table.setmetatable(meta) - } - - fun setSimple(propertyName: String, value: Any) { - table.set(propertyName, valueOf(value).luaValue) - } -} diff --git a/src/main/kotlin/dev/znci/rocket/scripting/api/RocketValueBase.kt b/src/main/kotlin/dev/znci/rocket/scripting/api/RocketValueBase.kt deleted file mode 100644 index 8eead85..0000000 --- a/src/main/kotlin/dev/znci/rocket/scripting/api/RocketValueBase.kt +++ /dev/null @@ -1,13 +0,0 @@ -package dev.znci.rocket.scripting.api - -/** - * Base class for Rocket API values, extending `RocketLuaValue`. - * This class provides a common structure for all Rocket values, including a `valueName` - * that identifies the value within the Rocket API. - * It serves as a superclass for other classes that represent values in the Rocket API. - * - * @param valueName The name of the value, used for specifying the name of the value if it becomes a global. - */ -open class RocketValueBase( - open var valueName: String -): RocketLuaValue() \ No newline at end of file diff --git a/src/main/kotlin/dev/znci/rocket/scripting/api/annotations/RocketNativeFunction.kt b/src/main/kotlin/dev/znci/rocket/scripting/api/annotations/RocketNativeFunction.kt deleted file mode 100644 index d1651bf..0000000 --- a/src/main/kotlin/dev/znci/rocket/scripting/api/annotations/RocketNativeFunction.kt +++ /dev/null @@ -1,30 +0,0 @@ - -/** - * Copyright 2025 znci - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dev.znci.rocket.scripting.api.annotations - -/** - * Annotation to mark a function as a native function in the Rocket framework. - * This allows functions to be registered as callable from Lua. - * - * @param name The name of the function when it is added to a LuaTable. - * If not specified, the default name is "INHERIT_FROM_DEFINITION", - * which inherits the function name. - */ -@Target(AnnotationTarget.FUNCTION) -@Retention(AnnotationRetention.RUNTIME) -@Suppress("unused") -annotation class RocketNativeFunction(val name: String = "INHERIT_FROM_DEFINITION") \ No newline at end of file diff --git a/src/main/kotlin/dev/znci/rocket/scripting/api/annotations/RocketNativeProperty.kt b/src/main/kotlin/dev/znci/rocket/scripting/api/annotations/RocketNativeProperty.kt deleted file mode 100644 index be8548b..0000000 --- a/src/main/kotlin/dev/znci/rocket/scripting/api/annotations/RocketNativeProperty.kt +++ /dev/null @@ -1,26 +0,0 @@ - -/** - * Copyright 2025 znci - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dev.znci.rocket.scripting.api.annotations - -/** - * Annotation to mark a property as a native property in the Rocket framework. - * This allows the property to be exposed to Lua, making it accessible for manipulation from Lua scripts. - */ -@Target(AnnotationTarget.PROPERTY) -@Retention(AnnotationRetention.RUNTIME) -@Suppress("unused") -annotation class RocketNativeProperty(val name: String = "INHERIT_FROM_DEFINITION") \ No newline at end of file diff --git a/src/main/kotlin/dev/znci/rocket/scripting/classes/Command.kt b/src/main/kotlin/dev/znci/rocket/scripting/classes/CommandReference.kt similarity index 67% rename from src/main/kotlin/dev/znci/rocket/scripting/classes/Command.kt rename to src/main/kotlin/dev/znci/rocket/scripting/classes/CommandReference.kt index efed6c1..a796d7b 100644 --- a/src/main/kotlin/dev/znci/rocket/scripting/classes/Command.kt +++ b/src/main/kotlin/dev/znci/rocket/scripting/classes/CommandReference.kt @@ -15,7 +15,10 @@ */ package dev.znci.rocket.scripting.classes +import dev.znci.rocket.Rocket +import dev.znci.twine.TwineTable import org.bukkit.command.CommandExecutor +import org.bukkit.command.TabCompleter /** * Represents a command in the Rocket API, containing information about the command's name, @@ -30,16 +33,20 @@ import org.bukkit.command.CommandExecutor * @param permissionMessage The message shown when the user does not have the required permission to use the command. * The message should be in MiniMessage format. * @param aliases A list of alternative names for the command. + * @param fallbackPrefix The prefix used for the command to override the default command prefix defined in the config. * @param argCount The number of arguments that the command expects. * @param executor The `CommandExecutor` responsible for executing the command logic. + * @param tabCompleter The `TabCompleter` responsible for tab completions. */ -data class Command( - val name: String, - var description: String, - var usage: String, - var permission: String, - var permissionMessage: String, - var aliases: List, - var argCount: Int, - var executor: CommandExecutor -) \ No newline at end of file +data class CommandReference( + var name: String = "", + var description: String = "", + var usage: String = "", + var permission: String = "", + var permissionMessage: String = "", + var aliases: List = listOf(), + var fallbackPrefix: String = Rocket.instance.config.getString("command-fallback-prefix") ?: "rocket", + var argCount: Int = 0, + var executor: CommandExecutor = CommandExecutor { _, _, _, _ -> true }, + var tabCompleter: TabCompleter = TabCompleter { _, _, _, _ -> listOf() } +) : TwineTable("") \ No newline at end of file diff --git a/src/main/kotlin/dev/znci/rocket/scripting/events/EventListener.kt b/src/main/kotlin/dev/znci/rocket/scripting/events/EventListener.kt index aafa66c..0455eaf 100644 --- a/src/main/kotlin/dev/znci/rocket/scripting/events/EventListener.kt +++ b/src/main/kotlin/dev/znci/rocket/scripting/events/EventListener.kt @@ -15,8 +15,9 @@ */ package dev.znci.rocket.scripting.events -import dev.znci.rocket.scripting.PlayerManager import dev.znci.rocket.scripting.ScriptManager +import dev.znci.rocket.scripting.globals.tables.LuaPlayer +import dev.znci.rocket.scripting.globals.tables.LuaPlayers import org.bukkit.Bukkit import org.bukkit.event.Cancellable import org.bukkit.event.Event @@ -107,7 +108,7 @@ object EventListener : Listener { } if (player != null) { - luaTable.set("player", PlayerManager.getPlayerTable(player)) + luaTable.set("player", LuaPlayer(player)) } // Interaction event diff --git a/src/main/kotlin/dev/znci/rocket/scripting/globals/tables/Commands.kt b/src/main/kotlin/dev/znci/rocket/scripting/globals/tables/Commands.kt index d8c702f..ecdd52e 100644 --- a/src/main/kotlin/dev/znci/rocket/scripting/globals/tables/Commands.kt +++ b/src/main/kotlin/dev/znci/rocket/scripting/globals/tables/Commands.kt @@ -17,241 +17,192 @@ package dev.znci.rocket.scripting.globals.tables import dev.znci.rocket.i18n.LocaleManager import dev.znci.rocket.scripting.PermissionsManager -import dev.znci.rocket.scripting.PlayerManager -import dev.znci.rocket.scripting.ScriptManager -import dev.znci.rocket.scripting.util.defineProperty +import dev.znci.rocket.scripting.annotations.Global +import dev.znci.rocket.scripting.classes.CommandReference import dev.znci.rocket.util.MessageFormatter +import dev.znci.twine.TwineNative +import dev.znci.twine.annotations.TwineNativeFunction import org.bukkit.Bukkit import org.bukkit.command.Command import org.bukkit.command.CommandExecutor import org.bukkit.command.CommandMap import org.bukkit.command.CommandSender -import org.bukkit.command.defaults.BukkitCommand +import org.bukkit.command.TabCompleter import org.bukkit.entity.Player import org.luaj.vm2.LuaTable -import org.luaj.vm2.LuaValue -import org.luaj.vm2.lib.OneArgFunction -import org.luaj.vm2.lib.TwoArgFunction -import org.luaj.vm2.lib.ZeroArgFunction -class LuaCommands : LuaTable() { - init { - set("register", object : TwoArgFunction() { - override fun call(eventName: LuaValue, callback: LuaValue): LuaValue { - val commandStr = eventName.tojstring() - val luaCallback = callback.checkfunction() - - val returnedTable = luaCallback.call() - - val customCommand = object : BukkitCommand(commandStr) { - override fun execute(sender: CommandSender, commandLabel: String, args: Array): Boolean { - val table = PlayerManager.getPlayerTable(sender as Player) - val luaArgs = convertArgsToLua(args) - - val referenceFunction = returnedTable.get("reference").checkfunction() - val reference = referenceFunction.call() - val permission = reference.get("permission").tojstring() - val usage = reference.get("usage").tojstring() - val permissionMessage = reference.get("permissionMessage").tojstring() - val argCount = reference.get("argCount").toint() - - // If the player does not have permission, show the configured no permission message - // or the default no permission message - if (permission != "" && PermissionsManager.hasPermission(sender, permission).not()) { - var noPermissionMessage = LocaleManager.getMessageAsComponent("no_permission") - if (permissionMessage.isNotEmpty()) { - noPermissionMessage = MessageFormatter.formatMessage(permissionMessage) - } - sender.sendMessage(noPermissionMessage) - return true - } - - // If no arguments are provided, show the usage - if (args.isEmpty() && argCount > 0) { - var usageMessage = LocaleManager.getMessageAsComponent("invalid_action") - if (usage.isNotEmpty()) { - usageMessage = MessageFormatter.formatMessage(usage) - } - sender.sendMessage(usageMessage) - return true +@Global +class LuaCommands : TwineNative("commands") { + @TwineNativeFunction + fun new(): LuaCommand { + return LuaCommand() + } +} + +class LuaCommand() : TwineNative("") { + val commandReference = CommandReference() + + @TwineNativeFunction + fun register(): LuaCommand { + try { + // TODO: Add alias support + unregisterCommand(commandReference.name) + val command = object : Command(commandReference.name) { + override fun execute(sender: CommandSender, commandLabel: String, args: Array): Boolean { + val player = sender as Player + + // Show the no permission message if a permission is provided + if (commandReference.permission != "" && PermissionsManager.hasPermission(sender, commandReference.permission)) { + var noPermissionMessage = LocaleManager.getMessageAsComponent("no_permission") + + // Uses the user-specified no permission message if it is provided + if (commandReference.permissionMessage.isNotEmpty()) { + noPermissionMessage = MessageFormatter.formatMessage(commandReference.permissionMessage) } - returnedTable.get("executor_function").checkfunction().call(table, luaArgs) + player.sendMessage(noPermissionMessage) return true } - } - - registerCommand(customCommand) - - return NIL - } - }) - - set("unregister", object : TwoArgFunction() { - override fun call(eventName: LuaValue, callback: LuaValue): LuaValue { - val commandStr = eventName.tojstring() - - unregisterCommand(commandStr) - - ScriptManager.enabledCommands.remove(commandStr) - - return NIL - } - }) - set("new", object : ZeroArgFunction() { - override fun call(): LuaValue { - val table = LuaTable() - val commandReference = dev.znci.rocket.scripting.classes.Command( - name = "", - description = "", - usage = "", - permission = "", - permissionMessage = "", - aliases = listOf(""), - argCount = 0 - ) { _, _, _, _ -> true } - - table.set("aliases", object : OneArgFunction() { - override fun call(arg: LuaValue?): LuaValue { - if (arg != null) { - val aliases = arg.checktable() - for (i in 1..aliases.length()) { - commandReference.aliases = commandReference.aliases.plus(aliases.get(i).tojstring()) - } + // Show the no args message if none are provided and the argCount is above 0 + if (args.isEmpty() && commandReference.argCount > 0) { + var usageMessage = LocaleManager.getMessageAsComponent("invalid_action") + if (commandReference.usage.isNotEmpty()) { + usageMessage = MessageFormatter.formatMessage(commandReference.usage) } - - return table + player.sendMessage(usageMessage) + return true } - }) - - table.set("executor", object : OneArgFunction() { - override fun call(arg: LuaValue?): LuaValue { - if (arg != null) { - val luaCallback = arg.checkfunction() - commandReference.executor = CommandExecutor { sender, _, _, args -> - val luaArgs = convertArgsToLua(args) - val senderTable = PlayerManager.getPlayerTable(sender as Player) - - luaCallback.call(senderTable, luaArgs) - return@CommandExecutor true - } + commandReference.executor.onCommand(sender, this, "", args) + return true + } - table.set("executor_function", luaCallback) - } + override fun tabComplete( + sender: CommandSender, + alias: String, + args: Array + ): List { + val returnedCompletions = commandReference.tabCompleter.onTabComplete(sender, this, "", args) + ?.filterNotNull() + ?: emptyList() - return table - } - }) + return returnedCompletions + } + } - table.set("description", object : OneArgFunction() { - override fun call(arg: LuaValue?): LuaValue { - if (arg != null) { - val description = arg.tojstring() - commandReference.description = description - } + // Use reflection to make the commandMap accessible to forcefully add the command + val commandMap = Bukkit.getServer().javaClass.getDeclaredField("commandMap").apply { + isAccessible = true + }.get(Bukkit.getServer()) as CommandMap - return table - } - }) + commandMap.register(commandReference.fallbackPrefix, command) - table.set("usage", object : OneArgFunction() { - override fun call(arg: LuaValue?): LuaValue { - if (arg != null) { - val usage = arg.tojstring() - - commandReference.usage = usage - } + updateCommandsForAll() + } catch (e: Exception) { + e.printStackTrace() + } + return this + } - return table - } - }) + private fun updateCommandsForAll() { + Bukkit.getServer().onlinePlayers.forEach { player -> + player.updateCommands() + } + } - table.set("permission", object : OneArgFunction() { - override fun call(arg: LuaValue?): LuaValue { - if (arg != null) { - val permission = arg.tojstring() - commandReference.permission = permission - } + private fun unregisterCommand(commandName: String): Boolean { + try { + val commandMap = Bukkit.getServer().javaClass.getDeclaredField("commandMap").apply { + isAccessible = true + }.get(Bukkit.getServer()) as CommandMap - return table - } - }) + val existingCommand = commandMap.getCommand(commandName) + if (existingCommand != null) { + commandMap.knownCommands.remove(existingCommand.name) + existingCommand.unregister(commandMap) + } - table.set("permissionMessage", object : OneArgFunction() { - override fun call(arg: LuaValue?): LuaValue { - if (arg != null) { - val permissionMessage = arg.tojstring() - commandReference.permissionMessage = permissionMessage - } + return true + } catch (e: Exception) { + // The command *probably* isn't in existence, so it throws an error + return false + } + } - return table - } - }) + @TwineNativeFunction + fun aliases(aliasList: Array): LuaCommand { + for (i in 1..aliasList.size) { + commandReference.aliases = commandReference.aliases.plus(aliasList[i]) + } + return this + } - table.set("argCount", object : OneArgFunction() { - override fun call(arg: LuaValue?): LuaValue { - if (arg != null) { - val argCount = arg.toint() - commandReference.argCount = argCount - } + @TwineNativeFunction + fun executor(callback: (sender: LuaPlayer, args: Array) -> Unit): LuaCommand { + commandReference.executor = CommandExecutor { commandSender, _, _, commandArgs -> + callback(LuaPlayer(commandSender as Player), commandArgs) + return@CommandExecutor true + } + return this + } - return table + @TwineNativeFunction + fun tabCompleter(callback: (sender: LuaPlayer, args: Array) -> Any): LuaCommand { + commandReference.tabCompleter = TabCompleter { commandSender, _, _, commandArgs -> + val result = callback(LuaPlayer(commandSender as Player), commandArgs) + when (result) { + is LuaTable -> { + val list = List(result.rawlen()) { i -> + result.get(i + 1).tojstring() } - }) - - table.set("reference", object : ZeroArgFunction() { - override fun call(): LuaValue { - val commandTable = LuaTable() - val luaAliases = LuaTable() - val aliases = commandReference.aliases - - for (i in 1..aliases.size) { - luaAliases.set(i, valueOf(aliases[i - 1])) - } - - defineProperty(commandTable, "aliases", { luaAliases }) - defineProperty(commandTable, "description", { valueOf(commandReference.description) }) - defineProperty(commandTable, "usage", { valueOf(commandReference.usage) }) - defineProperty(commandTable, "permission", { valueOf(commandReference.permission) }) - defineProperty(commandTable, "permissionMessage", { valueOf(commandReference.permissionMessage) }) - defineProperty(commandTable, "argCount", { valueOf(commandReference.argCount) }) + list + } + else -> emptyList() + } + } + return this + } - return commandTable - } - }) + @TwineNativeFunction + fun name(text: String): LuaCommand { + commandReference.name = text + return this + } - return table - } - }) + @TwineNativeFunction + fun description(text: String): LuaCommand { + commandReference.description = text + return this } - private fun registerCommand(command: Command) { - val commandMap = Bukkit.getServer().javaClass.getDeclaredField("commandMap").apply { - isAccessible = true - }.get(Bukkit.getServer()) as CommandMap + @TwineNativeFunction + fun usage(text: String): LuaCommand { + commandReference.usage = text + return this + } - commandMap.register(command.name, command) + @TwineNativeFunction + fun permission(text: String): LuaCommand { + commandReference.permission = text + return this } - private fun unregisterCommand(commandName: String) { - val commandMap = Bukkit.getServer().javaClass.getDeclaredField("commandMap").apply { - isAccessible = true - }.get(Bukkit.getServer()) as CommandMap + @TwineNativeFunction + fun permissionMessage(text: String): LuaCommand { + commandReference.permissionMessage = text + return this + } - val existingCommand = commandMap.getCommand(commandName) - if (existingCommand != null) { - commandMap.knownCommands.remove(existingCommand.name) - existingCommand.unregister(commandMap) - } + @TwineNativeFunction + fun argCount(value: Int): LuaCommand { + commandReference.argCount = value + return this } - private fun convertArgsToLua(args: Array): LuaTable { - val luaArgs = LuaTable() - for ((index, arg) in args.withIndex()) { - luaArgs.set(index + 1, valueOf(arg)) - } - return luaArgs + @TwineNativeFunction + fun fallbackPrefix(value: String): LuaCommand { + commandReference.fallbackPrefix = value + return this } } \ No newline at end of file diff --git a/src/main/kotlin/dev/znci/rocket/scripting/globals/tables/HTTPClient.kt b/src/main/kotlin/dev/znci/rocket/scripting/globals/tables/HTTPClient.kt index 7122d69..3ab9614 100644 --- a/src/main/kotlin/dev/znci/rocket/scripting/globals/tables/HTTPClient.kt +++ b/src/main/kotlin/dev/znci/rocket/scripting/globals/tables/HTTPClient.kt @@ -15,37 +15,99 @@ */ package dev.znci.rocket.scripting.globals.tables -import org.luaj.vm2.LuaTable -import org.luaj.vm2.LuaValue -import org.luaj.vm2.lib.OneArgFunction +import com.google.gson.JsonElement +import com.google.gson.JsonParser +import dev.znci.rocket.scripting.annotations.Global +import dev.znci.rocket.scripting.api.RocketError +import dev.znci.twine.TwineNative +import dev.znci.twine.TwineTable +import dev.znci.twine.annotations.TwineNativeFunction +import dev.znci.twine.annotations.TwineNativeProperty import java.net.URI import java.net.http.HttpClient +import java.net.http.HttpConnectTimeoutException import java.net.http.HttpRequest import java.net.http.HttpResponse +import java.time.Duration -class LuaHTTPClient : LuaTable() { - init { - set("get", object : OneArgFunction() { - override fun call(url: LuaValue): LuaValue { - return try { - val response = httpGet(url.tojstring()) - - valueOf(response) - } catch (e: Exception) { - NIL - } - } - }) +// TODO: Add map support, and come back to HTTPClient later. + +data class HTTPOptions( + val timeout: Int? = 30000, + val followRedirects: Boolean? = true, +// val headers: Map?, +// val body: Map? +) : TwineTable("") + +@Global +class LuaHTTPClient : TwineNative("http") { + @TwineNativeFunction("get") + fun sendGet(url: String, options: HTTPOptions): HTTPResponse { + return try { + val client = getClient(options) + + val requestBuilder = HttpRequest.newBuilder() + .uri(URI.create(url)) + .GET() + +// applyHeaders(requestBuilder, options) + + val request = requestBuilder.build() + + val response = client.send(request, HttpResponse.BodyHandlers.ofString()) + val responseBody = response.body() + val jsonElement = JsonParser.parseString(responseBody) + + HTTPResponse( + textContent = responseBody.toString(), + jsonContent = jsonElement + ) + } catch (e: HttpConnectTimeoutException) { + throw RocketError(getTimeoutMessage(options.timeout)) + } } - private fun httpGet(url: String): String { - val client = HttpClient.newHttpClient() - val request = HttpRequest.newBuilder() - .uri(URI.create(url)) - .build() + fun getClient(options: HTTPOptions): HttpClient { + return HttpClient.newBuilder().apply { + options.timeout?.let { + connectTimeout(Duration.ofMillis(it.toLong())) + } + followRedirects( + if (options.followRedirects == true) + HttpClient.Redirect.NORMAL + else + HttpClient.Redirect.NEVER + ) + }.build() + } - val response = client.send(request, HttpResponse.BodyHandlers.ofString()) +// fun applyHeaders(requestBuilder: HttpRequest.Builder, options: HTTPOptions) { +// options.headers?.forEach { (key, value) -> +// requestBuilder.header(key, value.toString()) +// } +// } - return response.body() + fun getTimeoutMessage(timeout: Int?): String { + return "HTTP Client request timed out after ${timeout}ms. Maybe your timeout threshold is too low." } -} \ No newline at end of file +} + +class HTTPResponse( + val textContent: String, + val jsonContent: JsonElement? +) : TwineNative("") { + @TwineNativeProperty + val text: String + get() { + return textContent + } + + @TwineNativeProperty + val json: TwineTable? + get() { + if (jsonContent == null) { + return null + } + return fromJSON(jsonContent) as TwineTable? + } +} diff --git a/src/main/kotlin/dev/znci/rocket/scripting/globals/tables/Location.kt b/src/main/kotlin/dev/znci/rocket/scripting/globals/tables/Location.kt index ace971f..92fd2c8 100644 --- a/src/main/kotlin/dev/znci/rocket/scripting/globals/tables/Location.kt +++ b/src/main/kotlin/dev/znci/rocket/scripting/globals/tables/Location.kt @@ -15,21 +15,22 @@ */ package dev.znci.rocket.scripting.globals.tables -import dev.znci.rocket.scripting.api.RocketNative -import dev.znci.rocket.scripting.api.RocketTable -import dev.znci.rocket.scripting.api.annotations.RocketNativeFunction -import dev.znci.rocket.scripting.api.annotations.RocketNativeProperty +import dev.znci.rocket.scripting.annotations.Global +import dev.znci.rocket.scripting.api.RocketError import dev.znci.rocket.scripting.util.getWorldByNameOrUUID +import dev.znci.twine.TwineNative +import dev.znci.twine.annotations.TwineNativeFunction +import dev.znci.twine.annotations.TwineNativeProperty import org.bukkit.Bukkit import org.bukkit.Location import org.bukkit.World -import org.luaj.vm2.LuaTable import org.luaj.vm2.LuaValue import java.util.* -class LuaLocations : RocketNative("location") { - @RocketNativeFunction - fun new(x: Double, y: Double, z: Double, worldUUID: String, yaw: Float = 0f, pitch: Float = 0f): LuaLocation { +@Global +class LuaLocations : TwineNative("location") { + @TwineNativeFunction + fun new(x: Double, y: Double, z: Double, worldUUID: String, yaw: Float = 0f, pitch: Float = 0f): LuaLocation? { return LuaLocation(x, y, z, worldUUID, yaw, pitch) } } @@ -42,53 +43,53 @@ class LuaLocation( worldUUID: String, yaw: Float = 0f, pitch: Float = 0f -) : RocketNative("") { +) : TwineNative("") { private var world: World? = getWorldByNameOrUUID(worldUUID) private var location: Location = Location(world, x, y, z, yaw, pitch) - @RocketNativeProperty("x") + @TwineNativeProperty("x") var xProperty: Double get() = location.x set(value) { location.x = value } - @RocketNativeProperty("y") + @TwineNativeProperty("y") var yProperty: Double get() = location.y set(value) { location.y = value } - @RocketNativeProperty("z") + @TwineNativeProperty("z") var zProperty: Double get() = location.z set(value) { location.z = value } - @RocketNativeProperty("world") + @TwineNativeProperty("world") var worldProperty: String get() = location.world.name set(value) { location.world = getWorldByNameOrUUID(value) } - @RocketNativeProperty("worldUUID") + @TwineNativeProperty("worldUUID") var worldUUIDProperty: String get() = location.world.uid.toString() set(value) { location.world = getWorldByNameOrUUID(value) } - @RocketNativeProperty("yaw") + @TwineNativeProperty("yaw") var yawProperty: Float get() = location.yaw set(value) { location.yaw = value } - @RocketNativeProperty("pitch") + @TwineNativeProperty("pitch") var pitchProperty: Float get() = location.pitch set(value) { @@ -109,7 +110,7 @@ class LuaLocation( } companion object { - fun fromBukkit(location: Location): RocketTable { + fun fromBukkit(location: Location): LuaLocation { return LuaLocation( location.x, location.y, @@ -120,69 +121,28 @@ class LuaLocation( ) } } -} + fun toBukkitLocation(): Location { + return try { + val x = this.xProperty + val y = this.yProperty + val z = this.zProperty + val worldUUIDStr = this.worldUUIDProperty -// -//class LuaLocation( -// x: Double, -// y: Double, -// z: Double, -// worldUUID: String, -// yaw: Float = 0f, -// pitch: Float = 0f -//) : LuaTable() { -// private var world: World? = Bukkit.getWorld(UUID.fromString(worldUUID)) -// private var location: Location = Location(world, x, y, z, yaw, pitch) -// -// companion object { -// fun fromBukkit(location: Location): LuaTable { -// return LuaLocation( -// location.x, -// location.y, -// location.z, -// location.world.uid.toString(), -// location.yaw, -// location.pitch -// ).getLocationTable() -// } -// } -// -// fun getLocationTable(): LuaTable { -// val table = LuaTable() -// -// defineProperty(table, "x", { valueOf(location.x) }, { value -> location.x = value.todouble() }) -// defineProperty(table, "y", { valueOf(location.y) }, { value -> location.y = value.todouble() }) -// defineProperty(table, "z", { valueOf(location.z) }, { value -> location.z = value.todouble() }) -// defineProperty(table, "world", { valueOf(location.world.name) }, { value -> location.world = Bukkit.getWorld(UUID.fromString(value.tojstring())) }) -// defineProperty(table, "worldUUID", { valueOf(location.world.uid.toString()) }, { value -> location.world = Bukkit.getWorld(UUID.fromString(value.tojstring())) }) -// defineProperty(table, "yaw", { valueOf(location.yaw.toDouble()) }, { value -> location.yaw = value.tofloat() }) -// defineProperty(table, "pitch", { valueOf(location.pitch.toDouble()) }, { value -> location.pitch = value.tofloat() }) -// -// return table -// } -//} - -fun LuaValue.toBukkitLocation(): Location { - if (this !is LuaTable) { - error("Expected a LuaTable, got ${this.typename()} (value: ${this.tojstring()})") - } + val worldUUID = try { + UUID.fromString(worldUUIDStr) + } catch (e: IllegalArgumentException) { + Bukkit.getWorld("world")?.uid ?: throw RocketError("Default world \"world\" does not exist.") + } + + val world = Bukkit.getWorld(worldUUID) + + val yaw = this.yawProperty + val pitch = this.pitchProperty - return try { - val x = this.get("x").todouble() - val y = this.get("y").todouble() - val z = this.get("z").todouble() - val worldUUIDStr = this.get("worldUUID").tojstring() - val worldUUID = try { - UUID.fromString(worldUUIDStr) - } catch (e: IllegalArgumentException) { - error("Invalid 'worldUUID': Not a valid UUID (value: $worldUUIDStr)") + Location(world, x, y, z, yaw, pitch) + } catch (e: Exception) { + throw RocketError("Invalid location provided.") } - val world = Bukkit.getWorld(worldUUID) - val yaw = this.get("yaw").tofloat() - val pitch = this.get("pitch").tofloat() - Location(world, x, y, z, yaw, pitch) - } catch (e: Exception) { - error("LuaTable does not represent a valid location: ${e.message}") } } \ No newline at end of file diff --git a/src/main/kotlin/dev/znci/rocket/scripting/globals/tables/Players.kt b/src/main/kotlin/dev/znci/rocket/scripting/globals/tables/Players.kt index 4c73149..3654565 100644 --- a/src/main/kotlin/dev/znci/rocket/scripting/globals/tables/Players.kt +++ b/src/main/kotlin/dev/znci/rocket/scripting/globals/tables/Players.kt @@ -16,67 +16,104 @@ package dev.znci.rocket.scripting.globals.tables import dev.znci.rocket.scripting.PermissionsManager +import dev.znci.rocket.scripting.annotations.Global import dev.znci.rocket.scripting.api.RocketError -import dev.znci.rocket.scripting.api.RocketNative -import dev.znci.rocket.scripting.api.RocketTable -import dev.znci.rocket.scripting.api.annotations.RocketNativeFunction -import dev.znci.rocket.scripting.api.annotations.RocketNativeProperty import dev.znci.rocket.util.MessageFormatter +import dev.znci.twine.TwineNative +import dev.znci.twine.TwineTable +import dev.znci.twine.annotations.TwineNativeFunction +import dev.znci.twine.annotations.TwineNativeProperty import net.kyori.adventure.title.Title import net.kyori.adventure.title.TitlePart import org.bukkit.Bukkit import org.bukkit.GameMode -import org.bukkit.Location +import org.bukkit.OfflinePlayer import org.bukkit.entity.Player import java.time.Duration +import java.util.UUID @Suppress("unused") -class LuaPlayers : RocketNative("players") { - @RocketNativeFunction("get") - fun getPlayerByName(playerName: String): LuaPlayer { - val player = Bukkit.getPlayer(playerName) ?: throw RocketError("Player not found") +@Global +class LuaPlayers : TwineNative("players") { + @TwineNativeFunction("get") + fun getPlayerByName(playerName: String): LuaPlayer? { + val player = Bukkit.getPlayer(playerName) + + if (player == null) { + return null + } return LuaPlayer(player) } - @RocketNativeFunction("getByUUID") - fun getPlayerByUUID(playerUUID: String): LuaPlayer { - val player = Bukkit.getPlayer(playerUUID) ?: throw RocketError("Player not found") + @TwineNativeFunction("getByUUID") + fun getPlayerByUUID(playerUUID: String): LuaPlayer? { + val player = Bukkit.getPlayer(playerUUID) + + if (player == null) { + return null + } return LuaPlayer(player) } - @RocketNativeFunction("getAll") + @TwineNativeFunction("getAll") fun getAllPlayers(): List { return Bukkit.getOnlinePlayers().map { LuaPlayer(it) } } + + @TwineNativeFunction("getOfflinePlayer") + fun getOfflinePlayer(playerName: String): LuaOfflinePlayer { + return LuaOfflinePlayer(Bukkit.getOfflinePlayer(playerName)) + } + + @TwineNativeFunction("getOfflinePlayerByUUID") + fun getOfflinePlayerByUUID(playerUUID: String): LuaOfflinePlayer { + return LuaOfflinePlayer(Bukkit.getOfflinePlayer(UUID.fromString(playerUUID))) + } + + @TwineNativeFunction("getCachedOfflinePlayer") + fun getCachedOfflinePlayer(playerName: String): LuaOfflinePlayer? { + val offlinePlayer = Bukkit.getOfflinePlayerIfCached(playerName) + + return if (offlinePlayer == null) { + null + } else { + LuaOfflinePlayer(offlinePlayer) + } + } + + @TwineNativeFunction("getAllOfflinePlayers") + fun getAllOfflinePlayers(): List { + return Bukkit.getOfflinePlayers().map { LuaOfflinePlayer(it) } + } } data class TitleTimeTable( val fadeIn: Long, val stay: Long, val fadeOut: Long -) : RocketTable("") +) : TwineTable("") @Suppress("unused") class LuaPlayer( - val player: Player -) : RocketNative("") { - @RocketNativeFunction + override val player: Player +) : LuaOfflinePlayer(player) { + @TwineNativeFunction fun send(message: Any): Boolean { val messageComponent = MessageFormatter.formatMessage(message.toString()) player.sendMessage(messageComponent) return true } - @RocketNativeFunction + @TwineNativeFunction fun sendActionbar(message: String): Boolean { val messageComponent = MessageFormatter.formatMessage(message) player.sendActionBar(messageComponent) return true } - @RocketNativeFunction + @TwineNativeFunction fun sendTitle(message: String, timeTable: TitleTimeTable): Boolean { val messageComponent = MessageFormatter.formatMessage(message) @@ -90,7 +127,7 @@ class LuaPlayer( return true } - @RocketNativeFunction + @TwineNativeFunction fun sendSubtitle(message: String, timeTable: TitleTimeTable): Boolean { val messageComponent = MessageFormatter.formatMessage(message) val times = Title.Times.times( @@ -103,138 +140,198 @@ class LuaPlayer( return true } - @RocketNativeFunction + @TwineNativeFunction fun setPlayerTime(value: Long, relative: Boolean): Boolean { player.setPlayerTime(value, relative) return true } - @RocketNativeFunction + @TwineNativeFunction fun addPermission(value: String): Boolean { player.addAttachment( - Bukkit.getPluginManager().getPlugin("Rocket")!! + Bukkit.getPluginManager().getPlugin("Twine")!! ).setPermission(value, true) return true } - @RocketNativeFunction + @TwineNativeFunction fun op(): Boolean { player.isOp = true return true } - @RocketNativeFunction + @TwineNativeFunction fun deop(): Boolean { player.isOp = false return true } - @RocketNativeFunction + @TwineNativeFunction fun teleport(location: LuaLocation): Boolean { player.teleport(location.toBukkitLocation()) return true } - @RocketNativeFunction + @TwineNativeFunction fun hasPermission(value: String): Boolean { - println(PermissionsManager.hasPermission(player, value)) return PermissionsManager.hasPermission(player, value) } - @RocketNativeFunction + @TwineNativeFunction fun isInGroup(value: String): Boolean { return PermissionsManager.isPlayerInGroup(player, value) } - @RocketNativeFunction + @TwineNativeFunction fun setGamemode(value: String): Boolean { - if (false) { // TODO: enum - throw RocketError("Invalid gamemode") - } - player.gameMode = GameMode.valueOf(value) return true } - @RocketNativeProperty - val name: String + @TwineNativeProperty + override val location: LuaLocation get() { - return player.name + return LuaLocation.fromBukkit(player.location) } - @RocketNativeProperty - val uuid: String + @TwineNativeProperty + override val name: String get() { - return player.uniqueId.toString() + return player.name } - @RocketNativeProperty + @TwineNativeProperty val world: String get() { return player.world.name } - @RocketNativeProperty + @TwineNativeProperty val ip: String? get() { return player.address?.hostString } - @RocketNativeProperty + @TwineNativeProperty val isFlying: Boolean get() { return player.isFlying } - @RocketNativeProperty + @TwineNativeProperty val isSneaking: Boolean get() { return player.isSneaking } - @RocketNativeProperty + @TwineNativeProperty val isSprinting: Boolean get() { return player.isSprinting } - @RocketNativeProperty + @TwineNativeProperty val isBlocking: Boolean get() { return player.isBlocking } - @RocketNativeProperty + @TwineNativeProperty val isSleeping: Boolean get() { return player.isSleeping } - private val block = player.getTargetBlockExact(100) + private val targetBlock + get() = player.getTargetBlockExact(100) - @RocketNativeProperty - var targetBlockType: String = "" + @TwineNativeProperty + val targetBlockType + get() = targetBlock?.type.toString() - @RocketNativeProperty - var targetBlockLocation: Location = Location(Bukkit.getWorld("world"), 0.0, 0.0, 0.0) + @TwineNativeProperty + val targetBlockLocation + get() = LuaLocation.fromBukkit(targetBlock?.location!!) - @RocketNativeProperty - var targetBlockLightLevel: Double = 0.0 + @TwineNativeProperty + val targetBlockLightLevel + get() = targetBlock?.lightLevel?.toDouble() - @RocketNativeProperty - var targetBlockTemperature: Double = 0.0 + @TwineNativeProperty + val targetBlockTemperature + get() = targetBlock?.temperature?.toDouble() - @RocketNativeProperty - var targetBlockHumidity: Double = 0.0 + @TwineNativeProperty + val targetBlockHumidity + get() = targetBlock?.humidity?.toDouble() +} - init { - block?.let { - targetBlockType = block.type.toString() - targetBlockLocation = block.location - targetBlockLightLevel = block.lightLevel.toDouble() - targetBlockTemperature = block.temperature - targetBlockHumidity = block.humidity +@Suppress("unused") +open class LuaOfflinePlayer(val offlinePlayer: OfflinePlayer) : TwineNative("") { + @TwineNativeProperty + val firstPlayed + get() = offlinePlayer.firstPlayed + + @TwineNativeProperty + val lastDeathLocation + get() = offlinePlayer.lastDeathLocation + + @TwineNativeProperty + val lastLogin + get() = offlinePlayer.lastLogin + + @TwineNativeProperty + val lastSeen + get() = offlinePlayer.lastSeen + + // location, name, and player are open because they are overridden in LuaPlayer with non-nullable values + // in the case of location and name and the actual player instance in the case of player + @TwineNativeProperty + open val location: LuaLocation? + get() { + val location = offlinePlayer.location + return if (location != null) { + LuaLocation.fromBukkit(location) + } else { + null + } } - } + + @TwineNativeProperty + open val name + get() = offlinePlayer.name + + @TwineNativeProperty + open val player + get() = offlinePlayer.player + + @TwineNativeProperty + val respawnLocation + get() = offlinePlayer.respawnLocation + + @TwineNativeProperty + val uuid + get() = offlinePlayer.uniqueId.toString() + + @TwineNativeProperty + val hasPlayedBefore + get() = offlinePlayer.hasPlayedBefore() + + @TwineNativeProperty + val isBanned + get() = offlinePlayer.isBanned + + @TwineNativeProperty + val isConnected + get() = offlinePlayer.isOnline + + @TwineNativeProperty + val isOnline + get() = offlinePlayer.isOnline + + @TwineNativeProperty + var whitelisted + get() = offlinePlayer.isWhitelisted + set(value) { offlinePlayer.isWhitelisted = value } } \ No newline at end of file diff --git a/src/main/kotlin/dev/znci/rocket/scripting/globals/tables/Server.kt b/src/main/kotlin/dev/znci/rocket/scripting/globals/tables/Server.kt new file mode 100644 index 0000000..01965b3 --- /dev/null +++ b/src/main/kotlin/dev/znci/rocket/scripting/globals/tables/Server.kt @@ -0,0 +1,165 @@ +package dev.znci.rocket.scripting.globals.tables + +import dev.znci.rocket.Rocket +import dev.znci.rocket.scripting.annotations.Global +import dev.znci.rocket.util.MessageFormatter +import dev.znci.rocket.util.MessageFormatter.toMiniMessage +import dev.znci.twine.TwineNative +import dev.znci.twine.annotations.TwineNativeFunction +import dev.znci.twine.annotations.TwineNativeProperty + + +@Suppress("unused") +@Global +class LuaServer : TwineNative("server") { + private val server = Rocket.instance.server + + @TwineNativeProperty + val ip + get() = server.ip + + @TwineNativeProperty + val port + get() = server.port + + @TwineNativeProperty + var maxPlayers + get() = server.maxPlayers + set(value) { server.maxPlayers = value} + + @TwineNativeProperty + val maxWorldSize + get() = server.maxWorldSize + + @TwineNativeProperty + val minecraftVersion + get() = server.version + + @TwineNativeProperty + val operators + get() = server.operators.map { LuaOfflinePlayer(it) } + + @TwineNativeProperty + val resourcePack + get() = server.resourcePack + + @TwineNativeProperty + val resourcePackHash + get() = server.resourcePackHash + + @TwineNativeProperty + val resourcePackPrompt + get() = server.resourcePackPrompt + + @TwineNativeProperty + val resourcePackRequired + get() = server.isResourcePackRequired + + @TwineNativeProperty + val spawnProtectionRadius + get() = server.spawnRadius + + @TwineNativeProperty + val tps + get() = ArrayList(server.tps.toList()) + + @TwineNativeProperty + val tickTimes + get() = ArrayList(server.tickTimes.toList()) + + @TwineNativeProperty + val viewDistance + get() = server.viewDistance + + @TwineNativeProperty + var whitelisted + get() = server.hasWhitelist() + set(value) { server.setWhitelist(value) } + + @TwineNativeProperty + var whitelistEnforced + get() = server.isWhitelistEnforced + set(value) { server.setWhitelistEnforced(value) } + + @TwineNativeProperty + val acceptingTransfers + get() = server.isAcceptingTransfers + + @TwineNativeProperty + val enforcingSecureProfiles + get() = server.isEnforcingSecureProfiles + + @TwineNativeProperty + val hardcore + get() = server.isHardcore + + @TwineNativeProperty + val loggingIPs + get() = server.isLoggingIPs + + @TwineNativeProperty + val stopping + get() = server.isStopping + + @TwineNativeProperty + val permissionMessage + get() = server.permissionMessage().toMiniMessage() + + // TODO: implement set functionality once enums are done + @TwineNativeProperty + val defaultGamemode + get() = server.defaultGameMode.toString() + + @TwineNativeProperty + val paused + get() = server.isPaused + + @TwineNativeProperty + var pauseWhenEmptyTime + get() = server.pauseWhenEmptyTime + set(value) { server.pauseWhenEmptyTime = value } + + @TwineNativeProperty + val allowEnd + get() = server.allowEnd + + @TwineNativeProperty + val allowNether + get() = server.allowNether + + @TwineNativeProperty + val allowFlight + get() = server.allowFlight + + @TwineNativeProperty + val idleTimeout + get() = server.idleTimeout + + @TwineNativeFunction + fun allowPausing(value: Boolean) = server.allowPausing(Rocket.instance, value) + + @TwineNativeFunction + fun broadcast(message: String) = server.broadcast(MessageFormatter.formatMessage(message)) + + @TwineNativeFunction + fun reload() = server.reload() + + @TwineNativeFunction + fun reloadMinecraftData() = server.reloadData() + + @TwineNativeFunction + fun reloadWhitelist() = server.reloadWhitelist() + + @TwineNativeFunction + fun reloadPermissions() = server.reloadPermissions() + + @TwineNativeFunction + fun reloadCommandAliases() = server.reloadCommandAliases() + + @TwineNativeFunction + fun shutdown() = server.shutdown() + + @TwineNativeFunction + fun restart() = server.restart() + +} \ No newline at end of file diff --git a/src/main/kotlin/dev/znci/rocket/scripting/globals/tables/Test.kt b/src/main/kotlin/dev/znci/rocket/scripting/globals/tables/Test.kt deleted file mode 100644 index 5a678e7..0000000 --- a/src/main/kotlin/dev/znci/rocket/scripting/globals/tables/Test.kt +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright 2025 znci - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package dev.znci.rocket.scripting.globals.tables - -import dev.znci.rocket.scripting.api.RocketNative -import dev.znci.rocket.scripting.api.annotations.RocketNativeFunction -import dev.znci.rocket.scripting.api.annotations.RocketNativeProperty - -@Suppress("unused") // FIXME: Remove this class after new api migration -class SimpleTest : RocketNative("testNext") { - - private var internalString = "Initial" - - @RocketNativeProperty - var stringRepresentation: String - get() { - println("Getter") - return internalString - } - set(value) { - println("Setter called with: $value") - internalString = value.uppercase() // just an example to showcase custom setter. - } - - @RocketNativeFunction - fun testFunction(arg1: String): String { - return "Hello, $arg1!" - } -} \ No newline at end of file diff --git a/src/main/kotlin/dev/znci/rocket/scripting/globals/values/TestValue.kt b/src/main/kotlin/dev/znci/rocket/scripting/globals/values/TestValue.kt index eb2be75..acd7440 100644 --- a/src/main/kotlin/dev/znci/rocket/scripting/globals/values/TestValue.kt +++ b/src/main/kotlin/dev/znci/rocket/scripting/globals/values/TestValue.kt @@ -1,8 +1,8 @@ package dev.znci.rocket.scripting.globals.values -import dev.znci.rocket.scripting.api.RocketProperty +import dev.znci.twine.TwineProperty -class TestValue : RocketProperty("testValue") { +class TestValue : TwineProperty("testValue") { init { this.value = "test" } diff --git a/src/main/kotlin/dev/znci/rocket/scripting/util/Properties.kt b/src/main/kotlin/dev/znci/rocket/scripting/util/Properties.kt index 72c18c0..4eddd40 100644 --- a/src/main/kotlin/dev/znci/rocket/scripting/util/Properties.kt +++ b/src/main/kotlin/dev/znci/rocket/scripting/util/Properties.kt @@ -34,7 +34,7 @@ import org.luaj.vm2.lib.TwoArgFunction * @throws org.luaj.vm2.LuaError If the value provided to the setter is invalid according to the validator. */ -@Deprecated("RocketNative Kotlin should be used instead.", ReplaceWith("RocketNative"), DeprecationLevel.WARNING) +@Deprecated("TwineNative Kotlin should be used instead.", ReplaceWith("TwineNative"), DeprecationLevel.WARNING) fun defineProperty( table: LuaTable, propertyName: String, diff --git a/src/main/kotlin/dev/znci/rocket/util/MessageFormatter.kt b/src/main/kotlin/dev/znci/rocket/util/MessageFormatter.kt index 58f5243..a587a95 100644 --- a/src/main/kotlin/dev/znci/rocket/util/MessageFormatter.kt +++ b/src/main/kotlin/dev/znci/rocket/util/MessageFormatter.kt @@ -26,4 +26,15 @@ object MessageFormatter { val messageComponent = miniMessage.deserialize(message) return messageComponent } + + /** + * Serializes a `Component` object into a string using the MiniMessage library. + * This allows the formatted message to be sent to players or logged. + * + * @param component The `Component` object to serialize. + * @return A string representation of the `Component`, formatted with MiniMessage syntax. + */ + fun Component.toMiniMessage(): String { + return miniMessage.serialize(this) + } } \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 2c9031d..cea9cff 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1 +1,2 @@ -locale: en_GB \ No newline at end of file +locale: en_GB +command-fallback-prefix: rocket \ No newline at end of file diff --git a/src/test/kotlin/dev/znci/rocket/scripting/globals/tables/LuaCommandsTest.kt b/src/test/kotlin/dev/znci/rocket/scripting/globals/tables/LuaCommandsTest.kt new file mode 100644 index 0000000..d04280b --- /dev/null +++ b/src/test/kotlin/dev/znci/rocket/scripting/globals/tables/LuaCommandsTest.kt @@ -0,0 +1,38 @@ +package dev.znci.rocket.scripting.globals.tables + +import dev.znci.rocket.Rocket +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.mockbukkit.mockbukkit.MockBukkit +import org.mockbukkit.mockbukkit.ServerMock + +/** + * Tests for the `new` function in `LuaCommands` class. + * + * The `new` function is a Twine native function that creates and returns + * a new instance of `LuaCommand`. + */ +class LuaCommandsTest { + + private lateinit var mockServer: ServerMock + private lateinit var plugin: Rocket + + @BeforeEach + fun setUp() { + mockServer = MockBukkit.mock() + plugin = MockBukkit.load(Rocket::class.java) + } + + @Test + fun `test new function returns new LuaCommand instance`() { + // Arrange + val luaCommands = LuaCommands() + + // Act + val result = luaCommands.new() + + // Assert + assertNotNull(result, "The new function should return a non-null LuaCommand instance.") + } +} \ No newline at end of file