From d1d9f5c91972ddbe5bd1617cfcfc7d2341948401 Mon Sep 17 00:00:00 2001 From: NaySurGithub <96527285+NaySurGithub@users.noreply.github.com> Date: Sat, 14 Nov 2026 21:24:15 +0100 Subject: [PATCH] feat: add bans, the allow list, the server list entry, tick rate, shutdown and the plugin list to the plugin api --- README.md | 14 ++ java/src/main/java/falcon/api/Server.java | 155 ++++++++++++++++++++++ native/include/falcon/Server.hpp | 112 ++++++++++++++++ native/include/falcon/falcon_api.h | 28 ++++ wasm/falcon.wit | 29 ++++ 5 files changed, 338 insertions(+) diff --git a/README.md b/README.md index 96f51e3..3806b0b 100644 --- a/README.md +++ b/README.md @@ -263,6 +263,20 @@ player.setCamera("minecraft:free", {0, 100, 0}, {30, 90, 0}, 2.0f, falcon::Camer player.fadeCamera(0.5f, 1.0f, 0.5f, 0x000000); ``` +### Administration + +`falcon::Server` reaches the ban lists, the allow list, the server list entry, the tick rate and the other plugins: + +```cpp +falcon::Server::ban("Griefer", "Breaking the spawn"); +falcon::Server::allowListAdd("Friend"); +falcon::Server::setMotd("Falcon - event tonight"); +if (falcon::Server::tps() < 15.0) + falcon::Server::broadcast("The server is under load"); +for (const std::string &name: falcon::Server::plugins()) + logger().info(name + " " + falcon::Server::pluginVersion(name).value_or("?")); +``` + ### C# plugins The `dotnet` runtime is deprecated. Write new plugins for Wasm, native C++ or Java. diff --git a/java/src/main/java/falcon/api/Server.java b/java/src/main/java/falcon/api/Server.java index 26d8c5d..db159b7 100644 --- a/java/src/main/java/falcon/api/Server.java +++ b/java/src/main/java/falcon/api/Server.java @@ -97,6 +97,161 @@ public boolean unloadWorld(String name) { } } + /** + * Bans a name, like /ban, and kicks the player when online; the reason is shown to them. + */ + public boolean ban(String name, String reason) { + try (Arena arena = Arena.ofConfined()) { + return api().serverBan(Interop.text(arena, name), Interop.text(arena, reason), + api().pluginName(mContext.handle())) != 0; + } + } + + public boolean pardon(String name) { + try (Arena arena = Arena.ofConfined()) { + return api().serverPardon(Interop.text(arena, name)) != 0; + } + } + + public boolean isBanned(String name) { + try (Arena arena = Arena.ofConfined()) { + return api().serverIsBanned(Interop.text(arena, name)) != 0; + } + } + + public List bannedNames() { + int count = api().serverBanCount(); + List names = new ArrayList<>(Math.max(count, 0)); + for (int index = 0; index < count; index++) { + names.add(Interop.string(api().serverBannedName(index))); + } + return names; + } + + /** + * Bans an address, like /ban-ip, and kicks every player connected from it. + */ + public boolean banIp(String address, String reason) { + try (Arena arena = Arena.ofConfined()) { + return api().serverBanIp(Interop.text(arena, address), Interop.text(arena, reason), + api().pluginName(mContext.handle())) != 0; + } + } + + public boolean pardonIp(String address) { + try (Arena arena = Arena.ofConfined()) { + return api().serverPardonIp(Interop.text(arena, address)) != 0; + } + } + + public boolean isIpBanned(String address) { + try (Arena arena = Arena.ofConfined()) { + return api().serverIsIpBanned(Interop.text(arena, address)) != 0; + } + } + + public List bannedIps() { + int count = api().serverIpBanCount(); + List addresses = new ArrayList<>(Math.max(count, 0)); + for (int index = 0; index < count; index++) { + addresses.add(Interop.string(api().serverBannedIp(index))); + } + return addresses; + } + + public boolean allowListAdd(String name) { + try (Arena arena = Arena.ofConfined()) { + return api().serverAllowListAdd(Interop.text(arena, name)) != 0; + } + } + + /** + * Removes a name and kicks the players the allow list no longer lets in. + */ + public boolean allowListRemove(String name) { + try (Arena arena = Arena.ofConfined()) { + return api().serverAllowListRemove(Interop.text(arena, name)) != 0; + } + } + + public List allowList() { + int count = api().serverAllowListCount(); + List names = new ArrayList<>(Math.max(count, 0)); + for (int index = 0; index < count; index++) { + names.add(Interop.string(api().serverAllowListName(index))); + } + return names; + } + + public boolean allowListEnabled() { + return api().serverAllowListEnabled() != 0; + } + + public void setAllowListEnabled(boolean enabled) { + api().serverSetAllowListEnabled(enabled ? 1 : 0); + } + + public String motd() { + return Interop.string(api().serverMotd()); + } + + /** + * The server name of the server list entry, until the server restarts; server.properties is not changed. + */ + public void setMotd(String motd) { + try (Arena arena = Arena.ofConfined()) { + api().serverSetMotd(Interop.text(arena, motd)); + } + } + + public int maxPlayers() { + return api().serverMaxPlayers(); + } + + public boolean setMaxPlayers(int maxPlayers) { + return api().serverSetMaxPlayers(maxPlayers) != 0; + } + + /** + * Ticks per second over the last ticks, at most 20. + */ + public double tps() { + return api().serverTps(); + } + + public double millisecondsPerTick() { + return api().serverMillisecondsPerTick(); + } + + /** + * Stops the server after the current tick, like /stop. + */ + public void shutdown() { + api().serverShutdown(); + } + + public List plugins() { + int count = api().serverPluginCount(); + List names = new ArrayList<>(Math.max(count, 0)); + for (int index = 0; index < count; index++) { + names.add(Interop.string(api().serverPluginName(index))); + } + return names; + } + + public Optional pluginVersion(String name) { + try (Arena arena = Arena.ofConfined()) { + MemorySegment version = api().serverPluginVersion(Interop.text(arena, name)); + return Interop.isNull(version) ? Optional.empty() : Optional.of(Interop.string(version)); + } + } + + public boolean isPluginEnabled(String name) { + try (Arena arena = Arena.ofConfined()) { + return api().serverPluginEnabled(Interop.text(arena, name)) != 0; + } + } + public Difficulty difficulty() { return Difficulty.of(api().serverDifficulty()); } diff --git a/native/include/falcon/Server.hpp b/native/include/falcon/Server.hpp index d11033d..72f0edc 100644 --- a/native/include/falcon/Server.hpp +++ b/native/include/falcon/Server.hpp @@ -26,6 +26,110 @@ namespace falcon { return detail::api().serverSetDifficulty(static_cast(difficulty)) != 0; } + /** Bans a name, like /ban, and kicks the player when online; the reason is shown to them. */ + static bool ban(const std::string &name, const std::string &reason = "") { + const char *source = detail::api().pluginName(detail::plugin()); + return detail::api().serverBan(name.c_str(), reason.c_str(), source) != 0; + } + + static bool pardon(const std::string &name) { + return detail::api().serverPardon(name.c_str()) != 0; + } + + static bool isBanned(const std::string &name) { + return detail::api().serverIsBanned(name.c_str()) != 0; + } + + static std::vector bannedNames() { + return list(detail::api().serverBanCount(), detail::api().serverBannedName); + } + + /** Bans an address, like /ban-ip, and kicks every player connected from it. */ + static bool banIp(const std::string &address, const std::string &reason = "") { + return detail::api().serverBanIp(address.c_str(), reason.c_str(), + detail::api().pluginName(detail::plugin())) != 0; + } + + static bool pardonIp(const std::string &address) { + return detail::api().serverPardonIp(address.c_str()) != 0; + } + + static bool isIpBanned(const std::string &address) { + return detail::api().serverIsIpBanned(address.c_str()) != 0; + } + + static std::vector bannedIps() { + return list(detail::api().serverIpBanCount(), detail::api().serverBannedIp); + } + + static bool allowListAdd(const std::string &name) { + return detail::api().serverAllowListAdd(name.c_str()) != 0; + } + + /** Removes a name and kicks the players the allow list no longer lets in. */ + static bool allowListRemove(const std::string &name) { + return detail::api().serverAllowListRemove(name.c_str()) != 0; + } + + static std::vector allowList() { + return list(detail::api().serverAllowListCount(), detail::api().serverAllowListName); + } + + static bool allowListEnabled() { + return detail::api().serverAllowListEnabled() != 0; + } + + static void setAllowListEnabled(bool enabled) { + detail::api().serverSetAllowListEnabled(enabled ? 1 : 0); + } + + static std::string motd() { + return detail::text(detail::api().serverMotd()); + } + + /** The server name of the server list entry, until the server restarts; server.properties is not changed. */ + static void setMotd(const std::string &motd) { + detail::api().serverSetMotd(motd.c_str()); + } + + /** The player limit, shown in the server list too. */ + static uint32_t maxPlayers() { + return detail::api().serverMaxPlayers(); + } + + static bool setMaxPlayers(uint32_t maxPlayers) { + return detail::api().serverSetMaxPlayers(maxPlayers) != 0; + } + + /** Ticks per second over the last ticks, at most 20. */ + static double tps() { + return detail::api().serverTps(); + } + + static double millisecondsPerTick() { + return detail::api().serverMillisecondsPerTick(); + } + + /** Stops the server after the current tick, like /stop. */ + static void shutdown() { + detail::api().serverShutdown(); + } + + static std::vector plugins() { + return list(detail::api().serverPluginCount(), detail::api().serverPluginName); + } + + static std::optional pluginVersion(const std::string &name) { + const char *version = detail::api().serverPluginVersion(name.c_str()); + if (version == nullptr) + return std::nullopt; + return std::string(version); + } + + static bool isPluginEnabled(const std::string &name) { + return detail::api().serverPluginEnabled(name.c_str()) != 0; + } + static std::string version() { return detail::api().serverVersion(); } @@ -86,5 +190,13 @@ namespace falcon { static bool unloadWorld(const std::string &name) { return detail::api().serverUnloadWorld(name.c_str()) != 0; } + + private: + static std::vector list(uint32_t count, const char *(*at)(uint32_t)) { + std::vector values; + for (uint32_t index = 0; index < count; ++index) + values.push_back(detail::text(at(index))); + return values; + } }; } diff --git a/native/include/falcon/falcon_api.h b/native/include/falcon/falcon_api.h index 5b90173..6c80b52 100644 --- a/native/include/falcon/falcon_api.h +++ b/native/include/falcon/falcon_api.h @@ -739,6 +739,34 @@ typedef struct FalconServerApi { void (*playerClearCamera)(FalconPlayer *player); int32_t (*playerItemCooldown)(FalconPlayer *player, FalconItem *item); int (*playerStartItemCooldown)(FalconPlayer *player, FalconItem *item, int32_t ticks); + + int (*serverBan)(const char *name, const char *reason, const char *source); + int (*serverPardon)(const char *name); + int (*serverIsBanned)(const char *name); + uint32_t (*serverBanCount)(void); + const char *(*serverBannedName)(uint32_t index); + int (*serverBanIp)(const char *address, const char *reason, const char *source); + int (*serverPardonIp)(const char *address); + int (*serverIsIpBanned)(const char *address); + uint32_t (*serverIpBanCount)(void); + const char *(*serverBannedIp)(uint32_t index); + int (*serverAllowListAdd)(const char *name); + int (*serverAllowListRemove)(const char *name); + uint32_t (*serverAllowListCount)(void); + const char *(*serverAllowListName)(uint32_t index); + int (*serverAllowListEnabled)(void); + void (*serverSetAllowListEnabled)(int enabled); + const char *(*serverMotd)(void); + void (*serverSetMotd)(const char *motd); + uint32_t (*serverMaxPlayers)(void); + int (*serverSetMaxPlayers)(uint32_t maxPlayers); + double (*serverTps)(void); + double (*serverMillisecondsPerTick)(void); + void (*serverShutdown)(void); + uint32_t (*serverPluginCount)(void); + const char *(*serverPluginName)(uint32_t index); + const char *(*serverPluginVersion)(const char *name); + int (*serverPluginEnabled)(const char *name); } FalconServerApi; typedef int (*FalconPluginEntry)(const FalconServerApi *api, FalconPlugin *plugin, FalconPluginCallbacks *callbacks); diff --git a/wasm/falcon.wit b/wasm/falcon.wit index d18e33a..f8373ca 100644 --- a/wasm/falcon.wit +++ b/wasm/falcon.wit @@ -131,6 +131,35 @@ interface server { /// One of the `FALCON_DIFFICULTY_*` values. server-difficulty: func() -> u32; server-set-difficulty: func(difficulty: u32) -> bool; + /// Kicks the player when online, like /ban; `source` is the name recorded as the one who banned. + server-ban: func(name: string, reason: string, source: string) -> bool; + server-pardon: func(name: string) -> bool; + server-is-banned: func(name: string) -> bool; + server-ban-count: func() -> u32; + server-banned-name: func(index: u32) -> option; + server-ban-ip: func(address: string, reason: string, source: string) -> bool; + server-pardon-ip: func(address: string) -> bool; + server-is-ip-banned: func(address: string) -> bool; + server-ip-ban-count: func() -> u32; + server-banned-ip: func(index: u32) -> option; + server-allow-list-add: func(name: string) -> bool; + server-allow-list-remove: func(name: string) -> bool; + server-allow-list-count: func() -> u32; + server-allow-list-name: func(index: u32) -> option; + server-allow-list-enabled: func() -> bool; + server-set-allow-list-enabled: func(enabled: bool); + /// The server name of the server list entry; server.properties is not changed. + server-motd: func() -> string; + server-set-motd: func(motd: string); + server-max-players: func() -> u32; + server-set-max-players: func(max-players: u32) -> bool; + server-tps: func() -> f64; + server-milliseconds-per-tick: func() -> f64; + server-shutdown: func(); + server-plugin-count: func() -> u32; + server-plugin-name: func(index: u32) -> option; + server-plugin-version: func(name: string) -> option; + server-plugin-enabled: func(name: string) -> bool; } interface players {