Contest: Name my upcoming combat script!

Which is the best name for Zarqon's upcoming combat consult script?


  • Total voters
    60
  • Poll closed .

zarqon

Well-known member
I started testing tonight and I'm a bit disappointed. First I ran a test on a tree which followed every branch to the end of combat, on a HC character with only 10 in-combat options. It took close to two minutes (certainly exacerbated by a lot of debugging printing going on, but still...). I shudder to think how long traversing the full tree would take on an aftercore character with lots of permed skills.

I started pruning back the tree in several places, and got the time down to a reasonable fraction of a second -- at least for this not-many-options character. However, I pruned too aggressively and too deep, and awoke shadow and flame in the darkness... or rather, I abandoned even some viable branches, so now I have to figure out how to add back in some of the legitimate possibilities without slowing it back down to a minutes-long test every round. Further testing on other characters will be necessary, but probably not tonight. I'm just about scripted out for tonight.

@slyz: Thanks for that. Just need to 1) make it account for items that rearrange stat distribution, and b) make it a function that returns an int[stat] map, and we're all set.

Speaking of passing maps around, did you know that if you have two maps (let's call them a and b) and you do an a = b; in your script, from then on b[key] = value; will also perform a[key] = value? In other words, both variables now point to the same map. That was confusing the crap out of me until I realized what was going on.

@Fluxx: The choice of where to adventure is the player's, not BatMan's. I think my solution is as complicated as I need to make it, especially considering that your formula would be easy to implement using a betweenBattleScript to adjust the base stat value depending on your level.
 

Winterbay

Active member
Speaking of passing maps around, did you know that if you have two maps (let's call them a and b) and you do an a = b; in your script, from then on b[key] = value; will also perform a[key] = value? In other words, both variables now point to the same map. That was confusing the crap out of me until I realized what was going on.

I discovered this while creating a sushi eating script where I needed to sort differently over two identical maps and then compare. The result was really odd until this got pointe dout to me. You need to iterate over each element and assign that to the new map if you want to separate maps. I'm not exactly sure why this would be a good thing but I cna live with it now that I know of it :)
 

slyz

Developer
@slyz: Thanks for that. Just need to 1) make it account for items that rearrange stat distribution, and b) make it a function that returns an int[stat] map, and we're all set.

As I said before, Mafia's "Muscle Experience", "Mysticality Experience" and "Moxie Experience" numeric modifiers take into account:
  • generic +XP effects
  • +ML
  • XP gained from familiars: all volleyballs (including tuned) and sombreros (in the sombrero stat gain formula, monster ML is taken as an average of the monster in your current location)
  • stat tuning is taken into account when the above is distributed over to the different stats
  • stat-specific +XP effects
All that is left to do is to distribute the XP gained from the base monster.
PHP:
stat distributed_stat()
{
	if ( string_modifier( "Stat Tuning" ) != "" )
		return string_modifier( "Stat Tuning" ).to_stat() ;
	return my_primestat() ;
}

float base_monster_attack()
{ return last_monster().monster_attack() - monster_level_adjustment() ; }

float [ stat ] stat_gains()
{
	float [ stat ] statgain;
	foreach st in $stats[ Muscle, Mysticality, Moxie ]
		statgain[ st ] = numeric_modifier( st + " Experience" ) + base_monster_attack() / 16.0 ;
	statgain[ distributed_stat() ] += base_monster_attack() / 16.0 ;
			
	return statgain;
}

float [ stat ] stats = stat_gains();
float total_stats = 0.0;
foreach st, num in stats total_stats += num ;

print( "Monster: " + last_monster() + " ( " + base_monster_attack() + " attack )" );
print( "Total stats (no modifier):  " + (base_monster_attack()/4.0) + " ( " + (base_monster_attack()/8.0) + " : " + (base_monster_attack()/16.0) + " : " + (base_monster_attack()/16.0) + " )" );
print( "Total stats (with modifiers):  " + total_stats );
foreach st, num in stats print( st + ": " + num );

Code:
> call test.ash

Monster: Caveman Hippy ( 240.0 attack )
Total stats (no modifier): 60.0 ( 30.0 : 15.0 : 15.0 )
Total stats (with modifiers): 96.708206
Moxie: 24.75
Muscle: 28.458204
Mysticality: 43.5

I tested this with combinations of using a sombrero, a volleyball, a tuned volleyball (Bandersnatch), a stat distribution modifing item (deck of tropical cards), some +XP effects, stat-specific +XP effects... Everything seemd to work.
I was glad mafia made this so easy... I started out with a complicated solution to account for the stat distribution tuning items, but it turned out the "Stat Tuning" string modifier made things a lot more easier.

I did spend some time figuring out how to get a monster's base attack though :D
 

Fluxxdog

Active member
@Fluxx: The choice of where to adventure is the player's, not BatMan's. I think my solution is as complicated as I need to make it, especially considering that your formula would be easy to implement using a betweenBattleScript to adjust the base stat value depending on your level.
I know, but otherwise, BatMan would have no basis to calculate to runaway. It's a boolean situation: Runaway? true/false. By giving it something to compare to, it could make that determination.

Lets fast forward to, say, the Filth Worm nest. FTF && SS have used Rave combos to knock loose a scent gland from a filthworm hatchling and have topped off your MP. They're done, time to pass on to BatMan. (Assume ML +0).
Scenario 1: You're just barely at LV 12. Those substats are going to be more valuable as you need some more Moxie to fight the Fratboys, thus BatMan should kick their butt. Also, since you can do the Rave Stats combo, get that little extra in.

Scenario 2: You've fought your way across the Hippy army to unlock the Filthworm quest but you're only about halfway to level 13. Kill off the filthworms, but it's not worth the MP to use Rave Stats now.

Scenario 3: You're basically cleaning up leftovers right now and are almost level 13. You've got better things to do with your adventures than kill filthworms. Use a free runaway.

Some monsters you don't want to run away from ever, like the Themthar Bandits. A list is going to be needed regardless for those, but for everything else, there should be a flexible way for BatMan to tell if it's worth running away.

The other option would be to not allow BatMan to runaway at all. Leave that completely up to the user to do manually. In all honesty, it's a situation that's not easy to narrow down to 1's and 0's. Given the option to run away in the exact same scenario, the same person could use either option given multiple times.

In all honesty, what I would love to do would be to have buttons on the fight page that you could click to run FTF, one to run SS (with FTF included), then run BatMan. How sweet would that be?

... actually, can that be done?
 

Fluxxdog

Active member
Yeah, that's something to learn another time. Scripting and flow I've gotten better at, but I haven't tackled relay yet. And honestly, before I do that, I want to get matchers under my belt first. RegEx is nothing new, just gotta relearn it, and understand the different functions that use it.

Back on topic, the biggest issues for substat value are always gonna be location AND critters. Gallery, you want to run away from some fights, but you're mainly there for the Louvre. And all the methods of running away, say you have poppers, do you want to use one on an armor? Ugh, I think trying to figure out how to implement running away in a combat script is going to be more trouble than it'll be worth. Then throw in goals, like bounty hunting... come to think of it, the only real use running away has is optimizing a run. Like, sewer monster, noodle, red ray, run, sewer again, noodle, yellow ray.

I do think I'm gonna put that substat value formula in my bBS, because I'd at least like to see what values it comes up with.
 

StDoodle

Minion
I think runaways are one of those things where, unless the fight script determines that it's either runaway or get beaten up, should be based on external information. Perhaps a data file / map of monsters that you'd want to run away from, that could be set by a between battle / some other script.

Also, if you're interested in getting the code to put fight buttons on a page, I already have that. I'd be happy to share if you're interested.

Also also, I'd love to see even a work-in-progress of batbrain. Zarqon just doesn't seem to want to get a hold of me. :( (I'm guessing he's been really busy lately, though the 14-hour time difference probably doesn't help.)
 

tgetgel

Member
Speaking of passing maps around, did you know that if you have two maps (let's call them a and b) and you do an a = b; in your script, from then on b[key] = value; will also perform a[key] = value? In other words, both variables now point to the same map. That was confusing the crap out of me until I realized what was going on.
Passing value-type vs. passing reference-type arguments. Just read about that in a C# programming book I am reading to refresh my coding skills.
 

tgetgel

Member
The other option would be to not allow BatMan to runaway at all. Leave that completely up to the user to do manually. In all honesty, it's a situation that's not easy to narrow down to 1's and 0's. Given the option to run away in the exact same scenario, the same person could use either option given multiple times.
One option I have not seen discussed is a variable choice for the style of play. Folks have mentioned the different styles of play, but not in this vein. Depending on the setting, meat might be the most important, or stats, or fewest number of turns. I know that it adds complexity, but maybe only in the method that is called to deal with the combat or evaluate the situation. I would be interested in seeing the script run in different play styles (hands free?).
 

Theraze

Active member
Actually, what zarqon said is that everything gets transferred into a value in meat... So then what you might want instead is meat-weighting. Choose that you value prime stats more heavily, or that you really want defence stats, or that you're just wanting maximum meat... If the various values have multiples that can be set, people can break them however they like. But zarqon had been good at giving us variables and letting us hang ourselves with screwing up their settings violently and then taking us by the hand and helping us make it work right. Without even a hearty corrective smack!
 

xKiv

Active member
Passing value-type vs. passing reference-type arguments. Just read about that in a C# programming book I am reading to refresh my coding skills.

Actually, it's more a difference between first-class maps and second-class maps ... in most modern languages, the *value* itself is the whole map (of the type "map").
In, say, perl, a map (hash) is a different kind of a *variable*, and that makes the difference.
 

lostcalpolydude

Developer
Staff member
I would generally consider a turn to be worth about 100 mainstat, and 1 mainstat to be worth maybe 100 meat (during a typical ascension), so the script would have to value a turn at 10k meat. Given that I would consider a dictionary to generally be a bad pull for the purposes of turn savings, this seems to be in the right ballpark.
 

zarqon

Well-known member
Various playstyles should already be accounted for. Basically, set valueOfAdventure (and, eventually, base_stat_value or whatever it will be named) to tweak the weights of things.

Currently the only problem with the everything-to-meat conversions I can see which may affect playstyles is that item_val() could account for other uses of items (such as pulverizing or using container items).

Running away, at present, will be considered only as a regular combat option, most strongly affected by your valueOfAdventure property.

  • Running away cost: valueOfAdventure + monster_value()
  • Beaten Up cost: Running away cost + 3*valueOfAdventure if you have no means of removing Beaten Up, otherwise the cost of removing Beaten Up.

Thus, if all available branches result in Beaten Up, running away will actually be the most profitable branch, so it will be taken. Likewise, higher values for valueOfAdventure will result in more costly methods of monster death being attempted rather than running away. Adding substats to monster_value() will make this even more tweakable. But as far as strategic free runaways to avoid spending a turn on a suboptimal monster, the first incarnation of BatMan will have no support for that.

Now that predictive knowledge about everything is available to FTF/SS, there are a lot of things which need rewriting. It's annoying to rewrite it because it already works fairly well -- but it will be better. Pickpocketing can be more aggressive about getting goals. I'll replace stasis_item() with stasis_action(), which will return an advevent of the available action that causes the highest profit with the lowest damage. This means that stasising with any low-damage option (such as using salsaball against a hellion or somesuch) will be possible even with no actual stasis item. I'll add DB combos to the entire list of available combat options rather than treating specially. Etc., etc.

Everything is kind of in a state of chaos right now, with functions being added and deleted and moved around every day, so I don't really have anything I'd care to share yet. I'm thinking I'll have a new thread for BatMan (containing both consult and relay versions, as well as BatBrain; that way everything is in one place). I'll leave FTF as is, in case any users want to continue with the old version, but I will no longer support it.

@Doodle: I haven't forgotten you! I'm just home (and awake) an average of 2 hours/day every day except Tuesdays and sometimes weekends. Check your kmailbox soon for a response to your last message.
 

zarqon

Well-known member
So. Just today I opted to eliminate dmg_familiars.txt and merge it into what was previously SS's stasis familiar switch -- and then put that inside a function that returns an advevent, which can be called any time to give a current estimate of all your familiar's combat-affecting actions. This will put all familiar processing in one place, as well as make accounting for familiar spentness easier.

So, now the function contains all familiars that were previously in SS and dmg_familiars.txt. I fleshed them out where needed to include stunning, deleveling, multiple types of damage, etc. But there are many more familiars to add -- dmg_familiars definitely did not include all damage-dealing familiars, and neither of them included barrrnacle-types, potato-types, or attack-blocking familiars (treat blocking the same as stunning for purposes of this script -- a percent of damage blocked is predictively identical to a percent chance to stun). Probably most relatively new familiars would not be there either.

I'm not going to be adding these in any time soon -- there are more urgent matters to attend to, BatMan-wise. But you all have been so helpful in the past that I figured I could throw a little of my scripting workload out to the general public and see if there were any takers.

If anyone would care to help add familiars, I would be most gladdened.

You should be able to get a good idea how it works simply be looking at how I've done the other familiars, but here are some guidelines to help out just in case.

  • Use average values for everything.
  • This function is utterly unconcerned with familiars that only act pre- or post-combat. Do not add them.
  • First, assign the action rate to r if it's an action rate that can be affected by Jingle Jangle. If it's not affected by JJ, set r to 1.0 and keep rates as multipliers of the individual fields. If you forget to assign a value to r, all of the familiar's actions will be multiplied by 0, negating your work and possibly confusing someone later.
  • For anything that does damage, assign to rec.dmg using to_spread(float totaldamage, string types). There's also a to_spread() with a third parameter for rate (float from 0 to 1.0). If the damage dealt is not spread evenly over the types, you can individually assign to rec.dmg[element]. Use $element[none] for physical damage.
  • For potatoes and teddy bears, assign to rec.stun. If it has a 30% chance of stunning which is affected by JJ, set r to 0.3 and stun to 1.0. If it is not affected by JJ, set r to 1.0 and stun to 0.3. Make sense? Note that stun can exceed 1.0, such as for multi-round stunners -- though I don't think any familiars stun for multiple rounds.
  • For anything that delevels, assign to rec.delevel, keeping in mind that you are using positive values to represent deleveling, not negative values.

It might seem complicated seeing it explained. I guess if you know enough to be helping out, you'll probably be able to figure it out fairly easily from looking at it:

PHP:
advevent famevent() {
   float weight = familiar_weight(my_familiar()) + weight_adjustment();
   advevent res; res.id = to_string(my_familiar());
   float r;
   switch (my_familiar()) {
     // starfish-types
      case $familiar[snow angel]: r = 0.33; res.dmg = to_spread(0.75*(weight + 3),"cold"); res.mp = dmg_dealt(res.dmg); break;
      case $familiar[spirit hobo]:
      case $familiar[gluttonous green ghost]: if (famspent) break;
      case $familiar[rogue program]:
      case $familiar[star starfish]: r = 0.33; res.dmg = to_spread(0.75*(weight + 3),"physical"); res.mp = dmg_dealt(res.dmg); break;
      case $familiar[animated macaroni duck]: r = 0.33; res.dmg = to_spread(0.75*(weight + 3 + min(20,2*my_level())*to_int(my_class() == $class[pastamancer])),"physical"); res.mp = dmg_dealt(res.dmg); break;
      case $familiar[autonomous disco ball]: if (familiar_equipped_equipment(my_familiar()) != familiar_equipment(my_familiar())) break;
                                             r = 0.33; res.dmg = to_spread(9.75,"physical"); res.mp = dmg_dealt(res.dmg); break;
      case $familiar[midget clownfish]: r = 0.33; res.dmg = to_spread(0.75*(modifier_eval("1+zone(sea)*0.5")*weight + 3),"sleaze"); res.mp = dmg_dealt(res.dmg); break;
      case $familiar[rock lobster]: r = 0.33; res.dmg = to_spread(0.75*(modifier_eval("1+zone(sea)*0.5")*weight + 3),"hot"); res.mp = dmg_dealt(res.dmg); break;
      case $familiar[slimeling]: if (famspent) break; r = 0.5; res.dmg = to_spread(0.75*(weight + 3),"physical"); res.mp = dmg_dealt(res.dmg); break;
     // others
      case $familiar[hobo monkey]: if (famspent) break; r = 1.0; res.meat = 75; break;
      case $familiar[ninja pirate zombie robot]: r = 0.5; if (round < 10) res.meat = 0.22 * (6.0*weight + 10);  res.mp = 0.22 * 1.5*(weight + 2) + 5; res.hp = res.mp;
                                                  res.dmg = to_spread(1.5*(weight + 2),"physical",0.22);  res.delevel = 0.22 * floor(weight/2.0);
                                                  res.meat = 0.12 * item_val($item[toast]); break;
      case $familiar[stocking mimic]:
      case $familiar[cocoabo]: r = 0.33; if (round < 10) res.meat = 0.25 * (4.0*weight + 9);
                               res.mp = 0.25 * (weight + 7); res.hp = res.mp;
                               res.delevel = 0.25 * (weight + 1)/3;
                               res.dmg = to_spread(weight + 2,"physical",0.25 + 0.25*to_int(round > 9)); break;
      case $familiar[feather boa constrictor]: r = 0.33; if (round < 10) res.meat = 0.5 * 5*(weight + 2);   // action rate?? arbitrarily using 33%
                                               res.stun = min(0.9,1.5*weight/100.0)/r;                      // stun rate is standard potato
                                               res.dmg = to_spread(weight + 2,"sleaze",0.5); break;
      case $familiar[hanukkimbo dreidl]: r = 1.7*weight/100.0 + 0.19; res.meat = 0.25 * 2*weight; res.stun = 0.25;
                                         res.dmg = to_spread(max(2,weight),"physical",0.25); res.mp = 0.25 * (weight + 1.0)/3.0; break;
      case $familiar[howling balloon monkey]: if (weight < 11) break; r = 1.0; res.mp = 0.275 * weight/3.0; res.dmg = to_spread(weight/3.0,"physical",0.225); break;
      case $familiar[baby yeti]: r = 0.5; res.mp = 0.5 * (weight/2.0 + weight + 5)/2.0; res.dmg = to_spread(res.mp,"cold"); break;
      case $familiar[personal raincloud]: r = 0.6; res.mp = 0.25 * (weight + 3)/2.0; res.hp = res.mp; res.dmg = to_spread(res.mp,"physical"); res.delevel = 0.25 * max(1.0,weight/8.0); break;
      case $familiar[adorable seal larva]: weight += min(20,2*my_level())*to_int(my_class() == $class[seal clubber]);
      case $familiar[mosquito]: r = 0.2; res.dmg = to_spread(max(1.0,0.25 * weight),"physical"); res.hp = dmg_dealt(res.dmg); break;
     // can damage player
      case $familiar[fuzzy dice]: r = 0.3; res.delevel = 0.055 * weight; res.stun = 0.111; res.meat = 0.83; res.mp = 0.055 * weight; res.hp = 0.083 * weight; res.dmg = to_spread(weight,"physical",0.083); break;
      case $familiar[robogoose]: r = 0.33; res.dmg = to_spread((weight + 4)/2.0,"physical",0.2); res.hp = 0.2*(2.0*weight + 14) - 0.2*(weight + 4.5); res.stun = 0.2; res.delevel = 0.2 * (weight + 4)/3.0; break;
      case $familiar[stab bat]:
      case $familiar[scary death orb]: r = ceil(weight/6.0)*0.1; res.hp = -(0.33 - 0.13*to_int(have_equipped(familiar_equipment(my_familiar())))) * (weight/1.5 + 2);
           string t = "spooky"; if (my_familiar() == $familiar[stab bat]) t = "physical"; res.dmg = to_spread(weight/1.5 + 2,t,0.66 + 0.14*to_int(have_equipped(familiar_equipment(my_familiar())))); break;
     // attacks!
      case $familiar[angry goat]: r = ceil(weight/6.0)*0.1; res.dmg = to_spread(weight/3.0 + 1,"physical, stench"); break;
      case $familiar[dandy lion]: if (item_type(equipped_item($slot[weapon])) != "whip") break; r = 0.5; res.dmg = to_spread(weight/1.5 + 1.5,"physical"); break;
      case $familiar[killer bee]: r = 0.33; res.dmg = to_spread(max(1.0,weight/4),"physical"); break;
      case $familiar[disembodied hand]: r = 1.0; res.dmg = to_spread(1.5*weight + get_power(familiar_equipped_equipment($familiar[disembodied hand]))/7.5,"physical"); break;
      case $familiar[flaming gravy fairy]: r = 0.33; res.dmg = to_spread(weight/1.5,"hot"); break;
      case $familiar[frozen gravy fairy]: r = 0.33; res.dmg = to_spread(weight/1.5,"cold"); break;
      case $familiar[sleazy gravy fairy]: r = 0.33; res.dmg = to_spread(weight/1.5,"sleaze"); break;
      case $familiar[spooky gravy fairy]: r = 0.33; res.dmg = to_spread(weight/1.5,"spooky"); break;
      case $familiar[stinky gravy fairy]: r = 0.33; res.dmg = to_spread(weight/1.5,"stench"); break;
      case $familiar[grue]: r = 0.1 + 0.1*ceil(weight/4); res.dmg = to_spread((2+(5-moon_light())*2.25) + weight/1.5,"spooky"); break;
      case $familiar[jill-o-lantern]: r = 0.25; res.dmg = to_spread((weight + 2)/3.0 + 1,"physical, hot, spooky"); break;
      case $familiar[magimechtech micromechamech]: r = 1.0; res.dmg[$element[hot]] = 0.15 * weight/1.5; res.dmg[$element[none]] = 0.566*weight - 2.0; break;
      case $familiar[mariachi chihuahua]: weight += min(20,2*my_level())*to_int(my_class() == $class[accordion thief]);
      case $familiar[sabre-toothed lime]: r = ceil(weight/6.0)*0.1; res.dmg = to_spread(weight/1.5+1.5,"physical"); break;
      case $familiar[misshapen animal skeleton]: r = 1.0; res.dmg = to_spread(weight/1.5 + 5.5,"spooky",0.66*(0.4 + ceil(weight/5.0)*0.1)); res.delevel = 0.33*weight/6.0; break;
      case $familiar[ninja snowflake]: r = 1.0; res.dmg = to_spread(weight/1.5,"cold",ceil(weight/6.0)*0.1); break;
      case $familiar[wereturtle]: r = to_int(moon_light() + 2*to_int(have_equipped($item[moontan lotion])) > 5); res.dmg = to_spread(weight + 3*moon_light(),"spooky",0.2+0.1*floor((weight-1)/4)); break;
      case $familiar[wizard action figure]: r = 0.83; res.dmg = to_spread(weight + 1,"physical, hot, cold, spooky, sleaze, stench"); break;
   }
   return factor(res,minmax(r + min(0.1,have_effect($effect[jingle jangle jingle])),0,1));
}

Any help would be much appreciated!
 

Theraze

Active member
Follow on the BatBrain thread to see how its backend is coming together... that needs to be completely solid before a frontend can be reliably added. Though various of us try in the meantime. :)
 

Winterbay

Active member
Follow on the BatBrain thread to see how its backend is coming together... that needs to be completely solid before a frontend can be reliably added. Though various of us try in the meantime. :)

With more or less good results :)
I found an interesting bug in mine today when the script decided to use thrust smacks against the giant that gives that annoying effect. Forgot that once I added in all skills attack skills also ended up in the system... May need to have special coding for specific monsters :(
 

Bale

Minion
With more or less good results :)
I found an interesting bug in mine today when the script decided to use thrust smacks against the giant that gives that annoying effect. Forgot that once I added in all skills attack skills also ended up in the system... May need to have special coding for specific monsters :(

OH! Good point! I really should add special handling for that giant to my own DestroyAllMonsters script. (Which is still unreleased mostly because it won't function at all until zarqon releases the next version of BatBrain.) Mine works very differently from your own, but it has the same problem with Procrastination Giants.
 
Top