dlg_create_world.lua 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474
  1. -- Luanti
  2. -- Copyright (C) 2014 sapier
  3. -- SPDX-License-Identifier: LGPL-2.1-or-later
  4. local function table_to_flags(ftable)
  5. -- Convert e.g. { jungles = true, caves = false } to "jungles,nocaves"
  6. local str = {}
  7. for flag, is_set in pairs(ftable) do
  8. str[#str + 1] = is_set and flag or ("no" .. flag)
  9. end
  10. return table.concat(str, ",")
  11. end
  12. -- Same as check_flag but returns a string
  13. local function strflag(flags, flag)
  14. return (flags[flag] == true) and "true" or "false"
  15. end
  16. local cb_caverns = { "caverns", fgettext("Caverns"),
  17. fgettext("Very large caverns deep in the underground") }
  18. local flag_checkboxes = {
  19. v5 = {
  20. cb_caverns,
  21. },
  22. v7 = {
  23. cb_caverns,
  24. { "ridges", fgettext("Rivers"), fgettext("Sea level rivers") },
  25. { "mountains", fgettext("Mountains") },
  26. { "floatlands", fgettext("Floatlands (experimental)"),
  27. fgettext("Floating landmasses in the sky") },
  28. },
  29. carpathian = {
  30. cb_caverns,
  31. { "rivers", fgettext("Rivers"), fgettext("Sea level rivers") },
  32. },
  33. valleys = {
  34. { "altitude_chill", fgettext("Altitude chill"),
  35. fgettext("Reduces heat with altitude") },
  36. { "altitude_dry", fgettext("Altitude dry"),
  37. fgettext("Reduces humidity with altitude") },
  38. { "humid_rivers", fgettext("Humid rivers"),
  39. fgettext("Increases humidity around rivers") },
  40. { "vary_river_depth", fgettext("Vary river depth"),
  41. fgettext("Low humidity and high heat causes shallow or dry rivers") },
  42. },
  43. flat = {
  44. cb_caverns,
  45. { "hills", fgettext("Hills") },
  46. { "lakes", fgettext("Lakes") },
  47. },
  48. fractal = {
  49. { "terrain", fgettext("Additional terrain"),
  50. fgettext("Generate non-fractal terrain: Oceans and underground") },
  51. },
  52. v6 = {
  53. { "trees", fgettext("Trees and jungle grass") },
  54. { "flat", fgettext("Flat terrain") },
  55. { "mudflow", fgettext("Mud flow"), fgettext("Terrain surface erosion") },
  56. { "temples", fgettext("Desert temples"),
  57. fgettext("Different dungeon variant generated in desert biomes (only if dungeons enabled)") },
  58. -- Biome settings are in mgv6_biomes below
  59. },
  60. }
  61. local mgv6_biomes = {
  62. {
  63. fgettext("Temperate, Desert, Jungle, Tundra, Taiga"),
  64. {jungles = true, snowbiomes = true}
  65. },
  66. {
  67. fgettext("Temperate, Desert, Jungle"),
  68. {jungles = true, snowbiomes = false}
  69. },
  70. {
  71. fgettext("Temperate, Desert"),
  72. {jungles = false, snowbiomes = false}
  73. },
  74. }
  75. local function create_world_formspec(dialogdata)
  76. local current_mg = dialogdata.mg
  77. local mapgens = core.get_mapgen_names()
  78. local flags = dialogdata.flags
  79. local game = pkgmgr.find_by_gameid(core.settings:get("menu_last_game"))
  80. if game == nil then
  81. -- should never happen but just pick the first game
  82. game = pkgmgr.games[1]
  83. core.settings:set("menu_last_game", game.id)
  84. end
  85. local disallowed_mapgen_settings = {}
  86. if game ~= nil then
  87. local gameconfig = Settings(game.path.."/game.conf")
  88. local allowed_mapgens = (gameconfig:get("allowed_mapgens") or ""):split()
  89. for key, value in pairs(allowed_mapgens) do
  90. allowed_mapgens[key] = value:trim()
  91. end
  92. local disallowed_mapgens = (gameconfig:get("disallowed_mapgens") or ""):split()
  93. for key, value in pairs(disallowed_mapgens) do
  94. disallowed_mapgens[key] = value:trim()
  95. end
  96. if #allowed_mapgens > 0 then
  97. for i = #mapgens, 1, -1 do
  98. if table.indexof(allowed_mapgens, mapgens[i]) == -1 then
  99. table.remove(mapgens, i)
  100. end
  101. end
  102. end
  103. if #disallowed_mapgens > 0 then
  104. for i = #mapgens, 1, -1 do
  105. if table.indexof(disallowed_mapgens, mapgens[i]) > 0 then
  106. table.remove(mapgens, i)
  107. end
  108. end
  109. end
  110. local ds = (gameconfig:get("disallowed_mapgen_settings") or ""):split()
  111. for _, value in pairs(ds) do
  112. disallowed_mapgen_settings[value:trim()] = true
  113. end
  114. end
  115. local mglist = ""
  116. local selindex
  117. do -- build the list of mapgens
  118. local i = 1
  119. local first_mg
  120. for k, v in pairs(mapgens) do
  121. if not first_mg then
  122. first_mg = v
  123. end
  124. if current_mg == v then
  125. selindex = i
  126. end
  127. i = i + 1
  128. mglist = mglist .. core.formspec_escape(v) .. ","
  129. end
  130. if not selindex then
  131. selindex = 1
  132. current_mg = first_mg
  133. end
  134. mglist = mglist:sub(1, -2)
  135. end
  136. -- The logic of the flag element IDs is as follows:
  137. -- "flag_main_foo-bar-baz" controls dialogdata.flags["main"]["foo_bar_baz"]
  138. -- see the buttonhandler for the implementation of this
  139. local mg_main_flags = function(mapgen, y)
  140. if mapgen == "singlenode" then
  141. return "", y
  142. end
  143. if disallowed_mapgen_settings["mg_flags"] then
  144. return "", y
  145. end
  146. local form = "checkbox[0," .. y .. ";flag_main_caves;" ..
  147. fgettext("Caves") .. ";"..strflag(flags.main, "caves").."]"
  148. y = y + 0.5
  149. form = form .. "checkbox[0,"..y..";flag_main_dungeons;" ..
  150. fgettext("Dungeons") .. ";"..strflag(flags.main, "dungeons").."]"
  151. y = y + 0.5
  152. local d_name = fgettext("Decorations")
  153. local d_tt
  154. if mapgen == "v6" then
  155. d_tt = fgettext("Structures appearing on the terrain (no effect on trees and jungle grass created by v6)")
  156. else
  157. d_tt = fgettext("Structures appearing on the terrain, typically trees and plants")
  158. end
  159. form = form .. "checkbox[0,"..y..";flag_main_decorations;" ..
  160. d_name .. ";" ..
  161. strflag(flags.main, "decorations").."]" ..
  162. "tooltip[flag_mg_decorations;" ..
  163. d_tt ..
  164. "]"
  165. y = y + 0.5
  166. form = form .. "tooltip[flag_main_caves;" ..
  167. fgettext("Network of tunnels and caves")
  168. .. "]"
  169. return form, y
  170. end
  171. local mg_specific_flags = function(mapgen, y)
  172. if not flag_checkboxes[mapgen] then
  173. return "", y
  174. end
  175. if disallowed_mapgen_settings["mg"..mapgen.."_spflags"] then
  176. return "", y
  177. end
  178. local form = ""
  179. for _, tab in pairs(flag_checkboxes[mapgen]) do
  180. local id = "flag_"..mapgen.."_"..tab[1]:gsub("_", "-")
  181. form = form .. ("checkbox[0,%f;%s;%s;%s]"):
  182. format(y, id, tab[2], strflag(flags[mapgen], tab[1]))
  183. if tab[3] then
  184. form = form .. "tooltip["..id..";"..tab[3].."]"
  185. end
  186. y = y + 0.5
  187. end
  188. if mapgen ~= "v6" then
  189. -- No special treatment
  190. return form, y
  191. end
  192. -- Special treatment for v6 (add biome widgets)
  193. -- Biome type (jungles, snowbiomes)
  194. local biometype
  195. if flags.v6.snowbiomes == true then
  196. biometype = 1
  197. elseif flags.v6.jungles == true then
  198. biometype = 2
  199. else
  200. biometype = 3
  201. end
  202. y = y + 0.3
  203. form = form .. "label[0,"..(y+0.1)..";" .. fgettext("Biomes") .. "]"
  204. y = y + 0.6
  205. form = form .. "dropdown[0,"..y..";6.3;mgv6_biomes;"
  206. for b=1, #mgv6_biomes do
  207. form = form .. mgv6_biomes[b][1]
  208. if b < #mgv6_biomes then
  209. form = form .. ","
  210. end
  211. end
  212. form = form .. ";" .. biometype.. "]"
  213. -- biomeblend
  214. y = y + 0.55
  215. form = form .. "checkbox[0,"..y..";flag_v6_biomeblend;" ..
  216. fgettext("Biome blending") .. ";"..strflag(flags.v6, "biomeblend").."]" ..
  217. "tooltip[flag_v6_biomeblend;" ..
  218. fgettext("Smooth transition between biomes") .. "]"
  219. return form, y
  220. end
  221. local y_start = 0.0
  222. local y = y_start
  223. local str_flags, str_spflags
  224. local label_flags, label_spflags = "", ""
  225. y = y + 0.3
  226. str_flags, y = mg_main_flags(current_mg, y)
  227. if str_flags ~= "" then
  228. label_flags = "label[0,"..y_start..";" .. fgettext("Mapgen flags") .. "]"
  229. y_start = y + 0.4
  230. else
  231. y_start = 0.0
  232. end
  233. y = y_start + 0.3
  234. str_spflags = mg_specific_flags(current_mg, y)
  235. if str_spflags ~= "" then
  236. label_spflags = "label[0,"..y_start..";" .. fgettext("Mapgen-specific flags") .. "]"
  237. end
  238. local retval =
  239. "size[12.25,7.4,true]" ..
  240. -- Left side
  241. "container[0,0]"..
  242. "field[0.3,0.6;6,0.5;te_world_name;" ..
  243. fgettext("World name") ..
  244. ";" .. core.formspec_escape(dialogdata.worldname) .. "]" ..
  245. "set_focus[te_world_name;false]"
  246. if not disallowed_mapgen_settings["seed"] then
  247. retval = retval .. "field[0.3,1.7;6,0.5;te_seed;" ..
  248. fgettext("Seed") ..
  249. ";".. core.formspec_escape(dialogdata.seed) .. "]"
  250. end
  251. retval = retval ..
  252. "label[0,2;" .. fgettext("Mapgen") .. "]"..
  253. "dropdown[0,2.5;6.3;dd_mapgen;" .. mglist .. ";" .. selindex .. "]"
  254. -- Warning when making a devtest world
  255. if game.id == "devtest" then
  256. retval = retval ..
  257. "container[0,3.5]" ..
  258. "box[0,0;5.8,1.7;#ff8800]" ..
  259. "textarea[0.4,0.1;6,1.8;;;"..
  260. fgettext("Development Test is meant for developers.") .. "]" ..
  261. "button[1,1;4,0.5;world_create_open_cdb;" .. fgettext("Install another game") .. "]" ..
  262. "container_end[]"
  263. end
  264. retval = retval ..
  265. "container_end[]" ..
  266. -- Right side
  267. "container[6.2,0]"..
  268. label_flags .. str_flags ..
  269. label_spflags .. str_spflags ..
  270. "container_end[]"..
  271. -- Menu buttons
  272. "container[0,6.9]"..
  273. "button[3.25,0;3,0.5;world_create_confirm;" .. fgettext("Create") .. "]" ..
  274. "button[6.25,0;3,0.5;world_create_cancel;" .. fgettext("Cancel") .. "]" ..
  275. "container_end[]"
  276. return retval
  277. end
  278. local function create_world_buttonhandler(this, fields)
  279. if fields["world_create_open_cdb"] then
  280. local dlg = create_contentdb_dlg("game")
  281. dlg:set_parent(this.parent)
  282. this:delete()
  283. this.parent:hide()
  284. dlg:show()
  285. return true
  286. end
  287. if fields["world_create_confirm"] or
  288. fields["key_enter"] then
  289. if fields["key_enter"] then
  290. -- HACK: This timestamp prevents double-triggering when pressing Enter on an input box
  291. -- and releasing it on a button[] or textlist[] due to instant formspec updates.
  292. this.parent.dlg_create_world_closed_at = core.get_us_time()
  293. end
  294. local worldname = fields["te_world_name"]
  295. local game, _ = pkgmgr.find_by_gameid(core.settings:get("menu_last_game"))
  296. local message
  297. if game == nil then
  298. message = fgettext_ne("No game selected")
  299. end
  300. if message == nil then
  301. -- For unnamed worlds use the generated name 'world<number>',
  302. -- where the number increments: it is set to 1 larger than the largest
  303. -- generated name number found.
  304. if worldname == "" then
  305. local worldnum_max = 0
  306. for _, world in ipairs(menudata.worldlist:get_list()) do
  307. if world.name:match("^world%d+$") then
  308. local worldnum = tonumber(world.name:sub(6))
  309. worldnum_max = math.max(worldnum_max, worldnum)
  310. end
  311. end
  312. worldname = "world" .. worldnum_max + 1
  313. end
  314. if menudata.worldlist:uid_exists_raw(worldname) then
  315. message = fgettext_ne("A world named \"$1\" already exists", worldname)
  316. end
  317. end
  318. if message == nil then
  319. this.data.seed = fields["te_seed"] or ""
  320. this.data.mg = fields["dd_mapgen"]
  321. -- actual names as used by engine
  322. local settings = {
  323. fixed_map_seed = this.data.seed,
  324. mg_name = this.data.mg,
  325. mg_flags = table_to_flags(this.data.flags.main),
  326. mgv5_spflags = table_to_flags(this.data.flags.v5),
  327. mgv6_spflags = table_to_flags(this.data.flags.v6),
  328. mgv7_spflags = table_to_flags(this.data.flags.v7),
  329. mgfractal_spflags = table_to_flags(this.data.flags.fractal),
  330. mgcarpathian_spflags = table_to_flags(this.data.flags.carpathian),
  331. mgvalleys_spflags = table_to_flags(this.data.flags.valleys),
  332. mgflat_spflags = table_to_flags(this.data.flags.flat),
  333. }
  334. message = core.create_world(worldname, game.id, settings)
  335. end
  336. if message == nil then
  337. core.settings:set("menu_last_game", game.id)
  338. menudata.worldlist:set_filtercriteria(game.id)
  339. menudata.worldlist:refresh()
  340. core.settings:set("mainmenu_last_selected_world",
  341. menudata.worldlist:raw_index_by_uid(worldname))
  342. end
  343. gamedata.errormessage = message
  344. this:delete()
  345. return true
  346. end
  347. this.data.worldname = fields["te_world_name"]
  348. this.data.seed = fields["te_seed"] or ""
  349. if fields["games"] then
  350. local gameindex = core.get_textlist_index("games")
  351. core.settings:set("menu_last_game", pkgmgr.games[gameindex].id)
  352. return true
  353. end
  354. for k,v in pairs(fields) do
  355. local split = string.split(k, "_", nil, 3)
  356. if split and split[1] == "flag" then
  357. -- We replaced the underscore of flag names with a dash.
  358. local flag = string.gsub(split[3], "-", "_")
  359. local ftable = this.data.flags[split[2]]
  360. assert(ftable)
  361. ftable[flag] = v == "true"
  362. return true
  363. end
  364. end
  365. if fields["world_create_cancel"] then
  366. this:delete()
  367. return true
  368. end
  369. if fields["mgv6_biomes"] then
  370. local entry = core.formspec_escape(fields["mgv6_biomes"])
  371. for b=1, #mgv6_biomes do
  372. if entry == mgv6_biomes[b][1] then
  373. local ftable = this.data.flags.v6
  374. ftable.jungles = mgv6_biomes[b][2].jungles
  375. ftable.snowbiomes = mgv6_biomes[b][2].snowbiomes
  376. return true
  377. end
  378. end
  379. end
  380. if fields["dd_mapgen"] then
  381. this.data.mg = fields["dd_mapgen"]
  382. return true
  383. end
  384. return false
  385. end
  386. function create_create_world_dlg()
  387. local retval = dialog_create("sp_create_world",
  388. create_world_formspec,
  389. create_world_buttonhandler,
  390. nil)
  391. retval.data = {
  392. worldname = "",
  393. -- settings the world is created with:
  394. seed = core.settings:get("fixed_map_seed") or "",
  395. mg = core.settings:get("mg_name"),
  396. flags = {
  397. main = core.settings:get_flags("mg_flags"),
  398. v5 = core.settings:get_flags("mgv5_spflags"),
  399. v6 = core.settings:get_flags("mgv6_spflags"),
  400. v7 = core.settings:get_flags("mgv7_spflags"),
  401. fractal = core.settings:get_flags("mgfractal_spflags"),
  402. carpathian = core.settings:get_flags("mgcarpathian_spflags"),
  403. valleys = core.settings:get_flags("mgvalleys_spflags"),
  404. flat = core.settings:get_flags("mgflat_spflags"),
  405. }
  406. }
  407. return retval
  408. end