Introducing QuArK development
Updated 27 Feb 2022
|
Upper levels: - QuArK Information Base - 3. Advanced customization |
3.1. Introducing QuArK development |
[ | - - ]
Index |
Introduction |
Tiglari - 27 Feb 2022 | [ Top ] |
QuArK development can be carried out at three levels:
The first two require just a text-editor, since a functioning QuArK installation includes the Python interpreter, while the last requires Delphi (Borland's version of Pascal, enhanced with visual development tools). The current developers are using versions 7; version 6 will also work, and with minor modifications (mainly replacing references to the `StringUtils' module to `SysUtils') Delphi 5 can also work. Free versions can sometimes be acquired from computer magazine CD's. The Delphi code is written in a mixture of French and English, and the French is slowly being converted to English (so there is a possibility that some references to things in the Delphi here will be outdated). Total conversions can typically be supported by creating new *.qrk files in the form of `addons', while supporting a new game usually requires some Delphi coding (mostly because each game tends to introduce some quirks into the texture and map formats, and the read/write code for these needs to be reasonably fast). Interface ideas on the other hand can often be supported just in Python, with maybe a little bit of Delphi to support a new `specific type' in a form (if you have a bright idea and think you need a new specific type, some Delphi coder might well be able to provide it). Central to everything is the concept of `QObject', QuArK's universal format for the storage and management of structured information. Maps, game information supplied in add-ons, and the descriptions of most of the dialog boxes and other data-entry windows in QuArK are all described as QOjects. The .Qrk files are just QObjects presented in a written format for QObjects, and understanding them is essential for following the Python and Delphi as well. A QObject is a thing (technically an instance of a class in Delphi) with two kinds of properties:
So a face is a QObject with a `v' specific of 9 numbers indicating the face and texture position information (the location of the corner and two ends of the Cyan L), and a `tex' specific giving the name of the texture. You can see the specifics of a map object by hitting the second button over the multi-page panel (it's usually only interesting for entities). And a poly (brush) is a QObject whose list of subitems are the non-shared faces of that poly (shared faces appear in the subitem list of the group containing that poly). And so on. There's one special specific that should always be there, and maybe shouldn't be thought of as a specific at all, and that is `name'. A QObject's name is divided into two parts, shortname and extension. The shortname is usually just an identifying label (tho for entities it's the classname), but the extension, separated by a dot or colon, specifies the kind of object. The dot separator is used for types of QObjects that can plausibly be stored in a file of their own (so they are in effect a file-type), while the colons are for smaller types of objects, such as the different components of a map. Here are some of the more important extensions:
Because each specific has a unique identifying name, the order of the specifics list doesn't matter, but the subelements don't have labels; for map objects, they are displayed in the tree-view, and the order is as given. In principle you could make something depend on it, tho I don't think anyone ever has). It might be useful to note that QObjects are sort of like a limited form of XML, with no character data interspersed between subelements. With QObjects in hand, we push on to .qrk's. |
.Qrk files |
Tiglari - 03 Mar 2019 | [ Top ] |
ToDo: *add a rundown of the major components of a game-support file and an addon file. .qrk files are just QObjects stored in files. There are actually two kinds of .qrk files, text and binary (both unfortunately with the extension .qrk). The text files can be read an edited with a text-editor (although the format is initially forbidding), whereas the binaries are much more compact, and allow `delay loading' which helps QuArK manage big ones with less chugging. To get a text version of a binary *.qrk, just open it, and save then save it (as `structured text', with the extension .txt to keep the file system happy). The `technical info' zips for the older QuArK versions, and the current development snapshots, contain the text versions of the *.qrk files, which furthermore tend to contain useful information such as comments on the syntax, etc. A (text format) .Qrk begins with the line `QQRKSRC1', which says that it's a QuArK text format file. Then there can be comment lines beginning with `//' (everything after `//' on a line is ignored), whitespace and blank lines etc. until we get down to business with a line whose first nonblank character is: '{'. '{' opens a QObject. We already know that QObjects have two `Specifics' (name-value pairs), and `Subitems', which are QObjects which will have a `name' specific. So it's not too surprising that the rest of a QObject will consist of a series of pairs: <Name> = <Value> (where <Value> can be as large an expanse of text as you like, if it's a great big QObject) Until we fetch up at the end with the closing `}', after which the file must end. So let's look at the beginning of the Portal of Praevus add-on (I started as a Hexen II entity guy, so we gotta have some Hx2 content here ...) QQRKSRC1 // This file has been written by QuArK Version 5.0.b5 // It's the text version of file: Praevus.qrk // Portal of Praevus add-on by tiglari@hexenworld.com // to be added on to datah2.qrk (data file for Hexen II) // // The Textures need to be further subsorted into wall, floor/ceiling, // button, etc; if you want to do this please do, it's not rocket // science! { QuArKProtected = "1" Description = "Portal of Praevus Data" praevus directory infos.qctx = { Game = "Hexen II" SourceDir = "PORTALS" // GameDir = "fortress" } After the opening guff, we see that the first specific is `QuArKProtected'. This will cause a warning to pop up whenever QuArK tries to save/overwrite this file (only use this if the file is considered part of QuArK). 'QuArKProtected''s value is a string, enclosed in quotes. There are several other schemes for representing values, such as Hex codes, which we'll get into shortly. Then comes our first subelement. Its Name specific will be `praevus directory infos.qctx'. Its shortname is `praevus directory infos' and its extension is .qctx, so it's a Quake Context, and modifies how things are done (here where to look for info about the game). This particular QObject has two ordinary string-specifics, and then ends. So it goes on with more subelements until the final '}' on line 6108. So that's the basic idea, but there is a bit more to it than that. First, in addition to strings, there are several additional types of specific-values. Binary data is introduced with `$'. So for example the line from the Defaults.qrk file prescribing the red default color for tags is: Tag = $FF0000 (a standard RGB color code, for those new to such things) Multiline strings can be specified as sequences of ordinary "-bounded strings separated by `$0D': Hint = "This ought to work" $0D" if it doesn't, why not try extreme skiing?" And sequences of numbers are bounded by ', a bounding box for example might be specified as: bbox = '-8 -8 -8 8 8 8' All this and some more stuff that I haven't seen exemplified is prescribed in the function ConstructObjsFromText in the file QkFileObject. * ToDo: sections on the major components of the major kinds of .qrk file would go nicely here * Forms: For game supporters, one of the most important kinds of components of .qrk files are :forms, since these control how the specifics of entities are entered into maps. :forms are also important in Python coding, since they are used to control in making dialog boxes, etc. To get a sense of how it goes, here's the beginning of the entity forms specifications from DATAH2.QRK: Entity forms.qctx = { // ENTFRM Ring_Flight:form = { Help = "Ring of Flight" bbox = '-8 -8 -44 8 8 20' mdl = "models/ringft.mdl" more: = { Typ="B" Txt="triggering" Cap="Push" Form="item_triggers:form" Hint="specifics for triggering when item is taken" } spawnflags: = {Txt="&" Typ="X1" Cap="FLOAT" Hint="item doesn't drop to floor when spawned "} } Ring_Regeneration:form = { ... .... Entity Forms is a Quake Context, its subitems are a (big) list of :form objects. These have some specifics (all QObjects, regardless of type, can have specifics freely added to them), and then comes a list of subitems. Each subitem is a little window, `control' or `widget' (GUI geekspeak) whereby some attribute of the entity can be set by the editor. I need to call them something, so I'll call them `fields'. The way fields work is a bit devious. They have names like `more:', `spawnflags:', etc. (colon, null extension). Then inside the brackets are listed further specifics, of which the most important is `Typ'. Typ determines what kind of control is used; X1 for example is a checkbox that sets the first bit position (flag) in an integer. Usually the name determines what attribute of the entity is being set, so the `spawnflags' subitem sets the the first bit position of the `spawnflags' attribute of the entity. But not always; `more:' above is just a label. Another important specific of fields is `Txt'. This is the `label' that appears to the left of the data-entry widget itself. If it's "&", then the name of the field is used as the label, so if you look at entity specifics in the map editor, you'll see the name of the various entity attributes on the left, their values on the right (in the data-entry widgets). Their are various further field specifics, "Cap" for additional text (appearing on the right) and "Hint", for flyover hints, and whatever more the Delphi code for that kind of Typ happens to support. So the :more button calls up an additional window for entering further specifics, its form is given as the value of the Form specific. Next is a large list of all the different Typs (programmers might like to know that they're defined in a big cascade of Case statements in prog/FormCfg.pas:TFormCfg..wmMessageInterne; once you've got the hang of it, it's not too hard to add new ones, without fully understanding everything that's going on). Browsing in the text versions of the game support files, especially dataq2.qrk, is a way to get started learning them. |
Guide to Typ's |
cdunde - 13 Aug 2021 | [ Top ] |
Typ's are used in all kinds of forms but the most common are Dialog Boxes.
These are placed within triple double quotes """ and ends with them also, because it's a multi-line string. First the variable, which can also be a stored setting, must be defined before and outside of the above area like this: NbrOfLines = "10" Then that variable can be used within the triple double quotes area like this: mesh_shader: = {Typ="M" Rows = """ + chr(34) + NbrOfLines + chr(34) + """ Scrollbars="1" Txt = "mesh shader" Hint="Contains the full text of this skin texture's shader, if any."$0D "This can be copied to a text file, changed and saved." } The chr(34) items, which is for a single double quote, must be used like this to avoid confusion with the triple double quotes. Here is a list of the most common used Typ's in .QRK :form definitions. Almost all components have some standard specifics you can use: Hint - displays a help message when the cursor hovers over the item. You can have spread it on several lines. Hint = "Help text"$0D0D"How to spr" "ead text on several lines." The dollar sign is used to enter one or more special/invisible characters. In this case two new-line characters. Help text Txt - defines the label. Labels are special in QuArK. If you set their value to "&" they will display the name of the component. name: = { Typ="E" Txt="&" } you will get 'name' as caption for this entry field. This is often used for the labels of entity specifics. SelectMe - set this to one and the component will become selected when its window/dialog is displayed. Note: The first character of Typ works as a flag. If you use a lower-case character the button/entry field will fill all available space its caption (Txt) does not occupy.
|
Understanding OOP |
Tiglari/cdunde - 05 Apr 2018 | [ Top ] |
1. Intro OOP, or object-oriented programming, is a programming methodology developed in the 70s that became popular in the 80s for building large and complex programs, such as especially GUI applications, of which QuArK is a fine instance. Popular OOP languages include C++, Java, Object Pascal/Delphi and Python. There are both subtle and obvious differences between the approaches taken by all of these languages, but there is also a lot of commonality, such that someone already familiar with OOP from the first three mentioned languages can probably pick up what they need to know about Python OOP from the documentation and QuArK code. 2. Basic Objects An object is basically a chunk of the computer's memory that has been organized to provide storage for some data, and methods for changing it and reading it. So it's like a 'struct' in C, or a 'record' in Pascal, but more powerful. For a simple example that's not totally irrelevant to a geometry-managing program such as QuArK, suppose we have circles to manage. A circle can be characterized by the three coordinates of its center, and by its radius: class Circle: x = 1 y = 0 z = 5 r = 1.5 (Important: watch your letters, they are case sensitive.) Or you can just create the object by writing: class Circle: pass and add the data later: Circle.x = 1 Circle.y = 0 Circle.z = 5 Circle.r = 1.5 Whichever you've done, you can get the data back later (Delphi users take note: there's no 'with' statement, since Guido van Rossum, Python's creator, finds them harmful to programs' maintainability). So if either of the above is in a file 'circles.py', in the Python interpreter you can write: >>> import plugins.circles [you type] >>> plugins.circles.Circle.r [ditto] 1.5 [Python answers] Note that Python is much more liberal than C++, Object Pascal/Delphi, or Java, since in these languages you'd have to declare your object as belonging to a 'type', and say what kinds of 'data members' it could have, in advance of using it. This rather minimalistic sort of class has its uses (for example as a package for passing bundles of data round in the 'live edit dialog' facility (LiveEditDlg in quarkpy.dlgclasses), but to make classes really worthwhile several closely related new ideas are needed, first instances and methods, and later, inheritance. 2. Methods and Instances A method is basically a function, closely associated with an object, for doing something involving its data (adding data, reading it, performing some calculation involving it and returning the result, or whatever). For example having created a circle, we might want to access its circumference and area; the idea is that we'll write things like answer = plugins.circle.circumference() (Parentheses because this is going to be produced by a kind of function, and functions are marked in Python by parentheses.) But to do this in a sensible way we need to distinguish two slightly different notions, 'circle' as a particular object, and 'circle' as a template for making objects. Different circles will have different centers and radii, but use the same method to make them. So we need a way to define both a template and a method for making things that fit it. Templates for objects are what 'classes' are really supposed to be, and the methods for making particular objects, 'instances', are called 'constructors'. In Python, constructors all have the same name, '__init__': the double underscores on either side are a signal that something with special meaning for Python is involved. All methods, including constructors, are defined as functions internal to the class, so their definitions start with 'def'. And these functions have 'self' as their first argument, which always refers to the object (being created, if the function is a constructor). So the revised definition for the circle class might look like this: import math class Circle: def __init__(self, x, y, z, r): self.x = x self.y = y self.z = z self.radius = r def circumference(self): return self.radius*math.pi def radius(self): return self.radius At this point, we have completed the creation of our class. Because we write this code in a text editor we need to save it as a Python file, in this case as 'circles.py', and include 'import math' (because we have used its pi function in our 'def circumference') in front of the 'circles' definition, before we save it. We then need to move the 'circles.py' file to the plugins folder. As a matter of good coding practice, the 'import' and 'from' statements are usually grouped together at the beginning of the Python file. At this point we could continue to add more code, to this file, to apply (or use) the 'class Circle' and its definitions we just created, or we can create another Python 'plugin' file and 'import circles' to there, which is what we will do now just to demonstrate how files can pass data and functions. So we will now write another simple 'plugin' Python file (and save that to the plugins folder as well) with the following code to see the effect of the Circle class in the circles.py file: import quarkpy.qmacro # to bring in qmacro functions we need import quarkx # to bring in quarkx functions we need from circles import * # to bring in all of our circle class def printcir(self): circle = Circle(1,0,3,1.5) # assigns values to the attributes circle.circumference() # passes the values to circle class answer = circle.circumference() # collects the 'return' data print answer # prints the answer to the console But before we can save this file, we need to add some code to create a menu item, that we can use to activate (or call) the 'printcir' function: def WindowClick(self): import quarkpy.mapoptions # brings in the 'Options' menu printcir(self) # calls our function quarkx.console(1) # brings the console into view quarkpy.mapoptions.items.append(quarkpy.qmenu.item("&Print Cir.", WindowClick)) # adds it to the 'Options' menu For a concrete example of some rather basic classes, look at the two kinds of menu items defined in quarkpy.qmenu, 'item', and 'popup'. These classes have only constructors, and no other methods, and all the constructors do is attach the same parameters as data members, and add in some extra default stuff, such as 'normal' as a value of 'state'. Note that functions, such as 'onclick', can be attached to objects by constructors (but these aren't methods). 3. Inheritance The final thing we need for a real OOP system is inheritance, which is basing classes on other classes. In a real geometry-managing program we'd have lots of different shapes, with various features. For example points wouldn't have radius or circumference (perhaps), but they would have a 'center' or 'origin', like all other figures. So we might define: class Figure: def __init__(self, x, y, z): self.x = x self.y = y self.z = z def origin(self): return self.x, self.y, self.z class Point(Figure): pass class Circle(Figure): def __init__(self, x, y, z, r): Figure.__init__(self, x, y, z) self.radius = r def circumference(self): return self.radius*math.pi def radius(self): return self.radius A derived class 'inherits' all methods and data members from its parent, so we can write something like: >>>from Figures import * >>>point = Point(1,1,1) >>>point.origin() (1,1,1) In addition to being inherited, methods (and data members) can be 'overridden': if the derived class defines a method or data member with the same name as one of the parent class, then the definition in the derived class is normally used rather than in the parent, for instances of the derived class. This is illustrated with the second constructor __init__() of class Circle with the code line of Figure.__init__(self, x, y, z) as shown above in the script. However, sometimes we need to get at the parent method, which is also illustrated in this constructor: when one name is put in front of another with '.', we say that the first name is a qualifier of the second. So to get at the parent's definition, we qualify the method name with the name of the parent class, but when we do this we have to provide the 'self' argument explicitly. Indeed, any method of any class can be used in this way, qualifying it with the class name and providing the first argument. So what is the difference between a method and a function that just happens to have been attached to an object, such as the onclick function for qmenu.item? First, only methods participate in inheritance, and second, only methods can be called via the shortcut technique with an instance as qualifier, and automatically supplied as first argument (but you could write something like Circle.circumference(mycircle) instead of mycircle.circumference(), if you wanted to). Unfortunately I haven't been able to find any really simple example of a base class and some subclasses in the QuArK code. Some important ones are the dialog boxes, base class dialogbox in quarkpy.qmacro, and the handles, base class GenericHandle in qhandles.py. But there's lots of stuff in them. An example of a subclass of dialogbox is SimpleCancelDlgBox defined in quarkpy.qeditor; you can find further subclasses of SimpleCancelDlgBox by searching for 'CancelDlgBox)' (the final parenthesis picks up that it's being used as a base class in a class statement). There is much more to Python classes than this; it wouldn't be a bad idea to look over the classes section in the tutorial, although there's a fair amount of stuff that wouldn't make much sense to beginners (but also some reasonably straightforward examples. 4. Classes, Builtin types and Extension types. There's one more basic topic that really has to be covered, which is the difference between classes, builtin types, and extension types. Classes are the things we've just covered, whereas builtin types are the basic types such as int, string etc. provided by Python. A basic difference between them is that you can't add random new stuff to a builtin type. For example, if you write: myinteger = 2 myinteger.tag = 'this cool one I just invented' mycircle = Circle(0,0,0,3) mycircle.tag= 'this cool one I just invented' Extension types are types which have been defined in extensions to Python, which in the case of QuArK Python will have been written in Delphi. Some important extension types for QuArK Python are vector and qobject, created by calls such as: myvector = quarkx.vect(1, 0, 10) face = quarkx.newobj("top:f") An especially noteworthy feature is, that the QObject type in particular is defined by means of a rather large Delphi class hierarchy; Delphi classes are also OOP, but different in various ways from Python. The 'suffixes' to the string argument passed to quarkx.newobj (":f", ":b", etc.) determine what Delphi type is used to make the instance of the extension class, and this then determines what data members and methods are available from Python (e.g. .normal is available for faces (suffix ":f"), but not polys (suffix ":p"). |
Copyright (c) 2022, GNU General Public License by The QuArK (Quake Army Knife) Community - https://quark.sourceforge.io/ |
[ Top - ] | -