Compare commits
41 Commits
1.0
...
dev/experi
| Author | SHA1 | Date | |
|---|---|---|---|
|
346d64aea5
|
|||
|
c6669af651
|
|||
|
3984ab3fca
|
|||
|
bc2d5a7e75
|
|||
|
2521860bb4
|
|||
|
091b5adb91
|
|||
|
4c20c7fd02
|
|||
|
0aaf17b17f
|
|||
|
d02a80299f
|
|||
|
a3eb0eba86
|
|||
|
de43210747
|
|||
|
39996f9847
|
|||
|
9b183b7d8d
|
|||
|
5f431ff138
|
|||
|
c4a6a01908
|
|||
|
205e813fc4
|
|||
|
0f1c9bfb1b
|
|||
|
b77d6b16e8
|
|||
|
87dc18f009
|
|||
|
7d4c6e383e
|
|||
|
de27654e67
|
|||
|
627cee9af3
|
|||
|
31059a4ad8
|
|||
|
5833aab62a
|
|||
|
052593bc14
|
|||
|
531b0b97c1
|
|||
|
1a4600bdc9
|
|||
|
b768ba5bd9
|
|||
|
18a857193f
|
|||
|
a1a629279c
|
|||
|
824fdf9569
|
|||
|
0835683294
|
|||
|
2bd7fe9841
|
|||
|
23fd4e2c1a
|
|||
|
e5856b3d11
|
|||
|
17189effca
|
|||
|
bbeb41dd7e
|
|||
|
3b3a80ca0a
|
|||
|
a317e3e2c2
|
|||
|
19c1666c2e
|
|||
|
3282a3e9c0
|
@@ -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
|
||||||
@@ -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 = [
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,33 @@ 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,
|
||||||
|
0x15, EntityActionPacket.class
|
||||||
|
),
|
||||||
|
// client bound
|
||||||
|
Map.of(
|
||||||
|
PingPacket.class, 0x1F,
|
||||||
|
ChunkDataPacket.class, 0x20,
|
||||||
|
JoinGamePacket.class, 0x23,
|
||||||
|
PlayerAbilitiesPacket.class,0x2C,
|
||||||
|
SPlayerPositionAndLookPacket.class, 0x2F,
|
||||||
|
SetExperiencePacket.class, 0x40,
|
||||||
|
SpawnPositionPacket.class, 0x46
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
18
protocol/src/main/java/mc/protocol/api/Server.java
Normal file
18
protocol/src/main/java/mc/protocol/api/Server.java
Normal 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);
|
||||||
|
}
|
||||||
42
protocol/src/main/java/mc/protocol/di/PoolModule.java
Normal file
42
protocol/src/main/java/mc/protocol/di/PoolModule.java
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
17
protocol/src/main/java/mc/protocol/event/EventBus.java
Normal file
17
protocol/src/main/java/mc/protocol/event/EventBus.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
26
protocol/src/main/java/mc/protocol/event/SimpleEventBus.java
Normal file
26
protocol/src/main/java/mc/protocol/event/SimpleEventBus.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
12
protocol/src/main/java/mc/protocol/model/Location.java
Normal file
12
protocol/src/main/java/mc/protocol/model/Location.java
Normal 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;
|
||||||
|
}
|
||||||
11
protocol/src/main/java/mc/protocol/model/Look.java
Normal file
11
protocol/src/main/java/mc/protocol/model/Look.java
Normal 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;
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
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.utils.EntityActionAction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entity Action packet.
|
||||||
|
*
|
||||||
|
* <p>Структура пакета</p>
|
||||||
|
* <pre>
|
||||||
|
* | FIELD | TYPE | NOTES |
|
||||||
|
* |------------|--------|-------------------------------------------|
|
||||||
|
* | Entity ID | VarInt | ID игрока |
|
||||||
|
* | Action ID | VarInt | ID действия |
|
||||||
|
* | Jump Boost | VarInt | Используется только при "Action ID" = 5. |
|
||||||
|
* | | | В этом случае значение будет от 0 до 100. |
|
||||||
|
* | | | В остальных случаях значение 0. |
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Entity_Action" target="_top">Entity Action</a>
|
||||||
|
*/
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Getter
|
||||||
|
@EqualsAndHashCode
|
||||||
|
@ToString
|
||||||
|
public class EntityActionPacket implements ClientSidePacket {
|
||||||
|
|
||||||
|
private Integer entityId;
|
||||||
|
private EntityActionAction action;
|
||||||
|
private Integer jumpBoost;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readSelf(NetByteBuf netByteBuf) {
|
||||||
|
this.entityId = netByteBuf.readVarInt();
|
||||||
|
int actionId = netByteBuf.readVarInt();
|
||||||
|
this.jumpBoost = netByteBuf.readVarInt();
|
||||||
|
|
||||||
|
this.action = EntityActionAction.valueOfCode(actionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void passivate() {
|
||||||
|
this.entityId = null;
|
||||||
|
this.action = null;
|
||||||
|
this.jumpBoost = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,4 +34,9 @@ public class LoginStartPacket implements ClientSidePacket {
|
|||||||
this.name = netByteBuf.readString();
|
this.name = netByteBuf.readString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void passivate() {
|
||||||
|
this.name = null;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 */
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package mc.protocol.packets.server;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import mc.protocol.io.NetByteBuf;
|
||||||
|
import mc.protocol.packets.ServerSidePacket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Experience packet.
|
||||||
|
*
|
||||||
|
* <p>Структура пакета</p>
|
||||||
|
* <pre>
|
||||||
|
* | FIELD | TYPE | NOTES |
|
||||||
|
* |------------------|----------|------------------------|
|
||||||
|
* | Experience bar | Float | Значение от 0.0 до 1.0 |
|
||||||
|
* | Level | VarInt | |
|
||||||
|
* | Total Experience | VarInt | |
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Set_Experience">Set Experience</a>
|
||||||
|
* @see <a href="https://minecraft.fandom.com/wiki/Experience#Leveling_up">Experience: Leveling up</a>
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class SetExperiencePacket implements ServerSidePacket {
|
||||||
|
|
||||||
|
private float experienceBar;
|
||||||
|
private int level;
|
||||||
|
private int totalExperience;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeSelf(NetByteBuf netByteBuf) {
|
||||||
|
netByteBuf.writeFloat(this.experienceBar);
|
||||||
|
netByteBuf.writeVarInt(this.level);
|
||||||
|
netByteBuf.writeVarInt(this.totalExperience);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
30
protocol/src/main/java/mc/protocol/pool/PacketFactory.java
Normal file
30
protocol/src/main/java/mc/protocol/pool/PacketFactory.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
26
protocol/src/main/java/mc/protocol/pool/PacketPool.java
Normal file
26
protocol/src/main/java/mc/protocol/pool/PacketPool.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
6
protocol/src/main/java/mc/protocol/pool/Passivable.java
Normal file
6
protocol/src/main/java/mc/protocol/pool/Passivable.java
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
package mc.protocol.pool;
|
||||||
|
|
||||||
|
public interface Passivable {
|
||||||
|
|
||||||
|
void passivate();
|
||||||
|
}
|
||||||
20
protocol/src/main/java/mc/protocol/utils/ChatMode.java
Normal file
20
protocol/src/main/java/mc/protocol/utils/ChatMode.java
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
15
protocol/src/main/java/mc/protocol/utils/Difficulty.java
Normal file
15
protocol/src/main/java/mc/protocol/utils/Difficulty.java
Normal 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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package mc.protocol.utils;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public enum EntityActionAction {
|
||||||
|
START_SNEAKING(0),
|
||||||
|
STOP_SNEAKING(1),
|
||||||
|
LEAVE_BED(2),
|
||||||
|
START_SPRINTING(3),
|
||||||
|
STOP_SPRINTING(4),
|
||||||
|
START_JUMP_WITH_HORSE(5),
|
||||||
|
STOP_JUMP_WITH_HORSE(6),
|
||||||
|
OPEN_HORSE_INVENTORY(7),
|
||||||
|
START_FLYING_WITH_ELYTRA(8);
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static EntityActionAction valueOfCode(int code) {
|
||||||
|
for (EntityActionAction action : EntityActionAction.values()) {
|
||||||
|
if (action.code == code) {
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final int code;
|
||||||
|
}
|
||||||
15
protocol/src/main/java/mc/protocol/utils/GameMode.java
Normal file
15
protocol/src/main/java/mc/protocol/utils/GameMode.java
Normal 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;
|
||||||
|
}
|
||||||
16
protocol/src/main/java/mc/protocol/utils/LevelType.java
Normal file
16
protocol/src/main/java/mc/protocol/utils/LevelType.java
Normal 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;
|
||||||
|
}
|
||||||
18
protocol/src/main/java/mc/protocol/utils/MainHand.java
Normal file
18
protocol/src/main/java/mc/protocol/utils/MainHand.java
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
23
protocol/src/main/java/mc/protocol/utils/Table.java
Normal file
23
protocol/src/main/java/mc/protocol/utils/Table.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)) {
|
||||||
|
|||||||
164
server/src/main/java/mc/server/PacketHandler.java
Normal file
164
server/src/main/java/mc/server/PacketHandler.java
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
package mc.server;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import mc.protocol.ProtocolConstant;
|
||||||
|
import mc.protocol.State;
|
||||||
|
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.Difficulty;
|
||||||
|
import mc.protocol.utils.GameMode;
|
||||||
|
import mc.protocol.utils.LevelType;
|
||||||
|
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;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("java:S2189")
|
||||||
|
public void onLoginStart(ConnectionContext context, LoginStartPacket loginStartPacket) {
|
||||||
|
var loginSuccessPacket = new LoginSuccessPacket();
|
||||||
|
loginSuccessPacket.setUuid(UUID.randomUUID());
|
||||||
|
loginSuccessPacket.setName(loginStartPacket.getName());
|
||||||
|
|
||||||
|
context.sendNow(loginSuccessPacket);
|
||||||
|
context.setState(State.PLAY);
|
||||||
|
|
||||||
|
var joinGamePacket = new JoinGamePacket();
|
||||||
|
joinGamePacket.setEntityId(random.nextInt());
|
||||||
|
joinGamePacket.setGameMode(GameMode.SURVIVAL);
|
||||||
|
joinGamePacket.setDimension(0/*Overworld*/);
|
||||||
|
joinGamePacket.setDifficulty(Difficulty.PEACEFUL);
|
||||||
|
joinGamePacket.setLevelType(LevelType.FLAT);
|
||||||
|
|
||||||
|
context.send(joinGamePacket);
|
||||||
|
|
||||||
|
Location spawnLocation = new Location(7d, 130d, 7d);
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
// -- Эксперименты -- //
|
||||||
|
|
||||||
|
var setExperiencePacket = new SetExperiencePacket();
|
||||||
|
setExperiencePacket.setExperienceBar(0f);
|
||||||
|
setExperiencePacket.setLevel(0);
|
||||||
|
setExperiencePacket.setTotalExperience(100);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
context.sendNow(setExperiencePacket);
|
||||||
|
|
||||||
|
setExperiencePacket.setExperienceBar(setExperiencePacket.getExperienceBar() + 0.01f);
|
||||||
|
setExperiencePacket.setLevel(setExperiencePacket.getLevel() + 1);
|
||||||
|
if (setExperiencePacket.getExperienceBar() > 1.0f) {
|
||||||
|
setExperiencePacket.setExperienceBar(0f);
|
||||||
|
setExperiencePacket.setLevel(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
TimeUnit.MILLISECONDS.sleep(10);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
server/src/main/java/mc/server/di/PacketHandlerModule.java
Normal file
15
server/src/main/java/mc/server/di/PacketHandlerModule.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user