Feature - Implemented random_attributes not being passed to consult scripts

DoctorRotelle

Developer
string main( int initround, monster foe, string url ) {

// always returns false
print("untouchable: "+foe.random_attributes['untouchable'],"purple");

// returns true when true
print("untouchable: "+last_monster( ).random_attributes['untouchable'],"purple");

}

To work around it, I've simply added a line of code to override the passed foe:
foe = last_monster();
 

Bale

Minion
That's intended behavior.

A better discussion is if the intention is the best implementation.
 

lostcalpolydude

Developer
Staff member
Since last_monster() is an instance of the current monster you are fighting, rather than a generic version of that monster (and that's a recent change), this should likely be updated too.
 

Veracity

Developer
Staff member
string main( int initround, monster foe, string url ) {
This is a bug in your consult script. FightRequest.java does this to call a consult script:

Code:
				String[] parameters = new String[3];
				parameters[0] = String.valueOf( FightRequest.currentRound );
				parameters[1] = MonsterStatusTracker.getLastMonsterName();
				parameters[2] = FightRequest.lastResponseText;
The first parameter is a string which is the current round number. When you declare it to be an integer, as you did, KoLmafia converts it to an integer and gives it to you.

The second parameter is a string which is the name of the monster you are fighting. When you convert it to a monster, as you did, KoLmafia finds the base monster with that name.

The third parameter is the text of the fight page. It is NOT a "url".

Consult scripts have NEVER had a "monster" object as their second parameter. People insist on doing it, however, and back before we would temporarily register unknown monsters, ASH didn't know how to convert the unknown "lastMonsterName" into a monster object and would give you $monster[none]. That was still a bug in the consult script, but we now register unknown monsters into temporary monster objects so that buggy consult scripts could continue to work.

Your "work around" is exactly what you should have been doing all along.
 
Last edited:

Veracity

Developer
Staff member
Or, we could do a fair bit of whackage to how ASH functions are invoked internally.

The public way to execute an ASH function is in Interpreter.java:

Code:
	public Value execute( final String functionName, final String[] parameters )
or

Code:
	public Value execute( final String functionName, final String[] parameters, final boolean executeTopLevel )
Note that "parameters" is an array of String objects.

These end up calling:

Code:
	private Value executeScope( final Scope topScope, final String functionName, final String[] parameters,
				    final boolean executeTopLevel )
and

Code:
	private boolean requestUserParams( final Function targetFunction, final String[] parameters, Object[] values )
That last method gets the parameter list from the targetFunction and iterates over it, taking input Strings from the parameter list (until it runs out) or prompting the user (thereafter). For each function parameter/string value pair, it coerces the string into the desired data type by calling:

Code:
					value = DataTypes.parseValue( type, input, false );
That is how the monster name ends up as a $monster value, if the consult script says the second prameter is a "monster" rather than a "string".

The above is exactly how we want it to behave if you are calling an ASH function from the CLI: you specify the function name, give it a comma delimited list of strings to use as parameters, and if you didn't give enough, we prompt.

So, when a consult script is invoked internally, as you saw from the code I first cited, we pass it an array of strings - just as if the user had typed those.

Seems to me we could change the above four methods to all take Object[] parameters. And then, we would call DataTypes.parseValue( Type, String, boolean ) ONLY if the parameter was a String. Otherwise, we'd call a new method: DataTypes.coerceValue( Type, Object, boolean ), which would try to convert the Object into a Value of the specified type, using other methods already available in the DataTypes module. For example:

Code:
	public static final Value makeItemValue( final AdventureResult ar )
or

Code:
	public static final Value makeMonsterValue( final MonsterData monster )
That last one would be what would be used if we invoked a consult script like this:

Code:
				Object[] parameters = new String[3];
				parameters[0] = IntegerPool.get( FightRequest.currentRound );
				parameters[1] = MonsterStatusTracker.getLastMonster();
				parameters[2] = FightRequest.lastResponseText;
				consultInterpreter.execute( "main", parameters );
The round number would be a real integer and would thus not be converted to a string and parsed back into an integer.
The monster name would now be the real monster object - the same one you get from last_monster().

That actually doesn't seem that difficult. Seems odd to change the whole mechanism in which we invoke ASH functions in order to change how we invoke consult scripts, but, what the heck.
 

Veracity

Developer
Staff member
CCS:

Code:
[ default ]
consult consult.ash
attack with weapon

consult.ash:

Code:
void main(int initround, monster foe, string page)
{
    print( "initround = " + initround );
    print( "monster = " + foe );
    foreach attribute in foe.random_attributes
	print( "-> " + attribute );
}
Entering a battle and pressing the "script" button:

Code:
[514] Oil Peak
Encounter: electrified, clingy oil tycoon
Round 0: Veracity wins initiative!
You lose 1 hit point
initround = 1
monster = oil tycoon
-> electrified
-> clingy
Round 1: Veracity attacks!
...
I'd say that works. Revision 15870

For reference, here is the Type.coerceValue method:

Code:
	public Value coerceValue( final Object object, final boolean returnDefault )
	{
		if ( object instanceof String )
		{
			return this.parseValue( (String) object, returnDefault );
		}
		if ( object instanceof Integer )
		{
			int integer = ( (Integer) object ).intValue();
			switch ( this.type )
			{
			case DataTypes.TYPE_BOOLEAN:
				return DataTypes.makeBooleanValue( integer );
			case DataTypes.TYPE_INT:
				return DataTypes.makeIntValue( integer );
			case DataTypes.TYPE_FLOAT:
				return DataTypes.makeFloatValue( integer );
			case DataTypes.TYPE_STRING:
				return new Value( DataTypes.STRING_TYPE, String.valueOf( integer ) );
			case DataTypes.TYPE_ITEM:
				return DataTypes.makeItemValue( integer, returnDefault );
			case DataTypes.TYPE_SKILL:
				return DataTypes.makeSkillValue( integer, returnDefault );
			case DataTypes.TYPE_EFFECT:
				return DataTypes.makeEffectValue( integer, returnDefault );
			case DataTypes.TYPE_FAMILIAR:
				return DataTypes.makeFamiliarValue( integer, returnDefault );
			case DataTypes.TYPE_MONSTER:
				return DataTypes.makeMonsterValue( integer, returnDefault );
			case DataTypes.TYPE_THRALL:
				return DataTypes.makeThrallValue( integer, returnDefault );
			case DataTypes.TYPE_SERVANT:
				return DataTypes.makeServantValue( integer, returnDefault );
			}
			return null;
		}
		if ( object instanceof MonsterData )
		{
			MonsterData monster = (MonsterData) object;
			switch ( this.type )
			{
			case DataTypes.TYPE_INT:
				return DataTypes.makeIntValue( monster.getId() );
			case DataTypes.TYPE_STRING:
				return new Value( DataTypes.STRING_TYPE, monster.getName() );
			case DataTypes.TYPE_MONSTER:
				return DataTypes.makeMonsterValue( monster );
			}
			return null;
		}
		return null;
	}
"this" the type of the user's variable - the parameter in the argument list.
"object" is the parameter passed in.

So, if you provide a String, we call parseValue, just as before, to parse the string into the correct type the script asks for.
If you provide an integer, for all datatypes that have a natural integer associated with them, we generate the correct object.
And if you provide a MonsterData, we'll give you the monster ID, the name, or the specific monster, as desired.

So, consult scripts that want "foe" to be a "monster" get the same object as last_monster() returns.
And scripts that want "foe" to be a string get the monster name, just as before.

This could be fleshed out to have additional data types that have "make" functions in the DataTypes package, but, for now, Strings, Integers, and MonsterData objects are all we're using.
 

Kyrinia

New member
Not to dredge up an old thread, but I can't seem to get this to work while using the ocrs reward items, foe.random_attributes is returning invalid field name, even with just the example code listed here
Invalid field name 'random_attributes' (randomconsult.ash, line 5)

This is in aftercore wearing the diceprint do-rag.

Edit: trying to query last_monster() for the attributes also gives the same error, invalid field
 
Last edited:

lostcalpolydude

Developer
Staff member
A quick check of last_monster() shows that random_modifiers is one of the fields, so it was apparently renamed at some point.
 
Top