forked from Lehona/Ikarus
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Ikarus_Doc.d
2834 lines (2176 loc) · 107 KB
/
Ikarus_Doc.d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//######################################################
//
// Dokumentation zum Skriptpaket "Ikarus"
// Autor : Sektenspinner
// Version : 1.1.4
//
//######################################################
/*
Inhalt
I. ) Abgrenzung
II. ) Voraussetzungen / Setup
1. Ikarus unter Gothic 2
2. Ikarus unter Gothic 1
3. Setup
III. ) Zu Grunde liegende Konzepte
1. Der Speicher, Adressen und Zeiger
2. Klassen
3. Nicht darstellbare primitive Datentypen
IV. ) Klassen
V. ) Funktionen
1. Elementarer Speicherzugriff
2. Parser Zeug
3. Sprünge
4. String Funktionen
5. Menü-Funktionen
6. Globale Instanzen initialisieren
7. Ini Zugriff
* 7.1. Tastenzuordnungen
8. Tastendrücke erkennen
9. Maschinencode ausführen
10. Enginefunktionen aufrufen
11. Externe Biblioteken
12. Verschiedenes
13. "Obskure" Funktionen
VI. ) Gefahren
VII. ) Beispiele
*/
//######################################
// I. Abgrenzung
//######################################
"Ikarus" ist eine Sammlung von Engine Klassen (bzw. ihrem Speicherbild)
und einigen Funktionen, die helfen mit diesen Klassen umzugehen. Ikarus
ist nützlich um jenseits der Grenzen von Daedalus aber innerhalb der
Grenzen der ZenGin zu arbeiten. Dadurch können einige sonst
unerreichbare Daten und Objekte ausgewertet und verändert werden,
darunter einige, die für Modder durchaus interessant sind. Ferner können
mit fortgesschrittenen Methoden auch Engine Funktionen aus den Skripten
heraus aufgerufen werden.
Ikarus eröffnet nur sehr begrenzte Möglichkeiten das Verhalten der
ZenGin selbst zu verändern.
//######################################
// II. Voraussetzungen / Setup
//######################################
Dieses Skriptpaket setzt ein Verständnis grundlegender
Programmierkonzepte und eine gute Portion Ausdauer und Forschungsgeist
voraus. Die meisten Klasseneigenschaften sind nicht dokumentiert, das
heißt oft muss man ausprobieren ob sie das tun, was man erwartet.
Das WoG-Editing Forum ist ein guter Ort um Erfahrungen mit den Klassen
und Eigenschaften auszutauschen, damit das Rad nicht ständig neu
erfunden werden muss.
//--------------------------------------
// 1.) Ikarus unter Gothic 2
//--------------------------------------
Dieses Skriptpaket ist nur auf einer Gothic 2 Reportversion lauffähig.
Auf anderen Gothic Versionen wird die Nutzung dieser Skripte zu
Abstürzen führen. Wer sich also dazu entscheidet, dieses Skriptpaket zu
verwenden, muss dafür sorgen, dass die Spieler der Mod ebenfalls die
Reportversion nutzen.
Neuere Reportversionen als 2.6.0.0 (zur Zeit bei Nico in Planung) werden
KEINE Probleme bereiten und werden voll mit diesem Paket kompatibel sein.
//--------------------------------------
// 2.) Ikarus unter Gothic 1
//--------------------------------------
Ikarus wurde ursprünglich nur für Gothic 2 erstellt. Mittlerweile ist
Ikarus aber relativ gut mit Gothic 1 in der Version 1.08k_mod (neuste
Playerkit-Version) lauffähig. Allerdings sind noch nicht alle Klassen an
Gothic 1 angepasst. Nicht angepasste Klassen tragen ein ".unverified" im
Namen. Zur Zeit (Stand September 2010) ist insbesondere zCMenuItem
fehlerhaft, weshalb auch diesbetreffende Ikarus Funktionen abstürzen
werden. oCAIHuman und zCCamera sind ebenfalls noch nicht angepasst.
Abgesehen davon sind mir keine Fehler bekannt.
//--------------------------------------
// 3.) Setup
//--------------------------------------
Ikarus besteht aus drei Teilen, Konstanten, Klassen und dem Ikarus-Kern,
die in genau dieser Reihenfolge geparst werden müssen. Der Ikarus-Kern
ist für Gothic 1 und 2 identisch und besteht aus der einzigen Datei
Ikarus.d. Für die Konstanten und Klassen gibt es dagegen jeweils eine
Gothic 1 und eine Gothic 2 Version von der natürlich jeweils genau die
richtige geparst werden muss. Ikarus nutzt einen C_NPC und muss daher
nach der C_NPC Klasse (nach der Datei classes.d) geparst werden. Andere
Abhängigkeiten gibt es nicht.
In der Konstantendatei gibt es ein paar Nutzervariablen mit denen unter
anderem die Debugausgabe von Ikarus geregelt wird.
Beispielsweise könnte man in einer Gothic 2 Installation die
Ikarus-Dateien in das Verzeichnis _intern packen und die Gothic.src so
verändern, dass sie folgendermaßen beginnt:
//******************
_INTERN\CONSTANTS.D
_INTERN\CLASSES.D
_INTERN\Ikarus_Const_G2.d
_INTERN\EngineClasses_G2\*.d
_INTERN\Ikarus.d
AI\AI_INTERN\AI_CONSTANTS.D
[...]
//******************
//######################################
// III. Zu Grunde liegende Konzepte
//######################################
Ich habe versucht den folgenden Text so zu schreiben, dass auch
programmiertechnisch Unbedarfte sich ein Bild davon machen können, worum
es hier geht. Das soll nicht heißen, dass programmiertechnisch
Unbedarfte das Paket nach lesen dieses Textes sofort effektiv nutzen
können. Aber ich wollte lieber etwas zu viel schreiben, als zu wenig.
Wer sich mit Programmieren auskennt sollte mindestens Punkt 1 und 2
problemlos überspringen können.
//--------------------------------------
// 1.) Der Speicher, Adressen und Zeiger
Der Speicher ist eine große Tabelle von Daten. Die Positionen in der
Tabelle werden durch Adressen benannt, also positive ganze Zahlen. An
jeder so identifizierten Speicherzelle steht ein Byte mit Daten (also
ein Wort, das aus 8 binären Ziffern (0 oder 1) besteht, zum Beispiel
00101010). Meistens erstreckt sich ein Datum (also eine Dateneinheit)
über mehrere zusammenhängende Speicherzellen, oft 4 Stück.
Wenn Gothic ausgeführt wird, liegt Gothic im Speicher herum. "Gothic"
ist im dem Fall sowohl das Programm selbst (der Maschinencode) als auch
die Daten auf denen Gothic arbeitet. Programm und Daten sind in
getrennten Bereichen (Segmenten), für die getrennte
Zugriffsberechtigungen gelten. Auf das Datensegment darf lesend und
schreibend zugegriffen werden und das ist auch das Segment mit dem sich
dieses Skriptpaket beschäftigt.
Während Gothic arbeitet, werden immer wieder neue Objekte angelegt und
nicht mehr benötigte Objekte zerstört, was es schwierig machen kann, ein
bestimmtes Objekt, sagen wir eine bestimmte Truhe zu finden, da man
nicht von vorneherein wissen kann, wo die Daten zu dieser Truhe im
Speicher aufbewahrt werden.
Ganz bestimmte Objekte kann man aber sehr leicht finden. Das sind
meistens sehr wichtige Objekte, die es nur einmal gibt (und irgendetwas
muss ja leicht zu finden sein, sonst könnte die Engine selbst ja gar
nicht arbeiten). Oft ist vereinbahrt, dass ein solches wichtiges Objekt
immer an einer ganz bestimmten Speicherstelle zu finden ist (man weiß
schon, wo man suchen muss, bevor man das Programm ausführt). In diesem
Fall, muss man nur auf die passende Speicherstelle zugreifen und hat die
Daten gefunden, die man will.
Wenn die Position des Objekts im Speicher nicht bekannt ist, gibt es
manchmal eine Stelle im Speicher, an der man die aktuelle Adresse des
Objekts nachschauen kann. Dann kann man das Objekt indirekt finden, in
dem man zunächst an einem wohlbekannten Ort nachschlägt, wo das Objekt
liegt und mit diesem Wissen anschließend darauf zugreift.
Vergleichbar ist das mit einem Buch: Angenommen wir suchen Kapitel 5.
Wenn wir wissen, dass Kapitel 5 auf Seite 42 anfängt, können wir das
Buch direkt richtig aufschlagen.
Im Allgemeinen wird es aber so sein, dass wir das nicht wissen (denn bei
verschiedenen Büchern sind die Kapitelanfänge im Allgemeinen auf
verschiedenen Seiten). Zum Glück gibt es die Vereinbahrung, dass das
Inhaltsverzeichnis immer am Anfang des Buches ist, das heißt wir können
das Inhaltsverzeichnis finden und dort nachschauen wo Kapitel 5 anfängt.
Mit diesem Wissen ist dann Kapitel 5 leicht zu finden.
Navigation im Speicher ist im Grunde genau dies: Von wenigen Objekten
ist bekannt, wo sie herumliegen. Sie dienen als eine Art
Inhaltsverzeichnis und beinhalten Verweise auf andere Objekte. Diese
Verweise nennt man auch Zeiger oder Pointer.
Beispiel:
An Speicherstelle 0xAB0884 (das ist hexadezimal für 11208836, also auch
nur eine Zahl) steht immer die Adresse das aktuellen oGame (das ist eine
Datensammlung, die wichtige Informationen zur aktuellen Spielsitzung
enthält). Wenn man diesem Verweis folgt, findet man also ein Objekt vom
Typ oCGame. Dieses Objekt beinhaltet verschiedene Eigenschaften, unter
anderem die Eigenschaft "world". Hier findet sich abermals eine Adresse,
die diesmal auf ein Objekt vom Typ oWorld zeigt. Hier findet sich
widerum ein Verweis auf einen zCSkyController_Outdoor, der hat unter
anderem eine Textur, die widerum Daten hat... So kann man sich nach und
nach vom großen Ganzen auf einen einzelnen Pixel am Himmel vorhangeln.
//--------------------------------------
// 2.) Klassen
Ich habe bereits von Objekten gesprochen, die Eigenschaften haben. Es
ist aber so, dass Speicher im Computer etwas unstrukturiertes,
"untypisiertes" ist. Man sieht einem Speicherbereich nicht an, ob es
sich bei seinem Inhalt zum Beispiel um eine Kommazahl, eine Zeichenkette
oder um Farbwerte handelt. Das heißt, selbst wenn man sich überlegt, was
man unter einem Baum, einer Truhe oder einem Npc versteht, muss man
genau festlegen in welcher Reihenfolge welche Daten abgelegt werden. Das
nennt man auch das Speicherlayout eines Objekts und wird in Daedalus als
Klasse notiert. Beispiel:
class cSandwich {
var cToast oben;
var cKäse käse;
var cToast unten;
};
Dies beschreibt eine Klasse, die drei Unterobjekte beinhaltet. Direkt am
Anfang der Klasse steht ein Objekt vom Typ cToast, dann kommt ein Objekt
vom Typ cKäse und dann noch ein Objekt vom Typ cToast. Leider kann man
das in Daedalus nicht so hinschreiben, sondern die Unterobjekte müssen
in primitive Typen heruntergebrochen werden. Wenn man verfolgt, was ein
cToast und cKäse ist, könnte man das Speicherbild von cSandwich
vielleicht so beschreiben:
class cSandwich {
//var cToast oben;
var int oben_bräunungsgrad;
var int oben_Gebuttert;
//var cKäse käse;
var string käse_name;
var int käse_fettgehalt;
//var cToast unten;
var int unten_bräunungsgrad;
var int unten_Gebuttert;
};
Wenn man ein konkretes Sandwich sw vorliegen hätte, könnte sw.käse_name
zum Beispiel "Edammer" sein und sw.unten_gebuttert könnte 1 sein, das
heißt der untere Toast wäre mit Butter bestrichen.
Mit dem Wissen, dass eine Ganzzahl (int) immer 4 Byte groß ist und ein
string immer 20 Byte, weiß man schon viel über die Klasse.
Angenommen, ein cSandwich stünde an Speicherposition 123452 (das heißt,
das Objekt beginnt dort), dann findet man an Position 123452 den Wert
"oben_bräunungsgrad", an Position 123456 den wert "oben_Gebuttert", an
Position 123460 die Zeichenkette "käse_name", an Position 123480 den
Wert "käse_fettgehalt" usw..
Auch wenn dieser Hintergrund nicht unbedingt notwendig ist, um dieses
Paket zu benutzen, halte ich es für nützlich dies zu verstehen.
//--------------------------------------
// 3.) Nicht darstellbare primitive Datentypen
Nicht alle primitiven Datentypen ("primitiv" heißt nicht weiter sinnvoll
zerteilbar) die die ZenGin kennt, sind in Daedalus auch verfügbar.
Deadalus kennt nur Ganzzahlen (int) und Zeichenketten (string). Das
heißt aber nicht, dass man mit anderen Datentypen nicht auch arbeiten
könnte, aber man muss darauf achten die Datentypen korrekt zu behandeln.
Ein bisschen ist das, als hätte man einen Chemieschrank und auf jeder
Flasche steht "Destilliertes Wasser", obwohl sich in bei weitem nicht
allen Flaschen genau das verbirgt. Die Chemikalien funktionieren
natürlich noch genauso gut, solange man weiß, was wirklich hinter den
Ettiketten steckt. Wenn man aber eine Säure behandelt, als wäre sie
Wasser, wird einen das Ettikett nicht eines besseren belehren und es
knallt (unter Umständen).
Das "Destillierte Wasser" ist in unserem Fall eine Ganzzahl, also ein
integer der Größe 32 bit. Alles was nicht gerade Zeichenkette ist, ist
in den Klassen dieses Pakets als solche Ganzzahl deklariert. Was sich
wirklich dahinter verbirgt, geht aus den Kommentaren hervor.
Einige wichtige Datentypen sind:
//##### int ######
Wenn es nicht nur als int deklariert ist, sondern auch im Kommentar
steht, dass es ein int ist, dann ist es ein int! Also eine gewöhnliche 4
Byte große Ganzzahl mit Vorzeichen.
//##### zREAL ####
Ein zREAL ist ein 4 Byte IEEE Float, oft mit "single" bezeichnet. Das
ist eine Gleitkommazahl mit der man in Daedalus normalerweise nicht
rechnen kann. Ich habe aber mal Funktionen geschrieben, die solche
Zahlen verarbeiten können:
http://forum.worldofplayers.de/forum/showthread.php?t=500080
Zum Beispiel bietet diese Skriptsammlung eine Funktion roundf, die einen
solchen zREAL in eine Ganzzahl umwandelt (rundet).
//##### zBOOL ####
(Sinnloserweise) auch 4 Byte groß. Ein zBOOL ist wie eine Ganzzahl, aber
mit der Vereinbahrung, dass nur zwischen "der Wert ist 0" und "der Wert
ist nicht 0" unterschieden wird. Ein zBOOL hat also die Bedeutung eines
Wahrheitswerts. 0 steht für falsch/unzutreffend, 1 steht für
wahr/zutreffend.
//##### zDWORD ####
Eine 4 Byte große Zahl ohne Vorzeichen. Das heißt die
Vergleichsoperationen <, <=, >, >= liefern mitunter falsche Ergebnisse,
da ein sehr großes zDWORD als negative Zahl interpretiert wird, obwohl
eine positive Zahl gemeint ist. Das sollte aber nur in Außnahmefällen
von Bedeutung sein, im Regelfall kann ein zDWORD behandelt werden wie
ein Integer, solange man nicht versucht negative Werte hineinzuschreiben.
//#### zCOLOR ####
Ein 4 Byte großes Speicherwort, bei dem jedes Byte für einen Farbkanal
steht: blau, grün, rot und alpha. Jeder Kanal ist also mit 8 bit
aufgelöst. Die Farbe "orange" würde als Hexadezimalzahl interpretiert so
aussehen:
0xFFFF7F00
blauer Kanal: 00, es ist kein Blauanteil in der Farbe.
grüner Kanal: 7F, also mittelstark vertreten
roter Kanal: FF, also so rot als möglich.
alpha Kanal: FF, also volle Opazität.
Die scheinbar umgekehrte Reihenfolge kommt daher, dass die
niederwertigen Bytes auch an den kleineren Adressen steht. Im Speicher
sieht die Farbe so aus:
Byte0: 00
Byte1: 7F
Byte2: FF
Byte3: FF
Siehe dazu auch "little Endian" z.B. in der Wikipedia.
//##### zVEC3 #####
Dies ist kein primitiver Datentyp, wird aber oft verwendet: Es ist ein
Trippel aus drei zREALs und stellt einen Vektor im dreidimensionalen
Raum dar. Deklariert habe ich solche zVEC3 in der Regel als integer
Arrays der Länge 3.
//## Zeigertypen ###
Ein Zeiger ist ein vier Byte großes Speicherwort und enthält die Adresse
eines anderen Objekts. In aller Regel weiß man von welchem Typ das
Objekt ist, auf das der Zeiger zeigt. Als Datentyp des Zeigers gibt man
den Datentyp des Objekts an, auf das gezeigt wird und versieht diesen
mit einem *. Nehmen wir mal an, wir treffen auf folgendes:
var int ptr; //cSandwich*
Dass ptr als Integer deklariert ist, soll uns nicht weiter stören, der
wahre Datentyp steht im Kommentar: Es ist (kein cSandwich aber) ein
Zeiger auf ein cSandwich (die Klasse aus dem vorherigen Abschnitt). Das
heißt, nimmt man den Wert von ptr her und interpretiert ihn als Adresse,
wird man an dieser Adresse ein cSandwich im Speicher vorfinden. An
folgenden Adressen ist also folgendes zu finden:
ptr + 0 : int oben_bräunungsgrad
ptr + 4 : int oben_Gebuttert
ptr + 8 : string käse_name
ptr + 28 : int käse_fettgehalt
ptr + 32 : int unten_bräunungsgrad
ptr + 36 : int unten_Gebuttert
(man beachte, dass ein Integer 4 Byte und ein String 20 Byte groß ist)
Es kann auch sein, dass ein Zeiger auf gar kein Objekt zeigt. Dann ist
sein Wert (als Zahl interpretiert) 0. Man spricht von einem Null-Zeiger.
Zum Beispiel könnte ein Zeiger auf die aktuelle Spielsitzung Null sein,
solange der Spieler noch im Hauptmenü ist und gar keine Sitzung
existiert.
Natürlich gibt es auch Zeiger auf Zeiger. Diese sind dann entsprechend
mit zusätzlichen Sternen gekennzeichnet.
//######################################
// IV. Die Klassen
//######################################
Die Klassen, die ich herausgesucht habe, sind bei weitem nicht alle
Klassen der Engine (es gibt viel mehr). Aber es sind die Klassen, von
denen ich glaube, dass sie für Modder am interessantesten sein können.
Ich habe versucht zu jeder Klasse unter dem Punkt "nützlich für" ein
Beispiel zu nennen, wofür man das Wissen über und den Zugriff auf diese
Klassen nutzen könnte. Vielleicht sind manche der genannten Sachen
schwieriger als ich vermute. Ich habe das meiste nämlich nicht
ausprobiert.
//########### oCGame #############
Hält Eigenschaften der Session und Referenzen auf zahlreiche globalen
Objekte, zum Beispiel die Welt oder den InfoManager. Einige
Einstellungen sind auch interessant.
Nützlich für:
-Marvin Modus an und ausschalten (game_testmode)
-Spiel pausieren (singleStep)
-Interface ausschalten wie mit toggle Desktop (game_drawall)
-uvm.
//##### oCInformationManager #####
Das Objekt, dass die Anzeige von Dialogen übernimmt. Kümmert sich zum
Beispiel darum, dass in den Views die passenden Dinge angezeigt werden.
Nützlich für:
-Diry Hacks mit isDone, z.B. um während eines Dialogs ein Dokument
anzuzeigen
-herausfinden, was im Dialog gerade passiert, z.B ob der Spieler gerade
eine Auswahl treffen muss.
-?
//######## oCInfoManager #########
Hält eine Liste aller Skript-Infos (oCInfo).
Nützlich für:
-?
//########## oCInfo ##############
In weiten Teilen schon in Daedalus bekannt. Zusätzlich ist eine Liste
von Choices erreichbar sowieso die Eigenschaft "told", die in Daedalus
über Npc_KnowsInfo gelesen werden kann.
Nützlich für:
-Choiceliste bearbeiten. Man könnte eine Funktion implementieren, die
nur selektiv eine Choice aus der Liste entfernt.
-Nach Choicewahl durch den Nutzer entscheiden, welche Choice gewählt
wurde, selbst wenn alle Choices die selbe Funktion benutzen (man kann
sich unbegrenzt viele Dialogoptionen zur Laufzeit überlegen).
-Einen nicht permanenten, schon erhaltenen Dialog wieder freischalten.
//######## oCInfoChoice ##########
Hält einen Beschreibungstext und den Symbolindex der Funktion, die
aufgerufen werden soll.
//#### oCMob und Unterklassen ####
Die verschiedenen Mobs sind aus dem Spacer bekannt. Besonders die
verschließbaren Mobs sind interessant.
Nützlich für:
-Das vom Spieler fokussierte Mob (oCNpc.focus_vob) bearbeiten, zum
Beispiel Truhen durch Zauber öffnen.
-Von Npc geöffnete Tür wieder verschließen.
//######## zCVob und oCNpc #######
Sind vollständig von Nico übernommen. Einen oCNpc vollständig zu kennen
hat dieses Skriptpaket erst möglich gemacht!
Nützlich für:
-Positionsdaten auslesen und verändern.
-Heldenfokus analysieren
-Zugriff auf einen ganzen Haufen anderer Dinge
//############ oCItem ############
Vobs mit bekannten Skripteigenschaften. Zusätzlich lässt die Eigenschaft
"instanz" auf die Skriptinstanz des Items schließen. "amount" (für
fallengelassenen Itemstapel) sollte nicht vergessen werden zu
berücksichtigen, wo nötig.
Nützlich für:
-Items für den Helden beim Tauchen aufheben
-Telekinese?
//######### zCCamera #############
Die Kamera eben. :-)
Aber Vorsicht: Die zCCamera ist kein zCVob. Sie hält aber eine Referenz
auf ein Hilfsvob (connectedVob).
Nützlich für:
-Positionsdaten ermitteln
-Screenblende einbauen (i.d.R. aber über VFX leichter möglich).
//##### zCMenu / zCMenuItem ######
Das Hauptmenü ist ein Menü. Der "Spiel starten" Button ist ein
Menüelement. Es gibt aber noch mehr Menüs (zum Beispiel das Statusmenü)
die ihrerseits Menüitems beinhalten.
Vorsicht: Zum Teil werden diese Objekte nur beim ersten mal erstellt und
dann im Speicher behalten (auch beim Laden eines anderen Savegames!) zum
Teil werden sie bei jeder Benutzung neu erzeugt.
Nützlich für:
-Charaktermenü überarbeiten (ziemlich fummlig)
-Speichern Menü-Item unter bestimmten Umständen deaktivieren
(Realisierung von Speicherpunkten / Speicherzonen).
//########## zCOption ############
Kapselt die Informationen der Gothic.ini und der [ModName].ini.
Funktionen um die Daten zu lesen und zu verändern stehen in Ikarus
bereit.
Nützlich für:
-Daten Sessionübergreifend festhalten (Lade / Speicherverhalten,
Schwierigkeitsgrad...)
-Einstellungen des Spielers lesen (zum Beispiel Sichtweite)
//########## zCParser ############
Der Parser ist nicht nur die Klasse, die beim Kompilieren der Skripte
meckert sondern auch eine virtuelle Maschine, die den kompilierten Code
ausführt. Der Parser hält zudem die Symboltabelle.
Nützlich für:
-Daedalus Code zur Laufzeit bearbeiten (Parser Stack).
//######## zCPar_Symbol ##########
Jedes logische Konstrukt (Variable, Instanz, Funktion, Klasse) wird über
ein Symbol identifiziert. Im Grunde sind alle diese Objekte der Reihe
nach durchnummeriert. Mit dieser Nummer erreicht man über die
Symboltabelle des Parsers diese Symbole. Ein Symbol hat einen Namen und
seinen Inhalt, der verschiedenen Typs sein kann.
Nützlich für:
-"call by reference"
-Funktionen anhand ihres Namens aufrufen
-Instance-Offsets bearbeiten.
//#### zCSkyController_Outdoor ####
Beinhaltet alles, was mit dem Himmel zu tun hat. Insbesondere die
Planeten (Sonne und Mond), eine Farbtabelle zur Beleuchtung (je nach
Tageszeit verschieden) (nicht aber die Lightmap selbst, die ist auf die
Polys verteilt) sowie aktuelle Regeneinstellungen.
Nützlich für:
-Regen starten
-Das Sonnenlicht umfärben
-Aussehen der Planeten (Sonne / Mond) ändern
//## zCTrigger und Unterklassen ###
zCTrigger, oCTriggerScript, oCTriggerChangeLevel und oCMover sind aus
dem Spacer bekannt. Jetzt kann man auch auf sie zugreifen.
Nützlich für:
-Ziel eines oCTriggerScripts verändern
-Triggerschleife bauen, die jeden Frame feuert, indem nach einem
Wld_SendTrigger die Eigenschaft _zCVob_nextOnTimer auf die aktuelle Zeit
gesetzt wird (siehe zTimer).
//####### zCWaynet und Co #########
Das zCWaynet beinhaltet eine Liste von allen zCWaypoints und allen
oCWays (das sind die roten Linien zwischen den Waypoints). zCWaypoints
sind keine Vobs, aber jeder zCWaypoint hat ein Hilfsvob in der Welt
(einen zCVobWaypoint), der auch wirklich ein Vob ist, aber sonst nichts
kann.
Jeder oCWay kennt die beiden beteiligten zCWaypoints zwischen denen er
verläuft und jeder zCWaypoints kennt alle oCWays, die von ihm ausgehen.
Nützlich für:
-?
//########### oWorld ##############
Hält neben dem Vobbaum, in dem alle Vobs enthalten sind auch Dinge wie
SkyController und Waynet. Außerdem gibt es neben dem sehr technischen
Bsp-Tree noch die activeVobList, die alle Vobs enthält die auf
irgendeine Art selbst aktiv sind. Das sind Npcs, Trigger usw. In jedem
Frame wird die activeVobList in die walkList kopiert. Die walkList wird
dann sequenziell durchlaufen.
Nützlich für:
-Objekte in der Welt suchen, zum Beispiel Npcs (voblist_npcs), items
(voblist_items), alle Vobs mit AI, z.B. auch Trigger (activeVobList)
-Mir ist es mit rumgehacke an activeVobList und walklist gelungen alles
außer den Helden einzufrieren und später zu reaktivieren
//######## zCZoneZFog #############
Fogzones sind Gebiete mit (möglicherweise farbigem) Nebel, der die
Sichtweite beeinflusst.
Nützlich für:
-evtl. mysteriöse Orte mit obskuren Farbwechseln
-möglicherweise automatische Sichtweiten Korrektur abhängig von der
Framerate
//####### VERSCHIEDENES ###########
//zCTree
Ein Knoten in einem Baum. Das kann ein innerer Knoten oder ein
Blattknoten sein.
Es gibt Zeiger auf das erste Kind, den linken und rechten
Geschwisterknoten sowie den Elternknoten.
Natürlich gibt es auch einen Zeiger auf die Daten.
Der Vobtree ist aus zCTrees aufgebaut.
//zCArray
Ein zCArray dient zur Verwaltung eines Felds von Daten. Das zCArray hat
einen Zeiger auf die Speicherzelle in der das Feld beginnt.
Die Daten sind in diesem Feld einfach aneinandergereit. Das zCArray
kennt zudem die Anzahl der Objekte im Feld (wichtig um nicht jenseits
des Feldendes zu lesen). Unter Umständen ist mehr Speicher alloziert als
das Feld gerade groß ist, daher kennt das zCArray neben der Größe des
Feldes auch die Größe des reservierten Speichers.
//zCArraySort
Wie ein Array, nur dass die Objekte immer auf bestimmte Art und Weise
geordnet sind (es sei denn, du machst diese Ordnung kaputt).
//zList
Sehr gewöhnungsbedürftige generische Liste, die mit der Eigenschaft
"next" des generischen Typs arbeitet. Kaum verwendet.
//zCList
Ein Listenelement. Beinhaltet den Zeiger auf genau ein Objekt (die
Daten) und einen Zeiger auf das nächste Listenelement.
//zCListSort
Wie ein Listenelement, nur dass die Gesamtheit der Listenelemente auf
bestimmte Art und Weise geordnet ist (es sei denn du machst diese
Ordnung kaputt).
//zCTimer
Man kann ablesen wie lange ein Frame braucht und es gibt Tuning
Parameter für minimale und maximale Framerate. Vorsicht. Hieran drehen,
kann dazu führen, dass das Spiel einfriert!
//oCWorldTimer
Enthält die Weltzeit (0 = 0:00, 6000000 = 24:00) und den aktuellen Tag.
//oCSpawnManager
Enthält ein Array aller im Moment gespawnter Npcs. Das Spawnen kann
ausgeschaltet werden (spawningEnabled). Die eigentlich interessanten
Werte, die insert und remove Reichweite sind statisch, ich habe die
Adressen dieser Werte über der Klasse angegeben.
//oCPortalRoom und oCPortalRoomManager
Der einzige oCPortalRoomManager der Welt kennt alle oCPortalRooms. Zudem
wird festgehalten in welchem Raum der Spieler gerade ist (curPlayerRoom)
und wo er vorher war (oldPlayerRoom).
Jeder oCPortalRoom hat einen Besitzer, eine Besitzergilde und einen
Namen.
//zCVobLight
Ein Licht. Es ist zu erwarten, dass der Bsp-Tree wissen muss, welche
Lichter welches Weltsegment betreffen. Daher ist nicht ganz klar, in
welchen Grenzen man zum Beispiel Lichter verschieben oder den
Leuchtradius vergrößern kann. Die Farbe des Lichtes lässt sich aber zum
Beispiel problemlos verändern.
//oCMag_Book
Wird genutzt um den Kreis über dem Spieler bei der Zauberauswahl
anzuzeigen. Außerdem enthält diese Klasse Zuordnungen von Zaubern <->
Items <-> Tasten.
//zString
Ein String. Da diese Klasse ein Daedalus-Primitiv ist, ist es in aller
Regel nicht nötig, auf die einzelnen Eigenschaften zuzugreifen. Ich habe
diese Eigenschaften allerdings gebraucht um Speicherallokation zu
implementieren.
//######################################
// V. Die Funktionen
//######################################
Ikarusfunktionen beginnen mit dem Präfix "MEM_". Das soll eine Kurzform
von "Memory" sein und neben der Zugehörigkeit zu Ikarus auch andeuten,
dass Eingriffe in den Speicher von Gothic stattfinden (und evtl.
entsprechende Vorsicht geboten ist).
Funktionen mit dem Präfix "MEMINT_" sind interne ("private") Funktionen
von Ikarus die nach außen hin keine sinnvolle Anwendungen haben oder
zumindest undokumentiert sind. Es ist nicht garantiert, dass solche
Funktionen in späteren Ikarus Versionen in dieser Form enthalten sein
werden.
Eine Ausnahme bilden Stringbearbeitungsfunktionen, die ein eigenes
Präfix "STR_" besitzen.
//######################################
// 1.) Elementarer Speicherzugriff
Lesen und schreiben von strings und integern ist mit folgenden
Funktionen möglich:
func int MEM_ReadInt (var int address)
func void MEM_WriteInt (var int address, var int val)
func string MEM_ReadString (var int address)
func void MEM_WriteString (var int address, var string val)
Wenn address <= 0 ist, wird ein Fehler ausgegeben. Andernfalls wird
versucht an dieser Adresse zu lesen bzw. zu schreiben.
Liegt die Adresse in einem ungültigen Bereich, zum Beispiel in einem
Codesegment, gibt es eine Zugriffsverletzung (Gothic stürzt ab).
Bei Stringoperationen ist es zudem nötig, dass an der angegebene Stelle
bereits ein gültiger zString steht. Wenn dort Unsinn steht, kann lesen
und schreiben gleichmaßen Gothic zum Absturz bringen.
Ein Beispiel für die Benutzung von MEM_WriteInt ist zum Beispiel
folgende Ikarus Funktion, mit der Debugmeldungen an und ausgeschaltet
werden können:
//******************
func void MEM_SetShowDebug (var int on) {
MEM_WriteInt (showDebugAddress, on);
};
//******************
Zudem sind zwei Bequemlichkeitsfunktionen implementiert:
func int MEM_ReadIntArray (var int arrayAddress, var int offset)
func int MEM_WriteIntArray (var int arrayAddress, var int offset, var
int value)
Diese Funktionen lesen / schreiben den Wert an Stelle arrayAddress + 4 *
offset. Beispielsweise könnte man folgendermaßen den i-ten Eintrag in
der activeVoblist der Welt lesen:
//----------------------
func int GetActiveVob (var int i) {
MEM_InitGlobalInst(); //MEM_World muss initialisiert sein
if (i >= MEM_World.activeVobList_numInArray)
|| (i < 0) {
//jenseits der Array Grenze
MEM_Error ("GetActiveVob: Lesen jenseits der Array Grenze!");
return 0;
};
//Den i-ten Eintrag aus der activeVobList holen:
return MEM_ReadIntArray (MEM_World.activeVobList_array, i);
};
//----------------------
Um auf einzelne Bytes zuzugreifen gibt es außerdem noch:
func int MEM_ReadByte (var int adr)
func void MEM_WriteByte (var int adr, var int val)
Hierbei wird nur das Byte an Adresse adr verändert, nicht etwa ein
ganzes vier Byte Wort. Das heißt, die drei Folgebytes bleiben unberührt.
Falls in MEM_WriteByte nicht 0 <= val < 256 gilt wird eine Warnung
ausgegeben, und val entsprechend zugeschnitten. Insbesondere sollten
keine negativen Zahlen übergeben werden.
//######################################
// 2.) Parser Zeug
Mit der Adresse eines Objekts allein kann man nur sehr unbequem auf die
Objekteigenschaften zugreifen.
Besser ist es, wenn eine Instanz auf das Objekt zeigt, und dann mit
"instanz.eigenschaft" auf eine beliebige Eigenschaft zugegriffen werden
kann.
Dazu gibt es folgende Funktionen:
func instance MEM_PtrToInst (var int ptr)
func void MEM_AssignInst (var int inst, var int ptr)
Im Prinzip sind beide für den selben Zweck geschaffen.
MEM_PtrToInst nimmt einen Zeiger entgegen und gibt eine Instanz zurück,
die in einer Zuweisung genutzt werden kann.
MEM_AssignInst nimmt eine Instanz (eigentlich den Symbolindex) und eine
Adresse entgegen und sorgt dafür, dass die Instanz auf ptr zeigt.
Als Merkregel: Folgende Formulierungen ist äquivalent:
1.) inst = MEM_PtrToInst (ptr);
2.) MEM_AssignInst (inst, ptr);
Dabei kann inst von beliebigem Objekttyp sein. ptr ist ein Adresse (als
integer).
Beispiel zur Benutzung:
//----------------------
func void somefunc() {
//Hole Helden
var oCNpc her;
her = Hlp_GetNpc (PC_HERO);
//Hat der Held ein Vob im Fokus?
if (!her.focus_vob) { return; };
//Lasse meinFocusVob auf her.focus_vob zeigen
var zCVob meinFocusVob;
meinFocusVob = MEM_PtrToInst (her.focus_vob);
//Nutze meinFocusVob, z.B. um Vobnamen auszugeben
Print (meinFocusVob._zCObject_objectName);
};
//----------------------
Die umgekehrte Funktion zu MEM_PtrToInst ist MEM_InstToPtr. Diese
Funktion gibt die Adresse des Objekts zurück, auf das die übergebene
Instanz zeigt.
func int MEM_InstToPtr (var instance inst)
Zum Beispiel liefert MEM_InstToPtr (hero) die Adresse des Helden im
Speicher.
Eine Funktion um eine Instanz auf das selbe Ziel wie eine andere Instanz
zeigen zu lassen ist folgende:
func instance MEM_CpyInst (var int inst)
Folgender Code ist äquivalent:
1.) selfCopy = MEM_CpyInst (self);
2.) selfCopy = MEM_PtrToInst (MEM_InstToPtr (self));
Anmerkung: Es gibt ein alias zu MEM_InstToPtr unter dem Namen
MEM_InstGetOffset.
Anmerkung: Instanzen eines Typs und "Variablen" eines Typs sind
gleichwertig. Im obigen Beispiel wäre es möglich gewesen "meinFocusVob"
außerhalb der Funktion als "instance meinFocusVob (zCVob);" zu
deklarieren.
Anmerkung: MEM_AssignInst und MEM_PtrToInst geben eine Warnung aus,
falls ptr == 0, weil eine Zuweisung eines Nullzeigers in vielen Fällen
nicht absichtlich passieren wird. Um ganz bewusst 0 zuzuweisen gibt es
MEM_AssignInstNull bzw. MEM_NullToInst. Die Benutzung dieser Funktionen
ist analog, der ptr Parameter entfällt allerdings.
Anmerkung: MEM_AssignInst kann im Gegensatz zu MEM_PtrToInst genutzt
werden um Instanzen in anderen Parsern zu verändern. In aller Regel ist
dies aber nicht nötig.
//******************
Nicht immer weiß man zur Kompilierzeit, wann man welche Funktion
aufrufen will. Wenn man zum Beispiel die Condition-Funktion eines Mobs
aufrufen will, das der Spieler im Fokus hat, ist man zur Kompilierzeit
ratlos, weil man nicht ahnen kann, welches Mob sich der Spieler
aussuchen wird. Ikarus stellt eine Möglichkeit zur Verfügung mit deren
Hilfe Funktionen anhand ihres Namens oder ihres Symbolindexes aufgerufen
werden können. Im Beispiel des Mobs, kann der Name der
Condition-Funktion einfach im Mob nachgeschaut werden.
Die Funktionen sind leicht zu benutzen und leicht zu erklären:
func void MEM_CallByString (var string fnc)
func void MEM_CallByID (var int ID)
MEM_CallByString ruft die Funktion mit Namen fnc auf, hierbei muss der
Name GROSSGESCHRIEBEN sein. MEM_CallByID ruft die Funktion mit
Symbolindex ID auf. An den Symbolindex einer Funktion kommt man mit zum
Beispiel mit MEM_FindParserSymbol oder MEM_GetFuncID (siehe unten).
MEM_CallByID ist schneller als MEM_CallByString und sollte bevorzugt
werden, wenn die selbe Funktion sehr oft aufgerufen werden muss.
Falls die aufzurufende Funktion Parameter hat, müssen diese zuvor auf
den Datenstack gelegt werden. Das geht mit den Funktionen:
func void MEM_PushIntParam (var int param)
func void MEM_PushStringParam (var string strParam)
func void MEM_PushInstParam (var int instance)
Die Parameter müssen in der richtigen Reihenfolge gepusht werden, von
links nach rechts.
Hat eine Funktion einen Rückgabewert sollte dieser nach Aufruf vom
Datenstack heruntergeholt werden, sonst können unter ungünstigen
Umständen Stacküberläufe entstehen (abgesehen davon will man den
Rückgabewert vielleicht einfach haben, weil er wichtige Informationen
enthält).
Dies geht mit den Funktionen:
func int MEM_PopIntResult()
func string MEM_PopStringResult()
func instance MEM_PopInstResult()
Siehe dazu Beispiel 5.
Anmerkung: Die genannten Funktionen funktionieren ohne Einschränkung
auch für Externals.
//******************
Ergänzend zum letzten Abschnitt:
func int MEM_GetFuncID(var func function)
Gibt den Symbolindex einer Funktion zurück, der zum Beispiel in
MEM_CallByID verwendet werden kann. Eine Funktion muss (bzgl der Parsing
Reihenfolge) noch nicht deklariert sein um an MEM_GetFuncID übergeben
werden zu können.
func void MEM_Call(var func function)
Äquivalent zu MEM_CallByID(MEM_GetFuncID(function)). Dies ermöglicht
zweierlei interessante Dinge: Zum einen ist es möglich dass sich zwei
Funktionen gegenseitig aufrufen (was sonst problematisch ist, weil
normalerweise eine Funktion deklariert sein muss, bevor sie aufgerufen
werden kann), zum anderen ist es möglich var func parameter sinnvoll zu
verwenden und somit modulare Algorithmen zu gestalten. Hier ein Beispiel:
//----------------------
func void DoTwice(var func f) {
/* Wir könnten direkt MEM_Call(f) verwenden.
* zu Demonstrationszwecken hier aber nochmal
* eine Zuweisung von Funktionen: */
var func g; g = f; //(*)
MEM_Call(g);
MEM_Call(g);
};
func void bar() {
DoTwice(foo); //gibt zwei mal "Hello" aus.
};
func void foo() {
Print("Hello");
};
//----------------------
Beachte:
- Klassenvariablen machen erhebliche Probleme. MEM_Call erlaubt es
nicht, beispielsweise die on_equip Funktion eines Items aufzurufen. Nur
gewöhnliche "var func" außerhalb von Klassen funktionieren.
- Gothic legt ein recht unsinniges Verhalten bei der Zuweisung von
Funktionen an den Tag. An Stelle (*) des obigen Beispiels wird nicht
etwa der Inhalt von f in g übertragen. Stattdessen wird der Symbolindex
von f in g geschrieben, das heißt, g verweist nun sozusagen auf f. Mit
diesem Verhalten kommt MEM_Call klar, MEM_Call wird folgende "Kette" von
Referenzen vorfinden und zurückverfolgen: MEM_Call.fnc -> DoTwice.g ->
DoTwice.f -> foo und wird schließlich foo aufrufen. Würden wir an Stelle
(*) allerdings ein "f = g;" einfügen wäre diese Kette zerstört und f und
g würden zyklisch aufeinander verweisen. MEM_Call würde in einer
Endlosschleife gefangen sein. Ich habe das hier an Beispielen diskutiert:
http://forum.worldofplayers.de/forum/threads/
969446-Skriptpaket-Ikarus-3/page11?p=17243175&viewfull=0#post17243175
//******************
func int MEM_FindParserSymbol (var string inst)
Kleines Nebenprodukt: Liefert den Index des Parsersymbols mit Namen inst
zurück, falls ein solches Symbol existiert. Falls keines existiert wird
eine Warnung ausgegeben und -1 zurückgegeben. Einen Schritt weiter geht
die Funktion:
func int MEM_GetParserSymbol (var string inst)
Sie sucht ebenfalls das Parsersymbol mit Namen inst, gibt aber nicht den
Index zurück sondern direkt einen Zeiger auf das passende zCPar_Symbol.
Existiert kein solches Symbol wird eine Warnung ausgegeben und 0
zurückgegeben.
So kann man Variablen anhand ihres Namens finden und bearbeiten.
Beispiel:
//----------------------
func void SetVarTo (var string variableName, var int value) {
var int symPtr;
symPtr = MEM_GetParserSymbol (variableName);
if (symPtr) { //!= 0
var zCPar_Symbol sym;
sym = MEM_PtrToInst (symPtr);
if ((sym.bitfield & zCPar_Symbol_bitfield_type)
== zPAR_TYPE_INT) {
sym.content = value;
} else {
MEM_Error ("SetVarTo: Die Variable ist kein Integer!");
};
} else {
MEM_Error ("SetVarTo: Das Symbol existiert nicht!");
};
};
func void foo() {
var int myVar;
SetVarTo ("FOO.MYVAR", 42); //äquivalent zu myVar = 42;
};
//----------------------
//######################################
// 3.) Sprünge
Rücksprünge sind sehr elegant möglich. Mithilfe zweier einfacher Zeilen
kann die aktuelle Position im Parserstack (darunter versteht man einen
maschinennahen Code der beim Kompilieren der Skripte erzeugt wird)
abgefragt und gesetzt werden. Wird die Position im Parserstack
verändert, so wird die Ausführung an dieser neuen Stelle fortgesetzt.
Beispiel: Folgender Code gibt die Zahlen von 0 bis 42 aus:
//----------------------
func void foo() {
/* Initialisierung */