diff --git a/.github/workflows/build-mod.yml b/.github/workflows/build-mod.yml
index ff102b50..ca4d9c51 100644
--- a/.github/workflows/build-mod.yml
+++ b/.github/workflows/build-mod.yml
@@ -1,4 +1,4 @@
-name: Build Mod
+name: "Build Mods"
on:
push:
@@ -7,39 +7,20 @@ on:
pull_request:
paths:
- "mod/**/*"
+ workflow_call:
jobs:
build:
- runs-on: ubuntu-latest
+ runs-on: "ubuntu-latest"
steps:
- - uses: actions/checkout@v3
- - name: Set up JDK 17
- uses: actions/setup-java@v3
+ - uses: "actions/checkout@v4"
+
+ - name: "Setting up JDK 17"
+ uses: "actions/setup-java@v4"
with:
java-version: "17"
distribution: "adopt"
- - run: ./gradlew build
- working-directory: ./mod
-
- - name: Upload Forge Build
- uses: actions/upload-artifact@v3
- with:
- name: Forge
- path: mod/dist/*-forge.jar
- - name: Upload Fabric Build
- uses: actions/upload-artifact@v3
- with:
- name: Fabric
- path: mod/dist/*-fabric.jar
-
- - name: Release Tag
- if: startsWith(github.ref, 'refs/tags/v')
- uses: softprops/action-gh-release@v1
- with:
- prerelease: true
- fail_on_unmatched_files: true
- files: |
- mod/dist/*.jar
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ - name: "Building mods"
+ working-directory: "./mod"
+ run: "./gradlew build"
diff --git a/.github/workflows/build-server.yml b/.github/workflows/build-server.yml
index eacd5eed..4b961e9b 100644
--- a/.github/workflows/build-server.yml
+++ b/.github/workflows/build-server.yml
@@ -1,4 +1,4 @@
-name: Build+Test Server
+name: "Build+Test Server"
on:
push:
@@ -7,23 +7,31 @@ on:
pull_request:
paths:
- "server/**/*"
+ workflow_call:
jobs:
build:
- runs-on: ubuntu-latest
- strategy:
- matrix:
- version: ["lts/*", "latest"]
+ runs-on: "ubuntu-latest"
steps:
- - uses: actions/checkout@v3
- - name: Use latest Node.js LTS
- uses: actions/setup-node@v3
+ - uses: "actions/checkout@v4"
+
+ - name: "Setting up Bun"
+ uses: oven-sh/setup-bun@v2
with:
- node-version: ${{ matrix.version }}
- # cache: "yarn"
- - run: yarn
- working-directory: ./server
- - run: yarn build
- working-directory: ./server
- - run: yarn test
- working-directory: ./server
+ bun-version: latest
+
+ - name: "Installing dependencies"
+ working-directory: "./server"
+ run: "bun install"
+
+ - name: "Checking types"
+ working-directory: "./server"
+ run: "bun run check:types"
+
+ - name: "Checking style"
+ working-directory: "./server"
+ run: "bun run check:style"
+
+ - name: "Running tests"
+ working-directory: "./server"
+ run: "bun run test"
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 00000000..1566bcc5
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,35 @@
+name: "Publishing to release"
+
+on:
+ release:
+ types:
+ - "published"
+
+permissions:
+ contents: "write"
+
+jobs:
+ release-mod:
+ runs-on: "ubuntu-latest"
+ steps:
+ - uses: "actions/checkout@v4"
+
+ - name: "Setting up JDK 17"
+ uses: "actions/setup-java@v4"
+ with:
+ java-version: "17"
+ distribution: "adopt"
+
+ - name: "Building mods"
+ working-directory: "./mod"
+ run: "./gradlew build"
+
+ - name: "Publishing mods"
+ working-directory: "./mod"
+ run: |
+ for file in $(find "dist/" -maxdepth 1 -type f -name "*.jar"); do
+ echo "Uploading $file"
+ gh release upload ${{ github.event.release.tag_name }} "$file" --clobber
+ done
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/Dockerfile b/Dockerfile
index 7714d197..77e4b138 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,6 @@
# base is shared between build/test and deploy
-FROM node:18-alpine AS base
+# See options at: https://hub.docker.com/r/oven/bun
+FROM oven/bun:latest AS base
WORKDIR /usr/src/app/
@@ -8,29 +9,28 @@ COPY ./server/package.json /usr/src/app/package.json
FROM base AS build
-COPY ./server/yarn.lock /usr/src/app/yarn.lock
-RUN yarn
+COPY ./server/bun.lock /usr/src/app/bun.lock
+COPY ./server/bunfig.toml /usr/src/app/bunfig.toml
+RUN bun install
# copy source as late as possible, to reuse docker cache with node_modules
COPY ./server /usr/src/app
-RUN yarn build
-
-FROM build AS test
-RUN yarn test
# final image only includes minimal files
FROM base AS deploy
+COPY --from=build /usr/src/app/bun.lock /usr/src/app/bun.lock
+COPY --from=build /usr/src/app/bunfig.toml /usr/src/app/bunfig.toml
COPY --from=build /usr/src/app/node_modules /usr/src/app/node_modules
-COPY --from=build /usr/src/app/dist /usr/src/app/dist
+COPY --from=build /usr/src/app/src /usr/src/app/src
ENV NODE_ENV=production
ENV HOST=0.0.0.0
#Mount your FS or volume or whatnot to this folder
-RUN mkdir /data
+# TODO: Fix env override of config data
ENV MAPSYNC_DATA_DIR=/data
-EXPOSE 12312/tcp
+# EXPOSE 12312/tcp
-CMD [ "yarn", "start" ]
+CMD [ "bun", "run", "start" ]
diff --git a/README.md b/README.md
index 5533744f..857dafca 100644
--- a/README.md
+++ b/README.md
@@ -46,11 +46,10 @@ You can control who has access to a Sync Server by editing its `allowed-users.tx
System Install
-- install recent nodejs (~17)
+- install [Bun](https://bun.sh/)
- clone code, `cd server`
-- `npm install`
-- `npm run build` -- this has to be run after every time the code is edited
-- `npm run start`
+- `bun install`
+- `bun start`
- to stop, press Ctrl+C twice
diff --git a/mod/common/build.gradle b/mod/common/build.gradle
index 999ab4b2..b40281c0 100644
--- a/mod/common/build.gradle
+++ b/mod/common/build.gradle
@@ -20,6 +20,9 @@ dependencies {
modCompileOnly("maven.modrinth:journeymap:5JbcGXLn")
// https://modrinth.com/mod/xaeros-minimap/version/23.6.2_Fabric_1.18.2 (23.6.2 fabric)
modCompileOnly("maven.modrinth:xaeros-minimap:Jwydpps9")
+
+ // https://github.com/TooTallNate/Java-WebSocket
+ compileOnly("org.java-websocket:Java-WebSocket:1.6.0")
}
tasks {
diff --git a/mod/common/src/main/java/gjum/minecraft/mapsync/common/CatchupLogic.java b/mod/common/src/main/java/gjum/minecraft/mapsync/common/CatchupLogic.java
index d75d001a..b71d134b 100644
--- a/mod/common/src/main/java/gjum/minecraft/mapsync/common/CatchupLogic.java
+++ b/mod/common/src/main/java/gjum/minecraft/mapsync/common/CatchupLogic.java
@@ -32,7 +32,7 @@ public void addCatchupChunks(List catchupChunks) {
if (catchupChunks.isEmpty()) return;
var catchupDim = catchupChunks.get(0).dimension();
if (!dimensionState.dimension.equals(catchupDim)) {
- logger.warn("Catchup chunks from wrong dimension " + catchupDim + ", expected " + dimensionState.dimension);
+ LOGGER.warn("Catchup chunks from wrong dimension " + catchupDim + ", expected " + dimensionState.dimension);
return;
}
synchronized (this.catchupChunks) {
diff --git a/mod/common/src/main/java/gjum/minecraft/mapsync/common/MapSyncMod.java b/mod/common/src/main/java/gjum/minecraft/mapsync/common/MapSyncMod.java
index ce47224e..c67236d9 100644
--- a/mod/common/src/main/java/gjum/minecraft/mapsync/common/MapSyncMod.java
+++ b/mod/common/src/main/java/gjum/minecraft/mapsync/common/MapSyncMod.java
@@ -4,13 +4,16 @@
import gjum.minecraft.mapsync.common.config.ModConfig;
import gjum.minecraft.mapsync.common.config.ServerConfig;
import gjum.minecraft.mapsync.common.data.*;
+import gjum.minecraft.mapsync.common.net.SyncAddress;
import gjum.minecraft.mapsync.common.net.SyncClient;
import gjum.minecraft.mapsync.common.net.packet.*;
+import java.util.stream.Collectors;
import net.minecraft.client.KeyMapping;
import net.minecraft.client.Minecraft;
import net.minecraft.client.multiplayer.ServerData;
import net.minecraft.network.protocol.game.ClientboundLoginPacket;
import net.minecraft.network.protocol.game.ClientboundRespawnPacket;
+import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
@@ -18,7 +21,6 @@
import org.lwjgl.glfw.GLFW;
import java.util.*;
-import java.util.stream.Collectors;
import static gjum.minecraft.mapsync.common.Cartography.chunkTileFromLevel;
@@ -27,7 +29,7 @@ public abstract class MapSyncMod {
private static final Minecraft mc = Minecraft.getInstance();
- public static final Logger logger = LogManager.getLogger(MapSyncMod.class);
+ public static final Logger LOGGER = LogManager.getLogger(MapSyncMod.class);
private static MapSyncMod INSTANCE;
@@ -123,30 +125,41 @@ public void handleRespawn(ClientboundRespawnPacket packet) {
if (syncServerAddresses.isEmpty()) return shutDownSyncClients();
// will be filled with clients that are still wanted (address) and are still connected
- var existingClients = new HashMap();
+ var existingClients = new HashMap();
- for (SyncClient client : syncClients) {
- if (client.isShutDown) continue;
+ for (final SyncClient client : this.syncClients) {
+ if (client.isShutDown) {
+ continue;
+ }
// avoid reconnecting to same sync server, to keep shared state (expensive to resync)
- if (!client.gameAddress.equals(serverConfig.gameAddress)) {
- debugLog("Disconnecting sync client; different game server");
+ if (!StringUtils.equals(client.gameAddress, serverConfig.gameAddress)) {
+ LOGGER.warn("Disconnecting sync client; different game server");
client.shutDown();
- } else if (!syncServerAddresses.contains(client.address)) {
- debugLog("Disconnecting sync client; different sync address");
+ }
+ else if (!syncServerAddresses.contains(client.syncAddress.toString())) {
+ LOGGER.warn("Disconnecting sync client; different sync address");
client.shutDown();
- } else {
- existingClients.put(client.address, client);
+ }
+ else {
+ existingClients.put(client.syncAddress, client);
}
}
- syncClients = syncServerAddresses.stream().map(address -> {
- var client = existingClients.get(address);
- if (client == null) client = new SyncClient(address, serverConfig.gameAddress);
- client.autoReconnect = true;
- return client;
- }).collect(Collectors.toList());
-
- return syncClients;
+ this.syncClients = syncServerAddresses.stream()
+ .map(SyncAddress::of)
+ .filter(Objects::nonNull)
+ .distinct()
+ .map((address) -> {
+ SyncClient client = existingClients.get(address);
+ if (client == null) {
+ client = new SyncClient(address, serverConfig.gameAddress);
+ }
+ client.autoReconnect = true;
+ return client;
+ })
+ .collect(Collectors.toCollection(ArrayList::new));
+
+ return this.syncClients;
}
public List shutDownSyncClients() {
@@ -214,11 +227,6 @@ public void handleMcChunkPartialChange(int cx, int cz) {
// TODO update ChunkTile in a second or so; remember dimension in case it changes til then
}
- public void handleSyncServerEncryptionSuccess() {
- debugLog("tcp encrypted");
- // TODO tell server our current dimension
- }
-
public void handleRegionTimestamps(ClientboundRegionTimestampsPacket packet, SyncClient client) {
DimensionState dimension = getDimensionState();
if (dimension == null) return;
@@ -258,7 +266,7 @@ public void handleSharedChunk(ChunkTile chunkTile) {
public void handleCatchupData(ClientboundChunkTimestampsResponsePacket packet) {
var dimensionState = getDimensionState();
if (dimensionState == null) return;
- debugLog("received catchup: " + packet.chunks.size() + " " + packet.chunks.get(0).syncClient.address);
+ debugLog("received catchup: " + packet.chunks.size() + " " + packet.chunks.get(0).syncClient.syncAddress);
dimensionState.addCatchupChunks(packet.chunks);
}
@@ -269,9 +277,9 @@ public void requestCatchupData(List chunks) {
}
debugLog("requesting more catchup: " + chunks.size());
- var byServer = new HashMap>();
+ var byServer = new HashMap>();
for (CatchupChunk chunk : chunks) {
- var list = byServer.computeIfAbsent(chunk.syncClient.address, (a) -> new ArrayList<>());
+ var list = byServer.computeIfAbsent(chunk.syncClient.syncAddress, (a) -> new ArrayList<>());
list.add(chunk);
}
for (List chunksForServer : byServer.values()) {
@@ -283,7 +291,7 @@ public void requestCatchupData(List chunks) {
public static void debugLog(String msg) {
// we could also make use of slf4j's debug() but I don't know how to reconfigure that at runtime based on globalConfig
if (modConfig.isShowDebugLog()) {
- logger.info(msg);
+ LOGGER.info(msg);
}
}
}
diff --git a/mod/common/src/main/java/gjum/minecraft/mapsync/common/ModGui.java b/mod/common/src/main/java/gjum/minecraft/mapsync/common/ModGui.java
index a81215c8..3703c17e 100644
--- a/mod/common/src/main/java/gjum/minecraft/mapsync/common/ModGui.java
+++ b/mod/common/src/main/java/gjum/minecraft/mapsync/common/ModGui.java
@@ -2,15 +2,17 @@
import com.mojang.blaze3d.vertex.PoseStack;
import gjum.minecraft.mapsync.common.config.ServerConfig;
+import java.util.ArrayList;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.components.EditBox;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.TextComponent;
+import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
-import java.util.List;
-
import static gjum.minecraft.mapsync.common.MapSyncMod.getMod;
public class ModGui extends Screen {
@@ -78,8 +80,12 @@ protected void init() {
public void connectClicked(Button btn) {
try {
if (syncServerAddressField == null) return;
- var addresses = List.of(syncServerAddressField.getValue().split("[^-_.:A-Za-z0-9]+"));
- serverConfig.setSyncServerAddresses(addresses);
+ serverConfig.setSyncServerAddresses(
+ Stream.of(StringUtils.split(syncServerAddressField.getValue(), ","))
+ .map(String::trim)
+ .filter(StringUtils::isNotEmpty)
+ .collect(Collectors.toCollection(ArrayList::new))
+ );
getMod().shutDownSyncClients();
getMod().getSyncClients();
btn.active = false;
@@ -117,7 +123,7 @@ public void render(@NotNull PoseStack poseStack, int i, int j, float f) {
for (var client : syncClients) {
int statusColor;
String statusText;
- if (client.isEncrypted()) {
+ if (client.isEstablished()) {
numConnected++;
statusColor = 0x008800;
statusText = "Connected";
@@ -128,7 +134,7 @@ public void render(@NotNull PoseStack poseStack, int i, int j, float f) {
statusColor = 0xffffff;
statusText = "Connecting...";
}
- statusText = client.address + " " + statusText;
+ statusText = client.syncAddress + " " + statusText;
drawString(poseStack, font, statusText, left, msgY, statusColor);
msgY += 10;
}
diff --git a/mod/common/src/main/java/gjum/minecraft/mapsync/common/net/ClientHandler.java b/mod/common/src/main/java/gjum/minecraft/mapsync/common/net/ClientHandler.java
deleted file mode 100644
index 37e2fa90..00000000
--- a/mod/common/src/main/java/gjum/minecraft/mapsync/common/net/ClientHandler.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package gjum.minecraft.mapsync.common.net;
-
-import gjum.minecraft.mapsync.common.data.CatchupChunk;
-import gjum.minecraft.mapsync.common.net.packet.*;
-import io.netty.channel.ChannelHandlerContext;
-import io.netty.channel.ChannelInboundHandlerAdapter;
-
-import java.io.IOException;
-import java.net.ConnectException;
-
-import static gjum.minecraft.mapsync.common.MapSyncMod.getMod;
-
-/**
- * tightly coupled to {@link SyncClient}
- */
-public class ClientHandler extends ChannelInboundHandlerAdapter {
- private final SyncClient client;
-
- public ClientHandler(SyncClient client) {
- this.client = client;
- }
-
- @Override
- public void channelRead(ChannelHandlerContext ctx, Object packet) {
- try {
- if (!client.isEncrypted()) {
- if (packet instanceof ClientboundEncryptionRequestPacket pktEncryptionRequest) {
- client.setUpEncryption(ctx, pktEncryptionRequest);
- } else throw new Error("Expected encryption request, got " + packet);
- } else if (packet instanceof ChunkTilePacket pktChunkTile) {
- getMod().handleSharedChunk(pktChunkTile.chunkTile);
- } else if (packet instanceof ClientboundRegionTimestampsPacket pktRegionTimestamps) {
- getMod().handleRegionTimestamps(pktRegionTimestamps, client);
- } else if (packet instanceof ClientboundChunkTimestampsResponsePacket pktCatchup) {
- for (CatchupChunk chunk : pktCatchup.chunks) {
- chunk.syncClient = this.client;
- }
- getMod().handleCatchupData((ClientboundChunkTimestampsResponsePacket) packet);
- } else throw new Error("Expected packet, got " + packet);
- } catch (Throwable err) {
- err.printStackTrace();
- ctx.close();
- }
- }
-
- @Override
- public void exceptionCaught(ChannelHandlerContext ctx, Throwable err) throws Exception {
- if (err instanceof IOException && "Connection reset by peer".equals(err.getMessage())) return;
- if (err instanceof ConnectException && err.getMessage().startsWith("Connection refused: ")) return;
-
- SyncClient.logger.info("[map-sync] Network Error: " + err);
- err.printStackTrace();
- ctx.close();
- super.exceptionCaught(ctx, err);
- }
-
- @Override
- public void channelInactive(ChannelHandlerContext ctx) throws Exception {
- client.handleDisconnect(new RuntimeException("Channel inactive"));
- super.channelInactive(ctx);
- }
-}
diff --git a/mod/common/src/main/java/gjum/minecraft/mapsync/common/net/ClientboundPacketDecoder.java b/mod/common/src/main/java/gjum/minecraft/mapsync/common/net/ClientboundPacketDecoder.java
deleted file mode 100644
index aac61eb0..00000000
--- a/mod/common/src/main/java/gjum/minecraft/mapsync/common/net/ClientboundPacketDecoder.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package gjum.minecraft.mapsync.common.net;
-
-import gjum.minecraft.mapsync.common.net.packet.*;
-import io.netty.buffer.ByteBuf;
-import io.netty.channel.ChannelHandlerContext;
-import io.netty.handler.codec.ReplayingDecoder;
-import org.jetbrains.annotations.Nullable;
-
-import java.util.List;
-
-public class ClientboundPacketDecoder extends ReplayingDecoder {
- public static @Nullable Packet constructServerPacket(int id, ByteBuf buf) {
- if (id == ChunkTilePacket.PACKET_ID) return ChunkTilePacket.read(buf);
- if (id == ClientboundEncryptionRequestPacket.PACKET_ID) return ClientboundEncryptionRequestPacket.read(buf);
- if (id == ClientboundChunkTimestampsResponsePacket.PACKET_ID) return ClientboundChunkTimestampsResponsePacket.read(buf);
- if (id == ClientboundRegionTimestampsPacket.PACKET_ID) return ClientboundRegionTimestampsPacket.read(buf);
- return null;
- }
-
- @Override
- protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List