Skip to main content

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

Internet Janitor

844
Posts
55
Topics
7,419
Followers
23
Following
A member registered Aug 02, 2018 · View creator page →

Creator of

Recent community posts

I'm sure many of us have encountered Neko from time to time during our computing adventures. I spent a little time tinkering, and whipped up a version of this iconic pixelated companion for Decker:


If you want this little scamp in your decks, you can copy and paste it from here: http://beyondloom.com/decker/goofs/deko.html

This contraption uses Kenji Gotoh's Neko sprites, as is traditional. If any of you are feeling like some adventure, it's a simple matter to edit the frames stored in the internal "sprites" field to customize your Deko. Perhaps you could add new behaviors, more interactivity, or sounds? Feel free to share your variations or ask question!

(1 edit)

I've made some clarifications to that table to be more precise.

Primitive widgets will ONLY be sent automatic view[] events when they are set to be "animated" AND the card upon which they appear is the active card.

Before the .animated property existed, a cruder way of obtaining a 60hz event pump was to go[] to the current card without any transition effects, indirectly scheduling the view[] event for the card to be re-triggered. One advantage of this old approach- which still works fine- is that by making the "dispatch plumbing" within  a card explicit, it's very easy for blocking scripts to send their own "synthetic"view[] events to the current card and keep everything ticking along in the background. Dialogizer provides a synthetic animate[] event while its blocking dialogs are open that can be forwarded to view[] if desired.

There's nothing inherently wrong with long scripts. If you can describe what your code is doing I might have some suggestions for ways you can simplify and shorten them?

I don't think the load time is too bad at 20mb; it's noticeable, but compared to games made in Unity or Ren'Py loading in a few seconds is still pretty fast.

Looks like you built this game with Decker 1.62; in the 1.63 release I fixed a bottleneck that was slowing down deck loading a bit in native-decker, but it was most noticeable in debug builds. Still, upgrading might make your development a little bit snappier. I'm tinkering with some other performance improvements presently.

Using contraptions as reusable graphical assets is certainly one way to cut down on repeated background images; it looks like you're doing this pretty extensively already for puzzle elements. If any elements are one-offs you could also use canvas widgets as graphical elements you can show/hide, move around the card, or copy from place to place. I was a little surprised to see that you aren't using canvases at all in this deck!

The macFieldWindow contraption is designed for displaying "rich text", which can contain hyperlinks, inline images, and text spans with customized patterns or fonts, but cannot contain arbitrary embedded widgets, like a button. It would be possible to add buttons to the contraption by editing it, but I recommend sidestepping that route for now if possible.

I'm not clear on what you mean by "the font importer". In this tutorial post I describe steps one could follow for building a special-purpose tool for converting bitmaps of glyphs from a particular font collection into Decker fonts, but it makes a variety of simplifying assumptions: assuming glyphs are a particular size, that they appear in ASCII order, that the imported image is a black-on-transparent image that's already completely ready to use, and so on. The image you posted contains antialiased white-on-transparent glyphs; you'll have a much easier time importing and editing them as black-on-white or black-on-transparent, and they will need to be flattened into 1-bit color.

If you have a set of glyphs as an image, a somewhat more general approach to building a font from them would be to use the Font Editor that comes with Decker (examples/fonts.deck) and which I linked previously. The "sheet" mode of this tool is designed to break the card background image into uniform-sized glyphs based on the boundaries designated by the invisible button named "workzone". The grid overlay and "snap to grid" mode can make it easier to select and reposition glyphs within this tool so that Decker will understand which DeckRoman character they each correspond to. You'll need to make sure your new font's glyph size is set properly and that "workzone" is positioned to enclose all the glyphs before you can "Apply" your changes to the sheet, and if you don't want a monospaced font you'll probably need to make additional refinements to each glyph's sizing in the "glyph" tab.

1. Decker supports a limited set of unicode characters within text, and unrecognized characters are turned into character 255, "UNKNOWN", upon ingestion. You can, however, very easily customize how this character appears in a given font using the font editor. (The font editor is part of a larger deck All About Fonts that contains a variety of other notes you might find useful).

2. It isn't possible to use a different font within a field exclusively while text is highlighted, but you could certainly make intentionally illegible custom fonts that could still be copied to the clipboard as ordinary text. This would be mechanically a bit different, but might still preserve a similar idea. Perhaps there could be some affordance within the game for giving a player a "scratchpad" field they can paste into? You could alternatively build a contraption of some kind which encapsulates swapping out the font of a field when it is clicked, but it would be difficult to provide this kind of behavior on a granular level and still permit selection. In a locked field, links could be used to trigger arbitrary scripts when particular text fragments are clicked, but they do have a recognizable visual appearance: a dotted underline.

3. You cannot select text within a locked field, but there are a few subtle consequences of how field and text patterns work which might allow you to accomplish something similar to what you're describing:

  • A transparent field on top of a black background (with text in the default pattern 1) will appear to be invisible until it is selected, in which case it will appear white.
  • If you change the pattern of a span of text within a "rich text" field to white (rather than changing the default pattern for the entire field), it will appear to be invisible until it is selected, in which case it will appear white within a black selection highlight.

As a general note, some of the limitations of text and selections within Decker are a consequence of its approach to ensuring that fields are usable on keyboardless devices like tablets. The "touch input" mode (which can be manually enabled via the Decker menu) will interfere with some kinds of nasty text obfuscation tricks.

Yep. There really isn't much I can do about it.

It absolutely is not. I have only published WigglyPaint here (on itch.io) and on newgrounds.com. If you see people sharing links to pirate sites, please ask them to link to this page on itch.io instead. Pirate sites may harbor malware or ads, and are frequently outdated and encrusted with misleading slop.

I exchanged emails with SssAdmin and they agreed to change the name of their App.

You might find it useful to take a look at lildoc.lil in the Decker repository. It's a markdown processor and Lil syntax highlighter used for generating all of Decker's documentation.

Depends on what you want, I suppose?

Out of the box, with no extensions or scripting, Decker is already a perfectly serviceable presentation tool: make slides as cards, put text in fields, doodle on card backgrounds, flip through them with arrow keys.

The included PDF module offers a starting point for making slide-decks capable of exporting printable thumbnail versions of themselves.

If you wanted slides to have rigid "templates" and a centralized way of altering or applying these styles across a large deck, you could consider making a series of full-card-sized contraptions- a contraption for title cards, a contraption for bulleted lists with a heading, etc- and placing one contraption instance on each slide.

It would also certainly be possible to write a lilt script that "compiled" a textual syntax like Adelie into decks, or perhaps a module that interpreted them on the fly.

(1 edit)

I was not asked for my permission. I don't appreciate my free software being repackaged and sold for a profit, especially when it's re-published using the same name as my original software, using domain names that likewise imply that their products are somehow "official".

The dialog boxes controlled by dialogizer are canvas widgets located on a specific card. You shouldn't go[] to a different card while a dialog box is open. There are plenty of other ways to hide a transition if you're really insistent, like hiding and showing other widgets on the current card or even using the sledgehammer approach and replacing the background image of the current card with a screenshot of a different one:

card.image.paste[app.render[someOtherCard]]

(Note that this will destroy any previous background image on the current card!)

Like I said previously, remember that arithmetic precedence in Lil is right-to-left unless otherwise grouped by parentheses or brackets!

Without parentheses, Lil understands an expression like

p.hour+1>10

as equivalent to

p.hour+(1>10)

and not

(p.hour+1)>10

Parsing "sys.now" into time-parts like your example:

p:"%p" parse "%e" format sys.now

Gives us a dictionary like so:

{"year":2025,"month":12,"day":18,"hour":23,"minute":30,"second":45}

Note that this time is in GMT. The hour (and possibly minute) values might need to be adjusted to reflect a time zone offset to get local time. Otherwise, people in certain countries might find the game much harder than others!

At any rate, we'll want to structure our checks from most to least specific. First, the easter-egg time. We want both p.hour to be equal to 12 AND p.minute to be equal to 13:

(p.hour=12)&(p.minute=13)

Parentheses are important here: remember that arithmetic precedence in Lil is right-to-left unless otherwise grouped by parentheses or brackets.

Since seconds range between 0 and 59, 7 appears in the seconds place iff p.second modulo 10 is equal to 7:

(7=10%p.second)

For your hour ranges, you'll probably need ">" and "<"; there are many ways to express the same thing:

  (p.hour<10) | (p.hour>19)
! (p.hour >9) & (p.hour<20)
! p.hour in (10,11,12,13,14,15,16,17,18,19)

Bringing it all together, here's one possible approach for what you described:

if (p.hour=12)&(p.minute=13)
 # easter egg...
elseif (p.hour<10) | (p.hour>19) | (7=10%p.second)
 # allowed...
else
 # denied...
end

Does that make sense?

This game was not written using Decker, and is not appropriate for this jam.

(1 edit)

The "+" operator in Lil is exclusively for addition. It will coerce strings into numbers in a best-effort fashion, which is why you're getting zero:

"5"+3               # 8
" 2.3" + "1Hello"   # 3.3
0+"Hello"           # 0

The "fuse" operator is binary; it expects a left string argument to intercalate between elements of a right list argument:

":" fuse "A","B","C"   # "A:B:C"
"" fuse "A","B","C"    # "ABC"

Assigning a list to a field's ".text" attribute will implicitly concatenate the elements of a list together, as a convenience. The following are equivalent:

hellotext.text:"" fuse "Hi","There"
hellotext.text:        "Hi","There"

Cards are normally in scope by name, so you don't typically need a fully-qualified path to reach a widget on another card. The following will be equivalent from a card/widget level script:

deck.cards.A.widgets.name.text
           A.widgets.name.text

Does that help?

If you aren't already, I strongly recommend using The Listener to try expressions out piece-by-piece as you develop.

Many of the requirements in this manifesto seem strongly oriented toward a statically-compiled system with a complex backend, but Decker does check a number of boxes:

  • We don't need hot-reloads or live "previews"; everything in Decker is continuously interpreted on the fly. Likewise, we don't really have any concept of a "deployment" lifecycle apart from occasionally exporting .html builds of decks.
  • Decks are single self-contained files which are, nevertheless, line-oriented, so you can dump them in the moral equivalent of a dropbox and get "atomic" updates or use a traditional VCS like git to manage changes.
  • Using lilt to perform "headless" automated testing with the same scripting APIs as ordinary interactive Decker offers pretty handy facilities for testing. It's certainly possible to rig up test fixtures to run continuously as a deck is updated, but it isn't a default.

I wrote a new blog post outlining a number of useful design patterns and ways of working that fall out of the “stack-of-cards” metaphor shared by Decker and HyperCard:

http://beyondloom.com/blog/sketchpad.html

I'd love to hear your thoughts!

If alert[] is called with a single string, it has no meaningful result; the only thing a user can do is press "OK".

There is no reason to guard your call to go[] with a conditional.

on release do
 if rect.overlaps[me target]
  alert["pop up message"]
  go["Next"]
 end
end

Most of the time, the fields and other widgets on different cards of a deck contain entirely different data. Occasionally though, as in this question, you might have data like a game's score counter that you want to display on and modify from many cards. There's an elegant way to (ab)use Prototypes and Contraptions to create this kind of "singleton" widget state:

When the value of a field in a Contraption instance has never been modified, the value of the corresponding field in the Contraption's Prototype is inherited. By exposing a ".value" attribute for our Contraptions which read from and write to a field in the Prototype, rather than the Contraption instance, every instance of the Contraption will share a single value:

on get_value   do card.def.widgets.f.data   end
on set_value x do card.def.widgets.f.data:x end

This becomes slightly more complicated if we want the field to be unlocked and user-editable, since we can no longer rely on inheriting the prototype's value automatically. In this case, we will need to explicitly replicate changes back to the prototype and poll the value from the prototype when the contraption is updated by a view[] event:

on get_value   do card.def.widgets.f.data   end
on set_value x do card.def.widgets.f.data:x end
on change      do set_value[f.data]         end
on view        do f.data:get_value[]        end

As a complete example, here's a "scoreCounter" prototype which uses this mechanism:

%%WGT0{"w":[{"name":"sc","type":"contraption","size":[100,20],"pos":[302,95],"def":"scoreCounter","widgets":{"f":{}}}],"d":{"scoreCounter":{"name":"scoreCounter","size":[100,20],"margin":[0,0,0,0],"description":"an example of a \"singleton\" contraption which shares data across every instance.","script":"on get_value   do card.def.widgets.f.data   end\non set_value x do card.def.widgets.f.data:x end\non change      do set_value[f.data]         end\non view        do f.data:get_value[]        end\n","attributes":{"name":["value"],"label":["Value"],"type":["number"]},"widgets":{"f":{"type":"field","size":[100,20],"pos":[0,0],"style":"plain","align":"center","value":"0"}}}}}

Anywhere a scoreCounter appears within a deck, it will represent an identical view of the same underlying value. A pleasant side-effect of this approach is that scripts on cards can refer to any scoreCounter when they want to read or modify the game's score; there's no need to reach for a "source of truth" on some specific card.

I hope some of you find this to be a useful technique for your projects!

If you want to increment a local variable, like "t" in your example, you must use the assignment operator ":" (read aloud "becomes" or "gets") like so:

t:t+1

Local variables exist only within the lifespan of a single event.

Persistent state, like an inventory or a system for tracking a user's action, must reside in a widget somewhere. This has been discussed many times and places within this forum. I recommend starting with this thread: https://itch.io/t/2702720/how-to-have-interaction-between-cards

Confirmed; Keystores were not properly serializing raw lists. I've patched the issue in the source repo.

Dictionaries, tables, and strings do not exhibit this problem, so any can be used as a workaround.

By default, the Ply renderer doesn't have the deck, cards, or any local widgets in scope when it evaluates a Lil fragment, so it can't manipulate parts of the deck.

If you're using "twee.render[story passagename vars]" directly like in this demo, "vars" can be a dictionary containing arbitrary bindings, including references to the widgets of the current card.

The PlyPlayer contraption has similar functionality, but it is less flexible; you can read and write its .vars attribute, but this feature is only designed to store data that can be serialized in a field's .data attribute, not references to deck parts.

The easiest way to have Ply stories interact with a surrounding deck is to use the passage[] event that the PlyPlayer emits every time it visits a new passage. By keeping deck-influencing logic separate from the Ply story itself the story will still be entirely playable within the Twine editor. There's some relevant discussion in this thread.

Does that help at all?

Decker community · Created a new topic Public Transit v1.3

Fun little update for PublicTransit- I added 8 new transition effects:

  • RotateDown, RotateUp, RotateLeft, RotateRight: a variation on the "stretch" family with a different feel.
  • PageDown, PageUp, PageLeft, PageRight: simulated 'page flip' effect to make your deck feel more like a book or pamphlet.

Itch.io GIF uploads have been busted for quite a while, so no preview GIF; just follow the link above, give them a spin, and see if any stir some inspiration in you!

(1 edit)

Just to be clear, a field's .value attribute, which is represented as a rich text table, is the complete, underlying contents stored in the field. Fields have .text, .images, and .data attributes which can be read and written, but all three are alternate views/subsets of the same contents (as a plain-text string, as a list of inline images, and as encoded/decoded LOVE data, respectively), as scripting conveniences.

Buttons have a .value attribute as well, but it represents the boolean (1/0) value of checkboxes, and has no relationship to the .text attribute of the button, which controls the button's label.

Sliders have a .value attribute which corresponds to their numerical value, and Grids have a .value attribute which corresponds to the table data they contain.

From the reference manual's description of the Prototype Interface,

when a definition is updated, the nameposshowlockedanimatedvolatilefontpattern, and script attributes of Contraptions will be preserved, as well the valuescrollrowcol, and image content of the widgets they contain (as applicable) if they have been modified from their original values in the prototype, but everything else will be regenerated from the definition. The state of contraptions is kept, and the behavior and appearance is changed.

Any other state that you wish to survive prototype modification should be stashed in internal widgets; I recommend refreshing these settings (alongside applying settings from the enclosing contraption instance like .font and .locked, where appropriate) in the view[] event handler in the prototype script.

If you want a button to be clickable but have absolutely no visual effect when clicked, set the style to "Invisible" and then additionally apply "Show Transparent" (found in the Widgets menu).

You could write a script to record the position of all the canvases automatically (rather than recording every coordinate by hand), stash those positions in a field somewhere, and then have another script re-apply them when appropriate.

Let's say you have widgets something like this:

The "remember" script looks like:

on click do
 p:select key value..pos where value..type="canvas" from card.widgets
 positions.data:raze p
end

And the "restore" script looks like:

on click do
 each pos name in positions.data
  card.widgets[name].pos:pos
 end
end

In practice, this field and buttons will probably be hidden (set to "show none"). If you ever want to record the current state of the canvases without un-hiding the buttons you can simulate a click via the listener:

remember.event.click

Does that make sense?

"unless" does not short-circuit; No operators in Lil do.

In this specific case I would probably write

if !deck.cards.potato deck.add["card" "potato"] end

and then subsequently use "deck.cards.potato". Even if you then also assign it to a variable, the end result is slightly shorter.

An offline copy of Decker's documentation is included with the Native-Decker download inside the "Docs" folder.

You can also access the latest docs through the links in the Help menu, or the links on the main decker website.

State in a deck, like a score counter, needs to live in a widget, on some card.

The straightforward way to have a global score counter would be to make a field (or slider) widget somewhere on a specific card; let's call the card "inventory" and the field "score". Scripts on another card can then refer to that field by one of its "fully-qualified" names:

deck.cards.inventory.widgets.score
inventory.widgets.score

For example, to add 100 points to the score, a button script might be

on click do
 inventory.widgets.score.data:100+inventory.widgets.score.data
end

Or, using a local variable to reduce repetition,

on click do
 s:inventory.widgets.score
 s.data:100+s.data
end

In fact, if you're referring to this widget constantly throughout your deck, you might consider setting up an alias in the deck-level script:

scoreCounter:deck.cards.inventory.widgets.score

After which point you could just use the name "scoreCounter" in any script, on any card.

Each card could have their own "score" field (perhaps volatile) that is drawn from the central source-of-truth score counter and updated when the card gets a view[] event:

on view do
 score.data:scoreCounter.data
end

(Note that when you change the score counter you will generally also want to call view[] to perform this update.)

Does that make sense?

If you're interested in making this even tidier- at the cost of some additional up-front complexity- I could describe how to use a contraption to build a "game HUD" that behaves like a single object shared across many cards.

For the record, I'd strongly recommend updating Decker.

v1.54 is over 8 months old; we're up to v1.61.

There are many reasons a card can be sent a view[] event, including exiting editing tools, (re)opening a deck, or closing a dialog box. It should not be understood as an event that is fired when a card is visited at all, but rather a request to update the contents of a card. I do not recommend attempting to use timing as a method to disambiguate the origin of a view[] event; such methods are inherently brittle and error-prone.

To address may's problem, I would recommend altering the script on the originating card to send a new event- one not generated by Decker itself- to the destination card after the transition. Let's call the new event "visited":

on click do
 go["Next" "SlideDown"]
 deck.card.event["visited"]
end

(Note that after go[] has completed, "deck.card" will refer to the card we just navigated to.)

The destination card can then fire the dialog in response to this "synthetic" event:

on visited do
 dd.open[deck a]
 dd.say["Kindling Village"]
 dd.close[]
end

Update: the jam page for Dec(k)-Month The Third is up!

Reviewing vactur, it seems like some of these operations reimplement Lil primitives or brief idioms. For example,

vac.constant: on _ n c do
  on _ do c end @ (range n) end
end

Replicates n copies of a constant c. This could be implemented in terms of the primitive "take"; possibly with an enclosed version of c if it may be something other than an atom:

3 take 5
# (5,5,5)
3 take list 11,22
# ((11,22),(11,22),(11,22))

Likewise,

vac.isin: on _ v s do
  t: vac.constant[(count v) 0]
  each x in s
    t: t | (v = x)
  end
  t
end

The core of this loop could be expressed as

max each x in s v = x end

But the whole operation appears to be equivalent to the primitive "in":

(2,3,5) in (4,11,17,5,3)
# (0,1,1)

The "partitioning[]" function is implemented in terms of "where" (a primitive in some APL-family languages, but not Lil); 

vac.where: on _ m do
  extract index where value from m
end
vac.partitioning: on _ m do
  vac.where[m],vac.where[!m]
end

But we can do the equivalent in a single query by using a "by" clause to group rows:

extract index by value from m

Not only does this reduce overhead, it makes the formulation more general:

vac.partitioning[1,0,1,1,0,1]
# (0,2,3,5,1,4)
extract index by value from 1,0,1,1,0,1
# (0,2,3,5,1,4)
extract index by value from "ABAABA"
# (0,2,3,5,1,4)

Please don't take this as discouraging your efforts; just pointing out some alternatives.

There are quite a few similar pirate websites spawned from the overwhelming popularity of WigglyPaint. None of them are by me, or created with my permission. I have no ability to update the version of WigglyPaint they provide, I cannot vouch for those sites being free of ads or malware, and many of them don't link to my itch.io pages or my personal website (beyondloom.com), so they actively prevent the discovery of my other projects.

I also have never published Android or iOS "apps" for WigglyPaint or Decker; anything you find to the contrary is illegitimate.

I would appreciate it if people refrained from linking to these pirate sites and apps. I don't use social media, so it is impossible for me to even consider correcting the countless misinformed users who share these URLs with one another. It is very frustrating to offer my work for free and still have it stolen.

When the arrow keys are pressed, a navigate[] event is sent to the current card with an argument of "up", "down", "left", or "right". If you wanted to make these events move a canvas, you might have an event handler in the card-level script something like

on navigate dir do
 deltas:("up","down","left","right") dict ((list 0,-1),(list 0,1),(list -1,0),(list 1,0))
 myCanvas.pos:myCanvas.pos+deltas[dir]*32
end

If you wanted that canvas to trigger "exits" when it matches certain positions, you could mark them with buttons (perhaps set to "Show None") and then send them a "click" event on an overlap; the buttons could in turn go[] to other cards, trigger alert boxes, etc.

on navigate dir do
 deltas:("up","down","left","right") dict ((list 0,-1),(list 0,1),(list -1,0),(list 1,0))
 myBug.pos:myBug.pos+deltas[dir]*32
 each button in extract value where value..type="button" from card.widgets
  if button.pos~myBug.pos button.event["click"] end
 end
end


For a fully-worked example of a game that uses arrow keys for tile-based movement, you should take a look at Decker Sokoban.