refactoring
This commit is contained in:
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
Запуск
|
||||
gradle :server:run --args="--config=config.yml --logconfig==logback.xml"
|
||||
|
||||
Сборка
|
||||
gradle :server:shadowJar
|
||||
*/
|
||||
|
||||
//file:noinspection GrUnresolvedAccess
|
||||
plugins {
|
||||
id 'com.github.johnrengelman.shadow' version '7.0.0'
|
||||
}
|
||||
|
||||
apply from: rootDir.toPath().resolve('logic.gradle').toFile()
|
||||
apply plugin: 'application'
|
||||
|
||||
application {
|
||||
mainClassName = 'mc.server.Main'
|
||||
|
||||
if (project.hasProperty('jvmArgs')) {
|
||||
applicationDefaultJvmArgs = List.of((project.jvmArgs as String).split('\\s+'))
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':protocol')
|
||||
|
||||
implementation libs.logger.logback
|
||||
|
||||
implementation libs.yaml
|
||||
implementation libs.ioutils
|
||||
implementation libs.jopt
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
archiveBaseName.set(jar.archiveBaseName.get())
|
||||
archiveVersion.set(project.version as String)
|
||||
archiveClassifier.set('')
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
# suppress inspection "UnusedProperty" for whole file
|
||||
module.name=server
|
||||
@@ -1,167 +0,0 @@
|
||||
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 joptsimple.OptionParser;
|
||||
import joptsimple.OptionSet;
|
||||
import joptsimple.util.PathConverter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import mc.protocol.State;
|
||||
import mc.protocol.api.Server;
|
||||
import mc.protocol.di.DaggerProtocolComponent;
|
||||
import mc.protocol.di.ProtocolComponent;
|
||||
import mc.protocol.di.ProtocolModule;
|
||||
import mc.protocol.packets.KeepAlivePacket;
|
||||
import mc.protocol.packets.client.HandshakePacket;
|
||||
import mc.protocol.packets.client.LoginStartPacket;
|
||||
import mc.protocol.packets.client.StatusServerRequestPacket;
|
||||
import mc.server.config.Config;
|
||||
import mc.server.di.ConfigModule;
|
||||
import mc.server.di.DaggerServerComponent;
|
||||
import mc.server.di.ServerComponent;
|
||||
import mc.server.service.PlayerManager;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@Slf4j
|
||||
@SuppressWarnings("java:S106")
|
||||
public class Main {
|
||||
private static final String CLI_CONFIG = "config";
|
||||
private static final String CLI_LOGCONFIG = "logconfig";
|
||||
|
||||
private void run(OptionSet optionSet) {
|
||||
log.info("mc-project launch");
|
||||
|
||||
ConfigModule configModule = new ConfigModule((Path) optionSet.valueOf(CLI_CONFIG));
|
||||
|
||||
ServerComponent serverComponent = DaggerServerComponent.builder()
|
||||
.configModule(configModule)
|
||||
.build();
|
||||
|
||||
Config config = serverComponent.getConfig();
|
||||
PlayerManager playerManager = serverComponent.getPlayerManager();
|
||||
|
||||
ProtocolComponent protocolComponent = DaggerProtocolComponent.builder()
|
||||
.protocolModule(new ProtocolModule(true))
|
||||
.build();
|
||||
|
||||
Server server = protocolComponent.getServer();
|
||||
PacketHandler packetHandler = serverComponent.getPacketHandler();
|
||||
|
||||
server.onNewConnect(connectionContext -> connectionContext.setState(State.HANDSHAKING));
|
||||
server.onDisonnect(connectionContext -> {
|
||||
connectionContext.setState(null);
|
||||
connectionContext.getCustomProperty("player", Player.class).ifPresent(playerManager::remove);
|
||||
});
|
||||
|
||||
server.listenPacket(State.HANDSHAKING, HandshakePacket.class, packetHandler::onHandshake);
|
||||
server.listenPacket(State.STATUS, KeepAlivePacket.class, packetHandler::onKeepAlive);
|
||||
server.listenPacket(State.STATUS, StatusServerRequestPacket.class, packetHandler::onServerStatus);
|
||||
server.listenPacket(State.LOGIN, LoginStartPacket.class, packetHandler::onLoginStart);
|
||||
server.listenPacket(State.PLAY, KeepAlivePacket.class, packetHandler::onKeepAlivePlay);
|
||||
|
||||
server.bind(config.server().host(), config.server().port());
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
OptionParser optionParser = createOptionParser();
|
||||
OptionSet optionSet = optionParser.parse(args);
|
||||
|
||||
if (optionSet.has("help")) {
|
||||
try {
|
||||
optionParser.printHelpOn(System.out);
|
||||
} catch (IOException e) {
|
||||
System.err.printf("Can't print help page: %s%n", e.getMessage());
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
return;
|
||||
} else if (optionSet.has("init")) {
|
||||
Path configPath = (Path) optionSet.valueOf(CLI_CONFIG);
|
||||
Path logbackPath = (Path) optionSet.valueOf(CLI_LOGCONFIG);
|
||||
|
||||
if (!initializeCheckFiles(configPath, logbackPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
InputStream configResource = Objects.requireNonNull(Main.class.getResourceAsStream("/config-sample.yml"));
|
||||
InputStream logbackResource = Objects.requireNonNull(Main.class.getResourceAsStream("/logback-sample.xml"));
|
||||
|
||||
try(OutputStream configOut = Files.newOutputStream(configPath);
|
||||
OutputStream logbackOut = Files.newOutputStream(logbackPath)) {
|
||||
IOUtils.copy(configResource, configOut);
|
||||
IOUtils.copy(logbackResource, logbackOut);
|
||||
}
|
||||
|
||||
System.out.println("Initialization environment done.");
|
||||
return;
|
||||
}
|
||||
|
||||
reconfigureLogback(optionSet);
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
optionSet.asMap().forEach((optionSpec, objects) -> {
|
||||
if (optionSpec.isForHelp()) return;
|
||||
log.debug("OptionSet | {} = {}", optionSpec.options(), objects);
|
||||
});
|
||||
}
|
||||
|
||||
new Main().run(optionSet);
|
||||
}
|
||||
|
||||
private static OptionParser createOptionParser() {
|
||||
OptionParser optionParser = new OptionParser();
|
||||
|
||||
optionParser.acceptsAll(List.of("h", "help"), "Help page").forHelp();
|
||||
optionParser.accepts("init", "Initialize environment");
|
||||
|
||||
optionParser.accepts(CLI_CONFIG, "Path to configuration file")
|
||||
.withRequiredArg()
|
||||
.withValuesConvertedBy(new PathConverter())
|
||||
.defaultsTo(Paths.get("config.yml"));
|
||||
|
||||
optionParser.accepts(CLI_LOGCONFIG, "Path to logger configuratuin file")
|
||||
.withRequiredArg()
|
||||
.withValuesConvertedBy(new PathConverter())
|
||||
.defaultsTo(Paths.get("logback.xml"));
|
||||
|
||||
return optionParser;
|
||||
}
|
||||
|
||||
private static boolean initializeCheckFiles(Path... paths) {
|
||||
for (Path path : paths) {
|
||||
if (Files.exists(path)) {
|
||||
System.err.printf("File '%s' already exist. Initialization environment canceled.%n",
|
||||
path.toAbsolutePath());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void reconfigureLogback(OptionSet optionSet) throws IOException {
|
||||
LoggerContext logbackContext = (LoggerContext) LoggerFactory.getILoggerFactory();
|
||||
logbackContext.reset();
|
||||
JoranConfigurator configurator = new JoranConfigurator();
|
||||
|
||||
Path logbackPath = (Path) optionSet.valueOf(CLI_LOGCONFIG);
|
||||
try(InputStream in = Objects.requireNonNull(
|
||||
Files.newInputStream(logbackPath), "File not found: " + logbackPath.toAbsolutePath())) {
|
||||
|
||||
configurator.setContext(logbackContext);
|
||||
configurator.doConfigure(in);
|
||||
} catch (JoranException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,178 +0,0 @@
|
||||
package mc.server;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import mc.protocol.*;
|
||||
import mc.protocol.api.ConnectionContext;
|
||||
import mc.protocol.model.Location;
|
||||
import mc.protocol.model.Look;
|
||||
import mc.protocol.model.ServerInfo;
|
||||
import mc.protocol.packets.KeepAlivePacket;
|
||||
import mc.protocol.packets.client.HandshakePacket;
|
||||
import mc.protocol.packets.client.LoginStartPacket;
|
||||
import mc.protocol.packets.client.StatusServerRequestPacket;
|
||||
import mc.protocol.packets.server.*;
|
||||
import mc.protocol.serializer.TextSerializer;
|
||||
import mc.protocol.utils.Difficulty;
|
||||
import mc.protocol.utils.GameMode;
|
||||
import mc.protocol.world.Chunk;
|
||||
import mc.protocol.world.World;
|
||||
import mc.server.config.Config;
|
||||
import mc.server.service.PlayerManager;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class PacketHandler {
|
||||
|
||||
private final Random random = new Random(System.currentTimeMillis());
|
||||
private final Config config;
|
||||
private final World world;
|
||||
private final PlayerManager playerManager;
|
||||
|
||||
public void onHandshake(ConnectionContext context, HandshakePacket packet) {
|
||||
context.setState(packet.getNextState());
|
||||
}
|
||||
|
||||
public void onKeepAlive(ConnectionContext context, KeepAlivePacket packet) {
|
||||
context.sendNow(packet);
|
||||
context.disconnect();
|
||||
}
|
||||
|
||||
public void onKeepAlivePlay(ConnectionContext context, KeepAlivePacket packet) {
|
||||
try {
|
||||
TimeUnit.MILLISECONDS.sleep(50);
|
||||
context.sendNow(packet);
|
||||
} catch (InterruptedException e) {
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("{}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void onServerStatus(ConnectionContext context, StatusServerRequestPacket packet) {
|
||||
ServerInfo serverInfo = new ServerInfo();
|
||||
serverInfo.version().name(ProtocolConstant.PROTOCOL_NAME);
|
||||
serverInfo.version().protocol(ProtocolConstant.PROTOCOL_NUMBER);
|
||||
serverInfo.players().max(config.players().maxOnlile());
|
||||
if (config.players().fakeOnline().enable()) {
|
||||
serverInfo.players().online(config.players().fakeOnline().value());
|
||||
} else {
|
||||
serverInfo.players().online(playerManager.online());
|
||||
}
|
||||
serverInfo.players().sample(Collections.emptyList());
|
||||
serverInfo.description(TextSerializer.fromPlain(config.motd()));
|
||||
|
||||
if (config.iconPath() != null) {
|
||||
serverInfo.favicon(faviconToBase64(config.iconPath()));
|
||||
}
|
||||
|
||||
StatusServerResponse response = new StatusServerResponse();
|
||||
response.setInfo(serverInfo);
|
||||
|
||||
context.sendNow(response);
|
||||
}
|
||||
|
||||
@SuppressWarnings("java:S2589")
|
||||
public void onLoginStart(ConnectionContext context, LoginStartPacket loginStartPacket) {
|
||||
Player player = playerManager.addAndCreate(context, loginStartPacket.getName(), GameMode.SURVIVAL, world.getSpawn());
|
||||
context.setCustomProperty("player", player);
|
||||
|
||||
var loginSuccessPacket = new LoginSuccessPacket();
|
||||
loginSuccessPacket.setUuid(player.getUuid());
|
||||
loginSuccessPacket.setName(player.getName());
|
||||
|
||||
context.sendNow(loginSuccessPacket);
|
||||
context.setState(State.PLAY);
|
||||
|
||||
var joinGamePacket = new JoinGamePacket();
|
||||
joinGamePacket.setEntityId(random.nextInt());
|
||||
joinGamePacket.setGameMode(player.getGameMode());
|
||||
joinGamePacket.setDimension(0/*Overworld*/);
|
||||
joinGamePacket.setDifficulty(Difficulty.PEACEFUL);
|
||||
joinGamePacket.setLevelType(world.getLevelType());
|
||||
|
||||
context.send(joinGamePacket);
|
||||
|
||||
var spawnPositionPacket = new SpawnPositionPacket();
|
||||
spawnPositionPacket.setSpawn(player.getLocation());
|
||||
|
||||
context.send(spawnPositionPacket);
|
||||
|
||||
var playerAbilitiesPacket = new PlayerAbilitiesPacket();
|
||||
playerAbilitiesPacket.setCatFly(true);
|
||||
playerAbilitiesPacket.setFlying(true);
|
||||
playerAbilitiesPacket.setCreativeMode(false);
|
||||
playerAbilitiesPacket.setInvulnerable(true);
|
||||
playerAbilitiesPacket.setFieldOfView(0.0f);
|
||||
playerAbilitiesPacket.setFlyingSpeed(0.05f);
|
||||
|
||||
context.send(playerAbilitiesPacket);
|
||||
|
||||
context.flushSending();
|
||||
|
||||
Location chunkLocation = player.getLocation().toChunkXZ();
|
||||
Chunk chunk = world.getChunk(chunkLocation.getIntX(), chunkLocation.getIntZ());
|
||||
|
||||
var chunkDataPacket = new ChunkDataPacket();
|
||||
chunkDataPacket.setX(chunk.getX());
|
||||
chunkDataPacket.setZ(chunk.getZ());
|
||||
|
||||
context.send(chunkDataPacket);
|
||||
|
||||
for (int i = 1; i <= config.world().viewDistance(); i++) {
|
||||
int minX = chunkLocation.getIntX() - i;
|
||||
int minZ = chunkLocation.getIntZ() - i;
|
||||
int maxX = chunkLocation.getIntX() + i;
|
||||
int maxZ = chunkLocation.getIntZ() + 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);
|
||||
|
||||
context.send(chunkDataPacket);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context.flushSending();
|
||||
|
||||
var playerPositionAndLookPacket = new SPlayerPositionAndLookPacket();
|
||||
playerPositionAndLookPacket.setPosition(player.getLocation());
|
||||
playerPositionAndLookPacket.setLook(new Look(0f, 0f));
|
||||
playerPositionAndLookPacket.setTeleportId(random.nextInt());
|
||||
|
||||
context.send(playerPositionAndLookPacket);
|
||||
|
||||
KeepAlivePacket keepAlivePacket = new KeepAlivePacket();
|
||||
keepAlivePacket.setPayload(System.currentTimeMillis());
|
||||
|
||||
context.send(keepAlivePacket);
|
||||
|
||||
context.flushSending();
|
||||
}
|
||||
|
||||
private static String faviconToBase64(Path iconPath) {
|
||||
try {
|
||||
return "data:image/png;base64," +
|
||||
Base64.getEncoder().encodeToString(
|
||||
IOUtils.toByteArray(Files.newInputStream(iconPath)));
|
||||
} catch (IOException e) {
|
||||
log.error("Can't read icon '{}'", iconPath.toAbsolutePath(), e);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package mc.server;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import mc.protocol.api.ConnectionContext;
|
||||
import mc.protocol.model.Location;
|
||||
import mc.protocol.utils.GameMode;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public class Player {
|
||||
|
||||
private final ConnectionContext connectionContext;
|
||||
private final UUID uuid;
|
||||
private final String name;
|
||||
private final GameMode gameMode;
|
||||
private final Location location;
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package mc.server.di;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import mc.protocol.world.World;
|
||||
import mc.server.PacketHandler;
|
||||
import mc.server.config.Config;
|
||||
import mc.server.service.PlayerManager;
|
||||
|
||||
@Module
|
||||
public class PacketHandlerModule {
|
||||
|
||||
@Provides
|
||||
public PacketHandler providePacketHandler(Config config, World world, PlayerManager playerManager) {
|
||||
return new PacketHandler(config, world, playerManager);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package mc.server.di;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import mc.protocol.di.ServerScope;
|
||||
import mc.server.service.PlayerManager;
|
||||
|
||||
@Module
|
||||
public class PlayersModule {
|
||||
|
||||
@Provides
|
||||
@ServerScope
|
||||
PlayerManager providePlayerManager() {
|
||||
return new PlayerManager();
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package mc.server.di;
|
||||
|
||||
import dagger.Component;
|
||||
import mc.protocol.di.ServerScope;
|
||||
import mc.server.PacketHandler;
|
||||
import mc.server.config.Config;
|
||||
import mc.server.service.PlayerManager;
|
||||
|
||||
@Component(modules = {
|
||||
ConfigModule.class, PacketHandlerModule.class, WorldModule.class, PlayersModule.class
|
||||
})
|
||||
@ServerScope
|
||||
public interface ServerComponent {
|
||||
|
||||
Config getConfig();
|
||||
PacketHandler getPacketHandler();
|
||||
PlayerManager getPlayerManager();
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package mc.server.di;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import mc.protocol.di.ServerScope;
|
||||
import mc.protocol.world.World;
|
||||
import mc.server.world.VoidWorld;
|
||||
|
||||
@Module
|
||||
public class WorldModule {
|
||||
|
||||
@Provides
|
||||
@ServerScope
|
||||
public World provideWorld() {
|
||||
return new VoidWorld();
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package mc.server.service;
|
||||
|
||||
import mc.protocol.api.ConnectionContext;
|
||||
import mc.protocol.model.Location;
|
||||
import mc.protocol.utils.GameMode;
|
||||
import mc.server.Player;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.UUID;
|
||||
|
||||
public class PlayerManager {
|
||||
|
||||
private final LinkedList<Player> players = new LinkedList<>();
|
||||
|
||||
public Player addAndCreate(ConnectionContext context, String name, GameMode gameMode, Location location) {
|
||||
context.setUsedContext(true);
|
||||
Player player = new Player(context, UUID.randomUUID(), name, gameMode, location);
|
||||
players.add(player);
|
||||
return player;
|
||||
}
|
||||
|
||||
public void remove(Player player) {
|
||||
players.remove(player);
|
||||
}
|
||||
|
||||
public int online() {
|
||||
return players.size();
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package mc.server.world;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import mc.protocol.world.Chunk;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public class VoidChunk implements Chunk {
|
||||
|
||||
private final int x;
|
||||
private final int z;
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
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(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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user