Les fichiers ENT
Les fichiers additifs ent ont pour but de modifier certains aspects d'une map comme le changement de textures, l'ajout ou la suppression de points de respawn, la modification des décors, etc. Si vous voulez en connaître plus, veuillez consulter sur le site, tous les articles liés à cette fonction, notamment dans la rubrique MDK, et plus précisément dans la sous rubrique ENT Editor.
Les codes édités ci-après s'appliquent principalement aux sources CRSBOT mais restent compatibles avec les sources ROCMOD à la différence d'un seul fichier relatif à la gestion des Waypoints. Cette différence sera développée in fine, ainsi qu'une documentation sur la création des répertoires d'accueil des fichiers ent et des routes associées.
Ces codes ont été inspirés des sources GOLDRUSH dans la gestion des fichiers aef. L'idée de la multiplicité des fichiers ent pour une même map, m'est apparue évidente en exploitant le mode MDK.
Fichier g_local_h
Après
qboolean G_ParseSpawnVars( qboolean inSubBSP ); |
Ajoutez
qboolean G_ParseSpawnAEVars( qboolean inSubBSP ); // FSMOD |
A la fin du fichier ajoutez
extern vmCvar_t g_mapVar; // FSMOD |
Fichier g_bot.c
Après
static botSpawnQueue_t botSpawnQueue[BOT_SPAWN_QUEUE_DEPTH]; |
Ajoutez
qboolean G_findAEFile( const char* gametype ); // FSMOD
static fileHandle_t aefFile; // FSMOD
|
A la fin de la fonction G_DoesMapSupportGametype, après
Ajoutez
|
// FSMOD GO
if ( G_findAEFile( gametype ))
{
return qtrue;
}
return qfalse;
}
qboolean G_findAEFile( const char* gametype )
{
char entPath[128];
vmCvar_t mapname;
int len;
aefFile = 0;
trap_Cvar_Register( &mapname, "mapname", "", CVAR_SERVERINFO | CVAR_ROM, 0.0, 0.0 );
// first up, try finding an .ent file with the gametype extension in mvar folders
switch (g_mapVar.integer)
{
case 1:
Com_sprintf(entPath, 128, "maps/ent/mvar1/%s_%s.ent\0", mapname.string, level.gametypeData->name);
break;
case 2:
Com_sprintf(entPath, 128, "maps/ent/mvar2/%s_%s.ent\0", mapname.string, level.gametypeData->name);
break;
case 3:
Com_sprintf(entPath, 128, "maps/ent/mvar3/%s_%s.ent\0", mapname.string, level.gametypeData->name);
break;
case 4:
Com_sprintf(entPath, 128, "maps/ent/mvar4/%s_%s.ent\0", mapname.string, level.gametypeData->name);
break;
case 5:
Com_sprintf(entPath, 128, "maps/ent/mvar5/%s_%s.ent\0", mapname.string, level.gametypeData->name);
break;
case 6:
Com_sprintf(entPath, 128, "maps/ent/mvar6/%s_%s.ent\0", mapname.string, level.gametypeData->name);
break;
case 7:
Com_sprintf(entPath, 128, "maps/ent/mvar7/%s_%s.ent\0", mapname.string, level.gametypeData->name);
break;
case 8:
Com_sprintf(entPath, 128, "maps/ent/mvar8/%s_%s.ent\0", mapname.string, level.gametypeData->name);
break;
default:
Com_sprintf(entPath, 128, "maps/ent/%s_%s.ent\0", mapname.string, level.gametypeData->name);
break;
}
len = trap_FS_FOpenFile(entPath, &aefFile, FS_READ);
//file not found
if (!aefFile)
{
return qfalse;
}
trap_FS_FCloseFile(aefFile);
//file is too big, we cant read it
if (len >= 131072)
{
Com_Printf( S_COLOR_RED "G_findAEFile: file %s found, but too large (%i)! Max allowed %i bytes!", entPath, len, 131072);
return qfalse;
}
// found file
return qtrue;
// FSMOD stop
|
Fichier g_spawn.c
Après
Ajoutez
|
// FSMOD GO
qboolean G_LoadAEFile(void);
char *G_GetEntFileToken(void);
static fileHandle_t entFile;
extern char *buffer;
// FSMOD STOP
|
Dans la fonction G_ParseSpawnVars, à la fin du groupe
|
if (inSubBSP)
{
HandleEntityAdjustment();
}
return qtrue;
}
|
Ajoutez
|
// FSMOD GO
qboolean G_ParseSpawnAEVars( qboolean inSubBSP )
{
char keyname[MAX_TOKEN_CHARS];
char com_token[MAX_TOKEN_CHARS];
char *token;
level.numSpawnVars = 0;
level.numSpawnVarChars = 0;
// parse the opening brace
token = G_GetEntFileToken();
if (!token)
{
return qfalse;
}
Com_sprintf(com_token, sizeof(com_token), "%s", token);
if ( com_token[0] != '{' )
{
Com_Error( ERR_FATAL, "G_ParseSpawnVars: found %s when expecting {",com_token );
}
// go through all the key / value pairs
while ( 1 )
{
// parse key
token = G_GetEntFileToken();
if (!token)
{
Com_Error( ERR_FATAL, "G_ParseSpawnVars: EOF without closing brace" );
}
Com_sprintf(keyname, sizeof(keyname), "%s", token);
if ( keyname[0] == '}' )
{
break;
}
// parse value
token = G_GetEntFileToken();
if (!token)
{
Com_Error( ERR_FATAL, "G_ParseSpawnVars: EOF without closing brace" );
}
Com_sprintf(com_token, sizeof(com_token), "%s", token);
if ( com_token[0] == '}' )
{
Com_Error( ERR_FATAL, "G_ParseSpawnVars: closing brace without data" );
}
if ( level.numSpawnVars == MAX_SPAWN_VARS )
{
Com_Error( ERR_FATAL, "G_ParseSpawnVars: MAX_SPAWN_VARS" );
}
AddSpawnField(keyname, com_token);
}
if (inSubBSP)
{
HandleEntityAdjustment();
}
return qtrue;
}
//FSMOD STOP
|
Dans la fonction G_SpawnEntitiesFromString remplacez le groupe
|
if (!inSubBSP)
{
level.spawning = qfalse;
}
|
Par celui ci
|
// FSMOD GO
if (!inSubBSP)
{
if (G_LoadAEFile())
{
while(G_ParseSpawnAEVars(qfalse))
{
G_SpawnGEntityFromSpawnVars(qfalse);
}
}
}
if (!inSubBSP)
{
level.spawning = qfalse;
}
// FSMOD STOP
|
A la fin du fichier g_spawn.c, après le groupe
trap_LinkEntity ( ent );
} |
Ajoutez
// FSMOD GO
qboolean G_LoadAEFile(void) //GR3.0: Check (and read) AEF files (additional entities file)
{
char entPath[128];
vmCvar_t mapname;
int len;
entFile = 0;
trap_Cvar_Register( &mapname, "mapname", "", CVAR_SERVERINFO | CVAR_ROM, 0.0, 0.0 );
// first up, try finding an .ent file with the gametype extension in mvar folders
switch (g_mapVar.integer)
{
case 1:
Com_sprintf(entPath, 128, "maps/ent/mvar1/%s_%s.ent\0", mapname.string, level.gametypeData->name);
break;
case 2:
Com_sprintf(entPath, 128, "maps/ent/mvar2/%s_%s.ent\0", mapname.string, level.gametypeData->name);
break;
case 3:
Com_sprintf(entPath, 128, "maps/ent/mvar3/%s_%s.ent\0", mapname.string, level.gametypeData->name);
break;
case 4:
Com_sprintf(entPath, 128, "maps/ent/mvar4/%s_%s.ent\0", mapname.string, level.gametypeData->name);
break;
case 5:
Com_sprintf(entPath, 128, "maps/ent/mvar5/%s_%s.ent\0", mapname.string, level.gametypeData->name);
break;
case 6:
Com_sprintf(entPath, 128, "maps/ent/mvar6/%s_%s.ent\0", mapname.string, level.gametypeData->name);
break;
case 7:
Com_sprintf(entPath, 128, "maps/ent/mvar7/%s_%s.ent\0", mapname.string, level.gametypeData->name);
break;
case 8:
Com_sprintf(entPath, 128, "maps/ent/mvar8/%s_%s.ent\0", mapname.string, level.gametypeData->name);
break;
default:
Com_sprintf(entPath, 128, "maps/ent/%s_%s.ent\0", mapname.string, level.gametypeData->name);
break;
}
len = trap_FS_FOpenFile(entPath, &entFile, FS_READ);
if (!entFile)
{ // failing that, just try by map name
Com_sprintf(entPath, 128, "maps/ent/%s.ent\0", mapname.string);
len = trap_FS_FOpenFile(entPath, &entFile, FS_READ);
if (!entFile)
{
Com_Printf( "No additional entity file found (skipping)\n" );
return qfalse;
}
}
Com_Printf(S_COLOR_YELLOW "Reading additional entity data from \"%s\"\n", entPath);
if (len >= 131072)
{
Com_Printf( S_COLOR_RED "file too large: %s is %i, max allowed is %i", entPath, len, 131072);
trap_FS_FCloseFile(entFile);
return qfalse;
}
trap_FS_Read(buffer, len, entFile);
buffer[len] = 0;
trap_FS_FCloseFile(entFile);
return qtrue;
}
static char token[MAX_TOKEN_CHARS];
char *G_GetEntFileToken(void)
{
qboolean hasNewLines = qfalse;
const char *data;
int c = 0, len;
data = buffer;
len = 0;
token[0] = 0;
// make sure incoming data is valid
if (!data)
{
buffer = NULL;
return NULL;
}
while (1)
{
// skip whitespace
data = SkipWhitespace(buffer, &hasNewLines);
if ( !data )
{
buffer = NULL;
return NULL; // EOF
//return token;
}
c = *data;
// skip double slash comments
if (c == '/' && data[1] == '/')
{
data += 2;
while (*data && *data != 'n')
{
data++;
}
}
else if ( c=='/' && data[1] == '*')
{
data += 2;
while (*data && (*data != '*' || data[1] != '/' ))
{
data++;
}
if (*data)
{
data += 2;
}
}
else
{
break;
}
}
// handle quoted strings
if (c == '"')
{
data++;
while (1)
{
c = *data++;
if (c=='"' || !c)
{
token[len] = 0;
buffer = ( char * ) data;
return token;
}
if (len < MAX_TOKEN_CHARS)
{
token[len] = c;
len++;
}
}
}
// parse a regular word
do
{
if (len < MAX_TOKEN_CHARS)
{
token[len] = c;
len++;
}
data++;
c = *data;
} while (c>32);
if ( len == MAX_TOKEN_CHARS )
{
len = 0;
}
token[len] = 0;
buffer = (char *)data;
if ( token[0] == 0 || token[0] == ' ' )
{
return NULL; // EOF
}
return token;
}
// FSMOD STOP
|
Fichier zg_waypoint.c
Dans la fonction G_LoadPoints remplacez le groupe
|
Com_sprintf(routePath, 128, "botroutes/%s_%s.wps\0", mapname.string, level.gametypeData->name);
pointsFile = NULL;
|
Par celui-ci
|
// FSMOD GO
switch (g_mapVar.integer)
{
case 1:
Com_sprintf(routePath, 1024, "botroutes/mvar1/%s_%s.wps\0", mapname.string, level.gametypeData->name);
break;
case 2:
Com_sprintf(routePath, 1024, "botroutes/mvar2/%s_%s.wps\0", mapname.string, level.gametypeData->name);
break;
case 3:
Com_sprintf(routePath, 1024, "botroutes/mvar3/%s_%s.wps\0", mapname.string, level.gametypeData->name);
break;
case 4:
Com_sprintf(routePath, 1024, "botroutes/mvar4/%s_%s.wps\0", mapname.string, level.gametypeData->name);
break;
case 5:
Com_sprintf(routePath, 1024, "botroutes/mvar5/%s_%s.wps\0", mapname.string, level.gametypeData->name);
break;
case 6:
Com_sprintf(routePath, 1024, "botroutes/mvar6/%s_%s.wps\0", mapname.string, level.gametypeData->name);
break;
case 7:
Com_sprintf(routePath, 1024, "botroutes/mvar7/%s_%s.wps\0", mapname.string, level.gametypeData->name);
break;
case 8:
Com_sprintf(routePath, 1024, "botroutes/mvar8/%s_%s.wps\0", mapname.string, level.gametypeData->name);
break;
default:
Com_sprintf(routePath, 1024, "botroutes/%s_%s.wps\0", mapname.string, level.gametypeData->name);
break;
}
// FSMOD STOP
|
A noter que le fichier zg_waypoint.g est propre au mode CRSBOT car il assure la prise en charge des fichiers botroutes en extension wps.
Si vous codez sous ROCMOD il conviendra de mobiliser le fichier ai_wpnav.c qui lui, assure la gestion des fichiers botroutes en extension wnt.
Fichier ai_wpnav.c
Dans la fonction int LoadPathData(const char *filename), remplacez la ligne
Com_sprintf(routePath, 1024, "botroutes/%s.wnt\0", filename); |
Par le groupe
|
// FSMOD GO
switch (g_mapVar.integer)
{
case 1:
Com_sprintf(routePath, 1024, "botroutes/mvar1/%s_%s.wnt\0", filename, bg_gametypeData[gametype].name);
break;
case 2:
Com_sprintf(routePath, 1024, "botroutes/mvar2/%s_%s.wnt\0", filename, bg_gametypeData[gametype].name);
break;
case 3:
Com_sprintf(routePath, 1024, "botroutes/mvar3/%s_%s.wnt\0", filename, bg_gametypeData[gametype].name);
break;
case 4:
Com_sprintf(routePath, 1024, "botroutes/mvar4/%s_%s.wnt\0", filename, bg_gametypeData[gametype].name);
break;
case 5:
Com_sprintf(routePath, 1024, "botroutes/mvar5/%s_%s.wnt\0", filename, bg_gametypeData[gametype].name);
break;
case 6:
Com_sprintf(routePath, 1024, "botroutes/mvar6/%s_%s.wnt\0", filename, bg_gametypeData[gametype].name);
break;
case 7:
Com_sprintf(routePath, 1024, "botroutes/mvar7/%s_%s.wnt\0", filename, bg_gametypeData[gametype].name);
break;
case 8:
Com_sprintf(routePath, 1024, "botroutes/mvar8/%s_%s.wnt\0", filename, bg_gametypeData[gametype].name);
break;
default:
Com_sprintf(routePath, 1024, "botroutes/%s_%s.wnt\0", filename, bg_gametypeData[gametype].name);
break;
}
// FSMOD STOP
|
Gestion des fichiers ENT
Ces codes permettent de gérer au choix 9 entités ENT de 0 à 8. Mais avant de les exploiter il convient de se pencher un peu sur la configuration du serveur.
Plutôt qu'un long discours, voici un extrait de l'arborescence de mon serveur FSMOD.

Vous devrez créer un répertoire maps, dans lequel vous allez créer un répertoire ent et dans lequel vous allez finalement créer autant de répertoires mvar numérotés selon le classement que vous adopterez.
Dans le répertoire botroutes vous allez créer également les mêmes répertoires que vous avez créés dans le répertoire ent. Il est important de souligner que ces répertoires auront la même numérotation.
Voyons maintenant ce que vous allez inscrire dans ces répertoires.
Répertoire maps

Le contenu de ce répertoire appelle une précision importante et tout repose sur le type de fichiers ent que vous voulez exploiter.
Plusieurs cas peuvent ainsi se présenter:
Vous voulez simplement changer les textures d'une map, en ce cas la création d'un fichier ent suffira.
Vous voulez simplement ajouter quelques points de respawn, en ce cas la création d'un fichier ent suffira.
Vous voulez modifier tous les points de respawn, en ce cas il va falloir modifier le fichier bsp des maps concernées. C'est la raison pour laquelle, à ce stade du tutoriel vous constatez la présence de fichiers bsp dans le répertoire maps.
La suppression des points de respawn natifs est absolument nécessaire car l'ajout de nouveaux points n'invalidera pas l'existence des points originels et ces derniers généreront des bots statiques pour ne pas avoir été intégrés dans le canevas des botroutes.
Pour illustrer cette singularité, voici un exemple de la map standard mp_finca dans laquelle une partie du thêatre non accessible dans sa version d'origine a pu être exploitée:
| Version d'origine |
Version modifiée |
 |
 |
Bien entendu les 2 cas font chacun l'objet d'un fichier ent différent, donc un placement dans un dossier mvar différent. Ces 2 fichiers ent inclueront les points de respawn des bots et éventuellement quelques agréments de décor, muret, poste de guet, arbres, etc.., car il est possible de modifier également le thêatre originel de mp_finca. D'ailleurs pour ce thêatre originel vous pouvez recopier les anciens points de respawn ou mieux, en créer de nouveaux. Tout est possible avec les fichiers ent.
Modification des fichiers BSP
Extraire le fichier bsp du fichier de la map concernée soit avec Pakscape, soit en renommant un clone du pk3 en zip puis en le décompressant.
Faire une recherche sur info_player_deathmatch et effacez tous les paragraphes qui renferment ce classname. Veillez à effacer les accolades ouvrantes et fermantes.
{
"classname" "info_player_deathmatch"
"angles" "0 360 0"
"origin" "-5392 -5120 70"
} |
Si vous voulez effacer les points de respawn afférents à d'autres gametypes, effacez tous les paragrahes renfermant la ligne "classname" "gametype_player"
{
"classname" "gametype_player"
"gametype" "inf elim ctf dem"
"angles" "0 90 0"
"origin" "-1248 -1560 74"
"spawnflags" "2"
} |
ATTENTION : Certains programmeurs utilisent le classname gametype_player pour spécifier le DM, ainsi la ligne gametype pourrait se présenter comme suit:
"gametype" "dm tdm inf elim ctf dem" |
Il est évident que ce cas doit faire l'objet d'un même traitement que les les précédents.
A ce stade, il est important de préciser plusieurs points:
- Les fichiers maps pk3 dans le répertoire base du serveur ne doivent jamais être modifiés. Ils servent de lien corrélatif avec leurs homologues inclus dans le répertoire base côté client.
- Le chargement des fichier bsp du répertoire maps s'opère chronologiquement après les fichiers pk3 du répertoire base. C'est donc eux qui délivrent les informations au serveur.
- Les fichiers ent se chargent après le fichier bsp et lui délivrent les modifications.
- Malgré la présence de ces fichiers bsp il n'y aura aucun mismatch client /serveur dans la mesure où les nouveaux éléments codés dans les fichiers ent sont envoyés au client par les échanges de paquets, de telle sorte que le client verra exactement ce que 'voit' le serveur.
Les autres répertoires
Les captures suivantes n'ont qu'une vocation illustrative
Les fichiers botroutes
Le principe de base énonce que si un fichier ent lié à une map a été placé dans le répertoire maps/ent/mvar1 , le fichier route associé à cette map doit être placé impérativement dans le répertoire botroutes/mvar1. La concordance dans la numérotation des répertoires mvar doit être toujours respectée.
Dans la capture suivante on s'aperçoit que les routes sont placées directement dans le répertoire botroutes sans être affectées à un répertoire mvar. La raison est simple, c'est que les maps concernées n'ont pas fait l'objet d'un fichier additif ent. Conférez à cet effet, la sortie "default" de la boucle switch des codes précédents.

A noter toutefois qu'il était tout à fait possible d'inscrire ces routes dans un répertoire mvar et de le préciser au serveur par une commande qui sera décrite plus bas.
La capture suivante illustre par exemple le contenu du répertoire botroutes/mvar4.

IMPORTANT : Les fichiers ent ainsi que les routes associées doivent impératvement être nommés avec un suffixe d'identification du gametype. Ainsi un fichier ent du type mp_finca.ent ou un fichier route du type mp_finca.wps (wnt) ne seront pas reconnus. Il faudra leur préférer les types mp_finca_dm.ent et mp_finca_dm.wps (wnt).
le cvar g_mapVar
Il a pour fonction de fixer l'index numéral de 0 à 8. Il ne doit pas être intégré au fichier de configuration du serveur mais doit être précisé pour chaque map du fichier mapcycle, comme le montrent les extraits suivants:
La map UK_Prague a été enrichie d'un fichier ent dans le répertoire mvar4.
|
map10
{
Command "map UK_Prague;kick allbots"
cvars
{
g_mapVar "4"
g_botsFile "colbot/j.txt"
g_motd "^3[Map 10-v4]"
scorelimit "50"
bot_minplayers "10"
}
}
|
La map mp_hos1 n'a pas été modifiée par un fichier ent, le cvar g_mapVar est donc par défaut à 0.
map 12
{
Command "map mp_hos1;kick allbots"
cvars
{
g_mapVar "0"
g_botsFile "colbot/s.txt"
g_motd "^3[Map 12-v11]"
scorelimit "40"
bot_minplayers "7"
}
} |
Les routes wps et wnt
Je vous invite à consulter sur le présent site, la méthodologie dans l'élaboration des botroutes wps ou wnt . Toutefois à l'époque où elles avaient été décrites, je n'avais pas fait echo des fichiers ent dont la prise en compte est nécessaires dans les scripts de création.
La modification est simple.
Dans le répertoire wps ajouter l'arborescence de répertoires maps/ent et placez dans le répertoire ent les fichiers ent afférents aux maps que vous avez choisies.
Pour les routes wnt procédez de la même façon dans le répertoire wnt.
Dans les 2 cas les fichiers ent peuvent conserver leur suffixe gametype.
Puis procéder sans autre changement, conformément à la méthode développée dans ma documentation.
-=
=-