31 May 2018 Tagged: eulorac++

Ok, so I’m walking the earth, takin' names an makin' claims. There’s a rhythm to it. I get one scroll, I use one scroll. So long as nothing breaks my stride I’ve got no problems.

However, on occasion something comes up. Target selection is improved but it’s not perfect. Maybe I target a spent marker and try to build again. It fails and I move on, with naught but 15 seconds wasted and an extra scroll in my pack. Not a big deal really. Like swallowing a kernel of corn without chewing: you don’t even know it until you look at what comes out in the end. (And then, you know, you flush it, you don’t re-eat it!)

Except in this case that kernel of corn is a ticking time bomb that’s going to go off as soon as you try to build a different type of claim. Because as it turns out, that extra scroll (or scrolls) is equipped. When Foxybot tries to equip the new type of scroll it fails and Foxybot doesn’t realize it. It then tries to build the claim which succeeds in producing a bundle of the old type which you still have in mind, but fails to build the claim.

Now you’re going to have an accumulating abundance of bundles instead of the resources you’re actually after. That old scroll is stuck up in there and it’s no joke. Lets have a look at the code.

src/client/foxybot/botactivity.cpp(610)
if (!worldHandler::IsInBrain(scrollName))
{
        if (!worldHandler::EquipRecipe(scrollName))   (1)
         {
                        OutputMsg(csString("Missing blueprint for the claim!"));
                        //noBuild = true;
                        //Error();
            return false;
         }
}
1 Location of equip attempt

This is part of ExploreActivity::DoBuild() and, in the case where the proper scroll is not already equipped, it’s calling worldHandler::EquipRecipe(). If these sounds familiar it could be because we’ve looked at them before. Now it’s time for another look. Here’s how we left EquipRecipe() last time:

src/client/foxybot/worldhandler.cpp(181)
bool worldHandler::EquipRecipe(csString itemName)
{
	psInventoryCache::CachedItemDescription* slot = FindItemSlot(itemName, true);
	if (slot == NULL)
		return false;

	//if (slot->containerID != CONTAINER_INVENTORY_EQUIPMENT) //equip only if truly needed
	{
		MoveItems(slot->containerID, slot->slot, CONTAINER_INVENTORY_EQUIPMENT, PSCHARACTER_SLOT_MIND, slot->stackCount);
	}

	return true;
}

If you take a close look at that code listing, you’ll see that the only case where it returns false is when the a source slot containing the needed scroll is not found. After that it returns true after the move attempt regardless of success. This explains why Foxybot doesn’t notice the failure.

Well, we know from the prior listing that this code is only called when the proper scroll is not already equipped. So lets add a check to see if there is anything in there and, if so, remove it.

src/client/foxybot/worldhandler.cpp(181)
bool worldHandler::EquipRecipe(csString itemName)
{
+      	if (GetBrainItemName() != "") (1)
+	{
+		Dequip(GetBrainItemName()); (2)
+	}
+
	psInventoryCache::CachedItemDescription* slot = FindItemSlot(itemName, true);
	if (slot == NULL)
		return false;

	//if (slot->containerID != CONTAINER_INVENTORY_EQUIPMENT) //equip only if truly needed
	{
		MoveItems(slot->containerID, slot->slot, CONTAINER_INVENTORY_EQUIPMENT, PSCHARACTER_SLOT_MIND, slot->stackCount);
	}

	return true;
}
1 If she’s cramping your style
2 Show her the door

What’s meant to happen now is equipped scrolls get removed before attempting to equip a new kind. However, if you run this, you’ll actually find that nothing appears to happen differently at all. Further investigation shows that eventually, down the call chain, InventoryWindow::Dequip gets called to grunt through each inventory slot in search of a match.

src/client/gui/inventorywindow.cpp(324)
pawsSlot* fromSlot = NULL;
// See if we can find the item in the equipment slots.
for ( size_t z = 0; z < equipmentSlots.GetSize(); z++ )
{
    if ( equipmentSlots[z] && !equipmentSlots[z]->IsEmpty() )
    {
        csString tip(equipmentSlots[z]->GetToolTip()); (1)
        if ( tip.CompareNoCase(itemName) )
        {
            fromSlot = equipmentSlots[z];
            break;
        }
    }
}
1 Enter: the tip

So what’s that doing? It’s looping through all the equipment slots and if there is something in there, it’s comparing the tool tip of that thing to the name of the item you’re trying to dequip. In our case it’s never finding a match and so Dequip() never finds what to dequip.

Funny thing, that tool tip seems to come with its very own leading space character. So the values are never equal because of that extra space on the front. Let’s give that tip a snip.

src/client/gui/inventorywindow.cpp(324)
pawsSlot* fromSlot = NULL;
// See if we can find the item in the equipment slots.
for ( size_t z = 0; z < equipmentSlots.GetSize(); z++ )
{
    if ( equipmentSlots[z] && !equipmentSlots[z]->IsEmpty() )
    {
-       csString tip(equipmentSlots[z]->GetToolTip());
+       csString tip(equipmentSlots[z]->GetToolTip().trim());
        if ( tip.CompareNoCase(itemName) )
        {
            fromSlot = equipmentSlots[z];
            break;
        }
    }
}

Bang! There you go. Improper scrolls are now removed as needed; fresh, crisp scrolls are equipped in their place; un-chewed corn passes without incident, and you no longer have to walk around with old ideas stuck in your head.

You’re welcome.

Diana Coman - 2018-05-31 20:38:23

Nice catch that extra space on the tooltip, I had no idea!

(Initially a move to an occupied slot would just swap but like pretty much everything else in Eulora, it…​ changed.)

Mocky - 2018-06-01 04:02:31

Yeah, it’s like an archeology dig into Eulora’s past, in the sense of inferring how this used to work from looking at the code.


Add a Comment