[Урок] Объективизация SP, псевдоклассы, ООП

Kailo

Участник
Сообщения
168
Реакции
755
Содержание
1. Вступление
2. Определение объекта
3. Пример 1 (separate)
4. Пример 2 (row)
5. Пример 3 (mempool)

Вступление
Рассказ о моём опыте применение принципов ООП в SP.
Коротко и просто определим понятие объекта - набор переменных, массивов, Handle-ов объеденных как параметры какого-либо описываемого нами объекта с набором методов (функций) для действий над объектом или его параметрами. Объект не имеет ограничений по области существования и удаляется в конце работы программы или по нашему желанию.
По требованиям, которые следуют из свойств объекта, для реализации объекта нам придется создать Handle (или несколько) которые позволят нам реализовать хранение информации.
Так же мы будем использовать methodmaps как интерфейс для работы с нашим объектом.

Определение объекта
Для разбора выше сказанного я предлагаю рассмотреть конкретный пример.
Представим себя разработчиками RPG модификации. Нам нужно создать на карте ATM (банкоматы), которые позволят класть или снимать деньги на счёт, т.к. деньги которые у игрока на руках будут выпадать с него при смерти. Так же я хочу что бы для каждого банкомата можно было поставить его модель, цвет, звук при использовании.
Поискав в моделях игры я нашел две хорошо подходящие модели:
1) props_downtown\atm.mdl
2) props_unique\atm01.mdl
atm.JPG
Для управления объектами во всех 3-х примерах я буду использовать ArrayList как основу, хотя можно использовать и StringMap или KeyValues.
Модель, цвет (зеленый, синий, красный, желтый) и звук будут определяться случайным образом.
Для начала создадим функцию спавна модели:
PHP:
// © Maxim "Kailo" Telezhenko, 2018

#pragma newdecls required

#include <sdktools>

char g_atmmodel1[] = "models\\props_downtown\\atm.mdl";

public void OnPluginStart()
{
    RegConsoleCmd("sm_atm", Cmd_ATM);
}

public void OnMapStart()
{
    PrecacheModel(g_atmmodel1, true);
}

public Action Cmd_ATM(int client, int args)
{
    float pos[3], ang[3];
    GetClientAbsOrigin(client, pos);
    GetClientAbsAngles(client, ang);
    ang[0] = 0.0; // zeroing pitch
    DynamicProp(g_atmmodel1, pos, ang);

    return Plugin_Handled;
}

int DynamicProp(const char[] model, const float origin[3] = NULL_VECTOR, const float angles[3] = NULL_VECTOR)
{
    int entity = CreateEntityByName("dynamic_prop", -1);
    if (entity != -1) {
        DispatchKeyValueVector(entity, "origin", origin);
        DispatchKeyValueVector(entity, "angles", angles);
        DispatchKeyValue(entity, "model", model);
        DispatchKeyValue(entity, "solid", "6");
        DispatchKeyValue(entity, "fademindist", "-1");
        DispatchKeyValue(entity, "fademaxdist", "0");
        DispatchKeyValue(entity, "fadescale", "1");
        DispatchKeyValue(entity, "MinAnimTime", "5");
        DispatchKeyValue(entity, "MaxAnimTime", "10");
        DispatchSpawn(entity);
        ActivateEntity(entity);
    }
    return entity;
}
Результат:
atm-1.jpg

Определяем параметры которые закрепляются за объектом:
PHP:
float pos[3]; // координаты где банкомат расположен
float ang; // куда повернут банкомат
char model[128]; // модель
int color[3]; // цвет банкомата
char sound[128]; // звук при использовании
int ref; // reference на entity банкомата
Теперь мы можем написать основу (интерфейс) нашего объекта.
PHP:
// © Maxim "Kailo" Telezhenko, 2018

#pragma newdecls required

#include <sdktools>

#include "atm/atm.sp"

char g_atmmodel1[] = "models\\props_downtown\\atm.mdl";
ATM g_atm;

public void OnPluginStart()
{
    RegConsoleCmd("sm_atm", Cmd_ATM);
}

public void OnMapStart()
{
    g_atm = INVALID_OBJECT;
    PrecacheModel(g_atmmodel1, true);
}

public Action Cmd_ATM(int client, int args)
{
    float pos[3], ang[3];
    GetClientAbsOrigin(client, pos);
    GetClientAbsAngles(client, ang);
    if (g_atm != INVALID_OBJECT)
    {
        g_atm.Close();
    }
    g_atm = ATM(pos, ang[1], g_atmmodel1, {255, 200, 200}, ""); // pass only yaw of angle

    return Plugin_Handled;
}
PHP:
// © Maxim "Kailo" Telezhenko, 2018

#define INVALID_OBJECT view_as<ATM>(-1)

methodmap ATM
{
    public void SetPos(const float pos[3])
    {
    }
    public void GetPos(float pos[3])
    {
    }
 
    property float ang
    {
        public get() {}
        public set(float ang) {}
    }
 
    public void SetModel(const char[] model)
    {
    }
    public void GetModel(char[] buffer, int maxlength)
    {
    }
 
    public void SetColor(const int color[3])
    {
    }
    public void GetColor(int color[3])
    {
    }
 
    public void SetSound(const char[] sound)
    {
    }
    public void GetSound(char[] buffer, int maxlength)
    {
    }
 
    property int ref
    {
        public get() {}
        public set(int ref) {}
    }

    public void Kill()
    {
    }

    public void Spawn()
    {
    }

    public ATM(const float pos[3], float ang, const char[] model, const int color[3], const char[] sound)
    {
    }
 
    // Destructor
    public void Close()
    {
    }
};

Если у вашего объекта будут только cell-перменые можно обойтись и функциями стандартного ArrayList, но для удобство работы с параметрами строками и массивами мы добавим пару новых функций к ArrayList:
PHP:
// © Maxim "Kailo" Telezhenko, 2018

#if defined _arrayliste_included
 #endinput
#endif
#define _arrayliste_included

methodmap ArrayListE < ArrayList
{
    public ArrayListE(int blocksize = 1, int startsize = 0)
    {
        return view_as<ArrayListE>(new ArrayList(blocksize, startsize));
    }

    public void SetStringEx(int index, const char[] str, int size, int block)
    {
        block *= 4;
        size -= 1;
        int i = 0;
        for (; str[i] && i < size; i++)
        {
            this.Set(index, str[i], block + i, true);
        }
        this.Set(index, 0, block + i, true);
    }

    public int GetStringEx(int index, char[] buffer, int maxlength, int block)
    {
        block *= 4;
        maxlength -= 1;
        int i = 0;
        int c;
        for (; (c = this.Get(index, block + i, true)) && i < maxlength; i++)
        {
            buffer[i] = c;
        }
        buffer[i] = 0;
        return i - 1;
    }
 
    public void SetArrayEx(int index, const any[] values, int size, int block)
    {
        for (int i = 0; i < size; i++)
        {
            this.Set(index, values[i], block + i, false);
        }
    }
 
    public int GetArrayEx(int index, any[] buffer, int size, int block)
    {
        for (int i = 0; i < size; i++)
        {
            buffer[i] = this.Get(index, block + i, false);
        }
    }
};

Для простого теста новый банкомат создается на нашей позиции, а старый удаляется.

Пример 1 (separate)
Рассмотрим 1-й вариант размещения перменных в ArrayList:
Для каждого нового объекта создается собственный ArrayList и объект располагается в первой ячейки массива (new array[1][OBJECT_SIZE]).
Теперь мы можем сделать реализацию интерфейса на основе нашего массива.
PHP:
// © Maxim "Kailo" Telezhenko, 2018

#include <arrayliste>

#define INVALID_OBJECT view_as<ATM>(0)

#define ATMObject_Pos 0
#define ATMObject_Ang 3
#define ATMObject_Model 4
#define ATMObject_Color 36
#define ATMObject_Sound 39
#define ATMObject_Ref 71
#define ATMObject_SIZE 72

#define THIS (view_as<ArrayListE>(this))
methodmap ATM
{
    public void SetPos(const float pos[3])
    {
        THIS.SetArrayEx(0, pos, 3, ATMObject_Pos);
    }
    public void GetPos(float pos[3])
    {
        THIS.GetArrayEx(0, pos, 3, ATMObject_Pos);
    }
 
    property float ang
    {
        public get() { return THIS.Get(0, ATMObject_Ang, false); }
        public set(float ang) { return THIS.Set(0, ang, ATMObject_Ang, false); }
    }
 
    public void SetModel(const char[] model)
    {
        THIS.SetStringEx(0, model, 32, ATMObject_Model);
    }
    public void GetModel(char[] buffer, int maxlength)
    {
        THIS.GetStringEx(0, buffer, maxlength, ATMObject_Model);
    }
 
    public void SetColor(const int color[3])
    {
        THIS.SetArrayEx(0, color, 3, ATMObject_Color);
    }
    public void GetColor(int color[3])
    {
        THIS.GetArrayEx(0, color, 3, ATMObject_Color);
    }
 
    public void SetSound(const char[] sound)
    {
        THIS.SetStringEx(0, sound, 32, ATMObject_Sound);
    }
    public void GetSound(char[] buffer, int maxlength)
    {
        THIS.GetStringEx(0, buffer, maxlength, ATMObject_Sound);
    }
 
    property int ref
    {
        public get() { return THIS.Get(0, ATMObject_Ref, false); }
        public set(int ref) { return THIS.Set(0, ref, ATMObject_Ref, false); }
    }
 
    public void Kill()
    {
        int entity = EntRefToEntIndex(this.ref);
        if (entity != -1)
        {
            AcceptEntityInput(entity, "Kill");
        }
    }
 
    public void Spawn()
    {
        this.Kill();
        char model[128];
        this.GetModel(model, sizeof(model));
        float pos[3], ang[3] = {0.0, 0.0, 0.0};
        this.GetPos(pos);
        ang[1] = this.ang;
        int entity = DynamicProp(model, pos, ang);
        int color[3];
        this.GetColor(color);
        SetEntityRenderColor(entity, color[0], color[1], color[2], 255);
        this.ref = EntIndexToEntRef(entity);
    }

    public ATM(const float pos[3], float ang, const char[] model, const int color[3], const char[] sound)
    {
        ATM self = view_as<ATM>(new ArrayListE(ATMObject_SIZE, 1));
        self.SetPos(pos);
        self.ang = ang;
        self.SetModel(model);
        self.SetColor(color);
        self.SetSound(sound);
        self.ref = INVALID_ENT_REFERENCE;
        self.Spawn();
        return self;
    }
 
    // Destructor
    public void Close()
    {
        this.Kill();
        THIS.Close();
    }
};
#undef THIS
PHP:
// © Maxim "Kailo" Telezhenko, 2018

#pragma newdecls required

#include <sdktools>

#include "atm/atm.sp"

char g_atmmodel1[] = "models\\props_downtown\\atm.mdl";
ATM g_atm;

public void OnPluginStart()
{
    RegConsoleCmd("sm_atm", Cmd_ATM);
    HookEvent("round_start", OnRoundStart, EventHookMode_PostNoCopy);
}

public void OnMapStart()
{
    g_atm = INVALID_OBJECT;
    PrecacheModel(g_atmmodel1, true);
}

public Action Cmd_ATM(int client, int args)
{
    float pos[3], ang[3];
    GetClientAbsOrigin(client, pos);
    GetClientAbsAngles(client, ang);
    if (g_atm != INVALID_OBJECT)
    {
        g_atm.Close();
    }
    g_atm = ATM(pos, ang[1], g_atmmodel1, {255, 200, 200}, ""); // pass only yaw of angle

    return Plugin_Handled;
}

public void OnRoundStart(Event event, const char[] name, bool dontBroadcast)
{
    if (g_atm != INVALID_OBJECT)
        g_atm.Spawn();
}

int DynamicProp(const char[] model, const float origin[3] = NULL_VECTOR, const float angles[3] = NULL_VECTOR)
{
    int entity = CreateEntityByName("dynamic_prop", -1);
    if (entity != -1) {
        DispatchKeyValueVector(entity, "origin", origin);
        DispatchKeyValueVector(entity, "angles", angles);
        DispatchKeyValue(entity, "model", model);
        DispatchKeyValue(entity, "solid", "6");
        DispatchKeyValue(entity, "fademindist", "-1");
        DispatchKeyValue(entity, "fademaxdist", "0");
        DispatchKeyValue(entity, "fadescale", "1");
        DispatchKeyValue(entity, "MinAnimTime", "5");
        DispatchKeyValue(entity, "MaxAnimTime", "10");
        DispatchSpawn(entity);
        ActivateEntity(entity);
    }
    return entity;
}

Теперь банкомат будет спавниться в указанной точке с нужными параметрами в каждом раунде.
atm-2.jpg
Если нам надо больше чем один банкомат то меняет переменную g_atm на массив и добавляем соответствующие функции добавления/удаления/управления.

Пример 2 (row)
Если в первом примере идентификатором банкомата по сути является адрес Handle-а, то во втором случае мы делаем общий ArrayList для хранения банкоматов, но при этом т.к. есть возможность, что какой-либо верхний элемент в массиве будет удален и все нижестоящие индексы сместятся, индекс в массиве не может выступать в роли идентификатора (new array[n][OBJECT_SIZE]). Для этого мы добавим еще один параметр (int id;) к объекту и будем работать с ним подобно userid. Общий счетчик выступает идентификатором создаваемому объекту и увеличивается на один. Для доступа используется функция поиска. Теперь мы будем создавать новые банкоматы пока не достигнем максимума (3), а потом при создании нового будет исчезать наиболее старый.

PHP:
// © Maxim "Kailo" Telezhenko, 2018

#include <arrayliste>

#define ATMObject_Id 0
#define ATMObject_Pos 1
#define ATMObject_Ang 4
#define ATMObject_Model 5
#define ATMObject_Color 37
#define ATMObject_Sound 40
#define ATMObject_Ref 72
#define ATMObject_SIZE 73

static ArrayListE ATM_BUFFER = null;
static int g_counter = 0;

enum ATM
{
    INVALID_OBJECT = -1
};

#define IDX (view_as<int>(this))
methodmap ATM
{
    property int id
    {
        public get() { return ATM_BUFFER.Get(IDX, ATMObject_Id, false); }
    }

    public void SetPos(const float pos[3])
    {
        ATM_BUFFER.SetArrayEx(IDX, pos, 3, ATMObject_Pos);
    }
    public void GetPos(float pos[3])
    {
        ATM_BUFFER.GetArrayEx(IDX, pos, 3, ATMObject_Pos);
    }
 
    property float ang
    {
        public get() { return ATM_BUFFER.Get(IDX, ATMObject_Ang, false); }
        public set(float ang) { return ATM_BUFFER.Set(IDX, ang, ATMObject_Ang, false); }
    }
 
    public void SetModel(const char[] model)
    {
        ATM_BUFFER.SetStringEx(IDX, model, 32, ATMObject_Model);
    }
    public void GetModel(char[] buffer, int maxlength)
    {
        ATM_BUFFER.GetStringEx(IDX, buffer, maxlength, ATMObject_Model);
    }
 
    public void SetColor(const int color[3])
    {
        ATM_BUFFER.SetArrayEx(IDX, color, 3, ATMObject_Color);
    }
    public void GetColor(int color[3])
    {
        ATM_BUFFER.GetArrayEx(IDX, color, 3, ATMObject_Color);
    }
 
    public void SetSound(const char[] sound)
    {
        ATM_BUFFER.SetStringEx(IDX, sound, 32, ATMObject_Sound);
    }
    public void GetSound(char[] buffer, int maxlength)
    {
        ATM_BUFFER.GetStringEx(IDX, buffer, maxlength, ATMObject_Sound);
    }
 
    property int ref
    {
        public get() { return ATM_BUFFER.Get(IDX, ATMObject_Ref, false); }
        public set(int ref) { return ATM_BUFFER.Set(IDX, ref, ATMObject_Ref, false); }
    }
 
    public void Kill()
    {
        int entity = EntRefToEntIndex(this.ref);
        if (entity != -1)
        {
            AcceptEntityInput(entity, "Kill");
        }
    }
 
    public void Spawn()
    {
        this.Kill();
        char model[128];
        this.GetModel(model, sizeof(model));
        float pos[3], ang[3] = {0.0, 0.0, 0.0};
        this.GetPos(pos);
        ang[1] = this.ang;
        int entity = DynamicProp(model, pos, ang);
        int color[3];
        this.GetColor(color);
        SetEntityRenderColor(entity, color[0], color[1], color[2], 255);
        this.ref = EntIndexToEntRef(entity);
    }

    public ATM(const float pos[3], float ang, const char[] model, const int color[3], const char[] sound)
    {
        ATM self = view_as<ATM>(ATM_BUFFER.Length);
        ATM_BUFFER.Resize(view_as<int>(self) + 1);
        ATM_BUFFER.Set(view_as<int>(self), g_counter++, ATMObject_Id, false);
        self.SetPos(pos);
        self.ang = ang;
        self.SetModel(model);
        self.SetColor(color);
        self.SetSound(sound);
        self.ref = INVALID_ENT_REFERENCE;
        self.Spawn();
        return self;
    }
 
    // Destructor
    public void Close()
    {
        this.Kill();
        ATM_BUFFER.Erase(IDX);
    }
};
#undef IDX

methodmap ATMSys
{
    public static void Init()
    {
        if (ATM_BUFFER == null)
            ATM_BUFFER = new ArrayListE(ATMObject_SIZE, 0);
        ATM_BUFFER.Clear();
        g_counter = 0;
    }
 
    public static int Count()
    {
        return ATM_BUFFER.Length;
    }
 
    public static ATM Get(int index)
    {
        return view_as<ATM>(index);
    }
 
    public static ATM GetById(int id)
    {
        return view_as<ATM>(ATM_BUFFER.FindValue(id, 0));
    }
 
    public static ATM Front()
    {
        if (ATM_BUFFER.Length == 0)
            return INVALID_OBJECT;
 
        return view_as<ATM>(0);
    }
};
PHP:
// © Maxim "Kailo" Telezhenko, 2018

#pragma newdecls required

#include <sdktools>

#include "atm/atm.sp"

#define MAX_COUNT 3

char g_atmmodel1[] = "models\\props_downtown\\atm.mdl";

public void OnPluginStart()
{
    RegConsoleCmd("sm_atm", Cmd_ATM);
    HookEvent("round_start", OnRoundStart, EventHookMode_PostNoCopy);
}

public void OnMapStart()
{
    ATMSys.Init();
    PrecacheModel(g_atmmodel1, true);
}

public Action Cmd_ATM(int client, int args)
{
    float pos[3], ang[3];
    GetClientAbsOrigin(client, pos);
    GetClientAbsAngles(client, ang);
    if (ATMSys.Count() == MAX_COUNT)
        ATMSys.Front().Close();
    ATM(pos, ang[1], g_atmmodel1, {255, 200, 200}, ""); // pass only yaw of angle

    return Plugin_Handled;
}

public void OnRoundStart(Event event, const char[] name, bool dontBroadcast)
{
    for (int i = 0, count = ATMSys.Count(); i < count; i++)
        ATMSys.Get(i).Spawn();
}

int DynamicProp(const char[] model, const float origin[3] = NULL_VECTOR, const float angles[3] = NULL_VECTOR)
{
    int entity = CreateEntityByName("dynamic_prop", -1);
    if (entity != -1) {
        DispatchKeyValueVector(entity, "origin", origin);
        DispatchKeyValueVector(entity, "angles", angles);
        DispatchKeyValue(entity, "model", model);
        DispatchKeyValue(entity, "solid", "6");
        DispatchKeyValue(entity, "fademindist", "-1");
        DispatchKeyValue(entity, "fademaxdist", "0");
        DispatchKeyValue(entity, "fadescale", "1");
        DispatchKeyValue(entity, "MinAnimTime", "5");
        DispatchKeyValue(entity, "MaxAnimTime", "10");
        DispatchSpawn(entity);
        ActivateEntity(entity);
    }
    return entity;
}
atm-3.jpg

Пример 3 (mempool)
Ну и 3й вариант организации (думаю что вы вольны придумать еще больше способов на ArrayList или к примеру на KeyValues или StringMap) превращает ArrayList в бесконечный блок памяти с 4-х байтовами ячейками (new array[n][1]). Для управления которой создается специальный псевдокласс, а так же скелет объекта для более удобного создания собственных.

PHP:
// © Maxim "Kailo" Telezhenko, 2018

#if defined _memory_included
 #endinput
#endif
#define _memory_included

#define _AL(%0) (view_as<ArrayList>(%0))

methodmap MemPool
{
    public MemPool()
    {
        return view_as<MemPool>(new ArrayList(1, 0));
    }
 
    property int Length
    {
        public get()
        {
            return _AL(this).Length;
        }
    }
 
    public void Set(int adr, any value)
    {
        _AL(this).Set(adr, value, 0, false)
    }
 
    public any Get(int adr)
    {
        return _AL(this).Get(adr, 0, false)
    }
 
    public void Resize(int newsize)
    {
        return _AL(this).Resize(newsize);
    }
};
stock static MemPool mempool;

methodmap MemBook
{
    public MemBook()
    {
        return view_as<MemBook>(new ArrayList(2, 0));
    }
 
    public int Alloc(int size)
    {
        int count = _AL(this).Length;
        if (count == 0)
        {
            _AL(this).Push(1);
            _AL(this).Set(0, size, 1, false);
            mempool.Resize(size + 1);
            return 1;
        }
        int total = _AL(this).Get(0, 1, false) + 1;
        for (int i = 1, p, space; i < count; i++)
        {
            p = _AL(this).Get(i, 0, false);
            space = p - total;
            if (space >= size)
            {
                _AL(this).ShiftUp(i);
                _AL(this).Set(i, total, 0, false);
                _AL(this).Set(i, size, 1, false);
                return total;
            }
            total = _AL(this).Get(i, 0, false) + _AL(this).Get(i, 1, false);
        }
        mempool.Resize(total + size);
        _AL(this).Push(total);
        _AL(this).Set(_AL(this).Length - 1, size, 1, false);
        return total;
    }
 
    public void Free(int adr)
    {
        int i = _AL(this).FindValue(adr);
        if (i == -1)
            ThrowError("Wrong memory address to free");
  
        _AL(this).Erase(i);
  
        int total = _AL(this).Length;
        if (total > 0)
        {
            total = total - 1;
            total = _AL(this).Get(total, 0) + _AL(this).Get(total, 1);
        }
        mempool.Resize(total);
    }
};
stock static MemBook membook;

stock static bool inited = false;
stock static const char sErrorNotInit[] = "Memory not initialized";
stock static const char sErrorOutOfBounds[] = "Address is out of bounds";

stock int memalloc(int size)
{
    // Init if not yet
    if (!inited)
    {
        mempool = MemPool();
        membook = MemBook();
        inited = true;
    }
 
    return membook.Alloc(size);
}

stock void free(int adr)
{
    if (!inited)
        ThrowError(sErrorNotInit);
 
    membook.Free(adr);
}

stock void store(int adr, any value)
{
    if (!inited)
        ThrowError(sErrorNotInit);
 
    if (adr >= mempool.Length)
        ThrowError(sErrorOutOfBounds);
 
    mempool.Set(adr, value);
}

stock any load(int adr)
{
    if (!inited)
        ThrowError(sErrorNotInit);
 
    if (adr >= mempool.Length)
        ThrowError(sErrorOutOfBounds);

    return mempool.Get(adr);
}

stock void mempaste(int adr, const any[] array, int size)
{
    if (adr + size > mempool.Length)
        ThrowError(sErrorOutOfBounds);
 
    for (int i = 0; i < size; i++)
        mempool.Set(adr + i, array[i]);
}

stock void memcopy(int adr, any[] array, int size)
{
    if (adr + size > mempool.Length)
        ThrowError(sErrorOutOfBounds);
 
    for (int i = 0; i < size; i++)
        array[i] = mempool.Get(adr + i);
}
PHP:
// © Maxim "Kailo" Telezhenko, 2018

#if defined _object_included
 #endinput
#endif
#define _object_included

#include <memory>

stock void MovHelper(any[] to, const any[] from, int last)
{
    to[0] = from[last] & 0x00FFFFFF;
}

stock void MovHelper2(any[] to, any value)
{
    to[0] = value;
}

enum Object
{
    INVALID_OBJECT = 0
};

#define _INT(%0) (view_as<int>(%0))
#define _OBJECT(%0) (view_as<Object>(%0))
#define THIS _OBJECT(this)
methodmap Object
{
    public Object(int size)
    {
        return _OBJECT(memalloc(size));
    }
 
    public void Free()
    {
        free(_INT(this));
    }
 
    public void Set(int off, any value)
    {
        store(_INT(this) + off, value);
    }
 
    public any Get(int off)
    {
        return load(_INT(this) + off);
    }
 
    public void SetArray(int off, const any[] array, int length)
    {
        for (int i = 0, adr = _INT(this) + off; i < length; i++)
            store(adr + i, array[i]);
    }
 
    public void GetArray(int off, any[] array, int length)
    {
        for (int i = 0, adr = _INT(this) + off; i < length; i++)
            array[i] = load(adr + i);
    }
 
    public void SetString(int off, const char[] str, int maxlength)
    {
        int length = (strlen(str) + 4) / 4;
        if (length > maxlength) {
            int back = maxlength - 1;
            this.SetArray(off, view_as<any>(str), back);
            any last[1];
            MovHelper(last, view_as<any>(str), back);
            this.SetArray(off + back, last, 1);
        } else
            this.SetArray(off, view_as<any>(str), length);
    }
 
    public void GetString(int off, char[] str, int maxlength)
    {
        int limit = maxlength - 1;
        int i = 0, block = this.Get(off + i);
        while (i * 4 < limit)
        {
            if (!(block & 0xFF && block & 0xFF00 && block & 0xFF0000 && block & 0xFF000000))
            {
                limit = 0;
                if (block & 0xFF)
                {
                    str[i * 4 + 0] = block & 0xFF;
                    limit = 1;
                    if (block & 0xFF00)
                    {
                        str[i * 4 + 1] = (block & 0xFF00) >> 8;
                        limit = 2;
                        if (block & 0xFF0000)
                        {
                            limit = 3;
                            str[i * 4 + 2] = (block & 0xFF0000) >> 16;
                            if (block & 0xFF000000)
                            {
                                limit = 4;
                                str[i * 4 + 3] = (block & 0xFF000000) >> 24;
                            }
                        }
                    }
                }
                str[i * 4 + limit] = '\0';
                return;
            }
            MovHelper2(view_as<any>(str[i * 4]), block);
            i++;
            block = this.Get(off + i);
        }
        limit = limit - i * 4;
        int c, a;
        for (c = 0; c < limit; c++)
        {
            a = block & 0xFF << (c * 8)
            if (a == '\0')
            {
                break;
            }
            str[i * 4 + c] = a;
        }
        str[i * 4 + c] = '\0';
    }
};
#undef _OBJECT
#undef THIS
#undef _INT

PHP:
// © Maxim "Kailo" Telezhenko, 2018

#include <object>

#define ATMObject_Id 0
#define ATMObject_Pos 1
#define ATMObject_Ang 4
#define ATMObject_Model 5
#define ATMObject_Color 37
#define ATMObject_Sound 40
#define ATMObject_Ref 72
#define ATMObject_SIZE 73

methodmap ATM < Object
{
    public void SetPos(const float pos[3])
    {
        this.SetArray(ATMObject_Pos, pos, 3);
    }
    public void GetPos(float pos[3])
    {
        this.GetArray(ATMObject_Pos, pos, 3);
    }
 
    property float ang
    {
        public get() { return this.Get(ATMObject_Ang); }
        public set(float ang) { return this.Set(ATMObject_Ang, ang); }
    }
 
    public void SetModel(const char[] model)
    {
        this.SetString(ATMObject_Model, model, 32);
    }
    public void GetModel(char[] buffer, int maxlength)
    {
        this.GetString(ATMObject_Model, buffer, maxlength);
    }
 
    public void SetColor(const int color[3])
    {
        this.SetArray(ATMObject_Color, color, 3);
    }
    public void GetColor(int color[3])
    {
        this.GetArray(ATMObject_Color, color, 3);
    }
 
    public void SetSound(const char[] sound)
    {
        this.SetString(ATMObject_Sound, sound, 32);
    }
    public void GetSound(char[] buffer, int maxlength)
    {
        this.GetString(ATMObject_Sound, buffer, maxlength);
    }
 
    property int ref
    {
        public get() { return this.Get(ATMObject_Ref); }
        public set(int ref) { return this.Set(ATMObject_Ref, ref); }
    }
 
    public void Kill()
    {
        int entity = EntRefToEntIndex(this.ref);
        if (entity != -1)
        {
            AcceptEntityInput(entity, "Kill");
        }
    }
 
    public void Spawn()
    {
        this.Kill();
        char model[128];
        this.GetModel(model, sizeof(model));
        float pos[3], ang[3] = {0.0, 0.0, 0.0};
        this.GetPos(pos);
        ang[1] = this.ang;
        int entity = DynamicProp(model, pos, ang);
        int color[3];
        this.GetColor(color);
        SetEntityRenderColor(entity, color[0], color[1], color[2], 255);
        this.ref = EntIndexToEntRef(entity);
    }

    public ATM(const float pos[3], float ang, const char[] model, const int color[3], const char[] sound)
    {
        ATM self = view_as<ATM>(Object(ATMObject_SIZE));
        self.SetPos(pos);
        self.ang = ang;
        self.SetModel(model);
        self.SetColor(color);
        self.SetSound(sound);
        self.ref = INVALID_ENT_REFERENCE;
        self.Spawn();
        return self;
    }
 
    // Destructor
    public void Close()
    {
        this.Kill();
        this.Free();
    }
};
PHP:
// © Maxim "Kailo" Telezhenko, 2018

#pragma newdecls required

#include <sdktools>

#include "atm/atm.sp"

#define MAX_COUNT 3

char g_atmmodel1[] = "models\\props_downtown\\atm.mdl";
ArrayList g_atms;

public void OnPluginStart()
{
    RegConsoleCmd("sm_atm", Cmd_ATM);
    HookEvent("round_start", OnRoundStart, EventHookMode_PostNoCopy);
    g_atms = new ArrayList(1, 0);
}

public void OnMapStart()
{
    g_atms.Clear();
    PrecacheModel(g_atmmodel1, true);
}

public Action Cmd_ATM(int client, int args)
{
    float pos[3], ang[3];
    GetClientAbsOrigin(client, pos);
    GetClientAbsAngles(client, ang);
    if (g_atms.Length == MAX_COUNT)
    {
        (view_as<ATM>(g_atms.Get(0))).Close();
        g_atms.Erase(0);
    }
    g_atms.Push(ATM(pos, ang[1], g_atmmodel1, {255, 200, 200}, "")); // pass only yaw of angle

    return Plugin_Handled;
}

public void OnRoundStart(Event event, const char[] name, bool dontBroadcast)
{
    for (int i = 0, count = g_atms.Length; i < count; i++)
        (view_as<ATM>(g_atms.Get(i))).Spawn();
}

int DynamicProp(const char[] model, const float origin[3] = NULL_VECTOR, const float angles[3] = NULL_VECTOR)
{
    int entity = CreateEntityByName("dynamic_prop", -1);
    if (entity != -1) {
        DispatchKeyValueVector(entity, "origin", origin);
        DispatchKeyValueVector(entity, "angles", angles);
        DispatchKeyValue(entity, "model", model);
        DispatchKeyValue(entity, "solid", "6");
        DispatchKeyValue(entity, "fademindist", "-1");
        DispatchKeyValue(entity, "fademaxdist", "0");
        DispatchKeyValue(entity, "fadescale", "1");
        DispatchKeyValue(entity, "MinAnimTime", "5");
        DispatchKeyValue(entity, "MaxAnimTime", "10");
        DispatchSpawn(entity);
        ActivateEntity(entity);
    }
    return entity;
}

Заключение
Вы можете использовать удобную вам конфигурацию объекта и способа его хранения, а так же добавить выгрузку объектов в БД или файл для последующей загрузки при запуске сервера.
Зачем все это?
Это добавляет читаемости и структурированности коду, что позволяет более проще в нём ориентироваться, когда тебе надо понять только интерфейс объекта не вникая в его реализацию, а части программы могут писаться независимо друг от друга. Да, при простоте плагина это будет излишеством, но очень сильно помогает в объемных и сложных плагинах.
Впрочем каждый решает для себя сам. У меня все.
 
Последнее редактирование:
Сверху