Et bien voilà, maintenant que je commence à avoir un peu de bouteille en nwscript (notamment grâce à ce forum et à ses estimés autochtones
), voici ma première et modeste contribution, qui j'espère sera utile à certains d'entre vous.
L'idée de base de ce système est de reproduire plus ou moins le système qu'utilise Ultima Online pour spawner ses créatures : un "point de spawn" est placé sur la carte, et celui-ci est paramétré pour spawner telle créature, tel nombre, etc. Je le préfère personnellement au sytème classique de rencontres par triggers de NWN, pas vraiment flexible et vraiment ch... à gérer.
Comment ça marche en pratique : on utilise des waypoints personnalisés transformés pour l'occasion en "spawnpoints", que l'on place sur la carte. On peut spawner des créatures ET des plaçables ainsi (utilité pour les plaçables : ils peuvent être détruits, déverrouillés, etc, sans souci, ils seront respawnés intacts).
Chaque "spawnpoint" possède un tag particulier du type SP_a_b_c_d :
- a est le type d'objet à spawner, créature (C), plaçable (P), spécial (S). Personnellement j'utilise le type spécial pour les PNJ majeurs et créatures uniques.
- b est le tag de l'objet à spawner.
- c est le nombre d'objets à spawner sur ce spawnpoint.
- d est la fréquence d'apparition, exprimée F_(un nombre entre 1 et 20), représentant les chances de spawn sur un jet de d20. Ce dernier paramètre peut être absent si la rencontre n'est pas aléatoire, mais planifiée (100% de chances de spawn).
Par exemple, pour spawner 1d6 orques de tag ORC et de resref orc001 sur 12 ou moins sur un jet de d20(fréquence), le tag du spawnpoint sera le suivant: SP_C_ORC_1D6_F12
Les spawnpoints sont activés dès qu'un personnage entre dans la zone, et les créatures et plaçables ainsi créés détruits dès que la zone est vide pour économiser les ressources. A noter que les spawns sont uniques, quand une zone est vide de spawns, elle le reste tant que tous les joueurs présents n'ont pas quitté la zone. Un délai personnalisable permet, éventuellement, de retarder la destruction lorsque la zone est vide, pour éviter le camping de spawns en quittant / revenant dans la zone.
Avec une liste de spawnpoints précréés dans la palette, on arrive à un système, je trouve, très souple, car on peut recréer en deux clics des "tables de rencontres" papier, de manière individuelle pour chaque point de spawn, entièrement basées sur le hasard. En retravaillant un peu le script, on peut facilement arriver à permettre de créer également plusieurs types de créatures avec un seul point de spawn.
ATTENTION : le CR et le nombre des créatures spawnées est totalement indépendant du niveau et du nombre des personnages. Tout est uniquement basé sur les paramètres des "points de spawn", que le builder peut placer comme il le souhaite dans ses zones.
Premier script, à placer sur le OnEnter de vos zones :
#include "lib_spwn_crtrs"
#include "lib_spwn_plcbls"
#include "lib_spwn_spcls"
void ActivateSpawnpoint(object oSpawnpoint, string sSpawnpoint)
{
/*On récupère les coordonnées du spawnpoint dans la zone
et le type d'objet à spawner, puis on spawne l'objet d'après
une liste dans la bibliothèque correspondante.*/
location lSpawnpoint = GetLocation(oSpawnpoint);
if ((GetStringLeft(sSpawnpoint, 5) == "SP_C_"))
{
string sCreature = GetSpawnType_Creature(sSpawnpoint);
CreateObject(OBJECT_TYPE_CREATURE, sCreature, lSpawnpoint);
}
else if ((GetStringLeft(sSpawnpoint, 5) == "SP_P_"))
{
string sPlaceable = GetSpawnType_Placeable(sSpawnpoint);
CreateObject(OBJECT_TYPE_PLACEABLE, sPlaceable, lSpawnpoint);
}
else if ((GetStringLeft(sSpawnpoint, 5) == "SP_S_"))
{
string sSpecial = GetSpawnType_Special(sSpawnpoint);
CreateObject(OBJECT_TYPE_CREATURE, sSpecial, lSpawnpoint);
}
}
void SearchForSpawnpoints()
{
/*On fouille la zone à la recherche de spawnpoints, et on
vérifie qu'ils n'ont pas déjà spawné un objet, auquel cas on
active le spawn et on place une variable locale indiquant qu'il a
fait son job.*/
object oSpawnpoint = GetFirstObjectInArea();
while (GetIsObjectValid(oSpawnpoint))
{
string sSpawnpoint = GetTag(oSpawnpoint);
if ((GetStringLeft(sSpawnpoint, 3) == "SP_")&&(GetLocalInt(oSpawnpoint, "NO_SPAWN") == FALSE))
{
SetLocalInt(oSpawnpoint, "NO_SPAWN", TRUE);
ActivateSpawnpoint(oSpawnpoint, sSpawnpoint);
}
oSpawnpoint = GetNextObjectInArea();
}
}
void main()
{
/*On vérifie qu'au moment où un personnage entre dans la
zone, aucun autre joueur n'est déjà présent. Si oui, on déclenche
le processus de spawn et on incrémente le compteur individuel
de personnages dans la zone de 1.*/
int nPCNumber = GetLocalInt(OBJECT_SELF, "PC_NUMBER");
object oEntering = GetEnteringObject();
if (GetIsPC(oEntering))
{
nPCNumber ++;
SetLocalInt(OBJECT_SELF, "PC_NUMBER", nPCNumber);
if (nPCNumber == 1)
SearchForSpawnpoints();
}
}
Les trois bibliothèques correspondantes aux trois différents types d'objet à spawner (avec quelques exemples représentatifs, à vous de modifier les tags et les resrefs correspondants). Les fonctions Random vous permettent de définir plusieurs blueprints d'une même créature (avec, par exemple, un équipement différent), dont un sera choisi aléatoirement par le script :
Créatures :
string GetSpawnType_Creature(string sSpawnpoint)
{
int nRoll = d20();
int nNumber;
// RANDOM ENCOUNTERS
if (sSpawnpoint == "SP_C_KOBOLD_1_F8")
{
if (nRoll <= 8)
{
switch (Random(4))
{
case 0 : return "kobold001"; break;
case 1 : return "kobold002"; break;
case 2 : return "kobold003"; break;
case 3 : return "kobold004"; break;
}
}
}
else if (sSpawnpoint == "SP_C_KOBOLD_1D2_F14")
{
if (nRoll <= 14)
{
for (nNumber = d2(); nNumber > 0; nNumber --)
{
switch (Random(4))
{
case 0 : return "kobold001"; break;
case 1 : return "kobold002"; break;
case 2 : return "kobold003"; break;
case 3 : return "kobold004"; break;
}
}
}
}
// PLANNED ENCOUNTERS
else if (sSpawnpoint == "SP_C_KOBOLD_GUARD_1")
{
switch (Random(2))
{
case 0 : return "koboldguard001"; break;
case 1 : return "koboldguard002"; break;
}
}
return "default_crtr";
}
Plaçables :
string GetSpawnType_Placeable (string sSpawnpoint)
{
// MISCELLANEOUS INTERIOR
if (sSpawnpoint == "SP_P_CHAIR")
return "chair001";
else if (sSpawnpoint == "SP_P_COUCH")
return "couch001";
// CONTAINERS
else if (sSpawnpoint == "SP_P_HERB")
return "herb001";
else if (sSpawnpoint == "SP_P_FERN")
return "fern001";
return "default_plcbl";
}
Spécial :
string GetSpawnType_Special(string sSpawnpoint)
{
if (sSpawnpoint == "SP_S_DELDUIN")
return "delduin001";
else if (sSpawnpoint == "SP_S_WALDO")
return "waldo001";
return "default_spcl";
}
Dernier script à placer en OnExit de vos zones :
void DestroyObjectsInArea()
{
/*Fonction de destruction de tous les objets présents dans la
zone, exceptés les créatures dont le tag commence par PERM_ et
les plaçables dont le tag commence par STATIC_*/
object oObjectToDestroy = GetFirstObjectInArea();
while (GetIsObjectValid(oObjectToDestroy))
{
if (
GetObjectType(oObjectToDestroy) == OBJECT_TYPE_ITEM ||
GetObjectType(oObjectToDestroy) == OBJECT_TYPE_CREATURE &&
GetStringLeft(GetTag(oObjectToDestroy), 5) != "PERM_" ||
GetObjectType(oObjectToDestroy) == OBJECT_TYPE_PLACEABLE &&
GetStringLeft(GetTag(oObjectToDestroy), 7) != "STATIC_"
)
DestroyObject(oObjectToDestroy);
oObjectToDestroy = GetNextObjectInArea();
}
}
void UpdateSpawnpoints()
{
//Fonction qui retire le "no spawn".
object oSpawnpoint = GetFirstObjectInArea();
while (GetIsObjectValid(oSpawnpoint))
{
if (GetStringLeft(GetTag(oSpawnpoint), 3) == "SP_")
DeleteLocalInt(oSpawnpoint, "NO_SPAWN");
oSpawnpoint = GetNextObjectInArea();
}
}
void UpdateArea()
{
/*On décrémente le compteur de 1 chaque fois qu'un joueur
quitte la zone, rétroactivement selon le délai fixé en fonction
principale. Si la zone est vide à ce moment, on détruit les objets
présents et on retire la variable locale empêchant le spawn des
spawnpoints.*/
int nPCNumber = GetLocalInt(OBJECT_SELF, "PC_NUMBER");
if (GetIsPC(GetExitingObject()))
{
nPCNumber --;
SetLocalInt(OBJECT_SELF, "PC_NUMBER", nPCNumber);
if (nPCNumber == 0)
{
DestroyObjectsInArea();
UpdateSpawnpoints();
}
}
}
void main()
{
/*Délai à modifier ou supprimer selon le temps que l'on
souhaite avant remise à zéro du système.*/
DelayCommand(30.0, UpdateArea());
}