refactoring: swap modules
This commit is contained in:
111
server/src/main/java/mc/server/Main.java
Normal file
111
server/src/main/java/mc/server/Main.java
Normal file
@@ -0,0 +1,111 @@
|
||||
package mc.server;
|
||||
|
||||
import ch.qos.logback.classic.LoggerContext;
|
||||
import ch.qos.logback.classic.joran.JoranConfigurator;
|
||||
import ch.qos.logback.core.joran.spi.JoranException;
|
||||
import com.typesafe.config.Config;
|
||||
import lombok.SneakyThrows;
|
||||
import mc.cliparser.CommandLine;
|
||||
import mc.cliparser.CommandLineParser;
|
||||
import mc.cliparser.Option;
|
||||
import mc.server.di.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Objects;
|
||||
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
//region Setup CommandLine
|
||||
Option logconfigOption = Option.builder().longName("logconfig").hasArgs(true).build();
|
||||
Option configOption = Option.builder().longName("config").hasArgs(true).build();
|
||||
var cliParser = new CommandLineParser();
|
||||
cliParser.addOption(logconfigOption);
|
||||
cliParser.addOption(configOption);
|
||||
|
||||
CommandLine commandLine = cliParser.parse(args);
|
||||
//endregion
|
||||
|
||||
//region Configuration Logback
|
||||
Path logconfigPath;
|
||||
if (commandLine.has(logconfigOption)) {
|
||||
logconfigPath = Paths.get(logconfigOption.value());
|
||||
} else {
|
||||
logconfigPath = defaultLogbackConfigPath();
|
||||
}
|
||||
reconfigureLogback(logconfigPath);
|
||||
//endregion
|
||||
|
||||
//region Setup config
|
||||
ConfigModule configModule = new ConfigModule("./config-default.conf",
|
||||
commandLine.has(configOption) ? Paths.get(configOption.value()) : null);
|
||||
ConfigComponent configComponent = DaggerConfigComponent.builder()
|
||||
.configModule(configModule).build();
|
||||
//endregion
|
||||
|
||||
//region Debug log config
|
||||
Logger log = LoggerFactory.getLogger("LAUNCHER");
|
||||
if (log.isDebugEnabled()) {
|
||||
Config config = configComponent.getConfig();
|
||||
log.debug("Logback config path: {}", logconfigOption.value() == null ? "(default)" : logconfigOption.value());
|
||||
log.debug("Config path: {}", configOption.value() == null ? "(default)" : configOption.value());
|
||||
|
||||
config.entrySet().stream()
|
||||
.map(entry -> String.format("[CONFIG] %s = %s", entry.getKey(), entry.getValue().render()))
|
||||
.sorted()
|
||||
.forEach(log::debug);
|
||||
}
|
||||
//endregion
|
||||
|
||||
ServerComponent serverComponent = DaggerServerComponent.builder()
|
||||
.processorModule(new ProcessorModule(configComponent.getConfig()))
|
||||
.build();
|
||||
serverComponent.getProcessors().forEach(processor -> processor.setup(serverComponent.getProtocolHandlersBus()));
|
||||
|
||||
NettyServer server = serverComponent.getNettyServer();
|
||||
|
||||
String host = configComponent.getConfig().getString("server.host");
|
||||
int port = configComponent.getConfig().getInt("server.port");
|
||||
|
||||
log.info("Server starting: {}:{}", host, port);
|
||||
server.start(host, port);
|
||||
}
|
||||
|
||||
//TODO нужно продумать как этот метод сделать доступным для расширенных версий сервера
|
||||
/**
|
||||
* Перенастраиваем logback с учетом путей.
|
||||
* <p>По-умолчанию, logback пытается искать свои конфиги по заранее зашитым путям.
|
||||
* Здесь мы изменяем эти принципы.
|
||||
*/
|
||||
private static void reconfigureLogback(@Nonnull Path configPath) throws IOException {
|
||||
if (Files.notExists(configPath)) {
|
||||
throw new FileNotFoundException(configPath.toAbsolutePath().toString());
|
||||
}
|
||||
|
||||
LoggerContext logbackContext = (LoggerContext) LoggerFactory.getILoggerFactory();
|
||||
logbackContext.reset();
|
||||
JoranConfigurator configurator = new JoranConfigurator();
|
||||
|
||||
try(InputStream in = Files.newInputStream(configPath)) {
|
||||
configurator.setContext(logbackContext);
|
||||
configurator.doConfigure(in);
|
||||
} catch (JoranException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private static Path defaultLogbackConfigPath() {
|
||||
URL url = Objects.requireNonNull(Main.class.getResource("/logback-default.xml"));
|
||||
return Paths.get(url.toURI());
|
||||
}
|
||||
}
|
||||
55
server/src/main/java/mc/server/NettyServer.java
Normal file
55
server/src/main/java/mc/server/NettyServer.java
Normal file
@@ -0,0 +1,55 @@
|
||||
package mc.server;
|
||||
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.handler.logging.LogLevel;
|
||||
import io.netty.handler.logging.LoggingHandler;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import mc.protocol.handler.ProtocolHandlersBus;
|
||||
import mc.protocol.handler.ProtocolInboundHandler;
|
||||
import mc.protocol.handler.codec.ProtocolDecoder;
|
||||
import mc.protocol.handler.codec.ProtocolEncoder;
|
||||
import mc.protocol.handler.codec.ProtocolSplitter;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class NettyServer {
|
||||
|
||||
private final ProtocolHandlersBus protocolHandlersBus;
|
||||
|
||||
public void start(String host, int port) {
|
||||
try {
|
||||
createServerBootstrap().bind(host, port).sync().channel().closeFuture().sync();
|
||||
} catch (InterruptedException e) {
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("{}: {}", e.getClass().getSimpleName(), e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ServerBootstrap createServerBootstrap() {
|
||||
ServerBootstrap bootstrap = new ServerBootstrap();
|
||||
|
||||
bootstrap.group(new NioEventLoopGroup(1), new NioEventLoopGroup())
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.childHandler(new ChannelInitializer<SocketChannel>() {
|
||||
@Override
|
||||
protected void initChannel(@Nonnull SocketChannel socketChannel) {
|
||||
socketChannel.pipeline()
|
||||
.addLast("packet_splitter", new ProtocolSplitter())
|
||||
.addLast("logger", new LoggingHandler(LogLevel.DEBUG))
|
||||
.addLast("packet_decoder", new ProtocolDecoder(false))
|
||||
.addLast("packet_encoder", new ProtocolEncoder())
|
||||
.addLast("packet_handler", new ProtocolInboundHandler(protocolHandlersBus));
|
||||
}
|
||||
});
|
||||
|
||||
return bootstrap;
|
||||
}
|
||||
}
|
||||
20
server/src/main/java/mc/server/Player.java
Normal file
20
server/src/main/java/mc/server/Player.java
Normal file
@@ -0,0 +1,20 @@
|
||||
package mc.server;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import mc.protocol.model.Location;
|
||||
import mc.protocol.utils.GameMode;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public class Player {
|
||||
|
||||
private final ChannelHandlerContext ctx;
|
||||
private final UUID uuid;
|
||||
private final String name;
|
||||
private final GameMode gameMode;
|
||||
private final Location location;
|
||||
}
|
||||
27
server/src/main/java/mc/server/PlayerManager.java
Normal file
27
server/src/main/java/mc/server/PlayerManager.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package mc.server;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import mc.protocol.model.Location;
|
||||
import mc.protocol.utils.GameMode;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.UUID;
|
||||
|
||||
public class PlayerManager {
|
||||
|
||||
private final LinkedList<Player> players = new LinkedList<>();
|
||||
|
||||
public Player create(ChannelHandlerContext ctx, String name, GameMode gameMode, Location location) {
|
||||
Player player = new Player(ctx, UUID.randomUUID(), name, gameMode, location);
|
||||
players.add(player);
|
||||
return player;
|
||||
}
|
||||
|
||||
public void remove(Player player) {
|
||||
players.remove(player);
|
||||
}
|
||||
|
||||
public int online() {
|
||||
return players.size();
|
||||
}
|
||||
}
|
||||
10
server/src/main/java/mc/server/ServetAttributes.java
Normal file
10
server/src/main/java/mc/server/ServetAttributes.java
Normal file
@@ -0,0 +1,10 @@
|
||||
package mc.server;
|
||||
|
||||
import io.netty.util.AttributeKey;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
@UtilityClass
|
||||
public class ServetAttributes {
|
||||
|
||||
public static final AttributeKey<Player> PLAYER = AttributeKey.newInstance("PLAYER");
|
||||
}
|
||||
13
server/src/main/java/mc/server/di/ConfigComponent.java
Normal file
13
server/src/main/java/mc/server/di/ConfigComponent.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package mc.server.di;
|
||||
|
||||
import com.typesafe.config.Config;
|
||||
import dagger.Component;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
@Component(modules = ConfigModule.class)
|
||||
@Singleton
|
||||
public interface ConfigComponent {
|
||||
|
||||
Config getConfig();
|
||||
}
|
||||
36
server/src/main/java/mc/server/di/ConfigModule.java
Normal file
36
server/src/main/java/mc/server/di/ConfigModule.java
Normal file
@@ -0,0 +1,36 @@
|
||||
package mc.server.di;
|
||||
|
||||
import com.typesafe.config.Config;
|
||||
import com.typesafe.config.ConfigFactory;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
import java.nio.file.Path;
|
||||
|
||||
@Module
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class ConfigModule {
|
||||
|
||||
private final String defaultResource;
|
||||
private final Path configPath;
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
Config provideConfig() {
|
||||
Config defaultConfig = ConfigFactory.parseResources(defaultResource);
|
||||
Config config;
|
||||
|
||||
if (configPath != null) {
|
||||
Config userConfig = ConfigFactory.parseFile(configPath.toFile());
|
||||
config = userConfig.withFallback(defaultConfig).resolve();
|
||||
} else {
|
||||
config = defaultConfig.resolve();
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
}
|
||||
17
server/src/main/java/mc/server/di/PlayerManagerModule.java
Normal file
17
server/src/main/java/mc/server/di/PlayerManagerModule.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package mc.server.di;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import mc.server.PlayerManager;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
@Module
|
||||
public class PlayerManagerModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
PlayerManager providePlayerManager() {
|
||||
return new PlayerManager();
|
||||
}
|
||||
}
|
||||
48
server/src/main/java/mc/server/di/ProcessorModule.java
Normal file
48
server/src/main/java/mc/server/di/ProcessorModule.java
Normal file
@@ -0,0 +1,48 @@
|
||||
package mc.server.di;
|
||||
|
||||
import com.typesafe.config.Config;
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.multibindings.IntoSet;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import mc.protocol.world.World;
|
||||
import mc.server.PlayerManager;
|
||||
import mc.server.processor.*;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Random;
|
||||
|
||||
@Module
|
||||
@RequiredArgsConstructor
|
||||
public class ProcessorModule {
|
||||
|
||||
private final Config config;
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
@Singleton
|
||||
PacketProcessor provideProcessorHadshake() {
|
||||
return new ProcessorHandshake();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
@Singleton
|
||||
PacketProcessor provideProcessorStatus() {
|
||||
return new ProcessorStatus(config);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
@Singleton
|
||||
PacketProcessor provideProcessorLogin(PlayerManager playerManager, World world) {
|
||||
return new ProcessorLogin(playerManager, new Random(System.currentTimeMillis()), config, world);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
@Singleton
|
||||
PacketProcessor provideProcessorPlay() {
|
||||
return new ProcessorPlay();
|
||||
}
|
||||
}
|
||||
21
server/src/main/java/mc/server/di/ServerComponent.java
Normal file
21
server/src/main/java/mc/server/di/ServerComponent.java
Normal file
@@ -0,0 +1,21 @@
|
||||
package mc.server.di;
|
||||
|
||||
import dagger.Component;
|
||||
import mc.protocol.handler.ProtocolHandlersBus;
|
||||
import mc.server.NettyServer;
|
||||
import mc.server.processor.PacketProcessor;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Set;
|
||||
|
||||
@Component(modules = {
|
||||
ServerModule.class, ProcessorModule.class, PlayerManagerModule.class,
|
||||
WorldModule.class
|
||||
})
|
||||
@Singleton
|
||||
public interface ServerComponent {
|
||||
|
||||
ProtocolHandlersBus getProtocolHandlersBus();
|
||||
NettyServer getNettyServer();
|
||||
Set<PacketProcessor> getProcessors();
|
||||
}
|
||||
24
server/src/main/java/mc/server/di/ServerModule.java
Normal file
24
server/src/main/java/mc/server/di/ServerModule.java
Normal file
@@ -0,0 +1,24 @@
|
||||
package mc.server.di;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import mc.protocol.handler.ProtocolHandlersBus;
|
||||
import mc.server.NettyServer;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
@Module
|
||||
public class ServerModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
ProtocolHandlersBus providePacketProcessor() {
|
||||
return new ProtocolHandlersBus();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
NettyServer provideNettyServer(ProtocolHandlersBus protocolHandlersBus) {
|
||||
return new NettyServer(protocolHandlersBus);
|
||||
}
|
||||
}
|
||||
18
server/src/main/java/mc/server/di/WorldModule.java
Normal file
18
server/src/main/java/mc/server/di/WorldModule.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package mc.server.di;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import mc.protocol.world.World;
|
||||
import mc.server.world.VoidWorld;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
@Module
|
||||
public class WorldModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
World provideWorld() {
|
||||
return new VoidWorld();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package mc.server.processor;
|
||||
|
||||
import mc.protocol.handler.ProtocolHandlersBus;
|
||||
|
||||
public interface PacketProcessor {
|
||||
|
||||
void setup(ProtocolHandlersBus protocolHandlersBus);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package mc.server.processor;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import mc.protocol.ProtocolAttributes;
|
||||
import mc.protocol.State;
|
||||
import mc.protocol.handler.ProtocolHandlersBus;
|
||||
import mc.protocol.packets.handshaking.client.HandshakePacket;
|
||||
|
||||
public class ProcessorHandshake implements PacketProcessor {
|
||||
|
||||
@Override
|
||||
public void setup(ProtocolHandlersBus protocolHandlersBus) {
|
||||
protocolHandlersBus.addHandler(State.HANDSHAKING, HandshakePacket.class, this::handshake);
|
||||
}
|
||||
|
||||
private void handshake(ChannelHandlerContext ctx, HandshakePacket packet) {
|
||||
ctx.channel().attr(ProtocolAttributes.STATE).set(packet.getNextState());
|
||||
}
|
||||
}
|
||||
142
server/src/main/java/mc/server/processor/ProcessorLogin.java
Normal file
142
server/src/main/java/mc/server/processor/ProcessorLogin.java
Normal file
@@ -0,0 +1,142 @@
|
||||
package mc.server.processor;
|
||||
|
||||
import com.typesafe.config.Config;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import mc.protocol.ProtocolAttributes;
|
||||
import mc.protocol.State;
|
||||
import mc.protocol.handler.ProtocolHandlersBus;
|
||||
import mc.protocol.model.Location;
|
||||
import mc.protocol.model.Look;
|
||||
import mc.protocol.packets.KeepAlivePacket;
|
||||
import mc.protocol.packets.login.client.LoginStartPacket;
|
||||
import mc.protocol.packets.login.server.LoginSuccessPacket;
|
||||
import mc.protocol.packets.play.server.*;
|
||||
import mc.protocol.utils.Difficulty;
|
||||
import mc.protocol.utils.GameMode;
|
||||
import mc.protocol.world.Chunk;
|
||||
import mc.protocol.world.World;
|
||||
import mc.server.Player;
|
||||
import mc.server.PlayerManager;
|
||||
import mc.server.ServetAttributes;
|
||||
import mc.server.util.LocationUtils;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class ProcessorLogin implements PacketProcessor {
|
||||
|
||||
private final PlayerManager playerManager;
|
||||
private final Random random;
|
||||
private final Config config;
|
||||
private final World world;
|
||||
|
||||
@Override
|
||||
public void setup(ProtocolHandlersBus protocolHandlersBus) {
|
||||
protocolHandlersBus.addHandler(State.LOGIN, LoginStartPacket.class, this::login);
|
||||
}
|
||||
|
||||
private void login(ChannelHandlerContext ctx, LoginStartPacket packet) {
|
||||
Player player = playerManager.create(ctx, packet.getName(), GameMode.SURVIVAL, world.getSpawn());
|
||||
ctx.channel().attr(ServetAttributes.PLAYER).set(player);
|
||||
|
||||
sendLoginSuccess(player);
|
||||
|
||||
sendJoinGame(player);
|
||||
sendSpawnPosition(player);
|
||||
sendPlayerAbilities(player);
|
||||
ctx.flush();
|
||||
|
||||
sendWorldData(player);
|
||||
|
||||
sendPlayerPositionAndLook(player);
|
||||
sendKeepAlive(player);
|
||||
ctx.flush();
|
||||
}
|
||||
|
||||
private void sendLoginSuccess(Player player) {
|
||||
var loginSuccessPacket = new LoginSuccessPacket();
|
||||
loginSuccessPacket.setUuid(player.getUuid());
|
||||
loginSuccessPacket.setName(player.getName());
|
||||
|
||||
player.getCtx().writeAndFlush(loginSuccessPacket);
|
||||
player.getCtx().channel().attr(ProtocolAttributes.STATE).set(State.PLAY);
|
||||
}
|
||||
|
||||
private void sendJoinGame(Player player) {
|
||||
var joinGamePacket = new JoinGamePacket();
|
||||
joinGamePacket.setEntityId(random.nextInt());
|
||||
joinGamePacket.setGameMode(player.getGameMode());
|
||||
joinGamePacket.setDimension(0/*Overworld*/);
|
||||
joinGamePacket.setDifficulty(Difficulty.PEACEFUL);
|
||||
joinGamePacket.setLevelType(world.getLevelType());
|
||||
|
||||
player.getCtx().write(joinGamePacket);
|
||||
}
|
||||
|
||||
private void sendSpawnPosition(Player player) {
|
||||
var spawnPositionPacket = new SpawnPositionPacket();
|
||||
spawnPositionPacket.setSpawn(player.getLocation());
|
||||
|
||||
player.getCtx().write(spawnPositionPacket);
|
||||
}
|
||||
|
||||
private void sendPlayerAbilities(Player player) {
|
||||
var playerAbilitiesPacket = new PlayerAbilitiesPacket();
|
||||
playerAbilitiesPacket.setCatFly(true);
|
||||
playerAbilitiesPacket.setFlying(true);
|
||||
playerAbilitiesPacket.setCreativeMode(false);
|
||||
playerAbilitiesPacket.setInvulnerable(true);
|
||||
playerAbilitiesPacket.setFieldOfView(0.0f);
|
||||
playerAbilitiesPacket.setFlyingSpeed(0.05f);
|
||||
|
||||
player.getCtx().write(playerAbilitiesPacket);
|
||||
}
|
||||
|
||||
@SuppressWarnings("java:S2589")
|
||||
private void sendWorldData(Player player) {
|
||||
Location chunkLocation = LocationUtils.toChunkXZ(player.getLocation());
|
||||
Chunk chunk = world.getChunk((int) chunkLocation.getX(), (int) chunkLocation.getZ());
|
||||
|
||||
var chunkDataPacket = new ChunkDataPacket();
|
||||
chunkDataPacket.setX(chunk.getX());
|
||||
chunkDataPacket.setZ(chunk.getZ());
|
||||
|
||||
player.getCtx().write(chunkDataPacket);
|
||||
|
||||
for (int i = 1; i <= config.getInt("world.view-distance"); i++) {
|
||||
int minX = (int) chunkLocation.getX() - i;
|
||||
int minZ = (int) chunkLocation.getZ() - i;
|
||||
int maxX = (int) chunkLocation.getX() + i;
|
||||
int maxZ = (int) chunkLocation.getZ() + i;
|
||||
for (int z = minZ; z <= maxZ; z++) {
|
||||
for (int x = minX; x <= maxX; x++) {
|
||||
if ((z == minZ || z == maxZ) || (x == minX || x == maxX)) {
|
||||
chunkDataPacket = new ChunkDataPacket();
|
||||
chunkDataPacket.setX(x);
|
||||
chunkDataPacket.setZ(z);
|
||||
player.getCtx().write(chunkDataPacket);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
player.getCtx().flush();
|
||||
}
|
||||
|
||||
private void sendPlayerPositionAndLook(Player player) {
|
||||
var playerPositionAndLookPacket = new SPlayerPositionAndLookPacket();
|
||||
playerPositionAndLookPacket.setPosition(player.getLocation());
|
||||
playerPositionAndLookPacket.setLook(new Look().set(0f, 0f));
|
||||
playerPositionAndLookPacket.setTeleportId(random.nextInt());
|
||||
|
||||
player.getCtx().write(playerPositionAndLookPacket);
|
||||
}
|
||||
|
||||
private void sendKeepAlive(Player player) {
|
||||
var keepAlivePacket = new KeepAlivePacket();
|
||||
keepAlivePacket.setPayload(System.currentTimeMillis());
|
||||
|
||||
player.getCtx().write(keepAlivePacket);
|
||||
}
|
||||
}
|
||||
30
server/src/main/java/mc/server/processor/ProcessorPlay.java
Normal file
30
server/src/main/java/mc/server/processor/ProcessorPlay.java
Normal file
@@ -0,0 +1,30 @@
|
||||
package mc.server.processor;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import mc.protocol.State;
|
||||
import mc.protocol.handler.ProtocolHandlersBus;
|
||||
import mc.protocol.packets.KeepAlivePacket;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Slf4j
|
||||
public class ProcessorPlay implements PacketProcessor {
|
||||
|
||||
@Override
|
||||
public void setup(ProtocolHandlersBus protocolHandlersBus) {
|
||||
protocolHandlersBus
|
||||
.addHandler(State.PLAY, KeepAlivePacket.class, this::keepAlive);
|
||||
}
|
||||
|
||||
private void keepAlive(ChannelHandlerContext ctx, KeepAlivePacket packet) {
|
||||
try {
|
||||
TimeUnit.MILLISECONDS.sleep(50);
|
||||
ctx.writeAndFlush(packet);
|
||||
} catch (InterruptedException e) {
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("{}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package mc.server.processor;
|
||||
|
||||
import com.typesafe.config.Config;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import mc.protocol.ProtocolConstant;
|
||||
import mc.protocol.State;
|
||||
import mc.protocol.handler.ProtocolHandlersBus;
|
||||
import mc.protocol.model.ServerInfo;
|
||||
import mc.protocol.model.ServerInfoSerializer;
|
||||
import mc.protocol.model.text.TextSerializer;
|
||||
import mc.protocol.packets.KeepAlivePacket;
|
||||
import mc.protocol.packets.status.client.StatusServerRequestPacket;
|
||||
import mc.protocol.packets.status.server.StatusServerResponse;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class ProcessorStatus implements PacketProcessor {
|
||||
|
||||
private final Config config;
|
||||
|
||||
@Override
|
||||
public void setup(ProtocolHandlersBus protocolHandlersBus) {
|
||||
protocolHandlersBus
|
||||
.addHandler(State.STATUS, KeepAlivePacket.class, this::keepAlive)
|
||||
.addHandler(State.STATUS, StatusServerRequestPacket.class, this::statusRequest);
|
||||
}
|
||||
|
||||
private void keepAlive(ChannelHandlerContext ctx, KeepAlivePacket packet) {
|
||||
ctx.writeAndFlush(packet);
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
private void statusRequest(ChannelHandlerContext ctx, StatusServerRequestPacket packet) {
|
||||
ServerInfo serverInfo = new ServerInfo();
|
||||
serverInfo.version().name(ProtocolConstant.PROTOCOL_NAME);
|
||||
serverInfo.version().protocol(ProtocolConstant.PROTOCOL_NUMBER);
|
||||
serverInfo.players().max(config.getInt("players.max-online"));
|
||||
serverInfo.players().online(config.getInt("players.fake-online.value"));
|
||||
serverInfo.players().sample(Collections.emptyList());
|
||||
serverInfo.description(TextSerializer.fromPlain(config.getString("motd")));
|
||||
|
||||
StatusServerResponse response = new StatusServerResponse();
|
||||
response.setInfo(ServerInfoSerializer.toStringPlain(serverInfo));
|
||||
|
||||
ctx.writeAndFlush(response);
|
||||
}
|
||||
}
|
||||
12
server/src/main/java/mc/server/util/LocationUtils.java
Normal file
12
server/src/main/java/mc/server/util/LocationUtils.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package mc.server.util;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
import mc.protocol.model.Location;
|
||||
|
||||
@UtilityClass
|
||||
public class LocationUtils {
|
||||
|
||||
public Location toChunkXZ(Location location) {
|
||||
return new Location().set((int) location.getX() >> 4, 0d, (int) location.getZ() >> 4);
|
||||
}
|
||||
}
|
||||
11
server/src/main/java/mc/server/world/VoidChunk.java
Normal file
11
server/src/main/java/mc/server/world/VoidChunk.java
Normal file
@@ -0,0 +1,11 @@
|
||||
package mc.server.world;
|
||||
|
||||
import lombok.Data;
|
||||
import mc.protocol.world.Chunk;
|
||||
|
||||
@Data
|
||||
public class VoidChunk implements Chunk {
|
||||
|
||||
private final int x;
|
||||
private final int z;
|
||||
}
|
||||
26
server/src/main/java/mc/server/world/VoidWorld.java
Normal file
26
server/src/main/java/mc/server/world/VoidWorld.java
Normal file
@@ -0,0 +1,26 @@
|
||||
package mc.server.world;
|
||||
|
||||
import mc.protocol.model.Location;
|
||||
import mc.protocol.utils.LevelType;
|
||||
import mc.protocol.world.Chunk;
|
||||
import mc.protocol.world.World;
|
||||
|
||||
public class VoidWorld implements World {
|
||||
|
||||
private static final Location spawn = new Location().set(7d, 130d, 7d);
|
||||
|
||||
@Override
|
||||
public LevelType getLevelType() {
|
||||
return LevelType.FLAT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Location getSpawn() {
|
||||
return VoidWorld.spawn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Chunk getChunk(int x, int z) {
|
||||
return new VoidChunk(x, z);
|
||||
}
|
||||
}
|
||||
27
server/src/main/resources/config-default.conf
Normal file
27
server/src/main/resources/config-default.conf
Normal file
@@ -0,0 +1,27 @@
|
||||
server {
|
||||
host: "127.0.0.1"
|
||||
port: 25565
|
||||
}
|
||||
|
||||
motd: """&bmc-project &8:: &4ZERO
|
||||
&8develop by &7DmitriyMX"""
|
||||
|
||||
disconnect-reason: "&4Server is not available."
|
||||
|
||||
players {
|
||||
max-online: 0
|
||||
fake-online {
|
||||
enable: false
|
||||
value: 0
|
||||
}
|
||||
}
|
||||
|
||||
# Размер значка: 64x64 px
|
||||
icon {
|
||||
enable: false
|
||||
path: "favicon.png"
|
||||
}
|
||||
|
||||
world {
|
||||
view-distance: 1
|
||||
}
|
||||
23
server/src/main/resources/logback-default.xml
Normal file
23
server/src/main/resources/logback-default.xml
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<statusListener class="ch.qos.logback.core.status.NopStatusListener" />
|
||||
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<layout class="ch.qos.logback.classic.PatternLayout">
|
||||
<Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%35.35logger{34}] -- %msg%n</Pattern>
|
||||
</layout>
|
||||
</appender>
|
||||
|
||||
<root level="info">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
</root>
|
||||
|
||||
<logger name="LAUNCHER" level="debug" additivity="false">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
</logger>
|
||||
|
||||
<!-- раскоментировать для простотра дампа пакетов -->
|
||||
<logger name="io.netty.handler.logging.LoggingHandler" level="debug" additivity="false">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
</logger>
|
||||
</configuration>
|
||||
95
server/src/test/java/mc/server/di/ConfigModuleTest.java
Normal file
95
server/src/test/java/mc/server/di/ConfigModuleTest.java
Normal file
@@ -0,0 +1,95 @@
|
||||
package mc.server.di;
|
||||
|
||||
import com.typesafe.config.Config;
|
||||
import lombok.SneakyThrows;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.net.URL;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class ConfigModuleTest {
|
||||
|
||||
private static final String emptyConfig = "./config-empty.conf";
|
||||
|
||||
/*
|
||||
Проверяем, что загруженный объект конфига является singleton
|
||||
*/
|
||||
@Test
|
||||
void singleton() {
|
||||
ConfigComponent component = DaggerConfigComponent.builder()
|
||||
.configModule(new ConfigModule(emptyConfig, pathResource("/config-1.conf"))).build();
|
||||
Config config1 = component.getConfig();
|
||||
Config config2 = component.getConfig();
|
||||
|
||||
assertEquals(config1, config2);
|
||||
assertSame(config1, config2);
|
||||
}
|
||||
|
||||
/*
|
||||
Корректная загрузка конфига
|
||||
*/
|
||||
@Test
|
||||
void loadConfig() {
|
||||
ConfigComponent component = DaggerConfigComponent.builder()
|
||||
.configModule(new ConfigModule(emptyConfig, pathResource("/config-1.conf"))).build();
|
||||
|
||||
Config config = component.getConfig();
|
||||
assertEquals("value1", config.getString("key1"));
|
||||
assertEquals("value2", config.getString("key2.subkey1"));
|
||||
assertEquals("value3", config.getString("key3.subkey1"));
|
||||
assertEquals("value4", config.getString("\"key4.somename\""));
|
||||
assertEquals("value5", config.getString("key5"));
|
||||
}
|
||||
|
||||
/*
|
||||
Проверка include
|
||||
*/
|
||||
@Test
|
||||
void includeTest() {
|
||||
ConfigComponent component = DaggerConfigComponent.builder()
|
||||
.configModule(new ConfigModule(emptyConfig, pathResource("/config-2.conf"))).build();
|
||||
|
||||
Config config = component.getConfig();
|
||||
assertEquals("value1", config.getString("key1"));
|
||||
assertEquals("value2", config.getString("key2.subkey1"));
|
||||
assertEquals("value3", config.getString("key3.subkey1"));
|
||||
assertEquals("value4", config.getString("\"key4.somename\""));
|
||||
assertEquals("value5", config.getString("key5"));
|
||||
}
|
||||
|
||||
/*
|
||||
Работа с многострочностью
|
||||
*/
|
||||
@Test
|
||||
void multilineTest() {
|
||||
ConfigComponent component = DaggerConfigComponent.builder()
|
||||
.configModule(new ConfigModule(emptyConfig, pathResource("/config-1.conf"))).build();
|
||||
|
||||
Config config = component.getConfig();
|
||||
assertEquals("line1\nline2", config.getString("key6"));
|
||||
}
|
||||
|
||||
/*
|
||||
Проверяем работу merge config
|
||||
*/
|
||||
@Test
|
||||
void mergeConfigTest() {
|
||||
ConfigComponent component = DaggerConfigComponent.builder()
|
||||
.configModule(new ConfigModule("./config-1.conf", pathResource("/config-3.conf"))).build();
|
||||
Config config = component.getConfig();
|
||||
|
||||
assertEquals("value1_merged", config.getString("key1"));
|
||||
assertEquals("value2", config.getString("key2.subkey1"));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private static Path pathResource(String resource) {
|
||||
URL url = ConfigModuleTest.class.getResource(resource);
|
||||
assertNotNull(url);
|
||||
|
||||
return Paths.get(url.toURI());
|
||||
}
|
||||
}
|
||||
15
server/src/test/resources/config-1.conf
Normal file
15
server/src/test/resources/config-1.conf
Normal file
@@ -0,0 +1,15 @@
|
||||
key1: value1
|
||||
|
||||
key2.subkey1: value2
|
||||
|
||||
key3 {
|
||||
subkey1: value3
|
||||
}
|
||||
|
||||
"key4.somename": value4
|
||||
|
||||
variable: value5
|
||||
key5: ${variable}
|
||||
|
||||
key6: """line1
|
||||
line2"""
|
||||
1
server/src/test/resources/config-2.conf
Normal file
1
server/src/test/resources/config-2.conf
Normal file
@@ -0,0 +1 @@
|
||||
include "config-1.conf"
|
||||
1
server/src/test/resources/config-3.conf
Normal file
1
server/src/test/resources/config-3.conf
Normal file
@@ -0,0 +1 @@
|
||||
key1: value1_merged
|
||||
0
server/src/test/resources/config-empty.conf
Normal file
0
server/src/test/resources/config-empty.conf
Normal file
Reference in New Issue
Block a user