[English]

NEWS
INTRODUCTION
TERMINOLOGIE
LE CODE SOURCE
LES BUGS
TUTORIAUX
DOWNLOADS
L'AUTEUR

  LES MODS: AJOUTER UN MOUVEMENT POUR LE MARINE AFIN DE RECHARGER SES ARMES

Dans chaque nouveaux jeux FPS en 3D, le joueur a le choix de recherger ses armes lorsqu'il le désire. Dans AvP, il est actuellement impossible de le faire: à la place, le personnage recharge automatiquement s'il n'y a plus de balles.
A travers cela, nous allons voir dans ce tutorial comment coder un nouveau mouvement, qui vous donnera les bases pour étudier une petite partie des armes, le système de configuration des touches du joueur et d'obtenir une astuce sur l'utilisation de chaînes de caractères dans un jeu vidéo. Pour manipuler les textstrings (concept de chaînes de caractères) dans le mod, vous aurez besoin de l'utilitaire Huffcomp de l'archive avptools.

1) Les différentes étapes pour faire un tel mod

Afin de créer ce mouvement, vous aurez besoin d'achever 2 étapes principales.
  • Premièrement, vous devez mettre à jour le menu de Configuration des touches Marine et le code pour gérer les requêtes du joueur sur les touches.
  • Deuxièmement, vous pouvez créer l'effet du mouvement, c-à-d le rechargement de l'arme courante.
2) Mettre à jour le menu de Configuration des touches Marine

Commençons avec le menu: comme tout le monde le sait, vous pouvez redéfinir vous touches de configuration pour chaque espèce et les réinitialiser à un état par défaut. Plus, vous avez une configuration primaire et une secondaire par espèce qui peuvent être utilisées simultanément.

a) gestion des touches

Ici, nous allons définir les touches par défaut pour le mouvement du Marine: 'R' (pour la première config) et rien du tout (pour la seconde config).

AvP possède des constantes pour chaque touche, dépendant de votre langage: celles-ci sont définies dans le fichier platform.h, sous l'enum KEY_ID. Donc celles que nous predront seront les enums KEY_R et KEY_VOID.

Maintenant, ouvrez le fichier usr_io.c et trouvez la définition de l'objet DefaultMarineInputPrimaryConfig pour la section English (nous verrons dans un autre tutorial comment gérer une version d'AvP dans une autre langue). Puis, juste après KEY_TAB,, ajoutez ce qui suit: KEY_R, // ReloadWeapon;. Ensuite, faîtes la même chose avec l'objet DefaultMarineInputSecondaryConfig, mais avec l'enum KEY_VOID.

b) affichage des touches

Maintenant que vous avez ajouté les touches, nous avons besoin de les afficher dans le menu de Configuration des touches Marine.

Tout est question de chaînes de caractères: ouvrez juste le fichier langenum.h et localisez la ligne TEXTSTRING_MARINE_KEY_SHOWSCORES, (qui fait référence à la clé précédente), puis ajoutez la ligne TEXTSTRING_MARINE_KEY_RELOAD_WEAPON, (il s'agira de votre textstring pour le mouvement affiché). Finalement, vous devez déclarer le nouveau textstring dans le fichier ressource "language.txt": AvP stocke chaque chaîne de caractères dans ce fichier (ce qui est utile pour les produits traduits...). Pour des raisons qui seront expliquées dans le troisième chapitre de ce tutorial, nous allons copier ce fichier et apporter des changements à ce dernier. Voici la manipulation:

Note importante: faites une copie de votre fichier "language.txt" original avant de faire ces manipulations, ou vous risquez probablement de le perdre...
  • allez en mode console sous Windows et tappez: huffcomp language.txt mylanguage.txt
    Cela décompressera le premier fichier vers le second.

  • ouvrez le fichier "mylanguage.txt", localisez la seconde ligne "Presenter les Scores" (la première ligne du fichier est pour le Predator) et juste après, placez votre textstring: "Recharger". Sauvegardez et fermez le fichier.

  • maintenant, compressez votre nouveau fichier ressource avec: huffcomp mylanguage.txt my_language.txt.

  • finalement, vous pouvez supprimer le fichier "mylanguage.txt".
c) gestion des requêtes du joueur

Maintenant que les clés sont définies, nous devons les associer avec une entrée joueur: ouvrez le fichier usr_io.h et jetez un coup d'oeil à la structure PLAYER_INPUT_CONFIGURATION. Grâce aux commentaires, nous pouvons voir que la première partie de la structure définie des entrées communes pour les espèces comme Avant, marcher, etc... et que la seconde partie est réservée à chaque espèce. Cela signifie que si nous ajoutons une entrée à la première partie, les 3 espèces la posséderont: dans ce tutorial, seul le Marine peut recharger ses armes, donc nous avons juste besoin de placer l'entrée dans la seconde partie comme suit:
typedef struct
{
unsigned char Forward;
unsigned char Backward;
unsigned char Left;
unsigned char Right;

unsigned char Strafe;
unsigned char StrafeLeft;
unsigned char StrafeRight;
unsigned char LookUp;
unsigned char LookDown;
unsigned char CentreView;
unsigned char Walk;
unsigned char Crouch;

unsigned char Jump;
unsigned char Operate;
unsigned char FirePrimaryWeapon;
unsigned char FireSecondaryWeapon;

union
{
unsigned char NextWeapon; // Predator & Marine
unsigned char AlternateVision; // Alien
};

(...)

union
{
unsigned char Predator_MessageHistory; // Predator
unsigned char Marine_ShowScores; // Marine
};
// unsigned char Predator_Say; // nous partageons cette entrée Predator avec notre nouvelle entrée Marine
union
{
unsigned char Predator_Say; // Predator
unsigned char ReloadWeapon; // Marine // ==> NOTRE NOUVELLE ENTREE
};

unsigned char Predator_SpeciesSay;
unsigned char Predator_ShowScores;
unsigned char ExpansionSpace7;
unsigned char ExpansionSpace8;
} PLAYER_INPUT_CONFIGURATION;
Avec cela, la structure gardera sa taille originale et l'entrée appropriée sera utilisée soit que nous jouons en tant que Marine ou Predator. Et concernant l'Alien ? Comment le moteur peut comprendre que les 2 variables ne correspondent pas à l'Alien ? En fait, il existe des constantes définies au-dessus de la structure qui indiquent combien d'entrées nous possédons pour chaque espèce. Dans cet exemple, vous aurez besoin de changer les lignes
#if MARINE_DEMO||DEATHMATCH_DEMO
#define NUMBER_OF_MARINE_INPUTS 27
#else
#define NUMBER_OF_MARINE_INPUTS 27
#endif
en:
#if MARINE_DEMO||DEATHMATCH_DEMO
#define NUMBER_OF_MARINE_INPUTS 27
#else
#define NUMBER_OF_MARINE_INPUTS 28
#endif
Okay, donc nous avons notre nouvelle entrée ReloadWeapon: dans AvP, cette variable sera accessible à partir de la table KeyboardInput (KeyboardInput[primaryInput->ReloadWeapon] pour la première config et KeyboardInput[secondaryInput->ReloadWeapon] pour la seconde).

L'entrée joueur devrait maintenant correspondre à la requête joueur: dans le fichier bh_types.h, localisez la structure PLAYER_INPUT_REQUESTS et à la fin de la section Marine, placez la ligne unsigned int Rqst_Reload :1;. Maintenant ouvrez le fichier usr_io.c et dans la procédure void InitPlayerGameInput(STRATEGYBLOCK* sbPtr), sous la ligne playerStatusPtr->Mvt_InputRequests.Mask2 = 0;, ajoutez celle-ci: playerStatusPtr->Mvt_InputRequests.Flags.Rqst_Reload = 0;.
Voyez la note spéciale à la fin de ce tutorial si vous voulez plus détails sur cette ligne...

La dernière chose à faire est d'associer l'entrée joueur à la requête joueur dans la procédure void ReadPlayerGameInput(STRATEGYBLOCK* sbPtr): dans "character specific abilities", la partie Marine, ajoutez ce qui suit juste avant l'opérande break;:
if(KeyboardInput[primaryInput->ReloadWeapon]
||KeyboardInput[secondaryInput->ReloadWeapon])
playerStatusPtr->Mvt_InputRequests.Flags.Rqst_Reload = 1;
A ce point, le jeu peut être compilé, mais FAITES ATTENTION DE NE PAS L'EXECUTER AVANT D'AVOIR LU LE TROISIEME CHAPITRE, ou vous bousillerez vos fichiers de configuration...

3) Créer l'effet de mouvement

Maintenant, nous allons coder l'effet de presser la touche par défaut 'R': premièrement, nous allons créer un nouveau mouvement joueur et nous adapterons le système de gestion d'état des armes.
a) le nouveau mouvement joueur

Okay, donc tout ce que nous avons besoin de faire à present est de coder le nouveau mouvement joueur: ceci est implémenté dans le fichier pmove.c. Prenez la procédure void ExecuteFreeMovement(STRATEGYBLOCK* sbPtr), et juste après l'opérande switch et les lignes
if (playerStatusPtr->Mvt_InputRequests.Flags.Rqst_Operate)
OperateObjectInLineOfSight();
ajoutez le code suivant:
// Si le joueur est un Marine et demande à recharger
if ((AvP.PlayerType == I_Marine)&&(playerStatusPtr->Mvt_InputRequests.Flags.Rqst_Reload))
{
PLAYER_WEAPON_DATA *weaponPtr;
TEMPLATE_WEAPON_DATA *twPtr;

// Obtenir l'arme courante du joueur
weaponPtr = &(playerStatusPtr->WeaponSlot[playerStatusPtr->SelectedWeaponSlot]);

// Si nous avons assez de balles et ne rechargeons pas encore
if((weaponPtr->PrimaryMagazinesRemaining > 0) && (weaponPtr->CurrentState != WEAPONSTATE_RELOAD_PRIMARY))
{
// Nous changeons l'arme en un état de rechargement
weaponPtr->CurrentState = WEAPONSTATE_RELOAD_PRIMARY;
weaponPtr->StateTimeOutCounter = WEAPONSTATE_INITIALTIMEOUTCOUNT;

// Nous obtenons le template (modèle) correspondant à l'arme...
twPtr = &TemplateWeapon[weaponPtr->WeaponIDNumber];

// ...et exécutons la fonction de gestion d'état de l'arme
(*twPtr->WeaponStateFunction[weaponPtr->CurrentState])((void *)playerStatusPtr,weaponPtr);
}
}
b) adaptation du système de gestion d'état des armes

Par défaut, les armes se rechargent directement lorsque les compteurs de balles se trouvent UNIQUEMENT à 0. Dans notre mod, nous avons besoin de recharger MEME s'il y a toujours des balles: c'est pourquoi nous devons changer le système de gestion d'état des armes pour nous permettre une telle chose.

Ouvrez juste le fichier weapons.c, localisez la procédure void UpdateWeaponStateMachine(void) et changez la section suivante (ajoutez juste ce qui est en bleu):
case WEAPONSTATE_RELOAD_PRIMARY:
{
/* load a new magazine */
TEMPLATE_AMMO_DATA *templateAmmoPtr = &TemplateAmmo[twPtr->PrimaryAmmoID];
// ICI ! if (weaponPtr->PrimaryRoundsRemaining==0) {
if (weaponPtr->WeaponIDNumber==WEAPON_TWO_PISTOLS) {
/* Two pistols reloads BOTH primary and secondary. */
if (weaponPtr->PrimaryMagazinesRemaining) {
weaponPtr->PrimaryRoundsRemaining = templateAmmoPtr->AmmoPerMagazine;
weaponPtr->PrimaryMagazinesRemaining--;
}
if (weaponPtr->SecondaryMagazinesRemaining) {
weaponPtr->SecondaryRoundsRemaining = templateAmmoPtr->AmmoPerMagazine;
weaponPtr->SecondaryMagazinesRemaining--;
}
} else {
/* Grenade launcher has already reloaded at this point. */
weaponPtr->PrimaryRoundsRemaining = templateAmmoPtr->AmmoPerMagazine;
weaponPtr->PrimaryMagazinesRemaining--;
}
// ICI AUSSI ! }
weaponPtr->CurrentState = WEAPONSTATE_IDLE;
weaponPtr->StateTimeOutCounter=0;
playerStatusPtr->Encumberance=twPtr->Encum_Idle;

break;
}
4) Utiliser le mod

Ce type de mod ne peut pas être utilisé directement dans votre répertoire AvP pour une simple raison: nous avons changé le système de configuration, donc cela ne sera pas compatible avec l'AvP original. C'est pourquoi la manière la plus simple de l'utiliser est de copier votre dossier AvP, placer votre exécutable dans cette copie et supprimer votre/vos profil(s).

Après cela, vous pouvez démarrer le jeu, sélectionner n'importe quel mode de jeu, tester votre nouveau mouvement, changer votre configuration...

5) Conclusion

Nous avons vu que pour ajouter un tel mouvement joueur, nous avions du changer le menu de Configuration des touches Marine, en ajoutant un nouvel item dans les la liste des mouvements que nous avons appelé "Recharger" et en mettant à jour le système d'entrées et de requêtes joueur. Après cela, nous avons ajouté le mouvement, lié en interne à ce système par le moteur, pour finalement modifier le système de gestion d'état des armes pour combler nos désirs.

Notez que le présent mod vous permet de recharger quand vous le sougaitez, même si le chargeur de votre arme est plein, plus seules les balles primaires ont été prises en considération.

J'espère que ce tutorial pluis évolué vous donnera des idées et/ou vous apprendra quelque chose.

Note spéciale:
Avant d'ajouter la ligne "unsigned int Rqst_Reload :1;", vous pouvez remarquer que la somme des longeurs en bits des autres variables est égale à 32 bits, ce qui correspond à la taille exacte du type "unsigned int". Cela signifie qu'en ajoutant notre ligne, nous augmentaons la taille de la structure "PLAYER_INPUT_REQUESTS" dans le jeu, c-à-d la taille de l'union "Mvt_InputRequests" dans la structure "PLAYER_STATUS".

Quand les entrées joueur sont remises à 0 (c-à-d avec la procédure "void InitPlayerGameInput(STRATEGYBLOCK* sbPtr)"), les lignes "playerStatusPtr->Mvt_InputRequests.Mask = 0;" et "playerStatusPtr->Mvt_InputRequests.Mask2 = 0;" remettent à 0 les drapeaux de requêtes jusqu'à une limite de 32 bits (taille du type des 2 masques), donc si nous ne mettons pas manuellement à 0 notre nouveau drapeau de requête, le jeu essayera de recharger indéfiniment l'arme courante !

Donc en clair: si vous voulez créer des nouveaux mouvements avec des nouvelles requêtes, n'oubliez pas de réinitialiser chaque nouveau drapeau de requête manuellement.