CloudWorks API
CloudWorks API — это открытый приватный API для модов DrownedCloud. Предоставляет разработчикам ряд полезных функций.
- Загрузчики
- NeoForge
- Скачиваний на Modrinth
- 5,1 тыс.
- Подписчиков на Modrinth
- 1
- версии
- 4
- Обновлён
- 8 августа 20268 авг.
- Опубликован
- в 2026
Коротко о моде
Нужен и в игре, и на сервере
CloudWorks API
Универсальный интерфейс для разработки модов — предоставляет стандартизированные возможности для модов на NeoForge 1.21.1, включая систему прочных блоков, парсинг рецептов и захват логов консоли.
Версия: 1.1.0-1.21.1 | Платформа: NeoForge 1.21.1 | Java 21
Содержание
- Быстрый старт
- DurableBlock — система прочных блоков
- RecipeParser — парсинг рецептов
- ConsoleSeeker — захват логов консоли
- Отладочные команды
- Архитектура модулей
- Лицензия
Быстрый старт
Добавление зависимости
Добавьте следующее в ваш build.gradle:
repositories {
maven { url = 'https://your-maven-repo' }
}
dependencies {
implementation 'com.cloudworks:CloudWorksAPI:1.1.0-1.21.1'
}Опциональная зависимость: интеграция с модом Jade
Если вам нужен показ прочности прочных блоков через Jade, добавьте зависимость Jade API:
repositories {
maven { url = "https://api.modrinth.com/maven" }
}
dependencies {
compileOnly "maven.modrinth:jade:15.10.5+neoforge"
}Включение модулей
CloudWorks API использует аннотации для активации модулей по требованию. Добавьте соответствующую аннотацию к вашему главному классу мода:
@Mod("your_mod_id")
@CloudworksRecipeParser // Включает модуль RecipeParser
@CloudWorksConsoleSeeker // Включает ConsoleSeeker + API
public class YourMod {
public YourMod(IEventBus modEventBus) {
// ...
}
}- С аннотацией: принудительно включает модуль, игнорируя переключатель в конфиге
- Без аннотации: управляется через конфиг
- Каждый модуль можно независимо включать/отключать, они не зависят друг от друга
DurableBlock — система прочных блоков
Основная идея
DurableBlock предоставляет полноценную систему прочных блоков, позволяющую блокам иметь значения прочности, сопротивление урону и автоматическое восстановление.
В отличие от традиционных решений, эта система не полагается на BlockEntity. Вместо этого она использует подкласс LivingEntity (DurableBlockEntity) для хранения данных о прочности и получения урона. Такая архитектура сочетает сохранение данных BlockEntity с возможностью получать атаки, как у LivingEntity.
Архитектура
DurableBlock (Block)
│ Создаёт сущность при установке
│ Свойства: maxDurability, baseResistance, сопротивления по типам
▼
DurableBlockEntity (LivingEntity)
│ Сущность только на сервере (clientTrackingRange=0)
│ Привязана к координатам блока (boundPos), каждый тик проверяет существование блока
│ Расчёт прочности, обработка урона, сохранение в NBT
│
├── Обычный поток: прочность достигает нуля → блок ломается → сущность самоуничтожается
└── PersistentDurableBlockEntity (подкласс)
└── Защита от читерского убийства: перехватывает атаки с мгновенным убийством → потеря 20% прочностиОсновные классы
| Класс | Ответственность |
|---|---|
DurableBlock | Базовый класс прочного блока, задаёт атрибуты прочности и параметры сопротивления, управляет созданием сущности |
DurableBlockEntity | Замена блок-энтити (LivingEntity), обрабатывает урон, расчёт прочности, сохранение данных |
PersistentDurableBlock | Постоянный прочный блок, расширяет DurableBlock, использует PersistentDurableBlockEntity |
PersistentDurableBlockEntity | Постоянная сущность, перехватывает атаки с мгновенным убийством, преобразует их в потерю прочности |
DurableBlockDamageType | Перечисление типов урона (EXPLOSION / PHYSICAL / MAGIC) |
Использование
Базовый прочный блок
public class MyConcreteBlock extends DurableBlock {
public MyConcreteBlock() {
super(Properties.of()
.strength(3.0f)
.requiresCorrectToolForDrops(),
200, // maxBlockDurability: Максимальная прочность
2, // baseResistance: Плоское снижение урона
0.3f, // explosionResistance: Снижение урона от взрывов (0.0 ~ 1.0)
0.5f, // physicalResistance: Снижение физического урона (0.0 ~ 1.0)
0.1f, // magicResistance: Снижение магического урона (0.0 ~ 1.0)
0 // recoveryRate: Автовосстановление в секунду (0 = нет)
);
}
}
#### Persistent Durable Block (with Code Game Protection)
```java
public class MyPersistentBlock extends PersistentDurableBlock {
public MyPersistentBlock() {
super(Properties.of().strength(3.0f),
200, 2, 0.3f, 0.5f, 0.1f, 0);
}
}Расчёт урона
actualDamage = max(0, rawDamage - baseResistance) × (1 - typeResistance)- Базовое сопротивление: Плоское снижение сырого урона (минимум 0)
- Сопротивление типу: Применяет соответствующий коэффициент снижения в зависимости от типа урона (взрыв/физический/магический)
Классификация типов урона:
- Взрыв: TNT, криперы, кристаллы Края, взрывы кровати/якоря возрождения
- Физический: Атаки в ближнем бою, стрелы, трезубцы, снаряды
- Магический: Зелья, дыхание дракона, эффект иссушения, урон от пустоты, огонь/молния
Сущность только на стороне сервера
Сущности прочных блоков регистрируются с clientTrackingRange(0), то есть сущность существует только на сервере. Клиент ничего не знает о её существовании. Это даёт следующие преимущества:
- Игроки не могут атаковать сущность в ближнем бою: На стороне клиента нет цели; блок можно только сломать
- Не мешает взаимодействию с блоками: Не препятствует установке блоков сверху или добыче левым кликом
- Атаки мобов и дальние атаки работают как обычно: Иссушители, зомби и дальние атаки по-прежнему могут наносить урон сущности на сервере
- Синхронизация не нужна: Нет проблем с синхронизацией данных сущности между клиентом и сервером
Code Game Protection (Persistent)
PersistentDurableBlockEntity использует многослойный механизм перехвата для защиты от атак мгновенного убийства:
- Перехват
remove(): Проверяет флагinternalRemoval; внешние вызовы вместо этого вызывают потерю 20% прочности - Перехват
setHealth(): Предотвращает обнуление здоровья извне - Перехват
setPose(): Предотвращает состояниеPose.DYING - Обнаружение в
hurt(): Проверяет тегinstant_kill_weapon, отменяет события урона от оружия мгновенного убийства - Коррекция состояния
dead: Проверяет и исправляет полеdeadкаждый тик - Кадры неуязвимости: 3 тика неуязвимости после каждого попадания, ограничивая попадания до 5 в секунду
Тег оружия мгновенного убийства
Тег предметов cloudworks_api:instant_kill_weapon помечает оружие мгновенного убийства. Когда сущность с предметом из этого тега атакует, PersistentDurableBlockEntity напрямую отменяет событие урона.
Содержимое по умолчанию:
{
"values": [
"avaritia:infinity_sword"
]
}Авторы сборок могут добавлять больше оружия в этот тег через датапак.
Управление жизненным циклом
- Установка блока:
DurableBlock.setPlacedBy()создаёт сущность на позиции блока - Разрушение блока: Сущность больше не удаляется событиями разрушения блока; она сама управляет своим жизненным циклом
- Проверка каждый тик: Сущность каждый тик проверяет, существует ли ещё её привязанный блок; если нет — самоуничтожается
- Истощение прочности:
onDurabilityZero()ломает блок и удаляет сущность - Сохранение в NBT: Данные прочности сохраняются в чанки через
addAdditionalSaveData/readAdditionalSaveData
Механизм привязанной позиции
Каждая сущность хранит позицию привязанного блока в boundPos. Когда сущность собирается исчезнуть по любой причине, она уничтожает блок на этой позиции. Одновременно она каждый тик проверяет, существует ли ещё блок на этой позиции и совпадает ли он с ожидаемым типом; если нет — удаляет себя. Такая конструкция предотвращает баг, при котором хитбокс сущности, заходящий в область соседних блоков, ошибочно удалял другие сущности.
Интеграция с модом Jade
DurableBlock включает встроенную интеграцию с Jade (DurableBlockJadePlugin), которая отображает информацию о прочности блока в подсказках Jade:
- Показывает
Durability: current / maxстрокой ниже названия блока - Текущая прочность отображается с 2 знаками после запятой
- Поскольку сущность использует подход только на стороне сервера, данные о прочности собираются на сервере через
IServerDataProviderи синхронизируются с клиентом через пакеты данных Jade
Дизайн хитбокса
Хитбокс сущности — 1.02×1.02 (чуть больше блока 1×1), центрирован по центру блока (y+0.5), выступает на 0.01 блока со всех шести сторон. Это позволяет мобам атаковать сущность с любой стороны, при этом сущность не мешает установке блоков или передвижению.
RecipeParser — Парсинг рецептов
Основные концепции
Поток парсинга рецептов:
Recipe JSON --> serializeRecipe() --> JsonElement
|
v
DSL Template (.rpml) --> TemplateParser --> TemplateNode (AST)
|
v
RecipeExtractor.extract()
|
v
RecipeData
(Ingredient[], Product[])- Сериализация: сериализовать рецепт в JSON через Minecraft Codec
- Парсинг шаблона: распарсить DSL-шаблон
.rpmlв AST - Извлечение данных: извлечь структурированные данные из JSON согласно AST
Основные классы
| Класс | Ответственность |
|---|---|
RecipeParser | Синглтон-ядро, управляет загрузкой шаблонов, парсингом рецептов и переносом жидкостей |
RecipeParserAPI | Статический фасадный класс, предоставляет все API наружу |
RecipeData | Результат парсинга, содержит inputs и outputs |
Ingredient | Входной материал: id, count, unit, type |
Product | Выходной продукт: id, count, unit, type, rate (вероятность) |
Справочник по API
Все API вызываются через статические методы RecipeParserAPI.
Базовые запросы
| Метод | Описание |
|---|---|
getRecipeData(ResourceLocation, RecipeManager) | Распарсить один рецепт |
getRecipeDataBatch(Collection, RecipeManager) | Пакетный парсинг рецептов |
isRecipeParsable(ResourceLocation, RecipeManager) | Проверить, можно ли распарсить рецепт |
getParsableRecipes(String modId, String recipeType, RecipeManager) | Получить все парсируемые рецепты заданного типа |
Расширенные запросы
// Найти рецепты, производящие цель
List<RecipeParseResult> parseProduceRecipe(
ResourceLocation targetId, // ID целевого предмета/жидкости
QueryMode mode, // ITEM или FLUID
RecipeManager recipeManager
)
// Найти рецепты, использующие цель как вход
List<RecipeParseResult> parseUsageRecipe(
ResourceLocation targetId,
QueryMode mode,
RecipeManager recipeManager
)| mode | Логика сопоставления (produce) | Логика сопоставления (usage) |
|---|---|---|
ITEM | Совпадает с прямым выходом + переносом жидкость→предмет | Совпадает с ингредиентами, где unit=item |
FLUID | Совпадает с выходами жидкостей + обратным переносом | Совпадает с ингредиентами, где unit=fluid |
Асинхронные методы API
Все асинхронные API вызываются через статические методы RecipeParserAPI. Тяжёлая работа по сканированию и парсингу рецептов выполняется на выделенных рабочих потоках (пул потоков AsyncRecipeParser, 2 демон-потока), а результаты возвращаются в серверный поток через колбэк MinecraftServer.
RecipeParserAPI.parseProduceRecipeAsync(
ResourceLocation.parse("minecraft:oak_planks"),
QueryMode.ITEM,
recipeManager,
results -> {
// Этот колбэк выполняется в серверном потоке -- безопасно манипулировать объектами Minecraft
for (RecipeParseResult r : results) {
sendSuccess("Found recipe: " + r.getRecipeId());
}
},
errorMsg -> sendFailure("Query failed: " + errorMsg),
server
);
Сигнатуры асинхронных методов:
| Метод | Описание |
|---|---|
| `getRecipeDataAsync(id, mgr, cb, err, server)` | Асинхронно разбирает один рецепт |
| `getRecipeDataBatchAsync(ids, mgr, cb, err, server)` | Асинхронно разбирает рецепты пакетом |
| `parseProduceRecipeAsync(id, mode, mgr, cb, err, server)` | Асинхронно ищет рецепты производства |
| `parseUsageRecipeAsync(id, mode, mgr, cb, err, server)` | Асинхронно ищет рецепты использования |
#### Модели данных
**Ingredient**
| Поле | Тип | Описание |
|---|---|---|
| `id` | `String` | ID предмета/жидкости/тега |
| `count` | `double` | Количество |
| `unit` | `String` | `"item"` / `"fluid"` |
| `type` | `String` | `"solid"` / `"tag"` / `"fluid"` |
**Product**
| Поле | Тип | Описание |
|---|---|---|
| `id` | `String` | ID продукта |
| `count` | `double` | Количество |
| `unit` | `String` | `"item"` / `"fluid"` |
| `type` | `String` | `"solid"` / `"tag"` / `"fluid"` |
| `rate` | `double` | Вероятность выпадения (0.0 ~ 1.0, по умолчанию 1.0) |
### Шаблоны рецептов DSL
Шаблоны рецептов (файлы `.rpml`) находятся в `cloudworks/recipe_parser/templates/`, именуются в формате `{modid}_{recipetype}.rpml`.
#### Типы маркеров
| Маркер | Синтаксис | Описание |
|---|---|---|
| `input` | `<input,id=foo,count=1,unit=item,type=solid>` | Объявляет входной ингредиент |
| `output` | `<output,id=bar,count=1,unit=item,type=solid>` | Объявляет выходной продукт |
| `object` | `<object,id=myObj>` | Объявляет узел JSON-объекта |
| `key` | `<key,id=myKey>` | Динамический обход ключей JSON |
| `io_attribute` | `<count,output_id=bar>` | Внедряет атрибуты в маркеры |
| `symbol` | `<symbol,id=sym,input_id=keyIng>` | Объявляет символ для shaped-крафта |
| `patternline` | `<patternline,id=line>` | Объявляет строку шаблона для shaped-крафта |
| `duplicate` | `<duplicate,id=dupX,structure=X>` | Объявляет повторяемую структуру JSON-объекта |
| `script` | `<script,set_global_fluid_transfer=true>` | Скрипт глобальных настроек |
| `optional` | `<optional,id=opt>` | Необязательное поле |
| `variable` | `<variable,id=var>` | Ссылка на переменную |
#### Примеры шаблонов
**Ванильный верстак (shaped)**
```json
{
"type": "minecraft:crafting_shaped",
<script,set_global_fluid_transfer=true,set_global_default_transfer_rate=250>
"pattern": [
<patternline,id=line>
],
"key": <object,id=keyObj>
<key,id=key>
<object,id=keyIng>
"item": <input,id=keyIng,count=1,unit=item,type=solid>
"tag": <input,id=keyIng,count=1,unit=item,type=tag>
<symbol,id=sym,input_id=keyIng>
<count,id=keyIng_count,input_id=keyIng>
<patternline,id=line,input_id=sym>
"result": <object,id=res>
"count": <count,output_id=res>
"id": <type,output_id=res>
"item": <output,id=res,count=1,unit=item,type=solid>Ванильный верстак (shapeless)
{
"type": "minecraft:crafting_shapeless",
"ingredients": [
<object,id=ingStruct>
"item": <input,id=ingItem,count=1,unit=item,type=solid>
"tag": <input,id=ingItem,count=1,unit=item,type=tag>
<count,id=ingCount,input_id=ingItem>
<duplicate,id=ingStruct>
],
"result": <object,id=res>
"count": <count,output_id=res>
"id": <type,output_id=res>
"item": <output,id=res,count=1,unit=item,type=solid>Перевод жидкости в предмет
Когда рецепт выдаёт жидкость, её можно преобразовать в эквивалентный предмет — тогда рецепт можно будет найти и по ID предмета.
Логика перевода: Всего жидкости (mB) ÷ rate → округление (по стратегии округления) × количество продукта за единицу
Глобальные настройки (задаются через `
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
global_fluid_transfer | boolean | false | Включён ли перевод жидкости в предмет |
global_default_transfer_rate | double | 100 | Сколько mB переводится в 1 предмет |
global_default_transfer_result | string | null | ID предмета-результата перевода по умолчанию |
global_default_transfer_extra_input | map | {} | Дополнительные ингредиенты |
global_default_transfer_float_round | enum | default | round_up / round_down / default |
global_enable_template_config | boolean | true | Включены ли внешние файлы конфигурации шаблонов |
Конфиг на уровне рецепта (cloudworks/recipe_parser/templates_config/{modid}_{recipetype}.json):
{
"recipe:id": {
"enable_transfer": true,
"transfer_blacklist": ["fluid:to_skip"],
"methods": [{
"rate": 100,
"round": "default",
"extra_input": {"minecraft:item": 1},
"result": {"minecraft:output_item": 2}
}]
}
}Механизм обновления шаблонов
cloudworks/recipe_parser/config.json:
{
"version": "1.1.0-1.21.1",
"enable_update": true,
"force_update": false,
"update_ignore": ["minecraft_crafting.rpml"]
}force_update=true→ Пропустить все проверки, выпустить все файлыenable_update=false→ Пропустить обновлениеversionсовпадает → Пропустить обновление- Иначе → Обновить версию, выпустить файлы, кроме тех, что в
update_ignore
ConsoleSeeker — захват консольных логов
ConsoleSeeker выводит консольные логи в игровой чат в реальном времени и предоставляет полный конвейер событий, чтобы другие моды могли фильтровать и обрабатывать логи.
Включение
@CloudWorksConsoleSeeker // Принудительно включает ConsoleSeeker + APIИли настройте в cloudworks/console_seeker/config.json:
{
"enable_module": true,
"enable_api": false,
"enable_command_for_any_operator": true,
"list_type": "whitelist",
"player_list": [],
"max_log_length": 150,
"enable_timestamp": false
}| Поле | Тип | По умолчанию | Описание |
|---|---|---|---|
enable_module | boolean | true | Включать ли модуль ConsoleSeeker |
enable_api | boolean | false | Включать ли API событий (аннотации переопределяют это) |
enable_command_for_any_operator | boolean | true | Разрешать ли всем операторам использовать команды |
list_type | string | "whitelist" | "whitelist" / "blacklist" |
player_list | string[] | [] | Список имён игроков |
max_log_length | int | 150 | Максимальная длина лога (0 = без обрезки) |
enable_timestamp | boolean | false | Показывать ли метки времени |
Архитектура
Log4j2 Logger
│
▼
ChatAppender (Log4j2 Appender)
│ Фильтрует подстроку [CHAT]
▼
LogToChatManager
│ Фильтрует по уровню лога и подписке игрока
▼
ConsoleSeekerEventManager
│ Внутренние фильтрующие блоки → собирает tagSet
▼
NeoForge EVENT_BUS
│ Рассылает ConsoleSeekerInfoEvent / WarnEvent / ErrorEvent
▼
ExternalLogFilter (подписывается другими модами)
│ Сопоставляет tagSet → parse() → onReceive()
▼
Пользовательская логика других модовКонвейер событий
Три независимых конвейера событий для разных уровней логов:
| Класс события | Уровень лога |
|---|---|
ConsoleSeekerInfoEvent | INFO |
ConsoleSeekerWarnEvent | WARN |
ConsoleSeekerErrorEvent | ERROR |
Все события наследуются от ConsoleSeekerLogEvent со следующими полями:
| Поле | Тип | Описание |
|---|---|---|
loggerName | String | Имя логгера |
level | Level | Уровень лога |
message | String | Отформатированное сообщение (ANSI-коды цвета удалены) |
timestamp | long | Unix-время (миллисекунды) |
threadName | String | Имя потока |
thrownString | String | Трассировка стека исключения (null, если исключения нет) |
tagSet | `Set` | Набор тегов направленной доставки |
Внутренние фильтры (активная фильтрация)
Регистрируйте внутренние фильтрующие блоки через ConsoleSeekerEventManager, чтобы ConsoleSeeker активно фильтровал логи:
// Реализуйте интерфейс InternalFilterUnit
InternalFilterUnit myUnit = new InternalFilterUnit() {
public DirectedDeliveryTag getTag() {
return new DirectedDeliveryTag("mymod", "cpu_alert");
}
public boolean test(LogEvent event) {
return event.getLoggerName().startsWith("com.example");
}
};
// Зарегистрируйте во списке внутренних фильтрующих блоков
ConsoleSeekerEventManager.addInternalFilterUnit(myUnit);
**Правила фильтрации**:
- Пустой список → все события лога публикуются напрямую, tagSet пуст
- Непустой список → хотя бы один юнит должен пройти для публикации; собирает теги из прошедших юнитов в tagSet
- Все юниты не прошли → событие отбрасывается
### Теги направленной доставки
Использует формат `"modid:tagName"` для предотвращения конфликтов между модами:
```java
// Создать тег
DirectedDeliveryTag tag = new DirectedDeliveryTag("mymod", "cpu_alert");
// Правила сопоставления: пустой тег совпадает со всем, непустой тег требует точного совпадения
tag.matches(otherTag);Внешние фильтры (пассивная фильтрация)
Моды-потребители расширяют `ExternalLogFilter` для кастомного парсинга логов:
public class MyCpuFilter extends ExternalLogFilter<Integer> {
public MyCpuFilter() {
super(new DirectedDeliveryTag("mymod", "cpu_alert"));
}
@Override
protected Integer parse(ConsoleSeekerLogEvent event) {
// Кастомная логика парсинга
return Integer.parseInt(event.getMessage());
}
@Override
protected void onReceive(Integer value) {
// Обработка результата парсинга
System.out.println("CPU usage: " + value);
}
}
// Зарегистрировать, чтобы начать получать события
MyCpuFilter filter = new MyCpuFilter();
filter.register();Порядок работы:
- Сопоставить
tagSetсобытия с тегом фильтра (совпадение по пересечению, пустой набор тегов совпадает со всем) parse()преобразует сырое событие в кастомный тип (по умолчанию возвращает сырую строку лога)onReceive()обрабатывает результат парсинга
Команды отладки
Префикс команд: /cloudworks. Требуется OP-уровень 4 (для некоторых команд).
Модуль рецептов
/cloudworks recipe parse produce item [id] — Найти рецепты, создающие указанный предмет
/cloudworks recipe parse produce liquid [id] — Найти рецепты, создающие указанную жидкость
/cloudworks recipe parse usage item [id] — Найти рецепты, использующие указанный предмет
/cloudworks recipe parse usage liquid [id] — Найти рецепты, использующие указанную жидкость
/cloudworks recipe parsebatch <modid> <type> — Пакетный парсинг
/cloudworks recipe listtemplates — Список загруженных шаблоновМодуль консоли
/cloudworks console <info|warn|error> <on|off> — Переключить вывод логов в чат
/cloudworks config console player_list add <player_name> — Добавить игрока в список
/cloudworks config console player_list remove <player_name> — Удалить игрока из списка
/cloudworks config console player_list query — Просмотреть список игроковЗапрос статуса
/cloudworks status — Просмотреть статус всех модулейВывод включает: количество шаблонов RecipeParser, включённые уровни ConsoleSeeker/статус API/количество юнитов фильтра.
Архитектура модулей
src/main/java/com/cloudworks/api/
├── CloudWorksAPI.java # Точка входа мода NeoForge
├── annotation/
│ ├── CloudworksRecipeParser.java # Включает RecipeParser
│ └── CloudWorksConsoleSeeker.java # Включает ConsoleSeeker
├── command/
│ └── DebugCommand.java # Единая точка входа отладочных команд
│
├── durableblock/ # Модуль DurableBlock
│ ├── DurableBlock.java # Базовый класс прочного блока
│ ├── DurableBlockEntity.java # Замена block entity (LivingEntity)
│ ├── PersistentDurableBlock.java # Постоянный прочный блок
│ ├── PersistentDurableBlockEntity.java # Сущность защиты кода игры
│ ├── DurableBlockDamageType.java # Перечисление типов урона
│ └── jade/
│ └── DurableBlockJadePlugin.java # Плагин интеграции с модом Jade
│
├── recipeparser/ # Модуль RecipeParser
│ ├── RecipeParser.java # Ядро-синглтон
│ ├── RecipeParserAPI.java # Статический фасад API
│ ├── AsyncRecipeParser.java # Асинхронный пул потоков
│ ├── ApiSelfTest.java # Автотест
│ ├── DebugOutputWriter.java # Экспорт отладочного JSON
│ ├── dsl/ # Движок шаблонов DSL
│ │ ├── Template.java, TemplateNode.java
│ │ ├── TemplateParser.java, TemplateTokenizer.java
│ │ ├── TemplateValidator.java, RecipeExtractor.java
│ │ ├── GlobalSettings.java, TemplateConfig.java
│ │ └── ...
│ └── model/ # Модели данных
│ ├── RecipeData.java, Ingredient.java, Product.java
│ └── QueryMode.java, RecipeParseResult.java
│
└── consoleseeker/ # Модуль ConsoleSeeker
├── ChatAppender.java # Appender Log4j2
├── LogToChatManager.java # Менеджер вывода логов в чат
├── ConsoleSeekerConfig.java # Управление файлом конфигурации
├── ConsoleSeekerEventManager.java # Ядро конвейера событий
├── ConsoleSeekerCommand.java # Обработчик команд
├── InternalFilterUnit.java # Интерфейс внутреннего юнита фильтра
├── ExternalLogFilter.java # Абстрактный класс внешнего фильтра
├── DirectedDeliveryTag.java # Тег направленной доставки
├── LogFilter.java # Утилита логов (обрезка/удаление цвета)
└── event/ # Классы событий
├── ConsoleSeekerLogEvent.java # Базовый класс события
├── ConsoleSeekerInfoEvent.java
├── ConsoleSeekerWarnEvent.java
└── ConsoleSeekerErrorEvent.java
---
## Лицензия
Этот проект распространяется с открытым исходным кодом под лицензией GNU General Public License v3.0. Полный текст см. в [LICENSE](LICENSE).
CloudWorks API — Unified Mod Development Interface
Copyright (C) 2026 CloudWorks Team
Эта программа является свободным программным обеспечением: вы можете распространять её и/или изменять на условиях GNU General Public License, опубликованной Фондом свободного программного обеспечения, либо версии 3 лицензии, либо (на ваше усмотрение) любой более поздней версии.
Эта программа распространяется в надежде, что она будет полезной, но БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ, даже без подразумеваемой гарантии ТОВАРНОЙ ПРИГОДНОСТИ или ПРИГОДНОСТИ ДЛЯ КОНКРЕТНОЙ ЦЕЛИ. Подробности см. в GNU General Public License.
Вы должны были получить копию GNU General Public License вместе с этой программой. Если нет, см. <https://www.gnu.org/licenses/>.Частые вопросы
Как установить CloudWorks API?
Установите NeoForge под свою версию Minecraft, скачайте CloudWorks API под ту же версию и загрузчик, положите файл в папку mods и запустите игру с профилем загрузчика.
Для каких версий Minecraft есть CloudWorks API?
У CloudWorks API есть сборки для Minecraft 1.21.1.
Нужен ли CloudWorks API на сервере?
Нужен и в игре, и на сервере