Archived
0

refactoring

This commit is contained in:
2021-06-17 15:06:10 +03:00
parent e7b5120661
commit e7f7b9654e
38 changed files with 252 additions and 961 deletions

View File

@@ -3,19 +3,24 @@ package mc.protocol.handler.codec;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import mc.protocol.ProtocolAttributes;
import mc.protocol.State;
import mc.protocol.buffer.NetByteBuf;
import mc.protocol.packets.ClientSidePacket;
import mc.protocol.packets.UnknownPacket;
import mc.protocol.pool.PacketObjectPool;
import java.util.List;
import java.util.Objects;
@Slf4j
@RequiredArgsConstructor
public class ProtocolDecoder extends ByteToMessageDecoder {
private final boolean readUnknownPackets;
@Override
public void channelActive(ChannelHandlerContext ctx) {
ctx.channel().attr(ProtocolAttributes.STATE).set(State.HANDSHAKING);
@@ -30,7 +35,17 @@ public class ProtocolDecoder extends ByteToMessageDecoder {
Class<? extends ClientSidePacket> packetClass = state.getClientSidePacketById(packetId);
if (packetClass == null) {
log.warn("Unknown packet: State {} ; Id 0x{}", state, packetIdAsHexcode(packetId));
netByteBuf.skipBytes(netByteBuf.readableBytes());
if (readUnknownPackets) {
UnknownPacket unknownPacket = new UnknownPacket();
unknownPacket.setState(state);
unknownPacket.setId(packetId);
unknownPacket.setDataSize(netByteBuf.readableBytes());
unknownPacket.readSelf(netByteBuf);
out.add(unknownPacket);
} else {
netByteBuf.skipBytes(netByteBuf.readableBytes());
}
} else {
ClientSidePacket packet = PacketObjectPool.getInstance().getPool(packetClass).borrowObject();
packet.readSelf(netByteBuf);

View File

@@ -4,7 +4,7 @@ import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import mc.protocol.State;
import mc.protocol.io.NetByteBuf;
import mc.protocol.buffer.NetByteBuf;
@NoArgsConstructor
@Data

View File

@@ -1,9 +0,0 @@
apply from: rootDir.toPath().resolve('logic.gradle').toFile()
dependencies {
implementation libs.netty
implementation libs.json
implementation libs.objpool
testImplementation libs.lang3
}

View File

@@ -1,2 +0,0 @@
# suppress inspection "UnusedProperty" for whole file
module.name=protocol

View File

@@ -1,85 +0,0 @@
package mc.protocol;
import io.netty.channel.ChannelHandlerContext;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import mc.protocol.api.ConnectionContext;
import mc.protocol.packets.ServerSidePacket;
import mc.protocol.pool.Passivable;
import java.util.Map;
import java.util.Optional;
@EqualsAndHashCode
public class NettyConnectionContext implements ConnectionContext, Passivable {
@Accessors(chain = true)
@Setter
private ChannelHandlerContext ctx;
/**
* @deprecated костыль
*/
@Deprecated
@Getter
@Setter
private boolean usedContext;
@Override
public State getState() {
return ctx.channel().attr(NetworkAttributes.STATE).get();
}
@Override
public void setState(State state) {
ctx.channel().attr(NetworkAttributes.STATE).set(state);
}
/**
* @deprecated костыль
*/
@Deprecated
@Override
public <T> void setCustomProperty(String key, T value) {
Map<String, Object> map = ctx.channel().attr(NetworkAttributes.CUSTOM_PROPERTIES).get();
map.put(key, value);
}
/**
* @deprecated костыль
*/
@Deprecated
@SuppressWarnings("unchecked")
@Override
public <T> Optional<T> getCustomProperty(String key, Class<T> classResult) {
Map<String, Object> map = ctx.channel().attr(NetworkAttributes.CUSTOM_PROPERTIES).get();
return (Optional<T>) Optional.ofNullable(map.getOrDefault(key, null));
}
@Override
public void send(ServerSidePacket packet) {
ctx.write(packet);
}
@Override
public void sendNow(ServerSidePacket packet) {
ctx.writeAndFlush(packet);
}
@Override
public void flushSending() {
ctx.flush();
}
@Override
public void disconnect() {
ctx.disconnect();
}
@Override
public void passivate() {
this.ctx = null;
}
}

View File

@@ -1,103 +0,0 @@
package mc.protocol;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
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.api.ConnectionContext;
import mc.protocol.api.Server;
import mc.protocol.event.EventBus;
import mc.protocol.io.codec.ProtocolDecoder;
import mc.protocol.io.codec.ProtocolEncoder;
import mc.protocol.io.codec.ProtocolSplitter;
import mc.protocol.packets.ClientSidePacket;
import javax.annotation.Nonnull;
import javax.inject.Provider;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Consumer;
@Slf4j
@RequiredArgsConstructor
public class NettyServer implements Server {
private final Provider<ProtocolDecoder> protocolDecoderProvider;
private final Provider<PacketInboundHandler> packetInboundHandlerProvider;
private final EventBus eventBus;
private Consumer<ConnectionContext> consumerNewConnection;
private Consumer<ConnectionContext> consumerDisconnect;
@Override
public void bind(String host, int port) {
log.info("Network starting: {}:{}", host, port);
try {
createServerBootstrap().bind(host, port).sync().channel().closeFuture().sync();
} catch (InterruptedException e) {
if (log.isTraceEnabled()) {
log.trace("{}: {}", e.getClass().getSimpleName(), e.getMessage(), e);
}
}
}
@Override
public void onNewConnect(Consumer<ConnectionContext> consumer) {
this.consumerNewConnection = consumer;
}
@Override
public void onDisonnect(Consumer<ConnectionContext> consumer) {
this.consumerDisconnect = consumer;
}
@Override
@SuppressWarnings("java:S2326") // Сонар, ты бредишь
public <P extends ClientSidePacket> void listenPacket(State state, Class<P> packetClass, EventBus.EventHandler<P> eventHandler) {
this.eventBus.subscribe(state, packetClass, eventHandler);
}
private ServerBootstrap createServerBootstrap() {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(new NioEventLoopGroup(1), new NioEventLoopGroup())
.channel(NioServerSocketChannel.class)
.childHandler(createChannelChannelInitializer());
return bootstrap;
}
private ChannelInitializer<SocketChannel> createChannelChannelInitializer() {
return new ChannelInitializer<>() {
@Override
protected void initChannel(@Nonnull SocketChannel socketChannel) {
ChannelPipeline pipeline = socketChannel.pipeline();
createChannelHandlerMap().forEach(pipeline::addLast);
}
};
}
private Map<String, ChannelHandler> createChannelHandlerMap() {
Map<String, ChannelHandler> map = new LinkedHashMap<>();
map.put("packet_splitter", new ProtocolSplitter());
map.put("logger", new LoggingHandler(LogLevel.DEBUG));
ProtocolDecoder protocolDecoder = protocolDecoderProvider.get();
protocolDecoder.setConsumerNewConnection(consumerNewConnection);
protocolDecoder.setConsumerDisconnect(consumerDisconnect);
map.put("packet_decoder", protocolDecoder);
map.put("packet_encoder", new ProtocolEncoder());
map.put("packet_handler", packetInboundHandlerProvider.get());
return map;
}
}

View File

@@ -1,42 +0,0 @@
package mc.protocol.api;
import mc.protocol.State;
import mc.protocol.packets.ServerSidePacket;
import java.util.Optional;
public interface ConnectionContext {
/**
* @deprecated костыль
*/
@Deprecated
void setUsedContext(boolean value);
/**
* @deprecated костыль
*/
@Deprecated
boolean isUsedContext();
State getState();
void setState(State state);
/**
* @deprecated костыль
*/
@Deprecated
<T> void setCustomProperty(String key, T value);
/**
* @deprecated костыль
*/
@Deprecated
<T> Optional<T> getCustomProperty(String key, Class<T> classResult);
void send(ServerSidePacket packet);
void sendNow(ServerSidePacket packet);
void flushSending();
void disconnect();
}

View File

@@ -1,18 +0,0 @@
package mc.protocol.api;
import mc.protocol.State;
import mc.protocol.packets.ClientSidePacket;
import mc.protocol.event.EventBus;
import java.util.function.Consumer;
public interface Server {
void bind(String host, int port);
void onNewConnect(Consumer<ConnectionContext> consumer);
void onDisonnect(Consumer<ConnectionContext> consumer);
@SuppressWarnings("java:S2326") // Сонар, ты бредишь
<P extends ClientSidePacket> void listenPacket(State state, Class<P> packetClass, EventBus.EventHandler<P> eventHandler);
}

View File

@@ -1,42 +0,0 @@
package mc.protocol.di;
import dagger.Module;
import dagger.Provides;
import mc.protocol.NettyConnectionContext;
import mc.protocol.State;
import mc.protocol.packets.ClientSidePacket;
import mc.protocol.packets.UnknownPacket;
import mc.protocol.pool.NettyConnectionContextFactory;
import mc.protocol.pool.PacketFactory;
import mc.protocol.pool.PacketPool;
import org.apache.commons.pool2.ObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPool;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Module
public class PoolModule {
@Provides
@ServerScope
@SuppressWarnings({ "rawtypes", "unchecked" })
PacketPool providePacketPool() {
Map<Class<? extends ClientSidePacket>, ObjectPool> map = Stream.of(State.values())
.flatMap(state -> state.getClientSidePackets().values().stream())
.distinct()
.collect(Collectors.toMap(
packetClass -> packetClass,
packetClass -> new GenericObjectPool(new PacketFactory<>(packetClass))));
map.put(UnknownPacket.class, new GenericObjectPool(new PacketFactory<>(UnknownPacket.class)));
return new PacketPool(map);
}
@Provides
@ServerScope
ObjectPool<NettyConnectionContext> providePoolNettyConnectionContext() {
return new GenericObjectPool<>(new NettyConnectionContextFactory());
}
}

View File

@@ -1,14 +0,0 @@
package mc.protocol.di;
import dagger.Component;
import mc.protocol.api.Server;
@Component(modules = {
ProtocolModule.class,
PoolModule.class
})
@ServerScope
public interface ProtocolComponent {
Server getServer();
}

View File

@@ -1,57 +0,0 @@
package mc.protocol.di;
import dagger.Module;
import dagger.Provides;
import lombok.RequiredArgsConstructor;
import mc.protocol.NettyConnectionContext;
import mc.protocol.NettyServer;
import mc.protocol.PacketInboundHandler;
import mc.protocol.api.Server;
import mc.protocol.event.EventBus;
import mc.protocol.event.SimpleEventBus;
import mc.protocol.io.codec.ProtocolDecoder;
import mc.protocol.pool.PacketPool;
import org.apache.commons.pool2.ObjectPool;
import javax.inject.Provider;
@Module
@RequiredArgsConstructor
public class ProtocolModule {
private final boolean readUnknownPackets;
@Provides
@ServerScope
Server provideServer(
Provider<ProtocolDecoder> protocolDecoderProvider,
Provider<PacketInboundHandler> packetInboundHandlerProvider,
EventBus eventBus
) {
return new NettyServer(protocolDecoderProvider, packetInboundHandlerProvider, eventBus);
}
@Provides
ProtocolDecoder provideProtocolDecoder(
ObjectPool<NettyConnectionContext> poolNettyConnectionContext,
PacketPool poolPackets
) {
return new ProtocolDecoder(readUnknownPackets, poolNettyConnectionContext, poolPackets);
}
@Provides
PacketInboundHandler providePacketInboundHandler(
ObjectPool<NettyConnectionContext> poolNettyConnectionContext,
PacketPool packetPool,
EventBus eventBus
) {
return new PacketInboundHandler(poolNettyConnectionContext, packetPool, eventBus);
}
@Provides
@ServerScope
EventBus provideEventBus() {
return new SimpleEventBus();
}
}

View File

@@ -1,10 +0,0 @@
package mc.protocol.di;
import javax.inject.Scope;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ServerScope {
}

View File

@@ -1,17 +0,0 @@
package mc.protocol.event;
import mc.protocol.State;
import mc.protocol.api.ConnectionContext;
import mc.protocol.packets.ClientSidePacket;
public interface EventBus {
<P extends ClientSidePacket> void subscribe(State state, Class<P> packetClass, EventHandler<P> eventHandler);
<P extends ClientSidePacket> void emit(State state, ConnectionContext channelContext, P packet);
@FunctionalInterface
interface EventHandler<P extends ClientSidePacket> {
void handle(ConnectionContext channelContext, P packet);
}
}

View File

@@ -1,26 +0,0 @@
package mc.protocol.event;
import mc.protocol.State;
import mc.protocol.api.ConnectionContext;
import mc.protocol.packets.ClientSidePacket;
import mc.protocol.utils.Table;
@SuppressWarnings({ "rawtypes", "unchecked" })
public class SimpleEventBus implements EventBus {
private final Table<State, Class<? extends ClientSidePacket>, EventHandler> table = new Table<>();
@Override
public <P extends ClientSidePacket> void subscribe(State state, Class<P> packetClass, EventHandler<P> eventHandler) {
table.put(state, packetClass, eventHandler);
}
@Override
public <P extends ClientSidePacket> void emit(State state, ConnectionContext channelContext, P packet) {
EventHandler eventHandler = table.getColumnAndRow(state, packet.getClass());
if (eventHandler != null) {
eventHandler.handle(channelContext, packet);
}
}
}

View File

@@ -1,24 +0,0 @@
package mc.protocol.pool;
import mc.protocol.NettyConnectionContext;
import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
public class NettyConnectionContextFactory extends BasePooledObjectFactory<NettyConnectionContext> {
@Override
public NettyConnectionContext create() throws Exception {
return new NettyConnectionContext();
}
@Override
public PooledObject<NettyConnectionContext> wrap(NettyConnectionContext context) {
return new DefaultPooledObject<>(context);
}
@Override
public void passivateObject(PooledObject<NettyConnectionContext> pooledObj) {
pooledObj.getObject().passivate();
}
}

View File

@@ -1,24 +0,0 @@
package mc.protocol.model.text;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
class TextTest {
@Test
void contentTest() {
Text actual;
Text expected;
actual = Text.builder().append("123").build();
expected = new Text(null, null, "123", null);
assertEquals(expected, actual);
actual = Text.builder().append("123").append(Text.of("456")).build();
expected = new Text(null, null, "123", List.of(Text.of("456")));
assertEquals(expected, actual);
}
}

View File

@@ -1,40 +0,0 @@
package mc.protocol.serializer;
import mc.protocol.model.text.Text;
import mc.protocol.model.text.TextColor;
import mc.protocol.model.text.TextStyle;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
class TextSerializerTest {
@ParameterizedTest
@MethodSource("paramsPlain")
void fromPlain(String sample, Text expected) {
Text actual = TextSerializer.fromPlain(sample);
assertEquals(expected, actual);
}
@SuppressWarnings("unused")
static Stream<Arguments> paramsPlain() {
return Stream.of(
Arguments.of("text", Text.of("text")),
Arguments.of("&&text", Text.of("&text")),
Arguments.of("&ztext", Text.of("text")),
Arguments.of("&4red_text", Text.of(TextColor.DARK_RED, "red_text")),
Arguments.of("&l&4red_text", Text.of(TextColor.DARK_RED, TextStyle.BOLD, "red_text")),
Arguments.of("&4&lred_text", Text.of(TextColor.DARK_RED, TextStyle.BOLD, "red_text")),
Arguments.of("&4red_text &eyellow_text", Text.builder()
.color(TextColor.DARK_RED)
.append("red_text ")
.append(Text.of(TextColor.YELLOW, "yellow_text"))
.build())
);
}
}

View File

@@ -44,7 +44,7 @@ public class NettyServer {
socketChannel.pipeline()
.addLast("packet_splitter", new ProtocolSplitter())
.addLast("logger", new LoggingHandler(LogLevel.DEBUG))
.addLast("packet_decoder", new ProtocolDecoder())
.addLast("packet_decoder", new ProtocolDecoder(false))
.addLast("packet_encoder", new ProtocolEncoder())
.addLast("packet_handler", new ProtocolInboundHandler(protocolHandlersBus));
}

View File

@@ -1,8 +1,8 @@
package mc.server;
import io.netty.channel.ChannelHandlerContext;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import mc.protocol.api.ConnectionContext;
import mc.protocol.model.Location;
import mc.protocol.utils.GameMode;
@@ -12,7 +12,7 @@ import java.util.UUID;
@Getter
public class Player {
private final ConnectionContext connectionContext;
private final ChannelHandlerContext ctx;
private final UUID uuid;
private final String name;
private final GameMode gameMode;

View File

@@ -1,9 +1,8 @@
package mc.server.service;
package mc.server;
import mc.protocol.api.ConnectionContext;
import io.netty.channel.ChannelHandlerContext;
import mc.protocol.model.Location;
import mc.protocol.utils.GameMode;
import mc.server.Player;
import java.util.LinkedList;
import java.util.UUID;
@@ -12,9 +11,8 @@ 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);
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;
}

View 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");
}

View File

@@ -2,14 +2,15 @@ package mc.server.di;
import dagger.Module;
import dagger.Provides;
import mc.protocol.di.ServerScope;
import mc.server.service.PlayerManager;
import mc.server.PlayerManager;
import javax.inject.Singleton;
@Module
public class PlayersModule {
public class PlayerManagerModule {
@Provides
@ServerScope
@Singleton
PlayerManager providePlayerManager() {
return new PlayerManager();
}

View File

@@ -5,11 +5,12 @@ import dagger.Module;
import dagger.Provides;
import dagger.multibindings.IntoSet;
import lombok.RequiredArgsConstructor;
import mc.server.processor.PacketProcessor;
import mc.server.processor.ProcessorHandshake;
import mc.server.processor.ProcessorStatus;
import mc.protocol.world.World;
import mc.server.PlayerManager;
import mc.server.processor.*;
import javax.inject.Singleton;
import java.util.Random;
@Module
@RequiredArgsConstructor
@@ -30,4 +31,18 @@ public class ProcessorModule {
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();
}
}

View File

@@ -8,7 +8,10 @@ import mc.server.processor.PacketProcessor;
import javax.inject.Singleton;
import java.util.Set;
@Component(modules = {ServerModule.class, ProcessorModule.class})
@Component(modules = {
ServerModule.class, ProcessorModule.class, PlayerManagerModule.class,
WorldModule.class
})
@Singleton
public interface ServerComponent {

View File

@@ -2,16 +2,17 @@ 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;
import javax.inject.Singleton;
@Module
public class WorldModule {
@Provides
@ServerScope
public World provideWorld() {
@Singleton
World provideWorld() {
return new VoidWorld();
}
}

View 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);
}
}

View 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);
}
}
}
}

View 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);
}
}

View File

@@ -1,11 +1,9 @@
package mc.server.world;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Data;
import mc.protocol.world.Chunk;
@RequiredArgsConstructor
@Getter
@Data
public class VoidChunk implements Chunk {
private final int x;

View File

@@ -7,7 +7,7 @@ import mc.protocol.world.World;
public class VoidWorld implements World {
private static final Location spawn = new Location(7d, 130d, 7d);
private static final Location spawn = new Location().set(7d, 130d, 7d);
@Override
public LevelType getLevelType() {

View File

@@ -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('')
}

View File

@@ -1,2 +0,0 @@
# suppress inspection "UnusedProperty" for whole file
module.name=server

View File

@@ -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);
}
}
}

View File

@@ -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 "";
}
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}