refactoring
This commit is contained in:
@@ -3,19 +3,24 @@ package mc.protocol.handler.codec;
|
|||||||
import io.netty.buffer.ByteBuf;
|
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.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import mc.protocol.ProtocolAttributes;
|
import mc.protocol.ProtocolAttributes;
|
||||||
import mc.protocol.State;
|
import mc.protocol.State;
|
||||||
import mc.protocol.buffer.NetByteBuf;
|
import mc.protocol.buffer.NetByteBuf;
|
||||||
import mc.protocol.packets.ClientSidePacket;
|
import mc.protocol.packets.ClientSidePacket;
|
||||||
|
import mc.protocol.packets.UnknownPacket;
|
||||||
import mc.protocol.pool.PacketObjectPool;
|
import mc.protocol.pool.PacketObjectPool;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class ProtocolDecoder extends ByteToMessageDecoder {
|
public class ProtocolDecoder extends ByteToMessageDecoder {
|
||||||
|
|
||||||
|
private final boolean readUnknownPackets;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void channelActive(ChannelHandlerContext ctx) {
|
public void channelActive(ChannelHandlerContext ctx) {
|
||||||
ctx.channel().attr(ProtocolAttributes.STATE).set(State.HANDSHAKING);
|
ctx.channel().attr(ProtocolAttributes.STATE).set(State.HANDSHAKING);
|
||||||
@@ -30,7 +35,17 @@ public class ProtocolDecoder extends ByteToMessageDecoder {
|
|||||||
Class<? extends ClientSidePacket> packetClass = state.getClientSidePacketById(packetId);
|
Class<? extends ClientSidePacket> packetClass = state.getClientSidePacketById(packetId);
|
||||||
if (packetClass == null) {
|
if (packetClass == null) {
|
||||||
log.warn("Unknown packet: State {} ; Id 0x{}", state, packetIdAsHexcode(packetId));
|
log.warn("Unknown packet: State {} ; Id 0x{}", state, packetIdAsHexcode(packetId));
|
||||||
|
|
||||||
|
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());
|
netByteBuf.skipBytes(netByteBuf.readableBytes());
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ClientSidePacket packet = PacketObjectPool.getInstance().getPool(packetClass).borrowObject();
|
ClientSidePacket packet = PacketObjectPool.getInstance().getPool(packetClass).borrowObject();
|
||||||
packet.readSelf(netByteBuf);
|
packet.readSelf(netByteBuf);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import lombok.Data;
|
|||||||
import lombok.NoArgsConstructor;
|
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.buffer.NetByteBuf;
|
||||||
|
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@Data
|
@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()
|
socketChannel.pipeline()
|
||||||
.addLast("packet_splitter", new ProtocolSplitter())
|
.addLast("packet_splitter", new ProtocolSplitter())
|
||||||
.addLast("logger", new LoggingHandler(LogLevel.DEBUG))
|
.addLast("logger", new LoggingHandler(LogLevel.DEBUG))
|
||||||
.addLast("packet_decoder", new ProtocolDecoder())
|
.addLast("packet_decoder", new ProtocolDecoder(false))
|
||||||
.addLast("packet_encoder", new ProtocolEncoder())
|
.addLast("packet_encoder", new ProtocolEncoder())
|
||||||
.addLast("packet_handler", new ProtocolInboundHandler(protocolHandlersBus));
|
.addLast("packet_handler", new ProtocolInboundHandler(protocolHandlersBus));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package mc.server;
|
package mc.server;
|
||||||
|
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import mc.protocol.api.ConnectionContext;
|
|
||||||
import mc.protocol.model.Location;
|
import mc.protocol.model.Location;
|
||||||
import mc.protocol.utils.GameMode;
|
import mc.protocol.utils.GameMode;
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@ import java.util.UUID;
|
|||||||
@Getter
|
@Getter
|
||||||
public class Player {
|
public class Player {
|
||||||
|
|
||||||
private final ConnectionContext connectionContext;
|
private final ChannelHandlerContext ctx;
|
||||||
private final UUID uuid;
|
private final UUID uuid;
|
||||||
private final String name;
|
private final String name;
|
||||||
private final GameMode gameMode;
|
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.model.Location;
|
||||||
import mc.protocol.utils.GameMode;
|
import mc.protocol.utils.GameMode;
|
||||||
import mc.server.Player;
|
|
||||||
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@@ -12,9 +11,8 @@ public class PlayerManager {
|
|||||||
|
|
||||||
private final LinkedList<Player> players = new LinkedList<>();
|
private final LinkedList<Player> players = new LinkedList<>();
|
||||||
|
|
||||||
public Player addAndCreate(ConnectionContext context, String name, GameMode gameMode, Location location) {
|
public Player create(ChannelHandlerContext ctx, String name, GameMode gameMode, Location location) {
|
||||||
context.setUsedContext(true);
|
Player player = new Player(ctx, UUID.randomUUID(), name, gameMode, location);
|
||||||
Player player = new Player(context, UUID.randomUUID(), name, gameMode, location);
|
|
||||||
players.add(player);
|
players.add(player);
|
||||||
return 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.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
import mc.protocol.di.ServerScope;
|
import mc.server.PlayerManager;
|
||||||
import mc.server.service.PlayerManager;
|
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
public class PlayersModule {
|
public class PlayerManagerModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@ServerScope
|
@Singleton
|
||||||
PlayerManager providePlayerManager() {
|
PlayerManager providePlayerManager() {
|
||||||
return new PlayerManager();
|
return new PlayerManager();
|
||||||
}
|
}
|
||||||
@@ -5,11 +5,12 @@ import dagger.Module;
|
|||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
import dagger.multibindings.IntoSet;
|
import dagger.multibindings.IntoSet;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import mc.server.processor.PacketProcessor;
|
import mc.protocol.world.World;
|
||||||
import mc.server.processor.ProcessorHandshake;
|
import mc.server.PlayerManager;
|
||||||
import mc.server.processor.ProcessorStatus;
|
import mc.server.processor.*;
|
||||||
|
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@@ -30,4 +31,18 @@ public class ProcessorModule {
|
|||||||
PacketProcessor provideProcessorStatus() {
|
PacketProcessor provideProcessorStatus() {
|
||||||
return new ProcessorStatus(config);
|
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 javax.inject.Singleton;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@Component(modules = {ServerModule.class, ProcessorModule.class})
|
@Component(modules = {
|
||||||
|
ServerModule.class, ProcessorModule.class, PlayerManagerModule.class,
|
||||||
|
WorldModule.class
|
||||||
|
})
|
||||||
@Singleton
|
@Singleton
|
||||||
public interface ServerComponent {
|
public interface ServerComponent {
|
||||||
|
|
||||||
|
|||||||
@@ -2,16 +2,17 @@ package mc.server.di;
|
|||||||
|
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
import mc.protocol.di.ServerScope;
|
|
||||||
import mc.protocol.world.World;
|
import mc.protocol.world.World;
|
||||||
import mc.server.world.VoidWorld;
|
import mc.server.world.VoidWorld;
|
||||||
|
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
public class WorldModule {
|
public class WorldModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@ServerScope
|
@Singleton
|
||||||
public World provideWorld() {
|
World provideWorld() {
|
||||||
return new VoidWorld();
|
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;
|
package mc.server.world;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Data;
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import mc.protocol.world.Chunk;
|
import mc.protocol.world.Chunk;
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
@Data
|
||||||
@Getter
|
|
||||||
public class VoidChunk implements Chunk {
|
public class VoidChunk implements Chunk {
|
||||||
|
|
||||||
private final int x;
|
private final int x;
|
||||||
@@ -7,7 +7,7 @@ import mc.protocol.world.World;
|
|||||||
|
|
||||||
public class VoidWorld implements 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
|
@Override
|
||||||
public LevelType getLevelType() {
|
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