Система автоматической обработки команд чата [v1.4] [final]

Добавлен , опубликован
Алгоритмы, Наработки и Способности
Способ реализации:
cJass
Тип:
Наработка
Для большинства современных масштабных (как DotA) и не очень (например Legion TD) карт характерно наличие команд чата как основного метода управления игрой. Если команд немного, то с ними вполне можно справиться и обычным образом (создав кучу однотипных триггеров). Но когда кол-во команд начинает расти, то организовать их стает очень сложно. Самым очевидным решением для такой ситуации мне кажется создание системы учета этих самых команд. Однажды я попал в такую ситуацию, но у меня было решение, коим я и хочу поделиться с вами. Полная работоспособность данной системы не гарантируется, поэтому используйте ее с осторожностью.
Для чего нужна эта система
Как было сказано ранее, при большом кол-ве команд управлять ими становится неудобно. Эта система может спасти ваше положение и обеспечить безбедную старость простое управление оными.
Просто берем и добавляем команду в систему. Нужно указать только ее имя и функцию, которая будет её обрабатывать. Вот так:
CommandSystem_RegisterCommand("open", "OpenHandler");
Система поддерживает переименование команд и обмен их обработчиками. Добавление самих команд может происходить "на лету" (в т.ч после запуска карты), что позволяет создавать более гибкие сценарии.
Команды, вводимые в чате могут иметь параметры (доп. информацию). Чтобы использовать параметры, нужно написать имя команды и отделить их пробелами. Кол-во параметров в системе настраивается полем MaxParamCount. По умолчанию оно равно пяти.
Также существует Lite версия (обеспечивает только базовую функциональность)
Предупреждение
Данный продукт распространяется по принципу "как есть" и не несет какой-то дополнительной контекстуальной нагрузки. Каждый имеет право на его свободное использование и распространение. Автор не несет никакой ответственности за результаты работы этой программы. Используйте ее на свой страх и риск.
Убедительно прошу: при использовании сего чуда в своей карте указывайте автора ( DDarkSniper). Хотя бы в самом далеком месте
Код cJass
#include "cj_types.j"

/* === Система игровых команд ===
        ~ Автор: DDarkSniper
        ~ Дата: 8.05.2013
        ~ Дополнение: 3.01.2014
   Описание:
        Система игровых команд позволяет с удобством управлять 
        вводимыми с клавиатуры командами 
*/
    
library CommandSystem initializer init {

    define {
        CheckString(s) = (s != null and s != "")
    
        MaxParamCount   = 5    // макс. кол-во параметров для одной команды
        MaxCommandCount = 100  // макс. кол-во команд, хранимых в словаре системы
        MinInputLength  = 3    // мин. длина сообщения, в котором может содержаться команда (+1 для добавляемого пробела)
        
        PermissionException = "You have not enough permission to execute this command" 
        CommandException    = "Wrong command"
        DisabledException   = "Requested command is unavailable at this moment"
        
        CommandPrefix       = "-"  // префикс команды (знак ее начала)   
        
        ShowListeningMessage = true
        
        ListeningEnabled   = "Now you can type commands in your chat window"
        ListeningDisabled  = "You can not type any chat commands anymore"
        
        CommandKey = StringHash("Commands") // Ключ хранения обработчиков в хеш-таблице
        StateKey   = StringHash("State")
    }

    // ==== свойства ====
    public string Command;      // название команды, обрабатываемой в данный момент
    public player Source;       // источник (игрок, вызывавший команду)
        
    public bool   HasParams;                // были ли при вызове команды указаны параметры 
    public int    ParamCount;               // кол-во указанных параметров (0, если нет)

    public string Params[MaxParamCount];    // параметры вызова команды (отделяются пробелами)    
    
    // === доп.свйоства ===
    public trigger CommandDetector;     // триггер, отвечающий на команды (возможно переопределение)
    public int     CommandCount;        // кол-во зарегистрированных команд в системе
    
    
    // === поля (внутренние переменные) ===
    private hashtable database;
    private string command;
    private force RegistredPlayers;           // игроки, которые могут вводить команды
    
    // ==== методы ====
    // определение вывода сообщения игроку (можно переопределить для создания эффектов)
    private void DisplayMessage(player p, string text) {
        DisplayTextToPlayer(p, 0, 0, text);
    }
    
    /* [GetCommand] returns string
       | Описание: возвращает обработчик команды по ее имени
       | Параметры:
       |     ~ name [string] - имя команды (без префикса)
       | Пример вызова:
       |    string hide = CommandSystem_GetCommand("hide");
    */
    public string GetCommand(string name) {
        if (CheckString(name) and database != null) {
            return LoadStr(database, CommandKey, StringHash(name));
        } return null;
    }
    
    /* [ContainsCommand] returns boolean
       | Описание: показыавет, содержится ли команда с указанным именем в системе
       | Параметры:
       |     ~ name [string] - имя команды (без префикса)
       | Пример вызова:
       |    if (CommandSystem_ContainsCommand("shot")) { ... }
    */
    public bool HasCommand(string name) {
        return CheckString(GetCommand(name));
    }
        
    /* [HasAccess] returns boolean
       | Описание: показыавет, содержится ли команда с указанным именем в системе
       | Параметры:
       |     ~ name [string] - имя команды (без префикса)
       |     ~ p [player] - игрок, для которого проверяется наличие доступа
       | Пример вызова:
       |    if (CommandSystem_HasAccess("share", Player(0))) { ... }
    */
    public bool HasAccess(string name, player p) {
        if (HasCommand(name) and p != null) {
            // возвращается противоположное значение, т.к при отсутсвии записи нужно вернуть True
            return not LoadBoolean(database, StringHash("Access" + I2S(GetPlayerId(p))), StringHash(name));
        } return false;
    }
    
    /* [SetAccess] returns nothing
       | Описание: устанавливает состояние доступа для указанного игрока
       | Параметры:
       |     ~ name [string] - имя команды (без префикса)
       |     ~ p [player] - игрок, для которого устанавливается доступ
       |     ~ hasAccess [boolean] - получит ли игрок доступ 
       | Пример вызова:
       |    CommandSystem_SetAccess("vote", Player(0), false);
    */
    public void SetAccess(string name, player p, bool hasAccess) {
        if (HasCommand(name) and p != null) {
            SaveBoolean(database, StringHash("Access" + I2S(GetPlayerId(p))), StringHash(name), not hasAccess);
        }
    }
    
    /* [ResetAccess] returns nothing
       | Описание: сбрасывает настройки доступа всех игроков для указанной команды
       | Параметры:
       |     ~ name [string] - имя команды (без префикса)
       |     ~ value [boolean] - значение сброса
       | Пример вызова:
       |    CommandSystem_ResetAccess("vote", true);
    */
    boolean tvalue;
    public void ResetAccess(string name, bool value) {
        command = name;
        tvalue = value;
        ForForce(RegistredPlayers, lambda void function() {
            SetAccess(command, GetEnumPlayer(), tvalue); 
        });
    }
    
    /* [IsCommandEnabled] returns boolean
       | Описание: показыавет, активна ли команда в данный момент
       | Параметры:
       |     ~ name [string] - имя команды (без префикса)
       | Пример вызова:
       |    if (CommandSystem_IsCommandEnabled("give")) { ... }
    */
    public bool IsCommandEnabled(string name) {
        if (HasCommand(name)) {
            return not LoadBoolean(database, StateKey, StringHash(name));
        } return false;
    }
    
    /* [SetAccess] returns nothing
       | Описание: устанавливает состояние команды (включена\отключена)
       | Параметры:
       |     ~ name [string] - имя команды (без префикса)
       |     ~ enabled [boolean] - включена ли команда
       | Пример вызова:
       |    CommandSystem_SetCommandState("vote", false);
    */
    public void SetCommandState(string name, bool enabled) {
        if (HasCommand(name)) {
            SaveBoolean(database, StateKey, StringHash(name), not enabled);
        } 
    }
    
    /* [RegisterCommand] returns nothing
       | Описание: регистрирует команду в системе для определенных пользователей
       | Параметры:
       |     ~ name [string] - имя команды (без префикса)
       |     ~ handler [string] - название функции обработки команды 
       |     ~ target [force] - группа игрков, для которой регистрируется команда
       | Пример вызова:
       |    CommandSystem_RegisterCommand("jump", "JumpHandler");
    */
    public void RegisterCommandForce(string name, string handler, force target) {
        // строки должны быть значимыми
        if (CheckString(name) or CheckString(handler)) { 
            if (not HasCommand(name)) { 
                SaveStr(database, CommandKey, StringHash(name), handler);
                // установка доступа
                ResetAccess(name, (target == null));
                
                if (target != null) {
                    command = name;
                    ForForce(target, lambda void function() {
                        SetAccess(command, GetEnumPlayer(), true);
                    });
                    // используется данная переменная, т.к для типа force не зарезервирована соответсвующая
                    if (bj_wantDestroyGroup) {
                        DestroyForce(target);
                    }
                }
            
                CommandCount++; 
            }
        }
    
    }
    
    public void RegisterCommand(string name, string handler) {
        RegisterCommandForce(name, handler, null);
    }
    
    /* [RemoveCommand] returns nothing
       | Описание: удаляет указанную команду из системы
       | Параметры:
       |     ~ name [string] - имя команды (без префикса)
       | Пример вызова:
       |    CommandSystem_RemoveCommand("useless");
    */
    public void RemoveCommand(string name) {
        if (HasCommand(name)) {
            SaveStr(database, CommandKey, StringHash(name), null);
            ResetAccess(name, true);
            
            CommandCount--;
        }
    }
    
    /* [RenameCommand] returns trigger
       | Описание: изменяет имя указанной команды
       | Параметры:
       |     ~ name [string] - исходное имя (без префикса)
       |     ~ newName [string] - целевое имя (без префикса)
       | Пример вызова:
       |    CommandSystem_RenameCommand("goto", "togo");
    */
    public void RenameCommand(string name, string newName) {
        if (HasCommand(name) and CheckString(newName)) {
            command = GetCommand(name);
            RegisterCommand(newName, command);
        }
    }
    
    /* [ChangeHandler] returns nothing
       | Описание: обменивает команды с указанными названиями их обработчиками
       | Параметры:
       |     ~ name [string] - имя команды (без префикса)
       |     ~ handler [string] - название функции-обработчика (без префикса)
       |     ~ check [boolean] - следует ли проверять наличие функции в системе
       | Пример вызова:
       |    CommandSystem_SwapCommands("goto", "togo");
    */
    public void ChangeHandler(string name, string handler, bool check) {
        if (not check or (HasCommand(name) and CheckString(handler))) {
            SaveStr(database, CommandKey, StringHash(name), handler);
        }
    }
    
    /* [SwapCommands] returns nothing
       | Описание: обменивает команды с указанными названиями их обработчиками
       | Параметры:
       |     ~ name1 [string] - имя команды 1 (без префикса)
       |     ~ name2 [string] - имя команды 2 (без префикса)
       | Пример вызова:
       |    CommandSystem_SwapCommands("goto", "togo");
    */
    public void SwapCommands(string name1, string name2) {
        if (HasCommand(name1) and HasCommand(name2)) {
            string c1 = GetCommand(name1);
            string c2 = GetCommand(name2);
        
            ChangeHandler(name1, c2, false);
            ChangeHandler(name2, c1, false);
        }
    }
   
    /* [ExecuteCommand] returns nothing
       | Описание: запускает указанную команду (при выполнении ее требований)
       | Параметры:
       |     ~ name [string] - имя команды для запуска
       |     ~ source [player] - игрок, вызвавший команду
       | Пример вызова:
       |    CommandSystem_ExecuteCommand("run", Player(2));
    */
    public void ExecuteCommand(string name, player source) {
        command = GetCommand(name);
        if (CheckString(command)) {
            if (HasAccess(name, source)) {
                if (IsCommandEnabled(name)) {
                    ExecuteFunc(command);
                } else {
                    DisplayMessage(source, DisabledException);
                }
            } else {
                DisplayMessage(source, PermissionException);
            }
        } else {
            DisplayMessage(source, CommandException);
        }
    }
    
    /* [SetListeningTo] returns nothing
       | Описание: устанавливает состояние наблюдения за сообщениями игрока
       | Параметры:
       |     ~ p [player] - игрок, вызвавший команду
       |     ~ enabled [boolean] - true для включения, false для отключения
       | Пример вызова:
       |    CommandSystem_SetListeningTo(Player(5), true);
    */
    public void SetListeningTo(player p, bool enabled) {
        if (p != null and GetPlayerId(p) < 12) { 
            if (enabled and not IsPlayerInForce(p, RegistredPlayers)) {
                ForceAddPlayer(RegistredPlayers, p);
                TriggerRegisterPlayerChatEvent(CommandDetector, p, CommandPrefix, false);
                
                if (ShowListeningMessage) { DisplayMessage(p, ListeningEnabled); }
            }
            if (not enabled and IsPlayerInForce(p, RegistredPlayers)) {
                ForceRemovePlayer(RegistredPlayers, p);
            
                if (ShowListeningMessage) { DisplayMessage(p, ListeningDisabled); }
            }
        }
    }
    
    private void ProcessMessage() {
        if (not IsPlayerInForce(GetTriggerPlayer(), RegistredPlayers)) { return; }
        // пробел добавляется для корректного отделения параметров
        string input = GetEventPlayerChatString() + " ";
        int length = StringLength(input);
    
        if (length < MinInputLength) { return; }
        // проверка на префикс
        if (SubString(input, 0, StringLength(CommandPrefix)) == CommandPrefix) {
            set Source = GetTriggerPlayer();
            set ParamCount = 0;
            bool found = false; // была ли команда отделена от параметров
            int pos = 1; int whitespace = 0; 
            
            loop { exitwhen (pos==length) or (ParamCount==MaxParamCount);
                // поиск пробелов
                if (SubString(input, pos, pos+1) == " ") {
                    if (whitespace != (pos-1)) {
                        if (found) {
                            Params[ParamCount] = SubString(input, whitespace + 1, pos);
                            ParamCount++;
                        } else {
                            Command = SubString(input, 1, pos); 
                            found   = true;
                        }
                    }
                    whitespace = pos;
                } pos++;
            }
            // если за этот проход не удалось отделить команду, то выделяем все символы, кроме последнего пробела
            if (!found) { Command = SubString(input, 1, length-1); }
            if (ParamCount > 0) { HasParams = true; } else { HasParams = false; }
            
            ExecuteCommand(Command, Source);
            
            Source = null;
            Command = null;
        }
    }
   
     private void init() {
        set database = InitHashtable();
        
        set CommandDetector = CreateTrigger();
        set CommandCount = 0;

        TriggerAddAction(CommandDetector, function ProcessMessage);
        // если требуется обработка команд только от определенных игроков, то 
        // замените следующий участок кода на свой
        RegistredPlayers = bj_FORCE_ALL_PLAYERS;
        // Пример:
        // RegistredPlayers = CreateForce();
        // ForceAddPlayer(RegistredPlayers, Player(0));
        // ForceAddPlayer(RegistredPlayers, Player(1));
        // ForceAddPlayer(RegistredPlayers, Player(2));
        ForForce(RegistredPlayers, lambda void function() {
            TriggerRegisterPlayerChatEvent(CommandDetector, GetEnumPlayer(), CommandPrefix, false);
        });
    }
}
Подробное описание функций
Для начала скажу - для использования системы необходим JNGP ,
т.к она полностью написана на cJass (буду благодарен, если кто-нибудь поможет перевести на Jass или подскажет толковый конвертер)
Чтобы добавить ее в свою карту вы можете просто скопировать отсюда код (из блока "Код cJass") и вставить в содержание своей карты. Лучше выделить под нее отдельное место, дабы сохранить простоту организации проекта. Сразу после этого лучше протестировать карту на работоспособность, иначе во время очередного запуска могут возникнуть непредвиденные ошибки, которые будет нелегко отловить.
Итак, все работает, но команд пока что не добавлено. Как же быть? А вот так:
  • создаем место под описание команд (я создаю для каждой команды по отдельному "триггеру" и очищаю его содержимое)
  • вызываем функцию CommandSystem_RegisterCommand(%name, %func) , где %name - имя команды без префикса (например, "kick"), а %func- название функции, обрабатывающей команду
Например: CommandSystem_RegisterCommand("kick", "KickHandler")
  • Для вызова команды без чата используйте метод CommandSystem_ExecuteCommand(%name, %player) , где %name - имя вызываемой команды, а %player - игрок, вызвавший ее. Если команда имеет параметры, то перед вызовом задайте их вручную (используя свойство CommandSystem_Params
Пример обработчика команды
Пример взят из приложенной тестовой карты. Вы можете найти триггер Spawn в папке Команды.
Вот содержание обработчика команды *spawn*:
void SpawnHandler() {
    if (CommandSystem_HasParams) {    // если у команды имеются параметры
        // отсчет в массиве начинается с нулевого индекса
        if (CommandSystem_Params[0] == "knight") { 
            CreateUnitAtLoc(CommandSystem_Source, 'hkni', GetRandomLocInRect(GetPlayableMapRect()), 0);
        }
        if (CommandSystem_Params[0] == "horse") {
            CreateUnitAtLoc(CommandSystem_Source, 'hhdl', GetRandomLocInRect(GetPlayableMapRect()), 0);
        }
    }
}
Другие методы этой системы:
  • GetCommand(%name) - возвращает триггер команды с именем %name или null, если такая не была зарегистрирована
  • HasCommand(%name) - возвращает True, если команда с именем %name была зарегистрирована, иначе False
  • SwapCommands(%name1, %name2) - обменивает обработчиками команды под именами %name1 и %name2
  • RenameCommand(%name, %newName, %replace) - изменяет имя существующей команды %name на %newName, параметр %replace указывает, следует ли перезаписать команду с именем %newName, если она уже существует
  • RemoveCommand(%name) - удаляет из системы команду под имением %name
  • HasAccess(%name, %player) - возвращает True, если для игрока %player разрешен запуск команды %name, иначе False
  • SetAccess(%name, %player, %value) - устанавливает возможность вызова игроком %player команды %name. Укажите True вместо %value, чтобы разрешить, иначе - False
  • ResetAccess(%name) - сбрасывает настройки доступа команды %name для всех зарегистрированных игроков
  • SetListeningTo(%player, %value) - устанавливает состояние наблюдения за сообщениями игрока %player. Если %value = True, то его сообщения будут обработаны в системе команд. Укажите %value = False, чтобы система перестала обрабатывать сообщения игрока %player
  • SetCommandState(%name, %enabled) - устанавливает состояние активности команды (действует на всех игроков), если %enabled = true, то команда активна.
Внимание: lite версия содержит только часть этих методов
Обработка входных параметров
Система имеет встроенную поддержку параметров (по умолчанию до 5)
При их использовании есть несколько правил:
  • Параметры отделяются пробелами
  • Все параметры обрабатываются как строки (string), если вам нужны числа или другие типы данных, используйте сторонние библиотеки
  • Параметры хранятся в массиве CommandSystem_Params и не сбрасываются при очередной обработке для экономии ресурсов. Используйте только то кол-во параметров, которое указано в CommandSystem_ParamCount.
Как использовать параметры:
  • Проверьте свойство CommandSystem_HasParams. Оно должно иметь значение True
  • Получите нужные строки из массива CommandSystem_Params
  • Не получайте параметры, имеющие индекс больше чем (CommandSystem_ParamCount - 1)
  • {...}
  • PROFIT!
Настройка отлова сообщений
Если вам необходимо, чтобы команды работали только у определенных игроков (например только красного),
то найдите инициализатор системы, т.е метод init(). Там вы найдете следующий участок кода:
RegistredPlayers = bj_FORCE_ALL_PLAYERS;
замените его подобным образом:
 // Пример:
 RegistredPlayers = CreateForce();

 ForceAddPlayer(RegistredPlayers, Player(0));
 ForceAddPlayer(RegistredPlayers, Player(1));
 ForceAddPlayer(RegistredPlayers, Player(2));
В данном случае система будет обрабатывать только сообщения первых трех игроков.
История изменений (changelog)
(v1.0b) [3.01.14] - первый релиз. Сырой и хлипкий. Понабрал минусов и заставил своих потомков отдуваться за себя
(v1.1b) [4.01.14] - получил средние оценки. Последний основной релиз, использующий триггеры.
(v1.2) [4.01.14] - да, он вышел в тот же день, что и 1.1b. Использует строки как идентификаторы функций (спасибо за это nvc123). Добавлена возможность настроек доступа к командам (command access)
(v1.3) [5.01.14] - разделение системы на обычную и lite-редакцию. Добавлена возможность управления обработкой пользовательских сообщений (метод SetListeningTo(%player, %value))
(v1.4) [19.01.14] - добавлена возможность включения и выключения команд (метод SetCommandState), возможность регистрации команды только для определенной группы игроков (RegisterCommandForce)
Как со мной связаться
В большинстве сетей вы сможете найти меня под ником ddarksniper (или DDarkSniper).
Если вы предпочитаете электронную почту - то пишите на ящик ddarksniper97[at]gmail.com
Дальнейшее развитие системы не планируется, но поддержку вы сможете получить в любое время
P.S: если минусуете, то пожалуйста пишите за что именно
Скриншоты
`
ОЖИДАНИЕ РЕКЛАМЫ...
1
28
10 лет назад
1
Timoxxx, берём mpq архиватор
достаём j фаил
получаем версию на jass
5
2
10 лет назад
5
единственная проблема - форматирование кода. после обработки cJass код превращается в такое аппетитное блюдо для мух...
0
5
10 лет назад
0
nvc123:
Timoxxx, берём mpq архиватор
достаём j фаил
получаем версию на jass
Мне код этой наработки на джассе не нужен, просто автор сказал, что развивать ее больше не планирует, несмотря на то, что он хотел ее перевести на jass в последней версии. (да, перевод - это не развитие, но все-таки...)
nvc123, ответь в лс, пожалуйста!
2
28
10 лет назад
2
Timoxxx, ответил
Чтобы оставить комментарий, пожалуйста, войдите на сайт.