Rage(PC) Modding Notes

来源:互联网 发布:淘宝店运营视频教程 编辑:程序博客网 时间:2024/05/01 06:25
从rage狂怒的mod笔记资料来看中看的出来,跟doom3和quake3的很多东西已经完全不一样了,我觉得乃是在doom3源代码的基础之上,不断的改进,基本上大部分都改动过了


________________________________________ 

Rage(PC) 

Modding Notes 

_________________________________________ 
?

Nov 19, 2014 
Draft 1.01 

Written by: Dheu 

______________________________________Notes____________________________________ 

This Document looks best in a fixed-width font, such as Courier New. 

------------------------------------------------------------------------------- 
Table of Contents: 
------------------------------------------------------------------------------- 

I. Basics 
I.1 Requirements 
I.2 Recommendations 
I.3 Game Architecture 
I.4 Using the Game Console 
I.5 Console Variables 
I.6 Standard Tools 
I.7 Best Practices 
I.8 Hard verus Easy changes 
II. Getting Started 
II.1 Installation 
II.2 Setting up Software Change Management 
II.3 Fix Bugs 
III. Hello World Popup Message 
III.1 Adding Strings 
III.2 Tutorial Messages 
III.3 Firing a Job event 
IV. Scripting 
IV.1 Basics 
Iv.2 Common Commands 
Iv.3 Action Script 
IV.4 Map Scripts and Event Handlers 
IV.5 Global Scripts and Event Handlers 
IV.6 Persisting data 
IV.7 Global Script Memory Limits 
IV.8 DECL Pathing 
V. Editing and Customizing NPCs 
V.1 Mr. Potato Head style 
V.2 Deforming Models with Blender 
V.3 Animations 
VI. Adding Textures 
VI.1 Simple Textures (ie for Icons) 
VI.2 Textures for models 
VI.3 Splash Screens for Maps 
VII. Adding Sounds and Audio 
VIII. NPCs 
VIII.1 Followers 
VIII.2 Setting up Interaction/Activation Handlers 
VIII.3 Merchants 
VIII.4 Animations 
IX. Building, Dependencies and Deployment 
IX.1 Map Mods 
IX.2 Asset/Script Mods 
IX.2 When it fails 

X. External Appendix 
A. Console Commands 
B. Console Variables 
C. ID Script Full listing 
D. Script operations 


=============================================================================== 
I. > > > > Basics 
=============================================================================== 

The Rage Tool Kit (ID Studio) comes with several tutorials that focused on 
making new maps from the assets that ship with Rage. These notes therefore 
do not cover the subjects addressed by those tutorials. Rather these notes 
cover subjects outside the scope of the tutorials that come with Rage Tool  
Kit. 

------------------------------------------------------------------------------- 
I.1 > > > > Requirements 
------------------------------------------------------------------------------- 

To use the Rage Tool Kit, you must own Rage and have it installed. If you do 
not have Rage installed, the toolkit will not work. 

Rage Tool Kit Requires a 64 bit OS and a minimum of 2 Gigs of Ram. It will 
bail immediately if a 32 bit Operating System is detected. 

Rage can be purchased through Steam: http://store.steampowered.com/ 

------------------------------------------------------------------------------- 
I.2 > > > > Recommendations 
------------------------------------------------------------------------------- 

I would recommend no less than 16 GB of RAM and 32GB + if you are 
making/modding maps. A solid state Hard drive is also highly recommend, 
but not required. 

------------------------------------------------------------------------------- 
I.3 > > > > Game Architecture 
------------------------------------------------------------------------------- 


1) The game engine: 

You generally don't touch this part. It handles rendering 
the 3D environment, managing input, output and events. Rage 
uses ID Tech 5. 

2) Map files: 

Map files serve 2 purposes. They define WHERE stuff goes and they 
also store off HIGH LEVEL INSTANCE DATA about the objects embedded 
within. The instance data can include customized initialization 
and spawn information. 

"Stuff" includes characters, trigger zones, sound emitters, 
invisible cameras and patrol path nodes. If you have experience modding 
other games such as Skyrim or Never Winter Nights, some of these 
concepts should sound familiar. 

For those that are new to this, I will simply say that there are a lot 
of invisible objects hidden in maps that you can't see that help 
make things easier for game play. 

3) Models: 

Model files provide the look and feel of objects when rendered 
within the Map. These are read in and injected into the map at run 
time. Maps contain "pointers" to these models, but not the model data 
itself. 

Model files come in 2 flavors. lwo files (Light Wave Objects) and 
MD6 files. The primary difference is that LWO files are simply 
meshes and textures. MD6 files are more complicated. They are 
similar to maps in that they mostly contain pointers to lots of 
other information... like what textures to use, what skeleton 
to use, what mesh to use and what animations to use. 

While MD6 files can be used to describe inanimate objects 
like rocks, they are generally used to describe the more complicated 
objects with moving parts... like cars and NPCs. 

MD6 is discussed in more detail below. 

4) Textures: 

ID Tech 5 uses TGA files for textures. Raw textures often consist 
of many small files. An NPC's hat for example may have its own 
dedicated texture file. 

But when you build a map, often those small textures will get 
pulled together and merged into a larger "megatexture" that can 
be paged and streamed into memory as needed. 

5) 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. Rage supports wav files for background music and ambient 
sounds. There are also rumors that it supports 192 Khz ogg files, 
but this isn't confirmed. However, the engine will convert whatever 
audio you provide to MS-ADPCM format when it builds your mod. 

6) Scripts 

ID Tech 5 comes with its own comprehensive scripting language. Generally 
scripts are used as event handlers, helping to coordinate complex 
scenes and interactions. 

------------------------------------------------------------------------------- 
I.4 > > > > Using the Game Console 
------------------------------------------------------------------------------ 

Anytime you run Rage in 64 bit mode (With mod support), console is enabled. 

The console comes with hundreds of commands to help developers and players 
alike. Type "listCmds" to see a full list (or go to the bottom of these 
notes). 

My Favorite: 

find <string> 

very useful command. Searches animations, models, commands, 
cvars and images for anything containing the string. So if 
you want to know what map commands are available for example, 
you might try: 

i.e.: find map 

Other Example commands: 

Notarget 

Enemies do not see you (invisibility) 

Noclip 

Walk through walls. More importantly, disables the activation of 
triggers. 

map <map name> 

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. 

selectDebugEntity 

Selects whatever entity is below the crosshair as the debug 
entity (for other commands that affect the selected entity) 

list items 

list is a command, but "items" is actually an incorrect 
parameter. However, this will cause the console to list 
valid entity types, which is far more useful feedback 
than the default feedback. 

ie: list inventoryItem 

give (item) # 

give the player an item # times 

ie : give inventory/currency/currency_unit 999 


------------------------------------------------------------------------------- 
I.5 > > > > Console Variables 
------------------------------------------------------------------------------ 

Console variables are very similar to console commands. Console variable 
control parameters about how the current game engine is running. Type 
"listCvars" to see a full list (or see Appendix) 

My Favorite Cvar: 

g_showEntityInfo 

When set to 1, you will see a blue box around game entities 
within the game telling you what their instance ID is. This 
can come in handy for other commands that allow you to 
target entities. Especially when designing scripts for 
maps that we never received the source code for. 

Very useful, even during normal game play and it brings 
attention to hidden items and doors. 

Other Example Cvars: 

ai_enable 

Similar to the Notarget command. However, this disables NPC's 
brains all together. So they don't move, they don't take damage, 
etc... 

g_showHud 0 

Turn off the game hud. Can be handy if you are recording a 
video. 

g_stopTime 1 

Freeze time (except for you). 

------------------------------------------------------------------------------- 
I.6 > > > > Standard Tools 
------------------------------------------------------------------------------- 

If you want to actually deform Rage NPCs , you will need additional software. 
Gimp is the best free image editing program that I know of for updating 
textures. As for model deformation, Blender3D is probably the best free 
option. As working with rage involves dealing with a lot of metadata 
files, I also recommend a good text editing program like Notepad++ 

GIMP: 

http://www.gimp.org/ 

Blender: 

http://www.blender.org/ 

NotePad++: 

http://notepad-plus-plus.org/ 


------------------------------------------------------------------------------- 
I.7 > > > > Best Practices 
------------------------------------------------------------------------------- 

A general best practice that is universal when it comes to modding games is 
to avoid changing original game resources when possible. Break your changes 
out into new directories and files names and raw resources. 

Most games that support modding provide hooking mechanisms so that you don't 
have to edit the original game resources to make things happen. This is 
true for most of Rage modding (but not all). 

As you mod rage, you will discover most raw resources have an associated  
metadata file that "points" to the raw resource. 

.m2 : Textures 
.md6mesh : Model Mesh 
.md6anim : Model Animations 
.md6def : Binds Meshes to Animations 
.def : Entity Definitions 
.shdsnd : Sound (wav) 
.vtr : Vocal Track (lip synched) 
.tdef : Type definitions (Jobs, behaviors, etc...) 
etc.... 

You can create your own metadata files and Rage will simply import them when 
it starts up. In some cases, Rage will reload/refresh directories without 
having to restart the toolkit. 

Part of the reason I install version control software is so that I can see 
what files are edited by my actions and even what lines are created in those 
files. Then I can create my own files named after my mod and move those 
changes out of the original games files. 

This helps in several ways. It makes it easier to identify where my changes 
are. It minimizes the chance of conflicts with other mods and it eases the 
burden of merging two mods if a decision is ever made to go that route. 

------------------------------------------------------------------------------- 
I.8 > > > > Hard versus easy Changes 
------------------------------------------------------------------------------- 

When you are making your own map, you can do just about anything you  
want. However, changing an original game map is much more difficult, 
especially if your goal is to introduce a change that affect multiple 
maps. 

When ID studio compiles a map, it includes pointers to entities such 
as NPCs. However the map also includes high level entity information 
embedded within the map itself. Basically "instance" data. 

For example, the type of information defined within : 

decls -> entityDefs ... 

So take this scenario: 

- You make a mod with a custom map where you embed Ghost Bandits that are 
smart and tough. 

- Someone else makes a mod with a custom map where they embed Ghost 
Bandits that are dumb and weak. 

If a user installs both mods, both mods will work fine because the 
ai and damage susceptibility are defined at a high level and included 
with the instance data embedded in the map. 

That may sound great... until you realize this same protection prevents 
you from (easily) changing the ai or damage susceptibility of any of the 
monsters in the original game maps because you don't have the original maps 
to recompile them with your changes. 

When editing the original game, you are more or less limited to 
global scripting changes which are applied at runtime. For example, 
you could update script_main.script to call a custom method that 
you write (it is called every time a user enters a new map). Your 
method could examine the map name and the active layers and take actions 
based on the two. 

Most rage maps have enemy instances embedded into them and each instance 
has a unique ID. So if you had a list of maps and instances, you could 
use script commands to alter the enemies on a map (or add new enemies 
or simulate nightmare "dynamic spawning" enemies when current enemies 
die, etc....) 

However there is a major drawback to this as well: 

Only 1 mod can edit the script_main.script, which is where global 
changes are defined. So if a user wishes to install 2 mods that edit 
the original game with scripts, one of those mods will break. 

The good news is, that only high level information is embedded. The 
EntityDef points to many lower level support resources and those resources 
are NOT embedded into the maps. For example, MD6 resources define the  
mesh and animations of the NPC. So you can change the look of all Ghost  
Bandits in the original game, but you couldn't add additional Ghost 
bandits to a map without scripts. 

Other low level (global) entities that are safe for editing if 
you are targeting the original game: 

- inventory 
- weapons 
- ammo 
- engineering schematics 
- md6 (models) 
- m2 (materials/textures) 
- ** Jobs 
- sounds/voicetracks 

** Jobs are global, but they are generally started by map scripts/objects 
or other (already existing) jobs. You can add new jobs to the game 
with minimal edits, but you will have to edit an existing job to 
do so, which means the possibility of mod conflicts with other mods. 

As stated, if you are making your own maps, then these concerns don't really 
apply to you. 

=============================================================================== 
II. > > > > Getting Started 
=============================================================================== 

------------------------------------------------------------------------------- 
II.1 > > > > Installation 
------------------------------------------------------------------------------- 

Ensure Rage is installed and then open up Steam. 

Go to the "Library" tab. 

Notice at the top of the page next to the Search box, there is a combo box  
which normally reads "All Games". Click on "All Games" and change it to 
"Tools" 

Scroll down and right click on the Rage Tool Kit and select "Install Game..." 

DO NOT run the Toolkit after install is done if you want an early version 
controlled snapshot. 

------------------------------------------------------------------------------- 
II.2 > > > > Setting up Software Change Management 
------------------------------------------------------------------------------- 

1) Install a FRESH COPY of The Rage Toolkit (See Part I. above) 

2) Install tortoiseSVN 
- Go to http://tortoisesvn.tigris.org/ 
- Download correct version 
- Install, use defaults 
- Restart Computer 

3) Create Repository 
- Open Windows Explorer 
- Create a new folder and name it. For example: C:\svnrepo (Drive should 
have at least 100 Gigs of free space) 
- Right-click on the newly created folder and select: 
TortoiseSVN -> Create repository here... 
- Select "Create folder Structure" 
- [OK] -> [OK] 

A repository is then created inside the new folder. DO NOT EDIT THOSE 
FILES YOUSELF! 

4) Import the Rage Toolkit directory into the Repository: 

- Using Explorer, browser to: 

C:\Program Files (x86)\Steam\steamapps\common 

- Right Click "rage tool kit" 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. 

5) Check Out the Code Base: 

- Using Explorer, browser to: 

C:\Program Files (x86)\Steam\steamapps\common 

- Right Click "rage tool kit" and DELETE it. 
- Using Explorer, browser to: 

C:\Program Files (x86)\Steam\steamapps 

- Right Click "common" 
- Select "SVN checkout..." 
- Update "Checkout Directory" : 
C:\Program Files (x86)\Steam\steamapps\common\rage tool kit 
- It will begin the checkout process. Find something else to do for 1.5 

6) Set up Virtual Mount: 

Many rage resources have the path "W:\Rage" hard coded into them as the 
location of the rage files. It is unclear if this may cause problems, 
however it is trivial to set up a virtual mount and makes accessing 
the tool kit much easier. 

Start -> [Search Programs and Files] <- Enter "cmd" 
cd c:\ 
mkdir RageFix 
subst W: C:\RageFix 
cd W: 
mklink /D Rage "C:\Program Files (x86)\Steam\steamapps\common\rage tool kit" 

If you close the window, when you open explorer, you should now see a 
W: drive in your listing. 


7) Finally, it is time to start up rage tool kit and extract all the resources: 
- Steam -> Library -> Change "All Games" to "Tools" 
- Right click Rage ToolKit -> Play Game 
- When the startup window appears hit Enter 
- When you see the ID Studio menu appear, select "ID Studio" 
- If it is your first time, you will get a message about your default 
layout being changed. 
- Exit the game and restart 
- When the startup window appears hit tilde (`\~) and enter the command: 

exec buildAssets.cfg 

- Wait 20 to 60 minutes while it uncompressed resources 

8) Rage will likely close. I believe it is suppose to restart itself, but it 
tends to fail. So... As painful as it is, you need ot start it up and 
type buildAssets.cfg yet again. 

- Steam -> Library -> Change "All Games" to "Tools" 
- Right click Rage ToolKit -> Play Game 
- When the startup window appears hit (`) and enter the command: 

exec buildAssets.cfg 

- When you find that running the script creates a progress bar the 
finishes rather quickly and the toolkit auto closes within 2 or 3 
minutes... it is time to start it up without using the buildAsset.cfg 
command: 

- Steam -> Library -> Change "All Games" to "Tools" 
- Right click Rage ToolKit -> Play Game 
- When the startup window appears hit Enter 
- When you see the ID Studio menu appear, select "ID Studio" 

You should see the splash say something about "finishing up". 

9) Add Dynamic Resources to SVN Ignore: 

- Using Explorer, browse to: 

C:\Program Files (x86)\Steam\steamapps\common\rage tool kit 

- Right Click "virtualtextures" and select: 

TortoiseSVN -> Unversion and Add to Ignore List -> "virtualtextures" 

- Right Click "mods" and select: 

TortoiseSVN -> Unversion and Add to Ignore List -> "generated" 

- Using Explorer, browse to: 

C:\Program Files (x86)\Steam\steamapps\common\rage tool kit\base 

- Right Click "generated" and select: 

TortoiseSVN -> Unversion and Add to Ignore List -> "generated" 

- Right Click "SAVES" and select: 

TortoiseSVN -> Unversion and Add to Ignore List -> "SAVES" 

- Right Click "cache" and select: 

TortoiseSVN -> Unversion and Add to Ignore List -> "cache" 

10) Take a snapshot of the rage tool kit Before you start making changes: 

- IMPORTANT: Shut the toolkit down before proceeding 

- Using Explorer, browse to: 

C:\Program Files (x86)\Steam\steamapps\common 

- Right Click "rage tool kit" and select: 

SVN Commit ... 

You will see a giant list of changed files (mostly new files). 

Check all by clicking on the bold "All" 

In the message, put "After buildAssets" 

Hit [OK] 

- Now wait another 10 mins... 

11) [Option] Copy your backed-up Rage folder contents into the rage tool kit 
directory. 

------------------------------------------------------------------------------- 
II.3 > > > > Fix Bugs 
------------------------------------------------------------------------------- 

There are several bugs that come for free with the Rage Toolkit. 

1) When ID Studio compiles your mod, it will write the scorcher dlc 
weapon textures to the wrong offset, causing the nail gun that 
comes with the scorcher DLC to look odd when your mod is enabled. 

Fix: (This should be done now...) 

open up : 

W:\Rage\base\decls\skins\vehicle_buggy.skin 

Search the file for: "#ifdef DVD3" 

You will see 10 segments throughout the file that look like: 

#ifdef DVD3 
robocola 

"models/vehicles/class2/buggy01bodypanels" 
"models/vehicles/class2/buggy01bodypanels4" 

#endif 

You need to comment these sections out by placing "//" 
in at the front of each line within the ifdef. IE: 

// #ifdef DVD3 
// robocola 
// { 
// "models/vehicles/class2/buggy01bodypanels" 
// "models/vehicles/class2/buggy01bodypanels4" 
// } 
// #endif 

2) When you create textures, those textures will be written to 
either 

game.resources <- non-animated textures 

or 

_vmtr_dlc.pages <- animated textures 

Depending mostly on if the texture is mapped to an animated 
entity such as an NPC or a buggy. 

As of this writing (Rage.1700.354669), rage does not load 
the animated textures that ship with mods. 

Fix/Hack: (To be done later when you build/deploy your mod) 

After you build your mod, you must make a zip/7-zip installer 
that places your _vmtr_dlc.pages file in a non standard location 

This is discussed in more detail below in the Textures 
section. Hopefully the need for this hack will not be needed 
in the future. 

=============================================================================== 
III. > > > > Hello World Popup Message 
=============================================================================== 

For our fist mod, we will simply make a message pop up that says hello 
world when you get out of the buggy at the beginning of the game. This 
will introduce you to a couple of simple concepts: 

1) Adding Strings 
2) Tutorial Messages 
3) Job Creation 

------------------------------------------------------------------------------- 
III.1 > > > > Adding Strings 
------------------------------------------------------------------------------- 

Adding new strings to the game for use in message boxes and other gui 
components is pretty simple, however the button to add strings is not 
obvious. 

Start up rage tool kit 
World Edit -> Tools -> String Browser 

Next to the Find box, there is a little [abc] button. Click it to add 
new text: 

"#str_mod1_popup_title" "Hello" 
"#str_mod1_popup_body" "World!" 

After you finish editing a field, make sure to hit [ENTER] for the change 
to take effect. 

If you have version control installed, you will notice that your edit 
just caused a file to get updated: 

<Rage Tool Kit>/base/strings/english.lang 

If you right click the file and do a diff, you will notice that it added 
the new lines to the bottom of the file. 

IMPORTANT: You can edit the english.lang file directly to save time. 
However, you must add all new strings to the END of the file. 
Do not insert your new strings into the middle or the head of the 
file. 

------------------------------------------------------------------------------- 
III.2 > > > > Tutorial Messages 
------------------------------------------------------------------------------- 

One of the easiest ways to pop up a message is to create a job that quickly 
starts and stops which has an associated tutorial message. The tutorial 
message will pop up and freeze the game until the user hits the Accept 
button. 

TutorialEvents are defined in the Media Browser. The easiest way to create 
one is to copy an existing one: 

Start up rage tool kit 
In the bottom left corner, you will find the "Media Browser" tab 
decls -> tutorialEvent -> new -> dlc1 -> intro 

Right click "intro" and select "duplicate" 
place it under a folder that represents your mod name. ie: 
"awesomemod/intro" 

Now double click the new "intro" tutorial Event you created 
and change the headingText and bodyText to point at your 
new Strings. Also change the type to: "TUTORIAL_TEXT" 

And there you have it. You have made a tutorial message. 

NOTES: 

if you go to the file system and you have version control software 
installed, you will notice that your change updated the file: 

<Rage Tool Kit>/base/decls/typeinfo/dlc_1_tutorialEvents.tdef 

If you right click and do a diff, you will see your change at 
the bottom of the file: 

tutorialEvent awesomemod/intro { 
edit = { 
headingText = "#str_mod1_popup_title"; 
bodyText = "#str_mod1_popup_body"; 
retryInMS = 1000; 



In section I.6 above, I mentioned that a best practice is to 
break out changes into our own files. So at this time, I open 
up explorer and go to: 

<Rage Tool Kit>/base/decls/typeinfo/ 

I create the file "awesomemod.tdef" and then I copy the contents 
from the bottom of dlc_1_tutorialEvents.tdef to my new file. 
Then I save my new file and finally I right click: 

dlc_1_tutorialEvents.tdef and select TortoiseSvn -> Revert 

This restores the file to its unedited version. If you shut 
down ID Studio and restart it, you will find your new tutorial 
event is still there. Furthermore, if you make any further 
changes to it, ID Studio will update your new file. 

------------------------------------------------------------------------------- 
III.3 > > > > Firing a Job event 
------------------------------------------------------------------------------- 

Jobs and scripts are the glue that holds the game together. There are a 
couple of different types of jobs. As far as I can tell, the type controls 
things like: 

- Can the job repeat 
- Does the job show up in the players list of jobs when in progress 
or completed. 

I am not going to claim that I possess a comprehensive knowledge of 
all the types. What I can tell you is that after several days of 
experimenting, I was unable to get a job to start without some 
"help" from either a script or an existing job. 

Luckily, he game does offer the ability for an existing job to fork 
multiple additional jobs. This is typically done as a "reward". 

So to add a job to the (original) game, we will update an existing job 
definition so that it starts our new job when the existing job 
completes. 

Start up rage tool kit 
In the bottom left corner, you will find the "Media Browser" tab 
decls -> job -> jobs -> dlc1 

Right click "rumbles_in_the_dark" and select "duplicate" 
place it under a folder that represents your mod name. ie: 
"jobs/awesomemod/intro" 

Now double click the new "intro" job you created 
and change the jobNameId to point to "#str_mod1_popup_title" 
and the jobSummaryId to point to "#str_mod1_popup_body" 

Finally, change: 
jobType -> Optional 
requiredDLC -> "GAME_DLC_STATE_DEFAULT" 
acceptTutorial -> "awesomemod/intro" 
saveAsJob <- clear value so that it has nothing. 
msDelayForTutorial -> 3000 

Now... we want the job to stop itself as soon as it begins. So 
we expand "acceptRewards". Right click "acceptRewards" and 
select "appendItem". This will incease the num count to 1. 

Expand the new item and declare the reward type as: 

reward "JOBREWARD_JOBCOMPLETION" 
jobDecl "jobs/awesomemod/intro" 

Make sure to hit the save icon at the top. 

So our job immediately completes itself, but triggers our tutorial message 
during its brief existence. Perfect. 

All that is left is hooking the job up to an existing job. Before we 
do that, this would be the best time to use SVN to see what changed 
and extract out the changes into a new file. I am not going to walk 
you through it. I am just telling you now would be the time to do it 
if you want to keep your changes externalized. 

Finally, 

Start up rage tool kit 
In the bottom left corner, you will find the "Media Browser" tab 
decls -> job -> jobs -> support 

Double click the support_game_start 
Right click "rewards" and select "Add Item". 
Change the reward to: 

reward "JOBREWARD_JOBACCEPT" 
jobDecl "jobs/awesomemod/intro" 

Make sure to hit the save icon at the top. 

At this point you can build your mod and test it out. (See the 
deployment section below for help). 

=============================================================================== 
IV. > > > > Scripting 
=============================================================================== 

Where are scripts used? 

1) Maps 

Every map can have a script associated with it that is loaded when the 
player enters the map. By default, these scripts have a "main" function 
which receives a thread of execution when the map loads. 

The most common use of map scripts is: 
- Dynamic handlers for things that are not embedded in the map 
(ie: respawning enemies). 
- The development of global scripts 

2) Spawn Settings 

AI based entities can call a sequence of script commands when they 
spawn. For example, to have an old man begin walking across town or 
have an enemy jump on a nearby zip line. 

3) Animations 

If you click on an MD6 file and then click on an animation alias, you 
will see a little timeline graph appear under the model. If you right 
click in the timeline, you can add an event. 

These events can do lots of things from having the NPC drop their 
attached weapon to restoring health. One of the options is: 

ae_ScriptFunction 

Which allows you to call a global function or a function attached to 
the entities assigned Script Object handler. So for example, you 
could setup a custom animweb for an NPC and have the various 
animations alert your script as to what is happening with 
callbacks. 

3) Global Event Handlers 

Global scripts are tricky to write because 

- you can't compile them while ID Studio is running 
- you can crash the game if you attempt to access or spawn something 
that is not in memory (typically defined/enforced by the map). 

However, let there be no mistake... You CAN edit and create global 
scripts. It is discussed below. 

------------------------------------------------------------------------------- 
IV.1 > > > > Basics: 
------------------------------------------------------------------------------- 

A) Environment Variables 

When a map is loaded within ID Tech 5, several globals are setup and made 
available: 

sys <- The main thread of the game. 

ie: sys.print( "^2" + sys.getTime()); 

$<entity_name> <- You can reference any embedded map entities using 
$<entity_name> (no less then/greater than signs). Use of 
references means that your script is obviously tightly 
bound to the map. 

$player1 <- While Rage does not expose the functionality, ID Tech 
$player2 supports up to 4 co-op players. However you should 
$player3 use $player1 when playing single player. 
$player4 

$null <- place holder for unassigned/initialized objects. 
$self <- entity that the script is attached to. You can also use 
the object "self" without the $. 

$self_target0 <- Not sure. The assumption is that these refer to 
$self_target1 combat targets. I've never used them 
$self_target2 
$self_target3 
$self_target4 
$self_target5 
$self_target6 
$self_target7 
$self_target8 
$self_target9 

$executor <- No idea... Could be the object running the code or 
it could be the dude that just killed the NPC. 

$activator <- If you activate an NPC by talking to them, this would 
be you... 

B) Vectors: 

vector v <- A vector is basically an array of 3 floats. In 
v_x ID tech 5, vectors use the property accessors 
v_y "_x", "_y" and "_z" to access the various 
v_z float values. 

C) Type Casting: 

ID Tech 5 supports type casting, but only through variable assignment: 

MyCustomClass mcc = $someCompatibleEntity; 
mcc.MyCustomClassMethod... 

If you are calling a method that takes no parameters and doesn't 
return a value, you can also use the callFunction method: 

$someEntity.callFunction("NoParamNoReturnMethod") 

D) Decls Loading: 

Some methods require a decl parameter. They seem to be initialized 
like so: 

@sound(<file path>) <- instantiate a decl and cast to sound. 

@job(<file path>) <- instantiate a decl and cast to sound. 

ie: @sound( "music/maps/well/level_three" ) 

E) Includes: 

The line below is how you would include code from one file in 
another file: 

#include "filename.script" 

------------------------------------------------------------------------------- 
IV.2 > > > > Common Commands 
------------------------------------------------------------------------------- 

This is by no means a comprehensive listing. It is simply an overview of 
the most common commands and objects. For a full listing of commands,  
parameters, return types, constants and the Object Inheritance heiarchy, 

see 

<Rage Tool Kit>/base/script/script_events.script 


A) idEntity : 

Pretty much everything in the game is an Entity. (Entity is the base 
object), however there are lots of children of entity that add additional 
methods. For a full hierarchy and all methods, return values, parameters 
and descriptions, see the Appendix below. 

activate getNextTeamEntity setCanBecomeDormant 
activateTargets getOrigin setClipMask 
addClipMaskFlag getScriptObject setClipModel 
addContentsFlag getScriptObjectBool setColor 
addTarget getScriptObjectFloat setColorAndAlpha 
angleTo getScriptObjectString setContents 
bind getSize setHudWatchTarget 
bindPosition getTarget setHighlight 
bindToJoint getTeamChain setLinearVelocity 
bindToTag getTeamMaster setModel 
cacheSoundShader getWorldOrigin setName 
callFunction hasFunction setOwner 
clampAngles hide setOrigin 
distanceFromBounds hideRenderModel setProgressionOwner 
distanceTo isHidden setScriptObject 
distanceToPoint joinTeam setShaderParms 
fadeSound makeActivatable setSkin 
findEntity notifyProgressionOwner setTakesDamage 
findTeamEntity numTargets setWorldOrigin 
getAngles playVoiceOver show 
getAngularVelocity quitTeam showRenderModel 
getClipMask randomTarget signalEvent 
getColor remove startSoundShader 
getContents removeBinds stopSound 
getLinearVelocity removeClipMaskFlag targetsReady 
getMaxs removeContentsFlag teleport 
getMins removeTarget testFunctionality 
getModel restorePosition touches 
getModelForward setAngles unbind 
getName setAngularVelocity wait 
waitFrame 

B) idAnimatedEntity < idEntity 

getJointHandle 
getJointPos setJointPos startFX 
getJointAngle setJointAngle stopFX 

C) idActor : < idAnimatedEntity 

Probably the second most common object you will work with. 

currentNPC getWalkState leftFoot 
decreaseHealth getWeaponReadyState needsHealth 
disableLegIK hideAttachment numOfItemTypeInInventory 
disableWalkIK increaseHealth removeAllInventoryItems 
enableLegIK isDead removeInventoryItem 
enableWalkIK isDying rightFoot 
giveInventoryItem itemInInventory showAttachment 
weaponBurstMode 

D) idThread : (sys methods) 


angToForward getThreadHandle setThreadName 
angToRight getTicsPerSecond sin 
angToUp getTime spawn 
arcCos getTraceBody spawnDecal 
arcSin getTraceEndPos sqrt 
arcTan getTraceEntity strLeft 
arcTan2 getTraceFraction strLength 
assert getTraceJoint strMid 
cacheSoundShader getTraceNormal strRight 
clearSignalThread halt strSkip 
cos isEntityValid strToFloat 
createArray isLayerActive strToInt 
debugArrow isValidDecl tan 
debugBounds killByType terminate 
debugCircle killThread threadCall 
debugLine killThreadId trace 
destroyArray music tracePoint 
disableSaves onSignal trigger 
drawText pause vecCrossProduct 
enableSaves playerIsIncapacitated vecDotProduct 
endThread print vecLength 
error println vecNormalize 
executeCommandText radiusDamage vecToAngles 
fadeSoundGroup random wait 
getcvar randomInt waitFor 
getDeclName randomSetSeed waitForEvent 
getEntity remove waitForFlags 
getFrameTime say waitForThread 
getMapPath setcvar waitFrame 
getThreadName setThreadEntity warning 

------------------------------------------------------------------------------- 
IV.3 > > > > Action Script 
------------------------------------------------------------------------------- 

Action script is a small subset of ID Tech 5's scripting language. It 
consists of 95 commands that can be used to coordinate quick and 
dirty cut scenes without having to get your hands dirty writing actual 
code or creating/editing map.scripts. 

Examples: 

- Have enemies stand around talking to each other, unaware of the player, 
but then have them stop talking and fight if the player reveals himself. 

- Have an enemy perform a spectacular entrance animation before attacking, 
like riding in on a zipline or jumping down from a balcony. 

- Have members of wellspring starts doing things to make the town feel alive. 
Like walking from point a to point b, or enter a looping idle 
animation that makes them sit or play a game... 

To create action script: 

- go to Media Brwoser -> decls -> entityDef -> ai 
- select an entity 
- In the Properties window, open up: aiEditable -> spawnSettings 
- change the number of action script events from 0 to 1 
- When you click on the action script, a gui will come up that 
lists all action scripts. 
- When you select an action, parameter combos will appear. 

While you can add action scripts to entity templates in the media browser,  
normally you first make the map and inject your instance and then add the 
action script to the instance. Thus you don't change ALL instances of 
Ghost. Only a specific instance of Ghost. 


Notes: 

Action script sequences are meant to fire once and do not support 
looping or conditional branching. 

Technically, "action_" precedes all the function names. They are defined 
in the object hierarchy under the idAI2 Entity. 

Action Scripts do not reside in compiled .script files. Instead they 
become part of the entitydef or instance. Thus they provide a way of 
creating scripts that don't rely on global or map specific support 
objects/functions. 


List: 

ChangeAnimState MoveToEntity SetPlayerFocus 
ChangeAnimStateVia MoveToEntityNoFail SetPosture 
ClearAimFocus MoveToPathPoint SetScriptAbort 
ClearLookFocus MoveToPathPointNoFail SetScriptFlag 
ClearScriptFlag MoveToPoint SetSitState 
ClearWorldState MoveToPointNoFail SetStandState 
CrouchToStand ForceAwarenessByDistance SetSubWeb 
Dive ForcePlayerInteraction SetWalkState 
Dodge PlayInteractionVoiceOver ShowAttachment 
DrawWeapon PlayOverrideAnim SpringAngles 
DropAttachment PlayOverrideAnimInterrupt SpringAnglesDisable 
EnableAutoFocus PlayVoiceOver SpringOrigin 
EnableBodyRotation PullTriggerLeft SpringOriginDisable 
EnableDamage PullTriggerRight SpringOriginAtRest 
EnableHeadTracking ReleaseTriggerRight StandToCrouch 
EnablePain ReloadWeapon StartAnim 
EnterVehicle ReloadWeaponTorso StopVoiceOver 
EnableWalkIK SetPlayerEnemy TakeItem 
ForceAnimState SearchToTarget Trigger 
Pain SetAccuracy TurnToPoint 
ForceOpenCombat SetActionNodeGroup TurnToEntity 
PerformCoverAction SetAimFocusOffset TurnToEntityWithOffset 
GiveItem SetAimPoint UseZipline 
HideAttachment SetAIVar Wait 
HolsterWeapon SetAlertCycle WaitForAIVar 
Idle SetEnemy WaitForAnim 
IgnorePlayerApproach SetFireMode WaitForAnimVia 
LeapAttack SetFocus WaitForEntity 
LoopAnim SetLookFocusOffset WaitForPlayerInteraction 
LoopAnimExitAtEnd SetMoveMode ReleaseTriggerLeft 
Melee SetMovePushStatus WaitForTraversalAnim 
MoveToCover WaitForPlayerInteractionDist 

For a full listing that includes parameters and descriptions (descriptions 
are with the constants), see: 

<Rage Tool Kit>/base/script/script_events.script 

Thread Notes and Bugs 

Action Script commands are meant to be used from the spawn settings of 
NPC entities. While you can technically call action_<SCRIPT> commands 
from regular script, you have to be very careful about threading. 

All action script commands are syncronis... that is, they lock the 
thread until they have completed. Typically, they use the object they are 
affecting as their lock. 

So if you use action_MoveToPoint, that thread will continue to override 
and control the entity until they reach the point. Any other threads 
will stop executing. For example, threads that might make the NPC 
follow the player. Or threads that might cause an interaction icon 
to appear when you approach the NPC. 

In particular, the "action_Set" commands can cause havoc. Unlike 
other commands that have a begin and an end, the Set commands take 
ownership of the entity for the lifetime of the thread they are called 
from. The Set commands are really only safe to use from the spawn 
settings of the NPC. 

Finally, if you use an event (such as hitting the quick-use command) 
to fire action scripts on an NPC, secondary threads attempting to 
call action_<Script>s on an NPC already performing an action script 
will actually crash the game. 

So general advice : 
1) Stick to commands that can finish/end 
2) Only use Set commands from spawn settings. 

So how do you use an Action Script command and cancel it? There are 
two options: 

1) You remove/respawn the entity associated with the command... (thus 
releasing the lock). 

2) YOu run your action script commands in a managed thread which 
you can terminate/kill at any time. I will note that this 
last approach is not bullet proof. Killing the thread doesn't 
really kill it. It suspends it and marks it for termination 
when you change maps. So if you use terminate(threadID) followed 
by "waitForThread(threadID)", waitForThread, will never return. 

However, suspending the thread does allow you to start another 
action script without crashing the game. But there is a limit of 
around 8 or so threads, suspended or not, that can simultaneously 
hold a lock on an entity. Once you hit the max locks, action scripts 
will stop working for that entity until you remove\respawn the entity. 

(You know the actions scrtips aren't working because they return 
immediatly when the lock attempt fails... so you can time the 
commands to detect when you have saturated the locks). 

Here is the framework for a global function solution. Note that you 
would have to create an gameStateInt and make sure it made it into the 
map/game. This is only pseudo code. It hasn't been tested: 

The method would be called like: thread actionWrapper(...); 

void actionWrapper(entity someEntity, int actionType, int actionParam, int attempts) { 

if (attempts > 3) { 
sys.setGameStateInt("yourmod_actionThread",0); 
return; 


if (0 != sys.getGameStateInt("yourmod_actionThread")) { 
sys.killThread(sys.getGameStateInt("yourmod_actionThread")); 
sys.wait(2.5); 


sys.setGameStateInt("yourmod_actionThread",sys.getThreadID()); 

float start = sys.getTime(); 

// action commands... 
if (1 == actionType) { 
someEntity.action_SomeAction(); 
} else if (2 == actionType) { 
someEntity.action_SomeAction(actionParam); 


if ((sys.getTime() - start) < 1.0) { 

// Depending on the action, it probably didn't work... Time 
// to remove the target entity, spawn a duplicate in its place and 
// retry... 

// Remove/Respawn entity... 
someEntity.hide(); 
entity newEntity = sys.spawn("some/replacement"); 
newEntity.teleport(someEntity.getOrigin(),someEntity.getAngles()); 
someEntity.remove(); 

// Retry 
thread actionWrapper(newEntitty, type, someParam, attempts + 1); 
} else { 
sys.setGameStateInt("yourmod_actionThread",0); 





Again, these are only issues if you use action_ script commands from 
within normal scripts and those scripts have multiple thread sources. 
As long as you avoid action_ scripts from normal scripts or you use a 
singleton pattern to ensure only 1 thread runs the actions, you 
shouldn't have an issue. 

------------------------------------------------------------------------------- 
IV.4 > > > > Map Scripts and Event Handlers 
------------------------------------------------------------------------------- 

As mentioned above, When you make a map, that map can be given a script 
which will load with the map. By default, the map will call the function 
"main()" within the script. However if you select the "World" Object within 
the World Editor when a map is loaded, over in the Entity Inspector (bottom 
left corner, left most tab) you will see a "call list" which is a list of 
functions you want called when the map loads. You can add additional methods 
to be called there. 

If you define objects and functions within a map script (and compile it), 
they will be exposed the next time you load the map. They don't actually 
have to be in the script itself. They can be in a script that your map 
script "#includes" 

If you then embed an INSTANCE of an entity into the map, you can shift 
click the INSTANCE and when you go to the entities script object type 
the combo box will list any Objects you have defined on the map script. 

In this section I will walk you through a simple example: 


A) Duplicate an existing (small) map: 

ID Studio -> World Editor -> Open -> Map -> tutorial... 
-> basic_env -> basic_env.map 

Then 

File -> Save As -> game/awesomemod.map 

It will ask you if you want to copy over the land textures, click [Copy] 

B) Create a Script: 

1) File -> New -> Map Script 

Note that a map can only have 1 script. 

In the future, after you open the map, you will go to 
File -> Open -> Map Script 

C) Define an Event Handler: 

// ----------------------------------------------------------------------- 
// <Rage Tool Kit>/base/maps/game/awesomemod.script 
// ----------------------------------------------------------------------- 
object AwesomeNPC { 
void init(); 
void start(); 
string msg; 
}; 

void AwesomeNPC::init() { 
sys.println( "^2[Awesome]: init"); 
// properties are set BEFORE init is called, 
if (msg == "") { msg = "Stop touching me!"; } 
int myThread = thread start(); 


void AwesomeNPC::start() { 
vector origin = self.getOrigin(); 
string s_origin = "" + origin_x + "," + origin_y + "," + origin_z; 
sys.println( "^2[Awesome]: name : [" + self.getName() + "]"); 
sys.println( "^2[Awesome]: model : [" + self.getModel() + "]"); 
sys.println( "^2[Awesome]: origin : [" + s_origin + "]"); 
self.setHudWatchTarget( true ); 
self.setOwner($player1); // prevent collision 
sys.wait(3); // wait 3 seconds 
sys.println( "^2[Awesome]: Starting monitor loop..."); 
while (1) { 
if ( self.isDead() ) { break; } 
if ( self.touches( $player1 ) ) { 
sys.println( "^2[Awesome]: " + msg); 

if ( self.needsHealth() ) { 
self.increaseHealth(10.0); 

sys.wait( 1.5 ); 
}
sys.println( "^2[Awesome]: [" + self.getName() + "] has died"); 


void main() { 

--------------------------------------------------------------------------- 


D) Compile Script: 

File -> Compile Script 

E) Re-Open Map 

World Edit -> Open -> Map -> game/awesomemod.map 

F) Create Entity INSTANCE: 

1) Right Click somewhere within the map boundaries (center?) and select: 

New Entity -> ai -> settlers -> wellspring -> Olive 

hit [ESC] to unselect all and then SHIFT + Click Olive within the map. 

G) Assign to your Event Handler: 

- Open the Entity Inspector (bottom left window, left most tab) 

- Expand [+] Script Object and click on the "type" combo box. 

You should see the AwesomeNPC type appear. Assign it to Olive. 

- Click the World Edit window, Hit ESC to deselect everything and then select 
Olive again with SHIFT + click. 

When the Entity Inspector re-draws, it should expand and show the 
AwesomeNPC's AI "msg" variables. This lets you customize what she 
says when you run into her. 

- File -> Save 

H) Build the map/script: 

World Edit: Tab -> Build -> Full Map Build + MegaBake [default] 

[Done] (When finished) 

I) Test the map/script: 

- Click on the Engine Tab 
- Hit the tilda key (~/`) to open console 
- type : devmap game/awesomemod` 

L) Walk over to Olive... maybe through her. Then open console and look 
at the output. 


In this particular example, I used a re-firing thread to periodically 
check the state of the NPC. There are often multiple ways of achieving the  
same goal. Many events such as job interactions and trigger events 
will automatically call "callbacks" on your Event Handler if you have 
methods set up to receive them. 

See: <Rage Tool Kit>/base/script/rage_npc.script for an example. 

Finally, it should be noted that the scope of map scripts are limited to 
the map. When you leave the map, any threads spawned by your script will 
be ungracefully killed along with the parent thread. So for example, if 
the script above was attached to a sentry bot, the sentry bot would be 
destroyed and the script thread would be killed when you transitioned 
between maps. So if you relied on the died handler above to update 
a game variable or job state, you might miss the event. 

------------------------------------------------------------------------------- 
IV.5 > > > > Global Scripts and Event Handlers 
------------------------------------------------------------------------------- 

A) Mod Compatibility 

Before you embrace global scripts and event handlers, be aware that 
only 1 mod can edit the games main script: script_main.script. 

If a user tries to install 2 mods that edit that script, then one of them 
will not work. The Scorchers DLC does not edit script_main.script, but 
that does not mean that a future DLC will not. 

Avoid Global Script Edits if you can. Sadly, I realize that most mods 
that wish to edit/update the vanilla game will not be able to avoid 
making global edits. 

B) Compiling 

So, if you start up ID Studio and open: 

<Rage Tool Kit>/base/script/script_main.script 

And attempt to compile it without making ANY edits, you will get a 
redefinition error. This is because the script is already loaded in memory 
because it is needed by ID Studio. 

So... to edit the script, you must do it without using ID Studio. 
That is to say, if you shut down ID Studio, make edits to 

<Rage Tool Kit>/base/script/script_main.script 

using your favorite text editing program, when you restart ID 
Studio, it will compile script_main.script and startup and make your 
changes available. 

Of coarse this basically sucks as you can't easily hit a compile button to 
see if your script has errors before you add it to the game. 

So the typical way of writing global scripts is to start by creating 
the script as if it is going to be part of a stand alone map. 

Basically, follow the directions for IV.3 above. 

Once you have your script(s) working... then you move your code 
to a new file and use a #include to add it in script_main.script. 

Building on to the IV.3 example above, I would move the AwesomeNPC 
Event handler I created for the map to a stand alone file in the 
same directory as script_main.script: 

// ----------------------------------------------------------------------- 
// <Rage Tool Kit>/base/script/awesome_npc.script 
// ----------------------------------------------------------------------- 
object AwesomeNPC { 
void init(); 
void start(); 
string msg; 
}; 

void AwesomeNPC::init() { 
sys.println( "^2[Awesome]: init"); 
// properties are set BEFORE init is called, 
if (msg == "") { msg = "Stop touching me!"; } 
int myThread = thread start(); 


void AwesomeNPC::start() { 
vector origin = self.getOrigin(); 
string s_origin = "" + origin_x + "," + origin_y + "," + origin_z; 
sys.println( "^2[Awesome]: name : [" + self.getName() + "]"); 
sys.println( "^2[Awesome]: model : [" + self.getModel() + "]"); 
sys.println( "^2[Awesome]: origin : [" + s_origin + "]"); 
self.setHudWatchTarget( true ); 
self.setOwner($player1); // prevent collision 
sys.wait(3); // wait 3 seconds 
sys.println( "^2[Awesome]: Starting monitor loop..."); 
while (1) { 
if ( self.isDead() ) { break; } 
if ( self.touches( $player1 ) ) { 
sys.println( "^2[Awesome]: " + msg); 

if ( self.needsHealth() ) { 
self.increaseHealth(10.0); 

sys.wait( 1.5 ); 
}
sys.println( "^2[Awesome]: [" + self.getName() + "] has died"); 

--------------------------------------------------------------------------- 

Notice that I left out the "main" block at the bottom. 

I also need to update my map script and remove the definition as it will 
cause conflicts once the AwesomeNPC definition becomes global: 

// ----------------------------------------------------------------------- 
// <Rage Tool Kit>/base/maps/game/awesomemod.script 
// ----------------------------------------------------------------------- 
void main() { 

-------------------------------------------------------------------------- 

Finally, I add a #include to script_main.script when ID Studio is not 
running: 


// ----------------------------------------------------------------------- 
// <Rage Tool Kit>/base/maps/game/script_main.script 
// ----------------------------------------------------------------------- 
/*********************************************************************** 

script_main.script 

This is the main script that is loaded before any level scripts load. 

***********************************************************************/ 
// these are automatically included by every script file 
//#include "script_defs.script" 
//#include "script_events.script" 

// base defines and util functions 
#include "script_util.script" 
#include "debug_drawtext.script" 
#include "rage_npc.script" 
#include "awesome_npc.script" 

void script_main() { 
sys.print( "^3 --== Entering script_main() ==--\n"); 

// 
// Do any script setup here 
// 

sys.print( "Exiting script_main()\n" ); 



--------------------------------------------------------------------------- 

If you do all of this, when you restart ID Studio, it will load the 
new AwesomeNPC as an available Script Object type which you can assign 
to whoever. 

A more common example would be creating a new usable inventory item 
that can activate your script from anywhere in the game. 

C) Be careful with your names 

I advise adding your mods name to most of your globally exposed functions 
and Event Handler Objects to avoid conflicts with methods and event 
handler objects that may exist in vanilla or mod maps. 

D) Threading 

Like Map Scripts, Global scripts and event handlers are ungracefully 
shutdown when the player transitions between maps. Like map scripts, 
you can not create a thread and expect it to still be running when you 
transition areas. 

E) Persistence /cleanup / initialization 

Trying to intercept exit map events or entity/prop destruction events is 
generally a bad idea. Even if you had the source code for the map, there 
are lots of ways one can leave a map and you would have to make sure you 
had code in place to handle them all. 

A better strategy is to focus on the map ENTER event. Perform your cleanup 
and re-initialization then. 

script_main.script: script_main() gets called every time a player enters a 
new map. So a simple strategy is to create a master "JustEnteredMap" 
event/routine that examines hidden inventory items and determines 
what was happening before you changed areas and takes appropriate 
actions to create the illusion of persistence. 

------------------------------------------------------------------------------- 
IV.6 > > > > Persisting data 
------------------------------------------------------------------------------- 

I have actually found that persisting (script) data is one of the hardest 
things to do in this game and also very expensive. 

The main way of persisting data is by creating jobs and maintaining job 
state, but dealing with jobs is not easy from script. 

The other way I have been able to persist data is through the presence of 
hidden inventory items. 

** When you define an inventoryItem, you can mark it as not visible in the 
players inventory. 

You can think of a hidden inventory item like a global variable. It has an 
initial value of 0 (because there are 0 instances in the players inventory). 
Scripts can then add and remove instances, thereby incrementing and 
decrementing the value. 

The only thing you have to be careful with is when you first enter a map, 
it takes the game some time to load everything and repopulate the players 
inventory. script_main.script runs well before the inventory is updated, 
so if you use inventory items for persistence, you will need to delay 
examination of the player inventory by 2 or 3 seconds using a thread. 

void do_checks() { 
sys.wait(2.5); 
... 


void script_main() { 
thread do_checks(); 


The game also supports something called a GameStateInt. 

GameStateInts are short lived global variables handy for communicating 
with in game objects like jobs and vo interaction and also between 
script objects/threads. IE: They are technically a property on the player 
and have to be declared in IDStudio (and used/referenced by a job or a VO 
so that they are compiled into the mod). 

However like all script variables, the life of a GameStateInt is limited 
to the game map unless it is part of a running Job. 

INCLUDING HIDDEN INVENTOYITEM 

To get hidden inventory items included in the build, I normally 
create a welcome message job (see tutorial above) and as one of the 
rewards, remove all the hidden items from the players inventory. This 
will hint to the engine that it needs to include the items, even if 
the reward is to remove them. 

GAMESTATEINT SETUP 

A) Declare your Game State Int variable 

Media Browser -> decls -> gameStateInt 

B) High-light an existing variable and select duplicate 

Use whatever path you want. IE: 

"awesomemod/awesomestate" 

C) Browser to your new GameStateInt and double click 

THere is one propery, this is the default value. Typically it is 0. 

D) To access from script: 

float awesomestate = $player1.getGameStateInt("awesomemod/awesomestate"); 

E) To set from Script: 

$player1.setGameStateInt("awesomemod/awesomestate",(awesomestate + 1)); 

If you are unsure what path to use, Dble click on the variable within 
ID Studio and note the "Name" Field above the properties. 

NOTE: To get GameStateInts included in the build, you will likely need to 
associate them with a job. For example, you could make them a failure 
condition, but use values that are so crazy that the job would never fail. 

------------------------------------------------------------------------------- 
IV.7 > > > > Global Script Memory Limits 
------------------------------------------------------------------------------- 

A) What is a global script? 

As far as I can tell, pretty much all scripts are global except those 
used by spawn settings (action scripts). 

B) What is the memory limit? 

ID has a 65K limit. This can be exceeded by either having 65K of compiled statements or 65K of variables. 

C) What happens if I break the limit? 

The game crashes to the main menu without saving when you try to load or start a game. 

D) How can I see how much memory I am using? 

When Rage loads a map, it tells you how much statment memory is being 
used in the console and the log. The output looks something like: 

Memory usage: 
Strings: 5, 568 bytes 
Statements: 918, 29376 bytes <-- Maxes out at 65536 
Functions: 695, 85736 bytes 
Variables: 16548 bytes 
Mem used: 417232 bytes 
Static data: 803352 bytes 
Allocated: 1133568 bytes 
Thread size: 7888 bytes 

The first number (918) is the number of statements. The second is how much 
memory they are using. 

Unfortunately, Variable memory usage is not as easy to discover. 
I see it when running mods in Rage, but not in ID Studio. So you just 
have to get into the habit of building daily so you know when you have 
broken the limit. 

E) That tells me overall how much is used. How can I tell how much I am 
using. 

The easiest way is to have your script load with a small custom map. 
Alternatively, if you have included your script as part of 
script_main.script, your script will be loaded any time you load any 
devmap within the engine tab of ID Studio. So for example, open console and 
type: 

devmap tutorial/basic_env/basic_env 

Then re-open console and scroll up to see the memory usage summary. 

To figure out how much you are using, take a before and after 
snapshot. IE: load up the map with your scripts commented out and then 
load it up with your scripts included. 

On my machine, a clean build with no extra scripts is already using: 

Statements: 916, 29312 bytes 

Thus you dont really have 65K to play with. You also want to leave some 
breathing room for the original games scripts. IE: The game may start and 
play fine and then half way through the game, it crashes when you enter 
a map because that map has enough scripted material to push the game over 
the max. 

Surprisingly, Rage doesn't actually use many per-map scripts, but they do 
exist. You will want to leave about 3K of breathing space to be on the safe  
side. 

So that leaves you with about 32K to play with. 

F) Any Guidlines? 

The memory limit has nothing to do with the actual size of your script or 
variable names. At runtime, scripts get compiled. Things like Documentation 
are discarded. Variables names are replaced with memory addresses, so the 
size of your variable and method names has no impact. 

So what uses up statement memory? 

- Every class object/type definition 
- Every method definition 
- Every method parameter uses 
- Every API method call. 

Variables do NOT use up statement memory, but they do use up variable 
memory, which is not as easy to see from ID Studio. In general, variables 
use less memory than API methods. So if using some variables can 
avoid calling a api method more than twice, generally you should use a 
variable. 

statements include: 

if (condition) 
somevar = value; 
sys.getTime(); 
self.localMethod(); 

So lets take this example: 

void debugPrint(string msg) { 
if (debug) { sys.println(msg); } 


Now assume that you use debugPrint() throughout all of your code. The 
question is : Does such a strategy save any memory? The answer is yes. 

The reason here is because you are taking 2 statements that always happen 
together (if and println) and combining them into one. Calling debugPrint  
one time may be more expensive, but after 2 or 3 calls, it starts to save 
memory. 

Even Better: 

// #define debugPrint(msg) (msg) 
#define debugPrint(msg) sys.println( msg ); 

Now, when you compile for production, you can remove the sys.println() 
methods from you code altogether by swapping comments. In my case, this 
shaves off about 3K of memory, which is ironically how much you want to  
leave for safety reason. So this particular strategy us highly recommended.  

As far as I can tell, every API method results in a larger block of 
inlined code at compile time. Some methods are more expensive than 
others. 

Take this example: 

$player1.removeInventoryItem( "some/item1",2); 
$player1.removeInventoryItem( "some/item2",2); 
$player1.removeInventoryItem( "some/item3",2); 
$player1.removeInventoryItem( "some/item4",2); 
$player1.removeInventoryItem( "some/item5",2); 
$player1.removeInventoryItem( "some/item6",2); 

You can save about 2K of statement memory by doing this instead: 

int c = 1; 
while (c != 7) { 
$player1.removeInventoryItem( "some/item" + c,2); 
c += 1; 


Each usage of removeInventoryItem will be expanded inline. By placing it in 
a loop and doing variable manipulation, we cause the code block to be 
inlined once instead of 6 times. 

G) What Else? 

When writing scripts, you need to make a habit of compiling your mod and 
keeping an eye on the Statement Memory usage. This is especially true when 
introducing new ID script commands that you are not familiar with. Do 
before and after snapshots and see how much memory using that ID Script 
function you have your eye on really costs you. 

By doing daily checks, you will have some idea of when/if you have 
done something that is pushing your script over the memory limit. 

Personally, I tend to code however I want and then retroactively free 
up memory by applying some of these concepts if/when the limit is 
actually hit: 

- Instead of using polymorphism, you can collapse several object classes 
into one class with a class variable that marks the type. 

H) Uninvestigated workarounds: 

The console allows you to compile and load a script using console 
commands. For example there are commnads like: 

convertToSuperScript converts a .script file to a .ss file 
ReloadSuperScriptDLL Reload super script 
script executes a line of script 

Furthermore, a script can execute console commands: 

sys.executeCommandText("console command"); 

It MIGHT be possible to make larger scripts dynamically load 
after the map loads using console commands relayed from a 
very small global bootstrap script, allowing you to bypass the 
typical global memory script size checks. However I haven't spent 
much time on this because even if one was successful, I am not 
sure how stable the solution would be. Basically this assume that the 
script limit is a bug and not by design. 

If someone looks into this and is able to compile and load 
scripts from console at runtime that bypass memory limits, let 
me know and I will add a blurb here to the notes. 

------------------------------------------------------------------------------- 
IV.8 > > > > DECL Pathing 
------------------------------------------------------------------------------- 

There is a console command called "give", which has a script counterpart: 

$entity.giveInventoryItem("decl/path",#); 

What I noticed as I created various objects is that there is a degree of 
refactoring that goes on by the compiler when you compile the mod. 

For example, DECLS has the following paths: 

decls -> throwable -> throwable -> .... 
decls -> weapon -> weapon -> .... 
decls -> inventoryItem -> inventory -> .... 
decls -> health -> inventory -> .... 
decls -> ammo -> ammo -> .... 

However, if I make a new throwable, instead of using the path: 

give "throwable/throwable/customthrowable" 

instead I have to use the path: 

give "throwable/customthrowable" 

Same thing for ammo, health and weapons. What this tells me is that at 
compile time, ID Studio collapses most of the directories into a single 
resources directory. 

This is why you often times see parent directories with a child that has 
the same name as the parent. 

At the end of the day, this just makes everything scattered and confusing. 

You might think that all inventoryItem definitions that you can use with 
the give command would be found under inventoryItem, but in fact they are 
spread out amongst various parent category directories. 

Keep this in mind if you make use of the giveInventoryItem() command. 
Under almost all conditions, you ignore the root directory name and start 
from the child directory in describing the path to the object. 

=============================================================================== 
V. > > > > Editing and Customizing NPCs 
=============================================================================== 


------------------------------------------------------------------------------- 
V.1 > > > > Mr. Potato Head style 
------------------------------------------------------------------------------- 

Md6Mesh files use a pretty strait forward, human readable format. Internally, 
most of the files describe separate objects referred to as sub-meshes. For 
example, one submesh may be called "hat". Another may be called "belt". And 
then there is the face, the torso, the backpack, ect.... 

So, the raw md6mesh files do not describe a single hull, but rather many 
parts that are combined together when the model is built using ID Studio. 
Thus 12 different torsos/boots/jackets and various accessories could result 
in hundreds of unique looking combinations. 

Well, as it turns out, each submesh is more or less independent of the file 
it is sitting in. So using nothing but a text editor like Notepad (or 
NotePad++), you can literally copy and paste parts from one NPC 
to another, so long as the body types are similar. 

All that matters is that the skeletons of the models you are swapping 
between are the same. Most humanoid NPCs and monsters all use the same 
skeleton. 

A) Example: 


1) Create the directory: 

<Rage Tool Kit>\base\md6\awesomemod\mesh 

And copy 

<Rage Tool Kit>\base\md6\characters\settlers\mesh\ginny.md6mesh 

to 

<Rage Tool Kit>\base\md6\awesomemod\mesh\ginny.md6mesh 

2) Start up rage tool kit (restart it if it is already running) 
MEDIA_B -> decls -> md6def -> settlers -> wellspring -> ginny.md6 
Right click "ginny.md6" -> duplicate -> "awesomemod/newginny.md6" 

Browse to the duplicate: 

MEDIA_B -> decls -> md6def -> awesomemod -> /newginny.md6 

(Double click to open) 

Over on the right hand side under "Model Def Properties", you will 
see: MD6 Mesh Name. Click on the field, then the [...] and browse 
to the new md6mesh you just duplicated. (md6/awesomemod/mesh/ginny) 

3) Grab Elizabeth 4: 

Using Notepad or Notepad++, Open up 

<Rage Tool Kit>\base\md6\characters\settlers\mesh\Elizabeth.md6mesh 

Search for: name "elizabeth4" 
(with double quotes0> 

Grab the Elizabeth 4 mesh (place cursor before "mesh", scroll down 
to the end of the mesh definition and SHIFT + CLICK after it to 
highlight all, then hit CTRL + C to copy). 

4) Paste into newginny.md6mesh: 

Using Notepad or Notepad++, Open up 

<Rage Tool Kit>\base\md6\awesomemod\ginny.md6mesh 

search for "fem_bikini_low03" until you find the mesh definition. 

Highlight the mesh definition, hit delete and then paste in 
the elizabeth4 mesh in its place. 

5) Grab Elizabeth 6 

Same as step 3, only this time you are grabbing the "Elizabeth4" 
mesh 

6) Paste into newginny.md6mesh 

search for "fem_shortsleeve_low01" until you find the mesh definition 

Highlight the mesh definition, hit delete and then paste in 
the elizabeth6 mesh in its place. 

7) Scrap Ginny's shoulder pad: 

search ginny.md6mesh for shoulder_low01 until you find the mesh. hi- 
light and delete it. 

8) Update numMeshes: 

You replace 2 meshes and removed 1. So numMeshes should be changed to 
15. 

9) Save ginny.md6mesh. 

If you open up an MD6 NPC file in ID Studio 

(decls -> md6 -> awesomemod -> newginny) 

You will notice a little button in the preview toolbar which reads 
"Reload Everything" when you hover over it. This will reload the mesh 
and textures, allowing you to see your changes to the text file as you 
go. Often times I have to hit the reload twice on my machine for the 
change to get recognized, but this saves me a lot of time. 

** To be comprehensive, we should have also grabbed Elizabeth's neck 
(elizabeth5) and replaced Ginny's neck. However as this is just an 
example, I didn't feel the extra steps necessary. 

B) Notes: 

1) NumMeshes 

At the top of md6mesh files you probably noticed the "init" block 
where the sub-meshes are defined with names. The init block can be a 
good guide for the layout of the file, but it is ultimately ignored 
by the loader. 

When these files are loaded, the only thing ID Tech actually cares 
about are the numMeshes and numJoints values, and then the actual 
definition blocks themselves. You shouldn't be messing with 
numJoints. 

So for example, if you change numMeshes to "1" and load it up in ID 
studio, the engine will show the very first mesh and ignore the 
others. 

It also doesn't matter if the name of the mesh actually matches the 
name in the init block. (As you probably noticed during the example). 
There is no validation. 

** I still try to keep the order and names consistent for my own 
sanity. 

2) Seeing what you are doing: 

As mentioned in the last step of the example, at any time you can 
hit the "reload everything" button and it will show you the current 
state of the model. 

3) identifying what a sub-mesh represents visually 

It isn't always easy identifying what a sub-mesh translates to 
visually. Often times they have generic names like "Elizabeth1", 
"Elizabeth2". 

I normally use the texture mapping as a hint. Sometimes the texture 
name alone is all I need. IE "common/eyes_blue"... probably maps 
to the eyes. 

However, when editing models, I also normally keep another program 
running (like Adobe Photoshop) that can show me all the TGA's in a 
directory. I also leave ID Studio running so I can see the textures 
on the model. Between the two, I can normally figure out what part a 
sub-mesh maps to. 

4) Editing Textures: 

If you plan on editing textures, you will want to grab the texture 
information from the md6mesh file and write it down somewhere. 

md6mesh files do not point directly at raw tga's. Rather they 
point at texture definitions, typically found under 

decls -> material -> models -> ... 

These definitions are normally contained within .m2 files. 
Most of these definitions include several tga files for the 
bump map, specular and diffuse settings. 

If you want to edit model textures, you will want to duplicate 
the texture definition that your cloned md6 file points at 
(maybe place the new definitions under your own directory) 
and then edit the definitions so that they points to your 
own raw tga files. 

------------------------------------------------------------------------------- 
V.2 > > > > Deforming Models with Blender 
------------------------------------------------------------------------------- 

Before we begin, lets clarify something: 

ID Tech 5 lets you export models to LZO from the preview pane. These lzo  
files include mesh/hull information, but do not include bones, skeleton or 
armature information. 

However bones, skeleton and armature information don't really matter if 
we are talking about static objects in the game that don't move... like 
rocks... or even many of the weapons. 

When editing a non-animated PROP, you can assign the render model to an 
lzo file that you have made without the need to convert to md6. 

These directions only apply to people who wish to edit ANIMATED entities 
such as NPCs, where the edits must be made to md6mesh files. 

A) Breaking things out: 

So, the first step to editing an MD6mesh is to export each of the 
md6mesh subparts as separate lzo files. This actually isn't as 
complicated as it sounds thanks to the numMeshes property. 

As mentioned earlier, when numMeshes is set to 1, then ID Studio 
will only load the first mesh defined in the file. This fact will save 
us a lot of time. 

Example: 

1) Backup the md6mesh file: 

Copy 

<Rage Tool Kit>\base\md6\characters\settlers\mesh\ginny.md6mesh 

to 

<Rage Tool Kit>\base\md6\characters\settlers\mesh\ginny2.md6mesh 

2) Fix NumMeshes: 

Using Notepad or Notepad++, Open up 

<Rage Tool Kit>\base\md6\characters\settlers\mesh\ginny.md6mesh 

One line 5, Change "numMeshes 16" to "numMeshes 1" 

Save the file. 

Scroll down to the first mesh definition: 

name "Object43" 
shader "models/characters/settlers/wellspring/females/bracer2_ginny" 

Note the name of the sub-mesh is "Object43" 


3) Start up rage tool kit (restart it if it is already running) 
MEDIA_B -> decls -> md6def -> settlers -> wellspring -> ginny.md6 

If all is going well, you should see Ginny's left arm bracelet. 

If you still see the full ginny.md6 model, hit the "Reload everything" 
button. 

4) Within the Preview Pane, find the toolbar button that says 
"Export Model" when you hover over it and click it. Place the 
file anywhere you want, but make sure you name it "Object43". 

5) Back to the text editor, SHIFT + CLICK to highlight the Object43 
mesh definition and delete it. Then hit save. Note the name of 
the next mesh: 

eye_shadow02 

6) Back to ID Studio, Within the Preview Pane, find the leftmost 
toolbar button that says "Reload All". Hit it once or twice. 

The second object (eye_shadow) is small and black, so you may 
not see it. Still, export the lzo model with the name 
"eye_shadow02" 

7) Repeat step 5 and 6 until there are no more sub-meshes left. 
That is, deleting the top most mesh, reload the file in ID Studio 
and then save the mesh as its sub-mesh name over and over until 
there are no sub-meshes left. 

I found sometimes ID Studio would crash. When that happens, it is 
not a big deal. Just restart it and continue where you left off. 

When you are done you should have 16 lzo files. One for each of the 
md6mesh's original sub-meshes. At this time, you can delete 
ginny.md6mesh and restore ginny2.md6mesh back to the original file. 

B) Import into blender: (I used a fresh install of version 2.68) 

1) Activate the Lightwave import script: 

Start Blender (if it is not already running) 

Within Blender: From top to bottom, you will see 3 menu bars 
The top menu bar will have an "i" icon at the far left. 
The middle menu bar will have a 3d box icon at the far left. 
The bottom Menu Bar will have clock icon at the far left. 

You want to click on the top menu bar's "File" and select 
"User Preferences". Go to the "Addons" Tab, category 
"Import/Export" and check 

[X] Import Lightwave Objects 

Click the "Save as Default" button. 

2) Clear the default Scene: 

The Default Scene contains 3 items. A light source, a cube and a 
camera. Right click each of the 3 items and hit the "Delete" key. 
Confirm you want to delete. 

** You can also press "a", which will select all and hit delete. 
** You don't want any extra stuff in the scene or it will complicate 
the export. 

3) File -> Import -> LightWave Object (lwo) 

Browse to the location you exported and import. 

(RAGE)\base\Object43.lwo 

4) Repeat as necessary. Each time you import a new lwo, it will 
create a new layer dedicated to that lwo. 

C) Maneuvering: 

Middle MOUSE = ROTATE CAMERA (z axis) 

SHIFT + Middle Mouse = MOVE CAMERA (x,y) 

MOUSE WHEEL UP/DOWN = ZOOM IN OUT 

'a': toggle selecting everything/nothing (only affects 
currently selected layer). 

IMPORTANT: You can not do anything that would add or delete 
a vertice. If you do something unexpected, I would recommend 
backtracking to "B.2" above and re-performing the previous 
steps. 

D) Exporting: 

File -> Export -> "Object (.obj)" 

OBJ format is also human readable and follows the same basic 
principles as md6. However, OBJ lists the vertices in a 
different order (left to right) and with the polarity 
switched of the second parameter switched (negative when 
original was positive). 

Still, it is pretty easy to merge the various points by 
eye and make the necessary adjustments. Assuming the changes 
were minor (clothing seem shifting, etc)... you can generally 
assume the original MD6's polarity is still accurate. 

Still, this can be very tedious if you are dealing with 1000+ 
points. So I wrote a merge script in python that you can 
download here: 

https://docs.google.com/file/d/0B0VAFIuYmSp5N1dZR21saUxmcG8/edit?pli=1 

The way I run it: I installed python 2.6. I place: 

obj_md6_merge.py 
original_copy.md6mesh 
exported_mesh.obj 

in the same directory. 

** original_copy.md6mesn should be a copy as it will get clobbered by the 
script. 

Then I right click obj_md6_merge.py from explorer and choose 
"Edit with IDLE" (IDLE editor is installed when you install python). 

This will bring up 2 python windows. One window will show you the source 
code of the file, the other window is an interactive shell prompt. 

Within the interactive shell, I type: 

>>> import obj_md6_merge 
>>> obj_md6_merge.merge("exported_mesh.obj", "original_copy.md6mesh") 

If you named your layers properly, it will match up the sub-meshes 
in the obj file and merge any changed lines with the corresponding 
md6mesh, automatically sapping y/z coordinates and flipping polarity. 

However, it can fail if the sub-mesh names don't match up or if new 
vertices are encountered. 

------------------------------------------------------------------------------- 
V.3 > > > > Animations 
------------------------------------------------------------------------------- 

A) AnimWebs 

1) What the hell are these things? 

An animWeb is an object that defines all the "states" that an NPC 
supports as well as the animations associated with those states. An 
animWeb is how the game engine translates the AI state of the NPC with 
how they look within the game. 

A typical use case scenario: 

NPC is relaxed and interacting with a device in their hand when an 
enemy shows up. The game engine will go to the animWeb and say: 

I need to go from the current state to state "attack" 

The animWeb will compute a PATH of states that the NPC can follow 
to go from their current state to the desired state. Furthermore, 
the animWeb knows what animations should play with each state and 
how long the animations should play. 

Finally, the engine begins rolling through the states and animations 
by applying them to the NPC. 

In order to go from idle_interact_device to "attack", the 
NPC has to go through states : put_device_away, draw_weapon, 
aim, approach and attack. 

2) Structure 

The top of the animWeb has a declaration of all states defined within 
the animWeb. Some of the states are standard/hard coded. However you 
are also free to make your own states. You will then see a declaration 
of subwebs (again, some are hard coded, but you are also free to make 
your own). 

A subweb is a parent state. For example, you may have a subweb for 
pistol animations and a subweb for rifle animations. Most the time 
subwebs will encompass both the weapon and combat aspects of the NPC. So 
example subweb names: 

hands_relaxed 
hands_searching 
hands_combat 
hands_interacting 
pistol_relaxed 
pistol_searching 
pistol_combat 
pistol_interacting 

Every singe one of these subwebs could have an "idle" state. Thus lower 
in file, your idle state may in fact be defined many times and 
associated with many different animations, 1 for each subweb. 

The state definition itself includes the animation as well as other 
states that the state can lead into. These are called edges. 

subWeb "hands_relaxed" { 
node "idle" { 
... 
blendTrees { 
tree { 
anims { 
alias { 
name "md6/../idle.md6anim" 





edges { 
edge { 
toState "car_lean_on_hood" 
toSubWeb "hands_actionScript" 
... 





Edges (target states) can be in different subwebs. So for example: 

hands_relaxed could have a state called "to_combat" which targets 
the "idle" state of pistol_relaxed. So if someone was in a hands_relaxed 
subweb state and an external force requested that the NPC be in a state 
that was only defined in pistol_combat, the NPC would automatically 
transition to the other subweb through the to_combat state. 

On the other hand, if an external force requested a state that was not 
in the current subweb and there was no way of transitioning to the 
subweb with the state, then the game would simply ignore the request. 

In ID Studio, when you open up an animWeb, you will see several 
different visualizations of this web. I prefer the Tree View. If you 
click on one of the states, you will see a "Going To" and "Coming From" 
tab in the right most pane which summarizes what leads to the state and 
what the state can lead to. 

However, most the time I don't use ID Studio's viewer since you can't 
edit animWebs from ID Studio. I normally only bring it up in ID Studio 
to verify that there aren't any mistakes. (When it doesn't render, it is 
normally because I forgot to define the state). 

** The "Coming From" information is deduced at runtime and is not part 
of the file format. 

3) Control 

Animation states are "driven" by one of three sources: Behaviors, 
pcInteractions and scripts. 

I talk about this in more detail below. 

B) Behaviors 

NPCs are defined in: 

decls -> entityDef -> ai 

There are two key properties: 

aiEditable -> behaviors -> decl 
aiConstants -> animation -> animWebs -> animWebs[0] 

The first property points to an aiBehavior, which determines how the 
NPC behaves. For example, what weapons do they prefer (if any)? Do 
they throw grenades? Do they take cover? (It doesnt make sense to take 
cover if your are enraged with no weapon). You describe how the NPC 
fights by answering a lot questions and that in turn causes the 
behavior to generate a bunch of desired states for the NPC at runtime. 

The states are fed to the animWeb. 

As mentioned above, the animWeb is basically state to animation 
translator. Only it is more advanced than that. It doesn't simply 
translate a state to an animtion, it also includes a sequence of 
animations that should be played before the target state/animation 
to ensure a smooth transition. 

However, the main point here is that you have no control over what 
states the BehaviorAI requests. Your AnimWeb either has the target 
state or it doesn't. So while there is some freedom in making 
animWebs, most of that freedom is localized to scripted animations. 

BehaviorAI driven states are more or less hard coded. 

It is not always easy figuring out what the "raw" state requests are 
that come from an AI. One way is to make a custom AnimWeb with no 
subwebs or animations. When you spawn a map with an NPC that is hooked 
up to that animWeb, the console will begin filling with error messages 
revealing the states that the behavior AI is attempting to goto 
unsuccessfully. However, this is limited. For example, the AI 
wont attempt many states unless conditions are right. So you may 
not account for fcover states (for example) because during your 
testing/data gathering, you never fought near cover. 

Or you can simply use an existing animWeb/behavior combo and assume 
everything that is needed is there. Unfortunately, it is generally 
not clear what is required for the Behavior AI and what has been 
created to help the animWeb transition between state requests. 

In one file, I found about 460 declared states. Below I listed some 
of them. Chances are, states starting with "to_..." are not actually 
needed by the behaviorAI, but they are setup to help the animWeb make 
smooth transitions between states and subwebs: 

idle to_hands_relaxed 
walk to_pipe_relaxed 
run to_pistol_relaxed 
sprint to_rifle_relaxed 
kick to_pipe_combat 
throw to_pistol_combat 
punch to_rifle_combat 
fall to_rifle_search 
back to_pistol_search 
forward to_pipe_search 
left hide_into 
right hide 
aiming hide_out 
blocked turn_left 
collapse turn_right 
to_crouch pain_head 
to_stand pain_chest 
to_relaxed pain_gut 
to_search pain_left_leg 
to_combat pain_right_leg 
to_run pain_right_arm 


ID Studio does not support editing AnimWebs from within 
ID Studio. You can duplicate webs and change specific animations, 
but you can't add new subwebs, states or fix an animation that is 
pointing to NULL. The last three things require you to edit the 
".aweb" file directly. 

You might notice that the animweb is defined on the entity under 
"aiConstants".. meaning the animweb can't be changed once the entity is 
spawned. However, the behavior is defined under aiEditable... which means  
it CAN change after the NPC is spawned. 

However, when you change an NPC's behavior, that will cause the state 
machine to start spitting out different states. If the animweb doesn't 
support the states, the NPC is likely to stand there doing nothing... 

So if I had a ranged enemy that was suppose to change to melee when the 
player got close enough (maybe I flip the behavior using a script), if 
I was pointing to a ranged animweb, chances are the enemy would just 
stand there because his animweb wouldn't support a state/animation 
transition from ranged-weapon to melee-weapon. If you want complex 
enemies capable of adapting/changing their fighting behaviors, you will 
need to make sure you are using a composite animweb that can support 
multiple behaviors. An excellent example is: "ai_bandits_body.aweb" 

Death Animations: aiBehaviors have a property that marks the NPC 
as supporting Death Animations or not. If the NPC does not support 
death animations, they will go strait to ragdoll. This may be preferred 
for custom NPC models. Odd that they put the property on the aiBehavior 
and not the entityDef. 


C) Scripts 

So you have an AI driven NPC and you want them to strike a pose. 

You can use scripts to tell the NPC to go to any subweb/state you 
want. Granted, you will need to define a custom state that is 
associated with the animation. And you probably also want to 
define at least 1 transition edge state so that your custom state 
can return to the idle of one of the existing subwebs. Otherwise, 
your NPC will not be able to stop the assigned animation. 

That is assuming you want the NPC to continue working. You might 
also create a state/animation that doesn't link to any other 
states so that when you put them into that state, they essentially 
become stuck until a script moves them out. 

So while we are here, lets talk about some of the script commands 
and the differences between them: 

npc.action_ChangeAnimState(AIANIMWEB_BODY,"ai/some/animweb/hands_relaxed/idle", AIANIMWAIT_DONT_WAIT); 

The ChangeAnimState command is the graceful state change command. 
It will do what the game always does: politely request a new 
desired state and transition to it. However, this command can fail 
if there is no way to path from the current state to the desired 
state. If the behavior AI requests a desired state during the 
transition, your request may also be interrupted. 

npc.action_ForceAnimState( AIANIMWEB_BODY, "ai/some/animweb/subweb/state", 0, AIANIMWAIT_WAIT_FOR_DEST_TO_START_BLEND ); 

This version is the not-so-graceful version. It causes the NPC to 
immediatly blend to the target state from their current state. 
The animWeb is not consulted for a graceful set of transition 
animations, however the target state and subweb must still exist 
on the animweb assigned to the NPC. 

The third parameter to ForceAnimState is the amount of time in ms 
you want the NPC's current animation to "blend" into the target 
animation. A value of 0 means there will be no blend. The transition 
will happen immediatly is a "snap" like fashion. A value of 3000, 
would cause a more graceful blend from the NPC's current animation 
to the target animation over the coard of 3 seconds. 

The last parameter on these action_ commands indicates when the 
action_ command should return: 

AIANIMWAIT_DONT_WAIT 

This causes the call to be asynchronous. The action fires and whatever 
line is after the action takes place immediatly. 

AIANIMWAIT_WAIT_FOR_DEST_TO_START_BLEND 

This will wait until the blend has completed and the target 
state/animation is running before it returns. 

AIANIMWAIT_WAIT_FOR_DEST_TO_FINISH_BLEND 

This will wait until the target state/animation has completed before 
returning. Note that some states are marked as repeating, so using this 
with repeating state is a bad idea as it means it will never return. 

action_LoopAnim(string animWebNode, int milliseconds); 

action_LoopAnim - If milliseconds is 0, it starts a looping/repeating 
animation and returns immediately. If milliseconds is non-zero, it plays 
the repeating animation for a period of time and then returns. 

action_LoopAnimExitAtEnd(string animWebNode, int milliseconds, int framesFromEnd) 

Same as LoopAnim, but stops the animation after a certain number of 
seconds or frames. I am actually not cerain if this one returns immediatly 
or waits until the animation is done. 

In most of these commands we have the animWebNode parameter. This is  
where your state and subweb naming freedom comes into play. In fact, there 
may be states that you ONLY want accessible from scripts. You may place 
them in a subweb like: 

pistol_awesomeStuff 

IMPORTANT NOTE: AI and scripts dont mix very well. If the AI is sending a 
state change request at the same time a script command arrives to change  
the animation, generally the AI breaks and the NPC ends up just stuck in 
the animation. Punching or damaging the NPC will normally wake the AI up, 
but that is a bad solution. 

The robust solution checks that the NPC is working and if they break, 
respawn a clone of the NPC (in the original NPC's position) which would 
not have been affected by the action scripts. (Thus fixing the stuck AI 
issue). 

D) Interactions 

"Interactions" are defined under: 

decls -> aiInteraction 
decls -> aiPlayerInteraction 

Interactions are normally invoked through job declarations: 

decls -> jobs 

But can also be defined on the entityDef 

decls -> entityDef -> ai -> npc -> aiEditable -> interactions 


Interactions affect the NPC model and can point directly to an 
animation alias defined on the MD6 without needing an AnimWeb or 
state translation. 

E) MD6Def Animation Aliases 

Lets take a look at some of the animation aliases. Open up: 

decls -> md6Def -> bandits -> Ghost -> pipe.md6 

FYI: "MD6" is in fact defined by the file: 

<Rage Tool Kit>/base/decls/md6def/bandits_ghosts_pipe.md6def 

The format is a bit confusing. Personally, I think it is done backwards. 

At the bottom of the .md6def is a block called "aliases". 

The aliases define the name that you see in IDStudio and associate it 
with a .md6anim file. This is pretty strait forward. 

Then there is a block called "events" above aliases. Events allows you 
to associate a timeline of events to fire when a specfic .md6anim file 
plays. Why would you do this? 

This is an override system for NPCs that use the same animations. For 
example, crossbows and shotguns both use the RIFLE_ animation subwebs 
and they both rely on the "Shoot" event animation to fire the weapon. 

But it looks very different when someone shoots a shotgun and a crossbow. 
So the NPCs likely both have their own .md6 with local overrides so that 
the shoot aliases maps to different animations when they fire. 

The MD6 is also where you hook up AI hints to the game engine. The 
game engine has no idea what is going on in an animation. If someone 
is throwing a punch, the game engine doesn't know when the punch should 
land and do damage. So the timeline on the animation Aliases is where you 
can define AI hints to the game engine to say "Here is where the foot 
hits the ground, so play the footstep noise" or "Here is where the 
bullet should leave the gun, so activate the attachment" or "Here 
is where the NPC is dead, so activate rag-doll". 

what else can you do? Change where the head turns (What the NPC is looking 
at) or disable animations regarding certain body parts at specific frames 
in the animation (like facial expressions, or an entire leg). You can even 
fire global and object specific script functions. 

Within ID Studio, you should see a list of animations for our Pipe ghost 
bandit in the right pane. 

- Grey animations are inherited from a parent class. 

- Black animations are locally defined OR override a parent animation. 

- Red animations are aliases that point to a missing file. 

Clicking on an animation brings up the animation viewer. At the bottom 
of the screen is a timeline (width is number of frames) and within you 
can see any events defined to fire at certain frames. You can also right 
click within the timeline to add new events. 

Facial animations such as blinking and viseme_A (What the face looks like 
when a spoken word contains an "a") get used by the lipsynch system if 
the NPC is involved in VO tracks or spoken interactions. Otherwise, most 
the animations are not used unless explicitly hooked up to an interaction. 

F) Props and Weapons 

On an Enities MD6 file (decls -> MD6 -> ...) you will find a list of 
attachments. The reason you define an attachment on the MD6 is so that 
the engine knows where and how to connect the attachment to the NPC. This 
is important because, without that information, the default location is 
the NPC's origin (center). So small props like pistols may not show up 
on an npc in game if you do not define how to attach the MD6 model that 
the pistol uses to the NPC. 

I ran into this issue when I tried to make my custom NPC use new 
weapons. At firwst I thought it wasnt' working... until I attached a 
large weapon and saw that it was being connected incorrectly. 

So in essence, to get an NPC to use a prop or weapon you MUST define 
an attachment on the MD6 for the NPC that tells the game engine 
what joint and offset to connect the attachment to. Luckily, ID 
studio supports this in game and include a list of joints when you 
right click the Attachment list on the MD6 viewer in game and select 
"Add New Attachement". 

=============================================================================== 
VI. > > > > Adding Textures 
=============================================================================== 


------------------------------------------------------------------------------- 
VI.1 > > > > Simple Textures 
------------------------------------------------------------------------------- 

Lets suppose you are making a new inventory item and you want to add your 
own icon for how it appears in the players inventory. 

A) Notes: 

1) Most textures are 24 bit, however textures which support translucent 
zones and invisibility (such as icons), are 32 bit. Icons are 
typically 64 pixels x 64 pixels. 

2) Avoid the use of spaces and underscores in your file name. 

B) Example: 


1) After you create your icon, place it under: 

<Rage Tool Kit>\base\textures\yourmod 

2) Start up rage tool kit 

Go to the Media browser -> textures -> yourmod 

you should see your file. 

3) Right click the files and select "Generate Material" 

** Note, this will create the file name: 

<Rage Tool Kit>\base\decls\m2\generated.m2 

you can rename this file to reflect your mod name once you 
are finished importing the textures. 

4) Fix the declarations: 


ID Tech 5 uses a default texture declaration that is normally 
not quite right. What you need to do is take a look at other 
texture meta-data wrappers that are used in the same manner as 
your texture and copy their settings. 

In this case, since we are making a gui icon for the hud, I note 
that there is already some hud textures. For example: 


Media Browser -> decls -> material -> hud -> info_window 

So, we click on info_window, Right click -> Edit Text 

Then we browse to 

Media Browser -> decls -> material -> textures -> yourmod 

Right click your texture and click EDIT 

** Important: If you plan on saving changes, you EDIT and 
not EDIT TEXT. The FIle menu in EDIT TEXT tends to lock 
up and crash the game if you ever attemtp to save from 
that window. 

Re-declare your texture in the same manner as the hud: 


stageProgram guiBlend 
transMap textures/yourmod/somefilename.tga 


5) Hook it up. 

For this simple example, we will duplicate an existing item and 
add the icon to it. 

Media Browser -> decls -> inventoryItem -> deployable 
Right click "sentrybot" and select "duplicate". 
place it under "yourmod/awesome_sentry" 

Open up your new awesome_sentry. Click on the icon Property 
field and browse to your new icon. 

To test in game: open console and type "give yourmod/awesome_sentry". 
You should see your icon. 

------------------------------------------------------------------------------- 
VI.2 > > > > Textures for models 
------------------------------------------------------------------------------- 

A) Notes: 

1) Most model support textures have multiple components. The tool kit 
recognizes/expects specular maps to end with _s.tga, height maps 
to end with _h.tga, normal maps to end with _local.tga and diffuse 
maps to not have any underscores. 

2) If you use proper naming conventions: 

base.tga, base_h.tga, base_s.tga, base_local.tga 

When you import base.tga, Rage will automatically import all relevant 
files. This saves a lot of time and energy. 

B) Example: 

1) Placed/save your textures under: 

<Rage Tool Kit>\base\models\yourmod 


2) Start up Rage tool kit, open the media browser and browse to your new  
files(s). 


3) Right click the BASE files (ie the ones without underscores) and select 
"Generate Material". If you have proper underscores in place, Rage 
will import all relevant files at the same time. 

** Note, this will create the file name: 

<Rage Tool Kit>\base\decls\m2\generated.m2 

you can rename this file to reflect your mod name once you 
are finished importing the textures. 

4) Fix the declarations: 

Browse to: decls -> materials -> models -> yourmod 

And use EDIT TEXT to look at other textures for an idea of how your 
texture should be setup. For example, if you edited the skins of an 
NPC, then looking at the original skin definition is probably the best 
example you are going to find. 

When you find something similar, browse to your texture and select 
EDIT 

** This is important. Do NOT use Edit Text when actually updating a 
texture or it tends to corrupt things. Always always always use 
"Edit" when you are actually going to change text and save it. 

For example, I wanted to import a new shirt for ginny, so I looked at 
the original shirt definition located under: 

decls -> material -> models -> characters -> settlers 
-> wellspring -> females -> shortsleeve_ginny 

Selected "Edit Text" on the original so I could see it in the 
background. 

Then i went to my new texture and selected "EDIT". Copied the 
original content to my new texture decl: 


renderbump "-size 2048 2048 - trace .02 models/char...." 
specularmap models/characters/settlers/wellspring/fem... 
bumpmap addnormals( models/characters/settlers... ) 
diffusemap models/characters/settlers/wellspring/fem... 
ambientProgram characterVmtr 



Then replaced the important bits. Personally I even left tabs in place 
in case the compiler is picky. 

5) You can now safely use the textures within your mod and they will be 
deployed when you build it. 


C) SPECIAL DEPLOYMENT NOTES: 


You might find that your imported textures work fine when you are play 
testing in ID Studio's engine tab using devmap mode, however when you 
export your mod to Rage, your texture dont seem to make it. 

As of Sept 2013 (Rage.1700.354669), Rage (the game) has a bug where 
it does not load the "_vmtr_dlc.pages" file that is placed in mods 
virtual textures directory. 

A temporary fix/hack is to create a 7-zip installer that places 
the _vmtr_dlc.pages file into the virtual textures directory 
of either dlc1 or dlc2, 2 DLCs that pretty much every user has 
that dont use virtual textures. 

IE, 7-zip would be extracted to the RAGE directory and it would 
include the paths: 

mods/yourmod/info.txt 
mods/yourmod/base/english.streamed 
mods/yourmod/base/streamed.resources 
mods/yourmod/base/gameresources.resources 
mods/yourmod/virtual textures/somemap.pages <- if you have a map 
dlc/dlc1/virtual textures/_vmtr_dlc.pages <- hack 

One problem with this hack is that it limits how many mods a user can 
have installed. At least mods that require virtual textures. 

So I would also recommend creating a separate installer that places your 
_vmtr_dlc.pages under dlc2 instead of dlc1. Then on your download page 
offer 2 downloads with instructions that tell users to try the other one 
if the first one doesn't work when trying to install multiple mods. 

Hopefully Rage will eventually release a patch that fixes this problem. 

------------------------------------------------------------------------------- 
VI.3 > > > > Splash screens for Maps 
------------------------------------------------------------------------------- 

So you make a map and you want to know how to make a splash screen for it 
that appears while the user is waiting for it to load. 

Locate: 

base\swf\loading 

swf is a shockwave flash format. You will notice several swf and fla files 
exist that support existing maps. As it turns out, the compiler is smart 
enough to look in the directory above for a swf/fla file named after your 
map. 

So to make a splash, copy a pair of existing swf/fla files and then 
rename the copies so that they match your own maps name. 

=============================================================================== 
VII. > > > > Adding Sounds and Audio 
=============================================================================== 

A) Create the sound directory: 

Sounds should be placed in the directory: 

<rage tool kit>/base/sound/... 

Go ahead and create that directory now. 

Next, you need to follow the games original conventions. These 
conventions are derived by looking at some of the existing sound 
definitions defined in: 

<rage tool kit>\base\decls\sound\vo.sndshd 

So for example, if we are creating new Voice Tracks (VO), then we would 
need to use the directory structure: 

<rage tool kit>\base\sound\vo\english\ai\yourmod\npc\yourfile.wav 

While Rage does support Ogg Vorbis, the encoding that you use doesn't 
really matter as the file will be converted to a Rage format for 
distribution anyway. 

Speaking of which, in order for ID studio to play a sound, it has to be 
compressed by ID Studio into .msadpcm format. 

ID Studio will automatically convert your wav files to that format under 
the base/compressed/... directory, but not until it builds your mod for 
distribution and even then, not unless the audio file appears within 
your compile requirements dependency tree. (Discussed in the build 
section below). 

So this presents a little bit of a dilemma. Either you blindly trust that 
the audio will be compressed when you are done, or... you compress it 
yourself. 

B) Once the wav files are in place, you need to define a sound shader for 
the various sounds. 

The master voice sound shader definition file is: 

<rage tool kit>\base\decls\sound\vo.sndshd 

This single (enormous) file declares the vast majority of raw voice 
resources in the game. If you open the file up, the main thing you will 
notice is that each "sound" declaration maps to exactly 1 wav file. 

We could edit this file, but we want to avoid that to help minimize mod 
conflicts with other mods or DLC. So the second step is making our own 
master vo file for our mod. 

<rage tool kit>\base\decls\sound\vo_yourmod.sndshd 

sound vo/english/yourmod/somenpc/line1 { 
edit = { 
parms = { 
minDistance = 256; 
maxDistance = 512; 
groups = "SSG_VO"; 
falloff = "sound/falloffs/vo"; 

soundFiles = { 
num = 1; 
item[0] = "sound/vo/english/yourmod/line1.wav"; 





If you are also creating a combat ai set (grunts, hits, etc..), you want 
to make a separate file for that: 

<rage tool kit>\base\decls\sound\vo_ai_npc.sndshd 

Open up ID Studio. You should now see your VO tracks under: 

MEDIA_B -> decls -> sound -> vo -> english -> yourmod 

C) Creating a Voice Over and Voice Track definition 

Creating a sound wrapper is only the first step if you are creating NPC 
voice acting. You also need a voice-track wrapper which associates the 
audio with lip sync information and body animation and a voice over group, 
which acts as a vocal category. 

1) Right click on: MEDIA_B -> decls -> voiceover -> ai 

** or select your mod name if you have done this before 

Select: create new VO from sndshd 
Click on [snd] button and browse to one of your audio files. 

2) After you select import, open up: MEDIA_B -> decls -> voiceover -> ai 

You should see your file. You likely want to move it to a folder 
based on your mod. Right click the file and select rename: 

yourmod\filename 

A new folder will appear under voiceovers for your mod. 

3) When you imported the sound, it also created a .vtr file under: 

<rage tool kit>\base\voicetrack\english\ai\thefilename.vtr 

You will likely want to move this to a mod specific location: 

<rage tool kit>\base\voicetrack\english\yourmod\thefilename.vtr 

4) VoiceOvers are groups of Voice Tracks. For exampe, we might create a 
voice over for "pain", which maps to 1 of 5 different voice tracks. You 
generally need a voice over group for each voice track, even if the 
group only has 1 track. Voice Overs are tracked by the file: 

<rage tool kit>\base\decls\voiceover\english.vo 

If you diff the file, you will see the change at the bottom, which is 
surprisingly incorrect: 

voiceover compmod/line1 { 
edit = { 
tracks = { 
num = 1; 
item[0] = "theFilename"; 




Notice how all the other entriesin the english.vo file have the full 
path to the VTR, yet the import function did not include the path or 
the .vtr extension. We need to fix this. But first... this is a good 
time to break out changes to our own mod-specific vo file. So create 
the file: 

<rage tool kit>\base\decls\voiceover\english_yourmod.vo 

Copy the incorrect line from the bottom of the english.vo to the top 
of your new file and correct the path: 

voiceover compmod/line1 { 
edit = { 
tracks = { 
num = 1; 
item[0] = "voicetrack/english/ai/theFileName.vtr"; 




Revert english.vo back to it's non-changed state. Finally, you probably 
want to move the .vtr to a location that reflects your mod. So you 
will want to create the directory: 

<rage tool kit>\base\voicetrack\english\yourmod\ 

then move the vtr to it: 

<rage tool kit>\base\voicetrack\english\yourmod\thefilename.vtr 

and finally update your english_yourmod.vo file: 


voiceover compmod/line1 { 
edit = { 
tracks = { 
num = 1; 
item[0] = "voicetrack/english/yourmod/theFileName.vtr"; 




D) Compressing yourself 

Right now if you try to play your sounds within ID Studio, you will hear a 
beep noise, which is irritating. 

The truth is, I am not certain why or when ID Studio decides to compress 
your raw audio files into ms adpcm. However, you can do it yourself if you 
have no patience. 

I udrf Magic Audio Converter from: 

http://www.magic-video-software.com/magic_audio_converter/index.html 

With the full feature set enabled, you can convert wav files to ms adpcm. 
The output will also be named ".wav", but you can rename it to ID Studios 
convention. 

Place your compressed files into: 

<rage tool kit>\base\compressed\sound\vo\english\yourmod 

(Same path convention you used when defining the sounds). 

Restart ID Studio, and now when you click on the files you will hear them 
play. 

E) Close Captioning 

1) Add strings that encompass what is said. (See the section on strings) 

2) Go to: 

decls => voiceover => yourmod => yourfile 

When you dble click a line to open it, it will ask you if you want to 
open the voice over browser. Select "Yes" 

Look for the pane "Voice tracks for Selected Voice Over". Double click 
the vtr for the voice track and a Voice Track Editor will appear. 

use slowal for the viseme set and select your string. Also make sure it 
points to the correct line. (sometimes it doesn't). 

Save. 

F) Lip Sync 


Go to: 

decls => voiceover => yourmod => yourfile 

When you dble click a line to open it, it will ask you if you want to 
open the voice over browser. Select "Yes" 

Look for the pane "Voice tracks for Selected Voice Over". There is a 
button in the pane that when you hover over it says "click to generate 
the lip sync data for the voice track" 

At the moment, clicking this button results in an error as ID Studio does 
not include the VO generation utilities. 

Lip Sync Kludging: 

Sometimes, something is better than nothing. If you open up an 
existing VTR file, you will notice lip-sync data with timing 
info. One option is to drag lip animation from another 
file that is as long or longer than your file and trim it so 
that the events stop within your audio files length. 

Personally, I would rather have the mouth moving, even if it is 
not saying the right words, then to have no mouth movement. 

This requires editing the VTR file using a text editor. This also 
allows you to steam body and facial animation examples from other 
existing VTR files, even if ID Studio hasn't implemented the 
feature. 

** I will likely create a python script that can auto-generate the VTR 
lip data in the near future. Keep an eye on the resources section 
of the Rage Companion Mod website. 

=============================================================================== 
VIII. > > > > NPCs 
=============================================================================== 

------------------------------------------------------------------------------- 
VIII.1 > > > > Followers 
------------------------------------------------------------------------------- 

So why does the sentry bot follow the player, but not other NPCs? 

As far as I can tell, it has to do with relationships. 

Relationships can be defined directly on an NPC's entityDef, or the NPC 
can be assigned to a faction which indicates how the NPC feels about 
several other groups. 

Lets take a look an an NPC that is already configured to follow you: 

decls -> entityDef -> ai -> sentybot -> sentrybot_buddy. 

You may notice there is also a non-buddy version of the sentry bot. If you  
compare the enemy and ally version, you will notice the main difference is 
the faction: 

Properties -> faction -> myFaction -> "faction/player_sentrybot" 

The player_sentrybot faction has the entity "like" the player. But that 
isn't everything. NPCs move and follow the player as a result of states 
fed to the NPC's animation system from the Behavior AI. Notice the 
sentrybot_buddy behavior property: 

aiEditable -> behaviors -> decl 

If you open the behavior that it points at: 

decls -> aiBehavior -> behaviors -> sentrybot -> sentrybot 

You will notice a properly on it called followFriendlyThreshold. 
In the case of a sentry bot, the followFriendlyThreshold is set to 

ATTITUDE_LIKE 

And at the same time, the sentry_bot's faction makes the sentry bot like 
the player. 

This leads one to assume that if the followThreashold is equal to or less 
than the relationship, that the NPC will follow the player. However I have 
found this is not necessarily true. 

The bandit/default_pistol has a followThreshold of ATTITUDE_LOVE. But when 
I tried to make a companion with a faction that was set to LOVE the player, 
the NPC stopped following the player as soon as the first mission of the 
game was finished. 

At present, I assume the game prevents relationships from being stronger 
than like. Specifically, I think it looks at relationships and fixes 
them when jobs complete. Especially jobs that affect the players 
faction status. 

So really, it appears the key to getting an NPC to follow the player is 
to choose or create a behavior AI that has the followThreshold set to 
like and then ensure that the NPC likes or loves the player. 

The other thing to notice is on the EntityDef: 

walkIK -> enabled -> true. 

If walk is disabled, then they can't move, even if they wanted to follow you. 

------------------------------------------------------------------------------- 
VIII.2 > > > > Setting up Interaction/Activation Handlers 
------------------------------------------------------------------------------- 

There are two ways of setting up an NPC so that you will see the Interaction 
Activation icon hovering over them when they come into focus. You can do 
it declaratively or using scripts. 

A) Declaratively (No scripts) 

Declarative interactions are pretty strait forward. You start by making 
the interaction itself: 

Media Browser -> decls -> ai -> PlayerInteraction -> pcinteractions 

You will notice things like an interaction activation radius (how far you 
need to be for the icon to appear). And an interaction list. 

Each item in the interaction list needs a game-wide unique interactName. 
Use your mod name and some identifier: 

ie: interactName "awesomemod_first" 

Technically, for an Interaction Icon to appear, you need 1 of two things: 

- The Interaction must have a messageVO on the interaction definition 
- The NPC must have a merchant inventory defined. 

You will notice that the interaction has several VO files: 

- approachVO : Opening Voice track/text that is played only once. 
If it was a merchant, you might get an "welcome to 
my shop" introduction. 

- secondaryApproachVO : For interactions that have only 1 interaction 
list item, this is what will get played when you 
approach the NPC after the approachVO has played 
once. If it was a merchant, it may be "Welcome 
back!". 

- messageVO : What gets played after the approach. If it was 
our merchant, he might say "I have the best 
selection!" 

- Finish/Abort VO : The final line. If it is a merchant, this will 
play when the inventory window is closed. He 
might say "Excellent selection. Please come 
back" 

If the Interaction only has an approachVO and SecondaryVO and the NPC 
is not a merchant, you will not see the activate icon appear on them 
when you approach. 

In the example above, I used a merchant. A merchant may be fine with a 
single interaction list item. But you can create more complex interactions 
by making multiple interaction list items. 

Each interaction list item has an activation and expiration condition. If 
you want the interaction to always be available, then you want to force 
the expiration condition to always false/never expires. Typically 
interactions are linked to job conditions, which in turn may be invisible and 
linked to game state. Thus as the game progresses, commentary can change. A 
non-merchant NPC may even become a merchant. 

One issue with declarative PCInteractions is that these interactions are 
driven by a dedicated Interaction AI Brain. Put a different way, every NPC in 
the game can have only 1 "brain". That brain is typically setup when the NPC 
spawns, and there is really no way of changing it once it is set. 

So, if you setup an NPC with the brain that scans the area around them 
and enables/progresses declared interactions, then that NPC can not also 
use the brain that allows them to follow the Player and vice versa. 

You can however use scripts to get around this. For example, you can use 
scripts to detect when an NPC is within range of the player and manually 
activate an Interaction. Conversely, you could create an interactive NPC and 
use scripts to monitor how far the player is and force them to catch up. 

Dealing with detecting map environments is complicated stuff. (Where is the 
wall/enemies/water/obstacles). I think it is far easier to write custom 
scripts to activate interactions on NPCs than re-invent the follower system.  

B) Scripted 

Take the following example: 

if ( self == $player1.getFocusEntity() && self.distanceTo( $player1 ) < 105 )     

self.beginWaitForPlayerInteraction(); 
while( 1 ) { 
if ( self.playerTriggeredInteraction( $player1 ) ) { 
sys.print("Player Triggered Interaction!"); 
// Do something.... 
break; 

if ( self != $player1.getFocusEntity() ) { 
break; 

if ( self.distanceTo( $player1 ) > 105 ) { 
break; 

sys.waitFrame(); 

self.endWaitForPlayerInteraction(); 


The assumption here is that you have an NPC assigned to a custom script 
object that monitors the NPC when the NPC spawns using a while loop. 
This code block would be part of the while loop. 

What this code block will do is cause the Activation icon to appear 
when you approach the NPC. And if the player hits "E", then the "Do 
Something" will execute. For example, "Do Something" could be the 
startVO(). 

If the player looks or walks away, the tight loop will break. 

One advantage of this method is that it also allows you to execute script 
code when the interaction begins, which is actually, surprisingly hard to 
do with declarative Interaction Handlers. 

There is one caveat: Normally, when an NPC's interaction is activated, if 
the NPC has a merchant inventory, the inventory will open after the 
interaction. This not true when the interaction is forced via sript. 

The only way I have been able to get merchants to work is with declarative  
interactions, which basically means you can't have a merchant follower 
without some tricks. 

Travelling Merchant Work around: 
-------------------------------- 

One work around is to spawn an invisible merchant NPC on top of your follower 
when you approach them or swap in a follower clone (hide/unhide) that is set 
up as a merchant. 

if ( self == $player1.getFocusEntity() && self.distanceTo( $player1 ) < 105 )  

clone.setOrigin(self.getOrigin()); 
clone.setAngles(self.getAngles()); 
clone.show() 
self.hide() 

clone.beginWaitForPlayerInteraction(); 
while( 1 ) { 
if ( clone.playerTriggeredInteraction( $player1 ) ) { 
sys.print("Player Triggered Interaction!"); 
break; 

if ( self != $player1.getFocusEntity() ) { 
break; 

if ( self.distanceTo( $player1 ) > 105 ) { 
break; 

sys.waitFrame(); 

self.endWaitForPlayerInteraction(); 


Bugs: 

If you use any "action_<someaction>" command from within a script, 
the action command will work, but it will prevent the interaciton icon from  
appearing on the NPC. You can still use action script commands from 
the spawn settings of NPCs with no issues. But dont use action script 
commands from an Object Script handler if you need interaction/activation 
icons to work properly. 

------------------------------------------------------------------------------- 
VIII.3 > > > > Merchants 
------------------------------------------------------------------------------- 

So how do you create a merchant? 

In reality, every NPC is a merchant... or capable of being a merchant. To 
flag an NPC as a mechant, all you have to do is add merchant items to the 
NPCs definition: 

Media Browser -> Decls -> entityDef -> ai -> SomeNPC 

Under Properties, go to aiEditable -> interactions 

This is where you will find the merchant definitions. 

Most of the lists are self explanatory: 

- Conditional Merchant Inventory : A lists of inventory items that will 
become available throughout the game based on conditions. FOr example, 
Ammo for a plasma rifle might have a condition on the player possessing 
a plasma rifle. 

- Dynamic Merchant Inventory : Stuff that will appear randomly throughout 
the game. 

- Special Dynamic Merchant Inventory : Like Dynamic, only with a discount. 
You can think of this as the "On Sale" items. 

Getting Merchants to work: 

If you edit an NPC, and place items in the merchant invenoty, you may 
notice when you test the map/game that you can not activate the NPC to 
see/buy your inventory. 

This is because Rage has a system. First it plays the playerInteraction, 
then it opens up the inventory only after the playerInteraction associated 
with the merchant has finished playing. So under interactions, if you 
leave the "playerInteraction" value set to NULL, the merchant wont work. 

You MUST define a playerInteraction for the merchant to work. 

Player Interactions are defined under: 

Media Browser -> decls -> ai -> PlayerInteraction -> pcinteractions 

For the interaction to work, you need at least 1 item in the 
interactions "interactList" Property and it needs to have a 
unique interactName. (Use your mod name and some identifier) 

ie: interactName "awesomemod_first" 

The other values dont really matter and in fact you can set 
almost all the VO's to NULL if you don't actually want any 
spoken audio. 

Merchants and Movement 

As mentioned above in VIII.2, Rage does not support merchants that are 
capable of following the player. Conversely, moving NPCs can't be merchants. 

If you attempt to make an NPC that can move (like a sentry bot) a merchant, 
there will be a race condition when they spawn. If they see you and activate 
the merchant inventory quickly, then they wont follow you. If they don't see 
you immediately and the follower AI kicks in, then you wont be able to 
activate the merchant interaction. 

I talk about a work around in VIII.2 above. 

=============================================================================== 
IX. > > > > Building, Dependencies and Deployment 
=============================================================================== 

A) Raw versus compiled resources: 

By default, most of Rages resources are raw, uncompressed files, but the 
game engine needs them to be in a streamlined binary (compressed) format 
to use. 

For example, if you look in the folder: 

<RAGETOOLKIT>\base\md6\ 

You will notice there is a default.md6 file. 

If you ran buildAssets.cfg when you installed Rage, you will likely have 
a "generated" directory where binary (usable) versions of files are stored. 

<RAGETOOLKIT>\base\generated\md6\default.md6\_default.bmd6anim 

Not only is the generate directory needed to view/listen to resources 
in ID Studio, it also acts as a cache for objects. 

It is unclear how and when ID Studio decides to reload/re-generate 
raw resources, but sometimes this caching can lead to issues. Especially 
when you use external version management software where you can revert 
and roll back changes. If ID Studio uses simple timestamps to track when 
cached objects need to be refreshed, then reverting a file using SVN 
can lead to a corrupted cache. 

So while it is painful, before building a release, especially one I plan 
on publishing to the web, I clear all cache oriented files and force 
ID studio to rebuild everything. I also do this when I run into awkward 
unexplainable issues to rule out cache problems. 

THese are the files that I delete: 

<Rage TOol Kit>/base/pages.xml 
<Rage TOol Kit>/base/vmtr.xml 
<Rage TOol Kit>/pagefiles/build.pages 
<Rage TOol Kit>/pagefiles/build_processed.pages 
<Rage TOol Kit>/virtualtextures/_vmtr_v23_256_C.pages 
<Rage TOol Kit>/virtualtextures/_vmtr_v23_256_C.vmtr 

If you have made a custom map, then any files within the 
<Rage TOol Kit>/virtualtextures directory that have your 
map's name(s) in them... 

Finally, I blow away the entire "generated" directory: 

<Rage TOol Kit>/base/generated 

This will force ID Studio to rebuild all objects for your mod. Note that 
it will only build objects that it thinks your mod needs. This is 
discussed more below. 

B) Fighting the not-so-optimizing compiler: 

When ID Studio builds your mod, it tries to optimize the size by doing a 
dependency trace and only include things that your mod needs. 

If you click on almost any entity in ID studio's decl section, you will 
see a graph with lots of lines pointing to various objects. That graph 
is the dependency graph. It points to all the things the entity needs 
to ensure the entity can function properly. 

If ID Studio was working correctly, then it would look for any entities 
that you edited, and then follow those entities dependency graphs to 
determine what needs to be included in the mod. 

I wouldn't mind this so much, except that ID Studio fails horribly. If you 
install Rage Tool Kit and immediately build a mod with no maps that does 
absolutely nothing, the final mod size will be around 200 MB. 

So it is not really clear to me what the optimizing compiler is doing 
other than creating a dependency headache for mod developers... but I 
digress. 

What this means to you is that just because you import a resource like a 
sound or model into ID Studio does not mean it will get deployed with your 
mod. It will only get included if ID Studio detects that the mod needs it 
because you have it attached to something that you are building. 

This dependency "hint" is normally a vanilla job that is edited so that it 
starts your own custom job, which rewards items (or removes items.. still 
identifies the item). Thus the resources get marked for inclusion. If you 
add a resource to the game... be it a texture, model, sound, etc... that 
is not connected to in some what via a "hint" job, it will not be deployed 
with your mod. 

Additionally, Rage itself is very map centric. When it loads a map, it 
only loads resources that MIGHT be accessed from the map because the map 
points to them directly or points to them indirectly. So even if a 
resource is deployed with your mod, it may not be available from all 
maps or at the right time. This is mostly an issue with Asset Mods that 
rely heavily on global scripts and is discussed further below. 

------------------------------------------------------------------------------- 
IX.1 > > > > Map Mods 
------------------------------------------------------------------------------- 

A "Map Mod" is a mod where all the new quests/features, etc... take place in 
a new map that you create as part of the mod. Bottom line is that a "Map Mod" 
makes almost no changes to the original game. All of your changes are 
localized to new maps that you create. 

Some "map mods" may include 1 very simple edit to the original game to link 
to the new mod, but edits are very limited. 

A map mod could technically be a mod that starts/takes place when the game 
ends, as long as the mod takes place in new maps ... or clones of existing 
maps that you have renamed and edited. 

A) Dependencies 

Dependencies with new custom maps that YOU create are not that difficult. 
You simply have to follow 1 rule: 

Make sure you embed instances of everything the map needs into the map, 
even if that means placing them in a non-accessible area. 

If you want to write a map script that dynamically spawns enemies, all you  
have to do is make sure at least 1 valid instance of your enemy is embedded 
in the map. This will ensure the sounds, models, textures and animations 
are all in memory when your script spawns the AI decl. 

B) Scope 

As mentioned earlier, most settings involving an entity are baked into the 
map. Instance data is that stuff that you see when you SHIFT click the 
entity that you have placed in your map and look at its properties over in 
the entity inspector. 

Basically, if you see it in the entity inspector, then the setting is baked 
into the map. So if you make a weak ghost bandit instance in your map, it  
doesn't matter if someone installs another mod that has maps where all 
ghosts are invulnerable. 

Your ghost will still be weak because all settings you see in the entity 
inspector are baked into the map. 

THings that are not baked into the mod: Anything you dont see in the entity 
inspector can be changed by other mods. For example, another mod could make 
ghost bandits look like amazon females with a different voice set and 
different animations by redefining what the bandit md6 looks like 
internally. 

If you dynamically spawn ghosts at runtime, you will be spawning whatever 
the entityDef/ai/bandit definition describes. So if another mod changes 
that definition, you may not be spawning what you think. 

If you dynamically spawn enemies, you may want to make a duplicate copy of 
their entityDef and dynamically spawn your own custom copy to ensure no 
unexpected changes by other mods. (Or maybe you want to leave that 
possibility open. It is totally up to you). 

C) Testing 

The easiest way to test your map is open the map up in ID Studio 

File -> Open -> Map 

Once it is loaded, go to the Build Menu and select: 

Full Map Build + Mega Bake 

This will build what is called a combo map. 

THen go to the engine tab, hit "tilda" to open console and type: 

devmap game/<yourmap> 

One thing you might notice is that the textures are a little fuzzy. THat 
is because fast building maps doesn't normally include the level of 
detail that producion building does. 

D) Build Production: 

Open the Map (FIle -> Open -> Map) 
In the World Edit tab, go to Build -> Build Wizard 
Goto the Advanced Tab 
Change the preset to " Full Map Build + Mega Bake [Default]" 
Uncheck "Build Unique" 
Check "Build Detail" 
In "Build Selected MegaTextures", select your map 
Check any other build optoins that apply 
When you are done, hit build 
Repeat for any all custom maps. 


------------------------------------------------------------------------------- 
IX.2 > > > > Asset/Script Mods 
------------------------------------------------------------------------------- 

When you make a map, the map clearly defines what it needs. So when you 
compile your mod, the game knows exactly what models, sounds and even ai 
declarations to add to the game, but when you write scripts, the game 
doesn't know... and as a result, often times the thing your script needs 
is not present in memory when you try to access it. 

Take this example: 

You make an asset mod (No Map) where you edit one of the original 
games jobs so that it rewards a new item you created. Your item has 
a script attached to it that plays a new sound and spawns a new 3d 
prop in the world using script commands... 

Result: 

The inventoryItem and its icon will be included in the mod such that 
when the job activates, they will definitely be present in memory. The 
script will also be present, but running it will either not work or 
may crash the game. 

WHY: 

ID Studio knows the script compiled, but it doesn't know what the script 
actually does or needs... So when it did its dependency trace, it didn't 
realize it needed to include the new sound and prop because they are 
only referenced by the script. 

Even if you pointed to existing sounds and props, the script would only 
work if you were standing in a map that HAPPENED to already contain the  
model and sound in pre-cache. 

So whether you use items/sounds/resources that came with the game 
or new resources you create your own, the problem is the same... The 
optimizing compiler doesn't know you need those things, so they will 
not be in memory when your script tries to use them. 

To make things even more confusing... When you test you scripts in ID 
STUDIO, (maybe within a temporary testing map), all game resources are 
in memory. So you likely will not realize you have a script dependency 
issue until you try to deploy your mod and run it from the actual game. 

This is probably one of the most frustrating things about modding rage. 
Scripts assume the programmer will only spawn/use entities and items that 
are already in memory when the script runs because ID Studio assumes all 
mods are map mods. (If it was a map script and you made the map, then you 
would know what was safe to reference). 

So it is up to you to keep track of what your script spawns/plays and 
make sure that those objects are in memory when the script runs. 

A) Props, sounds, textures: 

With the exception of AIs (I will get to that in a moment), you can 
get around most issues by creating a welcome message (see tutorial above) 
and "rewarding" the player by removing items and deployables that link to 
the items that your script needs. 

So if your script needs to play a sound (existing or not), you need 
a new hidden inventoryItem with that sound associated with it (maybe a 
dropSound for example). 

The main two settings you want to "flip" for hidden items are: 

noPickupMessage : true 
playerCanSeeInInventory : false 

You will also want to mark hidden items as "singular". This setting 
will not prevent items from stacking, however it will eliminate all 
but 1 instance of the item when the player changes maps. 

Any sounds, props, textures, etc... that the item points to will be 
loaded into memory when the engine loads the players inventory. 

If you have a truely global script with no entity attachment, then 
you can make the resources globably available by adding the 
hidden items to the players inventory definition: 

decls/entityDef/player -> startingInventory 

However I haven't really tested this method. The welcome message jon is the 
only method I have proven experience with. 

B) AIs 

AIs (Animated entities such as NPCS) are more complicated because they 
are not simply a model. They include behaviors, voice tracks, animations, 
textures, weapon props for their inventory items, etc... 

A simple item prop can help you get the model mesh in memory, but that is 
just the tip of the iceburg. 

I did find 1 work around, but it is limited in scope and comes with its 
own bugs. 

Rage includes a mind control dart weapon as well as remote control cars. 
When you use these items, you see the player model from the remotely 
controlled entity. To support this, every map in rage already includes MD6  
links to the various player armors. However, the MD6 itself is dynamically 
loaded at runtime. 

So.. you can edit one of the player outfits so that it points at a 
different md6mesh and different animations. Then your AI can point to the 
corresponding player MD6 outfit. 

THis will cause the game to load the model and any associated animations 
when it loads the maps and your script will be able to spawn AI's that 
use the model/animations since they will be in memory. 

By the time Rage gives you access to remove control cars, you already have 
to buy a non-ark suit. Thus the first 2 arksuit outfits are safe for use. 
You could technically use the other outfits as well, though you risk a 
user seeing something unusual when they use the remove control car. 

------------------------------------------------------------------------------- 
IX.3 > > > > When it fails: 
------------------------------------------------------------------------------- 

Sometimes a build fails and when this happens you may find that when you 
try to start up the tool kit that the menu that appears does not have the 
"ID Studio" Link. 

This is because during the build process, the menu is replaced with a version 
that does not have the IDSTUDIO link, as the menu flash program is part of 
build. When the build fails because of a crash, the menu does not get 
restored. 

Luckily, ID Studio baks up the original menu .swf file. You should find it 
under: 

<Rage Tool Kit>/base/swf/ 

Look for any ".bak" files and manually restore them. If you have version 
control software installed, it should be obvious. 

=============================================================================== 
X. > > > > External Appendix 
=============================================================================== 

A) Console Commands: 

http://docs.google.com/file/d/0B0VAFIuYmSp5WE10cDUtS1A2WVE 

B) Console Variables 

http://docs.google.com/file/d/0B0VAFIuYmSp5VkpjRTE0TGdIR0k 

C) Script Commands and Constants 

http://docs.google.com/file/d/0B0VAFIuYmSp5Ri1SYlNuaGhYb1E 

D) Script operations 

http://docs.google.com/file/d/0B0VAFIuYmSp5SjlsdFFydVkzaWs 
0 0
原创粉丝点击