X
 
  • Filtre
  • Heure
  • Afficher
Tout nettoyer
nouveaux messages

  • Ajouter un nouveau NPC voyageant dans les tavernes

    1.Introduction

    Dans Mount & Blade (ainsi que sur Warband et WFAS), les tavernes sont fréquentées par toutes sortes de personnes : des rançonneurs, des poètes itinérants, des marchands de livres, des mercenaires, des compagnons, ...
    Dans ce tutoriel, je vais vous apprendre à créer un nouveau personnage, à le faire voyager de tavernes en tavernes ainsi que lui donner un dialogue (minimal, car les dialogues ne sont pas le sujet de ce tutoriel).

    Prérequis :
    -Savoir utiliser le module système : ICI
    -Un peu de patience et de logique
    -Pour modifier le module système, j'utilise l'excellent Notepad++, bien plus agréable à utiliser que le Bloc-Notes. Je vous conseille fortement de faire de même.
    -Un niveau de compréhension de l'anglais correct (ou un niveau de compréhension du "français Google Traduction", ça marche aussi )

    Pour moi, un bon tutoriel ne se résume pas à "Faites, faites cela, priez, et normalement ça marche", aussi, je m'efforcerai d'expliquer chaque ligne de code, de façon à ce que vos connaissances puissent être réutilisées à d'autres fins.

    Je modifierai la Native de Warband.

    2.Code

    A.Créer le/les personnage(s)

    Pour créer votre personnage, vous devrez ouvrir le fichier module_troops.py. Ce fichier contient tous les personnages (troupes) du jeu : non seulement les soldats réguliers swadiens, vaegirs, etc. mais aussi tous les seigneurs, le joueur, les taverniers, anciens de village, marchand de livres, mercenaires, villageois, ... Bref, tous les personnages.

    Chaque troupe est organisée autour d'un tuple. Un tuple est un ensemble d'informations séparées par une virgule et délimité par des crochets.
    Un exemple de tuple :
    Code:
    ["tavern_bookseller_2","Book_Merchant","Book_Merchant",tf_hero|tf_is_merchant|tf_randomize_face, 0, reserved, fac_commoners,[itm_fur_coat,itm_hide_boots,
                   itm_book_wound_treatment_reference, itm_book_leadership, itm_book_intelligence, itm_book_trade, 
                   itm_book_engineering, itm_book_weapon_mastery],def_attrib|level(5),wp(20),knows_common,merchant_face_1, merchant_face_2],
    Un tuple est entre crochets, et les informations sont séparées par des virgules.
    ["npc1","Borcha","Borcha",tf_hero|tf_unmoveable_in_party_window, 0, reserved, fac_commoners,[itm_khergit_armor,itm_nomad_boots,itm_knife],
    str_8|agi_7|int_12|cha_7|level(3),wp(60),knows_tra cker_npc|
    knows_ironflesh_1|knows_power_strike_1|knows_pathf inding_3|knows_athletics_2|knows_tracking_1|knows_ riding_2,
    #skills 2/3 player at that level
    0x00000004bf086143259d061a9046e23500000000001db52c 0000000000000000],
    • "npc1","Borcha","Borcha" : L'ID est utilisé dans les fichiers du jeu pour indiquer qu'on parle de cette troupe-ci. Pas d'espace, beaucoup d'abréviations. Les deux informations suivantes sont le nom au singulier (en anglais) puis le nom au pluriel. Borcha étant unique, il n'y a pas de nom au pluriel, donc on met le même.

    • tf_hero|tf_unmoveable_in_party_window, : les flags (drapeaux) du personnage. Ils sont séparés par des | et possèdent un préfixe (ici, tf_ pour Troop Flag). Le terme français le plus approprié pour parler des flags serait attribut. Borcha possède deux flags : tf_hero, qui indique que c'est un héros, ce qui implique qu'il est unique, qu'il ne peut pas mourir, ... tf_unmoveable_in_party_window signifie que vous ne pouvez pas le mettre en garnison dans un château ou une ville, que vous ne pouvez pas le mettre dans "prisonniers sauvés" après une bataille etc. Référez-vous au fichier header_troops.py pour avoir plus de flags.

    • 0, reserved, : le premier indique où l'on peut trouver le personnage (nous, nous le ferons avec des scripts, ce qui offre plus de possibilités) et le deuxième sera toujours à 0 ou reserved.

    • fac_commoners représente la faction du personnage, avec le préfixe fac_. Les factions sont définies dans Module_factions.py. L’intérêt est que si vous êtes en mauvaise relation avec le royaume de Swadia, vous aurez un malus de moral si vous avez des soldats swadiens, etc.

    • [itm_khergit_armor,itm_nomad_boots,itm_knife], représente l'équipement. Pour les marchands, vous pouvez aussi leur mettre des marchandises. Encore une fois, on préférera généralement utiliser des scripts. Pour les troupes régulières, c'est-à-dire qui n'ont pas tf_hero, sachez que si vous mettez deux armures, certains auront la première, d'autres la deuxième, d'autres aucune. Toutefois ce n'est pas l'objet de ce tuto.

    • str_8|agi_7|int_12|cha_7|level(3),wp(60),knows_tra cker_npc|
      knows_ironflesh_1|knows_power_strike_1|knows_pathf inding_3|knows_athletics_2|knows_tracking_1|knows_ riding_2,
      : définit les attributs, compétences et maîtrises d'armes. Référez-vous au fichier header_troops.py.

    • #skills 2/3 player at that level est ce qu'on appelle un commentaire. C'est-à-dire du code sans effet qui sert à indiquer ce que vous voulez. Vous pouvez très bien mettre des indications en français "ce code-ci fait ça". Ils servent uniquement à la lisibilité. Sur Notepad++, ils sont en verts. Un commentaire commence par # et prend tout ce qui suit ET est sur la même ligne.

    • 0x00000004bf086143259d061a9046e23500000000001db52c 0000000000000000 : le code hexadécimal du visage. Vous pouvez l'obtenir en activant le mode édition avant de lancer le jeu, puis en faisant Ctrl+E en modifiant votre visage pour obtenir le code.
      Vous pouvez remplacer un code hexadécimal par des "types" tels rhodok_face_young_1, swadian_woman_face_1, etc. définis au début du module_troops (vers la 150ème ligne environ). Dans ce cas, pensez à utiliser le flag tf_randomize_face.




    Vous pouvez retrouver ces informations au début du module_troops :
    Spoiler:
    Code:
    #  Each troop contains the following fields:
    #  1) Troop id (string): used for referencing troops in other files. The prefix trp_ is automatically added before each troop-id .
    #  2) Toop name (string).
    #  3) Plural troop name (string).
    #  4) Troop flags (int). See header_troops.py for a list of available flags
    #  5) Scene (int) (only applicable to heroes) For example: scn_reyvadin_castle|entry(1) puts troop in reyvadin castle's first entry point
    #  6) Reserved (int). Put constant "reserved" or 0.
    #  7) Faction (int)
    #  8) Inventory (list): Must be a list of items
    #  9) Attributes (int): Example usage:
    #           str_6|agi_6|int_4|cha_5|level(5)
    # 10) Weapon proficiencies (int): Example usage:
    #           wp_one_handed(55)|wp_two_handed(90)|wp_polearm(36)|wp_archery(80)|wp_crossbow(24)|wp_throwing(45)
    #     The function wp(x) will create random weapon proficiencies close to value x.
    #     To make an expert archer with other weapon proficiencies close to 60 you can use something like:
    #           wp_archery(160) | wp(60)
    # 11) Skills (int): See header_skills.py to see a list of skills. Example:
    #           knows_ironflesh_3|knows_power_strike_2|knows_athletics_2|knows_riding_2
    # 12) Face code (int): You can obtain the face code by pressing ctrl+E in face generator screen
    # 13) Face code (int)(2) (only applicable to regular troops, can be omitted for heroes):
    #     The game will create random faces between Face code 1 and face code 2 for generated troops
    # 14) Troop image (string): If this variable is set, the troop will use an image rather than its 3D visual during the conversations


    Maintenant, nous allons passer à la création de notre/nos personnage(s). Pour ma part, je vais m'inspirer du marchand de livres 2 :

    Code:
    ["tavern_bookseller_2","Book_Merchant","Book_Merchant",tf_hero|tf_is_merchant|tf_randomize_face, 0, reserved, fac_commoners,[itm_fur_coat,itm_hide_boots,
                   itm_book_wound_treatment_reference, itm_book_leadership, itm_book_intelligence, itm_book_trade, 
                   itm_book_engineering, itm_book_weapon_mastery],def_attrib|level(5),wp(20),knows_common,merchant_face_1, merchant_face_2],
    Je vais créer trois personnages, et je vais les appeler affectueusement "Henry", "Jean" et "Jacques" :
    Code:
    ["npc_henry","Henry","Henry",tf_hero|tf_is_merchant, 0, reserved, fac_commoners,[itm_fur_coat,itm_hide_boots, itm_book_leadership,itm_hide_boots
                   ],def_attrib|level(5),wp(20),knows_common,0x000000090000214136db6db6db6db6db00000000001db6db0000000000000000],
      ["npc_jacques","Jacques","Jacques",tf_hero|tf_is_merchant, 0, reserved, fac_commoners,[itm_fur_coat,itm_hide_boots, itm_book_leadership,itm_hide_boots
                   ],def_attrib|level(5),wp(20),knows_common,0x000000090000214136db6db6db6db6db00000000001db6db0000000000000000],
      ["npc_jean","Jean","Jean",tf_hero|tf_is_merchant, 0, reserved, fac_commoners,[itm_cuir_bouilli,itm_plate_boots, itm_book_leadership,itm_hide_boots
                   ],def_attrib|level(5),wp(20),knows_common,0x000000090000214136db6db6db6db6db00000000001db6db0000000000000000],
    Vous remarquerez que je leur ai mis un visage prédéfini (le même pour les trois, ce sont des triplés )
    Mettez le code en dessous du tuple du tavern_bookseller_2.

    B.Script de voyage

    Encore une fois, inspirons-nous du script des marchands de livres, dans le module_scripts :
    Code:
    #script_update_booksellers
      # INPUT: none
      # OUTPUT: none
      ("update_booksellers",
        [(try_for_range, ":town_no", towns_begin, towns_end),
           (party_set_slot, ":town_no", slot_center_tavern_bookseller, 0),
         (try_end),
    	  
         (try_for_range, ":troop_no", tavern_booksellers_begin, tavern_booksellers_end),
           (store_random_in_range, ":town_no", towns_begin, towns_end),
           (party_set_slot, ":town_no", slot_center_tavern_bookseller, ":troop_no"),
         (try_end),
    
         ]),
    Une fois de plus, nous avons affaire à un tuple, bien plus simple toutefois :
    1. le nom d'identification du script entre guillemets
    2. les opérations qui en découlent, entre crochets. C'est le script en lui-même.

    Chaque opération est entre parenthèses et se termine par un virgule. La première donnée à l'intérieur des parenthèses est le nom de l'opération (il y en a des centaines, aidez-vous du fichier header_operations, très utile), suivi d'un ou plusieurs paramètres. Le nombre de paramètres dépend de l'opération. Nous allons donc examiner ligne par ligne le code.

    Code:
    (try_for_range, ":town_no", towns_begin, towns_end),
    try_for_range est une opération qui permet de répéter plusieurs fois un bout de code. Un try_for_range va toujours de pair avec un try_end, et c'est le code entre les deux qui sera exécuté plusieurs fois. Le premier paramètre (ici ":town_no") est la variable où l'on stockera une valeur, qui sera différente à chaque répétition. Pour ceux qui ont déjà fait de la programmation, on dira que c'est une boucle, que la condition et que la variable :town_no ont une valeur inférieure à towns_end, qu'au début de la boucle elle vaut towns_begin, qu'elle augmente de 1 à chaque fois, et que towns_begin et towns_end sont des constantes. En fait, chaque ville possède un n° d'ID et towns_begin vaut town_1 et towns_end vaut un de plus que town_22, qui est la dernière ville.

    Pour ceux qui n'ont jamais programmé et pour qui la notion de variable est inconnue, on dira qu'une variable est une donnée qui peut changer au cours de l'exécution du programme, donc du jeu (une donnée qui peut varier quoi).

    Sur M&B, il y a deux types de variables : les variables globales, qui sont utilisables partout dans le module système et commencent par un $ (par exemple "$g_talk_troop") et les variables locales qui ne sont utilisables que dans le fichier où elle sont déclarées (crées). C'est le cas de ":town_no".

    En bref, on retiendra ici que cette ligne signifie que le code qui suit, jusqu'au premier try_end, sera exécuté autant de fois qu'il y a de villes (soit, 22 fois) et qu'à chaque nouvelle exécution, la variable locale ":town_no" indiquera quelle ville on "traite".

    Ici, la variable :town_no contiendra toujours le nom de la ville (ou plus précisément, son numéro d'ID, mais ça on s'en fiche ). Toutefois elle peut changer de valeur, par exemple, si à un moment elle contient "town_1", elle contiendra à un moment "town_15". En théorie, rien ne l'empêche de contenir autre chose bien sûr
    Code:
    try_for_range     = 6 # Works like a for loop from lower-bound up to (upper-bound - 1)
    		      # (try_for_range,<destination>,<lower_bound>,<upper_bound>),
    Les informations du header_operations résument bien ce que je dis.



    Code:
     (party_set_slot, ":town_no", slot_center_tavern_bookseller, 0),
    Cette ligne signifierait, en français : "Dans la ville ":town_no", mettez la valeur 0 au slot "slot_center_tavern_bookseller".
    Qu'est-ce qu'un slot ? C'est une sorte de sous-variable liée à un personnage, une ville, une armée, ... (dans le module_system, une ville est une armée )
    Chaque slot est en fait un nombre. Ainsi, une ville peut contenir un grand nombre de slots. "slot_center_tavern_bookseller" est défini dans module_constants :
    Code:
    slot_center_tavern_bookseller     = 98
    Ainsi, ce bout de code signifie "Au slot 98 de la ville ":town_no", assignez la valeur 0". Dans ce cas-ci, 0 est équivalent à "rien".

    Code:
    party_set_slot                  = 501 # (party_set_slot,<party_id>,<slot_no>,<value>),
    Comme toujours, le header_operations vous sera d'une grande aide, surtout si vous vous débrouillez seul.

    Code:
         (try_end),
    C'est la fin du try_for_range.

    Code:
    (try_for_range, ":town_no", towns_begin, towns_end),
           (party_set_slot, ":town_no", slot_center_tavern_bookseller, 0),
         (try_end),
    En résumé, les 3 lignes que nous venons voir peuvent se traduire en français par quelque chose du genre : "Dans la ville 1, mettre la valeur 0 au slot slot_center_tavern_bookseller (ce qui sera utilisé plus tard pour dire qu'il n'y a pas de marchand de livres dans la taverne de la ville 1). Ensuite, faire de même dans la ville 2, puis 3, etc. Jusqu'à avoir fait toutes les villes."
    Donc, ces lignes permettent de dire au jeu de bouger les marchands de livres de toutes les tavernes. Sans ces 3 lignes, un même marchand de livres pourrait être dans 5 villes à la fois !


    Code:
    (try_for_range, ":troop_no", tavern_booksellers_begin, tavern_booksellers_end),
           (store_random_in_range, ":town_no", towns_begin, towns_end),
           (party_set_slot, ":town_no", slot_center_tavern_bookseller, ":troop_no"),
         (try_end),
    Ce code sera exécuté deux fois : la première fois, :troop_no vaudra le premier marchand de livres, la deuxième fois :troop_no vaudra le deuxième.
    Code:
           (store_random_in_range, ":town_no", towns_begin, towns_end),
           (party_set_slot, ":town_no", slot_center_tavern_bookseller, ":troop_no"),
    Regardons dans le header_operations :
    Code:
    store_random_in_range  = 2136	# gets random number in range [range_low,range_high] excluding range_high 
    # (store_random_in_range,<destination>,<range_low>,<range_high>),
    store_random_in_range prendra donc dans ce cas-ci une ville au hasard, et la stockera dans :town_no.
    party_set_slot placera le marchand de livres :troop_no dans la ville :town_no. Cette deuxième partie du script se résume donc à "prendre un ville au hasard et y mettre le marchand de livres 1. Prendre une ville aléatoire et y mettre le marchand de livres 2."



    Vous vous demandez sûrement ce que sont towns_begin, towns_end, tavern_booksellers_begin et booksellers_end. En fait, quand on dit "la variable :town_no contient Uxkhal", il serait plus juste de dire que :town_no contient le numéro d'identification (ID). Pour les villes, vous pouvez retrouver leur numéro d'identification dans ID_parties.py, et pour les personnages, dans ID_troops.py. towns_begin est en fait une constante, qui contient l'ID de la ville 1 (Sargoth). Towns_end contient le château 1, dont l'ID est un de plus que la ville 22. Sachez qu'il en est de même pour les troupes (tavern_booksellers_end contient trp_tavern_minstrel_1, mais nous devrons corriger ça car nous avons ajouté nos NPCs en dessous du marchand de livres 2).

    Autres constantes : les slots ! Comme dit plus haut, un slot est un nombre. Il est donc lui aussi défini dans module_constants, le fichier où sont définies toutes les constantes.
    Voici donc les extraits qui nous intéressent :
    Code:
    tavern_minstrels_begin = "trp_tavern_minstrel_1"
    
    tavern_booksellers_begin = "trp_tavern_bookseller_1"
    tavern_booksellers_end   = tavern_minstrels_begin
    
    slot_center_tavern_bookseller     = 98
    Nous allons donc créer un script pour nos compères :
    Code:
    	 ("update_npc",
        [(try_for_range, ":town_no", towns_begin, towns_end),
           (party_set_slot, ":town_no", slot_tavern_npc, 0),
         (try_end),
    	
    	(try_for_range, ":troop_no", npc_begin, npc_end),
           (store_random_in_range, ":town_no", towns_begin, towns_end),
           (party_set_slot, ":town_no", slot_tavern_npc, ":troop_no"),
         (try_end),
    Le code est le même que pour les marchands de livres, avec quelques changements :
    • Le nom du script. Et oui ! Il ne peut PAS y avoir deux scripts de même nom.
    • On utilise de nouvelles constantes : slot_center_tavern_npc, npc_begin et npc_end


    Nous allons définir des nouvelles constantes et en changer quelques unes :
    Code:
    ###nouvelles : 
    slot_tavern_npc                   = 567
    npc_begin = "trp_npc_henry"
    npc_end = tavern_minstrels_begin
    
    
    #changées :
    tavern_booksellers_begin = "trp_tavern_bookseller_1"
    tavern_booksellers_end   = tavern_ministrels_begin
    ####devient : 
    tavern_booksellers_begin = "trp_tavern_bookseller_1"
    tavern_booksellers_end   = npc_begin
    Si vous avez bien suivi, vous devriez comprendre l'origine du changement.
    Astuce : si vous voulez savoir où sont vos personnages, ajoutez ces 3 lignes :
    Cela aura pour effet d'afficher en bas à gauche, à chaque déplacement, des phrases comme "Henry dort à la taverne de Halmar." Sachez que s4 et s5 sont des variables de registres. C'est-à-dire prédéfinies (pour leur nom). Notez que le "s" veut dire "string" (chaine de caractères).


    C'est bien beau, nous avons créé un script tout frais. Encore faut-il l'activer ! Les bardes itinérants, rançonneurs, marchands de livres, voyageurs et cie ne changent de position que dans 2 cas de figures : le début du jeu, et tous les trois jours.

    Commençons par le début du jeu : dans le module_scripts, cherchez "script_update_booksellers". Normalement, vous ne trouverez que deux occurrences : celle que nous cherchons, et celle qui est un commentaire juste avant la définition du script.

    Un extrait de ce que nous cherchons, et la ligne que nous allons ajouter :

    C'est une partie du script game_start, qui se lance au début du jeu, au moment où vous cliquez sur "commencer une partie".

    Maintenant, ouvrez le module_simples_triggers, et ajoutez ceci entre deux tuples :

    Toutes les 48h, nos compères changeront de location. Bien sûr, vous pouvez changer le délai.

    C.Le placer dans la taverne

    Ceux qui ont déjà modifié des scènes auront sûrement remarqué des petites flèches jaunes, les points d'entrée. Ils sont numérotés.
    Pour ajouter un personnage sur une scène, il faut utiliser le code comme ceci :
    Code:
    (set_visitor, numero_point_entree, personnage),
    Dans le module_game_menus, cherchez cette ligne :
    Code:
    (party_get_slot, ":tavern_bookseller", "$current_town", slot_center_tavern_bookseller),
    Cette ligne fait partie du code qui permet d'ajouter les marchands de livres (ces actions se déroulent quand on clique sur "visiter la taverne").
    Code:
    (try_begin),
                   (party_get_slot, ":tavern_bookseller", "$current_town", slot_center_tavern_bookseller),
                   (gt, ":tavern_bookseller", 0),
                   (set_visitor, ":cur_entry", ":tavern_bookseller"),
                   (val_add, ":cur_entry", 1),
                 (try_end),
    Le schéma du code est simple : on prend la valeur du slot slot_center_tavern_bookseller de la ville $current_town (pas besoin d'être un génie en anglais pour comprendre) et on la stocke dans :tavern_bookseller.
    Si cette variable vaut plus que 0, donc si elle contient une unité, on l'ajoute au point d'entrée :cur_entry et on augmente sa valeur de 1. Ainsi, si vous avez un marchand de livres au point 3, le prochain NPC ajouté sera au 4.

    Adaptons-le aux NPCs, et copions-le en dessous :
    Code:
    (try_begin),
                   (party_get_slot, ":tavern_npc", "$current_town", slot_tavern_npc),
                   (gt, ":tavern_npc", 0),
                   (set_visitor, ":cur_entry", ":tavern_npc"),
                   (val_add, ":cur_entry", 1),
                 (try_end),
    Le code est facile à comprendre si vous avez bien suivi.

    D.Dialogue de base

    Simple : cherchez
    Code:
    [anyone, "start", [(is_between, "$g_talk_troop", tavern_booksellers_begin, tavern_booksellers_end),
                         ],
    et juste au-dessus, entrez
    Code:
    [anyone, "start", [(is_between, "$g_talk_troop", npc_begin, npc_end),(str_store_troop_name, s11, "$g_talk_troop"),],
       "I'm {s11}, and you ?", "my_npc_talk", []],
       
       [anyone|plyr, "my_npc_talk", [],
       "I am me. Bye.", "close_window", []],
    C'est un simple dialogue de bonjour/aurevoir, où le joueur sera assez cassant. Notez que le texte est en anglais : normal, c'est pour faciliter les traductions.
    $g_talk_troop est une variable qui contient le personnage auquel on parle.



    3.Conclusion

    J'espère que ce tutoriel vous aura été utile, que j'ai été compréhensible, que vous allez en redemander etc.
    N'hésitez pas à poser vos questions, qu'il s'agisse de questions théoriques ("J'ai pas trop bien compris pourquoi on fait ça à ce moment-là") ou de problèmes ("Quand je compile, il y a une erreur.", "Je vois personne ").
    Dernière modification par ALG, 01-06-2012, 20h03.

  • #2
    Je ne l'avais pas vu. Très bon boulot
    +1 rep.

    Commentaire

    Chargement...
    X