View Full Version : BatBrain -- a central nervous system for consult scripts
zarqon
11-05-2010, 08:39 PM
BatBrain 1.23
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 absolutely 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) {
act(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!
<will write more description here shortly -- selected function list, applications>
<data file format and keywords will go in separate post>
Changelog
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 m_event() 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 rounds = 0 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. n 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 my_stat() 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 to_profit(). 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. eval() substats rather than to_int()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 unarmed() 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 attack_action() 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 kill_rounds(), but not monster_stat(), assume the pessimistic side of monsters' 5% HP variance, capped at +5. Convert page 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 all 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 attack_action(), 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 set_monster() command, thus making BB useful speculatively outside of combat. If you don't initialize BB it will initialize to last_monster() automatically in act(). 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 act() 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 enqueue() and macro() 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 to_profit() much less expensive, and refactor detections/responses in both act() and batround() 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 attack_action(). 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 attack_action(). Continue to cache profit in the profit field of advevents, but always recalculate in to_profit(). 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, my_turncount() 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 kill_rounds() 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 stun_action() sort. Use recent effect() 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.
Winterbay
04-13-2011, 12:11 PM
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 (http://kolmafia.us/showthread.php?5234-Spam-attack-and-save-servers&p=48473&viewfull=1#post48473)) 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 (http://kolmafia.us/showthread.php?5234-Spam-attack-and-save-servers) 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.
Theraze
04-13-2011, 06:38 PM
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.
zarqon
04-14-2011, 03:53 AM
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!
Winterbay
04-14-2011, 06:34 AM
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.
Winterbay
04-14-2011, 06:49 AM
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.
zarqon
04-14-2011, 07:00 AM
@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.
Winterbay
04-14-2011, 07:27 AM
@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.clear();
queue[count(queue)] = get_action($skill[Stringozzi Serpent]);
queue[count(queue)] = get_action($skill[Weapon of the Pastalord]);
macro()
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;
m.append(batround());
if(first != "") m.append(first+"; ");
foreach i,o in queue m.append(o.id+"; 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(funky.group(0),funky.group(1)+","+funky.group(2));
}
vprint("Executing macro: "+m,9);
if(last != "") m.append(last+"; ");
return macro(to_string(m));
}
string macro() { return macro("", ""); }
Winterbay
04-14-2011, 08:11 AM
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 :)
zarqon
04-14-2011, 08:36 AM
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()==$mo nster[none]||monster_attack(last_monster())==monster_level_ad justment()){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.id="base";retal.id="retal";onhit.id="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.id=fir.id+"; "+sec.id;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.mp=fir. mp+sec.mp;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;a.mp*=fact;a.meat*=fa ct;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.id=id;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":res.mp=eval(excise(bit," ",""),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))}re s.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.dmgt ypes),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]:base.mp+=0.25*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_prope rty("_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_modifie r()+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.rat e*(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))}}retur n 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.repla ce_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(r ng.group(0),rng.group(1));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_sk ill(in)==$skill[weapon of the pastalord]&&!contains_text(factors[ty,in].dmgtypes,"physical"))factors[ty,in].dmgtypes+=",physical"}}advevent famevent(){advevent res;res.id=to_string(my_familiar());if(famspent)re turn 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())+8 2==to_int(my_familiar()));if(my_familiar()==$famil iar[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()),factor s["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(rate.group(1),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;res.id="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_fam iliar()){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,fameve nt(),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.dmgty pes))+" 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))}fore ach eq,uip in factors["gear"]{if(!have_equipped(to_item(eq)))continue;vprint_ht ml("Factoring in "+to_item(eq)+": "+to_html(to_spread(eval(uip.dmg,fvars),uip.dmgtype s))+" damage, "+uip.special,4);if(contains_text(uip.special,"retal"))retal=merge(retal,to_event(to_item(eq),uip));els e if(contains_text(uip.special,"onhit"))onhit=merge(onhit,to_event(to_item(eq),uip));els e 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(a.id!=to_string(my_familiar())&&!a.id.contains_text("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(a.mp,-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(a.id,"use ")):res.append("Throw "+excise(a.id,"use ",""));break;case(contains_text(a.id,"skill ")):res.append("Cast "+excise(a.id,"skill ",""));break;case(a.id=="attack"):res.append("Attack with weapon");break;case(a.id=="jiggle"):res.append("Jiggle Your Chefstaff");break;default:res.append(a.id)}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(a.mp!=0)res.append(" <span color='blue'>MP: "+rnum(a.mp)+"</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;ban g=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($sl ot[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||la st_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,fvar s);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_mod ifier("Familiar Weight"));int burrowgrub_amt(){return(1+to_int(have_effect($effe ct[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(i sseal&&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;cas e 2015:case 2103:d=factor(merge(d,regular(1)),hitchance(4));br eak;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(ite m_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_te xt(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(opt.id,"use ")&&!to_item(excise(opt.id,"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(value.id,"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.id==whichid)return 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()==$loca tion[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¯otext=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())))retur n 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.a ppend("; if match \" yellow eye\"; skill 7082; endif");return res+"; endsub; "}string macro(string mac){return act(visit_url("fight.php?action=macro¯otext="+url_encode(mac),true,true))}string macro(advevent todo,string rep){if(rep=="0")return macro(batround()+todo.id+"; call batround");return macro(batround()+"sub main; "+todo.id+"; 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(o.id+"; call batround; ");if(have_skill($skill[funkslinging])){matcher funky=create_matcher("(use \\d+); call batround; use (\\d+;)",m);while(funky.find())m.replace_string(funky.grou p(0),funky.group(1)+","+funky.group(2))}vprint("Executing macro: "+m,9);return macro(to_string(m))}setvar("BatMan_macrofy",false);setvar("BatMan_profitforstasis",15.0);setvar("BatMan_baseSubstatValue",5.0);
:D
@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!
Winterbay
04-14-2011, 08:54 AM
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(), "");
break;
default:
page = macro(get_action("attack"), "");
}
}
Winterbay
04-14-2011, 12:54 PM
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?
tebee
04-14-2011, 01:26 PM
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
Winterbay
04-14-2011, 02:02 PM
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 :)
zarqon
04-14-2011, 02:33 PM
@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*)
Winterbay
04-14-2011, 02:36 PM
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.
zarqon
04-14-2011, 02:38 PM
Were you checking to make sure your get_action() results weren't empty? It will return an empty event if the action is not in your available combat options.
Winterbay
04-14-2011, 08:27 PM
Isn't attacking always an option? Or is it disregarded if you cannot hit the monster?
I'm now invoking my powers as a Minion.
Please note that this thread has a new first post.
@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.
Thank you. I was really trying to find a way to write this with the macro queue before I gave up and did it that way. Briefly I had a version that queued up noodles and dangeraction with macro(), then spammed dangeraction in case the first dangeraction. It was... inelegant.
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
}
Oh. Oh wow. So I can count on BatBrain to recognize if a skill (like shieldbutt) is presently available! That's sweet.
Winterbay
04-14-2011, 09:15 PM
Good initiative Bale. I quite like the forum software that allows you to do this :)
Theraze
04-15-2011, 02:07 PM
Managed to get negative rounds-to-kill count using the spamattack posted (currently) in thread 18, which sometimes ends in a division by zero error.
Regarding how I got to that point, using a ranged weapon as a myst class to clean up the easy mobs, not a muscle weapon, which might be part of why your calculation line is getting off... since you don't have either the 3/4ths damage for ranged weapons being calculated, or the +ranged damage if weapon stat == moxie.
Not sure exactly what's causing it, but BatBrain gives my expected damage above 10, while spamattack suggests I'll do 2...
> ash import <batbrain> regular(1);
Returned: aggregate float [element]
none => 10.355
> ash import <zlib> monster foe = to_monster(get_property("lastMonster")); float total_damage = min(floor(my_buffedstat($stat[muscle])) - monster_defense(foe), 0) + min(0.1 * get_power(equipped_item($slot[weapon])),1) + numeric_modifier("Weapon Damage") * (1 + to_float(numeric_modifier("Weapon Damage Percent"))/100.0) + min(0.1 * get_power(equipped_item($slot[offhand])),1);
Returned: 2.0 Believe the problem MAY be the various min... that means that you can never go higher than 0 for your muscle bonus-damage, can never have a weapon power above 1, and your offhand item (if it happens to be a weapon) can never go above 1... yeah, looks like that's probably the issue.
So, here's a line that should work better.
total_damage = (max(floor((my_buffedstat($stat[muscle])) * (weapon_type(equipped_item($slot[weapon])) == $stat[moxie] ? .75 : 1)) - monster_defense(foe), 0) + max(0.1 * get_power(equipped_item($slot[weapon])),1) + numeric_modifier("Weapon Damage") + (weapon_type(equipped_item($slot[weapon])) == $stat[moxie] ? numeric_modifier("Ranged Damage") : 0)) * (1 + to_float(numeric_modifier("Weapon Damage Percent"))/100.0) + (to_slot(equipped_item($slot[offhand])) == $slot[weapon] ? max(0.1 * get_power(equipped_item($slot[offhand])),1) : 0);
Comparison of after/before:
> ash import <zlib> monster foe = to_monster("caustic bull"); int total_damage = (max(floor((my_buffedstat($stat[muscle])) * (weapon_type(equipped_item($slot[weapon])) == $stat[moxie] ? .75 : 1)) - monster_defense(foe), 0) + max(0.1 * get_power(equipped_item($slot[weapon])),1) + numeric_modifier("Weapon Damage") + (weapon_type(equipped_item($slot[weapon])) == $stat[moxie] ? numeric_modifier("Ranged Damage") : 0)) * (1 + to_float(numeric_modifier("Weapon Damage Percent"))/100.0) + (to_slot(equipped_item($slot[offhand])) == $slot[weapon] ? max(0.1 * get_power(equipped_item($slot[offhand])),1) : 0);
Returned: 85
> ash import <zlib> monster foe = to_monster("caustic bull"); int total_damage = min(floor(my_buffedstat($stat[muscle])) - monster_defense(foe), 0) + min(0.1 * get_power(equipped_item($slot[weapon])),1) + numeric_modifier("Weapon Damage") * (1 + to_float(numeric_modifier("Weapon Damage Percent"))/100.0) + min(0.1 * get_power(equipped_item($slot[offhand])),1);
Returned: 2 This also accounts for whether or not the item in the second hand is a weapon, if you are using ranged damage so your muscle bonus is only 75%, and that when using ranged damage, you also get bonuses from the 'ranged damage' modifier. Also, by adding more parenthesis, weapon damage percent should modify the entire weapon damage stack, not just the 'weapon damage' modifier.
Winterbay
04-15-2011, 10:18 PM
Managed to get negative rounds-to-kill count using the spamattack posted (currently) in thread 18, which sometimes ends in a division by zero error.
Regarding how I got to that point, using a ranged weapon as a myst class to clean up the easy mobs, not a muscle weapon, which might be part of why your calculation line is getting off... since you don't have either the 3/4ths damage for ranged weapons being calculated, or the +ranged damage if weapon stat == moxie.
Not sure exactly what's causing it, but BatBrain gives my expected damage above 10, while spamattack suggests I'll do 2...Believe the problem MAY be the various min... that means that you can never go higher than 0 for your muscle bonus-damage, can never have a weapon power above 1, and your offhand item (if it happens to be a weapon) can never go above 1... yeah, looks like that's probably the issue.
So, here's a line that should work better.
total_damage = (max(floor((my_buffedstat($stat[muscle])) * (weapon_type(equipped_item($slot[weapon])) == $stat[moxie] ? .75 : 1)) - monster_defense(foe), 0) + max(0.1 * get_power(equipped_item($slot[weapon])),1) + numeric_modifier("Weapon Damage") + (weapon_type(equipped_item($slot[weapon])) == $stat[moxie] ? numeric_modifier("Ranged Damage") : 0)) * (1 + to_float(numeric_modifier("Weapon Damage Percent"))/100.0) + (to_slot(equipped_item($slot[offhand])) == $slot[weapon] ? max(0.1 * get_power(equipped_item($slot[offhand])),1) : 0);
Comparison of after/before:This also accounts for whether or not the item in the second hand is a weapon, if you are using ranged damage so your muscle bonus is only 75%, and that when using ranged damage, you also get bonuses from the 'ranged damage' modifier. Also, by adding more parenthesis, weapon damage percent should modify the entire weapon damage stack, not just the 'weapon damage' modifier.
Well, yes. It's mainly programmed for me and as such it uses muscle as attack stat :)
This version uses batbrain and does so in a way that avoids division by zero which shows up in there as well when the expected damage I can do with an attack is 0, it should also work with moxie weapons (or at least I think so since batbrain probably takes care of that...).
Edit: The old version also had a parenthesis error in where it put the floor and does not take elemental damage bonuses into account.
Winterbay
04-17-2011, 09:29 PM
Ok, so double posting here, but since the last one is rather old...
Is there a way to set the result of "last_monster()" to anything but the last monster? I wonder because if possible that would let me use batbrain to predict what type of damage I can do against a certain monster without having to fight it first. My consult script does this to a certain extent by allowing the use of the (foe)-versions of the different monster functions to adapt my numbers based on a given monster, but since batbrain seems useful I'd like to be able to incorporate more of that into my script, preferably without losing that ability.
Theraze
04-17-2011, 10:40 PM
Doesn't look like there's anything in RuntimeLibrary that will do SetNextMonsterName...
I think you need to make a request for zarqon to overload BatBrain's functions for predictive usage. Though, the way he has it set up he'll have to refactor a lot of the code to implement.
Theraze
04-18-2011, 01:10 AM
Well, might be able to just have a declaration like
monster my_monster = last_monster();and then replace all of the occurances of last_monster() with my_monster... then if you want to check on a different monster, just declare
my_monster = $monster[Something Else];in your BatBrain execution/alias...
Currently my FinalAttack.ash looks like this:
import "BatBrain.ash";
advevent dangeraction() {
// Check moxious maneuver, shieldbutt, saucegeyser, attack
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
}
boolean can_splash() {
if(!have_skill($skill[Wave of Sauce]) || !have_skill($skill[Saucegeyser])
|| !(have_effect($effect[Jabañero Saucesphere]) >0 || have_effect($effect[Jalapeño Saucesphere]) >0))
return false;
if(dmg_dealt(get_action($skill[Wave of Sauce]).dmg) >= monster_stat("hp")) return false;
int normal = numeric_modifier("spell damage");
if(normal >= 25)
return true;
int cold = numeric_modifier("cold spell damage");
int hot = numeric_modifier("hot spell damage");
if(have_skill($skill[Immaculate Seasoning]) && cold != hot && max(cold, hot) + normal >= 25)
return true;
if(monster_element() == $element[cold] && cold + normal >= 25
&& (have_skill($skill[Immaculate Seasoning]) || have_equipped($item[Gazpacho's Glacial Grimoire])))
return true;
if(monster_element() == $element[hot] && hot + normal >= 25
&& (have_skill($skill[Immaculate Seasoning]) || have_equipped($item[Codex of Capsaicin Conjuration])
|| have_equipped($item[Ol' Scratch's manacles]) ||(have_equipped($item[Ol' Scratch's ash can]) && my_class() == $class[Sauceror])))
return true;
return false;
}
string splashSauce() {
queue[count(queue)] = get_action($skill[Wave of Sauce]);
queue[count(queue)] = get_action($skill[Saucegeyser]);
return macro("mark spashStart", "goto SpashStart");
}
void main(int initround, monster foe, string page) {
page = act(page);
switch(foe) {
case $monster[a.m.c. gremlin]:
foreach da in $items[divine champagne popper, tattered scrap of paper]
if(get_action(da).id != "") {
page = get_action(da);
return;
}
break;
case $monster[clingy pirate]:
if(get_action($item[cocktail napkin]).id != "") {
page = macro(get_action($item[cocktail napkin]));
return;
}
break;
case $monster[mother hellseal]:
if(get_action($skill[entangling noodles]).id != "")
queue[count(queue)] = get_action($skill[entangling noodles]);
queue[count(queue)] = get_action($skill[lunging thrust-smack]);
page = macro("", "repeat");
break;
case $monster[naughty sorority nurse]:
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(can_splash())
page = splashSauce();
else {
if(have_skill($skill[entangling noodles]))
queue[count(queue)] = get_action($skill[entangling noodles]);
queue[count(queue)] = dangeraction();
page = macro("", "repeat");
}
return;
// In Fernswarthy's Basement
case $monster[the ghost of fernswarthy n great-grandfather]:
case $monster[a n stone golem]:
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]:
}
switch(my_location()) {
case $location[convention hall lobby]:
queue[count(queue)] = get_action($skill[entangling noodles]);
queue[count(queue)] = get_action($skill[wave of sauce]);
queue[count(queue)] = get_action($skill[candyblast]);
queue[count(queue)] = get_action($item[bottle of Gü-Gone]);
page = macro("", "repeat");
break;
default:
if(can_splash())
page = splashSauce();
else page = macro(get_action("attack"), "");
}
}
Are there ways to use BatBrain to simplify can_splash()? (Also, obviously my convention hall section needs some logic. I simply copied the current contents of my CCS.)
I'm not sure you're taking into account +ML. I'm currently running +62ML and fighting in the library with an ice sickle and a pilgrim shield (buffed muscle of 58) and the script is trying to futilely attack
Winterbay
04-19-2011, 02:14 PM
kain: Which script is this? If it's my spamattack-script then no it isn't, at least not for muscle and moxie classes where it's basically just an "attack; repeat;"-macro. For myst it should take ML into account since that is the only one that is at least somewhat advanced.
My fault there, I was playing around with the script that Bale posted directly above my post (post 31)
Winterbay
04-19-2011, 02:30 PM
Right :)
I think that if you are in the library you will end up in this part:
default:
if(can_splash())
page = splashSauce();
else page = macro(get_action("attack"), "");
Unless I'm reading that part wrong I think it'll only attack if you do not have enough +spell damage to manage a splash (or do not have the skills to splash)
man, that's so obvious I should have seen that. Guess I need some MOAR COFFEE!
Yeah, that little consult script isn't trying to be clever. It's just a simple script for muscle or moxie classes who aren't in over their ML. I don't have a lot of incentive to be more clever since I know that I'll be blown away by BatMan when it is complete.
Perhaps I'll eventually add some checks for ability to hit the current monster, but this script is not a complete monster killing solution and I'm not trying to make that script.
Continuing to refine my understanding of BatBrain I was able to improve my detection of when to splash sauce spells for awesomeness.
boolean can_splash() {
if(my_class() != $class[sauceror] || !have_skill($skill[Wave of Sauce]) || !have_skill($skill[Saucegeyser])
|| !(have_effect($effect[Jabañero Saucesphere]) >0 || have_effect($effect[Jalapeño Saucesphere]) >0)
|| dmg_dealt(get_action($skill[Wave of Sauce]).dmg) >= monster_stat("hp"))
return false;
switch(normalize_dmgtype("sauce")) {
case "hot": return numeric_modifier("spell damage") + numeric_modifier("hot spell damage") >= 25;
case "cold": return numeric_modifier("spell damage") + numeric_modifier("cold spell damage") >= 25;
}
return numeric_modifier("spell damage") >= 25;
}
BatBrain's normalize_dmgtype() is very nice for figuring out what sort of damage a "sauce" or "pasta" spell will do.
Winterbay
04-21-2011, 08:24 AM
BatBrain's normalize_dmgtype() is very nice for figuring out what sort of damage a "sauce" or "pasta" spell will do.
Ahh. I was wondering how it went from pasta or sauce to anything useful. I'll have to look into that :)
zarqon
04-21-2011, 05:09 PM
That would work for last_monster(), but not for monster stats, a much more significant part of combat. These change during combat and need to be accessed using, for instance, monster_hp() rather than monster_hp(monster). However, this is part of the reason for BatBrain to use a wrapper function for monster stats. It would be fairly easy to use versions with parameters if last_monster() != foe.
Making BatBrain speculative outside of combat is something I'm hoping to do, but first I want to make it work in combat. It already has the flexibility to speculate in combat, although this needs to be improved with regard to player stats. Once it can do that well, I think we can start to trust its predictions.
Also, Bale: haven't done a thing with splashback yet -- will probably gank some of that when I do, thanks.
Winterbay
04-21-2011, 05:19 PM
Yeah, I've changed all (or almost all) instances of monster_stat() functions with
(my_monster != last_monster()) ? monster_stat(my_monster) : monster_stat() and defined my_monster as last_monster(), so IF I want to test something at least I only need to change one thing in the script :)
zarqon
04-21-2011, 06:19 PM
Much easier to change in monster_stat() itself, no?
Winterbay
04-21-2011, 06:58 PM
Well, yes. But with monster_stat I meant all instances of monster_hp, monster_defense and monster_attack as well as monster_element which are used on various places in the script. The script also uses last_monster() for certain checks in the script and I changed those as well.
My finishing attack script has evolved some more in a direction I find troubling. Quite frankly BatBrain's macro building commands aren't as flexible as I'd like so I find myself calling macro(string mac) directly, like this:
string dangermacro() {
string act = batround();
if(get_action($skill[entangling noodles]).id != "")
act += get_action($skill[entangling noodles]).id+"; call batround; ";
act += "sub main; "+dangeraction().id+"; call batround; endsub; call main; repeat";
return macro(act);
}
That does exactly what I want which the other macro() commands just cannot do. I often want to execute a sequence of commands (or perhaps just entangling noodles) followed by a repeating finisher.
I was happy to figure out that hitchance(1) will tell me if "attack" is likely to hit. I've used that to trigger my danger action. I love BatBrain's build in functions!
It looks like conditions like this one are often checked:
if(get_action($skill[entangling noodles]).id != "")
Maybe something like this would save some typing ?
boolean can_use( string whichid) { return (get_action(whichid).id != ""); }
boolean can_use( item it ) { return (get_action(it).id != ""); }
boolean can_use( skill sk ) { return (get_action(sk).id != ""); }
I'm probably completely off though, I haven't delved into batbrain enough yet.
Actually, that sounds like a good idea to me slyz. I use that a lot. Here's another section I just wrote for my FinalAttack:
string mac = batround();
case $location[8-Bit Realm]:
int dam = m_regular() + (item_amount($item[blue shell]) > 0? 25: 0);
if(get_action($skill[entangling noodles]).id != "" && dam > my_hp()) {
mac += get_action($skill[entangling noodles]).id+"; call batround; ";
dam -= m_regular();
}
if(get_action($item[blue shell]).id != "" && my_hp() > dam)
mac += get_action($item[blue shell]).id+"; call batround; ";
mac += "sub main; "+get_action("attack").id+"; call batround; endsub; call main; repeat";
return macro(mac);
zarqon
04-22-2011, 03:52 AM
Bale, how is what you're doing different from queueing the actions, and adding the repeating finisher as the postact parameter of macro(string, string)? You're doing unnecessary work again. :)
@slyz: The main idea behind opts is that it is a sortable map of all your combat actions. You should be able to sort it by the qualities you want in an action, and perform the top action in the list. Need to restore X mp? Sort opts by -profit and then by -min(X,value.mp). I didn't want to go in a piecewise direction with it. I'll hold off on those functions for a bit. As far as I'm concerned, BatBrain is not ready for public use and this thread is premature.
Bale, how is what you're doing different from queueing the actions, and adding the repeating finisher as the postact parameter of macro(string, string)? You're doing unnecessary work again. :)
LOL! I'm an idiot! How's this look?
string dangermacro() {
if(get_action($skill[entangling noodles]).id != "")
queue[count(queue)] = get_action($skill[entangling noodles]);
return macro("", "sub main; "+dangeraction().id+"; call batround; endsub; call main; repeat");
}
And this?
case $location[8-Bit Realm]:
int dam = m_regular() + (item_amount($item[blue shell]) > 0? 25: 0);
if(get_action($skill[entangling noodles]).id != "" && dam > my_hp()) {
queue[count(queue)] = get_action($skill[entangling noodles]);
dam -= m_regular();
}
if(get_action($item[blue shell]).id != "" && my_hp() > dam)
queue[count(queue)] = get_action($item[blue shell]);
return macro("", "sub main; "+get_action("attack").id+"; call batround; endsub; call main; repeat");
Actually, there is some greater flexibility to writing this way. Time to refactor some code!
Refactoring complete! I have now successfully compressed all my CCSs into three lines.
[ default ]
consult SmartStasis.ash
consult FinalAttack.ash
FinalAttack.ash is not perfect, but it does contain all the information I was previously using in several CCSs. And it is so darn tidy! Check out spamAttack at the top -- it brings consult macro development full circle back to the beginning.
import "BatBrain.ash";
string spamAttack(advevent finisher) {
if(finisher.id == "") abort("Invalid finishing attack!");
return macro("", "sub main; "+finisher.id+"; call batround; "+"endsub; call main; repeat");
}
advevent dangeraction() {
// Check shieldbutt, moxious maneuver, saucegeyser, attack
foreach da in $strings[skill 2005, skill 7008, skill 4012, attack]
if(get_action(da).id != "") return get_action(da);
return new advevent; // you should never reach this
}
string splashSauce() {
string preact = "mark SauceLoop";
if(queue[0] == get_action($skill[entangling noodles])) {
queue.clear();
preact = get_action($skill[entangling noodles]).id +"; call batround;" + preact;
}
queue[count(queue)] = get_action($skill[Wave of Sauce]);
queue[count(queue)] = get_action($skill[Saucegeyser]);
return macro(preact, "goto SauceLoop");
}
boolean can_splash() {
if(my_class() != $class[sauceror] || !have_skill($skill[Wave of Sauce]) || !have_skill($skill[Saucegeyser])
|| !(have_effect($effect[Jabañero Saucesphere]) >0 || have_effect($effect[Jalapeño Saucesphere]) >0)
|| dmg_dealt(get_action($skill[Wave of Sauce]).dmg) >= monster_stat("hp"))
return false;
switch(normalize_dmgtype("sauce")) {
case "hot": return numeric_modifier("spell damage") + numeric_modifier("hot spell damage") >= 25;
case "cold": return numeric_modifier("spell damage") + numeric_modifier("cold spell damage") >= 25;
}
return numeric_modifier("spell damage") >= 25;
}
string take_action(monster foe, string page) {
float dam = m_regular();
boolean stun;
void cast_noodles() {
if(stun || get_action($skill[entangling noodles]).id == "") return;
queue[count(queue)] = get_action($skill[entangling noodles]);
stun = true;
}
if((dam > my_hp()/ 3 || $monsters[Uppercase Q, Quantum Mechanic] contains foe)
&& !(foe == $monster[a.m.c. gremlin] && item_amount($item[divine champagne popper]) > 0) )
cast_noodles();
switch(foe) {
case $monster[a.m.c. gremlin]:
foreach da in $items[divine champagne popper, tattered scrap of paper]
if(get_action(da).id != "")
return get_action(da);
break;
case $monster[mother hellseal]:
return spamAttack(get_action($skill[lunging thrust-smack]));
case $monster[naughty sorority nurse]:
if(my_mp() >= 6+ my_level() * 2 && item_amount($item[frigid ninja stars]) > 0) {
string [int] rave_stun = split_string(get_property("raveCombo3"), ",");
foreach key,rave in rave_stun
queue[count(queue)] = get_action(to_skill(rave));
queue[count(queue)] = get_action($skill[Moxious Maneuver]);
queue[count(queue)] = get_action($skill[Moxious Maneuver]);
queue[count(queue)] = get_action($item[frigid ninja stars]);
return macro();
}
abort();
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]:
if(can_splash())
return splashSauce();
return spamAttack(dangeraction());
// In Fernswarthy's Basement
case $monster[the ghost of fernswarthy n great-grandfather]:
case $monster[a n stone golem]:
for it from 3755 to 3759
if(item_amount(to_item(it)) > 3) {
queue[count(queue)] = get_action(to_item(it));
queue[count(queue)] = get_action(to_item(it));
}
return macro("mark lovesongLoop", "goto lovesongLoop");
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]:
for it from 3118 to 3120
if(item_amount(to_item(it)) > 3) {
queue[count(queue)] = get_action(to_item(it));
queue[count(queue)] = get_action(to_item(it));
return macro("");
}
for it from 3754 to 3759
if(item_amount(to_item(it)) > 3) {
queue[count(queue)] = get_action(to_item(it));
queue[count(queue)] = get_action(to_item(it));
return macro("");
}
return macro("mark divinesongLoop", "goto divinesongLoop");
}
switch(my_location()) {
case $location[8-Bit Realm]:
if(get_action($item[blue shell]).id != "" && my_hp() > 25) {
if(dam +25 > my_hp())
cast_noodles();
if(my_hp() > (stun? 25: 25 + dam))
queue[count(queue)] = get_action($item[blue shell]);
}
return spamAttack(get_action("attack"));
case $location[convention hall lobby]:
cast_noodles();
queue[count(queue)] = get_action($skill[wave of sauce]);
queue[count(queue)] = get_action($skill[candyblast]);
return spamAttack(get_action($item[bottle of Gü-Gone]));
break;
}
if(can_splash())
return splashSauce();
else if(hitchance(1) < .2)
return spamAttack(dangeraction());
return spamAttack(get_action("attack"));
}
void main(int initround, monster foe, string page) {
page = act(page);
page = take_action(foe, page);
}
zarqon: Have I finally managed to successfully use Batbrain ?
zarqon
04-22-2011, 09:32 AM
That looks pretty sweet. Unfortunately your basement section won't work because, like your shadow, mafia doesn't ambiguate those monsters yet. You can match basement monsters in your regular CCS, but not in a consult script -- last_monster() will return $monster[none]. I've already submitted that bug report.
Expect some shiny new functions in the next BatBrain update:
enqueue(advevent)
enqueue(advevent, string repeatcondition)
enqueue(advevent, string pre, string post)
Those should let you make your script simpler still.
Huh. It doesn't? Pretty strange. Mafia recognizes those monsters as constants, so I just assumed it would recognize those monsters.
enqueue(advevent)
enqueue(advevent, string repeatcondition)
enqueue(advevent, string pre, string post)
Ooooooooooooooooooooooooooooooooooooooooooooo!
That's just what I've been wanting. The last one will even allow me to instert if conditions, while loops, or the like. I guess I won't need spamAttack() anymore. ;)
Winterbay
04-24-2011, 09:58 PM
So, I don't think I'll get any closer to something good without comments now so here it is. My latest version of spamattack that utilises BatBrain for a lot of things. Please give me ideas on what to do and how to make it more efficient.
Winterbay
04-26-2011, 11:02 AM
I guess this is as good a place as any to ask this: Is there a way to get a consult-script to call itself if the monster isn't dead when it has ran once?
Maybe with a cli_execute() ? But that seems really awkward. Why not simply avoid exiting the consult script in the first place?
Winterbay
04-26-2011, 12:39 PM
Yes... I guess I could do a while (monster_health > 0) -loop somewhere.
Yes... I guess I could do a while (monster_health > 0) -loop somewhere.
That won't work because of monster ML variance. Sometimes the monsters health goes slightly negative according to mafia. You'd have to check for win text. "WINWINWIN" However, I wouldn't do that since...
Is there a way to get a consult-script to call itself if the monster isn't dead when it has ran once?
Well that isn't really a problem is it? Unless things changed somewhere, the final line of a CCS will be repeated until the monster is dead. That means your script will be called until mafia detects death or it fails to execute an action.
zarqon
04-27-2011, 03:36 AM
If you're using monster_stat("hp"), it's capped on the low end at 1.
But you don't actually need to parse combat completion, just assume the monster is always alive. It's been my experience that mafia won't execute further combat actions once the combat is over. You can check the round variable to see where you are in the combat, and if it's 0 the combat is over.
If you're executing all of your actions using BatBrain functions like macro(), it automatically sets round to maxround if it's 0. So you can just do this:
while (round < maxround) {
// call my consulty goodness
}
Winterbay
04-27-2011, 05:03 AM
I went with:
repeat{ ... }until(contains_text(page, "WINWINWIN") || page == "");
That works with "real" fights and testing where I supply the html of a fight page to the script in order to see what would happen (just having the winwinwin there made it never stop since 1 pass through the script emptied "page".
Also, I've had a couple of instances where the script ran once and then the fight aborted and the monster had still ~5HP left due to monster and spell variance.
Just to share, this is what has happened to my FinalAttack with the implementation of BatBrain's enqueue() command. It's bloody brilliant how easy it makes writing macros. I think that this makes a great example of how to use BatBrain. It is just complicated enough to show some interesting techniques, but reasonably short and simple enough not to become too overwhelming to a beginning scripter.
import "BatBrain.ash";
advevent dangeraction() {
// Check shieldbutt, moxious maneuver, saucegeyser, attack
foreach da in $strings[skill 2005, skill 7008, skill 4012, attack] {
if(get_action(da).id != "" && !(da == "skill 7008" && hitchance(0) < .8))
return get_action(da);
}
return new advevent; // you should never reach this
}
string splashSauce() {
enqueue(get_action($skill[Wave of Sauce]), "mark SauceLoop", "");
enqueue(get_action($skill[Saucegeyser]), "", "goto SauceLoop");
return macro();
}
boolean can_splash() {
if(my_class() != $class[sauceror] || !(have_skill($skill[Wave of Sauce]) && have_skill($skill[Saucegeyser]))
|| !(have_effect($effect[Jabañero Saucesphere]) >0 || have_effect($effect[Jalapeño Saucesphere]) >0)
|| dmg_dealt(get_action($skill[Wave of Sauce]).dmg) >= monster_stat("hp"))
return false;
switch(normalize_dmgtype("sauce")) {
case "hot": return numeric_modifier("spell damage") + numeric_modifier("hot spell damage") >= 25;
case "cold": return numeric_modifier("spell damage") + numeric_modifier("cold spell damage") >= 25;
}
return numeric_modifier("spell damage") >= 25;
}
string take_action(monster foe, string page) {
#print("FinalAttack macro to kill a "+foe+" in the "+my_location()+". ( "+hitchance(1)+" )", "blue");
float dam = m_regular();
boolean stun;
void cast_noodles() {
if(stun || get_action($skill[entangling noodles]).id == "") return;
enqueue(get_action($skill[entangling noodles]));
stun = true;
}
if((dam > my_hp()/ 3 || $monsters[Uppercase Q, Quantum Mechanic] contains foe)
&& !(foe == $monster[a.m.c. gremlin] && item_amount($item[divine champagne popper]) > 0) )
cast_noodles();
switch(foe) {
case $monster[A.M.C. gremlin]:
foreach da in $items[divine champagne popper, tattered scrap of paper]
if(get_action(da).id != "")
return macro(get_action(da));
break;
case $monster[mother hellseal]:
enqueue(get_action($skill[lunging thrust-smack]), "");
return macro();
// This monster was specific to a certain character of mine, not generally useful
case $monster[naughty sorority nurse]:
if(my_mp() >= 6+ my_level() * 2 && item_amount($item[frigid ninja stars]) > 0) {
string [int] rave_stun = split_string(get_property("raveCombo3"), ",");
foreach key,rave in rave_stun
enqueue(get_action(to_skill(rave)));
enqueue(get_action($skill[Moxious Maneuver]));
enqueue(get_action($skill[Moxious Maneuver]));
enqueue(get_action($item[frigid ninja stars]));
return macro();
}
abort();
case $monster[mine crab]:
enqueue(get_action($item[Miniborg Destroy-O-Bot]));
enqueue(get_action($item[Miniborg Destroy-O-Bot]), "");
return macro();
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]:
if(can_splash())
return splashSauce();
enqueue(get_action(dangeraction()), "");
return macro();
// In Fernswarthy's Basement
case $monster[the ghost of fernswarthy n great-grandfather]:
case $monster[a n stone golem]:
for it from 3755 to 3759
if(item_amount(to_item(it)) > 3) {
enqueue(get_action(to_item(it)));
enqueue(get_action(to_item(it)));
}
return macro("mark lovesongLoop", "goto lovesongLoop");
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]:
for it from 3118 to 3120
if(item_amount(to_item(it)) > 3) {
enqueue(get_action(to_item(it)));
enqueue(get_action(to_item(it)));
}
for it from 3754 to 3759
if(item_amount(to_item(it)) > 3) {
enqueue(get_action(to_item(it)));
enqueue(get_action(to_item(it)), "");
}
return macro("mark divinesongLoop", "goto divinesongLoop");
}
switch(my_location()) {
case $location[8-Bit Realm]:
if(get_action($item[blue shell]).id != "" && my_hp() > 25) {
if(dam +25 > my_hp())
cast_noodles();
if(my_hp() > (stun? 25: 25 + dam))
enqueue(get_action($item[blue shell]));
}
enqueue(get_action("attack"), "");
return macro();
// This is set up for a specific character. Needs to be changed for circumstance
case $location[convention hall lobby]:
cast_noodles();
enqueue(get_action($skill[wave of sauce]));
enqueue(get_action($skill[candyblast]));
enqueue(get_action($item[bottle of Gü-Gone]), "");
return macro();
}
if(can_splash())
return splashSauce();
else if(hitchance(1) < .1)
enqueue(dangeraction(), "");
enqueue(get_action("attack"), "");
return macro();
}
void main(int initround, monster foe, string page) {
page = act(page);
page = take_action(foe, page);
}
zarqon
05-02-2011, 05:45 PM
Since Bale made me this thread, and it contains lots of good discussion already, I decided to make this the official BatBrain thread. I think it's ready for release, so I'm calling this update 1.0. It has some really awesome new features.
BatBrain 1.0 updates!
Smarter aborting of macros (don't use "match" for poisoned, etc.), and a generally more streamlined batround.
Smarter insertion of batround into the macros. Subroutines including batround are still built-in for actions with repeat conditions.
New wrapper function for player stats -- my_stat(string which). This is basically identical to the already existing monster_stat(string which), except for the player instead. Presently the only possible values are "hp" and "mp". All references to the ASH functions my_hp() and my_mp() have been replaced with this, and you should do the same if you're writing a script using BatBrain -- for reasons I will soon divulge.
Major changes to the enqueueing process:
All versions of enqueue() now return a boolean and reject actions (don't enqueue them) if they are empty or if you lack the MP at that point. It returns true if the action was accepted and added to the queue. Now, if (enqueue(get_action("myaction"))), then myaction is available to your character and was added to the queue.
All versions of macro() now clear the queue, after enqueueing any actions specified in parameters. It will clear the queue regardless of whether the action was actually enqueued.
And now for something truly amazing: Predictive Adjustment
I'm quite excited to announce this; the framework for it has been sitting there for a long time but I've finally made it work.
When an action is enqueued, the script environment is adjusted to a post-queue state. In other words, if you enqueue an action such as "attack", then check monster_stat("hp"), it will return an adjusted value -- the monster's HP minus your expected damage. If you enqueue a second attack, it adjusts the value further. Checking my_stat("hp") will also include adjustments for the monster's expected damage during those rounds. These adjustments also include retaliation damage, familiar actions, passive effects, etc. -- and significant meat gain/loss if you are predicted to kill the monster or die trying (monster_value() and beatenup, respectively). You can now easily build entire predictive combats in your script.
Since this is a new feature, there are definitely some special cases and formula variables which are not properly altered when adjusting for an enqueued action, but all the most important ones -- accessible in monster_stat() and my_stat() -- are handled. Additionally, since all the adjustments are kept in a gobal advevent variable called adj, you can check the fields of that variable any time for your estimated progress in any of those areas.
For example, if your Volleyball has an ant pick equipped, that's an average of 5 meat per round and 4.4 cold damage. You're facing a Battlefield monster and you enqueue noodles, followed by a saucestorm, followed by a regular attack. That's three rounds -- so assuming no other factors at play, if you check adj.meat at that point, it will contain 15. If you check adj.mp, it will contain -13 (the cost of both skills). If you check adj.hp, it will contain 0, since Noodles is assumed to stun for 3 rounds. The $element[none] field of adj.dmg is used as the monster's HP adjustment, and it will contain -(your estimated saucestorm damage + your estimated attack damage + 26.4). That last number is 4.4 times 3, and doubled because Battlefield monsters are vulnerable to cold.
At last, a predictive solution to plinking:
while (m_hit_chance() > 0.07) enqueue(get_action($skill[suckerpunch]));
Known Issues
Predictive adjustment presently doesn't work for actions with repeat conditions -- it will treat them like single actions. I don't know if I'll ever try to make this work -- generally, if you know the repeat conditions, you generally know the situation post-macro, at which point if the combat isn't over your script can recalculate.
Predictive adjustment also doesn't know about auto-funkifying yet. If you enqueue two consective item throws, the macro will automatically convert these to a single funkslinging toss -- but this happens at the time of macrofying, not at the time of enqueueing, so it predicts this like two rounds rather than one. This is an issue which will be fixed. Auto-funkifying will be moved into enqueueing -- but for now, it just means extra-conservative combat estimations; not too harmful.
Neat, eh? Have fun!
Winterbay
05-02-2011, 09:42 PM
Man that was a lot of stuff to synch with my bloatified version of BatBrain (more line breaks and {}s :)).
It looks amazing though. I hope to get to play with it sometime when my life becomes less hectic.
shazbot
05-03-2011, 12:13 AM
EDIT: Nevermind, verbosity 9 + stocking mimic = ludicrous amount of messages that causes my mafia to seize.
At last, a predictive solution to plinking:
while (m_hit_chance() > 0.07) enqueue(get_action($skill[suckerpunch]));
OMG
That's.... amazing.
Boogaloogaloo
05-03-2011, 11:44 AM
There is a condition (lines 944 and 955 of version 1.0) in the action filter for choosing whether or not to whip the guard turtles that doesn't seem right. Maybe I'm reading it wrong, but I can't see any reason why it should be there.
Anyhow, removing the highlighted condition fixes the handling of guard turtles.
if (have_equipped($item[fouet de tortue-dressage]) && my_location() == $location[outer compound] && // un-brainwash turtles
(contains_text(action,"frenchturtle.gif") || get_property("lastNemesisReset").to_int() == my_ascensions()) && my_stat("mp") >= 5*mp_cost($skill[apprivoisez la tortue])) {
zarqon
05-03-2011, 01:49 PM
Yeah, that's backwards. We want to whip only French turtles during the Nemesis quest, then all turtles after that. I'll just change it to check for a Flail of the Seven Aspects instead.
Winterbay
05-04-2011, 08:14 PM
Hmm... Say I enqueue a couple of skills and then want to try a new set of skills, or exchange one of the enqueued ones.. Is there a way to revert BatBrain's knowledge of monster stats and player stats to pre-queueing?
A sort of reset()/revert() function perhaps.
Edit: Had an idea, but it appears to not be working. It enqueues empty things all the time :(
Maybe:
clear queue;
adj = new advevent;
I found a bug in m_dpr(). The explanation is posted in the SmartStasis thread (http://kolmafia.us/showthread.php?1715-SmartStasis-a-complex-script-for-a-simple-CCS&p=49847&viewfull=1#post49847), but I'm posting here to make sure Zarqon doesn't miss it.
m_dpr() should be changed to:
float m_dpr(float att_mod, float stun_mod) {
adj.att += att_mod;
adj.stun += stun_mod;
float res = m_hit_chance()*m_regular();
adj.att -= att_mod;
adj.stun -= stun_mod;
return res;
}
zarqon
05-05-2011, 03:13 PM
@slyz: That won't work -- it fails to account for unknown stat adjustment and existing multi-round stuns.
Updates coming soon -- but I'm playing a music festival (http://www.worlddjfest.com/) this weekend so all my time right now is going into that. My first time playing on this huge a stage to this huge a crowd -- it's very exciting even if I am just one of the afternoon "filler" bands!
Upcoming additions to BatBrain:
int kill_rounds(advevent)
int kill_rounds(spread)
int die_rounds()
void clear_queue()
@slyz: That won't work -- it fails to account for unknown stat adjustment and existing multi-round stuns.
I don't understand. Neither does the current m_dpr() then ? Wasn't the purpose of "temp" to return "adj" to its previous state after computing m_hit_chance()*m_regular() ?
EDIT: Break a leg!
Updates coming soon -- but I'm playing a music festival (http://www.worlddjfest.com/) this weekend so all my time right now is going into that. My first time playing on this huge a stage to this huge a crowd -- it's very exciting even if I am just one of the afternoon "filler" bands!
I'm never going to be able to look at a banana the same way again. :eek:
Please break two legs! Hopefully, other people's legs.
Winterbay, I'm in a myst run so I'm finally biting my teeth into your spamattack spellmacroing script. Could you tell me why your script has special fear of giants? I don't get it. I suppose if it was just for Procrastination Giants I could understand, but why all giants?
Winterbay
05-19-2011, 06:07 AM
Well, it's me being lazy I think :)
It is the procrastination giants fault and should probably be changed to "if(m == $monster[procrastination giant])" instead of the check for "giant". Also, the version I have on my computer is broken (so I hope that isn't the version I uploaded here...) due to my restoring subs not being where I want them to be but I have no time to fix this until possibly next week (unless I get time over and internet on the trains I'll be on today and tomorrow).
It looks like the mp restoration is a bit weird. I'll take a look at fixing that later.
So far I've modded it a tiny bit though to play nice with FinalAttack. I then imported your script into mine to handle spell casting when attacks won't hit and I don't want to sauce spash. I'm pretty happy with the result though I'll probably play around with it a bit more.
In case you're curious I'll attach our children here. You might be interested in the way I changed the main function of your script and exported the logic to a subroutine for use with FinalAttack.
Winterbay
05-19-2011, 09:11 AM
Yeah, the main function would need changing in order to work as an import since it is set up to be used as a consult-script which has a specific set up in the main function :)
I'll download it and see if I get to poke at it during the train ride. Could be interesting.
I'm looking around and I think it would be more prudent to have a version of hitchance that you would pass an action into instead of your magic number integer. I feel like in it's current form someone would get confused as to what needs to be passed in without looking at batbrain's source.
tl;dr: I agree with Rinn.
Longer version: The problem is that "attack" is not a skill. :( That means you would need to pass it a string. Of course if the action is not "attack" you can do skillname.to_string() to convert it and have an overloaded form that accepts a skill. It would be loads less confusing than passing an int completely unrelated to its meaning.
I would assume that an empty version that takes no arguments would be use for attack, it's not like the function couldn't be overloaded for both strings and $skills.
zarqon
05-21-2011, 05:41 AM
That's pretty much a private/internal function; you shouldn't really need to use that function in a consult script. Your hit chance is already inluded in all of the options which have one (to BB's knowledge). Show me some examples where it's needed and you'll have a case.
Winterbay
05-21-2011, 07:32 AM
I use it in my script to decide if I should attack the monster or cast spells at it as such:
//Check if we should just attack the monster instead of spellslinging
boolean will_attack(monster foe)
{
float rounds;
boolean no_attack = foe == $monster[procrastination giant] || contains_text(to_lower_case(to_string(foe)), "quantum");
if(get_action("attack").id != "")
{
vprint("You have " + hitchance(1) * 100 + "% chance to hit the monster", "purple", 7);
if(dmg_dealt(get_action("attack").dmg) > 0 && hitchance(1) > 0.8)
{ //How many rounds will it take me to kill the monster by attacking
rounds = ceil(monster_stat("hp") / (dmg_dealt(get_action("attack").dmg) + m_hit_chance()*dmg_dealt(retal.dmg) + dmg_dealt(baseround().dmg)));
vprint("Expected received damage per round: " + m_dpr(0,0) + ", Rounds to kill: " + rounds + ", Expected damage: " + dmg_dealt(get_action("attack").dmg) + ", Hit chance: " + hitchance(1),"purple",8);
if(!no_attack && (ceil(my_hp() / m_dpr(0,0)) > rounds) && (rounds < 10)) //Do not attack if it isn't over quickly and do not attack giants
{
vprint("Monster is weak. We are just going to bash its head in. It'll take " + rounds + " rounds.", "purple", 2);
return true;
}
}
}
return false;
}
zarqon
05-21-2011, 07:46 PM
That's why BatBrain contains kill_rounds() and die_rounds() -- or it will. Although they haven't made their way into the "official" release yet, they are in the placeholder I posted in the SS thread. The first of those accepts either a spread or an advevent.
if (kill_rounds(get_action("attack")) < die_rounds()) print("Your regular attack will kill this monster!");
Not bad. So if kill_rounds(get_action("attack")) < die_rounds() && m_dpr(0,0)*die_rounds()*meatperhp - to_profit(get_action("attack")) < 5*meatpermp then it is worth using attack, even if you can hit only with a critical.
I just pulled the number 5 from somewhere (http://kol.coldfront.net/thekolwiki/index.php/Handful_of_numbers). In practice it would be the mp required to kill the monster with combat skills.
Winterbay
05-22-2011, 05:33 PM
My monster_stat-function currently looks like this in order to not have my consult script break on b-monsters while in b-core:
// adjusted monster stat (unknown_ml and current or projected +/-ML)
float monster_stat(string which)
{
int bees = 0;
if(my_path() == "Bees Hate You")
{
for i from 0 to length(to_string(m)) -1
{
if(char_at(to_string(m),i) == "b")
bees = bees + 1;
}
}
switch (which)
{
case "att": return adj.att + (m == last_monster() ? monster_attack() : monster_attack(m)) + bees * 15;
case "def": return adj.def + (m == last_monster() ? monster_defense() : monster_defense(m)) + bees * 15;
case "hp": return adj.dmg[$element[none]] + max(1.0, (m == last_monster() ? monster_hp() : monster_hp(m))) + bees * 15;
}
return 0;
}
15 is probably not the correct number but it is at least closer to the reality than not having it there :)
Weatherboy
05-25-2011, 03:36 AM
I've been using Bale's FinalAttack and SpellKill posted back and I'm just, "wow". I was watching it yesterday while I was questing and noticed it was hitting with attacks that kept missing, until the monster died from a crit or familiar attack, instead of shieldbutting. While I was disturbed at first, when I finally figured out that it knew I couldn't die no matter how many times opp hit me and was doing the absolute cheapest way of killing said monster. Bale, dude, you collecting anything new?
LoL!
That was actually zarqon's brilliance, not mine. SS knows that will happen and simply does that. Only if you can't kill the monster with crits & familiar actions while taking negligible damage will FinalAttack take over. It's wonderful so please send batbits to zarqon.
Winterbay
05-30-2011, 11:56 AM
As stated in the SS thread regarding the experimental new BatBrain and SS there appears to be some oddity going on. My script calculates and enqueues skills for me and tehn executes a macro. Today I noticed that it wouldn't saucesplash as it should and realised that this was due to the macro aborting after 1 cast in the 2-cast-chain with the Danger-message. A check with verbosity 9 indicated a wonderful "hpbelow 304" even though my maxhp is currently 100.
In a test I then put in print outs of m_dpr(0,0) after and before every enquement of a skill with the following result:
SpamAttack: We're actually NOT going to one-shot (with Wave of Sauce) because noodles and then two shotting would be cheaper.
Expected damage per round (before Saucestorm: 33.229385).
Expected damage per round (after Saucestorm: 132.91754).
Expected damage per round (before Salsaball: 132.91754).
Expected damage per round (after Salsaball: 6.58).
SpamAttack: We are going to 2-shot with Saucestorm and Salsaball.
I have jaba and jala saucespheres active but surely they should not give that sort of effect?
Edit: Further digging indicates m_hit_chance() to part of the problem as it returns interesting values such as 6.58 which sounds a bit high considering it should range from 0 to 1...
Edit edit: Even further investigations show that m_hit_chance() get above 1 because stunmod gets to below 0. Changing m_hit_chance() to:
float m_hit_chance()
{
float stunmod = minmax(merge(baseround(),adj).stun, 0.0, 1.0);
if (contains_text(m,"Gremlin"))
return 1.0 - stunmod;
return (1.0 - stunmod)*minmax(0.55 + (max(monster_stat("att"),0.0) - my_buffedstat($stat[moxie]))*0.055,my_location() == $location[slime tube] ? 0.8 : 0.06,0.94);
}
removes this problems. I'v eno idea if it causes any other though, and also I'm not sure where the -3.0 in adj.stun comes from when enqueueing saucespells...
Winterbay
05-31-2011, 09:19 AM
I've reverted the above change in my local copy since I've figured out the problem (or at least I think I have):
The fucntion "adjust(advevent a)" adapts the script environment when I enqueue something. In its current version it looks like this:
void adjust(advevent a)
{
a = merge(a,baseround()); // add base round
if (monster_stat("hp")-dmg_dealt(a.dmg) > 0) // monster is alive; add monster attack
{
a.hp -= m_dpr(0,0);
if (my_stat("hp") < 1) // you died
a.meat -= beatenup;
else // you didn't die; add retaliation damage
a = merge(a,factor(retal,m_hit_chance()));
}
if (monster_stat("hp")-dmg_dealt(a.dmg) < 1) // monster died
a.meat += monstervalue();
a.dmg = to_spread(-dmg_dealt(a.dmg),"physical"); // monster hp is stored in dmg[none]
round += count(split_string(a.id,";")); // a round or several go by
adj.stun -= max(adj.stun, count(split_string(a.id,";")));
adj = merge(adj,a);
}
The calculation of the stun-parameter seems just wrong to me. It leads to negative stuns and ever increasing m_dpr(0,0). My local copy now has the following instead:
// adjusts script to predicted post-action state; stacks (only reset when you hit the server using act())
void adjust(advevent a)
{
a = merge(a,baseround()); // add base round
if (a.id == "attack")
a = merge(a,onhit); // add onhit event for regular attack (others?)
if (monster_stat("hp")-dmg_dealt(a.dmg) > 0) // monster is alive; add monster attack
{
a.hp -= m_dpr(0,0);
if (my_stat("hp") < 1)
a.meat -= beatenup; // you died
else
a = merge(a,factor(retal,m_hit_chance())); // you didn't die; add retaliation damage
}
if (monster_stat("hp")-dmg_dealt(a.dmg) < 1)
a.meat += monstervalue(); // monster died
a.dmg = to_spread(-dmg_dealt(a.dmg),"physical"); // monster hp is stored in dmg[none]
round += count(split_string(a.id,";")); // a round or several go by
vprint("adj.stun (1345): " + adj.stun + ", a.stun (1345): " + a.stun + ", round: " + round + ", a.id: " + a.id, "red", 9);
adj = merge(adj,a);
adj.stun = max(adj.stun - 1, 0);
}
Which seems to work much better accordign to a little test I did today:
Expected damage per round (before noodles: 9.188834).
Expected damage per round (before wave of sauce: 0.0).
Expected damage per round (before salsaball: 0.0).
Expected damage per round (before saucegeyser1: 9.188834).
Expected damage per round (after everything: 9.188834).
We can see that the m_dpr(0,0) is at ~9 at the start, then goes to 0 as I enqueue noodles, and when the script expects it to have run out it goes back up to ~9 again.
Compare this with the result of the original code and the same type of queue:
Expected damage per round (before noodles: 9.188834).
Expected damage per round (before wave of sauce: 0.0).
Expected damage per round (before salsaball: 9.188834).
Expected damage per round (before saucegeyser1: 36.755337).
Expected damage per round (after everything: 64.32184).
Also I am not sure why the Purse rat gets added to each action I do since I do not have a crown of thrones, it's just my familiar. Since this affects the round-counter the round increases by way more than it should and will reach maxrounds a lot faster (I've enqueued 4 skills and the round number is 13...)
zarqon
05-31-2011, 03:04 PM
Congrats on locating the bug, Winterbay. That should be a min() rather than a max(). I'll probably just minmax() it as an extra safeguard.
Fun how just two characters being off can totally mess up the whole script.
I've actually expanded advevents locally to include an integer "rounds", containing the number of rounds something takes. That ought to solve these complications based on round numbers and merging events. It also makes it easy to tell whether something is an action or just supplementary event information (or a free action in certain cases).
Other than Salve and Mistletoe, are there any other "quick" actions?
Winterbay
05-31-2011, 03:08 PM
Wouldn't a min() make it choose 0 all the time rather than the amount of turns of a stun that is remaining?
zarqon
05-31-2011, 04:27 PM
No. Let's say adj.stun is 2; we have enqueued an action that stuns for two rounds. Now we enqueue another action, which does not stun.
adj.stun -= min(adj.stun, a.rounds); // note the assignment operator
adj.stun is 2, and the action takes 1 round, meaning that stun will be decremented by one. Since only one round has gone by, this is exactly correct!
Or suppose that instead we'd enqueued a 3-round DB combo as our second action.
adj.stun (2) < rounds (3), so stun will be decremented by 2, thus making it 0 again. Correct!
Although this does make me notice that simulation is off for multi-round advevents. Some of that is unavoidable, since one advevent should always technically be one action, but it could be handled better for cases of "event squash". For instance, a 3-round DB combo should include 3 times the monster's attack rather than one, with stun calculated sequentially. Now that events include the number of rounds, that's possible.
Winterbay
05-31-2011, 07:25 PM
Ahh yes, the problem in the current version was that it decremented by 3 since a.id contained "skill 3004; base; Purse Rat" which meant that we got 2 - 3 = -1 and problems :)
zarqon
06-02-2011, 05:41 PM
Finally had a chance to tie up some of the loose ends and spin a proper release. w00t
In this update:
Substats are now integrated into advevents (they had been in the record declaration but not actually used).
A new field "rounds" now describes the number of rounds an advevent takes. This not only makes clearing the queue possible (below), but it eliminates guesswork when enqueueing actions, which may have an arbitrary number of commands separated by semicolons, and not all of them actions.
Track usage of Stealth Mistletoe.
As requested, if you have a Juju Mojo Mask equipped but don't have one of the gazes active, it will attempt to attract the appropriate gaze for your mainstat. It does this while enqueueing -- if the action you queued will attract the correct gaze, it does nothing further. Otherwise, it slips your class's trivial skill in front of the action it's about to enqueue.
Don't use m_dpr() for the macro abort -- use m_regular(), the monster's full attack. We want to abort if we are in danger of being one-shotted -- any number smaller than that is meaningless.
I have no idea wtf was going on when verbosity was set to 9 or higher, but mafia very much disliked something about the to_html(advevent) business in stasis_action(). Changed that to a simple print command -- less information, but seriously that was just off da chain insane.
And some new useful functions:
void reset_queue() -- clears the queue and correspondingly un-adjusts the script environment. The only known issue with this is that it resets stun to 0 -- which may not be true if you have a multi-round stun currently active. MORAL: enqueue enough things to use up your stunners. MOREL: a kind of mushroom.
int kill_rounds( spread )
int kill_rounds( advevent ) -- the number of rounds it would take a given action or damage spread to kill the monster. This accounts for retaliation damage, familiar actions -- everything that BB knows about.
int die_rounds() -- the number of rounds it will take the monster to kill you.
Enjoy, everyone. Sorry it took me a while. Still using the little coding-unfriendly netbook.
SS update coming tomorrow, I think -- with Mayfly support!
Winterbay
06-02-2011, 10:09 PM
Coolio.
On the subject of batbrain. Can anyone explain why the following code doesn't work?
import <batbrain.ash>
void main(int initround, monster foe, string page)
{
page = act(page);
for i from 1 to 4
{
if(get_action($skill[salsaball]).id != "")
{
vprint("i = " + i + " Expected damage per round (before salsaball: " + m_dpr(0,0) + ").", "purple", 9);
vprint("my_stat(hp): " + my_stat("hp") + ", my_stat(mp): " + my_stat("mp"), 9);
vprint("monster_stat(hp): " + monster_stat("hp"), 9);
enqueue(get_action($skill[salsaball]));
}
else
vprint("Say what?",-9);
}
//page = macro(); - this done to abort the script before the activation of the macro
}
The output at verbosity 9 against a cursed pirate is the following:
Building options...
Options built! (61 actions)
i = 1 Expected damage per round (before salsaball: 0.17324999).
my_stat(hp): 136.0, my_stat(mp): 339.0
monster_stat(hp): 90.0
Say what?
Say what?
Say what?
You're on your own, partner.
Meaning that after the first enqueue of salsaball it cannot be enqueued again. Why not?
Did you try that with the old or new BatBrain? The vprints in the new BatBrain should have helped with debugging...
As requested, if you have a Juju Mojo Mask equipped but don't have one of the gazes active, it will attempt to attract the appropriate gaze for your mainstat. It does this while enqueueing -- if the action you queued will attract the correct gaze, it does nothing further. Otherwise, it slips your class's trivial skill in front of the action it's about to enqueue.
Those function names and comments leave something to be desired.
Other than that, nice update! (I really like the comment on QUEUEING.) Thanks, but I worry that this script is becoming somewhat risqué.
Winterbay
06-03-2011, 08:41 AM
It was the new version, which makes me confused because there are a lot of things missing in that output, such as the meatperhp and mp readouts which should be there at verbosity 9.
Crap. I may have found the problem... Check me on this: (look at the lines in red)
boolean enqueue(advevent a, string pre, string post) {
if (a.id == "") return vprint("Unable to enqueue empty action.",-8); // allows if(enqueue(get_action()))
if (round + a.rounds >= maxround) return vprint("Can't enqueue '"+a.id+"': combat too long.",-8);
if (my_stat("mp")+a.mp < 0) return vprint("Unable to enqueue '"+a.id+"': insufficient MP.",-7); // everybody to the limit
// attract gays
boolean have_gays() { foreach i,ev in queue if (which_gays(ev.id) != $stat[none]) return true; return false; }
if (have_equipped($item[juju mojo mask]) && have_effect($effect[gaze of the volcano god]) + have_effect($effect[Gaze of the Lightning God]) +
have_effect($effect[gaze of the trickster god]) == 0 && !have_gays() && which_gays(a.id) != my_primestat())
enqueue(get_action(to_skill(1000*(my_class().to_in t()+1))),"","");
adjust(a);
a.id.replace_string("; ","; call batround; ");
a.id = (pre.length() > 0 ? pre+"; " : "")+a.id+"; call batround"+(post.length() > 0 ? "; "+post : "")+"; ";
queue[count(queue)] = a;
return true;
}
That alters a.id. A.di is actually get_action(skill.id) which passes back a value of opts, so aren't you passing an element of a map by reference, not value? That means you're changing the original element in opts.
If so, then it would explain the problem because get_action() would no longer be able to match salasaball to skill 5000.
Does that sound correct?
Winterbay
06-03-2011, 10:15 PM
That could be it... Yet another reason for my "advevent copy(advevent a)" that I thought of adding...
zarqon
06-04-2011, 04:07 AM
Oh dear. That looks right to me. Old habits die hard -- I'm used to languages where passing by reference must be specified explicitly, so I usually operate on the parameter (copy) in a function, to avoid needlessly declaring another variable. It looks like adjust() also has this same issue. I'll hasten to post a fix posthaste.
roippi
06-04-2011, 08:44 AM
Hmm..
1 MP costs 4.7916665μ.
1 HP costs 0.88917524μ.
chef's hat (35.0 @ +57.0): 220μ * 54.95% = 120.89
Knob Goblin tongs (10.0 @ +57.0): 25μ * 15.7% = 3.925
Knob Goblin pants (5.0 @ +57.0): 30μ * 7.85% = 2.355
Value of stat gain: 935μ
Building options...
Evaluating 'loc(vibrato)'...
Evaluating 'loc(vibrato)'...
Evaluating 'loc(vibrato)'...
Evaluating 'loc(vibrato)'...
Evaluating 'min(50,ceil(5*sqrt(min(785.0,71.0))))'...
Evaluating '1.0*(12+min(0.15*785.0,20)+min(20.0,40)+0.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Evaluating '0.0*min(0.9,0.015*32.0)'...
Options built! (98 actions)
Unexpected error, debug log printed.
Winterbay
06-04-2011, 08:49 AM
Yeah, I was just about to say the same thing. Also, Mafia does not like to display the nice µ symbol for some reason. It shows up as μ instead...
Same code as in #93 gives the following output:
1 MP costs 9.090909μ.
1 HP costs 2.7272727μ.
enchanted bean (50.0 @ +40.0): 700μ * 70.0% = 490.0
sonar-in-a-biscuit (10.0 @ +40.0): 11μ * 14.0% = 1.54
Value of stat gain: 35μ
Building options...
Options built! (18 actions)
i = 1 Expected damage per round according to m_dpr(0,0), (before salsaball): 6.2116895, and with mregular(): 8.058739
my_stat(hp): 24.0, my_stat(mp): 54.0
monster_stat(hp): 18.0
Unexpected error, debug log printed.
1 MP costs 9.090909μ.
1 HP costs 2.7272727μ.
enchanted bean (50.0 @ +40.0): 700μ * 70.0% = 490.0
sonar-in-a-biscuit (10.0 @ +40.0): 11μ * 14.0% = 1.54
Value of stat gain: 35μ
Building options...
Options built! (18 actions)
i = 1 Expected damage per round according to m_dpr(0,0), (before salsaball): 6.2116895, and with mregular(): 8.058739
my_stat(hp): 24.0, my_stat(mp): 54.0
monster_stat(hp): 18.0
Unexpected error, debug log printed.
You're on your own, partner.
at verbosity = 9.
zeroToNine
06-04-2011, 12:03 PM
Running BatBrain 1.2 in the latest KoLmafia (r9390), I get the following error every battle:
class java.lang.ClassCastException: net.sourceforge.kolmafia.textui.parsetree.Value cannot be cast to net.sourceforge.kolmafia.textui.parsetree.Composit eValue
java.lang.ClassCastException: net.sourceforge.kolmafia.textui.parsetree.Value cannot be cast to net.sourceforge.kolmafia.textui.parsetree.Composit eValue
at net.sourceforge.kolmafia.textui.parsetree.Composit eReference.getSlice(CompositeReference.java:107)
at net.sourceforge.kolmafia.textui.parsetree.Composit eReference.getValue(CompositeReference.java:178)
at net.sourceforge.kolmafia.textui.parsetree.Composit eReference.execute(CompositeReference.java:91)
at net.sourceforge.kolmafia.textui.parsetree.Operator .applyTo(Operator.java:391)
at net.sourceforge.kolmafia.textui.parsetree.Expressi on.execute(Expression.java:221)
at net.sourceforge.kolmafia.textui.parsetree.Expressi on.execute(Expression.java:150)
at net.sourceforge.kolmafia.textui.parsetree.Operator .applyTo(Operator.java:591)
at net.sourceforge.kolmafia.textui.parsetree.Expressi on.execute(Expression.java:221)
at net.sourceforge.kolmafia.textui.parsetree.Assignme nt.execute(Assignment.java:101)
at net.sourceforge.kolmafia.textui.parsetree.BasicSco pe.execute(BasicScope.java:451)
at net.sourceforge.kolmafia.textui.parsetree.UserDefi nedFunction.execute(UserDefinedFunction.java:129)
at net.sourceforge.kolmafia.textui.parsetree.Function Call.execute(FunctionCall.java:166)
at net.sourceforge.kolmafia.textui.parsetree.Assignme nt.execute(Assignment.java:101)
at net.sourceforge.kolmafia.textui.parsetree.BasicSco pe.execute(BasicScope.java:451)
at net.sourceforge.kolmafia.textui.parsetree.UserDefi nedFunction.execute(UserDefinedFunction.java:129)
at net.sourceforge.kolmafia.textui.parsetree.Function Call.execute(FunctionCall.java:166)
at net.sourceforge.kolmafia.textui.parsetree.BasicSco pe.execute(BasicScope.java:451)
at net.sourceforge.kolmafia.textui.parsetree.UserDefi nedFunction.execute(UserDefinedFunction.java:129)
at net.sourceforge.kolmafia.textui.parsetree.Function Call.execute(FunctionCall.java:166)
at net.sourceforge.kolmafia.textui.parsetree.Function Return.execute(FunctionReturn.java:98)
at net.sourceforge.kolmafia.textui.parsetree.BasicSco pe.execute(BasicScope.java:451)
at net.sourceforge.kolmafia.textui.parsetree.UserDefi nedFunction.execute(UserDefinedFunction.java:129)
at net.sourceforge.kolmafia.textui.parsetree.Function Call.execute(FunctionCall.java:166)
at net.sourceforge.kolmafia.textui.parsetree.BasicSco pe.execute(BasicScope.java:451)
at net.sourceforge.kolmafia.textui.parsetree.Else.exe cute(Else.java:63)
at net.sourceforge.kolmafia.textui.parsetree.If.execu te(If.java:81)
at net.sourceforge.kolmafia.textui.parsetree.BasicSco pe.execute(BasicScope.java:451)
at net.sourceforge.kolmafia.textui.parsetree.UserDefi nedFunction.execute(UserDefinedFunction.java:129)
at net.sourceforge.kolmafia.textui.parsetree.Function Call.execute(FunctionCall.java:166)
at net.sourceforge.kolmafia.textui.parsetree.Assignme nt.execute(Assignment.java:101)
at net.sourceforge.kolmafia.textui.parsetree.BasicSco pe.execute(BasicScope.java:451)
at net.sourceforge.kolmafia.textui.parsetree.UserDefi nedFunction.execute(UserDefinedFunction.java:129)
at net.sourceforge.kolmafia.textui.Interpreter.execut eScope(Interpreter.java:282)
at net.sourceforge.kolmafia.textui.Interpreter.execut e(Interpreter.java:213)
at net.sourceforge.kolmafia.textui.Interpreter.execut e(Interpreter.java:206)
at net.sourceforge.kolmafia.request.FightRequest.next Round(FightRequest.java:613)
at net.sourceforge.kolmafia.request.FightRequest.runO nce(FightRequest.java:1257)
at net.sourceforge.kolmafia.request.FightRequest.run( FightRequest.java:1281)
at net.sourceforge.kolmafia.request.GenericRequest.ha ndleServerRedirect(GenericRequest.java:1682)
at net.sourceforge.kolmafia.request.GenericRequest.re trieveServerReply(GenericRequest.java:1519)
at net.sourceforge.kolmafia.request.GenericRequest.ex ecute(GenericRequest.java:1231)
at net.sourceforge.kolmafia.request.GenericRequest.ru n(GenericRequest.java:1119)
at net.sourceforge.kolmafia.request.AdventureRequest. run(AdventureRequest.java:220)
at net.sourceforge.kolmafia.RequestThread.postRequest (RequestThread.java:73)
at net.sourceforge.kolmafia.KoLAdventure.run(KoLAdven ture.java:987)
at net.sourceforge.kolmafia.RequestThread.postRequest (RequestThread.java:103)
at net.sourceforge.kolmafia.KoLmafia.executeAdventure Once(KoLmafia.java:1443)
at net.sourceforge.kolmafia.KoLmafia.executeRequestOn ce(KoLmafia.java:1461)
at net.sourceforge.kolmafia.KoLmafia.executeRequest(K oLmafia.java:1351)
at net.sourceforge.kolmafia.KoLmafia.makeRequest(KoLm afia.java:1233)
at net.sourceforge.kolmafia.textui.command.AdventureC ommand.run(AdventureCommand.java:103)
at net.sourceforge.kolmafia.KoLmafiaCLI.executeComman d(KoLmafiaCLI.java:538)
at net.sourceforge.kolmafia.KoLmafiaCLI.executeLine(K oLmafiaCLI.java:412)
at net.sourceforge.kolmafia.swingui.CommandDisplayFra me$CommandQueueHandler.handleQueue(CommandDisplayF rame.java:202)
at net.sourceforge.kolmafia.swingui.CommandDisplayFra me$CommandQueueHandler.run(CommandDisplayFrame.jav a:183)
It appears the problem is in line 873 of BatBrain:
advevent a = merge(adjustment,a);
Since a is only just being defined, the second parameter to merge appears to be passed as 0 in ASH, and the KoLmafia Java exception happens in BatBrain's merge function at line 109, when it tries to defererence sec.id.
I'm not entirely sure why the correct merge function (the one that takes advevent arguments) gets called at all, and whether the not-yet-defined "a" gets somehow semi-cast to an advevent in order to satisfy the function definition, but then this gets forgotten when KoLmafia tries to get the "id" field; however, a short-term workaround in BatBrain appears to be to change line 873 to:
advevent a; a = merge(adjustment,a);
The same construct appears to be necessary at line 583.
fronobulax
06-04-2011, 12:27 PM
I saw the same error and changing
advevent a = merge(adjustment,a); to
advevent a;
a = merge(adjustment,a); in the two places noted above worked for me. (Thanks zeroToNine).
I would be very interested in Veracity's comments since it could be related to how the script is parsed.
Veracity
06-04-2011, 02:00 PM
I'm used to languages where passing by reference must be specified explicitly ...
You don't use Java, eh?
I saw the same error and changing
advevent a = merge(adjustment,a); to
advevent a;
a = merge(adjustment,a); in the two places noted above worked for me. (Thanks zeroToNine).
I would be very interested in Veracity's comments since it could be related to how the script is parsed.
Interesting. Apparently we add "a" to the symbol table for the scope before interpreting the initializer. I don't recall ever seeing code which used the (uninitialized) variable in its own initializer before.
I'll have to think about this.
Edit: I suspect that
advevent a = merge( adjustment, new advevent );
would work. I have no idea what "merge" is. Something which replaces the "adjustment" field of an advevent? Why not just initialize the advevent with the appropriate adjustment?
advevent a = new advevent(,,adjustment);
... as appropriate, depending on which field adjustment goes into.
Winterbay
06-04-2011, 02:57 PM
advevent a = merge( adjustment, new advevent );
would work. I have no idea what "merge" is. Something which replaces the "adjustment" field of an advevent? Why not just initialize the advevent with the appropriate adjustment?
Merge adds two advevents to each other so instead of doing
advevent a, b, c;
c.id = a.id + b.id;
c.next = a.next + b.next
you do
advevent a, b, c;
c = merge(a,b);
.
Veracity
06-04-2011, 03:08 PM
So, he's trying to use merge as a way to copy an advevent. If id and next are the two fields of an advevent, either of the following would work:
advevent a = merge( adjustment, new advevent);
advevent a = new advevent( adjustment.id, adjustment.next );
The first will do an extra memory allocation to make an empty advevent.
Revision 9391 fixes the ASH parser to disallow using an uninitialized variable in its own initializer expression.
Winterbay
06-04-2011, 04:08 PM
While we're on the trail of interesting bugs. I am currently getting a -2.27% chance to hit monsters since the return value of hitchance() looks like this:
return minmax((6.0 + attack - max(monster_stat("def"),0.0))/10.5,0.0,1.0) - fumble_chance(); // change minimum to critchance I think it would be better to do
return minmax((6.0 + attack - max(monster_stat("def"),0.0))/10.5 - fumble_chance(),0.0,1.0); // change minimum to critchance
Or am I wrong?
shouldn't that have *(1-fumble_chance()) instead?
In fact, I think it should be
float straight_atk_chance = minmax(6.0 + attack - max(monster_stat("def"),0.0))/10.5, 0.0, 1,0);
return crit_chance + (1-crit_chance-fumble_chance())*straight_atk_chance;
(once you have crit_chance figured out).
redwulf25_ci
06-04-2011, 06:04 PM
I saw the same error and changing
advevent a = merge(adjustment,a); to
advevent a;
a = merge(adjustment,a); in the two places noted above worked for me. (Thanks zeroToNine).
I would be very interested in Veracity's comments since it could be related to how the script is parsed.
I only found one instance of
advevent a = merge(adjustment,a); in batbrain.
Winterbay
06-04-2011, 06:08 PM
I only found one instance of
advevent a = merge(adjustment,a); in batbrain.
I had three instances of
advevent <something> = function(something, something other)
I did a quick search through the code for advevent and changed the ones I found so that they defined the variable first.
zarqon
06-04-2011, 06:24 PM
Ha, originally did have it as merge(eventtobecopied, new advevent), then opted to use the empty variable instead just because it's shorter and I thought it might be a smidge faster (not allocating another variable).
advevent a = new advevent(,,adjustment);
... as appropriate, depending on which field adjustment goes into.
Now that is a useful bit of information. Didn't have any idea until that post how to declare a new record and populate its fields in one fell swoop. Thanks.
Using merge(a, new record) is definitely not as optimal as new record(a.field1, a.field2, a.field3), since it needs two extra records (there's another one in the function to receive the summed fields of both records). The whole copying records thing is a little bit hackish anyway.
However, two fields in advevents are maps -- float[element] and int[stat], respectively. This would make the construct statement rather messy, since I'm pretty sure new advevent(a.id, a.mapvariable) would still have the same problem of copying pointers rather than maps, no? I assume there is an equivalent constructor statement for maps? If so, it's still possible but it would be a very long command. Using merge(), though hackish, is easier on the eyes. :)
I'll get this fixed and posted within a few minutes.
You don't use Java, eh?
Nope. A few CS classes I took in uni used Java, but I didn't delve much into it beyond the class excercises. My programming experience, which is nearly all as a hobby, is mainly in PHP and Delphi (object-oriented Pascal). I have written a few web apps some years back that some organizations are still using, however.
EdFox
06-04-2011, 07:44 PM
I have a bug report. I honestly don't know which script is responsible but after today's earlier adventures with SS getting blamed for BB's error I'm putting it here. :confused:
This is with the latest SS, BB posted this afternoon and using Bale's FinalAttack right after SS in my CCS. (I need Robin Williams to read that line)
This one only happens on certain monsters. Everything seems to go well, there's just a "You're on your own partner" break in the middle.
Monsters that trigger this bug:
Modern Zombie
dirty old lihc
beebee queue
bee swarm
EDIT: Possibly relevant bit: I had a haiku katana equipped for all combats. Each time this error occurred, Spring Raindrop was not cast. As the log shows, the first action was noodles. On other monsters, Spring was always cast before noodling or anything else.
Verbosity 10 logs:
Start of combat:
[320] Defiled Alcove
Encounter: modern zmobie
Strategy: C:\Users\Eszetela\Documents\My Dropbox\KoLmafia\.kolmafia\ccs\Final.ccs [default]
Round 0: edfox loses initiative!
You lose 24 hit points
1 MP costs 17.0μ.
1 HP costs 5.1μ.
Value of stat gain: 15μ
Value of stat gain: 15μ
Monster value: 15
You will die in 3067 rounds.
Your attack will kill the monster in 1 rounds.
Profit per round: ActionProfitDamageOtherbase; Pet Cheezling (0μ)0μ--
Building options...
Options built! (14 actions)
Building custom actions...
1/4 monsters drop goals here.
Custom actions built! (0 actions)
Stasis action chosen: skill 3004
This monster is not your huckleberry.
Stasis loop complete.
Executing macro: scrollwhendone; sub batround; if haseffect 436 || haseffect 8 || haseffect 264 || haseffect 282 || haseffect 283 || haseffect 284; abort "BatBrain abort: poisoned"; endif; if hpbelow 185; abort "BatBrain abort: Danger, Will Robinson"; endif; endsub; skill 3004; call batround;
Round 1: edfox executes a macro!
Round 1: edfox casts ENTANGLING NOODLES!
Building options...
Options built! (13 actions)
SmartStasis complete.
You're on your own, partner.
Click here to continue in the relay browser.Continuing the same combat via relay with the 'script' button:
1 MP costs 17.0μ.
1 HP costs 5.1μ.
Value of stat gain: 15μ
Building options...
Options built! (13 actions)
Executing macro: scrollwhendone; sub batround; if haseffect 436 || haseffect 8 || haseffect 264 || haseffect 282 || haseffect 283 || haseffect 284; abort "BatBrain abort: poisoned"; endif; if hpbelow 2; abort "BatBrain abort: Danger, Will Robinson"; endif; endsub; sub batsub1; skill 2005; call batround; endsub; call batsub1; repeat ; sub batsub2; attack; call batround; endsub; call batsub2; repeat ;
Round 2: edfox executes a macro!
Round 2: edfox casts SHIELDBUTT!
Round 3: modern zmobie takes 233 damage.
Round 3: modern zmobie takes 30 damage.
Round 3: modern zmobie drops 4 attack power.
Round 3: edfox wins the fight!
After Battle: Your Evilometer emits five quick beeps.
After Battle: Cheez Wizz covers one of its jalapeño "eyes" with a pseudopod, in a sort of rough facsimile of a wink, and burbles cheerfully at you
You gain 117 Meat
You gain 12 Strongness
You gain a Muscle point!
You gain 5 Mysteriousness
You gain 3 Cheek
Building options...
Options built! (5 actions)More info. Continuing the attack on a bee swarm failed with a different error that Bale has been referring to and the Danger Will Robinson bit appeared in the relay browser. Although now I can't find the post he made about negative hit chances... so perhaps I'm referring to something in my head. ANYWAY! Perhaps this is the causes off all these stops.
spellKill: Monster HP is 351.0
You have -4.5454545% chance to hit the monster
Macro called on empty queue!
spellKill: Monster HP is 351.0
You have -4.5454545% chance to hit the monster
Macro called on empty queue!
You're on your own, partner.
Winterbay
06-04-2011, 08:12 PM
Which spells do you have available for use? It may be that you no skills that can kill the monster in 5 turns, or you could have too little MP left, another thing that can generate that sort of error since afaia spellKill, just as my version of it, doesn't include restorers in the macro.
EdFox
06-04-2011, 08:18 PM
I'm running as a SC and don't have many offensive spells permed. Eliminating buffs and other noncombat things from the available skills pane:
Clobber (0 mp)
Shieldbutt (5 mp)
Spectral Snapper (20 mp)
Entangling Noodles (3 mp)
Lasagna Bandages (6 mp)
I'm pretty sure it's not an MP issue. I'm using Universal Recovery set at recover at 20% up to 50% and with a pool of 62 that should never not be enough to shieldbutt a few times.
EDIT:
I may have just figured it out, or at least found something relevant. I was using a haiku katana the whole time while this error occurred. On bugged combats where I got the stop SS did not cast Spring Raindrop. On every other combat it casts Spring as the very first action, even before noodles (I would rather noodle first spring second but that's a quibble). I went back through my logs and this appears to be 100%. All combats with the monsters that cause the stopper had no Spring Raindrop.
With Universal Recovery watching MP every round I should still have plenty even without Spring but it's an interesting coincidence.
Winterbay
06-04-2011, 08:57 PM
The only time Bale's FinalAttack calls spellKill is if shieldbutt is for some reason unavailable. Looking at your permed spells none of those are taken into account by spellKill so it is no surprise that script exits with an empty macro queue at least.
I've no idea why using Spring Raindrop or not could have to do with Shieldbutt not being available as an option...
zarqon
06-05-2011, 05:18 AM
This probably has to do with un-stunnable monsters, since BatBrain as yet doesn't know which monsters are immune to multi-round stuns. In all of these cases, was Noodles the last action before SS was finished? I suspect it was, which meant when SS finished mafia saw a KoL error message and aborted.
That's something easily added if I could just find a list of these monsters, and it might fix the issue. So far I haven't been able to track down such a list, however. The Wiki apparently doesn't care about this; the pages on stunning, bosses, and special monsters have nothing about immunity to stun.
Winterbay
06-05-2011, 07:38 AM
High level bees are definitely immune that much is sure.
All Hobo Bosses are also definitely immune.
This is just a preliminary list, but we're starting to get somewhere. :)
Raven434
06-05-2011, 06:21 PM
Here is an odd one I noticed with my Dandy Lion as a fam and having a having a whip equipped. I have a juju mask equipped.
My .ccs looks like this:
[ default ]
consult SmartStasis.ash
skill wrath of the trickster god
skill wrath of the lightning god
skill wrath of the volcano god
skill cannelloni cannon
When the whip activates the Dandy Lion, the combat throws me out to a manual intervention.
[118] Dark Heart of the Woods
Encounter: Fallen Archfiend
Strategy: D:\Games\KoL\Current Builds\Joe_Mama\ccs\Juju.ccs [default]
Round 0: Joe_Mama loses initiative!
You lose 6 hit points
Round 1: fallen archfiend takes 4 damage.
You lose 21 hit points
You will die in 2 rounds.
Your attack will kill the monster in 4 rounds.
Round 1: Joe_Mama executes a macro!
Round 1: Joe_Mama casts ENTANGLING NOODLES!
Round 2: Spuds MacKenzie uses his vines to give you an invigorating head massage. It's not the same as giving you a foot massage, but it's in the same ballpark.
You gain 6 hit points
You gain 3 Mana Points
Round 2: Like the majority of whipholders, you crack your whip. Cleo Leo is startled by the noise and goes feral, clawing your opponent for 19 damage. All fear the kitten with a whip!
Round 2: fallen archfiend takes 19 damage.
You're on your own, partner.
Click here to continue in the relay browser.
Am I invoking the script incorrectly or maybe my .ccs is a little brain-damaged or...???
The basic logic flow is:
Cast noodles if needed
Cast one of the 3 juju skills if active
Cast Canelloni Cannon if the monster hasn't been vaporized
Thanks!
Winterbay
06-05-2011, 09:34 PM
I would suggest you try that again at a higher verbosity so that the submitted macro can be seen and analysed for odditites. I've also been experiencing odd breaks after casting of noodles, but I think that is mainly due to unstunnable monsters, but I'm not sure...
Re. unstunnable monsters, I don't think KoL or Mafia throws an error when noodling them.
Winterbay
06-05-2011, 09:50 PM
No possibly not, but something is making things iffy after noodling (not seen it happen when SS decides to not noodle). Not sure what it is though.
Raven434
06-05-2011, 09:59 PM
I set verbosity for 10:
BBB: No need to eat a cookie given the present counters.
You don't have any spooky putty monsters.
Request 10 of 129 (Friars: Dark Elbow of the Woods) in progress...
[132] Dark Elbow of the Woods
Encounter: L imp
Strategy: D:\Games\KoL\Current Builds\Joe_Mama\ccs\Juju.ccs [default]
Round 0: Joe_Mama loses initiative!
You lose 6 hit points
Round 1: l imp takes 10 damage.
You lose 21 hit points
1 MP costs 6.451613μ.
1 HP costs 10.0μ.
Imp Ale (30.0 @ +124.2435): 25μ * 67.27305% = 16.818262
cast (30.0 @ +124.2435): 32μ * 67.27305% = 21.527376
flaming crutch (30.0 @ +124.2435): 143μ * 67.27305% = 96.20046
Value of stat gain: 255μ
Factoring in Scarysauce: (6) damage, retal
Imp Ale (30.0 @ +124.2435): 25μ * 67.27305% = 16.818262
cast (30.0 @ +124.2435): 32μ * 67.27305% = 21.527376
flaming crutch (30.0 @ +124.2435): 143μ * 67.27305% = 96.20046
Value of stat gain: 255μ
Monster value: 431.55
You will die in 2 rounds.
Your attack will kill the monster in 4 rounds.
Profit per round: ActionProfitDamageOtherbase; Dandy Lion; crown (0μ)77.58μ--HP: 5.5 MP: 3.5
Building options...
Evaluating '1.55*(5.5+min(0.07*64.0,15)+min(35.0,25)+3.333333 3)'...
Evaluating '1.55*(12+min(0.15*64.0,20)+min(35.0,40)+3.3333333 )'...
Evaluating '1.55*(2.5+min(35.0,5)+0.0)'...
Options built! (8 actions)
Building custom actions...
Custom actions built! (0 actions)
Stasis action chosen: skill 3004
Stasis loop complete.
Unable to enqueue empty action.
Executing macro: scrollwhendone; sub batround; if haseffect 436 || haseffect 8 || haseffect 264 || haseffect 282 || haseffect 283 || haseffect 284; abort "BatBrain abort: poisoned"; endif; if hpbelow 21; abort "BatBrain abort: Danger, Will Robinson"; endif; endsub; skill 3004; call batround;
Round 1: Joe_Mama executes a macro!
Round 1: Joe_Mama casts ENTANGLING NOODLES!
Round 2: Spuds MacKenzie uses his vines to give you an invigorating head massage. It's not the same as giving you a foot massage, but it's in the same ballpark.
You gain 5 hit points
You gain 4 Mana Points
Round 2: Like the majority of whipholders, you crack your whip. Cleo Leo is startled by the noise and goes feral, clawing your opponent for 26 damage. All fear the kitten with a whip!
Round 2: l imp takes 26 damage.
Building options...
Evaluating '1.55*(5.5+min(0.07*64.0,15)+min(35.0,25)+3.333333 3)'...
Evaluating '1.55*(12+min(0.15*64.0,20)+min(35.0,40)+3.3333333 )'...
Evaluating '1.55*(2.5+min(35.0,5)+0.0)'...
Options built! (7 actions)
SmartStasis complete.
You're on your own, partner.
Click here to continue in the relay browser.
I finish the combat in a browser window
Round 2: Joe_Mama casts CANNELLONI CANNON!
Round 3: l imp takes 93 damage.
Round 3: Spuds MacKenzie uses his vines to give you an invigorating head massage. It's not the same as giving you a foot massage, but it's in the same ballpark.
You gain 5 hit points
You gain 4 Mana Points
Round 3: Like the majority of whipholders, you crack your whip. Cleo Leo is startled by the noise and goes feral, clawing your opponent for 35 damage. All fear the kitten with a whip!
Round 3: l imp takes 35 damage.
Round 3: Joe_Mama wins the fight!
After Battle: Spuds MacKenzie surveys the scene from atop the throne, and gains 1 Experience.
After Battle: Cleo Leo notices you're bleeding, nearly faints at the sight, then bandages you with a strip torn from his ruffly sleeves.
You gain 24 hit points
After Battle: Cleo Leo touches you with one exquisitely-manicured claw. The built-up static electricity in his wool suit shocks you.
You gain 26 Mana Points
You gain 40 Meat
After Battle: Cleo Leo does a mincing two-step, swinging his tail like a cane. Though it's utterly lacking in vim and verve, you applaud politely.
You acquire an item: Imp Ale
You acquire an item: cast
You gain 8 Strengthliness
You gain 15 Enchantedness
You gain 9 Smarm
Casting Empathy of the Newt 1 times...
You acquire an effect: Empathy (duration: 10 Adventures)
Empathy of the Newt was successfully cast.
Casting Curiosity of Br'er Tarrypin 1 times...
You acquire an effect: Curiosity of Br'er Tarrypin (duration: 10 Adventures)
Curiosity of Br'er Tarrypin was successfully cast.
Casting Jingle Bells 1 times...
You acquire an effect: Jingle Jangle Jingle (duration: 10 Adventures)
Jingle Bells was successfully cast.
Raven434
06-05-2011, 10:13 PM
No possibly not, but something is making things iffy after noodling (not seen it happen when SS decides to not noodle). Not sure what it is though.
If I change my juju.ccs to invoke noodles manually, my Dandy Lion can go feral and Mafia does not tell me I am on my own.
[ default ]
skill entangling noodles
consult SmartStasis.ash
special action
skill wrath of the trickster god
skill wrath of the lightning god
skill wrath of the volcano god
skill cannelloni cannon
Hmmmm.....
EdFox
06-05-2011, 11:09 PM
This probably has to do with un-stunnable monsters, since BatBrain as yet doesn't know which monsters are immune to multi-round stuns. In all of these cases, was Noodles the last action before SS was finished? I suspect it was, which meant when SS finished mafia saw a KoL error message and aborted.
I can't speak for all cases because the session log doesn't seem to show the on your own partner break but today it was true in all cases.
I'll do my best to develop a list. I don't remember this happening at all before yesterday when I was in the cyrpt but that was using previous versions of SS and BB.
Current list:
Modern Zombie
dirty old lihc
beebee queue
bee swarm
buzzerker
Queen Bee
chatty pirate
moneybee
Larry of the field of signs
carbuncle top
Victor the Insult Comic Hellhound
EDIT: Now this is just annoying. I ran into the bug with a chatty pirate, then shortly thereafter another one went by with no error at all. The only difference is the Spring Raindrop thing. I wish I had noted my MP but these were automated combats.
Bugged:
[480] F'c'le
Encounter: chatty pirate
Strategy: C:\Users\Eszetela\Documents\My Dropbox\KoLmafia\.kolmafia\ccs\Final.ccs [default]
Round 0: edfox loses initiative!
You lose 25 hit points
You lose 30 hit points
Monster value: 445.28
You will die in 2 rounds.
Your attack will kill the monster in 1 rounds.
Profit per round: Action Profit Damage Other base; Pet Cheezling (0μ) 0μ --
1/6 monsters drop goals here.
Round 1: edfox executes a macro!
Round 1: edfox casts ENTANGLING NOODLES!
You're on your own, partner.
Click here to continue in the relay browser.
Round 2: edfox executes a macro!
Round 2: edfox attacks!
Round 3: chatty pirate takes 150 damage.
Round 3: edfox wins the fight!
After Battle: With a plop!, Cheez Wizz hops onto the corpse of your foe, which begins to smoke and dissolve in a rather gruesome fashion. After a minute, the cheezling crawls back up onto your shoulder, burbling happily; it's much warmer than before, and as the heat spreads through your body, you feel much better. And kind of creeped out.
You gain 19 hit points
You gain 17 Muscularity Points
After Battle: Cheez Wizz covers one of its jalapeño "eyes" with a pseudopod, in a sort of rough facsimile of a wink, and burbles cheerfully at you
You gain 150 Meat
You gain 17 Fortitude
You gain 7 Wizardliness
You gain 6 SarcasmUnbugged:
[483] F'c'le
Encounter: chatty pirate
Strategy: C:\Users\Eszetela\Documents\My Dropbox\KoLmafia\.kolmafia\ccs\Final.ccs [default]
Round 0: edfox loses initiative!
You lose 25 hit points
You lose 30 hit points
Monster value: 445.28
You will die in 4 rounds.
Your attack will kill the monster in 1 rounds.
Profit per round: Action Profit Damage Other base; Pet Cheezling (0μ) 0μ --
1/6 monsters drop goals here.
Round 1: edfox executes a macro!
Round 1: edfox casts SPRING RAINDROP ATTACK!
You gain 25 hit points
You gain 22 Muscularity Points
You lose 32 hit points
Round 2: edfox executes a macro!
Round 2: edfox casts ENTANGLING NOODLES!
Round 3: edfox executes a macro!
Round 3: edfox attacks!
Round 4: chatty pirate takes 151 damage.
Round 4: edfox wins the fight!
After Battle: Cheez Wizz covers one of its jalapeño "eyes" with a pseudopod, in a sort of rough facsimile of a wink, and burbles cheerfully at you
You gain 145 Meat
You gain 10 Muscleboundness
You gain 8 Mysteriousness
You gain 11 Smarm
roippi
06-05-2011, 11:42 PM
I'm not sure what I've done here, but downloading fresh copies of Batbrain and SS don't fix the problem. Getting a divide-by-zero error from m_dpr(). Even when I'm below safe moxie. Arg.
[3813] Giant's Castle
Encounter: Procrastination Giant
Round 0: roippi wins initiative!
Division by zero (BatBrain.ash, line 576)
You're on your own, partner.
The offending line in Batbrain:
576: return ceil(my_stat("hp") / m_dpr(0,-adj.stun)) + adj.stun
> ashq import <batbrain.ash>; print( m_dpr(0,-adj.stun));
0.0
Any idea how I managed to do this?
zarqon
06-06-2011, 05:47 AM
That function needs to be fixed. I'm on it. However, I can't say why m_dpr() would return 0 for you.
About the other issue... I'll try submitting the same macro with a character who has Noodles against one of these monsters in the browser (I added a macro testing box to my fight.php) and see what KoL says.
Isn't there some safety check in mafia -- if N rounds have gone by without a consult script doing anything, it will abort? How does it calculate this? It's a possible cause worth investigating for these mystery aborts.
Isn't there some safety check in mafia -- if N rounds have gone by without a consult script doing anything, it will abort? How does it calculate this? It's a possible cause worth investigating for these mystery aborts.
There's a safety check in KoL. If a macro executes 37 instructions in a row without advancing combat you'll get a macro abort to that effect.
zarqon
06-06-2011, 05:58 AM
No I'm talking about for consult scripts. There's a check for those as well, to avoid endless loops in the event that a consult script does nothing.
roippi
06-06-2011, 03:39 PM
I seem to have fixed my issue by nuking my zlib from orbit and starting over. Still not sure what I did to mess up m_dpr().
ETA: would an .isstunnable proxy record make sense? Do $monsters even have proxy data?
Magus_Prime
06-06-2011, 03:46 PM
Still testing BatBrain and SmartStasis in Beecore. I'm now trying Bale's FinalAttack script as well and I'm consistently getting beaten up by the high-level bees. The scripts are making the correct decisions against almost all monsters except for the bees.
FinalAttack seems to prefer physical attacks over magical in almost all circumstances and that, combined with the unstunability of the high-level bees will do me in every time. That would be okay if I were a muscle-class but I'm a myst-class this run.
I'm using the latest versions of zlib, BatBrain, and SmartStasis and I think it's the most recent version of FinalAttack (
The relay browser just generated the "Danger Will Robinson" error and left me to my own devices. Here's the output from KOLMafia with zlib verbosity set to 10.
[2612] Haunted Bedroom
Encounter: Queen Bee
Round 0: Arbos wins initiative!
Round 1: Lefty latches onto your opponent's face with his wriggling tentacles.
Round 1: queen bee drops 3 attack power.
Round 1: queen bee drops 3 defense.
1 MP costs 3.8461537μ.
1 HP costs 0.5366726μ.
Value of stat gain: 10μ
Value of stat gain: 10μ
Monster value: 10
Evaluating 'max(2,(25.0/2))'...
You will die in 3950 rounds.
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Your attack will kill the monster in 1 rounds.
Evaluating 'max(2,(25.0/2))'...
Profit per round: ActionProfitDamageOtherbase; Pair of Ragged Claws (0μ)0μ6.25 Actual: 1 (0 μ/dmg)
Building options...
Evaluating '45*loc(chasm)'...
Evaluating '1.0*(5.5+min(0.07*287.0,15)+min(10.0,25)+0.0)'...
Evaluating '1.0*(12+min(0.15*287.0,20)+min(10.0,40)+0.0)'...
Evaluating '1.0*(28+min(0.25*287.0,30)+min(10.0,60)+0.0)'...
Evaluating '1.0*(2.5+min(10.0,5)+0.0)'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Options built! (36 actions)
Building custom actions...
Evaluating 'max(2,(25.0/2))'...
Custom actions built! (0 actions)
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Stasis action chosen: use 1316
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
This monster is not your huckleberry.
Stasis loop complete.
Evaluating 'max(2,(25.0/2))'...
SmartStasis complete.
1 MP costs 3.8461537μ.
1 HP costs 0.5366726μ.
Value of stat gain: 10μ
Building options...
Evaluating '45*loc(chasm)'...
Evaluating '1.0*(5.5+min(0.07*287.0,15)+min(10.0,25)+0.0)'...
Evaluating '1.0*(12+min(0.15*287.0,20)+min(10.0,40)+0.0)'...
Evaluating '1.0*(28+min(0.25*287.0,30)+min(10.0,60)+0.0)'...
Evaluating '1.0*(2.5+min(10.0,5)+0.0)'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Options built! (36 actions)
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Executing macro: scrollwhendone; sub batround; if haseffect 436 || haseffect 8 || haseffect 264 || haseffect 282 || haseffect 283 || haseffect 284; abort "BatBrain abort: poisoned"; endif; if hpbelow 14; abort "BatBrain abort: Danger, Will Robinson"; endif; endsub; sub batsub1; attack; call batround; endsub; call batsub1; repeat ;
Round 1: Arbos executes a macro!
Round 1: Arbos attacks!
Round 2: Lefty snaps at your opponent with his sharp claws, dealing 3 (+1) damage.
Round 2: queen bee takes 4 damage.
You lose 35 hit points
Round 2: Arbos attacks!
Round 3: Lefty snaps at your opponent with his sharp claws, dealing 6 (+3) damage.
Round 3: queen bee takes 9 damage.
You lose 35 hit points
Round 3: Arbos attacks!
Round 4: queen bee takes 28 damage.
You lose 33 hit points
Round 4: Arbos attacks!
You lose 31 hit points
Round 5: Arbos attacks!
Round 6: Lefty snaps at your opponent with his sharp claws, dealing 1 (+4) damage.
Round 6: queen bee takes 5 damage.
You lose 34 hit points
Round 6: Arbos attacks!
You lose 35 hit points
Round 7: Arbos attacks!
You lose 32 hit points
Building options...
Evaluating '45*loc(chasm)'...
Evaluating '1.0*(5.5+min(0.07*287.0,15)+min(10.0,25)+0.0)'...
Evaluating '1.0*(12+min(0.15*287.0,20)+min(10.0,40)+0.0)'...
Evaluating '1.0*(28+min(0.25*287.0,30)+min(10.0,60)+0.0)'...
Evaluating '1.0*(2.5+min(10.0,5)+0.0)'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Evaluating 'max(2,(25.0/2))'...
Options built! (36 actions)
You're on your own, partner.
Round 8: Arbos uses the divine can of silly string and uses the divine noisemaker!
Round 9: queen bee takes 29 damage.
You gain 18 Wizardliness
Round 9: queen bee takes 19 damage.
You gain 14 Fortitude
You lose 37 hit points
You've had the crap beaten out of you... attempting to find some more crap.
Visiting Relaxing Hot Tub in clan VIP lounge
You lose an effect: Beaten Up
You gain 238 hit points
Any thoughts?
Theraze
06-06-2011, 04:10 PM
Bees still don't have official data in mafia, so it thinks that (all) bees have 1 attack, 1 defense, and 1 hp. Or maybe 0 on those. Anyways, as such, it expects to be able to stasis them as long as possible with no risk.
roippi
06-06-2011, 04:22 PM
You might just want to have a separate [bee] section in your CCS. I'm not sure if the bees even have monster data for batbrain to parse yet.
Therazeninja'd
Weatherboy
06-06-2011, 05:05 PM
Also, Bale's FinalAttack only processes combat spells when Shieldbutt isn't available, you're a saucerer, have jala and jaba active, and haven't been laid in a couple weeks. ;)
Winterbay
06-06-2011, 06:41 PM
BatBrain uses the zlib variable unkown_ml for unkown monsters, if you get constantly beaten up by the bees that indicates to me that you have it set too low. I set mine at 170 and that solved most of my problems with the bees (well at the moment I have it set to a special bee section of BatBrain but that is my own modification)
Theraze
06-06-2011, 07:08 PM
170 is the default, but it still decides sometimes for me that it should stasis rather than noodle/abort when my attack is under 100... It's possible that unknown_ml isn't used anymore since mafia makes internal monster overrides, so it exists...
Winterbay
06-06-2011, 07:18 PM
Well, even if it exists it is set to 0 so it still counts (I know this since even the bee monsters I've ran into under a day still gets the benefit of adding the unknown_ml on top of my calculated "actual" value, at least I think so...)
Theraze
06-06-2011, 08:37 PM
No clue why it hasn't treated mine as 170 then, since they're all there, and it's still tried to stasis.
Winterbay
06-06-2011, 09:49 PM
I just tried it with the last bee I ran into:
> ash monster_hp(to_monster("beebee swarm"))
Returned: 0
> ash monster_attack(to_monster("beebee swarm"))
Returned: 0
> ash monster_defense(to_monster("beebee swarm"))
Returned: 0
The code in batbrain clearly says to treat 0-health monsters as unknown monsters.
Theraze
06-06-2011, 10:29 PM
Eh, seems if I set it to 0 it seems it should allow me to manually fight unknown monsters, so... that should force it to work properly for me, at least. :)
If you are using a Black Cat, drop rate is never 100%. Modest suggestion to change line 337:
if (!should_pp && count(stolen) == 0 && (rec.rate*(item_drop_modifier()+100)/100.0 < 100 || my_familiar() == $familiar[Black Cat])) should_pp = true;
Yes, I have a character doing Black Cat right now. ;)
Theraze
06-07-2011, 04:46 AM
Definitely not working right for unknown... I have it set to 0, and it's still automatically fighting without an abort.
zarqon
06-07-2011, 05:32 AM
Mafia doesn't even know about bees yet. As of now, $monster[queen bee] generates an error. Note that it thought your stat gain was worth 10 meat. That's because the monster's stats were all 0.
So for now, looks like a regular CSS might be the only way to get special handling for bees, since consult scripts can't even recognize them.
This did prompt me to add additional debugging prints showing monster name/stats to SS though, which will wend their way into the next update. It also makes me wonder whether or not the "unknown_ml" setting is working.
And for future reference, verbosity 9 is ideal for debugging. Verbosity 10 just adds all the modifier evaluations, which we don't need to see unless you're getting an "Evaluator syntax error".
EDIT: Wow, ninjas all over the place. It appears further research into unknown_ml is necessary. Bale, good point about the kitties -- will add your tweak.
Theraze
06-07-2011, 05:40 AM
unknown_ml is not working, but it's going through the consult scripts with the bees. The automatically-adding-monsters-to-mafia's-internal-database thing that got added recently made unknown monsters work with consult scripts, but I think it broke the unknown_ml stat thing as well.
I'm tired and repeating myself. Time to sleep. :)
Edit: Here's what they return. You're expecting 0, I think what you need to look for is 1 instead... problem is that there are some legitimate '1' stat mobs.
> last_monster
Returned: bee swarm
> monster_attack
Returned: 1
> monster_defense
Returned: 1
> monster_hp
Returned: 0
The relevant part would be this:
if (m == $monster[none] || monster_attack(m) == 0 || monster_attack(m) == monster_level_adjustment())
adj.att = to_int(vars["unknown_ml"]);
if (m == $monster[none] || monster_defense(m) == 0 || monster_defense(m) == monster_level_adjustment())
adj.def = to_int(vars["unknown_ml"]);
if (m == $monster[none] || monster_hp(m) == 0 || monster_hp(m) == monster_level_adjustment())
adj.dmg[$element[none]] = to_int(vars["unknown_ml"]);
becoming this, or something like it:
if (m == $monster[none] || (monster_attack(m) == 1 && monster_defense(m) == 1 && monster_hp(m) == 0) || monster_attack(m) == monster_level_adjustment())
adj.att = to_int(vars["unknown_ml"]);
if (m == $monster[none] || (monster_attack(m) == 1 && monster_defense(m) == 1 && monster_hp(m) == 0) || monster_defense(m) == monster_level_adjustment())
adj.def = to_int(vars["unknown_ml"]);
if (m == $monster[none] || (monster_attack(m) == 1 && monster_defense(m) == 1 && monster_hp(m) == 0) || monster_hp(m) == monster_level_adjustment())
adj.dmg[$element[none]] = to_int(vars["unknown_ml"]);
Winterbay
06-07-2011, 05:52 AM
Actually what the script expects is for the unspaded monster to have every stat compared to monster_level_adjustment(), but if you mcd is set to 1 in that case I wonder why that didn't affect the hp.
Theraze
06-07-2011, 06:21 AM
Yeah, MCD at 0, it detected the attack/def at 1. I thought there was a change regarding those as the lowest values for unspaded, but the only one I can find there is this change: http://kolmafia.us/showthread.php?6565-9278-If-we-see-an-unknown-monster-temporarily-register-it-so-that-consult-scripts
Winterbay
06-07-2011, 06:37 AM
What is odd is the fact that your monster-override reports 1 for attack and defense and 0 for hp while mine reported 0 for all three values...
zarqon
06-07-2011, 07:13 AM
I ran elaborate tests on this a while ago -- some "unknown"/unspaded monster stat values include +ML, others don't. However, I re-tested just now and found another problem.
Filtering out the monsters which have all three stats at 0, here are some relevant monster stats at ML +15:
Big Creepy Spider (ATT 16, DEF 15, HP 16)
Brutus, the Toga-Clad Lout (ATT 0, DEF 0, HP 295)
Completely Different Spider (ATT 16, DEF 15, HP 16)
Crate (ATT 16, DEF 15, HP 16)
Danglin' Chad (ATT 0, DEF 0, HP 315)
Don Crimbo (ATT 0, DEF 0, HP 1000015)
Drunken Half-Orc Hobo (ATT 16, DEF 15, HP 16)
Essence of Tofu (ATT 0, DEF 0, HP 80)
Fiendish Can of Asparagus (ATT 16, DEF 15, HP 16)
Fluffy Bunny (ATT 16, DEF 15, HP 16)
Frog (ATT 3, DEF 3, HP 3)
Gorgolok, the Demonic Hellseal (ATT 0, DEF 0, HP 415)
Hellseal guardian (ATT 0, DEF 0, HP 225)
Hellseal pup (ATT 0, DEF 0, HP 103)
Hung-over Half-Orc Hobo (ATT 16, DEF 15, HP 16)
Lumpy, the Demonic Sauceblob (ATT 0, DEF 0, HP 515)
Monty Basingstoke-Pratt, IV (ATT 0, DEF 0, HP 255)
Mother hellseal (ATT 0, DEF 0, HP 172)
Mother Slime (ATT 0, DEF 0, HP 3015)
n Bottles of Beer on a Golem (ATT 15, DEF 15, HP 15)
A n Stone Golem (ATT 15, DEF 15, HP 15)
A n-Dimensional Horror (ATT 15, DEF 15, HP 15)
A n-Headed Hydra (ATT 15, DEF 15, HP 15)
Newt (ATT 3, DEF 3, HP 3)
Possessed Can of Tomatoes (ATT 16, DEF 15, HP 16)
Protector Spectre (ATT 0, DEF 165, HP 131)
Rushing Bum (ATT 16, DEF 15, HP 16)
Salamander (ATT 3, DEF 3, HP 3)
Skate Board Member (ATT 0, DEF 0, HP 765)
Sleeping Knob Goblin Guard (ATT 16, DEF 15, HP 16)
Slow Talkin' Elliot (ATT 0, DEF 0, HP 215)
Sub-Assistant Knob Mad Scientist (ATT 16, DEF 16, HP 16)
The Beast with n Ears (ATT 15, DEF 15, HP 15)
The Beast with n Eyes (ATT 15, DEF 15, HP 15)
The Big Wisniewski (ATT 0, DEF 0, HP 2015)
The Demon Spirit of New Wave (ATT 0, DEF 0, HP 515)
The Ghost of Fernswarthy's n great-grandfather (ATT 15, DEF 15, HP 15)
The Man (ATT 0, DEF 0, HP 2015)
The Whole Kingdom (ATT 16, DEF 15, HP 10000015)
Undead Elbow Macaroni (ATT 16, DEF 16, HP 16)
Unearthed Monstrosity (ATT 0, DEF 0, HP 515)
It looks initially like we can consider a stat to be "unknown" if stat <= +ML. However there are a few monsters, such as spiders and bunnies, where the defense is one less, meaning it would be considered as unknown_ml (erroneous, since the unadjusted stat is actually 1). Is this due to defense being 90% of attack? I guess this is calculated after adding the ML adjustment -- which causes us troubles, since it's not intuitively reversible at +ML of 10 or greater. For instance, at ML +20, the bunny's defense stat ((1+20)*0.9 = 19) minus ML adjustment would be -1. So we have to add 10% of +ML back in to arrive at the actual defense stat. Yikes.
Also note that exception needs to be taken for CLEESH monsters; +ML is not added to their stats either (should it be?).
EDIT: Also noticed something unusual with basement monsters. With no +ML, their stats are all 1. However, with +ML, their stats equal the monster level adjustment. ?!?!!!?!
ANOTHER EDIT TO NOTE: The defense stat issue starts to manifest at ML +9, not 10, since it's added to the defense stat before being reduced.
And this is all somewhat beside the point of actually unknown monsters. What does mafia say their stats are in combat? Further, what does mafia return for last_monster()?
Winterbay
06-07-2011, 07:46 AM
I'll let you knwo as soon as I encounter my first bee of the day. I'll add some print statements to my consult script to make sure I get those.
Edit:
So, I added the following code to my Consult script:
vprint("You are fighting a " + to_string(foe) + ". Mafia considers that this monster has an attack of " + monster_attack() + " or " + monster_attack(foe) + " when given a monster name.", 9);
vprint("Mafia further considers that this monster has a defense value of " + monster_defense() + " or " + monster_defense(foe) + " when given a monster name.", 9);
vprint("Mafia further further considers that this monster has a HP value of " + monster_hp() + " or " + monster_hp(foe) + " when given a monster name.", 9);
vprint("Your current ML-adjustment is: " + monster_level_adjustment() + ".", 9);
Which gave the following output versus a beebee queue (first monster of the day actually):
You are fighting a beebee queue. Mafia considers that this monster has an attack of 1 or 0 when given a monster name.
Mafia further considers that this monster has a defense value of 1 or 0 when given a monster name.
Mafia further further considers that this monster has a HP value of 0 or 0 when given a monster name.
Your current ML-adjustment is: 0.
This indicates that the difference between my test and theraze's test earlier might've been that I tested with monster_hp($monstername) and Theraze tested with monster_hp()...
Edit, edit:
Also
> ash last_monster()
Returned: beebee queue
Winterbay
06-07-2011, 09:49 AM
Back on the topic of aborting after noodling. I think the problem may be (it has at least been the case in many of my fights I've noticed) that the abort for low health is calculated before teh enqueuing of the noodles and thus the script aborts after I've cast noodles since it first casts noodles and then calls the batround-sub which includes the abort.
So, despite me being able to 1 or 2 shot the monster with my spells it aborts since the abort value is set too high (often around my max hp).
Abort for low health is calculated at 1 hit of the monster being able to kill you. That has nothing to do with noodles. However I see the problem. If you are noodling, that abort is irrelevant for 2 turns. Perhaps there needs to be two versions of batround? With and without the abort so that batroundsafe can be called for the first two rounds if noodling... I'd say it is getting complicated, but honestly it got complicated a long time ago.
Winterbay
06-07-2011, 10:23 AM
The problem is that after it has noodled the chances of the monster one-shotting me is rather small...
I changed that line in batround from
res.append("; abort \"BatBrain abort: poisoned\"; endif; if hpbelow "+ceil(m_regular()+1)+"; abort \"BatBrain abort: Danger, Will Robinson\"; endif; ");
to
res.append("; abort \"BatBrain abort: poisoned\"; endif; if hpbelow "+ceil(m_dpr(0,0)+1)+"; abort \"BatBrain abort: Danger, Will Robinson\"; endif; ");
Part of the problem here is that m_regular() does not take stunning into account so it will stay the same no matter how stunned the monster is, while m_dpr(0,0) does take the planned stunning effect into account.
A recent fight against a pirate shows the following:
[575] Barrrney's Barrr
Encounter: Yes, You're a Rock Starrr
Encounter: tetchy pirate
Strategy: D:\Mafia\ccs\PM.ccs [default]
Round 0: winterbay wins initiative!
You lose 12 hit points
1 MP costs 5.882353µ.
1 HP costs 1.7647059µ.
bottle of rum (20.0 @ +30.0): 35µ * 26.0% = 9.1
cocktail napkin (10.0 @ +30.0): 25µ * 13.0% = 3.25
Value of stat gain: 185µ
bottle of rum (20.0 @ +30.0): 35µ * 26.0% = 9.1
cocktail napkin (10.0 @ +30.0): 25µ * 13.0% = 3.25
Value of stat gain: 185µ
Monster value: 253.35
You will die in 7 rounds.
Your attack will kill the monster in 4 rounds.
Profit per round: Action Profit Damage Other base; Rogue Program (0µ) 34.94µ 5.94 (0 µ/dmg) 31.5% stun chance MP: 5.94
Building options...
Options built! (25 actions)
Building custom actions...
Custom actions built! (1 actions)
Custom action: use 5120 (no stun)
Executing macro: scrollwhendone; sub batround; if haseffect 436 || haseffect 8 || haseffect 264 || haseffect 282 || haseffect 283 || haseffect 284; abort "BatBrain abort: poisoned"; endif; if hpbelow 12; abort "BatBrain abort: Danger, Will Robinson"; endif; endsub; use 5120; call batround;
Round 1: winterbay executes a macro!
Pirate insults known: 1 (0.00%)
Round 1: winterbay uses the Massive Manual of Marauder Mockery!
You acquire an effect: Embarrassed (duration: 1 Adventure)
Round 2: tetchy pirate takes 2 damage.
Round 2: tetchy pirate takes 4 damage.
You lose 13 hit points
Building options...
Options built! (25 actions)
Stasis action chosen: attack
Stasis loop complete.
SmartStasisStun => 3.0
Executing macro: scrollwhendone; sub batround; if haseffect 436 || haseffect 8 || haseffect 264 || haseffect 282 || haseffect 283 || haseffect 284; abort "BatBrain abort: poisoned"; endif; if hpbelow 1; abort "BatBrain abort: Danger, Will Robinson"; endif; endsub; skill 3004; call batround;
Round 2: winterbay executes a macro!
Round 2: winterbay casts ENTANGLING NOODLES!
Building options...
Options built! (24 actions)
SmartStasis complete.
Which is what I would expect when I cast noodles.
zarqon
06-08-2011, 10:34 AM
It originally used m_dpr() but that's actually a pretty useless number there -- if the monster doesn't hit it does 0, and if it hits it does more than m_dpr(). A number in the middle doesn't really help you not die, which is the point of the abort.
Whether we use m_dpr() or m_regular(), we can get unintended behavior since that number is set in stone when batround is submitted. If you enqueue noodles and then a series of attacks, and the monster can deal you a lot of damage, m_dpr() would return 0 since the monster is stunned, so the abort (if hp < 1, abort!) would be useless for the entire combat. Using m_regular() avoids that problem but as Winterbay and others found, it causes unwanted aborts when you are actually safe due to stunning. This gets more complicated in that it's possible to enqueue multiple stunners of varying durations at any point in the combat. In other words, that number needs to be variable rather than constant, which is impossible in a BALLS subroutine.
There are two ways around this.
1) Don't include a low HP abort at all. Allow people to macro themselves to death.
2) Take that abort out of batround. Instead, include a separate abort after each queued action, and only if the estimated stun at that point in the combat is 0.
I'm leaning towards 2, but could be easily persuaded towards 1, due to the extreme difficulty of tracking stunning during combat (which BatBrain doesn't and may never do).
Handling for stunning is probably the least developed part of BatBrain at the moment, so it's good to have this discussion. I do feel that m_event() was a really great move in the right direction, though. Basically, a monster is just a passive damage effect which rewards you for removing it. :)
1) Don't include a low HP abort at all. Allow people to macro themselves to death.
2) Take that abort out of batround. Instead, include a separate abort after each queued action, and only if the estimated stun at that point in the combat is 0.
3) Allow user defined abort threshold by checking autoAbortThreshold.
You forgot option 3. I'd also agree that 2 would be ideal. If you cannot manage that then please check autoAbortThreshold so that the player can decide the HP level he wants to abort combat or leave out the HP abort if it is '-0.05' since option 3 is clearly superior to option 1.
zarqon
06-08-2011, 10:45 AM
Sorry? That's not an option -- adding a setting does not solve the problem of aborting when stuns are in effect.
I'm not sure solution 2) would work. If I understand correctly, when you add an "abort <condition>" line in a KoL macro, it stays active for the rest of the macro. So if your macro contains several "abort hpbelow X" lines, they will all stay active for the duration of the macro.
It would be pretty easy to test though, I'll go ahead and do just that in a few hours.
Sorry? That's not an option -- adding a setting does not solve the problem of aborting when stuns are in effect.
That setting is already part of mafia. It is set on the HP/MP Usage tab and mafia's default combat macros will abort when HP drops below that percentage of HP.
It doesn't solve the problem of how to handle stuns. But if you are going to escape that problem by allowing the user to macro themselves to death, it would make sense to use that setting instead.
I'm not sure solution 2) would work. If I understand correctly, when you add an "abort <condition>" line in a KoL macro, it stays active for the rest of the macro. So if your macro contains several "abort hpbelow X" lines, they will all stay active for the duration of the macro.
Actually, you misunderstand the two different ways of handling aborts. A top level abort which contains its own condition will be valid for an entire macro. However, if you tuck an abort into an if-endif statement and do not use a cond for the abort, then it is valid only when the if cond is true.
zarqon
06-08-2011, 11:20 AM
@Bale: Oh! It still doesn't solve the problem we're talking about but it is nice in that it would allow users to choose which of two problems they want to have in effect. So, it's a viable workaround if option 2 doesn't work, but if it's something the script can automate better, I'd rather not put the burden on the user.
I like script transparency with mafia settings, so I will include that check, either to replace option 1 or to supplement option 2.
Haha, I suddenly imagined someone watching an automated combat and frantically adjusting their autoAbortThreshold property on the fly to account for stuns.
If Bale is correct about aborts in conditions (which I suspect he is, due to the lack of complaints since moving most of BatBrain's aborts inside conditionals), I will proceed with this after each action:
if (m_hit_chance() > 0) macro += "; if hpbelow"+max((m_regular()+1),autoAbortThreshold*my_stat("hp"))+"; abort dangerrobwillinson"
After each action, if the monster has any chance of hitting you, it adds an abort if your HP is less than one hit from the monster, or your autoAbortThreshold HP amount, whichever is higher. This also solves a similar problem with delevel-and-plink strategies (the monster's damage number will decrease correctly), potentially avoiding other unwanted aborts.
Winterbay
06-08-2011, 11:38 AM
A construct such as
skill x
if hpbelow y1
abort "Low health"
endif
call batround
skill z
if hpbelow y2
abort "Low health"
endif
call batround
Could work I guess and allow recalcualtion of variables between each thing.
Winterbay
06-08-2011, 11:49 AM
On a different note. The high level bees appear to, apart from being resitant to multi level stuns, to have a spell resistance of 50%. I'm going to guess that it's possible that the medium level ones have a 25% resitance but I've not tested that so far. Adding:
case $monster[queen bee]:
case $monster[beebee king]:
foreach el in $elements[]
mres[el] = 0.5;
mres[$element[none]] = 0.5;
break;
To the monster resistance part appears to have stopped my consult script from underkilling the bees today.
fronobulax
06-08-2011, 12:48 PM
Dirty old lich (http://kol.coldfront.net/thekolwiki/index.php/Dirty_old_lihc) is not spaded, thus it has incomplete monster data in mafia, thus SS/BB treats it as an unknown and returns control to the user because I have not set any variables telling it to do otherwise, correct?
If I don't want that I can either
Wait until it is spaded and data is added to mafia.
Make a special entry in my CCS for it.
Set variables so SS/BB has a clue what to do.
For the latter, what are the variables? unknown_ml is 170 although it is not clear to me what a better value should be or even if the current versions of SS/BB use it in the expected manner.
Thanks.
Winterbay
06-08-2011, 01:34 PM
If things are working as they should haveing unkown_ml at anythign other than 0 should use that value for your unkown monster. Mine seems to be doing that so I wonder why it doesn't for everyone...
Theraze
06-08-2011, 02:21 PM
Next time you fight one, cancel out of the fight and check monster_attack or whatever else is unspaded. Validate what, exactly, it's set to, and note your MCD if set as well. Check in BatBrain to see what it has for substituting unknown_ml... at last check, it was having 0 for the stats in BatBrain, but on my testing, the monsters had a 1 instead during combat. If this is the case for you as well, you may need to either replace the 0 with 1, and expect it to be wrong when dealing with the first few weak monsters, or do one of your other options...
Winterbay
06-09-2011, 07:15 AM
I just updated the plural monsters file with information on the swarms of ghoul whelps and bees. The flock of seagulls are listed as a group monster by the wiki but says nowhere how big the group is so I left that one out.
Winterbay
06-09-2011, 10:45 AM
Double posting, but since it's a completely different matter I think it's warranted.
I ran into the naughty sorority nurse today. She is stated in batbrain.ash ot have no attack so m_dpr which calls m_even which calls m_regular returns 0 which means that die_rounds will get a division by 0 error.
I changed die_rounds to the following:
int die_rounds() // how many rounds at the current ML and stun until you die
{ //naughty sorority nurse and quiet healer have no attack
return ($monsters[naughty sorority nurse, quiet healer] contains m ? 999999 : ceil(my_stat("hp") / m_dpr(0,-adj.stun)) + adj.stun);
}
Which I think will avoid that problem.
Why not replace m_dpr() by max( m_dpr(), 0.0001 ) or something like that?
Winterbay
06-09-2011, 11:11 AM
I guess that could work as well, and also be more general in case there are any other cases where m_dpr turns out to be 0.
zarqon
06-09-2011, 12:14 PM
I already mentioned that I'd be fixing that silly division by 0 error.
unknown_ml is currently working correctly for unknown stats, but not unknown monsters. It is a little bit broken for the defense stat with +9 or higher ML, as I mentioned previously, but a fix for that is already in my local copy and will be in the next update.
That leaves unknown monsters. I found unknown monsters (tested on my shadow, since mafia doesn't ambiguate those yet) also had stats of 1, making them completely indistinguishable at runtime from known monsters with stats of 1. (And thus unknown_ml can't behave as desired.)
It would be far more useful if they had stats of 0, since this is what other unknown stats are in mafia's monster data. I think we can submit this as a feature request to make detecting unknown monsters possible. And for consistency. I'll do that soonish unless someone (like Bale perhaps) beats me to it. Right now I'm attempting to repair my laptop myself -- it is in pieces all over my table at the moment. Next step: attempting to rework a GPU chip using a hair dryer and an infrared thermometer. Exciting times.
Theraze
06-09-2011, 01:24 PM
That would take changing the Math.max( 1, <calculation> ) on each set to have 0 instead... can in-battle attack or those actually ever start at 0 based on -ML?
For the next version of BatBrain, could the 'meat-symbol' be changed from the actual symbol to µ so that it's actually portable between systems?
zarqon
06-09-2011, 02:41 PM
Originally did that, it printed as "μ" on this Linux machine rather than the actual character. Will do entity_encode("μ") -- a bit of a pain, but will work everywhere.
Winterbay
06-09-2011, 02:53 PM
On my system it kept showing up as a rather funny box and some other symbol as well. Not so much of a problem, but looks odd.
Theraze
06-09-2011, 07:46 PM
I'm guessing that having the symbol in is also why, every time I edit BatBrain with a standard text editor (Notepad), it adds 3 non-printing characters to the start of the file that have to be removed with a hex editor before mafia can parse it again.
Edit: Any way to have kill_rounds combine damage and hit chance? I split out a version that does dmg_dealt(regular(1)) * hitchance(1) inside, but if there were an official way to do this, it would be more accurate for monsters that you can easily kill if you'd ever hit them...
It would be far more useful if they had stats of 0, since this is what other unknown stats are in mafia's monster data. I think we can submit this as a feature request to make detecting unknown monsters possible. And for consistency. I'll do that soonish unless someone (like Bale perhaps) beats me to it.
Sure. I'll take care of that for you. I'm pretty sure that this counts as a bug, not a feature request since it is not operating as intended.
That was FAST! Unknown monster issue fixed in Revision 9427, thanks to Velocity Veracity.
Addendum: Yeah, it's only been 2 hours since my last post in this thread so I'm double-posting. I'm a minion so I can decide when new posting is relevant enough to break the rules. :P
Theraze
06-10-2011, 02:42 AM
Another bug... hitchance can go negative, which does bizarre things to some of the other calculations. I've replaced the following line:
return minmax((6.0 + attack - max(monster_stat("def"),0.0))/10.5,0.0,1.0) - fumble_chance(); // change minimum to critchance
with this:
return max(((9+numeric_modifier("Critical Hit Percent"))*(numeric_modifier("Critical")+1)/100), minmax((6.0 + attack - max(monster_stat("def"),0.0))/10.5,0.0,1.0) - fumble_chance()); // change minimum to critchance
Basically, take the higher of critchance or the actual calculation... I believe that the critchance is correct. It starts with a 9% chance + percentage modifiers, multiplied by 1+critical multipliers, divided by 100 to provide just a percentage chance.
Might be better to do that as a separate function though... critical_chance... Let me work that up.
Edit: Have these showing like this now:
> ash import <batbrain> hitchance(1)
Returned: 1.0
> ash import <batbrain> critical_chance()
Returned: 0.09
> ash import <batbrain> fumble_chance()
Returned: 0.045454547 This is just below fumbles, since I thought they were related... anything similar to Dr. Awkward's fumble code probably needs to be added. At the very least, Dr. Awkward probably forces critical chance to 0.
// ======= CRITICALS =======
float critical_chance() {
return (9+numeric_modifier("Critical Hit Percent"))*(numeric_modifier("Critical")+1)/100;
}
I've replaced the line above with this, which should hopefully be more accurate...
return minmax((6.0 + attack - max(monster_stat("def"),0.0))/10.5 + critical_chance() - fumble_chance(),0.0,1.0); // change minimum to critchance
After deciding if it will hit or miss with normal chances, add the chance of a critical hit, subtract fumbles... though this might be better?
return minmax((6.0 + attack - max(monster_stat("def"),0.0))/10.5 + critical_chance() - fumble_chance(),critical_chance(),1.0-fumble_chance()); // change minimum to critchance
Since your lowest chance to hit should be only getting criticals, and your highest chance to hit shouldn't be higher than your chance of fumbling...
Winterbay
06-10-2011, 07:57 AM
Isn't it just enough to move the fumble_chance() part into the minmax-statement? I mean unless fumble_chance() = 0 there is no way I am going to reach 1.0 in hitchance anyway...
Theraze
06-10-2011, 01:33 PM
That's why my max in the lower ones is 1.0-fumble_chance(), and the min is critical_chance(). :) It does have +critical_chance()-fumble_chance() in the actual section being compared, but I put them as boundaries as well.
Edit: Winterbay found a case where the scaling HP on a bee was set to his MCD level, so BatBrain decided that it was an unknown monster despite it being properly set. This is due to the unknown monster detection code matching on monster none, stat 0, or stat MCD. However, since Veracity's awesome change, this is no longer the case... it's only ever stat 0 now.
> current_mcd
Returned: 10
> monster_attack
Returned: 0 That would be the swarm of ghuol whelps with no data on them returning 0 for all stat-checks. This means we should just be able to use this for the unknown checks:
if (monster_attack(m) == 0)
adj.att = to_int(vars["unknown_ml"]);
if (monster_defense(m) == 0)
adj.def = to_int(vars["unknown_ml"]);
if (monster_hp(m) == 0)
adj.dmg[$element[none]] = to_int(vars["unknown_ml"]);
I said it the last time and I will say it again ... those fixes are all wrong.
Looking over the wiki page for hit chance (http://kol.coldfront.net/thekolwiki/index.php/Hit_Chance) (<- link):
baserate = ((6+attack-defense)/10.5), minimum 0 maximum 1
fumble chance = fumbrate
normal hit = (1-fumbrate)*(baserate-critrate)
critical hit = (baserate) or (critrate), whichever is lesser
miss = (1-fumbrate)*(1-baserate)
This is obvisouly wrong, since it would mean you can't hit with criticals if you can't hit without them. "critical hits are a fixed percentage of all non-fumble attack attempts" actually means that it's
normal hit = (1-fumbrate)*(1-critrate)*baserate
critical hit = (1-fumbrate)*critrate
miss = 1 - normal hit - critical hit
hit = (1-fumbrate)*( critrate + (1-critrate)*baserate )
So, with Theraze's function definition(s) ..
float baserate = minmax (6.0 + attack - max(monster_stat("def"),0.0))/10.5, 0, 1);
float critrate = critical_chance();
return (1-fumble_chance())*( critrate + (1-critrate)*baserate );
roippi
06-10-2011, 04:27 PM
I believe 10.5 in the denominator should be an 11, the wiki is incorrect. [1 (http://kolspading.com/forums/viewtopic.php?f=3&t=82)]
I notice that you just posted this on the discussion page there; right above your post there seems to be some discussion of why they didn't correct the value to 11. I still don't quite understand why after reading it.
I believe 10.5 in the denominator should be an 11, the wiki is incorrect. [1 (http://kolspading.com/forums/viewtopic.php?f=3&t=82)]
Sounds legit! (http://xkcd.com/906/)
Theraze
06-10-2011, 04:33 PM
Sounds good to me. Implemented into my copy of BatBrain. :) Little tweak... needs another left parenthesis before the 6.0 on the baserate.
roippi
06-10-2011, 04:54 PM
Sounds legit! (http://xkcd.com/906/)
haha. I should've linked to cat fancy magazine.
ETA: oh great, THIS post gets a new page.
Winterbay
06-10-2011, 05:01 PM
There is an error in batfactors.txt, the skill Harpoon! (however not very useful it may be) is actually usable above water. Thus the "zone(sea)" is a bit on the wrong side, the formula is a bit simplistic as well but I guess it won't matter that much even though with enough modifiers it could be a rather big difference.
fronobulax
06-12-2011, 12:06 PM
I don't think this is new information but
[2091] Battlefield (Hippy Uniform)
Encounter: Naughty Sorority Nurse
Strategy: C:\KolMafia\ccs\CafeBoob.ccs [default]
Round 0: cafeboob loses initiative!
Division by zero (BatBrain.ash, line 576)
Round 1: cafeboob attacks!
css is consult SmartStasis and then attack with weapon.
I don't really like division by zero, unless it is a deliberate kludge to force an execution error, but I am comfortable with taking the limit as the denominator approaches zero.
Winterbay
06-12-2011, 06:56 PM
that is a known bug and should be solved in the next update of batbrain. In the meantime you can change the offending function according to the discussion a couple of posts up.
Winterbay
06-13-2011, 06:47 AM
Volcanometeor showereruption only works if you have at east 1 volcanic ash in your inventory. I suggest adding the followiong to the SKILLS-section:
case 55: if(item_amount($item[volcanic ash]) == 0)
continue;
break;
zarqon
06-13-2011, 05:51 PM
1.4 Updates
This update may break your consult scripts, since hitchance() now takes a string rather than an integer -- hitchance() is now used whenever adding an item to opts[], and it works for all canonically-ID'd actions. A better name for it now might be success_chance() but I stuck with the old name. Basically I moved all the code dealing with factoring actions due to blocking into hitchance(), such as Cunctatitis, black kitties, and certain item/skill-blocking monsters. It made the code cleaner in a few places.
It should hopefully also reveal that there is no need to actually use the hitchance() function in your script. This function is for building options -- your script needs only deal with the options.
There's one factoring element I'm not sure about but I need to know to make this information complete. What constitutes a "spell" and is there a programmatic way of identifying if something is a spell? Some monsters, like the royal bees, have spell resistance, which cannot simply be added to their normal resistance but should instead be used to reduce the damage dealt by spells. Certain other monsters are totally immune to spells, which I believe may even include Noodles, but not other multistuns. I'd rather not make a big list of spells if this is something which could be calculated programatically.
Fixed: hp aborts when the monster is stunned -- as I speculated I would, they are now added each round when the monster has any chance of hitting you, and can be tweaked using autoAbortThreshold. When the monster is not stunned, BatBrain macros will abort if your HP goes below one monster attack or your autoAbortThreshold, whichever is higher. Transparency with and improvement of mafia properties -- I feel satisfied with that.
Fixed: division by zero error.
Fixed: meat symbol.
Fixed: critical_chance() is now a part of damage and hitchance calculations. Thanks Theraze.
Fixed: hitchance will never be negative.
Changed: hitchance formula uses 11 rather than 10.5 [1] (http://www.youtube.com/watch?v=jEUQUFvuV6I).
Fixed: Volcanometeor Showercovolcanoconiosisruption requires volcanic ash.
Began: support for multistun immunity. Right now it only includes three monsters -- Cyrus and royal bees. Having this in monster proxy records would be better, someday.
Tweaked: some of the core functions to work on copies of records rather than the supplied parameter.
Fixed: detection of unknown monster defense at higher ML. Unknown monsters should be working now.
Added: more comments (http://www.youtube.com/watch?v=lwnFE_NpMsE&feature=list_related&playnext=1&list=SL).
ETA: would an .isstunnable proxy record make sense? Do $monsters even have proxy data?
That's an excellent idea, but I don't think monsters have proxy info yet. A few excellent fields for them would be booleans 'multistun' and 'copy'. A 'boss' flag is nice in theory but would not actually be useful compared to those other two.
If you are using a Black Cat, drop rate is never 100%.
Just noticed I forgot to add that in. Added it just now, so it will definitely be in the next update, which I don't think will be too long coming.
Edit: Any way to have kill_rounds combine damage and hit chance? I split out a version that does dmg_dealt(regular(1)) * hitchance(1) inside, but if there were an official way to do this, it would be more accurate for monsters that you can easily kill if you'd ever hit them...
It has already done this since its inception. You want kill_rounds(get_action("attack")).
Theraze
06-13-2011, 06:26 PM
I'm getting some weird results here with kill_rounds... Here's a moneybee, so as a low scaling mob its stats mean that it should be the same as mine.
> ash import <batbrain> kill_rounds(regular(1))
Returned: 4
> ash import <batbrain> (dmg_dealt(regular(1)) * hitchance("attack") == 0 ? 999999 : ceil(to_float(monster_stat("hp")) / (dmg_dealt(regular(1)) * hitchance(1) + m_hit_chance()*dmg_dealt(retal.dmg) + dmg_dealt(baseround().dmg))));
Returned: 42
> ash import <batbrain> kill_rounds(get_action("attack"))
Returned: 1120000 The first is what was printed in BatBrain 1.3, the second is my modified version of kill_rounds to try to add in hit_chance properly, and the third is what you suggested using...
Somehow, I don't think that 1.12 million rounds is right. My criticals should kill a mob with the same stats as me, if nothing else...
Edit: Question on this line in hitchance...
return minmax((6.0 + attack - max(monster_stat("def"),0.0))/11.0 - fumble_chance(),critchance(),1.0);
Do criticals count as an addition to the to-hit chance, or are they under the main system? Because by this, it's working on the only criticals you'll get are out of your normal to-hit chance or the critchance(), whichever is lower. If critical chance is an additional chance to hit, it should look more like:
return minmax((6.0 + attack - max(monster_stat("def"),0.0))/11.0 - fumble_chance() + critchance(),critchance(),1.0);
Guessing that the problem has to do with this:
> ash import <batbrain> get_action("attack").dmg[$element[none]]
Returned: 0.0 If it thinks that it will do 0 damage every hit, even on the criticals, there isn't much hope of finishing off the battle...
Or a bit more complete:
> ashq import <batbrain> foreach el in $elements[] print(get_action("attack").dmg[el])
0.0
0.0
0.0
0.0
0.0
0.0 That was against directly following this fight:
[2395] Haunted Ballroom
Encounter: floating platter of hors d'oeuvres
Strategy: attack with weapon
Round 0: Theraze wins initiative!
Round 1: Theraze executes a macro!
Round 1: Theraze attacks!
Round 2: floating platter of hors d'oeuvres takes 156 damage.
Round 2: floating platter of hors d'oeuvres takes 11 damage.
You gain 11 hit points
Round 2: Theraze wins the fight!
You gain 18 Muscleboundness
You gain 7 Mysteriousness
You gain 6 ChutzpahNot sure how it got to 0 from this:
> ash import <batbrain> regular(1)
Returned: aggregate float [element]
hot => 8.0
none => 117.647995
stench => 8.0
Chef_Rannos
06-13-2011, 11:10 PM
Round 0: chef_rannos wins initiative!
Bad monster value: "queen bee" (BatBrain.ash, line 668)
Consult script 'SmartStasis.ash' not found.
You're on your own, partner.
That was using KoLmafia-9414.jar.
I updated to 9443 and it looks like that fixed the issue...gotta remember to update my version more often. ;)
zarqon
06-14-2011, 02:42 AM
Theraze, your options are empty when importing BatBrain. Call act() or build_options() first and you'll get something closer to what BatBrain thinks in combat.
> ash import <BatBrain.ash> act(""); get_action("attack");
Factoring in Scarysauce: (6) damage, retal
Returned: record advevent
id => attack
dmg => aggregate float [element]
**none => 35.05091
att => 0.0
def => 0.0
stun => 0.0
hp => -0.10847107
mp => 0.0
meat => 0.0
rounds => 1
stats => aggregate int [stat]
**Moxie => 0
**Muscle => 0
**Mysticality => 0
EDIT: Also, criticals are a percent of non-fumbles, so capping the hitchance on the high end at 1 - fumble_chance() is the way to go (somehow that didn't make it in this update either). For the purposes of hitchance() we don't care if a hit is critical or regular -- a hit is a hit. Extra average damage for criticals is part of the calculations for damage. So that line should be:
return minmax((6.0 + attack - max(monster_stat("def"),0.0))/11.0,(1.0 - fumble_chance())*critchance(),1.0 - fumble_chance())*through;
Theraze
06-14-2011, 02:56 AM
Ah... is that why my spamattack that uses BatBrain suddenly stopped finding options? With BatBrain 1.3, it found options. With 1.4, it didn't find anything that would kill the moneybee in 10 hits. I'll try it tonight with build_options() at the top.
Question... any chance we can have it detect poison danger with some 'magic' autoAntidote setting, like -1 or something like that? If you're in danger and using an anti-anti-antidote will cure it, save it... if not, skip the cure, since it's not needed.
shazbot
06-14-2011, 03:20 AM
So occasionally SmartStasis will run to round 29, at which point it gives out and spamattack gets called. Unfortunately, in the situations where I got to round 29, I got the message about being unable to enqeue, combat too long. This is line 916, the condition being if round + a.rounds >= maxRounds. I think that should be > instead of >=, as maxRounds is 30, and you should be able to act on the 30th round.
Theraze
06-14-2011, 03:49 AM
Interesting... the *through is new as well... not in release. Does that mean that 1.4 release doesn't have block detection on skills? Also this part:
(1.0 - fumble_chance())*critchance() for min seems... high. Shouldn't the low edge still be critchance()? So this:
return minmax((6.0 + attack - max(monster_stat("def"),0.0))/11.0,critchance(),1.0 - fumble_chance())*through;
Not sure why this happens, but aren't skills supposed to match like this?
> ash import <batbrain> act(""); get_action("skill fearful fettucini");
Returned: record advevent
id =>
dmg => aggregate float [element]
att => 0.0
def => 0.0
stun => 0.0
hp => 0.0
mp => 0.0
meat => 0.0
rounds => 0
stats => aggregate int [stat]
Hmm... maybe it's applying hitchance to spells too? Appears so:
> ash import <batbrain> act(""); hitchance("skill fearful fettucini")
Returned: 0.09
> ash import <batbrain> act(""); hitchance("skill 3019")
Returned: 0.09
zarqon
06-14-2011, 04:20 AM
1.5 Updates, technically on the same day as 1.4 (here at least).
Updates to fix stuff since it was already a goodly amount of stuff. See the first post for what's fixed. Also added this to the first post:
Any consult script that uses BatBrain must call act(page) before doing anything else, or your combat environment will not be built correctly.
This update makes SmartStasis require an update quickly too (as it stands now it thinks that all of its custom actions don't take a round), so that will be coming shortly.
@Theraze: That exact poison thing has been on my todo list for a while, but it'll be a chore to code so it keeps getting pushed off. The tricky part is that you may be in no danger of losing the combat from being poisoned, but you may be in danger of not winning -- i.e. you need to be unpoisoned to kill the monster -- which opens up a big can of worms, coding-wise. Which reminds me: can you have multiple poisoned effects? If so, which one is removed by the antidote?
Also, what? (1.0 - fumble_chance())*critchance() is lower than just critchance().
About matching skills, you need to pass the actual skill to get_action(), as in get_action($skill[fearful fettucini]). Otherwise, get_action("skill 3019") would also work.
However, using your test code those probably would still return an empty event, since presently BatBrain is overly aggressive about skill availability -- skills are not available unless they appear in your options on the page text. This makes detection simple and 100% accurate, regardless of birdform, conditionally available skills, etc. However, it has two problems: it makes testing difficult, since using an empty page for testing means all of your skills are unavailable. And, it means that skills are unavailable even though you may be able to make them available by restoring MP. A predictive script may want to restore MP to be able to cast a skill, but right now the script wouldn't know that option was there until after the MP was restored and options were rebuilt. But until there's a better option -- and I've never tested have_skill() for conditionally available skills because I was too skeptical that it could account for everything -- that's what's there. Would welcome suggestions for a better method to detect skill availability.
@shazbot: Okay, changed it.
Theraze
06-14-2011, 04:31 AM
Sorry, checked and it appears that the skill matcher in hitchance was off. Should be more like this:
matcher aid = create_matcher("(use|skill) (\\d+)?",id); (file://\\d+)?",id);)I'll check if it's that way in the new release. Yep, new release still has this:
matcher aid = create_matcher("(use|skill) (d+?)",id)Tested like this and that only matches the letter d:
ash matcher aid = create_matcher("(use|skill) (d+?)","skill 3019"); aid.find(); aid.group(2);
Like this...
> ash matcher aid = create_matcher("(use|skill) (d+?)","skill ddd"); aid.find(); aid.group(2);
Returned: d
> ash matcher aid = create_matcher("(use|skill) (d+?)","skill 3019"); aid.find(); aid.group(2);
No match attempted or previous match failed ()
Returned: void
> ash matcher aid = create_matcher("(use|skill) (\\d+)?","skill 3019"); aid.find(); aid.group(2);
Returned: 3019
zarqon
06-14-2011, 04:38 AM
Yep, I'd tested the matcher one way, then altered it to include attack and jiggle, decided against it, and undid things too far. It's OK because I also forgot a semicolon so it doesn't even verify. Fixed the matcher and posted one that actually verifies.
Theraze
06-14-2011, 04:44 AM
Ah, was just going to post the semicolon code-line. :)
And yeah, that makes perfect sense on the crit/fumble thing.
lostcalpolydude
06-14-2011, 11:31 AM
Which reminds me: can you have multiple poisoned effects? If so, which one is removed by the antidote?
Probably not by accident. I believe all of them are removed.
So that line should be:
return minmax((6.0 + attack - max(monster_stat("def"),0.0))/11.0,(1.0 - fumble_chance())*critchance(),1.0 - fumble_chance())*through;
Are you *sure* that (6.0 + attack - max(monster_stat("def"),0.0))/11.0 is percentage of all attack attempts, and not just all that are not fumbles or criticals?
I.e. I think that it should actually be
float base = minmax((6.0 + attack - max(monster_stat("def"),0.0))/11.0, 0, 1);
return (1-fumble)*(critchance() + (1-critchance())*base);
I see a kolspanding thread that end with "P(hit) = 6/11 + (1/11)*(player_attack - monster_defense)", and has "Crits were removed before numbers were posted (only way to get a 0%), and the rounds they occurred in were not included. Fumbles were prevented." which to me means that this chance only fires if the attack isn't already a fumble or critical.
Theraze
06-15-2011, 04:56 AM
Bee Thoven also needs to be skipped for noodles, like the bee king and the queen.
Edit: Also, found some skills that were missing in hitchance. I'm now using the following skill code for hitchance... if there's other skills I missed, please let me know. :)
switch (to_int(aid.group(2))) {
case 7008: attack = my_buffedstat($stat[moxie]); break; // moxman
case 0001: case 2003: case 7097: break; // Attack with beak, headbutt, turtle of 7 tails; same hitchance as attack
case 1003: if (have_skill($skill[eye of the stoat])) attack += 20; // ts
case 1004: case 1005: if (my_class() == $class[seal clubber] && item_type(equipped_item($slot[weapon])) == "club" &&
weapon_hands(equipped_item($slot[weapon])) == 2) return through; // lts
if (aid.group(2) == 1003 || aid.group(2) == 1004) break;
if (have_skill($skill[eye of the stoat])) attack *= 1.25 + 0.05*to_int(my_class() == $class[seal clubber]); break;
case 7010: case 7011: case 7012: case 7013: case 7014: attack = my_buffedstat($stat[moxie]) + 10; break; // Attack with bottle-rocket
case 2015: case 2103: attack = my_buffedstat($stat[muscle]) + 20; break; // kneebutt & head+knee
default: attack = 0;
}
Changes I remember... think this is everything, but might have forgotten something.
1) Attack with beak and headbutt both use normal to-hit chance.
2) Old LS skill gets the 2 hand club bonus, else it uses weapon attack. No stoat bonuses.
3) Bottle rockets are moxie+10.
This should keep me from dying when it thinks that it can headbutt un-de-buffed royal bees. It actually wants to shieldbutt them now, which makes me much happier. And more alive. :)
Edit2: This part:
monster_hp(m) == monster_level_adjustment()from the unknown monster code makes it improperly match early scaling monsters as unknown, putting them from the 1-11 hp they really have to 170 or whatever you have set as unknown monster ML. Is there still any reason for this part of the check, since they always return 0 instead of scaling up, regardless of your ML or MCD settings? Same goes for attack/defense on scaling mobs...
One other (future) thing. Bee royal tier (all 3) have delevel resistance as well as damage resistance... don't think BatBrain currently has any way of tracking reduced delevels. For that matter, don't know if it tracks any delevels commands yet, for purposes of planning future rounds.
Winterbay
06-15-2011, 07:40 PM
I've been working on improving the data for Harpoon! and have come up with the following which should be slightly more correct than what we have today:
floor(min(800,buffedmus)/4)+{0.1,0.15,0.2}*weaponpower+floor(1.5*bonusmelee )+floor(1.5*bonushot)+floor(1.5*bonuscold)+floor(1 .5*bonusstench)+floor(1.5*bonussleaze)+floor(1.5*b onusspooky)
This requires the following additional fvars[]:
fvars["bonusmelee"] = numeric_modifier("Weapon Damage");
fvars["bonushot"] = numeric_modifier("Hot Damage");
fvars["bonuscold"] = numeric_modifier("Cold Damage");
fvars["bonusstench"] = numeric_modifier("Stench Damage");
fvars["bonussleaze"] = numeric_modifier("Sleaze Damage");
fvars["bonusspooky"] = numeric_modifier("Spooky Damage");
Edit: Tested, and at least it doesn't give any errors. And going by the formula at the wiki the calculated damage output should be correct. Unfortunately I am falling over drunk and cannot test it in reality :)
Edit, edit: I also have one other edit that I've added to my local copy regarding the difference between pasta and sauce spells.
I've added one fvars[] called spelldmgs and do the following:
fvars["spelldmgs"] = numeric_modifier("Spell Damage");
if(have_skill($skill[intrinsic spiciness]))
fvars["spelldmg"] = fvars["spelldmgs"] - 10;
else
fvars["spelldmg"] = fvars["spelldmgs"];
In the datafile I've added the "s"-version to all saucespells. THis since intrinsic spiciness only applies to sauce spells but Mafia adds it to the Spell Damage-modifier no matter what (correctly since it the modifier isn't for casting spells), it's for seeing what you have.
Gdunge
06-16-2011, 05:30 AM
Halp! The last update totally h0z0red my system!
It was working, then I upgraded to BatBrain 1.5 and SmartStasis 3.5. Now wherever I try to adventure (everywhere I've tried) I get this:
> zlib verbosity = 9
Previous value of verbosity: 9
> adv last
Bad monster value: "queen bee" (BatBrain.ash, line 667)
Consult script 'SmartStasis.ash' not found.
You're on your own, partner.
Click here to continue in the relay browser.
No idea what is going on here. I'm using KoLmafia r9414.
Winterbay
06-16-2011, 05:57 AM
Try doing "update clear" in the cli and restart Mafia (close down, open up, log in). It may be that a stale override is making strange things.
Veracity
06-16-2011, 06:45 AM
I hate it when I see that suggestion - especially since we have never, ever, written overrides for monsters.
You are running version 9414? That's 9 days old - an eternity in the world of KoL/KoLmafia.
Ironically, the bees were added to monsters.txt on June 8 in revision 9420 - released one day after the build you are currently running.
Winterbay
06-16-2011, 06:52 AM
Ahh yes, I always forget which things there are overrides for and which there are not. Also, I read the version number as 9441 which should've had the bees in it :)
Veracity
06-16-2011, 06:58 AM
We don't write overrides for ANYTHING anymore unless you explicitly tell us to do so by doing "update save". I think that as time passes, there are diminishingly few people who have old overrides lurking. Unless they specifically and proactively told KoLmafia to write them, in which case that's their own lookout.
Theraze
06-16-2011, 01:41 PM
One more top tier bees thing... they all have a 50% damage resistance to all elements as well as regular damage. It's listed on the main page of the wiki as well as discussed on talk. Bee Thoven is the only one where people are still working through confusion due to that it's also a plural mob, but I expect people to figure out that if it does regular damage using an AoE 2 skill, that means that it fits with the other resist levels. I've added the following to my monster resistance section:
case $monster[bee thoven]:
case $monster[beebee king]:
case $monster[queen bee]: foreach el in $elements[] mres[el] = 0.5; break;
Booty crab is 25% physical resistance, not 100%. Moved it from the section with the protector sphere to the one with the kegtank.
Not sure how to edit it properly, but batfactors doesn't appear to be counting physical damage in addition to the extra damage... it currently has this for the various TT skills:
skill 2003 Headbutt {0.1,0.15,0.2}*helmetpower physical regular
skill 2005 Shieldbutt {0.1,0.15,0.2}*shieldpower physical att -5, regular
skill 2015 Kneebutt {0.1,0.15,0.2}*pantspower physical stun 0.45, regular
skill 2103 Head + Knee Combo {0.1,0.15,0.2}*(helmetpower+pantspower) physical stun 0.45, regular
skill 2105 Head + Shield Combo {0.1,0.15,0.2}*(helmetpower+shieldpower) physical att -5, regular
skill 2106 Knee + Shield Combo {0.1,0.15,0.2}*(pantspower+shieldpower) physical stun 0.45, att -5, regular
skill 2107 Head + Knee + Shield Combo 0.15*(helmetpower+pantspower+shieldpower) physical stun 0.45, att -5, regular
However, each of these does its damage in addition to the standard physical damage. Also noting that moxious maneuver is officially 0 damage in there. Does BatBrain do special damage handling on TT/moxious, or...?
Noting because it thought that with 30 hp left on a royal bee, it thought there was nothing I could do to kill it successfully in one round, while a single Head+Knee+Shield easily finished it, doing 43 damage... but without the 14 damage from the physical hit, it would have been under the 30 damage.
Also occurred to me that it's possible that it's missing out on bonus turtle-wear damage...
Edit: Ah, the "regular" part makes it do regular damage. The fact that it says the damage estimate is low is something I need to try to figure out...
balkin
06-16-2011, 08:42 PM
Halp! The last update totally h0z0red my system!
It was working, then I upgraded to BatBrain 1.5 and SmartStasis 3.5. Now wherever I try to adventure (everywhere I've tried) I get this:
> zlib verbosity = 9
Previous value of verbosity: 9
> adv last
Bad monster value: "queen bee" (BatBrain.ash, line 667)
Consult script 'SmartStasis.ash' not found.
You're on your own, partner.
Click here to continue in the relay browser.
No idea what is going on here. I'm using KoLmafia r9414.
I am getting this same message in r9459.
Is SmartStasis.ash in your /scripts folder? Is the file name capitalized the same way?
That cannot be the problem because that particular error message can only be the result of the most recent version of BatBrain.
Though perhaps your suggestion is true if his message is different and balkin is confused. He should probably have copy-pasted his actual error message in case it is different and he missed the differences.
balkin
06-16-2011, 09:28 PM
Is SmartStasis.ash in your /scripts folder? Is the file name capitalized the same way?
Yes and yes
balkin
06-16-2011, 09:39 PM
[204032] Giant's Castle
Encounter: Goth Giant
Strategy: F:\KOL mafia\ccs\chefboyartie.ccs [default]
Round 0: chefboyartie wins initiative!
Bad monster value: "queen bee" (BatBrain.ash, line 668)
Consult script 'SmartStasis.ash' not found.
You're on your own, partner.
Click here to continue in the relay browser.
I have been getting this since the update of batbrain to 1.5 and I have SmartStasis3.5 using Kolmafia r9459 converted to exe using jar2exe wizard 1.8 just in case that helps.
That's weird. I wonder why it is complaining about line 668 instead of 667. Did you edit the script in any way?
Even so I wouldn't expect to see that error with r9459. Please confirm for me: When you start the program does it say "r9459" at the top of the window?
balkin
06-16-2011, 10:43 PM
That's weird. I wonder why it is complaining about line 668 instead of 667. Did you edit the script in any way?
Even so I wouldn't expect to see that error with r9459. Please confirm for me: When you start the program does it say "r9459" at the top of the window?
No I did not edit the script and it says Kolmafia v14.6 r9459 at the top
Theraze
06-17-2011, 01:25 AM
Try running update clear in the gCLI, restart mafia, and see if that fixes it? Might still be old monster override files from before that behaviour was changed...
balkin
06-17-2011, 01:53 AM
Try running update clear in the gCLI, restart mafia, and see if that fixes it? Might still be old monster override files from before that behaviour was changed...
Thank you this seems to have fixed the problem. Keep up the great work to all on the development team.
Veracity
06-17-2011, 02:31 AM
Might still be old monster override files from before that behaviour was changed...
KoLmafia has never written a "monster override file". I have no clue what the problem was.
Gdunge
06-17-2011, 02:57 AM
You are running version 9414? That's 9 days old - an eternity in the world of KoL/KoLmafia.
Ironically, the bees were added to monsters.txt on June 8 in revision 9420 - released one day after the build you are currently running.
Actually, I'm using a piece of software called MafiaUpdate, that is supposed to download the latest build and then launch it for me. It *looks* like that's what it's doing, but maybe not. I ran it today, and it said it was getting an update, downloaded something, and launched KoLmafia. However, the version number in the window title bar (and the output of the "version" command) both say r9414.
OTOH, when I tried to play an adventure, it worked just fine. No error messages at all.
The only change I made was to run the MafiaUpdate program.
Hypothesis: it's downloading diffs from SVN and applying them to my old copy, and then failing to update the version number. (I've seen a *nix cron script that works this way.)
Anyway, I downloaded the latest .jar file from builds.kolmafia.us, ran it, and it now reports the version as r9460. Adventuring still works correctly.
So, hmmm. Back to playing. Thanks for the help, all!
garjon
06-17-2011, 06:10 PM
case $familiar[slimeling]: if (contains_text(lastaction,"/slimeling.gif") || contains_text(lastaction,"You get the jump") ||
contains_text(lastaction,"You quickly conjure a saucy salve")) break;
print("Your slimeling needs sating.","olive"); famspent = true; set_property("slimelingFullness","0.0"); break;
Correct me if I'm wrong, but I think this is resetting what mafia thinks my fullness is on my slimeling if I get the jump on a mob, or use salve...
Old, but I found something ...
There's one factoring element I'm not sure about but I need to know to make this information complete. What constitutes a "spell" and is there a programmatic way of identifying if something is a spell?
Wiki page on spells says: (http://kol.coldfront.net/thekolwiki/index.php/Spells)
Not Spells
Skills that are not listed as "Combat Spell" in their skill descriptions.
balkin
06-17-2011, 07:37 PM
KoLmafia has never written a "monster override file". I have no clue what the problem was.
Before I did the update clear I was paying attention to the start up of mafia and it was doing data overide on alot of files.
Here is a link to the full start-up text maybe it will help.
http://home.comcast.net/~fhicks25/Files/9459%20startup.txt
Veracity
06-17-2011, 10:15 PM
Ah ha. You obviously typed "update data" into the CLI at some point, which downloaded ALL the data file - current as of when you did it. My advice?
Don't Do That.
balkin
06-17-2011, 11:21 PM
Ah ha. You obviously typed "update data" into the CLI at some point, which downloaded ALL the data file - current as of when you did it. My advice?
Don't Do That.
I will take that advice and not do it again.
zarqon
06-19-2011, 02:46 PM
Updates mixed with assorted responses.
I added Theraze's changes to hitchance for those additional skills, with a few tiny tweaks. Thanks! While I was at it, I made fumbles only apply to actual attacks with your weapon, and slightly altered the attack line to account for the attack chance being a percent of non-criticals, as xKiv mentioned. Moved the booty crab to the 25% resistance section. Didn't add elemental resistance for top-tier bees yet since the Wiki only mentions elemental "spell resistance", not vanilla "resistance."
@Theraze: BatBrain does not consider bonus turtlegear damage for the various butts. I remember thinking about that for 0.11 seconds before immediately getting a headache and deciding to postpone that for another day -- but that was back before the data files had all been consolidated into batfactors. If this can't be added programmatically, it's another good thing to add to batfactors.
You're mostly right about checking stats against ML adjustment to determine unknown stats. It's not needed anymore -- except for basement monsters, which still have their stats equal to ML adjustment. However, since presently consult scripts don't even work in the basement, I removed those checks.
Are the top-tier bees the only known monsters with delevel resistance? This isn't difficult to add.
@Winterbay: Re: Harpoon! Thanks for fleshing out that placeholder formula. I'm reluctant to add so many fvars just for a single skill, so instead I hardcoded Harpoon a la Clobber. Consider that skills are completely rebuilt between each server hit -- and the entire map of fvars is submitted to eval() for every formula evaluated, even if the formula is as simple as "10".
Re: Spiciness. Thanks for bringing this up again, it reminded me to look into this. Your solution isn't quite correct, since Spiciness doesn't actually add a flat 10 damage at lower levels. So instead I accounted for Spiciness by moving the assignment of that particular fvar into the skills-building loop; it should do nicely, without requiring a separate fvar:
fvars["spelldmg"] = numeric_modifier("Spell Damage");
if (have_skill($skill[intrinsic spiciness]) && to_class(to_skill(sk).class) != $class[sauceror]) fvars["spelldmg"] -= min(my_level(),10);
Aside: Evidently the 'class' field for skills returns a string. Not sure if this is an oversight or not, so I mentioned it in my latest bug report.
@garjon: Quite the opposite, actually. The "break" command means that the following code in the switch statement will not be executed.
@xKiv: Thanks, but that's a lots-of-server-hits method. Right now this works:
boolean is_spell(skill s) {
if ($classes[pastamancer,sauceror] contains to_class(s.class)) return true;
if (s.to_int() > 27 && s.to_int() < 44) return true; // hobopolis spells
return ($skills[lasagna bandages,volcanometeor showeruption] contains s);
}
But I'm considering just adding this as a keyword in batfactors instead. I guess I'll start figuring out which monsters have spell resistance/immunity. So far, all I've done with this is add the Beer Golem's spell blocking, but that doesn't work yet since mafia can't recognize that monster. Others to follow.
I also accounted for the Clockwork Apparatus transforming fumbles into various beneficial effects.
I'll be starting my first Beecore run tomorrow, so more likely than not I'll finally get more aggressive about support for bees. :)
Enjoy!
Theraze
06-19-2011, 03:34 PM
Looking forwards to the inevitable tweaks and upgrades that will come from your new fun challenge fun. At least, I hope it's a fun challenge run. :)
Regarding the basement monsters... they're not supposed to be unknown anymore. They had placeholder stats since the scaling monster code was added, and with Veracity rolling in the rest of my monsters.txt updates yesterday, they should now have Darzil's actually spaded stats in. That should mean that they provide the proper stat, not just ML, and that consult scripts SHOULD work against them...
Veracity
06-19-2011, 04:03 PM
I added Theraze's changes to hitchance for those additional skills, with a few tiny tweaks.
How come I always see that word as "hitch-ance"? As in "observ-ance" or "reflect-ance" or the like.
Now you have to see it that way, too. >:-)
Theraze
06-19-2011, 04:22 PM
Not sure why, but with the latest version, it's not calculating get_action("attack") very well...
You will die in 9782 rounds.
Your attack will kill the monster in 480000 rounds. That'd be on a Knob Goblin Bean Counter while I'm level 13, the message SmartStasis gives when the fight starts...
adeyke
06-19-2011, 05:05 PM
After updating BatBrain, SmartStasis seems to have started spamming LTS until I either run out of MP or I manage to finally get critical hit (I'm running enough ML to always miss without a crit). When I equip a ranged weapon, it apparently still tries to use LTS, since it aborts with "This skill is useless with ranged weapons."
Theraze
06-19-2011, 05:06 PM
LTS shouldn't ever get used by SmartStasis, since it's a, well, stasis script. What else do you have in your CCS?
adeyke
06-19-2011, 05:17 PM
I made sure that it was the SmartStasis doing it. I simplified my CCS to just this:
[ default ]
consult smartstasis.ash
attack with weapon
And it's still using LTS.
garjon
06-19-2011, 06:51 PM
@garjon: Quite the opposite, actually. The "break" command means that the following code in the switch statement will not be executed.
I'm watching it execute, actually...
Monster: The Axe Wound, ATT: 227, DEF: 205, HP: 214, Value: 944.73
Profit per round: ActionProfitDamageOtherbase; Slimeling (-30.0μ)117.6μ18.45 (1.63 μ/dmg)MP: 18.45
Your slimeling needs sating.
slimelingFullness => 0.0
You will die in 15 rounds.
Your attack will kill the monster in 2140000 rounds.
You have the latest use_for_items.txt. Will not check again today.
19/19 monsters drop goals here.
Custom action: use 2405 (no stun)
Round 1: garjon executes a macro!
Round 1: garjon uses the rock band flyers!
Round 2: FridgeRaider leaps on your opponent, sliming it for 41 damage. It's inspiring!
Round 2: the axe wound takes 41 damage.
You gain 41 Muscularity Points
You lose 20 hit points
Round 2: garjon casts ENTANGLING NOODLES!
Round 3: FridgeRaider paces around aimlessly, making wet squishy noises.
Round 3: garjon casts SHIELDBUTT!
Round 4: the axe wound takes 8 damage.
Round 4: the axe wound takes 16 damage.
Round 4: the axe wound drops 5 attack power.
Round 4: FridgeRaider sniffs around, looking for something to absorb.
Round 4: garjon casts SHIELDBUTT!
Round 5: the axe wound takes 10 damage.
Round 5: the axe wound takes 24 damage.
Round 5: the axe wound drops 5 attack power.
Round 5: FridgeRaider leaps on your opponent, sliming it for 36 damage. It's inspiring!
Round 5: the axe wound takes 36 damage.
You gain 36 Muscularity Points
You lose 12 hit points
Round 5: garjon casts SHIELDBUTT!
Round 6: the axe wound takes 9 damage.
Round 6: the axe wound takes 19 damage.
Round 6: the axe wound drops 5 attack power.
Round 6: FridgeRaider leaps on your opponent, sliming it for 25 damage. It's inspiring!
Round 6: the axe wound takes 25 damage.
You gain 25 Muscularity Points
You lose 11 hit points
Round 6: garjon casts SHIELDBUTT!
Round 7: the axe wound takes 8 damage.
Round 7: the axe wound takes 25 damage.
Round 7: the axe wound drops 5 attack power.
Round 7: FridgeRaider sniffs around, looking for something to absorb.
Round 7: garjon wins the fight!
After Battle: FridgeRaider hops around, dancing a jig accompanied by wet squelching noises.
You acquire an item: line
You acquire an item: line
You gain 41 Strengthliness
You gain 15 Enchantedness
You gain 9 Chutzpah
Theraze
06-19-2011, 11:56 PM
Might need to have "You twiddle your thumbs" added or any other such pages, regarding garjon's bug? Since similar to "You get the jump" it means that there's currently a non-action fight.php page.
zarqon
06-20-2011, 03:47 AM
@adeyke: SmartStasis will prolong combat using any profitable low-damage skill available to you, including LTS. If you have a ranged weapon equipped and KoL itself gives an abort error there without consuming a round, then the error is in BatBrain for considering the skill available. If on the KoL side it just spends a round without doing anything (the goal of stasis) and the abort is generated by mafia, the error is in mafia. Probably the error is in BatBrain -- although why the skill appears in your drop-down menu in KoL is a mystery if it's just going to result in an error.
@garjon: Slimeling fullness detection evidently still needs a little work. The code is correct, but evidently another case has arisen, such as Theraze mentioned. A lot of things are getting broken by macrofication these days. It's really annoying to debug macro/ASH interaction problems since it silently aborts without giving us a clue what happened and we have no feedback without turning on debug logging and then painstakingly searching through the debug log for instances where the problem occurred, so this might take some time to fix. If you would like to post a debug log from a combat where the problem occurred, it could speed things along. In the meantime, you could remove that command which adjusts the preference, since it's a feature which has evidently become a bug.
Why lts though? I have every skill permed and I just noticed it's using lts to stasis (also headbutt sometimes?). Wouldn't something cheaper be way more effective, like the 0mp class combat skill or a seal tooth or spices or something else?
Also I noticed it was spending a ton of mp on salve to cap off my hp, but it seemed to only be draining my mp reserves when I could just cast cocoon when the combat finished for cheaper. It's just once I started using smartstasis I feel like it's actually using more mp then its generating. I'm doing a 100% hipster run in beecore and I'm really feeling the mp drain even with a spring raindrop attack firing every turn, and when my ccs was just using "entangling noodles, spring raindrop attack, shieldbutt", I feel like I had consistently had more mp.
Theraze
06-20-2011, 04:19 AM
If it aborts as non-functional, doesn't it abort with a 0 actual mp cost? Or does it still use the mp, despite failing to execute?
Edit:
Bizarre twist on the attack kill_rounds thing... apparently mysticality classes have proper kill_rounds, muscle classes it expects tens of thousands ranging up to millions of rounds to finish off weak treasury mobs...
adeyke
06-20-2011, 06:22 AM
I'm at a loss for why it thinks prolonging combat is a good idea in my situation. I don't think I was wearing anything with a chance of good things happening per round, and I was just using a purse rat as familiar. So no Crown of Thrones and no stasis familiar. What was happening is that each round, it would use LTS and miss and the opponents would just bash themselves against my saucespheres. This continued until I either got the crit and killed them, or I ran out of MP and had to finish the combat in the relay browser.
This behavior really perplexes me. If it was trying to prolong combat, it could have used a cheaper skill, normal attack, or a facsimile dictionary. Those would have less cost and less chance of ending combat prematurely. On the other hand, if it was trying to end the combat, it could have used shieldbutt or a spell. Sometimes it did use headbutt (that matches Rinn's observation), but that's hardly a big improvement.
As for the ranged weapon, I think SmartStasis is generating a macro with a melee skill and KoL is then giving an error about the macro not working because of the ranged weapon.
zarqon
06-20-2011, 01:57 PM
Found the kill_rounds error -- we shouldn't name the fumble event or it adds to the ID of attack, meaning that get_action() won't find it anymore.
I also think I figured the LTS thing out. Factoring all options by hitchance is also factoring the MP cost of skills. Will have to rework -- or unwork -- how that's done. Will post an update as soon as I have that sorted.
In the case of restoring in combat when you have better out-of-combat options, you're probably not using Bale's UR, so the script is not very informed about the value of HP/MP. If you're not using UR, your HP value is based on your cheapest NPC restore, or a scroll of drastic healing, whichever gives the best rate. MP is calculated based on your cheapest NPC restore. So in mid-run levels, it's understandable that Salve would be seen as profitable. For better results, either use UR or trick SS by setting your own values for _meatpermp and _meatperhp (these are daily properties which SS reads using get_property()).
Theraze
06-20-2011, 03:32 PM
Good enough... commenting out the fumble merge line fixes my problem for now, though it does mean that fumbles aren't taken into account. Not a major issue when you're mostly dealing with easy kills though. :)
In the case of restoring in combat when you have better out-of-combat options, you're probably not using Bale's UR, so the script is not very informed about the value of HP/MP. If you're not using UR, your HP value is based on your cheapest NPC restore, or a scroll of drastic healing, whichever gives the best rate. MP is calculated based on your cheapest NPC restore. So in mid-run levels, it's understandable that Salve would be seen as profitable. For better results, either use UR or trick SS by setting your own values for _meatpermp and _meatperhp (these are daily properties which SS reads using get_property()). I've been using universal recovery for years now.
_meatperhp=0.84577113
_meatpermp=17.0
That seems about right in a bees run without access to any decent mp restores.
Found the kill_rounds error -- we shouldn't name the fumble event or it adds to the ID of attack, meaning that get_action() won't find it anymore.
That was privately driving me crazy! Thank you! I made the change to fumble() and it works like a charm.
zarqon
06-21-2011, 03:16 AM
@Rinn: Huh. At those values Salve is definitely not profitable, so I can't say why SS is casting it. Perhaps fixing this hitch-ance business will sort it out.
@Theraze: Just change the "fumble" to a "" and you'll be accounting for fumbles again. I'll try to get an update done in the next hour before work starts, but I'm also in the middle of trying to fix some Ubuntu issues that sometimes make the computer unusable. We'll see.
I should mention, all of this get_action() business is not the ideal use of BatBrain. Ideally, scripters would sort opts[] based on the criteria they want and then enqueue the first acceptable match -- like stasis_action() and stun_action() do. That is the reason for opts being indexed by an integer rather than the ID itself. Knowing the action name is unimportant. But old habits die hard, and we've been writing CCS's and scripts using action names for quite a while.
EDIT: And done. Should be fixed, and hopefully stable for a while. Too many updates lately.
Theraze
06-21-2011, 03:19 AM
@Theraze: Just change the "fumble" to a "" and you'll be accounting for fumbles again. I'll try to get an update done in the next hour before work starts, but I'm also in the middle of trying to fix some Ubuntu issues that sometimes make the computer unusable. We'll see.
I tried the "" and it went back to 480 thousand rounds, so... I'll leave it commented out for now. :)
I tried the "" and it went back to 480 thousand rounds, so... I'll leave it commented out for now. :)
Replace fumble() with this:
advevent fumble() {
if (string_modifier("Outfit") == "Clockwork Apparatus")
return to_event("",to_spread(15*0.25,"physical"),"stun 0.25, hp 12.5*0.25, mp 12.5*0.25, att -1, def -1");
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 to_event("",to_spread(0,"physical"),"hp -"+max(1.0,to_float(wpnpwr)*0.05));
}
I changed "fumble" to "" twice in that function and it fixed the problem.
Theraze
06-21-2011, 03:30 AM
Same. Not sure why it didn't work for me.
Hrm. It seems that BatBrain does not understand the damaging power of pufferfish! Is this a known bug?
Visit to The Sea: Madness Reef in progress...
[288534] Madness Reef
Encounter: pufferfish
Strategy: C:\My Dropbox\KolMafia\ccs\default.ccs [default]
Round 0: character wins initiative!
1 MP costs 3.7037036μ.
1 HP costs 0.025478048μ.
pufferfish spine (5.0 @ +148.87482): 7000μ * 12.44374% = 871.06177
rough fish scale (4.0 @ +148.87482): 2500μ * 9.954992% = 248.8748
seaweed (5.0 @ +148.87482): 4100μ * 12.44374% = 510.19333
Value of stat gain: 1397.5μ
Factoring in plastic pumpkin bucket: 9.19 damage,
pufferfish spine (5.0 @ +148.87482): 7000μ * 12.44374% = 871.06177
rough fish scale (4.0 @ +148.87482): 2500μ * 9.954992% = 248.8748
seaweed (5.0 @ +148.87482): 4100μ * 12.44374% = 510.19333
Value of stat gain: 1397.5μ
Monster: Pufferfish, ATT: 410, DEF: 459, HP: 760, Value: 3277.63
Profit per round: ActionProfitDamageOtherbase; plastic pumpkin bucket; Grouper Groupie (0μ)0μ9.19 (0 μ/dmg)
Building options...
Options built! (78 actions)
You will die in 1335 rounds.
Your attack will kill the monster in 1 rounds.
Building custom actions...
Custom actions built! (0 actions)
pufferfish spine (5.0 @ +148.87482): 7000μ * 12.44374% = 871.06177
pufferfish spine (6.0 @ +148.87482): 7000μ * 14.932489% = 1045.2742
rough fish scale (4.0 @ +148.87482): 2500μ * 9.954992% = 248.8748
rough fish scale (4.8 @ +148.87482): 2500μ * 11.9459915% = 298.64978
seaweed (5.0 @ +148.87482): 4100μ * 12.44374% = 510.19333
seaweed (6.0 @ +148.87482): 4100μ * 14.932489% = 612.23206
pufferfish spine (5.0 @ +148.87482): 7000μ * 12.44374% = 871.06177
pufferfish spine (6.5 @ +148.87482): 7000μ * 16.176863% = 1132.3804
rough fish scale (4.0 @ +148.87482): 2500μ * 9.954992% = 248.8748
rough fish scale (5.2 @ +148.87482): 2500μ * 12.94149% = 323.53723
seaweed (5.0 @ +148.87482): 4100μ * 12.44374% = 510.19333
seaweed (6.5 @ +148.87482): 4100μ * 16.176863% = 663.2514
Executing macro: scrollwhendone; sub batround; if haseffect 8 || haseffect 264 || haseffect 282 || haseffect 283 || haseffect 284; abort "BatBrain abort: poisoned"; endif; endsub; skill 52; skill 51; skill 50; call batround; skill 5003; skill 5005; skill 5008; call batround; if hpbelow 28; abort "BatBrain abort: Danger, Will Robinson"; endif; skill 51; skill 50; skill 52; call batround; skill 5005; skill 5008; call batround; if hpbelow 27; abort "BatBrain abort: Danger, Will Robinson"; endif;
Round 1: character executes a macro!
Round 1: character casts RUN LIKE THE WIND!
Round 2: pufferfish takes 7 damage.
You lose 4 hit points
You lose 8 hit points
Round 2: character casts POP AND LOCK IT!
Round 3: pufferfish takes 15 damage.
Round 3: Cuddles rolls the plastic pumpkin bucket at it. It promptly trips over it and falls, taking 33 damage.
Round 3: pufferfish takes 33 damage.
You lose 16 hit points
Round 3: character casts BREAK IT ON DOWN!
You acquire an effect: Rave Concentration (duration: 1 Adventure)
You lose 32 hit points
Round 4: character casts DISCO EYE-POKE!
Round 5: pufferfish takes 3 damage.
Round 5: pufferfish drops 3 attack power.
Round 5: pufferfish drops 3 defense.
You lose 64 hit points
You lose 56 hit points
Round 5: character casts DISCO DANCE OF DOOM!
Round 6: pufferfish takes 8 damage.
Round 6: pufferfish drops 5 attack power.
Round 6: pufferfish drops 5 defense.
You lose 128 hit points
Round 6: character casts DISCO DANCE II: ELECTRIC BOOGALOO!
Round 7: pufferfish takes 8 damage.
You acquire an effect: Disco Concentration (duration: 1 Adventure)
Round 7: pufferfish drops 7 attack power.
Round 7: pufferfish drops 7 defense.
Round 7: Cuddles rolls the plastic pumpkin bucket at it. It gets its foot caught in it and falls over, sustaining 26 damage.
Round 7: pufferfish takes 26 damage.
You lose 256 hit points
Round 7: character casts POP AND LOCK IT!
Round 8: pufferfish takes 9 damage.
Round 8: Cuddles rolls the plastic pumpkin bucket at it. It gets its foot caught in it and falls over, sustaining 31 damage.
Round 8: pufferfish takes 31 damage.
You lose 512 hit points
Round 8: character casts BREAK IT ON DOWN!
Round 9: pufferfish takes 10 damage.
You lose 1,024 hit points
Round 9: character casts RUN LIKE THE WIND!
Round 10: pufferfish takes 16 damage.
You acquire an effect: Rave Nirvana (duration: 1 Adventure)
You lose 2,048 hit points
Building options...
Options built! (78 actions)
SmartStasis complete.
Requests complete.
Drkirre
06-21-2011, 05:31 AM
Most odd.
"Round 0: drkirre loses initiative!
Cannot apply operator contains to aggregate boolean [class] (boolean [class]) and s[] (string) (BatBrain.ash, line 784)
Consult script 'smartstasis.ash' not found.
You're on your own, partner.
Click here to continue in the relay browser."
I have smartstasis.ash and batbrain both in the scripts folder.. this is quite a conundrum.
Not at all puzzling. Today's BatBrain requires a version of KoLmafia that is only 11 hours old. You'll need r9487 or higher.
Get the latest jar build (http://builds.kolmafia.us/).
Drkirre
06-21-2011, 05:35 AM
Ah, fair enough. Thanks! ^.^
zarqon
06-21-2011, 06:58 AM
@Bale: Yes, presently there is no support for anything ongoing unless it's a status effect. It's one of the tricky puzzles to solve on the todo list.
The last line in hitchance() does not work.
return min(critchance() + (1.0 - critchance())*(6.0 + attack - max(monster_stat("def"),0.0))/11.0,1.0)*through;
That goes negative every time your attack much is 7 lower than the monster's defense. Obviously you can never have a hitchance less than 0. Ooops. It should be:
return min(critchance() + (1.0 - critchance())*max(6.0 + attack - max(monster_stat("def"),0.0), 0.0)/11.0,1.0)*through;
I needed to fix this to make killrounds() be correct all the time, not just when I was fighting weak monsters. VERY important bugfix.
Theraze
06-23-2011, 05:12 AM
Is there any good way to mark the NSN as having 100 resistance to damage, since they generally heal 100 every round? If consult scripts were aware of this, it would help them not try to use low-damage skills to finish them off...
Powered by vBulletin® Version 4.2.0 Copyright © 2012 vBulletin Solutions, Inc. All rights reserved.