BatBrain -- a central nervous system for consult scripts

There is an error in batfactors.txt, the skill Harpoon! (however not very useful it may be) is actually usable above water. Thus the "zone(sea)" is a bit on the wrong side, the formula is a bit simplistic as well but I guess it won't matter that much even though with enough modifiers it could be a rather big difference.
 
I don't think this is new information but
[2091] Battlefield (Hippy Uniform)
Encounter: Naughty Sorority Nurse
Strategy: C:\KolMafia\ccs\CafeBoob.ccs [default]
Round 0: cafeboob loses initiative!
Division by zero (BatBrain.ash, line 576)
Round 1: cafeboob attacks!

css is consult SmartStasis and then attack with weapon.

I don't really like division by zero, unless it is a deliberate kludge to force an execution error, but I am comfortable with taking the limit as the denominator approaches zero.
 
that is a known bug and should be solved in the next update of batbrain. In the meantime you can change the offending function according to the discussion a couple of posts up.
 
Volcanometeor showereruption only works if you have at east 1 volcanic ash in your inventory. I suggest adding the followiong to the SKILLS-section:
Code:
case 55:	if(item_amount($item[volcanic ash]) == 0)
			continue;
		break;
 
1.4 Updates

This update may break your consult scripts, since hitchance() now takes a string rather than an integer -- hitchance() is now used whenever adding an item to opts[], and it works for all canonically-ID'd actions. A better name for it now might be success_chance() but I stuck with the old name. Basically I moved all the code dealing with factoring actions due to blocking into hitchance(), such as Cunctatitis, black kitties, and certain item/skill-blocking monsters. It made the code cleaner in a few places.

It should hopefully also reveal that there is no need to actually use the hitchance() function in your script. This function is for building options -- your script needs only deal with the options.

There's one factoring element I'm not sure about but I need to know to make this information complete. What constitutes a "spell" and is there a programmatic way of identifying if something is a spell? Some monsters, like the royal bees, have spell resistance, which cannot simply be added to their normal resistance but should instead be used to reduce the damage dealt by spells. Certain other monsters are totally immune to spells, which I believe may even include Noodles, but not other multistuns. I'd rather not make a big list of spells if this is something which could be calculated programatically.

Fixed: hp aborts when the monster is stunned -- as I speculated I would, they are now added each round when the monster has any chance of hitting you, and can be tweaked using autoAbortThreshold. When the monster is not stunned, BatBrain macros will abort if your HP goes below one monster attack or your autoAbortThreshold, whichever is higher. Transparency with and improvement of mafia properties -- I feel satisfied with that.
Fixed: division by zero error.
Fixed: meat symbol.
Fixed: critical_chance() is now a part of damage and hitchance calculations. Thanks Theraze.
Fixed: hitchance will never be negative.
Changed: hitchance formula uses 11 rather than 10.5 [1].
Fixed: Volcanometeor Showercovolcanoconiosisruption requires volcanic ash.
Began: support for multistun immunity. Right now it only includes three monsters -- Cyrus and royal bees. Having this in monster proxy records would be better, someday.
Tweaked: some of the core functions to work on copies of records rather than the supplied parameter.
Fixed: detection of unknown monster defense at higher ML. Unknown monsters should be working now.
Added: more comments.

ETA: would an .isstunnable proxy record make sense? Do $monsters even have proxy data?

That's an excellent idea, but I don't think monsters have proxy info yet. A few excellent fields for them would be booleans 'multistun' and 'copy'. A 'boss' flag is nice in theory but would not actually be useful compared to those other two.

If you are using a Black Cat, drop rate is never 100%.

Just noticed I forgot to add that in. Added it just now, so it will definitely be in the next update, which I don't think will be too long coming.

Edit: Any way to have kill_rounds combine damage and hit chance? I split out a version that does dmg_dealt(regular(1)) * hitchance(1) inside, but if there were an official way to do this, it would be more accurate for monsters that you can easily kill if you'd ever hit them...

It has already done this since its inception. You want kill_rounds(get_action("attack")).
 
I'm getting some weird results here with kill_rounds... Here's a moneybee, so as a low scaling mob its stats mean that it should be the same as mine.
> ash import <batbrain> kill_rounds(regular(1))

Returned: 4

> ash import <batbrain> (dmg_dealt(regular(1)) * hitchance("attack") == 0 ? 999999 : ceil(to_float(monster_stat("hp")) / (dmg_dealt(regular(1)) * hitchance(1) + m_hit_chance()*dmg_dealt(retal.dmg) + dmg_dealt(baseround().dmg))));

Returned: 42

> ash import <batbrain> kill_rounds(get_action("attack"))

Returned: 1120000
The first is what was printed in BatBrain 1.3, the second is my modified version of kill_rounds to try to add in hit_chance properly, and the third is what you suggested using...

Somehow, I don't think that 1.12 million rounds is right. My criticals should kill a mob with the same stats as me, if nothing else...

Edit: Question on this line in hitchance...
Code:
   return minmax((6.0 + attack - max(monster_stat("def"),0.0))/11.0 - fumble_chance(),critchance(),1.0);
Do criticals count as an addition to the to-hit chance, or are they under the main system? Because by this, it's working on the only criticals you'll get are out of your normal to-hit chance or the critchance(), whichever is lower. If critical chance is an additional chance to hit, it should look more like:
Code:
   return minmax((6.0 + attack - max(monster_stat("def"),0.0))/11.0 - fumble_chance() + critchance(),critchance(),1.0);

Guessing that the problem has to do with this:
> ash import <batbrain> get_action("attack").dmg[$element[none]]

Returned: 0.0
If it thinks that it will do 0 damage every hit, even on the criticals, there isn't much hope of finishing off the battle...

Or a bit more complete:
> ashq import <batbrain> foreach el in $elements[] print(get_action("attack").dmg[el])

0.0
0.0
0.0
0.0
0.0
0.0
That was against directly following this fight:
[2395] Haunted Ballroom
Encounter: floating platter of hors d'oeuvres
Strategy: attack with weapon
Round 0: Theraze wins initiative!
Round 1: Theraze executes a macro!
Round 1: Theraze attacks!
Round 2: floating platter of hors d'oeuvres takes 156 damage.
Round 2: floating platter of hors d'oeuvres takes 11 damage.
You gain 11 hit points
Round 2: Theraze wins the fight!
You gain 18 Muscleboundness
You gain 7 Mysteriousness
You gain 6 Chutzpah
Not sure how it got to 0 from this:
> ash import <batbrain> regular(1)

Returned: aggregate float [element]
hot => 8.0
none => 117.647995
stench => 8.0
 
Last edited:
Round 0: chef_rannos wins initiative!
Bad monster value: "queen bee" (BatBrain.ash, line 668)
Consult script 'SmartStasis.ash' not found.
You're on your own, partner.

That was using KoLmafia-9414.jar.

I updated to 9443 and it looks like that fixed the issue...gotta remember to update my version more often. ;)
 
Theraze, your options are empty when importing BatBrain. Call act() or build_options() first and you'll get something closer to what BatBrain thinks in combat.

> ash import <BatBrain.ash> act(""); get_action("attack");

Factoring in Scarysauce: (6) damage, retal
Returned: record advevent
id => attack
dmg => aggregate float [element]
**none => 35.05091
att => 0.0
def => 0.0
stun => 0.0
hp => -0.10847107
mp => 0.0
meat => 0.0
rounds => 1
stats => aggregate int [stat]
**Moxie => 0
**Muscle => 0
**Mysticality => 0

EDIT: Also, criticals are a percent of non-fumbles, so capping the hitchance on the high end at 1 - fumble_chance() is the way to go (somehow that didn't make it in this update either). For the purposes of hitchance() we don't care if a hit is critical or regular -- a hit is a hit. Extra average damage for criticals is part of the calculations for damage. So that line should be:

PHP:
   return minmax((6.0 + attack - max(monster_stat("def"),0.0))/11.0,(1.0 - fumble_chance())*critchance(),1.0 - fumble_chance())*through;
 
Last edited:
Ah... is that why my spamattack that uses BatBrain suddenly stopped finding options? With BatBrain 1.3, it found options. With 1.4, it didn't find anything that would kill the moneybee in 10 hits. I'll try it tonight with build_options() at the top.

Question... any chance we can have it detect poison danger with some 'magic' autoAntidote setting, like -1 or something like that? If you're in danger and using an anti-anti-antidote will cure it, save it... if not, skip the cure, since it's not needed.
 
So occasionally SmartStasis will run to round 29, at which point it gives out and spamattack gets called. Unfortunately, in the situations where I got to round 29, I got the message about being unable to enqeue, combat too long. This is line 916, the condition being if round + a.rounds >= maxRounds. I think that should be > instead of >=, as maxRounds is 30, and you should be able to act on the 30th round.
 
Interesting... the *through is new as well... not in release. Does that mean that 1.4 release doesn't have block detection on skills? Also this part:
Code:
(1.0 - fumble_chance())*critchance()
for min seems... high. Shouldn't the low edge still be critchance()? So this:
Code:
   return minmax((6.0 + attack - max(monster_stat("def"),0.0))/11.0,critchance(),1.0 - fumble_chance())*through;

Not sure why this happens, but aren't skills supposed to match like this?
> ash import <batbrain> act(""); get_action("skill fearful fettucini");

Returned: record advevent
id =>
dmg => aggregate float [element]
att => 0.0
def => 0.0
stun => 0.0
hp => 0.0
mp => 0.0
meat => 0.0
rounds => 0
stats => aggregate int [stat]

Hmm... maybe it's applying hitchance to spells too? Appears so:
> ash import <batbrain> act(""); hitchance("skill fearful fettucini")

Returned: 0.09

> ash import <batbrain> act(""); hitchance("skill 3019")

Returned: 0.09
 
Last edited:
1.5 Updates, technically on the same day as 1.4 (here at least).

Updates to fix stuff since it was already a goodly amount of stuff. See the first post for what's fixed. Also added this to the first post:

Any consult script that uses BatBrain must call act(page) before doing anything else, or your combat environment will not be built correctly.

This update makes SmartStasis require an update quickly too (as it stands now it thinks that all of its custom actions don't take a round), so that will be coming shortly.

@Theraze: That exact poison thing has been on my todo list for a while, but it'll be a chore to code so it keeps getting pushed off. The tricky part is that you may be in no danger of losing the combat from being poisoned, but you may be in danger of not winning -- i.e. you need to be unpoisoned to kill the monster -- which opens up a big can of worms, coding-wise. Which reminds me: can you have multiple poisoned effects? If so, which one is removed by the antidote?

Also, what? (1.0 - fumble_chance())*critchance() is lower than just critchance().

About matching skills, you need to pass the actual skill to get_action(), as in get_action($skill[fearful fettucini]). Otherwise, get_action("skill 3019") would also work.

However, using your test code those probably would still return an empty event, since presently BatBrain is overly aggressive about skill availability -- skills are not available unless they appear in your options on the page text. This makes detection simple and 100% accurate, regardless of birdform, conditionally available skills, etc. However, it has two problems: it makes testing difficult, since using an empty page for testing means all of your skills are unavailable. And, it means that skills are unavailable even though you may be able to make them available by restoring MP. A predictive script may want to restore MP to be able to cast a skill, but right now the script wouldn't know that option was there until after the MP was restored and options were rebuilt. But until there's a better option -- and I've never tested have_skill() for conditionally available skills because I was too skeptical that it could account for everything -- that's what's there. Would welcome suggestions for a better method to detect skill availability.

@shazbot: Okay, changed it.
 
Last edited:
Sorry, checked and it appears that the skill matcher in hitchance was off. Should be more like this:
Code:
   matcher aid = create_matcher("(use|skill) ([URL="file://\\d+)?",id);"]\\d+)?",id);[/URL]
I'll check if it's that way in the new release. Yep, new release still has this:
Code:
   matcher aid = create_matcher("(use|skill) (d+?)",id)
Tested like this and that only matches the letter d:
ash matcher aid = create_matcher("(use|skill) (d+?)","skill 3019"); aid.find(); aid.group(2);

Like this...
> ash matcher aid = create_matcher("(use|skill) (d+?)","skill ddd"); aid.find(); aid.group(2);

Returned: d

> ash matcher aid = create_matcher("(use|skill) (d+?)","skill 3019"); aid.find(); aid.group(2);

No match attempted or previous match failed ()
Returned: void

> ash matcher aid = create_matcher("(use|skill) (\\d+)?","skill 3019"); aid.find(); aid.group(2);

Returned: 3019
 
Last edited:
Yep, I'd tested the matcher one way, then altered it to include attack and jiggle, decided against it, and undid things too far. It's OK because I also forgot a semicolon so it doesn't even verify. Fixed the matcher and posted one that actually verifies.
 
Ah, was just going to post the semicolon code-line. :)

And yeah, that makes perfect sense on the crit/fumble thing.
 
So that line should be:

PHP:
   return minmax((6.0 + attack - max(monster_stat("def"),0.0))/11.0,(1.0 - fumble_chance())*critchance(),1.0 - fumble_chance())*through;

Are you *sure* that (6.0 + attack - max(monster_stat("def"),0.0))/11.0 is percentage of all attack attempts, and not just all that are not fumbles or criticals?
I.e. I think that it should actually be

PHP:
float base = minmax((6.0 + attack - max(monster_stat("def"),0.0))/11.0, 0, 1);
return (1-fumble)*(critchance() + (1-critchance())*base);

I see a kolspanding thread that end with "P(hit) = 6/11 + (1/11)*(player_attack - monster_defense)", and has "Crits were removed before numbers were posted (only way to get a 0%), and the rounds they occurred in were not included. Fumbles were prevented." which to me means that this chance only fires if the attack isn't already a fumble or critical.
 
Last edited:
Bee Thoven also needs to be skipped for noodles, like the bee king and the queen.

Edit: Also, found some skills that were missing in hitchance. I'm now using the following skill code for hitchance... if there's other skills I missed, please let me know. :)
Code:
         switch (to_int(aid.group(2))) {
            case 7008: attack = my_buffedstat($stat[moxie]); break;      // moxman
            case 0001: case 2003: case 7097: break;    // Attack with beak, headbutt, turtle of 7 tails; same hitchance as attack
            case 1003: if (have_skill($skill[eye of the stoat])) attack += 20;  // ts
            case 1004: case 1005: if (my_class() == $class[seal clubber] && item_type(equipped_item($slot[weapon])) == "club" &&
                        weapon_hands(equipped_item($slot[weapon])) == 2) return through;    // lts
                    if (aid.group(2) == 1003 || aid.group(2) == 1004) break;
                    if (have_skill($skill[eye of the stoat])) attack *= 1.25 + 0.05*to_int(my_class() == $class[seal clubber]); break;
            case 7010: case 7011: case 7012: case 7013: case 7014: attack = my_buffedstat($stat[moxie]) + 10; break;    // Attack with bottle-rocket
            case 2015: case 2103: attack = my_buffedstat($stat[muscle]) + 20; break;    // kneebutt & head+knee
            default: attack = 0;
         }
Changes I remember... think this is everything, but might have forgotten something.
1) Attack with beak and headbutt both use normal to-hit chance.
2) Old LS skill gets the 2 hand club bonus, else it uses weapon attack. No stoat bonuses.
3) Bottle rockets are moxie+10.

This should keep me from dying when it thinks that it can headbutt un-de-buffed royal bees. It actually wants to shieldbutt them now, which makes me much happier. And more alive. :)

Edit2: This part:
Code:
monster_hp(m) == monster_level_adjustment()
from the unknown monster code makes it improperly match early scaling monsters as unknown, putting them from the 1-11 hp they really have to 170 or whatever you have set as unknown monster ML. Is there still any reason for this part of the check, since they always return 0 instead of scaling up, regardless of your ML or MCD settings? Same goes for attack/defense on scaling mobs...

One other (future) thing. Bee royal tier (all 3) have delevel resistance as well as damage resistance... don't think BatBrain currently has any way of tracking reduced delevels. For that matter, don't know if it tracks any delevels commands yet, for purposes of planning future rounds.
 
Last edited:
I've been working on improving the data for Harpoon! and have come up with the following which should be slightly more correct than what we have today:
Code:
floor(min(800,buffedmus)/4)+{0.1,0.15,0.2}*weaponpower+floor(1.5*bonusmelee)+floor(1.5*bonushot)+floor(1.5*bonuscold)+floor(1.5*bonusstench)+floor(1.5*bonussleaze)+floor(1.5*bonusspooky)

This requires the following additional fvars[]:
Code:
fvars["bonusmelee"] = numeric_modifier("Weapon Damage");
fvars["bonushot"] = numeric_modifier("Hot Damage");
fvars["bonuscold"] = numeric_modifier("Cold Damage");
fvars["bonusstench"] = numeric_modifier("Stench Damage");
fvars["bonussleaze"] = numeric_modifier("Sleaze Damage");
fvars["bonusspooky"] = numeric_modifier("Spooky Damage");

Edit: Tested, and at least it doesn't give any errors. And going by the formula at the wiki the calculated damage output should be correct. Unfortunately I am falling over drunk and cannot test it in reality :)

Edit, edit: I also have one other edit that I've added to my local copy regarding the difference between pasta and sauce spells.
I've added one fvars[] called spelldmgs and do the following:
Code:
fvars["spelldmgs"] = numeric_modifier("Spell Damage");
if(have_skill($skill[intrinsic spiciness]))
	fvars["spelldmg"] = fvars["spelldmgs"] - 10;
else
	fvars["spelldmg"] = fvars["spelldmgs"];

In the datafile I've added the "s"-version to all saucespells. THis since intrinsic spiciness only applies to sauce spells but Mafia adds it to the Spell Damage-modifier no matter what (correctly since it the modifier isn't for casting spells), it's for seeing what you have.
 
Last edited:
Back
Top