Archived
0

109 Commits
1.0 ... develop

Author SHA1 Message Date
7046cd5f92 gradle upgrade 2021-07-18 14:48:10 +03:00
df6e6d7be2 PlayerBlockPlacementPacket 2021-07-18 14:37:39 +03:00
6c51d8ee30 HandAnimationPacket 2021-07-18 14:16:22 +03:00
31d795e0d9 PlayerDiggingAndMorePacket 2021-07-18 13:57:52 +03:00
376a5264e3 PlayerOnGroundPacket 2021-07-18 12:36:19 +03:00
a1d3cf5825 CPlayerAbilitiesPacket 2021-07-18 12:32:26 +03:00
bbdd2bc280 Merge branch 'dev/world' into develop 2021-07-18 12:23:01 +03:00
90f223160c изменен конфиг карты 2021-07-18 12:22:24 +03:00
ee82930426 генератор плоской карты 2021-07-18 12:15:10 +03:00
f280e9beaa стабилизируем код 2021-07-18 00:01:44 +03:00
b0bb86f215 Merge branch 'dev/pool' into dev/world
# Conflicts:
#	protocol/src/main/java/mc/protocol/packets/play/client/CPlayerPositionAndLookPacket.java
#	protocol/src/main/java/mc/protocol/packets/play/client/PlayerLookPacket.java
#	protocol/src/main/java/mc/protocol/packets/play/client/PlayerPositionPacket.java
#	protocol/src/main/java/mc/protocol/packets/play/server/ChunkDataPacket.java
#	protocol/src/test/java/mc/protocol/buffer/NetByteBufReadTest.java
#	protocol/src/test/java/mc/protocol/buffer/NetByteBufWriteTest.java
#	server/src/main/resources/logback-default.xml
2021-07-17 19:10:08 +03:00
910ed30360 Merge branch 'dev/utils' into dev/world 2021-07-17 17:37:07 +03:00
ee394bf183 add BlockLocation, ChunkSectionLocation 2021-07-12 19:10:57 +03:00
9c9523d629 used Vector 2021-07-12 19:10:40 +03:00
51f01f84cf add Vector 2021-07-12 19:10:21 +03:00
c1854c8f73 сериализация полного чанка 2021-07-10 16:24:16 +03:00
f149019b88 fix tests 2021-07-09 14:22:39 +03:00
ee8c9e4a3e рефакторинг object pool для NetByteBuf 2021-07-09 14:19:05 +03:00
84b4069f7b рефакторинг object pool 2021-07-09 13:40:31 +03:00
41acf32cb8 перенос pool фабрик в модуль utils 2021-07-09 12:26:15 +03:00
9ed6db2484 NibbleArray -> BitArray 2021-06-27 05:44:14 +03:00
fd074ae690 Merge branch 'dev/utils' into dev/world 2021-06-27 05:37:16 +03:00
a348689b37 add BitArray 2021-06-27 05:33:38 +03:00
c189472244 new module: utils 2021-06-27 02:50:02 +03:00
0a5eeb9e86 Processor* -> Scenario* 2021-06-24 20:46:08 +03:00
f9ca87cfc4 поправка к работе NibbleArray 2021-06-24 20:42:18 +03:00
b9af693a34 поправлена работа с pool object в сериализации чанков 2021-06-24 13:27:14 +03:00
eda9219ea0 disable logger 2021-06-24 13:24:55 +03:00
3424830d95 update tests 2021-06-24 13:18:23 +03:00
72b7b22e32 отказываемся от Palette в пользу Direct mode 2021-06-24 13:17:52 +03:00
06be69f3e4 первый удачный алгоритм загрузки мира 2021-06-21 00:22:55 +03:00
bb3f0bbdcb refactoring object pool 2021-06-19 16:46:45 +03:00
b1307442e1 gradle: add args for compile 2021-06-19 16:45:48 +03:00
4b587c55e9 реорганизация pool objects 2021-06-18 00:49:28 +03:00
bbf6fde3a1 Merge branch 'refactory' into develop 2021-06-17 15:10:29 +03:00
696d18cf41 refactoring: swap modules 2021-06-17 15:09:29 +03:00
e7f7b9654e refactoring 2021-06-17 15:06:10 +03:00
e7b5120661 refactoring 2021-06-15 23:56:53 +03:00
475f1a28ca refactoring 2021-06-13 17:06:46 +03:00
222f2dba61 refactoring: реорганизация загрузки конфигураций 2021-06-13 14:37:45 +03:00
e76f7ff375 BUG: field collision 2021-06-13 14:33:18 +03:00
95474a32c4 refactoring: Cli-Parser 2021-06-13 14:32:00 +03:00
59b374e623 refactoring: Config
YAML -> HOCON
2021-05-11 16:07:46 +03:00
2b9f021419 refactoring: move package 2021-05-10 20:10:24 +03:00
3e9649a8e0 refactoring: PacketInboundHandler 2021-05-10 18:55:36 +03:00
c63f5ce3eb refactoring: ProtocolConstant 2021-05-10 16:08:39 +03:00
dbb476bf11 refactoring: ProtocolDecoder 2021-05-10 16:08:39 +03:00
a5f68e76e5 refactoring: Pooled objects 2021-05-10 16:04:40 +03:00
38918f5eaf refactoring: ProtocolEncoder 2021-05-10 16:04:40 +03:00
f9a71250b1 refactoring: Packets 2021-05-10 16:04:39 +03:00
70d8efe421 refactoring: State 2021-05-10 02:27:12 +03:00
a3fcfcf65a refactoring: Packet interfaces 2021-05-10 02:11:04 +03:00
3165eca0ca refactoring: add mc.utils.pool 2021-05-10 02:06:49 +03:00
50fc39e924 refactoring: ProtocolSplitter 2021-05-09 23:28:36 +03:00
7f7fefdc98 refactoring: NetByteBuf 2021-05-09 22:53:07 +03:00
72b06bca7b начало рефакторинга 2021-05-09 22:49:04 +03:00
74fc258834 Merge branch 'master' into develop 2021-05-09 22:17:44 +03:00
325546a76d update README 2021-05-09 22:12:15 +03:00
4215b5615e update version 2021-05-09 22:07:25 +03:00
10af38e102 Merge branch 'develop' 2021-05-09 22:05:23 +03:00
b049352fe3 PlayerManager 2021-05-09 20:07:08 +03:00
c4767bd240 config: fake online 2021-05-09 18:54:23 +03:00
2d4895fef0 Merge branch 'dev/world' into develop 2021-05-09 18:48:54 +03:00
04316d9cbd грузим чанки при входе 2021-05-09 18:47:51 +03:00
20791ed881 VoidWorld, VoidChunk 2021-05-09 18:43:57 +03:00
2b0ad9895b add World, Chunk interfaces 2021-05-09 18:42:33 +03:00
ab17160f9d config: add view-distance 2021-05-09 18:41:31 +03:00
8a6f37924e PingPacket -> KeepAlivePacket 2021-05-09 17:00:25 +03:00
f10fb46d23 уменьшена скорость отдачи KeepAlive 2021-05-09 16:59:20 +03:00
c6669af651 EntityActionPacket 2021-05-08 20:14:14 +03:00
3984ab3fca порядок пакетов в State 2021-05-08 19:50:09 +03:00
bc2d5a7e75 правки режима и координат спавна 2021-05-08 18:13:24 +03:00
2521860bb4 fix DI 2021-05-06 14:55:14 +03:00
091b5adb91 Merge branch 'dev/network-api' into develop 2021-05-06 14:44:40 +03:00
4c20c7fd02 рефакторинг DI 2021-05-06 14:43:56 +03:00
0aaf17b17f добавлен пул для NettyConnectionContext 2021-05-06 14:21:24 +03:00
d02a80299f рефакторинг протокола 2021-05-06 13:58:05 +03:00
a3eb0eba86 рефакторинг EventBus 2021-05-06 13:50:40 +03:00
de43210747 рефакторинг протокола 2021-05-06 13:43:01 +03:00
39996f9847 Merge branch 'dev/event-bus' into dev/network-api
# Conflicts:
#	protocol/build.gradle
#	protocol/src/main/java/mc/protocol/NettyServer.java
#	protocol/src/main/java/mc/protocol/PacketInboundHandler.java
#	protocol/src/main/java/mc/protocol/State.java
#	protocol/src/main/java/mc/protocol/di/ProtocolModule.java
#	server/src/main/java/mc/server/Main.java
2021-05-06 13:42:01 +03:00
9b183b7d8d Merge branch 'dev/object-pool' into dev/network-api
# Conflicts:
#	protocol/src/main/java/mc/protocol/PacketInboundHandler.java
#	protocol/src/main/java/mc/protocol/di/ProtocolModule.java
#	protocol/src/main/java/mc/protocol/io/codec/ProtocolDecoder.java
2021-05-06 13:30:41 +03:00
5f431ff138 рефакторинг протокола 2021-05-06 13:14:42 +03:00
c4a6a01908 убираем reactor 2021-05-05 20:43:09 +03:00
205e813fc4 простая реализация EventBus 2021-05-05 20:09:49 +03:00
0f1c9bfb1b интерфейс EventBus 2021-05-05 20:09:32 +03:00
b77d6b16e8 повторное использование объектов Packet 2021-05-04 18:49:24 +03:00
87dc18f009 fix PLAY:KeepAlive 2021-05-03 17:10:26 +03:00
7d4c6e383e пересмотр событийной модели 2021-05-03 17:09:46 +03:00
de27654e67 check send packet 2021-05-03 17:07:51 +03:00
627cee9af3 fix 2021-05-03 17:07:22 +03:00
31059a4ad8 fix ChunkDataPacket 2021-05-03 16:06:53 +03:00
5833aab62a немного привёл код в порядок 2021-05-03 15:34:09 +03:00
052593bc14 PlayerLookPacket 2021-05-03 00:46:40 +03:00
531b0b97c1 PlayerPositionPacket 2021-05-03 00:46:40 +03:00
1a4600bdc9 fix NPE 2021-05-03 00:46:40 +03:00
b768ba5bd9 отправка пустого чанка 2021-05-03 00:46:40 +03:00
18a857193f debug: log send packet 2021-05-03 00:46:40 +03:00
a1a629279c debug: packet id as hex 2021-05-03 00:46:40 +03:00
824fdf9569 CPlayerPositionAndLookPacket 2021-05-03 00:46:39 +03:00
0835683294 TeleportConfirmPacket 2021-05-03 00:46:39 +03:00
2bd7fe9841 PlayerPositionAndLookPacket 2021-05-03 00:46:39 +03:00
23fd4e2c1a PlayerAbilitiesPacket 2021-05-03 00:46:39 +03:00
e5856b3d11 SpawnPositionPacket 2021-05-02 18:50:09 +03:00
17189effca PluginMessagePacket 2021-05-02 18:20:07 +03:00
bbeb41dd7e ClientSettingsPacket 2021-05-02 17:14:54 +03:00
3b3a80ca0a JoinGamePacket 2021-05-02 16:25:31 +03:00
a317e3e2c2 начало входа на сервер 2021-05-02 16:24:47 +03:00
19c1666c2e перенос обработчиков пакетов в отдельный класс 2021-05-02 15:05:27 +03:00
3282a3e9c0 next version 2021-05-01 16:57:27 +03:00
147 changed files with 4294 additions and 839 deletions

View File

@@ -1,12 +1,13 @@
# MC-SERVER
![version: 1.0-SNAPSHOT](https://img.shields.io/badge/version-1.0-05b.svg?style=flat)
![codename: ZERO](https://img.shields.io/badge/codename-ZERO-509.svg?style=flat)
![version: 1.1](https://img.shields.io/badge/version-1.1-05b.svg?style=flat)
![codename: VOID](https://img.shields.io/badge/codename-VOID-509.svg?style=flat)
![protocol: 1.12.2](https://img.shields.io/badge/protocol-1.12.2-075.svg?style=flat)
Написанный с нуля сервер **Minecraft 1.12.2**.
На данный момент может только показывать информацию о себе. Подключение к серверу не возможно.
На данный момент сервер может показывать о себе информацию в списке серверов (motd, онлайн, иконка) и позволять
игрокам подключиться к себе. Загружается пустой мир.
---

2
cli-parser/build.gradle Normal file
View File

@@ -0,0 +1,2 @@
//file:noinspection GrUnresolvedAccess
apply from: rootDir.toPath().resolve('logic.gradle').toFile()

View File

@@ -0,0 +1,2 @@
# suppress inspection "UnusedProperty" for whole file
module.name=cli-parser

View 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);
}
}

View 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);
}
}
}
}
}

View 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;
}

View File

@@ -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));
}
}

View File

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

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -13,13 +13,18 @@ ext {
lombok : 'org.projectlombok:lombok:1.18.12',
annotations: 'com.google.code.findbugs:jsr305:3.0.2',
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',
yaml : 'org.yaml:snakeyaml:1.28',
hocon : 'com.typesafe:config:1.4.1',
json : 'com.eclipsesource.minimal-json:minimal-json:0.9.5',
ioutils : 'commons-io:commons-io:2.6',
jopt : 'net.sf.jopt-simple:jopt-simple:6.0-alpha-3'
jopt : 'net.sf.jopt-simple:jopt-simple:6.0-alpha-3',
objpool : 'org.apache.commons:commons-pool2:2.9.0'
]
libs.netty = [
transport: "io.netty:netty-transport:${netty_version}",
codec : "io.netty:netty-codec:${netty_version}",
handler : "io.netty:netty-handler:${netty_version}"
]
libs.logger = [

View File

@@ -1,21 +1,28 @@
//file:noinspection GrUnresolvedAccess
apply plugin: 'java'
apply plugin: 'java-library'
apply plugin: 'jacoco'
apply from: rootDir.toPath().resolve('libs.gradle').toFile()
String getProperty1(String propertyName1, String propertyName2) {
return (String) (project.hasProperty(propertyName1) ? project.property(propertyName1) : project.property(propertyName2))
}
project.group = getProperty1('module.group', 'project.group')
project.version = getProperty1('module.version', 'project.version')
jar.archiveBaseName.set(getProperty1('module.name', 'project.name'))
project.group = getProperty1('module.group', 'prj.group')
project.version = getProperty1('module.version', 'prj.version')
jar.archiveBaseName.set(getProperty1('module.name', 'prj.name'))
compileJava {
sourceCompatibility = targetCompatibility = JavaVersion.VERSION_11
options.encoding = 'UTF-8'
}
gradle.projectsEvaluated {
tasks.withType(JavaCompile) {
options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
}
}
repositories {
mavenLocal()
mavenCentral()
@@ -31,13 +38,18 @@ dependencies {
implementation libs.dagger2.implementation
annotationProcessor libs.dagger2.annotationProcessor
testAnnotationProcessor libs.lombok
testCompileOnly libs.lombok
testImplementation libs.test.junit5.api
testImplementation libs.test.junit5.params
testRuntimeOnly libs.test.junit5.engine
testRuntimeOnly libs.test.logger
}
test {
useJUnitPlatform()
}
jacoco {
toolVersion = '0.8.7'
}

View File

@@ -1,8 +1,10 @@
apply from: rootDir.toPath().resolve('logic.gradle').toFile()
dependencies {
api libs.netty
api libs.reactor
api project(':utils')
implementation libs.netty.transport
implementation libs.netty.codec
implementation libs.json
testImplementation libs.lang3

View File

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

View File

@@ -1,43 +0,0 @@
package mc.protocol;
import io.netty.bootstrap.ServerBootstrap;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import mc.protocol.di.DaggerProtocolComponent;
import mc.protocol.di.ProtocolComponent;
import mc.protocol.packets.Packet;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Sinks;
import java.util.Map;
@SuppressWarnings("rawtypes")
@Slf4j
@RequiredArgsConstructor
public class NettyServer {
private final ServerBootstrap serverBootstrap;
private final Map<Class<? extends Packet>, Sinks.Many<ChannelContext>> observedMap;
public void bind(String host, int port) {
log.info("Network starting: {}:{}", host, port);
try {
serverBootstrap.bind(host, port).sync().channel().closeFuture().sync();
} catch (InterruptedException e) {
if (log.isTraceEnabled()) {
log.trace("{}: {}", e.getClass().getSimpleName(), e.getMessage(), e);
}
}
}
@SuppressWarnings("unchecked")
public <P extends Packet> Flux<ChannelContext<P>> packetFlux(Class<P> packetClass) {
return observedMap.get(packetClass).asFlux().map(ChannelContext.class::cast);
}
public static NettyServer createServer() {
ProtocolComponent component = DaggerProtocolComponent.create();
return component.getNettyServer();
}
}

View File

@@ -1,21 +0,0 @@
package mc.protocol;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import lombok.RequiredArgsConstructor;
import mc.protocol.packets.Packet;
import reactor.core.publisher.Sinks;
import java.util.Map;
@SuppressWarnings("rawtypes")
@RequiredArgsConstructor
public class PacketInboundHandler extends SimpleChannelInboundHandler<Packet> {
private final Map<Class<? extends Packet>, Sinks.Many<ChannelContext>> observedMap;
@Override
protected void channelRead0(ChannelHandlerContext ctx, Packet packet) {
observedMap.get(packet.getClass()).tryEmitNext(new ChannelContext<>(ctx, packet));
}
}

View File

@@ -4,7 +4,7 @@ import io.netty.util.AttributeKey;
import lombok.experimental.UtilityClass;
@UtilityClass
public class NetworkAttributes {
public class ProtocolAttributes {
public static final AttributeKey<State> STATE = AttributeKey.newInstance("STATE");
}

View File

@@ -5,6 +5,6 @@ import lombok.experimental.UtilityClass;
@UtilityClass
public class ProtocolConstant {
public static final String PROTOCOL_NAME = "1.12.2";
public static final int PROTOCOL_NUMBER = 340;
public final String PROTOCOL_NAME = "1.12.2";
public final int PROTOCOL_NUMBER = 340;
}

View File

@@ -3,14 +3,16 @@ package mc.protocol;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import mc.protocol.packets.ClientSidePacket;
import mc.protocol.packets.Packet;
import mc.protocol.packets.PingPacket;
import mc.protocol.packets.KeepAlivePacket;
import mc.protocol.packets.ServerSidePacket;
import mc.protocol.packets.client.HandshakePacket;
import mc.protocol.packets.client.LoginStartPacket;
import mc.protocol.packets.client.StatusServerRequestPacket;
import mc.protocol.packets.server.DisconnectPacket;
import mc.protocol.packets.server.StatusServerResponse;
import mc.protocol.packets.handshaking.client.HandshakePacket;
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 java.util.Collections;
@@ -19,60 +21,87 @@ import java.util.Map;
@RequiredArgsConstructor
public enum State {
HANDSHAKING(-1,
HANDSHAKING(0,
// client side
Map.of(0x00, HandshakePacket.class)
Map.of(0x00, HandshakePacket.class),
// server side
Collections.emptyMap()
),
STATUS(1,
// client side
Map.of(
0x00, StatusServerRequestPacket.class,
0x01, PingPacket.class
0x01, KeepAlivePacket.class
),
// server side
Map.of(
StatusServerResponse.class, 0x00,
PingPacket.class, 0x01
KeepAlivePacket.class, 0x01
)
),
LOGIN(2,
// server bound
// client side
Map.of(0x00, LoginStartPacket.class),
// client bound
Map.of(DisconnectPacket.class, 0x00)
// server side
Map.of(
DisconnectPacket.class, 0x00,
LoginSuccessPacket.class, 0x02
)
),
PLAY(3,
// client side
Map.ofEntries(
Map.entry(0x00, TeleportConfirmPacket.class),
Map.entry(0x04, ClientSettingsPacket.class),
Map.entry(0x09, PluginMessagePacket.class),
Map.entry(0x0B, KeepAlivePacket.class),
Map.entry(0x0C, PlayerOnGroundPacket.class),
Map.entry(0x0D, PlayerPositionPacket.class),
Map.entry(0x0E, CPlayerPositionAndLookPacket.class),
Map.entry(0x0F, PlayerLookPacket.class),
Map.entry(0x13, CPlayerAbilitiesPacket.class),
Map.entry(0x14, PlayerDiggingAndMorePacket.class),
Map.entry(0x15, EntityActionPacket.class),
Map.entry(0x1D, HandAnimationPacket.class),
Map.entry(0x1F, PlayerBlockPlacementPacket.class)
),
// server side
Map.of(
KeepAlivePacket.class, 0x1F,
ChunkDataPacket.class, 0x20,
JoinGamePacket.class, 0x23,
SPlayerAbilitiesPacket.class,0x2C,
SPlayerPositionAndLookPacket.class, 0x2F,
SpawnPositionPacket.class, 0x46
)
);
@Nullable
public static State getById(int id) {
for (State state : State.values()) {
if (state.id == id) {
return state;
}
}
return null;
// а зачем усложнять?
//@formatter:off
if (id == 1) return STATUS;
else if (id == 2) return LOGIN;
else if (id == 3) return PLAY;
else return HANDSHAKING;
//@formatter:on
}
@Getter
private final int id;
@Getter
private final Map<Integer, Class<? extends ClientSidePacket>> clientSidePackets;
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
public Class<? extends ClientSidePacket> getClientSidePacketById(int id) {
return clientSidePackets == null ? null : clientSidePackets.get(id);
}
@Nullable
public Integer getServerSidePacketId(Class<? extends Packet> clazz) {
public Integer getServerSidePacketId(Class<? extends ServerSidePacket> clazz) {
return serverSidePackets == null ? null : serverSidePackets.get(clazz);
}
}

View File

@@ -1,4 +1,4 @@
package mc.protocol.io;
package mc.protocol.buffer;
import io.netty.buffer.ByteBuf;
import lombok.EqualsAndHashCode;
@@ -6,6 +6,8 @@ import lombok.RequiredArgsConstructor;
import lombok.ToString;
import lombok.experimental.Delegate;
import lombok.extern.slf4j.Slf4j;
import mc.protocol.model.text.Text;
import mc.protocol.model.text.TextSerializer;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
@@ -32,22 +34,32 @@ import java.util.UUID;
* | | | | этого числа). |
* | VarInt | >= 1 ; <= 5 | Число от -2147483648 и 2147483647 | 32-bit число с плавающей размерностью от 1 до 5 байт |
* | VarLong | >= 1 ; <= 10 | Число от -9223372036854775808 и 9223372036854775807 | 64-bit число с плавающей размерностью от 1 до 10 байт |
* | Text | | JSON | По файту является String (n), который имеет формат JSON |
*
* [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>
* [3] - <a href="http://unicode.org/glossary/#unicode_scalar_value">Unicode Scalar Value</a>
* </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
@RequiredArgsConstructor
@EqualsAndHashCode(callSuper = false)
@ToString
public class NetByteBuf extends ByteBuf {
public abstract class NetByteBuf extends ByteBuf {
@Delegate
private final ByteBuf byteBuf;
protected ByteBuf byteBuf;
public void writeUnsignedByte(int value) {
byteBuf.writeByte((byte)(value & 0xFF));
}
public void writeText(Text text) {
writeString(TextSerializer.toStringPlain(text));
}
//region String
public String readString() {
@@ -61,9 +73,9 @@ public class NetByteBuf extends ByteBuf {
if (length == 0) {
return "";
} 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) {
throw new DecoderException("String length less zero!");
throw new NetIOException("String length less zero!");
}
byte[] bytes = new byte[length * 4];
@@ -108,22 +120,7 @@ public class NetByteBuf extends ByteBuf {
//region VarInt
public int readVarInt() {
int numRead = 0;
int result = 0;
byte read;
do {
if ((numRead + 1) > 5) {
log.warn("VarInt is too big");
break;
}
read = readByte();
int value = (read & 0b01111111);
result |= (value << (7 * numRead));
numRead++;
} while ((read & 0b10000000) != 0);
return result;
return readVarInt(this.byteBuf);
}
public void writeVarInt(int value) {
@@ -177,4 +174,23 @@ public class NetByteBuf extends ByteBuf {
writeLong(uuid.getLeastSignificantBits());
}
//endregion
public static int readVarInt(ByteBuf byteBuf) {
int numRead = 0;
int result = 0;
byte read;
do {
if ((numRead + 1) > 5) {
log.warn("VarInt is too big");
break;
}
read = byteBuf.readByte();
int value = (read & 0b01111111);
result |= (value << (7 * numRead));
numRead++;
} while ((read & 0b10000000) != 0);
return result;
}
}

View File

@@ -0,0 +1,8 @@
package mc.protocol.buffer;
public class NetIOException extends RuntimeException {
public NetIOException(String message) {
super(message);
}
}

View File

@@ -1,11 +0,0 @@
package mc.protocol.di;
import dagger.Component;
import mc.protocol.NettyServer;
@Component(modules = ProtocolModule.class)
@ServerScope
public interface ProtocolComponent {
NettyServer getNettyServer();
}

View File

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

View File

@@ -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 {
}

View File

@@ -0,0 +1,31 @@
package mc.protocol.handler;
import io.netty.channel.ChannelHandlerContext;
import mc.protocol.State;
import mc.protocol.packets.ClientSidePacket;
import mc.utils.Table;
public class ProtocolHandlersBus {
@SuppressWarnings("rawtypes")
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;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
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);
}
}

View File

@@ -0,0 +1,42 @@
package mc.protocol.handler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import mc.protocol.ProtocolAttributes;
import mc.protocol.State;
import mc.protocol.packets.ClientSidePacket;
import mc.protocol.pool.ProtocolObjectPool;
import java.io.IOException;
import java.util.Objects;
@Slf4j
@RequiredArgsConstructor
public class ProtocolInboundHandler extends SimpleChannelInboundHandler<ClientSidePacket> {
private static final String CLIENT_FORCE_DISCONNECTED_IOEXCEPTION_MESSAGE_RU = "Программа на вашем хост-компьютере разорвала установленное подключение";
private final ProtocolHandlersBus protocolHandlersBus;
@Override
protected void channelRead0(ChannelHandlerContext ctx, ClientSidePacket packet) {
State state = Objects.requireNonNull(ctx.channel().attr(ProtocolAttributes.STATE).get());
protocolHandlersBus.process(state, ctx, packet);
ProtocolObjectPool.packet().returnObject(packet);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
if (cause instanceof IOException && cause.getLocalizedMessage().equalsIgnoreCase(CLIENT_FORCE_DISCONNECTED_IOEXCEPTION_MESSAGE_RU)) {
log.warn("Client '{}' force disconnected", ctx.channel().remoteAddress());
if (log.isTraceEnabled()) {
log.trace("{}", cause.getMessage(), cause);
}
} else {
log.error("{}", cause.getMessage(), cause);
}
}
}

View File

@@ -0,0 +1,67 @@
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.ProtocolObjectPool;
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) {
State state = Objects.requireNonNull(ctx.channel().attr(ProtocolAttributes.STATE).get());
NetByteBuf netByteBuf = ProtocolObjectPool.netByteBuf().borrowObject(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 = ProtocolObjectPool.packet().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 = ProtocolObjectPool.packet().borrowObject(packetClass);
packet.readSelf(netByteBuf);
if (log.isDebugEnabled()) {
log.debug("IN: {}:{}", state, packet);
}
out.add(packet);
}
ProtocolObjectPool.netByteBuf().returnObject(netByteBuf);
}
private static String packetIdAsHexcode(int packetId) {
String hexPacketId = Integer.toHexString(packetId).toUpperCase();
if (hexPacketId.length() == 1) hexPacketId = "0" + hexPacketId;
return hexPacketId;
}
}

View File

@@ -0,0 +1,42 @@
package mc.protocol.handler.codec;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import lombok.extern.slf4j.Slf4j;
import mc.protocol.ProtocolAttributes;
import mc.protocol.State;
import mc.protocol.buffer.NetByteBuf;
import mc.protocol.packets.ServerSidePacket;
import mc.protocol.pool.ProtocolObjectPool;
import java.util.Objects;
@Slf4j
public class ProtocolEncoder extends MessageToByteEncoder<ServerSidePacket> {
@Override
protected void encode(ChannelHandlerContext ctx, ServerSidePacket packet, ByteBuf out) {
State state = Objects.requireNonNull(ctx.channel().attr(ProtocolAttributes.STATE).get());
Integer packetId = state.getServerSidePacketId(packet.getClass());
if (packetId == null) {
log.error("Unknown send packet: State {} ; Class {}", state, packet.getClass());
return;
}
if (log.isDebugEnabled()) {
log.debug("OUT: {}:{}", state, packet);
}
NetByteBuf buffer = ProtocolObjectPool.netByteBuf().borrowObject();
buffer.writeVarInt(packetId);
packet.writeSelf(buffer);
NetByteBuf netByteBuf = ProtocolObjectPool.netByteBuf().borrowObject(out);
netByteBuf.writeVarInt(buffer.readableBytes());
netByteBuf.writeBytes(buffer);
ProtocolObjectPool.netByteBuf().returnObject(netByteBuf);
ProtocolObjectPool.netByteBuf().returnObject(buffer);
}
}

View File

@@ -1,10 +1,11 @@
package mc.protocol.io.codec;
package mc.protocol.handler.codec;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import mc.protocol.io.NetByteBuf;
import mc.protocol.buffer.NetByteBuf;
import mc.protocol.pool.ProtocolObjectPool;
import java.util.List;
@@ -12,7 +13,7 @@ public class ProtocolSplitter extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
NetByteBuf netByteBuf = new NetByteBuf(in);
NetByteBuf netByteBuf = ProtocolObjectPool.netByteBuf().borrowObject(in);
netByteBuf.markReaderIndex();
do {
@@ -25,7 +26,7 @@ public class ProtocolSplitter extends ByteToMessageDecoder {
}
}
int sizePacket = new NetByteBuf(Unpooled.wrappedBuffer(sizePacketRaw)).readVarInt();
int sizePacket = NetByteBuf.readVarInt(Unpooled.wrappedBuffer(sizePacketRaw));
if (netByteBuf.readableBytes() >= sizePacket) {
byte[] bytes = new byte[sizePacket];
@@ -36,5 +37,7 @@ public class ProtocolSplitter extends ByteToMessageDecoder {
break;
}
} while (netByteBuf.readableBytes() > 0);
ProtocolObjectPool.netByteBuf().returnObject(netByteBuf);
}
}

View File

@@ -1,8 +0,0 @@
package mc.protocol.io;
public class DecoderException extends RuntimeException {
public DecoderException(String message) {
super(message);
}
}

View File

@@ -1,58 +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.extern.slf4j.Slf4j;
import mc.protocol.NetworkAttributes;
import mc.protocol.State;
import mc.protocol.io.NetByteBuf;
import mc.protocol.packets.ClientSidePacket;
import mc.protocol.packets.UnknownPacket;
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) throws Exception {
ctx.channel().attr(NetworkAttributes.STATE).set(State.HANDSHAKING);
super.channelActive(ctx);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
ctx.channel().attr(NetworkAttributes.STATE).set(null);
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("Unkown packet: State {} ; Id {}", state, packetId);
if (readUnknownPackets) {
UnknownPacket unknownPacket = new UnknownPacket(state, packetId, netByteBuf.readableBytes());
unknownPacket.readSelf(netByteBuf);
out.add(unknownPacket);
} else {
netByteBuf.skipBytes(netByteBuf.readableBytes());
}
} else {
ClientSidePacket packet = packetClass.getDeclaredConstructor().newInstance();
packet.readSelf(netByteBuf);
out.add(packet);
}
}
}

View File

@@ -1,29 +0,0 @@
package mc.protocol.io.codec;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import mc.protocol.NetworkAttributes;
import mc.protocol.State;
import mc.protocol.io.NetByteBuf;
import mc.protocol.packets.ServerSidePacket;
import java.util.Objects;
public class ProtocolEncoder extends MessageToByteEncoder<ServerSidePacket> {
@Override
protected void encode(ChannelHandlerContext ctx, ServerSidePacket packet, ByteBuf out) {
State state = ctx.channel().attr(NetworkAttributes.STATE).get();
int packetId = Objects.requireNonNull(state.getServerSidePacketId(packet.getClass()));
NetByteBuf buffer = new NetByteBuf(Unpooled.buffer());
buffer.writeVarInt(packetId);
packet.writeSelf(buffer);
NetByteBuf netByteBuf = new NetByteBuf(out);
netByteBuf.writeVarInt(buffer.readableBytes());
netByteBuf.writeBytes(buffer);
}
}

View File

@@ -0,0 +1,11 @@
package mc.protocol.model;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import mc.utils.vector.Vector3i;
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class BlockLocation extends Vector3i {
}

View File

@@ -0,0 +1,10 @@
package mc.protocol.model;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import mc.utils.vector.Vector3i;
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class ChunkSectionLocation extends Vector3i {
}

View File

@@ -0,0 +1,10 @@
package mc.protocol.model;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import mc.utils.vector.Vector3d;
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class Location extends Vector3d {
}

View File

@@ -0,0 +1,26 @@
package mc.protocol.model;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import mc.utils.vector.Vector2f;
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class Look extends Vector2f {
/**
* Equal X
* @return X
*/
public float getYaw() {
return this.getX();
}
/**
* Equal Y
* @return Y
*/
public float getPitch() {
return this.getY();
}
}

View File

@@ -1,10 +1,10 @@
package mc.protocol.serializer;
package mc.protocol.model;
import com.eclipsesource.json.Json;
import com.eclipsesource.json.JsonArray;
import com.eclipsesource.json.JsonObject;
import lombok.experimental.UtilityClass;
import mc.protocol.model.ServerInfo;
import mc.protocol.model.text.TextSerializer;
import java.util.Spliterator;
import java.util.Spliterators;
@@ -14,7 +14,11 @@ import java.util.stream.StreamSupport;
@UtilityClass
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()
.add("version", createVersionObj(info))
.add("players", createPlayersObj(info))

View File

@@ -1,12 +1,9 @@
package mc.protocol.serializer;
package mc.protocol.model.text;
import com.eclipsesource.json.Json;
import com.eclipsesource.json.JsonArray;
import com.eclipsesource.json.JsonObject;
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;
@@ -16,6 +13,10 @@ public class TextSerializer {
private static final Map<Character, TextStyle> legacyStyleCodes;
private static final Map<Character, TextColor> legacyColorCodes;
public String toStringPlain(Text text) {
return toJsonObject(text).toString();
}
public JsonObject toJsonObject(Text text) {
JsonObject jsonObject = Json.object();

View File

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

View File

@@ -1,16 +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
}
}

View File

@@ -1,20 +1,13 @@
package mc.protocol.packets;
import lombok.Data;
import mc.protocol.io.NetByteBuf;
import mc.protocol.buffer.NetByteBuf;
/**
* Пинг-пакет.
*
* <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>
* <pre>
* | FIELD | TYPE | NOTES |
@@ -22,12 +15,19 @@ import mc.protocol.io.NetByteBuf;
* | Payload | Long | Любое уникальное число |
* </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
public class PingPacket implements ClientSidePacket, ServerSidePacket {
public class KeepAlivePacket implements ClientSidePacket, ServerSidePacket {
private Long payload;
private long payload;
@Override
public void readSelf(NetByteBuf netByteBuf) {
@@ -38,4 +38,9 @@ public class PingPacket implements ClientSidePacket, ServerSidePacket {
public void writeSelf(NetByteBuf netByteBuf) {
netByteBuf.writeLong(payload);
}
@Override
public void passivate() {
this.payload = 0;
}
}

View File

@@ -1,9 +1,9 @@
package mc.protocol.packets;
/**
* Пакет.
* Сетевой пакет.
*
* <p>Структура пакета</p>
* <p>Структура</p>
* <pre>
* | FIELD | TYPE | NOTES |
* |-------------|--------|-------------------------------------------|

View File

@@ -1,6 +1,6 @@
package mc.protocol.packets;
import mc.protocol.io.NetByteBuf;
import mc.protocol.buffer.NetByteBuf;
/**
* Пакеты отправляемые сервером.

View File

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

View File

@@ -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 {
}

View File

@@ -1,11 +1,11 @@
package mc.protocol.packets.client;
package mc.protocol.packets.handshaking.client;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import mc.protocol.State;
import mc.protocol.io.NetByteBuf;
import mc.protocol.buffer.NetByteBuf;
import mc.protocol.packets.ClientSidePacket;
/**
@@ -18,14 +18,14 @@ import mc.protocol.packets.ClientSidePacket;
* | FIELD | TYPE | NOTES |
* |------------------|----------------|----------------------------------------------|
* | Protocol version | VarInt | Версия протокола [1] |
* | Server address | Stirng | Hostname или IP |
* | Server address | Stirng (255) | Hostname или IP |
* | 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>
*
* @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
*/
@NoArgsConstructor
@@ -41,10 +41,17 @@ public class HandshakePacket implements ClientSidePacket {
@Override
public void readSelf(NetByteBuf netByteBuf) {
protocolVersion = netByteBuf.readVarInt();
host = netByteBuf.readString(255);
port = netByteBuf.readUnsignedShort();
nextState = State.getById(netByteBuf.readVarInt());
this.protocolVersion = netByteBuf.readVarInt();
this.host = netByteBuf.readString(255);
this.port = netByteBuf.readUnsignedShort();
this.nextState = State.getById(netByteBuf.readVarInt());
}
@Override
public void passivate() {
this.protocolVersion = 0;
this.host = null;
this.port = 0;
this.nextState = null;
}
}

View File

@@ -1,10 +1,10 @@
package mc.protocol.packets.client;
package mc.protocol.packets.login.client;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import mc.protocol.io.NetByteBuf;
import mc.protocol.buffer.NetByteBuf;
import mc.protocol.packets.ClientSidePacket;
/**
@@ -19,7 +19,7 @@ import mc.protocol.packets.ClientSidePacket;
* | Name | String | Имя/Логин игрока |
* </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
@Getter
@@ -34,4 +34,9 @@ public class LoginStartPacket implements ClientSidePacket {
this.name = netByteBuf.readString();
}
@Override
public void passivate() {
this.name = null;
}
}

View File

@@ -1,11 +1,10 @@
package mc.protocol.packets.server;
package mc.protocol.packets.login.server;
import lombok.Data;
import mc.protocol.State;
import mc.protocol.io.NetByteBuf;
import mc.protocol.buffer.NetByteBuf;
import mc.protocol.model.text.Text;
import mc.protocol.packets.ServerSidePacket;
import mc.protocol.serializer.TextSerializer;
/**
* Diconnect packet.
@@ -16,7 +15,7 @@ import mc.protocol.serializer.TextSerializer;
* <pre>
* | FIELD | TYPE | NOTES |
* |-------------|--------|----------------------------------|
* | JSON Reason | String | Причина отключения. Опционально. |
* | JSON Reason | Text | Причина отключения. Опционально. |
* </pre>
*
* <p>Пример JSON Reason</p>
@@ -26,7 +25,7 @@ import mc.protocol.serializer.TextSerializer;
* }
* </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
*/
@Data
@@ -39,6 +38,6 @@ public class DisconnectPacket implements ServerSidePacket {
@Override
public void writeSelf(NetByteBuf netByteBuf) {
netByteBuf.writeString(TextSerializer.toJsonObject(reason).toString());
netByteBuf.writeText(reason);
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,77 @@
package mc.protocol.packets.play.client;
import lombok.*;
import mc.protocol.buffer.NetByteBuf;
import mc.protocol.packets.ClientSidePacket;
import javax.annotation.Nullable;
/**
* Entity Action packet.
*
* <p>Структура пакета</p>
* <pre>
* | FIELD | TYPE | NOTES |
* |------------|--------|-------------------------------------------|
* | Entity ID | VarInt | ID игрока |
* | Action ID | VarInt | ID действия |
* | Jump Boost | VarInt | Используется только при "Action ID" = 5. |
* | | | В этом случае значение будет от 0 до 100. |
* | | | В остальных случаях значение 0. |
* </pre>
*
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Entity_Action" target="_top">Entity Action</a>
*/
@NoArgsConstructor
@Getter
@EqualsAndHashCode
@ToString
public class EntityActionPacket implements ClientSidePacket {
private Integer entityId;
private Action action;
private Integer jumpBoost;
@Override
public void readSelf(NetByteBuf netByteBuf) {
this.entityId = netByteBuf.readVarInt();
int actionId = netByteBuf.readVarInt();
this.jumpBoost = netByteBuf.readVarInt();
this.action = Action.valueOfCode(actionId);
}
@Override
public void passivate() {
this.entityId = null;
this.action = 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;
}
}

View File

@@ -0,0 +1,42 @@
package mc.protocol.packets.play.client;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import mc.protocol.buffer.NetByteBuf;
import mc.protocol.packets.ClientSidePacket;
import mc.protocol.utils.Hand;
/**
* Отправляется, когда Игрок взмахивает рукой.
*
* <p>Структура пакета</p>
* <pre>
* | FIELD | TYPE | NOTES |
* |-------|---------|------------------------------|
* | Hand | VarInt | Используемая рука в анимации |
* | | | - 0: основная рука |
* | | | - 1: левая рука |
* </pre>
*
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Animation_.28serverbound.29">Animation (serverbound)</a>
*/
@NoArgsConstructor
@Getter
@EqualsAndHashCode
@ToString
public class HandAnimationPacket implements ClientSidePacket {
private Hand hand;
@Override
public void readSelf(NetByteBuf netByteBuf) {
this.hand = Hand.valueById(netByteBuf.readVarInt());
}
@Override
public void passivate() {
this.hand = null;
}
}

View File

@@ -0,0 +1,78 @@
package mc.protocol.packets.play.client;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import mc.protocol.buffer.NetByteBuf;
import mc.protocol.model.BlockLocation;
import mc.protocol.packets.ClientSidePacket;
import mc.protocol.utils.Face;
import mc.protocol.utils.Hand;
import mc.protocol.utils.SerializeUtil;
import mc.utils.vector.Vector3f;
/**
* Игрок размещает блок.
*
* <p>Структура пакета</p>
* <pre>
* | FIELD | TYPE | NOTES |
* |-------------------|----------|---------------------------------------------------------------------------|
* | Location | Position | Позиция блока |
* | Face | VarInt | Сторона блока, к которому ставится новый блок |
* | Hand | VarInt | Используемая рука |
* | | | - 0: основная рука |
* | | | - 1: левая рука |
* | Cursor Position X | Float | Положение перекрестия на блоке, от 0 до 1 с увеличением с West на East. |
* | Cursor Position Y | Float | Положение перекрестия на блоке, от 0 до 1 с увеличением с Bottom на Top. |
* | Cursor Position Z | Float | Положение перекрестия на блоке, от 0 до 1 с увеличением с North на South. |
* </pre>
*
* <p>Значения Стороны блока</p>
* <pre>
* | VALUE | OFFCET | FACE |
* |-------|--------|--------|
* | 0 | -Y | Bottom |
* | 1 | +Y | Top |
* | 2 | -Z | North |
* | 3 | +Z | South |
* | 4 | -X | West |
* | 5 | +X | East |
* </pre>
*
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Player_Block_Placement">Player Block Placement</a>
*/
@NoArgsConstructor
@Getter
@EqualsAndHashCode
@ToString
public class PlayerBlockPlacementPacket implements ClientSidePacket {
private BlockLocation location;
private Face face;
private Hand hand;
private Vector3f cursorPosition;
@Override
public void readSelf(NetByteBuf netByteBuf) {
this.location = SerializeUtil.long2location(netByteBuf.readLong());
this.face = Face.valueById(netByteBuf.readVarInt());
this.hand = Hand.valueById(netByteBuf.readVarInt());
this.cursorPosition = new Vector3f();
this.cursorPosition.set(
netByteBuf.readFloat(),
netByteBuf.readFloat(),
netByteBuf.readFloat()
);
}
@Override
public void passivate() {
this.location = null;
this.face = null;
this.hand = null;
this.cursorPosition = null;
}
}

View File

@@ -0,0 +1,82 @@
package mc.protocol.packets.play.client;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import mc.protocol.buffer.NetByteBuf;
import mc.protocol.model.BlockLocation;
import mc.protocol.packets.ClientSidePacket;
import mc.protocol.utils.DiggingStatus;
import mc.protocol.utils.Face;
import mc.protocol.utils.SerializeUtil;
/**
* Клиент копает, ломает блок (и не только).
*
* <p>Структура пакета</p>
* <pre>
* | FIELD | TYPE | NOTES |
* |----------|----------|------------------------------------------|
* | Status | VarInt | Действие Игрока к блоку |
* | Location | Position | Позиция блока |
* | Face | Byte | Сторона блока, с которой взаимодействуют |
* </pre>
*
* <p>Возможные действия Игрока к блоку</p>
* <pre>
* | VALUE | DESCRIPTION | NOTES |
* |--------- |-------------------------|---------------------------------------------------------------------------------------|
* | 0 | Начал ломать | |
* | 1 | Прекратил ломать | Отправляется, когда Игрок отпустил клавишу "копания" |
* | 2 | Закончил ломать | Отправляется, когда Игрок закончил ломать блок. Т.е. блок готов сломаться |
* | 3 | Бросает стек предметов | Отправляется, когда Игрок с помощью клавиши "Выкинуть предмет"(Q) |
* | | | с модификатором(??) для выкидывания полного стека. |
* | | | Поле Location всегда будет 0/0/0, а Face всегда -Y. |
* | 4 | Бросает предмет | Отправляется, когда Игрок выкидывает предмет. |
* | | | Поле Location всегда будет 0/0/0, а Face всегда -Y. |
* | 5 | Стреляет стрелой / | Указывает, что текущего состояние удерживаемого предмета должно |
* | | Заканчивает есть / | быть обновлено. Например, поедание еды, натягивание луков, использование ведер и т.д. |
* | | и т.д. | Поле Location всегда будет 0/0/0, а Face всегда -Y. |
* | 6 | Поменять предмет в руке | Отправляется когда Игрок меняет предмет предмет во второй руке через "свап". |
* | | | Поле Location всегда будет 0/0/0, а Face всегда -Y. |
* </pre>
*
* <p>Значения Стороны блока</p>
* <pre>
* | VALUE | OFFCET | FACE |
* |-------|--------|--------|
* | 0 | -Y | Bottom |
* | 1 | +Y | Top |
* | 2 | -Z | North |
* | 3 | +Z | South |
* | 4 | -X | West |
* | 5 | +X | East |
* </pre>
*
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Player_Digging">Player Digging</a>
*/
@NoArgsConstructor
@Getter
@EqualsAndHashCode
@ToString
public class PlayerDiggingAndMorePacket implements ClientSidePacket {
private DiggingStatus status;
private BlockLocation location;
private Face face;
@Override
public void readSelf(NetByteBuf netByteBuf) {
this.status = DiggingStatus.valueById(netByteBuf.readVarInt());
this.location = SerializeUtil.long2location(netByteBuf.readLong());
this.face = Face.valueById(netByteBuf.readByte());
}
@Override
public void passivate() {
this.status = null;
this.location = null;
this.face = null;
}
}

View File

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

View File

@@ -0,0 +1,39 @@
package mc.protocol.packets.play.client;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
import mc.protocol.buffer.NetByteBuf;
import mc.protocol.packets.ClientSidePacket;
/**
* Клиент сообщает: на земле ли он.
*
* <p>Структура пакета</p>
* <pre>
* | FIELD | TYPE | NOTES |
* |-----------|---------|---------------------------------------------|
* | On Ground | Boolean | true, если Игрок находится на земле |
* </pre>
*
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Player">Player</a>
*/
@NoArgsConstructor
@Getter
@EqualsAndHashCode
@ToString
public class PlayerOnGroundPacket implements ClientSidePacket {
private boolean onGround;
@Override
public void readSelf(NetByteBuf netByteBuf) {
this.onGround = netByteBuf.readBoolean();
}
@Override
public void passivate() {
onGround = false;
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,121 @@
package mc.protocol.packets.play.server;
import io.netty.buffer.Unpooled;
import lombok.Data;
import mc.protocol.buffer.NetByteBuf;
import mc.protocol.packets.ServerSidePacket;
import mc.protocol.pool.ProtocolObjectPool;
import mc.protocol.utils.SerializeUtil;
import mc.protocol.world.Chunk;
import mc.protocol.world.ChunkSection;
/**
* Данные чанка.
*
* <h2>Структура пакета</h2>
* <pre>
* | FIELD | TYPE | NOTES |
* |--------------------------|------------- |------------------------------------------------------------------------------------|
* | Chunk X | Integer | Координаты чанка (координата блока, делённая на 16, округленная в меньшую сторону) |
* | Chunk Z | Integer | Координаты чанка (координата блока, делённая на 16, округленная в меньшую сторону) |
* | Is Full chunk | Boolean | См. Chunk Format |
* | Available Sections | VarInt | Битовая маска, где каждый бит - это часть чанка (0-15) |
* | Size of Data | VarInt | Размер поля "Data" |
* | Data | Byte array | Данные чанка. См. Data Structure |
* | Number of block entities | VarInt | Количество элементов в поле "Block entities" |
* | Block entities | Array of NBT | Все сущности в чанке |
* </pre>
*
* <h2>Data Structure</h2>
* <pre>
* | FIELD | TYPE | NOTES |
* |--------|------------------------|------------------------------------------------------------|
* | Data | Array of Chunk Section | См. Chunk Section Structure |
* | Biomes | Byte array | Optional. Отправляются только если "Is Full chunk" == true |
* </pre>
*
* <h2>Chunk Section Structure</h2>
* <pre>
* | FIELD | TYPE | NOTES |
* |-------------------|---------------|---------------------------------------------------------------------|
* | Bits Per Block | Unsigned Byte | Определяет, сколько битов используется для кодирования блока |
* | Palette | Byte array | См. Palette Structure |
* | Data Array Length | VarInt | |
* | Data Array | Array of Long | |
* | Block Light | Byte array | Половина байна на блок |
* | Sky Light | Byte array | Optional. Только для LevelType == Overworld. Половина байна на блок |
* </pre>
*
* <h2>Palette Structure</h2>
* <p>Есть два типа: Indirect и Direct.</p>
* <p>
* Indirect используется, если "Bits Per Block" < 9. При этом, если "Bits Per Block" <= 4,
* то должно использоваться значение 4.
* </p>
* <p>Для Indirect формат следующий</p>
* <pre>
* | FIELD | TYPE | NOTES |
* |----------------|-----------------|--------------------------------|
* | Palette Length | VarInt | Количество элементов в массиве |
* | Palette | Array of VarInt | Идентификаторы блоков |
* </pre>
*
* <p>Direct используется, если "Bits Per Block" >= 9</p>
* <p>Для Direct формат следующий</p>
* <pre>
* | FIELD | TYPE | NOTES |
* |----------------------|--------|-------------|
* | Dummy Palette Length | VarInt | Всегда == 0 |
* </pre>
*
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Chunk_Data">Chunk Data</a>
* @see <a href="https://wiki.vg/index.php?title=Chunk_Format&oldid=14135">Chunk Format</a>
*/
@Data
public class ChunkDataPacket implements ServerSidePacket {
private static final int FULL_BIT_MASK = 0b11111111_11111111;
private Chunk chunk;
@Override
public void writeSelf(NetByteBuf netByteBuf) {
netByteBuf.writeInt(chunk.getX()); // Chunk X
netByteBuf.writeInt(chunk.getZ()); // Chunk Z
netByteBuf.writeBoolean(true); // Is Full chunk
netByteBuf.writeVarInt(FULL_BIT_MASK); // Available Sections
NetByteBuf data = createDataStructure();
netByteBuf.writeVarInt(data.readableBytes()); // Size of Data
netByteBuf.writeBytes(data); // Data
netByteBuf.writeVarInt(0); // Number of block entities
// Block entities (NBT's)
ProtocolObjectPool.netByteBuf().returnObject(data);
}
private NetByteBuf createDataStructure() {
NetByteBuf dataStructure = ProtocolObjectPool.netByteBuf().borrowObject(Unpooled.buffer());
for (int h = 0; h < 16; h++) {
ChunkSection section = chunk.getSection(h);
NetByteBuf data = SerializeUtil.serializeChunkSection(section);
dataStructure.writeBytes(data); // Data
ProtocolObjectPool.netByteBuf().returnObject(data);
}
// <Biomes>
for (int z = 0; z < 16; z++) {
for (int x = 0; x < 16; x++) {
dataStructure.writeByte(chunk.getBiome(
(chunk.getX() << 4) + x,
(chunk.getZ() << 4) + z));
}
}
// </Biomes>
return dataStructure;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,10 +1,8 @@
package mc.protocol.packets.server;
package mc.protocol.packets.status.server;
import lombok.Data;
import mc.protocol.io.NetByteBuf;
import mc.protocol.model.ServerInfo;
import mc.protocol.buffer.NetByteBuf;
import mc.protocol.packets.ServerSidePacket;
import mc.protocol.serializer.ServerInfoSerializer;
/**
* Status server packet, response.
@@ -52,10 +50,10 @@ public class StatusServerResponse implements ServerSidePacket {
/**
* Информация о серере.
*/
private ServerInfo info;
private String info;
@Override
public void writeSelf(NetByteBuf netByteBuf) {
netByteBuf.writeString(ServerInfoSerializer.toJsonObject(info).toString());
netByteBuf.writeString(info);
}
}

View File

@@ -0,0 +1,39 @@
package mc.protocol.pool;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.PooledByteBufAllocator;
import lombok.SneakyThrows;
import mc.protocol.buffer.NetByteBuf;
import mc.utils.pool.ObjectPool;
import mc.utils.pool.PassivablePooledObjectFactory;
import org.apache.commons.pool2.impl.GenericObjectPool;
@SuppressWarnings({ "rawtypes", "unchecked" })
public class NetByteBufObjectPool implements ObjectPool<NetByteBuf> {
private final org.apache.commons.pool2.ObjectPool apacheObjectPool;
NetByteBufObjectPool() {
this.apacheObjectPool = new GenericObjectPool<>(new PassivablePooledObjectFactory<>(PooledNetByteBuf.class));
}
@Override
public NetByteBuf borrowObject() {
return borrowObject(PooledByteBufAllocator.DEFAULT.directBuffer());
}
@SneakyThrows
public NetByteBuf borrowObject(ByteBuf byteBuf) {
PooledNetByteBuf pooledNetByteBuf = (PooledNetByteBuf) apacheObjectPool.borrowObject();
pooledNetByteBuf.init(byteBuf);
return pooledNetByteBuf;
}
@Override
@SneakyThrows
public void returnObject(NetByteBuf netByteBuf) {
if (netByteBuf instanceof PooledNetByteBuf) {
apacheObjectPool.returnObject(netByteBuf);
}
}
}

View File

@@ -0,0 +1,18 @@
package mc.protocol.pool;
import io.netty.buffer.ByteBuf;
import mc.protocol.buffer.NetByteBuf;
import mc.utils.pool.Passivable;
public class PooledNetByteBuf extends NetByteBuf implements Passivable {
void init(ByteBuf byteBuf) {
this.byteBuf = byteBuf.retain();
}
@Override
public void passivate() {
this.byteBuf.release();
this.byteBuf = null;
}
}

View File

@@ -0,0 +1,42 @@
package mc.protocol.pool;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import mc.protocol.model.ChunkSectionLocation;
import mc.protocol.model.Location;
import mc.protocol.model.Look;
import mc.protocol.packets.ClientSidePacket;
import mc.utils.pool.MultiObjectPool;
import mc.utils.pool.ObjectPool;
import mc.utils.pool.PassivableMultiObjectPool;
import mc.utils.pool.SimpleObjectPool;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class ProtocolObjectPool {
private static final ObjectPool<Location> LOCATION_POOL = new SimpleObjectPool<>(Location.class);
private static final ObjectPool<ChunkSectionLocation> CHUNK_SECTION_LOCATION_POOL = new SimpleObjectPool<>(ChunkSectionLocation.class);
private static final ObjectPool<Look> LOOK_POOL = new SimpleObjectPool<>(Look.class);
private static final NetByteBufObjectPool NETBYTEBUF_POOL = new NetByteBufObjectPool();
private static final MultiObjectPool<ClientSidePacket> PACKET_POOL = new PassivableMultiObjectPool<>();
public static ObjectPool<Location> location() {
return LOCATION_POOL;
}
public static ObjectPool<ChunkSectionLocation> chunkSectionLocation() {
return CHUNK_SECTION_LOCATION_POOL;
}
public static ObjectPool<Look> look() {
return LOOK_POOL;
}
public static NetByteBufObjectPool netByteBuf() {
return NETBYTEBUF_POOL;
}
public static MultiObjectPool<ClientSidePacket> packet() {
return PACKET_POOL;
}
}

View File

@@ -0,0 +1,11 @@
package mc.protocol.pool;
import io.netty.buffer.ByteBuf;
import mc.protocol.buffer.NetByteBuf;
public class UnpooledNetByteBuf extends NetByteBuf {
public UnpooledNetByteBuf(ByteBuf byteBuf) {
this.byteBuf = byteBuf;
}
}

View File

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

View File

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

View File

@@ -0,0 +1,32 @@
package mc.protocol.utils;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import javax.annotation.Nullable;
@RequiredArgsConstructor
public enum DiggingStatus {
STARTED(0),
CANCELLED(1),
FINISHED(2),
DROP_ITEM_STACK(3),
DROP_ITEM(4),
HELT_UPDATE_STATE(5),
SWAP_HAND(6);
@Nullable
public static DiggingStatus valueById(int id) {
for (DiggingStatus diggingStatus : DiggingStatus.values()) {
if (diggingStatus.getId() == id) {
return diggingStatus;
}
}
return null;
}
@Getter
private final int id;
}

View File

@@ -0,0 +1,31 @@
package mc.protocol.utils;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import javax.annotation.Nullable;
@RequiredArgsConstructor
public enum Face {
BOTTOM(0),
TOP(1),
NORTH(2),
SOUTH(3),
WEST(4),
EAST(5);
@Nullable
public static Face valueById(int id) {
for (Face face : Face.values()) {
if (face.getId() == id) {
return face;
}
}
return null;
}
@Getter
private final int id;
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,52 @@
package mc.protocol.utils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import mc.protocol.model.BlockLocation;
import mc.protocol.model.ChunkSectionLocation;
import mc.protocol.model.Location;
import mc.protocol.pool.ProtocolObjectPool;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class LocationUtils {
//region Unpooled
public static ChunkSectionLocation location2chunk(Location location) {
var chunkSectionLocation = new ChunkSectionLocation();
chunkSectionLocation.set(
(int) location.getX() >> 4,
(int) location.getY() >> 4,
(int) location.getZ() >> 4);
return chunkSectionLocation;
}
public static ChunkSectionLocation location2chunk(BlockLocation blockLocation) {
var chunkSectionLocation = new ChunkSectionLocation();
chunkSectionLocation.set(
blockLocation.getX() >> 4,
blockLocation.getY() >> 4,
blockLocation.getZ() >> 4);
return chunkSectionLocation;
}
//endregion
//region Pooled
public static ChunkSectionLocation location2chunkPooled(Location location) {
ChunkSectionLocation chunkSectionLocation = ProtocolObjectPool.chunkSectionLocation().borrowObject();
chunkSectionLocation.set(
(int) location.getX() >> 4,
(int) location.getY() >> 4,
(int) location.getZ() >> 4);
return chunkSectionLocation;
}
public static ChunkSectionLocation location2chunkPooled(BlockLocation blockLocation) {
ChunkSectionLocation chunkSectionLocation = ProtocolObjectPool.chunkSectionLocation().borrowObject();
chunkSectionLocation.set(
blockLocation.getX() >> 4,
blockLocation.getY() >> 4,
blockLocation.getZ() >> 4);
return chunkSectionLocation;
}
//endregion
}

View File

@@ -0,0 +1,77 @@
package mc.protocol.utils;
import io.netty.buffer.Unpooled;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import mc.protocol.buffer.NetByteBuf;
import mc.protocol.model.BlockLocation;
import mc.protocol.pool.ProtocolObjectPool;
import mc.protocol.world.Block;
import mc.protocol.world.ChunkSection;
import mc.utils.array.BitArray;
import mc.utils.array.BitByteArray;
import mc.utils.array.BitLongArray;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class SerializeUtil {
private static final int BITS_PER_BLOCK = 13;
private static final int ALL_BLOCKS = 16 * 16 * 16;
public static NetByteBuf serializeChunkSection(ChunkSection section) {
BitArray blockArray = new BitLongArray(BITS_PER_BLOCK, ALL_BLOCKS);
BitArray blockLight = new BitByteArray(4, ALL_BLOCKS);
BitArray skyLight = new BitByteArray(4, ALL_BLOCKS);
for (int y = 0; y < 16; y++) {
for (int z = 0; z < 16; z++) {
for (int x = 0; x < 16; x++) {
Block block = section.getBlock(x, y, z);
int blockState = blockIdMetaSerialize(block.getId(), block.getMeta());
blockArray.put(blockState);
blockLight.put(block.getLight());
skyLight.put(section.getSkyLight(x, y, z));
}
}
}
NetByteBuf result = ProtocolObjectPool.netByteBuf().borrowObject(Unpooled.buffer());
result.writeUnsignedByte(BITS_PER_BLOCK); // Bits Per Block
result.writeVarInt(0); // Palette, Direct mode
result.writeVarInt(blockArray.size()); // Data Array Length
result.writeBytes(blockArray.byteBuffer()); // Data Array
result.writeBytes(blockLight.byteBuffer()); // Block Light
result.writeBytes(skyLight.byteBuffer()); // Sky Light
return result;
}
public static int blockIdMetaSerialize(int id, int meta) {
return (id << 4) | meta;
}
public static int[] blockIdMetaDeserialize(int blockState) {
return new int[]{
blockState >> 4,
blockState & 0b1111
};
}
public static long location2long(BlockLocation blockLocation) {
return ((long) (blockLocation.getX() & 0x3FFFFFF) << 38)
| ((long) (blockLocation.getZ() & 0x3FFFFFF) << 12)
| (blockLocation.getY() & 0xFFF);
}
public static BlockLocation long2location(long value) {
BlockLocation blockLocation = new BlockLocation();
blockLocation.set(
(int) (value >> 38),
(int) (value & 0xFFF),
(int) (value << 26 >> 38)
);
return blockLocation;
}
}

View File

@@ -0,0 +1,14 @@
package mc.protocol.world;
import mc.protocol.model.BlockLocation;
public interface Block {
int getId();
int getMeta();
BlockLocation getLocation();
int getLight();
void setLight(int value);
}

View File

@@ -0,0 +1,10 @@
package mc.protocol.world;
public interface Chunk {
int getX();
int getZ();
ChunkSection getSection(int height);
byte getBiome(int x, int z);
}

View File

@@ -0,0 +1,14 @@
package mc.protocol.world;
import mc.protocol.model.BlockLocation;
public interface ChunkSection {
int getY();
Block getBlock(int x, int y, int z);
Block getBlock(BlockLocation blockLocation);
int getSkyLight(int x, int y, int z);
int getSkyLight(BlockLocation blockLocation);
}

View File

@@ -0,0 +1,15 @@
package mc.protocol.world;
import mc.protocol.model.ChunkSectionLocation;
import mc.protocol.model.Location;
import mc.protocol.utils.LevelType;
public interface World {
LevelType getLevelType();
Location getSpawn();
Chunk getChunk(int x, int z);
Chunk getChunk(ChunkSectionLocation chunkSectionLocation);
}

View File

@@ -1,6 +1,7 @@
package mc.protocol.io;
package mc.protocol.buffer;
import io.netty.buffer.Unpooled;
import mc.protocol.pool.UnpooledNetByteBuf;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
@@ -16,7 +17,6 @@ import java.util.UUID;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
class NetByteBufReadTest {
@@ -34,7 +34,7 @@ class NetByteBufReadTest {
void readBoolean(byte sourceByte, boolean expectedValue) {
baos.write(sourceByte);
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
NetByteBuf netByteBuf = new UnpooledNetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
assertEquals(expectedValue, netByteBuf.readBoolean());
}
@@ -45,7 +45,7 @@ class NetByteBufReadTest {
random.nextBytes(bytes);
baos.write(bytes[0]);
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
NetByteBuf netByteBuf = new UnpooledNetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
assertEquals(bytes[0], netByteBuf.readByte());
}
@@ -55,7 +55,7 @@ class NetByteBufReadTest {
void readUnsignedByte(byte sourceByte, int expectedValue) {
baos.write(sourceByte);
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
NetByteBuf netByteBuf = new UnpooledNetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
assertEquals(expectedValue, netByteBuf.readUnsignedByte());
}
@@ -65,7 +65,7 @@ class NetByteBufReadTest {
int value = Integer.valueOf(random.nextInt()).shortValue();
new DataOutputStream(baos).writeShort(value);
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
NetByteBuf netByteBuf = new UnpooledNetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
assertEquals(value, netByteBuf.readShort());
}
@@ -75,7 +75,7 @@ class NetByteBufReadTest {
int value = 32768;
new DataOutputStream(baos).writeShort(value);
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
NetByteBuf netByteBuf = new UnpooledNetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
assertEquals(value, netByteBuf.readUnsignedShort());
}
@@ -90,7 +90,7 @@ class NetByteBufReadTest {
baos.write(bytes);
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
NetByteBuf netByteBuf = new UnpooledNetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
assertEquals(string, netByteBuf.readString());
}
@@ -106,9 +106,9 @@ class NetByteBufReadTest {
baos.write(bytes);
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
NetByteBuf netByteBuf = new UnpooledNetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
assertThrows(DecoderException.class, () -> netByteBuf.readString(length));
assertThrows(NetIOException.class, () -> netByteBuf.readString(length));
}
@Test
@@ -125,9 +125,9 @@ class NetByteBufReadTest {
baos.write(bytes);
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
NetByteBuf netByteBuf = new UnpooledNetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
assertThrows(DecoderException.class, () -> netByteBuf.readString(-1));
assertThrows(NetIOException.class, () -> netByteBuf.readString(-1));
}
@ParameterizedTest
@@ -135,7 +135,7 @@ class NetByteBufReadTest {
void readVarInt(byte[] sourceBytes, int expectedValue) throws IOException {
baos.write(sourceBytes);
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
NetByteBuf netByteBuf = new UnpooledNetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
assertEquals(expectedValue, netByteBuf.readVarInt());
}
@@ -144,7 +144,7 @@ class NetByteBufReadTest {
void readVarInt_tooBig() throws IOException {
baos.write(new byte[]{ (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0x0F });
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
NetByteBuf netByteBuf = new UnpooledNetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
assertEquals(-1, netByteBuf.readVarInt());
}
@@ -154,7 +154,7 @@ class NetByteBufReadTest {
void readVarLong(byte[] sourceBytes, long expectedValue) throws IOException {
baos.write(sourceBytes);
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
NetByteBuf netByteBuf = new UnpooledNetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
assertEquals(expectedValue, netByteBuf.readVarLong());
}
@@ -165,7 +165,7 @@ class NetByteBufReadTest {
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
(byte) 0xFF, (byte) 0x0F });
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
NetByteBuf netByteBuf = new UnpooledNetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
assertEquals(-1, netByteBuf.readVarLong());
}
@@ -197,7 +197,7 @@ class NetByteBufReadTest {
(byte) (leastSignificantBits & 0xFF)
});
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
NetByteBuf netByteBuf = new UnpooledNetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
assertEquals(uuid, netByteBuf.readUUID());
}
@@ -209,7 +209,7 @@ class NetByteBufReadTest {
baos.write(bytes);
byte[] actualBytes = new byte[128];
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
NetByteBuf netByteBuf = new UnpooledNetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
assertEquals(bytes.length, netByteBuf.readableBytes());
@@ -226,7 +226,7 @@ class NetByteBufReadTest {
baos.write(bytes);
byte[] buff = new byte[128];
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
NetByteBuf netByteBuf = new UnpooledNetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
netByteBuf.readBytes(buff, 3, 11);
byte[] expectedBytes = new byte[11];

View File

@@ -1,7 +1,8 @@
package mc.protocol.io;
package mc.protocol.buffer;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import mc.protocol.pool.UnpooledNetByteBuf;
import org.apache.commons.lang3.RandomStringUtils;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
@@ -30,7 +31,7 @@ class NetByteBufWriteTest {
@MethodSource("paramsWriteBoolean")
void writeBoolean(boolean sourceValue, byte expectedByte) {
ByteBuf byteBuf = Unpooled.buffer();
NetByteBuf netByteBuf = new NetByteBuf(byteBuf);
NetByteBuf netByteBuf = new UnpooledNetByteBuf(byteBuf);
netByteBuf.writeBoolean(sourceValue);
@@ -41,18 +42,28 @@ class NetByteBufWriteTest {
@MethodSource("paramsWriteByte")
void writeByte(byte sourceValue, byte expectedByte) {
ByteBuf byteBuf = Unpooled.buffer();
NetByteBuf netByteBuf = new NetByteBuf(byteBuf);
NetByteBuf netByteBuf = new UnpooledNetByteBuf(byteBuf);
netByteBuf.writeByte(sourceValue);
assertEquals(expectedByte, byteBuf.array()[0]);
}
@Test
void writeUnsignedByte() {
ByteBuf byteBuf = Unpooled.buffer();
NetByteBuf netByteBuf = new UnpooledNetByteBuf(byteBuf);
netByteBuf.writeUnsignedByte(129);
assertEquals(129, netByteBuf.readUnsignedByte());
}
@ParameterizedTest
@MethodSource("paramsWriteString")
void writeString(String string, int exceptedLength) {
ByteBuf byteBuf = Unpooled.buffer();
NetByteBuf netByteBuf = new NetByteBuf(byteBuf);
NetByteBuf netByteBuf = new UnpooledNetByteBuf(byteBuf);
netByteBuf.writeString(string);
@@ -71,11 +82,11 @@ class NetByteBufWriteTest {
String overSizeString = RandomStringUtils.randomAscii(Short.MAX_VALUE + Short.MAX_VALUE);
ByteBuf byteBuf = Unpooled.buffer();
NetByteBuf netByteBuf = new NetByteBuf(byteBuf);
NetByteBuf netByteBuf = new UnpooledNetByteBuf(byteBuf);
netByteBuf.writeString(overSizeString);
NetByteBuf netByteBuf2 = new NetByteBuf(byteBuf.copy());
NetByteBuf netByteBuf2 = new UnpooledNetByteBuf(byteBuf.copy());
String actualString = netByteBuf2.readString();
String expectedString = overSizeString.substring(0, Short.MAX_VALUE);
@@ -87,7 +98,7 @@ class NetByteBufWriteTest {
@MethodSource("paramsWriteVarInt")
void writeVarInt(int sourceValue, byte[] expectedBytes) {
ByteBuf byteBuf = Unpooled.buffer();
NetByteBuf netByteBuf = new NetByteBuf(byteBuf);
NetByteBuf netByteBuf = new UnpooledNetByteBuf(byteBuf);
netByteBuf.writeVarInt(sourceValue);
byte[] actualArray = netByteBuf.copy(0, netByteBuf.readableBytes()).array();
@@ -99,7 +110,7 @@ class NetByteBufWriteTest {
@MethodSource({ "paramsWriteVarInt", "paramsWriteVarLong" })
void writeVarLong(long sourceValue, byte[] expectedBytes) {
ByteBuf byteBuf = Unpooled.buffer();
NetByteBuf netByteBuf = new NetByteBuf(byteBuf);
NetByteBuf netByteBuf = new UnpooledNetByteBuf(byteBuf);
netByteBuf.writeVarLong(sourceValue);
byte[] actualArray = netByteBuf.copy(0, netByteBuf.readableBytes()).array();
@@ -112,7 +123,7 @@ class NetByteBufWriteTest {
final UUID uuid = UUID.randomUUID();
ByteBuf byteBuf = Unpooled.buffer();
NetByteBuf netByteBuf = new NetByteBuf(byteBuf);
NetByteBuf netByteBuf = new UnpooledNetByteBuf(byteBuf);
netByteBuf.writeUUID(uuid);
@@ -148,7 +159,7 @@ class NetByteBufWriteTest {
random.nextBytes(bytes);
ByteBuf byteBuf = Unpooled.buffer();
NetByteBuf netByteBuf = new NetByteBuf(byteBuf);
NetByteBuf netByteBuf = new UnpooledNetByteBuf(byteBuf);
netByteBuf.writeBytes(bytes);
byte[] actualArray = netByteBuf.copy(0, netByteBuf.readableBytes()).array();
@@ -162,7 +173,7 @@ class NetByteBufWriteTest {
random.nextBytes(bytes);
ByteBuf byteBuf = Unpooled.buffer();
NetByteBuf netByteBuf = new NetByteBuf(byteBuf);
NetByteBuf netByteBuf = new UnpooledNetByteBuf(byteBuf);
netByteBuf.writeBytes(bytes, 3, 11);

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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())
);
}
}

View File

@@ -15,7 +15,7 @@ apply from: rootDir.toPath().resolve('logic.gradle').toFile()
apply plugin: 'application'
application {
mainClassName = 'mc.server.Main'
mainClass.set('mc.server.Main')
if (project.hasProperty('jvmArgs')) {
applicationDefaultJvmArgs = List.of((project.jvmArgs as String).split('\\s+'))
@@ -23,13 +23,14 @@ application {
}
dependencies {
implementation project(':cli-parser')
implementation project(':protocol')
implementation libs.logger.logback
implementation libs.hocon
implementation libs.yaml
implementation libs.ioutils
implementation libs.jopt
implementation libs.netty.transport
implementation libs.netty.handler
}
shadowJar {

View File

@@ -3,198 +3,110 @@ package mc.server;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;
import joptsimple.OptionParser;
import joptsimple.OptionSet;
import joptsimple.util.PathConverter;
import lombok.extern.slf4j.Slf4j;
import mc.protocol.NettyServer;
import mc.protocol.ProtocolConstant;
import mc.protocol.model.ServerInfo;
import mc.protocol.packets.PingPacket;
import mc.protocol.packets.client.HandshakePacket;
import mc.protocol.packets.client.LoginStartPacket;
import mc.protocol.packets.client.StatusServerRequestPacket;
import mc.protocol.packets.server.DisconnectPacket;
import mc.protocol.packets.server.StatusServerResponse;
import mc.protocol.serializer.TextSerializer;
import mc.server.config.Config;
import mc.server.di.ConfigModule;
import mc.server.di.DaggerServerComponent;
import mc.server.di.ServerComponent;
import org.apache.commons.io.IOUtils;
import com.typesafe.config.Config;
import lombok.SneakyThrows;
import mc.cliparser.CommandLine;
import mc.cliparser.CommandLineParser;
import mc.cliparser.Option;
import mc.server.di.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
@Slf4j
@SuppressWarnings("java:S106")
public class Main {
private static final String CLI_CONFIG = "config";
private static final String CLI_LOGCONFIG = "logconfig";
private void run(OptionSet optionSet) {
log.info("mc-project launch");
ConfigModule configModule = new ConfigModule((Path) optionSet.valueOf(CLI_CONFIG));
ServerComponent serverComponent = DaggerServerComponent.builder()
.configModule(configModule)
.build();
Config config = serverComponent.getConfig();
NettyServer server = NettyServer.createServer();
server.packetFlux(HandshakePacket.class)
.doOnNext(channel -> log.info("{}", channel.getPacket()))
.subscribe(channel -> channel.setState(channel.getPacket().getNextState()));
server.packetFlux(PingPacket.class)
.doOnNext(channel -> log.info("{}", channel.getPacket()))
.subscribe(channel -> channel.getCtx().writeAndFlush(channel.getPacket()).channel().disconnect());
server.packetFlux(StatusServerRequestPacket.class)
.doOnNext(channel -> log.info("{}", channel.getPacket()))
.subscribe(channel -> {
ServerInfo serverInfo = new ServerInfo();
serverInfo.version().name(ProtocolConstant.PROTOCOL_NAME);
serverInfo.version().protocol(ProtocolConstant.PROTOCOL_NUMBER);
serverInfo.players().max(config.players().maxOnlile());
serverInfo.players().online(config.players().onlile());
serverInfo.players().sample(Collections.emptyList());
serverInfo.description(TextSerializer.fromPlain(config.motd()));
if (config.iconPath() != null) {
serverInfo.favicon(faviconToBase64(config.iconPath()));
}
StatusServerResponse response = new StatusServerResponse();
response.setInfo(serverInfo);
channel.getCtx().writeAndFlush(response);
});
server.packetFlux(LoginStartPacket.class)
.doOnNext(channel -> log.info("{}", channel.getPacket()))
.subscribe(channel -> {
DisconnectPacket disconnectPacket = new DisconnectPacket();
disconnectPacket.setReason(TextSerializer.fromPlain(config.disconnectReason()));
channel.getCtx().writeAndFlush(disconnectPacket).channel().disconnect();
});
server.bind(config.server().host(), config.server().port());
}
public static void main(String[] args) throws IOException {
OptionParser optionParser = createOptionParser();
OptionSet optionSet = optionParser.parse(args);
//region Setup CommandLine
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")) {
try {
optionParser.printHelpOn(System.out);
} catch (IOException e) {
System.err.printf("Can't print help page: %s%n", e.getMessage());
e.printStackTrace(System.err);
}
return;
} else if (optionSet.has("init")) {
Path configPath = (Path) optionSet.valueOf(CLI_CONFIG);
Path logbackPath = (Path) optionSet.valueOf(CLI_LOGCONFIG);
CommandLine commandLine = cliParser.parse(args);
//endregion
if (!initializeCheckFiles(configPath, logbackPath)) {
return;
}
InputStream configResource = Objects.requireNonNull(Main.class.getResourceAsStream("/config-sample.yml"));
InputStream logbackResource = Objects.requireNonNull(Main.class.getResourceAsStream("/logback-sample.xml"));
try(OutputStream configOut = Files.newOutputStream(configPath);
OutputStream logbackOut = Files.newOutputStream(logbackPath)) {
IOUtils.copy(configResource, configOut);
IOUtils.copy(logbackResource, logbackOut);
}
System.out.println("Initialization environment done.");
return;
//region Configuration Logback
Path logconfigPath;
if (commandLine.has(logconfigOption)) {
logconfigPath = Paths.get(logconfigOption.value());
} else {
logconfigPath = defaultLogbackConfigPath();
}
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()) {
optionSet.asMap().forEach((optionSpec, objects) -> {
if (optionSpec.isForHelp()) return;
log.debug("OptionSet | {} = {}", optionSpec.options(), objects);
});
Config config = configComponent.getConfig();
log.debug("Logback config path: {}", logconfigOption.value() == null ? "(default)" : logconfigOption.value());
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()
.scenarioModule(new ScenarioModule(configComponent.getConfig()))
.worldModule(new FlatWorldModule(configComponent.getConfig()))
.build();
serverComponent.getPacketScenarios().forEach(scenario -> scenario.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 String faviconToBase64(Path iconPath) {
try {
return "data:image/png;base64," +
Base64.getEncoder().encodeToString(
IOUtils.toByteArray(Files.newInputStream(iconPath)));
} catch (IOException e) {
log.error("Can't read icon '{}'", iconPath.toAbsolutePath(), e);
return "";
}
}
private static boolean initializeCheckFiles(Path... paths) {
for (Path path : paths) {
if (Files.exists(path)) {
System.err.printf("File '%s' already exist. Initialization environment canceled.%n",
path.toAbsolutePath());
return false;
}
}
return true;
}
private static void reconfigureLogback(OptionSet optionSet) throws IOException {
LoggerContext logbackContext = (LoggerContext) LoggerFactory.getILoggerFactory();
logbackContext.reset();
JoranConfigurator configurator = new JoranConfigurator();
Path logbackPath = (Path) optionSet.valueOf(CLI_LOGCONFIG);
try(InputStream in = Objects.requireNonNull(
Files.newInputStream(logbackPath), "File not found: " + logbackPath.toAbsolutePath())) {
try(InputStream in = Files.newInputStream(configPath)) {
configurator.setContext(logbackContext);
configurator.doConfigure(in);
} catch (JoranException 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());
}
}

View 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;
}
}

View File

@@ -0,0 +1,20 @@
package mc.server;
import io.netty.channel.ChannelHandlerContext;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import mc.protocol.model.Location;
import mc.protocol.utils.GameMode;
import java.util.UUID;
@RequiredArgsConstructor
@Getter
public class Player {
private final ChannelHandlerContext ctx;
private final UUID uuid;
private final String name;
private final GameMode gameMode;
private final Location location;
}

View File

@@ -0,0 +1,27 @@
package mc.server;
import io.netty.channel.ChannelHandlerContext;
import mc.protocol.model.Location;
import mc.protocol.utils.GameMode;
import java.util.LinkedList;
import java.util.UUID;
public class PlayerManager {
private final LinkedList<Player> players = new LinkedList<>();
public Player create(ChannelHandlerContext ctx, String name, GameMode gameMode, Location location) {
Player player = new Player(ctx, UUID.randomUUID(), name, gameMode, location);
players.add(player);
return player;
}
public void remove(Player player) {
players.remove(player);
}
public int online() {
return players.size();
}
}

View 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");
}

View File

@@ -1,38 +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 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 int maxOnlile;
private int onlile;
}
}

View 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();
}

View File

@@ -1,67 +1,36 @@
package mc.server.di;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import dagger.Module;
import dagger.Provides;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import mc.server.config.Config;
import org.yaml.snakeyaml.Yaml;
import java.io.IOException;
import java.nio.file.Files;
import javax.inject.Singleton;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
@Slf4j
@Module
@RequiredArgsConstructor
@Slf4j
public class ConfigModule {
private final String defaultResource;
private final Path configPath;
@Provides
@Singleton
Config provideConfig() {
Config config = new Config();
Map<String, Object> map = new Yaml().load(readConfigAsString());
Config defaultConfig = ConfigFactory.parseResources(defaultResource);
Config config;
config.server().host(fromYamlPath("server/host", map, "127.0.0.1"));
config.server().port(fromYamlPath("server/port", map, 25565));
config.motd(fromYamlPath("motd", map, ""));
config.disconnectReason(fromYamlPath("disconnect-reason", map, ""));
config.players().maxOnlile(fromYamlPath("players/max-online", map, 0));
config.players().onlile(fromYamlPath("players/online", map, 0));
if (Boolean.TRUE.equals(fromYamlPath("icon/enable", map, false))) {
config.iconPath(Paths.get(fromYamlPath("icon/path", map, "favicon.png")));
if (configPath != null) {
Config userConfig = ConfigFactory.parseFile(configPath.toFile());
config = userConfig.withFallback(defaultConfig).resolve();
} else {
config = defaultConfig.resolve();
}
map.clear();
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;
}
}
}

View File

@@ -0,0 +1,61 @@
package mc.server.di;
import com.typesafe.config.Config;
import mc.protocol.model.Location;
import mc.protocol.utils.SerializeUtil;
import mc.protocol.world.World;
import mc.server.world.FlatWorld;
import mc.utils.array.BitArray;
import mc.utils.array.BitLongArray;
public class FlatWorldModule extends WorldModule {
public FlatWorldModule(Config config) {
super(config);
}
@Override
World provideWorld() {
String[] rawConfigParts = this.config.getString("world.flat-generator").split(";");
byte biome = Byte.parseByte(rawConfigParts[0]);
return new FlatWorld(spawn(), flatConfig(rawConfigParts), biome);
}
private Location spawn() {
Location spawn = new Location();
spawn.set(
config.getDouble("world.spawn.x"),
config.getDouble("world.spawn.y"),
config.getDouble("world.spawn.z")
);
return spawn;
}
private BitArray flatConfig(String[] rawConfigParts) {
BitArray flatConfig = new BitLongArray(13, 256);
int k = 0;
for (int i = 1; i < rawConfigParts.length; i++) {
String[] part1 = rawConfigParts[i].split(",");
String[] part2 = part1[1].split(":");
int count = Integer.parseInt(part1[0]);
int blockId = Integer.parseInt(part2[0]);
int blockMeta = Integer.parseInt(part2[1]);
k += count;
for (int j = 0; j < count; j++) {
flatConfig.put(SerializeUtil.blockIdMetaSerialize(blockId, blockMeta));
}
}
if (k < 256) {
for (int i = 0; i < (256 - k); i++) {
flatConfig.put(0);
}
}
return flatConfig;
}
}

View File

@@ -0,0 +1,17 @@
package mc.server.di;
import dagger.Module;
import dagger.Provides;
import mc.server.PlayerManager;
import javax.inject.Singleton;
@Module
public class PlayerManagerModule {
@Provides
@Singleton
PlayerManager providePlayerManager() {
return new PlayerManager();
}
}

Some files were not shown because too many files have changed in this diff Show More