20/09/2014, 12:46 AM
(Última modificación: 20/09/2014, 08:53 PM por meTaLiCroSS.)
La intro del HLSDK:
Para no empezar con explicaciones cientificas ni porquerias pintando un cuadro perfecto, usare un vocabulario flexible y entendible.
► AddToFullPack (escrita como pfnAddToFullPack) es una función importada del engine, la cual es llamada por el mod. (cada mod configura la funcion a su gusto). Esta puede ser hookeada por el módulo Fakemeta.
Esta se encarga de realizar una tarea completa: Codifica todos los datos de visibilidad a importar de las entidades, a todos los jugadores. Es por esta razon, que la funcion AddToFullPack es llamada multiples veces por frame, ya que por cada jugador, es llamada ésta por cada entidad (incluyendo otros jugadores), siendo una funcion bien pesada. .
Vamos por la declaracion de la funcion y sus argumentos.
Declaracion de AddToFullPack en el HLSDK:
Declaracion de AddToFullPack como función en Pawn, siendo previamente transformada por el módulo:
► Arg: state
Es una copia de la estructura entity_state_s, la cual *para ser mejor comprendida* vendrian siendo los entvars (pevs) de datos de visualizacion que afectarían el físico de las entidades enviadas.
Unas muy obvias por ejemplo son origin y angles, gracias a este par el cliente procesa la posicion y los angulos de vista de las entidades y jugadores. Son los datos necesarios que el cliente necesita para poder procesar graficamente las entidades (y también para predecirlas).
Como es una estructura que no es manipulada por alguna native, se crearon especificamente las natives get_es y set_es (es -> entity_state) que permiten obtener y cambiar estos valores. (tal asi como la estructura entvars_s es manipulada por la native (set_)pev/entity_(s/g)et_*)
La declaracion de esta estructura en el HLSDK es:
(Si bien se percatan, vendrian siendo los entvars especificos que afectan la visibilidad de las entidades. A simple vista son entendibles la mayoria.)
Para poder acceder a estos mediante las natives (s/g)et_es, se creo la siguiente enumeracion que van dentro del segundo argumento de ambas natives:
► Arg: e
Este es el index de la entidad que vamos a procesar.
► Arg: ent
A diferencia de e, ent es lo mismo solo que internamente es un puntero a la estructura edict_s de la entidad que vamos a procesar, pero como en Pawn las entidades son manejadas directamente con sus indexes, este argumento es transformado al index de la entidad, siendo lo mismo ent con e.
Pero por razones de comodidad, utiliza ent por sobre e.
► Arg: host
Este es el jugador al cual se le procesaran todas las entidades del servidor. Siempre va a ser un player (daaaah). Con respecto a este jugador, se ve si la entidad debe ser necesariamente procesada o no.
► Arg: hostflags
Valdrá 1 si la cvar cl_lw del host es distinta a 0. De no serlo, valdrá 0.
*** La cvar cl_lw en un jugador define si tiene su prediccion activada (para mejor fluidez). ***
► Arg: player
Valdrá 1 si ent es un jugador. De no serlo, valdrá 0.
Esto es simplemente para ahorrar una llamada más para detectar si es veridicamente un player o no.
► Arg: pSet
pSet es un puntero al PVS del host , seteado en la funcion exportada SetupVisibility (llamada en cada jugador antes de llamar multiples veces AddToFullPack) con respecto a la posicion del jugador. PVS significa "Potentially Visible Set", que vendria siendo "Conjunto Potencialmente Visible" (el traductor se portó bien). La logica implementada, es simple: "Si una entidad se encuentra en el PVS de un jugador, será enviada al mismo jugador; de no serlo, no se enviará", así evitando el envio innecesario de paquetes al player con informacion que no servira. (es por esto que algunos cheats muestran jugadores hasta un cierto punto.)
► return (int)
Si la funcion le devuelve 1 al engine, la entidad será transmitida al cliente.
Si la funcion le devuelve 0 al engine, no será transmitida.
*** Este valor puede ser obtenido con get_orig_retval() hookeando la funcion en Post. ***
*** Se puede hacer override del return, utilizando forward_return(FMV_CELL, ...) hookeando la funcion en Pre. ***
__________________________________________________________
Ya con la mayoria de estos datos aclarados, puedo ceder a mostrar la función escrita en el HLSDK (en C++) que filtra las entidades que deben ser enviadas y que no.
/// = comentario mio
Y aqui un intento mio de mostrar como es utilizado pfnAddToFullPack en el engine, dentro de la funcion SV_WriteEntitiesToClient() (con mucha ayuda de OSHLDS, OpenHLDS, y un decompilado parcial de engine_i686.so)
Con el codigo parcial escrito arriba se entiende un poco mejor el caso. SV_WriteEntitiesToClient() es llamado en cada jugador, y esta es llamada todos los frames.
Y asi es como al final, todas las entidades filtradas son comprimidas y enviadas al cliente.
__________________________________________________________
Ya con la mayoria explicada, podemos pasar a filosofar un poco la utilidad de esta misma funcion.
Despues pondré unos ejemplos maliciosos de que cosillas puedes hacer con esta funcion
Espero les haya gustado (quien sea lo haya leido entero) esta guia que me costo un tiempo escribiendo a la velocidad de la luz (?), cualquier duda pueden preguntar como también corregirme algún detalle que haya errado/olvidado.
Cita:/*
AddToFullPack
Return 1 if the entity state has been filled in for the ent and the entity will be propagated to the client, 0 otherwise
*/
Para no empezar con explicaciones cientificas ni porquerias pintando un cuadro perfecto, usare un vocabulario flexible y entendible.
► AddToFullPack (escrita como pfnAddToFullPack) es una función importada del engine, la cual es llamada por el mod. (cada mod configura la funcion a su gusto). Esta puede ser hookeada por el módulo Fakemeta.
Esta se encarga de realizar una tarea completa: Codifica todos los datos de visibilidad a importar de las entidades, a todos los jugadores. Es por esta razon, que la funcion AddToFullPack es llamada multiples veces por frame, ya que por cada jugador, es llamada ésta por cada entidad (incluyendo otros jugadores), siendo una funcion bien pesada. .
Vamos por la declaracion de la funcion y sus argumentos.
Declaracion de AddToFullPack en el HLSDK:
Código PHP:
int AddToFullPack( struct entity_state_s *state, int e, edict_t *ent, edict_t *host, int hostflags, int player, unsigned char *pSet )
Declaracion de AddToFullPack como función en Pawn, siendo previamente transformada por el módulo:
Código PHP:
public AddToFullPack(state, e, ent, host, hostflags, player, pSet)
► Arg: state
Es una copia de la estructura entity_state_s, la cual *para ser mejor comprendida* vendrian siendo los entvars (pevs) de datos de visualizacion que afectarían el físico de las entidades enviadas.
Unas muy obvias por ejemplo son origin y angles, gracias a este par el cliente procesa la posicion y los angulos de vista de las entidades y jugadores. Son los datos necesarios que el cliente necesita para poder procesar graficamente las entidades (y también para predecirlas).
Como es una estructura que no es manipulada por alguna native, se crearon especificamente las natives get_es y set_es (es -> entity_state) que permiten obtener y cambiar estos valores. (tal asi como la estructura entvars_s es manipulada por la native (set_)pev/entity_(s/g)et_*)
La declaracion de esta estructura en el HLSDK es:
Código PHP:
struct entity_state_s
{
// Fields which are filled in by routines outside of delta compression
int entityType;
// Index into cl_entities array for this entity.
int number;
float msg_time;
// Message number last time the player/entity state was updated.
int messagenum;
// Fields which can be transitted and reconstructed over the network stream
vec3_t origin;
vec3_t angles;
int modelindex;
int sequence;
float frame;
int colormap;
short skin;
short solid;
int effects;
float scale;
byte eflags;
// Render information
int rendermode;
int renderamt;
color24 rendercolor;
int renderfx;
int movetype;
float animtime;
float framerate;
int body;
byte controller[4];
byte blending[4];
vec3_t velocity;
// Send bbox down to client for use during prediction.
vec3_t mins;
vec3_t maxs;
int aiment;
// If owned by a player, the index of that player ( for projectiles ).
int owner;
// Friction, for prediction.
float friction;
// Gravity multiplier
float gravity;
// PLAYER SPECIFIC
int team;
int playerclass;
int health;
qboolean spectator;
int weaponmodel;
int gaitsequence;
// If standing on conveyor, e.g.
vec3_t basevelocity;
// Use the crouched hull, or the regular player hull.
int usehull;
// Latched buttons last time state updated.
int oldbuttons;
// -1 = in air, else pmove entity number
int onground;
int iStepLeft;
// How fast we are falling
float flFallVelocity;
float fov;
int weaponanim;
// Parametric movement overrides
vec3_t startpos;
vec3_t endpos;
float impacttime;
float starttime;
// For mods
int iuser1;
int iuser2;
int iuser3;
int iuser4;
float fuser1;
float fuser2;
float fuser3;
float fuser4;
vec3_t vuser1;
vec3_t vuser2;
vec3_t vuser3;
vec3_t vuser4;
};
(Si bien se percatan, vendrian siendo los entvars especificos que afectan la visibilidad de las entidades. A simple vista son entendibles la mayoria.)
Para poder acceder a estos mediante las natives (s/g)et_es, se creo la siguiente enumeracion que van dentro del segundo argumento de ambas natives:
Código PHP:
enum EntityState
{
// Fields which are filled in by routines outside of delta compression
ES_EntityType, // int
// Index into cl_entities array for this entity
ES_Number, // int
ES_MsgTime, // float
// Message number last time the player/entity state was updated
ES_MessageNum, // int
// Fields which can be transitted and reconstructed over the network stream
ES_Origin, // float array[3]
ES_Angles, // float array[3]
ES_ModelIndex, // int
ES_Sequence, // int
ES_Frame, // float
ES_ColorMap, // int
ES_Skin, // short
ES_Solid, // short
ES_Effects, // int
ES_Scale, // float
ES_eFlags, // byte
// Render information
ES_RenderMode, // int
ES_RenderAmt, // int
ES_RenderColor, // byte array[3], RGB value
ES_RenderFx, // int
ES_MoveType, // int
ES_AnimTime, // float
ES_FrameRate, // float
ES_Body, // int
ES_Controller, // byte array[4]
ES_Blending, // byte array[4]
ES_Velocity, // float array[3]
// Send bbox down to client for use during prediction
ES_Mins, // float array[3]
ES_Maxs, // float array[3]
ES_AimEnt, // int
// If owned by a player, the index of that player (for projectiles)
ES_Owner, // int
// Friction, for prediction
ES_Friction, // float
// Gravity multiplier
ES_Gravity, // float
// PLAYER SPECIFIC
ES_Team, // int
ES_PlayerClass, // int
ES_Health, // int
ES_Spectator, // bool
ES_WeaponModel, // int
ES_GaitSequence, // int
// If standing on conveyor, e.g.
ES_BaseVelocity, // float array[3]
// Use the crouched hull, or the regular player hull
ES_UseHull, // int
// Latched buttons last time state updated
ES_OldButtons, // int
// -1 = in air, else pmove entity number
ES_OnGround, // int
ES_iStepLeft, // int
// How fast we are falling
ES_flFallVelocity, // float
ES_FOV, // float
ES_WeaponAnim, // int
// Parametric movement overrides
ES_StartPos, // float array[3]
ES_EndPos, // float array[3]
ES_ImpactTime, // float
ES_StartTime, // float
// For mods
ES_iUser1, // int
ES_iUser2, // int
ES_iUser3, // int
ES_iUser4, // int
ES_fUser1, // float
ES_fUser2, // float
ES_fUser3, // float
ES_fUser4, // float
ES_vUser1, // float array[3]
ES_vUser2, // float array[3]
ES_vUser3, // float array[3]
ES_vUser4 // float array[3]
};
► Arg: e
Este es el index de la entidad que vamos a procesar.
► Arg: ent
A diferencia de e, ent es lo mismo solo que internamente es un puntero a la estructura edict_s de la entidad que vamos a procesar, pero como en Pawn las entidades son manejadas directamente con sus indexes, este argumento es transformado al index de la entidad, siendo lo mismo ent con e.
Pero por razones de comodidad, utiliza ent por sobre e.
► Arg: host
Este es el jugador al cual se le procesaran todas las entidades del servidor. Siempre va a ser un player (daaaah). Con respecto a este jugador, se ve si la entidad debe ser necesariamente procesada o no.
► Arg: hostflags
Valdrá 1 si la cvar cl_lw del host es distinta a 0. De no serlo, valdrá 0.
*** La cvar cl_lw en un jugador define si tiene su prediccion activada (para mejor fluidez). ***
► Arg: player
Valdrá 1 si ent es un jugador. De no serlo, valdrá 0.
Esto es simplemente para ahorrar una llamada más para detectar si es veridicamente un player o no.
► Arg: pSet
pSet es un puntero al PVS del host , seteado en la funcion exportada SetupVisibility (llamada en cada jugador antes de llamar multiples veces AddToFullPack) con respecto a la posicion del jugador. PVS significa "Potentially Visible Set", que vendria siendo "Conjunto Potencialmente Visible" (el traductor se portó bien). La logica implementada, es simple: "Si una entidad se encuentra en el PVS de un jugador, será enviada al mismo jugador; de no serlo, no se enviará", así evitando el envio innecesario de paquetes al player con informacion que no servira. (es por esto que algunos cheats muestran jugadores hasta un cierto punto.)
► return (int)
Si la funcion le devuelve 1 al engine, la entidad será transmitida al cliente.
Si la funcion le devuelve 0 al engine, no será transmitida.
*** Este valor puede ser obtenido con get_orig_retval() hookeando la funcion en Post. ***
*** Se puede hacer override del return, utilizando forward_return(FMV_CELL, ...) hookeando la funcion en Pre. ***
__________________________________________________________
Ya con la mayoria de estos datos aclarados, puedo ceder a mostrar la función escrita en el HLSDK (en C++) que filtra las entidades que deben ser enviadas y que no.
/// = comentario mio
Código PHP:
/*
AddToFullPack
Return 1 if the entity state has been filled in for the ent and the entity will be propagated to the client, 0 otherwise
state is the server maintained copy of the state info that is transmitted to the client
a MOD could alter values copied into state to send the "host" a different look for a particular entity update, etc.
e and ent are the entity that is being added to the update, if 1 is returned
host is the player's edict of the player whom we are sending the update to
player is 1 if the ent/e is a player and 0 otherwise
pSet is either the PAS or PVS that we previous set up. We can use it to ask the engine to filter the entity against the PAS or PVS.
we could also use the pas/ pvs that we set in SetupVisibility, if we wanted to. Caching the value is valid in that case, but still only for the current frame
*/
int AddToFullPack( struct entity_state_s *state, int e, edict_t *ent, edict_t *host, int hostflags, int player, unsigned char *pSet )
{
int i;
// don't send if flagged for NODRAW and it's not the host getting the message
if ( ( ent->v.effects == EF_NODRAW ) &&
( ent != host ) )
return 0; /// es por eso que con el flag EF_NODRAW en _effects, uno no ve a la entidad.
// Ignore ents without valid / visible models
if ( !ent->v.modelindex || !STRING( ent->v.model ) )
return 0; /// si la entidad no tiene model, para que necesitamos saber de ella?
// Don't send spectators to other players
if ( ( ent->v.flags & FL_SPECTATOR ) && ( ent != host ) )
{
return 0; /// desde cuando los espectadores se han visto? jajaja
}
// Ignore if not the host and not touching a PVS/PAS leaf
// If pSet is NULL, then the test will always succeed and the entity will be added to the update
if ( ent != host )
{
if ( !ENGINE_CHECK_VISIBILITY( (const struct edict_s *)ent, pSet ) ) /// en esta funcion especifica, es donde el engine pregunta si la entidad esta en el PVS del host, lo que explique arriba
{
return 0;
}
}
// Don't send entity to local client if the client says it's predicting the entity itself.
if ( ent->v.flags & FL_SKIPLOCALHOST )
{
if ( ( hostflags & 1 ) && ( ent->v.owner == host ) )
return 0; /// si ent tiene el flag FL_SKIPLOCALHOST, tiene como owner al host, y host tiene la cvar cl_lw activada, no se enviara la entidad (alguna caracteristica oculta quizas)
}
if ( host->v.groupinfo )
{
/// Aqui hay un pequeño juego con groupinfo. Es algo dificil de entender pero luego de unas variadas lecturas del codigo se entendera.
UTIL_SetGroupTrace( host->v.groupinfo, GROUP_OP_AND );
// Should always be set, of course
if ( ent->v.groupinfo )
{
if ( g_groupop == GROUP_OP_AND ) /// siempre sera GROUP_OP_AND, ya que previamente setea el groupop asi
{
if ( !(ent->v.groupinfo & host->v.groupinfo ) )
return 0;
}
else if ( g_groupop == GROUP_OP_NAND )
{
if ( ent->v.groupinfo & host->v.groupinfo )
return 0;
}
}
/// En conclusion:
/// Si el jugador tiene un groupinfo seteado (un bit al azar, definido por los modders)
/// Y si la entidad tambien tiene un groupinfo seteado
/// La entidad seria transmitida, si ambos tienen el mismo bit asignado.
UTIL_UnsetGroupTrace();
}
/// Esta es la parte hardcore, donde se copian muchos entvars a la estructura que sera procesada por el engine.
memset( state, 0, sizeof( *state ) );
// Assign index so we can track this entity from frame to frame and
// delta from it.
state->number = e;
state->entityType = ENTITY_NORMAL;
// Flag custom entities.
if ( ent->v.flags & FL_CUSTOMENTITY ) /// las entidades "beams" son un poco magicas, ya que algunas con definir su posicion y un angulo provocan un rayo laser hasta donde choque, y es gracias a esto
{
state->entityType = ENTITY_BEAM;
}
//
// Copy state data
//
// Round animtime to nearest millisecond
state->animtime = (int)(1000.0 * ent->v.animtime ) / 1000.0;
memcpy( state->origin, ent->v.origin, 3 * sizeof( float ) );
memcpy( state->angles, ent->v.angles, 3 * sizeof( float ) );
memcpy( state->mins, ent->v.mins, 3 * sizeof( float ) );
memcpy( state->maxs, ent->v.maxs, 3 * sizeof( float ) );
memcpy( state->startpos, ent->v.startpos, 3 * sizeof( float ) );
memcpy( state->endpos, ent->v.endpos, 3 * sizeof( float ) );
state->impacttime = ent->v.impacttime;
state->starttime = ent->v.starttime;
state->modelindex = ent->v.modelindex;
state->frame = ent->v.frame;
state->skin = ent->v.skin;
state->effects = ent->v.effects;
// This non-player entity is being moved by the game .dll and not the physics simulation system
// make sure that we interpolate it's position on the client if it moves
if ( !player &&
ent->v.animtime &&
ent->v.velocity[ 0 ] == 0 &&
ent->v.velocity[ 1 ] == 0 &&
ent->v.velocity[ 2 ] == 0 )
{
state->eflags |= EFLAG_SLERP;
}
state->scale = ent->v.scale;
state->solid = ent->v.solid;
state->colormap = ent->v.colormap;
state->movetype = ent->v.movetype;
state->sequence = ent->v.sequence;
state->framerate = ent->v.framerate;
state->body = ent->v.body;
for (i = 0; i < 4; i++)
{
state->controller[i] = ent->v.controller[i];
}
for (i = 0; i < 2; i++)
{
state->blending[i] = ent->v.blending[i];
}
state->rendermode = ent->v.rendermode;
state->renderamt = ent->v.renderamt;
state->renderfx = ent->v.renderfx;
state->rendercolor.r = ent->v.rendercolor.x;
state->rendercolor.g = ent->v.rendercolor.y;
state->rendercolor.b = ent->v.rendercolor.z;
state->aiment = 0;
if ( ent->v.aiment )
{
state->aiment = ENTINDEX( ent->v.aiment );
}
state->owner = 0;
if ( ent->v.owner )
{
int owner = ENTINDEX( ent->v.owner );
// Only care if owned by a player
if ( owner >= 1 && owner <= gpGlobals->maxClients )
{
state->owner = owner;
}
}
// HACK: Somewhat...
// Class is overridden for non-players to signify a breakable glass object ( sort of a class? )
if ( !player )
{
/// Activado este valor en las entidades "func_breakable", se vera que sus decals de balas seran agujeros de vidrios.
/// https://amxmodx-es.com/Thread-como-remover-las-puertas-ventanas-del-map?pid=36803#pid36803
state->playerclass = ent->v.playerclass;
}
// Special stuff for players only
if ( player )
{
memcpy( state->basevelocity, ent->v.basevelocity, 3 * sizeof( float ) );
state->weaponmodel = MODEL_INDEX( STRING( ent->v.weaponmodel ) );
state->gaitsequence = ent->v.gaitsequence;
state->spectator = ent->v.flags & FL_SPECTATOR;
state->friction = ent->v.friction;
state->gravity = ent->v.gravity;
// state->team = ent->v.team;
//
state->usehull = ( ent->v.flags & FL_DUCKING ) ? 1 : 0;
state->health = ent->v.health;
}
return 1;
}
Y aqui un intento mio de mostrar como es utilizado pfnAddToFullPack en el engine, dentro de la funcion SV_WriteEntitiesToClient() (con mucha ayuda de OSHLDS, OpenHLDS, y un decompilado parcial de engine_i686.so)
Código PHP:
#define MAX_PACKET_ENTITIES 256
void SV_WriteEntitiesToClient(client_t *client, sizebuf_t *msg)
{
unsigned int i, clientflags, visible_entities, isplayer;
byte *pvs, *pas, *pvs_ptr;
client_t *loopclient;
edict_t *ent
entity_state_t *entity_state_ptr, entities[MAX_PACKET_ENTITIES];
pvs = NULL;
pas = NULL;
gEntityInterface.pfnSetupVisibility(client->target, client->edict, &pvs, &pas);
// code code ...
visible_entities = 0;
clientflags = (client->cl_lw != 0);
pvs_ptr = pvs;
for(i = 1; i < sv.num_edicts; i++)
{
ent = &sv.edicts[i];
if(i > 0 && i < svs.allocated_client_slots)
isplayer = 1;
else
isplayer = 0;
if(isplayer != 0)
{
loopclient = &svs.clients[i-1];
if((loopclient->active == 0 && loopclient->spawned == 0) || loopclient->hltv != 0)
continue;
}
if(visible_entities > MAX_PACKET_ENTITIES)
{
Con_Printf("Too many entities in visible packet list.\n");
break;
}
entity_state_ptr = &entities[visible_entities];
if(gEntityInterface.pfnAddToFullPack(entity_state_ptr, i, ent, client->edict, clientflags, isplayer, pvs_ptr))
{
visible_entities++;
}
}
// code code...
}
Con el codigo parcial escrito arriba se entiende un poco mejor el caso. SV_WriteEntitiesToClient() es llamado en cada jugador, y esta es llamada todos los frames.
Y asi es como al final, todas las entidades filtradas son comprimidas y enviadas al cliente.
__________________________________________________________
Ya con la mayoria explicada, podemos pasar a filosofar un poco la utilidad de esta misma funcion.
- Gracias a AddToFullPack podemos hacer que ciertos users vean distintas cosas que a otros jugadores. Dicho este detalle, es por que siempre asocian esta funcion con, por ejemplo, jugadores que pueden ser vistos por unos pocos.
- Como esta explicado, es una funcion que, se repite por cada jugador, y dentro de esta, pregunta por cada entidad. Date una idea de cuantas veces es llamado AddToFullPack por frame, con 32 players, habiendo cerca de 50 entidades presentes. Aquí es donde debes hablar de optimizacion real
- ¿Alguna vez has llegado al punto de tener multiples entidades presentes en el juego, y terminas no viendo a todos? Claro, es porque hay un limite de 256 entidades que pueden ser transferidas al cliente en un frame.
- AddToFullPack esta ubicado al final de la rutina del engine que realiza cada frame.
- El argumento pSet, que es un puntero al PVS del host de la funcion, puede ser usado adjunto la funcion exportada CheckVisibility (la misma que vez arriba como ENGINE_CHECK_VISIBILITY), ya que esta requiere un puntero de un PVS, para verificar si una entidad esta en el conjunto potencialmente visible de la posicion del jugador.
- ¿Alguna vez te preguntaste como funcionan los wallhack? Bueno, creo que aqui es donde llegas a aclarar un poco esa duda. ¿Como crees que los otros jugadores (especificamente en cualquier juego) son transmitidos a los que juegan? Los wallhacks aprovechan esos datos para ser mostrados pese esten detras de una muralla o no. OJO que el Conjunto Potencialmente Visible, como se describe a si mismo, no dice que son las entidades que esta viendo o no, si no las que "potencialmente puede ver" (daaaah)
Despues pondré unos ejemplos maliciosos de que cosillas puedes hacer con esta funcion
Espero les haya gustado (quien sea lo haya leido entero) esta guia que me costo un tiempo escribiendo a la velocidad de la luz (?), cualquier duda pueden preguntar como también corregirme algún detalle que haya errado/olvidado.
Strings en goldsrc (STRING, ALLOC_STRING, MAKE_STRING) - GoldSrc: gamedll, hlsdk, etc - Obteniendo nombres de texturas de mapas - FindEntityByString y derivados - Detectar Ataques de Knife - El Parametro fNoMonsters de los Traces - Funcion: AddToFullPack - Compresión de digitos - Native: register_event
► Si vas a pedirme ayuda con code vía Mensaje Privado, anda pensando primero como me vas a cargar la PayPal... ◄
► Si vas a pedirme ayuda con code vía Mensaje Privado, anda pensando primero como me vas a cargar la PayPal... ◄