_________________________________________
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
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</pre><pre id="faqspan-2">
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:
</pre><pre id="faqspan-3">
{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)
-------------------------------------------------------------------------------</pre><pre id="faqspan-4">
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
</pre><pre id="faqspan-5">
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.