_________________________________________ ����������������������������������������� Vampire the Masquerade Bloodlines(PC) Mod Development Guide _________________________________________ ����������������������������������������� May 25, 2008 Version 1.0 Written by: Dheu Email: [email protected] 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 <COMMAND>" 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 <map name> (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 <instance_name>: 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 <website> (Steam Users Ignore) 3) Extract zip file to <installation dir>/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 "<install_dir>\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 "<install_dir>\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 <install_dir>/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 <filename>.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 = [<install root>\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.<command_name>() Console variables are accessed through the cvar variable: __main__.cvar.<variable_name>=value e.g. : |--------------------------------------------------------------------| |Ex: Filename = [<install root>\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 = [<install root>\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; "<entity name>,<method>,<param>,<delay>,<max fires>,<python script>," <entity name> : Can be any named entity within the same map. The name should NOT be in quotes. <method> : Name of a valid method on the target entity to fire. Each entity supports different methods. See attached Appendix G for a full listing. <param> : Optional parameter to pass into the method. If the method requires more than 1 parameter, you have to use the <python script> option. <delay> : Delay in seconds before calling the method. Can be floating point value. <max fires> : 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. <python script> 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 <clan number> : 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 "<More...>" 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 "<More>". 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 = [<vampire root>/python/vamputil.py] | |-------------------------------------------------------------------| |import dialogutil | |-------------------------------------------------------------------| |-------------------------------------------------------------------| |Filename = [<vampire root>/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}{<More...> }{<More...> }{ 3 }{npc.IsMax()}{}... {4}{Response2 }{Response2 }{ }{npc.Count()}{}... {5}{<More...> }{<More...> }{ 5 }{npc.IsMax()}{}... {6}{Response3 }{Response3 }{ }{npc.Count()}{}... {7}{<More...> }{<More...> }{ 7 }{npc.IsMax()}{}... {8}{Response4 }{Response4 }{ }{npc.Count()}{}... {9}{<More...> }{<More...> }{ 9 }{npc.IsMax()}{}... {10}{Response5 }{Response5 }{ }{npc.Count()}{}... {11}{<More...> }{<More...> }{ 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 <More...> 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: <VTMB DIR>/Vampire/dlg/goo/foo.dlg The dialog engine will look for VCD files under the directory: <VTMB DIR>/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 : <VTMB DIR>/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<index>_col_<speaker>.vcd <index> : 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. <speaker> : 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 intermediate line with "..." (that has no associated audio). This will activate the subtitles. Once subtitles are activated within a dialog, they stay on till the end of the dialog. You can then redirect to the real line with your segmented audio. e.g. {10}{...}{...}{#}{}{}{}{}{}{}{}{}{} {11}{(AUTO-LINK)}{}{12}{}{}{}{}{}{}{}{}{} {12}{Yes, what... }{Yes, what... }{#}{}{}{}{}{}{}{}{}{} {13}{Response 1 }{Response 1}{}{}{}{}{}{}{}{}{}{} {14}{Response 2 }{Response 1 }{#}{}{}{}{}{}{}{}{}{} ... {25}{(STARTING CONDITION)}{ }{10}{1 }{}{}{}{}{}{}{}{} B) Conversation Design Normally when designing dialog for a game or mod, you would lock yourself in a room with a whiteboard or at least a lot of paper and brainstorm on what should be said by who and when... And in time you develop your scripts. Then you would send your scripts off to a recording studio to be spoken, and recorded by professional actors. However, most mod developers don�t have the luxury of some things like funding. Thus, many people will be looking to make use of existing audio and animation data. The MAIN difference is how you go about designing your dialogs. Instead of designing a dialog and then having it recorded, you listen to existing audio with a particular conversation or even just a moment in mind. You let the existing material guide you. This holds true whether you use a hybrid or a full audio approach. The material will provide the brainstorming. In fact you may not even know how you are going to get from point A to point B, but after hearing a few usable sound bytes, the ideas start to form. It is frustrating for people used to being in full control of their conversations, but quality wise, building on top of what is there and within its context is generally best. C) Breaking out Sound Bytes Start by finding the target characters sound files. After you extract the .vpk files. Normally they are found somewhere under : <VTMB INSTALLATION>/Vampire/sound/character/ Copy all associated files (mp3, lip and vcd) to a working directory. Then uncompress all the mp3 audio as wav files and delete the mp3 files. If you wish, you can create a single text file with all the spoken text. The lip files contain the sentences. You can do this the hard way (by hand) or if you have installed cygwin, you can do it the easy way: - Start cygwin, - Use "cd" to change directory to the working directory ( /cygdrive/c/... ) - from the working directory type: $ for f in *.lip ; do echo $f; sed �n 4p $f; done > compiled.txt compiled.txt will be created with all the spoken text. (Be certain to open compiled.txt with WordPad the first time you open it) By my 3rd conversation, I stopped bothering with the text files because I came to realize that word searches rarely helped me as they didn't tell me anything about the tonality, the speed, the emotion. What was more effective was opening each wav file up and trimming out re-usable, stand alone words and phrases. I used the file name to help describe what the audio was. I would also label the line number and the trim mark in the file name: help_141_0.932.wav me_311_5.876.wav gasp_did_you_hear_that_521_7.234.wav This will normally take an entire evening. Now from a single directory, you could double click on files and see if the tonality is correct for certain combinations of words. This will allow you to form new sentences more effectively. Winamp allows you to queue things up easily by CTRL + Clicking wav files.So you can experiment and see how the end product might sound before you spend time appending everything together with CoolEdit. If you need a specific word that doesn't exist, you can often piece it together by using 2 different words with similar syllables. Example : subterfuge + admit = submit But occasionally, you just have to admit defeat and try to think of a different word. D) Re-Composition This is the hard part. As I mentioned before, when I trimmed the files, I kept track of the trim marks. CoolEdit does a good job of showing you the selected area in the bottom right corner of the app: [ -_--_--################-___-_- ] ^ ^ | | 2.345 6.789 Each wav file has a lip file with data in it that tells the engine how to move the mouth with the words. Problem is, the time signatures in the lip files will not match up with the timing of your new sentence. If I trimmed the example above and I wanted to START a sentence with the word that used to reside at 2.345, I would need to subtract 2.345 from all the time signatures within the lip file. And, I actually did that for my first conversation. Took 3 days to make 3 lines. So, I decided to make a python script to do it for me. It is named lipedit.py (You may recognize this name if you have already read the dialog section). The script comes with the zip version of this Guide and should be located in the tools/ directory. To use it, you will have to have installed python (See standard Tools in Section I). Right Clip lipedit.py, chose "Edit with idle". Two Python windows will pop up. Close the one with the source code in it. In the (mostly emtpy) window type: >>> import lipedit >>> lipedit.fix("C:\\input\\file.lip","C:\\output\\file.lip",-2.345) lipedit uses regular expressions to scan and fix the values. Use Case Scenario: Typically, I would use CoolEdit to see at what time the word should be spoken. Lets say the word "Help" starts at the 5.0 time signature in my wav file. Next I look at the filename that help came from: help_141_0.932.wav Then I would open up line141_col_e.lip and search for the instance of the Word "help" around the 0.932. WORD help .928 1.115 { 113 h .915 1.010 1.000 0 213 e 1.010 1.050 1.000 0 413 l 1.050 1.100 1.000 0 513 p 1.100 1.115 1.000 0 } I would note that the first ACTUAL SYLLABLE of help begins at .915 So to make "help" appear at 5.0, I need to add 4.085: (yes, you will still need a calculator). >>> lipedit.fix('C:\in\line141_col_e.lip','C:\output\help.lip',4.085) Now I open up help.lip and past the correct time signature for help into a new lip file that I am creating for the sentence. Note that the python script ONLY FIXES the time signatures. You still have to go in, copy out the words (with the corrected time signatures) into a new lip file and update the lip file with the text that should appear to the user in the subtitles. If this all sounds crazy, then go look at a lip file and then come back here and you will know what I mean. The last step is the vcd file. The easiest thing to do is simply correct the file name/path, the audio length and then remove expressions, etc from the rest of the file. Of course, you are also free to throw stuff in. Just remember that any existing material was thrown off by the trim. And I didn't make a py script to autofix vcd files. It is a tedious process. Even with the help of the lip timing script, once you get into the flow, you only average about 1 lip file every 45 minutes, and that is after you have pieced the wav files together (which may take you days). Alternatively, if you have the means to record your own audio, you can use that. However, your biggest hurdle will be making realistic LIP files. If you DO decide to be brave and record your own audio, you may want to generate text files of all the conversations. Then you can search for a word that you say and use the script to correct the timing (maybe). Or you could fudge it by hand. If I was piecing a line together word by word from scratch, I probably wouldn't bother with the python script. Even if you cant find an exact word, something like it will normally do. For example, when muted, you probably cant tell if a models lips are saying devious or delicious. =============================================================================== VI > > > > Animating NPCs with Python =============================================================================== The animation for NPCs is divided up into 3 categories: skeletal animations Facial expressions and Lip synching. Once you understand what animations and expressions are available to you, there are several ways of incorporating them into the game: Dynamic: 1) Direct (SetAnimation) 2) Dispositions 3) Gestures (Interesting Places) 4) Schedules Embedded 1) Scripted Sequences 2) Choreographed scenes ------------------------------------------------------------------------------- VI.1 > > > > Skeletal Animations ------------------------------------------------------------------------------- Models have a skeleton and a hull. The skeletons attach to the hull and control movement of the hull. (The hull is normally painted with a skin). Being that models are so different, it should not be a surprise that skeletal animations are NOT globally standardized. For example, how could stubs the zombie with no arms and no legs execute a "standard" animation that involves jumping into the air. The good news is that most HUMAN LIKE NPCS (2 arms, 2 legs and a head)share a common skeleton. Thus, there is a set of "standard" animations. This is made possible because the MDL format allows models that share the same base skeleton to also share animations by linking to an external MDL file were common animations are defined. The vast majority of Human-Like NPCS link to "npc_allsequences.mdl". This is defined twice, once for male models and once for female models. models/character/shared/female/npc_allsequences.mdl or models/character/shared/male/npc_allsequences.mdl in both circumstance however, the master animation file further breaks out the common animations into separate animation files: npc_allsequences.mdl : - linkto -> shared/.../animal_feed.mdl - linkto -> shared/.../baseball.mdl - linkto -> shared/.../bushhook.mdl - linkto -> shared/.../claws.mdl - linkto -> shared/.../finished_moves.mdl - linkto -> shared/.../fists.mdl - linkto -> shared/.../forced_feed.mdl - linkto -> shared/.../katana.mdl - linkto -> shared/.../knife.mdl - linkto -> shared/.../meleeshared_onehand.mdl - linkto -> shared/.../meleeshared_twohand.mdl - linkto -> shared/.../misc.mdl - linkto -> shared/.../move_and_ranged.mdl - linkto -> shared/.../seductive_feed.mdl - linkto -> shared/.../sherifsword.mdl - linkto -> shared/.../sledgehammer.mdl - linkto -> shared/.../stake.mld - linkto -> shared/.../tireiron.mdl - linkto -> shared/.../zombie_feed.mdl If you take the time to open up each of these files with VPKTool Model Tool tab, you can see all the animation names listed in the info window at the bottom of the tool. I have taken the liberty of documenting these in Appendix H. Examples: dance01 dance02 dance03 cower_idle cower2_idle cower3_idle Raw animations are generally not applied DIRECTLY within the game. They are used by internal mechanisms such as the combat system, gestures, and schedules and by embedded elements such as scripted_sequence. The only time they are used directly within the game is when you have a "prop_dynamic" object. For example, in the strip club, there are dancers up on stage that you can�t approach. They are in fact not npcs but "prop_dynamic" objects with an animation set. You can create the illusion of applying an animation to an npc by Hiding the npc (set the model to models/null.mdl), dynamically Create a prop_dynamic where they are standing, and then set the Model/Animation. The downside of applying animations in this manner is that you have no control over the model�s facial expressions. This may or may not be a big deal for you. Example: _EntCreate = __main__.CreateEntityNoSpawn _EntSpawn = __main__.CallEntitySpawn npc = __main__.FindEntityByName("targetNPCName") e=_EntCreate ("prop_dynamic", npc.GetOrigin(),npc.GetAngles()) e.SetName("prop_" + npc.GetName) e.SetModel(npc.GetModelName()) e.SetParent(npc) npc.SetModel("models/null.mdl") _EntSpawn(e) e.SetAnimation("dance03") Some models contain additional internal animations that are specific to that model. For example, models/character/npc/common/stripper/Stripper3.mdl contains several animations that only that specific model will perform. Other models link to additional external MDL files for animations. For example, models/character/npc/common/lotusblossom_girl/lotusblossom_girl.mdl links to character/shared/female/stripper.mdl in addition to the common animations. ------------------------------------------------------------------------------- VI.2 > > > > Facial Expressions ------------------------------------------------------------------------------- Facial Expressions are basically animations meant specifically for an NPCs face. Because faces are more unique than the body, they get special attention. Each model defines their own set of supported expressions. You can browse the expressions for a given model\character under the "Vampire\expressions" directory. 95% of the models just implement the standard set of expressions (located in Vampire\expressions\expressions.txt). The "common" expressions are as follows: "Neutral" "Nearly Crying" "Nearly Crying_No Deform" "Joy" "Melancholy Smile" "Meloncholy_NoDeform" "Fear" "Confused" "Disgust_NoDeform" "Very Frightened" "Disgust" "Sad_NoDeform" "Sly Smile" "Apathy" "Fear_NoDeform" "Flirtatious" "Lowered Both" "Sly Smile_NoDeform" "Anger" "Raised Both" "Confused_NoDeform" "Mad" "Raised Right" "Flirtatious_NoDeform" "Enraged" "Raised Left" "Knockback" "Sad" "Lowered Right" "Anger_No Deform " "Miserable" "Lowered Left" Example: vv=mySpawnNPC("") vv.SetExpression("Flirtatious") In the case of VV, her model supports additional expressions. NOTES: - I found when executing the SetExpression() method, the next if statement (wherever it executed from) would throw an exception saying function expects 7 parameters. You can avoid breaking later code by preemptively calling if from within a try-catch block after using the SetExpression method. i.e.: def SetExpression(npc, expression): try: npc.SetExpression(expression) except: npc.SetExpression(expression) data="dummy" try: if data=="": return except: pass return - Model specific animations will only work on the original model. If the model is a clone of the original (ie: someone copied vv.mdl to vv2.mdl so that they could link to a different outfit), the vv specific expressions will not work on the clone. The expressions will only work on the original vv.mdl. ------------------------------------------------------------------------------- VI.3 > > > > Dispositions ------------------------------------------------------------------------------- A disposition is a combination of Body animation and Facial Expression. However, dispositions can not include just any Animation. They are limited to the STANCE animations defined in stances.mdl. Dispositions are mostly used to emphasis the facial expression with additional body language. For example, if someone is mad at you, they might place a hand on the hip or cross their arms. If they are trying to seduce you they may lay down on a bed in addition to the sexy smirk they throw at you. Like expressions, MOST dispositions are common. However, there are a few Dispositions that are meant for specific characters. Common Dispositions: -------------------- When setting a disposition, you specify a name and level. The name corresponds to the body animation and the level corresponds to the facial mood. Disposition Levels --------------------------- Neutral [1] Anger [1,2,3] Joy [1,2,3] Sad [1,2,3] Fear [1,2] Disgust [1] Apathy [1] Flirtatious [1] Confused [1] Lay [1,2,3,4] Damaged [1] Dead [1] Sitting [1] Bartender [1,2] BehindBack [1] Unique Dispositions: -------------------- Disposition Levels --------------------------- Therese [1,2,3] -> Therese Only Lily [1,2] -> Lily Only ChairDamaged [1,2] -> Lily Only PrinceSitting [1] -> Chunk and Lacroix Only Fortunately, all NPCs have a method on them called SetDisposition which makes Using dispositions much easier than animations. vv=mySpawnNPC() vv.SetDisposition("Apathy",1) ------------------------------------------------------------------------------- VI.4 > > > > Schedules and ScriptedDisciplines ------------------------------------------------------------------------------- A schedule is a tiny program that executes on an NPC. Every NPC has a Schedule assigned to it. When you cast a spell in the game, under the hood, the game assigns your target a schedule that results in the spells execution. However schedules are not just used for spells. When you tell an NPC to follow you, they execute a special schedule that causes them to follow you around the map. Most NPCs execute the common schedule SCHED_TROIKA_IDLE. This schedule includes a laundry list of tasks such as scanning for enemies, the pc, looking at the player if they are near, presenting a dialog icon if the pc gets close enough, etc. As you can imagine, changing an NPC�s schedule can cause an animation to happen. However, it should be noted that schedules are not JUST animations. For example, setting an NPC�s schedule to SCHED_TROIKA_D_BLOODBOIL_EXPLODE will show the very cool blood boil explosion animation. It will also kill the npc in the process. FROM : vdata/system/disciplinetgt_xxx SCHED_TROIKA_D_RAVENS SCHED_TROIKA_D_TRANCE SCHED_TROIKA_DISORIENTED SCHED_TROIKA_D_BURROWING_BEETLE SCHED_TROIKA_D_SPECTRAL_WOLVES SCHED_TROIKA_D_SPECTRAL_WOLVES_ESCAPE SCHED_TROIKA_D_BLOODSUCKERS_COMMUNION SCHED_TROIKA_D_PESTILENCE" // Note: Dies at the end! (Dmg_Health 100%) SCHED_TROIKA_SWAT_INSECTS SCHED_TROIKA_D_HYSTERIA SCHED_TROIKA_D_HALLUCINATION SCHED_TROIKA_D_MASS_HALLUCINATION SCHED_TROIKA_D_VISION_OF_DEATH SCHED_TROIKA_D_BERSERK SCHED_TROIKA_D_SUICIDE SCHED_TROIKA_D_VISION_OF_DEATH SCHED_TROIKA_D_BRAINWIPE SCHED_TROIKA_D_BRAINWIPE_END SCHED_TROIKA_D_POSSESSION SCHED_TROIKA_D_DAZE SCHED_TROIKA_D_BLOODSHOT_KNOCKBACK SCHED_TROIKA_D_BLOODBOIL_EXPLODE SCHED_TROIKA_D_BLOODBOIL_BOSS * You can find other schedules by using the "picker" console command from in game. Example: vv=mySpawnNPC() vv.ChangeSchedule("SCHED_TROIKA_D_TRANCE") You can accomplish the same thing by using SetScriptedDiscipline() vv=mySpawnNPC() vv.SetScriptedDiscipline("dominate 1") ------------------------------------------------------------------------------- VI.5 > > > > Gestures and intersting_place ------------------------------------------------------------------------------- Interesting places are objects. You can think of them as phone-booth sized invisible boxes on a map that draw nearby NPCs to them like a magnet. When the NPC enters the box, they perform whatever action the box is configured for. Interesting places generally use gestures to define/refine the activities that may occur when a user enters the box. Gestures map to animations, however while it is safe to say that every Gesture has a corresponding animation, it is not safe to say that every animation has a corresponding gesture. (SAMPLE) FROM : Vampire/vdata/system/interestingplacetypelist.txt ACT_CONVERSE_NORMAL_TALK ACT_CONVERSE_NORMAL_LISTEN ACT_COUCH_SIT_INTO ACT_COUCH_SIT_IDLE ACT_COUCH_SIT_OUTOF ACT_COWER_INTO ACT_COWER ACT_COWER_OUTOF ACT_COWER2_INTO ACT_COWER2 ACT_COWER2_OUTOF ACT_COWER3_INTO ACT_COWER3 ACT_COWER3_OUTOF ACT_DIE ACT_DISORIENTED ACT_DISPOSITION ACT_DISPOSITION_MESMERIZED ACT_DOORKNOCK ,,, (See Appendix I for more) All NPC�s have a SetGesture() method on them. You can pass in one of the ACT_<gesture> strings, or you can pass in an animation name. vv=mySpawnNPC() vv.SetGesture ("ACT_COUCH_SIT_INTO") vv.SetGesture ("ACT_COUCH_SIT_OUTOF") OR vv=mySpawnNPC() vv.SetGesture ("CouchTV_Into") vv.SetGesture ("CouchTV_outof") Single shot animations and gestures will execute only once and then stop. However looping animations and gestures will continue to execute over and over again as part of the npc�s schedule. Furthermore, the looping IDLE_DISPOSITION gesture is hard coded into all the SCHED_TROIKA_IDLE schedule so that an npc will always alternate between the idle action and the looping gesture you assign them. This is very annoying and as a result, I recommend avoiding any looping gestures or animations with SetGesture. ------------------------------------------------------------------------------- VI.6 > > > > scripted_sequence and logic_choreographed_scene ------------------------------------------------------------------------------- Both of these entities allow you to apply an animation to an entity on the map. Unlike the limited pure python solutions discussed above, these entities must be embedded into the map you wish to execute the animation on and hooked up to the entity by name. A) scripted_sequence Scripted Sequences are used to execute animations and movements on map characeters. By default, they execute once and then self-delete. You can override this behavior by setting some of the spawn flags: bit value Meaning 1 1 2 2 3 4 Persistent 4 8 5 16 Start Enabled 6 32 7 64 8 128 9 256 Enable looping Post Idle Animation 10 512 11 1024 12 2048 13 4096 14 8192 15 16384 16 32768 Using bits : So a spawn flag has 16 options : 0000000000000000 They are either true or false. To enable an option, you set the bit to 1. When you are finished, you convert the binary value to decimal. The right most value of the binary number corresponds to bit 1. To enable bit 3,5 and 9 = 100010100 Calculator -> View -> Scientific, check "[*] Bin". Enter the number above and then hit "[*] Dec" Value = 276. Alternatively, you can add the values up from the value colum of those bits you wish to enable. Persistent + Post Idle Animation Enabled = 4 + 256 = 260 Example: Have VV execute a standard animation in your apartment. The following entity must be embedded into the apartment: We use spawn flag 260 so that the animation will loop. { "classname" "scripted_sequence" "targetname" "vv_mesmerized" "spawnflags" "260" "m_flRadius" "5" "m_flRepeat" "0" "m_fMoveTo" "0" "m_iszEntity" "vv" "m_iszPlay" "dance01" "m_iszPostIdle" "dance01" "origin" "-1768.03 -2646.97 144.03" "angles" "0 165 0" } ] import custom ] vv=custom.mySpawnNPC("vv") ] seq=FindEntityByName("vv_mesmerized") ] seq.SetOrigin(vv.GetOrigin()) ] seq.BeginSequence() Option Details: "m_flRadius"- Search radius for entities. Depending on the spawn flag, it may or may not care if the Entity matches the name of the m_iszEntity property. "m_iszIdle" - If set, this is the animation that plays PRIOR to calling BeginSequence. Only has affect if StartEnabled is true. "m_iszPlay" - When you call BeginSequence, this is the animation that plays "m_iszPostIdle" - Animaiton that plays after m_iszPlay. "m_fMoveTo" - 0 : entity plays animations where it is. 1 : entity walks to scripted_sequence, then starts 2 : entity runs to scripted_sequence, then starts 3 : Walk to scripted_sequence, Walk back when finished 4 : Walk to scripted_sequence, Teleport back on finish B) logic_choreographed_scene Some MDL files contain skeletal animations meant for as many as 4 NPC models. To use these animations, typically the game uses the logic_choreographed_scene entity to identify the animation and the models to execute the animations on. For example, the opening scene of the game uses a scene with 4 npcs to have your PC and their sire kneel in front of the theater. logic_choreographed_scene is not limited to multi-actor animations. You can in fact use them to apply any animation from any mdl to any other entity model. Example: Have VV execute the stripper lap dance in your apartment, even though it is not one of the standard animations: { "classname" "logic_choreographed_scene" "targetname" "mylapdance" "origin" "-1768.03 -2602.09 144.03" "angles" "0 300 0" "position_start" "1" "position_end" "3" "hide_ents" "0" "force_lod" "0" "target1" "noone" "target2" "vv" "BaseAnim" "models/cinematic/Hollywood/Vesuvius/Lap_danceGroup_4.mdl" "SceneFile" "sound/cinematic/Hollywood/Vesuvius/custom.vcd" "OnCompletion" " mylapdance,Start,,0,-1,," } ** The actual targets of the animation are controlled by the SceneFile. The internal target[x] attributes are ignored and used mostly as a local place holder so you don�t have to go open the scene file to figure out what to name your targets. The Corresponding .vcd file would be updated as follows: |--------------------------------------------------------------------| |Filename = [<vampire root>/sound/cinematic/.../custom.vcd] | |--------------------------------------------------------------------| |actor "noone" | |{ | | channel "Anim" | | { | | event sequence "Lap_dance" | | { | | time 0.000000 3600 | | param "entire_scene" | | } | | } | | bonerename "Bip02" "Bip01" | |} | |actor "vv" | |{ | | channel "Anim" | | { | | event sequence "Lap_dance" | | { | | time 0.000000 3600 | | param "entire_scene" | | } | | } | | bonerename "Bip01" "Bip01" | |} | |fps 60 | |snap off | |--------------------------------------------------------------------| To run the animation, you would create a vv model named "vv", a null model named "noone" and then call the Start() method on the embedded entity. ] import custom ] vv=custom.mySpawnNPC("vv") ] noone=custom.mySpawnNPC("noone","models/null.mdl") ] scene=FindEntityByName("mylapdance") ] scene.Start() Since the vv model is a real NPC (and not a prop_dynamic), you can set her expression: ] custom.SetExpression(vv,"Sly Smile") =============================================================================== VII. > > > > Editing Models/Skins =============================================================================== ------------------------------------------------------------------------------- VII.1 Basics ------------------------------------------------------------------------------- Models are composed of several files: <modelname>.mdl : Defines the structure of the model along with animation, bounding box, hit box, material, mesh and LOD info. <modelname>.vtx : vtx files store hardware optimized material, skinning and triangle strip/fan information for each Level Of Detail (LOD) of each mesh in the MDL. They come in several names for backwards compatibility with older hardware. (DirectX 7) <modelname.phy> : [optional] contains jointed ragdoll collision model. <skinname>.vmt : meta data file that "redirects" to actual texture. <skinname>.ttz : ttz and tth work together to define the models <skinname>.tth texture. When working with skins, you must maintain both. Additionally, MDL files may link to other MDL files internally for shared animations. ------------------------------------------------------------------------------- VII.2 MDL File Details ------------------------------------------------------------------------------- MDL Files contain a lot of information. You can view more of this information using VPKTool. Use the Model Tools tab to select a .mdl file and open it. 1) Texture Search Paths : Where to search for the VMT files. 2) Texture Names : The name of the VMT files. It is important to note that MDL files do not link directly to textures. They link to VMT files. VMT files are text based property files that point to the actual textures: ############################## ####################### --> # search/path/TEXTURE1(.vmt) # --> # /full/path/TEXTURE1 # #########/ ############################## ####################### # MDL # #########\ ############################## ####################### --> # search/path/TEXTURE2(.vmt) # --> # /full/path/TEXTURE2 # ############################## ####################### 3) VPKTool has is a window at the bottom that often times goes unnoticed, however contains some very cool info if you scroll up. Aside from the texture info, it lists the models animations. Most Animations come from a shared animations mdl file. Normally: /models/character/shared/[male\female]/npc_allsequences.mdl However, in addition to this "external" link, the models specific animations are also listed. Typically these exist for cut-scenes or complex dialogs where the general purpose expression engine wasn't good enough. ------------------------------------------------------------------------------- VII.3 Working with downloaded Skins ------------------------------------------------------------------------------- The instructions below assume you have extracted the contents of all the .vpk files somewhere accessible. A) Replacing the Original Skin To keep things simple, we will start with the typical scenario: you Download someone else�s skin from the internet and want to install it into the game. For this example, we will use Dark VV: http://vh.noirscape.org/files.php?action=showfile&file=174 Most 3rd party skins REPLACE the existing Skin. This is done by Clobbering the original skins vmt, ttz and tth files. Following the chart above, the MDL will find the new VMT files (by the same name) which point to the new Texture files (by the same name). The key here is that the mdl file is unchanged. Thus, when you play the game, the new skin will be visible on the original character. B) Adding new clothing options to existing NPCs Why replace VV�s skin, when you can install an additional outfit? The main reason is because normal people wouldn�t know how to use/access the skin. However, As a script savy modder armed with this guide, this shouldn�t phase you. 1) Creating the new "outfit" model. We start by duplicating the original model. You can do this the hard way by renaming each file, or you can do this the easy way by simply copying the whole directory to a new directory name. Lets go easy! THE MODEL: ---------- Copy the Original MODEL files (mdl, vtx, phy, etc...): FROM: \Vampire\models\character\npc\unique\downtown\vv\* TO : \Vampire\models\character\npc\unique\downtown\vv2\* Update the Original Model OPEN: \Vampire\models\character\npc\unique\downtown\vv2\vv.mdl UPDATE: "Texture Search Path", change directory to vv2 COMMIT ALL CHANGES: When you hit this button, it will prompt you for a place to save. Save as a new name: \Vampire\models\character\npc\unique\downtown\vv2\vv2.mdl NOTE: Internally, the MDL file saves its own name. So it is important not to rename the file (using explorer) or even move it to a different directory after you save. If you decide to rename the file, re-open and save again using VPKTool. Clean up : Delete Original MDL: FROM: \Vampire\models\character\npc\unique\downtown\vv2\vv.mdl THE SKIN: --------- Copy MATERIALS: FROM: \Vampire\materials\models\character\npc\unique\downtown\vv TO : \Vampire\materials\models\character\npc\unique\downtown\vv2 Zip files downloaded off the internet normally don�t contain all required skins. For example, it may update the clothes, but not the teeth or hair. Most zip files assume that the original directories materials are still present. So we must copy everything over. UNZIP SKIN: TO : \Vampire\materials\models\character\npc\unique\downtown\vv2 Note that the zip file may contain the full directory path. Be Certain to unzip the files directly to the taget directory (vv2 in this case). You should be prompted about overwriting existing files. Say yes. UPDATE VMT(s) INSIDE: \Vampire\materials\models\character\npc\unique\downtown\vv2 The original author likely expected their textures to be in a different directory. So you must open the .vmt files (use notepad) and update the paths. In this case, to "vv2". 2) [Optional] Optimizatized method (uses less disk space) The method above is the easy way. The downside is that a lot of textures may unnecessarily be duplicated. A more efficient method is to extract the downloaded skin to its own empty directory. Take note of the textures it updates. Rename them (say with a 2 somewhere in the name). Copy the renamed texture files to the original materials directory (VV) and update the corresponding (and renamed) vmt files to point to the new textures. Now duplicate the VV model (and associated vtx files) and rename them, but place within the original models directly. Finally, edit the newly named mdl file, and only update those textures that came with your download (which you should have renamed). This allows you to re-use the unchanged textures (like teeth). 3) Changing Clothes with a Script (from console) You can change an NPCs model relatively easily using the SetModel() command: pc=__main__.FindPlayer() pc.SetModel("models/character/npc/unique/downtown/vv2/vv2.mdl") Above, I use the PC as an example, but it works on NPCs as well. Use the "picker" console command to get an NPCs instance name, then: npc=__main__.FindEntityByName("<npc_instance_name>") npc.SetModel("models/character/npc/unique/downtown/vv2/vv2.mdl") NOTES ON MULTIPLE SKINS: MDL files DO support multiple skins. An example is: Vampire\models\scenery\physics\cube\cube.mdl If you create this entity and assign it to the variable "cube" you can change its skin using the command: cube.FadeToSkin(1) Cube only requires 1 texture, but the MDL file has 4 textures. When an mdl file has more textures than a model needs, FadeToSkin(#) attempts to reload the skin starting with an offset that corresponds to #. If a model requires 5 textures to map to all of its parts, then all it need do to define an additional skin is define 10 internal texture links. The function npc.FadeToSkin(1) would offset the starting texture accordingly. If there were 15 textures defined, FadeToSkin(2) would apply the last 5, etc... However, VPKTool does not allow you to ADD textures to the MDL file, so there is no way to take advantage of the engines multiple skin support using VPKTool alone. There may be a way using more advanced external tools, but that is outside the scope of this tutorial. ------------------------------------------------------------------------------- VII.4 Editing Skins yourself ------------------------------------------------------------------------------- The instructions below assume you have extracted the contents of all the .vpk files somewhere accessible. For this example, we will create a WHITE business suit for Therese. The instructions also assume the installation of the latest Gimp: http://gimp-win.sourceforge.net/stable.html GIMP comes with a DDS plugin already installed that allows you to SAVE as DDS. If you use another image editing program, you will need to find a DDS plugin or a TGA to DDS stand alone conversion utility from the internet. A) Locate the original NPC's outfit: Normally the outfit skins are located under : <VTMB install dir>\Vampire\materials\models\character\npc Example : ...\Vampire\materials\models\character\npc\unique\santa_monica\therese B) Copy all outfit files to a working directory. I personally prefer a Desktop accessible directory such as: <Desktop>\work C) Use VPKTool to convert the TTZ files to TGA: - Run VPKTool - Click on the "Texture Tools" Tab - Browse to your working directory - Select a TTZ file. - Click on the "Convert TTZ to TGA" button. - Repeat C steps above for each TTZ file. E) Open the File up in Gimp (Or you image editing program of choice) In this specific example, we want to edit the files : businesssuit_body.tga businesssuit_skirt.tga For this example, we will do a simple color inversion. Find the "Free Select Tool" on the tool menu and outline the 3 items in the image that are obviously fabric. If you mess up, dont worry. You can "ADD" to the selection by holding down shift. Or you can "REMOVE" from the selection by holding down CTRL. Once the 3 fabric areas are selected, from the Menu, select : Colors -> Invert When you save, specifiy "DDS" as the file format you wish to save in. COMPRESSION : DXT5 [X] Generate mipmaps If this is not available (because your not using GIMP) You can save as TGA, but you will need to find a TGA to DDS conversion program from the internet. Open up the Skirt file and invert the entire image then save in the same fashion. E) Convert the DDS back into TTZ: - Run VPKTool - Click on the "Texture Tools" Tab - Browse to your working directory for your DDS files. - Check the Header Information: [X] Hint DXT5 - Click the "convert DDS to TTZ" button. This will create a new TTZ file AND update the local TTH file. You must maintain both. F) Backup and Copy Backup the original files that you edited. (Typically simply rename) and then paste the new TTZ /TTH files into the materials directory. Now when you see Therese, she will have a WHITE suit on instead of a dark brown suit. =============================================================================== VIII. > > > > Cameras and Cut scenes =============================================================================== TODO (I didn�t do any camera controls or cut scene work with my mod) =============================================================================== IX. > > > > Custom Items =============================================================================== ------------------------------------------------------------------------------- IX.1 Defining Custom items ------------------------------------------------------------------------------- Items are defined in: /Vampire/vdata/items I am not going to go into a lot of detail about the item format. Most of the time you find an item similar to what you want to add and re-use its properties. You can point to any existing mdl file the game has to offer for how it looks when it is sitting on the ground. A) The Bad News: You can not add additional items to the game. What I mean by that is the item names were hard coded into the games executable. The directory above contains configuration data for all the items, however if you simply paste a new item into the directory it will not show up or be accessible within the game. B) The Good News: A lot of items were embedded into the game that are not used. How do you know what is used and what isn't? Not an easy question. Personally, I install cygwin. Then cd to cygdrive/c/Program.../Vampire and then type: grep -Hir <item_name> . It takes about 3 min to run and when it is finished it tells me if the item is used by the game and where. (It is important that you have installed whatever patches you plan on building upon and extracted all the meta data out of the map .bsp files before you run the grep). Don't pay attention to hits on the .vpk files. What you care about is if it is in the metadata of a BSP map file, a python script (.py) or dialog file (.dlg). C) A few examples: These items are unused by the original game: item_p_occult_lockpicking item_g_ring_serial_killer_1 You can edit these items, there description, display model, etc... However, when embedding them within a map or accessing them from your scripting code, you will still have to refer to them by their original name. D) NOTES: There are a finite number of such items. Sometimes, adding a new item to the game means getting rid of something else. If you are building your mod on top of someone else's mod and you wish to add a special item, you must be certain that you do not use an item that one of your support mods depends on. Note the word "depend". If it is not necessary for the mods function, then you could remove the item from the mod so that you can use it. For example, WESP updated the 2 items above and embedded them into the game with the 5.6 Patch. However, they are not NEEDED by the game, so you could remove them if you needed them so that you could use them yourself. Wesp also freed up item_g_wireless_camera_2, item_g_wireless_camera_3 and item_g_wireless_camera_4 by changing the associated quest to only require 1 stackable camera. The companion mod which builds on top of Wesp's work, uses item_g_wireless_camera_2 and item_g_wireless_camera_3. However, of those two items, only the first is NECESSARY for the mods functionality. So if you really needed the slots, you could use both of the item slots above as well as camera_3 and camera_4. These are just examples and some of the considerations you should be aware of. ------------------------------------------------------------------------------- IX.2 Capturing Item usage events ------------------------------------------------------------------------------- The VTMB engine does not provide a nice way to capture events. I will discuss 2 approaches here which are work arounds. A) Basics : Defining Aliases and Binds Throughout this guide I have focused on Python. I have mentioned console commands, but generally only when they are needed to accomplish something that can't be done from python. Well this is one of those situations. The console supports the concept of aliases. An alias is like a tiny, one line program. It can execute console commands or python commands. ] alias foo "print 'This is a python command'" ] foo This is a python command The console also supports the concept of key bindings. A key can be bound to execute a string, or execute an alias: ] bind t " print 'You PRESSED T!'" ] bind f "foo" If you hide the console, then press t or f and then unhide the console, you will see messages printed. Finally, python supports the concept of KEYDOWN and KEYUP events through the use of "+" or "-" infront of an alias definition. ] alias +foo "print 'KEY DOWN!'" ] alias -foo "print 'KEY UP!'" ] bind f "+foo" +foo will execute when the f key goes down and -foo will execute when the f key comes up, even though you only bind f to +foo. B) Requiring users to setup special cfg values: So, one approach to capturing item usage events is to capture the attack event and then execute some code to see if you should do something special based on the weapon equipped. |--------------------------------------------------------------------| |Filename = [<vampire root>/Vampire/cfg/autoexec.cfg] | |--------------------------------------------------------------------| | // Required by Mod | | alias u_i_1 "__main__.ScheduleTask(0.0,'OnPlayerAttackStart()')" | | alias u_i_2 "__main__.ScheduleTask(0.0,'OnPlayerAttackEnd()')" | | alias +m_attack "u_i_1;+attack" | | alias -m_attack "u_i_2;-attack" | |--------------------------------------------------------------------| Notice the code above uses ScheduleTask. This forks the event off so that it returns quickly and prevents any python errors/exceptions from breaking the users ability to attack. |--------------------------------------------------------------------| |Filename = [<vampire root>/Vampire/python/vamputils.py] | |--------------------------------------------------------------------| | ... | | def OnPlayerAttackStart(): | | if __main__.FindPlayer().HasWeaponEquipped("item_my_item") | | print "THEY ARE USING MY EDITED ITEM/WEAPON!\n" | | # MAKE PC UNSEEN but unable to move (example) | | __main__.ccmd.notarget="" | | __main__.ccmd.player_immobilize="" | | | | | | def OnPlayerAttackEnd(): | | if __main__.FindPlayer().HasWeaponEquipped("item_my_item") | | print "THEY STOPPED USING MY ITEM/WEAPON!\n" | | __main__.ccmd.notarget="" | | __main__.ccmd.player_mobilize="" | | | |--------------------------------------------------------------------| The last part is the tricky part. For all of this to work, the USER must manually hook up the +m_attack you defined within their config.cfg so that your script receives the OnPlayerAttackStart and OnPlayerAttackEnd notifications. |--------------------------------------------------------------------| |Filename = [<vampire root>/Vampire/cfg/config.cfg] | |--------------------------------------------------------------------| | ... | | bind "MWHEELUP" "invprev" | | bind "MOUSE1" "+m_attack" // changed from bind "MOUSE1" "+attack" | | bind "MOUSE2" "vdiscipline_last" | | ... |--------------------------------------------------------------------| And this is the dilemma with this solution. It requires a user who is not afraid to get their hands dirty with a config.cfg file. Still it works and in some ways it is less intrusive since the user KNOWS what you are doing. C) AUTO Setup config values: Here, we use the same approach as A., however we do a little extra leg work so that the user doesn't have to do anything at all. For starters, we add a new line to your autoexec.cfg: |--------------------------------------------------------------------| |Filename = [<vampire root>/Vampire/cfg/autoexec.cfg] | |--------------------------------------------------------------------| | // Required by Mod | | alias execonsole "exec console.cfg" | | alias u_i_1 "__main__.ScheduleTask(0.0,' OnPlayerAttackStart()')" | | alias u_i_2 "__main__.ScheduleTask(0.0,' OnPlayerAttackEnd()')" | | alias +m_attack "u_i_1;+attack" | | alias -m_attack "u_i_2;-attack" | |--------------------------------------------------------------------| The new execonsole alias allows us to write console commands to a file and execute them from python. The idea is, when the game loads, we fix the attack binding to point to our custom binding. Easier said than done. For one thing, this involves reading in the config.cfg file, parsing it for "+attack" and then sending a command to the game to rebind the associated key with "+m_attack". And we must not forget that the player can bind multiple keys to +attack (MOUSE1 and the letter "q" for example) |--------------------------------------------------------------------| |Filename = [<vampire root>/Vampire/python/vamputils.py] | |--------------------------------------------------------------------| | ... | | def OnPlayerAttackStart(): | | ... | | | | def OnPlayerAttackEnd(): | | ... | | | | def FixAttackBinding(): | | data = '' | | fin = None | | try: | | fin = open('Vampire/cfg/config.cfg',"r") | | line = fin.readline() | | while line: | | s = line.rfind('"+attack"') | | if -1 != s: | | r = s | | s = line.find(' ') | | data='%sbind %s "+m_attack"\n' % (data,line[s:r].strip()) | | line=fin.readline() | | finally: | | if fin: fin.close() | | | | if 0 != len(data): | | cfg=open('Vampire/cfg/console.cfg', 'w') | | try: cfg.write(data) | | finally: cfg.close() | | __main__.ccmd.execonsole="" | | | | FixAttackBinding() | |--------------------------------------------------------------------| In this scenario, the binding is fixed when the game is started. If the user changes their config within the game, they would break the item until they restarted the game. However, telling a user they have to restart their game if they change the config isn't as complicated as walking them through editing their config.cfg file. D) Discovering what NPC is under the target hair: The game doesn't really support this, but here I talk about workarounds. There are two workarounds. The easy and the hard work around. The hard work around involves computing a range of values that represents a cone in the direction that the PC is facing and then grabbing the coordinates of all the NPCs on the map and seeing who is in that cone and who is closest. This actually isn't that hard if you treat the map as a 2D map, however when you bring the z axis into the equations (maybe pc is looking up at a balcony), the computations get more difficult. Other than the mathematical complexity, the other issue with this approach is that python is slow compared to C++. A much easier method is using a console command to change something about the npc under the crosshair and then examine all NPCs for the change. Once found, change the "something" back. The two console methods I was able to use with this method: npc_freeze npc_hearing_sensitivity #.# I mention 2, because if you are trying to make a freeze gun, you don't want to depend on NPCs who are frozen to identify who is under the target hair. On the other hand, freeze doesn't take parameters, so it is ideal as you can accomplish the grapple without having to write to the console.cfg file. Here is an example: |--------------------------------------------------------------------| |Filename = [<vampire root>/Vampire/python/vamputils.py] | |--------------------------------------------------------------------| | ... | | # STUN GUN... | | def OnUsedMyWeapon(targetNPC): | | print "TARGET [%s] " % targetNPC.GetName() | | targetNPC.Faint() | | | | def OnPlayerAttackStart(): | | if __main__.FindPlayer().HasWeaponEquipped("item_my_item") | | __main__.grapple=None | | __main__.ccmd.npc_freeze="" | | __main__.ScheduleTask(0.1,'OnGrappleNPC()') | | | | def OnPlayerAttackEnd(): | | pass | | | | def OnGrappleNPC(found=0): | | if found: | | OnUsedMyWeapon(__main__.grapple) | | else: | | npcs = __main__.FindEntitiesByClass("npc_V*") | | for npc in npcs: | | try: | | if (npc.playbackrate==0.00): | | __main__.grapple=npc | | __main__.ccmd.npc_freeze="" | | __main__.ScheduleTask(0.1,'OnGrappleNPC(1)') | | break; | | except: | | pass | | | | def FixAttackBinding(): | | ... | |--------------------------------------------------------------------| Using npc_hearing_sensitivity is basically the same, however you have to use the console function (See III.3) to send the initial command. npc_hearing_sensitivity 1.2 If an npc is found, you can use npc.TweakParam("HEARING 1") to fix without having to issue another console command. =============================================================================== X. > > > > Miscellaneous =============================================================================== A) Death When an NPC is killed, a copy of their instance name is placed in a global array called __main__.G.morgue[]. The IsDead() function ultimately looks up the name in the array to decide if someone is dead. B) Reserved Entity Names "!player" <- refers to the (potentially unnamed) player. "!playerController" <- refers to the (potentially unnamed) player controller if one exists. (There are used by cut scenes) "!dialogpartner" <- When you begin a conversation with someone, this refers to the person you are talking to. It only remains valid while the conversation is active. C) Special Embedded Entity Targets Some embedded entities have a model reference instead of a targetname. Model reference takes the form : *<number> ex: "*8". This represents the 8th instance of the class type at runtime. So if you ran FindEntitiesByClass() on the class of the embedded entity, *8 would correspond to the array[8] instance returned by the function. This means when you embed new data into map files, you should always append changes to the bottom so that you don't risk messing up other index references. =============================================================================== XI. > > > > Legalities =============================================================================== VTMB offers no provisional rights to mod developers. This has different implications in different regions, however the bottom line is this: YOU CAN'T MAKE ANY MONEY FROM YOUR MOD (ie : you can't sell it) This should not come as a surprise since free work is generally convention within the modding communities. Also, DO NOT distribute vampire.exe with your mod. Doing so is blatantly illegal and would be construed as distributing a pirated or "cracked" version of the game. =============================================================================== XII. > > > > Common Scenarios and Examples =============================================================================== A) Discovering a location on the map directly in front of or behind you: ------------------------------------------------------------------------ # USAGE : pc = FindPlayer() # loc = TraceLine(pc.GetOrigin(),pc.GetAngles()[1],50) def TraceLine(pos, angle, dist): from math import pi as _pi from math import cos as _cos, sin as _sin # degToRad : r = d/(360/2pi) xoffset = dist * _cos((angle/(360/(2*_pi)))) yoffset = dist * _sin((angle/(360/(2*_pi)))) return (pos[0]+xoffset, pos[1]+yoffset, pos[2]) B) Turn someone or something around 180 degrees. Calculates Facing. ------------------------------------------------------------------------ ## Usage : angle = pc.GetAngles()[1] ## facing=(0,RevAngle(angle),0) ## ## Param 1 = angle degrees as a decimal between 180 and -180 def RevAngle(angle): return (abs(((angle+180)/360)-0.5)*360)-180 C) Figure out if 2 objects are within a certain distance of each other in 3D. ----------------------------------------------------------------------------- ## USAGE : npc = FindEntityByname("VV") ## near = npc.Near(FindPlayer().GetOrigin()) ## ## Param 1 = location (x,y,z) ## Param 2 = radius [default 200] from __main__ import Character def _Near(self,loc,r=200): # Avoid square root function. very inefficient # if (Distance)^2 > (x2-x1)^2 + (y2-y1)^2 + (z2-z1)^2 loc2=self.GetOrigin() xd=loc2[0]-loc[0] yd=loc2[1]-loc[1] zd=loc2[2]-loc[2] return (r*r) > (xd*xd) + (yd*yd) + (zd*zd) Character.Near=_Near D) Like Traceline, but you can offset the point by an angle. IE: 90 would be the point directly to your right. angle 0 would be the same as TraceLine(). ---------------------------------------------------------------------------- ## USAGE : loc = FindPlayer().TraceCircle(50,90) ## ## Param 1 = distance from entity ## Param 2 = angle from entities current facing from __main__ import Character def _TraceCircle(self, radius=50, angleOffset=0): from math import pi as _pi from math import cos as _cos, sin as _sin pos = self.GetOrigin() angle = self.GetAngles()[1] + angleOffset # degToRad : r = d/(360/2pi) xoffset = radius * _cos((angle/(360/(2*_pi)))) yoffset = radius * _sin((angle/(360/(2*_pi)))) return (pos[0]+xoffset, pos[1]+yoffset, pos[2]) Character.TraceCircle=_TraceCircle E) Test if the PC is in stealth or not: --------------------------------------- ## USAGE : inStealth = FindPlayer().IsStealth() from __main__ import Character def _IsStealth(self): squating = ((self.GetCenter()[2] - self.GetOrigin()[2]) == 18) return (self.active_obfuscate or squating) Character.IsStealth=_IsStealth F) Dynamically spawn an entity --------------------------------------- ## USAGE : vv = SpawnEntity("MyVV") ## ## All but the first param is optional ## ## param 1 : entityName (string name of entity that you make it up) ## param 2 : entityType. VTMB internal classname. (def "npc_VVampire") ## param 3 : model. String specifying full internal model path. ## param 4 : distance in front of PC to create entity (def is 50) def SpawnEntity(entityName="", \ entityType="npc_VVampire", \ model="models/character/npc/unique/downtown/vv/vv.mdl", \ distance=50): pc = __main__.FindPlayer() position = pc.GetOrigin() angle = pc.GetAngles()[1] # calculate point in front of PC point = TraceLine(position,angle,distance) # reverse angle so npc is facing pc facing=(0,RevAngle(angle),0) ent = __main__.CreateEntityNoSpawn(entityType, point, facing ) try: ent.SetRelationship("player D_NU 0") except: pass try: ent.SetModel(model) except: pass try: ent.SetName(entityName) except: pass __main__.CallEntitySpawn(ent) return ent G) Dynamically spawn an NPC ---------------------------- ## USAGE : vv = SpawnNPC("MyVV") ## ## All but the first param is optional ## ## param 1 : npcName (string name of NPC that you make it up) ## param 3 : model. String specifying full internal model path. ## param 4 : distance in front of PC to create entity (def is 50) def SpawnNPC(npcName="", \ model="models/character/npc/unique/downtown/vv/vv.mdl", \ distance=50): ent = SpawnEntity(npcName,"npc_VVampire", model, distance) ent.LookAtEntityEye("!player") return ent H) Dynamically spawn a physics Object ------------------------------------- ## USAGE : stool = SpawnPhysics ("MyStool") ## ## All but the first param is optional ## ## param 1 : objectName (string name of object that you make it up) ## param 3 : model. String specifying full internal model path. ## param 4 : distance in front of PC to create entity (def is 50) def SpawnPhysics(propName="", \ model="models/scenery/structural/society/stool.mdl", \ distance=50 ): return SpawnEntity(propName,"prop_physics", model, distance) I) Teleporting and Moving NPCs ------------------------------------- 1) Removing/Hiding All entities support the Kill() function, which removes the entity from the game world completely. ent.Kill() Alternatively, you can set an Entity to hidden: ent.ScriptHide() And then Unhide it when you want to: ent.ScriptUnhide(). Hide/Unhide result in physical entities completely disappearing. Other methods include setting the model to NULL ent.SetModel("models/null.mdl") Changing a model to null allows an entity to continue to fire events Without being seen. 2) Moving and Teleporting Entities If you want your entity to WALK somewhere, most entites have a WalkToNode() method. The easiest way to move an entity is to use the SetOrigin() method npc = FindEntityByName("Ugly Dude") npc.SetOrigin(FindPlayer().GetOrigin()) =============================================================================== < < < < < FREQUENTLY ASKED QUESTIONS > > > > > =============================================================================== Q: Why aren't there any Frequently asked questions? A: Because this is the first release of the Guide. =============================================================================== < < < < < VTMB LINKS > > > > > =============================================================================== 1) http://www.vampirebloodlines.com/ The official site of VTMB. 2) http://www.planetvampire.com If you want to talk about the game, the forums here can't be beat. 3) http://www.patches-scrolls.de/vampire_bloodlines.php Wesp's Unofficial Patch Website: 4) http://www.strategyinformer.com/pc/mods/..." Strategy Informers VTMB Page. 5) http://www.fileplanet.com/94454/0/section/Vampire:... FilePlanet's VTMB Page 6) http://browse.files.filefront.com/Vampire... FileFront's VTMB Page 7) http://www.gamebanshee.com/vampirebloodlines/ Game Banshee's VTMB Page 8) http://www.tessmage.com/ Tess specializes in Skin's and even supports his own Unofficial Patch. 9) http://vh.noirscape.org/files.php?cat=2 Vampire Heaven (Dedicated to Vampires). Includes some VTMB stuff: 10) http://paine.planetvampire.gamespy.com/?action=files What can I say? It is Pain's website dediicated to VTMB. Doesn't look like it has been updated for almost 3 years, but it has a few unique downloads. 11) http://vampirebloodlines.ru/combat/files/ A russian fan site. You can translate it using google's translation service. here 12) http://corellon.clandlan.net/index.php?page=corellon/vtmb/index A spanish fan site. You can translate it using google's translation service. here 13) http://www.vampire-network.net/ A french fan site. You can translate it using google's translation service. here =============================================================================== < < < < < APPENDICES > > > > > =============================================================================== ------------------------------------------------------------------------------- A. > > > > Entity Classes ------------------------------------------------------------------------------- ai_script_conditions logic_choreographed_scene aiscripted_schedule logic_npc_condition aiscripted_sequence logic_pythoncheck ambient_generic logic_relay ambient_location logic_squad_condition ambient_soundscheme logic_timer camera_cinematic logic_visibility_test camera_keyframe math_counter camera_track move_rope env_beam mover_keyframe env_fade npc_VAndreiBlood env_floating_camera npc_VAsianVampire env_particle npc_VBach env_particle_hud npc_VBrujah env_physexplosion npc_VCamera env_physimpact npc_VCameraSecurity env_shake npc_VChangBrosBlade env_shooter npc_VChangBrosClaw env_spark npc_VCop env_sprite npc_VDialogPedestrian env_steam npc_VGargoyle events_player npc_VGhoulCroucher events_world npc_VHengeyokai filter_activator_class npc_VHuman filter_activator_feat npc_VHumanCombatPatrol filter_activator_inventory npc_VHumanCombatant filter_activator_mass npc_VLasombra filter_activator_name npc_VManBat filter_multi npc_VMingXiao func_areaportal npc_VNewscaster func_areaportalwindow npc_VPedestrian func_breakable npc_VProneDialog func_breakable_surf npc_VRat func_brush npc_VSabbatGunman func_button npc_VSabbatLeader func_door npc_VScurrying func_door_rotating npc_VSheriffMan func_dustmotes npc_VTaxiDriver func_elevator npc_VTzimisce func_illusionary npc_VTzimisceHeadClaw func_keyframed_mover npc_VTzimisceRunner func_lod npc_VVampire func_monitor npc_VVampireBoss func_movelinear npc_VWerewolf func_particle npc_VYukie func_physbox npc_VZombie func_pushable npc_maker func_rotating npc_maker_fleshpile game_sign npc_maker_zombie game_text npc_payphone game_ui params_explosion hud_timer params_particle info_hint phys_animlink info_landmark phys_ballsocket info_node phys_constraint info_node_bach_run_1 phys_convert info_node_bach_run_2 phys_hinge info_node_bach_teleport_1 phys_thruster info_node_bach_teleport_2 point_camera info_node_bach_teleport_3 point_explosion info_node_bach_teleport_4 point_target info_node_chang_column point_teleport info_node_chang_jumpbase prop_button info_node_chang_ledge prop_clockhand info_node_chang_teleport prop_destructable info_node_climb prop_doorknob info_node_cover_corner prop_doorknob_electronic info_node_cover_low prop_dynamic info_node_cover_med prop_dynamic_ornament info_node_crosswalk prop_hacking info_node_hint prop_haunted info_node_kick_over prop_keypad info_node_link prop_largehull_ignore info_node_manbat_fly_to_point prop_mover info_node_patrol_point prop_padlock info_node_sabbat_arch prop_physics info_node_sabbat_bottom prop_physics_contested info_node_sabbat_dive prop_radio info_node_sabbat_hide prop_ragdoll info_node_sabbat_nojump prop_sign info_node_sabbat_top prop_slashable info_node_shoot_at prop_switch info_node_tzimisce scripted_sequence info_node_werewolf security_camera info_node_werewolf_hint sky_camera info_player_start trigger_autosave info_target trigger_bomb_site info_teleport_destination trigger_changelevel infodecal trigger_checkvolume inspection_brush trigger_discipline_context inspection_node trigger_electric_bugaloo intersting_place trigger_environmental_audio intersting_place_conversation trigger_hurt item_container trigger_impact item_container_animated trigger_inventory_check item_container_lock trigger_look keyframe_rope trigger_multiple light trigger_once light_dynamic trigger_player_activity_level light_environment trigger_push light_spot trigger_small_hull logic_auto trigger_stealth_mod logic_case trigger_teleport logic_case_toggle trigger_werewolf_zone Developer Notes: How did I come up with this list? 1) Used VPKTool to extract all maps (BSD) to txt files under meta directory. 2) Installed CYGWin 3) cd cygdrive/c/Program Files.../Vampire/maps/meta/ 4) cat `ls` | grep "classname" | sort | uniq > all.txt ------------------------------------------------------------------------------- B. > > > > Map Names ------------------------------------------------------------------------------- 1) Santa Monica 4) Hollywood sm_pawnshop_1 hw_609_1 sm_apartment_1 hw_ash_sewer_1 sm_asylum_1 hw_asphole_1 sm_bailbonds_1 hw_cemetery_1 sm_basement_1 hw_chinese_1 sm_beachhouse_1 hw_hub_1 sm_diner_1 hw_jewelry_1 sm_embrace_1 hw_luckystar_1 sm_gallery_1 hw_metalhead_1 sm_gallery_1_particle_test hw_netcafe_1 sm_hub_1 hw_redspot_1 sm_hub_2 hw_sewer_1 sm_junkyard_1 hw_sinbin_1 sm_medical_1 hw_tawni_1 sm_oceanhouse_1 hw_vesuvius_1 sm_oceanhouse_2 hw_warrens_1 sm_pier_1 hw_warrens_2 hw_warrens_3 sm_shreknet_1 hw_warrens_4 sm_tattoo hw_warrens_5 sm_vamparena sm_warehouse_1 5) Chinatown ch_hub_1 2) Los Angeles ch_cloud_1 la_abandoned_building_1 ch_dragon_1 la_bradbury_2 ch_fulab_1 la_bradbury_3 ch_glaze_1 la_chantry_1 ch_lotus_1 la_confession_1 ch_ramen_1 la_crackhouse_1 ch_sewer_1 la_dane_1 ch_shrekhub la_empire_1 ch_temple_1 la_empire_2 ch_temple_2 la_empire_3 ch_temple_3 la_expipe_1 ch_temple_4 la_hospital_1 ch_tsengs_1 la_hub_1 ch_zhaos_1 la_malkavian_1 la_malkavian_2 6) Special la_malkavian_3 sp_boat la_malkavian_4 sp_camwarehouse la_malkavian_5 sp_epilogue la_museum_1 sp_genesisdevice_1 la_parkinggarage_1 sp_giovanni_1 la_PlagueBearer_Sewer_1 sp_giovanni_2a la_sewer_1 sp_giovanni_2b la_skyline_1 sp_giovanni_3 la_ventruetower_1 sp_giovanni_4 la_ventrueTower_1b sp_giovanni_5 la_ventruetower_2 sp_lonewolf_1 la_ventruetower_3 sp_masquerade_1 sp_ninesintro 3) E3 sp_observatory_1 e3_chinese_1 sp_observatory_2 E3_Combat sp_smut E3_confession_1 sp_soc_1 E3_hub_1 sp_soc_2 sp_soc_3 sp_soc_4 sp_taxiride sp_theatre sp_tutorial_1 Developer Notes: How did I come up with this list? Answer) Vampire/vdata/system/mapnames_normalized.txt You can also see a list of most maps from console by Typing "maps". Not all of these maps are recognized (ie : nothing with sewer, smut, boat). ------------------------------------------------------------------------------- C. > > > > Item Name Summary ------------------------------------------------------------------------------- item_a_body_armor item_k_hitman_ji_key item_a_hvy_cloth item_k_hitman_lu_key item_a_hvy_leather item_k_kiki_key item_a_lt_cloth item_k_leopold_int_key item_a_lt_leather item_k_lilly_key item_d_animalism item_k_lucky_star_murder_key item_d_dementation item_k_malcolm_office_key item_d_dominate item_k_malkavian_refrigerator_key item_d_holy_light item_k_murietta_key item_d_thaumaturgy item_k_museum_basement_key item_g_animaltrainingbook item_k_museum_office_key item_g_astrolite item_k_museum_storage_key item_g_bach_journal item_k_museum_storeroom_key item_g_badlucktalisman item_k_netcafe_office_key item_g_bailbond_receipt item_k_oceanhouse_basement_key item_g_bertrams_cd item_k_oceanhouse_sewer_key item_g_bloodpack item_k_oceanhouse_upstairs_key item_g_bluebloodpack item_k_oh_front_key item_g_brotherhood_flyer item_k_sarcophagus_key item_g_car_stereo item_k_shrekhub_four_key item_g_cash_box item_k_shrekhub_one_key item_g_chewinggum item_k_shrekhub_three_key item_g_computerbookhighgrade item_k_skyline_haven_key item_g_computerbooklowgrade item_k_tatoo_parlor_key item_g_driver_license_gamble item_k_tawni_apartment_key item_g_drugs_drug_box item_k_tutorial_chopshop_stairs_key item_g_drugs_morphine_bottle item_m_money_clip item_g_drugs_perscription_bottle item_m_money_envelope item_g_drugs_pill_bottle item_m_wallet item_g_edane_print_report item_p_gargoyle_talisman item_g_edane_report item_p_occult_blood_buff item_g_eldervitaepack item_p_occult_dexterity item_g_eyes item_p_occult_dodge item_g_gargoyle_book item_p_occult_experience item_g_garys_cd item_p_occult_frenzy item_g_garys_film item_p_occult_hacking item_g_garys_photo item_p_occult_heal_rate item_g_garys_tape item_p_occult_lockpicking item_g_ghost_pendant item_p_occult_obfuscate item_g_giovanni_invitation_maria item_p_occult_passive_durations item_g_giovanni_invitation_victor item_p_occult_presence item_g_guy_magazine item_p_occult_regen item_g_hannahs_appt_book item_p_occult_strength item_g_hatters_screenplay item_p_occult_thaum_damage item_g_horrortape_1 item_p_research_hg_computers item_g_horrortape_2 item_p_research_hg_dodge item_g_idol_cat item_p_research_hg_firearms item_g_idol_crane item_p_research_hg_melee item_g_idol_dragon item_p_research_lg_computers item_g_idol_elephant item_p_research_lg_dodge item_g_jumbles_flyer item_p_research_lg_firearms item_g_junkyard_businesscard item_p_research_lg_stealth item_g_keyring item_p_research_mg_brawl item_g_larry_briefcase item_p_research_mg_finance item_g_lilly_diary item_p_research_mg_melee item_g_lilly_photo item_p_research_mg_security item_g_lilly_purse item_s_physicshand item_g_lillyonbeachphoto item_w_avamp_blade item_g_linedpaper item_w_baseball_bat item_g_locket item_w_baton item_g_lockpick item_w_bush_hook item_g_mercurio_journal item_w_chang_blade item_g_milligans_businesscard item_w_chang_claw item_g_oh_diary item_w_chang_energy_ball item_g_pisha_book item_w_chang_ghost item_g_pisha_fetish item_w_claws item_g_pulltoy item_w_claws_ghoul item_g_ring03 item_w_claws_protean4 item_g_ring_gold item_w_claws_protean5 item_g_ring_serial_killer_1 item_w_colt_anaconda item_g_ring_serial_killer_2 item_w_crossbow item_g_ring_silver item_w_crossbow_flaming item_g_sewerbook_1 item_w_deserteagle item_g_stake item_w_fireaxe item_g_vampyr_apocrypha item_w_fists item_g_vv_photo item_w_flamethrower item_g_wallet item_w_gargoyle_fist item_g_warr_clipboard item_w_glock_17c item_g_warr_ledger_1 item_w_grenade_frag item_g_warr_ledger_2 item_w_hengeyokai_fist item_g_warrens4_passkey item_w_ithaca_m_37 item_g_watch_fancy item_w_katana item_g_watch_normal item_w_knife item_g_werewolf_bloodpack item_w_mac_10 item_g_wireless_camera_1 item_w_manbat_claw item_g_wireless_camera_2 item_w_mingxiao_melee item_g_wireless_camera_3 item_w_mingxiao_spit item_g_wireless_camera_4 item_w_mingxiao_tentacle item_i_written item_w_occultblade item_k_ash_cell_key item_w_rem_m_700_bach item_k_carson_apartment_key item_w_remington_m_700 item_k_chinese_theatre_key item_w_sabbatleader_attack item_k_clinic_cs_key item_w_severed_arm item_k_clinic_maintenance_key item_w_sheriff_sword item_k_clinic_stairs_key item_w_sledgehammer item_k_edane_key item_w_steyr_aug item_k_empire_jezebel_key item_w_supershotgun item_k_empire_mafia_key item_w_thirtyeight item_k_fu_cell_key item_w_throwing_star item_k_fu_office_key item_w_tire_iron item_k_gallery_noir_key item_w_torch item_k_gimble_key item_w_tzimisce2_claw item_k_hannahs_safe_key item_w_tzimisce2_head item_w_tzimisce3_claw item_w_tzimisce_melee item_w_unarmed item_w_uzi item_w_werewolf_attacks item_w_wolf_head item_w_zombie_fists weapon_physcannon weapon_physgun weapon_pistol Developer Notes: How did I come up with this list? Answer) Vampire/vdata/system/items/ ------------------------------------------------------------------------------- D. > > > > Game States (Thanks to wesp for this list) ------------------------------------------------------------------------------- -3 Tutorial. -2 Tutorial done, transition to haven. -1 Entered haven. 0 Entered Santa Monica. 1 Convinced Trip to sell you guns. 2 Showing Elysium tip for the first time (temporary). 3 Showing combat tip for the first time (temporary). 5 Spoke with Beckett at warehouse. 10 Entered downtown. 15 Elizabeth Dane completed. 20 Met Bach at Grout's mansion. 25 Returned from Grout's mansion. 30 Spoke with Beckett at Museum. 35 Returned to prince from Museum. 40 Spoke with Andrei (added by wesp). 45 Spoke with Gary. 50 Mandarin started experiments. 55 Rescued Barabus. 60 Spoke with Chang brothers (added by wesp). 65 Returned to prince from Giovanni mansion. 70 Spoke with Johansen. 75 Returned to prince from Leopold Society. 80 Spoke with Ming-Xiao after Hallowbrook. 85 Spoke with Prince about Ming-Xiao. 90 Spoke with Jack after Griffith park. 95 Spoke with end-game cabbie. 100 Cabbie takes you to Chinatown (Kuei-Jin ending). 105 Not used. 110 Cabbie takes you to Prince (Prince ending). 115 Cabbie takes you to Anarchs (Anarch ending). 120 Cabbie takes you to Chantry (Camarilla ending). 125 Cabbie takes you to Chinatown (Solo ending). ------------------------------------------------------------------------------- E. > > > > Common Models ------------------------------------------------------------------------------- http://docs.google.com/Doc?id=dhgs89mq_3gtwn2chb ------------------------------------------------------------------------------- F. > > > > VCLAN Values (Patch 1.2) ------------------------------------------------------------------------------- http://docs.google.com/Doc?id=dhgs89mq_4fgq4nrfg ------------------------------------------------------------------------------- F.5.6 > > > > VCLAN Values (Patch 5.6) ------------------------------------------------------------------------------- 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 Variables and Commands ------------------------------------------------------------------------------- http://docs.google.com/Doc?id=dhgs89mq_10cfs83dqp =============================================================================== < < < < < Contributing Authors > > > > > =============================================================================== Initial Guide Creation: - Dheu General Advice: - Wesp =============================================================================== < < < < < Final Words.... > > > > > =============================================================================== The material presented in this Guide is the result of six months of trial and error with VTMB as I built my own mod. It includes the basics, the lessons that I learned, the bugs that I discovered and the workarounds for those limitations that I once thought would be show stoppers. One thing that I learned over the months is that there is SO MUCH that I still down known about this game. It is by no means complete and I invite others to share their knowledge with the community at large. Feel free to email me if you have a contribution to make to this Guide. If it is small, I can add it myself or if it is larger, I an give you temporary write/update access to the document. My ultimate hope is that this Guide will encourage other developers to create new adventures or add new game enhancements.