[Modding] Scripting Fragen

Maus

Senior Member
Registriert
07.08.2002
Beiträge
9.378
Danke schon mal. Mit MYAREA hat es funktioniert und MoveBetweenAreas dann auch.

[Gelöst]
Jetzt eine philosophische Frage fast:
Kann ich mit GlobalLT (2 z.B.) eine Variable abfragen, die nicht existiert? Ich dachte immer, wenn man Global (=0) abfrägt, dann hat man diesen Fall, weil "does not exist" halt =0 ist. Und 0 ist kleiner als 2. Aber der Skriptblock läuft nicht...
Ich probier das mal aus. Wenn ich die Variable vorher erstmal auf 1 setze und dann den GlobatLT abfrage...

Und noch zu LeaveAreaLUA: mein Verdacht wäre, dass das nur mit cre funktioniert, die in der GAM eingetragen sind. Weil ich nur Beispiele damit gefunden habe... Müsste ich mal in NI durchjagen, mal schauen.

edit: das Problem war, dass das Skript wegen eines fehlenden Continue() vorher fest hing und nicht weiter lief bis zur Stelle, wo ich die GlobalLT abgefragt habe...
 
Zuletzt bearbeitet:

Maus

Senior Member
Registriert
07.08.2002
Beiträge
9.378
Mal eine schwierige Frage zur Performance: Wie programmiere ich effizient, so dass das Spiel nicht langsamer wird. Mir kommt es so vor, wie wenn Installationen mit vielen Mods mit fortschreitender Spieldauer immer langsamer laufen. Da ergeben sich dann ein paar Fragen:

a) Korrekt, dass man besser keine Skript in die BALDUR.BCS schreibt, weil die praktisch permanent läuft? Lieber in Area-Skripte Trigger setzen, etc.?

b) Wie ist das mit den Variablen? Packt BG die einfach in einen Speicher, der sich immer mehr füllt und wo die Skripte dann nachschauen, wenn eine Variable in einem Trigger sitzt? Möglichst sparsam mit Variablen umgehen ist klar und vor allem wieder verwenden mit anderen Werten, wenn man sie nicht mehr benötigt, würde ich mal sagen.

c) Gibt es hinsichtlich Performance einen Unterschied zwischen globalen und lokalen Variablen? Sind die lokalen vielleicht irgendwo weggepackt, bis z.B. das Skript oder die Area wieder geladen werden? Oder ist es ziemlich egal, ob eine Variable global oder lokal definiert wird, weil sie alle im selben Speicherbereich bereit gehalten werden?

Gibt sonst wichtige Dinge, was man nicht machen sollte? Nicht so viele Timer, weil die immer mitlaufen, lieber auf Ereignisse triggern oder ähnliches?
 

Taimon

Infinity Engineer
Registriert
25.11.2001
Beiträge
1.501
a) Korrekt, dass man besser keine Skript in die BALDUR.BCS schreibt, weil die praktisch permanent läuft? Lieber in Area-Skripte Trigger setzen, etc.?
Ja, so wenig wie möglich in die BALDUR.BCS packen.

b) Wie ist das mit den Variablen? Packt BG die einfach in einen Speicher, der sich immer mehr füllt und wo die Skripte dann nachschauen, wenn eine Variable in einem Trigger sitzt?
Die Variablen sind in unterschiedlichen Hashtables. Es gibt eine globale Tabelle, eine pro Area und eine pro Actor.

c) Gibt es hinsichtlich Performance einen Unterschied zwischen globalen und lokalen Variablen? Sind die lokalen vielleicht irgendwo weggepackt, bis z.B. das Skript oder die Area wieder geladen werden? Oder ist es ziemlich egal, ob eine Variable global oder lokal definiert wird, weil sie alle im selben Speicherbereich bereit gehalten werden?
Ich würde versuchen die globalen Variablen zu minimieren, aber generell ist ein Hashtable-Lookup eigentlich recht schnell. Der Zugriff auf die globale und die jeweilige Area-Hashtable könnte allerdings schneller sein als der Zugriff auf die Actor-Hashtable, da die Engine nicht erst das Actor-Objekt "akquirieren" muss. (Spielt wahrscheinlich kaum eine Rolle.)

Gibt sonst wichtige Dinge, was man nicht machen sollte? Nicht so viele Timer, weil die immer mitlaufen, lieber auf Ereignisse triggern oder ähnliches?
Wie an anderer Stelle schon erläutert: die meisten Timer sind auch nur Variablen und werden identisch behandelt.
Allgemeine Performance-Empfehlungen kann ich nicht geben, aber der generelle Tenor ist schon möglichst wenig global zu machen.
 

Maus

Senior Member
Registriert
07.08.2002
Beiträge
9.378
Da dann gleich die Nachfrage: Sind die Begleiter in der Gruppe auch Actors? Oder sind die automatisch geladen, weil ja eigentlich immer da?

In einem Lookup-Table muss man aber auch erstmal suchen, oder sind die prinzipiell sortiert? Ich kenne das eher so, dass man Sachen, die man schnell braucht, in Lookup-Tables packt anstatt sie zu berechnen oder über ne Funktion zu holen. Aber Lookup ist immer noch langsamer als sie gleich in den Arbeitsspeicher zu schreiben.
Daher hätte ich gesagt: je größer die Lookup-Table umso schlechter, da noch was reinzupacken. Und GLOBAL dürfte die größte sein ;) Richtiges Verständnis oder nur gefährliches Halbwissen?
 

Taimon

Infinity Engineer
Registriert
25.11.2001
Beiträge
1.501
Sind die Begleiter in der Gruppe auch Actors?
Ja. Mit Actors meine ich im Prinzip alles, was ein Skript ausführen kann. (Intern wird die Klasse "CGameAIBase" genannt.)
Dazu zählen alle CREs aber auch Türen, Traps, etc.

Oder sind die automatisch geladen, weil ja eigentlich immer da?
Geladen sind sie sicher, aber trotzdem muss vor einem Zugriff sichergestellt werden, dass niemand anderes gerade etwas an dem Objekt ändert. Deswegen gibt es eine Funktion, die den exklusiven Zugriff sicherstellt.

In einem Lookup-Table muss man aber auch erstmal suchen, oder sind die prinzipiell sortiert?
Hashtabellen funktionieren so, dass der Schlüssel (in diesem Fall der Variablenname) mittels einer Hashfunktion auf einen Zahlenwert abgebildet wird. Der Zahlenwert wird als Index in ein Feld genommen und an der Stelle steht dann der gesuchte Wert.
Rein theoretisch ist damit die Lookup-Zeit unabhängig von der Anzahl der Elemente. Praktisch gibt es aber irgendwann Kollisionen (selber Zahlenwert bei unterschiedlichen Schlüsseln), da man die Feldgröße auch nicht beliebig groß machen möchte.

Aber Lookup ist immer noch langsamer als sie gleich in den Arbeitsspeicher zu schreiben.
Ich denke, hier bringst du etwas durcheinander. Der Speicherort ist unabhängig von der Struktur.
Und heutzutage ist praktisch sowieso fast alles im RAM, was häufiger benötigt wird. Wir haben ja mittlerweile mehr als 640 KB zur Verfügung. :)

je größer die Lookup-Table umso schlechter, da noch was reinzupacken. Und GLOBAL dürfte die größte sein
Wie schon erwähnt ist die Lookup-Zeit theoretisch konstant, in der Praxis hat man aber 20-30% Kollisionen. Es hängt stark von der Güte der Hashfunktion und dem zur Verfügung stehenden Speicherplatz ab. Und wenn die Tabelle stetig wächst, muss man auch irgendwann das Feld, in dem die Werte gespeichert werden, vergrößern. (Dauert auch eine Weile.)
Mein Kommentar, dass der Zugriff auf die globale Tabelle schneller sein könnte, bezog sich nur darauf, dass das Spiel genau weiß, an welcher Speicheradresse die Tabelle steht. Bei den anderen muss man sich erst das Objekt holen. Der Zugriff innerhalb der Tabelle ist aufgrund der Kollisionen aber sicher langsamer.
 

Maus

Senior Member
Registriert
07.08.2002
Beiträge
9.378
Uh, das triggert jetzt bei mir Verständnisfragen...

Also die Hashfunktion macht aus dem dem Variablennamen einen numerischen Wert? Und die Tabelle hat nachher zwei Spalten (Hashwert und Variablenname) oder 3 Spalten (+Variablenwert)? Oder nur 2 Spalten (Hashwert und Variablenwert)?

Und wenn das Spiel einen Variablennamen hat, dann berechnet es den Hashwert davon über die Hashfunktion und schaut in der Tabelle nach? Und kann dann den Variablenwert dort finden?

Und Kollision würde heißen, dass zwei Variablennamen denselben Hashwert ergeben, korrekt? Ist die Hashfunktion bekannt, so dass man beim Auswählen von Variablennamen darauf Rücksicht nehmen könnte? Und wenn Kollisionen möglich sind, müsste weiter oben im Text die Tabelle ja schon immer Hashwert und Variablennamen enthalten, um die Richtigkeit der Zuordnung überprüfen zu können.

Und das macht deswegen Sinn, weil in der Tabelle die Hashwerte fest zuschrieben sind und nicht ein Pointer drüberlaufen muss, der prüft, wo der entsprechende Eintrag im Array steht? und dadurch wird die Zugriffszeit unabhängig von der Größe?
 

Taimon

Infinity Engineer
Registriert
25.11.2001
Beiträge
1.501
Ich fürchte, wir driften hier ganz schön ab. Einige Sachen sind sicher auch implementierungsabhängig.

Aber ich versuch's trotzdem mal:
So eine Hashtabelle wird mit einer bestimmten initialen Größe erzeugt. Diese bestimmt die Größe des Feldes (Array).
Typischerweise ist das ein Array aus Pointern (Verweise) auf Listen. (Es hat im Speicher also eigentlich nur eine Spalte. Wenn man den Index dazuzählen würde, wären es zwei.)
Die Listen wiederum verwalten alle Elemente für den jeweiligen Hashwert. Bei Kollisionen stehen also mehrere Elemente in der Liste. (Das Element ist das eigentliche Datum, hier wäre es der Variablenname und -wert.)
Initial sind die Listen alle leer. (Wahrscheinlich so umgesetzt, dass der Pointer NULL ist.)
Wenn man einen Wert zu der Hashtabelle hinzufügt, wird über die Hashfunktion ein Eintrag im Array selektiert. Dazu wird der Hashwert vom Schlüssel (Variablenname) gebildet.
Das hinzuzufügende Element wird dann einfach an die jeweilige Liste angehängt. Wenn es keine Kollisionen gibt, hat die Liste nur einen Wert.
Beim Lookup passiert quasi dasselbe, nur das der Wert von dem Element zurückgegeben wird. (Bei Kollisionen muss die Liste nach dem richtigen Variablennamen durchsucht werden.)

Ist die Hashfunktion bekannt, so dass man beim Auswählen von Variablennamen darauf Rücksicht nehmen könnte?
Kann man sicherlich recherchieren, aber ich würde darauf vertrauen, dass die meisten Implementierungen hier schon eine passende Funktion ausgewählt haben.
Also keine Rücksicht darauf nehmen.
 

Maus

Senior Member
Registriert
07.08.2002
Beiträge
9.378
Ich verstehen Jastey XP-Anpassungs-Skript nicht. Und stelle daher hier mal die Frage, bevor das im anderen Thread ausufert.

Erstmal noch hier der Code, den ich ein wenig verkürze, damit es besser passt:

Code:
/* Level 5 */

IF
InParty(Myself)
Global("C#BE_LevelUp","LOCALS",0) GlobalLT("C#LevelUp","LOCALS",5)
XPGT(Player1,15999)
XPLT(Myself,16000)
XPGT(Myself,7999)
CheckStat(Player1, 0, LEVELDRAIN)
THEN
RESPONSE #100
SetInterrupt(FALSE)
SetGlobal("C#LevelUp","LOCALS",5)
AddXPObject(Myself,8000)
SetInterrupt(TRUE)
END


/* Level 4 */

IF
InParty(Myself)
Global("C#BE_LevelUp","LOCALS",0) GlobalLT("C#LevelUp","LOCALS",4)
XPGT(Player1,7999)
XPLT(Myself,8000)
XPGT(Myself,3999)
CheckStat(Player1, 0, LEVELDRAIN)
THEN
RESPONSE #100
SetInterrupt(FALSE)
SetGlobal("C#LevelUp","LOCALS",4)
AddXPObject(Myself,4000)
SetInterrupt(TRUE)
END


/* Level 3 */

IF
InParty(Myself)
Global("C#BE_LevelUp","LOCALS",0) GlobalLT("C#LevelUp","LOCALS",3)
XPGT(Player1,3999)
XPLT(Myself,4000)
XPGT(Myself,1999)
CheckStat(Player1, 0, LEVELDRAIN)
THEN
RESPONSE #100
SetInterrupt(FALSE)
SetGlobal("C#LevelUp","LOCALS",3)
AddXPObject(Myself,2000)
SetInterrupt(TRUE)
END

/* Level 2 */

IF
InParty(Myself)
Global("C#BE_LevelUp","LOCALS",0) Global("C#LevelUp","LOCALS",0)
XPGT(Player1,1999)
XPLT(Myself,2000)
CheckStat(Player1, 0, LEVELDRAIN)
THEN
RESPONSE #100
SetInterrupt(FALSE)
SetGlobal("C#LevelUp","LOCALS",2)
AddXPObject(Myself,2000)
SetInterrupt(TRUE)
END

IF
InParty(Myself)
Global("C#BE_LevelUp","LOCALS",0)
THEN
RESPONSE #100
SetGlobal("C#BE_LevelUp","LOCALS",1)
END

So, und jetzt das Problem:
NPC hat 2500 XP, der HC hat 12000 XP. Dann ist der "Level-3 Block" true und wird ausgeführt und der NPC bekommt 2000 XP und hat dann 4500 XP. Die Blocks vorher werden nicht ausgeführt, weil XPGT(Myself,ab) nicht true ist. Die späteren Blocks werden nicht ausgeführt wegen der LevelUp-Variable die gesetzt wird (und weil die XPLT Bedingung nicht erfüllt ist).

Ich dachte zuerst, dass für jeden Stufen/XP-Aufstieg das Skript dann einmal durchlaufen wird. Aber der letzte Block sollte das doch verhindern, wenn die BE_LevelUp Variable auf 1 gesetzt wird kann das Skript nie wieder laufen, oder? Bzw. schon, aber es passiert nichts.

Da ich aber schon Jasteys Mods gespielt habe und mir ziemlich sicher bin, dass da der Stufenaufstieg über mehrere Stufen klappt, bin ich ein wenig irritiert.

Und ich hätte die Skriptblöcke einfach in anderer Reihenfolge genutzt: also zuerst nach "Level 1" fragen, und wenn ja, dann die XP für "Level 2" geben, dann abfragen ob es mit dem nächsten weitergehen soll etc. Dann würde das Skript nur einmal laufen und zwar so lange, bis das Ziel-Level erreicht ist.

Aber da ich ja offensichtlich einen Denkfehler drin habe: welcher ist es? ;)
 
Zuletzt bearbeitet:

Argent

Senior Member
Registriert
13.07.2010
Beiträge
186
Du hättest Recht, wenn die Skriptblöcke alle mit Continue() abschließen würden. Dann würde das Skript bis zum Ende durchlaufen und damit auch den letzten Block erreichen und ausführen.

Da diese Aktion fehlt, wird nur der jeweils erste zutreffende Block ausgeführt, und dann wird der Skriptdurchlauf neu gestartet. Der letzte Block mit der "C#BE_LevelUp" Variable wird also nur erreicht, wenn alle anderen Blöcke vorher nicht (mehr) zutreffen.
 

Maus

Senior Member
Registriert
07.08.2002
Beiträge
9.378
Ähm ja, genau. *hust*

Danke ;)
 

Jastey

Matron Modderholic
Registriert
16.05.2004
Beiträge
12.918
Da wir grad dabei sind. ich weiß auch nicht mehr, warum ich die Blöcke nicht in die "richtige" Reihenfolge gesetzt habe und mit Continue() gearbeitet habe. Das würde dann denselben Effekt haben, oder?
Wäre das effizienter?
 

Argent

Senior Member
Registriert
13.07.2010
Beiträge
186
Mit Continue() müsste man die Skriptblöcke komplett umschreiben, da alle Bedingungen nur einmal pro Skriptdurchgang ausgewertet werden. Es wären wahrscheinlich deutlich mehr Blöcke notwendig, um alle Fälle abzudecken. Die jetzige Implementierung benötigt zwar mehrere Skriptdurchgänge, ist dafür aber recht kompakt geschrieben.
 

Maus

Senior Member
Registriert
07.08.2002
Beiträge
9.378
Ich hätte da eine Idee und könnte das vielleicht heute abend mal aufschreiben... Dann könntet ihr schauen, ob das funktionieren würde ;)
 
Oben