Merge branch 'fix-skin-in-room'
This commit is contained in:
35 files changed
+1124
-700
No files matched your search
@@ -8,15 +8,19 @@ import io.ib67.sfcraft.module.chat.ChatPingModule;
|
||||
import io.ib67.sfcraft.module.chat.ChatSendLocModule;
|
||||
import io.ib67.sfcraft.module.compat.ModCompatModule;
|
||||
import io.ib67.sfcraft.module.randomevt.LongNightModule;
|
||||
import io.ib67.sfcraft.module.room.CreativeRoomModule;
|
||||
import io.ib67.sfcraft.registry.RoomRegistry;
|
||||
import io.ib67.sfcraft.registry.chat.SimpleMessageDecorator;
|
||||
import io.ib67.sfcraft.registry.event.SFRandomEventRegistry;
|
||||
import io.ib67.sfcraft.init.GuiceModInitializer;
|
||||
import io.ib67.sfcraft.geoip.GeoIPService;
|
||||
import io.ib67.sfcraft.geoip.MaxMindGeoIPService;
|
||||
import io.ib67.sfcraft.inject.MinecraftServerSupplier;
|
||||
import io.ib67.sfcraft.registry.event.RandomEventRegistry;
|
||||
import io.ib67.sfcraft.registry.RandomEventRegistry;
|
||||
import io.ib67.sfcraft.module.command.BackModule;
|
||||
import io.ib67.sfcraft.module.command.ManagementModule;
|
||||
import io.ib67.sfcraft.registry.room.SimpleRoomRegistry;
|
||||
import io.ib67.sfcraft.room.RoomTeleporter;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
|
||||
@@ -35,6 +39,8 @@ public class SFCraftInitializer extends GuiceModInitializer {
|
||||
binder().bind(GeoIPService.class).to(MaxMindGeoIPService.class);
|
||||
binder().bind(RandomEventRegistry.class).to(SFRandomEventRegistry.class);
|
||||
binder().bind(SimpleMessageDecorator.class).in(Singleton.class);
|
||||
binder().bind(RoomTeleporter.class).in(Singleton.class);
|
||||
binder().bind(RoomRegistry.class).to(SimpleRoomRegistry.class).in(Singleton.class);
|
||||
}
|
||||
|
||||
private void registerFeatures() {
|
||||
@@ -51,6 +57,8 @@ public class SFCraftInitializer extends GuiceModInitializer {
|
||||
registerFeature(ChatSendLocModule.class);
|
||||
registerFeature(LongNightModule.class);
|
||||
registerFeature(ModCompatModule.class);
|
||||
registerFeature(RoomModule.class);
|
||||
registerFeature(CreativeRoomModule.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
package io.ib67.sfcraft.config;
|
||||
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public class SFConfig {
|
||||
public boolean enableOfflineExempt = true;
|
||||
public int maintainceStartHour = 13;
|
||||
public int maintainceEndHour = 17;
|
||||
public boolean disableTemporarily;
|
||||
public String domain = "localhost";
|
||||
public String serverSecret = RandomStringUtils.random(32);
|
||||
|
||||
public boolean isClosed(Clock clock) {
|
||||
if (disableTemporarily) return false;
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package io.ib67.sfcraft.mixin.server.subserver;
|
||||
|
||||
import io.ib67.sfcraft.SFCraft;
|
||||
import io.ib67.sfcraft.module.RoomModule;
|
||||
import io.ib67.sfcraft.registry.RoomRegistry;
|
||||
import io.ib67.sfcraft.util.Helper;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
import net.minecraft.world.TeleportTarget;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Mixin(Entity.class)
|
||||
public abstract class EntityMixin {
|
||||
@Shadow
|
||||
public abstract UUID getUuid();
|
||||
|
||||
@Inject(at = @At("HEAD"), method = "teleportTo", cancellable = true)
|
||||
private void sf$redirectTeleport(TeleportTarget teleportTarget, CallbackInfoReturnable<Entity> cir) {
|
||||
if (RoomModule.isVirtual(this.getUuid())) {
|
||||
cir.setReturnValue((Entity) (Object) this);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(at = @At("HEAD"), method = "canUsePortals", cancellable = true)
|
||||
private void sf$disablePortal(boolean allowVehicles, CallbackInfoReturnable<Boolean> cir) {
|
||||
var registry = SFCraft.getInjector().getInstance(RoomRegistry.class);
|
||||
var $this = (Entity) (Object) this;
|
||||
if (registry.isRoomWorld($this.getWorld().getRegistryKey())) {
|
||||
cir.setReturnValue(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package io.ib67.sfcraft.mixin.server.subserver;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import io.ib67.sfcraft.SFCraft;
|
||||
import io.ib67.sfcraft.module.RoomModule;
|
||||
import net.minecraft.network.packet.s2c.play.PlayerListS2CPacket;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@Mixin(PlayerListS2CPacket.Entry.class)
|
||||
public class PlayerListS2CPacket$EntryMixin {
|
||||
@Unique
|
||||
private static final byte[] lock = new byte[0];
|
||||
@Unique
|
||||
private static volatile RoomModule module;
|
||||
|
||||
@Unique
|
||||
private static RoomModule getModule() {
|
||||
if (module == null) {
|
||||
synchronized (lock) {
|
||||
if (module == null) {
|
||||
module = SFCraft.getInjector().getInstance(RoomModule.class);
|
||||
}
|
||||
}
|
||||
}
|
||||
return module;
|
||||
}
|
||||
|
||||
@Redirect(method = "<init>(Lnet/minecraft/server/network/ServerPlayerEntity;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/network/ServerPlayerEntity;getGameProfile()Lcom/mojang/authlib/GameProfile;"))
|
||||
private static GameProfile sf$redirectGameProfileForRoom(ServerPlayerEntity instance) {
|
||||
if (!RoomModule.isVirtual(instance.getUuid())) {
|
||||
return instance.getGameProfile();
|
||||
}
|
||||
return getModule().devirtualize(instance.getUuid());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package io.ib67.sfcraft.mixin.server.subserver;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import io.ib67.sfcraft.SFCraft;
|
||||
import io.ib67.sfcraft.module.RoomModule;
|
||||
import io.ib67.sfcraft.registry.RoomRegistry;
|
||||
import io.ib67.sfcraft.util.Helper;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.network.ClientConnection;
|
||||
import net.minecraft.server.PlayerManager;
|
||||
import net.minecraft.server.network.ConnectedClientData;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import net.minecraft.server.world.ServerWorld;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.util.math.GlobalPos;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
import net.minecraft.world.TeleportTarget;
|
||||
import org.slf4j.Logger;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
import java.util.Set;
|
||||
|
||||
@Mixin(PlayerManager.class)
|
||||
|
||||
public abstract class PlayerManagerMixin {
|
||||
@Shadow
|
||||
@Final
|
||||
private static Logger LOGGER;
|
||||
|
||||
@Inject(method = "onPlayerConnect", at = @At("TAIL"))
|
||||
public void sf$updateSession(ClientConnection connection, ServerPlayerEntity player, ConnectedClientData clientData, CallbackInfo ci) {
|
||||
var registry = SFCraft.getInjector().getInstance(RoomRegistry.class);
|
||||
var room = registry.getRoomBy(player.getUuid());
|
||||
if (room != null) {
|
||||
var spawn = room.getSpawnPosition();
|
||||
if(player.getWorld().getRegistryKey() != spawn.dimension()){
|
||||
teleportToRoom(player, spawn);
|
||||
}
|
||||
|
||||
LOGGER.info("Room player joined as " + player.getName().getLiteralString() + "/" + player.getUuid());
|
||||
room.onPlayerJoin(player);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "respawnPlayer", at = @At("TAIL"))
|
||||
public void sf$updateSession(ServerPlayerEntity player, boolean alive, Entity.RemovalReason removalReason, CallbackInfoReturnable<ServerPlayerEntity> cir) {
|
||||
var registry = SFCraft.getInjector().getInstance(RoomRegistry.class);
|
||||
var room = registry.getRoomBy(player.getUuid());
|
||||
if (!alive && room != null) {
|
||||
var spawn = room.getSpawnPosition();
|
||||
teleportToRoom(cir.getReturnValue(), spawn);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "checkCanJoin", cancellable = true, at = @At("HEAD"))
|
||||
private void sf$bypassJoinLimit(SocketAddress address, GameProfile profile, CallbackInfoReturnable<Text> cir) {
|
||||
if (RoomModule.isVirtual(profile.getId())) cir.setReturnValue(null);
|
||||
}
|
||||
|
||||
@Unique
|
||||
private void teleportToRoom(ServerPlayerEntity player, GlobalPos spawn) {
|
||||
var world = player.getServer().getWorld(spawn.dimension());
|
||||
var pos = spawn.pos();
|
||||
world.getChunkManager().chunkLoadingManager.getTicketManager().handleChunkEnter(player.getWatchedSection(), player);
|
||||
player.teleport(world, pos.getX(), pos.getY(), pos.getZ(), Set.of(), 0, 0);
|
||||
}
|
||||
|
||||
@Inject(method = "remove", at = @At("HEAD"))
|
||||
public void sf$removePlayers(ServerPlayerEntity instance, CallbackInfo ci) {
|
||||
var registry = SFCraft.getInjector().getInstance(RoomRegistry.class);
|
||||
var room = registry.getRoomBy(instance.getUuid());
|
||||
if (room != null) {
|
||||
room.onPlayerQuit(instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package io.ib67.sfcraft.mixin.server.subserver;
|
||||
|
||||
import com.mojang.datafixers.DataFixer;
|
||||
import io.ib67.sfcraft.module.RoomModule;
|
||||
import io.ib67.sfcraft.util.Helper;
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
import net.minecraft.util.WorldSavePath;
|
||||
import net.minecraft.world.PlayerSaveHandler;
|
||||
import net.minecraft.world.level.storage.LevelStorage;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@Mixin(PlayerSaveHandler.class)
|
||||
public class PlayerSaveHandlerMixin {
|
||||
@Shadow
|
||||
@Final
|
||||
private File playerDataDir;
|
||||
@Unique
|
||||
private File virtualPlayerFolder;
|
||||
|
||||
@Inject(at = @At("TAIL"), method = "<init>")
|
||||
private void sf$createVirtualPlayerDataFolder(LevelStorage.Session session, DataFixer dataFixer, CallbackInfo ci) {
|
||||
this.virtualPlayerFolder = session.getDirectory(WorldSavePath.PLAYERDATA).resolve("in_rooms").toFile();
|
||||
this.virtualPlayerFolder.mkdirs();
|
||||
}
|
||||
|
||||
@Redirect(at = @At(value = "FIELD", target = "Lnet/minecraft/world/PlayerSaveHandler;playerDataDir:Ljava/io/File;"), method = "savePlayerData")
|
||||
private File sf$getPlayerDataFolder(PlayerSaveHandler instance, PlayerEntity entity) {
|
||||
if (RoomModule.isVirtual(entity.getUuid())) {
|
||||
return virtualPlayerFolder;
|
||||
} else {
|
||||
return this.playerDataDir;
|
||||
}
|
||||
}
|
||||
|
||||
@Redirect(at = @At(value = "FIELD", target = "Lnet/minecraft/world/PlayerSaveHandler;playerDataDir:Ljava/io/File;"), method = "loadPlayerData(Lnet/minecraft/entity/player/PlayerEntity;Ljava/lang/String;)Ljava/util/Optional;")
|
||||
private File sf$saveGetPlayerFolder(PlayerSaveHandler instance, PlayerEntity player) {
|
||||
if (RoomModule.isVirtual(player.getUuid())) {
|
||||
return virtualPlayerFolder;
|
||||
} else {
|
||||
return this.playerDataDir;
|
||||
}
|
||||
}
|
||||
}
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
package io.ib67.sfcraft.mixin.server.subserver;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import io.ib67.sfcraft.SFCraft;
|
||||
import io.ib67.sfcraft.module.RoomModule;
|
||||
import io.ib67.sfcraft.registry.RoomRegistry;
|
||||
import io.ib67.sfcraft.room.CookieState;
|
||||
import io.ib67.sfcraft.room.RequestedRoom;
|
||||
import net.minecraft.network.ClientConnection;
|
||||
import net.minecraft.network.packet.c2s.common.CookieResponseC2SPacket;
|
||||
import net.minecraft.network.packet.s2c.common.CookieRequestS2CPacket;
|
||||
import net.minecraft.server.PlayerManager;
|
||||
import net.minecraft.server.network.ServerLoginNetworkHandler;
|
||||
import net.minecraft.text.Text;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.Unique;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
|
||||
import static io.ib67.sfcraft.room.CookieState.*;
|
||||
import static io.ib67.sfcraft.room.RoomTeleporter.ROOM_COOKIE;
|
||||
|
||||
@Mixin(ServerLoginNetworkHandler.class)
|
||||
public abstract class ServerLoginNetworkHandlerMixin {
|
||||
@Shadow
|
||||
abstract void startVerify(GameProfile profile);
|
||||
|
||||
@Shadow
|
||||
private @Nullable GameProfile profile;
|
||||
@Shadow
|
||||
@Final
|
||||
private boolean transferred;
|
||||
@Shadow
|
||||
@Final
|
||||
private ClientConnection connection;
|
||||
|
||||
@Shadow
|
||||
public abstract void disconnect(Text reason);
|
||||
|
||||
@Shadow
|
||||
@Final
|
||||
private static Logger LOGGER;
|
||||
@Unique
|
||||
private CookieState sf$cookieState = CookieState.NOT_SENT;
|
||||
@Unique
|
||||
private RequestedRoom sf$room = null;
|
||||
@Unique
|
||||
private boolean clean;
|
||||
|
||||
@Inject(method = "tick", at = @At("TAIL"))
|
||||
void restoreVerify(CallbackInfo ci) {
|
||||
if ((sf$cookieState == SENT && sf$room != null) || (clean && sf$cookieState != DONE)) {
|
||||
sf$cookieState = CookieState.RECV;
|
||||
startVerify(this.profile);
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "onCookieResponse", at = @At("HEAD"), cancellable = true)
|
||||
private void sf$onRoomId(CookieResponseC2SPacket packet, CallbackInfo ci) {
|
||||
if (packet.key().equals(ROOM_COOKIE)) {
|
||||
if (sf$cookieState != SENT) throw new IllegalStateException("Protocol error");
|
||||
var roomSvc = SFCraft.getInjector().getInstance(RoomModule.class);
|
||||
try {
|
||||
if (packet.payload().length == 0) {
|
||||
// clean reconnect.
|
||||
LOGGER.info("Player " + profile.getName() + " requested a clean reconnection");
|
||||
clean = true;
|
||||
ci.cancel();
|
||||
return;
|
||||
}
|
||||
this.sf$room = roomSvc.readRoomRequest(packet.payload());
|
||||
} catch (Exception t) {
|
||||
LOGGER.error("Failed to read cookie: {0}", t);
|
||||
this.disconnect(Text.of("Protocol error."));
|
||||
ci.cancel();
|
||||
return;
|
||||
}
|
||||
}
|
||||
ci.cancel();
|
||||
}
|
||||
|
||||
@Inject(method = "startVerify", at = @At("HEAD"), cancellable = true)
|
||||
void sf$makeTransferPlayerProfile(GameProfile profile, CallbackInfo ci) {
|
||||
if (transferred && !clean) {
|
||||
switch (this.sf$cookieState) {
|
||||
case NOT_SENT -> {
|
||||
this.profile = profile;
|
||||
this.connection.send(new CookieRequestS2CPacket(ROOM_COOKIE));
|
||||
this.sf$cookieState = SENT;
|
||||
}
|
||||
case RECV -> {
|
||||
var ssr = SFCraft.getInjector().getInstance(RoomRegistry.class);
|
||||
var room = ssr.getRoomBy(sf$room.identifier());
|
||||
if (room != null) {
|
||||
this.profile = new GameProfile(sf$room.profileUuid(), sf$room.profileName());
|
||||
var session = room.getPlayerManager().getSessionBy(sf$room.profileUuid());
|
||||
if (session == null) {
|
||||
disconnect(Text.of("Session isn't exists."));
|
||||
ci.cancel();
|
||||
return;
|
||||
}
|
||||
session.onPlayerLogin(sf$room.profileUuid());
|
||||
this.sf$cookieState = DONE;
|
||||
this.startVerify(this.profile);
|
||||
ci.cancel();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.sf$cookieState != CookieState.DONE) {
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
if (clean) {
|
||||
this.sf$cookieState = DONE;
|
||||
}
|
||||
}
|
||||
|
||||
@Redirect(method = "tickVerify", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/PlayerManager;checkCanJoin(Ljava/net/SocketAddress;Lcom/mojang/authlib/GameProfile;)Lnet/minecraft/text/Text;"))
|
||||
private Text sf$bypassRoomPlayer(PlayerManager instance, SocketAddress address, GameProfile profile) {
|
||||
if (sf$cookieState == DONE) return null;
|
||||
return instance.checkCanJoin(address, profile);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package io.ib67.sfcraft.mixin.server.subserver;
|
||||
|
||||
import io.ib67.sfcraft.SFCraft;
|
||||
import io.ib67.sfcraft.registry.RoomRegistry;
|
||||
import net.minecraft.network.packet.s2c.play.PositionFlag;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import net.minecraft.server.world.ServerWorld;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
@Mixin(ServerPlayerEntity.class)
|
||||
public abstract class ServerPlayerEntityMixin {
|
||||
@Inject(at = @At("HEAD"), method = "teleport(Lnet/minecraft/server/world/ServerWorld;DDDLjava/util/Set;FF)Z", cancellable = true)
|
||||
public void sf$interceptTeleport(ServerWorld world, double destX, double destY, double destZ, Set<PositionFlag> flags, float yaw, float pitch, CallbackInfoReturnable<Boolean> cir) {
|
||||
var room = roomRegistry().getRoomBy($this().getUuid());
|
||||
if (room != null) {
|
||||
if (world.getRegistryKey() != room.getSpawnPosition().dimension()) {
|
||||
cir.setReturnValue(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(at = @At("HEAD"), method = "teleport(Lnet/minecraft/server/world/ServerWorld;DDDFF)V", cancellable = true)
|
||||
public void sf$interceptTeleport(ServerWorld targetWorld, double x, double y, double z, float yaw, float pitch, CallbackInfo ci) {
|
||||
var room = roomRegistry().getRoomBy($this().getUuid());
|
||||
if (room != null) {
|
||||
if (targetWorld.getRegistryKey() != room.getSpawnPosition().dimension()) {
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private RoomRegistry roomRegistry() {
|
||||
return SFCraft.getInjector().getInstance(RoomRegistry.class);
|
||||
}
|
||||
|
||||
private ServerPlayerEntity $this() {
|
||||
return (ServerPlayerEntity) (Object) this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package io.ib67.sfcraft.module;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import io.ib67.sfcraft.ServerModule;
|
||||
import io.ib67.sfcraft.config.SFConfig;
|
||||
import io.ib67.sfcraft.inject.MinecraftServerSupplier;
|
||||
import io.ib67.sfcraft.room.RequestedRoom;
|
||||
import io.ib67.sfcraft.registry.RoomRegistry;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
|
||||
import net.minecraft.command.CommandRegistryAccess;
|
||||
import net.minecraft.network.PacketCallbacks;
|
||||
import net.minecraft.network.codec.PacketCodecs;
|
||||
import net.minecraft.network.packet.s2c.common.ServerTransferS2CPacket;
|
||||
import net.minecraft.network.packet.s2c.common.StoreCookieS2CPacket;
|
||||
import net.minecraft.registry.RegistryKey;
|
||||
import net.minecraft.server.command.CommandManager;
|
||||
import net.minecraft.server.command.ServerCommandSource;
|
||||
import net.minecraft.server.world.ChunkTicketType;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.util.Pair;
|
||||
import net.minecraft.util.Unit;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.ChunkPos;
|
||||
import net.minecraft.world.World;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.SecretKeyFactory;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Key;
|
||||
import java.security.spec.KeySpec;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import static io.ib67.sfcraft.room.RoomTeleporter.ROOM_COOKIE;
|
||||
import static io.ib67.sfcraft.util.SFConsts.COMMAND_RECO;
|
||||
|
||||
@Log4j2
|
||||
public class RoomModule extends ServerModule {
|
||||
private static final byte[] EMPTY = new byte[0];
|
||||
@Inject
|
||||
private RoomRegistry roomRegistry;
|
||||
@Inject
|
||||
private MinecraftServerSupplier serverSupplier;
|
||||
@Inject
|
||||
private SFConfig config;
|
||||
private Key key;
|
||||
private final List<Pair<RegistryKey<World>, BlockPos>> pregenQueue = new ArrayList<>();
|
||||
private final Map<UUID, GameProfile> uuidMapper = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void onInitialize() {
|
||||
key = getKeyFromPassword(config.serverSecret, RandomStringUtils.random(16));
|
||||
CommandRegistrationCallback.EVENT.register(this::registerCommand);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
for (Pair<RegistryKey<World>, BlockPos> registryKeyBlockPosPair : pregenQueue) {
|
||||
var world = serverSupplier.get().getWorld(registryKeyBlockPosPair.getLeft());
|
||||
if (world == null) {
|
||||
log.warn("Cannot find world " + registryKeyBlockPosPair.getLeft());
|
||||
continue;
|
||||
}
|
||||
world.getChunkManager().addTicket(ChunkTicketType.START, new ChunkPos(registryKeyBlockPosPair.getRight()), 64, Unit.INSTANCE);
|
||||
}
|
||||
}
|
||||
|
||||
private void registerCommand(CommandDispatcher<ServerCommandSource> serverCommandSourceCommandDispatcher, CommandRegistryAccess commandRegistryAccess, CommandManager.RegistrationEnvironment registrationEnvironment) {
|
||||
serverCommandSourceCommandDispatcher.register(
|
||||
LiteralArgumentBuilder.<ServerCommandSource>literal("reco")
|
||||
.requires(it -> it.isExecutedByPlayer() && COMMAND_RECO.hasPermission(it.getPlayer()))
|
||||
.executes(this::onCleanReconnect)
|
||||
);
|
||||
}
|
||||
|
||||
private int onCleanReconnect(CommandContext<ServerCommandSource> serverCommandSourceCommandContext) {
|
||||
var player = serverCommandSourceCommandContext.getSource().getPlayer();
|
||||
if (!isVirtual(player.getUuid())) {
|
||||
player.sendMessage(Text.literal("你不在任何 \"房间\" 内。"));
|
||||
return 0;
|
||||
}
|
||||
var networkHandler = player.networkHandler;
|
||||
networkHandler.send(new StoreCookieS2CPacket(ROOM_COOKIE, EMPTY), PacketCallbacks.always(() -> {
|
||||
networkHandler.sendPacket(new ServerTransferS2CPacket(config.domain, serverSupplier.get().getServerPort()));
|
||||
}));
|
||||
return 0;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public byte[] encrypt(byte[] b) {
|
||||
var cipher = Cipher.getInstance("AES");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key);
|
||||
return cipher.doFinal(b);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public byte[] decrypt(byte[] b) {
|
||||
var cipher = Cipher.getInstance("AES");
|
||||
cipher.init(Cipher.DECRYPT_MODE, key);
|
||||
return cipher.doFinal(b);
|
||||
}
|
||||
|
||||
public byte[] serializeRequestedRoom(RequestedRoom room) {
|
||||
var buf = Unpooled.buffer();
|
||||
ByteBuf nbuf = null;
|
||||
try {
|
||||
Identifier.PACKET_CODEC.encode(buf, room.identifier());
|
||||
PacketCodecs.GAME_PROFILE.encode(buf, new GameProfile(room.profileUuid(), room.profileName()));
|
||||
var encrypted = encrypt(buf.array());
|
||||
nbuf = Unpooled.buffer();
|
||||
nbuf.writeShortLE(encrypted.length);
|
||||
nbuf.writeBytes(encrypted);
|
||||
return nbuf.array();
|
||||
} finally {
|
||||
if (nbuf != null) nbuf.release();
|
||||
buf.release();
|
||||
}
|
||||
}
|
||||
|
||||
public RequestedRoom readRoomRequest(byte[] payload) {
|
||||
ByteBuf buf = null;
|
||||
ByteBuf decrypted = null;
|
||||
try {
|
||||
buf = Unpooled.wrappedBuffer(payload);
|
||||
var len = buf.readUnsignedShortLE();
|
||||
var content = new byte[len];
|
||||
buf.readBytes(content);
|
||||
decrypted = Unpooled.wrappedBuffer(decrypt(content));
|
||||
|
||||
var identifier = Identifier.PACKET_CODEC.decode(decrypted);
|
||||
var profile = PacketCodecs.GAME_PROFILE.decode(decrypted);
|
||||
return new RequestedRoom(identifier, profile.getName(), profile.getId());
|
||||
} finally {
|
||||
if (buf != null) buf.release(); //todo
|
||||
if (decrypted != null) decrypted.release();
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private static SecretKey getKeyFromPassword(String password, String salt) {
|
||||
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
|
||||
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt.getBytes(), 65536, 256);
|
||||
SecretKey secret = new SecretKeySpec(factory.generateSecret(spec)
|
||||
.getEncoded(), "AES");
|
||||
return secret;
|
||||
}
|
||||
|
||||
public void enqueuePregen(RegistryKey<World> world, BlockPos spawnPos) {
|
||||
pregenQueue.add(new Pair<>(world, spawnPos));
|
||||
}
|
||||
|
||||
public UUID generateIdForRoom(GameProfile issuer, String name, Identifier room) {
|
||||
var uuid = UUID.nameUUIDFromBytes((name + "@" + room.toString()).getBytes(StandardCharsets.UTF_8));
|
||||
var result = new UUID(uuid.getMostSignificantBits(), 0);
|
||||
if (issuer != null) uuidMapper.put(result, issuer);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static boolean isVirtual(UUID uuid) {
|
||||
return uuid.getLeastSignificantBits() == 0;
|
||||
}
|
||||
|
||||
public GameProfile devirtualize(UUID virtual) {
|
||||
return uuidMapper.get(virtual);
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,7 @@ import io.ib67.sfcraft.ServerModule;
|
||||
import io.ib67.sfcraft.callback.SFCallbacks;
|
||||
import io.ib67.sfcraft.module.randomevt.longnight.DawnAfterLongNightEvent;
|
||||
import io.ib67.sfcraft.module.randomevt.longnight.LongNightEvent;
|
||||
import io.ib67.sfcraft.registry.event.RandomEventRegistry;
|
||||
import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents;
|
||||
import io.ib67.sfcraft.registry.RandomEventRegistry;
|
||||
import net.fabricmc.fabric.api.event.player.UseItemCallback;
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
import net.minecraft.item.ItemStack;
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package io.ib67.sfcraft.module.room;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import io.ib67.sfcraft.ServerModule;
|
||||
import io.ib67.sfcraft.inject.MinecraftServerSupplier;
|
||||
import io.ib67.sfcraft.module.RoomModule;
|
||||
import io.ib67.sfcraft.registry.RoomRegistry;
|
||||
import io.ib67.sfcraft.room.RoomTeleporter;
|
||||
import io.ib67.sfcraft.room.create.CreativeSpaceFactory;
|
||||
import io.ib67.sfcraft.room.create.CreativeSpaceRoom;
|
||||
import io.ib67.sfcraft.util.SFConsts;
|
||||
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
|
||||
import net.minecraft.command.CommandRegistryAccess;
|
||||
import net.minecraft.registry.RegistryKey;
|
||||
import net.minecraft.server.command.CommandManager;
|
||||
import net.minecraft.server.command.ServerCommandSource;
|
||||
import net.minecraft.world.chunk.ChunkStatus;
|
||||
|
||||
public class CreativeRoomModule extends ServerModule {
|
||||
@Inject
|
||||
private RoomRegistry roomRegistry;
|
||||
@Inject
|
||||
private RoomTeleporter teleporter;
|
||||
@Inject
|
||||
private RoomModule roomModule;
|
||||
@Inject
|
||||
private MinecraftServerSupplier serverSupplier;
|
||||
private CreativeSpaceRoom room;
|
||||
|
||||
@Override
|
||||
public void onInitialize() {
|
||||
CommandRegistrationCallback.EVENT
|
||||
.register(this::registerCommand);
|
||||
room = new CreativeSpaceRoom();
|
||||
roomRegistry.registerRoomType(CreativeSpaceRoom.class, CreativeSpaceRoom.WORLD, new CreativeSpaceFactory(room));
|
||||
roomRegistry.createRoomOf(CreativeSpaceRoom.class, CreativeSpaceRoom.IDENTIFIER, null, (String) null);
|
||||
roomModule.enqueuePregen(CreativeSpaceRoom.WORLD,CreativeSpaceRoom.SPAWN_POS);
|
||||
}
|
||||
|
||||
private void registerCommand(CommandDispatcher<ServerCommandSource> dispatcher,
|
||||
CommandRegistryAccess registry, CommandManager.RegistrationEnvironment env) {
|
||||
dispatcher.register(
|
||||
LiteralArgumentBuilder.<ServerCommandSource>literal("playground")
|
||||
.requires(it -> it.isExecutedByPlayer() && SFConsts.COMMAND_PLAYGROUND.hasPermission(it.getPlayer()))
|
||||
.executes(this::gotoPlayground)
|
||||
);
|
||||
}
|
||||
|
||||
private int gotoPlayground(CommandContext<ServerCommandSource> serverCommandSourceCommandContext) {
|
||||
var player = serverCommandSourceCommandContext.getSource().getPlayer();
|
||||
teleporter.teleportTo(room, player);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package io.ib67.sfcraft.registry.event;
|
||||
package io.ib67.sfcraft.registry;
|
||||
|
||||
import io.ib67.sfcraft.module.randomevt.ActiveEvent;
|
||||
import io.ib67.sfcraft.module.randomevt.RandomEvent;
|
||||
@@ -0,0 +1,26 @@
|
||||
package io.ib67.sfcraft.registry;
|
||||
|
||||
import io.ib67.sfcraft.subserver.Room;
|
||||
import io.ib67.sfcraft.subserver.RoomFactory;
|
||||
import io.ib67.sfcraft.subserver.RoomSession;
|
||||
import net.minecraft.registry.RegistryKey;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface RoomRegistry {
|
||||
Collection<? extends Room> getRooms();
|
||||
|
||||
Room getRoomBy(Identifier identifier);
|
||||
|
||||
RoomSession getRoomBy(UUID player);
|
||||
|
||||
<T extends Room> T createRoomOf(Class<T> type, Identifier roomId, ServerPlayerEntity issuer, String... arguments);
|
||||
|
||||
boolean isRoomWorld(RegistryKey<World> world);
|
||||
|
||||
<T extends Room> void registerRoomType(Class<T> type, RegistryKey<World> worldKey, RoomFactory<T> factory);
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package io.ib67.sfcraft.registry.event;
|
||||
|
||||
import io.ib67.sfcraft.module.randomevt.ActiveEvent;
|
||||
import io.ib67.sfcraft.module.randomevt.RandomEvent;
|
||||
import io.ib67.sfcraft.registry.RandomEventRegistry;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package io.ib67.sfcraft.registry.room;
|
||||
|
||||
import io.ib67.sfcraft.registry.RoomRegistry;
|
||||
import io.ib67.sfcraft.subserver.Room;
|
||||
import io.ib67.sfcraft.subserver.RoomFactory;
|
||||
import io.ib67.sfcraft.subserver.RoomSession;
|
||||
import net.minecraft.registry.RegistryKey;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
public class SimpleRoomRegistry implements RoomRegistry {
|
||||
private final ConcurrentMap<Identifier, Room> rooms = new ConcurrentHashMap<>();
|
||||
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
|
||||
private final Map<Class<?>, RoomFactory<?>> factories = new HashMap<>();
|
||||
private final Set<RegistryKey<World>> roomWorlds = new HashSet<>();
|
||||
|
||||
@Override
|
||||
public Collection<? extends Room> getRooms() {
|
||||
return rooms.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Room getRoomBy(Identifier identifier) {
|
||||
return rooms.get(identifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoomSession getRoomBy(UUID player) {
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
return rooms.values().stream()
|
||||
.map(it -> it.getPlayerManager().getSessionBy(player))
|
||||
.filter(Objects::nonNull)
|
||||
.findFirst().orElse(null);
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Room> T createRoomOf(Class<T> type, Identifier roomId, ServerPlayerEntity issuer, String... arguments) {
|
||||
if (getRoomBy(roomId) != null) {
|
||||
throw new IllegalArgumentException("duplicate room id: " + roomId);
|
||||
}
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
var room = factories.get(type).create(issuer, arguments);
|
||||
rooms.put(roomId, room);
|
||||
return (T) room;
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRoomWorld(RegistryKey<World> world) {
|
||||
return roomWorlds.contains(world);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Room> void registerRoomType(Class<T> type, RegistryKey<World> worldKey, RoomFactory<T> factory) {
|
||||
roomWorlds.add(worldKey);
|
||||
factories.put(type, factory);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package io.ib67.sfcraft.room;
|
||||
|
||||
public enum CookieState {
|
||||
NOT_SENT,
|
||||
SENT,
|
||||
RECV,
|
||||
DONE,
|
||||
ERROR
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package io.ib67.sfcraft.room;
|
||||
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public record RequestedRoom(
|
||||
Identifier identifier,
|
||||
String profileName,
|
||||
UUID profileUuid
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package io.ib67.sfcraft.room;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import io.ib67.sfcraft.config.SFConfig;
|
||||
import io.ib67.sfcraft.inject.MinecraftServerSupplier;
|
||||
import io.ib67.sfcraft.module.RoomModule;
|
||||
import io.ib67.sfcraft.registry.RoomRegistry;
|
||||
import io.ib67.sfcraft.subserver.Room;
|
||||
import io.ib67.sfcraft.util.Helper;
|
||||
import net.minecraft.network.PacketCallbacks;
|
||||
import net.minecraft.network.packet.s2c.common.ServerTransferS2CPacket;
|
||||
import net.minecraft.network.packet.s2c.common.StoreCookieS2CPacket;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.util.Colors;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
public class RoomTeleporter {
|
||||
@Inject
|
||||
private RoomRegistry registry;
|
||||
@Inject
|
||||
private RoomModule roomModule;
|
||||
@Inject
|
||||
private MinecraftServerSupplier serverSupplier;
|
||||
@Inject
|
||||
private SFConfig config;
|
||||
|
||||
public static final Identifier ROOM_COOKIE = Identifier.of("sfcraft", "room");
|
||||
|
||||
public void teleportTo(Room room, ServerPlayerEntity player) {
|
||||
if (roomModule.isVirtual(player.getUuid())) {
|
||||
player.sendMessage(Text.literal("您已经在某个房间里了").withColor(Colors.LIGHT_RED));
|
||||
return;
|
||||
}
|
||||
var finalUuid = roomModule.generateIdForRoom(player.getGameProfile(), player.getName().getLiteralString(), room.getServerIdentifier());
|
||||
var sess = room.getPlayerManager().createSessionFor(finalUuid);
|
||||
if (player.getServer().getWorld(sess.getSpawnPosition().dimension()) == null) {
|
||||
throw new IllegalStateException("world isn't exist");
|
||||
}
|
||||
var networkHandler = player.networkHandler;
|
||||
networkHandler.reconfigure();
|
||||
networkHandler.send(new StoreCookieS2CPacket(ROOM_COOKIE, roomModule.serializeRequestedRoom(new RequestedRoom(
|
||||
room.getServerIdentifier(),
|
||||
player.getName().getLiteralString(),
|
||||
finalUuid
|
||||
))), PacketCallbacks.always(() -> {
|
||||
networkHandler.sendPacket(new ServerTransferS2CPacket(config.domain, serverSupplier.get().getServerPort()));
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package io.ib67.sfcraft.room;
|
||||
|
||||
import io.ib67.sfcraft.subserver.Room;
|
||||
import io.ib67.sfcraft.subserver.RoomPlayerManager;
|
||||
import io.ib67.sfcraft.subserver.RoomSession;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import net.minecraft.util.math.GlobalPos;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public abstract class SimpleRoomPlayerManager implements RoomPlayerManager {
|
||||
protected final Map<UUID, PlayerEntry> onlinePlayers = new HashMap<>();
|
||||
protected final Room room;
|
||||
|
||||
protected SimpleRoomPlayerManager(Room room) {
|
||||
this.room = room;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ServerPlayerEntity> getJoinedPlayers() {
|
||||
return onlinePlayers.values().stream().map(it -> it.player).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RoomSession> getSessions() {
|
||||
return onlinePlayers.values().stream().map(it -> it.session).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoomSession createSessionFor(UUID uuid) {
|
||||
return onlinePlayers.computeIfAbsent(uuid, this::makeSession).getSession();
|
||||
}
|
||||
|
||||
private PlayerEntry makeSession(UUID uuid) {
|
||||
var session = createSession(uuid);
|
||||
return new PlayerEntry(null, new DelegatedSession(session));
|
||||
}
|
||||
|
||||
protected abstract RoomSession createSession(UUID id);
|
||||
|
||||
@Override
|
||||
public RoomSession getSessionBy(UUID uuid) {
|
||||
var proc = onlinePlayers.get(uuid);
|
||||
if (proc == null) return null;
|
||||
return proc.session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerPlayerEntity getPlayer(UUID uuid) {
|
||||
return onlinePlayers.get(uuid).getPlayer();
|
||||
}
|
||||
|
||||
class DelegatedSession extends RoomSession {
|
||||
private final RoomSession session;
|
||||
|
||||
protected DelegatedSession(RoomSession session) {
|
||||
super(SimpleRoomPlayerManager.this.room);
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalPos getSpawnPosition() {
|
||||
return session.getSpawnPosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayerLogin(UUID uuid) {
|
||||
session.onPlayerLogin(uuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayerJoin(ServerPlayerEntity player) {
|
||||
onlinePlayers.get(player.getUuid()).setPlayer(player);
|
||||
session.onPlayerJoin(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayerQuit(ServerPlayerEntity player) {
|
||||
onlinePlayers.remove(player.getUuid());
|
||||
session.onPlayerQuit(player);
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
class PlayerEntry {
|
||||
private ServerPlayerEntity player;
|
||||
private RoomSession session;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package io.ib67.sfcraft.room.create;
|
||||
|
||||
import io.ib67.sfcraft.room.SimpleRoomPlayerManager;
|
||||
import io.ib67.sfcraft.subserver.Room;
|
||||
import io.ib67.sfcraft.subserver.RoomSession;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class CreativePlayerManager extends SimpleRoomPlayerManager {
|
||||
protected CreativePlayerManager(Room room) {
|
||||
super(room);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RoomSession createSession(UUID id) {
|
||||
return new CreativePlayerSession(room);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package io.ib67.sfcraft.room.create;
|
||||
|
||||
import io.ib67.sfcraft.subserver.Room;
|
||||
import io.ib67.sfcraft.subserver.RoomSession;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.util.Colors;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.GlobalPos;
|
||||
import net.minecraft.world.GameMode;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class CreativePlayerSession extends RoomSession {
|
||||
private final GlobalPos spawn;
|
||||
|
||||
protected CreativePlayerSession(Room room) {
|
||||
super(room);
|
||||
spawn = new GlobalPos(
|
||||
CreativeSpaceRoom.WORLD,
|
||||
CreativeSpaceRoom.SPAWN_POS
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayerJoin(ServerPlayerEntity player) {
|
||||
player.sendMessage(Text.of("你现在正在创造游乐园中。"));
|
||||
player.sendMessage(Text.literal("游乐园中的生物不能逃逸到其他维度。").withColor(Colors.GRAY));
|
||||
player.sendMessage(Text.literal("使用 /reco 或重新加入游戏即可离开。").withColor(Colors.GRAY));
|
||||
player.changeGameMode(GameMode.CREATIVE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GlobalPos getSpawnPosition() {
|
||||
return spawn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayerLogin(UUID uuid) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlayerQuit(ServerPlayerEntity player) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package io.ib67.sfcraft.room.create;
|
||||
|
||||
import io.ib67.sfcraft.subserver.RoomFactory;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class CreativeSpaceFactory implements RoomFactory<CreativeSpaceRoom> {
|
||||
private final CreativeSpaceRoom room;
|
||||
|
||||
@Override
|
||||
public CreativeSpaceRoom create(ServerPlayerEntity issuer, String[] arguments) {
|
||||
return room;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package io.ib67.sfcraft.room.create;
|
||||
|
||||
import io.ib67.sfcraft.subserver.Room;
|
||||
import io.ib67.sfcraft.subserver.RoomPlayerManager;
|
||||
import lombok.Getter;
|
||||
import net.minecraft.registry.RegistryKey;
|
||||
import net.minecraft.registry.RegistryKeys;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
public class CreativeSpaceRoom implements Room {
|
||||
public static final RegistryKey<World> WORLD = RegistryKey.of(RegistryKeys.WORLD, Identifier.of("sfcraft", "playground"));
|
||||
public static final Identifier IDENTIFIER = Identifier.of("sfcraft", "creative_space");
|
||||
public static final BlockPos SPAWN_POS = new BlockPos(0, 66, 0);
|
||||
@Getter
|
||||
private final RoomPlayerManager playerManager;
|
||||
|
||||
public CreativeSpaceRoom() {
|
||||
this.playerManager = new CreativePlayerManager(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
for (ServerPlayerEntity joinedPlayer : playerManager.getJoinedPlayers()) {
|
||||
joinedPlayer.networkHandler.disconnect(Text.of("Room is shutting down"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Identifier getServerIdentifier() {
|
||||
return IDENTIFIER;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package io.ib67.sfcraft.subserver;
|
||||
|
||||
import net.minecraft.network.packet.s2c.common.StoreCookieS2CPacket;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.util.math.GlobalPos;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public interface Room {
|
||||
Identifier getServerIdentifier();
|
||||
|
||||
RoomPlayerManager getPlayerManager();
|
||||
|
||||
void shutdown();
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package io.ib67.sfcraft.subserver;
|
||||
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface RoomFactory<T extends Room> {
|
||||
T create(ServerPlayerEntity issuer, String[] arguments);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package io.ib67.sfcraft.subserver;
|
||||
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface RoomPlayerManager {
|
||||
List<ServerPlayerEntity> getJoinedPlayers();
|
||||
|
||||
List<RoomSession> getSessions();
|
||||
|
||||
RoomSession createSessionFor(UUID uuid);
|
||||
|
||||
RoomSession getSessionBy(UUID uuid);
|
||||
|
||||
ServerPlayerEntity getPlayer(UUID uuid);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package io.ib67.sfcraft.subserver;
|
||||
|
||||
import lombok.Getter;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import net.minecraft.util.math.GlobalPos;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public abstract class RoomSession {
|
||||
@Getter
|
||||
protected final Room room;
|
||||
|
||||
protected RoomSession(Room room) {
|
||||
this.room = room;
|
||||
}
|
||||
|
||||
public abstract GlobalPos getSpawnPosition();
|
||||
|
||||
public abstract void onPlayerLogin(UUID uuid);
|
||||
|
||||
public abstract void onPlayerJoin(ServerPlayerEntity player);
|
||||
|
||||
public abstract void onPlayerQuit(ServerPlayerEntity player);
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import net.minecraft.block.Block;
|
||||
import net.minecraft.block.CropBlock;
|
||||
import net.minecraft.block.Fertilizable;
|
||||
import net.minecraft.block.SaplingBlock;
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
import net.minecraft.fluid.EmptyFluid;
|
||||
import net.minecraft.fluid.LavaFluid;
|
||||
import net.minecraft.fluid.WaterFluid;
|
||||
@@ -16,15 +17,18 @@ import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import net.minecraft.server.world.ServerWorld;
|
||||
import net.minecraft.text.Text;
|
||||
import net.minecraft.util.Colors;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.Direction;
|
||||
import net.minecraft.world.World;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public class Helper {
|
||||
public static final char COLOR = '§';
|
||||
|
||||
@@ -20,6 +20,8 @@ public class SFConsts {
|
||||
public static final Permission<PlayerEntity> COMMAND_LISTOFFLINE = ofSFCPermission("command.listoffline", false);
|
||||
public static final Permission<PlayerEntity> COMMAND_LISTPERM = ofSFCPermission("command.listperm", false);
|
||||
public static final Permission<PlayerEntity> COMMAND_LISTGEO = ofSFCPermission("command.listgeo", false);
|
||||
public static final Permission<PlayerEntity> COMMAND_PLAYGROUND = ofSFCPermission("command.playground", true);
|
||||
public static final Permission<PlayerEntity> COMMAND_RECO = ofSFCPermission("command.reco", true);
|
||||
|
||||
public static Permission<PlayerEntity> ofSFCPermission(String key, boolean byDefault) {
|
||||
return new Permission<>("sfcraft." + key, byDefault);
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"type": "overworld",
|
||||
"generator": {
|
||||
"type": "flat",
|
||||
"settings": {
|
||||
"layers": [
|
||||
{
|
||||
"height": 64,
|
||||
"block": "minecraft:white_stained_glass"
|
||||
}
|
||||
],
|
||||
"structure_overrides": [],
|
||||
"biome": "plains"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,13 @@
|
||||
"common.MobEntityMixin",
|
||||
"server.hack.EntitySpawnS2CPacketMixin",
|
||||
"server.hack.RegistryHandlerMixin",
|
||||
"server.hack.HoverEntityContentMixin"
|
||||
"server.hack.HoverEntityContentMixin",
|
||||
"server.subserver.ServerLoginNetworkHandlerMixin",
|
||||
"server.subserver.EntityMixin",
|
||||
"server.subserver.PlayerManagerMixin",
|
||||
"server.subserver.PlayerSaveHandlerMixin",
|
||||
"server.subserver.ServerPlayerEntityMixin",
|
||||
"server.subserver.PlayerListS2CPacket$EntryMixin"
|
||||
],
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
|
||||
Reference in new issue
Block a user