_________________________________________ ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ Vampire the Masquerade Bloodlines(PC) Mod Development Guide _________________________________________ ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ May 25, 2008 Version 1.0 Written by: Dheu Email: Dheuster@gmail.com Use subject: BloodlinesDevGuide 1.0 Living Document: (read before emailing anyone) ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ This is a living document. http://docs.google.com/Doc?id=dhgs89mq_12hbmgkpd9 If you see any mistakes, or have anything that you want to add, email me and I will add you to the update list. (You will need a google account) If you make additions/corrections, be certain to give yourself credit in the Contributing Authors section at the end of the guide. ______________________________________Notes____________________________________ This Tutorial\FAQ was also published at: http://www.gamefaqs.com/ http://www.gamewinners.com/ http://www.cheatcc.com/ Official 1.2 Patch : vtmb_1_2.exe 15.2 MB (15,973,576 bytes) http://www.vampirebloodlines.com/patch/ This Document looks best in a fixed-width font, such as Courier New. Vampire The Masquerade Bloodlines is Copyright © 2004 by Activision I am not affiliated with Activision or anyone who had anything to do with the creation of this game. This Document may be posted on any site. You may not charge for, or in any way profit from this Document. ------------------------------------------------------------------------------- Table of Contents: ------------------------------------------------------------------------------- I. Basics I.1 Game Architecture I.2 Enabling the Game Console I.3 Standard Tools I.4 VPK Files II. Getting Started II.1 Creating a baseline II.2 Unofficial Patches II.3 Uncompressing Game Files\Change Management III. Introduction to Python III.1 The __main__ Object III.2 Executing Python commands from Console. III.3 Executing Console commands from Python III.4 Entities III.5 Creating Entities with Python III.6 Events III.7 Input\Output III.8 Bringing it all Together III.9 Known Bugs IV. Dialogs IV.1 Dialog Basics IV.2 DLG File Format IV.3 Special Conditions and Colored Responses IV.4 Dialog Engine Commands and Flow Control IV.5 Considerations and Scripting IV.6 Dialog Engine Bugs V. Dialog Audio Synchronization V.1 DLG, VCD and LIP Files V.2 VCD Internals V.3 LIP Internals V.4 Creating Custom Audio VI. Animating NPCs with Python VI.1 Skeletal Animations VI.2 Facial Expressions VI.3 Dispositions VI.4 Schedules and ScriptedDisciplines VI.5 Gestures and interesting_place VI.6 scripted_sequence and logic_choreographed_scene VII. Editing Models/Skins VII.1 Basics VII.2 MDL File Details VII.3 Working with downloaded Skins VII.4 Editing Skins yourself VIII. Cameras and Cut scenes IX. Custom Items X. Miscellaneous XI. Legalities XII. Common Scenarios and Examples Frequently Asked Questions VTMB Links Appendices A. Entity Classes B. Map Names C. Item Name Summary D. Game States External E. Common Models : http://docs.google.com/Doc?id=dhgs89mq_3gtwn2chb F. VCLAN Values : http://docs.google.com/Doc?id=dhgs89mq_4fgq4nrfg F.5.6 VCLAN Values : http://docs.google.com/Doc?id=dhgs89mq_5cxmzw4vg G Entity Details : http://docs.google.com/Doc?id=dhgs89mq_6fmx3cxgt H Animations : http://docs.google.com/Doc?id=dhgs89mq_7dbfdbwdh I Gestures : http://docs.google.com/Doc?id=dhgs89mq_8fpssv86r J Console Vars : http://docs.google.com/Doc?id=dhgs89mq_10cfs83dqp Contributing Authors Final Words.... =============================================================================== I. > > > > Basics =============================================================================== These notes make certain assumptions about the person reading them. In short, I assume the reader is a programmer of some ilk, who either has python experience or has enough general programming experience that you can pick up Python from a good tutorial in about 30 min. ------------------------------------------------------------------------------- I.1 > > > > Game Architecture ------------------------------------------------------------------------------- The game is composed of 5 Major parts: 1) The game engine: You generally don’t touch this part. It handles rendering the 3D environment, managing input, output and events. VTMB uses an early prototype version of the Valve Source Engine for rendering and event management. (Basically fully functional except Enemy AI). 2) Map files: Map files (.bsp) serve 2 purposes. They define the visual environment and also provide an initial set of objects within those environments. Eembedded objects include characters, trigger zones, sound emitters, invisible cameras and patrol path nodes. (If you have experience with Never Winter Nights editing, some of this may sound familiar.) For those that are new to this, I will simply say that there is a lot of invisible objects hidden on maps that you can’t see that help make things easier for the game engine. 3) Models: Model files (.mdl) provide the look and feel of objects when rendered within the Map. These are read in at run time and injected into the map. (maps contain "pointers" to these models, but not the model data itself.) Some MDL files don’t contain any model data, but only contain animations. This is discussed more below. 4) Sounds: Ever watch a movie with the sound turned off? Gets real boring real fast. On the other hand, you can take a crappy looking 16 color sprite and give it a human voice and it adds a whole new dimension of interaction. VTMB uses wav files for background music and ambient sounds. It uses mp3s for dialogs. 5) Scripts A good scripting language is what separates RPGs from Action games. Scripts allow developers to compose cut scenes, conversations, remember choices and edit the environment based on user actions. The scripting language for bloodlines is python, and it is the glue that holds the game together. ------------------------------------------------------------------------------- I.2 > > > > Enabling the Game Console ------------------------------------------------------------------------------- Since VTMB doesn’t come with a slick Map Editor like Neverwinter Nights, modding the game will involve a lot of work within the game itself. For example, you may need to scout out locations, characters and animation sequences for re-use. To this end, you will need the console. To Enable the console, Right click the icon you use to start the game. Go to "Properties" and add –console to the target. The new launch target should look something like: "C:\Program Files\Activision\Vampire - Bloodlines\vampire.exe" -console STEAM USERS : Right Click VTMB link in Steam, select Properties and the Set launch options. Add "-console" (without quotes) to the input box. You may have problems if running VISTA. Now when you start the game, the console will appear. Hit the tilde (~) to Hide/unhide the console. The console comes with hundreds of commands to help developers and players alike. Simply hit a letter to see all commands that start with that letter. Type "HELP " to see more info on a specific command. A full listing of commands and variables is included in attached Appendix J. I found the following commands invaluable during the development of my MOD. notarget [0/1] Enemies do not see you (invisibility) draw_hud 0: Removes the hud. noclip [0/1] Walk through walls. More importantly, disables the activation of triggers. cl_showfps 1: Shows your frame rate (important when testing models) cl_showpos 1: Shows your position and map name. picker: Quite possibly the most useful console command the game has to offer. Displays a bounding box around any entities you approach which show additional entity information such as its base class and instance name. maps Shows list of game maps map (no bsp) Teleport to a map. Be warned that scripts often assume a certain game state, so if you teleport somewhere before you would normally have access to it, things may break. Even starting a conversation may cause the game to crash. report_enties: logs a list of all entity classes currently in memory to the console. Unfortunately, it doesn't tell you the instance name. ( you will need to use some python for that) ent_info: give the name of an entity class (typically found using the previous command), this function will list the entities "inputs" and "outputs". The inputs translate to functions that you can call on the entity. Some entities inherit functions from their parents. In these situations, not all functions are seen. But it’s a good starting point. The outputs translate to events that the entity can detect (and then re-throw). You can only detect events with embedded entities. See Appendix G for a full listing of entity functions and events ent_dump : Given the name of an entity, prints all properties and attributes associated with it to console. Sometimes you have to name the instance first. > FindPlayer().SetName("pc") > ent_dump pc createplayer : Even though you may think you are starting the game over, you are in fact only rebuilding your own character. Once creation process is over, the game resumes in your last location. You will lose all attributes an XP. vstat get "attribute" value console method of increasing stats. Negative values are ignored. Less useful, but still interesting: ai_show_interesting Displays "interesting places" as phone-booth sized cubes scattered about the map. These are like magnets and pull NPCs toward them. When the NPC enters, they perform an action associated with the booth. The booths have info overlaid; typically the default action that an NPC will perform when they enter the "booth". cl_entityreport 1 Displays a list of all entity instances. Unfortunately, most of the info is C++ related: C classes and vector data that we can't touch from python. cl_pdump # Display additional entity info on screen. Pass in the entity number retrieved using either picker or cl_entityreport. set to -1 to deactivate. For a full list of commands use : cmdlist For a list of both variables and commands use : cvarlist The console is connected directly to the game engine. If a command is entered that the engine does not recognize, it relays the command to the python shell. This means you can access python objects and methods from console. As you learn more about python, you will be able to do even more from the console. However trying to call python functions immediately from autoexec.cfg or user.cfg wont work as the shell isn’t initialized when those modules load. You can however define aliases and bind keys that will execute python functions at a later time. FREEZE/LOCKING ISSUES: Using the console outside of the game menus can make the game unstable. Specifically, displaying the console normally pauses the game and when you click on the [X] in the top right corner to remove the console, sometimes the game will remain paused. This behavior is mostly unpredictable. If this happens to you; in order to get the game to un-pause you need to hide the console from a game menu. IE : hit ESC, enable/hide the console, then hit ESC to return the game. I have also been told that going to the character stats window will un-pause the game. If you wish to avoid the possibility of Locking/Freezing, simply make a habit of going to the game menu before activating console. ------------------------------------------------------------------------------- I.3 > > > > Standard Tools ------------------------------------------------------------------------------- VPKTool : === THE === modding tool for VTMB. The most recent release is version 3.9a and you can access it here: www.strategyinformer.com/pc/vampirethemasqueradebloodlines/tool/7142.html or from Turfster's website: http://turfster.cjb.net/ ** It is also included with all of WESP's patches VPKTool is really all you need for basic modding. However there are other tools that help with specific aspects. Python 2.1.2 The scripting language that the original game used. You can download the original shell here. It may also have been distributed with this Dev Guide. http://www.python.org/ftp/python/2.1.2/ The main thing here is the IDLE editor, which provides indention and Color coding support. DialogEditor DialogEditor is handy for visualizing character dialogs. Unfortunately it doesn’t allow you to edit conditions or actions, making it almost useless for creating dialogs. But if your goal is to update existing lines, Or review spelling in new text that you did by hand, then it is a godsend. Bear in mind when NPCs talk, they are playing pre-recorded audio. So While you can change player responses, you can’t change what the NPCs say. There is room for limited ADDITIONS and new material). ZVTools A set of Python scripts by ZylonBane that allow you to grab objects and move them around within a map. More importantly, it has the ability to save off all the map data. Basically the scripts allow runtime editing of the maps. Very cool if your mod will involve editing the maps. Now, if you want to actually EDIT models or Skins or introduce new custom content into the game, you may need some additional software. The original game was built using Maya for texturing and 3D Studio Max for models. The best free stuff that I know of is Blender3D for Model editing and Gimp for Image editing. There is a blender import plug-in for importing the models into Blender. It requires the installation of python. If you own Half Life 2, you can download the Steam Software Development Kit via Steam. The SDK offers tools for editing MDL models. While steams tools are easier to use than Blender, editing models is a complicated task for common users regardless of the tool. The general editing and re-skinning of models is outside the scope of this tutorial, however I will touch on some of the basics. ------------------------------------------------------------------------------- I.4 > > > > VPK Files ------------------------------------------------------------------------------- If you look inside the Bloodlines installation directory, you will notice some large files with the extension .vpk. All the resources for the game are compressed within these files. This serves 3 purposes: 1) Faster Loading: Easier to ask the OS to retrieve 1 big file than 300 small files. 2) Smaller Footprint: Hey, who wants the game to take up another 5 gigs? 3) User Overrides: When a user extracts a resource from the .vpk file and places it within the Vampires installation directory, (directory structure intact), the game engine will load the users uncompress version on startup instead of the one in the VPK file. While this design allows user mods, the downside is that it only allows 1 User Mod to be installed at a time. =============================================================================== II > > > > Getting Started =============================================================================== There are some pre-emptive steps that need to be taken care of before you can really begin modding. At a bare minimum, you must uncompress the files into a state in which you can make changes to them. This may be all you need if you are just playing around and feeling out what you can do. However, if you wish to share your mod with other people, you will eventually need to keep track of your changes, build a zip file for distribution and provide instructions for installing it. If you are a forward thinker, you can take some steps now to make the distribution and installation instructions easier to create and manage. ------------------------------------------------------------------------------- II.1 > > > > Creating a baseline ------------------------------------------------------------------------------- A baseline is a snapshot of all the files in your game folder BEFORE you start editing them. If a user has a different version of the game than the one you build your mod upon, they may break their game if they install your mod. For this reason, Most mod developers will provide very specific instructions for installing their mod. These instructions generally go something like: 1) Install a fresh copy of VTMB (you MUST uninstall if it exists) 2) Install the Official 1.2 patch from (Steam Users Ignore) 3) Extract zip file to /Vampire The official 1.2 patch is supported by the Distributor, so despite being an external dependency (not included in your mod distribution), it is relatively safe to assume it will be around for many years. Unfortunately, Troika went out of business shortly after releasing VTMB so there is no expectation that new official patches will be release. Luckily, the Official 1.2 patch is still very much playable. A few misspelled words, a few side quests that may not open up if you don’t do things in the right order. However, the main quest is solid. That said, if you search around the Internet you will likely see Fan supported "Unofficial Patches". Read below for more info: ------------------------------------------------------------------------------- II.2 > > > > Unofficial Patches ------------------------------------------------------------------------------- Unofficial patches are user built mods (much like what you may be endeavoring to create). Some simply fix scripting and timing issues with the original game. Some re-introduce content that wasn't linked into the original game. Some of the more advanced patches actually update the game engine and/or add support for higher resolutions and textures. Problems with Unofficial Patches: - Unofficial patches are NOT supported by a distributor. They are supported by an every day guy like yourself. Unlike companies, human beings are fickle. Relying on someone else’s work creates a high-risk external dependency. Will the patch still be around when you are done? You must think forward. Not just weeks and months, but years into the future. - There is more than 1 mod out there with the term "Patch" in it. If you tell someone to download an unofficial "patch", you must also be certain to specify the AUTHOR and hope users get the right one. The safest bet is to use an actual url and hope the site is still up in 12 months. - Some Unofficial Patches come in the form of an installable executable. It is dangerous to ask users to install an executable, period. Taking the risk yourself is one thing, asking someone else to do the same is another. - The more installation requirements you place on users, the less likely someone is to take the time out to install your mod. Most of the issues above can be solved through MOD EXTENSION: Basically, you use the 1.2 patch as your baseline, but then install an unofficial patch/mod on top of your baseline, keeping track of all the files it installs/touches. When you package up your mod, you include the patch files as part of your distribution. By distributing the patch with your mod, you remove the risk of your baseline disappearing and decrease the work required by the user. If you DISTRIBUTE someone else’s work with yours, you should ask for permission. Furthermore, you should make it clear when you ask for permission that your mod will likely introduce modifications to their patch. My recommendation: I advise checking out some of the Unofficial patches. You want to avoid requiring users to install ANYTHING other than the official patches and your mod. As such, you are looking for permission to include the desired patch in your mod's distribution. If you can’t find an author willing to grant permission. Then use the Official 1.2 patch. Before I get swamped with emails, I personally used Wesp’s 5.6 "Basic" patch as Wesp kindly granted permission to distribute the patch contents with my mod. http://www.patches-scrolls.de/vampire_bloodlines.php Note that his permission was not granted for you and your mod. If you wish to use an unofficial patch, you must contact the author. NOTES: You CAN and SHOULD install this game twice on the same computer. To do so, simply install the game (and any patches), rename the directory and then use the games uninstaller. Now install again. You will be able to run both copies independently. (If you are a steam user, you may need to make a dos based batch script to rename the base directory so that the version you wish to run gets executed.) ------------------------------------------------------------------------------- II.3 > > > > Uncompressing Game Files\Change Management ------------------------------------------------------------------------------- In the introduction to this section, we discussed the concept of change management: keeping track of what you change now so that you can create a distribution later. You can approach change management in one of two ways - Install the game and manually keep track of the files that you edit Typical strategy for single man teams making "small" mods that don’t touch many files. Unfortunately, most people don’t know how many files they will be touching when they begin development. - Set up software to track the changes for you. With 76,000 support files, this is recommended for anyone planning on making SUBSTANTIAL changes to the game. It is also a must if you plan on sharing development tasks with other people. Unfortunately, setting up software change management can take up to 3 hours even with my step by step instructions. If you are unsure, I recommend installing 2 copies of the game and START by using manual change management. A) Manual change management 1) Install a FRESH COPY of VTMB - Yes, you should uninstall first if it is already installed. - DO NOT run the game after install 2) Install the Official 1.2 Patch - Go to http://www.vampirebloodlines.com/patch/ - Download latest Patch (1.2) - Install, use defaults - DO NOT run the game after install You don’t need to keep track of changes made by the 1.2 patch, but just for your general information and amusement, the 1.2 patch updates the following files: /bin/engine.dll /Vampire/python/vamputil.py /Vampire/python/warrens/warrens.py /Vampire/dlls/vampire.dll /Vampire/cl_dlls/client.dll It also adds the following vpk files: /Vampire/pack102.vpk - Empty /Vampire/pack103.vpk - dlg\Hollywood\isaac.dlg - dlg\main characters\regent.dlg - dlg\santa monica\e.dlg - vdata\hackerterminals\haven_pc.txt - vdata\hackerterminals\shrekhub2_terminal.txt - vdata\system\infobartypes.txt - vdata\system\strings.txt 3) Uncompress VPK files - Run VPKTool - Goto VPK Extractor Tab - Check "0 length wav fix", open "\Vampire\pack000.vpk" - Right Click -> Select All - Right Click -> Extract - Repeat extraction for all VPK files IN ORDER from smallest to largest. ORDER IS IMPORTANT! Some files will report errors, don’t worry. 4) Remove Compiled Python files (.pyc) Python source code is stored in ".pk" files. When a pk file is loaded by the game engine, it automatically compiles it into a .pyc file. The game engine knows to do this based on the time stamp of the files. If the PK file is newer than the PYC file, then the PYC is out of date and needs to be recompiled. The original VPK files don’t contain any .py scripts, but they DO contain .pyc files. The 1.2 Patch installs .pk files, but not pyc. As a result, you have a mix match: 1.2 pk files and 1.0 pyc files. The issue is that the pyc files are 1.0, but have a newer timestamp, therefore the game engine will not compile the fixes. If I have lost you, don’t worry about it. To resolve this mix up, we simply delete all .pyc files under the Vampire/python subdirectory 5) [optional] Install Unofficial Patch (that you have permission to use) - Note, whether it came as a zip or an exe, keep a copy of the original patch around for later reference. 6) [optional] Extract Metadata From Map(bsp) : 3-4 hours To add entities and other items to maps, you will need to edit the map data. This is done by opening a map using VPK Tool, extracting its data (in text form), changing it and then writing it back. If you extract the text data from all the maps into text versions of the maps upfront, you can search the data for examples of how to set up entities/ camera shots, event handlers etc... There are a lot of maps and it takes VPKTool a while to extract the data. So even streamlined, this can take 3 to 4 hours. To streamline the process, I have included a directory called meta with this guide that contains all the map names with the extension .txt. Copy the meta directory to your Vampire/maps directory. Whether you use my included files or make them yourself, once you have a directory of mirrored empty text files, use VPK Tool to open up the binary versions (in the parent directory) one at a time, copying the contents to the text version under meta. If you don’t have the patience, you don’t have to do this step right now. You can extract/save off to the meta directory AS NEEDED. B) Setting up Software Change Management 1) Install tortoiseSVN - Go to http://tortoisesvn.tigris.org/ - Download correct version - Install, use defaults - Restart Computer 2) Create Repository - Open Windows Explorer - Create a new folder and name it. For example: C:\svnrepo (Drive should have at least 10 Gigs of free space) - Right-click on the newly created folder and select: TortoiseSVN -> Create repository here... - Choose a repo type. I used native. Berkeley wont work over a Network drive if you plan on splitting up development tasks with other team members later. A repository is then created inside the new folder. DO NOT EDIT THOSE FILES YOUSELF! 3) Install a FRESH COPY of VTMB - Yes, you should uninstall first if it is already installed. - DO NOT run the game after install 4) Install 1.2 Official Patch - Go to http://www.vampirebloodlines.com/patch/ - Download latest Patch (1.2) - Install, use defaults - DO NOT run the game after install 5) Uncompress VPK files - Run VPKTool - Goto VPK Extractor Tab - Check "0 length wav fix", open "\Vampire\pack000.vpk" - Right Click -> Select All - Right Click -> Extract - Repeat extraction for all VPK files IN ORDER from smallest to largest. ORDER IS IMPORTANT! Some files will report errors, don’t worry. 6) Remove Compiled Python scripts - Traverse /Vampire/python directories and DELETE any .pyc files. (pyc = compiled python files) See Manual Version Control step 4 above if you would like to see explanation 7) Import the VTMB directory into the Repository: - Using Explorer, browser to C:\Program Files\Activision (or equivalent) - Right Click "Vampire – Bloodlines" and select: TortoiseSVN -> Import - Use the browse button [...] to select the REPO : (file:///C:/svnrepo) - Take a nap or something. Import takes about 1.5 hours. 8) Check Out the Code Base: - Right Click "Vampire – Bloodlines" and DELETE it. - Right Click the C:\Program Files\Activision - Select "SVN checkout..." - Update Checkout Directory : C:\Program Files\Activision\Vampire - Bloodlines - It will begin the checkout process. Find something else to do for 1.5 hours. 9) [optional] Install Unofficial Patch (that you have permission to use) - Note, whether it came as a zip or an exe, keep a copy of the original patch around for later reference. - If you install an unofficial patch, you need to commit the changes after it is installed. Commit is like telling the computer to take a "snapshot" of the directory. You can restore to any snapshot at a later time (or compare what has changed) - Revisit the installation directory (C:\Program Files\Activision) - You should see an explanation point icon over "Vampire - Bloodlines" (This means changes have been detected). - Right Click "Vampire – Bloodlines", Select: Tortoise SVN -> Check For Modifications. (Depending on the patch it may take while.) - Click on the "Text Status" Table Header to re-order by that. - Anything marked "non-versioned" is new and has been ADDED by the patch. - Use SHIFT_CLICK to highlight all non-versioned items. Right click your hi-lighted list and select "Add" (Then close dialog with [OK] - Right Click "Vampire - Bloodlines" and select "SVN Commit..." - As message, put name or version of unofficial patch. Hit [OK] 10) [optional] Extract Metadata From Map Files : 3-4 hours To add entities and other items to maps, you will need to edit the map data. This is done by opening a map using VPK Tool, extracting its data (in text form), changing it and then writing it back. If you extract the text data from all the maps into text versions of the maps upfront, you can baseline your map data and use "Diff" functionality to compare changes made to maps. Furthermore, you can search the text based data for examples of how to set up entities/ camera shots, event handlers etc... There are a lot of maps and it takes VPKTool a while to extract the data. Even streamlined, this can take 3 to 4 hours. To streamline the process, I have included a directory called meta with this guide that contains all the map names with the extension .txt. Copy the meta directory to your Vampire/maps directory. Whether you use my included files or make them yourself, once you have a directory of mirrored empty text files, use VPK Tool to open up the binary versions (in the parent directory) one at a time, copying the contents to the text version under meta. If you don’t have the patience, you don’t have to do this step right now. You can extract/save off to the meta directory AS NEEDED. C) Developer Notes : How did I create the meta directory: I installed cygwin and then: $ cd "/cygdrive/c/Program Files/[...]/Vampire/maps/meta" $ touch `ls .. –l | awk '{print $9}'` $ for f in *.bsp ; do mv $f `echo $f | sed 's/\(.*\.\)bsp/\1txt/'` ; done =============================================================================== III > > > > Introduction to Python =============================================================================== VTMB uses a stripped down version of Python 2.1.2. Like JavaScript, Python is a high level programming language that is relatively easy to pick up. Python is used to "glue" the game together. Its role is similar to that of an orchestra conductor. That is, it mostly conducts already existing objects into doing things at the right time. For example, orchestrating a cut scene. I recommend downloading python 2.1.2 if you will be editing the scripts in this game. The Python distribution will install IDLE, a nice Python editor and will also include documentation for that version of the language so that you know what other commands are available to you. However, you do not HAVE to download python. The game comes with a minimal python shell built in. From the console, you can type "import .py" and the game will automatically compile any non-compiled python scripts. If there are any errors, it displays the errors to the console. If you edited one of the python scripts that came with the game (and errors were detected), the game reverts to the original script distributed in the .vpk file. Basic python functionality is included, but advanced python modules such as threading and OS are not. You must be careful not to use the more advanced Python modules from your game scrtips (such as "re") or the game will crash. NOTES: - Python has a special object called None which represents, you guessed it: NOTHING. If used within a conditional, None acts the same way as a Boolean value of false. - Python only receives input from the C++ engine. You cannot capture input directly from the user (without hacky workarounds). - Console commands go strait to the C++ Engine. Only unrecognized commands get sent along to the Python Shell. Be careful when defining console aliases. e.g.: ]a=2 <- python assignment to new variable a ]alias a "echo Hello" <- console alias a executes "echo Hello" ]a=3 <- intercepted by console, results in error. For a quick tutorial on Python (lists, maps, tuples, for loops, method definitions, etc...) I recommend: http://www.diveintopython.org/toc/index.html ------------------------------------------------------------------------------- III.1 > > > > The __main__ Object ------------------------------------------------------------------------------- The VTMB python shell is loaded when you start VTMB. At start up, the system initializes a standard python root level object called "__main__" Here is a list of the VTMB methods made available via the __main__ object. Entity __main__.FindEntityByName(str name) Searches for a single entity on the map with the name specified. If a single entity is found, returns the Entity. If more than one Entity by the same name is found, throws an exception. If no entity is found by that name, returns None. Entity[] __main__.FindEntitiesByName(str name) Searches for any entity by the name specified on the current map. If found, returns an array of Entities. If none are found, returns None. Note : The string may contain the wild card "*": Example: FindEntitiesByName("cop_*") Entity[] __main__.FindEntitiesByClass(str class) Searches for any entity belonging to the CLASS specified on the current map. If found, returns an array of Entities. If none are found, returns None. Character __main__.FindPlayer() Returns an object handle to the Player. If the player does not exist (because a game has not been loaded), returns None. void __main__.ChangeMap(string MapName) Changes Map. See Appendix B for map name listing. You can also type "maps" in the console for a list of maps. Entity __main__.CreateEntityNoSpawn(str class,tpl loc,tpl facing ) First step of creating a new Entity Using Python. This allows you to set the entity up (model, name, location, etc) before actually spawning it with the CallEntitySpawn() method. void __main__.CallEntitySpawn(Entity ptr) Spawns an unspawned entity defined with CreateEntityNoSpawn. void __main__.ScheduleTask(float delay, String Command) Threading support. Executes command in parallel to current thread. You can use a delay of 0.0 if you simply want to fork, or you can use another delay if you want to give the engine time to do something. void __main__.SquadSeesPlayer The idea is that you enter an area with hostiles who don’t attack (because they cant see you). In practice, enemies normally don’t go hostile till a conversation ends or you enter a trigger area. This command is relatively unused. bool __main__.OneOfSet This command is used by dialogs to prevent choices from showing up several times. For example, maybe you have a line that can show up if the person has Persuasion 5 OR Seduction 5. But what if the PC has both? You don’t want the option showing up twice. See the dialog section below (VIII) for more info and an example of use. Here is a list of the variables\properties made available via the __main__ object: __main__.ccmd <- Access to console commands __main__.cvar <- Access to console variables __main__.G <- Global storage (Remembered by Save game). NOTE : __main__.G does not exist until the user starts a new game or loads a save game. ------------------------------------------------------------------------------- III.2 > > > > Executing Python commands from Console. ------------------------------------------------------------------------------- So how do we access this "__main__" object? As mentioned in section I.2, the console is connected directly to the game engine. However, if a command is entered that the engine does not recognize, it is passed along to the python shell. This means you can access python objects and methods from console: ]pc=__main__.FindPlayer() ]pc.GetCenter() Furthermore, if the method is a child of __main__, you do not have to explicitly specify __main__. Though it is a good habit to get into and helps to ensure you don’t conflict with console objects. ]pc=FindPlayer() ]pc.GetCenter() You can also define simple 1 line python methods. ]def hello(s): print "Hello [%s]" % (s) Then you can run your function: ]hello("World") Hello [World] If you use the Python introspection command "dir" to examine __main__’s methods: ]dir(__main__) You will now see hello() as one of __main__’s methods. You will also likely notice a lot more methods than the small list in III.1. This is because most maps in VTMB load a city specific python script with helper functions. Python uses the line return to separate commands and indentation to indicate function blocks (scope). In other words, to create anything beyond a simple 1 line method, you must define your function in an external file and import it. |--------------------------------------------------------------------| |Ex: Filename = [\Vampire\python\custom.py] | |--------------------------------------------------------------------| |import __main__ | | | |def showInstances(prefix="npc_V"): | | entities = __main__.FindEntitiesByClass(prefix+"*") | | print "Class Name" | | print "--------------------------------------------------------"| | for ent in entities: | | name="" | | try: name=ent.GetName() | | except: pass | | if name != "": | | print "%s %s" % (ent.classname.ljust(35),ent.GetName()) | |--------------------------------------------------------------------| Once created, you can import the file using the import command: ]import custom ]custom.showInstances() Class Name" ---------------------------------------------------------" ... ... ------------------------------------------------------------------------------- III.3 > > > > Executing Console commands from Python ------------------------------------------------------------------------------- So now that you know how to use console to execute Python, how do you execute one of those handy console commands from python? Console commands are accessed through the ccmd variable: __main__.ccmd.() Console variables are accessed through the cvar variable: __main__.cvar.=value e.g. : |--------------------------------------------------------------------| |Ex: Filename = [\Vampire\python\custom.py] | |--------------------------------------------------------------------| |import __main__ | | | |def debugMode(): | | __main__.cvar.draw_hud=0 | | __main__.cvar.cl_showfps=1 | | __main__.cvar.cl_showpos=1 | | try: __main__.ccmd.notarget() | | except: pass | | try: __main__.ccmd.noclip() | | except: pass | | try: __main__.ccmd.picker() | | except: pass | |--------------------------------------------------------------------| Bugs and Limitations: The cvar pointer is very useful, however the ccmd pointer is less useful. I have played around with the command and it does not appear as though the C++ handler recognizes parameters. Furthermore, while you can call methods that do not take parameters, any method called always throws an unspecified exception (however, it still works). Therefore you must either place the call within a try catch as I did above, or you can avoid the error message by assigning the function name to a string. This also results in the function being called but avoids sending the error message to console. __main__.ccmd.picker="" Hack/Work Around: Luckily, the ccmd command can be used to invoke custom aliases. If you define a custom alias within Vampire/cfg/autoexec.cfg that executes the contents of a .cfg file: alias execonsole "exec console.cfg" You can use python file io to write commands to the console.cfg file and then execute them using your alias. |--------------------------------------------------------------------| |Ex: Filename = [\Vampire\python\custom.py] | |--------------------------------------------------------------------| |import __main__ | | | | def console(data=""): | | if data=="": return | | cfg=open('Vampire/cfg/console.cfg', 'w') | | try: cfg.write(data) | | finally: cfg.close() | | __main__.ccmd.execonsole="" | |--------------------------------------------------------------------| ] oldname=__main__.cvar.name ] __main__.cvar.name="Yukie" ] import custom ] custom.console("vclan 124") NOTES: vclan can crash the game if the appropriate model is not In precache. Use with caution. ------------------------------------------------------------------------------- III.4 > > > > Entities ------------------------------------------------------------------------------- From Python's perspective, all game objects are Entities: characters, triggers, cameras, even some script sequences are grouped together as entities. They come in 2 flavors: Embedded and Dynamic. Python can be used to manipulate either. A) Embedded Entities As the name implies, Embedded entities are embedded into the map data. These entities must exist before the game is started. You can remove, add or edit embedded entities using the map editing tools provided by VPKTool. After uncompressing VPK files, you will find the Map data under the directory: Vampire/maps. The Maps themselves are also compressed as .bsp files. Using VPKTool, we can futher uncompress the map data into META Data. NOTES: Generally speaking, I will open the map in question, highlight all the metadata and paste it into notepad. As mentioned in the Getting Started chapter, I recommend saving a new file, named after the map (but with .txt extension), possibly within the sub directly "/Vampire/maps/meta". The metadata is basically a laundry list of object declarations. It is pretty easy to read, but it is MASSIVE and can be overwhelming at first. What is so special about Embedded Entities? Embedded entities provide FULL ACCESS to all the properties and events that an entity supports. The game engine does some things when a map loads that can only be done at that time. For example, hooking up events. For this reason, you MUST USE embedded entities if you need to receive events or set some non-accessible properties/attributes. If you need user Interaction (Dialog), you MUST use Embedded Entities. B) Dynamic Entities Dynamic entities are not known to the engine when the map loads. Since some things can only be done when the map loads (setting up events), there are limitations on what can be done with dynamic entities. When you create a dynamic entity, it will receive default values for all of its' properties. Some of these can be changed. For example, you can change the model of a dynamically created NPC because there is a method called "SetModel" which provides access to that property/attribute. However, you can NOT change the dialog that an NPC speaks because there is no method for changing an NPC's associated dialog file. For those properties that can not be changed, you are stuck with the default values. The inability to change/set all properties is irritating and in some cases make things impossible. For example, since you can't set the dialog of a dynamic NPC, you can't dynamically create NPC's that the PC can talk to. If you want dialog interaction, you have to use embedded entities. Other notable limitations/irritations: - Dynamically spawned npc_V* classes have no clipping area (you walk through them like ghosts). I believe this is because the default "solid" property is 0 or SOLID_NONE. Luckily other entity types such as props ARE solid by default. - You can’t define event handlers for Dynamic entities such as "OnDeath" - Some entities have properties that must have valid values in order to successfully spawn. If the default value is invalid and you can't set it, then you effectively can't create that entity dynamically. Example : npc_maker C) Entity Scope Embedded or Dynamic, entities are inherently local to the map they are defined/created on. The GetName\SetName method mentioned in III.4 implies all Entities have a name. All entities CAN have a name, but they do not necessarily have a name, nor are their names necessarily unique. A game/map designer can guarantee that within a particular map, a specific entity name exists and is unique, but that is about it. Luckily, that is generally all you need to ensure your camera sequences and cut scenes work correctly. That said, if you are modding the game, you must be careful not to create a new Entity with the same name as an existing entity within the map, else you may cause Unexpected behavior. ------------------------------------------------------------------------------- III.5 > > > > Creating Entities with Python ------------------------------------------------------------------------------- Spawning items from Python is a 3 step process: Create, Set Properties, Spawn A) Create : The "Creation" step is done with the method: __main__.CreateEntityNoSpawn(String classname, Location, Facing) The function takes 3 parameters: classname : This is the main parameter. It is a string value which determines what additional attributes, properties and methods the entity INSTANCE will have. See Appendix A for a list of known classname Strings. For this example, we will use npc_VVampire. Location : A 3 float tuple. representing x,y and z coordinates. Most People use the Player’s current location for the value. ex: myloc = (1.0,2.0,3.0) or myloc = __main__.FindPlayer().GetOrigin() Facing : Yet another 3 float tuple. The first float Represents angle, up or down, that the character is looking. (are you looking up at the ceiling or down at the floor). The second is what direction they are facing right to left. The 3rd is forwards/backwards motion. ex : myFacing = (0.0, 180.00, 0.00) or myFacing = __main__.FindPlayer().GetAngles() Code Example: pc = __main__.FindPlayer() loc = pc.GetOrigin() ent = __main__.CreateEntityNoSpawn("npc_VVampire", loc, (0,0,0) ) B) Set Properties Some Entities have REQUIRED properties or the call to spawn will fail. NPCs require that you set a model value for example. ent.SetModel("models/character/npc/unique/downtown/vv/vv.mdl") ent.SetName("myEnt") In making this, I didn’t have the time to go through and test each entity type for required parameters, so you will just have to figure it out as you go. When you try to spawn an entity, you will receive errors messages telling you what must be set to proceed. In attached Appendix G, I provide a list of properties and methods for each specific classname. However, those methods do not include methods inherited from parents. For example, npc_VVampire extends a C++ base Class (referred to as the Proxy Class) which extends the Python Character class which extends the Entity Class: Each of these "layers" adds more methods to the final instance. Methods provided by the npc_VVampire classname: AllowAlertLookaround AllowKickHintUse BarterBegin ChangeMasqueradeLevel AllowOpenDoors BarterEnd ClearActiveDisciplines ChangeSchedule Bloodgain FadeHeadAsCameraTarget ClearPatrolPath BloodHeal FadeBodyAsCameraTarget FollowPatrolPath Bloodloss LookAtEntityCenter FrenzyTrigger DisableThink LookAtEntityDefault FrenzyUpdate Faint LookAtEntityOrigin Inventory_Remove FadeToSkin pl_criminal_attack LookAtEntityEye FleeAndDie pl_supernatural_attack MakeInvincible FrenzyCheck pl_supernatural_flee pl_criminal_flee HumanityAdd SetBloodShieldDiscipline pl_investigate HungerCheck SetBodyAsCameraTarget physdamagescale MoneyAdd SetDefaultDialogCamera PlayDialogFile MoneyRemove SetDontFacePlayerInDialog SetBodygroup MoveToDest SetHeadAsCameraTarget SetBossMonster MoveToHome SetInvestigateMode SetFallToGround PlayFloat SetInvestigateModeCombat SetFollowerBoss RotateToDest SetMovementMultiplier SetFollowerType RotateToHome SetScriptedDiscipline SetRelationship skin SpawnTempParticle SetSkinFadeTime TakeDamage StartPlayerDialog SetSpeechVolume TweakParam StartPlayerDialogRemote SetupPatrolType WalkToNode StartPlayerDialogUnforced StayEntrenched WillTalk TeleportToEntity UseInteresting Methods provided b the Proxy base class: Alpha ClearParent Kill Color ScriptHide SetFakeSilence SetParent ScriptUnhide SetSoundOverrideEnt Use Methods provided by the Character base class: NOTE: From this point down, ALL npcs have these methods: GiveAmmo AmmoCount GiveItem BumpStat HasItem DialogDiscipline IsFollowerOf GetQuestState RemoveItem HasWeaponEquipped SeductiveFeed IsMale SetCamera React SetDisposition SetQuest SetExpression StartBarter SetGesture WorldMap Methods Provided by the Entity base class: NOTE: From this point down, ALL entities have these methods: GetAngles GetCenter GetAngleVectors GetModelName GetName GetOrigin IsAlive SetModel SetAngles SetOrigin SetName Methods Provided by the Python PyObject base object (part of the language spec). NOTE: All python objects support these methods: __init__() __getattr__() __setattr__() __dict__() __doc__() Attributes: All entity INSTANCES have attributes, however attributes are read- only. If you spawn an entity and set its name. e.g. : ent.SetName("test"), You can use the console to see its attributes : ent_dump "test" This will dump all attributes to the console. You can then read the attributes using dot notation. ]ent.classname ‘npc_VVampire’ Some entities have a method called TweakParam() which will allow you to edit select attributes. If you scan the source code, you will see it only used to change vision, hearing and squad. C) Spawn: Once all desired/required properties are set, we spawn the object with the command: __main__.CallEntitySpawn(ent) It is important to note that despite all the methods above, not all npc_VVampire class attributes are being exposed. For example, there is a "dialogname" attribute which allows dialogs, but no method for setting it from python. Therefore, we can not dynamically create an NPC that can have a dialog with the PC using the standard dialog engine. In the case of npc_maker, the class requires you to set an NPCType attribute to spawn, however there is no setter from python. Therefore the entity can’t be created dynamically at all. See Section XII : Common Scenarios, for a more robust spawn example function. ------------------------------------------------------------------------------- III.6 > > > > Events ------------------------------------------------------------------------------- The game engine supports hundreds of events. However, only embedded entities receive events. I will tell you upfront that there is no way to receive or handle game events from python ALONE. If you wish to receive/handle events you will HAVE to work with events already established or embed new entities into the maps. A) ENTITIES: The vast majority of entities only handle events meant for the entity itself. For example, if you embed an npc_Vhuman (see Attached Appendix G), you can set it up so that it receives an OnDialogBegin event and calls a python method: { "classname" "npc_VHuman" "targetname" "customNPC" ... "OnDialogBegin" ",,,0,-1,OnBeginDialog('customNPC')," } That is rather strait forward, but what about the awkward string with commas? Here is a break down of the event protocol; ",,,,,," : Can be any named entity within the same map. The name should NOT be in quotes. : Name of a valid method on the target entity to fire. Each entity supports different methods. See attached Appendix G for a full listing. : Optional parameter to pass into the method. If the method requires more than 1 parameter, you have to use the option. : Delay in seconds before calling the method. Can be floating point value. : As the name implies, maximum number of times the event will fire. A value of –1 means there is no maximum and it will always fire. Call a global or module specific python method. Example: To create an opponent that begins a conversation with you when They Lose half of their health the first time, but not a second time: { "classname" "npc_VVampire" "targetname" "customNPC" ... "OnHalfHealth" " customNPC, StartPlayerDialogRemote,,0,1,," } To activate multiple methods, you simply embed the event handler multiple Times. Here we disable any vampire disciplines when we start the convo. { "classname" "npc_VVampire" "targetname" "customNPC" ... "OnHalfHealth" " customNPC, StartPlayerDialogRemote,,0,1,," "OnHalfHealth" " customNPC, ClearActiveDisciplines,,0,-1,," } B) Player Events: It is cool that you can receive events about entities you create, but what about the player? You don’t exactly create the player? There is a special entity called events_player that you can plug into a map to receive player events. (From Appendix G): events_player EVENTS: output: OnFrenzyBegin output: OnFrenzyEnd output: OnWolfMorphBegin (Animalism War Form) output: OnWolfMorphEnd (Animalism War Form) output: OnPlayerTookDamage output: OnPlayerKilled output: OnPlayerSoundLoud output: OnActivateAuspex output: OnActivateCelerity output: OnActivateCorpusVampirus (Blood Buff) output: OnActivateFortitude output: OnActivateObfuscate output: OnActivatePotence output: OnActivatePresense output: OnActivateProtean output: OnActivateAnimalismLvl1 output: OnActivateAnimalismLvl2 output: OnActivateDementationLvl1 output: OnActivateDementationLvl2 output: OnActivateDominateLvl1 output: OnActivateDominateLvl2 output: OnActivateThaumaturgyLvl1 output: OnActivateThaumaturgyLvl2 METHODS: input: EnableOutputs input: DisableOutputs input: CreateControllerNPC input: RemoveControllerNPC input: AwardExp input: ClearDialogCombatTimers input: ImmobilizePlayer input: MobilizePlayer input: RemoveDisciplines input: RemoveDisciplinesNow input: MakePlayerUnkillable input: MakePlayerKillable C) World Events Like events_player, the events_world object lets you key into certain global events: (From Appendix G): events_world EVENTS: output: OnCopsOutside output: OnCopsComing output: OnStartCopPursuitMode output: OnEndCopPursuitMode output: OnStartCopAlertMode output: OnEndCopAlertMode output: OnStartHunterPursuitMode output: OnEndHunterPursuitMode output: OnMasqueradeLevel1 output: OnMasqueradeLevel2 output: OnMasqueradeLevel3 output: OnMasqueradeLevel4 output: OnMasqueradeLevel5 output: OnMasqueradeLevelChanged output: OnPlayerHasNoBlood output: OnCombatMusicStart output: OnCombatMusicEnd output: OnAlertMusicStart output: OnAlertMusicEnd output: OnNormalMusicStart output: OnNormalMusicEnd output: OnUseBegin output: OnUseEnd input: SetSafeArea input: SetCopWaitArea input: SetCopGrace input: SetNosferatuTolerant input: SetNoFrenzyArea input: AIEnable input: FadeGlobalWetness input: HideCutsceneInterferingEntities input: UnhideCutsceneInterferingEntities input: PlayEndCredits input: ClearDialogCombatTimers Of all the events, the two I personally found the most useful were OnCombatMusicStart and OnNormalMusicStart. These (not so well named events) basically signify the start and end of combat. If you maintain an "InCombat" flag, you can also use OnBeginNormalMusic to indicate when a map has been loaded from a save game. D) Other/Misc Events You may have noticed there are a few events missing, like entering a map, the player going stealth or the player picking up an item. I wont pretend to possess a comprehensive knowledge of all events, but I can tell you about what I have discovered and some workarounds I have created. For entering maps, you can embed a logic_auto entity: { "classname" "logic_auto" " spawnflags" "0" "OnMapLoad" ",,,0,-1,OnEnterMap('sm_hub_1')," "origin" "-2420.54 -2558.76 -111.97" } ** ALL Maps (.bsp) already have a logic_auto entity embedded into them which loads a python script associated with the city that the map is located in. Hence the subdirectories under Vampire/Python. Note that OnEnterMap will only fire when actually traversing into a map from another map. Loading a save game will not cause the event to fire. However, when you load a save game, OnBeginNormalMusic will still fire. For detecting item pickup, you can use a trigger_inventory_check: { "classname" "trigger_inventory_check" "targetname" "inventory_check" "StartDisabled" "0" "spawnflags" "1" "itemname" "item_w_tire_iron" "OnPlayerHasItem" "inventory_check,Disable,,0,-1,," "OnPlayerHasItem" " popup_35,OpenWindow,,0.5,-1, ," } Some events are not supported. For example, detecting that the player has gone stealth (crouches). You can design workarounds for some. For stealth, you can poll certain states using a logic_timer entity: { "classname" "logic_timer" "StartDisabled" "0" "UseRandomTime" "0" "RefireTime" "15.0" "OnTimer" ",,,0,-1,OnPollEvent()," "origin" "-2420.54 -2558.76 -111.97" } (In vamputil.py) stealth=0 def OnPollEvent(): global stealth pc = __main__.FindPlayer() crouched = ((pc.GetCenter()[2] - pc.GetOrigin()[2]) == 18) if not stealth: if (pc.active_obfuscate or crouched): stealth=1 OnStealthBegin() else: if (not pc.active_obfuscate and not crouched): stealth=0 OnStealthEnd() E) Events to Avoid: There isn’t an easy to detect leaving a map as most maps have multiple exit points. The PC may be teleported away to a different map by a door, an area trigger, a conversation. There about half a dozen ways to exit an area and you would have to search and update every entity on the map and every dialog associated with every entity on the map. Even then you may miss something embedded in a python event script. In general, I would avoid designing a mod that relies on the ability to detect when the player leaves an area. Globally detecting events that are normally defined on a per-entity basis is generally a no no. For example, detecting when things die. That would involve lots of map edits. The more stuff you edit, the greater the chance for bugs. D) Companion Mod I don’t mean to plug my mod, but as I built my companion mod it occurred to me that others may want to make mods as well (hence this guide). I added the events described above to pretty much every map. But instead of having them call my specific functions, I had them call global methods that I defined in vamputil.py. That way other mod developers could hook into those events easily. If you are doing something small that only impacts 1 map, you will probably want to keep your mod small and only make the edits to that map But if your goals are larger and you need events like I describe above throughout the entire game, you may consider downloading my mod and starting from there as a lot of the groundwork has already been done. ------------------------------------------------------------------------------- III.7 > > > > Input\Output ------------------------------------------------------------------------------- In general, direct user input/output from python is a no-no. I describe some workarounds below, but most of these are theoretical and have not been tested. A) INPUT: Within VTMB, input is generally done using dialogs. You enter a conversation with someone, make some choices and the dialog system fires some of your python methods as a result. VTMB does not support receiving user input DIRECTLY into python. There is a hacky work around, but I don’t recommend it. Still I will describe it below for completeness: Building upon the console hack I described in section III.3, you can save off the users current configuration, rebind keys to python methods to pass key press events into python data ="host_writeconfig backup.cfg\n" data+="unbindall\n" data+=’bind "1" "OnInput(\’1\’)"\n’ data+=’bind "2" "OnInput(\’2\’)"\n’ data+=’bind "3" "OnInput(\’3\’)"\n’ data+=’bind "4" "OnInput(\’4\’)"\n’ data+=’bind "ENTER" "OnInputFinish()"\n’ data+=’bind "ESC" "OnInputCancel()"\n’ console(data) and then later, when you are done, restore the keyboard config: data="exec backup.cfg" console(data) The primary danger with this is if the user exits the game before finishing your home-made user input prompt, they may lose their configuration. There are ways to mitigate the danger (look for backup.cfg within vamputils and restore/delete if found). Even with a mitigation strategy, ultimately this is a kludge. Hopefully you can design your mod so that you do not need user input beyond what can be gained using a normal dialog file. B) OUTPUT: I am aware of 4 ways of getting data to the user. 1) hud_timer objects can be used to show time or any number to the user that can fit within a clock like sequence (00:00:00). For example, I used this in my companion mod to show hits remaining when you possess a traveling companion or how much time is left before you can re-feed. FYI : There is a HUD Counter, but I could never get it to work. 2) game_sign objects rely on files which describe a background and text. Typically, the files are pre-fabricated within your vdata directory and game_sign entities that reference the files are embedded into maps. You call the OpenWindow() method on them to make them appear. One downside of a game sign is that the game pauses while the sign is up. 3) If you look up the game_sign entity in Appendix G, you will notice that it has a ChangeFile() method on it. Using Python file io, you can dynamically construct a sign file, save it off, create a game_sign entity on the fly (doesn’t have to be embedded), Change the file to the temp file you created and then call open window. This elaborate work around would allow you to open professional looking text presentation windows to the user from python without edits to the map files. Combined with the (mitigated) input hack above, you could simulate a decent looking user prompt. Bear in mind that game signs pause the game. So If you were to use this hack to create your own dialog system, you wouldn't be able to animate NPCs for emotion, nor would their mouths move if you told them to speak a dialog line. There may be other uses for the hack, but I haven’t thought of any yet. 4) For simple feedback, you can use the console hack in conjunction with the "say" command to have text print to the upper left corner of the hud. The text will appear to have been said by the current value of __main__.cvar.name. ------------------------------------------------------------------------------- III.8 > > > > Bringing it all together ------------------------------------------------------------------------------- So we can call both python AND console commands. We can access both python and console variables. This means you can tackle most tasks using one approach or the other. If you get stuck or run into a bug, you have a backup plan. For example, take the vclan console command. If you issue that command and the clan tries to load a model that isn’t currently in memory, you get a pre-cache error and the game crashes. This holds true for other commands like npc_create and the console version of SetModel. However, if you change the pc model using Python, the model is loaded into memory and the crash does not occur. In the case of creating new objects, you can use python to be more precise about What you want. You can assign a name, location, facing, initial model, hearing, vision, squad and health. (health is done by changing the cvar sk_basenpctroika_health variable before and after spawn) A) Useful Console Commands (to call from python) autosave self explanatory shake brief earthquake (think explosion) player_immobilize/player_mobilize self explanatory fadein/fadeout self explanatory vclan : change clan of protagonist. Also resets stats to 1/0 npc_freeze : "Freezes" npc under crosshair. You can then search all npcs (examine npc.playbackrate) to discover which one the PC is looking at Nice work around for "grappling" NPCs from python. teleport_player "entity_name" Needed when you wish to teleport the PC somewhere and set their facing. Bear in mind that pc.SetOrigin() also works for teleportation within a map, but pc.SetAngles() does not. (all the time) This console command correctly changes the facing. B) Useful Player Objects and Commands ]pc=__main__.FindPlayer() Enables the following commands: ]pc.WorldMap(int WorldMap_State) Opens up the world map city chooser for teleporting to various cities. WorldMap states vary thoughout the game. The "up to date" value is generally maintained by __main__.G.WorldMap_State , however you can enter whatever value you want. ]pc.BumpStat(string "attribute_name" , int value) Can be used to RAISE pc stats. Negative values are ignored. NOTE: In order to lower a stat, you must "reset" the player using the console command vclan and then restore stats. You can get the players current clan number using "pc.clan" ]pc.GiveItem(string "item_name") Give yourself an item, provided you have the patients to look up its item code. The console version "give" is actually nicer as it auto-lists all the built in item codes. Examples: item_w_deserteagle item_w_flamethrower item_w_rocketlauncher item_w_fireaxe item_p_occult_dexterity ]pc.giveAmmo(string "item_w_...",int amount) Name pretty much says it all. It should be noted that giveAmmo works on stackable items such as bloodpacks. ]pc.SetModel(string "model_path") Unlike the console version, this one wont crash the game if the model is not already in memory. ]pc.HumanityAdd(#) Works for both positive and negative numbers. ]pc.Bloodgain(#) Gives PC blood. When the game starts, the PC has a maximum blood pool of 15 ]pc.Bloodloss(#) Take blood away from PC. ]pc.MoneyAdd(#) Gives PC money ]pc.BloodHeal Activates the bloodheal discipline even if you don’t have it. Heals a small amount of health for some of your blood pool points. ]pc.ChangeMasqueradeLevel(#) Accepts both positive and negative values. Negative is good. Positive is bad. ]pc.CalcFeat(string type) Feat values are the result of skills, attributes and items. This function does the calculation for you. Example: if pc.CalcFeat("haggle") == 3 NOTES: Additional pc controls can be installed on a per map basis by injecting an events_player object into a map (if one is not found). See III.5.B above. If you name the PC: ]__main__.FindPlayer().SetName("pc") you can retrieve a list of PC attributes using the ent_dump command: ]ent_dump "pc" ------------------------------------------------------------------------------- III.9 > > > > Known Bugs ------------------------------------------------------------------------------- Make no mistake, VTMB is a C++ based game. The engine is not written in Python and the objects exposed to python are merely proxies to the actual objects in C++. Your control over the objects is limited to what the game engine exposes to you (via the input methods). This confused me at first as I thought I could set/change every attribute I saw using the ent_dump command from Python. This simply isn’t true. (Though you can use vstats to change many of the player attributes). Bugs So I built a mod and then during testing, the game kept crashing when the player would change maps. 2 days later, I traced the bug down. Here was the problem: __main__.G.somestring=FindPlayer().GetName() <-- OK __main__.G.someint=FindPlayer().clan <-- OK __main__.G.somefloat=FindPlayer().GetOrigin()[2] <-- OK __main__.G.someinstance=FindPlayer() <-- NOT OK The issue here is that FindPlayer() returns a runtime instance. The same can be said for FindEntityByName, FindEntitiesByName and FindEntitiesByClass. __main__.G gets saved with your save game. While you can store properties of entities as part of a save game, you can not save off any instance pointers or you will crash the game. This rule holds true for G and sub attributes of a G (hash maps, lists, etc) or Entities attributes as they are saved off as part of the save game. Module scope global variables and local scope variables are NOT saved as part of the save Game and are therefore safe: You temporarily store instances references. The "G" Delimma: Vamputil.py gets loaded with the game and its methods are available from every map, so it makes sense to add your module imports there. However, there is a snag. Vamputil gets imported by the game before the game has set up the persistent "G" variable. This causes 2 issues. ISSUE 1: Take this scenario: VAMPUTIL.PY: import foo FOO.PY: from __main__ import * def somefunction(): G.persist = "1" The issue is that when the game imported foo, G didn’t exist. So when foo imported * from __main__, it didn’t get G. when the function gets called, an exception will be thrown. Two possible workarounds. One is to import G locally from methods that access it. def somefunction(): from __main__ import G G.persist = "1" The other work around is to ONLY import __main__ and not import anything from It. Reference elements of __main__ explicitly from the code. This causes a runtime lookup and avoids the exception. FOO.PY: import __main__ def somefunction(): __main__.G.persist = "1" ISSUE 2: If a variable does not exist on G, that is fine. You can test for the variables using a simple if statement: If not __main__.G.somevariable: __main__.G.somevariable=somethingelse However, complex data structures will throw exceptions. For example If not __main__.G.somehasmap["valiue"]: exception thrown Many times a module needs to initialize and setup lists and hashmaps managed by the module when the player first starts the game. Of course we have the issue above: G doesn’t exist when vamputils loads. So initialization needs to happen from a secondary method that happens after vamputils loads. There are 2 solutions. First solution is to embed a logic_auto entity in one of the early maps (sm_pawnshop_1.bsp -> Your apartment at the beginning of the game) and have it call your initialization method there. { "classname" "logic_auto" "spawnflags" "0" "OnMapLoad" ",,,0,1,yourmodule.initialize()," "origin" "-2420.54 -2558.76 -111.97" } The second solution is similar to the first. But instead of installing your own logic_auto entity, you simply add your code to python/santimonica/santimonica.py which basically gets loaded by a logic_auto entity that is already installed in the map. Minor Invincibility bugs: There are 4 ways of making the pc invincible. You can use the console command "buddha". This allows the PC to get down to 1 hit point, but not actually die. There is an events_player method "MakePlayerUnkillable()". This allows The PC to get down to 1/4 their total hit points, but they will not go lower. There is the console command "god" which prevents the player from taking any damage period. And then there is the console variable debug_no_damage, which when set to true prevents anyone from taking damage (even enemies). Of these four methods, the first two only work if the PC is one of the 7 standard clans. If you use vclan to change to a non-standard clan, then buddha and MakePlayerUnkillable will not stop the PC from dying. =============================================================================== IV > > > > Dialogs =============================================================================== ------------------------------------------------------------------------------- IV.1 Dialog Basics ------------------------------------------------------------------------------- So we have talked about Python and writing scripts, but how do we activate those scripts? The most common way within this game is a dialog. A) Where are they? Dialog files end with the extension ".dlg". Once you unpack the VPK files, you will find the Dialog files for the various game characters located under Vampire/dlg/... B) How are dialogs made accessible in game? Dialogs must be connected to an embedded game entity. The only way to add a new dialog to the game is to embed a new entity that links to your new dialog. Alternatively, you can edit an existing dialog that is already connected to an existing game entity. If you are new to this and you aren't comfortable messing with map files just yet, the second approach is a good way to get started. C) Can I change dialogs while the game is running? YES! You can!. While python scripts are compiled once per game (and then cached), dialogs are loaded into a special dialog engine each time you start a dialog. So if you make edits to a dialog, the next time you start the dialog, the edits will immediately be seen. ------------------------------------------------------------------------------- IV.2 DLG File Format ------------------------------------------------------------------------------- If you open up a dialog file, you will see a 13 column table defined with "{" and "}": { }{ }{ }{ }{ }{ }{ }{ }{ }{ }{ }{ }{ } The FIRST column is the row index. The first row should always have an index value of 1: {1}{ }{ }{ }{ }{ }{ }{ }{ }{ }{ }{ }{ } {2}{ }{ }{ }{ }{ }{ }{ }{ }{ }{ }{ }{ } {11}{ }{ }{ }{ }{ }{ }{ }{ }{ }{ }{ }{ } Not all row numbers have to be accounted for (There can be skips to allow people to add responses), however, all rows should appear in descending order. The SECOND column is what the NPC/PC says if the Protagonist is Male {1}{ Male : NPC Statement}{ }{ }{ }{ }{ }{ }{ }{ }{ }{ }{ } {2}{ Male : PC Response 1}{ }{ }{ }{ }{ }{ }{ }{ }{ }{ }{ } {3}{ Male : PC Response 2}{ }{ }{ }{ }{ }{ }{ }{ }{ }{ }{ } The THIRD column is what the NPC/PC says if the Protagonist is Female # MALE FEMALE {1}{Male}{ Female : NPC Statement}{ }{ }{ }{ }{ }{ }{ }{ }{ }{ } {2}{Male}{ Female : PC Response 1}{ }{ }{ }{ }{ }{ }{ }{ }{ }{ } {3}{Male}{ Female : PC Response 2}{ }{ }{ }{ }{ }{ }{ }{ }{ }{ } The FOURTH column contains an integer value which redirects to another row within the dialog file if the PC chooses that response. The special character # means it is an NPC statement # MALE FEMALE NEXT {1}{NPC Male}{NPC Female}{ # }{ }{ }{ }{ }{ }{ }{ }{ }{ } {2}{PC Male }{PC Female }{ 5 }{ }{ }{ }{ }{ }{ }{ }{ }{ } {3}{PC Male }{PC Female }{ 7 }{ }{ }{ }{ }{ }{ }{ }{ }{ } {4}{ }{ }{ }{ }{ }{ }{ }{ }{ }{ }{ }{ } {5}{NPC Male}{NPC Female}{ # }{ }{ }{ }{ }{ }{ }{ }{ }{ } {6}{ }{ }{ }{ }{ }{ }{ }{ }{ }{ }{ }{ } {7}{NPC Male}{NPC Female}{ # }{ }{ }{ }{ }{ }{ }{ }{ }{ } When scanning for responses, the engine stops when it hits a row with a "#". The blank row is not necessary, but has been added to improve readability. The FIFTH column is used for conditions. You can use python or one of the built in special conditions (See IV.2 below for Special Conditionals) The engine supports "and" logic operations using "and" {1}{M}{F}{5}{pc.armor==3 and npc.classname=="npc_VVampire"} The dialog engine supports "or" logic operations using "or", but the entire condition must be contained in parenthesis: {1}{M}{F}{5}{(pc.armor==3 or npc.classname=="npc_VVampire")} You wont see many "or" statements throughout the dialogs because you can achieve the same effect by using 2 lines. {1}{M}{F}{5}{(pc.armor==3} {2}{M}{F}{5}{(npc.classname=="npc_VVampire"} But what if both conditions are true? You don't want the same dialog line appearing twice. To solve this issue, the game engine provides a special command called "OneOfSet)". It is made specifically for the dialog engine and ensures only 1 dialog out of several will appear (the first one to evaluate true). {1}{M}{F}{5}{(pc.armor==3 and OneOfSet(1,2)} {2}{M}{F}{5}{(npc.classname=="npc_VVampire" and OneOfSet(2,2)} The SIXTH column allows you to execute 1 line of python script. If it is an NPC statement, the script will execute when the NPC says the statement. If it is within a choice, the script will only execute if the player chooses the choice. Example Script: (In vamputils.py) def pcFollow(npc): npc.SetFollowerBoss("!player") def pcNFollow(npc): npc.SetFollowerBoss("") Example Dialog: {1}{NPC}{NPC}{#}{ }{ }{}{}{}{}{}{}{} {2}{sit}{sit}{1}{npc.IsFollowerOf(pc) }{pcNFollow(npc)}{}{}{}{}{}{}{} {3}{go }{go }{1}{!npc.IsFollowerOf(pc)}{pcFollow(npc) }{}{}{}{}{}{}{} {4}{end}{end}{}{ }{ }{}{}{}{}{}{}{} {5}{ }{ }{ }{ }{ }{}{}{}{}{}{}{} The REMAINING COLUMNS (7-13) provide specific responses based on the PC's class. Column 7 = Brujah Column 8 = Gangrel Column 9 = Nosferatu Column 10 = Toreador Column 11 = Tremere Column 12 = Ventrue Column 13 = Malkavian Note that using these columns does not allow gender specific responses Which is one of the reasons they are not utilized more throughout the game. (If you want to use these, you can still SIMULATE gender specific responses by using starting conditionals. See IV.4). ------------------------------------------------------------------------------- IV.3 Special Conditions and Colored Responses ------------------------------------------------------------------------------- The special conditionals include: F_Seduction # M_Seduction # Seduction # Intimidate # Dominate # Persuasion # Dementation # Thaumaturgy # Humanity # Special conditions mean that the ability is equal to or greater than the number. Most result in responses with special colors/fonts. If the number is negative, then it means less than (the positive value of) the number and the font/color is not applied. You can perform "and" Boolean logic with the special conditionals, but you must use the special "&" operator: {1}{M}{F}{5}{Seduction 3 & pc.armor==3 and npc.class=="npc_VVampire"} You can NOT use "or" logic conditionals with the special conditions, however you can use separate lines and the "OneOfSet" command to simulate or conditionals: {1}{M}{F}{5}{(Seduction 5 & OneOfSet(1,2)} {2}{M}{F}{5}{(Persuation 5 & OneOfSet(2,2)} NOTES : The Dominate condition normally only appears if the player is Ventrue. If you wish to have dominate options appear for Tremere as well, there are two approaches: 1) The best approach is to simply include a Thaumaturgy based response that also depends on a minimum dominate ability. You wont hear the domination sound and the blood points wont be subtracted, but the response will still be red. -------------------------------------------------------------------- {5}{M}{F}{9}{ Dominate 4 }{... {6}{M}{F}{9}{ Thaumaturgy 1 & getattr(pc,"base_dominate")>3 }{... 2) An alternate approach uses a dialog engine bug you can exploit: -------------------------------------------------------------------- {5}{M}{F}{9}{ Dominate 4 {5}{M}{F}{9}{ getattr(pc,"base_dominate")>3 and IsClan(pc,"Tremere"} The main 2 things to note here is that BOTH LINES HAVE THE SAME LINE NUMBER BUT ONLY 1 LINE EVALUATES TO TRUE. When the game engine parses the file, it scans the conditions and the text Content in two separate scans. When 2 lines have the same number it confuses the engine and treats both lines the same. From my experience, it seems the number of lines from the top of the file (not the line number or which condition is actually true) dictate which line controls how the text displays for both responses. Sometimes it is the top and sometimes it is the bottom. You have to test and possibly swap to get this trick to work. Unlike the previous approach, not only will the response be red, it will execute the dominate animation and produce the dominate sound when selected. It will even subtract the correct number of blood points. The really interesting thing about this bug is that it doesn't matter if the PC even has the dominate ability. All that matters is that only 1 condition evaluates to true and both lines have the same number (this can be used with any of the special abilities to color lines). I hesitated to even mention this exploit because the problem with it is that overuse quickly makes a mod un-maintainable. Dialogs containing this exploit are super-sensitive to editing. If someone adds new lines above your condition, the engine may suddenly begin using the wrong line to display. Having to re-test ALL the conditions every time the dialog is edited is cumbersome to say the least. So I recommend the first method, unless you are squeezed for space and only have room for 1 additional line number. ------------------------------------------------------------------------------- IV.4 Dialog Engine Commands and Flow Control ------------------------------------------------------------------------------- A) AUTO-END The auto-end command will automatically end the dialog if the command is reached. It is embedded in the response section surrounded by brackets. Below, there nothing but a single AUTO-END in the "Everyone" column, so the dialog will always end after the NPC's statement. {201}{(STATEMENT MALE)}{STATEMENT FEMALE }{#}{ }{}{}{}{}{}{}{}{} {202}{(AUTO-END)}{}{0}{}{}{}{}{}{}{}{}{} In the second example below, the conversation will end unless the PC is a female. {201}{(STATEMENT MALE)}{STATEMENT FEMALE }{#}{ }{}{}{}{}{}{}{}{} {202}{(AUTO-END)}{I Don't Know}{0}{}{}{}{}{}{}{}{}{} B) AUTO-LINK The auto-link command will automatically link to a new line in the dialog when it is reached. Below, there nothing but a single AUTO-LINK in the "Everyone" column, so the dialog will link to the next NPC statement. {201}{(STATEMENT MALE)}{STATEMENT FEMALE }{#}{ }{}{}{}{}{}{}{}{} {202}{(AUTO-LINK)}{}{211}{}{}{}{}{}{}{}{}{} Here is an advanced example. If the npc is following the pc and female, you will see a "no" response. If the npc is following the pc and male, the dialog forwards to 211. If the npc is NOT following the pc and the pc is male, you autolink to 221, otherwise you end. {201}{(STATEMENT MALE)}{STATEMENT FEMALE }{#}{ }{}{}{}{}{}{}{}{} {202}{(AUTO-LINK)}{No}{211}{pc.IsFollower()}{}{}{}{}{}{}{}{} {202}{(AUTO-LINK)}{}{221}{pc.IsMale()}{}{}{}{}{}{}{}{} {202}{(AUTO-END)}{}{0}{ }{}{}{}{}{}{}{}{} C) "..." "..." is a reserved NPC statement that basically represents no response. It mostly comes into play when an NPC statement does not have an associated audio file (discussed later). Normally when an NPC statement does not have an associated audio file, the text displays on the screen. Essentially the engine turns on sub-titles for the remainder of the dialog. To be certain that the player had a chance to read the text, the engine will prompt the user even if the dialog would normally end. For example, the following would show the NPC saying ".." and prompt the user to hit 1) to continue (if line 201 did not have any associated audio): {201}{.. }{.. }{#}{ }{}{}{}{}{}{}{}{} {202}{(AUTO-END)}{}{0}{ }{}{}{}{}{}{}{}{} However, 3 periods and the dialog would end without prompting the user, even if there was no audio. {201}{... }{... }{#}{ }{}{}{}{}{}{}{}{} {202}{(AUTO-END)}{}{0}{ }{}{}{}{}{}{}{}{} D) STARTING CONDITION At the bottom of most dialog files, you will find many lines marked as (STARTING CONDITION). When a dialog is loaded, the dialog engine searches for a block of STARTING CONDITIONALS at the end of the dialog. They are evaluated from top to bottom. As soon as the first condition evaluates to true, the engine is redirected to the NPCs first line. Note that you can not chain STARTING CONDITIONS (they can not redirect to another starting condition block. Example: ... {2000}{(STARTING CONDITION)}{}{10}{ not pc.IsMale()}{}{}{}{}{}{}{}{} {2001}{(STARTING CONDITION)}{}{20}{ pc.Charisma>2()}{}{}{}{}{}{}{}{} Above, if the pc is female, you go to dialog at 10. If the PC is male and has decent charisma, you go to dialog at 20. If none of the starting conditions is true, the dialog attempts to start at the beginning of the file (line 1). Using a system like this, you could design clan specific responses for both genders. Code placed in the 6th column does NOT get executed with starting conditionals. HOWEVER, the conditional supports python and there is nothing to stop you from executing code in the conditional block This is mostly useful for fixing npcs before dialogs start: {200}{(STARTING CONDITION)}{}{0 }{ mySetup(npc) }{}{}{}{}{}{}{}{} {201}{(STARTING CONDITION)}{}{10}{ not pc.IsMale()}{}{}{}{}{}{}{}{} {202}{(STARTING CONDITION)}{}{20}{ pc.Charisma>2()}{}{}{}{}{}{}{}{} (In Vamputils.py) def mySetup(npc): # do whatever return 0 Notice that the line number of the first STARTING CONDITION is 0. When this is the case, the Starting Condition is evaluated but its result is ignored and it moves on. In other words, it doesn’t matter if mySetup(npc) returns 0 or 1, however we return 0 to be on the safe side. E) NOTES You can simulate secondary starting conditional blocks by creating a silent audio response (The line MUST have an associated sound file that plays nothing) followed by a group of (Auto-Link) lines with conditionals. {10}{[silence] ...}{[silence] ... }{#}{}{}{}{}{}{}{}{}{} {11}{(Auto-Link)}{(Auto-Link)}{20}{pc.Charisma==1}{}{}{}{}{}{}{}{} {12}{(Auto-Link)}{(Auto-Link)}{30}{pc.Charisma==2}{}{}{}{}{}{}{}{} {13}{(Auto-Link)}{(Auto-Link)}{40}{pc.Charisma==3}{}{}{}{}{}{}{}{} ... {2000}{(STARTING CONDITION)}{}{10}{ not pc.IsMale()}{}{}{}{}{}{}{}{} {2001}{(STARTING CONDITION)}{}{50}{ pc.IsMale()()}{}{}{}{}{}{}{}{} You can force what amounts to closed captioning for a specific dialog by first redirecting to a line with no associated audio file. When a line has no audio, close captioning is automatically turned on. It remains on until the dialog is over, even if the next response has audio. {10}{... }{... }{#}{}{}{}{}{}{}{}{}{} {11}{(Auto-Link)}{(Auto-Link)}{20}{pc.Charisma==1}{}{}{}{}{}{}{}{} {12}{(Auto-Link)}{(Auto-Link)}{30}{pc.Charisma==2}{}{}{}{}{}{}{}{} {13}{(Auto-Link)}{(Auto-Link)}{40}{pc.Charisma==3}{}{}{}{}{}{}{}{} ... {2000}{(STARTING CONDITION)}{}{10}{ not pc.IsMale()}{}{}{}{}{}{}{}{} {2001}{(STARTING CONDITION)}{}{50}{ pc.IsMale()()}{}{}{}{}{}{}{}{} ------------------------------------------------------------------------------- IV.5 Considerations and Scripting ------------------------------------------------------------------------------- A) Four choices per NPC Statement The dialog engine will display at most 4 responses. So you must be careful with conditionals to make sure you don’t introduce a bug. For example, a high level character could meet your Intimidation, Persuasion, Seduction and Domination criteria, making other responses unavailable. People also cheat. Don’t assume just because it is the first conversation of the game that they will have "at most" some specific value. General rule of thumb : When possible, try to handle both branches of a conditional response (see below) so that you can accurately control/predict the number of responses and link to "" as a 4th response if more than 4 options may be possible. {1}{NPC Male}{NPC Female}{# }{ }{}{}{}{}{}{}{}{} {2}{PC Male }{PC Female }{5 }{ Persuasion 4 }{}{}{}{}{}{}{}{} {3}{PC Male }{PC Female }{5 }{ Persuasion -4 }{}{}{}{}{}{}{}{} {4}{ }{ }{ }{ }{}{}{}{}{}{}{}{} {5}{NPC Male}{NPC Female}{# }{ }{}{}{}{}{}{}{}{} You can also control the number of responses using the OneOfSet() command discussed earlier. The common scenario : You have a default way of getting through a dialog, but you want to offer a shortcut or special reward to someone who has invested into a character with Dialog Skills. You could display and handle lines for all possibilities (like above), or you could allow the user to take advantage of their most powerful dialog skill and not bother displaying the other options. {1}{Male }{Female}{#}{ }{}{}{}{}{}{}{}{} {2}{max1 }{max 1}{10}{ }{}{}{}{}{}{}{}{} {3}{max 2}{max 2}{20}{Dominate 3 & OneOfSet(1,4)}{}{}{}{}{}{}{}{} {4}{max 2}{max 2}{30}{Persuasion 5 & OneOfSet(2,4)}{}{}{}{}{}{}{}{} {5}{max 2}{max 2}{40}{Seduction 4 & OneOfSet(3,4)}{}{}{}{}{}{}{}{} {6}{max 2}{max 2}{50}{Intimidate 3 & OneOfSet(4,4)}{}{}{}{}{}{}{}{} {7}{max 3}{max 3}{60}{ }{}{}{}{}{}{}{}{} This allows you to keep track of when you might need to use a "". Again, these are just ideas of how to help manage potential dialog bugs. In practice, you will normally end up using some combination of the examples above. B) Scripts and Fail fast execution When the conditional contains an AND, it uses FAILFAST execution. This means if the first condition fails, the second condition is not even executed. This is important if your function also performs additional hidden tasks. We will talk more about this later. When dealing with lots of conditionals it is also good practice to make the first response a de facto non-conditional response that ensures the game can continue. C) Advanced tricks For profession grade responses, you can build python functions to help guarantee all responses are seen. Basically you make some methods that execute with every displayed line to keep track of how many lines have displayed. Then, at the right moment, display more and link to the remaining choices. Example Script: |-------------------------------------------------------------------| |Filename = [/python/vamputil.py] | |-------------------------------------------------------------------| |import dialogutil | |-------------------------------------------------------------------| |-------------------------------------------------------------------| |Filename = [/python/dialogutil.py] | |-------------------------------------------------------------------| | from __main__ import Character | | def _Reset(self): | | self.dialogcounter=0 | | def _Count(self): | | if self.dialogcounter == 3: return 1 | | self.dialogcounter += 1 | | return 1 | | def _IsMax(self): | | if self.dialogcounter==3: | | self.Reset() | | return 1 | | return 0 | | Character.Reset = _Reset | | Character.Count = _Count | | Character.IsMax = _IsMax | |-------------------------------------------------------------------| Example Dialog: {1}{Opening Line}{Opening Line}{ # }{npc.Reset()}{}... {2}{Response1 }{Response1 }{ }{npc.Count()}{}... {3}{ }{ }{ 3 }{npc.IsMax()}{}... {4}{Response2 }{Response2 }{ }{npc.Count()}{}... {5}{ }{ }{ 5 }{npc.IsMax()}{}... {6}{Response3 }{Response3 }{ }{npc.Count()}{}... {7}{ }{ }{ 7 }{npc.IsMax()}{}... {8}{Response4 }{Response4 }{ }{npc.Count()}{}... {9}{ }{ }{ 9 }{npc.IsMax()}{}... {10}{Response5 }{Response5 }{ }{npc.Count()}{}... {11}{ }{ }{ 11 }{npc.IsMax()}{}... {12}Nevermind }{Nevermind }{ }{ }{}... Thanks to fail fast, as long as you put the npc.Count() method last within the conditional (5th) column, it will ensure the line isn't counted if an earlier condition fails. IE: {#}{Response }{Response }{ }{npc.IsMale() and npc.Count()}{}... If the line doesn't display because the npc is female, then it isn't counted. Also notice that there is a every other line that redirects to itself. And a Reset at the beginning. This allows you to build large, dynamic and complex dialogs without even having to think about weather a dialog option will appear or not. ------------------------------------------------------------------------------- IV.6 Dialog Engine Bugs ------------------------------------------------------------------------------- 1) Do not assign to 2 dimensional arrays or 2 dimensional hash maps from dialog Files. Bug in the dialog engine seems to reset the array to null. IE: module.My2DArray[1][2]="3" <- will cause engine to set variable My2DArray to null. Wrap alterations to complex data structures with python methods. (This is a better practice anyway) module.MyArraySetter(1,2,"3") 2) Calling an npc_maker’s Spawn method will not work while the PC is in a dialog. There is no error, it simply will not spawn anything. Workarounds: - Use ScheduleTask() to delay spawn() - Call spawn from NPCs OnDialogEnd Event (If it is an NPC that you embed/create). 3) Single letter responses are ignored by the dialog engine. Example: None of these lines will display nor will any of their conditions even execute for evaluation. {1}{Some Question}{Some Question}{#}{}{}{}{}{}{}{}{}{} {2}{A }{1 }{10}{IsFollower("E") }{}{}{}{}{}{}{}{} {3}{B }{2 }{20}{IsFollower("Lily") }{}{}{}{}{}{}{}{} {4}{C }{3 }{30}{IsFollower("VV") }{}{}{}{}{}{}{}{} {5}{D }{4 }{40}{IsFollower("Knox") }{}{}{}{}{}{}{}{} You need at least 2 non-space, non-tab letters for the engine to recognize the line. For example, placing a period at the end of response can fix the bug. {1}{Some Question}{Some Question}{#}{}{}{}{}{}{}{}{}{} {2}{A. }{1. }{10}{IsFollower("E") }{}{}{}{}{}{}{}{} {3}{B. }{2. }{20}{IsFollower("Lily") }{}{}{}{}{}{}{}{} {4}{C. }{3. }{30}{IsFollower("VV") }{}{}{}{}{}{}{}{} {5}{D. }{4. }{40}{IsFollower("Knox") }{}{}{}{}{}{}{}{} =============================================================================== V > > > > Dialog Audio Synchronization =============================================================================== ------------------------------------------------------------------------------- V.1 DLG, VCD and LIP Files ------------------------------------------------------------------------------- So you make your dialog file and text appears on the screen when you begin a conversation with your game entity (be it a person or a telephone). But how do you make it actually speak? In reality, the dialog engine involves 3 files: DLG : DLG files are discussed in the previous section. They link to embedded entities and thus invoke the dialog engine. They coordinate what NPC say and provide the text for the PC response choices. When subtitles are turned on, normally the text appearing in the DLG file appears for the NPCs as well. VCD : VCD files are general purpose sequence and synchronization files. They are used by several VTMB game components including cut scenes, animations and dialogs. They can do almost anything, but in regards to dialogs, they are mostly used to specify what audio file plays when an NPC makes a statement. LIP : Lip files break out how to move the mouth as the associated mp3 is playing audio. They also provides subtitles when the player is not interacting with the dialog engine. (This is most common during cut scenes that involve spoken audio. The introduction is a good example). DLG files implicitly look for VCD files based on their file path. Suppose the dialog file is located at: /Vampire/dlg/goo/foo.dlg The dialog engine will look for VCD files under the directory: /Vampire/sound/character/dlg/goo/foo/ Furthermore, if the dialog engine is displaying an NPC statement at line 10 of foo.dlg, it will specifically look for the file : /Vampire/sound/character/dlg/goo/foo/line10_col_e.vcd Hopefully the file paths are obvious, but just in case I will break it down. An Embedded entity links to the file "dlg/goo/foo.dlg". The game engine will never look for files outside the Vampire directory. For this reason, "Vampire/" is considered the BASE DIRECTORY for dialog files and is not included in the derived search path. After the embedded entity invokes the game engine, the game engine searches for a VCD file under "/Vampire/sound/character/". So in this case, "/Vampire/sound/character/" is considered the BASE DIRECTORY for the dialog engine. The VCD file name will follow the pattern: line_col_.vcd : Line number that the NPC response appears on in the corresponding .dlg file. By line number I mean the number that appears in the first column of the line, NOT the physical distance from the top of the file. : Has several possible values: e : everyone/default f : female (if PC is female) n : nosferatu (if PC is nosferatu) In practice, only the 3 above are ever used (though the engine may support more) The dialog engine will not actually look for a LIP file directly. If a VCD file is found, it looks for a LIP file only if the VCD has a speak event, and then the name of the lip file is based on the name of the audio file and not on the name of the dialog file. What this means is, you can add new spoken NPC statements to the game by simply making a new line number in the DLG fle and creating a corresponding VCD file. If the VCD file points to existing NPC audio, you wont have to include the audio (mp3) or the lip file with your mod. This is handy to know as it can help to keep your mod size down. ------------------------------------------------------------------------------- V.2 VCD Internals ------------------------------------------------------------------------------- As mentioned earlier, VCD files provide general sequence and synchronization capabilities. There is A LOT that could be said about these files. However, all we care about right now is how they help us with dialogs. What is the minimum that you have to do to get audio to play? The first line of a VCD file identifies an actor block. actor "Vandal" { } The actor is the NAME of the in game entity that the VCD data will be applied to. This is important. The audio that we set up will play at the location of the entity named "Vandal". However, if the entities name that you are talking to is not Vandal, no audio will play. Another problem that can happen is if there is more than 1 entity on the map with the same name. The engine will play the audio at the first entity it comes across, which may or may not be the one standing in front of you. The VCD files target actor is hard coded into the VCD file and can not be changed at runtime. Luckily, you CAN rename entities at runtime using the SetName() command. Within the actor block are many different channels. Channels are groups of events that all start at the same time and execute simultaneously. VCD files support numerous channels, but the main one we are concerned with here is creating a channel with a speak event. The syntax looks like so: actor "Vandal" { channel "My unique channel name" { event speak "My unique event name" { time 0.000000 14.461179 param "character/dlg/santa monica/vandal/line551_col_e.wav" param2 "70dB" fixedlength } } } The text after channel and event speak is arbitrary. It is whatever you want to call the channel. If you wish to stick with game conventions, any channel with a speak event is called "Speech" and NPC speak events are called: "NPC Line". time : When to start and stop within the associated sound file. If you want to use a small inner sound sample from another existing sentence, you can specify the start/stop range here without having to bloat your mod with a new mp3 file. You can also use the time range to shave time off a slightly unsynchronized audio file without bloating the upload with large audio files. param : In the case of Speech, the event parameter is the name of a wav file. YOU ALWAYS SPECIFY A FILE WITH A >WAV EXTENSION. If an mp3 file is detected in the target directory, it will be used in the wav files place, but if you specify mp3, it will not work. param2 : The volume level of the sound as it plays. fixedlength : I have no idea what this is or why it is there (how could audio not be fixed length?). Still. it is there in almost all the VCD files, so we go with the flow. You can also set up additional channels for executing gestures or facial animations on the entity while the audio is playing. channel "Gestures" { event gesture "A little something extra" { time 0.000000 14.666667 param "ACT_CONVERSE_NORMAL_TALK" } } channel "Expressions" { event expression "A smiling finish" { time 12.000000 14.666667 param "vandal" param2 "Joy" event_ramp { 1.0000 0.0000 } } } Note that expressions and Gestures stop when the VCD is finished "playing". If you want an expression/gesture to "stick" (angry woman with arms crossed for example), you will want to add SetDisposition to the 6th column of the DLG line. See the sections below for more information on gestures, expressions and dispositions. You can also use channels for altering the volume of the audio to create inflection or correct audio issues without having to redistribute the audio files. (Of course if you are distributing the audio for the first time, you should try to get it right so that alterations are not needed). channel "Speech Triggers" { event loud "Get louder here" { time 10.110000 10.240000 param "0.130" } event loud " Get louder here as well" { time 10.320000 10.429999 param "0.110" } } You will occasionally see a bonerename parameter at the end of the actor block. I'm not certain why this is, but I suspect that some sequences or animations may rename bones for their animation and the bonerename is added to vcd files to be certain that the bones are called what the animation expects them to be. If you see bonerename in one of the characters existing vcd files, I recommend doing it as well in your vcd file. And if you don't see it in the characters existing vcd files, you probably don't need to do it in your files either. actor "Vandal" { ... bonerename "Bip01" "Bip01" } ------------------------------------------------------------------------------- V.3 LIP Internals ------------------------------------------------------------------------------- As mentioned in part A above, the LIP file that gets loaded is based on the name of the wav file that the speak event is associated with. To keep life simple we always place the VCD, LIP and MP3 files into the same directory together. (and name them with the same convention). This convention is used by the main quest and thus is probably a good idea if you want to keep your mod code maintainable. Here is a small example lip file: I have spaced things for easy reading, but in reality you SHOULD NOT add scope spacing. ------------------------------------------------------ |VERSION 1.2 | |PLAINTEXT | |{ | | "Whoa" | |} | |WORDS | |{ | | WORD Whoa 0.000 1.000 | | { | | 119 w 0.000 0.250 1.000 0 | | 652 ah 0.250 0.750 1.000 0 | | 596 ao 0.750 1.000 1.000 0 | | } | |} | |EMPHASIS | |{ | |} | |CLOSECAPTION | |{ | | english | | { | | PHRASE unicode 12 " W h o a " 0.000 1.000 | | } | |} | |OPTIONS | |{ | | voice_duck 1 | | speaker_name Neo | |} | ------------------------------------------------------ PLAINTTEXT : A summary of the sentence, likely copied from the dialog file. The reality is that the contents of PLAINTEXT are completely arbitrary and have no impact on the game at all. The Plaintext block doesn't get used by anything nor does it need to match the WORDS block or the CLOSECAPTION block. (Though it is convenient when it does). WORDS : A breakdown of how the mouth moves with the scene. The text that follows the WORD definition doesn't have to match anything (PLAINTEXT OR CLOSECAPTIONING), however it normally does in practice to help keep the file maintainable. Each word is described using one of several dozen mouth pose aliases with timing info. 119 w 0.000 0.250 1.000 0 The first 2 elements describe the mouth pose. At the time of this writing (version 1.0), I do not have a comprehensive list of mouth poses. However, they appear to be global and you can use words\mouth poses said by any model on any other model. The third and fourth element are your start and stop time. I'm not certain what the last 2 numbers are, but some dialogs have a zero at the end and some dialogs do not. I find that the presence of the zero tends to affect the articulation emphasis of the models mouth. I have also found when copying and pasting words from various dialog files that mixing and matching lines that end with 0 and lines that don't end with zero will normally crash the game at runtime. You can remedy this by making them all the same. Finally, if a dialog is crashing, I found that often times removing or adding the zero to the end of the line will stop the crash. (if it had a 0, remove or if it didn't have a zero, add). My best guess is that the last 2 numbers are an event_ramp. IE: how fast/slow the mouth slides into and out of the pose. When the 0 isn't there, it likely takes on a default value. CLOSECAPTION : The closecaption block actually does get used. However, it is ONLY used when the audio is part of a cut scene. If the lip/audio is being executed as a result of an NPC dialog, the NPC Statement within the DLG file takes priority over the closecaption block. As you can see, the CLOSECAPTION block contains a Unicode phrase including string length and time signatures for when it should appear. The most important thing to mention here is the Unicode string itself. It may appear that the Unicode is simply a normal string with spaces between the characters. But in reality, the spaces are null characters. When python prints a string, it shows non-printables using their escaped ASCII values. So from python the command: >>> str('"Whoa"').encode("utf-16")[2:] yields: "\x00W\x00h\x00o\x00a\x00"\x00 ** NOTICE that the double quotes are also Unicode ** however, in Word or notepad, the string appears as: " W h o a " To make matters worse, even once you understand what is really there, most text editors wont let you insert a null character into your text. The good news is that you don't NEED close captioning for 95% of the game text. If you know for certain that your audio is intended to support a DLG conversation, then I recommend that you leave the area blank: CLOSECAPTION { } If you ARE creating audio for a cut scene (or you are obsessive), you have two options. You can use a Hex Editor, which will allow you to see and insert the null characters, or you can use my lipedit utility. If you downloaded the zip version of this guide, a file called "lipedit.py" should be under the tools directory. Before running the script, prepare the lip file with a non-Unicode sentence: CLOSECAPTION { english { PHRASE unicode "Whoa" 0.000 1.000 } } NOTE : No indentation and no phrase length. However, the timing info is present. Once the lip file is prepped, use Explorer to find the lipedit.py script, right click it and Select "Edit with IDLE" ** If you do not see "Edit with IDLE", then you need to install python 2.1.2. (See I.3 - Standard Tools) Two windows will pop up. CLOSE the one that contains the source code. Now within the empty python window type: >>> import lipedit.py >>> lipedit.fix('C:\Path\input.lip','C:\Path\output.lip') Obviously you should replace the path info with the full absolute path of your input and output lip files. This will read in, parse and update the unfinished Unicode block, including updating the character count. OPTIONS : speaker_name : The name that the subtitle system will place next to spoken text during a cut scene if subtitles are turned on. It does NOT have to actually match the name of the entity. (hence why it is "optional") voice_duck : ??? - No idea, but it is present in all the lip files, so I include it. ------------------------------------------------------------------------------- V.4 Creating Custom Audio ------------------------------------------------------------------------------- This is a little outside the scope of this tutorial, but why not. A) Tools I used: - Python 2.1.2 : (if you haven't already installed it) - WinAmp : (Don't use media player. It locks the files) - CoolEdit 96 : There are a plethora of other sound editing tools. This just happens to be my favorite. Simple to use and gets the job done without too many extras. - DbPowerAmp : Mp3 to wave/wave to mp3 conversion (I use release 10) NOTE : VTMB mp3 audio is 44100Hz Mono at 64Kbps, constant bitrate. B) Full Audio or Partial Audio? Having no audio at all is better than having bad audio. Even if the script is good, bad audio will detract from the players ability to use their imagination. If the player is speaking to a sexy model but it sounds like a dude speaking in his falsetto, I don’t care what the girl is saying, the person playing the game wont be able to get into it. A nice middle of the road strategy that I have seen: Have the first few words spoken by the character, but not the whole sentence. Most people have enough imagination to carry out the rest of the sentence with the established voice. IE: "Yes? How may I help you?" With just the word "Yes" being spoken at the beginning. This strategy will save you hours, days, if not weeks and months of dialog development time. It will also give you more freedom in designing your dialogs. Only the opening line even needs audio. Secondary lines dont need anything. *** And if you use VCD files to point to segments of existing audio, you will decrease the size of your mod as you wont be including full audio. There is a catch. If a line has audio, it normally doesn't display. So to get your hybrid lines to display you need to activate subtitles. I find the best way to do this is to direct the dialog engine to an interme