Universal Recovery Script

I believe I have found the issue. The function mall_quant determines the number of mall items needed to restore. It returns a floating point (unlike the function used for looking at inventory inside ronin which returns an integer).

So, if you are trying to heal 50 HP (an amount that is passed to the mall_quant function), the item bullpup tent which restores 1000 HP will return 50/1000 = 0.05 bull pup tent quantity needed to restore 50 HP.

The function is used in 3 places. In meat_to_mp function where it is rounded up via ceil.
In the function to actually use the item where it is rounded up
And in the function that determines the best mall item "best_mall" which fails to round it up

So in the above scenario where bullpup tent costs 1900, "best_mall" will, for determining which item is best, internally calculate that the cost to heal 50 HP using a quantity of bullpup tents as = 1900 * 0.05 = 95 meat
However, the cost to restore is not 95 meat because you cannot consume a fraction of an item, it is 1900. This item should not be the chosen result of the best_mall function. And costs should not be calculated based on fractions of an item. The function was obviously designed with multi using items that heal much much less.

Because it does get rounded up for MP restoring, and skills are used by default when possible, this is only an issue if someone is playing in casual, aftercore, or after ronin, and has 0 known healing skills. As such, it was not really noticed until I started a brand new multi that tried to use this script from day 1 of the account existing.

I am currently trying to decide exactly how to fix it while ensuring I don't break anything else. I am honestly not sure what the point is in having mall_quant return a floating point instead of having it automatically ceil inside the function itself. It gets ceil in every usage except one... in which case it should have been ceil and the fact it isn't is the cause of this bug...

Incidentally, it is quite lucky I had my multi stuck in a super long glover HC ascension (I was trying to see the worst glover one can have... HC with 0 skills permed)
I am going to have to make sure NOT to perm a healing skill, and ascend into softcore in order to test possible fixes for this issue

Also, because the way the logic works this is very much an edge case, not a common issue like I thought. And not exactly easy to test since perming a healing skill is one of the first thing people often do. edit: its only an edge case for HP recovery, it actually does affect the MP recovery functionality too.

Also, it will take days (and an annoying inefficient detour, I need to not perm a healing skill, and ascend into casual or get into aftercore without buying a healing skill in run) before my multi is even set up such that I can even start testing fixes. So I will get back to this when I have it set up.
 
Last edited:
Due to Bale's continued absence :( I would like to share this updated version of his script for anyone still using it, so that all his work can continue to be useful. It contains a few fixes which I have found important for its continued operation.

- Fix a divide by 0 error
- Fix hard-to-track-down error involving scrolls of drastic healing
- Remove version checking, as the script is not being updated
- filter items with ZLib's be_good() to verify items are path-safe
- Nerf to Cannelloni Cocoon was causing UR to senselessly abort. Account for the nerf.
- Simply return true as a Vampyre (skip healing altogether)

It also contains an attempted fix to taltamir's problem above. Adding a ceil(), as it turns out, was not the correct solution, as my mallmode multi found out when UR kept failing to find any MP restoratives. So instead, I changed mall_quant() to consider the price of the item relative to the actual amount of restoration possible with that item. If your maximum HP is 30 and you are at 10 health, an item that restores 5000 HP will now be considered as though it only restores 20 HP for the purpose of finding its cost. This will hopefully result in more sensible choices across the board.
 

Attachments

Last edited:
Adding a ceil() was not the right fix. Found a much better solution (above post edited accordingly and attachment updated).

@Crowther: Yep, that and other path restrictions ought to be handled by the be_good() check!
 
Thank you zarqon! I'd made. minor, changes to the script to make it stop failing in certain conditions but your insertion of the be_good() check checks is a HUGE help.
 
Last edited:
Okay, with my previous improvement the script would abort in mallcore when your HP or MP was at max due to all items being considered as effectively restoring 0. So I've updated the previous post again with an improvement to the improvement that only considers "actual" restore amounts when you are below max, and otherwise considers theoretical (unlimited) restore amounts. In so doing, I also slightly refactored best_mall() to sort a map of options rather than maintaining a "best" option, which improves debug output at high verbosity.

This would be easier on SVN, frono, so your idea sounds fine to me. :)
 
svn checkout https://svn.code.sf.net/p/kolmafiascripts/mafiarecovery/code/ if you don't want to use the Scripts Menu
this should be placed in a codebox (and its link status should be revoked), as the forum software automatically truncates it and puts some ... in the middle otherwise. So if you just copy pasted it you will get a broken link that doesn't work.

like so
Code:
svn checkout https://svn.code.sf.net/p/kolmafiascripts/mafiarecovery/code/

Also best to add a note to uninstall the old version first?
 
Last edited:
It is recommended that users uninstall the OBE Universal Recovery

and then install the new.

Code:
svn checkout https://svn.code.sf.net/p/kolmafiascripts/mafiarecovery/code/

But please, make your life easier and use the Scripts Menu the day after you update to r19385.
 
Hello Frono: some thoughts on updates to the script if you're interested.

Kingdom of Exploathing
- Doc Galaktik's isn't an available location and the script tries to purchase anti-anti-antidotes
- Medicinal Herb's medicinal herbs can't be purchased as the guild isn't unlockable

Most of the Doc's or guild restores are switchable in the relay GUI but the antidote isn't one of them.

I made the following change to lines 1572 - 1573

Code:
	if ($strings[Zombie,Dark Gyffte,Kingdom of Exploathing] contains my_path()) return true;	 // Paths where anti-anti-antidote isn't useful

Bale preferred to use booleans but that would have meant more involved changes.
 
Hello Frono: some thoughts on updates to the script if you're interested.

Kingdom of Exploathing
- Doc Galaktik's isn't an available location and the script tries to purchase anti-anti-antidotes
- Medicinal Herb's medicinal herbs can't be purchased as the guild isn't unlockable

Most of the Doc's or guild restores are switchable in the relay GUI but the antidote isn't one of them.

I made the following change to lines 1572 - 1573

Code:
	if ($strings[Zombie,Dark Gyffte,Kingdom of Exploathing] contains my_path()) return true;	 // Paths where anti-anti-antidote isn't useful

Bale preferred to use booleans but that would have meant more involved changes.

Thanks. Could you give me more context? The complete file and I can diff or maybe before and after that shows enough context that I can see lines that do not change? Maybe I'm just brain dead but I don't think I have the right place.
 
I don't have diff on this computer but I can supply more context. Change:

Code:
// This will cure poisoning. It will only fail if the character does not possess an antidote, or can't afford to buy one.
// It will also attempt to keep a few spare anti-anti-antidotes in inventory, for emergency in-combat use.
boolean unpoison() {
	if(zombie)		 // Zombie Masters don't stay poisoned and cannot purchase antidotes.
		return true;

	// Am I poisoned?
	boolean poisoned() {


to

Code:
// This will cure poisoning. It will only fail if the character does not possess an antidote, or can't afford to buy one.
// It will also attempt to keep a few spare anti-anti-antidotes in inventory, for emergency in-combat use.
boolean unpoison() {
	if ($strings[Zombie,Dark Gyffte,Kingdom of Exploathing] contains my_path()) return true;	 // Paths where anti-anti-antidote isn't useful
	
	// Am I poisoned?
	boolean poisoned() {

If I were more energetic I would have created booleans for Dark Gyffe and Kingdom of Exploathing and then gone through the entire script modifying the logic to include those checks where appropriate. If I were even more energetic I would have grouped the booleans into a general set of cases and refactored the script recovery logic so that it would be simpler to add exception cases in the future.
 
Last edited:
UR checks the value of chateau_Available to determine availability of the Chateau but doesn't seem to check whether it is in standard or not. As a result UR keeps trying to use the Chateau to rest, and failing, when in a standard run.

I don't have a one line fix for this one.
 
UR checks the value of chateau_Available to determine availability of the Chateau but doesn't seem to check whether it is in standard or not. As a result UR keeps trying to use the Chateau to rest, and failing, when in a standard run.

I don't have a one line fix for this one.

:-(

Is a workaround to manually set chateau_Available? Is this a KoLmafia bug because it is set to true even when it should not be?
 
How does it check "is Chateau available"?

Presumably it could also check "is_unrestricted( $item[ Chateau Mantegna room key ] )"
 
At present the logic is:

Code:
int camp_hp = numeric_modifier("Base Resting HP") * (numeric_modifier("Resting HP Percent")+100) / 100 + numeric_modifier("Bonus Resting HP");
int camp_mp = numeric_modifier("Base Resting MP") * (numeric_modifier("Resting MP Percent")+100) / 100 + numeric_modifier("Bonus Resting MP");
int chateau_hp = 250; // Range is 200-300
int chateau_mp = 125; // Range is 100-150

Code:
int rest_hp() {
	if(get_property("chateauAvailable") == "false")
		return camp_hp;
	if(my_level() < 13)
		return chateau_hp;
	return max(camp_hp, chateau_hp);
}
int rest_mp() {
	if(get_property("chateauAvailable") == "false")
		return camp_mp;
	if(my_level() < 13)
		return chateau_mp;
	return max(camp_mp, chateau_mp);
}

boolean rest() {
	if(get_property("chateauAvailable") == "false" || (my_level() >= 13 && camp_mp > chateau_mp) )
		return cli_execute("rest");
	visit_url("place.php?whichplace=chateau&action=chateau_restbox");
	return true;
}

If I type this in the gCLI:
Code:
prefref chateauAvailable

I get "TRUE" as the value.

I think "TRUE" is correct because I do have the chateau when out of standard. Inserting logic to check "is_unrestricted" is probably the way to go.

In a perfect world we should probably add handling for the new campground item of the month as well.
 
Last edited:
I looked at mafia and it is pretty clear that mafia makes a distinction between availability and usability. So the solution looks to be to add the is_unrestricted check. I will try and do that later today.
 
Back
Top