Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Sugarcube 2.7.0 error - Looking for help with JavaScript macro creation.

Hello Twine Community,

I've been following the video tutorial series by Vegetarian Zombie. Most recently, I used his Using Macros video to display three player stats at the top of each slide where the macro was placed. When I add the macro to the slide and play, I receive two errors:

1. a popup error that states "Error [tw-user-script-0]: Unexpected token <."
2. An error in the debug mode of Twine that states "Error: macro <<stats>> does not exist."

Here's the code I used to create the script, per the YouTube video series.
macros.stats = {
	handler : function(place, macroName, params, parser) {
		var player = params[0,1,2];
		new Wikifier(place, "Experience: " + player.experience);
			if (player.experience >= 1 && < 20) {
				new Wikifier(place, "Rank: What is TEAMS?");
	}
			if (player.experience >= 20 && < 40) {
				new Wikifier(place, "Rank: Novice");
	}
	}
}

I'm going to start learning JavaScript on Code Academy, because I think it would help me recognize what's wrong here, but any advise in the meantime is much appreciated!

Comments

  • edited August 2016
    For the second issue, check the SugarCube macros API.

    http://www.motoslave.net/sugarcube/2/docs/api-macro.html

    There's a specific format of
    Macro.add(name , definition [, deep])
    

    eg you'd likely start with
    Macro.add("stats",
    

    to put a new macro called stats into the game.
  • Thanks, Claretta. I had some basic errors in the code that I altered to meet the format you linked to. Still receiving the same error, though, and I'm not sure why.

    Basically, as the player progresses through the game, they will gain experience (player.experience). If their exp is between 1 and 20, I want it to display the rank "Rank: What is TEAMS?" If their exp is between 20 and 40, I want it to display "Rank: Novice" I'd like to place the macro on each slide instead of having to individually code each slide with variables to have the correct experience and rank appear. Here's what my altered code looks like:
    Macro.add("stats", {
    	handler : function(place, macroName, params, parser) {
    		var player = params[0,1];
    		new Wikifier(place, "Experience: " + player.experience);
    			if (player.experience >= 1 && < 20) {
    				new Wikifier(place, "Rank: What is TEAMS?");
    	}
    			if (player.experience >= 20 && < 40) {
    				new Wikifier(place, "Rank: Novice");
    		}
    	}
    });
    

    I changed to macro.add and properly ended the macro with );. Otherwise, everything else is about the same and I believe should work. Any other tips?
  • edited August 2016
    There are some issues with the code, the first two are major the others minor:

    1. Your handler is the wrong syntax for SugarCube 2.

    2. The conditional parts of both of your if statement are malformed, you need to include the variable after the && operator.
    if (player.experience >= 1 && player.experience < 20)
    
    3. Because your if conditionals are mutually exclusive you should use an else if

    4. You are not checking if a parameter is being passed to your macro, nor are you checking if the first parameter is an array or has any elements.

    Try starting with something like the following:
    Macro.add("stats", {
    	handler : function() {
    		if (this.args.length === 0) {
    			return this.error('no parameters specified');
    		}
    		if (!Array.isArray(this.args[0]) || this.args[0].length < 2) {
    			return this.error('not enough players specified');
    		}
    
    		var player = this.args[0][1];
    		new Wikifier(this.output, "Experience: " + player.experience);
    		
    		if (player.experience >= 1 && player.experience < 20) {
    			new Wikifier(this.output, "Rank: What is TEAMS?");
    		} else if (player.experience >= 20 && player.experience < 40) {
    			new Wikifier(this.output, "Rank: Novice");
    		}
    	}
    });
    
  • Thanks, greyelf. We're certainly getting somewhere.

    I've been able to generate both errors, and now I'm stuck on "no parameters specified."

    Full disclosure, I don't fully understand why the "no parameters" error is being generated. I read up on functions and parameters at w3schools.com and a few other forums. I also tried the following:
    handler : function(player, "stats", this)
    

    And in a passage, I entered:
    <<set $player to {
    	"Experience: " : 0
    }>>
    

    I tried different variations of the above two, adding what I thought were parameters to allow the code to work. I added "Level" and "Rank"... I also thought it might be worthwhile to create an array for the rank and level data, but decided to hold off. Any other advice or guidance you could provide is greatly appreciated.
  • If you read SugarCube 2's Macro API documentation you will see that it consists of two sections:

    1. Macros API: Which describes how to add, remove, and find a macro.

    This section also contains an example of defining a macro's handler function, which you may notice does not get passed any parameters.

    2. Macro Execution Context API: Which describes the properties that are available within the handler via the this variable.

    These properties are replacements for the older place, macroName, params, parser parameters.

    Based on the var player = params[0,1]; line in your original code I assumed that the value you are passing as the first parameter to your version of the stats macro was an Array of Objects and that the array contained at least two items. I assumed this because of the ,1 part of that line which is referencing the second element of an Array.

    eg, Your call of the stats macro looked something like the following:
    note: that your code may have different a variable name and your Object may have more properties that just experience.
    <<set $var to [{experience: 10}, {experience: 15}]>>
    
    <<stats $var>>
    
    ... this is why the first check (this.args.length === 0) in my example is testing to see that at least one parameter was passed to the macro, and why my second check (!Array.isArray(this.args[0]) || this.args[0].length < 2) is testing that the first parameter is an Array which has at least two items in it. You can safely remove both of these checks if you wish.

    I noticed that in your last example you are setting am Object property named "Experience: " which contains both a colon and a space character in the property name, this means that the related references in the stats macro code would need to be something like the following:
    if (player["Experience: "] >= 1 && player["Experience: "] < 20) {
    
    ... I suggest you remove the colon and space character from your property name.
  • This is extremely helpful. I understand how the code fits together enough to create the array and define $player.

    When I run <<stats $player>> the text that is returned is:

    Experienceundefined

    What I get from this is that player.experience is undefined, and I am struggling with how to define it. I'd like to start with 0 experience, and add code to increase exp after certain tasks are completed. In the first tile, I have:
    <<set $experience to 0>>
    <<set $player to ["experience", "rank"]>>
    <<set $player.experience to [$experience]>>
    
    <<stats $player>>
    

    In successive tiles, I use the <<visited>> macro to count whether or not they've been to places, awarding experience for the first time a tile is visited. If I can define player.experience, I'll be able to add to it accordingly, and I believe the macro will run successfully. Thanks again.
  • You're confusing the object and array literals—or generic objects and arrays period, not sure which.

    To create a property named experience on an object, which is stored in the $player story variable, try something like the following:
    /*
    	Assign an object to the story variable `$player` which contains the
    	properties: `experience` and `rank`.
    */
    <<set $player to {
    	experience : 0,
    	rank       : 0
    }>>
    
    n.b. You don't need to align them as I did, that's simply for readability. I was also unsure was kind of value rank was supposed to hold, so I just used 0.


    Also. Rather than predeclaring all of the properties up-front within the object literal, you may also add them after the object is created. For example:
    /*
    	Assign an empty object to the story variable `$player`.
    */
    <<set $player to {}>>
    
    /*
    	Assign the value `0` to the `experience` property—creating it, if necessary—
    	on the object stored within the the story variable `$player`.
    */
    <<set $player.experience to 0>>
    
  • Thanks for weighing in! I simplified the macro using your and greyelf's advice:
    Macro.add("stats", {
    	handler : function() {
    	
    		var player = {experience : 0};
    		new Wikifier(this.output, "Experience" + player.experience);
    			if (player.Experience >= 0 && player.Experience < 20) {
    				new Wikifier(this.output, "Rank: What is TEAMS?");
    	} else if (player.Experience >= 20 && player.Experience < 40) {
    					new Wikifier(this.output, "Rank: Novice");
    		}
    	}
    });
    

    I simply created a property of player (experience) and set it to zero.

    Last question: by the logic of the macro, when a player's experience is between 0 and 20, "Rank: What is TEAMS?" should be displayed. When I run the tile, I see Experience0, which is good, because I also want this to display, but where do the if statement outputs come into play?
  • You initialise the player variable within a story passage (preferably within the StoryInit passage) and then either pass it to the macro as a parameter, or directly reference it via the State.variables object.

    The following example references the $player variable via the State.variables object:
    Macro.add("stats", {
    	handler : function() {
    		if (! State.variables.hasOwnProperty('player')) {
    			return;
    		}
    		var player = State.variables.player;
    
    		new Wikifier(this.output, "Experience " + player.experience);
    
    		if (player.experience >= 0 && player.experience < 20) {
    			new Wikifier(this.output, " Rank: What is TEAMS?");
    		} else if (player.experience >= 20 && player.experience < 40) {
    			new Wikifier(this.output, " Rank: Novice");
    		}
    	}
    });
    
    You would use code like the following with your story's passage to initialise the player variable, change the value of the player's experience, and to display the player's experience and rank.
    /* Initialise the player variable. */
    <<set $player to {
    	experience : 0,
    	rank       : 0
    }>>
    
    <<stats>>
    
    /* Change the player's experience. */
    <<set $player.experience to 21>>
    
    <<stats>>
    
  • That's it. Thank you @Claretta, @greyelf, and @TheMadExile!!

    It took a few steps, but everything works like a charm now. I also had to adjust the case in a couple of spots in the macro from player.Experience to player.experience. After I did that, it all showed up just fine. Much appreciate the help!
  • You've finally got your macro working, however, just to play devil's advocate for a moment—which I should have done earlier. Something as simple as what you're doing here could easily be done as a widget macro. For example:
    <<widget "stats">>\
    Experience: <<=$player.experience>>  Rank: \
    <<if $player.experience lt 20>>
    What is TEAMS?
    <<elseif $player.experience lt 40>>
    Novice
    <</if>>\
    <</widget>>
    
    I simplified your conditionals, as they were unnecessarily complicated, and the whitespace might need adjusting, however, you should get the idea.
  • Woah, what a cool built-in macro. Just a couple of clicks and it was set up! Thanks again... still a good exercise to work through the actual JavaScript macro setup. I learned that I have a lot to learn. :smiley:
Sign In or Register to comment.