Choicescript-Like Stats Screen And Opposed Pair Stats In Twine

Adapting some of my old Choicescript projects to Twine. How would I go about setting opposed pair stats like in Choicescript, or a functional equivalent? As an example, one of my stat pairs is Limelight/Shadows, and I want to be able to have it measured and displayed as a percentage where one end falls when the other rises. Also, how would I set up a permanent sidebar link to a menu where stats are displayed?

You may want to consider using SugarCube for your new project, as it comes with both an extendable menu area (the StoryMenu special passage) in its sidebar, as well as a extendable Dialog system you could use to display the stats without causing the end-user to navigate away from the ‘current’ Passage.

SugarCube is also (in some ways) easier to extend. But you can do it in Harlowe, it’s just a little more clunky.

Story Stylesheet
.statBar {
  width: 20em;  /* Overall stat-bar width (in 'm' characters) */
  background: #fb5;  /* Right-side background color */
  color: #111;  /* Text color */
  font-weight: bold;
  margin: 0.5ex auto;
  position: relative;
}

.statBar div {
  position: relative;
  z-index: 1;
}

.statBar .statLeftBg {
  position: absolute;
  top: 0px;
  left: 0px;
  background: #1a5;  /* Left-side background color */
  z-index: 0;
}
Opposed Stat passage (displays the stat bar)
{
(set: _bar to '<div class="statBar">')
(set: _bar to it + '<div style="float:right">&nbsp;$right ' + (text: 100-$percent) + '%&nbsp;</div>')
(set: _bar to it + '<div class="statLeftBg" style="width:'+(text: $percent)+'%">&nbsp;</div>')
(set: _bar to it + '<div>&nbsp;$left $percent%</div>')
(set: _bar to it + '</div>')
_bar
}
%+ passage
{
	(set: _end to 50 + 50*(sign: $change))
	(set: $percent to it + (abs: $change)/100 * (_end - $percent))
}
%- passage (optional: unlike ChoiceScript, my %+ is ok with negative changes)
(set: $percent to -it)(display: "%+")
Using the above code
{
	<!-- Initialize a stat. -->
	(set: $Limelight to 50)

	<!-- Change a stat (note that it's ok to use negative values with %+) -->
	(set: $percent to $Limelight)(set: $change to -30)
	(display: "%+")
	(set: $Limelight to $percent)

	<!-- Display a stat bar -->
	(set: $left to "Limelight")(set: $right to "Shadows")(set: $percent to $Limelight)
	(display: "Opposed Stat")
}
1 Like

I wrote a Twine game in 2016 called “Stuff and Nonsense” which had a basic stat screen (more inventory than stats) which displays on the side (and players could also click on a link to get more info on what the stats meant). There was a huge amount of code making that work, and I reckon I’ll stick to ChoiceScript for that from now on—ChoiceScript is a little harder to learn but has lots of really excellent functionality, especially with stats, especially opposed stats.

May I ask why you want it in Twine?

I can send you the chunks of code from “Stuff and Nonsense” if that’s helpful for you.

https://philome.la/FBanksBooks/stuff-and-nonsense/play/index.html

2 Likes

The Twine Cookbook also includes two Harlowe based recipes for showing dynamic content within the left margin of the story area, the same area of the page that Harlowe’s own <tw-sidebar> (header) element is re-positioned to. note: both recipes support the 3.x series of Harlowe.
“Left Sidebar”: Harlowe (both v1.x and v2.x series)
“Left Sidebar”: Harlowe (only v2.1.0 or later)

note: My early post was not mean to imply that such functionality could not be achieve in Harlowe, just that it may not be the best story format to use to do such. Especially if you also plan to use Array or Data-Map for storage of information, because Harlowe implements its own variations of those objects and those implementations are quite inefficient when it comes to things like looping / contents manipulation / element accessing / etc…

Yeah, if you’re not already too attached to Harlowe…here’s the same code for SugarCube:

Story Stylesheet (same as Harlowe)
.statBar {
  width: 20em;  /* Overall stat-bar width (in 'm' characters) */
  background: #fb5;  /* Right-side background color */
  color: #111;  /* Text color */
  font-weight: bold;
  margin: 0.5ex auto;
  position: relative;
}

.statBar div {
  position: relative;
  z-index: 1;
}

.statBar .statLeftBg {
  position: absolute;
  top: 0px;
  left: 0px;
  background: #1a5;  /* Left-side background color */
  z-index: 0;
}
Story JavaScript (defines <<fair_plus>>)
// <<fair_plus "$stat" _change>>
Macro.add('fair_plus', {
	handler: function() {
		var percent = State.getVar(this.args[0]), change = this.args[1]
		var end = 50 + 50 * Math.sign(change)
		percent += Math.abs(change)/100 * (end - percent)
		State.setVar(this.args[0], percent)
	}
})
passage with widget tag (defines <<opposed_stat>>, <<fair_minus>>)
<<widget "fair_minus">><<fair_plus $args[0] -$args[1]>><</widget>>

<<widget "opposed_stat">><<nobr>>
<div class="statBar">
	<div style="float:right">&nbsp;$args[1] <<= 100-$args[2]>>%&nbsp;</div>
	<<= '<div class="statLeftBg" style="width:'+$args[2]+'%">&nbsp;</div>'>>
	<div>&nbsp;$args[0] $args[2]%</div>
</div>
<</nobr>><</widget>>
<!-- Initialize a stat. -->
<<set $Limelight to 50>>
​
<!-- Change a stat (note that negative values are ok) -->
<<fair_plus "$Limelight" -30>>
​
<!-- Display a stat bar -->
<<opposed_stat "Limelight" "Shadows" $Limelight>>
2 Likes

This is over a year later, but your fairmath code is so elegant, @JoshGrams, I was so happy to have found it ! Can I ask if there’s any easy way to have the fairmath calculation round up to the nearest percent? Using something like <<fair_plus "$courage" +3>> results in funny percentages like 7.85% courage! No worries if not, though! :slight_smile:

Off the top of my head, I’d probably keep the full accuracy in the variable and round it only for display, so throw a Math.round() around the $args[2] and 100-$args[2]? Let me know if you can’t get that to work and I’ll look at it more closely…

Hm, I’m still getting percentages after adding Math.round around what you indicated, but no rush! Thanks for your time!

I believe Josh had something like the following in mind:

<<widget "opposed_stat">><<nobr>>
<div class="statBar"><<set _args2 to Math.round($args[2])>>
	<div style="float:right">&nbsp;$args[1] <<= 100 - _args2>>%&nbsp;</div>
	<div class="statLeftBg" @style="'width:' + _args2 + '%'">&nbsp;</div>
	<div>&nbsp;$args[0] _args2%</div>
</div>
<</nobr>><</widget>>
1 Like

Yes, that’s pretty much what I tried–I’m still getting decimal percentages, but I just realized that this widget only applies to opposed stats, and not all fairmath used?

Oh. Never mind, it looks like ChoiceScript actually does convert it back to an integer. I was worried what that would do to small changes near the ends, but maybe they just have no effect in extreme cases. Yeah. Then you can round it off in fair_plus:

// <<fair_plus "$stat" _change>>
Macro.add('fair_plus', {
	handler: function() {
		var percent = State.getVar(this.args[0]), change = this.args[1]
		var end = 50 + 50 * Math.sign(change)
		percent += Math.abs(change)/100 * (end - percent)
		State.setVar(this.args[0], Math.round(percent))
	}
})
2 Likes

Thank you, it’s working flawlessly now! I appreciate your help! :slight_smile:

Hey! I just started out in twine (Sugarcube 2) and got normal stat bars down thanks to Chapel’s stuff, but I tried to use this for opposed stats and when I do like <<fair_plus “$mischief” +5>> it makes the stat bar go like 500% Michieveous and -400% Rational (like I showed in the picture). Any idea what I could be doing wrong?

Oh, that’s very odd. I might need to see the code for that one: sent you a PM.

Hi,

First off, I just wanna say thank you SO much for posting this. I’ve just started coding my own game recently, and could find little to nothing about opposing stats. Then, when I did none of it worked, your post was the only thing that helped me create what I wanted.

I’ve coded everything that you’ve written and double checked it. But, for some reason my stats just won’t move. I don’t know what I’m doing wrong, and would GREATLY appreciate it if you could help me :cry:

Without seeing the code, my first guess would be that you’re still on the same passage? Stuff in Twine doesn’t auto-update like a spreadsheet, so they won’t change until you go to a new page to re-display them.

As explained by @JoshGrams, we need an example of how your using the previous described “fair_plus” technique, and more importantly knowing “where” you’re displaying the “stats”.

However you don’t necessarily need to do a Passage Transition to update a section of the page, you can use one of the DOM Macros (like <<replace>>) to do that to any area you’ve previously identified using markup or a HTML element. However you may need to delay the updating of the current page if the identified area you’re dynamically updating hasn’t been added to the page’s DOM yet.

The following is a very basic example of how to use the <<replace>> macro to dynamically update an area of the page that was assigned an ID of number using Custom Style markup.

<<set $number to 5>>

number: @@#number;$number@@

<<link "Increase Number by 5">>
	<<set $number to $number + 5>>
	<<replace "#number">>$number<</replace>>
<</link>>

note: In the above example a <<link>> macro is used to delay the execution of the < macro until after the identified area has been added to the page’s DOM, however there are other techniques that can also be used depending on the exact requirements.

1 Like

Hello!! Thank you so much for sharing this with us, when I came upon your post. I immedietly tried to copy paste everything.

Unfortunately, I got a <<opposite_stat>> macro does’nt exist error.

I’m fairly new twine so I guess I’m missing something, maybe something related to the widgets?

Update: It’s alright. I got it : )

1 Like

I’m so sorry to bother you, but I’ve added your code to my story format (I’m using sugarcube!) And I can’t make the stats move for some odd reason. Is there something im doing wrong? I’ve added the stats implemented into the passage just for visual sakes.