Is there a way to read a file in to a buffer?

zarqon

Well-known member
I like it. It doesn't allow ASH to access the current ccs, which was part of the OP's request, but it allows scripts to provide specific ccs's for specific instances, which is exactly what the OP ordered.

These "consult functions" should also be allowed to pass combat back to mafia without doing anything, which I seem to recall normal consult scripts are not able to do.

EDIT: Perhaps it could also accept filenames? If the third parameter ends in ".ash" or no function exists in the namespace by that name, search the script paths for a ccs and use that. I'm thinking of scripters who like the functionality but might not be up to the implementation of writing a full consult function.
 

dj_d

Member
If you could make the current ccs available - just as a big buffer
buffer current_ccs()
that would rock the situation. Mafia's string handling capabilities are good enough that it doesn't have to be anything fancier than that.
 

jasonharper

Developer
Ok, here's what I've come up with as a workable implementation of in-script combat customization. It ended up as neither CCS nor consult, but an unholy chimera of the two. Fortunately, unstable DNA is readily available at the moment!

There would be a version of adventure() that takes a third string argument, naming a function.

That function should have a signature like this:
string my_consult_function(int round, monster opponent, string pageText)

'round' starts at zero, and is incremented by one on each successive call to your function. It's not exactly the same as the combat round number, since there are actions that take no rounds (such as "steal" when that's unavailable or inappropriate), and actions that take more than one round (skill use with insufficient MP, thus requiring a restorative to be used first).

The normal consult-related functions could probably be used, but there's a better way - just return a string in the form of a CCS command. (Actually, I'm surprised that this isn't the way consult scripts worked from the beginning - seems like it would have been a lot easier to implement.) It will be executed, and then, assuming that the combat hasn't been finished or aborted, your function will be called again to produce the next action. Note that, unlike existing consult scripts, your script isn't being reloaded each time, so you've got continuity of global variables between each call.

To allow your function to modify the user's combat strategy rather than completely replacing it, there would be one new built-in function:
string get_ccs_action(int round)
It will return the specified line from the CCS section appropriate for the monster currently being fought (which therefore makes this function only usable from within a consult function). Numbers past the end of the CCS will return copies of the last line. It will transparently handle switching to a different CCS section after a "default" command or use of CLEESH - complications that would make it difficult to provide a version of the function that allowed you to explicitly specify a section. Also, if the user has specified a simple battle action instead of a CCS, this function will make it look as if they had a CCS that read:
1: steal
2: <their battle action>

So, a minimal do-nothing consult function would look like this:
Code:
string null_consult(int round, string opp, string text) {
  return get_ccs_action(round);
}

adventure(1, $location[whatever], "null_consult");

And one that inserts an action into the user's strategy might look like this:
Code:
int insertRound = 2;
string insertAction = "item meat vortex";

string insert_consult(int round, string opp, string text) {
  if (round == insertRound) return insertAction;
  else if (round > insertRound) round = round - 1;
  return get_ccs_action(round);
}
To make that really useful, you'd need to scan the CCS to determine the right place to insert the action - perhaps after any "steal" or "skill entangling noodles" command, but before anything that can kill the monster.

Now the big question: does this meet everyone's needs?
 

zarqon

Well-known member
I would imagine a common practice would be checking for an action's presence in the ccs, which would require foreaching through get_ccs_action(1-30). If the action is present, no special handling is needed. With this implementation, another function boolean action_exists(string action), which like get_ccs_action() would only be callable from within a consult function, would be a very nice addition. It should probably also do partial matching.

I would also imagine common practice would be to perform a variety of actions (probably taking multiple rounds) at the beginning of the combat, ignoring the existing ccs, then handing combat back to mafia. Would this implementation still allow a consult function to do something like:

Code:
string ccs_for_brigands(int round, string opp, string text) {
  if (have_skill($skill[entangling noodles]) && monster_attack() > my_buffedstat($stat[moxie]) + 4)
   use_skill($skill[entangling noodles]);
  if (item_amount($item[meat vortex]) > 0) throw_item($item[meat vortex]);
  run_combat();
}

adventure(my_adventures(),$location[themthar hills],"ccs_for_brigands")

And if so, what "round" would mafia think it is for continuing with an existing ccs when run_combat() is called?

Anyway, any of the above mentioned methods, including this "unholy chimera" would be awesome as far as I'm concerned.

EDIT: Better idea than run_combat(): the function could return "done" or "handoff" or some such, whereupon mafia would resume handling the combat, and the consult function would no longer be called.

So, if my suggestions were all worked into the chimera:

Code:
string ccs_for_brigands(int round, string opp, string text) {
  if (ccs_contains_action("meat vortex")) return "done";
  if (have_skill($skill[entangling noodles]) && monster_attack() > my_buffedstat($stat[moxie]) + 4)
   use_skill($skill[entangling noodles]);
  if (item_amount($item[meat vortex]) > 0) throw_item($item[meat vortex]);
  return "done";
}
 

jasonharper

Developer
action_exists() could be easily and efficiently written in ASH; in fact it might even be more efficient in ASH since you could check for multiple things in a single pass through the CCS.

run_combat() wouldn't work in a consult function: it would just call the function again as it tried to determine the next round's action. Also, there's a syntax error in your example - the function has to return something, which would be executed after the combat was actually over if run_combat() worked.

The problem with returning "done" as you suggest is that mafia wouldn't know what line number in the CCS to continue from. I don't see any easy way around needing to pass every action through the consult function - perhaps "combat filter" would be a better name for it. It's likely that you'd have a global 'offset' variable, increased by 1 every time you insert a command that didn't come from the CCS, then just do return get_ccs_action(round - offset); after all insertions have been done.

Another possible approach:
1. On round 0, read the entire CCS into a map or array, or perhaps concatenate it all into one big string.
2. Perform whatever modifications you want to that representation (action_exists() becomes particularly simple if everything's in a string!).
3. Return elements from the data structure one by one.
 

zarqon

Well-known member
Yeah, agreed on run_combat()... that's why I thought "done" would work better. I still think a way to exit the consult function is necessary, though. It would probably work just fine if "done" popped back out on round 1, since consult functions could easily avoid duplicate actions using action_exists(). Either that or the function could return "done 3" or whatever round number we wanted mafia to think it was for purposes of the ccs.

Also, for flexibility, action_exists() should return an int -- the round where the action was found, or -1 for not found. This would allow for the detection of disco combos.
 

dj_d

Member
Love how this is going, but one thing - it seems to make it overly complicated to do the simplest thing, which is "Append X to the start of my battle". Or am I missing a way to do that easily?

Also, for no reason I can describe, I'd rather know that the CCS actually said to attack 30 times, vs. that it simply said "attack". So I think I'd prefer calls to undefined rounds to return "" instead of the last specified entry. Probably because you can calculate the second information from the first, but not vice versa.

Otherwise, awesome.
 

jasonharper

Developer
I showed how a function that inserts an action at an arbitrary location would look - appending to the start would be the same thing, with a location of 0. Is a 3-line function really that complicated?

[quote author=dj_d link=topic=2130.msg11068#msg11068 date=1231918828]Also, for no reason I can describe, I'd rather know that the CCS actually said to attack 30 times, vs. that it simply said "attack". So I think I'd prefer calls to undefined rounds to return "" instead of the last specified entry. Probably because you can calculate the second information from the first, but not vice versa.[/quote]

There is no observable difference between a CCS that says "attack" 30 times versus once, so what exactly could you do with this knowledge? This couldn't easily be implemented anyway, since the expected use of the return value of get_ccs_action() is for you to return it from your consult function - how would mafia know what to do if it received ""?
 

zarqon

Well-known member
[quote author=dj_d link=topic=2130.msg11068#msg11068 date=1231918828]
the simplest thing, which is "Append X to the start of my battle".
[/quote]

What Jason described:

Code:
// adds salve to beginning of combat if it doesn't already exist in the ccs
string insert_consult(int round, string opp, string text) {
  if (action_exists("salve")) return get_ccs_action(round);
  else {
    if (round == 1) return "salve";
    if (round > 1) round = round - 1;      // compensate for insertion
    return get_ccs_action(round);        // for the rest of the combat, executes your existing ccs
  }
}

What I described:

Code:
// does the same as above
string insert_consult(int round, string opp, string text) {
  if (!action_exists("salve") && my_mp() >= mp_cost($skill[saucy salve]))
    use_skill($skill[saucy salve]);
  return "done 1";               // mafia resumes your ccs from round 1
}

Either way, it isn't too terribly complicated.
 

jasonharper

Developer
The combat filter feature has been added in r6789. Go kill monsters with it!

Note that it doesn't behave particularly well if the filter function doesn't exist, or is declared improperly. Here's an odd case: if the function is mistakenly declared to return void rather than a string, the string "void" is what ends up getting passed to the combat machinery - which happens to match the "ovoid leather thing" combat item. It took me a while to figure out why my test script was doing nothing other than trying to use that item!
 

zarqon

Well-known member
Awesome! I'm pretty sure this will work its way into OCW immediately. Now I'm eagerly waiting for the next daily build post...

Thanks a lot Jason!
 

dj_d

Member
Thanks JH!

To the earlier points - I was pretty zonked when I wrote that up; I missed your posts completely. That's what I get for trying to give quick feedback.
 

zarqon

Well-known member
Testing this with OCW... Problems:

  1. There is no way to detect whether the current CCS contains a call to a consult script, since the round in question simply returns "attack with weapon". My CCS:

    [ default ]
    1: consult smartstasis.ash
    2: attack with weapon

    for i from 1 to 30 print("Action "+i+": "+ get_ccs_action(i)); results in:

    Action 1: attack with weapon
    Action 2: attack with weapon
    Action 3: attack with weapon
    ...
  2. There is no ASH function to test if a command already exists in the CCS, which everyone who uses combat filters will want to do. It can be written in ASH, but chances are it will be written every single time, which is a good case for an ASH function.
  3. There is no way to pass combat back to mafia, which would make combat filters vastly more user-friendly, because you wouldn't have to worry about compensating for however many actions you performed already in the final return. An easy solution is to return "done", after which mafia resumes combat from round 1. Or, "done n" to make mafia resume from round n.

Since it's far from immediately evident, here is how to append an action to the start of combat, with a few debugging print commands thrown in:

Code:
string add_lts(int round, string foe, string url) {
   boolean action_exists(string action) {
      for i from 1 to 30
         if (contains_text(to_lower_case(get_ccs_action(i)),to_lower_case(action))) return true;
      print("The string '"+action+"' does not exist in the CCS.");
      return false;
   }
   if (action_exists("lunging") || !have_skill($skill[lunging thrust-smack])) {
      print("You can't cast LTS, or the original plan includes LTS; following it...");
      return get_ccs_action(round);
   }
   if (round == 1) {
      print("Inserting Thrust-Smack...");
      return "skill lunging thrust-smack";
   }
   return get_ccs_action(round-1);
}

If action_exists() were added to ASH and combat filters could return "done" that would be:

Code:
string add_lts(int round, string foe, string url) {
   if (action_exists("lunging") || !have_skill($skill[lunging thrust-smack])) return "done";
   if (round == 1) return "skill lunging thrust-smack";
   return "done";
}

Just a bit easier, I'd say.
 

jasonharper

Developer
The index numbers used with combat filter functions and get_ccs_action() start at 0; counting things starting at 1 is something only puny humans do. You're not seeing that "consult" line only because you've skipped over it.

I didn't implement action_exists() because it's not clear just how it should work - substring match? Complete match? Leftmost match? Arbitrary regular expression? And should it return a boolean? Index of first match? Count of matches? I don't see anything fundamentally wrong with there being boilerplate code that's used, with modifications as needed for the task at hand, in nearly every instance of combat filters.

I see what you're getting at with the "done" command, but it wouldn't work quite like you show because the first round is generally NOT going to be the point where you insert actions - that would kill any pickpocketing or pastamancer ghost summoning. Your filter function is going to have to be capable of passing thru actions prior to the insertion point, the same code should be able to handle passing thru actions after the insertion point.

And a heads-up for you: there will soon be a "special action" line that can appear in a CCS, indicating the point where mafia should insert context-sensitive actions (like olfacting a specific monster). That's likely to be a good point to do whatever your combat filter wants to do, so you may not even need to insert anything - just replace "special action" with your action when it goes by.
 

zarqon

Well-known member
Hmmm... puny humans and sometimes mafia. Mafia itself threw me off then with its "1: consult SmartStasis.ash". I didn't type that 1 in there, mafia added it when parsing my ccs, so I assumed that would be the corresponding round number. Puny of me, I realize. :)

I'm okay with action_exists() not existing (which should be a case-insensitive contains_text() check and should return an int for the first match found, by the way), I just think dj_d and I might be the only people to even try using combat filters given their complexity, so I'm campaigning to make them more accessible.

Part of that includes "done." I'm not satisfied on that score yet. I think it would go a long way towards making combat filters a viable option for more scripters, in addition to making all combat filter functions much smaller. As far as round number, "done" could easily return to the exact same round number (for ccs purposes) that it was when it first called the combat filter. In every case where I would use combat filters, rather than returning strings per round, I would perform whatever actions necessary (using action_exists() to avoid unwanted duplicates) and then return "done", ignoring the round number entirely. e.g.

Code:
string lts_first(int round, string foe, string url) {
   if (contains_text(url,"pickpocket text")) steal();
   if (have_skill($skill[lunging thrust-smack])) use_skill($skill[lunging thrust-smack]);
   return "done";
}

Easy as pie. (Much easier, actually. Making pies is a lot of work.) I would probably never return anything other than "done" if this existed because it would be so easy to script, and because it ideally suits situations for which combat filters are preferable to existing alternatives.

The "special action" will be nice, but it doesn't make combat filters more accessible, since we would still need a template to figure out how to use them. Since they're just a function added into a script, they seem like they should be simpler than an entire separate consult script, when really they're far more complicated as they presently stand. Consult scripts can pass things back to mafia using run_combat() without worrying about round number, but combat filters have more to keep track of. Seems counterintuitive.

Sorry if I seem whiny, I'm just trying to make things simpler on the scripting end, since I think a huge part of mafia's appeal is its scriptability.
 
Top