init.lua 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871
  1. local S = minetest.get_translator("advtrains_doc_integration")
  2. local fsescape = minetest.formspec_escape
  3. local worldpath = minetest.get_worldpath() .. DIR_DELIM
  4. advtrains_doc_integration = {}
  5. local function S2(a, b)
  6. return S(a, S(b))
  7. end
  8. local function Ss(x)
  9. if type(x) ~= "string" then
  10. return x
  11. end
  12. return minetest.get_translated_string("en", x)
  13. end
  14. local function txescape(str)
  15. return (string.gsub(tostring(str), "[:^\\]", [[\%1]]))
  16. end
  17. local function htescape(str)
  18. return (string.gsub(tostring(str), "([<>])", [[\%1]])) -- clip to one result
  19. end
  20. local function latex_escape(str)
  21. return (string.gsub(str, ".", {
  22. ["&"] = [[\&]],
  23. ["%"] = [[\%]],
  24. ["$"] = [[\$]],
  25. ["#"] = [[\#]],
  26. ["_"] = [[\_]],
  27. ["{"] = [[\{]],
  28. ["}"] = [[\}]],
  29. ["~"] = [[\textasciitilde]],
  30. ["^"] = [[\textasciicircum]],
  31. ["\\"] = [[\textbackslash]],
  32. }))
  33. end
  34. local function SL(x)
  35. return latex_escape(Ss(x))
  36. end
  37. local function spairs(tbl, sort)
  38. local keys = {}
  39. local kn = {}
  40. for k in pairs(tbl or {}) do
  41. table.insert(keys, k)
  42. end
  43. table.sort(keys, sort)
  44. for i = 2, #keys do
  45. kn[keys[i-1]] = keys[i]
  46. end
  47. return function(t, n)
  48. local k = kn[n]
  49. if n == nil then
  50. k = keys[1]
  51. end
  52. return k, t[k]
  53. end, tbl, nil
  54. end
  55. local function map(tbl, func)
  56. local t = {}
  57. for k, v in pairs(tbl or {}) do
  58. t[k] = func(v)
  59. end
  60. return t
  61. end
  62. local function htmono(str)
  63. return string.format("<mono>%s</mono>", htescape(str))
  64. end
  65. local function htheader(str)
  66. return string.format("<b>%s</b>", htescape(str))
  67. end
  68. local function htaction(action, str, noesc)
  69. if not noesc then
  70. str = htescape(str)
  71. end
  72. return string.format("<action name=%s><style color=cyan>%s</style></action>", action, str)
  73. end
  74. local function describe_conns(conns)
  75. local connsdesc = {[0] = "N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW"}
  76. if type(conns) == "table" then
  77. if conns.c then
  78. if conns.y and conns.y ~= 0 then
  79. return ("%s%+d%%"):format(describe_conns(conns.c), conns.y*100)
  80. else
  81. return describe_conns(conns.c)
  82. end
  83. else
  84. local cst = map(conns, describe_conns)
  85. local cstl = #cst
  86. if cstl == 2 then
  87. return ("%s - %s"):format(unpack(cst))
  88. elseif cstl == 3 then
  89. return ("[%s <-] %s -> %s"):format(cst[3], cst[1], cst[2])
  90. elseif cstl == 4 then
  91. return ("%s - %s; %s - %s"):format(unpack(cst))
  92. elseif cstl == 5 then
  93. return ("[%s,%s <-] %s -> %s"):format(cst[3], cst[4], cst[1], cst[2])
  94. end
  95. end
  96. else
  97. return connsdesc[tonumber(conns)]
  98. end
  99. end
  100. advtrains_doc_integration.describe_conns = describe_conns
  101. local function describe_length(x)
  102. local inch = x/0.0254
  103. local infdenom = 32
  104. local infnum = math.floor(inch*infdenom)%infdenom
  105. local ft = math.floor(inch/12)
  106. inch = math.floor(inch)%12
  107. local st = {}
  108. if ft > 0 then
  109. table.insert(st, ft .. "'")
  110. end
  111. local infstr = ""
  112. if infnum > 0 then
  113. while infnum%2 == 0 do
  114. infnum, infdenom = infnum/2, infdenom/2
  115. end
  116. infstr = string.format(" %d/%d", infnum, infdenom)
  117. end
  118. if inch > 0 then
  119. table.insert(st, string.format(' %s%s"', inch, infstr))
  120. elseif infnum > 0 then
  121. table.insert(st, infstr .. '"')
  122. end
  123. if not next(st) then
  124. st = '0"'
  125. end
  126. return string.format("%d mm (%s)", 1000*x, table.concat(st))
  127. end
  128. local function describe_speed(x)
  129. local kmph = x*3.6
  130. local mph = kmph/1.609344
  131. return string.format("%.1f m/s (%.1f km/h; %.1f mph)", x, kmph, mph)
  132. end
  133. local function addlist(lst, tbl, title, fallback1, fallback2, mapf)
  134. if not tbl then
  135. if fallback2 then
  136. table.insert(lst, fallback2)
  137. elseif fallback2 == false and fallback1 then
  138. table.insert(lst, fallback1)
  139. end
  140. elseif next(tbl) ~= nil then
  141. table.insert(lst, title)
  142. for k, v in pairs(tbl) do
  143. if mapf then
  144. k = mapf(k, v)
  145. end
  146. table.insert(lst, "• " .. k)
  147. end
  148. elseif fallback1 then
  149. table.insert(lst, fallback1)
  150. end
  151. end
  152. local function get_coupler_name(n)
  153. return advtrains.coupler_types[n] or n
  154. end
  155. local function ht_coupler_name(n)
  156. local s = advtrains.coupler_types[n]
  157. if s then
  158. return htescape(s)
  159. else
  160. return htmono(n)
  161. end
  162. end
  163. local function dlxtrains_livery_information(prototype)
  164. if not dlxtrains then
  165. return nil
  166. end
  167. local old_update_livery = dlxtrains.update_livery
  168. dlxtrains.update_livery = function(_, _, x)
  169. return coroutine.yield(x)
  170. end
  171. local env = {
  172. coroutine = coroutine,
  173. dlxtrains = table.copy(dlxtrains),
  174. }
  175. local function main(G, f)
  176. setfenv(0, G)
  177. f()
  178. return error()
  179. end
  180. local t = {coroutine.resume(coroutine.create(main), env, prototype.custom_may_destroy or function() end)}
  181. dlxtrains.update_livery = old_update_livery
  182. if not t[1] then
  183. return nil
  184. end
  185. return unpack(t, 2)
  186. end
  187. local advtrains_livery_tools_information
  188. do -- helper for Marnack's Advtrains livery tools
  189. local atliv = {}
  190. function atliv:get_textures_from_design(design)
  191. local tp = type(design)
  192. if tp == "string" then
  193. return self:get_textures_from_design(self.liveries[design])
  194. elseif tp ~= "table" then
  195. return
  196. end
  197. local template = self.templates[design.livery_template_name]
  198. if not template then
  199. return nil
  200. end
  201. local odef = template.overlays
  202. local textures = map(template.base_textures, function(str) return {str} end)
  203. for _, overlay in spairs(design.overlays) do
  204. local o = odef[overlay.id]
  205. local t = textures[(o or {}).slot_idx]
  206. if t and o then
  207. local alpha = math.min(255, math.max(0, o.alpha or 255))
  208. table.insert(t, string.format("(%s^[colorize:%s:%d)", txescape(o.texture), txescape(overlay.color), alpha))
  209. end
  210. end
  211. return map(textures, function(st) return table.concat(st, "^") end)
  212. end
  213. local mt = {
  214. __index = atliv,
  215. }
  216. advtrains_livery_tools_information = function(wname)
  217. if not advtrains_livery_database then
  218. return nil
  219. end
  220. local tnames = advtrains_livery_database.get_livery_template_names_for_wagon(wname)
  221. if next(tnames) == nil then
  222. return nil
  223. end
  224. local templates = {}
  225. for _, tname in pairs(tnames) do
  226. templates[tname] = advtrains_livery_database.get_wagon_livery_template(wname, tname)
  227. end
  228. local lnames = advtrains_livery_database.get_predefined_livery_names(wname)
  229. local lnames_t = {}
  230. local liveries = {}
  231. for _, lid in pairs(lnames) do
  232. local lname = lid.livery_name
  233. liveries[lname] = advtrains_livery_database.get_predefined_livery(wname, lname)
  234. table.insert(lnames_t, lname)
  235. end
  236. table.sort(tnames)
  237. table.sort(lnames_t)
  238. local obj = {
  239. templates = templates,
  240. template_names = tnames,
  241. liveries = liveries,
  242. livery_names = lnames_t,
  243. }
  244. return setmetatable(obj, mt)
  245. end
  246. end
  247. local function list_itemstring(x)
  248. local item = ItemStack(x)
  249. if item:is_empty() then
  250. return S("Emptyness")
  251. end
  252. return string.format("%s: %d", item:get_short_description(), item:get_count())
  253. end
  254. local function blankline(st)
  255. return table.insert(st, "")
  256. end
  257. local prototype_cache = {}
  258. advtrains_doc_integration.prototypes = prototype_cache -- ONLY FOR DEBUGGING
  259. local function adjust_wagon_prototype(itemname, prototype)
  260. local p = prototype_cache[itemname]
  261. if p then
  262. return p
  263. end
  264. p = table.copy(prototype)
  265. if p._doc_wagon_longdesc then
  266. p.longdesc = p._long_wagon_longdesc
  267. end
  268. if type(p.horn_sound) == "string" then
  269. p.horn_sound = {name = prototype.horn_sound}
  270. end
  271. if p.horn_sound and p.horn_sound.name == "" then
  272. p.horn_sound = nil
  273. end
  274. local pax, driver = 0, 0
  275. if p.seats and p.seat_groups then
  276. for _, v in pairs(p.seats) do
  277. if p.seat_groups[v.group].driving_ctrl_access then
  278. driver = driver + 1
  279. else
  280. pax = pax + 1
  281. end
  282. p.seat_groups[v.group].count = (p.seat_groups[v.group].count or 0) + 1
  283. end
  284. end
  285. p.max_passengers = pax
  286. p.max_drivers = driver
  287. p.max_seats = pax+driver
  288. local bikelivdef = p.livery_definition
  289. local dlxlivdef = dlxtrains_livery_information(p)
  290. local atlivdef = advtrains_livery_tools_information(itemname)
  291. p.dlxtrains_livery = dlxlivdef
  292. p.advtrains_livery_tools = atlivdef
  293. local txbase = p.textures
  294. if dlxlivdef then
  295. txbase = {string.format("%s_%s.png", dlxlivdef.filename_prefix, dlxlivdef[0].code)}
  296. end
  297. p.livery_textures = {[0] = txbase}
  298. if bikelivdef then
  299. local slot = p.livery_texture_slot
  300. local components = bikelivdef.components
  301. local basefile = bikelivdef.base_texture_file
  302. for _, pdef in ipairs(bikelivdef.presets) do
  303. local tx = table.copy(txbase)
  304. local st = {basefile}
  305. for _, l in ipairs(pdef.livery_stack.layers) do
  306. table.insert(st, string.format("(%s^[colorize:%s)", txescape(components[l.component].texture_file), txescape(l.color)))
  307. end
  308. tx[slot] = table.concat(st, "^")
  309. table.insert(p.livery_textures, tx)
  310. end
  311. end
  312. if atlivdef then
  313. for _, dname in ipairs(atlivdef.livery_names) do
  314. table.insert(p.livery_textures, atlivdef:get_textures_from_design(dname) or txbase)
  315. end
  316. end
  317. prototype_cache[itemname] = p
  318. return p
  319. end
  320. local function doc_register_wagon(itemname)
  321. local prototype = adjust_wagon_prototype(itemname, advtrains.wagon_prototypes[itemname])
  322. prototype.name = itemname
  323. minetest.override_item(itemname, {_doc_items_create_entry = false})
  324. doc.add_entry("advtrains_wagons", itemname, {
  325. name = ItemStack(itemname):get_short_description(),
  326. data = prototype,
  327. })
  328. if doc.sub.identifier then
  329. doc.sub.identifier.register_object(itemname, "advtrains_wagons", itemname)
  330. end
  331. end
  332. local wlivprev = {}
  333. local function get_livery_preview_selection(pname, itemname)
  334. return (wlivprev[pname] or {})[itemname] or 0
  335. end
  336. local function get_livery_preview(itemname, id)
  337. local tx = (prototype_cache[itemname] or {}).livery_textures
  338. return tx[id] or tx[0]
  339. end
  340. local function render_livery_textures(pname, itemname)
  341. local str = table.concat(map(get_livery_preview(itemname, get_livery_preview_selection(pname, itemname)), fsescape), ",")
  342. return str
  343. end
  344. local function set_livery_preview_selection(pname, itemname, id)
  345. local t = wlivprev[pname]
  346. if not t then
  347. t = {}
  348. wlivprev[pname] = t
  349. end
  350. t[itemname] = id
  351. end
  352. local function doc_render_wagon_information(prototype, pname)
  353. local desctext = {}
  354. if prototype._doc_wagon_longdesc then
  355. table.insert(desctext, tostring(prototype._doc_wagon_longdesc))
  356. blankline(desctext)
  357. end
  358. table.insert(desctext, htheader(S("Basic Information")))
  359. table.insert(desctext, S("Itemstring: @1", htmono(prototype.name)))
  360. addlist(desctext, prototype.drops, S("Drops:"), S("Drops nothing"), false, function(_, v) return list_itemstring(v) end)
  361. addlist(desctext, prototype.drives_on, S("Drives on:"), nil, nil, htmono)
  362. addlist(desctext, prototype.coupler_types_front, S("Compatible front couplers:"), S2("Front coupler: @1", "Absent"), S2("Front coupler: @1", "Universal"), ht_coupler_name)
  363. addlist(desctext, prototype.coupler_types_back, S("Compatible rear couplers:"), S2("Rear coupler: @1", "Absent"), S2("Rear coupler: @1", "Universal"), ht_coupler_name)
  364. table.insert(desctext, S("Wagon span: @1", prototype.wagon_span and describe_length(2*prototype.wagon_span) or S("Undefined")))
  365. table.insert(desctext, S("Maximum speed: @1", prototype.max_speed and describe_speed(prototype.max_speed) or S("Undefined")))
  366. table.insert(desctext, S2("Motive power: @1", prototype.is_locomotive and "Present" or "Absent"))
  367. local hornsound = prototype.horn_sound
  368. table.insert(desctext, S("Horn sound: @1", hornsound and htaction("playhorn", htmono(hornsound.name)) or S("Undefined")))
  369. blankline(desctext)
  370. table.insert(desctext, htheader(S("Wagon Capacity")))
  371. table.insert(desctext, S("Passenger seats: @1", prototype.max_passengers))
  372. table.insert(desctext, S("Driver seats: @1", prototype.max_drivers))
  373. if prototype.has_inventory then
  374. addlist(desctext, prototype.inventory_list_sizes, S("Cargo inventory size:"), S2("Cargo inventory: @1", "Present"), false, function(k, v)
  375. return string.format("%s: %d", htmono(k), v)
  376. end)
  377. else
  378. table.insert(desctext, S2("Cargo inventory: @1", "Absent"))
  379. end
  380. if techage and prototype.techage_liquid_capacity then
  381. table.insert(desctext, S("Liquid Capacity (Techage): @1", string.format("%d", prototype.techage_liquid_capacity)))
  382. end
  383. blankline(desctext)
  384. table.insert(desctext, htheader(S("Wagon Appearance")))
  385. table.insert(desctext, S("Mesh: @1", prototype.mesh and htmono(prototype.mesh) or "None"))
  386. addlist(desctext, prototype.textures, S("Textures:"), S("No textures"), false, function(_, v) return htmono(v) end)
  387. local livids = 0
  388. local function livprev(desc)
  389. livids = livids+1
  390. return htaction(string.format("preview_%d", livids), desc)
  391. end
  392. local livrst = (get_livery_preview_selection(pname, prototype.name) ~= 0) and " " .. htaction("preview_0", S("[Reset Preview]")) or ""
  393. local bikeliv = S("Unsupported")
  394. local bikelivdesc = nil
  395. if prototype.set_livery then
  396. if prototype.livery_definition then
  397. bikeliv = S("Supported by the multi_component_liveries mod")
  398. bikelivdesc = {}
  399. addlist(bikelivdesc, prototype.livery_definition.components, S("Livery components:"), nil, nil, function(_, v) return htescape(v.description) end)
  400. addlist(bikelivdesc, prototype.livery_definition.presets, S("Livery presets:") .. livrst, nil, nil, function(_, v) livids = livids+1 return livprev(v.description) end)
  401. bikelivdesc = table.concat(bikelivdesc, "\n")
  402. else
  403. bikeliv = S("Supported")
  404. end
  405. end
  406. table.insert(desctext, S("Livery system with bike painter: @1", bikeliv))
  407. table.insert(desctext, bikelivdesc)
  408. local dlxlivdef = prototype.dlxtrains_livery
  409. table.insert(desctext, S2("DlxTrains livery system: @1", dlxlivdef and "Supported" or "Unsupported"))
  410. local atlivdef = prototype.advtrains_livery_tools
  411. table.insert(desctext, S2("Advtrains livery tools (Marnack): @1", atlivdef and "Supported" or "Unsupported"))
  412. if atlivdef then
  413. addlist(desctext, atlivdef.template_names, S("Livery templates:"), nil, nil, function(_, v) return htescape(v) end)
  414. addlist(desctext, atlivdef.livery_names, S("Livery presets:") .. livrst, nil, nil, function(_, v) return livprev(v) end)
  415. end
  416. blankline(desctext)
  417. table.insert(desctext, htheader(S("Implementation Details")))
  418. local attachment_offset_support = S("Unsupported")
  419. if advtrains_attachment_offset_patch then
  420. local t = advtrains_attachment_offset_patch
  421. if prototype.get_on == t.get_on_override and prototype.get_off == t.get_off_override then
  422. attachment_offset_support = S("Supported")
  423. end
  424. end
  425. table.insert(desctext, S("Proper player attachment positioning: @1", attachment_offset_support))
  426. for k, v in pairs {
  427. custom_on_activate = "Custom instantiation callback",
  428. custom_on_step = "Custom step function",
  429. custom_on_velocity_change = "Custom velocity change callback",
  430. } do
  431. table.insert(desctext, S2(v .. ": @1", prototype[k] and "Defined" or "Undefined"))
  432. end
  433. local x0, y0 = doc.FORMSPEC.ENTRY_START_X+0.25, doc.FORMSPEC.ENTRY_START_Y
  434. local x1, y1 = doc.FORMSPEC.ENTRY_END_X+0.75, doc.FORMSPEC.ENTRY_END_Y+0.625
  435. local width, height = x1-x0, y1-y0
  436. local mside = height/2
  437. local mesh = fsescape(prototype.mesh or "")
  438. local textures = render_livery_textures(pname, prototype.name)
  439. local fstext = {
  440. string.format("hypertext[%f,%f;%f,%f;entry_body;%s]", x0, y0, width-mside, height+0.875, fsescape(table.concat(desctext, "\n"))),
  441. string.format("item_image[%f,%f;%f,%f;%s]", x1-mside, y0+0.0625, mside, mside, fsescape(prototype.name)),
  442. string.format("model[%f,%f;%f,%f;%s;%s;%s;%f,%f]",
  443. x1-mside, y1-mside, mside, mside, "wagon_model", mesh, textures, -30, 135),
  444. }
  445. return table.concat(fstext, "\n")
  446. end
  447. if doc then
  448. minetest.register_on_mods_loaded(function()
  449. for k in pairs(advtrains.wagon_prototypes) do
  450. doc_register_wagon(k)
  451. end
  452. end)
  453. if doc.sub.items then
  454. local register_factoid = doc.sub.items.register_factoid
  455. local function group_factoid(cat, gr, f)
  456. register_factoid(cat, "groups", function(_, def)
  457. return f(def.groups[gr] or 0) or ""
  458. end)
  459. end
  460. for cat, cinfo in pairs{
  461. nodes = {
  462. not_blocking_trains = S("This block does not block trains."),
  463. save_in_at_nodedb = S("This block is saved in the Advtrains node database."),
  464. },
  465. } do
  466. for group, ginfo in pairs(cinfo) do
  467. local tp = type(ginfo)
  468. if tp == "string" then
  469. group_factoid(cat, group, function(x)
  470. if x > 0 then
  471. return ginfo
  472. end
  473. end)
  474. elseif tp == "function" then
  475. group_factoid(cat, group, ginfo)
  476. end
  477. end
  478. end
  479. register_factoid("nodes", "groups", function(_, ndef)
  480. if ndef.advtrains then
  481. if ndef.advtrains.set_aspect then
  482. return S("This is a signal with a variable aspect.")
  483. elseif ndef.advtrains.get_aspect then
  484. return S("This is a signal with a static aspect.")
  485. end
  486. end
  487. if ndef.at_conns then
  488. return S("This track has the following conns table by default: @1", describe_conns(ndef.at_conns) or "?")
  489. end
  490. return ""
  491. end)
  492. end
  493. doc.add_category("advtrains_wagons", {
  494. name = S("Wagons"),
  495. build_formspec = doc_render_wagon_information,
  496. })
  497. end
  498. local function latex_colordesc(cstr)
  499. local color = string.match(cstr,"^#(%x%x%x%x%x%x)$")
  500. cstr = SL(cstr)
  501. if color then
  502. color = SL(string.upper(color))
  503. return string.format([[\tikz \definecolor{c}{HTML}{%s} \draw[fill=c] (0,0) rectangle (1em,1em); \texttt{%s}]], color, cstr)
  504. else
  505. return string.format([[\texttt{%s}]], cstr)
  506. end
  507. end
  508. function advtrains_doc_integration.write_wagon_info_as_latex(itemname)
  509. local filename = string.format("%satdoc_wagon_%s.tex", worldpath, itemname:gsub(":", "_"))
  510. local prototype = adjust_wagon_prototype(advtrains.wagon_prototypes[itemname])
  511. local wname = ItemStack(itemname):get_short_description()
  512. local st = {string.format([[
  513. \documentclass{article}
  514. \usepackage[a4paper,margin=1in,bottom=1.5in]{geometry}
  515. \usepackage[T1]{fontenc}
  516. \usepackage{tikz}
  517. \usepackage{booktabs,multirow,tabularx}
  518. \renewcommand{\arraystretch}{1.5}
  519. \usepackage{hyperref}
  520. \hypersetup{pdftitle={Wagon Datasheet: %s}}
  521. \title{Wagon Datasheet}
  522. \author{%s}
  523. \setlength{\parindent}{0pt}
  524. \begin{document}
  525. \maketitle
  526. ]], SL(wname), SL(wname))}
  527. table.insert(st, [[\section{Basic Information}]])
  528. if prototype.longdesc then
  529. table.insert(st, SL(prototype.longdesc) .. "\n")
  530. end
  531. table.insert(st, [[\begin{tabularx}{\textwidth}{l X}]])
  532. table.insert(st, string.format([[Itemstring & \texttt{%s}\\]], SL(itemname)))
  533. if prototype.drives_on then
  534. local i0 = #st+1
  535. local count = 0
  536. for k in pairs(prototype.drives_on) do
  537. table.insert(st, string.format([[& \texttt{%s}\\]], SL(k)))
  538. count = count + 1
  539. end
  540. if count > 0 then
  541. st[i0] = string.format([[Drives on %s]], st[i0])
  542. end
  543. end
  544. if prototype.wagon_span then
  545. table.insert(st, string.format([[Wagon span & %d mm\\]], prototype.wagon_span*2000))
  546. end
  547. if prototype.max_speed then
  548. table.insert(st, string.format([[Maximum speed & %d m/s\\]], prototype.max_speed))
  549. end
  550. table.insert(st, string.format([[Motive power & %s\\]], prototype.is_locomotive and "Present" or "Absent"))
  551. if prototype.horn_sound then
  552. table.insert(st, string.format([[Horn sound & \texttt{%s}\\]], SL(prototype.horn_sound.name)))
  553. else
  554. table.insert(st, [[Horn sound & Undefined\\]])
  555. end
  556. if prototype.mesh then
  557. table.insert(st, string.format([[Mesh & \texttt{%s}\\]], SL(prototype.mesh)))
  558. end
  559. if prototype.textures then
  560. local i0 = #st+1
  561. local count = 0
  562. for _, i in pairs(prototype.textures) do
  563. table.insert(st, string.format([[& \texttt{%s}\\]], SL(i)))
  564. count = count + 1
  565. end
  566. if count > 0 then
  567. st[i0] = string.format([[Textures %s]], st[i0])
  568. end
  569. end
  570. do
  571. local i0 = #st+1
  572. local count = 0
  573. for _, i in ipairs(prototype.drops or {}) do
  574. local item = ItemStack(i)
  575. if not item:is_empty() then
  576. local desc = string.format([[\texttt{%s}]], SL(item:get_name()))
  577. if item:is_known() then
  578. desc = SL(item:get_short_description())
  579. end
  580. table.insert(st, string.format([[& %s: %d\\]], desc, item:get_count()))
  581. count = count + 1
  582. end
  583. end
  584. if count > 0 then
  585. st[i0] = [[Drops ]] .. st[i0]
  586. else
  587. table.insert(st, [[Drops & Nothing \\]])
  588. end
  589. end
  590. table.insert(st, [[\end{tabularx}]])
  591. table.insert(st, [[\section{Coupler Compatibility}]])
  592. do
  593. local fcouplers = prototype.coupler_types_front
  594. local rcouplers = prototype.coupler_types_back
  595. local ccouplers = {}
  596. local lcouplers = {}
  597. local couplerid = {}
  598. local flim, rlim
  599. for k in pairs(fcouplers or {}) do
  600. flim = true
  601. ccouplers[k] = true
  602. end
  603. for k in pairs(rcouplers or {}) do
  604. rlim = true
  605. ccouplers[k] = true
  606. end
  607. for k in pairs(ccouplers) do
  608. local desc = SL(get_coupler_name(k))
  609. table.insert(lcouplers, desc)
  610. couplerid[desc] = k
  611. end
  612. table.sort(lcouplers)
  613. table.insert(st, [[
  614. \begin{tabularx}{\textwidth}{X c c}
  615. \toprule
  616. \multirow[t]{2}{*}{\bfseries Coupler Type} & \multicolumn{2}{c}{\bfseries Compatibility}\\
  617. \cmidrule(lr){2-3}
  618. & {\bfseries Front Coupler} & {\bfseries Rear Coupler}\\\midrule
  619. ]])
  620. if not (fcouplers and rcouplers) then
  621. local fd = fcouplers and "" or [[$\bullet$]]
  622. local rd = rcouplers and "" or [[$\bullet$]]
  623. table.insert(st, string.format([[\textit{Universal}&%s&%s\\]], fd, rd))
  624. end
  625. for i = 1, #lcouplers do
  626. local cdesc = lcouplers[i]
  627. local cid = couplerid[cdesc]
  628. local fd, rd = "", ""
  629. if flim then
  630. fd = fcouplers[cid] and [[$\bullet$]] or ""
  631. elseif not fcouplers then
  632. fd = [[$\Uparrow$]]
  633. end
  634. if rlim then
  635. rd = rcouplers[cid] and [[$\bullet$]] or ""
  636. elseif not rcouplers then
  637. rd = [[$\Uparrow$]]
  638. end
  639. table.insert(st, string.format([[%s&%s&%s\\]], cdesc, fd, rd))
  640. end
  641. table.insert(st, [[\bottomrule]])
  642. table.insert(st, [[\end{tabularx}]])
  643. end
  644. local hasinv = prototype.has_inventory
  645. local hasseats = prototype.max_seats>0
  646. local taliquid = prototype.techage_liquid_capacity or 0
  647. if hasinv or hasseats or taliquid>0 then
  648. table.insert(st, [[\section{Wagon Capacity}]])
  649. if hasseats then
  650. table.insert(st, [[
  651. \begin{tabularx}{\textwidth}{X c c}
  652. \toprule
  653. {\bfseries Seat Group} & {\bfseries Driver Stand} & {\bfseries Seat Count}\\\midrule
  654. ]])
  655. for _, d in pairs(prototype.seat_groups) do
  656. table.insert(st, string.format([[%s & %s & %d\\]], SL(d.name), d.driving_ctrl_access and [[$\bullet$]] or "", d.count))
  657. end
  658. table.insert(st, [[\bottomrule]])
  659. table.insert(st, [[\end{tabularx}]])
  660. end
  661. if hasinv then
  662. if next(prototype.inventory_list_sizes or {}) ~= nil then
  663. table.insert(st, [[
  664. \begin{tabularx}{\textwidth}{X c}
  665. \toprule
  666. {\bfseries Inventory Name} & {\bfseries Capacity}\\\midrule
  667. ]])
  668. for k, v in pairs(prototype.inventory_list_sizes) do
  669. table.insert(st, string.format([[\texttt{%s} & %d\\]], SL(k), v))
  670. end
  671. table.insert(st, [[\bottomrule]])
  672. table.insert(st, [[\end{tabularx}]])
  673. else
  674. table.insert(st, [[This wagon has an inventory of unknown size.]])
  675. end
  676. end
  677. if taliquid > 0 then
  678. table.insert(st, string.format([[
  679. \begin{tabularx}{\textwidth}{X l}
  680. {Liquid Capacity (Techage)} & %d
  681. \end{tabularx}
  682. ]], taliquid))
  683. end
  684. end
  685. if prototype.set_livery then
  686. if prototype.livery_definition then
  687. table.insert(st, [[\section{Multi-Component Liveries}]])
  688. local components = prototype.livery_definition.components
  689. local presets = prototype.livery_definition.presets
  690. table.insert(st, [[\subsection*{Components}]])
  691. table.insert(st, [[\begin{itemize}]])
  692. for _, c in ipairs(components) do
  693. table.insert(st, string.format([[\item %s]], SL(c.description)))
  694. end
  695. table.insert(st, [[\end{itemize}]])
  696. for _, p in ipairs(presets) do
  697. table.insert(st, string.format([[\subsection*{Preset: %s}]], SL(p.description)))
  698. table.insert(st, [[
  699. \begin{tabularx}{\textwidth}{X c}
  700. \toprule
  701. {\bfseries Component} & {\bfseries Color} \\\midrule
  702. ]])
  703. for _, c in ipairs(p.livery_stack.layers) do
  704. local cdesc = SL(components[c.component].description)
  705. table.insert(st, string.format([[%s & %s\\]], cdesc, latex_colordesc(c.color)))
  706. end
  707. table.insert(st, [[
  708. \bottomrule
  709. \end{tabularx}
  710. ]])
  711. end
  712. else
  713. table.insert(st, [[\section{Livery System (Bike Painter)}]])
  714. table.insert(st, [[This wagon can be painted by the bike painter.]])
  715. end
  716. end
  717. local dlxlivdef = dlxtrains_livery_information(prototype)
  718. if dlxlivdef then
  719. table.insert(st, [[
  720. \section{DlxTrains Livery Sytem}
  721. This wagon can be customized with DlxTrains' livery system.
  722. ]])
  723. end
  724. local atlivdef = advtrains_livery_tools_information(itemname)
  725. if atlivdef then
  726. table.insert(st, [[\section{Advtrains Livery Tool (Marnack)}]])
  727. for _, tname in ipairs(atlivdef.template_names) do
  728. local tdef = atlivdef.templates[tname]
  729. table.insert(st, string.format([[\subsection*{Template: %s}]], SL(tname)))
  730. table.insert(st, SL(tdef.notes))
  731. table.insert(st, "")
  732. table.insert(st, "This template contains the following components:")
  733. table.insert(st, [[\begin{itemize}]])
  734. for _, overlay in ipairs(tdef.overlays) do
  735. table.insert(st, string.format([[\item %s]], SL(overlay.name)))
  736. end
  737. table.insert(st, [[\end{itemize}]])
  738. end
  739. for _, lname in ipairs(atlivdef.livery_names) do
  740. local ldef = atlivdef.liveries[lname]
  741. local tname = ldef.livery_template_name
  742. table.insert(st, string.format([[\subsection*{Preset: %s}]], SL(lname)))
  743. table.insert(st, string.format([[Template: %s]], SL(tname)))
  744. table.insert(st, "")
  745. table.insert(st, [[
  746. \begin{tabularx}{\textwidth}{X c}
  747. \toprule
  748. {\bfseries Component} & {\bfseries Color}\\\midrule]])
  749. for _, overlay in pairs(ldef.overlays) do
  750. local cname = atlivdef.templates[tname].overlays[overlay.id].name
  751. table.insert(st, string.format([[%s & %s\\]], SL(cname), latex_colordesc(overlay.color)))
  752. end
  753. table.insert(st, [[
  754. \bottomrule
  755. \end{tabularx}
  756. ]])
  757. end
  758. end
  759. table.insert(st, [[
  760. \end{document}
  761. ]])
  762. st = table.concat(st, "\n")
  763. minetest.safe_file_write(filename, st)
  764. end
  765. function advtrains_doc_integration.write_all_wagons_as_latex()
  766. for k in pairs(advtrains.wagon_prototypes) do
  767. advtrains_doc_integration.write_wagon_info_as_latex(k)
  768. end
  769. end
  770. minetest.register_chatcommand("atdoc_write", {
  771. params = "",
  772. description = S("Export Advtrains-related information"),
  773. privs = {server = true},
  774. func = function()
  775. advtrains_doc_integration.write_all_wagons_as_latex()
  776. end,
  777. })
  778. minetest.register_on_player_receive_fields(function(player, formname, fields)
  779. if formname ~= "doc:entry" then
  780. return
  781. end
  782. local pname = player:get_player_name()
  783. local cat, ent = doc.get_selection(pname)
  784. if cat ~= "advtrains_wagons" or ent == nil then
  785. return
  786. end
  787. local act = fields.entry_body
  788. if not act then
  789. return
  790. elseif act == "action:playhorn" then
  791. local sound = (advtrains.wagon_prototypes[ent] or {}).horn_sound
  792. if type(sound) == "table" then
  793. sound = table.copy(sound)
  794. else
  795. sound = {name = sound}
  796. end
  797. if type(sound.name) ~= "string" or sound.name == "" then
  798. return
  799. end
  800. minetest.sound_play(sound, {to_player = pname}, true)
  801. else
  802. local txid = string.match(act, [[^action:preview_(%d+)$]])
  803. txid = tonumber(txid)
  804. if txid then
  805. set_livery_preview_selection(pname, ent, txid)
  806. doc.show_entry(pname, cat, ent)
  807. end
  808. end
  809. end)