0

13 Commits

Author SHA1 Message Date
f2ab5521d1 избавляемся от рефлексии: CommnadManager: register 2021-01-08 20:04:58 +03:00
d686f56b0c избавляемся от рефлексии: скины для голов 2021-01-08 18:53:47 +03:00
070485e951 Merge branch 'develop' into dev/modules
# Conflicts:
#	build.gradle
2021-01-08 18:18:06 +03:00
d0f1ea7b90 gradle: вынесена общая логика gradle-modules 2021-01-07 09:24:54 +03:00
d74265db60 gradle: авто-версионирование с параметром 2021-01-07 08:23:11 +03:00
97bf4b93f1 gradle: добавлена автоматизация версионирования 2021-01-07 08:09:27 +03:00
368c495886 gradle: разделение на gradle-module's 2021-01-07 04:47:46 +03:00
9a14817ff8 gradle: перенесены репозитарии в плагин 2021-01-07 03:41:38 +03:00
132dd2f4ca gradle: убран лишний плагин 2021-01-07 02:25:29 +03:00
1a23102f2e gradle: libs.gradle преобразован в gradle-plugin 2021-01-07 01:42:10 +03:00
f09f0e7a89 gradle: gradle-plugin вынесен в buildSrc 2021-01-07 01:23:00 +03:00
05f0245323 gradle: перенесены методы в gradle-plugin 2021-01-07 00:57:53 +03:00
c98c5d2c34 gradle: перенесены зависимости в отдельный файл 2021-01-06 23:29:32 +03:00
45 changed files with 931 additions and 126 deletions

View File

@@ -1,6 +1,6 @@
# GHAST TOOLS
![version: 1.9](https://img.shields.io/badge/version-1.9-0a0.svg?style=flat)
![version: 1.11-SNAPSHOT](https://img.shields.io/badge/version-1.11--SNAPSHOT-0a0.svg?style=flat)
![bukkit-api: 1.12](https://img.shields.io/badge/bukkit--api-1.12-d50.svg?style=flat)
Набор вспомогательных инструментов для Bukkit API.
@@ -654,7 +654,7 @@ repositories {
```
```groovy
implementation group: 'ghast', name: 'ghast-tools', version: '1.9'
implementation group: 'ghast', name: 'ghast-tools', version: '1.11-SNAPSHOT'
```
### Maven
@@ -673,7 +673,7 @@ implementation group: 'ghast', name: 'ghast-tools', version: '1.9'
<dependency>
<groupId>ghast</groupId>
<artifactId>ghast-tools</artifactId>
<version>1.9</version>
<version>1.11-SNAPSHOT</version>
</dependency>
</dependencies>
```

View File

@@ -1,79 +1,33 @@
plugins {
id 'java'
}
import libs.LibsPlugin
def publishScript = file(rootProject.getProjectDir().getPath() + '/publish.gradle')
subprojects {
apply plugin: 'java'
apply plugin: LibsPlugin
apply plugin: LogicPlugin
def publishScript = rootDir.toPath().resolve('publish.gradle').toFile()
if (publishScript.exists()) {
apply from: publishScript.path
apply from: publishScript
}
project.group = projectGroup
project.version = projectVersion
project.version = moduleVersion
jar.archiveBaseName.set(moduleName)
repositories {
mavenLocal()
mavenCentral()
maven { url 'https://hub.spigotmc.org/nexus/content/groups/public' }
maven { url 'https://dmx-mc-project.gitlab.io/maven-repository/' }
}
ext {
junitVersion = '5.5.2'
libs = [
bukkit: [lib: 'org.bukkit:bukkit:1.12.2-R0.1-SNAPSHOT', exclude: [
'com.google.code.gson:gson',
'com.googlecode.json-simple:json-simple',
'commons-lang:commons-lang',
'org.yaml:snakeyaml'
]],
commons_text: 'org.apache.commons:commons-text:1.9',
lombok: 'org.projectlombok:lombok:1.18.12',
reflection_object: 'ru.dmitriymx:reflection-object:1.2',
test: [
junit5: [
"org.junit.jupiter:junit-jupiter-api:$junitVersion",
"org.junit.jupiter:junit-jupiter-engine:$junitVersion"
],
mock: ['org.mockito:mockito-core:1.10.19'],
h2db: 'com.h2database:h2:1.4.200'
]
]
}
def compileOnly2(library) {
dependencies.compileOnly library.lib, {
library.exclude.each { String excludeLibStr ->
String[] excludeLib = excludeLibStr.split(':')
exclude group: excludeLib[0], module: excludeLib[1]
}
}
}
def testImplementation2(library) {
dependencies.testImplementation library.lib, {
library.exclude.each { String excludeLibStr ->
String[] excludeLib = excludeLibStr.split(':')
exclude group: excludeLib[0], module: excludeLib[1]
}
}
}
dependencies {
compileOnly libs.lombok
annotationProcessor libs.lombok
compileOnly2 libs.bukkit
implementation libs.commons_text
implementation libs.reflection_object
testImplementation libs.test.junit5
testImplementation libs.test.mock
testImplementation2 libs.bukkit
testImplementation libs.test.h2db
}
test {
useJUnitPlatform()
}
}

View File

@@ -0,0 +1,67 @@
import org.gradle.api.DefaultTask
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.tasks.TaskAction
import java.nio.charset.StandardCharsets
import java.nio.file.Path
class LogicPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
project.tasks.register('incrementVersion', IncrementVersion)
}
}
class IncrementVersion extends DefaultTask {
@TaskAction
def incrementVersion() {
String nextVer
if (project.hasProperty('nextVersion')) {
nextVer = project.property('nextVersion')
} else {
nextVer = nextVersion()
}
incrementReadmeVersion(nextVer)
incrementGradlePropsVersion(nextVer)
}
private void incrementReadmeVersion(String nextVersion) {
String nextVersion2 = nextVersion.replaceAll(/-/, '--')
Reader reader = this.getClass().getResource('README.SRC.MD').toURI().toURL().newReader()
File readmeFile = project.file(project.getRootDir().toPath().resolve('README.MD'))
readmeFile.withWriter { wr ->
reader.eachLine { line ->
wr << line.replaceAll('@VERSION@', nextVersion)
.replaceAll('@VERSION2@', nextVersion2)
wr << "\n"
}
}
}
private void incrementGradlePropsVersion(String nextVersion) {
Path gradlePropertiesPath = project.projectDir.toPath().resolve('gradle.properties');
Properties gradleProperties = new Properties()
gradleProperties.load(gradlePropertiesPath.newReader())
gradleProperties.setProperty('moduleVersion', nextVersion)
gradlePropertiesPath.withWriter(StandardCharsets.UTF_8.name(), { wr ->
gradleProperties.each {
wr << "${it.key}=${it.value}"
wr << "\n"
}
})
}
private String nextVersion() {
def matcher = project.property('moduleVersion') =~ /^(.*\d+\.)(\d+)(-.+)?$/
matcher.find()
return matcher.group(1) + (matcher.group(2).toInteger() + 1) + (matcher.group(3) == null ? '' : matcher.group(3))
}
}

View File

@@ -0,0 +1,39 @@
package libs
class LibsExtention {
private def junit_version = '5.5.2'
final def commons_text = 'org.apache.commons:commons-text:1.9'
final def lombok = 'org.projectlombok:lombok:1.18.12'
final def bukkit = filter([
lib : 'org.bukkit:bukkit:1.12.2-R0.1-SNAPSHOT',
exclude: [
'com.google.code.gson:gson',
'com.googlecode.json-simple:json-simple',
'commons-lang:commons-lang',
'org.yaml:snakeyaml'
]])
final def test = [
junit5: [
"org.junit.jupiter:junit-jupiter-api:$junit_version",
"org.junit.jupiter:junit-jupiter-engine:$junit_version"
],
mock : ['org.mockito:mockito-core:1.10.19'],
h2db : 'com.h2database:h2:1.4.200'
]
private static def filter(library) {
Object[] result = new Object[2]
result[0] = library.lib
result[1] = {
library.exclude.each { String excludeLibStr ->
String[] excludeLib = excludeLibStr.split(':')
exclude group: excludeLib[0], module: excludeLib[1]
}
}
return result
}
}

View File

@@ -0,0 +1,13 @@
package libs
import org.gradle.api.Plugin
import org.gradle.api.Project
class LibsPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
project.extensions.create('libs', LibsExtention)
project.extensions.create('repos', ReposExtention)
}
}

View File

@@ -0,0 +1,20 @@
package libs
import org.gradle.api.Action
import org.gradle.api.artifacts.repositories.MavenArtifactRepository
class ReposExtention {
final def mavenSpigotMC = createSimpleMavenRepo('SpigotMC repository', 'https://hub.spigotmc.org/nexus/content/groups/public')
final def mavenDmxMcProject = createSimpleMavenRepo('DmitriyMX MC Project repository', 'https://dmx-mc-project.gitlab.io/maven-repository/')
private static Action<MavenArtifactRepository> createSimpleMavenRepo(String name, String url) {
return new Action<MavenArtifactRepository>() {
@Override
void execute(MavenArtifactRepository mavenArtifactRepository) {
mavenArtifactRepository.setName(name)
mavenArtifactRepository.setUrl(url)
}
}
}
}

View File

@@ -0,0 +1,636 @@
# GHAST TOOLS
![version: @VERSION@](https://img.shields.io/badge/version-@VERSION2@-0a0.svg?style=flat)
![bukkit-api: 1.12](https://img.shields.io/badge/bukkit--api-1.12-d50.svg?style=flat)
Набор вспомогательных инструментов для Bukkit API.
_Основан на версии Bukkit API 1.12._
## Содержание
1. [Перед использованием](#перед-использованием)
2. [GhastTools](#ghasttools)
3. [AssetsManager](#assetsmanager)
4. [BuildHelper](#buildhelper)
5. [CommandManager](#commandmanager)
6. [EffectsHelper](#effectshelper)
7. [EventContext](#eventcontext)
8. [JdbcTemplate](#jdbctemplate)
9. [ScheduleManager](#schedulemanager)
10. [ScheduleTask](#scheduletask)
11. [I18n](#i18n)
12. [XLog](#xlog)
13. [Подключение](#подключение)
1. [Gradle](#gradle)
2. [Maven](#maven)
---
## Перед использованием
Перед началом использования, необходимо в вашем Bukkit-плагине прописать подобный код:
```java
void onLoad() {
GhastTools.setPlugin(this);
}
```
Это необходимо сделать, т.к. весь инструментарий основан на статических (static) методах.
Все методы данного набора инструментов объеденены в классы, выполняющие роль группировщиков.
## GhastTools
Методы общего назначения или без определённой группировки.
### loadConfig
Загрузка файла настроек плагина - `config.yml`.
```java
YamlConfiguration config = GhastTools.loadConfig();
```
По-умолчанию "гаст" пытается файл найти в папке плагина - `getDataFolder()`.
Если файла там нет, то выгружает встроенный (имеющийся в `.jar` файле плагина) в эту папку и загружает его.
```java
YamlConfiguration config = GhastTools.loadConfig(false);
```
Если передать параметр `false`, то при отсутствии файла `config.yml` в папке плагина,
будет загруден исключительно встроенный файл настроек.
## AssetsManager
Методы по работе с файлами плагина (_"ассетами"_).
У каждой группы методов один и тот же набор входных параметров:
- `resourceName` - наименование и путь к файлу в папке плагина
- `defaultResourceName` - наименование и путь к файлу в плагине.
_Опционально. По-умолчанию равнен `resourceName`_
- `saveDefault` - необходимость скопировать содержимое файла из `defaultResourceName` в файл `resourceName`.
_Опционально. По-умолчанию равен `true`_
Правила поиска файлов так же одинаков для каждой группы:
- В начале файл ищется в папке плагина
- Если файл отсутствует в папке плагина, то...
- если `defaultResourceName` не равен `null`...
- если `saveDefault` равен `true`, то файл из плагина будет выгружен в папку плагина и от туда загружен в память.
- если `saveDefault` равен `false`, то данные будут взяты из файлв в плагине.
- если `defaultResourceName` равен `null`, то будет брошено исключение `AssetsException` с описанием ошибки вида "Файл X не найден".
### getAsInputStream
```java
InputStream inputStream = AssetsManager.getAsInputStream("translate.ru.yml", "translate.yml", false);
```
### getAsReader
```java
Reader reader = AssetsManager.getAsReader("translate.ru.yml", "translate.yml", false);
```
### getAsString
```java
String string = AssetsManager.getAsString("readme.txt", "readme.txt", StandardCharsets.UTF_8, false);
```
У этой группы есть дополнительный _опциональный_ параметр - `charset` - в котором указывается кодиривка получаемой строки.
о-умолчанию равен `StandardCharsets.UTF_8`_
## BuildHelper
Набор методов облегчающих размецение объектов на карте.
### placeSkull
```java
Location location = ...;
Skull skull = BuildHelper.placeSkull(location, BlockFace.NORTH)
```
### placeSignWall
```java
Location location = ...;
Sign signWall = BuildHelper.placeSignWall(location, BlockFace.NORTH)
```
## CommandManager
Регистрация команд.
Имеется два варианта использования: упрощённый
```java
CommandManager.register("start", (sender, args) -> sender.sendMessage("hello!"));
```
и подробный:
```java
CommandManager.create("start")
.useOnlyPlayer()
.executer((sender, args) -> sender.sendMessage("hello!"))
.register();
```
### register
Упрощенная регистрация команды. Указывается лишь название команды и исполнитель.
```java
CommandManager.register("start", (sender, args) -> sender.sendMessage("hello!"));
```
### create
Конструктор для подробного варианта регистрации команды.
```java
CommandManager.Builder builder = CommandManager.create("start");
```
### executer
Указание исполнителя для команды
```java
CommandManager.Builder builder = CommandManager.create("start")
.executer((sender, args) -> sender.sendMessage("hello!"));
```
### onError
Обработчик исключений
```java
CommandManager.Builder builder = CommandManager.create("start")
.onError((sender, commandName, args, exception) -> {
sender.sendMessage(ChatColor.RED + "Произошла ошибка при выполнении команды '" + commandName + "'.");
exception.printStackTrace();
});
```
### useOnlyPlayer
Указание, что данную команду могут использовать только Игроки.
Опционально можно указать сообщение, которое будет выводиться в консоль.
_Отменяет действие указателя `useOnlyConsole`_
```java
CommandManager.Builder builder = CommandManager.create("start")
.useOnlyPlayer("Команду могут использовать только игроки");
```
### useOnlyConsole
Указание, что данную команду можно использовать только в консоле.
Опционально можно указать сообщение, которое будет выводиться Игроку.
_Отменяет действие указателя `useOnlyPlayer`_
```java
CommandManager.Builder builder = CommandManager.create("start")
.useOnlyConsole(ChatColor.RED + "Команду можно использовать только в консоли");
```
### register
Регистрация описанной в Конструкторе команды.
```java
CommandManager.create("start")
.useOnlyPlayer()
.executer((sender, args) -> sender.sendMessage("hello!"))
.register();
```
## EffectsHelper
Набор методов для работы с эффектами.
### playSound
Воспроизвести звук.
```java
Location location = ...;
EffectsHelper.playSound(location, Sound.AMBIENT_CAVE, 1.0f);
```
### particle
Создание частиц.
```java
Location location = ...;
EffectsHelper.particle(location, Particle.REDSTONE, 1.0d, 1.0d, 1.0d, 1.0d, 5);
```
## EventContext
Регистрация группы обработчиков событий, объединённых общим условием выполнения.
```java
EventContext.create()
.filter(() -> Bukkit.getOnlinePlayers().size() > 10)
.onEvent(PlayerJoinEvent.class, event -> { event.getPlayer().kickPlayer("Max players"); });
```
### create
Создание контекста событий.
```java
EventContext eventContext = EventContext.create();
```
### filter
Условие, при котором будут срабатывать обработчики событий в данном контексте.
```java
EventContext.create()
.filter(() -> Bukkit.getOnlinePlayers().size() > 10)
```
### onEvent
Указание события и его обработчика.
_Обработчик события регистрируется сразу же._
```java
EventContext.create()
.filter(() -> Bukkit.getOnlinePlayers().size() > 10)
.onEvent(PlayerJoinEvent.class, event -> event.getPlayer().kickPlayer("Max players"));
```
### cancelEvent
Отменить событие.
_Обработчик события регистрируется сразу же._
```java
EventContext.create()
.cancelEvent(BlockPlaceEvent.class);
```
эквивалентен коду:
```java
EventContext.create()
.onEvent(BlockPlaceEvent.class, event -> event.setCancelled(true))
```
## JdbcTemplate
Инструмент для упрощения работы с SQL базами данных, работающими через JDBC.
Для начала потребуется создать объект `DataSource`
```java
// На примере MySQL
MysqlDataSource dataSource = new MysqlDataSource();
dataSource.setServerName("localhost");
dataSource.setPort(3306);
dataSource.setCharacterEncoding(StandardCharsets.UTF_8.name());
dataSource.setDatabaseName("MyDataBase");
dataSource.setUser("root");
dataSource.setPassword("secret");
```
После чего создать `JdbcTemplate`
```java
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
```
### execute
Выполнение SQL запроса без последующей обработки результатов выполнения.
```java
jdbcTemplate.execute("CREATE TABLE my_table (id int, name varchar(16));");
```
### query
Выполнение SQL запроса и обработка его результатов.
Может возвращать любой тип объектов.
```java
String name = jdbcTemplate.query("SELECT name FROM my_table LIMIT 0,1", resultSet -> {
if (resultSet.next()) {
return resultSet.getString("name");
} else {
return null;
}
});
```
```java
List<String> names = jdbcTemplate.query("SELECT name FROM my_table", resultSet -> {
if (resultSet.next()) {
List<String> list = new ArrayList<>();
do {
list.add(resultSet.getString("name"));
} while (resultSet.next());
return list;
} else {
return Collections.emptyList();
}
});
```
### queryOne
Выполнение SQL запроса с расчетом, что результат будет единичным либо не будет вовсе.
Возвращает `Optional`.
```java
Optional<String> optName = jdbcTemplate.queryOne("SELECT name FROM my_table WHERE name LIKE 'dmitriymx'", rs -> rs.getString("name"));
```
### queryList
Выполнение SQL запроса и обработка результата как списка данных.
Возвращает `List`.
```java
List<String> names = jdbcTemplate.queryList("SELECT name FROM my_table", (resultSet, rowNum) -> resultSet.getString("name"));
```
### queryForMap
Выполнение SQL запроса с расчетом, что результат будет единичным либо не будет вовсе.
Возвращает `Map<String, Object>`, где ключ — это наименование колонок таблицы, а значения — это значения в ячейках таблицы.
```java
Map<String, Object> map = jdbcTemplate.queryForMap("SELECT * FROM my_table LIMIT 0,1;");
```
### queryForMapList
Выполнение SQL запроса и обработка результата как списка данных.
Возвращает `List<Map<String, Object>>`, где ключ — это наименование колонок таблицы, а значения — это значения в ячейках таблицы.
```java
List<Map<String, Object>> mapList = jdbcTemplate.queryForMapList("SELECT * FROM my_table");
```
### update
Выполнение SQL запроса где будет происходить обновление данных в таблице.
Под "обновлением" подразумеваются любые изменения в таблице: `UPDATE`, `DELETE`, `INSERT`.
Возвращает число строк, которые были _по факту обновлены_ в таблице.
```java
int rows = jdbcTemplate.update("DELETE FROM my_table WHERE name LIKE 'dmitriymx';");
```
## ScheduleManager
Набор методов для создания параллельных задач, выполняющихся один раз или по рассписанию.
### createTask
Создание конструктора задачи.
```java
ScheduleManager.Builder builder = ScheduleManager.createTask();
```
### useBukkitScheduler
Если задача будет взаимодействоватьс **Bukkit API** или необходима привязка задачи к _тикам_,
то необходимо использовать данный указатель.
В ином случае, указатель не нужен.
```java
ScheduleManager.Builder builder = ScheduleManager.createTask()
.useBukkitScheduler();
```
### after
Указание, что задачу нужно выполнить не сразу, а с некоторой задержкой перед запуском.
```java
ScheduleManager.Builder builder = ScheduleManager.createTask()
.after(5, TimeUnit.MINUTES);
```
### every
Указание, что задачу нужно повторять через указанное время.
```java
ScheduleManager.Builder builder = ScheduleManager.createTask()
.every(5, TimeUnit.MINUTES);
```
### create
Создание описанной задачи.
```java
ScheduleTask scheduleTask = ScheduleManager.createTask()
.every(1, TimeUnit.SECONDS)
.create(() -> Bukkit.getServer().getLogger().info("TimeMS: " + System.currentTimeMillis()));
```
_Задача будет только создана. Для её выполнения нужно вызвать `scheduleTask.start()`._
### execute
Создание и выполнение описанной задачи.
```java
ScheduleTask scheduleTask = ScheduleManager.createTask()
.every(1, TimeUnit.SECONDS)
.execute(() -> Bukkit.getServer().getLogger().info("TimeMS: " + System.currentTimeMillis()));
```
## ScheduleTask
Вспомогательный объект, созданный через `ScheduleManager`. Позволяет управлять созданной задачей.
### start
Запускает задачу, если она еще не запущена.
```java
ScheduleTask scheduleTask = ...;
scheduleTask.start();
```
### isCanceled
Возвращает состояние задачи. Если `true`, значит задача была или _отменена/остоновлена_ или была завершена.
```java
ScheduleTask scheduleTask = ...;
boolean status = scheduleTask.isCanceled();
```
### cancel
Отменяет/Остонавливает выполнение задачи.
```java
ScheduleTask scheduleTask = ...;
scheduleTask.cancel();
```
## I18n
Инструмент для работы с мультиязыковыми сообщениями или просто сообщениями, которые храняться в отдельном файле.
Позволяет использовать шаблонизированные сообщения вида `Привет, {player}!`.
### loadMessages
Загрузка сообщений в инструмент.
Передать можно как "мапу" с перечислением ключ-сообщение, так и `Reader` на файл в формате `key=message` (как у `Properties`).
```java
Map<String, String> messagesMap = ...;
I18n.loadMessages(messagesMap);
```
```java
Reader reader = AssetsManager.getAsReader("messages.properties");
I18n.loadMessages(reader);
```
В первом параметре можно указать код языка, для которого загружаются сообщения. По-умолчанию будет "en".
```java
Map<String, String> enMessagesMap = ...;
Map<String, String> ruMessagesMap = ...;
I18n.loadMessages("en", enMessagesMap);
I18n.loadMessages("ru", ruMessagesMap);
```
```java
Reader readerEn = AssetsManager.getAsReader("messages.properties");
Reader readerRu = AssetsManager.getAsReader("messages.ru.properties");
I18n.loadMessages("en", readerEn);
I18n.loadMessages("ru", readerRu);
```
### get
Получение сообщения по его ключу.
```java
String msg = I18n.get("player.join.msg");
```
Если следующим сообщением указать `Map<String, Object>`, то можно будет воспользоваться шаблонизатором.
```java
Map<String, String> messagesMap = new HashMap<>();
messagesMap.put("player.join.msg", "Привет, {player}!");
I18n.loadMessages(messagesMap);
Map<String, String> params = new HashMap<>();
params.put("player", event.getPlayer().getName());
String msg = I18n.get("player.join.msg", params);
```
Однако можно создавать `Map<String, String> params` явно, а воспользоваться [paramBuilder()](#parambuilder)
```java
String msg = I18n.get("player.join.msg", I18n.paramBuilder()
.add("player", event.getPlayer().getName())
.build());
```
Можно первым параметром указать код языка.
```java
String msg = I18n.get("ru", "player.join.msg");
```
### paramBuilder
Инструмент для параметизирования шаблонов сообщений.
```java
Map<String, Object> params = I18n.paramBuilder()
.add("player", event.getPlayer().getName())
.build();
String msg = I18n.get("player.join.msg", params);
```
## XLog
Замена стандартному `getLogger()`, который использует `java.utils.Logger` и не всегда удобен для логирования.
Имеет 4 уровня логирования: `debug`, `info`, `warning`, `error`.
Сообщения могут быть шаблонизированными. Синтаксис шаблонов — `java.text.MessageFormat`.
Примеры:
```java
XLog.info("Hello");
XLog.info("Player {0} join game", event.getPlayer().getName());
XLog.error("ERROR!", exception);
XLog.error("ERROR: {0}", exception.getMessage());
// Для экранирования "{" нужно перед ней поставить "'".
// А для использования "'" нужно их дублировать.
XLog.error("ERROR ''{0}'' in Event '{{1}'}: {2}", exception.getClass(), event.getClass(), exception.getMessage());
XLog.error("ERROR: {0}", exception.getMessage(), exception);
```
---
## Подключение
### Gradle
```groovy
repositories {
maven { url 'https://dmx-mc-project.gitlab.io/maven-repository/' }
}
```
```groovy
implementation group: 'ghast', name: 'ghast-tools', version: '@VERSION@'
```
### Maven
```xml
<repositories>
<repository>
<id>dmx-mc-project</id>
<url>https://dmx-mc-project.gitlab.io/maven-repository/</url>
</repository>
</repositories>
```
```xml
<dependencies>
<dependency>
<groupId>ghast</groupId>
<artifactId>ghast-tools</artifactId>
<version>@VERSION@</version>
</dependency>
</dependencies>
```

View File

@@ -1,3 +1 @@
projectGroup=ghast
projectName=ghast-tools
projectVersion=1.4

View File

@@ -0,0 +1,3 @@
dependencies {
compileOnly libs.bukkit
}

View File

@@ -0,0 +1,2 @@
moduleName=phantom-classes
moduleVersion=1.0-SNAPSHOT

View File

@@ -0,0 +1,16 @@
package com.mojang.authlib;
import com.mojang.authlib.properties.PropertyMap;
import java.util.UUID;
public class GameProfile {
public GameProfile(UUID id, String name) {
}
public PropertyMap getProperties() {
return null;
}
}

View File

@@ -0,0 +1,8 @@
package com.mojang.authlib.properties;
public class Property {
public Property(String value, String name) {
}
}

View File

@@ -0,0 +1,12 @@
package com.mojang.authlib.properties;
import com.google.common.collect.ForwardingMultimap;
import com.google.common.collect.Multimap;
public class PropertyMap extends ForwardingMultimap<String, Property> {
@Override
protected Multimap<String, Property> delegate() {
return null;
}
}

View File

@@ -0,0 +1,7 @@
package net.minecraft.server.v1_12_R1;
public class BlockPosition {
public BlockPosition(double x, double y, double z) {
}
}

View File

@@ -0,0 +1,5 @@
package net.minecraft.server.v1_12_R1;
public class TileEntity {
}

View File

@@ -0,0 +1,10 @@
package net.minecraft.server.v1_12_R1;
import com.mojang.authlib.GameProfile;
public class TileEntitySkull extends TileEntity {
public void setGameProfile(GameProfile gameprofile) {
}
}

View File

@@ -0,0 +1,8 @@
package net.minecraft.server.v1_12_R1;
public class WorldServer {
public TileEntity getTileEntity(BlockPosition pos) {
return null;
}
}

View File

@@ -0,0 +1,10 @@
package org.bukkit.craftbukkit.v1_12_R1;
import org.bukkit.command.SimpleCommandMap;
public class CraftServer {
public SimpleCommandMap getCommandMap() {
return null;
}
}

View File

@@ -0,0 +1,10 @@
package org.bukkit.craftbukkit.v1_12_R1;
import net.minecraft.server.v1_12_R1.WorldServer;
public class CraftWorld {
public WorldServer getHandle() {
return null;
}
}

View File

@@ -1,2 +1,2 @@
rootProject.name = projectName
include 'phantom-classes'
include 'tools'

13
tools/build.gradle Normal file
View File

@@ -0,0 +1,13 @@
repositories {
maven repos.mavenSpigotMC
maven repos.mavenDmxMcProject
}
dependencies {
compileOnly project(':phantom-classes')
compileOnly libs.bukkit
implementation libs.commons_text
testImplementation libs.bukkit
testImplementation libs.test.h2db
}

2
tools/gradle.properties Normal file
View File

@@ -0,0 +1,2 @@
moduleName=ghast-tools
moduleVersion=1.11-SNAPSHOT

View File

@@ -1,6 +1,10 @@
package ghast;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import lombok.experimental.UtilityClass;
import net.minecraft.server.v1_12_R1.BlockPosition;
import net.minecraft.server.v1_12_R1.TileEntitySkull;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.SkullType;
@@ -8,8 +12,7 @@ import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.Sign;
import org.bukkit.block.Skull;
import ru.dmitriymx.reflection.ReflectionClass;
import ru.dmitriymx.reflection.ReflectionObject;
import org.bukkit.craftbukkit.v1_12_R1.CraftWorld;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
@@ -19,9 +22,6 @@ import java.util.UUID;
@SuppressWarnings("unused")
public class BuildHelper {
private final Class<?> CLASS_BLOCKPOSITION = getClassForName("net.minecraft.server.v1_12_R1.BlockPosition");
private final Class<?> CLASS_GAMEPROFILE = getClassForName("com.mojang.authlib.GameProfile");
/**
* Установка черепа.
* <p>
@@ -95,17 +95,10 @@ public class BuildHelper {
* @param skinUrl URL на текстуру
*/
public static void setPlayerHeadSkin(Skull skull, String skinUrl) {
//TODO заменить рефлексию на "фантомные" классы
ReflectionObject refobjBlockPosition = new ReflectionClass(CLASS_BLOCKPOSITION)
.constructor(double.class, double.class, double.class)
.newInstance(skull.getX(), skull.getY(), skull.getZ());
new ReflectionObject(skull.getWorld())
.method("getHandle").invoke()
.method("getTileEntity", CLASS_BLOCKPOSITION)
.invoke(refobjBlockPosition.getOriginalObject())
.method("setGameProfile", CLASS_GAMEPROFILE)
.invoke(getRefObjPlayerProfile(skinUrl).getOriginalObject());
((TileEntitySkull) (((CraftWorld) skull.getWorld())
.getHandle()
.getTileEntity(new BlockPosition(skull.getX(), skull.getY(), skull.getZ()))))
.setGameProfile(generateTexturedPlayerProfile(skinUrl));
}
public Sign placeSignWall(Location location, BlockFace face) {
@@ -119,29 +112,12 @@ public class BuildHelper {
return sign;
}
private ReflectionObject getRefObjPlayerProfile(String url){
ReflectionObject refobjProperty = new ReflectionClass(
getClassForName("com.mojang.authlib.properties.Property"))
.constructor(String.class, String.class)
.newInstance("textures", Base64.getEncoder()
.encodeToString(("{textures:{SKIN:{url:\"" + url + "\"}}}").getBytes(StandardCharsets.UTF_8)));
private GameProfile generateTexturedPlayerProfile(String skinUrl) {
GameProfile gameProfile = new GameProfile(UUID.randomUUID(), null);
ReflectionObject refobjGameProfile = new ReflectionClass(CLASS_GAMEPROFILE)
.constructor(UUID.class, String.class)
.newInstance(UUID.randomUUID(), null);
refobjGameProfile
.method("getProperties").invoke()
.method("put", Object.class, Object.class)
.invoke("textures", refobjProperty.getOriginalObject());
return refobjGameProfile;
}
private Class<?> getClassForName(String className) {
try {
return Class.forName(className);
} catch (Exception e) {
throw new RuntimeException(e);
}
gameProfile.getProperties().put("textures",
new Property("textures", Base64.getEncoder().encodeToString(
("{textures:{SKIN:{url:\"" + skinUrl + "\"}}}").getBytes(StandardCharsets.UTF_8))));
return gameProfile;
}
}

View File

@@ -4,8 +4,7 @@ import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.experimental.UtilityClass;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import ru.dmitriymx.reflection.ReflectionObject;
import org.bukkit.craftbukkit.v1_12_R1.CraftServer;
@UtilityClass
@SuppressWarnings("unused")
@@ -59,12 +58,9 @@ public class CommandManager {
}
public void register() {
//TODO для Paper такие "извращения" не требуются. Нужно продумать.
new ReflectionObject(Bukkit.getServer())
.method("getCommandMap").invoke()
.method("register", String.class, Command.class).invoke(
name, new CommandWrapper(name, this.onlyPlayer, this.deniedMessage,
this.executer, this.errorConsumer)
((CraftServer) Bukkit.getServer()).getCommandMap().register(
name,
new CommandWrapper(name, this.onlyPlayer, this.deniedMessage, this.executer, this.errorConsumer)
);
}
}