Coding help for new twine game [Inventory/Item Map]

Twine Version: 2.x sugar cube 2
[also choose a Story Format tag above]

Looking for some coding help. In my StoryInt passage I have a bunch of variable set but for the sake of the example of what I need help with let’s pick two $Honeycake and $pot and $shards (which is the currency) and they are set to zero. There’s a later passage where you can buy a bunch of items and the way I have done that is giving each item its own silent passage. And all that works and purchasing items has them pass their “is true” evaluation flags in the [[inventory]] passage and values go up as intended. So in the inventory passage I have the value of $Honeycake and $pot (and all the others but keeping this simple) printing so I can track the count along with the value of $shards for tracking that too. Then there are passages [[Sell Apple Pie|SellItems]] and [[Sell Bread|SellItems]] and on that page I’ve got this code to try avoid individual item pages (especially as that is going to grow as development continues).

<<set $itemMap = {
“Honeycake”: “Honeycake”,
“Iron Pot”: “pot”,
“Woolen Blanket”: “blanket”,
“Leather Belt Pouch”: “pouch”,
“Oil Lantern”: “lantern”,
// Add more item mappings as needed
}>>

<<set _selectedItem = $itemMap[$itemName]>>
<<switch _selectedItem>>
    <<case "Honeycake">>
        <<if $Honeycake !== 0>>
            <<set $shards += 10>>
            <<set $Honeycake -= 1>>
            You sold a honeycake for 10 shards.
        <<else>>
            <<print "You cannot sell that item.">>
        <</if>>
    <<break>>
    <<case "pot">>
        <<if $pot !== 0>>
            <<set $shards += 20>>
            <<set $pot -= 1>>
            You sold an iron pot for 20 shards.
        <<else>>
            <<print "You cannot sell that item.">>
        <</if>>
    <<break>>
    <<!-- Add more cases for other items as needed -->`Preformatted text`
<</switch>>

I’ve tried all sorts - putting the variable names in to the item map code and everything but it just doesn’t seem to update the values at all and all I get on clicking is a blank page (except for the navigation links at the bottom). If it’s at all relevant I’ve just remembered that there’s <> and it’s closing code containing all this code. Any help would be much appreciated.

1 Like

Is a macro meant for <<for>>. documentation.
<<switch>> is an alternative to <<if>>, with each case being a new <<elseif>>.

Is the code you provided complete or is it just a snippet (i.e. is there a <<for>> somewhere for the <<break>> macro).

It’s a snippet, but the rest follows the same format. There is no for. I’m not sure why I’ve got break there. I thought that was proper. To be fair I’m no coder - I’m mostly using AI to generate my code and I’m learning coding by it getting me part of the way there from king it in plain English what I want it to do then figuring out why it’s not working but this has me stumped.

When to comes to Twine coding, AI tend to create more issues than fixes unfortunately…

So if I understood properly:

  • you have a list of items the player can find/purchase
  • you have a passage where the player can purchase said item (which will remove coins from their inventory)
  • you have a passage where the player can sell said items (which will add coins in their inventory
  • you want to have your code/passage number be as efficient as possible

Did I miss anything?

1 Like

Yep that’s all correct. I’ve tested with earlier code attempts that each individual item code works in isolation updating the values correctly. At one point selling any item would only update the count for the first item (and the coins count), then I had it that selling any item would sell all items and the current version of the code seems to do nothing so far as I can see. So the individual elements seem to work - it’s just the encapsulating code that (in theory) makes things more efficient that doesn’t seem to be working. If I’m explaining that correctly anyway.

Ok let’s see!

  • If you want to use an Item map, you may want to consider having objects inside objects for organisation. Something like the example below. If these items (description/price/etc…) won’t ever change during the game, you can use a setup variables instead of a regular $var (saves State space/helps with loading).
setup.itemsMap {
      honeycake : { name : "Honeycake", price : 10},
      pot : {name : "Iron Pot", price : 20}
}

~

  • I’m making assumption here about the efficiency passages, and merged in the example below the Buying and Selling.
    Note:
    – it does not allow the player to buy items if they don’t have the coins, or sell them if they don’t have any.
    – I based it on the original code you provided (with $Honeycakes rather than an inventory variable), rather than using the itemMap (though it can be possible)
    – this does not take into account whether there is a story inventory to begin with (it can be fixed by adding a condition and having a variable track the store inventory)
    – I used Cycy’s Liveupdate Custom macro to make the code easier to organise. It’s similar to this stat page.
<<liveblock>>
    <span id="action"></span>
    Honeycake : 
        <<if $coins gt 10>>
            <<link "Buy">>
                <<set $shards -= 10, $Honeycake ++>>
                <<replace "#action">>You buy a Honeycake for 10 coins<</replace>>
                <<update>>
            <</link>>
        <<else>>[Buy]<</if>>
        <<if $Honeycakes>>
            <<link "Sell">>
                <<set $shards += 10, $Honeycakes -->>
                <<replace "#action">>You sold a Honeycake for 10 coins<</replace>>
                <<update>>
            <</link>>
        <<else>>[Sell]<</if>>
<</liveblock>>

Inside the <<liveblock>> you can add as many items as you want!
~

  • A neat macro you may find useful is Chapel’s Simple Inventory Custom Macro set if you want to display/track what the player has in their inventory. I think it could be tweaked to include a Buy/Sell mechanic?
1 Like

OK. I’ve done some testing. And it sort of works in a weird sort of way.

The code you gave me (sincerely, thank you very much for this) doesn’t work for honey cakes (I may have made a typo somewhere, I’ll need to double check) doesn’t work but I added another item myself off the list of items my cheat button gives me and that works - mostly as intended. It updates the counts properly at least. The rest of what doesn’t work with that particular item seems more about the way I’ve got my flow passage flow set up than a problem with the code so that’s all good. Though it prints

setup.itemsMap {
      honeycake : { name : "Honeycake", price : 10},
      pot : {name : "Iron Pot", price : 20}
      choccake : {name : "Chocolate Cake", price : 15),
}

at the top of the passage. Now that I’ve said that I assume putting it in <> tags would solve that issue.

That’s because I just saw I made a typo on the $honeycake variable. One line has $honeycakes, one has $honeycake… that’s my bad.

This should be in a special passage called StoryInit, inside a <<set>> macro.

Lil note: the Forum eats the macro formatting outside of blockcode :stuck_out_tongue: You can use this to help:

`<<theMacroInQuestion>>`

Don’t worry about it - I spotted it too and fixed and tested OK. I’d assumed I’d tweaked something by hand or hit a key when I went to copy and paste to copy it and made the mistake.

Ah, that would make much more sense sticking it in there with the rest of the initialisation stuff than in its own item map initialisation passage LOL Thanks for the tip.

I thought the preformatted text option was meant to avoid that. Ah well, I know for next time. Thanks again.

Just one final test to do when I finish coding in all the items I’ve done so far and make sure it all works as intended but short of me hamfisting the copy, paste, edit bits as needed for each item I think I should be in business now - or more to the point my virtual shops will be. I can see this being fairly easily adapted to the gift giving mechanic too to cut down on the number of passages I’d need for that. Though I might just leave that as is - I’ve written little scenes for those so far so would be a shame to lose them. But we’ll see how I feel as I have more items/characters to give gifts to. Might be easier to lose the scenes and adapt this.

1 Like

Values wise - Everything works perfectly. I’ve even worked in the code to get the discounts at two of the shops working as intended. Only thing is there is no feedback until you check the main inventory - the words within <<replace>> etc. don’t appear at any point. Same with the shard value adjusting until you navigate somewhere else - the shard value not updating I can live with. I think I was seeing it update “dynamically” because my previous system was based on navigating multiple passages.

The block code I created was made to give feedback on the passage itself, inside the <<liveblock>> macro (the <<update>> macro only updates code inside <<liveblock>>s). The <<replace>> macro target the <span id="action"></span> element specifically.

If you want feedback on other parts of the page, you’d need to:

  • share with us the rest of the code of the passage (assuming the value of $shard is on the passage.
  • tell us where on the page you’d like the feedback to appear (and share with us the code of that passage if relevant)

:wink:

Ah, I misunderstood what that segment of the code was doing. On the face of it to my still pretty novice eyes it looks like on purchase/sale of an item it was supposed to out put that you’d bought/sold X for Y. $shards is set to 0 in story init passage, and increased to 100 in a story event just to give the player some walking about money. Its value is displayed in the left side bar within the StoryCaption passage if their value is not 0. Do you need the code for that too?

Here’s the whole passage code for one of the shops, though for brevity I’ve cut the items down as the code for each one is essentially the same besides the name:

<<set $prevpassage = "Foodstall2">>
<<set $lastpassage = "FoodInventory">>
<h2> Delilah's Food Stall - Stock </h2>
<<if $playerProfession == "Farmer">>
    <<set $FoodStallDiscount = 0.5>>  <!-- 50% discount for Farmers -->
<<elseif $playerProfession == "Merchant">>
    <<set $FoodStallDiscount = 0.75>>  <!-- 25% discount for Merchants -->
<<else>>
    <<set $FoodStallDiscount = 0>>  <!-- No discount for other professions -->
<</if>>

<<liveblock>>
    <span id="action"></span>
    Loaf of Bread 5 Shards : 
        <<if $shards >= 5 * (1 - $FoodStallDiscount)>>
            <<link "Buy">>
                <<set $shards -= 5 * (1 - $FoodStallDiscount), $bread ++>>
                <<replace "#action">>You buy a Loaf of bread <</replace>>
                <<update>>
            <</link>>
        <<else>>[Buy]<</if>>
        <<if $bread>>
            <<link "Sell">>
                <<set $shards += 5, $bread -->>
                <<replace "#action">>You sold a loaf of bread for 5 shards<</replace>>
                <<update>>
            <</link>>
        <<else>>[Sell]<</if>>
        
            Chocolate Cake 20 Shards : 
        <<if $shards > 20 * (1 - $FoodStallDiscount)>>
            <<link "Buy">>
                <<set $shards -= 20 * (1 - $FoodStallDiscount), $choccake ++>>
                <<replace "#action">>You buy a Chocolate Cake<</replace>>
                <<update>>
            <</link>>
        <<else>>[Buy]<</if>>
        <<if $choccake>>
            <<link "Sell">>
                <<set $shards += 20, $choccake -->>
                <<replace "#action">>You sold a Chocolate Cake <</replace>>
                <<update>>
            <</link>>
        <<else>>[Sell]<</if>>
<</liveblock>>


[[Go Back]]

The way it displays in the game you’ve got (I’m leaving out gaps/spaces here)
Item Name
Buy
Sell

So logically to me it seems the easiest/best place to but the transaction out puts is after buy and sell for each item - but you know more about the code than I do so whatever is easiest to do/is best practice.

Yup so, StoryCaption is not part of the passage. And lucky for you, the Liveupdate Macro Set has an extra little macro: <<live>>
so if you have <<live $shards>> in your StoryCaption passage, the variable will update every time <<update>> is triggered. It’s like the <<print>> macro with extra.

~
As for the text feedback on the passage, I forgot there’s an order to the code :joy:
Try putting the <<update>> before <<replace>>, and you’ll get a response everytime.

Loaf of Bread 5 Shards : 
        <<if $shards >= 5 * (1 - $FoodStallDiscount)>>
            <<link "Buy">>
                <<set $shards -= 5 * (1 - $FoodStallDiscount), $bread ++>>
                <<update>>
                 <<replace "#bread-buy">>You buy a Loaf of bread <</replace>>
            <</link>>
        <<else>>[Buy]<</if>>   <span id="bread-buy"></span>

        <<if $bread>>
            <<link "Sell">>
                <<set $shards += 5, $bread -->>
                <<update>>
                <<replace "#bread-sell">>You sold a loaf of bread for 5 shards<</replace>>
            <</link>>
        <<else>>[Sell]<</if>>   <span id="bread-sell"></span>

Time for a :coffee: it seems

All tested now, works perfectly. You’re a legend.

1 Like

I’m not sure if this should be a new topic or not - apologies if it should. Run into another issue that’s causing me a headache. I have a series of nested if statements that check a variety of conditions - but sometimes the passage links are disappearing and I can’t for the life of me figure out why. Every time I think I’ve got it figured out and go to test it seems like something different causes the passage link to disappear. I have modified some of the code for this forum - not too sure of the guidelines so redacted kink content but kept the structure. Here’s the code:

<<if $RlevelGG1 >= 3>>\
    <<if $VisitedPassageAurelia>>\
        <<if $WClevel >= 2>>\
			<<if $RlevelGG1 < 4 && $GG1stat < 200 && $WClevel >= 2>>
                [[Question?|PassageAurelia2]]
            <<elseif $RlevelGG1 < 5 && $GG1stat < 400 && $WClevel >= 4>>\
                [[Question?|PassageAurelia2]]
            <<elseif $RlevelGG1 == 5 && $GG1stat >= 500 && $WClevel == 5>>\
                [[Question?|PassageAurelia3]]
            <</if>>\
        <<else>>\
            [[Question?|PassageAurelia]]
        <</if>>\
    <<else>>\
        [[Question?|PassageAurelia]]
    <</if>>\
<</if>>\

So the intended execution of this is as follows:
If $RlevelGG1 is above 3 the options appear - lower than that the options do not.
If the state of $WClevel is above 2 for the first gate the options appear - lower than that they do not.
If the state of $GG1stat is between certain thresholds the options do/do not appear.
There are various gated conditions tied to the progress of the various variables that govern this interaction with the character.

I did discover an interesting bug with another piece of code on this passage when selecting [[Look at Aurelia|GG1App]] caused the options above to disappear for no reason that I can see. I can supply the code from this passage if required but I can see no issues with it - it mostly just prints the current state of the numerous variables associated with this character and some descriptive text. The only potentially relevant code I can see on that passage is as follows:

<<if $RlevelGG1 > 5>>\
    <<set $RlevelGG1 = 5>>\
<</if>>\
<<if $RprogressGG1 >= 100>>\
    <<set $RlevelGG1 = $RlevelGG1 + 1>>\
    <<set $RprogressGG1 = $RprogressGG1 % 100>>  <!-- Reset progress to remainder after dividing by 100 -->\
<</if>>\
<<if $RlevelGG1 < 5>>  <!-- Check if $RlevelGG1 is less than 5 -->\
    <<set $RprogressGG1 = $RprogressGG1 + 1>>  <!-- Increase $RprogressGG1 -->\
<</if>>\

Many thanks in advance.

Your code reads if $RlevelGG1 is GREATER THAN OR EQUAL TO. Do you want it to only be GREATER THAN? Same with $WCLevel.

<<if $RlevelGG1 < 4 && $GG1stat < 200 && $WClevel >= 2>>
                [[Question?|PassageAurelia2]]<-SAME PASSAGE
            <<elseif $RlevelGG1 < 5 && $GG1stat < 400 && $WClevel >= 4>>\
                [[Question?|PassageAurelia2]] <-SAME PASSAGE
            <<elseif $RlevelGG1 == 5 && $GG1stat >= 500 && $WClevel == 5>>\
                [[Question?|PassageAurelia3]]
            <</if>>\

PassageAurealia2 is repeated in two of the conditions. Not sure if that is intended or an issue.

<<if $RlevelGG1 > 5>>\
    <<set $RlevelGG1 = 5>>\
<</if>>\
<<if $RprogressGG1 >= 100>>\
    <<set $RlevelGG1 = $RlevelGG1 + 1>>\
    <<set $RprogressGG1 = $RprogressGG1 % 100>>  <!-- Reset progress to remainder after dividing by 100 -->\
<</if>>\
<<if $RlevelGG1 < 5>>  <!-- Check if $RlevelGG1 is less than 5 -->\
    <<set $RprogressGG1 = $RprogressGG1 + 1>>  <!-- Increase $RprogressGG1 -->\
<</if>>\

I would combine the first if condition with the last. What happens if $RlevelGG1 is equal to 5?

<<if $RlevelGG1 > 5>>\
    <<set $RlevelGG1 = 5>>\
<<else>>
    <<set $RprogressGG1 = $RprogressGG1 + 1>>  <!-- Increase $RprogressGG1 -->\
<</if>>\

Sorry, I perhaps wasn’t clear. I should have said if $RlevelGG1 is 3 or above and likewise with $WClevel being 2 or above.

Yes, its intended to be repeated - its supposed to be two gated conditions that lead to the same place if the gates are passed for the relationship level, stat level and wclevel and otherwise make them unavailable. But in my testing even if the conditions are met to make it available its not coming back once it gets to a point that the conditions fail and hides it for the first time. I think that’s what’s happening anyway.

Sorry, combine which first If condition with the last? I’m a little confused as you’ve linked that with the code governing $RprogressGG1 and $RlevelGG1 which so far as I can tell the code for that all works as intended.

5 is intended as the maximum value of $RlevelGG1. So nothing particularly happens if its equal to 5. Just if it is increased above 5 by $RprogressGG1 then it resets back to the maximum. This is to save me having to write additional code in passages that increase the value of $RprogressGG1 to check if $RlevelGG1 is equal to 5 then don’t increase it. Does that make sense?

Actually, I’ve just had a re-read before I post the reply - I think I see what you mean about combining now. Looks like it should do the same thing as I’ve got it doing now - but in a more efficient way. Thanks.

I think I’ve fix the issues I was having. Comments really do help instead of just staring at lines of code.

1 Like

Back again - this time I’m having some issues with implementing Time. I’ve got several passages where the time is implemented - switching out _hours and _minutes where I want to increment either depending on the situation. And a passage called ManaRegen which allows mana to regen over time. It all mostly works but I’m seeing some weird behaviours. I’m trying to use 24 hour clock and when _hours reaches 24 it should reset to zero and increment days. However, incrementing _hours was sometimes increasing the time to, for example 26:15. So I fixed that with modulo and now the hours are updating correctly - but the day isn’t changing. I’m also seeing some weird effects with the mana regen sometimes rolling backwards when the time gets rolled back if its exceeded 24. Here’s the time code:

<<set _hours to parseInt($time.split(':')[0])>>\
<<set _minutes to parseInt($time.split(':')[1])>>\
<!-- Check _hours rollover first -->
<<if _hours gte 24>>\
  <<set _hours -= 24>>\
  <<set $day += 1>>\
<</if>>\
<!-- Now increment time -->
<<set _hours += 4>>\
<!--  Check _minutes rollover -->
<<if _minutes gte 60>>\
  <<set _hours += 1>>\
  <<set _minutes -= 60>>\
<</if>>\
<<if _hours >= 24>>\
  <<set _hours = _hours % 24>>\
<</if>> \
<!--  Reconstruct time string -->
<<set _hours to _hours.toString().padStart(2, '0')>>\
<<set _minutes to _minutes.toString().padStart(2, '0')>>\
<<set $time to _hours + ":" + _minutes>>\
<!--  Reset _minutes -->
<<set _minutes to $time.split(':')[1]>>\
<<silently>><<include ManaRegen>><</silently>>

And here’s the ManaRegen code

 <!--  Check if hour changed -->\
<<if $time != $lastRegenTime>>\
\
  <!--  Calculate hours passed -->\
  <<set $hoursPassed = parseInt($time.split(':')[0]) - parseInt($lastRegenTime.split(':')[0])>>\
  \
  <!--  Calculate regen amount -->\
  <<set $regenAmount = $hoursPassed * $manaRegenRate>>\
\
  <!--  Update last regen time -->\
  <<set $lastRegenTime = $time>>\
\
  <!--  Add regen amount -->\
  <<set $mana += $regenAmount>>\
\
<</if>>\
<<if $mana > $maxmana>><<set $mana = $maxmana>><</if>>\
`
<<if $mana < 0>>\
	<<set $mana = 0>>\
<</if>>\

In the beginning of your code, you check for the rollover before you increment the time. When we consider a scenario where the hours are currently 22, for example, then what happens is that there is no rollover (because 22 < 24), but then you increment the hours by 4 in the next step, so they will be 26. You need to change the order there: first increment the time, then check whether that increment has caused a rollover.

In the current version of your code, the day rollover (<<set $day += 1>>) won’t happen, because it is conditional on _hours gte 24 (near the beginning of the code), but that condition won’t be true, because the modulo further below (<<set _hours = _hours % 24>>) prevents it. Changing the order, as mentioned above, should fix that; first increment the time, then check if a rollover is necessary, and if yes, reset the hours to the correct amount and increment the day.

I think what’s happening is the following. You’re setting the regenerated amount here…

<<set $regenAmount = $hoursPassed * $manaRegenRate>>

… and if $hoursPassed is negative, then you’ll see a negative mana “regeneration” effect (I assume that $manaRegenRate is always positive).

How can $hoursPassed be negative? You’re setting it here:

<<set $hoursPassed = parseInt($time.split(':')[0]) - parseInt($lastRegenTime.split(':')[0])>>

In the case where the hours part of $lastRegenTime is larger than the hours part of the current time, the result will be negative. And that’s the case when the day has rolled over. For example, if $lastRegenTime was at 21:00 on the previous day and now the current time is 04:00 o’clock in the morning, then the calculation above would set $hoursPassed to 4 - 21 = -17, whereas the right result would of course be 7.

The code for getting the correct result here depends a bit on how long the time period between regenerations can be, and how complex you want the whole thing to be. You should probably keep track of the day when the last regeneration time was (not only the hours). Then check whether the day is the same as the current day:

  • If yes, then just subtract the hours as in your code above.
  • If the day of $lastRegenTime is the previous day, then the hours passed are the sum of: (the hours from $lastRegenTime until midnight on the previous day, i.e. 24 - parseInt($lastRegenTime.split(':')[0])) + (the hours from midnight to now, i.e. parseInt($time.split(':')[0])).
  • If the day of $lastRegenTime is further in the past, you need to add 24 hours for every further day in between, of course.

(There may be various more elegant ways to manage the whole thing, but this would be one basic way to do it.)