aboutsummaryrefslogtreecommitdiff
path: root/builtin/mainmenu/tab_settings.lua
blob: f46fd17d6f36738dc4bc1d0883aa490c4f189c30 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
--Minetest
--Copyright (C) 2013 sapier
--
--This program is free software; you can redistribute it and/or modify
--it under the terms of the GNU Lesser General Public License as published by
--the Free Software Foundation; either version 2.1 of the License, or
--(at your option) any later version.
--
--This program is distributed in the hope that it will be useful,
--but WITHOUT ANY WARRANTY; without even the implied warranty of
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
--GNU Lesser General Public License for more details.
--
--You should have received a copy of the GNU Lesser General Public License along
--with this program; if not, write to the Free Software Foundation, Inc.,
--51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

--------------------------------------------------------------------------------

local function dlg_confirm_reset_formspec(data)
	local retval =
		"size[8,3]" ..
		"label[1,1;".. fgettext("Are you sure to reset your singleplayer world?") .. "]"..
		"button[1,2;2.6,0.5;dlg_reset_singleplayer_confirm;"..
				fgettext("Yes") .. "]" ..
		"button[4,2;2.8,0.5;dlg_reset_singleplayer_cancel;"..
				fgettext("No!!!") .. "]"
	return retval
end

local function dlg_confirm_reset_btnhandler(this, fields, dialogdata)

	if fields["dlg_reset_singleplayer_confirm"] ~= nil then
		local worldlist = core.get_worlds()
		local found_singleplayerworld = false

		for i=1,#worldlist,1 do
			if worldlist[i].name == "singleplayerworld" then
				found_singleplayerworld = true
				gamedata.worldindex = i
			end
		end

		if found_singleplayerworld then
			core.delete_world(gamedata.worldindex)
		end

		core.create_world("singleplayerworld", 1)

		worldlist = core.get_worlds()

		found_singleplayerworld = false

		for i=1,#worldlist,1 do
			if worldlist[i].name == "singleplayerworld" then
				found_singleplayerworld = true
				gamedata.worldindex = i
			end
		end
	end

	this.parent:show()
	this:hide()
	this:delete()
	return true
end

local function showconfirm_reset(tabview)
	local new_dlg = dialog_create("reset_spworld",
		dlg_confirm_reset_formspec,
		dlg_confirm_reset_btnhandler,
		nil)
	new_dlg:set_parent(tabview)
	tabview:hide()
	new_dlg:show()
end

local function gui_scale_to_scrollbar()

	local current_value = tonumber(core.setting_get("gui_scaling"))

	if (current_value == nil) or current_value < 0.25 then
		return 0
	end

	if current_value <= 1.25 then
		return ((current_value - 0.25)/ 1.0) * 700
	end

	if current_value <= 6 then
		return ((current_value -1.25) * 100) + 700
	end

	return 1000
end

local function scrollbar_to_gui_scale(value)

	value = tonumber(value)

	if (value <= 700) then
		return ((value / 700) * 1.0) + 0.25
	end

	if (value <=1000) then
		return ((value - 700) / 100) + 1.25
	end

	return 1
end

local function formspec(tabview, name, tabdata)
	local video_drivers = core.get_video_drivers()
	
	local video_driver_string = ""
	local current_video_driver_idx = 0
	local current_video_driver = core.setting_get("video_driver")
	for i=1, #video_drivers, 1 do
	
		if i ~= 1 then
			video_driver_string = video_driver_string .. ","
		end
		video_driver_string = video_driver_string .. video_drivers[i]
		
		local video_driver = string.gsub(video_drivers[i], " ", "")
		if current_video_driver:lower() == video_driver:lower() then
			current_video_driver_idx = i
		end
	end
	
	
	local tab_string =
		"box[0,0;3.5,4;#999999]" ..
		"checkbox[0.25,0;cb_smooth_lighting;".. fgettext("Smooth Lighting")
				.. ";".. dump(core.setting_getbool("smooth_lighting")) .. "]"..
		"checkbox[0.25,0.5;cb_particles;".. fgettext("Enable Particles") .. ";"
				.. dump(core.setting_getbool("enable_particles"))	.. "]"..
		"checkbox[0.25,1;cb_3d_clouds;".. fgettext("3D Clouds") .. ";"
				.. dump(core.setting_getbool("enable_3d_clouds")) .. "]"..
		"checkbox[0.25,1.5;cb_fancy_trees;".. fgettext("Fancy Trees") .. ";"
				.. dump(core.setting_getbool("new_style_leaves")) .. "]"..
		"checkbox[0.25,2.0;cb_opaque_water;".. fgettext("Opaque Water") .. ";"
				.. dump(core.setting_getbool("opaque_water")) .. "]"..
		"checkbox[0.25,2.5;cb_connected_glass;".. fgettext("Connected Glass") .. ";"
				.. dump(core.setting_getbool("connected_glass"))	.. "]"..
		"dropdown[0.25,3.25;3.25;dd_video_driver;"
			.. video_driver_string .. ";" .. current_video_driver_idx .. "]" ..
		"tooltip[dd_video_driver;" ..
			fgettext("Restart minetest for driver change to take effect") .. "]" ..
		"box[3.75,0;3.75,2.5;#999999]" ..
		"checkbox[4,0;cb_mipmapping;".. fgettext("Mip-Mapping") .. ";"
				.. dump(core.setting_getbool("mip_map")) .. "]"..
		"checkbox[4,0.5;cb_anisotrophic;".. fgettext("Anisotropic Filtering") .. ";"
				.. dump(core.setting_getbool("anisotropic_filter")) .. "]"..
		"checkbox[4,1.0;cb_bilinear;".. fgettext("Bi-Linear Filtering") .. ";"
				.. dump(core.setting_getbool("bilinear_filter")) .. "]"..
		"checkbox[4,1.5;cb_trilinear;".. fgettext("Tri-Linear Filtering") .. ";"
				.. dump(core.setting_getbool("trilinear_filter")) .. "]"..
		"box[7.75,0;4,4;#999999]" ..
		"checkbox[8,0;cb_shaders;".. fgettext("Shaders") .. ";"
				.. dump(core.setting_getbool("enable_shaders")) .. "]"
	if PLATFORM ~= "Android" then
		tab_string = tab_string ..
		"button[8,4.75;3.75,0.5;btn_change_keys;".. fgettext("Change keys") .. "]"
	else
		tab_string = tab_string ..
		"button[8,4.75;3.75,0.5;btn_reset_singleplayer;".. fgettext("Reset singleplayer world") .. "]"
	end
	tab_string = tab_string ..
		"box[0,4.25;3.5,1.25;#999999]" ..
		"label[0.25,4.25;" .. fgettext("GUI scale factor") .. "]" ..
		"scrollbar[0.25,4.75;3,0.4;sb_gui_scaling;horizontal;" ..
		 gui_scale_to_scrollbar() .. "]" ..
		"tooltip[sb_gui_scaling;" ..
			fgettext("Scaling factor applied to menu elements: ") ..
			dump(core.setting_get("gui_scaling")) .. "]"

	if PLATFORM == "Android" then
		tab_string = tab_string ..
		"box[4.25,2.75;3.25,2.15;#999999]" ..
		"checkbox[4.5,2.75;cb_touchscreen_target;".. fgettext("Touch free target") .. ";"
				.. dump(core.setting_getbool("touchtarget")) .. "]"
	end

	if core.setting_get("touchscreen_threshold") ~= nil then
		tab_string = tab_string ..
				"label[4.5,3.5;" .. fgettext("Touchthreshold (px)") .. "]" ..
				"dropdown[4.5,4;3;dd_touchthreshold;0,10,20,30,40,50;" ..
				((tonumber(core.setting_get("touchscreen_threshold"))/10)+1) .. "]"
	end

	if core.setting_getbool("enable_shaders") then
		tab_string = tab_string ..
				"checkbox[8,0.5;cb_bumpmapping;".. fgettext("Bumpmapping") .. ";"
						.. dump(core.setting_getbool("enable_bumpmapping")) .. "]"..
				"checkbox[8,1.0;cb_generate_normalmaps;".. fgettext("Generate Normalmaps") .. ";"
						.. dump(core.setting_getbool("generate_normalmaps")) .. "]"..
				"checkbox[8,1.5;cb_parallax;".. fgettext("Parallax Occlusion") .. ";"
						.. dump(core.setting_getbool("enable_parallax_occlusion")) .. "]"..
				"checkbox[8,2.0;cb_waving_water;".. fgettext("Waving Water") .. ";"
						.. dump(core.setting_getbool("enable_waving_water")) .. "]"..
				"checkbox[8,2.5;cb_waving_leaves;".. fgettext("Waving Leaves") .. ";"
						.. dump(core.setting_getbool("enable_waving_leaves")) .. "]"..
				"checkbox[8,3.0;cb_waving_plants;".. fgettext("Waving Plants") .. ";"
						.. dump(core.setting_getbool("enable_waving_plants")) .. "]"
	else
		tab_string = tab_string ..
				"textlist[8.33,0.7;4,1;;#888888" .. fgettext("Bumpmapping") .. ";0;true]" ..
				"textlist[8.33,1.2;4,1;;#888888" .. fgettext("Generate Normalmaps") .. ";0;true]" ..
				"textlist[8.33,1.7;4,1;;#888888" .. fgettext("Parallax Occlusion") .. ";0;true]" ..
				"textlist[8.33,2.2;4,1;;#888888" .. fgettext("Waving Water") .. ";0;true]" ..
				"textlist[8.33,2.7;4,1;;#888888" .. fgettext("Waving Leaves") .. ";0;true]" ..
				"textlist[8.33,3.2;4,1;;#888888" .. fgettext("Waving Plants") .. ";0;true]"
		end
	return tab_string
end

--------------------------------------------------------------------------------
local function handle_settings_buttons(this, fields, tabname, tabdata)
	if fields["cb_fancy_trees"] then
		core.setting_set("new_style_leaves", fields["cb_fancy_trees"])
		return true
	end
	if fields["cb_smooth_lighting"] then
		core.setting_set("smooth_lighting", fields["cb_smooth_lighting"])
		return true
	end
	if fields["cb_3d_clouds"] then
		core.setting_set("enable_3d_clouds", fields["cb_3d_clouds"])
		return true
	end
	if fields["cb_opaque_water"] then
		core.setting_set("opaque_water", fields["cb_opaque_water"])
		return true
	end
	if fields["cb_mipmapping"] then
		core.setting_set("mip_map", fields["cb_mipmapping"])
		return true
	end
	if fields["cb_anisotrophic"] then
		core.setting_set("anisotropic_filter", fields["cb_anisotrophic"])
		return true
	end
	if fields["cb_bilinear"] then
		core.setting_set("bilinear_filter", fields["cb_bilinear"])
		return true
	end
	if fields["cb_trilinear"] then
		core.setting_set("trilinear_filter", fields["cb_trilinear"])
		return true
	end
	if fields["cb_shaders"] then
		if (core.setting_get("video_driver") == "direct3d8" or core.setting_get("video_driver") == "direct3d9") then
			core.setting_set("enable_shaders", "false")
			gamedata.errormessage = fgettext("To enable shaders the OpenGL driver needs to be used.")
		else
			core.setting_set("enable_shaders", fields["cb_shaders"])
		end
		return true
	end
	if fields["cb_connected_glass"] then
		core.setting_set("connected_glass", fields["cb_connected_glass"])
		return true
	end
	if fields["cb_particles"] then
		core.setting_set("enable_particles", fields["cb_particles"])
		return true
	end
	if fields["cb_bumpmapping"] then
		core.setting_set("enable_bumpmapping", fields["cb_bumpmapping"])
	end
	if fields["cb_generate_normalmaps"] then
		core.setting_set("generate_normalmaps", fields["cb_generate_normalmaps"])
	end
	if fields["cb_parallax"] then
		core.setting_set("enable_parallax_occlusion", fields["cb_parallax"])
		return true
	end
	if fields["cb_waving_water"] then
		core.setting_set("enable_waving_water", fields["cb_waving_water"])
		return true
	end
	if fields["cb_waving_leaves"] then
		core.setting_set("enable_waving_leaves", fields["cb_waving_leaves"])
	end
	if fields["cb_waving_plants"] then
		core.setting_set("enable_waving_plants", fields["cb_waving_plants"])
		return true
	end
	if fields["btn_change_keys"] ~= nil then
		core.show_keys_menu()
		return true
	end

	if fields["sb_gui_scaling"] then
		local event = core.explode_scrollbar_event(fields["sb_gui_scaling"])

		if event.type == "CHG" then
			local tosave = string.format("%.2f",scrollbar_to_gui_scale(event.value))
			core.setting_set("gui_scaling",tosave)
			return true
		end
	end
	if fields["cb_touchscreen_target"] then
		core.setting_set("touchtarget", fields["cb_touchscreen_target"])
		return true
	end
	if fields["btn_reset_singleplayer"] then
		showconfirm_reset(this)
		return true
	end

	--Note dropdowns have to be handled LAST!
	local ddhandled = false
	if fields["dd_touchthreshold"] then
		core.setting_set("touchscreen_threshold",fields["dd_touchthreshold"])
		ddhandled = true
	end
	if fields["dd_video_driver"] then
		local video_driver = string.gsub(fields["dd_video_driver"], " ", "")
		core.setting_set("video_driver",string.lower(video_driver))
		ddhandled = true
	end
	
	return ddhandled
end

tab_settings = {
	name = "settings",
	caption = fgettext("Settings"),
	cbf_formspec = formspec,
	cbf_button_handler = handle_settings_buttons
	}
n> if (rel_pos.X < MAP_BLOCKSIZE - 1) { rel_pos.X++; } else { rel_pos.X = 0; block_pos.X++; return true; } break; case 1: if (rel_pos.Y < MAP_BLOCKSIZE - 1) { rel_pos.Y++; } else { rel_pos.Y = 0; block_pos.Y++; return true; } break; case 2: if (rel_pos.Z < MAP_BLOCKSIZE - 1) { rel_pos.Z++; } else { rel_pos.Z = 0; block_pos.Z++; return true; } break; case 3: if (rel_pos.Z > 0) { rel_pos.Z--; } else { rel_pos.Z = MAP_BLOCKSIZE - 1; block_pos.Z--; return true; } break; case 4: if (rel_pos.Y > 0) { rel_pos.Y--; } else { rel_pos.Y = MAP_BLOCKSIZE - 1; block_pos.Y--; return true; } break; case 5: if (rel_pos.X > 0) { rel_pos.X--; } else { rel_pos.X = MAP_BLOCKSIZE - 1; block_pos.X--; return true; } break; } return false; } /* * Removes all light that is potentially emitted by the specified * light sources. These nodes will have zero light. * Returns all nodes whose light became zero but should be re-lighted. * * \param bank the light bank in which the procedure operates * \param from_nodes nodes whose light is removed * \param light_sources nodes that should be re-lighted * \param modified_blocks output, all modified map blocks are added to this */ void unspread_light(Map *map, const NodeDefManager *nodemgr, LightBank bank, UnlightQueue &from_nodes, ReLightQueue &light_sources, std::map<v3s16, MapBlock*> &modified_blocks) { // Stores data popped from from_nodes u8 current_light; ChangingLight current; // Data of the current neighbor mapblock_v3 neighbor_block_pos; relative_v3 neighbor_rel_pos; // A dummy boolean bool is_valid_position; // Direction of the brightest neighbor of the node direction source_dir; while (from_nodes.next(current_light, current)) { // For all nodes that need unlighting // There is no brightest neighbor source_dir = 6; // The current node const MapNode &node = current.block->getNodeNoCheck( current.rel_position, &is_valid_position); const ContentFeatures &f = nodemgr->get(node); // If the node emits light, it behaves like it had a // brighter neighbor. u8 brightest_neighbor_light = f.light_source + 1; for (direction i = 0; i < 6; i++) { //For each neighbor // The node that changed this node has already zero light // and it can't give light to this node if (current.source_direction + i == 5) { continue; } // Get the neighbor's position and block neighbor_rel_pos = current.rel_position; neighbor_block_pos = current.block_position; MapBlock *neighbor_block; if (step_rel_block_pos(i, neighbor_rel_pos, neighbor_block_pos)) { neighbor_block = map->getBlockNoCreateNoEx(neighbor_block_pos); if (neighbor_block == NULL) { current.block->setLightingComplete(bank, i, false); continue; } } else { neighbor_block = current.block; } // Get the neighbor itself MapNode neighbor = neighbor_block->getNodeNoCheck(neighbor_rel_pos, &is_valid_position); const ContentFeatures &neighbor_f = nodemgr->get( neighbor.getContent()); u8 neighbor_light = neighbor.getLightRaw(bank, neighbor_f); // If the neighbor has at least as much light as this node, then // it won't lose its light, since it should have been added to // from_nodes earlier, so its light would be zero. if (neighbor_f.light_propagates && neighbor_light < current_light) { // Unlight, but only if the node has light. if (neighbor_light > 0) { neighbor.setLight(bank, 0, neighbor_f); neighbor_block->setNodeNoCheck(neighbor_rel_pos, neighbor); from_nodes.push(neighbor_light, neighbor_rel_pos, neighbor_block_pos, neighbor_block, i); // The current node was modified earlier, so its block // is in modified_blocks. if (current.block != neighbor_block) { modified_blocks[neighbor_block_pos] = neighbor_block; } } } else { // The neighbor can light up this node. if (neighbor_light < neighbor_f.light_source) { neighbor_light = neighbor_f.light_source; } if (brightest_neighbor_light < neighbor_light) { brightest_neighbor_light = neighbor_light; source_dir = i; } } } // If the brightest neighbor is able to light up this node, // then add this node to the output nodes. if (brightest_neighbor_light > 1 && f.light_propagates) { brightest_neighbor_light--; light_sources.push(brightest_neighbor_light, current.rel_position, current.block_position, current.block, (source_dir == 6) ? 6 : 5 - source_dir /* with opposite direction*/); } } } /* * Spreads light from the specified starting nodes. * * Before calling this procedure, make sure that all ChangingLights * in light_sources have as much light on the map as they have in * light_sources (if the queue contains a node multiple times, the brightest * occurrence counts). * * \param bank the light bank in which the procedure operates * \param light_sources starting nodes * \param modified_blocks output, all modified map blocks are added to this */ void spread_light(Map *map, const NodeDefManager *nodemgr, LightBank bank, LightQueue &light_sources, std::map<v3s16, MapBlock*> &modified_blocks) { // The light the current node can provide to its neighbors. u8 spreading_light; // The ChangingLight for the current node. ChangingLight current; // Position of the current neighbor. mapblock_v3 neighbor_block_pos; relative_v3 neighbor_rel_pos; // A dummy boolean. bool is_valid_position; while (light_sources.next(spreading_light, current)) { spreading_light--; for (direction i = 0; i < 6; i++) { // This node can't light up its light source if (current.source_direction + i == 5) { continue; } // Get the neighbor's position and block neighbor_rel_pos = current.rel_position; neighbor_block_pos = current.block_position; MapBlock *neighbor_block; if (step_rel_block_pos(i, neighbor_rel_pos, neighbor_block_pos)) { neighbor_block = map->getBlockNoCreateNoEx(neighbor_block_pos); if (neighbor_block == NULL) { current.block->setLightingComplete(bank, i, false); continue; } } else { neighbor_block = current.block; } // Get the neighbor itself MapNode neighbor = neighbor_block->getNodeNoCheck(neighbor_rel_pos, &is_valid_position); const ContentFeatures &f = nodemgr->get(neighbor.getContent()); if (f.light_propagates) { // Light up the neighbor, if it has less light than it should. u8 neighbor_light = neighbor.getLightRaw(bank, f); if (neighbor_light < spreading_light) { neighbor.setLight(bank, spreading_light, f); neighbor_block->setNodeNoCheck(neighbor_rel_pos, neighbor); light_sources.push(spreading_light, neighbor_rel_pos, neighbor_block_pos, neighbor_block, i); // The current node was modified earlier, so its block // is in modified_blocks. if (current.block != neighbor_block) { modified_blocks[neighbor_block_pos] = neighbor_block; } } } } } } struct SunlightPropagationUnit{ v2s16 relative_pos; bool is_sunlit; SunlightPropagationUnit(v2s16 relpos, bool sunlit): relative_pos(relpos), is_sunlit(sunlit) {} }; struct SunlightPropagationData{ std::vector<SunlightPropagationUnit> data; v3s16 target_block; }; /*! * Returns true if the node gets sunlight from the * node above it. * * \param pos position of the node. */ bool is_sunlight_above(Map *map, v3s16 pos, const NodeDefManager *ndef) { bool sunlight = true; mapblock_v3 source_block_pos; relative_v3 source_rel_pos; getNodeBlockPosWithOffset(pos + v3s16(0, 1, 0), source_block_pos, source_rel_pos); // If the node above has sunlight, this node also can get it. MapBlock *source_block = map->getBlockNoCreateNoEx(source_block_pos); if (source_block == NULL) { // But if there is no node above, then use heuristics MapBlock *node_block = map->getBlockNoCreateNoEx(getNodeBlockPos(pos)); if (node_block == NULL) { sunlight = false; } else { sunlight = !node_block->getIsUnderground(); } } else { bool is_valid_position; MapNode above = source_block->getNodeNoCheck(source_rel_pos, &is_valid_position); if (is_valid_position) { if (above.getContent() == CONTENT_IGNORE) { // Trust heuristics if (source_block->getIsUnderground()) { sunlight = false; } } else if (above.getLight(LIGHTBANK_DAY, ndef) != LIGHT_SUN) { // If the node above doesn't have sunlight, this // node is in shadow. sunlight = false; } } } return sunlight; } static const LightBank banks[] = { LIGHTBANK_DAY, LIGHTBANK_NIGHT }; void update_lighting_nodes(Map *map, const std::vector<std::pair<v3s16, MapNode>> &oldnodes, std::map<v3s16, MapBlock*> &modified_blocks) { const NodeDefManager *ndef = map->getNodeDefManager(); // For node getter functions bool is_valid_position; // Process each light bank separately for (LightBank bank : banks) { UnlightQueue disappearing_lights(256); ReLightQueue light_sources(256); // Nodes that are brighter than the brightest modified node was // won't change, since they didn't get their light from a // modified node. u8 min_safe_light = 0; for (auto it = oldnodes.cbegin(); it < oldnodes.cend(); ++it) { u8 old_light = it->second.getLight(bank, ndef); if (old_light > min_safe_light) { min_safe_light = old_light; } } // If only one node changed, even nodes with the same brightness // didn't get their light from the changed node. if (oldnodes.size() > 1) { min_safe_light++; } // For each changed node process sunlight and initialize for (auto it = oldnodes.cbegin(); it < oldnodes.cend(); ++it) { // Get position and block of the changed node v3s16 p = it->first; relative_v3 rel_pos; mapblock_v3 block_pos; getNodeBlockPosWithOffset(p, block_pos, rel_pos); MapBlock *block = map->getBlockNoCreateNoEx(block_pos); if (block == NULL || block->isDummy()) { continue; } // Get the new node MapNode n = block->getNodeNoCheck(rel_pos, &is_valid_position); if (!is_valid_position) { break; } // Light of the old node u8 old_light = it->second.getLight(bank, ndef); // Add the block of the added node to modified_blocks modified_blocks[block_pos] = block; // Get new light level of the node u8 new_light = 0; if (ndef->get(n).light_propagates) { if (bank == LIGHTBANK_DAY && ndef->get(n).sunlight_propagates && is_sunlight_above(map, p, ndef)) { new_light = LIGHT_SUN; } else { new_light = ndef->get(n).light_source; for (const v3s16 &neighbor_dir : neighbor_dirs) { v3s16 p2 = p + neighbor_dir; bool is_valid; MapNode n2 = map->getNode(p2, &is_valid); if (is_valid) { u8 spread = n2.getLight(bank, ndef); // If it is sure that the neighbor won't be // unlighted, its light can spread to this node. if (spread > new_light && spread >= min_safe_light) { new_light = spread - 1; } } } } } else { // If this is an opaque node, it still can emit light. new_light = ndef->get(n).light_source; } if (new_light > 0) { light_sources.push(new_light, rel_pos, block_pos, block, 6); } if (new_light < old_light) { // The node became opaque or doesn't provide as much // light as the previous one, so it must be unlighted. // Add to unlight queue n.setLight(bank, 0, ndef); block->setNodeNoCheck(rel_pos, n); disappearing_lights.push(old_light, rel_pos, block_pos, block, 6); // Remove sunlight, if there was any if (bank == LIGHTBANK_DAY && old_light == LIGHT_SUN) { for (s16 y = p.Y - 1;; y--) { v3s16 n2pos(p.X, y, p.Z); MapNode n2; n2 = map->getNode(n2pos, &is_valid_position); if (!is_valid_position) break; // If this node doesn't have sunlight, the nodes below // it don't have too. if (n2.getLight(LIGHTBANK_DAY, ndef) != LIGHT_SUN) { break; } // Remove sunlight and add to unlight queue. n2.setLight(LIGHTBANK_DAY, 0, ndef); map->setNode(n2pos, n2); relative_v3 rel_pos2; mapblock_v3 block_pos2; getNodeBlockPosWithOffset(n2pos, block_pos2, rel_pos2); MapBlock *block2 = map->getBlockNoCreateNoEx( block_pos2); disappearing_lights.push(LIGHT_SUN, rel_pos2, block_pos2, block2, 4 /* The node above caused the change */); } } } else if (new_light > old_light) { // It is sure that the node provides more light than the previous // one, unlighting is not necessary. // Propagate sunlight if (bank == LIGHTBANK_DAY && new_light == LIGHT_SUN) { for (s16 y = p.Y - 1;; y--) { v3s16 n2pos(p.X, y, p.Z); MapNode n2; n2 = map->getNode(n2pos, &is_valid_position); if (!is_valid_position) break; // This should not happen, but if the node has sunlight // then the iteration should stop. if (n2.getLight(LIGHTBANK_DAY, ndef) == LIGHT_SUN) { break; } // If the node terminates sunlight, stop. if (!ndef->get(n2).sunlight_propagates) { break; } relative_v3 rel_pos2; mapblock_v3 block_pos2; getNodeBlockPosWithOffset(n2pos, block_pos2, rel_pos2); MapBlock *block2 = map->getBlockNoCreateNoEx( block_pos2); // Mark node for lighting. light_sources.push(LIGHT_SUN, rel_pos2, block_pos2, block2, 4); } } } } // Remove lights unspread_light(map, ndef, bank, disappearing_lights, light_sources, modified_blocks); // Initialize light values for light spreading. for (u8 i = 0; i <= LIGHT_SUN; i++) { const std::vector<ChangingLight> &lights = light_sources.lights[i]; for (std::vector<ChangingLight>::const_iterator it = lights.begin(); it < lights.end(); ++it) { MapNode n = it->block->getNodeNoCheck(it->rel_position, &is_valid_position); n.setLight(bank, i, ndef); it->block->setNodeNoCheck(it->rel_position, n); } } // Spread lights. spread_light(map, ndef, bank, light_sources, modified_blocks); } } /*! * Borders of a map block in relative node coordinates. * Compatible with type 'direction'. */ const VoxelArea block_borders[] = { VoxelArea(v3s16(15, 0, 0), v3s16(15, 15, 15)), //X+ VoxelArea(v3s16(0, 15, 0), v3s16(15, 15, 15)), //Y+ VoxelArea(v3s16(0, 0, 15), v3s16(15, 15, 15)), //Z+ VoxelArea(v3s16(0, 0, 0), v3s16(15, 15, 0)), //Z- VoxelArea(v3s16(0, 0, 0), v3s16(15, 0, 15)), //Y- VoxelArea(v3s16(0, 0, 0), v3s16(0, 15, 15)) //X- }; /*! * Returns true if: * -the node has unloaded neighbors * -the node doesn't have light * -the node's light is the same as the maximum of * its light source and its brightest neighbor minus one. * . */ bool is_light_locally_correct(Map *map, const NodeDefManager *ndef, LightBank bank, v3s16 pos) { bool is_valid_position; MapNode n = map->getNode(pos, &is_valid_position); const ContentFeatures &f = ndef->get(n); if (f.param_type != CPT_LIGHT) { return true; } u8 light = n.getLightNoChecks(bank, &f); assert(f.light_source <= LIGHT_MAX); u8 brightest_neighbor = f.light_source + 1; for (const v3s16 &neighbor_dir : neighbor_dirs) { MapNode n2 = map->getNode(pos + neighbor_dir, &is_valid_position); u8 light2 = n2.getLight(bank, ndef); if (brightest_neighbor < light2) { brightest_neighbor = light2; } } assert(light <= LIGHT_SUN); return brightest_neighbor == light + 1; } void update_block_border_lighting(Map *map, MapBlock *block, std::map<v3s16, MapBlock*> &modified_blocks) { const NodeDefManager *ndef = map->getNodeDefManager(); bool is_valid_position; for (LightBank bank : banks) { // Since invalid light is not common, do not allocate // memory if not needed. UnlightQueue disappearing_lights(0); ReLightQueue light_sources(0); // Get incorrect lights for (direction d = 0; d < 6; d++) { // For each direction // Get neighbor block v3s16 otherpos = block->getPos() + neighbor_dirs[d]; MapBlock *other = map->getBlockNoCreateNoEx(otherpos); if (other == NULL) { continue; } // Only update if lighting was not completed. if (block->isLightingComplete(bank, d) && other->isLightingComplete(bank, 5 - d)) continue; // Reset flags block->setLightingComplete(bank, d, true); other->setLightingComplete(bank, 5 - d, true); // The two blocks and their connecting surfaces MapBlock *blocks[] = {block, other}; VoxelArea areas[] = {block_borders[d], block_borders[5 - d]}; // For both blocks for (u8 blocknum = 0; blocknum < 2; blocknum++) { MapBlock *b = blocks[blocknum]; VoxelArea a = areas[blocknum]; // For all nodes for (s32 x = a.MinEdge.X; x <= a.MaxEdge.X; x++) for (s32 z = a.MinEdge.Z; z <= a.MaxEdge.Z; z++) for (s32 y = a.MinEdge.Y; y <= a.MaxEdge.Y; y++) { MapNode n = b->getNodeNoCheck(x, y, z, &is_valid_position); u8 light = n.getLight(bank, ndef); // Sunlight is fixed if (light < LIGHT_SUN) { // Unlight if not correct if (!is_light_locally_correct(map, ndef, bank, v3s16(x, y, z) + b->getPosRelative())) { // Initialize for unlighting n.setLight(bank, 0, ndef); b->setNodeNoCheck(x, y, z, n); modified_blocks[b->getPos()]=b; disappearing_lights.push(light, relative_v3(x, y, z), b->getPos(), b, 6); } } } } } // Remove lights unspread_light(map, ndef, bank, disappearing_lights, light_sources, modified_blocks); // Initialize light values for light spreading. for (u8 i = 0; i <= LIGHT_SUN; i++) { const std::vector<ChangingLight> &lights = light_sources.lights[i]; for (std::vector<ChangingLight>::const_iterator it = lights.begin(); it < lights.end(); ++it) { MapNode n = it->block->getNodeNoCheck(it->rel_position, &is_valid_position); n.setLight(bank, i, ndef); it->block->setNodeNoCheck(it->rel_position, n); } } // Spread lights. spread_light(map, ndef, bank, light_sources, modified_blocks); } } /*! * Resets the lighting of the given VoxelManipulator to * complete darkness and full sunlight. * Operates in one map sector. * * \param offset contains the least x and z node coordinates * of the map sector. * \param light incoming sunlight, light[x][z] is true if there * is sunlight above the voxel manipulator at the given x-z coordinates. * The array's indices are relative node coordinates in the sector. * After the procedure returns, this contains outgoing light at * the bottom of the voxel manipulator. */ void fill_with_sunlight(MMVManip *vm, const NodeDefManager *ndef, v2s16 offset, bool light[MAP_BLOCKSIZE][MAP_BLOCKSIZE]) { // Distance in array between two nodes on top of each other. s16 ystride = vm->m_area.getExtent().X; // Cache the ignore node. MapNode ignore = MapNode(CONTENT_IGNORE); // For each column of nodes: for (s16 z = 0; z < MAP_BLOCKSIZE; z++) for (s16 x = 0; x < MAP_BLOCKSIZE; x++) { // Position of the column on the map. v2s16 realpos = offset + v2s16(x, z); // Array indices in the voxel manipulator s32 maxindex = vm->m_area.index(realpos.X, vm->m_area.MaxEdge.Y, realpos.Y); s32 minindex = vm->m_area.index(realpos.X, vm->m_area.MinEdge.Y, realpos.Y); // True if the current node has sunlight. bool lig = light[z][x]; // For each node, downwards: for (s32 i = maxindex; i >= minindex; i -= ystride) { MapNode *n; if (vm->m_flags[i] & VOXELFLAG_NO_DATA) n = &ignore; else