Русское сообщество по скриптингу

Смена модели игрока & SVC_BAD

Статьи или фрагменты кода для новичков и уже опытных скриптеров по AMXX.

Модератор: Chuvi

Правила форума
1. Запрещено материться и оскорблять других участников форума.
2. Запрещен флуд, оффтоп, дабл постинг во всех разделах форума, кроме раздела "Болтовня".
3. Запрещено взламывать сайт/форум или наносить любой вред проекту.
4. Запрещено рекламировать другие ресурсы.
5. Запрещено создавать темы без информативного названия. Название темы должно отображать ее смысл.

В данном разделе форума разрешено создавать темы, касающие только обучающему материалу по AMX Mod X.

Смена модели игрока & SVC_BAD

Сообщение iplague » 20 окт 2017, 05:18

[TUT-CS] Смена модели игрока и избежание ошибки SVC_BAD
смена модели игрока, ошибка svc_bad, zombie plague mod

Вы должны зарегистрироваться, чтобы видеть ссылки.

Преамбула.
Эта статья будет полезной как для начинающих так и для продвинутых скриптеров, имеющих дело со сменой моделей в Counter-Strike посредством AMXX. В статье будут описаны различные доступные методы смены модели и варианты возникновения ошибки SVC_BAD.

Подробнее об ошибке SVC_BAD.
Эта ошибка обычно вызвана неверными сообщениями от движка. При получении подобных сообщений, наиболее вероятным результатом является то, что вас отсоединит от сервера с одной из следующих причин в вашей консоли:
- Host_Error: Illegible Server Message: SVC_BAD
- Host_Error: UserMsg: Not Present on Client ###
- Host_Error: CL_ParseServerMessage: Bad server message
- и т.д.

В основном, причины данной ошибки могут быть такими:
- Потеря (повреждение) сетевых данных
- MOD или плагины, отправляющие несуществующие недействительные сообщения
- MOD или плагины, отправляющие сообщения с неправильным количеством аргументов

Кроме того, так же эта ошибка возникает при одновременном изменении большого количества моделей игроков, и может возникнуть почти для каждого клиента без видимых причин. Вероятно, это какой-то глюк, который VALVe никогда не исправит, так что мы можем с этим поделать? Читайте дальше.

Использование нативов из модуля CStrike
Вероятно, ваша первая попытка сменить модели игроков заключалась в использовании модуля <cstrike>. Конечно, этот метод имеет преимущество простоты использования, и всё что вам нужно - это разобраться в трёх нативах. Однако, не рекомендуется использовать этот метод, если задачей вашего плагина является одновременная смена множества моделей (например, ZM)
Взглянув на исходник, мы видим, что модель устанавливается и отслеживается на InfoBuffer игрока, казалось бы - ничего плохого в этом нет, однако...
Код: Выделить всё
static cell AMX_NATIVE_CALL cs_set_user_model(AMX *amx, cell *params) // cs_set_user_model(index, const model[]); = 2 params
{
    // ...

    g_players[params[1]].SetModel(model);
    g_players[params[1]].SetModelled(true);

    SETCLIENTKEYVALUE(params[1], GETINFOKEYBUFFER(pPlayer), "model", (char*)g_players[params[1]].GetModel());

    // ...
}

Но вот что происходит: чтобы CS не сменил пользовательскую модель обратно на дефолтную, выполняется проверяет события Player PostThink, чтобы при смене модели пользовательская модель была сразу же установлена обратно.
Код: Выделить всё
void PlayerPostThink(edict_t* pPlayer) {
    int entityIndex = ENTINDEX(pPlayer);

    if(g_players[entityIndex].GetModelled()) {
        if (g_players[entityIndex].GetInspectModel() && strcmp(g_players[entityIndex].GetModel(), GETCLIENTKEYVALUE(GETINFOKEYBUFFER(pPlayer), "model")) != 0) {
            //LOG_CONSOLE(PLID, "%s should have model %s and currently has %s", STRING(pPlayer->v.netname), (char*)g_players[entityIndex].GetModel(), GETCLIENTKEYVALUE(GETINFOKEYBUFFER(pPlayer), "model"));
            SETCLIENTKEYVALUE(entityIndex, GETINFOKEYBUFFER(pPlayer), "model", (char*)g_players[entityIndex].GetModel());
            g_players[entityIndex].SetInspectModel(false);
        }
    }
    RETURN_META(MRES_IGNORED);

Проблема возникает в таких случаях, как начало раунда, когда CS возвращает все модели к стандартным (gign, sas, leet и т.д.) Если кому то задана нестандартная модель, то происходит вот что:
- игроку задана пользовательская модель (например, зомби) - и сообщение об этом отправляется всем игрокам;
- новый раунд - отправлено много сообщений: обновить счёт, удалить entites, спавн игроков и т.д.
- CS сбрасывает модель игрока обратно на «leet» - добавлено еще одно сообщение
- модель игрока снова мгновенно меняется на PostThink - еще одно сообщение.


Как вы можете видеть, с началом нового раунда необходимо получить/передать много сетевого трафика. Вы даже можете проверить это самостоятельно с помощью логирующих плагинов (например, Вы должны зарегистрироваться, чтобы видеть ссылки.).
На всем известном моде war3ft есть ситуация, когда игрок может спавниться на базе противника в начале раунда. При этом ему задаётся модель команды противника (раса шпион, способность Веселье клинков; или же предмет mole). Иногда игроки с высоким пингом успевают увидеть как сначала появляется игрок в "форме" противника, затем его "форма" меняется на противоположную. У игроков без потерь пакетов такого не наблюдается.

Теперь, скажем, у всех 32 игроков на сервере есть пользовательские модели. В начале раунда обычно отправляется много сообщений, и в добавок теперь вы должны включить все сообщения об обновлении модели. И описанная ошибка, о котором мы рассказывали ранее, выглядит следующим образом: казалось бы, всякий раз, когда кто-то близок к переполнению сетевого канала (получает слишком много данных одновременно), тогда [почти] каждый игрок получит "дисконнект" с причиной SVC_BAD ...
И если вам посчастливилось остаться на сервере, вы также можете заметить, что сервер «фризит» в течение нескольких секунд, в то время как другие игроки будут "кикнуты" (например, как при high CPU usage - «высокой загрузке процессора» со стороны сервера).
-----
Кстати, еще одна проблема, с которой вы столкнетесь, когда модуль <cstrike> обрабатывает ваши собственные модели, заключается в том, что всякий раз, когда вы используете родной cs_set_user_team () на игроке, его модель изменяется независимо от передачи CS_DONTCHANGE в качестве третьего аргумента.

Мы можем увидеть причину этого, снова взглянув на исходный код.
Код: Выделить всё
static cell AMX_NATIVE_CALL cs_set_user_team(AMX *amx, cell *params) // cs_set_user_team(index, team, model = 0); = 3 params
{
    // ...

    int model = params[3];

    *((int *)pPlayer->pvPrivateData + OFFSET_TEAM) = params[2];
    if (model != 0)
        *((int *)pPlayer->pvPrivateData + OFFSET_INTERNALMODEL) = model;
    
    
// This makes the model get updated right away.
    MDLL_ClientUserInfoChanged(pPlayer, GETINFOKEYBUFFER(pPlayer)); //  If this causes any problems for WON, do this line only in STEAM builds.

    // ...
}

После того, как команда и внутренняя модель установлены, выполняется вызов DLL_ClientUserInfoChanged, что фактически заставляет CS сбросить модель игрока.

Способ №1 - Fakemeta
Нам нужно найти аналогичный по функциональности <cstrike'y> натив, который позволит полностью избежать одновременной установки моделей всем игрокам в начале раунда. У Fakemetа есть все, что нам нужно.
Есть пять шагов, которые нам придется выполнить:
1. Установка модели игрока.
2. Получение модели игрока.
3. Возврат дефолтной (по умолчанию) модели.
4. Предотвращение смены CS'кой нашей заданной модели.
5. Предотвращение смены игроком заданной модели.


Для начала вам нужно задать 2 глобальных массива: первый будет булевым (игрок использует пользовательскую модель - да/нет), а второй сохранит имя (строку) пользовательской модели, при необходимости.
Код: Выделить всё
new g_has_custom_model[33]
new g_player_model[33][32

1. Чтобы установить модель, мы просто сообщаем движку установить пару новых значений для клиента:
Код: Выделить всё
stock fm_cs_set_user_model( player, const modelname[] )
{
    // Set new model
    engfunc( EngFunc_SetClientKeyValue, player, engfunc( EngFunc_GetInfoKeyBuffer, player ), "model", modelname )
    
    
// Remember this player has a custom model
    g_has_custom_model[player] = true

2. Чтобы получить модель, мы могли бы просто использовать родной cs_get_user_model (), но если вы планируете вообще не включать <cstrike>, используйте вместо этого следующее:
Код: Выделить всё
stock fm_cs_get_user_model( player, model[], len )
{
    // Retrieve current model
    engfunc( EngFunc_InfoKeyValue, engfunc( EngFunc_GetInfoKeyBuffer, player ), "model", model, len )

3. Чтобы восстановить модель по умолчанию, как мы видели ранее, достаточно сообщить движку об изменении UserInfo пользователя, и эту работу для нас выполнит CS:
Код: Выделить всё
stock fm_cs_reset_user_model( player )
{
    // Player doesn't have a custom model any longer
    g_has_custom_model[player] = false
    
    dllfunc
( DLLFunc_ClientUserInfoChanged, player, engfunc( EngFunc_GetInfoKeyBuffer, player ) )

4. Чтобы заблокировать CS от изменения модели, мы используем forward FM_SetClientKeyValue:
Код: Выделить всё
public plugin_init()
{
    // ...
    register_forward( FM_SetClientKeyValue, "fw_SetClientKeyValue" )
}

public fw_SetClientKeyValue( id, const infobuffer[], const key[] )
{
    // Block CS model changes
    if ( g_has_custom_model[id] && equal( key, "model" ) )
        return FMRES_SUPERCEDE;
    
    return FMRES_IGNORED
;

5. Однако, поскольку мы блокируем SetClientKeyValue, CS больше не будет препятствовать тому, чтобы игрок изменил свою модель, набрав модель «modelname» в своей консоли. Поэтому мы должны позаботиться об этом таким образом:
Код: Выделить всё
public plugin_init()
{
    // ...
    register_forward( FM_SetClientKeyValue, "fw_SetClientKeyValue" )
}

public fw_SetClientKeyValue( id, const infobuffer[], const key[] )
{
    // Block CS model changes
    if ( g_has_custom_model[id] && equal( key, "model" ) )
    {
        // Get current model
        static currentmodel[32]
        fm_cs_get_user_model( id, currentmodel, charsmax( currentmodel ) )
        
        
// Check whether it matches the custom model - if not, set it again
        if ( !equal( currentmodel, g_player_model[id] ) )
            fm_cs_set_user_model( id, g_player_model[id] )
        
        return FMRES_SUPERCEDE
;
    }
    
    return FMRES_IGNORED
;

Обратите внимание, что вы также можете использовать: set_user_info( player, "model", modelname ) и get_user_info( player, "model", model[], len ), чтобы установить и получить модели игроков. Они, вероятно, тоже работают отлично, и их легче набирать, но всё же FakeMeta :thumbs_up

Наконец, чтобы всегда быть задержка между изменениями модели, вы можете использовать следующий код, когда вам нужно изменить модель игрока:
Код: Выделить всё
#define MODELCHANGE_DELAY 0.5 // delay between model changes
new Float:g_models_targettime // target time for the last model change

public fm_cs_user_model_update( id )
{
    static Float:current_time
    current_time 
= get_gametime()
    
    
// Delay needed?
    if ( current_time - g_models_targettime >= MODELCHANGE_DELAY )
    {
        fm_cs_set_user_model( id )
        g_models_targettime = current_time
    
}
    else
    
{
        set_task( (g_models_targettime + MODELCHANGE_DELAY) - current_time, "fm_cs_set_user_model", id )
        g_models_targettime = g_models_targettime + MODELCHANGE_DELAY
    
}


Способ №2 - отдельные энтити
Предположим, вы уже пытались использовать предыдущий метод в своём плагине и задавали более длительные задержки для задач, но вы все равно получаете SVC_BAD время от времени. Есть ли еще какое-то решение, которое стоит попробовать? Ну, просто не меняйте никаких моделей.

Этот последний метод заключается в том, чтобы сделать реальных игроков невидимыми и задать отдельные энтити (которым будут присвоены ваши модели), которые будут привязаны к игрокам и копировать их движения. В следствии, не должна возникать ошибка SVC_BAD, так как вы как бы вообще и не меняете модели игроков на самом деле (идея взята из ChickenMod 1.0.5.1).

Плохая новость заключается в том, что энтити могут не создаться или просто сделать сервер менее стабильным (не говоря уже о том, что большее количество объектов также означает более высокий уровень использования ЦП), поэтому он является экспериментальным.

Тем не менее, давайте продолжим. Обратите внимание, что вам понадобится 2 объекта для каждого игрока, так как их невидимость также скрывают оружие. Сначала настройте 3 глобальных массива. Один для хранения имени модели, в то время как остальные будут использоваться для хранения индекса объектов, следующих за игроком. Кроме того, определите имена классов для своих моделей, если пожелаете.
Код: Выделить всё
new g_player_model[33][32]
new g_ent_playermodel[33]
new g_ent_weaponmodel[33]

new const PLAYERMODEL_CLASSNAME[] = "ent_playermodel"
new const WEAPONMODEL_CLASSNAME[] = "ent_weaponmodel" 

Далее, функция, которую вам нужно будет вызывать, когда вы хотите установить пользовательскую модель игрока:
Код: Выделить всё
stock fm_set_playermodel_ent( id, const modelname[] )
{
    // Make original player entity invisible
    set_pev( id, pev_rendermode, kRenderTransTexture )
    // This is not 0 because it would hide the shadow and some effects when firing weapons
    set_pev( id, pev_renderamt, 1.0 )
    
    
// Since we're passing the short model name to the function
    // we need to make the full path out of it
    static modelpath[100]
    formatex( modelpath, charsmax( modelpath ), "models/player/%s/%s.mdl", modelname, modelname )
    
    
// Check if the entity assigned to this player exists
    if ( !pev_valid( g_ent_playermodel[id] ) )
    {
        // If it doesn't, proceed to create a new one
        g_ent_playermodel[id] = engfunc( EngFunc_CreateNamedEntity, engfunc( EngFunc_AllocString, "info_target" ) )
        
        
// If it failed to create for some reason, at least this will prevent further "Invalid entity" errors...
        if ( !pev_valid( g_ent_playermodel[id] ) ) return;
        
        
// Set its classname
        set_pev( g_ent_playermodel[id], pev_classname, PLAYERMODEL_CLASSNAME )
        
        
// Make it follow the player
        set_pev( g_ent_playermodel[id], pev_movetype, MOVETYPE_FOLLOW )
        set_pev( g_ent_playermodel[id], pev_aiment, id )
        set_pev( g_ent_playermodel[id], pev_owner, id )
    }
    
    
// Entity exists now, set its model
    engfunc( EngFunc_SetModel, g_ent_playermodel[id], modelpath )

Этот сток вернет значение, использует ли пользователь пользовательскую модель:
Код: Выделить всё
stock fm_has_custom_model( id )
{
    return pev_valid( g_ent_playermodel[id] ) ? true : false;

Это будет вызвано после события смены оружия, чтобы обновить нашу «модель оружия»:
Код: Выделить всё
stock fm_set_weaponmodel_ent( id )
{
    // Get the player's p_ weapon model
    static model[100]
    pev( id, pev_weaponmodel2, model, charsmax( model ) )
    
    
// Check if the entity assigned to this player exists
    if ( !pev_valid(g_ent_weaponmodel[id]) )
    {
        // If it doesn't, proceed to create a new one
        g_ent_weaponmodel[id] = engfunc( EngFunc_CreateNamedEntity, engfunc( EngFunc_AllocString, "info_target" ) )
        
        
// If it failed to create for some reason, at least this will prevent further "Invalid entity" errors...
        if ( !pev_valid( g_ent_weaponmodel[id] ) ) return;
        
        
// Set its classname
        set_pev( g_ent_weaponmodel[id], pev_classname, WEAPONMODEL_CLASSNAME )
        
        
// Make it follow the player
        set_pev( g_ent_weaponmodel[id], pev_movetype, MOVETYPE_FOLLOW )
        set_pev( g_ent_weaponmodel[id], pev_aiment, id )
        set_pev( g_ent_weaponmodel[id], pev_owner, id )
    }
    
    
// Entity exists now, set its model
    engfunc( EngFunc_SetModel, g_ent_weaponmodel[id], model )

Наконец, это функция, которая избавится от вашей дефолтной модели игрока (и энтити):
Код: Выделить всё
stock fm_remove_model_ents( id )
{
    // Make the player visible again
    set_pev( id, pev_rendermode, kRenderNormal )
    
    
// Remove "playermodel" ent if present
    if ( pev_valid( g_ent_playermodel[id] ) )
    {
        engfunc( EngFunc_RemoveEntity, g_ent_playermodel[id] )
        g_ent_playermodel[id] = 0
    
}
    // Remove "weaponmodel" ent if present
    if ( pev_valid( g_ent_weaponmodel[id] ) )
    {
        engfunc( EngFunc_RemoveEntity, g_ent_weaponmodel[id] )
        g_ent_weaponmodel[id] = 0
    
}

Это было не так сложно? Но есть еще некоторые вопросы, которые необходимо решить ...

1. Когда игрок умрет, объект (труп), который появился на земле, не будет иметь присвоенной модели. Чтобы исправить это, нам нужно связать сообщение с трупом следующим образом:
Код: Выделить всё
public plugin_init()
{
    // ...
    register_message( get_user_msgid( "ClCorpse" ), "message_clcorpse" )
}

public message_clcorpse()
{
    // Get player's id
    static id
    id 
= get_msg_arg_int( 12 )
    
    
// Check if the player is using a custom player model
    if ( fm_has_custom_model( id ) )
    {
        // Set correct model on player corpse
        set_msg_arg_string( 1, g_player_model[id] )
    }

2. Если мы хотим задать игроку другой рендеринг (например, свечение), нам нужно будет установить его на нашей настраиваемой энтити:
Код: Выделить всё
// Set a red glow on the "playermodel" entity
set_pev( g_ent_playermodel[id], pev_renderfx, kRenderFxGlowShell )
set_pev( g_ent_playermodel[id], pev_color, Float:{200.0, 0.0, 0.0} )
set_pev( g_ent_playermodel[id], pev_renderamt, 50.0 )

// Or, if you're using fakemeta_util's stock instead:
fm_set_rendering( g_ent_playermodel[id], kRenderFxGlowShell, 200, 0, 0, kRenderNormal, 50 

3. Если модель достаточно велика, ее "носитель" может увидеть ее часть (ноги, руки), что выглядело бы некрасиво. Вам может и не понадобиться это, но возьмите на заметку:
Код: Выделить всё
public plugin_init()
{
    // ...
    register_forward( FM_AddToFullPack, "fw_AddToFullPack" )
}

public fw_AddToFullPack( es, e, ent, host, hostflags, player )
{
    // Narrow down our matches a bit
    if ( player ) return FMRES_IGNORED;
    
    
// Check if it's one of our custom model ents being sent to its owner
    if ( ent == g_ent_playermodel[host] || ent == g_ent_weaponmodel[host] )
        return FMRES_SUPERCEDE;
    
    return FMRES_IGNORED
;


В итоге, собрав всё вместе получаем наглядный пример для ясности:
Код: Выделить всё
#include <amxmodx>
#include <fakemeta>
#include <hamsandwich>
#include <cstrike>

new const ZOMBIE_MODEL[] = "zombie" // The model we're gonna use for zombies
new const PLAYERMODEL_CLASSNAME[] = "ent_playermodel"
new const WEAPONMODEL_CLASSNAME[] = "ent_weaponmodel"

new g_player_model[33][32] // player's model name (string)
new g_ent_playermodel[33] // playermodel entity following this player
new g_ent_weaponmodel[33] // weaponmodel entity following this player
new g_zombie[33] // whether the player is a zombie
new g_glow[33] // whether the player has glow

/*================================================================================
 [Plugin Start]
=================================================================================*/

public plugin_precache()
{
    new modelpath[100]
    formatex( modelpath, charsmax( modelpath ), "models/player/%s/%s.mdl", ZOMBIE_MODEL, ZOMBIE_MODEL )
    engfunc( EngFunc_PrecacheModel, modelpath )
}

public plugin_init()
{
    register_plugin( "Player Model Changer Example", "0.3", "MeRcyLeZZ" )
    
    RegisterHam
( Ham_Spawn, "player", "fw_PlayerSpawn", 1 )
    register_forward( FM_AddToFullPack, "fw_AddToFullPack" )

    register_event( "CurWeapon", "event_curweapon", "be", "1=1" )
    register_message( get_user_msgid( "ClCorpse" ), "message_clcorpse" )
    
    register_clcmd
( "say /glow", "clcmd_sayglow" )
}

/*================================================================================
 [Player Spawn Event]
=================================================================================*/

public fw_PlayerSpawn( id )
{
   
    
// Not alive or didn't join a team yet
    if ( !is_user_alive( id ) || !cs_get_user_team( id ) )
        return;
    
    
// Set to zombie if on Terrorist team
    g_zombie[id] = cs_get_user_team( id ) == CS_TEAM_T ? true : false;
    
    
// Check if the player is a zombie
    if ( g_zombie[id] )
    {
        // Store our custom model in g_player_model[id]
        copy( g_player_model[id], charsmax( g_player_model[] ), ZOMBIE_MODEL )
        
        
// Set the model on our playermodel entity
        fm_set_playermodel_ent( id, g_player_model[id] )
    }
    // Not a zombie, but still has a custom model
    else if ( fm_has_custom_model( id ) )
    {
        // Reset it back to default
        fm_remove_model_ents( id )
    }
}

/*================================================================================
 [Add to Full Pack Forward]
=================================================================================*/

public fw_AddToFullPack( es, e, ent, host, hostflags, player )
{
    // Narrow down our matches a bit
    if ( player ) return FMRES_IGNORED;
    
    
// Check if it's one of our custom model ents being sent to its owner
    if ( ent == g_ent_playermodel[host] || ent == g_ent_weaponmodel[host] )
        return FMRES_SUPERCEDE;
    
    return FMRES_IGNORED
;
}

/*================================================================================
 [Weapon Change Event]
=================================================================================*/

public event_curweapon( id )
{
    // Check if the player is using a custom player model
    if ( fm_has_custom_model( id ) )
    {
        // Update weapon model on entity
        fm_set_weaponmodel_ent( id )
    }
}

/*================================================================================
 [ClCorpse Message]
=================================================================================*/

public message_clcorpse()
{
    // Get player's id
    static id
    id 
= get_msg_arg_int( 12 )
    
    
// Check if the player is using a custom player model
    if ( fm_has_custom_model( id ) )
    {
        // Set correct model on player corpse
        set_msg_arg_string( 1, g_player_model[id] )
    }
}

/*================================================================================
 [Client Disconnect Event]
=================================================================================*/

public client_disconnect( id )
{
    // Check if the player was using a custom player model
    if ( fm_has_custom_model( id ) )
    {
        // Remove custom entities
        fm_remove_model_ents( id )
    }
}

/*================================================================================
 [Client Command: Say /Glow]
=================================================================================*/

public clcmd_sayglow( id )
{
    // Turn glow on/off
    g_glow[id] = !( g_glow[id] )
    
    
// Check if the player is using a custom player model
    if ( fm_has_custom_model( id ) )
    {
        // Check if the player has glow
        if ( g_glow[id] )
        {
            // Set glow on playermodel entity
            fm_set_rendering( g_ent_playermodel[id], kRenderFxGlowShell, 200, 0, 0, kRenderNormal, 50 )
        }
        else
        
{
            // Remove glow on playermodel entity
            fm_set_rendering( g_ent_playermodel[id] )
        }
    }
    else
    
{
        // Set and remove glow the usual way
        if ( g_glow[id] )
        {
            fm_set_rendering( id, kRenderFxGlowShell, 200, 0, 0, kRenderNormal, 50 )
        }
        else
        
{
            fm_set_rendering( id )
        }
    }
}

/*================================================================================
 [Stocks]
=================================================================================*/

stock fm_set_playermodel_ent( id, const modelname[] )
{
    // Make original player entity invisible
    set_pev( id, pev_rendermode, kRenderTransTexture )
    // This is not 0 because it would hide the shadow and some effects when firing weapons
    set_pev( id, pev_renderamt, 1.0 )
    
    
// Since we're passing the short model name to the function
    // we need to make the full path out of it
    static modelpath[100]
    formatex( modelpath, charsmax( modelpath ), "models/player/%s/%s.mdl", modelname, modelname )
    
    
// Check if the entity assigned to this player exists
    if ( !pev_valid( g_ent_playermodel[id] ) )
    {
        // If it doesn't, proceed to create a new one
        g_ent_playermodel[id] = engfunc( EngFunc_CreateNamedEntity, engfunc( EngFunc_AllocString, "info_target" ) )
        
        
// If it failed to create for some reason, at least this will prevent further "Invalid entity" errors...
        if ( !pev_valid( g_ent_playermodel[id] ) ) return;
        
        
// Set its classname
        set_pev( g_ent_playermodel[id], pev_classname, PLAYERMODEL_CLASSNAME )
        
        
// Make it follow the player
        set_pev( g_ent_playermodel[id], pev_movetype, MOVETYPE_FOLLOW )
        set_pev( g_ent_playermodel[id], pev_aiment, id )
        set_pev( g_ent_playermodel[id], pev_owner, id )
    }
    
    
// Entity exists now, set its model
    engfunc( EngFunc_SetModel, g_ent_playermodel[id], modelpath )
}

stock fm_has_custom_model( id )
{
    return pev_valid( g_ent_playermodel[id] ) ? true : false;
}

stock fm_set_weaponmodel_ent( id )
{
    // Get the player's p_ weapon model
    static model[100]
    pev( id, pev_weaponmodel2, model, charsmax( model ) )
    
    
// Check if the entity assigned to this player exists
    if ( !pev_valid(g_ent_weaponmodel[id]) )
    {
        // If it doesn't, proceed to create a new one
        g_ent_weaponmodel[id] = engfunc( EngFunc_CreateNamedEntity, engfunc( EngFunc_AllocString, "info_target" ) )
        
        
// If it failed to create for some reason, at least this will prevent further "Invalid entity" errors...
        if ( !pev_valid( g_ent_weaponmodel[id] ) ) return;
        
        
// Set its classname
        set_pev( g_ent_weaponmodel[id], pev_classname, WEAPONMODEL_CLASSNAME )
        
        
// Make it follow the player
        set_pev( g_ent_weaponmodel[id], pev_movetype, MOVETYPE_FOLLOW )
        set_pev( g_ent_weaponmodel[id], pev_aiment, id )
        set_pev( g_ent_weaponmodel[id], pev_owner, id )
    }
    
    
// Entity exists now, set its model
    engfunc( EngFunc_SetModel, g_ent_weaponmodel[id], model )
}

stock fm_remove_model_ents( id )
{
    // Make the player visible again
    set_pev( id, pev_rendermode, kRenderNormal )
    
    
// Remove "playermodel" ent if present
    if ( pev_valid( g_ent_playermodel[id] ) )
    {
        engfunc( EngFunc_RemoveEntity, g_ent_playermodel[id] )
        g_ent_playermodel[id] = 0
    
}
    // Remove "weaponmodel" ent if present
    if ( pev_valid( g_ent_weaponmodel[id] ) )
    {
        engfunc( EngFunc_RemoveEntity, g_ent_weaponmodel[id] )
        g_ent_weaponmodel[id] = 0
    
}
}

// Set entity's rendering type (from fakemeta_util)
stock fm_set_rendering(entity, fx = kRenderFxNone, r = 255, g = 255, b = 255, render = kRenderNormal, amount = 16)
{
    new Float:color[3]
    color[0] = float(r)
    color[1] = float(g)
    color[2] = float(b)
    
    set_pev
(entity, pev_renderfx, fx)
    set_pev(entity, pev_rendercolor, color)
    set_pev(entity, pev_rendermode, render)
    set_pev(entity, pev_renderamt, float(amount))


Подытожим:
Вероятно, это самые лучшие способы решения SVC_BAD. Вы даже можете попробовать включить оба метода в один плагин и добавить определение (или CVAR) для администраторов сервера, чтобы динамически менять метод при наличии ошибки, или что-то еще, зависит от фантазии.

Кроме того, не постесняйтесь проверить на Zombie Plague Mod различные методы реализации для использования нескольких случайных моделей.

Перевёл iPlague.
vk.com/amxxdevelopment
Аватара пользователя
iplague
 
Сообщения: 46
Зарегистрирован: 23 май 2016, 13:50
Благодарил (а): 2 раз.
Поблагодарили: 13 раз.
Опыт программирования: Около года
Языки программирования: pawn

Вернуться в Статьи / фрагменты кода

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 7