Secret Door Kludge
Userlevel: Moderate to Advanced
Version: 0.1, working beta
PROBLEM:
no true D&D secret doors in game.
NEEDED FUNCTIONALITY:
1. Truly hidden from view (not clickable, not selectable, don't glow
when TAB is held)
2. Emulate 3E/NWN rules for detection:
Skill check is Search vs Hide.
Skill check is halved if not in 'active search'.
Elves are always considered to be in 'active search' mode.
3. Make secret door access available to all players after being found.
WHAT I DIDN'T/COULDN'T ACHIEVE (or, what I'm still wrestling with)
1. An elegant solution. My secret doors are anything but. This is a
total kludge.
2. Non-placeable dependant secret doors. Mine currently rely on an
object (boulder, bookshelf) as cover.
3. Resetting of secret door.
4. Locked or trapped secret doors. Haven't looked too deeply into
changing this.
5. Finding the door/travelling through door animations/sounds/effects.
Haven't gotten to this yet.
6. Henchman/animal companion/etc being able to follow PC through door.
FINAL BEHAVIOR:
Character sees non-clickable background object. If the character is
nearby and a search roll beats the DC of the secret door, the character
receives a message: "You found a secret door!" The previously non-usable
object transforms into a selectable object that ANYONE can click on. A
message floats over the object: "Secret door found by (playername)".
On clicking the newly obvious secret door, you are transitioned to the
other side of the door (which can be anywhere in the module, but likely
is nearby).
This doesn't sound too hard, but becomes difficult to achieve because of
some lacking functionality in NWNScript. Biggest problem is I can't seem
to find out if someone is in 'Active Search' modal state. I've tried
everything... everything. If anyone knows of a way that actually WORKS
(I've worn out theory), please let me know. Without knowing that, I was
forced to use a fake Trap to emulate the searching functionality. Also,
not having events like onDetected or onHeartbeat for trap objects was
problematic. Not having access to set something USEABLE/UNUSEABLE via
script didn't help. All in all, I hurt my head.
But it does work. I love this game.
OK. Making these babies requires some patience. Here's step by step.
=============
SETUP: Making CUSTOM TEMPLATES.
Open an area in the toolset, then make the following custom templates.
Do not place any of these into the area yet.
1. Make new CUSTOM WAYPOINT:
(Template for the destination of secret doors.)
Set name and tag to SDOOR_W_ (The BlueprintResRef will be SDOOR_W_ and
you shouldn't need to change it.)
Note: I put Appearance Type to RED to distinguish from my normal WPs.
2. Make new CUSTOM PLACEABLE:
(Template for the undetected secret door object.)
Set name and tag to SDOOR_X_
Set the default Appearance Type for your secret doors. Since this is
configurable later, I just use Boulder for default.
PLOT must be off; USEABLE must be off; STATIC should be on. Crank the
Hardness/HP/Saves of the object to 200 or so.
Nothing else needs to be changed. No description needed.
3. Make new CUSTOM PLACEABLE:
(Template for the detected secret door object.)
Set name and tag to SDOOR_Y_
Set the default Appearance Type. Note this could be different from the
one used by SDOOR_X_ but generally is the same object.
PLOT must be off; USEABLE must be on; STATIC will be greyed out.
Everything should be off in LOCK and TRAP. Setting these will break the
secret doors!
Feel free to add a simple description along the lines of "This object
conceals a secret door"
In the scripts tab, you need to enter the following script under the
onUsed event:
Script Name: sdoor_y_use
onUse event for SDOOR_Y_
Purpose: send user to secret door's destination when object is clicked.
NWScript: void main()
{
object oPlayer = GetLastUsedBy();
string sSecretDoorID = GetStringRight(GetTag(OBJECT_SELF), 3);
SpeakString("Used by " + GetName(oPlayer), TALKVOLUME_TALK);
object oWaypoint = GetObjectByTag("SDOOR_W_" + sSecretDoorID);
location lWaypoint = GetLocation(oWaypoint);
AssignCommand(oPlayer, JumpToLocation(lWaypoint));
}
4. Make new CUSTOM TRIGGER -- TRAP
(This template is used to emulate the PC finding the secret door by D&D
rules.)
Set Name and Tag to SDOOR_T_
Leave trap type alone (probably set to 'Spike Trap, Average'. It's
moot.)
DETECTION DC is important: this is the difficulty of spotting your
Secret Door. It can be changed for each secret door you make, so set it
to something easy as the default, like 15 or so.
ONE SHOT must be off; DETECTABLE must be on; DISARMABLE should be off.
No scripts are needed.
5. Make new CUSTOM TRIGGER -- GENERIC TRIGGER
(This the template for a "zone" around the secret door that provides
needed onEnter/onExit events.)
Name and Tag need to be SDOOR_Z_
The following scripts need to be added:
Script Name: sdoor_z_enter
onEnter event for SDOOR_Z_
Purpose: see if entering PC has detected the fake trap yet.
NWScript: void main()
{
object oPC = GetEnteringObject();
if (GetIsPC(oPC))
{
string sSecretDoorID = GetStringRight(GetTag(OBJECT_SELF), 3);
SetLocalObject(OBJECT_SELF, "lastEntered", oPC);
object oTrap = GetNearestTrapToObject(oPC);
if ( GetTag(oTrap) == "SDOOR_T_" + sSecretDoorID )
{
SendMessageToPC(oPC, "You found a secret door!");
object oXObj = GetNearestObjectByTag("SDOOR_X_" + sSecretDoorID, oPC);
location lHere = GetLocation(oXObj);
DestroyObject(oTrap);
DestroyObject(oXObj);
CreateObject(OBJECT_TYPE_PLACEABLE, "sdoor_y_" + sSecretDoorID,lHere);
DestroyObject(OBJECT_SELF);
}
}
}
Script Name: sdoor_z_exit
onExit event for SDOOR_Z_
Purpose: see if exiting PC detected the fake trap.
NWScript: void main()
{
object oPC = GetExitingObject();
if (GetIsPC(oPC))
{
string sSecretDoorID = GetStringRight(GetTag(OBJECT_SELF), 3);
DeleteLocalObject(OBJECT_SELF, "lastEntered");
object oTrap = GetNearestTrapToObject(oPC);
if ( GetTag(oTrap) == "SDOOR_T_" + sSecretDoorID )
{
SendMessageToPC(oPC, "You found a secret door!");
object oXObj = GetNearestObjectByTag("SDOOR_X_" + sSecretDoorID, oPC);
location lHere = GetLocation(oXObj);
DestroyObject(oTrap);
DestroyObject(oXObj);
CreateObject(OBJECT_TYPE_PLACEABLE,"sdoor_y_" + sSecretDoorID, lHere);
DestroyObject(OBJECT_SELF);
}
}
}
Script Name: sdoor_z_heart
onHeartbeat event for SDOOR_Z_
Purpose: see if PC in area has detected the fake trap yet.
NWScript: void main()
{
object oPC = GetLocalObject(OBJECT_SELF,"lastEntered");
if (oPC!=OBJECT_INVALID)
{
string sSecretDoorID = GetStringRight(GetTag(OBJECT_SELF), 3);
object oTrap = GetNearestTrapToObject(oPC);
if ( GetTag(oTrap) == "SDOOR_T_" + sSecretDoorID )
{
SendMessageToPC(oPC,"You found a secret door!");
object oXObj = GetNearestObjectByTag("SDOOR_X_" + sSecretDoorID, oPC);
location lHere = GetLocation(oXObj);
DestroyObject(oTrap);
DestroyObject(oXObj);
CreateObject(OBJECT_TYPE_PLACEABLE,"sdoor_y_" + sSecretDoorID, lHere);
DestroyObject(OBJECT_SELF);
}
}
}
Whew. OK, the setup is done. These templates are done... you shouldn't
have to be touching them directly again.
=============
PLACING: Placing usable secret doors. This sequence must be done for
each secret door used in an module.
1. Go to your premade SDOOR_W_ waypoint, and do an "Edit Copy" on it.
Change the NAME and TAG to SDOOR_W_001
(For the next secret door made within the same module, use 002 instead;
then 003, etc.)
The BlueprintResRef should automatically read 'SDOOR_W_001' -- check to
be sure.
2. Go to the SDOOR_X_ placeable, and do an "Edit Copy".
Change NAME and TAG to SDOOR_X_001 (or 002, 003, etc.)
3. Go to the SDOOR_Y_ placeable, do "Edit Copy"
Change TAG to SDOOR_Y_001
Change NAME to 'Secret Door'
4. Go to the SDOOR_T_ trap, do "Edit Copy"
Change TAG/NAME to SDOOR_T_001
Change the Detect DC to reflect your secret door's DC
5. Go to the SDOOR_Z_ trigger, do "Edit Copy"
Change the TAG/NAME to SDOOR_Z_001
6. Put a SDOOR_W_001 waypoint into your area. Place it where you want
your secret door's destination to be.
7. Put a SDOOR_X_001 object into your area. Place it where the secret
door is to be found.
8. Draw a SDOOR_T_001 trap so that the SDOOR_X_001 object entirely
covers it. I generally just make a tiny square trap.
9. Draw a SDOOR_Z_001 trigger zone in a circle around the SDOOR_X_001
object. As for size, it should have a radius of about 3 in-game meters
or so... not too large.
You do not have to place a SDOOR_Y_001 object; it's generated
automatically.
=============
And you're done. Build, save, load, use!
Hope this isn't too bizarre & kludgey. I'll be working on improvements
and welcome comments.
|