- 102
- 66
chat_emoji 3.2
смайлы и иконки чата Arizona RP в mimgui, шрифте ImGui и диалогах SA-MP
смайлы и иконки чата Arizona RP в mimgui, шрифте ImGui и диалогах SA-MP
Библиотека, чтобы рисовать в своих mimgui-окнах те же смайлы и иконки, что показывает чат Arizona RP. Всё вытащено из
_chat.asi.Всего 1598 картинок. Из них 1354 идут ровно в том же порядке, что и в панели смайлов чата, плюс 244 скрытых: они в панель не попали, но в игре рисуются. Это ленты и плашки под ники, короны VIP, крылья уровней, значки лавок и недвижимости, телефоны, деньги, оружие, логотипы с биржи, плашки «ВИП ЧАТ», «АДМИН», «РЕКЛАМА».
Те же иконки работают и в обычных диалогах SA-MP. Там их рисует сам плагин чата, так что от скрипта нужна только строка с токеном.
Что нового в 3.2
Аризона обновила
_chat.asi (сборка от 08.09), и в нём появилось 14 новых иконок: Telegram, VK и логотипы компаний с биржи. Набор пересобран под эту версию. Заодно плагин отдал имя apple логотипу Apple, так что яблоко теперь ищется как red_apple. Такие имена из базы Unicode есть теперь у всех стандартных смайлов, и от плагина они не зависят.Что ещё добавилось:
- смайлы можно вшить прямо в шрифт ImGui, и тогда они работают в обычных
imgui.Button,Combo, вкладках и вInputTextпрямо при наборе, подробнее ниже; - поиск понимает русский: «огонь», «сердце», «дигл». Слова взяты из Unicode CLDR, для иконок Аризоны дописаны руками. С неправильной раскладкой тоже ищет,
jujymнайдёт огонь; - атлас больше не округляется до 2048×2048 и занимает 10.6 МБ видеопамяти вместо 16. Для смайлов до 20 px есть малый атлас на 2.7 МБ, раньше мелкие рябили.
emoji.FORMAT = 'dxt5'сжимает обе текстуры ещё вчетверо; toUnicodeиfromUnicodeпереводят токены в настоящие смайлы и обратно. Пригодится, если пересылаешь чат в Telegram-бота или из него в игру;emoji.weapon(id)отдаёт иконку оружия по номеру GTA,crown(n)иwings(n)короны и крылья с цифрой от 1 до 10;- библиотека сама замечает, что Аризона обновила плагин, и пишет в
moonloader.log, что пора запуститьrebuild.bat.
Что исправлено:
drawText,drawImageиdraw3Dв 3.1 молча ничего не рисовали, аtooltipTextпоказывал токены текстом. Функции mimgui это C-функции,type()у них'cdata', а проверка ждала'function'. Поймал, когда гонял всё в игре.sendChatотправлял все части длинного сообщения в одном кадре, и антифлуд съедал всё, кроме первой. Теперь между частями пауза 1.1 секунды.- Лаунчер Аризоны грузит плагин чата и на чужих серверах, поэтому «плагин загружен» ещё не значит, что смайлы увидят все.
sendChatтеперь смотрит на название сервера и на чужом вырезает токены. reload()после пересборки мог получить старую текстуру от соседнего скрипта. Теперь в ключ общей текстуры входит хеш атласа.
Старые скрипты трогать не нужно, API 3.1 работает как раньше. Чтобы обновиться, замени
lib/chat_emoji.lua и всю папку resource/chat_emoji.Почему нельзя просто подключить шрифт
Первое, что приходит в голову: скормить
seguiemj.ttf в AddFontFromFileTTF. Не сработает, причём по двум независимым причинам.mimgui собран со
stb_truetype, а он рисует только контуры и ничего не знает про цветные глифы COLR/CPAL. Так устроены и Segoe UI Emoji, и шрифт иконок Arizona: базовый контур у них часто пустой, вся картинка лежит в цветных слоях. Получишь пустые прямоугольники.Вторая причина добивает:
ImWchar в mimgui шестнадцатибитный, а смайлы живут выше U+FFFF. Такие коды в него физически не влезают.Поэтому всё заранее растеризовано в PNG-атлас. В игре он грузится текстурой D3D9 (пул MANAGED, device reset переживает) и рисуется через
imgui.Image и draw list.Обойти это для обычных виджетов всё-таки можно. ImGui 1.74, на котором собран mimgui, умеет «свои глифы»: прямоугольник в атласе шрифта, привязанный к любой кодовой точке. Библиотека кладёт туда картинки из своего атласа на коды начиная с
U+E700. В 16 бит они помещаются, а stb_truetype для них вообще не нужен.Смайлы в шрифте ImGui
Lua:
imgui.OnInitialize(function()
emoji.load()
emoji.fontGlyphs() -- в шрифт по умолчанию
end)
imgui.Button(emoji.toGlyphs('Купить :u1f3c6:'))
if imgui.BeginTabItem(emoji.toGlyphs(':u1f50d: Поиск')) then ... end
-- поле ввода: смайл из панели вставляется глифом и виден сразу
emoji.inputText('##msg', buf, { glyphs = true })
sampSendChat(u8:decode(emoji.fromGlyphs(ffi.string(buf))))
Звать из
OnInitialize, пока mimgui не создал текстуру шрифта. Смайл красится цветом текста: при белом цвета родные, в TextDisabled он тусклый. Атлас шрифта у каждого скрипта свой, и все 1598 смайлов при 14 px добавляют к нему около 2 МБ. Если нужны не все, передай фильтр:
Lua:
emoji.fontGlyphs(nil, { filter = function(e) return e.catId == 'smileys' end })
Смайлы в диалогах SA-MP
Тот же токен работает в обычном
sampShowDialog: в заголовке, в тексте, в строках списка, в колонках таблицы.Я разобрал плагин дизассемблером, чтобы это проверить, а не поверить на слово. Разборщик токенов у него один на всё: функция ищет в строке двоеточия и разбирает то, что между ними. Вызывается она из двух мест сразу, из отрисовки чата и из перехваченных GUI-классов SA-MP, на которых собран диалог. Плагин вешает хуки на методы этих классов, считает разметку и кеширует её по контролу, сбрасывая при смене текста.
Вывод простой: для диалогов ни текстура, ни mimgui не нужны. Нужна только строка.
Lua:
local emoji = require 'chat_emoji'
emoji.loadTable() -- только таблицы, без PNG и D3D
emoji.dialog(1234,
'Главное меню ' .. emoji.tok('arz'),
emoji.tok('trophy') .. ' Статистика\n' ..
emoji.tok('moneybag') .. ' Баланс\n' ..
emoji.tok('gear') .. ' Настройки',
'Выбрать', 'Отмена', 2) -- 2 = DIALOG_STYLE_LIST
Колонки в
TABLIST разделяются табуляцией и работают как обычно:
Lua:
emoji.dialog(1235, 'Игроки',
'Ник\tУровень\tДеньги\n' ..
emoji.tok('crown') .. ' Nick_Name\t12\t1.250.000 ' .. emoji.tok('cash'),
'Открыть', 'Назад', 5)
emoji.dialog это тонкая обёртка над sampShowDialog с тем же порядком аргументов. Делает она две вещи: гонит текст через emoji.fit() и не отправляет текст длиннее лимита, а возвращает false, причина. Длинный текст диалога обрезается молча, и ловить это в бою неприятно. Обёртка не обязательна, sampShowDialog с токеном в строке работает и напрямую.Кто увидит смайлы. Токен это обычный текст, и там, где его некому нарисовать, он таким и останется. Тут две разные ситуации.
emoji.fit(text) проверяет, загружен ли плагин чата у тебя, и годится для того, что видишь только ты: диалоги, локальные сообщения. emoji.fit(text, 'server') смотрит на название сервера и нужен для того, что уйдёт другим игрокам. Разница появилась не просто так: лаунчер Аризоны грузит плагин и на чужих серверах, и раньше остальные видели в чате :u1f603:.Про кодировку. Токен состоит из цифр и латиницы, поэтому cp1251 его не портит. Перекодировать надо только свой русский текст, ровно как обычно.
Установка
Код:
moonloader/lib/chat_emoji.lua
moonloader/resource/chat_emoji/ (вся папка)
Папок
lib и resource/chat_emoji может не быть, создай сам. Пути жёстко зашиты, модуль ищет файлы именно там. Малый атлас и файл русского поиска необязательны: без них мелкие смайлы берутся из большого атласа, а поиск понимает только латиницу.В архиве лежат четыре примера, клади что-то одно:
chat_emoji_showcase.lua, команда/emjnew: всё новое из 3.2, а/emjtourпрогоняет автотур, его удобно записыватьchat_emoji_minimal.lua, команда/sm: пять способов применения в полсотни строкchat_emoji_demo.lua, команда/emj: панель выбора и витрина размеровchat_emoji_dialogs.lua, команда/emjd: конструктор диалогов
Быстрый старт
Lua:
local imgui = require 'mimgui'
local emoji = require 'chat_emoji'
imgui.OnInitialize(function()
local ok, err = emoji.load()
if not ok then print(err) end
end)
-- дальше в отрисовке окна:
emoji.image('smiley', 24) -- просто картинка
if emoji.button('arz', 32) then end -- кнопка
emoji.text('hello :u1f603: {FF4040}bye :u1f44b:', 18, { color = true, wrap = 300 })
local picked = emoji.picker('grid', { size = 24, height = 300 })
if picked then emoji.sendChat(emoji.encode(picked)) end
emoji.unload() в onScriptTerminate больше звать не обязательно: с 3.1 библиотека сама отпускает текстуру, когда скрипт выгружается.Если тебе нужны только диалоги,
mimgui можно не подключать вовсе. Хватит emoji.loadTable(), её можно звать откуда угодно, хоть из main().Нужна определённая версия, проверь на старте:
assert(emoji.atLeast('3.2')). Файл библиотеки один на все скрипты, и кто-нибудь рано или поздно положит поверх старый.Что ещё умеет
Lua:
-- поиск: по-русски, по-английски, с неправильной раскладкой
emoji.find('огонь', 10)
-- поверх игры без окна и метка над точкой в мире
emoji.drawText(20, 340, 'Таймер :u23f3: 05:00', { size = 22 })
emoji.draw3D(x, y, z + 1, ':u1f451: босс', { size = 24 })
-- кнопка, строка списка и подсказка со смайлами
emoji.textButton('Купить :u1f3c6:', { id = 'buy' })
emoji.selectable(':u23f0: Часы', selected == 1, { id = 'row1' })
emoji.tooltipText('Осталось :u23f3: 5 минут')
-- иконки игры
emoji.weapon(24) -- Desert Eagle
emoji.crown(3) -- корона с цифрой 3
emoji.wings(10) -- крылья с цифрой 10
-- Telegram, VK, Discord
local tg = emoji.toUnicode(u8(lineFromGame)) -- токены в настоящие смайлы
local ingame = emoji.fromUnicode(messageFromTg) -- и обратно
API
Загрузка:
| функция | что делает |
|---|---|
emoji.loadTable([dir]) | только имена и коды, без текстуры и mimgui |
emoji.load([dir]) | то же плюс текстуры, из OnInitialize |
emoji.reload(), emoji.unload() | перечитать после пересборки, освободить |
emoji.atLeast(version) | не старее ли библиотека нужной версии |
emoji.checkSource() | свежий ли атлас для этого _chat.asi |
Поиск и токены:
| функция | что делает |
|---|---|
emoji.get(key) | запись по имени, коду, токену или самой записи |
emoji.encode(key), emoji.tok(key) | токен :uXXXX: |
emoji.find(query, [limit]) | поиск, лучшие совпадения первыми |
emoji.category(id) | записи категории по названию или короткому имени: 'hidden_weapons', 'hidden_brands' |
emoji.weapon(id), crown(n), wings(n) | иконки оружия, корон и крыльев |
emoji.stylize(text) | латиница и цифры «плитками» из панели чата |
emoji.parse(str) | разбор строки на куски текста и смайлы |
Текст, чат и диалоги:
| функция | что делает |
|---|---|
emoji.dialog(id, cap, text, b1, b2, style) | sampShowDialog с проверкой длины |
emoji.sendChat(text, [opts]) | в чат: режет под лимит и делает паузу между частями |
emoji.fit(text, [target]) | оставить токены или вырезать: для себя или для всех |
emoji.canRender(), serverSupports() | загружен ли плагин у меня, Аризона ли сервер |
emoji.strip(text) | убрать токены вместе с лишним пробелом |
emoji.expand(text), decode(text) | :trophy: в :u1f3c6: и обратно |
emoji.measure(text), split(text) | сколько байт и токенов в строке, порезать не разрывая токены |
emoji.toUnicode(text), fromUnicode(text) | мост с Telegram, VK, Discord |
Отрисовка:
| функция | что делает |
|---|---|
emoji.image(key, size), button(key, size) | картинка и кнопка |
emoji.width(key, size) | какую ширину займёт при высоте size |
emoji.text(str, [size], [opts]) | текст со смайлами, переносом и цветами сервера |
emoji.drawText(x, y, str), draw3D(x, y, z, str) | поверх игры и в мире |
emoji.textButton, selectable, tooltipText | виджеты со смайлами в подписи |
emoji.picker(id, [opts]), pickerButton, inputText | панель выбора с поиском, избранным и недавними |
emoji.fontGlyphs(), toGlyphs, fromGlyphs | смайлы в шрифте ImGui |
Поля:
emoji.list (все записи по порядку), emoji.byName, emoji.byCp, emoji.categories, emoji.strings (подписи панели, можно переопределить).Искать можно по имени, коду и синонимам.
emoji.get('<3') найдёт сердце, emoji.get('red_apple') яблоко. Полный список функций лежит в README на GitHub, а в VS Code с расширением Lua работают подсказки типов.Размер
Задаётся при каждой отрисовке, ничем не зафиксирован. Аргумент это высота в пикселях, ширина считается сама.
Lua:
emoji.image('arz', 16)
emoji.image('arz', mySlider[0]) -- хоть каждый кадр разный
Ячейка основного атласа 40 px, поэтому до 40 картинка идеальна, до 64 разницы почти не видно, дальше начинает мылить. До 20 px берётся малый атлас с ячейкой 20 px: ImGui рисует без mip-уровней, и сжатая втрое картинка рябила. Нужен крупнее, пересобери с
--cell 64.Широкие иконки. Плашки вроде «ВИП ЧАТ» или BUY бывают в несколько раз шире своей высоты. В атласе такие занимают несколько ячеек подряд, число лежит в
e.cells, и все функции рисуют с этим соотношением. Отдельно думать об этом не надо, но если считаешь раскладку руками, бери ширину через emoji.width(), а не size.Масштаб у всех иконок общий, так что при одном
size мелкий значок останется мелким, а крупный крупным, как в самом чате.Как вставить смайл в чат
Чат кодирует смайлы токеном
:uXXXX:, где XXXX это шестнадцатеричный код.
Lua:
emoji.sendChat('привет ' .. emoji.encode('smiley')) -- уйдёт как ':u1f603:'
Токен из цифр и латиницы, перекодировать его в cp1251 не нужно. Каждый токен съедает 8-9 байт лимита сообщения. Длинный текст
sendChat режет сам и отправляет частями с паузой, так что антифлуд их не съест.Сборка под себя
В репозитории лежит
rebuild.bat. Двойной клик, и он сам найдёт Python, поставит Pillow, найдёт _chat.asi рядом с игрой, распакует из него шрифты и списки, возьмёт тот же fontcustom\seguiemj_1.45.ttf, что использует чат, и соберёт оба атласа.
Код:
rebuild.bat --asi "C:\...\bin\arizona\_chat.asi"
rebuild.bat --emoji "C:\Windows\Fonts\seguiemj.ttf"
Python нужен только для сборки. В игре он не нужен, там работает готовый PNG. Когда Аризона в следующий раз обновит плагин, библиотека сама напишет об этом в лог.
Что учесть
- MoonLoader запускает каждый скрипт в своей Lua-машине, но текстуры всё равно одни на процесс. Кто загрузил первым, тот их создал, остальные берут готовые по указателю через переменную окружения, а время жизни считает сам COM. Так что десяток скриптов с библиотекой это по-прежнему около 13 МБ видеопамяти на все.
- Иконки из второго шрифта (группа «Скрытые: интерфейс (big_icons)») в чат токеном, скорее всего, не вставятся: текст чата рисуется другим шрифтом. В своих окнах они работают нормально.
- Файлы модуля и описания атласа целиком в ASCII, названия категорий и русские слова для поиска записаны escape-последовательностями. Пересохранять их можно в любой кодировке, ничего не сломается.
Исходники
GitHub · история изменений
Последнее редактирование: