Исходник chat_emoji 3.2: смайлы и иконки чата Arizona RP в mimgui, шрифт ImGui и диалоги (1598 штук)

e11evated

Активный
Автор темы
102
66
chat_emoji 3.2
смайлы и иконки чата Arizona RP в mimgui, шрифте ImGui и диалогах SA-MP​




Библиотека, чтобы рисовать в своих mimgui-окнах те же смайлы и иконки, что показывает чат Arizona RP. Всё вытащено из _chat.asi.

Всего 1598 картинок. Из них 1354 идут ровно в том же порядке, что и в панели смайлов чата, плюс 244 скрытых: они в панель не попали, но в игре рисуются. Это ленты и плашки под ники, короны VIP, крылья уровней, значки лавок и недвижимости, телефоны, деньги, оружие, логотипы с биржи, плашки «ВИП ЧАТ», «АДМИН», «РЕКЛАМА».

Те же иконки работают и в обычных диалогах SA-MP. Там их рисует сам плагин чата, так что от скрипта нужна только строка с токеном.

picker.png
widgets.png




Что нового в 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.

search.gif
font.gif

game.gif
telegram.gif

hud.gif




Почему нельзя просто подключить шрифт

Первое, что приходит в голову: скормить 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 · история изменений
 
Последнее редактирование:

вайега52

cache miss
Модератор
3,084
3,416
MoonLoader запускает каждый скрипт в своей Lua-машине. Подключишь библиотеку в трёх скриптах, будет три копии текстуры, по 16 МБ видеопамяти каждая. Пара скриптов нормально, десяток уже перебор
подход с import в мунлоадере, либо DLL библиотека со шрифтами должны помочь в этом)
 
  • Нравится
Реакции: e11evated

e11evated

Активный
Автор темы
102
66
а можно перенести полностью аризоновский чат на клиент?
Технически да, _chat.asi это обычный клиентский ASI-плагин, сервер для работы ему не нужен.

Но есть ограничения:
  1. Только 0.3.7-R3.
  2. Некоторые функции работать не будут.
  3. Смайлы видят только те, у кого стоит плагин.