Placeable in-area transitions 1-click?

So I’ve seen transitions in PWs where they’re just little tab-glowy spheres on the ground. When you click them once they move you (usually) within the same area. Like a small path through a line of trees and it pops you out on the other side.

I’ve messed around with things like x0_o2_use_wdoor but the placeable takes two clicks. Even if it isn’t something that’s a container and I don’t have inventory checked. Happens when the script is in OnUse or OnClick. The first click sounds like it’s opening the placeable and then the 2nd click will teleport to the waypoint.

How is this done on the first click?

This seems to works (1 click, no sound) regardless of inventory state of the placeable:

void main()
{
    // for OnClick - instant teleport
    object oUser = GetPlaceableLastClickedBy();
    // for OnUsed - teleport when used
    object oUser = GetLastUsedBy();
    // pick one from above; below - destination    
    object oDest = GetObjectByTag("JUMPHERE");

    SpeakString("Teleporting " + GetName(oUser));

    AssignCommand(oUser, ClearAllActions());
    AssignCommand(oUser, JumpToObject(oDest));
}

If the target location is in the same area, you also need to jump the player’s associates. Here’s a function that does that:

// Jump associates of oPC to oTarget if it's in the same area.
// Bloodsong swiped this from Amurayi's ultimate teleport script
// Proleric made this recursive because henchman can have horses in 1.69
void bhtJumpAssociates(object oPC, object oTarget, float fDelay = 2.0)
{

  if (GetArea(oTarget) == GetArea(oPC))
    {
            DelayCommand(fDelay, ptJumpAssociate(oPC, ASSOCIATE_TYPE_ANIMALCOMPANION, oTarget));
            DelayCommand(fDelay, ptJumpAssociate(oPC, ASSOCIATE_TYPE_DOMINATED, oTarget));
            DelayCommand(fDelay, ptJumpAssociate(oPC, ASSOCIATE_TYPE_FAMILIAR, oTarget));
            DelayCommand(fDelay, ptJumpAssociate(oPC, ASSOCIATE_TYPE_HENCHMAN, oTarget));
            DelayCommand(fDelay, ptJumpAssociate(oPC, ASSOCIATE_TYPE_SUMMONED, oTarget));
    }
}
// Jump associates of a given type to a target location
void ptJumpAssociate(object i_oPC, int i_type, object i_oWP)
{
    int i = 0;
    object oAssociate = GetAssociate(i_type, i_oPC, ++i);
    while (GetIsObjectValid(oAssociate))
    {
      AssignCommand(oAssociate, ClearAllActions(TRUE));
      AssignCommand(oAssociate, JumpToObject(i_oWP));
      bhtJumpAssociates(oAssociate, i_oWP);               // Recursive line added by Proleric
      oAssociate = GetAssociate(i_type, i_oPC, ++i);
    }
}

Iterating over PC faction will achieve the same effect (transfer PC, associates and any number of sub-associates) without recursion and delays:

void JumpPartyToLocation(location lDest, object oPC)
{
    object oMember;
    object oMaster;
    object oArea;

    oArea = GetArea(oPC);

    AssignCommand(oPC, ClearAllActions());
    AssignCommand(oPC, JumpToLocation(lDest));

    if(GetAreaFromLocation(lDest) != oArea || !GetIsPC(oPC))
    {
        return;
    }

    oMember = GetFirstFactionMember(oPC, FALSE);

    while(GetIsObjectValid(oMember))
    {
        oMaster = GetMaster(oMember);
        while(oMaster != oPC && oMaster != OBJECT_INVALID)
        {
            oMaster = GetMaster(oMaster);
        }
        if(oMaster == oPC)
        {
            AssignCommand(oMember, ClearAllActions());
            AssignCommand(oMember, JumpToLocation(lDest));
        }
        oMember = GetNextFactionMember(oPC, FALSE);
    }
}


void main()
{
    object oUser = GetPlaceableLastClickedBy();
    object oDest = GetObjectByTag("JUMPHERE");

    JumpPartyToLocation(GetLocation(oDest), oUser);
}

Using this code on a non-PC master would of course be disastrous, hence the !GetIsPC(oPC) sentinel.

Couple of things. First I don’t understand why nobody has suggested using the built-in functions contained in x0_i0_transport.nss which contains -

//:://////////////////////////////////////////////////
//:: X0_I0_TRANSPORT
//:: Copyright (c) 2002 Floodgate Entertainment
//:://////////////////////////////////////////////////
/*

Functions for making creatures travel/transport to new locations.

 */
//:://////////////////////////////////////////////////
//:: Created By: Naomi Novik
//:: Created On: 09/12/2002
//:://////////////////////////////////////////////////

/**********************************************************************
 * CONSTANTS
 **********************************************************************/


/**********************************************************************
 * FUNCTION PROTOTYPES
 **********************************************************************/


// Target goes to specified destination object intelligently.
// If location is in same area, walk (or run) there.
// If location is in different area, walk (or run) to
//     most appropriate door, area transition, or waypoint, 
//     then jump.
// If either of these fail, jump after fDelay seconds.
void TravelToObject(object oDest, object oTarget=OBJECT_SELF, int bRun=FALSE, float fDelay=10.0);

// Target goes to specified location intelligently. See
// TravelToObject for description.
void TravelToLocation(location lDest, object oTarget=OBJECT_SELF, int bRun=FALSE, float fDelay=10.0);

// Find nearest exit to target (either door or waypoint).
object GetNearestExit(object oTarget=OBJECT_SELF);

// Find best exit based on desired target area
object GetBestExit(object oTarget=OBJECT_SELF, object oTargetArea=OBJECT_INVALID);

// Transport a player and his/her associates to a waypoint.
// This does NOT transport the rest of the player's party,
// only their henchman, summoned, dominated, etc.
void TransportToWaypoint(object oPC, object oWaypoint);

// Transport a player and his/her associates to a location.
// This does NOT transport the rest of the player's party,
// only their henchman, summoned, dominated, etc.
void TransportToLocation(object oPC, location oLoc);

// Transport an entire party to a waypoint
void TransportAllToWaypoint(object oPC, object oWay);

// Transport an entire party to a location
void TransportAllToLocation(object oPC, location lLoc);

As far as @NWShacker’s last code goes it can be improved. Instead of 1st getting oPC’s area, clearing their actions, jumping to a location and then doing a return if oPC is not a PC is concerned it would be better to test for them being the PC in main() and only executing JumpPartyToLocation() if they are indeed a PC like -

void main()
{
    object oUser = GetPlaceableLastClickedBy();
    object oDest = GetObjectByTag("JUMPHERE");

    if(GetIsPC(oUser))
        JumpPartyToLocation(GetLocation(oDest), oUser);
}

Just a thought.

TR

Because anybody who read past the function declarations in that file can see that they only transfer one of each associate type with the PC. This was already discussed on this forum.

They also don’t transfer henchmen of henchmen of henchmen (…), which my code does.

x0_i0_transport only makes sense in SoU itself and should not be used for any module production.

That check allows transfer of party-less NPCs with the same function without a risk of hanging up the game.

Fun fact

Making a cyclic party:

AddHenchman(A, B);
AddHenchman(B, C);
AddHenchman(C, A);

breaks the game.

Are you saying that -

// Transport an entire party with all associates to a location.
void TransportAllToLocation(object oPC, location oLoc)
{
    object oPartyMem = GetFirstFactionMember(oPC, TRUE);
    while (GetIsObjectValid(oPartyMem)) {
        TransportToLocation(oPartyMem, oLoc);
        oPartyMem = GetNextFactionMember(oPC, TRUE);
    }
    TransportToLocation(oPC, oLoc);
}

The final function in that library, doesn’t work as advertised then?

TR

Also, how do NPC’s get past this?

void JumpPartyToLocation(location lDest, object oPC)
{
    object oMember;
    object oMaster;
    object oArea;

    oArea = GetArea(oPC);

    AssignCommand(oPC, ClearAllActions());
    AssignCommand(oPC, JumpToLocation(lDest));

    if(GetAreaFromLocation(lDest) != oArea || !GetIsPC(oPC))
    {
        return;
    }

when failing the check for a PC is failed?

TR

It does what it advertises, but only if you look at it with SoU in mind. It is outdated for HoTU+ (i.e. it has no idea of multiple henchmen).

TransportAllToLocation transfers all PCs and their: 1 henchman, 1 summon, etc. It doesn’t go deeper or to the sibling branches of associate tree.

There is also a bug in that functions code: oPC is transported twice if it is a PC.

They don’t, which is exactly what I want. They jump themselves, leaving the rest of the script to PCs (who pull their whole party). And if we make a cross-area jump, then it doesn’t matter.

Thank you for getting me to look through that function in more detail because I have discovered an even bigger bug according to the lexicon.

    oPartyMem = GetNextFactionMember(oPC, TRUE);

According to the lexicon that particular line means that only PCs will be transported. To actually work as it is supposed to that line should actually be

    oPartyMem = GetNextFactionMember(oPC, FALSE);

to transport non PCs of the PC’s faction. Here is the example code from the lexicon.

//given this function declaration
object GetFirstFactionMember(object oMemberOfFaction, int bPCOnly = TRUE);

this is the actual example code

// This code shows how you can cycle through all the party 
// members of the first PC in the module.

// This will ONLY get PC members - any associates (Henchmen,
// familiars, summoned creatures and so on) will not be got by
// this loop. If the bPCOnly was FALSE in this script, it would ONLY
// get henchmen, familiars and so on for the faction.

void main()
{
    // We alway use the SAME object for the GetFirst/Next faction 
    // member calls - always define it first. Here it is the first PC.

    object oPC = GetFirstPC();

    // Get the first PC party member

    object oPartyMember = GetFirstFactionMember(oPC, TRUE);

    // We stop when there are no more valid PC's in the party.

    while(GetIsObjectValid(oPartyMember) == TRUE)
    {
        // Do something to party member

        // Get the next PC member of oPC's faction.
        // If we put anything but oPC into this, it may be a totally
        // unreliable loop!

        oPartyMember = GetNextFactionMember(oPC, TRUE);
    }
}

TR

Use of GetFirstFactionMember(oPC, TRUE) is correct in this context.

TransportAllToLocation scans all PCs in the multi-PC party, then jumps them one by one (and some of their assocs) with TransportToLocation.

So, with a single PC you’d just call TransportToLocation. But then this function is not multi-hench aware. It’s not even aware that NPCs may have associates at all. Maybe they couldn’t have in SoU… But they do now.

It might work for someone, but in the long run I wouldn’t recommend this include file.


@Tarot_Redhand Perhaps it’s a good idea to make a spot on https://nwn.wiki for some useful or frequently-asked-for snippets? They eventually get buried under other threads. Or a stub for updated lexicon pages.

Until you brought it up, I wasn’t even aware of the possibility for henchpersons to have their own henchpersons. This may have something to do with my having nothing to do with mp, and thus having sp blinkers on… On other hand it may not. Cest la vie.

Well I did go through the original nwn.bioware.com forums and create Just The FAQs, ma’am. Basically I took the contents of 2 separate threads and created pdf documents from them -

  1. Neverwinter Nights SCRIPTING FAQ & TUTORIALS.pdf (188 Pages, 36 articles) - Mainly by David Gaider
  2. Neverwinter Nights Newbie FAQ #2.pdf (103 Pages, 102 Questions Answered) - Mainly by Jassper

TR

Actually, it’s an SP issue, too. For example, horses can be henchmen of henchmen. Summoned creatures can be associates of associates, and so on.

@NWShacker interesting that the faction method works. I find factions a bit quirky, using them only as a last resort, but in this case it seems neater.

The delay in my code ensures that the PC arrives first, exactly on target and within any trigger that may be there. Delaying associates of associates is questionable, though it can make better use of space for, say, horses to arrive after the main party.

Horses as henchies. I know they can be quite intelligent but it would not occur to me that they were henchies. There again I have not created a module with horses in them (got an unfinished one with a horse that’s polymorphed into a giant chicken but that’s another story…)

TR

1 Like

@Proleric yeah, using a delay, it is possible to control which assoc might end up closer to PC when scanning the faction. I.e. henches could have priority over disposables for someone. But then it would require quite a party to make a difference. In my script it seems PC always arrives first.

By the way, the associate system is pretty flexible. For example, summons can have own summons or even true henchmen (they are correctly disbanded on unsummon - sub-summons disappear without VFX).


That’s a nice compilation, but having a wiki with examples from this and users would be useful too. Or do I have a wrong impression and the nwn.wiki is beta / testing-only / off-limits for general user?

Wow, that was a lot, thank you. I hope you’ll help me boil it down.

First off, I’ve seen PWs where they just have that little floaty highlight glow that you click. What placeable is that usually?

I tried this, but got a compile error, said variable within scope. I assume that’s oUser?

{
    // for OnClick - instant teleport
    object oUser = GetPlaceableLastClickedBy();
    // for OnUsed - teleport when used
    object oUser = GetLastUsedBy();
    // pick one from above; below - destination    
    object oDest = GetObjectByTag("JUMPHERE");

    SpeakString("Teleporting " + GetName(oUser));

    AssignCommand(oUser, ClearAllActions());
    AssignCommand(oUser, JumpToObject(oDest));
}

Otherwise, I couldn’t get the other scripts to work. Do they use a waypoint tag – DST_(tag of placeable) – as the teleport destination?

Just comment out the oUser you don’t need. It’s been written like that on purpose so you can choose the context in which you want the script to run.

Or reuse the same script for both events (though its rather pointless / problematic):

object oUser = GetPlaceableLastClickedBy();
if(!GetIsObjectValid(oUser))
{
    oUser = GetLastUsedBy();
}

You can jump to any destination you want: an object with some tag or a fixed location, preferably stored as local variable on the placeable.

Can you post a screenshot?

That’s probably just an invisible placeable.