Choicescript-Like Stats Screen And Opposed Pair Stats In Twine

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.

Yeah, the current page won’t change just by changing a variable. See the post a couple up from yours for the basic principle of modifying something that already exists on the page. But it’d be slightly complicated to update the actual stat bar and I haven’t gotten around to sitting down and figuring that out, I’m afraid.

Ohh ok, thank you so much!

Hi, thank you for all your efforts.
I implemented this code and it works very well. I just have a problem with the math. I have a starting opposed stat bar of 50/50 by default.
If I change the value by adding a number, it rounds differently than if I subtract.

e.g.:
<<fair_plus “$X” +5>> gives 53/47
<<fair_plus “$X” -5>> gives 48/52

So the two results are not even.

While:
<<fair_plus “$X” +10>> gives 55/45
<<fair_plus “$X” -10>> gives 45/55

In this case, it rounds correctly.

Do you know why, and how to correct the rounding?
Thank you!

edit: I’ve also put the statbar in the header, but I can’t manage to put it in line with text and/or another statbar. It always goes to a new line.

If you plug the numbers into the given formula from Josh’s post, the result is 50 + 2.5 = 52.5 in the +5 case, which Math.round will round up to 53.

In the -5 case, we get 50 + (-2.5) = 47.5, and Math.round will round that up to 48.

(In the +10 and -10 cases, the results are 50 + 5 = 55 and 50 + (-5) = 45 directly, so there’s no effect from the final rounding.)

That’s where the resulting numbers come from in the example.

One possible solution that comes to mind is rounding before adding/subtracting, so that we only add or subtract whole numbers (in the example, 3 and -3). We could attempt to do it like this:

percent += Math.round(... etc. ...)

The problem with that approach is that, if the fractional part is exactly 0.5 and we have a negative number, JavaScript will round in the positive direction, so Math.round(-2.5) will become -2, not -3. This would lead to the same asymmetry (percent would be 50 + (-2) = 48).

So, I could be overlooking something, but I think I’d sort of “pretend” that the change is positive, then round it (so that it will be rounded the same way, no matter whether it’s intended to be negative or positive), and then restore the original sign of the change.

To do that, I’d change the line

percent += Math.abs(change)/100 * (end - percent)

to this:

percent += Math.sign(change) * Math.round(change/100 * (end - percent))

This part: change/100 * (end - percent) will always be positive:

  • if change is positive, then of course change/100 will be positive, and (end-percent) will be positive, because end will have been set to 100 in the preceding line (and percent will never be larger than 100)
  • if change is negative, then change/100 will be negative, and (end-percent) will be negative too, because end will have been set to 0 in the preceding line; the multiplication of the negatives will result in a positive value

So returning to the example, both in the case of +5 and -5, the value of change/100 * (end - percent) will be 2.5, when the starting value of the stat (= percent) was 50.

We can round that: Math.round(change/100 * (end - percent)) and will thus get 3.

Now we only need to restore the original sign of the intended change, by doing: Math.sign(change) * ...

… and we will get either percent += 3 or percent += -3.

which will result in 53 or 47, respectively, so we’ll have the same change by 3 from 50 both when it’s positive and when it’s negative.

I only tested this perfunctorily in the browser console, but it should work, I think. (It also works when plugging in some example numbers from the Choicescript site, and preserves the asymmetric changes which the fairmath system has by design, when the value of the stat is not 50, but higher or lower than that.)

1 Like

Not only you solved the problem, but you took the time to analyze it and explain it in terms even a newbie like me could understand.
Thank you very much sir, you and your avatar are great!

1 Like