Autopsie technique d'un jeu que même son studio ne peut plus reconstruire
The Witcher 1 tourne sur une version lourdement modifiée du moteur Aurora de BioWare, la même base que Neverwinter Nights. CD Projekt l'a rebaptisée « Electron » et l'a poussée bien au-delà de ses origines.
Le format d'archive est hérité de BioWare, mais CDPR l'a modifié. La variante Witcher utilise des entrées de 26 octets au lieu de 22 dans le fichier KEY (16 octets de nom + 2 de type + 4 d'index + 4 d'index BIF). On a dû reverse-engineer le format pour extraire quoi que ce soit.
2009, jour 111 (21 avril) — date de compilation de l'Enhanced Edition
Réparties en 45 types différents : textures, scripts Lua, dialogues, sons, templates GFF, journaux de quête...
The Witcher utilise deux langages de script simultanément : NWScript (hérité de Neverwinter Nights) pour la logique de jeu, et Lua pour l'interface et les systèmes temps-réel.
Écrit par Arkadiusz Sito chez CD Projekt Red. Un système d'IA complet avec machine à états, gestion de groupes, formations, et perception :
// File: inc_ai.nss // Author: Arkadiusz Sito // Copyright (c) CD Projekt Red Studio void _ResetModifiers() { object oPlayer = GetFirstPC(); _SetModCurrentTarget(-2, oPlayer); _SetModTargetIsPC(-10, oPlayer); _SetModCloser(-3, oPlayer); _SetModGuardian(-20, oPlayer); _SetModDefensive(-20, oPlayer); _SetModPCAttacker(-60, oPlayer); _SetModMaxAttackers(5, oPlayer); _SetModMaxAttackersAmount(4, oPlayer); }
Chaque PNJ a un profil comportemental. Les commentaires sont restés en polonais dans le code :
// tw> Sprawdza, jaki behaviour ma ustawiona postac int GetBehaviourType(object oSource); // as> Zeruje wszystkie profile zachowan u postaci void ClearBehaviours(object oSource); // as> Ustawia konkretny typ zachowania void SetBehaviour(int nBehaviour, object oSource); // as> Aktualizuje licznik napastnikow void _AdjustAttackers(int nDelta, object oSource); // as> Dodaje postac do druzyny obiektu oLeader void AddToParty(object oLeader, object oSource); // as> Okresla nowy cel ataku z uwzgednieniem zasad podzialu przeciwnikow object DetermineNewTarget(object oIntruder, object oSource);
Le fichier source complet du mini-jeu de poker aux dés, avec des notes de développement en polonais absolument savoureuses :
--[[ TODO: BUGZORY: - w momencie kiedy zapauzuje gre przed odpaleniem minigry a ona sie zalaczy to nie da sie odpauzowc bo spacja nie jest handlowana :) → "quand on met pause avant de lancer le mini-jeu et qu'il se lance quand même, on ne peut plus enlever la pause car espace n'est pas géré :)" + mozna kliknac kostke w locie a ona wtedy wroci do puli zamiast wpasc na baze :) → "on peut cliquer un dé en vol et il retourne dans le pool au lieu d'atterrir sur la base :)" DTODO11 : bug - ai rzuca kostkami 2 razy :) → "bug — l'IA lance les dés 2 fois :)" ]]
Le fichier .luc est du bytecode Lua compilé, mais les définitions d'attaque sont en Lua source lisible :
-- Définitions d'attaque du Ghoul DefAttack { WeaponType = "Monster", Level = "Basic", Name = "m0_ghoul_att", NumberOfHits = 2, Damage = { Medium = "Attack", Min = 20, Max = 24 }, Alternates = { { -- attaque spéciale : empoisonnement Helper = { Damage = { Min = 36, Max = 48 }, DefenderEffects = { { Type = "Poisoning", BaseIntensity = 105 }, } }, Conditions = { Chance = 0.28 } }, } }
4 252 lignes de définitions API. Au-delà des fonctions Aurora/BioWare standard, CDPR a ajouté des dizaines de fonctions spécifiques au Witcher :
// Adds current grease ability to item. U can remove it with ClearGrease. // - sAbility - nazwa identyfikujaca zdolnosc // - nMinutes - czas trwania zdolnosci w minutach swiata gry. 0 - ability nie konczy sie. int AddGreaseAbility(string sAbility, object oItem, int nMinutes = 0); void ClearGrease(object oItem); // tw> wejscie w fistfight void EnterFistfightMode(object oCreature); // tw> wyjscie z fistfightu void LeaveFistfightMode(object oCreature); // ds> Otwiera panel "przelewu" zlota (np. do przekupstwa innego creature'a). void OpenPlayerBribePanel(object oPlayer, object oTarget, int nAmount); // as> Finds the best waypoint to hideout of rain. object GetBestRainHideout(object oSource); // as> Internal function. void _DespawnBecouseOfRain(object oSource); // ^ "Becouse" — faute gravée dans le binaire void SlotDlaTomka01(); void SlotDlaTomka02(); void SlotDlaMichala5(); void SlotDlaMichala6();
Les initiales des développeurs sont visibles dans les commentaires de l'API :
Les statistiques réelles de chaque monstre, extraites directement du code source Lua. Cliquez sur un monstre pour voir ses données complètes.
Les dialogues sont stockés en format GFF (Generic File Format) de BioWare, variante V3.3. Chaque fichier .dlg contient un arbre de nœuds NPC et de réponses joueur, liés par des index. Texte original en polonais, traduit ci-dessous.
Le système de combat repose sur des multiplicateurs de dégâts par type de médium (acier vs argent), des probabilités d'attaques alternatives, et un système d'huile de lame.
Chaque monstre a un Steel_Mult et un Silver_Mult. C'est la mécanique centrale : l'épée d'acier pour les humains, l'argent pour les monstres. Ce n'est pas du lore, c'est un multiplicateur de dégâts codé en dur.
-- Exemple : l'Alp MediumResistance = { Steel_Mult = 0.25, -- l'acier ne fait que 25% des dégâts Silver_Mult = 2.0, -- l'argent fait 200% des dégâts Attack_Mult = 0.25, -- les attaques physiques non-épée : 25% }
AddGreaseAbility)La fameuse mécanique d'application d'huile sur les lames. Dans le code moteur, c'est une fonction C++ exposée au NWScript :
// Ajoute une huile à une arme. Durée en minutes de jeu. 0 = permanent. int AddGreaseAbility(string sAbility, object oItem, int nMinutes = 0); void ClearGrease(object oItem);
Chaque monstre a une attaque de base et des alternatives déclenchées par probabilité. L'Alp, par exemple :
2 coups, 24-30 dégâts chacun
100% de chance (fallback)
1 coup, 48-60 dégâts
19% de chance
1 coup, 48-60 dégâts + Stun (intensité 105)
32% de chance
1 coup, 48-60 dégâts + Knockdown
46% de chance
Fichier attackeffects.2da, qui définit les effets visuels (traînées, sang, blessures) par type de coup :
| Type | Lueur | Traînée | Sang | Blessures |
|---|---|---|---|---|
| Parade | fx_parry_metal | — | — | Non |
| Normal | fx_sword_g_01 | fx_wpntrail01 | fx_blood_l | Non |
| Critique | fx_sword_g_01 | fx_wpntrail01 | fx_bleeding00 | Oui |
| Finisher | fx_sword_g_01 | fx_wpntrail01 | fx_bleeding01 | Oui + fx supplémentaire |
| Combat à mains nues | — | — | fx_blood_z (tête) | Non |
93 profils d'affiliation encodent toute la politique du monde de The Witcher. Chaque PNJ porte des drapeaux d'appartenance, d'hostilité, et de peur.
Oui, il y a un système d'alcool codé dans les profils. Chaque PNJ peut avoir une tolérance à l'alcool :
const int PROFILE_TYPE_ALCOHOL_MILD_3 = 27; // léger, 3 verres const int PROFILE_TYPE_ALCOHOL_MILD_5 = 28; // léger, 5 verres const int PROFILE_TYPE_ALCOHOL_MILD_7 = 29; // léger, 7 verres const int PROFILE_TYPE_ALCOHOL_MEDIUM_3 = 30; // moyen, 3 verres const int PROFILE_TYPE_ALCOHOL_MEDIUM_5 = 31; // moyen, 5 verres const int PROFILE_TYPE_ALCOHOL_STRONG_7 = 35; // fort, 7 verres
Le code est truffé de traces humaines : commentaires en polonais, fonctions placeholder, fautes d'orthographe immortalisées dans l'API, notes de frustration dans les TODO.
Dans l'API du moteur (4 252 lignes de fonctions C++), on trouve des emplacements vides réservés à des développeurs individuels :
void SlotDlaTomka01(); — littéralement « Emplacement pour Tomek #1 »void SlotDlaTomka02();void SlotDlaMichala5(); — « Emplacement pour Michał #5 »C'est la version game dev de réserver des constantes dans un enum « au cas où ». Sauf qu'ici, c'est des fonctions moteur entières réservées par prénom.
void _DespawnBecouseOfRain(object oSource);int _IsDespawningBecouseOfRain(object oSource);« Becouse » au lieu de « Because ». Gravé dans l'API binaire du moteur. Impossible à corriger sans casser tous les scripts qui l'appellent.
Le fichier minigame_dices.lua (420 Ko !) est une capsule temporelle de game dev. Le développeur documentait ses bugs directement dans le code source :
Le code mélange librement polonais et anglais, parfois dans la même ligne :
// ds> Otwiera panel "przelewu" zlota (np. do przekupstwa innego creature'a). // "Ouvre le panneau de transfert d'or (par ex. pour corrompre une créature)" void OpenPlayerBribePanel(object oPlayer, object oTarget, int nAmount); // msl> Zerowanie poziomu postaci. Jej talentów, doświadczenia i poziomu. // "Remise à zéro du niveau du personnage. Ses talents, expérience et niveau." void ResetXP(object oCreature); // Zwraca 1 jezeli aktualnie zaladowany modul to savegame. // "Retourne 1 si le module actuellement chargé est une sauvegarde." int IsModuleSaveGame();
Les noms internes des chapitres sont en polonais :
Composée par Adam Skorupa, la bande originale de The Witcher mélange orchestration slave, chœurs médiévaux et instruments folkloriques polonais. Extraite directement du répertoire Soundtracks du jeu.
Textures extraites directement des archives BIF en résolvant le format KEY/BIF modifié par CD Projekt. Portraits de journal, atlas UV de modèles 3D, textures de monstres.







Les fameuses « cartes de conquête » que Geralt collecte. Illustrations peintes à la main, 1024×1024, stockées dans le BIF localisé (localized00.bif) car les versions censurées variaient par région. On a dû d'abord trouver les placeholders dans textures00.bif avant de localiser les vraies dans un second fichier KEY.



























Les textures « dépliées » qui s'appliquent sur les modèles 3D. On voit la géométrie du mesh dans la disposition des éléments :



