Show posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.

Show posts Menu

Topics - llamazing

Your scripts / Default Destination Checker
January 27, 2021, 01:57:34 AM
So apparently if a map has no default destination designated, then the first one listed in the map data file becomes the default. This caused a bug in Ocean's Heart where if someone saved the game on a particular map then they would spawn at a destination that was blocked by a locked door, and the hero would be stuck. The solution is to always designate one of the map's destinations as the default whenever there is more than one (choosing one that won't be blocked by an obstruction, of course).

I wrote a script to scan all the map data files, reporting any found that have more than one destination without a default destination specified.


The list of maps found is printed to the console.
Your projects / Quest Log Menu
January 22, 2021, 07:52:25 PM
The Quest Log Menu shows a list of main & side-quests, tracking their current progress and status. Also featured by Ocean's Heart.

See the wiki page for documentation and examples.

Feel free to open an issue for feature requests, bug reports, or assistance in how to write/construct a particular quest log entry.

The repo files include a playable short demo that showcases various features that are possible (a mini-dungeon with different side-quests in each room).
Your scripts / Dialog_Box script to change font
February 06, 2020, 01:59:55 PM
It was asked on Discord how to change the font used in the dialog_box script for certain dialogs.

Here's an updated script where you can assign the font and font_size properties to a dialog entry to specify what to use, otherwise it will use default values.

Original author: Christopho, GPLv3
Here's a tutorial I put together explaining how to create seamless loops for .ogg music using the metatags LOOPSTART and LOOPEND.

It assumes you have an existing music file and explains how to find the values to use for the start and end of the loop. The free audio editor Audacity is used.
Your scripts / Generate map images
September 14, 2019, 03:23:28 PM
I revisited my script to generate images from a map .dat file. The original topic can be found here:,1122.0.html

Notable improvements:
*I adjusted the colors for a sleeker and more uniform appearance
*It now generate a composite image from all map layers
*It now exports an image file
*Can generate a "world" image of multiple maps linked by edge teletransporters
*Can generate individual images for all maps in a single operation

The image exported is a .ppm file, which has a very simple file format, but the file size is not optimal. The image can be converted to a .png using GIMP.

I experimented with a pure-lua library that generates png images, but it was slow and was actually generating the exact same sequence of bits, just with a different header (so there was no improvement in file size over the .ppm file). Maybe a better png library could be an option in the future.

Solarus crashes for me when trying to export an image of approximately 8000x8000 pixels or larger. I'm guessing this is some sort of memory limitation that is being exceeded (the outputted image would be over 100MB), but no error is given. The default SCALE_FACTOR is 8 (maps rendered at 1/8th original size), so this will probably not be an issue at this scale even when generating a world map image (unless you have a VERY big world map), just beware. I could possibly improve memory performance here by generating the image data one row at a time instead of doing the entire image at once if this becomes a problem.

Also note there are some bugs in Solarus v1.6 related to the drawing order (which are fixed in v1.6.2). I am not able to test in v1.6.2 right now, but the image might look slightly different.
Code (lua) Select

local map_imager = require"scripts/map_imager"
map_imager(map_id) --to generate one image for the specified map_id (string)
--equivalent to map_imager:export_map(map_id)
map_imager() --to generate images for ALL maps
--equivalent to map_imager:export_all()
map_imager:export_world_map(map_id) --to generate an image of the specified map_id (string) and any maps connected by edge teletransporters

When generating a world map image, it will also output coordinates of each map with 0,0 being the top-left corner. This could be useful if you need assistance in figuring out world coordinates of your maps.

Here is a world map generated from Ocean's Heart using "ballast_harbor/ballast_harbor" as the starting map (converted to png with GIMP):
Your projects / Pathfinding Experiment
January 13, 2019, 02:54:57 PM
I've been working on an experiment in pathfinding that has given good results. It uses a network of "navigation beacon" entities distributed on a map and calculates an optimal path to get from point A to B assuming both are in range of beacons connected in the same network.

I still have some ideas to develop things further, but it's far enough along to make a playable demo, so I thought I'd share. Screenshots won't do this one justice, so check it out if it sounds like something that would interest you.

Solarus v1.6 is required (and it indeed makes use of some cool new features :) ).
Your scripts / Dialog Box script with Name Displayed
November 23, 2018, 04:42:08 PM
I wanted to have the name of the person speaking displayed in at the top of the dialog box, so I modified the dialog_box.lua script by Christopho (I used the one from Children of Solarus since it looked to be the most up-to-date version).

The new syntax for the dialog text uses a line beginning with '##' to specify the displayed name. Without any lines beginning with '##', no name will be displayed and the behavior of the text box is the same as it was before.

The name will also be applied to subsequent panes within the same dialog until it is changed by specifying a new name. Beginning a line '###' turns the displayed name off again.

Beginning a line with '##>' shifts the name over to the top-right side of the dialog box. This is useful if you want the player's name to appear right-justified while the names of all other NPCs are left-justified.

Finally, beginning the displayed name text with '&' signifies that the given name is actually a savegame variable key and its current value will be used for the name text that gets displayed (e.g. '##>&player_name').

Lines beginning with '##' must be the first line of a set of lines for a given pane.

Sample dialog text:
Code (lua) Select

dialog{ text = [[
First Pane: Beginning the line with two
'#' characters causes the name Hector to
appear at the top of the dialog.
Second Pane: The name Hector continues
to be shown in subsequent panes as well.

Third Pane: Note the empty line above is
used to complete the previous pane for
it to have 3 lines of text (even empty).
Fourth Pane: Putting 3 '#' characters at
the start of a pane causes it to display
no name at the top of the dialog.
Fifth Pane: The > immediately after the
'#' characters causes the name to be
shifted over to the right.
Sixth Pane: This can be useful to show
the player's name. In this case, the
text shown is literally 'Player'.
Seventh Pane: Here the '&' character is
used to instead display the value of a
savegame_variable named 'player_name'.

The modified script (GNU GPL v3, originally by Christopho) is attached along with a modified version of dialog_box.png (CC BY-SA 4.0, originally by Metallizer & Olivier Cléro)
Development / API optional parameters: error using nil
November 11, 2018, 03:08:49 AM
I tried making a new surface and got a strange error:

bad argument #1 to create (number, string or no value expected, got nil)

The only way I've found to get around it is the following, but it's a bit awkward:
Code (lua) Select

local surface = width and sol.surface.create(width, height) or sol.surface.create()

It's a bit strange that there'd be a distinction between a value of 'nil' and 'no value' when it comes to specifying optional values. Is there a better way to get around it while working purely in lua? i.e. is there a way to assign 'no value' to a lua variable?

For context, the value of width is being specified by a data file, and I'm wanting to use default values if the width is omitted in the data file.
Development / set_text_key for Text Surfaces
October 21, 2018, 07:44:37 PM
The quest documentation indicates that text_surface:set_text_key(key) is equivalent to text_surface:set_text(sol.language.get_string(key)), but is that really true?

If they are indeed equivalent then that would mean that the text wouldn't get updated when the language is changed and text_surface:set_text_key() would need to be called again just to update it. That doesn't seem right, could anyone confirm?
Given the discussion on the Ocean's Heart project topic about making a map menu, I wanted to test the feasibility of generating a mini-map image from the info in the map .dat file. The endeavor has been a success.

What I did is read the map .dat file from a lua script, which gives the size, position and stacking order of all tiles. Also reading the tileset .dat file used by that map gives the ground type (e.g. traversable, wall, etc.) for each tile. I assigned colors to the tiles based on the ground type. Then on a surface I drew rectangles whose size and position corresponds to each tile at 1/8th scale, drawn in the same order as on the map. I drew layers 0 to 2 separately and ended up with this result for the overworld maps:

Note that sometimes the layer 2 image got clipped because the window I was using was only 480 pixels in height. It didn't appear to be losing anything in the cases where it was clipped.

I then manually overlaid the layers, using 50% opacity for layers 1 & 2, and lined up the maps for how they fit together. In hindsight I realize I could have made the script to do the same thing.

Note that the Hourglass Fort Outside and Naerreturn Bay maps overlap with each other, so I moved the Hourglass map off to the side.

This mapping technique could be useful either in generating a reference image from which to create hand-drawn images of the maps, or a modified version of the script could be used to have the quest itself generate the map images to use for the map menu. For pixel-precise accuracy, the purple border between maps (so that edge teletranpsorters are visible) should be removed.

In order to reproduce my results and run the script, do the following:
1) Download version 0.1 of the Ocean's Heart project from the topic linked above (back up any existing save files if you already have the quest)
2) Change the quest size to 640 by 480 in quest.dat
3) In main.lua of that quest comment out line 5, require("scripts/features"), so that the HUD is not displayed
4) Replace the scripts/game_manager.lua file with this one:
Code (lua) Select
local game_manager = {}

local initial_game = require("scripts/initial_game")

-- Starts the game from the given savegame file,
-- initializing it if necessary.
function game_manager:start_game(file_name)

local exists =
local game =
if not exists then
-- Initialize a new savegame.
end, 480) --to prevent display from scaling at 2x

local map_layers = require("scripts/menus/display_map")

function game:on_draw(dst_surface)
dst_surface:fill_color{0,0,0} --fill black, don't want to see what's underneath

for layer = 0,2 do
local surface = map_layers[layer]
if surface then
local width, height = surface:get_size()
surface:draw(dst_surface, 1, layer*(height+1)+1) --1 px from edge of screen with images separated by 1 px

return game_manager

5) Add this script to the scripts/menus directory, named display_map.lua:
Code (lua) Select
local map_id = "new_limestone/limestone_present"
--local map_id = "goatshead_island/goatshead_harbor"
--local map_id = "goatshead_island/west_goat"
--local map_id = "goatshead_island/goat_hill"
--local map_id = "goatshead_island/lighthouse_cape"
--local map_id = "goatshead_island/poplar_forest"
--local map_id = "goatshead_island/poplar_coast"
--local map_id = "goatshead_island/crabhook_village"
--local map_id = "Yarrowmouth/yarrowmouth_village"
--local map_id = "Yarrowmouth/juniper_grove"
--local map_id = "Yarrowmouth/foothills"
--local map_id = "Yarrowmouth/silent_glade"
--local map_id = "Yarrowmouth/naerreturn_bay"
--local map_id = "Yarrowmouth/kingsdown"
--local map_id = "Yarrowmouth/hourglass_fort/outside"
--local map_id = "ballast_harbor/ballast_harbor"

local tilesets = {}
local maps = {}

local SCALE_FACTOR = 8 --draw maps at 1/8th actual size

--color to draw tile based on ground property; ignore if not listed
local tile_colors = {
traversable = {0, 255, 0}, --green
wall = {127, 127, 127}, --grey
low_wall = {204, 204, 204}, --light grey
teletransporter = {255, 255, 0}, --yellow; this is a type of entity, not a tile ground property
shallow_water = {0, 255, 255}, --light blue
deep_water = {0, 0, 255}, --blue
hole = {0, 0, 0}, --black
ladder = {255, 0, 255}, --pink
lava = {255, 0, 0}, --red
prickles = {255, 127, 0}, --orange
ice = {127, 255, 255}, --pale blue

--load tileset .dat file to determine ground property of each tile
local function load_tileset(tileset_id)
local tileset = {}

local env = {}
function env.tile_pattern(properties)
local id =
assert(id, "tile pattern without id")
id = tonumber(id)
assert(id, "tile pattern id must be a number")

local ground = properties.ground
assert(ground, "tile pattern without ground")

if tile_colors[ground] then --ignore ground properties that don't have a color assigned
tileset[id] = tile_colors[ground] --link the color to use with the tile id

setmetatable(env, {__index = function() return function() end end})

local chunk = sol.main.load_file("tilesets/"..tileset_id..".dat")
setfenv(chunk, env)

return tileset

--load map .dat file to get list of tiles used
local function load_map(map_id)
local map = { tiles = {} }

local env = {}

--properties stores the size and coordinates for the map and the tileset used
local x = tonumber(properties.x)
assert(x, "property x must be a number")
local y = tonumber(properties.y)
assert(y, "property y must be a number")

local width = tonumber(properties.width)
assert(width, "property width must be a number")
local height = tonumber(properties.height)
assert(height, "property height must be a number")

local tileset = properties.tileset
assert(tileset, "properties without tileset")

map.x = x
map.y = y
map.width = width
map.height = height
map.tileset = tileset

--each tile defines a size, coordinates and layer as well as the tile id to use
function env.tile(properties)
local pattern = properties.pattern --pattern is the tile id
assert(pattern, "tile without pattern")
pattern = tonumber(pattern)
assert(pattern, "tile pattern must be a number")

local layer = properties.layer
assert(layer, "tile without layer")
layer = tonumber(layer)
assert(layer, "tile layer must be a number")

local x = tonumber(properties.x)
assert(x, "tile x must be a number")
local y = tonumber(properties.y)
assert(y, "tile y must be a number")

local width = tonumber(properties.width)
assert(width, "tile width must be a number")
local height = tonumber(properties.height)
assert(height, "tile height must be a number")

table.insert(map.tiles, {
pattern = pattern,
layer = layer,
x = x,
y = y,
width = width,
height = height,

--also extract teletransporter usage to be able to draw teletransporter locations
function env.teletransporter(properties)
local layer = properties.layer
assert(layer, "tile without layer")
layer = tonumber(layer)
assert(layer, "tile layer must be a number")

local x = tonumber(properties.x)
assert(x, "tile x must be a number")
local y = tonumber(properties.y)
assert(y, "tile y must be a number")

local width = tonumber(properties.width)
assert(width, "tile width must be a number")
local height = tonumber(properties.height)
assert(height, "tile height must be a number")

table.insert(map.tiles, {
pattern = "teletransporter", --instead of using tile pattern to determine color, tile_colors has a "teletransporter" entry
layer = layer,
x = x,
y = y,
width = width,
height = height,

setmetatable(env, {__index = function() return function() end end})

local chunk, err = sol.main.load_file("maps/"..map_id..".dat")
setfenv(chunk, env)

return map

local function generate_map(map_id)
assert(type(map_id)=="string", "Bad argument #1 to 'generate_map' (string expected)")

--load map info from .dat file if not already loaded
if not maps[map_id] then maps[map_id] = load_map(map_id) end
local map = maps[map_id]

--load tileset info from .dat file if not already loaded
if not tilesets[map.tileset] then tilesets[map.tileset] = load_tileset(map.tileset) end
local tileset = tilesets[map.tileset]

--create a surface for each layer
local layers = {}
for layer = 0,2 do
layers[layer] = sol.surface.create(map.width/SCALE_FACTOR+2, map.height/SCALE_FACTOR+2) --include room for 1px border
layers[layer]:fill_color({127, 0, 255}, 0, 0, map.width/SCALE_FACTOR+2, 1) --top 1px purple border
layers[layer]:fill_color({127, 0, 255}, 0, 0, 1, map.height/SCALE_FACTOR+2) --left 1px purple border
layers[layer]:fill_color({127, 0, 255}, 0, map.height/SCALE_FACTOR+1, map.width/SCALE_FACTOR+2, 1) --bottom 1px purple border
layers[layer]:fill_color({127, 0, 255}, map.width/SCALE_FACTOR+1, 0, 1, map.height/SCALE_FACTOR+2) --right 1px purple border

--draw each tile
for _,tile in ipairs(map.tiles) do
local pattern = tile.pattern
local tile_color = tile_colors[pattern] or tileset[pattern]

--draw the tile as solid color on surface corresponding to the correct layer
if tile_color and layers[tile.layer] then --ignore corner and empty tiles

return layers

return generate_map(map_id)

I'm trying to use a straight movement, and the straight_movement:set_max_distance(max_distance) function is not working the way that I'd expect.

Below is simplified code of what I am trying to do. At 4 seconds the table obj is moved 100 pixels to the right, and then 3 seconds after that it is moved 100 pixels to the left to return it to its original position.

The problem is that the set_max_distance function appears to be calculating the distance from (0,0), when I'd expect it to calculate the distance from what the coordinates were at the start of the movement. The result is that the second movement ends up being a 200 pixel movement to the left instead of the 100 pixels that I wanted (the object stops when x=-100, which is 100 pixels from (0,0)).

Code ( lua) Select
local obj = {x=0, y=0}
sol.timer.start(4000, function()
  print(obj.x) --0 at 4 seconds
  local movt1 = sol.movement.create"straight"
  function movt1:on_finished() print(obj.x) end --obj.x=100 at 5 seconds
  sol.timer.start(3000, function()
    print(obj.x) --100 at 7 seconds
    local movt2 = sol.movement.create"straight"
    function movt2:on_finished() print(obj.x) end --obj.x=-100 at 9 seconds; expected obj.x=0 at 8 seconds
I'm not expecting anyone to have heard of the game Cythera, which is a mac exclusive tile from the late 90s. It had an interesting dialog system where certain words said by NPCs would be highlighted, and the player could ask about them go into more depth on that topic. I've discussed this type of dialog system previously in this topic.

I've put together a quick little sample quest that tries to replicate the dialog system used by Cythera. It replicates the dialog text of three characters from the game (one of which is fairly complicated and a good testbed to push my dialog script to its limits).

When talking to NPCs, pretty much all of them respond to the key words "name", "job" and "bye" (to close the dialog). Beyond that, certain key words are highlighted in blue denoting that the player can take the conversation further by asking about those topics. The player can either type the keyword into a text box or select them from a running list by using the mouse.

A little quirk to be aware of is that only the first 5 letters of whatever the player types in are considered (it can be less than 5 letters too, depending on the word). This makes it easy for "fish", "fishing" or "fisherman" to all give the same response. Note that while Cythera only ever considered the first 4 letters entered, I decided to bump the limit up to 5 for a little more versatility. I don't think that it would ever be necessary to use more than 5 letters, though.

See if you can figure out how to complete this short little quest. I will say that there are two possible solutions. No items are required, and there are not any enemies.

Installation instructions:
The Solarus ALTTP resource pack is required for this quest. Confirmed to work with version 1.5.6, which can be found here. Download and extract the ALTTP resource pack. Then download my quest and extract it to the the same directory as the resource pack. For any duplicate files between the two, the ones from my quest should overwrite the ones from the resource pack.

Feel free to borrow and or modify the scripts for your own usage. Note that most of the images and sounds included in the quest actually come from Cythera and should not be used. If I end up going further with this project, it will ultimately not use any Cythera assets or text.

This demo quest has the following features:
  • Interactive dialog system
    • NPC portraits
    • clickable hyperlink text
    • text input
    • dynamic dialog content that depends on a multitude of factors
  • Preserve a log of NPC dialog for later reference
  • Objective tracking
  • Day/night cycle that darkens the map at night
    • lluminating light sources at night
    • Different music between night & day
    • NPC movements synchronized with the time
    • sleep in a bed to advance time
  • Read-only console that prints notifications for the player
  • Various UI elements with mouse/keyboard interaction

The quest can be downloaded from its github project page
Development / Pickable Item with No Shadow (Solved)
December 25, 2016, 12:02:10 AM
Using the ALTTP resource pack, I wanted to create a pickable treasure for the bottle, which looks bad if it has a shadow.

According to the Equipment Items documentation, the method item:set_shadow(shadow_animation), where shadow_animation is nil gives no shadow. However, when I try doing item:set_shadow(), I get the following error:

Error: In on_created: [string "items/bottle_1.lua"]:9: bad argument #1 to set_shadow (string expected, got no value))

I also tried item:set_shadow(false) and item:set_shadow("nil") to no avail. What am I doing wrong here?
Development / Clear contents of Quest Editor console?
December 04, 2016, 10:16:36 AM
Is there a way to clear what's displayed in the console of the Quest Editor? When there are a bunch of errors that I've fixed by editing my scripts, it would be nice to reset the console to a blank slate before running the quest again in order to verify the fix.

I checked the menus and right-click pop-up menu but didn't see anything to clear the contents of the console. I'm wondering if maybe there is a command that can be typed into the console to clear it?
Development / Getting the sprite of a Dynmaic Tile
September 25, 2016, 08:31:09 PM
I have a tile that I converted to a dynamic tile (pattern from the ALTTP resource pack), but when I call get_sprite() for the dynamic tile from in the map:on_draw() function, it returns nil. What am I doing wrong?

What I want to do is read the frame number so that I can synchronize an overlay with the animation.
Development / NPC movement paths and obstacle detection
September 23, 2016, 10:44:15 PM
I've been playing around with assigning path movements to npcs in order to have them follow a fixed route. The problem I'm running into is obstacle avoidance. Now it's easy enough to choose a route for the NPC that is clear of obstacles on the map, and I'd only have the NPCs moving around inside of towns where there are no enemies, so collisions with enemies are not a concern either. It's just collisions with the hero that I have to worry about.

I started off by setting set_ignore_obstacles(true) for the NPC movement. For the most part, that works. If the NPC's path goes through the hero's position then the hero will just get stuck for a little while until the NPC passes the hero, and it's just a little annoyance. The problem occurs if the hero happens to be standing where the NPCs path stops (and the NPC just stands in that spot for a while). Now the hero cannot move at all until the NPC starts a new path movement.

So then I played around with trying to push the hero out of the way when there's a collision with the NPC. So in the NPC movement's on_position_changed() function (any time the NPC moves), I check if the NPC and hero overlap with NPC:overlaps(hero, "overlapping"), and if they do, I assign a movement to the hero to nudge the hero out of the NPC's path.

I'm assuming that I need to move the hero one pixel at a time so that if the hero gets pushed over a 16x16 teletransporter (say inside a doorway), that the hero doesn't get pushed too far and skip the teletransporter. Is that correct? Pixel movement also seems preferable over path movement so that the hero can be moved just a couple pixels out of the way of the NPC instead of multiples of 8 pixels.

So I got something setup to move the hero when an NPC walks into him, but now I'm running into problems with making sure the hero doesn't get pushed into an obstacle when nudged. As an example, consider an NPCs walking along a path in the North direction with the Hero to the North of the NPC. When a collision occurs, I want to nudge the hero to the NW because if I only nudge the hero N, then the NPC could end up pushing the hero for the entire length of the path.

To nudge the hero to the NW, I give the hero's pixel movement a trajectory with several entries of {-1, -1}. The problem now is if there is an obstruction on the map to the west of the hero, I still want the hero to be moved to the N. It seems the hero's movement just calls on_obstacle_reached() and doesn't move the hero at all, even though there is no obstacle to the North.

The workaround I then used for the above problem is to give a trajectory that only moves one pixel W then one pixel N, alternating (i.e. {{-1,0}, {0,-1}, {-1,0}, {0,-1}...etc.}). This actually works for the most part, but there are still a few corner cases that don't quite work.

See the example image below, where the green 16x16 box is the hero, the blue is the npc, who is walking to the north to the doorway in the building above. The grey pixels (and the doorway) are traversable. So then the first time the NPC and hero bounding boxes overlap, there are six pixels (cyan) where the overlap occurs. Now obviously nudging the hero to the north fails because there is a building in the way, but nudging the hero to the east also fails because if the hero is moved 1 pixel to the east, the hero still overlaps the NPC by 5 pixels.

I don't have a good solution to this problem. The only thing I can think of is to do set_ignore_obstacles(true) for the hero and do my own manual obstacle detection, but that's tricky because it runs the risk of pushing the hero into the side of a building and getting stuck. I'm assuming I'd have to use map:get_ground() to do my own collision text, but how does that work? With a "wall_top_right" tile, for example, the bottom left corner of the tile is traversable and the top right corner is not. So does that mean that a coordinate in the top right half of the tile returns "wall_top_right" whereas a coordinate in the bottom left half of the tile returns "traversable"?

Or does anyone have any better ideas?
Development / Pattern matching and UTF-8
September 08, 2016, 02:47:52 AM
I'm working on a custom dialog manager script for the features I described in this topic. A few questions have arisen in the process regarding UTF-8 characters:

1) In the text of my dialogs.dat file, keywords are denoted by prefacing the keyword with the "@" character. My original concept was to iterate through the keywords in the text using the following: text:gmatch("@(%w+)")which works great for ASCII text and forces keywords to only be alpha-numeric characters. However, my limited testing seems to conclude that it will not work as intended if UTF-8 characters are used.

It looks like a better solution is to instead use the following: text:gmatch("@([^%p%s]+)")which grabs all characters between the @ character and the first punctuation or space character (it only looks at one line at a time, so new-line characters are excluded).

Am I correct that pattern matching doesn't work with UTF-8 characters, and is my second proposed solution viable for when UTF-8 characters are used?

2) I also convert the text input of the player (in their native language) to lowercase with string.lower() so that case does not matter. My testing seems to indicate that string.lower() does not do anything for UTF-8 characters, but I see a note in the lua manual for the string.lower() entry that it depends on the locale.

How does the locale affect string.lower() and is the locale set automatically by the Solarus engine? Or perhaps by the operating system?

3) I've noticed that string.len() doesn't work the way I want it to in regard to UTF-8 (multibyte) characters since it returns the number of bytes in the string, and not the number of actual characters. It's easy enough to write my own custom function to count the number of characters in a string, but I figure I may as well ask...

Are there any included lua functions in the Solarus engine that return the number of characters in a string? It looks like lua 5.3 might have support for that, but as far as I can tell, Solarus appears to be using lua 5.1.
Development / Getting all strings in strings.dat?
September 04, 2016, 06:22:04 PM
It there a good way to either get all key/value entries in strings.dat for a given language or just the ones with keys beginning with a certain pattern?

Or do I have to parse strings.dat in my own script to extract the info myself?

I was trying to manually keep a list of strings.dat keys I'm interested in, but there's too many and it's becoming too much of a hassle.