Accessing and modifying story variables via arguments in widgets

Twine Version: 2.7.0

This is my first few trembling steps with Twine and SugarCube, so please be patient with me!

I anticipate having a lot of instances in my game where I need to update the stat of a character and inform the player of this. As I will have many characters, each with the same set of stats, I though that adding a widget could help.

My goal is to have a widget that can be used as such to increase the Strength of John by 1:

<<statChange "$john" "strength" 1>>

The widget should then increase the strength of John by 1 as well as render a message to the player informing them of this.

My problem is that I don’t understand how to access and modify the character story variable using the arguments passed to the widget. Any pointers?

The easiest is just to increase the variable:
<<set $johnStrength ++>> or <<set $johnStrength += 1>>
You don’t need a special widget to do this. The only thing you need is have that variable $johnStrength set at the start of the game.
Even rendering a message can be done on the page itself (but the code will depend on what kind of message you’d want to include (is it a notification on the side? or a popup? or just text on the page…)

If you reaaaallly want to create a widget for this, it would be to wrap the code in the widget macro (-ish):

<<widget "statChange">>
    <<set _args[0] += _args[2]>>
    /*Code for the message, if needed including _args[1] */
<</widget>>

And you could use it as:
<<statChange "$johnStrength" "strength" 1>>

EDIT: I forgot you can’t edit _args…

<<widget "statStrength">>
    <<set $johnStrength += _args[0]>>
    /*Code for the message*/
<</widget>>

And you could use it as:
<<statStrength 1>>

Here’s a similar thread from several months ago with a solution using the State.variables object.

Thank you for your reply! I might have to do it without a widget.

There are a few reasons I figured a widget would be helpful:

  • I’ll have a number of characters each with the same stats. They’re all objects set at the start of the game. Updating them with simpler syntax would save time
  • I’d like to return a bit more complex messages, like “John’s strength increased a lot” or “… decreased a bit” depending on the change value. Perhaps with conditional styling etc.

I’m aiming to keep the code as simple as possible and not copying the same things over and over, but it seems very tricky!

If they are all objects, you can change $johnStrenght to $john.strength, but the code above would stay the same.

If you want complex messages in the widget depending on the value to add:

<<if _args[0] gt 4>> (or whatever position the added/substracted number is or the cutoff)
     John's strength increased by a lot
<<elseif _args[0] lt -2>>
     John's strength decreased by a bit
<<else>>
    whatever text you want
<</if>>

Thank you both for your help! I’ve constructed a widget that I’m happy with now.

Here’s the basics (don’t worry about the grammar in the messages, I’ll get to that haha):

<<widget "statChange">><<nobr>>

    <<set _name to _args[0]>>
    <<set _key to _args[1]>>
    <<set _change to _args[2]>>

	<<if _change lt -5>>
    		<<set _diff to "much less">>
        <<elseif _change lt 0>>
        	<<set _diff to "less">>
        <<elseif _change gt 5>>
        	<<set _diff to "much more">>
        <<elseif _change gt 0>>
        	<<set _diff to "more">>
    <</if>>
    
    <<if _key.split(".").contains("strength")>>
		_name is _diff strong
	<<elseif _key.split(".").contains("stamina")>>
		_name is now _diff fit
	<</if>>
        
	<<run State.setVar($args[1], State.getVar($args[1]) + $args[2]) >>

<</nobr>><</widget>>

Used as such:

<<statChange "John" "$john.strength" 1>>

Here are a few suggestions to improve your widget.

 
1) The story variable style ($args) of widget arguments is deprecated, meaning it will go away at some point. You should only use the temporary variable style (_args). For example:

<<run State.setVar($args[1], State.getVar($args[1]) + $args[2]) >>

Should ideally be:

<<run State.setVar(_args[1], State.getVar(_args[1]) + _args[2])>>

Further, since you’ve already made copies of the arguments, you may as well reuse those for increased clarity:

<<run State.setVar(_key, State.getVar(_key) + _change)>>

 
2) The <Array>.contains() method was deprecated in favor of the <Array>.includes() method. For example:

<<if _key.split(".").contains("strength")>>
	_name is _diff strong
<<elseif _key.split(".").contains("stamina")>>
	_name is now _diff fit
<</if>>

Should ideally be:

<<if _key.split(".").includes("strength")>>
	_name is _diff strong
<<elseif _key.split(".").includes("stamina")>>
	_name is now _diff fit
<</if>>

Furthermore, since you’re really just looking for strings, you could simply use <String>.includes() to avoid unnecessarily splitting the string into an array. For example, ideally you’d do:

<<if _key.includes("strength")>>
	_name is _diff strong
<<elseif _key.includes("stamina")>>
	_name is now _diff fit
<</if>>

 
Putting it all together:

<<widget "statChange">><<nobr>>
	<<set _name to _args[0]>>
	<<set _key to _args[1]>>
	<<set _change to _args[2]>>

	<<run State.setVar(_key, State.getVar(_key) + _change)>>

	<<if _change lt -5>>
		<<set _diff to "much less">>
	<<elseif _change lt 0>>
		<<set _diff to "less">>
	<<elseif _change gt 5>>
		<<set _diff to "much more">>
	<<elseif _change gt 0>>
		<<set _diff to "more">>
	<</if>>

	<<if _key.includes("strength")>>
		_name is _diff strong
	<<elseif _key.includes("stamina")>>
		_name is now _diff fit
	<</if>>
<</nobr>><</widget>>

Usage is exactly the same.

3 Likes