A script or two and a kangaroo (nothing to do with kangaroos)

Hi all,
Hoping to use this post to get some useful (reusable) scripts that will make life easier. Reusable, meaning they require variables on something, to get them working. This way that can hopefully be used multiple times.

First up, I got some help from the old “Chatest McGPT” to help write a script.

The reusable script (on_exit_area) is supposed to check the variable on the area “bodybag”, to the tag of the creature in the area to delete. An area cleanup script essentially. Below is the script, in all its flawed glory.
Testing it comes up negative.

(to clarify what I’m doing and isn’t working, is I place the script on exit of the area, then place variables in the area “bodybag” -sstring = tag of the creature(s) to delete.)

// By NWShacker, 30.10.2021
// Removes from oArea all objects with type matching iType or tag matching sTag

void NWSH_CleanArea(object oArea=OBJECT_SELF, int iType=OBJECT_TYPE_CREATURE, string sTag="BodyBag")
{
    object oOrigin;
    object oDelete;
    object oItem;
    int iDelete;

    // choose any object as origin for GetNearestObject()
    oOrigin = GetFirstObjectInArea(GetArea(oArea));

    // destroy all objects with type matching iType
    oDelete = GetNearestObject(iType, oOrigin, iDelete = 1);
    while(GetIsObjectValid(oDelete))
    {
        DestroyObject(oDelete);
        oDelete = GetNearestObject(iType, oOrigin, ++iDelete);
    }

    // destroy all objects with tag matching sTag (and their contents)
    oDelete = GetNearestObjectByTag(sTag, oOrigin, iDelete = 1);
    while(GetIsObjectValid(oDelete))
    {
        oItem = GetFirstItemInInventory(oDelete);
        while(GetIsObjectValid(oItem))
        {
            DestroyObject(oItem);
            oItem = GetNextItemInInventory(oDelete);
        }
        DestroyObject(oDelete);
        oDelete = GetNearestObjectByTag(sTag, oOrigin, ++iDelete);
    }

    // extra code in case oOrigin also matches any of the above criteria
    if((GetObjectType(oOrigin) & iType) > 0 || GetTag(oOrigin) == sTag)
    {
        oItem = GetFirstItemInInventory(oOrigin);
        while(GetIsObjectValid(oItem))
        {
            DestroyObject(oItem);
            oItem = GetNextItemInInventory(oOrigin);
        }
        DestroyObject(oOrigin);
    }
}

void main()
{
    // Destroy creatures and body bags:
    // NWSH_CleanArea(OBJECT_SELF, OBJECT_TYPE_CREATURE);

    // Destroy creatures, items on floor and body bags:
    // NWSH_CleanArea(OBJECT_SELF, OBJECT_TYPE_CREATURE|OBJECT_TYPE_ITEM);

    // Caveat: if sTag is "", this script will destroy items owned by PCs.
}

Any assistance will be appreciated.

1 Like

The first thing I noticed here is that you have nothing going on in void main() so it’s no wonder the script does nothing at the moment.

EDIT: Isn’t your script made, or at least the NWSH_CleanArea function, by NWShacker? NWShacker is a great scripter working with NWN1, so on that alone, I will assume that this function works as it should. You just need to use it in void main().

EDIT2: So perhaps something like this?

// By NWShacker, 30.10.2021
// Removes from oArea all objects with type matching iType or tag matching sTag

void NWSH_CleanArea(object oArea=OBJECT_SELF, int iType=OBJECT_TYPE_CREATURE, string sTag="BodyBag")
{
    object oOrigin;
    object oDelete;
    object oItem;
    int iDelete;

    // choose any object as origin for GetNearestObject()
    oOrigin = GetFirstObjectInArea(GetArea(oArea));

    // destroy all objects with type matching iType
    oDelete = GetNearestObject(iType, oOrigin, iDelete = 1);
    while(GetIsObjectValid(oDelete))
    {
        DestroyObject(oDelete);
        oDelete = GetNearestObject(iType, oOrigin, ++iDelete);
    }

    // destroy all objects with tag matching sTag (and their contents)
    oDelete = GetNearestObjectByTag(sTag, oOrigin, iDelete = 1);
    while(GetIsObjectValid(oDelete))
    {
        oItem = GetFirstItemInInventory(oDelete);
        while(GetIsObjectValid(oItem))
        {
            DestroyObject(oItem);
            oItem = GetNextItemInInventory(oDelete);
        }
        DestroyObject(oDelete);
        oDelete = GetNearestObjectByTag(sTag, oOrigin, ++iDelete);
    }

    // extra code in case oOrigin also matches any of the above criteria
    if((GetObjectType(oOrigin) & iType) > 0 || GetTag(oOrigin) == sTag)
    {
        oItem = GetFirstItemInInventory(oOrigin);
        while(GetIsObjectValid(oItem))
        {
            DestroyObject(oItem);
            oItem = GetNextItemInInventory(oOrigin);
        }
        DestroyObject(oOrigin);
    }
}

void main()
{
    // Destroy creatures and body bags:
    NWSH_CleanArea(OBJECT_SELF, OBJECT_TYPE_CREATURE,"tagofbodytoremove");
NWSH_CleanArea(OBJECT_SELF, OBJECT_TYPE_CREATURE,"tagofbodytoremove2");

    // Destroy creatures, items on floor and body bags:
    // NWSH_CleanArea(OBJECT_SELF, OBJECT_TYPE_CREATURE|OBJECT_TYPE_ITEM);

    // Caveat: if sTag is "", this script will destroy items owned by PCs.
}
2 Likes

Thanks Angalf, I’ll give it a shot and get back to you.
EDIT: Thank you! Worked a treat! I just replaced “tagofbodytoremove” with “BodyBag”. I did notice and this is no issue at all, but I renamed a creatures tag to delete in the area and it deleted it fine, but it deleted the other creatures that all share the same resref. So I’m assuming this destroys by resref, which is cool.

By the way, do you have any awesome scripts that make life easier for your campaign building? Was hoping that maybe others might be inclined to share, so there’s a post with handy reusable scripts.

Here is a simple one I use, which checks (trigger on enter) if a party member is in the party and alive, if so, speak string of dialog. Nice way to have companions chime in as they move around.
Uses variable parameters on the trigger: “CheckCharacterTag”, “DialogSpoken” & “DialogText” -quick and easy to setup when you save the variables for next time.

void main()
{
    // Get the tag of the character to check
    string sCharacterTag = GetLocalString(OBJECT_SELF, "CheckCharacterTag");
    
    // Check if the party member with the specified tag is alive
    object oCharacter = GetObjectByTag(sCharacterTag);
    
    if (GetIsObjectValid(oCharacter) && GetIsObjectValid(GetArea(oCharacter)))
    {
        if (GetIsDead(oCharacter))
        {
            // Character is dead, do nothing
            return;
        }
        else
        {
            // Check if the dialog has been spoken before
            int nDialogSpoken = GetLocalInt(OBJECT_SELF, "DialogSpoken");
            
            if (nDialogSpoken == 0)
            {
                // Get the dialog text from the trigger's local variable
                string sDialog = GetLocalString(OBJECT_SELF, "DialogText");
                
                // Speak the dialog above the character's head
                AssignCommand(oCharacter, SpeakString(sDialog, TALKVOLUME_TALK));
                
                // Set the flag to indicate that the dialog has been spoken
                SetLocalInt(OBJECT_SELF, "DialogSpoken", 1);
            }
        }
    }
}

Any campaign you download is likely to have a number of generically useful campaign scripts. I created a number for my Silverwand Sample Campaign and added more in my King’s Festival + Queen’s Harvest campaigns. Rather than a post with a bunch of actual scripts, maybe it would make sense to post a list of useful scripts and the campaigns they can be found in. In many cases the name of a script is enough to indicate what it is for.

1 Like

Probably my most used script, it stops the player going somewhere until a journal quest is at the right point…

void main()
{
object oPC=GetClickingObject();
if(!GetIsPC(oPC))return;
int nInt;
nInt=GetLocalInt(oPC,"NW_JOURNAL_ENTRYname of journal quest");
if (nInt< journal entry to beat, should be a number)
{
FloatingTextStringOnCreature("You shall not pass !",oPC);
return;
}
object oTarget;
location ITarget;
oTarget=GetWaypointByTag("wp_whatever you want");
ITarget=GetLocation(oTarget);
if(GetAreaFromLocation(ITarget)==OBJECT_INVALID)return;
oTarget=GetFirstFactionMember(oPC,FALSE);
while(GetIsObjectValid(oTarget))
{
AssignCommand(oTarget,ClearAllActions());
AssignCommand(oTarget,ActionJumpToLocation(ITarget));
oTarget=GetNextFactionMember(oPC,FALSE);
}
}

Stick it on a transition… On click.

1 Like

Here’s the same thing to start another module and as a bonus it sets the time of arrival.

#include "ginc_companion"
void main()
{
object oPC=GetClickingObject();
if(!GetIsPC(oPC))return;
int nInt;
nInt=GetLocalInt(oPC,"NW_JOURNAL_ENTRYjournal quest name");
if (nInt<number to beat)
{
FloatingTextStringOnCreature("Not now, maybe later !",oPC);
return;
}
SetTime(6,0,0,0); 
SaveRosterLoadModule("module name","wp_module sart waypoint");
}
1 Like

And this is very important it makes people dance !

void main()
{
int nRandom=Random(2);
if (nRandom==0)
{
PlayCustomAnimation(OBJECT_SELF,"dance02",1);
}
else
{
PlayCustomAnimation(OBJECT_SELF,"dance01",1);
}
}

Goes on the heartbeat of an npc.

2 Likes

I have a script I’m using right now that checks for the PCs or a companion’s “insight”. Since this is no skill or ability that’s included in NWN2, I did my own homebrewn version where I check for intelligence + wisdom + d20 against a dc of 17 in this case. I had my own script for this that’s quite convoluted (but it works), but here’s kevL_s’ version that’s much more compact. It’s a starting conditional script to be placed on a node in a conversation, preferably a red node…well, you all know how starting conditional works so…:

// kevL_s more compact version of my script
int StartingConditional(string sTag)
{
	object oPC;

	if (sTag == "$PC")
		oPC = GetFirstPC();
	else
		oPC = GetObjectByTag(sTag);

	int i = GetAbilityModifier(ABILITY_INTELLIGENCE, oPC);
	int w = GetAbilityModifier(ABILITY_WISDOM, oPC);

	return d20() + i + w >= 17;
}

And here’s my variation of Tsongo’s “don’t go there yet” script. A bit more compact perhaps:

void main()
{

	object oPC = GetFirstPC();

	if(GetJournalEntry("questtag",oPC) <1)
	{
		FloatingTextStringOnCreature("You have no reason to leave this area.", GetFirstPC(FALSE), FALSE);
		return;		
	}
	
	else	
		JumpPartyToArea(oPC, GetWaypointByTag("waypointtag"));
	

}

And my recruiting companions script that I’ve “stolen” from somewhere years ago that works really well:

/******************************************************************
Adds a Companion NPC and sets their XP equal to the PC's if they
are below. This script merges four scripts together so that you
don't have to setup all four script calls in a conversation.

This script also has a convention: the RosterName is the same as
the creature's TAG, resref, etc. They should all be named
the same.
*******************************************************************/
#include "ginc_param_const"
#include "ginc_debug"
#include "ginc_misc"

void main(string sCompanionTag)
{
//FROM: ga_roster_add_object
//Adds a NPC to the global roster of NPCs available to be added to
//a player's party. Roster Name is a 10-character name used to
//reference that NPC in other Roster related functions.
//The NPC will be left in the game world, but will now exist
//in the roster as well.

object oCompanion = GetObjectByTag(sCompanionTag);
int bResult = AddRosterMemberByCharacter(sCompanionTag, oCompanion);

//FROM: ga_roster_selectable
SetIsRosterMemberSelectable(sCompanionTag, 1);

//FROM: ga_roster_party_add
object oPC = GetFirstPC();
AddRosterMemberToParty(sCompanionTag, oPC);

	/////////////////////////////////////////////////////
	// Make the companion visible on the Roster GUI
	/////////////////////////////////////////////////////
	SetIsRosterMemberCampaignNPC(sCompanionTag, 0);
	
	/////////////////////////////////////////////////////
	// Just in case we forgot to set this, when we add a
	// companion to our party, we've met them. 
	/////////////////////////////////////////////////////
	SetLocalInt(oPC, "met_" + sCompanionTag, TRUE);


int nXP = GetPCAverageXP();
SetXP(oCompanion, nXP);
ForceRest(oCompanion);
}

Another one that I really like is when you enter a conversation the companions place themselves in a great formation looking at the NPC who owns the dialogue. This script is made by travus and kevL_s. I use it a lot in my latest module and the one I’m working on now:

//  ga_jump_face_npc
/*
	Jumps companions into formation and makes them face the sFacee at random intervals.
	The party will then be immobilized to prevent them from moving around during the convo.
	Place this on the very first node of the dialog.
	Use this in conjunction with the remove_csi script to remove the immobilization effect.
	
	sFacee - The tag of the NPC to talk to. 
	fSpacing - The spacing between party menbers. The minimum is 0.8.
	
	script by travus with additions of kevL_s
*/

#include "ginc_group"
#include "nw_i0_spells"

void FaceParty(object oPC, string sFacee)
{
	vector v = GetPosition(GetObjectByTag(sFacee));
	object oFM = GetFirstFactionMember(oPC, FALSE);

	while (GetIsObjectValid(oFM))
	{
		AssignCommand(oFM, ClearAllActions());
		DelayCommand(GetRandomDelay(), AssignCommand(oFM, SetFacingPoint(v, TRUE)));
		ApplyEffectToObject(DURATION_TYPE_PERMANENT, EffectCutsceneImmobilize(), oFM);
		oFM = GetNextFactionMember(oPC, FALSE);
	}	
}

void main(string sFacee, float fSpacing)
{
	object oPC = GetFirstPC();
	
	if (fSpacing < 0.8) fSpacing = 0.8f;
	
	ResetGroup(PARTY_GROUP_NAME);
	GroupAddFaction(PARTY_GROUP_NAME, oPC, GROUP_LEADER_FIRST, TRUE);	
	GroupSetBMAFormation(PARTY_GROUP_NAME, fSpacing);
	GroupSetNoise(PARTY_GROUP_NAME);	
	GroupMoveToFormationLocation(PARTY_GROUP_NAME, GetLocation(oPC), MOVE_JUMP_INSTANT);
	DelayCommand(0.1f, FaceParty(oPC, sFacee));
}

At the end of the conversation you need to run this script by travus so that the companions are no longer immobilized:

// remove_csi
/*
	Place this on the last node of the dialog to remove the immobilize effect
	from the ga_jump_face_convo script.
*/

#include "x0_i0_petrify"

void main()
{
	object oPC = GetFirstPC();
	object oFM = GetFirstFactionMember(oPC, FALSE);

	while (GetIsObjectValid(oFM))
	{
		RemoveEffectOfType(oFM, EFFECT_TYPE_CUTSCENEIMMOBILIZE);
		oFM = GetNextFactionMember(oPC, FALSE);
	}
}

And another favourite by kevL_s: When you speak to an innkeeper the PC doesn’t run around the counter to talk to the innkeeper (I mean, who does that in the real world?) but teleports to a waypoint in front of the counter instead. You need a waypoint for that and this script is to be put on the OnConversation of the Innkeeper NPC:

// 'oc_bartender'
/*
    OnConversation script for innkeeper. Script by kevL_s.
*/

const string TAG_WAYPOINT = "innkeeptalk";
const float  DEFAULT_DIST_DIALOG = 2.f;

void BeginDialog(object oSpeaker);


// OBJECT_SELF is innkeeper
void main()
{
    object oPc = GetLastSpeaker();

    if (GetDistanceToObject(oPc) > DEFAULT_DIST_DIALOG)
    {
        AssignCommand(oPc, ClearAllActions());

        object oWaypoint = GetNearestObjectByTag(TAG_WAYPOINT);
        AssignCommand(oPc, ActionMoveToObject(oWaypoint, TRUE, 0.f));

        object oSpeaker = OBJECT_SELF; // req'd - do not put OBJECT_SELF directly into the next call
        AssignCommand(oPc, ActionDoCommand(BeginDialog(oSpeaker)));
    }
    else // standard start conversation ->
    {
        ClearAllActions();
        BeginConversation();
    }
}


// OBJECT_SELF is pc here
void BeginDialog(object oSpeaker)
{
    ClearAllActions();
    SetFacingPoint(GetPosition(oSpeaker));

    object oPc = OBJECT_SELF; // req'd - do not put OBJECT_SELF directly into the next calls

    AssignCommand(oSpeaker, ClearAllActions());
    AssignCommand(oSpeaker, SetFacingPoint(GetPosition(oPc)));

    AssignCommand(oSpeaker, ActionStartConversation(oPc));
}
3 Likes

This doesn’t need a script… Have the inkeeper standing on a walkable area, put the bar around him but leave a walkable gap, fill the gap with a collision box, now nobody’s bar hopping.

1 Like

Cool scripts! Thanks for sharing.

Also regarding conversation distances, there is two things you can do to help with that. One, I believe is in the module properties, which you can change the minimum distance of a conversation, although that might be mod wide. Can’t recall now without the toolset open, if this option is available for the properties of an individual dialog.

Or alternatively, you could place a speak trigger down and on the parameter “Run” = 0, change it to “-1” (one of the rare instances you can use -1 integer) - don’t go trying it elsewhere, can corrupt a module if used incorrectly. Now your player will not move and start the conversation from their exact point when entering the trigger. (this does have the downfall of having PC’s/party members initiate a verbal “hello” (which you may be able to disable anyway, in the parameters) can’t remember.

1 Like

Hi guys, was going to share this script, which I recently got and have been placing around a fair bit to “scripthidden” = no, on an entering of a trigger. The idea is to have it as an easy means to reveal scripthidden NPC’s around the area, as they come into play.
It seems to work fine, but was random and took me awhile to realize that it only seems to work on the first instance of “NPCTagToReveal”= tag of creature to unscripthide parameter that I place on the trigger itself.

Could a script guru please help me fix it, so that the script “unscript-hides” all the parameters on the trigger with NPCTagToReveal and not just the first?

Here it is and once fixed, I think it would be a great little script to have.

void main()
{
    // Get the object that triggered the OnEnter event.
    object oTrigger = GetEnteringObject();

    // Check if the triggering object is a player character.
    if (GetIsPC(oTrigger))
    {
        // Get the custom variable "NPCTagToReveal" from the trigger.
        string sTagToReveal = GetLocalString(OBJECT_SELF, "NPCTagToReveal");

        // Check if the custom variable is set and not empty.
        if (sTagToReveal != "")
        {
            // Get all objects in the trigger's area.
            object oObject = GetFirstObjectInArea(GetArea(oTrigger));
            while (GetIsObjectValid(oObject))
            {
                // Check if the object is a creature and has the specified tag.
                if (GetObjectType(oObject) == OBJECT_TYPE_CREATURE && GetTag(oObject) == sTagToReveal)
                {
                    // Set the scripthidden property of the object to 0 (visible).
                    SetScriptHidden(oObject, 0);
                }
                oObject = GetNextObjectInArea(GetArea(oTrigger));
            }
        }
    }
}

EDIT: I think i just saw (correct me if I am wrong), that the script is “getting the next object in the area” that is another trigger like it? If so, I think that’s wrong and maybe that is affecting other similar triggers in the area?

Let me just say first: I’m a bit too stupid for this (so it’s probably quite meaningless for me to post here), but I’m going to take a guess anyway. I’m not sure I understand the need of using this bit:

I would probably do the script like this instead (I often have to simplify scripts so that I can follow and understand them myself, my brain is STILL not made for scripting even though I understand a bit nowadays):

void main()
{
    // Get the object that triggered the OnEnter event.
    object oEnterObject = GetEnteringObject();

    // Check if the triggering object is a player character.
    if (GetIsPC(oEnterObject) && !GetLocalInt(OBJECT_SELF,"Done"))
    {
		SetLocalInt(OBJECT_SELF,"Done",TRUE);
    
            // Get all objects in the trigger's area.
            object oObject = GetFirstObjectInArea(GetArea(oEnterObject ));
            while (GetIsObjectValid(oObject))
            {
                // Check if the object is a creature.
                if (GetObjectType(oObject) == OBJECT_TYPE_CREATURE)
                {
                    // Set the scripthidden property of the object to 0 (visible).
                    SetScriptHidden(oObject, 0);
                }
                oObject = GetNextObjectInArea(GetArea(oEnterObject));
            }
       
    }
}

However, my version of the script will go over ALL creatures in the area, even those not scripthidden and try to un-scripthide those, so that may bog down the game. It will also only run one time which I think is prefered, but I don’t know.

This is where it gets tricky. To run over all variables (I think that’s the right word) on the trigger itself, we would need some kind of loop, unless you want to do several if statements after one another. Maybe there’s an easy solution for this, but I don’t know it. Otherwise I guess you would have to do something along the lines of:

string sTagToReveal = GetLocalString(OBJECT_SELF, "NPCTagToReveal");
string sTagToReveal2 = GetLocalString(OBJECT_SELF, "NPCTagToReveal2");

        // Check if the custom variable is set and not empty.
        if (sTagToReveal != "")
        {
             //lots of code here
        }

        if(sTagToReveal2 !="")
        {
            //same kind of "lots of code" here
        }

1 Like

Thanks Andgalf, I’ll give it a try in the morning! Yeah I got that script from chatgpt again I think. I’ve really only got like 10 custom scripts and revealing them is showing how dodgy they really are haha. Thanks again.

It could just be me not being a good enough scripter though. I think the script you showed here looks solid. It apparently just doesn’t do quite what you want it to do.

Try this if u like, put debug in so you can see what’s happening (i didn’t test it) – comment out or delete the debug lines later …

// 'tr_enter_reveal'
/*
	OnEnter script for a trigger - reveals scripthidden creatures that have a
	specified tag.
	
	The tag to reveal needs to be set on the trigger as "NPCTagToReveal".

	Multiple tags can be specified in this format: "tag1,tag2,tag3[...]"
*/

void main()
{
	object oTrigger = OBJECT_SELF;
	SendMessageToPC(GetFirstPC(FALSE), "tr_enter_reveal ( " + GetTag(oTrigger) + " )");					// debug

	if (!GetLocalInt(oTrigger, "Done") && GetIsPC(GetEnteringObject()))									// check if this is done yet and
	{																									// Check if the triggering object is a controlled player character.
		SendMessageToPC(GetFirstPC(FALSE), ". is PC");													// debug
		SetLocalInt(oTrigger, "Done", TRUE);															// mark this as done

		string sTagCurrent, sTagObject;
		object oArea = GetArea(oTrigger), oObject;
		int pos;

		string sTagToReveal = GetLocalString(oTrigger, "NPCTagToReveal");								// Get the custom variable "NPCTagToReveal" from the trigger.
		while (sTagToReveal != "")																		// Check if the custom variable is set and not empty.
		{
			SendMessageToPC(GetFirstPC(FALSE), ". . sTagToReveal0= " + sTagToReveal);					// debug

			pos = FindSubString(sTagToReveal, ",");														// check for multiple tags to search for
			if (pos == -1)
			{																							// only one tag ->
				sTagCurrent = sTagToReveal;																// search for 'sTagCurrent'
				sTagToReveal = "";																		// stops the while() loop
			}
			else
			{																							// has multiple tags ->
				sTagCurrent = GetStringLeft(sTagToReveal, pos);											// search for 'sTagCurrent'
				sTagToReveal = GetStringRight(sTagToReveal, GetStringLength(sTagToReveal) - pos - 1);	// delete 'sTagCurrent,' from 'sTagToReveal'
			}
			SendMessageToPC(GetFirstPC(FALSE), ". . sTagCurrent= " + sTagCurrent);						// debug
			SendMessageToPC(GetFirstPC(FALSE), ". . sTagToReveal1= " + sTagToReveal);					// debug


			oObject = GetFirstObjectInArea(oArea);
			while (GetIsObjectValid(oObject))															// Get all objects in the trigger's area.
			{
				if (GetObjectType(oObject) == OBJECT_TYPE_CREATURE)
				{
					sTagObject = GetTag(oObject);
					SendMessageToPC(GetFirstPC(FALSE), ". . . " + sTagObject);							// debug

					if (sTagObject == sTagCurrent)														// Check if the object is a creature and has the specified tag.
					{
						SendMessageToPC(GetFirstPC(FALSE), ". . . . <c=green>found</c>");				// debug
						SetScriptHidden(oObject, FALSE);												// Set the scripthidden property of the object to 0 (visible).
					}
				}
				oObject = GetNextObjectInArea(oArea);
			}
		}
	}
	else SendMessageToPC(GetFirstPC(FALSE), ". is Done or !pc");
}
2 Likes

Hehe, I suspected there was some way to do it, but it’s beyond my scripting abilities. Glad that kevL_s could help out once again. StringLeft and StringRight and SubString is a bit beyond my understanding at the moment.

2 Likes

hehe, its just addition and subtraction …

  • the first char of a string is always position 0
  • FindSubString() returns -1 if the substring is not found

Ok, but what is a substring then?

a part (or all) of a given string …

eg. “sub” is a substring of “substring”

@andgalf All credit to you for attempting to learn scripting, I reckon. Well done!

@kevL_s Thanks Kev, that’s a much nicer looking script in the toolset (from a novice point of view). I’m not always entirely clear on what variables need to be used from a script.

Does the trigger this script is to be attached to, need to surround the scripthidden NPC’s in question? Or am I reading that wrong?

I’m also not entirely sure what variables I need to use. I get “NPCTagToReveal” for the first instance, but do I then add a different variable for additional ones? Or can I just add them all as “NPCTagToReveal”?

Thanks again for your help! Everyone that helps me, will 100% be getting credited.

EDIT: I’ve tested the new script using “NPCTagToReveal” on the 4 NPC’s to reveal, using individual “NPCTagToReveal” for each tag. I’ve also tested it using one “NPCTagToReveal” variable with each of the strings separated by a comma. Not sure if I’m doing it correctly, but both instances only reveal 1 of the 4 creatures and it seems to be random each time.

Here is the screenshot of the debug messages: