doc_ui.lua 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. local S = minetest.get_translator "advtrains_doc_integration"
  2. local D = advtrains_doc_integration.describe
  3. local H = advtrains_doc_integration.hypertext
  4. local utils = advtrains_doc_integration.utils
  5. local fsescape = minetest.formspec_escape
  6. local function S2(a, b)
  7. return S(a, S(b))
  8. end
  9. local function addlist(lst, tbl, title, fallback1, fallback2, mapf)
  10. if not tbl then
  11. if fallback2 then
  12. table.insert(lst, fallback2)
  13. elseif fallback2 == false and fallback1 then
  14. table.insert(lst, fallback1)
  15. end
  16. elseif next(tbl) ~= nil then
  17. table.insert(lst, title)
  18. for k, v in pairs(tbl) do
  19. if mapf then
  20. k = mapf(k, v)
  21. end
  22. table.insert(lst, H.listitem(k, true))
  23. end
  24. elseif fallback1 then
  25. table.insert(lst, fallback1)
  26. end
  27. end
  28. local function describe_function(f)
  29. if not f then
  30. return S("Undefined")
  31. end
  32. return H.describe_function(f) or S("Defined")
  33. end
  34. local function blankline(st)
  35. return table.insert(st, "")
  36. end
  37. local wlivprev = {}
  38. local function get_livery_preview_selection(pname, itemname)
  39. return (wlivprev[pname] or {})[itemname] or 0
  40. end
  41. local function get_livery_preview(itemname, id)
  42. local tx = (advtrains_doc_integration.prototypes[itemname] or {}).livery_textures
  43. return tx[id] or tx[0]
  44. end
  45. local function render_livery_textures(pname, itemname)
  46. local str = table.concat(utils.map(get_livery_preview(itemname, get_livery_preview_selection(pname, itemname)), fsescape), ",")
  47. return str
  48. end
  49. local function set_livery_preview_selection(pname, itemname, id)
  50. local t = wlivprev[pname]
  51. if not t then
  52. t = {}
  53. wlivprev[pname] = t
  54. end
  55. t[itemname] = id
  56. end
  57. local function doc_render_wagon_information(prototype, pname)
  58. local desctext = {}
  59. if prototype._doc_wagon_longdesc then
  60. table.insert(desctext, tostring(prototype._doc_wagon_longdesc))
  61. blankline(desctext)
  62. end
  63. table.insert(desctext, H.header(S("Basic Information")))
  64. table.insert(desctext, S("Itemstring: @1", H.mono(prototype.name)))
  65. addlist(desctext, prototype.drops, S("Drops:"), S("Drops nothing"), false, function(_, v) return H.describe_item(v) end)
  66. addlist(desctext, prototype.drives_on, S("Drives on:"), nil, nil, H.mono)
  67. addlist(desctext, prototype.coupler_types_front, S("Compatible front couplers:"), S2("Front coupler: @1", "Absent"), S2("Front coupler: @1", "Universal"), H.describe_coupler)
  68. addlist(desctext, prototype.coupler_types_back, S("Compatible rear couplers:"), S2("Rear coupler: @1", "Absent"), S2("Rear coupler: @1", "Universal"), H.describe_coupler)
  69. table.insert(desctext, S("Wagon span: @1", prototype.wagon_span and D.length(2*prototype.wagon_span) or S("Undefined")))
  70. table.insert(desctext, S("Maximum speed: @1", prototype.max_speed and D.speed(prototype.max_speed) or S("Undefined")))
  71. table.insert(desctext, S2("Motive power: @1", prototype.is_locomotive and "Present" or "Absent"))
  72. table.insert(desctext, S("Horn sound: @1", H.describe_sound("playhorn", prototype.horn_sound)))
  73. if prototype.doors.open.sound or prototype.doors.close.sound then
  74. table.insert(desctext, S("Door sound: @1 (when opening), @2 (when closing)",
  75. H.describe_sound("playdooropen", prototype.doors.open.sound),
  76. H.describe_sound("playdoorclose", prototype.doors.close.sound)))
  77. else
  78. table.insert(desctext, S2("Door sound: @1", "Undefined"))
  79. end
  80. blankline(desctext)
  81. table.insert(desctext, H.header(S("Wagon Capacity")))
  82. table.insert(desctext, S("Passenger seats: @1", prototype.max_passengers))
  83. table.insert(desctext, S("Driver seats: @1", prototype.max_drivers))
  84. if prototype.has_inventory then
  85. addlist(desctext, prototype.inventory_list_sizes, S("Cargo inventory size:"), S2("Cargo inventory: @1", "Present"), false, function(k, v)
  86. return string.format("%s: %d", H.mono(k), v)
  87. end)
  88. else
  89. table.insert(desctext, S2("Cargo inventory: @1", "Absent"))
  90. end
  91. if techage and prototype.techage_liquid_capacity then
  92. table.insert(desctext, S("Liquid Capacity (Techage): @1", string.format("%d", prototype.techage_liquid_capacity)))
  93. end
  94. blankline(desctext)
  95. table.insert(desctext, H.header(S("Wagon Appearance")))
  96. table.insert(desctext, S("Mesh: @1", prototype.mesh and H.mono(prototype.mesh) or "None"))
  97. addlist(desctext, prototype.textures, S("Textures:"), S("No textures"), false, function(_, v) return H.mono(v) end)
  98. local livsel = get_livery_preview_selection(pname, prototype.name)
  99. local livrst = (livsel ~= 0 and not prototype.dlxtrains_livery) and " " .. H.action("preview_0", S("[Reset Preview]")) or ""
  100. local livids = 0
  101. local function livprev(desc)
  102. livids = livids+1
  103. local label = H.plain(desc)
  104. if livids == livsel then
  105. label = H.bold(desc)
  106. end
  107. return H.action(string.format("preview_%d", livids), label, true)
  108. end
  109. local dlxlivdef = prototype.dlxtrains_livery
  110. table.insert(desctext, S2("DlxTrains livery system: @1", dlxlivdef and "Supported" or "Unsupported"))
  111. if dlxlivdef then
  112. livids = -1
  113. table.insert(desctext, "Livery presets:")
  114. for k = 0, dlxlivdef.count-1 do
  115. table.insert(desctext, H.listitem(string.format("%s (%s, %s)", H.mono(dlxlivdef[k].code), livprev(S("default")), livprev(S("weathered"))), true))
  116. end
  117. end
  118. local bikeliv = S("Unsupported")
  119. local bikelivdesc = nil
  120. if prototype.set_livery then
  121. if prototype.livery_definition then
  122. bikeliv = S("Supported by the multi_component_liveries mod")
  123. bikelivdesc = {}
  124. addlist(bikelivdesc, prototype.livery_definition.components, S("Livery components:"), nil, nil, function(_, v) return H.plain(v.description) end)
  125. addlist(bikelivdesc, prototype.livery_definition.presets, S("Livery presets:") .. livrst, nil, nil, function(_, v) return livprev(v.description) end)
  126. bikelivdesc = table.concat(bikelivdesc, "\n")
  127. else
  128. bikeliv = S("Supported")
  129. end
  130. end
  131. table.insert(desctext, S("Livery system with bike painter: @1", bikeliv))
  132. table.insert(desctext, bikelivdesc)
  133. local atlivdef = prototype.advtrains_livery_tools
  134. table.insert(desctext, S2("Advtrains livery tools (Marnack): @1", atlivdef and "Supported" or "Unsupported"))
  135. if atlivdef then
  136. addlist(desctext, atlivdef.template_names, S("Livery templates:"), nil, nil, function(_, v) return H.plain(v) end)
  137. addlist(desctext, atlivdef.livery_names, S("Livery presets:") .. livrst, nil, nil, function(_, v) return livprev(v) end)
  138. end
  139. blankline(desctext)
  140. table.insert(desctext, H.header(S("Implementation Details")))
  141. local attachment_offset_support = S("Unsupported")
  142. if advtrains_attachment_offset_patch then
  143. local t = advtrains_attachment_offset_patch
  144. if prototype.get_on == t.get_on_override and prototype.get_off == t.get_off_override then
  145. attachment_offset_support = S("Supported")
  146. end
  147. end
  148. table.insert(desctext, S("Proper player attachment positioning: @1", attachment_offset_support))
  149. for k, v in pairs {
  150. custom_on_activate = "Custom instantiation callback",
  151. custom_on_step = "Custom step function",
  152. custom_on_velocity_change = "Custom velocity change callback",
  153. } do
  154. table.insert(desctext, S(v .. ": @1", describe_function(prototype[k])))
  155. end
  156. local x0, y0 = doc.FORMSPEC.ENTRY_START_X+0.25, doc.FORMSPEC.ENTRY_START_Y
  157. local x1, y1 = doc.FORMSPEC.ENTRY_END_X+0.75, doc.FORMSPEC.ENTRY_END_Y+0.625
  158. local width, height = x1-x0, y1-y0
  159. local mside = height/2
  160. local mesh = fsescape(prototype.mesh or "")
  161. local textures = render_livery_textures(pname, prototype.name)
  162. local fstext = {
  163. string.format("hypertext[%f,%f;%f,%f;entry_body;%s]", x0, y0, width-mside, height+0.875, fsescape(table.concat(desctext, "\n"))),
  164. string.format("item_image[%f,%f;%f,%f;%s]", x1-mside, y0+0.0625, mside, mside, fsescape(prototype.name)),
  165. string.format("model[%f,%f;%f,%f;%s;%s;%s;%f,%f]",
  166. x1-mside, y1-mside, mside, mside, "wagon_model", mesh, textures, -30, 135),
  167. }
  168. return table.concat(fstext, "\n")
  169. end
  170. if doc then
  171. advtrains_doc_integration.register_on_prototype_loaded(function(itemname, prototype)
  172. minetest.override_item(itemname, {_doc_items_create_entry = false})
  173. doc.add_entry("advtrains_wagons", itemname, {
  174. name = ItemStack(itemname):get_short_description(),
  175. data = prototype,
  176. })
  177. if doc.sub.identifier then
  178. doc.sub.identifier.register_object(itemname, "advtrains_wagons", itemname)
  179. end
  180. end)
  181. if doc.sub.items then
  182. local register_factoid = doc.sub.items.register_factoid
  183. local function ndef_field_factoid(cat, ftype, f, ...)
  184. local ftp = type(f)
  185. if ftp == "string" then
  186. local desc = f
  187. f = function(x)
  188. if x ~= nil then
  189. return desc
  190. end
  191. end
  192. end
  193. local keys = {...}
  194. local idx = function(t)
  195. for _, k in ipairs(keys) do
  196. if type(t) ~= "table" then
  197. return nil
  198. end
  199. t = t[k]
  200. end
  201. return t
  202. end
  203. local function fgen(_, def)
  204. return f(idx(def)) or ""
  205. end
  206. return register_factoid(cat, ftype, fgen)
  207. end
  208. local function group_factoid(cat, gr, f)
  209. local function func(x)
  210. return f(x or 0)
  211. end
  212. return ndef_field_factoid(cat, "groups", func, "groups", gr)
  213. end
  214. for cat, cinfo in pairs{
  215. nodes = {
  216. not_blocking_trains = S("This block does not block trains."),
  217. save_in_at_nodedb = S("This block is saved in the Advtrains node database."),
  218. },
  219. } do
  220. for group, ginfo in pairs(cinfo) do
  221. local tp = type(ginfo)
  222. if tp == "string" then
  223. group_factoid(cat, group, function(x)
  224. if x > 0 then
  225. return ginfo
  226. end
  227. end)
  228. elseif tp == "function" then
  229. group_factoid(cat, group, ginfo)
  230. end
  231. end
  232. end
  233. for fname, t in pairs {
  234. advtrains = {
  235. on_train_enter = S("This track reacts to passing trains."),
  236. on_train_approach = S("This track reacts to approaching trains."),
  237. },
  238. luaautomation = {
  239. fire_event = S("This block handles LuaATC events."),
  240. },
  241. } do
  242. for subfname, val in pairs(t) do
  243. local function f(x)
  244. if x ~= nil then
  245. return val
  246. end
  247. end
  248. ndef_field_factoid("nodes", "groups", f, fname, subfname)
  249. end
  250. end
  251. register_factoid("nodes", "groups", function(_, ndef)
  252. if ndef.advtrains then
  253. local atdef = ndef.advtrains
  254. if atdef.set_aspect then
  255. return S("This is a signal with a variable aspect.")
  256. elseif atdef.get_aspect then
  257. return S("This is a signal with a static aspect.")
  258. end
  259. end
  260. if ndef.at_conns then
  261. return S("This track has the following conns table by default: @1", D.conns(ndef.at_conns) or "?")
  262. end
  263. return ""
  264. end)
  265. end
  266. doc.add_category("advtrains_wagons", {
  267. name = S("Wagons"),
  268. build_formspec = doc_render_wagon_information,
  269. })
  270. end
  271. minetest.register_on_player_receive_fields(function(player, formname, fields)
  272. if formname ~= "doc:entry" then
  273. return
  274. end
  275. local pname = player:get_player_name()
  276. local cat, ent = doc.get_selection(pname)
  277. if cat ~= "advtrains_wagons" or ent == nil then
  278. return
  279. end
  280. local act = fields.entry_body
  281. local prototype = advtrains_doc_integration.prototypes[ent]
  282. local sounds = {
  283. ["action:playhorn"] = prototype.horn_sound,
  284. ["action:playdooropen"] = prototype.doors.open.sound,
  285. ["action:playdoorclose"] = prototype.doors.close.sound,
  286. }
  287. if not act then
  288. return
  289. elseif sounds[act] then
  290. minetest.sound_play(sounds[act], {to_player = pname}, true)
  291. else
  292. local txid = string.match(act, [[^action:preview_(%d+)$]])
  293. txid = tonumber(txid)
  294. if txid then
  295. set_livery_preview_selection(pname, ent, txid)
  296. doc.show_entry(pname, cat, ent)
  297. end
  298. end
  299. end)