Compare commits

..

88 Commits
0.4.8 ... 0.4.9

Author SHA1 Message Date
kwolekr
1ad81c1ce3 Bump version to 0.4.9 2014-01-01 16:54:28 -05:00
kwolekr
25b1cca415 Fix exception caused by destroying sockets on Server shutdown 2013-12-31 15:00:49 -05:00
kaeza
767b2e7b17 Fix enum element name in Lua HUD code (position vs. pos) 2013-12-31 06:13:54 +01:00
ShadowNinja
2902a29c2d Initialize world before creating BanManager and RollbackManager 2013-12-30 14:29:33 -05:00
ShadowNinja
829426c714 Fix InventoryList reading order
Lua does not guarantee that the indexes of a table will be in numerical order.
2013-12-30 14:00:05 -05:00
Novatux
8e1d78e9de Revert "Fix minetest.facedir_to_dir when param2 is 5 or 7."
This reverts commit 4e5760a541.
2013-12-30 18:45:34 +01:00
PilzAdam
4b31a38ae9 Remove FPS from window title
This doubles performance on some window managers (WTF?!)
2013-12-25 03:52:44 +01:00
Kahrl
bf9f50bc0e Make SHA1::addBytes(..., 0) a no-op instead of an assertion failure 2013-12-25 00:29:00 +01:00
Kahrl
3aa28bc7a2 Use sleep_ms instead of select in httpfetch when max_fd == -1, fixes WSAEINVAL 2013-12-24 19:28:09 +01:00
Kahrl
a5377251e2 Fix particle code ignoring return value of std::vector::erase(). Wat. 2013-12-23 15:11:53 +01:00
RealBadAngel
a58b47d0f6 Fix wrong names for parallax settings in config example. 2013-12-22 00:30:17 +01:00
RealBadAngel
7ae0b90ff7 Bugfix to get_all_craft_recipes.
Indexes for empty slots shall not be skipped.
2013-12-21 23:03:51 +01:00
Kahrl
a50db0e824 Check for denied access in itemdef/nodedef/media fetch loop 2013-12-19 21:47:08 +01:00
ShadowNinja
e1f9ba435f Don't throw a error when writing JSON fails 2013-12-18 18:18:43 -05:00
Kahrl
ba8fa0bd42 Log guilty node name when allow_metadata_inventory_move/put/take fails 2013-12-18 23:04:13 +01:00
ShadowNinja
1ed90c90c3 Add 'minetest.write_json' 2013-12-18 16:46:53 -05:00
ShadowNinja
49cec3f782 Handle LuaErrors in Lua -> C++ calls on LuaJIT 2013-12-18 16:35:55 -05:00
BlockMen
38d112033b Fix formspec field labels 2013-12-17 01:13:16 +01:00
sapier
54dbd78f90 Fix broken httpfetch due to SimpleThread removal 2013-12-16 23:31:56 +01:00
BlockMen
c9ac9992c7 Fix MSVC compiling issue 2013-12-16 01:13:08 +01:00
sapier
3946d2a504 Reduce cloud coloring for colored fog and cloud mode 2013-12-16 00:16:06 +01:00
PilzAdam
5fd92d67d5 Remove an unused function and include from client.cpp 2013-12-15 19:57:08 +01:00
sapier
c120ea57c9 Add setting to disable direction dependent fog and sky colors 2013-12-15 15:30:02 +01:00
MirceaKitsune
848f80b2e5 Directional fog + horizon colors, based on sun & moon positions at sunrise / sunset 2013-12-15 14:28:50 +01:00
sapier
e9e9fd7c3f Replace SimpleThread by JThread now implementing same features 2013-12-15 13:39:42 +01:00
BlockMen
9772322613 Add alpha setting to font shadow 2013-12-14 21:11:38 +01:00
kwolekr
7a4c1e7327 Update mapgen params in ServerMap after Mapgen init 2013-12-14 10:49:20 -05:00
kwolekr
15c316765d Handle Lua errors in on_generate callbacks instead of throwing SIGABRT 2013-12-14 01:59:16 -05:00
kwolekr
c3708b456e Add map feature generation notify Lua API 2013-12-14 01:52:24 -05:00
ShadowNinja
83853ccd41 Remove support for optdepends.txt 2013-12-13 13:10:24 -05:00
Kahrl
e275b7099a Don't call curl_global_init twice 2013-12-13 18:08:53 +01:00
sapier
0d990bd189 Replace any direct curl usage by httpfetch 2013-12-13 18:05:46 +01:00
Kahrl
b03135548b Use httpfetch_async in serverlist announce code 2013-12-13 18:05:39 +01:00
Kahrl
0a903e69fb Implement urlencode and urldecode 2013-12-13 18:05:35 +01:00
Kahrl
0404bbf671 Rewrite client media download and support hash-based remote download
Move most of the media-related code in client.cpp into a new class
ClientMediaDownloader (clientmedia.cpp, clientmedia.h). Among other
things, this class does the following things:

- Download [remote_server][sha1] instead of [remote_server][name]. This
is to support servers that provide the same file name with different
contents.
- Initially fetch [remote_server]index.mth. This file should follow the
Minetest Hashset format (currently version 1) and contain a list of SHA1
hashes that exist on the server.
- The list of needed SHA1s is uploaded (via HTTP POST) when index.mth is
requested, so servers can optionally narrow down the list to the needs
of the client.
- If index.mth is missing (HTTP response code 404), we enter compat mode,
fetching [remote_server][name] as before this commit.
- remote_server can now contain multiple servers, separated by commas.
The downloader code attempts to split requests between the different
servers, as permitted by each server's index.mth. If one server claims
to have a file but actually doesn't (or something fails), we ask a
different server that also claims to have it.
- As before, when none of the remote servers provide a particular
file, we download it via the conventional method, i.e. using
the minetest protocol: TOSERVER_REQUEST_MEDIA / TOCLIENT_MEDIA.
- Bugfix: Every downloaded file's SHA1 is now verified against the SHA1
announced by the minetest server (before loading it and inserting it
into the file cache).
- Bugfix: Only send TOSERVER_RECEIVED_MEDIA when we actually have all
media. This should fix #863.
2013-12-13 18:05:20 +01:00
Kahrl
0ea3e6dbe2 Implement httpfetch module and initialize it from main()
Add curl_parallel_limit setting that will replace media_fetch_threads in
a later commit.

Fix a typo in MutexedQueue::pop_back() that made it impossible to compile
code that used this function. (Noticed this while implementing httpfetch.)
2013-12-13 18:05:10 +01:00
Ilya Zhuravlev
67bf7130ce Fix a bug in GenericCAO GENERIC_CMD_UPDATE_POSITION parsing. 2013-12-13 13:18:00 +04:00
stujones11
d9ef072305 Make line_of_sight return blocking node position 2013-12-12 16:11:00 -05:00
kaeza
33de69a173 Add 'on_prejoinplayer' callback 2013-12-12 13:42:14 -05:00
Ilya Zhuravlev
76036abdb0 Add configurable font shadow. 2013-12-12 21:47:54 +04:00
sapier
4ccaa6d0af Implement search tab and version picker 2013-12-11 23:07:38 +01:00
Ilya Zhuravlev
dfd1f87762 Add Lithuanian translation. 2013-12-11 22:10:56 +04:00
PilzAdam
6c4a8766ab Check if width, height or start index of a list[] is negative 2013-12-09 22:39:24 +01:00
RealBadAngel
dae03382bf Optimize shaders code. Add settings at compile time. 2013-12-09 13:28:50 +01:00
kwolekr
50b0e9f7a4 Mapgen Indev: Fix segfault in cave generation due to uninitialized variable 2013-12-08 22:13:05 -05:00
Novatux
206565d965 Fix shaders on some GPUs 2013-12-08 08:01:20 +01:00
kwolekr
3cc45fd8ad Fix leak and possible segfault in minetest.set_mapgen_params 2013-12-08 01:37:41 -05:00
kwolekr
83cc882335 EmergeManager: Fix Lua mapgen override param handling 2013-12-08 01:31:06 -05:00
kwolekr
bbae8eb751 Dungeongen: Create dungeon gen tuneables; add desert temples for Mapgen V6 2013-12-07 22:45:26 -05:00
ShadowNinja
a0dce51af6 Move script_run_callbacks to Lua 2013-12-07 22:03:07 -05:00
ShadowNinja
96f753a108 Fix paralax occlusion setting 2013-12-06 22:42:09 -05:00
sfan5
e15681080e Fix small mistake in Lua API docs 2013-12-06 18:37:32 +01:00
sapier
35f88ac67c Fix win32 jthread implementation 2013-12-06 17:49:10 +01:00
sweetbomber
22dbbf0a6f Improve (re)spawn, add cache_block_before_spawn and max_spawn_height settings 2013-12-05 16:09:02 -05:00
ShadowNinja
000da6b25d Only create one alias metatable 2013-12-04 14:31:55 -05:00
PilzAdam
e51ad5337f Use a table in set_physics_override() 2013-12-03 23:20:00 +01:00
PilzAdam
15be2659ea Add sneak and sneak_glitch to set_physics_override() 2013-12-03 23:20:00 +01:00
Sfan5
d9de9f23d9 Fix LevelDB maps 2013-12-03 20:03:20 +01:00
RealBadAngel
2330267d22 Shaders rework. 2013-12-03 18:55:25 +01:00
PilzAdam
60113bde74 Dont move item back when not clicking on a slot 2013-12-03 18:05:36 +01:00
proller
1309e52198 Rename names -> can_see_far_names in announce 2013-12-03 20:14:06 +04:00
sapier
5004f31575 Fix broken async locking in release build 2013-12-03 17:50:00 +02:00
proller
6cbd1b8bf7 Remove link to #, add unlimited_player_transfer_distance to announce 2013-12-03 19:48:43 +04:00
Sfan5
6fa3892a71 Fix MinGW build 2013-12-01 20:58:46 +01:00
sapier
04e9a9d541 Cleanup jthread and fix win32 build 2013-12-01 16:25:46 +01:00
kwolekr
f3439c40d8 Cavegen: Fix possible out-of-bounds heightmap access 2013-12-01 08:57:35 -05:00
kwolekr
20e3d550fa Decoration: Add schematic Y-slice probability support 2013-12-01 01:24:59 -05:00
sapier
de0cdbc01c Fix log threadname lookup handling not beeing threadsafe 2013-11-30 21:22:15 +01:00
0gb.us
e605d70256 Assume a selection box for fences
Similar to assuming a selection box for the nodebox drawtype, minetest.register_item() now assumes a selection box for the fencelike drawtype.
2013-11-30 13:22:50 -05:00
ShadowNinja
4696c59a5f Add error handler to async step 2013-11-30 13:05:13 -05:00
ShadowNinja
4594ba6522 Optimize table creation 2013-11-30 13:05:13 -05:00
Novatux
06baf05c64 Add documentation for minetest.swap_node 2013-11-30 18:57:56 +01:00
Novatux
d879a539cd Add minetest.swap_node 2013-11-30 18:37:56 +01:00
Ilya Zhuravlev
752e11e114 Fix MSVC build. 2013-11-30 21:00:18 +04:00
kwolekr
9a927476ca Change default value of is_ground_content to true
Most modders would otherwise forget to explicitly define this, and generated nodes aliased from mods would wall-off caves
2013-11-30 02:37:10 -05:00
kwolekr
ff7d7080e3 LuaVoxelManip: Add area parameters back to calc_lighting and set_lighting, made optional this time; also fixed a slight bug with night values being ignored 2013-11-30 00:27:39 -05:00
kwolekr
769b2d7c05 LuaVoxelManip: Add get_light_data() and set_light_data() 2013-11-30 00:01:43 -05:00
MetaDucky
5be786c804 Fixed potential NULL pointer and leak when setting node metadata 2013-11-29 23:35:16 -05:00
kwolekr
747bc40840 Cavegen: Respect is_ground_content MapNode setting; fix some code formatting issues 2013-11-29 20:54:04 -05:00
sapier
d19a69cd0d Fix broken thread stop handling 2013-11-30 02:15:31 +01:00
sapier
d02ce1cf4d Fix old gcc build 2013-11-29 23:36:18 +01:00
sapier
2e66aca357 Fix modstore/favourites hang by adding asynchronous lua job support 2013-11-29 22:09:14 +01:00
sapier
b08d7558de Split server/client port setting to avoid port number clashes in mainmenu 2013-11-28 21:43:06 +01:00
ShadowNinja
ff25218374 Don't use variable length arrays 2013-11-28 11:22:41 -05:00
ShadowNinja
7433d65d3e Rollback fixes and get_node_actions 2013-11-27 18:13:06 -05:00
Mario Barrera
1fd9a11e30 SQLite rollback 2013-11-27 18:13:06 -05:00
sapier
4f246f0e22 Fix line_of_sight (AGAIN) 2013-11-25 00:04:05 +01:00
PilzAdam
f87c1c2410 Reenable -dev suffix 2013-11-24 19:01:25 +01:00
179 changed files with 9493 additions and 3238 deletions

View File

@@ -12,7 +12,7 @@ set(VERSION_EXTRA "" CACHE STRING "Stuff to append to version string")
# Also remember to set PROTOCOL_VERSION in clientserver.h when releasing
set(VERSION_MAJOR 0)
set(VERSION_MINOR 4)
set(VERSION_PATCH 8)
set(VERSION_PATCH 9)
if(VERSION_EXTRA)
set(VERSION_PATCH ${VERSION_PATCH}-${VERSION_EXTRA})
else()

19
builtin/async_env.lua Normal file
View File

@@ -0,0 +1,19 @@
engine.log("info","Initializing Asynchronous environment")
dofile(SCRIPTDIR .. DIR_DELIM .. "misc_helpers.lua")
function engine.job_processor(serialized_function, serialized_data)
local fct = marshal.decode(serialized_function)
local params = marshal.decode(serialized_data)
local retval = marshal.encode(nil)
if fct ~= nil and type(fct) == "function" then
local result = fct(params)
retval = marshal.encode(result)
else
engine.log("error","ASYNC WORKER: unable to deserialize function")
end
return retval,retval:len()
end

59
builtin/async_event.lua Normal file
View File

@@ -0,0 +1,59 @@
local tbl = engine or minetest
tbl.async_jobs = {}
if engine ~= nil then
function tbl.async_event_handler(jobid, serialized_retval)
local retval = nil
if serialized_retval ~= "ERROR" then
retval= marshal.decode(serialized_retval)
else
tbl.log("error","Error fetching async result")
end
assert(type(tbl.async_jobs[jobid]) == "function")
tbl.async_jobs[jobid](retval)
tbl.async_jobs[jobid] = nil
end
else
minetest.register_globalstep(
function(dtime)
local list = tbl.get_finished_jobs()
for i=1,#list,1 do
local retval = marshal.decode(list[i].retval)
assert(type(tbl.async_jobs[jobid]) == "function")
tbl.async_jobs[list[i].jobid](retval)
tbl.async_jobs[list[i].jobid] = nil
end
end)
end
function tbl.handle_async(fct, parameters, callback)
--serialize fct
local serialized_fct = marshal.encode(fct)
assert(marshal.decode(serialized_fct) ~= nil)
--serialize parameters
local serialized_params = marshal.encode(parameters)
if serialized_fct == nil or
serialized_params == nil or
serialized_fct:len() == 0 or
serialized_params:len() == 0 then
return false
end
local jobid = tbl.do_async_callback( serialized_fct,
serialized_fct:len(),
serialized_params,
serialized_params:len())
tbl.async_jobs[jobid] = callback
return true
end

View File

@@ -515,34 +515,45 @@ minetest.register_on_punchnode(function(pos, node, puncher)
end)
minetest.register_chatcommand("rollback_check", {
params = "[<range>] [<seconds>]",
params = "[<range>] [<seconds>] [limit]",
description = "check who has last touched a node or near it, "..
"max. <seconds> ago (default range=0, seconds=86400=24h)",
"max. <seconds> ago (default range=0, seconds=86400=24h, limit=5)",
privs = {rollback=true},
func = function(name, param)
local range, seconds = string.match(param, "(%d+) *(%d*)")
local range, seconds, limit =
param:match("(%d+) *(%d*) *(%d*)")
range = tonumber(range) or 0
seconds = tonumber(seconds) or 86400
minetest.chat_send_player(name, "Punch a node (limits set: range="..
dump(range).." seconds="..dump(seconds).."s)")
limit = tonumber(limit) or 5
if limit > 100 then
minetest.chat_send_player(name, "That limit is too high!")
return
end
minetest.chat_send_player(name, "Punch a node (range="..
range..", seconds="..seconds.."s, limit="..limit..")")
minetest.rollback_punch_callbacks[name] = function(pos, node, puncher)
local name = puncher:get_player_name()
minetest.chat_send_player(name, "Checking...")
local actor, act_p, act_seconds =
minetest.rollback_get_last_node_actor(pos, range, seconds)
if actor == "" then
minetest.chat_send_player(name, "Checking "..minetest.pos_to_string(pos).."...")
local actions = minetest.rollback_get_node_actions(pos, range, seconds, limit)
local num_actions = #actions
if num_actions == 0 then
minetest.chat_send_player(name, "Nobody has touched the "..
"specified location in "..dump(seconds).." seconds")
"specified location in "..seconds.." seconds")
return
end
local nodedesc = "this node"
if act_p.x ~= pos.x or act_p.y ~= pos.y or act_p.z ~= pos.z then
nodedesc = minetest.pos_to_string(act_p)
local time = os.time()
for i = num_actions, 1, -1 do
local action = actions[i]
minetest.chat_send_player(name,
("%s %s %s -> %s %d seconds ago.")
:format(
minetest.pos_to_string(action.pos),
action.actor,
action.oldnode.name,
action.newnode.name,
time - action.time))
end
local nodename = minetest.get_node(act_p).name
minetest.chat_send_player(name, "Last actor on "..nodedesc..
" was "..actor..", "..dump(act_seconds)..
"s ago (node is now "..nodename..")")
end
end,
})
@@ -554,7 +565,7 @@ minetest.register_chatcommand("rollback", {
func = function(name, param)
local target_name, seconds = string.match(param, ":([^ ]+) *(%d*)")
if not target_name then
local player_name = nil;
local player_name = nil
player_name, seconds = string.match(param, "([^ ]+) *(%d*)")
if not player_name then
minetest.chat_send_player(name, "Invalid parameters. See /help rollback and /help rollback_check")
@@ -564,13 +575,13 @@ minetest.register_chatcommand("rollback", {
end
seconds = tonumber(seconds) or 60
minetest.chat_send_player(name, "Reverting actions of "..
dump(target_name).." since "..dump(seconds).." seconds.")
target_name.." since "..seconds.." seconds.")
local success, log = minetest.rollback_revert_actions_by(
target_name, seconds)
if #log > 10 then
if #log > 100 then
minetest.chat_send_player(name, "(log is too long to show)")
else
for _,line in ipairs(log) do
for _, line in pairs(log) do
minetest.chat_send_player(name, line)
end
end

View File

@@ -46,3 +46,8 @@ setmetatable(minetest.env, {
return rawget(table, key)
end
})
function minetest.rollback_get_last_node_actor(pos, range, seconds)
return minetest.rollback_get_node_actions(pos, range, seconds, 1)[1]
end

View File

@@ -17,6 +17,20 @@
--------------------------------------------------------------------------------
-- Generic implementation of a filter/sortable list --
-- Usage: --
-- Filterlist needs to be initialized on creation. To achieve this you need to --
-- pass following functions: --
-- raw_fct() (mandatory): --
-- function returning a table containing the elements to be filtered --
-- compare_fct(element1,element2) (mandatory): --
-- function returning true/false if element1 is same element as element2 --
-- uid_match_fct(element1,uid) (optional) --
-- function telling if uid is attached to element1 --
-- filter_fct(element,filtercriteria) (optional) --
-- function returning true/false if filtercriteria met to element --
-- fetch_param (optional) --
-- parameter passed to raw_fct to aquire correct raw data --
-- --
--------------------------------------------------------------------------------
filterlist = {}
@@ -157,7 +171,7 @@ function filterlist.process(this)
this.m_processed_list = {}
for k,v in pairs(this.m_raw_list) do
if this.m_filtercriteria == nil or
if this.m_filtercriteria == nil or
this.m_filter_fct(v,this.m_filtercriteria) then
table.insert(this.m_processed_list,v)
end
@@ -167,7 +181,7 @@ function filterlist.process(this)
return
end
if this.m_sort_list[this.m_sortmode] ~= nil and
if this.m_sort_list[this.m_sortmode] ~= nil and
type(this.m_sort_list[this.m_sortmode]) == "function" then
this.m_sort_list[this.m_sortmode](this)
@@ -237,7 +251,7 @@ function compare_worlds(world1,world2)
end
--------------------------------------------------------------------------------
function sort_worlds_alphabetic(this)
function sort_worlds_alphabetic(this)
table.sort(this.m_processed_list, function(a, b)
--fixes issue #857 (crash due to sorting nil in worldlist)

View File

@@ -98,7 +98,7 @@ function minetest.facedir_to_dir(facedir)
--indexed into by a table of correlating facedirs
[({[0]=1, 2, 3, 4,
5, 4, 6, 2,
5, 2, 6, 4,
6, 2, 5, 4,
1, 5, 3, 6,
1, 6, 3, 5,
@@ -501,7 +501,7 @@ minetest.nodedef_default = {
post_effect_color = {a=0, r=0, g=0, b=0},
paramtype = "none",
paramtype2 = "none",
is_ground_content = false,
is_ground_content = true,
sunlight_propagates = false,
walkable = true,
pointable = true,

File diff suppressed because it is too large Load Diff

View File

@@ -24,17 +24,15 @@ minetest.registered_aliases = {}
-- For tables that are indexed by item name:
-- If table[X] does not exist, default to table[minetest.registered_aliases[X]]
local function set_alias_metatable(table)
setmetatable(table, {
__index = function(name)
return rawget(table, minetest.registered_aliases[name])
end
})
end
set_alias_metatable(minetest.registered_items)
set_alias_metatable(minetest.registered_nodes)
set_alias_metatable(minetest.registered_craftitems)
set_alias_metatable(minetest.registered_tools)
local alias_metatable = {
__index = function(t, name)
return rawget(t, minetest.registered_aliases[name])
end
}
setmetatable(minetest.registered_items, alias_metatable)
setmetatable(minetest.registered_nodes, alias_metatable)
setmetatable(minetest.registered_craftitems, alias_metatable)
setmetatable(minetest.registered_tools, alias_metatable)
-- These item names may not be used because they would interfere
-- with legacy itemstrings
@@ -106,6 +104,11 @@ function minetest.register_item(name, itemdef)
-- Use the nodebox as selection box if it's not set manually
if itemdef.drawtype == "nodebox" and not itemdef.selection_box then
itemdef.selection_box = itemdef.node_box
elseif itemdef.drawtype == "fencelike" and not itemdef.selection_box then
itemdef.selection_box = {
type = "fixed",
fixed = {-1/8, -1/2, -1/8, 1/8, 1/2, 1/8},
}
end
setmetatable(itemdef, {__index = minetest.nodedef_default})
minetest.registered_nodes[itemdef.name] = itemdef
@@ -311,6 +314,45 @@ minetest.register_item(":", {
groups = {not_in_creative_inventory=1},
})
function minetest.run_callbacks(callbacks, mode, ...)
assert(type(callbacks) == "table")
local cb_len = #callbacks
if cb_len == 0 then
if mode == 2 or mode == 3 then
return true
elseif mode == 4 or mode == 5 then
return false
end
end
local ret = nil
for i = 1, cb_len do
local cb_ret = callbacks[i](...)
if mode == 0 and i == 1 then
ret = cb_ret
elseif mode == 1 and i == cb_len then
ret = cb_ret
elseif mode == 2 then
if not cb_ret or i == 1 then
ret = cb_ret
end
elseif mode == 3 then
if cb_ret then
return cb_ret
end
ret = cb_ret
elseif mode == 4 then
if (cb_ret and not ret) or i == 1 then
ret = cb_ret
end
elseif mode == 5 and cb_ret then
return cb_ret
end
end
return ret
end
--
-- Callback registration
--
@@ -338,6 +380,7 @@ minetest.registered_on_generateds, minetest.register_on_generated = make_registr
minetest.registered_on_newplayers, minetest.register_on_newplayer = make_registration()
minetest.registered_on_dieplayers, minetest.register_on_dieplayer = make_registration()
minetest.registered_on_respawnplayers, minetest.register_on_respawnplayer = make_registration()
minetest.registered_on_prejoinplayers, minetest.register_on_prejoinplayer = make_registration()
minetest.registered_on_joinplayers, minetest.register_on_joinplayer = make_registration()
minetest.registered_on_leaveplayers, minetest.register_on_leaveplayer = make_registration()
minetest.registered_on_player_receive_fields, minetest.register_on_player_receive_fields = make_registration_reverse()

View File

@@ -22,7 +22,7 @@ function get_mods(path,retval,modpack)
for i=1,#mods,1 do
local toadd = {}
local modpackfile = nil
toadd.name = mods[i]
toadd.path = path .. DIR_DELIM .. mods[i] .. DIR_DELIM
if modpack ~= nil and
@@ -33,7 +33,7 @@ function get_mods(path,retval,modpack)
local error = nil
modpackfile,error = io.open(filename,"r")
end
if modpackfile ~= nil then
modpackfile:close()
toadd.is_modpack = true
@@ -52,14 +52,16 @@ modmgr = {}
function modmgr.extract(modfile)
if modfile.type == "zip" then
local tempfolder = os.tempfolder()
if tempfolder ~= nil and
tempfodler ~= "" then
tempfolder ~= "" then
engine.create_dir(tempfolder)
engine.extract_zip(modfile.name,tempfolder)
return tempfolder
if engine.extract_zip(modfile.name,tempfolder) then
return tempfolder
end
end
end
return nil
end
-------------------------------------------------------------------------------
@@ -80,7 +82,7 @@ function modmgr.getbasefolder(temppath)
path=temppath
}
end
testfile = io.open(temppath .. DIR_DELIM .. "modpack.txt","r")
if testfile ~= nil then
testfile:close()
@@ -89,9 +91,9 @@ function modmgr.getbasefolder(temppath)
path=temppath
}
end
local subdirs = engine.get_dirlist(temppath,true)
--only single mod or modpack allowed
if #subdirs ~= 1 then
return {
@@ -100,7 +102,7 @@ function modmgr.getbasefolder(temppath)
}
end
testfile =
testfile =
io.open(temppath .. DIR_DELIM .. subdirs[1] ..DIR_DELIM .."init.lua","r")
if testfile ~= nil then
testfile:close()
@@ -109,8 +111,8 @@ function modmgr.getbasefolder(temppath)
path= temppath .. DIR_DELIM .. subdirs[1]
}
end
testfile =
testfile =
io.open(temppath .. DIR_DELIM .. subdirs[1] ..DIR_DELIM .."modpack.txt","r")
if testfile ~= nil then
testfile:close()
@@ -131,7 +133,7 @@ function modmgr.isValidModname(modpath)
if modpath:find("-") ~= nil then
return false
end
return true
end
@@ -142,20 +144,20 @@ function modmgr.parse_register_line(line)
if pos1 ~= nil then
pos2 = line:find("\"",pos1+1)
end
if pos1 ~= nil and pos2 ~= nil then
local item = line:sub(pos1+1,pos2-1)
if item ~= nil and
item ~= "" then
local pos3 = item:find(":")
if pos3 ~= nil then
local retval = item:sub(1,pos3-1)
if retval ~= nil and
retval ~= "" then
return retval
end
end
end
end
end
@@ -169,10 +171,10 @@ function modmgr.parse_dofile_line(modpath,line)
if pos1 ~= nil then
pos2 = line:find("\"",pos1+1)
end
if pos1 ~= nil and pos2 ~= nil then
local filename = line:sub(pos1+1,pos2-1)
if filename ~= nil and
filename ~= "" and
filename:find(".lua") then
@@ -187,37 +189,37 @@ function modmgr.identify_modname(modpath,filename)
local testfile = io.open(modpath .. DIR_DELIM .. filename,"r")
if testfile ~= nil then
local line = testfile:read()
while line~= nil do
local modname = nil
if line:find("minetest.register_tool") then
modname = modmgr.parse_register_line(line)
end
if line:find("minetest.register_craftitem") then
modname = modmgr.parse_register_line(line)
end
if line:find("minetest.register_node") then
modname = modmgr.parse_register_line(line)
end
if line:find("dofile") then
modname = modmgr.parse_dofile_line(modpath,line)
end
if modname ~= nil then
testfile:close()
return modname
end
line = testfile:read()
end
testfile:close()
end
return nil
end
@@ -231,29 +233,29 @@ function modmgr.tab()
if modmgr.selected_mod == nil then
modmgr.selected_mod = 1
end
local retval =
local retval =
"vertlabel[0,-0.25;".. fgettext("MODS") .. "]" ..
"label[0.8,-0.25;".. fgettext("Installed Mods:") .. "]" ..
"textlist[0.75,0.25;4.5,4;modlist;" ..
modmgr.render_modlist(modmgr.global_mods) ..
modmgr.render_modlist(modmgr.global_mods) ..
";" .. modmgr.selected_mod .. "]"
retval = retval ..
"label[0.8,4.2;" .. fgettext("Add mod:") .. "]" ..
"label[0.8,4.2;" .. fgettext("Add mod:") .. "]" ..
-- TODO Disabled due to upcoming release 0.4.8 and irrlicht messing up localization
-- "button[0.75,4.85;1.8,0.5;btn_mod_mgr_install_local;".. fgettext("Local install") .. "]" ..
"button[2.45,4.85;3.05,0.5;btn_mod_mgr_download;".. fgettext("Online mod repository") .. "]"
local selected_mod = nil
if filterlist.size(modmgr.global_mods) >= modmgr.selected_mod then
selected_mod = filterlist.get_list(modmgr.global_mods)[modmgr.selected_mod]
end
if selected_mod ~= nil then
local modscreenshot = nil
--check for screenshot beeing available
local screenshotfilename = selected_mod.path .. DIR_DELIM .. "screenshot.png"
local error = nil
@@ -262,40 +264,40 @@ function modmgr.tab()
screenshotfile:close()
modscreenshot = screenshotfilename
end
if modscreenshot == nil then
modscreenshot = modstore.basetexturedir .. "no_screenshot.png"
end
retval = retval
retval = retval
.. "image[5.5,0;3,2;" .. engine.formspec_escape(modscreenshot) .. "]"
.. "label[8.25,0.6;" .. selected_mod.name .. "]"
local descriptionlines = nil
error = nil
local descriptionfilename = selected_mod.path .. "description.txt"
descriptionfile,error = io.open(descriptionfilename,"r")
if error == nil then
descriptiontext = descriptionfile:read("*all")
descriptionlines = engine.splittext(descriptiontext,42)
descriptionfile:close()
else
descriptionlines = {}
table.insert(descriptionlines,fgettext("No mod description available"))
end
retval = retval ..
retval = retval ..
"label[5.5,1.7;".. fgettext("Mod information:") .. "]" ..
"textlist[5.5,2.2;6.2,2.4;description;"
for i=1,#descriptionlines,1 do
retval = retval .. engine.formspec_escape(descriptionlines[i]) .. ","
end
if selected_mod.is_modpack then
retval = retval .. ";0]" ..
retval = retval .. ";0]" ..
"button[10,4.85;2,0.5;btn_mod_mgr_rename_modpack;" ..
fgettext("Rename") .. "]"
retval = retval .. "button[5.5,4.85;4.5,0.5;btn_mod_mgr_delete_mod;"
@@ -304,11 +306,11 @@ function modmgr.tab()
--show dependencies
retval = retval .. ",Depends:,"
toadd = modmgr.get_dependencies(selected_mod.path)
retval = retval .. toadd .. ";0]"
retval = retval .. "button[5.5,4.85;4.5,0.5;btn_mod_mgr_delete_mod;"
.. fgettext("Uninstall selected mod") .. "]"
end
@@ -320,15 +322,15 @@ end
function modmgr.dialog_rename_modpack()
local mod = filterlist.get_list(modmgr.global_mods)[modmgr.selected_mod]
local retval =
local retval =
"label[1.75,1;".. fgettext("Rename Modpack:") .. "]"..
"field[4.5,1.4;6,0.5;te_modpack_name;;" ..
mod.name ..
"]" ..
"button[5,4.2;2.6,0.5;dlg_rename_modpack_confirm;"..
"button[5,4.2;2.6,0.5;dlg_rename_modpack_confirm;"..
fgettext("Accept") .. "]" ..
"button[7.5,4.2;2.8,0.5;dlg_rename_modpack_cancel;"..
"button[7.5,4.2;2.8,0.5;dlg_rename_modpack_cancel;"..
fgettext("Cancel") .. "]"
return retval
@@ -340,15 +342,15 @@ function modmgr.precheck()
if modmgr.world_config_selected_world == nil then
modmgr.world_config_selected_world = 1
end
if modmgr.world_config_selected_mod == nil then
modmgr.world_config_selected_mod = 1
end
if modmgr.hide_gamemods == nil then
modmgr.hide_gamemods = true
end
if modmgr.hide_modpackcontents == nil then
modmgr.hide_modpackcontents = true
end
@@ -357,27 +359,27 @@ end
--------------------------------------------------------------------------------
function modmgr.render_modlist(render_list)
local retval = ""
if render_list == nil then
if modmgr.global_mods == nil then
modmgr.refresh_globals()
end
render_list = modmgr.global_mods
end
local list = filterlist.get_list(render_list)
local last_modpack = nil
for i,v in ipairs(list) do
if retval ~= "" then
retval = retval ..","
end
local color = ""
if v.is_modpack then
local rawlist = filterlist.get_raw_list(render_list)
local all_enabled = true
for j=1,#rawlist,1 do
if rawlist[j].modpack == list[i].name and
@@ -386,14 +388,14 @@ function modmgr.render_modlist(render_list)
break
end
end
if all_enabled == false then
color = mt_color_grey
else
color = mt_color_dark_green
end
end
if v.typ == "game_mod" then
color = mt_color_blue
else
@@ -408,34 +410,34 @@ function modmgr.render_modlist(render_list)
end
retval = retval .. v.name
end
return retval
end
--------------------------------------------------------------------------------
function modmgr.dialog_configure_world()
modmgr.precheck()
local worldspec = engine.get_worlds()[modmgr.world_config_selected_world]
local mod = filterlist.get_list(modmgr.modlist)[modmgr.world_config_selected_mod]
local retval =
"size[11,6.5]" ..
"label[0.5,-0.25;" .. fgettext("World:") .. "]" ..
"label[1.75,-0.25;" .. worldspec.name .. "]"
if modmgr.hide_gamemods then
retval = retval .. "checkbox[0,5.75;cb_hide_gamemods;" .. fgettext("Hide Game") .. ";true]"
else
retval = retval .. "checkbox[0,5.75;cb_hide_gamemods;" .. fgettext("Hide Game") .. ";false]"
end
if modmgr.hide_modpackcontents then
retval = retval .. "checkbox[2,5.75;cb_hide_mpcontent;" .. fgettext("Hide mp content") .. ";true]"
else
retval = retval .. "checkbox[2,5.75;cb_hide_mpcontent;" .. fgettext("Hide mp content") .. ";false]"
end
if mod == nil then
mod = {name=""}
end
@@ -447,11 +449,11 @@ function modmgr.dialog_configure_world()
modmgr.get_dependencies(mod.path) .. ";0]" ..
"button[9.25,6.35;2,0.5;btn_config_world_save;" .. fgettext("Save") .. "]" ..
"button[7.4,6.35;2,0.5;btn_config_world_cancel;" .. fgettext("Cancel") .. "]"
if mod ~= nil and mod.name ~= "" and mod.typ ~= "game_mod" then
if mod.is_modpack then
local rawlist = filterlist.get_raw_list(modmgr.modlist)
local all_enabled = true
for j=1,#rawlist,1 do
if rawlist[j].modpack == mod.name and
@@ -460,7 +462,7 @@ function modmgr.dialog_configure_world()
break
end
end
if all_enabled == false then
retval = retval .. "button[5.5,-0.125;2,0.5;btn_mp_enable;" .. fgettext("Enable MP") .. "]"
else
@@ -474,15 +476,15 @@ function modmgr.dialog_configure_world()
end
end
end
retval = retval ..
"button[8.5,-0.125;2.5,0.5;btn_all_mods;" .. fgettext("Enable all") .. "]" ..
"textlist[5.5,0.5;5.5,5.75;world_config_modlist;"
retval = retval .. modmgr.render_modlist(modmgr.modlist)
retval = retval .. ";" .. modmgr.world_config_selected_mod .."]"
return retval
end
@@ -490,23 +492,23 @@ end
function modmgr.handle_buttons(tab,fields)
local retval = nil
if tab == "mod_mgr" then
retval = modmgr.handle_modmgr_buttons(fields)
end
if tab == "dialog_rename_modpack" then
retval = modmgr.handle_rename_modpack_buttons(fields)
end
if tab == "dialog_delete_mod" then
retval = modmgr.handle_delete_mod_buttons(fields)
end
if tab == "dialog_configure_world" then
retval = modmgr.handle_configure_world_buttons(fields)
end
return retval
end
@@ -516,13 +518,13 @@ function modmgr.get_dependencies(modfolder)
if modfolder ~= nil then
local filename = modfolder ..
DIR_DELIM .. "depends.txt"
local dependencyfile = io.open(filename,"r")
if dependencyfile then
local dependency = dependencyfile:read("*l")
while dependency do
if toadd ~= "" then
if toadd ~= "" then
toadd = toadd .. ","
end
toadd = toadd .. dependency
@@ -542,11 +544,11 @@ function modmgr.get_worldconfig(worldpath)
DIR_DELIM .. "world.mt"
local worldfile = Settings(filename)
local worldconfig = {}
worldconfig.global_mods = {}
worldconfig.game_mods = {}
for key,value in pairs(worldfile:to_table()) do
if key == "gameid" then
worldconfig.id = value
@@ -554,7 +556,7 @@ function modmgr.get_worldconfig(worldpath)
worldconfig.global_mods[key] = engine.is_yes(value)
end
end
--read gamemods
local gamespec = gamemgr.find_by_gameid(worldconfig.id)
gamemgr.get_game_mods(gamespec, worldconfig.game_mods)
@@ -573,11 +575,11 @@ function modmgr.handle_modmgr_buttons(fields)
local event = explode_textlist_event(fields["modlist"])
modmgr.selected_mod = event.index
end
if fields["btn_mod_mgr_install_local"] ~= nil then
engine.show_file_open_dialog("mod_mgt_open_dlg",fgettext("Select Mod File:"))
end
if fields["btn_mod_mgr_download"] ~= nil then
modstore.update_modlist()
retval.current_tab = "dialog_modstore_unsorted"
@@ -585,26 +587,26 @@ function modmgr.handle_modmgr_buttons(fields)
retval.show_buttons = false
return retval
end
if fields["btn_mod_mgr_rename_modpack"] ~= nil then
retval.current_tab = "dialog_rename_modpack"
retval.is_dialog = true
retval.show_buttons = false
return retval
end
if fields["btn_mod_mgr_delete_mod"] ~= nil then
retval.current_tab = "dialog_delete_mod"
retval.is_dialog = true
retval.show_buttons = false
return retval
end
if fields["mod_mgt_open_dlg_accepted"] ~= nil and
fields["mod_mgt_open_dlg_accepted"] ~= "" then
modmgr.installmod(fields["mod_mgt_open_dlg_accepted"],nil)
end
return nil;
end
@@ -612,27 +614,27 @@ end
function modmgr.installmod(modfilename,basename)
local modfile = modmgr.identify_filetype(modfilename)
local modpath = modmgr.extract(modfile)
if modpath == nil then
gamedata.errormessage = fgettext("Install Mod: file: \"$1\"", modfile.name) ..
fgettext("\nInstall Mod: unsupported filetype \"$1\"", modfile.type)
fgettext("\nInstall Mod: unsupported filetype \"$1\" or broken archive", modfile.type)
return
end
local basefolder = modmgr.getbasefolder(modpath)
if basefolder.type == "modpack" then
local clean_path = nil
if basename ~= nil then
clean_path = "mp_" .. basename
end
if clean_path == nil then
clean_path = get_last_folder(cleanup_path(basefolder.path))
end
if clean_path ~= nil then
local targetpath = engine.get_modpath() .. DIR_DELIM .. clean_path
if not engine.copy_dir(basefolder.path,targetpath) then
@@ -642,19 +644,19 @@ function modmgr.installmod(modfilename,basename)
gamedata.errormessage = fgettext("Install Mod: unable to find suitable foldername for modpack $1", modfilename)
end
end
if basefolder.type == "mod" then
local targetfolder = basename
if targetfolder == nil then
targetfolder = modmgr.identify_modname(basefolder.path,"init.lua")
end
--if heuristic failed try to use current foldername
if targetfolder == nil then
targetfolder = get_last_folder(basefolder.path)
end
end
if targetfolder ~= nil and modmgr.isValidModname(targetfolder) then
local targetpath = engine.get_modpath() .. DIR_DELIM .. targetfolder
engine.copy_dir(basefolder.path,targetpath)
@@ -662,7 +664,7 @@ function modmgr.installmod(modfilename,basename)
gamedata.errormessage = fgettext("Install Mod: unable to find real modname for: $1", modfilename)
end
end
engine.delete_dir(modpath)
modmgr.refresh_globals()
@@ -671,7 +673,7 @@ end
--------------------------------------------------------------------------------
function modmgr.handle_rename_modpack_buttons(fields)
if fields["dlg_rename_modpack_confirm"] ~= nil then
local mod = filterlist.get_list(modmgr.global_mods)[modmgr.selected_mod]
local oldpath = engine.get_modpath() .. DIR_DELIM .. mod.name
@@ -681,7 +683,7 @@ function modmgr.handle_rename_modpack_buttons(fields)
modmgr.selected_mod = filterlist.get_current_index(modmgr.global_mods,
filterlist.raw_index_by_uid(modmgr.global_mods, fields["te_modpack_name"]))
end
return {
is_dialog = false,
show_buttons = true,
@@ -698,25 +700,25 @@ function modmgr.handle_configure_world_buttons(fields)
modmgr.world_config_enable_mod(nil)
end
end
if fields["key_enter"] ~= nil then
modmgr.world_config_enable_mod(nil)
end
if fields["cb_mod_enable"] ~= nil then
local toset = engine.is_yes(fields["cb_mod_enable"])
modmgr.world_config_enable_mod(toset)
end
if fields["btn_mp_enable"] ~= nil or
fields["btn_mp_disable"] then
local toset = (fields["btn_mp_enable"] ~= nil)
modmgr.world_config_enable_mod(toset)
end
if fields["cb_hide_gamemods"] ~= nil then
local current = filterlist.get_filtercriteria(modmgr.modlist)
if current == nil then
current = {}
end
@@ -728,13 +730,13 @@ function modmgr.handle_configure_world_buttons(fields)
current.hide_game = false
modmgr.hide_gamemods = false
end
filterlist.set_filtercriteria(modmgr.modlist,current)
end
if fields["cb_hide_mpcontent"] ~= nil then
local current = filterlist.get_filtercriteria(modmgr.modlist)
if current == nil then
current = {}
end
@@ -746,21 +748,21 @@ function modmgr.handle_configure_world_buttons(fields)
current.hide_modpackcontents = false
modmgr.hide_modpackcontents = false
end
filterlist.set_filtercriteria(modmgr.modlist,current)
end
if fields["btn_config_world_save"] then
local worldspec = engine.get_worlds()[modmgr.world_config_selected_world]
local filename = worldspec.path ..
DIR_DELIM .. "world.mt"
local worldfile = Settings(filename)
local mods = worldfile:to_table()
local rawlist = filterlist.get_raw_list(modmgr.modlist)
local i,mod
for i,mod in ipairs(rawlist) do
if not mod.is_modpack and
@@ -773,42 +775,42 @@ function modmgr.handle_configure_world_buttons(fields)
mods["load_mod_"..mod.name] = nil
end
end
-- Remove mods that are not present anymore
for key,value in pairs(mods) do
if key:sub(1,9) == "load_mod_" then
worldfile:remove(key)
end
end
if not worldfile:write() then
engine.log("error", "Failed to write world config file")
end
modmgr.modlist = nil
modmgr.worldconfig = nil
return {
is_dialog = false,
show_buttons = true,
current_tab = engine.setting_get("main_menu_tab")
}
end
if fields["btn_config_world_cancel"] then
modmgr.worldconfig = nil
return {
is_dialog = false,
show_buttons = true,
current_tab = engine.setting_get("main_menu_tab")
}
end
if fields["btn_all_mods"] then
local list = filterlist.get_raw_list(modmgr.modlist)
for i=1,#list,1 do
if list[i].typ ~= "game_mod" and
not list[i].is_modpack then
@@ -816,9 +818,9 @@ function modmgr.handle_configure_world_buttons(fields)
end
end
end
return nil
end
--------------------------------------------------------------------------------
@@ -849,9 +851,9 @@ end
--------------------------------------------------------------------------------
function modmgr.handle_delete_mod_buttons(fields)
local mod = filterlist.get_list(modmgr.global_mods)[modmgr.selected_mod]
if fields["dlg_delete_mod_confirm"] ~= nil then
if mod.path ~= nil and
mod.path ~= "" and
mod.path ~= engine.get_modpath() then
@@ -863,7 +865,7 @@ function modmgr.handle_delete_mod_buttons(fields)
gamedata.errormessage = fgettext("Modmgr: invalid modpath \"$1\"", mod.path)
end
end
return {
is_dialog = false,
show_buttons = true,
@@ -875,8 +877,8 @@ end
function modmgr.dialog_delete_mod()
local mod = filterlist.get_list(modmgr.global_mods)[modmgr.selected_mod]
local retval =
local retval =
"field[1.75,1;10,3;;" .. fgettext("Are you sure you want to delete \"$1\"?", mod.name) .. ";]"..
"button[4,4.2;1,0.5;dlg_delete_mod_confirm;" .. fgettext("Yes") .. "]" ..
"button[6.5,4.2;3,0.5;dlg_delete_mod_cancel;" .. fgettext("No of course not!") .. "]"
@@ -887,10 +889,10 @@ end
--------------------------------------------------------------------------------
function modmgr.preparemodlist(data)
local retval = {}
local global_mods = {}
local game_mods = {}
--read global mods
local modpath = engine.get_modpath()
@@ -898,31 +900,31 @@ function modmgr.preparemodlist(data)
modpath ~= "" then
get_mods(modpath,global_mods)
end
for i=1,#global_mods,1 do
global_mods[i].typ = "global_mod"
table.insert(retval,global_mods[i])
end
--read game mods
local gamespec = gamemgr.find_by_gameid(data.gameid)
gamemgr.get_game_mods(gamespec, game_mods)
for i=1,#game_mods,1 do
game_mods[i].typ = "game_mod"
table.insert(retval,game_mods[i])
end
if data.worldpath == nil then
return retval
end
--read world mod configuration
local filename = data.worldpath ..
DIR_DELIM .. "world.mt"
local worldfile = Settings(filename)
for key,value in pairs(worldfile:to_table()) do
if key:sub(1, 9) == "load_mod_" then
key = key:sub(10)
@@ -948,17 +950,17 @@ end
function modmgr.init_worldconfig()
modmgr.precheck()
local worldspec = engine.get_worlds()[modmgr.world_config_selected_world]
if worldspec ~= nil then
--read worldconfig
modmgr.worldconfig = modmgr.get_worldconfig(worldspec.path)
if modmgr.worldconfig.id == nil or
modmgr.worldconfig.id == "" then
modmgr.worldconfig = nil
return false
end
modmgr.modlist = filterlist.create(
modmgr.preparemodlist, --refresh
modmgr.comparemod, --compare
@@ -966,13 +968,13 @@ function modmgr.init_worldconfig()
if element.name == uid then
return true
end
end,
end,
function(element,criteria)
if criteria.hide_game and
element.typ == "game_mod" then
return false
end
if criteria.hide_modpackcontents and
element.modpack ~= nil then
return false
@@ -982,15 +984,15 @@ function modmgr.init_worldconfig()
{ worldpath= worldspec.path,
gameid = worldspec.gameid }
)
filterlist.set_filtercriteria(modmgr.modlist, {
hide_game=modmgr.hide_gamemods,
hide_modpackcontents= modmgr.hide_modpackcontents
})
filterlist.add_sort_mechanism(modmgr.modlist, "alphabetic", sort_mod_list)
filterlist.set_sortmode(modmgr.modlist, "alphabetic")
return true
return true
end
return false
@@ -1013,34 +1015,34 @@ function modmgr.comparemod(elem1,elem2)
if elem1.modpack ~= elem2.modpack then
return false
end
if elem1.path ~= elem2.path then
return false
end
return true
end
--------------------------------------------------------------------------------
function modmgr.gettab(name)
local retval = ""
if name == "mod_mgr" then
retval = retval .. modmgr.tab()
end
if name == "dialog_rename_modpack" then
retval = retval .. modmgr.dialog_rename_modpack()
end
if name == "dialog_delete_mod" then
retval = retval .. modmgr.dialog_delete_mod()
end
if name == "dialog_configure_world" then
retval = retval .. modmgr.dialog_configure_world()
end
return retval
end
@@ -1054,7 +1056,7 @@ function modmgr.mod_exists(basename)
if filterlist.raw_index_by_uid(modmgr.global_mods,basename) > 0 then
return true
end
return false
end
@@ -1064,7 +1066,7 @@ function modmgr.get_global_mod(idx)
if modmgr.global_mods == nil then
return nil
end
if idx < 1 or idx > filterlist.size(modmgr.global_mods) then
return nil
end
@@ -1081,7 +1083,7 @@ function modmgr.refresh_globals()
if element.name == uid then
return true
end
end,
end,
nil, --filter
{}
)
@@ -1098,7 +1100,7 @@ function modmgr.identify_filetype(name)
type = "zip"
}
end
if name:sub(-6):lower() == "tar.gz" or
name:sub(-3):lower() == "tgz"then
return {
@@ -1106,14 +1108,14 @@ function modmgr.identify_filetype(name)
type = "tgz"
}
end
if name:sub(-6):lower() == "tar.bz2" then
return {
name = name,
type = "tbz"
}
end
if name:sub(-2):lower() == "7z" then
return {
name = name,

View File

@@ -21,22 +21,68 @@
modstore = {}
--------------------------------------------------------------------------------
-- @function [parent=#modstore] init
function modstore.init()
modstore.tabnames = {}
table.insert(modstore.tabnames,"dialog_modstore_unsorted")
table.insert(modstore.tabnames,"dialog_modstore_search")
modstore.modsperpage = 5
modstore.basetexturedir = engine.get_texturepath() .. DIR_DELIM .. "base" ..
modstore.basetexturedir = engine.get_texturepath() .. DIR_DELIM .. "base" ..
DIR_DELIM .. "pack" .. DIR_DELIM
modstore.lastmodtitle = ""
modstore.last_search = ""
modstore.searchlist = filterlist.create(
function()
if modstore.modlist_unsorted ~= nil and
modstore.modlist_unsorted.data ~= nil then
return modstore.modlist_unsorted.data
end
return {}
end,
function(element,modid)
if element.id == modid then
return true
end
return false
end, --compare fct
nil, --uid match fct
function(element,substring)
if substring == nil or
substring == "" then
return false
end
substring = substring:upper()
if element.title ~= nil and
element.title:upper():find(substring) ~= nil then
return true
end
if element.details ~= nil and
element.details.author ~= nil and
element.details.author:upper():find(substring) ~= nil then
return true
end
if element.details ~= nil and
element.details.description ~= nil and
element.details.description:upper():find(substring) ~= nil then
return true
end
return false
end --filter fct
)
modstore.current_list = nil
modstore.details_cache = {}
end
--------------------------------------------------------------------------------
-- @function [parent=#modstore] nametoindex
function modstore.nametoindex(name)
for i=1,#modstore.tabnames,1 do
@@ -49,55 +95,94 @@ function modstore.nametoindex(name)
end
--------------------------------------------------------------------------------
function modstore.gettab(tabname)
-- @function [parent=#modstore] getsuccessfuldialog
function modstore.getsuccessfuldialog()
local retval = ""
retval = retval .. "size[6,2]"
if modstore.lastmodentry ~= nil then
retval = retval .. "label[0,0.25;" .. fgettext("Successfully installed:") .. "]"
retval = retval .. "label[3,0.25;" .. modstore.lastmodentry.moddetails.title .. "]"
local is_modstore_tab = false
if tabname == "dialog_modstore_unsorted" then
retval = modstore.getmodlist(modstore.modlist_unsorted)
is_modstore_tab = true
end
if tabname == "dialog_modstore_search" then
is_modstore_tab = true
end
if is_modstore_tab then
return modstore.tabheader(tabname) .. retval
end
if tabname == "modstore_mod_installed" then
return "size[6,2]label[0.25,0.25;Mod: " .. modstore.lastmodtitle ..
" installed successfully]" ..
"button[2.5,1.5;1,0.5;btn_confirm_mod_successfull;ok]"
end
return ""
end
retval = retval .. "label[0,0.75;" .. fgettext("Shortname:") .. "]"
retval = retval .. "label[3,0.75;" .. engine.formspec_escape(modstore.lastmodentry.moddetails.basename) .. "]"
--------------------------------------------------------------------------------
function modstore.tabheader(tabname)
local retval = "size[12,9.25]"
retval = retval .. "tabheader[-0.3,-0.99;modstore_tab;" ..
"Unsorted,Search;" ..
modstore.nametoindex(tabname) .. ";true;false]"
end
retval = retval .. "button[2.5,1.5;1,0.5;btn_confirm_mod_successfull;" .. fgettext("ok") .. "]"
return retval
end
--------------------------------------------------------------------------------
-- @function [parent=#modstore] gettab
function modstore.gettab(tabname)
local retval = ""
local is_modstore_tab = false
if tabname == "dialog_modstore_unsorted" then
modstore.modsperpage = 5
retval = modstore.getmodlist(modstore.modlist_unsorted)
is_modstore_tab = true
end
if tabname == "dialog_modstore_search" then
retval = modstore.getsearchpage()
is_modstore_tab = true
end
if is_modstore_tab then
return modstore.tabheader(tabname) .. retval
end
if tabname == "modstore_mod_installed" then
return modstore.getsuccessfuldialog()
end
if tabname == "modstore_downloading" then
return "size[6,2]label[0.25,0.75;" .. fgettext("Downloading") ..
" " .. modstore.lastmodtitle .. " " ..
fgettext("please wait...") .. "]"
end
return ""
end
--------------------------------------------------------------------------------
-- @function [parent=#modstore] tabheader
function modstore.tabheader(tabname)
local retval = "size[12,10.25]"
retval = retval .. "tabheader[-0.3,-0.99;modstore_tab;" ..
"Unsorted,Search;" ..
modstore.nametoindex(tabname) .. ";true;false]" ..
"button[4,9.9;4,0.5;btn_modstore_close;" ..
fgettext("Close modstore") .. "]"
return retval
end
--------------------------------------------------------------------------------
-- @function [parent=#modstore] handle_buttons
function modstore.handle_buttons(current_tab,fields)
modstore.lastmodtitle = ""
if fields["modstore_tab"] then
local index = tonumber(fields["modstore_tab"])
if index > 0 and
index <= #modstore.tabnames then
if modstore.tabnames[index] == "dialog_modstore_search" then
filterlist.set_filtercriteria(modstore.searchlist,modstore.last_search)
filterlist.refresh(modstore.searchlist)
modstore.modsperpage = 4
modstore.currentlist = {
page = 0,
pagecount =
math.ceil(filterlist.size(modstore.searchlist) / modstore.modsperpage),
data = filterlist.get_list(modstore.searchlist),
}
end
return {
current_tab = modstore.tabnames[index],
is_dialog = true,
@@ -105,170 +190,426 @@ function modstore.handle_buttons(current_tab,fields)
}
end
modstore.modlist_page = 0
end
if fields["btn_modstore_page_up"] then
if modstore.current_list ~= nil and modstore.current_list.page > 0 then
modstore.current_list.page = modstore.current_list.page - 1
end
end
if fields["btn_modstore_page_down"] then
if modstore.current_list ~= nil and
if modstore.current_list ~= nil and
modstore.current_list.page <modstore.current_list.pagecount-1 then
modstore.current_list.page = modstore.current_list.page +1
end
end
if fields["btn_hidden_close_download"] ~= nil then
if fields["btn_hidden_close_download"].successfull then
modstore.lastmodentry = fields["btn_hidden_close_download"]
return {
current_tab = "modstore_mod_installed",
is_dialog = true,
show_buttons = false
}
else
modstore.lastmodtitle = ""
return {
current_tab = modstore.tabnames[1],
is_dialog = true,
show_buttons = false
}
end
end
if fields["btn_confirm_mod_successfull"] then
modstore.lastmodentry = nil
modstore.lastmodtitle = ""
return {
current_tab = modstore.tabnames[1],
is_dialog = true,
show_buttons = false
}
end
if fields["btn_modstore_search"] or
(fields["key_enter"] and fields["te_modstore_search"] ~= nil) then
modstore.last_search = fields["te_modstore_search"]
filterlist.set_filtercriteria(modstore.searchlist,fields["te_modstore_search"])
filterlist.refresh(modstore.searchlist)
modstore.currentlist = {
page = 0,
pagecount = math.ceil(filterlist.size(modstore.searchlist) / modstore.modsperpage),
data = filterlist.get_list(modstore.searchlist),
}
end
for i=1, modstore.modsperpage, 1 do
local installbtn = "btn_install_mod_" .. i
if fields[installbtn] then
local modlistentry =
modstore.current_list.page * modstore.modsperpage + i
local moddetails = modstore.get_details(modstore.current_list.data[modlistentry].id)
local fullurl = engine.setting_get("modstore_download_url") ..
moddetails.download_url
local modfilename = os.tempfolder() .. ".zip"
if engine.download_file(fullurl,modfilename) then
modmgr.installmod(modfilename,moddetails.basename)
os.remove(modfilename)
modstore.lastmodtitle = modstore.current_list.data[modlistentry].title
return {
current_tab = "modstore_mod_installed",
is_dialog = true,
show_buttons = false
}
else
gamedata.errormessage = "Unable to download " ..
moddetails.download_url .. " (internet connection?)"
if fields["btn_modstore_close"] then
return {
is_dialog = false,
show_buttons = true,
current_tab = engine.setting_get("main_menu_tab")
}
end
for key,value in pairs(fields) do
local foundat = key:find("btn_install_mod_")
if ( foundat == 1) then
local modid = tonumber(key:sub(17))
for i=1,#modstore.modlist_unsorted.data,1 do
if modstore.modlist_unsorted.data[i].id == modid then
local moddetails = modstore.modlist_unsorted.data[i].details
if modstore.lastmodtitle ~= "" then
modstore.lastmodtitle = modstore.lastmodtitle .. ", "
end
modstore.lastmodtitle = modstore.lastmodtitle .. moddetails.title
engine.handle_async(
function(param)
local fullurl = engine.setting_get("modstore_download_url") ..
param.moddetails.download_url
if param.version ~= nil then
local found = false
for i=1,#param.moddetails.versions, 1 do
if param.moddetails.versions[i].date:sub(1,10) == param.version then
fullurl = engine.setting_get("modstore_download_url") ..
param.moddetails.versions[i].download_url
found = true
end
end
if not found then
return {
moddetails = param.moddetails,
successfull = false
}
end
end
if engine.download_file(fullurl,param.filename) then
return {
texturename = param.texturename,
moddetails = param.moddetails,
filename = param.filename,
successfull = true
}
else
return {
modtitle = param.title,
successfull = false
}
end
end,
{
moddetails = moddetails,
version = fields["dd_version" .. modid],
filename = os.tempfolder() .. "_MODNAME_" .. moddetails.basename .. ".zip",
texturename = modstore.modlist_unsorted.data[i].texturename
},
function(result)
if result.successfull then
modmgr.installmod(result.filename,result.moddetails.basename)
os.remove(result.filename)
else
gamedata.errormessage = "Failed to download " .. result.moddetails.title
end
if gamedata.errormessage == nil then
engine.button_handler({btn_hidden_close_download=result})
else
engine.button_handler({btn_hidden_close_download={successfull=false}})
end
end
)
return {
current_tab = "modstore_downloading",
is_dialog = true,
show_buttons = false,
ignore_menu_quit = true
}
end
end
break
end
end
end
--------------------------------------------------------------------------------
-- @function [parent=#modstore] update_modlist
function modstore.update_modlist()
modstore.modlist_unsorted = {}
modstore.modlist_unsorted.data = engine.get_modstore_list()
if modstore.modlist_unsorted.data ~= nil then
modstore.modlist_unsorted.pagecount =
math.ceil((#modstore.modlist_unsorted.data / modstore.modsperpage))
else
modstore.modlist_unsorted.data = {}
modstore.modlist_unsorted.pagecount = 1
end
modstore.modlist_unsorted.data = {}
modstore.modlist_unsorted.pagecount = 1
modstore.modlist_unsorted.page = 0
engine.handle_async(
function(param)
return engine.get_modstore_list()
end,
nil,
function(result)
if result ~= nil then
modstore.modlist_unsorted = {}
modstore.modlist_unsorted.data = result
if modstore.modlist_unsorted.data ~= nil then
modstore.modlist_unsorted.pagecount =
math.ceil((#modstore.modlist_unsorted.data / modstore.modsperpage))
else
modstore.modlist_unsorted.data = {}
modstore.modlist_unsorted.pagecount = 1
end
modstore.modlist_unsorted.page = 0
modstore.fetchdetails()
engine.event_handler("Refresh")
end
end
)
end
--------------------------------------------------------------------------------
function modstore.getmodlist(list)
local retval = ""
retval = retval .. "label[10,-0.4;" .. fgettext("Page $1 of $2", list.page+1, list.pagecount) .. "]"
retval = retval .. "button[11.6,-0.1;0.5,0.5;btn_modstore_page_up;^]"
retval = retval .. "box[11.6,0.35;0.28,8.6;#000000]"
local scrollbarpos = 0.35 + (8.1/(list.pagecount-1)) * list.page
retval = retval .. "box[11.6," ..scrollbarpos .. ";0.28,0.5;#32CD32]"
retval = retval .. "button[11.6,9.0;0.5,0.5;btn_modstore_page_down;v]"
if #list.data < (list.page * modstore.modsperpage) then
return retval
-- @function [parent=#modstore] fetchdetails
function modstore.fetchdetails()
for i=1,#modstore.modlist_unsorted.data,1 do
engine.handle_async(
function(param)
param.details = engine.get_modstore_details(tostring(param.modid))
return param
end,
{
modid=modstore.modlist_unsorted.data[i].id,
listindex=i
},
function(result)
if result ~= nil and
modstore.modlist_unsorted ~= nil
and modstore.modlist_unsorted.data ~= nil and
modstore.modlist_unsorted.data[result.listindex] ~= nil and
modstore.modlist_unsorted.data[result.listindex].id ~= nil then
modstore.modlist_unsorted.data[result.listindex].details = result.details
engine.event_handler("Refresh")
end
end
)
end
end
--------------------------------------------------------------------------------
-- @function [parent=#modstore] getscreenshot
function modstore.getscreenshot(ypos,listentry)
if listentry.details ~= nil and
(listentry.details.screenshot_url == nil or
listentry.details.screenshot_url == "") then
if listentry.texturename == nil then
listentry.texturename = modstore.basetexturedir .. "no_screenshot.png"
end
return "image[0,".. ypos .. ";3,2;" ..
engine.formspec_escape(listentry.texturename) .. "]"
end
local endmod = (list.page * modstore.modsperpage) + modstore.modsperpage
if listentry.details ~= nil and
listentry.texturename == nil then
--make sure we don't download multiple times
listentry.texturename = "in progress"
--prepare url and filename
local fullurl = engine.setting_get("modstore_download_url") ..
listentry.details.screenshot_url
local filename = os.tempfolder() .. "_MID_" .. listentry.id
--trigger download
engine.handle_async(
--first param is downloadfct
function(param)
param.successfull = engine.download_file(param.fullurl,param.filename)
return param
end,
--second parameter is data passed to async job
{
fullurl = fullurl,
filename = filename,
modid = listentry.id
},
--integrate result to raw list
function(result)
if result.successfull then
local found = false
for i=1,#modstore.modlist_unsorted.data,1 do
if modstore.modlist_unsorted.data[i].id == result.modid then
found = true
modstore.modlist_unsorted.data[i].texturename = result.filename
break
end
end
if found then
engine.event_handler("Refresh")
else
engine.log("error","got screenshot but didn't find matching mod: " .. result.modid)
end
end
end
)
end
if listentry.texturename ~= nil and
listentry.texturename ~= "in progress" then
return "image[0,".. ypos .. ";3,2;" ..
engine.formspec_escape(listentry.texturename) .. "]"
end
return ""
end
--------------------------------------------------------------------------------
--@function [parent=#modstore] getshortmodinfo
function modstore.getshortmodinfo(ypos,listentry,details)
local retval = ""
retval = retval .. "box[0," .. ypos .. ";11.4,1.75;#FFFFFF]"
--screenshot
retval = retval .. modstore.getscreenshot(ypos,listentry)
--title + author
retval = retval .."label[2.75," .. ypos .. ";" ..
engine.formspec_escape(details.title) .. " (" .. details.author .. ")]"
--description
local descriptiony = ypos + 0.5
retval = retval .. "textarea[3," .. descriptiony .. ";6.5,1.55;;" ..
engine.formspec_escape(details.description) .. ";]"
--rating
local ratingy = ypos
retval = retval .."label[7," .. ratingy .. ";" ..
fgettext("Rating") .. ":]"
retval = retval .. "label[8.7," .. ratingy .. ";" .. details.rating .."]"
--versions (IMPORTANT has to be defined AFTER rating)
if details.versions ~= nil and
#details.versions > 1 then
local versiony = ypos + 0.05
retval = retval .. "dropdown[9.1," .. versiony .. ";2.48,0.25;dd_version" .. details.id .. ";"
local versions = ""
for i=1,#details.versions , 1 do
if versions ~= "" then
versions = versions .. ","
end
versions = versions .. details.versions[i].date:sub(1,10)
end
retval = retval .. versions .. ";1]"
end
if details.basename then
--install button
local buttony = ypos + 1.2
retval = retval .."button[9.1," .. buttony .. ";2.5,0.5;btn_install_mod_" .. details.id .. ";"
if modmgr.mod_exists(details.basename) then
retval = retval .. fgettext("re-Install") .."]"
else
retval = retval .. fgettext("Install") .."]"
end
end
return retval
end
--------------------------------------------------------------------------------
--@function [parent=#modstore] getmodlist
function modstore.getmodlist(list,yoffset)
modstore.current_list = list
if #list.data == 0 then
return ""
end
if yoffset == nil then
yoffset = 0
end
local scrollbar = ""
scrollbar = scrollbar .. "label[0.1,9.5;"
.. fgettext("Page $1 of $2", list.page+1, list.pagecount) .. "]"
scrollbar = scrollbar .. "box[11.6," .. (yoffset + 0.35) .. ";0.28,"
.. (8.6 - yoffset) .. ";#000000]"
local scrollbarpos = (yoffset + 0.75) +
((7.7 -yoffset)/(list.pagecount-1)) * list.page
scrollbar = scrollbar .. "box[11.6," ..scrollbarpos .. ";0.28,0.5;#32CD32]"
scrollbar = scrollbar .. "button[11.6," .. (yoffset + (0.3))
.. ";0.5,0.5;btn_modstore_page_up;^]"
scrollbar = scrollbar .. "button[11.6," .. 9.0
.. ";0.5,0.5;btn_modstore_page_down;v]"
local retval = ""
local endmod = (list.page * modstore.modsperpage) + modstore.modsperpage
if (endmod > #list.data) then
endmod = #list.data
end
for i=(list.page * modstore.modsperpage) +1, endmod, 1 do
--getmoddetails
local details = modstore.get_details(list.data[i].id)
local details = list.data[i].details
if details == nil then
details = {}
details.title = list.data[i].title
details.author = ""
details.rating = -1
details.description = ""
end
if details ~= nil then
local screenshot_ypos = (i-1 - (list.page * modstore.modsperpage))*1.9 +0.2
retval = retval .. "box[0," .. screenshot_ypos .. ";11.4,1.75;#FFFFFF]"
--screenshot
if details.screenshot_url ~= nil and
details.screenshot_url ~= "" then
if list.data[i].texturename == nil then
local fullurl = engine.setting_get("modstore_download_url") ..
details.screenshot_url
local filename = os.tempfolder()
if engine.download_file(fullurl,filename) then
list.data[i].texturename = filename
end
end
end
if list.data[i].texturename == nil then
list.data[i].texturename = modstore.basetexturedir .. "no_screenshot.png"
end
retval = retval .. "image[0,".. screenshot_ypos .. ";3,2;" ..
engine.formspec_escape(list.data[i].texturename) .. "]"
--title + author
retval = retval .."label[2.75," .. screenshot_ypos .. ";" ..
engine.formspec_escape(details.title) .. " (" .. details.author .. ")]"
--description
local descriptiony = screenshot_ypos + 0.5
retval = retval .. "textarea[3," .. descriptiony .. ";6.5,1.55;;" ..
engine.formspec_escape(details.description) .. ";]"
--rating
local ratingy = screenshot_ypos + 0.6
retval = retval .."label[10.1," .. ratingy .. ";" ..
fgettext("Rating") .. ": " .. details.rating .."]"
--install button
local buttony = screenshot_ypos + 1.2
local buttonnumber = (i - (list.page * modstore.modsperpage))
retval = retval .."button[9.6," .. buttony .. ";2,0.5;btn_install_mod_" .. buttonnumber .. ";"
if modmgr.mod_exists(details.basename) then
retval = retval .. fgettext("re-Install") .."]"
else
retval = retval .. fgettext("Install") .."]"
end
local screenshot_ypos =
yoffset +(i-1 - (list.page * modstore.modsperpage))*1.9 +0.2
retval = retval .. modstore.getshortmodinfo(screenshot_ypos,
list.data[i],
details)
end
end
modstore.current_list = list
return retval
return retval .. scrollbar
end
--------------------------------------------------------------------------------
function modstore.get_details(modid)
if modstore.details_cache[modid] ~= nil then
return modstore.details_cache[modid]
end
--@function [parent=#modstore] getsearchpage
function modstore.getsearchpage()
local retval = ""
local search = ""
local retval = engine.get_modstore_details(tostring(modid))
modstore.details_cache[modid] = retval
return retval
if modstore.last_search ~= nil then
search = modstore.last_search
end
retval = retval ..
"button[9.5,0.2;2.5,0.5;btn_modstore_search;".. fgettext("Search") .. "]" ..
"field[0.5,0.5;9,0.5;te_modstore_search;;" .. search .. "]"
--show 4 mods only
modstore.modsperpage = 4
retval = retval ..
modstore.getmodlist(
modstore.currentlist,
1.75)
return retval;
end

View File

@@ -0,0 +1,71 @@
uniform sampler2D baseTexture;
uniform sampler2D normalTexture;
uniform sampler2D useNormalmap;
uniform vec4 skyBgColor;
uniform float fogDistance;
uniform vec3 eyePosition;
varying vec3 vPosition;
varying vec3 eyeVec;
#ifdef ENABLE_PARALLAX_OCCLUSION
varying vec3 tsEyeVec;
#endif
const float e = 2.718281828459;
void main (void)
{
vec3 color;
vec2 uv = gl_TexCoord[0].st;
#ifdef USE_NORMALMAPS
float use_normalmap = texture2D(useNormalmap,vec2(1.0,1.0)).r;
#endif
#ifdef ENABLE_PARALLAX_OCCLUSION
float height;
vec2 tsEye = vec2(tsEyeVec.x,-tsEyeVec.y);
if (use_normalmap > 0.0) {
float map_height = texture2D(normalTexture, uv).a;
if (map_height < 1.0){
float height = PARALLAX_OCCLUSION_SCALE * map_height - PARALLAX_OCCLUSION_BIAS;
uv = uv + height * tsEye;
}
}
#endif
#ifdef ENABLE_BUMPMAPPING
if (use_normalmap > 0.0) {
vec3 base = texture2D(baseTexture, uv).rgb;
vec3 vVec = normalize(eyeVec);
vec3 bump = normalize(texture2D(normalTexture, uv).xyz * 2.0 - 1.0);
vec3 R = reflect(-vVec, bump);
vec3 lVec = normalize(vVec);
float diffuse = max(dot(lVec, bump), 0.0);
float specular = pow(clamp(dot(R, lVec), 0.0, 1.0),1.0);
color = mix (base,diffuse*base,1.0) + 0.1 * specular * diffuse;
} else {
color = texture2D(baseTexture, uv).rgb;
}
#else
color = texture2D(baseTexture, uv).rgb;
#endif
float alpha = texture2D(baseTexture, uv).a;
vec4 col = vec4(color.r, color.g, color.b, alpha);
col *= gl_Color;
col = col * col; // SRGB -> Linear
col *= 1.8;
col.r = 1.0 - exp(1.0 - col.r) / e;
col.g = 1.0 - exp(1.0 - col.g) / e;
col.b = 1.0 - exp(1.0 - col.b) / e;
col = sqrt(col); // Linear -> SRGB
if(fogDistance != 0.0){
float d = max(0.0, min(vPosition.z / fogDistance * 1.5 - 0.6, 1.0));
col = mix(col, skyBgColor, d);
}
gl_FragColor = vec4(col.r, col.g, col.b, alpha);
}

View File

@@ -0,0 +1,102 @@
uniform mat4 mWorldViewProj;
uniform mat4 mInvWorld;
uniform mat4 mTransWorld;
uniform float dayNightRatio;
uniform vec3 eyePosition;
varying vec3 vPosition;
varying vec3 eyeVec;
#ifdef ENABLE_PARALLAX_OCCLUSION
varying vec3 tsEyeVec;
#endif
void main(void)
{
gl_Position = mWorldViewProj * gl_Vertex;
vPosition = (mWorldViewProj * gl_Vertex).xyz;
eyeVec = (gl_ModelViewMatrix * gl_Vertex).xyz;
#ifdef ENABLE_PARALLAX_OCCLUSION
vec3 normal,tangent,binormal;
normal = normalize(gl_NormalMatrix * gl_Normal);
if (gl_Normal.x > 0.5) {
// 1.0, 0.0, 0.0
tangent = normalize(gl_NormalMatrix * vec3( 0.0, 0.0, -1.0));
binormal = normalize(gl_NormalMatrix * vec3( 0.0, -1.0, 0.0));
} else if (gl_Normal.x < -0.5) {
// -1.0, 0.0, 0.0
tangent = normalize(gl_NormalMatrix * vec3( 0.0, 0.0, 1.0));
binormal = normalize(gl_NormalMatrix * vec3( 0.0, -1.0, 0.0));
} else if (gl_Normal.y > 0.5) {
// 0.0, 1.0, 0.0
tangent = normalize(gl_NormalMatrix * vec3( 1.0, 0.0, 0.0));
binormal = normalize(gl_NormalMatrix * vec3( 0.0, 0.0, 1.0));
} else if (gl_Normal.y < -0.5) {
// 0.0, -1.0, 0.0
tangent = normalize(gl_NormalMatrix * vec3( 1.0, 0.0, 0.0));
binormal = normalize(gl_NormalMatrix * vec3( 0.0, 0.0, 1.0));
} else if (gl_Normal.z > 0.5) {
// 0.0, 0.0, 1.0
tangent = normalize(gl_NormalMatrix * vec3( 1.0, 0.0, 0.0));
binormal = normalize(gl_NormalMatrix * vec3( 0.0, -1.0, 0.0));
} else if (gl_Normal.z < -0.5) {
// 0.0, 0.0, -1.0
tangent = normalize(gl_NormalMatrix * vec3(-1.0, 0.0, 0.0));
binormal = normalize(gl_NormalMatrix * vec3( 0.0, -1.0, 0.0));
}
mat3 tbnMatrix = mat3( tangent.x, binormal.x, normal.x,
tangent.y, binormal.y, normal.y,
tangent.z, binormal.z, normal.z);
tsEyeVec = normalize(eyeVec * tbnMatrix);
#endif
vec4 color;
//color = vec4(1.0, 1.0, 1.0, 1.0);
float day = gl_Color.r;
float night = gl_Color.g;
float light_source = gl_Color.b;
/*color.r = mix(night, day, dayNightRatio);
color.g = color.r;
color.b = color.r;*/
float rg = mix(night, day, dayNightRatio);
rg += light_source * 2.5; // Make light sources brighter
float b = rg;
// Moonlight is blue
b += (day - night) / 13.0;
rg -= (day - night) / 13.0;
// Emphase blue a bit in darker places
// See C++ implementation in mapblock_mesh.cpp finalColorBlend()
b += max(0.0, (1.0 - abs(b - 0.13)/0.17) * 0.025);
// Artificial light is yellow-ish
// See C++ implementation in mapblock_mesh.cpp finalColorBlend()
rg += max(0.0, (1.0 - abs(rg - 0.85)/0.15) * 0.065);
color.r = clamp(rg,0.0,1.0);
color.g = clamp(rg,0.0,1.0);
color.b = clamp(b,0.0,1.0);
// Make sides and bottom darker than the top
color = color * color; // SRGB -> Linear
if(gl_Normal.y <= 0.5)
color *= 0.6;
//color *= 0.7;
color = sqrt(color); // Linear -> SRGB
color.a = gl_Color.a;
gl_FrontColor = gl_BackColor = color;
gl_TexCoord[0] = gl_MultiTexCoord0;
}

View File

@@ -1,46 +0,0 @@
uniform sampler2D myTexture;
uniform sampler2D normalTexture;
uniform vec4 skyBgColor;
uniform float fogDistance;
varying vec3 vPosition;
varying vec3 viewVec;
void main (void)
{
vec4 col = texture2D(myTexture, vec2(gl_TexCoord[0]));
float alpha = col.a;
vec2 uv = gl_TexCoord[0].st;
vec4 base = texture2D(myTexture, uv);
vec4 final_color = vec4(0.2, 0.2, 0.2, 1.0) * base;
vec3 vVec = normalize(viewVec);
vec3 bump = normalize(texture2D(normalTexture, uv).xyz * 2.0 - 1.0);
vec3 R = reflect(-vVec, bump);
vec3 lVec = normalize(vec3(0.0, -0.4, 0.5));
float diffuse = max(dot(lVec, bump), 0.0);
vec3 color = diffuse * texture2D(myTexture, gl_TexCoord[0].st).rgb;
float specular = pow(clamp(dot(R, lVec), 0.0, 1.0),1.0);
color += vec3(0.2*specular*diffuse);
col = vec4(color.r, color.g, color.b, alpha);
col *= gl_Color;
col = col * col; // SRGB -> Linear
col *= 1.8;
col.r = 1.0 - exp(1.0 - col.r) / exp(1.0);
col.g = 1.0 - exp(1.0 - col.g) / exp(1.0);
col.b = 1.0 - exp(1.0 - col.b) / exp(1.0);
col = sqrt(col); // Linear -> SRGB
if(fogDistance != 0.0){
float d = max(0.0, min(vPosition.z / fogDistance * 1.5 - 0.6, 1.0));
alpha = mix(alpha, 0.0, d);
}
gl_FragColor = vec4(col.r, col.g, col.b, alpha);
}

View File

@@ -1,45 +0,0 @@
uniform sampler2D myTexture;
uniform sampler2D normalTexture;
uniform vec4 skyBgColor;
uniform float fogDistance;
varying vec3 vPosition;
varying vec3 viewVec;
void main (void)
{
vec4 col = texture2D(myTexture, vec2(gl_TexCoord[0]));
float alpha = col.a;
vec2 uv = gl_TexCoord[0].st;
vec4 base = texture2D(myTexture, uv);
vec4 final_color = vec4(0.2, 0.2, 0.2, 1.0) * base;
vec3 vVec = normalize(viewVec);
vec3 bump = normalize(texture2D(normalTexture, uv).xyz * 2.0 - 1.0);
vec3 R = reflect(-vVec, bump);
vec3 lVec = normalize(vec3(0.0, -0.4, 0.5));
float diffuse = max(dot(lVec, bump), 0.0);
vec3 color = diffuse * texture2D(myTexture, gl_TexCoord[0].st).rgb;
float specular = pow(clamp(dot(R, lVec), 0.0, 1.0),1.0);
color += vec3(0.2*specular*diffuse);
col = vec4(color.r, color.g, color.b, alpha);
col *= gl_Color;
col = col * col; // SRGB -> Linear
col *= 1.8;
col.r = 1.0 - exp(1.0 - col.r) / exp(1.0);
col.g = 1.0 - exp(1.0 - col.g) / exp(1.0);
col.b = 1.0 - exp(1.0 - col.b) / exp(1.0);
col = sqrt(col); // Linear -> SRGB
if(fogDistance != 0.0){
float d = max(0.0, min(vPosition.z / fogDistance * 1.5 - 0.6, 1.0));
col = mix(col, skyBgColor, d);
}
gl_FragColor = vec4(col.r, col.g, col.b, alpha);
}

View File

@@ -1,98 +0,0 @@
uniform mat4 mWorldViewProj;
uniform mat4 mInvWorld;
uniform mat4 mTransWorld;
uniform float dayNightRatio;
varying vec3 vPosition;
varying vec3 viewVec;
void main(void)
{
gl_Position = mWorldViewProj * gl_Vertex;
vPosition = (mWorldViewProj * gl_Vertex).xyz;
vec3 tangent;
vec3 binormal;
vec3 c1 = cross( gl_Normal, vec3(0.0, 0.0, 1.0) );
vec3 c2 = cross( gl_Normal, vec3(0.0, 1.0, 0.0) );
if( length(c1)>length(c2) )
{
tangent = c1;
}
else
{
tangent = c2;
}
tangent = normalize(tangent);
//binormal = cross(gl_Normal, tangent);
//binormal = normalize(binormal);
vec4 color;
//color = vec4(1.0, 1.0, 1.0, 1.0);
float day = gl_Color.r;
float night = gl_Color.g;
float light_source = gl_Color.b;
/*color.r = mix(night, day, dayNightRatio);
color.g = color.r;
color.b = color.r;*/
float rg = mix(night, day, dayNightRatio);
rg += light_source * 1.5; // Make light sources brighter
float b = rg;
// Moonlight is blue
b += (day - night) / 13.0;
rg -= (day - night) / 13.0;
// Emphase blue a bit in darker places
// See C++ implementation in mapblock_mesh.cpp finalColorBlend()
b += max(0.0, (1.0 - abs(b - 0.13)/0.17) * 0.025);
// Artificial light is yellow-ish
// See C++ implementation in mapblock_mesh.cpp finalColorBlend()
rg += max(0.0, (1.0 - abs(rg - 0.85)/0.15) * 0.065);
color.r = rg;
color.g = rg;
color.b = b;
// Make sides and bottom darker than the top
color = color * color; // SRGB -> Linear
if(gl_Normal.y <= 0.5)
color *= 0.6;
//color *= 0.7;
color = sqrt(color); // Linear -> SRGB
color.a = gl_Color.a;
gl_FrontColor = gl_BackColor = color;
gl_TexCoord[0] = gl_MultiTexCoord0;
vec3 n1 = normalize(gl_NormalMatrix * gl_Normal);
vec4 tangent1 = vec4(tangent.x, tangent.y, tangent.z, 0);
//vec3 t1 = normalize(gl_NormalMatrix * tangent1);
//vec3 b1 = cross(n1, t1);
vec3 v;
vec3 vVertex = vec3(gl_ModelViewMatrix * gl_Vertex);
vec3 vVec = -vVertex;
//v.x = dot(vVec, t1);
//v.y = dot(vVec, b1);
//v.z = dot(vVec, n1);
//viewVec = vVec;
viewVec = normalize(vec3(0.0, -0.4, 0.5));
//Vector representing the 0th texture coordinate passed to fragment shader
//gl_TexCoord[0] = vec2(gl_MultiTexCoord0);
// Transform the current vertex
//gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}

View File

@@ -0,0 +1,54 @@
uniform sampler2D baseTexture;
uniform sampler2D normalTexture;
uniform sampler2D useNormalmap;
uniform vec4 skyBgColor;
uniform float fogDistance;
uniform vec3 eyePosition;
varying vec3 vPosition;
varying vec3 eyeVec;
const float e = 2.718281828459;
void main (void)
{
vec3 color;
vec2 uv = gl_TexCoord[0].st;
#ifdef USE_NORMALMAPS
float use_normalmap = texture2D(useNormalmap,vec2(1.0,1.0)).r;
#endif
#ifdef ENABLE_BUMPMAPPING
if (use_normalmap > 0.0) {
vec3 base = texture2D(baseTexture, uv).rgb;
vec3 vVec = normalize(eyeVec);
vec3 bump = normalize(texture2D(normalTexture, uv).xyz * 2.0 - 1.0);
vec3 R = reflect(-vVec, bump);
vec3 lVec = normalize(vVec);
float diffuse = max(dot(lVec, bump), 0.0);
float specular = pow(clamp(dot(R, lVec), 0.0, 1.0),1.0);
color = mix (base,diffuse*base,1.0) + 0.1 * specular * diffuse;
} else {
color = texture2D(baseTexture, uv).rgb;
}
#else
color = texture2D(baseTexture, uv).rgb;
#endif
float alpha = texture2D(baseTexture, uv).a;
vec4 col = vec4(color.r, color.g, color.b, alpha);
col *= gl_Color;
col = col * col; // SRGB -> Linear
col *= 1.8;
col.r = 1.0 - exp(1.0 - col.r) / e;
col.g = 1.0 - exp(1.0 - col.g) / e;
col.b = 1.0 - exp(1.0 - col.b) / e;
col = sqrt(col); // Linear -> SRGB
if(fogDistance != 0.0){
float d = max(0.0, min(vPosition.z / fogDistance * 1.5 - 0.6, 1.0));
col = mix(col, skyBgColor, d);
}
gl_FragColor = vec4(col.r, col.g, col.b, alpha);
}

View File

@@ -1,37 +1,44 @@
uniform mat4 mWorldViewProj;
uniform mat4 mInvWorld;
uniform mat4 mTransWorld;
uniform float dayNightRatio;
uniform float animationTimer;
uniform vec3 eyePosition;
varying vec3 vPosition;
varying vec3 viewVec;
varying vec3 eyeVec;
#ifdef ENABLE_WAVING_LEAVES
float smoothCurve( float x ) {
return x * x *( 3.0 - 2.0 * x );
}
float triangleWave( float x ) {
return abs( fract( x + 0.5 ) * 2.0 - 1.0 );
}
float smoothTriangleWave( float x ) {
return smoothCurve( triangleWave( x ) ) * 2.0 - 1.0;
}
#endif
void main(void)
{
gl_TexCoord[0] = gl_MultiTexCoord0;
#ifdef ENABLE_WAVING_LEAVES
vec4 pos = gl_Vertex;
vec4 pos2 = mTransWorld*gl_Vertex;
pos.x += (smoothTriangleWave(animationTimer*10.0 + pos2.x * 0.01 + pos2.z * 0.01) * 2.0 - 1.0) * 0.4;
pos.y += (smoothTriangleWave(animationTimer*15.0 + pos2.x * -0.01 + pos2.z * -0.01) * 2.0 - 1.0) * 0.2;
pos.z += (smoothTriangleWave(animationTimer*10.0 + pos2.x * -0.01 + pos2.z * -0.01) * 2.0 - 1.0) * 0.4;
gl_Position = mWorldViewProj * pos;
#else
gl_Position = mWorldViewProj * gl_Vertex;
#endif
vPosition = (mWorldViewProj * gl_Vertex).xyz;
vec3 tangent;
vec3 binormal;
vec3 c1 = cross( gl_Normal, vec3(0.0, 0.0, 1.0) );
vec3 c2 = cross( gl_Normal, vec3(0.0, 1.0, 0.0) );
if( length(c1)>length(c2) )
{
tangent = c1;
}
else
{
tangent = c2;
}
tangent = normalize(tangent);
//binormal = cross(gl_Normal, tangent);
//binormal = normalize(binormal);
eyeVec = (gl_ModelViewMatrix * gl_Vertex).xyz;
vec4 color;
//color = vec4(1.0, 1.0, 1.0, 1.0);
@@ -45,7 +52,7 @@ void main(void)
color.b = color.r;*/
float rg = mix(night, day, dayNightRatio);
rg += light_source * 1.5; // Make light sources brighter
rg += light_source * 2.5; // Make light sources brighter
float b = rg;
// Moonlight is blue
@@ -60,9 +67,9 @@ void main(void)
// See C++ implementation in mapblock_mesh.cpp finalColorBlend()
rg += max(0.0, (1.0 - abs(rg - 0.85)/0.15) * 0.065);
color.r = rg;
color.g = rg;
color.b = b;
color.r = clamp(rg,0.0,1.0);
color.g = clamp(rg,0.0,1.0);
color.b = clamp(b,0.0,1.0);
// Make sides and bottom darker than the top
color = color * color; // SRGB -> Linear
@@ -76,23 +83,5 @@ void main(void)
gl_FrontColor = gl_BackColor = color;
gl_TexCoord[0] = gl_MultiTexCoord0;
vec3 n1 = normalize(gl_NormalMatrix * gl_Normal);
vec4 tangent1 = vec4(tangent.x, tangent.y, tangent.z, 0);
//vec3 t1 = normalize(gl_NormalMatrix * tangent1);
//vec3 b1 = cross(n1, t1);
vec3 v;
vec3 vVertex = vec3(gl_ModelViewMatrix * gl_Vertex);
vec3 vVec = -vVertex;
//v.x = dot(vVec, t1);
//v.y = dot(vVec, b1);
//v.z = dot(vVec, n1);
//viewVec = vVec;
viewVec = normalize(vec3(0.0, -0.4, 0.5));
//Vector representing the 0th texture coordinate passed to fragment shader
//gl_TexCoord[0] = vec2(gl_MultiTexCoord0);
// Transform the current vertex
//gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}

View File

@@ -0,0 +1,54 @@
uniform sampler2D baseTexture;
uniform sampler2D normalTexture;
uniform sampler2D useNormalmap;
uniform vec4 skyBgColor;
uniform float fogDistance;
uniform vec3 eyePosition;
varying vec3 vPosition;
varying vec3 eyeVec;
const float e = 2.718281828459;
void main (void)
{
vec3 color;
vec2 uv = gl_TexCoord[0].st;
#ifdef USE_NORMALMAPS
float use_normalmap = texture2D(useNormalmap,vec2(1.0,1.0)).r;
#endif
#ifdef ENABLE_BUMPMAPPING
if (use_normalmap > 0.0) {
vec3 base = texture2D(baseTexture, uv).rgb;
vec3 vVec = normalize(eyeVec);
vec3 bump = normalize(texture2D(normalTexture, uv).xyz * 2.0 - 1.0);
vec3 R = reflect(-vVec, bump);
vec3 lVec = normalize(vVec);
float diffuse = max(dot(vec3(-1.0, -0.4, 0.5), bump), 0.0);
float specular = pow(clamp(dot(R, lVec), 0.0, 1.0),1.0);
color = mix (base,diffuse*base,1.0) + 0.1 * specular * diffuse;
} else {
color = texture2D(baseTexture, uv).rgb;
}
#else
color = texture2D(baseTexture, uv).rgb;
#endif
float alpha = gl_Color.a;
vec4 col = vec4(color.r, color.g, color.b, alpha);
col *= gl_Color;
col = col * col; // SRGB -> Linear
col *= 1.8;
col.r = 1.0 - exp(1.0 - col.r) / e;
col.g = 1.0 - exp(1.0 - col.g) / e;
col.b = 1.0 - exp(1.0 - col.b) / e;
col = sqrt(col); // Linear -> SRGB
if(fogDistance != 0.0){
float d = max(0.0, min(vPosition.z / fogDistance * 1.5 - 0.6, 1.0));
alpha = mix(alpha, 0.0, d);
}
gl_FragColor = vec4(col.r, col.g, col.b, alpha);
}

View File

@@ -1,15 +1,27 @@
uniform mat4 mWorldViewProj;
uniform mat4 mInvWorld;
uniform mat4 mTransWorld;
uniform float dayNightRatio;
uniform float animationTimer;
uniform vec3 eyePosition;
varying vec3 vPosition;
varying vec3 eyeVec;
void main(void)
{
#ifdef ENABLE_WAVING_WATER
vec4 pos2 = gl_Vertex;
pos2.y -= 2.0;
pos2.y -= sin (pos2.z/WATER_WAVE_LENGTH + animationTimer * WATER_WAVE_SPEED * WATER_WAVE_LENGTH) * WATER_WAVE_HEIGHT
+ sin ((pos2.z/WATER_WAVE_LENGTH + animationTimer * WATER_WAVE_SPEED * WATER_WAVE_LENGTH) / 7.0) * WATER_WAVE_HEIGHT;
gl_Position = mWorldViewProj * pos2;
#else
gl_Position = mWorldViewProj * gl_Vertex;
#endif
eyeVec = (gl_ModelViewMatrix * gl_Vertex).xyz;
vPosition = (mWorldViewProj * gl_Vertex).xyz;
vec4 color;
@@ -24,7 +36,7 @@ void main(void)
color.b = color.r;*/
float rg = mix(night, day, dayNightRatio);
rg += light_source * 1.0; // Make light sources brighter
rg += light_source * 2.5; // Make light sources brighter
float b = rg;
// Moonlight is blue
@@ -39,13 +51,13 @@ void main(void)
// See C++ implementation in mapblock_mesh.cpp finalColorBlend()
rg += max(0.0, (1.0 - abs(rg - 0.85)/0.15) * 0.065);
color.r = rg;
color.g = rg;
color.b = b;
color.r = clamp(rg,0.0,1.0);
color.g = clamp(rg,0.0,1.0);
color.b = clamp(b,0.0,1.0);
color.a = gl_Color.a;
gl_FrontColor = gl_BackColor = color;
gl_TexCoord[0] = gl_MultiTexCoord0;
}

View File

@@ -0,0 +1,54 @@
uniform sampler2D baseTexture;
uniform sampler2D normalTexture;
uniform sampler2D useNormalmap;
uniform vec4 skyBgColor;
uniform float fogDistance;
uniform vec3 eyePosition;
varying vec3 vPosition;
varying vec3 eyeVec;
const float e = 2.718281828459;
void main (void)
{
vec3 color;
vec2 uv = gl_TexCoord[0].st;
#ifdef USE_NORMALMAPS
float use_normalmap = texture2D(useNormalmap,vec2(1.0,1.0)).r;
#endif
#ifdef ENABLE_BUMPMAPPING
if (use_normalmap > 0.0) {
vec3 base = texture2D(baseTexture, uv).rgb;
vec3 vVec = normalize(eyeVec);
vec3 bump = normalize(texture2D(normalTexture, uv).xyz * 2.0 - 1.0);
vec3 R = reflect(-vVec, bump);
vec3 lVec = normalize(vVec);
float diffuse = max(dot(lVec, bump), 0.0);
float specular = pow(clamp(dot(R, lVec), 0.0, 1.0),1.0);
color = mix (base,diffuse*base,1.0) + 0.1 * specular * diffuse;
} else {
color = texture2D(baseTexture, uv).rgb;
}
#else
color = texture2D(baseTexture, uv).rgb;
#endif
float alpha = texture2D(baseTexture, uv).a;
vec4 col = vec4(color.r, color.g, color.b, alpha);
col *= gl_Color;
col = col * col; // SRGB -> Linear
col *= 1.8;
col.r = 1.0 - exp(1.0 - col.r) / e;
col.g = 1.0 - exp(1.0 - col.g) / e;
col.b = 1.0 - exp(1.0 - col.b) / e;
col = sqrt(col); // Linear -> SRGB
if(fogDistance != 0.0){
float d = max(0.0, min(vPosition.z / fogDistance * 1.5 - 0.6, 1.0));
col = mix(col, skyBgColor, d);
}
gl_FragColor = vec4(col.r, col.g, col.b, alpha);
}

View File

@@ -1,16 +1,45 @@
uniform mat4 mWorldViewProj;
uniform mat4 mInvWorld;
uniform mat4 mTransWorld;
uniform float dayNightRatio;
uniform float animationTimer;
uniform vec3 eyePosition;
varying vec3 vPosition;
varying vec3 eyeVec;
#ifdef ENABLE_WAVING_PLANTS
float smoothCurve( float x ) {
return x * x *( 3.0 - 2.0 * x );
}
float triangleWave( float x ) {
return abs( fract( x + 0.5 ) * 2.0 - 1.0 );
}
float smoothTriangleWave( float x ) {
return smoothCurve( triangleWave( x ) ) * 2.0 - 1.0;
}
#endif
void main(void)
{
gl_TexCoord[0] = gl_MultiTexCoord0;
#ifdef ENABLE_WAVING_PLANTS
vec4 pos = gl_Vertex;
vec4 pos2 = mTransWorld * gl_Vertex;
if (gl_TexCoord[0].y < 0.05) {
pos.x += (smoothTriangleWave(animationTimer * 20.0 + pos2.x * 0.1 + pos2.z * 0.1) * 2.0 - 1.0) * 0.8;
pos.y -= (smoothTriangleWave(animationTimer * 10.0 + pos2.x * -0.5 + pos2.z * -0.5) * 2.0 - 1.0) * 0.4;
}
gl_Position = mWorldViewProj * pos;
#else
gl_Position = mWorldViewProj * gl_Vertex;
#endif
vPosition = (mWorldViewProj * gl_Vertex).xyz;
eyeVec = (gl_ModelViewMatrix * gl_Vertex).xyz;
vec4 color;
//color = vec4(1.0, 1.0, 1.0, 1.0);
@@ -24,7 +53,7 @@ void main(void)
color.b = color.r;*/
float rg = mix(night, day, dayNightRatio);
rg += light_source * 1.0; // Make light sources brighter
rg += light_source * 2.5; // Make light sources brighter
float b = rg;
// Moonlight is blue
@@ -39,9 +68,9 @@ void main(void)
// See C++ implementation in mapblock_mesh.cpp finalColorBlend()
rg += max(0.0, (1.0 - abs(rg - 0.85)/0.15) * 0.065);
color.r = rg;
color.g = rg;
color.b = b;
color.r = clamp(rg,0.0,1.0);
color.g = clamp(rg,0.0,1.0);
color.b = clamp(b,0.0,1.0);
// Make sides and bottom darker than the top
color = color * color; // SRGB -> Linear
@@ -54,5 +83,4 @@ void main(void)
gl_FrontColor = gl_BackColor = color;
gl_TexCoord[0] = gl_MultiTexCoord0;
}

View File

@@ -0,0 +1 @@
trans_alphach_ref

View File

@@ -0,0 +1,90 @@
uniform sampler2D baseTexture;
uniform sampler2D normalTexture;
uniform sampler2D useNormalmap;
uniform vec4 skyBgColor;
uniform float fogDistance;
uniform vec3 eyePosition;
varying vec3 vPosition;
varying vec3 eyeVec;
#ifdef ENABLE_PARALLAX_OCCLUSION
varying vec3 tsEyeVec;
#endif
const float e = 2.718281828459;
void main (void)
{
vec3 color;
vec2 uv = gl_TexCoord[0].st;
#ifdef USE_NORMALMAPS
float use_normalmap = texture2D(useNormalmap,vec2(1.0,1.0)).r;
#endif
#ifdef ENABLE_PARALLAX_OCCLUSION
float height;
vec2 tsEye = vec2(tsEyeVec.x,-tsEyeVec.y);
if (use_normalmap > 0.0) {
float map_height = texture2D(normalTexture, uv).a;
if (map_height < 1.0){
float height = PARALLAX_OCCLUSION_SCALE * map_height - PARALLAX_OCCLUSION_BIAS;
uv = uv + height * tsEye;
}
}
#endif
/* Steep parallax code, for future use
if ((parallaxMappingMode == 2.0) && (use_normalmap > 0.0)) {
const float numSteps = 40.0;
float height = 1.0;
float step = 1.0 / numSteps;
vec4 NB = texture2D(normalTexture, uv);
vec2 delta = tsEye * parallaxMappingScale / numSteps;
for (float i = 0.0; i < numSteps; i++) {
if (NB.a < height) {
height -= step;
uv += delta;
NB = texture2D(normalTexture, uv);
} else {
break;
}
}
}
*/
#ifdef ENABLE_BUMPMAPPING
if (use_normalmap > 0.0) {
vec3 base = texture2D(baseTexture, uv).rgb;
vec3 vVec = normalize(eyeVec);
vec3 bump = normalize(texture2D(normalTexture, uv).xyz * 2.0 - 1.0);
vec3 R = reflect(-vVec, bump);
vec3 lVec = normalize(vVec);
float diffuse = max(dot(lVec, bump), 0.0);
float specular = pow(clamp(dot(R, lVec), 0.0, 1.0),1.0);
color = mix (base,diffuse*base,1.0) + 0.1 * specular * diffuse;
} else {
color = texture2D(baseTexture, uv).rgb;
}
#else
color = texture2D(baseTexture, uv).rgb;
#endif
float alpha = texture2D(baseTexture, uv).a;
vec4 col = vec4(color.r, color.g, color.b, alpha);
col *= gl_Color;
col = col * col; // SRGB -> Linear
col *= 1.8;
col.r = 1.0 - exp(1.0 - col.r) / e;
col.g = 1.0 - exp(1.0 - col.g) / e;
col.b = 1.0 - exp(1.0 - col.b) / e;
col = sqrt(col); // Linear -> SRGB
if(fogDistance != 0.0){
float d = max(0.0, min(vPosition.z / fogDistance * 1.5 - 0.6, 1.0));
col = mix(col, skyBgColor, d);
}
gl_FragColor = vec4(col.r, col.g, col.b, alpha);
}

View File

@@ -0,0 +1,102 @@
uniform mat4 mWorldViewProj;
uniform mat4 mInvWorld;
uniform mat4 mTransWorld;
uniform float dayNightRatio;
uniform vec3 eyePosition;
varying vec3 vPosition;
varying vec3 eyeVec;
#ifdef ENABLE_PARALLAX_OCCLUSION
varying vec3 tsEyeVec;
#endif
void main(void)
{
gl_Position = mWorldViewProj * gl_Vertex;
vPosition = (mWorldViewProj * gl_Vertex).xyz;
eyeVec = (gl_ModelViewMatrix * gl_Vertex).xyz;
#ifdef ENABLE_PARALLAX_OCCLUSION
vec3 normal,tangent,binormal;
normal = normalize(gl_NormalMatrix * gl_Normal);
if (gl_Normal.x > 0.5) {
// 1.0, 0.0, 0.0
tangent = normalize(gl_NormalMatrix * vec3( 0.0, 0.0, -1.0));
binormal = normalize(gl_NormalMatrix * vec3( 0.0, -1.0, 0.0));
} else if (gl_Normal.x < -0.5) {
// -1.0, 0.0, 0.0
tangent = normalize(gl_NormalMatrix * vec3( 0.0, 0.0, 1.0));
binormal = normalize(gl_NormalMatrix * vec3( 0.0, -1.0, 0.0));
} else if (gl_Normal.y > 0.5) {
// 0.0, 1.0, 0.0
tangent = normalize(gl_NormalMatrix * vec3( 1.0, 0.0, 0.0));
binormal = normalize(gl_NormalMatrix * vec3( 0.0, 0.0, 1.0));
} else if (gl_Normal.y < -0.5) {
// 0.0, -1.0, 0.0
tangent = normalize(gl_NormalMatrix * vec3( 1.0, 0.0, 0.0));
binormal = normalize(gl_NormalMatrix * vec3( 0.0, 0.0, 1.0));
} else if (gl_Normal.z > 0.5) {
// 0.0, 0.0, 1.0
tangent = normalize(gl_NormalMatrix * vec3( 1.0, 0.0, 0.0));
binormal = normalize(gl_NormalMatrix * vec3( 0.0, -1.0, 0.0));
} else if (gl_Normal.z < -0.5) {
// 0.0, 0.0, -1.0
tangent = normalize(gl_NormalMatrix * vec3(-1.0, 0.0, 0.0));
binormal = normalize(gl_NormalMatrix * vec3( 0.0, -1.0, 0.0));
}
mat3 tbnMatrix = mat3( tangent.x, binormal.x, normal.x,
tangent.y, binormal.y, normal.y,
tangent.z, binormal.z, normal.z);
tsEyeVec = normalize(eyeVec * tbnMatrix);
#endif
vec4 color;
//color = vec4(1.0, 1.0, 1.0, 1.0);
float day = gl_Color.r;
float night = gl_Color.g;
float light_source = gl_Color.b;
/*color.r = mix(night, day, dayNightRatio);
color.g = color.r;
color.b = color.r;*/
float rg = mix(night, day, dayNightRatio);
rg += light_source * 2.5; // Make light sources brighter
float b = rg;
// Moonlight is blue
b += (day - night) / 13.0;
rg -= (day - night) / 13.0;
// Emphase blue a bit in darker places
// See C++ implementation in mapblock_mesh.cpp finalColorBlend()
b += max(0.0, (1.0 - abs(b - 0.13)/0.17) * 0.025);
// Artificial light is yellow-ish
// See C++ implementation in mapblock_mesh.cpp finalColorBlend()
rg += max(0.0, (1.0 - abs(rg - 0.85)/0.15) * 0.065);
color.r = clamp(rg,0.0,1.0);
color.g = clamp(rg,0.0,1.0);
color.b = clamp(b,0.0,1.0);
// Make sides and bottom darker than the top
color = color * color; // SRGB -> Linear
if(gl_Normal.y <= 0.5)
color *= 0.6;
//color *= 0.7;
color = sqrt(color); // Linear -> SRGB
color.a = gl_Color.a;
gl_FrontColor = gl_BackColor = color;
gl_TexCoord[0] = gl_MultiTexCoord0;
}

View File

@@ -1,25 +0,0 @@
uniform sampler2D myTexture;
uniform vec4 skyBgColor;
uniform float fogDistance;
varying vec3 vPosition;
void main (void)
{
//vec4 col = vec4(1.0, 0.0, 0.0, 1.0);
vec4 col = texture2D(myTexture, vec2(gl_TexCoord[0]));
float a = col.a;
col *= gl_Color;
col = col * col; // SRGB -> Linear
col *= 1.8;
col.r = 1.0 - exp(1.0 - col.r) / exp(1.0);
col.g = 1.0 - exp(1.0 - col.g) / exp(1.0);
col.b = 1.0 - exp(1.0 - col.b) / exp(1.0);
col = sqrt(col); // Linear -> SRGB
if(fogDistance != 0.0){
float d = max(0.0, min(vPosition.z / fogDistance * 1.5 - 0.6, 1.0));
col = mix(col, skyBgColor, d);
}
gl_FragColor = vec4(col.r, col.g, col.b, a);
}

View File

@@ -1,23 +0,0 @@
uniform sampler2D myTexture;
uniform float fogDistance;
varying vec3 vPosition;
void main (void)
{
vec4 col = texture2D(myTexture, vec2(gl_TexCoord[0]));
col *= gl_Color;
float a = gl_Color.a;
col = col * col; // SRGB -> Linear
col *= 1.8;
col.r = 1.0 - exp(1.0 - col.r) / exp(1.0);
col.g = 1.0 - exp(1.0 - col.g) / exp(1.0);
col.b = 1.0 - exp(1.0 - col.b) / exp(1.0);
col = sqrt(col); // Linear -> SRGB
if(fogDistance != 0.0){
float d = max(0.0, min(vPosition.z / fogDistance * 1.5 - 0.6, 1.0));
a = mix(a, 0.0, d);
}
gl_FragColor = vec4(col.r, col.g, col.b, a);
}

View File

@@ -1 +0,0 @@
trans_alphach

View File

@@ -1,25 +0,0 @@
uniform sampler2D myTexture;
uniform float fogDistance;
varying vec3 vPosition;
void main (void)
{
vec4 col = texture2D(myTexture, vec2(gl_TexCoord[0]));
col *= gl_Color;
float a = col.a;
col = col * col; // SRGB -> Linear
col *= 1.8;
col.r = 1.0 - exp(1.0 - col.r) / exp(1.0);
col.g = 1.0 - exp(1.0 - col.g) / exp(1.0);
col.b = 1.0 - exp(1.0 - col.b) / exp(1.0);
col = sqrt(col); // Linear -> SRGB
if(fogDistance != 0.0){
float d = max(0.0, min(vPosition.z / fogDistance * 1.5 - 0.6, 1.0));
a = mix(a, 0.0, d);
}
gl_FragColor = vec4(col.r, col.g, col.b, a);
}

View File

@@ -1,51 +0,0 @@
uniform mat4 mWorldViewProj;
uniform mat4 mInvWorld;
uniform mat4 mTransWorld;
uniform float dayNightRatio;
varying vec3 vPosition;
void main(void)
{
gl_Position = mWorldViewProj * gl_Vertex;
vPosition = (mWorldViewProj * gl_Vertex).xyz;
vec4 color;
//color = vec4(1.0, 1.0, 1.0, 1.0);
float day = gl_Color.r;
float night = gl_Color.g;
float light_source = gl_Color.b;
/*color.r = mix(night, day, dayNightRatio);
color.g = color.r;
color.b = color.r;*/
float rg = mix(night, day, dayNightRatio);
rg += light_source * 1.0; // Make light sources brighter
float b = rg;
// Moonlight is blue
b += (day - night) / 13.0;
rg -= (day - night) / 13.0;
// Emphase blue a bit in darker places
// See C++ implementation in mapblock_mesh.cpp finalColorBlend()
b += max(0.0, (1.0 - abs(b - 0.13)/0.17) * 0.025);
// Artificial light is yellow-ish
// See C++ implementation in mapblock_mesh.cpp finalColorBlend()
rg += max(0.0, (1.0 - abs(rg - 0.85)/0.15) * 0.065);
color.r = rg;
color.g = rg;
color.b = b;
color.a = gl_Color.a;
gl_FrontColor = gl_BackColor = color;
gl_TexCoord[0] = gl_MultiTexCoord0;
}

View File

@@ -1,4 +1,4 @@
Minetest Lua Modding API Reference 0.4.8
Minetest Lua Modding API Reference 0.4.9
========================================
More information at http://www.minetest.net/
Developer Wiki: http://dev.minetest.net/
@@ -129,13 +129,6 @@ screenshot.png:
description.txt:
File containing desctiption to be shown within mainmenu.
optdepends.txt:
An alternative way of specifying optional dependencies.
Like depends.txt, a single line contains a single modname.
NOTE: This file exists for compatibility purposes only and
support for it will be removed from the engine by the end of 2013.
init.lua:
The main Lua script. Running this script should register everything it
wants to register. Subsequent execution depends on minetest calling the
@@ -1105,6 +1098,7 @@ minetest.pos_to_string({x=X,y=Y,z=Z}) -> "(X,Y,Z)"
^ Convert position to a printable string
minetest.string_to_pos(string) -> position
^ Same but in reverse
minetest.formspec_escape(string) -> string
^ escapes characters [ ] \ , ; that can not be used in formspecs
minetest.is_yes(arg)
^ returns whether arg can be interpreted as yes
@@ -1174,6 +1168,9 @@ minetest.register_on_respawnplayer(func(ObjectRef))
^ Called when player is to be respawned
^ Called _before_ repositioning of player occurs
^ return true in func to disable regular player placement
minetest.register_on_prejoinplayer(func(name, ip))
^ Called before a player joins the game
^ If it returns a string, the player is disconnected with that string as reason
minetest.register_on_joinplayer(func(ObjectRef))
^ Called when a player joins the game
minetest.register_on_leaveplayer(func(ObjectRef))
@@ -1254,6 +1251,8 @@ Environment access:
minetest.set_node(pos, node)
minetest.add_node(pos, node): alias set_node(pos, node)
^ Set node at position (node = {name="foo", param1=0, param2=0})
minetest.swap_node(pos, node)
^ Set node at position, but don't remove metadata
minetest.remove_node(pos)
^ Equivalent to set_node(pos, "air")
minetest.get_node(pos)
@@ -1290,6 +1289,10 @@ minetest.get_perlin(seeddiff, octaves, persistence, scale)
^ Return world-specific perlin noise (int(worldseed)+seeddiff)
minetest.get_voxel_manip()
^ Return voxel manipulator object
minetest.set_gen_notify(flags)
^ Set the types of on-generate notifications that should be collected
^ flags is a comma-delimited combination of:
^ dungeon, temple, cave_begin, cave_end, large_cave_begin, large_cave_end
minetest.get_mapgen_object(objectname)
^ Return requested mapgen object if available (see Mapgen objects)
minetest.set_mapgen_params(MapgenParams)
@@ -1302,8 +1305,9 @@ minetest.set_mapgen_params(MapgenParams)
^ flags and flagmask are in the same format and have the same options as 'mgflags' in minetest.conf
minetest.clear_objects()
^ clear all objects in the environments
minetest.line_of_sight(pos1,pos2,stepsize) ->true/false
^ checkif there is a direct line of sight between pos1 and pos2
minetest.line_of_sight(pos1, pos2, stepsize) -> true/false, pos
^ Check if there is a direct line of sight between pos1 and pos2
^ Returns the position of the blocking node when false
^ pos1 First position
^ pos2 Second position
^ stepsize smaller gives more accurate results but requires more computing
@@ -1402,8 +1406,8 @@ minetest.handle_node_drops(pos, drops, digger)
^ Can be overridden to get different functionality (eg. dropping items on
ground)
Rollbacks:
minetest.rollback_get_last_node_actor(p, range, seconds) -> actor, p, seconds
Rollback:
minetest.rollback_get_node_actions(pos, range, seconds, limit) -> {{actor, pos, time, oldnode, newnode}, ...}
^ Find who has done something to a node, or near a node
^ actor: "player:<name>", also "liquid".
minetest.rollback_revert_actions_by(actor, seconds) -> bool, log messages
@@ -1489,7 +1493,7 @@ minetest.delete_particlespawner(id, player)
^ otherwise on all clients
Schematics:
minetest.create_schematic(p1, p2, probability_list, filename)
minetest.create_schematic(p1, p2, probability_list, filename, slice_prob_list)
^ Create a schematic from the volume of map specified by the box formed by p1 and p2.
^ Apply the specified probability values to the specified nodes in probability_list.
^ probability_list is an array of tables containing two fields, pos and prob.
@@ -1498,6 +1502,9 @@ minetest.create_schematic(p1, p2, probability_list, filename)
^ If there are two or more entries with the same pos value, the last occuring in the array is used.
^ If pos is not inside the box formed by p1 and p2, it is ignored.
^ If probability_list is nil, no probabilities are applied.
^ Slice probability works in the same manner, except takes a field called ypos instead which indicates
^ the y position of the slice with a probability applied.
^ If slice probability list is nil, no slice probabilities are applied.
^ Saves schematic in the Minetest Schematic format to filename.
minetest.place_schematic(pos, schematic, rotation, replacements)
@@ -1523,7 +1530,16 @@ minetest.parse_json(string[, nullvalue]) -> something
^ nullvalue: returned in place of the JSON null; defaults to nil
^ On success returns a table, a string, a number, a boolean or nullvalue
^ On failure outputs an error message and returns nil
^ Example: parse_json("[10, {\"a\":false}]") -> {[1] = 10, [2] = {a = false}}
^ Example: parse_json("[10, {\"a\":false}]") -> {10, {a = false}}
minetest.write_json(data[, styled]) -> string or nil and error message
^ Convert a Lua table into a JSON string
^ styled: Outputs in a human-readable format if this is set, defaults to false
^ Un-serializable things like functions and userdata are saved as null.
^ Warning: JSON is more strict than the Lua table format.
1. You can only use strings and positive integers of at least one as keys.
2. You can not mix string and integer keys.
This is due to the fact that Javascript has two distinct array and object values.
^ Example: write_json({10, {a = false}}) -> "[10, {\"a\": false}]"
minetest.serialize(table) -> string
^ Convert a table containing tables, strings, numbers, booleans and nils
into string form readable by minetest.deserialize
@@ -1701,10 +1717,13 @@ Player-only: (no-op for other objects)
{jump=bool,right=bool,left=bool,LMB=bool,RMB=bool,sneak=bool,aux1=bool,down=bool,up=bool}
- get_player_control_bits(): returns integer with bit packed player pressed keys
bit nr/meaning: 0/up ,1/down ,2/left ,3/right ,4/jump ,5/aux1 ,6/sneak ,7/LMB ,8/RMB
- set_physics_override(speed, jump, gravity)
modifies per-player walking speed, jump height, and gravity.
Values default to 1 and act as offsets to the physics settings
in minetest.conf. nil will keep the current setting.
- set_physics_override({
speed = 1.0, -- multiplier to default value
jump = 1.0, -- multiplier to default value
gravity = 1.0, -- multiplier to default value
sneak = true, -- whether player can sneak
sneak_glitch = true, -- whether player can use the sneak glitch
})
- hud_add(hud definition): add a HUD element described by HUD def, returns ID number on success
- hud_remove(id): remove the HUD element of the specified id
- hud_change(id, stat, value): change a value of a previously added HUD element
@@ -1815,11 +1834,19 @@ methods:
- update_map(): Update map after writing chunk back to map.
^ To be used only by VoxelManip objects created by the mod itself; not a VoxelManip that was
^ retrieved from minetest.get_mapgen_object
- set_lighting(light): Set the lighting within the VoxelManip
- set_lighting(light, p1, p2): Set the lighting within the VoxelManip to a uniform value
^ light is a table, {day=<0...15>, night=<0...15>}
^ To be used only by a VoxelManip object from minetest.get_mapgen_object
- calc_lighting(): Calculate lighting within the VoxelManip
^ (p1, p2) is the area in which lighting is set; defaults to the whole area if left out
- get_light_data(): Gets the light data read into the VoxelManip object
^ Returns an array (indicies 1 to volume) of integers ranging from 0 to 255
^ Each value is the bitwise combination of day and night light values (0..15 each)
^ light = day + (night * 16)
- set_light_data(light_data): Sets the param1 (light) contents of each node in the VoxelManip
^ expects lighting data in the same format that get_light_data() returns
- calc_lighting(p1, p2): Calculate lighting within the VoxelManip
^ To be used only by a VoxelManip object from minetest.get_mapgen_object
^ (p1, p2) is the area in which lighting is set; defaults to the whole area if left out
- update_liquids(): Update liquid flow
VoxelArea: A helper class for voxel areas
@@ -1880,6 +1907,12 @@ the current mapgen.
Returns an array containing the humidity values of nodes in the most recently generated chunk by the
current mapgen.
- gennotify
Returns a table mapping requested generation notification types to arrays of positions at which the
corresponding generated structures are located at within the current chunk. To set the capture of positions
of interest to be recorded on generate, use minetest.set_gen_notify().
Possible fields of the table returned are: dungeon, temple, cave_begin, cave_end, large_cave_begin, large_cave_end
Registered entities
--------------------
- Functions receive a "luaentity" as self:

View File

@@ -1,4 +1,4 @@
Minetest Lua Mainmenu API Reference 0.4.6
Minetest Lua Mainmenu API Reference 0.4.9
========================================
Introduction
@@ -33,9 +33,9 @@ engine.close()
Filesystem:
engine.get_scriptdir()
^ returns directory of script
engine.get_modpath()
engine.get_modpath() (possible in async calls)
^ returns path to global modpath
engine.get_modstore_details(modid)
engine.get_modstore_details(modid) (possible in async calls)
^ modid numeric id of mod in modstore
^ returns {
id = <numeric id of mod in modstore>,
@@ -47,7 +47,7 @@ engine.get_modstore_details(modid)
license = <short description of license>,
rating = <float value of current rating>
}
engine.get_modstore_list()
engine.get_modstore_list() (possible in async calls)
^ returns {
[1] = {
id = <numeric id of mod in modstore>,
@@ -55,19 +55,21 @@ engine.get_modstore_list()
basename = <basename for mod>
}
}
engine.get_gamepath()
engine.get_gamepath() (possible in async calls)
^ returns path to global gamepath
engine.get_dirlist(path,onlydirs)
engine.get_texturepath() (possible in async calls)
^ returns path to default textures
engine.get_dirlist(path,onlydirs) (possible in async calls)
^ path to get subdirs from
^ onlydirs should result contain only dirs?
^ returns list of folders within path
engine.create_dir(absolute_path)
engine.create_dir(absolute_path) (possible in async calls)
^ absolute_path to directory to create (needs to be absolute)
^ returns true/false
engine.delete_dir(absolute_path)
engine.delete_dir(absolute_path) (possible in async calls)
^ absolute_path to directory to delete (needs to be absolute)
^ returns true/false
engine.copy_dir(source,destination,keep_soure)
engine.copy_dir(source,destination,keep_soure) (possible in async calls)
^ source folder
^ destination folder
^ keep_source DEFAULT true --> if set to false source is deleted after copying
@@ -76,11 +78,11 @@ engine.extract_zip(zipfile,destination) [unzip within path required]
^ zipfile to extract
^ destination folder to extract to
^ returns true/false
engine.download_file(url,target)
engine.download_file(url,target) (possible in async calls)
^ url to download
^ target to store to
^ returns true/false
engine.get_version()
engine.get_version() (possible in async calls)
^ returns current minetest version
engine.sound_play(spec, looped) -> handle
^ spec = SimpleSoundSpec (see lua-api.txt)
@@ -105,10 +107,10 @@ engine.get_game(index)
DEPRECATED:
addon_mods_paths = {[1] = <path>,},
}
engine.get_games() -> table of all games in upper format
engine.get_games() -> table of all games in upper format (possible in async calls)
Favorites:
engine.get_favorites(location) -> list of favorites
engine.get_favorites(location) -> list of favorites (possible in async calls)
^ location: "local" or "online"
^ returns {
[1] = {
@@ -128,21 +130,21 @@ engine.get_favorites(location) -> list of favorites
engine.delete_favorite(id, location) -> success
Logging:
engine.debug(line)
engine.debug(line) (possible in async calls)
^ Always printed to stderr and logfile (print() is redirected here)
engine.log(line)
engine.log(loglevel, line)
engine.log(line) (possible in async calls)
engine.log(loglevel, line) (possible in async calls)
^ loglevel one of "error", "action", "info", "verbose"
Settings:
engine.setting_set(name, value)
engine.setting_get(name) -> string or nil
engine.setting_get(name) -> string or nil (possible in async calls)
engine.setting_setbool(name, value)
engine.setting_getbool(name) -> bool or nil
engine.setting_getbool(name) -> bool or nil (possible in async calls)
engine.setting_save() -> nil, save all settings to config file
Worlds:
engine.get_worlds() -> list of worlds
engine.get_worlds() -> list of worlds (possible in async calls)
^ returns {
[1] = {
path = <full path to world>,
@@ -174,7 +176,7 @@ engine.gettext(string) -> string
fgettext(string, ...) -> string
^ call engine.gettext(string), replace "$1"..."$9" with the given
^ extra arguments, call engine.formspec_escape and return the result
engine.parse_json(string[, nullvalue]) -> something
engine.parse_json(string[, nullvalue]) -> something (possible in async calls)
^ see minetest.parse_json (lua_api.txt)
dump(obj, dumped={})
^ Return object serialized as a string
@@ -182,9 +184,24 @@ string:split(separator)
^ eg. string:split("a,b", ",") == {"a","b"}
string:trim()
^ eg. string.trim("\n \t\tfoo bar\t ") == "foo bar"
minetest.is_yes(arg)
minetest.is_yes(arg) (possible in async calls)
^ returns whether arg can be interpreted as yes
Async:
engine.handle_async(async_job,parameters,finished)
^ execute a function asynchronously
^ async_job is a function receiving one parameter and returning one parameter
^ parameters parameter table passed to async_job
^ finished function to be called once async_job has finished
^ the result of async_job is passed to this function
Limitations of Async operations
-No access to global lua variables, don't even try
-Limited set of available functions
e.g. No access to functions modifying menu like engine.start,engine.close,
engine.file_open_dialog
Class reference
----------------
Settings: see lua_api.txt

View File

@@ -712,7 +712,6 @@ end
minetest.register_node("default:stone", {
description = "Stone",
tiles ={"default_stone.png"},
is_ground_content = true,
groups = {cracky=3},
drop = 'default:cobble',
legacy_mineral = true,
@@ -722,7 +721,6 @@ minetest.register_node("default:stone", {
minetest.register_node("default:stone_with_coal", {
description = "Stone with coal",
tiles ={"default_stone.png^default_mineral_coal.png"},
is_ground_content = true,
groups = {cracky=3},
drop = 'default:coal_lump',
sounds = default.node_sound_stone_defaults(),
@@ -731,7 +729,6 @@ minetest.register_node("default:stone_with_coal", {
minetest.register_node("default:stone_with_iron", {
description = "Stone with iron",
tiles ={"default_stone.png^default_mineral_iron.png"},
is_ground_content = true,
groups = {cracky=3},
drop = 'default:iron_lump',
sounds = default.node_sound_stone_defaults(),
@@ -740,7 +737,6 @@ minetest.register_node("default:stone_with_iron", {
minetest.register_node("default:dirt_with_grass", {
description = "Dirt with grass",
tiles ={"default_grass.png", "default_dirt.png", "default_dirt.png^default_grass_side.png"},
is_ground_content = true,
groups = {crumbly=3, soil=1},
drop = 'default:dirt',
sounds = default.node_sound_dirt_defaults({
@@ -751,7 +747,6 @@ minetest.register_node("default:dirt_with_grass", {
minetest.register_node("default:dirt_with_grass_footsteps", {
description = "Dirt with grass and footsteps",
tiles ={"default_grass_footsteps.png", "default_dirt.png", "default_dirt.png^default_grass_side.png"},
is_ground_content = true,
groups = {crumbly=3, soil=1},
drop = 'default:dirt',
sounds = default.node_sound_dirt_defaults({
@@ -762,7 +757,6 @@ minetest.register_node("default:dirt_with_grass_footsteps", {
minetest.register_node("default:dirt", {
description = "Dirt",
tiles ={"default_dirt.png"},
is_ground_content = true,
groups = {crumbly=3, soil=1},
sounds = default.node_sound_dirt_defaults(),
})
@@ -770,7 +764,6 @@ minetest.register_node("default:dirt", {
minetest.register_node("default:sand", {
description = "Sand",
tiles ={"default_sand.png"},
is_ground_content = true,
groups = {crumbly=3, falling_node=1},
sounds = default.node_sound_sand_defaults(),
})
@@ -778,7 +771,6 @@ minetest.register_node("default:sand", {
minetest.register_node("default:gravel", {
description = "Gravel",
tiles ={"default_gravel.png"},
is_ground_content = true,
groups = {crumbly=2, falling_node=1},
sounds = default.node_sound_dirt_defaults({
footstep = {name="default_gravel_footstep", gain=0.45},
@@ -788,7 +780,6 @@ minetest.register_node("default:gravel", {
minetest.register_node("default:sandstone", {
description = "Sandstone",
tiles ={"default_sandstone.png"},
is_ground_content = true,
groups = {crumbly=2,cracky=2},
drop = 'default:sand',
sounds = default.node_sound_stone_defaults(),
@@ -797,7 +788,6 @@ minetest.register_node("default:sandstone", {
minetest.register_node("default:clay", {
description = "Clay",
tiles ={"default_clay.png"},
is_ground_content = true,
groups = {crumbly=3},
drop = 'default:clay_lump 4',
sounds = default.node_sound_dirt_defaults({
@@ -808,7 +798,6 @@ minetest.register_node("default:clay", {
minetest.register_node("default:brick", {
description = "Brick",
tiles ={"default_brick.png"},
is_ground_content = true,
groups = {cracky=3},
drop = 'default:clay_brick 4',
sounds = default.node_sound_stone_defaults(),
@@ -817,7 +806,6 @@ minetest.register_node("default:brick", {
minetest.register_node("default:tree", {
description = "Tree",
tiles ={"default_tree_top.png", "default_tree_top.png", "default_tree.png"},
is_ground_content = true,
groups = {snappy=2,choppy=2,oddly_breakable_by_hand=1},
sounds = default.node_sound_wood_defaults(),
})
@@ -825,7 +813,6 @@ minetest.register_node("default:tree", {
minetest.register_node("default:jungletree", {
description = "Jungle Tree",
tiles ={"default_jungletree_top.png", "default_jungletree_top.png", "default_jungletree.png"},
is_ground_content = true,
groups = {snappy=2,choppy=2,oddly_breakable_by_hand=1},
sounds = default.node_sound_wood_defaults(),
})
@@ -849,6 +836,7 @@ minetest.register_node("default:leaves", {
visual_scale = 1.3,
tiles ={"default_leaves.png"},
paramtype = "light",
is_ground_content = false,
groups = {snappy=3},
drop = {
max_items = 1,
@@ -871,7 +859,6 @@ minetest.register_node("default:leaves", {
minetest.register_node("default:cactus", {
description = "Cactus",
tiles ={"default_cactus_top.png", "default_cactus_top.png", "default_cactus_side.png"},
is_ground_content = true,
groups = {snappy=2,choppy=3},
sounds = default.node_sound_wood_defaults(),
})
@@ -883,7 +870,6 @@ minetest.register_node("default:papyrus", {
inventory_image = "default_papyrus.png",
wield_image = "default_papyrus.png",
paramtype = "light",
is_ground_content = true,
walkable = false,
groups = {snappy=3},
sounds = default.node_sound_leaves_defaults(),
@@ -892,7 +878,6 @@ minetest.register_node("default:papyrus", {
minetest.register_node("default:bookshelf", {
description = "Bookshelf",
tiles ={"default_wood.png", "default_wood.png", "default_bookshelf.png"},
is_ground_content = true,
groups = {snappy=2,choppy=3,oddly_breakable_by_hand=2},
sounds = default.node_sound_wood_defaults(),
})
@@ -904,7 +889,6 @@ minetest.register_node("default:glass", {
inventory_image = minetest.inventorycube("default_glass.png"),
paramtype = "light",
sunlight_propagates = true,
is_ground_content = true,
groups = {snappy=2,cracky=3,oddly_breakable_by_hand=3},
sounds = default.node_sound_glass_defaults(),
})
@@ -916,7 +900,6 @@ minetest.register_node("default:fence_wood", {
inventory_image = "default_fence.png",
wield_image = "default_fence.png",
paramtype = "light",
is_ground_content = true,
selection_box = {
type = "fixed",
fixed = {-1/7, -1/2, -1/7, 1/7, 1/2, 1/7},
@@ -932,7 +915,6 @@ minetest.register_node("default:rail", {
inventory_image = "default_rail.png",
wield_image = "default_rail.png",
paramtype = "light",
is_ground_content = true,
walkable = false,
selection_box = {
type = "fixed",
@@ -949,7 +931,6 @@ minetest.register_node("default:ladder", {
wield_image = "default_ladder.png",
paramtype = "light",
paramtype2 = "wallmounted",
is_ground_content = true,
walkable = false,
climbable = true,
selection_box = {
@@ -966,7 +947,6 @@ minetest.register_node("default:ladder", {
minetest.register_node("default:wood", {
description = "Wood",
tiles ={"default_wood.png"},
is_ground_content = true,
groups = {snappy=2,choppy=2,oddly_breakable_by_hand=2},
sounds = default.node_sound_wood_defaults(),
})
@@ -974,7 +954,6 @@ minetest.register_node("default:wood", {
minetest.register_node("default:mese", {
description = "Mese",
tiles ={"default_mese.png"},
is_ground_content = true,
groups = {cracky=1,level=2},
sounds = default.node_sound_defaults(),
})
@@ -982,7 +961,6 @@ minetest.register_node("default:mese", {
minetest.register_node("default:cloud", {
description = "Cloud",
tiles ={"default_cloud.png"},
is_ground_content = true,
sounds = default.node_sound_defaults(),
})
@@ -1104,6 +1082,7 @@ minetest.register_node("default:torch", {
paramtype = "light",
paramtype2 = "wallmounted",
sunlight_propagates = true,
is_ground_content = false,
walkable = false,
light_source = LIGHT_MAX-1,
selection_box = {
@@ -1126,6 +1105,7 @@ minetest.register_node("default:sign_wall", {
paramtype = "light",
paramtype2 = "wallmounted",
sunlight_propagates = true,
is_ground_content = false,
walkable = false,
selection_box = {
type = "wallmounted",
@@ -1160,6 +1140,7 @@ minetest.register_node("default:chest", {
paramtype2 = "facedir",
groups = {snappy=2,choppy=2,oddly_breakable_by_hand=2},
legacy_facedir_simple = true,
is_ground_content = false,
sounds = default.node_sound_wood_defaults(),
on_construct = function(pos)
local meta = minetest.get_meta(pos)
@@ -1192,6 +1173,7 @@ minetest.register_node("default:chest_locked", {
paramtype2 = "facedir",
groups = {snappy=2,choppy=2,oddly_breakable_by_hand=2},
legacy_facedir_simple = true,
is_ground_content = false,
sounds = default.node_sound_wood_defaults(),
after_place_node = function(pos, placer)
local meta = minetest.get_meta(pos)
@@ -1277,6 +1259,7 @@ minetest.register_node("default:furnace", {
paramtype2 = "facedir",
groups = {cracky=2},
legacy_facedir_simple = true,
is_ground_content = false,
sounds = default.node_sound_stone_defaults(),
on_construct = function(pos)
local meta = minetest.get_meta(pos)
@@ -1310,6 +1293,7 @@ minetest.register_node("default:furnace_active", {
drop = "default:furnace",
groups = {cracky=2},
legacy_facedir_simple = true,
is_ground_content = false,
sounds = default.node_sound_stone_defaults(),
on_construct = function(pos)
local meta = minetest.get_meta(pos)
@@ -1334,18 +1318,13 @@ minetest.register_node("default:furnace_active", {
end,
})
function hacky_swap_node(pos,name)
function swap_node(pos,name)
local node = minetest.get_node(pos)
local meta = minetest.get_meta(pos)
local meta0 = meta:to_table()
if node.name == name then
return
end
node.name = name
local meta0 = meta:to_table()
minetest.set_node(pos,node)
meta = minetest.get_meta(pos)
meta:from_table(meta0)
minetest.swap_node(pos, node)
end
minetest.register_abm({
@@ -1400,7 +1379,7 @@ minetest.register_abm({
local percent = math.floor(meta:get_float("fuel_time") /
meta:get_float("fuel_totaltime") * 100)
meta:set_string("infotext","Furnace active: "..percent.."%")
hacky_swap_node(pos,"default:furnace_active")
swap_node(pos,"default:furnace_active")
meta:set_string("formspec",
"size[8,9]"..
"image[2,2;1,1;default_furnace_fire_bg.png^[lowpart:"..
@@ -1426,7 +1405,7 @@ minetest.register_abm({
if fuel.time <= 0 then
meta:set_string("infotext","Furnace out of fuel")
hacky_swap_node(pos,"default:furnace")
swap_node(pos,"default:furnace")
meta:set_string("formspec", default.furnace_inactive_formspec)
return
end
@@ -1434,7 +1413,7 @@ minetest.register_abm({
if cooked.item:is_empty() then
if was_active then
meta:set_string("infotext","Furnace is empty")
hacky_swap_node(pos,"default:furnace")
swap_node(pos,"default:furnace")
meta:set_string("formspec", default.furnace_inactive_formspec)
end
return
@@ -1452,7 +1431,6 @@ minetest.register_abm({
minetest.register_node("default:cobble", {
description = "Cobble",
tiles ={"default_cobble.png"},
is_ground_content = true,
groups = {cracky=3},
sounds = default.node_sound_stone_defaults(),
})
@@ -1460,7 +1438,6 @@ minetest.register_node("default:cobble", {
minetest.register_node("default:mossycobble", {
description = "Mossy Cobble",
tiles ={"default_mossycobble.png"},
is_ground_content = true,
groups = {cracky=3},
sounds = default.node_sound_stone_defaults(),
})
@@ -1468,7 +1445,6 @@ minetest.register_node("default:mossycobble", {
minetest.register_node("default:steelblock", {
description = "Steel Block",
tiles ={"default_steel_block.png"},
is_ground_content = true,
groups = {snappy=1,bendy=2},
sounds = default.node_sound_stone_defaults(),
})
@@ -1481,6 +1457,7 @@ minetest.register_node("default:nyancat", {
paramtype2 = "facedir",
groups = {cracky=2},
legacy_facedir_simple = true,
is_ground_content = false,
sounds = default.node_sound_defaults(),
})
@@ -1488,6 +1465,7 @@ minetest.register_node("default:nyancat_rainbow", {
description = "Nyancat Rainbow",
tiles ={"default_nc_rb.png"},
inventory_image = "default_nc_rb.png",
is_ground_content = false,
groups = {cracky=2},
sounds = default.node_sound_defaults(),
})

View File

@@ -22,15 +22,15 @@
# Client and server
#
# Network port (UDP)
#port =
# Name of player; on a server this is the main admin
#name =
#name =
#
# Client stuff
#
# Port to connect to (UDP)
#remote_port =
# Key mappings
# See http://irrlicht.sourceforge.net/docu/namespaceirr.html#a54da2a0e231901735e3da1b0edf72eb3
#keymap_forward = KEY_KEY_W
@@ -82,7 +82,7 @@
#vsync = false
#fov = 72
# Address to connect to (#blank = start local server)
#address =
#address =
# Enable random user input, for testing
#random_input = false
# Timeout for client to remove unused map data from memory
@@ -115,7 +115,7 @@
# disable for speed or for different looks.
#smooth_lighting = true
# Path to texture directory. All textures are first searched from here.
#texture_path =
#texture_path =
# Video back-end.
# Possible values: null, software, burningsvideo, direct3d8, direct3d9, opengl
#video_driver = opengl
@@ -170,13 +170,37 @@
#enable_shaders = true
# Set to true to enable textures bumpmapping. Requires shaders enabled.
#enable_bumpmapping = false
# Set to true enables parallax occlusion mapping. Requires shaders enabled.
#enable_parallax_occlusion = false
# Scale of parallax occlusion effect
#parallax_occlusion_scale = 0.08
# Bias of parallax occlusion effect, usually scale/2
#parallax_occlusion_bias = 0.04
# Set to true enables waving water. Requires shaders enabled.
#enable_waving_water = false
# Parameters for waving water:
#water_wave_height = 1.0
#water_wave_length = 20.0
#water_wave_speed = 5.0
# Set to true enables waving leaves. Requires shaders enabled.
#enable_waving_leaves = false
# Set to true enables waving plants. Requires shaders enabled.
#enable_waving_plants = false
# The time in seconds it takes between repeated
# right clicks when holding the right mouse button
#repeat_rightclick_time = 0.25
# Make fog and sky colors depend on daytime (dawn/sunset) and view direction
#directional_colored_fog = true
# will only work for servers which use remote_media setting
# and only for clients compiled with cURL
#media_fetch_threads = 8
# Default timeout for cURL, in milliseconds
# Only has an effect if compiled with cURL
#curl_timeout = 5000
# Limits number of parallel HTTP requests. Affects:
# - Media fetch if server uses remote_media setting
# - Serverlist download and server announcement
# - Downloads performed by main menu (e.g. mod manager)
# Only has an effect if compiled with cURL
#curl_parallel_limit = 8
# Url to the server list displayed in the Multiplayer Tab
#serverlist_url = servers.minetest.net
@@ -188,17 +212,24 @@
# Path to TrueTypeFont or bitmap
#font_path = fonts/liberationsans.ttf
#font_size = 13
# Font shadow offset, if 0 then shadow will not be drawn.
#font_shadow = 1
# Font shadow alpha (opaqueness, between 0 and 255)
#font_shadow_alpha = 128
#mono_font_path = fonts/liberationmono.ttf
#mono_font_size = 13
# This font will be used for certain languages
#fallback_font_path = fonts/DroidSansFallbackFull.ttf
#fallback_font_size = 13
#fallback_font_shadow = 1
#fallback_font_shadow_alpha = 128
#
# Server stuff
#
# Network port to listen (UDP)
#port =
# Name of server
#server_name = Minetest server
# Description of server
@@ -232,7 +263,7 @@
# Gives some stuff to players at the beginning
#give_initial_stuff = false
# New users need to input this password
#default_password =
#default_password =
# Available privileges: interact, shout, teleport, settime, privs, ...
# See /privs in game for a full list on your server and mod configuration.
#default_privs = interact, shout
@@ -248,6 +279,10 @@
#disable_anticheat = false
# If true, actions are recorded for rollback
#enable_rollback_recording = false
# If true, blocks are cached (and generated if not before) before a player is spawned.
#cache_block_before_spawn = true
# Defines the maximum height a player can spawn in a map, above water level
#max_spawn_height = 50
# Profiler data print interval. #0 = disable.
#profiler_print_interval = 0
@@ -308,7 +343,7 @@
#emergequeue_limit_diskonly =
# Maximum number of blocks to be queued that are to be generated.
# Leave blank for an appropriate amount to be chosen automatically.
#emergequeue_limit_generate =
#emergequeue_limit_generate =
# Number of emerge threads to use. Make this field blank, or increase this number, to use multiple threads.
# On multiprocessor systems, this will improve mapgen speed greatly, at the cost of slightly buggy caves.
#num_emerge_threads = 1

968
po/lt/minetest.po Normal file
View File

@@ -0,0 +1,968 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: minetest\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-11-23 17:37+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
#: builtin/gamemgr.lua:23
msgid "Game Name"
msgstr ""
#: builtin/gamemgr.lua:25 builtin/mainmenu.lua:310
msgid "Create"
msgstr ""
#: builtin/gamemgr.lua:26 builtin/mainmenu.lua:311 builtin/modmgr.lua:331
#: builtin/modmgr.lua:448 src/guiKeyChangeMenu.cpp:195 src/keycode.cpp:223
msgid "Cancel"
msgstr ""
#: builtin/gamemgr.lua:118
msgid "Gamemgr: Unable to copy mod \"$1\" to game \"$2\""
msgstr ""
#: builtin/gamemgr.lua:216
msgid "GAMES"
msgstr ""
#: builtin/gamemgr.lua:217 builtin/mainmenu.lua:1076
msgid "Games"
msgstr ""
#: builtin/gamemgr.lua:234
msgid "Mods:"
msgstr ""
#: builtin/gamemgr.lua:235
msgid "edit game"
msgstr ""
#: builtin/gamemgr.lua:238
msgid "new game"
msgstr ""
#: builtin/gamemgr.lua:248
msgid "EDIT GAME"
msgstr ""
#: builtin/gamemgr.lua:269
msgid "Remove selected mod"
msgstr ""
#: builtin/gamemgr.lua:272
msgid "<<-- Add mod"
msgstr ""
#: builtin/mainmenu.lua:158
msgid "Ok"
msgstr ""
#: builtin/mainmenu.lua:297
msgid "World name"
msgstr ""
#: builtin/mainmenu.lua:300
msgid "Seed"
msgstr ""
#: builtin/mainmenu.lua:303
msgid "Mapgen"
msgstr ""
#: builtin/mainmenu.lua:306
msgid "Game"
msgstr ""
#: builtin/mainmenu.lua:319
msgid "Delete World \"$1\"?"
msgstr ""
#: builtin/mainmenu.lua:320 builtin/modmgr.lua:877
msgid "Yes"
msgstr ""
#: builtin/mainmenu.lua:321
msgid "No"
msgstr ""
#: builtin/mainmenu.lua:364
msgid "A world named \"$1\" already exists"
msgstr ""
#: builtin/mainmenu.lua:381
msgid "No worldname given or no game selected"
msgstr ""
#: builtin/mainmenu.lua:650
msgid "To enable shaders the OpenGL driver needs to be used."
msgstr ""
#: builtin/mainmenu.lua:818
msgid "CLIENT"
msgstr ""
#: builtin/mainmenu.lua:819
msgid "Favorites:"
msgstr ""
#: builtin/mainmenu.lua:820
msgid "Address/Port"
msgstr ""
#: builtin/mainmenu.lua:821
msgid "Name/Password"
msgstr ""
#: builtin/mainmenu.lua:824
msgid "Public Serverlist"
msgstr ""
#: builtin/mainmenu.lua:829 builtin/mainmenu.lua:874 builtin/mainmenu.lua:937
#: src/keycode.cpp:229
msgid "Delete"
msgstr ""
#: builtin/mainmenu.lua:833
msgid "Connect"
msgstr ""
#: builtin/mainmenu.lua:875 builtin/mainmenu.lua:938
msgid "New"
msgstr ""
#: builtin/mainmenu.lua:876 builtin/mainmenu.lua:939
msgid "Configure"
msgstr ""
#: builtin/mainmenu.lua:877
msgid "Start Game"
msgstr ""
#: builtin/mainmenu.lua:878 builtin/mainmenu.lua:941
msgid "Select World:"
msgstr ""
#: builtin/mainmenu.lua:879
msgid "START SERVER"
msgstr ""
#: builtin/mainmenu.lua:880 builtin/mainmenu.lua:943
msgid "Creative Mode"
msgstr ""
#: builtin/mainmenu.lua:882 builtin/mainmenu.lua:945
msgid "Enable Damage"
msgstr ""
#: builtin/mainmenu.lua:884
msgid "Public"
msgstr ""
#: builtin/mainmenu.lua:886
msgid "Name"
msgstr ""
#: builtin/mainmenu.lua:888
msgid "Password"
msgstr ""
#: builtin/mainmenu.lua:889
msgid "Server Port"
msgstr ""
#: builtin/mainmenu.lua:899
msgid "SETTINGS"
msgstr ""
#: builtin/mainmenu.lua:900
msgid "Fancy trees"
msgstr ""
#: builtin/mainmenu.lua:902
msgid "Smooth Lighting"
msgstr ""
#: builtin/mainmenu.lua:904
msgid "3D Clouds"
msgstr ""
#: builtin/mainmenu.lua:906
msgid "Opaque Water"
msgstr ""
#: builtin/mainmenu.lua:909
msgid "Mip-Mapping"
msgstr ""
#: builtin/mainmenu.lua:911
msgid "Anisotropic Filtering"
msgstr ""
#: builtin/mainmenu.lua:913
msgid "Bi-Linear Filtering"
msgstr ""
#: builtin/mainmenu.lua:915
msgid "Tri-Linear Filtering"
msgstr ""
#: builtin/mainmenu.lua:918
msgid "Shaders"
msgstr ""
#: builtin/mainmenu.lua:920
msgid "Preload item visuals"
msgstr ""
#: builtin/mainmenu.lua:922
msgid "Enable Particles"
msgstr ""
#: builtin/mainmenu.lua:924
msgid "Finite Liquid"
msgstr ""
#: builtin/mainmenu.lua:927
msgid "Change keys"
msgstr ""
#: builtin/mainmenu.lua:940 src/keycode.cpp:248
msgid "Play"
msgstr ""
#: builtin/mainmenu.lua:942
msgid "SINGLE PLAYER"
msgstr ""
#: builtin/mainmenu.lua:955
msgid "Select texture pack:"
msgstr ""
#: builtin/mainmenu.lua:956
msgid "TEXTURE PACKS"
msgstr ""
#: builtin/mainmenu.lua:976
msgid "No information available"
msgstr ""
#: builtin/mainmenu.lua:1005
msgid "Core Developers"
msgstr ""
#: builtin/mainmenu.lua:1020
msgid "Active Contributors"
msgstr ""
#: builtin/mainmenu.lua:1028
msgid "Previous Contributors"
msgstr ""
#: builtin/mainmenu.lua:1069
msgid "Singleplayer"
msgstr ""
#: builtin/mainmenu.lua:1070
msgid "Client"
msgstr ""
#: builtin/mainmenu.lua:1071
msgid "Server"
msgstr ""
#: builtin/mainmenu.lua:1072
msgid "Settings"
msgstr ""
#: builtin/mainmenu.lua:1073
msgid "Texture Packs"
msgstr ""
#: builtin/mainmenu.lua:1080
msgid "Mods"
msgstr ""
#: builtin/mainmenu.lua:1082
msgid "Credits"
msgstr ""
#: builtin/modmgr.lua:236
msgid "MODS"
msgstr ""
#: builtin/modmgr.lua:237
msgid "Installed Mods:"
msgstr ""
#: builtin/modmgr.lua:243
msgid "Add mod:"
msgstr ""
#: builtin/modmgr.lua:244
msgid "Local install"
msgstr ""
#: builtin/modmgr.lua:245
msgid "Online mod repository"
msgstr ""
#: builtin/modmgr.lua:284
msgid "No mod description available"
msgstr ""
#: builtin/modmgr.lua:288
msgid "Mod information:"
msgstr ""
#: builtin/modmgr.lua:299
msgid "Rename"
msgstr ""
#: builtin/modmgr.lua:301
msgid "Uninstall selected modpack"
msgstr ""
#: builtin/modmgr.lua:312
msgid "Uninstall selected mod"
msgstr ""
#: builtin/modmgr.lua:324
msgid "Rename Modpack:"
msgstr ""
#: builtin/modmgr.lua:329 src/keycode.cpp:227
msgid "Accept"
msgstr ""
#: builtin/modmgr.lua:423
msgid "World:"
msgstr ""
#: builtin/modmgr.lua:427 builtin/modmgr.lua:429
msgid "Hide Game"
msgstr ""
#: builtin/modmgr.lua:433 builtin/modmgr.lua:435
msgid "Hide mp content"
msgstr ""
#: builtin/modmgr.lua:442
msgid "Mod:"
msgstr ""
#: builtin/modmgr.lua:444
msgid "Depends:"
msgstr ""
#: builtin/modmgr.lua:447 src/guiKeyChangeMenu.cpp:187
msgid "Save"
msgstr ""
#: builtin/modmgr.lua:464
msgid "Enable MP"
msgstr ""
#: builtin/modmgr.lua:466
msgid "Disable MP"
msgstr ""
#: builtin/modmgr.lua:470 builtin/modmgr.lua:472
msgid "enabled"
msgstr ""
#: builtin/modmgr.lua:478
msgid "Enable all"
msgstr ""
#: builtin/modmgr.lua:577
msgid "Select Mod File:"
msgstr ""
#: builtin/modmgr.lua:616
msgid "Install Mod: file: \"$1\""
msgstr ""
#: builtin/modmgr.lua:617
msgid ""
"\n"
"Install Mod: unsupported filetype \"$1\""
msgstr ""
#: builtin/modmgr.lua:638
msgid "Failed to install $1 to $2"
msgstr ""
#: builtin/modmgr.lua:641
msgid "Install Mod: unable to find suitable foldername for modpack $1"
msgstr ""
#: builtin/modmgr.lua:661
msgid "Install Mod: unable to find real modname for: $1"
msgstr ""
#: builtin/modmgr.lua:855
msgid "Modmgr: failed to delete \"$1\""
msgstr ""
#: builtin/modmgr.lua:859
msgid "Modmgr: invalid modpath \"$1\""
msgstr ""
#: builtin/modmgr.lua:876
msgid "Are you sure you want to delete \"$1\"?"
msgstr ""
#: builtin/modmgr.lua:878
msgid "No of course not!"
msgstr ""
#: builtin/modstore.lua:183
msgid "Page $1 of $2"
msgstr ""
#: builtin/modstore.lua:243
msgid "Rating"
msgstr ""
#: builtin/modstore.lua:251
msgid "re-Install"
msgstr ""
#: builtin/modstore.lua:253
msgid "Install"
msgstr ""
#: src/client.cpp:2917
msgid "Item textures..."
msgstr ""
#: src/game.cpp:940
msgid "Loading..."
msgstr ""
#: src/game.cpp:1000
msgid "Creating server...."
msgstr ""
#: src/game.cpp:1016
msgid "Creating client..."
msgstr ""
#: src/game.cpp:1025
msgid "Resolving address..."
msgstr ""
#: src/game.cpp:1122
msgid "Connecting to server..."
msgstr ""
#: src/game.cpp:1219
msgid "Item definitions..."
msgstr ""
#: src/game.cpp:1226
msgid "Node definitions..."
msgstr ""
#: src/game.cpp:1233
msgid "Media..."
msgstr ""
#: src/game.cpp:3409
msgid "Shutting down stuff..."
msgstr ""
#: src/game.cpp:3439
msgid ""
"\n"
"Check debug.txt for details."
msgstr ""
#: src/guiDeathScreen.cpp:96
msgid "You died."
msgstr ""
#: src/guiDeathScreen.cpp:104
msgid "Respawn"
msgstr ""
#: src/guiFormSpecMenu.cpp:1656 src/guiMessageMenu.cpp:107
#: src/guiTextInputMenu.cpp:139
msgid "Proceed"
msgstr ""
#: src/guiKeyChangeMenu.cpp:121
msgid "Keybindings. (If this menu screws up, remove stuff from minetest.conf)"
msgstr ""
#: src/guiKeyChangeMenu.cpp:161
msgid "\"Use\" = climb down"
msgstr ""
#: src/guiKeyChangeMenu.cpp:176
msgid "Double tap \"jump\" to toggle fly"
msgstr ""
#: src/guiKeyChangeMenu.cpp:288
msgid "Key already in use"
msgstr ""
#: src/guiKeyChangeMenu.cpp:363
msgid "press key"
msgstr ""
#: src/guiKeyChangeMenu.cpp:389
msgid "Forward"
msgstr ""
#: src/guiKeyChangeMenu.cpp:390
msgid "Backward"
msgstr ""
#: src/guiKeyChangeMenu.cpp:391 src/keycode.cpp:228
msgid "Left"
msgstr ""
#: src/guiKeyChangeMenu.cpp:392 src/keycode.cpp:228
msgid "Right"
msgstr ""
#: src/guiKeyChangeMenu.cpp:393
msgid "Use"
msgstr ""
#: src/guiKeyChangeMenu.cpp:394
msgid "Jump"
msgstr ""
#: src/guiKeyChangeMenu.cpp:395
msgid "Sneak"
msgstr ""
#: src/guiKeyChangeMenu.cpp:396
msgid "Drop"
msgstr ""
#: src/guiKeyChangeMenu.cpp:397
msgid "Inventory"
msgstr ""
#: src/guiKeyChangeMenu.cpp:398
msgid "Chat"
msgstr ""
#: src/guiKeyChangeMenu.cpp:399
msgid "Command"
msgstr ""
#: src/guiKeyChangeMenu.cpp:400
msgid "Console"
msgstr ""
#: src/guiKeyChangeMenu.cpp:401
msgid "Toggle fly"
msgstr ""
#: src/guiKeyChangeMenu.cpp:402
msgid "Toggle fast"
msgstr ""
#: src/guiKeyChangeMenu.cpp:403
msgid "Toggle noclip"
msgstr ""
#: src/guiKeyChangeMenu.cpp:404
msgid "Range select"
msgstr ""
#: src/guiKeyChangeMenu.cpp:405
msgid "Print stacks"
msgstr ""
#: src/guiPasswordChange.cpp:106
msgid "Old Password"
msgstr ""
#: src/guiPasswordChange.cpp:122
msgid "New Password"
msgstr ""
#: src/guiPasswordChange.cpp:137
msgid "Confirm Password"
msgstr ""
#: src/guiPasswordChange.cpp:153
msgid "Change"
msgstr ""
#: src/guiPasswordChange.cpp:162
msgid "Passwords do not match!"
msgstr ""
#: src/guiPauseMenu.cpp:122
msgid "Continue"
msgstr ""
#: src/guiPauseMenu.cpp:133
msgid "Change Password"
msgstr ""
#: src/guiPauseMenu.cpp:143
msgid "Sound Volume"
msgstr ""
#: src/guiPauseMenu.cpp:152
msgid "Exit to Menu"
msgstr ""
#: src/guiPauseMenu.cpp:161
msgid "Exit to OS"
msgstr ""
#: src/guiPauseMenu.cpp:170
msgid ""
"Default Controls:\n"
"- WASD: move\n"
"- Space: jump/climb\n"
"- Shift: sneak/go down\n"
"- Q: drop item\n"
"- I: inventory\n"
"- Mouse: turn/look\n"
"- Mouse left: dig/punch\n"
"- Mouse right: place/use\n"
"- Mouse wheel: select item\n"
"- T: chat\n"
msgstr ""
#: src/guiVolumeChange.cpp:107
msgid "Sound Volume: "
msgstr ""
#: src/guiVolumeChange.cpp:121
msgid "Exit"
msgstr ""
#: src/keycode.cpp:223
msgid "Left Button"
msgstr ""
#: src/keycode.cpp:223
msgid "Middle Button"
msgstr ""
#: src/keycode.cpp:223
msgid "Right Button"
msgstr ""
#: src/keycode.cpp:223
msgid "X Button 1"
msgstr ""
#: src/keycode.cpp:224
msgid "Back"
msgstr ""
#: src/keycode.cpp:224
msgid "Clear"
msgstr ""
#: src/keycode.cpp:224
msgid "Return"
msgstr ""
#: src/keycode.cpp:224
msgid "Tab"
msgstr ""
#: src/keycode.cpp:224
msgid "X Button 2"
msgstr ""
#: src/keycode.cpp:225
msgid "Capital"
msgstr ""
#: src/keycode.cpp:225
msgid "Control"
msgstr ""
#: src/keycode.cpp:225
msgid "Kana"
msgstr ""
#: src/keycode.cpp:225
msgid "Menu"
msgstr ""
#: src/keycode.cpp:225
msgid "Pause"
msgstr ""
#: src/keycode.cpp:225
msgid "Shift"
msgstr ""
#: src/keycode.cpp:226
msgid "Convert"
msgstr ""
#: src/keycode.cpp:226
msgid "Escape"
msgstr ""
#: src/keycode.cpp:226
msgid "Final"
msgstr ""
#: src/keycode.cpp:226
msgid "Junja"
msgstr ""
#: src/keycode.cpp:226
msgid "Kanji"
msgstr ""
#: src/keycode.cpp:226
msgid "Nonconvert"
msgstr ""
#: src/keycode.cpp:227
msgid "End"
msgstr ""
#: src/keycode.cpp:227
msgid "Home"
msgstr ""
#: src/keycode.cpp:227
msgid "Mode Change"
msgstr ""
#: src/keycode.cpp:227
msgid "Next"
msgstr ""
#: src/keycode.cpp:227
msgid "Prior"
msgstr ""
#: src/keycode.cpp:227
msgid "Space"
msgstr ""
#: src/keycode.cpp:228
msgid "Down"
msgstr ""
#: src/keycode.cpp:228
msgid "Execute"
msgstr ""
#: src/keycode.cpp:228
msgid "Print"
msgstr ""
#: src/keycode.cpp:228
msgid "Select"
msgstr ""
#: src/keycode.cpp:228
msgid "Up"
msgstr ""
#: src/keycode.cpp:229
msgid "Help"
msgstr ""
#: src/keycode.cpp:229
msgid "Insert"
msgstr ""
#: src/keycode.cpp:229
msgid "Snapshot"
msgstr ""
#: src/keycode.cpp:232
msgid "Left Windows"
msgstr ""
#: src/keycode.cpp:233
msgid "Apps"
msgstr ""
#: src/keycode.cpp:233
msgid "Numpad 0"
msgstr ""
#: src/keycode.cpp:233
msgid "Numpad 1"
msgstr ""
#: src/keycode.cpp:233
msgid "Right Windows"
msgstr ""
#: src/keycode.cpp:233
msgid "Sleep"
msgstr ""
#: src/keycode.cpp:234
msgid "Numpad 2"
msgstr ""
#: src/keycode.cpp:234
msgid "Numpad 3"
msgstr ""
#: src/keycode.cpp:234
msgid "Numpad 4"
msgstr ""
#: src/keycode.cpp:234
msgid "Numpad 5"
msgstr ""
#: src/keycode.cpp:234
msgid "Numpad 6"
msgstr ""
#: src/keycode.cpp:234
msgid "Numpad 7"
msgstr ""
#: src/keycode.cpp:235
msgid "Numpad *"
msgstr ""
#: src/keycode.cpp:235
msgid "Numpad +"
msgstr ""
#: src/keycode.cpp:235
msgid "Numpad -"
msgstr ""
#: src/keycode.cpp:235
msgid "Numpad /"
msgstr ""
#: src/keycode.cpp:235
msgid "Numpad 8"
msgstr ""
#: src/keycode.cpp:235
msgid "Numpad 9"
msgstr ""
#: src/keycode.cpp:239
msgid "Num Lock"
msgstr ""
#: src/keycode.cpp:239
msgid "Scroll Lock"
msgstr ""
#: src/keycode.cpp:240
msgid "Left Shift"
msgstr ""
#: src/keycode.cpp:240
msgid "Right Shift"
msgstr ""
#: src/keycode.cpp:241
msgid "Left Control"
msgstr ""
#: src/keycode.cpp:241
msgid "Left Menu"
msgstr ""
#: src/keycode.cpp:241
msgid "Right Control"
msgstr ""
#: src/keycode.cpp:241
msgid "Right Menu"
msgstr ""
#: src/keycode.cpp:243
msgid "Comma"
msgstr ""
#: src/keycode.cpp:243
msgid "Minus"
msgstr ""
#: src/keycode.cpp:243
msgid "Period"
msgstr ""
#: src/keycode.cpp:243
msgid "Plus"
msgstr ""
#: src/keycode.cpp:247
msgid "Attn"
msgstr ""
#: src/keycode.cpp:247
msgid "CrSel"
msgstr ""
#: src/keycode.cpp:248
msgid "Erase OEF"
msgstr ""
#: src/keycode.cpp:248
msgid "ExSel"
msgstr ""
#: src/keycode.cpp:248
msgid "OEM Clear"
msgstr ""
#: src/keycode.cpp:248
msgid "PA1"
msgstr ""
#: src/keycode.cpp:248
msgid "Zoom"
msgstr ""
#: src/main.cpp:1472
msgid "needs_fallback_font"
msgstr ""
#: src/main.cpp:1547
msgid "Main Menu"
msgstr ""
#: src/main.cpp:1723
msgid "No world selected and no address provided. Nothing to do."
msgstr ""
#: src/main.cpp:1731
msgid "Could not find or load game \""
msgstr ""
#: src/main.cpp:1745
msgid "Invalid gamespec."
msgstr ""
#: src/main.cpp:1790
msgid "Connection error (timed out?)"
msgstr ""

View File

@@ -188,7 +188,7 @@ message (STATUS "LuaJIT library: ${LUA_LIBRARY}")
message (STATUS "LuaJIT headers: ${LUA_INCLUDE_DIR}")
set(USE_LUAJIT 0)
if(LUA_LIBRARY AND LUA_INCLUDE_DIR)
if(LUA_LIBRARY AND LUA_INCLUDE_DIR)
message (STATUS "LuaJIT found.")
set(USE_LUAJIT 1)
else(LUA_LIBRARY AND LUA_INCLUDE_DIR)
@@ -307,6 +307,7 @@ set(common_SRCS
pathfinder.cpp
convert_json.cpp
gettext.cpp
httpfetch.cpp
${JTHREAD_SRCS}
${common_SCRIPT_SRCS}
${UTIL_SRCS}
@@ -359,6 +360,7 @@ set(minetest_SRCS
guiDeathScreen.cpp
guiChatConsole.cpp
client.cpp
clientmedia.cpp
filecache.cpp
tile.cpp
shader.cpp
@@ -500,7 +502,7 @@ if(MSVC)
# Flags for C files (sqlite)
# /MT = Link statically with standard library stuff
set(CMAKE_C_FLAGS_RELEASE "/O2 /Ob2 /MT")
if(BUILD_SERVER)
set_target_properties(${PROJECT_NAME}server PROPERTIES
COMPILE_DEFINITIONS "SERVER")
@@ -508,13 +510,13 @@ if(MSVC)
else()
# Probably GCC
if(WARN_ALL)
set(RELEASE_WARNING_FLAGS "-Wall")
else()
set(RELEASE_WARNING_FLAGS "")
endif()
if(NOT APPLE AND NOT "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
CHECK_CXX_COMPILER_FLAG("-Wno-unused-but-set-variable" HAS_UNUSED_BUT_SET_VARIABLE_WARNING)
if(HAS_UNUSED_BUT_SET_VARIABLE_WARNING)
@@ -537,7 +539,7 @@ else()
if(USE_GPROF)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -pg")
endif()
if(BUILD_SERVER)
set_target_properties(${PROJECT_NAME}server PROPERTIES
COMPILE_DEFINITIONS "SERVER")

View File

@@ -31,7 +31,6 @@ BanManager::BanManager(const std::string &banfilepath):
m_banfilepath(banfilepath),
m_modified(false)
{
m_mutex.Init();
try{
load();
}

View File

@@ -110,9 +110,21 @@ void CaveV6::makeCave(v3s16 nmin, v3s16 nmax, int max_stone_height) {
(float)(ps->next() % ar.Z) + 0.5
);
int notifytype = large_cave ? GENNOTIFY_LARGECAVE_BEGIN : GENNOTIFY_CAVE_BEGIN;
if (mg->gennotify & (1 << notifytype)) {
std::vector <v3s16> *nvec = mg->gen_notifications[notifytype];
nvec->push_back(v3s16(of.X + orp.X, of.Y + orp.Y, of.Z + orp.Z));
}
// Generate some tunnel starting from orp
for (u16 j = 0; j < tunnel_routepoints; j++)
makeTunnel(j % dswitchint == 0);
notifytype = large_cave ? GENNOTIFY_LARGECAVE_END : GENNOTIFY_CAVE_END;
if (mg->gennotify & (1 << notifytype)) {
std::vector <v3s16> *nvec = mg->gen_notifications[notifytype];
nvec->push_back(v3s16(of.X + orp.X, of.Y + orp.Y, of.Z + orp.Z));
}
}
@@ -236,6 +248,9 @@ void CaveV6::carveRoute(v3f vec, float f, bool randomize_xz) {
continue;
u32 i = vm->m_area.index(p);
content_t c = vm->m_data[i].getContent();
if (!ndef->get(c).is_ground_content)
continue;
if (large_cave) {
int full_ymin = node_min.Y - MAP_BLOCKSIZE;
@@ -250,7 +265,6 @@ void CaveV6::carveRoute(v3f vec, float f, bool randomize_xz) {
}
} else {
// Don't replace air or water or lava or ignore
content_t c = vm->m_data[i].getContent();
if (c == CONTENT_IGNORE || c == CONTENT_AIR ||
c == c_water_source || c == c_lava_source)
continue;
@@ -345,9 +359,21 @@ void CaveV7::makeCave(v3s16 nmin, v3s16 nmax, int max_stone_height) {
(float)(ps->next() % ar.Z) + 0.5
);
int notifytype = large_cave ? GENNOTIFY_LARGECAVE_BEGIN : GENNOTIFY_CAVE_BEGIN;
if (mg->gennotify & (1 << notifytype)) {
std::vector <v3s16> *nvec = mg->gen_notifications[notifytype];
nvec->push_back(v3s16(of.X + orp.X, of.Y + orp.Y, of.Z + orp.Z));
}
// Generate some tunnel starting from orp
for (u16 j = 0; j < tunnel_routepoints; j++)
makeTunnel(j % dswitchint == 0);
notifytype = large_cave ? GENNOTIFY_LARGECAVE_END : GENNOTIFY_CAVE_END;
if (mg->gennotify & (1 << notifytype)) {
std::vector <v3s16> *nvec = mg->gen_notifications[notifytype];
nvec->push_back(v3s16(of.X + orp.X, of.Y + orp.Y, of.Z + orp.Z));
}
}
@@ -516,7 +542,8 @@ void CaveV7::carveRoute(v3f vec, float f, bool randomize_xz, bool is_ravine) {
v3s16 p(cp.X + x0, cp.Y + y0, cp.Z + z0);
p += of;
if (!is_ravine && mg->heightmap && should_make_cave_hole) {
if (!is_ravine && mg->heightmap && should_make_cave_hole &&
p.X <= node_max.X && p.Z <= node_max.Z) {
int maplen = node_max.X - node_min.X + 1;
int idx = (p.Z - node_min.Z) * maplen + (p.X - node_min.X);
if (p.Y >= mg->heightmap[idx] - 2)
@@ -530,8 +557,8 @@ void CaveV7::carveRoute(v3f vec, float f, bool randomize_xz, bool is_ravine) {
// Don't replace air, water, lava, or ice
content_t c = vm->m_data[i].getContent();
if (c == CONTENT_AIR || c == c_water_source ||
c == c_lava_source || c == c_ice)
if (!ndef->get(c).is_ground_content || c == CONTENT_AIR ||
c == c_water_source || c == c_lava_source || c == c_ice)
continue;
if (large_cave) {

View File

@@ -199,7 +199,7 @@ void SGUITTGlyph::unload()
//////////////////////
CGUITTFont* CGUITTFont::createTTFont(IGUIEnvironment *env, const io::path& filename, const u32 size, const bool antialias, const bool transparency)
CGUITTFont* CGUITTFont::createTTFont(IGUIEnvironment *env, const io::path& filename, const u32 size, const bool antialias, const bool transparency, const u32 shadow, const u32 shadow_alpha)
{
if (!c_libraryLoaded)
{
@@ -216,6 +216,9 @@ CGUITTFont* CGUITTFont::createTTFont(IGUIEnvironment *env, const io::path& filen
return 0;
}
font->shadow_offset = shadow;
font->shadow_alpha = shadow_alpha;
return font;
}
@@ -625,6 +628,14 @@ void CGUITTFont::draw(const core::stringw& text, const core::rect<s32>& position
CGUITTGlyphPage* page = n->getValue();
if (!use_transparency) color.color |= 0xff000000;
if (shadow_offset) {
for (size_t i = 0; i < page->render_positions.size(); ++i)
page->render_positions[i] += core::vector2di(shadow_offset, shadow_offset);
Driver->draw2DImageBatch(page->texture, page->render_positions, page->render_source_rects, clip, video::SColor(shadow_alpha,0,0,0), true);
for (size_t i = 0; i < page->render_positions.size(); ++i)
page->render_positions[i] -= core::vector2di(shadow_offset, shadow_offset);
}
Driver->draw2DImageBatch(page->texture, page->render_positions, page->render_source_rects, clip, color, true);
}
}

View File

@@ -207,7 +207,7 @@ namespace gui
//! \param antialias set the use_monochrome (opposite to antialias) flag
//! \param transparency set the use_transparency flag
//! \return Returns a pointer to a CGUITTFont. Will return 0 if the font failed to load.
static CGUITTFont* createTTFont(IGUIEnvironment *env, const io::path& filename, const u32 size, const bool antialias = true, const bool transparency = true);
static CGUITTFont* createTTFont(IGUIEnvironment *env, const io::path& filename, const u32 size, const bool antialias = true, const bool transparency = true, const u32 shadow = 0, const u32 shadow_alpha = 255);
static CGUITTFont* createTTFont(IrrlichtDevice *device, const io::path& filename, const u32 size, const bool antialias = true, const bool transparency = true);
static CGUITTFont* create(IGUIEnvironment *env, const io::path& filename, const u32 size, const bool antialias = true, const bool transparency = true);
static CGUITTFont* create(IrrlichtDevice *device, const io::path& filename, const u32 size, const bool antialias = true, const bool transparency = true);
@@ -369,6 +369,8 @@ namespace gui
s32 GlobalKerningWidth;
s32 GlobalKerningHeight;
core::ustring Invisible;
u32 shadow_offset;
u32 shadow_alpha;
};
} // end namespace gui

View File

@@ -19,6 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "client.h"
#include <iostream>
#include <algorithm>
#include "clientserver.h"
#include "jthread/jmutexautolock.h"
#include "main.h"
@@ -37,28 +38,19 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "itemdef.h"
#include "shader.h"
#include <IFileSystem.h>
#include "sha1.h"
#include "base64.h"
#include "clientmap.h"
#include "filecache.h"
#include "clientmedia.h"
#include "sound.h"
#include "util/string.h"
#include "hex.h"
#include "IMeshCache.h"
#include "serialization.h"
#include "util/serialize.h"
#include "config.h"
#include "util/directiontables.h"
#include "util/pointedthing.h"
#include "version.h"
#if USE_CURL
#include <curl/curl.h>
#endif
static std::string getMediaCacheDir()
{
return porting::path_user + DIR_DELIM + "cache" + DIR_DELIM + "media";
}
/*
QueuedMeshUpdate
*/
@@ -82,7 +74,6 @@ QueuedMeshUpdate::~QueuedMeshUpdate()
MeshUpdateQueue::MeshUpdateQueue()
{
m_mutex.Init();
}
MeshUpdateQueue::~MeshUpdateQueue()
@@ -177,7 +168,7 @@ void * MeshUpdateThread::Thread()
BEGIN_DEBUG_EXCEPTION_HANDLER
while(getRun())
while(!StopRequested())
{
/*// Wait for output queue to flush.
// Allow 2 in queue, this makes less frametime jitter.
@@ -223,46 +214,9 @@ void * MeshUpdateThread::Thread()
return NULL;
}
void * MediaFetchThread::Thread()
{
ThreadStarted();
log_register_thread("MediaFetchThread");
DSTACK(__FUNCTION_NAME);
BEGIN_DEBUG_EXCEPTION_HANDLER
#if USE_CURL
CURL *curl;
CURLcode res;
for (std::list<MediaRequest>::iterator i = m_file_requests.begin();
i != m_file_requests.end(); ++i) {
curl = curl_easy_init();
assert(curl);
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
curl_easy_setopt(curl, CURLOPT_URL, (m_remote_url + i->name).c_str());
curl_easy_setopt(curl, CURLOPT_FAILONERROR, true);
std::ostringstream stream;
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_data);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &stream);
curl_easy_setopt(curl, CURLOPT_USERAGENT, (std::string("Minetest ")+minetest_version_hash).c_str());
res = curl_easy_perform(curl);
if (res == CURLE_OK) {
std::string data = stream.str();
m_file_data.push_back(make_pair(i->name, data));
} else {
m_failed.push_back(*i);
infostream << "cURL request failed for " << i->name << " (" << curl_easy_strerror(res) << ")"<< std::endl;
}
curl_easy_cleanup(curl);
}
#endif
END_DEBUG_EXCEPTION_HANDLER(errorstream)
return NULL;
}
/*
Client
*/
Client::Client(
IrrlichtDevice *device,
@@ -304,12 +258,9 @@ Client::Client(
m_map_seed(0),
m_password(password),
m_access_denied(false),
m_media_cache(getMediaCacheDir()),
m_media_receive_started(false),
m_media_count(0),
m_media_received_count(0),
m_itemdef_received(false),
m_nodedef_received(false),
m_media_downloader(new ClientMediaDownloader()),
m_time_of_day_set(false),
m_last_time_of_day_f(-1),
m_time_of_day_update_timer(0),
@@ -333,9 +284,6 @@ Client::Client(
m_env.addPlayer(player);
}
for (size_t i = 0; i < g_settings->getU16("media_fetch_threads"); ++i)
m_media_fetch_threads.push_back(new MediaFetchThread(this));
}
Client::~Client()
@@ -345,9 +293,8 @@ Client::~Client()
m_con.Disconnect();
}
m_mesh_update_thread.setRun(false);
while(m_mesh_update_thread.IsRunning())
sleep_ms(100);
m_mesh_update_thread.Stop();
m_mesh_update_thread.Wait();
while(!m_mesh_update_thread.m_queue_out.empty()) {
MeshUpdateResult r = m_mesh_update_thread.m_queue_out.pop_front();
delete r.mesh;
@@ -365,10 +312,6 @@ Client::~Client()
}
}
for (std::list<MediaFetchThread*>::iterator i = m_media_fetch_threads.begin();
i != m_media_fetch_threads.end(); ++i)
delete *i;
// cleanup 3d model meshes on client shutdown
while (m_device->getSceneManager()->getMeshCache()->getMeshCount() != 0) {
scene::IAnimatedMesh * mesh =
@@ -474,7 +417,7 @@ void Client::step(float dtime)
core::list<v3s16> deleted_blocks;
float delete_unused_sectors_timeout =
float delete_unused_sectors_timeout =
g_settings->getFloat("client_delete_unused_sectors_timeout");
// Delete sector blocks
@@ -798,57 +741,11 @@ void Client::step(float dtime)
/*
Load fetched media
*/
if (m_media_receive_started) {
bool all_stopped = true;
for (std::list<MediaFetchThread*>::iterator thread = m_media_fetch_threads.begin();
thread != m_media_fetch_threads.end(); ++thread) {
all_stopped &= !(*thread)->IsRunning();
while (!(*thread)->m_file_data.empty()) {
std::pair <std::string, std::string> out = (*thread)->m_file_data.pop_front();
if(m_media_received_count < m_media_count)
m_media_received_count++;
bool success = loadMedia(out.second, out.first);
if(success){
verbosestream<<"Client: Loaded received media: "
<<"\""<<out.first<<"\". Caching."<<std::endl;
} else{
infostream<<"Client: Failed to load received media: "
<<"\""<<out.first<<"\". Not caching."<<std::endl;
continue;
}
bool did = fs::CreateAllDirs(getMediaCacheDir());
if(!did){
errorstream<<"Could not create media cache directory"
<<std::endl;
}
{
std::map<std::string, std::string>::iterator n;
n = m_media_name_sha1_map.find(out.first);
if(n == m_media_name_sha1_map.end())
errorstream<<"The server sent a file that has not "
<<"been announced."<<std::endl;
else
m_media_cache.update_sha1(out.second);
}
}
}
if (all_stopped) {
std::list<MediaRequest> fetch_failed;
for (std::list<MediaFetchThread*>::iterator thread = m_media_fetch_threads.begin();
thread != m_media_fetch_threads.end(); ++thread) {
for (std::list<MediaRequest>::iterator request = (*thread)->m_failed.begin();
request != (*thread)->m_failed.end(); ++request)
fetch_failed.push_back(*request);
(*thread)->m_failed.clear();
}
if (fetch_failed.size() > 0) {
infostream << "Failed to remote-fetch " << fetch_failed.size() << " files. "
<< "Requesting them the usual way." << std::endl;
request_media(fetch_failed);
}
if (m_media_downloader && m_media_downloader->isStarted()) {
m_media_downloader->step(this);
if (m_media_downloader->isDone()) {
delete m_media_downloader;
m_media_downloader = NULL;
}
}
@@ -1049,15 +946,15 @@ void Client::deletingPeer(con::Peer *peer, bool timeout)
string name
}
*/
void Client::request_media(const std::list<MediaRequest> &file_requests)
void Client::request_media(const std::list<std::string> &file_requests)
{
std::ostringstream os(std::ios_base::binary);
writeU16(os, TOSERVER_REQUEST_MEDIA);
writeU16(os, file_requests.size());
for(std::list<MediaRequest>::const_iterator i = file_requests.begin();
for(std::list<std::string>::const_iterator i = file_requests.begin();
i != file_requests.end(); ++i) {
os<<serializeString(i->name);
os<<serializeString(*i);
}
// Make data buffer
@@ -1069,6 +966,19 @@ void Client::request_media(const std::list<MediaRequest> &file_requests)
<<file_requests.size()<<" files)"<<std::endl;
}
void Client::received_media()
{
// notify server we received everything
std::ostringstream os(std::ios_base::binary);
writeU16(os, TOSERVER_RECEIVED_MEDIA);
std::string s = os.str();
SharedBuffer<u8> data((u8*)s.c_str(), s.size());
// Send as reliable
Send(0, data, true);
infostream<<"Client: Notifying server that we received all media"
<<std::endl;
}
void Client::ReceiveAll()
{
DSTACK(__FUNCTION_NAME);
@@ -1262,7 +1172,13 @@ void Client::ProcessData(u8 *data, u32 datasize, u16 sender_peer_id)
MapNode n;
n.deSerialize(&data[8], ser_version);
addNode(p, n);
bool remove_metadata = true;
u32 index = 8 + MapNode::serializedLength(ser_version);
if ((datasize >= index+1) && data[index]){
remove_metadata = false;
}
addNode(p, n, remove_metadata);
}
else if(command == TOCLIENT_BLOCKDATA)
{
@@ -1655,96 +1571,54 @@ void Client::ProcessData(u8 *data, u32 datasize, u16 sender_peer_id)
std::string datastring((char*)&data[2], datasize-2);
std::istringstream is(datastring, std::ios_base::binary);
// Mesh update thread must be stopped while
// updating content definitions
assert(!m_mesh_update_thread.IsRunning());
int num_files = readU16(is);
infostream<<"Client: Received media announcement: packet size: "
<<datasize<<std::endl;
std::list<MediaRequest> file_requests;
if (m_media_downloader == NULL ||
m_media_downloader->isStarted()) {
const char *problem = m_media_downloader ?
"we already saw another announcement" :
"all media has been received already";
errorstream<<"Client: Received media announcement but "
<<problem<<"! "
<<" files="<<num_files
<<" size="<<datasize<<std::endl;
return;
}
// Mesh update thread must be stopped while
// updating content definitions
assert(!m_mesh_update_thread.IsRunning());
for(int i=0; i<num_files; i++)
{
//read file from cache
std::string name = deSerializeString(is);
std::string sha1_base64 = deSerializeString(is);
// if name contains illegal characters, ignore the file
if(!string_allowed(name, TEXTURENAME_ALLOWED_CHARS)){
errorstream<<"Client: ignoring illegal file name "
<<"sent by server: \""<<name<<"\""<<std::endl;
continue;
}
std::string sha1_raw = base64_decode(sha1_base64);
std::string sha1_hex = hex_encode(sha1_raw);
std::ostringstream tmp_os(std::ios_base::binary);
bool found_in_cache = m_media_cache.load_sha1(sha1_raw, tmp_os);
m_media_name_sha1_map[name] = sha1_raw;
// If found in cache, try to load it from there
if(found_in_cache)
{
bool success = loadMedia(tmp_os.str(), name);
if(success){
verbosestream<<"Client: Loaded cached media: "
<<sha1_hex<<" \""<<name<<"\""<<std::endl;
continue;
} else{
infostream<<"Client: Failed to load cached media: "
<<sha1_hex<<" \""<<name<<"\""<<std::endl;
}
}
// Didn't load from cache; queue it to be requested
verbosestream<<"Client: Adding file to request list: \""
<<sha1_hex<<" \""<<name<<"\""<<std::endl;
file_requests.push_back(MediaRequest(name));
m_media_downloader->addFile(name, sha1_raw);
}
std::string remote_media = "";
std::vector<std::string> remote_media;
try {
remote_media = deSerializeString(is);
Strfnd sf(deSerializeString(is));
while(!sf.atend()) {
std::string baseurl = trim(sf.next(","));
if(baseurl != "")
m_media_downloader->addRemoteServer(baseurl);
}
}
catch(SerializationError) {
// not supported by server or turned off
}
m_media_count = file_requests.size();
m_media_receive_started = true;
if (remote_media == "" || !USE_CURL) {
request_media(file_requests);
} else {
#if USE_CURL
std::list<MediaFetchThread*>::iterator cur = m_media_fetch_threads.begin();
for(std::list<MediaRequest>::iterator i = file_requests.begin();
i != file_requests.end(); ++i) {
(*cur)->m_file_requests.push_back(*i);
cur++;
if (cur == m_media_fetch_threads.end())
cur = m_media_fetch_threads.begin();
}
for (std::list<MediaFetchThread*>::iterator i = m_media_fetch_threads.begin();
i != m_media_fetch_threads.end(); ++i) {
(*i)->m_remote_url = remote_media;
(*i)->Start();
}
#endif
// notify server we received everything
std::ostringstream os(std::ios_base::binary);
writeU16(os, TOSERVER_RECEIVED_MEDIA);
std::string s = os.str();
SharedBuffer<u8> data((u8*)s.c_str(), s.size());
// Send as reliable
Send(0, data, true);
m_media_downloader->step(this);
if (m_media_downloader->isDone()) {
// might be done already if all media is in the cache
delete m_media_downloader;
m_media_downloader = NULL;
}
ClientEvent event;
event.type = CE_TEXTURES_UPDATED;
m_client_event_queue.push_back(event);
}
else if(command == TOCLIENT_MEDIA)
{
@@ -1770,67 +1644,37 @@ void Client::ProcessData(u8 *data, u32 datasize, u16 sender_peer_id)
<<num_bunches<<" files="<<num_files
<<" size="<<datasize<<std::endl;
// Check total and received media count
assert(m_media_received_count <= m_media_count);
if (num_files > m_media_count - m_media_received_count) {
errorstream<<"Client: Received more files than requested:"
<<" total count="<<m_media_count
<<" total received="<<m_media_received_count
if (num_files == 0)
return;
if (m_media_downloader == NULL ||
!m_media_downloader->isStarted()) {
const char *problem = m_media_downloader ?
"media has not been requested" :
"all media has been received already";
errorstream<<"Client: Received media but "
<<problem<<"! "
<<" bunch "<<bunch_i<<"/"<<num_bunches
<<" files="<<num_files
<<" size="<<datasize<<std::endl;
num_files = m_media_count - m_media_received_count;
}
if (num_files == 0)
return;
}
// Mesh update thread must be stopped while
// updating content definitions
assert(!m_mesh_update_thread.IsRunning());
for(u32 i=0; i<num_files; i++){
assert(m_media_received_count < m_media_count);
m_media_received_count++;
std::string name = deSerializeString(is);
std::string data = deSerializeLongString(is);
// if name contains illegal characters, ignore the file
if(!string_allowed(name, TEXTURENAME_ALLOWED_CHARS)){
errorstream<<"Client: ignoring illegal file name "
<<"sent by server: \""<<name<<"\""<<std::endl;
continue;
}
bool success = loadMedia(data, name);
if(success){
verbosestream<<"Client: Loaded received media: "
<<"\""<<name<<"\". Caching."<<std::endl;
} else{
infostream<<"Client: Failed to load received media: "
<<"\""<<name<<"\". Not caching."<<std::endl;
continue;
}
bool did = fs::CreateAllDirs(getMediaCacheDir());
if(!did){
errorstream<<"Could not create media cache directory"
<<std::endl;
}
{
std::map<std::string, std::string>::iterator n;
n = m_media_name_sha1_map.find(name);
if(n == m_media_name_sha1_map.end())
errorstream<<"The server sent a file that has not "
<<"been announced."<<std::endl;
else
m_media_cache.update_sha1(data);
}
m_media_downloader->conventionalTransferDone(
name, data, this);
}
ClientEvent event;
event.type = CE_TEXTURES_UPDATED;
m_client_event_queue.push_back(event);
if (m_media_downloader->isDone()) {
delete m_media_downloader;
m_media_downloader = NULL;
}
}
else if(command == TOCLIENT_TOOLDEF)
{
@@ -2120,7 +1964,7 @@ void Client::ProcessData(u8 *data, u32 datasize, u16 sender_peer_id)
m_client_event_queue.push_back(event);
}
else if(command == TOCLIENT_HUDCHANGE)
{
{
std::string sdata;
v2f v2fdata;
u32 intdata = 0;
@@ -2149,7 +1993,7 @@ void Client::ProcessData(u8 *data, u32 datasize, u16 sender_peer_id)
m_client_event_queue.push_back(event);
}
else if(command == TOCLIENT_HUD_SET_FLAGS)
{
{
std::string datastring((char *)&data[2], datasize - 2);
std::istringstream is(datastring, std::ios_base::binary);
@@ -2258,7 +2102,7 @@ void Client::sendNodemetaFields(v3s16 p, const std::string &formname,
Send(0, data, true);
}
void Client::sendInventoryFields(const std::string &formname,
void Client::sendInventoryFields(const std::string &formname,
const std::map<std::string, std::string> &fields)
{
std::ostringstream os(std::ios_base::binary);
@@ -2462,7 +2306,7 @@ void Client::sendPlayerPos()
writeV3S32(&data[2], position);
writeV3S32(&data[2+12], speed);
writeS32(&data[2+12+12], pitch);
writeS32(&data[2+12+12+4], yaw);
writeS32(&data[2+12+12+4], yaw);
writeU32(&data[2+12+12+4+4], keyPressed);
// Send as unreliable
Send(0, data, false);
@@ -2514,7 +2358,7 @@ void Client::removeNode(v3s16 p)
}
}
void Client::addNode(v3s16 p, MapNode n)
void Client::addNode(v3s16 p, MapNode n, bool remove_metadata)
{
TimeTaker timer1("Client::addNode()");
@@ -2523,7 +2367,7 @@ void Client::addNode(v3s16 p, MapNode n)
try
{
//TimeTaker timer3("Client::addNode(): addNodeAndUpdate");
m_env.getMap().addNodeAndUpdate(p, n, modified_blocks);
m_env.getMap().addNodeAndUpdate(p, n, modified_blocks, remove_metadata);
}
catch(InvalidPositionException &e)
{}
@@ -2880,6 +2724,14 @@ ClientEvent Client::getClientEvent()
return m_client_event_queue.pop_front();
}
float Client::mediaReceiveProgress()
{
if (m_media_downloader)
return m_media_downloader->getProgress();
else
return 1.0; // downloader only exists when not yet done
}
void draw_load_screen(const std::wstring &text,
IrrlichtDevice* device, gui::IGUIFont* font,
float dtime=0 ,int percent=0, bool clouds=true);
@@ -2888,12 +2740,8 @@ void Client::afterContentReceived(IrrlichtDevice *device, gui::IGUIFont* font)
infostream<<"Client::afterContentReceived() started"<<std::endl;
assert(m_itemdef_received);
assert(m_nodedef_received);
assert(texturesReceived());
assert(mediaReceived());
// remove the information about which checksum each texture
// ought to have
m_media_name_sha1_map.clear();
// Rebuild inherited images and recreate textures
infostream<<"- Rebuilding images and textures"<<std::endl;
m_tsrc->rebuildImagesAndTextures();

View File

@@ -31,32 +31,21 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "clientobject.h"
#include "gamedef.h"
#include "inventorymanager.h"
#include "filecache.h"
#include "localplayer.h"
#include "server.h"
#include "hud.h"
#include "particles.h"
#include "util/pointedthing.h"
#include <algorithm>
struct MeshMakeData;
class MapBlockMesh;
class IGameDef;
class IWritableTextureSource;
class IWritableShaderSource;
class IWritableItemDefManager;
class IWritableNodeDefManager;
//class IWritableCraftDefManager;
class ClientEnvironment;
class ClientMediaDownloader;
struct MapDrawControl;
class MtEventManager;
class ClientNotReadyException : public BaseException
{
public:
ClientNotReadyException(const char *s):
BaseException(s)
{}
};
struct PointedThing;
struct QueuedMeshUpdate
{
@@ -114,7 +103,7 @@ struct MeshUpdateResult
}
};
class MeshUpdateThread : public SimpleThread
class MeshUpdateThread : public JThread
{
public:
@@ -132,31 +121,12 @@ class MeshUpdateThread : public SimpleThread
IGameDef *m_gamedef;
};
class MediaFetchThread : public SimpleThread
{
public:
MediaFetchThread(IGameDef *gamedef):
m_gamedef(gamedef)
{
}
void * Thread();
std::list<MediaRequest> m_file_requests;
MutexedQueue<std::pair<std::string, std::string> > m_file_data;
std::list<MediaRequest> m_failed;
std::string m_remote_url;
IGameDef *m_gamedef;
};
enum ClientEventType
{
CE_NONE,
CE_PLAYER_DAMAGE,
CE_PLAYER_FORCE_MOVE,
CE_DEATHSCREEN,
CE_TEXTURES_UPDATED,
CE_SHOW_FORMSPEC,
CE_SPAWN_PARTICLE,
CE_ADD_PARTICLESPAWNER,
@@ -365,7 +335,7 @@ class Client : public con::PeerHandler, public InventoryManager, public IGameDef
// Causes urgent mesh updates (unlike Map::add/removeNodeWithEvent)
void removeNode(v3s16 p);
void addNode(v3s16 p, MapNode n);
void addNode(v3s16 p, MapNode n, bool remove_metadata = true);
void setPlayerControl(PlayerControl &control);
@@ -426,19 +396,15 @@ class Client : public con::PeerHandler, public InventoryManager, public IGameDef
std::wstring accessDeniedReason()
{ return m_access_denied_reason; }
float mediaReceiveProgress()
{
if (!m_media_receive_started) return 0;
return 1.0 * m_media_received_count / m_media_count;
}
bool texturesReceived()
{ return m_media_receive_started && m_media_received_count == m_media_count; }
bool itemdefReceived()
{ return m_itemdef_received; }
bool nodedefReceived()
{ return m_nodedef_received; }
bool mediaReceived()
{ return m_media_downloader == NULL; }
float mediaReceiveProgress();
void afterContentReceived(IrrlichtDevice *device, gui::IGUIFont* font);
float getRTT(void);
@@ -455,12 +421,15 @@ class Client : public con::PeerHandler, public InventoryManager, public IGameDef
virtual bool checkLocalPrivilege(const std::string &priv)
{ return checkPrivilege(priv); }
private:
// The following set of functions is used by ClientMediaDownloader
// Insert a media file appropriately into the appropriate manager
bool loadMedia(const std::string &data, const std::string &filename);
// Send a request for conventional media transfer
void request_media(const std::list<std::string> &file_requests);
// Send a notification that no conventional media transfer is needed
void received_media();
void request_media(const std::list<MediaRequest> &file_requests);
private:
// Virtual methods from con::PeerHandler
void peerAdded(con::Peer *peer);
@@ -488,7 +457,6 @@ class Client : public con::PeerHandler, public InventoryManager, public IGameDef
MtEventManager *m_event;
MeshUpdateThread m_mesh_update_thread;
std::list<MediaFetchThread*> m_media_fetch_threads;
ClientEnvironment m_env;
con::Connection m_con;
IrrlichtDevice *m_device;
@@ -514,14 +482,9 @@ class Client : public con::PeerHandler, public InventoryManager, public IGameDef
bool m_access_denied;
std::wstring m_access_denied_reason;
Queue<ClientEvent> m_client_event_queue;
FileCache m_media_cache;
// Mapping from media file name to SHA1 checksum
std::map<std::string, std::string> m_media_name_sha1_map;
bool m_media_receive_started;
u32 m_media_count;
u32 m_media_received_count;
bool m_itemdef_received;
bool m_nodedef_received;
ClientMediaDownloader *m_media_downloader;
// time_of_day speed approximation for old protocol
bool m_time_of_day_set;

View File

@@ -50,9 +50,6 @@ ClientMap::ClientMap(
m_camera_direction(0,0,1),
m_camera_fov(M_PI)
{
m_camera_mutex.Init();
assert(m_camera_mutex.IsInitialized());
m_box = core::aabbox3d<f32>(-BS*1000000,-BS*1000000,-BS*1000000,
BS*1000000,BS*1000000,BS*1000000);
}

656
src/clientmedia.cpp Normal file
View File

@@ -0,0 +1,656 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
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.
*/
#include "clientmedia.h"
#include "httpfetch.h"
#include "client.h"
#include "clientserver.h"
#include "filecache.h"
#include "hex.h"
#include "sha1.h"
#include "debug.h"
#include "log.h"
#include "porting.h"
#include "settings.h"
#include "main.h"
#include "util/serialize.h"
#include "util/string.h"
static std::string getMediaCacheDir()
{
return porting::path_user + DIR_DELIM + "cache" + DIR_DELIM + "media";
}
/*
ClientMediaDownloader
*/
ClientMediaDownloader::ClientMediaDownloader():
m_media_cache(getMediaCacheDir())
{
m_initial_step_done = false;
m_name_bound = ""; // works because "" is an invalid file name
m_uncached_count = 0;
m_uncached_received_count = 0;
m_httpfetch_caller = HTTPFETCH_DISCARD;
m_httpfetch_active = 0;
m_httpfetch_active_limit = 0;
m_httpfetch_next_id = 0;
m_httpfetch_timeout = 0;
m_outstanding_hash_sets = 0;
}
ClientMediaDownloader::~ClientMediaDownloader()
{
if (m_httpfetch_caller != HTTPFETCH_DISCARD)
httpfetch_caller_free(m_httpfetch_caller);
for (std::map<std::string, FileStatus*>::iterator it = m_files.begin();
it != m_files.end(); ++it)
delete it->second;
for (u32 i = 0; i < m_remotes.size(); ++i)
delete m_remotes[i];
}
void ClientMediaDownloader::addFile(std::string name, std::string sha1)
{
assert(!m_initial_step_done);
// if name was already announced, ignore the new announcement
if (m_files.count(name) != 0) {
errorstream << "Client: ignoring duplicate media announcement "
<< "sent by server: \"" << name << "\""
<< std::endl;
return;
}
// if name is empty or contains illegal characters, ignore the file
if (name.empty() || !string_allowed(name, TEXTURENAME_ALLOWED_CHARS)) {
errorstream << "Client: ignoring illegal file name "
<< "sent by server: \"" << name << "\""
<< std::endl;
return;
}
// length of sha1 must be exactly 20 (160 bits), else ignore the file
if (sha1.size() != 20) {
errorstream << "Client: ignoring illegal SHA1 sent by server: "
<< hex_encode(sha1) << " \"" << name << "\""
<< std::endl;
return;
}
FileStatus *filestatus = new FileStatus;
filestatus->received = false;
filestatus->sha1 = sha1;
filestatus->current_remote = -1;
m_files.insert(std::make_pair(name, filestatus));
}
void ClientMediaDownloader::addRemoteServer(std::string baseurl)
{
assert(!m_initial_step_done);
#ifdef USE_CURL
infostream << "Client: Adding remote server \""
<< baseurl << "\" for media download" << std::endl;
RemoteServerStatus *remote = new RemoteServerStatus;
remote->baseurl = baseurl;
remote->active_count = 0;
remote->request_by_filename = false;
m_remotes.push_back(remote);
#else
infostream << "Client: Ignoring remote server \""
<< baseurl << "\" because cURL support is not compiled in"
<< std::endl;
#endif
}
void ClientMediaDownloader::step(Client *client)
{
if (!m_initial_step_done) {
initialStep(client);
m_initial_step_done = true;
}
// Remote media: check for completion of fetches
if (m_httpfetch_active) {
bool fetched_something = false;
HTTPFetchResult fetchresult;
while (httpfetch_async_get(m_httpfetch_caller, fetchresult)) {
m_httpfetch_active--;
fetched_something = true;
// Is this a hashset (index.mth) or a media file?
if (fetchresult.request_id < m_remotes.size())
remoteHashSetReceived(fetchresult);
else
remoteMediaReceived(fetchresult, client);
}
if (fetched_something)
startRemoteMediaTransfers();
// Did all remote transfers end and no new ones can be started?
// If so, request still missing files from the minetest server
// (Or report that we have all files.)
if (m_httpfetch_active == 0) {
if (m_uncached_received_count < m_uncached_count) {
infostream << "Client: Failed to remote-fetch "
<< (m_uncached_count-m_uncached_received_count)
<< " files. Requesting them"
<< " the usual way." << std::endl;
}
startConventionalTransfers(client);
}
}
}
void ClientMediaDownloader::initialStep(Client *client)
{
// Check media cache
m_uncached_count = m_files.size();
for (std::map<std::string, FileStatus*>::iterator
it = m_files.begin();
it != m_files.end(); ++it) {
std::string name = it->first;
FileStatus *filestatus = it->second;
const std::string &sha1 = filestatus->sha1;
std::ostringstream tmp_os(std::ios_base::binary);
bool found_in_cache = m_media_cache.load(hex_encode(sha1), tmp_os);
// If found in cache, try to load it from there
if (found_in_cache) {
bool success = checkAndLoad(name, sha1,
tmp_os.str(), true, client);
if (success) {
filestatus->received = true;
m_uncached_count--;
}
}
}
assert(m_uncached_received_count == 0);
// Create the media cache dir if we are likely to write to it
if (m_uncached_count != 0) {
bool did = fs::CreateAllDirs(getMediaCacheDir());
if (!did) {
errorstream << "Client: "
<< "Could not create media cache directory: "
<< getMediaCacheDir()
<< std::endl;
}
}
// If we found all files in the cache, report this fact to the server.
// If the server reported no remote servers, immediately start
// conventional transfers. Note: if cURL support is not compiled in,
// m_remotes is always empty, so "!USE_CURL" is redundant but may
// reduce the size of the compiled code
if (!USE_CURL || m_uncached_count == 0 || m_remotes.empty()) {
startConventionalTransfers(client);
}
else {
// Otherwise start off by requesting each server's sha1 set
// This is the first time we use httpfetch, so alloc a caller ID
m_httpfetch_caller = httpfetch_caller_alloc();
m_httpfetch_timeout = g_settings->getS32("curl_timeout");
// Set the active fetch limit to curl_parallel_limit or 84,
// whichever is greater. This gives us some leeway so that
// inefficiencies in communicating with the httpfetch thread
// don't slow down fetches too much. (We still want some limit
// so that when the first remote server returns its hash set,
// not all files are requested from that server immediately.)
// One such inefficiency is that ClientMediaDownloader::step()
// is only called a couple times per second, while httpfetch
// might return responses much faster than that.
// Note that httpfetch strictly enforces curl_parallel_limit
// but at no inter-thread communication cost. This however
// doesn't help with the aforementioned inefficiencies.
// The signifance of 84 is that it is 2*6*9 in base 13.
m_httpfetch_active_limit = g_settings->getS32("curl_parallel_limit");
m_httpfetch_active_limit = MYMAX(m_httpfetch_active_limit, 84);
// Write a list of hashes that we need. This will be POSTed
// to the server using Content-Type: application/octet-stream
std::string required_hash_set = serializeRequiredHashSet();
// minor fixme: this loop ignores m_httpfetch_active_limit
// another minor fixme, unlikely to matter in normal usage:
// these index.mth fetches do (however) count against
// m_httpfetch_active_limit when starting actual media file
// requests, so if there are lots of remote servers that are
// not responding, those will stall new media file transfers.
for (u32 i = 0; i < m_remotes.size(); ++i) {
assert(m_httpfetch_next_id == i);
RemoteServerStatus *remote = m_remotes[i];
actionstream << "Client: Contacting remote server \""
<< remote->baseurl << "\"" << std::endl;
HTTPFetchRequest fetchrequest;
fetchrequest.url =
remote->baseurl + MTHASHSET_FILE_NAME;
fetchrequest.caller = m_httpfetch_caller;
fetchrequest.request_id = m_httpfetch_next_id; // == i
fetchrequest.timeout = m_httpfetch_timeout;
fetchrequest.connect_timeout = m_httpfetch_timeout;
fetchrequest.post_fields = required_hash_set;
fetchrequest.extra_headers.push_back(
"Content-Type: application/octet-stream");
httpfetch_async(fetchrequest);
m_httpfetch_active++;
m_httpfetch_next_id++;
m_outstanding_hash_sets++;
}
}
}
void ClientMediaDownloader::remoteHashSetReceived(
const HTTPFetchResult &fetchresult)
{
u32 remote_id = fetchresult.request_id;
assert(remote_id < m_remotes.size());
RemoteServerStatus *remote = m_remotes[remote_id];
m_outstanding_hash_sets--;
if (fetchresult.succeeded) {
try {
// Server sent a list of file hashes that are
// available on it, try to parse the list
std::set<std::string> sha1_set;
deSerializeHashSet(fetchresult.data, sha1_set);
// Parsing succeeded: For every file that is
// available on this server, add this server
// to the available_remotes array
for(std::map<std::string, FileStatus*>::iterator
it = m_files.upper_bound(m_name_bound);
it != m_files.end(); ++it) {
FileStatus *f = it->second;
if (!f->received && sha1_set.count(f->sha1))
f->available_remotes.push_back(remote_id);
}
}
catch (SerializationError &e) {
infostream << "Client: Remote server \""
<< remote->baseurl << "\" sent invalid hash set: "
<< e.what() << std::endl;
}
}
// For compatibility: If index.mth is not found, assume that the
// server contains files named like the original files (not their sha1)
if (!fetchresult.succeeded && !fetchresult.timeout &&
fetchresult.response_code == 404) {
infostream << "Client: Enabling compatibility mode for remote "
<< "server \"" << remote->baseurl << "\"" << std::endl;
remote->request_by_filename = true;
// Assume every file is available on this server
for(std::map<std::string, FileStatus*>::iterator
it = m_files.upper_bound(m_name_bound);
it != m_files.end(); ++it) {
FileStatus *f = it->second;
if (!f->received)
f->available_remotes.push_back(remote_id);
}
}
}
void ClientMediaDownloader::remoteMediaReceived(
const HTTPFetchResult &fetchresult,
Client *client)
{
// Some remote server sent us a file.
// -> decrement number of active fetches
// -> mark file as received if fetch succeeded
// -> try to load media
std::string name;
{
std::map<unsigned long, std::string>::iterator it =
m_remote_file_transfers.find(fetchresult.request_id);
assert(it != m_remote_file_transfers.end());
name = it->second;
m_remote_file_transfers.erase(it);
}
assert(m_files.count(name) != 0);
FileStatus *filestatus = m_files[name];
assert(!filestatus->received);
assert(filestatus->current_remote >= 0);
RemoteServerStatus *remote = m_remotes[filestatus->current_remote];
filestatus->current_remote = -1;
remote->active_count--;
// If fetch succeeded, try to load media file
if (fetchresult.succeeded) {
bool success = checkAndLoad(name, filestatus->sha1,
fetchresult.data, false, client);
if (success) {
filestatus->received = true;
assert(m_uncached_received_count < m_uncached_count);
m_uncached_received_count++;
}
}
}
s32 ClientMediaDownloader::selectRemoteServer(FileStatus *filestatus)
{
assert(filestatus != NULL);
assert(!filestatus->received);
assert(filestatus->current_remote < 0);
if (filestatus->available_remotes.empty())
return -1;
else {
// Of all servers that claim to provide the file (and haven't
// been unsuccessfully tried before), find the one with the
// smallest number of currently active transfers
s32 best = 0;
s32 best_remote_id = filestatus->available_remotes[best];
s32 best_active_count = m_remotes[best_remote_id]->active_count;
for (u32 i = 1; i < filestatus->available_remotes.size(); ++i) {
s32 remote_id = filestatus->available_remotes[i];
s32 active_count = m_remotes[remote_id]->active_count;
if (active_count < best_active_count) {
best = i;
best_remote_id = remote_id;
best_active_count = active_count;
}
}
filestatus->available_remotes.erase(
filestatus->available_remotes.begin() + best);
return best_remote_id;
}
}
void ClientMediaDownloader::startRemoteMediaTransfers()
{
bool changing_name_bound = true;
for (std::map<std::string, FileStatus*>::iterator
files_iter = m_files.upper_bound(m_name_bound);
files_iter != m_files.end(); ++files_iter) {
// Abort if active fetch limit is exceeded
if (m_httpfetch_active >= m_httpfetch_active_limit)
break;
const std::string &name = files_iter->first;
FileStatus *filestatus = files_iter->second;
if (!filestatus->received && filestatus->current_remote < 0) {
// File has not been received yet and is not currently
// being transferred. Choose a server for it.
s32 remote_id = selectRemoteServer(filestatus);
if (remote_id >= 0) {
// Found a server, so start fetching
RemoteServerStatus *remote =
m_remotes[remote_id];
std::string url = remote->baseurl +
(remote->request_by_filename ? name :
hex_encode(filestatus->sha1));
verbosestream << "Client: "
<< "Requesting remote media file "
<< "\"" << name << "\" "
<< "\"" << url << "\"" << std::endl;
HTTPFetchRequest fetchrequest;
fetchrequest.url = url;
fetchrequest.caller = m_httpfetch_caller;
fetchrequest.request_id = m_httpfetch_next_id;
fetchrequest.timeout = 0; // no data timeout!
fetchrequest.connect_timeout =
m_httpfetch_timeout;
httpfetch_async(fetchrequest);
m_remote_file_transfers.insert(std::make_pair(
m_httpfetch_next_id,
name));
filestatus->current_remote = remote_id;
remote->active_count++;
m_httpfetch_active++;
m_httpfetch_next_id++;
}
}
if (filestatus->received ||
(filestatus->current_remote < 0 &&
!m_outstanding_hash_sets)) {
// If we arrive here, we conclusively know that we
// won't fetch this file from a remote server in the
// future. So update the name bound if possible.
if (changing_name_bound)
m_name_bound = name;
}
else
changing_name_bound = false;
}
}
void ClientMediaDownloader::startConventionalTransfers(Client *client)
{
assert(m_httpfetch_active == 0);
if (m_uncached_received_count == m_uncached_count) {
// In this case all media was found in the cache or
// has been downloaded from some remote server;
// report this fact to the server
client->received_media();
}
else {
// Some media files have not been received yet, use the
// conventional slow method (minetest protocol) to get them
std::list<std::string> file_requests;
for (std::map<std::string, FileStatus*>::iterator
it = m_files.begin();
it != m_files.end(); ++it) {
if (!it->second->received)
file_requests.push_back(it->first);
}
assert((s32) file_requests.size() ==
m_uncached_count - m_uncached_received_count);
client->request_media(file_requests);
}
}
void ClientMediaDownloader::conventionalTransferDone(
const std::string &name,
const std::string &data,
Client *client)
{
// Check that file was announced
std::map<std::string, FileStatus*>::iterator
file_iter = m_files.find(name);
if (file_iter == m_files.end()) {
errorstream << "Client: server sent media file that was"
<< "not announced, ignoring it: \"" << name << "\""
<< std::endl;
return;
}
FileStatus *filestatus = file_iter->second;
assert(filestatus != NULL);
// Check that file hasn't already been received
if (filestatus->received) {
errorstream << "Client: server sent media file that we already"
<< "received, ignoring it: \"" << name << "\""
<< std::endl;
return;
}
// Mark file as received, regardless of whether loading it works and
// whether the checksum matches (because at this point there is no
// other server that could send a replacement)
filestatus->received = true;
assert(m_uncached_received_count < m_uncached_count);
m_uncached_received_count++;
// Check that received file matches announced checksum
// If so, load it
checkAndLoad(name, filestatus->sha1, data, false, client);
}
bool ClientMediaDownloader::checkAndLoad(
const std::string &name, const std::string &sha1,
const std::string &data, bool is_from_cache, Client *client)
{
const char *cached_or_received = is_from_cache ? "cached" : "received";
const char *cached_or_received_uc = is_from_cache ? "Cached" : "Received";
std::string sha1_hex = hex_encode(sha1);
// Compute actual checksum of data
std::string data_sha1;
{
SHA1 data_sha1_calculator;
data_sha1_calculator.addBytes(data.c_str(), data.size());
unsigned char *data_tmpdigest = data_sha1_calculator.getDigest();
data_sha1.assign((char*) data_tmpdigest, 20);
free(data_tmpdigest);
}
// Check that received file matches announced checksum
if (data_sha1 != sha1) {
std::string data_sha1_hex = hex_encode(data_sha1);
infostream << "Client: "
<< cached_or_received_uc << " media file "
<< sha1_hex << " \"" << name << "\" "
<< "mismatches actual checksum " << data_sha1_hex
<< std::endl;
return false;
}
// Checksum is ok, try loading the file
bool success = client->loadMedia(data, name);
if (!success) {
infostream << "Client: "
<< "Failed to load " << cached_or_received << " media: "
<< sha1_hex << " \"" << name << "\""
<< std::endl;
return false;
}
verbosestream << "Client: "
<< "Loaded " << cached_or_received << " media: "
<< sha1_hex << " \"" << name << "\""
<< std::endl;
// Update cache (unless we just loaded the file from the cache)
if (!is_from_cache)
m_media_cache.update(sha1_hex, data);
return true;
}
/*
Minetest Hashset File Format
All values are stored in big-endian byte order.
[u32] signature: 'MTHS'
[u16] version: 1
For each hash in set:
[u8*20] SHA1 hash
Version changes:
1 - Initial version
*/
std::string ClientMediaDownloader::serializeRequiredHashSet()
{
std::ostringstream os(std::ios::binary);
writeU32(os, MTHASHSET_FILE_SIGNATURE); // signature
writeU16(os, 1); // version
// Write list of hashes of files that have not been
// received (found in cache) yet
for (std::map<std::string, FileStatus*>::iterator
it = m_files.begin();
it != m_files.end(); ++it) {
if (!it->second->received) {
assert(it->second->sha1.size() == 20);
os << it->second->sha1;
}
}
return os.str();
}
void ClientMediaDownloader::deSerializeHashSet(const std::string &data,
std::set<std::string> &result)
{
if (data.size() < 6 || data.size() % 20 != 6) {
throw SerializationError(
"ClientMediaDownloader::deSerializeHashSet: "
"invalid hash set file size");
}
const u8 *data_cstr = (const u8*) data.c_str();
u32 signature = readU32(&data_cstr[0]);
if (signature != MTHASHSET_FILE_SIGNATURE) {
throw SerializationError(
"ClientMediaDownloader::deSerializeHashSet: "
"invalid hash set file signature");
}
u16 version = readU16(&data_cstr[4]);
if (version != 1) {
throw SerializationError(
"ClientMediaDownloader::deSerializeHashSet: "
"unsupported hash set file version");
}
for (u32 pos = 6; pos < data.size(); pos += 20) {
result.insert(data.substr(pos, 20));
}
}

150
src/clientmedia.h Normal file
View File

@@ -0,0 +1,150 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
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.
*/
#ifndef CLIENTMEDIA_HEADER
#define CLIENTMEDIA_HEADER
#include "irrlichttypes.h"
#include "filecache.h"
#include <ostream>
#include <map>
#include <set>
#include <vector>
class Client;
struct HTTPFetchResult;
#define MTHASHSET_FILE_SIGNATURE 0x4d544853 // 'MTHS'
#define MTHASHSET_FILE_NAME "index.mth"
class ClientMediaDownloader
{
public:
ClientMediaDownloader();
~ClientMediaDownloader();
float getProgress() const {
if (m_uncached_count >= 1)
return 1.0 * m_uncached_received_count /
m_uncached_count;
else
return 0.0;
}
bool isStarted() const {
return m_initial_step_done;
}
// If this returns true, the downloader is done and can be deleted
bool isDone() const {
return m_initial_step_done &&
m_uncached_received_count == m_uncached_count;
}
// Add a file to the list of required file (but don't fetch it yet)
void addFile(std::string name, std::string sha1);
// Add a remote server to the list; ignored if not built with cURL
void addRemoteServer(std::string baseurl);
// Steps the media downloader:
// - May load media into client by calling client->loadMedia()
// - May check media cache for files
// - May add files to media cache
// - May start remote transfers by calling httpfetch_async
// - May check for completion of current remote transfers
// - May start conventional transfers by calling client->request_media()
// - May inform server that all media has been loaded
// by calling client->received_media()
// After step has been called once, don't call addFile/addRemoteServer.
void step(Client *client);
// Must be called for each file received through TOCLIENT_MEDIA
void conventionalTransferDone(
const std::string &name,
const std::string &data,
Client *client);
private:
struct FileStatus {
bool received;
std::string sha1;
s32 current_remote;
std::vector<s32> available_remotes;
};
struct RemoteServerStatus {
std::string baseurl;
s32 active_count;
bool request_by_filename;
};
void initialStep(Client *client);
void remoteHashSetReceived(const HTTPFetchResult &fetchresult);
void remoteMediaReceived(const HTTPFetchResult &fetchresult,
Client *client);
s32 selectRemoteServer(FileStatus *filestatus);
void startRemoteMediaTransfers();
void startConventionalTransfers(Client *client);
bool checkAndLoad(const std::string &name, const std::string &sha1,
const std::string &data, bool is_from_cache,
Client *client);
std::string serializeRequiredHashSet();
static void deSerializeHashSet(const std::string &data,
std::set<std::string> &result);
// Maps filename to file status
std::map<std::string, FileStatus*> m_files;
// Array of remote media servers
std::vector<RemoteServerStatus*> m_remotes;
// Filesystem-based media cache
FileCache m_media_cache;
// Has an attempt been made to load media files from the file cache?
// Have hash sets been requested from remote servers?
bool m_initial_step_done;
// Total number of media files to load
s32 m_uncached_count;
// Number of media files that have been received
s32 m_uncached_received_count;
// Status of remote transfers
unsigned long m_httpfetch_caller;
unsigned long m_httpfetch_next_id;
long m_httpfetch_timeout;
s32 m_httpfetch_active;
s32 m_httpfetch_active_limit;
s32 m_outstanding_hash_sets;
std::map<unsigned long, std::string> m_remote_file_transfers;
// All files up to this name have either been received from a
// remote server or failed on all remote servers, so those files
// don't need to be looked at again
// (use m_files.upper_bound(m_name_bound) to get an iterator)
std::string m_name_bound;
};
#endif // !CLIENTMEDIA_HEADER

View File

@@ -102,7 +102,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
added to object properties
*/
#define LATEST_PROTOCOL_VERSION 21
#define LATEST_PROTOCOL_VERSION 22
// Server's supported network protocol range
#define SERVER_PROTOCOL_VERSION_MIN 13
@@ -139,6 +139,12 @@ enum ToClientCommand
TOCLIENT_BLOCKDATA = 0x20, //TODO: Multiple blocks
TOCLIENT_ADDNODE = 0x21,
/*
u16 command
v3s16 position
serialized mapnode
u8 keep_metadata // Added in protocol version 22
*/
TOCLIENT_REMOVENODE = 0x22,
TOCLIENT_PLAYERPOS = 0x23, // Obsolete

View File

@@ -12,6 +12,7 @@
#define CMAKE_USE_FREETYPE @USE_FREETYPE@
#define CMAKE_STATIC_SHAREDIR "@SHAREDIR@"
#define CMAKE_USE_LEVELDB @USE_LEVELDB@
#define CMAKE_USE_LUAJIT @USE_LUAJIT@
#ifdef NDEBUG
#define CMAKE_BUILD_TYPE "Release"

View File

@@ -14,6 +14,7 @@
#define USE_FREETYPE 0
#define STATIC_SHAREDIR ""
#define USE_LEVELDB 0
#define USE_LUAJIT 0
#ifdef USE_CMAKE_CONFIG_H
#include "cmake_config.h"
@@ -33,6 +34,8 @@
#define STATIC_SHAREDIR CMAKE_STATIC_SHAREDIR
#undef USE_LEVELDB
#define USE_LEVELDB CMAKE_USE_LEVELDB
#undef USE_LUAJIT
#define USE_LUAJIT CMAKE_USE_LUAJIT
#endif
#endif

View File

@@ -556,7 +556,7 @@ Connection::Connection(u32 protocol_id, u32 max_packet_size, float timeout,
Connection::~Connection()
{
stop();
Stop();
// Delete peers
for(std::map<u16, Peer*>::iterator
j = m_peers.begin();
@@ -578,7 +578,7 @@ void * Connection::Thread()
u32 curtime = porting::getTimeMs();
u32 lasttime = curtime;
while(getRun())
while(!StopRequested())
{
BEGIN_DEBUG_EXCEPTION_HANDLER

View File

@@ -450,11 +450,11 @@ struct ConnectionEvent
return "CONNEVENT_NONE";
case CONNEVENT_DATA_RECEIVED:
return "CONNEVENT_DATA_RECEIVED";
case CONNEVENT_PEER_ADDED:
case CONNEVENT_PEER_ADDED:
return "CONNEVENT_PEER_ADDED";
case CONNEVENT_PEER_REMOVED:
case CONNEVENT_PEER_REMOVED:
return "CONNEVENT_PEER_REMOVED";
case CONNEVENT_BIND_FAILED:
case CONNEVENT_BIND_FAILED:
return "CONNEVENT_BIND_FAILED";
}
return "Invalid ConnectionEvent";
@@ -544,7 +544,7 @@ struct ConnectionCommand
}
};
class Connection: public SimpleThread
class Connection: public JThread
{
public:
Connection(u32 protocol_id, u32 max_packet_size, float timeout, bool ipv6);

View File

@@ -1649,6 +1649,8 @@ class GenericCAO : public ClientActiveObject
m_acceleration = readV3F1000(is);
if(fabs(m_prop.automatic_rotate) < 0.001)
m_yaw = readF1000(is);
else
readF1000(is);
bool do_interpolate = readU8(is);
bool is_end_position = readU8(is);
float update_interval = readF1000(is);
@@ -1693,12 +1695,18 @@ class GenericCAO : public ClientActiveObject
float override_speed = readF1000(is);
float override_jump = readF1000(is);
float override_gravity = readF1000(is);
// these are sent inverted so we get true when the server sends nothing
bool sneak = !readU8(is);
bool sneak_glitch = !readU8(is);
if(m_is_local_player)
{
LocalPlayer *player = m_env->getLocalPlayer();
player->physics_override_speed = override_speed;
player->physics_override_jump = override_jump;
player->physics_override_gravity = override_gravity;
player->physics_override_sneak = sneak;
player->physics_override_sneak_glitch = sneak_glitch;
}
}
else if(cmd == GENERIC_CMD_SET_ANIMATION)

View File

@@ -969,6 +969,8 @@ PlayerSAO::PlayerSAO(ServerEnvironment *env_, Player *player_, u16 peer_id_,
m_physics_override_speed(1),
m_physics_override_jump(1),
m_physics_override_gravity(1),
m_physics_override_sneak(true),
m_physics_override_sneak_glitch(true),
m_physics_override_sent(false)
{
assert(m_player);
@@ -1060,7 +1062,9 @@ std::string PlayerSAO::getClientInitializationData(u16 protocol_version)
os<<serializeLongString(gob_cmd_update_bone_position((*ii).first, (*ii).second.X, (*ii).second.Y)); // m_bone_position.size
}
os<<serializeLongString(gob_cmd_update_attachment(m_attachment_parent_id, m_attachment_bone, m_attachment_position, m_attachment_rotation)); // 4
os<<serializeLongString(gob_cmd_update_physics_override(m_physics_override_speed, m_physics_override_jump, m_physics_override_gravity)); // 5
os<<serializeLongString(gob_cmd_update_physics_override(m_physics_override_speed,
m_physics_override_jump, m_physics_override_gravity, m_physics_override_sneak,
m_physics_override_sneak_glitch)); // 5
}
else
{
@@ -1187,7 +1191,9 @@ void PlayerSAO::step(float dtime, bool send_recommended)
if(m_physics_override_sent == false){
m_physics_override_sent = true;
std::string str = gob_cmd_update_physics_override(m_physics_override_speed, m_physics_override_jump, m_physics_override_gravity);
std::string str = gob_cmd_update_physics_override(m_physics_override_speed,
m_physics_override_jump, m_physics_override_gravity,
m_physics_override_sneak, m_physics_override_sneak_glitch);
// create message and add to list
ActiveObjectMessage aom(getId(), true, str);
m_messages_out.push_back(aom);

View File

@@ -330,6 +330,8 @@ class PlayerSAO : public ServerActiveObject
float m_physics_override_speed;
float m_physics_override_jump;
float m_physics_override_gravity;
bool m_physics_override_sneak;
bool m_physics_override_sneak_glitch;
bool m_physics_override_sent;
};

View File

@@ -28,58 +28,38 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "main.h" // for g_settings
#include "settings.h"
#include "version.h"
#if USE_CURL
#include <curl/curl.h>
static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp)
{
((std::string*)userp)->append((char*)contents, size * nmemb);
return size * nmemb;
}
#endif
#include "httpfetch.h"
Json::Value fetchJsonValue(const std::string url,
struct curl_slist *chunk) {
#if USE_CURL
std::string liststring;
CURL *curl;
curl = curl_easy_init();
if (curl)
{
CURLcode res;
HTTPFetchRequest fetchrequest;
HTTPFetchResult fetchresult;
fetchrequest.url = url;
fetchrequest.useragent = std::string("Minetest ")+minetest_version_hash;
fetchrequest.timeout = g_settings->getS32("curl_timeout");
fetchrequest.caller = HTTPFETCH_SYNC;
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &liststring);
curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, g_settings->getS32("curl_timeout"));
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, g_settings->getS32("curl_timeout"));
curl_easy_setopt(curl, CURLOPT_USERAGENT, (std::string("Minetest ")+minetest_version_hash).c_str());
if (chunk != 0)
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk);
res = curl_easy_perform(curl);
if (res != CURLE_OK)
errorstream<<"Jsonreader: "<< url <<" not found (" << curl_easy_strerror(res) << ")" <<std::endl;
curl_easy_cleanup(curl);
struct curl_slist* runptr = chunk;
while(runptr) {
fetchrequest.extra_headers.push_back(runptr->data);
runptr = runptr->next;
}
httpfetch_sync(fetchrequest,fetchresult);
Json::Value root;
Json::Reader reader;
std::istringstream stream(liststring);
if (!liststring.size()) {
if (!fetchresult.succeeded) {
return Json::Value();
}
Json::Value root;
Json::Reader reader;
std::istringstream stream(fetchresult.data);
if (!reader.parse( stream, root ) )
{
errorstream << "URL: " << url << std::endl;
errorstream << "Failed to parse json data " << reader.getFormattedErrorMessages();
errorstream << "data: \"" << liststring << "\"" << std::endl;
errorstream << "data: \"" << fetchresult.data << "\"" << std::endl;
return Json::Value();
}
@@ -210,7 +190,7 @@ ModStoreModDetails readModStoreModDetails(Json::Value& details) {
}
if (retval.versions.size() < 1) {
errorstream << "readModStoreModDetails: not a single version specified!" << std::endl;
infostream << "readModStoreModDetails: not a single version specified!" << std::endl;
retval.valid = false;
}

View File

@@ -94,7 +94,7 @@ MapBlock* Database_LevelDB::loadBlock(v3s16 blockpos)
std::string datastr;
leveldb::Status s = m_database->Get(leveldb::ReadOptions(),
i64tos(getBlockAsInteger(blockpos)), &datastr);
if (datastr.length() == 0) {
if (datastr.length() == 0 && s.ok()) {
errorstream << "Blank block data in database (datastr.length() == 0) ("
<< blockpos.X << "," << blockpos.Y << "," << blockpos.Z << ")" << std::endl;

View File

@@ -206,7 +206,6 @@ JMutex g_debug_stacks_mutex;
void debug_stacks_init()
{
g_debug_stacks_mutex.Init();
}
void debug_stacks_print_to(std::ostream &os)

View File

@@ -25,11 +25,10 @@ void set_default_settings(Settings *settings)
{
// Client and server
settings->setDefault("port", "");
settings->setDefault("name", "");
// Client stuff
settings->setDefault("remote_port", "30000");
settings->setDefault("keymap_forward", "KEY_KEY_W");
settings->setDefault("keymap_backward", "KEY_KEY_S");
settings->setDefault("keymap_left", "KEY_KEY_A");
@@ -60,6 +59,7 @@ void set_default_settings(Settings *settings)
settings->setDefault("aux1_descends", "false");
settings->setDefault("doubletap_jump", "false");
settings->setDefault("always_fly_fast", "true");
settings->setDefault("directional_colored_fog", "true");
// Some (temporary) keys for debugging
settings->setDefault("keymap_print_debug_stacks", "KEY_KEY_P");
@@ -127,11 +127,21 @@ void set_default_settings(Settings *settings)
settings->setDefault("trilinear_filter", "false");
settings->setDefault("preload_item_visuals", "true");
settings->setDefault("enable_bumpmapping", "false");
settings->setDefault("enable_parallax_occlusion", "false");
settings->setDefault("parallax_occlusion_scale", "0.08");
settings->setDefault("parallax_occlusion_bias", "0.04");
settings->setDefault("enable_waving_water", "false");
settings->setDefault("water_wave_height", "1.0");
settings->setDefault("water_wave_length", "20.0");
settings->setDefault("water_wave_speed", "5.0");
settings->setDefault("enable_waving_leaves", "false");
settings->setDefault("enable_waving_plants", "false");
settings->setDefault("enable_shaders", "true");
settings->setDefault("repeat_rightclick_time", "0.25");
settings->setDefault("enable_particles", "true");
settings->setDefault("media_fetch_threads", "8");
settings->setDefault("curl_timeout", "5000");
settings->setDefault("curl_parallel_limit", "8");
settings->setDefault("serverlist_url", "servers.minetest.net");
settings->setDefault("serverlist_file", "favoriteservers.txt");
@@ -145,10 +155,14 @@ void set_default_settings(Settings *settings)
settings->setDefault("freetype", "true");
settings->setDefault("font_path", porting::getDataPath("fonts" DIR_DELIM "liberationsans.ttf"));
settings->setDefault("font_size", "13");
settings->setDefault("font_shadow", "1");
settings->setDefault("font_shadow_alpha", "128");
settings->setDefault("mono_font_path", porting::getDataPath("fonts" DIR_DELIM "liberationmono.ttf"));
settings->setDefault("mono_font_size", "13");
settings->setDefault("fallback_font_path", porting::getDataPath("fonts" DIR_DELIM "DroidSansFallbackFull.ttf"));
settings->setDefault("fallback_font_size", "13");
settings->setDefault("fallback_font_shadow", "1");
settings->setDefault("fallback_font_shadow_alpha", "128");
#else
settings->setDefault("freetype", "false");
settings->setDefault("font_path", porting::getDataPath("fonts" DIR_DELIM "fontlucida.png"));
@@ -157,6 +171,7 @@ void set_default_settings(Settings *settings)
// Server stuff
// "map-dir" doesn't exist by default.
settings->setDefault("port", "30000");
settings->setDefault("default_game", "minetest");
settings->setDefault("motd", "");
settings->setDefault("max_users", "15");
@@ -173,6 +188,8 @@ void set_default_settings(Settings *settings)
settings->setDefault("disallow_empty_password", "false");
settings->setDefault("disable_anticheat", "false");
settings->setDefault("enable_rollback_recording", "false");
settings->setDefault("cache_block_before_spawn", "true");
settings->setDefault("max_spawn_height", "50");
settings->setDefault("profiler_print_interval", "0");
settings->setDefault("enable_mapgen_debug_info", "false");
@@ -252,7 +269,7 @@ void set_default_settings(Settings *settings)
settings->setDefault("mgv7_np_terrain_alt", "4, 25, (600, 600, 600), 5934, 5, 0.6");
settings->setDefault("mgv7_np_terrain_persist", "0.6, 0.1, (500, 500, 500), 539, 3, 0.6");
settings->setDefault("mgv7_np_height_select", "-0.5, 1, (250, 250, 250), 4213, 5, 0.69");
settings->setDefault("mgv7_np_filler_depth", "0, 1.2, (150, 150, 150), 261, 4, 0.7");
settings->setDefault("mgv7_np_filler_depth", "0, 1.2, (150, 150, 150), 261, 4, 0.7");
settings->setDefault("mgv7_np_mount_height", "100, 30, (500, 500, 500), 72449, 4, 0.6");
settings->setDefault("mgv7_np_ridge_uwater", "0, 1, (500, 500, 500), 85039, 4, 0.6");
settings->setDefault("mgv7_np_mountain", "0, 1, (250, 350, 250), 5333, 5, 0.68");
@@ -270,7 +287,6 @@ void set_default_settings(Settings *settings)
settings->setDefault("mgmath_generator", "mandelbox");
settings->setDefault("curl_timeout", "5000");
// IPv6
settings->setDefault("enable_ipv6", "true");

View File

@@ -29,6 +29,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "settings.h" // For g_settings
#include "main.h" // For g_profiler
//#define DGEN_USE_TORCHES
NoiseParams nparams_dungeon_rarity =
{0.0, 1.0, v3f(500.0, 500.0, 500.0), 0, 2, 0.8};
NoiseParams nparams_dungeon_wetness =
@@ -40,54 +42,58 @@ NoiseParams nparams_dungeon_density =
///////////////////////////////////////////////////////////////////////////////
DungeonGen::DungeonGen(INodeDefManager *ndef, u64 seed, s16 waterlevel) {
this->ndef = ndef;
this->mapseed = seed;
this->water_level = waterlevel;
DungeonGen::DungeonGen(Mapgen *mapgen, DungeonParams *dparams) {
this->mg = mapgen;
this->vm = mapgen->vm;
#ifdef DGEN_USE_TORCHES
c_torch = ndef->getId("default:torch");
#endif
np_rarity = &nparams_dungeon_rarity;
np_wetness = &nparams_dungeon_wetness;
np_density = &nparams_dungeon_density;
/*
cid_water_source = ndef->getId("mapgen_water_source");
cid_cobble = ndef->getId("mapgen_cobble");
cid_mossycobble = ndef->getId("mapgen_mossycobble");
cid_torch = ndef->getId("default:torch");
*/
if (dparams) {
memcpy(&dp, dparams, sizeof(dp));
} else {
dp.c_water = mg->ndef->getId("mapgen_water_source");
dp.c_cobble = mg->ndef->getId("mapgen_cobble");
dp.c_moss = mg->ndef->getId("mapgen_mossycobble");
dp.c_stair = mg->ndef->getId("mapgen_stair_cobble");
dp.diagonal_dirs = false;
dp.mossratio = 3.0;
dp.holesize = v3s16(1, 2, 1);
dp.roomsize = v3s16(0,0,0);
dp.notifytype = GENNOTIFY_DUNGEON;
dp.np_rarity = nparams_dungeon_rarity;
dp.np_wetness = nparams_dungeon_wetness;
dp.np_density = nparams_dungeon_density;
}
}
void DungeonGen::generate(ManualMapVoxelManipulator *vm, u32 bseed,
v3s16 nmin, v3s16 nmax) {
void DungeonGen::generate(u32 bseed, v3s16 nmin, v3s16 nmax) {
//TimeTaker t("gen dungeons");
int approx_groundlevel = 10 + water_level;
int approx_groundlevel = 10 + mg->water_level;
if ((nmin.Y + nmax.Y) / 2 >= approx_groundlevel ||
NoisePerlin3D(np_rarity, nmin.X, nmin.Y, nmin.Z, mapseed) < 0.2)
NoisePerlin3D(&dp.np_rarity, nmin.X, nmin.Y, nmin.Z, mg->seed) < 0.2)
return;
this->vmanip = vm;
this->blockseed = bseed;
random.seed(bseed + 2);
cid_water_source = ndef->getId("mapgen_water_source");
cid_cobble = ndef->getId("mapgen_cobble");
cid_mossycobble = ndef->getId("mapgen_mossycobble");
//cid_torch = ndef->getId("default:torch");
cid_cobblestair = ndef->getId("mapgen_stair_cobble");
// Dungeon generator doesn't modify places which have this set
vmanip->clearFlag(VMANIP_FLAG_DUNGEON_INSIDE | VMANIP_FLAG_DUNGEON_PRESERVE);
vm->clearFlag(VMANIP_FLAG_DUNGEON_INSIDE | VMANIP_FLAG_DUNGEON_PRESERVE);
// Set all air and water to be untouchable to make dungeons open
// to caves and open air
for (s16 z = nmin.Z; z <= nmax.Z; z++) {
for (s16 y = nmin.Y; y <= nmax.Y; y++) {
u32 i = vmanip->m_area.index(nmin.X, y, z);
u32 i = vm->m_area.index(nmin.X, y, z);
for (s16 x = nmin.X; x <= nmax.X; x++) {
content_t c = vmanip->m_data[i].getContent();
if (c == CONTENT_AIR || c == cid_water_source)
vmanip->m_flags[i] |= VMANIP_FLAG_DUNGEON_PRESERVE;
content_t c = vm->m_data[i].getContent();
if (c == CONTENT_AIR || c == dp.c_water)
vm->m_flags[i] |= VMANIP_FLAG_DUNGEON_PRESERVE;
i++;
}
}
@@ -95,17 +101,18 @@ void DungeonGen::generate(ManualMapVoxelManipulator *vm, u32 bseed,
// Add it
makeDungeon(v3s16(1,1,1) * MAP_BLOCKSIZE);
// Convert some cobble to mossy cobble
for (s16 z = nmin.Z; z <= nmax.Z; z++) {
if (dp.mossratio != 0.0) {
for (s16 z = nmin.Z; z <= nmax.Z; z++)
for (s16 y = nmin.Y; y <= nmax.Y; y++) {
u32 i = vmanip->m_area.index(nmin.X, y, z);
u32 i = vm->m_area.index(nmin.X, y, z);
for (s16 x = nmin.X; x <= nmax.X; x++) {
if (vmanip->m_data[i].getContent() == cid_cobble) {
float wetness = NoisePerlin3D(np_wetness, x, y, z, mapseed);
float density = NoisePerlin3D(np_density, x, y, z, blockseed);
if (density < wetness / 3.0)
vmanip->m_data[i].setContent(cid_mossycobble);
if (vm->m_data[i].getContent() == dp.c_cobble) {
float wetness = NoisePerlin3D(&dp.np_wetness, x, y, z, mg->seed);
float density = NoisePerlin3D(&dp.np_density, x, y, z, blockseed);
if (density < wetness / dp.mossratio)
vm->m_data[i].setContent(dp.c_moss);
}
i++;
}
@@ -118,7 +125,7 @@ void DungeonGen::generate(ManualMapVoxelManipulator *vm, u32 bseed,
void DungeonGen::makeDungeon(v3s16 start_padding)
{
v3s16 areasize = vmanip->m_area.getExtent();
v3s16 areasize = vm->m_area.getExtent();
v3s16 roomsize;
v3s16 roomplace;
@@ -126,16 +133,17 @@ void DungeonGen::makeDungeon(v3s16 start_padding)
Find place for first room
*/
bool fits = false;
for (u32 i = 0; i < 100; i++)
for (u32 i = 0; i < 100 && !fits; i++)
{
bool is_large_room = ((random.next() & 3) == 1);
roomsize = is_large_room ?
v3s16(random.range(8, 16),random.range(8, 16),random.range(8, 16)) :
v3s16(random.range(4, 8),random.range(4, 6),random.range(4, 8));
roomsize += dp.roomsize;
// start_padding is used to disallow starting the generation of
// a dungeon in a neighboring generation chunk
roomplace = vmanip->m_area.MinEdge + start_padding + v3s16(
roomplace = vm->m_area.MinEdge + start_padding + v3s16(
random.range(0,areasize.X-roomsize.X-1-start_padding.X),
random.range(0,areasize.Y-roomsize.Y-1-start_padding.Y),
random.range(0,areasize.Z-roomsize.Z-1-start_padding.Z));
@@ -150,20 +158,13 @@ void DungeonGen::makeDungeon(v3s16 start_padding)
for (s16 x = 1; x < roomsize.X - 1; x++)
{
v3s16 p = roomplace + v3s16(x, y, z);
u32 vi = vmanip->m_area.index(p);
if (vmanip->m_flags[vi] & VMANIP_FLAG_DUNGEON_INSIDE)
{
fits = false;
break;
}
if (vmanip->m_data[vi].getContent() == CONTENT_IGNORE)
{
u32 vi = vm->m_area.index(p);
if ((vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_INSIDE) ||
vm->m_data[vi].getContent() == CONTENT_IGNORE) {
fits = false;
break;
}
}
if (fits)
break;
}
// No place found
if (fits == false)
@@ -183,9 +184,15 @@ void DungeonGen::makeDungeon(v3s16 start_padding)
makeRoom(roomsize, roomplace);
v3s16 room_center = roomplace + v3s16(roomsize.X / 2, 1, roomsize.Z / 2);
if (mg->gennotify & (1 << dp.notifytype)) {
std::vector <v3s16> *nvec = mg->gen_notifications[dp.notifytype];
nvec->push_back(room_center);
}
#ifdef DGEN_USE_TORCHES
// Place torch at room center (for testing)
//vmanip->m_data[vmanip->m_area.index(room_center)] = MapNode(cid_torch);
vm->m_data[vm->m_area.index(room_center)] = MapNode(c_torch);
#endif
// Quit if last room
if (i == room_count - 1)
@@ -197,12 +204,9 @@ void DungeonGen::makeDungeon(v3s16 start_padding)
v3s16 walker_start_place;
if(start_in_last_room)
{
if (start_in_last_room) {
walker_start_place = last_room_center;
}
else
{
} else {
walker_start_place = room_center;
// Store center of current room as the last one
last_room_center = room_center;
@@ -213,8 +217,7 @@ void DungeonGen::makeDungeon(v3s16 start_padding)
v3s16 doordir;
m_pos = walker_start_place;
bool r = findPlaceForDoor(doorplace, doordir);
if (r == false)
if (!findPlaceForDoor(doorplace, doordir))
return;
if (random.range(0,1) == 0)
@@ -231,10 +234,11 @@ void DungeonGen::makeDungeon(v3s16 start_padding)
// Find a place for a random sized room
roomsize = v3s16(random.range(4,8),random.range(4,6),random.range(4,8));
roomsize += dp.roomsize;
m_pos = corridor_end;
m_dir = corridor_end_dir;
r = findPlaceForRoomDoor(roomsize, doorplace, doordir, roomplace);
if (r == false)
if (!findPlaceForRoomDoor(roomsize, doorplace, doordir, roomplace))
return;
if (random.range(0,1) == 0)
@@ -250,7 +254,7 @@ void DungeonGen::makeDungeon(v3s16 start_padding)
void DungeonGen::makeRoom(v3s16 roomsize, v3s16 roomplace)
{
MapNode n_cobble(cid_cobble);
MapNode n_cobble(dp.c_cobble);
MapNode n_air(CONTENT_AIR);
// Make +-X walls
@@ -259,21 +263,21 @@ void DungeonGen::makeRoom(v3s16 roomsize, v3s16 roomplace)
{
{
v3s16 p = roomplace + v3s16(0, y, z);
if (vmanip->m_area.contains(p) == false)
if (vm->m_area.contains(p) == false)
continue;
u32 vi = vmanip->m_area.index(p);
if (vmanip->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE)
u32 vi = vm->m_area.index(p);
if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE)
continue;
vmanip->m_data[vi] = n_cobble;
vm->m_data[vi] = n_cobble;
}
{
v3s16 p = roomplace + v3s16(roomsize.X - 1, y, z);
if (vmanip->m_area.contains(p) == false)
if (vm->m_area.contains(p) == false)
continue;
u32 vi = vmanip->m_area.index(p);
if (vmanip->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE)
u32 vi = vm->m_area.index(p);
if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE)
continue;
vmanip->m_data[vi] = n_cobble;
vm->m_data[vi] = n_cobble;
}
}
@@ -283,21 +287,21 @@ void DungeonGen::makeRoom(v3s16 roomsize, v3s16 roomplace)
{
{
v3s16 p = roomplace + v3s16(x, y, 0);
if (vmanip->m_area.contains(p) == false)
if (vm->m_area.contains(p) == false)
continue;
u32 vi = vmanip->m_area.index(p);
if (vmanip->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE)
u32 vi = vm->m_area.index(p);
if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE)
continue;
vmanip->m_data[vi] = n_cobble;
vm->m_data[vi] = n_cobble;
}
{
v3s16 p = roomplace + v3s16(x, y, roomsize.Z - 1);
if (vmanip->m_area.contains(p) == false)
if (vm->m_area.contains(p) == false)
continue;
u32 vi = vmanip->m_area.index(p);
if (vmanip->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE)
u32 vi = vm->m_area.index(p);
if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE)
continue;
vmanip->m_data[vi] = n_cobble;
vm->m_data[vi] = n_cobble;
}
}
@@ -307,21 +311,21 @@ void DungeonGen::makeRoom(v3s16 roomsize, v3s16 roomplace)
{
{
v3s16 p = roomplace + v3s16(x, 0, z);
if (vmanip->m_area.contains(p) == false)
if (vm->m_area.contains(p) == false)
continue;
u32 vi = vmanip->m_area.index(p);
if (vmanip->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE)
u32 vi = vm->m_area.index(p);
if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE)
continue;
vmanip->m_data[vi] = n_cobble;
vm->m_data[vi] = n_cobble;
}
{
v3s16 p = roomplace + v3s16(x,roomsize. Y - 1, z);
if (vmanip->m_area.contains(p) == false)
if (vm->m_area.contains(p) == false)
continue;
u32 vi = vmanip->m_area.index(p);
if (vmanip->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE)
u32 vi = vm->m_area.index(p);
if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE)
continue;
vmanip->m_data[vi] = n_cobble;
vm->m_data[vi] = n_cobble;
}
}
@@ -331,11 +335,11 @@ void DungeonGen::makeRoom(v3s16 roomsize, v3s16 roomplace)
for (s16 x = 1; x < roomsize.X - 1; x++)
{
v3s16 p = roomplace + v3s16(x, y, z);
if (vmanip->m_area.contains(p) == false)
if (vm->m_area.contains(p) == false)
continue;
u32 vi = vmanip->m_area.index(p);
vmanip->m_flags[vi] |= VMANIP_FLAG_DUNGEON_UNTOUCHABLE;
vmanip->m_data[vi] = n_air;
u32 vi = vm->m_area.index(p);
vm->m_flags[vi] |= VMANIP_FLAG_DUNGEON_UNTOUCHABLE;
vm->m_data[vi] = n_air;
}
}
@@ -348,29 +352,32 @@ void DungeonGen::makeFill(v3s16 place, v3s16 size,
for (s16 x = 0; x < size.X; x++)
{
v3s16 p = place + v3s16(x, y, z);
if (vmanip->m_area.contains(p) == false)
if (vm->m_area.contains(p) == false)
continue;
u32 vi = vmanip->m_area.index(p);
if (vmanip->m_flags[vi] & avoid_flags)
u32 vi = vm->m_area.index(p);
if (vm->m_flags[vi] & avoid_flags)
continue;
vmanip->m_flags[vi] |= or_flags;
vmanip->m_data[vi] = n;
vm->m_flags[vi] |= or_flags;
vm->m_data[vi] = n;
}
}
void DungeonGen::makeHole(v3s16 place)
{
makeFill(place, v3s16(1, 2, 1), 0, MapNode(CONTENT_AIR),
VMANIP_FLAG_DUNGEON_INSIDE);
makeFill(place, dp.holesize, 0,
MapNode(CONTENT_AIR), VMANIP_FLAG_DUNGEON_INSIDE);
}
void DungeonGen::makeDoor(v3s16 doorplace, v3s16 doordir)
{
makeHole(doorplace);
#ifdef DGEN_USE_TORCHES
// Place torch (for testing)
//vmanip->m_data[vmanip->m_area.index(doorplace)] = MapNode(cid_torch);
vm->m_data[vm->m_area.index(doorplace)] = MapNode(c_torch);
#endif
}
@@ -398,33 +405,35 @@ void DungeonGen::makeCorridor(v3s16 doorplace,
if (partcount != 0)
p.Y += make_stairs;
if (vmanip->m_area.contains(p) == true &&
vmanip->m_area.contains(p + v3s16(0, 1, 0)) == true) {
if (vm->m_area.contains(p) == true &&
vm->m_area.contains(p + v3s16(0, 1, 0)) == true) {
if (make_stairs) {
makeFill(p + v3s16(-1, -1, -1), v3s16(3, 5, 3),
VMANIP_FLAG_DUNGEON_UNTOUCHABLE, MapNode(cid_cobble), 0);
makeFill(p + v3s16(-1, -1, -1), dp.holesize + v3s16(2, 3, 2),
VMANIP_FLAG_DUNGEON_UNTOUCHABLE, MapNode(dp.c_cobble), 0);
makeHole(p);
makeHole(p - dir);
// TODO: fix stairs code so it works 100% (quite difficult)
// exclude stairs from the bottom step
if (((make_stairs == 1) && i != 0) ||
((make_stairs == -1) && i != length - 1)) {
// exclude stairs from diagonal steps
if (((dir.X ^ dir.Z) & 1) &&
(((make_stairs == 1) && i != 0) ||
((make_stairs == -1) && i != length - 1))) {
// rotate face 180 deg if making stairs backwards
int facedir = dir_to_facedir(dir * make_stairs);
u32 vi = vmanip->m_area.index(p.X - dir.X, p.Y - 1, p.Z - dir.Z);
if (vmanip->m_data[vi].getContent() == cid_cobble)
vmanip->m_data[vi] = MapNode(cid_cobblestair, 0, facedir);
u32 vi = vm->m_area.index(p.X - dir.X, p.Y - 1, p.Z - dir.Z);
if (vm->m_data[vi].getContent() == dp.c_cobble)
vm->m_data[vi] = MapNode(dp.c_stair, 0, facedir);
vi = vmanip->m_area.index(p.X, p.Y, p.Z);
if (vmanip->m_data[vi].getContent() == cid_cobble)
vmanip->m_data[vi] = MapNode(cid_cobblestair, 0, facedir);
vi = vm->m_area.index(p.X, p.Y, p.Z);
if (vm->m_data[vi].getContent() == dp.c_cobble)
vm->m_data[vi] = MapNode(dp.c_stair, 0, facedir);
}
} else {
makeFill(p + v3s16(-1, -1, -1), v3s16(3, 4, 3),
VMANIP_FLAG_DUNGEON_UNTOUCHABLE, MapNode(cid_cobble), 0);
makeFill(p + v3s16(-1, -1, -1), dp.holesize + v3s16(2, 2, 2),
VMANIP_FLAG_DUNGEON_UNTOUCHABLE, MapNode(dp.c_cobble), 0);
makeHole(p);
}
@@ -462,15 +471,15 @@ bool DungeonGen::findPlaceForDoor(v3s16 &result_place, v3s16 &result_dir)
{
v3s16 p = m_pos + m_dir;
v3s16 p1 = p + v3s16(0, 1, 0);
if (vmanip->m_area.contains(p) == false
|| vmanip->m_area.contains(p1) == false
if (vm->m_area.contains(p) == false
|| vm->m_area.contains(p1) == false
|| i % 4 == 0)
{
randomizeDir();
continue;
}
if (vmanip->getNodeNoExNoEmerge(p).getContent() == cid_cobble
&& vmanip->getNodeNoExNoEmerge(p1).getContent() == cid_cobble)
if (vm->getNodeNoExNoEmerge(p).getContent() == dp.c_cobble
&& vm->getNodeNoExNoEmerge(p1).getContent() == dp.c_cobble)
{
// Found wall, this is a good place!
result_place = p;
@@ -483,18 +492,18 @@ bool DungeonGen::findPlaceForDoor(v3s16 &result_place, v3s16 &result_dir)
Determine where to move next
*/
// Jump one up if the actual space is there
if (vmanip->getNodeNoExNoEmerge(p+v3s16(0,0,0)).getContent() == cid_cobble
&& vmanip->getNodeNoExNoEmerge(p+v3s16(0,1,0)).getContent() == CONTENT_AIR
&& vmanip->getNodeNoExNoEmerge(p+v3s16(0,2,0)).getContent() == CONTENT_AIR)
if (vm->getNodeNoExNoEmerge(p+v3s16(0,0,0)).getContent() == dp.c_cobble
&& vm->getNodeNoExNoEmerge(p+v3s16(0,1,0)).getContent() == CONTENT_AIR
&& vm->getNodeNoExNoEmerge(p+v3s16(0,2,0)).getContent() == CONTENT_AIR)
p += v3s16(0,1,0);
// Jump one down if the actual space is there
if (vmanip->getNodeNoExNoEmerge(p+v3s16(0,1,0)).getContent() == cid_cobble
&& vmanip->getNodeNoExNoEmerge(p+v3s16(0,0,0)).getContent() == CONTENT_AIR
&& vmanip->getNodeNoExNoEmerge(p+v3s16(0,-1,0)).getContent() == CONTENT_AIR)
if (vm->getNodeNoExNoEmerge(p+v3s16(0,1,0)).getContent() == dp.c_cobble
&& vm->getNodeNoExNoEmerge(p+v3s16(0,0,0)).getContent() == CONTENT_AIR
&& vm->getNodeNoExNoEmerge(p+v3s16(0,-1,0)).getContent() == CONTENT_AIR)
p += v3s16(0,-1,0);
// Check if walking is now possible
if (vmanip->getNodeNoExNoEmerge(p).getContent() != CONTENT_AIR
|| vmanip->getNodeNoExNoEmerge(p+v3s16(0,1,0)).getContent() != CONTENT_AIR)
if (vm->getNodeNoExNoEmerge(p).getContent() != CONTENT_AIR
|| vm->getNodeNoExNoEmerge(p+v3s16(0,1,0)).getContent() != CONTENT_AIR)
{
// Cannot continue walking here
randomizeDir();
@@ -551,12 +560,12 @@ bool DungeonGen::findPlaceForRoomDoor(v3s16 roomsize, v3s16 &result_doorplace,
for (s16 x = 1; x < roomsize.X - 1; x++)
{
v3s16 p = roomplace + v3s16(x, y, z);
if (vmanip->m_area.contains(p) == false)
if (vm->m_area.contains(p) == false)
{
fits = false;
break;
}
if (vmanip->m_flags[vmanip->m_area.index(p)]
if (vm->m_flags[vm->m_area.index(p)]
& VMANIP_FLAG_DUNGEON_INSIDE)
{
fits = false;
@@ -577,12 +586,25 @@ bool DungeonGen::findPlaceForRoomDoor(v3s16 roomsize, v3s16 &result_doorplace,
}
v3s16 rand_ortho_dir(PseudoRandom &random)
v3s16 rand_ortho_dir(PseudoRandom &random, bool diagonal_dirs)
{
if (random.next() % 2 == 0)
return random.next() % 2 ? v3s16(-1, 0, 0) : v3s16(1, 0, 0);
else
return random.next() % 2 ? v3s16(0, 0, -1) : v3s16(0, 0, 1);
// Make diagonal directions somewhat rare
if (diagonal_dirs && (random.next() % 4 == 0)) {
v3s16 dir;
int trycount = 0;
do {
trycount++;
dir = v3s16(random.next() % 3 - 1, 0, random.next() % 3 - 1);
} while ((dir.X == 0 && dir.Z == 0) && trycount < 10);
return dir;
} else {
if (random.next() % 2 == 0)
return random.next() % 2 ? v3s16(-1, 0, 0) : v3s16(1, 0, 0);
else
return random.next() % 2 ? v3s16(0, 0, -1) : v3s16(0, 0, 1);
}
}

View File

@@ -30,40 +30,48 @@ with this program; if not, write to the Free Software Foundation, Inc.,
class ManualMapVoxelManipulator;
class INodeDefManager;
class Mapgen;
v3s16 rand_ortho_dir(PseudoRandom &random);
v3s16 rand_ortho_dir(PseudoRandom &random, bool diagonal_dirs);
v3s16 turn_xz(v3s16 olddir, int t);
v3s16 random_turn(PseudoRandom &random, v3s16 olddir);
int dir_to_facedir(v3s16 d);
struct DungeonParams {
content_t c_water;
content_t c_cobble;
content_t c_moss;
content_t c_stair;
int notifytype;
bool diagonal_dirs;
float mossratio;
v3s16 holesize;
v3s16 roomsize;
NoiseParams np_rarity;
NoiseParams np_wetness;
NoiseParams np_density;
};
class DungeonGen {
public:
ManualMapVoxelManipulator *vm;
Mapgen *mg;
u32 blockseed;
u64 mapseed;
ManualMapVoxelManipulator *vmanip;
INodeDefManager *ndef;
PseudoRandom random;
v3s16 csize;
s16 water_level;
NoiseParams *np_rarity;
NoiseParams *np_wetness;
NoiseParams *np_density;
content_t cid_water_source;
content_t cid_cobble;
content_t cid_mossycobble;
content_t cid_torch;
content_t cid_cobblestair;
content_t c_torch;
DungeonParams dp;
//RoomWalker
v3s16 m_pos;
v3s16 m_dir;
DungeonGen(INodeDefManager *ndef, u64 seed, s16 waterlevel);
void generate(ManualMapVoxelManipulator *vm, u32 bseed,
v3s16 full_node_min, v3s16 full_node_max);
//void generate(v3s16 full_node_min, v3s16 full_node_max, u32 bseed);
DungeonGen(Mapgen *mg, DungeonParams *dparams);
void generate(u32 bseed, v3s16 full_node_min, v3s16 full_node_max);
void makeDungeon(v3s16 start_padding);
void makeRoom(v3s16 roomsize, v3s16 roomplace);
@@ -79,50 +87,12 @@ class DungeonGen {
void randomizeDir()
{
m_dir = rand_ortho_dir(random);
m_dir = rand_ortho_dir(random, dp.diagonal_dirs);
}
};
class RoomWalker
{
public:
RoomWalker(VoxelManipulator &vmanip_, v3s16 pos, PseudoRandom &random,
INodeDefManager *ndef):
vmanip(vmanip_),
m_pos(pos),
m_random(random),
m_ndef(ndef)
{
randomizeDir();
}
void randomizeDir()
{
m_dir = rand_ortho_dir(m_random);
}
void setPos(v3s16 pos)
{
m_pos = pos;
}
void setDir(v3s16 dir)
{
m_dir = dir;
}
//bool findPlaceForDoor(v3s16 &result_place, v3s16 &result_dir);
//bool findPlaceForRoomDoor(v3s16 roomsize, v3s16 &result_doorplace,
// v3s16 &result_doordir, v3s16 &result_roomplace);
private:
VoxelManipulator &vmanip;
v3s16 m_pos;
v3s16 m_dir;
PseudoRandom &m_random;
INodeDefManager *m_ndef;
};
extern NoiseParams nparams_dungeon_rarity;
extern NoiseParams nparams_dungeon_wetness;
extern NoiseParams nparams_dungeon_density;
#endif

View File

@@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "server.h"
#include <iostream>
#include <queue>
#include "jthread/jevent.h"
#include "map.h"
#include "environment.h"
#include "util/container.h"
@@ -46,7 +47,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "mapgen_math.h"
class EmergeThread : public SimpleThread
class EmergeThread : public JThread
{
public:
Server *m_server;
@@ -55,31 +56,22 @@ class EmergeThread : public SimpleThread
Mapgen *mapgen;
bool enable_mapgen_debug_info;
int id;
Event qevent;
std::queue<v3s16> blockqueue;
EmergeThread(Server *server, int ethreadid):
SimpleThread(),
JThread(),
m_server(server),
map(NULL),
emerge(NULL),
mapgen(NULL),
enable_mapgen_debug_info(false),
id(ethreadid)
{
}
void *Thread();
void trigger()
{
setRun(true);
if(IsRunning() == false)
{
Start();
}
}
bool popBlockEmerge(v3s16 *pos, u8 *flags);
bool getBlockOrStartGen(v3s16 p, MapBlock **b,
BlockMakeData *data, bool allow_generate);
@@ -90,24 +82,24 @@ class EmergeThread : public SimpleThread
EmergeManager::EmergeManager(IGameDef *gamedef) {
//register built-in mapgens
registerMapgen("v6", new MapgenFactoryV6());
registerMapgen("v7", new MapgenFactoryV7());
registerMapgen("indev", new MapgenFactoryIndev());
registerMapgen("v6", new MapgenFactoryV6());
registerMapgen("v7", new MapgenFactoryV7());
registerMapgen("indev", new MapgenFactoryIndev());
registerMapgen("singlenode", new MapgenFactorySinglenode());
registerMapgen("math", new MapgenFactoryMath());
registerMapgen("math", new MapgenFactoryMath());
this->ndef = gamedef->getNodeDefManager();
this->biomedef = new BiomeDefManager();
this->params = NULL;
this->luaoverride_params = NULL;
this->luaoverride_params_modified = 0;
this->luaoverride_flagmask = 0;
this->gennotify = 0;
mapgen_debug_info = g_settings->getBool("enable_mapgen_debug_info");
queuemutex.Init();
int nthreads;
if (g_settings->get("num_emerge_threads").empty()) {
int nprocs = porting::getNumberOfProcessors();
@@ -118,7 +110,7 @@ EmergeManager::EmergeManager(IGameDef *gamedef) {
}
if (nthreads < 1)
nthreads = 1;
qlimit_total = g_settings->getU16("emergequeue_limit_total");
qlimit_diskonly = g_settings->get("emergequeue_limit_diskonly").empty() ?
nthreads * 5 + 1 :
@@ -126,19 +118,19 @@ EmergeManager::EmergeManager(IGameDef *gamedef) {
qlimit_generate = g_settings->get("emergequeue_limit_generate").empty() ?
nthreads + 1 :
g_settings->getU16("emergequeue_limit_generate");
for (int i = 0; i != nthreads; i++)
emergethread.push_back(new EmergeThread((Server *)gamedef, i));
infostream << "EmergeManager: using " << nthreads << " threads" << std::endl;
}
EmergeManager::~EmergeManager() {
for (unsigned int i = 0; i != emergethread.size(); i++) {
emergethread[i]->setRun(false);
emergethread[i]->Stop();
emergethread[i]->qevent.signal();
emergethread[i]->stop();
emergethread[i]->Wait();
delete emergethread[i];
delete mapgen[i];
}
@@ -152,7 +144,7 @@ EmergeManager::~EmergeManager() {
for (unsigned int i = 0; i < decorations.size(); i++)
delete decorations[i];
decorations.clear();
for (std::map<std::string, MapgenFactory *>::iterator iter = mglist.begin();
iter != mglist.end(); iter ++) {
delete iter->second;
@@ -165,83 +157,120 @@ EmergeManager::~EmergeManager() {
void EmergeManager::initMapgens(MapgenParams *mgparams) {
Mapgen *mg;
if (mapgen.size())
return;
// Resolve names of nodes for things that were registered
// (at this point, the registration period is over)
biomedef->resolveNodeNames(ndef);
for (size_t i = 0; i != ores.size(); i++)
ores[i]->resolveNodeNames(ndef);
for (size_t i = 0; i != decorations.size(); i++)
decorations[i]->resolveNodeNames(ndef);
// Apply mapgen parameter overrides from Lua
if (luaoverride_params) {
if (luaoverride_params_modified & MGPARAMS_SET_MGNAME)
mgparams->mg_name = luaoverride_params->mg_name;
if (luaoverride_params_modified & MGPARAMS_SET_MGNAME) {
MapgenParams *mgp = setMapgenType(mgparams, luaoverride_params->mg_name);
if (!mgp) {
errorstream << "EmergeManager: Failed to set new mapgen name"
<< std::endl;
} else {
mgparams = mgp;
}
}
if (luaoverride_params_modified & MGPARAMS_SET_SEED)
mgparams->seed = luaoverride_params->seed;
if (luaoverride_params_modified & MGPARAMS_SET_WATER_LEVEL)
mgparams->water_level = luaoverride_params->water_level;
if (luaoverride_params_modified & MGPARAMS_SET_FLAGS) {
mgparams->flags &= ~luaoverride_flagmask;
mgparams->flags |= luaoverride_params->flags;
}
delete luaoverride_params;
luaoverride_params = NULL;
}
// Create the mapgens
this->params = mgparams;
for (size_t i = 0; i != emergethread.size(); i++) {
mg = createMapgen(params->mg_name, 0, params);
mg = createMapgen(params->mg_name, i, params);
if (!mg) {
infostream << "EmergeManager: falling back to mapgen v6" << std::endl;
delete params;
params = createMapgenParams("v6");
mg = createMapgen("v6", 0, params);
infostream << "EmergeManager: Falling back to Mapgen V6" << std::endl;
params = setMapgenType(params, "v6");
mg = createMapgen(params->mg_name, i, params);
if (!mg) {
errorstream << "EmergeManager: CRITICAL ERROR: Failed to fall"
"back to Mapgen V6, not generating map" << std::endl;
}
}
mapgen.push_back(mg);
}
}
MapgenParams *EmergeManager::setMapgenType(MapgenParams *mgparams,
std::string newname) {
MapgenParams *newparams = createMapgenParams(newname);
if (!newparams) {
errorstream << "EmergeManager: Mapgen override failed" << std::endl;
return NULL;
}
newparams->mg_name = newname;
newparams->seed = mgparams->seed;
newparams->water_level = mgparams->water_level;
newparams->chunksize = mgparams->chunksize;
newparams->flags = mgparams->flags;
if (!newparams->readParams(g_settings)) {
errorstream << "EmergeManager: Mapgen override failed" << std::endl;
delete newparams;
return NULL;
}
delete mgparams;
return newparams;
}
Mapgen *EmergeManager::getCurrentMapgen() {
for (unsigned int i = 0; i != emergethread.size(); i++) {
if (emergethread[i]->IsSameThread())
return emergethread[i]->mapgen;
}
return NULL;
}
void EmergeManager::triggerAllThreads() {
void EmergeManager::startAllThreads() {
for (unsigned int i = 0; i != emergethread.size(); i++)
emergethread[i]->trigger();
emergethread[i]->Start();
}
bool EmergeManager::enqueueBlockEmerge(u16 peer_id, v3s16 p, bool allow_generate) {
std::map<v3s16, BlockEmergeData *>::const_iterator iter;
BlockEmergeData *bedata;
u16 count;
u8 flags = 0;
int idx = 0;
if (allow_generate)
flags |= BLOCK_EMERGE_ALLOWGEN;
{
JMutexAutoLock queuelock(queuemutex);
count = blocks_enqueued.size();
if (count >= qlimit_total)
return false;
@@ -250,7 +279,7 @@ bool EmergeManager::enqueueBlockEmerge(u16 peer_id, v3s16 p, bool allow_generate
u16 qlimit_peer = allow_generate ? qlimit_generate : qlimit_diskonly;
if (count >= qlimit_peer)
return false;
iter = blocks_enqueued.find(p);
if (iter != blocks_enqueued.end()) {
bedata = iter->second;
@@ -262,9 +291,9 @@ bool EmergeManager::enqueueBlockEmerge(u16 peer_id, v3s16 p, bool allow_generate
bedata->flags = flags;
bedata->peer_requested = peer_id;
blocks_enqueued.insert(std::make_pair(p, bedata));
peer_queue_count[peer_id] = count + 1;
// insert into the EmergeThread queue with the least items
int lowestitems = emergethread[0]->blockqueue.size();
for (unsigned int i = 1; i != emergethread.size(); i++) {
@@ -274,11 +303,11 @@ bool EmergeManager::enqueueBlockEmerge(u16 peer_id, v3s16 p, bool allow_generate
lowestitems = nitems;
}
}
emergethread[idx]->blockqueue.push(p);
}
emergethread[idx]->qevent.signal();
return true;
}
@@ -286,10 +315,10 @@ bool EmergeManager::enqueueBlockEmerge(u16 peer_id, v3s16 p, bool allow_generate
int EmergeManager::getGroundLevelAtPoint(v2s16 p) {
if (mapgen.size() == 0 || !mapgen[0]) {
errorstream << "EmergeManager: getGroundLevelAtPoint() called"
" before mapgen initialized" << std::endl;
" before mapgen initialized" << std::endl;
return 0;
}
return mapgen[0]->getGroundLevelAtPoint(p);
}
@@ -325,7 +354,7 @@ Mapgen *EmergeManager::createMapgen(std::string mgname, int mgid,
" not registered" << std::endl;
return NULL;
}
MapgenFactory *mgfactory = iter->second;
return mgfactory->createMapgen(mgid, mgparams, this);
}
@@ -339,7 +368,7 @@ MapgenParams *EmergeManager::createMapgenParams(std::string mgname) {
" not registered" << std::endl;
return NULL;
}
MapgenFactory *mgfactory = iter->second;
return mgfactory->createMapgenParams();
}
@@ -350,10 +379,10 @@ MapgenParams *EmergeManager::getParamsFromSettings(Settings *settings) {
MapgenParams *mgparams = createMapgenParams(mg_name);
if (!mgparams)
return NULL;
std::string seedstr = settings->get(settings == g_settings ?
"fixed_map_seed" : "seed");
mgparams->mg_name = mg_name;
mgparams->seed = read_seed(seedstr.c_str());
mgparams->water_level = settings->getS16("water_level");
@@ -385,7 +414,7 @@ void EmergeManager::registerMapgen(std::string mgname, MapgenFactory *mgfactory)
}
////////////////////////////// Emerge Thread //////////////////////////////////
////////////////////////////// Emerge Thread //////////////////////////////////
bool EmergeThread::popBlockEmerge(v3s16 *pos, u8 *flags) {
std::map<v3s16, BlockEmergeData *>::iterator iter;
@@ -395,31 +424,31 @@ bool EmergeThread::popBlockEmerge(v3s16 *pos, u8 *flags) {
return false;
v3s16 p = blockqueue.front();
blockqueue.pop();
*pos = p;
iter = emerge->blocks_enqueued.find(p);
if (iter == emerge->blocks_enqueued.end())
if (iter == emerge->blocks_enqueued.end())
return false; //uh oh, queue and map out of sync!!
BlockEmergeData *bedata = iter->second;
*flags = bedata->flags;
emerge->peer_queue_count[bedata->peer_requested]--;
delete bedata;
emerge->blocks_enqueued.erase(iter);
return true;
}
bool EmergeThread::getBlockOrStartGen(v3s16 p, MapBlock **b,
bool EmergeThread::getBlockOrStartGen(v3s16 p, MapBlock **b,
BlockMakeData *data, bool allow_gen) {
v2s16 p2d(p.X, p.Z);
//envlock: usually takes <=1ms, sometimes 90ms or ~400ms to acquire
JMutexAutoLock envlock(m_server->m_env_mutex);
JMutexAutoLock envlock(m_server->m_env_mutex);
// Load sector if it isn't loaded
if (map->getSectorNoGenerateNoEx(p2d) == NULL)
map->loadSectorMeta(p2d);
@@ -440,7 +469,7 @@ bool EmergeThread::getBlockOrStartGen(v3s16 p, MapBlock **b,
*b = block;
return map->initBlockMake(data, p);
}
*b = block;
return false;
}
@@ -455,13 +484,13 @@ void *EmergeThread::Thread() {
v3s16 last_tried_pos(-32768,-32768,-32768); // For error output
v3s16 p;
u8 flags;
map = (ServerMap *)&(m_server->m_env->getMap());
emerge = m_server->m_emerge;
mapgen = emerge->mapgen[id];
enable_mapgen_debug_info = emerge->mapgen_debug_info;
while (getRun())
while (!StopRequested())
try {
if (!popBlockEmerge(&p, &flags)) {
qevent.wait();
@@ -474,7 +503,7 @@ void *EmergeThread::Thread() {
bool allow_generate = flags & BLOCK_EMERGE_ALLOWGEN;
EMERGE_DBG_OUT("p=" PP(p) " allow_generate=" << allow_generate);
/*
Try to fetch block from memory or disk.
If not found and asked to generate, initialize generator.
@@ -482,8 +511,8 @@ void *EmergeThread::Thread() {
BlockMakeData data;
MapBlock *block = NULL;
std::map<v3s16, MapBlock *> modified_blocks;
if (getBlockOrStartGen(p, &block, &data, allow_generate)) {
if (getBlockOrStartGen(p, &block, &data, allow_generate) && mapgen) {
{
ScopeProfiler sp(g_profiler, "EmergeThread: Mapgen::makeChunk", SPT_AVG);
TimeTaker t("mapgen::make_block()");
@@ -496,12 +525,12 @@ void *EmergeThread::Thread() {
{
//envlock: usually 0ms, but can take either 30 or 400ms to acquire
JMutexAutoLock envlock(m_server->m_env_mutex);
JMutexAutoLock envlock(m_server->m_env_mutex);
ScopeProfiler sp(g_profiler, "EmergeThread: after "
"Mapgen::makeChunk (envlock)", SPT_AVG);
map->finishBlockMake(&data, modified_blocks);
block = map->getBlockNoCreateNoEx(p);
if (block) {
/*
@@ -513,16 +542,18 @@ void *EmergeThread::Thread() {
// Ignore map edit events, they will not need to be sent
// to anybody because the block hasn't been sent to anybody
MapEditEventAreaIgnorer
MapEditEventAreaIgnorer
ign(&m_server->m_ignore_map_edit_events_area,
VoxelArea(minp, maxp));
{ // takes about 90ms with -O1 on an e3-1230v2
try { // takes about 90ms with -O1 on an e3-1230v2
m_server->getScriptIface()->environment_OnGenerated(
minp, maxp, emerge->getBlockSeed(minp));
} catch(LuaError &e) {
m_server->setAsyncFatalError(e.what());
}
EMERGE_DBG_OUT("ended up with: " << analyze_block(block));
m_server->m_env->activateBlock(block, 0);
}
}
@@ -568,7 +599,7 @@ void *EmergeThread::Thread() {
err << "You can ignore this using [ignore_world_load_errors = true]."<<std::endl;
m_server->setAsyncFatalError(err.str());
}
END_DEBUG_EXCEPTION_HANDLER(errorstream)
log_deregister_thread();
return NULL;

View File

@@ -83,21 +83,23 @@ class EmergeManager : public IBackgroundBlockEmerger {
INodeDefManager *ndef;
std::map<std::string, MapgenFactory *> mglist;
std::vector<Mapgen *> mapgen;
std::vector<EmergeThread *> emergethread;
//settings
MapgenParams *params;
bool mapgen_debug_info;
u16 qlimit_total;
u16 qlimit_diskonly;
u16 qlimit_generate;
u32 gennotify;
MapgenParams *luaoverride_params;
u32 luaoverride_params_modified;
u32 luaoverride_flagmask;
//block emerge queue data structures
JMutex queuemutex;
std::map<v3s16, BlockEmergeData *> blocks_enqueued;
@@ -112,17 +114,18 @@ class EmergeManager : public IBackgroundBlockEmerger {
~EmergeManager();
void initMapgens(MapgenParams *mgparams);
MapgenParams *setMapgenType(MapgenParams *mgparams, std::string newname);
Mapgen *getCurrentMapgen();
Mapgen *createMapgen(std::string mgname, int mgid,
MapgenParams *mgparams);
MapgenParams *createMapgenParams(std::string mgname);
void triggerAllThreads();
void startAllThreads();
bool enqueueBlockEmerge(u16 peer_id, v3s16 p, bool allow_generate);
void registerMapgen(std::string name, MapgenFactory *mgfactory);
MapgenParams *getParamsFromSettings(Settings *settings);
void setParamsToSettings(Settings *settings);
//mapgen helper methods
Biome *getBiomeAtPoint(v3s16 p);
int getGroundLevelAtPoint(v2s16 p);

View File

@@ -354,7 +354,7 @@ ServerMap & ServerEnvironment::getServerMap()
return *m_map;
}
bool ServerEnvironment::line_of_sight(v3f pos1, v3f pos2, float stepsize)
bool ServerEnvironment::line_of_sight(v3f pos1, v3f pos2, float stepsize, v3s16 *p)
{
float distance = pos1.getDistanceFrom(pos2);
@@ -372,6 +372,9 @@ bool ServerEnvironment::line_of_sight(v3f pos1, v3f pos2, float stepsize)
MapNode n = getMap().getNodeNoEx(pos);
if(n.param0 != CONTENT_AIR) {
if (p) {
*p = pos;
}
return false;
}
}
@@ -874,6 +877,11 @@ bool ServerEnvironment::removeNode(v3s16 p)
return true;
}
bool ServerEnvironment::swapNode(v3s16 p, const MapNode &n)
{
return m_map->addNodeWithEvent(p, n, false);
}
std::set<u16> ServerEnvironment::getObjectsInsideRadius(v3f pos, float radius)
{
std::set<u16> objects;

View File

@@ -283,6 +283,7 @@ class ServerEnvironment : public Environment
// Script-aware node setters
bool setNode(v3s16 p, const MapNode &n);
bool removeNode(v3s16 p);
bool swapNode(v3s16 p, const MapNode &n);
// Find all active objects inside a radius around a point
std::set<u16> getObjectsInsideRadius(v3f pos, float radius);
@@ -294,7 +295,7 @@ class ServerEnvironment : public Environment
void step(f32 dtime);
//check if there's a line of sight between two positions
bool line_of_sight(v3f pos1, v3f pos2, float stepsize=1.0);
bool line_of_sight(v3f pos1, v3f pos2, float stepsize=1.0, v3s16 *p=NULL);
u32 getGameTime() { return m_game_time; }

View File

@@ -21,132 +21,99 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#define EXCEPTIONS_HEADER
#include <exception>
#include <string>
class BaseException : public std::exception
{
public:
BaseException(const char *s)
BaseException(const std::string s) throw()
{
m_s = s;
}
~BaseException() throw() {}
virtual const char * what() const throw()
{
return m_s;
return m_s.c_str();
}
const char *m_s;
protected:
std::string m_s;
};
class AsyncQueuedException : public BaseException
{
class AsyncQueuedException : public BaseException {
public:
AsyncQueuedException(const char *s):
BaseException(s)
{}
AsyncQueuedException(std::string s): BaseException(s) {}
};
class NotImplementedException : public BaseException
{
class NotImplementedException : public BaseException {
public:
NotImplementedException(const char *s):
BaseException(s)
{}
NotImplementedException(std::string s): BaseException(s) {}
};
class AlreadyExistsException : public BaseException
{
class AlreadyExistsException : public BaseException {
public:
AlreadyExistsException(const char *s):
BaseException(s)
{}
AlreadyExistsException(std::string s): BaseException(s) {}
};
class VersionMismatchException : public BaseException
{
class VersionMismatchException : public BaseException {
public:
VersionMismatchException(const char *s):
BaseException(s)
{}
VersionMismatchException(std::string s): BaseException(s) {}
};
class FileNotGoodException : public BaseException
{
class FileNotGoodException : public BaseException {
public:
FileNotGoodException(const char *s):
BaseException(s)
{}
FileNotGoodException(std::string s): BaseException(s) {}
};
class SerializationError : public BaseException
{
class SerializationError : public BaseException {
public:
SerializationError(const char *s):
BaseException(s)
{}
SerializationError(std::string s): BaseException(s) {}
};
class LoadError : public BaseException
{
class LoadError : public BaseException {
public:
LoadError(const char *s):
BaseException(s)
{}
LoadError(std::string s): BaseException(s) {}
};
class ContainerFullException : public BaseException
{
class ContainerFullException : public BaseException {
public:
ContainerFullException(const char *s):
BaseException(s)
{}
ContainerFullException(std::string s): BaseException(s) {}
};
class SettingNotFoundException : public BaseException
{
class SettingNotFoundException : public BaseException {
public:
SettingNotFoundException(const char *s):
BaseException(s)
{}
SettingNotFoundException(std::string s): BaseException(s) {}
};
class InvalidFilenameException : public BaseException
{
class InvalidFilenameException : public BaseException {
public:
InvalidFilenameException(const char *s):
BaseException(s)
{}
InvalidFilenameException(std::string s): BaseException(s) {}
};
class ProcessingLimitException : public BaseException
{
class ProcessingLimitException : public BaseException {
public:
ProcessingLimitException(const char *s):
BaseException(s)
{}
ProcessingLimitException(std::string s): BaseException(s) {}
};
class CommandLineError : public BaseException
{
class CommandLineError : public BaseException {
public:
CommandLineError(const char *s):
BaseException(s)
{}
CommandLineError(std::string s): BaseException(s) {}
};
class ItemNotFoundException : public BaseException
{
class ItemNotFoundException : public BaseException {
public:
ItemNotFoundException(const char *s):
BaseException(s)
{}
ItemNotFoundException(std::string s): BaseException(s) {}
};
class ServerError : public BaseException {
public:
ServerError(std::string s): BaseException(s) {}
};
// Only used on Windows (SEH)
class FatalSystemException : public BaseException
{
class FatalSystemException : public BaseException {
public:
FatalSystemException(const char *s):
BaseException(s)
{}
FatalSystemException(std::string s): BaseException(s) {}
};
/*
@@ -159,7 +126,7 @@ class InvalidPositionException : public BaseException
InvalidPositionException():
BaseException("Somebody tried to get/set something in a nonexistent position.")
{}
InvalidPositionException(const char *s):
InvalidPositionException(std::string s):
BaseException(s)
{}
};

View File

@@ -23,12 +23,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "clientserver.h"
#include "log.h"
#include "filesys.h"
#include "hex.h"
#include "sha1.h"
#include <string>
#include <iostream>
#include <fstream>
#include <sstream>
#include <stdlib.h>
bool FileCache::loadByPath(const std::string &path, std::ostream &os)
@@ -85,36 +82,8 @@ bool FileCache::update(const std::string &name, const std::string &data)
std::string path = m_dir + DIR_DELIM + name;
return updateByPath(path, data);
}
bool FileCache::update_sha1(const std::string &data)
{
SHA1 sha1;
sha1.addBytes(data.c_str(), data.size());
unsigned char *digest = sha1.getDigest();
std::string sha1_raw((char*)digest, 20);
free(digest);
std::string sha1_hex = hex_encode(sha1_raw);
return update(sha1_hex, data);
}
bool FileCache::load(const std::string &name, std::ostream &os)
{
std::string path = m_dir + DIR_DELIM + name;
return loadByPath(path, os);
}
bool FileCache::load_sha1(const std::string &sha1_raw, std::ostream &os)
{
std::ostringstream tmp_os(std::ios_base::binary);
if(!load(hex_encode(sha1_raw), tmp_os))
return false;
SHA1 sha1;
sha1.addBytes(tmp_os.str().c_str(), tmp_os.str().length());
unsigned char *digest = sha1.getDigest();
std::string sha1_real_raw((char*)digest, 20);
free(digest);
if(sha1_real_raw != sha1_raw){
verbosestream<<"FileCache["<<m_dir<<"]: filename "<<sha1_real_raw
<<" mismatches actual checksum"<<std::endl;
return false;
}
os<<tmp_os.str();
return true;
}

View File

@@ -36,9 +36,7 @@ class FileCache
}
bool update(const std::string &name, const std::string &data);
bool update_sha1(const std::string &data);
bool load(const std::string &name, std::ostream &os);
bool load_sha1(const std::string &sha1_raw, std::ostream &os);
private:
std::string m_dir;

View File

@@ -69,6 +69,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <iomanip>
#include <list>
#include "util/directiontables.h"
#include "util/pointedthing.h"
/*
Text input system
@@ -805,13 +806,26 @@ class GameGlobalShaderConstantSetter : public IShaderConstantSetter
float daynight_ratio_f = (float)daynight_ratio / 1000.0;
services->setPixelShaderConstant("dayNightRatio", &daynight_ratio_f, 1);
u32 animation_timer = porting::getTimeMs() % 100000;
float animation_timer_f = (float)animation_timer / 100000.0;
services->setPixelShaderConstant("animationTimer", &animation_timer_f, 1);
services->setVertexShaderConstant("animationTimer", &animation_timer_f, 1);
LocalPlayer* player = m_client->getEnv().getLocalPlayer();
v3f eye_position = player->getEyePosition();
services->setPixelShaderConstant("eyePosition", (irr::f32*)&eye_position, 3);
services->setVertexShaderConstant("eyePosition", (irr::f32*)&eye_position, 3);
// Normal map texture layer
int layer = 1;
int layer1 = 1;
int layer2 = 2;
// before 1.8 there isn't a "integer interface", only float
#if (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 8)
services->setPixelShaderConstant("normalTexture" , (irr::f32*)&layer, 1);
services->setPixelShaderConstant("normalTexture" , (irr::f32*)&layer1, 1);
services->setPixelShaderConstant("useNormalmap" , (irr::f32*)&layer2, 1);
#else
services->setPixelShaderConstant("normalTexture" , (irr::s32*)&layer, 1);
services->setPixelShaderConstant("normalTexture" , (irr::s32*)&layer1, 1);
services->setPixelShaderConstant("useNormalmap" , (irr::s32*)&layer2, 1);
#endif
}
};
@@ -1194,13 +1208,19 @@ void the_game(
server->step(dtime);
// End condition
if(client.texturesReceived() &&
if(client.mediaReceived() &&
client.itemdefReceived() &&
client.nodedefReceived()){
got_content = true;
break;
}
// Break conditions
if(client.accessDenied()){
error_message = L"Access denied. Reason: "
+client.accessDeniedReason();
errorstream<<wide_to_narrow(error_message)<<std::endl;
break;
}
if(!client.connectedAndInitialized()){
error_message = L"Client disconnected";
errorstream<<wide_to_narrow(error_message)<<std::endl;
@@ -1304,7 +1324,7 @@ void the_game(
*/
Sky *sky = NULL;
sky = new Sky(smgr->getRootSceneNode(), smgr, -1);
sky = new Sky(smgr->getRootSceneNode(), smgr, -1, client.getEnv().getLocalPlayer());
/*
A copy of the local inventory
@@ -1409,7 +1429,7 @@ void the_game(
bool invert_mouse = g_settings->getBool("invert_mouse");
bool respawn_menu_active = false;
bool update_wielded_item_trigger = false;
bool update_wielded_item_trigger = true;
bool show_hud = true;
bool show_chat = true;
@@ -1459,6 +1479,11 @@ void the_game(
bool use_weather = g_settings->getBool("weather");
core::stringw str = L"Minetest [";
str += driver->getName();
str += "]";
device->setWindowCaption(str.c_str());
for(;;)
{
if(device->run() == false || kill == true)
@@ -2277,10 +2302,6 @@ void the_game(
delete(event.show_formspec.formspec);
delete(event.show_formspec.formname);
}
else if(event.type == CE_TEXTURES_UPDATED)
{
update_wielded_item_trigger = true;
}
else if(event.type == CE_SPAWN_PARTICLE)
{
LocalPlayer* player = client.getEnv().getLocalPlayer();
@@ -2974,10 +2995,13 @@ void the_game(
scenetime_avg = scenetime_avg * 0.95 + (float)scenetime*0.05;
static float endscenetime_avg = 0;
endscenetime_avg = endscenetime_avg * 0.95 + (float)endscenetime*0.05;*/
u16 fps = (1.0/dtime_avg1);
std::ostringstream os(std::ios_base::binary);
os<<std::fixed
<<"Minetest "<<minetest_version_hash
<<" FPS = "<<fps
<<" (R: range_all="<<draw_control.range_all<<")"
<<std::setprecision(0)
<<" drawtime = "<<drawtime_avg
@@ -3366,21 +3390,6 @@ void the_game(
End of drawing
*/
static s16 lastFPS = 0;
//u16 fps = driver->getFPS();
u16 fps = (1.0/dtime_avg1);
if (lastFPS != fps)
{
core::stringw str = L"Minetest [";
str += driver->getName();
str += "] FPS=";
str += fps;
device->setWindowCaption(str.c_str());
lastFPS = fps;
}
/*
Log times and stuff for visualization
*/
@@ -3428,14 +3437,12 @@ void the_game(
L" running a different version of Minetest.";
errorstream<<wide_to_narrow(error_message)<<std::endl;
}
catch(ServerError &e)
{
catch(ServerError &e) {
error_message = narrow_to_wide(e.what());
errorstream<<wide_to_narrow(error_message)<<std::endl;
errorstream << "ServerError: " << e.what() << std::endl;
}
catch(ModError &e)
{
errorstream<<e.what()<<std::endl;
catch(ModError &e) {
errorstream << "ModError: " << e.what() << std::endl;
error_message = narrow_to_wide(e.what()) + wgettext("\nCheck debug.txt for details.");
}

View File

@@ -117,7 +117,8 @@ std::string gob_cmd_update_armor_groups(const ItemGroupList &armor_groups)
return os.str();
}
std::string gob_cmd_update_physics_override(float physics_override_speed, float physics_override_jump, float physics_override_gravity)
std::string gob_cmd_update_physics_override(float physics_override_speed, float physics_override_jump,
float physics_override_gravity, bool sneak, bool sneak_glitch)
{
std::ostringstream os(std::ios::binary);
// command
@@ -126,6 +127,9 @@ std::string gob_cmd_update_physics_override(float physics_override_speed, float
writeF1000(os, physics_override_speed);
writeF1000(os, physics_override_jump);
writeF1000(os, physics_override_gravity);
// these are sent inverted so we get true when the server sends nothing
writeU8(os, !sneak);
writeU8(os, !sneak_glitch);
return os.str();
}

View File

@@ -63,7 +63,8 @@ std::string gob_cmd_punched(s16 damage, s16 result_hp);
#include "itemgroup.h"
std::string gob_cmd_update_armor_groups(const ItemGroupList &armor_groups);
std::string gob_cmd_update_physics_override(float physics_override_speed, float physics_override_jump, float physics_override_gravity);
std::string gob_cmd_update_physics_override(float physics_override_speed,
float physics_override_jump, float physics_override_gravity, bool sneak, bool sneak_glitch);
std::string gob_cmd_update_animation(v2f frames, float frame_speed, float frame_blend);

View File

@@ -30,6 +30,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "sound.h"
#include "sound_openal.h"
#include "clouds.h"
#include "httpfetch.h"
#include <IGUIStaticText.h>
#include <ICameraSceneNode.h>
@@ -156,7 +157,7 @@ GUIEngine::GUIEngine( irr::IrrlichtDevice* dev,
m_sound_manager = &dummySoundManager;
//create topleft header
core::rect<s32> rect(0, 0, 500, 40);
core::rect<s32> rect(0, 0, 500, 20);
rect += v2s32(4, 0);
std::string t = std::string("Minetest ") + minetest_version_hash;
@@ -286,6 +287,8 @@ void GUIEngine::run()
cloudPostProcess();
else
sleep_ms(25);
m_script->Step();
}
}
@@ -505,51 +508,39 @@ bool GUIEngine::setTexture(texture_layer layer,std::string texturepath) {
}
/******************************************************************************/
#if USE_CURL
static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp)
{
FILE* targetfile = (FILE*) userp;
fwrite(contents,size,nmemb,targetfile);
return size * nmemb;
}
#endif
bool GUIEngine::downloadFile(std::string url,std::string target) {
#if USE_CURL
//download file via curl
CURL *curl;
bool retval = true;
curl = curl_easy_init();
FILE* targetfile = fopen(target.c_str(),"wb");
if (curl)
{
CURLcode res;
bool retval = true;
if (targetfile) {
HTTPFetchRequest fetchrequest;
HTTPFetchResult fetchresult;
fetchrequest.url = url;
fetchrequest.useragent = std::string("Minetest ")+minetest_version_hash;
fetchrequest.timeout = g_settings->getS32("curl_timeout");
fetchrequest.caller = HTTPFETCH_SYNC;
httpfetch_sync(fetchrequest,fetchresult);
FILE* targetfile = fopen(target.c_str(),"wb");
if (targetfile) {
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, targetfile);
curl_easy_setopt(curl, CURLOPT_USERAGENT, (std::string("Minetest ")+minetest_version_hash).c_str());
res = curl_easy_perform(curl);
if (res != CURLE_OK) {
errorstream << "File at url \"" << url
<<"\" not found (" << curl_easy_strerror(res) << ")" <<std::endl;
if (fetchresult.succeeded) {
if (fwrite(fetchresult.data.c_str(),1,fetchresult.data.size(),targetfile) != fetchresult.data.size()) {
retval = false;
}
fclose(targetfile);
}
else {
retval = false;
}
curl_easy_cleanup(curl);
return retval;
fclose(targetfile);
}
#endif
else {
retval = false;
}
return retval;
#else
return false;
#endif
}
/******************************************************************************/
@@ -576,3 +567,9 @@ void GUIEngine::stopSound(s32 handle)
{
m_sound_manager->stopSound(handle);
}
/******************************************************************************/
unsigned int GUIEngine::DoAsync(std::string serialized_fct,
std::string serialized_params) {
return m_script->DoAsync(serialized_fct,serialized_params);
}

View File

@@ -166,6 +166,9 @@ class GUIEngine {
return m_scriptdir;
}
/** pass async callback to scriptengine **/
unsigned int DoAsync(std::string serialized_fct,std::string serialized_params);
private:
/** find and run the main menu script */
@@ -244,7 +247,7 @@ class GUIEngine {
* @param url url to download
* @param target file to store to
*/
bool downloadFile(std::string url,std::string target);
static bool downloadFile(std::string url,std::string target);
/** array containing pointers to current specified texture layers */
video::ITexture* m_textures[TEX_LAYER_MAX];

View File

@@ -40,6 +40,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "log.h"
#include "tile.h" // ITextureSource
#include "hud.h" // drawItemStack
#include "hex.h"
#include "util/string.h"
#include "util/numeric.h"
#include "filesys.h"
@@ -421,6 +422,12 @@ void GUIFormSpecMenu::parseList(parserData* data,std::string element) {
s32 start_i = 0;
if(startindex != "")
start_i = stoi(startindex);
if (geom.X < 0 || geom.Y < 0 || start_i < 0) {
errorstream<< "Invalid list element: '" << element << "'" << std::endl;
return;
}
if(data->bp_set != 2)
errorstream<<"WARNING: invalid use of list without a size[] element"<<std::endl;
m_inventorylists.push_back(ListDrawSpec(loc, listname, pos, geom, start_i));
@@ -836,7 +843,7 @@ void GUIFormSpecMenu::parsePwdField(parserData* data,std::string element) {
Environment->setFocus(e);
}
if (label.length() > 1)
if (label.length() >= 1)
{
rect.UpperLeftCorner.Y -= 15;
rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + 15;
@@ -927,7 +934,7 @@ void GUIFormSpecMenu::parseSimpleField(parserData* data,std::vector<std::string>
evt.KeyInput.PressedDown = true;
e->OnEvent(evt);
if (label.length() > 1)
if (label.length() >= 1)
{
rect.UpperLeftCorner.Y -= 15;
rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + 15;
@@ -1019,7 +1026,7 @@ void GUIFormSpecMenu::parseTextArea(parserData* data,std::vector<std::string>& p
e->OnEvent(evt);
}
if (label.length() > 1)
if (label.length() >= 1)
{
rect.UpperLeftCorner.Y -= 15;
rect.LowerRightCorner.Y = rect.UpperLeftCorner.Y + 15;
@@ -1850,7 +1857,7 @@ void GUIFormSpecMenu::drawMenu()
v2u32 screenSize = driver->getScreenSize();
core::rect<s32> allbg(0, 0, screenSize.X , screenSize.Y);
if (m_bgfullscreen)
if (m_bgfullscreen)
driver->draw2DRectangle(m_bgcolor, allbg, &allbg);
else
driver->draw2DRectangle(m_bgcolor, AbsoluteRect, &AbsoluteClippingRect);
@@ -1953,7 +1960,7 @@ void GUIFormSpecMenu::drawMenu()
IItemDefManager *idef = m_gamedef->idef();
ItemStack item;
item.deSerialize(spec.name, idef);
video::ITexture *texture = idef->getInventoryTexture(item.getDefinition(idef).name, m_gamedef);
video::ITexture *texture = idef->getInventoryTexture(item.getDefinition(idef).name, m_gamedef);
// Image size on screen
core::rect<s32> imgrect(0, 0, spec.geom.X, spec.geom.Y);
// Image rectangle on screen
@@ -1992,7 +1999,7 @@ void GUIFormSpecMenu::drawMenu()
if (spec.tooltip != "")
{
core::rect<s32> rect = spec.rect;
if (rect.isPointInside(m_pointer))
if (rect.isPointInside(m_pointer))
{
m_tooltip_element->setVisible(true);
this->bringToFront(m_tooltip_element);
@@ -2162,7 +2169,7 @@ void GUIFormSpecMenu::acceptInput(bool quit=false)
for(u32 i=0; i<m_fields.size(); i++)
{
const FieldSpec &s = m_fields[i];
if(s.send)
if(s.send)
{
if(s.ftype == f_Button)
{
@@ -2188,8 +2195,11 @@ void GUIFormSpecMenu::acceptInput(bool quit=false)
if ((element) && (element->getType() == gui::EGUIET_COMBO_BOX)) {
e = static_cast<gui::IGUIComboBox*>(element);
}
fields[wide_to_narrow(s.fname.c_str())] =
wide_to_narrow(e->getItem(e->getSelected()));
s32 selected = e->getSelected();
if (selected >= 0) {
fields[wide_to_narrow(s.fname.c_str())] =
wide_to_narrow(e->getItem(selected));
}
}
else if (s.ftype == f_TabHeader) {
// no dynamic cast possible due to some distributions shipped
@@ -2462,12 +2472,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
move_amount = 0;
}
}
else if(getAbsoluteClippingRect().isPointInside(m_pointer))
{
// Clicked somewhere else: deselect
m_selected_amount = 0;
}
else
else if (!getAbsoluteClippingRect().isPointInside(m_pointer))
{
// Clicked outside of the window: drop
if(button == 1) // right
@@ -2661,7 +2666,7 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
for(u32 i=0; i<m_fields.size(); i++)
{
FieldSpec &s = m_fields[i];
// if its a button, set the send field so
// if its a button, set the send field so
// lua knows which button was pressed
if (((s.ftype == f_Button) || (s.ftype == f_CheckBox)) &&
(s.fid == event.GUIEvent.Caller->getID()))
@@ -2732,19 +2737,6 @@ bool GUIFormSpecMenu::OnEvent(const SEvent& event)
return Parent ? Parent->OnEvent(event) : false;
}
static inline bool hex_digit_decode(char hexdigit, unsigned char &value)
{
if(hexdigit >= '0' && hexdigit <= '9')
value = hexdigit - '0';
else if(hexdigit >= 'A' && hexdigit <= 'F')
value = hexdigit - 'A' + 10;
else if(hexdigit >= 'a' && hexdigit <= 'f')
value = hexdigit - 'a' + 10;
else
return false;
return true;
}
bool GUIFormSpecMenu::parseColor(std::string &value, video::SColor &color, bool quiet)
{
const char *hexpattern = NULL;

View File

@@ -46,4 +46,17 @@ static inline std::string hex_encode(const std::string &data)
return hex_encode(data.c_str(), data.size());
}
static inline bool hex_digit_decode(char hexdigit, unsigned char &value)
{
if(hexdigit >= '0' && hexdigit <= '9')
value = hexdigit - '0';
else if(hexdigit >= 'A' && hexdigit <= 'F')
value = hexdigit - 'A' + 10;
else if(hexdigit >= 'a' && hexdigit <= 'f')
value = hexdigit - 'a' + 10;
else
return false;
return true;
}
#endif

731
src/httpfetch.cpp Normal file
View File

@@ -0,0 +1,731 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
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.
*/
#include "socket.h" // for select()
#include "porting.h" // for sleep_ms()
#include "httpfetch.h"
#include <iostream>
#include <sstream>
#include <list>
#include <map>
#include <errno.h>
#include "jthread/jevent.h"
#include "config.h"
#include "exceptions.h"
#include "debug.h"
#include "log.h"
#include "util/container.h"
#include "util/thread.h"
JMutex g_httpfetch_mutex;
std::map<unsigned long, std::list<HTTPFetchResult> > g_httpfetch_results;
static void httpfetch_deliver_result(const HTTPFetchResult &fetchresult)
{
unsigned long caller = fetchresult.caller;
if (caller != HTTPFETCH_DISCARD) {
JMutexAutoLock lock(g_httpfetch_mutex);
g_httpfetch_results[caller].push_back(fetchresult);
}
}
static void httpfetch_request_clear(unsigned long caller);
unsigned long httpfetch_caller_alloc()
{
JMutexAutoLock lock(g_httpfetch_mutex);
// Check each caller ID except HTTPFETCH_DISCARD
const unsigned long discard = HTTPFETCH_DISCARD;
for (unsigned long caller = discard + 1; caller != discard; ++caller) {
std::map<unsigned long, std::list<HTTPFetchResult> >::iterator
it = g_httpfetch_results.find(caller);
if (it == g_httpfetch_results.end()) {
verbosestream<<"httpfetch_caller_alloc: allocating "
<<caller<<std::endl;
// Access element to create it
g_httpfetch_results[caller];
return caller;
}
}
assert("httpfetch_caller_alloc: ran out of caller IDs" == 0);
return discard;
}
void httpfetch_caller_free(unsigned long caller)
{
verbosestream<<"httpfetch_caller_free: freeing "
<<caller<<std::endl;
httpfetch_request_clear(caller);
if (caller != HTTPFETCH_DISCARD) {
JMutexAutoLock lock(g_httpfetch_mutex);
g_httpfetch_results.erase(caller);
}
}
bool httpfetch_async_get(unsigned long caller, HTTPFetchResult &fetchresult)
{
JMutexAutoLock lock(g_httpfetch_mutex);
// Check that caller exists
std::map<unsigned long, std::list<HTTPFetchResult> >::iterator
it = g_httpfetch_results.find(caller);
if (it == g_httpfetch_results.end())
return false;
// Check that result queue is nonempty
std::list<HTTPFetchResult> &callerresults = it->second;
if (callerresults.empty())
return false;
// Pop first result
fetchresult = callerresults.front();
callerresults.pop_front();
return true;
}
#if USE_CURL
#include <curl/curl.h>
/*
USE_CURL is on: use cURL based httpfetch implementation
*/
static size_t httpfetch_writefunction(
char *ptr, size_t size, size_t nmemb, void *userdata)
{
std::ostringstream *stream = (std::ostringstream*)userdata;
size_t count = size * nmemb;
stream->write(ptr, count);
return count;
}
static size_t httpfetch_discardfunction(
char *ptr, size_t size, size_t nmemb, void *userdata)
{
return size * nmemb;
}
class CurlHandlePool
{
std::list<CURL*> handles;
public:
CurlHandlePool() {}
~CurlHandlePool()
{
for (std::list<CURL*>::iterator it = handles.begin();
it != handles.end(); ++it) {
curl_easy_cleanup(*it);
}
}
CURL * alloc()
{
CURL *curl;
if (handles.empty()) {
curl = curl_easy_init();
if (curl == NULL) {
errorstream<<"curl_easy_init returned NULL"<<std::endl;
}
}
else {
curl = handles.front();
handles.pop_front();
}
return curl;
}
void free(CURL *handle)
{
if (handle)
handles.push_back(handle);
}
};
struct HTTPFetchOngoing
{
CurlHandlePool *pool;
CURL *curl;
CURLM *multi;
HTTPFetchRequest request;
HTTPFetchResult result;
std::ostringstream oss;
char *post_fields;
struct curl_slist *httpheader;
HTTPFetchOngoing(HTTPFetchRequest request_, CurlHandlePool *pool_):
pool(pool_),
curl(NULL),
multi(NULL),
request(request_),
result(request_),
oss(std::ios::binary),
httpheader(NULL)
{
curl = pool->alloc();
if (curl != NULL) {
// Set static cURL options
curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 1);
#if LIBCURL_VERSION_NUM >= 0x071304
// Restrict protocols so that curl vulnerabilities in
// other protocols don't affect us.
// These settings were introduced in curl 7.19.4.
long protocols =
CURLPROTO_HTTP |
CURLPROTO_HTTPS |
CURLPROTO_FTP |
CURLPROTO_FTPS;
curl_easy_setopt(curl, CURLOPT_PROTOCOLS, protocols);
curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS, protocols);
#endif
// Set cURL options based on HTTPFetchRequest
curl_easy_setopt(curl, CURLOPT_URL,
request.url.c_str());
curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS,
request.timeout);
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS,
request.connect_timeout);
if (request.useragent != "")
curl_easy_setopt(curl, CURLOPT_USERAGENT, request.useragent.c_str());
// Set up a write callback that writes to the
// ostringstream ongoing->oss, unless the data
// is to be discarded
if (request.caller == HTTPFETCH_DISCARD) {
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION,
httpfetch_discardfunction);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, NULL);
}
else {
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION,
httpfetch_writefunction);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &oss);
}
// Set POST (or GET) data
if (request.post_fields.empty()) {
curl_easy_setopt(curl, CURLOPT_HTTPGET, 1);
}
else {
curl_easy_setopt(curl, CURLOPT_POST, 1);
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE,
request.post_fields.size());
curl_easy_setopt(curl, CURLOPT_POSTFIELDS,
request.post_fields.c_str());
// request.post_fields must now *never* be
// modified until CURLOPT_POSTFIELDS is cleared
}
// Set additional HTTP headers
for (size_t i = 0; i < request.extra_headers.size(); ++i) {
httpheader = curl_slist_append(
httpheader,
request.extra_headers[i].c_str());
}
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, httpheader);
}
}
CURLcode start(CURLM *multi_)
{
if (curl == NULL)
return CURLE_FAILED_INIT;
if (multi_) {
// Multi interface (async)
CURLMcode mres = curl_multi_add_handle(multi_, curl);
if (mres != CURLM_OK) {
errorstream<<"curl_multi_add_handle"
<<" returned error code "<<mres
<<std::endl;
return CURLE_FAILED_INIT;
}
multi = multi_; // store for curl_multi_remove_handle
return CURLE_OK;
}
else {
// Easy interface (sync)
return curl_easy_perform(curl);
}
}
void complete(CURLcode res)
{
result.succeeded = (res == CURLE_OK);
result.timeout = (res == CURLE_OPERATION_TIMEDOUT);
result.data = oss.str();
// Get HTTP/FTP response code
result.response_code = 0;
if (curl != NULL) {
if (curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE,
&result.response_code) != CURLE_OK) {
//we failed to get a return code make sure it is still 0
result.response_code = 0;
}
}
if (res != CURLE_OK) {
infostream<<request.url<<" not found ("
<<curl_easy_strerror(res)<<")"
<<" (response code "<<result.response_code<<")"
<<std::endl;
}
}
~HTTPFetchOngoing()
{
if (multi != NULL) {
CURLMcode mres = curl_multi_remove_handle(multi, curl);
if (mres != CURLM_OK) {
errorstream<<"curl_multi_remove_handle"
<<" returned error code "<<mres
<<std::endl;
}
}
// Set safe options for the reusable cURL handle
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION,
httpfetch_discardfunction);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, NULL);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, NULL);
if (httpheader != NULL) {
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, NULL);
curl_slist_free_all(httpheader);
}
// Store the cURL handle for reuse
pool->free(curl);
}
};
class CurlFetchThread : public JThread
{
protected:
enum RequestType {
RT_FETCH,
RT_CLEAR,
RT_WAKEUP,
};
struct Request {
RequestType type;
HTTPFetchRequest fetchrequest;
Event *event;
};
CURLM *m_multi;
MutexedQueue<Request> m_requests;
size_t m_parallel_limit;
// Variables exclusively used within thread
std::vector<HTTPFetchOngoing*> m_all_ongoing;
std::list<HTTPFetchRequest> m_queued_fetches;
public:
CurlFetchThread(int parallel_limit)
{
if (parallel_limit >= 1)
m_parallel_limit = parallel_limit;
else
m_parallel_limit = 1;
}
void requestFetch(const HTTPFetchRequest &fetchrequest)
{
Request req;
req.type = RT_FETCH;
req.fetchrequest = fetchrequest;
req.event = NULL;
m_requests.push_back(req);
}
void requestClear(unsigned long caller, Event *event)
{
Request req;
req.type = RT_CLEAR;
req.fetchrequest.caller = caller;
req.event = event;
m_requests.push_back(req);
}
void requestWakeUp()
{
Request req;
req.type = RT_WAKEUP;
req.event = NULL;
m_requests.push_back(req);
}
protected:
// Handle a request from some other thread
// E.g. new fetch; clear fetches for one caller; wake up
void processRequest(const Request &req)
{
if (req.type == RT_FETCH) {
// New fetch, queue until there are less
// than m_parallel_limit ongoing fetches
m_queued_fetches.push_back(req.fetchrequest);
// see processQueued() for what happens next
}
else if (req.type == RT_CLEAR) {
unsigned long caller = req.fetchrequest.caller;
// Abort all ongoing fetches for the caller
for (std::vector<HTTPFetchOngoing*>::iterator
it = m_all_ongoing.begin();
it != m_all_ongoing.end();) {
if ((*it)->request.caller == caller) {
delete (*it);
it = m_all_ongoing.erase(it);
}
else
++it;
}
// Also abort all queued fetches for the caller
for (std::list<HTTPFetchRequest>::iterator
it = m_queued_fetches.begin();
it != m_queued_fetches.end();) {
if ((*it).caller == caller)
it = m_queued_fetches.erase(it);
else
++it;
}
}
else if (req.type == RT_WAKEUP) {
// Wakeup: Nothing to do, thread is awake at this point
}
if (req.event != NULL)
req.event->signal();
}
// Start new ongoing fetches if m_parallel_limit allows
void processQueued(CurlHandlePool *pool)
{
while (m_all_ongoing.size() < m_parallel_limit &&
!m_queued_fetches.empty()) {
HTTPFetchRequest request = m_queued_fetches.front();
m_queued_fetches.pop_front();
// Create ongoing fetch data and make a cURL handle
// Set cURL options based on HTTPFetchRequest
HTTPFetchOngoing *ongoing =
new HTTPFetchOngoing(request, pool);
// Initiate the connection (curl_multi_add_handle)
CURLcode res = ongoing->start(m_multi);
if (res == CURLE_OK) {
m_all_ongoing.push_back(ongoing);
}
else {
ongoing->complete(res);
httpfetch_deliver_result(ongoing->result);
delete ongoing;
}
}
}
// Process CURLMsg (indicates completion of a fetch)
void processCurlMessage(CURLMsg *msg)
{
// Determine which ongoing fetch the message pertains to
size_t i = 0;
bool found = false;
for (i = 0; i < m_all_ongoing.size(); ++i) {
if (m_all_ongoing[i]->curl == msg->easy_handle) {
found = true;
break;
}
}
if (msg->msg == CURLMSG_DONE && found) {
// m_all_ongoing[i] succeeded or failed.
HTTPFetchOngoing *ongoing = m_all_ongoing[i];
ongoing->complete(msg->data.result);
httpfetch_deliver_result(ongoing->result);
delete ongoing;
m_all_ongoing.erase(m_all_ongoing.begin() + i);
}
}
// Wait for a request from another thread, or timeout elapses
void waitForRequest(long timeout)
{
if (m_queued_fetches.empty()) {
try {
Request req = m_requests.pop_front(timeout);
processRequest(req);
}
catch (ItemNotFoundException &e) {}
}
}
// Wait until some IO happens, or timeout elapses
void waitForIO(long timeout)
{
fd_set read_fd_set;
fd_set write_fd_set;
fd_set exc_fd_set;
int max_fd;
long select_timeout = -1;
struct timeval select_tv;
CURLMcode mres;
FD_ZERO(&read_fd_set);
FD_ZERO(&write_fd_set);
FD_ZERO(&exc_fd_set);
mres = curl_multi_fdset(m_multi, &read_fd_set,
&write_fd_set, &exc_fd_set, &max_fd);
if (mres != CURLM_OK) {
errorstream<<"curl_multi_fdset"
<<" returned error code "<<mres
<<std::endl;
select_timeout = 0;
}
mres = curl_multi_timeout(m_multi, &select_timeout);
if (mres != CURLM_OK) {
errorstream<<"curl_multi_timeout"
<<" returned error code "<<mres
<<std::endl;
select_timeout = 0;
}
// Limit timeout so new requests get through
if (select_timeout < 0 || select_timeout > timeout)
select_timeout = timeout;
if (select_timeout > 0) {
// in Winsock it is forbidden to pass three empty
// fd_sets to select(), so in that case use sleep_ms
if (max_fd == -1) {
select_tv.tv_sec = select_timeout / 1000;
select_tv.tv_usec = (select_timeout % 1000) * 1000;
int retval = select(max_fd + 1, &read_fd_set,
&write_fd_set, &exc_fd_set,
&select_tv);
if (retval == -1) {
#ifdef _WIN32
errorstream<<"select returned error code "
<<WSAGetLastError()<<std::endl;
#else
errorstream<<"select returned error code "
<<errno<<std::endl;
#endif
}
}
else {
sleep_ms(select_timeout);
}
}
}
void * Thread()
{
ThreadStarted();
log_register_thread("CurlFetchThread");
DSTACK(__FUNCTION_NAME);
CurlHandlePool pool;
m_multi = curl_multi_init();
if (m_multi == NULL) {
errorstream<<"curl_multi_init returned NULL\n";
return NULL;
}
assert(m_all_ongoing.empty());
while (!StopRequested()) {
BEGIN_DEBUG_EXCEPTION_HANDLER
/*
Handle new async requests
*/
while (!m_requests.empty()) {
Request req = m_requests.pop_front();
processRequest(req);
}
processQueued(&pool);
/*
Handle ongoing async requests
*/
int still_ongoing = 0;
while (curl_multi_perform(m_multi, &still_ongoing) ==
CURLM_CALL_MULTI_PERFORM)
/* noop */;
/*
Handle completed async requests
*/
if (still_ongoing < (int) m_all_ongoing.size()) {
CURLMsg *msg;
int msgs_in_queue;
msg = curl_multi_info_read(m_multi, &msgs_in_queue);
while (msg != NULL) {
processCurlMessage(msg);
msg = curl_multi_info_read(m_multi, &msgs_in_queue);
}
}
/*
If there are ongoing requests, wait for data
(with a timeout of 100ms so that new requests
can be processed).
If no ongoing requests, wait for a new request.
(Possibly an empty request that signals
that the thread should be stopped.)
*/
if (m_all_ongoing.empty())
waitForRequest(100000000);
else
waitForIO(100);
END_DEBUG_EXCEPTION_HANDLER(errorstream)
}
// Call curl_multi_remove_handle and cleanup easy handles
for (size_t i = 0; i < m_all_ongoing.size(); ++i) {
delete m_all_ongoing[i];
}
m_all_ongoing.clear();
m_queued_fetches.clear();
CURLMcode mres = curl_multi_cleanup(m_multi);
if (mres != CURLM_OK) {
errorstream<<"curl_multi_cleanup"
<<" returned error code "<<mres
<<std::endl;
}
return NULL;
}
};
CurlFetchThread *g_httpfetch_thread = NULL;
void httpfetch_init(int parallel_limit)
{
verbosestream<<"httpfetch_init: parallel_limit="<<parallel_limit
<<std::endl;
CURLcode res = curl_global_init(CURL_GLOBAL_DEFAULT);
assert(res == CURLE_OK);
g_httpfetch_thread = new CurlFetchThread(parallel_limit);
}
void httpfetch_cleanup()
{
verbosestream<<"httpfetch_cleanup: cleaning up"<<std::endl;
g_httpfetch_thread->Stop();
g_httpfetch_thread->requestWakeUp();
g_httpfetch_thread->Wait();
delete g_httpfetch_thread;
curl_global_cleanup();
}
void httpfetch_async(const HTTPFetchRequest &fetchrequest)
{
g_httpfetch_thread->requestFetch(fetchrequest);
if (!g_httpfetch_thread->IsRunning())
g_httpfetch_thread->Start();
}
static void httpfetch_request_clear(unsigned long caller)
{
if (g_httpfetch_thread->IsRunning()) {
Event event;
g_httpfetch_thread->requestClear(caller, &event);
event.wait();
}
else {
g_httpfetch_thread->requestClear(caller, NULL);
}
}
void httpfetch_sync(const HTTPFetchRequest &fetchrequest,
HTTPFetchResult &fetchresult)
{
// Create ongoing fetch data and make a cURL handle
// Set cURL options based on HTTPFetchRequest
CurlHandlePool pool;
HTTPFetchOngoing ongoing(fetchrequest, &pool);
// Do the fetch (curl_easy_perform)
CURLcode res = ongoing.start(NULL);
// Update fetchresult
ongoing.complete(res);
fetchresult = ongoing.result;
}
#else // USE_CURL
/*
USE_CURL is off:
Dummy httpfetch implementation that always returns an error.
*/
void httpfetch_init(int parallel_limit)
{
}
void httpfetch_cleanup()
{
}
void httpfetch_async(const HTTPFetchRequest &fetchrequest)
{
errorstream<<"httpfetch_async: unable to fetch "<<fetchrequest.url
<<" because USE_CURL=0"<<std::endl;
HTTPFetchResult fetchresult(fetchrequest); // sets succeeded = false etc.
httpfetch_deliver_result(fetchresult);
}
static void httpfetch_request_clear(unsigned long caller)
{
}
void httpfetch_sync(const HTTPFetchRequest &fetchrequest,
HTTPFetchResult &fetchresult)
{
errorstream<<"httpfetch_sync: unable to fetch "<<fetchrequest.url
<<" because USE_CURL=0"<<std::endl;
fetchresult = HTTPFetchResult(fetchrequest); // sets succeeded = false etc.
}
#endif // USE_CURL

131
src/httpfetch.h Normal file
View File

@@ -0,0 +1,131 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
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.
*/
#ifndef HTTPFETCH_HEADER
#define HTTPFETCH_HEADER
#include <string>
#include <vector>
#include "config.h"
// Can be used in place of "caller" in asynchronous transfers to discard result
// (used as default value of "caller")
#define HTTPFETCH_DISCARD 0
#define HTTPFETCH_SYNC 1
struct HTTPFetchRequest
{
std::string url;
// Identifies the caller (for asynchronous requests)
// Ignored by httpfetch_sync
unsigned long caller;
// Some number that identifies the request
// (when the same caller issues multiple httpfetch_async calls)
unsigned long request_id;
// Timeout for the whole transfer, in milliseconds
long timeout;
// Timeout for the connection phase, in milliseconds
long connect_timeout;
// POST data (should be application/x-www-form-urlencoded
// unless a Content-Type header is specified in extra_headers)
// If this is empty a GET request is done instead.
std::string post_fields;
// If not empty, should contain entries such as "Accept: text/html"
std::vector<std::string> extra_headers;
//useragent to use
std::string useragent;
HTTPFetchRequest()
{
url = "";
caller = HTTPFETCH_DISCARD;
request_id = 0;
timeout = 0;
connect_timeout = 0;
}
};
struct HTTPFetchResult
{
bool succeeded;
bool timeout;
long response_code;
std::string data;
// The caller and request_id from the corresponding HTTPFetchRequest.
unsigned long caller;
unsigned long request_id;
HTTPFetchResult()
{
succeeded = false;
timeout = false;
response_code = 0;
data = "";
caller = HTTPFETCH_DISCARD;
request_id = 0;
}
HTTPFetchResult(const HTTPFetchRequest &fetchrequest)
{
succeeded = false;
timeout = false;
response_code = 0;
data = "";
caller = fetchrequest.caller;
request_id = fetchrequest.request_id;
}
};
// Initializes the httpfetch module
void httpfetch_init(int parallel_limit);
// Stops the httpfetch thread and cleans up resources
void httpfetch_cleanup();
// Starts an asynchronous HTTP fetch request
void httpfetch_async(const HTTPFetchRequest &fetchrequest);
// If any fetch for the given caller ID is complete, removes it from the
// result queue, sets fetchresult and returns true. Otherwise returns false.
bool httpfetch_async_get(unsigned long caller, HTTPFetchResult &fetchresult);
// Allocates a caller ID for httpfetch_async
// Not required if you want to set caller = HTTPFETCH_DISCARD
unsigned long httpfetch_caller_alloc();
// Frees a caller ID allocated with httpfetch_caller_alloc
// Note: This can be expensive, because the httpfetch thread is told
// to stop any ongoing fetches for the given caller.
void httpfetch_caller_free(unsigned long caller);
// Performs a synchronous HTTP request. This blocks and therefore should
// only be used from background threads.
void httpfetch_sync(const HTTPFetchRequest &fetchrequest,
HTTPFetchResult &fetchresult);
#endif // !HTTPFETCH_HEADER

View File

@@ -390,16 +390,18 @@ class CItemDefManager: public IWritableItemDefManager
/*
Make a mesh from the node
*/
bool reenable_shaders = false;
if(g_settings->getBool("enable_shaders")){
reenable_shaders = true;
g_settings->setBool("enable_shaders",false);
}
MeshMakeData mesh_make_data(gamedef);
MapNode mesh_make_node(id, param1, 0);
mesh_make_data.fillSingleNode(&mesh_make_node);
MapBlockMesh mapblock_mesh(&mesh_make_data);
scene::IMesh *node_mesh = mapblock_mesh.getMesh();
assert(node_mesh);
video::SColor c(255, 255, 255, 255);
if(g_settings->getBool("enable_shaders"))
c = MapBlock_LightColor(255, 0xffff, decode_light(f.light_source));
setMeshColor(node_mesh, c);
/*
@@ -455,6 +457,9 @@ class CItemDefManager: public IWritableItemDefManager
//no way reference count can be smaller than 2 in this place!
assert(cc->wield_mesh->getReferenceCount() >= 2);
if (reenable_shaders)
g_settings->setBool("enable_shaders",true);
}
// Put in cache

View File

@@ -2,10 +2,14 @@ if( UNIX )
set(JTHREAD_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/pthread/jmutex.cpp
${CMAKE_CURRENT_SOURCE_DIR}/pthread/jthread.cpp
${CMAKE_CURRENT_SOURCE_DIR}/pthread/jsemaphore.cpp
${CMAKE_CURRENT_SOURCE_DIR}/pthread/jevent.cpp
PARENT_SCOPE)
else( UNIX )
set(JTHREAD_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/win32/jmutex.cpp
${CMAKE_CURRENT_SOURCE_DIR}/win32/jthread.cpp
${CMAKE_CURRENT_SOURCE_DIR}/win32/jsemaphore.cpp
${CMAKE_CURRENT_SOURCE_DIR}/win32/jevent.cpp
PARENT_SCOPE)
endif( UNIX )

52
src/jthread/jevent.h Normal file
View File

@@ -0,0 +1,52 @@
/*
This file is a part of the JThread package, which contains some object-
oriented thread wrappers for different thread implementations.
Copyright (c) 2000-2006 Jori Liesenborgs (jori.liesenborgs@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
#ifndef JEVENT_H_
#define JEVENT_H_
#ifdef _WIN32
#include <windows.h>
#else
#include <semaphore.h>
#endif
class Event {
#ifdef _WIN32
HANDLE hEvent;
#else
sem_t sem;
#endif
public:
Event();
~Event();
void wait();
void signal();
};
#endif /* JEVENT_H_ */

View File

@@ -53,10 +53,8 @@ class JMutex
public:
JMutex();
~JMutex();
int Init();
int Lock();
int Unlock();
bool IsInitialized() { return initialized; }
private:
#if (defined(WIN32) || defined(_WIN32_WCE))
@@ -76,57 +74,6 @@ class JMutex
return false;
}
#endif // WIN32
bool initialized;
};
#ifdef _WIN32
class Event {
HANDLE hEvent;
public:
Event() {
hEvent = CreateEvent(NULL, 0, 0, NULL);
}
~Event() {
CloseHandle(hEvent);
}
void wait() {
WaitForSingleObject(hEvent, INFINITE);
}
void signal() {
SetEvent(hEvent);
}
};
#else
#include <semaphore.h>
class Event {
sem_t sem;
public:
Event() {
sem_init(&sem, 0, 0);
}
~Event() {
sem_destroy(&sem);
}
void wait() {
sem_wait(&sem);
}
void signal() {
sem_post(&sem);
}
};
#endif
#endif // JMUTEX_H

50
src/jthread/jsemaphore.h Normal file
View File

@@ -0,0 +1,50 @@
/*
Minetest
Copyright (C) 2013 sapier, < sapier AT gmx DOT net >
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.
*/
#ifndef JSEMAPHORE_H_
#define JSEMAPHORE_H_
#if defined(WIN32)
#include <windows.h>
#define MAX_SEMAPHORE_COUNT 1024
#else
#include <pthread.h>
#include <semaphore.h>
#endif
class JSemaphore {
public:
JSemaphore();
~JSemaphore();
JSemaphore(int initval);
void Post();
void Wait();
int GetValue();
private:
#if defined(WIN32)
HANDLE m_hSemaphore;
#else
sem_t m_semaphore;
#endif
};
#endif /* JSEMAPHORE_H_ */

View File

@@ -43,11 +43,20 @@ class JThread
JThread();
virtual ~JThread();
int Start();
void Stop();
int Kill();
virtual void *Thread() = 0;
bool IsRunning();
bool StopRequested();
void *GetReturnValue();
bool IsSameThread();
/*
* Wait for thread to finish
* Note: this does not stop a thread you have to do this on your own
* WARNING: never ever call this on a thread not started or already killed!
*/
void Wait();
protected:
void ThreadStarted();
private:
@@ -63,15 +72,17 @@ class JThread
HANDLE threadhandle;
#else // pthread type threads
static void *TheThread(void *param);
pthread_t threadid;
bool started;
#endif // WIN32
void *retval;
bool running;
bool requeststop;
JMutex runningmutex;
JMutex continuemutex,continuemutex2;
bool mutexinit;
};
#endif // JTHREAD_H

View File

@@ -0,0 +1,54 @@
/*
This file is a part of the JThread package, which contains some object-
oriented thread wrappers for different thread implementations.
Copyright (c) 2000-2006 Jori Liesenborgs (jori.liesenborgs@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
#include <assert.h>
#include "jthread/jevent.h"
#define UNUSED(expr) do { (void)(expr); } while (0)
Event::Event() {
int sem_init_retval = sem_init(&sem, 0, 0);
assert(sem_init_retval == 0);
UNUSED(sem_init_retval);
}
Event::~Event() {
int sem_destroy_retval = sem_destroy(&sem);
assert(sem_destroy_retval == 0);
UNUSED(sem_destroy_retval);
}
void Event::wait() {
int sem_wait_retval = sem_wait(&sem);
assert(sem_wait_retval == 0);
UNUSED(sem_wait_retval);
}
void Event::signal() {
int sem_post_retval = sem_post(&sem);
assert(sem_post_retval == 0);
UNUSED(sem_post_retval);
}

View File

@@ -24,44 +24,35 @@
DEALINGS IN THE SOFTWARE.
*/
#include <assert.h>
#include "jthread/jmutex.h"
#define UNUSED(expr) do { (void)(expr); } while (0)
JMutex::JMutex()
{
initialized = false;
int mutex_init_retval = pthread_mutex_init(&mutex,NULL);
assert( mutex_init_retval == 0 );
UNUSED(mutex_init_retval);
}
JMutex::~JMutex()
{
if (initialized)
pthread_mutex_destroy(&mutex);
}
int JMutex::Init()
{
if (initialized)
return ERR_JMUTEX_ALREADYINIT;
pthread_mutex_init(&mutex,NULL);
initialized = true;
return 0;
int mutex_dextroy_retval = pthread_mutex_destroy(&mutex);
assert( mutex_dextroy_retval == 0 );
UNUSED(mutex_dextroy_retval);
}
int JMutex::Lock()
{
if (!initialized)
return ERR_JMUTEX_NOTINIT;
pthread_mutex_lock(&mutex);
return 0;
int mutex_lock_retval = pthread_mutex_lock(&mutex);
assert( mutex_lock_retval == 0 );
return mutex_lock_retval;
UNUSED(mutex_lock_retval);
}
int JMutex::Unlock()
{
if (!initialized)
return ERR_JMUTEX_NOTINIT;
pthread_mutex_unlock(&mutex);
return 0;
int mutex_unlock_retval = pthread_mutex_unlock(&mutex);
assert( mutex_unlock_retval == 0 );
return mutex_unlock_retval;
UNUSED(mutex_unlock_retval);
}

View File

@@ -0,0 +1,59 @@
/*
Minetest
Copyright (C) 2013 sapier, < sapier AT gmx DOT net >
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.
*/
#include <assert.h>
#include "jthread/jsemaphore.h"
#define UNUSED(expr) do { (void)(expr); } while (0)
JSemaphore::JSemaphore() {
int sem_init_retval = sem_init(&m_semaphore,0,0);
assert(sem_init_retval == 0);
UNUSED(sem_init_retval);
}
JSemaphore::~JSemaphore() {
int sem_destroy_retval = sem_destroy(&m_semaphore);
assert(sem_destroy_retval == 0);
UNUSED(sem_destroy_retval);
}
JSemaphore::JSemaphore(int initval) {
int sem_init_retval = sem_init(&m_semaphore,0,initval);
assert(sem_init_retval == 0);
UNUSED(sem_init_retval);
}
void JSemaphore::Post() {
int sem_post_retval = sem_post(&m_semaphore);
assert(sem_post_retval == 0);
UNUSED(sem_post_retval);
}
void JSemaphore::Wait() {
int sem_wait_retval = sem_wait(&m_semaphore);
assert(sem_wait_retval == 0);
UNUSED(sem_wait_retval);
}
int JSemaphore::GetValue() {
int retval = 0;
sem_getvalue(&m_semaphore,&retval);
return retval;
}

View File

@@ -26,15 +26,19 @@
*/
#include "jthread/jthread.h"
#include <assert.h>
#include <sys/time.h>
#include <time.h>
#include <stdlib.h>
#define UNUSED(expr) do { (void)(expr); } while (0)
JThread::JThread()
{
retval = NULL;
mutexinit = false;
requeststop = false;
running = false;
started = false;
}
JThread::~JThread()
@@ -42,58 +46,59 @@ JThread::~JThread()
Kill();
}
void JThread::Stop() {
runningmutex.Lock();
requeststop = true;
runningmutex.Unlock();
}
void JThread::Wait() {
void* status;
runningmutex.Lock();
if (started) {
runningmutex.Unlock();
int pthread_join_retval = pthread_join(threadid,&status);
assert(pthread_join_retval == 0);
UNUSED(pthread_join_retval);
runningmutex.Lock();
started = false;
}
runningmutex.Unlock();
}
int JThread::Start()
{
int status;
if (!mutexinit)
{
if (!runningmutex.IsInitialized())
{
if (runningmutex.Init() < 0)
return ERR_JTHREAD_CANTINITMUTEX;
}
if (!continuemutex.IsInitialized())
{
if (continuemutex.Init() < 0)
return ERR_JTHREAD_CANTINITMUTEX;
}
if (!continuemutex2.IsInitialized())
{
if (continuemutex2.Init() < 0)
return ERR_JTHREAD_CANTINITMUTEX;
}
mutexinit = true;
}
runningmutex.Lock();
if (running)
{
runningmutex.Unlock();
return ERR_JTHREAD_ALREADYRUNNING;
}
requeststop = false;
runningmutex.Unlock();
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
//pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
continuemutex.Lock();
status = pthread_create(&threadid,&attr,TheThread,this);
status = pthread_create(&threadid,&attr,TheThread,this);
pthread_attr_destroy(&attr);
if (status != 0)
{
continuemutex.Unlock();
return ERR_JTHREAD_CANTSTARTTHREAD;
}
/* Wait until 'running' is set */
runningmutex.Lock();
runningmutex.Lock();
while (!running)
{
runningmutex.Unlock();
struct timespec req,rem;
req.tv_sec = 0;
@@ -102,10 +107,11 @@ int JThread::Start()
runningmutex.Lock();
}
started = true;
runningmutex.Unlock();
continuemutex.Unlock();
continuemutex2.Lock();
continuemutex2.Unlock();
return 0;
@@ -113,13 +119,30 @@ int JThread::Start()
int JThread::Kill()
{
runningmutex.Lock();
void* status;
runningmutex.Lock();
if (!running)
{
if (started) {
runningmutex.Unlock();
int pthread_join_retval = pthread_join(threadid,&status);
assert(pthread_join_retval == 0);
UNUSED(pthread_join_retval);
runningmutex.Lock();
started = false;
}
runningmutex.Unlock();
return ERR_JTHREAD_NOTRUNNING;
}
pthread_cancel(threadid);
if (started) {
runningmutex.Unlock();
int pthread_join_retval = pthread_join(threadid,&status);
assert(pthread_join_retval == 0);
UNUSED(pthread_join_retval);
runningmutex.Lock();
started = false;
}
running = false;
runningmutex.Unlock();
return 0;
@@ -128,17 +151,26 @@ int JThread::Kill()
bool JThread::IsRunning()
{
bool r;
runningmutex.Lock();
runningmutex.Lock();
r = running;
runningmutex.Unlock();
return r;
}
bool JThread::StopRequested() {
bool r;
runningmutex.Lock();
r = requeststop;
runningmutex.Unlock();
return r;
}
void *JThread::GetReturnValue()
{
void *val;
runningmutex.Lock();
if (running)
val = NULL;
@@ -157,17 +189,17 @@ void *JThread::TheThread(void *param)
{
JThread *jthread;
void *ret;
jthread = (JThread *)param;
jthread->continuemutex2.Lock();
jthread->runningmutex.Lock();
jthread->running = true;
jthread->runningmutex.Unlock();
jthread->continuemutex.Lock();
jthread->continuemutex.Unlock();
ret = jthread->Thread();
jthread->runningmutex.Lock();

View File

@@ -0,0 +1,43 @@
/*
This file is a part of the JThread package, which contains some object-
oriented thread wrappers for different thread implementations.
Copyright (c) 2000-2006 Jori Liesenborgs (jori.liesenborgs@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
#include "jthread/jevent.h"
Event::Event() {
hEvent = CreateEvent(NULL, 0, 0, NULL);
}
Event::~Event() {
CloseHandle(hEvent);
}
void Event::wait() {
WaitForSingleObject(hEvent, INFINITE);
}
void Event::signal() {
SetEvent(hEvent);
}

View File

@@ -24,43 +24,30 @@
DEALINGS IN THE SOFTWARE.
*/
#include <assert.h>
#include "jthread/jmutex.h"
JMutex::JMutex()
{
initialized = false;
}
JMutex::~JMutex()
{
if (initialized)
#ifdef JMUTEX_CRITICALSECTION
DeleteCriticalSection(&mutex);
#else
CloseHandle(mutex);
#endif // JMUTEX_CRITICALSECTION
}
int JMutex::Init()
{
if (initialized)
return ERR_JMUTEX_ALREADYINIT;
#ifdef JMUTEX_CRITICALSECTION
InitializeCriticalSection(&mutex);
#else
mutex = CreateMutex(NULL,FALSE,NULL);
if (mutex == NULL)
return ERR_JMUTEX_CANTCREATEMUTEX;
assert(mutex != NULL);
#endif // JMUTEX_CRITICALSECTION
}
JMutex::~JMutex()
{
#ifdef JMUTEX_CRITICALSECTION
DeleteCriticalSection(&mutex);
#else
CloseHandle(mutex);
#endif // JMUTEX_CRITICALSECTION
initialized = true;
return 0;
}
int JMutex::Lock()
{
if (!initialized)
return ERR_JMUTEX_NOTINIT;
#ifdef JMUTEX_CRITICALSECTION
EnterCriticalSection(&mutex);
#else
@@ -71,8 +58,6 @@ int JMutex::Lock()
int JMutex::Unlock()
{
if (!initialized)
return ERR_JMUTEX_NOTINIT;
#ifdef JMUTEX_CRITICALSECTION
LeaveCriticalSection(&mutex);
#else

View File

@@ -0,0 +1,64 @@
/*
Minetest
Copyright (C) 2013 sapier, < sapier AT gmx DOT net >
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.
*/
#include "jthread/jsemaphore.h"
JSemaphore::JSemaphore() {
m_hSemaphore = CreateSemaphore(
0,
0,
MAX_SEMAPHORE_COUNT,
0);
}
JSemaphore::~JSemaphore() {
CloseHandle(m_hSemaphore);
}
JSemaphore::JSemaphore(int initval) {
m_hSemaphore = CreateSemaphore(
0,
initval,
MAX_SEMAPHORE_COUNT,
0);
}
void JSemaphore::Post() {
ReleaseSemaphore(
m_hSemaphore,
1,
0);
}
void JSemaphore::Wait() {
WaitForSingleObject(
m_hSemaphore,
INFINITE);
}
int JSemaphore::GetValue() {
long int retval = 0;
ReleaseSemaphore(
m_hSemaphore,
0,
&retval);
return retval;
}

83
src/jthread/win32/jthread.cpp Normal file → Executable file
View File

@@ -26,7 +26,8 @@
*/
#include "jthread/jthread.h"
#include <assert.h>
#define UNUSED(expr) do { (void)(expr); } while (0)
#ifndef _WIN32_WCE
#include <process.h>
#endif // _WIN32_WCE
@@ -34,7 +35,7 @@
JThread::JThread()
{
retval = NULL;
mutexinit = false;
requeststop = false;
running = false;
}
@@ -43,35 +44,36 @@ JThread::~JThread()
Kill();
}
void JThread::Stop() {
runningmutex.Lock();
requeststop = true;
runningmutex.Unlock();
}
void JThread::Wait() {
runningmutex.Lock();
if (running)
{
runningmutex.Unlock();
WaitForSingleObject(threadhandle, INFINITE);
}
else
{
runningmutex.Unlock();
}
}
int JThread::Start()
{
if (!mutexinit)
{
if (!runningmutex.IsInitialized())
{
if (runningmutex.Init() < 0)
return ERR_JTHREAD_CANTINITMUTEX;
}
if (!continuemutex.IsInitialized())
{
if (continuemutex.Init() < 0)
return ERR_JTHREAD_CANTINITMUTEX;
}
if (!continuemutex2.IsInitialized())
{
if (continuemutex2.Init() < 0)
return ERR_JTHREAD_CANTINITMUTEX;
} mutexinit = true;
}
runningmutex.Lock();
if (running)
{
runningmutex.Unlock();
return ERR_JTHREAD_ALREADYRUNNING;
}
requeststop = false;
runningmutex.Unlock();
continuemutex.Lock();
#ifndef _WIN32_WCE
threadhandle = (HANDLE)_beginthreadex(NULL,0,TheThread,this,0,&threadid);
@@ -83,10 +85,10 @@ int JThread::Start()
continuemutex.Unlock();
return ERR_JTHREAD_CANTSTARTTHREAD;
}
/* Wait until 'running' is set */
runningmutex.Lock();
runningmutex.Lock();
while (!running)
{
runningmutex.Unlock();
@@ -94,18 +96,18 @@ int JThread::Start()
runningmutex.Lock();
}
runningmutex.Unlock();
continuemutex.Unlock();
continuemutex2.Lock();
continuemutex2.Unlock();
return 0;
}
int JThread::Kill()
{
runningmutex.Lock();
runningmutex.Lock();
if (!running)
{
runningmutex.Unlock();
@@ -121,17 +123,26 @@ int JThread::Kill()
bool JThread::IsRunning()
{
bool r;
runningmutex.Lock();
runningmutex.Lock();
r = running;
runningmutex.Unlock();
return r;
}
bool JThread::StopRequested() {
bool r;
runningmutex.Lock();
r = requeststop;
runningmutex.Unlock();
return r;
}
void *JThread::GetReturnValue()
{
void *val;
runningmutex.Lock();
if (running)
val = NULL;
@@ -156,23 +167,23 @@ DWORD WINAPI JThread::TheThread(void *param)
void *ret;
jthread = (JThread *)param;
jthread->continuemutex2.Lock();
jthread->runningmutex.Lock();
jthread->running = true;
jthread->runningmutex.Unlock();
jthread->continuemutex.Lock();
jthread->continuemutex.Unlock();
ret = jthread->Thread();
jthread->runningmutex.Lock();
jthread->running = false;
jthread->retval = ret;
CloseHandle(jthread->threadhandle);
jthread->runningmutex.Unlock();
return 0;
return 0;
}
void JThread::ThreadStarted()

View File

@@ -165,7 +165,9 @@ void LocalPlayer::move(f32 dtime, ClientEnvironment *env, f32 pos_max_d,
If sneaking, keep in range from the last walked node and don't
fall off from it
*/
if(control.sneak && m_sneak_node_exists && !(fly_allowed && g_settings->getBool("free_move")) && !in_liquid)
if(control.sneak && m_sneak_node_exists &&
!(fly_allowed && g_settings->getBool("free_move")) && !in_liquid &&
physics_override_sneak)
{
f32 maxd = 0.5*BS + sneak_max;
v3f lwn_f = intToFloat(m_sneak_node, BS);
@@ -225,7 +227,7 @@ void LocalPlayer::move(f32 dtime, ClientEnvironment *env, f32 pos_max_d,
// node.
m_need_to_get_new_sneak_node = true;
}
if(m_need_to_get_new_sneak_node)
if(m_need_to_get_new_sneak_node && physics_override_sneak)
{
v3s16 pos_i_bottom = floatToInt(position - v3f(0,BS/2,0), BS);
v2f player_p2df(position.X, position.Z);
@@ -264,6 +266,10 @@ void LocalPlayer::move(f32 dtime, ClientEnvironment *env, f32 pos_max_d,
// And the node above it has to be nonwalkable
if(nodemgr->get(map->getNode(p+v3s16(0,1,0))).walkable == true)
continue;
if (!physics_override_sneak_glitch) {
if (nodemgr->get(map->getNode(p+v3s16(0,2,0))).walkable)
continue;
}
}
catch(InvalidPositionException &e)
{
@@ -576,6 +582,6 @@ v3s16 LocalPlayer::getStandingNodePos()
{
if(m_sneak_node_exists)
return m_sneak_node;
return floatToInt(getPosition(), BS);
return floatToInt(getPosition() - v3f(0, BS, 0), BS);
}

View File

@@ -29,6 +29,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
std::list<ILogOutput*> log_outputs[LMT_NUM_VALUES];
std::map<threadid_t, std::string> log_threadnames;
JMutex log_threadnamemutex;
void log_add_output(ILogOutput *out, enum LogMessageLevel lev)
{
@@ -60,13 +61,17 @@ void log_remove_output(ILogOutput *out)
void log_register_thread(const std::string &name)
{
threadid_t id = get_current_thread_id();
log_threadnamemutex.Lock();
log_threadnames[id] = name;
log_threadnamemutex.Unlock();
}
void log_deregister_thread()
{
threadid_t id = get_current_thread_id();
log_threadnamemutex.Lock();
log_threadnames.erase(id);
log_threadnamemutex.Unlock();
}
static std::string get_lev_string(enum LogMessageLevel lev)
@@ -144,7 +149,7 @@ class Logbuf : public std::streambuf
}
m_buf += c;
}
private:
enum LogMessageLevel m_lev;
std::string m_buf;

View File

@@ -77,6 +77,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "subgame.h"
#include "quicktune.h"
#include "serverlist.h"
#include "httpfetch.h"
#include "guiEngine.h"
#include "mapsector.h"
@@ -309,7 +310,7 @@ class MyEventReceiver : public IEventReceiver
{
return keyIsDown[keyCode];
}
// Checks whether a key was down and resets the state
bool WasKeyDown(const KeyPress &keyCode)
{
@@ -361,7 +362,7 @@ class MyEventReceiver : public IEventReceiver
private:
IrrlichtDevice *m_device;
// The current state of keys
KeyList keyIsDown;
// Whether a key has been pressed or not
@@ -405,7 +406,7 @@ class RealInputHandler : public InputHandler
{
return m_receiver->right_active;
}
virtual bool getLeftClicked()
{
return m_receiver->leftclicked;
@@ -656,7 +657,7 @@ void SpeedTests()
}
}
}
infostream<<"All of the following tests should take around 100ms each."
<<std::endl;
@@ -668,7 +669,7 @@ void SpeedTests()
tempf += 0.001;
}
}
{
TimeTaker timer("Testing floating-point vector speed");
@@ -682,7 +683,7 @@ void SpeedTests()
{
TimeTaker timer("Testing std::map speed");
std::map<v2s16, f32> map1;
tempf = -324;
const s16 ii=300;
@@ -702,9 +703,8 @@ void SpeedTests()
{
infostream<<"Around 5000/ms should do well here."<<std::endl;
TimeTaker timer("Testing mutex speed");
JMutex m;
m.Init();
u32 n = 0;
u32 i = 0;
do{
@@ -753,7 +753,7 @@ int main(int argc, char *argv[])
/*
Parse command line
*/
// List all allowed options
std::map<std::string, ValueSpec> allowed_options;
allowed_options.insert(std::make_pair("help", ValueSpec(VALUETYPE_FLAG,
@@ -806,7 +806,7 @@ int main(int argc, char *argv[])
#endif
Settings cmd_args;
bool ret = cmd_args.parseCommandLine(argc, argv, allowed_options);
if(ret == false || cmd_args.getFlag("help") || cmd_args.exists("nonopt1"))
@@ -843,11 +843,11 @@ int main(int argc, char *argv[])
dstream<<"Build info: "<<minetest_build_info<<std::endl;
return 0;
}
/*
Low-level initialization
*/
// If trace is enabled, enable logging of certain things
if(cmd_args.getFlag("trace")){
dstream<<_("Enabling trace level debug output")<<std::endl;
@@ -865,7 +865,7 @@ int main(int argc, char *argv[])
porting::signal_handler_init();
bool &kill = *porting::signal_handler_killstatus();
porting::initializePaths();
// Create user data directory
@@ -880,7 +880,7 @@ int main(int argc, char *argv[])
// Debug handler
BEGIN_DEBUG_EXCEPTION_HANDLER
// List gameids if requested
if(cmd_args.exists("gameid") && cmd_args.get("gameid") == "list")
{
@@ -890,7 +890,7 @@ int main(int argc, char *argv[])
dstream<<(*i)<<std::endl;
return 0;
}
// List worlds if requested
if(cmd_args.exists("world") && cmd_args.get("world") == "list"){
dstream<<_("Available worlds:")<<std::endl;
@@ -904,25 +904,25 @@ int main(int argc, char *argv[])
" "<<_("with")<<" SER_FMT_VER_HIGHEST_READ="<<(int)SER_FMT_VER_HIGHEST_READ
<<", "<<minetest_build_info
<<std::endl;
/*
Basic initialization
*/
// Initialize default settings
set_default_settings(g_settings);
// Initialize sockets
sockets_init();
atexit(sockets_cleanup);
/*
Read config file
*/
// Path of configuration file in use
g_settings_path = "";
if(cmd_args.exists("config"))
{
bool r = g_settings->readConfigFile(cmd_args.get("config").c_str());
@@ -958,12 +958,12 @@ int main(int argc, char *argv[])
break;
}
}
// If no path found, use the first one (menu creates the file)
if(g_settings_path == "")
g_settings_path = filenames[0];
}
// Initialize debug streams
#define DEBUGFILE "debug.txt"
#if RUN_IN_PLACE
@@ -973,7 +973,7 @@ int main(int argc, char *argv[])
#endif
if(cmd_args.exists("logfile"))
logfile = cmd_args.get("logfile");
log_remove_output(&main_dstream_no_stderr_log_out);
int loglevel = g_settings->getS32("debug_log_level");
@@ -986,13 +986,16 @@ int main(int argc, char *argv[])
debugstreams_init(false, logfile.c_str());
else
debugstreams_init(false, NULL);
infostream<<"logfile = "<<logfile<<std::endl;
// Initialize random seed
srand(time(0));
mysrand(time(0));
// Initialize HTTP fetcher
httpfetch_init(g_settings->getS32("curl_parallel_limit"));
/*
Run unit tests
*/
@@ -1020,7 +1023,7 @@ int main(int argc, char *argv[])
port = g_settings->getU16("port");
if(port == 0)
port = 30000;
// World directory
std::string commanded_world = "";
if(cmd_args.exists("world"))
@@ -1031,12 +1034,12 @@ int main(int argc, char *argv[])
commanded_world = cmd_args.get("nonopt0");
else if(g_settings->exists("map-dir"))
commanded_world = g_settings->get("map-dir");
// World name
std::string commanded_worldname = "";
if(cmd_args.exists("worldname"))
commanded_worldname = cmd_args.get("worldname");
// Strip world.mt from commanded_world
{
std::string worldmt = "world.mt";
@@ -1048,7 +1051,7 @@ int main(int argc, char *argv[])
0, commanded_world.size()-worldmt.size());
}
}
// If a world name was specified, convert it to a path
if(commanded_worldname != ""){
// Get information about available worlds
@@ -1268,7 +1271,7 @@ int main(int argc, char *argv[])
}
server.start(port);
// Run server
dedicated_server_loop(server, kill);
@@ -1280,17 +1283,17 @@ int main(int argc, char *argv[])
/*
More parameters
*/
std::string address = g_settings->get("address");
if(commanded_world != "")
address = "";
else if(cmd_args.exists("address"))
address = cmd_args.get("address");
std::string playername = g_settings->get("name");
if(cmd_args.exists("name"))
playername = cmd_args.get("name");
bool skip_main_menu = cmd_args.getFlag("go");
/*
@@ -1298,7 +1301,7 @@ int main(int argc, char *argv[])
*/
// Resolution selection
bool fullscreen = g_settings->getBool("fullscreen");
u16 screenW = g_settings->getU16("screenW");
u16 screenH = g_settings->getU16("screenH");
@@ -1312,7 +1315,7 @@ int main(int argc, char *argv[])
// Determine driver
video::E_DRIVER_TYPE driverType;
std::string driverstring = g_settings->get("video_driver");
if(driverstring == "null")
@@ -1419,7 +1422,7 @@ int main(int argc, char *argv[])
if (device == 0)
return 1; // could not create selected driver.
/*
Continue initialization
*/
@@ -1434,10 +1437,10 @@ int main(int argc, char *argv[])
// Create time getter
g_timegetter = new IrrlichtTimeGetter(device);
// Create game callback for menus
g_gamecallback = new MainGameCallback(device);
/*
Speed tests (done after irrlicht is loaded to get timer)
*/
@@ -1448,7 +1451,7 @@ int main(int argc, char *argv[])
device->drop();
return 0;
}
device->setResizable(true);
bool random_input = g_settings->getBool("random_input")
@@ -1458,7 +1461,7 @@ int main(int argc, char *argv[])
input = new RandomInputHandler();
else
input = new RealInputHandler(device, &receiver);
scene::ISceneManager* smgr = device->getSceneManager();
guienv = device->getGUIEnvironment();
@@ -1473,7 +1476,9 @@ int main(int argc, char *argv[])
fallback = "fallback_";
u16 font_size = g_settings->getU16(fallback + "font_size");
font_path = g_settings->get(fallback + "font_path");
font = gui::CGUITTFont::createTTFont(guienv, font_path.c_str(), font_size);
u32 font_shadow = g_settings->getU16(fallback + "font_shadow");
u32 font_shadow_alpha = g_settings->getU16(fallback + "font_shadow_alpha");
font = gui::CGUITTFont::createTTFont(guienv, font_path.c_str(), font_size, true, true, font_shadow, font_shadow_alpha);
} else {
font = guienv->getFont(font_path.c_str());
}
@@ -1488,7 +1493,7 @@ int main(int argc, char *argv[])
// If font was not found, this will get us one
font = skin->getFont();
assert(font);
u32 text_height = font->getDimension(L"Hello, world!").Height;
infostream<<"text_height="<<text_height<<std::endl;
@@ -1556,7 +1561,7 @@ int main(int argc, char *argv[])
Clear everything from the GUIEnvironment
*/
guienv->clear();
/*
We need some kind of a root node to be able to add
custom gui elements directly on the screen.
@@ -1564,7 +1569,7 @@ int main(int argc, char *argv[])
*/
guiroot = guienv->addStaticText(L"",
core::rect<s32>(0, 0, 10000, 10000));
SubgameSpec gamespec;
WorldSpec worldspec;
bool simple_singleplayer_mode = false;
@@ -1588,13 +1593,13 @@ int main(int argc, char *argv[])
break;
}
first_loop = false;
// Cursor can be non-visible when coming from the game
device->getCursorControl()->setVisible(true);
// Some stuff are left to scene manager when coming from the game
// (map at least?)
smgr->clear();
// Initialize menu data
MainMenuData menudata;
menudata.address = address;
@@ -1643,7 +1648,7 @@ int main(int argc, char *argv[])
infostream<<"Waited for other menus"<<std::endl;
GUIEngine* temp = new GUIEngine(device, guiroot, &g_menumgr,smgr,&menudata,kill);
delete temp;
//once finished you'll never end up here
smgr->clear();
@@ -1674,8 +1679,6 @@ int main(int argc, char *argv[])
// Save settings
g_settings->set("name", playername);
g_settings->set("address", address);
g_settings->set("port", itos(port));
if((menudata.selected_world >= 0) &&
(menudata.selected_world < (int)worldspecs.size()))
@@ -1685,7 +1688,7 @@ int main(int argc, char *argv[])
// Break out of menu-game loop to shut down cleanly
if(device->run() == false || kill == true)
break;
current_playername = playername;
current_password = password;
current_address = address;
@@ -1707,7 +1710,7 @@ int main(int argc, char *argv[])
server["description"] = menudata.serverdescription;
ServerList::insert(server);
}
// Set world path to selected one
if ((menudata.selected_world >= 0) &&
(menudata.selected_world < (int)worldspecs.size())) {
@@ -1715,7 +1718,7 @@ int main(int argc, char *argv[])
infostream<<"Selected world: "<<worldspec.name
<<" ["<<worldspec.path<<"]"<<std::endl;
}
// If local game
if(current_address == "")
{
@@ -1830,11 +1833,11 @@ int main(int argc, char *argv[])
#endif
#endif // !SERVER
// Update configuration file
if(g_settings_path != "")
g_settings->updateConfigFile(g_settings_path.c_str());
// Print modified quicktune values
{
bool header_printed = false;
@@ -1851,10 +1854,13 @@ int main(int argc, char *argv[])
}
}
// Stop httpfetch thread (if started)
httpfetch_cleanup();
END_DEBUG_EXCEPTION_HANDLER(errorstream)
debugstreams_deinit();
return retval;
}

View File

@@ -75,8 +75,6 @@ Map::Map(std::ostream &dout, IGameDef *gamedef):
m_gamedef(gamedef),
m_sector_cache(NULL)
{
/*m_sector_mutex.Init();
assert(m_sector_mutex.IsInitialized());*/
}
Map::~Map()
@@ -931,7 +929,8 @@ void Map::updateLighting(std::map<v3s16, MapBlock*> & a_blocks,
/*
*/
void Map::addNodeAndUpdate(v3s16 p, MapNode n,
std::map<v3s16, MapBlock*> &modified_blocks)
std::map<v3s16, MapBlock*> &modified_blocks,
bool remove_metadata)
{
INodeDefManager *ndef = m_gamedef->ndef();
@@ -1018,8 +1017,9 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n,
/*
Remove node metadata
*/
removeNodeMetadata(p);
if (remove_metadata) {
removeNodeMetadata(p);
}
/*
Set the node on the map
@@ -1319,17 +1319,17 @@ void Map::removeNodeAndUpdate(v3s16 p,
}
}
bool Map::addNodeWithEvent(v3s16 p, MapNode n)
bool Map::addNodeWithEvent(v3s16 p, MapNode n, bool remove_metadata)
{
MapEditEvent event;
event.type = MEET_ADDNODE;
event.type = remove_metadata ? MEET_ADDNODE : MEET_SWAPNODE;
event.p = p;
event.n = n;
bool succeeded = true;
try{
std::map<v3s16, MapBlock*> modified_blocks;
addNodeAndUpdate(p, n, modified_blocks);
addNodeAndUpdate(p, n, modified_blocks, remove_metadata);
// Copy modified_blocks to event
for(std::map<v3s16, MapBlock*>::iterator
@@ -1679,7 +1679,7 @@ void Map::transformLiquidsFinite(std::map<v3s16, MapBlock*> & modified_blocks)
v3s16 p0 = m_transforming_liquid.pop_front();
u16 total_level = 0;
// surrounding flowing liquid nodes
NodeNeighbor neighbors[7];
NodeNeighbor neighbors[7];
// current level of every block
s8 liquid_levels[7] = {-1, -1, -1, -1, -1, -1, -1};
// target levels
@@ -1780,8 +1780,8 @@ void Map::transformLiquidsFinite(std::map<v3s16, MapBlock*> & modified_blocks)
liquid_levels[D_BOTTOM] == LIQUID_LEVEL_SOURCE &&
total_level >= LIQUID_LEVEL_SOURCE * can_liquid_same_level-
(can_liquid_same_level - relax) &&
can_liquid_same_level >= relax + 1) {
total_level = LIQUID_LEVEL_SOURCE * can_liquid_same_level;
can_liquid_same_level >= relax + 1) {
total_level = LIQUID_LEVEL_SOURCE * can_liquid_same_level;
}
// prevent lakes in air above unloaded blocks
@@ -1790,9 +1790,9 @@ void Map::transformLiquidsFinite(std::map<v3s16, MapBlock*> & modified_blocks)
}
// calculate self level 5 blocks
u8 want_level =
u8 want_level =
total_level >= LIQUID_LEVEL_SOURCE * can_liquid_same_level
? LIQUID_LEVEL_SOURCE
? LIQUID_LEVEL_SOURCE
: total_level / can_liquid_same_level;
total_level -= want_level * can_liquid_same_level;
@@ -1850,7 +1850,7 @@ void Map::transformLiquidsFinite(std::map<v3s16, MapBlock*> & modified_blocks)
/*
if (total_level > 0) //|| flowed != volume)
infostream <<" AFTER level=" << (int)total_level
infostream <<" AFTER level=" << (int)total_level
//<< " flowed="<<flowed<< " volume=" << volume
<< " wantsame="<<(int)want_level<< " top="
<< (int)liquid_levels_want[D_TOP]<< " topwas="
@@ -1860,7 +1860,7 @@ void Map::transformLiquidsFinite(std::map<v3s16, MapBlock*> & modified_blocks)
//u8 changed = 0;
for (u16 i = 0; i < 7; i++) {
if (liquid_levels_want[i] < 0 || !neighbors[i].l)
if (liquid_levels_want[i] < 0 || !neighbors[i].l)
continue;
MapNode & n0 = neighbors[i].n;
p0 = neighbors[i].p;
@@ -1907,7 +1907,7 @@ void Map::transformLiquidsFinite(std::map<v3s16, MapBlock*> & modified_blocks)
*/
/*
if (
new_node_content == n0.getContent()
new_node_content == n0.getContent()
&& (nodemgr->get(n0.getContent()).liquid_type != LIQUID_FLOWING ||
(n0.getLevel(nodemgr) == (u8)new_node_level
//&& ((n0.param2 & LIQUID_FLOW_DOWN_MASK) ==
@@ -2279,7 +2279,7 @@ void Map::transformLiquids(std::map<v3s16, MapBlock*> & modified_blocks)
updateLighting(lighting_modified_blocks, modified_blocks);
}
NodeMetadata* Map::getNodeMetadata(v3s16 p)
NodeMetadata *Map::getNodeMetadata(v3s16 p)
{
v3s16 blockpos = getNodeBlockPos(p);
v3s16 p_rel = p - blockpos*MAP_BLOCKSIZE;
@@ -2289,8 +2289,7 @@ NodeMetadata* Map::getNodeMetadata(v3s16 p)
<<PP(blockpos)<<std::endl;
block = emergeBlock(blockpos, false);
}
if(!block)
{
if(!block){
infostream<<"WARNING: Map::getNodeMetadata(): Block not found"
<<std::endl;
return NULL;
@@ -2299,7 +2298,7 @@ NodeMetadata* Map::getNodeMetadata(v3s16 p)
return meta;
}
void Map::setNodeMetadata(v3s16 p, NodeMetadata *meta)
bool Map::setNodeMetadata(v3s16 p, NodeMetadata *meta)
{
v3s16 blockpos = getNodeBlockPos(p);
v3s16 p_rel = p - blockpos*MAP_BLOCKSIZE;
@@ -2309,13 +2308,13 @@ void Map::setNodeMetadata(v3s16 p, NodeMetadata *meta)
<<PP(blockpos)<<std::endl;
block = emergeBlock(blockpos, false);
}
if(!block)
{
if(!block){
infostream<<"WARNING: Map::setNodeMetadata(): Block not found"
<<std::endl;
return;
return false;
}
block->m_node_metadata.set(p_rel, meta);
return true;
}
void Map::removeNodeMetadata(v3s16 p)
@@ -2342,8 +2341,7 @@ NodeTimer Map::getNodeTimer(v3s16 p)
<<PP(blockpos)<<std::endl;
block = emergeBlock(blockpos, false);
}
if(!block)
{
if(!block){
infostream<<"WARNING: Map::getNodeTimer(): Block not found"
<<std::endl;
return NodeTimer();
@@ -2362,8 +2360,7 @@ void Map::setNodeTimer(v3s16 p, NodeTimer t)
<<PP(blockpos)<<std::endl;
block = emergeBlock(blockpos, false);
}
if(!block)
{
if(!block){
infostream<<"WARNING: Map::setNodeTimer(): Block not found"
<<std::endl;
return;
@@ -3188,48 +3185,56 @@ void ServerMap::prepareBlock(MapBlock *block) {
}
}
s16 ServerMap::findGroundLevel(v2s16 p2d)
/**
* Get the ground level by searching for a non CONTENT_AIR node in a column from top to bottom
*/
s16 ServerMap::findGroundLevel(v2s16 p2d, bool cacheBlocks)
{
#if 0
/*
Uh, just do something random...
*/
// Find existing map from top to down
s16 max=63;
s16 min=-64;
v3s16 p(p2d.X, max, p2d.Y);
for(; p.Y>min; p.Y--)
s16 level;
// The reference height is the original mapgen height
s16 referenceHeight = m_emerge->getGroundLevelAtPoint(p2d);
s16 maxSearchHeight = 63 + referenceHeight;
s16 minSearchHeight = -63 + referenceHeight;
v3s16 probePosition(p2d.X, maxSearchHeight, p2d.Y);
v3s16 blockPosition = getNodeBlockPos(probePosition);
v3s16 prevBlockPosition = blockPosition;
// Cache the block to be inspected.
if(cacheBlocks) {
emergeBlock(blockPosition, true);
}
// Probes the nodes in the given column
for(; probePosition.Y > minSearchHeight; probePosition.Y--)
{
MapNode n = getNodeNoEx(p);
if(n.getContent() != CONTENT_IGNORE)
if(cacheBlocks) {
// Calculate the block position of the given node
blockPosition = getNodeBlockPos(probePosition);
// If the node is in an different block, cache it
if(blockPosition != prevBlockPosition) {
emergeBlock(blockPosition, true);
prevBlockPosition = blockPosition;
}
}
MapNode node = getNodeNoEx(probePosition);
if (node.getContent() != CONTENT_IGNORE &&
node.getContent() != CONTENT_AIR) {
break;
}
if(p.Y == min)
goto plan_b;
// If this node is not air, go to plan b
if(getNodeNoEx(p).getContent() != CONTENT_AIR)
goto plan_b;
// Search existing walkable and return it
for(; p.Y>min; p.Y--)
{
MapNode n = getNodeNoEx(p);
if(content_walkable(n.d) && n.getContent() != CONTENT_IGNORE)
return p.Y;
}
}
// Move to plan b
plan_b:
#endif
// Could not determine the ground. Use map generator noise functions.
if(probePosition.Y == minSearchHeight) {
level = referenceHeight;
} else {
level = probePosition.Y;
}
/*
Determine from map generator noise functions
*/
s16 level = m_emerge->getGroundLevelAtPoint(p2d);
return level;
//double level = base_rock_level_2d(m_seed, p2d) + AVERAGE_MUD_AMOUNT;
//return (s16)level;
}
bool ServerMap::loadFromFolders() {

View File

@@ -61,6 +61,8 @@ enum MapEditEventType{
MEET_ADDNODE,
// Node removed (changed to air)
MEET_REMOVENODE,
// Node swapped (changed without metadata change)
MEET_SWAPNODE,
// Node metadata of block changed (not knowing which node exactly)
// p stores block coordinate
MEET_BLOCK_NODE_METADATA_CHANGED,
@@ -99,6 +101,8 @@ struct MapEditEvent
return VoxelArea(p);
case MEET_REMOVENODE:
return VoxelArea(p);
case MEET_SWAPNODE:
return VoxelArea(p);
case MEET_BLOCK_NODE_METADATA_CHANGED:
{
v3s16 np1 = p*MAP_BLOCKSIZE;
@@ -236,7 +240,8 @@ class Map /*: public NodeContainer*/
These handle lighting but not faces.
*/
void addNodeAndUpdate(v3s16 p, MapNode n,
std::map<v3s16, MapBlock*> &modified_blocks);
std::map<v3s16, MapBlock*> &modified_blocks,
bool remove_metadata = true);
void removeNodeAndUpdate(v3s16 p,
std::map<v3s16, MapBlock*> &modified_blocks);
@@ -245,7 +250,7 @@ class Map /*: public NodeContainer*/
These emit events.
Return true if succeeded, false if not.
*/
bool addNodeWithEvent(v3s16 p, MapNode n);
bool addNodeWithEvent(v3s16 p, MapNode n, bool remove_metadata = true);
bool removeNodeWithEvent(v3s16 p);
/*
@@ -307,7 +312,22 @@ class Map /*: public NodeContainer*/
*/
NodeMetadata* getNodeMetadata(v3s16 p);
void setNodeMetadata(v3s16 p, NodeMetadata *meta);
/**
* Sets metadata for a node.
* This method sets the metadata for a given node.
* On success, it returns @c true and the object pointed to
* by @p meta is then managed by the system and should
* not be deleted by the caller.
*
* In case of failure, the method returns @c false and the
* caller is still responsible for deleting the object!
*
* @param p node coordinates
* @param meta pointer to @c NodeMetadata object
* @return @c true on success, false on failure
*/
bool setNodeMetadata(v3s16 p, NodeMetadata *meta);
void removeNodeMetadata(v3s16 p);
/*
@@ -408,7 +428,7 @@ class ServerMap : public Map
void prepareBlock(MapBlock *block);
// Helper for placing objects on ground level
s16 findGroundLevel(v2s16 p2d);
s16 findGroundLevel(v2s16 p2d, bool cacheBlocks);
/*
Misc. helper functions for fiddling with directory and file
@@ -476,6 +496,7 @@ class ServerMap : public Map
u64 getSeed(){ return m_seed; }
MapgenParams *getMapgenParams(){ return m_mgparams; }
void setMapgenParams(MapgenParams *mgparams){ m_mgparams = mgparams; }
// Parameters fed to the Mapgen
MapgenParams *m_mgparams;

View File

@@ -68,7 +68,6 @@ MapBlock::MapBlock(Map *parent, v3s16 pos, IGameDef *gamedef, bool dummy):
reallocate();
#ifndef SERVER
//mesh_mutex.Init();
mesh = NULL;
#endif
}

View File

@@ -1111,19 +1111,20 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data):
*/
bool enable_shaders = g_settings->getBool("enable_shaders");
bool enable_bumpmapping = g_settings->getBool("enable_bumpmapping");
bool enable_parallax_occlusion = g_settings->getBool("enable_parallax_occlusion");
video::E_MATERIAL_TYPE shadermat1, shadermat2, shadermat3, bumpmaps1, bumpmaps2;
shadermat1 = shadermat2 = shadermat3 = bumpmaps1 = bumpmaps2 = video::EMT_SOLID;
video::E_MATERIAL_TYPE shadermat1, shadermat2, shadermat3,
shadermat4, shadermat5;
shadermat1 = shadermat2 = shadermat3 = shadermat4 = shadermat5 =
video::EMT_SOLID;
if (enable_shaders) {
IShaderSource *shdrsrc = m_gamedef->getShaderSource();
shadermat1 = shdrsrc->getShader("test_shader_1").material;
shadermat2 = shdrsrc->getShader("test_shader_2").material;
shadermat3 = shdrsrc->getShader("test_shader_3").material;
if (enable_bumpmapping) {
bumpmaps1 = shdrsrc->getShader("bumpmaps_solids").material;
bumpmaps2 = shdrsrc->getShader("bumpmaps_liquids").material;
}
shadermat1 = shdrsrc->getShader("solids_shader").material;
shadermat2 = shdrsrc->getShader("liquids_shader").material;
shadermat3 = shdrsrc->getShader("alpha_shader").material;
shadermat4 = shdrsrc->getShader("leaves_shader").material;
shadermat5 = shdrsrc->getShader("plants_shader").material;
}
for(u32 i = 0; i < collector.prebuffers.size(); i++)
@@ -1204,22 +1205,18 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data):
material.setFlag(video::EMF_FOG_ENABLE, true);
//material.setFlag(video::EMF_ANTI_ALIASING, video::EAAM_OFF);
//material.setFlag(video::EMF_ANTI_ALIASING, video::EAAM_SIMPLE);
material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
//material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
material.setTexture(0, p.tile.texture);
if (enable_shaders) {
video::E_MATERIAL_TYPE smat1 = shadermat1;
video::E_MATERIAL_TYPE smat2 = shadermat2;
video::E_MATERIAL_TYPE smat3 = shadermat3;
if (enable_bumpmapping) {
ITextureSource *tsrc = data->m_gamedef->tsrc();
std::string fname_base = tsrc->getTextureName(p.tile.texture_id);
if (enable_shaders) {
ITextureSource *tsrc = data->m_gamedef->tsrc();
material.setTexture(2, tsrc->getTexture("disable_img.png"));
if (enable_bumpmapping || enable_parallax_occlusion) {
std::string fname_base = tsrc->getTextureName(p.tile.texture_id);
std::string normal_ext = "_normal.png";
size_t pos = fname_base.find(".");
std::string fname_normal = fname_base.substr(0, pos) + normal_ext;
if (tsrc->isKnownSourceImage(fname_normal)) {
// look for image extension and replace it
size_t i = 0;
@@ -1227,19 +1224,15 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data):
fname_base.replace(i, 4, normal_ext);
i += normal_ext.length();
}
material.setTexture(1, tsrc->getTexture(fname_base));
smat1 = bumpmaps1;
smat2 = bumpmaps2;
material.setTexture(2, tsrc->getTexture("enable_img.png"));
}
}
p.tile.applyMaterialOptionsWithShaders(material, smat1, smat2, smat3);
p.tile.applyMaterialOptionsWithShaders(material,
shadermat1, shadermat2, shadermat3, shadermat4, shadermat5);
} else {
p.tile.applyMaterialOptions(material);
}
// Create meshbuffer
// This is a "Standard MeshBuffer",
@@ -1300,7 +1293,8 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack, u32 daynight_rat
{
bool enable_shaders = g_settings->getBool("enable_shaders");
bool enable_bumpmapping = g_settings->getBool("enable_bumpmapping");
bool enable_parallax_occlusion = g_settings->getBool("enable_parallax_occlusion");
if(!m_has_animation)
{
m_animation_force_timer = 100000;
@@ -1369,18 +1363,21 @@ bool MapBlockMesh::animate(bool faraway, float time, int crack, u32 daynight_rat
os<<"^[verticalframe:"<<(int)tile.animation_frame_count<<":"<<frame;
// Set the texture
buf->getMaterial().setTexture(0, tsrc->getTexture(os.str()));
if (enable_shaders && enable_bumpmapping)
buf->getMaterial().setTexture(2, tsrc->getTexture("disable_img.png"));
if (enable_shaders && (enable_bumpmapping || enable_parallax_occlusion))
{
std::string basename,normal;
basename = tsrc->getTextureName(tile.texture_id);
std::string fname_base,fname_normal;
fname_base = tsrc->getTextureName(tile.texture_id);
unsigned pos;
pos = basename.find(".");
normal = basename.substr (0, pos);
normal += "_normal.png";
os.str("");
os<<normal<<"^[verticalframe:"<<(int)tile.animation_frame_count<<":"<<frame;
if (tsrc->isKnownSourceImage(normal))
pos = fname_base.find(".");
fname_normal = fname_base.substr (0, pos);
fname_normal += "_normal.png";
if (tsrc->isKnownSourceImage(fname_normal)){
os.str("");
os<<fname_normal<<"^[verticalframe:"<<(int)tile.animation_frame_count<<":"<<frame;
buf->getMaterial().setTexture(1, tsrc->getTexture(os.str()));
buf->getMaterial().setTexture(2, tsrc->getTexture("enable_img.png"));
}
}
}

Some files were not shown because too many files have changed in this diff Show More