0 votes
by (1.4k points)

Hello all!

SugarCube 2.21 forever!

I asked this in the comments to an older question, but I think it has been left behind in the great progress of Twine questions, so here it is on its own:

If I have a <<link>> macro in this format I do not know how to add an id:

<<link "key">><<set $unlock to 1>><</link>>

And in this format I do not know how to add any content like a <<set>> macro:

<a class="link-internal macro-link" id="key" tabindex="0">key</a>

As you can see from the older question, my intent is to have different keys activate different <<link>> macros, so that I can trigger any macros I want using keystrokes.

 

Thank you all!

Patrick

2 Answers

+1 vote
by (44.7k points)

Rather than manually adding IDs to each link, it makes much more sense to add them all automatically.  Since you're using SugarCube you can just drop the following JavaScript into your JavaScript section and that will all be taken care of automatically:

/* Keyboard links - Start */
var KBIntervalID = 0;
$(document).on(":passagerender", function (ev) {
	clearInterval(KBIntervalID);
	UpdateLinks(ev.content);
	// Search passages for links every 300ms, just in case they get updated, and marks them for key clicks
	KBIntervalID = setInterval(UpdateLinks, 300);
});
// Adds key shortcut indicators to links in passage if there are less than 11 links in the passsage.
function UpdateLinks(Container) {
	// Enables keyboard shortcuts on passages that do not have the "DisableKeyLinks" tag
	if (!tags().includes("DisableKeyLinks")) {
		var Links, i;
		if (typeof Container === "undefined") {
			Container = document;
			Links = $("#passages a").toArray();
		} else {
			Links = $(Container).find("a").toArray();
		}
		if (Links.length > 0) {
			for (i = 0; i < Links.length; i++) {
				if ((Links[i].getAttribute("data-nokey") == "true") || (Links[i].parentElement.getAttribute("data-nokey") == "true")) {
					Links.deleteAt(i);
					i--;
				}
			}
		}
		if (Links.length === 1) {
			if (!Links[0].id.includes("Link")) {
				Links[0].id = "NextLink";
			}
		} else if (Links.length >= 1 && Links.length <= 10) {
			var n = 1;
			for (i = 0; i < Links.length; i++) {
				if (!Links[i].id.includes("Link")) {
					while ($(Container).find("#Link" + n).length) {
						++n;
						if (n > 10) {
							break;
						}
					}
					if (n < 10) {
						$("<sup>[" + n + "]</sup>").appendTo(Links[i]);
						Links[i].id = "Link" + n;
					} else if (n === 10) {
						$("<sup>[0]</sup>").appendTo(Links[i]);
						Links[i].id = "Link0";
						break;
					} else {
						break;
					}
				}
			}
		}
	}
};
$(document).on("keyup", function (e) {
	// Enables keyboard shortcuts on passages that do not have the "DisableKeyLinks" tag
	if (!tags().includes("DisableKeyLinks")) {
		// Trigger next link click on right arrow key or "1" (normal and numpad)
		if (((e.key == "ArrowRight") || (e.keyCode == 49) || (e.keyCode == 97)) && $("#NextLink")) {
			if ((!tags().includes("IgnoreArrowKeys")) || ((e.key != "ArrowRight"))) {
				e.preventDefault();
				$("#NextLink").click();
			}
		}
		// Trigger link click on keys "0" through "9"
		if ((e.keyCode > 47) && (e.keyCode < 58)) {
			if ($("#Link" + (e.keyCode - 48))) {
				e.preventDefault();
				$("#Link" + (e.keyCode - 48)).click();
			}
		}
		// Trigger link click on numpad keys "0" through "9"
		if ((e.keyCode > 95) && (e.keyCode < 106)) {
			if ($("#Link" + (e.keyCode - 96))) {
				e.preventDefault();
				$("#Link" + (e.keyCode - 96)).click();
			}
		}
		// Trigger random click on "." key
		if (e.key == ".") {
			e.preventDefault();
			var Links = $("#passages a"), n, UsableLinks = [];
			if (Links.length > 0) {
				for (n = 0; n < Links.length; n++) {
					if (!$(Links[n]).data("nokey")) {
						UsableLinks.push(n);
					}
				}
				if (UsableLinks.length > 0) {
					n = random(UsableLinks.length - 1);
					Links[UsableLinks[n]].click();
				}
			}
		}
		// Trigger back click on left arrow key or backquote
		if ((e.key == "ArrowLeft") || (e.keyCode == 192)) {
			if ((!tags().includes("IgnoreArrowKeys")) || ((e.key != "ArrowLeft"))) {
				e.preventDefault();
				Engine.backward();
			}
		}
	}
});
/* Keyboard links - End */

You can see more information on this code and see exactly how it works in my Twine/SugarCube sample code collection here.  (Click "Jump to Start" in the UI bar to see other sample code there.)

If you still want to write your own code, instead of adding an ID to a Twine/SugarCube link, you can simply add the ID a <span> around that link, and then use ".parentElement" to check the parents of any links.  An example of how to do that is within the above code.

Hope that helps!  :-)

by (1.4k points)

Thank you for this!

Let me see if I get it right: for the project I have in hand at the moment I want to trigger different sets of macros for pressing and releasing the space bar to create a "one-button game". So I have this in the Javascript, taken from the earlier question:

(function () {
	$(document).on('keydown', function (ev) {
		if (ev.key === ' ') {
	$('a#press').click();
}
	});
}());

(function () {
	$(document).on('keyup', function (ev) {
		if (ev.key === ' ') {
	$('a#release').click();
}
	});
}());

And in the passage I have:

<span id="press"><<link "">><<set $press += 1>><<timed 0.1s>><<set $shield to 1>><</timed>><</link>></span>

<span id="release"><<link "">><<set $press to 0>><<set $shield to 0>><<if $press > 1>><<else>><<replace "#b">><<include "strike">><</replace>><</if>><</link>></span>

So far anyway.

How should I use .parentElement for this do you think?

 

by (159k points)

> The selectors used for your two click() function calls.

If you place your 2nd example in a Passage and then use your web-browser's built in Web Developer Tools to Inspect the HTML structure generated when that passage is viewed you will see it looks something like.

<div id="story" role="main">
  <div id="passages">
    <div id="passage-start" data-passage="Start" class="passage">
      <span id="press">
        <a class="link-internal macro-link" tabindex="0"></a>
      </span>
      <br>
      <span id="release">
        <a class="link-internal macro-link" tabindex="0"></a>
      </span>
    </div>
  </div>
</div>


If you select the 1st a (anchor) element and copy it's selector you will see that it looks something like

#press > a

(note: the > in the above indicates that the a element is a direct child of the parent #press ID'ed element)

Based on the above information and the fact that the selector of 2nd anchor element will look simular to the 1st we can extrapolate that the selectors needed to target the two links would look like

$('#press a').click();

$('#release a').click();

 

by (1.4k points)
Works like anything! Thank you very kindly Greyelf!
+1 vote
by (159k points)

> I do not know how to add any content like a <<set>> macro...

As long as the content you want to execute are only <<set>> macros then you can add a data-setter HTML Attribute to your 2nd example like so.

<a id="key" data-setter="$unlock to 1" class="link-internal macro-link" tabindex="0">key</a>


note: If the $unlock variable in your examples is meant to represent a type of situation where something can only one of two states (eg. either: true / false, yes / no, on / off) then you should be using a Boolean value instead of a Numerical value.

<<set $unlock to false>>
<<if not $unlock>>You fail to open the door, it's locked!<</if>>

<<set $unlock to true>>
<<if $unlock>>You open the door.<</if>>

<a class="link-internal macro-link" id="key" tabindex="0" data-setter="$unlock to true">key</a>

 

...