Benutzer-Werkzeuge

Webseiten-Werkzeuge


Skripttutorial

Das Tutorial ist so aufgebaut, dass am Ende eine kleine Mission steht, die den Spieler ein Schwert für einen Nsc holen lässt, im Verlauf wird, an den entsprechenden Stellen erklärt, wie eine Teilaufgabe umzusetzen ist. Z.B. wird für eine hol mir ein Schwert Mission natürlich ein tolles Schwert benötigt, also wird zu diesem Zeitpunkt die Item-Klasse erklärt und dann mit Inhalt gefüllt, bis ein entsprechendes Schwert vorliegt, das dann ins Spiel eingefügt und dort benutzt werden kann. Sollte sich ein Script in G1 und G2 unterscheiden, liegt es jeweils in zwei Fassungen vor. Scripte zu denen kein G2 gegenstück existiert können so übernommen werden.

Der Auftraggeber

Um einen neuen Nsc im Spiel zur Verfügung zu haben muß eine Instanz der Klasse C_Npc erstellt werden. Im folgenden wird erst kurz ein allgemeiner Abriss der Klasse gegeben und diese dann mit Inhalt gefüllt.

Die Klasse

CLASS C_NPC
{
 VAR INT     id;  // absolute ID des NPCs

Die Id ist ein eindeutiger Indentifier, mit dem der Nsc im Programm referenziert werden kann und wird zum Beispiel benutzt um Tagesabläufe mit dem Nsc zu verknüpfen.

 VAR STRING name [5];  // Namen des NPC

Der Name ist ein String und taucht z.B. auf wenn man im Spiel einen Nsc im Focus hat.

 VAR STRING  slot;

Unbenutzt, aber vorhanden, lässt sich also für spezielle Zwecke als Membervariable, die einen String neben dem Namen speichern kann benutzen.

 VAR INT  npcType;

Integer, mit dem in Gothic der Charakter bestimmten Gruppen zugeordnet wird. Npc_TypeMain ist z.B. einer der Hauptcharaktere (z.B. Diego).

 VAR INT     flags;

Hier lässt sich der Nsc auf Immortal setzen flags = NPC_FLAG_IMMORTAL;

 VAR INT     attribute  [ATR_INDEX_MAX] ;

Die Attributwerte des Nsc werden in ein Array gespeichert, die Benutzung wird an Hand des Beispielcharakters, der weiter unten erstellt wird klarer.

 VAR INT protection  [PROT_INDEX_MAX];

Die Protection wird genauso gehandelt wie die Attribute, hier kann man dem Nsc einen natürlichen Rüstschutz gegen bestimmte Schadenstypen verpassen, Z.B. gegen Feuer

 VAR INT damage   [DAM_INDEX_MAX] ;

Gibt die Möglichkeit mehrere Arten von Nahkampfschäden an, den einen Nsc zuzuweisen, die er mit den bloßen Fäusten verursacht, ist aber nicht unbedingt sinnvoll, weil Nsc die meiste Zeit eine Waffe in der Hand haben sollten, zum rumexperimentieren Damagetypes, die das Array akzeptiert sind :

  • DAM_INDEX_BLUNT
  • DAM_INDEX_EDGE
  • DAM_INDEX_POINT
  • DAM_INDEX_FIRE
  • DAM_INDEX_FLY
  • DAM_INDEX_MAGIC
 VAR INT damagetype;

Gibt den Nahkampfschaden an, den ein Nsc mit den bloßen Fäusten macht, ist aber nicht unbedingt sinnvoll, weil Nsc die meiste Zeit eine Waffe in der Hand haben sollten.

 VAR INT guild,level;

Mit Gilde gibt man dem Nsc eine entsprechende Gilde, wie z.B. GIL_NONE für gildenlos. Mit Level wird dem Nsc ein entsprechender Level verpasst.

 VAR FUNC mission [MAX_MISSIONS];

Obsolet

 var INT fight_tactic;

Hier wird dem Nsc seine Taktik, die er in Kämpfen anwendet zugeordnet. Wird aber später noch genauer erklärt, wie die FAI´s funktionieren

VAR INT weapon;

Obsolet

VAR INT voice;

Stimmnr. des Charakters à wichtig für Standardausgaben in Zuständen

 VAR INT voicePitch;

Gibt die Möglichkeit die Stimme des Nsc zu pitchen.

 VAR INT bodymass;

Obsolet

 VAR FUNC daily_routine;  // Tagesablauf

Hier wird der Tagesablauf den ein Nsc hat angemeldet, exklusiv zu start_aistate.

 VAR FUNC start_aistate;  // Zustandsgesteuert

Hier kann ein Zustand angegeben werden, in dem sich ein Nsc beim einsetzen befindet. Darf nur exklusiv zu daily_routine benutzt werden.

// **********************
// Spawn
// **********************
VAR STRING spawnPoint;  // Beim Tod, wo respawnen ?

Erfordert einen gültigen Waypoint (muss komplett groß geschrieben werden!) aus dem zen, wenn leer ist ein toter Nsc für immer tot.

VAR INT spawnDelay;  // Mit Delay in (Echtzeit)-Sekunden

Verzögerung, mit der der Spawnvorgang begonnen wird Spawnzeitpunkt = Todeszeitpunkt +spawnDelay.

 // **********************
 // SENSES
 // **********************
 VAR INT  senses;  // Sinne

Den Nscs stehen drei Sinne zur Verfügung :

  • SENSE_SEE: Sichtkegel gibt Wahrnehmungsbegrenzung an, ist echte Sicht, d.h. Hindernisse „stören“ beim sehen.
  • SENSE_HEAR: Wände etc blockieren, Sichthindernisse wie z.B. ein Baum nicht oder hinter einem Nsc kann damit auch etwas wahrgenommen werden, obwohl es außerhalb des Sichtkegels liegt.
  • SENSE_SMELL: ist die mächtigste Wahrnehmung und einzige Begrenzung ist die Range, d.h. alles was innerhalb der senses_Range liegt wird wahrgenommen.
 VAR INT senses_range;

Reichweite der Sinne in cm.

// **********************
// Feel free to use
// **********************
 
 VAR INT  aivar[50];

Array von 50 variablen, die ein Nsc speichern kann à Achtung werden von der AI-Logik verwendet (siehe AI_constants).

 VAR STRING wp;

C++-Code (Gothic-exe) speichert hier den aktuellen W(ay)P(oint) des Nsc, damit er für Abfragen zur Verfügung steht, d.h. ein T(ages)A(blauf) übergibt einen WP und dieser ist dann der aktuelle self.wp.

 // **********************
 // Experience dependant
 // **********************
 VAR INT  exp;  // EXerience Points
 VAR INT  exp_next;  // EXerience Points needed to advance to next level
 VAR INT  lp;       // Learn Points
};

Diese drei Variablen speichern ExperiencePoints, die Anzahl der benötigten Experience Points für den nächsten level und die Anzahl von Lp des Charakters.Wird aber nur für den Spieler benötigt.

So jetzt soll es auch schon losgehen der ganzen Sache mal ein wenig Fleisch zu geben und einen NSc zu kreieren, der auch wirklich in der Welt rumläuft.

Der Nsc soll Gunther heißen, keiner Gilde angehören und ein Ambient Nsc sein. Er wird einen Level von 17 haben, Stärke ist 100, Ausdauer auch, er hat kein Mana und 200 H(it)P(oints). Zu Übungzwecken ist der Nsc leider taub und hat eine WN Reichweite von 20m, dafür ist er ein guter Kämpfer, mit dem Talent 1_Hand der Stufe zwei.Dafür benötigt er natürlich eine Waffe, die seinem Talent entspricht und sich in seinem Inventory befindet.

Gothic 1:

instance None_999_Gunther (Npc_Default)  // Klasse von der der Nsc abgeleitet ist kann auch
                                         // direkt von C_Npc abgeleitet werden
{
  //-------- primary data --------
  name      = "Gunther";
  guild      = GIL_NONE;
  npctype      = NPCTYPE_AMBIENT;
  level      = 17;
  voice      = 8;
  id       = 999;
 
  // ----- attributes --------
  attribute[ATR_STRENGTH]  = 100;
  attribute[ATR_DEXTERITY]  = 100;
  attribute[ATR_MANA_MAX]  = 0;
  attribute[ATR_MANA]   = 0;
  attribute[ATR_HITPOINTS_MAX]= 200;
  attribute[ATR_HITPOINTS]  = 200;
 
  //-------- visuals --------
  Mdl_SetVisual   (self, "humans.mds");   // basic animation file - Bei Menschen immer Humans.mds,
                                          // so lange keine selbsterstellten Daten vorliegen, sondern nur
                                          // auf Animantionsdaten von Gothic gearbeitet wird.
 
  Mdl_ApplyOverlayMds  (self, "humans_militia.mds"); // overlay animation file ß Bewegungtyp: Militär
  Mdl_SetVisualBody  (self, "hum_body_naked0",  // body mesh
         0,      // body texture variant
         1,      // skin color
         "hum_head_fighter",  // head mesh
         51,      // head texture variant
         2,      // teeth texture variant
         GRD_ARMOR_M);   // armor instance
 
  B_Scale     (self);       // body width according to strength of character
 
  Mdl_SetModelFatness  (self, 0);      // limb fatness
 
  //-------- talents --------
  Npc_SetTalentSkill  (self, NPC_TALENT_1H,2);
 
  //-------- inventory --------
  EquipItem    (self, ItMw_1H_Mace_War_03);
  // Zehn Heiltränke falls der Bursche mal verletzt wird:
  CreateInvItems (self, ITFO_Potion_Health_01, 10);
 
  //--------senses-------------
 
  senses  = SENSE_SEE | SENSE_SMELL;
  senses_range = 2000;
 
  //------------- ai -------------
  fight_tactic    = FAI_HUMAN_STRONG;
 
  // Anmeldung des Tagesablaufs, wird aber später noch genauer erklärt:
  daily_routine    = Rtn_start_999;
};

Gothic 2:
Folgende Dinge sind am Script für Gothic2 geändert, damit es funktioniert.
1. Die Funktion „B_Scale“ existiert nicht mehr. Stattdessen benutzen wir „B_SetNpcVisual“, durch sie wird das Aussehen des Npcs bestimmt sie enthält „Mdl_SetVisual“, „Mdl_SetVisualBody“ und wie die früherer B_Scale „Mdl_SetModelScale“
2. Es wurde eine andere Rüstung gewählt, da die ursprüngliche unter diesem Namen nicht mehr existiert.
3. Das Kampfskillsystem hat sich ein Gothic2 geändert, daher wird entweder die Funktion „B_SetFightSkills“, oder eine Manuele Prozentangabe benutzt.
4. Das Schwert und die Tränke wurden ausgetauscht, da beide si nicht mehr existieren.

instance None_999_Gunther (Npc_Default)  // Klasse von der der Nsc abgeleitet ist kann auch
                                         // direkt von C_Npc abgeleitet werden
{
  //-------- primary data --------
  name      = "Gunther";
  guild      = GIL_NONE;
  npctype      = NPCTYPE_AMBIENT;
  level      = 17;
  voice      = 8;
  id       = 999;
 
  // ----- attributes --------
  attribute[ATR_STRENGTH]  = 100;
  attribute[ATR_DEXTERITY]  = 100;
  attribute[ATR_MANA_MAX]  = 0;
  attribute[ATR_MANA]   = 0;
  attribute[ATR_HITPOINTS_MAX]= 200;
  attribute[ATR_HITPOINTS]  = 200;
 
  //-------- visuals --------
  B_SetNpcVisual 	//Funktion, in der das Aussehen des Npc bestimmt wird. Je nach Geschlecht wird ein Körpermesh vergeben. Außerdem wird der Npc automatisch gescalet
  (self, 
  MALE, //Geschlecht
  "Hum_Head_Fatbald", //KOpf Mesh
  51, //Kopf Textur
  0,  //Body Textur
  ITAR_MIL_M);  //Rüstung,armor instance
  Mdl_ApplyOverlayMds	(self,"Humans_Militia.mds"); // overlay animation file ß Bewegungtyp: Militär
  Mdl_SetModelFatness  (self, 0);      // limb fatness
 
  //-------- talents --------
B_SetFightSkills (self, 40);    //  Hierbei werden alle Skills automatisch auf 40% gesetzt. 
//Manuel setzt man die Skills wie folgend, hier wird nur der Einhandkampf auf 40 % gesetzt
//HitChance	[NPC_TALENT_1H]			= 40; 					
//HitChance	[NPC_TALENT_2H]			= 0;
//HitChance	[NPC_TALENT_BOW]		= 0;
//HitChance	[NPC_TALENT_CROSSBOW]	= 0;
 
 
  //-------- inventory --------
  EquipItem    (self, ItMw_1h_Mil_Sword);
  // Zehn Heiltränke falls der Bursche mal verletzt wird:
  CreateInvItems (self, ItPo_Health_01, 10);
 
  //--------senses-------------
 
  senses  = SENSE_SEE | SENSE_SMELL;
  senses_range = 2000;
 
  //------------- ai -------------
  fight_tactic    = FAI_HUMAN_STRONG;
 
  // Anmeldung des Tagesablaufs, wird aber später noch genauer erklärt:
  daily_routine    = Rtn_start_999;
};

Auch wenn es erst später erkärt wird muß hier diese Funktion schon angelegt sein, damit der Nsc auch „klappt“.

func void Rtn_Start_999 ()
{
};

Kopiert man diesen Nsc jetzt per Texteditor in ein vorhandenes File und hat das Parameter -zreparse in den GothicStarter eingetragen, kann man im Spiel über die Konsole per insert [instance], hier None_999_Gunther den Nsc schon mal einfügen und betrachten, auch wenn er jetzt noch nicht so viel tut. Im GothicStarter MUSS der Parameter -devmode angegeben sein, um die Konsole öffnen zu können.
Um aber die Übersicht bei vielen Nscs nicht zu verlieren ist es ganz nützlich die schon in Gothic verwendeten Strukturen zu benutzen. Nscs sind im Ordner /_work/data/skripts/content/story/Npc abgelegt. Wenn ein File mit der Dateierweiterung .d erstellt wird und in diesem Ordner abgelegt wird, wird es automatisch mit geparst und steht dann im Spiel zur Verfügung.

Der Bursche kriegt eine Aufgabe

Damit er was zu tun hat, während er auf Euch wartet, muß der Charakter entweder einen TA oder einen StartState haben, hier wird jetzt der etwas komplexere Teil eine TA zu erstellen erklärt, weil darin auch das erstellen eines Z(u)S(tands) enthalten ist, der für StartState nötig ist. In diesem Zustand wird nicht viel passieren, außer das der Nsc sich auf den zugewiesenen Wegpunkt begibt um hier auf den Spieler zu warten und sich von Zeit zu Zeit mal von diesem WP wegzubegeben um zu pinkeln.

Damit das W(ahr)N(ehmungs)-System etwas deutlicher wird, bekommt der Nsc erst mal nur zwei WN, d.h. aber auch, dass er erst mal nur auf die Annäherung und Ansprechen des Spielers reagiert und ihm alles andere egal ist, wie Schaden nehmen angesprochen werden etc.

Gothic 1:

func void ZS_GuntherWait ()
{
  PrintDebugNpc (PD_TA_FRAME, "ZS_GuntherWait");
                // Debugausgaben, die man sich im Spy anzeigen lassen kann
 
  Npc_PercEnable(self, PERC_ASSESSPLAYER,  B_AssessSC);
                // führt unter anderem dazu, dass der Nsc bei Annäherung des Spielers
                // kontrolliert ob er eine Info hat, die ihm zum Ansprechen des Spielers
                // veranlasst (Info wird später erstellt)
  Npc_PercEnable(self, PERC_ASSESSTALK, B_AssessTalk);
                // macht den Nsc ansprechbar, damit der Spieler auch den Auftrag beenden kann
 
  AI_StandUp  (self);
  AI_SetWalkmode  (self, NPC_WALK);   // Walkmode für den Zustand, in diesem Fall gehen
  AI_GotoWP  (self, self.wp);   // Gehe zum Tagesablaufstart
  AI_AlignToWP (self);   // Ausrichten am WP (Pfeilrichtung im Spacer)
};
 
func void ZS_GuntherWait_Loop ()
{
  PrintDebugNpc (PD_TA_LOOP, "ZS_GuntherWait_Loop"); // s.o.
  AI_GotoWP (self, self.wp);   // Gehe zum Tagesablaufstart
  AI_Wait   (self, 100); // 100 Sekunden warten, bis die Wp gewechselt wird
  AI_GotoWP (self, Npc_GetNearestWP (Self));
            // Anderen WP in der Nähe suchen, damit er nicht dahin pinkelt wo er steht
 
  // Wichtig: Namen von Animationen müssen immer komplett gross geschrieben werden!
  // Also nicht "t_pee", "t_Pee" etc.
  AI_PlayAni(self,  "T_PEE");
  AI_Wait   (self, 100); // 100 Sekunden warten, bis die Loop beendet wird
};
 
func void ZS_GuntherWait_End ()
{
  PrintDebugNpc (PD_TA_FRAME,"ZS_GuntherWait_End"); // s.o.
};

Gothic 2:
Hier wurde nur „B_AssessSC“ gegen „B_AssessPlayer“ augetauscht, da „B_AssessSC“ in G2 nicht mehr existiert.

func void ZS_GuntherWait ()
{
  PrintDebugNpc (PD_TA_FRAME, "ZS_GuntherWait");
                // Debugausgaben, die man sich im Spy anzeigen lassen kann
 
  Npc_PercEnable(self, PERC_ASSESSPLAYER,  B_AssessPlayer);
                // führt unter anderem dazu, dass der Nsc bei Annäherung des Spielers
                // kontrolliert ob er eine Info hat, die ihm zum Ansprechen des Spielers
                // veranlasst (Info wird später erstellt)
  Npc_PercEnable(self, PERC_ASSESSTALK, B_AssessTalk);
                // macht den Nsc ansprechbar, damit der Spieler auch den Auftrag beenden kann
 
  AI_StandUp  (self);
  AI_SetWalkmode  (self, NPC_WALK);   // Walkmode für den Zustand, in diesem Fall gehen
  AI_GotoWP  (self, self.wp);   // Gehe zum Tagesablaufstart
  AI_AlignToWP (self);   // Ausrichten am WP (Pfeilrichtung im Spacer)
};
 
func void ZS_GuntherWait_Loop ()
{
  PrintDebugNpc (PD_TA_LOOP, "ZS_GuntherWait_Loop"); // s.o.
  AI_GotoWP (self, self.wp);   // Gehe zum Tagesablaufstart
  AI_Wait   (self, 100); // 100 Sekunden warten, bis die Wp gewechselt wird
  AI_GotoWP (self, Npc_GetNearestWP (Self));
            // Anderen WP in der Nähe suchen, damit er nicht dahin pinkelt wo er steht
 
  // Wichtig: Namen von Animationen müssen immer komplett gross geschrieben werden!
  // Also nicht "t_pee", "t_Pee" etc.
  AI_PlayAni(self,  "T_PEE");
  AI_Wait   (self, 100); // 100 Sekunden warten, bis die Loop beendet wird
};
 
func void ZS_GuntherWait_End ()
{
  PrintDebugNpc (PD_TA_FRAME,"ZS_GuntherWait_End"); // s.o.
};

So jetzt ist es schon fast fertig, damit aber dieser ZS auch als TA verwendet werden kann muss er angemeldet werden. Dafür muss in der Datei TA.d (in G1 sie in /_work/data/skripts/content/story/ZS/, in G2 in /_work/Data/Scripts/Content/AI/Human/) folgende Zeilen auftauchen:

func void TA_GuntherWait (  var int start_h,
                            var int start_m,
                            var int stop_h,
                            var int stop_m,
                            VAR string waypoint)
{
   TA_Min (self,  start_h,start_m, stop_h, stop_m, ZS_GuntherWait, waypoint);
};

Anmerkung:In der TA.d findet man auch eine Übersicht über die möglichen bereits vorhandenen Tagesabläufe für NSCs.

und die

func void Rtn_Start_999 ()
{
   TA_GuntherWait (0,00,13,00, "OC1");
   TA_GuntherWait (13,00,0,00, "OC1");
};

führt in dieser Form dazu, dass sich der Nsc nach dem Einfügen 24h am Eingang des Oldcamps rumtreibt.
(für Gothic 2 z.B. „NW_XARDAS_TOWER_03“ als Waypoint nehmen)

Wichtig: Kleinbuchstaben in Waypoints sind nicht erlaubt! Der letzte Parameter der TA_* muss immer komplett gross geschrieben werden, sonst taucht der NSC nicht im Spiel auf. „oc1“, „Oc1“ oder „oC1“ sind also keine gültigen Angaben!

HINWEIS: TAs müssen mindesten zweimal in den Routinen auftauchen, auch wenn die Nscs 24h lang das gleiche tun, das hat was damit zu tun, dass der C++-Code die TAs von ZS nur so unterscheiden kann .

Zustände sind bei G1 in /_work/data/skripts/content/story/ZS zu finden und bei G2 in \_work\data\Scripts\Content\AI\Human\TA_Human. Es empfiehlt sich auch hier wieder, wie schon beim Npc, ein eigenes File mit einem Texteditor zu erstellen und mit der Dateierweiterung .d ( dieses ist zwingend) zu speichern. Auch hier wird wieder mit einer Wildcard geparst, so dass es reicht das File im entsprechenden Pfad abzulegen.

Ein letzter Schritt und Gunther ist dauerhaft im Spiel :

Anmeldung in der /_work/data/skripts/content/story/Startup.d

Wld_InsertNpc(None_999_Gunther,"OC1");

in den Oldcamp-Block, schließlich steht Gunther ja da rum.

Der Auftrag

Aufträge werden in Gothic über ein Dialogsystem implementiert, deshalb kommt jetzt erst mal ein leerer Dialog an Hand dessen die Grundfunktionalität erklärt wird und der später mit Inhalt gefüllt wird, bis es zu einem Auftrag mit hol mir das Schwert und werde dafür belohnt führt.

instance Instanz_Name (C_INFO)
{
    npc         = NPC_Name;
 
    Diesem Nsc "gehört" der Dialog
 
    condition       = Instanz_Name_Condition;
 
    // Hier gehört der Funktionsname, der Funktion rein, 
    // die die Bedingungen, das der Dialog gültig ist überprüft.
    information     = Instanz_Name_Info;
 
    Diese Funktion steuert den eigentlichen Dialog
 
    important       = 1;

Ist ein Dialog Important spricht der Nsc den Sc von sich aus an, sobald er ihn entdeckt (deshalb WN AssessPlayer in ZS_GuntherWait aktiviert s.o.)

    permanent       = 0;
};

Ist ein Dialog Permanent wird er, so lange seine Condition True ist immer angeboten

 FUNC int  Instanz_Name_Condition()
 {
    return 1;
 };

Hier sollten per Abfrage Bedingungen erfragt werden, damit der Dialog gestartet werden kann. Gibt die Funktion einfach TRUE zurück wird der Dialog immer angeboten.

 func void  Instanz_Name_Info()
 {
    // Wie hier der Inhalt reinkommt wird beim Auftrag "basteln" erklärt.
 };

Da es sich beim Exit auch um einen Dialog handelt wird hier nicht mehr Schritt für Schritt erklärt wofür dieser ist, nur soviel, die Exit-Funktion sorgt dafür, das es immer eine Auswahl im Dialogscreen gibt, mit der der Spieler den Dialog verlassen kann.

// ************************ EXIT **************************
 
instance  Instanz_Name_Exit (C_INFO)
{
   npc         =  NPC_Name;
   condition   =  Instanz_Name_Exit_Condition;
   information =  Instanz_Name_Exit_Info;
   important   = 0;
   permanent   = 1;
   description = "ENDE";
};
 
FUNC int  Instanz_Name_Exit_Condition()
{
   return 1;
};
 
FUNC VOID  Instanz_Name_Exit_Info()
{
   AI_StopProcessInfos (self);
};

Durch geschicktes Verschachteln von Dialogen und Dialogbedingungen wird dann ein Auftrag gebaut.

Lets Go: Notwendige globale Variablen, ist so nicht sauber und sollte in einem eigenen File gepflegt werden, aber für die Tutorial Zwecke reicht es so erst mal. In Gothic sind sie aber in _work/data/skripts/content/story/Storyglobals.d abgelegt, es ist also ganz sinnvoll hier einen neuen Teil einzufügen, der neu erstellte globale Variablen aufnimmt.

const string GunthersSword  = " Bringe Gunthers Schwert zurück";
var int int_GotSword;
 
instance None_999_Gunther_AskForSword (C_INFO)
{
  npc         = None_999_Gunther;
  condition       = None_999_Gunther_AskForSword_Condition;
  information     = None_999_Gunther_AskForSword_Info;
  important       = TRUE;   // Gunther soll den Spieler selbsttätig ansprechen
 
  permanent       = FALSE;  // Es reicht aber wenn er einmal den Auftrag vergibt
 
  description     = ""; // Bleibt hier leer, weil es eine Importantinfo ist, die
                        // direkt abgefeuert wird und somit gar nicht im Dialogscreen auftaucht
};
 
FUNC int  None_999_Gunther_AskForSword_Condition()
{
  if (hero.level >= 0) // Nur Helden, die "schon" auf Level null sind,
                       // haben die Ehre Gunther sein Schwert bringen zu dürfen
  {
     return TRUE;
  };
  return FALSE;
};
 
func void  None_999_Gunther_AskForSword_Info()
{
  AI_Output   ( self, other, "None_999_Gunther_AskForSword_Info_8_01"); // Hey Du, Heute schon was vor?
  // Achtung: In dem String, der die Outputunit verschlüsselt,
  // ist die erste Zahl die Stimmnummer und die zweite eine laufende Nummer,
  // die nur einmal pro vorangestelltem String verwendet werden darf
 
  Info_ClearChoices(None_999_Gunther_AskForSword);
 
  Info_AddChoice(None_999_Gunther_AskForSword, 
                "Auf jedenfall genug", None_999_Gunther_AskForSword_Yes);
 
  Info_AddChoice(None_999_Gunther_AskForSword, 
                "Nichts spezielles",   None_999_Gunther_AskForSword_No);
};
 
// Der Spieler will nicht
func void None_999_Gunther_AskForSword_Yes ()
{                                                                              
     AI_Output(other,self,
               "None_999_Gunther_AskForSword_Info_8_02");  // Es geht Dich zwar nichts an, [...]
     AI_Output(self,other,
               "None_999_Gunther_AskForSword_Info_8_03");  // Na gut dann scheidest Du wohl aus:
 
     AI_StopProcessInfos (self);
};
 
// Der Spieler bekundet Interesse
func void None_999_Gunther_AskForSword_No ()
{                                                                               
     AI_Output(other,self,"None_999_Gunther_AskForSword_Info_8_04");  // Bisher noch nichts spezielles.
 
     AI_Output(self,other,"None_999_Gunther_AskForSword_Info_8_05");  // Das ist gut, denn Du mußt [...]
 
     Log_CreateTopic (GunthersSword,LOG_MISSION);
     Log_SetTopicStatus  (GunthersSword,LOG_RUNNING);
     B_LogEntry(GunthersSword, "Gunther hat mich gebeten sein Milhouseschwert für Ihn aufzutreiben");
     AI_StopProcessInfos (self);
};

So das ist erst mal der Dialog, mit dem Gunther den Auftrag vergibt, wenn man sagt man hat noch nix vor wird der Auftrag direkt vergeben, damit der Dialog erst mal etwas kleiner und übersichtlicher bleibt. Die Add_Choice Befehle bauen im Dialogscreen den angegeben String ein und wenn man ihm im Dialog aufruft wird auf die Funktion verwiesen, die hinter dem entsprechenden String angegeben ist. Hier z.B.: None_999_Gunther_AskForSword_No.

Dialoge-Dateien sollten der Übersichtlichkeit halber in den Ordner /_work/data/skripts/content/story/Missions und werden mitgeparst wenn sie mit Dia_ beginnen. Jetzt müssen die Untertitel aktualisiert werden, wie das geht wird auf der Untertitel und Sprachausgabe Seite erklärt.

Wenn man jetzt das Spiel startet und zu Gunther geht spricht er den Hero an und man kann sich für oder gegen das Holen des Schwertes entscheiden.

Anmerkung: Bitte daran denken in der Mod.ini den Parameter force_subtitles=1 zu setzen, sonst gibt es keine Textausgaben, weil keine Soundfiles zu den Ausgaben existieren.

Anmerkung 2: Zum Erstellen von Dialogen existiert ein Tool, den Miranda Dialog Creator.

Den Gegenstand der Begierde erstellen

Gunther will ein Schwert haben, das es so in Gothic nicht gibt, also muss es erstellt werden.

Die Klasse

CLASS C_Item
{
 // Für alle Items
 VAR INT  id;

Id wie bei der C_Npc Klasse (s.o.)

 VAR STRING  name,nameID;

Synonym zu C_Npc

 VAR INT  hp,hp_max;

Synonym zu C_Npc

 VAR INT  mainflag,flags;  // Hauptflag und weitere Flags
 VAR INT  weight,value;

Gewicht und Wert lassen sich als integer angeben.

  // Für Waffen
  VAR INT  damageType;  // Welche Schadensarten

Synonym zu C_Npc.

  VAR INT  damageTotal;

Synonym zu C_Npc

  VAR INT damage [DAM_INDEX_MAX];

Synonym zu C_Npc

  // Für Rüstungen
  VAR INT  wear;

Hier ist eigentlich nur WEAR_TORSO interessant, weil keine Helme im Spiel sind.

  VAR INT  protection  [PROT_INDEX_MAX];

Dies ist ein Array in dem für jede Schadensklasse ein eigener Rüstschutz angegeben werden kann.

  // Für Nahrung
  VAR INT  nutrition      ;  // HP-Steigerung bei Nahrung

Obsolet

  // Benötigte Attribute zum Benutzen des Items
  VAR INT  cond_atr  [3]    ;
  VAR INT  cond_value  [3]    ;

Ein Array von zweimal drei Integer, in dem angegeben werde kann welche Stärke (z.B. 10) etc. nötig ist um diese Item anzulegen oder zu Benutzen.

  // Attribute, die bei anlegen des Items verändert werden
  VAR INT  change_atr  [3]    ;
  VAR INT  change_value [3]    ;

Obsolet

  // Parserfunktionen
  VAR FUNC magic;       // Parserfunktion zum "Magie Header"
  VAR FUNC on_equip;    // Parserfunktion, wenn Item equipped wird.
  VAR FUNC on_unequip;  // Parserfunktion, wenn Item unequipped wird.
  VAR FUNC on_state  [4];
 
  // Besitzer
  VAR FUNC owner;
 
  // Besitzer : Instanz-Name
 
  VAR INT  ownerGuild;
 
  // Besitzer : Gilde
 
  VAR INT  disguiseGuild;
 
  // Zur Schau getragene Gilde durch Verkleidung

Obsolet ?

  // Die 3DS-Datei
 
  VAR STRING visual;

Gibt an welche Datei als Darstellung für das Item geladen wird.

     // Veränderung des NSC-Meshes beim Anlegen dieses Gegenstandes
 
     VAR STRING  visual_change     ;  // ASC - File

Wird zum Anlegen einer Rüstung benutzt, weil dann ein anderes ASC.File als visual geladen wird.

  VAR INT  visual_skin;

Texturvariation für das betreffende Rüstungsmesh.

  VAR STRING  scemeName;

Interner Name für das Benutzen von Items.

  VAR INT  material;

Mit dem Material sind z.B. die „Klangeigenschaften“ eines Items festgelegt.

  // VAR STRING pfx;  // Magic Weapon PFX

Obsolet

  VAR INT  munition;  // Instance of Munition

Bei Bögen wird hier angegeben mit welcher Munition sie schießen (Arrow V Bolt).

  VAR INT  spell;

Gibt an welchen Spell magische Items ausführen.

  VAR INT  range;

Gibt den Radius an, in dem eine Nahkampfwaffe trifft.

  VAR INT  mag_circle;

Welchem Magischen Zirkel ist dieses Item zugeordnet.

  VAR STRING description;

Die Beschreibung, die beim Betrachten eines Items auftaucht.

  VAR STRING text[ITM_TEXT_MAX];
  VAR INT  count[ITM_TEXT_MAX];
};

Vars mit denen sich die Werte der Items in den Statusscreen transportieren lassen.

So dann soll jetzt mal ein schönes Schwert entstehen, nach dem Gunther suchen lassen kann. Damit den Spieler das Schwert auch interessiert sollte es natürlich ein paar nette Werte haben, also soll es 1000 Erz wert sein und einen Schaden von 300 machen, das Visual wird aus den Gothicdaten geklaut, weil Visuals erstellen an einer andern Stelle erklärt wird. Ansonsten muss das Schwert natürlich eine Nahkampfwaffe und ein Schwert sein, sollte aus Metall bestehen und vielleicht noch ein bisschen von sich preisgeben(Beschreibung).

Gothic 1:

INSTANCE ItMw_1H_GuntherWantedSword (C_Item)
{
   name          =   "Milhouse´ Schwert";

   mainflag      =   ITEM_KAT_NF;    // Nahkampfwaffe
   flags         =   ITEM_SWD;       // Schwert
   material      =   MAT_METAL;  // besteht aus Metall

   value         =   1000;           // Wert von 1000

   damageTotal   =   300;
   damagetype    =   DAM_EDGE;       // Verursacht Klingenschaden
   range         =   200;            // Hat einen Radius von 2m

   cond_atr[2]   =   ATR_STRENGTH;   // Was muß man haben, damit man es führen darf
   cond_value[2] =   5;              // Wie hoch muß man das (s.o.) haben
   visual        =   "ItMw_1H_Sword_Old_01.3DS";     // Visual aus den Gothicdaten "gemopst"

   description   =   name;                                                  //Überschrift (Waffenbeschreibung)
   TEXT[2]       =   "Schaden";                 COUNT[2]    = 300;          //Text und Wert in der ersten Zeile
   TEXT[3]       =   "Benötigte Stärke";       COUNT[3]    = cond_value[2]; //Text und Wert in der zweiten Zeile
   TEXT[4]       =   "einhand Schwert";                                     //Text in der dritten Zeile
   TEXT[5]       =   "Wert";                   COUNT[5]    = value;         //Text und Wert in der vierten Zeile
};

Gothic 2:
Hier wurde das Visual ausgetauscht, da dieses in G2 nicht existierte.

INSTANCE ItMw_1H_GuntherWantedSword (C_Item)
{
   name          =   "Milhouse´ Schwert";
 
   mainflag      =   ITEM_KAT_NF;    // Nahkampfwaffe
   flags         =   ITEM_SWD;       // Schwert
   material      =   MAT_METAL;  // besteht aus Metall
 
   value         =   1000;           // Wert von 1000
 
   damageTotal   =   300;
   damagetype    =   DAM_EDGE;       // Verursacht Klingenschaden
   range         =   200;            // Hat einen Radius von 2m
 
   cond_atr[2]   =   ATR_STRENGTH;   // Was muß man haben, damit man es führen darf
   cond_value[2] =   5;              // Wie hoch muß man das (s.o.) haben
   visual        =   "ItMw_020_1h_sword_old_01.3DS";     // Visual aus den Gothicdaten "gemopst"
 
   description   =   name;                                                  //Überschrift (Waffenbeschreibung)
   TEXT[2]       =   "Schaden";                 COUNT[2]    = 300;          //Text und Wert in der ersten Zeile
   TEXT[3]       =   "Benötigte Stärke";       COUNT[3]    = cond_value[2]; //Text und Wert in der zweiten Zeile
   TEXT[4]       =   "einhand Schwert";                                     //Text in der dritten Zeile
   TEXT[5]       =   "Wert";                   COUNT[5]    = value;         //Text und Wert in der vierten Zeile
};

Das Item lässt sich jetzt genauso wie Gunther per Konsole im Spiel einfügen.

Bei den Waffen empfiehlt es sich ein eigenes File (*.d) anzulegen, das dann im Ordner /_work/data/skripts/content/Items abgelegt wird. Hier muss dann allerdings auch dafür gesorgt werden, dass das Programm die neue Datei auch liest.

Dafür öffnet man die Datei /_work/data/skripts/content/gothic.src, in dieser Datei sind alle Pfade angegeben, aus denen Contentskripte geladen werden. Diese Pfade sind relativ zum Pfad /_work/data/skripts/

Hier fügt man für das neue File den Pfad ITEMS\FILENAME.D ein und schon werden die Daten aus dem File gelesen.

Achtung! Diese Datei erfordert zwingend Großbuchstaben. Falls es mal beim Gamestart Probleme geben sollte kann es sein, das eine Datei Daten aus einer anderen Datei referenziert und deshalb diese vor einer anderen geparst werden muß. Lösungshinweise: keine Wildcards verwenden, sondern die Files einzeln per Namen parsen und zum anderen ein wenig mit der Reihenfolge rumspielen, aber nur der Daten, die neu erstellt wurden, weil sonst das ganze File kaputt gehen kann.

Natürlich lassen sich so auch neue Ordner referenzieren, aber das ist dann doch eine Sache zum Ausprobieren und rumspielen.

Den "Auftrag erfüllt"-Dialog erstellen

Hier wird wieder ein Dialog angelegt, in dem bestimmte Bedingungen erfüllt sein müssen und an Hand dieser werde noch Belohnungen verteilt, in diesem Fall Experience Points und Learn Points erhöhen. Da Dialoge oben schon genauer erklärt wurden hier nur noch der Dialog, der zum Auftrag erfüllen vorhanden sein muß.

// ************************ EXIT **************************
instance  None_999_Gunther_AskForSword_Exit (C_INFO)
{
  npc         =  None_999_Gunther;
  condition   =  None_999_Gunther_AskForSword_Exit_Condition;
  information =  None_999_Gunther_AskForSword_Exit_Info;
  important   = 0;
  permanent   = 1;
  description = "ENDE";
};
 
FUNC int  None_999_Gunther_AskForSword_Exit_Condition()
{
   return true;   // Dem Spieler dauerhaft die Möglichkeit geben den Dialog zu verlassen
};
 
FUNC VOID  None_999_Gunther_AskForSword_Exit_Info()
{
   AI_StopProcessInfos ( self );
};
 
 
instance None_999_Gunther_BringSword (C_INFO)
{
   npc          = None_999_Gunther;
   condition   = None_999_Gunther_bringSword_Condition;
   information = None_999_Gunther_bringSword_Info;
   important   = FALSE;
   permanent   = TRUE;  // So lange der Auftrag nicht erfüllt ist muß der
                        // Nsc den Dialog immer wieder bringen, alles
                        // andere wird in der Bedingung für den Dialog geregelt
 
   description = "Hast Du das Schwert ?";
};
 
FUNC int  None_999_Gunther_bringSword_Condition()
{
   // 1.Nur wenn der Held den Auftrag hat kann er ihn auch beenden
   // 2.Nur wenn der Auftrag nicht als erfüllt gilt wird der Dialog angeboten
   // 3.Schwert instanz abfragen, nur dann macht Auftraglösen Sinn
   if (Npc_KnowsInfo ( hero, None_999_Gunther_AskForSword))   // (siehe 1)
   &&! int_GotSword                                            // (siehe 2)
   && (Npc_HasItems (other, ItMw_1H_GuntherWantedSword) >= 1) // (siehe 3)
   {
       return TRUE;
   };
   return FALSE;
};
 
func void  None_999_Gunther_bringSword_Info()
{
   Info_ClearChoices (None_999_Gunther_bringSword);
 
   Info_AddChoice    (None_999_Gunther_bringSword,
                      "Ich hab Dein Schwert gebs dir aber nicht",
                      None_999_Gunther_bringSword_Yes);
 
   Info_AddChoice    (None_999_Gunther_bringSword,
                      "Ich hab Dein Schwert hier ist es",
                      None_999_Gunther_bringSword_No);
};
 
// Der Spieler will das Schwert nicht übergeben
func void None_999_Gunther_bringSword_Yes ()
{
   AI_Output( other, self, "None_999_Gunther_bringSword_Info_8_06"); // Ich habs, aber gebs Dir nicht
   AI_Output( self, other, "None_999_Gunther_bringSword_Info_8_07"); // Du hast sie ja wohl nicht alle
 
   AI_StopProcessInfos (self);
 
   AI_StartState( self, ZS_Attack, 0, ""); // Gunther ist jetzt ein wenig sauer und greift an
};
 
// Der Spieler bekundet Interesse
func void None_999_Gunther_bringSword_No ()
{
   AI_Output   ( other, self, "None_999_Gunther_bringSword_Info_8_08"); // Hier Dein Schwert, [...]
   AI_Output   ( self, other, "None_999_Gunther_bringSword_Info_8_09"); // Kein Problem [...]
 
   B_LogEntry      (GunthersSword, "Ich habe Gunther sein Schwert gebracht");
 
   other.exp   = other.exp + 100;  // Dem Spieler 100 Experience Points geben
   other.lp    = other.lp + 10;    // Und weil er so nett war auch gleich no ein paar lp
 
   Npc_GiveItem ( other, ItMw_1H_GuntherWantedSword, self); // Gunther will ja auch sein Schwert haben
   AI_StopProcessInfos (self);
};

Der Dialog wird dann natürlich genau so an- und abgelegt wie der vorherige.

Anmerkung zum obigen Codeabschnitt:

other.exp   = other.exp + 100;  //man kann auch die dafür geschaffene Funktion B_GiveXP(100) verwenden
                                //die Zahl in Klammern gibt die erhaltenen Exp an

other.exp kann man übrigens auch für JEDEN anderen Wert eines NPC verwenden (z.B. other.health).

self steht immer für den NPC mit dem der Spieler redet. Das solltet ihr euch merken um nicht aus Versehen dem falschen irgendwelche Gegenstände oder Fähigkeiten zu geben.

Außerdem könnt ihr auch hero angeben wenn ihr den Spieler meint, also:

AI_Output( self, hero, "None_999_Gunther_bringSword_Info_8_07"); // Du hast sie ja wohl nicht alle

ist das selbe wie

AI_Output( self, other, "None_999_Gunther_bringSword_Info_8_07"); // Du hast sie ja wohl nicht alle
tutorials/skript-tutorial.txt · Zuletzt geändert: 2016/12/30 20:28 von milky-way