Иконка ресурса

TextAligner - расширение для PlaceholderAPI для выравнивания текста 1.1.0

Создайте и подтвердите аккаунт для скачивания

MrDrag0nXYT

Вопросы по форуму решаю **ТОЛЬКО** на форуме
Модератор
Пользователь
Сообщения
3 445
Решения
108
Веб-сайт
drakoshaslv.ru
MrDrag0nXYT добавил(а) новый ресурс:

You must be logged in to see this link. - Простое расширение для PlaceholderAPI для красивого выравнивания текста по центру или правой стороне

TextAligner​

Простое расширение для PlaceholderAPI для настройки выравнивания текста по центру или правой стороне​


💾 Системные требования

  • Java 16+
  • Paper 1.16.5+


🚀 Начало работы

  1. Установите плагин PlaceholderAPI и...

You must be logged in to see this link.
 
MrDrag0nXYT добавил(а) новый ресурс:

You must be logged in to see this link. - Простое расширение для PlaceholderAPI для красивого выравнивания текста по центру или правой стороне



You must be logged in to see this link.
Молодец, а теперь учись писать быстрый код, держи diff - разберешься

Код:
Subject: [PATCH] Оптимизации от HomaPlus
---
Index: src/main/java/plus/tson/utl/uns/UnsafeUtils.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/main/java/plus/tson/utl/uns/UnsafeUtils.java b/src/main/java/plus/tson/utl/uns/UnsafeUtils.java
new file mode 100644
--- /dev/null    (revision 893b5cab64ef64f8233174b29c939f65e3c6f2d1)
+++ b/src/main/java/plus/tson/utl/uns/UnsafeUtils.java    (revision 893b5cab64ef64f8233174b29c939f65e3c6f2d1)
@@ -0,0 +1,137 @@
+package plus.tson.utl.uns;
+
+import sun.misc.Unsafe;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+
+
+/**
+ * Legacy unsafe utils, used when jdk.internal.misc.Unsafe is not available
+ */
+public class UnsafeUtils {
+    //object reference size
+    public static final int REF_SIZE;
+    public static final int REF_SIZE_D2;
+    public static final int REF_SIZE_M2;
+    public static final Unsafe UNSAFE;
+    //string `bytes` offset
+    public static final long STR_OFFSET;
+
+    static {
+        try {
+            Field field = Unsafe.class.getDeclaredField("theUnsafe");field.setAccessible(true);
+            UNSAFE = (Unsafe) field.get(null);
+            REF_SIZE = UNSAFE.addressSize();
+            REF_SIZE_D2 = REF_SIZE>>1;
+            REF_SIZE_M2 = REF_SIZE<<1;
+            STR_OFFSET = offset(String.class, "value");
+        } catch (Throwable e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+
+    /**
+     * Atomically updates Java variable to {@code cur} if it is currently
+     * holding {@code expected}.
+     *
+     * <p>This operation has memory semantics of a {@code volatile} read
+     * and write.  Corresponds to C11 atomic_compare_exchange_strong. See {@link Unsafe#compareAndSwapObject}
+     *
+     * @return {@code true} if successful
+     */
+    public static boolean compareAndSwap(Object ref, long offset, Object prev, Object cur){
+        return UNSAFE.compareAndSwapObject(ref, offset, prev, cur);
+    }
+
+
+    /**
+     * Atomically updates Java variable to {@code cur} if it is currently
+     * holding {@code expected}.
+     *
+     * <p>This operation has memory semantics of a {@code volatile} read
+     * and write.  Corresponds to C11 atomic_compare_exchange_strong. See {@link Unsafe#compareAndSwapInt}
+     *
+     * @return {@code true} if successful
+     */
+    public static boolean compareAndSwap(Object ref, long offset, int prev, int cur){
+        return UNSAFE.compareAndSwapInt(ref, offset, prev, cur);
+    }
+
+
+    /**
+     * @return Offset of object field {@code name} in {@code clazz}
+     */
+    public static long offset(Class<?> clazz, String name){
+        try {
+            return UNSAFE.objectFieldOffset(clazz.getDeclaredField(name));
+        } catch (NoSuchFieldException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+
+    /**
+     * @return Offset of object/static field {@code name} in {@code clazz}
+     */
+    public static long offsetS(Class<?> clazz, String name){
+        try {
+            Field field = clazz.getDeclaredField(name);
+            if(Modifier.isStatic(field.getModifiers()))
+                return UNSAFE.staticFieldOffset(field);
+            return UNSAFE.objectFieldOffset(field);
+        } catch (NoSuchFieldException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+
+    /**
+     * Unsafe create string without copy bytes
+     */
+    public static String stringOf(byte[] bytes) {
+        try {
+            String str = (String) UNSAFE.allocateInstance(String.class);
+            UNSAFE.putObject(str, STR_OFFSET, bytes);
+            return str;
+        } catch (InstantiationException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+
+    /**
+     * @return reference to string bytes, {@link String#value}
+     */
+    public static byte[] getBytes(String str){
+        return (byte[]) UNSAFE.getObject(str, STR_OFFSET);
+    }
+
+
+    /**
+     * Unsafe get object field value
+     */
+    public static <T> T get(Object src, long offSet){
+        return (T) UNSAFE.getObject(src, offSet);
+    }
+
+
+    /**
+     * Unsafe set object field value
+     */
+    public static void set(Object src, long offSet, Object value){
+        UNSAFE.putObject(src, offSet, value);
+    }
+
+
+    /**
+     * Unsafe allocate instance
+     */
+    public static <T> T newObj(Class<T> clazz){
+        try {
+            return (T) UNSAFE.allocateInstance(clazz);
+        } catch (InstantiationException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
\ No newline at end of file
Index: src/main/java/zxc/mrdrag0nxyt/TextAlignerPlaceholder.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/main/java/zxc/mrdrag0nxyt/TextAlignerPlaceholder.java b/src/main/java/zxc/mrdrag0nxyt/TextAlignerPlaceholder.java
--- a/src/main/java/zxc/mrdrag0nxyt/TextAlignerPlaceholder.java    (revision 75246872a10866082ee2a674dbf793cc12b732e3)
+++ b/src/main/java/zxc/mrdrag0nxyt/TextAlignerPlaceholder.java    (revision 893b5cab64ef64f8233174b29c939f65e3c6f2d1)
@@ -5,24 +5,27 @@
 import org.bukkit.OfflinePlayer;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
+import plus.tson.utl.uns.UnsafeUtils;
+
 
 public final class TextAlignerPlaceholder extends PlaceholderExpansion {
-    private static final String SPACE = " ";
-
     @Override
     public @NotNull String getIdentifier() {
         return "textaligner";
     }
 
+
     @Override
     public @NotNull String getAuthor() {
-        return "MrDrag0nXYT (drakoshaslv)";
+        return "MrDrag0nXYT, HomaPlus";
     }
+
 
     @Override
     public @NotNull String getVersion() {
-        return "1.0.0";
+        return "1.1.0";
     }
+
 
     @Override
     public boolean persist() {
@@ -30,44 +33,67 @@
     }
 
 
-    /*
-     * %textaligner_center;<length>;<Text with {placeholder}>%
-     * %textaligner_right;<length>;<Text with {placeholder}>%
+    /**
+     * %textaligner_center;length;Text with {placeholder}%
+     * <br>
+     * %textaligner_right;length;Text with {placeholder}%
      */
     @Override
     public @Nullable String onRequest(OfflinePlayer player, @NotNull String paramsString) {
-        String[] params = paramsString.split(";", 3);
-        if (params.length < 3) return null;
+        String[] params;
+        if ((params = paramsString.split(";", 3)).length < 3) return null;
 
         return switch (params[0]) {
-            case "center" -> handleTextAlign(player, params, SpaceStyle.CENTER);
-            case "right" -> handleTextAlign(player, params, SpaceStyle.RIGHT);
+            case "center" -> handleTextAlign(player, params, true);
+            case "right" -> handleTextAlign(player, params, false);
             default -> null;
         };
     }
 
 
-    private @Nullable String handleTextAlign(OfflinePlayer player, String @NotNull [] params, SpaceStyle spaceStyle) {
-        try {
-            int maxLength = Integer.parseInt(params[1]);
-            String parsed = PlaceholderAPI.setBracketPlaceholders(player, params[2]);
+    /**
+     * @return указанную длину строки, либо длину самой строки
+     * <br>
+     * Позволяет использовать заглушки типа `#####` для указания длины.
+     * <br>
+     * Так же данный способ будет работать значительно быстрее, тк не выделяет память в куче + нет исключений
+     */
+    private static int getMaxLen(String str) {
+        byte[] bytes;
+        int sum = 0;
+        for (byte b : bytes = UnsafeUtils.getBytes(str)) {
+            if (b >= '0' && b <= '9')
+                sum = sum * 10 + b - '0';
+            else
+                return bytes.length;
+        }
+        return bytes.length;
+    }
 
-            int diff = maxLength - parsed.length();
-            if (diff <= 0) return parsed;
 
-            int spaces = switch (spaceStyle) {
-                case CENTER -> diff / 2;
-                case RIGHT -> diff;
-            };
+    private static String handleTextAlign(OfflinePlayer player, String @NotNull [] params, boolean center) {
+        String result = PlaceholderAPI.setBracketPlaceholders(player, params[2]);
 
-            return SPACE.repeat(spaces) + parsed;
+        int spaces;
+        if ((spaces = getMaxLen(params[1]) - result.length()) <= 0) return result;
 
-        } catch (NumberFormatException ignored) {
-        }
-        return null;
-    }
+        if(center) spaces >>= 1;
+
+        byte[] bResultPre = UnsafeUtils.getBytes(result),
+               bResult    = new byte[bResultPre.length + spaces];
+
+        //заполняю пробелами
+        for (int i = 0; i < bResultPre.length; i++)
+            bResult[i] = ' ';
+
+        //Переиспользую экземпляр строки из параметров
+        //строку-результат PAPI использовать небезопасно, тк может быть константой
+        result = params[0];
 
-    private enum SpaceStyle {
-        CENTER, RIGHT
+        //Собираю результирующую строку
+        System.arraycopy(bResultPre, 0, bResult, spaces, bResultPre.length);
+        UnsafeUtils.set(result, UnsafeUtils.STR_OFFSET, bResult);
+
+        return result;
     }
 }
\ No newline at end of file

Мог бы заворочаться и бонусом избавится еще и от создания string[3] на 44-ой строке но мне лень
 
Последнее редактирование:
Молодец, а теперь учись писать быстрый код, держи diff - разберешься

Код:
Subject: [PATCH] Оптимизации от HomaPlus
---
Index: src/main/java/plus/tson/utl/uns/UnsafeUtils.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/main/java/plus/tson/utl/uns/UnsafeUtils.java b/src/main/java/plus/tson/utl/uns/UnsafeUtils.java
new file mode 100644
--- /dev/null    (revision 893b5cab64ef64f8233174b29c939f65e3c6f2d1)
+++ b/src/main/java/plus/tson/utl/uns/UnsafeUtils.java    (revision 893b5cab64ef64f8233174b29c939f65e3c6f2d1)
@@ -0,0 +1,137 @@
+package plus.tson.utl.uns;
+
+import sun.misc.Unsafe;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+
+
+/**
+ * Legacy unsafe utils, used when jdk.internal.misc.Unsafe is not available
+ */
+public class UnsafeUtils {
+    //object reference size
+    public static final int REF_SIZE;
+    public static final int REF_SIZE_D2;
+    public static final int REF_SIZE_M2;
+    public static final Unsafe UNSAFE;
+    //string `bytes` offset
+    public static final long STR_OFFSET;
+
+    static {
+        try {
+            Field field = Unsafe.class.getDeclaredField("theUnsafe");field.setAccessible(true);
+            UNSAFE = (Unsafe) field.get(null);
+            REF_SIZE = UNSAFE.addressSize();
+            REF_SIZE_D2 = REF_SIZE>>1;
+            REF_SIZE_M2 = REF_SIZE<<1;
+            STR_OFFSET = offset(String.class, "value");
+        } catch (Throwable e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+
+    /**
+     * Atomically updates Java variable to {@code cur} if it is currently
+     * holding {@code expected}.
+     *
+     * <p>This operation has memory semantics of a {@code volatile} read
+     * and write.  Corresponds to C11 atomic_compare_exchange_strong. See {@link Unsafe#compareAndSwapObject}
+     *
+     * @return {@code true} if successful
+     */
+    public static boolean compareAndSwap(Object ref, long offset, Object prev, Object cur){
+        return UNSAFE.compareAndSwapObject(ref, offset, prev, cur);
+    }
+
+
+    /**
+     * Atomically updates Java variable to {@code cur} if it is currently
+     * holding {@code expected}.
+     *
+     * <p>This operation has memory semantics of a {@code volatile} read
+     * and write.  Corresponds to C11 atomic_compare_exchange_strong. See {@link Unsafe#compareAndSwapInt}
+     *
+     * @return {@code true} if successful
+     */
+    public static boolean compareAndSwap(Object ref, long offset, int prev, int cur){
+        return UNSAFE.compareAndSwapInt(ref, offset, prev, cur);
+    }
+
+
+    /**
+     * @return Offset of object field {@code name} in {@code clazz}
+     */
+    public static long offset(Class<?> clazz, String name){
+        try {
+            return UNSAFE.objectFieldOffset(clazz.getDeclaredField(name));
+        } catch (NoSuchFieldException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+
+    /**
+     * @return Offset of object/static field {@code name} in {@code clazz}
+     */
+    public static long offsetS(Class<?> clazz, String name){
+        try {
+            Field field = clazz.getDeclaredField(name);
+            if(Modifier.isStatic(field.getModifiers()))
+                return UNSAFE.staticFieldOffset(field);
+            return UNSAFE.objectFieldOffset(field);
+        } catch (NoSuchFieldException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+
+    /**
+     * Unsafe create string without copy bytes
+     */
+    public static String stringOf(byte[] bytes) {
+        try {
+            String str = (String) UNSAFE.allocateInstance(String.class);
+            UNSAFE.putObject(str, STR_OFFSET, bytes);
+            return str;
+        } catch (InstantiationException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+
+    /**
+     * @return reference to string bytes, {@link String#value}
+     */
+    public static byte[] getBytes(String str){
+        return (byte[]) UNSAFE.getObject(str, STR_OFFSET);
+    }
+
+
+    /**
+     * Unsafe get object field value
+     */
+    public static <T> T get(Object src, long offSet){
+        return (T) UNSAFE.getObject(src, offSet);
+    }
+
+
+    /**
+     * Unsafe set object field value
+     */
+    public static void set(Object src, long offSet, Object value){
+        UNSAFE.putObject(src, offSet, value);
+    }
+
+
+    /**
+     * Unsafe allocate instance
+     */
+    public static <T> T newObj(Class<T> clazz){
+        try {
+            return (T) UNSAFE.allocateInstance(clazz);
+        } catch (InstantiationException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
\ No newline at end of file
Index: src/main/java/zxc/mrdrag0nxyt/TextAlignerPlaceholder.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/src/main/java/zxc/mrdrag0nxyt/TextAlignerPlaceholder.java b/src/main/java/zxc/mrdrag0nxyt/TextAlignerPlaceholder.java
--- a/src/main/java/zxc/mrdrag0nxyt/TextAlignerPlaceholder.java    (revision 75246872a10866082ee2a674dbf793cc12b732e3)
+++ b/src/main/java/zxc/mrdrag0nxyt/TextAlignerPlaceholder.java    (revision 893b5cab64ef64f8233174b29c939f65e3c6f2d1)
@@ -5,24 +5,27 @@
 import org.bukkit.OfflinePlayer;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
+import plus.tson.utl.uns.UnsafeUtils;
+
 
 public final class TextAlignerPlaceholder extends PlaceholderExpansion {
-    private static final String SPACE = " ";
-
     @Override
     public @NotNull String getIdentifier() {
         return "textaligner";
     }
 
+
     @Override
     public @NotNull String getAuthor() {
-        return "MrDrag0nXYT (drakoshaslv)";
+        return "MrDrag0nXYT, HomaPlus";
     }
+
 
     @Override
     public @NotNull String getVersion() {
-        return "1.0.0";
+        return "1.1.0";
     }
+
 
     @Override
     public boolean persist() {
@@ -30,44 +33,67 @@
     }
 
 
-    /*
-     * %textaligner_center;<length>;<Text with {placeholder}>%
-     * %textaligner_right;<length>;<Text with {placeholder}>%
+    /**
+     * %textaligner_center;length;Text with {placeholder}%
+     * <br>
+     * %textaligner_right;length;Text with {placeholder}%
      */
     @Override
     public @Nullable String onRequest(OfflinePlayer player, @NotNull String paramsString) {
-        String[] params = paramsString.split(";", 3);
-        if (params.length < 3) return null;
+        String[] params;
+        if ((params = paramsString.split(";", 3)).length < 3) return null;
 
         return switch (params[0]) {
-            case "center" -> handleTextAlign(player, params, SpaceStyle.CENTER);
-            case "right" -> handleTextAlign(player, params, SpaceStyle.RIGHT);
+            case "center" -> handleTextAlign(player, params, true);
+            case "right" -> handleTextAlign(player, params, false);
             default -> null;
         };
     }
 
 
-    private @Nullable String handleTextAlign(OfflinePlayer player, String @NotNull [] params, SpaceStyle spaceStyle) {
-        try {
-            int maxLength = Integer.parseInt(params[1]);
-            String parsed = PlaceholderAPI.setBracketPlaceholders(player, params[2]);
+    /**
+     * @return указанную длину строки, либо длину самой строки
+     * <br>
+     * Позволяет использовать заглушки типа `#####` для указания длины.
+     * <br>
+     * Так же данный способ будет работать значительно быстрее, тк не выделяет память в куче + нет исключений
+     */
+    private static int getMaxLen(String str) {
+        byte[] bytes;
+        int sum = 0;
+        for (byte b : bytes = UnsafeUtils.getBytes(str)) {
+            if (b >= '0' && b <= '9')
+                sum = sum * 10 + b - '0';
+            else
+                return bytes.length;
+        }
+        return bytes.length;
+    }
 
-            int diff = maxLength - parsed.length();
-            if (diff <= 0) return parsed;
 
-            int spaces = switch (spaceStyle) {
-                case CENTER -> diff / 2;
-                case RIGHT -> diff;
-            };
+    private static String handleTextAlign(OfflinePlayer player, String @NotNull [] params, boolean center) {
+        String result = PlaceholderAPI.setBracketPlaceholders(player, params[2]);
 
-            return SPACE.repeat(spaces) + parsed;
+        int spaces;
+        if ((spaces = getMaxLen(params[1]) - result.length()) <= 0) return result;
 
-        } catch (NumberFormatException ignored) {
-        }
-        return null;
-    }
+        if(center) spaces >>= 1;
+
+        byte[] bResultPre = UnsafeUtils.getBytes(result),
+               bResult    = new byte[bResultPre.length + spaces];
+
+        //заполняю пробелами
+        for (int i = 0; i < bResultPre.length; i++)
+            bResult[i] = ' ';
+
+        //Переиспользую экземпляр строки из параметров
+        //строку-результат PAPI использовать небезопасно, тк может быть константой
+        result = params[0];
 
-    private enum SpaceStyle {
-        CENTER, RIGHT
+        //Собираю результирующую строку
+        System.arraycopy(bResultPre, 0, bResult, spaces, bResultPre.length);
+        UnsafeUtils.set(result, UnsafeUtils.STR_OFFSET, bResult);
+
+        return result;
     }
 }
\ No newline at end of file

Мог бы заворочаться и бонусом избавится еще и от создания string[3] на 44-ой строке но мне лень
offtop а вы знаете толк в извращениях
 
offtop Юзать unsafe это -реп. Не нужно рекомендовать этого делать и темболее самому использовать. Иначе получишь такиеже утечки по памяти, как в Hytale, в котором тоже решили, что они умнее сборщика мусора
 
offtop Юзать unsafe это -реп. Не нужно рекомендовать этого делать и темболее самому использовать. Иначе получишь такиеже утечки по памяти, как в Hytale, в котором тоже решили, что они умнее сборщика мусора
Если руки из жопы - утечки памяти можно устроить и без unsafe
Далеко за примером ходить не надо - все Форки Spigot с типичной утечкой памятью, и их нужно перезапускать каждые 12-24 часа

В правильных руках это очень мощный и вполне безопасный инструмент
 
В правильных руках это очень мощный и вполне безопасный инструмент
offtop Ага, поэтому он называется Unsafe и любой разраб, который у тебя это увидит, посчитает тебя странным. Это не является хорошей практикой, джава сама справляется со сборкой мусора
 
Ага, поэтому он называется Unsafe и любой разраб, который у тебя это увидит, посчитает тебя странным. Это не является хорошей практикой, джава сама справляется со сборкой мусора
offtop
Чел, не тебе меня учить. Я как минимум писал еще до существования этого форума + выпустил #1 по производительности ядро
Закрывать возможности к ручному управлению памятью - с точки зрения языка уже не хорошая практика

Безопасное использования "Небезопасного" - является вполне эффективным способом внешней оптимизации, когда не могу вручную управлять тем что под капотом
Повсеместно его использовать так же не стоит - это крайняя мера, когда прямой доступ закрыт

Правильное и ручное управление памятью - всегда было фундаментом для действительно быстрого кода
На пару-тройку (или десяток) копий массивов байтов меньше, как и аллигаций мусора - оперативке лучше
Особенно когда это массово

Конечно это не сравнится с тем ужасом что под капотом ядра и капля в море погоды не сделает
Но все-же ~
 
Последнее редактирование:
Чел, не тебе меня учить. Я как минимум писал еще до существования этого форума + выпустил #1 по производительности
offtop Сделав что-то, ты не становишься гением, который не может допускать банальных ошибок, не нужно считать себя всегда правильным


Безопасное использования "Небезопасного" - является вполне эффективным способом внешней оптимизации
offtop Без комментариев, тут впринципе сам на себя ответил. Небезопасность не нужно использовать
 
Вам необходимо зарегистрироваться для просмотра изображений-вложений

Реализовал выравнивание в топах на голограммах. Можно ли его сделать ещё ровнее, или это максимум?
 
Вам необходимо зарегистрироваться для просмотра изображений-вложений

Реализовал выравнивание в топах на голограммах. Можно ли его сделать ещё ровнее, или это максимум?
А без цифр из РП такой же результат получается?
 
А без цифр из РП такой же результат получается?
Добавь в вырывание поддержку Unicode пробелов разного типа, тогда можно выравнивать прям до малейших изменений. По сути эти все картинки это разные юникод символы автоматически сгенерированные oraxen/nexo/itemsadder, и в некоторых из них есть свои пробелы и расстояния от самого символа до границы.

У меня просто была похожая ситуация и правильные пробелы их решили. Хотя, мб, тут по-другому работает.
 
По сути эти все картинки это разные юникод символы автоматически сгенерированные oraxen/nexo/itemsadder, и в некоторых из них есть свои пробелы и расстояния от самого символа до границы.
Хороший вопрос как это конкретно тут реализовать, эти картинки могут быть самой разной длины
 
Хороший вопрос как это конкретно тут реализовать, эти картинки могут быть самой разной длины
Картинки да, но не юникоды. На стороне сервера юникод остается одинаковой длины. Людям самим уже придётся настраивать и смащять картинки на стороне рп. В Nexo даже есть специальный плейсхолдер для смещения. По факту без рп все ОК, а с рп там просто картинка появляется на месте юникода. А это уже не твои проблемы, а проблемы выравнивания и смещения на стороне рп.
 
Хороший вопрос как это конкретно тут реализовать, эти картинки могут быть самой разной длины
Ну да, у тебя в общем нельзя выравнивать нормально чтобы один плейсхолдер был ровно слева, один справа. Например чтобы ники красиво смотрелись в топах. (1.21.11 - 1.16.5).
Вам необходимо зарегистрироваться для просмотра изображений-вложений
 
Назад
Сверху Снизу