refactoring
This commit is contained in:
@@ -3,19 +3,24 @@ package mc.protocol.handler.codec;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import mc.protocol.ProtocolAttributes;
|
||||
import mc.protocol.State;
|
||||
import mc.protocol.buffer.NetByteBuf;
|
||||
import mc.protocol.packets.ClientSidePacket;
|
||||
import mc.protocol.packets.UnknownPacket;
|
||||
import mc.protocol.pool.PacketObjectPool;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class ProtocolDecoder extends ByteToMessageDecoder {
|
||||
|
||||
private final boolean readUnknownPackets;
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) {
|
||||
ctx.channel().attr(ProtocolAttributes.STATE).set(State.HANDSHAKING);
|
||||
@@ -30,7 +35,17 @@ public class ProtocolDecoder extends ByteToMessageDecoder {
|
||||
Class<? extends ClientSidePacket> packetClass = state.getClientSidePacketById(packetId);
|
||||
if (packetClass == null) {
|
||||
log.warn("Unknown packet: State {} ; Id 0x{}", state, packetIdAsHexcode(packetId));
|
||||
netByteBuf.skipBytes(netByteBuf.readableBytes());
|
||||
|
||||
if (readUnknownPackets) {
|
||||
UnknownPacket unknownPacket = new UnknownPacket();
|
||||
unknownPacket.setState(state);
|
||||
unknownPacket.setId(packetId);
|
||||
unknownPacket.setDataSize(netByteBuf.readableBytes());
|
||||
unknownPacket.readSelf(netByteBuf);
|
||||
out.add(unknownPacket);
|
||||
} else {
|
||||
netByteBuf.skipBytes(netByteBuf.readableBytes());
|
||||
}
|
||||
} else {
|
||||
ClientSidePacket packet = PacketObjectPool.getInstance().getPool(packetClass).borrowObject();
|
||||
packet.readSelf(netByteBuf);
|
||||
|
||||
@@ -4,7 +4,7 @@ import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import mc.protocol.State;
|
||||
import mc.protocol.io.NetByteBuf;
|
||||
import mc.protocol.buffer.NetByteBuf;
|
||||
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
@@ -1,9 +0,0 @@
|
||||
apply from: rootDir.toPath().resolve('logic.gradle').toFile()
|
||||
|
||||
dependencies {
|
||||
implementation libs.netty
|
||||
implementation libs.json
|
||||
implementation libs.objpool
|
||||
|
||||
testImplementation libs.lang3
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
# suppress inspection "UnusedProperty" for whole file
|
||||
module.name=protocol
|
||||
@@ -1,85 +0,0 @@
|
||||
package mc.protocol;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.Accessors;
|
||||
import mc.protocol.api.ConnectionContext;
|
||||
import mc.protocol.packets.ServerSidePacket;
|
||||
import mc.protocol.pool.Passivable;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
@EqualsAndHashCode
|
||||
public class NettyConnectionContext implements ConnectionContext, Passivable {
|
||||
|
||||
@Accessors(chain = true)
|
||||
@Setter
|
||||
private ChannelHandlerContext ctx;
|
||||
|
||||
/**
|
||||
* @deprecated костыль
|
||||
*/
|
||||
@Deprecated
|
||||
@Getter
|
||||
@Setter
|
||||
private boolean usedContext;
|
||||
|
||||
@Override
|
||||
public State getState() {
|
||||
return ctx.channel().attr(NetworkAttributes.STATE).get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setState(State state) {
|
||||
ctx.channel().attr(NetworkAttributes.STATE).set(state);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated костыль
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
public <T> void setCustomProperty(String key, T value) {
|
||||
Map<String, Object> map = ctx.channel().attr(NetworkAttributes.CUSTOM_PROPERTIES).get();
|
||||
map.put(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated костыль
|
||||
*/
|
||||
@Deprecated
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> Optional<T> getCustomProperty(String key, Class<T> classResult) {
|
||||
Map<String, Object> map = ctx.channel().attr(NetworkAttributes.CUSTOM_PROPERTIES).get();
|
||||
return (Optional<T>) Optional.ofNullable(map.getOrDefault(key, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(ServerSidePacket packet) {
|
||||
ctx.write(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendNow(ServerSidePacket packet) {
|
||||
ctx.writeAndFlush(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flushSending() {
|
||||
ctx.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect() {
|
||||
ctx.disconnect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void passivate() {
|
||||
this.ctx = null;
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
package mc.protocol;
|
||||
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.handler.logging.LogLevel;
|
||||
import io.netty.handler.logging.LoggingHandler;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import mc.protocol.api.ConnectionContext;
|
||||
import mc.protocol.api.Server;
|
||||
import mc.protocol.event.EventBus;
|
||||
import mc.protocol.io.codec.ProtocolDecoder;
|
||||
import mc.protocol.io.codec.ProtocolEncoder;
|
||||
import mc.protocol.io.codec.ProtocolSplitter;
|
||||
import mc.protocol.packets.ClientSidePacket;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.inject.Provider;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class NettyServer implements Server {
|
||||
|
||||
private final Provider<ProtocolDecoder> protocolDecoderProvider;
|
||||
private final Provider<PacketInboundHandler> packetInboundHandlerProvider;
|
||||
private final EventBus eventBus;
|
||||
private Consumer<ConnectionContext> consumerNewConnection;
|
||||
private Consumer<ConnectionContext> consumerDisconnect;
|
||||
|
||||
@Override
|
||||
public void bind(String host, int port) {
|
||||
log.info("Network starting: {}:{}", host, port);
|
||||
|
||||
try {
|
||||
createServerBootstrap().bind(host, port).sync().channel().closeFuture().sync();
|
||||
} catch (InterruptedException e) {
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("{}: {}", e.getClass().getSimpleName(), e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewConnect(Consumer<ConnectionContext> consumer) {
|
||||
this.consumerNewConnection = consumer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisonnect(Consumer<ConnectionContext> consumer) {
|
||||
this.consumerDisconnect = consumer;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("java:S2326") // Сонар, ты бредишь
|
||||
public <P extends ClientSidePacket> void listenPacket(State state, Class<P> packetClass, EventBus.EventHandler<P> eventHandler) {
|
||||
this.eventBus.subscribe(state, packetClass, eventHandler);
|
||||
}
|
||||
|
||||
private ServerBootstrap createServerBootstrap() {
|
||||
ServerBootstrap bootstrap = new ServerBootstrap();
|
||||
|
||||
bootstrap.group(new NioEventLoopGroup(1), new NioEventLoopGroup())
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.childHandler(createChannelChannelInitializer());
|
||||
|
||||
return bootstrap;
|
||||
}
|
||||
|
||||
private ChannelInitializer<SocketChannel> createChannelChannelInitializer() {
|
||||
return new ChannelInitializer<>() {
|
||||
@Override
|
||||
protected void initChannel(@Nonnull SocketChannel socketChannel) {
|
||||
ChannelPipeline pipeline = socketChannel.pipeline();
|
||||
createChannelHandlerMap().forEach(pipeline::addLast);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private Map<String, ChannelHandler> createChannelHandlerMap() {
|
||||
Map<String, ChannelHandler> map = new LinkedHashMap<>();
|
||||
|
||||
map.put("packet_splitter", new ProtocolSplitter());
|
||||
map.put("logger", new LoggingHandler(LogLevel.DEBUG));
|
||||
|
||||
ProtocolDecoder protocolDecoder = protocolDecoderProvider.get();
|
||||
protocolDecoder.setConsumerNewConnection(consumerNewConnection);
|
||||
protocolDecoder.setConsumerDisconnect(consumerDisconnect);
|
||||
map.put("packet_decoder", protocolDecoder);
|
||||
|
||||
map.put("packet_encoder", new ProtocolEncoder());
|
||||
map.put("packet_handler", packetInboundHandlerProvider.get());
|
||||
|
||||
return map;
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package mc.protocol.api;
|
||||
|
||||
import mc.protocol.State;
|
||||
import mc.protocol.packets.ServerSidePacket;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public interface ConnectionContext {
|
||||
|
||||
/**
|
||||
* @deprecated костыль
|
||||
*/
|
||||
@Deprecated
|
||||
void setUsedContext(boolean value);
|
||||
|
||||
/**
|
||||
* @deprecated костыль
|
||||
*/
|
||||
@Deprecated
|
||||
boolean isUsedContext();
|
||||
|
||||
State getState();
|
||||
void setState(State state);
|
||||
|
||||
/**
|
||||
* @deprecated костыль
|
||||
*/
|
||||
@Deprecated
|
||||
<T> void setCustomProperty(String key, T value);
|
||||
|
||||
/**
|
||||
* @deprecated костыль
|
||||
*/
|
||||
@Deprecated
|
||||
<T> Optional<T> getCustomProperty(String key, Class<T> classResult);
|
||||
|
||||
void send(ServerSidePacket packet);
|
||||
void sendNow(ServerSidePacket packet);
|
||||
void flushSending();
|
||||
|
||||
void disconnect();
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package mc.protocol.api;
|
||||
|
||||
import mc.protocol.State;
|
||||
import mc.protocol.packets.ClientSidePacket;
|
||||
import mc.protocol.event.EventBus;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public interface Server {
|
||||
|
||||
void bind(String host, int port);
|
||||
|
||||
void onNewConnect(Consumer<ConnectionContext> consumer);
|
||||
void onDisonnect(Consumer<ConnectionContext> consumer);
|
||||
|
||||
@SuppressWarnings("java:S2326") // Сонар, ты бредишь
|
||||
<P extends ClientSidePacket> void listenPacket(State state, Class<P> packetClass, EventBus.EventHandler<P> eventHandler);
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package mc.protocol.di;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import mc.protocol.NettyConnectionContext;
|
||||
import mc.protocol.State;
|
||||
import mc.protocol.packets.ClientSidePacket;
|
||||
import mc.protocol.packets.UnknownPacket;
|
||||
import mc.protocol.pool.NettyConnectionContextFactory;
|
||||
import mc.protocol.pool.PacketFactory;
|
||||
import mc.protocol.pool.PacketPool;
|
||||
import org.apache.commons.pool2.ObjectPool;
|
||||
import org.apache.commons.pool2.impl.GenericObjectPool;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@Module
|
||||
public class PoolModule {
|
||||
|
||||
@Provides
|
||||
@ServerScope
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
PacketPool providePacketPool() {
|
||||
Map<Class<? extends ClientSidePacket>, ObjectPool> map = Stream.of(State.values())
|
||||
.flatMap(state -> state.getClientSidePackets().values().stream())
|
||||
.distinct()
|
||||
.collect(Collectors.toMap(
|
||||
packetClass -> packetClass,
|
||||
packetClass -> new GenericObjectPool(new PacketFactory<>(packetClass))));
|
||||
map.put(UnknownPacket.class, new GenericObjectPool(new PacketFactory<>(UnknownPacket.class)));
|
||||
|
||||
return new PacketPool(map);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ServerScope
|
||||
ObjectPool<NettyConnectionContext> providePoolNettyConnectionContext() {
|
||||
return new GenericObjectPool<>(new NettyConnectionContextFactory());
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package mc.protocol.di;
|
||||
|
||||
import dagger.Component;
|
||||
import mc.protocol.api.Server;
|
||||
|
||||
@Component(modules = {
|
||||
ProtocolModule.class,
|
||||
PoolModule.class
|
||||
})
|
||||
@ServerScope
|
||||
public interface ProtocolComponent {
|
||||
|
||||
Server getServer();
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package mc.protocol.di;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import mc.protocol.NettyConnectionContext;
|
||||
import mc.protocol.NettyServer;
|
||||
import mc.protocol.PacketInboundHandler;
|
||||
import mc.protocol.api.Server;
|
||||
import mc.protocol.event.EventBus;
|
||||
import mc.protocol.event.SimpleEventBus;
|
||||
import mc.protocol.io.codec.ProtocolDecoder;
|
||||
import mc.protocol.pool.PacketPool;
|
||||
import org.apache.commons.pool2.ObjectPool;
|
||||
|
||||
import javax.inject.Provider;
|
||||
|
||||
@Module
|
||||
@RequiredArgsConstructor
|
||||
public class ProtocolModule {
|
||||
|
||||
private final boolean readUnknownPackets;
|
||||
|
||||
@Provides
|
||||
@ServerScope
|
||||
Server provideServer(
|
||||
Provider<ProtocolDecoder> protocolDecoderProvider,
|
||||
Provider<PacketInboundHandler> packetInboundHandlerProvider,
|
||||
EventBus eventBus
|
||||
) {
|
||||
return new NettyServer(protocolDecoderProvider, packetInboundHandlerProvider, eventBus);
|
||||
}
|
||||
|
||||
@Provides
|
||||
ProtocolDecoder provideProtocolDecoder(
|
||||
ObjectPool<NettyConnectionContext> poolNettyConnectionContext,
|
||||
PacketPool poolPackets
|
||||
) {
|
||||
return new ProtocolDecoder(readUnknownPackets, poolNettyConnectionContext, poolPackets);
|
||||
}
|
||||
|
||||
@Provides
|
||||
PacketInboundHandler providePacketInboundHandler(
|
||||
ObjectPool<NettyConnectionContext> poolNettyConnectionContext,
|
||||
PacketPool packetPool,
|
||||
EventBus eventBus
|
||||
) {
|
||||
return new PacketInboundHandler(poolNettyConnectionContext, packetPool, eventBus);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@ServerScope
|
||||
EventBus provideEventBus() {
|
||||
return new SimpleEventBus();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package mc.protocol.di;
|
||||
|
||||
import javax.inject.Scope;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
@Scope
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface ServerScope {
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package mc.protocol.event;
|
||||
|
||||
import mc.protocol.State;
|
||||
import mc.protocol.api.ConnectionContext;
|
||||
import mc.protocol.packets.ClientSidePacket;
|
||||
|
||||
public interface EventBus {
|
||||
|
||||
<P extends ClientSidePacket> void subscribe(State state, Class<P> packetClass, EventHandler<P> eventHandler);
|
||||
|
||||
<P extends ClientSidePacket> void emit(State state, ConnectionContext channelContext, P packet);
|
||||
|
||||
@FunctionalInterface
|
||||
interface EventHandler<P extends ClientSidePacket> {
|
||||
void handle(ConnectionContext channelContext, P packet);
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package mc.protocol.event;
|
||||
|
||||
import mc.protocol.State;
|
||||
import mc.protocol.api.ConnectionContext;
|
||||
import mc.protocol.packets.ClientSidePacket;
|
||||
import mc.protocol.utils.Table;
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
public class SimpleEventBus implements EventBus {
|
||||
|
||||
private final Table<State, Class<? extends ClientSidePacket>, EventHandler> table = new Table<>();
|
||||
|
||||
@Override
|
||||
public <P extends ClientSidePacket> void subscribe(State state, Class<P> packetClass, EventHandler<P> eventHandler) {
|
||||
table.put(state, packetClass, eventHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <P extends ClientSidePacket> void emit(State state, ConnectionContext channelContext, P packet) {
|
||||
EventHandler eventHandler = table.getColumnAndRow(state, packet.getClass());
|
||||
|
||||
if (eventHandler != null) {
|
||||
eventHandler.handle(channelContext, packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package mc.protocol.pool;
|
||||
|
||||
import mc.protocol.NettyConnectionContext;
|
||||
import org.apache.commons.pool2.BasePooledObjectFactory;
|
||||
import org.apache.commons.pool2.PooledObject;
|
||||
import org.apache.commons.pool2.impl.DefaultPooledObject;
|
||||
|
||||
public class NettyConnectionContextFactory extends BasePooledObjectFactory<NettyConnectionContext> {
|
||||
|
||||
@Override
|
||||
public NettyConnectionContext create() throws Exception {
|
||||
return new NettyConnectionContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PooledObject<NettyConnectionContext> wrap(NettyConnectionContext context) {
|
||||
return new DefaultPooledObject<>(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void passivateObject(PooledObject<NettyConnectionContext> pooledObj) {
|
||||
pooledObj.getObject().passivate();
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package mc.protocol.model.text;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class TextTest {
|
||||
|
||||
@Test
|
||||
void contentTest() {
|
||||
Text actual;
|
||||
Text expected;
|
||||
|
||||
actual = Text.builder().append("123").build();
|
||||
expected = new Text(null, null, "123", null);
|
||||
assertEquals(expected, actual);
|
||||
|
||||
actual = Text.builder().append("123").append(Text.of("456")).build();
|
||||
expected = new Text(null, null, "123", List.of(Text.of("456")));
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package mc.protocol.serializer;
|
||||
|
||||
import mc.protocol.model.text.Text;
|
||||
import mc.protocol.model.text.TextColor;
|
||||
import mc.protocol.model.text.TextStyle;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class TextSerializerTest {
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("paramsPlain")
|
||||
void fromPlain(String sample, Text expected) {
|
||||
Text actual = TextSerializer.fromPlain(sample);
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
static Stream<Arguments> paramsPlain() {
|
||||
return Stream.of(
|
||||
Arguments.of("text", Text.of("text")),
|
||||
Arguments.of("&&text", Text.of("&text")),
|
||||
Arguments.of("&ztext", Text.of("text")),
|
||||
Arguments.of("&4red_text", Text.of(TextColor.DARK_RED, "red_text")),
|
||||
Arguments.of("&l&4red_text", Text.of(TextColor.DARK_RED, TextStyle.BOLD, "red_text")),
|
||||
Arguments.of("&4&lred_text", Text.of(TextColor.DARK_RED, TextStyle.BOLD, "red_text")),
|
||||
|
||||
Arguments.of("&4red_text &eyellow_text", Text.builder()
|
||||
.color(TextColor.DARK_RED)
|
||||
.append("red_text ")
|
||||
.append(Text.of(TextColor.YELLOW, "yellow_text"))
|
||||
.build())
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,7 @@ public class NettyServer {
|
||||
socketChannel.pipeline()
|
||||
.addLast("packet_splitter", new ProtocolSplitter())
|
||||
.addLast("logger", new LoggingHandler(LogLevel.DEBUG))
|
||||
.addLast("packet_decoder", new ProtocolDecoder())
|
||||
.addLast("packet_decoder", new ProtocolDecoder(false))
|
||||
.addLast("packet_encoder", new ProtocolEncoder())
|
||||
.addLast("packet_handler", new ProtocolInboundHandler(protocolHandlersBus));
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package mc.server;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import mc.protocol.api.ConnectionContext;
|
||||
import mc.protocol.model.Location;
|
||||
import mc.protocol.utils.GameMode;
|
||||
|
||||
@@ -12,7 +12,7 @@ import java.util.UUID;
|
||||
@Getter
|
||||
public class Player {
|
||||
|
||||
private final ConnectionContext connectionContext;
|
||||
private final ChannelHandlerContext ctx;
|
||||
private final UUID uuid;
|
||||
private final String name;
|
||||
private final GameMode gameMode;
|
||||
@@ -1,9 +1,8 @@
|
||||
package mc.server.service;
|
||||
package mc.server;
|
||||
|
||||
import mc.protocol.api.ConnectionContext;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import mc.protocol.model.Location;
|
||||
import mc.protocol.utils.GameMode;
|
||||
import mc.server.Player;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.UUID;
|
||||
@@ -12,9 +11,8 @@ public class PlayerManager {
|
||||
|
||||
private final LinkedList<Player> players = new LinkedList<>();
|
||||
|
||||
public Player addAndCreate(ConnectionContext context, String name, GameMode gameMode, Location location) {
|
||||
context.setUsedContext(true);
|
||||
Player player = new Player(context, UUID.randomUUID(), name, gameMode, location);
|
||||
public Player create(ChannelHandlerContext ctx, String name, GameMode gameMode, Location location) {
|
||||
Player player = new Player(ctx, UUID.randomUUID(), name, gameMode, location);
|
||||
players.add(player);
|
||||
return player;
|
||||
}
|
||||
10
server-new/src/main/java/mc/server/ServetAttributes.java
Normal file
10
server-new/src/main/java/mc/server/ServetAttributes.java
Normal file
@@ -0,0 +1,10 @@
|
||||
package mc.server;
|
||||
|
||||
import io.netty.util.AttributeKey;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
@UtilityClass
|
||||
public class ServetAttributes {
|
||||
|
||||
public static final AttributeKey<Player> PLAYER = AttributeKey.newInstance("PLAYER");
|
||||
}
|
||||
@@ -2,14 +2,15 @@ package mc.server.di;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import mc.protocol.di.ServerScope;
|
||||
import mc.server.service.PlayerManager;
|
||||
import mc.server.PlayerManager;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
@Module
|
||||
public class PlayersModule {
|
||||
public class PlayerManagerModule {
|
||||
|
||||
@Provides
|
||||
@ServerScope
|
||||
@Singleton
|
||||
PlayerManager providePlayerManager() {
|
||||
return new PlayerManager();
|
||||
}
|
||||
@@ -5,11 +5,12 @@ import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import dagger.multibindings.IntoSet;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import mc.server.processor.PacketProcessor;
|
||||
import mc.server.processor.ProcessorHandshake;
|
||||
import mc.server.processor.ProcessorStatus;
|
||||
import mc.protocol.world.World;
|
||||
import mc.server.PlayerManager;
|
||||
import mc.server.processor.*;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Random;
|
||||
|
||||
@Module
|
||||
@RequiredArgsConstructor
|
||||
@@ -30,4 +31,18 @@ public class ProcessorModule {
|
||||
PacketProcessor provideProcessorStatus() {
|
||||
return new ProcessorStatus(config);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
@Singleton
|
||||
PacketProcessor provideProcessorLogin(PlayerManager playerManager, World world) {
|
||||
return new ProcessorLogin(playerManager, new Random(System.currentTimeMillis()), config, world);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IntoSet
|
||||
@Singleton
|
||||
PacketProcessor provideProcessorPlay() {
|
||||
return new ProcessorPlay();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,10 @@ import mc.server.processor.PacketProcessor;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Set;
|
||||
|
||||
@Component(modules = {ServerModule.class, ProcessorModule.class})
|
||||
@Component(modules = {
|
||||
ServerModule.class, ProcessorModule.class, PlayerManagerModule.class,
|
||||
WorldModule.class
|
||||
})
|
||||
@Singleton
|
||||
public interface ServerComponent {
|
||||
|
||||
|
||||
@@ -2,16 +2,17 @@ package mc.server.di;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import mc.protocol.di.ServerScope;
|
||||
import mc.protocol.world.World;
|
||||
import mc.server.world.VoidWorld;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
@Module
|
||||
public class WorldModule {
|
||||
|
||||
@Provides
|
||||
@ServerScope
|
||||
public World provideWorld() {
|
||||
@Singleton
|
||||
World provideWorld() {
|
||||
return new VoidWorld();
|
||||
}
|
||||
}
|
||||
142
server-new/src/main/java/mc/server/processor/ProcessorLogin.java
Normal file
142
server-new/src/main/java/mc/server/processor/ProcessorLogin.java
Normal file
@@ -0,0 +1,142 @@
|
||||
package mc.server.processor;
|
||||
|
||||
import com.typesafe.config.Config;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import mc.protocol.ProtocolAttributes;
|
||||
import mc.protocol.State;
|
||||
import mc.protocol.handler.ProtocolHandlersBus;
|
||||
import mc.protocol.model.Location;
|
||||
import mc.protocol.model.Look;
|
||||
import mc.protocol.packets.KeepAlivePacket;
|
||||
import mc.protocol.packets.login.client.LoginStartPacket;
|
||||
import mc.protocol.packets.login.server.LoginSuccessPacket;
|
||||
import mc.protocol.packets.play.server.*;
|
||||
import mc.protocol.utils.Difficulty;
|
||||
import mc.protocol.utils.GameMode;
|
||||
import mc.protocol.world.Chunk;
|
||||
import mc.protocol.world.World;
|
||||
import mc.server.Player;
|
||||
import mc.server.PlayerManager;
|
||||
import mc.server.ServetAttributes;
|
||||
import mc.server.util.LocationUtils;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class ProcessorLogin implements PacketProcessor {
|
||||
|
||||
private final PlayerManager playerManager;
|
||||
private final Random random;
|
||||
private final Config config;
|
||||
private final World world;
|
||||
|
||||
@Override
|
||||
public void setup(ProtocolHandlersBus protocolHandlersBus) {
|
||||
protocolHandlersBus.addHandler(State.LOGIN, LoginStartPacket.class, this::login);
|
||||
}
|
||||
|
||||
private void login(ChannelHandlerContext ctx, LoginStartPacket packet) {
|
||||
Player player = playerManager.create(ctx, packet.getName(), GameMode.SURVIVAL, world.getSpawn());
|
||||
ctx.channel().attr(ServetAttributes.PLAYER).set(player);
|
||||
|
||||
sendLoginSuccess(player);
|
||||
|
||||
sendJoinGame(player);
|
||||
sendSpawnPosition(player);
|
||||
sendPlayerAbilities(player);
|
||||
ctx.flush();
|
||||
|
||||
sendWorldData(player);
|
||||
|
||||
sendPlayerPositionAndLook(player);
|
||||
sendKeepAlive(player);
|
||||
ctx.flush();
|
||||
}
|
||||
|
||||
private void sendLoginSuccess(Player player) {
|
||||
var loginSuccessPacket = new LoginSuccessPacket();
|
||||
loginSuccessPacket.setUuid(player.getUuid());
|
||||
loginSuccessPacket.setName(player.getName());
|
||||
|
||||
player.getCtx().writeAndFlush(loginSuccessPacket);
|
||||
player.getCtx().channel().attr(ProtocolAttributes.STATE).set(State.PLAY);
|
||||
}
|
||||
|
||||
private void sendJoinGame(Player player) {
|
||||
var joinGamePacket = new JoinGamePacket();
|
||||
joinGamePacket.setEntityId(random.nextInt());
|
||||
joinGamePacket.setGameMode(player.getGameMode());
|
||||
joinGamePacket.setDimension(0/*Overworld*/);
|
||||
joinGamePacket.setDifficulty(Difficulty.PEACEFUL);
|
||||
joinGamePacket.setLevelType(world.getLevelType());
|
||||
|
||||
player.getCtx().write(joinGamePacket);
|
||||
}
|
||||
|
||||
private void sendSpawnPosition(Player player) {
|
||||
var spawnPositionPacket = new SpawnPositionPacket();
|
||||
spawnPositionPacket.setSpawn(player.getLocation());
|
||||
|
||||
player.getCtx().write(spawnPositionPacket);
|
||||
}
|
||||
|
||||
private void sendPlayerAbilities(Player player) {
|
||||
var playerAbilitiesPacket = new PlayerAbilitiesPacket();
|
||||
playerAbilitiesPacket.setCatFly(true);
|
||||
playerAbilitiesPacket.setFlying(true);
|
||||
playerAbilitiesPacket.setCreativeMode(false);
|
||||
playerAbilitiesPacket.setInvulnerable(true);
|
||||
playerAbilitiesPacket.setFieldOfView(0.0f);
|
||||
playerAbilitiesPacket.setFlyingSpeed(0.05f);
|
||||
|
||||
player.getCtx().write(playerAbilitiesPacket);
|
||||
}
|
||||
|
||||
@SuppressWarnings("java:S2589")
|
||||
private void sendWorldData(Player player) {
|
||||
Location chunkLocation = LocationUtils.toChunkXZ(player.getLocation());
|
||||
Chunk chunk = world.getChunk((int) chunkLocation.getX(), (int) chunkLocation.getZ());
|
||||
|
||||
var chunkDataPacket = new ChunkDataPacket();
|
||||
chunkDataPacket.setX(chunk.getX());
|
||||
chunkDataPacket.setZ(chunk.getZ());
|
||||
|
||||
player.getCtx().write(chunkDataPacket);
|
||||
|
||||
for (int i = 1; i <= config.getInt("world.view-distance"); i++) {
|
||||
int minX = (int) chunkLocation.getX() - i;
|
||||
int minZ = (int) chunkLocation.getZ() - i;
|
||||
int maxX = (int) chunkLocation.getX() + i;
|
||||
int maxZ = (int) chunkLocation.getZ() + i;
|
||||
for (int z = minZ; z <= maxZ; z++) {
|
||||
for (int x = minX; x <= maxX; x++) {
|
||||
if ((z == minZ || z == maxZ) || (x == minX || x == maxX)) {
|
||||
chunkDataPacket = new ChunkDataPacket();
|
||||
chunkDataPacket.setX(x);
|
||||
chunkDataPacket.setZ(z);
|
||||
player.getCtx().write(chunkDataPacket);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
player.getCtx().flush();
|
||||
}
|
||||
|
||||
private void sendPlayerPositionAndLook(Player player) {
|
||||
var playerPositionAndLookPacket = new SPlayerPositionAndLookPacket();
|
||||
playerPositionAndLookPacket.setPosition(player.getLocation());
|
||||
playerPositionAndLookPacket.setLook(new Look().set(0f, 0f));
|
||||
playerPositionAndLookPacket.setTeleportId(random.nextInt());
|
||||
|
||||
player.getCtx().write(playerPositionAndLookPacket);
|
||||
}
|
||||
|
||||
private void sendKeepAlive(Player player) {
|
||||
var keepAlivePacket = new KeepAlivePacket();
|
||||
keepAlivePacket.setPayload(System.currentTimeMillis());
|
||||
|
||||
player.getCtx().write(keepAlivePacket);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package mc.server.processor;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import mc.protocol.State;
|
||||
import mc.protocol.handler.ProtocolHandlersBus;
|
||||
import mc.protocol.packets.KeepAlivePacket;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Slf4j
|
||||
public class ProcessorPlay implements PacketProcessor {
|
||||
|
||||
@Override
|
||||
public void setup(ProtocolHandlersBus protocolHandlersBus) {
|
||||
protocolHandlersBus
|
||||
.addHandler(State.PLAY, KeepAlivePacket.class, this::keepAlive);
|
||||
}
|
||||
|
||||
private void keepAlive(ChannelHandlerContext ctx, KeepAlivePacket packet) {
|
||||
try {
|
||||
TimeUnit.MILLISECONDS.sleep(50);
|
||||
ctx.writeAndFlush(packet);
|
||||
} catch (InterruptedException e) {
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("{}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
12
server-new/src/main/java/mc/server/util/LocationUtils.java
Normal file
12
server-new/src/main/java/mc/server/util/LocationUtils.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package mc.server.util;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
import mc.protocol.model.Location;
|
||||
|
||||
@UtilityClass
|
||||
public class LocationUtils {
|
||||
|
||||
public Location toChunkXZ(Location location) {
|
||||
return new Location().set((int) location.getX() >> 4, 0d, (int) location.getZ() >> 4);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
package mc.server.world;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Data;
|
||||
import mc.protocol.world.Chunk;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
@Data
|
||||
public class VoidChunk implements Chunk {
|
||||
|
||||
private final int x;
|
||||
@@ -7,7 +7,7 @@ import mc.protocol.world.World;
|
||||
|
||||
public class VoidWorld implements World {
|
||||
|
||||
private static final Location spawn = new Location(7d, 130d, 7d);
|
||||
private static final Location spawn = new Location().set(7d, 130d, 7d);
|
||||
|
||||
@Override
|
||||
public LevelType getLevelType() {
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
Запуск
|
||||
gradle :server:run --args="--config=config.yml --logconfig==logback.xml"
|
||||
|
||||
Сборка
|
||||
gradle :server:shadowJar
|
||||
*/
|
||||
|
||||
//file:noinspection GrUnresolvedAccess
|
||||
plugins {
|
||||
id 'com.github.johnrengelman.shadow' version '7.0.0'
|
||||
}
|
||||
|
||||
apply from: rootDir.toPath().resolve('logic.gradle').toFile()
|
||||
apply plugin: 'application'
|
||||
|
||||
application {
|
||||
mainClassName = 'mc.server.Main'
|
||||
|
||||
if (project.hasProperty('jvmArgs')) {
|
||||
applicationDefaultJvmArgs = List.of((project.jvmArgs as String).split('\\s+'))
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':protocol')
|
||||
|
||||
implementation libs.logger.logback
|
||||
|
||||
implementation libs.yaml
|
||||
implementation libs.ioutils
|
||||
implementation libs.jopt
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
archiveBaseName.set(jar.archiveBaseName.get())
|
||||
archiveVersion.set(project.version as String)
|
||||
archiveClassifier.set('')
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
# suppress inspection "UnusedProperty" for whole file
|
||||
module.name=server
|
||||
@@ -1,167 +0,0 @@
|
||||
package mc.server;
|
||||
|
||||
import ch.qos.logback.classic.LoggerContext;
|
||||
import ch.qos.logback.classic.joran.JoranConfigurator;
|
||||
import ch.qos.logback.core.joran.spi.JoranException;
|
||||
import joptsimple.OptionParser;
|
||||
import joptsimple.OptionSet;
|
||||
import joptsimple.util.PathConverter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import mc.protocol.State;
|
||||
import mc.protocol.api.Server;
|
||||
import mc.protocol.di.DaggerProtocolComponent;
|
||||
import mc.protocol.di.ProtocolComponent;
|
||||
import mc.protocol.di.ProtocolModule;
|
||||
import mc.protocol.packets.KeepAlivePacket;
|
||||
import mc.protocol.packets.client.HandshakePacket;
|
||||
import mc.protocol.packets.client.LoginStartPacket;
|
||||
import mc.protocol.packets.client.StatusServerRequestPacket;
|
||||
import mc.server.config.Config;
|
||||
import mc.server.di.ConfigModule;
|
||||
import mc.server.di.DaggerServerComponent;
|
||||
import mc.server.di.ServerComponent;
|
||||
import mc.server.service.PlayerManager;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@Slf4j
|
||||
@SuppressWarnings("java:S106")
|
||||
public class Main {
|
||||
private static final String CLI_CONFIG = "config";
|
||||
private static final String CLI_LOGCONFIG = "logconfig";
|
||||
|
||||
private void run(OptionSet optionSet) {
|
||||
log.info("mc-project launch");
|
||||
|
||||
ConfigModule configModule = new ConfigModule((Path) optionSet.valueOf(CLI_CONFIG));
|
||||
|
||||
ServerComponent serverComponent = DaggerServerComponent.builder()
|
||||
.configModule(configModule)
|
||||
.build();
|
||||
|
||||
Config config = serverComponent.getConfig();
|
||||
PlayerManager playerManager = serverComponent.getPlayerManager();
|
||||
|
||||
ProtocolComponent protocolComponent = DaggerProtocolComponent.builder()
|
||||
.protocolModule(new ProtocolModule(true))
|
||||
.build();
|
||||
|
||||
Server server = protocolComponent.getServer();
|
||||
PacketHandler packetHandler = serverComponent.getPacketHandler();
|
||||
|
||||
server.onNewConnect(connectionContext -> connectionContext.setState(State.HANDSHAKING));
|
||||
server.onDisonnect(connectionContext -> {
|
||||
connectionContext.setState(null);
|
||||
connectionContext.getCustomProperty("player", Player.class).ifPresent(playerManager::remove);
|
||||
});
|
||||
|
||||
server.listenPacket(State.HANDSHAKING, HandshakePacket.class, packetHandler::onHandshake);
|
||||
server.listenPacket(State.STATUS, KeepAlivePacket.class, packetHandler::onKeepAlive);
|
||||
server.listenPacket(State.STATUS, StatusServerRequestPacket.class, packetHandler::onServerStatus);
|
||||
server.listenPacket(State.LOGIN, LoginStartPacket.class, packetHandler::onLoginStart);
|
||||
server.listenPacket(State.PLAY, KeepAlivePacket.class, packetHandler::onKeepAlivePlay);
|
||||
|
||||
server.bind(config.server().host(), config.server().port());
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
OptionParser optionParser = createOptionParser();
|
||||
OptionSet optionSet = optionParser.parse(args);
|
||||
|
||||
if (optionSet.has("help")) {
|
||||
try {
|
||||
optionParser.printHelpOn(System.out);
|
||||
} catch (IOException e) {
|
||||
System.err.printf("Can't print help page: %s%n", e.getMessage());
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
return;
|
||||
} else if (optionSet.has("init")) {
|
||||
Path configPath = (Path) optionSet.valueOf(CLI_CONFIG);
|
||||
Path logbackPath = (Path) optionSet.valueOf(CLI_LOGCONFIG);
|
||||
|
||||
if (!initializeCheckFiles(configPath, logbackPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
InputStream configResource = Objects.requireNonNull(Main.class.getResourceAsStream("/config-sample.yml"));
|
||||
InputStream logbackResource = Objects.requireNonNull(Main.class.getResourceAsStream("/logback-sample.xml"));
|
||||
|
||||
try(OutputStream configOut = Files.newOutputStream(configPath);
|
||||
OutputStream logbackOut = Files.newOutputStream(logbackPath)) {
|
||||
IOUtils.copy(configResource, configOut);
|
||||
IOUtils.copy(logbackResource, logbackOut);
|
||||
}
|
||||
|
||||
System.out.println("Initialization environment done.");
|
||||
return;
|
||||
}
|
||||
|
||||
reconfigureLogback(optionSet);
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
optionSet.asMap().forEach((optionSpec, objects) -> {
|
||||
if (optionSpec.isForHelp()) return;
|
||||
log.debug("OptionSet | {} = {}", optionSpec.options(), objects);
|
||||
});
|
||||
}
|
||||
|
||||
new Main().run(optionSet);
|
||||
}
|
||||
|
||||
private static OptionParser createOptionParser() {
|
||||
OptionParser optionParser = new OptionParser();
|
||||
|
||||
optionParser.acceptsAll(List.of("h", "help"), "Help page").forHelp();
|
||||
optionParser.accepts("init", "Initialize environment");
|
||||
|
||||
optionParser.accepts(CLI_CONFIG, "Path to configuration file")
|
||||
.withRequiredArg()
|
||||
.withValuesConvertedBy(new PathConverter())
|
||||
.defaultsTo(Paths.get("config.yml"));
|
||||
|
||||
optionParser.accepts(CLI_LOGCONFIG, "Path to logger configuratuin file")
|
||||
.withRequiredArg()
|
||||
.withValuesConvertedBy(new PathConverter())
|
||||
.defaultsTo(Paths.get("logback.xml"));
|
||||
|
||||
return optionParser;
|
||||
}
|
||||
|
||||
private static boolean initializeCheckFiles(Path... paths) {
|
||||
for (Path path : paths) {
|
||||
if (Files.exists(path)) {
|
||||
System.err.printf("File '%s' already exist. Initialization environment canceled.%n",
|
||||
path.toAbsolutePath());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void reconfigureLogback(OptionSet optionSet) throws IOException {
|
||||
LoggerContext logbackContext = (LoggerContext) LoggerFactory.getILoggerFactory();
|
||||
logbackContext.reset();
|
||||
JoranConfigurator configurator = new JoranConfigurator();
|
||||
|
||||
Path logbackPath = (Path) optionSet.valueOf(CLI_LOGCONFIG);
|
||||
try(InputStream in = Objects.requireNonNull(
|
||||
Files.newInputStream(logbackPath), "File not found: " + logbackPath.toAbsolutePath())) {
|
||||
|
||||
configurator.setContext(logbackContext);
|
||||
configurator.doConfigure(in);
|
||||
} catch (JoranException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,178 +0,0 @@
|
||||
package mc.server;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import mc.protocol.*;
|
||||
import mc.protocol.api.ConnectionContext;
|
||||
import mc.protocol.model.Location;
|
||||
import mc.protocol.model.Look;
|
||||
import mc.protocol.model.ServerInfo;
|
||||
import mc.protocol.packets.KeepAlivePacket;
|
||||
import mc.protocol.packets.client.HandshakePacket;
|
||||
import mc.protocol.packets.client.LoginStartPacket;
|
||||
import mc.protocol.packets.client.StatusServerRequestPacket;
|
||||
import mc.protocol.packets.server.*;
|
||||
import mc.protocol.serializer.TextSerializer;
|
||||
import mc.protocol.utils.Difficulty;
|
||||
import mc.protocol.utils.GameMode;
|
||||
import mc.protocol.world.Chunk;
|
||||
import mc.protocol.world.World;
|
||||
import mc.server.config.Config;
|
||||
import mc.server.service.PlayerManager;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class PacketHandler {
|
||||
|
||||
private final Random random = new Random(System.currentTimeMillis());
|
||||
private final Config config;
|
||||
private final World world;
|
||||
private final PlayerManager playerManager;
|
||||
|
||||
public void onHandshake(ConnectionContext context, HandshakePacket packet) {
|
||||
context.setState(packet.getNextState());
|
||||
}
|
||||
|
||||
public void onKeepAlive(ConnectionContext context, KeepAlivePacket packet) {
|
||||
context.sendNow(packet);
|
||||
context.disconnect();
|
||||
}
|
||||
|
||||
public void onKeepAlivePlay(ConnectionContext context, KeepAlivePacket packet) {
|
||||
try {
|
||||
TimeUnit.MILLISECONDS.sleep(50);
|
||||
context.sendNow(packet);
|
||||
} catch (InterruptedException e) {
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("{}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void onServerStatus(ConnectionContext context, StatusServerRequestPacket packet) {
|
||||
ServerInfo serverInfo = new ServerInfo();
|
||||
serverInfo.version().name(ProtocolConstant.PROTOCOL_NAME);
|
||||
serverInfo.version().protocol(ProtocolConstant.PROTOCOL_NUMBER);
|
||||
serverInfo.players().max(config.players().maxOnlile());
|
||||
if (config.players().fakeOnline().enable()) {
|
||||
serverInfo.players().online(config.players().fakeOnline().value());
|
||||
} else {
|
||||
serverInfo.players().online(playerManager.online());
|
||||
}
|
||||
serverInfo.players().sample(Collections.emptyList());
|
||||
serverInfo.description(TextSerializer.fromPlain(config.motd()));
|
||||
|
||||
if (config.iconPath() != null) {
|
||||
serverInfo.favicon(faviconToBase64(config.iconPath()));
|
||||
}
|
||||
|
||||
StatusServerResponse response = new StatusServerResponse();
|
||||
response.setInfo(serverInfo);
|
||||
|
||||
context.sendNow(response);
|
||||
}
|
||||
|
||||
@SuppressWarnings("java:S2589")
|
||||
public void onLoginStart(ConnectionContext context, LoginStartPacket loginStartPacket) {
|
||||
Player player = playerManager.addAndCreate(context, loginStartPacket.getName(), GameMode.SURVIVAL, world.getSpawn());
|
||||
context.setCustomProperty("player", player);
|
||||
|
||||
var loginSuccessPacket = new LoginSuccessPacket();
|
||||
loginSuccessPacket.setUuid(player.getUuid());
|
||||
loginSuccessPacket.setName(player.getName());
|
||||
|
||||
context.sendNow(loginSuccessPacket);
|
||||
context.setState(State.PLAY);
|
||||
|
||||
var joinGamePacket = new JoinGamePacket();
|
||||
joinGamePacket.setEntityId(random.nextInt());
|
||||
joinGamePacket.setGameMode(player.getGameMode());
|
||||
joinGamePacket.setDimension(0/*Overworld*/);
|
||||
joinGamePacket.setDifficulty(Difficulty.PEACEFUL);
|
||||
joinGamePacket.setLevelType(world.getLevelType());
|
||||
|
||||
context.send(joinGamePacket);
|
||||
|
||||
var spawnPositionPacket = new SpawnPositionPacket();
|
||||
spawnPositionPacket.setSpawn(player.getLocation());
|
||||
|
||||
context.send(spawnPositionPacket);
|
||||
|
||||
var playerAbilitiesPacket = new PlayerAbilitiesPacket();
|
||||
playerAbilitiesPacket.setCatFly(true);
|
||||
playerAbilitiesPacket.setFlying(true);
|
||||
playerAbilitiesPacket.setCreativeMode(false);
|
||||
playerAbilitiesPacket.setInvulnerable(true);
|
||||
playerAbilitiesPacket.setFieldOfView(0.0f);
|
||||
playerAbilitiesPacket.setFlyingSpeed(0.05f);
|
||||
|
||||
context.send(playerAbilitiesPacket);
|
||||
|
||||
context.flushSending();
|
||||
|
||||
Location chunkLocation = player.getLocation().toChunkXZ();
|
||||
Chunk chunk = world.getChunk(chunkLocation.getIntX(), chunkLocation.getIntZ());
|
||||
|
||||
var chunkDataPacket = new ChunkDataPacket();
|
||||
chunkDataPacket.setX(chunk.getX());
|
||||
chunkDataPacket.setZ(chunk.getZ());
|
||||
|
||||
context.send(chunkDataPacket);
|
||||
|
||||
for (int i = 1; i <= config.world().viewDistance(); i++) {
|
||||
int minX = chunkLocation.getIntX() - i;
|
||||
int minZ = chunkLocation.getIntZ() - i;
|
||||
int maxX = chunkLocation.getIntX() + i;
|
||||
int maxZ = chunkLocation.getIntZ() + i;
|
||||
|
||||
for (int z = minZ; z <= maxZ; z++) {
|
||||
for (int x = minX; x <= maxX; x++) {
|
||||
if ((z == minZ || z == maxZ) || (x == minX || x == maxX)) {
|
||||
chunkDataPacket = new ChunkDataPacket();
|
||||
chunkDataPacket.setX(x);
|
||||
chunkDataPacket.setZ(z);
|
||||
|
||||
context.send(chunkDataPacket);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context.flushSending();
|
||||
|
||||
var playerPositionAndLookPacket = new SPlayerPositionAndLookPacket();
|
||||
playerPositionAndLookPacket.setPosition(player.getLocation());
|
||||
playerPositionAndLookPacket.setLook(new Look(0f, 0f));
|
||||
playerPositionAndLookPacket.setTeleportId(random.nextInt());
|
||||
|
||||
context.send(playerPositionAndLookPacket);
|
||||
|
||||
KeepAlivePacket keepAlivePacket = new KeepAlivePacket();
|
||||
keepAlivePacket.setPayload(System.currentTimeMillis());
|
||||
|
||||
context.send(keepAlivePacket);
|
||||
|
||||
context.flushSending();
|
||||
}
|
||||
|
||||
private static String faviconToBase64(Path iconPath) {
|
||||
try {
|
||||
return "data:image/png;base64," +
|
||||
Base64.getEncoder().encodeToString(
|
||||
IOUtils.toByteArray(Files.newInputStream(iconPath)));
|
||||
} catch (IOException e) {
|
||||
log.error("Can't read icon '{}'", iconPath.toAbsolutePath(), e);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package mc.server.di;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
import mc.protocol.world.World;
|
||||
import mc.server.PacketHandler;
|
||||
import mc.server.config.Config;
|
||||
import mc.server.service.PlayerManager;
|
||||
|
||||
@Module
|
||||
public class PacketHandlerModule {
|
||||
|
||||
@Provides
|
||||
public PacketHandler providePacketHandler(Config config, World world, PlayerManager playerManager) {
|
||||
return new PacketHandler(config, world, playerManager);
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package mc.server.di;
|
||||
|
||||
import dagger.Component;
|
||||
import mc.protocol.di.ServerScope;
|
||||
import mc.server.PacketHandler;
|
||||
import mc.server.config.Config;
|
||||
import mc.server.service.PlayerManager;
|
||||
|
||||
@Component(modules = {
|
||||
ConfigModule.class, PacketHandlerModule.class, WorldModule.class, PlayersModule.class
|
||||
})
|
||||
@ServerScope
|
||||
public interface ServerComponent {
|
||||
|
||||
Config getConfig();
|
||||
PacketHandler getPacketHandler();
|
||||
PlayerManager getPlayerManager();
|
||||
}
|
||||
Reference in New Issue
Block a user