BatBrain -- a central nervous system for consult scripts


Well-known member

BatBrain (Battle Brain) -- part of my ongoing BatMan project -- is a function library for script authors who want to write a consult script. Years of work and lots of collaborative effort (from scripters, devs, Wiki scrapers, and lots of users) have gone into this and it is pretty much the awesomest ASH library script to come down the pike since ASH was a twinkle in Hola's eye.

For Users

Like ZLib (which BatBrain also requires), BatBrain is a function library for use by other scripts. Running this script will do basically nothing; don't even bother. However, without this script sitting somewhere in your mafia directory, other scripts which want to use these functions won't work. So that's all you have to do -- download it. You're done.

For Scripters

This script is designed to provide you a comprehensive picture of the combat environment, then give you easy-to-use tools to decide what to do with your available options. Using this library, your script will approach omniscience regarding your current battle. You can then predictively build your combat, finally submitting the entire thing as a macro when you're done.

Server-friendly consult scripts with capabilities far exceeding both a normal CCS and a BALLS macro are now within even a mediocre ASH scripter's grasp.

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

import <BatBrain.ash>

void main(int initround, monster foe, string pg) {
  // now you can smack down the monster with BatBrain-powered gusto!

Actually, I recommend importing SmartStasis instead -- then you'll have the power of both scripts at your disposal!

If you'd like an example of how to use BatBrain in a consult script, I have included a very simple sample script called, appropriately, SimpleSmack. This is the script I use for exactly everything, in order to run BatBrain through its paces. It takes advantage of BatBrain's queueing ability to attempt to build and then submit a full combat as a macro, with just a few custom actions and safeguards in place to preserve automation. Something much more involved, like WHAM, is also possible if you want further ideas.

I've also included a consult script for handling Pokefam battles -- SimplePoke -- which is basically BatBrain for Pokefam fights.

BatBrain documentation: click here or type "ashwiki batbrain" in your CLI.

batfactors documentation: click here or type "ashwiki batfactors" in your CLI.


Type this in your CLI:
svn checkout

5.01.11 - Mayday! Mayday!  BatBrain version 1.0 posted.  Now that it's predictive in combat I figured it's finally ready for release.
6.02.11 - 1.1 changes: lots.  Some highlights (because I don't remember them all): slightly reformatted advevents to include the number of rounds an event takes (and made some progress on integrating stats more), cleaned up some output, new functions kill_rounds() and die_rounds(), and automatic acquisition of stat-appropriate Juju Gazes.
6.03.11 - 1.2 changes: reworked monster stuff a bit -- there's now a function [tt]m_event()[/tt] which returns the entire monster event, including damage, self-heals, beaten up, and special moves.  Still needs some fleshing out but it's a marked improvement.  BatBrain now uses this in its predictions.  NSN and quiet healer never deal damage.  Some functions were altering opts[] -- change those to work on copies.  Predictive adjustment now works for multi-round events -- it factors in the base round and monster attack for each round.
6.04.11 - 1.3 changes: consolidate round-building code in one place.  Don't include monster value in meat if the action wins the combat -- that's the job of an importing script.  Fix copying advevents -- use new (blank) advevents rather than the variable being initialized.
6.14.11 - 1.4 changes: fix divide by zero error.  Consolidate success chance code for all actions into hitchance(), and factor all actions by their success chance when adding to opts[].  Use proper critical hit chance (with modifiers) to calculate both hitchance and damage.  Use 11 rather than 10.5 for hit chance based on info on KoLspading.  Change low-HP aborts in macros to be recalculated each round, and skip if the monster is stunned -- and allow autoAbortThreshold to determine the abort cutoff if it's set higher than the monster's damage.
6.14.11 - 1.5 changes: hard on the heels of 1.4, more stuff revamped thanks to this attentive community and me actually having a few free hours at work.  Rework the way rounds are added to events -- an advevent now takes 0 rounds unless the round number is specified.  This fixes the issue where Bander augmentation made an action take 2 rounds, and allows me to remove a lot of [tt]rounds = 0[/tt] statements.  Fix the way fumbles and crits are used in hitchance.  Include blocking for skills/attack.  Black Cat means pickpocket should never consider items at 100%.  Allow enqueueing (yes, definitely sexy) up to maxround.
6.19.11 - 1.6 changes: hitchance for more skills, fumbles only apply to attacks, one more little fix to hitchance formula.  Hardcode damage for Clobber.  Remove stat == ML checks for determining unknown stats.  Account for Spiciness; it doesn't count for non-sauce skills.  Fix resistance for booty crab.  Account for Clockwork Apparatus transforming fumbles into awesome.  Bee thoven is multistun-immune.  [I]n[/I] Bottles of Beer on a Golem blocks spells.
6.21.11 - Happy summer to those of you in the Northern Hemisphere!  1.7 changes: don't factor skill cost by hitchance.  Don't name the fumble event since it interferes with the attack event.
7.02.11 - Happy in between Canada Day and American Independence Day!  1.8 changes: fix a bounds issue in hitchance.  Fix a bounds issue in Harpoon! formula.  Expand [tt]my_stat()[/tt] to include all stats, sensitive to speculation using "whatif".  Using the previous feature, only remove dangerous poisons.  Tracking for once-per-combat issues is now saved in an external map, to give BatBrain-powered relay scripts accurate information.  During enqueueing, precede deleveling skills with Mistletoe if you haven't used that yet this combat.  More efficient parsing of events using a matcher rather than a bunch of stringslinging.  Added the critical event to regular attacks.  Items granting special results for a critical hit mostly added to batfactors.
7.08.11 - 1.9 changes: Include deleveling in [tt]to_profit()[/tt].  When selecting stasis action, consider all actions that will never kill the monster equal in terms of damage.  Ranged smacks are impossible.  Mistletoe precedes only appropriate skills, and only if you have MP enough (predictively) for both. Automatically add "hascombatitem" and "hasskill" conditions to all repeat-enqueued actions.  Buzzerker is multistun-immune.  [tt]eval()[/tt] substats rather than [tt]to_int()[/tt]ing them.
9.06.11 - 1.10 changes: it's been a while, so it's hard to say what all was changed. Fixed stasis item selection sort.  Allow pipe-delimited lists of monsters in "monster" and "notmonster" tags.  Support for Fist skills, including Master's passive augmentation of unarmed attack.  Filter all melee skills when wielding a ranged weapon.  Where appropriate, consider your effective familiar in the event you're using a Doppelshifter or costume wardrobe.  Miscellaneous other improvements.
9.22.11 - 1.11 changes: Use mafia's Unarmed modifier for Fist detection, also check fistSkillsKnown rather than my_path().  Don't keep considering skills if you acquire Amnesia mid-combat.
11.30.11 - 1.12 changes: Improvements galore! Revert erroneous unarmed checks -- instead, add a simple little [tt]unarmed()[/tt] function.  Add a "once" keyword to batfactors -- any action flagged with "once" will only be available -- you guessed it -- once.  Rework happenings -- now all actions that are enqueued officially "happen" and use canonical action names.  Enqueueing also triggers an options rebuild, since some actions may be unavailable or different after the first use.  Fix hitchance for moxman.  Resistance for nemesis demons appears to be 40% across the board.  Fancypants Scarecrow support.  Naughty shuriken support.  Chilled Monkey Brain support.  Feed support.  New [tt]attack_action()[/tt] function which returns a decent option for killing the monster.  Quiet healer does have an attack.  Stickler for Promptness support.  Monster meat drops are capped at 12 in Fistcore, before bonuses.  All items are worth no less than 50 meat, to avoid thinking nontradeables such as paperclip sproingers are free.  In [tt]kill_rounds()[/tt], but not [tt]monster_stat()[/tt], assume the pessimistic side of monsters' 5% HP variance, capped at +5.  Convert [tt]page[/tt] into a global variable containing the most recent page load -- removed as a parameter for most functions.
12.2.11 - 1.14 changes: Fix for detecting unknown bang potions/spheres.  Fix for Knee hitchance.
12.6.11 - 1.15 changes: Fix round detection on multi-round pages.  Complete the happenings revamp -- BB now records [I]all[/I] actions and the number of times they have happened this fight.  Resetting the queue will wipe enqueued happenings which have not yet happened.  Add deleveling doubling for Mistletoe.  Now that dangerpoisons uses the rather expensive [tt]attack_action()[/tt], significantly speed things up by building the list of dangerous poisons only once at the beginning of combat.
1.15.12 - 1.16 changes: Reformatted BB -- you can now initialize BB to a specific monster using the [tt]set_monster()[/tt] command, thus making BB useful speculatively outside of combat.  If you don't initialize BB it will initialize to [tt]last_monster()[/tt] automatically in [tt]act()[/tt].  Safer, better attack action selection.  Don't erroneously factor monster special moves by monster hit chance.  Speed: cache base round and monster events per round.  Detect combat completion in [tt]act()[/tt] and don't continue with options rebuild and responses.
3.12.12 - 1.17 changes: New format for batfactors, allowing for greater accuracy in spreads.  HP is now handled as player damage and is a spread.  Better support for some Boris skills (still doesn't know about hitchance though).
3.14.12 - 1.18 changes: Fixed player damage having a minimum of 1, even for 0-pdmg actions.  Improved Boris support for Cleave and Song of Fortune's auto-crit.  Normalize "perfect" damage type.
3.31.12 - 1.19 changes: Added Black Cat and O.A.F. item/skill blocking/replacement.  Rework [tt]enqueue()[/tt] and [tt]macro()[/tt] commands to keep macro information out of the queue.  Implement auto-funk when enqueueing two consecutive item throws!  Support for Sick Pythons, Bifurcating Blow, clumsiness bark, and orange agent. Speed: cache profit of advevents to make [tt]to_profit()[/tt] much less expensive, and refactor detections/responses in both [tt]act()[/tt] and [tt]batround()[/tt] to use a faster and more accurate switch statement.  Track base pairs thrown at Cyrus using BB's happenings detection rather than the previous error-prone method in SS.  Only return 50 for items with no accessible value.  Fix combat completion detection for monsters with no Adventure Again link.  Significantly improve the sort in [tt]attack_action()[/tt].  Holy crap this was a big update.
4.01.12 - 1.20 changes: Fix a devious little bug which allowed empty macros to be submitted.
4.14.12 - 1.21 changes: Refine the refinement to [tt]attack_action()[/tt].  Continue to cache profit in the profit field of advevents, but always recalculate in [tt]to_profit()[/tt]. Fix damage calculations for "regular" attacks.  Fix auto-funk, and avoid performing it when you don't need the combat to be faster.
4.19.12 - 1.22 changes: Add an important line back in which was unintentionally removed previously.  When detecting happenings on the final page of combat, [tt]my_turncount()[/tt] has already advanced, so set the occurrence to turncount - 1.
4.29.12 - 1.23 changes: Fix inconsistencies in happenings tracking for relay play vs. automated play.  Fix [tt]kill_rounds()[/tt] erroneously having a minimum of 2.  Don't add fumbles to melee attack skills.  Don't allow enqueueing of more items than you have.  Song of Battle grants 100% hitchance.  Cache action type functions in global variables smack, plink, and buytime.  Considerably improve the [tt]stun_action()[/tt] sort.  Use recent [tt]effect()[/tt] function in formulas to eliminate several fvars and checks.  Include material component cost for skills.  Be completely pessimistic about monster HP variance throughout.  Speed improvement: only consider m.poison, not all possible poison effects.  Despite appearing in the menu, Mighty Axing is not available with Trusty unequipped.
7.12.12 - 1.24 changes: Hopefully final combat completion detection method -- not fooled by CAB or Bugged Bugbear.  Improved support for: Boshing Smack Slap, OPS, enchanted fire extinguisher, PADL phones, communications windchimes, double-ice shards, Mighty Axing, Cleave. Record compound happenings individually.  Properly account for MP/HP regen for both gain and loss.  Hold back possibly needed tower items in all known locations.  Ignore deleveling profit when your attack will one-shot the monster.  Improve DB action detection for Mistletoe insertion.  Don't include empty batrounds in the macro.
8.10.12 - 1.25 changes: Don't use a reserved word for a variable name.  Don't throw goal items.
11.05.12 - 1.26 changes: Two MP updates for Zombiecore: 1) ignore all MP loss other than skill costs, and 2) ignore all MP gain not from infecting zombies (only 2.5 skills can do this).  Add ongoing infection damage to base event for Infectious Bite.  QWOPped Up grants Clumsy.  Skeletal scabbard doubles damage from swords.  Support for Bear Hug, Grizzly Scene, Ravenous Pounce, Howl of the Alpha thanks to Winterbay.  Misses are now glancing blows.  Also, don't factor fumbles and crits by hitchance.  Move declarations outside foreach loop for greater efficiency.
11.23.12 - 1.27 changes: Mafia monster name changes.  In Zombiecore [tt]my_maxmp()[/tt] may be 0, so don't divide by that. Fix Pop and Lock It matcher. Restore BatBrain's knowledge of equipment and effects to its former glory.
12.01.12 - 1.28 changes: Support for Jalapeño and Jabañero Saucespheres, new physically resistant monsters, and Raging Animal.  Differentiate monster attack/defense elements for determining monster attack/resistances.
1.03.12 - 1.29 changes: If player has Manuel, use his supplied stats without any adjustments for unknown_ml or HP variance.  When simulating a round, cap gains to HP/MP from player actions [I]before[/I] applying the monster event.  Moxious Maneuver uses moxie for damage.
1.18.13 - 1.30 changes: Unknown monster stats are now -1.  Properly remove items for Mother Slime and Chester.  Properly filter skills based on page text.  Ignore mafia's "Found in this fight" message when parsing items.  SC Nemesis fixes: Track seal pup wailing with happened("sealwail").  Smacks work with the Sledgehammer.  Remove all non-weapon sources of damage from options when facing mother hellseals.
1.27.13 - 1.31 changes: Add special attacks for elemental hobos and frustrating knight.  Don't ignore elemental damage in [tt]die_rounds()[/tt].
1.31.13 - 1.31.1 changes: Complete monster attack as a spread fix and the final death of the longstanding Salve/Bandages bug.  Only include double-ice cap retaliation event if it hasn't happened.
4.1.13 - /\/\^v^/\/\ changes: multifarious!  Described [URL=""]here[/URL].
5.1.13 - ~(^._.^) changes: Fix elemental form bug, more [tt]to_event()[/tt] refactoring.  Integrate [tt]endscombat[/tt] in several more places.
5.8.13 - 1.35 changes: Names of phyla have changed.  Reset spell damage before compensating for Spiciness.  Allow enqueueing up to 3 rounds beyond end of combat.
5.29.13 - 1.36 changes: [URL=""]Multifarious again![/URL]
6.02.13 - 1.37 changes: BRICKO reactors only do huge damage to BRICKO monsters.  Faster, better skills filtering regardless of browser-added quotation marks. More yellow-ray delay tweaking.
6.07.13 - 1.38 changes: Don't count Master of the Surprising Fist twice.  Add fvar wpnpower support for Silent skills.  Fix endless loop with Juju mask equipped.
6.19.13 - 1.39 changes: BatBrain is on SourceForge!  Read about the changes to this version at the link below.

Changes beyond this point are listed on BatBrain's SourceForge changelog; however, I will continue to post more in-depth update notes in this thread.
Last edited:


Active member
Note from Minion, Bale: Originally, this thread was split from another discussion just after BatBrain was first written. This post does not discuss BatBrain, but it forms the basis for the discussion that follows. Consequently this post was retained both here (and there) after the threads were split. At the time BatBrain was posted in the SmartStasis thread.

I've poked around a bit in my version of this and made it cast spells if you are a myst class. It generates a macro of up to 7 spells (entangling noodles, 5 dynamically generated spells followed by spaghetti spear or salsaballs depending on class) and executes that. It also makes sure that you have enough MP when you start casting for the planned spells. It does not take advantage of sauce synergy and the handling of elemental spell bonuses could be better, but since unless you tune your spells they are randomly choosen it's a bit more work than I have time for atm to fix.
Last edited by a moderator:


Active member
Looks like BatBrain has a function, regular, that could return how much damage a regular hit will do and could be used to decide if that's enough/better as a myst class... regular(1) should return the value of regular hits...

Edit: Winterbay said this:
It only uses spells for mystclasses, for everyone else it just attacks just like the original spamattack. It currently attacks if I can kill the monster in 10 rounds, and it cannot kill me in the same amount of rounds even if you're a myst class. No need to waste MP when you are backfarming.
However, looking at the download, I don't see myst classes ever using attack.


Well-known member
Do it! Import BatBrain!

Quite a few BatBrain functions are useful here -- your first command in main() should be page = act(page); then your action list will be built and the current round will be detected and saved to the global variable round.

All combat actions are a BatBrain record type called advevents, but there are enough functions that you shouldn't need to deal much with those directly.

get_action(skill) returns a skill action from your options list.

get_action("attack") returns your regular weapon attack. You can also use "jiggle" to get your chefstaff action in the event you want to use that.

There's also a get_action(item), but I think that's outside the scope of your script here. For all forms of get_action(), if you don't have the action available, get_action(whatever).id will be an empty string.

get_action(whatever).dmg returns a spread of how much raw damage it will do, tuned by any cookbooks or skills in effect.

dmg_dealt(get_action(whatever).dmg) will return a float of the actual damage dealt to the monster, after accounting for resistances, vulnerabilities, and Bander augmentation.

macro(get_action(whatever),"") will build and execute a macro that spams the skill -- but with all the appropriate autoresponses built in. Presently there is no detection for running out of MP, however the string parameter is the repeat condition, so you could easily build your own macro to spam an action X times. You can also call macro(advevent) without the second parameter to perform the action only one time.

Anytime you call macro(), that also includes a call to act(), so your available action list will be rebuilt.

m_dpr(0,0) returns a float -- the average amount of damage the monster will do to you per round, accounting for player resistances. The two parameters are monster attack adjustment and stun adjustment, respectively. For this situation using 0's ought to be fine.

Assuming no stunning or deleveling, the monster will kill the player in ceil(my_hp() / m_dpr(0,0)) rounds.

A variable named maxround is also available. It is usually 30, but will be 50 for the appropriate monsters. If the above number of rounds until player death > maxround, the monster can't kill you. If ceil(monster_stat("hp") / dmg_dealt(get_action("attack").dmg)) < maxround, your regular attack can kill the monster before combat ends.

Also, rather than using ASH's monster_hp(), monster_attack(), and monster_defense(), I recommend using BatBrain's wrapper function monster_stat(string). The string supplied should be either "att", "def", or "hp". This allows your script to respond appropriately to monsters of unknown ML.

Have fun!


Active member
I could do that I guess, but then I would, as part of how I work, have to understsnd BatBrain and last time I tried to do that my head almsot exploded. I find the code way too obfuscated for my level of programing and as such hard to read, and I like to understand what I do when writing code. It took me several tries of tiral and error before I finally managed to figure our how to build my darn map of skills and damages (I tried a lot of a lot more complex versions first because I didn't think of the current, almost obvious, one...).

And the script is in no way meant to be optimal, it just tries to build the most cost-efficient way to kill the current monster with your available spells in order to not make me think so much when trying to decide what to cast :)


I've just started to review BatBrain and it is a thing of beauty. I won't claim to understand most of it yet, but it is kinda fun trying to figure it out. Unlike Winterbay, this does not make my head explode. dj_d's scripting makes my head explode, but this makes my head feel like it is full of bees. Happy bees.

BTW, zarqon I noticed an error on line 279 which could hurt people who are not using UR. That is no longer how seltzer is accessed. Now you need to check dispensary_available() since you can sell off the outfit and still access the dispensary.


Active member
I've managed to sort through farm.ash which is the only of dj_d's scripts that I'm really poking around in and now have a fairly good idea of how it works. Good enough to be able to add features and change the normal behaviour even though the setup options atthe top still baffles the hell out of me. Eatdrink is a mess of gartantuan proportions so I only use that as part of farm, and that's about it.

I tried adding poison removal to SmartishStasis/BatBrain a while back (before zarqon fixed that bug) and it failed miserably because I couldn't figure out anything of what the script was doing well enough to get it to do what i wanted it to.


Well-known member
@Winterbay: But... that's why I just explained it. You can ignore the rest -- just use the functions I mentioned as though they exist in ASH. That's the point of a library -- you don't need to understand the inner workings.

@Bale: thanks again! I'll fix that dispensary_available issue in the next update, when I swap potions/spheres back into the data file.


Active member
@Winterbay: But... that's why I just explained it. You can ignore the rest -- just use the functions I mentioned as though they exist in ASH. That's the point of a library -- you don't need to understand the inner workings.

Yes, I know. But I've only just started including zlib in anything I've done because well, I want to understand what the function I use does :)
I may take a look at the monster_stat()-function though since my workaround may fail with unkown monsters as you say. But I've probably wasted enough work time getting this version together to start doing that now, I should probaby do some work as well :)


Winterbay: To help me work out BatBrain I decided to rewrite a simpler introduction to BatBrain than zarqon's post above. As he said, everything runs on advevents - adventure events. You do need to know a little bit about them. First of all, you need to turn any action into an adventure event.

get_action(skill) - converts a skill into an advevent.
get_action("attack") - your regular weapon attack.
get_action("jiggle") -your chefstaff action.
get_action(item) - creates an advevent for item use.

Think of get_action() as a to_advevent() function. ;) Now for the only thing you really need to know about them:

advevent.dmg is the damage spread for the action. This is a map of average damage mapped by element: typedef float[element] spread; To turn this into the damage being dealt by a given action is this AMAZING function:

dmg_dealt(get_action(whatever).dmg) - will return a float of the actual damage dealt to the monster, after accounting for resistances, vulnerabilities, and Bander augmentation.

In other words, you can tell how much damage Weapon of the Pastalord will do to your current target accounting for flavor, half physical damage and monster element this easily!

print("Weapon of the Pastalord will do: "+ 
     dmg_dealt(get_action($skill[Weapon of the Pastalord]).dmg)
     + " damage to this "+ last_monster() +".");

After you've used that wonderful damage information to determine what actions to use, you can build and submit the macro with this:

macro(get_action("attack"), "") - the second parameter tells it how many times to repeat the action. "0" is only once and "" is forever.

This builds in antidote use, gremlin tool gathering, whipping turtles when you're taming them to beat your nemesis, learning rave combos, yellow ray usage and other awesomeness. It's off the chart how much great stuff is built in based on your current situation.

Right now you're thinking that you still cannot make a more complicated macro that uses two different spells, but zarqon actually has support for that. Here's how to submit a macro that uses Stringozzi Serpent followed by Weapon of the Pastalord.

queue[count(queue)] = get_action($skill[Stringozzi Serpent]);
queue[count(queue)] = get_action($skill[Weapon of the Pastalord]);

It's just that simple. The only weakness I can see is that there's no way to add a repeat on the end of that macro or wrap it in a loop.

zarqon, correct me if I'm wrong. But it might be nice to have something like this, so that sauce splashing could be looped...

string macro(string first, string last) {   // executes the advevent[int] variable named queue
   buffer m;
   if(first != "") m.append(first+"; ");
   foreach i,o in queue m.append("; call batround; ");
   if (have_skill($skill[funkslinging])) {  // compact any consecutive item throws if you can funksling
      matcher funky = create_matcher("(use \\d+); call batround; use (\\d+;)",m);
      while (funky.find()) m.replace_string(,",";
   vprint("Executing macro: "+m,9);
   if(last != "") m.append(last+"; ");
   return macro(to_string(m));
string macro() { return macro("", ""); }


Active member
I am currently going through batbrain and adding enters, tabs and {} where I feel is appropriate so that the code becomes legible (for me that is). As an example, the to_event()-function is 26 lines of code in the original version, but in mine it is 51 lines and oh so much more readable :)

After I've done that I might dare actually use things :)


Well-known member
Here's an easier-to-read version:

import<zlib.ash> check_version("BatBrain","batbrain","0.8.2",1715);float[string]fvars;fvars["famgear"]=to_int(equipped_item($slot[familiar])==familiar_equipment(my_familiar()));fvars["spelldmgpercent"]=1.0+(numeric_modifier("Spell Damage Percent")/100);fvars["spelldmg"]=numeric_modifier("Spell Damage");fvars["yuletide"]=to_int(have_effect($effect[yuletide])>0);fvars["havesugar"]=to_int(have_effect($effect[sugar rush])>0);fvars["fweight"]=familiar_weight(my_familiar())+numeric_modifier("Familiar Weight");if(have_skill($skill[kneebutt]))fvars["pantspower"]=(have_skill($skill[Tao of the Terr]))?get_power(equipped_item($slot[pants]))*2:get_power(equipped_item($slot[pants]));if(have_skill($skill[headbutt]))fvars["helmetpower"]=(have_skill($skill[Tao of the Terr]))?get_power(equipped_item($slot[hat]))*2:get_power(equipped_item($slot[hat]));if(have_skill($skill[shieldbutt]))fvars["shieldpower"]=(item_type(equipped_item($slot[offhand]))=="shield")?get_power(equipped_item($slot[offhand])):0;if(have_equipped($item[staff of queso escusado]))fvars["stinkycheese"]=to_int(get_property("_stinkyCheeseCount"));boolean famspent;boolean smusted;boolean waved;boolean should_putty;boolean should_olfact;boolean should_pp;int round;int[item]stolen;int maxround=30;float attadj,defadj,stunadj,hpadj;if(last_monster()==$monster[none]||monster_attack(last_monster())==monster_level_adjustment()){attadj=to_int(vars["unknown_ml"]);defadj=attadj;hpadj=attadj}float monster_stat(string which){switch(which){case"att":return attadj+monster_attack();case"def":return defadj+monster_defense();case"hp":return hpadj+max(1.0,monster_hp())}return 0}int how_many_foes(){int[monster]pluralm;load_current_map("pluralMonsters",pluralm);return(pluralm contains last_monster())?pluralm[last_monster()]:1}int howmanyfoes=how_many_foes();typedef float[element]spread;typedef int[stat]substats;record combat_rec{string ufname;string dmg;string dmgtypes;string special};record advevent{string id;spread dmg;float att;float def;float stun;float hp;float mp;float meat};advevent base;advevent retal;advevent onhit;"base";"retal";"onhit";spread merge(spread fir,spread sec){spread res;res[$element[none]]=fir[$element[none]]+sec[$element[none]];foreach el in $elements[]if((fir contains el)||(sec contains el))res[el]=fir[el]+sec[el];return res}advevent merge(advevent fir,advevent sec){advevent res;"; ";res.dmg=merge(fir.dmg,sec.dmg);res.att=fir.att+sec.att;res.def=fir.def+sec.def;res.stun=(fir.stun>=1.0||sec.stun>=1.0)?max(fir.stun,sec.stun):fir.stun+sec.stun-fir.stun*sec.stun;res.hp=fir.hp+sec.hp;;res.meat=fir.meat+sec.meat;return res}advevent merge(advevent fir,advevent sec,advevent thr){return merge(merge(fir,sec),thr)}spread factor(spread f,float fact){foreach el in f f[el]*=fact;return f}advevent factor(advevent a,float fact){a.dmg=factor(a.dmg,fact);a.att*=fact;a.def*=fact;a.stun*=fact;a.hp*=fact;*=fact;a.meat*=fact;return a}string elem_to_color(element src){switch(src){case $element[hot]:return"red";case $element[cold]:return"blue";case $element[spooky]:return"gray";case $element[sleaze]:return"purple";case $element[stench]:return"green"}return"black"}string to_html(spread src){buffer b;if(src contains $element[none])b.append(rnum(src[$element[none]]));b.append("<b>");foreach el in $elements[]if(src contains el&&src[el]!=0)b.append(" <span style='color: "+elem_to_color(el)+"'>("+rnum(src[el])+")</span>");return b.to_string()+"</b>"}spread to_spread(float dmg,string types,float factor){spread res;if(dmg<=0)return res;string[int]t=split_string(types,",");foreach i,bit in t res[to_element(bit)]=factor*dmg/to_float(count(t));return res}spread to_spread(float dmg,string types){return to_spread(dmg,types,1.0)}float item_val(item i);float dmg_dealt(spread action);advevent to_event(string id,spread dmg,string special){int aoe=(howmanyfoes>1&&contains_text(special,"aoe "))?min(howmanyfoes,to_int(substring(excise(special,"aoe ",""),0,1))):1;advevent res;;res.dmg=factor(dmg,aoe);fvars["dmg"]=dmg_dealt(res.dmg);string[int]parts=split_string(special,", ");if(count(parts)>0)foreach num,bit in parts{if(contains_text(bit," "))switch(excise(bit,""," ")){case"att":res.att=aoe*eval(excise(bit," ",""),fvars);break;case"def":res.def=aoe*eval(excise(bit," ",""),fvars);break;case"stun":res.stun=eval(excise(bit," ",""),fvars);break;case"hp":res.hp=eval(excise(bit," ",""),fvars);break;case"mp"," ",""),fvars);break;case"meat":res.meat=eval(excise(bit," ",""),fvars);break;case"item":string[int]its=split_string(excise(bit," ",""),"; ");float v;foreach n,it in its{if(id=="crown"&&$ints[3450,4469,1445,1446,1447,1448]contains to_int(to_item(it))&&stolen contains to_item(it)){v=0;break}v+=item_val(to_item(it))}res.meat+=v/count(its);break;case"monster":if(last_monster()!=to_monster(excise(bit," ","")))return to_event(id,to_spread(0,"physical"),"");case"notmonster":if(last_monster()==to_monster(excise(bit," ","")))return to_event(id,to_spread(0,"physical"),"")}}return res}advevent to_event(string id,combat_rec src){return to_event(id,to_spread(eval(src.dmg,fvars),src.dmgtypes),src.special)}spread get_resistance(element which){spread res;foreach el in $elements[]res[el]=0;res[$element[none]]=0;if(which!=$element[none])res[which]=1;switch(which){case $element[hot]:res[$element[sleaze]]=-1;res[$element[stench]]=-1;break;case $element[spooky]:res[$element[hot]]=-1;res[$element[stench]]=-1;break;case $element[cold]:res[$element[hot]]=-1;res[$element[spooky]]=-1;break;case $element[sleaze]:res[$element[cold]]=-1;res[$element[spooky]]=-1;break;case $element[stench]:res[$element[cold]]=-1;res[$element[sleaze]]=-1;break}return res}spread mres=get_resistance(monster_element());boolean isseal;switch(last_monster()){case $monster[trophyfish]:foreach el in $elements[]mres[el]=0.25;case $monster[war frat kegrider]:case $monster[war hippy dread squad]:mres[$element[none]]=0.25;break;case $monster[gargantuchicken]:case $monster[heavy kegtank]:case $monster[mobile armored sweat lodge]:mres[$element[none]]=0.5;break;case $monster[frosty]:foreach el in $elements[]mres[el]=1;case $monster[snow queen]:case $monster[chalkdust wraith]:case $monster[ancient protector spirit]:case $monster[Protector Spectre]:case $monster[booty crab]:case $monster[ghost miner]:mres[$element[none]]=1;break;case $monster[broodling seal]:case $monster[centurion of sparky]:case $monster[hermetic seal]:case $monster[spawn of wally]:case $monster[heat seal]:case $monster[navy seal]:case $monster[servant of grodstank]:case $monster[shadow of black bubbles]:case $monster[watertight seal]:case $monster[wet seal]:isseal=true;if(item_type(equipped_item($slot[weapon]))=="club")break;case $monster[hulking construct]:mres[$element[none]]=1;case $monster[zombo]:case $monster[spooky hobo]:foreach el in $elements[]mres[el]=1;break;case $monster[the ghost of fernswarthy n great-grandfather]:mres[$element[none]]=1;maxround=50;break;case $monster[a n stone golem]:mres[$element[none]]=0.5;case $monster[the beast with n ears]:case $monster[the beast with n eyes]:case $monster[a n-headed hydra]:case $monster[n bottles of beer on a golem]:case $monster[a n-dimensional horror]:case $monster[the naughty sorceress]:case $monster[naughty sorceress(2)]:case $monster[the man]:case $monster[big wisniewski]:maxround=50;break;case $monster[blackberry bush]:case $monster[cactuary]:if(weapon_type(equipped_item($slot[weapon]))!=$stat[moxie])onhit.hp-=5.3;break;case $monster[quiet healer]:base.hp+=0.25*17.5;break;case $monster[knob goblin mad scientist]*9;break}spread pres=get_resistance($element[none]);foreach el in $elements[]{if(el!=$element[slime]&&have_effect(to_effect(to_string(el)+"form"))>0){pres=get_resistance(el);break}pres[el]=elemental_resistance(el)/100.0}float dmg_dealt(spread action){float res;element eform;foreach el in $elements[]if(el!=$element[slime]&&have_effect(to_effect(to_string(el)+"form"))>0){eform=el;break}foreach el in action{if(eform==$element[none])res+=max(to_int(action[el]>0),action[el]-mres[el]*action[el]);else res+=action[el]}if(eform==$element[none])return min(res,monster_stat("hp"));return max(to_int(res>0),res-mres[eform]*res)}int hpgoal=round(to_float(get_property("hpAutoRecoveryTarget"))*to_float(my_maxhp()));int hpmin=round(to_float(get_property("hpAutoRecovery"))*to_float(my_maxhp()));float meatpermp,meatperhp;if(get_property("_meatpermp")!="")meatpermp=to_float(get_property("_meatpermp"));else{if(my_primestat()==$stat[mysticality]||(my_class()==$class[accordion thief]&&my_level()>8))meatpermp=100.0/(1.5*to_float(my_level())+5);else if(have_outfit("Elite Guard"))meatpermp=8;else meatpermp=17-to_int(galaktik_cures_discounted())*5}if(get_property("_meatperhp")!="")meatperhp=to_float(get_property("_meatperhp"));else meatperhp=10-to_int(galaktik_cures_discounted())*4;float stat_value(substats s){float res,i;foreach st,v in s{i=v*to_float(vars["BatMan_baseSubstatValue"]);if(st==my_primestat())i*=2.0;if(st==current_hit_stat())i*=1.5;if(my_buffedstat(st)==my_defstat())i*=1.5;res+=i}vprint("Value of stat gain: "+rnum(res),10);return res}substats m_stats(){stat d_stat(){return(string_modifier("Stat Tuning")=="")?my_primestat():string_modifier("Stat Tuning").to_stat()}substats res;foreach st in $stats[]res[st]=numeric_modifier(st+" Experience")+((monster_attack()-monster_level_adjustment())/16.0)*(1+to_int(st==d_stat()));return res}float item_val(item i){if(is_tradeable(i)&&historical_price(i)>max(100,2*autosell_price(i)))return historical_price(i);return autosell_price(i)}float item_val(item i,float rate){float modv=item_val(i)*minmax(rate*(item_drop_modifier()+100)/100.0,0,100)/100.0;vprint(i+" ("+rate+" @ +"+item_drop_modifier()+"): "+item_val(i)+" meat * "+minmax(rate*(item_drop_modifier()+100)/100.0,0,100)+"% = "+modv,8);return modv}float monstervalue(){float res=to_float(meat_drop())*(max(0,meat_drop_modifier()+100))/100.0;int skipped;should_pp=(monster_attack()==0);foreach num,rec in item_drops_array(){switch(rec.type){case"p":if(my_primestat()!=$stat[moxie]||skipped>0)break;foreach dealy in stolen if(item_drops()contains dealy)break;res+=item_val(rec.drop)*minmax(rec.rate*(numeric_modifier("Pickpocket Chance")+100)/100.0,0,100)/100.0;should_pp=true;break;case"":if(stolen contains rec.drop&&skipped<stolen[rec.drop]){skipped+=1;break}if(!should_pp&&count(stolen)==0&&rec.rate*(item_drop_modifier()+100)/100.0<100)should_pp=true;case"c":if(item_type(rec.drop)=="shirt"&&!have_skill($skill[torso awaregness]))break;if(!is_displayable(rec.drop))break;case"n":res+=item_val(rec.drop,max(rec.rate,0.01))}}return res+stat_value(m_stats())}float runaway_cost(){return monstervalue()+to_int(get_property("valueOfAdventure"))}float beatenup_cost(){if(my_familiar()==$familiar[wild hare])return 0;if(item_amount($item[personal massager])>0)return 100;float cheapest=to_int(get_property("valueOfAdventure"))*3;if(have_skill($skill[tongue of the otter]))cheapest=min(cheapest,meatpermp*mp_cost($skill[tongue of the otter]));if(have_skill($skill[tongue of the walrus]))cheapest=min(cheapest,meatpermp*mp_cost($skill[tongue of the walrus]));if(item_amount($item[tiny house])>0)cheapest=min(cheapest,item_val($item[tiny house]));if(item_amount($item[forest tears])>0)cheapest=min(cheapest,item_val($item[forest tears]));if(item_amount($item[sgeea])>0)cheapest=min(cheapest,item_val($item[sgeea]));return cheapest}float beatenup=beatenup_cost()+runaway_cost();combat_rec[string,int]factors;load_current_map("batfactors",factors);if(last_monster()==$monster[mother slime]||last_monster()==$monster[chester]||have_effect($effect[form of...bird])>0){remove factors["item"];vprint("You can't use items in this combat.",-6)}if(have_effect($effect[temporary amnesia])>0){remove factors["skill"];vprint("You can't cast any skills with amnesia!",-5)}float spell_elem_bonus(string types){float result;int milieu=0;foreach el in $elements[]{if(types.contains_text(to_string(el))){milieu+=1;result+=numeric_modifier(to_string(el)+" Spell Damage")}}if(milieu==0)return 0;return(result/(milieu+to_int(types.contains_text("physical"))))}string normalize_dmgtype(string dmgt){switch(dmgt){case"pasta":if(have_equipped($item[chester aquarius medallion])||have_equipped($item[sinful desires]))return"sleaze";if(have_equipped($item[necrotelicomnicon]))return"spooky";if(have_equipped($item[cookbook of the damned]))return"stench";case"sauce":if(have_equipped($item[ol' scratch's manacles])||have_equipped($item[capsaicin conjuration])||have_equipped($item[snapdragon pistil]))return"hot";if(have_equipped($item[glacial grimoire])||have_equipped($item[double-ice box]))return"cold";if(dmgt=="sauce"){if(have_skill($skill[immaculate seasoning])&&dmg_dealt(to_spread(1+spell_elem_bonus("hot"),"hot"))!=dmg_dealt(to_spread(1+spell_elem_bonus("cold"),"cold")))return(dmg_dealt(to_spread(1+spell_elem_bonus("hot"),"hot"))>dmg_dealt(to_spread(1+spell_elem_bonus("cold"),"cold")))?"hot":"cold";return"hot,cold"}if(have_effect($effect[spirit of cayenne])>0)return"hot";if(have_effect($effect[spirit of peppermint])>0)return"cold";if(have_effect($effect[spirit of garlic])>0)return"stench";if(have_effect($effect[spirit of wormwood])>0)return"spooky";if(have_effect($effect[spirit of bacon grease])>0)return"sleaze";string res="cold,hot,sleaze,spooky,stench,physical";if(res.contains_text(monster_element()))res.replace_string(monster_element()+",","");return res}return dmgt}foreach ty,in,rec in factors{string deranged(string sane){matcher rng=create_matcher("\\{.+?,(.+?),.+?}",sane);while(rng.find())sane=sane.replace_string(,;return sane}if(contains_text(rec.special,"custom")){remove factors[ty,in];continue}factors[ty,in].dmg=deranged(rec.dmg);factors[ty,in].special=deranged(rec.special);if(ty=="skill"){factors[ty,in].dmgtypes=normalize_dmgtype(rec.dmgtypes);if(to_skill(in)==$skill[weapon of the pastalord]&&!contains_text(factors[ty,in].dmgtypes,"physical"))factors[ty,in].dmgtypes+=",physical"}}advevent famevent(){advevent res;;if(famspent)return res;if(!(factors["fam"]contains to_int(my_familiar()))||(my_familiar()==$familiar[dandy lion]&&item_type(equipped_item($slot[weapon]))!="whip"))return res;fvars["fweight"]=familiar_weight(my_familiar())+weight_adjustment()+min(20,2*my_level())*to_int(to_int(my_class())+82==to_int(my_familiar()));if(my_familiar()==$familiar[mad hatrack]){if(get_power(equipped_item($slot[familiar]))>0&&get_power(equipped_item($slot[familiar]))<200)fvars["fweight"]=min(fvars["fweight"],floor(get_power(equipped_item($slot[familiar]))/4.0));res=to_event(to_string(my_familiar()),factors["hatrack",to_int(equipped_item($slot[familiar]))])}else res=to_event(to_string(my_familiar()),factors["fam",to_int(my_familiar())]);if(round>9)res.meat=0;matcher rate=create_matcher("rate (.+?)(, |$)",factors["fam",to_int(my_familiar())].special);float r=(rate.find())?eval(,fvars):0;if(r==0||r==1)return factor(res,r);return factor(res,minmax(r+min(0.1,have_effect($effect[jingle jangle jingle]))+min(0.25,to_int(have_equipped($item[loathing legion helicopter]))),0,1.0))}advevent crown(){advevent res;"crown";boolean cutoff=factors["crown",to_int(my_enthroned_familiar())].special.contains_text("r3");if(cutoff&&round>3)return res;res=to_event("crown",factors["crown",to_int(my_enthroned_familiar())]);float crownrate=(cutoff)?1:0.315;switch(my_enthroned_familiar()){case $familiar[chihuahua]:case $familiar[sandworm]:case $familiar[snow angel]:crownrate=0.5;break;case $familiar[Green Pixie]:crownrate=0.2;break;case $familiar[Maple Leaf]:crownrate=0.25;break;case $familiar[Black Cat]:crownrate=1;break}return factor(res,crownrate)}advevent baseround(){return(have_equipped($item[crown of thrones])&&my_enthroned_familiar()!=$familiar[none]&&factors["crown"]contains to_int(my_enthroned_familiar()))?merge(base,famevent(),crown()):merge(base,famevent())}foreach ef,fect in factors["effect"]{if(have_effect(to_effect(ef))==0)continue;vprint_html("Factoring in "+to_effect(ef)+": "+to_html(to_spread(eval(fect.dmg,fvars),fect.dmgtypes))+" damage, "+fect.special,4);if(contains_text(fect.special,"retal"))retal=merge(retal,to_event(to_effect(ef),fect));else if(contains_text(fect.special,"onhit"))onhit=merge(onhit,to_event(to_effect(ef),fect));else base=merge(base,to_event(to_effect(ef),fect))}foreach eq,uip in factors["gear"]{if(!have_equipped(to_item(eq)))continue;vprint_html("Factoring in "+to_item(eq)+": "+to_html(to_spread(eval(uip.dmg,fvars),uip.dmgtypes))+" damage, "+uip.special,4);if(contains_text(uip.special,"retal"))retal=merge(retal,to_event(to_item(eq),uip));else if(contains_text(uip.special,"onhit"))onhit=merge(onhit,to_event(to_item(eq),uip));else base=merge(base,to_event(to_item(eq),uip))}float fumble_chance(){if(have_effect($effect[clumsy])>0)return 1;if(boolean_modifier("Never Fumble"))return 0;return(1.0/max(22.0,to_int(have_effect($effect[sticky hands])>0)*30.0))*max(1.0,numeric_modifier("Fumble"))/(1.0+to_int(have_skill($skill[eye of the stoat])))}float fumble_damage(){int wpnpwr=get_power(equipped_item($slot[weapon]));if(have_skill($skill[double-fisted skull smashing])&&item_type(equipped_item($slot[offhand]))!="shield")wpnpwr+=get_power(equipped_item($slot[offhand]));return max(1.0,to_float(wpnpwr)*0.05)}onhit.hp=(-fumble_chance()*fumble_damage());float m_hit_chance(){float stunmod=min(1.0,stunadj+0.5*(1.0-min(1.0,stunadj))*to_int(have_equipped($item[navel ring of navel gazing])));if(contains_text(last_monster(),"Gremlin"))return 1.0-stunmod;return(1.0-stunmod)*minmax(0.55+(max(monster_stat("att"),0.0)-my_buffedstat($stat[moxie]))*0.055,0.06,0.94)}float m_regular(){float res;res=max(1.0,max(0.0,max(monster_stat("att"),0.0)-my_defstat())+0.225*max(monster_stat("att"),0.0)-numeric_modifier("Damage Reduction"))*(1-minmax((square_root(numeric_modifier("Damage Absorption")/10)-1)/10,0,0.9));if(monster_element()==$element[none])return minmax(res,1.0,my_hp());return minmax(res-pres[monster_element()]*res,1.0,my_hp())}float m_dpr(float att_mod,float stun_mod){attadj+=att_mod;stunadj+=stun_mod;float res=m_hit_chance()*m_regular();attadj-=att_mod;stunadj-=stun_mod;return res}boolean intheclear(){if(stunadj>0.94)return true;if(last_monster()==$monster[none]||m_dpr(0,0)>my_hp()||have_effect($effect[strangulated])>0)return false;return(monster_stat("att")<my_defstat()-6+to_int(vars["threshold"]))}float to_profit(advevent a){float res;if(!=to_string(my_familiar())&&!"base"))a=merge(a,baseround());if(dmg_dealt(a.dmg)<monster_stat("hp")){a=merge(a,factor(retal,m_hit_chance()));res-=m_dpr(a.att,a.stun)*meatperhp}return res+minmax(a.hp,-my_hp(),my_maxhp()-my_hp())*meatperhp+minmax(,-my_mp(),my_maxmp()-my_mp())*meatpermp+a.meat}string to_html(advevent a,boolean table){buffer res;if(table)res.append("<table width=100%><tr><th>Action</th><th>Profit</th><th>Damage</th><th>Other</th></tr>");res.append("<tr><td align=left>");switch{case(contains_text(,"use ")):res.append("Throw "+excise(,"use ",""));break;case(contains_text(,"skill ")):res.append("Cast "+excise(,"skill ",""));break;case("attack"):res.append("Attack with weapon");break;case("jiggle"):res.append("Jiggle Your Chefstaff");break;default:res.append(}res.append(" <span color='green'><small>("+rnum(a.meat)+"μ)</small></span></td><td align=right><span color='green'><b>");res.append(rnum(to_profit(a))+"μ</b></span></td><td align=center>");if(dmg_dealt(a.dmg)>0){res.append(to_html(a.dmg)+" <small>");if(a.dmg[$element[none]]!=dmg_dealt(a.dmg))res.append("Actual: "+rnum(dmg_dealt(a.dmg)));res.append(" <span color='green'>("+rnum(-a.meat/dmg_dealt(a.dmg))+" MPD)</span></small>")}else res.append("--");res.append("</td><td align=center>");if(a.att!=0)res.append(" <span color='gray'>Att: "+rnum(a.att)+" <small>("+rnum(m_dpr(a.att,0)-m_dpr(0,0))+" DPR)</small> Def: "+rnum(a.def)+"</span> ");if(a.stun!=0)res.append(" "+rnum(a.stun*100.0)+"% stun chance");if(a.hp!=0)res.append(" <span color='red'>HP: "+rnum(a.hp)+"</span> ");if(!=0)res.append(" <span color='blue'>MP: "+rnum("</span> ");res.append("</td></tr>");if(table)res.append("</table>");return res.to_string()}string to_html(advevent a){return to_html(a,true)}advevent[int]custom;advevent[int]opts;void addopt(advevent a,float cost,float factor){int c=count(opts);opts[c]=a;if(last_monster()==$monster[mine crab]&&dmg_dealt(a.dmg)>39)a.hp-=100000;if(factor!=1.0)opts[c]=factor(opts[c],factor);opts[c].meat-=cost}advevent bander(int which){if(!smusted&&factors["bander",which].special.contains_text("smust"))return to_event("#bander",to_spread(0,"physical"),"stun 2");return to_event("#bander",factors["bander",which])}combat_rec get_bang(string which){combat_rec res;res.dmg="0";res.dmgtypes="physical";switch(which){case"healing":res.dmg="-14";return res;case"confusion":res.special="att -2.5, def -2.5";return res;case"ettin strength":res.special="att 10";return res;case"mental acuity":res.special="att 5, def 5";return res;case"teleportitis":res.special="def 17.5";return res;case"blessing":res.special="def 25";return res;case"detection":case"sleepiness":case"inebriety":return res}string known;for i from 819 upto 827 known+=get_property("lastBangPotion"+i);advevent bang;float bcount;foreach bp in $strings[healing,confusion,ettin strength,mental acuity,teleportitis,blessing,detection,sleepiness,inebriety]{if(contains_text(known,bp))continue;bcount+=1;bang=merge(bang,to_event("",get_bang(bp)))}if(bcount>1)bang=factor(bang,1.0/bcount);res.dmg=bang.dmg[$element[none]];if(bang.att!=0)res.special="att "+bang.att;if(bang.def!=0)res.special="def "+bang.att;return res}combat_rec get_sphere(string which){combat_rec res;res.dmg="0";res.dmgtypes="physical";switch(which){case"fire":res.dmg="9";res.dmgtypes="hot";return res;case"nature":res.special="att -2, def -2, stun 1";return res;case"lightning":res.dmg="9";return res;case"water":res.special="hp 4.5";return res}return res}float hitchance(int ts){float attack=my_buffedstat(weapon_type(equipped_item($slot[weapon])));switch(ts){case 0:attack=my_buffedstat($stat[moxie]);break;case 2:if(have_skill($skill[eye of the stoat]))attack+=20;case 3:if(my_class()==$class[seal clubber]&&item_type(equipped_item($slot[weapon]))=="club"&&weapon_hands(equipped_item($slot[weapon]))==2)return 1.0;if(ts==2)break;if(have_skill($skill[eye of the stoat]))attack*=1.25+0.05*to_int(my_class()==$class[seal clubber]);break;case 4:attack=my_buffedstat($stat[muscle])+20}return minmax((6.0+attack-max(monster_stat("def"),0.0))/10.5,0.0,1.0)*(1.0-fumble_chance())}spread regular(int ts){spread res;if(ts!=0&&last_monster()==$monster[a n-dimensional horror])return res;float ltsadj=(ts==3)?1.25+0.05*to_int(my_class()==$class[seal clubber]):(ts==5)?1.4:1.0;boolean ranged=(weapon_type(equipped_item($slot[weapon]))==$stat[moxie]&&ts>0);float radj=(ranged)?0.75:1.0;if(equipped_item($slot[weapon])==$item[none])radj=0.25+0.75*to_int(ts==0);res[$element[none]]=max(0,max(0,floor(my_buffedstat($stat[muscle])*ltsadj*radj)-monster_stat("def"))+max(1,0.15*get_power(equipped_item($slot[weapon]))+0.5)*1.09*max(1.0,ts)+numeric_modifier("Weapon Damage")+to_int(ranged)*numeric_modifier("Ranged Damage"))*(100+numeric_modifier("Weapon Damage Percent")+to_int(ranged)*numeric_modifier("Ranged Damage Percent"))/100;if(have_skill($skill[double-fisted])&&to_slot(equipped_item($slot[offhand]))==$slot[weapon])res[$element[none]]+=0.15*get_power(equipped_item($slot[offhand]))+0.5;if(ts>0)foreach el in $elements[]if(numeric_modifier(el+" Damage")>0)res[el]=numeric_modifier(el+" Damage");return res}float dam,rate;void build_items(float factor){foreach it,fields in factors["item"]{if(item_amount(to_item(it))==0)continue;rate=item_val(to_item(it))*to_int(!to_item(it).reusable||last_monster()==$monster[the naughty sorceress]||last_monster()==$monster[bonerdagon]);switch(it){case 3388:if(get_counters("Zombo's Empty Eye",0,50)!="")continue;break;case 2065:if(my_location()!=$location[battlefield(frat uniform)]||string_modifier("Outfit")!="Frat Warrior Fatigues"||get_counters("PADL Phone",0,10)!="")continue;case 2354:if(to_float(my_hp())/my_maxhp()<0.25)fields.special="hp 275";else if(to_float(my_mp())/my_maxmp()<0.25)fields.special="mp 175";else fields.dmg="175";if(to_item(it)==$item[PADL phone])break;if(my_location()!=$location[battlefield(hippy uniform)]||get_counters("Communications Windchimes",0,10)!=""||string_modifier("Outfit")!="War Hippy Fatigues")continue;break;case 3101:rate*=0.5;break;case 819:case 820:case 821:case 822:case 823:case 824:case 825:case 826:case 827:if(get_property("lastBangPotion"+it)==""){if(item_amount(to_item(it))>to_int(get_property("autoPotionID")=="false"))custom[count(custom)]=to_event("use "+it,get_bang(""));continue}fields=get_bang(get_property("lastBangPotion"+it));break;case 2174:case 2175:case 2176:case 2177:if(get_property("lastStoneSphere"+it)==""&&get_property("autoSphereID")=="true"){custom[count(custom)]=to_event("use "+it,get_sphere(""));continue}fields=get_sphere(get_property("lastStoneSphere"+it));break}fvars["itemamount"]=item_amount(to_item(it));dam=eval(fields.dmg,fvars);if(isseal&&dam>0)dam=min(1.0,dam);else if(have_equipped($item[v for vivala mask]))dam*=1.5;addopt(to_event("use "+it,to_spread(dam,fields.dmgtypes),fields.special),rate,factor)}}void build_skills(string page,float factor){if(count(factors["skill"])==0)return;vprint("1 MP costs "+meatpermp+" meat.",8);spread d;fvars["bonusdb"]=numeric_modifier("DB Combat Damage")+to_int(my_familiar()==$familiar[frumious bandersnatch])*0.75*(familiar_weight(my_familiar())+numeric_modifier("Familiar Weight"));int burrowgrub_amt(){return(1+to_int(have_effect($effect[yuletide mutations])>0))*min(my_level(),15)}foreach sk,fields in factors["skill"]{if(mp_cost(to_skill(sk))>my_mp())continue;if(!contains_text(page,"value=\""+sk+"\""))continue;fvars["elembonus"]=spell_elem_bonus(fields.dmgtypes);d=to_spread(0,"physical");switch(sk){case 7082:if(contains_text(page,"red eye")){d=to_spread((4.5-minmax(have_effect($effect[everything looks red]),0,2))*fvars["fweight"],"hot");if(have_effect($effect[everything looks red])>0)fields.special="stats "+(4.5*fvars["fweight"])+"-"+(4.5*fvars["fweight"])+"-"+(4.5*fvars["fweight"])}else if(contains_text(page,"blue eye")){if(have_effect($effect[everything looks blue])>0)d=to_spread(1.5*fvars["fweight"],"cold");else fields.special="stun 3"}else if(contains_text(page,"yellow eye")){if(have_effect($effect[everything looks yellow])>0)d=to_spread(0.75*fvars["fweight"],"physical");else d=to_spread(6*monster_hp(),"hot,cold,spooky,sleaze,stench,physical")}break;case 7074:if(my_maxhp()-my_hp()<=2*burrowgrub_amt()||my_maxmp()-my_mp()<=burrowgrub_amt())continue;break;case 7061:fvars["wpnpower"]=get_power(equipped_item($slot[weapon]));break;case 7003:case 7004:case 7005:case 7006:case 7007:fvars["breath"]=have_effect(to_effect(to_skill(sk)));case 7081:fvars["botcharges"]=get_property("bagOTricksCharges").to_int();default:dam=eval(fields.dmg,fvars);if(isseal&&dam>0)dam=max(1.0,dam);d=to_spread(dam,fields.dmgtypes);if(contains_text(fields.special,"regular"))switch(sk){case 7008:d=factor(regular(0),hitchance(0));break;case 1003:d=factor(regular(2),hitchance(2));break;case 1005:d=factor(regular(3),hitchance(3));break;case 7096:d=regular(5);break;case 7097:d=factor(regular(1),hitchance(1)*7);break;case 2015:case 2103:d=factor(merge(d,regular(1)),hitchance(4));break;default:d=merge(d,regular(1))}}advevent bander(int which){if(!smusted&&factors["bander",which].special.contains_text("smust"))return to_event("#bander",to_spread(0,"physical"),"stun 2");return to_event("#bander",factors["bander",which])}if(my_familiar()==$familiar[bandersnatch]&&factors["bander"]contains sk)addopt(merge(bander(sk),to_event("skill "+to_skill(sk),d,"mp -"+mp_cost(to_skill(sk))+", "+fields.special)),0,factor);else addopt(to_event("skill "+to_skill(sk),d,"mp -"+mp_cost(to_skill(sk))+", "+fields.special),0,factor)}}void build_options(string page){clear(opts);fvars["monsterhp"]=monster_stat("hp");fvars["monsterattack"]=monster_stat("att");fvars["buffedmys"]=my_buffedstat($stat[mysticality]);fvars["buffedmus"]=my_buffedstat($stat[muscle]);fvars["buffedmox"]=my_buffedstat($stat[moxie]);fvars["maxhp"]=my_maxhp();fvars["myhp"]=my_hp();fvars["mymp"]=my_mp();float factor=1.0-0.5*to_int(have_effect($effect[cunctatitis])>0);addopt(to_event("attack",factor(regular(1),hitchance(1)),"hp "+to_string(-fumble_chance()*fumble_damage())),0,factor);if(item_type(equipped_item($slot[weapon]))=="chefstaff"&&contains_text(page,"<form name=chefstaffform")&&factors["chef"]contains to_int(equipped_item($slot[weapon]))){if(isseal)factors["chef",to_int(equipped_item($slot[weapon]))].dmg="1";addopt(to_event("jiggle",factors["chef",to_int(equipped_item($slot[weapon]))]),0,1)}float ifactor=factor-0.5*to_int(my_familiar()==$familiar[black cat]);if(contains_text(to_string(last_monster()),"Naughty Sorceress")||last_monster()==$monster[bonerdagon])ifactor*=0.75;build_items(ifactor);if(contains_text(to_string(last_monster()),"Naughty Sorceress")||last_monster()==$monster[bonerdagon])factor*=0.5;build_skills(page,factor);int monster_kill_rounds(float dmg){if(dmg==0)return 999999;return ceil(to_float(monster_stat("hp"))/(dmg+m_hit_chance()*dmg_dealt(retal.dmg)+dmg_dealt(base.dmg)))}if(last_monster()==$monster[your shadow])foreach i,opt in opts if(opt.hp<=0)remove opts[i];sort opts by monster_kill_rounds(dmg_dealt(value.dmg))*max(1,-value.meat)}advevent stasis_action(){sort opts by-to_profit(value);sort opts by dmg_dealt(value.dmg);foreach i,opt in opts{if(contains_text(,"use ")&&!to_item(excise(,"use ","")).reusable)continue;return opt}return new advevent}advevent stun_action(boolean foritem){sort opts by-to_profit(value)/max(0.00001,value.stun-1+to_int(foritem&&have_skill($skill[funkslinging])&&contains_text(,"use ")));foreach i,opt in opts{if(opt.stun>1-to_int(foritem&&have_skill($skill[funkslinging])))return opt;else break}return new advevent}advevent get_action(string whichid){foreach i,opt in opts if( opt;return new advevent}advevent get_action(item it){return get_action("use "+to_int(it))}advevent get_action(skill sk){return get_action("skill "+to_int(sk))}string act(string action){round=to_int(excise(action,"var onturn = ",";"));if(round==0)round=maxround;if(!famspent)switch(my_familiar()){case $familiar[hobo monkey]:if(contains_text(action,"your shoulder, and hands you some Meat"))famspent=true;break;case $familiar[gluttonous green ghost]:if(!contains_text(action,"/ggg.gif")&&!contains_text(action,"You quickly conjure a saucy salve")){print("Your ghost is hongry.","olive");famspent=true}break;case $familiar[slimeling]:if(!contains_text(action,"/slimeling.gif")&&!contains_text(action,"You quickly conjure a saucy salve")){print("Your slimeling needs sating.","olive");famspent=true}break;case $familiar[spirit hobo]:if(contains_text(action,"Oh, Helvetica,")||contains_text(action,"millennium hands and shrimp.")){print("Your hobo is now sober.  Sober hobo sober hobo.","olive");famspent=true}break}if(my_familiar()==$familiar[bandersnatch]&&contains_text(action,"cloud of smust"))smusted=true;if(my_location()==$location[outside the club]&&contains_text(action,"You lazily wave your hands"))waved=true;foreach doodad,n in extract_items(action){stolen[doodad]+=n;if(contains_text(action,"grab something")||contains_text(action,"You manage to wrest")||(my_class()==$class[disco bandit]&&contains_text(action,"knocked loose some treasure."))){vprint_html("<span color='green'>You snatched a "+doodad+" ("+rnum(item_val(doodad))+" μ)!</span>",5);should_pp=vprint("Revised monster value: "+rnum(monstervalue()),"green",-4)}else vprint_html("<span color='green'>Look! You found "+n+" "+doodad+" ("+rnum(n*item_val(doodad))+" μ)!</span>",5)}if(have_equipped($item[bag o tricks]))switch{case(contains_text(action,"Bag o' Tricks begins squirming")):case(contains_text(action,"Bag o' Tricks continues")):set_property("bagOTricksCharges","3");break;case(contains_text(action,"Bag o' Tricks begins")):set_property("bagOTricksCharges","2");break;case(contains_text(action,"Bag o' Tricks suddenly feels")):set_property("bagOTricksCharges","1");break;case(contains_text(action,"a single fat bumblebee")):case(contains_text(action,"You open the bag and ")):set_property("bagOTricksCharges","0");break}if(last_monster()==$monster[mother slime]){if(contains_text(action,"ground trembles as Mother Slime shudders"))mres[$element[none]]=1.0;if(contains_text(action,"Veins of purple shoot"))mres[$element[sleaze]]=1.0;if(contains_text(action," a bluish tinge"))mres[$element[cold]]=1.0;if(contains_text(action,"skin becomes ashy and gray"))mres[$element[spooky]]=1.0;if(contains_text(action,"Mother Slime becomes decidedly more reddish"))mres[$element[hot]]=1.0;if(contains_text(action,"looks greener than she did a minute ago"))mres[$element[stench]]=1.0}build_options(action);if(my_location()==$location[slime tube]&&contains_text(action,"a massive loogie that sticks")&&equipped_item($slot[weapon])==$item[none])vprint("Your rusty weapon has been slimed!  Finish combat by yourself.",0);if(get_property("autoAntidote")!="0"){int poison_level(){if(have_effect($effect[Toad In The Hole])>0)return 1;if(have_effect($effect[Majorly Poisoned])>0)return 2;if(have_effect($effect[Really Quite Poisoned])>0)return 3;if(have_effect($effect[Somewhat Poisoned])>0)return 4;if(have_effect($effect[A Little Bit Poisoned])>0)return 5;if(have_effect($effect[Hardly Poisoned at All])>0)return 6;return 0}if(have_effect($effect[Duct Out of Water])>0&&poison_level()>1)return act(use_skill($skill[spew poison]));if(poison_level()>0){vprint("Poison level: "+poison_level()+" (set to remove if "+get_property("autoAntidote")+" or lower)","olive",2);if(item_amount($item[anti-anti-antidote])>0&&poison_level()<=to_int(get_property("autoAntidote")))return act(throw_item($item[anti-anti-antidote]));}}if(item_amount($item[molybdenum magnet])>0&&contains_text(to_string(last_monster()),"Gremlin")&&!contains_text(action,"macroaction: use 2497")&&contains_text(action,"whips")&&(contains_text(action,"a hammer")||contains_text(action,"a crescent wrench")||contains_text(action,"pliers")||contains_text(action,"a screwdriver")))return act(throw_item($item[molybdenum magnet]));if(have_equipped($item[ruby rod])&&my_location()==$location[seaside megalopolis]&&(contains_text(action,"A mechanical hand emerges")||contains_text(action,"liquid nitrogen")||contains_text(action,"freaky alien thing")||contains_text(action,"vile-smelling, milky-white")||contains_text(action,"spinning, whirring, vibrating, tubular")))return act(attack());if(have_equipped($item[fouet de tortue-dressage])&&my_location()==$location[outer compound]&&(contains_text(action,"frenchturtle.gif")||get_property("lastNemesisReset").to_int()==my_ascensions())&&my_mp()>=5*mp_cost($skill[apprivoisez la tortue])){return visit_url("fight.php?action=macro&macrotext=skill 7083; repeat")}if(my_class()==$class[disco bandit]&&!waved&&have_skill($skill[gothy handwave])&&my_mp()>0){switch(last_monster()){case $monster[breakdancing raver]:if(!have_skill($skill[break it on down])&&!contains_text(action,"macroaction: skill 49")&&contains_text(action,"he raver drops "))return act(use_skill($skill[gothy handwave]));break;case $monster[pop-and-lock raver]:if(!have_skill($skill[pop and lock it])&&!contains_text(action,"macroaction: skill 49")&&contains_text(action,"movements suddenly became spastic and jerky."))return act(use_skill($skill[gothy handwave]));break;case $monster[running man]:if(!have_skill($skill[run like the wind])&&!contains_text(action,"macroaction: skill 49")&&contains_text(action,"The raver turns "))return act(use_skill($skill[gothy handwave]))}}if(my_familiar()==$familiar[he-boulder]&&have_effect($effect[everything looks yellow])==0&&contains_text(action," yellow eye")&&contains_text(to_lower_case(vars["ftf_yellow"]),to_lower_case(last_monster().to_string())))return act(use_skill($skill[point at your opponent]));return action}string batround(){buffer res;res.append("abort match effect*Poisoned || match effect*Toad || match \"a massive loogie\" || hpbelow "+round(m_dpr(0,0))+"; scrollwhendone; sub batround");if(item_amount($item[molybdenum magnet])>0&&contains_text(to_string(last_monster()),"Gremlin"))res.append("; if match whips && (match \"a hammer\" || match \"a crescent wrench\" || match pliers || match \"a screwdriver\"); item 2497; endif");if(have_equipped($item[ruby rod])&&my_location()==$location[seaside megalopolis])res.append("; if match \"A mechanical hand emerges\" || match \"liquid nitrogen\" || match \"freaky alien thing\" || "+"match \"vile-smelling, milky-white\" || match \"spinning, whirring, vibrating, tubular\"; attack; endif");if(my_class()==$class[disco bandit]&&have_skill($skill[gothy handwave])&&!waved)switch(last_monster()){case $monster[breakdancing raver]:if(!have_skill($skill[break it on down]))res.append("; if match \"he raver drops \"; skill 49; endif");break;case $monster[pop-and-lock raver]:if(!have_skill($skill[pop and lock it]))res.append("; if match \"movements suddenly became spastic and jerky.\"; skill 49; endif");break;case $monster[running man]:if(!have_skill($skill[run like the wind]))res.append("; if match \"The raver turns \"; skill 49; endif")}if(my_familiar()==$familiar[he-boulder]&&have_effect($effect[everything looks yellow])==0&&contains_text(to_lower_case(vars["ftf_yellow"]),to_lower_case(last_monster().to_string())))res.append("; if match \" yellow eye\"; skill 7082; endif");return res+"; endsub; "}string macro(string mac){return act(visit_url("fight.php?action=macro&macrotext="+url_encode(mac),true,true))}string macro(advevent todo,string rep){if(rep=="0")return macro(batround()"; call batround");return macro(batround()+"sub main; ""; call batround; endsub; call main; repeat "+rep)}string macro(advevent todo){return macro(todo,"0")}advevent[int]queue;string macro(){buffer m;m.append(batround());foreach i,o in queue m.append("; call batround; ");if(have_skill($skill[funkslinging])){matcher funky=create_matcher("(use \\d+); call batround; use (\\d+;)",m);while(funky.find())m.replace_string(,","}vprint("Executing macro: "+m,9);return macro(to_string(m))}setvar("BatMan_macrofy",false);setvar("BatMan_profitforstasis",15.0);setvar("BatMan_baseSubstatValue",5.0);


@Bale: That was a great explanation. (Although I think of get_action() as a simple map lookup -- there are no conversions happening in that function. But that's purely academic.) I would also avoid documenting macro(advevent,"0") -- the string there specifies (the) repeat condition(s) in BALLS. There is a single parameter version to simply perform an action once.

The functions in BatBrain are, naturally, still evolving. The focus until now has been making all combat information available and accurate. The focus henceforth will be making that information easily usable -- which I've been figuring out by working on the new SS. The various get_action() functions were just added yesterday. SmartStasis will soon be using these functions rather than adding raw data to the custom actions list.

Once the functions have stabilized, I'll throw BatBrain up on the ASH Wiki as a resource for combat scripters.

I like your tweak to macro() -- will probably add it.


I think of get_action() as a simple map lookup -- there are no conversions happening in that function. But that's purely academic.)

Yup. I got that. However it was tough to really get my mind around the effects of get_action() until I reframed it that way. Then suddenly it became easy to use.

I would also avoid documenting macro(advevent,"0") -- the string there specifies (the) repeat condition(s) in BALLS. There is a single parameter version to simply perform an action once.

Ah. So there is! Just leave off the repeat parameter if you don't want to repeat! I won't document that again.

I like your tweak to macro() -- will probably add it.

Yay! I'm a superstar! I only spent a little over an hour studying BatBrain and I've already managed to improve it!


Active member
That's not easier :)
Also, do you realy use three spaces as indentation or is it Notepad++ that converts something else into that? In my mind a simple tab would be much easier to do than three spaces...


That's zarqon who uses three spaces, not me. I was only copying his method.

I've got my own very simple consult script called FinalAttack.ash which I've been using to conclude a fight. (My version of spamattack.) I just modified it to use BatBrain. I'm not trying to be particularly optimal, I'm just trying to conclude a combat with any of my characters. (I sometimes edit it on the fly when I'm playing.) I am interested in zarqon's commentary about if I used BatBrain properly.

import "BatBrain.ash";

advevent dangeraction() {
	if(my_primestat() == $stat[moxie])
		return get_action($skill[moxious maneuver]);
	else if(item_type(equipped_item($slot[off-hand])) == "shield" && have_skill($skill[Shieldbutt]))
		return get_action($skill[Shieldbutt]);
	else if(have_skill($skill[Saucegeyser]))
		return get_action($skill[Saucegeyser]);
	return get_action("attack");

void main(int initround, monster foe, string page) {
	page = act(page);
	switch(foe) {
	case $monster[Candied Yam Golem]:
	case $monster[Malevolent Tofurkey]:
	case $monster[Possessed Can of Cranberry Sauce]:
	case $monster[Stuffing Golem]:
	case $monster[Inebriated Tofurkey]:
	case $monster[Hammered Yam Golem]:
	case $monster[Plastered Can of Cranberry Sauce]:
	case $monster[Soused Stuffing Golem]:
	case $monster[El Novio Cadáver]:
	case $monster[El Padre Cadáver]:
	case $monster[La Novia Cadáver]:
	case $monster[La Persona Inocente Cadáver]:
	case $monster[angry bassist]:
	case $monster[blue-haired girl]:
	case $monster[evil ex-girlfriend]:
	case $monster[peeved roommate]:
	case $monster[random scenester]:
		if(have_skill($skill[entangling noodles]))
			page = macro(get_action($skill[entangling noodles])); 
		page = macro(dangeraction(), ""); 
		page = macro(get_action("attack"), "");


Active member
So... trying this out. Would:
rounds = monster_stat("hp") / max(1,dmg_dealt(get_action("attack").dmg)) / hitchance(1)
be the number of rounds it would take me to kill the monster taking my chance to hit ti into account?


There is a minor error on line 132 of the new version posted above - it's got 9,5 instead of what I assume should be 9.5


Active member
So, new version of the script. Found some small errors of placing in the spell damage formulas. This version does not use batbrain at all due to unexpected results in some ways and so I've reverted that for now :)

Edit: It also fixes the 9,5 vs. 9.5 error. It does so by instead having 11 there :)


Well-known member
@Winterbay: hehehehehehehe

You're mostly correct, but you don't need the max(1,dmg) -- that's already taken into account in dmg_dealt(). Also, your hit chance is already included in the attack event.

To be entirely correct, you need to access a couple important advevents in BatBrain -- baseround() and retal. The baseround() event includes things that happen every round, such as passive damage and your familiar's attacks (even from the CoT). The retal event happens whenever the monster hits you and includes things like Saucespheres and pointy accessories. Here's the "very correct" way:

killrounds = ceil(monster_stat("hp") / (dmg_dealt(get_action("attack").dmg) + m_hit_chance()*dmg_dealt(retal.dmg) + dmg_dealt(baseround().dmg)))

Or, taking advantage of BatBrain's merge() and factor() functions for doing arithmetic with advevents:

killrounds = ceil(to_float(monster_stat("hp")) / dmg_dealt(merge(get_action("attack"), factor(retal, m_hit_chance()), baseround()).dmg))

The first one is actually more accurate, however, since if the monster has resistances the multiple sources of damage will all be reduced, yet they will still add up to more than one.

@Bale: well done; your example has convinced me to add the tweak you suggested right now. The alternative in your above example would be to add noodles to the queue and then spam the queue with dangeraction, then call macro(), which is slightly inelegant in that it ignores the capability of BALLS to spam actions on your behalf.

I'm also going to clear the queue in macro() after executing it, so you need not bother clearing it anymore.

As far as whether you're using BatBrain correctly, yes. However, you are making unnecessary checks. If get_action() returns an empty event, the skill is unavailable. Otherwise, the skill is available. This will do the same as you posted:

advevent dangeraction() {
   foreach da in $strings[skill 7008, skill 2005, skill 4012, attack]
      if (get_action(da).id != "") return get_action(da);
   return new advevent;    // you should never reach this

I suppose if my feature request is implemented that undisambiguates/ambiguates your various shadows, you could reach the last line when fighting your shadow, because there's a line to remove all options that don't restore HP when facing your shadow -- but presently last_monster() can never equal $monster[your shadow] so that's a future concern. And who would automate combat against your shadow anyway?

(I would! *cackles wickedly*)


Active member
zarqon: I did put in that max(1,dmg) since the script died from a divide by zero-error when I didn't. I'll have to look more into your suggestion when I get back home.