Compare commits

...
This repository has been archived on 2024-07-29. You can view files and clone it, but cannot push or open issues or pull requests.

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,11 +1,11 @@
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;

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