THE QUAKECUSTOM SYSTEM Version 1.0 Copyright: QuakeCustom is written by Jeff Epler and derived from the QuakeC code written by Id Software. You are granted the right to create QuakeCustom modifications for personal or server use. If you distribute QuakeCustom modifications, you must offer a free method (Such as FTP or HTTP) to access the modifications, and you must at least offer the source of your QuakeCustom modifications. You may not distribute binary-only "progs.dat" created using QuakeCustom. If you wish to package QuakeCustom on a CD-ROM or other for-profit compilation method, you must first recieve my written permission. Table of contents: 1. Introduction 2. What you need 3. How to use it 4. A short introduction to the console 5. Writing a QuakeCustom modification (May contain technical QuakeC details) 5.1. Changes 5.1.1. Compared to Id's progs106 5.1.2. Compared to previous QuakeCustom versions 5.2. QuakeCustom hooks 5.3. QuakeCustom directives 5.3.1. exclude 5.3.2. require 5.3.3. file 5.3.4. precache 5.3.5. impulse and impulseN (Where N is a series of digits) 5.3.6. object, objectr and replace 5.3.7. flag 5.3.8. keep and keepv 5.3.9. command and bind 5.4. Example code 5.5. Future 6. Available QuakeCustom modifications 6.1. The included modifications 6.2. QuakeCustom modifications available elsewhere 7. About me 1. Introduction The QuakeCustom system is intended to make the addition of new items, player capabilities, and weapons easy. QuakeCustom is a combination of a modified QuakeC source code base and a script in the Python language. To add a new weapon, item, or morph, you just place the .qc file in the QuakeCustom directory and run the qcustom script. Because many of the features formerly built in to progs.dat are now modular (monsters and items), you can generate smaller progs.dat -- this gives you more room for your own mods. For instance, most deathmatch servers can leave out the monsters. The smallest progs.dat I can generate is 196760 bytes, though it is not very useful. 2. What you need (I invite feedback on this section, since I use quake exclusively under the Linux environment, and have never tried these DOS versions of the tools I use in QuakeCustom) o Registered quake, which can be ordered from 1-800-ID-GAMES, or bought in software stores. http://www.idsoftware.com/ Install this by following the instructions. o QuakeCustom http://incolor.inetnebr.com/jepler/quake/ Install all the files from qcustom?.zip in the directory where you installed quake. Use -d with pkzip, or otherwise cause your unzip utility to create directories. o The QuakeC compiler (qcc). ftp://ftp.idgames.com/idstuff/source/qutils.zip (Source and win32 binaries) ftp://ftp.cdrom.com/pub/idgames2/utils/quakec (Other qcc versions, may be a DOS one there) Place this in a directory in your path, or in the QuakeCustom directory. o The Python interpreter. ftp://ftp.python.org/pub/python/pythonwin/index.html (Windows (95?) only) ftp://ftp.python.org/pub/python/wpy/ (Read dos-pyth.txt for my installation experience) ftp://ftp.python.org/pub/python/src/python1.4.tar.gz (Source, compilable on most Unix; I don't know about using the python source with any DOS or Windows compiler) Install this by following the instructions. o Optional, other files created to work with QuakeCustom. None exist yet, but I hope that other QuakeC modification authors will make use of my work and make it easier to make "Compilations". I will also be seeking the permission of the authors of my favorite quakec work to release QuakeCustom versions of their changes. Unzip these files according to their directions. Often, this will require the '-d' option to pkunzip, so that directories are created. 3. How to use it This is the step that QuakeCustom makes much easier than the other schemes I've worked with. Drop some QuakeCustom-compatible .qc files in a subdirectory, and execute the qcustom script: C:\GAMES\QUAKE\QCUSTOM> python qcustom.py -r or use the batch file included, which will clean up junk files most versions of qcc leave around: C:\GAMES\QUAKE\QCUSTOM> runme (In other words, run the qcustom script with the python interpreter, in the qcustom directory. The script takes arguments: python qcustom.py [-r] [directories ...] -r specifies that .qc files should be sought recursively, and you can also specifically list directories that are to be searched for added .qc files. If no directories are specified, the current directory is used) You will see messages such as: m-demon.qc needs morph.qc. w-fishgu.qc needs m-fish.qc. [...] exclude w-spike.qc exclude w-templa.qc * Output from qcc starts here outputfile: progs.dat compiling defs.qc [...] compiling w-lshiel.qc compiling w-soulsw.qc writing progdefs.h 108848 strofs 25471 numstatements 2532 numfunctions 4946 numglobaldefs 239 numfielddefs 14204 numpr_globals 502124 TOTAL SIZE Now, you have a progs.dat which contains all the modifications you placed in the directory. If you see an error before the blank line, this means there was an error in the qcustom.py script, and you should first check for a newer version of QuakeCustom and then write me to report the bug. If you see an error below the blank line, it probably means that the author of the modification should be sent a bug report. If you see the message Run your QuakeC compiler now... then QuakeCustom wasn't able to run your QuakeC compiler by itself. You'll have to run it manually. This may indicate that the qcc executable isn't installed correctly. (Make sure it's called 'qcc.exe', not 'qccdos.exe', or 'advqcc.exe' or the like) Read the *.txt files created by the qcustom script for a description of impulses (impulses.txt), needed external files (needed.txt), and new objects (objects.txt). Especially make a note of the things in impulses.txt, as you'll need to type these impulses at the console or bind keys to them. Now, change directories to the main quake directory and run quake with a special argument: C:\GAMES\QUAKE\QCUSTOM> cd .. C:\GAMES\QUAKE\> quake -game qcustom (q95 for Windows 95 users. Make sure -game is lowercase) Start a game. You should now enjoy all the neat features of the QuakeCustom modifications you've installed. 4. A short introduction to the console Suppose that 15 is the impulse to morph into a Human, 12 is the impulse to become a Fiend, and 21 is the impulse to ready the Fish Gun. (This is the case with the default QuakeCustom files) Let's first turn into a fiend. Follow these steps: Type ~ (The 'console' pops down) Type 'impulse 12' Type ~ (the 'console' pops back up) You should see a teleportation-like flash, and the message 'Player has become a fiend.' Now attacking will claw, and jumping will jump forward damaging anything you run into. Now, let's bind some keys. H will turn us Human, F will make us a Fiend, and 9 will get out the Fish Gun: Type ~ (The 'console' pops down) Type 'bind f "impulse 12"' Type 'bind h "impulse 15"' Type 'bind 9 "impulse 21"' Type return, then ~ (the 'console' pops back up) Type h (you turn back to a human) If you exit Quake by choosing the 'quit' option, and not by rudely closing the window or turning off the computer, all the keys that you bind should be available the next time you run QuakeCustom. 5. Writing a QuakeCustom modification (May contain technical QuakeC details) You can two basic kinds of things in a QuakeCustom modification, Objects (items or monsters) or Impulses (weapons, special abilities, or anything else). In either case, you may need to precache more files than Quake does manually, so QuakeCustom permits you to add Precaches as well. 5.1. Changes 5.1.1. Compared to Id's progs106 T_Damage has an extra parameter added for the type of damage done, for use with the .resist/.immune/.vulnerable variables. A quick conversion method is to pass 0 for this parameter. Otherwise, pass an or'd (|) value of the types of damage that the damage sourced is like. 5.1.2. Compared to previous QuakeCustom versions QuakeCustom 1.0 second release to QuakeCustom 2.0: QuakeCustom 1.0 used directives like // object item_foo QuakeCustom 2.0 requires that you write //#object item_foo instead, because QuakeCustom was reading comments not intended for it and incorrectly interpreting them. Modularized monsters, items. Added 'objectr', 'replace', 'keep', and 'keepv'. Used GNU Indent to re-format QuakeC source code. QuakeCustom 1.0 to QuakeCustom 1.0 second release: Minor changes to work in DOS (All filenames uppercase) 5.2. QuakeCustom hooks In QuakeCustom, there have been hooks (function pointers) added at many places in player.qc and client.qc, making it possible to modify most aspects of the player's behavior. Here is a list of the hooks, and what they do: .void() _stand; Called when the player is standing still. Responsible for performing animation frames, or any other activity. (For instance, regeneration when the player is a Zombie) .void(entity attacker, float take) _pain; Called when the player is injured. .void() _run; Called when the player is moving. Responsible for performing animation frames, or any other activity. .void() _impulse; Called when the player sends an impulse in the range 1..8, or greater than the highest defined impulse but less than 255. (Usually used to select weapons for a morph) .void() _attack; Called when the player presses attack, and time > self.attack_finished. Different from extra_fire(), _attack is checked before extra_fire. .void() _jump; Called before regular checks for 'able to jump' are made. Used for Scrag's flying. .void() _jump2; Called after regular checks for 'able to jump' are made. .void() _watermove; Calleed every frame, replaces drowning checks. .float(entity what, entity you) _can_get_p; Returns true if 'you' can pick up and use 'what' .float health_modifier; How much greater or less than the regular human this player's health can be. Among other things, this makes max health 100*self.health_modifier .float resist; .float immune; .float vulnerable; Tells if the player (or monster) resists (takes 1/2 damage from), is immune to (takes no damage from) or is vulnerable to (takes double damage from) a given sort of attack. See the ATTACK_* constants in defs.qc. .string(entity targ, entity attacker) _killmsg; .string(entity targ, entity attacker) _killmsg2; Death messages are of the form (dead player name)+_killmsg()+(killing player name)+_killmsg2() Modify them for morphs, use extra_killmsg{,2} for weapons .void() extra_fire; Called when a player has a special weapon armed. This is checked for after _attack, so that morphs cannot generally use special weapons. .string() extra_killmsg; .string() extra_killmsg2; Like _killmsg, _killmsg2 except for extra weapons. .void() extra_set_current_ammo; Called to set the ammo display for special weapons. In making a new morph, you generally need to deal with these hooks: _stand, _pain, _run, _impulse, _attack, _jump, _jump2, _watermove, _can_get_p, health_modifier, resist, immune, vulnerable, _killmsg, and _killmsg2. Setting any hook to SUB_Null means that the default human player action will be taken. In making a new weapon, you generally need to deal with these hooks: extra_killmsg, extra_killmsg2, extra_fire, extra_set_current_ammo You can reset all these to default by calling the functions reset_player_morph, reset_player_weapon or reset_player_all with the player entity you want to turn back to normal. You should use these functions to keep your code compatible, since future versions of QuakeCustom might add new hooks, and your code will be written assuming default player behavior. 5.3. Quakec Directives QuakeCustom uses specially formatted comments, called Directives, to create its list of objects, impulses, precaches, excluded and required files. The general format is: //#keyword name comment ... The keywords can be: exclude file flag impulse impulseN (Where N is a series of digits) keep keepv object objectr precache replace require 5.3.1. exclude To exclude a file from consideration by QuakeCustom, make the first line read exactly "//#exclude". No impulses, objects, or precaches will be read from this file. It will not be included in progs.src. If a file requires an excluded file, an error will be printed. 5.3.2. require Tell QuakeCustom that the current file requires that another .qc file be present and compiled before it. An example: // (quux.qc) //#require foo // 'foo.qc' from current directory //#require bar.qc // 'bar.qc' from current directory //#require ./baz.qc // 'baz.qc' from current directory //#require items/gleep.qc // 'gleep.qc' from items directory //#require impulses/xyzzy // 'xyzzy.qc' from items directory foo.qc, etc., will be required for compiling quux.qc file, and will be placed before it in the progs.src. If foo.qc isn't already among the files that QuakeCustom was going to inspect, it is added to the list. One good use of //#require is to keep 'projects': // (_morph.qc) //#require morphs/m-demon.qc //#require morphs/m-fish.qc //#require morphs/m-hknigh.qc //#require morphs/m-human.qc //#require morphs/m-ogre.qc //#require morphs/m-shalra.qc //#require morphs/m-shambl.qc //#require morphs/m-templa.qc //#require morphs/m-wizard.qc //#require morphs/m-zombie.qc Now, 'python qcustom.py _morph.qc' will load all the morph files. This is about the same effect as 'python qcustom.py -r morphs'. 5.3.3. file Tell QuakeCustom that an external file (.mdl, .wav, etc) is required by this file. As in QuakeC, use / for directories, not \. Example: // (m-dole.qc) //#file progs/dole.mdl //#file sound/dole/attack.wav Here, the Dole morph requires a model file and a sound file. QuakeCustom will issue an error if these files do not exist. Another use is to ensure that documentation is always sent along with your mod: // (m-dole.qc) //#file republican.doc (Yes, it's trivial to remove the #file line, but maybe people will be nice) 5.3.4. precache Tell QuakeCustom that a precache function needs to be called each time the server starts a level. // (m-dole.qc) //#precache player_dole_precache void() player_dole_precache = { precache_model("progs/dole.mdl") precache_sound("dole/attack.wav") }; 5.3.5. impulse and impulseN (Where N is a series of digits) Tell QuakeCustom that an impulse function needs to be added. QuakeCustom will (currently) choose the impulse number for itself. // (m-dole.qc) //#impulse player_dole_become Turn into Bob Dole //#impulse3 choose_a_party Choose your political party void() player_dole_become = { // Do stuff to accomplish the morph here }; void(float party) choose_a_party = { bprint(self.netname); if(party==0) bprint("is a libertarian.\n"); else if(party==1) bprint("is a republican.\n"); else (party==2) bprint("is a democrat.\n"); }; The difference between impulse and impulseN is that for impulseN several impulses are handled in the same function, and their numbers are guaranteed to be contiguous. 5.3.6. object, objectr, and replace This allows you to make a list of object spawn functions provided by your code, note that they are replacable, or make your objects replace existing objects. Object does nothing but make an entry in 'objects.txt', which is a good way to collect a list of classnames usable with your code set. // (m-dole.qc) //#object campaign_contribution A campaign contribution void() campaign_contribution = { precache_model("progs/money.mdl") // Code here to spawn the object -- Model like a check? }; Objectr creates a function *_replace, where * is the classname you gave to objectr. You should call this when spawning your item, and if it returns a nonzero status then return. This means that someone has replaced your item with their own. // (m-dole.qc) //#objectr campaign_contribution A campaign contribution void() campaign_contribution = { if (campaign_contribution_replace()) return; precache_model("progs/money.mdl") // Code here to spawn the object -- Model like a check? }; Replace will allow your object to replace a replacable object, some fraction of the time. It is a good idea to //#require the file which contains the object you want to replace: // (m-dole.qc) //#objectr campaign_contribution A campaign contribution //#require items/ammo.qc //#replace ammo_shells campaign_contribution 0.8 void() campaign_contribution = { if (campaign_contribution_replace()) return; precache_model("progs/money.mdl") // Code here to spawn the object -- Model like a check? }; Now, campaign_contribution will replace ammo_shells 80% of the time (0.8). If some other object type replaced campaign_contribution 50% of the time, then it would actually appear in 40% of the places ammo_shells would have, since *_replace() is tested whenever an object might be spawned. In this situation: //#require items/ammo_qc //#replace ammo_shells foo 0.5 //#replace ammo_shells bar 1.0 void() foo = { ... }; void() bar = { ... }; The probabilities are as follows: ammo_shells: 0.25 foo: 0.25 bar: 0.50 This is because there are 2 replace directives for ammo_shells, so the probability of each replacement taking place is 1/2 what it normally would be. If there were three replace directives for ammo_shells, they would each be 1/3 of what they normally would be. Another useful technique is to have a directive-only file with replace: //#require items/ammo.qc //#require items/cheese.qc //#replace ammo_shells ammo_cheese 0.5 So if ammo_cheese is usually intended not to replace anything, you can merely include this file to make it do so. You may only use object types declared objectr as the first operand of //#require, 5.3.7 flag Allocate a flag which is saved from level to level. You must use the special functions 'getflag' and 'setflag' to access these flags. float getflag(entity object, float flagnum); // returns 0 if flag is not set on object, !=0 if it is set // note that 'getflag(self, FL_CHEESE) == TRUE' is // not the way to test things, since getflag can return // any nonzero number when a flag is set. void setflag(entity object, float flagnum, float set); // Sets flag flagnum on object //#flag FL_CHEESE //#impulse ToggleCheese void() ToggleCheese = { if (getflag(self, FL_CHEESE)) setflag(self, FL_CHEESE, 0); else setflag(self, FL_CHEESE, 1); } Do not define FL_CHEESE yourself, or use any functions besides getflag and setflag to access it. Qcustom will take care of saving these flags for you. All flags start with a value of 0. getflag and setflag can be used on non-player entities, but in that case the settings are (obviously) not saved from level to level. However, it is suggested that you use getflag and setflag only for player data that needs to remain from level to level. 5.3.8. keep and keepv Allow values on players to be saved between levels. //#keep func-to-save func-to-restore bits [initial-value] //#keepv variable min max increment [initial-value] When using the first form, func-to-save should return the value (in the range 0..2**bits-1) to save when called with the entity as self. func-to-restore is called with the value earlier saved, and the entity as self. When using the second form, 'variable' should be the name of a .float entity field, 'min' and 'max' are the largest and smallest values that will ever be stored, and 'increment' is the smallest meaningful increment between one value and the next. The optional initial-value will be saved to the variable (or through func-to-restore) when SetNewParms is called. Example: //#keep CheeseGun_GetAmmo CheeseGun_SetAmmo 8 0 .float ammo_CheeseGun; /* In the range 0..200 */ float() CheeseGun_GetAmmo = { return self.ammo_CheeseGun; }; void(float val) CheeseGun_SetAmmo = { self.ammo_CheeseGun = val}; Or, using the second form: // .float ammo_CheeseGun2; /* is defined for us, range 0..200 */ //#keepv ammo_CheeseGun2 0 200 0 5.3.9. command and bind These directives are used to create a sample autoexec.cfg which includes console commands for impulses, and then bindings for the console commands. //#impulse player_zombie_become Become a Zombie //#command zombie player_zombie_become //#bind w zombie Here, 'w' iz bound to the 'zombie' command, and the 'zombie' command is aliased to whatever impulse player_zombie_become was assigned. The autoexec.bat produced from might look like this: alias zombie "impulse 13" bind w "zombie" Also useful is: //#command +foo start_foo //#command -foo stop_foo //#bind q +foo alias +foo "impulse 13" alias -foo "impulse 14" bind q "+foo" 5.4. Example code There is some sample code in m-templa.qc (A template for new morphs) and w-templa.qc. (A template for new weapons) Some other testing code is in test/. 5.5. Future I hope to add some of the following to QuakeCustom in the future (the list below is in no particular order): o Allow the 'impulse' directive to ask for particular impulse numbers o Allow QuakeCustom to assemble a .pak file containing progs.dat and those files listed in the 'file' directive. o Add whatever hooks are necessary for bots o Versions of my favorite quakec mods for QuakeCustom o Monsters of your own kind should be friendly to you when morphed? o (friendly monsters in general) o Clean up the qcustom.py source o Re-write documentation in .html 6. Available QuakeCustom modifications 6.1. The included modifications Included are the ChaseCam, several new weapons and several morphs. modules/hook.qc // Grappling Hook convert/chasecam.qc // ChaseCam 3.3 by Rob Albin items/ammo.qc // Formerly builtin items items/armor.qc // Formerly builtin items items/health.qc // Formerly builtin items items/keys.qc // Formerly builtin items items/powerups.qc // Formerly builtin items items/weapons.qc // Formerly builtin items monsters/boss.qc // Formerly builtin monster monsters/demon.qc // Formerly builtin monster monsters/dog.qc // Formerly builtin monster monsters/enforcer.qc // Formerly builtin monster monsters/fish.qc // Formerly builtin monster monsters/hknight.qc // Formerly builtin monster monsters/knight.qc // Formerly builtin monster monsters/ogre.qc // Formerly builtin monster monsters/oldone.qc // Formerly builtin monster monsters/shalrath.qc // Formerly builtin monster monsters/shambler.qc // Formerly builtin monster monsters/soldier.qc // Formerly builtin monster monsters/tarbaby.qc // Formerly builtin monster monsters/wizard.qc // Formerly builtin monstes monsters/zombie.qc // Formerly builtin monster morphs/m-demon.qc // Morph into the Fiend morphs/m-fish.qc // Morph into the Rotfish morphs/m-hknigh.qc // Morph into the Hell Knight morphs/m-human.qc // Morph into the Human morphs/m-ogre.qc // Morph into the Ogre morphs/m-shalra.qc // Morph into the Vore morphs/m-shambl.qc // Morph into the Shambler morphs/m-wizard.qc // Morph into the Scrag morphs/m-zombie.qc // Morph into the Zombie weapons/w-fishgu.qc // Turns a player into a fish weapons/w-flame.qc // An ugly 'flame' weapon weapons/w-lbolt.qc // A lightning-bolt weapon, like Hexen weapons/w-lshiel.qc // An ugly 'lightning shield' weapons/w-soulsw.qc // Deathmatch-only, exchange souls with // your opponent (and weapons too) weapons/w-shotgu.qc // Sample, reimplements the shotguns weapons/w-spike.qc // Sample, reimplements the nailguns 6.2. QuakeCustom modifications available elsewhere There are none at this time. Mail me and I'll add things to this list. 7. About me I'm the author of several QuakeC mods. Currently, the best known seems to be my 'Morph' mod. [QuakeCustom includes all the goodies of Morph, plus more] I play and program for quake under Linux on a Pentium 133 with 32 megs memory and about 1 gig of disk. My modem is only 14.4, so I don't play deathmatch much. I am a second year computer science major at the University of Nebraska in Lincoln (USA). You can mail me with any questions about QuakeCustom you have, but I encourage you to seek out FAQs related to Quake and QuakeC first if you think your problem might not be QuakeCustom's fault. I especially seek other mods that are written to work with QuakeCustom, or examples of code that cannot currently be converted to QuakeCustom because of a capability I have not included. Unfortunately, I am unlikely to be able to answer questions where there is a DOS or Windows problem, since I do not use those operating systems. Please _do_ mail me success stories, especially windows and dos folks. I want to make sure that my instructions are usable on those operating systems even though I haven't been able to test them out myself. Jeff Epler jepler@inetnebr.com November 30, 1996