Sunday, January 25, 2009

Aurora Engine: The Easy Way to Add Secret and Placeable Doors to Your NWN Mod

It is shocking to me that the Bioware builders page on this subject is so absurdly out of date and incomplete. After extensive trial and error I've managed to document at least enough of the procedure so that Noobs like myself can quickly and easily set up secret and placable doors.

Placeable Doors:
Placeable doors are easy. You cannot use a "Hidden Wall Door" or "Hidden Trapdoor" from the miscelaneous interior standard pallet. Instead go to "Secret Object" and select a door from there. Now give the door a unique tag (For example "DoorPlaceableTag"). Under most circumstances you may want to remove the word "Secret" from the name so only the word "Door" will come up in-game if the player mouses over it.

Finally you need to place the waypoint. This waypoint will be the door's destination. Place the waypoint at the point where you want the door users to teleport to when thy go through the door and give it the same tag as your placeable door with the prefix "DST_" (for example "DST_DoorPlaceableTag").

Optionally you can use any door placeable and a waypoint using the naming conventions above and simply change the OnUsed script to x0_o2_use_wdoor.

If you open the OnUsed script you will see that the comments tell you to use a "LOC_" prefix. This is wrong. It is nothing shy of sadistic that this has never been corrected in any of the patches.


Secret Doors
How it works:
A secret door is simulated by putting an "OnHeartbeat" script on an invisible "trigger placeable" object. Every heartbeat (about once every six seconds) this script checks the skills of nearby players and rolls to see if the most skilled searcher has spotted the secret door.


When one of the players spots the door the trigger placeable destroys itself, spawns in a door at it's location, and sets it's Tag name as a string variable on it.

The door has an OnUsed script that uses the string variable to teleport anyone who uses it (along with henchmen and pets) to a waypoint with the same name.

How to set it up:
Place a "Hidden Trapdoor Trigger" or a "Hidden Wall Door Trigger" where you want the door to appear and give it a unique tag (for example "SecretDoorTag"). You will find the "Hidden Wall Door Trigger" and "Hidden Trapdoor Trigger" in the standard pallet under "Miscellaneous Interior". Place the trigger object in the center of the desired detection radius.

Next you need to place the waypoint that will act as the door's destination. Place the waypoint at the point where you want the door users to teleport when they go through the door to and give it the same tag as the Hidden Door Trigger object with the prefix "DST_" (for example "DST_SecretDoorTag").

I am referring to the "Hidden Trapdoor Trigger" and the "Hidden Wall Door Trigger" as "trigger objects" to avoid confusion, as they are not technically "triggers". In other words... they are not painted from the "trigger" pallet. They are placed from the "placeable" pallet. They're just called "triggers" in the placeable menu.

Hidden door trigger objects use the "invisible object" appearance. When the door appears it will be facing the same direction as the placeable.

To set the secret door's properties change the following fields:

  • Reflex Save: Determines the distance at which a PC can search for this door in meters.
  • Will Save: Determines the DC of finding this door.

Wall Door Placement Guidelines:
When placing wall doors be careful to place the trigger so when the door spawns it will be flush against the wall. Place the "hidden wall door trigger" so the arrow faces away from the wall and the back edge of the invisible object placeable is flush with the wall.

Wall Doors must be placed where they can be accessed by the players in-game. This can be problematic sometimes. For example, when using the city interior tileset the walkmesh often does not extend all the way to the wall, especially in the alcoves. If you spawn your doors so they appear flush against these walls they will be too far off the walkmesh for the players to fire the "OnUsed" event. A few placeable walls can go a long way towards solving this problem. There's a great selection of placeable walls in the Community Expansion Pack. Also... Door Placables set as "static" objects and placed backwards, with the doors facing the wall, can make a reasonable looking wall placable. As a rule if you cannot click-select the door in the toolset's graphic window it's too far off the walkmesh to be usable by the players.


Changing the Door Spawned by Hidden Door Triggers:
You will need to create a new door blueprint and modify the OnHeartbeat script if you want a door other than the default door to spawn.

Before you ask... No... You cannot use the secret doors under "secret object" in the standard pallet. The OnUsed script is wrong. The door placeables in the "secret object" pallet are for use with the Secret Object Triggers in the standard "Triggers" pallet. Use of these triggers is beyond the scope of this post.

  1. First... create a new blueprint for the door appearance that you want to spawn. For example... the default wall door has a wooden appearance. To create a stone door select the Hidden Wall Door and "Edit Copy". Change the appearance, for example, to "Wall Door Stone". I also like to change the name to "Secret Door" (it just looks cooler than the default name). Make a note of the new placeable resref for the next step, modifying the hidden trigger's OnHeartbeat script.
  2. Now place a Hidden Wall Door Trigger or Hidden Trapdoor Trigger placeable object where you want your custom secret door to spawn.
  3. Copy and Paste the below script into the script editor. Change the area highlighted in red (around line 116) to the resref (not tag) of the placable object that you created in step one. Give your new script a unique name. Now go into the properties of your hidden door trigger and replace the OnHeartbeat script with your new script.
  4. Set the destination waypoint the same way you would if you were using a default door. Just place a waypoint at the point where you want the door users to teleport to and give it the same tag with the prefix "DST_" (for example "DST_SecretDoorTag").
The OnHeartbeat script for your Hidden Door Trigger object:
Copy and paste everything in black. The red font you will replace with the resref of your custom door placeable.

//::///////////////////////////////////////////////
//:: nw_o2_dtwalldoor.nss
//:: Copyright (c) 2001-2 Bioware Corp.
//:://////////////////////////////////////////////
//
// This script runs on either the Hidden Trap Door
// or Hidden Wall Door Trigger invisible objects.
// This script will do a check and see
// if any PC comes within a radius of this Trigger.

// If the PC has the search skill or is an Elf then
// a search check will be made.

// It will create a Trap or Wall door that will have
// its Destination set to a waypoint that has
// a tag of DST_

// The radius is determined by the Reflex saving
// throw of the invisible object

// The DC of the search stored by the Willpower
// saving throw.

//
//:://////////////////////////////////////////////
//:: Created By : Robert Babiak
//:: Created On : June 25, 2002
//::---------------------------------------------
//:: Modifyed By : Robert, Andrew, Derek
//:: Modifyed On : July - September
//:://////////////////////////////////////////////

void main()
{
// get the radius and DC of the secret door.
float fSearchDist = IntToFloat(GetReflexSavingThrow(OBJECT_SELF));
int nDiffaculty = GetWillSavingThrow(OBJECT_SELF);

// what is the tag of this object used in setting the destination
string sTag = GetTag(OBJECT_SELF);

// has it been found?
int nDone = GetLocalInt(OBJECT_SELF,"D_"+sTag);
int nReset = GetLocalInt(OBJECT_SELF,"Reset");

// ok reset the door is destroyed, and the done and reset flas are made 0 again
if (nReset == 1)
{
nDone = 0;
nReset = 0;

SetLocalInt(OBJECT_SELF,"D_"+sTag,nDone);
SetLocalInt(OBJECT_SELF,"Reset",nReset);

object oidDoor= GetLocalObject(OBJECT_SELF,"Door");
if (oidDoor != OBJECT_INVALID)
{
SetPlotFlag(oidDoor,0);
DestroyObject(oidDoor,GetLocalFloat(OBJECT_SELF,"ResetDelay"));
}

}


int nBestSkill = -50;
object oidBestSearcher = OBJECT_INVALID;
int nCount = 1;

// Find the best searcher within the search radius.
object oidNearestCreature = GetNearestCreature(CREATURE_TYPE_PLAYER_CHAR, PLAYER_CHAR_IS_PC);
int nDoneSearch = 0;
int nFoundPCs = 0;

while ((nDone == 0) &&
(nDoneSearch == 0) &&
(oidNearestCreature != OBJECT_INVALID)
)
{
// what is the distance of the PC to the door location
float fDist = GetDistanceBetween(OBJECT_SELF,oidNearestCreature);

if (fDist <= fSearchDist) { int nSkill = GetSkillRank(SKILL_SEARCH,oidNearestCreature); if (nSkill > nBestSkill)
{
nBestSkill = nSkill;
oidBestSearcher = oidNearestCreature;
}
nFoundPCs = nFoundPCs +1;
}
else
{
// If there is no one in the search radius, don't continue to search
// for the best skill.
nDoneSearch = 1;
}
nCount = nCount +1;
oidNearestCreature = GetNearestCreature(CREATURE_TYPE_PLAYER_CHAR, PLAYER_CHAR_IS_PC, OBJECT_SELF ,nCount);
}

if ((nDone == 0) &&
(nFoundPCs != 0) &&
(GetIsObjectValid(oidBestSearcher))
)
{
int nMod = d20();

// did we find it.
if ((nBestSkill +nMod > nDiffaculty))
{
location locLoc = GetLocation (OBJECT_SELF);
object oidDoor;
// yes we found it, now create the appropriate door, EDIT THE LINE BELOW TO CHANGE RESREF OF SPAWNED DOOR
oidDoor = CreateObject(OBJECT_TYPE_PLACEABLE,"NW_PL_HIDDENDR01",locLoc,TRUE);

SetLocalString( oidDoor, "Destination" , "DST_"+sTag );
// make this door as found.
SetLocalInt(OBJECT_SELF,"D_"+sTag,1);
SetPlotFlag(oidDoor,1);
SetLocalObject(OBJECT_SELF,"Door",oidDoor);

} // if skill search found
} // if Object is valid
}