diff --git a/.gitignore b/.gitignore
index b85d271..047fe92 100644
--- a/.gitignore
+++ b/.gitignore
@@ -68,4 +68,5 @@ hs_err_pid*
 replay_pid*
 
 # End of https://www.toptal.com/developers/gitignore/api/java
-local.gradle
\ No newline at end of file
+local.gradle
+config.yml
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 14746e7..2a65317 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
+  <component name="GradleMigrationSettings" migrationVersion="1" />
   <component name="GradleSettings">
     <option name="linkedExternalProjectsSettings">
       <GradleProjectSettings>
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..95c6c66
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,7 @@
+<component name="InspectionProjectProfileManager">
+  <profile version="1.0">
+    <option name="myName" value="Project Default" />
+    <inspection_tool class="UnqualifiedFieldAccess" enabled="true" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="UnqualifiedMethodAccess" enabled="true" level="WARNING" enabled_by_default="true" />
+  </profile>
+</component>
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index d57aaaf..c161809 100644
--- a/build.gradle
+++ b/build.gradle
@@ -23,6 +23,8 @@ dependencies {
     implementation 'net.minestom:minestom-snapshots:fd51c8d17a'
     implementation 'io.github.TogAr2:MinestomPvP:PR62-SNAPSHOT'
     implementation 'com.google.code.gson:gson:2.10.1'
+    implementation 'org.spongepowered:configurate-yaml:4.1.2'
+    implementation 'com.google.guava:guava:32.0.1-android'
 }
 
 java {
@@ -31,24 +33,25 @@ java {
     }
 }
 
-tasks {
-    jar {
-        manifest {
-            attributes 'Main-Class': 'eu.mhsl.minenet.minigames.Main'
-            attributes 'Multi-Release': true
-        }
-        duplicatesStrategy = 'exclude'
-        from configurations.compileClasspath.collect { it.isDirectory() ? it : zipTree(it) }
-    }
-    build {
-        dependsOn(shadowJar)
-    }
-    shadowJar {
-        mergeServiceFiles()
-        archiveClassifier.set("")
+shadowJar {
+    archiveClassifier.set("") // Ohne "-all" im Namen
+    mergeServiceFiles()
+    manifest {
+        attributes(
+            'Main-Class': 'eu.mhsl.craftattack.teamLobby.Main',
+            'Multi-Release': 'true'
+        )
     }
 }
 
+jar {
+    enabled = false
+}
+
+build {
+    dependsOn(shadowJar)
+}
+
 if (file("local.gradle").exists()) {
     apply from: "local.gradle"
 }
diff --git a/src/main/java/de/mhsl/craftattack/Main.java b/src/main/java/de/mhsl/craftattack/Main.java
deleted file mode 100644
index 41b7b4c..0000000
--- a/src/main/java/de/mhsl/craftattack/Main.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package de.mhsl.craftattack;
-
-public class Main {
-    public static void main(String[] args) {
-        System.out.println("Hello, World!");
-    }
-}
\ No newline at end of file
diff --git a/src/main/java/eu/mhsl/craftattack/teamLobby/Authentication.java b/src/main/java/eu/mhsl/craftattack/teamLobby/Authentication.java
new file mode 100644
index 0000000..efed536
--- /dev/null
+++ b/src/main/java/eu/mhsl/craftattack/teamLobby/Authentication.java
@@ -0,0 +1,35 @@
+package eu.mhsl.craftattack.teamLobby;
+
+import net.minestom.server.extras.MojangAuth;
+import net.minestom.server.extras.bungee.BungeeCordProxy;
+import net.minestom.server.extras.velocity.VelocityProxy;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Objects;
+
+public class Authentication {
+    public enum Method {
+        NONE,
+        VANILLA,
+        BUNGEECORD,
+        VELOCITY
+    }
+
+    public record Values(String method, @Nullable String secret) {
+    }
+
+    public static void init(Values values) {
+        Method method = Method.valueOf(values.method);
+
+        switch(method) {
+            case VANILLA -> MojangAuth.init();
+            case BUNGEECORD -> BungeeCordProxy.enable();
+            case VELOCITY -> {
+                Objects.requireNonNull(values.secret, "Velocity proxy needs an secret");
+                VelocityProxy.enable(values.secret);
+            }
+        }
+
+        System.out.printf("Server authentication requirement is set to '%s'!%n", method);
+    }
+}
diff --git a/src/main/java/eu/mhsl/craftattack/teamLobby/Lobby.java b/src/main/java/eu/mhsl/craftattack/teamLobby/Lobby.java
new file mode 100644
index 0000000..cd6b312
--- /dev/null
+++ b/src/main/java/eu/mhsl/craftattack/teamLobby/Lobby.java
@@ -0,0 +1,150 @@
+package eu.mhsl.craftattack.teamLobby;
+
+import eu.mhsl.craftattack.teamLobby.data.Team;
+import eu.mhsl.craftattack.teamLobby.util.CommonHandler;
+import eu.mhsl.craftattack.teamLobby.util.PluginMessageUtil;
+import eu.mhsl.craftattack.teamLobby.util.PosSerializer;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.NamedTextColor;
+import net.kyori.adventure.util.Ticks;
+import net.minestom.server.MinecraftServer;
+import net.minestom.server.coordinate.Pos;
+import net.minestom.server.coordinate.Vec;
+import net.minestom.server.entity.Entity;
+import net.minestom.server.entity.Player;
+import net.minestom.server.event.instance.AddEntityToInstanceEvent;
+import net.minestom.server.event.instance.RemoveEntityFromInstanceEvent;
+import net.minestom.server.event.player.PlayerBlockBreakEvent;
+import net.minestom.server.event.player.PlayerBlockInteractEvent;
+import net.minestom.server.instance.InstanceContainer;
+import net.minestom.server.instance.block.Block;
+import net.minestom.server.network.packet.server.play.ParticlePacket;
+import net.minestom.server.particle.Particle;
+import net.minestom.server.potion.Potion;
+import net.minestom.server.potion.PotionEffect;
+import net.minestom.server.registry.DynamicRegistry;
+import net.minestom.server.timer.TaskSchedule;
+import net.minestom.server.utils.NamespaceID;
+import net.minestom.server.world.DimensionType;
+import org.spongepowered.configurate.ConfigurationNode;
+
+import java.util.UUID;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+public class Lobby extends InstanceContainer {
+    private static final DynamicRegistry.Key<DimensionType> dimension = MinecraftServer.getDimensionTypeRegistry()
+        .register(
+            NamespaceID.from("mhsl:team_lobby"),
+            DimensionType.builder()
+                .ambientLight(2.0f)
+                .build()
+        );
+
+    private final ConfigurationNode lobbyConfig = Main.getConfig().node("lobby");
+    private final Pos buttonLocation = PosSerializer.deserialize(this.lobbyConfig.node("buttonLocation").getString());
+    private final String targetServer = this.lobbyConfig.node("connectsTo").getString();
+    public final Pos spawnPoint = PosSerializer.deserialize(this.lobbyConfig.node("spawnpoint").getString());
+
+    private final Team team;
+    private boolean isComplete;
+    private boolean isJoining;
+
+    public final ParticlePacket availableParticles = new ParticlePacket(
+        Particle.COMPOSTER,
+        this.buttonLocation.add(0.5),
+        new Vec(0.5, 0.5, 0.5),
+        0.001f,
+        3
+    );
+
+    public final ParticlePacket progressParticles = new ParticlePacket(
+        Particle.TOTEM_OF_UNDYING,
+        this.buttonLocation.add(0.5),
+        new Vec(0.5, 0.5, 0.5),
+        0.1f,
+        5
+    );
+
+    public Lobby(Team team) {
+        super(UUID.randomUUID(), dimension);
+        MinecraftServer.getInstanceManager().registerInstance(this);
+        this.team = team;
+
+        //noinspection UnstableApiUsage
+        this.eventNode()
+            .addListener(PlayerBlockBreakEvent.class, CommonHandler::cancel)
+            .addListener(AddEntityToInstanceEvent.class, this::onPlayerChange)
+            .addListener(RemoveEntityFromInstanceEvent.class, this::onPlayerChange)
+            .addListener(PlayerBlockInteractEvent.class, this::onBlockInteract);
+
+        MinecraftServer.getSchedulerManager().scheduleTask(
+            this::particleTick,
+            TaskSchedule.seconds(3),
+            TaskSchedule.millis(100)
+        );
+
+        this.setGenerator(unit -> unit.modifier().fillHeight(0, 10, Block.BAMBOO_BLOCK));
+
+        this.setBlock(this.spawnPoint.sub(0, 1 ,0), Block.DIAMOND_BLOCK);
+    }
+
+    private <TEvent> void onPlayerChange(TEvent event) {
+        MinecraftServer.getSchedulerManager().scheduleNextTick(() -> {
+            this.isJoining = false;
+            this.isComplete = this.getPlayers().stream()
+                .map(Entity::getUuid)
+                .collect(Collectors.toSet())
+                .containsAll(this.team.players());
+
+            this.update();
+        });
+    }
+
+    private synchronized void onBlockInteract(PlayerBlockInteractEvent event) {
+        if(!event.getBlockPosition().sameBlock(this.buttonLocation)) return;
+        if(this.isJoining) return;
+        if(!this.isComplete) {
+            this.everyMember(p -> p.sendActionBar(Component.text("Dein Team ist nicht vollständig!", NamedTextColor.RED)));
+            return;
+        }
+        this.isJoining = true;
+        this.connect();
+        this.update();
+    }
+
+    private void update() {
+        Block button = (this.isComplete ? Block.WARPED_BUTTON : Block.CRIMSON_BUTTON)
+            .withProperty("face", "floor")
+            .withProperty("powered", this.isJoining ? "true" : "false");
+
+        this.setBlock(this.buttonLocation, button);
+    }
+
+    private void connect() {
+        if(!this.isJoining) return;
+        this.everyMember(p -> {
+            p.addEffect(new Potion(PotionEffect.DARKNESS, 0, 5 * Ticks.TICKS_PER_SECOND));
+            p.sendActionBar(Component.text("Verbinde...", NamedTextColor.GREEN));
+            PluginMessageUtil.connect(p, this.targetServer);
+        });
+        MinecraftServer.getSchedulerManager().scheduleTask(
+            () -> {
+                this.isJoining = false;
+                this.update();
+            },
+            TaskSchedule.seconds(10),
+            TaskSchedule.stop()
+        );
+    }
+
+    private void particleTick() {
+        if(!this.isComplete) return;
+
+        this.everyMember(p -> p.sendPacket(this.isJoining ? this.progressParticles : this.availableParticles));
+    }
+
+    private void everyMember(Consumer<Player> consumer) {
+        this.getPlayers().forEach(consumer);
+    }
+}
diff --git a/src/main/java/eu/mhsl/craftattack/teamLobby/LobbyManager.java b/src/main/java/eu/mhsl/craftattack/teamLobby/LobbyManager.java
new file mode 100644
index 0000000..2e4ff62
--- /dev/null
+++ b/src/main/java/eu/mhsl/craftattack/teamLobby/LobbyManager.java
@@ -0,0 +1,34 @@
+package eu.mhsl.craftattack.teamLobby;
+
+import eu.mhsl.craftattack.teamLobby.data.Team;
+import net.minestom.server.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.*;
+
+public class LobbyManager {
+    private final Set<Team> teams = new HashSet<>() {
+        {
+            this.add(new Team(UUID.randomUUID(), "Testerr", "#123123", List.of(
+                UUID.fromString("c291290d-cffc-4649-aeec-d6f4417896ea"),
+                UUID.fromString("959ed433-14ea-38fe-918b-75b7d09466af")
+            )));
+        }
+    };
+    private final Map<Team, Lobby> instances = new HashMap<>();
+
+    public synchronized @NotNull Lobby getPlayerInstance(Player player) {
+        UUID playerId = player.getUuid();
+        Team targetTeam = this.teams.stream()
+            .filter(team -> team.players().contains(playerId))
+            .findAny()
+            .orElseThrow(() -> new NoSuchElementException("Player is not in any Team!"));
+
+        if(!this.instances.containsKey(targetTeam)) {
+            Lobby instance = new Lobby(targetTeam);
+            this.instances.put(targetTeam, instance);
+        }
+
+        return this.instances.get(targetTeam);
+    }
+}
diff --git a/src/main/java/eu/mhsl/craftattack/teamLobby/Main.java b/src/main/java/eu/mhsl/craftattack/teamLobby/Main.java
new file mode 100644
index 0000000..572acf5
--- /dev/null
+++ b/src/main/java/eu/mhsl/craftattack/teamLobby/Main.java
@@ -0,0 +1,86 @@
+package eu.mhsl.craftattack.teamLobby;
+
+import net.minestom.server.MinecraftServer;
+import net.minestom.server.entity.Player;
+import net.minestom.server.event.player.AsyncPlayerConfigurationEvent;
+import org.spongepowered.configurate.ConfigurateException;
+import org.spongepowered.configurate.ConfigurationNode;
+import org.spongepowered.configurate.yaml.YamlConfigurationLoader;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetSocketAddress;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
+import java.util.Arrays;
+
+public class Main {
+    private static ConfigurationNode config;
+    private static LobbyManager lobbyManager;
+    public static void main(String[] args) throws ConfigurateException {
+        String filename = "config.yml";
+        File configFile = new File(filename);
+
+        if (!configFile.exists()) {
+            System.out.println("Konfigurationsdatei nicht gefunden. Defaultkonfiguration wird verwendet.");
+
+            try (InputStream in = Main.class.getResourceAsStream("/config.yml")) {
+                if (in == null) {
+                    throw new FileNotFoundException("Beispielkonfiguration 'config.yml' nicht im Ressourcenpfad gefunden.");
+                }
+
+                Files.copy(in, configFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
+            } catch (IOException e) {
+                throw new RuntimeException("Fehler beim Kopieren der Beispielkonfiguration: ", e);
+            }
+        }
+
+        config = YamlConfigurationLoader.builder()
+            .path(configFile.toPath())
+            .build()
+            .load();
+
+        lobbyManager = new LobbyManager();
+
+        ConfigurationNode serverCfg = config.node("server");
+
+        MinecraftServer server = MinecraftServer.init();
+        MinecraftServer.setBrandName("mhsl.eu - TeamLobby");
+        MinecraftServer.setCompressionThreshold(serverCfg.node("compressionThreshold").getInt(128));
+        System.setProperty("minestom.chunk-view-distance", String.valueOf(serverCfg.node("viewDistance").getInt(12)));
+
+        ConfigurationNode authCfg = config.node("authentication");
+        Authentication.Values auth = new Authentication.Values(
+            authCfg.node("method").getString(Authentication.Method.NONE.name()),
+            authCfg.node("secret").getString()
+        );
+        Authentication.init(auth);
+
+        MinecraftServer.getGlobalEventHandler().addListener(
+            AsyncPlayerConfigurationEvent.class,
+            event -> {
+                try {
+                    Player p = event.getPlayer();
+                    System.out.printf("Player %s joined: %s%n", p.getUsername(), p.getUuid());
+                    Lobby lobby = lobbyManager.getPlayerInstance(p);
+                    event.setSpawningInstance(lobby);
+                    p.setRespawnPoint(lobby.spawnPoint);
+                } catch(Exception e) {
+                    event.getPlayer().kick(String.format("Login: %s", e.getMessage()));
+                    System.err.println(Arrays.toString(e.getStackTrace()));
+                }
+            }
+        );
+
+        int port = config.node("server", "port").getInt(25565);
+        server.start(new InetSocketAddress("0.0.0.0", port));
+
+        System.out.println("Server is running!");
+    }
+
+    public static ConfigurationNode getConfig() {
+        return config;
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/eu/mhsl/craftattack/teamLobby/data/Team.java b/src/main/java/eu/mhsl/craftattack/teamLobby/data/Team.java
new file mode 100644
index 0000000..414ed6f
--- /dev/null
+++ b/src/main/java/eu/mhsl/craftattack/teamLobby/data/Team.java
@@ -0,0 +1,12 @@
+package eu.mhsl.craftattack.teamLobby.data;
+
+import java.util.List;
+import java.util.UUID;
+
+public record Team(
+    UUID teamId,
+    String teamName,
+    String hexColor,
+    List<UUID> players
+) {
+}
diff --git a/src/main/java/eu/mhsl/craftattack/teamLobby/util/CommonHandler.java b/src/main/java/eu/mhsl/craftattack/teamLobby/util/CommonHandler.java
new file mode 100644
index 0000000..ed45726
--- /dev/null
+++ b/src/main/java/eu/mhsl/craftattack/teamLobby/util/CommonHandler.java
@@ -0,0 +1,9 @@
+package eu.mhsl.craftattack.teamLobby.util;
+
+import net.minestom.server.event.trait.CancellableEvent;
+
+public class CommonHandler {
+    public static void cancel(CancellableEvent event) {
+        event.setCancelled(true);
+    }
+}
diff --git a/src/main/java/eu/mhsl/craftattack/teamLobby/util/PluginMessageUtil.java b/src/main/java/eu/mhsl/craftattack/teamLobby/util/PluginMessageUtil.java
new file mode 100644
index 0000000..9844c4b
--- /dev/null
+++ b/src/main/java/eu/mhsl/craftattack/teamLobby/util/PluginMessageUtil.java
@@ -0,0 +1,16 @@
+package eu.mhsl.craftattack.teamLobby.util;
+
+import com.google.common.io.ByteArrayDataOutput;
+import com.google.common.io.ByteStreams;
+import net.minestom.server.entity.Player;
+import net.minestom.server.network.packet.server.common.PluginMessagePacket;
+
+public class PluginMessageUtil {
+    private static final String bungeeTargetSelector = "bungeecord:main";
+    public static void connect(Player p, String bungeeServerTargetName) {
+        ByteArrayDataOutput out = ByteStreams.newDataOutput();
+        out.writeUTF("Connect");
+        out.writeUTF(bungeeServerTargetName);
+        p.sendPacket(new PluginMessagePacket(bungeeTargetSelector, out.toByteArray()));
+    }
+}
diff --git a/src/main/java/eu/mhsl/craftattack/teamLobby/util/PosSerializer.java b/src/main/java/eu/mhsl/craftattack/teamLobby/util/PosSerializer.java
new file mode 100644
index 0000000..0d99306
--- /dev/null
+++ b/src/main/java/eu/mhsl/craftattack/teamLobby/util/PosSerializer.java
@@ -0,0 +1,46 @@
+package eu.mhsl.craftattack.teamLobby.util;
+
+import com.google.gson.*;
+import net.minestom.server.coordinate.Pos;
+
+import java.lang.reflect.Type;
+
+public class PosSerializer {
+    private static final Gson gson = new GsonBuilder()
+        .registerTypeAdapter(Pos.class, new PosAdapter())
+        .create();
+
+    public static String serialize(Pos pos) {
+        return gson.toJson(pos);
+    }
+
+    public static Pos deserialize(String json) {
+        return gson.fromJson(json, Pos.class);
+    }
+
+    private static class PosAdapter implements JsonSerializer<Pos>, JsonDeserializer<Pos> {
+
+        @Override
+        public JsonElement serialize(Pos src, Type typeOfSrc, JsonSerializationContext context) {
+            JsonObject obj = new JsonObject();
+            obj.addProperty("x", src.x());
+            obj.addProperty("y", src.y());
+            obj.addProperty("z", src.z());
+            obj.addProperty("yaw", src.yaw());
+            obj.addProperty("pitch", src.pitch());
+            return obj;
+        }
+
+        @Override
+        public Pos deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
+            JsonObject obj = json.getAsJsonObject();
+            double x = obj.get("x").getAsDouble();
+            double y = obj.get("y").getAsDouble();
+            double z = obj.get("z").getAsDouble();
+            float yaw = obj.has("yaw") ? obj.get("yaw").getAsFloat() : 0f;
+            float pitch = obj.has("pitch") ? obj.get("pitch").getAsFloat() : 0f;
+            return new Pos(x, y, z, yaw, pitch);
+        }
+    }
+
+}
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
new file mode 100644
index 0000000..61440b9
--- /dev/null
+++ b/src/main/resources/config.yml
@@ -0,0 +1,13 @@
+server:
+  port: 25565
+  compressionThreshold: 128
+  viewDistance: 8
+
+lobby:
+  spawnpoint: '{"x":0.5, "y":10, "z":0.5, "yaw":0, "pitch":90}'
+  buttonLocation: '{"x":0, "y":10, "z":0}'
+  connectsTo: 'serverName'
+
+authentication:
+  method: 'NONE' # supported values: 'NONE', 'VANILLA', 'BUNGEECORD', 'VELOCITY'
+  secret: '' # only for VELOCITY proxies