[Tutorial] Moddingerfahrungen - Tricks und Kniffe

Ascalon

Senior Member
Registriert
08.04.2008
Beiträge
2.730
Wie bringe ich einen Charakter von BGI nach BGII?

Es gibt ungefähr eine Milliarde Wege, einen Charakter, der bereits in BGI bestand, in BGII weiter zu spielen. Davon sind drei Wege* okay und nutzbar, je nach dem, welchen Effekt man erzielen will:

I.) Der Charakter trennt sich von der Gruppe, geht eine Zeitlang seinen eigenen Weg und stößt in Amn wieder hinzu.

Die einfachste Möglichkeit schlechthin. Erschafft einfach eine zweite Figur und lasst sie in Amn erscheinen.

II.) Der Charakter trennt sich von der Gruppe, folgt Charname aber nach Amn.

Dies ist die Möglichkeit, eine Spielfigur weiter zu benutzen. Wenn ich einen NSC mühsam in BGI aufgelevelt habe, dann wäre es eine Schande, dies alles einfach hinter mir zu lassen. Bis Breager v.4.X habe ich diese Möglichkeit selbst genutzt. Und die sieht folgendermaßen aus:

- In das Override-Script der Figur kommt:

Code:
IF
See(player1)
Global("ENDOFBG1","GLOBAL",1) // vgl. Anm. 1
AreaCheck("AR7300")  // vgl. Anm. 2
THEN
RESPONSE #100
SetGlobal("ACBRETOBG2","GLOBAL",1) // vgl. Anm. 3
SetLeavePartyDialogFile() // vgl. Anm. 4
StartDialogNoSet(player1)
END

1.) Die Globale "ENDOFBG1" kann den Wert 0, 1 oder 2 haben. 0 steht dabei für: Alles, was vor dem regulären Ende von BG1 passiert, also bevor Sarevoks Todesfilm gespielt wird. 1 ist alles, was zwischen Sarevoks Tod und dem Chateau Irenicus passiert, 2 ist alles, was nach dem Introfilm von BGII stattfindet.

2.) Das Area 7300 ist der Fürstenpalast. In diesem Fall gehe ich davon aus, dass der Spieler nur noch dann zum Fürstenpalast geht wenn er das Spiel wechseln will.

3.) Eine Globale, die festlegt, dass der Charakter gewechselt ist.

4.) Der nachfolgende Dialog wird über die ACBreP-Dateil gespielt.

In die Datei ACBreP.D kommt dann:

Code:
IF ~Global("ACBRETOBG2","GLOBAL",1)~ THEN BEGIN X
~Ich glaube, es wird Zeit, Lebewohl zu sagen.~
IF ~~ THEN DO ~SetGlobal("ACBRETOBG2","GLOBAL",2)LeaveParty()~ EXIT
END

Und in die AR7300 kommt:

Code:
IF
Global("ENDOFBG1","GLOBAL",1)
Global("ACBRETOBG2","GLOBAL",2)
THEN
RESPONSE #100
SetGlobal("ACBRETOBG2","GLOBAL",3)
ClearAllActions()
StartCutsceneMode()
MoveViewObject("ACBRE",INSTANT)
Wait(1)
FadeToColor([20.0],0)
Wait(1)
AddXPObject("ACBRE",50000)  // Geschätzt das, was die Charaktere bekommen, wenn sie durch CI durchwetzen
ActionOverride("ACBRE",TakePartyItem("ACAXE"))
ActionOverride("ACBRE",DestroyItem("ACAXE"))
ActionOverride("ACBRE",DropInventory())  //Breagar soll keine Gegenstände aus BGI mitnehmen und seine Axt (ACAXE) soll den Charakteren nicht in die Hände fallen.
ActionOverride("ACBRE",CreateItem("ACAXE",0,0,0))
ActionOverride("ACBRE",CreateItem("ACCHAN",0,0,0))
ActionOverride("ACBRE",CreateItem("HELM08",0,0,0)) // Seine drei StartItems
ActionOverride("ACBRE",FillSlot(SLOT_HELMET))
ActionOverride("ACBRE",FillSlot(SLOT_ARMOR))
ActionOverride("ACBRE",FillSlot(SLOT_WEAPON0))  // Items werden angelegt
ActionOverride("ACBRE",MoveBetweenAreas("AR0334",[648.229],3))  //Breagar verschwindet nach AR0334, also Cromwells Schmiede.
Wait(2)
FadeFromColor([20.0],0)
Wait(1)
EndCutSceneMode()
END

III.) Der Charakter wird zusammen mit Charname gefangen genommen und kommt so nach Amn.

Schitzophrenerweise ist dieser Weg gleichzeitig der einfachste und schwierigste zugleich, denn dafür muss man die Mechanik des Übergangs am besten kennen. Theroretisch müssten mehrere Skripte angepasst werden, wenn jeder Schritt nachvollzogen werden soll - Wir begnügen uns damit, den Übergang zu umgehen. Aber vorweg eine Sache, die man verstehen muss:

In Dialogen gibt es verschiedene Möglichkeiten, Aktionen durchzuziehen. Und jede Möglichkeit hat ihre Vor- und ihre Nachteile. Angenommen, ich frage jemanden, ob er eine Salami- oder eine Schinkenpizza will, dann sieht mein Dialog entweder so aus:
Code:
IF ~~ THEN BEGIN PIZZA
SAY ~Was willst du auf deine Pizza?~
++ ~Salami~ + PIZZA1
++ ~Schinken~ + PIZZA2
END

IF ~~ THEN BEGIN PIZZA1
SAY ~Okay.~
IF ~~ THEN DO ~CreateItem("SALPIZZA")~ EXIT
END

IF ~~ THEN BEGIN PIZZA2
SAY ~Okay.~
IF ~~ THEN DO ~CreateItem("SCHPIZZA")~ EXIT
END

Oder so:

Code:
IF ~~ THEN BEGIN PIZZA
SAY ~Was willst du auf deine Pizza?~
++ ~Salami~ DO ~CreateItem("SALPIZZA")~ + PIZZA1
++ ~Schinken~ DO ~CreateItem("SCHPIZZA")~ + PIZZA1
END

IF ~~ THEN BEGIN PIZZA1
SAY ~Okay.~
IF ~~ THEN EXIT
END

Beide Dialoge sehen im Spiel vollkommen gleich aus und beide sorgen dafür, dass ich am Ende eine Pizza habe, nur kann es manchmal von Vorteil sein, die Pizza erst NACH dem Okay zu haben und nicht schon vorher. In unserem Fall ist es jedoch etwas anders,

Die Figur, die uns nach BGII bringt, ist Belt. Genauer gesagt, die Kreatur BELTBRD.CRE mit dem Dialog BELTBRD.DLG.

Er fragt uns: "Sind Sie bereit für die Reise nach Athkatla?", wir sagen "Ja, ich bin bereit." und er sagt: "Viel Glück und eine gute Reise." Sobald er diesen Satz gesagt hat beginnt der Übergang, und wenn ich in diese Aktion reinpfusche bekomme ich nur Malesse (jedenfalls hat es bei mir nicht funktioniert). Also greifen wir nicht in DIESEN State ein, sondern in den davor (wie im 2. Pizzabeispiel). Dazu brauchen wir den Befehl ADD_TRANS_ACTION.

ADD_TRANS_ACTION ist der Befehl, der einer Transition eine Aktion hinzufügt. Eine Transition ist alles das, was nach dem SAY kommt. IF ~~ THEN DO xyz.

Wir wollen die Transition
Code:
++ ~Ja, ich bin bereit.~ + 12[/B] in [B]++ ~Ja, ich bin bereit.~ DO ~ActionOverride("ACBRE",LeaveParty())ActionOverride("ACBRE",ChangeAIScript("",DEFAULT))ActionOverride("ACBRE",ClearAllActions())ActionOverride("ACBRE",DropInventory())SetGlobal("ACBREPartyBG1","GLOBAL",1)~ + 12
ändern. Das geht so:

In die Datei "ACBELT.D" (oder wie auch immer) kommt:

Code:
ADD_TRANS_ACTION BELTBRD BEGIN 11 END BEGIN 0 END ~ActionOverride("ACBRE",LeaveParty())ActionOverride("ACBRE",ChangeAIScript("",DEFAULT))ActionOverride("ACBRE",ClearAllActions())ActionOverride("ACBRE",DropInventory())SetGlobal("ACBREPartyBG1","GLOBAL",1)~

Diese wird Compiliert und der erste Teil ist getan. Was passiert da genau?

ADD_TRANS_ACTION - Der Befehl selbst
BELTBRD - Die DLG-Datei, in die eingegriffen wird
BEGIN 11 END - der State, in den eingegriffen wird
BEGIN 0 END - Die Transition, in die eingegriffen wird

ActionOverride("ACBRE",LeaveParty())
ActionOverride("ACBRE",ChangeAIScript("",DEFAULT))
ActionOverride("ACBRE",ClearAllActions())
ActionOverride("ACBRE",DropInventory())
SetGlobal("ACBREPartyBG1","GLOBAL",1)

Zuerst verlässt Breagar die Party, dann wird sein Skript, das ihm vom Spieler evtl. zugewiesen wurde, gelöscht. Dann, falls er noch irgendwelche Restbefehle hatte, werden diese abgebrochen, sein ganzer Kram wird abgelegt (die Putzfrauen im Palast müssen ja auch was zu tun haben) und die Globale wird gesetzt. Die Globale erfüllt den gleichen Zweck wie oben das Global("ACBRETOBG2","GLOBAL",3), ich habe nur eine neue Globale benutzt, um der BGT-Norm zu entsprechen.

Jetzt ist Breagar vorbereitet, um nach BGII zu kommen. Was jetzt kommt, ist trivial. Wir suchen uns ein Areal und einen Punkt aus, wo wir Big B. haben wollen. zum Beispiel einen der Käfige in Irenicus' Keller. Also wird eine "AC0602.BAF" erstellt mit dem Inhalt:

Code:
IF
	OnCreation()  // sobald das Areal das erste mal aufgerufen wird
	Global("ACBREPartyBG1","GLOBAL",1) // Wenn Breagar mitgenommen wurde
	Global("ACBREEXISTS","GLOBAL",1) // Nur zur Sicherheit noch ne Abfrage
	Global("ACBRE","AR0602",0) // Wenn Dies hier noch nicht gemacht wurde
THEN
	RESPONSE #100
		SetGlobal("ACBRE","AR0602",1)
		MoveGlobal("AR0602","ACBRE",[3374.3068])
		Continue()
END

Fertig. Das ganze kommt mittels

Code:
EXTEND_TOP ~AR0602.bcs~ ~ACBre/BAF/AC0602.BAF~
in der TP2 an das Areaskript dran, fertig. Wilkommen im Chateau Irenicus, Breagar.
_______________
* Wenn ich in diesem Thread von Wegen und Möglichkeiten rede, dann sind das alles nur MEINE Erfahrungen. Es gibt wahrscheinlich noch gut eine Milliarde anderer Möglichkeiten, aber das, was ich hier aufzähle, sind die Sachen, die ich probiert habe und die funktionieren.
 
Zuletzt bearbeitet von einem Moderator:

Gerri

Senior Member
Registriert
21.11.2006
Beiträge
920
Boah! :up: Ich verneige mich in Ehrfurcht vor Deiner Erfahrung! :)
 

Yago

Senior Member
Registriert
28.05.2007
Beiträge
469
Tolle Anleitung Ascalon :-)
Solltest du noch mehr in die richtung bringen könnte ich ja schon bald zum modden anfangen XD (Aber dafür habe ich ja sowieso nie Zeit)

Btw Was ist aus dem Bilderwettbewerb geworden?
 

Toran

Schattenritter
Registriert
09.03.2008
Beiträge
2.377
Spitze, Ascalon. :up:
 

Arren

Senior Member
Registriert
28.04.2009
Beiträge
421
Jau, super Ascalon, danke für die Erfahrungsberichte!:up::up:
Ich hoffe, man darf das hier vorgestellte Nutzen ohne Copyrightrechte zu verletzen?:)
Auch hoffe ich, dass du noch mehr deines Wissen an uns weitergibst!:D
 

Ascalon

Senior Member
Registriert
08.04.2008
Beiträge
2.730
Ich hoffe, man darf das hier vorgestellte Nutzen ohne Copyrightrechte zu verletzen?

Äh, nein. Ich werde hier meine Sachen nur reinstellen um damit anzugeben was ich alles kann. ;)
 

Ascalon

Senior Member
Registriert
08.04.2008
Beiträge
2.730
Skripterstellung für Anfänger​

Wie man einen einfachen Dialog schreibt kann man in jeden 3. Tutorial nachlesen, aber wie funktionieren eigentlich Skripte? Was brauche ich, damit eins funktioniert, was kann ich alles machen, was... ja was zur Hölle IST ein Skript eigentlich?

Das Script

Fangen wir also mal ganz von vorne an, stelle mer uns ma janz dumm. Ein Script ist das "Drehbuch", das einem Charakter sagt, was er wann zu tun hat. Jede Figur kann bis zu 5 davon haben:

Das OVERRIDE Script wird immer als erstes abgefragt. Sobald ein Block im Override Script wahr wird, wird dieser ausgeführt.

Danach folgen CLASS, RACE, GENERAL, die ebenfalls von oben nach unten abgefragt werden. Das letzte inst das DEFAULT Script, das in Game verändert werden kann, sobald der NSC die Party betritt.

Außerdem haben auch einige Gegenstände Scripte, fast alle Areas haben Scripte und über allem schwebt das Script BALDUR.BCS, das die ganze Zeit abgefragt wird.

Ein Script wird von oben nach unten durchgegangen, bis zum ersten Scriptblock, dessen Vorraussetzungen erfüllt werden, dann beginnt die Suche wieder von Oben. Ausnahme: Wenn der Scriptblock mit dem Befehl "CONTINUE()" beendet wird. Näheres dazu unten.

Scriptblock

Ein Scriptblock hat immer die gleiche Form:

IF
- Voraussetzung
- Voraussetzung
- Voraussetzung
THEN
RESPONSE #X
- Aktion
- Aktion
(RESPONSE #Y)
- Aktion
- Aktion
END

Der Teil von IF bis THEN beinhaltet die Trigger, der (die) Tei(e) zwischen RESPONSE und END die Aktionen. Kommen wir zunächst zum

TRIGGERBLOCK

Hier werden die Voraussetzungen gelistet, unter denen dieses Script startet. Alle Trigger finden sich in der IESDP aufgelistet (siehe weiter unten). ein Triggerblock kann so aussehen:

Code:
IF
        See(PLAYER1)
THEN

Das bedeutet, dass das Script immer dann ausgeführt wird, wenn die Figur aktiv ist, handlungsfähig ist und den Hauptcharakter sieht. Ein Scriptblock kann aber auch so aussehen:

Code:
IF
	!Global("ENDOFBG1","GLOBAL",2)
	Global("ACBREEXISTS","GLOBAL",1)
	InMyArea("ACBRECUT")
	!Global("ACWORKTIMERSTARTED","GLOBAL",1)
	Global("ACBRESPAWNED","AR6701",1)
	OR(3)
	Global("ACACCIDENT","GLOBAL",2)
	Global("ACBREINPARTY","GLOBAL",2)
	TimeOfDay(NIGHT)
THEN

Hier werden ein Haufen Dinge abgefragt, um einen Haufen Sonderfälle zu überbrücken.

Ein ! vor einem Trigger negiert ihn, !Global("ENDOFBG1","GLOBAL",2) meint also: Wenn die Variable "ENDOFBG1" nicht auf 2 steht.

OR(3) sagt aus, dass mindestens eine der drei Trigger aktiviert werden (der Fachmann sagt: Wenn ein Trigger als "Wahr" erkannt wird) muss. Dabei ist es ein inklusives Oder, also heißt es nicht "Nur eine dieser Optionen" sondern "Entweder eine, oder zwei, oder alle dieser Optionen". OR() steht immer vor den Abfragen, und das kann dan auch schonmal ein OR(10) sein. Diee Abfrage ist ein beliebter Stolperstein, denn wenn das Skript so aussehen würde:

Code:
IF
	!Global("ENDOFBG1","GLOBAL",2)
	Global("ACBREEXISTS","GLOBAL",1)
	InMyArea("ACBRECUT")
	!Global("ACWORKTIMERSTARTED","GLOBAL",1)
	Global("ACBRESPAWNED","AR6701",1)
	OR(2)
	Global("ACACCIDENT","GLOBAL",2)
	Global("ACBREINPARTY","GLOBAL",2)
	TimeOfDay(NIGHT)
THEN

dann wäre die Abfrage, ob es Nacht ist, eine notwendige Voraussetzung und würde nicht mehr als hinreichend gezählt.

Eine sonderstellung hält der Trigger "TRUE()" inne, der immer als wahr ausgegeben wird. [ach was. - der Setzer]

Das bedeutet nichts anderes als dass der Scriptblock

Code:
IF
   TRUE()
THEN

Bei jeder erneuten Abfrage des Scriptes gefeuert wird

AKTIONSBLOCK

Im Aktionsblock werden alle Befehle aufgelistet, die die Figur/das Areas, dem das Skript angetackert ist, ausführt, und zwar beginnend vom ersten runter bis zum letzten. Jeder Aktionsblock beginnt mit RESPONSE #X, wobei X einen Wert von 0 bis 100 haben kann und die Wahrscheinlichkeit angibt, mit der der Part darunter getriggert wird.

Code:
IF
See(PLAYER1)
THEN
RESPONSE #100
Dialog(PLAYER1)
END

sagt: WENN ich den HC sehe dann beginne ich zu 100% einen Dialog mit ihm.

Code:
IF
See(PLAYER1)
THEN
RESPONSE #50
Dialog(PLAYER1)
RESPONSE #50
Kill(myself)
END

sagt: Wenn ich den HC sehe rede ich zu 50% mit ihm, zu 50% begehe ich Selbstmord.

Code:
IF
See(PLAYER1)
THEN
RESPONSE #100
Dialog(PLAYER1)
RESPONSE #100
Kill(myself)
END

scheint übrigens ebenfalls eine 50/50 Chance zu sein.

Einmal triggernde Scripte

Was mache ich, wenn ich einen NSC habe, der den HC ansprechen soll? Ein Script wie dieses:

Code:
IF
See(PLAYER1)
THEN
RESPONSE #100
Dialog(PLAYER1)
END

Was passiert dann? Das Script startet, wenn der NSC den Spieler sieht, spricht er ihn an. Wenn das Gespräch vorüber ist, startet das Script von oben, und sobald es an diesem Punkt angelangt ist, spricht er ihn wieder an. Und wieder. Und wieder. Also müssen wir eine Globale einbauen, die das ganze auf einmal beschränkt. Zum Beispiel so:

Code:
IF
Global("MYGLOBAL","LOCALS",0)
See(PLAYER1)
THEN
RESPONSE #100
SetGlobal("MYGLOBAL","LOCALS",1)
Dialog(PLAYER1)
END

Jetzt wird der Block ausgeführt, wenn die Globale auf Null steht, der Block selbst setzt sie auf 1 und schon sind alle Sorgen weg. Dummerweise auch der Scriptblock, denn wenn aus irgendwelchen blöden Gründen das Script verzögert wird, dann wird der Block irgendwann ungültig und dann wars schade drum. Eine ausführlichere Variante findet sich hier. Hier werden normale Banter, verzögerte Banter und Romanzen/Freundschaften abgehandelt.

Fallstricke

Gibts so einige. Einer meiner beliebtesten war es, eine Minicutscene in ein Personenscript einzubinden:

Code:
IF
...
THEN
RESPONSE #100
SetGlobal("ACBREPLOT","GLOBAL",2)
ClearAllActions()
StartCutSceneMode()
Wait(1)
FadeToColor([60.0],0)
Wait(3)
[B]DestroySelf()[/B]
Wait(3)
FadeFromColor([60.0],0)
Wait(4)
ActionOverride("ACBRE",StartDialogNoSet(PLAYER1))
END

Der Bildschirm sollte in den Cutscenemode gehen (alle Steuerelemente verschwinden), dann ins Schwarze faden, dann sollte die Figur sich selbst löschen, dann sollte alles wieder hell werden und dann sollte Breagar ein Gespräch anfangen. Was passierte? Die Cutscene startete, die Figur verschwand... und das Skript brach ab, denn es war ja keine Figur mehr da, die es hätte ausführen können.

Was auch immer beliebt ist ist ein Globalfehler:

Code:
IF
Global("MYGLOBAL","LOCALS",0)
See(PLAYER1)
THEN
RESPONSE #100
SetGlobal("MYGLOBAL","LOCALS",0)
Dialog(PLAYER1)
END

So etwas entsteht meistens, wenn man die Zeile Global("MYGLOBAL","LOCALS",0) einfach nur Copypastet und ein Set davorschreibt. Wenn man eine Globale wie "ACBREAGARWANTSTOTALKABOUTBEERINTAVERNS" (zu Demonstrationszwecken überhöht) hat, dann will man das nicht 5000 mal schreiben, zumal dann die Fehlerwahrscheinlichkeit irgendwann gegen 100 geht. Also kopiert man die obere Zeile und vergisst, dass die Globale ja verändert werden sollte. Die Macher von der Holy Handgranate sind auch in diese Falle gestiefelt.

Falsche Reihenfolge der Befehle:

Code:
IF
Global("MYGLOBAL","LOCALS",0)
See(PLAYER1)
THEN
RESPONSE #100
Enemy()
Attack(PLAYER1)
SetGlobal("MYGLOBAL","LOCALS",1)
END

Wenn ich den HC sehe, werde ich feindlich und greife ihn an, dann setze ich die Globale auf 1. Das sieht auf den erstem Blick richtig aus, leider pausiert ein Script unter Umständen bei Befehlen, die länger brauchen, um ausgeführt zu werden: Attack, WalkToPoint, EscapeAreaMove, Spell... Faustregel: Je länger eine Aktion braucht, um durchgeführt zu werden, desto weiter sollte sie hinten stehen. Bei mehreren langen Aktionen lieber zwei Blöcke machen.

Die IESDP

findet sich hier: http://iesdp.gibberlings3.net/main.htm

Ohne diese Seite ist Modden quasi nicht möglich. Hier sind alle Aktionen, Trigger und Identifier aufgeführt und erklärt (was das im einzelnen ist kläre ma später). Bookmarken, wenn ihr wirklich modden wollt - oder noch besser downloaden.
 

Toran

Schattenritter
Registriert
09.03.2008
Beiträge
2.377
@Ascalon
Wahnsinn :up:
 

Khamal

Member
Registriert
06.03.2009
Beiträge
60
Sehr schöner Thread Ascalon. Das macht mir ja fast Lust, mich selbst mal an einem Mod zu versuchen ;)
 

White Agnus

Senior Member
Registriert
05.09.2008
Beiträge
5.088
Hinzufügen eines Actors zu einer Are.

Da Ascalon schon so schön vorgelegt hat, will ich Euch mal das Area Patching ein bischen näher bringen und fange mit den Actors an... ;)

Okay, jetzt werdet Ihr Euch fragen, wieso sollte man eine Creature einer Area hinzufügen, wenn man das ganze über ein Script machen kann…
Die Antwort dazu ist eigentlich, dass es über die Area viel einfacher ist, die Creature zu bestimmten Zeiten auftauchen zu lassen…
Aussderdem kann man Ihr für die Area selbst Scripte zuweisen.
Der Nachteil ist aber, dass man die Area im Savegame noch nicht besucht haben darf, da sonst der Actor nicht auftaucht…
Der Code, den ich hier schildere ist für das tp2 Coding gedacht!

Ok, dann fang ich mal mit ein paar Erklärungen an, die für die Anleitung wichtig sein könnten:

Was ist ein Offset?

Ein Offset ist eine Adresse, die zu bestimmten Werten in einer Datei zeigen, die mit Weidu ausgelesen und geschrieben werden können.

Was ist eine Variable?

Eine Variable ist ein Zeichensatz, der eine Zahl oder einen String (also einen anderen Zeichensatz) enthalten kann.

Was ist eine Schleife?

Eine Schleife wiederholt einen Code Block solange bis die Bedingung erfüllt ist…
Im folgenden Code werden FOR und IF Schleifen benutzt….

Fangen wir mit der FOR Schleife in Weidu an:
Code:
FOR (var=0; var< var2; var=var+1) BEGIN
… (hier steht, was ausgeführt warden soll…)
END

var=0 ist der Startpunkt, also 0
var< var2 ist die Bedingung, die Schleife läuft durch, solange var kleiner als var2 ist
var=var+1 solange die vorherige Bedingung nicht erfüllt ist, wird var bei jedem Schritt um 1 erhöht



So jetzt noch die IF Schleife:
In der tp2 kann ACTION_IF und PATCH_IF benutzt werden, da in diesem Beispiel nur PATCH_IF genutzt wird, werde ich dieses erklären:

Code:
PATCH_IF (Variable = Bedingung) THEN BEGIN
… (hier steht, was ausgeführt werden soll, wenn die Bedingung wahr ist…)
END
ELSE BEGIN
… (hier steht, was ausgeführt werden soll, wenn die Bedingung nicht wahr ist…)
END

Was ist ein Bit, was ist ein Nibble, was ist ein Byte?

Ein Bit ist eine 0 oder eine 1.
Ein Nibble sind 4 Bit. (Mit einem Nibble können 16 Zahlen dargestellt werden)
Ein Byte sind 8 Bit. (Mit einem Byte können 256 Zahlen dargestellt werden)


Was bedeuten die ganze READ_* und WRITE_* im Code?

Mit READ_* kann man den Wert eines Offsets einlesen.
Mit WRITE_* können Werte in ein Offset geschrieben werden.

Folgende Argumente gibt es für *:
BYTE: Hiermit wird ein Byte eingelesen/geschrieben, also acht bit
SHORT: Hiermit werden zwei Byte eingelesen/geschrieben, also 16 Bit
LONG: Hiermit werden vier Byte eingelesen/geschrieben, also 32 Bit
ASCII: Hiermit wird ein Zeichensatz eingelesen/geschrieben
STRREF: Hiermit wird ein Stringref. Aus der Dialog(f).tlk gelesen/geschrieben

Was ist Hexadezimal?

Hexadezimal ist ein Zahlensystem, genau wie unser Dezimalsystem, nur dass es 16 Zeichen darstellen kann, im Gegensatz zu 10:

0=0
1=1
2=2
3=3
4=4
5=5
6=6
7=7
8=8
9=9
10=a
11=b
12=c
13=d
14=e
15=f




Code:
COPY_EXISTING ~ARXXXX.are~ ~override~

Mit dieser Zeile wird die bereits existierende Area in den Override Ordner kopiert, sodass diese gepatcht werden kann.

Code:
 READ_LONG  0x54  "actors_offset"
 READ_SHORT 0x58  "actors_num"
 READ_SHORT 0x5a  "infotrig_num"
 READ_LONG  0x5c  "infotrig_offset"
 READ_LONG  0x60  "spawns_offset"
 READ_LONG  0x64  "spawns_num"
 READ_LONG  0x68  "entrances_offset"
 READ_LONG  0x6c  "entrances_num"
 READ_LONG  0x70  "cont_offset"
 READ_SHORT 0x74  "cont_num"
 READ_SHORT 0x76  "items_num"
 READ_LONG  0x78  "items_offset"
 READ_LONG  0x7c  "vert_offset"
 READ_SHORT 0x80  "vert_num"
 READ_SHORT 0x82  "amb_num"
 READ_LONG  0x84  "amb_offset"
 READ_LONG  0x88  "vars_offset"
 READ_LONG  0x8c  "vars_num"
 READ_LONG  0xa0  "expbmp_offset"
 READ_LONG  0xa4  "doors_num"
 READ_LONG  0xa8  "doors_offset"
 READ_LONG  0xac  "anim_num"
 READ_LONG  0xb0  "anim_offset"
 READ_LONG  0xb4  "tiled_num"
 READ_LONG  0xb8  "tiled_offset"
 READ_LONG  0xbc  "songs_offset"
 READ_LONG  0xc0  "rest_spawns_offset"
 READ_LONG  0xc4  "automap_offset"
 READ_LONG  0xc8  "automap_num"
 READ_LONG  0xcc	"projectile_traps_offset"
 READ_LONG  0xd0	"projectile_traps_num"

Hiermit werden die ganzen Offsets der Area eingelesen. [Hier braucht nichts geändert zu werden.]

Code:
SET actor_1=1
 FOR( cnt=0; cnt<"%actors_num%"; cnt=cnt+1 ) BEGIN 
   READ_ASCII ("%actors_offset %"+0x110*cnt) ~ actor_name ~ (32) NULL
   PATCH_IF (("%actor_name%" STRING_COMPARE_CASE "Actorname")=0) THEN BEGIN
     actor_1=0
   END ELSE BEGIN
     actor_1=1
END
 END


Hiermit wird überprüft, ob der Actorname bereits vorhanden ist. (Wenn er existiert, dann wird das Patchen übersprungen) Dieser Schritt wird nicht unbedingt benötigt…
In diesem Block muss eigentlich nur der Actorname eingegeben werden!
Wenn der Name nicht vorhanden ist, hat die Variable actor_1 auf den Wert 1 gesetzt, wenn er bereits vorhanden ist, wird actor_1 auf 0 gesetzt…

Code:
PATCH_IF (actor_1=1) BEGIN             
…
END

Dieser Patchprozess wird nur ausgeführt, wenn actor_1 = 1 ist, also wenn der vorherige Block zurückgibt, dass der Actorname nicht vorhanden ist…

Code:
SET offset = ("%actors_offset%"+0x110*"%actors_num%")

Die Variable offset wird hiermit auf das letzte Offset des letzten Actor der Area gesetzt.

Code:
INSERT_BYTES offset 0x110

Hiermit werden 110 Bytes der Area am offset hinzugefügt, damit man einen neuen Actor hinzufügen kann…

Code:
WRITE_ASCII offset ~Name~

Hier wird der Actorname angegeben. (Dieser hat nichts mit dem Namen der Creature zu tun…)

Code:
WRITE_SHORT (offset+0x20) XXX

Das ist die X-Koordinate, wo der Actor spawnen soll…

Code:
WRITE_SHORT (offset+0x22) XXX

Das ist die Y.Koordinate, wo der Actor spawnen soll…

Code:
WRITE_SHORT (offset+0x24) XXX

Das ist die X-Koordinate, worauf der Actor zugeht

Code:
WRITE_SHORT (offset+0x26) XXX

Das ist die Y-Koordinate, worauf der Actor zugeht



Code:
 WRITE_LONG  (offset+0x34) X

Das ist die Ausrichtung, in welche der Actor blickt…
Hier gibt’s folgende Einstellungen:
0 = Süden
1 = Süden/Süden/Westen
2 = Süden/Westen
3 = Süden/Westen/Westen
4 = Westen
5 = Westen/Westen/Norden
6 = Westen/Norden
7 = Westen/Norden/Norden
8 = Norden
9 = Norden/Norden/Osten
10 = Norden/Osten
11 = Norden/Osten/Osten
12 = Osten
13 = Osten/Osten/Süden
14 = Osten/Süden
15 = Osten/Süden/Süden



Code:
WRITE_LONG  (offset+0x38) 0xffffffff

Hier wird der Removal Timer angegeben, ich kann leider nicht genauer erklären, wie er funktioniert, aber der Actor erscheint nicht, wenn der Timer nicht gesetzt wird…

Code:
WRITE_LONG  (offset+0x40) 0xffffffff

Hier wird angegeben zu welchen Zeiten der Actor auftauchen soll… (mit jedem Nibble (4 Bit) können vier Zeiten abgebildet werden)
So fangen wir mal mit dem ersten Nibble an:
0x00000001 : 00:30-01:29
0x00000002 : 01:30-02:29
0x00000004 : 02:30-03:29
0x00000008 : 03:30-04:29

Jetzt werdet Ihr Euch fragen, wie kann ich mit einem Bit mehrere Zeiten einstellen, dass funktioniert nach folgendem Prinzip:

0x00000003 : [=1+2] 00:30-01:29 und 01:30-02:29
0x00000005 : [=1+4] 00:30-01:29 und 02:30-03:29
0x00000006 : [=2+4] 01:30-02:29 und 02:30-03:29
0x00000007 : [=1+2+4] 00:30-01:29 und 01:30-02:29 und 02:30-03:29
0x00000009 : [=1+8] 00:30-01:29 und 03:30-04:29
0x0000000a : [=2+8] 01:30-02:29 und 03:30-04:29
0x0000000b : [=1+2+8] 00:30-01:29 und 01:30-02:29 und 03:30-04:29
0x0000000c : [=4+8] 02:30-03:29 und 03:30-04:29
0x0000000d : [=1+4+8] 00:30-01:29 und 02:30-03:29 und 03:30-04:29
0x0000000e : [=2+4+8] 01:30-02:29 und 02:30-03:29 und 03:30-04:29
0x0000000f : [=1+2+4+8] 00:30-01:29 und 01:30-02:29 und 02:30-03:29 und 03:30-04:29


Ok, dann liste ich noch die Zeiten der Nibbles auf:
0x87654321

1: 00:30-04:29
2: 04:30-08:29
3: 08:30-12:29
4: 12:30-16:29
5: 16:30-20:29
6: 20:30-00:29
7: wird nicht genutzt
8: wird nicht genutzt

Code:
WRITE_ASCII  (offset+0x48) ~Dialog~

Hiermit kann dem Actor ein Dialog zugewiesen werden. Dieser Dialog wird nur für diesen Actor ausgeführt. (d.h. der normale Dialog der Creature dieses Actor wird durch den überschrieben)

Code:
WRITE_ASCII  (offset+0x50) ~OverrideScript~
WRITE_ASCII  (offset+0x58) ~GeneralScript~
WRITE_ASCII  (offset+0x60) ~ClassScript~
WRITE_ASCII  (offset+0x68) ~RaceScript~
WRITE_ASCII  (offset+0x70) ~DefaultScript~
WRITE_ASCII  (offset+0x78) ~SpecificScript~

Hiermit können dem Actor Scripte zugewiesen werden. Diese Scripte werden nur für diesen Actor ausgeführt. (d.h. die normalen Scripte werden für die Creature dieses Actor überschrieben)



Code:
WRITE_ASCII (offset+0x80) ~Crefilename~

Hiermit wird angegeben, welche Creature dem Actor zugewiesen wird.

Code:
SET "actors_num"="%actors_num%"+1
WRITE_SHORT 0x58  "%actors_num%"

Hiermit wird die Nummer der Actoren der Area um eins erhöht und dann der neue Wert geschrieben…


So jetzt kommen wir zum letzten Teil, dem Patchen der anderen Offsets:

Code:
SET "actor_ext"=0x110*actor_1

Hiermit wird angegeben, dass die Variable actor_ext um 0x110*actor_1 erhöht wird.


Code:
 PATCH_IF ("%infotrig_offset%">"%actors_offset%") 
 OR (("%infotrig_offset%" == "%actors_offset%") AND ("%actors_num%" == 0))   
 BEGIN
   "infotrig_offset"="%infotrig_offset%"+"%actor_ext%"
   WRITE_LONG  0x5c "%infotrig_offset%"
 END
 PATCH_IF ("%spawns_offset%">"%actors_offset%") 
 OR (("%spawns_offset%" == "%actors_offset%") AND ("%actors_num%" == 0))   
 BEGIN
   "spawns_offset"="%spawns_offset%"+"%actor_ext%"
   WRITE_LONG  0x60  "%spawns_offset%"
 END
 PATCH_IF ("%entrances_offset%">"%actors_offset%") 
 OR (("%entrances_offset%" == "%actors_offset%") AND ("%actors_num%" == 0))   
 BEGIN
   "entrances_offset"="%entrances_offset%"+"%actor_ext%"
   WRITE_LONG  0x68  "%entrances_offset%"
 END
 PATCH_IF ("%cont_offset%">"%actors_offset%") 
 OR (("%cont_offset%" == "%actors_offset%") AND ("%actors_num%" == 0))   
 BEGIN
   "cont_offset"="%cont_offset%"+"%actor_ext%"
   WRITE_LONG  0x70  "%cont_offset%"
 END
 PATCH_IF ("%items_offset%">"%actors_offset%") 
 OR (("%items_offset%" == "%actors_offset%") AND ("%actors_num%" == 0))   
 BEGIN
   "items_offset"="%items_offset%"+"%actor_ext%"
   WRITE_LONG  0x78  "%items_offset%"
 END
 PATCH_IF ("%vert_offset%">"%actors_offset%") 
 OR (("%vert_offset%" == "%actors_offset%") AND ("%actors_num%" == 0))   
 BEGIN
   "vert_offset"="%vert_offset%"+"%actor_ext%"
   WRITE_LONG  0x7c  "%vert_offset%"
 END
 PATCH_IF ("%amb_offset%">"%actors_offset%") 
 OR (("%amb_offset%" == "%actors_offset%") AND ("%actors_num%" == 0))   
 BEGIN
   "amb_offset"="%amb_offset%"+"%actor_ext%"
   WRITE_LONG  0x84  "%amb_offset%"
 END
 PATCH_IF ("%vars_offset%">"%actors_offset%") 
 OR (("%vars_offset%" == "%actors_offset%") AND ("%actors_num%" == 0))   
 BEGIN
   "vars_offset"="%vars_offset%"+"%actor_ext%"
   WRITE_LONG  0x88  "%vars_offset%"
 END
 PATCH_IF ("%expbmp_offset%">"%actors_offset%") 
 OR (("%expbmp_offset%" == "%actors_offset%") AND ("%actors_num%" == 0))   
 BEGIN
   "expbmp_offset"="%expbmp_offset%"+"%actor_ext%"
   WRITE_LONG  0xa0  "%expbmp_offset%"
 END
 PATCH_IF ("%doors_offset%">"%actors_offset%") 
 OR (("%doors_offset%" == "%actors_offset%") AND ("%actors_num%" == 0))   
 BEGIN
   "doors_offset"="%doors_offset%"+"%actor_ext%"
   WRITE_LONG  0xa8  "%doors_offset%"
 END
 PATCH_IF ("%anim_offset%">"%actors_offset%") 
 OR (("%anim_offset%" == "%actors_offset%") AND ("%actors_num%" == 0))   
 BEGIN
   "anim_offset"="%anim_offset%"+"%actor_ext%"
   WRITE_LONG  0xb0  "%anim_offset%"
 END
 PATCH_IF ("%tiled_offset%">"%actors_offset%") 
 OR (("%tiled_offset%" == "%actors_offset%") AND ("%actors_num%" == 0))   
 BEGIN
   "tiled_offset"="%tiled_offset%"+"%actor_ext%"
   WRITE_LONG  0xb8  "%tiled_offset%"
 END
 PATCH_IF ("%songs_offset%">"%actors_offset%") 
 OR (("%songs_offset%" == "%actors_offset%") AND ("%actors_num%" == 0))   
 BEGIN
   "songs_offset"="%songs_offset%"+"%actor_ext%"
   WRITE_LONG  0xbc  "%songs_offset%"
 END
 PATCH_IF ("%rest_spawns_offset%">"%actors_offset%") 
 OR (("%rest_spawns_offset%" == "%actors_offset%") AND ("%actors_num%" == 0))   
 BEGIN
   "rest_spawns_offset"="%rest_spawns_offset%"+"%actor_ext%"
   WRITE_LONG  0xc0  "%rest_spawns_offset%"
 END
 PATCH_IF ("%automap_offset%">"%actors_offset%") 
 OR (("%automap_offset%" == "%actors_offset%") AND ("%actors_num%" == 0))   
 BEGIN
   "automap_offset"="%automap_offset%"+"%actor_ext%"
   WRITE_LONG  0xc4  "%automap_offset%"
 END
 PATCH_IF ("%projectile_traps_offset%">"%actors_offset%") 
 OR (("%projectile_traps_offset%" == "%actors_offset%") AND ("%actors_num%" == 0))   
 BEGIN
   "projectile_traps_offset"="%projectile_traps_offset%"+"%actor_ext%"
   	 WRITE_LONG  0xcc  "%projectile_traps_offset%"
 END

Falls irgendein Offset größer als das actors_offset , oder gleichgroß wie das actors_offset ist, dann wird das offset um die Variable actor_ext erhöht, damit die are File nicht kaputt gemacht wird… ;) [Hier muss wieder nichts geändert werden]

Code:
BUT_ONLY_IF_IT_CHANGES

Das gibt an, dass falls keine Änderungen an der Area vorgenommen warden, diese nicht in den Override Ordner kopiert wird!


So zum Schluss noch der Code am Stück:

Code:
COPY_EXISTING ~AR0300.are~ ~override~
READ_LONG  0x54  "actors_offset"
 READ_SHORT 0x58  "actors_num"
 READ_SHORT 0x5a  "infotrig_num"
 READ_LONG  0x5c  "infotrig_offset"
 READ_LONG  0x60  "spawns_offset"
 READ_LONG  0x64  "spawns_num"
 READ_LONG  0x68  "entrances_offset"
 READ_LONG  0x6c  "entrances_num"
 READ_LONG  0x70  "cont_offset"
 READ_SHORT 0x74  "cont_num"
 READ_SHORT 0x76  "items_num"
 READ_LONG  0x78  "items_offset"
 READ_LONG  0x7c  "vert_offset"
 READ_SHORT 0x80  "vert_num"
 READ_SHORT 0x82  "amb_num"
 READ_LONG  0x84  "amb_offset"
 READ_LONG  0x88  "vars_offset"
 READ_LONG  0x8c  "vars_num"
 READ_LONG  0xa0  "expbmp_offset"
 READ_LONG  0xa4  "doors_num"
 READ_LONG  0xa8  "doors_offset"
 READ_LONG  0xac  "anim_num"
 READ_LONG  0xb0  "anim_offset"
 READ_LONG  0xb4  "tiled_num"
 READ_LONG  0xb8  "tiled_offset"
 READ_LONG  0xbc  "songs_offset"
 READ_LONG  0xc0  "rest_spawns_offset"
 READ_LONG  0xc4  "automap_offset"
 READ_LONG  0xc8  "automap_num"
 READ_LONG  0xcc	"projectile_traps_offset"
 READ_LONG  0xd0	"projectile_traps_num"

SET actor_1=1
 FOR( cnt=0; cnt<"%actors_num%"; cnt=cnt+1 ) BEGIN 
   READ_ASCII ("%actors_offset %"+0x110*cnt) ~ actor_name ~ (32) NULL
   PATCH_IF (("%actor_name%" STRING_COMPARE_CASE "White Agnus")=0) BEGIN
     actor_1=0
   END ELSE BEGIN
     actor_1=1
END
 END

PATCH_IF (actor_1=1) BEGIN             
SET offset = ("%actors_offset%"+0x110*"%actors_num%")
INSERT_BYTES offset 0x110
WRITE_ASCII offset ~White Agnus~ 	//Actorname
WRITE_SHORT (offset+0x20) 1517	//Position X
WRITE_SHORT (offset+0x22) 1274	//Position Y
WRITE_SHORT (offset+0x24) 1517	//Destination X
WRITE_SHORT (offset+0x26) 1274	//Destination Y
WRITE_LONG  (offset+0x34) 4		//Actor Orientation
WRITE_LONG  (offset+0x38) 0xffffffff	//Actor Removal Timer
WRITE_LONG  (offset+0x40) 0xff0ffff0	//Actor Appearance Schedule
WRITE_ASCII  (offset+0x48) ~~		//Dialog
WRITE_ASCII  (offset+0x50) ~~		//OverrideScript
WRITE_ASCII  (offset+0x58) ~~		// GeneralScript
WRITE_ASCII  (offset+0x60) ~~		// ClassScript
WRITE_ASCII  (offset+0x68) ~~		// RaceScript
WRITE_ASCII  (offset+0x70) ~~		// DefaultScript
WRITE_ASCII  (offset+0x78) ~~		// SpecificScript
WRITE_ASCII (offset+0x80) ~Wagnus~	// Crefilename
SET "actors_num"="%actors_num%"+1
WRITE_SHORT 0x58  "%actors_num%"
END

SET "actor_ext"=0x110*actor_1

	PATCH_IF ("%infotrig_offset%">"%actors_offset%") 
 OR (("%infotrig_offset%" == "%actors_offset%") AND ("%actors_num%" == 0))   
 BEGIN
   "infotrig_offset"="%infotrig_offset%"+"%actor_ext%"
   WRITE_LONG  0x5c "%infotrig_offset%"
 END
 PATCH_IF ("%spawns_offset%">"%actors_offset%") 
 OR (("%spawns_offset%" == "%actors_offset%") AND ("%actors_num%" == 0))   
 BEGIN
   "spawns_offset"="%spawns_offset%"+"%actor_ext%"
   WRITE_LONG  0x60  "%spawns_offset%"
 END
 PATCH_IF ("%entrances_offset%">"%actors_offset%") 
 OR (("%entrances_offset%" == "%actors_offset%") AND ("%actors_num%" == 0))   
 BEGIN
   "entrances_offset"="%entrances_offset%"+"%actor_ext%"
   WRITE_LONG  0x68  "%entrances_offset%"
 END
 PATCH_IF ("%cont_offset%">"%actors_offset%") 
 OR (("%cont_offset%" == "%actors_offset%") AND ("%actors_num%" == 0))   
 BEGIN
   "cont_offset"="%cont_offset%"+"%actor_ext%"
   WRITE_LONG  0x70  "%cont_offset%"
 END
 PATCH_IF ("%items_offset%">"%actors_offset%") 
 OR (("%items_offset%" == "%actors_offset%") AND ("%actors_num%" == 0))   
 BEGIN
   "items_offset"="%items_offset%"+"%actor_ext%"
   WRITE_LONG  0x78  "%items_offset%"
 END
 PATCH_IF ("%vert_offset%">"%actors_offset%") 
 OR (("%vert_offset%" == "%actors_offset%") AND ("%actors_num%" == 0))   
 BEGIN
   "vert_offset"="%vert_offset%"+"%actor_ext%"
   WRITE_LONG  0x7c  "%vert_offset%"
 END
 PATCH_IF ("%amb_offset%">"%actors_offset%") 
 OR (("%amb_offset%" == "%actors_offset%") AND ("%actors_num%" == 0))   
 BEGIN
   "amb_offset"="%amb_offset%"+"%actor_ext%"
   WRITE_LONG  0x84  "%amb_offset%"
 END
 PATCH_IF ("%vars_offset%">"%actors_offset%") 
 OR (("%vars_offset%" == "%actors_offset%") AND ("%actors_num%" == 0))   
 BEGIN
   "vars_offset"="%vars_offset%"+"%actor_ext%"
   WRITE_LONG  0x88  "%vars_offset%"
 END
 PATCH_IF ("%expbmp_offset%">"%actors_offset%") 
 OR (("%expbmp_offset%" == "%actors_offset%") AND ("%actors_num%" == 0))   
 BEGIN
   "expbmp_offset"="%expbmp_offset%"+"%actor_ext%"
   WRITE_LONG  0xa0  "%expbmp_offset%"
 END
 PATCH_IF ("%doors_offset%">"%actors_offset%") 
 OR (("%doors_offset%" == "%actors_offset%") AND ("%actors_num%" == 0))   
 BEGIN
   "doors_offset"="%doors_offset%"+"%actor_ext%"
   WRITE_LONG  0xa8  "%doors_offset%"
 END
 PATCH_IF ("%anim_offset%">"%actors_offset%") 
 OR (("%anim_offset%" == "%actors_offset%") AND ("%actors_num%" == 0))   
 BEGIN
   "anim_offset"="%anim_offset%"+"%actor_ext%"
   WRITE_LONG  0xb0  "%anim_offset%"
 END
 PATCH_IF ("%tiled_offset%">"%actors_offset%") 
 OR (("%tiled_offset%" == "%actors_offset%") AND ("%actors_num%" == 0))   
 BEGIN
   "tiled_offset"="%tiled_offset%"+"%actor_ext%"
   WRITE_LONG  0xb8  "%tiled_offset%"
 END
 PATCH_IF ("%songs_offset%">"%actors_offset%") 
 OR (("%songs_offset%" == "%actors_offset%") AND ("%actors_num%" == 0))   
 BEGIN
   "songs_offset"="%songs_offset%"+"%actor_ext%"
   WRITE_LONG  0xbc  "%songs_offset%"
 END
 PATCH_IF ("%rest_spawns_offset%">"%actors_offset%") 
 OR (("%rest_spawns_offset%" == "%actors_offset%") AND ("%actors_num%" == 0))   
 BEGIN
   "rest_spawns_offset"="%rest_spawns_offset%"+"%actor_ext%"
   WRITE_LONG  0xc0  "%rest_spawns_offset%"
 END
 PATCH_IF ("%automap_offset%">"%actors_offset%") 
 OR (("%automap_offset%" == "%actors_offset%") AND ("%actors_num%" == 0))   
 BEGIN
   "automap_offset"="%automap_offset%"+"%actor_ext%"
   WRITE_LONG  0xc4  "%automap_offset%"
 END
 PATCH_IF ("%projectile_traps_offset%">"%actors_offset%") 
 OR (("%projectile_traps_offset%" == "%actors_offset%") AND ("%actors_num%" == 0))   
 BEGIN
   "projectile_traps_offset"="%projectile_traps_offset%"+"%actor_ext%"
   	 WRITE_LONG  0xcc  "%projectile_traps_offset%"
 END
BUT_ONLY_IF_IT_CHANGES

Hiermit wird die Creature Wagnus.cre von 04:30-20:29 auf Position x= 1517 Y=1274 mit der Ausrichtung nach Westen hinzugefügt.

Die Offsets sind auch alle im IESDP unter File Formats und dann unter Are Version 1 nachzulesen. Ausserdem ist die Weidu Documentation nicht zu verachten, wenn es um tp2 Code geht. ;)


Soo, ich hoffe ich hab mich gut ausgedrückt und es ist alles so verständlich, wie es sein soll…
Falls eine Erklärung fehlt, sagt mir Bescheid und ich ergänze es. :)
 
Zuletzt bearbeitet:

Toran

Schattenritter
Registriert
09.03.2008
Beiträge
2.377
@White Agnus Ascalon
Was ihr beiden hier reinstellt ist allererste Sahne. :up:
 

Arren

Senior Member
Registriert
28.04.2009
Beiträge
421
@White Agnus Ascalon
Was ihr beiden hier reinstellt ist allererste Sahne.


/signed

Sogar AREA-Modding wird hier erklärt:eek:, macht weiter so!
Ihr motiviert zum Modden, das ist toll:up::up::up:
 

Ascalon

Senior Member
Registriert
08.04.2008
Beiträge
2.730
Wie bastele ich eine Cutscene?​

Cutscenes sind im Grunde einfach nur Scripte, die über den Befehl StartCutScene("SCRIPTNAME") getriggert werden. Dabei ist es egal, ob der Befehl von einem Dialog aus gestartet wird oder einem anderem Script, aber ein paar kleine Dinge müssen schon noch beachtet werden. Angenommen, eine Cutscene soll sich sofort an ein Gespräch anschließen, dann würde das ganze so aussehen (Beispiel wieder aus Breagar genommen):

Code:
IF ~~ THEN BEGIN ACPIDFORGE
SAY ~Dann wollen wir mit der Arbeit beginnen.~
IF ~~ THEN DO ~SetGlobal(...)ClearAllActions()StartCutSceneMode()StartCutScene("ACCUT_8")~ EXIT
END

1. ClearAllActions()

Der Befehl führt dazu, dass alle Aktionen, die Figuren zugewiesen wurden, abgebrochen werden. Wenn jemand zu dem Zeitpunkt, an dem dieser Block triggert, gerade mitten im Laufen wäre, bleibt er an dieser Stelle stehen, wenn jemand angreift, wird der Angriff abgebrochen und so weiter und so fort.

2. StartCutSceneMode()

Dieser Befehl startet den Cutscenemodus [Ach? - Der Setzter], was bedeutet, dass alle Steuerungselemente wie die Benutzeroberfläche und der Mauszeiger verschwinden.

3. StartCutScene("ACCUT_8")

Hier wird die Cutscene gestartet. Der Name der Cutscene ist in diesem Falle ACCUT_8.BCS. Ja genau: Es ist tatsächlich nichts weiter als ein Script. Schauen wir uns mal ein Cutscene-Script an, dessen Ergebnis jeder kennen sollte:

Code:
IF
	True()
THEN
	RESPONSE #100
		CutSceneId("Gorion")
		Wait(2)
		ForceSpell("Ogre02",WIZARD_LIGHTNING_BOLT)
		Wait(2)
		DisplayStringHead(Myself,4439) // Lauf, mein Kind, lauf schnell weg!
		Wait(1)
		ForceSpell("Ogre",WIZARD_MAGIC_MISSILE)
		Wait(1)
		ForceSpellRES("BGCH1CUT","Tamoko") // Wort der Macht: Schlaf
		ForceSpell("Ogre",WIZARD_MAGIC_MISSILE)
		ForceSpell(Myself,WIZARD_SHIELD)
		Wait(1)
		ForceSpell([ENEMY],WIZARD_FLAME_ARROW)
		Wait(1)
		ForceSpell([ENEMY],WIZARD_MELF_ACID_ARROW)
		Wait(1)
		ForceSpell([ENEMY],WIZARD_MAGIC_MISSILE)
		ForceSpell([ENEMY],WIZARD_MAGIC_MISSILE)
		Wait(1)
		ForceSpell([ENEMY],WIZARD_MAGIC_MISSILE)
		ForceSpell([ENEMY],WIZARD_MAGIC_MISSILE)
		AttackNoSound("Sarevok")
END

IF
	True()
THEN
	RESPONSE #100
		CutSceneId(Player1)
		Wait(7)
		MoveToPoint([3171.1887])
		Wait(17)
		Kill("Gorion")
		Wait(4)
		JumpToPoint([3522.3712])
		ActionOverride("M05PCspy",DestroySelf())
		RevealAreaOnMap("AR6526")
		HideAreaOnMap("AR0015")
		SetGlobal("Chapter","GLOBAL",0)
		IncrementChapter("Chptxt-1")
		SetGlobal("Chapter","GLOBAL",2)
		AddJournalEntry(75206,INFO) // Kapitel 2: Isolation  Beim Erwachen wird Euch bewusst, dass alles nicht nur ein schrecklicher Traum gewesen ist. [...]
		DayNight(DAWN_END)
		Weather(NOWEATHER)
		MoveViewPoint([3522.3712],INSTANT)
		ActionOverride("Sarevok",DestroySelf())
		ActionOverride("Tamoko",DestroySelf())
		Kill("Ogre")
		Kill("Ogre02")
		SmallWait(1)
		EndCutSceneMode()
END

IF
	True()
THEN
	RESPONSE #100
		CutSceneId("Sarevok")
		Wait(10)
		AttackNoSound("Gorion")
END

IF
	True()
THEN
	RESPONSE #100
		CutSceneId("Tamoko")
		Wait(3)
		ForceSpell(Player1,FORCE_DAMAGE_1)
END

IF
	True()
THEN
	RESPONSE #100
		CutSceneId("Ogre")
		Wait(8)
		Attack("Gorion")
END

IF
	True()
THEN
	RESPONSE #100
		CutSceneId("Ogre02")
		Wait(5)
		Attack("Gorion")
END

Es ist natürlich die Cutscene von Gorions Tod. Im Grunde genommen eine Anzahl von Scriptblöcken wie man sie bereits kennt (kennen sollte). mit einem Unterschied: Die Zeile CutSceneId("NAME"), die diesen Scriptblock einer bestimmten Figur zuweist. CutSceneId(PLAYER1) bedeutet in diesem Fall, dass PLAYER1 diesen Scriptblock ausführt, als sei er in seinem eigenen Overridescript platziert (quasi ein Over-Override, wenn man so will). CutSceneId("Ogre02") bedeutet, dass die Creatur mit der DV "Ogre02" den Befehl ausführt und so weiter und so fort. Je nach dem, wie kompliziert die Cutscene ist, brauchen wir unter Umständen nur einen einzigen Scriptblock:

Code:
IF
	True()
THEN
	RESPONSE #100
		CutSceneId(Player1)
		Wait(1)
		CreateVisualEffect("spcrtwpn",[648.229])
		Wait(2)
		ActionOverride("ACBRECUT",Swing())
		Wait(5)
		CreateVisualEffect("spcrtwpn",[648.229])
		Wait(2)
		ActionOverride("ACBRECUT",Swing())
		Wait(7)
		ActionOverride("ACBRECUT",StartDialogNoSet(PLAYER1))
END

Hier steht Breagar am Amboss, hämmert auf das Eisen ein (mit dem Effekt, der auch dann kommt, wenn Cromwell schmiedet) und beendet die Cutscene mit einem Gespräch mit PLAYER1. Ich selbst habe mir angewöhnt, einfache Scripte immer über CutSceneId(Player1) laufen zu lassen... und das klappt auch bestens, so lange PLAYER1 in Blickweite ist.

Sonderfall:
Wie gestalte ich eine Cutscene, die ganz wo anders spielen soll?


Mein Tip: In einer Modifikation gar nicht. Es geht mir nichts mehr auf den Geist als die ewiglangen Cutscenes, die Region Of Terror mit sich bringt. Die sind zwar toll programmiert und wahnsinnig mystisch und beeindruckend and Stuff, dienen aber letzten Endes nur dem Selbstzweck (oder, wie ich es ab heute nenne: Modsturbation). Aber wir wollen ja nicht dem Asci zugucken, wie er neue Wörter erfindet, wir wollen was über Cutscenes lernen. Und da haben wir ein großes Problem: Dummerweise sieht man nur, was die Spielercharaktere sehen. Wenn ein Event direkt vor der Nase von Charname stattfindet, dann ist das ja auch nicht die Welle, wenn das Event allerdings weitweg passieren soll, dann brauchen wir jemanden, der für uns zuguckt. Natürlich KÖNNEN wir Charname auch dahin teleportieren, aber das sieht etwas blöde aus, wenn der Typ, über den gerade gelästert wird, direkt neben den Gesprächspartnern steht. Da bringt BGII uns ein freundliches Helferlein mit: Die Figur OBSERVE.CRE , die nicht nur unsichtbar und untötbar, sondern dankenswerterweise auf mit dem Effekt "Clear Fog Of War" verstehen ist. Mit anderen Worten: Das was diese Figur sieht, das sehen wir auch. Also einfach ein Create Creature("OBSERVE") in die Cutscene einbauen und alles ist bestens.

Wenn die Cutscene jetzt auch noch GANZ WO ANDERS stattfinden soll, müssen wir leidergottes die Charaktere da hin schaffen. Zum Beispiel so:

Code:
LeaveAreaLUA("AR1600","",[802.2310],14)
ActionOverride(Player2,LeaveAreaLUA("AR1600","",[833.2270],1))
ActionOverride(Player3,LeaveAreaLUA("AR1600","",[866.2240],1))
ActionOverride(Player4,LeaveAreaLUA("AR1600","",[844.2348],1))
ActionOverride(Player5,LeaveAreaLUA("AR1600","",[883.2313],1))
ActionOverride(Player6,LeaveAreaLUA("AR1600","",[916.2260],1))

Am besten schön weit weg, so dass die Kamera sie nicht einfängt. Und nicht vergessen, sie am Ende des Scriptes wieder zurück zu schicken!

Wie beende ich eine Cutscene?

Auf zwei verschiedene Arten und Weisen:

1.) Mit dem Befehl "EndCutSceneMode()", vergleiche oben das Script über Gorions Tod.

2.) Indem ich mit StartDialogNoSet() oder ähnlichem einen Dialog erzwinge, der den Spieler nötigt, weiterzuklicken. Nachdem der Dialog berendet ist, geht das Spiel ganz normal weiter. Will ich also eine längere Cutscene haben, in der ein Gespräch auftritt, an dem der Spieler aktiv teilnehmen soll, dann muss ich praktisch zwei Cutscenes machen, die eine wird mit dem Dialog beendet und die zweite wird durch den Dialog gestartet.

So, das sind erstmal die Grundlagen. Ich kann mir denken, dass ich an der einen oder anderen Stelle mehr Fragen aufgworfen als beantwortet habe... aber dafür ist der Thread ja da, newa? Also, wenn was unklar ist: Fragen!
 

Gerri

Senior Member
Registriert
21.11.2006
Beiträge
920
Modsturbation :up: Ich liege schon wieder schreiend und lachend am Boden.:D
Fast so gut wie der Zweihänder des Genozid +12
Ascalon Dein Wortwitz ist klasse!
Cutscenes: denk bitte an meine... ;)
 

Pareus

Member
Registriert
19.08.2009
Beiträge
48
Dieser Thread ist echt 'ne feine Sache - von mir auch ein :up:

Nicht nur, dass du's gut erklärst ohne dabei wie ein Prof rüberzukommen, du schaffst es auch gleichzeitig informativ UND unterhaltsam zu schreiben.

Wenn ich das also richtig verstehe, hätte dieser dämliche Effekt bei RoT, dass CHARNAME bei den Cutscenes immer mittendrin statt nur dabei ist, mit der OBSERVE.CRE vermieden werden können?

War das bei TDD nicht genauso, oder täusch ich mich da?

...im Übrigen sind Unmengen an Cutscenes auch etwas, dass ich an den großen Mods nicht mag - als Beispiel sei da nur die Riatavin-Main-Quest aus TDD genannt, wo circa 5 Minuten lang eine Cutscene der nächsten folgte... :rolleyes:
 

Ascalon

Senior Member
Registriert
08.04.2008
Beiträge
2.730
Wenn ich das also richtig verstehe, hätte dieser dämliche Effekt bei RoT, dass CHARNAME bei den Cutscenes immer mittendrin statt nur dabei ist, mit der OBSERVE.CRE vermieden werden können?

Das kann ich nicht genau sagen, da ich die Szenen nicht vor dem Auge habe. Möglich ist es, es kann allerdings auch sein, dass Charname nur deswegen zu sehen ist, weil die Bildschirmauflösung geändert wurde.

@ Gerri: Jetzt könntest du's ja auch quasi selbst machen. :D
 

Gerri

Senior Member
Registriert
21.11.2006
Beiträge
920
@Ascalon
Theoretisch hast Du Recht, dass ich es selbst machen könnte.
Über den groben Aufbau war ich mir im klaren, aber das mit der zusehenden Kreatur war neu für mich. Deswegn habe ich mich auch immer gewundert, warum Charname bei so vielen Cuts immer daneben steht.
Vielleicht sollte man da mal einen Fix basteln.
Was ist aber, wenn die Cut durch einen Trigger starten soll? Mit NI hab ich keine Erfahrung um die rauszubekommen.
 

Ascalon

Senior Member
Registriert
08.04.2008
Beiträge
2.730
Was für einen Trigger meinst du genau? Wenn du willst das bestimmte Punkte erfüllt sein sollen, dann sollte man klären, welche das sind. Die meisten Dinge lassen sich über die Globalen herausfinden - dazu müsstest du nur in die jeweiligen D-Dateien oder Skripte schauen. Und dann das ganze machen wie bei "How to ensure your banters always run when you want them to":

Code:
IF
...
Global("MyCutScene","GLOBAL",0) // Oder LOCALS, Oder Arealbezogen
THEN
RESPONSE #100
SetGlobal("MyCutScene","GLOBAL",1)
END

IF
...
Global("MyCutScene","GLOBAL",1)
THEN
RESPONSE #100
ClearAllActions()
StartCutSceneMode()
StartCutScene("MYCUT")
END

Das wird dann dem entsprechenden Skript angehängt. Wenn z.B. das auslösende Ereignis sein soll, dass Hull sein Schwert hat, dann käme das ganze in die HULL.BCS rein, also kommt in die TP2: EXTEND_TOP ~Hull.bcs~ ~MyMod/MyScript.baf~ Natürlich müsstest du dann im Dialog von Hull raussuchen, wie was verändert wird, wenn er sein Schwert hat.
 

Ascalon

Senior Member
Registriert
08.04.2008
Beiträge
2.730
Charaktererschaffung Teil 1

Wie erstelle ich einen verdammt guten NPC - Teil 1: Grundlagen

Tja Leute, holt euch mal ein Bier oder alkoholfreies Getränk nach Wahl, das könnte etwas länger werden. Denn wir verlassen den Codingzirkus mal für einen Moment und beschäftigen uns mit dem, was einen NSC-Mod ausmacht: Den NSC selbst. Was unterscheidet einen guten NSC von einem schlechten, wie kann ich ihn ins Spiel eingliedern und vor allem: Wie bringe ich die Fangemeinde dazu, den NSC zu lieben (denn im Grunde sind wir doch alles Attentionwhores...)

Dazu werde ich mal wieder Breagar, der sich in den letzten 14 Monaten wirklich zu einem kleinen Fanfave gemausert hat, heranziehen – was mich immer noch ein wenig erstaunt, aber noch mehr erfreut. Ich werde einige Stichpunkte ansprechen, bei denen die meisten sagen werden: „Is ja logisch!“ und bei diesen dann ins Detail gehen, auch zu einem Punkt, an dem massiv im Spiel gespoilert wird – Ich empfehle also allen, die aus irgendeinem Grunde BGI und II nicht kennen, das hier nicht zu lesen.

Kurz noch: Ich werde von „dem Charakter“ reden und „dem Helden“... natürlich sind damit auch die Damen gemeint.

1.Charaktere haben eine Mutti

Ein einfacher Satz, aber er enthält jede Menge Dramen. Egal ob Kämpfer, Magier oder Dieb, jeder Charakter der bekannten Rassen wurde geboren, hat eine Kindheit und eine Pubertät hinter sich (bis auf Saerileth, die steckt noch drin). Das ist normal, das ist gut so, das bedeutet, dass der Charakter nicht mit Morgenstern und Kettenhemd sich einfach mit einem *Plopp* materialisierte sondern das durchlief, was wir alle durchlaufen haben: In die Windeln kacken, Zahnweh haben, peinliche Momente mit 12 unter der Bettdecke, einem Jugend oder einem Mädchen hinterherschwärmen, in der Schule oder im Job mal einen Rüffel vom Lehrer oder Ausbilder bekommen. Wer stellt sich noch gerade einen Minsk mit Pampers vor?

Was will ich damit sagen: So wie der Charakter heute drauf ist wird nicht gestern entschieden, sondern ist ein Produkt aus Kindheit, Jugend und Erfahrungen im Erwachsenenalter.

Breagar z.B. hat seine Kindheit und Jugend im Clan Bardormar erlebt, ist selten vor die Tür gekommen, wird als direkter Nachfahre des Clansgründers etwas genauer unter die Lupe genommen als andere und ist voll mit dem Gedankengut konservativer Zwerge. Das bedeutet, dass er der Umwelt erstmal mit Misstrauen begegnet, besonders wenn sie spitze Ohren hat. Das bedeutet, dass er Werte, die von konservativen Zwergen hochgehalten werden, ebenfalls hochhält und das bedeutet, dass er eine Verpflichtung seines Erbes gegenüber spürt.

Wir sehen: Das, was den Charakter heute ausmacht, ist nicht vom Himmel gefallen, sondern wurde durch Erziehung, Erlebnisse, Ereignisse, Begegnungen und alles andere während seiner vorherigen Laufbahn aufgebaut. Und das wiederum bedeutet: Wenn wir einen komplexen Charakter erschaffen wollen, der in sich stimmig wirkt und der auf seine Umwelt reagiert, dann müssen wir seine Kindheit und Jugend kennen. Folgende 10 Fragen sollte man also beantworten können:

1.)Wo und auf welche Weise kam der Charakter zur Welt?
2.)Wer waren seine Eltern? Kennt er seine Eltern? In welchem Verhältnis steht er zu ihnen?
3.)Wie hat der Charakter seine Kindheit verbracht?
4.)Gab es einschneidende Erlebnisse in seiner Kindheit? Krieg, Tod eines Elternteils, die Begegnung mit einem großen Helden, eine Erfindung, die in seinem Beisein gemacht wurde...
5.)Wie war seine Kindheit und Jugend? Glücklich oder düster? Reich oder arm?
6.)Gab es einschneidende Erlebnisse in seiner Jugend?
7.)Hatte der Charakter eine ungestörte Adoleszenz? Konnte er alle Erlebnisse haben, die man an und für sich als heranwachsender hat? Alkohol, Frauen...
8.)Welchen Beruf hat der Charakter erlernt? Eine Frage, die bei Paladinen oder Klerikern schnell zu beantworten ist, bei Druiden allerdings nicht so einfach, wie Cernd zeigt, und bei Waldläufern und Kämpfern und Dieben ebenfalls nicht.
9.)Mag der Charakter seinen Beruf?
10.)Wie stellt sich der Charakter seine Zukunft vor?


Frage 10 wird uns gleich etwas weiter beschäftigen, zuvor aber noch ein paar Hinweise: Ein Charakter, der auf dem Schlachtfeld als Sohn eines Dämons und eines Halbogerelfen geboren wurde, der in seiner Kindheit erleben musste, wie seine Heimatsphäre von Unicron gefressen wurde, den Beruf des Leibgardisten von Graf Strahd von Zarovich lernte, dabei bettelarm durchs Leben stromerte, bis er in seiner Jugend von Elminster großgezogen wurde und jetzt nichts sehnlicher will, als Charname bei seiner Rache zu helfen... Der hat zwar alle diese Fragen beantwortet, aber so, dass sich eher 1000 neue Fragen aufgeworfen haben. Grundsätzlich sollten diese 10 Fragen soweit alles abdecken für jemanden, der sich wenigstens halbwegs mit der Materie auskennt.

Um wieder zu Breagar zu kommen: Breagar wurde als Sohn eines Bäckers und seiner Frau geboren, erlebte seine Kindheit in einem Clan, wohlbehütet, aber beobachtet, da er Nachfahre des Clansgründers ist. Durch die Clansgeschichte hegt er einen immensen Groll auf Duergar. Er lernte den Beruf des Bäckers, verliebte sich, wechselte zum Schmied (weil der Vater des Mädchens nur Schmiede in seiner Familie haben will), überwarf sich dadurch mit dem Vater. Seine Zukunft sieht er an der Seite seiner Geliebten als Schmied. Somit haben wir einen Charakter, der glaubwürdig und interessant, aber nicht übertrieben exotisch daherkommt.
 
Oben