Merge branch 'refactory' into develop
This commit is contained in:
2
cli-parser/build.gradle
Normal file
2
cli-parser/build.gradle
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
//file:noinspection GrUnresolvedAccess
|
||||||
|
apply from: rootDir.toPath().resolve('logic.gradle').toFile()
|
||||||
2
cli-parser/gradle.properties
Normal file
2
cli-parser/gradle.properties
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# suppress inspection "UnusedProperty" for whole file
|
||||||
|
module.name=cli-parser
|
||||||
17
cli-parser/src/main/java/mc/cliparser/CommandLine.java
Normal file
17
cli-parser/src/main/java/mc/cliparser/CommandLine.java
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package mc.cliparser;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@ToString
|
||||||
|
public class CommandLine {
|
||||||
|
|
||||||
|
private final Set<Option> options;
|
||||||
|
|
||||||
|
public boolean has(Option option) {
|
||||||
|
return options.contains(option);
|
||||||
|
}
|
||||||
|
}
|
||||||
52
cli-parser/src/main/java/mc/cliparser/CommandLineParser.java
Normal file
52
cli-parser/src/main/java/mc/cliparser/CommandLineParser.java
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package mc.cliparser;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
public class CommandLineParser {
|
||||||
|
|
||||||
|
private final Set<Option> options = new HashSet<>();
|
||||||
|
|
||||||
|
public void addOption(Option option) {
|
||||||
|
options.add(option);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandLine parse(String[] args) {
|
||||||
|
|
||||||
|
Set<Option> foundOptions = new HashSet<>();
|
||||||
|
AtomicReference<Option> refCurrentOption = new AtomicReference<>(null);
|
||||||
|
|
||||||
|
for (String arg : args) {
|
||||||
|
if (refCurrentOption.get() != null) {
|
||||||
|
refCurrentOption.get().value(arg);
|
||||||
|
foundOptions.add(refCurrentOption.get());
|
||||||
|
refCurrentOption.set(null);
|
||||||
|
} else {
|
||||||
|
parseOptArgs(arg, foundOptions, refCurrentOption);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new CommandLine(foundOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("java:S125")
|
||||||
|
private void parseOptArgs(String arg, Set<Option> foundOptions, AtomicReference<Option> refCurrentOption) {
|
||||||
|
String optName;
|
||||||
|
if (arg.startsWith("--")) {
|
||||||
|
optName = arg.substring(2);
|
||||||
|
} else /*if (args[i].startsWith("-"))*/ {
|
||||||
|
optName = arg.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Option option : options) {
|
||||||
|
if (optName.equals(option.shortName()) || optName.equals(option.longName())) {
|
||||||
|
if (option.hasArgs()) {
|
||||||
|
refCurrentOption.set(option);
|
||||||
|
} else {
|
||||||
|
foundOptions.add(option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
25
cli-parser/src/main/java/mc/cliparser/Option.java
Normal file
25
cli-parser/src/main/java/mc/cliparser/Option.java
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package mc.cliparser;
|
||||||
|
|
||||||
|
import lombok.*;
|
||||||
|
import lombok.experimental.Accessors;
|
||||||
|
|
||||||
|
@Accessors(fluent = true)
|
||||||
|
@Builder
|
||||||
|
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
@EqualsAndHashCode
|
||||||
|
@ToString
|
||||||
|
public class Option {
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final String shortName;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final String longName;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final boolean hasArgs;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
private String value;
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package mc.cliparser;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class CommandLineParserTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void optionTest() {
|
||||||
|
Option option = Option.builder().shortName("v").build();
|
||||||
|
assertNull(option.longName());
|
||||||
|
assertFalse(option.hasArgs());
|
||||||
|
|
||||||
|
option = Option.builder().longName("version").build();
|
||||||
|
assertNull(option.shortName());
|
||||||
|
assertFalse(option.hasArgs());
|
||||||
|
|
||||||
|
option = Option.builder().shortName("v").longName("value1").hasArgs(true).build();
|
||||||
|
assertNotNull(option.shortName());
|
||||||
|
assertNotNull(option.longName());
|
||||||
|
assertTrue(option.hasArgs());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shortOptionFlag() {
|
||||||
|
Option option = Option.builder().shortName("v").build();
|
||||||
|
|
||||||
|
var parser = new CommandLineParser();
|
||||||
|
parser.addOption(option);
|
||||||
|
|
||||||
|
CommandLine commandLine = parser.parse(new String[]{ "-v" });
|
||||||
|
assertTrue(commandLine.has(option));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void longOptionFlag() {
|
||||||
|
Option option = Option.builder().longName("version").build();
|
||||||
|
|
||||||
|
var parser = new CommandLineParser();
|
||||||
|
parser.addOption(option);
|
||||||
|
|
||||||
|
CommandLine commandLine = parser.parse(new String[]{ "--version" });
|
||||||
|
assertTrue(commandLine.has(option));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void argsOption() {
|
||||||
|
Option option1 = Option.builder().shortName("v").longName("value1").hasArgs(true).build();
|
||||||
|
Option option2 = Option.builder().shortName("a").longName("value2").hasArgs(true).build();
|
||||||
|
|
||||||
|
var parser = new CommandLineParser();
|
||||||
|
parser.addOption(option1);
|
||||||
|
parser.addOption(option2);
|
||||||
|
|
||||||
|
CommandLine commandLine = parser.parse(new String[]{ "--value1", "arg1", "-a", "arg2" });
|
||||||
|
assertTrue(commandLine.has(option1));
|
||||||
|
assertEquals("arg1", option1.value());
|
||||||
|
assertTrue(commandLine.has(option2));
|
||||||
|
assertEquals("arg2", option2.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void noPassOption() {
|
||||||
|
Option option = Option.builder().longName("version").build();
|
||||||
|
|
||||||
|
var parser = new CommandLineParser();
|
||||||
|
parser.addOption(option);
|
||||||
|
|
||||||
|
CommandLine commandLine = parser.parse(new String[]{ "--any-opt" });
|
||||||
|
assertFalse(commandLine.has(option));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
project.group=mc-project
|
prj.group=mc-project
|
||||||
project.name=mc-server
|
prj.name=mc-server
|
||||||
project.version=1.1
|
prj.version=1.1
|
||||||
10
libs.gradle
10
libs.gradle
@@ -13,16 +13,20 @@ ext {
|
|||||||
lombok : 'org.projectlombok:lombok:1.18.12',
|
lombok : 'org.projectlombok:lombok:1.18.12',
|
||||||
annotations: 'com.google.code.findbugs:jsr305:3.0.2',
|
annotations: 'com.google.code.findbugs:jsr305:3.0.2',
|
||||||
lang3 : 'org.apache.commons:commons-lang3:3.11',
|
lang3 : 'org.apache.commons:commons-lang3:3.11',
|
||||||
netty : ["io.netty:netty-transport:${netty_version}",
|
|
||||||
"io.netty:netty-handler:${netty_version}"],
|
|
||||||
reactor : 'io.projectreactor:reactor-core:3.4.5',
|
reactor : 'io.projectreactor:reactor-core:3.4.5',
|
||||||
yaml : 'org.yaml:snakeyaml:1.28',
|
hocon : 'com.typesafe:config:1.4.1',
|
||||||
json : 'com.eclipsesource.minimal-json:minimal-json:0.9.5',
|
json : 'com.eclipsesource.minimal-json:minimal-json:0.9.5',
|
||||||
ioutils : 'commons-io:commons-io:2.6',
|
ioutils : 'commons-io:commons-io:2.6',
|
||||||
jopt : 'net.sf.jopt-simple:jopt-simple:6.0-alpha-3',
|
jopt : 'net.sf.jopt-simple:jopt-simple:6.0-alpha-3',
|
||||||
objpool : 'org.apache.commons:commons-pool2:2.9.0'
|
objpool : 'org.apache.commons:commons-pool2:2.9.0'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
libs.netty = [
|
||||||
|
transport: "io.netty:netty-transport:${netty_version}",
|
||||||
|
codec : "io.netty:netty-codec:${netty_version}",
|
||||||
|
handler : "io.netty:netty-handler:${netty_version}"
|
||||||
|
]
|
||||||
|
|
||||||
libs.logger = [
|
libs.logger = [
|
||||||
slf4j : ["org.slf4j:slf4j-api:${slf4j_version}",
|
slf4j : ["org.slf4j:slf4j-api:${slf4j_version}",
|
||||||
"org.slf4j:jcl-over-slf4j:${slf4j_version}"],
|
"org.slf4j:jcl-over-slf4j:${slf4j_version}"],
|
||||||
|
|||||||
16
logic.gradle
16
logic.gradle
@@ -1,15 +1,16 @@
|
|||||||
//file:noinspection GrUnresolvedAccess
|
//file:noinspection GrUnresolvedAccess
|
||||||
apply plugin: 'java'
|
apply plugin: 'java'
|
||||||
apply plugin: 'java-library'
|
apply plugin: 'java-library'
|
||||||
|
apply plugin: 'jacoco'
|
||||||
apply from: rootDir.toPath().resolve('libs.gradle').toFile()
|
apply from: rootDir.toPath().resolve('libs.gradle').toFile()
|
||||||
|
|
||||||
String getProperty1(String propertyName1, String propertyName2) {
|
String getProperty1(String propertyName1, String propertyName2) {
|
||||||
return (String) (project.hasProperty(propertyName1) ? project.property(propertyName1) : project.property(propertyName2))
|
return (String) (project.hasProperty(propertyName1) ? project.property(propertyName1) : project.property(propertyName2))
|
||||||
}
|
}
|
||||||
|
|
||||||
project.group = getProperty1('module.group', 'project.group')
|
project.group = getProperty1('module.group', 'prj.group')
|
||||||
project.version = getProperty1('module.version', 'project.version')
|
project.version = getProperty1('module.version', 'prj.version')
|
||||||
jar.archiveBaseName.set(getProperty1('module.name', 'project.name'))
|
jar.archiveBaseName.set(getProperty1('module.name', 'prj.name'))
|
||||||
|
|
||||||
compileJava {
|
compileJava {
|
||||||
sourceCompatibility = targetCompatibility = JavaVersion.VERSION_11
|
sourceCompatibility = targetCompatibility = JavaVersion.VERSION_11
|
||||||
@@ -31,13 +32,18 @@ dependencies {
|
|||||||
implementation libs.dagger2.implementation
|
implementation libs.dagger2.implementation
|
||||||
annotationProcessor libs.dagger2.annotationProcessor
|
annotationProcessor libs.dagger2.annotationProcessor
|
||||||
|
|
||||||
|
testAnnotationProcessor libs.lombok
|
||||||
|
testCompileOnly libs.lombok
|
||||||
|
|
||||||
testImplementation libs.test.junit5.api
|
testImplementation libs.test.junit5.api
|
||||||
testImplementation libs.test.junit5.params
|
testImplementation libs.test.junit5.params
|
||||||
testRuntimeOnly libs.test.junit5.engine
|
testRuntimeOnly libs.test.junit5.engine
|
||||||
|
|
||||||
testRuntimeOnly libs.test.logger
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jacoco {
|
||||||
|
toolVersion = '0.8.7'
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
apply from: rootDir.toPath().resolve('logic.gradle').toFile()
|
apply from: rootDir.toPath().resolve('logic.gradle').toFile()
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation libs.netty
|
api project(':utils')
|
||||||
|
|
||||||
|
implementation libs.netty.transport
|
||||||
|
implementation libs.netty.codec
|
||||||
implementation libs.json
|
implementation libs.json
|
||||||
implementation libs.objpool
|
|
||||||
|
|
||||||
testImplementation libs.lang3
|
testImplementation libs.lang3
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,18 +0,0 @@
|
|||||||
package mc.protocol;
|
|
||||||
|
|
||||||
import io.netty.util.AttributeKey;
|
|
||||||
import lombok.experimental.UtilityClass;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
@UtilityClass
|
|
||||||
public class NetworkAttributes {
|
|
||||||
|
|
||||||
public static final AttributeKey<State> STATE = AttributeKey.newInstance("STATE");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated костыль
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public static final AttributeKey<Map<String, Object>> CUSTOM_PROPERTIES = AttributeKey.newInstance("CUSTOM_PROPERTIES");
|
|
||||||
}
|
|
||||||
10
protocol/src/main/java/mc/protocol/ProtocolAttributes.java
Normal file
10
protocol/src/main/java/mc/protocol/ProtocolAttributes.java
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package mc.protocol;
|
||||||
|
|
||||||
|
import io.netty.util.AttributeKey;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
|
||||||
|
@UtilityClass
|
||||||
|
public class ProtocolAttributes {
|
||||||
|
|
||||||
|
public static final AttributeKey<State> STATE = AttributeKey.newInstance("STATE");
|
||||||
|
}
|
||||||
@@ -5,6 +5,6 @@ import lombok.experimental.UtilityClass;
|
|||||||
@UtilityClass
|
@UtilityClass
|
||||||
public class ProtocolConstant {
|
public class ProtocolConstant {
|
||||||
|
|
||||||
public static final String PROTOCOL_NAME = "1.12.2";
|
public final String PROTOCOL_NAME = "1.12.2";
|
||||||
public static final int PROTOCOL_NUMBER = 340;
|
public final int PROTOCOL_NUMBER = 340;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,16 @@ package mc.protocol;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import mc.protocol.packets.ClientSidePacket;
|
import mc.protocol.packets.ClientSidePacket;
|
||||||
import mc.protocol.packets.Packet;
|
|
||||||
import mc.protocol.packets.KeepAlivePacket;
|
import mc.protocol.packets.KeepAlivePacket;
|
||||||
import mc.protocol.packets.ServerSidePacket;
|
import mc.protocol.packets.ServerSidePacket;
|
||||||
import mc.protocol.packets.client.*;
|
import mc.protocol.packets.handshaking.client.HandshakePacket;
|
||||||
import mc.protocol.packets.server.*;
|
import mc.protocol.packets.login.client.LoginStartPacket;
|
||||||
|
import mc.protocol.packets.login.server.DisconnectPacket;
|
||||||
|
import mc.protocol.packets.login.server.LoginSuccessPacket;
|
||||||
|
import mc.protocol.packets.play.client.*;
|
||||||
|
import mc.protocol.packets.play.server.*;
|
||||||
|
import mc.protocol.packets.status.client.StatusServerRequestPacket;
|
||||||
|
import mc.protocol.packets.status.server.StatusServerResponse;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -16,10 +21,13 @@ import java.util.Map;
|
|||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public enum State {
|
public enum State {
|
||||||
|
|
||||||
HANDSHAKING(-1,
|
HANDSHAKING(0,
|
||||||
// client side
|
// client side
|
||||||
Map.of(0x00, HandshakePacket.class)
|
Map.of(0x00, HandshakePacket.class),
|
||||||
|
// server side
|
||||||
|
Collections.emptyMap()
|
||||||
),
|
),
|
||||||
|
|
||||||
STATUS(1,
|
STATUS(1,
|
||||||
// client side
|
// client side
|
||||||
Map.of(
|
Map.of(
|
||||||
@@ -32,17 +40,19 @@ public enum State {
|
|||||||
KeepAlivePacket.class, 0x01
|
KeepAlivePacket.class, 0x01
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|
||||||
LOGIN(2,
|
LOGIN(2,
|
||||||
// server bound
|
// client side
|
||||||
Map.of(0x00, LoginStartPacket.class),
|
Map.of(0x00, LoginStartPacket.class),
|
||||||
// client bound
|
// server side
|
||||||
Map.of(
|
Map.of(
|
||||||
DisconnectPacket.class, 0x00,
|
DisconnectPacket.class, 0x00,
|
||||||
LoginSuccessPacket.class, 0x02
|
LoginSuccessPacket.class, 0x02
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|
||||||
PLAY(3,
|
PLAY(3,
|
||||||
// server bound
|
// client side
|
||||||
Map.of(
|
Map.of(
|
||||||
0x00, TeleportConfirmPacket.class,
|
0x00, TeleportConfirmPacket.class,
|
||||||
0x04, ClientSettingsPacket.class,
|
0x04, ClientSettingsPacket.class,
|
||||||
@@ -53,7 +63,7 @@ public enum State {
|
|||||||
0x0F, PlayerLookPacket.class,
|
0x0F, PlayerLookPacket.class,
|
||||||
0x15, EntityActionPacket.class
|
0x15, EntityActionPacket.class
|
||||||
),
|
),
|
||||||
// client bound
|
// server side
|
||||||
Map.of(
|
Map.of(
|
||||||
KeepAlivePacket.class, 0x1F,
|
KeepAlivePacket.class, 0x1F,
|
||||||
ChunkDataPacket.class, 0x20,
|
ChunkDataPacket.class, 0x20,
|
||||||
@@ -64,37 +74,29 @@ public enum State {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public static State getById(int id) {
|
public static State getById(int id) {
|
||||||
for (State state : State.values()) {
|
// а зачем усложнять?
|
||||||
if (state.id == id) {
|
//@formatter:off
|
||||||
return state;
|
if (id == 1) return STATUS;
|
||||||
}
|
else if (id == 2) return LOGIN;
|
||||||
}
|
else if (id == 3) return PLAY;
|
||||||
|
else return HANDSHAKING;
|
||||||
return null;
|
//@formatter:on
|
||||||
}
|
}
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private final int id;
|
private final int id;
|
||||||
|
|
||||||
@Getter
|
|
||||||
private final Map<Integer, Class<? extends ClientSidePacket>> clientSidePackets;
|
private final Map<Integer, Class<? extends ClientSidePacket>> clientSidePackets;
|
||||||
private final Map<Class<? extends ServerSidePacket>, Integer> serverSidePackets;
|
private final Map<Class<? extends ServerSidePacket>, Integer> serverSidePackets;
|
||||||
|
|
||||||
State(int id, Map<Integer, Class<? extends ClientSidePacket>> clientSidePackets) {
|
|
||||||
this.id = id;
|
|
||||||
this.clientSidePackets = clientSidePackets;
|
|
||||||
this.serverSidePackets = Collections.emptyMap();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public Class<? extends ClientSidePacket> getClientSidePacketById(int id) {
|
public Class<? extends ClientSidePacket> getClientSidePacketById(int id) {
|
||||||
return clientSidePackets == null ? null : clientSidePackets.get(id);
|
return clientSidePackets == null ? null : clientSidePackets.get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public Integer getServerSidePacketId(Class<? extends Packet> clazz) {
|
public Integer getServerSidePacketId(Class<? extends ServerSidePacket> clazz) {
|
||||||
return serverSidePackets == null ? null : serverSidePackets.get(clazz);
|
return serverSidePackets == null ? null : serverSidePackets.get(clazz);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,4 +1,4 @@
|
|||||||
package mc.protocol.io;
|
package mc.protocol.buffer;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
@@ -6,6 +6,8 @@ import lombok.RequiredArgsConstructor;
|
|||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
import lombok.experimental.Delegate;
|
import lombok.experimental.Delegate;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import mc.protocol.model.text.Text;
|
||||||
|
import mc.protocol.model.text.TextSerializer;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@@ -32,15 +34,15 @@ import java.util.UUID;
|
|||||||
* | | | | этого числа). |
|
* | | | | этого числа). |
|
||||||
* | VarInt | >= 1 ; <= 5 | Число от -2147483648 и 2147483647 | 32-bit число с плавающей размерностью от 1 до 5 байт |
|
* | VarInt | >= 1 ; <= 5 | Число от -2147483648 и 2147483647 | 32-bit число с плавающей размерностью от 1 до 5 байт |
|
||||||
* | VarLong | >= 1 ; <= 10 | Число от -9223372036854775808 и 9223372036854775807 | 64-bit число с плавающей размерностью от 1 до 10 байт |
|
* | VarLong | >= 1 ; <= 10 | Число от -9223372036854775808 и 9223372036854775807 | 64-bit число с плавающей размерностью от 1 до 10 байт |
|
||||||
* | Position | 8 | 64-bit число разделённое на три части: x, y, z | Кодируется формулой: |
|
* | Text | | JSON | По файту является String (n), который имеет формат JSON |
|
||||||
* | | | | ((x & 0x3FFFFFF) << 38) | ((y & 0xFFF) << 26) | (z & 0x3FFFFFF) |
|
|
||||||
*
|
*
|
||||||
* [1] - <a href="https://en.wikipedia.org/wiki/Single-precision_floating-point_format">Single-precision floating-point format</a>
|
* [1] - <a href="https://en.wikipedia.org/wiki/Single-precision_floating-point_format">Single-precision floating-point format</a>
|
||||||
* [2] - <a href="https://en.wikipedia.org/wiki/Double-precision_floating-point_format">Double-precision floating-point format</a>
|
* [2] - <a href="https://en.wikipedia.org/wiki/Double-precision_floating-point_format">Double-precision floating-point format</a>
|
||||||
* [3] - <a href="http://unicode.org/glossary/#unicode_scalar_value">Unicode Scalar Value</a>
|
* [3] - <a href="http://unicode.org/glossary/#unicode_scalar_value">Unicode Scalar Value</a>
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=7368#Data_types">Data types</a>
|
* @see <a href="https://wiki.vg/index.php?title=Data_types&oldid=14345#Definitions">Data types</a>
|
||||||
|
* @see <a href="https://wiki.vg/index.php?title=Chat&oldid=14272">Chat</a>
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@@ -55,6 +57,10 @@ public class NetByteBuf extends ByteBuf {
|
|||||||
byteBuf.writeByte((byte)(value & 0xFF));
|
byteBuf.writeByte((byte)(value & 0xFF));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void writeText(Text text) {
|
||||||
|
writeString(TextSerializer.toStringPlain(text));
|
||||||
|
}
|
||||||
|
|
||||||
//region String
|
//region String
|
||||||
public String readString() {
|
public String readString() {
|
||||||
return readString(Short.MAX_VALUE);
|
return readString(Short.MAX_VALUE);
|
||||||
@@ -67,9 +73,9 @@ public class NetByteBuf extends ByteBuf {
|
|||||||
if (length == 0) {
|
if (length == 0) {
|
||||||
return "";
|
return "";
|
||||||
} else if (length > maxLength) {
|
} else if (length > maxLength) {
|
||||||
throw new DecoderException("String length exceeds maximum length: " + length + " > " + maxLength);
|
throw new NetIOException("String length exceeds maximum length: " + length + " > " + maxLength);
|
||||||
} else if (length < 0) {
|
} else if (length < 0) {
|
||||||
throw new DecoderException("String length less zero!");
|
throw new NetIOException("String length less zero!");
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] bytes = new byte[length * 4];
|
byte[] bytes = new byte[length * 4];
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package mc.protocol.buffer;
|
||||||
|
|
||||||
|
public class NetIOException extends RuntimeException {
|
||||||
|
|
||||||
|
public NetIOException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package mc.protocol.handler;
|
||||||
|
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import mc.protocol.State;
|
||||||
|
import mc.protocol.packets.ClientSidePacket;
|
||||||
|
import mc.protocol.utils.Table;
|
||||||
|
|
||||||
|
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||||
|
public class ProtocolHandlersBus {
|
||||||
|
|
||||||
|
private final Table<State, Class<? extends ClientSidePacket>, Handler> table = new Table<>();
|
||||||
|
|
||||||
|
public <P extends ClientSidePacket> ProtocolHandlersBus addHandler(State state, Class<P> packetClass, Handler<P> handler) {
|
||||||
|
table.put(state, packetClass, handler);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <P extends ClientSidePacket> void process(State state, ChannelHandlerContext ctx, P packet) {
|
||||||
|
Handler handler = table.getColumnAndRow(state, packet.getClass());
|
||||||
|
|
||||||
|
if (handler != null) {
|
||||||
|
handler.handle(ctx, packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface Handler<P extends ClientSidePacket> {
|
||||||
|
void handle(ChannelHandlerContext ctx, P packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,37 +1,28 @@
|
|||||||
package mc.protocol;
|
package mc.protocol.handler;
|
||||||
|
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import io.netty.channel.SimpleChannelInboundHandler;
|
import io.netty.channel.SimpleChannelInboundHandler;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import mc.protocol.ProtocolAttributes;
|
||||||
|
import mc.protocol.State;
|
||||||
import mc.protocol.packets.ClientSidePacket;
|
import mc.protocol.packets.ClientSidePacket;
|
||||||
import mc.protocol.event.EventBus;
|
|
||||||
import mc.protocol.pool.PacketPool;
|
|
||||||
import org.apache.commons.pool2.ObjectPool;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class PacketInboundHandler extends SimpleChannelInboundHandler<ClientSidePacket> {
|
public class ProtocolInboundHandler extends SimpleChannelInboundHandler<ClientSidePacket> {
|
||||||
|
|
||||||
private static final String CLIENT_FORCE_DISCONNECTED_IOEXCEPTION_MESSAGE_RU = "Программа на вашем хост-компьютере разорвала установленное подключение";
|
private static final String CLIENT_FORCE_DISCONNECTED_IOEXCEPTION_MESSAGE_RU = "Программа на вашем хост-компьютере разорвала установленное подключение";
|
||||||
|
|
||||||
private final ObjectPool<NettyConnectionContext> poolNettyConnectionContext;
|
private final ProtocolHandlersBus protocolHandlersBus;
|
||||||
private final PacketPool poolPackets;
|
|
||||||
private final EventBus eventBus;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void channelRead0(ChannelHandlerContext ctx, ClientSidePacket packet) throws Exception {
|
protected void channelRead0(ChannelHandlerContext ctx, ClientSidePacket packet) {
|
||||||
State state = ctx.channel().attr(NetworkAttributes.STATE).get();
|
State state = Objects.requireNonNull(ctx.channel().attr(ProtocolAttributes.STATE).get());
|
||||||
|
protocolHandlersBus.process(state, ctx, packet);
|
||||||
NettyConnectionContext context = poolNettyConnectionContext.borrowObject().setCtx(ctx);
|
|
||||||
eventBus.emit(state, context, packet);
|
|
||||||
|
|
||||||
if (!context.isUsedContext()) {
|
|
||||||
poolNettyConnectionContext.returnObject(context);
|
|
||||||
}
|
|
||||||
poolPackets.returnObject(packet);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -39,7 +30,7 @@ public class PacketInboundHandler extends SimpleChannelInboundHandler<ClientSide
|
|||||||
if (cause instanceof IOException && cause.getLocalizedMessage().equalsIgnoreCase(CLIENT_FORCE_DISCONNECTED_IOEXCEPTION_MESSAGE_RU)) {
|
if (cause instanceof IOException && cause.getLocalizedMessage().equalsIgnoreCase(CLIENT_FORCE_DISCONNECTED_IOEXCEPTION_MESSAGE_RU)) {
|
||||||
log.warn("Client '{}' force disconnected", ctx.channel().remoteAddress());
|
log.warn("Client '{}' force disconnected", ctx.channel().remoteAddress());
|
||||||
if (log.isTraceEnabled()) {
|
if (log.isTraceEnabled()) {
|
||||||
log.trace("", cause);
|
log.trace("{}", cause.getMessage(), cause);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.error("{}", cause.getMessage(), cause);
|
log.error("{}", cause.getMessage(), cause);
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
|
||||||
|
State state = Objects.requireNonNull(ctx.channel().attr(ProtocolAttributes.STATE).get());
|
||||||
|
NetByteBuf netByteBuf = new NetByteBuf(in);
|
||||||
|
|
||||||
|
int packetId = netByteBuf.readVarInt();
|
||||||
|
Class<? extends ClientSidePacket> packetClass = state.getClientSidePacketById(packetId);
|
||||||
|
if (packetClass == null) {
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ClientSidePacket packet = PacketObjectPool.getInstance().getPool(packetClass).borrowObject();
|
||||||
|
packet.readSelf(netByteBuf);
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("IN: {}:{}", state, packet);
|
||||||
|
}
|
||||||
|
out.add(packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String packetIdAsHexcode(int packetId) {
|
||||||
|
String hexPacketId = Integer.toHexString(packetId).toUpperCase();
|
||||||
|
if (hexPacketId.length() == 1) hexPacketId = "0" + hexPacketId;
|
||||||
|
|
||||||
|
return hexPacketId;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,28 +1,32 @@
|
|||||||
package mc.protocol.io.codec;
|
package mc.protocol.handler.codec;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import io.netty.handler.codec.MessageToByteEncoder;
|
import io.netty.handler.codec.MessageToByteEncoder;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import mc.protocol.NetworkAttributes;
|
import mc.protocol.ProtocolAttributes;
|
||||||
import mc.protocol.State;
|
import mc.protocol.State;
|
||||||
import mc.protocol.io.NetByteBuf;
|
import mc.protocol.buffer.NetByteBuf;
|
||||||
import mc.protocol.packets.ServerSidePacket;
|
import mc.protocol.packets.ServerSidePacket;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class ProtocolEncoder extends MessageToByteEncoder<ServerSidePacket> {
|
public class ProtocolEncoder extends MessageToByteEncoder<ServerSidePacket> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void encode(ChannelHandlerContext ctx, ServerSidePacket packet, ByteBuf out) {
|
protected void encode(ChannelHandlerContext ctx, ServerSidePacket packet, ByteBuf out) {
|
||||||
State state = ctx.channel().attr(NetworkAttributes.STATE).get();
|
State state = Objects.requireNonNull(ctx.channel().attr(ProtocolAttributes.STATE).get());
|
||||||
Integer packetId = state.getServerSidePacketId(packet.getClass());
|
Integer packetId = state.getServerSidePacketId(packet.getClass());
|
||||||
if (packetId == null) {
|
if (packetId == null) {
|
||||||
log.error("Unknown send packet: State {} ; Class {}", state, packet.getClass());
|
log.error("Unknown send packet: State {} ; Class {}", state, packet.getClass());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug("OUT: {}:{}", state, packet);
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("OUT: {}:{}", state, packet);
|
||||||
|
}
|
||||||
|
|
||||||
NetByteBuf buffer = new NetByteBuf(Unpooled.buffer());
|
NetByteBuf buffer = new NetByteBuf(Unpooled.buffer());
|
||||||
buffer.writeVarInt(packetId);
|
buffer.writeVarInt(packetId);
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
package mc.protocol.io.codec;
|
package mc.protocol.handler.codec;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||||
import mc.protocol.io.NetByteBuf;
|
import mc.protocol.buffer.NetByteBuf;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
package mc.protocol.io;
|
|
||||||
|
|
||||||
public class DecoderException extends RuntimeException {
|
|
||||||
|
|
||||||
public DecoderException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
package mc.protocol.io.codec;
|
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
|
||||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.Setter;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import mc.protocol.NettyConnectionContext;
|
|
||||||
import mc.protocol.NetworkAttributes;
|
|
||||||
import mc.protocol.State;
|
|
||||||
import mc.protocol.api.ConnectionContext;
|
|
||||||
import mc.protocol.io.NetByteBuf;
|
|
||||||
import mc.protocol.packets.ClientSidePacket;
|
|
||||||
import mc.protocol.packets.UnknownPacket;
|
|
||||||
import mc.protocol.pool.PacketPool;
|
|
||||||
import org.apache.commons.pool2.ObjectPool;
|
|
||||||
|
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
@Slf4j
|
|
||||||
public class ProtocolDecoder extends ByteToMessageDecoder {
|
|
||||||
|
|
||||||
private final boolean readUnknownPackets;
|
|
||||||
private final ObjectPool<NettyConnectionContext> poolNettyConnectionContext;
|
|
||||||
private final PacketPool poolPackets;
|
|
||||||
|
|
||||||
@Setter
|
|
||||||
private Consumer<ConnectionContext> consumerNewConnection;
|
|
||||||
@Setter
|
|
||||||
private Consumer<ConnectionContext> consumerDisconnect;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void channelActive(@Nonnull ChannelHandlerContext ctx) throws Exception {
|
|
||||||
ctx.channel().attr(NetworkAttributes.CUSTOM_PROPERTIES).set(new HashMap<>());
|
|
||||||
|
|
||||||
NettyConnectionContext context = poolNettyConnectionContext.borrowObject().setCtx(ctx);
|
|
||||||
consumerNewConnection.accept(context);
|
|
||||||
|
|
||||||
poolNettyConnectionContext.returnObject(context);
|
|
||||||
super.channelActive(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void channelInactive(@Nonnull ChannelHandlerContext ctx) throws Exception {
|
|
||||||
NettyConnectionContext context = poolNettyConnectionContext.borrowObject().setCtx(ctx);
|
|
||||||
consumerDisconnect.accept(context);
|
|
||||||
|
|
||||||
ctx.channel().attr(NetworkAttributes.CUSTOM_PROPERTIES).get().clear();
|
|
||||||
ctx.channel().attr(NetworkAttributes.CUSTOM_PROPERTIES).set(null);
|
|
||||||
|
|
||||||
poolNettyConnectionContext.returnObject(context);
|
|
||||||
super.channelInactive(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
|
|
||||||
State state = Objects.requireNonNull(ctx.channel().attr(NetworkAttributes.STATE).get());
|
|
||||||
NetByteBuf netByteBuf = new NetByteBuf(in);
|
|
||||||
|
|
||||||
int packetId = netByteBuf.readVarInt();
|
|
||||||
Class<? extends ClientSidePacket> packetClass = state.getClientSidePacketById(packetId);
|
|
||||||
if (packetClass == null) {
|
|
||||||
log.warn("Unknown packet: State {} ; Id 0x{}", state, packetIdAsHexcode(packetId));
|
|
||||||
|
|
||||||
if (readUnknownPackets) {
|
|
||||||
UnknownPacket unknownPacket = poolPackets.borrowObject(UnknownPacket.class);
|
|
||||||
unknownPacket.setState(state);
|
|
||||||
unknownPacket.setId(packetId);
|
|
||||||
unknownPacket.setDataSize(netByteBuf.readableBytes());
|
|
||||||
unknownPacket.readSelf(netByteBuf);
|
|
||||||
out.add(unknownPacket);
|
|
||||||
} else {
|
|
||||||
netByteBuf.skipBytes(netByteBuf.readableBytes());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ClientSidePacket packet = poolPackets.borrowObject(packetClass);
|
|
||||||
packet.readSelf(netByteBuf);
|
|
||||||
log.debug("IN: {}:{}", state, packet);
|
|
||||||
out.add(packet);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String packetIdAsHexcode(int packetId) {
|
|
||||||
String hexPacketId = Integer.toHexString(packetId).toUpperCase();
|
|
||||||
if (hexPacketId.length() == 1) hexPacketId = "0" + hexPacketId;
|
|
||||||
|
|
||||||
return hexPacketId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +1,18 @@
|
|||||||
package mc.protocol.model;
|
package mc.protocol.model;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Data
|
@Data
|
||||||
public class Location {
|
public class Location {
|
||||||
private double x;
|
private double x = 0d;
|
||||||
private double y;
|
private double y = 0d;
|
||||||
private double z;
|
private double z = 0d;
|
||||||
|
|
||||||
public int getIntX() {
|
public Location set(double x, double y, double z) {
|
||||||
return (int) x;
|
this.x = x;
|
||||||
}
|
this.y = y;
|
||||||
|
this.z = z;
|
||||||
|
|
||||||
public int getIntZ() {
|
return this;
|
||||||
return (int) z;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Location toChunkXZ() {
|
|
||||||
return new Location(this.getIntX() >> 4, 0d, this.getIntZ() >> 4);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
package mc.protocol.model;
|
package mc.protocol.model;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Data
|
@Data
|
||||||
public class Look {
|
public class Look {
|
||||||
private float yaw;
|
private float yaw;
|
||||||
private float pitch;
|
private float pitch;
|
||||||
|
|
||||||
|
public Look set(float yaw, float pitch) {
|
||||||
|
this.yaw = yaw;
|
||||||
|
this.pitch = pitch;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package mc.protocol.serializer;
|
package mc.protocol.model;
|
||||||
|
|
||||||
import com.eclipsesource.json.Json;
|
import com.eclipsesource.json.Json;
|
||||||
import com.eclipsesource.json.JsonArray;
|
import com.eclipsesource.json.JsonArray;
|
||||||
import com.eclipsesource.json.JsonObject;
|
import com.eclipsesource.json.JsonObject;
|
||||||
import lombok.experimental.UtilityClass;
|
import lombok.experimental.UtilityClass;
|
||||||
import mc.protocol.model.ServerInfo;
|
import mc.protocol.model.text.TextSerializer;
|
||||||
|
|
||||||
import java.util.Spliterator;
|
import java.util.Spliterator;
|
||||||
import java.util.Spliterators;
|
import java.util.Spliterators;
|
||||||
@@ -14,7 +14,11 @@ import java.util.stream.StreamSupport;
|
|||||||
@UtilityClass
|
@UtilityClass
|
||||||
public class ServerInfoSerializer {
|
public class ServerInfoSerializer {
|
||||||
|
|
||||||
public JsonObject toJsonObject(ServerInfo info) {
|
public String toStringPlain(ServerInfo info) {
|
||||||
|
return toJsonObject(info).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonObject toJsonObject(ServerInfo info) {
|
||||||
JsonObject jsonObject = Json.object()
|
JsonObject jsonObject = Json.object()
|
||||||
.add("version", createVersionObj(info))
|
.add("version", createVersionObj(info))
|
||||||
.add("players", createPlayersObj(info))
|
.add("players", createPlayersObj(info))
|
||||||
@@ -1,12 +1,9 @@
|
|||||||
package mc.protocol.serializer;
|
package mc.protocol.model.text;
|
||||||
|
|
||||||
import com.eclipsesource.json.Json;
|
import com.eclipsesource.json.Json;
|
||||||
import com.eclipsesource.json.JsonArray;
|
import com.eclipsesource.json.JsonArray;
|
||||||
import com.eclipsesource.json.JsonObject;
|
import com.eclipsesource.json.JsonObject;
|
||||||
import lombok.experimental.UtilityClass;
|
import lombok.experimental.UtilityClass;
|
||||||
import mc.protocol.model.text.Text;
|
|
||||||
import mc.protocol.model.text.TextColor;
|
|
||||||
import mc.protocol.model.text.TextStyle;
|
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@@ -16,6 +13,10 @@ public class TextSerializer {
|
|||||||
private static final Map<Character, TextStyle> legacyStyleCodes;
|
private static final Map<Character, TextStyle> legacyStyleCodes;
|
||||||
private static final Map<Character, TextColor> legacyColorCodes;
|
private static final Map<Character, TextColor> legacyColorCodes;
|
||||||
|
|
||||||
|
public String toStringPlain(Text text) {
|
||||||
|
return toJsonObject(text).toString();
|
||||||
|
}
|
||||||
|
|
||||||
public JsonObject toJsonObject(Text text) {
|
public JsonObject toJsonObject(Text text) {
|
||||||
JsonObject jsonObject = Json.object();
|
JsonObject jsonObject = Json.object();
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package mc.protocol.packets;
|
package mc.protocol.packets;
|
||||||
|
|
||||||
import mc.protocol.io.NetByteBuf;
|
import mc.protocol.buffer.NetByteBuf;
|
||||||
import mc.protocol.pool.Passivable;
|
import mc.utils.pool.Passivable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Пакеты отправляемые клиентом.
|
* Пакеты отправляемые клиентом.
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
package mc.protocol.packets;
|
|
||||||
|
|
||||||
import mc.protocol.io.NetByteBuf;
|
|
||||||
|
|
||||||
public abstract class EmptyPacket implements ClientSidePacket, ServerSidePacket {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void readSelf(NetByteBuf netByteBuf) {
|
|
||||||
// empty
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeSelf(NetByteBuf netByteBuf) {
|
|
||||||
// empty
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void passivate() {
|
|
||||||
// pass
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +1,13 @@
|
|||||||
package mc.protocol.packets;
|
package mc.protocol.packets;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import mc.protocol.io.NetByteBuf;
|
import mc.protocol.buffer.NetByteBuf;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Пинг-пакет.
|
* Пинг-пакет.
|
||||||
*
|
*
|
||||||
* <p>Эхо-пакет, которым проверяется качество соединения между <b>Клиентом</b> и <b>Сервером</b>.</p>
|
* <p>Эхо-пакет, которым проверяется качество соединения между <b>Клиентом</b> и <b>Сервером</b>.</p>
|
||||||
*
|
*
|
||||||
* <p>По спецификации:</p>
|
|
||||||
* <oi>
|
|
||||||
* <li>если <b>Сервер</b> не ответил <b>Клиенту</b> в течении 20 секунд, <b>Клиент</b> отключается
|
|
||||||
* и выдаёт ошибку <i>"Timed out"</i>.</li>
|
|
||||||
* <li>если <b>Клиент</b> не отвечает <b>Серверу</b> в течении 30 секунд, <b>Сервер</b> отключает <b>Клиента</b>.</li>
|
|
||||||
* </oi>
|
|
||||||
*
|
|
||||||
* <p>Структура пакета</p>
|
* <p>Структура пакета</p>
|
||||||
* <pre>
|
* <pre>
|
||||||
* | FIELD | TYPE | NOTES |
|
* | FIELD | TYPE | NOTES |
|
||||||
@@ -22,25 +15,32 @@ import mc.protocol.io.NetByteBuf;
|
|||||||
* | Payload | Long | Любое уникальное число |
|
* | Payload | Long | Любое уникальное число |
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=7368#Keep_Alive">Keep Alive</a>
|
* <p>По спецификации:</p>
|
||||||
|
* <oi>
|
||||||
|
* <li>если Сервер не ответил Клиенту в течении 20 секунд, Клиент отключается и выдаёт ошибку "Timed out";</li>
|
||||||
|
* <li>если Клиент не отвечает Серверу в течении 30 секунд, Сервер отключает Клиента.</li>
|
||||||
|
* </oi>
|
||||||
|
*
|
||||||
|
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Keep_Alive_.28clientbound.29">Keep Alive (clientbound)</a>
|
||||||
|
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Keep_Alive_.28serverbound.29">Keep Alive (serverbound)</a>
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public class KeepAlivePacket implements ClientSidePacket, ServerSidePacket {
|
public class KeepAlivePacket implements ClientSidePacket, ServerSidePacket {
|
||||||
|
|
||||||
private Long payload;
|
private long payload;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void readSelf(NetByteBuf netByteBuf) {
|
public void readSelf(NetByteBuf netByteBuf) {
|
||||||
payload = netByteBuf.readLong();
|
payload = netByteBuf.readLong();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void passivate() {
|
|
||||||
this.payload = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeSelf(NetByteBuf netByteBuf) {
|
public void writeSelf(NetByteBuf netByteBuf) {
|
||||||
netByteBuf.writeLong(payload);
|
netByteBuf.writeLong(payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void passivate() {
|
||||||
|
this.payload = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package mc.protocol.packets;
|
package mc.protocol.packets;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Пакет.
|
* Сетевой пакет.
|
||||||
*
|
*
|
||||||
* <p>Структура пакета</p>
|
* <p>Структура</p>
|
||||||
* <pre>
|
* <pre>
|
||||||
* | FIELD | TYPE | NOTES |
|
* | FIELD | TYPE | NOTES |
|
||||||
* |-------------|--------|-------------------------------------------|
|
* |-------------|--------|-------------------------------------------|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package mc.protocol.packets;
|
package mc.protocol.packets;
|
||||||
|
|
||||||
import mc.protocol.io.NetByteBuf;
|
import mc.protocol.buffer.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,18 +0,0 @@
|
|||||||
package mc.protocol.packets.client;
|
|
||||||
|
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
import lombok.ToString;
|
|
||||||
import mc.protocol.packets.EmptyPacket;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Status server packet, request.
|
|
||||||
*
|
|
||||||
* <p>Клиент запрашивает получение информации о сервере</p>
|
|
||||||
*/
|
|
||||||
@NoArgsConstructor
|
|
||||||
@EqualsAndHashCode(callSuper = false)
|
|
||||||
@ToString
|
|
||||||
public class StatusServerRequestPacket extends EmptyPacket {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
package mc.protocol.packets.client;
|
package mc.protocol.packets.handshaking.client;
|
||||||
|
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
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;
|
||||||
import mc.protocol.packets.ClientSidePacket;
|
import mc.protocol.packets.ClientSidePacket;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -18,14 +18,14 @@ import mc.protocol.packets.ClientSidePacket;
|
|||||||
* | FIELD | TYPE | NOTES |
|
* | FIELD | TYPE | NOTES |
|
||||||
* |------------------|----------------|----------------------------------------------|
|
* |------------------|----------------|----------------------------------------------|
|
||||||
* | Protocol version | VarInt | Версия протокола [1] |
|
* | Protocol version | VarInt | Версия протокола [1] |
|
||||||
* | Server address | Stirng | Hostname или IP |
|
* | Server address | Stirng (255) | Hostname или IP |
|
||||||
* | Server port | Unsigned Short | Порт сервера |
|
* | Server port | Unsigned Short | Порт сервера |
|
||||||
* | Next stage | VarInt | ID State на который необходимо переключиться |
|
* | Next state | VarInt | ID State на который необходимо переключиться |
|
||||||
*
|
*
|
||||||
* [1] - <a href="https://wiki.vg/Protocol_version_numbers" target="_top">Protocol version numbers</a>
|
* [1] - <a href="https://wiki.vg/Protocol_version_numbers">Protocol version numbers</a>
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=7368#Handshake" target="_top">Handshake</a>
|
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Handshake">Handshake</a>
|
||||||
* @see State
|
* @see State
|
||||||
*/
|
*/
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@@ -41,10 +41,10 @@ public class HandshakePacket implements ClientSidePacket {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void readSelf(NetByteBuf netByteBuf) {
|
public void readSelf(NetByteBuf netByteBuf) {
|
||||||
protocolVersion = netByteBuf.readVarInt();
|
this.protocolVersion = netByteBuf.readVarInt();
|
||||||
host = netByteBuf.readString(255);
|
this.host = netByteBuf.readString(255);
|
||||||
port = netByteBuf.readUnsignedShort();
|
this.port = netByteBuf.readUnsignedShort();
|
||||||
nextState = State.getById(netByteBuf.readVarInt());
|
this.nextState = State.getById(netByteBuf.readVarInt());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -54,5 +54,4 @@ public class HandshakePacket implements ClientSidePacket {
|
|||||||
this.port = 0;
|
this.port = 0;
|
||||||
this.nextState = null;
|
this.nextState = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
package mc.protocol.packets.client;
|
package mc.protocol.packets.login.client;
|
||||||
|
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
import mc.protocol.io.NetByteBuf;
|
import mc.protocol.buffer.NetByteBuf;
|
||||||
import mc.protocol.packets.ClientSidePacket;
|
import mc.protocol.packets.ClientSidePacket;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -19,7 +19,7 @@ import mc.protocol.packets.ClientSidePacket;
|
|||||||
* | Name | String | Имя/Логин игрока |
|
* | Name | String | Имя/Логин игрока |
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=7368#Login_Start" target="_top">Login start</a>
|
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Login_Start">Login start</a>
|
||||||
*/
|
*/
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@Getter
|
@Getter
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
package mc.protocol.packets.server;
|
package mc.protocol.packets.login.server;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import mc.protocol.State;
|
import mc.protocol.State;
|
||||||
import mc.protocol.io.NetByteBuf;
|
import mc.protocol.buffer.NetByteBuf;
|
||||||
import mc.protocol.model.text.Text;
|
import mc.protocol.model.text.Text;
|
||||||
import mc.protocol.packets.ServerSidePacket;
|
import mc.protocol.packets.ServerSidePacket;
|
||||||
import mc.protocol.serializer.TextSerializer;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Diconnect packet.
|
* Diconnect packet.
|
||||||
@@ -16,7 +15,7 @@ import mc.protocol.serializer.TextSerializer;
|
|||||||
* <pre>
|
* <pre>
|
||||||
* | FIELD | TYPE | NOTES |
|
* | FIELD | TYPE | NOTES |
|
||||||
* |-------------|--------|----------------------------------|
|
* |-------------|--------|----------------------------------|
|
||||||
* | JSON Reason | String | Причина отключения. Опционально. |
|
* | JSON Reason | Text | Причина отключения. Опционально. |
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* <p>Пример JSON Reason</p>
|
* <p>Пример JSON Reason</p>
|
||||||
@@ -26,7 +25,7 @@ import mc.protocol.serializer.TextSerializer;
|
|||||||
* }
|
* }
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=7368#Disconnect_2" target="_top">Disconnect</a>
|
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Disconnect_.28login.29">Disconnect (login)</a>
|
||||||
* @see State
|
* @see State
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@@ -39,6 +38,6 @@ public class DisconnectPacket implements ServerSidePacket {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeSelf(NetByteBuf netByteBuf) {
|
public void writeSelf(NetByteBuf netByteBuf) {
|
||||||
netByteBuf.writeString(TextSerializer.toJsonObject(reason).toString());
|
netByteBuf.writeText(reason);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package mc.protocol.packets.server;
|
package mc.protocol.packets.login.server;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import mc.protocol.io.NetByteBuf;
|
import mc.protocol.buffer.NetByteBuf;
|
||||||
import mc.protocol.packets.ServerSidePacket;
|
import mc.protocol.packets.ServerSidePacket;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@@ -27,7 +27,7 @@ public class LoginSuccessPacket implements ServerSidePacket {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeSelf(NetByteBuf netByteBuf) {
|
public void writeSelf(NetByteBuf netByteBuf) {
|
||||||
netByteBuf.writeString(uuid.toString());
|
netByteBuf.writeString(this.uuid.toString());
|
||||||
netByteBuf.writeString(name);
|
netByteBuf.writeString(this.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,15 @@
|
|||||||
package mc.protocol.packets.client;
|
package mc.protocol.packets.play.client;
|
||||||
|
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
import mc.protocol.io.NetByteBuf;
|
import mc.protocol.buffer.NetByteBuf;
|
||||||
import mc.protocol.model.Location;
|
import mc.protocol.model.Location;
|
||||||
import mc.protocol.model.Look;
|
import mc.protocol.model.Look;
|
||||||
import mc.protocol.packets.ClientSidePacket;
|
import mc.protocol.packets.ClientSidePacket;
|
||||||
|
import mc.protocol.pool.LocationObjectPool;
|
||||||
|
import mc.protocol.pool.LookObjectPool;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Клиент сообщает о движении и повороте головы Игрока.
|
* Клиент сообщает о движении и повороте головы Игрока.
|
||||||
@@ -42,18 +44,20 @@ public class CPlayerPositionAndLookPacket implements ClientSidePacket {
|
|||||||
double x = netByteBuf.readDouble();
|
double x = netByteBuf.readDouble();
|
||||||
double y = netByteBuf.readDouble();
|
double y = netByteBuf.readDouble();
|
||||||
double z = netByteBuf.readDouble();
|
double z = netByteBuf.readDouble();
|
||||||
this.position = new Location(x, y, z);
|
this.position = LocationObjectPool.borrowObject().set(x, y, z);
|
||||||
|
|
||||||
float yaw = netByteBuf.readFloat();
|
float yaw = netByteBuf.readFloat();
|
||||||
float pitch = netByteBuf.readFloat();
|
float pitch = netByteBuf.readFloat();
|
||||||
this.look = new Look(yaw, pitch);
|
this.look = LookObjectPool.borrowObject().set(yaw, pitch);
|
||||||
|
|
||||||
this.onGround = netByteBuf.readBoolean();
|
this.onGround = netByteBuf.readBoolean();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void passivate() {
|
public void passivate() {
|
||||||
|
LocationObjectPool.returnObject(this.position);
|
||||||
this.position = null;
|
this.position = null;
|
||||||
|
LookObjectPool.returnObject(this.look);
|
||||||
this.look = null;
|
this.look = null;
|
||||||
this.onGround = false;
|
this.onGround = false;
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
package mc.protocol.packets.client;
|
package mc.protocol.packets.play.client;
|
||||||
|
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
import mc.protocol.utils.ChatMode;
|
import mc.protocol.utils.ChatMode;
|
||||||
import mc.protocol.utils.MainHand;
|
import mc.protocol.utils.MainHand;
|
||||||
import mc.protocol.io.NetByteBuf;
|
import mc.protocol.buffer.NetByteBuf;
|
||||||
import mc.protocol.packets.ClientSidePacket;
|
import mc.protocol.packets.ClientSidePacket;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1,12 +1,10 @@
|
|||||||
package mc.protocol.packets.client;
|
package mc.protocol.packets.play.client;
|
||||||
|
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.*;
|
||||||
import lombok.Getter;
|
import mc.protocol.buffer.NetByteBuf;
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
import lombok.ToString;
|
|
||||||
import mc.protocol.io.NetByteBuf;
|
|
||||||
import mc.protocol.packets.ClientSidePacket;
|
import mc.protocol.packets.ClientSidePacket;
|
||||||
import mc.protocol.utils.EntityActionAction;
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Entity Action packet.
|
* Entity Action packet.
|
||||||
@@ -31,7 +29,7 @@ import mc.protocol.utils.EntityActionAction;
|
|||||||
public class EntityActionPacket implements ClientSidePacket {
|
public class EntityActionPacket implements ClientSidePacket {
|
||||||
|
|
||||||
private Integer entityId;
|
private Integer entityId;
|
||||||
private EntityActionAction action;
|
private Action action;
|
||||||
private Integer jumpBoost;
|
private Integer jumpBoost;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -40,7 +38,7 @@ public class EntityActionPacket implements ClientSidePacket {
|
|||||||
int actionId = netByteBuf.readVarInt();
|
int actionId = netByteBuf.readVarInt();
|
||||||
this.jumpBoost = netByteBuf.readVarInt();
|
this.jumpBoost = netByteBuf.readVarInt();
|
||||||
|
|
||||||
this.action = EntityActionAction.valueOfCode(actionId);
|
this.action = Action.valueOfCode(actionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -49,4 +47,31 @@ public class EntityActionPacket implements ClientSidePacket {
|
|||||||
this.action = null;
|
this.action = null;
|
||||||
this.jumpBoost = null;
|
this.jumpBoost = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public enum Action {
|
||||||
|
START_SNEAKING(0),
|
||||||
|
STOP_SNEAKING(1),
|
||||||
|
LEAVE_BED(2),
|
||||||
|
START_SPRINTING(3),
|
||||||
|
STOP_SPRINTING(4),
|
||||||
|
START_JUMP_WITH_HORSE(5),
|
||||||
|
STOP_JUMP_WITH_HORSE(6),
|
||||||
|
OPEN_HORSE_INVENTORY(7),
|
||||||
|
START_FLYING_WITH_ELYTRA(8);
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static Action valueOfCode(int code) {
|
||||||
|
for (Action action : Action.values()) {
|
||||||
|
if (action.code == code) {
|
||||||
|
return action;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final int code;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
package mc.protocol.packets.client;
|
package mc.protocol.packets.play.client;
|
||||||
|
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
import mc.protocol.io.NetByteBuf;
|
import mc.protocol.buffer.NetByteBuf;
|
||||||
import mc.protocol.model.Look;
|
import mc.protocol.model.Look;
|
||||||
import mc.protocol.packets.ClientSidePacket;
|
import mc.protocol.packets.ClientSidePacket;
|
||||||
|
import mc.protocol.pool.LookObjectPool;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Клиент сообщает о повороте головы Игрока.
|
* Клиент сообщает о повороте головы Игрока.
|
||||||
@@ -35,13 +36,14 @@ public class PlayerLookPacket implements ClientSidePacket {
|
|||||||
public void readSelf(NetByteBuf netByteBuf) {
|
public void readSelf(NetByteBuf netByteBuf) {
|
||||||
float yaw = netByteBuf.readFloat();
|
float yaw = netByteBuf.readFloat();
|
||||||
float pitch = netByteBuf.readFloat();
|
float pitch = netByteBuf.readFloat();
|
||||||
this.look = new Look(yaw, pitch);
|
this.look = LookObjectPool.borrowObject().set(yaw, pitch);
|
||||||
|
|
||||||
this.onGround = netByteBuf.readBoolean();
|
this.onGround = netByteBuf.readBoolean();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void passivate() {
|
public void passivate() {
|
||||||
|
LookObjectPool.returnObject(this.look);
|
||||||
this.look = null;
|
this.look = null;
|
||||||
this.onGround = false;
|
this.onGround = false;
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
package mc.protocol.packets.client;
|
package mc.protocol.packets.play.client;
|
||||||
|
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
import mc.protocol.io.NetByteBuf;
|
import mc.protocol.buffer.NetByteBuf;
|
||||||
import mc.protocol.model.Location;
|
import mc.protocol.model.Location;
|
||||||
import mc.protocol.packets.ClientSidePacket;
|
import mc.protocol.packets.ClientSidePacket;
|
||||||
|
import mc.protocol.pool.LocationObjectPool;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Клиент сообщает о движении Игрока.
|
* Клиент сообщает о движении Игрока.
|
||||||
@@ -38,13 +39,14 @@ public class PlayerPositionPacket implements ClientSidePacket {
|
|||||||
double x = netByteBuf.readDouble();
|
double x = netByteBuf.readDouble();
|
||||||
double y = netByteBuf.readDouble();
|
double y = netByteBuf.readDouble();
|
||||||
double z = netByteBuf.readDouble();
|
double z = netByteBuf.readDouble();
|
||||||
this.position = new Location(x, y, z);
|
this.position = LocationObjectPool.borrowObject().set(x, y, z);
|
||||||
|
|
||||||
this.onGround = netByteBuf.readBoolean();
|
this.onGround = netByteBuf.readBoolean();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void passivate() {
|
public void passivate() {
|
||||||
|
LocationObjectPool.returnObject(this.position);
|
||||||
this.position = null;
|
this.position = null;
|
||||||
this.onGround = false;
|
this.onGround = false;
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
package mc.protocol.packets.client;
|
package mc.protocol.packets.play.client;
|
||||||
|
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
import mc.protocol.io.NetByteBuf;
|
import mc.protocol.buffer.NetByteBuf;
|
||||||
import mc.protocol.packets.ClientSidePacket;
|
import mc.protocol.packets.ClientSidePacket;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1,20 +1,20 @@
|
|||||||
package mc.protocol.packets.client;
|
package mc.protocol.packets.play.client;
|
||||||
|
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
import mc.protocol.io.NetByteBuf;
|
import mc.protocol.buffer.NetByteBuf;
|
||||||
import mc.protocol.packets.ClientSidePacket;
|
import mc.protocol.packets.ClientSidePacket;
|
||||||
import mc.protocol.packets.server.SPlayerPositionAndLookPacket;
|
import mc.protocol.packets.play.server.SPlayerPositionAndLookPacket;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Teleport сonfirm packet.
|
* Teleport сonfirm packet.
|
||||||
*
|
*
|
||||||
* <p>Структура пакета</p>
|
* <p>Структура пакета</p>
|
||||||
* <pre>
|
* <pre>
|
||||||
* | FIELD | TYPE | NOTES |
|
* | FIELD | TYPE | NOTES |
|
||||||
* |-------------|--------|-----------------------------------------------------------|
|
* |-------------|--------|------------------------------------------------------------|
|
||||||
* | Teleport ID | VarInt | ID, который был выдан пакетом {@link SPlayerPositionAndLookPacket} |
|
* | Teleport ID | VarInt | ID, который был выдан пакетом {@link SPlayerPositionAndLookPacket} |
|
||||||
* </pre>
|
* </pre>
|
||||||
*
|
*
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
package mc.protocol.packets.server;
|
package mc.protocol.packets.play.server;
|
||||||
|
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import mc.protocol.io.NetByteBuf;
|
import mc.protocol.buffer.NetByteBuf;
|
||||||
import mc.protocol.packets.ServerSidePacket;
|
import mc.protocol.packets.ServerSidePacket;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
package mc.protocol.packets.server;
|
package mc.protocol.packets.play.server;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import mc.protocol.utils.Difficulty;
|
import mc.protocol.utils.Difficulty;
|
||||||
import mc.protocol.utils.GameMode;
|
import mc.protocol.utils.GameMode;
|
||||||
import mc.protocol.utils.LevelType;
|
import mc.protocol.utils.LevelType;
|
||||||
import mc.protocol.io.NetByteBuf;
|
import mc.protocol.buffer.NetByteBuf;
|
||||||
import mc.protocol.packets.ServerSidePacket;
|
import mc.protocol.packets.ServerSidePacket;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package mc.protocol.packets.server;
|
package mc.protocol.packets.play.server;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import mc.protocol.io.NetByteBuf;
|
import mc.protocol.buffer.NetByteBuf;
|
||||||
import mc.protocol.packets.ServerSidePacket;
|
import mc.protocol.packets.ServerSidePacket;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
package mc.protocol.packets.server;
|
package mc.protocol.packets.play.server;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import mc.protocol.io.NetByteBuf;
|
import mc.protocol.buffer.NetByteBuf;
|
||||||
import mc.protocol.model.Location;
|
import mc.protocol.model.Location;
|
||||||
import mc.protocol.model.Look;
|
import mc.protocol.model.Look;
|
||||||
import mc.protocol.packets.ServerSidePacket;
|
import mc.protocol.packets.ServerSidePacket;
|
||||||
import mc.protocol.packets.client.TeleportConfirmPacket;
|
import mc.protocol.packets.play.client.TeleportConfirmPacket;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Установка позиции и угла осмотра Игрока.
|
* Установка позиции и угла осмотра Игрока.
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package mc.protocol.packets.server;
|
package mc.protocol.packets.play.server;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import mc.protocol.io.NetByteBuf;
|
import mc.protocol.buffer.NetByteBuf;
|
||||||
import mc.protocol.model.Location;
|
import mc.protocol.model.Location;
|
||||||
import mc.protocol.packets.ServerSidePacket;
|
import mc.protocol.packets.ServerSidePacket;
|
||||||
|
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package mc.protocol.packets.status.client;
|
||||||
|
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.ToString;
|
||||||
|
import mc.protocol.buffer.NetByteBuf;
|
||||||
|
import mc.protocol.packets.ClientSidePacket;
|
||||||
|
import mc.protocol.packets.ServerSidePacket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Status server packet, request.
|
||||||
|
*
|
||||||
|
* <p>Клиент запрашивает получение информации о сервере</p>
|
||||||
|
*/
|
||||||
|
@NoArgsConstructor
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
@ToString
|
||||||
|
public class StatusServerRequestPacket implements ClientSidePacket, ServerSidePacket {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readSelf(NetByteBuf netByteBuf) {
|
||||||
|
// empty
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeSelf(NetByteBuf netByteBuf) {
|
||||||
|
// empty
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void passivate() {
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
package mc.protocol.packets.server;
|
package mc.protocol.packets.status.server;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import mc.protocol.io.NetByteBuf;
|
import mc.protocol.buffer.NetByteBuf;
|
||||||
import mc.protocol.model.ServerInfo;
|
|
||||||
import mc.protocol.packets.ServerSidePacket;
|
import mc.protocol.packets.ServerSidePacket;
|
||||||
import mc.protocol.serializer.ServerInfoSerializer;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Status server packet, response.
|
* Status server packet, response.
|
||||||
@@ -52,10 +50,10 @@ public class StatusServerResponse implements ServerSidePacket {
|
|||||||
/**
|
/**
|
||||||
* Информация о серере.
|
* Информация о серере.
|
||||||
*/
|
*/
|
||||||
private ServerInfo info;
|
private String info;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeSelf(NetByteBuf netByteBuf) {
|
public void writeSelf(NetByteBuf netByteBuf) {
|
||||||
netByteBuf.writeString(ServerInfoSerializer.toJsonObject(info).toString());
|
netByteBuf.writeString(info);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package mc.protocol.pool;
|
||||||
|
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import mc.protocol.model.Location;
|
||||||
|
import org.apache.commons.pool2.BasePooledObjectFactory;
|
||||||
|
import org.apache.commons.pool2.ObjectPool;
|
||||||
|
import org.apache.commons.pool2.PooledObject;
|
||||||
|
import org.apache.commons.pool2.impl.DefaultPooledObject;
|
||||||
|
import org.apache.commons.pool2.impl.GenericObjectPool;
|
||||||
|
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public class LocationObjectPool extends BasePooledObjectFactory<Location> {
|
||||||
|
|
||||||
|
private static final ObjectPool<Location> instance = new GenericObjectPool<>(new LocationObjectPool());
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Location create() {
|
||||||
|
return new Location();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PooledObject<Location> wrap(Location location) {
|
||||||
|
return new DefaultPooledObject<>(location);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public static Location borrowObject() {
|
||||||
|
return instance.borrowObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public static void returnObject(Location location) {
|
||||||
|
instance.returnObject(location);
|
||||||
|
}
|
||||||
|
}
|
||||||
37
protocol/src/main/java/mc/protocol/pool/LookObjectPool.java
Normal file
37
protocol/src/main/java/mc/protocol/pool/LookObjectPool.java
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package mc.protocol.pool;
|
||||||
|
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import mc.protocol.model.Look;
|
||||||
|
import org.apache.commons.pool2.BasePooledObjectFactory;
|
||||||
|
import org.apache.commons.pool2.ObjectPool;
|
||||||
|
import org.apache.commons.pool2.PooledObject;
|
||||||
|
import org.apache.commons.pool2.impl.DefaultPooledObject;
|
||||||
|
import org.apache.commons.pool2.impl.GenericObjectPool;
|
||||||
|
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public class LookObjectPool extends BasePooledObjectFactory<Look> {
|
||||||
|
|
||||||
|
private static final ObjectPool<Look> instance = new GenericObjectPool<>(new LookObjectPool());
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Look create() {
|
||||||
|
return new Look();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PooledObject<Look> wrap(Look look) {
|
||||||
|
return new DefaultPooledObject<>(look);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public static Look borrowObject() {
|
||||||
|
return instance.borrowObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
public static void returnObject(Look look) {
|
||||||
|
instance.returnObject(look);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,30 +0,0 @@
|
|||||||
package mc.protocol.pool;
|
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import mc.protocol.packets.ClientSidePacket;
|
|
||||||
import org.apache.commons.pool2.BasePooledObjectFactory;
|
|
||||||
import org.apache.commons.pool2.PooledObject;
|
|
||||||
import org.apache.commons.pool2.impl.DefaultPooledObject;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class PacketFactory<P extends ClientSidePacket> extends BasePooledObjectFactory<P> {
|
|
||||||
|
|
||||||
private final Class<P> clazz;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public P create() throws Exception {
|
|
||||||
return clazz.getDeclaredConstructor().newInstance();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PooledObject<P> wrap(P packet) {
|
|
||||||
return new DefaultPooledObject<>(packet);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void passivateObject(PooledObject<P> pooledPacket) {
|
|
||||||
pooledPacket.getObject().passivate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package mc.protocol.pool;
|
||||||
|
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import mc.protocol.packets.ClientSidePacket;
|
||||||
|
import mc.utils.pool.WideClassObjectPool;
|
||||||
|
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public class PacketObjectPool extends WideClassObjectPool<ClientSidePacket> {
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private static final PacketObjectPool instance = new PacketObjectPool();
|
||||||
|
}
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
package mc.protocol.pool;
|
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import mc.protocol.packets.ClientSidePacket;
|
|
||||||
import org.apache.commons.pool2.ObjectPool;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class PacketPool {
|
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
private final Map<Class<? extends ClientSidePacket>, ObjectPool> mapPoolPackets;
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public <P extends ClientSidePacket> P borrowObject(Class<P> packetClass) throws Exception {
|
|
||||||
return (P) mapPoolPackets.get(packetClass).borrowObject();
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public <P extends ClientSidePacket> void returnObject(P packet) throws Exception {
|
|
||||||
mapPoolPackets.get(packet.getClass()).returnObject(packet);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -11,10 +11,10 @@ public enum ChatMode {
|
|||||||
public static ChatMode valueById(int id) {
|
public static ChatMode valueById(int id) {
|
||||||
// а зачем усложнять?
|
// а зачем усложнять?
|
||||||
//@formatter:off
|
//@formatter:off
|
||||||
if (id == 1) return FULL;
|
if (id == 1) return FULL;
|
||||||
else if (id == 2) return COMMANDS_ONLY;
|
else if (id == 2) return COMMANDS_ONLY;
|
||||||
else if (id == 3) return HIDDEN;
|
else if (id == 3) return HIDDEN;
|
||||||
else return null;
|
else return null;
|
||||||
//@formatter:on
|
//@formatter:on
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
package mc.protocol.utils;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public enum EntityActionAction {
|
|
||||||
START_SNEAKING(0),
|
|
||||||
STOP_SNEAKING(1),
|
|
||||||
LEAVE_BED(2),
|
|
||||||
START_SPRINTING(3),
|
|
||||||
STOP_SPRINTING(4),
|
|
||||||
START_JUMP_WITH_HORSE(5),
|
|
||||||
STOP_JUMP_WITH_HORSE(6),
|
|
||||||
OPEN_HORSE_INVENTORY(7),
|
|
||||||
START_FLYING_WITH_ELYTRA(8);
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public static EntityActionAction valueOfCode(int code) {
|
|
||||||
for (EntityActionAction action : EntityActionAction.values()) {
|
|
||||||
if (action.code == code) {
|
|
||||||
return action;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
private final int code;
|
|
||||||
}
|
|
||||||
@@ -10,9 +10,9 @@ public enum MainHand {
|
|||||||
public static MainHand valueById(int id) {
|
public static MainHand valueById(int id) {
|
||||||
// а зачем усложнять?
|
// а зачем усложнять?
|
||||||
//@formatter:off
|
//@formatter:off
|
||||||
if (id == 0) return LEFT;
|
if (id == 0) return LEFT;
|
||||||
else if (id == 1) return RIGHT;
|
else if (id == 1) return RIGHT;
|
||||||
else return null;
|
else return null;
|
||||||
//@formatter:on
|
//@formatter:on
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package mc.protocol.io;
|
package mc.protocol.buffer;
|
||||||
|
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
@@ -16,7 +16,6 @@ import java.util.UUID;
|
|||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
|
||||||
|
|
||||||
class NetByteBufReadTest {
|
class NetByteBufReadTest {
|
||||||
|
|
||||||
@@ -108,7 +107,7 @@ class NetByteBufReadTest {
|
|||||||
|
|
||||||
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
|
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
|
||||||
|
|
||||||
assertThrows(DecoderException.class, () -> netByteBuf.readString(length));
|
assertThrows(NetIOException.class, () -> netByteBuf.readString(length));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -127,7 +126,7 @@ class NetByteBufReadTest {
|
|||||||
|
|
||||||
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
|
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
|
||||||
|
|
||||||
assertThrows(DecoderException.class, () -> netByteBuf.readString(-1));
|
assertThrows(NetIOException.class, () -> netByteBuf.readString(-1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package mc.protocol.io;
|
package mc.protocol.buffer;
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
@@ -48,6 +48,16 @@ class NetByteBufWriteTest {
|
|||||||
assertEquals(expectedByte, byteBuf.array()[0]);
|
assertEquals(expectedByte, byteBuf.array()[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void writeUnsignedByte() {
|
||||||
|
ByteBuf byteBuf = Unpooled.buffer();
|
||||||
|
NetByteBuf netByteBuf = new NetByteBuf(byteBuf);
|
||||||
|
|
||||||
|
netByteBuf.writeUnsignedByte(129);
|
||||||
|
|
||||||
|
assertEquals(129, netByteBuf.readUnsignedByte());
|
||||||
|
}
|
||||||
|
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@MethodSource("paramsWriteString")
|
@MethodSource("paramsWriteString")
|
||||||
void writeString(String string, int exceptedLength) {
|
void writeString(String string, int exceptedLength) {
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package mc.protocol.handler.codec;
|
||||||
|
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class ProtocolSplitterTest {
|
||||||
|
|
||||||
|
byte[] expectedPacket1Bytes;
|
||||||
|
byte[] expectedPacket2Bytes;
|
||||||
|
byte[] bytes;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void split() {
|
||||||
|
prepare();
|
||||||
|
|
||||||
|
ByteBuf inputBuffer = Unpooled.wrappedBuffer(bytes);
|
||||||
|
List<Object> outputObject = new ArrayList<>();
|
||||||
|
|
||||||
|
ProtocolSplitter protocolSplitter = new ProtocolSplitter();
|
||||||
|
protocolSplitter.decode(null, inputBuffer, outputObject);
|
||||||
|
|
||||||
|
assertEquals(2, outputObject.size());
|
||||||
|
assertTrue(outputObject.get(0) instanceof ByteBuf);
|
||||||
|
assertArrayEquals(expectedPacket1Bytes, ((ByteBuf)outputObject.get(0)).array());
|
||||||
|
assertTrue(outputObject.get(1) instanceof ByteBuf);
|
||||||
|
assertArrayEquals(expectedPacket2Bytes, ((ByteBuf)outputObject.get(1)).array());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void prepare() {
|
||||||
|
/*
|
||||||
|
+-------------------------------------------------+
|
||||||
|
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
|
||||||
|
+--------+-------------------------------------------------+----------------+
|
||||||
|
|00000000| 10 00 d4 02 09 31 32 37 2e 30 2e 30 2e 31 63 dd |.....127.0.0.1c.|
|
||||||
|
|00000010| 01 01 00 |... |
|
||||||
|
+--------+-------------------------------------------------+----------------+
|
||||||
|
*/
|
||||||
|
expectedPacket1Bytes = new byte[]{
|
||||||
|
0x00, (byte) 0xd4, 0x02, 0x09, 0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x63,
|
||||||
|
(byte) 0xdd, 0x01
|
||||||
|
};
|
||||||
|
expectedPacket2Bytes = new byte[]{
|
||||||
|
0x00
|
||||||
|
};
|
||||||
|
bytes = new byte[2 + expectedPacket1Bytes.length + expectedPacket2Bytes.length];
|
||||||
|
bytes[0] = 0x10;
|
||||||
|
System.arraycopy(expectedPacket1Bytes, 0,
|
||||||
|
bytes, 1, expectedPacket1Bytes.length);
|
||||||
|
bytes[1 + expectedPacket1Bytes.length] = 0x01;
|
||||||
|
System.arraycopy(expectedPacket2Bytes, 0,
|
||||||
|
bytes, 2 + expectedPacket1Bytes.length, expectedPacket2Bytes.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -23,13 +23,14 @@ application {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation project(':cli-parser')
|
||||||
implementation project(':protocol')
|
implementation project(':protocol')
|
||||||
|
|
||||||
implementation libs.logger.logback
|
implementation libs.logger.logback
|
||||||
|
implementation libs.hocon
|
||||||
|
|
||||||
implementation libs.yaml
|
implementation libs.netty.transport
|
||||||
implementation libs.ioutils
|
implementation libs.netty.handler
|
||||||
implementation libs.jopt
|
|
||||||
}
|
}
|
||||||
|
|
||||||
shadowJar {
|
shadowJar {
|
||||||
|
|||||||
@@ -3,165 +3,109 @@ package mc.server;
|
|||||||
import ch.qos.logback.classic.LoggerContext;
|
import ch.qos.logback.classic.LoggerContext;
|
||||||
import ch.qos.logback.classic.joran.JoranConfigurator;
|
import ch.qos.logback.classic.joran.JoranConfigurator;
|
||||||
import ch.qos.logback.core.joran.spi.JoranException;
|
import ch.qos.logback.core.joran.spi.JoranException;
|
||||||
import joptsimple.OptionParser;
|
import com.typesafe.config.Config;
|
||||||
import joptsimple.OptionSet;
|
import lombok.SneakyThrows;
|
||||||
import joptsimple.util.PathConverter;
|
import mc.cliparser.CommandLine;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import mc.cliparser.CommandLineParser;
|
||||||
import mc.protocol.State;
|
import mc.cliparser.Option;
|
||||||
import mc.protocol.api.Server;
|
import mc.server.di.*;
|
||||||
import mc.protocol.di.DaggerProtocolComponent;
|
import org.slf4j.Logger;
|
||||||
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 org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.net.URL;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
@SuppressWarnings("java:S106")
|
|
||||||
public class Main {
|
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 {
|
public static void main(String[] args) throws IOException {
|
||||||
OptionParser optionParser = createOptionParser();
|
//region Setup CommandLine
|
||||||
OptionSet optionSet = optionParser.parse(args);
|
Option logconfigOption = Option.builder().longName("logconfig").hasArgs(true).build();
|
||||||
|
Option configOption = Option.builder().longName("config").hasArgs(true).build();
|
||||||
|
var cliParser = new CommandLineParser();
|
||||||
|
cliParser.addOption(logconfigOption);
|
||||||
|
cliParser.addOption(configOption);
|
||||||
|
|
||||||
if (optionSet.has("help")) {
|
CommandLine commandLine = cliParser.parse(args);
|
||||||
try {
|
//endregion
|
||||||
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)) {
|
//region Configuration Logback
|
||||||
return;
|
Path logconfigPath;
|
||||||
}
|
if (commandLine.has(logconfigOption)) {
|
||||||
|
logconfigPath = Paths.get(logconfigOption.value());
|
||||||
InputStream configResource = Objects.requireNonNull(Main.class.getResourceAsStream("/config-sample.yml"));
|
} else {
|
||||||
InputStream logbackResource = Objects.requireNonNull(Main.class.getResourceAsStream("/logback-sample.xml"));
|
logconfigPath = defaultLogbackConfigPath();
|
||||||
|
|
||||||
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(logconfigPath);
|
||||||
|
//endregion
|
||||||
|
|
||||||
reconfigureLogback(optionSet);
|
//region Setup config
|
||||||
|
ConfigModule configModule = new ConfigModule("./config-default.conf",
|
||||||
|
commandLine.has(configOption) ? Paths.get(configOption.value()) : null);
|
||||||
|
ConfigComponent configComponent = DaggerConfigComponent.builder()
|
||||||
|
.configModule(configModule).build();
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
//region Debug log config
|
||||||
|
Logger log = LoggerFactory.getLogger("LAUNCHER");
|
||||||
if (log.isDebugEnabled()) {
|
if (log.isDebugEnabled()) {
|
||||||
optionSet.asMap().forEach((optionSpec, objects) -> {
|
Config config = configComponent.getConfig();
|
||||||
if (optionSpec.isForHelp()) return;
|
log.debug("Logback config path: {}", logconfigOption.value() == null ? "(default)" : logconfigOption.value());
|
||||||
log.debug("OptionSet | {} = {}", optionSpec.options(), objects);
|
log.debug("Config path: {}", configOption.value() == null ? "(default)" : configOption.value());
|
||||||
});
|
|
||||||
|
config.entrySet().stream()
|
||||||
|
.map(entry -> String.format("[CONFIG] %s = %s", entry.getKey(), entry.getValue().render()))
|
||||||
|
.sorted()
|
||||||
|
.forEach(log::debug);
|
||||||
|
}
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
ServerComponent serverComponent = DaggerServerComponent.builder()
|
||||||
|
.processorModule(new ProcessorModule(configComponent.getConfig()))
|
||||||
|
.build();
|
||||||
|
serverComponent.getProcessors().forEach(processor -> processor.setup(serverComponent.getProtocolHandlersBus()));
|
||||||
|
|
||||||
|
NettyServer server = serverComponent.getNettyServer();
|
||||||
|
|
||||||
|
String host = configComponent.getConfig().getString("server.host");
|
||||||
|
int port = configComponent.getConfig().getInt("server.port");
|
||||||
|
|
||||||
|
log.info("Server starting: {}:{}", host, port);
|
||||||
|
server.start(host, port);
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO нужно продумать как этот метод сделать доступным для расширенных версий сервера
|
||||||
|
/**
|
||||||
|
* Перенастраиваем logback с учетом путей.
|
||||||
|
* <p>По-умолчанию, logback пытается искать свои конфиги по заранее зашитым путям.
|
||||||
|
* Здесь мы изменяем эти принципы.
|
||||||
|
*/
|
||||||
|
private static void reconfigureLogback(@Nonnull Path configPath) throws IOException {
|
||||||
|
if (Files.notExists(configPath)) {
|
||||||
|
throw new FileNotFoundException(configPath.toAbsolutePath().toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
LoggerContext logbackContext = (LoggerContext) LoggerFactory.getILoggerFactory();
|
||||||
logbackContext.reset();
|
logbackContext.reset();
|
||||||
JoranConfigurator configurator = new JoranConfigurator();
|
JoranConfigurator configurator = new JoranConfigurator();
|
||||||
|
|
||||||
Path logbackPath = (Path) optionSet.valueOf(CLI_LOGCONFIG);
|
try(InputStream in = Files.newInputStream(configPath)) {
|
||||||
try(InputStream in = Objects.requireNonNull(
|
|
||||||
Files.newInputStream(logbackPath), "File not found: " + logbackPath.toAbsolutePath())) {
|
|
||||||
|
|
||||||
configurator.setContext(logbackContext);
|
configurator.setContext(logbackContext);
|
||||||
configurator.doConfigure(in);
|
configurator.doConfigure(in);
|
||||||
} catch (JoranException e) {
|
} catch (JoranException e) {
|
||||||
throw new IOException(e);
|
throw new IOException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
private static Path defaultLogbackConfigPath() {
|
||||||
|
URL url = Objects.requireNonNull(Main.class.getResource("/logback-default.xml"));
|
||||||
|
return Paths.get(url.toURI());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
55
server/src/main/java/mc/server/NettyServer.java
Normal file
55
server/src/main/java/mc/server/NettyServer.java
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package mc.server;
|
||||||
|
|
||||||
|
import io.netty.bootstrap.ServerBootstrap;
|
||||||
|
import io.netty.channel.ChannelInitializer;
|
||||||
|
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.handler.ProtocolHandlersBus;
|
||||||
|
import mc.protocol.handler.ProtocolInboundHandler;
|
||||||
|
import mc.protocol.handler.codec.ProtocolDecoder;
|
||||||
|
import mc.protocol.handler.codec.ProtocolEncoder;
|
||||||
|
import mc.protocol.handler.codec.ProtocolSplitter;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class NettyServer {
|
||||||
|
|
||||||
|
private final ProtocolHandlersBus protocolHandlersBus;
|
||||||
|
|
||||||
|
public void start(String host, int port) {
|
||||||
|
try {
|
||||||
|
createServerBootstrap().bind(host, port).sync().channel().closeFuture().sync();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
if (log.isTraceEnabled()) {
|
||||||
|
log.trace("{}: {}", e.getClass().getSimpleName(), e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ServerBootstrap createServerBootstrap() {
|
||||||
|
ServerBootstrap bootstrap = new ServerBootstrap();
|
||||||
|
|
||||||
|
bootstrap.group(new NioEventLoopGroup(1), new NioEventLoopGroup())
|
||||||
|
.channel(NioServerSocketChannel.class)
|
||||||
|
.childHandler(new ChannelInitializer<SocketChannel>() {
|
||||||
|
@Override
|
||||||
|
protected void initChannel(@Nonnull SocketChannel socketChannel) {
|
||||||
|
socketChannel.pipeline()
|
||||||
|
.addLast("packet_splitter", new ProtocolSplitter())
|
||||||
|
.addLast("logger", new LoggingHandler(LogLevel.DEBUG))
|
||||||
|
.addLast("packet_decoder", new ProtocolDecoder(false))
|
||||||
|
.addLast("packet_encoder", new ProtocolEncoder())
|
||||||
|
.addLast("packet_handler", new ProtocolInboundHandler(protocolHandlersBus));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return bootstrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,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/src/main/java/mc/server/ServetAttributes.java
Normal file
10
server/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");
|
||||||
|
}
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
package mc.server.config;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
import lombok.ToString;
|
|
||||||
import lombok.experimental.Accessors;
|
|
||||||
|
|
||||||
import java.nio.file.Path;
|
|
||||||
|
|
||||||
@Accessors(fluent = true)
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@ToString
|
|
||||||
public class Config {
|
|
||||||
|
|
||||||
private final Server server = new Server();
|
|
||||||
private final Players players = new Players();
|
|
||||||
private final World world = new World();
|
|
||||||
|
|
||||||
private String motd;
|
|
||||||
private String disconnectReason;
|
|
||||||
private Path iconPath;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@ToString
|
|
||||||
public static class Server {
|
|
||||||
private String host;
|
|
||||||
private int port;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@ToString
|
|
||||||
public static class Players {
|
|
||||||
private final FakeOnline fakeOnline = new FakeOnline();
|
|
||||||
|
|
||||||
private int maxOnlile;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@ToString
|
|
||||||
public static class FakeOnline {
|
|
||||||
private boolean enable;
|
|
||||||
private int value;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@ToString
|
|
||||||
public static class World {
|
|
||||||
private int viewDistance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
13
server/src/main/java/mc/server/di/ConfigComponent.java
Normal file
13
server/src/main/java/mc/server/di/ConfigComponent.java
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package mc.server.di;
|
||||||
|
|
||||||
|
import com.typesafe.config.Config;
|
||||||
|
import dagger.Component;
|
||||||
|
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
@Component(modules = ConfigModule.class)
|
||||||
|
@Singleton
|
||||||
|
public interface ConfigComponent {
|
||||||
|
|
||||||
|
Config getConfig();
|
||||||
|
}
|
||||||
@@ -1,72 +1,36 @@
|
|||||||
package mc.server.di;
|
package mc.server.di;
|
||||||
|
|
||||||
|
import com.typesafe.config.Config;
|
||||||
|
import com.typesafe.config.ConfigFactory;
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
import dagger.Provides;
|
import dagger.Provides;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import mc.server.config.Config;
|
|
||||||
import org.yaml.snakeyaml.Yaml;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import javax.inject.Singleton;
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
@Module
|
@Module
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
public class ConfigModule {
|
public class ConfigModule {
|
||||||
|
|
||||||
|
private final String defaultResource;
|
||||||
private final Path configPath;
|
private final Path configPath;
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
@Singleton
|
||||||
Config provideConfig() {
|
Config provideConfig() {
|
||||||
Config config = new Config();
|
Config defaultConfig = ConfigFactory.parseResources(defaultResource);
|
||||||
Map<String, Object> map = new Yaml().load(readConfigAsString());
|
Config config;
|
||||||
|
|
||||||
config.server().host(fromYamlPath("server/host", map, "127.0.0.1"));
|
if (configPath != null) {
|
||||||
config.server().port(fromYamlPath("server/port", map, 25565));
|
Config userConfig = ConfigFactory.parseFile(configPath.toFile());
|
||||||
|
config = userConfig.withFallback(defaultConfig).resolve();
|
||||||
config.motd(fromYamlPath("motd", map, ""));
|
} else {
|
||||||
config.disconnectReason(fromYamlPath("disconnect-reason", map, ""));
|
config = defaultConfig.resolve();
|
||||||
|
|
||||||
config.players().maxOnlile(fromYamlPath("players/max-online", map, 0));
|
|
||||||
config.players().fakeOnline().enable(fromYamlPath("players/fake-online/enable", map, false));
|
|
||||||
config.players().fakeOnline().value(fromYamlPath("players/fake-online/value", map, 0));
|
|
||||||
|
|
||||||
config.world().viewDistance(fromYamlPath("world/view-distance", map, 0));
|
|
||||||
|
|
||||||
if (Boolean.TRUE.equals(fromYamlPath("icon/enable", map, false))) {
|
|
||||||
config.iconPath(Paths.get(fromYamlPath("icon/path", map, "favicon.png")));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
map.clear();
|
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String readConfigAsString() {
|
|
||||||
try {
|
|
||||||
return Files.readString(configPath);
|
|
||||||
} catch (IOException e) {
|
|
||||||
log.error("Can't load config from '{}'", configPath.toAbsolutePath(), e);
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private static <T> T fromYamlPath(String mapPath, Map<String, Object> map, T defaultValue) {
|
|
||||||
String[] keys = mapPath.split("/", 2);
|
|
||||||
|
|
||||||
if (map.containsKey(keys[0])) {
|
|
||||||
Object object = map.get(keys[0]);
|
|
||||||
if (keys.length > 1) {
|
|
||||||
return fromYamlPath(keys[1], (Map<String, Object>) object, defaultValue);
|
|
||||||
} else {
|
|
||||||
return (T) object;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
||||||
}
|
}
|
||||||
48
server/src/main/java/mc/server/di/ProcessorModule.java
Normal file
48
server/src/main/java/mc/server/di/ProcessorModule.java
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package mc.server.di;
|
||||||
|
|
||||||
|
import com.typesafe.config.Config;
|
||||||
|
import dagger.Module;
|
||||||
|
import dagger.Provides;
|
||||||
|
import dagger.multibindings.IntoSet;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import mc.protocol.world.World;
|
||||||
|
import mc.server.PlayerManager;
|
||||||
|
import mc.server.processor.*;
|
||||||
|
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ProcessorModule {
|
||||||
|
|
||||||
|
private final Config config;
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@IntoSet
|
||||||
|
@Singleton
|
||||||
|
PacketProcessor provideProcessorHadshake() {
|
||||||
|
return new ProcessorHandshake();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@IntoSet
|
||||||
|
@Singleton
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,18 +1,21 @@
|
|||||||
package mc.server.di;
|
package mc.server.di;
|
||||||
|
|
||||||
import dagger.Component;
|
import dagger.Component;
|
||||||
import mc.protocol.di.ServerScope;
|
import mc.protocol.handler.ProtocolHandlersBus;
|
||||||
import mc.server.PacketHandler;
|
import mc.server.NettyServer;
|
||||||
import mc.server.config.Config;
|
import mc.server.processor.PacketProcessor;
|
||||||
import mc.server.service.PlayerManager;
|
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
@Component(modules = {
|
@Component(modules = {
|
||||||
ConfigModule.class, PacketHandlerModule.class, WorldModule.class, PlayersModule.class
|
ServerModule.class, ProcessorModule.class, PlayerManagerModule.class,
|
||||||
|
WorldModule.class
|
||||||
})
|
})
|
||||||
@ServerScope
|
@Singleton
|
||||||
public interface ServerComponent {
|
public interface ServerComponent {
|
||||||
|
|
||||||
Config getConfig();
|
ProtocolHandlersBus getProtocolHandlersBus();
|
||||||
PacketHandler getPacketHandler();
|
NettyServer getNettyServer();
|
||||||
PlayerManager getPlayerManager();
|
Set<PacketProcessor> getProcessors();
|
||||||
}
|
}
|
||||||
|
|||||||
24
server/src/main/java/mc/server/di/ServerModule.java
Normal file
24
server/src/main/java/mc/server/di/ServerModule.java
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package mc.server.di;
|
||||||
|
|
||||||
|
import dagger.Module;
|
||||||
|
import dagger.Provides;
|
||||||
|
import mc.protocol.handler.ProtocolHandlersBus;
|
||||||
|
import mc.server.NettyServer;
|
||||||
|
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
@Module
|
||||||
|
public class ServerModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
ProtocolHandlersBus providePacketProcessor() {
|
||||||
|
return new ProtocolHandlersBus();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
NettyServer provideNettyServer(ProtocolHandlersBus protocolHandlersBus) {
|
||||||
|
return new NettyServer(protocolHandlersBus);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package mc.server.processor;
|
||||||
|
|
||||||
|
import mc.protocol.handler.ProtocolHandlersBus;
|
||||||
|
|
||||||
|
public interface PacketProcessor {
|
||||||
|
|
||||||
|
void setup(ProtocolHandlersBus protocolHandlersBus);
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package mc.server.processor;
|
||||||
|
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import mc.protocol.ProtocolAttributes;
|
||||||
|
import mc.protocol.State;
|
||||||
|
import mc.protocol.handler.ProtocolHandlersBus;
|
||||||
|
import mc.protocol.packets.handshaking.client.HandshakePacket;
|
||||||
|
|
||||||
|
public class ProcessorHandshake implements PacketProcessor {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setup(ProtocolHandlersBus protocolHandlersBus) {
|
||||||
|
protocolHandlersBus.addHandler(State.HANDSHAKING, HandshakePacket.class, this::handshake);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handshake(ChannelHandlerContext ctx, HandshakePacket packet) {
|
||||||
|
ctx.channel().attr(ProtocolAttributes.STATE).set(packet.getNextState());
|
||||||
|
}
|
||||||
|
}
|
||||||
142
server/src/main/java/mc/server/processor/ProcessorLogin.java
Normal file
142
server/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);
|
||||||
|
}
|
||||||
|
}
|
||||||
30
server/src/main/java/mc/server/processor/ProcessorPlay.java
Normal file
30
server/src/main/java/mc/server/processor/ProcessorPlay.java
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package mc.server.processor;
|
||||||
|
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import mc.protocol.State;
|
||||||
|
import mc.protocol.handler.ProtocolHandlersBus;
|
||||||
|
import mc.protocol.packets.KeepAlivePacket;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class ProcessorPlay implements PacketProcessor {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setup(ProtocolHandlersBus protocolHandlersBus) {
|
||||||
|
protocolHandlersBus
|
||||||
|
.addHandler(State.PLAY, KeepAlivePacket.class, this::keepAlive);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void keepAlive(ChannelHandlerContext ctx, KeepAlivePacket packet) {
|
||||||
|
try {
|
||||||
|
TimeUnit.MILLISECONDS.sleep(50);
|
||||||
|
ctx.writeAndFlush(packet);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
if (log.isTraceEnabled()) {
|
||||||
|
log.trace("{}", e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package mc.server.processor;
|
||||||
|
|
||||||
|
import com.typesafe.config.Config;
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import mc.protocol.ProtocolConstant;
|
||||||
|
import mc.protocol.State;
|
||||||
|
import mc.protocol.handler.ProtocolHandlersBus;
|
||||||
|
import mc.protocol.model.ServerInfo;
|
||||||
|
import mc.protocol.model.ServerInfoSerializer;
|
||||||
|
import mc.protocol.model.text.TextSerializer;
|
||||||
|
import mc.protocol.packets.KeepAlivePacket;
|
||||||
|
import mc.protocol.packets.status.client.StatusServerRequestPacket;
|
||||||
|
import mc.protocol.packets.status.server.StatusServerResponse;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ProcessorStatus implements PacketProcessor {
|
||||||
|
|
||||||
|
private final Config config;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setup(ProtocolHandlersBus protocolHandlersBus) {
|
||||||
|
protocolHandlersBus
|
||||||
|
.addHandler(State.STATUS, KeepAlivePacket.class, this::keepAlive)
|
||||||
|
.addHandler(State.STATUS, StatusServerRequestPacket.class, this::statusRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void keepAlive(ChannelHandlerContext ctx, KeepAlivePacket packet) {
|
||||||
|
ctx.writeAndFlush(packet);
|
||||||
|
ctx.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void statusRequest(ChannelHandlerContext ctx, StatusServerRequestPacket packet) {
|
||||||
|
ServerInfo serverInfo = new ServerInfo();
|
||||||
|
serverInfo.version().name(ProtocolConstant.PROTOCOL_NAME);
|
||||||
|
serverInfo.version().protocol(ProtocolConstant.PROTOCOL_NUMBER);
|
||||||
|
serverInfo.players().max(config.getInt("players.max-online"));
|
||||||
|
serverInfo.players().online(config.getInt("players.fake-online.value"));
|
||||||
|
serverInfo.players().sample(Collections.emptyList());
|
||||||
|
serverInfo.description(TextSerializer.fromPlain(config.getString("motd")));
|
||||||
|
|
||||||
|
StatusServerResponse response = new StatusServerResponse();
|
||||||
|
response.setInfo(ServerInfoSerializer.toStringPlain(serverInfo));
|
||||||
|
|
||||||
|
ctx.writeAndFlush(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
12
server/src/main/java/mc/server/util/LocationUtils.java
Normal file
12
server/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() {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user