Skip to content

CottonScript

UnbelievableFlavour edited this page Jun 8, 2022 · 7 revisions

CottonScript is a friendly scripting library that allows you to add Pulp-like interactivity to your Lua games. Its syntax is terse but powerful. A good rule of thumb when writing CottonScript is do less, less often.

General

Comments

You can comment your Lua with two dashes:

-- this is a comment

Comments can appear on their own line or after a valid expression.

Handling events

Event handlers are written like so:

function eventName()
    -- something
end

In addition to a handful of built-in events, you can also declare your own custom functions like so:

function self:aCustomFunctionName()
    -- something
end

and trigger them from the current entity with:

self:eventName()

or for all tiles that implement a particular event with:

-- emitting is currently not supported yet

These are the built-in events:

  • load: called once on the game, each room, and each tile when all assets are first loaded
  • start: called once on the game after all rooms and tiles have handled their load event
  • enter: called on the game, the current room, and each tile in the current room every time the Player enters a new room
  • exit: called on the game, the current room, and each tile in the current room every time the Player exits the current room
  • finish: called once on the game when the game is completed
  • loop : called on the game, 30 frames per second, before anything else is done that frame (except when a say text box or menu is on screen)
  • update: called on the Player every time they move or interact with something
  • bump: called on the Player every time they bump into a solid world tile
  • confirmPressed: called on the Player when the A button is pressed
  • confirmReleased: called on the Player when the A button is released
  • cancelPressed: called on the Player when the B button is pressed
  • cancelReleased: called on the Player when the B button is released
  • crank: called on the Player when the crank is turned
  • dock: called on the Player when the crank is docked
  • undock: called on the Player when the crank is undocked
  • draw: called on the Player 30 frames per second, before drawing
  • interact: called on an entity when the Player bumps into or acts upon it
  • collect: called on an entity when the Player steps onto or acts upon it
  • change: called on the game when the cursor moves to a different option in a menu or ask menu and when the menu first appears
  • select: called on the game when the player selects an option in a menu or ask menu
  • dismiss: called on the game when the player dismisses a menu submenu
  • invalid: called on the game when the player attempts to back out of an menu or ask menu

Variables

All uninitialized variables have a default value of 0. Variables can hold a number or a string.

Variables are initialized like so:

local varName = nil

And assigned like so:

varName = 0

Strings can be wrapped in double or single quotes. But for consistency I recommend using only one of the two:

varName = "some string"

or

varName = 'some string'

Math

Lua supports simple arithmetic operations.

Variables can be incremented or decremented like so:

varName = 0
varName = varName + 1 -- now equals 1
varName = varName - 1 -- equals 0 again

You can add to or subtract from a variable like so:

varName = 0
varName = varName + 4 -- now equals 4
varName = varName - 3 -- now equals 1

You can also multiply or divide a variable like so:

varName = 0
varName = varName + 1 * 6 -- now equals 6
varName = varName / 3 -- now equals 2

Compound expressions are also supported like so:

varName = 0
varName = varName + 1 * 2 -- now equals 2
varName = varName - 1 + 1 - 2 -- equals 0 again

Control Flow

Conditionals

Conditionals in PulpScript take the following form:

if varName == 0 then
    -- handle 0
elseif varName == 1 then
    -- handle 1
else
    -- handle all other values
end

Both the elseif [condition] then and else are optional. The following boolean comparisons are available:

  • ==: equal to
  • ~=: not equal to
  • >: greater than
  • <: less than
  • >=: greater than or equal to
  • <=: less than or equal to

String comparisons are case-sensitive. This also applies to functions, room, and tile names when used as arguments for functions.

Compound expressions are supported like so:

if varName1 == 0 and varName2 == 0 then
    -- do something when varName1 and varName2 are both 0
end

Loops

CottonScript also includes a while loop which can be thought of as a repeating if:

while varName==0 do
    -- repeat until varName is set to another value
end

Returning

User-defined functions and event handlers can return values. They can be exited early with return like so.

while varName==0 do
    ticks = ticks + 1
    if ticks<interval then
        return -- return from the "while" loop 
    end

    ticks = ticks - interval

    -- do something at a regular interval
end

Continue

You can also skip only 1 cycle of the while loop and keep going instead of completely returning from the loop like so:

local redFound = 0

while varName==0 do
    if color == "blue" then
        goto continue -- skips loop while the color is blue
    end

    redFound = redFound + 1

    ::continue::
end

Persistent storage

Variables can be persisted across launches using the store and restore functions. A game’s persistent storage file can be found in the following locations, for the Playdate Simulator: <SDKRoot>/Disk/Data/com.<author>.<name>/store.json

and on the hardware: /Disk/Data/com.<author>.<name>/store.json

event

event was a variable used in Pulp. And although this variable is really helpful in Pulp, it's not so helpful in Cotton. If you still find a need for this variable, please create a ticket so we will look for an alternative.

config

Since the config variables have grown a bit , we moved this section to a separate page.

Config Variables

datetime

datetime()

The datetime function has the following members reflecting the current time:

year: 1900-10,000 month: 1-12 day: 1-31 weekday: 0-6 hour: 0-23 minute: 0-59 second: 0-59 millisecond: 0-600

and

timestamp()

return seconds since midnight (hour 0), January 1 2000 UTC

String formatting

You can include variables in a string by adding .. between the string and the variable:

log("count is now set to " .. count)

Functions

log

log("message")

Logs message to the console. You can include variable values in log.

dump

dump()

Logs the current values contained in the config, and persistent storage.

say

say("message", at())

and

say("message", at(), function()
    -- do something after the text box is dismissed
end)

Displays message in a text box.

The text box can optionally be manually positioned and sized:

say("message", at( 10, 10 ))

or

say("message", at( 10, 10, 200, 100 ))

ask

ask("stringValue", at(), {
    {
      name = "optionOne",
      callback = function()
        -- do one thing
      end
    },
    {
      name = "optionTwo",
      callback = function()
        -- do another
      end
    }
})

Displays stringValue in a text box. The player can then select from the provided options. The options are presented as a vertical list in another box overlaying the bottom right corner of the question box. ask requires at least one option, six or more options will be paginated. Its text box can optionally be manually positioned and sized:

ask("stringValue", at( x, y ), ...

or

ask("stringValue", at( x, y, w, h ), ...

menu

menu(at( 10, 10, 200, 100 ), {
    {
      name = "optionOne",
      callback = (function()
        -- do one thing
      end)
    },
    {
      name = "optionTwo",
      callback = function()
        -- do another
      end
    }
})

Presents a paginated menu with one or more options at x,y. w and h are optional and specify the width and height of the menu minus chrome tiles and cursor. Menu options are selected by pressing the A button. Submenus can be dismissed by pressing the B button. By default the root menu cannot be dismissed. Set config.allowDismissRootMenu to true to allow the root menu to be dismissed by pressing B.

Here's an example for a menu with a submenu:

menu( at( 10, 10, 200, 100 ), {
    { name = "mainOptionOne", type = "submenu", callback = function()
        menu( at( 20, 20 ), {
            { name = "subOptionOne", callback = function() 
              -- do sub things
            end
            },
            { name = "subOptionTwo", callback = function() 
              -- do sub things
            end
            }
        })
    end
    },
    { name = "mainOptionTwo", callback = function()
        -- do another
    end
    }
})

fin

fin("message")

Displays message in a text and marks the game as finished. Also triggers the finish event.

wait

wait(duration, function()
    -- do something after duration seconds
end)

Note that this does not stop the run loop. If you do not want the player to be able to move during the pause you can call ignore before the wait call and listen at the end of its body.

tell

local otherTile = tell("id-of-the-entity")
-- do something with the entity with id: id-of-the-entity

Allows one entity to interact with other entity. You can for instance; call functions on it like so:

local otherTile = tell("id-of-the-entity")
otherTile:functionName()

ignore/listen

ignore()

and

listen()

Toggle whether the game should accept user input or not. Does not affect advancing or dismissing text.

act

act()

Make the player interact with the sprite or collect the item one tile in front of them based on their last movement direction. Note that this function is only really useful if you’ve set config.autoAct to false.

hide

hidePlayer()

Prevents the Player from being drawn. Can only be called from the Player entity.

show

showPlayer()

Makes sure the Player is being drawn. Can only be called from the Player entity.

window

local window = window(at(x, y, w, h))

Draws a window frame at x,y with dimensions w,h. (Coordinates and dimensions are in pixels, not tiles.) Unlike in Pulp, window can be used everywhere. Just remember that you need to call:

window:add()

to add it to the screen, and

window:remove()

to remove it afterwards.

sprite

local sprite = sprite(imagePath, at(x, y))

Draws the requested image at x,y. Unlike draw in Pulp, sprite can be used everywhere. Just remember that you need to call:

sprite:add()

to add it to the screen, and

sprite:remove()

to remove it afterwards.

This function is an alternative for the draw function in Pulp, but less performance heavy.

label

label("stringValue", at(x, y, w, h))

Draws text stringValue at x,y. len is an optional maximum width to draw. lines is an optional maximum height to draw. Does not soft-wrap but does support the \n newline character for hard-wrapping. Can only be called from the Player’s draw event.

fill rect

fillRect(colorName, at(x, y, w, h))

Fills a rect with colorName (either white or black) at x,y with dimensions w,h. (Coordinates and dimensions are in pixels.) Can only be called from the Player’s draw event.

fill circle

fillCircle(colorName, at(x, y, r))

Fills a circle with colorName (either white or black) at x,y with dimensions w. (Coordinates and dimensions are in pixels.) Can only be called from the Player’s draw event.

sound

sound("audioFileName")

Plays the sound matching audioFileName.<extension> located in Source/sounds. Supports:

  • wav
  • mp3

You can also decrease the volume of the sound. Do:

sound("audioFileName", 0.5)

once

once("audioFileName")

or

once("songName", function()
    -- do something when the song ends
end)

Plays the song matching audioFileName.<extension> located in Source/sounds then stops. Does nothing if the requested song is already playing. WIP, callback not yet working

loop

loop("audioFileName") 

Plays the song matching audioFileName.<extension> located in Source/sounds, looping back to the beginning (or the song’s loopFrom point if set in the editor) each time it completes. Does nothing if the requested song is already playing.

stop

stop()

Stops the currently playing song.

store

store()

Copies the current value of the global save variable into persistent storage.

restore

restore()

Sets the global save variable to the save values from persistent storage.

toss

toss()

Deletes the storage file.

Math functions

random

local varName = math.random (max)

or

local varName = math.random (min, max)

Returns an integer between the positive integers min (or 0) and max inclusive.

floor

local varName = math.floor(num)

Returns the largest integer less than or equal to the given number.

ceil

local varName = math.ceil(num)

Returns the next largest integer greater than or equal to the given number.

round

Not supported

Round is not supported by the math library. Though you could use ceil and floor as alternatives.

sine

local varName = math.sine(num)

Returns the sine of the given number (in radians).

cosine

local varName = math.cos(num)

Returns the cosine of the given number (in radians).

tangent

local varName = math.tan(num)

Returns the tangent of the given number (in radians).

radians

local varName = math.rad(num)

Returns the given number (in degrees) converted to radians.

degrees

local varName = math.deg(num)

Returns the given number (in radians) converted to degrees.

More functions

invert

invert()

or

local varName = invert()

Causes all drawing to be inverted (white displays as black and vice versa) until called again. Returns true when drawing is inverted and false when drawing is not inverted.

isSolid

local varName = isSolid(entity)

Returns true if the entity is solid, otherwise returns false. Just to clarify: All entities with collisionTypes other than overlap are solid.

at

at(x,y,w,h)

Returns an object { x = x, y = y, w = w, h = h }. This makes it easier and more readable to position dialogs and windows. This function is used in the docs above, though if you prefer you can also write position and size without this function.

Example with at function:

say("Message", at( 20, 20 ))

Example without at function:

say("Message", { x = 20, y = 20 })