local S = minetest.get_translator("advtrains_doc_integration") local fsescape = minetest.formspec_escape local worldpath = minetest.get_worldpath() .. DIR_DELIM 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 latex_escape(str) return string.gsub(str, ".", { ["&"] = [[\&]], ["%"] = [[\%]], ["$"] = [[\$]], ["#"] = [[\#]], ["_"] = [[\_]], ["{"] = [[\{]], ["}"] = [[\}]], ["~"] = [[\textasciitilde]], ["^"] = [[\textasciicircum]], ["\\"] = [[\textbackslash]], }) end local function SL(x) return latex_escape(Ss(x)) 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 quotestring(str) return string.format("%q", str) end if doc then if doc.sub.items then local register_factoid = doc.sub.items.register_factoid local function group_factoid(cat, gr, f) register_factoid(cat, "groups", function(_, def) return f(def.groups[gr] or 0) or "" end) 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 register_factoid("nodes", "groups", function(_, ndef) if ndef.advtrains then if ndef.advtrains.set_aspect then return S("This is a signal with a variable aspect.") elseif ndef.advtrains.get_aspect then return S("This is a signal with a static aspect.") end end return "" end) end doc.add_category("advtrains_wagons", { name = S("Wagons"), build_formspec = doc.entry_builders.formspec, }) end local function describe_length(x) local inch = math.floor(x/0.0254) local ft = math.floor(inch/12) inch = inch%12 local st = {} if ft > 0 then table.insert(st, ft .. "'") end if inch > 0 then table.insert(st, inch .. '"') end if not next(st) then st = '0"' end return string.format("%.2f m (%s)", x, table.concat(st)) end local function describe_speed(x) local kmph = x*3.6 local mph = kmph/1.609344 return string.format("%.1f m/s (%.1f km/h; %.1f mph)", x, kmph, mph) 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, "* " .. 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 dlxtrains_livery_information(prototype) if not dlxtrains then return nil end local old_update_livery = dlxtrains.update_livery dlxtrains.update_livery = function(_, _, x) return coroutine.yield(x) end local env = { coroutine = coroutine, dlxtrains = table.copy(dlxtrains), } local function main(G, f) setfenv(0, G) f() return error() end local t = {coroutine.resume(coroutine.create(main), env, prototype.custom_may_destroy or function() end)} dlxtrains.update_livery = old_update_livery if not t[1] then return nil end return unpack(t, 2) 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 function adjust_wagon_prototype(prototype) local p = table.copy(prototype) if p._doc_wagon_longdesc then p.longdesc = p._long_wagon_longdesc end if type(p.horn_sound) == "string" then p.horn_sound = {name = prototype.horn_sound} end local pax, driver = 0, 0 if p.seats and p.seat_groups then for _, v in pairs(p.seats) do if p.seat_groups[v.group].driving_ctrl_access then driver = driver + 1 else pax = pax + 1 end p.seat_groups[v.group].count = (p.seat_groups[v.group].count or 0) + 1 end end p.max_passengers = pax p.max_drivers = driver return p end local function doc_register_wagon(itemname) local prototype = adjust_wagon_prototype(advtrains.wagon_prototypes[itemname]) local desctext = {} if prototype._doc_wagon_longdesc then table.insert(desctext, tostring(prototype._doc_wagon_longdesc)) blankline(desctext) end table.insert(desctext, S("Itemstring: @1", quotestring(itemname))) addlist(desctext, prototype.drops, S("Drops:"), S("Drops nothing"), false, function(_, v) return list_itemstring(v) end) blankline(desctext) addlist(desctext, prototype.drives_on, S("Drives on:")) addlist(desctext, prototype.coupler_types_front, S("Compatible front couplers:"), S2("Front coupler: @1", "Absent"), S2("Front coupler: @1", "Universal"), get_coupler_name) addlist(desctext, prototype.coupler_types_back, S("Compatible rear couplers:"), S2("Rear coupler: @1", "Absent"), S2("Rear coupler: @1", "Universal"), get_coupler_name) table.insert(desctext, S("Wagon span: @1", prototype.wagon_span and describe_length(2*prototype.wagon_span) or S("Undefined"))) table.insert(desctext, S("Maximum speed: @1", prototype.max_speed and describe_speed(prototype.max_speed) or S("Undefined"))) table.insert(desctext, S2("Motive power: @1", prototype.is_locomotive and "Present" or "Absent")) local hornsound = prototype.horn_sound table.insert(desctext, S("Horn sound: @1", hornsound and quotestring(hornsound.name) or S("Undefined"))) blankline(desctext) 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: %s", k, v) end) else table.insert(desctext, S2("Cargo inventory: @1", "Absent")) end blankline(desctext) table.insert(desctext, S("Mesh: @1", quotestring(prototype.mesh))) addlist(desctext, prototype.textures, S("Textures:"), S("No textures"), false, function(_, v) return quotestring(v) end) local bikeliv = S("Unsupported") local bikelivdesc = nil if prototype.set_livery then if multi_component_liveries and prototype.set_livery == multi_component_liveries.set_livery and 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 tostring(v.description) end) addlist(bikelivdesc, prototype.livery_definition.presets, S("Livery presets:"), nil, nil, function(_, v) return tostring(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 dlxlivdef = dlxtrains_livery_information(prototype) table.insert(desctext, S2("DlxTrains livery system: @1", dlxlivdef and "Supported" or "Unsupported")) 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 blankline(desctext) 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 = table.concat(map(prototype.textures, fsescape), ",") if dlxlivdef then textures = fsescape(string.format("%s_%s.png", dlxlivdef.filename_prefix, dlxlivdef[0].code)) end local fstext = { string.format("textarea[%f,%f;%f,%f;;;%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(itemname)), string.format("model[%f,%f;%f,%f;%s;%s;%s;%f,%f]", x1-mside, y1-mside, mside, mside, "wagon_model", mesh, textures, -30, 135), } minetest.override_item(itemname, {_doc_items_create_entry = false}) doc.add_entry("advtrains_wagons", itemname, { name = ItemStack(itemname):get_short_description(), data = table.concat(fstext), }) end if doc then for k in pairs(advtrains.wagon_prototypes) do doc_register_wagon(k) end end local _register_wagon = advtrains.register_wagon function advtrains.register_wagon(...) _register_wagon(...) local name = ... if not string.find(name, ":") then name = "advtrains:" .. name end if doc then doc_register_wagon(name) end end advtrains_doc_integration = {} 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(advtrains.wagon_prototypes[itemname]) local st = {string.format([[ \documentclass{article} \usepackage[a4paper,margin=1in,bottom=1.5in]{geometry} \usepackage[T1]{fontenc} \usepackage{tikz} %%\usepackage{xcolor} %%\usepackage{amssymb} \usepackage{booktabs,multirow,tabularx} \renewcommand{\arraystretch}{1.5} \title{Wagon Datasheet} \author{%s} \begin{document} \maketitle ]], SL(ItemStack(itemname):get_short_description()))} 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 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 lcouplers[1] then lcouplers = {[[Default]]} 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 i == 1 then fd = fcouplers and "" or "Universal" fd = string.format([[\multirow[t]{%d}{*}{%s}]], #lcouplers, fd) end if rlim then rd = rcouplers[cid] and [[$\bullet$]] or "" elseif i == 1 then rd = rcouplers and "" or "Universal" rd = string.format([[\multirow[t]{%d}{*}{%s}]], #lcouplers, rd) 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_passengers+prototype.max_drivers>0 if hasinv or hasseats 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 end if prototype.set_livery then if multi_component_liveries and prototype.set_livery == multi_component_liveries.set_livery and 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) local cstr = c.color local color = string.match(c.color,"^#(%x%x%x%x%x%x)$") cstr = SL(cstr) if color then color = SL(string.upper(color)) table.insert(st, string.format([[%s & \tikz \definecolor{c}{HTML}{%s} \draw[fill=c] (0,0) rectangle (1em,1em); \texttt{%s}\\]], cdesc, color, cstr)) else table.insert(st, string.format([[%s & \texttt{%s}\\]], cdesc, cstr)) end 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 table.insert(st, [[ \end{document} ]]) st = table.concat(st, "\n") minetest.safe_file_write(filename, st) end minetest.register_chatcommand("atdoc_write", { params = "", description = S("Export Advtrains-related information"), privs = {server = true}, func = function() for k in pairs(advtrains.wagon_prototypes) do advtrains_doc_integration.write_wagon_info_as_latex(k) end end, })