Universal Recovery Script

Great Script

I first of all wanted to tell you that I really like this script. I can definitely tell you have put a lot of thought into how healing should really be done. For instance, it was not at all obvious to me why you want to heal from inventory first in hardcore. After all, I mostly just forgot about a lot of those items. After using the script thought, I am both sold on the idea of using inventory first and suprised at how much healing I was neglecting in the late game. So Kudos!

At the same time, I've now found I want it to go further in some ways. For instance, even though it is extremely efficient with my inventory items, I found I was still losing meat faster than when I would heal by hand (or even when I would let mafia use just that one skill I knew was good). So I found myself wanting it to be as efficient as possible with my skills, the same way as it was for my items. Since then I've been working on the code with this slant in mind. If your interested, I'll let you know how it goes.

Cheers, juyes
 
I am definitely interested. I'd love to know what sort of healing logic you have. If I think you're on to a good thing I might beg to include it in my own code.
 
Hey Bale,

I finally found to time to play with this. The way I am thinking the healing logic would change, is first to value healing efficiency (hp per mp, or mp per meat) above everything else, and second to waste as little as hp and mp restoration as possible even when using skills. The only way I could think of doing this, is to allow the script to come up short on healing if it makes sense for efficiency reasons. For instance, if I have a decent skill like Tongue or Power Nap, but casting them leaves me 10 hp shy of my target, then it is better for efficiency reasons to come up short by 10 rather than paying some ridiculous prices to Doc to make sure you get the last little bit up to the target amount. I have the hp implementation of this logic mostly working, but the mp portion still needs some work and testing.

I'm glad to hear you might be open to changes in the code. I also have some ideas about making the script easier to customize with fewer lines of code for handling special cases. For example, instead of having the use_oil and use_herb flags, I made a new map 'boolean [item] read_use_flag'. Then everything in the read_use_flag map is removed from the heal map if the corresponding checkbox in the mafia hp preferences is unchecked. I then only needed to add 3 lines of code to put red pixel potions, gauze garters, and fithly poultices into the read_use_flag map, and now the script reads my mafia preferences for those as well as for the massage oil and the medicinal herbs. I wanted them added because in hardcore I usually rush through the war and pixels so that I only have like 2 rpp and 3 gg for taking down the shadow, along with 5-8 gg/fp that I want to save for the NS, and I want all of those to be under my control.

Cheers, juyes
 
The only way I could think of doing this, is to allow the script to come up short on healing if it makes sense for efficiency reasons.
You're probably right about that. I'm not nearly as stubborn healing manually as the script is.

I made a new map 'boolean [item] read_use_flag'. Then everything in the read_use_flag map is removed from the heal map if the corresponding checkbox in the mafia hp preferences is unchecked.
Clever! If I want to borrow that, would you mind if I added it to a future version of my script?
 
Yes, please feel free to borrow any ideas, because I wouldn't even have known where to start without your script. Here are the relevent sections for the read_use_flag stuff.

At the top, with all the other configuration info I added.
Code:
// Disable these items if unchecked under hp recovery in kolmafia interface
boolean [item] read_use_flag;
	read_use_flag[$item[Medicinal Herb\'s medicinal herbs]] = true;
	read_use_flag[$item[red pixel potion]] = true;
	read_use_flag[$item[scented massage oil]] = true;
	read_use_flag[$item[gauze garter]] = true;
	read_use_flag[$item[filthy poultice]] = true;
The backslash just makes emacs syntax highlighting not get confused. C syntax highlighting actually works ok for ash, surprisingly.

And in the restore_values function, inside the 'foreach key in heal' loop:
Code:
	if (read_use_flag contains key) {
	    if (!contains_text(get_property("hpAutoRecoveryItems"), 
			       to_lower_case(key)) )
		remove heal[key];
	}

If your interested in other configuration suggestions. I also suggest pre-calculating the average hp and mp values and storing them in the recovery map file. I attached transform.ash, which calculates the averages and writes out the new map. And also recovery_map.txt, where I manually cleaned up the 999s and 666s to match the rest of the file better. Then in the rest of the script, you can replace the ave_heal function calls by map lookups, which seemed to speed up the script, even if the speedup may have all been in my head :) For completeness, I attached the whole restore_values function as well.

My other configuration changes were also to make special cases cleaner and easier to extend in the future. These include: a skills map/record structure to hold skill healing information, a makeable map/record structure to hold createable items (palm frond, etc), and a npc_cost map with a can_buy function (think have_skill for npc store buyables). In total, they all allowed me to replace a lot of the special case if...elseif...else blocks (or switch blocks in 2.3) with loops, which could be easily extened to other skills, createables, or npc mp restorers.

Once I test it a bit more, I can send you all the damage I have done, so you can pick and choose what to add back to Universal recovery. Is attaching files the best way to share and discuss the changes?

Cheers, juyes
 

Attachments

Oh my. So much. I think that calculating ave values is quick enough that I'll keep it the way it is, rather than add ave as a new item to the record. Just because I find it unaesthetic. At least unless I change my mind.

On the other hand, I have been considering adding palm frond, etc to the map as pseudo-items to make various things work smoothly. They'd still need some special handling, but it would help. I'm definitely interested in seeing what you're doing with npc_cost and can_buy.

I think that code blocks are the best way to discuss specific changes, but it is useful to attach files to show the big picture.

As a minor quibble:

Code:
	if (read_use_flag contains key) {
	    if (!contains_text(get_property("hpAutoRecoveryItems"), 
			       to_lower_case(key)) )
		remove heal[key];
	}
This is causing you to check preferences for every single item in read_use_flag. It would be better if you did string hpAutoRecoveryItems = get_property("hpAutoRecoveryItems") and then checked the string. Reducing unnecessary disc access is a good thing and probably removes more run time than average calculation.

I might actually use a derivative of this method to check preferences for each and every item that mafia's HP/MP is capable of configuring since it is so easy to do. Telling people that only some of the items in the menu are effective is not good UI.
 
Oh my. So much. I think that calculating ave values is quick enough that I'll keep it the way it is, rather than add ave as a new item to the record. Just because I find it unaesthetic. At least unless I change my mind.

No problem, you can always change your mind later. Ironically I did it in the first place because it was more unaesthetic for me to watch it recalulate thousands of averages that never changed. Overall it doesn't change the map much, and you could just change aveheal and keep the function if you would prefer that (this is only a two line change).

This is causing you to check preferences for every single item in read_use_flag. It would be better if you did string hpAutoRecoveryItems = get_property("hpAutoRecoveryItems") and then checked the string. Reducing unnecessary disc access is a good thing and probably removes more run time than average calculation.

I might actually use a derivative of this method to check preferences for each and every item that mafia's HP/MP is capable of configuring since it is so easy to do. Telling people that only some of the items in the menu are effective is not good UI.

Great point about cacheing the property string. A variant that allows you to check all the preferences is exactly the kind of extensibility I had envisioned.

On the other hand, I have been considering adding palm frond, etc to the map as pseudo-items to make various things work smoothly. They'd still need some special handling, but it would help. I'm definitely interested in seeing what you're doing with npc_cost and can_buy.

Here is some of the makeable code. I just note that most of the coding advantage comes from the sections not shown where you replace big if...elseif... (or switch) blocks with not-so-redundant loops.
Code:
/ These restorers can be created from other items
record makeable {
    item madefrom;
    int required;
    int makes;
};

makeable [item] create;
create[$item[palm-frond fan]] = new makeable($item[Palm frond], 2, 1);
create[$item[New Cloaca-Cola]] = new makeable($item[six-pack of New Cloaca-Cola], 1, 6);

// example use in choose_inventory
	foreach key in create {
	    if (item_amount(key) < 1 && 
		item_amount(create[key].madefrom) >= create[key].required) {
		if (heal[key].maxhp > 0 && heal[key].maxhp <= amount ) {
		    options [i] = key;
		    maxval [key] = heal[key].maxhp;
		    i = i + 1;
		}
	    }
	}

// example use in inv_quant
    if (create contains it)
	ihave =  ihave + create[it].makes * 
	    floor(item_amount(create[it].madefrom) / 
		  to_float(create[it].required));

Here is some of the npc_cost code. This changes even more if...elseif blocks into loops.
Code:
// cost info for npc purchasable mp restorers
int [item] npc_cost;
    npc_cost[$item[magical mystery juice]] = 100;
    npc_cost[$item[black cherry soda]] = 80;
    npc_cost[$item[knob goblin seltzer]] = 80;
    npc_cost[$item[Regular Cloaca Cola]] = 80;

// is this item npc purchaseable?
boolean can_buy(item it) {
    if (it == $item[magical mystery juice])
	return buy_mmj;
    else if (it == $item[black cherry soda])
	return black_market_available();
    else if (it == $item[knob goblin seltzer])
	return (have_outfit("knob goblin elite guard uniform") && 
		item_amount($item[Cobb\'s Knob lab key]) > 0  && 
		guard_maxmp() >= my_mp() && guard_maxhp() >= my_hp());
    else if (it == $item[Regular Cloaca Cola])
	return white_citadel_available();
    return false;
}

// example use in a new function of mine to find the most efficient npc restorer available

// get our most efficient npc item (by avemp/meat)
item find_efficient_item(int amount) {
    float max_efficiency = 0.0;
    float efficiency;
    item best = null;
    foreach it in npc_cost {
	if (can_buy(it) && heal[it].maxmp <= amount && npc_cost[it] < my_meat()) {
	    efficiency = heal[it].avemp/to_float(npc_cost[it]);
	    if (efficiency > max_efficiency) {
		max_efficiency = efficiency;
		best = it;
	    }

	}
    }
    return best;
}

And finally, for completeness the skill stuff. This just creates a skill map that parallels the heal map. You can then easily loop over it or check contains in various places.

Code:
// Type for storing skill data
record skill_info {		
    int minhp;
    int maxhp;
    float avehp;
};

// Global skill info
skill_info [skill] skills;
skills[$skill[Cannelloni Cocoon]] = new 
    skill_info(999999999, 999999999, 999999999.0);
skills[$skill[Disco Power Nap]] = new 
    skill_info(40, 40, 40.0);
skills[$skill[Tongue of the Walrus]] = new 
    skill_info(30, 40, 35.0);
skills[$skill[Disco Nap]] = new 
    skill_info(20, 20, 20.0);
skills[$skill[Lasagna Bandages]] = new 
    skill_info(10, 30, 20.0);
skills[$skill[Tongue of the Otter]] = new 
    skill_info(10, 20, 15.0);

// no examples shown, but you get the idea

Cheers, juyes
 
Interesting. I'm going to be considering that for a future version. I'm just finishing off brimstone bracelet control since that is something I've been annoyed with for a long time. (It's really dumb the way that mafia will auto-equip the brimstone bracelet to cast Cannelloni Cocoon when the bracelet destroys maxHP.) I think I'll add full configuration support as you've suggested and then make a release.
 
I look forward to your improvements, especially the brimstone logic. And please do consider some of the maps, because I can tell you are interested in easier to read and maintain code from your jump to the switch statement.

In my ongoing experiment, I finally got things working enough to test out an efficiency based version. I am extermely happy with the hp restoration as it seems much more efficient with meat. Additionally, hp coming up short had been no problem at all in practice. Indeed, being able to set the hp restoration percentage to 100 and be assurred it will never overheal or waste resources has been very nice.

But for mp, coming up short leads to problems. For instance, mafia will request 20 mp to cast Sonata, but if it heals just 18, which is not enough to cast and causes mafia to peace out. So I cannot allow it to come up short when mafia requests small amounts of mp. I have to think and experiment how to best trade off overall efficiency and not coming up short in those cases.

Cheers, juyes
 
I can tell you are interested in easier to read and maintain code from your jump to the switch statement.
Very much. It's hard to make it better without such attention. I managed to unravel a couple of minor, but hard to spot bugs in the process of making the code prettier.

I finally got things working enough to test out an efficiency based version. I am extermely happy with the hp restoration as it seems much more efficient with meat. Additionally, hp coming up short had been no problem at all in practice. Indeed, being able to set the hp restoration percentage to 100 and be assurred it will never overheal or waste resources has been very nice.
I look forward to seeing what you've done for that. You've already given me a few ideas that I look forward to trying out when I have a chance.

So I cannot allow it to come up short when mafia requests small amounts of mp.
Already occurred to me. ;) Fortunately MP restoration target is rarely set to 100% so pretty much every restorative can be used without waste.
 
Hey Bale,

Alright, I think I solved the MP issues, and the efficiency of it does rely on the fact that the MP target is usually less than 100%. It works by picking the most efficient npc mp restorer, and then using it to over-heal past your target but not past your max mp.

The HP code is working beautifully, and relies on a full meat cost analysis of 1) full healing, or 2) healing with you most efficient (non-full) skill, or 3) buying hp. It does the cost analysis early enough to use the full skill before any inventory would have been used.

Along the way I over-zealously broke the mallcore stuff, but I just broke the prism so I can test it as I integrate the new and the old function calls. I also still need to incorporate the birdform updates.

I found a bug in the map for black cherry soda, as the minimum should be 9 mp. Also, I remember at least one place where hp and mp were switched in a copy/paste type bug in your code. However, it was so long ago and things have diverged enough now that I can no longer pin down where it was. Sorry I didn't make a better note of it.

Cheers, juyes
 
Universal recovery v 2.4 released!

Changelog:
version 2.4 April 7, 2009
  • Considers effect of automatic equipment switching on MP cost of skills.
  • Will prevent automatic equipment switching into Brimstone Bracelet if it will lower max HP too much.
  • Fixed recovery logic for cases when HP target is 100% of max HP, to allow for wasted restoration.
  • Fixed round up error causing script to recover at low HP/MP, when set to not recover.
  • Knows when to purchase extra seltzer for next time, to save time from excessive outfit switching.
  • Less stubborn about HP restoration to save meat.

Also, I remember at least one place where hp and mp were switched in a copy/paste type bug in your code. However, it was so long ago and things have diverged enough now that I can no longer pin down where it was. Sorry I didn't make a better note of it.
No worries. I found that when inserting all those switch statements. It got fixed, though I am a bit embarrassed that someone noticed it.

I'm very curious to see what you've done to my script. I wonder if version 2.5 will be very different as a result.
 
Last edited:
One question. The comment at the top of choose_inventory says "sorted by strongest first.", but the sort seems to be ascending order so that the weakest items are first. Which way is it supposed to be?

I bought several different doc healing items and used the following debugging code to find the order.
Code:
        foreach j in options
            print("index: "+j+" , item: "+options[j]);

Cheers, juyes
 
It certainly should be strongest first. It seems that when jason implemented the sort function and I switched over from my own sort rountine, I accidentally did it wrong. Thanks for finding that bug. I hate noting a bug like that hours after I release a new version. :eek:

Well, it is certainly easy to fix. Replace "value" with "-value".
 
Sorry to find another flaw, but the script keeps trying to divide by zero in mall-mode. This breaks mafia and forces a restart, as I can't seem to call adventure.php in the relay browser after it comes up with the error.

It's happened twice, once in line 741 and once in line 1202, after trying to restore hp.
 
Universal recovery v 2.41 released!

Changelog:
version 2.41 April 7, 2009
  • Increased speed of all calculations involving skills thanks to suggestions made by juyes.
  • Two bugfixes thanks to juyes and fuzzyevil.
I've just added juyes suggestion for speeding up skill calculations. This was a particularly good idea since I was figuring out cost for skills based on auto-switching gear for mp cost reduction. It saves a lot of time to only figure that out once per skill instead of many times.

One question. The comment at the top of choose_inventory says "sorted by strongest first.", but the sort seems to be ascending order so that the weakest items are first.
Thanks. Fixed now.

Sorry to find another flaw, but the script keeps trying to divide by zero in mall-mode.
Thank you very much for finding that! It is fixed now.

I love having such attentive and helpful people using this script!
 
Last edited:
Just found another little bug and issued a 2.42 ... I'm sure it must be good now, or else I'll cry.

With a script this large and complex it's really hard to find every single potential problem.
 
The purpose of testing is to find bugs (as opposed to the inaccurate view that the purpose of testing is to show that there are no bugs).

All software has bugs. Even that which I write. You can't find all the bugs. You can only fix those you are told about.
 
It certainly should be strongest first. It seems that when jason implemented the sort function and I switched over from my own sort rountine, I accidentally did it wrong. Thanks for finding that bug. I hate noting a bug like that hours after I release a new version. :eek:

Strongest does make the most sense so that you can use your big healing items when you have room to. Thanks for the link, I had already stumbled upon that excellent post by Jason when I was wondering why the sort list seemed backwards.

Well, it is certainly easy to fix. Replace "value" with "-value".

I'm assuming you mean -maxval[value] in this particular case :P Also, you could potential do away with the maxval map altogether and just sort with 'sort options by -heal[value].maxhp;' and 'sort options by -heal[value].maxmp;' in the two separate sections.

And don't worry about the bugs, as its a big and complicated script that is getting better all the time.

My hats off to you as thanks for writing it, juyes
 
Back
Top