Re-usable Global Passage

In my story, I have this one section where there are a bunch of if statements that do different things based on some flags. The only purpose of this section is to complete the if statements - it's not to forward the story.

Now, I want to get to this section from different places, but then I need them to get back to where they were. Basically, I need a passage, not a section, but I need a passage that can be gotten to from any section. And I'd like to have several of these.

For example, I have a chickenhouse, and there are several sections in the chickenhouse where you can snag some corn. But I don't want to copy and paste the same pick-up-corn passage into several sections; rather, I want several sections to lead to the same passage.

Can I do that?


A while back I posted a modified version of the built in passage function, which would allow passages in the master section to be accessed from any section.

The modified passage function looks something like this:


    squiffy.story.passage = function(passageName) {
        var passage = squiffy.story.section.passages[passageName];
        var masterSection = squiffy.story.sections[''];
        if (!passage && masterSection) passage = masterSection.passages[passageName];
        if (!passage) return;
        setSeen(passageName);
        if (masterSection) {
            var masterPassage = masterSection.passages[''];
            if (masterPassage) {
                squiffy.story.run(masterPassage);
                squiffy.ui.write(masterPassage.text);
            }
        }
        var master = squiffy.story.section.passages[''];
        if (master) {
            squiffy.story.run(master);
            squiffy.ui.write(master.text);
        }
        squiffy.story.run(passage);
        squiffy.ui.write(passage.text);
        squiffy.story.save();
    };

That's one line added, and two lines moved to later in the function.
If you use this to replace the built-in definition of squiffy.story.passage in story.js it should work fine. I believe you can do this after compiling your game.

Doing this on the web version of Squiffy, or if you want to do it entirely within the Squiffy file, is a little harder. Unfortunately, it seems necessary to include the javascript for this change in the master section itself, meaning that when you resume a saved game, the changes won't work until the player clicks on an actual section link. I'm trying to work around this by abusing the _transition function, and I think I've got it working on a test game now. Unfortunately, this also means we would need to include the setSeen function, as this is a local variable and not accessible from user code.

This is how the first section looks:

[[ui init]]:
    squiffy.story.save = function() {
      squiffy.set('_output', squiffy.ui.output.html());
      if (!squiffy.get('_transition')) {
        squiffy.set('_transition', "squiffy.story.sections['ui init'].js");
      }
    };

    // Game-specific code goes here

    // This moves the player on to the actual first section of the game after the weird script stuff
    //   is done. change Start to your section name, or remove this and put your first section
    //   text here (but remember that any javascript in this first section will be run again when
    //   resuming a saved game)
    if (squiffy.get('_section') == "ui init") {
      squiffy.story.go('Start');
    }

This allows me to modify the Squiffy javascript for a single game. The line // Game-specific code goes here can be replaced with the modified passage function above. Although in the case of changing passage there is another small issue because of the private function setSeen.

You would need to either remove this line:

        setSeen(passageName);

or replace it with:

        var seenSections = squiffy.get('_seen_sections');
        if (!seenSections) seenSections = [];
        if (seenSections.indexOf(passageName) == -1) {
            seenSections.push(passageName);
            squiffy.set('_seen_sections', seenSections);
        }

(I should note that I have tested the modified passage function, and I have tested the system for modifying Squiffy's built-in functions, but I haven't tested the two bits of code together. Still, I expect it would work without any problems)


Is there a way to make this change permanent - that is, to work for all stories? Maybe I could modify one of the files in C:\Program Files (x86)\Squiffy\resources\app\ ? (or maybe not; I'm still not sure where squiffy compiler is) I don't see any reason for not making it available to all my games.

When you say "a while back I posted" - are you referring to posts in this squiffy forum or somewhere else? I've been using extensive searches through this forum for anything and everything I can find. I wish there were a wiki for all these tricks.

You are very wise in the ways of squiffy, and I wish I could tap into your exploits without you having to answer all of my posts! (You are kind to pay me your attention, and I'm very glad)

@mrangel maybe you have a github page I could explore?


I don't know that much; I've pretty much taught myself by trial and error while answering people's questions on the forum. All my answers are on here, but searching for them might be hard because many of the thread titles aren't particularly informative.

My first attempt at this particular problem was trying to fix someone else's code in a thread. At that point I didn't know about passages, so instead I made a suffix that could be added to a section link, so that it would display the section without leaving the current one (effectively making a section act like a passage). You can see this in my very silly test game, which I've only recently (today) modified so that it behaves correctly after refreshing the page, or returning to a previously started game.

I can't find the thread with the modified passage function in it, but I think the simpler modification I posted here might be better.


That "silly" game is right smart! Would you share the squiffy with with me? I learn best by looking at other people's code.

As for the subject at hand, I don't want to modify story.js, because I still have a lot to do with the story itself and editing story.js is not a good thing to do at this point. Besides that, if I ever want to make updates or edits to the story, I'll lose any modifications I made in story.js. So, I'd rather make modifications to the squiffy compiler.

Ah, I found your source (didn't read closely enough on the pre-story page)
http://textadventures.co.uk/forum/squiffy/topic/twsu7sqjoegndwy3_g_qxq/inventory-show-hide


Is there a way to make this change permanent - that is, to work for all stories? Maybe I could modify one of the files in C:\Program Files (x86)\Squiffy\resources\app\?

I have the Linux version of the desktop editor, but not the Windows one (and have only actually used the web editor). But assuming the layout is the same, it looks like you could search for squiffy.story.passage in the file Squiffy/resources/app/node_modules/squiffy/squiffy.template.js.

If you're changing it there, you probably don't need to mess around withsetSeen at all, which will make it easier :)


That "silly" game is right smart! Would you share the squiffy with with me? I learn best by looking at other people's code.

There's a link in the game description to the forum thread that inspired it, where I posted all the code.

Be warned that some of the code is pretty bad, because I didn't know about passages when I was doing it (trial and error, having never used Squiffy before)

I modified it again today, and haven't updated the code in that thread. But the changes I made there were basically wrapping it in the [[ui init]]: code I posted above, so that it doesn't stop working when you resume a saved game.

Here's the current code (including console.log breadcrumbs from when I was trying to understand why it wasn't working):

click to expand
@title Inventory test game

[[ui init]]:
    console.log ("Running init...")
    if (!window.sq) {
      console.log ("Doing functions...")
      window.sq = squiffy;
      squiffy.story.save = function() {
        console.log ("Saving...")
        squiffy.set('_output', squiffy.ui.output.html());
        if (!squiffy.get('_transition')) {
          console.log ("Setting _transition...")
          squiffy.set('_transition', "squiffy.story.sections['ui init'].js");
        }
      };

      // Any code you put here will be run once at the start of the game, and again when the
      //   game is reloaded so you can use it to define functions and stuff, or anything else
      //   that you want to be done as soon as the `sq` variable is available
      $(function() {
        var updateSidebar = function () {
          if (!$('#sidebar').length) {
            squiffy.ui.write(squiffy.story.sections['sidebar container'].text);
            // Ugly hack to stop the sidebar being hidden by the top/bottom bars in the editor
            // and allow you to remove the sidebar so you can look at the code while testing
            //     (it will reappear as soon as you visit another section in the game)
            if ($('.ui-layout-north:visible').length) {
              $('#showinv').css('top', $('#showinv').offset()['top']+92);
              $('#sidebar').css('top', $('#sidebar').offset()['top']+92);
              $('#sidebar').css('bottom', '100px');
              $('<button>', {id: 'killsidebar', style: 'position: absolute; bottom: 4px; right: 4px'}).click(function () {
                $('#killsidebar,#sidebar,#showinv').remove();
              }).text('kill sidebar').appendTo('#sidebar');
            }
            $('#showinv').click(e => $('#sidebar').animate({left: 0}));
            $('#hideinv').click(e => $('#sidebar').animate({left: 5-$('#sidebar').width()}));
          }
          if ($('#sidebar:visible').length) {
            $('#sidebar').children(':not(#hideinv,#killsidebar)').remove();
            $('#sidebar').append(squiffy.ui.processText(squiffy.story.sections['sidebar contents'].text));
            if (squiffy.get('gameover')) {
              $('#sidebar a').remove();
            }
          }
        };
        squiffy.story.originalGo = squiffy.story.go;
        squiffy.story.go = function (section) {
          console.log("go called for "+section);
          if (section.match(/^quick:/)) {
            var sec = squiffy.story.sections[section.replace(/^quick:\s*/, '')];
            if (!sec) {
              console.log("No such section: "+section);
              return;
            }
            squiffy.story.run(sec);
            squiffy.ui.write('<blockquote style="display: block; left: 2em; right: 2em; border: 1px dotted grey;">' + sec.text + '</blockquote>');
            squiffy.story.save();
          } else {
            console.log("Running originalGo");
            squiffy.story.originalGo (section);
          }
          if (squiffy.afterText) {
            var after = squiffy.afterText;
            delete(squiffy['afterText']);
            after();
          }
          updateSidebar();
        };
        updateSidebar();
      });
      // End of game-specific code

    }
    if (squiffy.get('_section') == "ui init") {
      console.log ("Moving to first section...")
      squiffy.story.go('Start');
    }

[[Start]]:
    squiffy.story.go('Continue');

@set potions = 0
@set bottles = 1
@set acid = 0
    
This is the starting page! Is it working?

[[Continue]]

[[sidebar container]]:
<button style="position: fixed; top: 6px; left: 2px;" id="showinv" type="button">Show sidebar</button>
<div id="sidebar" style="background-color: white; position: fixed; left: 1px; top: 4px; bottom: 4px; width: 20%; overflow-y: auto; border: 1px double blue;"><button style="position: absolute; top: 2px; right: 2px;" id="hideinv"  type="button">Hide sidebar</button></div>

[[sidebar contents]]:

<p style="font-weight: bold;">Inventory</p>

<p>You are {if health=true:healthy}{else: unhealthy}.</p>

{if potions>0:<p>{if potions>1:Has {potions} potions}{else:Has a potion}. [[Drink potion]](quick:drink potion)</p>}
{if acid>0:<p>{if acid>1:Has {acid} flasks of acid}{else:Has an acid flask}. [[Drink acid]](quick:drink acid)</p>}
{if bottles>0:<p>{if bottles>1:Has {bottles} empty flasks}{else:Has an empty flask}.</p>}
{if mixbottle>0:<p>{if mixbottle>1:Has {mixbottle} flasks}{else:Has a flask} filled with a weird goop you can't pour out. [[Discard]](quick:discard mix)</p>}
{if key=true:<p>Has {if dissolvedkey:partially dissolved }key {if not eatenkey:[[Swallow]](quick:eat key)}{else:(swallowed)}</p>}
{if soot:<p>Covered in soot.</p>}
{if talisman=true:<p>Has weird glowing talisman [[Rub]](quick:rub talisman)</p>}

[[rub talisman]]:
@set not talisman

You rub the talisman, and your hair seems to stand on end. You feel like an epic vision-quest is about to begin. {if bottles>0:{@bottles-=1}{@potions+=1}Suddenly, with a sloshing sound, one of your empty bottles fills up with a rainbow-coloured liquid. }Then the talisman vanishes.

[[eat key]]:
@set eatenkey

You know it's a really bad idea, but you swallow the key.

[[Continue]]:
<p>You see a potion and a key.</p>
<p>[[Get the potion]](get potion,takenpotion=true)</p>
<p>[[Get the key]](get key)</p>
<p>[[Move on]](hallway)</p>

[[get potion]]:

<p>You pick up the potion</p>
@set potions = 1
{if key=true:}{else:<p>[[Get the key]](get key)</p>}
<p>[[Move on]](hallway)</p>

[[get key]]:

@set key=true

<p>you pick up the key</p>
{if not takenpotion:<p>[[Get the potion]](get potion,takenpotion=true)</p>}

<p>[[Move on]](hallway)</p>

[[hallway]]:

You are in a hallway.

Doorways lead to the [[zoo]] and the [[kitchen]].

[[zoo]]:

You are in the zoo.

You can go to the [[science lab]] or the [[hallway]].

There is also a heavily locked cage door{if corroded: with acid burns on it}. You could kick it with all your strength and never make a dent. {if acid>0:Or you could try to [[melt through it with acid]].}{if key=true:Or [[use the key]] to open it.}

[[melt through it with acid]]:
@dec acid
@inc bottles

{if corroded:The bars are already melted, and drop right away. But security is charging angrily towards you, so your only option is to proceed [[into the cage]].}{else:{@corroded}The bars start to melt, but one bottle of acid isn't enough to remove them completely. And the zookeeper has called security, chasing you back towards the [[hallway]].}

[[into the cage]]:
    var completion = 30;
    $.each("bottles potions acid key health talisman eatenkey dissolvedkey mixbottle soot".split(/ /), function (i, attr) {
      if (squiffy.get(attr)) {completion+=7};
    });
    squiffy.set('complete', completion);

@set gameover

You step nervously into the zoo cage, wondering what kind of terrifying beast could lie beyond. A second later you see the cutest, most beautiful kitten you have ever laid eyes on, purring happily.

Congratulations, this is as close as this game gets to an ending. I hope you've not found any bugs along the way.

You achieved {complete}% completion on this play through.

[[use the key]]:
{if eatenkey:You stick your fingers down your throat and attempt to regurgitate the key. Unfortunately, this doesn't seem to work so well.}{else:The key doesn't seem to fit this door. But the zookeeper has seen you fiddling around, and is charging towards you. You'll have to come back later, once you found the right key.}

Would you like to flee towards the [[hallway]], or the [[science lab]]?

[[science lab]]:
You are in a science lab.

There is a huge vat of acid against one wall. If you want to, you could try [[jumping in]]{if bottles>0:, or [[fill a bottle with acid]]}.

Alternatively, you could go back up the stairs to the [[zoo]], or take the back passage to the [[carnival]].

[[jumping in]]:
    squiffy.afterText = function () {
      squiffy.story.go('gameover');
    };

That was a very silly thing to do. You are dead. In fact, you are very quickly dissolving.

[[fill a bottle with acid]]:
You reach down to the acid vat, and attempt to collect some.

{if bottles>0:{@acid+=1,bottles-=1}You quickly fill {if bottles=0:your empty bottle}{else:one of your bottles} with acid from the bath, being extra careful not to burn yourself.}{else:{@potions-=1,mixbottle+=1}But as you try to fill the bottle, it seems to be full of some sparkling rainbow liquid, very much like a health potion. You end up with a bottle filled with a weird mixture, which quickly solidifies.}

Now you can {if bottles>0:[[fill another]](fill a bottle with acid) or }go to the [[zoo]] or the [[carnival]].

[[carnival]]:
    if (!squiffy.get('talisman') && squiffy.get('bottles')) { setTimeout(function () {squiffy.story.go('quick:fortune teller');}, 4000); }

You are in some kind of carnival or circus. You don't see much of interest, but there are all kinds of snake-oil salesmen trying to sell you weird stuff, and dancers so exotic you don't know where to look.

{if tellerconfused:{if not talisman:{@talisman}A confused-looking fortune teller hands you a strange, mystic talisman. They seem to assume you will know what it means.}}

From here, crowds block most of the exits but you can see a way to get inside to either the [[science lab]] or the [[kitchen]].

[[fortune teller]]:
    squiffy.set('tellerconfused', squiffy.get('bottles')==0);
    squiffy.set('potions', squiffy.get('potions')+squiffy.get('bottles'));
    squiffy.set('bottles', 0);

As you walk away, one of the carnival fortune tellers makes a strange gesture towards your back. You're not sure what happened, but you get a strange urge to look at the bottles you were carrying.

{if tellerconfused:The bottles are exactly in the state you left them.{if not talisman: A second later, he calls out to you.}}

[[drink potion]]:
@dec potions
@inc bottles

@set health = true

<p>Gulp! you are now healthy!</p>

[[kitchen]]:

You are in a small galley kitchen that lies just off the main [[hallway]]. A back door leads outside, where there seems to be a [[carnival]] going on.

There is also a large sink, and a pile of glass [[bottles]](take bottle) which look like they have just been cleaned.

[[take bottle]]:
    if (squiffy.get('potions')+squiffy.get('bottles')+squiffy.get('acid') > 3) {
      squiffy.story.go("handsfull");
    } else {
      squiffy.set('bottles', squiffy.get('bottles')+1);
    }
    
You pick up a{if bottles>1:nother} bottle from the pile.

Would yout like to [[take another]](take bottle), or go back to the [[hallway]], or out to the [[carnival]]?

[[handsfull]]:
You're not some kind of octopus! Your hands are full

You can go to the [[hallway]], or the [[carnival]].

[[drink acid]]:
    squiffy.afterText = function () {
      if (!squiffy.get('health')) {
        squiffy.story.go('gameover');
      } else {
        squiffy.set('health', false);
      }
    };

@dec acid
@inc bottles

You drink a bottle of acid. That was a bit dumb.
{if health=true:You are no longer healthy.{if eatenkey:{@not eatenkey,dissolvedkey} You immediately throw up, and can recover a rather corroded key from the pool on the floor.}}{else:You were already sick, and now you are dead.}

[[discard mix]]:
@set mixbottle = 0
@set potions = 0
@set acid = 0
@set not health
@set soot

{if not eatenkey:{@not key}}

You throw the weird mixture away and it explodes, scorching your clothes and burning up most of the things you were carrying.

[[gameover]]:

@set gameover = true

GAME OVER

This is the end of the game for now, but there should be a restart link somewhere in the top right.

Ah, I made the change there in that quiffy.template.js file, and the global passage works! The story works great that way. The only thing about that is when you run the story in the editor, it gives you a nice warning for each call to that global passage:

WARNING: null line 9: In section 'another' there is a link to a passage called [globalpassage], which doesn't exist

So, I'm looking in that same folder at compiler.js at line 499 where it has the code for showing the warning. Ah, but this would take me hours to figure out. I'm thinking we have to add that global passage to some link list.


One more thing to remember: If moving to a new computer, you'll have to remember to make the same changes :p
And remember that this isn't standard functionality when helping other people on the forum.

The only thing about that is when you run the story in the editor, it gives you a nice warning for each call to that global passage:

I guess the online editor doesn't do that, because I've never seen anything like that. I've spent too long trying to debug something, and it turns out to be I've mistyped a section or passage name.

In any case, it would be nice to tidy up the warning.

So, I'm looking in that same folder at compiler.js at line 499 where it has the code for showing the warning. Ah, but this would take me hours to figure out. I'm thinking we have to add that global passage to some link list.

Looks like the change needs to be at line 470 above, where it gets the list of names to check against.

        this.checkPassageLinks = function(story, links, section, passage) {
            if (!story) return;
            var badLinks = _.filter(links, function(m) { return !this.linkDestinationExists(m, section.passages); }, this);
            this.showBadLinksWarning(badLinks, 'passage', '[', ']', section, passage);
        };

The parameter section.passages there is the list that the current links should be checked against. A similar function checkSectionLinks uses story.sections.

Depending what version of javascript we're using here, we might be able to replace section.passages in that line as follows:

        this.checkPassageLinks = function(story, links, section, passage) {
            if (!story) return;
            var badLinks = _.filter(links, function(m) { return !this.linkDestinationExists(m, {...section.passages, ...story.sections[''].passages}); }, this);
            this.showBadLinksWarning(badLinks, 'passage', '[', ']', section, passage);
        };

However, I'm not sure if that would cause an error if you don't have a global section.
Might be better to do:

        this.checkPassageLinks = function(story, links, section, passage) {
            if (!story) return;
            var validPassages = section.passages;
            if (story.sections['']) {
                if (story.sections[''].passages) {
                    validPassages = Object.assign({}, validPassages, story.sections[''].passages);
                }
            }
            var badLinks = _.filter(links, function(m) { return !this.linkDestinationExists(m, validPassages); }, this);
            this.showBadLinksWarning(badLinks, 'passage', '[', ']', section, passage);
        };

(this is off the top of my head, so make a backup before testing)

(for reference, ways to combine multiple plain objects would include:
Spread syntax: {...object1, ...object2} (only in recent versions of javascript)
Using assign: Object.assign({}, object1, object2) (if you omit the {}, it would modify the first object, which probably isn't desirable)


Yes, I'm aware that a new computer or even a program update or reinstall will undo this tinkering.

I made the changes to compiler.js as you proposed and it works like a charm!

You know, the desktop editor gives warnings about your silly game, too. When you build it, it works just fine, but the editor gives errors when you load it. Similar situation. It doesn't like the "quick:" prefixes for your passages, saying they don't exist.


I expected it would. As I've only used the online editor, I've not seen those messages. I've just been trying things to see what works.

If I was building that game now, I would probably just use passages in the master section for the inventory sections; which would be a lot easier. It was an experiment, really; poking around in the engine to get the requested result.


One thing I do need to do, though, is make the script-initialising script work correctly if the _transition attribute is used for its intended purpose. I'm not quite sure what _transition was meant for, but it probably wasn't this.

To make my modified functions load (so that I can add extra features as needed to any specific game), while still allowing squiffy.ui.transition to behave as originally designed, I think it would look something like this:

[[ui init]]:
    squiffy.story.save = function() {
      squiffy.set('_output', squiffy.ui.output.html());
      var initialiser = "squiffy.story.sections['ui init'].js";
      var transition = squiffy.get('_transition');
      if (transition != initialiser) {
        squiffy.set('_real_transition', transition);
      }
      squiffy.set('_transition', initialiser);
    };

    // Game-specific code goes here

    var realtransition = squiffy.get('_real_transition');
    if (realtransition) {
      eval('(' + realtransition + ')()');
    }

    // redirect to the real first section if necessary:
    squiffy.story.go('intro');

This means that other sections should be able to have code like:

    squiffy.ui.transition(function () {
      // some code here that changes the colour scheme during this section, perhaps?
      // or whatever the intended use of this function was

      // It will be run immediately,
      // and again on if the game is refreshed/resumed while in this section
    });

Maybe you could just do this:
(I haven't tried it, but it will probably work)

[Global Passage]:
code here

@User72:
No, that only works if it's in the same section as the link.

I would have expected this to work, but on looking at the code it seems not. So I created the code above to make it work :)


Came in late in this one. You pretty much want a reusable section. I've done something like that before, and I'm going to try to remember how it works...

[[firstSec]]:
We are in the first section.

{@retAddr=firstSec}

[[Go to the special section]](mulituse)

[[Go to the next section]](nextSec)

[[nextSec]]:

Here we are in the second section.

{@retAddr=nextSec}

[[Go to the special section]](mulituse)

[[mulituse]]:

We are now in the multiuse section.

[[Go home]]({retAddr})

Whenever you call multiuse, you need to make sure you've set retAddr to where you want to return to. It dosn't have to be the calling section (I just did that to make it obvious). Odds are you'll want to advance the plot and move forward. But this let's you reuse the same code can come back easily.


Whenever you call multiuse, you need to make sure you've set retAddr to where you want to return to.

That's another way of doing it, but ends up a little clunky setting an attribute in every section.

You could simplify this with something like:

[[]]:
    if(!squiffy.story.section.text.match(/\{@?retAddr/)) {
      squiffy.set ('retAddr', squiffy.get('_section'));
    }

If I got that right off the top of my head, it should set retAddr to the current section name whenever you visit a section that doesn't contain a link to it. Could be improved, but it should mean you don't always need to remember to include the {@retAddr=nextSec} line. But you can if you want to override it to return somewhere else (for example if the current section has effects that you don't want to repeat on returning).

I think I prefer the 'global passages' approach myself, but that's probably a personal preference thing.


Yeah, I try to stay as much in squiffy as I can. The editor isn't very good and if you use JS code, you have to handle it very carefully.

Sometimes, I have to admit, it feels a little weird hopping from JS to squiffy and back in one block of code.


Log in to post a reply.

Support

Forums