Archived
0

42 Commits

Author SHA1 Message Date
c705a2090d Scoreboard works 2021-05-08 17:58:15 +03:00
6dbf785d22 ScoreboardUpdateScorePacket 2021-05-08 17:01:32 +03:00
050130be60 ScoreboardObjectivePacket 2021-05-08 15:36:10 +03:00
9ccb9a6221 TeamsPacket 2021-05-08 04:48:28 +03:00
fda80d9f9d ScoreboardDisplayPacket 2021-05-06 17:05:20 +03:00
2521860bb4 fix DI 2021-05-06 14:55:14 +03:00
091b5adb91 Merge branch 'dev/network-api' into develop 2021-05-06 14:44:40 +03:00
4c20c7fd02 рефакторинг DI 2021-05-06 14:43:56 +03:00
0aaf17b17f добавлен пул для NettyConnectionContext 2021-05-06 14:21:24 +03:00
d02a80299f рефакторинг протокола 2021-05-06 13:58:05 +03:00
a3eb0eba86 рефакторинг EventBus 2021-05-06 13:50:40 +03:00
de43210747 рефакторинг протокола 2021-05-06 13:43:01 +03:00
39996f9847 Merge branch 'dev/event-bus' into dev/network-api
# Conflicts:
#	protocol/build.gradle
#	protocol/src/main/java/mc/protocol/NettyServer.java
#	protocol/src/main/java/mc/protocol/PacketInboundHandler.java
#	protocol/src/main/java/mc/protocol/State.java
#	protocol/src/main/java/mc/protocol/di/ProtocolModule.java
#	server/src/main/java/mc/server/Main.java
2021-05-06 13:42:01 +03:00
9b183b7d8d Merge branch 'dev/object-pool' into dev/network-api
# Conflicts:
#	protocol/src/main/java/mc/protocol/PacketInboundHandler.java
#	protocol/src/main/java/mc/protocol/di/ProtocolModule.java
#	protocol/src/main/java/mc/protocol/io/codec/ProtocolDecoder.java
2021-05-06 13:30:41 +03:00
5f431ff138 рефакторинг протокола 2021-05-06 13:14:42 +03:00
c4a6a01908 убираем reactor 2021-05-05 20:43:09 +03:00
205e813fc4 простая реализация EventBus 2021-05-05 20:09:49 +03:00
0f1c9bfb1b интерфейс EventBus 2021-05-05 20:09:32 +03:00
b77d6b16e8 повторное использование объектов Packet 2021-05-04 18:49:24 +03:00
87dc18f009 fix PLAY:KeepAlive 2021-05-03 17:10:26 +03:00
7d4c6e383e пересмотр событийной модели 2021-05-03 17:09:46 +03:00
de27654e67 check send packet 2021-05-03 17:07:51 +03:00
627cee9af3 fix 2021-05-03 17:07:22 +03:00
31059a4ad8 fix ChunkDataPacket 2021-05-03 16:06:53 +03:00
5833aab62a немного привёл код в порядок 2021-05-03 15:34:09 +03:00
052593bc14 PlayerLookPacket 2021-05-03 00:46:40 +03:00
531b0b97c1 PlayerPositionPacket 2021-05-03 00:46:40 +03:00
1a4600bdc9 fix NPE 2021-05-03 00:46:40 +03:00
b768ba5bd9 отправка пустого чанка 2021-05-03 00:46:40 +03:00
18a857193f debug: log send packet 2021-05-03 00:46:40 +03:00
a1a629279c debug: packet id as hex 2021-05-03 00:46:40 +03:00
824fdf9569 CPlayerPositionAndLookPacket 2021-05-03 00:46:39 +03:00
0835683294 TeleportConfirmPacket 2021-05-03 00:46:39 +03:00
2bd7fe9841 PlayerPositionAndLookPacket 2021-05-03 00:46:39 +03:00
23fd4e2c1a PlayerAbilitiesPacket 2021-05-03 00:46:39 +03:00
e5856b3d11 SpawnPositionPacket 2021-05-02 18:50:09 +03:00
17189effca PluginMessagePacket 2021-05-02 18:20:07 +03:00
bbeb41dd7e ClientSettingsPacket 2021-05-02 17:14:54 +03:00
3b3a80ca0a JoinGamePacket 2021-05-02 16:25:31 +03:00
a317e3e2c2 начало входа на сервер 2021-05-02 16:24:47 +03:00
19c1666c2e перенос обработчиков пакетов в отдельный класс 2021-05-02 15:05:27 +03:00
3282a3e9c0 next version 2021-05-01 16:57:27 +03:00
63 changed files with 1900 additions and 200 deletions

View File

@@ -1,3 +1,3 @@
project.group=mc-project project.group=mc-project
project.name=mc-server project.name=mc-server
project.version=1.0 project.version=1.1-SNAPSHOT

View File

@@ -19,7 +19,8 @@ ext {
yaml : 'org.yaml:snakeyaml:1.28', yaml : 'org.yaml:snakeyaml:1.28',
json : 'com.eclipsesource.minimal-json:minimal-json:0.9.5', json : 'com.eclipsesource.minimal-json:minimal-json:0.9.5',
ioutils : 'commons-io:commons-io:2.6', ioutils : 'commons-io:commons-io:2.6',
jopt : 'net.sf.jopt-simple:jopt-simple:6.0-alpha-3' jopt : 'net.sf.jopt-simple:jopt-simple:6.0-alpha-3',
objpool : 'org.apache.commons:commons-pool2:2.9.0'
] ]
libs.logger = [ libs.logger = [

View File

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

View File

@@ -1,20 +0,0 @@
package mc.protocol;
import io.netty.channel.ChannelHandlerContext;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import mc.protocol.packets.Packet;
@RequiredArgsConstructor
public class ChannelContext<P extends Packet> {
@Getter
private final ChannelHandlerContext ctx;
@Getter
private final P packet;
public void setState(State state) {
ctx.channel().attr(NetworkAttributes.STATE).set(state);
}
}

View File

@@ -0,0 +1,52 @@
package mc.protocol;
import io.netty.channel.ChannelHandlerContext;
import lombok.EqualsAndHashCode;
import lombok.Setter;
import lombok.experimental.Accessors;
import mc.protocol.api.ConnectionContext;
import mc.protocol.packets.ServerSidePacket;
import mc.protocol.pool.Passivable;
@EqualsAndHashCode
public class NettyConnectionContext implements ConnectionContext, Passivable {
@Accessors(chain = true)
@Setter
private ChannelHandlerContext ctx;
@Override
public State getState() {
return ctx.channel().attr(NetworkAttributes.STATE).get();
}
@Override
public void setState(State state) {
ctx.channel().attr(NetworkAttributes.STATE).set(state);
}
@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,29 +1,46 @@
package mc.protocol; package mc.protocol;
import io.netty.bootstrap.ServerBootstrap; 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.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import mc.protocol.di.DaggerProtocolComponent; import mc.protocol.api.ConnectionContext;
import mc.protocol.di.ProtocolComponent; import mc.protocol.api.Server;
import mc.protocol.packets.Packet; import mc.protocol.event.EventBus;
import reactor.core.publisher.Flux; import mc.protocol.io.codec.ProtocolDecoder;
import reactor.core.publisher.Sinks; 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.Map;
import java.util.function.Consumer;
@SuppressWarnings("rawtypes")
@Slf4j @Slf4j
@RequiredArgsConstructor @RequiredArgsConstructor
public class NettyServer { public class NettyServer implements Server {
private final ServerBootstrap serverBootstrap; private final Provider<ProtocolDecoder> protocolDecoderProvider;
private final Map<Class<? extends Packet>, Sinks.Many<ChannelContext>> observedMap; 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) { public void bind(String host, int port) {
log.info("Network starting: {}:{}", host, port); log.info("Network starting: {}:{}", host, port);
try { try {
serverBootstrap.bind(host, port).sync().channel().closeFuture().sync(); createServerBootstrap().bind(host, port).sync().channel().closeFuture().sync();
} catch (InterruptedException e) { } catch (InterruptedException e) {
if (log.isTraceEnabled()) { if (log.isTraceEnabled()) {
log.trace("{}: {}", e.getClass().getSimpleName(), e.getMessage(), e); log.trace("{}: {}", e.getClass().getSimpleName(), e.getMessage(), e);
@@ -31,13 +48,56 @@ public class NettyServer {
} }
} }
@SuppressWarnings("unchecked") @Override
public <P extends Packet> Flux<ChannelContext<P>> packetFlux(Class<P> packetClass) { public void onNewConnect(Consumer<ConnectionContext> consumer) {
return observedMap.get(packetClass).asFlux().map(ChannelContext.class::cast); this.consumerNewConnection = consumer;
} }
public static NettyServer createServer() { @Override
ProtocolComponent component = DaggerProtocolComponent.create(); public void onDisonnect(Consumer<ConnectionContext> consumer) {
return component.getNettyServer(); 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

@@ -3,19 +3,26 @@ package mc.protocol;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.SimpleChannelInboundHandler;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import mc.protocol.packets.Packet; import mc.protocol.packets.ClientSidePacket;
import reactor.core.publisher.Sinks; import mc.protocol.event.EventBus;
import mc.protocol.pool.PacketPool;
import org.apache.commons.pool2.ObjectPool;
import java.util.Map;
@SuppressWarnings("rawtypes")
@RequiredArgsConstructor @RequiredArgsConstructor
public class PacketInboundHandler extends SimpleChannelInboundHandler<Packet> { public class PacketInboundHandler extends SimpleChannelInboundHandler<ClientSidePacket> {
private final Map<Class<? extends Packet>, Sinks.Many<ChannelContext>> observedMap; private final ObjectPool<NettyConnectionContext> poolNettyConnectionContext;
private final PacketPool poolPackets;
private final EventBus eventBus;
@Override @Override
protected void channelRead0(ChannelHandlerContext ctx, Packet packet) { protected void channelRead0(ChannelHandlerContext ctx, ClientSidePacket packet) throws Exception {
observedMap.get(packet.getClass()).tryEmitNext(new ChannelContext<>(ctx, packet)); State state = ctx.channel().attr(NetworkAttributes.STATE).get();
NettyConnectionContext context = poolNettyConnectionContext.borrowObject().setCtx(ctx);
eventBus.emit(state, context, packet);
poolNettyConnectionContext.returnObject(context);
poolPackets.returnObject(packet);
} }
} }

View File

@@ -6,11 +6,8 @@ import mc.protocol.packets.ClientSidePacket;
import mc.protocol.packets.Packet; import mc.protocol.packets.Packet;
import mc.protocol.packets.PingPacket; import mc.protocol.packets.PingPacket;
import mc.protocol.packets.ServerSidePacket; import mc.protocol.packets.ServerSidePacket;
import mc.protocol.packets.client.HandshakePacket; import mc.protocol.packets.client.*;
import mc.protocol.packets.client.LoginStartPacket; import mc.protocol.packets.server.*;
import mc.protocol.packets.client.StatusServerRequestPacket;
import mc.protocol.packets.server.DisconnectPacket;
import mc.protocol.packets.server.StatusServerResponse;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.Collections; import java.util.Collections;
@@ -39,7 +36,35 @@ public enum State {
// server bound // server bound
Map.of(0x00, LoginStartPacket.class), Map.of(0x00, LoginStartPacket.class),
// client bound // client bound
Map.of(DisconnectPacket.class, 0x00) Map.of(
DisconnectPacket.class, 0x00,
LoginSuccessPacket.class, 0x02
)
),
PLAY(3,
// server bound
Map.of(
0x00, TeleportConfirmPacket.class,
0x04, ClientSettingsPacket.class,
0x09, PluginMessagePacket.class,
0x0B, PingPacket.class,
0x0D, PlayerPositionPacket.class,
0x0E, CPlayerPositionAndLookPacket.class,
0x0F, PlayerLookPacket.class
),
// client bound
Map.of(
PingPacket.class, 0x1F,
JoinGamePacket.class, 0x23,
ScoreboardDisplayPacket.class, 0x3B,
ScoreboardObjectivePacket.class, 0x42,
TeamsPacket.class, 0x44,
ScoreboardUpdateScorePacket.class, 0x45,
SpawnPositionPacket.class, 0x46,
ChunkDataPacket.class, 0x20,
PlayerAbilitiesPacket.class,0x2C,
SPlayerPositionAndLookPacket.class, 0x2F
)
); );
@Nullable @Nullable

View File

@@ -0,0 +1,16 @@
package mc.protocol.api;
import mc.protocol.State;
import mc.protocol.packets.ServerSidePacket;
public interface ConnectionContext {
State getState();
void setState(State state);
void send(ServerSidePacket packet);
void sendNow(ServerSidePacket packet);
void flushSending();
void disconnect();
}

View File

@@ -0,0 +1,18 @@
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

@@ -0,0 +1,42 @@
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,11 +1,14 @@
package mc.protocol.di; package mc.protocol.di;
import dagger.Component; import dagger.Component;
import mc.protocol.NettyServer; import mc.protocol.api.Server;
@Component(modules = ProtocolModule.class) @Component(modules = {
ProtocolModule.class,
PoolModule.class
})
@ServerScope @ServerScope
public interface ProtocolComponent { public interface ProtocolComponent {
NettyServer getNettyServer(); Server getServer();
} }

View File

@@ -2,88 +2,56 @@ package mc.protocol.di;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
import io.netty.bootstrap.ServerBootstrap; import lombok.RequiredArgsConstructor;
import io.netty.channel.ChannelHandler; import mc.protocol.NettyConnectionContext;
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 mc.protocol.ChannelContext;
import mc.protocol.NettyServer; import mc.protocol.NettyServer;
import mc.protocol.PacketInboundHandler; import mc.protocol.PacketInboundHandler;
import mc.protocol.State; import mc.protocol.api.Server;
import mc.protocol.event.EventBus;
import mc.protocol.event.SimpleEventBus;
import mc.protocol.io.codec.ProtocolDecoder; import mc.protocol.io.codec.ProtocolDecoder;
import mc.protocol.io.codec.ProtocolEncoder; import mc.protocol.pool.PacketPool;
import mc.protocol.io.codec.ProtocolSplitter; import org.apache.commons.pool2.ObjectPool;
import mc.protocol.packets.Packet;
import reactor.core.publisher.Sinks;
import javax.annotation.Nonnull;
import javax.inject.Provider; import javax.inject.Provider;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Module @Module
@RequiredArgsConstructor
public class ProtocolModule { public class ProtocolModule {
@SuppressWarnings("rawtypes") private final boolean readUnknownPackets;
@Provides
NettyServer provideServer(ServerBootstrap serverBootstrap,
Map<Class<? extends Packet>, Sinks.Many<ChannelContext>> observedMap) {
return new NettyServer(serverBootstrap, observedMap);
}
@Provides
ServerBootstrap provideServerBootstrap(ChannelInitializer<SocketChannel> channelChannelInitializer) {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(new NioEventLoopGroup(1), new NioEventLoopGroup())
.channel(NioServerSocketChannel.class)
.childHandler(channelChannelInitializer);
return bootstrap;
}
@Provides
ChannelInitializer<SocketChannel> provideChannelChannelInitializer(
Provider<Map<String, ChannelHandler>> channelHandlerMapProvider) {
return new ChannelInitializer<>() {
@Override
protected void initChannel(@Nonnull SocketChannel socketChannel) {
ChannelPipeline pipeline = socketChannel.pipeline();
channelHandlerMapProvider.get().forEach(pipeline::addLast);
}
};
}
@SuppressWarnings("rawtypes")
@Provides
Map<String, ChannelHandler> provideChannelHandlerMap(
Map<Class<? extends Packet>, Sinks.Many<ChannelContext>> observedMap) {
Map<String, ChannelHandler> map = new LinkedHashMap<>();
map.put("packet_splitter", new ProtocolSplitter());
map.put("logger", new LoggingHandler(LogLevel.DEBUG));
map.put("packet_decoder", new ProtocolDecoder(true));
map.put("packet_encoder", new ProtocolEncoder());
map.put("packet_handler", new PacketInboundHandler(observedMap));
return map;
}
@SuppressWarnings("rawtypes")
@Provides @Provides
@ServerScope @ServerScope
Map<Class<? extends Packet>, Sinks.Many<ChannelContext>> provideObservedMap() { Server provideServer(
return Stream.of(State.values()) Provider<ProtocolDecoder> protocolDecoderProvider,
.flatMap(state -> state.getClientSidePackets().values().stream()) Provider<PacketInboundHandler> packetInboundHandlerProvider,
.collect(Collectors.toMap(packetClass -> packetClass, v -> Sinks.many().multicast().directBestEffort())); 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

@@ -0,0 +1,17 @@
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

@@ -0,0 +1,26 @@
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

@@ -32,6 +32,8 @@ import java.util.UUID;
* | | | | этого числа). | * | | | | этого числа). |
* | VarInt | >= 1 ; <= 5 | Число от -2147483648 и 2147483647 | 32-bit число с плавающей размерностью от 1 до 5 байт | * | VarInt | >= 1 ; <= 5 | Число от -2147483648 и 2147483647 | 32-bit число с плавающей размерностью от 1 до 5 байт |
* | VarLong | >= 1 ; <= 10 | Число от -9223372036854775808 и 9223372036854775807 | 64-bit число с плавающей размерностью от 1 до 10 байт | * | VarLong | >= 1 ; <= 10 | Число от -9223372036854775808 и 9223372036854775807 | 64-bit число с плавающей размерностью от 1 до 10 байт |
* | Position | 8 | 64-bit число разделённое на три части: x, y, z | Кодируется формулой: |
* | | | | ((x & 0x3FFFFFF) << 38) | ((y & 0xFFF) << 26) | (z & 0x3FFFFFF) |
* *
* [1] - <a href="https://en.wikipedia.org/wiki/Single-precision_floating-point_format">Single-precision floating-point format</a> * [1] - <a href="https://en.wikipedia.org/wiki/Single-precision_floating-point_format">Single-precision floating-point format</a>
* [2] - <a href="https://en.wikipedia.org/wiki/Double-precision_floating-point_format">Double-precision floating-point format</a> * [2] - <a href="https://en.wikipedia.org/wiki/Double-precision_floating-point_format">Double-precision floating-point format</a>
@@ -49,6 +51,10 @@ public class NetByteBuf extends ByteBuf {
@Delegate @Delegate
private final ByteBuf byteBuf; private final ByteBuf byteBuf;
public void writeUnsignedByte(int value) {
byteBuf.writeByte((byte)(value & 0xFF));
}
//region String //region String
public String readString() { public String readString() {
return readString(Short.MAX_VALUE); return readString(Short.MAX_VALUE);
@@ -93,6 +99,11 @@ public class NetByteBuf extends ByteBuf {
} }
public void writeString(String string) { public void writeString(String string) {
if (string == null) {
writeVarInt(0);
return;
}
byte[] buf = string.getBytes(StandardCharsets.UTF_8); byte[] buf = string.getBytes(StandardCharsets.UTF_8);
if (buf.length > Short.MAX_VALUE) { if (buf.length > Short.MAX_VALUE) {

View File

@@ -4,31 +4,51 @@ import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder; import io.netty.handler.codec.ByteToMessageDecoder;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import mc.protocol.NettyConnectionContext;
import mc.protocol.NetworkAttributes; import mc.protocol.NetworkAttributes;
import mc.protocol.State; import mc.protocol.State;
import mc.protocol.api.ConnectionContext;
import mc.protocol.io.NetByteBuf; import mc.protocol.io.NetByteBuf;
import mc.protocol.packets.ClientSidePacket; import mc.protocol.packets.ClientSidePacket;
import mc.protocol.packets.UnknownPacket; import mc.protocol.packets.UnknownPacket;
import mc.protocol.pool.PacketPool;
import org.apache.commons.pool2.ObjectPool;
import javax.annotation.Nonnull;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.function.Consumer;
@Slf4j
@RequiredArgsConstructor @RequiredArgsConstructor
@Slf4j
public class ProtocolDecoder extends ByteToMessageDecoder { public class ProtocolDecoder extends ByteToMessageDecoder {
private final boolean readUnknownPackets; private final boolean readUnknownPackets;
private final ObjectPool<NettyConnectionContext> poolNettyConnectionContext;
private final PacketPool poolPackets;
@Setter
private Consumer<ConnectionContext> consumerNewConnection;
@Setter
private Consumer<ConnectionContext> consumerDisconnect;
@Override @Override
public void channelActive(ChannelHandlerContext ctx) throws Exception { public void channelActive(@Nonnull ChannelHandlerContext ctx) throws Exception {
ctx.channel().attr(NetworkAttributes.STATE).set(State.HANDSHAKING); NettyConnectionContext context = poolNettyConnectionContext.borrowObject().setCtx(ctx);
consumerNewConnection.accept(context);
poolNettyConnectionContext.returnObject(context);
super.channelActive(ctx); super.channelActive(ctx);
} }
@Override @Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception { public void channelInactive(@Nonnull ChannelHandlerContext ctx) throws Exception {
ctx.channel().attr(NetworkAttributes.STATE).set(null); NettyConnectionContext context = poolNettyConnectionContext.borrowObject().setCtx(ctx);
consumerDisconnect.accept(context);
poolNettyConnectionContext.returnObject(context);
super.channelInactive(ctx); super.channelInactive(ctx);
} }
@@ -40,19 +60,30 @@ public class ProtocolDecoder extends ByteToMessageDecoder {
int packetId = netByteBuf.readVarInt(); int packetId = netByteBuf.readVarInt();
Class<? extends ClientSidePacket> packetClass = state.getClientSidePacketById(packetId); Class<? extends ClientSidePacket> packetClass = state.getClientSidePacketById(packetId);
if (packetClass == null) { if (packetClass == null) {
log.warn("Unkown packet: State {} ; Id {}", state, packetId); log.warn("Unknown packet: State {} ; Id 0x{}", state, packetIdAsHexcode(packetId));
if (readUnknownPackets) { if (readUnknownPackets) {
UnknownPacket unknownPacket = new UnknownPacket(state, packetId, netByteBuf.readableBytes()); UnknownPacket unknownPacket = poolPackets.borrowObject(UnknownPacket.class);
unknownPacket.setState(state);
unknownPacket.setId(packetId);
unknownPacket.setDataSize(netByteBuf.readableBytes());
unknownPacket.readSelf(netByteBuf); unknownPacket.readSelf(netByteBuf);
out.add(unknownPacket); out.add(unknownPacket);
} else { } else {
netByteBuf.skipBytes(netByteBuf.readableBytes()); netByteBuf.skipBytes(netByteBuf.readableBytes());
} }
} else { } else {
ClientSidePacket packet = packetClass.getDeclaredConstructor().newInstance(); ClientSidePacket packet = poolPackets.borrowObject(packetClass);
packet.readSelf(netByteBuf); packet.readSelf(netByteBuf);
log.debug("IN: {}:{}", state, packet);
out.add(packet); out.add(packet);
} }
} }
private String packetIdAsHexcode(int packetId) {
String hexPacketId = Integer.toHexString(packetId).toUpperCase();
if (hexPacketId.length() == 1) hexPacketId = "0" + hexPacketId;
return hexPacketId;
}
} }

View File

@@ -4,19 +4,25 @@ import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder; import io.netty.handler.codec.MessageToByteEncoder;
import lombok.extern.slf4j.Slf4j;
import mc.protocol.NetworkAttributes; import mc.protocol.NetworkAttributes;
import mc.protocol.State; import mc.protocol.State;
import mc.protocol.io.NetByteBuf; import mc.protocol.io.NetByteBuf;
import mc.protocol.packets.ServerSidePacket; import mc.protocol.packets.ServerSidePacket;
import java.util.Objects; @Slf4j
public class ProtocolEncoder extends MessageToByteEncoder<ServerSidePacket> { public class ProtocolEncoder extends MessageToByteEncoder<ServerSidePacket> {
@Override @Override
protected void encode(ChannelHandlerContext ctx, ServerSidePacket packet, ByteBuf out) { protected void encode(ChannelHandlerContext ctx, ServerSidePacket packet, ByteBuf out) {
State state = ctx.channel().attr(NetworkAttributes.STATE).get(); State state = ctx.channel().attr(NetworkAttributes.STATE).get();
int packetId = Objects.requireNonNull(state.getServerSidePacketId(packet.getClass())); Integer packetId = state.getServerSidePacketId(packet.getClass());
if (packetId == null) {
log.error("Unknown send packet: State {} ; Class {}", state, packet.getClass());
return;
}
log.debug("OUT: {}:{}", state, packet);
NetByteBuf buffer = new NetByteBuf(Unpooled.buffer()); NetByteBuf buffer = new NetByteBuf(Unpooled.buffer());
buffer.writeVarInt(packetId); buffer.writeVarInt(packetId);

View File

@@ -0,0 +1,12 @@
package mc.protocol.model;
import lombok.AllArgsConstructor;
import lombok.Data;
@AllArgsConstructor
@Data
public class Location {
private double x;
private double y;
private double z;
}

View File

@@ -0,0 +1,11 @@
package mc.protocol.model;
import lombok.AllArgsConstructor;
import lombok.Data;
@AllArgsConstructor
@Data
public class Look {
private float yaw;
private float pitch;
}

View File

@@ -1,11 +1,12 @@
package mc.protocol.packets; package mc.protocol.packets;
import mc.protocol.io.NetByteBuf; import mc.protocol.io.NetByteBuf;
import mc.protocol.pool.Passivable;
/** /**
* Пакеты отправляемые клиентом. * Пакеты отправляемые клиентом.
*/ */
public interface ClientSidePacket extends Packet { public interface ClientSidePacket extends Packet, Passivable {
void readSelf(NetByteBuf netByteBuf); void readSelf(NetByteBuf netByteBuf);
} }

View File

@@ -13,4 +13,9 @@ public abstract class EmptyPacket implements ClientSidePacket, ServerSidePacket
public void writeSelf(NetByteBuf netByteBuf) { public void writeSelf(NetByteBuf netByteBuf) {
// empty // empty
} }
@Override
public void passivate() {
// pass
}
} }

View File

@@ -34,6 +34,11 @@ public class PingPacket implements ClientSidePacket, ServerSidePacket {
payload = netByteBuf.readLong(); payload = netByteBuf.readLong();
} }
@Override
public void passivate() {
this.payload = null;
}
@Override @Override
public void writeSelf(NetByteBuf netByteBuf) { public void writeSelf(NetByteBuf netByteBuf) {
netByteBuf.writeLong(payload); netByteBuf.writeLong(payload);

View File

@@ -1,17 +1,19 @@
package mc.protocol.packets; package mc.protocol.packets;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString; import lombok.ToString;
import mc.protocol.State; import mc.protocol.State;
import mc.protocol.io.NetByteBuf; import mc.protocol.io.NetByteBuf;
@NoArgsConstructor
@Data @Data
@ToString(exclude = "rawData") @ToString(exclude = "rawData")
public class UnknownPacket implements ClientSidePacket { public class UnknownPacket implements ClientSidePacket {
private final State state; private State state;
private final int id; private int id;
private final int dataSize; private int dataSize;
private byte[] rawData; private byte[] rawData;
@Override @Override
@@ -19,4 +21,12 @@ public class UnknownPacket implements ClientSidePacket {
rawData = new byte[dataSize]; rawData = new byte[dataSize];
netByteBuf.readBytes(rawData); netByteBuf.readBytes(rawData);
} }
@Override
public void passivate() {
this.state = null;
this.id = 0;
this.dataSize = 0;
this.rawData = null;
}
} }

View File

@@ -0,0 +1,64 @@
package mc.protocol.packets.client;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import mc.protocol.io.NetByteBuf;
import mc.protocol.model.Location;
import mc.protocol.model.Look;
import mc.protocol.packets.ClientSidePacket;
/**
* Клиент сообщает о движении и повороте головы Игрока.
*
* <p>Структура пакета</p>
* <pre>
* | FIELD | TYPE | NOTES |
* |-----------|---------|------------------------------------------------------------|
* | X | Double | Абсолютная позиция по X |
* | Y | Double | Абсолютная позиция по Y. |
* | | | Имеется ввиду позиция ног. Голова находиться выше на 1.62f |
* | Z | Double | Абсолютная позиция по Z |
* | Yaw | Float | Абсолютный поворот головы по OX, в градусах |
* | Pitch | Float | Абсолютный поворот головы по OY, в градусах |
* | On Ground | Boolean | true, если Игрок находится на земле |
* </pre>
*
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Player_Position_And_Look_.28serverbound.29">Player Position And Look (serverbound)</a>
*/
@NoArgsConstructor
@Getter
@EqualsAndHashCode
@ToString
public class CPlayerPositionAndLookPacket implements ClientSidePacket {
private Location position;
private Look look;
private boolean onGround;
@Override
public void readSelf(NetByteBuf netByteBuf) {
double x = netByteBuf.readDouble();
double y = netByteBuf.readDouble();
double z = netByteBuf.readDouble();
this.position = new Location(x, y, z);
float yaw = netByteBuf.readFloat();
float pitch = netByteBuf.readFloat();
this.look = new Look(yaw, pitch);
this.onGround = netByteBuf.readBoolean();
}
@Override
public void passivate() {
this.position = null;
this.look = null;
this.onGround = false;
}
public double getYPositionHead() {
return this.position.getY() + 1.62f;
}
}

View File

@@ -0,0 +1,104 @@
package mc.protocol.packets.client;
import lombok.*;
import mc.protocol.utils.ChatMode;
import mc.protocol.utils.MainHand;
import mc.protocol.io.NetByteBuf;
import mc.protocol.packets.ClientSidePacket;
/**
* Client settings packet.
*
* <p>Структура пакета</p>
* <pre>
* | FIELD | TYPE | NOTES |
* |-------------------- |---------------|---------------------------------------------------|
* | Locale | String (16) | например en_gb |
* | View Distance | Byte | Дистанция отрисовки со стороны Клиента, в чанках. |
* | Chat Mode | VarInt | 0: enabled |
* | | | 1: commands only |
* | | | 2: hidden |
* | | | [1] |
* | Chat Colors | Boolean | “Colors” multiplayer setting (???) |
* | Displayed Skin Parts | Unsigned Byte | битовая маска отображения скина. См. ниже |
* | Main Hand | VarInt | 0: Left |
* | | | 1: Right |
*
* [1] - <a href="https://wiki.vg/index.php?title=Chat&oldid=13165#Processing_chat">Processing chat</a>
* </pre>
*
* <p>Биты "Displayed Skin Parts"</p>
* <pre>
* Bit 0 (0x01): Плащ (Cape)
* Bit 1 (0x02): Рубашка (Jacket)
* Bit 2 (0x04): Левый рукав (Left Sleeve)
* Bit 3 (0x08): Правый рукав (Right Sleeve)
* Bit 4 (0x10): Левая штанина (Left Pants Leg)
* Bit 5 (0x20): Правая штанина (Right Pants Leg)
* Bit 6 (0x40): Шлем (Hat)
* </pre>
*
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Client_Settings">Client Settings</a>
*/
@NoArgsConstructor
@Getter
@EqualsAndHashCode
@ToString
public class ClientSettingsPacket implements ClientSidePacket {
private String locale;
private int viewDistance;
private ChatMode chatMode;
private boolean chatColors;
@SuppressWarnings("java:S116")
private int $displayedSkinPartsBitMask;
private MainHand mainHand;
@Override
public void readSelf(NetByteBuf netByteBuf) {
this.locale = netByteBuf.readString(16);
this.viewDistance = netByteBuf.readByte();
this.chatMode = ChatMode.valueById(netByteBuf.readVarInt());
this.chatColors = netByteBuf.readBoolean();
this.$displayedSkinPartsBitMask = netByteBuf.readUnsignedByte();
this.mainHand = MainHand.valueById(netByteBuf.readVarInt());
}
@Override
public void passivate() {
this.locale = null;
this.viewDistance = 0;
this.chatMode = null;
this.chatColors = false;
this.$displayedSkinPartsBitMask = 0;
this.mainHand = null;
}
public boolean isCapeEnabled() {
return ($displayedSkinPartsBitMask & 0x01) > 0;
}
public boolean isJacketEnabled() {
return ($displayedSkinPartsBitMask & 0x02) > 0;
}
public boolean isLeftSleeveEnabled() {
return ($displayedSkinPartsBitMask & 0x04) > 0;
}
public boolean isRightSleeveEnabled() {
return ($displayedSkinPartsBitMask & 0x08) > 0;
}
public boolean isLeftPantsEnabled() {
return ($displayedSkinPartsBitMask & 0x10) > 0;
}
public boolean isRightPantsEnabled() {
return ($displayedSkinPartsBitMask & 0x20) > 0;
}
public boolean isHatEnabled() {
return ($displayedSkinPartsBitMask & 0x40) > 0;
}
}

View File

@@ -47,4 +47,12 @@ public class HandshakePacket implements ClientSidePacket {
nextState = State.getById(netByteBuf.readVarInt()); nextState = State.getById(netByteBuf.readVarInt());
} }
@Override
public void passivate() {
this.protocolVersion = 0;
this.host = null;
this.port = 0;
this.nextState = null;
}
} }

View File

@@ -34,4 +34,9 @@ public class LoginStartPacket implements ClientSidePacket {
this.name = netByteBuf.readString(); this.name = netByteBuf.readString();
} }
@Override
public void passivate() {
this.name = null;
}
} }

View File

@@ -0,0 +1,48 @@
package mc.protocol.packets.client;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import mc.protocol.io.NetByteBuf;
import mc.protocol.model.Look;
import mc.protocol.packets.ClientSidePacket;
/**
* Клиент сообщает о повороте головы Игрока.
*
* <p>Структура пакета</p>
* <pre>
* | FIELD | TYPE | NOTES |
* |-----------|---------|---------------------------------------------|
* | Yaw | Float | Абсолютный поворот головы по OX, в градусах |
* | Pitch | Float | Абсолютный поворот головы по OY, в градусах |
* | On Ground | Boolean | true, если Игрок находится на земле |
* </pre>
*
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Player_Look">Player Look</a>
*/
@NoArgsConstructor
@Getter
@EqualsAndHashCode
@ToString
public class PlayerLookPacket implements ClientSidePacket {
private Look look;
private boolean onGround;
@Override
public void readSelf(NetByteBuf netByteBuf) {
float yaw = netByteBuf.readFloat();
float pitch = netByteBuf.readFloat();
this.look = new Look(yaw, pitch);
this.onGround = netByteBuf.readBoolean();
}
@Override
public void passivate() {
this.look = null;
this.onGround = false;
}
}

View File

@@ -0,0 +1,55 @@
package mc.protocol.packets.client;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import mc.protocol.io.NetByteBuf;
import mc.protocol.model.Location;
import mc.protocol.packets.ClientSidePacket;
/**
* Клиент сообщает о движении Игрока.
*
* <p>Структура пакета</p>
* <pre>
* | FIELD | TYPE | NOTES |
* |-----------|---------|-------------------------------------|
* | X | Double | Абсолютная позиция по X |
* | Feet Y | Double | Абсолютная позиция ног по Y. |
* | | | Голова находиться выше на 1.62f |
* | Z | Double | Абсолютная позиция по Z |
* | On Ground | Boolean | true, если Игрок находится на земле |
* </pre>
*
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Player_Position">Player Position</a>
*/
@NoArgsConstructor
@Getter
@EqualsAndHashCode
@ToString
public class PlayerPositionPacket implements ClientSidePacket {
private Location position;
private boolean onGround;
@Override
public void readSelf(NetByteBuf netByteBuf) {
double x = netByteBuf.readDouble();
double y = netByteBuf.readDouble();
double z = netByteBuf.readDouble();
this.position = new Location(x, y, z);
this.onGround = netByteBuf.readBoolean();
}
@Override
public void passivate() {
this.position = null;
this.onGround = false;
}
public double getYPositionHead() {
return this.position.getY() + 1.62f;
}
}

View File

@@ -0,0 +1,48 @@
package mc.protocol.packets.client;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import mc.protocol.io.NetByteBuf;
import mc.protocol.packets.ClientSidePacket;
/**
* Plugin Message packet.
*
* <p>Канал связи для модов и плагинов.</p>
*
* <p>Структура пакета</p>
* <pre>
* | FIELD | TYPE | NOTES |
* |--------------|-------------|------------------|
* | Channel name | String (20) | Название канала |
* | Data | Byte array | Любые данные |
* </pre>
*
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Plugin_Message_.28serverbound.29">Plugin Message (serverbound)</a>
* @see <a href="https://wiki.vg/index.php?title=Plugin_channels&oldid=14089">Plugin channels</a>
* @see <a href="https://dinnerbone.com/blog/2012/01/13/minecraft-plugin-channels-messaging/">Minecraft Plugin Channels + Messaging</a>
*/
@NoArgsConstructor
@Getter
@EqualsAndHashCode
@ToString
public class PluginMessagePacket implements ClientSidePacket {
private String channelName;
private byte[] rawData;
@Override
public void readSelf(NetByteBuf netByteBuf) {
this.channelName = netByteBuf.readString(20);
this.rawData = new byte[netByteBuf.readableBytes()];
netByteBuf.readBytes(this.rawData);
}
@Override
public void passivate() {
this.channelName = null;
this.rawData = null;
}
}

View File

@@ -0,0 +1,41 @@
package mc.protocol.packets.client;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import mc.protocol.io.NetByteBuf;
import mc.protocol.packets.ClientSidePacket;
import mc.protocol.packets.server.SPlayerPositionAndLookPacket;
/**
* Teleport сonfirm packet.
*
* <p>Структура пакета</p>
* <pre>
* | FIELD | TYPE | NOTES |
* |-------------|--------|-----------------------------------------------------------|
* | Teleport ID | VarInt | ID, который был выдан пакетом {@link SPlayerPositionAndLookPacket} |
* </pre>
*
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=7368#Login_Start" target="_top">Login start</a>
* @see SPlayerPositionAndLookPacket
*/
@NoArgsConstructor
@Getter
@EqualsAndHashCode
@ToString
public class TeleportConfirmPacket implements ClientSidePacket {
private int teleportId;
@Override
public void readSelf(NetByteBuf netByteBuf) {
this.teleportId = netByteBuf.readVarInt();
}
@Override
public void passivate() {
this.teleportId = 0;
}
}

View File

@@ -0,0 +1,69 @@
package mc.protocol.packets.server;
import io.netty.buffer.Unpooled;
import lombok.Data;
import mc.protocol.io.NetByteBuf;
import mc.protocol.packets.ServerSidePacket;
/**
* Данные чанка.
*
* <p>Структура пакета</p>
* <pre>
* | FIELD | TYPE | NOTES |
* |--------------------------|------------- |------------------------------------------------------------------------------------|
* | Chunk X | Integer | Координаты чанка (координата блока, делённая на 16, округленная в меньшую сторону) |
* | Chunk Z | Integer | Координаты чанка (координата блока, делённая на 16, округленная в меньшую сторону) |
* | Is Full chunk | Boolean | См. Chunk Format |
* | Available Sections | VarInt | Битовая маска, где каждый бит - это часть чанка (0-15) |
* | Size of Data | VarInt | Размер поля "Data" |
* | Data | Byte array | Данные чанка. См. Chunk Format |
* | Number of block entities | VarInt | Количество элементов в поле "Block entities" |
* | Block entities | Array of NBT | Все сущности в чанке |
* </pre>
*
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Chunk_Data">Chunk Data</a>
* @see <a href="https://wiki.vg/index.php?title=Chunk_Format&oldid=14135">Chunk Format</a>
*/
@Data
public class ChunkDataPacket implements ServerSidePacket {
private int x;
private int z;
@Override
public void writeSelf(NetByteBuf netByteBuf) {
netByteBuf.writeInt(x);
netByteBuf.writeInt(z);
netByteBuf.writeBoolean(true); // Is Full chunk
netByteBuf.writeVarInt(0b11111111); // Available Sections
NetByteBuf data = new NetByteBuf(Unpooled.buffer());
// <Data>
for (int i = 0; i < 16; i++) {
NetByteBuf dataBuff = new NetByteBuf(Unpooled.wrappedBuffer(new byte[4096]));
NetByteBuf blockLight = new NetByteBuf(Unpooled.wrappedBuffer(new byte[2048]));
NetByteBuf skyLight = new NetByteBuf(Unpooled.wrappedBuffer(new byte[2048]));
NetByteBuf biomes = new NetByteBuf(Unpooled.wrappedBuffer(new byte[256]));
// <Chunk Section>
data.writeUnsignedByte(13); // Bits Per Block
// <Palette>
data.writeUnsignedByte(0); // Palette Length (for direct)
// <Palette Data/>
// </Palette>
data.writeVarInt(dataBuff.readableBytes()); // Data Array Length
data.writeBytes(dataBuff); // Data Array
data.writeBytes(blockLight); // Block Light
data.writeBytes(skyLight); // Sky Light
// </Chunk Section>
data.writeBytes(biomes); // Biomes
}
// </Data>
netByteBuf.writeVarInt(data.readableBytes()); // Size of Data
netByteBuf.writeBytes(data); // Data
netByteBuf.writeVarInt(0); // Number of block entities
/* write NBT's */
}
}

View File

@@ -0,0 +1,59 @@
package mc.protocol.packets.server;
import lombok.Data;
import mc.protocol.utils.Difficulty;
import mc.protocol.utils.GameMode;
import mc.protocol.utils.LevelType;
import mc.protocol.io.NetByteBuf;
import mc.protocol.packets.ServerSidePacket;
/**
* Join game packet.
*
* <p>Структура пакета</p>
* <pre>
* | FIELD | TYPE | NOTES |
* |--------------------|---------------|----------------------------------------------------------------------|
* | Entity ID | Integer | ID сущности (игрока) |
* | Gamemode | Unsigned Byte | 0: Survival |
* | | | 1: Creative |
* | | | 2: Adventure |
* | | | 3: Spectator |
* | | | Bit 3 (0x8) is the hardcore flag. |
* | Dimension | Integer | -1: Nether |
* | | | 0: Overworld |
* | | | 1: End |
* | Difficulty | Unsigned Byte | 0: peaceful |
* | | | 1: easy |
* | | | 2: normal |
* | | | 3: hard |
* | Max Players | Unsigned Byte | Когда-то использовался клиентом для |
* | | | отображения списка игроков. Теперь не используется |
* | Level Type | String (16) | Принимает одно из значений: |
* | | | default, flat, largeBiomes, amplified, default_1_1 |
* | Reduced Debug Info | Boolean | Если true, то Клиент отображает меньше отладочной информации (в F3?) |
* </pre>
*
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Join_Game">Join Game</a>
*/
@Data
public class JoinGamePacket implements ServerSidePacket {
private int entityId;
private GameMode gameMode;
private int dimension;
private Difficulty difficulty;
private LevelType levelType;
private boolean reducedDebugInfo;
@Override
public void writeSelf(NetByteBuf netByteBuf) {
netByteBuf.writeInt(entityId);
netByteBuf.writeUnsignedByte(gameMode.getId());
netByteBuf.writeInt(dimension);
netByteBuf.writeUnsignedByte(difficulty.getId());
netByteBuf.writeUnsignedByte(0); // Max Players, unused
netByteBuf.writeString(levelType.getType());
netByteBuf.writeBoolean(reducedDebugInfo);
}
}

View File

@@ -0,0 +1,33 @@
package mc.protocol.packets.server;
import lombok.Data;
import mc.protocol.io.NetByteBuf;
import mc.protocol.packets.ServerSidePacket;
import java.util.UUID;
/**
* Подтверждение успешного логина.
*
* <p>Структура пакета</p>
* <pre>
* | FIELD | TYPE | NOTES |
* |----------|-------------|-------------------------------|
* | UUID | String (36) | Уникальный ID игрока |
* | Username | String (16) | Имя игрока, выданное сервером |
* </pre>
*
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Login_Success">Login Success</a>
*/
@Data
public class LoginSuccessPacket implements ServerSidePacket {
private UUID uuid;
private String name;
@Override
public void writeSelf(NetByteBuf netByteBuf) {
netByteBuf.writeString(uuid.toString());
netByteBuf.writeString(name);
}
}

View File

@@ -0,0 +1,60 @@
package mc.protocol.packets.server;
import lombok.Data;
import mc.protocol.io.NetByteBuf;
import mc.protocol.packets.ServerSidePacket;
/**
* Характеристики игрока.
*
* <p>Структура пакета</p>
* <pre>
* | FIELD | TYPE | NOTES |
* |------------------------------|----------|-----------------------------------------|
* | Flags | Byte | Битовая маска флагов. См. ниже значения |
* | Flying Speed | Float | Скорость полёта |
* | Field of View (FOV) Modifier | Float | Поле зрения |
* </pre>
*
* <p>Флаги "Flags"</p>
* <pre>
* Bit 0x01 - Неуязвимость (Invulnerable)
* Bit 0x02 - В полёте (Flying)
* Bit 0x04 - Может летать (Allow Flying)
* Bit 0x08 - Creative Mode (Instant Break)
* </pre>
*
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Player_Abilities_.28clientbound.29">Player Abilities</a>
*/
@Data
public class PlayerAbilitiesPacket implements ServerSidePacket {
@SuppressWarnings("java:S116")
private byte $flags = 0;
private float flyingSpeed;
private float fieldOfView;
@Override
public void writeSelf(NetByteBuf netByteBuf) {
netByteBuf.writeByte(this.$flags);
netByteBuf.writeFloat(this.flyingSpeed);
netByteBuf.writeFloat(this.fieldOfView);
}
//FIXME использование value значений
public void setInvulnerable(boolean value) {
this.$flags = (byte) (this.$flags | 0x01);
}
public void setFlying(boolean value) {
this.$flags = (byte) (this.$flags | 0x02);
}
public void setCatFly(boolean value) {
this.$flags = (byte) (this.$flags | 0x04);
}
public void setCreativeMode(boolean value) {
this.$flags = (byte) (this.$flags | 0x08);
}
}

View File

@@ -0,0 +1,83 @@
package mc.protocol.packets.server;
import lombok.Data;
import mc.protocol.io.NetByteBuf;
import mc.protocol.model.Location;
import mc.protocol.model.Look;
import mc.protocol.packets.ServerSidePacket;
import mc.protocol.packets.client.TeleportConfirmPacket;
/**
* Установка позиции и угла осмотра Игрока.
*
* <p>Структура пакета</p>
* <pre>
* | FIELD | TYPE | NOTES |
* |-------------|--------|-----------------------------------------------------------------------------------|
* | X | Double | Абсолютная или относительная позиция по X. Зависит от "Flags" |
* | Y | Double | Абсолютная или относительная позиция по Y. Зависит от "Flags" |
* | Z | Double | Абсолютная или относительная позиция по Z. Зависит от "Flags" |
* | Yaw | Float | Абсолютный или относительный поворот головы по OX, в градусах. Зависит от "Flags" |
* | Pitch | Float | Абсолютный или относительный поворот головы по OY, в градусах. Зависит от "Flags" |
* | Flags | Byte | Битовая маска значений флагов. См. значения ниже |
* | Teleport ID | VarInt | ID для подтверждения клиентом перемещения Игрока |
* </pre>
*
* <p>Значения "Flags"</p>
* <pre>
* | Field | Bit |
* |-------|------|
* | X | 0x01 |
* | Y | 0x02 |
* | Z | 0x04 |
* | X_ROT | 0x08 |
* | Y_ROT | 0x10 |
* </pre>
*
* <p>Примечание от Dinnerbone про "Flags":</p>
* <i>"It's a bitfield, X/Y/Z/Y_ROT/X_ROT. If X is set, the x value is relative and not absolute."</i>
*
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Player_Position_And_Look_.28clientbound.29">Player Position And Look (clientbound)</a>
* @see TeleportConfirmPacket
*/
@Data
public class SPlayerPositionAndLookPacket implements ServerSidePacket {
private Location position;
private Look look;
@SuppressWarnings("java:S116")
private byte $flags = 0;
private int teleportId;
@Override
public void writeSelf(NetByteBuf netByteBuf) {
netByteBuf.writeDouble(this.position.getX());
netByteBuf.writeDouble(this.position.getY());
netByteBuf.writeDouble(this.position.getZ());
netByteBuf.writeFloat(this.look.getYaw());
netByteBuf.writeFloat(this.look.getPitch());
netByteBuf.writeByte(this.$flags);
netByteBuf.writeVarInt(teleportId);
}
//FIXME использовать value значения
public void setFlagX(boolean value) {
this.$flags = (byte) (this.$flags | 0x01);
}
public void setFlagY(boolean value) {
this.$flags = (byte) (this.$flags | 0x02);
}
public void setFlagZ(boolean value) {
this.$flags = (byte) (this.$flags | 0x04);
}
public void setFlagXRot(boolean value) {
this.$flags = (byte) (this.$flags | 0x08);
}
public void setFlagYRot(boolean value) {
this.$flags = (byte) (this.$flags | 0x10);
}
}

View File

@@ -0,0 +1,39 @@
package mc.protocol.packets.server;
import lombok.Data;
import mc.protocol.io.NetByteBuf;
import mc.protocol.packets.ServerSidePacket;
/**
* Отображение Scoreboard.
*
* <p>Структура пакета</p>
* <pre>
* | FIELD | TYPE | NOTES |
* |------------|-------------|--------------------------------|
* | Position | Byte | Положение: |
* | | | 0 - list |
* | | | 1 - sidebar |
* | | | 2 - below name |
* | | | 3-18 - team specific sidebar |
* | Score Name | String (16) | Уникальное название Scoreboard |
* </pre>
*
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Display_Scoreboard" target="_top">Display Scoreboard</a>
*/
@Data
public class ScoreboardDisplayPacket implements ServerSidePacket {
private int position;
private String scoreName;
public void setPosition(int position) {
this.position = (position < 0) ? 0 : (Math.min(position, 18));
}
@Override
public void writeSelf(NetByteBuf netByteBuf) {
netByteBuf.writeByte(this.position);
netByteBuf.writeString(this.scoreName);
}
}

View File

@@ -0,0 +1,44 @@
package mc.protocol.packets.server;
import lombok.Data;
import mc.protocol.io.NetByteBuf;
import mc.protocol.packets.ServerSidePacket;
import mc.protocol.utils.ScoreboardObjectiveMode;
import mc.protocol.utils.ScoreboardObjectiveType;
/**
* Scoreboard objective packet.
*
* <p>Структура пакета</p>
* <pre>
* | FIELD | TYPE | NOTES |
* |-----------------|-------------|---------------------------------------------------|
* | Objective Name | String (16) | Уникальное наименование цели (objective) |
* | Mode | Byte | 0 - создание Scoreboard |
* | | | 1 - удаление Scoreboard |
* | | | 2 - обновление Scoreboard |
* | Objective Value | String (32) | Если "Mode" равен 0 или 2. Отображаемый текст |
* | Type | String (16) | Если "Mode" равен 0 или 2. "integer" или "hearts" |
* </pre>
*
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Scoreboard_Objective" target="_top">Scoreboard Objective</a>
*/
@Data
public class ScoreboardObjectivePacket implements ServerSidePacket {
private String objectiveName;
private ScoreboardObjectiveMode mode;
private String objectiveValue;
private ScoreboardObjectiveType type;
@Override
public void writeSelf(NetByteBuf netByteBuf) {
netByteBuf.writeString(this.objectiveName);
netByteBuf.writeByte(this.mode.getCode());
if (ScoreboardObjectiveMode.CREATE.equals(this.mode) || ScoreboardObjectiveMode.UPDATE.equals(this.mode)) {
netByteBuf.writeString(this.objectiveValue);
netByteBuf.writeString(this.type.name().toLowerCase());
}
}
}

View File

@@ -0,0 +1,43 @@
package mc.protocol.packets.server;
import lombok.Data;
import mc.protocol.io.NetByteBuf;
import mc.protocol.packets.ServerSidePacket;
import mc.protocol.utils.ScoreboardUpdateScoreAction;
/**
* Update score packet.
*
* <p>Структура пакета</p>
* <pre>
* | FIELD | TYPE | NOTES |
* |-----------------|-------------|--------------------------------------------------- |
* | Entity Name | String (40) | Сущность, которой принадлежит счет (score). |
* | | | Для Игроков - это ник |
* | | | Для других сущностей - это UUID |
* | Action | Byte | 0 - создать или обновить счет (score); 1 - удалить |
* | Objective Name | String (16) | Имя сущности, которой принадлежит счет (score) |
* | Value | VarInt | Если "Action" = 0. Значение счета (score) |
* </pre>
*
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Update_Score" target="_top">Update Score</a>
*/
@Data
public class ScoreboardUpdateScorePacket implements ServerSidePacket {
private String entityName;
private ScoreboardUpdateScoreAction action;
private String objective;
private int value;
@Override
public void writeSelf(NetByteBuf netByteBuf) {
netByteBuf.writeString(this.entityName);
netByteBuf.writeByte(this.action.getCode());
netByteBuf.writeString(this.objective);
if (ScoreboardUpdateScoreAction.CREATE_OR_UPDATE.equals(this.action)) {
netByteBuf.writeVarInt(this.value);
}
}
}

View File

@@ -0,0 +1,41 @@
package mc.protocol.packets.server;
import lombok.Data;
import mc.protocol.io.NetByteBuf;
import mc.protocol.model.Location;
import mc.protocol.packets.ServerSidePacket;
/**
* Спавн позиция игрока.
*
* <p>Используется призаходе игрока на сервер.</p>
*
* <p>Структура пакета</p>
* <pre>
* | FIELD | TYPE | NOTES |
* |----------|----------|-----------------------|
* | Location | Position | Локация спавна игрока |
* </pre>
*
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Spawn_Position">Spawn Position</a>
*/
@Data
public class SpawnPositionPacket implements ServerSidePacket {
private Location spawn;
@Override
public void writeSelf(NetByteBuf netByteBuf) {
long spawnSerialized =
((long) (floorDouble(spawn.getX()) & 0x3FFFFFF) << 38)
| ((long) (floorDouble(spawn.getY()) & 0xFFF) << 26)
| (floorDouble(spawn.getZ()) & 0x3FFFFFF);
netByteBuf.writeLong(spawnSerialized);
}
private static int floorDouble(double value) {
int i = (int) value;
return value < (double) i ? i - 1 : i;
}
}

View File

@@ -0,0 +1,119 @@
package mc.protocol.packets.server;
import lombok.Data;
import mc.protocol.io.NetByteBuf;
import mc.protocol.packets.ServerSidePacket;
import mc.protocol.utils.TeamsCollisionRule;
import mc.protocol.utils.TeamsMode;
import mc.protocol.utils.TeamsNameTagVisibility;
import java.util.ArrayList;
import java.util.List;
/**
* Teams packet.
*
* <p>Структура пакета</p>
* <pre>
* | FIELD | TYPE | NOTES |
* |------------ |-------------|-------------------------------------------------------|
* | Team Name | String (16) | Уникальное название команды (совместно со scoreboard) |
* | Mode | Byte | Режим. Определяет остальые поля пакета |
* | Data Fields | - | Определяется "Mode" |
* </pre>
*
* <p>Варианты "Mode"</p>
* <pre>
* | MODE | DATA FIELD | TYPE | NOTES |
* | VALUE | DESCRIPTION | | | |
* |-------|--------------------------|---------------------|----------------------|---------------------------------------------------|
* | 0 | create team | Team Display Name | String (32) | |
* | | | Team Prefix | String (16) | Отображается перед именем игроков текущей команды |
* | | | Team Suffix | String (16) | Отображается после имени игроков текущей команды |
* | | | Friendly Flags | Byte | Битовая маска: |
* | | | | | 0x01 - разрешён friendly fire |
* | | | | | 0x02 - могут видеть невидимок своей команды |
* | | | Name Tag Visibility | String (32) | фиксированные значения: |
* | | | | | - always |
* | | | | | - hideForOtherTeams |
* | | | | | - hideForOwnTeam |
* | | | | | - never |
* | | | Collision Rule | String (32) | фиксированные значения: |
* | | | | | - always |
* | | | | | - pushOtherTeams |
* | | | | | - pushOwnTeam |
* | | | | | - never |
* | | | Color | Byte | For colors, the same Chat colors (0-15). |
* | | | | | -1 indicates RESET/no color. |
* | | | Entity Count | VarInt | Количество элементов в поле "Entities" |
* | | | Entities | Array of String (40) | Уникальные идентификаторы участников команды. |
* | | | | | Для Игроков - это Имена |
* | | | | | Для любых других сущностей - это UUID |
* | 1 | remove team | - | - | удаление текущей команды |
* | 2 | update team info | Team Display Name | String (32) | |
* | | | Team Prefix | String (16) | (см. выше) |
* | | | Team Suffix | String (16) | (см. выше) |
* | | | Friendly Flags | Byte | (см. выше) |
* | | | Name Tag Visibility | String (32) | (см. выше) |
* | | | Collision Rule | String (32) | (см. выше) |
* | | | Color | Byte | (см. выше) |
* | 3 | add players to team | Entity Count | VarInt | (см. выше) |
* | | | Entities | Array of String (40) | (см. выше) |
* | 4 | remove players from team | Entity Count | VarInt | (см. выше) |
* | | | Entities | Array of String (40) | (см. выше) |
* </pre>
*
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Teams" target="_top">Teams</a>
*/
@Data
public class TeamsPacket implements ServerSidePacket {
private final List<String> members = new ArrayList<>();
private String name;
private TeamsMode mode;
private String displayName;
private String prefix;
private String suffix;
private TeamsNameTagVisibility nameTagVisibility;
private TeamsCollisionRule collisionRule;
private int color;
@Override
public void writeSelf(NetByteBuf netByteBuf) {
netByteBuf.writeString(this.name);
netByteBuf.writeByte(this.mode.getCode());
switch (this.mode) {
case CREATE:
netByteBuf.writeString(this.displayName);
netByteBuf.writeString(this.prefix);
netByteBuf.writeString(this.suffix);
netByteBuf.writeByte(0); // Friendly Flags
netByteBuf.writeString(this.nameTagVisibility.getCode());
netByteBuf.writeString(this.collisionRule.getCode());
netByteBuf.writeByte(this.color);
netByteBuf.writeVarInt(this.members.size());
this.members.forEach(netByteBuf::writeString);
break;
case UPDATE:
netByteBuf.writeString(this.displayName);
netByteBuf.writeString(this.prefix);
netByteBuf.writeString(this.suffix);
netByteBuf.writeByte(0); // Friendly Flags
netByteBuf.writeString(this.nameTagVisibility.getCode());
netByteBuf.writeString(this.collisionRule.getCode());
netByteBuf.writeByte(this.color);
break;
case ADD_MEMBER:
case REMOVE_MEMBER:
netByteBuf.writeVarInt(this.members.size());
this.members.forEach(netByteBuf::writeString);
break;
case REMOVE:
default:
break;
}
}
}

View File

@@ -0,0 +1,24 @@
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

@@ -0,0 +1,30 @@
package mc.protocol.pool;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import mc.protocol.packets.ClientSidePacket;
import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
@Slf4j
@RequiredArgsConstructor
public class PacketFactory<P extends ClientSidePacket> extends BasePooledObjectFactory<P> {
private final Class<P> clazz;
@Override
public P create() throws Exception {
return clazz.getDeclaredConstructor().newInstance();
}
@Override
public PooledObject<P> wrap(P packet) {
return new DefaultPooledObject<>(packet);
}
@Override
public void passivateObject(PooledObject<P> pooledPacket) {
pooledPacket.getObject().passivate();
}
}

View File

@@ -0,0 +1,26 @@
package mc.protocol.pool;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import mc.protocol.packets.ClientSidePacket;
import org.apache.commons.pool2.ObjectPool;
import java.util.Map;
@Slf4j
@RequiredArgsConstructor
public class PacketPool {
@SuppressWarnings("rawtypes")
private final Map<Class<? extends ClientSidePacket>, ObjectPool> mapPoolPackets;
@SuppressWarnings("unchecked")
public <P extends ClientSidePacket> P borrowObject(Class<P> packetClass) throws Exception {
return (P) mapPoolPackets.get(packetClass).borrowObject();
}
@SuppressWarnings("unchecked")
public <P extends ClientSidePacket> void returnObject(P packet) throws Exception {
mapPoolPackets.get(packet.getClass()).returnObject(packet);
}
}

View File

@@ -0,0 +1,6 @@
package mc.protocol.pool;
public interface Passivable {
void passivate();
}

View File

@@ -0,0 +1,20 @@
package mc.protocol.utils;
import javax.annotation.Nullable;
public enum ChatMode {
FULL,
COMMANDS_ONLY,
HIDDEN;
@Nullable
public static ChatMode valueById(int id) {
// а зачем усложнять?
//@formatter:off
if (id == 1) return FULL;
else if (id == 2) return COMMANDS_ONLY;
else if (id == 3) return HIDDEN;
else return null;
//@formatter:on
}
}

View File

@@ -0,0 +1,15 @@
package mc.protocol.utils;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Getter
public enum Difficulty {
PEACEFUL(0),
EASY(1),
NORMAL(2),
HARD(3);
private final int id;
}

View File

@@ -0,0 +1,15 @@
package mc.protocol.utils;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Getter
public enum GameMode {
SURVIVAL(0),
CREATIVE(1),
ADVENTURE(2),
SPECTATOR(3);
private final int id;
}

View File

@@ -0,0 +1,16 @@
package mc.protocol.utils;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Getter
public enum LevelType {
DEFAULT_TYPE("default"),
FLAT("flat"),
LARGE_BIOMES("largeBiomes"),
AMPLIFIED("amplified"),
DEFAULT_1_1("default_1_1");
private final String type;
}

View File

@@ -0,0 +1,18 @@
package mc.protocol.utils;
import javax.annotation.Nullable;
public enum MainHand {
LEFT,
RIGHT;
@Nullable
public static MainHand valueById(int id) {
// а зачем усложнять?
//@formatter:off
if (id == 0) return LEFT;
else if (id == 1) return RIGHT;
else return null;
//@formatter:on
}
}

View File

@@ -0,0 +1,14 @@
package mc.protocol.utils;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public enum ScoreboardObjectiveMode {
CREATE(0),
REMOVE(1),
UPDATE(2);
@Getter
private final int code;
}

View File

@@ -0,0 +1,6 @@
package mc.protocol.utils;
public enum ScoreboardObjectiveType {
INTEGER,
HEARTS
}

View File

@@ -0,0 +1,10 @@
package mc.protocol.utils;
import lombok.experimental.UtilityClass;
@UtilityClass
public class ScoreboardPosition {
public final int LIST = 0;
public final int SIDEBAR = 1;
public final int BELOW_NAME = 2;
}

View File

@@ -0,0 +1,13 @@
package mc.protocol.utils;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public enum ScoreboardUpdateScoreAction {
CREATE_OR_UPDATE(0),
REMOVE(1);
@Getter
private final int code;
}

View File

@@ -0,0 +1,23 @@
package mc.protocol.utils;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;
public class Table<C, R, V> {
private final Map<C, Map<R, V>> map = new HashMap<>();
@Nullable
public V getColumnAndRow(C column, R row) {
if (!map.containsKey(column)) {
return null;
}
return map.get(column).get(row);
}
public void put(C column, R row, V value) {
map.computeIfAbsent(column, c -> new HashMap<>()).put(row, value);
}
}

View File

@@ -0,0 +1,15 @@
package mc.protocol.utils;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public enum TeamsCollisionRule {
ALWAYS("always"),
PUSH_OTHER_TEAMS("pushOtherTeams"),
PUSH_OWN_TEAM("pushOwnTeam"),
NEVER("never");
@Getter
private final String code;
}

View File

@@ -0,0 +1,16 @@
package mc.protocol.utils;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public enum TeamsMode {
CREATE(0),
REMOVE(1),
UPDATE(2),
ADD_MEMBER(3),
REMOVE_MEMBER(4);
@Getter
private final int code;
}

View File

@@ -0,0 +1,15 @@
package mc.protocol.utils;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public enum TeamsNameTagVisibility {
ALWAYS("always"),
HIDE_FOR_OTHER_TEAMS("hideForOtherTeams"),
HIDE_FOR_OWN_TEAM("hideForOwnTeam"),
NEVER("never");
@Getter
private final String code;
}

View File

@@ -7,16 +7,15 @@ import joptsimple.OptionParser;
import joptsimple.OptionSet; import joptsimple.OptionSet;
import joptsimple.util.PathConverter; import joptsimple.util.PathConverter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import mc.protocol.NettyServer; import mc.protocol.State;
import mc.protocol.ProtocolConstant; import mc.protocol.api.Server;
import mc.protocol.model.ServerInfo; import mc.protocol.di.DaggerProtocolComponent;
import mc.protocol.di.ProtocolComponent;
import mc.protocol.di.ProtocolModule;
import mc.protocol.packets.PingPacket; import mc.protocol.packets.PingPacket;
import mc.protocol.packets.client.HandshakePacket; import mc.protocol.packets.client.HandshakePacket;
import mc.protocol.packets.client.LoginStartPacket; import mc.protocol.packets.client.LoginStartPacket;
import mc.protocol.packets.client.StatusServerRequestPacket; import mc.protocol.packets.client.StatusServerRequestPacket;
import mc.protocol.packets.server.DisconnectPacket;
import mc.protocol.packets.server.StatusServerResponse;
import mc.protocol.serializer.TextSerializer;
import mc.server.config.Config; import mc.server.config.Config;
import mc.server.di.ConfigModule; import mc.server.di.ConfigModule;
import mc.server.di.DaggerServerComponent; import mc.server.di.DaggerServerComponent;
@@ -30,8 +29,6 @@ import java.io.OutputStream;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.Base64;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@@ -52,45 +49,21 @@ public class Main {
Config config = serverComponent.getConfig(); Config config = serverComponent.getConfig();
NettyServer server = NettyServer.createServer(); ProtocolComponent protocolComponent = DaggerProtocolComponent.builder()
.protocolModule(new ProtocolModule(true))
.build();
server.packetFlux(HandshakePacket.class) Server server = protocolComponent.getServer();
.doOnNext(channel -> log.info("{}", channel.getPacket())) PacketHandler packetHandler = serverComponent.getPacketHandler();
.subscribe(channel -> channel.setState(channel.getPacket().getNextState()));
server.packetFlux(PingPacket.class) server.onNewConnect(connectionContext -> connectionContext.setState(State.HANDSHAKING));
.doOnNext(channel -> log.info("{}", channel.getPacket())) server.onDisonnect(connectionContext -> connectionContext.setState(null));
.subscribe(channel -> channel.getCtx().writeAndFlush(channel.getPacket()).channel().disconnect());
server.packetFlux(StatusServerRequestPacket.class) server.listenPacket(State.HANDSHAKING, HandshakePacket.class, packetHandler::onHandshake);
.doOnNext(channel -> log.info("{}", channel.getPacket())) server.listenPacket(State.STATUS, PingPacket.class, packetHandler::onKeepAlive);
.subscribe(channel -> { server.listenPacket(State.STATUS, StatusServerRequestPacket.class, packetHandler::onServerStatus);
ServerInfo serverInfo = new ServerInfo(); server.listenPacket(State.LOGIN, LoginStartPacket.class, packetHandler::onLoginStart);
serverInfo.version().name(ProtocolConstant.PROTOCOL_NAME); server.listenPacket(State.PLAY, PingPacket.class, packetHandler::onKeepAlivePlay);
serverInfo.version().protocol(ProtocolConstant.PROTOCOL_NUMBER);
serverInfo.players().max(config.players().maxOnlile());
serverInfo.players().online(config.players().onlile());
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);
channel.getCtx().writeAndFlush(response);
});
server.packetFlux(LoginStartPacket.class)
.doOnNext(channel -> log.info("{}", channel.getPacket()))
.subscribe(channel -> {
DisconnectPacket disconnectPacket = new DisconnectPacket();
disconnectPacket.setReason(TextSerializer.fromPlain(config.disconnectReason()));
channel.getCtx().writeAndFlush(disconnectPacket).channel().disconnect();
});
server.bind(config.server().host(), config.server().port()); server.bind(config.server().host(), config.server().port());
} }
@@ -159,17 +132,6 @@ public class Main {
return optionParser; return optionParser;
} }
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 "";
}
}
private static boolean initializeCheckFiles(Path... paths) { private static boolean initializeCheckFiles(Path... paths) {
for (Path path : paths) { for (Path path : paths) {
if (Files.exists(path)) { if (Files.exists(path)) {

View File

@@ -0,0 +1,167 @@
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.PingPacket;
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.*;
import mc.server.config.Config;
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.UUID;
@Slf4j
@RequiredArgsConstructor
public class PacketHandler {
private final Random random = new Random(System.currentTimeMillis());
private final Config config;
public void onHandshake(ConnectionContext context, HandshakePacket packet) {
context.setState(packet.getNextState());
}
public void onKeepAlive(ConnectionContext context, PingPacket packet) {
context.sendNow(packet);
context.disconnect();
}
public void onKeepAlivePlay(ConnectionContext context, PingPacket packet) {
context.sendNow(packet);
}
@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());
serverInfo.players().online(config.players().onlile());
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);
}
public void onLoginStart(ConnectionContext context, LoginStartPacket loginStartPacket) {
UUID playerUuid = UUID.randomUUID();
int playerEid = random.nextInt();
String playerName = loginStartPacket.getName();
var loginSuccessPacket = new LoginSuccessPacket();
loginSuccessPacket.setUuid(playerUuid);
loginSuccessPacket.setName(playerName);
context.sendNow(loginSuccessPacket);
context.setState(State.PLAY);
var joinGamePacket = new JoinGamePacket();
joinGamePacket.setEntityId(playerEid);
joinGamePacket.setGameMode(GameMode.SPECTATOR);
joinGamePacket.setDimension(0/*Overworld*/);
joinGamePacket.setDifficulty(Difficulty.PEACEFUL);
joinGamePacket.setLevelType(LevelType.FLAT);
context.send(joinGamePacket);
Location spawnLocation = new Location(0d, 63d, 0d);
var spawnPositionPacket = new SpawnPositionPacket();
spawnPositionPacket.setSpawn(spawnLocation);
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();
var chunkDataPacket = new ChunkDataPacket();
chunkDataPacket.setX(0);
chunkDataPacket.setZ(0);
context.sendNow(chunkDataPacket);
var playerPositionAndLookPacket = new SPlayerPositionAndLookPacket();
playerPositionAndLookPacket.setPosition(spawnLocation);
playerPositionAndLookPacket.setLook(new Look(0f, 0f));
playerPositionAndLookPacket.setTeleportId(random.nextInt());
context.send(playerPositionAndLookPacket);
PingPacket pingPacket = new PingPacket();
pingPacket.setPayload(System.currentTimeMillis());
context.send(pingPacket);
context.flushSending();
// --- Эксперименты --- //
String scoreboardName = "Score::List";
var scoreboardObjectivePacket = new ScoreboardObjectivePacket();
scoreboardObjectivePacket.setObjectiveName(scoreboardName);
scoreboardObjectivePacket.setMode(ScoreboardObjectiveMode.CREATE);
scoreboardObjectivePacket.setObjectiveValue(scoreboardName);
scoreboardObjectivePacket.setType(ScoreboardObjectiveType.INTEGER);
context.send(scoreboardObjectivePacket);
var scoreboardDisplayPacket = new ScoreboardDisplayPacket();
scoreboardDisplayPacket.setPosition(ScoreboardPosition.SIDEBAR);
scoreboardDisplayPacket.setScoreName(scoreboardName);
context.send(scoreboardDisplayPacket);
var scoreboardUpdateScorePacket = new ScoreboardUpdateScorePacket();
scoreboardUpdateScorePacket.setEntityName(playerName);
scoreboardUpdateScorePacket.setAction(ScoreboardUpdateScoreAction.CREATE_OR_UPDATE);
scoreboardUpdateScorePacket.setObjective(scoreboardName);
scoreboardUpdateScorePacket.setValue(100500);
context.send(scoreboardUpdateScorePacket);
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

@@ -0,0 +1,15 @@
package mc.server.di;
import dagger.Module;
import dagger.Provides;
import mc.server.PacketHandler;
import mc.server.config.Config;
@Module
public class PacketHandlerModule {
@Provides
public PacketHandler providePacketHandler(Config config) {
return new PacketHandler(config);
}
}

View File

@@ -1,10 +1,14 @@
package mc.server.di; package mc.server.di;
import dagger.Component; import dagger.Component;
import mc.server.PacketHandler;
import mc.server.config.Config; import mc.server.config.Config;
@Component(modules = ConfigModule.class) @Component(modules = {
ConfigModule.class, PacketHandlerModule.class
})
public interface ServerComponent { public interface ServerComponent {
Config getConfig(); Config getConfig();
PacketHandler getPacketHandler();
} }