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
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
- Rage(PC) Modding Notes
- Rage(PC) Modding Notes : Console Commands
- PC远程多媒体通信 (Notes)
- MODDING THE iROBOT CREATE
- 1097. LED Modding
- sicily--1097. LED Modding
- soj 1097. LED Modding
- sicily 1097. LED Modding
- 1097. LED Modding
- 路怒症 road rage
- RAGE的megatexture介绍
- RAGE的megatexture介绍
- RAGE的megatexture介绍
- Megatextures in Rage
- The Motorola Q Modding Tutorial
- [sicily online]1097. LED Modding
- 暴走漫画 Rage Comic
- 检查Rage的着色器
- Trucking
- HDU_4939_Stupid Tower Dfense_DP
- FU-A分包方式,以及从RTP包里面得到H.264数据和AAC数据的方法
- hdu4584(Building bridges)-简单题
- stm32 位带操作
- Rage(PC) Modding Notes
- How Many Paths Are There
- hdu 4939 Stupid Tower Defense 2014多校七 DP
- 求两条直线的交点,运用面向对象的思想编程实现C++源码
- 参照openRTSP写的一个RTSP client 加了一些注解
- maven的distributionManagement
- Populating Next Right Pointers in Each Node II leetcode
- webview使用详解
- 随笔