Script for placeable inventory

Which error? What’s in that line?

Here's complete script - it compiles OK.
// By NWShacker, 2020-12-22 (updated 2020-12-23)

// Spawns iItemCount number of items with ResRef sItemResRef
// in randomly selected containers with tag sContainerTag
// (containers are selected without repetition); iStackSize
// and sNewTag are passed directly to CreateItemOnObject()
void NWSH_SpawnItemsInContainers(string sContainerTag, string sItemResRef, int iItemCount, int iStackSize=1, string sNewTag="")
{
    object oContainer1;
    object oContainer2;
    object oTemp;
    string sVariable;
    int iContainers;
    int iContainer1;
    int iContainer2;

    // local variable to create container "array"
    sVariable = "container_" + sItemResRef;

    // get number of containers (count objects with this tag)
    do
    {
        oContainer1 = GetObjectByTag(sContainerTag, iContainers++);
        SetLocalObject(oContainer1, sVariable, oContainer1);
    }
    while(GetIsObjectValid(oContainer1));
    iContainers -= 1;

    // trivial case: number of containers does not exceed number of items
    if(iContainers <= iItemCount)
    {
        for(iContainer1 = 0; iContainer1 < iContainers; iContainer1++)
        {
            oContainer1 = GetObjectByTag(sContainerTag, iContainer1);
            CreateItemOnObject(sItemResRef, oContainer1, iStackSize, sNewTag);
            DeleteLocalObject(oContainer1, sVariable);
        }
        return;
    }

    // shuffle container "array"
    for(iContainer1 = 0; iContainer1 < iContainers; iContainer1++)
    {
        iContainer2 = iContainer1 + Random(iContainers - iContainer1);
        oContainer1 = GetObjectByTag(sContainerTag, iContainer1);
        oContainer2 = GetObjectByTag(sContainerTag, iContainer2);
        oTemp = GetLocalObject(oContainer1, sVariable);
        SetLocalObject(oContainer1, sVariable, GetLocalObject(oContainer2, sVariable));
        SetLocalObject(oContainer2, sVariable, oTemp);
    }

    // sample container "array"
    for(iContainer1 = 0; iContainer1 < iItemCount; iContainer1++)
    {
        oContainer1 = GetObjectByTag(sContainerTag, iContainer1);
        oContainer2 = GetLocalObject(oContainer1, sVariable);
        CreateItemOnObject(sItemResRef, oContainer2, iStackSize, sNewTag);
        // ** DEBUG DELETE THIS **
        AssignCommand(oContainer2, SpeakString("SPAWNING ITEM!"));
        // ** DEBUG DELETE THIS **
    }

    // local variable cleanup
    for(iContainer1 = 0; iContainer1 < iContainers; iContainer1++)
    {
        oContainer1 = GetObjectByTag(sContainerTag, iContainer1);
        DeleteLocalObject(oContainer1, sVariable);
    }
}

// Destroys all items with tag sItemTag in all containers with
// tag sContainerTag; if sItemTag is "", all items are destroyed
void NWSH_DestroyItemsInContainers(string sContainerTag, string sItemTag="")
{
    object oContainer;
    object oItem;
    int iContainer;

    do
    {
        oContainer = GetObjectByTag(sContainerTag, iContainer++);
        oItem = GetFirstItemInInventory(oContainer);
        while(GetIsObjectValid(oItem))
        {
            if(sItemTag == "" || GetTag(oItem) == sItemTag)
            {
                DestroyObject(oItem);
                // ** DEBUG DELETE THIS **
                AssignCommand(oContainer, SpeakString("DESTROY ITEM!"));
                // ** DEBUG DELETE THIS **
            }
            oItem = GetNextItemInInventory(oContainer);
        }
    }
    while(GetIsObjectValid(oContainer));
}

void main()
{
    // life
    NWSH_SpawnItemsInContainers(
        "Barrel",                     // container tag (barrel)
        "nw_it_mpotion021",           // item resref (ale)
        4);                           // 4 items to spawn

    // death
    AssignCommand(GetModule(), DelayCommand(10.0,
    NWSH_DestroyItemsInContainers(
        "Barrel",                     // container tag (barrel)
        "NW_IT_MPOTION021")));        // item tag (ale)
}

got it to work thanks!

What’s your problem with mine? Doesn’t it work as you expected?

@Dragonqueeny,

Yes, some feedback on other scripts provided would be appreciated. :slight_smile:

Did you try my own script? Was it too complicated to use? What was it that you did not find helpful?

What was it when you say you “love” it about one script, but remain silent on others?

Thanks in advance.

EDIT: The script I provided had been tested and confirmed as working. I tried various settings and all worked fine, including multiple random items if required. I also made sure it worked as you required.

There is an answer to this too, but as you say later, it is an important point to have raised at the time of request. Also, it is worth you mentioning you are coding for a PW, as there are other considerations in general for such. However, if you are not familiar with scripting, you may wish to steer clear of a PW and aim for a simpler single player module for now.

@NWShacker,

I do like the idea of “virtually” randomising the chests among themselves prior item creation. A bit more efficient than implementing a string variable as I did.

Nice approach! :+1:

I may have a go at rewriting mine with that method in mind, while (hopefully) maintaining some of the options I have. I’ll post back if I do it. :wink: It will certainly make it more efficient.

Thanks, Lance.

@Mmat,

I can offer some feedback for you, but hopefully @Dragonqueeny will respond themselves for you too.

  1. I presume the “plc_solwhite” placeable is just an extra debug feedback indicator?
  2. It may have been easier for the builder if you allow a single tag for the placeable, but that is just a personal preference I have.
  3. The script is currently limited to a specific tag and resref, but unless considering it for other items and placeables, then it matters not. It is worth considering for general application though.
  4. The biggest potential issue (although it will vary with practice and may not be such a big deal), is the potential for a while loop to have to repeat due to a location (int) clash. It may be possible this would cause a slight jump in the gameplay if there are too many integer clashes, which increases in likelihood by the time we get to i4.

I hope that is helpful. :slight_smile:

Thanks, Lance.

2 Likes

While I think it’s great that several of you offered scripts, I think it’s a little much to expect the OP to try them all out and give feedback on each. She wants to get something to work and move on… Looks like she picked one and got it working.

Just as an aside, the original request said the PC must open every chest to get all the parts (i.e. the 4th part needs to be in the last chest the PC opens). That’s a slightly different thing from what all these implement and adds a whole different level of complexity… but maybe that was not really what she meant :slight_smile:

1 Like

That’s a very good point …

I think this is the case.

It depends how long someone has spent trying to help I suppose … but I think I would at least try to give some feedback to encourage those who have made the effort for the OP. But maybe that’s just me?

Anyway, here is a version that works in a similar fashion to NWShacker. I intend to work on it a bit more, as I am going to try to have it accommodate other variations that my previous script included, but I did say I would post some of the next bit of work …

NB: This version currently does not allow multiple items across a random set, as my previous script does. I may look at adding that later.

//////////////////////////////////////////////////////////////////////////////////////////////
// FUNCTION: ITEMS RANDOMLY PLACED IN PLACEABLES BY LANCE BOTELLE (BASED ON NWShacker APPROACH)
//////////////////////////////////////////////////////////////////////////////////////////////
// EXAMPLE USAGE: PLACE THIS SCRIPT ON A TRIGGER WITH VARIABLE STRUCTURE AS FOLLOWS:-
// 1) A STRING VARIABLE CALLED "PLACETAG" WITH THE TAG OF THE PLACEABLE BEING USED FOR RANDOM FIND.
// 2) AN INTEGER VARIABLE CALLED "QUANTITY" WITH THE TOTAL NUMBER OF ITEMS TO FIND IN THE SET = X.
// 3) STRING VARIABLES IN FORMAT "RR1" CONTAINING RESREF OF OBJECT(S) TO CREATE. (TO BE UPDATED.)
//////////////////////////////////////////////////////////////////////////////////////////////
void CreateItemsInPlaceable(string sPlaceTag, string sItemResRef, int iItemCount, int iStackSize=1, string sNewTag="");
void CreateItemsInPlaceable(string sPlaceTag, string sItemResRef, int iItemCount, int iStackSize=1, string sNewTag="")
{
	//////////////////////////////////////////////////////////////////////////////////
	// COUNT PLACEABLES USED & STORE TEMPORARY VERSION OF SELF ON SELF (TO ALLOW SWAP)
	//////////////////////////////////////////////////////////////////////////////////
		
	// INITIALIZE	
	int iCount = 0;
	object oPlaceable = GetObjectByTag(sPlaceTag, iCount);	
		
	while(oPlaceable != OBJECT_INVALID)
	{		
		SetLocalObject(oPlaceable, sItemResRef, oPlaceable);
		
		iCount = iCount + 1;
		oPlaceable = GetObjectByTag(sPlaceTag, iCount);
	}
	
	// THE PLACEABLE COUNT
	int iPlaceableCount = iCount;		

	//////////////////////////////////////////////////////////////////////////////////
	// DEBUG CHECKS
	//////////////////////////////////////////////////////////////////////////////////	
	
	if(iPlaceableCount < 1 || iItemCount == 0 || sItemResRef == "" || iItemCount > iPlaceableCount)
	{		
		SendMessageToPC(GetFirstPC(), "<<< A MISSING PARAMETER OR ERROR IN FUNCTION USAGE >>>"); 
		return;
	}
	
	//////////////////////////////////////////////////////////////////////////////////
	// NOW SHUFFLE THE CHEST COPIES STORED ON THEMSELVES AMONG THEMSELVES (COUNTED)
	// THIS KEEPS SWITCHING THE STORED COPIES AROUND FOR AS MANY COUNTED
	//////////////////////////////////////////////////////////////////////////////////	
	
	iCount = 0; 
	
	while(iCount < iPlaceableCount)
	{		
		// GET THE CHEST ACCORDING TO ITS LOCATION
		object oChestToUpdate = GetObjectByTag(sPlaceTag, iCount);
		object oChestToSwap = GetObjectByTag(sPlaceTag, Random(iPlaceableCount));
		
		// RETRIEVE ITS CURRENTLY ASSOCIATED CHEST
		object oFromUpdate = GetLocalObject(oChestToUpdate, sItemResRef);
		object oFromSwap = GetLocalObject(oChestToSwap, sItemResRef);		
		
		// AND SWAP THEM!
		SetLocalObject(oChestToUpdate, sItemResRef, oFromSwap);		
		SetLocalObject(oChestToSwap, sItemResRef, oFromUpdate);		
		
		iCount = iCount + 1;
	}
	
	//////////////////////////////////////////////////////////////////////////////////
	// NOW RETRIEVE THE SHUFFLED OBJECT ON PLACEABLE & CREATE ITEM ON IT
	//////////////////////////////////////////////////////////////////////////////////		
	
	iCount = 0; 
	
	while(iCount < iItemCount)
	{
		// JUST GET THE CHESTS
		object oChest = GetObjectByTag(sPlaceTag, iCount);
		
		// BUT RETRIEVE THE "OBJECT" CHEST TO USE FROM THEM
		oChest = GetLocalObject(oChest, sItemResRef);		
		CreateItemOnObject(sItemResRef, oChest, iStackSize, sNewTag);
		
		// DEBUG FEEDBACK
		//SendMessageToPC(GetFirstPC(), ">>> CREATING: " + sItemResRef + " ON " + GetTag(oChest));
		
		iCount = iCount + 1;		
	} 
}

void main()
{
    // WAIT UNTIL WE HAVE A VALID PC IN THE GAME FOR FEEDBACK (IN CASE CREATURE TRIGGERS)
	object oPC = GetFirstPC(); if(oPC == OBJECT_INVALID){return;}	
	
	// RETRIEVE THE VARIABLES WHICHEVER WAY YOU WANT TO
	// BUT HERE IS AN EXAMPLE WITH DATA PROVIDED	
	
	string sPlaceTag = "TestChest";				// ENSURE ALL PLACEABLES REQUIRED HAVE TAG "Chest"	
	int iItemCount = 4;							// NUMBER OF ITEMS ACROSS THE DROP
	string sItemResRef = "nw_it_mpotion021";	// THE ITEM RES REF TO BE DROPPED
	
	// MY TEST TRIGGER WITH ITS VARIABLES
	//object oTrigger = OBJECT_SELF;	
	
	// GATHER INFO FROM THE TRIGGER VARS
	//sPlaceTag = GetLocalString(oTrigger, "PLACETAG");
	//iItemCount = GetLocalInt(oTrigger, "QUANTITY");
	//sItemResRef = GetLocalString(oTrigger, "RR1");
	
	CreateItemsInPlaceable(sPlaceTag, sItemResRef, iItemCount);
}

SCRIPT UPDATED

Here is an improved version (supporting multiple items), which I have also posted afresh below …

//////////////////////////////////////////////////////////////////////////////////////////////
// FUNCTION: ITEMS RANDOMLY PLACED IN PLACEABLES BY LANCE BOTELLE (BASED ON NWShacker APPROACH)
//////////////////////////////////////////////////////////////////////////////////////////////
// EXAMPLE USAGE: PLACE THIS SCRIPT ON A TRIGGER WITH VARIABLE STRUCTURE AS FOLLOWS:-
// 1) A STRING VARIABLE CALLED "PLACETAG" WITH THE TAG OF THE PLACEABLE BEING USED FOR RANDOM FIND.
// 2) AN INTEGER VARIABLE CALLED "QUANTITY" WITH THE TOTAL NUMBER OF ITEMS TO FIND IN THE SET = X.
// 3) STRING VARIABLES IN FORMAT "RR1" CONTAINING RESREF OF OBJECT(S) TO CREATE. (OPTIONAL NEXT)
// 3a) Multiple items (up to 5 max) can be checked in a set if added via sRR2, sRR3, sRR4 and sRR5
// NB: If 3a is used, make sure iItemCount matches the number of different items to be created,
// OR item defined by sRR1 will be created in all remaining situations.
//////////////////////////////////////////////////////////////////////////////////////////////
void CreateItemsInPlaceable(string sPlaceTag, int iItemCount, string sRR1, string sRR2="", string sRR3="", string sRR4="", string sRR5="", int iStackSize=1, string sNewTag="");
void CreateItemsInPlaceable(string sPlaceTag, int iItemCount, string sRR1, string sRR2="", string sRR3="", string sRR4="", string sRR5="", int iStackSize=1, string sNewTag="")
{
	//////////////////////////////////////////////////////////////////////////////////
	// COUNT PLACEABLES USED & STORE TEMPORARY VERSION OF SELF ON SELF (TO ALLOW SWAP)
	//////////////////////////////////////////////////////////////////////////////////
	
	string sVariable = "PlaceObject";
		
	// INITIALIZE	
	int iCount = 0;
	object oPlaceable = GetObjectByTag(sPlaceTag, iCount);	
		
	while(oPlaceable != OBJECT_INVALID)
	{		
		SetLocalObject(oPlaceable, sVariable, oPlaceable);
		
		iCount = iCount + 1;
		oPlaceable = GetObjectByTag(sPlaceTag, iCount);
	}
	
	// THE PLACEABLE COUNT
	int iPlaceableCount = iCount;		

	//////////////////////////////////////////////////////////////////////////////////
	// DEBUG CHECKS
	//////////////////////////////////////////////////////////////////////////////////	
	
	if(iPlaceableCount < 1 || iItemCount == 0 || sRR1 == "" || iItemCount > iPlaceableCount)
	{		
		SendMessageToPC(GetFirstPC(), "<<< A MISSING PARAMETER OR ERROR IN FUNCTION USAGE >>>"); 
		return;
	}
	
	//////////////////////////////////////////////////////////////////////////////////
	// NOW SHUFFLE THE CHEST COPIES STORED ON THEMSELVES AMONG THEMSELVES (COUNTED)
	// THIS KEEPS SWITCHING THE STORED COPIES AROUND FOR AS MANY COUNTED
	//////////////////////////////////////////////////////////////////////////////////	
	
	iCount = 0; 
	
	while(iCount < iPlaceableCount)
	{		
		// GET THE CHEST ACCORDING TO ITS LOCATION
		object oChestToUpdate = GetObjectByTag(sPlaceTag, iCount);
		object oChestToSwap = GetObjectByTag(sPlaceTag, Random(iPlaceableCount));
		
		// RETRIEVE ITS CURRENTLY ASSOCIATED CHEST
		object oFromUpdate = GetLocalObject(oChestToUpdate, sVariable);
		object oFromSwap = GetLocalObject(oChestToSwap, sVariable);		
		
		// AND SWAP THEM!
		SetLocalObject(oChestToUpdate, sVariable, oFromSwap);		
		SetLocalObject(oChestToSwap, sVariable, oFromUpdate);		
		
		iCount = iCount + 1;
	}
	
	//////////////////////////////////////////////////////////////////////////////////
	// NOW RETRIEVE THE SHUFFLED OBJECT ON PLACEABLE & CREATE ITEM ON IT
	//////////////////////////////////////////////////////////////////////////////////		
	
	iCount = 0; 
	
	while(iCount < iItemCount)
	{
		// JUST GET THE CHESTS
		object oChest = GetObjectByTag(sPlaceTag, iCount);
		
		// BUT RETRIEVE THE "OBJECT" CHEST TO USE FROM THEM
		oChest = GetLocalObject(oChest, sVariable);
		
		// MULTIPLE ITEM INTERCEPT (CURRENTLY UP TO FIVE ITEMS POSSIBLE)
		if(iCount == 1 && sRR2 != ""){sRR1 = sRR2;}
		else if(iCount == 2 && sRR3 != ""){sRR1 = sRR3;}
		else if(iCount == 3 && sRR4 != ""){sRR1 = sRR4;}
		else if(iCount == 4 && sRR5 != ""){sRR1 = sRR5;}
		
		// NOW CREATE THE ITEM REQUIRED		
		CreateItemOnObject(sRR1, oChest, iStackSize, sNewTag);
		
		// DEBUG FEEDBACK
		//SendMessageToPC(GetFirstPC(), ">>> CREATING: " + sRR1 + " ON " + GetTag(oChest));
		
		iCount = iCount + 1;		
	} 
}

void main()
{
    // WAIT UNTIL WE HAVE A VALID PC IN THE GAME FOR FEEDBACK (IN CASE CREATURE TRIGGERS)
	object oPC = GetFirstPC(); if(oPC == OBJECT_INVALID){return;}	
	
	// RETRIEVE THE VARIABLES WHICHEVER WAY YOU WANT TO
	// BUT HERE IS AN EXAMPLE WITH DATA PROVIDED	
	
	string sPlaceTag = "TestChest";		// ENSURE ALL PLACEABLES REQUIRED HAVE TAG "Chest"	
	int iItemCount = 4;					// NUMBER OF ITEMS ACROSS THE DROP
	string sRR1 = "nw_it_mpotion021";	// THE ITEM RES REF TO BE DROPPED
	string sRR2 = "";
	string sRR3 = "";
	string sRR4 = "";
	string sRR5 = "";
	
	int iStack = 1;
	string sNewTag = "";
	
	// MY TEST TRIGGER WITH ITS VARIABLES
	/*object oTrigger = OBJECT_SELF;	
	
	// GATHER INFO FROM THE TRIGGER VARS (NEW STACK & TAG WOULD BE HERE TOO)
	sPlaceTag = GetLocalString(oTrigger, "PLACETAG");
	iItemCount = GetLocalInt(oTrigger, "QUANTITY");
	sRR1 = GetLocalString(oTrigger, "RR1"); 
	sRR2 = GetLocalString(oTrigger, "RR2");
	sRR3 = GetLocalString(oTrigger, "RR3");
	sRR4 = GetLocalString(oTrigger, "RR4");
	sRR5 = GetLocalString(oTrigger, "RR5");
	*/
	
	CreateItemsInPlaceable(sPlaceTag, iItemCount, sRR1, sRR2, sRR3, sRR4, sRR5, iStack, sNewTag);
}
2 Likes

Oh yes of course. I do apologize. I have had a very hectic couple of days and been practically been droning.

@Mmat Your system is totally doable and nothing wrong with yours! I tried it and it was good, though got a bit confused on the below script if it was na onclosed script or something. Though it does work so I do appreciate it.

@Lance_Botelle Honestly his script was (from my point of view) fairly straight forward and as I mentioned I had a very stressful day so I have limited time to go over everything at that particular moment. I do like your system although I was not too sure how I felt about the strings, integers thingies. And yes I agree I should have mentioned it but well my mind was scattered all over the place when I asked for help, again I apologize for that.

1 Like

@Dragonqueeny,

The only real difference (from a perspective of builder usage) is that my own script presumed you would like to vary the variables you enter the script and so I gave a trigger example. However, it’s up to you how you pass the variables … In fact, you NEED to know how to pass the variables for any script to work - My script gave you an immediate framework.

However, for your immediate task (without differing items), I think NWShacker is the best approach, which is why I provide you with another version similar in design, but with some idea of how to retrieve the variables you need to use the function.

Anyway, at least you have a few versions to play around with and learn from.

Take care, Lance.

1 Like

I appreciate it and thank you for the feedback and support! <3

1 Like

@Dragonqueeny

I just updated the latest script, as I somehow managed to miss off the function call line - It’s in there now.

Thanks, Lance.

1 Like

@Dragonqueeny,

OK, Here is my final version (for now at least), which does what you originally requested (same as the last script I did), plus it now also allows for X multiple items randomly dropped across Y placeable objects. You can even add variables to support stack size and new tag to the item created. (This is something I thought I would need, so include it in case you have a need.)

You would need to write your own scripts to clear the items down and refresh, but you should be able to do that from what I have done.

I will post it separately here, as you may like to see the differences …

Thanks, Lance.

//////////////////////////////////////////////////////////////////////////////////////////////
// FUNCTION: ITEMS RANDOMLY PLACED IN PLACEABLES BY LANCE BOTELLE (BASED ON NWShacker APPROACH)
//////////////////////////////////////////////////////////////////////////////////////////////
// EXAMPLE USAGE: PLACE THIS SCRIPT ON A TRIGGER WITH VARIABLE STRUCTURE AS FOLLOWS:-
// 1) A STRING VARIABLE CALLED "PLACETAG" WITH THE TAG OF THE PLACEABLE BEING USED FOR RANDOM FIND.
// 2) AN INTEGER VARIABLE CALLED "QUANTITY" WITH THE TOTAL NUMBER OF ITEMS TO FIND IN THE SET = X.
// 3) STRING VARIABLES IN FORMAT "RR1" CONTAINING RESREF OF OBJECT(S) TO CREATE. (OPTIONAL NEXT)
// 3a) Multiple items (up to 5 max) can be checked in a set if added via sRR2, sRR3, sRR4 and sRR5
// NB: If 3a is used, make sure iItemCount matches the number of different items to be created,
// OR item defined by sRR1 will be created in all remaining situations.
//////////////////////////////////////////////////////////////////////////////////////////////
void CreateItemsInPlaceable(string sPlaceTag, int iItemCount, string sRR1, string sRR2="", string sRR3="", string sRR4="", string sRR5="", int iStackSize=1, string sNewTag="");
void CreateItemsInPlaceable(string sPlaceTag, int iItemCount, string sRR1, string sRR2="", string sRR3="", string sRR4="", string sRR5="", int iStackSize=1, string sNewTag="")
{
	//////////////////////////////////////////////////////////////////////////////////
	// COUNT PLACEABLES USED & STORE TEMPORARY VERSION OF SELF ON SELF (TO ALLOW SWAP)
	//////////////////////////////////////////////////////////////////////////////////
	
	string sVariable = "PlaceObject";
		
	// INITIALIZE	
	int iCount = 0;
	object oPlaceable = GetObjectByTag(sPlaceTag, iCount);	
		
	while(oPlaceable != OBJECT_INVALID)
	{		
		SetLocalObject(oPlaceable, sVariable, oPlaceable);
		
		iCount = iCount + 1;
		oPlaceable = GetObjectByTag(sPlaceTag, iCount);
	}
	
	// THE PLACEABLE COUNT
	int iPlaceableCount = iCount;		

	//////////////////////////////////////////////////////////////////////////////////
	// DEBUG CHECKS
	//////////////////////////////////////////////////////////////////////////////////	
	
	if(iPlaceableCount < 1 || iItemCount == 0 || sRR1 == "" || iItemCount > iPlaceableCount)
	{		
		SendMessageToPC(GetFirstPC(), "<<< A MISSING PARAMETER OR ERROR IN FUNCTION USAGE >>>"); 
		return;
	}
	
	//////////////////////////////////////////////////////////////////////////////////
	// NOW SHUFFLE THE CHEST COPIES STORED ON THEMSELVES AMONG THEMSELVES (COUNTED)
	// THIS KEEPS SWITCHING THE STORED COPIES AROUND FOR AS MANY COUNTED
	//////////////////////////////////////////////////////////////////////////////////	
	
	iCount = 0; 
	
	while(iCount < iPlaceableCount)
	{		
		// GET THE CHEST ACCORDING TO ITS LOCATION
		object oChestToUpdate = GetObjectByTag(sPlaceTag, iCount);
		object oChestToSwap = GetObjectByTag(sPlaceTag, Random(iPlaceableCount));
		
		// RETRIEVE ITS CURRENTLY ASSOCIATED CHEST
		object oFromUpdate = GetLocalObject(oChestToUpdate, sVariable);
		object oFromSwap = GetLocalObject(oChestToSwap, sVariable);		
		
		// AND SWAP THEM!
		SetLocalObject(oChestToUpdate, sVariable, oFromSwap);		
		SetLocalObject(oChestToSwap, sVariable, oFromUpdate);		
		
		iCount = iCount + 1;
	}
	
	//////////////////////////////////////////////////////////////////////////////////
	// NOW RETRIEVE THE SHUFFLED OBJECT ON PLACEABLE & CREATE ITEM ON IT
	//////////////////////////////////////////////////////////////////////////////////		
	
	iCount = 0; 
	
	while(iCount < iItemCount)
	{
		// JUST GET THE CHESTS
		object oChest = GetObjectByTag(sPlaceTag, iCount);
		
		// BUT RETRIEVE THE "OBJECT" CHEST TO USE FROM THEM
		oChest = GetLocalObject(oChest, sVariable);
		
		// MULTIPLE ITEM INTERCEPT (CURRENTLY UP TO FIVE ITEMS POSSIBLE)
		if(iCount == 1 && sRR2 != ""){sRR1 = sRR2;}
		else if(iCount == 2 && sRR3 != ""){sRR1 = sRR3;}
		else if(iCount == 3 && sRR4 != ""){sRR1 = sRR4;}
		else if(iCount == 4 && sRR5 != ""){sRR1 = sRR5;}
		
		// NOW CREATE THE ITEM REQUIRED		
		CreateItemOnObject(sRR1, oChest, iStackSize, sNewTag);
		
		// DEBUG FEEDBACK
		//SendMessageToPC(GetFirstPC(), ">>> CREATING: " + sRR1+ " ON " + GetTag(oChest));
		
		iCount = iCount + 1;		
	} 
}

void main()
{
    // WAIT UNTIL WE HAVE A VALID PC IN THE GAME FOR FEEDBACK (IN CASE CREATURE TRIGGERS)
	object oPC = GetFirstPC(); if(oPC == OBJECT_INVALID){return;}	
	
	// RETRIEVE THE VARIABLES WHICHEVER WAY YOU WANT TO
	// BUT HERE IS AN EXAMPLE WITH DATA PROVIDED	
	
	string sPlaceTag = "TestChest";		// ENSURE ALL PLACEABLES REQUIRED HAVE TAG "Chest"	
	int iItemCount = 4;					// NUMBER OF ITEMS ACROSS THE DROP
	string sRR1 = "nw_it_mpotion021";	// THE ITEM RES REF TO BE DROPPED
	string sRR2 = "";
	string sRR3 = "";
	string sRR4 = "";
	string sRR5 = "";
	
	int iStack = 1;
	string sNewTag = "";
	
	// MY TEST TRIGGER WITH ITS VARIABLES
	/*object oTrigger = OBJECT_SELF;	
	
	// GATHER INFO FROM THE TRIGGER VARS (NEW STACK & TAG WOULD BE HERE TOO)
	sPlaceTag = GetLocalString(oTrigger, "PLACETAG");
	iItemCount = GetLocalInt(oTrigger, "QUANTITY");
	sRR1 = GetLocalString(oTrigger, "RR1"); 
	sRR2 = GetLocalString(oTrigger, "RR2");
	sRR3 = GetLocalString(oTrigger, "RR3");
	sRR4 = GetLocalString(oTrigger, "RR4");
	sRR5 = GetLocalString(oTrigger, "RR5");
	*/
	
	CreateItemsInPlaceable(sPlaceTag, iItemCount, sRR1, sRR2, sRR3, sRR4, sRR5, iStack, sNewTag);
}
1 Like

With this being just a game, it is not a big issue, but:

Careful - shuffling too much will actually make the generator worse, yielding an uneven (not uniform) random variable.

Value at each i-th position should be considered for swapping only once. Code difference seems negligible, but the consequences are actually significant.

This is what I used:

for i from 0 to n-1:
 j = i + Random(n-i)
 swap(i,j)

The spawn function could also be optimized further by shuffiing only first k containers (where k = number of items to spawn), but I’m leaving it as is since it might be useful in another context.

1 Like

@NWShacker

I did wonder if this was the case regarding the random factor and a way of preventing any potential going awry under such circumstances. (Otherwise this extra bit seemed a little strange to me.)

However, I did test the script I have a few times and it still appeared “random” to me - at least with the numbers we were using, so “I let it go” as well. :slight_smile: Although, I see how it might get worse with fewer placeables.

If I do notice less randomness than I hoped for, then I can review it. At the moment, however, it has certainly stayed reasonably random in the few times I tested it. :slight_smile: As it currently stands, I only swap a placeable once, but (I concur) that it can be the target of multiple changes … which I realise is the point of the wiki bias quote you refer to.

Thanks for the quote and explanation.
Lance.

This is a good explanation (with pics):

Merry Christmas,
-NWSH

5 Likes

Hi Lance,

Thanks for your feedback

I’d like to tell you why I did what I did:

  1. Right. Highlighting might make debugging a bit easier (Depending on the area). And it’s Christmas, so a bright white light from above is nice. :slight_smile:
  2. Giving the containers an indiviual tag allows to grab a distinct one without cycling through the objects.
  3. A more general solution was not in the scope of the request.
  4. I don’t understand what you mean with “location (int) clash”
int i4=d10(); { while (i4==i1 || i4==i2 || i4==i3) i4=d10(); PlaceItem (i4, 4); }

Please note that the while-loop here ends at the second i4=d10() statement. the PlaceItem in this line is independent from the loop.
Have a nice day!

The second script was just a cleanup I used during testing. Probably there are a few lines to add to tweak it to your needs.

But as I can see, you have more than one solution for your task, so don’t bother with it.

1 Like

Hi @Mmat,

I was just looking at an extreme case of rolling 1 - 4 (or other duplicates in earlier loops) and if it may have any potential impact during the while loop.

I just prefer to avoid relying on a random roll to break out of it. :slightly_smiling_face: However, it is not likely to be an issue, but just a design pref really. In much the same way, my own preferance is to avoid relying on multiple tags … A single tag avoids the possibility of me messing the tags up during setup.

As I say, it’s all “good” - just some pointers on style or preferences, which is always subject to personal taste and overall requirements.

I even have moments of change in my own scripts, subject to feedback. For instance, I had change around with my own after seeing NWShacker script, experimenting with a different approach. And there is still room for change again, subject to my own requirements or preferences, including potential improved randomness.

I hope that helps. :slightly_smiling_face:
Lance

@NWShacker,

I checked out the site you linked to and still have a question to try to understand your own implementation of this “Fisher-Yates” piece of code, when we consider it within NWN script. Let me first try to explain how I currently understand how it should be implemented and what we have to date … starting with examples from the site …

// NAIVETE EXAMPLE FROM SITE
for (int i = 0; i < cards.Length; i++)
{
  int n = rand.Next(cards.Length);
  Swap(ref cards[i], ref cards[n]);
}

// FISHER YATES EXAMPLE FROM SITE
for (int i = cards.Length - 1; i > 0; i--)
{
  int n = rand.Next(i + 1);
  Swap(ref cards[i], ref cards[n]);
}

But, as I only really understand NWN script, here is how I think the two compare if written in NWN script, and please forgive me for switching to while loops which I find easier to concentrate on. I have presumed it makes no difference otherwise, but please correct me if I am wrong.

// NAIVETE EXAMPLE IN NWN SCRIPT
int iPlaceableCount = 10;
int iCount = 0; 
	
while(iCount < iPlaceableCount)
{		
	// NAIVETE
	int iRandom = Random(iPlaceableCount);	
	
	// SWAP CODE CONTINUES HERE
	
	iCount = iCount + 1;
}

// FISHER YATES EXAMPLE IN NWN
int iPlaceableCount = 10;
int iCount = 0; 
	
// LOOP WILL BE 0 - 8 WITH TEN
while(iCount < (iPlaceableCount-1))
{		
	// iRandom LOOP RANGE WITH TEN (10 DOWN TO 2)
	int iRandom = Random((iPlaceableCount+1) - iCount);	
		
	// TEN COUNT EXAMPLE (FISHER-YATES) FOR n:	
	// Random(10) //10 @ 0 (10-0 =10)
	// Random(9)  //9 @ 1  (10-1 =9)
	// Random(8)  //8 @ 2  (10-2 =8)
	// Random(7)  //7 @ 3  (10-3 =7)
	// Random(6)  //6 @ 4  (10-4 =6)
	// Random(5)  //5 @ 5  (10-5 =5)
	// Random(4)  //4 @ 6  (10-6 =4)
	// Random(3)  //3 @ 7  (10-7 =3)
	// Random(2)  //2 @ 8  (10-8 =2)
	
	// iRandom 1 NOT PASSED (TO FIT FISHER-YATES)
	// Random(1)  //1 @ 9  (NA)
	
	// SWAP CODE CONTINUES HERE				
	
	iCount = iCount + 1;
}

Therefore, my question is, why do you have the equivilent line of code, where you appear to add the iCount again?

// OR AS I UNDERSTAND YOUR CODE
int iRandom = iCount + Random(iPlaceableCount - iCount);	

As this appears to give the following comparable:-

// RESULTS:	
// 0 + Random(10)
// 1 + Random(9) 
// 2 + Random(8) 
// 3 + Random(7) 
// 4 + Random(6) 
// 5 + Random(5) 
// 6 + Random(4) 
// 7 + Random(3) 
// 8 + Random(2) 
// 9 + Random(1) 

So, I thought your code should be …

// shuffle container "array"
for(iContainer1 = 0; iContainer1 < (iContainers-1); iContainer1++)	// NOTE DIFF HERE
{
    iContainer2 = Random(iContainers - iContainer1);		// NOTE DIFF HERE
	
	// RESULTS:	
	// Random(10) //10 @ 0 (10-0 =10)
	// Random(9)  //9 @ 1  (10-1 =9)
	// Random(8)  //8 @ 2  (10-2 =8)
	// Random(7)  //7 @ 3  (10-3 =7)
	// Random(6)  //6 @ 4  (10-4 =6)
	// Random(5)  //5 @ 5  (10-5 =5)
	// Random(4)  //4 @ 6  (10-6 =4)
	// Random(3)  //3 @ 7  (10-7 =3)
	// Random(2)  //2 @ 8  (10-8 =2)
	
	// Random 1 NOT PASSED (TO FIT FISHER-YATES)
	// Random(1) 
	
    // SWAP CODE CONTINUES HERE
}

Thanks in advance.
Lance.

Hi All,

If I have understood correctly, then this would be the FISHER-YATES randomizing version of the script:-

NB: It still gives a random result regardless, so it just depends if I have interpreted the logic correctly as to whether it actually qualifies as FISHER-YATES version. (See post immediately above.)

//////////////////////////////////////////////////////////////////////////////////////////////
// FUNCTION: ITEMS RANDOMLY PLACED IN PLACEABLES BY LANCE BOTELLE (BASED ON NWShacker APPROACH)
//////////////////////////////////////////////////////////////////////////////////////////////
// EXAMPLE USAGE: PLACE THIS SCRIPT ON A TRIGGER WITH VARIABLE STRUCTURE AS FOLLOWS:-
// 1) A STRING VARIABLE CALLED "PLACETAG" WITH THE TAG OF THE PLACEABLE BEING USED FOR RANDOM FIND.
// 2) AN INTEGER VARIABLE CALLED "QUANTITY" WITH THE TOTAL NUMBER OF ITEMS TO FIND IN THE SET = X.
// 3) STRING VARIABLES IN FORMAT "RR1" CONTAINING RESREF OF OBJECT(S) TO CREATE. (OPTIONAL NEXT)
// 3a) Multiple items (up to 5 max) can be checked in a set if added via sRR2, sRR3, sRR4 and sRR5
// NB: If 3a is used, make sure iItemCount matches the number of different items to be created,
// OR item defined by sRR1 will be created in all remaining situations.
//////////////////////////////////////////////////////////////////////////////////////////////
void CreateItemsInPlaceable(string sPlaceTag, int iItemCount, string sRR1, string sRR2="", string sRR3="", string sRR4="", string sRR5="", int iStackSize=1, string sNewTag="");
void CreateItemsInPlaceable(string sPlaceTag, int iItemCount, string sRR1, string sRR2="", string sRR3="", string sRR4="", string sRR5="", int iStackSize=1, string sNewTag="")
{
	//////////////////////////////////////////////////////////////////////////////////
	// COUNT PLACEABLES USED & STORE TEMPORARY VERSION OF SELF ON SELF (TO ALLOW SWAP)
	//////////////////////////////////////////////////////////////////////////////////
	
	string sVariable = "PlaceObject";
		
	// INITIALIZE	
	int iCount = 0;
	object oPlaceable = GetObjectByTag(sPlaceTag, iCount);	
		
	while(oPlaceable != OBJECT_INVALID)
	{		
		SetLocalObject(oPlaceable, sVariable, oPlaceable);
		
		iCount = iCount + 1;
		oPlaceable = GetObjectByTag(sPlaceTag, iCount);
	}
	
	// THE PLACEABLE COUNT
	int iPlaceableCount = iCount;		

	//////////////////////////////////////////////////////////////////////////////////
	// DEBUG CHECKS
	//////////////////////////////////////////////////////////////////////////////////	
	
	if(iPlaceableCount < 1 || iItemCount == 0 || sRR1 == "" || iItemCount > iPlaceableCount)
	{		
		SendMessageToPC(GetFirstPC(), "<<< A MISSING PARAMETER OR ERROR IN FUNCTION USAGE >>>"); 
		return;
	}	
	
	//////////////////////////////////////////////////////////////////////////////////
	// NOW SHUFFLE THE CHEST COPIES STORED ON THEMSELVES AMONG THEMSELVES (COUNTED)
	// THIS KEEPS SWITCHING THE STORED COPIES AROUND FOR AS MANY COUNTED
	//////////////////////////////////////////////////////////////////////////////////	
		
	iCount = 0; 
	
	//while(iCount < iPlaceableCount) 	// NAIVETE
	while(iCount < (iPlaceableCount-1))	// FISHER-YATES
	{		
		// RANDOMIZE (VERSIONS)
		// int iRandom = Random(iPlaceableCount);					// NAIVETE		
		int iRandom = Random((iPlaceableCount+1) - iCount);			// FISHER-YATES
		
		//SendMessageToPC(GetFirstPC(), "RANDOM >>> " + IntToString(iRandom));
		
		// TEN COUNT EXAMPLE (FISHER-YATES) FOR n:	
		// Random(10) //10 @ 0 (10-0 =10)
		// Random(9)  //9 @ 1  (10-1 =9)
		// Random(8)  //8 @ 2  (10-2 =8)
		// Random(7)  //7 @ 3  (10-3 =7)
		// Random(6)  //6 @ 4  (10-4 =6)
		// Random(5)  //5 @ 5  (10-5 =5)
		// Random(4)  //4 @ 6  (10-6 =4)
		// Random(3)  //3 @ 7  (10-7 =3)
		// Random(2)  //2 @ 8  (10-8 =2)
		
		// iRandom 1 NOT PASSED (TO FIT FISHER-YATES)
		// Random(1)  //1 @ 9  (NA)
	
		// GET THE CHEST ACCORDING TO ITS LOCATION
		object oChestToUpdate = GetObjectByTag(sPlaceTag, iCount);
		object oChestToSwap = GetObjectByTag(sPlaceTag, iRandom);
		
		// RETRIEVE ITS CURRENTLY ASSOCIATED CHEST
		object oFromUpdate = GetLocalObject(oChestToUpdate, sVariable);
		object oFromSwap = GetLocalObject(oChestToSwap, sVariable);		
		
		// AND SWAP THEM!
		SetLocalObject(oChestToUpdate, sVariable, oFromSwap);		
		SetLocalObject(oChestToSwap, sVariable, oFromUpdate);		
		
		iCount = iCount + 1;
	}
	
	//////////////////////////////////////////////////////////////////////////////////
	// NOW RETRIEVE THE SHUFFLED OBJECT ON PLACEABLE & CREATE ITEM ON IT
	//////////////////////////////////////////////////////////////////////////////////		
	
	iCount = 0; 
	
	while(iCount < iItemCount)
	{
		// JUST GET THE CHESTS
		object oChest = GetObjectByTag(sPlaceTag, iCount);
		
		// BUT RETRIEVE THE "OBJECT" CHEST TO USE FROM THEM
		oChest = GetLocalObject(oChest, sVariable);		
		
		// MULTIPLE ITEM INTERCEPT (CURRENTLY UP TO FIVE ITEMS POSSIBLE)
		if(iCount == 1 && sRR2 != ""){sRR1 = sRR2;}
		else if(iCount == 2 && sRR3 != ""){sRR1 = sRR3;}
		else if(iCount == 3 && sRR4 != ""){sRR1 = sRR4;}
		else if(iCount == 4 && sRR5 != ""){sRR1 = sRR5;}
		
		// NOW CREATE THE ITEM REQUIRED		
		CreateItemOnObject(sRR1, oChest, iStackSize, sNewTag);
		
		// DEBUG FEEDBACK
		//SendMessageToPC(GetFirstPC(), ">>> CREATING: " + sItemResRef + " ON " + GetTag(oChest));
		//AssignCommand(oChest, SpeakString("SPAWNING ITEM!"));
		
		iCount = iCount + 1;		
	} 
}

void main()
{
    // WAIT UNTIL WE HAVE A VALID PC IN THE GAME FOR FEEDBACK (IN CASE CREATURE TRIGGERS)
	object oPC = GetFirstPC(); if(oPC == OBJECT_INVALID){return;}	
	
	// RETRIEVE THE VARIABLES WHICHEVER WAY YOU WANT TO
	// BUT HERE IS AN EXAMPLE WITH DATA PROVIDED	
	
	string sPlaceTag = "TestChest";		// ENSURE ALL PLACEABLES REQUIRED HAVE TAG "Chest"	
	int iItemCount = 4;					// NUMBER OF ITEMS ACROSS THE DROP
	string sRR1 = "nw_it_mpotion021";	// THE ITEM RES REF TO BE DROPPED
	string sRR2 = "";
	string sRR3 = "";
	string sRR4 = "";
	string sRR5 = "";
	
	int iStack = 1;
	string sNewTag = "";
	
	// MY TEST TRIGGER WITH ITS VARIABLES
	/*object oTrigger = OBJECT_SELF;	
	
	// GATHER INFO FROM THE TRIGGER VARS (NEW STACK & TAG WOULD BE HERE TOO)
	sPlaceTag = GetLocalString(oTrigger, "PLACETAG");
	iItemCount = GetLocalInt(oTrigger, "QUANTITY");
	sRR1 = GetLocalString(oTrigger, "RR1"); 
	sRR2 = GetLocalString(oTrigger, "RR2");
	sRR3 = GetLocalString(oTrigger, "RR3");
	sRR4 = GetLocalString(oTrigger, "RR4");
	sRR5 = GetLocalString(oTrigger, "RR5");
	*/
	
	CreateItemsInPlaceable(sPlaceTag, iItemCount, sRR1, sRR2, sRR3, sRR4, sRR5, iStack, sNewTag);
}