Indie game storeFree gamesFun gamesHorror games
Game developmentAssetsComics
SalesBundles
Jobs
Tags

Internet Janitor

477
Posts
33
Topics
1,152
Followers
12
Following
A member registered Aug 02, 2018 · View creator page →

Creator of

Recent community posts

(2 edits)

Not quite; that will also fire whenever the contraption is first displayed.

You probably want to detect the moment the last decrement occurs.

I made a simple version of this idea by creating a Contraption that consists of an invisible button layered on top of a "bar"-styled slider. When the button is clicked, it sets the slider to the maximum possible value and makes it "animated", so that it will tick down naturally. While the animation is in progress, the button is locked:

A paste-able version of this contraption is here:

%%WGT0{"w":[{"name":"cooldown1","type":"contraption","size":[100,25],"pos":[206,201],"def":"cooldown","widgets":{"s":{},"b":{},"l":{}}}],"d":{"cooldown":{"name":"cooldown","size":[100,25],"resizable":1,"margin":[5,5,5,5],"description":"a button which requires a configurable cooldown between clicks.","script":"on get_text   do l.text end\non set_text x do s.format:l.text:x end\non get_cooldown   do last s.interval end\non set_cooldown x do s.interval:0,1|x end\n\non view do\n s.font:card.font\n s.format:l.text\n if s.value\n  s.animated:1\n  s.value:s.value-1\n  b.locked:1\n else\n  s.animated:0\n  b.locked:0\n end\nend\n\non click do\n s.value:last s.interval\n card.event[\"click\"]\n view[]\nend","template":"on click do\n \nend","attributes":{"name":["text","cooldown"],"label":["Text","Cooldown"],"type":["string","number"]},"widgets":{"s":{"type":"slider","size":[100,25],"pos":[0,0],"locked":1,"interval":[0,100],"format":"","style":"bar"},"b":{"type":"button","size":[100,25],"pos":[0,0],"style":"invisible"},"l":{"type":"field","size":[100,20],"pos":[111,0],"show":"none"}}}}}

How's that?

As written, this example doesn't include a "disabled" mode for the cooldown buttons, which might be useful for some applications. It is resizable, and respects the .font attribute.  You could make a much fancier-looking version of this idea- possibly with color, etc- by using a canvas as the contraption's background and drawing the progress bar from scratch.

Glad you were able to resolve your problem!

Be aware that importing modules into your decks adds a copy of the module to your deck; occasionally Decker release notes may point out updates to Dialogizer, Puppeteer, or other modules that are included with Decker. If you want to be certain you're using the latest version of a module, you can use the Resources dialog to remove dd/pt/etc and then re-import them.

Carefully check the spelling and capitalization of the positions you're specifying against the diagram of stage directions, and that the arguments to Puppeteer's commands are in the correct order. For example, !move takes a POS as its second argument and does not alter EMOTE, while !anim does not take a POS argument or an EMOTE argument.

There are a number of working examples in the documentation; you could try changing them bit by bit to approach your use-case. If you're getting yourself confused by a complicated project, try making a new deck from scratch and assembling the smallest possible example that demonstrates the problem you observe.

If you want to issue multiple commands with one call to pt.command[], the second argument will need to be a list of strings. Placing a comma between strings joins them together into a list, even if they're spread across multiple lines:

pt.command[deck
 "!show NAME1 EMOTE customPOS1",
 "!show NAME2 EMOTE customPOS2"
]

The above is equivalent to:

pt.command[deck ("!show NAME1 EMOTE customPOS1","!show NAME2 EMOTE customPOS2")]

I think I see the problem. You've defined an "on command ..." function for the card, and defined some custom commands. This function will capture any "command" events sent to the card, which means the deck-level function that normally passes unhandled commands along to puppeteer:

on command x do pt.command[deck x] end

Doesn't get a chance to do its work!

You'll need to add a final "else" to the command handler on your card and pass unhandled events on to Puppeteer. You can do this directly:

on command x do
 if x~"mycommand1"
  #...
 elseif x~"mycommand2"
  #...
 else
  pt.command[deck x]
 end
end

Or you can use "send" to bubble the event up to the deck-level definition:

on command x do
 if x~"mycommand1"
  #...
 elseif x~"mycommand2"
  #...
 else
  send command[x]
 end
end

If you've added more "global" custom commands to the deck-level handler- in addition to those defined on the card- you'll want to use "send". Make sense?

Currently, copying text from a field always copies the plain-text equivalent of the selection. I'll give some thought to an alternative for rich text; perhaps an alternate "Copy Rich Text" menu item/shortcut.

Constraining an object within a concave shape while dragging is a rather difficult problem. You can easily construct examples that have no single "closest valid location"


In the general case I think this would probably require some kind of iterative solver.

If all you care about is the destination, you could use several calls to rect.overlaps[] to see if the player object overlaps any obstacles and "snap back" as demonstrated in the examples.

(1 edit)

Aha, so you're trying to do something like the Shadowgate inventory system?

There are a few ways to approach this. One way would be to use card.copy[] and card.paste[] to duplicate several widgets from one card to another, and then card.remove[] to get rid of the originals.

We'd need this to happen whenever we change cards, from anywhere in the deck; the easiest way to make this happen is to override the "go[]" function in a deck-level script. We'll want to special-case navigating to the card we're already on, since this idiom is used for some kinds of animation.

It will also be convenient if we have an easy way of identifying the widgets on a card that are items. We could search the current card for any canvas that is draggable, but a naming convention might be less error-prone: I'll name each item-canvas with an "item_" prefix for this example. For card transitions to look correct, we need to add the copied widgets to the destination before the transition starts, and to remove the originals after the transition completes.

We might define a deck script something like:

on go dest trans delay do
 if !"card"~typeof dest dest:deck.cards[dest] end
 if dest~deck.card
  send go[dest]
 else
  s:deck.card
  i:extract value where value..name like "item_*" from s.widgets
  c:s.copy[i]
  dest.paste[c]
  send go[dest trans (30 unless delay)]
  each item in i s.remove[item] end
 end
end

In action:


This solution comes with some important caveats. For one thing, the deck-level definition won't apply to calls to go[] fired from within modules or contraptions. You will also probably want to override the default navigate[] event handler. This solution relies on the simplifying assumption that the first argument to go[] is a card name or card interface; if you want to be able to navigate by index or with special names like "Next" or "First" or open hyperlinks you'll need to do some more work.

Does that help?

(1 edit)

in your "on click..." script, you're calling generate[] before you choose a new random value for enum1.

oh, it's just froppy

If you have an enum contraption named "enum1" and a button intended for randomizing it, you could give the button a script something like the following:

on click do
 enum1.value:random["\n" split enum1.options]
end

reading the "options" attribute of an enum contraption returns a newline-delimited string of options which can be cracked into a list of options with "split". Given a list, the random[] function will pick a random element, and the enum contraption also permits having its value written via the "value" attribute (automatically constraining writes to within the valid set of options).

If you're doing this sort of thing in many places, you could modify the enum prototype to expose a method for randomizing it, adding a function something like the following to the prototype script:

on get_randomize do
 on randomize do
  set_value[random["\n" split get_options[]]]
 end
end

And then simplify the button script above to

on click do
 enum1.randomize[]
end

Make sense?

In the context of query clauses, column names refer to the entire column as a list. The "where" clause expects an expression which yields a list of boolean values. Arithmetic operators like =, <, and + conform over list-list and list-scalar arguments.

Depending on how a predicate is written, it might naturally generalize to operating on lists for the same reason. For example: 

on iseven x do 0=2%x end
iseven[5]
# 0
iseven[11,22,33]
# (0,1,0)
extract value where iseven[value] from 11,24,3,8
# (24,8)

If a predicate is only designed to operate on a single scalar value at a time, you can use the "@" operator to apply it to each element of a column, like so:

on seconde x do x[1]="e" end
extract value where seconde@value from "Lemon","Lime","Soda","Demon"
# ("Lemon","Demon")

This shorthand is semantically equivalent to

extract value where each v in value seconde[v] end from "Lemon","Lime","Soda","Demon"

(And of course in this particular case the "like" operator would be simpler:)

extract value where value like ".e*" from "Lemon","Lime","Soda","Demon"

Does that clear things up?

Currently there aren't any configuration options exposed for size and position apart from the padding defined when you configure a custom border.

What did you have in mind?

excellent avatar btw

story of my life

itch.io will (probably) never betray you as steam has

you'll be unstoppable now

Decker community · Created a new topic Windows 95 Fonts

This morning I spent some time wrangling with .FNT font archives and managed to convert several of the bitmap fonts originally used in Windows 95 to the Decker font format:

Grab the deck here for 49 delightful new fonts!

I'm not really a fan of the way Discord traps information inside a walled garden. When someone asks a question in this forum, all kinds of lurkers are able to read and benefit from the answers, whereas Discord demands that users sign up simply to find out what information exists.

Anyone is absolutely welcome to post here about their own projects and WIPs. If there's anything I can do to make this forum more inviting, I'm open to feedback.

Decker's default window size is 512x342 pixels. If you draw pixel art in that size (or smaller) as black-on-white or black-on-transparent in some other tool it's fairly easy to import into Decker by dragging and dropping the file (bmp, png, gif, or jpeg) onto the Decker window or with the File -> Import Image... menu item. Decker automatically scales imported images down to a bit smaller than the full card so that resize handles are easily accessible, but if your image is already the right dimensions you can correct this while the selection is still active with Edit -> Resize to Original.

If you drag and drop or import a color or grayscale image, Decker will make a best-effort attempt at applying Atkinson Dithering to turn it into a 1-bit image. While the selection is still active you can resize the image and use j/k on the keyboard to adjust contrast, which may help make images more legible.

The process is somewhat more involved, but it is also possible to import 16-color images; quite a bit of discussion about doing so is available in this thread.

Decker can't display animated GIFs on a card out of the box, but there are some Contraptions that can add this capability, as well as the scripting APIs for decoding/encoding GIF framesto build variations on these examples.

Finally, it might be useful to know that with native-decker it's possible to activate Tracing Mode and use the built-in drawing tools to recreate anything you like from reference images. This can be a handy way to turn photos into something that blends in nicely with your own doodles.

Is that the kind of information you're looking for?

Lilt uses a fixed seed by default; this is a design decision inherited from K.

One simple way to randomize it would be something like

sys.seed:sys.ms

Note that Decker automatically randomizes the RNG seed at startup.

at least you can draw! :)

Are you already familiar with some existing programming language?

Do you want to write scripts for Decker?

If both of the above are true, dear reader, you might stand to benefit from a new guide I've written,

Learn Lil in 10 Minutes

 It's concise, reasonably comprehensive, and focuses on code examples over lengthy exposition. Give it a try, and see if it works for you!

Many pen-and-paper games, including RPGs, journaling games, and card games make use of random numbers and lookup tables to control outcomes. I thought it might be useful to detail the construction of a table-driven character generator to demonstrate how Decker can be used to make semi-automated play aids for such games. I'll make a random chicken generator, but I encourage you to take a stab at creating your own variation with a theme that appeals to you creatively.

This tutorial is written with Decker v1.41 in mind, so if you're on an older release make sure you upgrade before following along.

Names

The simplest way of using random lookup tables is choosing a single item from a list. We'll start by creating three widgets:

  • A grid which will contain our list of chicken names, called "names". This grid will contain a single column called "value". For the purposes of this example I have borrowed a list of 100 proposed chicken names found here. Note how I fill the grid with data by choosing "edit as CSV" (Comma-Separated Value). The first line of the CSV specifies the column name, and all subsequent lines are data.
  • A field which will contain one randomly chosen chicken name, called "name".
  • A button which will tell decker to choose a chicken name, titled "Generate".

Fill in the script for the button like so:

on click do
 name.text: random[names.value].value
end

Step by step,


Try clicking "Generate" a few times!


We could make a variety of additional random character traits by following the same pattern: make another grid with a column of options, fill it in appropriately, make a field to contain a result, and write a button script to make the selection. You can modify the contents of the grid to change the set of available options, and if you want certain options to be more common you can put your thumb on the scale by making multiple copies of some rows. If you don't want the user to see all the options up-front, you could hide the grid (Widgets -> Show None). You could also choose to generate multiple attributes from the same button script or divide them up to permit users to re-roll each individually.

Note that if you're editing grids in CSV mode, it is essential that any data rows containing commas be enclosed in double-quotes. If you prefer, you can compose large tables in a spreadsheet program like Excel, export as CSV, and either copy and paste them into the grid properties panel or select the grid in Interact mode and choose File -> Import Table...

Multiple Choice

Another variation on random selection that may be useful is selecting two or more values from a table, and ensuring for the sake of variety that they're distinct. Let's give our chicken two favorite foods from a table of chicken delicacies. First, an extra field and grid (along with some extra fields for labels):


Then I'll add my secret sauce to the "Generate" button's script:

on click do
 name.text: random[names.value].value
 food.text: "\n" fuse random[foods.value -2]..value
end

The main trick here is that by specifying a negative number as the second argument to the random[] function we will get random items without replacement; that is, distinct items. -3 would generate three distinct items, and so on, up to the size of the table. Since random[] will now return a list of dictionaries (each a row of the table), we can use the ".." syntax to obtain the "value" field from each dictionary, and intercalate newlines ("\n") between each food with "fuse".

A different (but equivalent) way of solving the problem would be to first peel the "value" column out of the grid's table and then choose two random items from the resulting list:

food.text: "\n" fuse random[foods.value.value -2]

Descriptions

In some cases, you may wish to glue together several choices to produce a coherent prose description. For example, we might wish to have a paragraph like:

She is a small pullet with red feathers.

The pronoun "She" needs to be selected along with "pullet" (a juvenile female hen) to make sense, so we'll have a grid with two columns, "type" and "pronoun", that are correlated appropriately. In CSV:

type,pronoun
chick,She
chick,He
pullet,She
cockerel,He
hen,She
rooster,He

Our widgets:

Since sizes and feather colors can be short lists, we'll demonstrate two different ways their options can be defined directly within our script.

Here's the new button script in its entirety. Generating the description is much more complicated than the previous two lines, but it contains many things we've seen before:

colors:insert value with
  "red"
  "brown"
  "white"
  "gray"
  "black"
end
on click do
 name.text: random[names.value].value
 food.text: "\n" fuse random[foods.value.value -2]
 d:random[types.value]
 d.size:random["bantam","small","medium-sized","large","colossal"]
 d.color:random[colors].value
 des.text:"%[pronoun]s is a %[size]s %[type]s with %[color]s feathers." format d
end

Step by step,


And the final chicken generator in action:


I hope this writeup starts some ideas fermenting in the minds of Decker users. Feel free to ask followup questions, and if you build your own thing-generator I'd love to see it!

(1 edit)

In general, for string manipulation the main tools Lil provides are:

  • drop, take, split, and parse for cutting strings apart into smaller pieces
  • fuse and format for gluing pieces together to make strings
  • like, in, <, >, and = for comparing and searching strings

...and a few of the more complicated utility functions functions in rtext do also apply to plain strings.

The Lil "comma" operator forms a list by combining the elements of its left and right arguments.

If you use this operator to combine a table and a number, the table will be coerced to its list interpretation (a list of the rows of the table, each a dictionary), and then combined with the number 1 (whose only element is itself), forming a list of several dictionaries and the number 1. Applied to such a list, random[] will occasionally choose the 1.

When you call a Lil function with multiple arguments, commas should not be placed between arguments. You probably meant

random[temp 1]

 Instead of

random[temp,1]

Furthermore, note that if you specify 1 as a second argument to random[] you will get a length-1 list as a result, whereas if you call random[] with only a single argument you will get a single value.

In preparing this post I have also observed that there is some inconsistency between native-decker and web-decker with respect to applying random[] to table values, which may have compounded the confusion; I'll have this fixed in the v1.41 release tomorrow. In the meantime the alternative is to explicitly crack the table into rows before making a random selection, like so:

random[(rows temp)]

Starting from the two fields you describe:

You can obtain the text of either field through its ".text" attribute. The "split" operator breaks a string on the right at instances of the string on the left and produces a list. Splitting one string on spaces gets us part of the way to what you're asking for. We can then use the comma operator to join a pair of lists.

" " split field1.text

The best way to experiment with this sort of thing is to use The Listener. Ask a "question", get an answer:

Another approach for gathering space-separated words from several fields would be to use an "each" loop:

Does that help point you in the right direction?

I don't see any reasonable way of accommodating displays with extreme aspect ratios automatically. Stretching Decker's display both to a non-integer upscale and a different aspect ratio would be truly hideous.

(1 edit)

Have you tried "Fullscreen" mode (under the Decker menu)?

(Note that this feature specifically does not work on iPhones, due to an intentional omission of API support in iOS Safari.)

FYI i've patched the problem with 0-width columns in grids; that should now offer a fairly flexible option for visually suppressing columns without physically removing them from the underlying table. You can try it out now at the bleeding-edge source revision, and the fix will be incorporated into the v1.41 release; probably next week.

You can adjust the widths of the columns of a grid to hide trailing columns. In "Interact" mode, a small draggable handle appears between column headers:


You can reset the columns to their default uniform spacing with the "Reset Widths" button in grid properties.

Manually resizing column widths enforces a minimum size. You can set column widths to 0 programmatically via "grid.widths", but a 0-width column looks a bit odd; I'll make a note to fix that in the next release.

Glad you like it. Thanks for your hard work, Jag!

excellent airbrushed floof

(1 edit)

To obtain a list of the cards with a particular group number you could use a query something like

c:extract value where value..widgets.group.text=2 from deck.cards

(Note that if a 'group' field doesn't exist it will behave the same as a card in group "0"; you probably want to count from 1 for your groups)

Given that list, you can pick a random item and navigate to some card like so:

go[random[c]]

Or, all at once,

go[random[extract value where value..widgets.group.text=2 from deck.cards]]

Does that make sense?

You're looking for "locking" a deck, which has previously been discussed here

Made a few minor enhancements to Dialogizer v1.2 that provide additional styling parameters  for making your dialog boxes a little bit "juicier":

  • The "speed" parameter, if set to a nonzero number, will cause Dialogizer to animate in the text in dialogs a word at a time. If the user clicks while text is animating, it will immediately finish.

http://beyondloom.com/decker/dialog.html#textspeed

  • You can now set up sound effects that will be played when dialogs open, advance a page, confirm a choice, or continuously while text is animated:

http://beyondloom.com/decker/dialog.html#soundeffects

The straightforward way is to use the "list" operator, which wraps its argument in a length-1 list:

 (list "foo") dict (list "bar") 
{"foo":"bar"}

A fancier, more concise way (provided your keys are strings) is to perform an amending assignment to an empty list, like so:

 ().foo:"bar"
{"foo":"bar"}
(1 edit)

Click the big "EXPORT" button on the bottom left.

The absence of a direct equivalent to SQL's "like" operator has plagued me for some time, so in Lil v1.40 I decided to introduce a first-class equivalent. Using this operator, we can now perform the same type of case-insensitive search (as well as quite a few handy variations) without needing a helper function:

ex:insert Date Entry with
     20210412 "Once Upon A Time"
     20220709 "There once were"
     20230101 "stars upon thars"
end
select where ((list "%l")format Entry) like "*upon*" from ex
# +----------+--------------------+
# | Date     | Entry              |
# +----------+--------------------+
# | 20210412 | "Once Upon A Time" |
# | 20230101 | "stars upon thars" |
# +----------+--------------------+

If you prefer the previous way, it will be necessary to rename the "like[]" helper function, since "like" is now a reserved word. Sorry for any inconvenience!

FYI, this issue was ultimately traced to a regression in SDL2 itself; the problem was patched there and should be corrected as of the next SDL2 release.