multiple main functions in a script

Veracity

Developer
Staff member
I was looking into an issue with VMF. That script will collect semirares for you (if configured to do so). It will also bust ghosts and fight vote monsters. All of those are on their own schedules, so when consuming adventures, it will break out to perform the designated actions and then continue. Which is to say, it essentially has a counter script built in.

However, the script will also call other scripts to consume turns (if configured). In particular, Gingerbread City and Spacegate. Since neither of those will collect semirares, if your counter expires while running them, they proceed and you lose it.

I'm considering making a custom counterScript, integrated with VMF, which will be active while running external scripts. Looking at how KoLmafia invokes a counterScript:

Code:
			KoLmafiaASH.logScriptExecution( "Starting counter script: ", scriptFile.getName(), interpreter );
			Value v = interpreter.execute( "main", new String[]
			{
				expired.getLabel(),
				String.valueOf( expired.getTurnsRemaining() )
			} );
			KoLmafiaASH.logScriptExecution( "Finished counter script: ", scriptFile.getName(), interpreter );
The property "counterScript" contains the name of an ASH file.
When a counter expires, that script is invoked via

boolean main( string name, int remaining );

In the context of my scripts, which only go places that take a single adventure at a time, remaining will always be 0, and the semirare (whatever) will occur on the very next adventure.

VMF contains a function:

void main()

This is the method which is invoked if you simply execute the script. I wonder if I could overload it and have

boolean main( string name, int remaining )

and simply set VMF as its own counterScript while it is executing, and thereby having access to all its own internal state? Intriguing!
 

Veracity

Developer
Staff member
Actually, since we synchronize on the interpreter used to execute a particular script, even if I can overload the main function like that, the counterscript would be blocked when the main script was running. Thus, no updating shared data. I could still do semirares with a custom counterScript, but I'd have to update my internal variables (and drink another Lucky Lindy) after calling the external script.

Sigh.
 

Veracity

Developer
Staff member
Huh. This turns out to be easy, with a small code change in KoLmafia.

Here's a test script:

Code:
// Counter Script Test

location CASTLE_TOP_FLOOR = $location[ The Castle in the Clouds in the Sky (Top Floor) ];
location LIMERICK_DUNGEON = $location[ The Limerick Dungeon ];

location last_location = get_property( "semirareLocation" ).to_location();
location next_location = ( last_location == CASTLE_TOP_FLOOR ) ? LIMERICK_DUNGEON : CASTLE_TOP_FLOOR;

void counterExpired( string label, string turns_remaining )
{
    print( "Counter '" + label + "' expires in " + turns_remaining + " turns" );
    if ( label == "Fortune Cookie" ) {
	if ( turns_remaining.to_int() == 0 ) {
	    print( "Last semirare = " + last_location );
	    print( "Collecting semirare at " + next_location );
	    adv1( next_location, 1, "" );
	} else {
	    print( "Nothing to do right now." );
	}
    }
    print( "Done with counter script" );
}

void main()
{
    print( "main@" + __FILE__ + " called" );

    string oldCounterScript = get_property( "counterScript" );
    string newCounterScript = "counterExpired@" + __FILE__;

    try {
	set_property( "counterScript", newCounterScript );
	cli_execute( "checkpoint" );

	if ( my_inebriety() > inebriety_limit() ) {
	    equip( $slot[ weapon ], $item[ none ] );
	    equip( $slot[ off-hand ], $item[ Drunkula's wineglass ] );
	}

	// Adventure once
	adv1( next_location, 1, "" );
    } finally {
	cli_execute( "outfit checkpoint" );
	set_property( "counterScript", oldCounterScript );
    }
}
I'm currently over-drunk, hence the Drunkula's wineglass.
Note also that having last_location and next_location set in globals like that would not work if I expected multiple timers. But this is just a test.
Test with an dummy counter:

Code:
[color=green]> counters add 0 Test clock.gif[/color]

Last semirare found 161 turns ago (on turn 5769) in The Castle in the Clouds in the Sky (Top Floor)

Unexpired counters:
Fortune Cookie (27)
Test (0)

[color=green]> cstest[/color]

main@cstest.ash called
counterScript => counterExpired@cstest.ash
Internal checkpoint created.
Taking off The Nuge's favorite crossbow...
Equipment changed.
Holding Drunkula's wineglass...
Equipment changed.

Visit to Dungeon: The Limerick Dungeon in progress...
Counter 'Test' expires in 0 turns
Done with counter script

[5931] The Limerick Dungeon
Encounter: The Slime Puddle
You gain 11 Roguishness

Wielding The Nuge's favorite crossbow...
Equipment changed.
Holding green LavaCo Lamp™...
Equipment changed.
counterScript =>

Since that worked, I forced a semi-rare:

Code:
[color=green]> pillkeeper semi[/color]

Taking pills for Sunday - Surprise Me

[color=green]> cstest[/color]

main@cstest.ash called
counterScript => counterExpired@cstest.ash
Internal checkpoint created.
Taking off The Nuge's favorite crossbow...
Equipment changed.
Holding Drunkula's wineglass...
Equipment changed.

Visit to Beanstalk: The Castle in the Clouds in the Sky (Top Floor) in progress...
Counter 'Fortune Cookie' expires in 0 turns
Last semirare = none
Collecting semirare at The Castle in the Clouds in the Sky (Top Floor)

Visit to Beanstalk: The Castle in the Clouds in the Sky (Top Floor) in progress...

[5932] The Castle in the Clouds in the Sky (Top Floor)
Encounter: All The Rave
You acquire an item: Mick's IcyVapoHotness Inhaler
Done with counter script

[5933] The Castle in the Clouds in the Sky (Top Floor)
Encounter: Punk Rock Giant
...
Round 4: Veracity wins the fight!

Wielding The Nuge's favorite crossbow...
Equipment changed.
Holding green LavaCo Lamp™...
Equipment changed.
counterScript =>
 
Top