Bug - Fixed Travoltan Trousers not used when automatically buying items for mood

Ryo_Sangnoir

Developer
Staff member
My mood contains Wasabi Sinuses, which is obtainable from Knob Goblin nasal spray from the Dispensary. If I type "acquire nasal spray", it equips my trousers before buying the item. If I don't have any in inventory and run out of the effect, it buys the item without equipping the trousers.

A workaround on my end is just to buy the items before I start farming (saving a whole 7 meat per), but this implies that mood item acquisition is different to normal item acquisition, so there might be other strange differences here.
 

Veracity

Developer
Staff member
Mood triggers that depend on an item simple post a UseItemRequest.
UseItemRequest calls InventoryManager.retrieveItem to get the nasal spray.
That is the same method used by "acquire".

Travoltan trousers are automatically equipped in NPCPurchaseRequest.ensureProperAttire. This will make sure that if an NPC store requires a particular outfit - the Hippy Store, for example - you are wearing it. At the very end of that:

Code:
		// Otherwise, maybe you can put on some Travoltan Trousers to decrease the cost of the
		// purchase, but only if auto-recovery isn't running.

		if ( !NPCPurchaseRequest.usingTrousers() && KoLConstants.inventory.contains( NPCPurchaseRequest.TROUSERS ) )
		{
			( new EquipmentRequest( NPCPurchaseRequest.TROUSERS, EquipmentManager.PANTS ) ).run();
		}
So, what's this "auto-recovery isn't running" thing? Earlier:

Code:
		// If the recovery manager is running, do not change equipment as this has the potential
		// for an infinite loop.

		if ( RecoveryManager.isRecoveryActive() )
		{
			if ( neededOutfit != OutfitPool.NONE )
			{
				KoLmafia.updateDisplay(
					MafiaState.ERROR,
					"Aborting implicit outfit change due to potential infinite loop in auto-recovery. Please buy the necessary " + getItemName() + " manually." );

				return false;
			}

			return true;
		}
What is that about? Does it apply to moods? I think not. There are numerous places where we check "if ( RecoveryManager.isRecoveryActive() || MoodManager.isExecuting() )". This is not one of them.

I transfered my Travoltan trousers to a character who has the dispensary open.

Code:
[color=green]> acquire nasal spray[/color]

Putting on Travoltan trousers...
Equipment changed.
Purchasing Knob Goblin nasal spray (1 @ 142)...
You spent 142 Meat
You acquire an item: Knob Goblin nasal spray
Purchases complete.
Putting on paperclip pants...
Equipment changed.

[color=green]> closet put * nasal spray[/color]

Placing items into closet...
Requests complete.

[color=green]> mood execute[/color]

Putting on Travoltan trousers...
Equipment changed.
Purchasing Knob Goblin nasal spray (1 @ 142)...
You spent 142 Meat
You acquire an item: Knob Goblin nasal spray
Purchases complete.
Using 1 Knob Goblin nasal spray...
You acquire an effect: Wasabi Sinuses (10)
Finished using 1 Knob Goblin nasal spray.
Putting on paperclip pants...
Equipment changed.
Mood swing complete.
As you can see, the fact that the acquisition is happening as part of a mood does not prevent using the Travoltan trousers.

The key is that moods are executed as part of RecoveryManager.runBetweenBattleChecks - which explicitly sets and resets RecoveryManager.isRecoveryActive.

I wish I remembered why we disallow outfit changes if isRecoveryActive. It is "to avoid infinite loops". Guess: if you are recovering HP or MP to 100% and changing equipment will lower your max value of such, when we restore your equipment, we'll notice you are not at 100% and will try again. Given that, looking at recovery:

Code:
		RecoveryManager.recoveryActive = true;

		if ( isScriptCheck )
		{
			KoLmafia.executeScript( Preferences.getString( "betweenBattleScript" ) );
		}

		SpecialOutfit.createImplicitCheckpoint();

		// Now, run the built-in behavior to take care of
		// any loose ends.

		if ( isMoodCheck )
		{
			MoodManager.execute();
		}

		if ( isHealthCheck )
		{
			RecoveryManager.recoverHP();
		}

		if ( isMoodCheck )
		{
			ManaBurnManager.burnExtraMana( false );
		}

		if ( isManaCheck )
		{
			RecoveryManager.recoverMP();
		}

		SpecialOutfit.restoreImplicitCheckpoint();

		...

		RecoveryManager.recoveryActive = false;
Notice that it executes your mood before it recovers HP and MP.

Notice that it saves & restores your outfit before doing any of those things, which is why you can't change your gear in a mood, which people have asked about numerous times. It does that specifically to recover from automatic outfit switching, but as we've seen, that is disabled while recovering, any way.

Notice that a user-supplied betweenBattleScript script will run before any other "recovery" - and is outside of the outfit save/restore. Such a script can do any equipment switching it wants, but automatic outfit switching for NPC purchases will still be disabled, since it is doing "recovery".

A user-supplied recoveryScript will be called by recoverHP and recoverMP. That has the same "feature": if it wants to use knob goblin seltzer or something, no Travoltan discount - although it can do any equipment changes it wants. It's a script.

So, we now understand your observed behavior. It may or may not be a "bug" since it is behaving as intended. but if we wanted to "fix" it, we could do two things:

1) Change NPCPurchaseRequest as follows:

Code:
		if ( RecoveryManager.isRecoveryActive() && !MoodManager.isExecuting() )
which will allow equipment changes during moods.

2) Change RecoverManager as follows:

Code:
		// Now, run the built-in behavior to take care of
		// any loose ends.

		SpecialOutfit.createImplicitCheckpoint();

		if ( isMoodCheck )
		{
			MoodManager.execute();
		}

		SpecialOutfit.restoreImplicitCheckpoint();

		SpecialOutfit.createImplicitCheckpoint();
Which will save and restore your outfit around the mood execution, which will recover from said newly-allowed outfit switching, and will also save and restore your outfit around the rest of recovery, which will recover from Recovery Scripts. :)
 

Bale

Minion
I wish I remembered why we disallow outfit changes if isRecoveryActive. It is "to avoid infinite loops". Guess: if you are recovering HP or MP to 100% and changing equipment will lower your max value of such, when we restore your equipment, we'll notice you are not at 100% and will try again.

I remember the discussion. You are basically correct. That did happen. Another problem was that, in addition to loops, there was trouble for people whose maximum MP dropped below current MP. They'd come out of restoration with hundreds or thousands of MP lost to equipment changes.

I believe that is also when Jason added "whatif" so that I could check the speculative results of equipment change in my recovery script.

Code:
string whatif = "whatif";
foreach key in gear
	whatif = whatif + " equip "+ key+ " "+ gear[key]+ ";";
cli_execute(whatif+ " quiet");
int changed_hp = numeric_modifier("Generated:_spec", "Buffed HP Maximum");
int changed_mp = numeric_modifier("Generated:_spec", "Buffed MP Maximum");
 

ckb

Minion
Staff member
I wish I remembered why we disallow outfit changes if isRecoveryActive. It is "to avoid infinite loops". Guess: if you are recovering HP or MP to 100% and changing equipment will lower your max value of such, when we restore your equipment, we'll notice you are not at 100% and will try again.

I believe this was the intended goal... though it reminds me of an issue I ran into. This was when I was often trying to recover much MP and cast a lot of Fridigalmation.

I wore an outfit that gave lots of MP and -mana cost - this included hobo dungarees. I tried to restore 7,777 MP using
> ashq restore_mp(7777)

Mafia changed to Travoltan trousers (lowering my max MP) and bought Cloaca Cola, switched pants back, then used them all. This restored 7,684 Muscularity Points. Still needing a few more, Mafia went to buy more to get up to the requested 7,777 MP. It should have only needed a few.
However, Mafia switched pants to buy the remaining few Cloaca Cola. This lowered my max MP AND it lost the difference to the pants switch. It bought 2 more Cloaca Cola (with the discount), then switched the pants back, then used the 2 Cloaca Cola.
This should have resulted in getting to the requested 7,777 MP, but the previous pants switch also lost ~500 MP, so I was well below the requested restore value.
Mafia repeated this loop, trying to restore MP to get to the requested value while also switching pants, losing MP.
I noticed this eventually and shut it down.

Code:
equip pants Travoltan trousers

buy 962 Regular Cloaca Cola for 72 each from White Citadel
You spent 69,264 Meat
You acquire Regular Cloaca Cola (962)

equip pants hobo dungarees

use 962 Regular Cloaca Cola
You gain 7,684 Muscularity Points

equip pants Travoltan trousers

buy 2 Regular Cloaca Cola for 72 each from White Citadel
You spent 144 Meat
You acquire Regular Cloaca Cola (2)

equip pants hobo dungarees

use 2 Regular Cloaca Cola
You gain 16 Muscularity Points

equip pants Travoltan trousers

buy 58 Diet Cloaca Cola for 72 each from White Citadel
You spent 4,176 Meat
You acquire Diet Cloaca Cola (58)

equip pants hobo dungarees

use 58 Diet Cloaca Cola
You gain 463 Muscularity Points

equip pants Travoltan trousers

buy 3 Diet Cloaca Cola for 72 each from White Citadel
You spent 216 Meat
You acquire Diet Cloaca Cola (3)

equip pants hobo dungarees

use 3 Diet Cloaca Cola
You gain 21 Muscularity Points

equip pants Travoltan trousers

buy 46 black cherry soda for 72 each from The Black Market
You spent 3,312 Meat
You acquire black cherry soda (46)

equip pants hobo dungarees

use 46 black cherry soda
You gain 454 Muscularity Points

equip pants Travoltan trousers

buy 3 black cherry soda for 72 each from The Black Market
You spent 216 Meat
You acquire black cherry soda (3)

equip pants hobo dungarees

use 3 black cherry soda
You gain 30 Muscularity Points

equip pants Travoltan trousers

buy 45 Knob Goblin seltzer for 72 each from The Knob Dispensary
You spent 3,240 Meat
You acquire Knob Goblin seltzer (45)

equip pants hobo dungarees

use 45 Knob Goblin seltzer
You gain 443 Muscularity Points

equip pants Travoltan trousers

buy 4 Knob Goblin seltzer for 72 each from The Knob Dispensary
You spent 288 Meat
You acquire Knob Goblin seltzer (4)

equip pants hobo dungarees

use 4 Knob Goblin seltzer
You gain 41 Muscularity Points

equip pants Travoltan trousers

buy 44 Doc Galaktik's Invigorating Tonic for 81 each from Doc Galaktik's Medicine Show
You spent 3,564 Meat
You acquire Doc Galaktik's Invigorating Tonic (44)

equip pants hobo dungarees

use 44 Doc Galaktik's Invigorating Tonic
You gain 448 Muscularity Points

equip pants Travoltan trousers

buy 3 Doc Galaktik's Invigorating Tonic for 81 each from Doc Galaktik's Medicine Show
You spent 243 Meat
You acquire Doc Galaktik's Invigorating Tonic (3)

equip pants hobo dungarees

use 3 Doc Galaktik's Invigorating Tonic
You gain 29 Muscularity Points

equip pants Travoltan trousers

buy 57 Cherry Cloaca Cola for 72 each from White Citadel
You spent 4,104 Meat
You acquire Cherry Cloaca Cola (57)

equip pants hobo dungarees

use 57 Cherry Cloaca Cola
You gain 456 Muscularity Points

equip pants Travoltan trousers

buy 3 Cherry Cloaca Cola for 72 each from White Citadel
You spent 216 Meat
You acquire Cherry Cloaca Cola (3)

equip pants hobo dungarees

use 3 Cherry Cloaca Cola
You gain 25 Muscularity Points

equip pants Travoltan trousers

buy 115 soda water for 63 each from The General Store
You spent 7,245 Meat
You acquire soda water (115)

equip pants hobo dungarees

use 115 soda water
You gain 463 Muscularity Points

equip pants Travoltan trousers

buy 6 soda water for 63 each from The General Store
You spent 378 Meat
You acquire soda water (6)

equip pants hobo dungarees

use 6 soda water
You gain 21 Muscularity Points

equip pants Travoltan trousers

buy 58 Regular Cloaca Cola for 72 each from White Citadel
You spent 4,176 Meat
You acquire Regular Cloaca Cola (58)
 

Veracity

Developer
Staff member
Interesting.

restore_mp() calls RecoveryManager.recoverMP()
restore_hp() calls RecoveryManager.recoverHP()

Neither of this is under RecoveryManager.isRecoveryActive(), so Travoltan trousers are usable.

On the other had, the CLI commands "recover mp" and "recover hp" DO set and restore isRecoveryActive.

That is a bug in the ASH functions. Will fix, regardless of what we decide to do with this bug request.
 

Veracity

Developer
Staff member
Huh.

RecoveryManager.recoverMP and RecoveryManager.recoverHP will call your recovery script, if any.
The CLI "recover" command protects your equipment by saving and restoring an outfit checkpoint around the call.
The ASH functions save/restore neither isRecoveryActive (thus prohibiting outfit switching) nor your equipment (thus cleaning up after recovery scripts).

I believe the ASH functions should mirror the behavior of the CLI command.
 
Top