Compare commits

..

16 commits

69 changed files with 2277 additions and 897 deletions

11
LICENSE Normal file
View file

@ -0,0 +1,11 @@
TheClashFruit's Source Only License v1
Copyright (c) 2023-2024 CRSS
You may not:
* Distribute or modify the software.
* Use the software for any commercial purpose.
* Include the software in any other product.
This software is provided "as-is" and without warranty.
The CRSS team is not liable for any damages resulting from the use of this software.

26
README.md Normal file
View file

@ -0,0 +1,26 @@
# CRSS Server's Plugin
This is the official plugin for the CRSS Server.
## Documentation
* [/](./docs)
* [gateway.md](./docs/gateway.md)
## License
```
TheClashFruit's Source Only License v1
Copyright (c) 2023-2024 CRSS
You may not:
* Distribute or modify the software.
* Use the software for any commercial purpose.
* Include the software in any other product.
This software is provided "as-is" and without warranty.
The CRSS team is not liable for any damages resulting from the use of this software.
```
See the [LICENSE](LICENSE) file for more information.

View file

@ -15,32 +15,26 @@ repositories {
}
maven {
name = "Vault"
url = "https://nexus.hc.to/content/repositories/pub_releases"
name = "Sonatype"
url = "https://oss.sonatype.org/content/repositories/snapshots/"
}
maven {
name = "Sonatype"
url = "https://oss.sonatype.org/content/repositories/snapshots/"
name "Minecraft Libraries"
url "https://libraries.minecraft.net/"
}
}
dependencies {
testImplementation platform('org.junit:junit-bom:5.9.1')
testImplementation 'org.junit.jupiter:junit-jupiter'
compileOnly("org.spigotmc:spigot-api:${project.bukkit_version}")
compileOnly("com.mojang:authlib:1.5.21")
shadow implementation("org.spigotmc:spigot-api:${project.bukkit_version}")
shadow implementation("net.milkbowl.vault:VaultAPI:1.5")
implementation 'org.eclipse.jetty:jetty-server:9.4.52.v20230823'
implementation 'org.eclipse.jetty:jetty-servlet:9.4.52.v20230823'
implementation 'org.eclipse.jetty.websocket:websocket-server:9.4.52.v20230823'
implementation 'org.eclipse.jetty.websocket:websocket-servlet:9.4.52.v20230823'
implementation 'javax.servlet:javax.servlet-api:4.0.1'
implementation 'javax.websocket:javax.websocket-api:1.1'
implementation 'org.eclipse.jetty:jetty-server:12.0.8'
implementation 'org.eclipse.jetty.ee10:jetty-ee10-servlet:12.0.8'
implementation 'org.eclipse.jetty.ee10.websocket:jetty-ee10-websocket-jetty-server:12.0.8'
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'org.mariadb.jdbc:mariadb-java-client:3.4.0'
}
processResources {
@ -83,6 +77,14 @@ shadowJar {
// exclude devrunner from jar
exclude('me/theclashfruit/devrunner')
// relocate stuff
relocate('com.google.gson', 'me.theclashfruit.deps.gson')
relocate('org.eclipse.jetty', 'me.theclashfruit.deps.jetty')
relocate('jakarta', 'me.theclashfruit.deps.jakarta')
relocate('org.slf4j', 'me.theclashfruit.deps.slf4j')
relocate('org.objectweb.asm', 'me.theclashfruit.deps.asm')
// relocate('org.mariadb', 'me.theclashfruit.deps.mariadb')
archiveBaseName.set(rootProject.name)
archiveClassifier.set('')
archiveVersion.set(project.version)

267
docs/gateway.md Normal file
View file

@ -0,0 +1,267 @@
# Gateway
Listen to events from the server such as player movement.
Path: `ws(s)://crss.blurryface.xyz/api/v2/gateway`
## Table of Contents
* [Requesting API Endpoints](#requesting-api-endpoints)
* [Subscribing to Events](#subscribing-to-events)
* [Ping-Pong](#ping-pong)
* [Endpoints](#endpoints)
* [`status`](#status)
* [`players`](#players)
* [`tile`](#tile)
* [Events](#events)
* [`player_join`](#player_join)
* [`player_leave`](#player_leave)
* [`player_move`](#player_move)
* [`player_chat`](#player_chat)
* [`player_death`](#player_death)
## Requesting API Endpoints
Send a JSON object with the `type` field set to `api` and the `endpoints` field set to an array of endpoints you want to request.
### Possible Endpoints
* `status`
* `players`
* `tile`
### Example
```json
{
"type": "api",
"endpoints": [
{
"path": "status",
"data": null
},
{
"path": "players",
"data": null
}
]
}
```
## Subscribing to Events
Send a JSON object with the `type` field set to `subscribe` and the `events` field set to an array of events you want to subscribe to.
### Possible Events
* `player_join`
* `player_leave`
* `player_move`
* `player_chat`
* `player_death`
### Example
```json
{
"type": "subscribe",
"events": [
"player_join",
"player_leave",
"player_move",
"player_chat",
"player_death"
]
}
```
## Ping-Pong
You need to send a "ping" message every 5 minutes to keep the connection alive:
```json
{
"type": "ping"
}
```
The server will respond with:
```json
{
"type": "pong"
}
```
If that doesn't happen, connect to the WebSocket again.
---
## Endpoints
### `status`
Request:
```json
{
"type": "api",
"endpoints": [
{
"path": "status",
"data": null
}
]
}
```
Response:
```json5
{
"type": "api",
"endpoint": "status",
"response": "{ ... }" // see status endpoint docs
}
```
### `players`
Request:
```json
{
"type": "api",
"endpoints": [
{
"path": "players",
"data": null
}
]
}
```
Response:
```json5
{
"type": "api",
"endpoint": "players",
"response": "{ ... }" // see players endpoint docs
}
```
### `tile`
Request:
```json
{
"type": "api",
"endpoints": [
{
"path": "tile",
"data": {
"world": "world",
"x": 0,
"z": 0,
"size": 256,
"zoom": 0
}
}
]
}
```
Response:
```json5
{
"type": "api",
"endpoint": "tile",
"response": "...", // base64 encoded PNG image
"request": { // the original request data
"world": "world",
"x": 0,
"z": 0,
"size": 256,
"zoom": 0
}
}
```
## Events
### `player_join`
Example:
```json
{
"type": "player_join",
"player": {
"name": "TheClashFruit",
"uuid": "942ae64d-e4e5-4dd0-a153-b6aba7817ca9"
}
}
```
### `player_leave`
Example:
```json
{
"type": "player_leave",
"player": {
"name": "TheClashFruit",
"uuid": "942ae64d-e4e5-4dd0-a153-b6aba7817ca9"
}
}
```
### `player_move`
Example:
```json
{
"type": "player_move",
"player": {
"name": "TheClashFruit",
"uuid": "942ae64d-e4e5-4dd0-a153-b6aba7817ca9"
},
"position": {
"distance": 0.0785923757599716,
"from": {
"world": "world",
"x": 98.31441469694991,
"y": 83.0,
"z": 254.93694099796153
},
"to": {
"world": "world",
"x": 98.38312726494456,
"y": 83.0,
"z": 254.8987920198995
}
}
}
```
### `player_chat`
Example:
```json
{
"type": "player_chat",
"player": {
"name": "TheClashFruit",
"uuid": "942ae64d-e4e5-4dd0-a153-b6aba7817ca9"
},
"message": "Hello, world!"
}
```
### `player_death`
Example:
```json
{
"type": "player_death",
"player": {
"name": "TheClashFruit",
"uuid": "942ae64d-e4e5-4dd0-a153-b6aba7817ca9"
},
"message": "TheClashFruit was slain by a zombie."
}
```

View file

@ -1,2 +1,2 @@
bukkit_version = 1.8.8-R0.1-SNAPSHOT
plugin_version = 2.0.0-alpha+mc1.8.8
bukkit_version = 1.10.2-R0.1-SNAPSHOT
plugin_version = 2.0.0-alpha+mc1.9.4

View file

@ -0,0 +1,120 @@
package me.theclashfruit.crss;
import me.theclashfruit.crss.backend.error.ErrorHandler;
import me.theclashfruit.crss.backend.error.GoneServlet;
import me.theclashfruit.crss.backend.IndexServlet;
import me.theclashfruit.crss.backend.v2.map.MarkersServlet;
import me.theclashfruit.crss.backend.v2.map.TileServlet;
import me.theclashfruit.crss.backend.v2.PlayersServlet;
import me.theclashfruit.crss.backend.v2.StatusServlet;
import me.theclashfruit.crss.backend.v2.gateway.GatewayServlet;
import me.theclashfruit.crss.banking.commands.AtmCommand;
import me.theclashfruit.crss.banking.commands.DatabaseCheck;
import me.theclashfruit.crss.listeners.InventoryListener;
import me.theclashfruit.crss.listeners.PlayerListener;
import me.theclashfruit.crss.utils.ReqMiddleware;
import me.theclashfruit.crss.utils.SqlUtil;
import org.bukkit.Bukkit;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import org.eclipse.jetty.ee10.servlet.ServletContextHandler;
import org.eclipse.jetty.ee10.websocket.server.config.JettyWebSocketServletContainerInitializer;
import org.eclipse.jetty.server.*;
import org.eclipse.jetty.util.Jetty;
import java.sql.Connection;
import java.util.logging.Logger;
public class CRSSPlugin extends JavaPlugin {
Server server = new Server(getConfig().getInt("web.port"));
Connector connector = new ServerConnector(server);
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
public static Logger LOGGER = Bukkit.getLogger();
@Override
public void onEnable() {
getLogger().info("Plugin enabled!");
saveDefaultConfig();
saveResource("unmined_settings.json", false);
saveResource("data/markers.json", false);
PluginManager pluginManager = getServer().getPluginManager();
context.setContextPath("/api");
// slash
context.addServlet(IndexServlet.class, "");
// v1
context.addServlet(GoneServlet.class, "/v1/*");
// v2
context.addServlet(StatusServlet.class, "/v2/status");
context.addServlet(PlayersServlet.class, "/v2/players");
context.addServlet(GatewayServlet.class, "/v2/gateway");
// map api
if (getConfig().getBoolean("web.map.enabled")) {
context.addServlet(TileServlet.class, "/v2/map/tile/*");
context.addServlet(MarkersServlet.class, "/v2/map/markers");
}
// websocket
JettyWebSocketServletContainerInitializer.configure(context, null);
try {
server.setServerInfo("crss/" + getDescription().getVersion() + "+jetty" + Jetty.VERSION);
ReqMiddleware reqMiddleware = new ReqMiddleware();
reqMiddleware.setHandler(context);
server.setHandler(reqMiddleware);
server.setErrorHandler(new ErrorHandler());
server.addConnector(connector);
if (getConfig().getBoolean("web.enabled") && getConfig().getBoolean("web.api.enabled")) {
server.start();
getLogger().info("API server started on port " + getConfig().getInt("web.port"));
}
} catch (Exception e) {
getLogger().throwing(this.getName(), "onEnable", e);
}
// Events
pluginManager.registerEvents(new PlayerListener(), this);
pluginManager.registerEvents(new InventoryListener(), this);
// Commands
getCommand("atm").setExecutor(new AtmCommand());
getCommand("checkdb").setExecutor(new DatabaseCheck());
// Database
SqlUtil.setConnectionDetails(
getConfig().getString("sql.connection"),
getConfig().getString("sql.username"),
getConfig().getString("sql.password")
);
SqlUtil.openConnection();
}
@Override
public void onDisable() {
getLogger().info("Plugin disabled!");
try {
server.stop();
} catch (Exception e) {
getLogger().throwing(this.getName(), "onDisable", e);
}
SqlUtil.closeConnection();
}
}

View file

@ -1,121 +0,0 @@
package me.theclashfruit.crss;
import me.theclashfruit.crss.api.ChatServlet;
import me.theclashfruit.crss.api.GatewaySocket;
import me.theclashfruit.crss.api.PlayersServlet;
import me.theclashfruit.crss.api.StatusServlet;
import me.theclashfruit.crss.commands.BalanceCommand;
import me.theclashfruit.crss.listener.ChatListener;
import me.theclashfruit.crss.listener.PlayerJoinListener;
import me.theclashfruit.crss.listener.SleepingListener;
import me.theclashfruit.crss.map.MapServlet;
import me.theclashfruit.crss.models.SocketData;
import me.theclashfruit.crss.models.SocketMessage;
import net.milkbowl.vault.economy.Economy;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.RegisteredServiceProvider;
import org.bukkit.plugin.java.JavaPlugin;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.ServletHandler;
import java.io.File;
import java.util.Arrays;
public class Plugin extends JavaPlugin {
GatewaySocket chatSocket;
Server server = new Server(25580);
Connector connector = new ServerConnector(server);
ServletHandler handler = new ServletHandler();
private static Economy economy = null;
@Override
public void onEnable() {
getLogger().info("Plugin enabled!");
saveDefaultConfig();
PluginManager pluginManager = getServer().getPluginManager();
if (!setupEconomy()) {
getLogger().severe("Vault not found! Disabling plugin.");
// pluginManager.disablePlugin(this);
}
// create api server
// ContextHandler context = new ContextHandler(new ApiHandler(), "/v1");
if(getConfig().getBoolean("web.api.enabled")) {
handler.addServletWithMapping(ChatServlet.class, "/api/v1/gateway");
handler.addServletWithMapping(PlayersServlet.class, "/api/v1/players");
handler.addServletWithMapping(StatusServlet.class, "/api/v1/status");
}
if(getConfig().getBoolean("web.map.enabled")) {
handler.addServletWithMapping(MapServlet.class, "/map");
if (!new File(this.getDataFolder(), getConfig().getString("web.map.path")).exists())
new File(this.getDataFolder(), getConfig().getString("web.map.path")).mkdirs();
}
server.addConnector(connector);
server.setHandler(handler);
if (!new File(this.getDataFolder(), "/natives").exists())
new File(this.getDataFolder(), "/natives").mkdirs();
try {
if(getConfig().getBoolean("web.enabled"))
server.start();
} catch (Exception e) {
throw new RuntimeException(e);
}
// register events
pluginManager.registerEvents(new SleepingListener(), this);
pluginManager.registerEvents(new ChatListener(), this);
pluginManager.registerEvents(new PlayerJoinListener(), this);
// register commands
getCommand("balance").setExecutor(new BalanceCommand());
}
@Override
public void onDisable() {
getLogger().info("Plugin disabled!");
// stop chat socket
try {
GatewaySocket.broadcast(new SocketMessage(
"serverStop",
new SocketData(
null,
"Server stopped.",
false
)
));
server.stop();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private boolean setupEconomy() {
RegisteredServiceProvider<Economy> economyProvider = getServer().getServicesManager().getRegistration(net.milkbowl.vault.economy.Economy.class);
if (economyProvider != null)
economy = economyProvider.getProvider();
return (economy != null);
}
public static Economy getEconomy() {
return economy;
}
}

View file

@ -1,15 +0,0 @@
package me.theclashfruit.crss.api;
import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
import javax.servlet.annotation.WebServlet;
@WebServlet(name = "Chat WebSocket API")
public class ChatServlet extends WebSocketServlet {
@Override
public void configure(WebSocketServletFactory factory) {
factory.register(GatewaySocket.class);
}
}

View file

@ -1,74 +0,0 @@
package me.theclashfruit.crss.api;
import com.google.gson.Gson;
import me.theclashfruit.crss.models.GatewayConnection;
import me.theclashfruit.crss.models.SocketData;
import me.theclashfruit.crss.models.SocketMessage;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.*;
import javax.websocket.EncodeException;
import java.io.IOException;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import static org.bukkit.Bukkit.getLogger;
@WebSocket
public class GatewaySocket {
private static final Set<GatewaySocket> gatewayConnections = new CopyOnWriteArraySet<>();
private Session session;
@OnWebSocketConnect
public void onConnect(Session session) throws IOException {
this.session = session;
gatewayConnections.add(this);
}
@OnWebSocketMessage
public void onMessage(String message) throws IOException, EncodeException {
getLogger().info(message);
try {
Gson gson = new Gson();
GatewayConnection socketMessage = gson.fromJson(message, GatewayConnection.class);
getLogger().info(socketMessage.getClientName());
for (String subscription : socketMessage.getSubscriptions()) {
getLogger().info(subscription);
}
} catch (Exception e) {
getLogger().throwing(GatewaySocket.class.getName(), "WebSocket", e);
}
}
@OnWebSocketClose
public void onClose(int statusCode, String reason) throws IOException {
gatewayConnections.remove(this);
}
@OnWebSocketError
public void onError(Throwable throwable) {
getLogger().throwing(GatewaySocket.class.getName(), "WebSocket", throwable);
}
public static void broadcast(SocketMessage message) {
gatewayConnections.forEach(endpoint -> {
try {
Gson gson = new Gson();
endpoint
.session
.getRemote()
.sendString(
gson.toJson(message)
);
} catch (IOException e) {
getLogger().throwing(GatewaySocket.class.getName(), "WebSocket", e);
}
});
}
}

View file

@ -1,46 +0,0 @@
package me.theclashfruit.crss.api;
import com.google.gson.Gson;
import me.theclashfruit.crss.models.PlayerList;
import me.theclashfruit.crss.models.PlayerLocation;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet(name = "Players API")
public class PlayersServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("application/json");
Gson gson = new Gson();
var onlinePlayers =Bukkit.getOnlinePlayers().toArray();
PlayerList[] playerList = new PlayerList[onlinePlayers.length];
for (int i = 0; i < onlinePlayers.length; i++) {
Player player = (Player) onlinePlayers[i];
playerList[i] = new PlayerList(
player.getUniqueId().toString(),
player.getDisplayName(),
player.getGameMode().getValue(),
player.getWorld().getName(),
new PlayerLocation(
player.getLocation().getX(),
player.getLocation().getY(),
player.getLocation().getZ()
)
);
}
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().println(gson.toJson(playerList));
}
}

View file

@ -1,32 +0,0 @@
package me.theclashfruit.crss.api;
import com.google.gson.Gson;
import me.theclashfruit.crss.models.Status;
import org.bukkit.Bukkit;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet(name = "Status API")
public class StatusServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("application/json");
Gson gson = new Gson();
Status status = new Status(
true,
Bukkit.getBukkitVersion(),
Bukkit.getOnlinePlayers().toArray().length,
Bukkit.getMaxPlayers(),
Bukkit.getMotd()
);
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().println(gson.toJson(status));
}
}

View file

@ -0,0 +1,42 @@
package me.theclashfruit.crss.backend;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import me.theclashfruit.crss.models.IndexResponse;
import me.theclashfruit.crss.models.Version;
import me.theclashfruit.crss.utils.JsonUtil;
import java.io.IOException;
import java.util.ArrayList;
@WebServlet(name = "API Index")
public class IndexServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
res.setContentType("application/json");
IndexResponse response = new IndexResponse(
"2.0.0-alpha+mc1.9.4",
new ArrayList<>(
) {{
add(new Version(
"1.0.0+mc1.1-alpha",
"/api/v1",
true
));
add(new Version(
"2.0.0-alpha+mc1.9.4",
"/api/v2",
false
));
}}
);
String resJson = JsonUtil.gson.toJson(response);
res.setStatus(200);
res.getWriter().println(resJson);
}
}

View file

@ -0,0 +1,36 @@
package me.theclashfruit.crss.backend.error;
import me.theclashfruit.crss.models.ErrorResponse;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.util.Callback;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import static org.eclipse.jetty.server.handler.ErrorHandler.ERROR_MESSAGE;
public class ErrorHandler implements Request.Handler {
@Override
public boolean handle(Request req, Response res, Callback callback) throws Exception {
int status = res.getStatus();
MimeTypes.Type type = MimeTypes.Type.APPLICATION_JSON;
res.getHeaders().put(type.getContentTypeField(StandardCharsets.UTF_8));
String message = (String) req.getAttribute(ERROR_MESSAGE);
ErrorResponse error = new ErrorResponse(
status,
status == 404 ? "This endpoint does not exist." : (message != null ? message : "An error occurred.")
);
String resJson = error.toJson();
res.write(true, ByteBuffer.wrap(resJson.getBytes()), callback);
return true;
}
}

View file

@ -0,0 +1,27 @@
package me.theclashfruit.crss.backend.error;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import me.theclashfruit.crss.models.ErrorResponse;
import java.io.IOException;
@WebServlet(name = "API Version Gone")
public class GoneServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
res.setContentType("application/json");
ErrorResponse error = new ErrorResponse(
410,
"This version of the API is no longer available."
);
String resJson = error.toJson();
res.setStatus(410);
res.getWriter().println(resJson);
}
}

View file

@ -0,0 +1,52 @@
package me.theclashfruit.crss.backend.v2;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import me.theclashfruit.crss.models.PlayerLevels;
import me.theclashfruit.crss.models.Player;
import me.theclashfruit.crss.models.PlayerPosition;
import me.theclashfruit.crss.models.PlayersResponse;
import org.bukkit.Bukkit;
import java.io.IOException;
import java.util.ArrayList;
@WebServlet(name = "Players API")
public class PlayersServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
res.setContentType("application/json");
ArrayList<Player> players = new ArrayList<>();
Bukkit.getOnlinePlayers().forEach(player -> {
players.add(new Player(
player.getUniqueId().toString(),
player.getName(),
player.getGameMode().getValue(), // TODO: Find a non-deprecated way.
new PlayerLevels(
player.getHealth(),
player.getFoodLevel(),
player.getSaturation()
),
new PlayerPosition(
player.getLocation().getX(),
player.getLocation().getY(),
player.getLocation().getZ()
)
));
});
PlayersResponse response = new PlayersResponse(
Bukkit.getOnlinePlayers().size(),
players
);
String resJson = response.toJson();
res.setStatus(200);
res.getWriter().println(resJson);
}
}

View file

@ -0,0 +1,30 @@
package me.theclashfruit.crss.backend.v2;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import me.theclashfruit.crss.models.StatusResponse;
import org.bukkit.Bukkit;
import org.bukkit.World;
import java.io.IOException;
@WebServlet(name = "Status API")
public class StatusServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
res.setContentType("application/json");
StatusResponse response = new StatusResponse(
Bukkit.getBukkitVersion().split("-")[0],
Bukkit.getOnlinePlayers().size(),
Bukkit.getWorlds().stream().map(World::getName).toArray(String[]::new)
);
String resJson = response.toJson();
res.setStatus(200);
res.getWriter().println(resJson);
}
}

View file

@ -0,0 +1,13 @@
package me.theclashfruit.crss.backend.v2.gateway;
import jakarta.servlet.annotation.WebServlet;
import org.eclipse.jetty.ee10.websocket.server.JettyWebSocketServlet;
import org.eclipse.jetty.ee10.websocket.server.JettyWebSocketServletFactory;
@WebServlet(name = "Gateway WebSocket API")
public class GatewayServlet extends JettyWebSocketServlet {
@Override
protected void configure(JettyWebSocketServletFactory factory) {
factory.register(GatewaySocket.class);
}
}

View file

@ -0,0 +1,87 @@
package me.theclashfruit.crss.backend.v2.gateway;
import me.theclashfruit.crss.enums.Channels;
import me.theclashfruit.crss.models.ErrorResponse;
import me.theclashfruit.crss.models.SocketReqMessage;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import static org.bukkit.Bukkit.getLogger;
@WebSocket
public class GatewaySocket {
private static final Set<GatewaySocket> gatewayConnections = new CopyOnWriteArraySet<>();
private Session session;
private final ArrayList<Channels> subscriptions = new ArrayList<>();
@OnWebSocketOpen
public void onOpen(Session session) {
this.session = session;
gatewayConnections.add(this);
}
@OnWebSocketClose
public void onClose(int statusCode, String reason) {
gatewayConnections.remove(this);
}
@OnWebSocketMessage
public void onMessage(String message) {
// TODO: implement stuff from /docs/gateway.md
try {
SocketReqMessage reqMessage = (SocketReqMessage) SocketReqMessage.fromJson(message, SocketReqMessage.class);
if (Objects.equals(reqMessage.getType(), "subscribe")) {
for (String channel : reqMessage.getEvents()) {
this.subscriptions.add(Channels.getChannel(channel));
}
}
} catch (Exception e) {
getLogger().throwing(GatewaySocket.class.getName(), "onMessage", e);
String[] stackTrace = Arrays.stream(e.getStackTrace()).map(StackTraceElement::toString).toArray(String[]::new);
StringBuilder stackTraceString = new StringBuilder();
for (String stackTraceElement : stackTrace) {
stackTraceString
.append(stackTraceElement)
.append("\n");
}
ErrorResponse error = new ErrorResponse(
1011,
stackTraceString.toString()
);
this.session.sendText(error.toJson(), null);
}
}
public static void broadcastToChannel(Channels channel, String message) {
gatewayConnections.stream()
.filter(gateway -> gateway.subscriptions != null)
.filter(gateway -> {
for (Channels subscription : gateway.subscriptions) {
if (subscription.equals(channel)) {
return true;
}
}
return false;
})
.forEach(gateway -> {
gateway.session.sendText(message, null);
});
}
}

View file

@ -0,0 +1,25 @@
package me.theclashfruit.crss.backend.v2.map;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import me.theclashfruit.crss.models.StatusResponse;
import me.theclashfruit.crss.utils.DataUtils;
import org.bukkit.Bukkit;
import org.bukkit.World;
import java.io.IOException;
@WebServlet(name = "Markers API")
public class MarkersServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
res.setContentType("application/json");
String resJson = DataUtils.getMarkers().toJson();
res.setStatus(200);
res.getWriter().println(resJson);
}
}

View file

@ -0,0 +1,66 @@
package me.theclashfruit.crss.backend.v2.map;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import me.theclashfruit.crss.utils.UnminedCLI;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
@WebServlet(name = "Tile API")
public class TileServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
String pathInfo = req.getPathInfo();
String[] pathParts = pathInfo.split("/");
int iZoom = Integer.parseInt(pathParts[2]);
int iX = Integer.parseInt(pathParts[3].split("\\.")[0]);
int iY = Integer.parseInt(pathParts[3].split("\\.")[1]);
String world = pathParts[1];
UnminedCLI unminedCLI = new UnminedCLI();
try {
String eTag = req.getHeader("If-None-Match");
byte[] tile = unminedCLI.renderTile(iZoom, iX, iY, world);
String tTag = generateETag(tile);
res.setHeader("ETag", tTag);
if (eTag != null && eTag.equals(tTag)) {
res.setStatus(304);
return;
}
res.setStatus(200);
res.setContentType("image/png");
res.getOutputStream().write(tile);
} catch (InterruptedException | IOException e) {
res.setStatus(500);
res.setContentType("plain/text");
res.getWriter().println("Internal Server Error");
}
}
public static String generateETag(byte[] data) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(data);
return Base64.getEncoder().encodeToString(hash);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Unable to find SHA-256 algorithm", e);
}
}
}

View file

@ -0,0 +1,25 @@
package me.theclashfruit.crss.banking.commands;
import me.theclashfruit.crss.banking.gui.MainPage;
import me.theclashfruit.crss.utils.ChestGui;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
public class AtmCommand implements CommandExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if(sender instanceof Player) {
Player player = (Player) sender;
ChestGui menuGui = new MainPage(player);
menuGui.openInventory();
} else {
sender.sendMessage("You must be a player to use this command.");
}
return true;
}
}

View file

@ -0,0 +1,19 @@
package me.theclashfruit.crss.banking.commands;
import me.theclashfruit.crss.utils.SqlUtil;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
public class DatabaseCheck implements CommandExecutor {
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (SqlUtil.isConnected()) {
sender.sendMessage("Connected to database!");
} else {
sender.sendMessage("Not connected to database!");
}
return true;
}
}

View file

@ -0,0 +1,99 @@
package me.theclashfruit.crss.banking.gui;
import me.theclashfruit.crss.constants.GlassPane;
import me.theclashfruit.crss.utils.ChestGui;
import me.theclashfruit.crss.utils.SkullUtil;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.SkullType;
import org.bukkit.entity.Item;
import org.bukkit.entity.Player;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.SkullMeta;
import org.bukkit.material.Skull;
public class MainPage extends ChestGui {
public MainPage(Player player) {
super(player, SIX_ROWS, "ATM");
ItemStack playerHead = new ItemStack(Material.SKULL_ITEM, 1, (short) SkullType.PLAYER.ordinal());
SkullMeta playerMeta = ((SkullMeta) playerHead.getItemMeta());
playerMeta.setOwner(player.getName());
playerMeta.setDisplayName(player.getName());
playerHead.setItemMeta(playerMeta);
// ----
ItemMeta glassPaneMeta = Bukkit.getItemFactory().getItemMeta(Material.STAINED_GLASS_PANE);
glassPaneMeta.setDisplayName(" ");
ItemStack lightGrayPane = GlassPane.LIGHT_GRAY;
ItemStack limePane = GlassPane.LIME;
ItemStack pinkPane = GlassPane.PINK;
ItemStack whitePane = GlassPane.WHITE;
ItemStack redPane = GlassPane.RED;
lightGrayPane.setItemMeta(glassPaneMeta);
limePane.setItemMeta(glassPaneMeta);
pinkPane.setItemMeta(glassPaneMeta);
whitePane.setItemMeta(glassPaneMeta);
redPane.setItemMeta(glassPaneMeta);
for (int i = 0; i < SIX_ROWS; i++) {
inventory.setItem(i, lightGrayPane);
}
inventory.setItem(
8,
SkullUtil.getCustomSkull(
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYmViNTg4YjIxYTZmOThhZDFmZjRlMDg1YzU1MmRjYjA1MGVmYzljYWI0MjdmNDYwNDhmMThmYzgwMzQ3NWY3In19fQ==",
"Exit"
)
);
inventory.setItem(9, limePane);
inventory.setItem(17, limePane);
inventory.setItem(18, pinkPane);
inventory.setItem(26, pinkPane);
inventory.setItem(27, whitePane);
inventory.setItem(35, whitePane);
inventory.setItem(36, redPane);
inventory.setItem(44, redPane);
inventory.setItem(20, playerHead);
inventory.setItem(
21,
SkullUtil.getCustomSkull("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNzQzNzM0NmQ4YmRhNzhkNTI1ZDE5ZjU0MGE5NWU0ZTc5ZGFlZGE3OTVjYmM1YTEzMjU2MjM2MzEyY2YifX19")
);
inventory.setItem(
22,
SkullUtil.getCustomSkull(
"eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMzA0MGZlODM2YTZjMmZiZDJjN2E5YzhlYzZiZTUxNzRmZGRmMWFjMjBmNTVlMzY2MTU2ZmE1ZjcxMmUxMCJ9fX0="
)
);
inventory.setItem(23, new ItemStack(Material.WATCH));
inventory.setItem(24, new ItemStack(Material.BARRIER));
for (int i = 29; i < 34; i++) {
inventory.setItem(i, new ItemStack(Material.BARRIER));
}
player.updateInventory();
}
@Override
public boolean handleClick(int slot) {
if (slot == 8) {
player.closeInventory();
}
return true;
}
}

View file

@ -1,16 +0,0 @@
package me.theclashfruit.crss.commands;
import me.theclashfruit.crss.Plugin;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
public class BalanceCommand implements CommandExecutor {
@Override
public boolean onCommand(CommandSender commandSender, Command command, String s, String[] strings) {
commandSender.sendMessage("Your balance is: " + Plugin.getEconomy().getBalance(commandSender.getName()));
return true;
}
}

View file

@ -0,0 +1,23 @@
package me.theclashfruit.crss.constants;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
public class GlassPane {
public static final ItemStack WHITE = new ItemStack(Material.STAINED_GLASS_PANE, 1, ((short) 0), ((byte) 0));
public static final ItemStack ORANGE = new ItemStack(Material.STAINED_GLASS_PANE, 1, ((short) 0), ((byte) 1));
public static final ItemStack MAGENTA = new ItemStack(Material.STAINED_GLASS_PANE, 1, ((short) 0), ((byte) 2));
public static final ItemStack LIGHT_BLUE = new ItemStack(Material.STAINED_GLASS_PANE, 1, ((short) 0), ((byte) 3));
public static final ItemStack YELLOW = new ItemStack(Material.STAINED_GLASS_PANE, 1, ((short) 0), ((byte) 4));
public static final ItemStack LIME = new ItemStack(Material.STAINED_GLASS_PANE, 1, ((short) 0), ((byte) 5));
public static final ItemStack PINK = new ItemStack(Material.STAINED_GLASS_PANE, 1, ((short) 0), ((byte) 6));
public static final ItemStack GRAY = new ItemStack(Material.STAINED_GLASS_PANE, 1, ((short) 0), ((byte) 7));
public static final ItemStack LIGHT_GRAY = new ItemStack(Material.STAINED_GLASS_PANE, 1, ((short) 0), ((byte) 8));
public static final ItemStack CYAN = new ItemStack(Material.STAINED_GLASS_PANE, 1, ((short) 0), ((byte) 9));
public static final ItemStack PURPLE = new ItemStack(Material.STAINED_GLASS_PANE, 1, ((short) 0), ((byte) 10));
public static final ItemStack BLUE = new ItemStack(Material.STAINED_GLASS_PANE, 1, ((short) 0), ((byte) 11));
public static final ItemStack BROWN = new ItemStack(Material.STAINED_GLASS_PANE, 1, ((short) 0), ((byte) 12));
public static final ItemStack GREEN = new ItemStack(Material.STAINED_GLASS_PANE, 1, ((short) 0), ((byte) 13));
public static final ItemStack RED = new ItemStack(Material.STAINED_GLASS_PANE, 1, ((short) 0), ((byte) 14));
public static final ItemStack BLACK = new ItemStack(Material.STAINED_GLASS_PANE, 1, ((short) 0), ((byte) 15));
}

View file

@ -0,0 +1,15 @@
package me.theclashfruit.crss.enums;
public enum Channels {
PLAYER_MOVE,
PLAYER_JOIN,
PLAYER_LEAVE,
PLAYER_CHAT,
PLAYER_DEATH;
// TODO: add more
public static Channels getChannel(String channel) {
return valueOf(channel.toUpperCase());
}
}

View file

@ -1,82 +0,0 @@
package me.theclashfruit.crss.listener;
import me.theclashfruit.crss.api.GatewaySocket;
import me.theclashfruit.crss.models.*;
import me.theclashfruit.crss.util.StringUtil;
import org.bukkit.ChatColor;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.player.PlayerChatEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.server.ServerCommandEvent;
public class ChatListener implements Listener {
@EventHandler
public void onChat(PlayerChatEvent event) {
GatewaySocket.broadcast(new SocketMessage(
"chatMessage",
new SocketData(
event.getPlayer().getName(),
event.getMessage(),
false
)
));
}
@EventHandler
public void onJoin(PlayerJoinEvent event) {
GatewaySocket.broadcast(new SocketMessage(
"playerJoin",
new SocketData(
null,
event.getPlayer().getName() + " joined the game.",
false
)
));
}
@EventHandler
public void onQuit(PlayerQuitEvent event) {
GatewaySocket.broadcast(new SocketMessage(
"playerQuit",
new SocketData(
null,
event.getPlayer().getName() + " left the game.",
false
)
));
}
@EventHandler
public void onDeath(PlayerDeathEvent event) {
GatewaySocket.broadcast(new SocketMessage(
"playerDeath",
new SocketData(
null,
event.getDeathMessage(),
false
)
));
}
@EventHandler
public void onServerCommand(ServerCommandEvent event) {
switch (StringUtil.getFirstPartBeforeSpace(event.getCommand())) {
case "say":
GatewaySocket.broadcast(new SocketMessage(
"serverSay",
new SocketData(
null,
ChatColor.stripColor(event.getCommand().substring(4)),
false
)
));
break;
default:
break;
}
}
}

View file

@ -1,20 +0,0 @@
package me.theclashfruit.crss.listener;
import me.theclashfruit.crss.Plugin;
import net.milkbowl.vault.economy.Economy;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
public class PlayerJoinListener implements Listener {
private Economy economy = Plugin.getEconomy();
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
if (!economy.hasAccount(player))
economy.createPlayerAccount(player);
}
}

View file

@ -1,46 +0,0 @@
package me.theclashfruit.crss.listener;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.plugin.Plugin;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerBedEnterEvent;
import org.bukkit.plugin.PluginManager;
import java.util.concurrent.atomic.AtomicInteger;
import static org.bukkit.Bukkit.getServer;
public class SleepingListener implements Listener {
Plugin plugin = getServer().getPluginManager().getPlugin("CRSS");
FileConfiguration config = plugin.getConfig();
@EventHandler
public void playerBedEnterEvent(PlayerBedEnterEvent event) {
World world = event.getBed().getWorld();
AtomicInteger sleeping = new AtomicInteger();
int players = world.getPlayers().size();
int sleepingPercentage = config.getInt("gameRules.playerSleepingPercentage");
world.getPlayers().forEach(player -> {
if(player.isSleeping())
sleeping.incrementAndGet();
});
if(sleeping.get() >= players * sleepingPercentage / 100) {
long rTime = 24000 - world.getTime();
world.setFullTime(world.getFullTime() + rTime);
if(world.isThundering() || world.hasStorm()) {
world.setThundering(false);
world.setStorm(false);
}
}
}
}

View file

@ -0,0 +1,25 @@
package me.theclashfruit.crss.listeners;
import me.theclashfruit.crss.utils.ChestGui;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryClickEvent;
import java.util.HashMap;
public class InventoryListener implements Listener {
public static HashMap<String, ChestGui> inventories = new HashMap<>();
@EventHandler
public void onClick(InventoryClickEvent event) {
if (event.getClickedInventory() == null) return;
ChestGui gui = inventories.get(event.getClickedInventory().getTitle());
if (gui == null) return;
event.setCancelled(
gui.handleClick(event.getSlot())
);
}
}

View file

@ -0,0 +1,122 @@
package me.theclashfruit.crss.listeners;
import me.theclashfruit.crss.backend.v2.gateway.GatewaySocket;
import me.theclashfruit.crss.enums.Channels;
import me.theclashfruit.crss.utils.SqlUtil;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.player.*;
public class PlayerListener implements Listener {
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
String msg = "{" +
"\"type\":\"player_join\"," +
"\"player\":{" +
"\"name\":\"" + player.getName() + "\"," +
"\"uuid\":\"" + player.getUniqueId() + "\"" +
"}" +
"}";
GatewaySocket.broadcastToChannel(Channels.PLAYER_JOIN, msg);
if (SqlUtil.isPlayerExists(player.getUniqueId().toString())) {
SqlUtil.updatePlayerOnlineStatus(
player.getUniqueId().toString(),
true
);
} else {
SqlUtil.addPlayer(
player.getUniqueId().toString(),
player.getName()
);
}
}
@EventHandler
public void onPlayerQuit(PlayerQuitEvent event) {
Player player = event.getPlayer();
String msg = "{" +
"\"type\":\"player_leave\"," +
"\"player\":{" +
"\"name\":\"" + player.getName() + "\"," +
"\"uuid\":\"" + player.getUniqueId() + "\"" +
"}" +
"}";
GatewaySocket.broadcastToChannel(Channels.PLAYER_LEAVE, msg);
SqlUtil.updatePlayerLastOnline(
player.getUniqueId().toString()
);
}
@EventHandler
public void onPlayerMove(PlayerMoveEvent event) {
Player player = event.getPlayer();
// too lazy to make classes for these lmao
String msg = "{" +
"\"type\":\"player_move\"," +
"\"player\":{" +
"\"name\":\"" + player.getName() + "\"," +
"\"uuid\":\"" + player.getUniqueId() + "\"" +
"}," +
"\"position\":{" +
"\"distance\":" + event.getFrom().distance(event.getTo()) + "," +
"\"from\":{" +
"\"world\":\"" + event.getFrom().getWorld().getName() + "\"," +
"\"x\":" + event.getFrom().getX() + "," +
"\"y\":" + event.getFrom().getY() + "," +
"\"z\":" + event.getFrom().getZ() +
"}," +
"\"to\":{" +
"\"world\":\"" + event.getTo().getWorld().getName() + "\"," +
"\"x\":" + event.getTo().getX() + "," +
"\"y\":" + event.getTo().getY() + "," +
"\"z\":" + event.getTo().getZ() +
"}" +
"}" +
"}";
if (event.getFrom().distance(event.getTo()) != 0)
GatewaySocket.broadcastToChannel(Channels.PLAYER_MOVE, msg);
}
@EventHandler
public void onPlayerChat(AsyncPlayerChatEvent event) {
Player player = event.getPlayer();
String msg = "{" +
"\"type\":\"player_chat\"," +
"\"player\":{" +
"\"name\":\"" + player.getName() + "\"," +
"\"uuid\":\"" + player.getUniqueId() + "\"" +
"}," +
"\"message\":\"" + event.getMessage() + "\"" +
"}";
GatewaySocket.broadcastToChannel(Channels.PLAYER_CHAT, msg);
}
@EventHandler
public void onPlayerDeath(PlayerDeathEvent event) {
Player player = event.getEntity();
String msg = "{" +
"\"type\":\"player_death\"," +
"\"player\":{" +
"\"name\":\"" + player.getName() + "\"," +
"\"uuid\":\"" + player.getUniqueId() + "\"" +
"}," +
"\"message\":\"" + event.getDeathMessage() + "\"" +
"}";
GatewaySocket.broadcastToChannel(Channels.PLAYER_DEATH, msg);
}
}

View file

@ -1,23 +0,0 @@
package me.theclashfruit.crss.map;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet(name = "Map Tile Servlet")
public class MapServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setContentType("text/plain");
/*
MapUtils mapUtils = new MapUtils();
mapUtils.generateTiles();
*/
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().println("Hello, World!");
}
}

View file

@ -1,90 +0,0 @@
package me.theclashfruit.crss.map;
import me.theclashfruit.crss.util.FileUtil;
import org.bukkit.plugin.Plugin;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import static org.bukkit.Bukkit.getLogger;
import static org.bukkit.Bukkit.getServer;
public class MapUtils {
Plugin plugin = getServer().getPluginManager().getPlugin("CRSS");
public void generateTiles() {
plugin.getConfig().getStringList("web.map.worlds").forEach(world -> {
try {
String unminedPath =
String.format(
"%s/natives/unmined/unmined-cli",
plugin.getDataFolder()
);
String worldFolder =
getServer().getWorld(world).getWorldFolder().getAbsolutePath().replace("./", "");
String outputPath =
String.format(
"%s/tmp/%s",
plugin.getDataFolder(),
world
);
String finalOutputPath =
outputPath.replace(
"tmp",
plugin.getConfig().getString("web.map.path")
);
String mapSettingPath =
String.format(
"%s/unmined_settings.json",
plugin.getDataFolder()
);
if(!new File(unminedPath).exists())
throw new RuntimeException("Unmined CLI not found!");
Process process = Runtime
.getRuntime()
.exec(
String.format(
"%s web render --imageformat png --world %s --output %s --mapsettings %s --zoomout 4",
unminedPath,
worldFolder,
outputPath,
mapSettingPath
)
);
BufferedReader stdInput = new BufferedReader(new InputStreamReader(process.getInputStream()));
BufferedReader stdError = new BufferedReader(new InputStreamReader(process.getErrorStream()));
String lines;
while ((lines = stdInput.readLine()) != null) {
getLogger().info(lines);
}
while ((lines = stdError.readLine()) != null) {
getLogger().severe(lines);
}
if(new File(finalOutputPath).exists())
FileUtil.deleteDirectory(new File(finalOutputPath));
Files.move(Paths.get(new File(outputPath, "tiles").getAbsolutePath()), Paths.get(new File(finalOutputPath).getAbsolutePath()), StandardCopyOption.REPLACE_EXISTING);
FileUtil.deleteDirectory(new File(outputPath));
} catch (IOException e) {
getLogger().severe("Error while generating map tiles!");
getLogger().severe(e.toString());
}
});
}
}

View file

@ -0,0 +1,27 @@
package me.theclashfruit.crss.models;
public class ErrorResponse extends Model {
private Integer error;
private String message;
public ErrorResponse(Integer error, String message) {
this.error = error;
this.message = message;
}
public Integer getError() {
return error;
}
public void setError(Integer error) {
this.error = error;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}

View file

@ -1,27 +0,0 @@
package me.theclashfruit.crss.models;
public class GatewayConnection {
private String clientName;
private String[] subscriptions;
public GatewayConnection(String clientName, String[] subscriptions) {
this.clientName = clientName;
this.subscriptions = subscriptions;
}
public String getClientName() {
return clientName;
}
public String[] getSubscriptions() {
return subscriptions;
}
public void setClientName(String clientName) {
this.clientName = clientName;
}
public void setSubscriptions(String[] subscriptions) {
this.subscriptions = subscriptions;
}
}

View file

@ -0,0 +1,29 @@
package me.theclashfruit.crss.models;
import java.util.ArrayList;
public class IndexResponse extends Model {
private String latestVersion;
public ArrayList<Version> versions;
public IndexResponse(String latestVersion, ArrayList<Version> versions) {
this.latestVersion = latestVersion;
this.versions = versions;
}
public String getLatestVersion() {
return latestVersion;
}
public void setLatestVersion(String latestVersion) {
this.latestVersion = latestVersion;
}
public ArrayList<Version> getVersions() {
return versions;
}
public void setVersions(ArrayList<Version> versions) {
this.versions = versions;
}
}

View file

@ -0,0 +1,13 @@
package me.theclashfruit.crss.models;
import me.theclashfruit.crss.utils.JsonUtil;
public class Model {
public String toJson() {
return JsonUtil.gson.toJson(this);
}
public static Object fromJson(String json, Class<? extends Model> clazz) {
return JsonUtil.gson.fromJson(json, clazz);
}
}

View file

@ -0,0 +1,57 @@
package me.theclashfruit.crss.models;
public class Player {
private String uuid;
private String name;
private Integer gameMode;
private PlayerLevels foodLevels;
private PlayerPosition position;
public Player(String uuid, String name, Integer gameMode, PlayerLevels foodLevels, PlayerPosition position) {
this.uuid = uuid;
this.name = name;
this.gameMode = gameMode;
this.foodLevels = foodLevels;
this.position = position;
}
public String getUuid() {
return uuid;
}
public String getName() {
return name;
}
public Integer getGameMode() {
return gameMode;
}
public PlayerLevels getFoodLevels() {
return foodLevels;
}
public PlayerPosition getPosition() {
return position;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public void setName(String name) {
this.name = name;
}
public void setGameMode(Integer gameMode) {
this.gameMode = gameMode;
}
public void setFoodLevels(PlayerLevels foodLevels) {
this.foodLevels = foodLevels;
}
public void setPosition(PlayerPosition position) {
this.position = position;
}
}

View file

@ -0,0 +1,37 @@
package me.theclashfruit.crss.models;
public class PlayerLevels extends Model {
private Double health;
private Integer food;
private Float saturation;
public PlayerLevels(Double health, Integer food, Float saturation) {
this.health = health;
this.food = food;
this.saturation = saturation;
}
public Double getHealth() {
return health;
}
public int getFood() {
return food;
}
public float getSaturation() {
return saturation;
}
public void setHealth(Double health) {
this.health = health;
}
public void setFood(Integer food) {
this.food = food;
}
public void setSaturation(Float saturation) {
this.saturation = saturation;
}
}

View file

@ -1,59 +0,0 @@
package me.theclashfruit.crss.models;
import org.bukkit.World;
public class PlayerList {
private String uniqueId;
private String displayName;
private int gameMode;
private String world;
private PlayerLocation location;
public PlayerList(String uniqueId, String displayName, int gameMode, String world, PlayerLocation location) {
this.uniqueId = uniqueId;
this.displayName = displayName;
this.gameMode = gameMode;
this.world = world;
this.location = location;
}
public String getUniqueId() {
return uniqueId;
}
public String getDisplayName() {
return displayName;
}
public int getGameMode() {
return gameMode;
}
public String getWorld() {
return world;
}
public PlayerLocation getLocation() {
return location;
}
public void setUniqueId(String uniqueId) {
this.uniqueId = uniqueId;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public void setGameMode(int gameMode) {
this.gameMode = gameMode;
}
public void setWorld(String world) {
this.world = world;
}
public void setLocation(PlayerLocation location) {
this.location = location;
}
}

View file

@ -1,36 +1,36 @@
package me.theclashfruit.crss.models;
public class PlayerLocation {
public class PlayerPosition extends Model {
private double x;
private double y;
private double z;
public PlayerLocation(double x, double y, double z) {
public PlayerPosition(double x, double y, double z) {
this.x = x;
this.y = y;
this.z = z;
}
public double getX() {
return x;
}
public double getY() {
return y;
}
public double getZ() {
return z;
}
public void setX(double x) {
this.x = x;
}
public void setY(double y) {
this.y = y;
}
public void setZ(double z) {
this.z = z;
}

View file

@ -0,0 +1,29 @@
package me.theclashfruit.crss.models;
import java.util.ArrayList;
public class PlayersResponse extends Model {
private Integer online;
private ArrayList<Player> players;
public PlayersResponse(Integer online, ArrayList<Player> players) {
this.online = online;
this.players = players;
}
public Integer getOnline() {
return online;
}
public ArrayList<Player> getPlayers() {
return players;
}
public void setOnline(Integer online) {
this.online = online;
}
public void setPlayers(ArrayList<Player> players) {
this.players = players;
}
}

View file

@ -1,37 +0,0 @@
package me.theclashfruit.crss.models;
public class SocketData {
private String username;
private String message;
private Boolean isSystemMessage;
public SocketData(String username, String message, Boolean isSystemMessage) {
this.username = username;
this.message = message;
this.isSystemMessage = isSystemMessage;
}
public String getUsername() {
return username;
}
public String getMessage() {
return message;
}
public Boolean getIsSystemMessage() {
return isSystemMessage;
}
public void setUsername(String username) {
this.username = username;
}
public void setMessage(String message) {
this.message = message;
}
public void setIsSystemMessage(Boolean isSystemMessage) {
this.isSystemMessage = isSystemMessage;
}
}

View file

@ -1,27 +0,0 @@
package me.theclashfruit.crss.models;
public class SocketMessage {
private String event;
private SocketData data;
public SocketMessage(String event, SocketData data) {
this.event = event;
this.data = data;
}
public String getEvent() {
return event;
}
public SocketData getData() {
return data;
}
public void setEvent(String event) {
this.event = event;
}
public void setData(SocketData data) {
this.data = data;
}
}

View file

@ -0,0 +1,32 @@
package me.theclashfruit.crss.models;
import me.theclashfruit.crss.enums.Channels;
import java.util.ArrayList;
import java.util.stream.Collectors;
public class SocketReqMessage extends Model {
private String type;
private String[] events;
public SocketReqMessage(String type, String[] events) {
this.type = type;
this.events = events;
}
public String getType() {
return type;
}
public String[] getEvents() {
return events;
}
public void setType(String type) {
this.type = type;
}
public void setEvents(String[] events) {
this.events = events;
}
}

View file

@ -1,57 +0,0 @@
package me.theclashfruit.crss.models;
public class Status {
private Boolean status;
private String version;
private int players;
private int maxPlayers;
private String motd;
public Status(Boolean status, String version, int players, int maxPlayers, String motd) {
this.status = status;
this.version = version;
this.players = players;
this.maxPlayers = maxPlayers;
this.motd = motd;
}
public Boolean getStatus() {
return status;
}
public String getVersion() {
return version;
}
public int getPlayers() {
return players;
}
public int getMaxPlayers() {
return maxPlayers;
}
public String getMotd() {
return motd;
}
public void setStatus(Boolean status) {
this.status = status;
}
public void setVersion(String version) {
this.version = version;
}
public void setPlayers(int players) {
this.players = players;
}
public void setMaxPlayers(int maxPlayers) {
this.maxPlayers = maxPlayers;
}
public void setMotd(String motd) {
this.motd = motd;
}
}

View file

@ -0,0 +1,37 @@
package me.theclashfruit.crss.models;
public class StatusResponse extends Model {
private String version;
private Integer online;
private String[] worlds;
public StatusResponse(String version, Integer online, String[] worlds) {
this.version = version;
this.online = online;
this.worlds = worlds;
}
public String getVersion() {
return version;
}
public Integer getOnline() {
return online;
}
public String[] getWorlds() {
return worlds;
}
public void setVersion(String version) {
this.version = version;
}
public void setOnline(Integer online) {
this.online = online;
}
public void setWorlds(String[] worlds) {
this.worlds = worlds;
}
}

View file

@ -0,0 +1,37 @@
package me.theclashfruit.crss.models;
public class Version extends Model {
private String name;
private String path;
private Boolean deprecated;
public Version(String name, String path, Boolean deprecated) {
this.name = name;
this.path = path;
this.deprecated = deprecated;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public Boolean getDeprecated() {
return deprecated;
}
public void setDeprecated(Boolean deprecated) {
this.deprecated = deprecated;
}
}

View file

@ -0,0 +1,43 @@
package me.theclashfruit.crss.models.data;
import me.theclashfruit.crss.models.Model;
public class MarkerCategory extends Model {
private String color;
private String icon;
private String title;
public MarkerCategory(String color, String icon, String title) {
this.color = color;
this.icon = icon;
this.title = title;
}
public String getColor() {
return color;
}
public String getIcon() {
return icon;
}
public String getTitle() {
return title;
}
public void setColor(String color) {
this.color = color;
}
public void setIcon(String icon) {
this.icon = icon;
}
public void setTitle(String title) {
this.title = title;
}
public String toString() {
return "MarkerCategory{color=" + color + ", icon=" + icon + ", title=" + title + "}";
}
}

View file

@ -0,0 +1,65 @@
package me.theclashfruit.crss.models.data;
import me.theclashfruit.crss.models.Model;
public class MarkerMarker extends Model {
private String category;
private String title;
private String description;
private Float x;
private Float y;
public MarkerMarker(String category, String title, String description, Float x, Float y) {
this.category = category;
this.title = title;
this.description = description;
this.x = x;
this.y = y;
}
public String getCategory() {
return category;
}
public String getTitle() {
return title;
}
public String getDescription() {
return description;
}
public Float getX() {
return x;
}
public Float getY() {
return y;
}
public void setCategory(String category) {
this.category = category;
}
public void setTitle(String title) {
this.title = title;
}
public void setDescription(String description) {
this.description = description;
}
public void setX(Float x) {
this.x = x;
}
public void setY(Float y) {
this.y = y;
}
public String toString() {
return "MarkerMarker{category=" + category + ", title=" + title + ", description=" + description + ", x=" + x + ", y=" + y + "}";
}
}

View file

@ -0,0 +1,46 @@
package me.theclashfruit.crss.models.data;
import me.theclashfruit.crss.models.Model;
import java.util.ArrayList;
import java.util.Map;
public class Markers extends Model {
private Integer version;
private Map<String, MarkerCategory> categories;
private ArrayList<MarkerMarker> markers;
public Markers(Integer version, Map<String, MarkerCategory> categories, ArrayList<MarkerMarker> markers) {
this.version = version;
this.categories = categories;
this.markers = markers;
}
public Integer getVersion() {
return version;
}
public Map<String, MarkerCategory> getCategories() {
return categories;
}
public ArrayList<MarkerMarker> getMarkers() {
return markers;
}
public void setVersion(Integer version) {
this.version = version;
}
public void setCategories(Map<String, MarkerCategory> categories) {
this.categories = categories;
}
public void setMarkers(ArrayList<MarkerMarker> markers) {
this.markers = markers;
}
public String toString() {
return "Markers{version=" + version + ", categories=" + categories + ", markers=" + markers + "}";
}
}

View file

@ -0,0 +1,64 @@
package me.theclashfruit.crss.models.tile;
import me.theclashfruit.crss.models.Model;
public class Tile extends Model {
private String eTag;
private Integer zoom;
private Integer x;
private Integer z;
private String world;
private Integer timestamp;
public Tile(String eTag, Integer zoom, Integer x, Integer z, String world) {
this.eTag = eTag;
this.zoom = zoom;
this.x = x;
this.z = z;
this.world = world;
}
public String getETag() {
return eTag;
}
public Integer getZoom() {
return zoom;
}
public Integer getX() {
return x;
}
public Integer getZ() {
return z;
}
public String getWorld() {
return world;
}
public void setETag(String eTag) {
this.eTag = eTag;
}
public void setZoom(Integer zoom) {
this.zoom = zoom;
}
public void setX(Integer x) {
this.x = x;
}
public void setZ(Integer z) {
this.z = z;
}
public void setWorld(String world) {
this.world = world;
}
}

View file

@ -0,0 +1,21 @@
package me.theclashfruit.crss.models.tile;
import me.theclashfruit.crss.models.Model;
import java.util.ArrayList;
public class TileCache extends Model {
private ArrayList<Tile> tiles;
public TileCache(ArrayList<Tile> tiles) {
this.tiles = tiles;
}
public ArrayList<Tile> getTiles() {
return tiles;
}
public void setTiles(ArrayList<Tile> tiles) {
this.tiles = tiles;
}
}

View file

@ -1,15 +0,0 @@
package me.theclashfruit.crss.util;
import java.io.File;
public class FileUtil {
public static boolean deleteDirectory(File directoryToBeDeleted) {
File[] allContents = directoryToBeDeleted.listFiles();
if (allContents != null) {
for (File file : allContents) {
deleteDirectory(file);
}
}
return directoryToBeDeleted.delete();
}
}

View file

@ -1,33 +0,0 @@
package me.theclashfruit.crss.util;
import com.google.gson.Gson;
import me.theclashfruit.crss.models.SocketMessage;
import javax.websocket.DecodeException;
import javax.websocket.Decoder;
import javax.websocket.EndpointConfig;
public class MessageDecoder implements Decoder.Text<SocketMessage> {
private static final Gson gson = new Gson();
@Override
public SocketMessage decode(String s) throws DecodeException {
return gson.fromJson(s, SocketMessage.class);
}
@Override
public boolean willDecode(String s) {
return (s != null);
}
@Override
public void init(EndpointConfig endpointConfig) {
// Custom initialization logic
}
@Override
public void destroy() {
// Close resources
}
}

View file

@ -1,28 +0,0 @@
package me.theclashfruit.crss.util;
import com.google.gson.Gson;
import me.theclashfruit.crss.models.SocketMessage;
import javax.websocket.EncodeException;
import javax.websocket.Encoder;
import javax.websocket.EndpointConfig;
public class MessageEncoder implements Encoder.Text<SocketMessage> {
private static final Gson gson = new Gson();
@Override
public String encode(SocketMessage message) throws EncodeException {
return gson.toJson(message);
}
@Override
public void init(EndpointConfig endpointConfig) {
// Custom initialization logic
}
@Override
public void destroy() {
// Close resources
}
}

View file

@ -1,8 +0,0 @@
package me.theclashfruit.crss.util;
public interface StringUtil {
static String getFirstPartBeforeSpace(String input) {
int index = input.contains(" ") ? input.indexOf(" ") : 0;
return input.substring(0, index);
}
}

View file

@ -0,0 +1,41 @@
package me.theclashfruit.crss.utils;
import me.theclashfruit.crss.listeners.InventoryListener;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
public class ChestGui {
public static final int ONE_ROW = 9;
public static final int TWO_ROWS = 18;
public static final int THREE_ROWS = 27;
public static final int FOUR_ROWS = 36;
public static final int FIVE_ROWS = 45;
public static final int SIX_ROWS = 54;
protected final Player player;
private String title;
private int size;
protected Inventory inventory;
public ChestGui(Player player, int size, String title) {
this.player = player;
this.inventory = Bukkit.createInventory(player, size, title);
InventoryListener.inventories.put(title, this);
}
/**
* @return `true` if the inventor click event should be cancelled.
*/
public boolean handleClick(int slot) {
return false;
}
public void openInventory() {
player.openInventory(inventory);
}
}

View file

@ -0,0 +1,50 @@
package me.theclashfruit.crss.utils;
import com.google.gson.Gson;
import me.theclashfruit.crss.models.data.Markers;
import org.bukkit.Bukkit;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class DataUtils {
public static Markers getMarkers() throws IOException {
File markersFile = Bukkit
.getPluginManager()
.getPlugin("CRSS")
.getDataFolder()
.toPath()
.resolve("data")
.resolve("markers.json")
.toFile();
if (!markersFile.exists()) {
throw new RuntimeException("Markers file not found!");
}
try (FileReader fileReader = new FileReader(markersFile)) {
return JsonUtil.gson.fromJson(fileReader, Markers.class);
}
}
public static void saveMarkers(Markers markers) throws IOException {
File markersFile = Bukkit
.getPluginManager()
.getPlugin("CRSS")
.getDataFolder()
.toPath()
.resolve("data")
.resolve("markers.json")
.toFile();
if (!markersFile.exists()) {
throw new RuntimeException("Markers file not found!");
}
try (FileWriter fileWriter = new FileWriter(markersFile, false)) {
fileWriter.write(JsonUtil.gson.toJson(markers));
}
}
}

View file

@ -0,0 +1,11 @@
package me.theclashfruit.crss.utils;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public class JsonUtil {
public static Gson gson = new GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.create();
}

View file

@ -0,0 +1,23 @@
package me.theclashfruit.crss.utils;
import org.bukkit.Bukkit;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.Jetty;
import static me.theclashfruit.crss.CRSSPlugin.LOGGER;
public class ReqMiddleware extends ContextHandler {
@Override
public boolean handle(Request req, Response res, Callback callback) throws Exception {
req.setAttribute("me.theclashfruit.crss.currentTimeMillis", System.currentTimeMillis());
res.getHeaders().put("X-Powered-By", "Minecraft " + Bukkit.getBukkitVersion());
res.getHeaders().put("Access-Control-Allow-Origin", "*");
return super.handle(req, res, callback);
}
}

View file

@ -0,0 +1,52 @@
package me.theclashfruit.crss.utils;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.SkullMeta;
import java.lang.reflect.Field;
import java.util.UUID;
import static me.theclashfruit.crss.CRSSPlugin.LOGGER;
public class SkullUtil {
public static ItemStack getCustomSkull(String base64) {
ItemStack skull = new ItemStack(Material.SKULL_ITEM, 1, (short) 3);
if (base64 == null || base64.isEmpty())
return skull;
SkullMeta skullMeta = (SkullMeta) skull.getItemMeta();
GameProfile profile = new GameProfile(UUID.randomUUID(), null);
profile.getProperties().put("textures", new Property("textures", base64));
try {
Field profileField = skullMeta.getClass().getDeclaredField("profile");
profileField.setAccessible(true);
profileField.set(skullMeta, profile);
} catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) {
LOGGER.throwing(SkullUtil.class.getName(), "getCustomSkull", e);
}
skull.setItemMeta(skullMeta);
return skull;
}
public static ItemStack getCustomSkull(String base64, String name) {
ItemStack skull = getCustomSkull(base64);
SkullMeta skullMeta = (SkullMeta) skull.getItemMeta();
skullMeta.setDisplayName(name);
skull.setItemMeta(skullMeta);
return skull;
}
}

View file

@ -0,0 +1,123 @@
package me.theclashfruit.crss.utils;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import static me.theclashfruit.crss.CRSSPlugin.LOGGER;
public class SqlUtil {
private static Connection connection;
private static String connectionString;
private static String username;
private static String password;
public static void setConnectionDetails(String connectionString, String username, String password) {
SqlUtil.connectionString = connectionString;
SqlUtil.username = username;
SqlUtil.password = password;
}
private static void openConnection(String connectionString, String username, String password) {
try {
if (!isConnected()) {
connection = DriverManager.getConnection(connectionString, username, password);
LOGGER.info("Connected to database!");
}
} catch (SQLException e) {
LOGGER.throwing(SqlUtil.class.getName(), "SqlUtil", e);
LOGGER.severe("Could not connect to database!");
}
}
public static void openConnection() {
if (connectionString == null) {
LOGGER.warning("Connection string is null, cannot connect to database!");
return;
}
LOGGER.info("Connecting to database...");
openConnection(connectionString, username, password);
}
public static void closeConnection() {
try {
if (isConnected()) {
connection.close();
LOGGER.info("Disconnected from database!");
}
} catch (SQLException e) {
LOGGER.throwing(SqlUtil.class.getName(), "closeConnection", e);
}
}
public static boolean isConnected() {
return (connection != null);
}
public static Connection getConnection() {
openConnection(connectionString, username, password);
return connection;
}
public static void addPlayer(String uuid, String username) {
try {
PreparedStatement statement = connection.prepareStatement("INSERT INTO players (uuid, username, first_join, last_online, is_online) VALUES (?, ?, current_timestamp, current_timestamp, 1)");
statement.setString(1, uuid);
statement.setString(2, username);
statement.executeUpdate();
} catch (SQLException e) {
LOGGER.throwing(SqlUtil.class.getName(), "addPlayer", e);
}
}
public static void updatePlayerOnlineStatus(String uuid, boolean isOnline) {
try {
PreparedStatement statement = connection.prepareStatement("UPDATE players SET is_online = ? WHERE uuid = ?");
statement.setInt(1, isOnline ? 1 : 0);
statement.setString(2, uuid);
statement.executeUpdate();
} catch (SQLException e) {
LOGGER.throwing(SqlUtil.class.getName(), "updatePlayerOnlineStatus", e);
}
}
public static void updatePlayerLastOnline(String uuid) {
try {
PreparedStatement statement = connection.prepareStatement("UPDATE players SET last_online = current_timestamp, is_online = 0 WHERE uuid = ?");
statement.setString(1, uuid);
statement.executeUpdate();
} catch (SQLException e) {
LOGGER.throwing(SqlUtil.class.getName(), "updatePlayerLastOnline", e);
}
}
public static boolean isPlayerExists(String uuid) {
try {
PreparedStatement statement = connection.prepareStatement("SELECT * FROM players WHERE uuid = ?");
statement.setString(1, uuid);
return statement.executeQuery().next();
} catch (SQLException e) {
LOGGER.throwing(SqlUtil.class.getName(), "isPlayerExists", e);
}
return false;
}
}

View file

@ -0,0 +1,228 @@
package me.theclashfruit.crss.utils;
import com.google.gson.Gson;
import me.theclashfruit.crss.models.tile.TileCache;
import org.apache.commons.lang.SystemUtils;
import org.bukkit.Bukkit;
import org.bukkit.World;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.concurrent.Semaphore;
import static me.theclashfruit.crss.CRSSPlugin.LOGGER;
public class UnminedCLI {
public static Path executablePath =
Bukkit
.getPluginManager()
.getPlugin("CRSS")
.getDataFolder()
.toPath()
.resolve("lib")
.resolve("unmined")
.resolve(SystemUtils.IS_OS_WINDOWS ? "unmined-cli.exe" : "unmined-cli");
private static final Semaphore semaphore = new Semaphore(1);
private static ArrayList<ArrayList<Integer>> tilesBeingRendered = new ArrayList<>();
public UnminedCLI() { }
public byte[] renderTile(int zoom, int chunkX, int chunkZ, String world) throws InterruptedException {
semaphore.acquire();
String safeWorld = world.replaceAll("[^a-zA-Z0-9_\\-]", "");
String[] bukkitWorlds = Bukkit.getWorlds().stream().map(World::getName).toArray(String[]::new);
if (!Arrays.asList(bukkitWorlds).contains(safeWorld))
return createErrorImage("World not found!");
int size;
int dimension;
if (chunkX > 32767 || chunkZ > 32767 || chunkX < -32767 || chunkZ < -32767)
return createErrorImage("Out of bounds!");
else if (zoom < 0)
size = (16 * (zoom * -2));
else if (zoom < 3 && zoom > 0)
size = (16 / (zoom * 2));
else
size = 16;
if (safeWorld.endsWith("_nether"))
dimension = -1;
else if (safeWorld.endsWith("_the_end"))
dimension = 1;
else
dimension = 0;
Path tilesPath = Bukkit
.getPluginManager()
.getPlugin("CRSS")
.getDataFolder()
.toPath()
.resolve("tiles")
.resolve(safeWorld)
.resolve(Integer.toString(zoom));
Path logPath = Bukkit
.getPluginManager()
.getPlugin("CRSS")
.getDataFolder()
.toPath()
.resolve("log.txt");
Path settingsPath = Bukkit
.getPluginManager()
.getPlugin("CRSS")
.getDataFolder()
.toPath()
.resolve("unmined_settings.json");
if (!tilesPath.toFile().exists())
tilesPath.toFile().mkdirs();
Thread thread = new Thread(() -> {
Process proc = null;
try {
ProcessBuilder processBuilder = new ProcessBuilder();
tilesBeingRendered.add(new ArrayList<>(Arrays.asList(chunkX * size, chunkZ * size)));
processBuilder.command(
executablePath.toString(),
"image",
"render",
"--mapsettings", settingsPath.toAbsolutePath().toString(),
"--zoom", Integer.toString(size == 16 ? 0 : zoom),
"--area", "\"c(" + (chunkX * size) + "," + (chunkZ * size) + "," + size + "," + size + ")\"",
"--world",
Bukkit
.getServer()
.getWorldContainer()
.toPath()
.resolve(
safeWorld
)
.toAbsolutePath()
.toString(),
"--dimension", String.valueOf(dimension),
"--output",
tilesPath
.resolve(chunkX + "." + chunkZ + ".png")
.toAbsolutePath()
.toString()
);
proc = processBuilder.start();
StringBuilder iOutput = new StringBuilder();
StringBuilder eOutput = new StringBuilder();
BufferedReader iReader = new BufferedReader(new InputStreamReader(proc.getInputStream()));
BufferedReader eReader = new BufferedReader(new InputStreamReader(proc.getErrorStream()));
String iLine;
String eLine;
while ((iLine = iReader.readLine()) != null) {
iOutput.append(iLine).append("\n");
}
while ((eLine = eReader.readLine()) != null) {
eOutput.append(eLine).append("\n");
}
try (FileWriter writer = new FileWriter(logPath.toFile(), true)) {
writer.append(iOutput.toString());
writer.append(eOutput.toString());
writer.append("----\n");
}
proc.waitFor();
} catch (IOException | InterruptedException e) {
LOGGER.throwing(this.getClass().getName(), "renderTile", e);
} finally {
if (proc != null) {
proc.destroyForcibly();
}
}
});
semaphore.release();
File tileFile = tilesPath
.resolve(chunkX + "." + chunkZ + ".png")
.toFile();
synchronized (this) {
if (!tileFile.exists() && !tilesBeingRendered.contains(new ArrayList<>(Arrays.asList(chunkX * size, chunkZ * size)))) {
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
LOGGER.throwing(this.getClass().getName(), "renderTile", e);
} finally {
tilesBeingRendered.remove(new ArrayList<>(Arrays.asList(chunkX * size, chunkZ * size)));
}
byte[] img = readTileImage(tileFile);
if (img != null)
return img;
return createErrorImage("Failed to render tile.");
}
}
return readTileImage(tileFile);
}
private byte[] readTileImage(File tileFile) {
try {
return Files.readAllBytes(tileFile.toPath());
} catch (IOException e) {
LOGGER.throwing(this.getClass().getName(), "renderTile", e);
}
return null;
}
private byte[] createErrorImage(String msg) {
BufferedImage image = new BufferedImage(256, 256, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = image.createGraphics();
graphics.setColor(Color.RED);
graphics.setFont(new Font("Arial", Font.BOLD, 24));
graphics.drawString(msg, 8, 32);
// image to byte array
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try {
ImageIO.write(image, "png", outputStream);
} catch (IOException e) {
LOGGER.throwing(this.getClass().getName(), "renderTile", e);
}
return outputStream.toByteArray();
}
}

View file

@ -13,7 +13,7 @@ import java.nio.file.Paths;
* <p>
* This class is used to run the server in development mode.
* </p>
* <p>Copyright 2023-2024 TheClashFruit</p>
* <p>Copyright (c) 2023-2024 TheClashFruit</p>
*/
public class DevRunner {
public static void main(String[] args) throws IOException {
@ -25,7 +25,7 @@ public class DevRunner {
ProcessBuilder pb = new ProcessBuilder();
pb.command("cmd", "/c", "start", "java", "-Xms128M", "-XX:MaxRAMPercentage=95.0", "-jar", "server.jar");
pb.command("cmd", "/c", "start", "java", "-Xms128M", "-XX:MaxRAMPercentage=95.0", "-jar", "server.jar", "nogui");
pb.directory(runDir);
pb.start();

View file

@ -1,15 +1,20 @@
prefix: '§7[§cCRSS§7]§r '
web:
enabled: true
enabled: false
port: 25580
api:
enabled: true
map:
enabled: true
access_log:
enabled: false
path: 'tiles'
worlds:
- world
token: 'changeme'
sql:
connection: 'jdbc:mariadb://localhost:3306/crss'
username: 'crss'
password: 'crss1234'
gameRules:
playerSleepingPercentage: 100

View file

@ -0,0 +1,27 @@
{
"$schema": "https://theclashfruit.theclashfruit.page/jsonschemas/crss/markers-1.0.json",
"version": 1,
"categories": {
"main": {
"color": "#FF0000",
"icon": "icon.png",
"title": "Main"
}
},
"markers": [
{
"category": "main",
"title": "Marker 1",
"description": "Description 1",
"x": 0.5,
"y": 0.5
},
{
"category": "main",
"title": "2000; 2000",
"description": "The two-thousandth marker.",
"x": 2000,
"y": 2000
}
]
}

View file

@ -1,15 +1,22 @@
name: CRSS
main: me.theclashfruit.crss.Plugin
main: me.theclashfruit.crss.CRSSPlugin
version: ${version}
database: true
author: TheClashFruit
commands:
balance:
description: Check your balance
usage: /balance
load: POSTWORLD
depend:
- Vault
commands:
crss:
description: Your description
usage: /<command>
permission: crss.admin
checkdb:
description: Your description
usage: /<command>
permission: crss.admin
atm:
description: Your description
usage: /<command>
permission: crss.banking.atm