From 832e8d07dc97bb8521cb38095f59b260ab483d0d Mon Sep 17 00:00:00 2001 From: Michael Rowe Date: Wed, 21 May 2025 11:23:22 -0700 Subject: [PATCH 1/7] Move code from src/main/java to fruitjuice subdir, to fix import errors --- src/main/java/{ => fruitjuice}/CustomBlockFace.java | 0 src/main/java/{ => fruitjuice}/FruitJuicePlugin.java | 0 src/main/java/{ => fruitjuice}/HitClickType.java | 0 src/main/java/{ => fruitjuice}/LocationType.java | 0 src/main/java/{ => fruitjuice}/RemoteSession.java | 0 src/main/java/{ => fruitjuice}/ServerListenerThread.java | 0 src/main/java/{ => fruitjuice}/cmd/CmdEntity.java | 0 src/main/java/{ => fruitjuice}/cmd/CmdEvent.java | 0 src/main/java/{ => fruitjuice}/cmd/CmdPlayer.java | 0 src/main/java/{ => fruitjuice}/cmd/CmdWorld.java | 0 10 files changed, 0 insertions(+), 0 deletions(-) rename src/main/java/{ => fruitjuice}/CustomBlockFace.java (100%) rename src/main/java/{ => fruitjuice}/FruitJuicePlugin.java (100%) rename src/main/java/{ => fruitjuice}/HitClickType.java (100%) rename src/main/java/{ => fruitjuice}/LocationType.java (100%) rename src/main/java/{ => fruitjuice}/RemoteSession.java (100%) rename src/main/java/{ => fruitjuice}/ServerListenerThread.java (100%) rename src/main/java/{ => fruitjuice}/cmd/CmdEntity.java (100%) rename src/main/java/{ => fruitjuice}/cmd/CmdEvent.java (100%) rename src/main/java/{ => fruitjuice}/cmd/CmdPlayer.java (100%) rename src/main/java/{ => fruitjuice}/cmd/CmdWorld.java (100%) diff --git a/src/main/java/CustomBlockFace.java b/src/main/java/fruitjuice/CustomBlockFace.java similarity index 100% rename from src/main/java/CustomBlockFace.java rename to src/main/java/fruitjuice/CustomBlockFace.java diff --git a/src/main/java/FruitJuicePlugin.java b/src/main/java/fruitjuice/FruitJuicePlugin.java similarity index 100% rename from src/main/java/FruitJuicePlugin.java rename to src/main/java/fruitjuice/FruitJuicePlugin.java diff --git a/src/main/java/HitClickType.java b/src/main/java/fruitjuice/HitClickType.java similarity index 100% rename from src/main/java/HitClickType.java rename to src/main/java/fruitjuice/HitClickType.java diff --git a/src/main/java/LocationType.java b/src/main/java/fruitjuice/LocationType.java similarity index 100% rename from src/main/java/LocationType.java rename to src/main/java/fruitjuice/LocationType.java diff --git a/src/main/java/RemoteSession.java b/src/main/java/fruitjuice/RemoteSession.java similarity index 100% rename from src/main/java/RemoteSession.java rename to src/main/java/fruitjuice/RemoteSession.java diff --git a/src/main/java/ServerListenerThread.java b/src/main/java/fruitjuice/ServerListenerThread.java similarity index 100% rename from src/main/java/ServerListenerThread.java rename to src/main/java/fruitjuice/ServerListenerThread.java diff --git a/src/main/java/cmd/CmdEntity.java b/src/main/java/fruitjuice/cmd/CmdEntity.java similarity index 100% rename from src/main/java/cmd/CmdEntity.java rename to src/main/java/fruitjuice/cmd/CmdEntity.java diff --git a/src/main/java/cmd/CmdEvent.java b/src/main/java/fruitjuice/cmd/CmdEvent.java similarity index 100% rename from src/main/java/cmd/CmdEvent.java rename to src/main/java/fruitjuice/cmd/CmdEvent.java diff --git a/src/main/java/cmd/CmdPlayer.java b/src/main/java/fruitjuice/cmd/CmdPlayer.java similarity index 100% rename from src/main/java/cmd/CmdPlayer.java rename to src/main/java/fruitjuice/cmd/CmdPlayer.java diff --git a/src/main/java/cmd/CmdWorld.java b/src/main/java/fruitjuice/cmd/CmdWorld.java similarity index 100% rename from src/main/java/cmd/CmdWorld.java rename to src/main/java/fruitjuice/cmd/CmdWorld.java From 597ebdd385caa879e0b1ad74151b74df5298beae Mon Sep 17 00:00:00 2001 From: Michael Rowe Date: Wed, 21 May 2025 11:41:32 -0700 Subject: [PATCH 2/7] Bump to version 0.0.4b; minor fixes based on VSCode suggestions. --- pom.xml | 2 +- src/main/java/fruitjuice/CustomBlockFace.java | 4 ++-- src/main/java/fruitjuice/cmd/CmdEntity.java | 1 + src/main/java/fruitjuice/cmd/CmdEvent.java | 2 +- src/main/resources/plugin.yml | 14 +++++++------- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index 9181f92d..ae822809 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.jdeast FruitJuice - 0.3.0 + 0.4.0b diff --git a/src/main/java/fruitjuice/CustomBlockFace.java b/src/main/java/fruitjuice/CustomBlockFace.java index bc216024..66eeeb78 100644 --- a/src/main/java/fruitjuice/CustomBlockFace.java +++ b/src/main/java/fruitjuice/CustomBlockFace.java @@ -1,11 +1,11 @@ package fruitjuice; -import org.bukkit.Material; +// import org.bukkit.Material; import org.bukkit.block.BlockFace; public class CustomBlockFace { - //TODO + // TODO public static BlockFace getSignValueOf(String str){ //NORTH //NORTH_NORTH_EAST diff --git a/src/main/java/fruitjuice/cmd/CmdEntity.java b/src/main/java/fruitjuice/cmd/CmdEntity.java index 1c57e4ce..7ef1ee86 100644 --- a/src/main/java/fruitjuice/cmd/CmdEntity.java +++ b/src/main/java/fruitjuice/cmd/CmdEntity.java @@ -26,6 +26,7 @@ public void execute(String command, String[] args) { if (entity == null) { plugin.getLogger().info("Entity [" + args[0] + "] not found."); session.send("Fail,This entity identity not exist"); + return; } // entity.getTile diff --git a/src/main/java/fruitjuice/cmd/CmdEvent.java b/src/main/java/fruitjuice/cmd/CmdEvent.java index 89735f10..cb39a452 100644 --- a/src/main/java/fruitjuice/cmd/CmdEvent.java +++ b/src/main/java/fruitjuice/cmd/CmdEvent.java @@ -30,7 +30,7 @@ public void execute(String command, String[] args) { Location loc = block.getLocation(); b.append(session.blockLocationToRelative(loc)); b.append(","); - b.append(session.blockFaceToNotch(event.getBlockFace())); + b.append(RemoteSession.blockFaceToNotch(event.getBlockFace())); b.append(","); b.append(event.getPlayer().getEntityId()); if (session.interactEventQueue.size() > 0) { diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index b278981a..3af1bbfd 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,8 +1,8 @@ -author: [Zhuowei,MinecraftDawn,jdeast] -database: false -description: Implementation of the Minecraft Python API -main: fruitjuice.FruitJuicePlugin name: FruitJuice -startup: postworld -version: '0.1.0' -api-version: 1.13 \ No newline at end of file +description: A plugin that provides a Python SDK for the Minecraft API. +author: [Zhuowei,MinecraftDawn,jdeast,mwrowe] +version: '0.4.0b' +api-version: 1.13 +main: FruitJuice.FruitJuicePlugin +database: false +startup: postworld \ No newline at end of file From 51e73248b03a5d2b7b7cd1af7c916b69ed9b8b0d Mon Sep 17 00:00:00 2001 From: Michael Rowe Date: Wed, 21 May 2025 13:44:58 -0700 Subject: [PATCH 3/7] Renamed src/main/java/fruitjuice -> .../FruitJuice; ignore maven stuff --- .gitignore | 3 +++ src/main/java/fruitjuice/CustomBlockFace.java | 2 +- src/main/java/fruitjuice/FruitJuicePlugin.java | 2 +- src/main/java/fruitjuice/HitClickType.java | 2 +- src/main/java/fruitjuice/LocationType.java | 2 +- src/main/java/fruitjuice/RemoteSession.java | 12 ++++++------ src/main/java/fruitjuice/ServerListenerThread.java | 2 +- src/main/java/fruitjuice/cmd/CmdEntity.java | 7 ++++--- src/main/java/fruitjuice/cmd/CmdEvent.java | 5 +++-- src/main/java/fruitjuice/cmd/CmdPlayer.java | 7 ++++--- src/main/java/fruitjuice/cmd/CmdWorld.java | 7 ++++--- 11 files changed, 29 insertions(+), 22 deletions(-) diff --git a/.gitignore b/.gitignore index ae132941..9f0b0667 100644 --- a/.gitignore +++ b/.gitignore @@ -65,3 +65,6 @@ target/maven-archiver .mvn mvnw mvnw.cmd + +# maven stuff +target/maven-status \ No newline at end of file diff --git a/src/main/java/fruitjuice/CustomBlockFace.java b/src/main/java/fruitjuice/CustomBlockFace.java index 66eeeb78..ddcd3795 100644 --- a/src/main/java/fruitjuice/CustomBlockFace.java +++ b/src/main/java/fruitjuice/CustomBlockFace.java @@ -1,4 +1,4 @@ -package fruitjuice; +package FruitJuice; // import org.bukkit.Material; import org.bukkit.block.BlockFace; diff --git a/src/main/java/fruitjuice/FruitJuicePlugin.java b/src/main/java/fruitjuice/FruitJuicePlugin.java index e36e2e2a..468c8891 100644 --- a/src/main/java/fruitjuice/FruitJuicePlugin.java +++ b/src/main/java/fruitjuice/FruitJuicePlugin.java @@ -1,4 +1,4 @@ -package fruitjuice; +package FruitJuice; import org.bukkit.Bukkit; import org.bukkit.Material; diff --git a/src/main/java/fruitjuice/HitClickType.java b/src/main/java/fruitjuice/HitClickType.java index 22696ce4..29230ff1 100644 --- a/src/main/java/fruitjuice/HitClickType.java +++ b/src/main/java/fruitjuice/HitClickType.java @@ -1,4 +1,4 @@ -package fruitjuice; +package FruitJuice; /** * Config option to determine whether hit events are triggered by LEFT clicks, RIGHT clicks (like the Pi) or BOTH diff --git a/src/main/java/fruitjuice/LocationType.java b/src/main/java/fruitjuice/LocationType.java index 016f1d8f..0b955ff9 100644 --- a/src/main/java/fruitjuice/LocationType.java +++ b/src/main/java/fruitjuice/LocationType.java @@ -1,4 +1,4 @@ -package fruitjuice; +package FruitJuice; /** * Config option to determine whether locations are relative to the spawn point (default like pi) or absolute diff --git a/src/main/java/fruitjuice/RemoteSession.java b/src/main/java/fruitjuice/RemoteSession.java index ddb14c57..b58cff93 100644 --- a/src/main/java/fruitjuice/RemoteSession.java +++ b/src/main/java/fruitjuice/RemoteSession.java @@ -1,9 +1,4 @@ -package fruitjuice; - -import fruitjuice.cmd.CmdEntity; -import fruitjuice.cmd.CmdEvent; -import fruitjuice.cmd.CmdPlayer; -import fruitjuice.cmd.CmdWorld; +package FruitJuice; import org.bukkit.*; import org.bukkit.block.Block; @@ -13,6 +8,11 @@ import org.bukkit.event.player.AsyncPlayerChatEvent; import org.bukkit.event.player.PlayerInteractEvent; +import FruitJuice.cmd.CmdEntity; +import FruitJuice.cmd.CmdEvent; +import FruitJuice.cmd.CmdPlayer; +import FruitJuice.cmd.CmdWorld; + import java.io.*; import java.net.Socket; import java.util.ArrayDeque; diff --git a/src/main/java/fruitjuice/ServerListenerThread.java b/src/main/java/fruitjuice/ServerListenerThread.java index 6951cb6c..c2bbb926 100644 --- a/src/main/java/fruitjuice/ServerListenerThread.java +++ b/src/main/java/fruitjuice/ServerListenerThread.java @@ -1,4 +1,4 @@ -package fruitjuice; +package FruitJuice; import java.io.*; import java.net.*; diff --git a/src/main/java/fruitjuice/cmd/CmdEntity.java b/src/main/java/fruitjuice/cmd/CmdEntity.java index 7ef1ee86..35cb3d12 100644 --- a/src/main/java/fruitjuice/cmd/CmdEntity.java +++ b/src/main/java/fruitjuice/cmd/CmdEntity.java @@ -1,12 +1,13 @@ -package fruitjuice.cmd; +package FruitJuice.cmd; -import fruitjuice.FruitJuicePlugin; -import fruitjuice.RemoteSession; import org.bukkit.Location; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.util.Vector; +import FruitJuice.FruitJuicePlugin; +import FruitJuice.RemoteSession; + public class CmdEntity { private final String preFix = "entity."; private RemoteSession session; diff --git a/src/main/java/fruitjuice/cmd/CmdEvent.java b/src/main/java/fruitjuice/cmd/CmdEvent.java index cb39a452..e28aac88 100644 --- a/src/main/java/fruitjuice/cmd/CmdEvent.java +++ b/src/main/java/fruitjuice/cmd/CmdEvent.java @@ -1,12 +1,13 @@ -package fruitjuice.cmd; +package FruitJuice.cmd; -import fruitjuice.RemoteSession; import org.bukkit.Location; import org.bukkit.block.Block; import org.bukkit.event.entity.ProjectileHitEvent; import org.bukkit.event.player.AsyncPlayerChatEvent; import org.bukkit.event.player.PlayerInteractEvent; +import FruitJuice.RemoteSession; + public class CmdEvent { private final String preFix = "events."; private RemoteSession session; diff --git a/src/main/java/fruitjuice/cmd/CmdPlayer.java b/src/main/java/fruitjuice/cmd/CmdPlayer.java index ef32d3e4..5c5e6ddd 100644 --- a/src/main/java/fruitjuice/cmd/CmdPlayer.java +++ b/src/main/java/fruitjuice/cmd/CmdPlayer.java @@ -1,12 +1,13 @@ -package fruitjuice.cmd; +package FruitJuice.cmd; -import fruitjuice.RemoteSession; -import fruitjuice.FruitJuicePlugin; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.entity.Player; import org.bukkit.util.Vector; +import FruitJuice.FruitJuicePlugin; +import FruitJuice.RemoteSession; + public class CmdPlayer { private final String preFix = "player."; private RemoteSession session; diff --git a/src/main/java/fruitjuice/cmd/CmdWorld.java b/src/main/java/fruitjuice/cmd/CmdWorld.java index bc48cb24..cb171691 100644 --- a/src/main/java/fruitjuice/cmd/CmdWorld.java +++ b/src/main/java/fruitjuice/cmd/CmdWorld.java @@ -1,7 +1,5 @@ -package fruitjuice.cmd; +package FruitJuice.cmd; -import fruitjuice.FruitJuicePlugin; -import fruitjuice.RemoteSession; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; @@ -19,6 +17,9 @@ import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; +import FruitJuice.FruitJuicePlugin; +import FruitJuice.RemoteSession; + import java.util.Collection; public class CmdWorld { From 11f59b9f35dd6050aa76a57d2c508aa9f5457d65 Mon Sep 17 00:00:00 2001 From: Michael Rowe Date: Wed, 21 May 2025 13:57:48 -0700 Subject: [PATCH 4/7] Add world.getBlockData in CmdWorld.java --- src/main/java/fruitjuice/cmd/CmdWorld.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/fruitjuice/cmd/CmdWorld.java b/src/main/java/fruitjuice/cmd/CmdWorld.java index cb171691..28491f54 100644 --- a/src/main/java/fruitjuice/cmd/CmdWorld.java +++ b/src/main/java/fruitjuice/cmd/CmdWorld.java @@ -47,10 +47,13 @@ public void execute(World world, String command, String[] args) { session.send(getBlocks(loc1, loc2)); -// // world.getBlockWithData untested -// } else if (c.equals("world.getBlockWithData")) { -// Location loc = parseRelativeBlockLocation(args[0], args[1], args[2]); -// send(world.getBlockTypeIdAt(loc) + "," + world.getBlockAt(loc).getData()); + // world.getBlockData + } else if (command.equals("getBlockData")) { + Location loc = session.parseRelativeBlockLocation(args[0], args[1], args[2]); + Block thisBlock = world.getBlockAt(loc); + BlockData blockData = thisBlock.getBlockData(); + + session.send(blockData.toString()); // world.setBlock } else if (command.equals("setBlock")) { From 41fc843cd1c3a8710795ced5b33d71d5dc35ff48 Mon Sep 17 00:00:00 2001 From: Michael Rowe Date: Thu, 22 May 2025 11:28:10 -0700 Subject: [PATCH 5/7] Try to force git to preserve FruitJuice directory capitalization --- src/main/java/{fruitjuice => FruitJuice}/CustomBlockFace.java | 0 src/main/java/{fruitjuice => FruitJuice}/FruitJuicePlugin.java | 0 src/main/java/{fruitjuice => FruitJuice}/HitClickType.java | 0 src/main/java/{fruitjuice => FruitJuice}/LocationType.java | 0 src/main/java/{fruitjuice => FruitJuice}/RemoteSession.java | 0 .../java/{fruitjuice => FruitJuice}/ServerListenerThread.java | 0 src/main/java/{fruitjuice => FruitJuice}/cmd/CmdEntity.java | 0 src/main/java/{fruitjuice => FruitJuice}/cmd/CmdEvent.java | 0 src/main/java/{fruitjuice => FruitJuice}/cmd/CmdPlayer.java | 0 src/main/java/{fruitjuice => FruitJuice}/cmd/CmdWorld.java | 0 10 files changed, 0 insertions(+), 0 deletions(-) rename src/main/java/{fruitjuice => FruitJuice}/CustomBlockFace.java (100%) rename src/main/java/{fruitjuice => FruitJuice}/FruitJuicePlugin.java (100%) rename src/main/java/{fruitjuice => FruitJuice}/HitClickType.java (100%) rename src/main/java/{fruitjuice => FruitJuice}/LocationType.java (100%) rename src/main/java/{fruitjuice => FruitJuice}/RemoteSession.java (100%) rename src/main/java/{fruitjuice => FruitJuice}/ServerListenerThread.java (100%) rename src/main/java/{fruitjuice => FruitJuice}/cmd/CmdEntity.java (100%) rename src/main/java/{fruitjuice => FruitJuice}/cmd/CmdEvent.java (100%) rename src/main/java/{fruitjuice => FruitJuice}/cmd/CmdPlayer.java (100%) rename src/main/java/{fruitjuice => FruitJuice}/cmd/CmdWorld.java (100%) diff --git a/src/main/java/fruitjuice/CustomBlockFace.java b/src/main/java/FruitJuice/CustomBlockFace.java similarity index 100% rename from src/main/java/fruitjuice/CustomBlockFace.java rename to src/main/java/FruitJuice/CustomBlockFace.java diff --git a/src/main/java/fruitjuice/FruitJuicePlugin.java b/src/main/java/FruitJuice/FruitJuicePlugin.java similarity index 100% rename from src/main/java/fruitjuice/FruitJuicePlugin.java rename to src/main/java/FruitJuice/FruitJuicePlugin.java diff --git a/src/main/java/fruitjuice/HitClickType.java b/src/main/java/FruitJuice/HitClickType.java similarity index 100% rename from src/main/java/fruitjuice/HitClickType.java rename to src/main/java/FruitJuice/HitClickType.java diff --git a/src/main/java/fruitjuice/LocationType.java b/src/main/java/FruitJuice/LocationType.java similarity index 100% rename from src/main/java/fruitjuice/LocationType.java rename to src/main/java/FruitJuice/LocationType.java diff --git a/src/main/java/fruitjuice/RemoteSession.java b/src/main/java/FruitJuice/RemoteSession.java similarity index 100% rename from src/main/java/fruitjuice/RemoteSession.java rename to src/main/java/FruitJuice/RemoteSession.java diff --git a/src/main/java/fruitjuice/ServerListenerThread.java b/src/main/java/FruitJuice/ServerListenerThread.java similarity index 100% rename from src/main/java/fruitjuice/ServerListenerThread.java rename to src/main/java/FruitJuice/ServerListenerThread.java diff --git a/src/main/java/fruitjuice/cmd/CmdEntity.java b/src/main/java/FruitJuice/cmd/CmdEntity.java similarity index 100% rename from src/main/java/fruitjuice/cmd/CmdEntity.java rename to src/main/java/FruitJuice/cmd/CmdEntity.java diff --git a/src/main/java/fruitjuice/cmd/CmdEvent.java b/src/main/java/FruitJuice/cmd/CmdEvent.java similarity index 100% rename from src/main/java/fruitjuice/cmd/CmdEvent.java rename to src/main/java/FruitJuice/cmd/CmdEvent.java diff --git a/src/main/java/fruitjuice/cmd/CmdPlayer.java b/src/main/java/FruitJuice/cmd/CmdPlayer.java similarity index 100% rename from src/main/java/fruitjuice/cmd/CmdPlayer.java rename to src/main/java/FruitJuice/cmd/CmdPlayer.java diff --git a/src/main/java/fruitjuice/cmd/CmdWorld.java b/src/main/java/FruitJuice/cmd/CmdWorld.java similarity index 100% rename from src/main/java/fruitjuice/cmd/CmdWorld.java rename to src/main/java/FruitJuice/cmd/CmdWorld.java From ec1c4efadc403831dc9724abb513ffa522d51e34 Mon Sep 17 00:00:00 2001 From: Michael Rowe Date: Thu, 22 May 2025 16:49:27 -0700 Subject: [PATCH 6/7] Add docker-compose.yml's and update documentation. --- README.md | 8 +- README_server_setup.md | 10 +- server_setup/README_setup.md | 264 ++++++++++++++++++++++++ server_setup/bedrock/docker-compose.yml | 49 +++++ server_setup/je/docker-compose.yml | 47 +++++ target/FruitJuice-0.4.0b.jar | Bin 0 -> 34165 bytes 6 files changed, 372 insertions(+), 6 deletions(-) create mode 100644 server_setup/README_setup.md create mode 100644 server_setup/bedrock/docker-compose.yml create mode 100644 server_setup/je/docker-compose.yml create mode 100644 target/FruitJuice-0.4.0b.jar diff --git a/README.md b/README.md index f7833e16..58ab4883 100644 --- a/README.md +++ b/README.md @@ -24,8 +24,13 @@ and connect it to a Bukkit server running this plugin and a websocket relay. - world.getBlocks(x1:int, y1:int, z1:int, x2:int, y2:int, z2:int) -> list - Get the blocks of the input position range + + + - world.getBlockData(x:int, y:int, z:int) -> str + - Get the state of the block at the input position, including material + (block type) and any other attributes (differ by block type). - + - world.setBlock(x:int, y:int, z:int, block:str) -> None: - Set the block of the input position @@ -223,3 +228,4 @@ mvn package - [opticyclic](https://github.com/opticyclic) - [timcu](https://www.triptera.com.au/wordpress/) - [pxai](https://github.com/pxai) + - [mwrowe](https://github.com/mwrowe) diff --git a/README_server_setup.md b/README_server_setup.md index 9988ce12..c6f62581 100644 --- a/README_server_setup.md +++ b/README_server_setup.md @@ -37,14 +37,14 @@ Note: updates to servers/plugins may require stopping your server, repeating ste 1. Make sure java is up to date ``` -java –-version +java -version ``` If this is not 21 or higher, you need to update java. See here for more general directions: https://docs.papermc.io/misc/java-install -On the Raspberry Pi, the default repo isn’t good enough. This will install jdk-21 from zulu (thanks https://pimylifeup.com/raspberry-pi-java/, modified to use non-headless as recommended by paper) +On the Raspberry Pi, the default repo isn�t good enough. This will install jdk-21 from zulu (thanks https://pimylifeup.com/raspberry-pi-java/, modified to use non-headless as recommended by paper) ``` sudo apt update @@ -115,7 +115,7 @@ java -Xmx2048M -Xms2048M -jar paper-1.20-17.jar 13. You can skip this step if you're only connecting locally (from computers all connected to the same router), and not with scratch. -On your router, forward ports 19132 (bedrock), 25565 (java), 4711 (python), 14711 (scratch), 80 (http) and 443 (https) to the same ports on your server (or different port numbers if you’ve changed the defaults). +On your router, forward ports 19132 (bedrock), 25565 (java), 4711 (python), 14711 (scratch), 80 (http) and 443 (https) to the same ports on your server (or different port numbers if you�ve changed the defaults). Unfortunately, the details depend on the specifics of your router. You can google something like "{router model} + port forwarding", but generally it's something like @@ -232,7 +232,7 @@ https://www.minecraft.net/en-us/download - Select "servers" tab - Click "add server" - Enter whatever you like for "Server name" - - Enter the server’s IP for "Server address" + - Enter the server�s IP for "Server address" - For WAN connections, this should be the external IP - For LAN connections, this should be the internal IP - Enter 19132 for the port @@ -244,7 +244,7 @@ https://www.minecraft.net/en-us/download https://www.python.org/downloads/ -During the installation, be sure to check the box to update your path to include python and pip, or you’ll get "command not found" errors in the next steps +During the installation, be sure to check the box to update your path to include python and pip, or you�ll get "command not found" errors in the next steps 2. Install [pyncraft](https://github.com/jdeast/pyncraft). In a terminal, type diff --git a/server_setup/README_setup.md b/server_setup/README_setup.md new file mode 100644 index 00000000..69372667 --- /dev/null +++ b/server_setup/README_setup.md @@ -0,0 +1,264 @@ +# Minecraft Server Setup Using Docker +### Customized to run python using PynCraft via FruitJuice and RCON + +How to set up a Python- and Scratch-enabled, Java+Bedrock server using Docker +that allows remote connections. You can interact with the Minecraft world +hosted on this server using the +[PynCraft python package](https://github.com/jdeast/pyncraft/tree/main). + +## Overview + +Setting up your own server can be challenging. Luckily, you shouldn't have +to! Docker is an application that lets you run a "container" from your +computer, or from a server in the cloud. This container acts like a separate +server even though it is using its host computer's resources. + +You configure the container to specify what resources-- like memory, +communication ports and shared directories-- it can use. Even though it +is running on the host, it is isolated from the host and can only access +what you specify. Often the container will be running a different OS, and +is provisioned with exactly the software it needs to do its job. If it +needs to install, say, a different C or Java compiler, you don't have to +worry about it messing with your system, because it is isolated. When you +are done with it, you shut it down, and delete it, leaving your system +unchanged. + +The recommended procedure here is: + +1) Install the software you need-- Minecraft, Docker, Python, VSCode, git, Scratch +2) Configure the directory you will work in. +3) Copy and customize the `docker-compose.yml` file that specifies the + docker configuration. +4) Run `docker compose up` to start the server! + +Once the minecraft server is running, multiple players can connect to it. +This guide assumes you will be setting up the server on a personal computer +that sits behind a firewall in your home or school network. To connect to +the server from within that network, you can use `localhost` if you are +connecting from the same machine that is running docker (the host); otherwise, +you will need to know the IP address of the host. + +## The Dreaded Command Line + +All of what is described here can be done without using the command line-- +the terminal app. But once you get used to it, the terminal is probably +easier, and you should really learn to use the terminal if you want to +be a programmer. + +_(work in progress)_ + +Windows now includes WSL, the "Windows Subsystem for Linux". This allows you to +install a Linux distribution (like Ubuntu, Debian, Fedora or Mint) with a +shell (like, terminal app) that also runs on Mac OS with only minor differences. +This gives you super powers on all three major operating systems. + +The only part of this that **has** to be run from the command line (I think) +is the `docker compose` command for starting and stoppin the docker container. +You can accomplish the same thing from within the Desktop app (I think), +but docker compose makes it super easy. + +## Software Installs + +### Minecraft: + +You will need a current copy of Minecraft, and a Minecraft account to use +on the server. If you've already been playing Minecraft, you're good to go. + +Otherwise, go to https://www.minecraft.net/en-us/download and then buy +the "Minecraft: Java & Bedrock" game license. Choose a player name. + +### Docker + +Docker desktop can be downloaded for free by individuals and schools +from https://docs.docker.com/get-started/get-docker/. Choose the appropriate version for your operating system. + +### Python + +You don't actuall need python to run the server, but you do if you want to +use it. Go to: + +https://www.python.org/downloads/ + +### Git + +This is used to download open source code from the GitHub and other +repositories. Not strictly needed to run the server. + +### Visual Studio Code (VSCode) + +VSCode is a free Integrated Development Environment (IDE) application that +may be used for developing code. It has a lot of nice features, like +an editor that colors your python code and checks for errors, a debugger, +an integrated terminal, a Docker plugin and tools for connecting to Docker containers so you can write code for them while they're running. It even +has AI built in to help you write code! It's free-- give it a try! + +## Configuration + +#### NOTE: BEDROCK EDITION IS NOT YET WORKING! + +You will need to set up the directory where you will run your server from. +It's easiest (in terms of less typing) if you use your home directory as +the root. I would suggest something like this: + +``` +~/minecraft-server + /bedrock + /plugins + FruitJuice.jar + /data + docker-compose.yml + /je + /plugins + FruitJuice.jar + /data + docker-compose.yml + /code + venv + (the python files you write) +``` + +Here `~` is your home directory. The `bedrock` and `je` subdirectories will +host bedrock and java-edition versions of your server. If you're only +running one or the other, that's fine-- just pick that one. But naming them +this way gives you the option to run both. **NOTE: you can run a bedrock +_server_ from a MacOS or Linux host, but you can't _play_ from them-- the +bedrock client only runs under Windows.** + +In the `bedrock` and/or `je` subdirectories, create additional subdirectories +`plugins` and `data`. The contents of these will be shared with the +docker container. `plugins` will hold the `FruitJuice.jar` file that +adds FruitJuice to the server. `data` will be mapped to the directory +on the server that contains all the data for the minecraft world that +you are hosting. + +Download the FruitJuice plugin from here: + +https://github.com/jdeast/FruitJuice/tree/master/target + +Click on the `FruitJuice-0.x.0.jar` file that is the latest version, +choose "download raw", then copy it to the `plugins` directory(s) shown above. + +Get the `docker-compose.yml` file(s) from here: + +https://github.com/jdeast/FruitJuice/tree/master/server_setup + +Download the file and add it to the appropriate server version(s). + +## Configuring the Server + +The `docker-compose.yml` file specifies tells docker what "image" to +use and what resources and parameters to give it. The image contains +the specification for the container, the operating system and all the +software it needs to host minecraft. We will use an image that is +maintained by the **itzg group**. This Java Edition image has been +downloaded more than 100 million times! The present document is only +meant to get you started. For more details on how you can customize +your server, refer to their [documentation](https://hub.docker.com/r/itzg/minecraft-server). + +**You need to customize the `docker-compose.yml` before you use it.** +Comments start with a `#`; the file is annotated to let you know what each +line means. You should change: + +- `OPS`: replace `AdminPlayerName` with your player name. This will give + you admin privileges, which are needed for many commands. +- `RCON_PASSWORD`: The [RCON package](https://github.com/conqp/rcon) provides + another way to send commands to minecraft independent of FruitJuice. + Setting a custom password helps secure your server, though it is not a + big deal if you are running the server behind a firewall. + + You may also want to change some of the other settings that control + game play. + + The `volumes` section tells what local directory on your computer will + hold the data for your world. If you wanted to have a second world, + you could do something like this: + ``` + volumes: + # attach the subdirectory 'data' to the container's /data path + - ./data_world2:/data + ``` +Note that each container can only host one world at a time (I think). +But you can run multiple containers-- just add another service. Most of +the settings would be the same, but you would have to change the port +mapping. For both ports and volumes, the settings are formatted like +`host:server`. The server value should stay the same, but you would change +the host value so each server is connected to different ports and volumes +on the host. + +## Starting the Server! + +The Docker Desktop tool (or the VSCode Docker plugin) provide a graphical +interface for interacting with Docker images, containers or volumes. +BUT they don't support `docker compose`-- you have to use the command line +for that. You can open a terminal from the Docker Desktop app (look +at the bottom right edge for ">_ Terminal") or in VSCode (at the upper +right of the menu bar, click the middle rectangle where the bottom half +is highlighted) or as a standalone app. + +### From the command line: +- Navigate to the directory for your server type: + ``` + cd ~/minecraft-server/je + ``` +- Start the container! + ``` + docker compose up + ``` + The first time you run this, Docker will download the `itzg/minecraft-server` + image from Dockerhub. Then it will start the container; you will see its + progress in the terminal. It may give you some warnings; look over this + stuff carefully if it fails to start properly. + +### Join the server: + +Start Minecraft. From the launcher, choose either Java or Bedrock Edition +to match your server. + +The first time you connect, you will then need to create an New installation. +Give it a name like "Pycraft-1.20.6" (if that is the version of Minecraft +you are using) and select the version of Minecraft you specified in +the `docker-compose.yml` file. + +Then launch minecraft with this installation. Pick **Multiplayer** mode, +then (the first time), "Add Server". If you are running Minecraft on the +same computer that is hosting the server, you can set the Server Address +to `localhost:25565` where the number is the gameplay port in the +`docker-compose.yml` file. If the server is running on a different +computer, replace `localhost` with the host computer's IP address. + +Once the server has been added, you should be able to click on it to join. +You should disconnect when you are not playing. + +### Stopping the server + +- To stop the server, hit control-C from the command line. This will kick + off anyone who is playing, so maybe warn them! Note that you have to be + in the same terminal session that is showing output from the server. + +- If you started the server in detached (`-d`) mode, make sure you are in + in the same directory as the docker-compose.yml file, then enter: + ``` + docker compose down + ``` + This will both stop the container and remove it. You should do this if + you update the container's configuration. + +- To list what containers you have running, you can type: + ``` + docker ps -a + ``` +- You can see what docker images you have on your computer with: + ``` + docker images + ``` +- If you want to remove an image to free up space, enter: + ``` + docker image rm + ``` + ...where `` can be just the first 5 characters of the + CONTAINER ID shown by the `docker images` command. Note that you have + to stop all the containers that use a given image before it can be deleted. + + + + diff --git a/server_setup/bedrock/docker-compose.yml b/server_setup/bedrock/docker-compose.yml new file mode 100644 index 00000000..e78f7be7 --- /dev/null +++ b/server_setup/bedrock/docker-compose.yml @@ -0,0 +1,49 @@ +# CONFIGURATION FOR PYCRAFT BEDROCK SERVER -- NOT YET WORKING!!! +services: + # you can launch multiple servers by adding more services, with unique names + pycraft-bedrock: + image: itzg/minecraft-bedrock-server + + # this lets you connect to the server using ssh + tty: true + stdin_open: true + + # ports enable communication between the host (left) and container (right) + ports: + # for game play + - "19132:19132/udp" + # for FruitJuice plugin + # - "4711:4711" + # for rcon commands -- note that this must be the same as RCON_PORT below + # - "25575:25575" + + # this sets environment variables in the container + environment: + EULA: "TRUE" + # MEMORY: "4G" + # installs the FruitJuice plugin + # TYPE: "PAPER" + # version of Minecraft-- check PaperMC/FruitJuice plugin for compatibility + VERSION: "LATEST" # "1.21.82.1" # + # gameplay settings + DIFFICULTY: "normal" + GAMEMODE: "creative" # "survival" + SERVER_NAME: "pycraft-bedrock" + ALLOW_CHEATS: "true" + # this gives players admin privileges + # put in the UUID of each player (shown when they join the server) + # OPS: "cecc18fd-0cb5-432a-xxxx-9fdef5e8007a" + + # RCON settings + # ENABLE_RCON: "true" + # RCON_PASSWORD: "aspergers-with-cheese" + # RCON_PORT: "25575" + + volumes: + # attach the relative directory 'data' to the container's /data path + - ./data:/data + # attach a plugins subdirectory to load the FruitJuice plugin + - ./plugins:/plugins + + # get the latest FruitJuice_*.jar and it add to /plugins subdirectory + # https://github.com/jdeast/FruitJuice/tree/master/target \ No newline at end of file diff --git a/server_setup/je/docker-compose.yml b/server_setup/je/docker-compose.yml new file mode 100644 index 00000000..d7c4ca4f --- /dev/null +++ b/server_setup/je/docker-compose.yml @@ -0,0 +1,47 @@ +services: + # you can launch multiple servers by adding more services, with unique names + pycraft: + image: itzg/minecraft-server # java edition + # this lets you connect to the server using ssh + + tty: true + stdin_open: true + + # ports enable communication between the host (left) and container (right) + ports: + # for game play + - "25565:25565" + # for FruitJuice plugin + - "4711:4711" + # for rcon commands -- note that this must be the same as RCON_PORT below + - "25575:25575" + + environment: + EULA: "TRUE" + MEMORY: "4G" + # installs the FruitJuice plugin + TYPE: "PAPER" + # version of Minecraft-- check PaperMC/FruitJuice plugin for compatibility + VERSION: "1.20.6" + # gameplay settings + DIFFICULTY: "normal" + MODE: "creative" # "survival" + SERVER_NAME: "PyCraft" + ALLOW_CHEATS: "true" + # this gives the player admin privileges -- modify with Micraft username + OPS: "AdminPlayerName" + + # RCON settings + ENABLE_RCON: "true" + RCON_PASSWORD: "change-to-custom-password" # change this! + RCON_PORT: "25575" + + volumes: + # attach the subdirectory 'data' to the container's /data path + - ./data:/data + # attach a plugins subdirectory to load the FruitJuice plugin + - ./plugins:/plugins + + # get the latest FruitJuice_*.jar and it add to /plugins subdirectory + # https://github.com/jdeast/FruitJuice/tree/master/target + \ No newline at end of file diff --git a/target/FruitJuice-0.4.0b.jar b/target/FruitJuice-0.4.0b.jar new file mode 100644 index 0000000000000000000000000000000000000000..977936de04f3e17f498c312b55906d5c78067ef5 GIT binary patch literal 34165 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 literal 0 HcmV?d00001 From d29f911137cf58a66747cc1145adb8d20677d4da Mon Sep 17 00:00:00 2001 From: Michael Rowe Date: Fri, 30 May 2025 16:37:47 -0700 Subject: [PATCH 7/7] Remove bedrock/je from server_setup; update docs and docker-compose.yml for bedrock --- server_setup/.env | 11 + server_setup/README_setup.md | 305 ++++++++++++++++-------- server_setup/bedrock/docker-compose.yml | 49 ---- server_setup/docker-compose.yml | 84 +++++++ server_setup/je/docker-compose.yml | 47 ---- 5 files changed, 306 insertions(+), 190 deletions(-) create mode 100644 server_setup/.env delete mode 100644 server_setup/bedrock/docker-compose.yml create mode 100644 server_setup/docker-compose.yml delete mode 100644 server_setup/je/docker-compose.yml diff --git a/server_setup/.env b/server_setup/.env new file mode 100644 index 00000000..f8783055 --- /dev/null +++ b/server_setup/.env @@ -0,0 +1,11 @@ +# change the "values" of the ENVIRONMENT_VARIABLES below +# lines that start with a # are comments and will be ignored + +# note that env vars defined in the shell will override these! + +# admin players (can run *all* commands): commas-separated like this: +# OPS_PLAYERS="admin_player_name,another_admin_name" +OPS_PLAYERS="" + +# password for remote console (RCON) access; YOU SHOULD CHANGE THIS! +RCON_PASSWORD="pick-a-better-password" \ No newline at end of file diff --git a/server_setup/README_setup.md b/server_setup/README_setup.md index 69372667..a609c9f6 100644 --- a/server_setup/README_setup.md +++ b/server_setup/README_setup.md @@ -5,6 +5,7 @@ How to set up a Python- and Scratch-enabled, Java+Bedrock server using Docker that allows remote connections. You can interact with the Minecraft world hosted on this server using the [PynCraft python package](https://github.com/jdeast/pyncraft/tree/main). +Details on setting up python and `PynCraft` are provided at that link. ## Overview @@ -25,7 +26,7 @@ unchanged. The recommended procedure here is: -1) Install the software you need-- Minecraft, Docker, Python, VSCode, git, Scratch +1) Install the software you need-- Minecraft and Docker 2) Configure the directory you will work in. 3) Copy and customize the `docker-compose.yml` file that specifies the docker configuration. @@ -34,10 +35,17 @@ The recommended procedure here is: Once the minecraft server is running, multiple players can connect to it. This guide assumes you will be setting up the server on a personal computer that sits behind a firewall in your home or school network. To connect to -the server from within that network, you can use `localhost` if you are -connecting from the same machine that is running docker (the host); otherwise, +the server from that network, you can use `localhost` if you are connecting +from the same machine that is running docker (the host); otherwise, you will need to know the IP address of the host. +Caveat emptor: if you expose ports in your local network to let others +connect to your server from the internet, you expose yourself to being hacked! +It is probably best to set up a DMZ within your network to isolate the server +from your other computers/devices, or better yet, use cloud hosting. If you +have no idea what I am talking about, you are probably better off keeping access +to within your VPN. + ## The Dreaded Command Line All of what is described here can be done without using the command line-- @@ -45,18 +53,18 @@ the terminal app. But once you get used to it, the terminal is probably easier, and you should really learn to use the terminal if you want to be a programmer. -_(work in progress)_ - Windows now includes WSL, the "Windows Subsystem for Linux". This allows you to -install a Linux distribution (like Ubuntu, Debian, Fedora or Mint) with a -shell (like, terminal app) that also runs on Mac OS with only minor differences. -This gives you super powers on all three major operating systems. +install a Linux distribution (like Ubuntu, Debian, Fedora or Mint) with +`bash` or another shell (like, terminal app). If you learn `bash`, it gives you +super powers on all three major operating systems (Windows, Linux, MacOS)! The only part of this that **has** to be run from the command line (I think) is the `docker compose` command for starting and stoppin the docker container. You can accomplish the same thing from within the Desktop app (I think), but docker compose makes it super easy. +_(work in progress -- not sure how much detail to go into)_ + ## Software Installs ### Minecraft: @@ -70,121 +78,169 @@ the "Minecraft: Java & Bedrock" game license. Choose a player name. ### Docker Docker desktop can be downloaded for free by individuals and schools -from https://docs.docker.com/get-started/get-docker/. Choose the appropriate version for your operating system. - -### Python - -You don't actuall need python to run the server, but you do if you want to -use it. Go to: - -https://www.python.org/downloads/ - -### Git - -This is used to download open source code from the GitHub and other -repositories. Not strictly needed to run the server. - -### Visual Studio Code (VSCode) +from https://docs.docker.com/get-started/get-docker/. Choose the appropriate +version for your operating system. Note that **you must have Docker running +when you launch the minecraft server.** -VSCode is a free Integrated Development Environment (IDE) application that -may be used for developing code. It has a lot of nice features, like -an editor that colors your python code and checks for errors, a debugger, -an integrated terminal, a Docker plugin and tools for connecting to Docker containers so you can write code for them while they're running. It even -has AI built in to help you write code! It's free-- give it a try! - -## Configuration - -#### NOTE: BEDROCK EDITION IS NOT YET WORKING! +## Server Directory Configuration You will need to set up the directory where you will run your server from. It's easiest (in terms of less typing) if you use your home directory as the root. I would suggest something like this: ``` -~/minecraft-server - /bedrock - /plugins - FruitJuice.jar - /data - docker-compose.yml - /je - /plugins - FruitJuice.jar - /data - docker-compose.yml +~/minecraft + /server + /plugins + FruitJuice.jar + ViaVersion.jar + /data + docker-compose.yml + .env /code venv (the python files you write) ``` -Here `~` is your home directory. The `bedrock` and `je` subdirectories will -host bedrock and java-edition versions of your server. If you're only -running one or the other, that's fine-- just pick that one. But naming them -this way gives you the option to run both. **NOTE: you can run a bedrock -_server_ from a MacOS or Linux host, but you can't _play_ from them-- the -bedrock client only runs under Windows.** +All your server stuff will go in the the (you guessed it) server subdirectory. +It holds: + +- The `/plugins` subdirectory: + + This is used to add plugins, as `*.jar` files, to the server. + + - **FruitJuice.jar** contains the compiled source code of this package, + which enables your server to provide an API you can use with Python. + + Download the FruitJuice plugin from the FruitJuice git repo: + https://github.com/jdeast/FruitJuice/tree/master/target + + Click on the `FruitJuice-0.x.0.jar` file that is the latest version, + choose "download raw", then copy it to the `plugins` directory(s) shown above. + + - The **ViaVersion.jar** plugin lets users connect to your server when + they are running newer versions of Minecraft, so you don't always have + to update your server to the latest. This is helpful if, for instance, + PaperMC is lagging behind the current version. -In the `bedrock` and/or `je` subdirectories, create additional subdirectories -`plugins` and `data`. The contents of these will be shared with the -docker container. `plugins` will hold the `FruitJuice.jar` file that -adds FruitJuice to the server. `data` will be mapped to the directory -on the server that contains all the data for the minecraft world that -you are hosting. + Download it from https://hangar.papermc.io/ViaVersion/ViaVersion. -Download the FruitJuice plugin from here: +- The `data` subdirectory: -https://github.com/jdeast/FruitJuice/tree/master/target + This will be "mapped" to the directory on the server that contains all + the data for the minecraft world that you are hosting. That way you can + get things like snapshots. The contents will be created when you create + the server. -Click on the `FruitJuice-0.x.0.jar` file that is the latest version, -choose "download raw", then copy it to the `plugins` directory(s) shown above. + If at some point you want to delete this world (for example, if you wanted + to change to a different seed), delete this directory and then + recreate it witout contents. -Get the `docker-compose.yml` file(s) from here: + You can give this a different name for each world if you want to maintain + more than one. If you do, you have to change the mapping in the + `docker-compose.yml` file (see Configuring the Server below). -https://github.com/jdeast/FruitJuice/tree/master/server_setup +- The `docker-compose.yml` file specifies your server configuration. + + Download it from here and add it to `minecraft/server/` as shown: + https://github.com/jdeast/FruitJuice/tree/master/server_setup + +- The `.env` file defines environment variables used by docker compose. + + These are variables that will be custom to your server. Copy this file + from the same directory that `docker-compose.yml` was in, and modify. + See below for explanation of values. -Download the file and add it to the appropriate server version(s). ## Configuring the Server -The `docker-compose.yml` file specifies tells docker what "image" to -use and what resources and parameters to give it. The image contains -the specification for the container, the operating system and all the -software it needs to host minecraft. We will use an image that is -maintained by the **itzg group**. This Java Edition image has been -downloaded more than 100 million times! The present document is only -meant to get you started. For more details on how you can customize -your server, refer to their [documentation](https://hub.docker.com/r/itzg/minecraft-server). - -**You need to customize the `docker-compose.yml` before you use it.** -Comments start with a `#`; the file is annotated to let you know what each -line means. You should change: - -- `OPS`: replace `AdminPlayerName` with your player name. This will give - you admin privileges, which are needed for many commands. -- `RCON_PASSWORD`: The [RCON package](https://github.com/conqp/rcon) provides - another way to send commands to minecraft independent of FruitJuice. - Setting a custom password helps secure your server, though it is not a - big deal if you are running the server behind a firewall. - - You may also want to change some of the other settings that control - game play. +The `docker-compose.yml` file specifies tells docker what Docker "image" to +use and what resources and parameters to give it when you run it. The image +contains the specification for the container: the operating system and all the +software it needs to host minecraft. We will use an image that is maintained +by the **itzg group**. This Docker image has been downloaded more than +100 million times! + +The present document is only meant to get you started. For more details on +how you can customize your server, refer to their +[documentation](https://hub.docker.com/r/itzg/minecraft-server). + +### Environment Variables + +You will need to set two environment variables, `OPS_PLAYERS` and +`RCON_PASSWORD` that are expected by docker compose. You can set these +by modifying the `.env` file described above, or you can set them from +the command line. The latter will override the values in the file. + +From the **MacOS or Linux** command line, export them as shown below +("> " is the prompt; don't include it): +``` +# this first command gives admin powers to the players (one is fine) +> export OPS_PLAYERS="my_players_name,another_admin_player" + +# change this to make your server more secure when running RCON +> export RCON_PASSWORD="a_secure_password" +``` +If you don't want to do this manually every time you start your server, +you can add it to your startup script (usually `~/.bashrc` or `~/.zshrc`). + +On **Windows**, right-click the Windows icon on the taskbar and select "System", +then click "Advanced system settings". In the System Properties window, click +the "Environment Variables" button. Then choose "System variable" and +enter "OPS_PLAYERS" and "RCON_PASSWORD" along with your values. + +### Mapped Volumes - The `volumes` section tells what local directory on your computer will - hold the data for your world. If you wanted to have a second world, - you could do something like this: - ``` - volumes: - # attach the subdirectory 'data' to the container's /data path - - ./data_world2:/data - ``` +The `volumes` section tells what local directory on your computer will +hold the data for your world. If you wanted to have a second world, +you could do something like this: +``` + volumes: + # attach the subdirectory 'data' to the container's /data path + - ./data-world2:/data +``` +Leave the `plugins:plugins` mapping alone-- it is how the plugins get loaded. + +### Bedrock Edition Compatibility + +Two additional plugins will be installed automatically when the server is +started" `geyser-spiget.jar` and `floodgate-spiget.jar` These will let +players connect to your server using the **bedrock** edition of Minecraft. + +**Note that even though they are connecting from bedrock, the commands they +run are based on java-edition.** + +Bedrock players will connect through the bedrock port. + +If you don't want to enable bedrock, you can comment out these lines. + +### Remote Console (RCON) Support + +RCON is a protocol that lets you issue Minecraft commands to the server. +If you are using the `pyncraft` package, it exposes this functionality +through the `Minecraft.runCommands()` method. If you don't want to use +this, you can comment out the lines related to RCON. + +### Other Settings + +You may want to customize the `docker-compose.yml` before you use it, +especially the ones related to game play. Comments start with a `#`; the +file is annotated to let you know what each line means. + +### Many Worlds + Note that each container can only host one world at a time (I think). But you can run multiple containers-- just add another service. Most of the settings would be the same, but you would have to change the port -mapping. For both ports and volumes, the settings are formatted like +mapping on the host side, and give your players the new port numbers. + +For both ports and volumes, the settings are formatted like `host:server`. The server value should stay the same, but you would change the host value so each server is connected to different ports and volumes on the host. +You must also use a different volume (like `/new-world-data:data`) + ## Starting the Server! The Docker Desktop tool (or the VSCode Docker plugin) provide a graphical @@ -259,6 +315,67 @@ You should disconnect when you are not playing. CONTAINER ID shown by the `docker images` command. Note that you have to stop all the containers that use a given image before it can be deleted. - + ## Scratch + + ### Server Configuration + + This is not set up yet. But I think the way to do this is to create + a `Dockerfile` that looks something like this: + +``` +FROM itzg/minecraft-server + +RUN apt-get install certbot websockify + +ENV MINECRAFT_SERVER="my-minecraft-server-name" + +RUN certbot certonly --standalone -d ${MINECRAFT_SERVER}.duckdns.org \ + && chmod -R 755 /etc/letsencrypt/ \ + && certbot renew + +RUN websockify \ + --cert=/etc/letsencrypt/live/${MINECRAFT_SERVER}.duckdns.org/fullchain.pem \ + --key=/etc/letsencrypt/live/${MINECRAFT_SERVER}.duckdns.org/privkey.pem \ + 14711 localhost:4711 & # must be running for scratch + +EXPOSE 4711 + +# need to set this up to run the service automatically too... + ``` + +### Client Configuration + +1. In a java-enabled web browser (some browsers on tablets do not allow this), +navigate to [this URL](https://jdeast.github.io/FruitJuice/?load_plugin=scratch.js) + + This loads a standard scratch interface, but with an additional "Minecraft" + set of command blocks that allow you to connect to your server, chat, set + blocks, spawn entities, etc. + +2. Load FruitJuice/scratch/examples/rainbowtower.sb + +3. Edit the address in the connect block to match your server's domain + (MY_MINECRAFT_SERVER.duckdns.org). + +4. Click the green flag and watch it build a rainbow tower. + +NOTE: Debugging is hard as many failures are silent. Your browser's developer +tools may help. + +# Credits + +A huge thank you to Geoff Bourne and the other contributors to the +`itzg/docker-minecraft-server` Docker image. Also many thanks to the developers +of all the plugins (Paper, RaspberryJuice, Geyser, Floodgate, ViaVersion) used! + +These directions were a combination of the directions here (java+bedrock) +https://jamesachambers.com/minecraft-java-bedrock-server-together-geyser-floodgate/ + +And here: (python) +https://jeremypedersen.com/posts/2022-03-28-mcpi-macos/ + +And here: (scratch) +https://www.instructables.com/Coding-in-Minecraft-With-Scratch/ +Along with useful discussions with James A. Chambers (https://jamesachambers.com/) diff --git a/server_setup/bedrock/docker-compose.yml b/server_setup/bedrock/docker-compose.yml deleted file mode 100644 index e78f7be7..00000000 --- a/server_setup/bedrock/docker-compose.yml +++ /dev/null @@ -1,49 +0,0 @@ -# CONFIGURATION FOR PYCRAFT BEDROCK SERVER -- NOT YET WORKING!!! -services: - # you can launch multiple servers by adding more services, with unique names - pycraft-bedrock: - image: itzg/minecraft-bedrock-server - - # this lets you connect to the server using ssh - tty: true - stdin_open: true - - # ports enable communication between the host (left) and container (right) - ports: - # for game play - - "19132:19132/udp" - # for FruitJuice plugin - # - "4711:4711" - # for rcon commands -- note that this must be the same as RCON_PORT below - # - "25575:25575" - - # this sets environment variables in the container - environment: - EULA: "TRUE" - # MEMORY: "4G" - # installs the FruitJuice plugin - # TYPE: "PAPER" - # version of Minecraft-- check PaperMC/FruitJuice plugin for compatibility - VERSION: "LATEST" # "1.21.82.1" # - # gameplay settings - DIFFICULTY: "normal" - GAMEMODE: "creative" # "survival" - SERVER_NAME: "pycraft-bedrock" - ALLOW_CHEATS: "true" - # this gives players admin privileges - # put in the UUID of each player (shown when they join the server) - # OPS: "cecc18fd-0cb5-432a-xxxx-9fdef5e8007a" - - # RCON settings - # ENABLE_RCON: "true" - # RCON_PASSWORD: "aspergers-with-cheese" - # RCON_PORT: "25575" - - volumes: - # attach the relative directory 'data' to the container's /data path - - ./data:/data - # attach a plugins subdirectory to load the FruitJuice plugin - - ./plugins:/plugins - - # get the latest FruitJuice_*.jar and it add to /plugins subdirectory - # https://github.com/jdeast/FruitJuice/tree/master/target \ No newline at end of file diff --git a/server_setup/docker-compose.yml b/server_setup/docker-compose.yml new file mode 100644 index 00000000..5ccb7e4f --- /dev/null +++ b/server_setup/docker-compose.yml @@ -0,0 +1,84 @@ +# Micraft Server with FruitJuice plugin +# This file is used to set up a Minecraft server using Docker Compose. +# The FruitJuice plugin lets you connect to the server from python using +# the `pyncraft` python library. + +# It expects two environment variables to be set: +# - OPS_PLAYERS: a comma-separated list of players to assign admin privileges +# - RCON_PASSWORD: the password for RCON commands +# These may be defined in a .env file in the same directory as this file, or set +# in your environment before running the docker-compose command. + +# It also expects a directory named 'data' to exist in the same directory as +# this file, and a directory named 'plugins' to exist, which will be used to +# store the FruitJuice plugin and any other plugins you want to use. + +# ViaVersion lets players connect to the server from newer versions of Minecraft. +# Get the latest ViaVersion.jar and it add to /plugins subdirectory +# https://hangar.papermc.io/ViaVersion/ViaVersion + +# Get the latest FruitJuice_*.jar and it add to /plugins subdirectory +# https://github.com/jdeast/FruitJuice/tree/master/target + +services: + # you can launch multiple servers by adding more services, with unique names + pyncraft: + image: itzg/minecraft-server # java edition + + # this lets you connect to the server using ssh + tty: true + stdin_open: true + + # ports enable communication between the host and container + # format is host_port:container_port; container_port should not change + ports: + # for game play + - "25565:25565" + # for FruitJuice plugin + - "4711:4711" + # for rcon commands -- note that this must be the same as RCON_PORT below + - "25575:25575" + # for bedrock edition via GeyserMC + - "19132:19132/udp" # uncomment to enable Bedrock Edition support + # scratch port + - 14711:14711 + + environment: + # this must be set to "TRUE" to accept the EULA + # see https://www.minecraft.net/en-us/eula + EULA: "TRUE" + # adjust this based on your host system's resources + MEMORY: "8G" + # installs the FruitJuice, Geyser, Floodgate and ViaVersion plugins + TYPE: "PAPER" + # version of Minecraft-- check PaperMC plugin for compatibility + VERSION: "1.20.6" + + # gameplay settings + DIFFICULTY: "normal" + MODE: "creative" # "survival" + SERVER_NAME: "PynCraft" + ALLOW_CHEATS: "true" + SEED: "9064150133272194" + # assign admin privileges to particular players (comma-separated) + OPS: "${OPS_PLAYERS}" + + # automatically download and install the Geyser and Floodgate plugins + # these enables players to join the server using Bedrock Edition + PLUGINS: | + https://download.geysermc.org/v2/projects/geyser/versions/latest/builds/latest/downloads/spigot + https://download.geysermc.org/v2/projects/floodgate/versions/latest/builds/latest/downloads/spigot + + # RCON settings - these allow you to send commands to the server + ENABLE_RCON: "true" + RCON_PASSWORD: "${RCON_PASSWORD}" + RCON_PORT: "25575" + + volumes: + # attach the subdirectory 'data' to the container's /data path + # format is host_path:container_path (container path should not change) + - ./data:/data + # attach a plugins subdirectory to load the FruitJuice plugin + - ./plugins:/plugins + + \ No newline at end of file diff --git a/server_setup/je/docker-compose.yml b/server_setup/je/docker-compose.yml deleted file mode 100644 index d7c4ca4f..00000000 --- a/server_setup/je/docker-compose.yml +++ /dev/null @@ -1,47 +0,0 @@ -services: - # you can launch multiple servers by adding more services, with unique names - pycraft: - image: itzg/minecraft-server # java edition - # this lets you connect to the server using ssh - - tty: true - stdin_open: true - - # ports enable communication between the host (left) and container (right) - ports: - # for game play - - "25565:25565" - # for FruitJuice plugin - - "4711:4711" - # for rcon commands -- note that this must be the same as RCON_PORT below - - "25575:25575" - - environment: - EULA: "TRUE" - MEMORY: "4G" - # installs the FruitJuice plugin - TYPE: "PAPER" - # version of Minecraft-- check PaperMC/FruitJuice plugin for compatibility - VERSION: "1.20.6" - # gameplay settings - DIFFICULTY: "normal" - MODE: "creative" # "survival" - SERVER_NAME: "PyCraft" - ALLOW_CHEATS: "true" - # this gives the player admin privileges -- modify with Micraft username - OPS: "AdminPlayerName" - - # RCON settings - ENABLE_RCON: "true" - RCON_PASSWORD: "change-to-custom-password" # change this! - RCON_PORT: "25575" - - volumes: - # attach the subdirectory 'data' to the container's /data path - - ./data:/data - # attach a plugins subdirectory to load the FruitJuice plugin - - ./plugins:/plugins - - # get the latest FruitJuice_*.jar and it add to /plugins subdirectory - # https://github.com/jdeast/FruitJuice/tree/master/target - \ No newline at end of file