BatBrain -- a central nervous system for consult scripts

Such as the gnomitronic demodulator, or whatever it's called... only usable once per combat, but SS keeps trying to use it to stasis since it does <7 damage.

I had reported this a while ago. Also Naughty Paper Shuriken's which are a one-time usable stun/tangle on pants-wearing monsters, normal damage to non-pants-wearers. It would be nice for a fix :)
 
Those are both already fixed on my side, w00t. So far I have these flagged in batfactors as once-only actions:


  • Gnomitronic Hyperspatial Demodulizer
  • Zombo's empty eye
  • Gothy Handwave
  • Entangling Noodles
  • Stealth Mistletoe
  • Give In to Your Vampiric Urges
  • Summon Mayfly Swarm
  • Rouse Sapling
  • Spray Sap
  • Put Down Roots
  • The 17 Cuts
  • Entangle
  • Open the Bag o' Tricks
  • Static Shock
  • Throw Shield
  • Feed

I know there must be others. What are they?

Happenings are being reworked -- all actions which are enqueued officially "happen", and they use canonical id's where possible. A few of the happenings had to get renamed in this process such as "monkeybrained", "shieldthrown", and "mistletoed", since those have canonical names. Hopefully this doesn't screw with other scripts too much, but in the long run it will make our lives easier -- there will also be happened() functions for skills, items, and advevents, so you (and I) don't have to try to remember what arbitrary word I chose to use for a happening. Happenings which are not actions will still use such words, however.

After successfully enqueueing an action, options will be rebuilt, so any action which has already "happened" will be excluded if flagged with "once" or have its results modified if it does something different after the first use. This has been working well for me so far and I consider it a significant upgrade to the action queue -- I'm excited to unleash it.

Unrelated side question: went up against the AT demonic nemesis a bit ago and all the damage predictions were significantly wrong. BatBrain estimated that Fearful Fettucini would do more than 200 damage, but when I cast it, it did closer to 70. The wiki doesn't mention it having resistance but it does say "huh?? physical damage cap?????? ehh??" Anyone have any numbers for this, or know where to find them?
 
According to its talk page...
He has roughly 40% spooky resist, probably a similar amount of physical resist. I'd thought I would do 146-166 +48 Spooky damage, but it came out to more like 86 +29 Spooky. Also, earworms did 40 damage, which looks like 20% of Max HP, not 10%.--Temporary man 21:13, 17 April 2010 (UTC)
and this...
Had +15 prismatic damage active, and only did 9 prismatic to him. Probably 40% resistance to all --RoyalTonberry 06:08, 29 June 2010 (UTC)

I thought these guys had scaling DR. --Flargen 06:19, 29 June 2010 (UTC)
The myst guys have 40% resist all and an additional soft damage cap. --Starwed 19:30, 17 July 2010 (UTC)
I agree that he must have a soft damage cap. I used combat items from big slimy cysts, and each one did only around 360 damage or so. I used 5 of them, one moxious maneuver (77 damage), and a beer bomb (41 damage) and killed him. --xiexingwu 05:46, 31 August 2010 (UTC)
 
Happenings are being reworked -- all actions which are enqueued officially "happen", and they use canonical id's where possible. A few of the happenings had to get renamed in this process such as "monkeybrained", "shieldthrown", and "mistletoed", since those have canonical names. Hopefully this doesn't screw with other scripts too much, but in the long run it will make our lives easier -- there will also be happened() functions for skills, items, and advevents, so you (and I) don't have to try to remember what arbitrary word I chose to use for a happening. Happenings which are not actions will still use such words, however.

That is amazing. I am eager to see it.
 
Ugh. I want to bug report my bug report.

Previously I posted a fix for unarmed hitchance() HERE. Unfortunately my fix has a serious problem that I want to warn you about before you roll out a new version of BatBrain. I discovered it when DAM wasted a bunch of MP and got me killed.

Master of the Surprising Fist should not add to the hit chance of moxious maneuver AKA skill 7008. My fix for this is somewhat inelegant, but it works:

Code:
float hitchance(string id) { 
   if (id == "jiggle") return 1.0;
   if (have_equipped($item[operation patriot shield]) && happened("shieldthrown") && !happened("shieldcrit")) return 1;
  // cunctatitis blocks 50% of everything, black cat blocks 50% of items/skills
   float through = 1.0 - 0.5*to_int(have_effect($effect[cunctatitis]) > 0);
   if (id == "attack") through *= 1.0 - fumble_chance();
    else if (my_fam() == $familiar[black cat]) through *= 0.5;
   float attack = my_stat(current_hit_stat().to_string()) + ((equipped_item($slot[weapon]) == $item[none] && equipped_item($slot[offhand]) == $item[none]&& have_skill($skill[master of the surprising fist]))? 20: 0);
   matcher aid = create_matcher("(attack|use|skill) ?(\\d+)?",id);
   if (aid.find()) switch (aid.group(1)) {
      case "attack": break;
      case "use": return (contains_text(to_string(m),"Naughty Sorceress") || m == $monster[bonerdagon]) ? through*0.75 : through;
      case "skill": if (contains_text(to_string(m),"Naughty Sorceress") || m == $monster[bonerdagon])
         through *= 0.5;
         switch (to_int(aid.group(2))) {
            case 0001: case 2003: case 7097: break;  // beak, head, turtle*7 tails all have regular hitchance
            case 7010: case 7011: case 7012: case 7013: case 7014: attack = 10;  // bottle rockets are mox+10
            case 7008: attack = my_stat(current_hit_stat().to_string()) + my_stat("Moxie"); break;             // moxman
            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 ($ints[1003,1004] contains aid.group(2).to_int()) break;
                    if (have_skill($skill[eye of the stoat])) attack *= 1.25 + 0.05*to_int(my_class() == $class[seal clubber]); break;
            case 2015: case 2103: attack = my_stat("Muscle") + 20; break;    // kneebutt & head+knee
            default: attack = 0;
         }
   }
   return attack == 0 ? through : min(critchance() + (1.0 - critchance())*max(6.0 + attack - max(monster_stat("def"),0),0)/11.0,1.0)*through;
}
 
Major update! Don't have time to add the description yet, but enjoy!

@Bale: Aha! I tried to be tricksy with the bottle-rockets but it ended up adding moxie on to the default hitstat for moxman! Your fix isn't correct either -- it should just be moxie, alone. Fixed, thanks for spotting that!

@Theraze: could have sworn I checked the Talk page... oh well. Thanks! I went ahead and added 40% resistance to all nemesis demons.
 
First bug report!

Reusable items (like Miniborg Destroy-O-Bot) should not have a minimum value of 50. Those actually do have a value of 0.

PS. Awesome update! I cannot wait to see how much you just updated DAM.
 
zarqon, are you aware that batfactors has not been updated with scarecrow information? The "scare" key is completely missing.

Since there are no actions in batfactors tagged with the "once" keyword, I suspect that these are both oversights.
 
Ok, I've read through most of the first half of this thread and I'm tired now. I just have a quick question, has anyone written something to handle getting the bonus substats from disco bleeding?

The target number of hp a monster needs before activating the combo is 24 so I assume you would just sort through the available combat actions, choose the one that brought the monster hp as close to 24 without a chance of going over then activate the combo.
 
Something like this, I think...

Code:
void discoBleeding() {
	sort opts by -to_profit(value);
	advevent best() {
		int dmg;
		foreach i, a in opts {
			dmg = dmg_dealt(a.dmg);
			if(dmg < monster_stat("hp") - 24 && a.meat >= 0)
				return a;
		}
		return new advevent;
	}
	
	advevent act;
	boolean done = false;
	while(monster_stat("hp") > 24 && !done) {
		act = best();
		if(act.id != "") enqueue(act);
		else done = true;
	}
	enqueue(get_action($skill[Disco Dance of Doom]));
	enqueue(get_action($skill[Disco Face Stab]));
	macro();
}
 
Last edited:
Now that I'm home from work, I can explain today's update!

1.12 Update

We'll start with the script-breaking change: some of the other changes required that page be converted into a global variable. So rather than passing the page around between functions, it's now kept in a global variable, updated every time act() is called. This means two things for combat scripts using BatBrain: 1) you shouldn't use any page-loading functions that don't include act(), and b) your script is probably broken now -- the page parameter was removed from most functions and you should also rename the page variable in your main() if it was previously named "page". You can look at SmartStasis to see how it's done. It looks like several scripts already updated before I made this post, so I apologize both in advance and retroactively for this.

Now onto the good stuff! Here are some functional changes:


  • BatBrain is getting smarter about the limitations of actions. Adding the keyword "once" in the special field of an action in batfactors will now make BatBrain consider that action a once-per-combat action.
  • Happenings have been reworked as I mentioned in the DAM thread. Now, all actions that are enqueued officially "happen" and use canonical action names. This means that certain happenings got renamed to use canonical values, such as "mistletoed", which is now "skill 5023". One nice side effect of doing it this way is that I was able to add versions of happened() that take skills, items, and advevents. So you can check happened($skill[entangling noodles]) before enqueueing noodles (not that you would need to though, since it won't be in your options anymore if it's happened once).
  • Enqueueing any action will add it to happenings and trigger an options rebuild, so actions which are only available once can be removed, or actions which do something different the second time can be altered.
  • There's now an attack_action() function which returns a decent option for killing the monster, irrespective of continued availability. This still needs some tweaking, but right now I'm actually using a simple finisher script which simply enqueues attack_action() over and over and then submits the whole macro, and it is actually doing a good job so far.
  • All items are worth no less than 50 meat, to avoid thinking nontradeables such as paperclip sproingers are free, or that it's free to throw your Gnomitropic dealy at the Bonerdagon.


And some improved support for stuff:


  • Fancypants Scarecrow support! BatBrain can now support this familiar, but batfactors is still incomplete. So far I've only added damage -- pants which cause other behaviors have not yet been added.
  • BB now knows about the naughty shuriken's once-only stun vs. pants-dropping monsters. Hehe, pants-dropping.
  • BB now knows about Chilled Monkey Brain's once-only stun.
  • BB knows about Feed's stun vs. humanoids.
  • Resistance for demonic forms of your nemesis is 40% across the board. There may be an additional soft cap, but the most important bit is there at least.
  • Stickler for Promptness increases familiar action rate by 25%.
  • Further Fistcore support: meat drops are capped at 12, before bonuses.
  • In kill_rounds(), assume the pessimistic side of monsters' 5% HP variance, capped at +5, to aid predictive enqueueing. Note that monster_stat("hp") will still return an unadjusted value.


And some bugfixes:


  • BatBrain now uses a simple little unarmed() function rather than erroneously checking the Unarmed modifier. This function may also be handy in your script. If so, enjoy.
  • Fix significantly overoptimistic hitchance for Moxious Maneuver.
  • The quiet healer is not a never-attacking monster.


Last, a WARNING OF CAUTION: The happenings revamp is not quite complete. There is one important piece of functionality missing: happenings are not yet reversible, so if you enqueue some stuff, then you don't like it and call reset_queue(), those events are not un-happened; BatBrain will still think that all the events which you previously enqueued have already happened this combat, even though they haven't. The actual structure of happenings needs to change for me to fix this (the number of times an action has happened also needs to be part of the data), but since I haven't seen any scripts taking full advantage of enqueueing yet and I wanted to get this update out, that's what you have for now.
 
Last edited:
Most of that was already in the change log, but thanks for the rest. It's kinda awesome.

since I haven't seen any scripts taking full advantage of enqueueing yet

Really? It has more power than I'm making use of in DAM? In DAM I'm relying on it to keep track of monster damage and so forth so I can know what to queue next. What is the full power of enqueueing? I want to learn!
 
The queue was idealized as a kind of trial zone, where a script could build a potential combat, store the results, then clear the queue and build another potential combat, etc. ad nauseum, eventually choosing the combat with the best results.

In practice so far, it's been nothing more than a kind of buffer to aid macrofication. Which is still awesome! But the queue does allow for a simulation-based script rather than a sequential selection-logic-based script.

However, you can't use it in this way until happenings get reversible, so this is a moot discussion for the nonce.

I'll probably eventually be adding something along the lines of a queue_profit() function which returns the cumulative profit of your combat queue -- and this one, unlike the single action to_profit(), would include both monster and player death, so killing the monster would definitely be evaluated as much better than dying. Then again, if you've set your baseSubstatValue low, the monster drops no items (or you've already stolen its single drop), and you'd have to use very expensive actions to kill it, it may be more profitable to run away!

Also, I'm marking your bug report Not a Bug. Look at line 829, which has been in BB since its early stages. I'm pretty sure you wouldn't want your script funkslinging Miniborgs at the NS.
 
Last edited:
I have been experimenting with it but could never come up with a good way to go back one step in the queue and not the entire way to 0. In order for a return to 0 to be workable in my spamattack I need to rewrite the logic and the time to do that has not happened yet, and neither has the time to have time to do that.
 
One more question about queueing: Is there a simple way to save the queue so that I can create a second queue and compare the results, then restore the first queue if I liked it better? That would be very helpful to utilize it the way you suggest.

I have been experimenting with it but could never come up with a good way to go back one step in the queue and not the entire way to 0. In order for a return to 0 to be workable in my spamattack I need to rewrite the logic and the time to do that has not happened yet, and neither has the time to have time to do that.

Winterbay is right, it would be nice to have a way to pop one element off the top of the queue. (Is it even moral to use stack terminology when talking about queues?)


Also, I'm marking your bug report Not a Bug. Look at line 829, which has been in BB since its early stages. I'm pretty sure you wouldn't want your script funkslinging Miniborgs at the NS.

Oh. That's really elegant. The price is multiplied by 0 if the item is reusable and I'm not fighting an item destroying monster. Very nicely done.
 
Last edited:
Something like this, I think...

Code:
void discoBleeding() {
	sort opts by -to_profit(value);
	advevent best() {
		int dmg;
		foreach i, a in opts {
			dmg = dmg_dealt(a.dmg);
			if(dmg < monster_stat("hp") - 24 && a.meat >= 0)
				return a;
		}
		return new advevent;
	}
	
	advevent act;
	boolean done = false;
	while(monster_stat("hp") > 24 && !done) {
		act = best();
		if(act.id != "") enqueue(act);
		else done = true;
	}
	enqueue(get_action($skill[Disco Dance of Doom]));
	enqueue(get_action($skill[Disco Face Stab]));
	macro();
}

This looks like it should work, but in testing it isn't. Is it because I'm in aftercore and it's looping through too many possibilities?

My main looks like this:

Code:
boolean main(int initround, monster foe, string pg)
{
	page = act(pg);
	discoBleeding();
}
 
foreaching over opts in aftercore seems to be problematic so it could be a reason. However it seems to me like it would choose the first matching option it finds which would hopefully mean that it doesn't have to iterate over the entire opts-map.
Try adding some print statements before and after lines that do things and see where it gets stuck?
 
In my experience foreaching opts in aftercore is not problematic at all -- in fact, SS foreaches opts at least twice every time through the stasis loop without problems.

Stardock, do you have any other sources of monster damage? Attack familiar, passive damage? That could be the problem, since Bale's function only considers the actual damage from your action.

Getting a monster to a target HP is an interesting use of BatBrain. It has several uses, I'd expect -- Disco Bleeding being one such. Here's a quick modification of BatBrain's attack_action() which refuses actions which will reduce the monster's HP below your target (it uses oneround() to include all the other stuff like familiar and monster actions).

PHP:
advevent attack_downto(int targethp) {
   float drnd = die_rounds();
   sort opts by -to_profit(value);
   sort opts by max(kill_rounds(value.dmg),drnd)*max(1,-value.meat);
   foreach i,opt in opts {
      if (monster_stat("hp") - dmg_dealt(oneround(opt)) < targethp) continue;
      vprint("Attack action chosen: "+opt.id,8);
      return opt;
   }
   return new advevent;
}

This would take the place of best() above. Now let's plug it in, with a few other shortcuts:

PHP:
advevent attack_downto(int targethp) {
   float drnd = die_rounds();
   sort opts by -to_profit(value);
   sort opts by max(kill_rounds(value.dmg),drnd)*max(1,-value.meat);
   foreach i,opt in opts {
      if (monster_stat("hp") - dmg_dealt(oneround(opt)) < targethp) continue;
      vprint("Attack action chosen: "+opt.id,8);
      return opt;
   }
   return new advevent;
}

while (monster_stat("hp") > 24 && enqueue(attack_downto(24))) {}   // keep enqueueing until enqueue fails
enqueue(get_action($skill[Disco Dance of Doom]));     // now do the combo (is this the right one btw?)
macro(get_action($skill[Disco Face Stab]));      // we can save a line by using macro(advevent)
 
Hmm, I don't think it's possible right now to predict it with enough accuracy to consistently get it to 24 (it has to be that exact number). You're not going to be able to execute this as one macro unfortunately.

Is there a way to predict the maximum possible damage of a macro using BatBrain (i.e. assume max damage formula and every hit crits)? If so you could do this:

1) Try to build a macro to get the monster to 24 hp (using maximum possible damage calculation so we don't overshoot it).
2) Evaluate the results of the macro and if monster hp is > 24 goto 1.
3) Disco Combo

Also, ideally you'd be doing this in the least amount of rounds since if you're using this in-run, there's a very real possibility you're going to get beaten up if you extend too long.

EDIT:
Also, I don't think we need to take the familiar into account because we can't guarantee the combo being successful if we have an attack familiar. Similar reasoning for passive damage dealing. Basically all I need to make this work is a way to find the maximum possible damage an action might do.
 
Last edited:
Back
Top