- Чтобы добавлять новые глобальные моды взаимодействующих с сервером (например новая персонализация - граффити)
- Для упращения работы с модами, вынесенные утилиты вам помогут не писать лишний код
- Автоматическое обновление базового пакета модов (стандартный пакет, квесты, лутбокс и прочее)
- Единый стиль и оформление режимов
repositories {
mavenCentral()
maven {
url 'https://repo.c7x.dev/repository/maven-public/'
credentials {
username System.getenv("CRI_REPO_LOGIN")
password System.getenv("CRI_REPO_PASSWORD")
}
}
}
dependencies {
implementation 'me.func:visual-driver:3.2.9.RELEASE'
// ссылка на nexus https://repo.c7x.ru/#browse/browse:maven-releases:me%2Ffunc%2Fvisual-driver
}
STANDARD
- стандартный набор модов (by @fiwka
, @func, @delfikpro
, @sworroo)
DIALOG
- кит с квестовыми диалогами (by @sworroo
, @func)
LOOTBOX
- набор для подключения лутбоксов (by @delfikpro
, @func)
MULTICHAT
- набор для мультичата (by @zabelov
, @zenk)
GRAFFITI
- кит с клиентом к сервису граффити и подключением мода (by @func)
EXPERIMENTAL
- экспериментальный набор для тестирования новых модов (by @func)
NPC
- модуль для работы с NPC (by @func)
BATTLEPASS
- модуль для работы с BattlePass (by @akamex
, @func)
HEALTH_BAR
- модуль добавляющий полоску здоровья (by @delfikpro)
DEBUG
- модуль для удобной разработки модов (by @func
, @fiwka)
При старте плагина напишите Anime.include(Kit.STANDARD)
, так вы подключите стандартный набор модов, если вам нужны
другие модули, например Kit.LOOTBOX, Kit.DIALOG, Kit.STANDARD (другие будут добавлены позже), то укажите их через
запятую: Anime.include(Kit.LOOTBOX, Kit.DIALOG, Kit.STANDARD)
Моды берутся по вшитой ссылке на cristalix storage
, если вы хотите заменить ссылку на наборы модов,
используйте env MOD_STORAGE_URL
, ссылка должна оканчиваться на /
, не рекомендуется.
Если вам не нужны граффити или нужно ограничить доступные места -
используйте Anime.modifyGraffitiPlaceCondition(canPlace: Predicate<Location>)
// Вариант 1
ModLoader.loadFromWeb("https://storage.c7x.ru/func/cristalix-dialogs.jar") // Загрузка мода в стандартную папку MOD_LOCAL_DIR_NAME
ModLoader.onJoining("cristalix-dialogs") // Загрузка мода в плагин, отправка игрокам мода при входе
// Вариант 2
ModLoader.onJoining(
"mod-bundle",
"cristalix-dialogs"
) // Пойдет искать загруженные моды, если кто-то не загружен, загрузит его из стандартной папки "anime", затем моды будут отправлены игрокам при входах на сервер
// Вариант 3
ModLoader.load("mods/mod-bundle.jar") // Загрузит мод по пути
ModLoader.send("mod-bundle.jar", player) // Отправит мод игроку
// Вариант 4
ModLoader.loadAll("mods") // Загрузит все моды из папки mods
ModLoader.send("mod-bundle.jar", player) // Отправит мод игроку
download(fileUrl: String, saveDir: String)
скачивание файла по ссылке, в указанную папку (если файл уже скачан - он
перезапишется), пример download("http://cristalix.ru/cristalix-standard-mod.jar", "mods")
loadFromWeb(fileUrl: String, saveDir: String = MOD_LOCAL_DIR_NAME)
загрузка файла в стандартную папку anime
по
адресу, затем прогрузка мода на уровнь плагина
loadManyFromWeb(vararg fileUrls: String)
то же самое, но можно указывать адреса через запятую
load(filePath: String)
прогрузка мода указав путь к файлу (не web), мод сохраняется по ключу имени файла,
например mod-bundle.jar
loadAll(dirPath: String)
загрузить все моды из папки по путю к папке
send(modName: String, player: Player)
отправка мода игроку, пример имени mod-bundle.jar
manyToOne(player: Player)
отправить все прогруженные моды игроку
oneToMany(modName: String)
отправить один мод по имени всем игрокам
sendAll()
отправить все прогруженные моды всем игрокам
// Пример 1 (Умный конструктор)
ModTransfer("Осталось %n% жизней!", 76666).send("func:health", player)
// Пример 2 (Билдер)
ModTransfer()
.item(ItemStack(AIR)) // Предмет, можно NMS, Bukkit
.nbt(NBTTagCompound()) // Любой тег
.string("Привет") // Строка
.json(object) // Любой объект, будет превращен в JSON строку
.byteBuffer("heyy".bytes()) // Массив byte
.double(43.5) // Прочие числа
.varInt(4)
.putDouble(5.6) // Методы c "put" для Java
.send("func:lol", player)
// Привет 3 (Переиспользование буффера)
val data = ModTransfer().integer(items.size)
.item(item.itemStack)
.string(item.title)
.string(item.rare.name)
players.forEach { player -> data.send("lootbox", player) }
Простым языком: вы кидаете мод в папку, API это видит и перезагружает мод у игроков.
Данный режим позволяет мнгновенно обновлять мод на клиенте игрока, что очень удобно для быстрого тестировая. Обычные
этапы разработки: написание кода -> компиляция -> заливка мода в папку сервера -> перезагрузка сервера -> вход на сервер
-> загрузка мода, всего 6 этопов. С данным режимом: написание кода -> компиляция -> заливка мода в папку сервера ->
смотрим, всего 4 этапа. Так же можно автоматически загруждать мод в папку прямо из среды разработки, но это уже задача
вашей среды.
Чтобы включить режим быстрого тестирования, допишите в Anime#include
кит Kit.DEBUG
. По умолчанию папка mods
является хранилищем тестовых модов, чтобы сменить стандартную папку - измените переменную среды MOD_TEST_PATH
.
Новый скорборд для тайконов и симуляторов! ВАЖНО! Группу нужно создать только один раз, не нужно ее делать на каждого игрока
Пример создания на Kotlin
// Где-то на уровне класса
private val group = TokenGroup(
Token.builder()
.title("Деньги")
.content { player -> Emoji.DOLLAR + " §a" + convert(user(player).vault[user(player).locationType!!.moneyType]!!) }
.build(),
Token.builder()
.title("Кристалики")
.content { player -> Emoji.DONATE + " §b" + convert(app.getUser(player)!!.donateCoins.toDouble()) }
.build(),
Token.builder()
.title("Склад")
.content { player -> Emoji.MINING + " §6" + convert(getStorage(player)) }
.build(),
)
// При входе игрока, через пару тиков
group.subscribe(player)
// Удалить токены из группы
group.removeTokens(group.tokens[0].uuid)
Сделали скорборд? Подписали на него игроков? - Ну все, теперь работает
Создание
val schema = ScoreBoard.builder()
.key("lobby") // ключевое слово скорборда
.header("Бедроковая коробка") // указываем верхушку
.empty() // пустая строка
.line("Привет") // неизменяемая строка
.line("Как дела", "433") // неизменяемая строка и значение
.empty() // пустая строка
.dynamic("Коины", player -> System.currentTimeMillis()) // слева "Коины", справа изменяемое значение
.build() // получаем схему
Показ игрокам (если делаете при входе, не забудьте задержку в 1 тик)
ScoreBoard.subscribe("lobby", player) // по ключевому слову
schema.show(player) // по схеме (переменная объявлена в примере выше)
На данный момент реализован только реактивный прогрессбар (меняете поля на сервере - меняются на клиентах), работает на экране игрока и в мире (если указать offsetZ != 0.0
).
Создание на языка java
ReactiveProgress.builder() // примеры того, что можно настроить
.position(Position.BOTTOM) // указать стартовую позицию снизу
.offsetY(32) // сместить вверх на 32 "пикселя"
.hideOnTab(false) // не скрывать при нажатии таба
.color(GlowColor.GREEN) // поставить зеленый цвет, можно поставить любой RGB, например через Tricolor
.build();
Чтобы показать его группе игроков и подписать их на обновление, используйте ReactiveProgress#send(vararg players: Player)
Отписать игроков от обновления - ReactiveProgress#unsubscribe(vararg players: Player)
Стереть прогресс у игроков и отписать от обновления - ReactiveProgress#delete(vararg players: Player)
Чтобы прогресс бар появился в мире, используйте поля offset, как координаты или в билдере вызовите location(origin: Location)
Как реактивно менять данные прогрессбара?
- Уровень прогресса, вызовите
ReactiveProgress#setProgress(value: Double)
(число от 0 до 1) - Строку над прогрессом, вызовите
ReactiveProgress#setTest(test: String)
Selection
Choicer
- Чтобы загрузить свою текстуру, используйте
Anime#loadTextures
илиAnime#loadTexture
(возвращает texture). - Если вы хотетите сменить валюту, то нужно изменить поле
vault
вSelection
на символ валюты или путь к текстуре - Если нужно закрыть меню, используйте
Anime#close
, которое закрывает любое окно игрока, кроме меню достижений и настроек.
Пример для языка kotlin
val menu = selection {
title = "Прокачки" // Название слева сверху
money = "Ваш баланс 999" // Баланс справа сверху, если не указать - монетка не покажется
hint = "Взять" // При наведении на предмет, будет отображаться этот текст
rows = 3 // Количество строчек
columns = 4 // Количество колонок
buttons( // Указание кнопок, если кнопок будет больше чем вмещается, меню станет многостраничным
button {
texture = "minecraft:textures/items/apple.png" // Текстура namespace:path
price = 88L // Цена предмета, если не указать - монета не покажется
title = "Куриные\nкрылья" // Название предмета (есть поддержка \n)
description = "+10 силы" // Комментарий предмета (есть поддержка \n)
onClick { player, index, button -> // Обработка нажатия по кнопке
player.sendMessage("Button id: $index, button $button")
}
},
button {
texture = Sprites.DUO.path() // Текстура с двойным режимом
price = 88L // Цена предмета, если не указать - монета не покажется
title = "Куриные\nкрылья" // Название предмета (есть поддержка \n)
},
button {
item = ItemStack(Material.HEAD) // Добавление иконки кнопки по предмету
price = 99
hint = "Продать" // Надпись при наведении на кнопку
title = "Картошка"
description = "+гниль"
hover = "Привет" // Всплывающий текст при наведении
}.sale(60) // Указать скидку 60%
)
}
menu.open(player) // Открываем меню игроку, можно сохранять меню в переменную и показывать много раз
// Кнопки можно менять реактивно после открытия меню, ставите title и она меняется у игроков
Пример для языка java
Selection menu = new Selection(
"Прокачки",
"Баланс 999",
"Купить",
4, // Количество строчек
3, // Количество колонок
new ReactiveButton()
.texture("minecraft:textures/items/apple.png")
.price(999)
.title("Название")
.description("описание")
.hover("Текст при наведении")
.onClick((player, index, button) -> {
player.sendMessage("Button id: $index, button $button");
}),
new ReactiveButton().material(Material.HEAD).sale(30) // Иконка по предмету/материалу, скидка 30%
);
menu.open(player);
Пример для языка kotlin
var menu = Reconnect { player -> player.sendMessage("Возвращаем вас в игру!") } // Первый вариант конструктора
menu = Reconnect(90) { player -> player.sendMessage("Возвращаем вас в игру!") } // Теперь указываем сколько секунд осталось
menu = Reconnect("Продать почку", 90, "Продать") { player -> player.sendMessage("Возвращаем вас в игру!") } // Теперь указываем все что только можно
menu.open(player) // Открываем меню игроку
Позволяет удостовериться, в корректности мотивов игрока. Поддерживает несколько строк.
Пример для языка kotlin
val menu = Confirmation("Купить Уганду за", "602 рубля", "Или все же нет?") { player -> // Через запятую указываем строки сообщения меню
player.sendMessage("Успешная покупка!") // Пишем что делать при подтверждении игроком
}
menu.open(player) // Открываем меню игроку
Пример для языка java
Confirmation menu = new Confirmation(Arrays.asList("Купить Уганду за", "602 рубля", "Или все же нет?"), player -> { // Через запятую указываем строки сообщения меню
player.sendMessage("Успешная покупка!") // Пишем что делать при подтверждении игроком
});
menu.open(player); // Открываем меню игроку
Панели над хотбаром, их можно указывать сколько угодно, вся ширина делится в таком же отношении, в котором делится текст ко всему тексту. Можно реактивно менять текст/прогресс/цвет, стратегию реализуйте сами хук в сеттер/по таймингу.
val moneyPanel = ReactivePanel.builder()
.color(GlowColor.GOLD)
.progress(0.5)
.text(Emoji.COIN + " " + stat?.money)
.build()
moneyPanel.send(player)
Что представляет из себя батлпасс?
Это приобретаемый набор заданий, за выполнение которых даются внутриигровые вещи.
val battlePass = BattlePass.new(399) {
pages = arrayListOf(
BattlePassPageAdvanced(
// Первая страница баттлпасса
300, // Опыт для прохождения уровня
10, // Цена скипа уровня
listOf(
// Список обычных предметов
NameTag.TAG1.getIcon(),
ArrowParticle.SLIME.getIcon(),
ArrowParticle.WATER_DROP.getIcon(),
StepParticle.SLIME.getIcon(),
Mask.HOUSTON.getIcon(),
KillMessage.GLOBAL.getIcon(),
MoneyKit.SMALL.getIcon(),
ArrowParticle.FALLING_DUST.getIcon(),
ArrowParticle.SPELL_INSTANT.getIcon(),
Mask.JASON.getIcon(),
),
listOf(
// Список премиум предметов
NameTag.TAG3.getIcon(),
Mask.TRADEGY.getIcon(),
Mask.HORROR.getIcon(),
MoneyKit.SMALL.getIcon(),
NameTag.TAG28.getIcon(),
Mask.COMEDY_MASK.getIcon(),
KillMessage.END.getIcon(),
ArrowParticle.REDSTONE.getIcon(),
StepParticle.REDSTONE.getIcon(),
Corpse.G1.getIcon(),
),
),
BattlePassPageAdvanced(
// Вторая страница баттлпасса
600,
20,
listOf(
KillMessage.DEAD.getIcon(),
MoneyKit.SMALL.getIcon(),
Mask.SCREAM.getIcon(),
NameTag.TAG10.getIcon(),
StepParticle.REDSTONE.getIcon(),
KillMessage.ROOM.getIcon(),
Mask.DALLAS.getIcon(),
NameTag.TAG16.getIcon(),
Mask.CREWMATE_LIME.getIcon(),
MoneyKit.NORMAL.getIcon()
),
listOf(
MoneyKit.SMALL.getIcon(),
KillMessage.SLEEP.getIcon(),
Mask.CREWMATE_WHITE.getIcon(),
NameTag.TAG17.getIcon(),
Mask.JASON.getIcon(),
ArrowParticle.VILLAGER_ANGRY.getIcon(),
NameTag.TAG20.getIcon(),
MoneyKit.NORMAL.getIcon(),
Mask.CREWMATE_PURPLE.getIcon(),
StepParticle.VILLAGER_ANGRY.getIcon()
),
)
)
sale(50.0) // Скидка 50%
facade.tags.add("Выполняйте квесты - получайте призы!")
facade.tags.add("BattlePass завершится в 01.04.2022")
}
BattlePass.send(player, battlePass)
BattlePass.show(player, battlePass, BattlePassUserData(100, false))
Редкость предмета берется из NBTTag
предмета rare
(DropRare), а статус собранности награды taken
(Boolean).
Можно указывать награду на каждый уровень, требуемый опыт.
Что представляет из себя баннер?
Это голограмма, рисунок в мире, маркер и прочее что является прямоугольником в мире.
Banners.new {
motionType = MotionType.CONSTANT // тип движения
watchingOnPlayer = false // смотрит ли баннер вечно на игрока
motionSettings = hashMapOf(
"yaw" to 0.0,
"pitch" to 0.0
) // магия
content = "" // текст на прямоугольнике
x = 0.0 // координата x
y = 0.0 // координата y
z = 0.0 // координата z
height = 100 // высота
weight = 100 // ширина
texture =
"" // текстура, например minecraft:apple (не забывайте что текстуру можно загружать через мод по web-ссылке)
red = 0 // красный цвет прямоугольника
green = 0 // зеленый цвет прямоугольника
blue = 0 // синий цвет прямоугольника
opacity = 0.62 // степерь прозрачности, от 0.0 до 1.0
}
Что за типы движения?
STEP_BY_TARGET
- баннер следует за сущностью
PERIODIC
- баннер двигается по периодичной траектории
CONSTANT
- баннер не двигается
Как создать Banner?
// Kotlin пример
Banners.new { // Баннер добавится в список баннеров и когда игроки будут заходить, они его увидят
// Элементарная заменя характеристик (указана лишь часть)
x = 1.0
y = 100.0
z = 1111000.0
texture = "minecraft:apple"
red = 255
opacity = 1.0
content = "Этот текст -\nмногострочный"
height = 120
weight = 200
// Продвинутые методы для замены свойств
location(player.location) // Указание места баннера через локацию
eyeLocation(player.bedSpawnLocation) // Указание поворота через точку, на которую нужно смотреть
target(player, 0.0, 0.0, 2.1) // Теперь баннер следует за игроком, баннер выше игрока на 2.1 блока
shineBlocks(false) // Чтобы баннер не просвечивался через стены
}
// Java пример
Banner banner = Banner.S.builder() // Создание баннера
.x(location.x)
.y(location.y+50)
.z(location.z)
.opacity(0.0) // Прозрачность, от 0.0 до 1.0
.height(50) // Высота
.weight(50) // Ширина
.content("§aЯ голограмма","§eОчень красивая","§cИ приятная глазу") // Многострочный текст
.watchingOnPlayer(true) // Указание, чтобы баннер всегда смотрел на игрока
.build();
Banners.add(banner); // Добавление в список баннеров, которые будут показаны игроку при входе на сервер
Методы утилиты Banners
:
Banners.new(data: Banner.() -> Unit): Banner
kotlin версия конструктора для баннера (пример выше)
Banners.new(banner: Banner): Banner
java версия конструктора для баннера (пример выше)
Banners.content(player: Player, uuid: UUID, content: String)
отправка нового текста игроку на баннер
Banners.content(player: Player, banner: Banner, content: String)
тоже самое, но через объект баннера
Banners.show(player: Player, vararg uuid: UUID)
показать игроку все указанные баннеры по uuid (баннеры уже есть в
списке глобальных)
Banners.show(player: Player, vararg banner: Banner)
показать игроку все баннеры (они могут не быть в списке баннеров и
созданы через конструктор Banner
)
Banners.remove(uuid: UUID)
удалить баннер из списка (не удаляет баннер игрокам)
Banners.hide(player: Player, vararg uuid: UUID)
удалить игроку все указанные баннер(ы)
Banners.hide(player: Player, vararg banner: Banner)
тоже самое, но через объекты класса Banner
Список доступных индикаторов (enum Indicators
):
HEALTH
индикатор здоровья над инвентарем,
EXP
индикатор уровня,
HUNGER
индикатор голода игрока,
ARMOR
индикатор брони,
VEHICLE
индикатор здоровья управляемого моба (свинья, лощадь...),
TAB
список игроков при нажатии TAB
HOT_BAR
9 слотов инвенторя игрока снизу экрана
AIR_BAR
воздух под водой
POTIONS
активные эффекты зелий
HAND
правая рука
NAME_TEMPLATE
ники игроков
Методы взаимодействия с клиентом:
Anime.hideIndicator(player: Player, vararg indicator: Indicators)
скрывает игроку указанные через запятую
индикаторы
Anime.showIndicator(player: Player, vararg indicator: Indicators)
показывает игроку указанные через запятую
индикаторы
Методы взаимодействия с клиентом:
Anime.sphere(to: Player, uuid: UUID, location: Location, color: Color, radius: Double)
создать сферу по указанным
координатам с указанным радиусом
Anime.sphere(to: Player, uuid: UUID, location: Location, color: Color, sX: Double, sY: Double, sZ: Double)
создать
сферу по указанным координатам с указанными размерами
Anime.teleportSphereTo(to: Player, uuid: UUID, location: Location)
телепортировать сферу
Anime.removeSphere(to: Player, uuid: UUID)
удалить сферу
Anime.moveSphereTo(to: Player, uuid: UUID, location: Location, time: Double)
плавно переместить сферу по указанным
координатам за указанное количество секунд
val chat = ModChat(
UUID.randomUUID(), //Id чата
"Системный чат", //Название
"С" //Символ на кнопке (можно несколько символов)
)
MultiChat.createKey("system", chat) //Делаем удобный ключ для использования
//Делаем обработку сообщений
MultiChat.registerHandler(chat) { player, message ->
player.sendMessage("Ты отправил в этот чат: $message")
}
MultiChat.sendChats(player, "system") //Отправляем игроку чат
MultiChat.sendMessage(player, "system", "Ты крутой сынок!") //Отправляем игроку сообщение
MultiChat.removeChats(player, "system") //Удаляем у игрока чат
Методы взаимодействия с клиентом:
MultiChat.createKey(id: String, chat: ModChat)
создать ключ для удобного использования чата
MultiChat.removeKey(chat: ModChat)
удалить ключ
MultiChat.removeKey(key: String)
удалить ключ по ключу чата
MultiChat.removeKey(id: UUID)
удалить ключ по id чата
MultiChat.registerHandler(id: UUID, consumer: BiConsumer<Player, String>)
добавить обработчик для сообщений, которые приходят в чат по id чата
MultiChat.registerHandler(key: String, consumer: BiConsumer<Player, String>)
добавить обработчик для сообщений, которые приходят в чат по ключу чата
MultiChat.sendChats(player: Player, vararg chats: ModChat)
отправить игроку сообщение все указанные чаты
MultiChat.sendChats(player: Player, vararg chats: UUID)
отправить игроку сообщение все указанные чаты по их id
MultiChat.sendChats(player: Player, vararg chats: String)
отправить игроку сообщение все указанные чаты по их ключу
MultiChat.removeChats(player: Player, vararg chats: ModChat)
удалить игроку все указанные чаты
MultiChat.removeChats(player: Player, vararg chats: UUID)
удалить игроку все указанные чаты по их id
MultiChat.removeChats(player: Player, vararg chats: String)
удалить игроку все указанные чаты по их ключу
MultiChat.sendMessage(player: Player, chat: ModChat, message: String)
отправить игроку сообщение в чат
MultiChat.sendMessage(player: Player, chat: UUID, message: String)
отправить игроку сообщение в чат по id чата
MultiChat.sendMessage(player: Player, chat: String, message: String)
отправить игроку сообщение в чат по ключу чата
MultiChat.broadcast(players: Collection<Player>, chat: ModChat, message: String)
отправить указанным игрокам сообщение в чат
MultiChat.broadcast(chat: ModChat, message: String)
отправить всем игрокам на сервере сообщение в чат
MultiChat.broadcast(players: Collection<Player>, chat: UUID, message: String)
отправить указанным игрокам сообщение в чат по id чата
MultiChat.broadcast(chat: UUID, message: String)
отправить всем игрокам на сервере сообщение в чат по id чата
MultiChat.broadcast(players: Collection<Player>, chat: UUID, message: String)
отправить указанным игрокам сообщение в чат по id чата
MultiChat.broadcast(chat: UUID, message: String)
отправить всем игрокам на сервере сообщение в чат по id чата
MultiChat.broadcast(players: Collection<Player>, chat: String, message: String)
отправить указанным игрокам сообщение в чат по ключу чата
MultiChat.broadcast(chat: String, message: String)
отправить всем игрокам на сервере сообщение в чат по ключу чата
Цвета:
- Цвета состоят из трез целых чисел и одного дробного, красный [0 .. 255], синий [0 .. 255], зеленый [0 .. 255], прозрачность [0 .. 1.0]
- Вы можете еспользовать
enum GlowColor
с готовыми частыми цветами или создать свойTricolor()
Методы взаимодействия с клиентом:
Glow.set(player: Player, red: Int, blue: Int, green: Int, alpha: Double)
ставит игроку свечение
Glow.set(player: Player, color: RGB)
то же самое, но через RGB
Glow.animate(player: Player, seconds: Double, red: Int, blue: Int, green: Int, alpha: Double)
плавное появление и
скрытие свечения с указанием длительности этой анимации
Glow.animate(player: Player, seconds: Double, color: RGB, alpha: Double)
то же самое, но через RGB
Glow.animate(player: Player, seconds: Double, red: Int, blue: Int, green: Int)
то же самое, но без указания
прозрачности (без прозрачности)
Glow.animate(player: Player, seconds: Double, color: RGB)
то же самое, но через RGB
Методы добавления места в глобальный список мест (не показывает игрокам!):
Glow.addPlace(red: Int, blue: Int, green: Int, x: Double, y: Double, z: Double, onJoin: (Player) -> Unit)
Glow.addPlace(red: Int, blue: Int, green: Int, x: Double, y: Double, z: Double)
Glow.addPlace(color: RGB, x: Double, y: Double, z: Double, onJoin: (Player) -> Unit)
Glow.addPlace(color: RGB, x: Double, y: Double, z: Double)
Glow.addPlace(place: GlowingPlace, onJoin: Consumer<Player>)
Glow.addPlace(place: GlowingPlace)
Методы показа мест игроку:
Glow.showPlace(player: Player, place: GlowingPlace)
показать игроку место (место может быть не глобальным!)
Glow.showLoadedPlace(player: Player, uuid: UUID)
показать игрку глобальное место по UUID
Glow.showAllPlaces(player: Player)
показать игроку все глобальные места
Очистка мест:
Glow.removePlace(place: GlowingPlace, vararg players: Player)
удаление места для указанных игроков и удаление из
списка глобальных
Glow.clearPlaces(vararg players: Player)
очистить указанным игрокам места и удалить все места их глобального
списка
Показывает текущее состояние бустера игрока.
Пример для языка Kotlin:
val boosterBar = boosterBar {
segments = listOf()
title = ""
subtitle = ""
isShowBackground = false
progress = 1.0
}
boosterBar.open(player)
Показывает текущее состояние бустера игрока.
Метод:
Anime.startBoosters(player: Player, vararg boosters: Booster)
показать индикатор
Методы:
Anime.reload(player: Player, seconds: Int, text: String)
простая перезарядка, с розовым цветом
Anime.reload(player: Player, seconds: Int, text: String, color: RGB, alpha: Double)
с использованием
RGB
Anime.reload(player: Player, seconds: Int, text: String, red: Int, green: Int, blue: Int, alpha: Double)
с любым
цветом в RGB
Методы:
Устаревший метод, который выводит сообщение только в правом нижнем углу:
Anime.bottomRightMessage(player: Player, text: String)
поставить справа снизу информацию игроку (строка делится
по \n
)
Anime.bottomRightMessage(player: Player, vararg text: String)
тоже самое, но удобнее, так как можно указать строки
через запятую
Новый метод, который выводит в 4 разных угла:
Anime.overlayText(player: Player, position: Position, text: String)
позволяет размещать текст в разные стороны экрана (строка делится
по \n
)
Anime.overlayText(player: Player, position: Position, vararg text: String)
тоже самое, но удобнее, так как можно указать строки
через запятую
Методы:
Npc.npc(init: NpcData.() -> Unit): NpcSmart
kotlin вариант конструктора для создания NPC (только через него можно
сделать глобальных NPC, которых плагин отправляет игроку при заходе на сервер)
Npc.link(data: NpcData): NpcSmart
java версия (делает тоже самое)
Npc.spawn(entityId: Int)
показать всем игрокам заранее созданный NPC по entityId (оно есть в NpcData)
Npc.kill(entityId: Int)
спрятать всем игрокам заранее созданный NPC по entityId (оно есть в NpcData)
Npc.hide(entityId: Int, player: Player)
скрыть конкретному игроку NPC по entityId
Npc.show(entityId: Int, player: Player)
показать конкретному игроку NPC по entityId
Npc.clear()
удалить всем игрокам всех NPC (не чистит глобальный список NPC)
NpcSmart:
var data: NpcData, // данные на клиенте
var click: Consumer<PlayerUseUnknownEntityEvent>? = null, // обработчик нажатия на NPC
private var leftArm: ItemStack? = null, // предмет в левой руке
private var rightArm: ItemStack? = null, // предмет в правой руке
private var head: ItemStack? = null, // предмет на голове
private var chest: ItemStack? = null, // предмет на груди
private var legs: ItemStack? = null, // предмет на ножках
private var feet: ItemStack? = null, // предмет на стопах
NpcSmart.slot(slot: EquipmentSlot, itemStack: ItemStack): NpcSmart
метод обновляющий слот NPC для всех игроков
NpcSmart.slot(slot: EquipmentSlot, itemStack: ItemStack, player: Player): NpcSmart
метод обновляющий слот NPC для
конкретного игрока
NpcSmart.kill(): NpcSmart
скрыть всем игрокам этого NPC
NpcSmart.show(player: Player): NpcSmart
показать игроку этого NPC
NpcSmart.hide(player: Player): NpcSmart
скрыть игроку этого NPC
NpcSmart.update(player: Player): NpcSmart
обновить игроку данные NPC (имя, координаты, поворот головы, положение тела
- сидеть, спать, шифтить)
NpcSmart.swingArm(mainHand: Boolean, player: Player): NpcSmart
пошевелить рукой, указав главная ли рука и игрока-получателя
NpcSmart.spawn(player: Player): NpcSmart
создать игроку NPC <--- Только тут игроку создается с нуля NPC
NpcData:
var id: Int = (Math.random() * Int.MAX_VALUE).toInt(), // entityId NPC
val uuid: UUID = UUID.randomUUID(), // UUID
var x: Double = 0.0,
var y: Double = 0.0,
var z: Double = 0.0,
var type: Int = 1000, // тип сущности 1000 - ИГРОК
var name: String? = null, // имя
var behaviour: NpcBehaviour = NpcBehaviour.NONE, // модель поведения NPC
var pitch: Float = 0f,
var yaw: Float = 0f,
var skinUrl: String? = null, // ссылка на скин
var skinDigest: String? = null, // ЕСЛИ ВЫ СТАВИТЕ ССЫЛКУ, ТО СТАВЬТЕ DIGEST (например uuid.toString())
var slimArms: Boolean = false, // тонкие руки
var sneaking: Boolean = false, // шифтит
var sleeping: Boolean = false, // спит
val sitting: Boolean = false, // сидит
val activationDistance: Int = -1 // -1 = без ограничения по блоком, любое другое число большее, чем 0 - радиус, в котором начинает смотреть на игрока
NpcData.onClick(init: Consumer<PlayerUseUnknownEntityEvent>)
при клике на NPC обработать событие
NpcData.location(location: Location)
указать координаты NPC через локацию
Модели поведения:
NONE
- нет поведения
STARE_AT_PLAYER
- следит за игроком
STARE_AND_LOOK_AROUND
- смотрит на игрока
Пример на Kotlin:
npc {
x = 1337.0
y = 225.5
z = -102.3
behaviour = NpcBehaviour.STARE_AND_LOOK_AROUND
onClick {
it.player.sendMessage("Хай")
}
}.slot(EquipmentSlot.HAND, CraftItemStack.asNMSCopy(ItemStack(Material.DIAMOND_BLOCK))).spawn()
Методы взаимодействия с клинтом:
Dialog.sendDialog(player: Player, dialog: Dialog)
отправляет игру ветку диалогов
Dialog.openDialog(player: Player, dialogId: String)
показывает игроку диалог по ID ветки
Dialog.dialog(player: Player, dialog: Dialog, openEntrypoint: String)
отправляет и показывает игроку диалог по ID
ветки
Структура диалога:
Dialog
состоит из набора точек входа Entrypoint
, в одном хранятся Screen
, это конкоетно что видит игрок - набор
строк и кнопок Button
с действиями Action
, например открытия нового окна. Набор Action вы можете увидить
в enum Actions
Пример кода:
new Dialog(new Entrypoint(
"id",
"название",
new Screen(а
"Первая видимая строка",
"Вторая видимая строка"
).buttons(
new Button("Имя кнопки").actions(
new Action (Actions.COMMAND).command("/next"),
new Action (Actions.CLOSE)
),
new Button ("Войти в очередь").actions(
Action("/hub"),
new Action (Actions.CLOSE)
)
)
))
Показ окна наград:
DailyRewardMenu.builder()
.rewards(buttons...) // 7 кнопок с наградами
.taken(false) // награда еще не взята
.currentDay(1) // текучая награда - это первый день (начинается с 1)
.build()
.open(player); // открыть игроку
func:reward:click
в этот канал прийдет callback, а именно - номер для (int), словить его можно с Anime#createReader
, УЯЗВИМОСТЬ игрок может написать чит, и сам отправлять в данный канал инфу, проверяйте, может ли игрок получить награду!
Уведомления возникают у игрока справа сверху, можно отправлять несколько, уведомление содержит UUID отправителя, в нем
может быть максимум 2 кнопки. Данная возможность не зависит от модулей animation-api
, мод предустановлен
через bungeecord
всем игрокам (Исходники мода cristalix-socials
не будут добавлены в данный репозиторий).
Для начала рассмотрим создание кнопок уведомлений:
Alert.button( // метод возвращает кнопку
"Принять", // сообщение на кнопке
"/ok", // команда при нажатии
GlowColor.GOLD, // цвет кнопки
false, // после нажатия удалится ли эта кнопка
true // после нажатия скрыть все уведомление
)
Всего есть два подхода к отправке уведомлений игроку:
1. Генерация уведомления прямо на месте отправки
2. Создание шаблона уведомления, затем замена контента под ситуацию, отправка
Рассмотрим первый вариант:
Alert.send(
player = player, // кому отослать
text = string, // сообщение
millis = long, // милисекунд до скрытия уведомления (1000 = 1 секунда)
frontColor = glowColor, // внешний цвет
backGroundColor = glowColor, // цвет фона
chatMessage = string, // сообщение в чате
buttons: notificationButton... // кнопки (максимум две) (vararg)
)
Рассмотрим второй вариант:
- Создание шаблона уведомления
Alert.put(
"hello", // ключ
NotificationData( // содержимое
null,
"notify",
"Привет %nick%,\nкак дела?",
toRGB(GlowColor.GREEN),
toRGB(GlowColor.GREEN),
3000,
listOf(
button("Принять", "/ok", GlowColor.GOLD) // создание кнопки
),
"Вам уведомление!"
)
)
- Получение шаблона, замена текста, отправка:
Alert.find("hello") // найти шаблон по ключу
.replace("%nick", player.name) // заменить placeholder
.replace("как дела?", "как спалось?")
.send(player) // отправить игроку
Метод показа дропа с лутбокса:
Anime.openLootBox(player: Player, vararg items: LootDrop)
открыть игроку лутбокс с указанием всего что выпало в виде
объектов LootDrop(сам предмет, название предмета, <НЕ ОБЯЗАТЕЛЬНО ПО УМАЛЧАНИЮ COMMON> редкость)
Редкости предметов:
enum class DropRare(val title: String, val color: String) {
COMMON("Обычный", "§a"),
RARE("Редкий", "§9"),
EPIC("Эпический", "§5"),
LEGENDARY("Легендарный", "§6"), ;
fun getColored(): String {
return "$color$title" // метод для получения окрашенного имени редкости
}
}
Список экранов окончания игр (enum EndStatus
):
WIN
экран победы,
LOSE
экран поражения,
DRAW
экран ничьей,
Методы вызова экрана окончания игры:
Anime.showEnding(player: Player, endStatus: EndStatus, key: String, value: String)
показывает экран окончания игроку,
с информацией. key
- сообщения слева, value
- сообщения справа. Используйте \n \n
- для переноса строки.
Anime.showEnding(player: Player, endStatus: EndStatus, key: List<String>, value: List<String>)
показывает экран
окончания игроку. key
- список строк слева, value
- список строк справа.
Пример использования:
Anime.showEnding(
player,
EndStatus.WIN,
listOf("Убийств:", "Смертей:"),
listOf("${user.kills}", "${user.deaths}")
)
Указаниие более чем 5 полей приведёт к нечитабельности статистки!
!!! ВАЖНО !!!
Это частный случай Banners, класс помечен @Deprecated
, новый инструмент способен делать намного больше, рекомендуем
испольовать его
Что такое маркер? Это случайный UUID генерируемый в конструкторе, координаты в текущем мире игрока x, y, z, размер
текстуры и путь к текстуре на клиенте (обычно через resource-pack, но можно загрузить игроку текстуру через метод в
разделе "Прочее"). Для упращения работы с текстурами, вынесено несколько базовых в enum MarkerSign
:
FINE("textures/others/znak_v_3.png")
ERROR("textures/others/znak_v_2.png")
WARNING("textures/others/znak_v_1.png")
QUESTION_FINE("textures/others/z1.png")
QUESTION_ERROR("textures/others/z2.png")
QUESTION_WARNING("textures/others/z1.png")
ARROW_DOWN("mcpatcher/cit/others/badges/arrow_down.png")
ARROW_UP("mcpatcher/cit/others/badges/arrow_up.png")
ARROW_RIGHT("mcpatcher/cit/others/badges/arrow_right.png")
ARROW_LEFT("mcpatcher/cit/others/badges/arrow_left.png")
Пример маркера: new Marker(225, 1, 5, 10, MarkerSign.ERROR)
Методы взаимодействия с клинтом:
Anime.marker(player: Player, marker: Marker)
показать маркер игроку
Anime.markers(player: Player, vararg markers: Marker)
показать игроку кучу маркеров
Anime.clearMarkers(player: Player)
очистить все маркеры у игрока
Anime.removeMarker(player: Player, marker: Marker)
удалить игроку маркер по маркеру
Anime.removeMarker(player: Player, uuid: UUID)
удалить игроку маркер по UUID маркера
Anime.moveMarker(player: Player, marker: Marker)
обновить нахождение маркера у игрока
Anime.moveMarker(player: Player, marker: Marker, seconds: Double)
обновить нахождение маркера у игрока анимированно за
указанное время
Anime.moveMarker(player: Player, uuid: UUID, toX: Double, toY: Double, toZ: Double, seconds: Double)
обновить
нахождение маркера у игрока анимированно за указанное время имея только UUID маркера
Anime.topMessage(player: Player, message: String)
сообщение сверху (поддерживает цвета и многострочные сообщения)
Anime.title(player: Player, text: String)
многострочный title, поддерживает все цвета, а так же \n
Anime.title(player: Player, vararg text: String)
то же самое, но строчк можно указывать через запятую или можно
передавать массив
Anime.alert(player: Player, title: String, description: String, seconds: Double)
отправить сообщение с комментарием с
указанием времени
Anime.alert(player: Player, title: String, description: String)
отправить сообщение с комментарием (на 7.3
секунды)
Anime.alert(player: Player, description: String, seconds: Double)
отправить отписание с сообщением "Внимание" с
указанием вреени
Anime.alert(player: Player, description: String)
комментарий с сообщением "Внимание" на 7.3 секунды
Anime.cursorMessage(player: Player, message: String)
сообщения появляющиеся на курсоре и летящие вниз
Anime.itemTitle(player: Player, item: ItemStack, title: String?, subtitle: String?, duration: Double)
отправить на
экран предмет с указанием продолжительности в секундах (можно указать сообщения сверху и снизу)
Anime.counting321(player: Player)
начать на экране игрока отсчет 3 2 1 GO!
Anime.bigAlert(player: Player, message: String)
отправляет текст на экран над хотбаром
Anime.killboardMessage(player: Player, text: String)
отправить в правую верхнюю часть экрана сообщение
Anime.killboardMessage(player: Player, text: String, topMargin: Int)
отправить в правую верхнюю часть экрана сообщение
с указанием отсупа сверху
Anime.timer(player: Player, text: String, duration: Int, red: Int, blue: Int, green: Int)
начать игроку отчсет сверху
с сообщением, длительностью, цветом полоски
Anime.timer(player: Player, text: String, duration: Int)
начать отсчет сверху с сообщением и продолжительностью
Anime.timer(player: Player, duration: Int)
начать отсчет с сообщением по умолчанию
Есть 3 типа сообщений: FINE, WARN, ERROR. Каждое сообщение имеет свой цвет и символ в начале
Anime.systemMessage(player: Player, messageStatus: MessageStatus, text: String)
отправить игроку системное сообщение
Anime.corpse(to: Player, name: String?, uuid: UUID, x: Double, y: Double, z: Double, secondsAlive: Int = 60)
создать
труп по UUID трупа игрока с Cristalix и координат локации (имя трупу указывать необязательно) (secondsAlive - количество
секунд через которое исчезнет труп)
Anime.corpse(to: Player, name: String?, skinUrl: String, x: Double, y: Double, z: Double, secondsAlive: Int = 60)
то
же самое, но можно ставить скин трупу по любой ссылке (имя трупу указывать необязательно)
Anime.clearAllCorpses(player: Player)
очистить игроку все трупы
Закрыть игроку активное меню (кроме настроек и меню ачивок):
Anime.close(player: Player)
Отправить пустой Buffer игроку в канал:
Anime.sendEmptyBuffer(channel: String, player: Player)
Персонализация:
Anime.lockPersonalization(player: Player)
заблокировать персонализацию игроку
Anime.unlockPersonalization(player: Player)
разблокировать персонализацию игроку
Загрузка фотографий:
Anime.loadTexture(player: Player, url: String)
загрузить игроку фотографию по ссылке в папку cache/animation/
Anime.loadTextures(player: Player, vararg url: String)
загрузить игроку фотографии по ссылкам в
папку cache/animation/
Когда игрок скачает фотографию/и от отправит серверу пустой буффер в канал func:loaded
Пример получения фото на моде: ResourceLocation.of("cache/animation", "файл.png")
Регистрация канала для входящих сообщений:
createReader(channel: String, listener: PluginMessageListener)