Newbie looking for some guidance

Hi all!

Made a mess of the first topic, but apparently you can’t fully delete it yourself, sorry!

New to the game-writing scene, and hoping for a couple of pointers. Partially to make sure I’m starting along the right lines, partially to make sure I stick to them going forward!

I’m working in Twine v2.9.2, Sugarcube, v2.37.3

So far I’ve managed to figure out the basics of adding macros/JS/CSS (not a coder, but I can usually follow what’s going on if it’s not too complex), and I’ve been able to:

Add a date/time system

Using Hituro’s DATESYSTEM
Day X

Monday 09:30

format for ease. I don’t want things like holidays to get in the way/break immersion, so I’m not using months/years.

Figure out a ‘back’ system

If a player navigate to an in-game settings page it makes sense that the ‘back’ button would return them to the previous passage.

Achieved via:
`[<<if State.passage != “testing”>><<link “testing”>><<set $passageBack = State.passage>><<goto “testing”>><><><<link “back”>><<goto $passageBack>><><>]`

(for some reason it keeps removing parts of the above and it won’t wrap in the `s but I’m sure you can work it out!)

Macro for coloured speech

This one was, admittedly, ChatGPT work (I know, “boo hiss,” but I’m not a programmer!), but it allows me simple coloured <<say “npc_name”>>speech<>

The eventual plan for what I’m working on is that it will have a centralised story path, however be effectively open-world and non-linear outside of that. I’m using the time tinkering with the prologue elements to try and not only learn how the platform works, but also to try and learn some best practises, which is one of the things I would love some help with.

In particular I’ve already realised that reusing elements as frequently as possible makes sense. Menu bars and passage header sections etc don’t need writing out in full every time for obvious reasons.

So, onto the real meat of what I’m asking!
There’s a few things that I’d like to incorporate, but would prefer to do them the right way from the start. Having to rework everything when I’m 200 passages in would not be fun!

So my queries are regarding, in no particular order:

Include/similar for events

If I’m working with a passage called ‘room_kitchen’ I would prefer to pull events into it, rather than create new passages for each event/‘page’.

For example if I have a three passage series on ‘making lunch’ I would prefer to pull that into the ‘room_kitchen’ passage, with each ‘nested passage’ linking to the next. This way I don’t have to rewrite the room every time, and I can account for requirements (“if it’s the right time” OR “if the following other conditions are met” ELSE “otherwise do one of these at random”)

Is using include the best way forward here? I don’t know how I could passage two/three of ‘making lunch’ using them.

Item/clothing system

I’d like to incorporate an item/clothing system, but I’m not sure where I’m best looking.
Ideally it would allow for single-buy items (I wouldn’t need two of the same armour piece, for example, so this would be prevented) and multi-buy (health potions, say).
The items/clothes would be taggable (‘armour’ or ‘work’ for example) so that I could refer to them elsewhere. “it’s raining, so to go outside your jacket must have the ‘waterproof’ tag or X happens instead” / “if you go to work wearing clothes that aren’t tagged ‘work’ then X happens”
There would need to be the option for unremovable items (like cursed equipment)
It would also ideally have some way to have requirements for equipping - “can’t wear X armour without Y defence stat”

Avatar that changes

Looking at proper graphic design would be way down the line, but it would be nice to have an avatar in the side menu that reflects the player’s chosen attributes (like hair/eye colour) and equipped items. Is there a prebuilt system for this, at all?

Right sided menu

Is there a way to move the default menu to the right hand side? All I could find in my searches was replacing it with a custom one.

My apologies for all the questions (and again for making a mess of the first topic!), I know there’s a lot there! I’ll probably have more down the line, but I’m trying to jog before I run! =D

There’s a lot of different questions here, so I can only answer some of them at once :smiley:

Events/Inclusion

For your event system, yes, <<include>> is a good way to bring content into a passage. You can easily write something like:

<<if $day is "Monday" and $metPrince is false>>
    <<include "meetPrinceFirstTime">>
<<elseif $metPrince is false>>
    <<include "maidTellsYouAboutPrince">>
<</if>>

This is a perfectly plausible way to do things, especially if each event can only happen in one place.

If these events could happen in a bunch of rooms, you might move the conditions themselves to a passage and just do <<include "check for prince events">> in each passage. This gets difficult when some events are shared and some are not. If you have enough of that sort of thing you may need a more robust event management system. One option is to use MQBN another one of my macro sets, which enables you to move the conditions to the events instead, and just do a simple:

<<set _events = MQBN.getStorylets()>>
<<for _event range _events>>
   <<storyletlink _event>><</storyletlink>>
<</for>>

Another option is to write your own. It’s not easy, but you can do it.

My general advice is use the easiest solution for the complexity you expect to have. If your kitchen events are only ever in the kitchen, use the first approach. If there are a fixed set of events that can appear in 1+ rooms, use the second. If there are many events that can appear in many places, use an event manager.

2 Likes

Right Bar

You can move the UIBar to the right with very little CSS

#ui-bar {
  left: initial;
  right: 0;
}
#ui-bar.stowed {
  left: initial;
  right: -15.5em;
}
button#ui-bar-toggle {
  left: 0;
  right: initial;
}
#ui-bar-toggle::before {
  content: "\f054";
}
#ui-bar.stowed #ui-bar-toggle::before {
  content: "\f053";
}

That moves the bar to the right, swaps the arrow to the left side, and swaps the arrow directions (right when open, left when closed)

2 Likes

I’m no Sugarcube expert, but I spent a lot of time at the beginning of the year learning it and did some similar things to what you’ve described. I had a few hub passages that the player would return to multiple times; rather than use include, I just linked to them. They would look different based on how many times you’d visited that passage already and the true/false state of other variables (whether you’d spoken with a character or were carrying a certain item, for example). I used basic if statements like <<if $item== true>> alongside <<if visited() is 1>>, <<elseif visited () is 2>>, and that worked fairly well. If your game is really complicated and what happens at lunch depends on a lot of factors, it’ll be harder. You’d want to narrow in on what variables shape the lunch passage to get a sense of how complicated it would be and whether it’s worth it to try and return to the same hub passage.

I also played around with a shopping mechanic. I checked the player’s $currency, and if they didn’t have enough to buy something then there was no “buy” link. If they did, there was a link, and if they bought a one-off item I toggled a true/false variable that determined if it was an option to buy again. Here’s what that looked like…



<<set $seeds to 5>> /* <-- seeds was the currency; this was for testing*/ 

There are a few scrolls that could be helpful to your explorations. /* <-- this was the basic text that didn't change. Everything below this allows a response that immediately updates depending on what you buy. The _temporary variables just update the text; if an item's $permanent variable is true, the scroll shows up as an option to read at your home and does NOT show up as something you can buy.  */ 

<<lb>> <<nobr>> <<if _beets is true>>You hand over the seeds and tuck the scroll under your arm.<</if>><<if _vurms is true>>The scroll is suprisingly dense, tightly rolled and hefty.<</if>><</nobr>>

<<if $worms is false>>Illustrated Forager's Guide to Worms - <<if $seeds >=1>> <<link "1 seeds">><<set $seeds to $seeds -=1>><<set $worms to true>><<set _vurms to true>><<set _beets to false>><<update>><</link>><<else>>1 seed<</if>><</if>>
<<if $beetles is false>> Care and Taming of Beetles - <<if $seeds >=2>> <<link "2 seeds">><<set $seeds to $seeds -=1>><<set _beets to true>><<set _vurms to false>><<set $beetles to true>><<update>><</link>><<else>>2 seeds<</if>><</if>><</lb>>

<<link "Go back" `previous()`>><</link>>


You could make a repeatable item pretty easily, I think; the buy link would increase a number <<set $potions to $potions += 1>> to decide how many the player had, and that link would always be available as long as the player had sufficient $money. (You could add a second $potionStock variable for store inventory if you wanted a finite number of them, too, and then the buy link would both increase $potions and decrease $potionStock.)

I also come up with any of this myself; the liveblock (<>) macro in particular is fully copied from something that @30x30 shared, and you’d need to add the below to the javascript for the story to get that passage I shared to work. There are probably easier ways;



// liveblock macro
(function () {
	"use strict";

	$(document).on(":liveupdate", function () {
		$(".macro-live").trigger(":liveupdateinternal");
	});

	Macro.add(['update', 'upd'], {
		handler: function handler() {
			$(document).trigger(":liveupdate");
		}
	});

	Macro.add(['live', 'l', 'lh'], {
		skipArgs: true,
		handler: function handler() {
			if (this.args.full.length === 0) {
				return this.error('no expression specified');
			}
			try {
				var statement = this.args.full;
				var result = toStringOrDefault(Scripting.evalJavaScript(statement), null);
				if (result !== null) {
					var lh = this.name === "lh";
					var $el = $("<span></span>").addClass("macro-live").wiki(lh ? Util.escape(result) : result).appendTo(this.output);
					$el.on(":liveupdateinternal", this.createShadowWrapper(function (ev) {
						var out = toStringOrDefault(Scripting.evalJavaScript(statement), null);
						$el.empty().wiki(lh ? Util.escape(out) : out);
					}));
				}
			} catch (ex) {
				return this.error("bad evaluation: " + (_typeof(ex) === 'object' ? ex.message : ex));
			}
		}
	});

	Macro.add(['liveblock', 'lb'], {
		tags: null,
		handler: function handler() {
			try {
				var content = this.payload[0].contents.trim();
				if (content) {
					var $el = $("<span></span>").addClass("macro-live macro-live-block").wiki(content).appendTo(this.output);
					$el.on(":liveupdateinternal", this.createShadowWrapper(function (ev) {
						$el.empty().wiki(content);
					}));
				}
			} catch (ex) {
				return this.error("bad evaluation: " + (_typeof(ex) === 'object' ? ex.message : ex));
			}
		}
	});
})();

I hope this makes sense, it’s been several months since I worked in Sugarcube, and like I said, I’m not an expert, though I had fun looking back at all of this.

1 Like

That particular macro is the work of Cyrus Firheir: <<liveblock>>.

There are a few similar macros, but Sugarcube now has <<do>> and <<redo>> built in, so the need for the older solutions is much reduced.

1 Like

Ah, thanks! I wanted to credit, but I couldn’t find the message where we first discussed it. I had some issues getting do/redo to do (or redo) what I wanted. I forget why… I’ll go back to it someday. Probably everything I shared should have a “do not use” warning, haha.

Items / Inventory

This is a very complex topic, because there are a hundred ways to approach having an inventory. None of them are wrong, but there are easy pitfalls to fall into — number one being to store too much information in story variables, and not enough in setup (which is where information that doesn’t need to be in the game’s saves lives).

Many people look for a 3rd party inventory system, of which there are two popular choices, @Chapel’s Simple Inventory, and @HiEv’s Universal Inventory. I don’t think HiEv will object if I say that the Simple Inventory is a lot simpler, while the Universal Inventory is notorious for a steep learning curve (and the docs are not entirely complete). Both of these can do what you want, with varying degrees of difficulty, but an Inventory system is one place where I feel a custom system tailored to your own game is a better choice.

If you do make your own, the basic principles are as follows:

  • Store item definitions in setup
  • Store which items a player has in $storyVariables
  • When you need to check for a property (or anything else) of an item the player carries, look up the version in your master list.
  • Only things that change during the game (such as quantity) belong in a story variable

The following is a very simple idea of these basics. I’ll include stacking, but not conditions.

Create your master list of items in StoryInit

:: StoryInit
<<set setup.items = {
   sword: {
      id: "sword",
      name: "Sword",
      type: "weapon",
      keywords: [ 'melee', 'metal' ],
      stacks: false,
      damage: 10
   },
   helmet_copper: {
      id: "helmet_copper",
      name: "Copper Helmet",
      type: "armour",
      keywords: [ 'head', 'metal' ],
      stacks: false,
      armour: 5
   },
}>>

Use a story variable to track the player’s inventory

:: StoryInit
<<set $inventory = []>>

When you get an item, store only the reference in the inventory

<<if !$inventory.find((i) => i.id == "sword")>>
    <<link "Pick up the sword">>
       <<run $inventory.push({ id: "sword", quantity: 0 })>>
    <</link>>
<</if>>

When you need to check an item’s properties, use the id to reference the master list

You are carrying the following weapons:
<<for _item range $inventory>>
    <<if setup.items[_item.id].type is "weapon">>
       * <<print setup.items[_item.id].name>>
    <</if>>
<</for>>

You can create some convenience functions for yourself to make working with this easier. I’d have one for has(), pickup() and drop() at least.

setup.get = function(item) {
   return State.variables.inventory.find((i) => i.id == item);
}
setup.has = function(item) {
   return !!setup.get(item);
}
setup.define = function(item) {
   return setup.items[item];
}
setup.pickup = function(item,quantity=1) {
   let master = setup.define(item);
   let have   = setup.has(item);
   if (have && master.stack) {
      setup.get(item).quantity += quantity;
   } else if (!have) {
      State.variables.inventory.push({ id: item, quantity: 1 });
   }
}
setup.drop = function(item,quantity=1) {
   let master   = setup.define(item);
   let existing = setup.get(item);
   if (existing && master.stack) {
      if (existing.quantity > quantity) {
         setup.get(item).quantity -= quantity;
      } else {
         State.variables.inventory.deleteFirst(existing);
      }
   } else if (existing) {
      State.variables.inventory.deleteFirst(existing);
   }
}

(Note, I’ve not tested these … they probably work, but they are here as examples)

3 Likes

(Some testing done, should work now)

1 Like

Hi David! Even before replying you’ve managed to make my life easier, so thankyou! :grin:

Would you say that include is the right way forward if I potentially need to link between several different passages?

As a poor example (I’m on mobile, so excuse the poor formatting!):

We start in room_kitchen (which has no real content of its own) where we’ll do something like;

if time is 1100-1300

/> include event_lunch_1

/> or include event_lunch_2 (this may be if 1 has been seen before, or based on other requirements)

/> else include event_kitchen_generic

Each of the includes are to things that may have multiple passages themselves. event_lunch_1 might have links for make soup/make sandwich, which themselves have links to further passages.

Using an include here would take me away from the actual kitchen passage, which would mean each sub-passage would need to pull the passages for menus/header, set a _currentLocation for the header to pick up, set the header image, and do anything else that each passage eventually needs, as opposed to just being content text.

Each event can only happen in one place, but the event may be more than one passage (either to give choices, or prevent it being excessively long), and may be used more than once.

I did see MQBN but I don’t know if it would be overkill - if there’s a way I can move between included passages then the organisation and logic is something I should be able to manage.

I think I’ve covered some of point 1 in the above, but they could be fairly complex.

It could be based on time, whether you’ve done that event/the previous event, whether another variable is true/false, or all three and more. I’m pretty certain there will end up being some fairly complex logic layouts down the line, and I want to work future-proof! :sweat_smile:

For point 2, I considered trying to do it by hand, but I know it would get out of control. There will, in time, likely be 100+ items, and I’d need to be able to move them between my ‘on-hand’ storage, being equipped (with some having equip/equip requirements), and stored elsewhere.

1 Like

I really hope you didn’t hand-write that for me, haha!

As a non-coder I would likely end up GPTing a lot of the code - if I was going to try and pick up a programing language it would be Python for unrelated reasons, and I simply don’t have the time for it!

I’ll do some further digging into the two you linked - there’s a good chance that if one/both nearly fit then I could find a workaround, I’m sure.

Also apologies for the triple post - I’m unfamiliar with the forum software, and it’s not quite as intuitive as I was expecting on mobile :sweat_smile:

The complexity there is in creating the items and their information, though, and no pre-rolled system really reduces that. There are pre-made inventory systems, but not pre-made item lists.

Moving things between multiple inventories is not really any harder than managing one. You just have a story variable for each inventory, and then change your helper functions to also take the name of the variable as an argument.

setup.get = function(item,inventory="inventory") {
   return State.variables[inventory].find((i) => i.id == item);
}

Now you can do things like <<if setup.has("sword","chest")>> and so on.

(Note: if you are doing this you may want a few more guardrails to check that the inventory exists, that it’s an array, and so on).

One of the things to learn about using SugarCube, is you need to fight the urge to avoid passage navigation. In SC, no change is saved until a passage navigation happens. If you present 5 screens of text (say, using <<replace>> or <<do>>) with choices, variables, etc. without changing passage, and then the user reloads (or closes their browser, or even just switches mobile app) then all that is lost. Similarly if they saved at the end of all that, and loaded the save, they would be at the start of the sequence again.

So you should change passage often, to make sure the player doesn’t lose progress. It’s how SC is designed, every other way is a workaround that you shouldn’t overuse. (yes, you can use Engine.play("passage") to reload the current passage, but then you might as well navigate.

You have many tools to make this easy. Use the PassageHeader and PassageFooter passages to display information, use StoryInterface to get a custom UI, use a story variable to change the location, etc. etc.

If you try my Ectocomp game Dr Morben’s Asylum you’ll see a location name on each passage (you need to turn it on in settings), and that is just a story variable called $location that I see in the first passage of each area.

That bit is easy enough, as I’d simply be creating them as they became relevant to the game. The monotonous legwork I can deal with, it’s reliable coding that I’m going to be crap at! GPT has done bits of JS for me in the past for something else, and while I usually get where I need it’s been horribly unreliable. Selfish though it is I’m more interested in designing the system than building it, haha. Even my working html/css is pretty iffy these days, as things like WordPress make life much easier and things have come a long way since I last wrote it by hand! That said I’m sure it could be done…

(as an aside, what’s the code for the code boxes, please? Can’t find it in the menus)

Okay, that actually makes a lot of sense then. I was simply trying to be outrageously lazy, but perhaps that isn’t the way forwards!

As it stands for each passage I’m doing something like this, however it will probably get tweaked/added to as I start to flesh things out.

<<nobr>>

/*
Description: partially so I can plan ahead, mostly for when I come back to edit later
*/

<<set _currentLocation = "Location">>

<<include "menu_bar">>
<<include "header">>

<img src="location_image" width="100%"><</nobr>>

Are PassageHeader/Footer ‘standard’ passages to use?

My menu bar currently just has a testing link in it, but will later the filled.

The header has a right-float _currentLocation followed by a horizontal rule, but that’s only because I haven’t yet gotten round to moving the location image to _locationImage or similar yet.

Edit: slowly figuring discourse out! Too used to ProBoards and a heavily-modified phpBB!

PassageHeader is automatically included before the passage, and PassageFooter after.

There are also PassageReady and PassageDone for code that runs before and after the passage but doesn’t output anything.

There are plenty of other special passages (like StoryInit which runs when the game starts, StoryMenu for links on the menu bar, etc. etc.) See Special Names in the SugarCube manual.

If you just change _currentLocation to $currentLocation it will stay set until you change it. Then you can put it in PassageHeader and you are all set.

re: Macro for coloured speech

One of the many issues with using a LLM like ChatGPT to find a solution to a Twine related problem is that they will often re-invent the wheel instead of suggesting the usage of existing examples / functionality / macro libraries.

For example:

1: The Basic usage (container) example in SugarCube’s documentation for its <<widget>> macro shows how to implement a very basic <<say>> widget.

2: Chapel created a Speech Box System macro addon, that many other Authors who include “Dialogue box” like functionality in their projects use.

Noted! Looks like I didn’t do enough homework!

I went with a temporary variable as I thought they were only valid for the current page, and I figured it would change regularly enough that it made sense. I don’t really know. It looks like when I get back to tinkering with things on Monday (away until then, using up some annual leave) I’ve got some reworking to do!

It’s definitely a downside, but I still needed it for the other half (which could definitely have been made separately) where the pc’s allocated colour changes based on one of their stats. Another thing to look at again :grin:

For the record, I did indeed.

In that case I promise I’ll take a stab at building one myself! :grin:

I’ve actually extended that code into a working proof of concept with multiple inventories, drop and pickup. It’s not what I would release as a user-friendly thing (mostly it’s all functions instead of macros), but I can share it with you if you like.

You’re more than welcome to, of course!

I’ve spent a little time with GPT running ideas, and it’s given me something it reckons should cover all the extra features and scenarios I’d considered.

I’ve yet to test it as I’m also packing for a holiday that starts tomorrow, but when I get back on Monday I’ll be spinning up a couple of blank stories to test out what it’s given me, the Universal Inventory system, and what you wrote out above. Figure it’s better to spend the time filling out the hidden workings before I write much, so I don’t have to go back and rework everything later!