Portal-Zone Gothic-Zone Gothic II-Zone Gothic 3-Zone Gothic 4-Zone Modifikationen-Zone Download-Zone Foren-Zone RPG-Zone Almanach-Zone Spirit of Gothic

 

Page 1 of 22 12345812 ... Last »
Results 1 to 20 of 424
  1. Visit Homepage View Forum Posts #1
    Exodus Sektenspinner's Avatar
    Join Date
    Jul 2004
    Location
    Karlsruhe
    Posts
    7,827
     
    Sektenspinner is offline

    Post [Scriptpaket] Zugriff auf ZenGine Objekte

    DIESER THREAD IST ALT! Hier ist Thread #2

    Einleitung

    Die Erkenntnisse bzgl. des Auslesens beliebiger Speicherstellen schrien geradezu danach, dass darauf aufbauend ein Skriptpaket entsteht, dass den Zugriff auf verschiedene Engineobjekte ermöglicht.

    Dieses Paket macht viele für Modder interessante Engineklassen in Daedalus verfügbar und bietet grundlegende Funktionalität zum Arbeiten mit Engineobjekten.
    Desweiteren sind nebem diesem allgemeinen Rahmenwerk auch einige sehr spezielle und unmittelbar nützliche Funktionen Teil dieses Pakets.

    Ich möchte hier zunächst eine kurze, unvollständige Übersicht darüber geben, was mit dem Paket möglich ist. Für eine umfassendere Einschätzung möchte ich auf die Doku verweisen.
    • Grundfunktionalität
      • Lesen und schreiben von Integern und Strings an beliebigen Speicherstellen.
      • aus Speicheradresse einen Objektzeiger gewinnen
      • aus einem Objektzeiger seine Speicheradresse gewinnen
    • Unmittelbar nützlich (nur Beispiele!):
      • Marvinmodus an und ausschalten
      • Spiel pausieren
      • Regen kontrollieren
      • Truhen oder Türen auf oder abschließen
      • Menüelemente bearbeiten (zum Beispiel "Speichern"-Menüpunkt deaktivieren)
      • SpawnManager Konstanten bearbeiten
    • Informationsbeschaffung:
      • Das vom Spieler fokussierte Objekt finden
      • Vobs anhand ihres Namens finden
      • Position der Kamera ermitteln
      • Waynet analysieren
      • herausfinden ob eine Taste gedrückt ist
      • Kommandozeilenparameter auslesen
    • Vollständig neue Möglichkeiten:
      • Gothic.ini lesen und schreiben, .ini Datei der Mod lesen
      • Daedaluscode zur Laufzeit bearbeiten
      • Speicher allozieren und freigeben (flüchtig bzgl. Laden und Speichern!)
    • Sonstiges / "Nice to have":
      • Choiceliste einer oCInfo direkt bearbeiten
      • Suchen von Parsersymbolen anhand ihres Namens
      • Funktionen anhand ihres Namens oder Symbolindex aufrufen
      • Im Code Labels initialisieren und zu ihnen springen (für elegante Schleifen)
      • Stringfunktionen: Zugriff auf einzelne Zeichen, Länge, Vergleich von Strings, String -> Integer Konvertierung


    Nicht für jedes der oben genannte Dinge, gibt es eine für sich stehende vorgefertigte Funktion. Was genau und mit welchen Parametern zur Verfügung steht, ist der Doku zu entnehmen. Grundsätzlich gilt: Dieses Paket ist eher als Türöffner zu verstehen, der das nötige Wissen über die Objekte und nur grundlegende Funktionen zur Verfügung stellt. Spezielle Anwendungen müssen selbst daraus abgeleitet werden.
    Ich biete zum Beispiel keine Funktionen an, die direkt den Marvin Modus abschaltet. Aber durch dieses Paket ist das Abschalten mit nur zwei Zeilen Code möglich, wenn man weiß wie es geht (in MEM_Game die Eigenschaft game_testmode überschreiben).

    Es sind weit mehr als die genannten Dinge möglich. Wenn man sich etwas einarbeitet, kann man mit diesem Paket eine große Menge an neuen Features implementieren. Was dieses Skriptpaket aber ausdrücklich nicht kann, ist Enginefunktionen aufzurufen, zu verändern oder gar zu ersetzen. Dafür gibt es G2Ext. Als Faustregel: Alle Features, die nur darauf angewiesen sind, Lese- und/oder Schreibzugriff auf die richtigen Daten zu haben, sind umsetzbar. Features, die ein vollständig neues Verhalten der Engine benötigen, sind dagegen mit großer Wahrscheinlichkeit nicht umsetzbar.
    Es ist aber nicht immer offensichtlich, ob ein konkretes Vorhaben mit Ikarus umgesetzt werden kann oder nicht. Im Zweifelsfall: Fragen! Einschätzungen, ob bestimmte, hier nicht genante Features, möglich sind und wenn ja, welche Klassen und Klasseneigenschaften nützlich sein könnten, kann ich auf Anfrage geben.
    Ansonsten hilft das Stöbern in den Klasseneingeschaften vielleicht weiter, um eine Idee davon zu bekommen, wo man rankommt und wo man vermutlich nicht so einfach rankommt.

    Damit die Scripte laufen, muss eine Gothic 2 Reportversion zur Verfügung stehen, auch bei den Spielern! Mit anderen Versionen ist dieses Paket inkompatibel, die Offsets stimmen nicht und Klassen sind möglicherweise verschieden.

    Doku:

    Hier eine kurze Übersicht, was das Paket beinhaltet. Wer weiß, was Klassen und Zeiger sind, den wird Abschnitt III langweilen, indem ich ein paar Grundbegriffe kläre. Abschnitt IV verliert ein paar Worte zu allen Klassen, die ich herausgeschrieben habe, Abschnitt V eine Übersicht über die Funktionen, die ich zur Verfügung stelle.

    Man beachte insbesondere auch Abschnitt VII, die Beispiele helfen vielleicht zu verstehen, worum es hier geht.

    Code:
    //######################################################
    //
    //  Skriptpaket "Ikarus"
    //      Author: Sektenspinner
    //
    //######################################################
    
    /*
        Inhalt
            I.  ) Abgrenzung
            II. ) Voraussetzungen
            III.) Zu Grunde liegende Konzepte
            IV. ) Klassen
            V.  ) 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 zEngine zu arbeiten. Dadurch können einige sonst 
    unerreichbaren Daten und Objekte ausgewertet und verändert werden, 
    darunter einige, die für Modder durchaus interessant sind.
    Ikarus eröffnet KEINE Möglichkeiten die zEngine selbst zu verändern. Wer 
    ein anderes Verhalten implementieren möchte, das nicht mit bestehenden 
    Strukturen auskommt, ist hier falsch und mit G2Ext besser beraten.
    
    //######################################
    // II. Technische Voraussetzungen
    //######################################
    
    Dieses Scriptpaket ist nur auf einer Gothic 2 Reportversion lauffähig. 
    Auf anderen Gothic Versionen wird die Nutzung dieser Scripte zu 
    Abstürzen führen. Wer sich also dazu entscheidet, dieses Scriptpaket 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.
    
    Dieses Scriptpaket 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. Im 
    Forum kann ich versuchen Fragen zu bestimmten Eigenschaften zu 
    beantworten und kann ebenfalls versuchen zu gegebenen Problemstellungen 
    passende Klassen und Eigenschaften zu benennen, die das gewünschte 
    leisten.
    
    Unterstützung von Gothic 1: Auch wenn andere Gothic Versionen und damit 
    auch Gothic 1 nicht offziell unterstützt werden ist Ikarus relativ gut 
    mit Gothic 1 lauffähig, wenn die Konstanten (größtenteils 
    Speicheroffsets) in Ikarus_Const.d angepasst werden. Da sich in Gothic 1 
    aber auch einige Klassen unterscheiden und bislang (August 2010) keine 
    an Gothic 1 angepassten Klassen verfügbar sind, kann es sein, dass 
    bestimmte Klassen nicht funktionieren.
    
    //######################################
    // 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). 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 Assemblercode) 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 Addresse 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 Allgemein 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) steht 
    immer die Addresse das aktuellen oGame (das ist eine Datensammlung, die 
    wichtige Information zur aktuellen Spielsitzung enthält). Wenn man 
    diesem Verweis folgt, findet man also ein Objekt vom Typ oGame. 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_braeunungsgrad;
            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_braeunungsgrad;
            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 
    "unten_braeunungsgrad", an Position 123456 den wert "unten_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 zEngine 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 die altbekannte 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 Scriptsammlung 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.
    
    //##### 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.
    
    //#### zCOLOUR ####
    
    Ein 4 Byte großes Speicherwort, bei dem jedes Byte für einen Farbkanal 
    steht: rot, grün, blau 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. 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_braeunungsgrad
    ptr + 4     : int oben_Gebuttert
    ptr + 8     : string käse_name
    ptr + 28    : int käse_fettgehalt
    ptr + 32    : int unten_braeunungsgrad
    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.
    
    //######## oCInfoManager #########
    
    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 Script-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.
    
    //######## 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 Scriptpaket erst möglich gemacht!
    
    Nützlich für:
    -Positionsdaten auslesen und verändern.
    -Zugriff auf einen ganzen Haufen anderer Dinge
    
    //############ oCItem ############
    
    Vobs mit bekannten Scripteigenschaften. Zusätzlich lässt die Eigenschaft 
    "instanz" auf die Scriptinstanz des Items schließen. "amount" (für 
    fallengelassenen Itemstapel) sollte nicht vergessen werden zu 
    berücksichtigen, wo nötig.
    
    Nützlich für:
    -Heldenfokus analysieren
    -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 
    (Speicherpunkte / 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 Scripte 
    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"
    -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 Vobtree 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_array)
    -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 Array hat einen Zeiger auf einen Speicherbereich in dem die Daten 
    stehen.
    Die Daten sind einfach aneinandergereit. Das Array kennt die Anzahl der 
    Objekte in dem Speicherbereich. Unter Umständen ist mehr Speicher 
    alloziert als nötig.
    
    //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 aber 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 dafür sorgen, dass das Spiel einfriert!
    
    //oCWorldTimer
    Enthält die Weltzeit (0 = 0:00, 6000000 ~=~ 23:59) 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. Ich habe nicht getestet, inwiefern man Lichter wirklich 
    bearbeiten kann. Es könnte sein, dass der Bsp-Tree wissen muss, welche 
    Lichter welches Weltsegment betreffen (im Spacer muss man ja auch am 
    Licht wackeln, damit es gerendert wird).
    Vielleicht werden Vergrößerungen des Lichtradius erst nach neuem Laden 
    wirksam. Licht ausschalten sollte aber auch so gehen. Kurz: Selbst 
    ausprobieren.
    
    //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
    //######################################
    
    //######################################
    // 1.) Low-Level 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 nicht im Datensegment, 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.
    
    Zudem sind zwei reine 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.
    
    //######################################
    // 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 Funktion:
    
    func void MEM_AssignInst (var int inst, var int ptr)
    
    Sie nimmt eine Instanz (eigentlich den Symbolindex) und eine Adresse 
    entgegen und sorgt dafür, dass die Instanz auf ptr zeigt.
    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;
        MEM_AssignInst (meinFocusVob, her.focus_vob);
        
        //Nutze meinFocusVob, z.B. um Vobnamen auszugeben
        Print (meinFocusVob._zCObject_objectName);
    };
    
    //******************
    
    Die umgekehrte Funktion zu MEM_AssignInst ist MEM_InstGetOffset. Sie 
    liest den Offset aus einer Instanz aus.
    
    func int MEM_InstGetOffset (var int inst)
    
    Zum Beispiel liefert MEM_InstGetOffset (hero) die Adresse des Helden im 
    Speicher. Diese Funktion wird seltener gebraucht als MEM_AssignInst.
    
    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 gibt eine Warnung aus, falls ptr == 0, weil 
    eine Zuweisung eines Nullzeigers in vielen Fällen nicht absichtlich 
    passieren wird. Um ganz bewusst 0 zu zuweisen gibt es MEM_AssignInstNull.
    
    //******************
    
    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 Symbolindex 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 werden, wenn es sich um ein Konstante handelt. 
    MEM_CallByID ruft die Funktion mit Symbolindex ID. An den Symbolindex 
    einer Funktion kommt man mit zum Beispiel mit MEM_FindParserSymbol. 
    MEM_CallByID ist schneller als MEM_CallByString und sollte bevorzugt 
    werden, wenn die selbe Funktion sehr oft aufgerufen werden muss.
    
    Für Funktionen ohne Parameter und Rückgabewerte ist dies das ganze 
    Geheimnis.
    
    Wenn 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)
    
    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 will man den Rückgabewert 
    vielleicht einfach haben).
    Dies geht mit den Funktionen:
    
    func int MEM_PopIntResult()
    func string MEM_PopStringResult()
    
    Siehe auch Beispiel 5.
    
    //******************
    
    func int MEM_FindParserSymbol (var string inst)
    
    Kleines Nebenprodukt: Liefert das zCPar_Symbol mit Namen inst zurück, 
    falls ein solches Symbol existiert. So kann man Variablen anhand ihres 
    Namens finden und bearbeiten. Es ließe sich eine Art CallByReference 
    bauen.
    
    //######################################
    // 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 
    assemblerartigen Code der beim Kompilieren der Scripte 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 */
        MEM_InitLabels();
        var int count; count = 0;
        
        /* In label die Ausführungsposition festhalten. */
        var int label;
        label = MEM_StackPos.position;
        /* <---- label zeigt jetzt hierhin,
         * also auf die Stelle NACH der Zuweisung von label. */
        
        Print (ConcatStrings ("COUNT: ", IntToString (count)));
        count += 1;
        
        if (count <= 42) {
            /* Die Ausführungsposition ersetzen,
             * bei dem "<-----" wird dann weitergemacht */
            MEM_StackPos.position = label;
        };
        
        /* Ist 43 erreicht, wird die "Schleife" verlassen. */
    };
    
    //******************
    
    Wichtig: MEM_InitLabels() muss nach dem Laden eines Spielstandes einmal 
    ausgeführt werden. Am einfachsten ist es, diese Funktion aus INIT_GLOBAL 
    aufzurufen. Erst nachdem diese Funktion aufgerufen wurde, kann korrekt 
    auf MEM_StackPos.position zugegriffen werden!
    
    Wichtig: Eigentlich selbstverständlich: Ein Label muss initialisiert 
    sein, bevor dorthin gesprungen werden kann! Vorwärtssprünge sind daher 
    im Allgemeinen schwierig, weil die Sprungstelle passiert wird, bevor das 
    Sprungziel passiert wird.
    
    Wichtig: Labels sind nach Speichern und Laden ungültig. Das heißt ein 
    Label muss "sofort" verwendet werden. Ich wüsste aber sowieso keinen 
    Grund, weshalb man sich Labels für längere Zeit aufheben sollte.
    
    Wichtig: Wer zwischen verschiedenen Funktionen hin und herspringt und 
    nicht weiß, was er tut, wird auf die Nase fallen. Wer mit Labels rechnet 
    und nicht weiß was er tut ebenfalls. Allgemeiner: Wer etwas anderes 
    macht als Zuweisungen der obigen Art, könnte auf die Nase fallen.
    
    //######################################
    // 4.) String Funktionen
    
    //******************
    
    func int STR_GetCharAt (var string str, var int pos)
    
    Liefert das Zeichen am offset pos im String str zurück (das heißt den 
    Zahlwert dieses Zeichens, wie man ihn in der ASCII-Tabelle findet). Die 
    Zählung beginnt bei 0.
    
    //******************
    
    func int STR_Len (var string str)
    
    Liefert die Länge des Strings in Zeichen.
    
    //******************
    
    const int STR_GREATER =  1;
    const int STR_EQUAL   =  0;
    const int STR_SMALLER = -1;
    
    func int STR_Compare (var string str1, var string str2) {
        
    Liefert STR_GREATER, wenn str1 lexikographisch nach str2 kommt und 
    entsprechend STR_SMALLER oder STR_EQUAL in den anderen Fällen. Beispiele:
    
    STR_Compare ("A", "B")      -> STR_SMALLER
    STR_Compare ("ABC", "ABC")  -> STR_EQUAL
    STR_Compare ("AA","A")      -> STR_GREATER
    STR_Compare ("BA", "BB")    -> STR_SMALLER
    STR_Compare ("B", "a")      -> STR_SMALLER
    STR_Compare ("A", "")       -> STR_GREATER
    
    (zum vorletzen Beispiel ist zu bemerken, dass Großbuchstaben 
    ironischerweise "kleiner" als Kleinbuchstaben sind)
    
    //******************
    
    func int STR_ToInt (var string str)
    
    Konvertiert die String-Repräsentation einer Ganzzahl in einen Integer. 
    Etwa wird "42" in den Integer 42 umgewandelt.
    
    Beispiele für gültige Strings:
    1.) 42
    2.) +42
    3.) -42
    
    Beispiele für ungültige Strings:
    1.) ++42
    2.) 42+
    3.) 4.2
    4.) HelloWorld
    
    Im Fehlerfall wird eine Warnung ausgegeben und 0 zurückgegeben.
    
    //******************
    
    func string STR_SubStr (var string str, var int start, var int count)
    
    Gibt den Teilstring von str zurück, der an Index start beginnt und count 
    Zeichen lang ist.
    
    Beispiele:
    STR_SubStr ("Hello World!", 0, 5): "Hello"
    STR_SubStr ("Hello World!", 6, 5): "World"
    
    Als Spezialfall davon ist folgende Funktion implementiert:
    
    func string STR_Prefix (var string str, var int count)
    
    STR_Prefix gibt den String zurück, der aus den ersten count Zeichen von 
    str besteht.
    Dies entspricht dem Verhalten von STR_SubStr, mit start == 0.
    
    //######################################
    // 5.) Menü-Funktionen
    
    Diese Funktionen sollen den Zugriff auf Menüelemente (zum Beispiel im 
    Charaktermenü) vereinfachen. Leider werden manche Menüs jedesmal neu 
    erzeugt (vom Script aus), andere dagegen werden einmal erzeugt und dann 
    behalten. Problem: Ein Charaktermenü gibt es zum Beispiel erst, nachdem 
    es das erste mal geöffnet wurde, danach liegt es im Speicher.
    Abhängig davon und von dem, was man eigentlich tun will, kann es nötig 
    sein in den Menüscripten Änderungen einzubringen oder es ist nötig, sich 
    das Menü als Objekt zu holen und in dem fertigen Objekt selbst 
    herumzuschmieren. Für letzteres gibt es hier eine Hilfestellung:
    
    func int MEM_GetMenuByString (var string menu)
    func int MEM_GetMenuItemByString (var string menuItem)
    
    Liefert die Adressen der des Menüs bzw. des Menüitems falls ein Menü 
    bzw. Menüitem mit diesem Namen existiert.
    
    //######################################
    // 6.) Globale Instanzen initialisieren
    
    Das Scriptpaket führt folgende Instanzen ein:
    
    instance MEM_Game           (oCGame);
    instance MEM_World          (oWorld);
    instance MEM_Timer          (zCTimer);
    instance MEM_WorldTimer     (oCWorldTimer);
    instance MEM_Vobtree        (zCTree);
    instance MEM_InfoMan        (oCInfoManager);
    instance MEM_InformationMan (oCInformationManager);
    instance MEM_Waynet         (zCWaynet);
    instance MEM_Camera         (zCCamera);
    instance MEM_SkyController  (zCSkyController_Outdoor);
    instance MEM_SpawnManager   (oCSpawnManager);
    
    Die hier benutzten Klassen haben alle eines gemeinsam: Es gibt stehts 
    maximal ein Objekt von ihnen zur gleichen Zeit (es gibt z.B. nicht 
    gleichzeitig zwei Welten oder zwei Himmel).
    
    Ich stelle eine Funktion zur Verfügung, die die Offsets dieser Instanzen 
    auf das entsprechende eindeutige Objekt setzt.
    
    func void MEM_InitGlobalInst()
    
    Nachdem MEM_InitGlobalInst aufgerufen wurde, können alle oben genannten 
    Instanzen genutzt werden. Nach dem Laden eines neue Spielstandes muss 
    MEM_InitGlobalInst() erneut aufgerufen werden!
    
    //######################################
    // 7.) Ini Zugriff
    
    Ini Dateien haben folgende Form:
    
    //**************************
    [mySection1]
    myOption1=myValue1
    myOption2=myValue2
    [mySection2]
    myOption1=myValue1
    myOption2=myValue2
    //**************************
    
    Literale in eckigen Klammern identifizieren Sektionen eindeutig. 
    Innerhalb von Sektionen werden Optionen mit eindeutigen Namen 
    identifiziert. Jede Option nimmt einen Wert an, der nach dem "="-Zeichen 
    steht. Da .ini Dateien keine Binärdateien sind, sind Sektionsnamen, 
    Optionsnamen und Werte alle vom Typ string.
    
    In diesem Skriptpaket gibt es Funktionen, um lesend und schreibend auf 
    die Gothic.ini zuzugreifen, sowie um lesend auf die .ini Datei der Mod 
    zuzugreifen. Zum Lesen gibt es:
    
    func string MEM_GetGothOpt (var string sectionname,
                                var string optionname)
    func string MEM_GetModOpt  (var string sectionname,
                                var string optionname)
    
    MEM_GetGothOpt durchsucht die Gothic.ini, MEM_GetModOpt die .ini Datei 
    der Mod. Gesucht wird nach der Option optionname in der Sektion 
    sectionname. Falls eine solche Sektion mit einer solchen Option 
    existiert, wird der Wert dieser Option zurückgegeben. Ein leerer String 
    sonst.
    
    Zudem habe ich Funktionen geschrieben, die die Existenz von Sektionen 
    und Optionen prüfen. Sie sollten selbsterklärend sein:
    
    func int MEM_GothOptSectionExists (var string sectionname)
    func int MEM_GothOptExists (var string sectionname,
                                var string optionname)
    func int MEM_ModOptSectionExists (var string sectionname)
    func int MEM_ModOptExists (var string sectionname,
                               var string optionname)
    
    Um schreibend auf die Gothic.ini zuzugreifen, gibt es folgende Funktion:
    
    func void MEM_SetGothOpt (var string section,
                              var string option,
                              var string value)
    
    Dabei wird die Option option in der Sektion section auf den Wert value 
    gesetzt. Falls die Sektion und/oder Option nicht existiert, werden beide 
    im Zweifelsfall angelegt.
    
    Die .ini Datei der Mod kann leider nicht beschrieben werden, da Gothic 
    Änderungen daran niemals auf die Festplatte zurückschreibt.
    
    //BEACHTE:
    1.) Falls du neue Optionen einführst gebietet der gute Stil, dass du 
    dies in einer eigenen Sektion tust und die Optionen verständlich 
    benennst! Als Norm schlage ich vor, dass eine Mod mit Namen "myMod" nur 
    in der Sektion "MOD_myMod" neue Eigenschaften einführen darf.
    2.) Die Gothic.ini wird erst beim Verlassen von Gothic physikalisch 
    beschrieben. Falls Gothic abstürzt, kann es also sein, dass Änderungen 
    verloren gehen.
    3.) Manche Änderungen werden erst nach einem Neustart von Gothic Wirkung 
    zeigen.
    
    //Nutzungsmöglichkeiten:
    -In der .ini Datei der Mod könnten dem Spieler zusätzliche 
    Konfigurationsmöglichkeiten gegeben werden, etwa könnte man dort ein 
    bestimmtes Features abschalten können, falls absehbar ist, dass nicht 
    jeder Spieler es mögen wird.
    -In der Gothic.ini könnte zum Beispiel der Schwierigkeitsgrad der Mod 
    hinterlegt sein, der sich dadurch auf alle Savegames auswirkt, und nicht 
    für jedes Savegame neu festgelegt werden muss.
    -In der Gothic.ini könnte gespeichert sein, ob die Mod bereits 
    mindestens einmal durchgespielt wurde. So könnte das zweite mal 
    Durchspielen verschieden gestaltet werden.
    -In der Gothic.ini könnten Highscores (evtl. zusammen mit idComputerName 
    gehasht zur Verifikation) hinterlegt sein.
    -In der Gothic.ini könnten statistische Informationen festgehalten 
    werden, etwa wie oft ein Spieler (insgesamt oder in der letzten Zeit) 
    geladen hat. Denkbar wäre ein System, dass den Schwierigkeitsgrad 
    drosselt, falls der Spieler sehr oft in kurzen Abständen stirbt.
    -uvm.
    
    
    //######################################
    // 8.) Tastendrücke erkennen
    
    Eine einfache Funktion ist folgende:
    
    func int MEM_KeyPressed(var int key)
    
    Liefert 1, falls die Taste gedrückt ist, die zu dem virtuellen 
    Tastencode key gehört. Die Tastencodes sind in Ikarus_Const.d zu finden.
    
    Mit MEM_KeyPressed(KEY_RETURN) kann man zum Beispiel abfragen ob die 
    ENTER Taste gedrückt ist.
    
    //######################
    
    Häufig wird man in einer Triggerschleife einem Tastendruck auflauern 
    wollen. Oft möchte man dabei nur einmal auf einen Tastendruck reagieren, 
    auch wenn der Spieler die Taste für eine bestimmt Zeitspanne festhält. 
    Dann ist es nötig zu unterscheiden, ob die Taste gerade neu gedrückt 
    wurde oder bloß noch gehalten wird. Dafür gibt es die Funktion:
    
    func int MEM_KeyState(var int key)
    
    Sie zieht neben der Tatsache, ob eine Taste tatsächlich gedrückt ist 
    oder nicht, auch noch in Betracht, was das letzte mal für diese Taste 
    zurückgegeben wurde. Es gibt die folgende Rückgabewerte:
    
    KEY_UP: Die Taste ist nicht gedrückt und war auch vorher nicht gedrückt. 
    ("nicht gedrückt")
    KEY_PRESSED: Die Taste ist gedrückt und war vorher nicht gedrückt. ("neu 
    gedrückt")
    KEY_HOLD: Die Taste ist gedrückt und war auch vorher gedrückt. ("immer 
    noch gedrückt")
    KEY_RELEASED: Die Taste ist nicht gedrückt und war vorher gedrückt. 
    ("losgelassen")
    
    KEY_PRESSED oder KEY_RELEASED werden also zurückgeben, wenn sich der 
    Zustand der Taste seit der letzten Abfrage geändert hat.
    KEY_UP oder KEY_HOLD werden zurückgegeben, wenn sich der Zustand nicht 
    geändert hat.
    
    Beachte: Wenn sich der Tastenzustand zwischen zwei Abfragen zweimal 
    ändert (zum Beispiel die Taste ganz schnell gedrückt und wieder 
    losgelassen wird), dann wird MEM_KeyState das nicht bemerken. Die 
    Funktion kann nur zu den Zeitpunkten an denen sie aufgerufen wird den 
    Tastenzustand überprüfen.
    
    Beachte auch: Die Funktion wird nie zweimal direkt hintereinander 
    KEY_PRESSED zurückgeben. Ein Aufruf von MEM_KeyState wird also die 
    Rückgabewerte von späteren Aufrufen verändern. Folgendes ist zum 
    Beispiel FALSCH FALSCH FALSCH:
    
    //******* SO NICHT! ********
    
    if (MEM_KeyState (KEY_RETURN) == KEY_UP) {
    	Print ("Die Taste ist oben!");
    } else if (MEM_KeyState (KEY_RETURN) == KEY_PRESSED) {
    	Print ("Die Taste wurde gerade gedrückt!");
    };
    
    //**************************
    
    Der else if Block wird niemals betreten werden. Wenn die Taste gerade 
    gedrückt wird, wird KEY_PRESSED nur in der ersten if-Abfrage auftauchen 
    und ist dann "verbraucht". Die zweite if-Abfrage bekommt dann nur noch 
    KEY_HOLD ab.
    
    So ist es besser:
    
    //**************************
    
    var int returnState;
    returnState = MEM_KeyState (KEY_RETURN);
    
    if (returnState == KEY_UP) {
    	Print ("Die Taste ist oben!");
    } else if (returnState == KEY_PRESSED) {
    	Print ("Die Taste wurde gerade gedrückt!");
    };
    
    //**************************
    
    Wenn mehrere Events auf die gleiche Taste hören, konkurrieren sie also 
    auch um die KEY_PRESSED Rückgabewerte!
    
    //######################
    
    Eine Funktion zum simulieren von Tastendrücken ist:
    func void MEM_InsertKeyEvent(var int key)
    
    In manchen Fällen wird die Engine (im nächsten Frame) so reagieren, als 
    wäre die Taste gedrückt, die mit dem virtuellen Tastencode key in 
    Verbindung steht. Zum Beispiel öffnet MEM_InsertKeyEvent(KEY_ESC) das 
    Hauptmenü oder schließt geöffnete Dokumente und 
    MEM_InsertKeyEvent(KEY_TAB) öffnet das Inventar, falls die Einstellungen 
    des Spielers TAB als Taste für das Inventar vorsieht.
    In anderen Fällen funktioniert diese Funktion nicht, zum Beispiel ist es 
    nicht möglich das Inventar auf diese Weise zu schließen.
    
    Das klingt nicht nur willkürlich sondern ist auch so. Das Problem ist, 
    dass die Engine auf verschiedene Arten und Weise abfragen kann ob eine 
    Taste gedrückt wurde und nur eine dieser Varianten auf 
    MEM_InsertKeyEvent anspringt. Was zum Beispiel funktioniert hat:
    
    -Inventar öffnen. (TAB)
    -Quicksave (F5)
    -Charaktermenü öffnen (C)
    -Pause togglen / Quickload (F9)
    -Tagebuch-Öffnen (L)
    -Hauptmenü öffnen / Dokument schließen (ESC)
    
    Beachte: Verschiedene Spieler nutzen verschiedene Tasten für bestimmte 
    Aktionen! Es ist aber möglich mit MEM_GetGothOpt an die Einstellungen 
    (Gothic.ini) heranzukommen. Hier sind ein oder zwei Tasten als 
    Hexadezimalstring (Vorsicht: Little Endian!) für die einzelnen Aktionen 
    registriert.
    
    //######################################
    // 9.) Verschiedenes
    
    func int MEM_SearchVobByName (var string str)
    
    Liefert die Adresse eines zCVobs mit dem Namen str, falls ein solches 
    Vob existiert. Andernfalls wird 0 zurückgegeben.
    
    Als Abwandlung davon gibt es
    
    func int MEM_SearchAllVobsByName (var string str)
    
    Diese Funktion erzeugt ein zCArray in dem alle Zeiger auf Vobs mit dem 
    Namen str stehen. Falls kein Vob mit dem Namen existiert wird ein leeres 
    zCArray erzeugt. Ein Zeiger auf das erzeugte zCArray wird dann 
    zurückgegeben. Dieses kann ausgewertet werden, sollte aber noch vor Ende 
    des Frames (bevor der Spieler Laden kann) wieder mit MEM_ArrayFree 
    freigegeben werden um Speicherlecks zu vermeiden. 
    Die Klasse zCArray ist in Misc.d zu finden.
    
    //******************
    
    func int Hlp_Is_oCMob(var int ptr)
    func int Hlp_Is_oCMobInter(var int ptr)
    func int Hlp_Is_oCMobLockable(var int ptr)
    func int Hlp_Is_oCMobContainer(var int ptr)
    func int Hlp_Is_oCMobDoor(var int ptr)
    func int Hlp_Is_oCNpc(var int ptr)
    func int Hlp_Is_oCItem(var int ptr)
    func int Hlp_Is_zCMover(var int ptr)
    func int Hlp_Is_oCMobFire(var int ptr)
    
    Diese Funktionen können u.a. nützlich sein, wenn es darum geht den Fokus 
    des Helden auszuwerten. Die Funktionen geben 1 zurück, falls der 
    übergebene Zeiger auf ein Objekt der angegebenen Klasse oder eine 
    Unterklasse dieser Klasse zeigt.
    
    Für einen Stuhl würden zum Beispiel Hlp_Is_oCMob und Hlp_Is_oCMobInter 1 
    zurückgeben, die anderen Funktionen 0.
    
    Natürlich kann man diese Funktionen noch für andere Objekttypen als Mobs 
    schreiben, wenn das nötig ist.
    
    //******************
    
    func void MEM_SetShowDebug (var int on)
    
    Setzt die Variable, die auch von "toggle debug" getoggelt wird. Dadurch 
    landen mit PrintDebug ausgegebenen Meldungen im Spy (wenn dort 
    Informationen geloggt werden). Es empfielt sich als Filter "Skript" im 
    Spy einzustellen, sonst gehen die Meldungen unter einem Haufen nutzlosem 
    Enginezeug unter.
    
    //******************
    
    func string MEM_GetCommandLine ()
    
    Gibt den Inhalt der Kommandozeile zurück, die an Gothic Übergeben wurde. 
    Diese könnte zum Beispiel so aussehen:
    
    "-TIME:7:35 -GAME:TEST_IKARUS.INI -ZREPARSE -ZWINDOW -ZLOG:5,S -DEVMODE 
    -ZMAXFRAMERATE:30"
    
    //******************
    func int  MEM_ArrayCreate ()
    func void MEM_ArrayFree   (var int zCArray_ptr)
    func void MEM_ArrayClear  (var int zCArray_ptr)
    func void MEM_ArrayInsert (var int zCArray_ptr, var int value)
    
    func void MEM_ArrayRemoveIndex     (var int zCArray_ptr, var int index)
    func void MEM_ArrayRemoveValue     (var int zCArray_ptr, var int value)
    func void MEM_ArrayRemoveValueOnce (var int zCArray_ptr, var int value)
    
    zCArrays werden an vielen Stellen von der Engine verwendet und ist eine 
    sehr einfache Feld-Datenstruktur.
    Manchmal möchte man in diese Strukturen etwas einfügen oder etwas daraus 
    entfernen. Das Einfügen ist nicht einfach, da eventuell der Platz im 
    Array nicht ausreicht und vergrößert werden muss. Daher habe ich diese 
    Funktion zur Verfügung gestellt. Die anderen Funktionen sind sozusagen 
    Beiwerk und leisten einfachere Dinge.
    zCArray_ptr ist in allen Funktionen ein Zeiger auf ein zCArray (siehe 
    Misc.d).
    
    MEM_ArrayCreate: Erzeuge ein leeres zCArray und gebe seine Adresse 
    zurück.
    MEM_ArrayFree:   Gebe sowohl das zCArray als auch seine Daten frei.
    MEM_ArrayClear:  Gibt die Daten des zCArray frei. Es wird zu einem 
    leeren Array.
    MEM_ArrayInsert: Fügt value ans Ende des Arrays an. Das Array wird 
    automatisch vergrößert falls es zu klein ist.
    
    Entfernen von Elementen passiert durch auffüllen der Lücke mit dem 
    letzten Element. Wird zum Beispiel aus einem Array (1,2,3,4,5) die 3 
    entfernt, wird das Ergebnis so aussehen: (1,2,5,4). Die 5 ist in die 
    entstehende Lücke gewandert.
    
    MEM_ArrayRemoveIndex: Entfernt das Element an Position index.
    MEM_ArrayRemoveValue: Sucht alle Vorkommen von value im Array und 
    entfernt diese.
    MEM_ArrayRemoveValueOnce: Sucht das erste Vorkommen von value im Array 
    und entfernt dies. Wird value nicht gefunden wird eine Warnung 
    ausgegeben.
    
    //Hinweis:
    Diese Funktionen erwarten Pointer auf ein zCArray! Nicht verwechseln mit 
    der Adresse des ersten Datenelements oder dem Symbolindex irgendeines 
    "var zCArray"! Manchmal wird man sich die Adresse eines zCArrays erst 
    ausrechnen müssen und zwar aus der Adresse des Objekts, dass dieses 
    zCArray beinhaltet und dem Offset des zCArrays in der entsprechenden 
    Klasse (Bytes zählen!).
    //Vorsicht:
    Beim Zerstören der Spielsession werden mit MEM_ArrayCreate angelegte 
    Arrays nicht freigegeben (-> Speicherleck), sind aber spätestens beim 
    Beenden des Spiels zerstört und nicht im Savegame (-> ungeeignet um 
    Storyinformationen zu speichern).
    //Einsatzmöglichkeit:
    Arrays dieser Art können sinnvoll zur Übergabe von großen Datenmengen 
    sein. Zum Beispiel nutzt MEM_SearchAllVobsByName Arrays um gleich eine 
    große Menge von Vobs zurückzugeben. Dabei wird verlangt, dass der Nutzer 
    das Array noch im selben Frame (bevor der Spieler Laden oder Speichern 
    könnte) wieder freigibt (mit MEM_ArrayFree).
    
    //******************
    func void MEM_CopyBytes (var int src, var int dst, var int byteCount)
    func void MEM_CopyWords (var int src, var int dst, var int wordcount) {
    
    Kopiert entsprechend viele Bytes bzw. Worte beginnend von Speicherstelle 
    src ans Ziel beginnend an Speicherstelle dst. Ein Wort ist 4 Byte groß, 
    was der Größe der meisten primitiven Typen entspricht. MEM_CopyWords 
    kopiert also einfach wordcount * 4 bytes.
    
    Wichtig: Die Speicherbereiche dürfen sich nicht überlappen, das heißt am 
    Beispiel von MEM_CopyBytes: Die Intervalle [src, src + byteCount - 1] 
    und [dst, dst + byteCount - 1] müssen disjunkt sein.
    
    //######################################
    // 10.) Obskure Funktionen
    
    Einige der internen Zwischenschritte könnten auch für andere nützlich 
    sein. Ich möchte ein paar hier für Interessierte kurz auflisten. Die 
    meisten, die diese Paket benutzen, werden sie nicht brauchen.
    
    //******************
    
    func int MEM_GetBufferCRC32 (var int buf, var int buflen) 
    
    Berechnet einen Hashwert aus einem Bytearray, dass an buf beginnt und 
    Länge buflen hat. Es wird die selbe Hashfunktion verwendet wie in Gothic.
    
    func int MEM_GetStringHash (var string str) {
    
    Berechnet den Hashwert eines Strings. Es wird die selbe Hashfunktion 
    verwendet wie in Gothic.
    Bemerkung: Diese Funktion wird von MEM_SearchVobByName benutzt.
    
    //******************
    
    func int MEM_Alloc (var int amount)
    
    Mit MEM_Alloc werden amount Byte Speicher alloziert und ein Zeiger auf 
    den Speicherbereich zurückgegeben.
    Gothic hält keine Referenz auf diesen Speicherbereich und kann ihn auch 
    nicht freigeben (auch nicht beim Zerstören der Session!).
    Speicher sollte daher nur dann reserviert werden, wenn er garantiert vor 
    dem Laden eines Spielstands wieder mit MEM_Free freigegeben werden kann 
    oder garantiert ist, dass Gothic von diesem Speicherbereich weiß und ihn 
    selbstständig freigibt.
    Vielleicht kann man mit dieser Funktion neue Objekte erzeugen und 
    dauerhaft in die Objektstruktur von Gothic einbauen. Das Bedarf aber 
    großer Vorsicht, da die Objektkonstrukturen nicht genutzt werden können. 
    Man müsste alles von Hand machen.
    
    Sehr gut geeignet dürfte diese Funktion sein um Kleinigkeiten wie 
    Listenelemente zu bauen und in vorhandenen Listen zu integrieren. Der 
    neu allozierte Speicher ist stets genullt.
    
    func int MEM_Realloc (var int oldptr, var int oldsize, var int newsize)
    
    Alloziert einen Speicherbereich der größe newsize und gibt einen Zeiger 
    auf diesen Speicherbereich zurück.
    Der Speicherbereich ab Stelle oldptr wird freigegeben.
    Falls newsize >= oldsize werden die ersten oldsize Bytes aus dem alten 
    Speicherbereich in den neuen übernommen. Der zusätzliche Speicher ist 
    mit Null initialisiert.
    Falls newsize <= oldsize werden alle Bytes des neuen Speicherbereichs 
    mit den entsprechenden Werten des alten Speicherbereichs initialisiert.
    Diese Funktion ist dazu gedacht um einen allozierten Speicherbereich zu 
    vergrößern oder zu verkleinern. Vorhandene Daten bleiben auf natürliche 
    Art und Weise erhalten.
    
    func void MEM_Free (var int ptr)
    
    Gibt einen allozierten Speicherbereich wieder frei.
    Vielleicht kann man so auch Engine-Objekte zerstören. Auch hier ist 
    große Vorsicht angesagt, da keine Destruktoren aufgerufen werden!
    
    Kleinigkeiten wie Listenelemente können so aber problemlos freigegeben 
    werden.
    
    //******************
    
    func void MEM_SetParser(var int parserID)
    
    Hiermit lässt sich der aktuelle Parser auf etwas anderes als den 
    Content-Parser umstellen. Dies ist nötig, wenn man zum Beispiel mit 
    Symbolen im Menü suchen und bearbeiten will. Kommunikation mit den 
    Menüscripten wird dadurch möglich. Aber leider ist das ganze nicht ohne 
    Tücken und vermutlich eher uninteressant, weil die Menüscripte selten 
    von Gothic aufgerufen werden. Bestimmte Menüobjekte bleiben über die 
    Session hinaus erhalten.
    
    //######################################
    // VI. Gefahren
    //######################################
    
    Mit diesem Scriptpaket kann man Gothic auf nie dagewesene Art und Weise 
    zum Absturz bringen. Wenn es sonst oft so ist, dass bei einem Bug eine 
    Quest einfach nicht läuft oder eine Option nicht erscheint, hat man 
    jetzt die Möglichkeit Fehler so einzubauen, dass Gothic den Spielern 
    zuverlässig um die Ohren fliegt. "Die Anwendung hat ein Problem 
    festgestellt und muss beendet werden", "Access Violation", "Assertion 
    failed" gehören zum Alltag beim Debugging, wenn man Features 
    implementiert, die nicht ganz trivial sind. Natürlich kann man solche 
    Fehler beheben und wenn man konzentriert und planvoll arbeitet, wird man 
    schon was vernünftiges zu Stand bringen. Aber frustresistenz sollte man 
    mitbringen.
    
    Kurz gesagt: Vorsicht! Aber keine Panik, wenn mal was schiefgeht: Mit 
    guten Debugmeldungen (nicht auf dem Bildschirm, sondern mit PrintDebug 
    in den Spy!) kann man alles beheben. (ich habe mittlerweile sogar ein 
    Skript geschrieben, dass ständig einen Skriptseitigen Callstack im zSpy 
    ausgibt und so hilft herauszufinden wo genau Gothic abstürzt, falls es 
    innerhalb des Skripte passiert, siehe Thread im Forum)
    
    //######################################
    // VII. Beispiele
    //######################################
    
    //--------------------------------------
    // 1.) Funktion zum öffnen der Truhe im Fokus:
    
    func void OpenFocussedChestOrDoor() {
        var oCNpc her;
        her = Hlp_GetNpc (hero);
        
        //Gar kein Fokusvob?
        if (!her.focus_vob) {
            Print ("Kein Fokus!");
            return;
        };
        
        //Fokusvob kein verschließbares Vob?
        if (!Hlp_Is_oCMobLockable(her.focus_vob)) {
            Print ("Keine Truhe oder Tür im Fokus!");
            return;
        };
        
        var oCMobLockable Lockable;
        MEM_AssignInst (Lockable, her.focus_vob);
        
        if (Lockable.bitfield & oCMobLockable_bitfield_locked) {
            Lockable.bitfield = Lockable.bitfield & ~ 
    oCMobLockable_bitfield_locked;
            
            Print (ConcatStrings (
                    "Folgendes Vob geöffnet: ",
                    Lockable._zCObject_objectName));
        } else {
            Print (ConcatStrings (
                    Lockable._zCObject_objectName,
                    " war gar nicht abgeschlossen!"));
        };
    };
    
    //--------------------------------------
    // 2.) Kameraposition ermitteln:
    
    func void PrintCameraPos() {
        /* Globale Instanzen (die es nur einmal gibt) initialisieren: */
        /* Initialisiert MEM_World, MEM_Game, etc. u.a. auch MEM_Camera */
        MEM_InitGlobalInst();
        
        /* Das Kameraobjekt ist kein vob (sondern was abstraktes),
         * weiß nicht wo und wie da Positionsdaten stehen.
        Ich arbeite lieber auf dem Kameravob: */
        var zCVob camVob;
        MEM_AssignInst (camVob, MEM_Camera.connectedVob);
        
        /*Hier muss man wissen wie die Transformationsmatrix aufgebaut ist:
    
            Sie besteht aus drei Vektoren, die x, y und z Richtung
            des lokalen Koordinatensystem des Kameravobs
            in Weltkoordinaten angeben (dabei müsste z die
            Blickrichtung sein). Ich habe diese Vektoren hier
            mit v1, v2, v3 Bezeichnet.
            Zusätzlich gibt es in der 4. Spalte die Translation,
            das heißt die Position der Kamera.
        
            v1_x    v2_x    v3_x    x
            v1_y    v2_y    v3_y    y
            v1_z    v3_z    v3_z    z
            0       0       0       0
            
            Die Matrix ist Zeilenweise im Speicher abgelegt.
            Da wir uns für die letzte Spalte interessieren sind die Indizes
            im trafoWorld Array 3, 7 und 11, die wir brauchen.
        */
        
        Print (ConcatStrings ("x: ", 
    IntToString(roundf(camVob.trafoObjToWorld[ 3]))));
        Print (ConcatStrings ("y: ", 
    IntToString(roundf(camVob.trafoObjToWorld[ 7]))));
        Print (ConcatStrings ("z: ", 
    IntToString(roundf(camVob.trafoObjToWorld[11]))));
    };
    
    //--------------------------------------
    // 3.) Regen starten
    
    func void StartRain() {
        /* Globale Instanzen initialisieren: */
        MEM_InitGlobalInst(); /* Hierrunter fällt auch der Skycontroller */
        
        /* man könnte sich jetzt hier was besseres überlegen,
         * aber ich machs mal so: */
        
        /* start am Anfang vom Tag */
        MEM_SkyController.rainFX_timeStartRain = 0; //FLOATNULL;
         /* ende am Ende vom Tag */
        MEM_SkyController.rainFX_timeStopRain = 1065353216; //FLOATEINS;
        
        /* Bemerkung dazu: Die Start und Endzeiten sind Gleitkommazahlen.
         * 0 steht für den Anfang des Tages 1 für das Ende des Tages.
         * Zum Aufbau des Gleitkommaformats google man nach IEEE 745.
         * Ich habe mal floats in Daedalus implementiert:
         * http://forum.worldofplayers.de/forum/showthread.php?t=500080
         * Diese Implementierung kann man nutzen um sich
         * floats aus Ganzzahlen bauen zu lassen. */
        
        /* Ergebnis: Ganzer Tag regen! (es sei denn man ist in einer Zone
         * in der es schneit, dann den ganzen Tag Schnee) */
    };
    
    //--------------------------------------
    // 4.) Geschachtelte Schleife
        
    /* Soll alle Paare (x,y) aufzählen mit
        0 <= x < max_x,
        0 <= j < max_y
    */
        
    func void printpairs(var int max_x, var int max_y) {
        /* System initialisieren */
        MEM_InitLabels();
        /* PrintDebug soll benutzt werden, also Debugausgabe aktivieren */
        MEM_SetShowDebug (1);
    
        var int x; var int y;
        x = 0; 
        
        /* while (x < max_x) */
        var int x_loop; x_loop = MEM_StackPos.position;
        if (x < max_x) {
            y = 0;
            /* while (y < max_y) */
            var int y_loop; y_loop = MEM_StackPos.position;
            if (y < max_y) {
                var string out; out = "(";
                out = ConcatStrings (out, IntToString (x));
                out = ConcatStrings (out, ", ");
                out = ConcatStrings (out, IntToString (y));
                out = ConcatStrings (out, ")");
                PrintDebug (out);
                
                y += 1;
                
                /* continue y_loop */
                MEM_StackPos.position = y_loop;
            };
            
            x += 1;
            
            /* continue x_loop */
            MEM_StackPos.position = x_loop;
        };
    };
    
    /* Ausgabe eines Aufrufs printpairs (4,2) wäre dann:
        00:36 Info:  5 U:    Skript: (0, 0) .... <zError.cpp,#465>
        00:36 Info:  5 U:    Skript: (0, 1) .... <zError.cpp,#465>
        00:36 Info:  5 U:    Skript: (1, 0) .... <zError.cpp,#465>
        00:36 Info:  5 U:    Skript: (1, 1) .... <zError.cpp,#465>
        00:36 Info:  5 U:    Skript: (2, 0) .... <zError.cpp,#465>
        00:36 Info:  5 U:    Skript: (2, 1) .... <zError.cpp,#465>
        00:36 Info:  5 U:    Skript: (3, 0) .... <zError.cpp,#465>
        00:36 Info:  5 U:    Skript: (3, 1) .... <zError.cpp,#465>
    */
    
    //--------------------------------------
    // 5.) Aufrufen einer Funktion
    //     anhand ihres Namens.
    
    /* Dieses Beispiel zeigt nicht, weshalb MEM_CallByString
     * praktisch ist, aber wie man die Funktion benutzt. */
     
    var zCVob someObject;
    func int MyFunction(var int param1, var string str1,
                        var int param2, var string str2) {
        Print (ConcatStrings (str1, str2)); //(*)
        return 100 * param1 + param2;
    };
    
    func void foo() {
        var int result;
        
        /* Der Code zwischen A und B ist in diesem Fall
         * äquivalent zu:
         *   result = MyFunction (42, "Hello ", 23, "World!");
         *                                                    */
        
        /* A */
        MEM_PushIntParam (42);
        MEM_PushStringParam ("Hello ");
        MEM_PushIntParam (23);
        MEM_PushStringParam ("World!");
        
        MEM_CallByString ("MYFUNCTION");
        
        result = MEM_PopIntResult();
        /* B */
        
        Print (IntToString (result)); //(**)
    };
    
    /* Ausgegeben wird "Hello World" (das macht MyFunction bei (*))
     * sowie "4223" (das macht foo bei (**)). */
    
    /* Anmerkung: Da Symbolindizes fortlaufend sind
     * und der Symbolindex von someObject einfach durch
     * someObject selbst gegeben ist, könnte
     * MEM_CallByString ("MYFUNCTION");
     * hier auch ersetzt werden durch
     * MEM_CallByID (someObject + 1); */
    Download des Pakets

    Das Scriptpaket heißt nicht umsonst Ikarus:
    Man kann die Grenzen von Daedalus hinter sich lassen, aber dabei auch auf die Schnauze fallen. Die sonst sehr sorgsame Kontrolle der Skriptparameter durch die Engine gibt es nicht mehr. Wer Mist macht, bekommt keine zSpy-Warnung, sondern landet auf dem Desktop mit Access Violations, fehlgeschlagenen Assertions und/oder "die Anwendung hat ein Problem festgestellt und muss beendet werden" Meldungen.

    Wer etwas umsetzen will und sich nicht sicher ist, ob es mit Ikarus überhaupt geht und falls ja, ob es vielleicht höchst unbequem zu implementieren sein wird, der sei auch an G2Ext verwiesen. Eventuell ist das konkrete Feature leichter, eleganter oder besser damit umzusetzen. Vielleicht auch nicht.

    Auf Ikarus aufbauende Scripte:


    Edit: 23.03.10, 18 Uhr: Neue Version, die IntToString benutzt anstatt mein Kürzel i2s.
    Edit: 23.03.10, 19:30 Uhr: Fix in STR_GetCharAt. Die Funktion hat nicht funktioniert. Außerdem Umwandlung von Tabs in Leerzeichen (für Leute mit anderer Tabsize).
    Edit: 02.05.10, 22 Uhr: Diverse Fixes am Code und Verbesserungen an der Dokumentation.
    Edit: 24.05.10, 17 Uhr: MEM_Realloc und Sprünge ergänzt sowie ein Bugfix und Dokuupdate.
    Edit: 13.06.10, 23 Uhr:
    • CallByString und CallByID Funktionalität ergänzt durch Funktionen, die Parameter pushen und Rückgabewerte popen. Beispiel 5 in der Dokumentation beschäftigt sich damit.
    • Alte CallByString und CallByID Derivate herausgenommen (ich hoffe die hat noch keiner benutzt).
    • In zCParser.d ein paar genauere Kommentare zu den Parsertokens eingefügt.
    • oCMag_Book Klasse hinzugefügt in Misc.d

    Edit: 20.06.10, 1 Uhr:
    • zCOption dokumentiert und Zugriffsmöglichkeit auf die .ini Dateien geschaffen.
    • MEM_ArrayInsert hinzugefügt (Nebenprodukt des .ini Zugriffs)
    • Möglichkeit die Kommandozeilenparameter auszulesen
    • entsprechendes Dokuupdate, rewrite dieses Posts

    Edit: 25.06.10, 16 Uhr: Konstanten in zCTrigger.d korrigiert.
    Edit: 27.06.10, 11 Uhr: Die Funktion MEM_KeyState hinzugefügt.
    Edit: 04.08.10, 17 Uhr:
    • Reimplementierung des Ikarus Kerns und vieler Funktionen (auf elegantere Weise, dadurch auch kompakter)
    • Auslagerung aller Konstanten (vorwiegend Adressen) in zugehörige Klassendateien bzw. in eine neue Datei Ikarus_Const.d.
      Ikarus.d selbst sollte damit unabhängig von der Gothic-Version sein und um Ikarus Gothic 1 tauglich zu machen müssen nur Ikarus_Const.d und evtl. die Klassendateien angepasst werden.
    • diverse Bugfixes
    • Konstanten für virtuelle Tastencodes in großem Umfang hinzugefügt (zu finden in Ikarus_Const.d).
    • Einige zCArray Funktionen (zum Beispiel um in Dingen wie der activeVobList herumzuschmieren oder als temporärer, skriptinterner Speicher).
    • Neu: MEM_InsertKeyEvent zum fingieren von Tastendrücken (funktioniert nur begrenzt)
    • Neu: MEM_CopyBytes / MEM_CopyWords zum Kopieren einer vorgegebenen Datenmenge von einer Quelle zu einem Ziel.
    • Neu: MEM_SearchAllVobsByName: Baut ein zCArray mit Zeigern auf sämtliche Vobs mit bestimmten Namen und liefert Zeiger auf das zCArray zurück.
    • Neu: STR_SubStr und STR_Prefix zum Abgreifen von Teilstrings.
    Für Spieler:
    Velaya # Velaya in English # Exodus Demo # Irrwichtel
    Tools für Modder:
    DiaDepp # DOPA-PARTER # zSlang
    Scripte für Modder:
    Ikarus Skriptpaket # Floats # Broadcasts
    Last edited by Sektenspinner; 18.03.2011 at 23:16.

  2. View Forum Posts #2
    Deus Oparilames's Avatar
    Join Date
    May 2004
    Location
    ex contrariis
    Posts
    10,906
     
    Oparilames is offline
    Besten Dank, gerade gestern habe ich mir vorgenommen das Scriptpaket sobald es erscheint zu nutzen.
    Einziges Problem: Ich bräuchte es wohl für G1. Das heißt es sind wohl Sachen dabei, die es in G1 nicht gibt und soziemlich alle Adressen dürften falsch sein.

    Naja, trotzdem danke, ich werde es mir trotzdem am WE mal genauer angucken.
    Oparilames nachdem er seinen Gesellenbrief erhalten hat:
    »Das war's mit dir, du Mistvieh!«

  3. Visit Homepage View Forum Posts #3
    Clockwork Origins Bonne6's Avatar
    Join Date
    Jun 2004
    Location
    Erlangen
    Posts
    11,488
     
    Bonne6 is offline
    Coole Sache, muss ich mir dann gleich mal ansehen, vor allem Regen, Marvin, allgemein Tastenkontrolle (müssten ja auch neue Hotkeys klappen) und evtl. SpawnManager, muss ich mir mal angucken Danke jedenfalls schon mal dafür

  4. Visit Homepage View Forum Posts #4
    Exodus Sektenspinner's Avatar
    Join Date
    Jul 2004
    Location
    Karlsruhe
    Posts
    7,827
     
    Sektenspinner is offline
    Quote Originally Posted by Oparilames View Post
    Besten Dank, gerade gestern habe ich mir vorgenommen das Scriptpaket sobald es erscheint zu nutzen.
    Einziges Problem: Ich bräuchte es wohl für G1. Das heißt es sind wohl Sachen dabei, die es in G1 nicht gibt und soziemlich alle Adressen dürften falsch sein.
    Ja. Wenn du Glück hast haben sich die Klassen nicht sehr verändert. Aber zumindest die Offsets wirst du korrigieren müssen.
    Ob es eine brauchbare Gothic 1 Version mit Debuginformationen gibt, weiß ich nicht, vielleicht kann Nico helfen.

    Die Offsets, die ich nutze sind:
    Code:
    /* Addressen der Parserinstanzen oder Pointer auf Parserinstanzen
    Zumindest der Pointer auf den Contentparser ist sehr praktisch */
    const int ContentParserAddress      = 11223232; //0xAB40C0;
    const int vfxParserPointerAddress   =  9234156; //0x8CE6EC
    const int menuParserPointerAddress  =  9248360; //0x8D1E68
    const int pfxParserPointerAddress   =  9278004; //0x8D9234
    /* Menüliste: Manche einmal gebauten Menüs liegen hier: */
    const int MEMINT_MenuListOffset = 9248324;
    /* Liste von Menüitems: Einmal erzeugte Menüitems liegen hier */
    const int  MEMINT_MenuItemArrayAddres = 9248508;
    /* Pointer auf Spiel und Timer. Zumindest das Spiel ist sehr wichtig
    dort kann man die Adressen vieler anderer Objekte nachlesen */
    const int MEMINT_oGame_Pointer_Address = 11208836; //0xAB0884
    const int MEMINT_zTimer_Address = 10073044; //0x99B3D4
    /* Adresse der Hashtabelle um Vobs darin zu finden: */
    const int MEMINT_crc_table_offset = 8598048; //0x833220
    /* Adresse des Speicherbereichs indem steht, welche Tasten gedrückt sind */
    const int MEMINT_KeyEvent_Offset = 9246328; //0x6D1678
    /* Zum Spiel pausieren */
    const int game_holdTime_Address = 11208840; //0xAB0888 //zBOOL*
    Mit dem Pointer auf das globale Game und auf den Contentparser könnte man schon sehr viel machen. Der Rest ist eher nebensächlich.

    Wenn man die Adressen nicht direkt rausbekommt, könnte man schauen ob man indirekt über bekannte Referenzen weiterkommt.
    Jeder Npc hat eine zCAIPlayer, die hat einen Pointer auf die Welt und die Welt hat einen Pointer auf die Session (das Game). Jetzt bräuchte man noch einen Pointer auf den Helden. Den kriegt man vielleicht mit einer Umkehrung von Nicos Parser Stack Hacking. Habe mich noch nicht ausreichend damit beschäftigt, um was darüber sagen zu können.

    Naja, vielleicht erbarmt sich auch ein Assembler-Gott und findet die Referenzen mit der Executable heraus.

    Coole Sache, muss ich mir dann gleich mal ansehen, vor allem Regen, Marvin, allgemein Tastenkontrolle (müssten ja auch neue Hotkeys klappen) und evtl. SpawnManager, muss ich mir mal angucken Danke jedenfalls schon mal dafür
    Wenn allgemein nutzbare Funktionen (die nicht zu speziell sind) oder neue Ideen wozu man irgendetwas gebrauchen könnte entstehen, könnte man die vielleicht sammeln, damit nicht jeder das Rad neu erfinden muss.
    Für Spieler:
    Velaya # Velaya in English # Exodus Demo # Irrwichtel
    Tools für Modder:
    DiaDepp # DOPA-PARTER # zSlang
    Scripte für Modder:
    Ikarus Skriptpaket # Floats # Broadcasts
    Last edited by Sektenspinner; 23.03.2010 at 18:44.

  5. View Forum Posts #5
    now also in your universe  Milky-Way's Avatar
    Join Date
    Jun 2007
    Posts
    13,456
     
    Milky-Way is offline
    Wirklich schöne Sache. Gerade vor ein paar Tagen erst habe ich mich noch mit künstlichem Regen herumgeschlagen, jetzt ist es hier direkt als Beispiel mit drin.

    Jetzt eine Frage: Wo kommen FLOATNULL und FLOATEINS her? Was genau bedeuten sie als Uhrzeiten? (um die Zeiten anpassen zu können; 0 = 0 Uhr, 1 = 24 Uhr, 0.5 = 12 Uhr ? )
    Ich nehme an, dass ich an die Funktion auch Parameter übergeben kann, wenn ich sie dahingehend abändere?
    Wie kann ich den Regen wieder stoppen? Regnet es nach einmaligem Aufruf von StartRain (); jeden Tag oder nur den ganzen Aufruf-Tag?

    Gut gefällt mir auch der Vorschlag, "Erfindungen" hier zu teilen.

    Direkt ein Fehler:
    IKARUS.D: Undefined function : I2S ( line 1058 )
    Die Engine-Klassen werden vorher geparst, allerdings gibt es in der Ikarus.d auch so schon einige FAULT und WARN Meldungen.
    Code:
    00:00 Info: 10 B:    zDSK: Opened file IKARUS.D .... <zDisk.cpp,#689
    00:00 Info: 10 B:    zDSK: Closed file IKARUS.D .... <zDisk.cpp,#632
    00:00 Info: 5  U:   PAR: CONTENT\STEINZEIT\IKARUS.D : Parse... .... <zError.cpp,#360
    00:00 Fault:0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Unhandled expression: EXP_STRING ( line 162 ) .... <zError.cpp,#366
    00:00 Fault:0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Cannot convert from type INSTANCE to INT ( line 357 ) .... <zError.cpp,#366
    00:00 Fault:0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Cannot convert from type INSTANCE to INT ( line 402 ) .... <zError.cpp,#366
    00:00 Warn: 0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Possible error, function should return value ( line 466 ) .... <zParser.cpp,#2700
    00:00 Fault:0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Possible error, function should return value ( line 593 ) .... <zError.cpp,#366
    00:00 Fault:0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Possible error, function should return value ( line 658 ) .... <zError.cpp,#366
    00:00 Fault:0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Function 'MEMINT_ASSIGNCONTENTINST': Argument 1: Cannot convert from type INSTANCE to INT ( line 765 ) .... <zError.cpp,#366
    00:00 Fault:0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Possible error, function should return value ( line 775 ) .... <zError.cpp,#366
    00:00 Fault:0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Function 'MEMINT_ASSIGNCONTENTINST': Argument 1: Cannot convert from type INSTANCE to INT ( line 812 ) .... <zError.cpp,#366
    00:00 Warn: 0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Possible error, function should return value ( line 826 ) .... <zParser.cpp,#2700
    00:00 Fault:0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Function 'MEMINT_ASSIGNCONTENTINST': Argument 1: Cannot convert from type INSTANCE to INT ( line 869 ) .... <zError.cpp,#366
    00:00 Fault:0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Function 'MEMINT_ASSIGNCONTENTINST': Argument 1: Cannot convert from type INSTANCE to INT ( line 872 ) .... <zError.cpp,#366
    00:00 Fault:0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Function 'MEMINT_ASSIGNCONTENTINST': Argument 1: Cannot convert from type INSTANCE to INT ( line 875 ) .... <zError.cpp,#366
    00:00 Fault:0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Function 'MEMINT_ASSIGNCONTENTINST': Argument 1: Cannot convert from type INSTANCE to INT ( line 878 ) .... <zError.cpp,#366
    00:00 Fault:0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Function 'MEMINT_ASSIGNCONTENTINST': Argument 1: Cannot convert from type INSTANCE to INT ( line 881 ) .... <zError.cpp,#366
    00:00 Fault:0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Function 'MEMINT_ASSIGNCONTENTINST': Argument 1: Cannot convert from type INSTANCE to INT ( line 884 ) .... <zError.cpp,#366
    00:00 Fault:0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Function 'MEMINT_ASSIGNCONTENTINST': Argument 1: Cannot convert from type INSTANCE to INT ( line 888 ) .... <zError.cpp,#366
    00:00 Fault:0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Function 'MEMINT_ASSIGNCONTENTINST': Argument 1: Cannot convert from type INSTANCE to INT ( line 892 ) .... <zError.cpp,#366
    00:00 Fault:0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Function 'MEMINT_ASSIGNCONTENTINST': Argument 1: Cannot convert from type INSTANCE to INT ( line 895 ) .... <zError.cpp,#366
    00:00 Fault:0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Function 'MEMINT_ASSIGNCONTENTINST': Argument 1: Cannot convert from type INSTANCE to INT ( line 898 ) .... <zError.cpp,#366
    00:00 Fault:0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Comparison is possible only in 'if' expression ( line 968 ) .... <zError.cpp,#366
    00:00 Fault:0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Comparison is possible only in 'if' expression ( line 979 ) .... <zError.cpp,#366
    00:00 Fault:0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Comparison is possible only in 'if' expression ( line 992 ) .... <zError.cpp,#366
    00:00 Fault:0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Comparison is possible only in 'if' expression ( line 1001 ) .... <zError.cpp,#366
    00:00 Fault:0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Comparison is possible only in 'if' expression ( line 1010 ) .... <zError.cpp,#366
    00:00 Fatal:-1 U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Undefined function : I2S ( line 1058 ) .... <zParser.cpp,#849
    00:00 --------------

    EDIT: Ich sehe gerade Deine Editierung, allerdings verweist der Download-Link noch immer auf das alte Paket.
    Last edited by Milky-Way; 23.03.2010 at 19:52.

  6. Visit Homepage View Forum Posts #6
    Clockwork Origins Bonne6's Avatar
    Join Date
    Jun 2004
    Location
    Erlangen
    Posts
    11,488
     
    Bonne6 is offline
    Quote Originally Posted by Milky-Way View Post
    Jetzt eine Frage: Wo kommen FLOATNULL und FLOATEINS her? Was genau bedeuten sie als Uhrzeiten? (um die Zeiten anpassen zu können; 0 = 0 Uhr, 1 = 24 Uhr, 0.5 = 12 Uhr ? )
    Ich nehme an, dass ich an die Funktion auch Parameter übergeben kann, wenn ich sie dahingehend abändere?
    Wie kann ich den Regen wieder stoppen? Regnet es nach einmaligem Aufruf von StartRain (); jeden Tag oder nur den ganzen Aufruf-Tag?

    Gut gefällt mir auch der Vorschlag, "Erfindungen" hier zu teilen.
    Mit den Floatwerten interessiert mich auch, vermute es ist so, wie du vemutest, nur werden ja da nur Int-Werte akzeptiert Allerdings kannst du ja die Zeit abfragen und dann Starten und wenn es aufhören soll manuell wieder beenden. Hab's noch nicht getestet, aber vermutlich die End-Zeit auch auf FLOATNULL setzen.

    Finde die Idee mit dem Teilen auch gut.

    Und Tasten abfragen klappt schon mal wunderbar, jetzt steht neuen Hotkeys nichts mehr im Wege

    @Sekti: Du hast bei den drei Spawnmanager-Konstanten geschrieben, dass die sehr interessant ist. Allerdings sind das doch nur die Adressen, oder? Wäre gut, wenn man mit denen was machen könnte, sind ja für die KI-Glocke wichtig, hat Zerxes ja mal erwähnt

    EDIT: Hab jetzt mal den Regen testen wollen, aber da tut sich gar nichts, auch Sonne abstellen ging nicht Dafür klappt schonmal das Anzeigen des Waynets
    Last edited by Bonne6; 23.03.2010 at 20:39.

  7. View Forum Posts #7
    research NicoDE's Avatar
    Join Date
    Dec 2004
    Posts
    7,404
     
    NicoDE is offline

    Post

    Quote Originally Posted by Sektenspinner View Post
    Naja, vielleicht erbarmt sich auch ein Assembler-Gott und findet die Referenzen mit der Executable heraus.
    Das kann jeder selbst herausfinden

    Hier ein paar Hinweise, um eine gute Analyse der GothicMod.exe/Gothic2.exe zu bekommen (gilt auch für die freie Version von IDA):

    Vorbereitungen:
    • Unter Vista oder Windows 7 IDA möglichst nicht ins "Programme"-Verzeichnis installieren (vermeidet Ärger mit der Benutzerkontensteuerung).
    • Die angehängten idauser.cfg und idauserg.cfg nach <IDA>\cfg entpacken (damit man diverse Einstellungen nicht immer wieder neu vornehmen muss).
    • Die <IDA>\loaders\dbg.ldw umbenennen (zu Beispiel: !dbg.ldw). Der DBG-Loader soll von IDA nicht mehr automatisch zum Einlesen der CodeView-Informationen aufgerufen werden, da das PDB-Plugin bessere Ergebnisse liefert.


    Analyse:
    • IDA starten
    • New
    • New disassembly database / Windows / PE Executable
    • <GOTHIC>\System\GothicMod.exe oder <GOTHIC>\System\Gothic2.exe auswählen
    • Next >
    • Next >
    • File loading / Start analysis now deaktivieren!
    • Finish
    • Generating list of strings kann abgebrochen werden
    • View / Open subviews / Signatures (Shift+F5) alle Einträge (vc32rtf) markieren und entfernen (Del)
    • Bis auf IDA View-A alle Views schließen (Performance)
    • File / Load File / PDB file...
      ...warten bis PDB: total <x> symbols loaded im Log auftaucht
    • Options / General... / Analysis / Analysis / Enabled aktivieren
      ...übernehmen und warten
    • Manuelle Bearbeitung für die Report-Version 2.6:
      • [G] .text:00784F80
      • [Y] void __thiscall zCConsole__AddEvalFunc(void *, int (__cdecl *)(const void *, void *));
      • [G] .text:007A0190
      • [Y] void zCParser__DefineExternal(void *, const void *, int (__cdecl *)(), int, int, ...);
      • [G] .text:0054ED60 [P]
      • [G] .text:0054EEE0 [P]
      • Options / General... / Analysis / Reanalyze program

    Danach sollte man seine Arbeit speichern und eine Kopie sichern - in IDA gibt es kein "zurück" und diverse Aktionen lassen sich kaum bis gar nicht wieder rückgängig machen.

    ps: Nützliches Plugin für IDA 5.x http://www.openrce.org/blog/view/1428
    Attached Files
    "Unter diesen schwierigen Umständen bin ich mir sicher, daß diese guten Menschen meinen augenblicklichen Bedarf an deren Gold verstehen werden." -- Connor
    Last edited by NicoDE; 23.03.2010 at 21:23.

  8. Visit Homepage View Forum Posts #8
    Exodus Sektenspinner's Avatar
    Join Date
    Jul 2004
    Location
    Karlsruhe
    Posts
    7,827
     
    Sektenspinner is offline
    Quote Originally Posted by Milky-Way View Post
    Jetzt eine Frage: Wo kommen FLOATNULL und FLOATEINS her? Was genau bedeuten sie als Uhrzeiten? (um die Zeiten anpassen zu können; 0 = 0 Uhr, 1 = 24 Uhr, 0.5 = 12 Uhr ? )
    Ich nehme an, dass ich an die Funktion auch Parameter übergeben kann, wenn ich sie dahingehend abändere?
    Hoppla, das hätte ich sagen sollen. Es handelt sich hier um Floats (Gleitkommazahlen). Eine 0 steht für den Anfang vom Tag eine 1 steht für das Ende vom Tag. Da Floats ein anderes Format haben, sieht die Float-Eins als Integer etwas komisch aus. Es ist 3F800000 oder dezimal 1065353216. Ich habe mal Floats in Daedalus aufbauend auf Integern implementiert. Aus diesem Script stammen auch die Konstanten.

    Wie kann ich den Regen wieder stoppen? Regnet es nach einmaligem Aufruf von StartRain (); jeden Tag oder nur den ganzen Aufruf-Tag?
    Gothic ist hier sehr primitiv: Beim Tageswechsel wird Startzeit und Endzeit des Regens für den nächsten Tag gewürfelt. Das heißt jeden Tag regnet es einmal. Ein Aufruf meiner Funktion betrifft daher nur den aktuellen Tag.
    Im Prinzip sagt der Aufruf:
    Start vom Regen ist 0:00 Uhr. Ende vom Regen ist 23:59.
    Der SkyController merkt dann, dass es zwischen Start und Endzeit ist, und rendert entsprechend den Regen.
    Den Regen kannst du stoppen indem du das Ende der Regenzeit passend wählst. Wenn du nicht all zu harte Übergänge haben willst, solltest du berücksichtigen, dass die ersten und letzten 20% der Regenzeit (also zwischen Anfang und Ende) zum Überblenden genutzt werden.
    Direkt ein Fehler:
    IKARUS.D: Undefined function : I2S ( line 1058 )

    [...]EDIT: Ich sehe gerade Deine Editierung, allerdings verweist der Download-Link noch immer auf das alte Paket.
    Danke, hab eine alte Datei ins Archiv gepackt. Sollte jetzt stimmen.

    Die Engine-Klassen werden vorher geparst, allerdings gibt es in der Ikarus.d auch so schon einige FAULT und WARN Meldungen.
    Code:
    00:00 Info: 10 B:    zDSK: Opened file IKARUS.D .... <zDisk.cpp,#689
    00:00 Info: 10 B:    zDSK: Closed file IKARUS.D .... <zDisk.cpp,#632
    00:00 Info: 5  U:   PAR: CONTENT\STEINZEIT\IKARUS.D : Parse... .... <zError.cpp,#360
    00:00 Fault:0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Unhandled expression: EXP_STRING ( line 162 ) .... <zError.cpp,#366
    00:00 Fault:0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Cannot convert from type INSTANCE to INT ( line 357 ) .... <zError.cpp,#366
    00:00 Fault:0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Cannot convert from type INSTANCE to INT ( line 402 ) .... <zError.cpp,#366
    00:00 Warn: 0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Possible error, function should return value ( line 466 ) .... <zParser.cpp,#2700
    00:00 Fault:0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Possible error, function should return value ( line 593 ) .... <zError.cpp,#366
    00:00 Fault:0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Possible error, function should return value ( line 658 ) .... <zError.cpp,#366
    00:00 Fault:0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Function 'MEMINT_ASSIGNCONTENTINST': Argument 1: Cannot convert from type INSTANCE to INT ( line 765 ) .... <zError.cpp,#366
    00:00 Fault:0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Possible error, function should return value ( line 775 ) .... <zError.cpp,#366
    00:00 Fault:0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Function 'MEMINT_ASSIGNCONTENTINST': Argument 1: Cannot convert from type INSTANCE to INT ( line 812 ) .... <zError.cpp,#366
    00:00 Warn: 0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Possible error, function should return value ( line 826 ) .... <zParser.cpp,#2700
    00:00 Fault:0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Function 'MEMINT_ASSIGNCONTENTINST': Argument 1: Cannot convert from type INSTANCE to INT ( line 869 ) .... <zError.cpp,#366
    00:00 Fault:0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Function 'MEMINT_ASSIGNCONTENTINST': Argument 1: Cannot convert from type INSTANCE to INT ( line 872 ) .... <zError.cpp,#366
    00:00 Fault:0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Function 'MEMINT_ASSIGNCONTENTINST': Argument 1: Cannot convert from type INSTANCE to INT ( line 875 ) .... <zError.cpp,#366
    00:00 Fault:0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Function 'MEMINT_ASSIGNCONTENTINST': Argument 1: Cannot convert from type INSTANCE to INT ( line 878 ) .... <zError.cpp,#366
    00:00 Fault:0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Function 'MEMINT_ASSIGNCONTENTINST': Argument 1: Cannot convert from type INSTANCE to INT ( line 881 ) .... <zError.cpp,#366
    00:00 Fault:0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Function 'MEMINT_ASSIGNCONTENTINST': Argument 1: Cannot convert from type INSTANCE to INT ( line 884 ) .... <zError.cpp,#366
    00:00 Fault:0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Function 'MEMINT_ASSIGNCONTENTINST': Argument 1: Cannot convert from type INSTANCE to INT ( line 888 ) .... <zError.cpp,#366
    00:00 Fault:0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Function 'MEMINT_ASSIGNCONTENTINST': Argument 1: Cannot convert from type INSTANCE to INT ( line 892 ) .... <zError.cpp,#366
    00:00 Fault:0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Function 'MEMINT_ASSIGNCONTENTINST': Argument 1: Cannot convert from type INSTANCE to INT ( line 895 ) .... <zError.cpp,#366
    00:00 Fault:0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Function 'MEMINT_ASSIGNCONTENTINST': Argument 1: Cannot convert from type INSTANCE to INT ( line 898 ) .... <zError.cpp,#366
    00:00 Fault:0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Comparison is possible only in 'if' expression ( line 968 ) .... <zError.cpp,#366
    00:00 Fault:0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Comparison is possible only in 'if' expression ( line 979 ) .... <zError.cpp,#366
    00:00 Fault:0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Comparison is possible only in 'if' expression ( line 992 ) .... <zError.cpp,#366
    00:00 Fault:0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Comparison is possible only in 'if' expression ( line 1001 ) .... <zError.cpp,#366
    00:00 Fault:0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Comparison is possible only in 'if' expression ( line 1010 ) .... <zError.cpp,#366
    00:00 Fatal:-1 U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Undefined function : I2S ( line 1058 ) .... <zParser.cpp,#849
    00:00 --------------
    Erstmal danke dafür. Ich parse mit Gothic, kriege also keine Warnmeldungen. In der Tat war ein grober Fehler in STR_GetCharAt, auf den ich durch die Warnung gestoßen wurde. Hab ihn korrigiert. Das mit i2s ist ja geklärt, was den Rest angeht:

    Code:
    00:00 Fault:0  U:   PAR: CONTENT\STEINZEIT\IKARUS.D: Comparison is possible only in 'if' expression ( line 992 ) .... <zError.cpp,#366
    Das ist Unfug. Natürlich darf ich die Operatoren || in Rechnungen benutzen.^^
    Selbst ">" und "<" muss zugelassen sein. Immerhin sind das Operationen auf Ganzzahlen.
    Aber ich habe mal ein "|" draus gemacht, dann sollte auch der GothicSourcer es schlucken. Und in diesem Fall ist es für das Ergebnis egal, welcher Operator genutzt wird.
    00:00 Fault:0 U: PAR: CONTENT\STEINZEIT\IKARUS.D: Function 'MEMINT_ASSIGNCONTENTINST': Argument 1: Cannot convert from type INSTANCE to INT ( line 884 ) .... <zError.cpp,#366
    Die Feststellung, dass ich syntaktisch nicht zwischen instance und int unterscheide ist zwar richtig, aber das das kann ich beim besten Willen nicht korrigieren. Du musst dem GothicSourcer beibringen, das zu ignorieren. Da gibts ja irgendeine Datei in der man das einstellen kann, wenn ichs richtig weiß.

    Der Rest war eine Stilfrage.

    Gut gefällt mir auch der Vorschlag, "Erfindungen" hier zu teilen.
    Jo, mir auch. Also wenn einer was baut wie:

    • Info_ClearSingleChoice: Eine einzelne Dialogoption aus der Choice-Liste entfernen. (oCInfo holen, Choiceliste holen, eine Choice suchen und umhängen (um Speicherleaks zu vermeiden am besten an die Choiceliste einer Hilfs-oCInfo, deren Choices man dann cleart)
    • Effiziente Schleifen (aufbauend auf einer Abwandlung von MEM_CallByString, man sollte aber mindestens das letzte Ergebnis der Symbolsuche cachen, damit zumindest ungeschachtelte Schleifen direkt aufgerufen werden)
    • Echte dynamische Arrays, die Weltenwechsel überleben (Abwandlung von MEM_Alloc nutzen und Speicher in Parsersymbol integrieren, dieses Parsersymbol zu einem Array machen, am besten im ersten Index den Symbolindex des Arrays Speichern, damit man das Array bequem übergeben kann, bei der Erzeugung müsste man einmalig in einem String angeben, welches Parsersymbol man zu einem Array machen will)
    • Erzeugen von Vobs (Abwandlung von MEM_Alloc nutzen, Vob in Welt einbauen).
    • Aufruf von Enginefunktionen (ok, das ist hart!, man müsste Assemblercode ins Datensegment schreiben und einen Externalaufruf hacken, sodass dieser Assemblercode im Datensegment aufgerufen wird, aber es sollte gehen, Datenausführungsverhinderung ist laut Nico i.d.R. kein Problem)
    • Austauschen von Vob-Visuals (sollte gehen indem man Visual-Pointer austauscht. Kann Probleme wegen Boundingboxen geben. Setzt wohl vorraus, dass in der Welt ein Vob mit dem neuen Visual bereits vorhanden und auffindbar ist. Außerdem sollte es vielleicht noch ein Vob geben, das noch eine Referenz auf das alte Visual hält um Leaks zu vermeiden)
    • Zugriff auf HUD Objekte und Austausch von Texturen (gerade im anderen Thread gelesen, läuft wohl auf das raussuchen der passenden Pointer raus und setzt vermutlich auch geladene Texturen voraus)
    • "füge deine tolle Idee hier ein"

    der kann das sehr gerne hier posten.

    Edit: Und weiter gehts:
    @Sekti: Du hast bei den drei Spawnmanager-Konstanten geschrieben, dass die sehr interessant ist. Allerdings sind das doch nur die Adressen, oder? Wäre gut, wenn man mit denen was machen könnte, sind ja für die KI-Glocke wichtig, hat Zerxes ja mal erwähnt
    Du musst MEM_ReadInt bzw. MEM_WriteInt benutzen um an diesen Adressen zu schreiben. Vorher musst du das Floatpaket nutzen um die passenden Floats zu bauen (wenn du die Konstante auf einen bestimmten Wert setzen willst).

    EDIT: Hab jetzt mal den Regen testen wollen, aber da tut sich gar nichts, auch Sonne abstellen ging nicht Dafür klappt schonmal das Anzeigen des Waynets
    Also bei mir macht die Beispielfunktion genau das richtige... Das heißt es regnet. Es sei denn es ist gerade Mitternacht.
    Wenn du nicht die Beispielfunktion sondern eine eigene Funktion benutzt schau mal ob du wenigstens den Standardregen am ersten Tag kaputt machst.^^

    masterState_sunOn scheint übrigens von der Engine nicht benutzt zu werden. Kein wundern, dass das nicht funktioniert.
    Aber vielleicht kannst du den Alphakanal der Sonne auf 0 setzen (oder die Farbwerte). Immerhin gibtes sun_colour[0] und sun_colour[1] zwischen denen vermutlich irgendwas überblendet wird. Müsste man probieren.

    @Nico: Danke. Das sollte helfen.
    Ah, ich hatte die Gothic.exe nicht die Gothic_mod.exe. Jetzt sieht das brauchbarer aus.
    Für Spieler:
    Velaya # Velaya in English # Exodus Demo # Irrwichtel
    Tools für Modder:
    DiaDepp # DOPA-PARTER # zSlang
    Scripte für Modder:
    Ikarus Skriptpaket # Floats # Broadcasts
    Last edited by Sektenspinner; 23.03.2010 at 22:19.

  9. Visit Homepage View Forum Posts #9
    Clockwork Origins Bonne6's Avatar
    Join Date
    Jun 2004
    Location
    Erlangen
    Posts
    11,488
     
    Bonne6 is offline
    Quote Originally Posted by Sektenspinner View Post
    Edit: Und weiter gehts:
    Du musst MEM_ReadInt bzw. MEM_WriteInt benutzen um an diesen Adressen zu schreiben. Vorher musst du das Floatpaket nutzen um die passenden Floats zu bauen (wenn du die Konstante auf einen bestimmten Wert setzen willst).
    Ah ok, muss ich mal so versuchen (nur erstmal das Floatpaket raussuchen ).

    Quote Originally Posted by Sektenspinner
    Also bei mir macht die Beispielfunktion genau das richtige... Das heißt es regnet. Es sei denn es ist gerade Mitternacht.
    Hast du Himmelseffekte aktiviert?
    Du hast oben die 20% fürs Überblenden geschrieben, das ist bei einem ganzen Tag ja einiges, d.h. ich hab vermutlich nur nicht lange genug gewartet

    Quote Originally Posted by Sektenspinner
    masterState_sunOn scheint übrigens von der Engine nicht benutzt zu werden. Kein wundern, dass das nicht funktioniert.
    Ich hatte es mit m_bSunVisible versucht, aber da hat sich auch nichts getan

    EDIT: Regen funzt jetzt, scheint am fehlenden Floatpaket gelegen zu haben Lässt sich auch einfach wieder beenden, indem man Endzeit auf FLOATNULL setzt, also beliebiges Starten und Beenden des Regens möglich. Müsste man aber wohl ein wenig schöner machen, weil so ist es ein sehr abruptes Ende.

    EDIT2: So, auch Spawnrange funzt, bin grad in einem km Entfernung und die KI kämpft trotzdem weiter. Ermöglicht jetzt z.b. größere Botk-Level oder andere Strategie-Mods
    Last edited by Bonne6; 23.03.2010 at 21:59.

  10. Visit Homepage View Forum Posts #10
    Exodus Sektenspinner's Avatar
    Join Date
    Jul 2004
    Location
    Karlsruhe
    Posts
    7,827
     
    Sektenspinner is offline
    Quote Originally Posted by Bonne6 View Post
    Du hast oben die 20% fürs Überblenden geschrieben, das ist bei einem ganzen Tag ja einiges, d.h. ich hab vermutlich nur nicht lange genug gewartet
    Bei dir ist standardmäßig nacht? Ist ja gruselig.^^

    Übrigens könnte man das "Problem" vermutlich lösen, indem man den Start auf -1 und das Ende auf 2 stellt. Dann liegen die Überblendzeiten außerhalb des Tages.

    Ich hatte es mit m_bSunVisible versucht, aber da hat sich auch nichts getan
    Das hat nur mit dem Lensflare zu tun und wird jeden Frame überschrieben. Da steht drin, ob grade was zwischen Sonne und Spieler ist denke ich. Könnte man also vielleicht verwenden um zu bestimmen oder der Spieler in einer Höhle steht.
    Wobei das unzuverlässig ist, könnte ja auch was anderes im Weg sein. Und nachts funktioniert das auch nicht.

    EDIT: Regen funzt jetzt, scheint am fehlenden Floatpaket gelegen zu haben Lässt sich auch einfach wieder beenden, indem man Endzeit auf FLOATNULL setzt, also beliebiges Starten und Beenden des Regens möglich. Müsste man aber wohl ein wenig schöner machen, weil so ist es ein sehr abruptes Ende.
    Ja, kriegt man hin. Sei b die Zeit, die du fürs ausblenden haben willst und t die aktuelle Zeit.
    Dann setze den Start des Regens auf t - 4*b und das Ende auf t + b.

    Auf ähnliche Weise kannst du einblenden.

    Rechnen mit floats natürlich nur mit dem Floatpaket. Integeroperationen machen Unsinn mit floats.

    EDIT2: So, auch Spawnrange funzt, bin grad in einem km Entfernung und die KI kämpft trotzdem weiter. Ermöglicht jetzt z.b. größere Botk-Level oder andere Strategie-Mods
    Jo, an Botk hab ich auch gedacht.
    Für Spieler:
    Velaya # Velaya in English # Exodus Demo # Irrwichtel
    Tools für Modder:
    DiaDepp # DOPA-PARTER # zSlang
    Scripte für Modder:
    Ikarus Skriptpaket # Floats # Broadcasts

  11. Visit Homepage View Forum Posts #11
    Clockwork Origins Bonne6's Avatar
    Join Date
    Jun 2004
    Location
    Erlangen
    Posts
    11,488
     
    Bonne6 is offline
    Quote Originally Posted by Sektenspinner View Post
    Bei dir ist standardmäßig nacht? Ist ja gruselig.^^

    Übrigens könnte man das "Problem" vermutlich lösen, indem man den Start auf -1 und das Ende auf 2 stellt. Dann liegen die Überblendzeiten außerhalb des Tages.
    Nene, lag wohl wirklich nur am Float-Paket, wobei sich da ja nichts geändert hat außer dem Ort, wo die zwei Variablen FLOATNULL und FLOATEINS stehen und das halt jetzt wirklich Floats sind

    Aber egal, hauptsache es klappt. Ich experimentiere mal weiter, vllt. finde ich heute noch was hübsches

    EDIT: Eine Mob_RemoveItem (var string mob, var C_Item item, var int amount); wäre praktisch und sollte jetzt ja auch machbar sein, werde ich mir mal für die nächsten Tage vornehmen vllt.
    Last edited by Bonne6; 24.03.2010 at 19:23.

  12. Visit Homepage View Forum Posts #12
    Exodus Sektenspinner's Avatar
    Join Date
    Jul 2004
    Location
    Karlsruhe
    Posts
    7,827
     
    Sektenspinner is offline
    EDIT: Eine Mob_RemoveItem (var string mob, var C_Item item, var int amount); wäre praktisch und sollte jetzt ja auch machbar sein, werde ich mir mal für die nächsten Tage vornehmen vllt.
    Problematisch ist das Item sauber zu entfernen. Wahrscheinlich geht das mit einem MEM_AssignInst an eine Scriptinstanz und dann einem Wld_RemoveItem.
    Anschließend musst du die verkettete Liste der Truhe fixen.
    Den amount-Eintrag bei Items auch nicht vergessen, es können ja Itemstapel drinliegen.

    Vielleicht ist es einfacher das Inventar von einem Npc und einer Truhe auszutauschen (Pointer umhängen) und dann die Funktionen für Npcs zu benutzen. (und hinterher wieder umhängen)

    Achja: Ich habe gerade mal die oCItems dokumentiert, das könnte auch für dich nützlich sein (neue Version ist bereits oben).

    Und wenn jemand ein Script braucht, dass Items ins Inventar des Helden transferiert falls dieser Nahe an ein Item herantaucht, hier:
    Code:
    //[...]
    //{
    	//----------------------------------
    	//  Automatisch oCItem im Fokus
    	//  aufheben beim Tauchen
    	//----------------------------------
    	
    	if (C_BodyStateContains (hero, BS_DIVE)) {
    		var oCNpc her;
    		her = Hlp_GetNpc (hero);
    		
    		if (her.focus_vob) {
    			var oCItem her_focusItem;
    			MEM_AssignInst (her_focusItem, her.focus_vob);
    			
    			if (Hlp_IsValidItem (her_focusItem)) {
    				if (Npc_GetDistToItem (hero, her_focusItem) < 170) {
    					CreateInvItems (hero, her_focusItem.instanz, her_focusItem.amount); //amount beachten
    					Wld_RemoveItem (her_focusItem);
    					
    					var string str;	str = ConcatStrings (her_focusItem.name, " aufgehoben!");
    					PrintScreen (str, -1, -1, FONT_SCREENSMALL, 3);
    				};
    			};
    		};
    	};
    //}
    Das Script jede Sekunde aufzurufen sollte genügen und ersetzt mühsame Mover und Trigger Abenteuer.
    Für Spieler:
    Velaya # Velaya in English # Exodus Demo # Irrwichtel
    Tools für Modder:
    DiaDepp # DOPA-PARTER # zSlang
    Scripte für Modder:
    Ikarus Skriptpaket # Floats # Broadcasts
    Last edited by Sektenspinner; 28.07.2010 at 19:12.

  13. View Forum Posts #13
    banned
    Join Date
    Sep 2009
    Location
    NRW, oder?
    Posts
    2,276
     
    TomTim is offline
    Mal eine kleine Frage als C++-Programmierer: Wie kannst du wissen, an welchen Stellen im Speicher geschrieben wird?

    Meines Wissens ist der Speicher (zunächst einmal) ein Sumpf. Ein unglaublich dichter, beinahe undurchsehbarer Sumpf. Sachen, die du irgendwo ablegst, könnten gelöscht werden, Sachen, die gelöscht wurden, könntest du abfragen.

    Dank Betriebsystemen ist der Speicher ein bisschen sicherer geworden. Anstatt nach Lust und Laune Speicher zu reservieren und freizugeben, einen Fehler zu machen und das Programm zum Absturz bringen, überprüft Windows, wie viel Speicher für irgendwas reserviert werden muss, ob eine Lücke in bereits reservierten Speicherstellen ausreicht oder ob man eine andere Stelle nimmt. Dieses System hat einen großen Vorteil: Man kann nicht mehr wie wild in den Speicher reinschreiben, Programme überschreiben und das Betriebsystem verarschen, zudem muss man sich nicht mehr mit komplexer Zeigerarithmetik die Nächte um die Ohren schlagen. Man muss sich nicht mehr darum kümmern, ob das 3-Byte-Objekt der Klasse XYZ in den Speicher der 4-Byte-Ganzzahl ABC reinschreibt, weil der Unterschied zwischen den verschiedenen Speicherstellen nicht 0x03, sondern 0x02 beträgt.

    Allerdings hat dieses System auch einen Nachteil: Speicheradressen werden abstrakt. Anstatt zu sagen: "Ich beschließe meine eigene Speicherverwaltung, gebe die Offsets bestimmter Objekte als Konstanten an und hantiere mit Speicheradressen. (was bei guter Programmierarbeit zu einem überaus schnellen Programm führen kann)", kann man mit diesem System nur noch sagen: "Ich weiss, meine Klasse CMyNPC belegt pro Objekt im Speicher 8 doubles, 4 floats, 20 ints und 10 strings (string-Klasse der STL). 8*8+4*4+20*4+10*4 (Zeiger sind immer 4 Byte groß)=64+16+80+40=200 Byte. Also, new-Operator von C++, suche nach einer Stelle im Speicher, wo 200 Byte frei sind, und sichere mein Objekt dort ab!".

    Diese Technik ist zwar ziemlich umständlich und nimmt dem Programmierer einen Teil seiner Freiheit, ist aber unglaublich komfortabel, da der Speicher für ein Programm nun zur Laufzeit vom Betriebssyytem gesetzt wird und nicht mehr zur Kompilierzeit vom Programmierer. Somit wird verhindert, dass ein Programm in ein bereits bestehendes Programm geladen wird. An die Speicheradressen kommt man also schlecht ran. Was mich zu meiner Anfangsfrage bringt: Wie kannst du wissen, an welchen Stellen im Speicher geschrieben wird?

  14. Visit Homepage View Forum Posts #14
    Exodus Sektenspinner's Avatar
    Join Date
    Jul 2004
    Location
    Karlsruhe
    Posts
    7,827
     
    Sektenspinner is offline
    Die Speicherverwaltung funktioniert in einigen Punkten so wie du sagst, aber absolute Adressen gibt es trotzdem. Ich versuche das mal zu erläutern:

    Es ist so, dass das Betriebssystem jedem Programm einen virtuellen Adressraum zur Verfügung stellt (jeweils im Prinzip die ganze Bandbreite von 0x00000000 bis 0xFFFFFFFF). Manche virtuellen Adressebereiche werden dann auch wirklich mit physikalischem Speicher indentifiziert. Auf welche physikalischen Bereiche, entscheidet das Betriebssystem.
    So wird wie du sagst verhindert, dass mehrere Programme den selben physikalischen Speicher nutzen und sich gegenseitig behindern.

    Aber diese Abstraktionsschicht ist für das Programm selbst vollständig opak (d.h. undurchsichtig). Das Programm selbst kennt zu keinem Zeitpunkt tatsächliche physikalische Adressen und kann auch niemals gezielt auf physikalische Adressen zugreifen. Die Umrechnung von virtuellen in physikalische Adressen geschieht erst "zwischen" CPU und Speicher in der MMU (Memory Management Unit).
    Kurz: Für das Programm sieht es so aus, als hätte es den gesamten Speicher ganz für sich zur Verfügung.

    Innerhalb des virtuellen Adressraums kann es durchaus absolute Adressen geben. Bestimmte (nicht alle) virtuellen Speicherplätze sind vom Programmstart an fest reserviert. Ich kann also immer auf den Parser an Adresse 0xAB40C0 zugreifen, das Betriebssystem (die MMU) ist dafür zuständig die tatsächliche physikalsiche Adresse (die variieren kann) zu bestimmen und den Hauptspeicherzugriff durchzuführen.

    Die Daten mit absoluten Adressen (im virtuellen Adresseraum) sind sogenannte statische Daten. Beispiel:

    Code:
    #include<stdio.h>
    
    int meineVariable = 42;
    
    int main (void) {
    	printf ("%d", meineVariable);
    }
    Hier ist meineVariable eine statische Variable. Sie wird nicht mit new erzeugt und auch nicht freigegeben. Sie wird IMMER an der selben virtuellen Adresse zu finden sein. Wenn ich also im Kompilat nachschaue wo sie liegt (z.B. mit IDA), kann ich sie im Speicher finden. Genauso kann es auch statische Objekte geben:
    Code:
    #include<stdio.h>
    class meineKlasse {
      public:
    	int var1;
    	int var2;
    };
    
    class meineKlasse meineInstanz;
    
    int main (void) {
    	meineInstanz.var1 = 42;
    	printf ("%d", meineInstanz.var1);
    	getchar();
    }
    Auch hier tritt nirgendwo new auf. Diese Situation ist aber eher selten (meistens legt man keine große Objekte statisch an). Aber oft gibt es statische Zeiger auf Klassen, zum Beispiel gibt es einen Zeiger auf das aktuelle Game. Das ist so deklariert:
    Code:
    oCGame* ogame;
    Mit IDA kann ich herausfinden wo dieser Pointer im (virtuellen) Speicher liegt. Mit dessen Hilfe kann ich dann das zugehörige oCGame finden (gesetzt den Fall, dass Gothic den Pointer aktuell hält).
    Auch alle mit "static" markierten Eigenschaften von Klassen haben feste Adressen.

    Ansonsten hast du recht: Ich kann nicht wissen, wo new ein neues Objekt hinlegt, wenn es zur Laufzeit erzeugt wird. Aber genauso wenig kann Gothic das und deswegen muss sich Gothic Referenzen auf das Objekt zwischenspeichern. Und diese Referenzen stehen wiederum irgendwo. Und dieses "irgendwo" ist direkt oder indirekt zugreifbar, man muss nur wissen von welcher statischen Eigenschaft man loslaufen muss um sie zu finden.

    Wenn ein Objekt gar nicht mehr direkt oder indirekt an einem statischen Zeiger hinge, könnte man gar nicht mehr hinnavigieren (Gothic aber auch nicht). Das sind Speicherleaks und stellen eine Fehlersituation dar.

    Meines Wissens ist der Speicher (zunächst einmal) ein Sumpf. Ein unglaublich dichter, beinahe undurchsehbarer Sumpf. Sachen, die du irgendwo ablegst, könnten gelöscht werden, Sachen, die gelöscht wurden, könntest du abfragen.
    Das ist im virtuellen Adressraum immernoch ein Problem. Wenn ich mir einen Item-Zeiger merke und das Item zerstöre ist der Zeiger natürlich ungültig und es kann zu Fehlern kommen. Aber "spontan" (das heißt ohne dass ich als Programmierer das direkt oder indirekt veranlasse) fliegt im Speicher nichts hin und her.

    Dank Betriebsystemen ist der Speicher ein bisschen sicherer geworden. Anstatt nach Lust und Laune Speicher zu reservieren und freizugeben, einen Fehler zu machen und das Programm zum Absturz bringen
    Programm zum Absturz bringen geht wie eh und je. Aber man kann das Betriebssystem nicht mehr zum Absturz bringen (so der Plan). Wenn ein Programm auf eine virtuelle Adresse zugreift, der kein physikalischer Speicher zugeordnet ist, gibt es eine Unterbrechnung und die Ausnahmebehandlung der Anwendung wird angeworfen, falls vorhanden. Sonst wird "ein Problem festgestellt" und das Programm abgewürgt.

    Im Prinzip hat sich durch die "neuen" Betriebssysteme für die Programme nichts geändert. Nur dass sie alleine in einen virtuellen Raum gestellt werden und niemand anderen kaputt machen können.
    Für Spieler:
    Velaya # Velaya in English # Exodus Demo # Irrwichtel
    Tools für Modder:
    DiaDepp # DOPA-PARTER # zSlang
    Scripte für Modder:
    Ikarus Skriptpaket # Floats # Broadcasts

  15. View Forum Posts #15
    banned
    Join Date
    Sep 2009
    Location
    NRW, oder?
    Posts
    2,276
     
    TomTim is offline
    Ich verstehe in manchen Abschnitten nur Bahnhof, tut mir leid. WO ist dieser "virtuelle Speicher"? Was ist der Unterschied zum reellen Speicher (den ich hier einfach mal als RAM-Baustein ansehe)? Was ist, wenn Speicher auf Adressen reserviert wird, den's gar nicht gibt, weil das 32-Bitsystem mit 4 Milliarden Bytes nur über 2 Milliarden Bytes verfügt? Und wie kann jedes Programm einen eigenen virtuellen Adressraum haben? Läuft das Ganze über die Festplatte und die Auslagerungsdatei?

    Und wenn meine Vermutungen stimmen, wie kann Gothic mit dem virtuellen Adressraum umgehen, wenn bereits andere Objekte gespeichert sind? Warum steht der Game-Zeiger nicht bei 0x00000001 - 0x00000004 (4 Bytes pro Zeiger, egal welcher Typ)?

  16. Visit Homepage View Forum Posts #16
    Exodus Sektenspinner's Avatar
    Join Date
    Jul 2004
    Location
    Karlsruhe
    Posts
    7,827
     
    Sektenspinner is offline
    Quote Originally Posted by TomTim View Post
    Ich verstehe in manchen Abschnitten nur Bahnhof, tut mir leid. WO ist dieser "virtuelle Speicher"?
    Virtuellen Speicher gibt es "nicht wirklich". Das Betriebssystem gaukelt jedem Programm vor, es hätte den gesamten Adressraum zur freien Verfügung. Um für die Programme die Illusion aufrecht zu erhalten muss das Betriebssystem einen ganzen Wust an Tabellen verwalten, die zwischen virtuellen Adressen und physikalischen Adressen übersetzen.

    Siehe virtuelle Speicherverwaltung in der Wikipedia für eine ausführliche Erklärung.

    Was ist der Unterschied zum reellen Speicher (den ich hier einfach mal als RAM-Baustein ansehe)? Was ist, wenn Speicher auf Adressen reserviert wird, den's gar nicht gibt, weil das 32-Bitsystem mit 4 Milliarden Bytes nur über 2 Milliarden Bytes verfügt?
    Wie genau Speicherallokation funktioniert weiß ich nicht. Ich gehe aber davon aus, dass man hier das Betriebssystem fragt. Das Betriebssystem organisiert dann physikalischen Speicher, gibt dem Programm aber eine virtuelle Adresse um darauf zuzugreifen.

    Und wie kann jedes Programm einen eigenen virtuellen Adressraum haben? Läuft das Ganze über die Festplatte und die Auslagerungsdatei?
    Nicht der ganz Adressraum ist wirklich mit physikalischem Speicher identifiziert. Anders gesagt: Es gibt Bereiche im virtuellen Adressraum, denen keine physikalsicher Speicher zugeordnet ist. Im allgemeinen wird von Programmen nur ein Bruchteil des riesigen 32 bit Adressraums genutzt.
    Entsprechend muss das Betriebssystem auch nur für die benutzen Bereiche physikalischen Speicher reservieren.
    Und wenn meine Vermutungen stimmen, wie kann Gothic mit dem virtuellen Adressraum umgehen, wenn bereits andere Objekte gespeichert sind?
    Gothic interessiert das wie gesagt nicht. Gothic "glaubt" alleine auf der Maschine zu sein.

    Warum steht der Game-Zeiger nicht bei 0x00000001 - 0x00000004 (4 Bytes pro Zeiger, egal welcher Typ)?
    Im Prinzip Spräche nichts dagegen, aber erstens ist dieser Game-Zeiger nicht die erste Variable die deklariert wird: Da liegt noch viel mehr herum, vermutlich auch Methodentabellen, viele statische Variablen, Konstanten usw die an niedrigeren Adressen liegen.
    Und zweitens sind die niedrigsten Adressen im virtuellen Adressraum für andere Dinge reserviert, genau weiß ich es nicht, aber ich schätze mal, dass das für die Kommunikation mit dem Betriebssystem wichtig ist.

    Ich hoffe ich habe deine Fragen richtig verstanden und konnte zumindest teilweise weiterhelfen.
    Für Spieler:
    Velaya # Velaya in English # Exodus Demo # Irrwichtel
    Tools für Modder:
    DiaDepp # DOPA-PARTER # zSlang
    Scripte für Modder:
    Ikarus Skriptpaket # Floats # Broadcasts

  17. View Forum Posts #17
    research NicoDE's Avatar
    Join Date
    Dec 2004
    Posts
    7,404
     
    NicoDE is offline
    Solange man absolute Adressen im Hauptmodul verwendet ist das relativ unproblematisch. Normalerweise wird die EXE immer an ihre bevorzugte Adresse geladen, da dieser Adressbereich fast immer frei ist (man müsste extra eine spezielle Software entwickeln, damit dies nicht mehr so ist) und das entsprechende Flag für den Loader (welches neuere Windows-Versionen dazu bewegen könnte, es an eine andere Adresse zu laden) in den Gothic-Modulen gar nicht gesetzt ist (das wurde erst danach eingeführt und wird bis heute fast nur von Microsoft verwendet).
    Das einzige praktische Problem könnte unter WINE auftreten (dort sind Relozierungen wahrscheinlicher). Aber selbst dort kann man mit einer einfachen Änderung im PE/COFF-Header die Relocations deaktivieren (dann muss der Loader des Betriebssystems es an die "bevorzugte" Adresse laden).
    "Unter diesen schwierigen Umständen bin ich mir sicher, daß diese guten Menschen meinen augenblicklichen Bedarf an deren Gold verstehen werden." -- Connor
    Last edited by NicoDE; 27.03.2010 at 15:35.

  18. View Forum Posts #18
    Dea
    Join Date
    Jul 2007
    Posts
    10,209
     
    Lehona is offline
    Inwiefern ist zCTimer nützlich bzw. sollte man es überhaupt benutzen? Und wenn ja: Wie macht man das?
    Oder sollte man für Timer zCWorldTimer benutzen?

  19. View Forum Posts #19
    Ehrengarde uhrparis's Avatar
    Join Date
    Jan 2006
    Location
    NRW Langenberg
    Posts
    2,855
     
    uhrparis is offline
    Sektenspinner

    Eine sehr schöne Sache, aber eben nur für Gothic 2.
    Könntest du das gleiche auch für Gothic 1 für Modifikationen erstellen?
    Wäre das machbar?
    In Punkto Marvin, was ist wenn bei einem der Marvin schon im Vorfeld nicht funktioniert... führt das Script Marvin trotzdem aus und wird auch angezeigt?

  20. Visit Homepage View Forum Posts #20
    Exodus Sektenspinner's Avatar
    Join Date
    Jul 2004
    Location
    Karlsruhe
    Posts
    7,827
     
    Sektenspinner is offline
    Quote Originally Posted by Lehona View Post
    Inwiefern ist zCTimer nützlich bzw. sollte man es überhaupt benutzen? Und wenn ja: Wie macht man das?
    Oder sollte man für Timer zCWorldTimer benutzen?
    Den zCWorldTimer braucht man nicht wirklich. Wenn du nicht gerade die Zahl der vergangenen Tage zurückstellen willst, kommst du mit Wld_SetTime eigentlich überall ran.
    Nützlich wäre es allenfalls wenn du die Zeit schneller vergehen lassen willst ("Nacht zu Tag Zauber") ohne dass Routine und Spawnmanager heiß laufen.

    Der zCTimer ist ein technischer Timer. Du kannst rausbekommen wieviel Zeit im aktuellen Frame vergangen ist, wieviel Zeit insgesamt vergangen ist und kommst an Referenzzeiten für onTimer Ereignisse.

    So kann man zum Beispiel eine Triggerschleife bauen, die in jedem Frame feuer:

    Code:
    func void meineSchleifenFunktion() {
        //Es wird gleich MEM_Timer genutzt, der muss initialisiert sein
        MEM_InitGlobalInst();
    
        //Triggernachricht senden
        Wld_SendTrigger ("MEIN_SCHLEIFEN_TRIGGER"); //ruft meineSchleifenFunktion auf
        
        //Triggerscript holen:
        var oCTriggerScript Mein_Schleifen_Trigger;
        Mein_Schleifen_Trigger = MEM_PtrToInst (MEM_SearchVobByName ("MEIN_SCHLEIFEN_TRIGGER"));
        Mein_Schleifen_Trigger._zCVob_nextOnTimer = MEM_Timer.totalTimeFloat; //wäre eigentlich jetzt schon wieder dran, wird aber erst im nächsten Frame bemerkt
    };
    Einfach die Verzögerungszeit des Triggers auf 0 setzen geht nicht, da er dann wirklich sofort feuert (dann kehrt der Aufruf von Wld_SendTrigger niemals zurück).
    Dieser Trigger feuert auch wirklich jeden Frame, selbst wenn das Spiel angehalten ist (F9, Menü, Charaktermenü...).
    Der eingestellte Firedelay bei MEIN_SCHLEIFEN_TRIGGER sollte irgendeinen positiven Wert haben (z.B: 1.0), welcher ist egal.

    Ich habe einen solchen Trigger genutzt um einen Zauber zu implementieren, der alles außer den Spieler anhält (freier Kurzstreckenteleport, Spieler läuft zum Zielort und drückt ENTER). Dazu habe ich in jedem Frame die WalkList der Welt verändert.

    Quote Originally Posted by uhrparis
    Könntest du das gleiche auch für Gothic 1 für Modifikationen erstellen?
    Können schon. Aber es braucht ein bisschen Zeit die ganzen Klassendokumentationen anzupassen.
    Wenn wirklich Interesse daran besteht, kann ich mich da mal dransetzen, aber nicht in den nächsten zwei Wochen.

    Quote Originally Posted by uhrparis
    In Punkto Marvin, was ist wenn bei einem der Marvin schon im Vorfeld nicht funktioniert... führt das Script Marvin trotzdem aus und wird auch angezeigt?
    Was meinst du? Und welches Script?
    Für Spieler:
    Velaya # Velaya in English # Exodus Demo # Irrwichtel
    Tools für Modder:
    DiaDepp # DOPA-PARTER # zSlang
    Scripte für Modder:
    Ikarus Skriptpaket # Floats # Broadcasts
    Last edited by Sektenspinner; 18.03.2011 at 11:12.

Page 1 of 22 12345812 ... Last »

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
Impressum | Link Us | intern
World of Gothic © by World of Gothic Team
Gothic, Gothic 2 & Gothic 3 are © by Piranha Bytes & Egmont Interactive & JoWooD Productions AG, all rights reserved worldwide