Aller au contenu

Photo

How To "Wildcard" a String? (Efficient Beggar Script Development WIP)


  • Veuillez vous connecter pour répondre
61 réponses à ce sujet

#26
Morbane

Morbane
  • Members
  • 1 883 messages
#include "x0_i0_position"

void main()
{
    object oPC = GetModuleItemLostBy();
	object oItem = GetModuleItemLost();
    object oActualPossessor = GetItemPossessor(oItem);
	object oBeggar = GetNearestObjectByTag("the_grey_man", oItem, 1);
	location lBegSpawn = GetRandomLocation(GetAreaFromLocation(GetLocation(oPC)), oItem, IntToFloat(d20(1)));

	if(oBeggar == OBJECT_INVALID)
	{
		 CreateObject(OBJECT_TYPE_CREATURE, "the_grey_man", lBegSpawn, TRUE);
		 SendMessageToPC(GetFirstPC(), "beggar created");
	}
	else if(GetIsObjectValid(oBeggar))
	{
		SendMessageToPC(GetFirstPC(), "beggar activated");
		DelayCommand(0.5f, AssignCommand(oBeggar, ActionJumpToLocation(lBegSpawn)));
		if(oActualPossessor == OBJECT_INVALID)
		{
			SendMessageToPC(GetFirstPC(), "item dropped");
			object oBeggar = GetNearestObjectByTag("the_grey_man", oPC);
			if (GetIsObjectValid(oBeggar))
			{
				SendMessageToPC(GetFirstPC(), "beggar valid");
				AssignCommand(oBeggar, ClearAllActions(TRUE));
				AssignCommand(oBeggar, ActionPickUpItem(oItem));
				return;
			}
		}
	}
    if(oActualPossessor == OBJECT_INVALID)
    {
		SendMessageToPC(GetFirstPC(), "item dropped");
        object oBeggar = GetNearestObjectByTag("the_grey_man", oPC);
        if (GetIsObjectValid(oBeggar))
        {
			SendMessageToPC(GetFirstPC(), "beggar valid");
			AssignCommand(oBeggar, ClearAllActions(TRUE));
			AssignCommand(oBeggar, ActionPickUpItem(oItem));
            return;
        }
    }
}

Alrighty then

 

The above code works great, and no duplicate beggars

 

The remaining problem is this; how to copy the picked-up item(s) into the beggar store?

 

I can copy them into the 'store beggar's' inventory but accessing the inv as a store eludes me.

 

ec's super include did not produce any 'obvious' options - 

 

suggestions?

 

thanks :)

 

after any solution for the store - i intend to make the beggar run away and "exit" - leaving the store beggar remaining in his hidy-hole ;)



#27
Lance Botelle

Lance Botelle
  • Members
  • 1 480 messages

<CODE SNIPPED>

Alrighty then
 
The above code works great, and no duplicate beggars
 
The remaining problem is this; how to copy the picked-up item(s) into the beggar store?
 
I can copy them into the 'store beggar's' inventory but accessing the inv as a store eludes me.
 
ec's super include did not produce any 'obvious' options - 
 
suggestions?
 
thanks :)
 
after any solution for the store - i intend to make the beggar run away and "exit" - leaving the store beggar remaining in his hidy-hole ;)


Hi Morbane,

Good to see you feeling better ... I hope.

Well, at the moment, the way the code is written, the item has been placed into the beggar's inventory. So, if the PC was to kill and loot the beggar's body, they would find all the items the beggar has picked up.

It really depends on how you want this to play out ... Will the beggar ever be able to be killed? If so, then you need to ensure a correct copy of all items are kept up to date on either the beggar themselves and/or their store object.

So, to answer your question, here are the 3 possibilites ...

1) Create item on both beggar and their store (if beggar can be killed).
2) Create item only on beggar if no store interaction. (Your current version.)
3) Create item on store object ONLY if beggar can NEVER be killed. (Probably the version you mean?)

If it is option 3 you need, then "ideally" you do not need to have the beggar pick up the item (to store within their own inventory), but to pick up (and actually destroy it so it is not found on the beggar if killed), and have the item created on the beggar's store object only.

To do this, you need to define the beggar's store as an object, which I think we already discussed earlier, and then copy said picked up item onto store object prior to destroying the original reference. Some pseudocode follows, but may help guide you ...
 
object oBeggarStore = GetObjectByTag("GreyManStore"); // This is the beggar's store object placed somewhere in module (anywhere).
object oCopyToStore = CopyItem(oItem, oBeggarStore, TRUE); // THIS KEEPS ORIGINAL VARIABLES INTACT
DestroyObject(oItem, 0.1, FALSE); // MAY NEED TO ALTER PLOT FLAG ETC - BUT NO DESTROY REF IS REQUIRED.
NB: You would then need to set up a conversation with the beggar that allows the PC to access the beggar's store. i.e. The same store object that the items have been copied to. "GreyManStore" in the above example.

Obviously, subject to you actual requirements will determine whether you destroy the copy on the beggar or not. However, please be aware of consistency with respect to items "bought" from beggar store. i.e. If a PC buys an item, then that item also needs to be destroyed from the beggars inventory in the event that the PC kills and loots them.

Do let me know how you get on ...

Lance.

#28
Morbane

Morbane
  • Members
  • 1 883 messages
#include "x0_i0_position"
#include "x0_i0_transport"
#include "ginc_actions"
void main()
{
    object oPC = GetModuleItemLostBy();
	object oItem = GetModuleItemLost();
    object oActualPossessor = GetItemPossessor(oItem);
	object oBeggar = GetNearestObjectByTag("the_grey_man", oItem, 1);
	location lBegSpawn = GetRandomLocation(GetAreaFromLocation(GetLocation(oPC)), oItem, IntToFloat(d20(1)));

	if(oBeggar == OBJECT_INVALID)
	{
		 CreateObject(OBJECT_TYPE_CREATURE, "the_grey_man", lBegSpawn, TRUE);
		 SendMessageToPC(GetFirstPC(), "beggar created");
		 if(oActualPossessor == OBJECT_INVALID)
	    {
			SendMessageToPC(GetFirstPC(), "item dropped");
	        object oBeggar = GetNearestObjectByTag("the_grey_man", oPC);
	        if (GetIsObjectValid(oBeggar))
	        {
				SendMessageToPC(GetFirstPC(), "beggar valid");
				AssignCommand(oBeggar, ClearAllActions(TRUE));
				AssignCommand(oBeggar, ActionPickUpItem(oItem));
	        }
			SendMessageToPC(GetFirstPC(), "all done");
			object oExit = GetNearestExit(oBeggar);
			AssignCommand(oBeggar, ActionForceMoveToObject(oExit, TRUE));
			if(GetIsOpen(oExit)) DestroyObject(oBeggar); //doesn't work!?
	    }
	}
	else if(GetIsObjectValid(oBeggar))
	{
		SendMessageToPC(GetFirstPC(), "beggar activated");
		DelayCommand(0.5f, AssignCommand(oBeggar, ActionJumpToLocation(lBegSpawn)));
		if(oActualPossessor == OBJECT_INVALID)
		{
			SendMessageToPC(GetFirstPC(), "item dropped");
			object oBeggar = GetNearestObjectByTag("the_grey_man", oPC);
			if (GetIsObjectValid(oBeggar))
			{
				SendMessageToPC(GetFirstPC(), "beggar valid");
				AssignCommand(oBeggar, ClearAllActions(TRUE));
				AssignCommand(oBeggar, ActionPickUpItem(oItem));
			}
			SendMessageToPC(GetFirstPC(), "all done");
			object oExit = GetNearestExit(oBeggar);
			AssignCommand(oBeggar, ActionForceMoveToObject(oExit, TRUE));
		}
	}
	object oBeggarStore = GetObjectByTag("shadow_beggar");
	object oCopyToStore = CopyItem(oItem, oBeggarStore, TRUE);
	DelayCommand(20.0f, DestroyObject(oItem, 0.1, FALSE));
}

Oh-Kay...

 

The beggar spawns - no dupes

 

Picks up item(s) within the given timeframe or relative time to nearest exit

 

BUT, i am having trouble making the spawned beggar destroy itself once reaching the nearest exit, and having copied the dropped item to the store - the store works even if the spawned beggar does not destroy itself...

 

i've tried several archaic functions to no avail...

 

any ideas?

 

thanks :)

 

 

edit: hmmm, i've made the beggar plot - mayhaps therein lies my dillema...:



#29
Morbane

Morbane
  • Members
  • 1 883 messages

addendum:

 

for a few times/tests the beggar was auto-opening the nearest exit - now he ain't doing it...

 

bass-ackwards logics >:(



#30
Lance Botelle

Lance Botelle
  • Members
  • 1 480 messages

<CODE SNIPPED>

Oh-Kay...
 
The beggar spawns - no dupes
 
Picks up item(s) within the given timeframe or relative time to nearest exit
 
BUT, i am having trouble making the spawned beggar destroy itself once reaching the nearest exit, and having copied the dropped item to the store - the store works even if the spawned beggar does not destroy itself...
 
i've tried several archaic functions to no avail...
 
any ideas?
 
thanks :)
 
 
edit: hmmm, i've made the beggar plot - mayhaps therein lies my dillema...:


Hi Morbane,

Plot beggar is (as you edited) possibly your problem. NOTE ALSO: I don't see why you need such a huge delay added to this line:-

DelayCommand(20.0f, DestroyObject(oItem, 0.1, FALSE));

Don't forget, that as far as the code is concerned, it matters not whether your item is yet picked up or not. However, if it is because you want the delay so that your beggar carries on with the item pick up, then it may be better to simply remove the Destroy part of the code to the moment after the beggar "acquires" the item in question. NOTE: This means a simple script in your beggar's OnAcquired hook to destroy any items they acquire. (A copy of the said item will have already been copied to the beggar's store earlier.)

Lance.

#31
Lance Botelle

Lance Botelle
  • Members
  • 1 480 messages

addendum:
 
for a few times/tests the beggar was auto-opening the nearest exit - now he ain't doing it...
 
bass-ackwards logics > :(


This is because this part of your code needs to come outside of the "else" requirement as it needs to apply to either the newly created or already created beggar:-

EDIT: Move these lines to just prior (or after as it does not matter) the lines of code that copy the item to the beggar's store.
 
SendMessageToPC(GetFirstPC(), "all done");
object oExit = GetNearestExit(oBeggar);
AssignCommand(oBeggar, ActionForceMoveToObject(oExit, TRUE));
Cheers,
Lance.

EDIT: Actually, I just noticed you repeat the code for both events, so that may not be it. I will take a look at "tidying" your code a little and post it for you again.

OK, This is untested, but it should work. Let me know how it tests for you ...
 
#include "x0_i0_position"
#include "x0_i0_transport"
#include "ginc_actions"

void main()
{
    object oPC = GetModuleItemLostBy();
    object oItem = GetModuleItemLost();
    object oActualPossessor = GetItemPossessor(oItem);
    object oBeggar = GetNearestObjectByTag("the_grey_man", oItem, 1);
    location lBegSpawn = GetRandomLocation(GetAreaFromLocation(GetLocation(oPC)), oItem, IntToFloat(d20(1)));
	
	// DEBUG MESSAGE
	SendMessageToPC(GetFirstPC(), "<<< PLAYER DROPPED AN ITEM >>>");

        // IF NO VALID BEGGAR FOUND, CREATE/DEFINE A VERSION TO WORK WITH    
	if(oBeggar == OBJECT_INVALID)
	{
		// IMPORTANT TO REDEFINE BEGGAR OBJECT HERE 
		oBeggar = CreateObject(OBJECT_TYPE_CREATURE, "the_grey_man", lBegSpawn, TRUE);
		 
		// DEBUG MESSAGE
		SendMessageToPC(GetFirstPC(), "<<< NO CURRENT BEGGAR - CREATE NEW ONE FOR THE TASK >>>");		
	}
	
	// NO INVALID CHECK REQUIRED (THEY MUST EXIST IF PREVIOUS CHECK IS FALSE)
	else
	{
		// DEBUG MESSAGE
		SendMessageToPC(GetFirstPC(), "<<< USING EXISTING BEGGAR >>>");
		
		// NO DELAY REQUIRED
		AssignCommand(oBeggar, ActionJumpToLocation(lBegSpawn));
	}
	
	// NOW DO STUFF WITH THE BEGGAR (THE CREATED OR FOUND ONE AS DEFINED ABOVE)
	// NOTE: A VALID BEGGAR MUST BE FOUND BY NOW (ILLOGICAL NOT TO BE)
	
	if(oActualPossessor == OBJECT_INVALID)
	{		
		AssignCommand(oBeggar, ClearAllActions(TRUE));
		AssignCommand(oBeggar, ActionPickUpItem(oItem));		
	}
	
	// MAKE A COPY IN THE BEGGARS STORE
	object oBeggarStore = GetObjectByTag("shadow_beggar");
	object oCopyToStore = CopyItem(oItem, oBeggarStore, TRUE);
	
	// ADD DESTRUCTION OF ORIGINAL ITEM IN THE BEGGARS ONAQUIRE
	// DestroyObject(oItem, 0.1, FALSE);
	
	// DEBUG MESSAGE
	SendMessageToPC(GetFirstPC(), "<<< SCRIPT FINISHED >>>");
	
	object oExit = GetNearestExit(oBeggar);
	AssignCommand(oBeggar, ActionForceMoveToObject(oExit, TRUE));	
	
	// YOU ARE BETTER OFF ADDING THIS PART TO THE DOOR SCRIPT
	// IE. IF BEGGAR OPENS DOOR, THEN DESTROY THEM (FROM DOORS ONOPEN HOOK)
	// if(GetIsOpen(oExit)){DestroyObject(oBeggar);}
	
	// MAKE BEGGAR OPEN THE DOOR (DOOR SCRIPT WILL DESTROY BEGGAR WHEN THEY OPEN THE DOOR)
	AssignCommand(oBeggar, ActionOpenDoor(oExit));
}


#32
Morbane

Morbane
  • Members
  • 1 883 messages

Hi Lance,

 

thanks for the awesome input :)

 

however, through trial and error, all of that repetitive code has proven absolutely necessary, presumably due to the random nature of how it is intended to occur.

 

the 20.0f delay for instance - without it the item disappears with no beggar to connect its disappearance to

 

and insuring the beggar is valid has also proven to be necessary - in testing the conditional was not falling through without it

 

now, to be clear - i myself like to see streamlined code, such as less bulk filling every scope - but in testing again such repetition has proven a necessary evil

 

 

ultimately - i think the best thing that came from your interest, is using the inventory disturbed slot on the beggar - as of this reply, i have not tried it yet, but i feel confident that it will prove useful in the exit routine for any given area - as scripting every possible door in the module is not particularly viable this late in the design of 45 areas.

 

thanks again, and when i get the doors opening and the beggar despawning properly, i will post that snip.

 

:D



#33
Morbane

Morbane
  • Members
  • 1 883 messages
#include "x0_i0_position"
#include "x0_i0_transport"
#include "ginc_actions"
#include "ginc_misc"
void main()
{
    object oPC = GetModuleItemLostBy();
	object oItem = GetModuleItemLost();
    object oActualPossessor = GetItemPossessor(oItem);
	object oBeggar = GetNearestObjectByTag("the_grey_man", oItem, 1);
	location lBegSpawn = GetRandomLocation(GetAreaFromLocation(GetLocation(oPC)), oItem, IntToFloat(d20(1)));

	if(oBeggar == OBJECT_INVALID)
	{
		 CreateObject(OBJECT_TYPE_CREATURE, "the_grey_man", lBegSpawn, TRUE);
		 SendMessageToPC(GetFirstPC(), "beggar created");
		 if(oActualPossessor == OBJECT_INVALID)
	    {
			SendMessageToPC(GetFirstPC(), "item dropped");
	        object oBeggar = GetNearestObjectByTag("the_grey_man", oPC);
	        if (GetIsObjectValid(oBeggar))
	        {
				SendMessageToPC(GetFirstPC(), "beggar valid");
				AssignCommand(oBeggar, ClearAllActions(TRUE));
				AssignCommand(oBeggar, ActionPickUpItem(oItem));
	        }
			object oExit = GetNearestExit(oBeggar);
			object oBeggarStore = GetObjectByTag("shadow_beggar");
			object oCopyToStore = CopyItem(oItem, oBeggarStore, TRUE);
			ForceExit("the_grey_man", GetTag(oExit), TRUE);
	    }
	}
	else if(GetIsObjectValid(oBeggar))
	{
		SendMessageToPC(GetFirstPC(), "beggar activated");
		DelayCommand(0.5f, AssignCommand(oBeggar, ActionJumpToLocation(lBegSpawn)));
		if(oActualPossessor == OBJECT_INVALID)
		{
			SendMessageToPC(GetFirstPC(), "item dropped");
			object oBeggar = GetNearestObjectByTag("the_grey_man", oPC);
			if (GetIsObjectValid(oBeggar))
			{
				SendMessageToPC(GetFirstPC(), "beggar valid");
				AssignCommand(oBeggar, ClearAllActions(TRUE));
				AssignCommand(oBeggar, ActionPickUpItem(oItem));
			}
			object oExit = GetNearestExit(oBeggar);
			object oBeggarStore = GetObjectByTag("shadow_beggar");
			object oCopyToStore = CopyItem(oItem, oBeggarStore, TRUE);
			ForceExit("the_grey_man", GetTag(oExit), TRUE);
		}
	}
}

Oh Ya - oh ya!

 

this works

 

the only observable bug is if the walkpath is too complicated, oBeggar wont figure out how to get to the dropped item - this seems to occur about 20% of the time in areas with complicated walkmeshes - in areas with several walkpath options, oBeggar makes it to the dropped item 90% of the time (roughly).

 

now i have to make it work for the default beggar that carries the beggarstore - he has a different tag since that seemed to mess with GetNearest*** - even between areas...

 

cheers :D



#34
kevL

kevL
  • Members
  • 4 052 messages
not sure you're going to want to hear this, Morbane

of course go with what you want, I'm just going to bring up some issues i encountered and ideas.


Issues:
a) if the PC drops more than one item, can lead to a problem.
B) if GetRandomLocation() returns a location outside the walkable area, no beggar spawn.
- so i put CalcSafeLocation() in and that seems to clear things up.
c) I wasn't getting good results with GetExit(), so I went with GetNearestObject(oDoor)
- the timeout for ActionForceMoveToObject() was kicking in
- the timeout was also kicking in for Doors that can be opened, but ironically *not* for doors that are static.
- so i decreased the timeout to 7.5 seconds.
d) if the beggar hangs on something (say the PC tries to talk to it enroute), the whole thing hangs.
- so I lined all actions up for the beggar with ActionDoCommand(), then SetCommandable(FALSE)
- note that SetCommandable() has to be done via AssignCommand() or else it executes before the other AssignCommands and therefore prevents any actions from queueing.

I agree with Lance, that significant parts of your code are redundant. But if it works good for you it works good for me (i can just zap the critter with my insta-kill..)
 
// 'morbs_beggar'
// 2014 mar 24, fixed localObjects.
// 2014 mar 25, fixed CopyAndStoreItem(), stop retaining bPlot var during iterations.

// see GetRandomLocation() in 'x0_i0_position' condensed w/ CalcSafeLocation()
//        oItem: denotes the Area
//        fDist: distance from oItem
//        oActor: size of walkable space needed
location GetRandomSafeLocation(object oItem, float fDist, object oActor)
{
    //SendMessageToPC(GetFirstPC(FALSE), "run GetRandomSafeLocation()");
    object oArea = GetArea(oItem);
    float fFace = IntToFloat(Random(360));

    location lItem = GetLocation(oItem);
    vector vItem = GetPositionFromLocation(lItem);

    vector vOut = Vector(0.f, 0.f, vItem.z);

    float fDir = 0.f;
    int i = 7;
    do
    {
        //SendMessageToPC(GetFirstPC(FALSE), ". iterate");

        // I want to do this because otherwise CalcSafeLocation() tends to
        // cramp out-of-bounds spawns into a corner ... So, make several
        // attempts to get a 'true' random location first.

        fDir = IntToFloat(Random(360));

        vOut.x = vItem.x + fDist * cos(fDir);
        vOut.y = vItem.y + fDist * sin(fDir);

        if (vOut.x < 0.f) vOut.x = -vOut.x;
        if (vOut.y < 0.f) vOut.y = -vOut.y;

        lItem = Location(oArea, vOut, fFace);

        --i;
    }
    while (!GetIsLocationValid(lItem) && i > -1);

    //SendMessageToPC(GetFirstPC(FALSE), "EXIT GetRandomSafeLocation()");

    // This ought ensure that a path from the input position to the output
    // location is valid.
    return CalcSafeLocation(oActor, lItem, 50.f, TRUE, FALSE);
}

// Copies individual item to store then destroys it.
void CopyAndDestroyItem(object oItem, object oStore)
{
    //SendMessageToPC(GetFirstPC(FALSE), "run CopyAndDestroyItem()");
    int bPlot = FALSE;
    if (GetPlotFlag(oItem))
    {
        bPlot = TRUE;
        SetPlotFlag(oItem, FALSE);
    }

    object oCopy = CopyItem(oItem, oStore, TRUE);
    DestroyObject(oItem);

    if (bPlot)
        SetPlotFlag(oCopy, TRUE);
}

// Copies bag contents to store then destroys source items.
void CopyAndStoreItem(object oItem, object oStore)
{
    //SendMessageToPC(GetFirstPC(FALSE), "run CopyAndStoreItem()");
    if (GetIsObjectValid(oItem)
        && GetIsObjectValid(oStore))
    {
        //SendMessageToPC(GetFirstPC(FALSE), ". . item & store VALID");
        object oCopy = OBJECT_INVALID;
        int bPlot = FALSE;

        object oContent = GetFirstItemInInventory(oItem);
        while (GetIsObjectValid(oContent))
        {
            //SendMessageToPC(GetFirstPC(FALSE), ". . . is in Bag: iterate contents");
            if (GetPlotFlag(oContent))
            {
                bPlot = TRUE;
                SetPlotFlag(oContent, FALSE);
            }
            else
                bPlot = FALSE;

            oCopy = CopyItem(oContent, oStore, TRUE);
            DestroyObject(oContent);
            if (bPlot)
                SetPlotFlag(oCopy, TRUE);

            oContent = GetNextItemInInventory(oItem);
        }

        //SendMessageToPC(GetFirstPC(FALSE), ". . call Copy & Destroy");
        AssignCommand(GetModule(), DelayCommand(0.1f, CopyAndDestroyItem(oItem, oStore)));
    }
}

// Checks localObject variables on Module.
int GetIsContent(object oItem)
{
    //SendMessageToPC(GetFirstPC(FALSE), "run GetIsContent()");
    int i = 0;
    string sVar = "DroppedContent";
    object oContent = GetLocalObject(OBJECT_SELF, sVar + IntToString(++i));
    while (GetIsObjectValid(oContent))
    {
        //SendMessageToPC(GetFirstPC(FALSE), ". content " + IntToString(i) + " : " + GetName(oContent));
        if (oContent == oItem)
        {
            //SendMessageToPC(GetFirstPC(FALSE), ". . return TRUE");
            return TRUE;
        }

        oContent = GetLocalObject(OBJECT_SELF, sVar + IntToString(++i));
    }

    //SendMessageToPC(GetFirstPC(FALSE), ". . return FALSE");
    return FALSE;
}

// Deletes localObject variables on Module.
void DeleteIsContent()
{
    //SendMessageToPC(GetFirstPC(FALSE), "run DeleteIsContent()");
    int i;
    int iVars = GetVariableCount(OBJECT_SELF);
    for (i = 0; i < iVars; ++i)
    {
        string sVar = GetVariableName(OBJECT_SELF, i);
        if (GetStringLeft(sVar, 14) == "DroppedContent")
        {
            DeleteLocalObject(OBJECT_SELF, sVar);

            --i;
        }
    }
}

// Make the beggar destucktible & destroy it.
// & close the door
void DestroyBeggar(object oBeggar, object oDoor)
{
    //SendMessageToPC(GetFirstPC(FALSE), "run DestroyBeggar()");
    SetPlotFlag(oBeggar, FALSE);
    SetImmortal(oBeggar, FALSE);
    AssignCommand(oBeggar, SetIsDestroyable(TRUE, FALSE));

    DestroyObject(oBeggar);

    if (GetIsObjectValid(oDoor)
        && GetIsOpen(oDoor))
    {
        AssignCommand(oDoor, DelayCommand(3.1f, ActionCloseDoor(oDoor)));
    }
    //SendMessageToPC(GetFirstPC(FALSE), "DestroyBeggar() EXIT");
}

// __________
// * MAIN ***
// ----------
void main()
{
    SendMessageToPC(GetFirstPC(FALSE), "\nRun ( morbs_beggar ) " + GetName(OBJECT_SELF));

    object oItem = GetModuleItemLost();
    if (!GetIsObjectValid(oItem)
        || GetIsObjectValid(GetItemPossessor(oItem)))
    {
        return;
    }

    if (GetIsContent(oItem)) // in a Bag. Items in bags are handled by the bag itself.
        return;

    // this is also used as a placeholder for the beggar in GetRandomSafeLocation()
    object oActor = GetModuleItemLostBy();

    // don't want disarm to trigger this. Really.
     if (GetIsInCombat(oActor))
        return;

    //SendMessageToPC(GetFirstPC(FALSE), ". dropped : " + GetName(oItem));

    DeleteIsContent(); // Clean up vars on Module, ready for another run
    DelayCommand(30.f, DeleteIsContent()); // keep the Vars clean. should do onExit area...

    // Checks if item dropped is a container w/ Content. If so, write the
    // contents as localObjects to the Module so they can be checked (see
    // GetIsContent()) and bypassed (above), since each one would otherwise
    // trigger this script. Items in bags are handled along with the bag --
    // via CopyAndStoreItem() (fortunately the engine always drops the
    // container first, so this actually works as intended).
    int i = 0;
    string sVar = "DroppedContent";
    object oContent = GetFirstItemInInventory(oItem);
    while (GetIsObjectValid(oContent))
    {
        SetLocalObject(OBJECT_SELF, sVar + IntToString(++i), oContent);
        //SendMessageToPC(GetFirstPC(FALSE), ". . setLocal " + IntToString(i));

        oContent = GetNextItemInInventory(oItem);
    }


    float fDist = IntToFloat(10 + d10());
    location lSpawn = GetRandomSafeLocation(oItem, fDist, oActor);

    object oBeggar = CreateObject(OBJECT_TYPE_CREATURE, "dexc_bombbunny", lSpawn, TRUE);
    if (GetIsObjectValid(oBeggar))
    {
        //SendMessageToPC(GetFirstPC(FALSE), ". bunny created");
        SetPlotFlag(oBeggar, TRUE);
        SetCreatureScriptsToSet(oBeggar, SCRIPTSET_NOAI);

        AssignCommand(oBeggar, ActionPickUpItem(oItem));

        object oDoor = GetNearestObject(OBJECT_TYPE_DOOR, oBeggar);
        AssignCommand(oBeggar, ActionForceMoveToObject(oDoor, TRUE, 2.35f, 7.65f));
        AssignCommand(oBeggar, ActionOpenDoor(oDoor));

        object oStore = GetObjectByTag("kg_st_flayer");
        AssignCommand(oBeggar, ActionDoCommand(CopyAndStoreItem(oItem, oStore)));

        AssignCommand(oBeggar, ActionDoCommand(DestroyBeggar(oBeggar, oDoor)));

        AssignCommand(oBeggar, SetCommandable(FALSE, oBeggar));
    }
}
ps. I was using dex's BombBunny for oBeggar - it was pretty funny dropping 4 items and watching them all buzz around! (it's a bunny on flames, which somehow i think Lance could appreciate :)

whether you find something interesting or push on with your own that's great,

Modifié par kevL, 25 mars 2014 - 08:42 .

  • Morbane aime ceci

#35
Lance Botelle

Lance Botelle
  • Members
  • 1 480 messages

Hi Lance,
 
thanks for the awesome input :)


Hi Morbane,

You are welcome. :)
 

however, through trial and error, all of that repetitive code has proven absolutely necessary, presumably due to the random nature of how it is intended to occur. the 20.0f delay for instance - without it the item disappears with no beggar to connect its disappearance to and insuring the beggar is valid has also proven to be necessary - in testing the conditional was not falling through without it


I am not sure why this should be the case, as once the beggar object is defined, then it should always work with whatever variable it ends up being. I suspect the problem you have lies elsewhere, but it should not be due to streamlined code.

I pointed out this would occur in an earlier post. However, the result should only be "cosmetic" regarding the object itself, as the code still knows what the item is. However, using a "location" for the beggar's movement position would be better than the item - and this is what I was alluding to.
 

now, to be clear - i myself like to see streamlined code, such as less bulk filling every scope - but in testing again such repetition has proven a necessary evil


See above comments. 
 

ultimately - i think the best thing that came from your interest, is using the inventory disturbed slot on the beggar - as of this reply, i have not tried it yet, but i feel confident that it will prove useful in the exit routine for any given area - as scripting every possible door in the module is not particularly viable this late in the design of 45 areas.


Actually, adding a script to doors is not very time consuming as you may think, especially when we can look at area objects and highlight in one go, but I recognise the use of the inventory disturbed hook as a useful alternative.
 

thanks again, and when i get the doors opening and the beggar despawning properly, i will post that snip.


No problem ... I look forward to that ...

I am also going to do a quick test of a script myself to see if I can see the problems you are referring to and I will also show you some other code that I would recommend, ref my other points about players dropping more than one item at a time.


Back in a bit,
Lance

#36
Lance Botelle

Lance Botelle
  • Members
  • 1 480 messages

Issues:
a) if the PC drops more than one item, can lead to a problem.
B) if GetRandomLocation() returns a location outside the walkable area, no beggar spawn.
- so i put CalcSafeLocation() in and that seems to clear things up.
c) I wasn't getting good results with GetExit(), so I went with GetNearestObject(oDoor)
- the timeout for ActionForceMoveToObject() was kicking in
- the timeout was also kicking in for Doors that can be opened, but ironically *not* for doors that are static.
- so i decreased the timeout to 7.5 seconds.
d) if the beggar hangs on something (say the PC tries to talk to it enroute), the whole thing hangs.
- so I lined all actions up for the beggar with ActionDoCommand(), then SetCommandable(FALSE)
- note that SetCommandable() has to be done via AssignCommand() or else it executes before the other AssignCommands and therefore prevents any actions from queueing.

I agree with Lance, that significant parts of your code are redundant. But if it works good for you it works good for me (i can just zap the critter with my insta-kill..)

<CODE SNIPPED>
 

ps. I was using dex's BombBunny for oBeggar - it was pretty funny dropping 4 items and watching them all buzz around! (it's a bunny on flames, which somehow i think Lance could appreciate :)

whether you find something interesting or push on with your own that's great,


Hi KevL,

I must admit, I always foresaw a problem with the player's PC dropping more than one item and advised Morbane to consider that in an earlier post as well. :) So, certainly, yes, I know exactly what you mean. :lol:

I also see the errors that could kick in if the beggar was interrupted ... and I tried to avoid addressing the issues that raised at this point, but would have looked at the same approaches as yourself. However, now that this thing has progressed, it feels like it is under my skin and I am going to look at the whole thing now ... doh! (As long as it does not get too out of hand.)

EDIT:

I would also consider things such as:-

1) Did the PC drop the item onto another creature/placeable.
2) Was it a bag containing items.

EDIT 2:

Some of the extra checks involved are starting to impinge on how a builder has set up their module. I am going to have to make some assumptions, like NEVER allowing items to be directly placed into a module at build time, which is the correct way to do it anyway, so that I can have the beggar continue to look for extra items after picking up the first.

EDIT 3:

Found out what GetNearestExit actually does ... I have not used that, so needed to determine if this is what we needed. It is not exactly what we need, and so I will make my own function to do what Morbane was after here.

Lance.

#37
Morbane

Morbane
  • Members
  • 1 883 messages

Thanks guys - but did you test my code? even once? 'cause it dont make multiple beggars - it makes the current spawn turn around and get the second (etc) dropped item - ok maybe i havent even tested my own code thoroughly enough since i cannot say what happens if the PC drops 5 items successively.

 

but with kevL's improved spawn code - daaam - good stuff ;)

 

also going to any door - no, not working 

 

and when he gets to the caged dire bear (in the area im testing in) he just stands there with his clones :P

 

but - hey - how else to get this little idea snapping to the tune, eh?

 

this is the best part of building imo - forumite-ing and coding - cant say much of the code i have that does work that no one has seen is very elegant but, hey - same project for over 3 years :P



#38
Morbane

Morbane
  • Members
  • 1 883 messages

Hi KevL,

I must admit, I always foresaw a problem with the player's PC dropping more than one item and advised Morbane to consider that in an earlier post as well. :) So, certainly, yes, I know exactly what you mean. :lol:

I also see the errors that could kick in if the beggar was interrupted ... and I tried to avoid addressing the issues that raised at this point, but would have looked at the same approaches as yourself. However, now that this thing has progressed, it feels like it is under my skin and I am going to look at the whole thing now ... doh! (As long as it does not get too out of hand.)

EDIT:

I would also consider things such as:-

1) Did the PC drop the item onto another creature/placeable.
2) Was it a bag containing items.

EDIT 2:

Some of the extra checks involved are starting to impinge on how a builder has set up their module. I am going to have to make some assumptions, like NEVER allowing items to be directly placed into a module at build time, which is the correct way to do it anyway, so that I can have the beggar continue to look for extra items after picking up the first.

EDIT 3:

Found out what GetNearestExit actually does ... I have not used that, so needed to determine if this is what we needed. It is not exactly what we need, and so I will make my own function to do what Morbane was after here.

Lance.

 

those are excellent points - i cannot profess to say that i know how to address them at first glance - at all...

 

looking very forward to what comes next! :D



#39
Morbane

Morbane
  • Members
  • 1 883 messages

not sure you're going to want to hear this, Morbane

 

Of course I want to here it! 

 

mucho gracias amigo!!



#40
Morbane

Morbane
  • Members
  • 1 883 messages

also going to any door - no, not working 

 

and when he gets to the caged dire bear (in the area im testing in) he just stands there with his clones :P

 

bah! GetNearestExit() goes to the jail door too :(

 

"nearest" is therefore the keyword here not "exit"

 

edit: looks like some waypoints will be necessary after all - i was hoping to do this all in script - hmph.



#41
kevL

kevL
  • Members
  • 4 052 messages

yes, waypoints or specially tagged/loopable objects for Exit ... simple and robust.

I didn't try your code, because i could see problems with it, but i Certainly started with it.

if you get the beggar (1, one beggar) picking up multiple objects, and generally acting as 'trash fairy' kudos!

& yep Bags w/ items inside require special handling ...



#42
Lance Botelle

Lance Botelle
  • Members
  • 1 480 messages

Hi Morbane,

TRIED AND TESTED  :wizard:

LATEST VERSION v1.00

Fixes Applied:-

1) Made sure beggar hangs around until all items dropped in the area are picked up.
2) Made sure beggar moves onto next item if PC picks up item from ground before they get to it.
3) Made sure beggar leaves area after they have picked up all items in an area.
4) Made sure player can speak with beggar at any time to view items found, but does not stop beggar activity.

NOTE LINE 126 HAS THIS: -

//AssignCommand(oBeggar, SetCommandable(TRUE, oBeggar)); // OR SET BEGGAR TO CONTINUE HOME AT CONV END

If you allow this line to run (uncomment it), then the beggar "leaving" activity will be interrupted if the beggar is spoken to. The builder must then recall the "leaving" routine from the end of conversation instead. Alternatively, leave this line as it is and the beggar will continue to leave and be destroyed even if the player is looking at the beggar's inventory in view to buy something.


OK, this script works (tested). The beggar goes around collecting items they find until none are left, and then they wander off and eventually get jumped to a predefined "Waypoint" object. In my code, this exit waypoint has a tag: "WP_BeggarHome". This waypoint can be anywhere in your module, as the beggar will jump to it after a short time.

The script is worked in as an #include script, which makes its inclusion a lot simpler as a generic piece of code ...

NB: I learned that PLOT objects are dropped from shops when acquired, which can cause problems. So, I added the fix to this code as well.


INSTRUCTIONS TO IMPLEMENT & OTHER OBSERVATIONS
=================================================

1) Add the include line #include "alb_inc_beggar" at the top of your OnUnaquired script.
2) Add the function as a line in your OnUnaquired script where oPC is the dropper and oItem is the module lost item:

// BEGGAR TEST
ActivateBeggar(oPC, oItem);


3) This function has added safety checks, but may not have covered every possibility of errors.

4) Note, you can still talk to the beggar as they continue to pick up stuff and their "shop" reflects the updated items they are finding and collecting. This script also stops players from bags with items in it. Anyway, have a look at it. You obviously have to do your own conversation that starts the shop and add it to the beggar.

5) My beggar was a normal humanoid with a conv script to open their shop with tag: "alb_store_beggar", which has been placed anywhere in your module.

6) The script creates a beggar with a template resref of "alb_beggar". So, be sure to either prepare all the necessary objects in your module prior to running the script, or alter the script to use your own resrefs and tags.

7) Make sure you recompile your OnUnacquired script after adding the #include and function call lines.

8) Make sure you OnAcquired script does not fire for other creatures like the beggar itself.

Cheers,

Lance.
 

// SIMPLE BEGGAR COLLECTION SCRIPT CREATED FOR MORBANE BY LANCE BOTELLE
// HAS MINIMAL CHECKS, BUT DOES A SIMPLE JOB

#include "x0_i0_position"
#include "x0_i0_transport"

void DestroyBeggarCheck(object oBeggar)
{
	object oHomeWP = GetWaypointByTag("WP_BeggarHome");
	float fDis = GetDistanceBetween(oHomeWP, oBeggar);
	
	SetLocalInt(oBeggar, "LEAVING", 1);
	
	SendMessageToPC(GetFirstPC(FALSE), "BEGGAR DISTANCE FROM HOME >>> " + FloatToString(fDis));
	
	if(oBeggar != OBJECT_INVALID)
	{
		SendMessageToPC(GetFirstPC(FALSE), "BEGGAR IS VALID >>> ");
	}
	
	if(fDis < 1.0)
	{
		SendMessageToPC(GetFirstPC(FALSE), "<<< DESTROY THE BEGGAR >>> ");
		
		AssignCommand(oBeggar, SetIsDestroyable(TRUE));	
		DestroyObject(oBeggar, 1.0, FALSE);	
	}
		
	else{DelayCommand(3.0, DestroyBeggarCheck(oBeggar));}
}

int iItemSafetyCheck(object oPC, object oItem)
{	
	// DROPPED TO ANOTHER CREATURE
	if(GetObjectType(GetItemPossessor(oItem)) == OBJECT_TYPE_CREATURE)
	{
		SetNoticeText(oPC, "<<< STILL POSSESSED - NO ACTION REQUIRED >>>");		// DEBUG
		return 0;
	}	
	
	// CONTAINER WITH ITEMS
	if(GetHasInventory(oItem) && GetFirstItemInInventory(oItem) != OBJECT_INVALID)
	{
		AssignCommand(oPC, ClearAllActions(TRUE));
		CopyObject(oItem, GetLocation(oPC), oPC);
		SetScale(oItem, 0.01,0.01,0.01); DestroyObject(oItem);
		SetNoticeText(oPC, "<<< YOU CANNOT DROP BAGS THAT STILL CONTAIN ITEMS >>>");
		return 0;
	}
	
	return 1;
}

void CheckBeggarState(object oBeggar, object oCurrentItem)
{
	// IMMEDIATE PICK UP AGAIN CHECK
	object oOwner = GetItemPossessor(oCurrentItem);
	
	// BEGGAR HAS THIS CURRENT ITEM ... WHAT DO THEY DO NEXT? (OR PICKED UP AGAIN)
	if(GetObjectType(oOwner) == OBJECT_TYPE_CREATURE)
	{
		// BEGGAR IS THE OWNER
		if(GetItemPossessor(oCurrentItem) == oBeggar)
		{
			SendMessageToPC(GetFirstPC(FALSE), "BEGGAR OWNS >>> " + GetFirstName(oCurrentItem));	
						
			// MAKE A COPY IN THE BEGGARS STORE
			object oBeggarStore = GetObjectByTag("alb_store_beggar");
			
			// PLOT ITEMS WILL DROP AT A STORE LOCATION
			int iRESET = 0;
			
			if(GetPlotFlag(oCurrentItem))
			{
				SetPlotFlag(oCurrentItem, FALSE); iRESET = 1;
			}
			
			object oCopyToStore = CopyItem(oCurrentItem, oBeggarStore, TRUE);
			
			if(iRESET == 1){SetPlotFlag(oCopyToStore, TRUE);}	
                        
                        // DESTROY COPY BECAUSE IS NOW IN STORE
			SetPlotFlag(oCurrentItem, FALSE);
			DestroyObject(oCurrentItem, 0.0, FALSE);
		}
		
		// ARE THERE OTHER ITEMS TO PICK UP?
		int nCount = 1;
		object oNewItem = GetNearestObject(OBJECT_TYPE_ITEM, oBeggar, nCount);	
		
		SendMessageToPC(GetFirstPC(FALSE), "NEXT ITEM >>> " + GetFirstName(oNewItem));
		
		while(oNewItem != OBJECT_INVALID)
		{
			// DO NOT WANT OWNED ITEMS
			object oCreatureCheck = GetItemPossessor(oNewItem);
			
			if(GetObjectType(oCreatureCheck) == OBJECT_TYPE_CREATURE) 
			{
				nCount = nCount + 1; 
				oNewItem = GetNearestObject(OBJECT_TYPE_ITEM, oBeggar, nCount);
			}	
			
			else{break;}		
		}
		
		SendMessageToPC(GetFirstPC(FALSE), "FOUND >>> " + GetFirstName(oNewItem));
		
		SendMessageToPC(GetFirstPC(FALSE), "COMMANDABLE >>> " + IntToString(GetCommandable(oBeggar)));
		
		// DROPPED, SO PICK UP IF NOT ALREADY DOING SO
		if(oNewItem != OBJECT_INVALID && GetCommandable(oBeggar) == FALSE)
		{
			AssignCommand(oBeggar, SetCommandable(TRUE, oBeggar));
			AssignCommand(oBeggar, ClearAllActions(TRUE));
			AssignCommand(oBeggar, ActionPickUpItem(oNewItem));	
			AssignCommand(oBeggar, SetCommandable(FALSE, oBeggar));	
			
			// DO ANOTHER CHECK
			DelayCommand(3.0, CheckBeggarState(oBeggar, oNewItem));
		}
		
		// NOTHING ELSE TO PICK UP - MAKE BEGGAR TALKABLE & START TO EXIT
		else
		{
			//AssignCommand(oBeggar, SetCommandable(TRUE, oBeggar)); // OR SET BEGGAR TO CONTINUE HOME AT CONV END
			location lBeggarHome = GetLocation(GetWaypointByTag("WP_BeggarHome"));
			TravelToLocation(lBeggarHome, oBeggar, FALSE);
			
			if(GetLocalInt(oBeggar, "LEAVING") == 0)
			{
				DestroyBeggarCheck(oBeggar);			
			}
		}
	}
	
	// CHECK BEGGAR ACTIONS AGAIN IN A FEW SECONDS
	else
	{
		DelayCommand(3.0, CheckBeggarState(oBeggar, oCurrentItem));
	}
}

void ActivateBeggar(object oPC, object oCurrentItem)
{    
    // CHECK IF THIS IS A VALID DROP
	if(iItemSafetyCheck(oPC, oCurrentItem) == 0){return;}
	
	// GET/SPAWN THE BEGGAR
    object oBeggar = GetNearestObjectByTag("alb_beggar", oPC, 1); 	
	
	// DO NOT RUN ANOTHER BEGGAR AS ONE IS ALREADY ACTIVE HERE
	if(oBeggar != OBJECT_INVALID && !GetCommandable(oBeggar))
	{
		// DEBUG MESSAGE
		SetNoticeText(oPC, "<<< BEGGAR IS ALREADY ACTIVE & DOING THEIR BEST! >>>");
		return;	
	}
	
	// PREPARE A NEARBY LOCATION TO PC DROPPER
	object oArea = GetArea(oPC); float fRandomDistance = IntToFloat(d8(3));	
	location lBegSpawn = GetRandomLocation(oArea, oPC, fRandomDistance); 
	
	// DEBUG MESSAGE
	SendMessageToPC(oPC, "<<< PLAYER DROPPED A VALID ITEM >>>");

        // IF NO VALID BEGGAR FOUND, CREATE/DEFINE A VERSION TO WORK WITH    
	if(oBeggar == OBJECT_INVALID)
	{
		// IMPORTANT TO REDEFINE BEGGAR OBJECT HERE 
		oBeggar = CreateObject(OBJECT_TYPE_CREATURE, "alb_beggar", lBegSpawn, TRUE);
		 
		// DEBUG MESSAGE
		SetNoticeText(oPC, "<<< NO CURRENT BEGGAR - CREATE NEW ONE FOR THE TASK >>>");		
	}	
	
	// START BEGGAR ACTIONS - AND DONT INTERFERE (SIMPLEST SOLUTION FOR NOW)	
	DelayCommand(1.01, AssignCommand(oBeggar, ClearAllActions(TRUE)));
	DelayCommand(1.02, AssignCommand(oBeggar, ActionPickUpItem(oCurrentItem)));		
	DelayCommand(1.03, AssignCommand(oBeggar, SetCommandable(FALSE, oBeggar)));
	
	// CHECK/UPDATE BEGGAR TASK
	DelayCommand(3.0, CheckBeggarState(oBeggar, oCurrentItem));
}

  • Morbane aime ceci

#43
Morbane

Morbane
  • Members
  • 1 883 messages

Epic  :D



#44
kevL

kevL
  • Members
  • 4 052 messages

this is challenging :)


have updated my version of the script above. It still uses one beggar/bunny per item or Bag dropped. Seems to handle multiple drops as well as bags properly (incl. Plot, thanks Lance), also spawns in tight places (you might find the subfunctions interesting/useful...), and transferring the items into a store.

If i ever do the 'only 1 beggar' situation, would likely go with something analogous to a walkwaypoint controller, or perhaps just the beggar or Module's or Area heartbeat,


notes: didn't check if the Plot flag gets reset properly.
... of course my store has problems with its pricing, but i think that's just my hastily constructed store.

 

 

 

[edit] I don't think the localObjects on the Module are deleted properly, oops



#45
Morbane

Morbane
  • Members
  • 1 883 messages

cool!

 

will mess around after i earn my paycheck on this lovely monday morning.

 

cheers :D



#46
kevL

kevL
  • Members
  • 4 052 messages

Sunday afternoon

 

:P

 

 

Temp .sig

--

The Lexicon illustrates that while the syntax is very simple, the Body of Knowledge (stuff you just have to know) is considerable. The outcome of f(x) may depend on whether x is object class foo or bar, except on Wednesdays...

Proleric - http://forum.bioware...ons/?p=16269010



#47
Dann-J

Dann-J
  • Members
  • 3 161 messages

I decided to have a crack at this as well, and came up with a single creature HB script that causes it to collect any non-plot dropped item in that particular area, and then transfer it to a store or container. It keeps checking whether the item it's after has been picked up by someone else, and aborts if necessary.That way you could have multiple urchins, scavengers or cleaners in an area, and they all run to pick up the item first. Finally clockroaches can perform the task they were designed for!

void main()
{
object oSelf = OBJECT_SELF;
int i = 1;
object oItem = GetNearestObject(OBJECT_TYPE_ITEM, oSelf, i);
while (GetIsObjectValid(oItem)) 
	{ 
	if (GetPlotFlag(oItem) == FALSE) break;
	else  
		{// ignore plot 
		items  i++;  
		oItem = GetNearestObject(OBJECT_TYPE_ITEM, oSelf, i);
		} 
	}// end while

if (GetCurrentAction(oSelf) == ACTION_PICKUPITEM 
&& GetIsObjectValid(GetItemPossessor(GetLocalObject(oSelf, 
"Item"))) ) 
	{// Abort if item was picked up by another 
	ClearAllActions(FALSE); 
	DeleteLocalObject(oSelf, "Item"); 
	}

// Pick up an item if not already busy
if (GetCurrentAction(oSelf) != 
ACTION_PICKUPITEM && !IsInConversation(oSelf) 
&& !GetIsInCombat(oSelf) 
&& GetIsObjectValid(oItem) ) 
	{ 
	AssignCommand(oSelf, ActionPickUpItem(oItem)); 
	DeleteLocalObject(oSelf, "Item"); 
	SetLocalObject(oSelf, "Item", oItem); 
	return; 
	}

// Empty inventory to store or container
string sStore = GetLocalString(oSelf, "Store");
object oStore = GetObjectByTag(sStore);
object oInv = GetFirstItemInInventory(oSelf);
while (GetIsObjectValid(oInv)) 
	{ 
	if (oInv == GetLocalObject(oSelf, "Item"))
		DeleteLocalObject(oSelf, "Item"); 
	CopyItem(oInv, oStore, TRUE); 
	DestroyObject(oInv, 0.1); 
	oInv = GetNextItemInInventory(oSelf); 
	}

ExecuteScript("nw_c2_default1", oSelf);
}


#48
Tchos

Tchos
  • Members
  • 5 030 messages

Dann, you should use the nice new code blocks for that stuff now!



#49
Dann-J

Dann-J
  • Members
  • 3 161 messages

Dann, you should use the nice new code blocks for that stuff now!

 

Done - thanks for the tip. It certainly looks much prettier.



#50
Tchos

Tchos
  • Members
  • 5 030 messages

Looking good.  B)  And this whole idea is pretty cool, as well.