Refactor code, part 2

This commit is contained in:
Y. Wang 2024-01-25 03:34:32 +01:00
parent b588e2b9fe
commit 2f95d90be3
10 changed files with 863 additions and 771 deletions

View File

@ -13,12 +13,15 @@ This mod adds the "Wagons" category that contains information related
to the wagons. It also adds adds certain Advtrains-related properties
to certain nodes.
This mod also provides the `atdoc_write` command, which can be used to
export wagon datasheets.
**Please keep in mind that, while this mod attempts to provide accurate
information, its accuracy, especially for information related to
liveries, is limited by various factors. Refer the source code of the
relevant mods for authoritative information.**
## API
## Documentation strings
Wagon mods can add a description to the `_doc_wagon_longdesc` field of
the wagon prototype; adding `advtrains_doc_integration` as an optional
@ -26,30 +29,10 @@ dependency is _not_ needed.
## CI
If you use [Mineunit][mineunit], you can copy (or add a symlink to)
this directory into the fixtures directory of your mod and then using
(for exmaple) the code below to generate wagon datasheets.
```
require "mineunit"
mineunit "core"
fixture "advtrains_doc_integration/ci/init"
sourcefile "init"
```
This mod also be used with [Mineunit][mineunit] in addition to a
regular Minetest environment, such as to generate wagon datasheets.
However, please note that, due to various limitations, the datasheet
generated in such an environment does not contain certain information
that would otherwise be available from Minetest.
[mineunit]: https://github.com/S-S-X/mineunit
If you do not use Mineunit, the following functions are available in
the `advtrains_doc_integration` table:
* `write_wagon_info_as_latex`, which exports the datasheet of a single
type of wagon as LaTeX, and
* `write_all_wagons_as_latex`, which exports all wagon datasheets as
LaTeX.
It is also possible to export wagon datasheets using the `atdoc_write`
command in Minetest.
The datasheets are exported to the world directory (or, for Mineunit,
the fixtures directory). Note that the result of generating datasheets
from Minetest and from CI may differ for various reasons.

View File

@ -5,4 +5,12 @@ project = "Documentation System Integration for Advtrains"
title = "Manual for advtrains_doc_integration"
package = "advtrains_doc_integration"
readme = "README.md"
-- vim: syntax=lua
custom_see_handler("^advtrains%.[%w%._]+$", function(name)
return name, "https://git.bananach.space/advtrains.git/tree/advtrains/api_doc.txt"
end)
custom_see_handler("^SimpleSoundSpec$", function()
return "SimpleSoundSpec", "https://api.minetest.net/sounds/#simplesoundspec"
end)
-- vim: ft=lua

View File

@ -7,7 +7,7 @@ local utils = advtrains_doc_integration.utils
local D = {}
--- Describe a conns object
-- @tparam integer|table conns The conns object to describe.
-- @tparam advtrains.conns|{advtrains.conns,number,...} conns The conns object to describe.
-- @treturn string|nil The description of the conns object.
function D.conns(conns)
local connsdesc = {[0] = "N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW"}

324
doc_ui.lua Normal file
View File

@ -0,0 +1,324 @@
local S = minetest.get_translator "advtrains_doc_integration"
local D = advtrains_doc_integration.describe
local H = advtrains_doc_integration.hypertext
local utils = advtrains_doc_integration.utils
local fsescape = minetest.formspec_escape
local function S2(a, b)
return S(a, S(b))
end
local function addlist(lst, tbl, title, fallback1, fallback2, mapf)
if not tbl then
if fallback2 then
table.insert(lst, fallback2)
elseif fallback2 == false and fallback1 then
table.insert(lst, fallback1)
end
elseif next(tbl) ~= nil then
table.insert(lst, title)
for k, v in pairs(tbl) do
if mapf then
k = mapf(k, v)
end
table.insert(lst, H.listitem(k, true))
end
elseif fallback1 then
table.insert(lst, fallback1)
end
end
local function list_itemstring(x)
local item = ItemStack(x)
if item:is_empty() then
return S("Emptyness")
end
return string.format("%s: %d", item:get_short_description(), item:get_count())
end
local function blankline(st)
return table.insert(st, "")
end
local wlivprev = {}
local function get_livery_preview_selection(pname, itemname)
return (wlivprev[pname] or {})[itemname] or 0
end
local function get_livery_preview(itemname, id)
local tx = (advtrains_doc_integration.prototypes[itemname] or {}).livery_textures
return tx[id] or tx[0]
end
local function render_livery_textures(pname, itemname)
local str = table.concat(utils.map(get_livery_preview(itemname, get_livery_preview_selection(pname, itemname)), fsescape), ",")
return str
end
local function set_livery_preview_selection(pname, itemname, id)
local t = wlivprev[pname]
if not t then
t = {}
wlivprev[pname] = t
end
t[itemname] = id
end
local function doc_render_wagon_information(prototype, pname)
local desctext = {}
if prototype._doc_wagon_longdesc then
table.insert(desctext, tostring(prototype._doc_wagon_longdesc))
blankline(desctext)
end
table.insert(desctext, H.header(S("Basic Information")))
table.insert(desctext, S("Itemstring: @1", H.mono(prototype.name)))
addlist(desctext, prototype.drops, S("Drops:"), S("Drops nothing"), false, function(_, v) return list_itemstring(v) end)
addlist(desctext, prototype.drives_on, S("Drives on:"), nil, nil, H.mono)
addlist(desctext, prototype.coupler_types_front, S("Compatible front couplers:"), S2("Front coupler: @1", "Absent"), S2("Front coupler: @1", "Universal"), utils.get_coupler_name)
addlist(desctext, prototype.coupler_types_back, S("Compatible rear couplers:"), S2("Rear coupler: @1", "Absent"), S2("Rear coupler: @1", "Universal"), utils.get_couple_name)
table.insert(desctext, S("Wagon span: @1", prototype.wagon_span and D.length(2*prototype.wagon_span) or S("Undefined")))
table.insert(desctext, S("Maximum speed: @1", prototype.max_speed and D.speed(prototype.max_speed) or S("Undefined")))
table.insert(desctext, S2("Motive power: @1", prototype.is_locomotive and "Present" or "Absent"))
table.insert(desctext, S("Horn sound: @1", H.describe_sound("playhorn", prototype.horn_sound)))
if prototype.doors.open.sound or prototype.doors.close.sound then
table.insert(desctext, S("Door sound: @1 (when opening), @2 (when closing)",
H.describe_sound("playdooropen", prototype.doors.open.sound),
H.describe_sound("playdoorclose", prototype.doors.close.sound)))
else
table.insert(desctext, S2("Door sound: @1", "Undefined"))
end
blankline(desctext)
table.insert(desctext, H.header(S("Wagon Capacity")))
table.insert(desctext, S("Passenger seats: @1", prototype.max_passengers))
table.insert(desctext, S("Driver seats: @1", prototype.max_drivers))
if prototype.has_inventory then
addlist(desctext, prototype.inventory_list_sizes, S("Cargo inventory size:"), S2("Cargo inventory: @1", "Present"), false, function(k, v)
return string.format("%s: %d", H.mono(k), v)
end)
else
table.insert(desctext, S2("Cargo inventory: @1", "Absent"))
end
if techage and prototype.techage_liquid_capacity then
table.insert(desctext, S("Liquid Capacity (Techage): @1", string.format("%d", prototype.techage_liquid_capacity)))
end
blankline(desctext)
table.insert(desctext, H.header(S("Wagon Appearance")))
table.insert(desctext, S("Mesh: @1", prototype.mesh and H.mono(prototype.mesh) or "None"))
addlist(desctext, prototype.textures, S("Textures:"), S("No textures"), false, function(_, v) return H.mono(v) end)
local livsel = get_livery_preview_selection(pname, prototype.name)
local livrst = (livsel ~= 0 and not prototype.dlxtrains_livery) and " " .. H.action("preview_0", S("[Reset Preview]")) or ""
local livids = 0
local function livprev(desc)
livids = livids+1
local label = H.plain(desc)
if livids == livsel then
label = H.bold(desc)
end
return H.action(string.format("preview_%d", livids), label, true)
end
local dlxlivdef = prototype.dlxtrains_livery
table.insert(desctext, S2("DlxTrains livery system: @1", dlxlivdef and "Supported" or "Unsupported"))
if dlxlivdef then
livids = -1
table.insert(desctext, "Livery presets:")
for k = 0, dlxlivdef.count-1 do
table.insert(desctext, H.listitem(string.format("%s (%s, %s)", H.mono(dlxlivdef[k].code), livprev(S("default")), livprev(S("weathered"))), true))
end
end
local bikeliv = S("Unsupported")
local bikelivdesc = nil
if prototype.set_livery then
if prototype.livery_definition then
bikeliv = S("Supported by the multi_component_liveries mod")
bikelivdesc = {}
addlist(bikelivdesc, prototype.livery_definition.components, S("Livery components:"), nil, nil, function(_, v) return H.plain(v.description) end)
addlist(bikelivdesc, prototype.livery_definition.presets, S("Livery presets:") .. livrst, nil, nil, function(_, v) return livprev(v.description) end)
bikelivdesc = table.concat(bikelivdesc, "\n")
else
bikeliv = S("Supported")
end
end
table.insert(desctext, S("Livery system with bike painter: @1", bikeliv))
table.insert(desctext, bikelivdesc)
local atlivdef = prototype.advtrains_livery_tools
table.insert(desctext, S2("Advtrains livery tools (Marnack): @1", atlivdef and "Supported" or "Unsupported"))
if atlivdef then
addlist(desctext, atlivdef.template_names, S("Livery templates:"), nil, nil, function(_, v) return H.plain(v) end)
addlist(desctext, atlivdef.livery_names, S("Livery presets:") .. livrst, nil, nil, function(_, v) return livprev(v) end)
end
blankline(desctext)
table.insert(desctext, H.header(S("Implementation Details")))
local attachment_offset_support = S("Unsupported")
if advtrains_attachment_offset_patch then
local t = advtrains_attachment_offset_patch
if prototype.get_on == t.get_on_override and prototype.get_off == t.get_off_override then
attachment_offset_support = S("Supported")
end
end
table.insert(desctext, S("Proper player attachment positioning: @1", attachment_offset_support))
for k, v in pairs {
custom_on_activate = "Custom instantiation callback",
custom_on_step = "Custom step function",
custom_on_velocity_change = "Custom velocity change callback",
} do
table.insert(desctext, S2(v .. ": @1", prototype[k] and "Defined" or "Undefined"))
end
local x0, y0 = doc.FORMSPEC.ENTRY_START_X+0.25, doc.FORMSPEC.ENTRY_START_Y
local x1, y1 = doc.FORMSPEC.ENTRY_END_X+0.75, doc.FORMSPEC.ENTRY_END_Y+0.625
local width, height = x1-x0, y1-y0
local mside = height/2
local mesh = fsescape(prototype.mesh or "")
local textures = render_livery_textures(pname, prototype.name)
local fstext = {
string.format("hypertext[%f,%f;%f,%f;entry_body;%s]", x0, y0, width-mside, height+0.875, fsescape(table.concat(desctext, "\n"))),
string.format("item_image[%f,%f;%f,%f;%s]", x1-mside, y0+0.0625, mside, mside, fsescape(prototype.name)),
string.format("model[%f,%f;%f,%f;%s;%s;%s;%f,%f]",
x1-mside, y1-mside, mside, mside, "wagon_model", mesh, textures, -30, 135),
}
return table.concat(fstext, "\n")
end
if doc then
advtrains_doc_integration.register_on_prototype_loaded(function(itemname, prototype)
minetest.override_item(itemname, {_doc_items_create_entry = false})
doc.add_entry("advtrains_wagons", itemname, {
name = ItemStack(itemname):get_short_description(),
data = prototype,
})
if doc.sub.identifier then
doc.sub.identifier.register_object(itemname, "advtrains_wagons", itemname)
end
end)
if doc.sub.items then
local register_factoid = doc.sub.items.register_factoid
local function ndef_field_factoid(cat, ftype, f, ...)
local ftp = type(f)
if ftp == "string" then
local desc = f
f = function(x)
if x ~= nil then
return desc
end
end
end
local keys = {...}
local idx = function(t)
for _, k in ipairs(keys) do
if type(t) ~= "table" then
return nil
end
t = t[k]
end
return t
end
local function fgen(_, def)
return f(idx(def)) or ""
end
return register_factoid(cat, ftype, fgen)
end
local function group_factoid(cat, gr, f)
local function func(x)
return f(x or 0)
end
return ndef_field_factoid(cat, "groups", func, "groups", gr)
end
for cat, cinfo in pairs{
nodes = {
not_blocking_trains = S("This block does not block trains."),
save_in_at_nodedb = S("This block is saved in the Advtrains node database."),
},
} do
for group, ginfo in pairs(cinfo) do
local tp = type(ginfo)
if tp == "string" then
group_factoid(cat, group, function(x)
if x > 0 then
return ginfo
end
end)
elseif tp == "function" then
group_factoid(cat, group, ginfo)
end
end
end
for fname, t in pairs {
advtrains = {
on_train_enter = S("This track reacts to passing trains."),
on_train_approach = S("This track reacts to approaching trains."),
},
luaautomation = {
fire_event = S("This block handles LuaATC events."),
},
} do
for subfname, val in pairs(t) do
local function f(x)
if x ~= nil then
return val
end
end
ndef_field_factoid("nodes", "groups", f, fname, subfname)
end
end
register_factoid("nodes", "groups", function(_, ndef)
if ndef.advtrains then
local atdef = ndef.advtrains
if atdef.set_aspect then
return S("This is a signal with a variable aspect.")
elseif atdef.get_aspect then
return S("This is a signal with a static aspect.")
end
end
if ndef.at_conns then
return S("This track has the following conns table by default: @1", D.conns(ndef.at_conns) or "?")
end
return ""
end)
end
doc.add_category("advtrains_wagons", {
name = S("Wagons"),
build_formspec = doc_render_wagon_information,
})
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= "doc:entry" then
return
end
local pname = player:get_player_name()
local cat, ent = doc.get_selection(pname)
if cat ~= "advtrains_wagons" or ent == nil then
return
end
local act = fields.entry_body
local prototype = advtrains_doc_integration.prototypes[ent]
local sounds = {
["action:playhorn"] = prototype.horn_sound,
["action:playdooropen"] = prototype.doors.open.sound,
["action:playdoorclose"] = prototype.doors.close.sound,
}
if not act then
return
elseif sounds[act] then
minetest.sound_play(sounds[act], {to_player = pname}, true)
else
local txid = string.match(act, [[^action:preview_(%d+)$]])
txid = tonumber(txid)
if txid then
set_livery_preview_selection(pname, ent, txid)
doc.show_entry(pname, cat, ent)
end
end
end)

92
hypertext.lua Normal file
View File

@ -0,0 +1,92 @@
--- Utilities for hypertext used in Minetest formspecs.
-- @module advtrains_doc_integration.hypertext
-- @alias H
local H = {}
local S = minetest.get_translator("advtrains_doc_integration")
--- Create a plain text hypertext element.
-- @tparam string str The content of the element.
-- @tparam[opt] boolean noescape Whether to avoid escaping the content.
-- @treturn string The hypertext element containing the text.
function H.plain(str, noescape)
str = tostring(str)
if noescape then
return str
end
return (string.gsub(str, "([<>])", [[\%1]]))
end
local function addtag(tag, attrs, str, noescape)
local attrlist = {}
for k, v in pairs(attrs) do
table.insert(attrlist, (" %s=%s"):format(k, v))
end
return ("<%s%s>%s</%s>"):format(tag, table.concat(attrlist), H.plain(str, noescape), tag)
end
--- Create a hypertext element with monospace text.
-- @tparam string str The content of the element.
-- @tparam[opt] boolean noescape Whether to avoid escaping the content.
-- @treturn string The hypertext element containing the text.
function H.mono(str, noescape)
return addtag("mono", {}, str, noescape)
end
--- Create a hypertext element with bold text.
-- @tparam string str The content of the element.
-- @tparam[opt] boolean noescape Whether to avoid escaping the content.
-- @treturn string The hypertext element containing the text.
function H.bold(str, noescape)
return addtag("b", {}, str, noescape)
end
--- Create a hypertext element with italic text.
-- @tparam string str The content of the element.
-- @tparam[opt] boolean noescape Whether to avoid escaping the content.
-- @treturn string The hypertext element containing the text.
function H.italic(str, noescape)
return addtag("i", {}, str, noescape)
end
--- Create a hypertext element with text formatted as header.
-- @tparam string str The content of the element.
-- @tparam[opt] boolean noescape Whether to avoid escaping the content.
-- @treturn string The hypertext element containing the text.
function H.header(str, noescape)
return H.bold(str, noescape)
end
--- Create a hypertext element with text formatted as an item in a list.
-- @tparam string str The content of the element.
-- @tparam[opt] boolean noescape Whether to avoid escaping the content.
-- @treturn string The hypertext element containing the text.
function H.listitem(str, noescape)
return "" .. H.plain(str, noescape)
end
--- Create a hypertext element with an action.
-- @tparam string action The name of the action.
-- @tparam string str The content of the element.
-- @tparam[opt] boolean noescape Whether to avoid escaping the content.
-- @treturn string The hypertext element containing the text.
function H.action(action, str, noescape)
return addtag("action", {name = action},
addtag("style", {color = "cyan"}, str, noescape), true)
end
--- Describe a soundspec.
-- @tparam[opt] string action The name of the action.
-- @tparam SimpleSoundSpec soundspec The soundspec to describe.
-- @treturn string The hypertext element describing the soundspec.
function H.describe_sound(action, soundspec)
if not soundspec then
return S("Undefined")
end
local sounddesc = H.mono(soundspec.name)
if action then
sounddesc = H.action(action, sounddesc, true)
end
return sounddesc
end
return H

776
init.lua
View File

@ -1,147 +1,16 @@
--- Entry point.
-- @module advtrains_doc_integration
local worldpath = minetest.get_worldpath() .. "/"
local worldpath = minetest.get_worldpath()
local modpath = minetest.get_modpath("advtrains_doc_integration")
local S = minetest.get_translator("advtrains_doc_integration")
local fsescape = minetest.formspec_escape
advtrains_doc_integration = {}
for _, m in ipairs{"mathutils", "describe", "utils"} do
for _, m in ipairs{
"mathutils", "utils", "describe", "hypertext", "latex",
} do
advtrains_doc_integration[m] = dofile(("%s/%s.lua"):format(modpath, m))
end
local D = advtrains_doc_integration.describe
local function S2(a, b)
return S(a, S(b))
end
local function Ss(x)
if type(x) ~= "string" then
return x
end
return minetest.get_translated_string("en", x)
end
local function txescape(str)
return (string.gsub(tostring(str), "[:^\\]", [[\%1]]))
end
local function htescape(str)
return (string.gsub(tostring(str), "([<>])", [[\%1]])) -- clip to one result
end
local function latex_escape(str)
return (string.gsub(str, ".", {
["&"] = [[\&]],
["%"] = [[\%]],
["$"] = [[\$]],
["#"] = [[\#]],
["_"] = [[\_]],
["{"] = [[\{]],
["}"] = [[\}]],
["~"] = [[\textasciitilde]],
["^"] = [[\textasciicircum]],
["\\"] = [[\textbackslash]],
}))
end
local function SL(x)
return latex_escape(Ss(x))
end
local function spairs(tbl, sort)
local keys = {}
local kn = {}
for k in pairs(tbl or {}) do
table.insert(keys, k)
end
table.sort(keys, sort)
for i = 2, #keys do
kn[keys[i-1]] = keys[i]
end
return function(t, n)
local k = kn[n]
if n == nil then
k = keys[1]
end
return k, t[k]
end, tbl, nil
end
local function map(tbl, func)
local t = {}
for k, v in pairs(tbl or {}) do
t[k] = func(v)
end
return t
end
local function htmono(str)
return string.format("<mono>%s</mono>", htescape(str))
end
local function htbold(str)
return string.format("<b>%s</b>", htescape(str))
end
local function htheader(str)
return htbold(str)
end
local function htitem(str)
return "" .. str
end
local function htaction(action, str, noesc)
if not noesc then
str = htescape(str)
end
return string.format("<action name=%s><style color=cyan>%s</style></action>", action, str)
end
local function htsound(action, soundspec)
if not soundspec then
return S("Undefined")
end
local sounddesc = htmono(soundspec.name)
if action then
sounddesc = htaction(action, sounddesc, true)
end
return sounddesc
end
local function addlist(lst, tbl, title, fallback1, fallback2, mapf)
if not tbl then
if fallback2 then
table.insert(lst, fallback2)
elseif fallback2 == false and fallback1 then
table.insert(lst, fallback1)
end
elseif next(tbl) ~= nil then
table.insert(lst, title)
for k, v in pairs(tbl) do
if mapf then
k = mapf(k, v)
end
table.insert(lst, htitem(k))
end
elseif fallback1 then
table.insert(lst, fallback1)
end
end
local function get_coupler_name(n)
return advtrains.coupler_types[n] or n
end
local function ht_coupler_name(n)
local s = advtrains.coupler_types[n]
if s then
return htescape(s)
else
return htmono(n)
end
end
local utils = advtrains_doc_integration.utils
local function dlxtrains_livery_information(prototype)
if not dlxtrains then
@ -183,16 +52,16 @@ do -- helper for Marnack's Advtrains livery tools
return nil
end
local odef = template.overlays
local textures = map(template.base_textures, function(str) return {str} end)
for _, overlay in spairs(design.overlays) do
local textures = utils.map(template.base_textures, function(str) return {str} end)
for _, overlay in utils.spairs(design.overlays) do
local o = odef[overlay.id]
local t = textures[(o or {}).slot_idx]
if t and o then
local alpha = math.min(255, math.max(0, o.alpha or 255))
table.insert(t, string.format("(%s^[colorize:%s:%d)", txescape(o.texture), txescape(overlay.color), alpha))
table.insert(t, string.format("(%s^[colorize:%s:%d)", utils.texture_escape(o.texture), utils.texture_escape(overlay.color), alpha))
end
end
return map(textures, function(st) return table.concat(st, "^") end)
return utils.map(textures, function(st) return table.concat(st, "^") end)
end
local mt = {
__index = atliv,
@ -229,31 +98,9 @@ do -- helper for Marnack's Advtrains livery tools
end
end
local function list_itemstring(x)
local item = ItemStack(x)
if item:is_empty() then
return S("Emptyness")
end
return string.format("%s: %d", item:get_short_description(), item:get_count())
end
local function blankline(st)
return table.insert(st, "")
end
local prototype_cache = {}
advtrains_doc_integration.prototypes = prototype_cache -- ONLY FOR DEBUGGING
local function adjust_soundspec(spec)
if type(spec) == "string" then
spec = {name = spec}
end
if type(spec) == "table" and spec.name and spec.name ~= "" then
return spec
end
return nil
end
local function adjust_wagon_prototype(itemname, prototype)
local p = prototype_cache[itemname]
if p then
@ -264,7 +111,7 @@ local function adjust_wagon_prototype(itemname, prototype)
if p._doc_wagon_longdesc then
p.longdesc = p._long_wagon_longdesc
end
p.horn_sound = adjust_soundspec(p.horn_sound)
p.horn_sound = utils.adjust_soundspec(p.horn_sound)
local pax, driver = 0, 0
if p.seats and p.seat_groups then
for _, v in pairs(p.seats) do
@ -284,7 +131,7 @@ local function adjust_wagon_prototype(itemname, prototype)
for _, state in pairs {"open", "close"} do
local st = (prototype.doors or {})[state] or {}
local state_data = {
sound = adjust_soundspec(st.sound),
sound = utils.adjust_soundspec(st.sound),
}
p.doors[state] = state_data
end
@ -316,7 +163,7 @@ local function adjust_wagon_prototype(itemname, prototype)
local tx = table.copy(txbase)
local st = {basefile}
for _, l in ipairs(pdef.livery_stack.layers) do
table.insert(st, string.format("(%s^[colorize:%s)", txescape(components[l.component].texture_file), txescape(l.color)))
table.insert(st, string.format("(%s^[colorize:%s)", utils.texture_escape(components[l.component].texture_file), utils.texture_escape(l.color)))
end
tx[slot] = table.concat(st, "^")
table.insert(p.livery_textures, tx)
@ -333,545 +180,39 @@ local function adjust_wagon_prototype(itemname, prototype)
return p
end
local function doc_register_wagon(itemname)
local prototype = adjust_wagon_prototype(itemname, advtrains.wagon_prototypes[itemname])
prototype.name = itemname
minetest.override_item(itemname, {_doc_items_create_entry = false})
doc.add_entry("advtrains_wagons", itemname, {
name = ItemStack(itemname):get_short_description(),
data = prototype,
})
if doc.sub.identifier then
doc.sub.identifier.register_object(itemname, "advtrains_wagons", itemname)
end
local registered_on_prototype_loaded = {}
--- Register a callback that will be run when a prototype becomes available.
-- @tparam function callback The callback to run when the prototype
-- becomes available. The callback function is passed the itemname and the
-- prototype of the wagon.
function advtrains_doc_integration.register_on_prototype_loaded(callback)
table.insert(registered_on_prototype_loaded, callback)
end
local wlivprev = {}
local function get_livery_preview_selection(pname, itemname)
return (wlivprev[pname] or {})[itemname] or 0
end
local function get_livery_preview(itemname, id)
local tx = (prototype_cache[itemname] or {}).livery_textures
return tx[id] or tx[0]
end
local function render_livery_textures(pname, itemname)
local str = table.concat(map(get_livery_preview(itemname, get_livery_preview_selection(pname, itemname)), fsescape), ",")
return str
end
local function set_livery_preview_selection(pname, itemname, id)
local t = wlivprev[pname]
if not t then
t = {}
wlivprev[pname] = t
end
t[itemname] = id
end
local function doc_render_wagon_information(prototype, pname)
local desctext = {}
if prototype._doc_wagon_longdesc then
table.insert(desctext, tostring(prototype._doc_wagon_longdesc))
blankline(desctext)
end
table.insert(desctext, htheader(S("Basic Information")))
table.insert(desctext, S("Itemstring: @1", htmono(prototype.name)))
addlist(desctext, prototype.drops, S("Drops:"), S("Drops nothing"), false, function(_, v) return list_itemstring(v) end)
addlist(desctext, prototype.drives_on, S("Drives on:"), nil, nil, htmono)
addlist(desctext, prototype.coupler_types_front, S("Compatible front couplers:"), S2("Front coupler: @1", "Absent"), S2("Front coupler: @1", "Universal"), ht_coupler_name)
addlist(desctext, prototype.coupler_types_back, S("Compatible rear couplers:"), S2("Rear coupler: @1", "Absent"), S2("Rear coupler: @1", "Universal"), ht_coupler_name)
table.insert(desctext, S("Wagon span: @1", prototype.wagon_span and D.length(2*prototype.wagon_span) or S("Undefined")))
table.insert(desctext, S("Maximum speed: @1", prototype.max_speed and D.speed(prototype.max_speed) or S("Undefined")))
table.insert(desctext, S2("Motive power: @1", prototype.is_locomotive and "Present" or "Absent"))
table.insert(desctext, S("Horn sound: @1", htsound("playhorn", prototype.horn_sound)))
if prototype.doors.open.sound or prototype.doors.close.sound then
table.insert(desctext, S("Door sound: @1 (when opening), @2 (when closing)",
htsound("playdooropen", prototype.doors.open.sound),
htsound("playdoorclose", prototype.doors.close.sound)))
else
table.insert(desctext, S2("Door sound: @1", "Undefined"))
end
blankline(desctext)
table.insert(desctext, htheader(S("Wagon Capacity")))
table.insert(desctext, S("Passenger seats: @1", prototype.max_passengers))
table.insert(desctext, S("Driver seats: @1", prototype.max_drivers))
if prototype.has_inventory then
addlist(desctext, prototype.inventory_list_sizes, S("Cargo inventory size:"), S2("Cargo inventory: @1", "Present"), false, function(k, v)
return string.format("%s: %d", htmono(k), v)
end)
else
table.insert(desctext, S2("Cargo inventory: @1", "Absent"))
end
if techage and prototype.techage_liquid_capacity then
table.insert(desctext, S("Liquid Capacity (Techage): @1", string.format("%d", prototype.techage_liquid_capacity)))
end
blankline(desctext)
table.insert(desctext, htheader(S("Wagon Appearance")))
table.insert(desctext, S("Mesh: @1", prototype.mesh and htmono(prototype.mesh) or "None"))
addlist(desctext, prototype.textures, S("Textures:"), S("No textures"), false, function(_, v) return htmono(v) end)
local livsel = get_livery_preview_selection(pname, prototype.name)
local livrst = (livsel ~= 0 and not prototype.dlxtrains_livery) and " " .. htaction("preview_0", S("[Reset Preview]")) or ""
local livids = 0
local function livprev(desc)
livids = livids+1
local label = htescape(desc)
if livids == livsel then
label = htbold(desc)
end
return htaction(string.format("preview_%d", livids), label, true)
end
local dlxlivdef = prototype.dlxtrains_livery
table.insert(desctext, S2("DlxTrains livery system: @1", dlxlivdef and "Supported" or "Unsupported"))
if dlxlivdef then
livids = -1
table.insert(desctext, "Livery presets:")
for k = 0, dlxlivdef.count-1 do
table.insert(desctext, htitem(string.format("%s (%s, %s)", htmono(dlxlivdef[k].code), livprev(S("default")), livprev(S("weathered")))))
minetest.register_on_mods_loaded(function()
for itemname, prototype in pairs(advtrains.wagon_prototypes) do
prototype = adjust_wagon_prototype(itemname, prototype)
prototype.name = itemname
for _, cb in ipairs(registered_on_prototype_loaded) do
cb(itemname, prototype)
end
end
end)
local bikeliv = S("Unsupported")
local bikelivdesc = nil
if prototype.set_livery then
if prototype.livery_definition then
bikeliv = S("Supported by the multi_component_liveries mod")
bikelivdesc = {}
addlist(bikelivdesc, prototype.livery_definition.components, S("Livery components:"), nil, nil, function(_, v) return htescape(v.description) end)
addlist(bikelivdesc, prototype.livery_definition.presets, S("Livery presets:") .. livrst, nil, nil, function(_, v) return livprev(v.description) end)
bikelivdesc = table.concat(bikelivdesc, "\n")
else
bikeliv = S("Supported")
end
end
table.insert(desctext, S("Livery system with bike painter: @1", bikeliv))
table.insert(desctext, bikelivdesc)
local atlivdef = prototype.advtrains_livery_tools
table.insert(desctext, S2("Advtrains livery tools (Marnack): @1", atlivdef and "Supported" or "Unsupported"))
if atlivdef then
addlist(desctext, atlivdef.template_names, S("Livery templates:"), nil, nil, function(_, v) return htescape(v) end)
addlist(desctext, atlivdef.livery_names, S("Livery presets:") .. livrst, nil, nil, function(_, v) return livprev(v) end)
end
blankline(desctext)
table.insert(desctext, htheader(S("Implementation Details")))
local attachment_offset_support = S("Unsupported")
if advtrains_attachment_offset_patch then
local t = advtrains_attachment_offset_patch
if prototype.get_on == t.get_on_override and prototype.get_off == t.get_off_override then
attachment_offset_support = S("Supported")
end
end
table.insert(desctext, S("Proper player attachment positioning: @1", attachment_offset_support))
for k, v in pairs {
custom_on_activate = "Custom instantiation callback",
custom_on_step = "Custom step function",
custom_on_velocity_change = "Custom velocity change callback",
} do
table.insert(desctext, S2(v .. ": @1", prototype[k] and "Defined" or "Undefined"))
end
local x0, y0 = doc.FORMSPEC.ENTRY_START_X+0.25, doc.FORMSPEC.ENTRY_START_Y
local x1, y1 = doc.FORMSPEC.ENTRY_END_X+0.75, doc.FORMSPEC.ENTRY_END_Y+0.625
local width, height = x1-x0, y1-y0
local mside = height/2
local mesh = fsescape(prototype.mesh or "")
local textures = render_livery_textures(pname, prototype.name)
local fstext = {
string.format("hypertext[%f,%f;%f,%f;entry_body;%s]", x0, y0, width-mside, height+0.875, fsescape(table.concat(desctext, "\n"))),
string.format("item_image[%f,%f;%f,%f;%s]", x1-mside, y0+0.0625, mside, mside, fsescape(prototype.name)),
string.format("model[%f,%f;%f,%f;%s;%s;%s;%f,%f]",
x1-mside, y1-mside, mside, mside, "wagon_model", mesh, textures, -30, 135),
}
return table.concat(fstext, "\n")
end
if doc then
minetest.register_on_mods_loaded(function()
for k in pairs(advtrains.wagon_prototypes) do
doc_register_wagon(k)
end
end)
if doc.sub.items then
local register_factoid = doc.sub.items.register_factoid
local function ndef_field_factoid(cat, ftype, f, ...)
local ftp = type(f)
if ftp == "string" then
local desc = f
f = function(x)
if x ~= nil then
return desc
end
end
end
local keys = {...}
local idx = function(t)
for _, k in ipairs(keys) do
if type(t) ~= "table" then
return nil
end
t = t[k]
end
return t
end
local function fgen(_, def)
return f(idx(def)) or ""
end
return register_factoid(cat, ftype, fgen)
end
local function group_factoid(cat, gr, f)
local function func(x)
return f(x or 0)
end
return ndef_field_factoid(cat, "groups", func, "groups", gr)
end
for cat, cinfo in pairs{
nodes = {
not_blocking_trains = S("This block does not block trains."),
save_in_at_nodedb = S("This block is saved in the Advtrains node database."),
},
} do
for group, ginfo in pairs(cinfo) do
local tp = type(ginfo)
if tp == "string" then
group_factoid(cat, group, function(x)
if x > 0 then
return ginfo
end
end)
elseif tp == "function" then
group_factoid(cat, group, ginfo)
end
end
end
for fname, t in pairs {
advtrains = {
on_train_enter = S("This track reacts to passing trains."),
on_train_approach = S("This track reacts to approaching trains."),
},
luaautomation = {
fire_event = S("This block handles LuaATC events."),
},
} do
for subfname, val in pairs(t) do
local function f(x)
if x ~= nil then
return val
end
end
ndef_field_factoid("nodes", "groups", f, fname, subfname)
end
end
register_factoid("nodes", "groups", function(_, ndef)
if ndef.advtrains then
local atdef = ndef.advtrains
if atdef.set_aspect then
return S("This is a signal with a variable aspect.")
elseif atdef.get_aspect then
return S("This is a signal with a static aspect.")
end
end
if ndef.at_conns then
return S("This track has the following conns table by default: @1", D.conns(ndef.at_conns) or "?")
end
return ""
end)
end
doc.add_category("advtrains_wagons", {
name = S("Wagons"),
build_formspec = doc_render_wagon_information,
})
end
local function latex_colordesc(cstr)
local color = string.match(cstr,"^#(%x%x%x%x%x%x)$")
cstr = SL(cstr)
if color then
color = SL(string.upper(color))
return string.format([[\tikz \definecolor{c}{HTML}{%s} \draw[fill=c] (0,0) rectangle (1em,1em); \texttt{%s}]], color, cstr)
else
return string.format([[\texttt{%s}]], cstr)
end
end
--- Write a wagon datasheet to a LaTeX file in the world path.
-- @tparam string itemname The item name of the wagon prototype.
function advtrains_doc_integration.write_wagon_info_as_latex(itemname)
local filename = string.format("%satdoc_wagon_%s.tex", worldpath, itemname:gsub(":", "_"))
local prototype = adjust_wagon_prototype(itemname, advtrains.wagon_prototypes[itemname])
local wname = ItemStack(itemname):get_short_description()
local st = {string.format([[
\documentclass{article}
\usepackage[a4paper,margin=1in,bottom=1.5in]{geometry}
\usepackage[T1]{fontenc}
\usepackage{tikz}
\usepackage{booktabs,multirow,tabularx}
\renewcommand{\arraystretch}{1.5}
\usepackage{hyperref}
\hypersetup{pdftitle={Wagon Datasheet: %s}}
\title{Wagon Datasheet}
\author{%s}
\setlength{\parindent}{0pt}
\begin{document}
\maketitle
]], SL(wname), SL(wname))}
table.insert(st, [[\section{Basic Information}]])
if prototype.longdesc then
table.insert(st, SL(prototype.longdesc) .. "\n")
local filename = string.format("%s/atdoc_wagon_%s.tex", worldpath, itemname:gsub(":", "_"))
local description = advtrains_doc_integration.latex.describe_wagon_prototype(itemname)
if description then
minetest.safe_file_write(filename, description)
end
table.insert(st, [[\begin{tabularx}{\textwidth}{l X}]])
table.insert(st, string.format([[Itemstring & \texttt{%s}\\]], SL(itemname)))
if prototype.drives_on then
local i0 = #st+1
local count = 0
for k in pairs(prototype.drives_on) do
table.insert(st, string.format([[& \texttt{%s}\\]], SL(k)))
count = count + 1
end
if count > 0 then
st[i0] = string.format([[Drives on %s]], st[i0])
end
end
if prototype.wagon_span then
table.insert(st, string.format([[Wagon span & %d mm\\]], prototype.wagon_span*2000))
end
if prototype.max_speed then
table.insert(st, string.format([[Maximum speed & %d m/s\\]], prototype.max_speed))
end
table.insert(st, string.format([[Motive power & %s\\]], prototype.is_locomotive and "Present" or "Absent"))
if prototype.horn_sound then
table.insert(st, string.format([[Horn sound & \texttt{%s}\\]], SL(prototype.horn_sound.name)))
else
table.insert(st, [[Horn sound & Undefined\\]])
end
if prototype.mesh then
table.insert(st, string.format([[Mesh & \texttt{%s}\\]], SL(prototype.mesh)))
end
if prototype.textures then
local i0 = #st+1
local count = 0
for _, i in pairs(prototype.textures) do
table.insert(st, string.format([[& \texttt{%s}\\]], SL(i)))
count = count + 1
end
if count > 0 then
st[i0] = string.format([[Textures %s]], st[i0])
end
end
do
local i0 = #st+1
local count = 0
for _, i in ipairs(prototype.drops or {}) do
local item = ItemStack(i)
if not item:is_empty() then
local desc = string.format([[\texttt{%s}]], SL(item:get_name()))
if item:is_known() then
desc = SL(item:get_short_description())
end
table.insert(st, string.format([[& %s: %d\\]], desc, item:get_count()))
count = count + 1
end
end
if count > 0 then
st[i0] = [[Drops ]] .. st[i0]
else
table.insert(st, [[Drops & Nothing \\]])
end
end
table.insert(st, [[\end{tabularx}]])
table.insert(st, [[\section{Coupler Compatibility}]])
do
local fcouplers = prototype.coupler_types_front
local rcouplers = prototype.coupler_types_back
local ccouplers = {}
local lcouplers = {}
local couplerid = {}
local flim, rlim
for k in pairs(fcouplers or {}) do
flim = true
ccouplers[k] = true
end
for k in pairs(rcouplers or {}) do
rlim = true
ccouplers[k] = true
end
for k in pairs(ccouplers) do
local desc = SL(get_coupler_name(k))
table.insert(lcouplers, desc)
couplerid[desc] = k
end
table.sort(lcouplers)
table.insert(st, [[
\begin{tabularx}{\textwidth}{X c c}
\toprule
\multirow[t]{2}{*}{\bfseries Coupler Type} & \multicolumn{2}{c}{\bfseries Compatibility}\\
\cmidrule(lr){2-3}
& {\bfseries Front Coupler} & {\bfseries Rear Coupler}\\\midrule
]])
if not (fcouplers and rcouplers) then
local fd = fcouplers and "" or [[$\bullet$]]
local rd = rcouplers and "" or [[$\bullet$]]
table.insert(st, string.format([[\textit{Universal}&%s&%s\\]], fd, rd))
end
for i = 1, #lcouplers do
local cdesc = lcouplers[i]
local cid = couplerid[cdesc]
local fd, rd = "", ""
if flim then
fd = fcouplers[cid] and [[$\bullet$]] or ""
elseif not fcouplers then
fd = [[$\Uparrow$]]
end
if rlim then
rd = rcouplers[cid] and [[$\bullet$]] or ""
elseif not rcouplers then
rd = [[$\Uparrow$]]
end
table.insert(st, string.format([[%s&%s&%s\\]], cdesc, fd, rd))
end
table.insert(st, [[\bottomrule]])
table.insert(st, [[\end{tabularx}]])
end
local hasinv = prototype.has_inventory
local hasseats = prototype.max_seats>0
local taliquid = prototype.techage_liquid_capacity or 0
if hasinv or hasseats or taliquid>0 then
table.insert(st, [[\section{Wagon Capacity}]])
if hasseats then
table.insert(st, [[
\begin{tabularx}{\textwidth}{X c c}
\toprule
{\bfseries Seat Group} & {\bfseries Driver Stand} & {\bfseries Seat Count}\\\midrule
]])
for _, d in pairs(prototype.seat_groups) do
table.insert(st, string.format([[%s & %s & %d\\]], SL(d.name), d.driving_ctrl_access and [[$\bullet$]] or "", d.count))
end
table.insert(st, [[\bottomrule]])
table.insert(st, [[\end{tabularx}]])
end
if hasinv then
if next(prototype.inventory_list_sizes or {}) ~= nil then
table.insert(st, [[
\begin{tabularx}{\textwidth}{X c}
\toprule
{\bfseries Inventory Name} & {\bfseries Capacity}\\\midrule
]])
for k, v in pairs(prototype.inventory_list_sizes) do
table.insert(st, string.format([[\texttt{%s} & %d\\]], SL(k), v))
end
table.insert(st, [[\bottomrule]])
table.insert(st, [[\end{tabularx}]])
else
table.insert(st, [[This wagon has an inventory of unknown size.]])
end
end
if taliquid > 0 then
table.insert(st, string.format([[
\begin{tabularx}{\textwidth}{X l}
{Liquid Capacity (Techage)} & %d
\end{tabularx}
]], taliquid))
end
end
if prototype.set_livery then
if prototype.livery_definition then
table.insert(st, [[\section{Multi-Component Liveries}]])
local components = prototype.livery_definition.components
local presets = prototype.livery_definition.presets
table.insert(st, [[\subsection*{Components}]])
table.insert(st, [[\begin{itemize}]])
for _, c in ipairs(components) do
table.insert(st, string.format([[\item %s]], SL(c.description)))
end
table.insert(st, [[\end{itemize}]])
for _, p in ipairs(presets) do
table.insert(st, string.format([[\subsection*{Preset: %s}]], SL(p.description)))
table.insert(st, [[
\begin{tabularx}{\textwidth}{X c}
\toprule
{\bfseries Component} & {\bfseries Color} \\\midrule
]])
for _, c in ipairs(p.livery_stack.layers) do
local cdesc = SL(components[c.component].description)
table.insert(st, string.format([[%s & %s\\]], cdesc, latex_colordesc(c.color)))
end
table.insert(st, [[
\bottomrule
\end{tabularx}
]])
end
else
table.insert(st, [[\section{Livery System (Bike Painter)}]])
table.insert(st, [[This wagon can be painted by the bike painter.]])
end
end
local dlxlivdef = dlxtrains_livery_information(prototype)
if dlxlivdef then
table.insert(st, [[
\section{DlxTrains Livery Sytem}
This wagon can be customized with DlxTrains' livery system.
]])
end
local atlivdef = advtrains_livery_tools_information(itemname)
if atlivdef then
table.insert(st, [[\section{Advtrains Livery Tool (Marnack)}]])
for _, tname in ipairs(atlivdef.template_names) do
local tdef = atlivdef.templates[tname]
table.insert(st, string.format([[\subsection*{Template: %s}]], SL(tname)))
table.insert(st, SL(tdef.notes))
table.insert(st, "")
table.insert(st, "This template contains the following components:")
table.insert(st, [[\begin{itemize}]])
for _, overlay in ipairs(tdef.overlays) do
table.insert(st, string.format([[\item %s]], SL(overlay.name)))
end
table.insert(st, [[\end{itemize}]])
end
for _, lname in ipairs(atlivdef.livery_names) do
local ldef = atlivdef.liveries[lname]
local tname = ldef.livery_template_name
table.insert(st, string.format([[\subsection*{Preset: %s}]], SL(lname)))
table.insert(st, string.format([[Template: %s]], SL(tname)))
table.insert(st, "")
table.insert(st, [[
\begin{tabularx}{\textwidth}{X c}
\toprule
{\bfseries Component} & {\bfseries Color}\\\midrule]])
for _, overlay in pairs(ldef.overlays) do
local cname = atlivdef.templates[tname].overlays[overlay.id].name
table.insert(st, string.format([[%s & %s\\]], SL(cname), latex_colordesc(overlay.color)))
end
table.insert(st, [[
\bottomrule
\end{tabularx}
]])
end
end
table.insert(st, [[
\end{document}
]])
st = table.concat(st, "\n")
minetest.safe_file_write(filename, st)
end
--- Write a wagon datasheet for each wagon using @{write_wagon_info_as_latex}.
function advtrains_doc_integration.write_all_wagons_as_latex()
for k in pairs(advtrains.wagon_prototypes) do
for k in pairs(prototype_cache) do
advtrains_doc_integration.write_wagon_info_as_latex(k)
end
end
@ -885,50 +226,7 @@ minetest.register_chatcommand("atdoc_write", {
end,
})
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= "doc:entry" then
return
end
local pname = player:get_player_name()
local cat, ent = doc.get_selection(pname)
if cat ~= "advtrains_wagons" or ent == nil then
return
end
local act = fields.entry_body
local prototype = prototype_cache[ent]
local sounds = {
["action:playhorn"] = prototype.horn_sound,
["action:playdooropen"] = prototype.doors.open.sound,
["action:playdoorclose"] = prototype.doors.close.sound,
}
if not act then
return
elseif sounds[act] then
minetest.sound_play(sounds[act], {to_player = pname}, true)
else
local txid = string.match(act, [[^action:preview_(%d+)$]])
txid = tonumber(txid)
if txid then
set_livery_preview_selection(pname, ent, txid)
doc.show_entry(pname, cat, ent)
end
end
end)
dofile(modpath .. "/doc_ui.lua")
if minetest.get_modpath("mtt") then
if doc then
mtt.register("wagon information formspec", function(cb)
local player = mtt.join_player("singleplayer")
for k in pairs(prototype_cache) do
print("Found wagon prototype: " .. k)
doc.show_entry("singleplayer", "advtrains_wagons", k, true)
end
player:leave()
cb()
end)
end
mtt.register("wagon datasheet generation", function(cb)
advtrains_doc_integration.write_all_wagons_as_latex()
cb()
end)
dofile(modpath .. "/mtt.lua")
end

316
latex.lua Normal file
View File

@ -0,0 +1,316 @@
--- Code related to LaTeX generation.
-- @module advtrains_doc_integration.latex
local latex = {}
local utils = advtrains_doc_integration.utils
--- Escape string in LaTeX.
-- @tparam string str The string to escape.
-- @treturn string The escaped string.
function latex.escape(str)
return (string.gsub(str, ".", {
["&"] = [[\&]],
["%"] = [[\%]],
["$"] = [[\$]],
["#"] = [[\#]],
["_"] = [[\_]],
["{"] = [[\{]],
["}"] = [[\}]],
["~"] = [[\textasciitilde]],
["^"] = [[\textasciicircum]],
["\\"] = [[\textbackslash]],
}))
end
--- Escape a translated string in LaTeX.
-- @tparam string lang The language to use for server-side translation
-- @tparam string str The string to escape.
-- @treturn The escaped string.
function latex.escape_translated(lang, str)
return latex.escape(minetest.get_translated_string(lang, str))
end
local function SL(str)
return latex.escape_translated("en", str)
end
--- Describe a color in LaTeX.
-- @tparam string cstr The color string.
-- @treturn string The string describing the color.
function latex.describe_color(cstr)
local color = string.match(cstr,"^#(%x%x%x%x%x%x)$")
cstr = SL(cstr)
if color then
color = SL(string.upper(color))
return string.format([[\tikz \definecolor{c}{HTML}{%s} \draw[fill=c] (0,0) rectangle (1em,1em); \texttt{%s}]], color, cstr)
else
return string.format([[\texttt{%s}]], cstr)
end
end
--- Describe a wagon prototype in LaTeX.
-- @tparam string itemname The item name of the wagon prototype.
-- @treturn string The description of the prototype.
function latex.describe_wagon_prototype(itemname)
local prototype = advtrains_doc_integration.prototypes[itemname]
local wname = ItemStack(itemname):get_short_description()
local st = {string.format([[
\documentclass{article}
\usepackage[a4paper,margin=1in,bottom=1.5in]{geometry}
\usepackage[T1]{fontenc}
\usepackage{tikz}
\usepackage{booktabs,multirow,tabularx}
\renewcommand{\arraystretch}{1.5}
\usepackage{hyperref}
\hypersetup{pdftitle={Wagon Datasheet: %s}}
\title{Wagon Datasheet}
\author{%s}
\setlength{\parindent}{0pt}
\begin{document}
\maketitle
]], SL(wname), SL(wname))}
table.insert(st, [[\section{Basic Information}]])
if prototype.longdesc then
table.insert(st, SL(prototype.longdesc) .. "\n")
end
table.insert(st, [[\begin{tabularx}{\textwidth}{l X}]])
table.insert(st, string.format([[Itemstring & \texttt{%s}\\]], SL(itemname)))
if prototype.drives_on then
local i0 = #st+1
local count = 0
for k in pairs(prototype.drives_on) do
table.insert(st, string.format([[& \texttt{%s}\\]], SL(k)))
count = count + 1
end
if count > 0 then
st[i0] = string.format([[Drives on %s]], st[i0])
end
end
if prototype.wagon_span then
table.insert(st, string.format([[Wagon span & %d mm\\]], prototype.wagon_span*2000))
end
if prototype.max_speed then
table.insert(st, string.format([[Maximum speed & %d m/s\\]], prototype.max_speed))
end
table.insert(st, string.format([[Motive power & %s\\]], prototype.is_locomotive and "Present" or "Absent"))
if prototype.horn_sound then
table.insert(st, string.format([[Horn sound & \texttt{%s}\\]], SL(prototype.horn_sound.name)))
else
table.insert(st, [[Horn sound & Undefined\\]])
end
if prototype.mesh then
table.insert(st, string.format([[Mesh & \texttt{%s}\\]], SL(prototype.mesh)))
end
if prototype.textures then
local i0 = #st+1
local count = 0
for _, i in pairs(prototype.textures) do
table.insert(st, string.format([[& \texttt{%s}\\]], SL(i)))
count = count + 1
end
if count > 0 then
st[i0] = string.format([[Textures %s]], st[i0])
end
end
do
local i0 = #st+1
local count = 0
for _, i in ipairs(prototype.drops or {}) do
local item = ItemStack(i)
if not item:is_empty() then
local desc = string.format([[\texttt{%s}]], SL(item:get_name()))
if item:is_known() then
desc = SL(item:get_short_description())
end
table.insert(st, string.format([[& %s: %d\\]], desc, item:get_count()))
count = count + 1
end
end
if count > 0 then
st[i0] = [[Drops ]] .. st[i0]
else
table.insert(st, [[Drops & Nothing \\]])
end
end
table.insert(st, [[\end{tabularx}]])
table.insert(st, [[\section{Coupler Compatibility}]])
do
local fcouplers = prototype.coupler_types_front
local rcouplers = prototype.coupler_types_back
local ccouplers = {}
local lcouplers = {}
local couplerid = {}
local flim, rlim
for k in pairs(fcouplers or {}) do
flim = true
ccouplers[k] = true
end
for k in pairs(rcouplers or {}) do
rlim = true
ccouplers[k] = true
end
for k in pairs(ccouplers) do
local desc = SL(utils.get_coupler_name(k))
table.insert(lcouplers, desc)
couplerid[desc] = k
end
table.sort(lcouplers)
table.insert(st, [[
\begin{tabularx}{\textwidth}{X c c}
\toprule
\multirow[t]{2}{*}{\bfseries Coupler Type} & \multicolumn{2}{c}{\bfseries Compatibility}\\
\cmidrule(lr){2-3}
& {\bfseries Front Coupler} & {\bfseries Rear Coupler}\\\midrule
]])
if not (fcouplers and rcouplers) then
local fd = fcouplers and "" or [[$\bullet$]]
local rd = rcouplers and "" or [[$\bullet$]]
table.insert(st, string.format([[\textit{Universal}&%s&%s\\]], fd, rd))
end
for i = 1, #lcouplers do
local cdesc = lcouplers[i]
local cid = couplerid[cdesc]
local fd, rd = "", ""
if flim then
fd = fcouplers[cid] and [[$\bullet$]] or ""
elseif not fcouplers then
fd = [[$\Uparrow$]]
end
if rlim then
rd = rcouplers[cid] and [[$\bullet$]] or ""
elseif not rcouplers then
rd = [[$\Uparrow$]]
end
table.insert(st, string.format([[%s&%s&%s\\]], cdesc, fd, rd))
end
table.insert(st, [[\bottomrule]])
table.insert(st, [[\end{tabularx}]])
end
local hasinv = prototype.has_inventory
local hasseats = prototype.max_seats>0
local taliquid = prototype.techage_liquid_capacity or 0
if hasinv or hasseats or taliquid>0 then
table.insert(st, [[\section{Wagon Capacity}]])
if hasseats then
table.insert(st, [[
\begin{tabularx}{\textwidth}{X c c}
\toprule
{\bfseries Seat Group} & {\bfseries Driver Stand} & {\bfseries Seat Count}\\\midrule
]])
for _, d in pairs(prototype.seat_groups) do
table.insert(st, string.format([[%s & %s & %d\\]], SL(d.name), d.driving_ctrl_access and [[$\bullet$]] or "", d.count))
end
table.insert(st, [[\bottomrule]])
table.insert(st, [[\end{tabularx}]])
end
if hasinv then
if next(prototype.inventory_list_sizes or {}) ~= nil then
table.insert(st, [[
\begin{tabularx}{\textwidth}{X c}
\toprule
{\bfseries Inventory Name} & {\bfseries Capacity}\\\midrule
]])
for k, v in pairs(prototype.inventory_list_sizes) do
table.insert(st, string.format([[\texttt{%s} & %d\\]], SL(k), v))
end
table.insert(st, [[\bottomrule]])
table.insert(st, [[\end{tabularx}]])
else
table.insert(st, [[This wagon has an inventory of unknown size.]])
end
end
if taliquid > 0 then
table.insert(st, string.format([[
\begin{tabularx}{\textwidth}{X l}
{Liquid Capacity (Techage)} & %d
\end{tabularx}
]], taliquid))
end
end
if prototype.set_livery then
if prototype.livery_definition then
table.insert(st, [[\section{Multi-Component Liveries}]])
local components = prototype.livery_definition.components
local presets = prototype.livery_definition.presets
table.insert(st, [[\subsection*{Components}]])
table.insert(st, [[\begin{itemize}]])
for _, c in ipairs(components) do
table.insert(st, string.format([[\item %s]], SL(c.description)))
end
table.insert(st, [[\end{itemize}]])
for _, p in ipairs(presets) do
table.insert(st, string.format([[\subsection*{Preset: %s}]], SL(p.description)))
table.insert(st, [[
\begin{tabularx}{\textwidth}{X c}
\toprule
{\bfseries Component} & {\bfseries Color} \\\midrule
]])
for _, c in ipairs(p.livery_stack.layers) do
local cdesc = SL(components[c.component].description)
table.insert(st, string.format([[%s & %s\\]], cdesc, latex.describe_color(c.color)))
end
table.insert(st, [[
\bottomrule
\end{tabularx}
]])
end
else
table.insert(st, [[\section{Livery System (Bike Painter)}]])
table.insert(st, [[This wagon can be painted by the bike painter.]])
end
end
local dlxlivdef = prototype.dlxtrains_livery
if dlxlivdef then
table.insert(st, [[
\section{DlxTrains Livery Sytem}
This wagon can be customized with DlxTrains' livery system.
]])
end
local atlivdef = prototype.advtrains_livery_tools
if atlivdef then
table.insert(st, [[\section{Advtrains Livery Tool (Marnack)}]])
for _, tname in ipairs(atlivdef.template_names) do
local tdef = atlivdef.templates[tname]
table.insert(st, string.format([[\subsection*{Template: %s}]], SL(tname)))
table.insert(st, SL(tdef.notes))
table.insert(st, "")
table.insert(st, "This template contains the following components:")
table.insert(st, [[\begin{itemize}]])
for _, overlay in ipairs(tdef.overlays) do
table.insert(st, string.format([[\item %s]], SL(overlay.name)))
end
table.insert(st, [[\end{itemize}]])
end
for _, lname in ipairs(atlivdef.livery_names) do
local ldef = atlivdef.liveries[lname]
local tname = ldef.livery_template_name
table.insert(st, string.format([[\subsection*{Preset: %s}]], SL(lname)))
table.insert(st, string.format([[Template: %s]], SL(tname)))
table.insert(st, "")
table.insert(st, [[
\begin{tabularx}{\textwidth}{X c}
\toprule
{\bfseries Component} & {\bfseries Color}\\\midrule]])
for _, overlay in pairs(ldef.overlays) do
local cname = atlivdef.templates[tname].overlays[overlay.id].name
table.insert(st, string.format([[%s & %s\\]], SL(cname), latex.describe_color(overlay.color)))
end
table.insert(st, [[
\bottomrule
\end{tabularx}
]])
end
end
table.insert(st, [[
\end{document}
]])
return table.concat(st, "\n")
end
return latex

View File

@ -15,6 +15,8 @@ local function gcdmain(x, y, ...)
end
--- Compute the greatest common divider.
-- Note that the behavior of this function is undefined if the arguments
-- include infinity or NaN.
-- @tparam integer ... The numbers used for the computation.
-- @treturn integer The GCD of the given numbers.
function M.gcd(...)

15
mtt.lua Normal file
View File

@ -0,0 +1,15 @@
if doc then
mtt.register("wagon information formspec", function(cb)
local player = mtt.join_player("singleplayer")
for k in pairs(advtrains_doc_integration.prototypes) do
print("Found wagon prototype: " .. k)
doc.show_entry("singleplayer", "advtrains_wagons", k, true)
end
player:leave()
cb()
end)
end
mtt.register("wagon datasheet generation", function(cb)
advtrains_doc_integration.write_all_wagons_as_latex()
cb()
end)

View File

@ -1,13 +1,13 @@
--- Utility functions.
-- @module advtrains_doc_integration.utils
-- @alias M
local M = {}
-- @alias utils
local utils = {}
--- Create a table by applying a function to each element.
-- @tparam table tbl The table to map from.
-- @tparam function func The function to apply.
-- @treturn table The resulting table.
function M.map(tbl, func)
function utils.map(tbl, func)
local t = {}
for k, v in pairs(tbl or {}) do
t[k] = func(v)
@ -15,4 +15,58 @@ function M.map(tbl, func)
return t
end
return M
--- Create an iterator that iterates through the table in the order of
-- the keys sorted in a certain order.
-- Note that the behavior is undefined if a key is added during the iteration.
-- @tparam table tbl The table to iterate
-- @tparam[opt] function sort The function passed to @{table.sort} for
-- sorting the keys. The default sorting order is used if the function
-- is not provided.
-- @return An iterator suitable for use with Lua's `for` loop.
function utils.spairs(tbl, sort)
local keys = {}
local kn = {}
for k in pairs(tbl or {}) do
table.insert(keys, k)
end
table.sort(keys, sort)
for i = 2, #keys do
kn[keys[i-1]] = keys[i]
end
return function(t, n)
local k = kn[n]
if n == nil then
k = keys[1]
end
return k, t[k]
end, tbl, nil
end
--- Gets the name of the coupler
-- @tparam string str The technical name of the coupler
-- @treturn string The name of the coupler
function utils.get_coupler_name(str)
return advtrains.coupler_types[str] or str
end
--- Adjust the soundspec to table form.
-- @tparam SimpleSoundSpec spec The soundspec to adjust.
-- @treturn SimpleSoundSpec The adjusted soundspec.
function utils.adjust_soundspec(spec)
if type(spec) == "string" then
spec = {name = spec}
end
if type(spec) == "table" and spec.name and spec.name ~= "" then
return spec
end
return nil
end
--- Escape the texture string.
-- @tparam string str The texture string to escape.
-- @treturn string The escaped texture string.
function utils.texture_escape(str)
return (string.gsub(tostring(str), "[:^\\]", [[\%1]]))
end
return utils