Is this a bug in this stock script, or is it something else?

By coincidence I encountered something that might be a bug in ga_jump_players. I use this script all the time when jumping the player to one area or another through dialogue. I have always set the bWholeParty to TRUE (or 1, the same thing) even though in the description it says it does nothing in campaign mode. So when testing a thing, I happened to leave this to 0, and then when I fired my OnClientEnter script it didn’t think that a player character had entered. Really strange (or maybe it’s logical?)

I had this function running on the OnClientEnter:

object oPC = GetEnteringObject();

if(!GetIsPC(oPC)) return;

and when things didn’t happen as they should I did a thing like this:

if(!GetIsPC(oPC))
{

SendMessageToPC(oPC,"The entering object is not a player. What?!?!");
return;

}

and I got that message when choosing 0 at bWholeParty. Maybe I’m interpreting the stock script wrong. Here’s the ga_jump_players script in its entirety:

// ga_jump_players(string sDestTag, int bWholeParty, int bOnlyThisArea)
/*
   Jumps the PC party to a waypoint or object.

   Parameters:
     string sDestTag   = Tag of waypoint or object to jump to.
     int bWholeParty   = DOES NOTHING IN CAMPAIGN.  If =0 then jump the PC only, else jump the PC's party.
     int bOnlyThisArea = DOES NOTHING
*/
	
// ChazM 3/11/05
// BMA-OEI 1/11/06 removed default params 
// EPF 2/6/06 -- fixed the loop in JumpParty to deal with multiple parties
// ChazM 3/3/06 - replaced JumpParty() w/ JumpPartyToArea() script command.  Modified to always jump whole party in campaign.
// ChazM 7/13/07 - Dominated removed from party if campaign flag set.

#include "ginc_debug"
#include "ginc_transition"

// Jump oPC's whole party.
//  oPartyMember - member of the party to jump
//  oDestination - the destination (typically a waypoint object)
//  bOnlyThisArea - Jump only members of the party in the same area as oPartyMember? Default is true
void JumpParty(object oPartyMember, object oDestination, int bOnlyThisArea)
{
	object oThisArea = GetArea(oPartyMember);
	
	object oJumper = GetFirstFactionMember(oPartyMember, FALSE);

	if(GetIsPC(oJumper))
	{
		oJumper = GetNextFactionMember(oPartyMember, FALSE);
	}


    while (GetIsObjectValid(oJumper) == TRUE)
    {
		if (!bOnlyThisArea || (GetArea(oJumper) == oThisArea))
		{
			AssignCommand(oJumper, JumpToObject(oDestination));
		}
        oJumper = GetNextFactionMember(oPartyMember, FALSE);
    }

	oJumper = GetFirstFactionMember(oPartyMember, TRUE);


    while (GetIsObjectValid(oJumper) == TRUE)
    {
		if (!bOnlyThisArea || (GetArea(oJumper) == oThisArea))
		{
			AssignCommand(oJumper, JumpToObject(oDestination));
		}
        oJumper = GetNextFactionMember(oPartyMember, TRUE);
    }
}

void main(string sDestTag, int bWholeParty, int bOnlyThisArea)
{
	object oPC = (GetPCSpeaker()==OBJECT_INVALID?OBJECT_SELF:GetPCSpeaker());
	if ( GetGlobalInt(CAMPAIGN_SWITCH_REMOVE_DOMINATED_ON_TRANSITION) == TRUE )
	{
		int nRemoved = RemoveDominatedFromPCParty(oPC);
	}
	
    object oDestination = GetObjectByTag(sDestTag);

	if (bWholeParty == 0 && GetGlobalInt(VAR_GLOBAL_GATHER_PARTY)==0)
	{
		AssignCommand(oPC, JumpToObject(oDestination));
	}
	else
	{
		// BMA-OEI 6/14/06
		SinglePartyTransition( oPC, oDestination );
		//JumpPartyToArea(oPC, oDestination);
		//JumpParty(oPC, oDestination, bOnlyThisArea);
	}
}

EDIT: Looking at it some more, and looking at my other scripts when jumping the PC not from dialogue, I see that I tend to always use the JumpPartyToArea function. So maybe things go wrong since this script uses AssignCommand(oPC, JumpToObject(oDestination)); when setting bWholeParty to 0? In any case I think it seems a bit scary and dumb to place the
int bWholeParty = DOES NOTHING IN CAMPAIGN.
if that is, as it seems, false.

hey Ag /brief note →

int bWholeParty = does nothing unless VAR_GLOBAL_GATHER_PARTY (a 'campaign' switch) is FALSE.

… not quite the same as “does nothing in [any] campaign”

@andgalf Just curious, were you controlling the main character or a companion during this case?

I was controlling the main character.

Interesting test results - There are two things I found causing issue in this case:

First, this line of code in an OnClientEnter script…

if(!GetIsPC(oPC)) return;

…seems to cause the script to RETURN 100% of the time if controlling a companion when entering the area.

Second, if the bWholeParty parameter in the ga_jump_party script is set to zero (and the VAR_GLOBAL_GATHER_PARTY flag is FALSE), then this line of code is used to jump the party to the new area:

AssignCommand(oPC, JumpToObject(oDestination));

Using that line of code appears to cause the last companion in your party to jump first into the new area, thus causing the if(!GetIsPC(oPC)) return; code in the OnClientEnter to RETURN as described above due to it being a companion.

Solution: Don’t use if(!GetIsPC(oPC)) return; code in a OnClientEnter script.

I’d really love if someone could confirm this, of course.

I have this in my own area OnClientEnter …

// ESSENTIAL TO USE THIS FUNCTION HERE !!!!
	object oPlayer = GetFirstEnteringPC();

I did this years ago and must have made the comment for a reason …

//RWT-OEI 09/07/05
//This returns the first entering player object when called from an
//OnClientEnter script. Should never return INVALID_OBJECT unless
//called from a script other than an OnClientEnter script.
//To be used along with GetNextEnteringPC() to iterate over the
//list of entering PCs

Suggesting an invalid object can happen other ways.

GetIsPC is a tricky function, it’s very specific and is very interesting in a PW setting.

For a regular module comparing the faction of the object with the faction of GetFirstPC() is 90% of the time the better solution.

Interesting replies here. Well, I’m going to keep my if(!GetIsPC(oPC)) return; code in my OnClientEnter scripts. But why, you may ask. Well, as I have it set up at the moment (one thing that Lance made it clear to me) is that whenever you enter a conversation with an NPC it always switches to the main PC. This was apparently a setting in campaign mode (I’ve never fiddled with this). So since when the ga_jump_players is called, you always, in my case, control the PC, and since I always set bWholeParty to 1 there shouldn’t be a problem. I have never had this fail until I set it to 0, so I don’t think it will be an issue for me if I just stick to this. And whenever I don’t jump the PC and the party from a conversation, I always use the function JumpPartyToArea.

Re what @Shallina mentions regarding a MP environment, I found it helpful to store the HOST as an object and test against this for certain ClientOnEnter checks with further loaded games. As this check takes place on the module OnClientEnter (NB: Not the area OnClientEnter), by the time we reach the area OnClientEnter, we can then determine if the PC entering the area is the one we actually need (HOST) if we then refer back to the stored object at load time, using GetTheGameHost function below.

@travus NOTE: If the first detected PC with GetFirstEnteringPC(); is NOT the player’s main PC (but one of their companion PCs), we can still have the script go ahead and do something with it if we wish to (because it is the first entering), but it may fail to work with some functions that follow due to the fact that not every PC may yet have entered. (As @andgalf experienced.) However, he can “bypass” this fail issue by ensuring certain parameters are met with the function he uses, or see it fail if not. That is what I believe is happening.

This is my home-brew function I use as the module loads to help store the host to check against if needed in a MP environment. But, as @andgalf designs SP only, then as long as he is aware of potential consequences (like this party jump function), then his usage should as @Shallina says, should be fine.

NB: The host is stored the first time it is called, and thereafter just returns the main PC that has been stored on the module. (NB: This function was also designed for backward compatibility at the time and so may look strange in what it is doing. i.e. There was a time I had not considered storing the host as an object and had to do so half way through a design. :wink: )

///////////////////////////////////////////////////////////////////////////////
// JUST RETURNS FIRST PC IN A SP GAME. (NB: COULD BE A DM IN A MP GAME SETUP!)
// MP: ALWAYS RETURNS THE SAME PLAYER, BUT THEY COULD BE A DM.
///////////////////////////////////////////////////////////////////////////////
object GetTheGameHost();
object GetTheGameHost()
{
	object oModule = GetModule();
	object oGAMEHOST = GetLocalObject(oModule, "GAMEHOST");		
	if(oGAMEHOST != OBJECT_INVALID){return oGAMEHOST;}
	
	// SETUP (BACKWARD COMPATIBLE)
	else
	{
		object oHost = OBJECT_INVALID;
		
		if(GetIsSinglePlayer())
		{
			oHost = GetFirstPC();
		} 
		
		else
		{
			object oTestHost = GetFirstPC();
			
			while(oTestHost != OBJECT_INVALID)
			{
				if(GetIsPCHost(oTestHost))
				{
					oHost = oTestHost; 
					
					// STORE FOR RETRIEVAL
					SetLocalObject(oModule, "GAMEHOST", oHost);
					
					break;
				}
			
				oTestHost = GetNextPC();	
			}
		
		}
	
		return oHost;
	}
}