Vendor libs/hump, sfxrlua, json4lua, narrator as real files

These were committed as empty submodule gitlinks with no .gitmodules,
so the folders were never populated and the game could not load
(require of libs/hump/timer, libs/sfxrlua/sfxr, narrator.narrator).
Checked out each at its pinned commit and vendored the files, matching
the existing bundled-libs convention. Removed the junk libs/libs/ tree.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Your Name 2026-08-11 11:37:09 -04:00
parent e73f63b5d4
commit 03532d29e4
219 changed files with 20254 additions and 252 deletions

View file

@ -0,0 +1,19 @@
- I looked at Monsieur Fogg
* ... and I could contain myself no longer.
'What is the purpose of our journey, Monsieur?'
'A wager,' he replied.
* * 'A wager!'[] I returned.
He nodded.
* * * 'But surely that is foolishness!'
* * * 'A most serious matter then!'
- - - He nodded again.
* * * 'But can we win?'
'That is what we will endeavour to find out,' he answered.
* * * 'A modest wager, I trust?'
'Twenty thousand pounds,' he replied, quite flatly.
* * * I asked nothing further of him then[.], and after a final, polite cough, he offered nothing more to me. <>
* * 'Ah[.'],' I replied, uncertain what I thought.
- - After that, <>
* ... but I said nothing[] and <>
- we passed the day in silence.
- -> END

View file

@ -0,0 +1,39 @@
name: "main"
scale_along_z: 0
embedded_instances {
id: "example"
data: "components {\n"
" id: \"example\"\n"
" component: \"/example-defold/example.gui\"\n"
" position {\n"
" x: 0.0\n"
" y: 0.0\n"
" z: 0.0\n"
" }\n"
" rotation {\n"
" x: 0.0\n"
" y: 0.0\n"
" z: 0.0\n"
" w: 1.0\n"
" }\n"
" property_decls {\n"
" }\n"
"}\n"
""
position {
x: 0.0
y: 0.0
z: 0.0
}
rotation {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
scale3 {
x: 1.0
y: 1.0
z: 1.0
}
}

View file

@ -0,0 +1,17 @@
font: "/builtins/fonts/vera_mo_bd.ttf"
material: "/builtins/fonts/font.material"
size: 32
antialias: 1
alpha: 1.0
outline_alpha: 0.0
outline_width: 0.0
shadow_alpha: 0.0
shadow_blur: 0
shadow_x: 0.0
shadow_y: 0.0
extra_characters: ""
output_format: TYPE_BITMAP
all_chars: false
cache_width: 0
cache_height: 0
render_mode: MODE_SINGLE_LAYER

View file

@ -0,0 +1,146 @@
script: "/example-defold/example.gui_script"
fonts {
name: "example"
font: "/example-defold/example.font"
}
background_color {
x: 0.0
y: 0.0
z: 0.0
w: 0.0
}
nodes {
position {
x: 320.0
y: 340.0
z: 0.0
w: 1.0
}
rotation {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
scale {
x: 0.5
y: 0.5
z: 0.5
w: 1.0
}
size {
x: 800.0
y: 0.0
z: 0.0
w: 1.0
}
color {
x: 0.102
y: 0.102
z: 0.102
w: 1.0
}
type: TYPE_TEXT
blend_mode: BLEND_MODE_ALPHA
text: "paragraph"
font: "example"
id: "paragraph"
xanchor: XANCHOR_NONE
yanchor: YANCHOR_NONE
pivot: PIVOT_N
outline {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
shadow {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
adjust_mode: ADJUST_MODE_FIT
line_break: true
layer: ""
inherit_alpha: true
alpha: 1.0
outline_alpha: 1.0
shadow_alpha: 1.0
template_node_child: false
text_leading: 1.0
text_tracking: 0.0
custom_type: 0
enabled: false
visible: true
material: ""
}
nodes {
position {
x: 320.0
y: 300.0
z: 0.0
w: 1.0
}
rotation {
x: 0.0
y: 0.0
z: 0.0
w: 1.0
}
scale {
x: 0.5
y: 0.5
z: 0.5
w: 1.0
}
size {
x: 800.0
y: 0.0
z: 0.0
w: 1.0
}
color {
x: 0.6
y: 0.2
z: 0.0
w: 1.0
}
type: TYPE_TEXT
blend_mode: BLEND_MODE_ALPHA
text: "choice"
font: "example"
id: "choice"
xanchor: XANCHOR_NONE
yanchor: YANCHOR_NONE
pivot: PIVOT_N
outline {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
shadow {
x: 1.0
y: 1.0
z: 1.0
w: 1.0
}
adjust_mode: ADJUST_MODE_FIT
line_break: true
layer: ""
inherit_alpha: true
alpha: 1.0
outline_alpha: 1.0
shadow_alpha: 1.0
template_node_child: false
text_leading: 1.0
text_tracking: 0.0
custom_type: 0
enabled: false
visible: true
material: ""
}
material: "/builtins/materials/gui.material"
adjust_reference: ADJUST_REFERENCE_PARENT
max_nodes: 512

View file

@ -0,0 +1,167 @@
local narrator = require('narrator.narrator')
---@class Self
---@field paragraph_template node
---@field paragraph_initial_position vector3
---@field paragraph_nodes node[]
---@field choice_template node
---@field choice_initial_position vector3
---@field choice_nodes node[]
---@field book Narrator.Book
---@field story Narrator.Story
---@param node node
---@param text string
---@return integer height
local function estimated_height(node, text)
local font_name = gui.get_font(node)
local font = gui.get_font_resource(font_name)
local metrics = resource.get_text_metrics(font, text, {
width = gui.get_size(node).x,
line_break = true
})
return metrics.height
end
---@param self Self
local function clear_output(self)
for index = #self.paragraph_nodes, 1, -1 do
local node = self.paragraph_nodes[index]
table.remove(self.paragraph_nodes, index)
gui.animate(node, 'color.w', 0, gui.EASING_LINEAR, 0.5, 0, function(_, node)
gui.delete_node(node)
end)
end
for index = #self.choice_nodes, 1, -1 do
local node = self.choice_nodes[index]
table.remove(self.choice_nodes, index)
gui.animate(node, 'color.w', 0, gui.EASING_LINEAR, 0.5, 0, function(_, node)
gui.delete_node(node)
end)
end
end
---@param self Self
local function display_output(self)
clear_output(self)
-- Pull all the paragraphs
local paragraphs = self.story:continue()
-- Get the available choices
local choices = self.story:get_choices()
local paragraph_position = vmath.vector3(self.paragraph_initial_position)
local choice_position = vmath.vector3(self.choice_initial_position)
for index = #paragraphs, 1, -1 do
local paragraph = paragraphs[index]
local node = gui.clone(self.paragraph_template)
gui.set_text(node, paragraph.text)
local height = estimated_height(node, paragraph.text)
if index < #paragraphs then
paragraph_position.y = paragraph_position.y + height / 2 + 16
end
gui.set_position(node, paragraph_position)
gui.set_enabled(node, true)
gui.set_alpha(node, 0)
gui.animate(node, 'color.w', 1, go.EASING_LINEAR, 0.5, index / 2)
table.insert(self.paragraph_nodes, node)
end
for index = 1, #choices do
local choice = choices[index]
local node = gui.clone(self.choice_template)
gui.set_text(node, choice.text)
local height = estimated_height(node, choice.text)
if index > 1 then
choice_position.y = choice_position.y - height / 2 - 16
end
gui.set_enabled(node, true)
gui.set_position(node, choice_position)
local size = gui.get_size(node)
size.y = height
gui.set_size(node, size)
gui.set_alpha(node, 0)
gui.animate(node, 'color.w', 1, go.EASING_LINEAR, 0.5, (#paragraphs + 1) / 2)
table.insert(self.choice_nodes, node)
end
end
---@param self Self
function init(self)
msg.post('@render:', 'clear_color', { color = vmath.vector4(1, 1, 1, 1)})
msg.post('.', 'acquire_input_focus')
self.paragraph_template = gui.get_node('paragraph')
self.paragraph_initial_position = gui.get_position(self.paragraph_template)
self.paragraph_nodes = {}
self.choice_template = gui.get_node('choice')
self.choice_initial_position = gui.get_position(self.choice_template)
self.choice_nodes = {}
-- Parse and save a book
self.book = narrator.parse_file('example-defold.book', { save = true })
-- Or load and parse the Ink file from the custom resources
-- local content = sys.load_resource('/example-defold/book.ink')
-- self.book = narrator.parseBook(content)
-- Or load a book from the saved lua module
-- self.book = require('example-defold.book')
-- Or parse a book from the string with Ink content
-- self.book = narrator.parseBook('Hello world!')
-- Init a story
self.story = narrator.init_story(self.book)
-- Begin the story
self.story:begin()
display_output(self)
end
---@param self Self
---@param action_id hash|string
---@param action table
function on_input(self, action_id, action)
if action_id ~= hash 'touch' or not action.pressed then
return
end
if not self.story:can_choose() then
-- Begin a new story
self.story = narrator.init_story(self.book)
self.story:begin()
display_output(self)
return true
end
for index, node in ipairs(self.choice_nodes) do
if gui.pick_node(node, action.x, action.y) then
-- Make a choice
self.story:choose(index)
display_output(self)
return true
end
end
end