Save position of companions and not PC

I have a quest where the PC is “imprisoned” and the companions can go and do stuff on their own while the PC is inside a small space within area with walkmesh triggers. Now I’ve encountered a problem. If the player saves in this area and then reloads, then everyone will be teleported to the PC and thus be imprisoned. Is there some way to save the position of the controlled companion when the game is saved so that I can teleport the companions to this spot and the PC to the prison spot when reloading?

There’s no native way of doing it, but a custom heartbeat script for the area that cycles through the roster and records positions of companions, with the client enter script restoring them should probably work? You probably want to set a more frequent HB rate for the area too.

Haven’t tried it out, but this might work for HB script:

void main()
{
	if (StoryProgressionVariableGoesHere == ??) // Fill this in yourself
	{
		string sRosterName = GetFirstRosterMember();
	
		while (sRosterName != "")
		{
			object oMember = GetObjectFromRosterName(sRosterName);
			if (GetIsObjectValid(oMember))
			{
				vector vPos = GetPosition(oMember);
				SetLocalFloat(oMember, "fPrisonPosX", vPos.x);
				SetLocalFloat(oMember, "fPrisonPosY", vPos.y);
				SetLocalFloat(oMember, "fPrisonPosZ", vPos.z);
			}
			sRosterName = GetNextRosterMember();
		}
	}
}

And this for on client enter:

void main ()
{
	if (StoryProgressionVariableGoesHere == ??) // Fill this in yourself
	{
		SetCustomHeartbeat(OBJECT_SELF, 1000);
		string sRosterName = GetFirstRosterMember();
	
		while (sRosterName != "")
		{
			object oMember = GetObjectFromRosterName(sRosterName);
			if (GetIsObjectValid(oMember))
			{
				vector vPos;
				vPos.x = GetLocalFloat(oMember, "fPrisonPosX");
				vPos.y = GetLocalFloat(oMember, "fPrisonPosY");
				vPos.z = GetLocalFloat(oMember, "fPrisonPosZ");
				location lJumpPoint = Location(OBJECT_SELF, vPos, 0.0f);
				AssignCommand(oMember, ClearAllActions());
				AssignCommand(oMember, JumpToLocation(lJumpPoint));
			}
			sRosterName = GetNextRosterMember();
		}
	}
	else SetCustomHeartbeat(OBJECT_SELF, 6000);
}

As you might infer from the above code, you most likely want a check for a variable you have that controls story progression to ensure it doesn’t happen on first entry, or when visiting the area during other points in the story.

1 Like

@Akhacha Tried it out. It seems to work really well, actually. Not sure I understand the

and the

What does these two do in this situation? Sorry, I still only understand basic stuff when it comes to scripting.

With the variable I just went with a simple global int.

EDIT: I also added a (maybe unnecessary) check if the roster members are in the area. Since I have more roster members than that is part of the party I thought it a good thing to check that. So my modified version of your heartbeatscript:


void main()
{

if (GetGlobalInt("chicken")) // Fill this in yourself
	{
		object oArea = GetObjectByTag("cavel1");
		
		string sRosterName = GetFirstRosterMember();
	
		while (sRosterName != "")
		{
			object oMember = GetObjectFromRosterName(sRosterName);
			if (GetIsObjectValid(oMember) && GetArea(oMember) == oArea)
			{
				vector vPos = GetPosition(oMember);
				SetLocalFloat(oMember, "fCavesPosX", vPos.x);
				SetLocalFloat(oMember, "fCavesPosY", vPos.y);
				SetLocalFloat(oMember, "fCavesPosZ", vPos.z);
			}
			sRosterName = GetNextRosterMember();
			
		
		}
	}


}

EDIT2: So, checking the Script Assist, I see SetCustomHeartBeat sets up a custom time heartbeat check for the characters? That’s good, I think, but do I really need the “else SetCustomHeartbeat(OBJECT_SELF, 6000);”? Maybe that’s for safety…Hmm, yeah. If the global int is gone after it having been there we need to reset the heartbeat script to the normal 6 seconds.

Good to hear it’s working properly. First of all, the target for the custom HB rate is the area - it targets OBJECT_SELF, which for the on client enter script is the area itself. You shouldn’t need to set it to 6000, that’s just the default rate and so I set it back after. It should be perfectly fine to skip that part though.

@Akhacha Thank you very much! Really glad there was an easy way of solving this! I give you credit in the script itself, of course (in my module/campaign), so if anyone else sees the code they will know it’s from you.

I couldn’t have coded this stuff on my own. It’s a bit too advanced for me. I can read it and I grasp 99 % of the whole thing though, but I’ve never used local floats or vector stuff in my own scripts. I have scripts that Lance, travus and kevL_s have done for me using vector stuff, but I’ve never got into it myself.

I also added SetOwnersControlledCompanion in the client enter script, so that the game immediately switches to one of the companions when entering, which made it work exactly as I wanted.

You’re welcome!

As far as floats go, they’re much like ints - an int is simply an integer, a whole number without decimals. A float, meanwhile, is a number that can contain decimals, which is useful when you need the extra precision. Other than that they can be handled much in the same way as ints, with many of the same conversion functions existing, such as FloatToString (compare IntToString).

A vector on the other hand is simply a collection of three floats, typically used for positioning, hence why they’re labeled X, Y and Z.

1 Like

Slight update on this since I was messing around with something that might have used SetCustomHearbeat on an area too… unfortunately, SetCustomHeartbeat does not seem to work on an area (nor triggers, for that matter), so this script would only save the positions of companions at the regular heartbeat rate of every six seconds. The basic function of the script will still work, but there might be some disparity between locations when loading, compared to saving.

If this bothers you, the simplest solution is probably to assign the heartbeat script to a placeable in the area (make sure it’s not marked as static).