Skip to main content

Indie game storeFree gamesFun gamesHorror games
Game developmentAssetsComics
SalesBundles
Jobs
TagsGame Engines

Internet Janitor

976
Posts
58
Topics
8,139
Followers
27
Following
A member registered Aug 02, 2018 · View creator page →

Creator of

Recent community posts

Sent you a late submission link.

If anyone else missed the deadline but is close to completion, please feel free to reach out to me in the next day or two! Otherwise, there's always Deck-Month this December...

Delightful, and very ambitious! Well done!

A simple tweak/addition that might be nice would be to give the "end turn" button a shortcut key; perhaps space?

You can use the app.render[card] function to take a "screenshot" of a card as an image. If we did this for our "map" card in the previous example, the snippet of code for compositing both images together would instead be:

card.image.paste[app.render[map].map[1 dict 36]]
card.image.paste[route.image.copy[] 0,0 1]

Make sense?

It is also possible to render widgets individually and/or programmatically walk over some subset of the widgets on a card to inspect them or render their (rich) text on a canvas, etc. Happy to expand on that with examples if you find you need it.

This is a neat mechanical conceit for storytelling. I'd like to see you do more with it!

(1 edit)

As Flocon noted, Decker uses paletted color and doesn't have any notion of partial transparency, so there are some limits to what we can do here, but I can see ways to achieve something like the effect you describe. If you haven't already, I recommend reading through All About Color for some relevant background information.

Let's assume for simplicity that you're using Decker's default color palette, the "map" and "route" cards are fully in black-and-white (patterns 0 and 1), and everything you want to fade in is part of the card backgrounds (no text in fields, etc).

If we created a new card to tinker with, we could use a script in The Listener to composite the card backgrounds together, assigning a different color to the pixels of "map":

card.image.paste[map.image.copy[].map[1 dict 36]]
card.image.paste[route.image.copy[] 0,0 1]

(Be careful to save before doing this sort of thing in the Listener; it's a very powerful tool, but you can't "undo" changes performed this way!)


We could then manipulate the color Decker uses to display the "map" (I picked 36 here arbitrarily) to fade it in or out on the fly. Here I have a slider with a range of 0-255 wired up to let me change it manually:

on change do
 patterns[36]:"%06h" parse 6 take "%02h" format me.value
end


The way I'm computing the "packed RGB" grayscale value here is by turning the 0-255 value of the slider into a 2-digit hex code, replicating it 3 times, and then parsing the result as a 6-digit hex number, which may be a little confusing. All About Color has some functions in the "col" module to make computing packed colors a bit easier.

How's that for a starting point?

(1 edit)

one extra little trick: normally if you click an "invisible" button, Decker inverts the colors of the background within the bounding box of the button. If you then additionally set the button to "show transparent" it will be fully invisible- just a clickable rectangle that gives you a pointer  cursor when you mouse over it.

(This deck rules, btw, Salty.)

(1 edit)

Having seen your stream, this can be simplified; for some reason I had assumed you wanted something integrated with the font editor.

Given a field named "f" and a canvas named "c", you can set the font of the canvas and field to your custom font manually, and then give the field the following script:

on change do
 c.clear[]
 c.text[f.text (0,0),(1,2)*c.lsize]
end

Since the contents of the canvas is always being redrawn from scratch to reflect the field contents, you could optionally reduce the file size of your deck a little bit by marking the canvas as "volatile" and dividing the script into a card script that updates the canvas whenever the card is refreshed:

on view do
 c.clear[]
 c.text[f.text (0,0),(1,2)*c.lsize]
end

And a script for the field to kick off an update whenever it changes:

on change do
 view[]
end

And if you wanted the canvas to always reflect the font(s) chosen for the field widget and any rich text inside it, you could generalize the script further to copy the field's font setting to the canvas before doing any drawing and use the field's .value (the rich text contents of the field) instead of .text (the plain-text contents):

on view do
 c.font:f.font
 c.clear[]
 c.text[f.value (0,0),(1,2)*c.lsize]
end

Hope that helps!

This is surprisingly mesmerizing to fiddle with; easy to see how a few seconds of drum break spawned the entire Jungle genre.

Yes! PuzzleScript embeds a JS rewrite of Sfxr, and the PuzzleScript "sound codes" are seeds for a random-number generator which produces all the other sound parameters. I initially considered a similar model for Dfxr- or potentially even using PuzzleScript-compatible sound codes- but that wouldn't make it possible for users to save sound effects they've manually tweaked after generation. Dfxr instead packs all the audio parameters into fixed-precision numbers and Base64 encodes the result. The codes are longer, but they can equally represent random presets or manual edits.

A finely crafted delight as always, ahm; well done!

From the decker reference manual,

If the grid has headers and more than one column is displayed, you can drag the space between column headings to resize the columns. By default, every column is given equal horizontal space. You can restore default spacing by clicking "Reset Widths" in the grid properties dialog.

The .widths property is described in the Grid Interface section, along with all the other scriptable properties:

x.widths: A list of up to 255 widths of table columns, in pixels. r/w.

Want to make a point-and-click adventure game faster to navigate with (optional) keyboard controls?

Wire up your game normally, with buttons (visible or invisible) that move between cards, but follow a naming convention for the buttons: name them "up", "down", "left", or "right" based on which keyboard cursor key you'd like them to correspond to. Then, in the deck-level script, override the navigate[] event handler to look up buttons on the active card by name and simulate clicking them:

on navigate dir do
 deck.card.widgets[dir].event.click
end

This won't interfere with clicking on the buttons normally. If any given card doesn't have buttons with those names, this script will harmlessly do nothing.


In interact mode, you can drag dividers between the column headers to adjust their sizes:

If you ever want to "unfix" the column widths you can click "Reset Widths" from the grid properties panel.

You can also set the column widths of a grid with scripts or via the Listener with the "grid.widths" property, which accepts a list of column widths in pixels:

mygrid.widths:60,30

Manual resizing constrains the minimum width of columns so that you can always distinguish their drag handles, but with scripting/the Listener it is possible to set columns to 0 pixels wide, as a way of hiding columns from the user without removing them from the underlying table.

Does that make sense?

(1 edit)

In Lil, the number 0, the empty string "", nil, the empty list (), or an empty dictionary is considered false and anything else is considered true. Most of the time, 1 and 0 are the clearest way to indicate true or false, respectively.

Your examples should probably be

on click do 
 canvas1.draggable:1
end

and

on click do
 canvas1.draggable:0
end

Does that make sense?

There are a few ways to do what I think you're asking for. You can set a canvas to have a "scale" setting greater than 1 to upscale whatever is drawn on it, and then draw text on it, or you could draw text on a canvas normally, copy the contents of the canvas as an image, upscale that, and then draw the upscaled image back to the canvas.

Presuming we start with the glyph editor in "All About Fonts", we could do the following:

1) create a new canvas widget and name it "upscaled". From its "Canvas properties" modal, set the scale factor as "2" (just for starters).

2) modify the card script, adding the following three lines to the "on view ..." function after the line that reads "sample.font:f"

upscaled.font:f
upscaled.clear[]
upscaled.text[sample.text (0,0),(1,2)*upscaled.lsize]

(This will word-wrap the beginning of the text in the "sample" field within the canvas, with a little extra room so it cuts off instead of ellipsizing)

3) tweak the "sample" field so that it triggers an update of the UI whenever it is changed by giving it a script like so:

on change do
 view[]
end

Now the upscaled preview will be updated whenever you draw on the glyph, change other font parameters, or type in the sample field. You can also adjust the "scale" property of the canvas as desired:


How's that?

(1 edit)

No, "Edit -> Add Outline" doesn't presently have a keyboard shortcut in either version of Decker.

I generally don't try to keep this sort of thing secret. Unless there's something i've straight-up forgotten to document, I've already spilled all my secret beans.

Your "srch" and "show" variables contain strings. In the context of a query subexpression they will be interpreted as string values, not the contents of a column by name. You can, however, use the implicit "column" dictionary to perform this indirection, something like

result:select column[show] where column[srch] like term from internal_copy.value

(the "column" dictionary is also how you can reference columns whose names are not valid lil identifiers.)

You might want to take a look at the examples for the "_hideby" column in grids; it's designed to assist in searching and filtering grid contents without requiring an auxiliary source of truth. 

correct. The text will always be some arrangement of DeckRoman letters and symbols, but you can make them look like whatever you want by making a custom font and applying that font to widgets in the deck.

(1 edit)

It wouldn't damage the files, but it wouldn't add support for those characters. When Decker loads a .deck file (including one embedded in a .html export), it normalizes UTF-8 into the supported DeckRoman character set and converts unsupported characters into �. A manually-edited deck would lose unsupported characters before scripts have a chance to run, and the file would not round-trip retaining that data.

As in Native-Decker, it would be possible to create a modified version of Web-Decker with a different set of lookup tables for converting between UTF-8 and DeckRoman. (This would make such decks incompatible with mainline Decker!) You'd also need to furnish a set of fonts corresponding to this rearranged text encoding- presumably at least replacing Decker's three built-in fonts 'Mono', 'Menu', and 'Body'.

If you just want to display text in a non-latin alphabet you can simply make custom fonts using Decker's font editor, but the underlying text will still be DeckRoman from the perspective of copying and pasting to the system clipboard, typing on a keyboard, or saving a .deck file to disk.

Please see this post from yesterday:

https://itch.io/post/16642684

And further discussion:

https://itch.io/t/5027154/is-it-possible-to-release-a-version-that-can-be-entere...

Phinxel's Phield Notes also describes a few ideas for working around these limitations:

https://ahmwwmaaa.neocities.org/decker/phield-notes#otherlangs

Unfortunately, Decker does not currently support Cyrillic.

Decker uses an internal byte encoding called DeckRoman, which supports a variety of diacritics and special letters for Latin-based alphabets, and provides a range of fonts with those characters.

It is possible to represent other alphabets using a custom font, but this does not automatically make Decker recognize natural text input in those alphabets.

For a more determined tinkerer, it is within the realm of possibility to fork Decker and modify the lookup tables it uses for converting between UTF-8 and DeckRoman, but this would require some programming experience.

This is a fantastically creative premise!

The .value attribute of a field is a rich text table. The alert[] function doesn't know anything about the .font property of a field you read rich text from; it could just as easily be a rich text table constructed on the fly by a script.

In a field, rich text runs with an empty string (the default) as their "font" cell are displayed using the font given by the field. The alert[] function always uses the "body" font for this purpose.

One way to get the behavior you appear to want is to write a function which patches all the default-font runs to use a specific font from a field. This in turn requires us to look up the font name from the deck:

on alert_from_field field do
 name:first extract key where value=field.font from deck.fonts
 alert[update font:name where !font from field.value]
end

While testing this example I identified and patched a bug in Native-Decker which can cause crashes when attempting to render a very large amount of text or text with a very large font in an alert[] dialog. The fix will be in the next release, but until then exercise some caution.

Nice video!

The ".." notation can simplify a few of these scripts a bit. Instead of writing

each c in selected c.draggable:0 end

You can use this shorthand:

selected..draggable:0

The same goes for this example:

each w in cx,co w.show:"none" end

Form a list, and then use ".." notation to write the attribute for each element in the list:

(cx,co)..show:"none"

It would be reasonably straightforward to sort the sound, prototype, and font lists alphabetically when they are displayed.

Is there a specific workflow that you're having trouble with?

I think this is a pretty nice release.

The phrase

deck:some_function[]

calls a function named "some_function" with no arguments and then assigns the result of calling that function to a variable named "deck". If the result of that function isn't the deck (it appears to be the number "1" in your example), you will then be calling the dialogizer functions with an invalid "deck" argument, which prevents them from working correctly.

If you have defined a function in a deck-level script:

on some_function argument do
 argument+2
end

You can invoke it from any card- or widget- level script by calling it like any other function:

some_function[3]

Widget scripts exist in a scope nested inside card scripts, which in turn exist in a scope nested inside the deck level script which in turn is nested inside a special magical scope with the definitions of all the default event handlers. Every variable or function definition in an outer scope is visible to the scopes nested within it, unless one of those scopes "shadows" it with a local definition with the same name.

Widgets, Cards, and the Deck all expose a ".event" attribute which is a function you can call to send a synthetic event to that deck-part. You can thus also invoke that deck-level function from elsewhere like so:

deck.event["some_function" 3]

This latter method works even if the target is not in a scope surrounding the caller, and it sets up automatic variable bindings ("me", the widgets on a card by name, etc) from the perspective of the target. This is most useful when you want to communicate across cards or simulate user interaction, like sending a button widget a "click" event which appears from that button's perspective like a user-generated click.

Does that help clarify?

I said I was sorry!

As a little secret, "ctrl + y" does the same thing as "ctrl + shift + z" (redo), since many people have that association in muscle-memory from other applications. For the same reason, ctrl and command always behave identically, so that folks used to mac shortcuts or windows/linux shortcuts can use what feels natural, and backspace/delete do the same thing outside the context of editing text: clearing selections, deleting widgets, etc.

Another set of undocumented shortcuts are that pressing the number keys 1, 2, or 3 while using a drawing tool will open the modals for choosing stroke pattern, fill pattern, or brush shape, respectively, if for some reason (screen real estate constraints?) you prefer those to the toolbars.

While Dialogizer is active, the view[] events requested by flagging a widget as .animated won't be produced naturally by Decker itself; all system events are "blocked" until the synchronous call to dd.say[] completes. Dialogizer sends a "synthetic" animate[] event to the current card every frame, and then you can in turn use that event to explicitly send a view[] event to other widgets that need to animate (or call the view[] function on the card, etc).

Perhaps your card script could have something like:

on animate do
 if alarm.animated
  alarm.event["view"]
 end
end

Or maybe you could do something a little more generic, querying the card to find any animated widgets and pump view[] events to them:

on animate do
 (extract value where value..animated from card.widgets)..event["view"]
end

This animate[] event is also how Puppeteer manages the animation for puppets. If you're using these libraries together, make sure your own animate[] event handler calls

pt.animate[deck]

at some point. You can also take a look at the card script for this part of the Dialogizer tutorial: http://beyondloom.com/decker/dialog.html#eventpumping

Does that help?

Swipes that move between cards generate navigate[] events, just like pressing the left or right arrow keys. You can override the default behavior by defining an empty handler for the navigate[] event on the card- or deck-level script, like so:

on navigate do
 
end

Does that solve your problem?

If you just want a static .gif, you can do something like

write[app.render[someCard]]

The app.render[] function takes a card or widget as an argument and produces a screenshot of it as an image interface, and the write[] function prompts the user to save a file; image interfaces are saved as .gif images. Presently, write[] does not allow the script to auto-populate a file name, but it will enforce a ".gif" suffix for whatever name the user chooses when writing out an image. Most of the complexity in the hybrid zine template is related to assembling all the page screenshots into the print layout with variable margins and page aspect ratio.

If you want an animated .gif that e.g. reflects animated patterns and potentially animated contraptions it gets a bit more complicated, but I could help work you through it if you describe your scenario in more detail. The WigglyKit deck includes one example of constructing a dictionary that write[] understands how to make an animated .gif with list of frames and frame delays.

One way to set an entire column of a table to a numeric value is to perform an assignment like this:

grid1.value.done:0

This expression produces a new, updated table. If we want the change to be reflected in the grid, we need to assign the result to the grid's .value attribute, so our whole "clear tasks" button script might be

on click do
 grid1.value:grid1.value.done:0
end

I'm the original author of WigglyPaint, and I am very aware of the numerous unethical websites and app publishers which are abusing my work. Please refer to the devlog posts above.

Here's a somewhat more compact approach that is roughly equivalent to your script:

dirs.face_north:compass_n
dirs.face_east :compass_e
dirs.face_west :compass_w
dirs.face_south:compass_s
 
each canvas name in dirs
 canvas.toggle["transparent" deck.cards.f.widgets[name].value]
end
 
compass_neutral.toggle["transparent" (!sum "transparent"=(range dirs)..show)]

If you want your contraption to update in response to a change in state elsewhere in the deck, you will need to somehow notify the contraption of that change. There are a number of ways to accomplish this. For example, if you wrapped the script you describe in an "on view do ... end" event handler defintion, you could also expose a way for code outside the contraption to fire that view event:

on get_update do
 view
end

And then fire it from the outside when appropriate:

mapArrowContraption.update[]

You could alternatively make a widget inside the contraption "animated" and use the view[] event that fires at 60hz to poll for changes to player direction on each frame, but this type of polling approach should be used sparingly; if lots of contraptions work this way it can add up to your game doing quite a bit of redundant work every frame!

You might also find this thread relevant.

Does any of that help point you in the right direction?

The art and sound design are fantastic; well done. I'm glad you had a positive experience working with Decker!

Variables in scripts within Decker only persist for the duration of an event handler. Static data that you just want to be able to reference from multiple places throughout a deck could be placed in the Deck-level script; go to File -> Properties... in the main menu, click "Script..." and then you can enter a declaration like

title_texts:"First","Second","Third","Fourth"

And then refer to the variable 'title_texts' from any card or widget script.

Again, this is for static data. For information that can change over time- like status flags or an inventory system in a game- you'll need to store the information in widgets somewhere on a card. This is a key idea in Decker: state and code live in physical, observable places within a deck. 

For example, you could also place this list within a field named 'texts' on a card named 'title' in JSON format:

["First","Second","Third","Fourth"]

And then access the contents of that field as a list from elsewhere in a script like

title.widgets.texts.data

For links in a rich text field to be clickable, you need to lock the field. In widget mode, select the field and choose "Widgets -> Locked" from the main menu.

If you haven't seen it already, I highly recommend reading through Phinxel's Phield Notes; it's very beginner-friendly and full of examples you can play with and borrow from for your own projects.

I might write the second example as

on click do
 t:"|" split "5|4|3|2|1|Boom!"
 me.text:t[(count t)%1+sum(me.text=t)*keys t]
end

Using the mod operator (%), taking advantage that for a list x, "keys x" is equivalent to "range count x", and that a failure to find a match for the current button text is harmlessly equivalent to matching the first option.

Let's say we have a table of directory information that looks like this, for consistency of the following examples:

files:insert dir name type with
 0 "snare-2.wav" ".wav"
 0 "foo.txt"     ".txt"
 0 "snare-3.wav" ".wav"
 0 "snare.wav"   ".wav"
end

Lil query clauses are executed right-to-left, like primitive operators within an expression. We can't define a result column and then reference it in clauses to its right, but we can chain queries, as I'll demonstrate shortly.

You're correct that @ is only useful for spreading unary operations down to the elements of a list, not spreading binary operators. Many binary operators in Lil such as "like", "in", "parse", and "format" generalize to listy left and right arguments in order make them easier to use within queries, but for "drop" this would be ambiguous, so there's no free lunch.

If I wanted to shave off the last four characters of each string in a list of strings I might use an explicit "each" loop:

each x in files.name -4 drop x end
# ("snare-2","foo","snare-3","snare")

In context (since loops are expressions),

select basename:each x in name -4 drop x end type from files
# +-----------+--------+
# | basename  | type   |
# +-----------+--------+
# | "snare-2" | ".wav" |
# | "foo"     | ".txt" |
# | "snare-3" | ".wav" |
# | "snare"   | ".wav" |
# +-----------+--------+

Or, I could factor the basename extraction into a unary helper function, and subsequently use "@"; this is a good approach if "data cleaning" operations like this are ever used in multiple places, and can also clarify intent by giving the operation an explicit name:

on baseof x do -4 drop x end
baseof @ files.name
# ("snare-2","foo","snare-3","snare")

In context,

select basename:baseof @ name type from files
# +-----------+--------+
# | basename  | type   |
# +-----------+--------+
# | "snare-2" | ".wav" |
# | "foo"     | ".txt" |
# | "snare-3" | ".wav" |
# | "snare"   | ".wav" |
# +-----------+--------+

If the filenames you're working with only contain a single "." you could also consider using "parse", which automatically spreads itself to a rightward list of strings and greedily stops consuming characters for the "%s" pattern when it encounters the next literal in the format string:

"%s." parse files.name
# ("snare-2","foo","snare-3","snare")
"%s." parse ("one.wav","two.wav","three.four.wav")
# ("one","two","three")

I'll optimistically use this last strategy going forward; your mileage may vary:

select basename:"%s." parse name type from files
# +-----------+--------+
# | basename  | type   |
# +-----------+--------+
# | "snare-2" | ".wav" |
# | "foo"     | ".txt" |
# | "snare-3" | ".wav" |
# | "snare"   | ".wav" |
# +-----------+--------+

I can then write another query against that table to sort and filter it. Note that by not specifying result columns I get all the columns of the input table:

select orderby basename asc where type=".wav" from select basename:"%s." parse name type from files
# +-----------+--------+
# | basename  | type   |
# +-----------+--------+
# | "snare"   | ".wav" |
# | "snare-2" | ".wav" |
# | "snare-3" | ".wav" |
# +-----------+--------+

If I was only interested in that first column as a list, I could use "extract" instead of "select". If you don't specify a result column for "extract", it defaults to peeling out the first column:

extract orderby basename asc where type=".wav" from select basename:"%s." parse name type from files
# ("snare","snare-2","snare-3")

If that's a daunting query to look at, I could name the subquery and write it like this instead:

bases:select basename:"%s." parse name type from files
extract orderby basename asc where type=".wav" from bases

You can use queries to filter, sort, and aggregate tables, lists, or dicts; the input will be "widened" into a table in the process as needed:

select from "One","Two","Three"
# +---------+
# | value   |
# +---------+
# | "One"   |
# | "Two"   |
# | "Three" |
# +---------+

Thus, if you want to sort a plain list you can query against it and then use "extract". Here's a different formulation to the original problem:

extract "%s." parse name where type=".wav" from files
# ("snare-2","snare-3","snare")
extract orderby value asc from extract "%s." parse name where type=".wav" from files
# ("snare","snare-2","snare-3")

Does that help clear things up?

This made noises which mildly annoyed the chicken on my arm. 10/10 would theremin again.