SetGlobalInt VARNAME Limit .... 32?

My wife just had to start over in my campaign when we noticed some strange behaviour in the game. It looks like it turned out to be a limit on the Global Int variable name holder!

I thought we had quite a limit, along the lines of 50, but the variable in question added up to 36 characters - and ended up being the reason for the fail … it was based on a long name of her main PC.

Can anyone else confirm this?

Thanks!

hey Lance,
yes it’s a good idea to limit varnames (and tags) to 32 chars in Nwn2

I also limit scriptnames to 32 chars.

Rostertags are even less: 24 chars (iirc)

 
[i believe some limits were boosted from only 16 chars in Nwn1]

1 Like

Huge sigh this end … :frowning:

I now have to check over all the global vars that may have this problem. Rather than limit the 15 + 15 characters for a PC name, I have decided to write two dedicated functions to truncate the string returned to a maximum of 24 characters so that I can add them to my own extra bit of string (now also reduced) and ensure they go no more than 31 characters - which I just confirmed my end as you posted this. :slight_smile:

1 Like

if dealing with player input this might come in handy

// Converts a string into only 'safe' characters.
string SanitizeString(string sString, string sLegal = "_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
{
    string sOut, sVal;

    int iLength = GetStringLength(sString);
    int i;
    for (i = 0; i != iLength; ++i)
    {
        sVal = GetSubString(sString, i, 1);
        if (TestStringAgainstPattern("**" + sVal + "**", sLegal))
            sOut += sVal;
    }
    return sOut;
}

make sure the return isn’t blank …

then check its final length …

1 Like

Hi again … Well I already had the following script due to the importance of the PC name in the workings of my campaign anyway, so I “simply” swapped the two offending functions for mine (now have LB in front):

(I am now making my way through the code looking for further instances that rely on longer variable names based on either the PC name or the player name.)

I am just going to check the LOCAL var limit now as well … This could be a nightmare!

EDIT: I just tested LOCAL var name lengths with 40 characters and they appear to work, so that’s a relief, as that is plenty! Just the GLOBAL ones to sort now.

///////////////////////////////////////////////////////////////////////////////
// REMOVE ANY ILLEGAL PLAYERS BROUGHT INTO WORLD //////////////////////////////
///////////////////////////////////////////////////////////////////////////////
int DismissIllegalCheck(object oCreature);
int DismissIllegalCheck(object oCreature)
{
	// MYBOSS VARIABLE HAS NOT BEEN SET YET
	object oMainPC = GetControlledCharacter(oCreature); 
	int Remove = 0; string sIllegal;	
	int iCheck = 0; string sName = LBGetName(oCreature);
	string sNameChk = "PCNCHK_" + sName;
	
	// IS THE PLAYER TRYING TO LOAD A PC OTHER THAN THEIR SAVED GAME VERSION		
	string sPLAYERNAME = LBGetPCPlayerName(oCreature);	
	string sExistName = GetGlobalString(sPLAYERNAME);
	
	// COMPANIONS MADE BY CAMPAIGN ARE OK (ONLY CHECK PLAYER CREATED ONES)		
	if(GetLevelByClass(CLASS_TYPE_ARCANE_SCHOLAR, oCreature) > 0){Remove = 1; sIllegal = "ARCANE SCHOLAR";}
	else if(GetLevelByClass(CLASS_TYPE_DOOMGUIDE, oCreature) > 0){Remove = 1; sIllegal = "DOOMGUIDE";}
	else if(GetLevelByClass(CLASS_TYPE_HARPER, oCreature) > 0){Remove = 1; sIllegal = "HARPER";}
	else if(GetLevelByClass(CLASS_TYPE_DRAGONDISCIPLE, oCreature) > 0){Remove = 1; sIllegal = "RED DRAGON DISCIPLE";}
	else if(GetLevelByClass(CLASS_TYPE_RED_WIZARD, oCreature) > 0){Remove = 1; sIllegal = "RED WIZARD";}
	else if(GetLevelByClass(CLASS_TYPE_SHADOWTHIEFOFAMN, oCreature) > 0){Remove = 1; sIllegal = "SHADOW THIEF";}
	else if(GetLevelByClass(47, oCreature) > 0){Remove = 1; sIllegal = "NEVERWINTER NINE";}	
		
	// CANNOT HAVE AN APOSTROPHE IN THE NAME (ROSTER RECOGNITION PROBLEMS)
	if(Remove == 0)
	{			
		int iNameLength = GetStringLength(sName);
		// SendMessageToAllPCs("NAME IS >>> " + sName); // DEBUG
				
		while(iCheck < iNameLength)
		{	
			string Letter = GetSubString(sName, iCheck, 1);		
			Letter = GetStringUpperCase(Letter);
			
			if(Letter != "A" && Letter != "B" && Letter != "C" && Letter != "D" && Letter != "E"
			&& Letter != "F" && Letter != "G" && Letter != "H" && Letter != "I" && Letter != "J"
			&& Letter != "K" && Letter != "L" && Letter != "M" && Letter != "N" && Letter != "O"
			&& Letter != "P" && Letter != "Q" && Letter != "R" && Letter != "S" && Letter != "T"
			&& Letter != "U" && Letter != "V" && Letter != "W" && Letter != "X" && Letter != "Y"
			&& Letter != "Z" && Letter != " "){Remove = 1; iCheck = 100; break;}
		
			iCheck = iCheck + 1; 
		}
	}
	
	// CANNOT HAVE A PALADIN (GOOD) JOIN A PARTY OF HARVESTERS (LATE ENTRANT CHECK)	
	if(Remove == 0 && GetGlobalInt("LERoleset") == 1)
	{
		object oLEADER = GetMainPC(GetFactionLeader(oCreature));	
		if(GetLevelByClass(CLASS_TYPE_PALADIN, oCreature) > 0 && 
		GetAlignmentGoodEvil(oCreature) == ALIGNMENT_GOOD && GetAlignmentGoodEvil(oLEADER) == ALIGNMENT_EVIL)
		{Remove = 1; sIllegal = "PALADIN (AS A HARVESTER)";}
	}	
	
	// CHECK IF PC WITH SAME NAME IS ALREADY IN THE WORLD (BEST CHECK FOR DUPLICATION)
	// ONCE DISMISSED OR ABANDONED, THIS PC CANNOT BE IMPORTED INTO THE GAME AGAIN - A NEW VERSION MUST BE CREATED
	// UPDATED TO ALLOW DISMISSED CREATED PCS TO COME BACK IN AFTER USING THE "LEAVE" OPTION
	if(Remove == 0)
	{			
		if(sExistName != "" && sExistName != sName){Remove = 1; iCheck = 300;} 
		
		else if(GetGlobalInt(sNameChk) == 1 && GetLocalInt(oCreature, sNameChk) == 0){Remove = 1; iCheck = 200;} 
		
		else
		{
			SetGlobalInt(sNameChk, 1); 
			SetLocalInt(oCreature, sNameChk, 1);
			
			// STORE PLAYER/PC RELATIONSHIP (TO PREVENT WRONG PC ENTRY DUE TO PLAYER MISTAKE)			
			SetGlobalString(sPLAYERNAME, sName);
		}
	}
		 
	if(Remove == 1)
	{	
		effect FREEZE = EffectCutsceneImmobilize();
		SetLocalInt(oCreature, "ILLEGAL", 1);
		StripPC(oCreature, 0,0, FALSE); // STILL REMOVE ALL EQUIP TO PREVENT ILLEGAL ENTRY OF ITEMS
		ApplyEffectToObject(DURATION_TYPE_PERMANENT, FREEZE, oCreature);
		
		// PROVIDE OTHER PLAYERS AS TO WHY A PLAYER MAY NOT HAVE ENTERED (ORIGINAL PLAYER MAINPC WILL GET GUI)
		string sResult = "<<< ILLEGAL CLASS: " + sIllegal + " ATTEMPTED TO ENTER THE GAME >>>";
		if(iCheck == 100){sResult = "<<< ILLEGAL CHARACTER NAME: " + sName + " CONTAINS ILLEGAL CHARACTERS >>>";}
		else if(iCheck == 200){sResult = "<<< DUPLICATE PC NAME: " + sName + " EXISTED AT SOME TIME ALREADY >>>";}
		else if(iCheck == 300){sResult = "<<< PC NAME MISMATCH (USE CORRECT PC) >>>\n\n" + sName + " v " + sExistName;}
		
		object oHost = GetTheGameHost(oCreature);
		SetNoticeText(oHost, CRED + sResult);		
				
		// THE ILLEGAL PC IS THE PLAYER'S MAIN PC (PRESENT A GUI)
		if(oMainPC == oCreature)
		{
			FadeToBlack(oCreature, 0.5, 0.0); 
			
			// OTHERWISE DISPLAY INFORMATION INSIDE A GUI
			string sTitleText = "ILLEGAL PC ENTRY";		
			
			// Expanded Text For Placeables
			string sInfo = "<color=Orange>" + sResult + "</color>\n\nThe PC you are trying to enter the module with is not allowed for the above reason." +
			"\n\nClasses not allowed in The Scroll include: Arcane Scholar, Doomguide, Harper, Dragon Disciple, Red Wizard or Shadow Thief. Furthermore, a Paladin cannot join a " +
			"game that has already started, which is running an evil party. This module requires a PC's name to be made from standard letters ONLY and cannot use a name that has " +
			"been used previously in the module. Furthermore, a player must always use the SAME PC when returning to a saved game. \n\nWhen you click on the button below, you will be taken back to the Main Menu where you can select a legal character." +
			"\n\n<color=Orange>Sorry for any inconvenience.</color>";		
			
			// DISPLAY THE TEXT IN A GUI
			DisplayGuiScreen(oCreature, "IllegalPCWarn", TRUE, "IllegalPCWarn.xml"); 
			SetGUIObjectText(oCreature, "IllegalPCWarn", "TITLETXT", -1, sTitleText);
			SetGUIObjectText(oCreature, "IllegalPCWarn", "BoxText", -1, sInfo);
		}
		
		// THE PC IS A CREATED PC (JUST INFORM ITS MAIN PC)
		else 
		{				
			// DELAY REQUIRED FOR COMPANION TO FINISH ATTACHING (TO BE THEN UNATTACHED)
			DelayCommand(0.5, RemovePCFromWorld(oCreature));
		}			
	}
	
	return Remove;
}

that’s interesting  :)

this looks okay,

        int iNameLength = GetStringLength(sName);
        // SendMessageToAllPCs("NAME IS >>> " + sName); // DEBUG

        while(iCheck < iNameLength)
        {
            string Letter = GetSubString(sName, iCheck, 1);
            Letter = GetStringUpperCase(Letter);

            if(Letter != "A" && Letter != "B" && Letter != "C" && Letter != "D" && Letter != "E"
            && Letter != "F" && Letter != "G" && Letter != "H" && Letter != "I" && Letter != "J"
            && Letter != "K" && Letter != "L" && Letter != "M" && Letter != "N" && Letter != "O"
            && Letter != "P" && Letter != "Q" && Letter != "R" && Letter != "S" && Letter != "T"
            && Letter != "U" && Letter != "V" && Letter != "W" && Letter != "X" && Letter != "Y"
            && Letter != "Z" && Letter != " "){Remove = 1; iCheck = 100; break;}

            iCheck = iCheck + 1;
        }


        if (!CheckLegalCharacterLabel(sName)) { Remove = 1; iCheck = 100; }


// Returns TRUE if all characters of 'sLabel' are upper/lowercase alphabet
// and/or spaces.
int CheckLegalCharacterLabel(string sLabel)
{
    int iCode;

    int iLength = GetStringLength(sLabel);
    int i = -1;
    while (++i != iLength)
    {
        iCode = CharToASCII(GetSubString(sLabel, i, 1));
        if (iCode != 32                         // !space
            && (    iCode < 65                  // < A
                || (iCode > 90 && iCode < 97)   // > Z && < a
                ||  iCode > 122))               // > z
        {
            return FALSE;
        }
    }
    return TRUE;
}

(untested, was just itching my fingers…)

1 Like

Hey! Nice one!

I’ll grab a copy … and put it besides the code to check out later.

Thanks for that! :slight_smile:

EDIT: Was there a place you found the ASCII values or is it just some standard ref somewhere?

EDIT: There, I already added it actually, so I will give it a go.

EDIT: I just finished going over looking for all possible offending GetName and GetPCPlayerName function calls, and changed them to my truncated versions. So, hopefully, that should sort the issues we were having with extra long names like my wife who chose: Priscilia Honeysuckle. When I added by “_POSSESSIONDONE” in front, it was making 36 characters - too long!

Just noticed your link to the ASCII - Much appreciated. Thanks again! :slight_smile:

1 Like

standard ASCII table

http://www.asciitable.com/

i don’t trust Nwn(2) to deal with UTF-8 (sometimes it does, sometimes it doesn’t)

2 Likes

offtopic: these are the shenanigans i go through

// Adds target to party.
// NOTE: GetIsRosterMember() is bugged so there's no way to check if a creature
// is already on the roster. So re-add it to the roster even if it's there
// already. All bets are officially off.
void AddTargetToParty(object oTarget, object oParty)
{
    SetRosterNPCPartyLimit(9999);

    string sTag = GetTag(oTarget);
    if (sTag == "") sTag = "tag"; // safety+ (prevent trying to add a blank tag to roster)

    string sRoster = sTag;

    if (GetStringLength(sRoster) > 24)
        sRoster = GetStringRight(sRoster, 24);

    int id = 0;
    while (IsOnRoster(sRoster))
    {
        sRoster = sTag + "_id" + IntToString(id++);

        if (GetStringLength(sRoster) > 24)
            sRoster = GetStringRight(sRoster, 24);
    }

    SetLocalString(oTarget, VAR_TARGET_ROSTERLABEL, sRoster);

    AddRosterMemberByCharacter(sRoster, oTarget);
    AddRosterMemberToParty(sRoster, oParty);
}
1 Like

Have you hit a size limit for the roster name then?

Although, I think you would be fairly good storing in a LOCAL, which seemed OK to me - although only tested to the 40.

yes, that function puts an arbitrary NPC on the roster (and adds it to the party)… so the NPC needs a true rosterlabel, not just a local, which – at least when i was coding the funct – is capped at 24 chars.

ironically the description in NwScript.nss says 10 chars

//RWT-OEI 09/12/05
//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.
//Returns false on any error
int AddRosterMemberByCharacter( string sRosterName, object oCharacter );

my funct there checks if the label is already on the Roster and adds an increment if so – needless to say it’s not a function that should be used in a real campaign with real Companions …

1 Like