Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d38b465b7c | ||
|
|
fd7ec2da91 | ||
|
|
c9ed379e39 | ||
|
|
e64feefc61 | ||
|
|
3e754382af | ||
|
|
7ef0a13250 | ||
|
|
0de3fb786d | ||
|
|
1d44a98f2f | ||
|
|
98ff4eb4ee | ||
|
|
a9d8df83d2 | ||
|
|
508b7b5e51 | ||
|
|
f7dc72f8aa | ||
|
|
0190f9b077 | ||
|
|
0c91a0d59d | ||
|
|
a26a66a8c4 | ||
|
|
100345f1e4 | ||
|
|
4535166a3b | ||
|
|
0346e68deb | ||
|
|
db62c227c8 | ||
|
|
983e45ae92 | ||
|
|
0a18dda158 | ||
|
|
9eaf93d41d | ||
|
|
2ac20982e0 | ||
|
|
96eac87d47 | ||
|
|
0cf1ed544c | ||
|
|
558e284e25 | ||
|
|
5c31445117 | ||
|
|
717ae67995 | ||
|
|
e8331f0c1d | ||
|
|
c009aa3a22 | ||
|
|
9af9d8f5d0 | ||
|
|
2c027b03db | ||
|
|
aef1332e42 | ||
|
|
16fc8b5fc2 | ||
|
|
fd845f27f5 | ||
|
|
ea62ee4b61 | ||
|
|
cd6becd442 | ||
|
|
829f262c79 | ||
|
|
246520b5cb | ||
|
|
38bb649582 | ||
|
|
82855a04ec | ||
|
|
6dfefaf229 | ||
|
|
d44f8a854b | ||
|
|
acf3a43095 | ||
|
|
4cc98d7add | ||
|
|
506203345b | ||
|
|
c259f7c8bd | ||
|
|
c62a121cca | ||
|
|
136eb32389 |
@@ -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 1)
|
||||
set(VERSION_PATCH 2-rc1)
|
||||
if(VERSION_EXTRA)
|
||||
set(VERSION_PATCH ${VERSION_PATCH}-${VERSION_EXTRA})
|
||||
endif()
|
||||
@@ -62,7 +62,7 @@ if(WIN32)
|
||||
elseif(APPLE)
|
||||
# Random placeholders; this isn't usually used and may not work
|
||||
# See https://github.com/toabi/minetest-mac/
|
||||
set(SHAREDIR "share/${PROJECT_NAME}")
|
||||
set(SHAREDIR "${CMAKE_INSTALL_PREFIX}/share/${PROJECT_NAME}")
|
||||
set(BINDIR "bin")
|
||||
set(DOCDIR "share/doc/${PROJECT_NAME}")
|
||||
set(EXAMPLE_CONF_DIR ${DOCDIR})
|
||||
@@ -78,17 +78,58 @@ elseif(UNIX) # Linux, BSD etc
|
||||
set(ICONDIR "unix/icons")
|
||||
set(LOCALEDIR "locale")
|
||||
else()
|
||||
set(SHAREDIR "share/${PROJECT_NAME}")
|
||||
set(BINDIR "bin")
|
||||
set(DOCDIR "share/doc/${PROJECT_NAME}")
|
||||
set(MANDIR "share/man")
|
||||
set(SHAREDIR "${CMAKE_INSTALL_PREFIX}/share/${PROJECT_NAME}")
|
||||
set(BINDIR "${CMAKE_INSTALL_PREFIX}/bin")
|
||||
set(DOCDIR "${CMAKE_INSTALL_PREFIX}/share/doc/${PROJECT_NAME}")
|
||||
set(MANDIR "${CMAKE_INSTALL_PREFIX}/share/man")
|
||||
set(EXAMPLE_CONF_DIR ${DOCDIR})
|
||||
set(XDG_APPS_DIR "share/applications")
|
||||
set(ICONDIR "share/icons")
|
||||
set(LOCALEDIR "share/locale")
|
||||
set(XDG_APPS_DIR "${CMAKE_INSTALL_PREFIX}/share/applications")
|
||||
set(ICONDIR "${CMAKE_INSTALL_PREFIX}/share/icons")
|
||||
set(LOCALEDIR "${CMAKE_INSTALL_PREFIX}/share/locale")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(CUSTOM_SHAREDIR "" CACHE STRING "Directory to install data files into")
|
||||
if(NOT CUSTOM_SHAREDIR STREQUAL "")
|
||||
set(SHAREDIR "${CUSTOM_SHAREDIR}")
|
||||
message(STATUS "Using SHAREDIR=${SHAREDIR}")
|
||||
endif()
|
||||
set(CUSTOM_BINDIR "" CACHE STRING "Directory to install binaries into")
|
||||
if(NOT CUSTOM_BINDIR STREQUAL "")
|
||||
set(BINDIR "${CUSTOM_BINDIR}")
|
||||
message(STATUS "Using BINDIR=${BINDIR}")
|
||||
endif()
|
||||
set(CUSTOM_DOCDIR "" CACHE STRING "Directory to install documentation into")
|
||||
if(NOT CUSTOM_DOCDIR STREQUAL "")
|
||||
set(DOCDIR "${CUSTOM_DOCDIR}")
|
||||
message(STATUS "Using DOCDIR=${DOCDIR}")
|
||||
endif()
|
||||
set(CUSTOM_MANDIR "" CACHE STRING "Directory to install manpages into")
|
||||
if(NOT CUSTOM_MANDIR STREQUAL "")
|
||||
set(MANDIR "${CUSTOM_MANDIR}")
|
||||
message(STATUS "Using MANDIR=${MANDIR}")
|
||||
endif()
|
||||
set(CUSTOM_EXAMPLE_CONF_DIR "" CACHE STRING "Directory to install example config file into")
|
||||
if(NOT CUSTOM_EXAMPLE_CONF_DIR STREQUAL "")
|
||||
set(EXAMPLE_CONF_DIR "${CUSTOM_EXAMPLE_CONF_DIR}")
|
||||
message(STATUS "Using EXAMPLE_CONF_DIR=${EXAMPLE_CONF_DIR}")
|
||||
endif()
|
||||
set(CUSTOM_XDG_APPS_DIR "" CACHE STRING "Directory to install .desktop files into")
|
||||
if(NOT CUSTOM_XDG_APPS_DIR STREQUAL "")
|
||||
set(XDG_APPS_DIR "${CUSTOM_XDG_APPS_DIR}")
|
||||
message(STATUS "Using XDG_APPS_DIR=${XDG_APPS_DIR}")
|
||||
endif()
|
||||
set(CUSTOM_ICONDIR "" CACHE STRING "Directory to install icons into")
|
||||
if(NOT CUSTOM_ICONDIR STREQUAL "")
|
||||
set(ICONDIR "${CUSTOM_ICONDIR}")
|
||||
message(STATUS "Using ICONDIR=${ICONDIR}")
|
||||
endif()
|
||||
set(CUSTOM_LOCALEDIR "" CACHE STRING "Directory to install l10n files into")
|
||||
if(NOT CUSTOM_LOCALEDIR STREQUAL "")
|
||||
set(LOCALEDIR "${CUSTOM_LOCALEDIR}")
|
||||
message(STATUS "Using LOCALEDIR=${LOCALEDIR}")
|
||||
endif()
|
||||
|
||||
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/builtin" DESTINATION "${SHAREDIR}")
|
||||
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/games/minimal" DESTINATION "${SHAREDIR}/games")
|
||||
set(MINETEST_GAME_SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/games/minetest_game")
|
||||
|
||||
@@ -21,4 +21,5 @@ dofile(minetest.get_modpath("__builtin").."/privileges.lua")
|
||||
dofile(minetest.get_modpath("__builtin").."/auth.lua")
|
||||
dofile(minetest.get_modpath("__builtin").."/chatcommands.lua")
|
||||
dofile(minetest.get_modpath("__builtin").."/static_spawn.lua")
|
||||
dofile(minetest.get_modpath("__builtin").."/detached_inventory.lua")
|
||||
|
||||
|
||||
@@ -371,3 +371,207 @@ minetest.register_chatcommand("set", {
|
||||
end,
|
||||
})
|
||||
|
||||
minetest.register_chatcommand("mods", {
|
||||
params = "",
|
||||
description = "lists mods installed on the server",
|
||||
privs = {},
|
||||
func = function(name, param)
|
||||
local response = ""
|
||||
local modnames = minetest.get_modnames()
|
||||
for i, mod in ipairs(modnames) do
|
||||
response = response .. mod
|
||||
-- Add space if not at the end
|
||||
if i ~= #modnames then
|
||||
response = response .. " "
|
||||
end
|
||||
end
|
||||
minetest.chat_send_player(name, response)
|
||||
end,
|
||||
})
|
||||
|
||||
local function handle_give_command(cmd, giver, receiver, stackstring)
|
||||
minetest.log("action", giver.." invoked "..cmd..', stackstring="'
|
||||
..stackstring..'"')
|
||||
minetest.log(cmd..' invoked, stackstring="'..stackstring..'"')
|
||||
local itemstack = ItemStack(stackstring)
|
||||
if itemstack:is_empty() then
|
||||
minetest.chat_send_player(giver, 'error: cannot give an empty item')
|
||||
return
|
||||
elseif not itemstack:is_known() then
|
||||
minetest.chat_send_player(giver, 'error: cannot give an unknown item')
|
||||
return
|
||||
end
|
||||
local receiverref = minetest.env:get_player_by_name(receiver)
|
||||
if receiverref == nil then
|
||||
minetest.chat_send_player(giver, receiver..' is not a known player')
|
||||
return
|
||||
end
|
||||
local leftover = receiverref:get_inventory():add_item("main", itemstack)
|
||||
if leftover:is_empty() then
|
||||
partiality = ""
|
||||
elseif leftover:get_count() == itemstack:get_count() then
|
||||
partiality = "could not be "
|
||||
else
|
||||
partiality = "partially "
|
||||
end
|
||||
-- The actual item stack string may be different from what the "giver"
|
||||
-- entered (e.g. big numbers are always interpreted as 2^16-1).
|
||||
stackstring = itemstack:to_string()
|
||||
if giver == receiver then
|
||||
minetest.chat_send_player(giver, '"'..stackstring
|
||||
..'" '..partiality..'added to inventory.');
|
||||
else
|
||||
minetest.chat_send_player(giver, '"'..stackstring
|
||||
..'" '..partiality..'added to '..receiver..'\'s inventory.');
|
||||
minetest.chat_send_player(receiver, '"'..stackstring
|
||||
..'" '..partiality..'added to inventory.');
|
||||
end
|
||||
end
|
||||
|
||||
minetest.register_chatcommand("give", {
|
||||
params = "<name> <itemstring>",
|
||||
description = "give item to player",
|
||||
privs = {give=true},
|
||||
func = function(name, param)
|
||||
local toname, itemstring = string.match(param, "^([^ ]+) +(.+)$")
|
||||
if not toname or not itemstring then
|
||||
minetest.chat_send_player(name, "name and itemstring required")
|
||||
return
|
||||
end
|
||||
handle_give_command("/give", name, toname, itemstring)
|
||||
end,
|
||||
})
|
||||
minetest.register_chatcommand("giveme", {
|
||||
params = "<itemstring>",
|
||||
description = "give item to yourself",
|
||||
privs = {give=true},
|
||||
func = function(name, param)
|
||||
local itemstring = string.match(param, "(.+)$")
|
||||
if not itemstring then
|
||||
minetest.chat_send_player(name, "itemstring required")
|
||||
return
|
||||
end
|
||||
handle_give_command("/giveme", name, name, itemstring)
|
||||
end,
|
||||
})
|
||||
minetest.register_chatcommand("spawnentity", {
|
||||
params = "<entityname>",
|
||||
description = "spawn entity at your position",
|
||||
privs = {give=true, interact=true},
|
||||
func = function(name, param)
|
||||
local entityname = string.match(param, "(.+)$")
|
||||
if not entityname then
|
||||
minetest.chat_send_player(name, "entityname required")
|
||||
return
|
||||
end
|
||||
print('/spawnentity invoked, entityname="'..entityname..'"')
|
||||
local player = minetest.env:get_player_by_name(name)
|
||||
if player == nil then
|
||||
print("Unable to spawn entity, player is nil")
|
||||
return true -- Handled chat message
|
||||
end
|
||||
local p = player:getpos()
|
||||
p.y = p.y + 1
|
||||
minetest.env:add_entity(p, entityname)
|
||||
minetest.chat_send_player(name, '"'..entityname
|
||||
..'" spawned.');
|
||||
end,
|
||||
})
|
||||
minetest.register_chatcommand("pulverize", {
|
||||
params = "",
|
||||
description = "delete item in hand",
|
||||
privs = {},
|
||||
func = function(name, param)
|
||||
local player = minetest.env:get_player_by_name(name)
|
||||
if player == nil then
|
||||
print("Unable to pulverize, player is nil")
|
||||
return true -- Handled chat message
|
||||
end
|
||||
if player:get_wielded_item():is_empty() then
|
||||
minetest.chat_send_player(name, 'Unable to pulverize, no item in hand.')
|
||||
else
|
||||
player:set_wielded_item(nil)
|
||||
minetest.chat_send_player(name, 'An item was pulverized.')
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
-- Key = player name
|
||||
minetest.rollback_punch_callbacks = {}
|
||||
|
||||
minetest.register_on_punchnode(function(pos, node, puncher)
|
||||
local name = puncher:get_player_name()
|
||||
if minetest.rollback_punch_callbacks[name] then
|
||||
minetest.rollback_punch_callbacks[name](pos, node, puncher)
|
||||
minetest.rollback_punch_callbacks[name] = nil
|
||||
end
|
||||
end)
|
||||
|
||||
minetest.register_chatcommand("rollback_check", {
|
||||
params = "[<range>] [<seconds>]",
|
||||
description = "check who has last touched a node or near it, "..
|
||||
"max. <seconds> ago (default range=0, seconds=86400=24h)",
|
||||
privs = {rollback=true},
|
||||
func = function(name, param)
|
||||
local range, seconds = string.match(param, "(%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)")
|
||||
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, "Nobody has touched the "..
|
||||
"specified location in "..dump(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)
|
||||
end
|
||||
local nodename = minetest.env: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,
|
||||
})
|
||||
|
||||
minetest.register_chatcommand("rollback", {
|
||||
params = "<player name> [<seconds>] | :<actor> [<seconds>]",
|
||||
description = "revert actions of a player; default for <seconds> is 60",
|
||||
privs = {rollback=true},
|
||||
func = function(name, param)
|
||||
local target_name, seconds = string.match(param, ":([^ ]+) *(%d*)")
|
||||
if not target_name then
|
||||
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")
|
||||
return
|
||||
end
|
||||
target_name = "player:"..player_name
|
||||
end
|
||||
seconds = tonumber(seconds) or 60
|
||||
minetest.chat_send_player(name, "Reverting actions of "..
|
||||
dump(target_name).." since "..dump(seconds).." seconds.")
|
||||
local success, log = minetest.rollback_revert_actions_by(
|
||||
target_name, seconds)
|
||||
if #log > 10 then
|
||||
minetest.chat_send_player(name, "(log is too long to show)")
|
||||
else
|
||||
for _,line in ipairs(log) do
|
||||
minetest.chat_send_player(name, line)
|
||||
end
|
||||
end
|
||||
if success then
|
||||
minetest.chat_send_player(name, "Reverting actions succeeded.")
|
||||
else
|
||||
minetest.chat_send_player(name, "Reverting actions FAILED.")
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
|
||||
@@ -16,4 +16,11 @@ minetest.digprop_woodlike = digprop_err
|
||||
minetest.digprop_leaveslike = digprop_err
|
||||
minetest.digprop_glasslike = digprop_err
|
||||
|
||||
minetest.node_metadata_inventory_move_allow_all = function()
|
||||
minetest.log("info", "WARNING: minetest.node_metadata_inventory_move_allow_all is obsolete and does nothing.")
|
||||
end
|
||||
|
||||
minetest.add_to_creative_inventory = function(itemstring)
|
||||
minetest.log('info', "WARNING: minetest.add_to_creative_inventory: This function is deprecated and does nothing.")
|
||||
end
|
||||
|
||||
|
||||
19
builtin/detached_inventory.lua
Normal file
19
builtin/detached_inventory.lua
Normal file
@@ -0,0 +1,19 @@
|
||||
-- Minetest: builtin/detached_inventory.lua
|
||||
|
||||
minetest.detached_inventories = {}
|
||||
|
||||
function minetest.create_detached_inventory(name, callbacks)
|
||||
local stuff = {}
|
||||
stuff.name = name
|
||||
if callbacks then
|
||||
stuff.allow_move = callbacks.allow_move
|
||||
stuff.allow_put = callbacks.allow_put
|
||||
stuff.allow_take = callbacks.allow_take
|
||||
stuff.on_move = callbacks.on_move
|
||||
stuff.on_put = callbacks.on_put
|
||||
stuff.on_take = callbacks.on_take
|
||||
end
|
||||
minetest.detached_inventories[name] = stuff
|
||||
return minetest.create_detached_inventory_raw(name)
|
||||
end
|
||||
|
||||
208
builtin/item.lua
208
builtin/item.lua
@@ -124,55 +124,84 @@ end
|
||||
function minetest.item_place_node(itemstack, placer, pointed_thing)
|
||||
local item = itemstack:peek_item()
|
||||
local def = itemstack:get_definition()
|
||||
if def.type == "node" and pointed_thing.type == "node" then
|
||||
local pos = pointed_thing.above
|
||||
local oldnode = minetest.env:get_node(pos)
|
||||
local olddef = ItemStack({name=oldnode.name}):get_definition()
|
||||
|
||||
if not olddef.buildable_to then
|
||||
minetest.log("info", placer:get_player_name() .. " tried to place"
|
||||
.. " node in invalid position " .. minetest.pos_to_string(pos)
|
||||
.. ", replacing " .. oldnode.name)
|
||||
return
|
||||
end
|
||||
|
||||
minetest.log("action", placer:get_player_name() .. " places node "
|
||||
.. def.name .. " at " .. minetest.pos_to_string(pos))
|
||||
|
||||
local newnode = {name = def.name, param1 = 0, param2 = 0}
|
||||
|
||||
-- Calculate direction for wall mounted stuff like torches and signs
|
||||
if def.paramtype2 == 'wallmounted' then
|
||||
local under = pointed_thing.under
|
||||
local above = pointed_thing.above
|
||||
local dir = {x = under.x - above.x, y = under.y - above.y, z = under.z - above.z}
|
||||
newnode.param2 = minetest.dir_to_wallmounted(dir)
|
||||
-- Calculate the direction for furnaces and chests and stuff
|
||||
elseif def.paramtype2 == 'facedir' then
|
||||
local placer_pos = placer:getpos()
|
||||
if placer_pos then
|
||||
local dir = {x = pos.x - placer_pos.x, y = pos.y - placer_pos.y, z = pos.z - placer_pos.z}
|
||||
newnode.param2 = minetest.dir_to_facedir(dir)
|
||||
minetest.log("action", "facedir: " .. newnode.param2)
|
||||
end
|
||||
end
|
||||
|
||||
-- Add node and update
|
||||
minetest.env:add_node(pos, newnode)
|
||||
|
||||
-- Run callback
|
||||
if def.after_place_node then
|
||||
def.after_place_node(pos, placer)
|
||||
end
|
||||
|
||||
-- Run script hook (deprecated)
|
||||
local _, callback
|
||||
for _, callback in ipairs(minetest.registered_on_placenodes) do
|
||||
callback(pos, newnode, placer)
|
||||
end
|
||||
|
||||
itemstack:take_item()
|
||||
if def.type ~= "node" or pointed_thing.type ~= "node" then
|
||||
return itemstack
|
||||
end
|
||||
|
||||
local under = pointed_thing.under
|
||||
local oldnode_under = minetest.env:get_node(under)
|
||||
local olddef_under = ItemStack({name=oldnode_under.name}):get_definition()
|
||||
olddef_under = olddef_under or minetest.nodedef_default
|
||||
local above = pointed_thing.above
|
||||
local oldnode_above = minetest.env:get_node(above)
|
||||
local olddef_above = ItemStack({name=oldnode_above.name}):get_definition()
|
||||
olddef_above = olddef_above or minetest.nodedef_default
|
||||
|
||||
if not olddef_above.buildable_to and not olddef_under.buildable_to then
|
||||
minetest.log("info", placer:get_player_name() .. " tried to place"
|
||||
.. " node in invalid position " .. minetest.pos_to_string(above)
|
||||
.. ", replacing " .. oldnode_above.name)
|
||||
return
|
||||
end
|
||||
|
||||
-- Place above pointed node
|
||||
local place_to = {x = above.x, y = above.y, z = above.z}
|
||||
|
||||
-- If node under is buildable_to, place into it instead (eg. snow)
|
||||
if olddef_under.buildable_to then
|
||||
minetest.log("info", "node under is buildable to")
|
||||
place_to = {x = under.x, y = under.y, z = under.z}
|
||||
end
|
||||
|
||||
minetest.log("action", placer:get_player_name() .. " places node "
|
||||
.. def.name .. " at " .. minetest.pos_to_string(place_to))
|
||||
|
||||
local oldnode = minetest.env:get_node(place_to)
|
||||
local newnode = {name = def.name, param1 = 0, param2 = 0}
|
||||
|
||||
-- Calculate direction for wall mounted stuff like torches and signs
|
||||
if def.paramtype2 == 'wallmounted' then
|
||||
local dir = {
|
||||
x = under.x - above.x,
|
||||
y = under.y - above.y,
|
||||
z = under.z - above.z
|
||||
}
|
||||
newnode.param2 = minetest.dir_to_wallmounted(dir)
|
||||
-- Calculate the direction for furnaces and chests and stuff
|
||||
elseif def.paramtype2 == 'facedir' then
|
||||
local placer_pos = placer:getpos()
|
||||
if placer_pos then
|
||||
local dir = {
|
||||
x = above.x - placer_pos.x,
|
||||
y = above.y - placer_pos.y,
|
||||
z = above.z - placer_pos.z
|
||||
}
|
||||
newnode.param2 = minetest.dir_to_facedir(dir)
|
||||
minetest.log("action", "facedir: " .. newnode.param2)
|
||||
end
|
||||
end
|
||||
|
||||
-- Add node and update
|
||||
minetest.env:add_node(place_to, newnode)
|
||||
|
||||
-- Run callback
|
||||
if def.after_place_node then
|
||||
-- Copy place_to because callback can modify it
|
||||
local place_to_copy = {x=place_to.x, y=place_to.y, z=place_to.z}
|
||||
def.after_place_node(place_to_copy, placer)
|
||||
end
|
||||
|
||||
-- Run script hook
|
||||
local _, callback
|
||||
for _, callback in ipairs(minetest.registered_on_placenodes) do
|
||||
-- Copy pos and node because callback can modify them
|
||||
local place_to_copy = {x=place_to.x, y=place_to.y, z=place_to.z}
|
||||
local newnode_copy = {name=newnode.name, param1=newnode.param1, param2=newnode.param2}
|
||||
local oldnode_copy = {name=oldnode.name, param1=oldnode.param1, param2=oldnode.param2}
|
||||
callback(place_to_copy, newnode_copy, placer, oldnode_copy)
|
||||
end
|
||||
|
||||
itemstack:take_item()
|
||||
return itemstack
|
||||
end
|
||||
|
||||
@@ -222,9 +251,11 @@ function minetest.node_punch(pos, node, puncher)
|
||||
-- Run script hook
|
||||
local _, callback
|
||||
for _, callback in ipairs(minetest.registered_on_punchnodes) do
|
||||
callback(pos, node, puncher)
|
||||
-- Copy pos and node because callback can modify them
|
||||
local pos_copy = {x=pos.x, y=pos.y, z=pos.z}
|
||||
local node_copy = {name=node.name, param1=node.param1, param2=node.param2}
|
||||
callback(pos_copy, node_copy, puncher)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function minetest.node_dig(pos, node, digger)
|
||||
@@ -242,29 +273,25 @@ function minetest.node_dig(pos, node, digger)
|
||||
minetest.log('action', digger:get_player_name() .. " digs "
|
||||
.. node.name .. " at " .. minetest.pos_to_string(pos))
|
||||
|
||||
if not minetest.setting_getbool("creative_mode") then
|
||||
local wielded = digger:get_wielded_item()
|
||||
local drops = minetest.get_node_drops(node.name, wielded:get_name())
|
||||
local wielded = digger:get_wielded_item()
|
||||
local drops = minetest.get_node_drops(node.name, wielded:get_name())
|
||||
|
||||
-- Wear out tool
|
||||
tp = wielded:get_tool_capabilities()
|
||||
dp = minetest.get_dig_params(def.groups, tp)
|
||||
wielded:add_wear(dp.wear)
|
||||
digger:set_wielded_item(wielded)
|
||||
-- Wear out tool
|
||||
local tp = wielded:get_tool_capabilities()
|
||||
local dp = minetest.get_dig_params(def.groups, tp)
|
||||
wielded:add_wear(dp.wear)
|
||||
digger:set_wielded_item(wielded)
|
||||
|
||||
-- Add dropped items to object's inventory
|
||||
if digger:get_inventory() then
|
||||
local _, dropped_item
|
||||
for _, dropped_item in ipairs(drops) do
|
||||
digger:get_inventory():add_item("main", dropped_item)
|
||||
end
|
||||
-- Add dropped items to object's inventory
|
||||
if digger:get_inventory() then
|
||||
local _, dropped_item
|
||||
for _, dropped_item in ipairs(drops) do
|
||||
digger:get_inventory():add_item("main", dropped_item)
|
||||
end
|
||||
end
|
||||
|
||||
local oldnode = nil
|
||||
|
||||
local oldmetadata = nil
|
||||
if def.after_dig_node then
|
||||
oldnode = node;
|
||||
oldmetadata = minetest.env:get_meta(pos):to_table()
|
||||
end
|
||||
|
||||
@@ -273,51 +300,22 @@ function minetest.node_dig(pos, node, digger)
|
||||
|
||||
-- Run callback
|
||||
if def.after_dig_node then
|
||||
def.after_dig_node(pos, oldnode, oldmetadata, digger)
|
||||
-- Copy pos and node because callback can modify them
|
||||
local pos_copy = {x=pos.x, y=pos.y, z=pos.z}
|
||||
local node_copy = {name=node.name, param1=node.param1, param2=node.param2}
|
||||
def.after_dig_node(pos_copy, node_copy, oldmetadata, digger)
|
||||
end
|
||||
|
||||
-- Run script hook (deprecated)
|
||||
-- Run script hook
|
||||
local _, callback
|
||||
for _, callback in ipairs(minetest.registered_on_dignodes) do
|
||||
callback(pos, node, digger)
|
||||
-- Copy pos and node because callback can modify them
|
||||
local pos_copy = {x=pos.x, y=pos.y, z=pos.z}
|
||||
local node_copy = {name=node.name, param1=node.param1, param2=node.param2}
|
||||
callback(pos_copy, node_copy, digger)
|
||||
end
|
||||
end
|
||||
|
||||
function minetest.node_metadata_inventory_move_allow_all(pos, from_list,
|
||||
from_index, to_list, to_index, count, player)
|
||||
minetest.log("verbose", "node_metadata_inventory_move_allow_all")
|
||||
local meta = minetest.env:get_meta(pos)
|
||||
local inv = meta:get_inventory()
|
||||
|
||||
local from_stack = inv:get_stack(from_list, from_index)
|
||||
local taken_items = from_stack:take_item(count)
|
||||
inv:set_stack(from_list, from_index, from_stack)
|
||||
|
||||
local to_stack = inv:get_stack(to_list, to_index)
|
||||
to_stack:add_item(taken_items)
|
||||
inv:set_stack(to_list, to_index, to_stack)
|
||||
end
|
||||
|
||||
function minetest.node_metadata_inventory_offer_allow_all(pos, listname, index, stack, player)
|
||||
minetest.log("verbose", "node_metadata_inventory_offer_allow_all")
|
||||
local meta = minetest.env:get_meta(pos)
|
||||
local inv = meta:get_inventory()
|
||||
local the_stack = inv:get_stack(listname, index)
|
||||
the_stack:add_item(stack)
|
||||
inv:set_stack(listname, index, the_stack)
|
||||
return ItemStack("")
|
||||
end
|
||||
|
||||
function minetest.node_metadata_inventory_take_allow_all(pos, listname, index, count, player)
|
||||
minetest.log("verbose", "node_metadata_inventory_take_allow_all")
|
||||
local meta = minetest.env:get_meta(pos)
|
||||
local inv = meta:get_inventory()
|
||||
local the_stack = inv:get_stack(listname, index)
|
||||
local taken_items = the_stack:take_item(count)
|
||||
inv:set_stack(listname, index, the_stack)
|
||||
return taken_items
|
||||
end
|
||||
|
||||
-- This is used to allow mods to redefine minetest.item_place and so on
|
||||
-- NOTE: This is not the preferred way. Preferred way is to provide enough
|
||||
-- callbacks to not require redefining global functions. -celeron55
|
||||
|
||||
@@ -244,6 +244,7 @@ minetest.register_item(":unknown", {
|
||||
inventory_image = "unknown_item.png",
|
||||
on_place = minetest.item_place,
|
||||
on_drop = minetest.item_drop,
|
||||
groups = {not_in_creative_inventory=1},
|
||||
})
|
||||
|
||||
minetest.register_node(":air", {
|
||||
@@ -258,6 +259,7 @@ minetest.register_node(":air", {
|
||||
diggable = false,
|
||||
buildable_to = true,
|
||||
air_equivalent = true,
|
||||
groups = {not_in_creative_inventory=1},
|
||||
})
|
||||
|
||||
minetest.register_node(":ignore", {
|
||||
@@ -272,23 +274,15 @@ minetest.register_node(":ignore", {
|
||||
diggable = false,
|
||||
buildable_to = true, -- A way to remove accidentally placed ignores
|
||||
air_equivalent = true,
|
||||
groups = {not_in_creative_inventory=1},
|
||||
})
|
||||
|
||||
-- The hand (bare definition)
|
||||
minetest.register_item(":", {
|
||||
type = "none",
|
||||
groups = {not_in_creative_inventory=1},
|
||||
})
|
||||
|
||||
--
|
||||
-- Creative inventory
|
||||
--
|
||||
|
||||
minetest.creative_inventory = {}
|
||||
|
||||
minetest.add_to_creative_inventory = function(itemstring)
|
||||
table.insert(minetest.creative_inventory, itemstring)
|
||||
end
|
||||
|
||||
--
|
||||
-- Callback registration
|
||||
--
|
||||
@@ -299,6 +293,12 @@ local function make_registration()
|
||||
return t, registerfunc
|
||||
end
|
||||
|
||||
local function make_registration_reverse()
|
||||
local t = {}
|
||||
local registerfunc = function(func) table.insert(t, 1, func) end
|
||||
return t, registerfunc
|
||||
end
|
||||
|
||||
minetest.registered_on_chat_messages, minetest.register_on_chat_message = make_registration()
|
||||
minetest.registered_globalsteps, minetest.register_globalstep = make_registration()
|
||||
minetest.registered_on_punchnodes, minetest.register_on_punchnode = make_registration()
|
||||
@@ -310,4 +310,5 @@ minetest.registered_on_dieplayers, minetest.register_on_dieplayer = make_registr
|
||||
minetest.registered_on_respawnplayers, minetest.register_on_respawnplayer = 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()
|
||||
|
||||
|
||||
@@ -44,5 +44,5 @@ minetest.register_privilege("fast", {
|
||||
description = "Can walk fast using the fast_move mode",
|
||||
give_to_singleplayer = false,
|
||||
})
|
||||
|
||||
minetest.register_privilege("rollback", "Can use the rollback functionality")
|
||||
|
||||
|
||||
224
doc/lua_api.txt
224
doc/lua_api.txt
@@ -254,9 +254,9 @@ Nodes are passed by value between Lua and the engine.
|
||||
They are represented by a table:
|
||||
{name="name", param1=num, param2=num}
|
||||
|
||||
param1 and param2 are 8 bit and 4 bit integers, respectively. The engine
|
||||
uses them for certain automated functions. If you don't use these
|
||||
functions, you can use them to store arbitrary values.
|
||||
param1 and param2 are 8 bit integers. The engine uses them for certain
|
||||
automated functions. If you don't use these functions, you can use them to
|
||||
store arbitrary values.
|
||||
|
||||
The functions of param1 and param2 are determined by certain fields in the
|
||||
node definition:
|
||||
@@ -423,7 +423,7 @@ effective towards.
|
||||
|
||||
Groups in crafting recipes
|
||||
---------------------------
|
||||
An example:
|
||||
An example: Make meat soup from any meat, any water and any bowl
|
||||
{
|
||||
output = 'food:meat_soup_raw',
|
||||
recipe = {
|
||||
@@ -431,7 +431,13 @@ An example:
|
||||
{'group:water'},
|
||||
{'group:bowl'},
|
||||
},
|
||||
preserve = {'group:bowl'}, -- Not implemented yet (TODO)
|
||||
-- preserve = {'group:bowl'}, -- Not implemented yet (TODO)
|
||||
}
|
||||
An another example: Make red wool from white wool and red dye
|
||||
{
|
||||
type = 'shapeless',
|
||||
output = 'wool:red',
|
||||
recipe = {'wool:white', 'group:dye,basecolor_red'},
|
||||
}
|
||||
|
||||
Special groups
|
||||
@@ -672,10 +678,12 @@ Examples:
|
||||
|
||||
Elements:
|
||||
|
||||
invsize[<W>,<H>;]
|
||||
size[<W>,<H>]
|
||||
^ Define the size of the menu in inventory slots
|
||||
^ deprecated: invsize[<W>,<H>;]
|
||||
|
||||
list[<inventory location>;<list name>;<X>,<Y>;<W>,<H>;]
|
||||
list[<inventory location>;<list name>;<X>,<Y>;<W>,<H>;<starting item index>]
|
||||
^ Show an inventory list
|
||||
|
||||
image[<X>,<Y>;<W>,<H>;<texture name>]
|
||||
@@ -684,20 +692,53 @@ image[<X>,<Y>;<W>,<H>;<texture name>]
|
||||
|
||||
field[<X>,<Y>;<W>,<H>;<name>;<label>;<default>]
|
||||
^ Textual field; will be sent to server when a button is clicked
|
||||
^ x and y position the field relative to the top left of the menu
|
||||
^ w and h are the size of the field
|
||||
^ fields are a set height, but will be vertically centred on h
|
||||
^ Position and size units are inventory slots
|
||||
^ name is the name of the field as returned in fields to on_receive_fields
|
||||
^ label, if not blank, will be text printed on the top left above the field
|
||||
^ default is the default value of the field
|
||||
^ default may contain variable references such as '${text}' which
|
||||
will fill the value from the metadata value 'text'
|
||||
^ Note: no extra text or more than a single variable is supported ATM.
|
||||
|
||||
field[<name>;<label>;<default>]
|
||||
^ as above but without position/size units
|
||||
^ special field for creating simple forms, such as sign text input
|
||||
^ must be used without a size[] element
|
||||
^ a 'Proceed' button will be added automatically
|
||||
|
||||
label[<X>,<Y>;<label>]
|
||||
^ x and y work as per field
|
||||
^ label is the text on the label
|
||||
^ Position and size units are inventory slots
|
||||
^ Not implemented
|
||||
|
||||
button[<X>,<Y>;<W>,<H>;<name>;<label>]
|
||||
^ Clickable button. When clicked, fields will be sent.
|
||||
^ Button will be visible as a field, with the value "active".
|
||||
^ x, y and name work as per field
|
||||
^ w and h are the size of the button
|
||||
^ label is the text on the button
|
||||
^ Position and size units are inventory slots
|
||||
^ Not implemented
|
||||
|
||||
image_button[<X>,<Y>;<W>,<H>;<texture name>;<name>;<label>]
|
||||
^ x, y, w, h, and name work as per button
|
||||
^ image is the filename of an image
|
||||
^ Position and size units are inventory slots
|
||||
|
||||
button_exit[<X>,<Y>;<W>,<H>;<name>;<label>]
|
||||
^ When clicked, fields will be sent and the form will quit.
|
||||
|
||||
image_button_exit[<X>,<Y>;<W>,<H>;<texture name>;<name>;<label>]
|
||||
^ When clicked, fields will be sent and the form will quit.
|
||||
|
||||
Inventory location:
|
||||
|
||||
- "context": Selected node metadata (deprecated: "current_name")
|
||||
- "current_player": Player to whom the menu is shown
|
||||
- "player:<name>": Any player
|
||||
- "nodemeta:<X>,<Y>,<Z>": Any node metadata
|
||||
- "detached:<name>": A detached inventory
|
||||
|
||||
Helper functions
|
||||
-----------------
|
||||
@@ -718,6 +759,8 @@ minetest namespace reference
|
||||
minetest.get_current_modname() -> string
|
||||
minetest.get_modpath(modname) -> eg. "/home/user/.minetest/usermods/modname"
|
||||
^ Useful for loading additional .lua modules or static data from mod
|
||||
minetest.get_modnames() -> list of installed mods
|
||||
^ Return a list of installed mods, sorted alphabetically
|
||||
minetest.get_worldpath() -> eg. "/home/user/.minetest/world"
|
||||
^ Useful for storing custom data
|
||||
minetest.is_singleplayer()
|
||||
@@ -740,11 +783,11 @@ minetest.register_craft(recipe)
|
||||
Global callback registration functions: (Call these only at load time)
|
||||
minetest.register_globalstep(func(dtime))
|
||||
^ Called every server step, usually interval of 0.05s
|
||||
minetest.register_on_placenode(func(pos, newnode, placer))
|
||||
minetest.register_on_placenode(func(pos, newnode, placer, oldnode))
|
||||
^ Called when a node has been placed
|
||||
^ Deprecated: Use on_construct or after_place_node in node definition instead
|
||||
minetest.register_on_dignode(func(pos, oldnode, digger))
|
||||
^ Called when a node has been dug. digger can be nil.
|
||||
^ Called when a node has been dug.
|
||||
^ Deprecated: Use on_destruct or after_dig_node in node definition instead
|
||||
minetest.register_on_punchnode(func(pos, node, puncher))
|
||||
^ Called when a node is punched
|
||||
@@ -764,6 +807,11 @@ minetest.register_on_joinplayer(func(ObjectRef))
|
||||
minetest.register_on_leaveplayer(func(ObjectRef))
|
||||
^ Called when a player leaves the game
|
||||
minetest.register_on_chat_message(func(name, message))
|
||||
^ Called always when a player says something
|
||||
minetest.register_on_player_receive_fields(func(player, formname, fields))
|
||||
^ Called when a button is pressed in player's inventory form
|
||||
^ Newest functions are called first
|
||||
^ If function returns true, remaining functions are not called
|
||||
|
||||
Other registration functions:
|
||||
minetest.register_chatcommand(cmd, chatcommand definition)
|
||||
@@ -808,6 +856,10 @@ Inventory:
|
||||
minetest.get_inventory(location) -> InvRef
|
||||
^ location = eg. {type="player", name="celeron55"}
|
||||
{type="node", pos={x=, y=, z=}}
|
||||
{type="detached", name="creative"}
|
||||
minetest.create_detached_inventory(name, callbacks) -> InvRef
|
||||
^ callbacks: See "Detached inventory callbacks"
|
||||
^ Creates a detached inventory. If it already exists, it is cleared.
|
||||
|
||||
Item handling:
|
||||
minetest.inventorycube(img1, img2, img3)
|
||||
@@ -837,6 +889,14 @@ minetest.get_craft_recipe(output) -> input
|
||||
stack 5, stack 6, stack 7, stack 8, stack 9 }
|
||||
^ input.items = nil if no recipe found
|
||||
|
||||
Rollbacks:
|
||||
minetest.rollback_get_last_node_actor(p, range, seconds) -> actor, p, seconds
|
||||
^ 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
|
||||
^ Revert latest actions of someone
|
||||
^ actor: "player:<name>", also "liquid".
|
||||
|
||||
Defaults for the on_* item definition functions:
|
||||
(These return the leftover itemstack)
|
||||
minetest.item_place_node(itemstack, placer, pointed_thing)
|
||||
@@ -939,12 +999,14 @@ methods:
|
||||
^ Dig node with the same effects that a player would cause
|
||||
- punch_node(pos)
|
||||
^ Punch node with the same effects that a player would cause
|
||||
|
||||
- get_meta(pos) -- Get a NodeMetaRef at that position
|
||||
- get_node_timer(pos) -- Get NodeTimerRef
|
||||
|
||||
- add_entity(pos, name): Spawn Lua-defined entity at position
|
||||
^ Returns ObjectRef, or nil if failed
|
||||
- add_item(pos, item): Spawn item
|
||||
^ Returns ObjectRef, or nil if failed
|
||||
- get_meta(pos) -- Get a NodeMetaRef at that position
|
||||
- get_player_by_name(name) -- Get an ObjectRef to a player
|
||||
- get_objects_inside_radius(pos, radius)
|
||||
- set_timeofday(val): val: 0...1; 0 = midnight, 0.5 = midday
|
||||
@@ -973,6 +1035,26 @@ methods:
|
||||
- to_table() -> nil or {fields = {...}, inventory = {list1 = {}, ...}}
|
||||
- from_table(nil or {})
|
||||
^ See "Node Metadata"
|
||||
|
||||
NodeTimerRef: Node Timers - a high resolution persistent per-node timer
|
||||
- Can be gotten via minetest.env:get_node_timer(pos)
|
||||
methods:
|
||||
- set(timeout,elapsed)
|
||||
^ set a timer's state
|
||||
^ timeout is in seconds, and supports fractional values (0.1 etc)
|
||||
^ elapsed is in seconds, and supports fractional values (0.1 etc)
|
||||
^ will trigger the node's on_timer function after timeout-elapsed seconds
|
||||
- start(timeout)
|
||||
^ start a timer
|
||||
^ equivelent to set(timeout,0)
|
||||
- stop()
|
||||
^ stops the timer
|
||||
- get_timeout() -> current timeout in seconds
|
||||
^ if timeout is 0, timer is inactive
|
||||
- get_elapsed() -> current elapsed time in seconds
|
||||
^ the node's on_timer function will be called after timeout-elapsed seconds
|
||||
- is_started() -> boolean state of timer
|
||||
^ returns true if timer is started, otherwise false
|
||||
|
||||
ObjectRef: Moving things in the game are generally these
|
||||
(basically reference to a C++ ServerActiveObject)
|
||||
@@ -1219,30 +1301,29 @@ Node definition (register_node)
|
||||
drawtype = "normal", -- See "Node drawtypes"
|
||||
visual_scale = 1.0,
|
||||
tiles = {tile definition 1, def2, def3, def4, def5, def6},
|
||||
^ Textures of node; +Y, -Y, +X, -X, +Z, -Z (old field name: tile_images)
|
||||
^ List can be shortened to needed length
|
||||
^ Old field name: tile_images
|
||||
special_tiles = {tile definition 1, Tile definition 2},
|
||||
^ Special textures of node; used rarely (old field name: special_materials)
|
||||
^ List can be shortened to needed length
|
||||
^ Old field name: special_materials
|
||||
alpha = 255,
|
||||
post_effect_color = {a=0, r=0, g=0, b=0},
|
||||
paramtype = "none",
|
||||
paramtype2 = "none",
|
||||
is_ground_content = false,
|
||||
sunlight_propagates = false,
|
||||
walkable = true,
|
||||
pointable = true,
|
||||
diggable = true,
|
||||
climbable = false,
|
||||
buildable_to = false,
|
||||
drop = "",
|
||||
-- alternatively drop = { max_items = ..., items = { ... } }
|
||||
liquidtype = "none",
|
||||
liquid_alternative_flowing = "",
|
||||
liquid_alternative_source = "",
|
||||
liquid_viscosity = 0,
|
||||
light_source = 0,
|
||||
damage_per_second = 0,
|
||||
post_effect_color = {a=0, r=0, g=0, b=0}, -- If player is inside node
|
||||
paramtype = "none", -- See "Nodes"
|
||||
paramtype2 = "none", -- See "Nodes"
|
||||
is_ground_content = false, -- Currently not used for anything
|
||||
sunlight_propagates = false, -- If true, sunlight will go infinitely through this
|
||||
walkable = true, -- If true, objects collide with node
|
||||
pointable = true, -- If true, can be pointed at
|
||||
diggable = true, -- If false, can never be dug
|
||||
climbable = false, -- If true, can be climbed on (ladder)
|
||||
buildable_to = false, -- If true, placed nodes can replace this node
|
||||
drop = "", -- alternatively drop = { max_items = ..., items = { ... } }
|
||||
liquidtype = "none", -- "none"/"source"/"flowing"
|
||||
liquid_alternative_flowing = "", -- Flowing version of source liquid
|
||||
liquid_alternative_source = "", -- Source version of flowing liquid
|
||||
liquid_viscosity = 0, -- Higher viscosity = slower flow (max. 7)
|
||||
light_source = 0, -- Amount of light emitted by node
|
||||
damage_per_second = 0, -- If player is inside node, this damage is caused
|
||||
node_box = {type="regular"}, -- See "Node boxes"
|
||||
selection_box = {type="regular"}, -- See "Node boxes"
|
||||
legacy_facedir_simple = false, -- Support maps made in and before January 2012
|
||||
@@ -1280,44 +1361,42 @@ Node definition (register_node)
|
||||
on_punch = func(pos, node, puncher),
|
||||
^ default: minetest.node_punch
|
||||
^ By default: does nothing
|
||||
on_dig = func(pos, node, digger),
|
||||
on_dig = func(pos, node, digger),
|
||||
^ default: minetest.node_dig
|
||||
^ By default: checks privileges, wears out tool and removes node
|
||||
|
||||
on_timer = function(pos,elapsed),
|
||||
^ default: nil
|
||||
^ called by NodeTimers, see EnvRef and NodeTimerRef
|
||||
^ elapsed is the total time passed since the timer was started
|
||||
^ return true to run the timer for another cycle with the same timeout value
|
||||
|
||||
on_receive_fields = func(pos, formname, fields, sender),
|
||||
^ fields = {name1 = value1, name2 = value2, ...}
|
||||
^ Called when an UI form (eg. sign text input) returns data
|
||||
^ default: nil
|
||||
|
||||
on_metadata_inventory_move = func(pos, from_list, from_index,
|
||||
to_list, to_index, count, player),
|
||||
^ Called when a player wants to move items inside the metadata
|
||||
^ Should move items, or some items, if permitted. If not, should do
|
||||
nothing.
|
||||
^ The engine ensures the action is valid, i.e. the stack fits at the
|
||||
given position
|
||||
^ default: minetest.node_metadata_inventory_move_allow_all
|
||||
allow_metadata_inventory_move = func(pos, from_list, from_index,
|
||||
to_list, to_index, count, player),
|
||||
^ Called when a player wants to move items inside the inventory
|
||||
^ Return value: number of items allowed to move
|
||||
|
||||
allow_metadata_inventory_put = func(pos, listname, index, stack, player),
|
||||
^ Called when a player wants to put something into the inventory
|
||||
^ Return value: number of items allowed to put
|
||||
^ Return value: -1: Allow and don't modify item count in inventory
|
||||
|
||||
allow_metadata_inventory_take = func(pos, listname, index, stack, player),
|
||||
^ Called when a player wants to take something out of the inventory
|
||||
^ Return value: number of items allowed to take
|
||||
^ Return value: -1: Allow and don't modify item count in inventory
|
||||
|
||||
on_metadata_inventory_offer = func(pos, listname, index, stack, player),
|
||||
^ Called when a player wants to put something into the metadata
|
||||
inventory
|
||||
^ Should check if the action is permitted (the engine ensures the
|
||||
action is valid, i.e. the stack fits at the given position)
|
||||
^ If permitted, modify the metadata inventory and return the
|
||||
"leftover" stack (normally nil).
|
||||
^ If not permitted, return itemstack.
|
||||
^ default: minetest.node_metadata_inventory_offer_allow_all
|
||||
|
||||
on_metadata_inventory_take = func(pos, listname, index, count, player),
|
||||
^ Called when a player wants to take something out of the metadata
|
||||
inventory
|
||||
^ Should check if the action is permitted (the engine ensures the
|
||||
action is valid, i.e. there's a stack of at least “count” items at
|
||||
that position)
|
||||
^ If permitted, modify the metadata inventory and return the
|
||||
stack of items
|
||||
^ If not permitted, return nil.
|
||||
^ default: minetest.node_metadata_inventory_take_allow_all
|
||||
on_metadata_inventory_move = func(pos, from_list, from_index,
|
||||
to_list, to_index, count, player),
|
||||
on_metadata_inventory_put = func(pos, listname, index, stack, player),
|
||||
on_metadata_inventory_take = func(pos, listname, index, stack, player),
|
||||
^ Called after the actual action has happened, according to what was allowed.
|
||||
^ No return value
|
||||
}
|
||||
|
||||
Recipe for register_craft: (shaped)
|
||||
@@ -1374,3 +1453,26 @@ Chatcommand definition (register_chatcommand)
|
||||
func = function(name, param), -- called when command is run
|
||||
}
|
||||
|
||||
Detached inventory callbacks
|
||||
{
|
||||
allow_move = func(inv, from_list, from_index, to_list, to_index, count, player),
|
||||
^ Called when a player wants to move items inside the inventory
|
||||
^ Return value: number of items allowed to move
|
||||
|
||||
allow_put = func(inv, listname, index, stack, player),
|
||||
^ Called when a player wants to put something into the inventory
|
||||
^ Return value: number of items allowed to put
|
||||
^ Return value: -1: Allow and don't modify item count in inventory
|
||||
|
||||
allow_take = func(inv, listname, index, stack, player),
|
||||
^ Called when a player wants to take something out of the inventory
|
||||
^ Return value: number of items allowed to take
|
||||
^ Return value: -1: Allow and don't modify item count in inventory
|
||||
|
||||
on_move = func(inv, from_list, from_index, to_list, to_index, count, player),
|
||||
on_put = func(inv, listname, index, stack, player),
|
||||
on_take = func(inv, listname, index, stack, player),
|
||||
^ Called after the actual action has happened, according to what was allowed.
|
||||
^ No return value
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
=============================
|
||||
Minetest World Format 22...23
|
||||
Minetest World Format 22...25
|
||||
=============================
|
||||
|
||||
This applies to a world format carrying the block serialization version
|
||||
22...23, used at least in
|
||||
- 0.4.dev-20120322 ... 0.4.dev-20120606
|
||||
- 0.4.0.
|
||||
22...25, used at least in
|
||||
- 0.4.dev-20120322 ... 0.4.dev-20120606 (22...23)
|
||||
- 0.4.0 (23)
|
||||
- 24 was never released as stable and existed for ~2 days
|
||||
|
||||
The block serialization version does not fully specify every aspect of this
|
||||
format; if compliance with this format is to be checked, it needs to be
|
||||
@@ -262,17 +263,26 @@ u8 flags
|
||||
|
||||
u8 content_width
|
||||
- Number of bytes in the content (param0) fields of nodes
|
||||
- Always 1
|
||||
if map format version <= 23:
|
||||
- Always 1
|
||||
if map format version >= 24:
|
||||
- Always 2
|
||||
|
||||
u8 params_width
|
||||
- Number of bytes used for parameters per node
|
||||
- Always 2
|
||||
|
||||
zlib-compressed node data:
|
||||
- content:
|
||||
u8[4096]: param0 fields
|
||||
u8[4096]: param1 fields
|
||||
u8[4096]: param2 fields
|
||||
if content_width == 1:
|
||||
- content:
|
||||
u8[4096]: param0 fields
|
||||
u8[4096]: param1 fields
|
||||
u8[4096]: param2 fields
|
||||
if content_width == 2:
|
||||
- content:
|
||||
u16[4096]: param0 fields
|
||||
u8[4096]: param1 fields
|
||||
u8[4096]: param2 fields
|
||||
- The location of a node in each of those arrays is (z*16*16 + y*16 + x).
|
||||
|
||||
zlib-compressed node metadata list
|
||||
@@ -285,9 +295,19 @@ zlib-compressed node metadata list
|
||||
u16 content_size
|
||||
u8[content_size] (content of metadata)
|
||||
|
||||
- unused node timers (version will be 24 when they are actually used):
|
||||
if version == 23:
|
||||
- Node timers
|
||||
if map format version == 23:
|
||||
u8 unused version (always 0)
|
||||
if map format version == 24: (NOTE: Not released as stable)
|
||||
u8 nodetimer_version
|
||||
if nodetimer_version == 0:
|
||||
(nothing else)
|
||||
if nodetimer_version == 1:
|
||||
u16 num_of_timers
|
||||
foreach num_of_timers:
|
||||
u16 timer position (z*16*16 + y*16 + x)
|
||||
s32 timeout*1000
|
||||
s32 elapsed*1000
|
||||
|
||||
u8 static object version:
|
||||
- Always 0
|
||||
@@ -317,17 +337,29 @@ foreach num_name_id_mappings
|
||||
u16 name_len
|
||||
u8[name_len] name
|
||||
|
||||
- Node timers
|
||||
if map format version == 25:
|
||||
u8 length of the data of a single timer (always 2+4+4=10)
|
||||
u16 num_of_timers
|
||||
foreach num_of_timers:
|
||||
u16 timer position (z*16*16 + y*16 + x)
|
||||
s32 timeout*1000
|
||||
s32 elapsed*1000
|
||||
|
||||
EOF.
|
||||
|
||||
Format of nodes
|
||||
----------------
|
||||
A node is composed of the u8 fields param0, param1 and param2.
|
||||
|
||||
The content id of a node is determined as so:
|
||||
- If param0 < 0x80,
|
||||
content_id = param0
|
||||
- Otherwise
|
||||
content_id = (param0<<4) + (param2>>4)
|
||||
if map format version <= 23:
|
||||
The content id of a node is determined as so:
|
||||
- If param0 < 0x80,
|
||||
content_id = param0
|
||||
- Otherwise
|
||||
content_id = (param0<<4) + (param2>>4)
|
||||
if map format version >= 24:
|
||||
The content id of a node is param0.
|
||||
|
||||
The purpose of param1 and param2 depend on the definition of the node.
|
||||
|
||||
|
||||
@@ -14,6 +14,14 @@ default = {}
|
||||
-- Load other files
|
||||
dofile(minetest.get_modpath("default").."/mapgen.lua")
|
||||
|
||||
-- Set a noticeable inventory formspec for players
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
local cb = function(player)
|
||||
minetest.chat_send_player(player:get_player_name(), "This is the [minimal] \"Minimal Development Test\" game. Use [minetest_game] for the real thing.")
|
||||
end
|
||||
minetest.after(2.0, cb, player)
|
||||
end)
|
||||
|
||||
--
|
||||
-- Tool definition
|
||||
--
|
||||
@@ -1125,7 +1133,7 @@ minetest.register_node("default:sign_wall", {
|
||||
on_construct = function(pos)
|
||||
--local n = minetest.env:get_node(pos)
|
||||
local meta = minetest.env:get_meta(pos)
|
||||
meta:set_string("formspec", "hack:sign_text_input")
|
||||
meta:set_string("formspec", "field[text;;${text}]")
|
||||
meta:set_string("infotext", "\"\"")
|
||||
end,
|
||||
on_receive_fields = function(pos, formname, fields, sender)
|
||||
@@ -1150,7 +1158,7 @@ minetest.register_node("default:chest", {
|
||||
on_construct = function(pos)
|
||||
local meta = minetest.env:get_meta(pos)
|
||||
meta:set_string("formspec",
|
||||
"invsize[8,9;]"..
|
||||
"size[8,9]"..
|
||||
"list[current_name;main;0,0;8,4;]"..
|
||||
"list[current_player;main;0,5;8,4;]")
|
||||
meta:set_string("infotext", "Chest")
|
||||
@@ -1162,25 +1170,6 @@ minetest.register_node("default:chest", {
|
||||
local inv = meta:get_inventory()
|
||||
return inv:is_empty("main")
|
||||
end,
|
||||
on_metadata_inventory_move = function(pos, from_list, from_index,
|
||||
to_list, to_index, count, player)
|
||||
minetest.log("action", player:get_player_name()..
|
||||
" moves stuff in chest at "..minetest.pos_to_string(pos))
|
||||
return minetest.node_metadata_inventory_move_allow_all(
|
||||
pos, from_list, from_index, to_list, to_index, count, player)
|
||||
end,
|
||||
on_metadata_inventory_offer = function(pos, listname, index, stack, player)
|
||||
minetest.log("action", player:get_player_name()..
|
||||
" moves stuff to chest at "..minetest.pos_to_string(pos))
|
||||
return minetest.node_metadata_inventory_offer_allow_all(
|
||||
pos, listname, index, stack, player)
|
||||
end,
|
||||
on_metadata_inventory_take = function(pos, listname, index, count, player)
|
||||
minetest.log("action", player:get_player_name()..
|
||||
" takes stuff from chest at "..minetest.pos_to_string(pos))
|
||||
return minetest.node_metadata_inventory_take_allow_all(
|
||||
pos, listname, index, count, player)
|
||||
end,
|
||||
})
|
||||
|
||||
local function has_locked_chest_privilege(meta, player)
|
||||
@@ -1207,7 +1196,7 @@ minetest.register_node("default:chest_locked", {
|
||||
on_construct = function(pos)
|
||||
local meta = minetest.env:get_meta(pos)
|
||||
meta:set_string("formspec",
|
||||
"invsize[8,9;]"..
|
||||
"size[8,9]"..
|
||||
"list[current_name;main;0,0;8,4;]"..
|
||||
"list[current_player;main;0,5;8,4;]")
|
||||
meta:set_string("infotext", "Locked Chest")
|
||||
@@ -1220,53 +1209,55 @@ minetest.register_node("default:chest_locked", {
|
||||
local inv = meta:get_inventory()
|
||||
return inv:is_empty("main")
|
||||
end,
|
||||
on_metadata_inventory_move = function(pos, from_list, from_index,
|
||||
to_list, to_index, count, player)
|
||||
allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
|
||||
local meta = minetest.env:get_meta(pos)
|
||||
if not has_locked_chest_privilege(meta, player) then
|
||||
minetest.log("action", player:get_player_name()..
|
||||
" tried to access a locked chest belonging to "..
|
||||
meta:get_string("owner").." at "..
|
||||
minetest.pos_to_string(pos))
|
||||
return
|
||||
return 0
|
||||
end
|
||||
return count
|
||||
end,
|
||||
allow_metadata_inventory_put = function(pos, listname, index, stack, player)
|
||||
local meta = minetest.env:get_meta(pos)
|
||||
if not has_locked_chest_privilege(meta, player) then
|
||||
minetest.log("action", player:get_player_name()..
|
||||
" tried to access a locked chest belonging to "..
|
||||
meta:get_string("owner").." at "..
|
||||
minetest.pos_to_string(pos))
|
||||
return 0
|
||||
end
|
||||
return stack:get_count()
|
||||
end,
|
||||
allow_metadata_inventory_take = function(pos, listname, index, stack, player)
|
||||
local meta = minetest.env:get_meta(pos)
|
||||
if not has_locked_chest_privilege(meta, player) then
|
||||
minetest.log("action", player:get_player_name()..
|
||||
" tried to access a locked chest belonging to "..
|
||||
meta:get_string("owner").." at "..
|
||||
minetest.pos_to_string(pos))
|
||||
return 0
|
||||
end
|
||||
return stack:get_count()
|
||||
end,
|
||||
on_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
|
||||
minetest.log("action", player:get_player_name()..
|
||||
" moves stuff in locked chest at "..minetest.pos_to_string(pos))
|
||||
return minetest.node_metadata_inventory_move_allow_all(
|
||||
pos, from_list, from_index, to_list, to_index, count, player)
|
||||
end,
|
||||
on_metadata_inventory_offer = function(pos, listname, index, stack, player)
|
||||
local meta = minetest.env:get_meta(pos)
|
||||
if not has_locked_chest_privilege(meta, player) then
|
||||
minetest.log("action", player:get_player_name()..
|
||||
" tried to access a locked chest belonging to "..
|
||||
meta:get_string("owner").." at "..
|
||||
minetest.pos_to_string(pos))
|
||||
return stack
|
||||
end
|
||||
on_metadata_inventory_put = function(pos, listname, index, stack, player)
|
||||
minetest.log("action", player:get_player_name()..
|
||||
" moves stuff to locked chest at "..minetest.pos_to_string(pos))
|
||||
return minetest.node_metadata_inventory_offer_allow_all(
|
||||
pos, listname, index, stack, player)
|
||||
end,
|
||||
on_metadata_inventory_take = function(pos, listname, index, count, player)
|
||||
local meta = minetest.env:get_meta(pos)
|
||||
if not has_locked_chest_privilege(meta, player) then
|
||||
minetest.log("action", player:get_player_name()..
|
||||
" tried to access a locked chest belonging to "..
|
||||
meta:get_string("owner").." at "..
|
||||
minetest.pos_to_string(pos))
|
||||
return
|
||||
end
|
||||
on_metadata_inventory_take = function(pos, listname, index, stack, player)
|
||||
minetest.log("action", player:get_player_name()..
|
||||
" takes stuff from locked chest at "..minetest.pos_to_string(pos))
|
||||
return minetest.node_metadata_inventory_take_allow_all(
|
||||
pos, listname, index, count, player)
|
||||
end,
|
||||
})
|
||||
|
||||
default.furnace_inactive_formspec =
|
||||
"invsize[8,9;]"..
|
||||
"size[8,9]"..
|
||||
"image[2,2;1,1;default_furnace_fire_bg.png]"..
|
||||
"list[current_name;fuel;2,3;1,1;]"..
|
||||
"list[current_name;src;2,1;1,1;]"..
|
||||
@@ -1405,7 +1396,7 @@ minetest.register_abm({
|
||||
meta:set_string("infotext","Furnace active: "..percent.."%")
|
||||
hacky_swap_node(pos,"default:furnace_active")
|
||||
meta:set_string("formspec",
|
||||
"invsize[8,9;]"..
|
||||
"size[8,9]"..
|
||||
"image[2,2;1,1;default_furnace_fire_bg.png^[lowpart:"..
|
||||
(100-percent)..":default_furnace_fire_fg.png]"..
|
||||
"list[current_name;fuel;2,3;1,1;]"..
|
||||
@@ -1572,40 +1563,6 @@ minetest.register_craftitem("default:scorched_stuff", {
|
||||
inventory_image = "default_scorched_stuff.png",
|
||||
})
|
||||
|
||||
--
|
||||
-- Creative inventory
|
||||
--
|
||||
|
||||
minetest.add_to_creative_inventory('default:pick_mese')
|
||||
minetest.add_to_creative_inventory('default:pick_steel')
|
||||
minetest.add_to_creative_inventory('default:axe_steel')
|
||||
minetest.add_to_creative_inventory('default:shovel_steel')
|
||||
|
||||
minetest.add_to_creative_inventory('default:torch')
|
||||
minetest.add_to_creative_inventory('default:cobble')
|
||||
minetest.add_to_creative_inventory('default:dirt')
|
||||
minetest.add_to_creative_inventory('default:stone')
|
||||
minetest.add_to_creative_inventory('default:sand')
|
||||
minetest.add_to_creative_inventory('default:sandstone')
|
||||
minetest.add_to_creative_inventory('default:clay')
|
||||
minetest.add_to_creative_inventory('default:brick')
|
||||
minetest.add_to_creative_inventory('default:tree')
|
||||
minetest.add_to_creative_inventory('default:wood')
|
||||
minetest.add_to_creative_inventory('default:leaves')
|
||||
minetest.add_to_creative_inventory('default:cactus')
|
||||
minetest.add_to_creative_inventory('default:papyrus')
|
||||
minetest.add_to_creative_inventory('default:bookshelf')
|
||||
minetest.add_to_creative_inventory('default:glass')
|
||||
minetest.add_to_creative_inventory('default:fence_wood')
|
||||
minetest.add_to_creative_inventory('default:rail')
|
||||
minetest.add_to_creative_inventory('default:mese')
|
||||
minetest.add_to_creative_inventory('default:chest')
|
||||
minetest.add_to_creative_inventory('default:furnace')
|
||||
minetest.add_to_creative_inventory('default:sign_wall')
|
||||
minetest.add_to_creative_inventory('default:water_source')
|
||||
minetest.add_to_creative_inventory('default:lava_source')
|
||||
minetest.add_to_creative_inventory('default:ladder')
|
||||
|
||||
--
|
||||
-- Aliases for the current map generator outputs
|
||||
--
|
||||
@@ -1722,133 +1679,24 @@ function on_punchnode(p, node)
|
||||
end
|
||||
minetest.register_on_punchnode(on_punchnode)
|
||||
|
||||
local function handle_give_command(cmd, giver, receiver, stackstring)
|
||||
if not minetest.get_player_privs(giver)["give"] then
|
||||
minetest.chat_send_player(giver, "error: you don't have permission to give")
|
||||
return
|
||||
end
|
||||
minetest.debug("DEBUG: "..cmd..' invoked, stackstring="'..stackstring..'"')
|
||||
minetest.log(cmd..' invoked, stackstring="'..stackstring..'"')
|
||||
local itemstack = ItemStack(stackstring)
|
||||
if itemstack:is_empty() then
|
||||
minetest.chat_send_player(giver, 'error: cannot give an empty item')
|
||||
return
|
||||
elseif not itemstack:is_known() then
|
||||
minetest.chat_send_player(giver, 'error: cannot give an unknown item')
|
||||
return
|
||||
end
|
||||
local receiverref = minetest.env:get_player_by_name(receiver)
|
||||
if receiverref == nil then
|
||||
minetest.chat_send_player(giver, receiver..' is not a known player')
|
||||
return
|
||||
end
|
||||
local leftover = receiverref:get_inventory():add_item("main", itemstack)
|
||||
if leftover:is_empty() then
|
||||
partiality = ""
|
||||
elseif leftover:get_count() == itemstack:get_count() then
|
||||
partiality = "could not be "
|
||||
else
|
||||
partiality = "partially "
|
||||
end
|
||||
-- The actual item stack string may be different from what the "giver"
|
||||
-- entered (e.g. big numbers are always interpreted as 2^16-1).
|
||||
stackstring = itemstack:to_string()
|
||||
if giver == receiver then
|
||||
minetest.chat_send_player(giver, '"'..stackstring
|
||||
..'" '..partiality..'added to inventory.');
|
||||
else
|
||||
minetest.chat_send_player(giver, '"'..stackstring
|
||||
..'" '..partiality..'added to '..receiver..'\'s inventory.');
|
||||
minetest.chat_send_player(receiver, '"'..stackstring
|
||||
..'" '..partiality..'added to inventory.');
|
||||
end
|
||||
end
|
||||
|
||||
minetest.register_on_chat_message(function(name, message)
|
||||
--print("default on_chat_message: name="..dump(name).." message="..dump(message))
|
||||
local cmd = "/giveme"
|
||||
if message:sub(0, #cmd) == cmd then
|
||||
local stackstring = string.match(message, cmd.." (.*)")
|
||||
if stackstring == nil then
|
||||
minetest.chat_send_player(name, 'usage: '..cmd..' stackstring')
|
||||
return true -- Handled chat message
|
||||
end
|
||||
handle_give_command(cmd, name, name, stackstring)
|
||||
return true
|
||||
end
|
||||
local cmd = "/give"
|
||||
if message:sub(0, #cmd) == cmd then
|
||||
local receiver, stackstring = string.match(message, cmd.." ([%a%d_-]+) (.*)")
|
||||
if receiver == nil or stackstring == nil then
|
||||
minetest.chat_send_player(name, 'usage: '..cmd..' name stackstring')
|
||||
return true -- Handled chat message
|
||||
end
|
||||
handle_give_command(cmd, name, receiver, stackstring)
|
||||
return true
|
||||
end
|
||||
local cmd = "/spawnentity"
|
||||
if message:sub(0, #cmd) == cmd then
|
||||
if not minetest.get_player_privs(name)["give"] then
|
||||
minetest.chat_send_player(name, "you don't have permission to spawn (give)")
|
||||
return true -- Handled chat message
|
||||
end
|
||||
if not minetest.get_player_privs(name)["interact"] then
|
||||
minetest.chat_send_player(name, "you don't have permission to interact")
|
||||
return true -- Handled chat message
|
||||
end
|
||||
local entityname = string.match(message, cmd.." (.*)")
|
||||
if entityname == nil then
|
||||
minetest.chat_send_player(name, 'usage: '..cmd..' entityname')
|
||||
return true -- Handled chat message
|
||||
end
|
||||
print(cmd..' invoked, entityname="'..entityname..'"')
|
||||
local player = minetest.env:get_player_by_name(name)
|
||||
if player == nil then
|
||||
print("Unable to spawn entity, player is nil")
|
||||
return true -- Handled chat message
|
||||
end
|
||||
local p = player:getpos()
|
||||
p.y = p.y + 1
|
||||
minetest.env:add_entity(p, entityname)
|
||||
minetest.chat_send_player(name, '"'..entityname
|
||||
..'" spawned.');
|
||||
return true -- Handled chat message
|
||||
end
|
||||
local cmd = "/pulverize"
|
||||
if message:sub(0, #cmd) == cmd then
|
||||
local player = minetest.env:get_player_by_name(name)
|
||||
if player == nil then
|
||||
print("Unable to pulverize, player is nil")
|
||||
return true -- Handled chat message
|
||||
end
|
||||
if player:get_wielded_item():is_empty() then
|
||||
minetest.chat_send_player(name, 'Unable to pulverize, no item in hand.')
|
||||
else
|
||||
player:set_wielded_item(nil)
|
||||
minetest.chat_send_player(name, 'An item was pulverized.')
|
||||
end
|
||||
return true
|
||||
end
|
||||
end)
|
||||
|
||||
--
|
||||
-- Test some things
|
||||
--
|
||||
|
||||
local function test_get_craft_result()
|
||||
print("test_get_craft_result()")
|
||||
minetest.log("info", "test_get_craft_result()")
|
||||
-- normal
|
||||
local input = {
|
||||
method = "normal",
|
||||
width = 2,
|
||||
items = {"", "default:coal_lump", "", "default:stick"}
|
||||
}
|
||||
print("torch crafting input: "..dump(input))
|
||||
minetest.log("info", "torch crafting input: "..dump(input))
|
||||
local output, decremented_input = minetest.get_craft_result(input)
|
||||
print("torch crafting output: "..dump(output))
|
||||
print("torch crafting decremented input: "..dump(decremented_input))
|
||||
minetest.log("info", "torch crafting output: "..dump(output))
|
||||
minetest.log("info", "torch crafting decremented input: "..dump(decremented_input))
|
||||
assert(output.item)
|
||||
print("torch crafting output.item:to_table(): "..dump(output.item:to_table()))
|
||||
minetest.log("info", "torch crafting output.item:to_table(): "..dump(output.item:to_table()))
|
||||
assert(output.item:get_name() == "default:torch")
|
||||
assert(output.item:get_count() == 4)
|
||||
-- fuel
|
||||
@@ -1857,10 +1705,10 @@ local function test_get_craft_result()
|
||||
width = 1,
|
||||
items = {"default:coal_lump"}
|
||||
}
|
||||
print("coal fuel input: "..dump(input))
|
||||
minetest.log("info", "coal fuel input: "..dump(input))
|
||||
local output, decremented_input = minetest.get_craft_result(input)
|
||||
print("coal fuel output: "..dump(output))
|
||||
print("coal fuel decremented input: "..dump(decremented_input))
|
||||
minetest.log("info", "coal fuel output: "..dump(output))
|
||||
minetest.log("info", "coal fuel decremented input: "..dump(decremented_input))
|
||||
assert(output.time)
|
||||
assert(output.time > 0)
|
||||
-- cook
|
||||
@@ -1869,14 +1717,14 @@ local function test_get_craft_result()
|
||||
width = 1,
|
||||
items = {"default:cobble"}
|
||||
}
|
||||
print("cobble cooking input: "..dump(output))
|
||||
minetest.log("info", "cobble cooking input: "..dump(output))
|
||||
local output, decremented_input = minetest.get_craft_result(input)
|
||||
print("cobble cooking output: "..dump(output))
|
||||
print("cobble cooking decremented input: "..dump(decremented_input))
|
||||
minetest.log("info", "cobble cooking output: "..dump(output))
|
||||
minetest.log("info", "cobble cooking decremented input: "..dump(decremented_input))
|
||||
assert(output.time)
|
||||
assert(output.time > 0)
|
||||
assert(output.item)
|
||||
print("cobble cooking output.item:to_table(): "..dump(output.item:to_table()))
|
||||
minetest.log("info", "cobble cooking output.item:to_table(): "..dump(output.item:to_table()))
|
||||
assert(output.item:get_name() == "default:stone")
|
||||
assert(output.item:get_count() == 1)
|
||||
end
|
||||
|
||||
@@ -443,7 +443,7 @@ minetest.register_abm({
|
||||
})--]]
|
||||
|
||||
minetest.register_node("experimental:tester_node_1", {
|
||||
description = "Tester Node 1",
|
||||
description = "Tester Node 1 (construct/destruct/timer)",
|
||||
tile_images = {"wieldhand.png"},
|
||||
groups = {oddly_breakable_by_hand=2},
|
||||
sounds = default.node_sound_wood_defaults(),
|
||||
@@ -455,6 +455,8 @@ minetest.register_node("experimental:tester_node_1", {
|
||||
experimental.print_to_everything("experimental:tester_node_1:on_construct("..minetest.pos_to_string(pos)..")")
|
||||
local meta = minetest.env:get_meta(pos)
|
||||
meta:set_string("mine", "test")
|
||||
local timer = minetest.env:get_node_timer(pos)
|
||||
timer:start(4, 3)
|
||||
end,
|
||||
|
||||
after_place_node = function(pos, placer)
|
||||
@@ -478,6 +480,11 @@ minetest.register_node("experimental:tester_node_1", {
|
||||
after_dig_node = function(pos, oldnode, oldmetadata, digger)
|
||||
experimental.print_to_everything("experimental:tester_node_1:after_dig_node("..minetest.pos_to_string(pos)..")")
|
||||
end,
|
||||
|
||||
on_timer = function(pos, elapsed)
|
||||
experimental.print_to_everything("on_timer(): elapsed="..dump(elapsed))
|
||||
return true
|
||||
end,
|
||||
})
|
||||
|
||||
minetest.register_craftitem("experimental:tester_tool_1", {
|
||||
@@ -509,7 +516,7 @@ minetest.register_craft({
|
||||
|
||||
--[[minetest.register_on_joinplayer(function(player)
|
||||
minetest.after(3, function()
|
||||
player:set_inventory_formspec("invsize[8,7.5;]"..
|
||||
player:set_inventory_formspec("size[8,7.5]"..
|
||||
"image[1,0.6;1,2;player.png]"..
|
||||
"list[current_player;main;0,3.5;8,4;]"..
|
||||
"list[current_player;craft;3,0;3,3;]"..
|
||||
@@ -517,6 +524,67 @@ minetest.register_craft({
|
||||
end)
|
||||
end)]]
|
||||
|
||||
-- Create a detached inventory
|
||||
local inv = minetest.create_detached_inventory("test_inventory", {
|
||||
allow_move = function(inv, from_list, from_index, to_list, to_index, count, player)
|
||||
experimental.print_to_everything("allow move asked")
|
||||
return count -- Allow all
|
||||
end,
|
||||
allow_put = function(inv, listname, index, stack, player)
|
||||
experimental.print_to_everything("allow put asked")
|
||||
return 1 -- Allow only 1
|
||||
end,
|
||||
allow_take = function(inv, listname, index, stack, player)
|
||||
experimental.print_to_everything("allow take asked")
|
||||
return 4 -- Allow 4 at max
|
||||
end,
|
||||
on_move = function(inv, from_list, from_index, to_list, to_index, count, player)
|
||||
experimental.print_to_everything(player:get_player_name().." moved items")
|
||||
end,
|
||||
on_put = function(inv, listname, index, stack, player)
|
||||
experimental.print_to_everything(player:get_player_name().." put items")
|
||||
end,
|
||||
on_take = function(inv, listname, index, stack, player)
|
||||
experimental.print_to_everything(player:get_player_name().." took items")
|
||||
end,
|
||||
})
|
||||
inv:set_size("main", 4*6)
|
||||
inv:add_item("main", "experimental:tester_tool_1")
|
||||
inv:add_item("main", "experimental:tnt 5")
|
||||
|
||||
minetest.register_chatcommand("test1", {
|
||||
params = "",
|
||||
description = "Test 1: Modify player's inventory view",
|
||||
func = function(name, param)
|
||||
local player = minetest.env:get_player_by_name(name)
|
||||
if not player then
|
||||
return
|
||||
end
|
||||
player:set_inventory_formspec(
|
||||
"size[13,7.5]"..
|
||||
"image[6,0.6;1,2;player.png]"..
|
||||
"list[current_player;main;5,3.5;8,4;]"..
|
||||
"list[current_player;craft;8,0;3,3;]"..
|
||||
"list[current_player;craftpreview;12,1;1,1;]"..
|
||||
"list[detached:test_inventory;main;0,0;4,6;0]"..
|
||||
"button[0.5,7;2,1;button1;Button 1]"..
|
||||
"button_exit[2.5,7;2,1;button2;Exit Button]"
|
||||
)
|
||||
minetest.chat_send_player(name, "Done.");
|
||||
end,
|
||||
})
|
||||
|
||||
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
experimental.print_to_everything("Inventory fields 1: player="..player:get_player_name()..", fields="..dump(fields))
|
||||
end)
|
||||
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
experimental.print_to_everything("Inventory fields 2: player="..player:get_player_name()..", fields="..dump(fields))
|
||||
return true -- Disable the first callback
|
||||
end)
|
||||
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
experimental.print_to_everything("Inventory fields 3: player="..player:get_player_name()..", fields="..dump(fields))
|
||||
end)
|
||||
|
||||
minetest.log("experimental modname="..dump(minetest.get_current_modname()))
|
||||
minetest.log("experimental modpath="..dump(minetest.get_modpath("experimental")))
|
||||
minetest.log("experimental worldpath="..dump(minetest.get_worldpath()))
|
||||
|
||||
@@ -160,6 +160,8 @@
|
||||
#disallow_empty_password = false
|
||||
# If true, disable cheat prevention in multiplayer
|
||||
#disable_anticheat = false
|
||||
# If true, actions are recorded for rollback
|
||||
#enable_rollback_recording = false
|
||||
|
||||
# Profiler data print interval. #0 = disable.
|
||||
#profiler_print_interval = 0
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
project(minetest)
|
||||
cmake_minimum_required( VERSION 2.6 )
|
||||
|
||||
if(RUN_IN_PLACE)
|
||||
add_definitions ( -DRUN_IN_PLACE )
|
||||
endif(RUN_IN_PLACE)
|
||||
|
||||
# Set some random things default to not being visible in the GUI
|
||||
mark_as_advanced(EXECUTABLE_OUTPUT_PATH LIBRARY_OUTPUT_PATH)
|
||||
mark_as_advanced(JTHREAD_INCLUDE_DIR JTHREAD_LIBRARY)
|
||||
@@ -139,13 +135,15 @@ else()
|
||||
#set(CLIENT_PLATFORM_LIBS -lXxf86vm)
|
||||
# This way Xxf86vm is found on OpenBSD too
|
||||
find_library(XXF86VM_LIBRARY Xxf86vm)
|
||||
mark_as_advanced(XXF86VM_LIBRARY)
|
||||
set(CLIENT_PLATFORM_LIBS ${CLIENT_PLATFORM_LIBS} ${XXF86VM_LIBRARY})
|
||||
endif()
|
||||
|
||||
find_package(Jthread REQUIRED)
|
||||
find_package(Sqlite3 REQUIRED)
|
||||
|
||||
# TODO: Create proper find script for Lua
|
||||
# Do not use system-wide installation of Lua, because it'll likely be a
|
||||
# different version and/or has different build options.
|
||||
set(LUA_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/lua/src")
|
||||
set(LUA_LIBRARY "lua")
|
||||
|
||||
@@ -155,6 +153,8 @@ configure_file(
|
||||
)
|
||||
|
||||
set(common_SRCS
|
||||
rollback_interface.cpp
|
||||
rollback.cpp
|
||||
genericobject.cpp
|
||||
voxelalgorithms.cpp
|
||||
sound.cpp
|
||||
@@ -251,7 +251,7 @@ set(minetest_SRCS
|
||||
guiKeyChangeMenu.cpp
|
||||
guiMessageMenu.cpp
|
||||
guiTextInputMenu.cpp
|
||||
guiInventoryMenu.cpp
|
||||
guiFormSpecMenu.cpp
|
||||
guiPauseMenu.cpp
|
||||
guiPasswordChange.cpp
|
||||
guiDeathScreen.cpp
|
||||
|
||||
@@ -304,6 +304,15 @@ Client::~Client()
|
||||
sleep_ms(100);
|
||||
|
||||
delete m_inventory_from_server;
|
||||
|
||||
// Delete detached inventories
|
||||
{
|
||||
for(std::map<std::string, Inventory*>::iterator
|
||||
i = m_detached_inventories.begin();
|
||||
i != m_detached_inventories.end(); i++){
|
||||
delete i->second;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Client::connect(Address address)
|
||||
@@ -1698,6 +1707,24 @@ void Client::ProcessData(u8 *data, u32 datasize, u16 sender_peer_id)
|
||||
assert(player != NULL);
|
||||
player->inventory_formspec = deSerializeLongString(is);
|
||||
}
|
||||
else if(command == TOCLIENT_DETACHED_INVENTORY)
|
||||
{
|
||||
std::string datastring((char*)&data[2], datasize-2);
|
||||
std::istringstream is(datastring, std::ios_base::binary);
|
||||
|
||||
std::string name = deSerializeString(is);
|
||||
|
||||
infostream<<"Client: Detached inventory update: \""<<name<<"\""<<std::endl;
|
||||
|
||||
Inventory *inv = NULL;
|
||||
if(m_detached_inventories.count(name) > 0)
|
||||
inv = m_detached_inventories[name];
|
||||
else{
|
||||
inv = new Inventory(m_itemdef);
|
||||
m_detached_inventories[name] = inv;
|
||||
}
|
||||
inv->deSerialize(is);
|
||||
}
|
||||
else
|
||||
{
|
||||
infostream<<"Client: Ignoring unknown command "
|
||||
@@ -1773,6 +1800,29 @@ void Client::sendNodemetaFields(v3s16 p, const std::string &formname,
|
||||
Send(0, data, true);
|
||||
}
|
||||
|
||||
void Client::sendInventoryFields(const std::string &formname,
|
||||
const std::map<std::string, std::string> &fields)
|
||||
{
|
||||
std::ostringstream os(std::ios_base::binary);
|
||||
|
||||
writeU16(os, TOSERVER_INVENTORY_FIELDS);
|
||||
os<<serializeString(formname);
|
||||
writeU16(os, fields.size());
|
||||
for(std::map<std::string, std::string>::const_iterator
|
||||
i = fields.begin(); i != fields.end(); i++){
|
||||
const std::string &name = i->first;
|
||||
const std::string &value = i->second;
|
||||
os<<serializeString(name);
|
||||
os<<serializeLongString(value);
|
||||
}
|
||||
|
||||
// Make data buffer
|
||||
std::string s = os.str();
|
||||
SharedBuffer<u8> data((u8*)s.c_str(), s.size());
|
||||
// Send as reliable
|
||||
Send(0, data, true);
|
||||
}
|
||||
|
||||
void Client::sendInventoryAction(InventoryAction *a)
|
||||
{
|
||||
std::ostringstream os(std::ios_base::binary);
|
||||
@@ -2067,6 +2117,13 @@ Inventory* Client::getInventory(const InventoryLocation &loc)
|
||||
return meta->getInventory();
|
||||
}
|
||||
break;
|
||||
case InventoryLocation::DETACHED:
|
||||
{
|
||||
if(m_detached_inventories.count(loc.name) == 0)
|
||||
return NULL;
|
||||
return m_detached_inventories[loc.name];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
|
||||
@@ -212,6 +212,8 @@ class Client : public con::PeerHandler, public InventoryManager, public IGameDef
|
||||
|
||||
void sendNodemetaFields(v3s16 p, const std::string &formname,
|
||||
const std::map<std::string, std::string> &fields);
|
||||
void sendInventoryFields(const std::string &formname,
|
||||
const std::map<std::string, std::string> &fields);
|
||||
void sendInventoryAction(InventoryAction *a);
|
||||
void sendChatMessage(const std::wstring &message);
|
||||
void sendChangePassword(const std::wstring oldpassword,
|
||||
@@ -391,6 +393,10 @@ class Client : public con::PeerHandler, public InventoryManager, public IGameDef
|
||||
|
||||
// Privileges
|
||||
std::set<std::string> m_privileges;
|
||||
|
||||
// Detached inventories
|
||||
// key = name
|
||||
std::map<std::string, Inventory*> m_detached_inventories;
|
||||
};
|
||||
|
||||
#endif // !CLIENT_HEADER
|
||||
|
||||
@@ -58,10 +58,16 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
PROTOCOL_VERSION 11:
|
||||
TileDef in ContentFeatures
|
||||
Nodebox drawtype
|
||||
Added after a release: TOCLIENT_INVENTORY_FORMSPEC
|
||||
(some dev snapshot)
|
||||
TOCLIENT_INVENTORY_FORMSPEC
|
||||
(0.4.0, 0.4.1)
|
||||
PROTOCOL_VERSION 12:
|
||||
TOSERVER_INVENTORY_FIELDS
|
||||
16-bit node ids
|
||||
TOCLIENT_DETACHED_INVENTORY
|
||||
*/
|
||||
|
||||
#define PROTOCOL_VERSION 11
|
||||
#define PROTOCOL_VERSION 12
|
||||
|
||||
#define PROTOCOL_ID 0x4f457403
|
||||
|
||||
@@ -316,6 +322,14 @@ enum ToClientCommand
|
||||
u32 len
|
||||
u8[len] formspec
|
||||
*/
|
||||
|
||||
TOCLIENT_DETACHED_INVENTORY = 0x43,
|
||||
/*
|
||||
[0] u16 command
|
||||
u16 len
|
||||
u8[len] name
|
||||
[2] serialized inventory
|
||||
*/
|
||||
};
|
||||
|
||||
enum ToServerCommand
|
||||
@@ -510,6 +524,19 @@ enum ToServerCommand
|
||||
u8[len] field value
|
||||
*/
|
||||
|
||||
TOSERVER_INVENTORY_FIELDS = 0x3c,
|
||||
/*
|
||||
u16 command
|
||||
u16 len
|
||||
u8[len] form name (reserved for future use)
|
||||
u16 number of fields
|
||||
for each field:
|
||||
u16 len
|
||||
u8[len] field name
|
||||
u32 len
|
||||
u8[len] field value
|
||||
*/
|
||||
|
||||
TOSERVER_REQUEST_MEDIA = 0x40,
|
||||
/*
|
||||
u16 command
|
||||
|
||||
@@ -4,16 +4,18 @@
|
||||
#define CMAKE_CONFIG_H
|
||||
|
||||
#define CMAKE_PROJECT_NAME "@PROJECT_NAME@"
|
||||
#define CMAKE_INSTALL_PREFIX "@CMAKE_INSTALL_PREFIX@"
|
||||
#define CMAKE_VERSION_STRING "@VERSION_STRING@"
|
||||
#define CMAKE_RUN_IN_PLACE @RUN_IN_PLACE@
|
||||
#define CMAKE_USE_GETTEXT @USE_GETTEXT@
|
||||
#define CMAKE_USE_SOUND @USE_SOUND@
|
||||
#define CMAKE_STATIC_SHAREDIR "@SHAREDIR@"
|
||||
|
||||
#ifdef NDEBUG
|
||||
#define CMAKE_BUILD_TYPE "Release"
|
||||
#else
|
||||
#define CMAKE_BUILD_TYPE "Debug"
|
||||
#endif
|
||||
#define CMAKE_USE_GETTEXT @USE_GETTEXT@
|
||||
#define CMAKE_USE_SOUND @USE_SOUND@
|
||||
#define CMAKE_BUILD_INFO "VER=@VERSION_STRING@ BUILD_TYPE="CMAKE_BUILD_TYPE" RUN_IN_PLACE=@RUN_IN_PLACE@ USE_GETTEXT=@USE_GETTEXT@ USE_SOUND=@USE_SOUND@ INSTALL_PREFIX=@CMAKE_INSTALL_PREFIX@"
|
||||
#define CMAKE_BUILD_INFO "VER=@VERSION_STRING@ BUILD_TYPE="CMAKE_BUILD_TYPE" RUN_IN_PLACE=@RUN_IN_PLACE@ USE_GETTEXT=@USE_GETTEXT@ USE_SOUND=@USE_SOUND@ STATIC_SHAREDIR=@SHAREDIR@"
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -8,9 +8,10 @@
|
||||
|
||||
#define PROJECT_NAME "Minetest"
|
||||
#define VERSION_STRING "unknown"
|
||||
#define BUILD_TYPE "unknown"
|
||||
#define RUN_IN_PLACE 0
|
||||
#define USE_GETTEXT 0
|
||||
#define USE_SOUND 0
|
||||
#define STATIC_SHAREDIR ""
|
||||
#define BUILD_INFO "non-cmake"
|
||||
|
||||
#ifdef USE_CMAKE_CONFIG_H
|
||||
@@ -19,12 +20,14 @@
|
||||
#define PROJECT_NAME CMAKE_PROJECT_NAME
|
||||
#undef VERSION_STRING
|
||||
#define VERSION_STRING CMAKE_VERSION_STRING
|
||||
#undef BUILD_INFO
|
||||
#define BUILD_INFO CMAKE_BUILD_INFO
|
||||
#undef RUN_IN_PLACE
|
||||
#define RUN_IN_PLACE CMAKE_RUN_IN_PLACE
|
||||
#undef USE_GETTEXT
|
||||
#define USE_GETTEXT CMAKE_USE_GETTEXT
|
||||
#undef USE_SOUND
|
||||
#define USE_SOUND CMAKE_USE_SOUND
|
||||
#undef STATIC_SHAREDIR
|
||||
#define STATIC_SHAREDIR CMAKE_STATIC_SHAREDIR
|
||||
#undef BUILD_INFO
|
||||
#define BUILD_INFO CMAKE_BUILD_INFO
|
||||
#endif
|
||||
|
||||
@@ -26,6 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
#include "settings.h"
|
||||
#include "mapblock.h" // For getNodeBlockPos
|
||||
#include "mapgen.h" // For mapgen::make_tree
|
||||
#include "map.h"
|
||||
|
||||
#define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")"
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
#include "util/numeric.h" // For IntervalLimiter
|
||||
#include "util/serialize.h"
|
||||
#include "util/mathconstants.h"
|
||||
#include "map.h"
|
||||
|
||||
class Settings;
|
||||
struct ToolCapabilities;
|
||||
|
||||
@@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
#include "environment.h"
|
||||
#include "gamedef.h"
|
||||
#include "log.h"
|
||||
#include "map.h"
|
||||
|
||||
static void setBillboardTextureMatrix(scene::IBillboardSceneNode *bill,
|
||||
float txs, float tys, int col, int row)
|
||||
|
||||
@@ -102,23 +102,6 @@ content_t trans_table_19[21][2] = {
|
||||
{CONTENT_BOOKSHELF, 29},
|
||||
};
|
||||
|
||||
MapNode mapnode_translate_from_internal(MapNode n_from, u8 version)
|
||||
{
|
||||
MapNode result = n_from;
|
||||
if(version <= 19)
|
||||
{
|
||||
content_t c_from = n_from.getContent();
|
||||
for(u32 i=0; i<sizeof(trans_table_19)/sizeof(trans_table_19[0]); i++)
|
||||
{
|
||||
if(trans_table_19[i][0] == c_from)
|
||||
{
|
||||
result.setContent(trans_table_19[i][1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
MapNode mapnode_translate_to_internal(MapNode n_from, u8 version)
|
||||
{
|
||||
MapNode result = n_from;
|
||||
|
||||
@@ -28,7 +28,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
|
||||
// Backwards compatibility for non-extended content types in v19
|
||||
extern content_t trans_table_19[21][2];
|
||||
MapNode mapnode_translate_from_internal(MapNode n_from, u8 version);
|
||||
MapNode mapnode_translate_to_internal(MapNode n_from, u8 version);
|
||||
|
||||
// Get legacy node name mapping for loading old blocks
|
||||
|
||||
@@ -63,7 +63,7 @@ static bool content_nodemeta_deserialize_legacy_body(
|
||||
//meta->setString("infotext","\"${text}\"");
|
||||
meta->setString("infotext",
|
||||
std::string("\"") + meta->getString("text") + "\"");
|
||||
meta->setString("formspec","hack:sign_text_input");
|
||||
meta->setString("formspec","field[text;;${text}]");
|
||||
return false;
|
||||
}
|
||||
else if(id == NODEMETA_CHEST) // ChestNodeMetadata
|
||||
@@ -77,7 +77,7 @@ static bool content_nodemeta_deserialize_legacy_body(
|
||||
}
|
||||
assert(inv->getList("main") && !inv->getList("0"));
|
||||
|
||||
meta->setString("formspec","invsize[8,9;]"
|
||||
meta->setString("formspec","size[8,9]"
|
||||
"list[current_name;main;0,0;8,4;]"
|
||||
"list[current_player;main;0,5;8,4;]");
|
||||
return false;
|
||||
@@ -94,7 +94,7 @@ static bool content_nodemeta_deserialize_legacy_body(
|
||||
}
|
||||
assert(inv->getList("main") && !inv->getList("0"));
|
||||
|
||||
meta->setString("formspec","invsize[8,9;]"
|
||||
meta->setString("formspec","size[8,9]"
|
||||
"list[current_name;main;0,0;8,4;]"
|
||||
"list[current_player;main;0,5;8,4;]");
|
||||
return false;
|
||||
@@ -115,7 +115,7 @@ static bool content_nodemeta_deserialize_legacy_body(
|
||||
is>>temp;
|
||||
meta->setString("src_time", ftos((float)temp/10));
|
||||
|
||||
meta->setString("formspec","invsize[8,9;]"
|
||||
meta->setString("formspec","size[8,9]"
|
||||
"list[current_name;fuel;2,3;1,1;]"
|
||||
"list[current_name;src;2,1;1,1;]"
|
||||
"list[current_name;dst;5,1;2,2;]"
|
||||
|
||||
@@ -292,13 +292,6 @@ class ItemSAO : public ServerActiveObject
|
||||
ServerActiveObject *puncher,
|
||||
float time_from_last_punch)
|
||||
{
|
||||
// Directly delete item in creative mode
|
||||
if(g_settings->getBool("creative_mode") == true)
|
||||
{
|
||||
m_removed = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Take item into inventory
|
||||
ItemStack item = createItemStack();
|
||||
Inventory *inv = puncher->getInventory();
|
||||
@@ -1143,16 +1136,6 @@ void PlayerSAO::disconnected()
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerSAO::createCreativeInventory()
|
||||
{
|
||||
if(m_inventory != &m_player->inventory)
|
||||
delete m_inventory;
|
||||
|
||||
m_inventory = new Inventory(m_player->inventory);
|
||||
m_inventory->clearContents();
|
||||
scriptapi_get_creative_inventory(m_env->getLua(), this);
|
||||
}
|
||||
|
||||
std::string PlayerSAO::getPropertyPacket()
|
||||
{
|
||||
m_prop.is_visible = (getHP() != 0);
|
||||
|
||||
@@ -163,8 +163,6 @@ class PlayerSAO : public ServerActiveObject
|
||||
|
||||
void disconnected();
|
||||
|
||||
void createCreativeInventory();
|
||||
|
||||
Player* getPlayer()
|
||||
{
|
||||
return m_player;
|
||||
|
||||
120
src/craftdef.cpp
120
src/craftdef.cpp
@@ -23,9 +23,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
#include "log.h"
|
||||
#include <sstream>
|
||||
#include <set>
|
||||
#include <algorithm>
|
||||
#include "gamedef.h"
|
||||
#include "inventory.h"
|
||||
#include "util/serialize.h"
|
||||
#include "strfnd.h"
|
||||
|
||||
// Check if input matches recipe
|
||||
// Takes recipe groups into account
|
||||
@@ -38,9 +40,17 @@ static bool inputItemMatchesRecipe(const std::string &inp_name,
|
||||
|
||||
// Group
|
||||
if(rec_name.substr(0,6) == "group:" && idef->isKnown(inp_name)){
|
||||
std::string rec_group = rec_name.substr(6);
|
||||
const struct ItemDefinition &def = idef->get(inp_name);
|
||||
if(itemgroup_get(def.groups, rec_group) != 0)
|
||||
Strfnd f(rec_name.substr(6));
|
||||
bool all_groups_match = true;
|
||||
do{
|
||||
std::string check_group = f.next(",");
|
||||
if(itemgroup_get(def.groups, check_group) == 0){
|
||||
all_groups_match = false;
|
||||
break;
|
||||
}
|
||||
}while(!f.atend());
|
||||
if(all_groups_match)
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -140,6 +150,8 @@ static bool craftGetBounds(const std::vector<std::string> &items, unsigned int w
|
||||
return success;
|
||||
}
|
||||
|
||||
#if 0
|
||||
// This became useless when group support was added to shapeless recipes
|
||||
// Convert a list of item names to a multiset
|
||||
static std::multiset<std::string> craftMakeMultiset(const std::vector<std::string> &names)
|
||||
{
|
||||
@@ -153,6 +165,7 @@ static std::multiset<std::string> craftMakeMultiset(const std::vector<std::strin
|
||||
}
|
||||
return set;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Removes 1 from each item stack
|
||||
static void craftDecrementInput(CraftInput &input, IGameDef *gamedef)
|
||||
@@ -508,17 +521,48 @@ bool CraftDefinitionShapeless::check(const CraftInput &input, IGameDef *gamedef)
|
||||
{
|
||||
if(input.method != CRAFT_METHOD_NORMAL)
|
||||
return false;
|
||||
|
||||
// Filter empty items out of input
|
||||
std::vector<std::string> input_filtered;
|
||||
for(std::vector<ItemStack>::const_iterator
|
||||
i = input.items.begin();
|
||||
i != input.items.end(); i++)
|
||||
{
|
||||
if(i->name != "")
|
||||
input_filtered.push_back(i->name);
|
||||
}
|
||||
|
||||
// Get input item multiset
|
||||
std::vector<std::string> inp_names = craftGetItemNames(input.items, gamedef);
|
||||
std::multiset<std::string> inp_names_multiset = craftMakeMultiset(inp_names);
|
||||
// If there is a wrong number of items in input, no match
|
||||
if(input_filtered.size() != recipe.size()){
|
||||
/*dstream<<"Number of input items ("<<input_filtered.size()
|
||||
<<") does not match recipe size ("<<recipe.size()<<") "
|
||||
<<"of recipe with output="<<output<<std::endl;*/
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get recipe item multiset
|
||||
std::vector<std::string> rec_names = craftGetItemNames(recipe, gamedef);
|
||||
std::multiset<std::string> rec_names_multiset = craftMakeMultiset(rec_names);
|
||||
// Try with all permutations of the recipe
|
||||
std::vector<std::string> recipe_copy = recipe;
|
||||
// Start from the lexicographically first permutation (=sorted)
|
||||
std::sort(recipe_copy.begin(), recipe_copy.end());
|
||||
//while(std::prev_permutation(recipe_copy.begin(), recipe_copy.end())){}
|
||||
do{
|
||||
// If all items match, the recipe matches
|
||||
bool all_match = true;
|
||||
//dstream<<"Testing recipe (output="<<output<<"):";
|
||||
for(size_t i=0; i<recipe.size(); i++){
|
||||
//dstream<<" ("<<input_filtered[i]<<" == "<<recipe_copy[i]<<")";
|
||||
if(!inputItemMatchesRecipe(input_filtered[i], recipe_copy[i],
|
||||
gamedef->idef())){
|
||||
all_match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
//dstream<<" -> match="<<all_match<<std::endl;
|
||||
if(all_match)
|
||||
return true;
|
||||
}while(std::next_permutation(recipe_copy.begin(), recipe_copy.end()));
|
||||
|
||||
// Recipe is matched when the multisets coincide
|
||||
return inp_names_multiset == rec_names_multiset;
|
||||
return false;
|
||||
}
|
||||
|
||||
CraftOutput CraftDefinitionShapeless::getOutput(const CraftInput &input, IGameDef *gamedef) const
|
||||
@@ -694,16 +738,26 @@ bool CraftDefinitionCooking::check(const CraftInput &input, IGameDef *gamedef) c
|
||||
if(input.method != CRAFT_METHOD_COOKING)
|
||||
return false;
|
||||
|
||||
// Get input item multiset
|
||||
std::vector<std::string> inp_names = craftGetItemNames(input.items, gamedef);
|
||||
std::multiset<std::string> inp_names_multiset = craftMakeMultiset(inp_names);
|
||||
// Filter empty items out of input
|
||||
std::vector<std::string> input_filtered;
|
||||
for(std::vector<ItemStack>::const_iterator
|
||||
i = input.items.begin();
|
||||
i != input.items.end(); i++)
|
||||
{
|
||||
if(i->name != "")
|
||||
input_filtered.push_back(i->name);
|
||||
}
|
||||
|
||||
// Get recipe item multiset
|
||||
std::multiset<std::string> rec_names_multiset;
|
||||
rec_names_multiset.insert(craftGetItemName(recipe, gamedef));
|
||||
|
||||
// Recipe is matched when the multisets coincide
|
||||
return inp_names_multiset == rec_names_multiset;
|
||||
// If there is a wrong number of items in input, no match
|
||||
if(input_filtered.size() != 1){
|
||||
/*dstream<<"Number of input items ("<<input_filtered.size()
|
||||
<<") does not match recipe size (1) "
|
||||
<<"of cooking recipe with output="<<output<<std::endl;*/
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check the single input item
|
||||
return inputItemMatchesRecipe(input_filtered[0], recipe, gamedef->idef());
|
||||
}
|
||||
|
||||
CraftOutput CraftDefinitionCooking::getOutput(const CraftInput &input, IGameDef *gamedef) const
|
||||
@@ -765,16 +819,26 @@ bool CraftDefinitionFuel::check(const CraftInput &input, IGameDef *gamedef) cons
|
||||
if(input.method != CRAFT_METHOD_FUEL)
|
||||
return false;
|
||||
|
||||
// Get input item multiset
|
||||
std::vector<std::string> inp_names = craftGetItemNames(input.items, gamedef);
|
||||
std::multiset<std::string> inp_names_multiset = craftMakeMultiset(inp_names);
|
||||
// Filter empty items out of input
|
||||
std::vector<std::string> input_filtered;
|
||||
for(std::vector<ItemStack>::const_iterator
|
||||
i = input.items.begin();
|
||||
i != input.items.end(); i++)
|
||||
{
|
||||
if(i->name != "")
|
||||
input_filtered.push_back(i->name);
|
||||
}
|
||||
|
||||
// Get recipe item multiset
|
||||
std::multiset<std::string> rec_names_multiset;
|
||||
rec_names_multiset.insert(craftGetItemName(recipe, gamedef));
|
||||
|
||||
// Recipe is matched when the multisets coincide
|
||||
return inp_names_multiset == rec_names_multiset;
|
||||
// If there is a wrong number of items in input, no match
|
||||
if(input_filtered.size() != 1){
|
||||
/*dstream<<"Number of input items ("<<input_filtered.size()
|
||||
<<") does not match recipe size (1) "
|
||||
<<"of fuel recipe with burntime="<<burntime<<std::endl;*/
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check the single input item
|
||||
return inputItemMatchesRecipe(input_filtered[0], recipe, gamedef->idef());
|
||||
}
|
||||
|
||||
CraftOutput CraftDefinitionFuel::getOutput(const CraftInput &input, IGameDef *gamedef) const
|
||||
|
||||
@@ -122,6 +122,7 @@ void set_default_settings(Settings *settings)
|
||||
settings->setDefault("enable_pvp", "true");
|
||||
settings->setDefault("disallow_empty_password", "false");
|
||||
settings->setDefault("disable_anticheat", "false");
|
||||
settings->setDefault("enable_rollback_recording", "false");
|
||||
|
||||
settings->setDefault("profiler_print_interval", "0");
|
||||
settings->setDefault("enable_mapgen_debug_info", "false");
|
||||
|
||||
@@ -42,6 +42,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
#include "localplayer.h"
|
||||
#endif
|
||||
#include "daynightratio.h"
|
||||
#include "map.h"
|
||||
|
||||
#define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")"
|
||||
|
||||
@@ -325,6 +326,7 @@ ServerEnvironment::ServerEnvironment(ServerMap *map, lua_State *L,
|
||||
m_emerger(emerger),
|
||||
m_random_spawn_timer(3),
|
||||
m_send_recommended_timer(0),
|
||||
m_active_block_interval_overload_skip(0),
|
||||
m_game_time(0),
|
||||
m_game_time_fraction_counter(0)
|
||||
{
|
||||
@@ -349,6 +351,17 @@ ServerEnvironment::~ServerEnvironment()
|
||||
}
|
||||
}
|
||||
|
||||
Map & ServerEnvironment::getMap()
|
||||
{
|
||||
return *m_map;
|
||||
}
|
||||
|
||||
ServerMap & ServerEnvironment::getServerMap()
|
||||
{
|
||||
return *m_map;
|
||||
}
|
||||
|
||||
|
||||
void ServerEnvironment::serializePlayers(const std::string &savedir)
|
||||
{
|
||||
std::string players_path = savedir + "/players";
|
||||
@@ -774,10 +787,18 @@ void ServerEnvironment::activateBlock(MapBlock *block, u32 additional_dtime)
|
||||
activateObjects(block);
|
||||
|
||||
// Run node timers
|
||||
std::map<v3s16, f32> elapsed_timers =
|
||||
std::map<v3s16, NodeTimer> elapsed_timers =
|
||||
block->m_node_timers.step((float)dtime_s);
|
||||
if(!elapsed_timers.empty())
|
||||
errorstream<<"Node timers don't work yet!"<<std::endl;
|
||||
if(!elapsed_timers.empty()){
|
||||
MapNode n;
|
||||
for(std::map<v3s16, NodeTimer>::iterator
|
||||
i = elapsed_timers.begin();
|
||||
i != elapsed_timers.end(); i++){
|
||||
n = block->getNodeNoEx(i->first);
|
||||
if(scriptapi_node_on_timer(m_lua,i->first,n,i->second.elapsed))
|
||||
block->setNodeTimer(i->first,NodeTimer(i->second.timeout,0));
|
||||
}
|
||||
}
|
||||
|
||||
/* Handle ActiveBlockModifiers */
|
||||
ABMHandler abmhandler(m_abms, dtime_s, this, false);
|
||||
@@ -1058,16 +1079,29 @@ void ServerEnvironment::step(float dtime)
|
||||
"Timestamp older than 60s (step)");
|
||||
|
||||
// Run node timers
|
||||
std::map<v3s16, f32> elapsed_timers =
|
||||
block->m_node_timers.step(dtime);
|
||||
if(!elapsed_timers.empty())
|
||||
errorstream<<"Node timers don't work yet!"<<std::endl;
|
||||
std::map<v3s16, NodeTimer> elapsed_timers =
|
||||
block->m_node_timers.step((float)dtime);
|
||||
if(!elapsed_timers.empty()){
|
||||
MapNode n;
|
||||
for(std::map<v3s16, NodeTimer>::iterator
|
||||
i = elapsed_timers.begin();
|
||||
i != elapsed_timers.end(); i++){
|
||||
n = block->getNodeNoEx(i->first);
|
||||
if(scriptapi_node_on_timer(m_lua,i->first,n,i->second.elapsed))
|
||||
block->setNodeTimer(i->first,NodeTimer(i->second.timeout,0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const float abm_interval = 1.0;
|
||||
if(m_active_block_modifier_interval.step(dtime, abm_interval))
|
||||
{
|
||||
do{ // breakable
|
||||
if(m_active_block_interval_overload_skip > 0){
|
||||
ScopeProfiler sp(g_profiler, "SEnv: ABM overload skips");
|
||||
m_active_block_interval_overload_skip--;
|
||||
break;
|
||||
}
|
||||
ScopeProfiler sp(g_profiler, "SEnv: modify in blocks avg /1s", SPT_AVG);
|
||||
TimeTaker timer("modify in active blocks");
|
||||
|
||||
@@ -1100,8 +1134,9 @@ void ServerEnvironment::step(float dtime)
|
||||
infostream<<"WARNING: active block modifiers took "
|
||||
<<time_ms<<"ms (longer than "
|
||||
<<max_time_ms<<"ms)"<<std::endl;
|
||||
m_active_block_interval_overload_skip = (time_ms / max_time_ms) + 1;
|
||||
}
|
||||
}
|
||||
}while(0);
|
||||
|
||||
/*
|
||||
Step script environment (run global on_step())
|
||||
|
||||
@@ -33,11 +33,12 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
#include <set>
|
||||
#include "irrlichttypes_extrabloated.h"
|
||||
#include "player.h"
|
||||
#include "map.h"
|
||||
#include <ostream>
|
||||
#include "activeobject.h"
|
||||
#include "util/container.h"
|
||||
#include "util/numeric.h"
|
||||
#include "mapnode.h"
|
||||
#include "mapblock.h"
|
||||
|
||||
class Server;
|
||||
class ServerEnvironment;
|
||||
@@ -46,6 +47,8 @@ class ServerActiveObject;
|
||||
typedef struct lua_State lua_State;
|
||||
class ITextureSource;
|
||||
class IGameDef;
|
||||
class Map;
|
||||
class ServerMap;
|
||||
class ClientMap;
|
||||
|
||||
class Environment
|
||||
@@ -191,11 +194,9 @@ class ServerEnvironment : public Environment
|
||||
IBackgroundBlockEmerger *emerger);
|
||||
~ServerEnvironment();
|
||||
|
||||
Map & getMap()
|
||||
{ return *m_map; }
|
||||
Map & getMap();
|
||||
|
||||
ServerMap & getServerMap()
|
||||
{ return *m_map; }
|
||||
ServerMap & getServerMap();
|
||||
|
||||
lua_State* getLua()
|
||||
{ return m_lua; }
|
||||
@@ -359,6 +360,7 @@ class ServerEnvironment : public Environment
|
||||
IntervalLimiter m_active_blocks_management_interval;
|
||||
IntervalLimiter m_active_block_modifier_interval;
|
||||
IntervalLimiter m_active_blocks_nodemetadata_interval;
|
||||
int m_active_block_interval_overload_skip;
|
||||
// Time from the beginning of the game in seconds.
|
||||
// Incremented in step().
|
||||
u32 m_game_time;
|
||||
|
||||
58
src/game.cpp
58
src/game.cpp
@@ -28,7 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
#include "server.h"
|
||||
#include "guiPauseMenu.h"
|
||||
#include "guiPasswordChange.h"
|
||||
#include "guiInventoryMenu.h"
|
||||
#include "guiFormSpecMenu.h"
|
||||
#include "guiTextInputMenu.h"
|
||||
#include "guiDeathScreen.h"
|
||||
#include "tool.h"
|
||||
@@ -77,6 +77,10 @@ struct TextDestChat : public TextDest
|
||||
{
|
||||
m_client->typeChatMessage(text);
|
||||
}
|
||||
void gotText(std::map<std::string, std::string> fields)
|
||||
{
|
||||
m_client->typeChatMessage(narrow_to_wide(fields["text"]));
|
||||
}
|
||||
|
||||
Client *m_client;
|
||||
};
|
||||
@@ -88,20 +92,39 @@ struct TextDestNodeMetadata : public TextDest
|
||||
m_p = p;
|
||||
m_client = client;
|
||||
}
|
||||
// This is deprecated I guess? -celeron55
|
||||
void gotText(std::wstring text)
|
||||
{
|
||||
std::string ntext = wide_to_narrow(text);
|
||||
infostream<<"Changing text of a sign node: "
|
||||
<<ntext<<std::endl;
|
||||
infostream<<"Submitting 'text' field of node at ("<<m_p.X<<","
|
||||
<<m_p.Y<<","<<m_p.Z<<"): "<<ntext<<std::endl;
|
||||
std::map<std::string, std::string> fields;
|
||||
fields["text"] = ntext;
|
||||
m_client->sendNodemetaFields(m_p, "", fields);
|
||||
}
|
||||
void gotText(std::map<std::string, std::string> fields)
|
||||
{
|
||||
m_client->sendNodemetaFields(m_p, "", fields);
|
||||
}
|
||||
|
||||
v3s16 m_p;
|
||||
Client *m_client;
|
||||
};
|
||||
|
||||
struct TextDestPlayerInventory : public TextDest
|
||||
{
|
||||
TextDestPlayerInventory(Client *client)
|
||||
{
|
||||
m_client = client;
|
||||
}
|
||||
void gotText(std::map<std::string, std::string> fields)
|
||||
{
|
||||
m_client->sendInventoryFields("", fields);
|
||||
}
|
||||
|
||||
Client *m_client;
|
||||
};
|
||||
|
||||
/* Respawn menu callback */
|
||||
|
||||
class MainRespawnInitiator: public IRespawnInitiator
|
||||
@@ -139,6 +162,13 @@ class NodeMetadataFormSource: public IFormSource
|
||||
return "";
|
||||
return meta->getString("formspec");
|
||||
}
|
||||
std::string resolveText(std::string str)
|
||||
{
|
||||
NodeMetadata *meta = m_map->getNodeMetadata(m_p);
|
||||
if(!meta)
|
||||
return str;
|
||||
return meta->resolveString(str);
|
||||
}
|
||||
|
||||
ClientMap *m_map;
|
||||
v3s16 m_p;
|
||||
@@ -1479,8 +1509,8 @@ void the_game(
|
||||
infostream<<"the_game: "
|
||||
<<"Launching inventory"<<std::endl;
|
||||
|
||||
GUIInventoryMenu *menu =
|
||||
new GUIInventoryMenu(guienv, guiroot, -1,
|
||||
GUIFormSpecMenu *menu =
|
||||
new GUIFormSpecMenu(guienv, guiroot, -1,
|
||||
&g_menumgr,
|
||||
&client, gamedef);
|
||||
|
||||
@@ -1490,7 +1520,8 @@ void the_game(
|
||||
PlayerInventoryFormSource *src = new PlayerInventoryFormSource(&client);
|
||||
assert(src);
|
||||
menu->setFormSpec(src->getForm(), inventoryloc);
|
||||
menu->setFormSource(new PlayerInventoryFormSource(&client));
|
||||
menu->setFormSource(src);
|
||||
menu->setTextDest(new TextDestPlayerInventory(&client));
|
||||
menu->drop();
|
||||
}
|
||||
else if(input->wasKeyDown(EscapeKey))
|
||||
@@ -2219,7 +2250,8 @@ void the_game(
|
||||
{
|
||||
infostream<<"Ground right-clicked"<<std::endl;
|
||||
|
||||
// sign special case, at least until formspec is properly implemented
|
||||
// Sign special case, at least until formspec is properly implemented.
|
||||
// Deprecated?
|
||||
if(meta && meta->getString("formspec") == "hack:sign_text_input" && !random_input)
|
||||
{
|
||||
infostream<<"Launching metadata text input"<<std::endl;
|
||||
@@ -2244,14 +2276,15 @@ void the_game(
|
||||
|
||||
/* Create menu */
|
||||
|
||||
GUIInventoryMenu *menu =
|
||||
new GUIInventoryMenu(guienv, guiroot, -1,
|
||||
GUIFormSpecMenu *menu =
|
||||
new GUIFormSpecMenu(guienv, guiroot, -1,
|
||||
&g_menumgr,
|
||||
&client, gamedef);
|
||||
menu->setFormSpec(meta->getString("formspec"),
|
||||
inventoryloc);
|
||||
menu->setFormSource(new NodeMetadataFormSource(
|
||||
&client.getEnv().getClientMap(), nodepos));
|
||||
menu->setTextDest(new TextDestNodeMetadata(nodepos, &client));
|
||||
menu->drop();
|
||||
}
|
||||
// Otherwise report right click to server
|
||||
@@ -2271,6 +2304,13 @@ void the_game(
|
||||
<<playeritem.name<<" is "
|
||||
<<def.node_placement_prediction<<std::endl;
|
||||
v3s16 p = neighbourpos;
|
||||
// Place inside node itself if buildable_to
|
||||
try{
|
||||
MapNode n_under = map.getNode(nodepos);
|
||||
if(nodedef->get(n_under).buildable_to)
|
||||
p = nodepos;
|
||||
}catch(InvalidPositionException &e){}
|
||||
// Find id of predicted node
|
||||
content_t id;
|
||||
bool found =
|
||||
nodedef->getId(def.node_placement_prediction, id);
|
||||
|
||||
@@ -29,6 +29,7 @@ class ICraftDefManager;
|
||||
class ITextureSource;
|
||||
class ISoundManager;
|
||||
class MtEventManager;
|
||||
class IRollbackReportSink;
|
||||
|
||||
/*
|
||||
An interface for fetching game-global definitions like tool and
|
||||
@@ -54,6 +55,10 @@ class IGameDef
|
||||
// Only usable on the client
|
||||
virtual ISoundManager* getSoundManager()=0;
|
||||
virtual MtEventManager* getEventManager()=0;
|
||||
|
||||
// Only usable on the server, and NOT thread-safe. It is usable from the
|
||||
// environment thread.
|
||||
virtual IRollbackReportSink* getRollbackReportSink(){return NULL;}
|
||||
|
||||
// Used on the client
|
||||
virtual bool checkLocalPrivilege(const std::string &priv)
|
||||
@@ -66,6 +71,7 @@ class IGameDef
|
||||
ITextureSource* tsrc(){return getTextureSource();}
|
||||
ISoundManager* sound(){return getSoundManager();}
|
||||
MtEventManager* event(){return getEventManager();}
|
||||
IRollbackReportSink* rollback(){return getRollbackReportSink();}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -18,7 +18,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
*/
|
||||
|
||||
|
||||
#include "guiInventoryMenu.h"
|
||||
#include "guiFormSpecMenu.h"
|
||||
#include "constants.h"
|
||||
#include "gamedef.h"
|
||||
#include "keycode.h"
|
||||
@@ -33,6 +33,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
#include "util/string.h"
|
||||
#include "util/numeric.h"
|
||||
|
||||
#include "gettext.h"
|
||||
|
||||
void drawItemStack(video::IVideoDriver *driver,
|
||||
gui::IGUIFont *font,
|
||||
const ItemStack &item,
|
||||
@@ -120,10 +122,10 @@ void drawItemStack(video::IVideoDriver *driver,
|
||||
}
|
||||
|
||||
/*
|
||||
GUIInventoryMenu
|
||||
GUIFormSpecMenu
|
||||
*/
|
||||
|
||||
GUIInventoryMenu::GUIInventoryMenu(gui::IGUIEnvironment* env,
|
||||
GUIFormSpecMenu::GUIFormSpecMenu(gui::IGUIEnvironment* env,
|
||||
gui::IGUIElement* parent, s32 id,
|
||||
IMenuManager *menumgr,
|
||||
InventoryManager *invmgr,
|
||||
@@ -133,6 +135,7 @@ GUIInventoryMenu::GUIInventoryMenu(gui::IGUIEnvironment* env,
|
||||
m_invmgr(invmgr),
|
||||
m_gamedef(gamedef),
|
||||
m_form_src(NULL),
|
||||
m_text_dst(NULL),
|
||||
m_selected_item(NULL),
|
||||
m_selected_amount(0),
|
||||
m_selected_dragging(false),
|
||||
@@ -140,15 +143,16 @@ GUIInventoryMenu::GUIInventoryMenu(gui::IGUIEnvironment* env,
|
||||
{
|
||||
}
|
||||
|
||||
GUIInventoryMenu::~GUIInventoryMenu()
|
||||
GUIFormSpecMenu::~GUIFormSpecMenu()
|
||||
{
|
||||
removeChildren();
|
||||
|
||||
delete m_selected_item;
|
||||
delete m_form_src;
|
||||
delete m_text_dst;
|
||||
}
|
||||
|
||||
void GUIInventoryMenu::removeChildren()
|
||||
void GUIFormSpecMenu::removeChildren()
|
||||
{
|
||||
const core::list<gui::IGUIElement*> &children = getChildren();
|
||||
core::list<gui::IGUIElement*> children_copy;
|
||||
@@ -175,7 +179,7 @@ void GUIInventoryMenu::removeChildren()
|
||||
}
|
||||
}
|
||||
|
||||
void GUIInventoryMenu::regenerateGui(v2u32 screensize)
|
||||
void GUIFormSpecMenu::regenerateGui(v2u32 screensize)
|
||||
{
|
||||
// Remove children
|
||||
removeChildren();
|
||||
@@ -183,24 +187,37 @@ void GUIInventoryMenu::regenerateGui(v2u32 screensize)
|
||||
v2s32 size(100,100);
|
||||
s32 helptext_h = 15;
|
||||
core::rect<s32> rect;
|
||||
|
||||
// Base position of contents of form
|
||||
v2s32 basepos = getBasePos();
|
||||
// State of basepos, 0 = not set, 1= set by formspec, 2 = set by size[] element
|
||||
// Used to adjust form size automatically if needed
|
||||
// A proceed button is added if there is no size[] element
|
||||
int bp_set = 0;
|
||||
|
||||
/* Convert m_init_draw_spec to m_inventorylists */
|
||||
|
||||
m_inventorylists.clear();
|
||||
m_images.clear();
|
||||
m_fields.clear();
|
||||
|
||||
Strfnd f(m_formspec_string);
|
||||
while(f.atend() == false)
|
||||
{
|
||||
std::string type = trim(f.next("["));
|
||||
if(type == "invsize")
|
||||
if(type == "invsize" || type == "size")
|
||||
{
|
||||
v2f invsize;
|
||||
invsize.X = stof(f.next(","));
|
||||
invsize.Y = stof(f.next(";"));
|
||||
infostream<<"invsize ("<<invsize.X<<","<<invsize.Y<<")"<<std::endl;
|
||||
f.next("]");
|
||||
if(type == "size")
|
||||
{
|
||||
invsize.Y = stof(f.next("]"));
|
||||
}
|
||||
else{
|
||||
invsize.Y = stof(f.next(";"));
|
||||
f.next("]");
|
||||
}
|
||||
infostream<<"Form size ("<<invsize.X<<","<<invsize.Y<<")"<<std::endl;
|
||||
|
||||
padding = v2s32(screensize.Y/40, screensize.Y/40);
|
||||
spacing = v2s32(screensize.Y/12, screensize.Y/13);
|
||||
@@ -218,6 +235,7 @@ void GUIInventoryMenu::regenerateGui(v2u32 screensize)
|
||||
DesiredRect = rect;
|
||||
recalculateAbsolutePosition(false);
|
||||
basepos = getBasePos();
|
||||
bp_set = 2;
|
||||
}
|
||||
else if(type == "list")
|
||||
{
|
||||
@@ -238,8 +256,13 @@ void GUIInventoryMenu::regenerateGui(v2u32 screensize)
|
||||
<<", pos=("<<pos.X<<","<<pos.Y<<")"
|
||||
<<", geom=("<<geom.X<<","<<geom.Y<<")"
|
||||
<<std::endl;
|
||||
f.next("]");
|
||||
m_inventorylists.push_back(ListDrawSpec(loc, listname, pos, geom));
|
||||
std::string start_i_s = f.next("]");
|
||||
s32 start_i = 0;
|
||||
if(start_i_s != "")
|
||||
start_i = stoi(start_i_s);
|
||||
if(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));
|
||||
}
|
||||
else if(type == "image")
|
||||
{
|
||||
@@ -254,8 +277,193 @@ void GUIInventoryMenu::regenerateGui(v2u32 screensize)
|
||||
<<", pos=("<<pos.X<<","<<pos.Y<<")"
|
||||
<<", geom=("<<geom.X<<","<<geom.Y<<")"
|
||||
<<std::endl;
|
||||
if(bp_set != 2)
|
||||
errorstream<<"WARNING: invalid use of button without a size[] element"<<std::endl;
|
||||
m_images.push_back(ImageDrawSpec(name, pos, geom));
|
||||
}
|
||||
else if(type == "field")
|
||||
{
|
||||
std::string fname = f.next(";");
|
||||
std::string flabel = f.next(";");
|
||||
|
||||
if(fname.find(",") == std::string::npos && flabel.find(",") == std::string::npos)
|
||||
{
|
||||
if(!bp_set)
|
||||
{
|
||||
rect = core::rect<s32>(
|
||||
screensize.X/2 - 580/2,
|
||||
screensize.Y/2 - 300/2,
|
||||
screensize.X/2 + 580/2,
|
||||
screensize.Y/2 + 300/2
|
||||
);
|
||||
DesiredRect = rect;
|
||||
recalculateAbsolutePosition(false);
|
||||
basepos = getBasePos();
|
||||
bp_set = 1;
|
||||
}
|
||||
else if(bp_set == 2)
|
||||
errorstream<<"WARNING: invalid use of unpositioned field in inventory"<<std::endl;
|
||||
|
||||
v2s32 pos = basepos;
|
||||
pos.Y = ((m_fields.size()+2)*60);
|
||||
v2s32 size = DesiredRect.getSize();
|
||||
rect = core::rect<s32>(size.X/2-150, pos.Y, (size.X/2-150)+300, pos.Y+30);
|
||||
}
|
||||
else
|
||||
{
|
||||
v2s32 pos;
|
||||
pos.X = stof(fname.substr(0,fname.find(","))) * (float)spacing.X;
|
||||
pos.Y = stof(fname.substr(fname.find(",")+1)) * (float)spacing.Y;
|
||||
v2s32 geom;
|
||||
geom.X = (stof(flabel.substr(0,flabel.find(","))) * (float)spacing.X)-(spacing.X-imgsize.X);
|
||||
pos.Y += (stof(flabel.substr(flabel.find(",")+1)) * (float)imgsize.Y)/2;
|
||||
|
||||
rect = core::rect<s32>(pos.X, pos.Y-15, pos.X+geom.X, pos.Y+15);
|
||||
|
||||
fname = f.next(";");
|
||||
flabel = f.next(";");
|
||||
if(bp_set != 2)
|
||||
errorstream<<"WARNING: invalid use of positioned field without a size[] element"<<std::endl;
|
||||
|
||||
}
|
||||
|
||||
std::string odefault = f.next("]");
|
||||
std::string fdefault;
|
||||
|
||||
// fdefault may contain a variable reference, which
|
||||
// needs to be resolved from the node metadata
|
||||
if(m_form_src)
|
||||
fdefault = m_form_src->resolveText(odefault);
|
||||
else
|
||||
fdefault = odefault;
|
||||
|
||||
FieldSpec spec = FieldSpec(
|
||||
narrow_to_wide(fname.c_str()),
|
||||
narrow_to_wide(flabel.c_str()),
|
||||
narrow_to_wide(fdefault.c_str()),
|
||||
258+m_fields.size()
|
||||
);
|
||||
|
||||
// three cases: field and no label, label and no field, label and field
|
||||
if (flabel == "")
|
||||
{
|
||||
spec.send = true;
|
||||
gui::IGUIElement *e = Environment->addEditBox(spec.fdefault.c_str(), rect, true, this, spec.fid);
|
||||
Environment->setFocus(e);
|
||||
|
||||
irr::SEvent evt;
|
||||
evt.EventType = EET_KEY_INPUT_EVENT;
|
||||
evt.KeyInput.Key = KEY_END;
|
||||
evt.KeyInput.PressedDown = true;
|
||||
e->OnEvent(evt);
|
||||
}
|
||||
else if (fname == "")
|
||||
{
|
||||
// set spec field id to 0, this stops submit searching for a value that isn't there
|
||||
Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, spec.fid);
|
||||
}
|
||||
else
|
||||
{
|
||||
spec.send = true;
|
||||
gui::IGUIElement *e = Environment->addEditBox(spec.fdefault.c_str(), rect, true, this, spec.fid);
|
||||
Environment->setFocus(e);
|
||||
rect.UpperLeftCorner.Y -= 15;
|
||||
rect.LowerRightCorner.Y -= 15;
|
||||
Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, 0);
|
||||
|
||||
irr::SEvent evt;
|
||||
evt.EventType = EET_KEY_INPUT_EVENT;
|
||||
evt.KeyInput.Key = KEY_END;
|
||||
evt.KeyInput.PressedDown = true;
|
||||
e->OnEvent(evt);
|
||||
}
|
||||
|
||||
m_fields.push_back(spec);
|
||||
}
|
||||
else if(type == "label")
|
||||
{
|
||||
v2s32 pos = padding;
|
||||
pos.X += stof(f.next(",")) * (float)spacing.X;
|
||||
pos.Y += stof(f.next(";")) * (float)spacing.Y;
|
||||
|
||||
rect = core::rect<s32>(pos.X, pos.Y+((imgsize.Y/2)-15), pos.X+300, pos.Y+((imgsize.Y/2)+15));
|
||||
|
||||
std::string flabel = f.next("]");
|
||||
if(bp_set != 2)
|
||||
errorstream<<"WARNING: invalid use of label without a size[] element"<<std::endl;
|
||||
|
||||
FieldSpec spec = FieldSpec(
|
||||
narrow_to_wide(""),
|
||||
narrow_to_wide(flabel.c_str()),
|
||||
narrow_to_wide(""),
|
||||
258+m_fields.size()
|
||||
);
|
||||
Environment->addStaticText(spec.flabel.c_str(), rect, false, true, this, spec.fid);
|
||||
m_fields.push_back(spec);
|
||||
}
|
||||
else if(type == "button" || type == "button_exit")
|
||||
{
|
||||
v2s32 pos = padding;
|
||||
pos.X += stof(f.next(",")) * (float)spacing.X;
|
||||
pos.Y += stof(f.next(";")) * (float)spacing.Y;
|
||||
v2s32 geom;
|
||||
geom.X = (stof(f.next(",")) * (float)spacing.X)-(spacing.X-imgsize.X);
|
||||
pos.Y += (stof(f.next(";")) * (float)imgsize.Y)/2;
|
||||
|
||||
rect = core::rect<s32>(pos.X, pos.Y-15, pos.X+geom.X, pos.Y+15);
|
||||
|
||||
std::string fname = f.next(";");
|
||||
std::string flabel = f.next("]");
|
||||
if(bp_set != 2)
|
||||
errorstream<<"WARNING: invalid use of button without a size[] element"<<std::endl;
|
||||
|
||||
FieldSpec spec = FieldSpec(
|
||||
narrow_to_wide(fname.c_str()),
|
||||
narrow_to_wide(flabel.c_str()),
|
||||
narrow_to_wide(""),
|
||||
258+m_fields.size()
|
||||
);
|
||||
spec.is_button = true;
|
||||
if(type == "button_exit")
|
||||
spec.is_exit = true;
|
||||
Environment->addButton(rect, this, spec.fid, spec.flabel.c_str());
|
||||
m_fields.push_back(spec);
|
||||
}
|
||||
else if(type == "image_button" || type == "image_button_exit")
|
||||
{
|
||||
v2s32 pos = padding;
|
||||
pos.X += stof(f.next(",")) * (float)spacing.X;
|
||||
pos.Y += stof(f.next(";")) * (float)spacing.Y;
|
||||
v2s32 geom;
|
||||
geom.X = (stof(f.next(",")) * (float)spacing.X)-(spacing.X-imgsize.X);
|
||||
geom.Y = (stof(f.next(";")) * (float)spacing.Y)-(spacing.Y-imgsize.Y);
|
||||
|
||||
rect = core::rect<s32>(pos.X, pos.Y, pos.X+geom.X, pos.Y+geom.Y);
|
||||
|
||||
std::string fimage = f.next(";");
|
||||
std::string fname = f.next(";");
|
||||
std::string flabel = f.next("]");
|
||||
if(bp_set != 2)
|
||||
errorstream<<"WARNING: invalid use of image_button without a size[] element"<<std::endl;
|
||||
|
||||
FieldSpec spec = FieldSpec(
|
||||
narrow_to_wide(fname.c_str()),
|
||||
narrow_to_wide(flabel.c_str()),
|
||||
narrow_to_wide(fimage.c_str()),
|
||||
258+m_fields.size()
|
||||
);
|
||||
spec.is_button = true;
|
||||
if(type == "image_button_exit")
|
||||
spec.is_exit = true;
|
||||
|
||||
video::ITexture *texture = m_gamedef->tsrc()->getTextureRaw(fimage);
|
||||
gui::IGUIButton *e = Environment->addButton(rect, this, spec.fid, spec.flabel.c_str());
|
||||
e->setImage(texture);
|
||||
e->setPressedImage(texture);
|
||||
e->setScaleImage(true);
|
||||
|
||||
m_fields.push_back(spec);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Ignore others
|
||||
@@ -265,16 +473,44 @@ void GUIInventoryMenu::regenerateGui(v2u32 screensize)
|
||||
}
|
||||
}
|
||||
|
||||
// Add children
|
||||
// If there's inventory, put the usage string at the bottom
|
||||
if (m_inventorylists.size())
|
||||
{
|
||||
changeCtype("");
|
||||
core::rect<s32> rect(0, 0, size.X-padding.X*2, helptext_h);
|
||||
rect = rect + v2s32(size.X/2 - rect.getWidth()/2,
|
||||
size.Y-rect.getHeight()-5);
|
||||
const wchar_t *text =
|
||||
L"Left click: Move all items, Right click: Move single item";
|
||||
const wchar_t *text = wgettext("Left click: Move all items, Right click: Move single item");
|
||||
Environment->addStaticText(text, rect, false, true, this, 256);
|
||||
changeCtype("C");
|
||||
}
|
||||
// If there's fields, add a Proceed button
|
||||
if (m_fields.size() && bp_set != 2)
|
||||
{
|
||||
// if the size wasn't set by an invsize[] or size[] adjust it now to fit all the fields
|
||||
rect = core::rect<s32>(
|
||||
screensize.X/2 - 580/2,
|
||||
screensize.Y/2 - 300/2,
|
||||
screensize.X/2 + 580/2,
|
||||
screensize.Y/2 + 240/2+(m_fields.size()*60)
|
||||
);
|
||||
DesiredRect = rect;
|
||||
recalculateAbsolutePosition(false);
|
||||
basepos = getBasePos();
|
||||
|
||||
// Add tooltip
|
||||
changeCtype("");
|
||||
{
|
||||
v2s32 pos = basepos;
|
||||
pos.Y = ((m_fields.size()+2)*60);
|
||||
|
||||
v2s32 size = DesiredRect.getSize();
|
||||
rect = core::rect<s32>(size.X/2-70, pos.Y, (size.X/2-70)+140, pos.Y+30);
|
||||
Environment->addButton(rect, this, 257, wgettext("Proceed"));
|
||||
}
|
||||
changeCtype("C");
|
||||
}
|
||||
// Add tooltip
|
||||
{
|
||||
// Note: parent != this so that the tooltip isn't clipped by the menu rectangle
|
||||
m_tooltip_element = Environment->addStaticText(L"",core::rect<s32>(0,0,110,18));
|
||||
m_tooltip_element->enableOverrideColor(true);
|
||||
@@ -287,7 +523,7 @@ void GUIInventoryMenu::regenerateGui(v2u32 screensize)
|
||||
}
|
||||
}
|
||||
|
||||
GUIInventoryMenu::ItemSpec GUIInventoryMenu::getItemAtPos(v2s32 p) const
|
||||
GUIFormSpecMenu::ItemSpec GUIFormSpecMenu::getItemAtPos(v2s32 p) const
|
||||
{
|
||||
core::rect<s32> imgrect(0,0,imgsize.X,imgsize.Y);
|
||||
|
||||
@@ -297,13 +533,14 @@ GUIInventoryMenu::ItemSpec GUIInventoryMenu::getItemAtPos(v2s32 p) const
|
||||
|
||||
for(s32 i=0; i<s.geom.X*s.geom.Y; i++)
|
||||
{
|
||||
s32 item_i = i + s.start_item_i;
|
||||
s32 x = (i%s.geom.X) * spacing.X;
|
||||
s32 y = (i/s.geom.X) * spacing.Y;
|
||||
v2s32 p0(x,y);
|
||||
core::rect<s32> rect = imgrect + s.pos + p0;
|
||||
if(rect.isPointInside(p))
|
||||
{
|
||||
return ItemSpec(s.inventoryloc, s.listname, i);
|
||||
return ItemSpec(s.inventoryloc, s.listname, item_i);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -311,7 +548,7 @@ GUIInventoryMenu::ItemSpec GUIInventoryMenu::getItemAtPos(v2s32 p) const
|
||||
return ItemSpec(InventoryLocation(), "", -1);
|
||||
}
|
||||
|
||||
void GUIInventoryMenu::drawList(const ListDrawSpec &s, int phase)
|
||||
void GUIFormSpecMenu::drawList(const ListDrawSpec &s, int phase)
|
||||
{
|
||||
video::IVideoDriver* driver = Environment->getVideoDriver();
|
||||
|
||||
@@ -323,7 +560,7 @@ void GUIInventoryMenu::drawList(const ListDrawSpec &s, int phase)
|
||||
|
||||
Inventory *inv = m_invmgr->getInventory(s.inventoryloc);
|
||||
if(!inv){
|
||||
infostream<<"GUIInventoryMenu::drawList(): WARNING: "
|
||||
infostream<<"GUIFormSpecMenu::drawList(): WARNING: "
|
||||
<<"The inventory location "
|
||||
<<"\""<<s.inventoryloc.dump()<<"\" doesn't exist"
|
||||
<<std::endl;
|
||||
@@ -331,7 +568,7 @@ void GUIInventoryMenu::drawList(const ListDrawSpec &s, int phase)
|
||||
}
|
||||
InventoryList *ilist = inv->getList(s.listname);
|
||||
if(!ilist){
|
||||
infostream<<"GUIInventoryMenu::drawList(): WARNING: "
|
||||
infostream<<"GUIFormSpecMenu::drawList(): WARNING: "
|
||||
<<"The inventory list \""<<s.listname<<"\" @ \""
|
||||
<<s.inventoryloc.dump()<<"\" doesn't exist"
|
||||
<<std::endl;
|
||||
@@ -342,13 +579,16 @@ void GUIInventoryMenu::drawList(const ListDrawSpec &s, int phase)
|
||||
|
||||
for(s32 i=0; i<s.geom.X*s.geom.Y; i++)
|
||||
{
|
||||
u32 item_i = i + s.start_item_i;
|
||||
if(item_i >= ilist->getSize())
|
||||
break;
|
||||
s32 x = (i%s.geom.X) * spacing.X;
|
||||
s32 y = (i/s.geom.X) * spacing.Y;
|
||||
v2s32 p(x,y);
|
||||
core::rect<s32> rect = imgrect + s.pos + p;
|
||||
ItemStack item;
|
||||
if(ilist)
|
||||
item = ilist->getItem(i);
|
||||
item = ilist->getItem(item_i);
|
||||
|
||||
bool selected = m_selected_item
|
||||
&& m_invmgr->getInventory(m_selected_item->inventoryloc) == inv
|
||||
@@ -404,7 +644,7 @@ void GUIInventoryMenu::drawList(const ListDrawSpec &s, int phase)
|
||||
}
|
||||
}
|
||||
|
||||
void GUIInventoryMenu::drawSelectedItem()
|
||||
void GUIFormSpecMenu::drawSelectedItem()
|
||||
{
|
||||
if(!m_selected_item)
|
||||
return;
|
||||
@@ -429,7 +669,7 @@ void GUIInventoryMenu::drawSelectedItem()
|
||||
drawItemStack(driver, font, stack, rect, NULL, m_gamedef);
|
||||
}
|
||||
|
||||
void GUIInventoryMenu::drawMenu()
|
||||
void GUIFormSpecMenu::drawMenu()
|
||||
{
|
||||
if(m_form_src){
|
||||
std::string newform = m_form_src->getForm();
|
||||
@@ -491,7 +731,7 @@ void GUIInventoryMenu::drawMenu()
|
||||
gui::IGUIElement::draw();
|
||||
}
|
||||
|
||||
void GUIInventoryMenu::updateSelectedItem()
|
||||
void GUIFormSpecMenu::updateSelectedItem()
|
||||
{
|
||||
// If the selected stack has become empty for some reason, deselect it.
|
||||
// If the selected stack has become smaller, adjust m_selected_amount.
|
||||
@@ -558,7 +798,36 @@ void GUIInventoryMenu::updateSelectedItem()
|
||||
}
|
||||
}
|
||||
|
||||
bool GUIInventoryMenu::OnEvent(const SEvent& event)
|
||||
void GUIFormSpecMenu::acceptInput()
|
||||
{
|
||||
if(m_text_dst)
|
||||
{
|
||||
std::map<std::string, std::string> fields;
|
||||
gui::IGUIElement *e;
|
||||
for(u32 i=0; i<m_fields.size(); i++)
|
||||
{
|
||||
const FieldSpec &s = m_fields[i];
|
||||
if(s.send)
|
||||
{
|
||||
if(s.is_button)
|
||||
{
|
||||
fields[wide_to_narrow(s.fname.c_str())] = wide_to_narrow(s.flabel.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
e = getElementFromId(s.fid);
|
||||
if(e != NULL)
|
||||
{
|
||||
fields[wide_to_narrow(s.fname.c_str())] = wide_to_narrow(e->getText());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
m_text_dst->gotText(fields);
|
||||
}
|
||||
}
|
||||
|
||||
bool GUIFormSpecMenu::OnEvent(const SEvent& event)
|
||||
{
|
||||
if(event.EventType==EET_KEY_INPUT_EVENT)
|
||||
{
|
||||
@@ -569,6 +838,12 @@ bool GUIInventoryMenu::OnEvent(const SEvent& event)
|
||||
quitMenu();
|
||||
return true;
|
||||
}
|
||||
if(event.KeyInput.Key==KEY_RETURN && event.KeyInput.PressedDown)
|
||||
{
|
||||
acceptInput();
|
||||
quitMenu();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if(event.EventType==EET_MOUSE_INPUT_EVENT
|
||||
&& event.MouseInput.Event == EMIE_MOUSE_MOVED)
|
||||
@@ -615,14 +890,14 @@ bool GUIInventoryMenu::OnEvent(const SEvent& event)
|
||||
|
||||
InventoryList *list = inv_s->getList(s.listname);
|
||||
if(list == NULL){
|
||||
errorstream<<"InventoryMenu: The selected inventory list \""
|
||||
verbosestream<<"InventoryMenu: The selected inventory list \""
|
||||
<<s.listname<<"\" does not exist"<<std::endl;
|
||||
s.i = -1; // make it invalid again
|
||||
break;
|
||||
}
|
||||
|
||||
if((u32)s.i >= list->getSize()){
|
||||
errorstream<<"InventoryMenu: The selected inventory list \""
|
||||
infostream<<"InventoryMenu: The selected inventory list \""
|
||||
<<s.listname<<"\" is too small (i="<<s.i<<", size="
|
||||
<<list->getSize()<<")"<<std::endl;
|
||||
s.i = -1; // make it invalid again
|
||||
@@ -860,7 +1135,7 @@ bool GUIInventoryMenu::OnEvent(const SEvent& event)
|
||||
{
|
||||
if(!canTakeFocus(event.GUIEvent.Element))
|
||||
{
|
||||
infostream<<"GUIInventoryMenu: Not allowing focus change."
|
||||
infostream<<"GUIFormSpecMenu: Not allowing focus change."
|
||||
<<std::endl;
|
||||
// Returning true disables focus change
|
||||
return true;
|
||||
@@ -868,15 +1143,45 @@ bool GUIInventoryMenu::OnEvent(const SEvent& event)
|
||||
}
|
||||
if(event.GUIEvent.EventType==gui::EGET_BUTTON_CLICKED)
|
||||
{
|
||||
/*switch(event.GUIEvent.Caller->getID())
|
||||
switch(event.GUIEvent.Caller->getID())
|
||||
{
|
||||
case 256: // continue
|
||||
setVisible(false);
|
||||
break;
|
||||
case 257: // exit
|
||||
dev->closeDevice();
|
||||
break;
|
||||
}*/
|
||||
case 257:
|
||||
acceptInput();
|
||||
quitMenu();
|
||||
// quitMenu deallocates menu
|
||||
return true;
|
||||
}
|
||||
// find the element that was clicked
|
||||
for(u32 i=0; i<m_fields.size(); i++)
|
||||
{
|
||||
FieldSpec &s = m_fields[i];
|
||||
// if its a button, set the send field so
|
||||
// lua knows which button was pressed
|
||||
if (s.is_button && s.fid == event.GUIEvent.Caller->getID())
|
||||
{
|
||||
s.send = true;
|
||||
acceptInput();
|
||||
if(s.is_exit){
|
||||
quitMenu();
|
||||
return true;
|
||||
}else{
|
||||
s.send = false;
|
||||
// Restore focus to the full form
|
||||
Environment->setFocus(this);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(event.GUIEvent.EventType==gui::EGET_EDITBOX_ENTER)
|
||||
{
|
||||
if(event.GUIEvent.Caller->getID() > 257)
|
||||
{
|
||||
acceptInput();
|
||||
quitMenu();
|
||||
// quitMenu deallocates menu
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,11 +29,21 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
class IGameDef;
|
||||
class InventoryManager;
|
||||
|
||||
struct TextDest
|
||||
{
|
||||
virtual ~TextDest() {};
|
||||
// This is deprecated I guess? -celeron55
|
||||
virtual void gotText(std::wstring text){}
|
||||
virtual void gotText(std::map<std::string, std::string> fields) = 0;
|
||||
};
|
||||
|
||||
class IFormSource
|
||||
{
|
||||
public:
|
||||
virtual ~IFormSource(){}
|
||||
virtual std::string getForm() = 0;
|
||||
// Fill in variables in field text
|
||||
virtual std::string resolveText(std::string str){ return str; }
|
||||
};
|
||||
|
||||
void drawItemStack(video::IVideoDriver *driver,
|
||||
@@ -43,7 +53,7 @@ void drawItemStack(video::IVideoDriver *driver,
|
||||
const core::rect<s32> *clip,
|
||||
IGameDef *gamedef);
|
||||
|
||||
class GUIInventoryMenu : public GUIModalMenu
|
||||
class GUIFormSpecMenu : public GUIModalMenu
|
||||
{
|
||||
struct ItemSpec
|
||||
{
|
||||
@@ -76,11 +86,12 @@ class GUIInventoryMenu : public GUIModalMenu
|
||||
}
|
||||
ListDrawSpec(const InventoryLocation &a_inventoryloc,
|
||||
const std::string &a_listname,
|
||||
v2s32 a_pos, v2s32 a_geom):
|
||||
v2s32 a_pos, v2s32 a_geom, s32 a_start_item_i):
|
||||
inventoryloc(a_inventoryloc),
|
||||
listname(a_listname),
|
||||
pos(a_pos),
|
||||
geom(a_geom)
|
||||
geom(a_geom),
|
||||
start_item_i(a_start_item_i)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -88,6 +99,7 @@ class GUIInventoryMenu : public GUIModalMenu
|
||||
std::string listname;
|
||||
v2s32 pos;
|
||||
v2s32 geom;
|
||||
s32 start_item_i;
|
||||
};
|
||||
|
||||
struct ImageDrawSpec
|
||||
@@ -106,15 +118,39 @@ class GUIInventoryMenu : public GUIModalMenu
|
||||
v2s32 pos;
|
||||
v2s32 geom;
|
||||
};
|
||||
|
||||
struct FieldSpec
|
||||
{
|
||||
FieldSpec()
|
||||
{
|
||||
}
|
||||
FieldSpec(const std::wstring name, const std::wstring label, const std::wstring fdeflt, int id):
|
||||
fname(name),
|
||||
flabel(label),
|
||||
fdefault(fdeflt),
|
||||
fid(id)
|
||||
{
|
||||
send = false;
|
||||
is_button = false;
|
||||
is_exit = false;
|
||||
}
|
||||
std::wstring fname;
|
||||
std::wstring flabel;
|
||||
std::wstring fdefault;
|
||||
int fid;
|
||||
bool send;
|
||||
bool is_button;
|
||||
bool is_exit;
|
||||
};
|
||||
|
||||
public:
|
||||
GUIInventoryMenu(gui::IGUIEnvironment* env,
|
||||
GUIFormSpecMenu(gui::IGUIEnvironment* env,
|
||||
gui::IGUIElement* parent, s32 id,
|
||||
IMenuManager *menumgr,
|
||||
InventoryManager *invmgr,
|
||||
IGameDef *gamedef
|
||||
);
|
||||
~GUIInventoryMenu();
|
||||
~GUIFormSpecMenu();
|
||||
|
||||
void setFormSpec(const std::string &formspec_string,
|
||||
InventoryLocation current_inventory_location)
|
||||
@@ -124,12 +160,18 @@ class GUIInventoryMenu : public GUIModalMenu
|
||||
regenerateGui(m_screensize_old);
|
||||
}
|
||||
|
||||
// form_src is deleted by this GUIInventoryMenu
|
||||
// form_src is deleted by this GUIFormSpecMenu
|
||||
void setFormSource(IFormSource *form_src)
|
||||
{
|
||||
m_form_src = form_src;
|
||||
}
|
||||
|
||||
// text_dst is deleted by this GUIFormSpecMenu
|
||||
void setTextDest(TextDest *text_dst)
|
||||
{
|
||||
m_text_dst = text_dst;
|
||||
}
|
||||
|
||||
void removeChildren();
|
||||
/*
|
||||
Remove and re-add (or reposition) stuff
|
||||
@@ -142,6 +184,7 @@ class GUIInventoryMenu : public GUIModalMenu
|
||||
void drawMenu();
|
||||
void updateSelectedItem();
|
||||
|
||||
void acceptInput();
|
||||
bool OnEvent(const SEvent& event);
|
||||
|
||||
protected:
|
||||
@@ -160,9 +203,11 @@ class GUIInventoryMenu : public GUIModalMenu
|
||||
std::string m_formspec_string;
|
||||
InventoryLocation m_current_inventory_location;
|
||||
IFormSource *m_form_src;
|
||||
TextDest *m_text_dst;
|
||||
|
||||
core::array<ListDrawSpec> m_inventorylists;
|
||||
core::array<ImageDrawSpec> m_images;
|
||||
core::array<FieldSpec> m_fields;
|
||||
|
||||
ItemSpec *m_selected_item;
|
||||
u32 m_selected_amount;
|
||||
@@ -22,14 +22,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
|
||||
#include "irrlichttypes_extrabloated.h"
|
||||
#include "modalMenu.h"
|
||||
#include "guiFormSpecMenu.h"
|
||||
#include <string>
|
||||
|
||||
struct TextDest
|
||||
{
|
||||
virtual void gotText(std::wstring text) = 0;
|
||||
virtual ~TextDest() {};
|
||||
};
|
||||
|
||||
class GUITextInputMenu : public GUIModalMenu
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -25,6 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
#include "main.h" // for g_settings
|
||||
#include "settings.h"
|
||||
#include "craftdef.h"
|
||||
#include "rollback_interface.h"
|
||||
|
||||
#define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")"
|
||||
|
||||
@@ -41,30 +42,25 @@ std::string InventoryLocation::dump() const
|
||||
|
||||
void InventoryLocation::serialize(std::ostream &os) const
|
||||
{
|
||||
switch(type){
|
||||
case InventoryLocation::UNDEFINED:
|
||||
{
|
||||
switch(type){
|
||||
case InventoryLocation::UNDEFINED:
|
||||
os<<"undefined";
|
||||
}
|
||||
break;
|
||||
case InventoryLocation::CURRENT_PLAYER:
|
||||
{
|
||||
break;
|
||||
case InventoryLocation::CURRENT_PLAYER:
|
||||
os<<"current_player";
|
||||
}
|
||||
break;
|
||||
case InventoryLocation::PLAYER:
|
||||
{
|
||||
break;
|
||||
case InventoryLocation::PLAYER:
|
||||
os<<"player:"<<name;
|
||||
}
|
||||
break;
|
||||
case InventoryLocation::NODEMETA:
|
||||
{
|
||||
break;
|
||||
case InventoryLocation::NODEMETA:
|
||||
os<<"nodemeta:"<<p.X<<","<<p.Y<<","<<p.Z;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
break;
|
||||
case InventoryLocation::DETACHED:
|
||||
os<<"detached:"<<name;
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
}
|
||||
|
||||
void InventoryLocation::deSerialize(std::istream &is)
|
||||
@@ -94,6 +90,11 @@ void InventoryLocation::deSerialize(std::istream &is)
|
||||
p.Y = stoi(fn.next(","));
|
||||
p.Z = stoi(fn.next(","));
|
||||
}
|
||||
else if(tname == "detached")
|
||||
{
|
||||
type = InventoryLocation::DETACHED;
|
||||
std::getline(is, name, '\n');
|
||||
}
|
||||
else
|
||||
{
|
||||
infostream<<"Unknown InventoryLocation type=\""<<tname<<"\""<<std::endl;
|
||||
@@ -198,78 +199,114 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame
|
||||
<<", to_list=\""<<to_list<<"\""<<std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
Do not handle rollback if both inventories are that of the same player
|
||||
*/
|
||||
bool ignore_rollback = (
|
||||
from_inv.type == InventoryLocation::PLAYER &&
|
||||
to_inv.type == InventoryLocation::PLAYER &&
|
||||
from_inv.name == to_inv.name);
|
||||
|
||||
/*
|
||||
Collect information of endpoints
|
||||
*/
|
||||
|
||||
int try_take_count = count;
|
||||
if(try_take_count == 0)
|
||||
try_take_count = list_from->getItem(from_i).count;
|
||||
|
||||
int src_can_take_count = 0xffff;
|
||||
int dst_can_put_count = 0xffff;
|
||||
|
||||
// Handle node metadata move
|
||||
if(from_inv.type == InventoryLocation::NODEMETA &&
|
||||
to_inv.type == InventoryLocation::NODEMETA &&
|
||||
from_inv.p != to_inv.p)
|
||||
/* Query detached inventories */
|
||||
|
||||
// Move occurs in the same detached inventory
|
||||
if(from_inv.type == InventoryLocation::DETACHED &&
|
||||
to_inv.type == InventoryLocation::DETACHED &&
|
||||
from_inv.name == to_inv.name)
|
||||
{
|
||||
errorstream<<"Directly moving items between two nodes is "
|
||||
<<"disallowed."<<std::endl;
|
||||
return;
|
||||
lua_State *L = player->getEnv()->getLua();
|
||||
src_can_take_count = scriptapi_detached_inventory_allow_move(
|
||||
L, from_inv.name, from_list, from_i,
|
||||
to_list, to_i, try_take_count, player);
|
||||
dst_can_put_count = src_can_take_count;
|
||||
}
|
||||
else if(from_inv.type == InventoryLocation::NODEMETA &&
|
||||
else
|
||||
{
|
||||
// Destination is detached
|
||||
if(to_inv.type == InventoryLocation::DETACHED)
|
||||
{
|
||||
lua_State *L = player->getEnv()->getLua();
|
||||
ItemStack src_item = list_from->getItem(from_i);
|
||||
src_item.count = try_take_count;
|
||||
dst_can_put_count = scriptapi_detached_inventory_allow_put(
|
||||
L, to_inv.name, to_list, to_i, src_item, player);
|
||||
}
|
||||
// Source is detached
|
||||
if(from_inv.type == InventoryLocation::DETACHED)
|
||||
{
|
||||
lua_State *L = player->getEnv()->getLua();
|
||||
ItemStack src_item = list_from->getItem(from_i);
|
||||
src_item.count = try_take_count;
|
||||
src_can_take_count = scriptapi_detached_inventory_allow_take(
|
||||
L, from_inv.name, from_list, from_i, src_item, player);
|
||||
}
|
||||
}
|
||||
|
||||
/* Query node metadata inventories */
|
||||
|
||||
// Both endpoints are nodemeta
|
||||
// Move occurs in the same nodemeta inventory
|
||||
if(from_inv.type == InventoryLocation::NODEMETA &&
|
||||
to_inv.type == InventoryLocation::NODEMETA &&
|
||||
from_inv.p == to_inv.p)
|
||||
{
|
||||
lua_State *L = player->getEnv()->getLua();
|
||||
int count0 = count;
|
||||
if(count0 == 0)
|
||||
count0 = list_from->getItem(from_i).count;
|
||||
infostream<<player->getDescription()<<" moving "<<count0
|
||||
<<" items inside node at "<<PP(from_inv.p)<<std::endl;
|
||||
scriptapi_node_on_metadata_inventory_move(L, from_inv.p,
|
||||
from_list, from_i, to_list, to_i, count0, player);
|
||||
src_can_take_count = scriptapi_nodemeta_inventory_allow_move(
|
||||
L, from_inv.p, from_list, from_i,
|
||||
to_list, to_i, try_take_count, player);
|
||||
dst_can_put_count = src_can_take_count;
|
||||
}
|
||||
// Handle node metadata take
|
||||
else if(from_inv.type == InventoryLocation::NODEMETA)
|
||||
{
|
||||
lua_State *L = player->getEnv()->getLua();
|
||||
int count0 = count;
|
||||
if(count0 == 0)
|
||||
count0 = list_from->getItem(from_i).count;
|
||||
infostream<<player->getDescription()<<" taking "<<count0
|
||||
<<" items from node at "<<PP(from_inv.p)<<std::endl;
|
||||
ItemStack return_stack = scriptapi_node_on_metadata_inventory_take(
|
||||
L, from_inv.p, from_list, from_i, count0, player);
|
||||
if(return_stack.count == 0)
|
||||
infostream<<"Node metadata gave no items"<<std::endl;
|
||||
return_stack = list_to->addItem(to_i, return_stack);
|
||||
list_to->addItem(return_stack); // Force return of everything
|
||||
}
|
||||
// Handle node metadata offer
|
||||
else if(to_inv.type == InventoryLocation::NODEMETA)
|
||||
{
|
||||
lua_State *L = player->getEnv()->getLua();
|
||||
int count0 = count;
|
||||
if(count0 == 0)
|
||||
count0 = list_from->getItem(from_i).count;
|
||||
ItemStack offer_stack = list_from->takeItem(from_i, count0);
|
||||
infostream<<player->getDescription()<<" offering "
|
||||
<<offer_stack.count<<" items to node at "
|
||||
<<PP(to_inv.p)<<std::endl;
|
||||
ItemStack reject_stack = scriptapi_node_on_metadata_inventory_offer(
|
||||
L, to_inv.p, to_list, to_i, offer_stack, player);
|
||||
if(reject_stack.count == offer_stack.count)
|
||||
infostream<<"Node metadata rejected all items"<<std::endl;
|
||||
else if(reject_stack.count != 0)
|
||||
infostream<<"Node metadata rejected some items"<<std::endl;
|
||||
reject_stack = list_from->addItem(from_i, reject_stack);
|
||||
list_from->addItem(reject_stack); // Force return of everything
|
||||
}
|
||||
// Handle regular move
|
||||
else
|
||||
{
|
||||
/*
|
||||
This performs the actual movement
|
||||
// Destination is nodemeta
|
||||
if(to_inv.type == InventoryLocation::NODEMETA)
|
||||
{
|
||||
lua_State *L = player->getEnv()->getLua();
|
||||
ItemStack src_item = list_from->getItem(from_i);
|
||||
src_item.count = try_take_count;
|
||||
dst_can_put_count = scriptapi_nodemeta_inventory_allow_put(
|
||||
L, to_inv.p, to_list, to_i, src_item, player);
|
||||
}
|
||||
// Source is nodemeta
|
||||
if(from_inv.type == InventoryLocation::NODEMETA)
|
||||
{
|
||||
lua_State *L = player->getEnv()->getLua();
|
||||
ItemStack src_item = list_from->getItem(from_i);
|
||||
src_item.count = try_take_count;
|
||||
src_can_take_count = scriptapi_nodemeta_inventory_allow_take(
|
||||
L, from_inv.p, from_list, from_i, src_item, player);
|
||||
}
|
||||
}
|
||||
|
||||
If something is wrong (source item is empty, destination is the
|
||||
same as source), nothing happens
|
||||
*/
|
||||
list_from->moveItem(from_i, list_to, to_i, count);
|
||||
|
||||
infostream<<"IMoveAction::apply(): moved "
|
||||
<<" count="<<count
|
||||
int old_count = count;
|
||||
|
||||
/* Modify count according to collected data */
|
||||
count = try_take_count;
|
||||
if(src_can_take_count != -1 && count > src_can_take_count)
|
||||
count = src_can_take_count;
|
||||
if(dst_can_put_count != -1 && count > dst_can_put_count)
|
||||
count = dst_can_put_count;
|
||||
/* Limit according to source item count */
|
||||
if(count > list_from->getItem(from_i).count)
|
||||
count = list_from->getItem(from_i).count;
|
||||
|
||||
/* If no items will be moved, don't go further */
|
||||
if(count == 0)
|
||||
{
|
||||
infostream<<"IMoveAction::apply(): move was completely disallowed:"
|
||||
<<" count="<<old_count
|
||||
<<" from inv=\""<<from_inv.dump()<<"\""
|
||||
<<" list=\""<<from_list<<"\""
|
||||
<<" i="<<from_i
|
||||
@@ -277,8 +314,142 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame
|
||||
<<" list=\""<<to_list<<"\""
|
||||
<<" i="<<to_i
|
||||
<<std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
ItemStack src_item = list_from->getItem(from_i);
|
||||
src_item.count = count;
|
||||
ItemStack from_stack_was = list_from->getItem(from_i);
|
||||
ItemStack to_stack_was = list_to->getItem(to_i);
|
||||
|
||||
/*
|
||||
Perform actual move
|
||||
|
||||
If something is wrong (source item is empty, destination is the
|
||||
same as source), nothing happens
|
||||
*/
|
||||
list_from->moveItem(from_i, list_to, to_i, count);
|
||||
|
||||
// If source is infinite, reset it's stack
|
||||
if(src_can_take_count == -1){
|
||||
list_from->deleteItem(from_i);
|
||||
list_from->addItem(from_i, from_stack_was);
|
||||
}
|
||||
// If destination is infinite, reset it's stack and take count from source
|
||||
if(dst_can_put_count == -1){
|
||||
list_to->deleteItem(to_i);
|
||||
list_to->addItem(to_i, to_stack_was);
|
||||
list_from->takeItem(from_i, count);
|
||||
}
|
||||
|
||||
infostream<<"IMoveAction::apply(): moved"
|
||||
<<" count="<<count
|
||||
<<" from inv=\""<<from_inv.dump()<<"\""
|
||||
<<" list=\""<<from_list<<"\""
|
||||
<<" i="<<from_i
|
||||
<<" to inv=\""<<to_inv.dump()<<"\""
|
||||
<<" list=\""<<to_list<<"\""
|
||||
<<" i="<<to_i
|
||||
<<std::endl;
|
||||
|
||||
/*
|
||||
Record rollback information
|
||||
*/
|
||||
if(!ignore_rollback && gamedef->rollback())
|
||||
{
|
||||
IRollbackReportSink *rollback = gamedef->rollback();
|
||||
|
||||
// If source is not infinite, record item take
|
||||
if(!src_can_take_count != -1){
|
||||
RollbackAction action;
|
||||
std::string loc;
|
||||
{
|
||||
std::ostringstream os(std::ios::binary);
|
||||
from_inv.serialize(os);
|
||||
loc = os.str();
|
||||
}
|
||||
action.setModifyInventoryStack(loc, from_list, from_i, false,
|
||||
src_item.getItemString());
|
||||
rollback->reportAction(action);
|
||||
}
|
||||
// If destination is not infinite, record item put
|
||||
if(!dst_can_put_count != -1){
|
||||
RollbackAction action;
|
||||
std::string loc;
|
||||
{
|
||||
std::ostringstream os(std::ios::binary);
|
||||
to_inv.serialize(os);
|
||||
loc = os.str();
|
||||
}
|
||||
action.setModifyInventoryStack(loc, to_list, to_i, true,
|
||||
src_item.getItemString());
|
||||
rollback->reportAction(action);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Report move to endpoints
|
||||
*/
|
||||
|
||||
/* Detached inventories */
|
||||
|
||||
// Both endpoints are same detached
|
||||
if(from_inv.type == InventoryLocation::DETACHED &&
|
||||
to_inv.type == InventoryLocation::DETACHED &&
|
||||
from_inv.name == to_inv.name)
|
||||
{
|
||||
lua_State *L = player->getEnv()->getLua();
|
||||
scriptapi_detached_inventory_on_move(
|
||||
L, from_inv.name, from_list, from_i,
|
||||
to_list, to_i, count, player);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Destination is detached
|
||||
if(to_inv.type == InventoryLocation::DETACHED)
|
||||
{
|
||||
lua_State *L = player->getEnv()->getLua();
|
||||
scriptapi_detached_inventory_on_put(
|
||||
L, to_inv.name, to_list, to_i, src_item, player);
|
||||
}
|
||||
// Source is detached
|
||||
if(from_inv.type == InventoryLocation::DETACHED)
|
||||
{
|
||||
lua_State *L = player->getEnv()->getLua();
|
||||
scriptapi_detached_inventory_on_take(
|
||||
L, from_inv.name, from_list, from_i, src_item, player);
|
||||
}
|
||||
}
|
||||
|
||||
/* Node metadata inventories */
|
||||
|
||||
// Both endpoints are same nodemeta
|
||||
if(from_inv.type == InventoryLocation::NODEMETA &&
|
||||
to_inv.type == InventoryLocation::NODEMETA &&
|
||||
from_inv.p == to_inv.p)
|
||||
{
|
||||
lua_State *L = player->getEnv()->getLua();
|
||||
scriptapi_nodemeta_inventory_on_move(
|
||||
L, from_inv.p, from_list, from_i,
|
||||
to_list, to_i, count, player);
|
||||
}
|
||||
else{
|
||||
// Destination is nodemeta
|
||||
if(to_inv.type == InventoryLocation::NODEMETA)
|
||||
{
|
||||
lua_State *L = player->getEnv()->getLua();
|
||||
scriptapi_nodemeta_inventory_on_put(
|
||||
L, to_inv.p, to_list, to_i, src_item, player);
|
||||
}
|
||||
// Source is nodemeta
|
||||
else if(from_inv.type == InventoryLocation::NODEMETA)
|
||||
{
|
||||
lua_State *L = player->getEnv()->getLua();
|
||||
scriptapi_nodemeta_inventory_on_take(
|
||||
L, from_inv.p, from_list, from_i, src_item, player);
|
||||
}
|
||||
}
|
||||
|
||||
mgr->setInventoryModified(from_inv);
|
||||
if(inv_from != inv_to)
|
||||
mgr->setInventoryModified(to_inv);
|
||||
@@ -361,47 +532,69 @@ void IDropAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame
|
||||
return;
|
||||
}
|
||||
|
||||
ItemStack item1;
|
||||
/*
|
||||
Do not handle rollback if inventory is player's
|
||||
*/
|
||||
bool ignore_src_rollback = (from_inv.type == InventoryLocation::PLAYER);
|
||||
|
||||
// Handle node metadata take
|
||||
/*
|
||||
Collect information of endpoints
|
||||
*/
|
||||
|
||||
int take_count = list_from->getItem(from_i).count;
|
||||
if(count != 0 && count < take_count)
|
||||
take_count = count;
|
||||
int src_can_take_count = take_count;
|
||||
|
||||
// Source is detached
|
||||
if(from_inv.type == InventoryLocation::DETACHED)
|
||||
{
|
||||
lua_State *L = player->getEnv()->getLua();
|
||||
ItemStack src_item = list_from->getItem(from_i);
|
||||
src_item.count = take_count;
|
||||
src_can_take_count = scriptapi_detached_inventory_allow_take(
|
||||
L, from_inv.name, from_list, from_i, src_item, player);
|
||||
}
|
||||
|
||||
// Source is nodemeta
|
||||
if(from_inv.type == InventoryLocation::NODEMETA)
|
||||
{
|
||||
lua_State *L = player->getEnv()->getLua();
|
||||
int count0 = count;
|
||||
if(count0 == 0)
|
||||
count0 = list_from->getItem(from_i).count;
|
||||
infostream<<player->getDescription()<<" dropping "<<count0
|
||||
<<" items from node at "<<PP(from_inv.p)<<std::endl;
|
||||
ItemStack return_stack = scriptapi_node_on_metadata_inventory_take(
|
||||
L, from_inv.p, from_list, from_i, count0, player);
|
||||
if(return_stack.count == 0)
|
||||
infostream<<"Node metadata gave no items"<<std::endl;
|
||||
item1 = return_stack;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Take item from source list
|
||||
if(count == 0)
|
||||
item1 = list_from->changeItem(from_i, ItemStack());
|
||||
else
|
||||
item1 = list_from->takeItem(from_i, count);
|
||||
ItemStack src_item = list_from->getItem(from_i);
|
||||
src_item.count = take_count;
|
||||
src_can_take_count = scriptapi_nodemeta_inventory_allow_take(
|
||||
L, from_inv.p, from_list, from_i, src_item, player);
|
||||
}
|
||||
|
||||
// Drop the item and apply the returned ItemStack
|
||||
ItemStack item2 = item1;
|
||||
if(scriptapi_item_on_drop(player->getEnv()->getLua(), item2, player,
|
||||
if(src_can_take_count != -1 && src_can_take_count < take_count)
|
||||
take_count = src_can_take_count;
|
||||
|
||||
int actually_dropped_count = 0;
|
||||
|
||||
ItemStack src_item = list_from->getItem(from_i);
|
||||
|
||||
// Drop the item
|
||||
ItemStack item1 = list_from->getItem(from_i);
|
||||
if(scriptapi_item_on_drop(player->getEnv()->getLua(), item1, player,
|
||||
player->getBasePosition() + v3f(0,1,0)))
|
||||
{
|
||||
if(g_settings->getBool("creative_mode") == true
|
||||
&& from_inv.type == InventoryLocation::PLAYER)
|
||||
item2 = item1; // creative mode
|
||||
actually_dropped_count = take_count - item1.count;
|
||||
|
||||
list_from->addItem(from_i, item2);
|
||||
if(actually_dropped_count == 0){
|
||||
infostream<<"Actually dropped no items"<<std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
// If source isn't infinite
|
||||
if(src_can_take_count != -1){
|
||||
// Take item from source list
|
||||
ItemStack item2 = list_from->takeItem(from_i, actually_dropped_count);
|
||||
|
||||
if(item2.count != actually_dropped_count)
|
||||
errorstream<<"Could not take dropped count of items"<<std::endl;
|
||||
|
||||
// Unless we have put the same amount back as we took in the first place,
|
||||
// set inventory modified flag
|
||||
if(item2.count != item1.count)
|
||||
mgr->setInventoryModified(from_inv);
|
||||
}
|
||||
}
|
||||
|
||||
infostream<<"IDropAction::apply(): dropped "
|
||||
@@ -409,6 +602,50 @@ void IDropAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame
|
||||
<<" list=\""<<from_list<<"\""
|
||||
<<" i="<<from_i
|
||||
<<std::endl;
|
||||
|
||||
src_item.count = actually_dropped_count;
|
||||
|
||||
/*
|
||||
Report drop to endpoints
|
||||
*/
|
||||
|
||||
// Source is detached
|
||||
if(from_inv.type == InventoryLocation::DETACHED)
|
||||
{
|
||||
lua_State *L = player->getEnv()->getLua();
|
||||
scriptapi_detached_inventory_on_take(
|
||||
L, from_inv.name, from_list, from_i, src_item, player);
|
||||
}
|
||||
|
||||
// Source is nodemeta
|
||||
if(from_inv.type == InventoryLocation::NODEMETA)
|
||||
{
|
||||
lua_State *L = player->getEnv()->getLua();
|
||||
scriptapi_nodemeta_inventory_on_take(
|
||||
L, from_inv.p, from_list, from_i, src_item, player);
|
||||
}
|
||||
|
||||
/*
|
||||
Record rollback information
|
||||
*/
|
||||
if(!ignore_src_rollback && gamedef->rollback())
|
||||
{
|
||||
IRollbackReportSink *rollback = gamedef->rollback();
|
||||
|
||||
// If source is not infinite, record item take
|
||||
if(!src_can_take_count != -1){
|
||||
RollbackAction action;
|
||||
std::string loc;
|
||||
{
|
||||
std::ostringstream os(std::ios::binary);
|
||||
from_inv.serialize(os);
|
||||
loc = os.str();
|
||||
}
|
||||
action.setModifyInventoryStack(loc, from_list, from_i,
|
||||
false, src_item.getItemString());
|
||||
rollback->reportAction(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IDropAction::clientApply(InventoryManager *mgr, IGameDef *gamedef)
|
||||
|
||||
@@ -32,9 +32,10 @@ struct InventoryLocation
|
||||
CURRENT_PLAYER,
|
||||
PLAYER,
|
||||
NODEMETA,
|
||||
DETACHED,
|
||||
} type;
|
||||
|
||||
std::string name; // PLAYER
|
||||
std::string name; // PLAYER, DETACHED
|
||||
v3s16 p; // NODEMETA
|
||||
|
||||
InventoryLocation()
|
||||
@@ -59,6 +60,11 @@ struct InventoryLocation
|
||||
type = NODEMETA;
|
||||
p = p_;
|
||||
}
|
||||
void setDetached(const std::string &name_)
|
||||
{
|
||||
type = DETACHED;
|
||||
name = name_;
|
||||
}
|
||||
|
||||
void applyCurrentPlayer(const std::string &name_)
|
||||
{
|
||||
@@ -80,13 +86,11 @@ class InventoryManager
|
||||
InventoryManager(){}
|
||||
virtual ~InventoryManager(){}
|
||||
|
||||
// Get an inventory or set it modified (so it will be updated over
|
||||
// network or so)
|
||||
// Get an inventory (server and client)
|
||||
virtual Inventory* getInventory(const InventoryLocation &loc){return NULL;}
|
||||
virtual std::string getInventoryOwner(const InventoryLocation &loc){return "";}
|
||||
// Set modified (will be saved and sent over network; only on server)
|
||||
virtual void setInventoryModified(const InventoryLocation &loc){}
|
||||
|
||||
// Used on the client to send an action to the server
|
||||
// Send inventory action to server (only on client)
|
||||
virtual void inventoryAction(InventoryAction *a){}
|
||||
};
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ if(DEFAULT_DLOPEN)
|
||||
else()
|
||||
option(LUA_USE_DLOPEN "Enable dlopen support." OFF)
|
||||
endif()
|
||||
mark_as_advanced(LUA_USE_DLOPEN)
|
||||
|
||||
if(DEFAULT_POSIX)
|
||||
else()
|
||||
@@ -55,6 +56,7 @@ if(DEFAULT_ANSI)
|
||||
else()
|
||||
option(LUA_ANSI "Disable non-ansi features." OFF)
|
||||
endif()
|
||||
mark_as_advanced(LUA_ANSI)
|
||||
|
||||
#
|
||||
# Lua version
|
||||
|
||||
@@ -878,7 +878,7 @@ int main(int argc, char *argv[])
|
||||
|
||||
// Initialize debug streams
|
||||
#define DEBUGFILE "debug.txt"
|
||||
#ifdef RUN_IN_PLACE
|
||||
#if RUN_IN_PLACE
|
||||
std::string logfile = DEBUGFILE;
|
||||
#else
|
||||
std::string logfile = porting::path_user+DIR_DELIM+DEBUGFILE;
|
||||
@@ -962,7 +962,7 @@ int main(int argc, char *argv[])
|
||||
// Legacy configuration file location
|
||||
filenames.push_back(porting::path_user +
|
||||
DIR_DELIM + ".." + DIR_DELIM + "minetest.conf");
|
||||
#ifdef RUN_IN_PLACE
|
||||
#if RUN_IN_PLACE
|
||||
// Try also from a lower level (to aid having the same configuration
|
||||
// for many RUN_IN_PLACE installs)
|
||||
filenames.push_back(porting::path_user +
|
||||
|
||||
147
src/map.cpp
147
src/map.cpp
@@ -32,6 +32,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
#include "nodedef.h"
|
||||
#include "gamedef.h"
|
||||
#include "util/directiontables.h"
|
||||
#include "rollback_interface.h"
|
||||
|
||||
#define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")"
|
||||
|
||||
@@ -932,12 +933,12 @@ void Map::updateLighting(core::map<v3s16, MapBlock*> & a_blocks,
|
||||
void Map::addNodeAndUpdate(v3s16 p, MapNode n,
|
||||
core::map<v3s16, MapBlock*> &modified_blocks)
|
||||
{
|
||||
INodeDefManager *nodemgr = m_gamedef->ndef();
|
||||
INodeDefManager *ndef = m_gamedef->ndef();
|
||||
|
||||
/*PrintInfo(m_dout);
|
||||
m_dout<<DTIME<<"Map::addNodeAndUpdate(): p=("
|
||||
<<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
|
||||
|
||||
|
||||
/*
|
||||
From this node to nodes underneath:
|
||||
If lighting is sunlight (1.0), unlight neighbours and
|
||||
@@ -950,6 +951,11 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n,
|
||||
|
||||
bool node_under_sunlight = true;
|
||||
core::map<v3s16, bool> light_sources;
|
||||
|
||||
/*
|
||||
Collect old node for rollback
|
||||
*/
|
||||
RollbackNode rollback_oldnode(this, p, m_gamedef);
|
||||
|
||||
/*
|
||||
If there is a node at top and it doesn't have sunlight,
|
||||
@@ -960,7 +966,7 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n,
|
||||
try{
|
||||
MapNode topnode = getNode(toppos);
|
||||
|
||||
if(topnode.getLight(LIGHTBANK_DAY, nodemgr) != LIGHT_SUN)
|
||||
if(topnode.getLight(LIGHTBANK_DAY, ndef) != LIGHT_SUN)
|
||||
node_under_sunlight = false;
|
||||
}
|
||||
catch(InvalidPositionException &e)
|
||||
@@ -980,7 +986,7 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n,
|
||||
{
|
||||
enum LightBank bank = banks[i];
|
||||
|
||||
u8 lightwas = getNode(p).getLight(bank, nodemgr);
|
||||
u8 lightwas = getNode(p).getLight(bank, ndef);
|
||||
|
||||
// Add the block of the added node to modified_blocks
|
||||
v3s16 blockpos = getNodeBlockPos(p);
|
||||
@@ -997,16 +1003,16 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n,
|
||||
// light again into this.
|
||||
unLightNeighbors(bank, p, lightwas, light_sources, modified_blocks);
|
||||
|
||||
n.setLight(bank, 0, nodemgr);
|
||||
n.setLight(bank, 0, ndef);
|
||||
}
|
||||
|
||||
/*
|
||||
If node lets sunlight through and is under sunlight, it has
|
||||
sunlight too.
|
||||
*/
|
||||
if(node_under_sunlight && nodemgr->get(n).sunlight_propagates)
|
||||
if(node_under_sunlight && ndef->get(n).sunlight_propagates)
|
||||
{
|
||||
n.setLight(LIGHTBANK_DAY, LIGHT_SUN, nodemgr);
|
||||
n.setLight(LIGHTBANK_DAY, LIGHT_SUN, ndef);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1028,7 +1034,7 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n,
|
||||
TODO: This could be optimized by mass-unlighting instead
|
||||
of looping
|
||||
*/
|
||||
if(node_under_sunlight && !nodemgr->get(n).sunlight_propagates)
|
||||
if(node_under_sunlight && !ndef->get(n).sunlight_propagates)
|
||||
{
|
||||
s16 y = p.Y - 1;
|
||||
for(;; y--){
|
||||
@@ -1044,12 +1050,12 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n,
|
||||
break;
|
||||
}
|
||||
|
||||
if(n2.getLight(LIGHTBANK_DAY, nodemgr) == LIGHT_SUN)
|
||||
if(n2.getLight(LIGHTBANK_DAY, ndef) == LIGHT_SUN)
|
||||
{
|
||||
unLightNeighbors(LIGHTBANK_DAY,
|
||||
n2pos, n2.getLight(LIGHTBANK_DAY, nodemgr),
|
||||
n2pos, n2.getLight(LIGHTBANK_DAY, ndef),
|
||||
light_sources, modified_blocks);
|
||||
n2.setLight(LIGHTBANK_DAY, 0, nodemgr);
|
||||
n2.setLight(LIGHTBANK_DAY, 0, ndef);
|
||||
setNode(n2pos, n2);
|
||||
}
|
||||
else
|
||||
@@ -1078,6 +1084,17 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n,
|
||||
block->expireDayNightDiff();
|
||||
}
|
||||
|
||||
/*
|
||||
Report for rollback
|
||||
*/
|
||||
if(m_gamedef->rollback())
|
||||
{
|
||||
RollbackNode rollback_newnode(this, p, m_gamedef);
|
||||
RollbackAction action;
|
||||
action.setSetNode(p, rollback_oldnode, rollback_newnode);
|
||||
m_gamedef->rollback()->reportAction(action);
|
||||
}
|
||||
|
||||
/*
|
||||
Add neighboring liquid nodes and the node itself if it is
|
||||
liquid (=water node was added) to transform queue.
|
||||
@@ -1099,7 +1116,7 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n,
|
||||
v3s16 p2 = p + dirs[i];
|
||||
|
||||
MapNode n2 = getNode(p2);
|
||||
if(nodemgr->get(n2).isLiquid() || n2.getContent() == CONTENT_AIR)
|
||||
if(ndef->get(n2).isLiquid() || n2.getContent() == CONTENT_AIR)
|
||||
{
|
||||
m_transforming_liquid.push_back(p2);
|
||||
}
|
||||
@@ -1115,7 +1132,7 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n,
|
||||
void Map::removeNodeAndUpdate(v3s16 p,
|
||||
core::map<v3s16, MapBlock*> &modified_blocks)
|
||||
{
|
||||
INodeDefManager *nodemgr = m_gamedef->ndef();
|
||||
INodeDefManager *ndef = m_gamedef->ndef();
|
||||
|
||||
/*PrintInfo(m_dout);
|
||||
m_dout<<DTIME<<"Map::removeNodeAndUpdate(): p=("
|
||||
@@ -1128,6 +1145,11 @@ void Map::removeNodeAndUpdate(v3s16 p,
|
||||
// Node will be replaced with this
|
||||
content_t replace_material = CONTENT_AIR;
|
||||
|
||||
/*
|
||||
Collect old node for rollback
|
||||
*/
|
||||
RollbackNode rollback_oldnode(this, p, m_gamedef);
|
||||
|
||||
/*
|
||||
If there is a node at top and it doesn't have sunlight,
|
||||
there will be no sunlight going down.
|
||||
@@ -1135,7 +1157,7 @@ void Map::removeNodeAndUpdate(v3s16 p,
|
||||
try{
|
||||
MapNode topnode = getNode(toppos);
|
||||
|
||||
if(topnode.getLight(LIGHTBANK_DAY, nodemgr) != LIGHT_SUN)
|
||||
if(topnode.getLight(LIGHTBANK_DAY, ndef) != LIGHT_SUN)
|
||||
node_under_sunlight = false;
|
||||
}
|
||||
catch(InvalidPositionException &e)
|
||||
@@ -1157,7 +1179,7 @@ void Map::removeNodeAndUpdate(v3s16 p,
|
||||
Unlight neighbors (in case the node is a light source)
|
||||
*/
|
||||
unLightNeighbors(bank, p,
|
||||
getNode(p).getLight(bank, nodemgr),
|
||||
getNode(p).getLight(bank, ndef),
|
||||
light_sources, modified_blocks);
|
||||
}
|
||||
|
||||
@@ -1219,7 +1241,7 @@ void Map::removeNodeAndUpdate(v3s16 p,
|
||||
// TODO: Is this needed? Lighting is cleared up there already.
|
||||
try{
|
||||
MapNode n = getNode(p);
|
||||
n.setLight(LIGHTBANK_DAY, 0, nodemgr);
|
||||
n.setLight(LIGHTBANK_DAY, 0, ndef);
|
||||
setNode(p, n);
|
||||
}
|
||||
catch(InvalidPositionException &e)
|
||||
@@ -1254,6 +1276,17 @@ void Map::removeNodeAndUpdate(v3s16 p,
|
||||
block->expireDayNightDiff();
|
||||
}
|
||||
|
||||
/*
|
||||
Report for rollback
|
||||
*/
|
||||
if(m_gamedef->rollback())
|
||||
{
|
||||
RollbackNode rollback_newnode(this, p, m_gamedef);
|
||||
RollbackAction action;
|
||||
action.setSetNode(p, rollback_oldnode, rollback_newnode);
|
||||
m_gamedef->rollback()->reportAction(action);
|
||||
}
|
||||
|
||||
/*
|
||||
Add neighboring liquid nodes and this node to transform queue.
|
||||
(it's vital for the node itself to get updated last.)
|
||||
@@ -1275,7 +1308,7 @@ void Map::removeNodeAndUpdate(v3s16 p,
|
||||
v3s16 p2 = p + dirs[i];
|
||||
|
||||
MapNode n2 = getNode(p2);
|
||||
if(nodemgr->get(n2).isLiquid() || n2.getContent() == CONTENT_AIR)
|
||||
if(ndef->get(n2).isLiquid() || n2.getContent() == CONTENT_AIR)
|
||||
{
|
||||
m_transforming_liquid.push_back(p2);
|
||||
}
|
||||
@@ -1603,7 +1636,7 @@ void Map::transformLiquids(core::map<v3s16, MapBlock*> & modified_blocks)
|
||||
while(m_transforming_liquid.size() != 0)
|
||||
{
|
||||
// This should be done here so that it is done when continue is used
|
||||
if(loopcount >= initial_size * 3)
|
||||
if(loopcount >= initial_size || loopcount >= 10000)
|
||||
break;
|
||||
loopcount++;
|
||||
|
||||
@@ -1791,7 +1824,30 @@ void Map::transformLiquids(core::map<v3s16, MapBlock*> & modified_blocks)
|
||||
n0.param2 = ~(LIQUID_LEVEL_MASK | LIQUID_FLOW_DOWN_MASK);
|
||||
}
|
||||
n0.setContent(new_node_content);
|
||||
setNode(p0, n0);
|
||||
|
||||
// Find out whether there is a suspect for this action
|
||||
std::string suspect;
|
||||
if(m_gamedef->rollback()){
|
||||
suspect = m_gamedef->rollback()->getSuspect(p0, 83, 1);
|
||||
}
|
||||
|
||||
if(!suspect.empty()){
|
||||
// Blame suspect
|
||||
RollbackScopeActor rollback_scope(m_gamedef->rollback(), suspect, true);
|
||||
// Get old node for rollback
|
||||
RollbackNode rollback_oldnode(this, p0, m_gamedef);
|
||||
// Set node
|
||||
setNode(p0, n0);
|
||||
// Report
|
||||
RollbackNode rollback_newnode(this, p0, m_gamedef);
|
||||
RollbackAction action;
|
||||
action.setSetNode(p0, rollback_oldnode, rollback_newnode);
|
||||
m_gamedef->rollback()->reportAction(action);
|
||||
} else {
|
||||
// Set node
|
||||
setNode(p0, n0);
|
||||
}
|
||||
|
||||
v3s16 blockpos = getNodeBlockPos(p0);
|
||||
MapBlock *block = getBlockNoCreateNoEx(blockpos);
|
||||
if(block != NULL) {
|
||||
@@ -1881,6 +1937,59 @@ void Map::removeNodeMetadata(v3s16 p)
|
||||
block->m_node_metadata.remove(p_rel);
|
||||
}
|
||||
|
||||
NodeTimer Map::getNodeTimer(v3s16 p)
|
||||
{
|
||||
v3s16 blockpos = getNodeBlockPos(p);
|
||||
v3s16 p_rel = p - blockpos*MAP_BLOCKSIZE;
|
||||
MapBlock *block = getBlockNoCreateNoEx(blockpos);
|
||||
if(!block){
|
||||
infostream<<"Map::getNodeTimer(): Need to emerge "
|
||||
<<PP(blockpos)<<std::endl;
|
||||
block = emergeBlock(blockpos, false);
|
||||
}
|
||||
if(!block)
|
||||
{
|
||||
infostream<<"WARNING: Map::getNodeTimer(): Block not found"
|
||||
<<std::endl;
|
||||
return NodeTimer();
|
||||
}
|
||||
NodeTimer t = block->m_node_timers.get(p_rel);
|
||||
return t;
|
||||
}
|
||||
|
||||
void Map::setNodeTimer(v3s16 p, NodeTimer t)
|
||||
{
|
||||
v3s16 blockpos = getNodeBlockPos(p);
|
||||
v3s16 p_rel = p - blockpos*MAP_BLOCKSIZE;
|
||||
MapBlock *block = getBlockNoCreateNoEx(blockpos);
|
||||
if(!block){
|
||||
infostream<<"Map::setNodeTimer(): Need to emerge "
|
||||
<<PP(blockpos)<<std::endl;
|
||||
block = emergeBlock(blockpos, false);
|
||||
}
|
||||
if(!block)
|
||||
{
|
||||
infostream<<"WARNING: Map::setNodeTimer(): Block not found"
|
||||
<<std::endl;
|
||||
return;
|
||||
}
|
||||
block->m_node_timers.set(p_rel, t);
|
||||
}
|
||||
|
||||
void Map::removeNodeTimer(v3s16 p)
|
||||
{
|
||||
v3s16 blockpos = getNodeBlockPos(p);
|
||||
v3s16 p_rel = p - blockpos*MAP_BLOCKSIZE;
|
||||
MapBlock *block = getBlockNoCreateNoEx(blockpos);
|
||||
if(block == NULL)
|
||||
{
|
||||
infostream<<"WARNING: Map::removeNodeTimer(): Block not found"
|
||||
<<std::endl;
|
||||
return;
|
||||
}
|
||||
block->m_node_timers.remove(p_rel);
|
||||
}
|
||||
|
||||
/*
|
||||
ServerMap
|
||||
*/
|
||||
|
||||
15
src/map.h
15
src/map.h
@@ -32,6 +32,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
#include "voxel.h"
|
||||
#include "modifiedstate.h"
|
||||
#include "util/container.h"
|
||||
#include "nodetimer.h"
|
||||
|
||||
extern "C" {
|
||||
#include "sqlite3.h"
|
||||
@@ -43,6 +44,7 @@ class ServerMapSector;
|
||||
class MapBlock;
|
||||
class NodeMetadata;
|
||||
class IGameDef;
|
||||
class IRollbackReportSink;
|
||||
|
||||
namespace mapgen{
|
||||
struct BlockMakeData;
|
||||
@@ -168,7 +170,7 @@ class Map /*: public NodeContainer*/
|
||||
void removeEventReceiver(MapEventReceiver *event_receiver);
|
||||
// event shall be deleted by caller after the call.
|
||||
void dispatchEvent(MapEditEvent *event);
|
||||
|
||||
|
||||
// On failure returns NULL
|
||||
MapSector * getSectorNoGenerateNoExNoLock(v2s16 p2d);
|
||||
// Same as the above (there exists no lock anymore)
|
||||
@@ -310,6 +312,15 @@ class Map /*: public NodeContainer*/
|
||||
void setNodeMetadata(v3s16 p, NodeMetadata *meta);
|
||||
void removeNodeMetadata(v3s16 p);
|
||||
|
||||
/*
|
||||
Node Timers
|
||||
These are basically coordinate wrappers to MapBlock
|
||||
*/
|
||||
|
||||
NodeTimer getNodeTimer(v3s16 p);
|
||||
void setNodeTimer(v3s16 p, NodeTimer t);
|
||||
void removeNodeTimer(v3s16 p);
|
||||
|
||||
/*
|
||||
Misc.
|
||||
*/
|
||||
@@ -326,7 +337,7 @@ class Map /*: public NodeContainer*/
|
||||
IGameDef *m_gamedef;
|
||||
|
||||
core::map<MapEventReceiver*, bool> m_event_receivers;
|
||||
|
||||
|
||||
core::map<v2s16, MapSector*> m_sectors;
|
||||
|
||||
// Be sure to set this to NULL when the cached sector is deleted
|
||||
|
||||
273
src/mapblock.cpp
273
src/mapblock.cpp
@@ -552,12 +552,12 @@ void MapBlock::serialize(std::ostream &os, u8 version, bool disk)
|
||||
throw SerializationError("ERROR: Not writing dummy block.");
|
||||
}
|
||||
|
||||
if(version <= 21)
|
||||
{
|
||||
serialize_pre22(os, version, disk);
|
||||
return;
|
||||
}
|
||||
|
||||
// Can't do this anymore; we have 16-bit dynamically allocated node IDs
|
||||
// in memory; conversion just won't work in this direction.
|
||||
if(version < 24)
|
||||
throw SerializationError("MapBlock::serialize: serialization to "
|
||||
"version < 24 not possible");
|
||||
|
||||
// First byte
|
||||
u8 flags = 0;
|
||||
if(is_underground)
|
||||
@@ -582,8 +582,7 @@ void MapBlock::serialize(std::ostream &os, u8 version, bool disk)
|
||||
tmp_nodes[i] = data[i];
|
||||
getBlockNodeIdMapping(&nimap, tmp_nodes, m_gamedef->ndef());
|
||||
|
||||
u8 content_width = 1;
|
||||
/*u8 content_width = (nimap.size() <= 255) ? 1 : 2;*/
|
||||
u8 content_width = 2;
|
||||
u8 params_width = 2;
|
||||
writeU8(os, content_width);
|
||||
writeU8(os, params_width);
|
||||
@@ -593,8 +592,7 @@ void MapBlock::serialize(std::ostream &os, u8 version, bool disk)
|
||||
}
|
||||
else
|
||||
{
|
||||
u8 content_width = 1;
|
||||
/*u8 content_width = 2;*/
|
||||
u8 content_width = 2;
|
||||
u8 params_width = 2;
|
||||
writeU8(os, content_width);
|
||||
writeU8(os, params_width);
|
||||
@@ -606,10 +604,7 @@ void MapBlock::serialize(std::ostream &os, u8 version, bool disk)
|
||||
Node metadata
|
||||
*/
|
||||
std::ostringstream oss(std::ios_base::binary);
|
||||
if(version >= 23)
|
||||
m_node_metadata.serialize(oss);
|
||||
else
|
||||
content_nodemeta_serialize_legacy(oss, &m_node_metadata);
|
||||
m_node_metadata.serialize(oss);
|
||||
compressZlib(oss.str(), os);
|
||||
|
||||
/*
|
||||
@@ -617,13 +612,10 @@ void MapBlock::serialize(std::ostream &os, u8 version, bool disk)
|
||||
*/
|
||||
if(disk)
|
||||
{
|
||||
// Version 23 doesn't actually contain node timers
|
||||
// (this field should have not been added)
|
||||
if(version == 23)
|
||||
writeU8(os, 0);
|
||||
// Node timers (uncomment when node timers are taken into use)
|
||||
/*if(version >= 24)
|
||||
m_node_timers.serialize(os);*/
|
||||
if(version <= 24){
|
||||
// Node timers
|
||||
m_node_timers.serialize(os, version);
|
||||
}
|
||||
|
||||
// Static objects
|
||||
m_static_objects.serialize(os);
|
||||
@@ -633,10 +625,14 @@ void MapBlock::serialize(std::ostream &os, u8 version, bool disk)
|
||||
|
||||
// Write block-specific node definition id mapping
|
||||
nimap.serialize(os);
|
||||
|
||||
if(version >= 25){
|
||||
// Node timers
|
||||
m_node_timers.serialize(os, version);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void MapBlock::deSerialize(std::istream &is, u8 version, bool disk)
|
||||
{
|
||||
if(!ser_ver_supported(version))
|
||||
@@ -666,7 +662,7 @@ void MapBlock::deSerialize(std::istream &is, u8 version, bool disk)
|
||||
u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
|
||||
u8 content_width = readU8(is);
|
||||
u8 params_width = readU8(is);
|
||||
if(content_width != 1)
|
||||
if(content_width != 1 && content_width != 2)
|
||||
throw SerializationError("MapBlock::deSerialize(): invalid content_width");
|
||||
if(params_width != 2)
|
||||
throw SerializationError("MapBlock::deSerialize(): invalid params_width");
|
||||
@@ -703,15 +699,15 @@ void MapBlock::deSerialize(std::istream &is, u8 version, bool disk)
|
||||
if(disk)
|
||||
{
|
||||
// Node timers
|
||||
if(version == 23)
|
||||
if(version == 23){
|
||||
// Read unused zero
|
||||
readU8(is);
|
||||
// Uncomment when node timers are taken into use
|
||||
/*else if(version >= 24){
|
||||
}
|
||||
if(version == 24){
|
||||
TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
|
||||
<<": Node timers"<<std::endl);
|
||||
m_node_timers.deSerialize(is);
|
||||
}*/
|
||||
<<": Node timers (ver==24)"<<std::endl);
|
||||
m_node_timers.deSerialize(is, version);
|
||||
}
|
||||
|
||||
// Static objects
|
||||
TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
|
||||
@@ -730,6 +726,12 @@ void MapBlock::deSerialize(std::istream &is, u8 version, bool disk)
|
||||
NameIdMapping nimap;
|
||||
nimap.deSerialize(is);
|
||||
correctBlockNodeIds(&nimap, data, m_gamedef);
|
||||
|
||||
if(version >= 25){
|
||||
TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
|
||||
<<": Node timers (ver>=25)"<<std::endl);
|
||||
m_node_timers.deSerialize(is, version);
|
||||
}
|
||||
}
|
||||
|
||||
TRACESTREAM(<<"MapBlock::deSerialize "<<PP(getPos())
|
||||
@@ -740,219 +742,6 @@ void MapBlock::deSerialize(std::istream &is, u8 version, bool disk)
|
||||
Legacy serialization
|
||||
*/
|
||||
|
||||
// List relevant id-name pairs for ids in the block using nodedef
|
||||
// Before serialization version 22
|
||||
static void getBlockNodeIdMapping_pre22(NameIdMapping *nimap, MapNode *nodes,
|
||||
INodeDefManager *nodedef)
|
||||
{
|
||||
std::set<content_t> unknown_contents;
|
||||
for(u32 i=0; i<MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE; i++)
|
||||
{
|
||||
content_t id = nodes[i].getContent();
|
||||
const ContentFeatures &f = nodedef->get(id);
|
||||
const std::string &name = f.name;
|
||||
if(name == "")
|
||||
unknown_contents.insert(id);
|
||||
else
|
||||
nimap->set(id, name);
|
||||
}
|
||||
for(std::set<content_t>::const_iterator
|
||||
i = unknown_contents.begin();
|
||||
i != unknown_contents.end(); i++){
|
||||
errorstream<<"getBlockNodeIdMapping_pre22(): IGNORING ERROR: "
|
||||
<<"Name for node id "<<(*i)<<" not known"<<std::endl;
|
||||
}
|
||||
}
|
||||
void MapBlock::serialize_pre22(std::ostream &os, u8 version, bool disk)
|
||||
{
|
||||
u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
|
||||
|
||||
MapNode *tmp_data = new MapNode[nodecount];
|
||||
|
||||
// Legacy data changes
|
||||
// This code has to change from post-22 to pre-22 format.
|
||||
INodeDefManager *nodedef = m_gamedef->ndef();
|
||||
for(u32 i=0; i<nodecount; i++)
|
||||
{
|
||||
const ContentFeatures &f = nodedef->get(tmp_data[i].getContent());
|
||||
// Mineral
|
||||
if(nodedef->getId("default:stone_with_coal") == tmp_data[i].getContent())
|
||||
{
|
||||
tmp_data[i].setContent(nodedef->getId("default:stone"));
|
||||
tmp_data[i].setParam1(1); // MINERAL_COAL
|
||||
}
|
||||
else if(nodedef->getId("default:stone_with_iron") == tmp_data[i].getContent())
|
||||
{
|
||||
tmp_data[i].setContent(nodedef->getId("default:stone"));
|
||||
tmp_data[i].setParam1(2); // MINERAL_IRON
|
||||
}
|
||||
// facedir_simple
|
||||
if(f.legacy_facedir_simple)
|
||||
{
|
||||
tmp_data[i].setParam1(tmp_data[i].getParam2());
|
||||
tmp_data[i].setParam2(0);
|
||||
}
|
||||
// wall_mounted
|
||||
if(f.legacy_wallmounted)
|
||||
{
|
||||
u8 wallmounted_new_to_old[8] = {0x04, 0x08, 0x01, 0x02, 0x10, 0x20, 0, 0};
|
||||
u8 dir_new_format = tmp_data[i].getParam2() & 7; // lowest 3 bits
|
||||
u8 dir_old_format = wallmounted_new_to_old[dir_new_format];
|
||||
tmp_data[i].setParam2(dir_old_format);
|
||||
}
|
||||
}
|
||||
|
||||
// Serialize nodes
|
||||
u32 ser_length = MapNode::serializedLength(version);
|
||||
SharedBuffer<u8> databuf_nodelist(nodecount * ser_length);
|
||||
for(u32 i=0; i<nodecount; i++)
|
||||
{
|
||||
tmp_data[i].serialize(&databuf_nodelist[i*ser_length], version);
|
||||
}
|
||||
|
||||
delete[] tmp_data;
|
||||
|
||||
// These have no compression
|
||||
if(version <= 3 || version == 5 || version == 6)
|
||||
{
|
||||
writeU8(os, is_underground);
|
||||
os.write((char*)*databuf_nodelist, databuf_nodelist.getSize());
|
||||
}
|
||||
else if(version <= 10)
|
||||
{
|
||||
/*
|
||||
With compression.
|
||||
Compress the materials and the params separately.
|
||||
*/
|
||||
|
||||
// First byte
|
||||
writeU8(os, is_underground);
|
||||
|
||||
// Get and compress materials
|
||||
SharedBuffer<u8> materialdata(nodecount);
|
||||
for(u32 i=0; i<nodecount; i++)
|
||||
{
|
||||
materialdata[i] = databuf_nodelist[i*ser_length];
|
||||
}
|
||||
compress(materialdata, os, version);
|
||||
|
||||
// Get and compress lights
|
||||
SharedBuffer<u8> lightdata(nodecount);
|
||||
for(u32 i=0; i<nodecount; i++)
|
||||
{
|
||||
lightdata[i] = databuf_nodelist[i*ser_length+1];
|
||||
}
|
||||
compress(lightdata, os, version);
|
||||
|
||||
if(version >= 10)
|
||||
{
|
||||
// Get and compress param2
|
||||
SharedBuffer<u8> param2data(nodecount);
|
||||
for(u32 i=0; i<nodecount; i++)
|
||||
{
|
||||
param2data[i] = databuf_nodelist[i*ser_length+2];
|
||||
}
|
||||
compress(param2data, os, version);
|
||||
}
|
||||
}
|
||||
// All other versions (newest)
|
||||
else
|
||||
{
|
||||
// First byte
|
||||
u8 flags = 0;
|
||||
if(is_underground)
|
||||
flags |= 0x01;
|
||||
if(getDayNightDiff())
|
||||
flags |= 0x02;
|
||||
if(m_lighting_expired)
|
||||
flags |= 0x04;
|
||||
if(version >= 18)
|
||||
{
|
||||
if(m_generated == false)
|
||||
flags |= 0x08;
|
||||
}
|
||||
writeU8(os, flags);
|
||||
|
||||
/*
|
||||
Get data
|
||||
*/
|
||||
|
||||
// Create buffer with different parameters sorted
|
||||
SharedBuffer<u8> databuf(nodecount*3);
|
||||
for(u32 i=0; i<nodecount; i++)
|
||||
{
|
||||
databuf[i] = databuf_nodelist[i*ser_length];
|
||||
databuf[i+nodecount] = databuf_nodelist[i*ser_length+1];
|
||||
databuf[i+nodecount*2] = databuf_nodelist[i*ser_length+2];
|
||||
}
|
||||
|
||||
/*
|
||||
Compress data to output stream
|
||||
*/
|
||||
|
||||
compress(databuf, os, version);
|
||||
|
||||
/*
|
||||
NodeMetadata
|
||||
*/
|
||||
if(version >= 14)
|
||||
{
|
||||
if(version <= 15)
|
||||
{
|
||||
try{
|
||||
std::ostringstream oss(std::ios_base::binary);
|
||||
content_nodemeta_serialize_legacy(oss, &m_node_metadata);
|
||||
os<<serializeString(oss.str());
|
||||
}
|
||||
// This will happen if the string is longer than 65535
|
||||
catch(SerializationError &e)
|
||||
{
|
||||
// Use an empty string
|
||||
os<<serializeString("");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::ostringstream oss(std::ios_base::binary);
|
||||
content_nodemeta_serialize_legacy(oss, &m_node_metadata);
|
||||
compressZlib(oss.str(), os);
|
||||
//os<<serializeLongString(oss.str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(disk)
|
||||
{
|
||||
// Versions up from 9 have block objects. (DEPRECATED)
|
||||
if(version >= 9)
|
||||
{
|
||||
// count=0
|
||||
writeU16(os, 0);
|
||||
}
|
||||
|
||||
// Versions up from 15 have static objects.
|
||||
if(version >= 15)
|
||||
{
|
||||
m_static_objects.serialize(os);
|
||||
}
|
||||
|
||||
// Timestamp
|
||||
if(version >= 17)
|
||||
{
|
||||
writeU32(os, getTimestamp());
|
||||
}
|
||||
|
||||
// Scan and write node definition id mapping
|
||||
if(version >= 21)
|
||||
{
|
||||
NameIdMapping nimap;
|
||||
getBlockNodeIdMapping_pre22(&nimap, data, m_gamedef->ndef());
|
||||
nimap.serialize(os);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MapBlock::deSerialize_pre22(std::istream &is, u8 version, bool disk)
|
||||
{
|
||||
u32 nodecount = MAP_BLOCKSIZE*MAP_BLOCKSIZE*MAP_BLOCKSIZE;
|
||||
|
||||
@@ -430,6 +430,26 @@ class MapBlock /*: public NodeContainer*/
|
||||
{
|
||||
return m_usage_timer;
|
||||
}
|
||||
|
||||
/*
|
||||
Node Timers
|
||||
*/
|
||||
// Get timer
|
||||
NodeTimer getNodeTimer(v3s16 p){
|
||||
return m_node_timers.get(p);
|
||||
}
|
||||
// Deletes timer
|
||||
void removeNodeTimer(v3s16 p){
|
||||
m_node_timers.remove(p);
|
||||
}
|
||||
// Deletes old timer and sets a new one
|
||||
void setNodeTimer(v3s16 p, NodeTimer t){
|
||||
m_node_timers.set(p,t);
|
||||
}
|
||||
// Deletes all timers
|
||||
void clearNodeTimers(){
|
||||
m_node_timers.clear();
|
||||
}
|
||||
|
||||
/*
|
||||
Serialization
|
||||
@@ -447,7 +467,6 @@ class MapBlock /*: public NodeContainer*/
|
||||
Private methods
|
||||
*/
|
||||
|
||||
void serialize_pre22(std::ostream &os, u8 version, bool disk);
|
||||
void deSerialize_pre22(std::istream &is, u8 version, bool disk);
|
||||
|
||||
/*
|
||||
|
||||
131
src/mapnode.cpp
131
src/mapnode.cpp
@@ -39,11 +39,9 @@ MapNode::MapNode(INodeDefManager *ndef, const std::string &name,
|
||||
{
|
||||
content_t id = CONTENT_IGNORE;
|
||||
ndef->getId(name, id);
|
||||
param0 = id;
|
||||
param1 = a_param1;
|
||||
param2 = a_param2;
|
||||
// Set content (param0 and (param2&0xf0)) after other params
|
||||
// because this needs to override part of param2
|
||||
setContent(id);
|
||||
}
|
||||
|
||||
void MapNode::setLight(enum LightBank bank, u8 a_light, INodeDefManager *nodemgr)
|
||||
@@ -236,23 +234,25 @@ u32 MapNode::serializedLength(u8 version)
|
||||
return 1;
|
||||
else if(version <= 9)
|
||||
return 2;
|
||||
else
|
||||
else if(version <= 23)
|
||||
return 3;
|
||||
else
|
||||
return 4;
|
||||
}
|
||||
void MapNode::serialize(u8 *dest, u8 version)
|
||||
{
|
||||
if(!ser_ver_supported(version))
|
||||
throw VersionMismatchException("ERROR: MapNode format not supported");
|
||||
|
||||
if(version <= 21)
|
||||
{
|
||||
serialize_pre22(dest, version);
|
||||
return;
|
||||
}
|
||||
|
||||
writeU8(dest+0, param0);
|
||||
writeU8(dest+1, param1);
|
||||
writeU8(dest+2, param2);
|
||||
|
||||
// Can't do this anymore; we have 16-bit dynamically allocated node IDs
|
||||
// in memory; conversion just won't work in this direction.
|
||||
if(version < 24)
|
||||
throw SerializationError("MapNode::serialize: serialization to "
|
||||
"version < 24 not possible");
|
||||
|
||||
writeU16(dest+0, param0);
|
||||
writeU8(dest+2, param1);
|
||||
writeU8(dest+3, param2);
|
||||
}
|
||||
void MapNode::deSerialize(u8 *source, u8 version)
|
||||
{
|
||||
@@ -265,9 +265,20 @@ void MapNode::deSerialize(u8 *source, u8 version)
|
||||
return;
|
||||
}
|
||||
|
||||
param0 = readU8(source+0);
|
||||
param1 = readU8(source+1);
|
||||
param2 = readU8(source+2);
|
||||
if(version >= 24){
|
||||
param0 = readU16(source+0);
|
||||
param1 = readU8(source+2);
|
||||
param2 = readU8(source+3);
|
||||
}
|
||||
else{
|
||||
param0 = readU8(source+0);
|
||||
param1 = readU8(source+1);
|
||||
param2 = readU8(source+2);
|
||||
if(param0 > 0x7F){
|
||||
param0 |= ((param2&0xF0)<<4);
|
||||
param2 &= 0x0F;
|
||||
}
|
||||
}
|
||||
}
|
||||
void MapNode::serializeBulk(std::ostream &os, int version,
|
||||
const MapNode *nodes, u32 nodecount,
|
||||
@@ -276,24 +287,20 @@ void MapNode::serializeBulk(std::ostream &os, int version,
|
||||
if(!ser_ver_supported(version))
|
||||
throw VersionMismatchException("ERROR: MapNode format not supported");
|
||||
|
||||
assert(version >= 22);
|
||||
assert(content_width == 1);
|
||||
assert(content_width == 2);
|
||||
assert(params_width == 2);
|
||||
|
||||
// Can't do this anymore; we have 16-bit dynamically allocated node IDs
|
||||
// in memory; conversion just won't work in this direction.
|
||||
if(version < 24)
|
||||
throw SerializationError("MapNode::serializeBulk: serialization to "
|
||||
"version < 24 not possible");
|
||||
|
||||
SharedBuffer<u8> databuf(nodecount * (content_width + params_width));
|
||||
|
||||
// Serialize content
|
||||
if(content_width == 1)
|
||||
{
|
||||
for(u32 i=0; i<nodecount; i++)
|
||||
writeU8(&databuf[i], nodes[i].param0);
|
||||
}
|
||||
/* If param0 is extended to two bytes, use something like this: */
|
||||
/*else if(content_width == 2)
|
||||
{
|
||||
for(u32 i=0; i<nodecount; i++)
|
||||
writeU16(&databuf[i*2], nodes[i].param0);
|
||||
}*/
|
||||
for(u32 i=0; i<nodecount; i++)
|
||||
writeU16(&databuf[i*2], nodes[i].param0);
|
||||
|
||||
// Serialize param1
|
||||
u32 start1 = content_width * nodecount;
|
||||
@@ -328,7 +335,7 @@ void MapNode::deSerializeBulk(std::istream &is, int version,
|
||||
throw VersionMismatchException("ERROR: MapNode format not supported");
|
||||
|
||||
assert(version >= 22);
|
||||
assert(content_width == 1);
|
||||
assert(content_width == 1 || content_width == 2);
|
||||
assert(params_width == 2);
|
||||
|
||||
// Uncompress or read data
|
||||
@@ -358,12 +365,11 @@ void MapNode::deSerializeBulk(std::istream &is, int version,
|
||||
for(u32 i=0; i<nodecount; i++)
|
||||
nodes[i].param0 = readU8(&databuf[i]);
|
||||
}
|
||||
/* If param0 is extended to two bytes, use something like this: */
|
||||
/*else if(content_width == 2)
|
||||
else if(content_width == 2)
|
||||
{
|
||||
for(u32 i=0; i<nodecount; i++)
|
||||
nodes[i].param0 = readU16(&databuf[i*2]);
|
||||
}*/
|
||||
}
|
||||
|
||||
// Deserialize param1
|
||||
u32 start1 = content_width * nodecount;
|
||||
@@ -372,47 +378,27 @@ void MapNode::deSerializeBulk(std::istream &is, int version,
|
||||
|
||||
// Deserialize param2
|
||||
u32 start2 = (content_width + 1) * nodecount;
|
||||
for(u32 i=0; i<nodecount; i++)
|
||||
nodes[i].param2 = readU8(&databuf[start2 + i]);
|
||||
if(content_width == 1)
|
||||
{
|
||||
for(u32 i=0; i<nodecount; i++) {
|
||||
nodes[i].param2 = readU8(&databuf[start2 + i]);
|
||||
if(nodes[i].param0 > 0x7F){
|
||||
nodes[i].param0 <<= 4;
|
||||
nodes[i].param0 |= (nodes[i].param2&0xF0)>>4;
|
||||
nodes[i].param2 &= 0x0F;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(content_width == 2)
|
||||
{
|
||||
for(u32 i=0; i<nodecount; i++)
|
||||
nodes[i].param2 = readU8(&databuf[start2 + i]);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Legacy serialization
|
||||
*/
|
||||
void MapNode::serialize_pre22(u8 *dest, u8 version)
|
||||
{
|
||||
// Translate to wanted version
|
||||
MapNode n_foreign = mapnode_translate_from_internal(*this, version);
|
||||
|
||||
u8 actual_param0 = n_foreign.param0;
|
||||
|
||||
// Convert special values from new version to old
|
||||
if(version <= 18)
|
||||
{
|
||||
// In these versions, CONTENT_IGNORE and CONTENT_AIR
|
||||
// are 255 and 254
|
||||
if(actual_param0 == CONTENT_IGNORE)
|
||||
actual_param0 = 255;
|
||||
else if(actual_param0 == CONTENT_AIR)
|
||||
actual_param0 = 254;
|
||||
}
|
||||
|
||||
if(version == 0)
|
||||
{
|
||||
dest[0] = actual_param0;
|
||||
}
|
||||
else if(version <= 9)
|
||||
{
|
||||
dest[0] = actual_param0;
|
||||
dest[1] = n_foreign.param1;
|
||||
}
|
||||
else
|
||||
{
|
||||
dest[0] = actual_param0;
|
||||
dest[1] = n_foreign.param1;
|
||||
dest[2] = n_foreign.param2;
|
||||
}
|
||||
}
|
||||
void MapNode::deSerialize_pre22(u8 *source, u8 version)
|
||||
{
|
||||
if(version <= 1)
|
||||
@@ -429,6 +415,11 @@ void MapNode::deSerialize_pre22(u8 *source, u8 version)
|
||||
param0 = source[0];
|
||||
param1 = source[1];
|
||||
param2 = source[2];
|
||||
if(param0 > 0x7f){
|
||||
param0 <<= 4;
|
||||
param0 |= (param2&0xf0)>>4;
|
||||
param2 &= 0x0f;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert special values from old version to new
|
||||
|
||||
@@ -33,10 +33,6 @@ class INodeDefManager;
|
||||
- Material = irrlicht's Material class
|
||||
- Content = (content_t) content of a node
|
||||
- Tile = TileSpec at some side of a node of some content type
|
||||
|
||||
Content ranges:
|
||||
0x000...0x07f: param2 is fully usable
|
||||
0x800...0xfff: param2 lower 4 bits are free
|
||||
*/
|
||||
typedef u16 content_t;
|
||||
#define MAX_CONTENT 0xfff
|
||||
@@ -84,10 +80,8 @@ struct MapNode
|
||||
{
|
||||
/*
|
||||
Main content
|
||||
0x00-0x7f: Short content type
|
||||
0x80-0xff: Long content type (param2>>4 makes up low bytes)
|
||||
*/
|
||||
u8 param0;
|
||||
u16 param0;
|
||||
|
||||
/*
|
||||
Misc parameter. Initialized to 0.
|
||||
@@ -102,7 +96,6 @@ struct MapNode
|
||||
/*
|
||||
The second parameter. Initialized to 0.
|
||||
E.g. direction for torches and flowing water.
|
||||
If param0 >= 0x80, bits 0xf0 of this is extended content type data
|
||||
*/
|
||||
u8 param2;
|
||||
|
||||
@@ -113,11 +106,9 @@ struct MapNode
|
||||
|
||||
MapNode(content_t content=CONTENT_AIR, u8 a_param1=0, u8 a_param2=0)
|
||||
{
|
||||
param0 = content;
|
||||
param1 = a_param1;
|
||||
param2 = a_param2;
|
||||
// Set content (param0 and (param2&0xf0)) after other params
|
||||
// because this needs to override part of param2
|
||||
setContent(content);
|
||||
}
|
||||
|
||||
// Create directly from a nodename
|
||||
@@ -135,25 +126,11 @@ struct MapNode
|
||||
// To be used everywhere
|
||||
content_t getContent() const
|
||||
{
|
||||
if(param0 < 0x80)
|
||||
return param0;
|
||||
else
|
||||
return (param0<<4) + (param2>>4);
|
||||
return param0;
|
||||
}
|
||||
void setContent(content_t c)
|
||||
{
|
||||
if(c < 0x80)
|
||||
{
|
||||
if(param0 >= 0x80)
|
||||
param2 &= ~(0xf0);
|
||||
param0 = c;
|
||||
}
|
||||
else
|
||||
{
|
||||
param0 = c>>4;
|
||||
param2 &= ~(0xf0);
|
||||
param2 |= (c&0x0f)<<4;
|
||||
}
|
||||
param0 = c;
|
||||
}
|
||||
u8 getParam1() const
|
||||
{
|
||||
@@ -165,19 +142,11 @@ struct MapNode
|
||||
}
|
||||
u8 getParam2() const
|
||||
{
|
||||
if(param0 < 0x80)
|
||||
return param2;
|
||||
else
|
||||
return param2 & 0x0f;
|
||||
return param2;
|
||||
}
|
||||
void setParam2(u8 p)
|
||||
{
|
||||
if(param0 < 0x80)
|
||||
param2 = p;
|
||||
else{
|
||||
param2 &= 0xf0;
|
||||
param2 |= (p&0x0f);
|
||||
}
|
||||
param2 = p;
|
||||
}
|
||||
|
||||
void setLight(enum LightBank bank, u8 a_light, INodeDefManager *nodemgr);
|
||||
@@ -233,7 +202,6 @@ struct MapNode
|
||||
|
||||
private:
|
||||
// Deprecated serialization methods
|
||||
void serialize_pre22(u8 *dest, u8 version);
|
||||
void deSerialize_pre22(u8 *source, u8 version);
|
||||
};
|
||||
|
||||
|
||||
@@ -377,18 +377,9 @@ class CNodeDefManager: public IWritableNodeDefManager
|
||||
}
|
||||
}
|
||||
// CONTENT_IGNORE = not found
|
||||
content_t getFreeId(bool require_full_param2)
|
||||
content_t getFreeId()
|
||||
{
|
||||
// If allowed, first search in the large 4-bit-param2 pool
|
||||
if(!require_full_param2){
|
||||
for(u16 i=0x800; i<=0xfff; i++){
|
||||
const ContentFeatures &f = m_content_features[i];
|
||||
if(f.name == "")
|
||||
return i;
|
||||
}
|
||||
}
|
||||
// Then search from the small 8-bit-param2 pool
|
||||
for(u16 i=0; i<=125; i++){
|
||||
for(u32 i=0; i<=0xffff; i++){
|
||||
const ContentFeatures &f = m_content_features[i];
|
||||
if(f.name == "")
|
||||
return i;
|
||||
@@ -492,16 +483,8 @@ class CNodeDefManager: public IWritableNodeDefManager
|
||||
u16 id = CONTENT_IGNORE;
|
||||
bool found = m_name_id_mapping.getId(name, id); // ignore aliases
|
||||
if(!found){
|
||||
// Determine if full param2 is required
|
||||
bool require_full_param2 = (
|
||||
def.param_type_2 == CPT2_FULL
|
||||
||
|
||||
def.param_type_2 == CPT2_FLOWINGLIQUID
|
||||
||
|
||||
def.legacy_wallmounted
|
||||
);
|
||||
// Get some id
|
||||
id = getFreeId(require_full_param2);
|
||||
id = getFreeId();
|
||||
if(id == CONTENT_IGNORE)
|
||||
return CONTENT_IGNORE;
|
||||
if(name != "")
|
||||
|
||||
@@ -55,7 +55,7 @@ class NodeMetadata
|
||||
i = m_stringvars.find(name);
|
||||
if(i == m_stringvars.end())
|
||||
return "";
|
||||
return i->second;
|
||||
return resolveString(i->second);
|
||||
}
|
||||
void setString(const std::string &name, const std::string &var)
|
||||
{
|
||||
@@ -64,6 +64,13 @@ class NodeMetadata
|
||||
else
|
||||
m_stringvars[name] = var;
|
||||
}
|
||||
// support variable names in values
|
||||
std::string resolveString(const std::string &str) const
|
||||
{
|
||||
if(str.substr(0,2) == "${" && str[str.length()-1] == '}')
|
||||
return resolveString(getString(str.substr(2,str.length()-3)));
|
||||
return str;
|
||||
}
|
||||
std::map<std::string, std::string> getStrings() const
|
||||
{
|
||||
return m_stringvars;
|
||||
|
||||
@@ -28,13 +28,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
|
||||
void NodeTimer::serialize(std::ostream &os) const
|
||||
{
|
||||
writeF1000(os, duration);
|
||||
writeF1000(os, timeout);
|
||||
writeF1000(os, elapsed);
|
||||
}
|
||||
|
||||
void NodeTimer::deSerialize(std::istream &is)
|
||||
{
|
||||
duration = readF1000(is);
|
||||
timeout = readF1000(is);
|
||||
elapsed = readF1000(is);
|
||||
}
|
||||
|
||||
@@ -42,19 +42,22 @@ void NodeTimer::deSerialize(std::istream &is)
|
||||
NodeTimerList
|
||||
*/
|
||||
|
||||
void NodeTimerList::serialize(std::ostream &os) const
|
||||
void NodeTimerList::serialize(std::ostream &os, u8 map_format_version) const
|
||||
{
|
||||
/*
|
||||
Version 0 is a placeholder for "nothing to see here; go away."
|
||||
*/
|
||||
|
||||
if(m_data.size() == 0){
|
||||
writeU8(os, 0); // version
|
||||
return;
|
||||
if(map_format_version == 24){
|
||||
// Version 0 is a placeholder for "nothing to see here; go away."
|
||||
if(m_data.size() == 0){
|
||||
writeU8(os, 0); // version
|
||||
return;
|
||||
}
|
||||
writeU8(os, 1); // version
|
||||
writeU16(os, m_data.size());
|
||||
}
|
||||
|
||||
writeU8(os, 1); // version
|
||||
writeU16(os, m_data.size());
|
||||
if(map_format_version >= 25){
|
||||
writeU8(os, 2+4+4);
|
||||
writeU16(os, m_data.size());
|
||||
}
|
||||
|
||||
for(std::map<v3s16, NodeTimer>::const_iterator
|
||||
i = m_data.begin();
|
||||
@@ -68,15 +71,23 @@ void NodeTimerList::serialize(std::ostream &os) const
|
||||
}
|
||||
}
|
||||
|
||||
void NodeTimerList::deSerialize(std::istream &is)
|
||||
void NodeTimerList::deSerialize(std::istream &is, u8 map_format_version)
|
||||
{
|
||||
m_data.clear();
|
||||
|
||||
if(map_format_version == 24){
|
||||
u8 timer_version = readU8(is);
|
||||
if(timer_version == 0)
|
||||
return;
|
||||
if(timer_version != 1)
|
||||
throw SerializationError("unsupported NodeTimerList version");
|
||||
}
|
||||
|
||||
u8 version = readU8(is);
|
||||
if(version == 0)
|
||||
return;
|
||||
if(version != 1)
|
||||
throw SerializationError("unsupported NodeTimerList version");
|
||||
if(map_format_version >= 25){
|
||||
u8 timer_data_len = readU8(is);
|
||||
if(timer_data_len != 2+4+4)
|
||||
throw SerializationError("unsupported NodeTimer data length");
|
||||
}
|
||||
|
||||
u16 count = readU16(is);
|
||||
|
||||
@@ -94,7 +105,7 @@ void NodeTimerList::deSerialize(std::istream &is)
|
||||
NodeTimer t;
|
||||
t.deSerialize(is);
|
||||
|
||||
if(t.duration <= 0)
|
||||
if(t.timeout <= 0)
|
||||
{
|
||||
infostream<<"WARNING: NodeTimerList::deSerialize(): "
|
||||
<<"invalid data at position"
|
||||
@@ -116,9 +127,9 @@ void NodeTimerList::deSerialize(std::istream &is)
|
||||
}
|
||||
}
|
||||
|
||||
std::map<v3s16, f32> NodeTimerList::step(float dtime)
|
||||
std::map<v3s16, NodeTimer> NodeTimerList::step(float dtime)
|
||||
{
|
||||
std::map<v3s16, f32> elapsed_timers;
|
||||
std::map<v3s16, NodeTimer> elapsed_timers;
|
||||
// Increment timers
|
||||
for(std::map<v3s16, NodeTimer>::iterator
|
||||
i = m_data.begin();
|
||||
@@ -126,13 +137,13 @@ std::map<v3s16, f32> NodeTimerList::step(float dtime)
|
||||
v3s16 p = i->first;
|
||||
NodeTimer t = i->second;
|
||||
t.elapsed += dtime;
|
||||
if(t.elapsed >= t.duration)
|
||||
elapsed_timers.insert(std::make_pair(p, t.elapsed));
|
||||
if(t.elapsed >= t.timeout)
|
||||
elapsed_timers.insert(std::make_pair(p, t));
|
||||
else
|
||||
i->second = t;
|
||||
}
|
||||
// Delete elapsed timers
|
||||
for(std::map<v3s16, f32>::const_iterator
|
||||
for(std::map<v3s16, NodeTimer>::const_iterator
|
||||
i = elapsed_timers.begin();
|
||||
i != elapsed_timers.end(); i++){
|
||||
v3s16 p = i->first;
|
||||
|
||||
@@ -35,15 +35,15 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
class NodeTimer
|
||||
{
|
||||
public:
|
||||
NodeTimer(): duration(0.), elapsed(0.) {}
|
||||
NodeTimer(f32 duration_, f32 elapsed_):
|
||||
duration(duration_), elapsed(elapsed_) {}
|
||||
NodeTimer(): timeout(0.), elapsed(0.) {}
|
||||
NodeTimer(f32 timeout_, f32 elapsed_):
|
||||
timeout(timeout_), elapsed(elapsed_) {}
|
||||
~NodeTimer() {}
|
||||
|
||||
void serialize(std::ostream &os) const;
|
||||
void deSerialize(std::istream &is);
|
||||
|
||||
f32 duration;
|
||||
f32 timeout;
|
||||
f32 elapsed;
|
||||
};
|
||||
|
||||
@@ -57,8 +57,8 @@ class NodeTimerList
|
||||
NodeTimerList() {}
|
||||
~NodeTimerList() {}
|
||||
|
||||
void serialize(std::ostream &os) const;
|
||||
void deSerialize(std::istream &is);
|
||||
void serialize(std::ostream &os, u8 map_format_version) const;
|
||||
void deSerialize(std::istream &is, u8 map_format_version);
|
||||
|
||||
// Get timer
|
||||
NodeTimer get(v3s16 p){
|
||||
@@ -81,7 +81,7 @@ class NodeTimerList
|
||||
}
|
||||
|
||||
// A step in time. Returns map of elapsed timers.
|
||||
std::map<v3s16, f32> step(float dtime);
|
||||
std::map<v3s16, NodeTimer> step(float dtime);
|
||||
|
||||
private:
|
||||
std::map<v3s16, NodeTimer> m_data;
|
||||
|
||||
@@ -50,7 +50,7 @@ Player::Player(IGameDef *gamedef):
|
||||
inventory.addList("craftresult", 1);
|
||||
|
||||
// Can be redefined via Lua
|
||||
inventory_formspec = "invsize[8,7.5;]"
|
||||
inventory_formspec = "size[8,7.5]"
|
||||
//"image[1,0.6;1,2;player.png]"
|
||||
"list[current_player;main;0,3.5;8,4;]"
|
||||
"list[current_player;craft;3,0;3,3;]"
|
||||
|
||||
@@ -150,7 +150,6 @@ class Player
|
||||
|
||||
u8 light;
|
||||
|
||||
// In creative mode, this is the invisible backup inventory
|
||||
Inventory inventory;
|
||||
|
||||
u16 hp;
|
||||
|
||||
@@ -29,6 +29,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
#include "filesys.h"
|
||||
#include "log.h"
|
||||
#include "util/string.h"
|
||||
#include <list>
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include "CoreFoundation/CoreFoundation.h"
|
||||
@@ -154,7 +155,7 @@ bool detectMSVCBuildDir(char *c_path)
|
||||
|
||||
void initializePaths()
|
||||
{
|
||||
#ifdef RUN_IN_PLACE
|
||||
#if RUN_IN_PLACE
|
||||
/*
|
||||
Use relative paths if RUN_IN_PLACE
|
||||
*/
|
||||
@@ -252,19 +253,41 @@ void initializePaths()
|
||||
#elif defined(linux)
|
||||
#include <unistd.h>
|
||||
|
||||
char buf[BUFSIZ];
|
||||
memset(buf, 0, BUFSIZ);
|
||||
// Get path to executable
|
||||
assert(readlink("/proc/self/exe", buf, BUFSIZ-1) != -1);
|
||||
|
||||
pathRemoveFile(buf, '/');
|
||||
std::string bindir = "";
|
||||
{
|
||||
char buf[BUFSIZ];
|
||||
memset(buf, 0, BUFSIZ);
|
||||
assert(readlink("/proc/self/exe", buf, BUFSIZ-1) != -1);
|
||||
pathRemoveFile(buf, '/');
|
||||
bindir = buf;
|
||||
}
|
||||
|
||||
path_share = std::string(buf) + "/../share/" + PROJECT_NAME;
|
||||
//path_share = std::string(INSTALL_PREFIX) + "/share/" + PROJECT_NAME;
|
||||
if (!fs::PathExists(path_share)) {
|
||||
dstream<<"WARNING: system-wide share not found at \""<<path_share<<"\"";
|
||||
path_share = std::string(buf) + "/..";
|
||||
dstream<<"WARNING: Using \""<<path_share<<"\" instead."<<std::endl;
|
||||
// Find share directory from these.
|
||||
// It is identified by containing the subdirectory "builtin".
|
||||
std::list<std::string> trylist;
|
||||
std::string static_sharedir = STATIC_SHAREDIR;
|
||||
if(static_sharedir != "" && static_sharedir != ".")
|
||||
trylist.push_back(static_sharedir);
|
||||
trylist.push_back(bindir + "/../share/" + PROJECT_NAME);
|
||||
trylist.push_back(bindir + "/..");
|
||||
|
||||
for(std::list<std::string>::const_iterator i = trylist.begin();
|
||||
i != trylist.end(); i++)
|
||||
{
|
||||
const std::string &trypath = *i;
|
||||
if(!fs::PathExists(trypath) || !fs::PathExists(trypath + "/builtin")){
|
||||
dstream<<"WARNING: system-wide share not found at \""
|
||||
<<trypath<<"\""<<std::endl;
|
||||
continue;
|
||||
}
|
||||
// Warn if was not the first alternative
|
||||
if(i != trylist.begin()){
|
||||
dstream<<"WARNING: system-wide share found at \""
|
||||
<<trypath<<"\""<<std::endl;
|
||||
}
|
||||
path_share = trypath;
|
||||
break;
|
||||
}
|
||||
|
||||
path_user = std::string(getenv("HOME")) + "/." + PROJECT_NAME;
|
||||
@@ -297,7 +320,7 @@ void initializePaths()
|
||||
|
||||
#elif defined(__FreeBSD__)
|
||||
|
||||
path_share = std::string(INSTALL_PREFIX) + "/share/" + PROJECT_NAME;
|
||||
path_share = STATIC_SHAREDIR;
|
||||
path_user = std::string(getenv("HOME")) + "/." + PROJECT_NAME;
|
||||
|
||||
#endif
|
||||
|
||||
365
src/rollback.cpp
Normal file
365
src/rollback.cpp
Normal file
@@ -0,0 +1,365 @@
|
||||
/*
|
||||
Minetest-c55
|
||||
Copyright (C) 2012 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 "rollback.h"
|
||||
#include <fstream>
|
||||
#include <list>
|
||||
#include <sstream>
|
||||
#include "log.h"
|
||||
#include "mapnode.h"
|
||||
#include "gamedef.h"
|
||||
#include "nodedef.h"
|
||||
#include "util/serialize.h"
|
||||
#include "util/string.h"
|
||||
#include "strfnd.h"
|
||||
#include "util/numeric.h"
|
||||
#include "inventorymanager.h" // deserializing InventoryLocations
|
||||
|
||||
#define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")"
|
||||
|
||||
#define POINTS_PER_NODE (16.0)
|
||||
|
||||
// Get nearness factor for subject's action for this action
|
||||
// Return value: 0 = impossible, >0 = factor
|
||||
static float getSuspectNearness(bool is_guess, v3s16 suspect_p, int suspect_t,
|
||||
v3s16 action_p, int action_t)
|
||||
{
|
||||
// Suspect cannot cause things in the past
|
||||
if(action_t < suspect_t)
|
||||
return 0; // 0 = cannot be
|
||||
// Start from 100
|
||||
int f = 100;
|
||||
// Distance (1 node = -x points)
|
||||
f -= POINTS_PER_NODE * intToFloat(suspect_p, 1).getDistanceFrom(intToFloat(action_p, 1));
|
||||
// Time (1 second = -x points)
|
||||
f -= 1 * (action_t - suspect_t);
|
||||
// If is a guess, halve the points
|
||||
if(is_guess)
|
||||
f *= 0.5;
|
||||
// Limit to 0
|
||||
if(f < 0)
|
||||
f = 0;
|
||||
return f;
|
||||
}
|
||||
|
||||
class RollbackManager: public IRollbackManager
|
||||
{
|
||||
public:
|
||||
// IRollbackManager interface
|
||||
|
||||
void reportAction(const RollbackAction &action_)
|
||||
{
|
||||
// Ignore if not important
|
||||
if(!action_.isImportant(m_gamedef))
|
||||
return;
|
||||
RollbackAction action = action_;
|
||||
action.unix_time = time(0);
|
||||
// Figure out actor
|
||||
action.actor = m_current_actor;
|
||||
action.actor_is_guess = m_current_actor_is_guess;
|
||||
// If actor is not known, find out suspect or cancel
|
||||
if(action.actor.empty()){
|
||||
v3s16 p;
|
||||
if(!action.getPosition(&p))
|
||||
return;
|
||||
action.actor = getSuspect(p, 83, 1);
|
||||
if(action.actor.empty())
|
||||
return;
|
||||
action.actor_is_guess = true;
|
||||
}
|
||||
infostream<<"RollbackManager::reportAction():"
|
||||
<<" time="<<action.unix_time
|
||||
<<" actor=\""<<action.actor<<"\""
|
||||
<<(action.actor_is_guess?" (guess)":"")
|
||||
<<" action="<<action.toString()
|
||||
<<std::endl;
|
||||
addAction(action);
|
||||
}
|
||||
std::string getActor()
|
||||
{
|
||||
return m_current_actor;
|
||||
}
|
||||
bool isActorGuess()
|
||||
{
|
||||
return m_current_actor_is_guess;
|
||||
}
|
||||
void setActor(const std::string &actor, bool is_guess)
|
||||
{
|
||||
m_current_actor = actor;
|
||||
m_current_actor_is_guess = is_guess;
|
||||
}
|
||||
std::string getSuspect(v3s16 p, float nearness_shortcut, float min_nearness)
|
||||
{
|
||||
if(m_current_actor != "")
|
||||
return m_current_actor;
|
||||
int cur_time = time(0);
|
||||
int first_time = cur_time - (100-min_nearness);
|
||||
RollbackAction likely_suspect;
|
||||
float likely_suspect_nearness = 0;
|
||||
for(std::list<RollbackAction>::const_reverse_iterator
|
||||
i = m_action_latest_buffer.rbegin();
|
||||
i != m_action_latest_buffer.rend(); i++)
|
||||
{
|
||||
if(i->unix_time < first_time)
|
||||
break;
|
||||
if(i->actor == "")
|
||||
continue;
|
||||
// Find position of suspect or continue
|
||||
v3s16 suspect_p;
|
||||
if(!i->getPosition(&suspect_p))
|
||||
continue;
|
||||
float f = getSuspectNearness(i->actor_is_guess, suspect_p,
|
||||
i->unix_time, p, cur_time);
|
||||
if(f >= min_nearness && f > likely_suspect_nearness){
|
||||
likely_suspect_nearness = f;
|
||||
likely_suspect = *i;
|
||||
if(likely_suspect_nearness >= nearness_shortcut)
|
||||
break;
|
||||
}
|
||||
}
|
||||
// No likely suspect was found
|
||||
if(likely_suspect_nearness == 0)
|
||||
return "";
|
||||
// Likely suspect was found
|
||||
return likely_suspect.actor;
|
||||
}
|
||||
void flush()
|
||||
{
|
||||
infostream<<"RollbackManager::flush()"<<std::endl;
|
||||
std::ofstream of(m_filepath.c_str(), std::ios::app);
|
||||
if(!of.good()){
|
||||
errorstream<<"RollbackManager::flush(): Could not open file "
|
||||
<<"for appending: \""<<m_filepath<<"\""<<std::endl;
|
||||
return;
|
||||
}
|
||||
for(std::list<RollbackAction>::const_iterator
|
||||
i = m_action_todisk_buffer.begin();
|
||||
i != m_action_todisk_buffer.end(); i++)
|
||||
{
|
||||
// Do not save stuff that does not have an actor
|
||||
if(i->actor == "")
|
||||
continue;
|
||||
of<<i->unix_time;
|
||||
of<<" ";
|
||||
of<<serializeJsonString(i->actor);
|
||||
of<<" ";
|
||||
of<<i->toString();
|
||||
if(i->actor_is_guess){
|
||||
of<<" ";
|
||||
of<<"actor_is_guess";
|
||||
}
|
||||
of<<std::endl;
|
||||
}
|
||||
m_action_todisk_buffer.clear();
|
||||
}
|
||||
|
||||
// Other
|
||||
|
||||
RollbackManager(const std::string &filepath, IGameDef *gamedef):
|
||||
m_filepath(filepath),
|
||||
m_gamedef(gamedef),
|
||||
m_current_actor_is_guess(false)
|
||||
{
|
||||
infostream<<"RollbackManager::RollbackManager("<<filepath<<")"
|
||||
<<std::endl;
|
||||
}
|
||||
~RollbackManager()
|
||||
{
|
||||
infostream<<"RollbackManager::~RollbackManager()"<<std::endl;
|
||||
flush();
|
||||
}
|
||||
|
||||
void addAction(const RollbackAction &action)
|
||||
{
|
||||
m_action_todisk_buffer.push_back(action);
|
||||
m_action_latest_buffer.push_back(action);
|
||||
|
||||
// Flush to disk sometimes
|
||||
if(m_action_todisk_buffer.size() >= 100)
|
||||
flush();
|
||||
}
|
||||
|
||||
bool readFile(std::list<RollbackAction> &dst)
|
||||
{
|
||||
// Load whole file to memory
|
||||
std::ifstream f(m_filepath.c_str(), std::ios::in);
|
||||
if(!f.good()){
|
||||
errorstream<<"RollbackManager::readFile(): Could not open "
|
||||
<<"file for reading: \""<<m_filepath<<"\""<<std::endl;
|
||||
return false;
|
||||
}
|
||||
for(;;){
|
||||
if(f.eof() || !f.good())
|
||||
break;
|
||||
std::string line;
|
||||
std::getline(f, line);
|
||||
line = trim(line);
|
||||
if(line == "")
|
||||
continue;
|
||||
std::istringstream is(line);
|
||||
|
||||
try{
|
||||
std::string action_time_raw;
|
||||
std::getline(is, action_time_raw, ' ');
|
||||
std::string action_actor;
|
||||
try{
|
||||
action_actor = deSerializeJsonString(is);
|
||||
}catch(SerializationError &e){
|
||||
errorstream<<"RollbackManager: Error deserializing actor: "
|
||||
<<e.what()<<std::endl;
|
||||
throw e;
|
||||
}
|
||||
RollbackAction action;
|
||||
action.unix_time = stoi(action_time_raw);
|
||||
action.actor = action_actor;
|
||||
int c = is.get();
|
||||
if(c != ' '){
|
||||
is.putback(c);
|
||||
throw SerializationError("readFile(): second ' ' not found");
|
||||
}
|
||||
action.fromStream(is);
|
||||
/*infostream<<"RollbackManager::readFile(): Action from disk: "
|
||||
<<action.toString()<<std::endl;*/
|
||||
dst.push_back(action);
|
||||
}
|
||||
catch(SerializationError &e){
|
||||
errorstream<<"RollbackManager: Error on line: "<<line<<std::endl;
|
||||
errorstream<<"RollbackManager: ^ error: "<<e.what()<<std::endl;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::list<RollbackAction> getEntriesSince(int first_time)
|
||||
{
|
||||
infostream<<"RollbackManager::getEntriesSince("<<first_time<<")"<<std::endl;
|
||||
// Collect enough data to this buffer
|
||||
std::list<RollbackAction> action_buffer;
|
||||
// Use the latest buffer if it is long enough
|
||||
if(!m_action_latest_buffer.empty() &&
|
||||
m_action_latest_buffer.begin()->unix_time <= first_time){
|
||||
action_buffer = m_action_latest_buffer;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Save all remaining stuff
|
||||
flush();
|
||||
// Load whole file to memory
|
||||
bool good = readFile(action_buffer);
|
||||
if(!good){
|
||||
errorstream<<"RollbackManager::getEntriesSince(): Failed to"
|
||||
<<" open file; using data in memory."<<std::endl;
|
||||
action_buffer = m_action_latest_buffer;
|
||||
}
|
||||
}
|
||||
return action_buffer;
|
||||
}
|
||||
|
||||
std::string getLastNodeActor(v3s16 p, int range, int seconds,
|
||||
v3s16 *act_p, int *act_seconds)
|
||||
{
|
||||
infostream<<"RollbackManager::getLastNodeActor("<<PP(p)
|
||||
<<", "<<seconds<<")"<<std::endl;
|
||||
// Figure out time
|
||||
int cur_time = time(0);
|
||||
int first_time = cur_time - seconds;
|
||||
|
||||
std::list<RollbackAction> action_buffer = getEntriesSince(first_time);
|
||||
|
||||
std::list<RollbackAction> result;
|
||||
|
||||
for(std::list<RollbackAction>::const_reverse_iterator
|
||||
i = action_buffer.rbegin();
|
||||
i != action_buffer.rend(); i++)
|
||||
{
|
||||
if(i->unix_time < first_time)
|
||||
break;
|
||||
|
||||
// Find position of action or continue
|
||||
v3s16 action_p;
|
||||
if(!i->getPosition(&action_p))
|
||||
continue;
|
||||
|
||||
if(range == 0){
|
||||
if(action_p != p)
|
||||
continue;
|
||||
} else {
|
||||
if(abs(action_p.X - p.X) > range ||
|
||||
abs(action_p.Y - p.Y) > range ||
|
||||
abs(action_p.Z - p.Z) > range)
|
||||
continue;
|
||||
}
|
||||
|
||||
if(act_p)
|
||||
*act_p = action_p;
|
||||
if(act_seconds)
|
||||
*act_seconds = cur_time - i->unix_time;
|
||||
return i->actor;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
std::list<RollbackAction> getRevertActions(const std::string &actor_filter,
|
||||
int seconds)
|
||||
{
|
||||
infostream<<"RollbackManager::getRevertActions("<<actor_filter
|
||||
<<", "<<seconds<<")"<<std::endl;
|
||||
// Figure out time
|
||||
int cur_time = time(0);
|
||||
int first_time = cur_time - seconds;
|
||||
|
||||
std::list<RollbackAction> action_buffer = getEntriesSince(first_time);
|
||||
|
||||
std::list<RollbackAction> result;
|
||||
|
||||
for(std::list<RollbackAction>::const_reverse_iterator
|
||||
i = action_buffer.rbegin();
|
||||
i != action_buffer.rend(); i++)
|
||||
{
|
||||
if(i->unix_time < first_time)
|
||||
break;
|
||||
if(i->actor != actor_filter)
|
||||
continue;
|
||||
const RollbackAction &action = *i;
|
||||
/*infostream<<"RollbackManager::revertAction(): Should revert"
|
||||
<<" time="<<action.unix_time
|
||||
<<" actor=\""<<action.actor<<"\""
|
||||
<<" action="<<action.toString()
|
||||
<<std::endl;*/
|
||||
result.push_back(action);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string m_filepath;
|
||||
IGameDef *m_gamedef;
|
||||
std::string m_current_actor;
|
||||
bool m_current_actor_is_guess;
|
||||
std::list<RollbackAction> m_action_todisk_buffer;
|
||||
std::list<RollbackAction> m_action_latest_buffer;
|
||||
};
|
||||
|
||||
IRollbackManager *createRollbackManager(const std::string &filepath, IGameDef *gamedef)
|
||||
{
|
||||
return new RollbackManager(filepath, gamedef);
|
||||
}
|
||||
|
||||
|
||||
54
src/rollback.h
Normal file
54
src/rollback.h
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
Minetest-c55
|
||||
Copyright (C) 2012 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 ROLLBACK_HEADER
|
||||
#define ROLLBACK_HEADER
|
||||
|
||||
#include <string>
|
||||
#include "irr_v3d.h"
|
||||
#include "rollback_interface.h"
|
||||
#include <list>
|
||||
|
||||
class IGameDef;
|
||||
|
||||
class IRollbackManager: public IRollbackReportSink
|
||||
{
|
||||
public:
|
||||
// IRollbackReportManager
|
||||
virtual void reportAction(const RollbackAction &action) = 0;
|
||||
virtual std::string getActor() = 0;
|
||||
virtual bool isActorGuess() = 0;
|
||||
virtual void setActor(const std::string &actor, bool is_guess) = 0;
|
||||
virtual std::string getSuspect(v3s16 p, float nearness_shortcut,
|
||||
float min_nearness) = 0;
|
||||
|
||||
virtual ~IRollbackManager(){}
|
||||
virtual void flush() = 0;
|
||||
// Get last actor that did something to position p, but not further than
|
||||
// <seconds> in history
|
||||
virtual std::string getLastNodeActor(v3s16 p, int range, int seconds,
|
||||
v3s16 *act_p, int *act_seconds) = 0;
|
||||
// Get actions to revert <seconds> of history made by <actor>
|
||||
virtual std::list<RollbackAction> getRevertActions(const std::string &actor,
|
||||
int seconds) = 0;
|
||||
};
|
||||
|
||||
IRollbackManager *createRollbackManager(const std::string &filepath, IGameDef *gamedef);
|
||||
|
||||
#endif
|
||||
416
src/rollback_interface.cpp
Normal file
416
src/rollback_interface.cpp
Normal file
@@ -0,0 +1,416 @@
|
||||
/*
|
||||
Minetest-c55
|
||||
Copyright (C) 2012 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 "rollback_interface.h"
|
||||
#include <sstream>
|
||||
#include "util/serialize.h"
|
||||
#include "util/string.h"
|
||||
#include "util/numeric.h"
|
||||
#include "map.h"
|
||||
#include "gamedef.h"
|
||||
#include "nodedef.h"
|
||||
#include "nodemetadata.h"
|
||||
#include "exceptions.h"
|
||||
#include "log.h"
|
||||
#include "inventorymanager.h"
|
||||
#include "inventory.h"
|
||||
#include "mapblock.h"
|
||||
|
||||
#define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")"
|
||||
|
||||
RollbackNode::RollbackNode(Map *map, v3s16 p, IGameDef *gamedef)
|
||||
{
|
||||
INodeDefManager *ndef = gamedef->ndef();
|
||||
MapNode n = map->getNodeNoEx(p);
|
||||
name = ndef->get(n).name;
|
||||
param1 = n.param1;
|
||||
param2 = n.param2;
|
||||
NodeMetadata *metap = map->getNodeMetadata(p);
|
||||
if(metap){
|
||||
std::ostringstream os(std::ios::binary);
|
||||
metap->serialize(os);
|
||||
meta = os.str();
|
||||
}
|
||||
}
|
||||
|
||||
std::string RollbackAction::toString() const
|
||||
{
|
||||
switch(type){
|
||||
case TYPE_SET_NODE: {
|
||||
std::ostringstream os(std::ios::binary);
|
||||
os<<"[set_node";
|
||||
os<<" ";
|
||||
os<<"("<<itos(p.X)<<","<<itos(p.Y)<<","<<itos(p.Z)<<")";
|
||||
os<<" ";
|
||||
os<<serializeJsonString(n_old.name);
|
||||
os<<" ";
|
||||
os<<itos(n_old.param1);
|
||||
os<<" ";
|
||||
os<<itos(n_old.param2);
|
||||
os<<" ";
|
||||
os<<serializeJsonString(n_old.meta);
|
||||
os<<" ";
|
||||
os<<serializeJsonString(n_new.name);
|
||||
os<<" ";
|
||||
os<<itos(n_new.param1);
|
||||
os<<" ";
|
||||
os<<itos(n_new.param2);
|
||||
os<<" ";
|
||||
os<<serializeJsonString(n_new.meta);
|
||||
os<<"]";
|
||||
return os.str(); }
|
||||
case TYPE_MODIFY_INVENTORY_STACK: {
|
||||
std::ostringstream os(std::ios::binary);
|
||||
os<<"[modify_inventory_stack";
|
||||
os<<" ";
|
||||
os<<serializeJsonString(inventory_location);
|
||||
os<<" ";
|
||||
os<<serializeJsonString(inventory_list);
|
||||
os<<" ";
|
||||
os<<inventory_index;
|
||||
os<<" ";
|
||||
os<<(inventory_add?"add":"remove");
|
||||
os<<" ";
|
||||
os<<serializeJsonString(inventory_stack);
|
||||
os<<"]";
|
||||
return os.str(); }
|
||||
default:
|
||||
return "none";
|
||||
}
|
||||
}
|
||||
|
||||
void RollbackAction::fromStream(std::istream &is) throw(SerializationError)
|
||||
{
|
||||
int c = is.get();
|
||||
if(c != '['){
|
||||
is.putback(c);
|
||||
throw SerializationError("RollbackAction: starting [ not found");
|
||||
}
|
||||
|
||||
std::string id;
|
||||
std::getline(is, id, ' ');
|
||||
|
||||
if(id == "set_node")
|
||||
{
|
||||
c = is.get();
|
||||
if(c != '('){
|
||||
is.putback(c);
|
||||
throw SerializationError("RollbackAction: starting ( not found");
|
||||
}
|
||||
// Position
|
||||
std::string px_raw;
|
||||
std::string py_raw;
|
||||
std::string pz_raw;
|
||||
std::getline(is, px_raw, ',');
|
||||
std::getline(is, py_raw, ',');
|
||||
std::getline(is, pz_raw, ')');
|
||||
c = is.get();
|
||||
if(c != ' '){
|
||||
is.putback(c);
|
||||
throw SerializationError("RollbackAction: after-p ' ' not found");
|
||||
}
|
||||
v3s16 loaded_p(stoi(px_raw), stoi(py_raw), stoi(pz_raw));
|
||||
// Old node
|
||||
std::string old_name;
|
||||
try{
|
||||
old_name = deSerializeJsonString(is);
|
||||
}catch(SerializationError &e){
|
||||
errorstream<<"Serialization error in RollbackAction::fromStream(): "
|
||||
<<"old_name: "<<e.what()<<std::endl;
|
||||
throw e;
|
||||
}
|
||||
c = is.get();
|
||||
if(c != ' '){
|
||||
is.putback(c);
|
||||
throw SerializationError("RollbackAction: after-old_name ' ' not found");
|
||||
}
|
||||
std::string old_p1_raw;
|
||||
std::string old_p2_raw;
|
||||
std::getline(is, old_p1_raw, ' ');
|
||||
std::getline(is, old_p2_raw, ' ');
|
||||
int old_p1 = stoi(old_p1_raw);
|
||||
int old_p2 = stoi(old_p2_raw);
|
||||
std::string old_meta;
|
||||
try{
|
||||
old_meta = deSerializeJsonString(is);
|
||||
}catch(SerializationError &e){
|
||||
errorstream<<"Serialization error in RollbackAction::fromStream(): "
|
||||
<<"old_meta: "<<e.what()<<std::endl;
|
||||
throw e;
|
||||
}
|
||||
c = is.get();
|
||||
if(c != ' '){
|
||||
is.putback(c);
|
||||
throw SerializationError("RollbackAction: after-old_meta ' ' not found");
|
||||
}
|
||||
// New node
|
||||
std::string new_name;
|
||||
try{
|
||||
new_name = deSerializeJsonString(is);
|
||||
}catch(SerializationError &e){
|
||||
errorstream<<"Serialization error in RollbackAction::fromStream(): "
|
||||
<<"new_name: "<<e.what()<<std::endl;
|
||||
throw e;
|
||||
}
|
||||
c = is.get();
|
||||
if(c != ' '){
|
||||
is.putback(c);
|
||||
throw SerializationError("RollbackAction: after-new_name ' ' not found");
|
||||
}
|
||||
std::string new_p1_raw;
|
||||
std::string new_p2_raw;
|
||||
std::getline(is, new_p1_raw, ' ');
|
||||
std::getline(is, new_p2_raw, ' ');
|
||||
int new_p1 = stoi(new_p1_raw);
|
||||
int new_p2 = stoi(new_p2_raw);
|
||||
std::string new_meta;
|
||||
try{
|
||||
new_meta = deSerializeJsonString(is);
|
||||
}catch(SerializationError &e){
|
||||
errorstream<<"Serialization error in RollbackAction::fromStream(): "
|
||||
<<"new_meta: "<<e.what()<<std::endl;
|
||||
throw e;
|
||||
}
|
||||
c = is.get();
|
||||
if(c != ']'){
|
||||
is.putback(c);
|
||||
throw SerializationError("RollbackAction: after-new_meta ] not found");
|
||||
}
|
||||
// Set values
|
||||
type = TYPE_SET_NODE;
|
||||
p = loaded_p;
|
||||
n_old.name = old_name;
|
||||
n_old.param1 = old_p1;
|
||||
n_old.param2 = old_p2;
|
||||
n_old.meta = old_meta;
|
||||
n_new.name = new_name;
|
||||
n_new.param1 = new_p1;
|
||||
n_new.param2 = new_p2;
|
||||
n_new.meta = new_meta;
|
||||
}
|
||||
else if(id == "modify_inventory_stack")
|
||||
{
|
||||
// Location
|
||||
std::string location;
|
||||
try{
|
||||
location = deSerializeJsonString(is);
|
||||
}catch(SerializationError &e){
|
||||
errorstream<<"Serialization error in RollbackAction::fromStream(): "
|
||||
<<"location: "<<e.what()<<std::endl;
|
||||
throw e;
|
||||
}
|
||||
c = is.get();
|
||||
if(c != ' '){
|
||||
is.putback(c);
|
||||
throw SerializationError("RollbackAction: after-loc ' ' not found");
|
||||
}
|
||||
// List
|
||||
std::string listname;
|
||||
try{
|
||||
listname = deSerializeJsonString(is);
|
||||
}catch(SerializationError &e){
|
||||
errorstream<<"Serialization error in RollbackAction::fromStream(): "
|
||||
<<"listname: "<<e.what()<<std::endl;
|
||||
throw e;
|
||||
}
|
||||
c = is.get();
|
||||
if(c != ' '){
|
||||
is.putback(c);
|
||||
throw SerializationError("RollbackAction: after-list ' ' not found");
|
||||
}
|
||||
// Index
|
||||
std::string index_raw;
|
||||
std::getline(is, index_raw, ' ');
|
||||
// add/remove
|
||||
std::string addremove;
|
||||
std::getline(is, addremove, ' ');
|
||||
if(addremove != "add" && addremove != "remove"){
|
||||
throw SerializationError("RollbackAction: addremove is not add or remove");
|
||||
}
|
||||
// Itemstring
|
||||
std::string stack;
|
||||
try{
|
||||
stack = deSerializeJsonString(is);
|
||||
}catch(SerializationError &e){
|
||||
errorstream<<"Serialization error in RollbackAction::fromStream(): "
|
||||
<<"stack: "<<e.what()<<std::endl;
|
||||
throw e;
|
||||
}
|
||||
// Set values
|
||||
type = TYPE_MODIFY_INVENTORY_STACK;
|
||||
inventory_location = location;
|
||||
inventory_list = listname;
|
||||
inventory_index = stoi(index_raw);
|
||||
inventory_add = (addremove == "add");
|
||||
inventory_stack = stack;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw SerializationError("RollbackAction: Unknown id");
|
||||
}
|
||||
}
|
||||
|
||||
bool RollbackAction::isImportant(IGameDef *gamedef) const
|
||||
{
|
||||
switch(type){
|
||||
case TYPE_SET_NODE: {
|
||||
// If names differ, action is always important
|
||||
if(n_old.name != n_new.name)
|
||||
return true;
|
||||
// If metadata differs, action is always important
|
||||
if(n_old.meta != n_new.meta)
|
||||
return true;
|
||||
INodeDefManager *ndef = gamedef->ndef();
|
||||
// Both are of the same name, so a single definition is needed
|
||||
const ContentFeatures &def = ndef->get(n_old.name);
|
||||
// If the type is flowing liquid, action is not important
|
||||
if(def.liquid_type == LIQUID_FLOWING)
|
||||
return false;
|
||||
// Otherwise action is important
|
||||
return true; }
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool RollbackAction::getPosition(v3s16 *dst) const
|
||||
{
|
||||
switch(type){
|
||||
case RollbackAction::TYPE_SET_NODE:
|
||||
if(dst) *dst = p;
|
||||
return true;
|
||||
case RollbackAction::TYPE_MODIFY_INVENTORY_STACK: {
|
||||
InventoryLocation loc;
|
||||
loc.deSerialize(inventory_location);
|
||||
if(loc.type != InventoryLocation::NODEMETA)
|
||||
return false;
|
||||
if(dst) *dst = loc.p;
|
||||
return true; }
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool RollbackAction::applyRevert(Map *map, InventoryManager *imgr, IGameDef *gamedef) const
|
||||
{
|
||||
try{
|
||||
switch(type){
|
||||
case TYPE_NOTHING:
|
||||
return true;
|
||||
case TYPE_SET_NODE: {
|
||||
INodeDefManager *ndef = gamedef->ndef();
|
||||
// Make sure position is loaded from disk
|
||||
map->emergeBlock(getContainerPos(p, MAP_BLOCKSIZE), false);
|
||||
// Check current node
|
||||
MapNode current_node = map->getNodeNoEx(p);
|
||||
std::string current_name = ndef->get(current_node).name;
|
||||
// If current node not the new node, it's bad
|
||||
if(current_name != n_new.name)
|
||||
return false;
|
||||
/*// If current node not the new node and not ignore, it's bad
|
||||
if(current_name != n_new.name && current_name != "ignore")
|
||||
return false;*/
|
||||
// Create rollback node
|
||||
MapNode n(ndef, n_old.name, n_old.param1, n_old.param2);
|
||||
// Set rollback node
|
||||
try{
|
||||
if(!map->addNodeWithEvent(p, n)){
|
||||
infostream<<"RollbackAction::applyRevert(): "
|
||||
<<"AddNodeWithEvent failed at "
|
||||
<<PP(p)<<" for "<<n_old.name<<std::endl;
|
||||
return false;
|
||||
}
|
||||
NodeMetadata *meta = map->getNodeMetadata(p);
|
||||
if(n_old.meta != ""){
|
||||
if(!meta){
|
||||
meta = new NodeMetadata(gamedef);
|
||||
map->setNodeMetadata(p, meta);
|
||||
}
|
||||
std::istringstream is(n_old.meta, std::ios::binary);
|
||||
meta->deSerialize(is);
|
||||
} else {
|
||||
map->removeNodeMetadata(p);
|
||||
}
|
||||
// NOTE: This same code is in scriptapi.cpp
|
||||
// Inform other things that the metadata has changed
|
||||
v3s16 blockpos = getContainerPos(p, MAP_BLOCKSIZE);
|
||||
MapEditEvent event;
|
||||
event.type = MEET_BLOCK_NODE_METADATA_CHANGED;
|
||||
event.p = blockpos;
|
||||
map->dispatchEvent(&event);
|
||||
// Set the block to be saved
|
||||
MapBlock *block = map->getBlockNoCreateNoEx(blockpos);
|
||||
if(block)
|
||||
block->raiseModified(MOD_STATE_WRITE_NEEDED,
|
||||
"NodeMetaRef::reportMetadataChange");
|
||||
}catch(InvalidPositionException &e){
|
||||
infostream<<"RollbackAction::applyRevert(): "
|
||||
<<"InvalidPositionException: "<<e.what()<<std::endl;
|
||||
return false;
|
||||
}
|
||||
// Success
|
||||
return true; }
|
||||
case TYPE_MODIFY_INVENTORY_STACK: {
|
||||
InventoryLocation loc;
|
||||
loc.deSerialize(inventory_location);
|
||||
ItemStack stack;
|
||||
stack.deSerialize(inventory_stack, gamedef->idef());
|
||||
Inventory *inv = imgr->getInventory(loc);
|
||||
if(!inv){
|
||||
infostream<<"RollbackAction::applyRevert(): Could not get "
|
||||
"inventory at "<<inventory_location<<std::endl;
|
||||
return false;
|
||||
}
|
||||
InventoryList *list = inv->getList(inventory_list);
|
||||
if(!list){
|
||||
infostream<<"RollbackAction::applyRevert(): Could not get "
|
||||
"inventory list \""<<inventory_list<<"\" in "
|
||||
<<inventory_location<<std::endl;
|
||||
return false;
|
||||
}
|
||||
if(list->getSize() <= inventory_index){
|
||||
infostream<<"RollbackAction::applyRevert(): List index "
|
||||
<<inventory_index<<" too large in "
|
||||
<<"inventory list \""<<inventory_list<<"\" in "
|
||||
<<inventory_location<<std::endl;
|
||||
}
|
||||
// If item was added, take away item, otherwise add removed item
|
||||
if(inventory_add){
|
||||
// Silently ignore different current item
|
||||
if(list->getItem(inventory_index).name != stack.name)
|
||||
return false;
|
||||
list->takeItem(inventory_index, stack.count);
|
||||
} else {
|
||||
list->addItem(inventory_index, stack);
|
||||
}
|
||||
// Inventory was modified; send to clients
|
||||
imgr->setInventoryModified(loc);
|
||||
return true; }
|
||||
default:
|
||||
errorstream<<"RollbackAction::applyRevert(): type not handled"
|
||||
<<std::endl;
|
||||
return false;
|
||||
}
|
||||
}catch(SerializationError &e){
|
||||
errorstream<<"RollbackAction::applyRevert(): n_old.name="<<n_old.name
|
||||
<<", SerializationError: "<<e.what()<<std::endl;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
156
src/rollback_interface.h
Normal file
156
src/rollback_interface.h
Normal file
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
Minetest-c55
|
||||
Copyright (C) 2012 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 ROLLBACK_INTERFACE_HEADER
|
||||
#define ROLLBACK_INTERFACE_HEADER
|
||||
|
||||
#include "irr_v3d.h"
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include "exceptions.h"
|
||||
|
||||
class Map;
|
||||
class IGameDef;
|
||||
struct MapNode;
|
||||
class InventoryManager;
|
||||
|
||||
struct RollbackNode
|
||||
{
|
||||
std::string name;
|
||||
int param1;
|
||||
int param2;
|
||||
std::string meta;
|
||||
|
||||
bool operator==(const RollbackNode &other)
|
||||
{
|
||||
return (name == other.name && param1 == other.param1 &&
|
||||
param2 == other.param2 && meta == other.meta);
|
||||
}
|
||||
bool operator!=(const RollbackNode &other)
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
RollbackNode():
|
||||
param1(0),
|
||||
param2(0)
|
||||
{}
|
||||
|
||||
RollbackNode(Map *map, v3s16 p, IGameDef *gamedef);
|
||||
};
|
||||
|
||||
struct RollbackAction
|
||||
{
|
||||
enum Type{
|
||||
TYPE_NOTHING,
|
||||
TYPE_SET_NODE,
|
||||
TYPE_MODIFY_INVENTORY_STACK,
|
||||
} type;
|
||||
|
||||
int unix_time;
|
||||
std::string actor;
|
||||
bool actor_is_guess;
|
||||
|
||||
v3s16 p;
|
||||
RollbackNode n_old;
|
||||
RollbackNode n_new;
|
||||
|
||||
std::string inventory_location;
|
||||
std::string inventory_list;
|
||||
u32 inventory_index;
|
||||
bool inventory_add;
|
||||
std::string inventory_stack;
|
||||
|
||||
RollbackAction():
|
||||
type(TYPE_NOTHING),
|
||||
unix_time(0),
|
||||
actor_is_guess(false)
|
||||
{}
|
||||
|
||||
void setSetNode(v3s16 p_, const RollbackNode &n_old_,
|
||||
const RollbackNode &n_new_)
|
||||
{
|
||||
type = TYPE_SET_NODE;
|
||||
p = p_;
|
||||
n_old = n_old_;
|
||||
n_new = n_new_;
|
||||
}
|
||||
|
||||
void setModifyInventoryStack(const std::string &inventory_location_,
|
||||
const std::string &inventory_list_, int index_,
|
||||
bool add_, const std::string &inventory_stack_)
|
||||
{
|
||||
type = TYPE_MODIFY_INVENTORY_STACK;
|
||||
inventory_location = inventory_location_;
|
||||
inventory_list = inventory_list_;
|
||||
inventory_index = index_;
|
||||
inventory_add = add_;
|
||||
inventory_stack = inventory_stack_;
|
||||
}
|
||||
|
||||
// String should not contain newlines or nulls
|
||||
std::string toString() const;
|
||||
void fromStream(std::istream &is) throw(SerializationError);
|
||||
|
||||
// Eg. flowing water level changes are not important
|
||||
bool isImportant(IGameDef *gamedef) const;
|
||||
|
||||
bool getPosition(v3s16 *dst) const;
|
||||
|
||||
bool applyRevert(Map *map, InventoryManager *imgr, IGameDef *gamedef) const;
|
||||
};
|
||||
|
||||
class IRollbackReportSink
|
||||
{
|
||||
public:
|
||||
virtual ~IRollbackReportSink(){}
|
||||
virtual void reportAction(const RollbackAction &action) = 0;
|
||||
virtual std::string getActor() = 0;
|
||||
virtual bool isActorGuess() = 0;
|
||||
virtual void setActor(const std::string &actor, bool is_guess) = 0;
|
||||
virtual std::string getSuspect(v3s16 p, float nearness_shortcut,
|
||||
float min_nearness) = 0;
|
||||
};
|
||||
|
||||
class RollbackScopeActor
|
||||
{
|
||||
public:
|
||||
RollbackScopeActor(IRollbackReportSink *sink, const std::string &actor,
|
||||
bool is_guess=false):
|
||||
m_sink(sink)
|
||||
{
|
||||
if(m_sink){
|
||||
m_actor_was = m_sink->getActor();
|
||||
m_actor_was_guess = m_sink->isActorGuess();
|
||||
m_sink->setActor(actor, is_guess);
|
||||
}
|
||||
}
|
||||
~RollbackScopeActor()
|
||||
{
|
||||
if(m_sink){
|
||||
m_sink->setActor(m_actor_was, m_actor_was_guess);
|
||||
}
|
||||
}
|
||||
private:
|
||||
IRollbackReportSink *m_sink;
|
||||
std::string m_actor_was;
|
||||
bool m_actor_was_guess;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -47,6 +47,7 @@ extern "C" {
|
||||
#include "daynightratio.h"
|
||||
#include "noise.h" // PseudoRandom for LuaPseudoRandom
|
||||
#include "util/pointedthing.h"
|
||||
#include "rollback.h"
|
||||
|
||||
static void stackDump(lua_State *L, std::ostream &o)
|
||||
{
|
||||
@@ -2106,6 +2107,7 @@ class NodeMetaRef
|
||||
|
||||
static void reportMetadataChange(NodeMetaRef *ref)
|
||||
{
|
||||
// NOTE: This same code is in rollback_interface.cpp
|
||||
// Inform other things that the metadata has changed
|
||||
v3s16 blockpos = getNodeBlockPos(ref->m_p);
|
||||
MapEditEvent event;
|
||||
@@ -3118,6 +3120,162 @@ const luaL_reg LuaPerlinNoise::methods[] = {
|
||||
{0,0}
|
||||
};
|
||||
|
||||
/*
|
||||
NodeTimerRef
|
||||
*/
|
||||
|
||||
class NodeTimerRef
|
||||
{
|
||||
private:
|
||||
v3s16 m_p;
|
||||
ServerEnvironment *m_env;
|
||||
|
||||
static const char className[];
|
||||
static const luaL_reg methods[];
|
||||
|
||||
static int gc_object(lua_State *L) {
|
||||
NodeTimerRef *o = *(NodeTimerRef **)(lua_touserdata(L, 1));
|
||||
delete o;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static NodeTimerRef *checkobject(lua_State *L, int narg)
|
||||
{
|
||||
luaL_checktype(L, narg, LUA_TUSERDATA);
|
||||
void *ud = luaL_checkudata(L, narg, className);
|
||||
if(!ud) luaL_typerror(L, narg, className);
|
||||
return *(NodeTimerRef**)ud; // unbox pointer
|
||||
}
|
||||
|
||||
static int l_set(lua_State *L)
|
||||
{
|
||||
NodeTimerRef *o = checkobject(L, 1);
|
||||
ServerEnvironment *env = o->m_env;
|
||||
if(env == NULL) return 0;
|
||||
f32 t = luaL_checknumber(L,2);
|
||||
f32 e = luaL_checknumber(L,3);
|
||||
env->getMap().setNodeTimer(o->m_p,NodeTimer(t,e));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_start(lua_State *L)
|
||||
{
|
||||
NodeTimerRef *o = checkobject(L, 1);
|
||||
ServerEnvironment *env = o->m_env;
|
||||
if(env == NULL) return 0;
|
||||
f32 t = luaL_checknumber(L,2);
|
||||
env->getMap().setNodeTimer(o->m_p,NodeTimer(t,0));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_stop(lua_State *L)
|
||||
{
|
||||
NodeTimerRef *o = checkobject(L, 1);
|
||||
ServerEnvironment *env = o->m_env;
|
||||
if(env == NULL) return 0;
|
||||
env->getMap().removeNodeTimer(o->m_p);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_is_started(lua_State *L)
|
||||
{
|
||||
NodeTimerRef *o = checkobject(L, 1);
|
||||
ServerEnvironment *env = o->m_env;
|
||||
if(env == NULL) return 0;
|
||||
|
||||
NodeTimer t = env->getMap().getNodeTimer(o->m_p);
|
||||
lua_pushboolean(L,(t.timeout != 0));
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int l_get_timeout(lua_State *L)
|
||||
{
|
||||
NodeTimerRef *o = checkobject(L, 1);
|
||||
ServerEnvironment *env = o->m_env;
|
||||
if(env == NULL) return 0;
|
||||
|
||||
NodeTimer t = env->getMap().getNodeTimer(o->m_p);
|
||||
lua_pushnumber(L,t.timeout);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int l_get_elapsed(lua_State *L)
|
||||
{
|
||||
NodeTimerRef *o = checkobject(L, 1);
|
||||
ServerEnvironment *env = o->m_env;
|
||||
if(env == NULL) return 0;
|
||||
|
||||
NodeTimer t = env->getMap().getNodeTimer(o->m_p);
|
||||
lua_pushnumber(L,t.elapsed);
|
||||
return 1;
|
||||
}
|
||||
|
||||
public:
|
||||
NodeTimerRef(v3s16 p, ServerEnvironment *env):
|
||||
m_p(p),
|
||||
m_env(env)
|
||||
{
|
||||
}
|
||||
|
||||
~NodeTimerRef()
|
||||
{
|
||||
}
|
||||
|
||||
// Creates an NodeTimerRef and leaves it on top of stack
|
||||
// Not callable from Lua; all references are created on the C side.
|
||||
static void create(lua_State *L, v3s16 p, ServerEnvironment *env)
|
||||
{
|
||||
NodeTimerRef *o = new NodeTimerRef(p, env);
|
||||
*(void **)(lua_newuserdata(L, sizeof(void *))) = o;
|
||||
luaL_getmetatable(L, className);
|
||||
lua_setmetatable(L, -2);
|
||||
}
|
||||
|
||||
static void set_null(lua_State *L)
|
||||
{
|
||||
NodeTimerRef *o = checkobject(L, -1);
|
||||
o->m_env = NULL;
|
||||
}
|
||||
|
||||
static void Register(lua_State *L)
|
||||
{
|
||||
lua_newtable(L);
|
||||
int methodtable = lua_gettop(L);
|
||||
luaL_newmetatable(L, className);
|
||||
int metatable = lua_gettop(L);
|
||||
|
||||
lua_pushliteral(L, "__metatable");
|
||||
lua_pushvalue(L, methodtable);
|
||||
lua_settable(L, metatable); // hide metatable from Lua getmetatable()
|
||||
|
||||
lua_pushliteral(L, "__index");
|
||||
lua_pushvalue(L, methodtable);
|
||||
lua_settable(L, metatable);
|
||||
|
||||
lua_pushliteral(L, "__gc");
|
||||
lua_pushcfunction(L, gc_object);
|
||||
lua_settable(L, metatable);
|
||||
|
||||
lua_pop(L, 1); // drop metatable
|
||||
|
||||
luaL_openlib(L, 0, methods, 0); // fill methodtable
|
||||
lua_pop(L, 1); // drop methodtable
|
||||
|
||||
// Cannot be created from Lua
|
||||
//lua_register(L, className, create_object);
|
||||
}
|
||||
};
|
||||
const char NodeTimerRef::className[] = "NodeTimerRef";
|
||||
const luaL_reg NodeTimerRef::methods[] = {
|
||||
method(NodeTimerRef, start),
|
||||
method(NodeTimerRef, set),
|
||||
method(NodeTimerRef, stop),
|
||||
method(NodeTimerRef, is_started),
|
||||
method(NodeTimerRef, get_timeout),
|
||||
method(NodeTimerRef, get_elapsed),
|
||||
{0,0}
|
||||
};
|
||||
|
||||
/*
|
||||
EnvRef
|
||||
*/
|
||||
@@ -3351,6 +3509,31 @@ class EnvRef
|
||||
return 1;
|
||||
}
|
||||
|
||||
// EnvRef:get_meta(pos)
|
||||
static int l_get_meta(lua_State *L)
|
||||
{
|
||||
//infostream<<"EnvRef::l_get_meta()"<<std::endl;
|
||||
EnvRef *o = checkobject(L, 1);
|
||||
ServerEnvironment *env = o->m_env;
|
||||
if(env == NULL) return 0;
|
||||
// Do it
|
||||
v3s16 p = read_v3s16(L, 2);
|
||||
NodeMetaRef::create(L, p, env);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// EnvRef:get_node_timer(pos)
|
||||
static int l_get_node_timer(lua_State *L)
|
||||
{
|
||||
EnvRef *o = checkobject(L, 1);
|
||||
ServerEnvironment *env = o->m_env;
|
||||
if(env == NULL) return 0;
|
||||
// Do it
|
||||
v3s16 p = read_v3s16(L, 2);
|
||||
NodeTimerRef::create(L, p, env);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// EnvRef:add_entity(pos, entityname) -> ObjectRef or nil
|
||||
// pos = {x=num, y=num, z=num}
|
||||
static int l_add_entity(lua_State *L)
|
||||
@@ -3431,19 +3614,6 @@ class EnvRef
|
||||
return 0;
|
||||
}
|
||||
|
||||
// EnvRef:get_meta(pos)
|
||||
static int l_get_meta(lua_State *L)
|
||||
{
|
||||
//infostream<<"EnvRef::l_get_meta()"<<std::endl;
|
||||
EnvRef *o = checkobject(L, 1);
|
||||
ServerEnvironment *env = o->m_env;
|
||||
if(env == NULL) return 0;
|
||||
// Do it
|
||||
v3s16 p = read_v3s16(L, 2);
|
||||
NodeMetaRef::create(L, p, env);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// EnvRef:get_player_by_name(name)
|
||||
static int l_get_player_by_name(lua_State *L)
|
||||
{
|
||||
@@ -3713,6 +3883,7 @@ const luaL_reg EnvRef::methods[] = {
|
||||
method(EnvRef, add_rat),
|
||||
method(EnvRef, add_firefly),
|
||||
method(EnvRef, get_meta),
|
||||
method(EnvRef, get_node_timer),
|
||||
method(EnvRef, get_player_by_name),
|
||||
method(EnvRef, get_objects_inside_radius),
|
||||
method(EnvRef, set_timeofday),
|
||||
@@ -3757,6 +3928,10 @@ class LuaPseudoRandom
|
||||
min = luaL_checkinteger(L, 2);
|
||||
if(!lua_isnil(L, 3))
|
||||
max = luaL_checkinteger(L, 3);
|
||||
if(max < min){
|
||||
errorstream<<"PseudoRandom.next(): max="<<max<<" min="<<min<<std::endl;
|
||||
throw LuaError(L, "PseudoRandom.next(): max < min");
|
||||
}
|
||||
if(max - min != 32767 && max - min > 32767/5)
|
||||
throw LuaError(L, "PseudoRandom.next() max-min is not 32767 and is > 32768/5. This is disallowed due to the bad random distribution the implementation would otherwise make.");
|
||||
PseudoRandom &pseudo = o->m_pseudo;
|
||||
@@ -4398,6 +4573,9 @@ static int l_get_inventory(lua_State *L)
|
||||
lua_getfield(L, 1, "pos");
|
||||
v3s16 pos = check_v3s16(L, -1);
|
||||
loc.setNodeMeta(pos);
|
||||
} else if(type == "detached"){
|
||||
std::string name = checkstringfield(L, 1, "name");
|
||||
loc.setDetached(name);
|
||||
}
|
||||
|
||||
if(get_server(L)->getInventory(loc) != NULL)
|
||||
@@ -4407,6 +4585,20 @@ static int l_get_inventory(lua_State *L)
|
||||
return 1;
|
||||
}
|
||||
|
||||
// create_detached_inventory_raw(name)
|
||||
static int l_create_detached_inventory_raw(lua_State *L)
|
||||
{
|
||||
const char *name = luaL_checkstring(L, 1);
|
||||
if(get_server(L)->createDetachedInventory(name) != NULL){
|
||||
InventoryLocation loc;
|
||||
loc.setDetached(name);
|
||||
InvRef::create(L, loc);
|
||||
}else{
|
||||
lua_pushnil(L);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// get_dig_params(groups, tool_capabilities[, time_from_last_punch])
|
||||
static int l_get_dig_params(lua_State *L)
|
||||
{
|
||||
@@ -4461,6 +4653,60 @@ static int l_get_modpath(lua_State *L)
|
||||
return 1;
|
||||
}
|
||||
|
||||
// get_modnames()
|
||||
// the returned list is sorted alphabetically for you
|
||||
static int l_get_modnames(lua_State *L)
|
||||
{
|
||||
// Get a list of mods
|
||||
core::list<std::string> mods_unsorted, mods_sorted;
|
||||
get_server(L)->getModNames(mods_unsorted);
|
||||
|
||||
// Take unsorted items from mods_unsorted and sort them into
|
||||
// mods_sorted; not great performance but the number of mods on a
|
||||
// server will likely be small.
|
||||
for(core::list<std::string>::Iterator i = mods_unsorted.begin();
|
||||
i != mods_unsorted.end(); i++)
|
||||
{
|
||||
bool added = false;
|
||||
for(core::list<std::string>::Iterator x = mods_sorted.begin();
|
||||
x != mods_unsorted.end(); x++)
|
||||
{
|
||||
// I doubt anybody using Minetest will be using
|
||||
// anything not ASCII based :)
|
||||
if((*i).compare(*x) <= 0)
|
||||
{
|
||||
mods_sorted.insert_before(x, *i);
|
||||
added = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!added)
|
||||
mods_sorted.push_back(*i);
|
||||
}
|
||||
|
||||
// Get the table insertion function from Lua.
|
||||
lua_getglobal(L, "table");
|
||||
lua_getfield(L, -1, "insert");
|
||||
int insertion_func = lua_gettop(L);
|
||||
|
||||
// Package them up for Lua
|
||||
lua_newtable(L);
|
||||
int new_table = lua_gettop(L);
|
||||
core::list<std::string>::Iterator i = mods_sorted.begin();
|
||||
while(i != mods_sorted.end())
|
||||
{
|
||||
lua_pushvalue(L, insertion_func);
|
||||
lua_pushvalue(L, new_table);
|
||||
lua_pushstring(L, (*i).c_str());
|
||||
if(lua_pcall(L, 2, 0, 0) != 0)
|
||||
{
|
||||
script_error(L, "error: %s", lua_tostring(L, -1));
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// get_worldpath()
|
||||
static int l_get_worldpath(lua_State *L)
|
||||
{
|
||||
@@ -4613,6 +4859,55 @@ static int l_get_craft_recipe(lua_State *L)
|
||||
return 1;
|
||||
}
|
||||
|
||||
// rollback_get_last_node_actor(p, range, seconds) -> actor, p, seconds
|
||||
static int l_rollback_get_last_node_actor(lua_State *L)
|
||||
{
|
||||
v3s16 p = read_v3s16(L, 1);
|
||||
int range = luaL_checknumber(L, 2);
|
||||
int seconds = luaL_checknumber(L, 3);
|
||||
Server *server = get_server(L);
|
||||
IRollbackManager *rollback = server->getRollbackManager();
|
||||
v3s16 act_p;
|
||||
int act_seconds = 0;
|
||||
std::string actor = rollback->getLastNodeActor(p, range, seconds, &act_p, &act_seconds);
|
||||
lua_pushstring(L, actor.c_str());
|
||||
push_v3s16(L, act_p);
|
||||
lua_pushnumber(L, act_seconds);
|
||||
return 3;
|
||||
}
|
||||
|
||||
// rollback_revert_actions_by(actor, seconds) -> bool, log messages
|
||||
static int l_rollback_revert_actions_by(lua_State *L)
|
||||
{
|
||||
std::string actor = luaL_checkstring(L, 1);
|
||||
int seconds = luaL_checknumber(L, 2);
|
||||
Server *server = get_server(L);
|
||||
IRollbackManager *rollback = server->getRollbackManager();
|
||||
std::list<RollbackAction> actions = rollback->getRevertActions(actor, seconds);
|
||||
std::list<std::string> log;
|
||||
bool success = server->rollbackRevertActions(actions, &log);
|
||||
// Push boolean result
|
||||
lua_pushboolean(L, success);
|
||||
// Get the table insert function and push the log table
|
||||
lua_getglobal(L, "table");
|
||||
lua_getfield(L, -1, "insert");
|
||||
int table_insert = lua_gettop(L);
|
||||
lua_newtable(L);
|
||||
int table = lua_gettop(L);
|
||||
for(std::list<std::string>::const_iterator i = log.begin();
|
||||
i != log.end(); i++)
|
||||
{
|
||||
lua_pushvalue(L, table_insert);
|
||||
lua_pushvalue(L, table);
|
||||
lua_pushstring(L, i->c_str());
|
||||
if(lua_pcall(L, 2, 0, 0))
|
||||
script_error(L, "error: %s", lua_tostring(L, -1));
|
||||
}
|
||||
lua_remove(L, -2); // Remove table
|
||||
lua_remove(L, -2); // Remove insert
|
||||
return 2;
|
||||
}
|
||||
|
||||
static const struct luaL_Reg minetest_f [] = {
|
||||
{"debug", l_debug},
|
||||
{"log", l_log},
|
||||
@@ -4626,10 +4921,12 @@ static const struct luaL_Reg minetest_f [] = {
|
||||
{"chat_send_player", l_chat_send_player},
|
||||
{"get_player_privs", l_get_player_privs},
|
||||
{"get_inventory", l_get_inventory},
|
||||
{"create_detached_inventory_raw", l_create_detached_inventory_raw},
|
||||
{"get_dig_params", l_get_dig_params},
|
||||
{"get_hit_params", l_get_hit_params},
|
||||
{"get_current_modname", l_get_current_modname},
|
||||
{"get_modpath", l_get_modpath},
|
||||
{"get_modnames", l_get_modnames},
|
||||
{"get_worldpath", l_get_worldpath},
|
||||
{"sound_play", l_sound_play},
|
||||
{"sound_stop", l_sound_stop},
|
||||
@@ -4638,6 +4935,8 @@ static const struct luaL_Reg minetest_f [] = {
|
||||
{"notify_authentication_modified", l_notify_authentication_modified},
|
||||
{"get_craft_result", l_get_craft_result},
|
||||
{"get_craft_recipe", l_get_craft_recipe},
|
||||
{"rollback_get_last_node_actor", l_rollback_get_last_node_actor},
|
||||
{"rollback_revert_actions_by", l_rollback_revert_actions_by},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
@@ -4674,6 +4973,7 @@ void scriptapi_export(lua_State *L, Server *server)
|
||||
LuaItemStack::Register(L);
|
||||
InvRef::Register(L);
|
||||
NodeMetaRef::Register(L);
|
||||
NodeTimerRef::Register(L);
|
||||
ObjectRef::Register(L);
|
||||
EnvRef::Register(L);
|
||||
LuaPseudoRandom::Register(L);
|
||||
@@ -5083,21 +5383,6 @@ void scriptapi_on_leaveplayer(lua_State *L, ServerActiveObject *player)
|
||||
scriptapi_run_callbacks(L, 1, RUN_CALLBACKS_MODE_FIRST);
|
||||
}
|
||||
|
||||
void scriptapi_get_creative_inventory(lua_State *L, ServerActiveObject *player)
|
||||
{
|
||||
realitycheck(L);
|
||||
assert(lua_checkstack(L, 20));
|
||||
StackUnroller stack_unroller(L);
|
||||
|
||||
Inventory *inv = player->getInventory();
|
||||
assert(inv);
|
||||
|
||||
lua_getglobal(L, "minetest");
|
||||
lua_getfield(L, -1, "creative_inventory");
|
||||
luaL_checktype(L, -1, LUA_TTABLE);
|
||||
inventory_set_list_from_lua(inv, "main", L, -1, PLAYER_INVENTORY_SIZE);
|
||||
}
|
||||
|
||||
static void get_auth_handler(lua_State *L)
|
||||
{
|
||||
lua_getglobal(L, "minetest");
|
||||
@@ -5183,6 +5468,40 @@ bool scriptapi_set_password(lua_State *L, const std::string &playername,
|
||||
return lua_toboolean(L, -1);
|
||||
}
|
||||
|
||||
/*
|
||||
player
|
||||
*/
|
||||
|
||||
void scriptapi_on_player_receive_fields(lua_State *L,
|
||||
ServerActiveObject *player,
|
||||
const std::string &formname,
|
||||
const std::map<std::string, std::string> &fields)
|
||||
{
|
||||
realitycheck(L);
|
||||
assert(lua_checkstack(L, 20));
|
||||
StackUnroller stack_unroller(L);
|
||||
|
||||
// Get minetest.registered_on_chat_messages
|
||||
lua_getglobal(L, "minetest");
|
||||
lua_getfield(L, -1, "registered_on_player_receive_fields");
|
||||
// Call callbacks
|
||||
// param 1
|
||||
objectref_get_or_create(L, player);
|
||||
// param 2
|
||||
lua_pushstring(L, formname.c_str());
|
||||
// param 3
|
||||
lua_newtable(L);
|
||||
for(std::map<std::string, std::string>::const_iterator
|
||||
i = fields.begin(); i != fields.end(); i++){
|
||||
const std::string &name = i->first;
|
||||
const std::string &value = i->second;
|
||||
lua_pushstring(L, name.c_str());
|
||||
lua_pushlstring(L, value.c_str(), value.size());
|
||||
lua_settable(L, -3);
|
||||
}
|
||||
scriptapi_run_callbacks(L, 3, RUN_CALLBACKS_MODE_OR_SC);
|
||||
}
|
||||
|
||||
/*
|
||||
item callbacks and node callbacks
|
||||
*/
|
||||
@@ -5393,6 +5712,29 @@ void scriptapi_node_after_destruct(lua_State *L, v3s16 p, MapNode node)
|
||||
script_error(L, "error: %s", lua_tostring(L, -1));
|
||||
}
|
||||
|
||||
bool scriptapi_node_on_timer(lua_State *L, v3s16 p, MapNode node, f32 dtime)
|
||||
{
|
||||
realitycheck(L);
|
||||
assert(lua_checkstack(L, 20));
|
||||
StackUnroller stack_unroller(L);
|
||||
|
||||
INodeDefManager *ndef = get_server(L)->ndef();
|
||||
|
||||
// Push callback function on stack
|
||||
if(!get_item_callback(L, ndef->get(node).name.c_str(), "on_timer"))
|
||||
return false;
|
||||
|
||||
// Call function
|
||||
push_v3s16(L, p);
|
||||
lua_pushnumber(L,dtime);
|
||||
if(lua_pcall(L, 2, 1, 0))
|
||||
script_error(L, "error: %s", lua_tostring(L, -1));
|
||||
if(lua_isboolean(L,-1) && lua_toboolean(L,-1) == true)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void scriptapi_node_on_receive_fields(lua_State *L, v3s16 p,
|
||||
const std::string &formname,
|
||||
const std::map<std::string, std::string> &fields,
|
||||
@@ -5434,7 +5776,134 @@ void scriptapi_node_on_receive_fields(lua_State *L, v3s16 p,
|
||||
script_error(L, "error: %s", lua_tostring(L, -1));
|
||||
}
|
||||
|
||||
void scriptapi_node_on_metadata_inventory_move(lua_State *L, v3s16 p,
|
||||
/*
|
||||
Node metadata inventory callbacks
|
||||
*/
|
||||
|
||||
// Return number of accepted items to be moved
|
||||
int scriptapi_nodemeta_inventory_allow_move(lua_State *L, v3s16 p,
|
||||
const std::string &from_list, int from_index,
|
||||
const std::string &to_list, int to_index,
|
||||
int count, ServerActiveObject *player)
|
||||
{
|
||||
realitycheck(L);
|
||||
assert(lua_checkstack(L, 20));
|
||||
StackUnroller stack_unroller(L);
|
||||
|
||||
INodeDefManager *ndef = get_server(L)->ndef();
|
||||
|
||||
// If node doesn't exist, we don't know what callback to call
|
||||
MapNode node = get_env(L)->getMap().getNodeNoEx(p);
|
||||
if(node.getContent() == CONTENT_IGNORE)
|
||||
return 0;
|
||||
|
||||
// Push callback function on stack
|
||||
if(!get_item_callback(L, ndef->get(node).name.c_str(),
|
||||
"allow_metadata_inventory_move"))
|
||||
return count;
|
||||
|
||||
// function(pos, from_list, from_index, to_list, to_index, count, player)
|
||||
// pos
|
||||
push_v3s16(L, p);
|
||||
// from_list
|
||||
lua_pushstring(L, from_list.c_str());
|
||||
// from_index
|
||||
lua_pushinteger(L, from_index + 1);
|
||||
// to_list
|
||||
lua_pushstring(L, to_list.c_str());
|
||||
// to_index
|
||||
lua_pushinteger(L, to_index + 1);
|
||||
// count
|
||||
lua_pushinteger(L, count);
|
||||
// player
|
||||
objectref_get_or_create(L, player);
|
||||
if(lua_pcall(L, 7, 1, 0))
|
||||
script_error(L, "error: %s", lua_tostring(L, -1));
|
||||
if(!lua_isnumber(L, -1))
|
||||
throw LuaError(L, "allow_metadata_inventory_move should return a number");
|
||||
return luaL_checkinteger(L, -1);
|
||||
}
|
||||
|
||||
// Return number of accepted items to be put
|
||||
int scriptapi_nodemeta_inventory_allow_put(lua_State *L, v3s16 p,
|
||||
const std::string &listname, int index, ItemStack &stack,
|
||||
ServerActiveObject *player)
|
||||
{
|
||||
realitycheck(L);
|
||||
assert(lua_checkstack(L, 20));
|
||||
StackUnroller stack_unroller(L);
|
||||
|
||||
INodeDefManager *ndef = get_server(L)->ndef();
|
||||
|
||||
// If node doesn't exist, we don't know what callback to call
|
||||
MapNode node = get_env(L)->getMap().getNodeNoEx(p);
|
||||
if(node.getContent() == CONTENT_IGNORE)
|
||||
return 0;
|
||||
|
||||
// Push callback function on stack
|
||||
if(!get_item_callback(L, ndef->get(node).name.c_str(),
|
||||
"allow_metadata_inventory_put"))
|
||||
return stack.count;
|
||||
|
||||
// Call function(pos, listname, index, stack, player)
|
||||
// pos
|
||||
push_v3s16(L, p);
|
||||
// listname
|
||||
lua_pushstring(L, listname.c_str());
|
||||
// index
|
||||
lua_pushinteger(L, index + 1);
|
||||
// stack
|
||||
LuaItemStack::create(L, stack);
|
||||
// player
|
||||
objectref_get_or_create(L, player);
|
||||
if(lua_pcall(L, 5, 1, 0))
|
||||
script_error(L, "error: %s", lua_tostring(L, -1));
|
||||
if(!lua_isnumber(L, -1))
|
||||
throw LuaError(L, "allow_metadata_inventory_put should return a number");
|
||||
return luaL_checkinteger(L, -1);
|
||||
}
|
||||
|
||||
// Return number of accepted items to be taken
|
||||
int scriptapi_nodemeta_inventory_allow_take(lua_State *L, v3s16 p,
|
||||
const std::string &listname, int index, ItemStack &stack,
|
||||
ServerActiveObject *player)
|
||||
{
|
||||
realitycheck(L);
|
||||
assert(lua_checkstack(L, 20));
|
||||
StackUnroller stack_unroller(L);
|
||||
|
||||
INodeDefManager *ndef = get_server(L)->ndef();
|
||||
|
||||
// If node doesn't exist, we don't know what callback to call
|
||||
MapNode node = get_env(L)->getMap().getNodeNoEx(p);
|
||||
if(node.getContent() == CONTENT_IGNORE)
|
||||
return 0;
|
||||
|
||||
// Push callback function on stack
|
||||
if(!get_item_callback(L, ndef->get(node).name.c_str(),
|
||||
"allow_metadata_inventory_take"))
|
||||
return stack.count;
|
||||
|
||||
// Call function(pos, listname, index, count, player)
|
||||
// pos
|
||||
push_v3s16(L, p);
|
||||
// listname
|
||||
lua_pushstring(L, listname.c_str());
|
||||
// index
|
||||
lua_pushinteger(L, index + 1);
|
||||
// stack
|
||||
LuaItemStack::create(L, stack);
|
||||
// player
|
||||
objectref_get_or_create(L, player);
|
||||
if(lua_pcall(L, 5, 1, 0))
|
||||
script_error(L, "error: %s", lua_tostring(L, -1));
|
||||
if(!lua_isnumber(L, -1))
|
||||
throw LuaError(L, "allow_metadata_inventory_take should return a number");
|
||||
return luaL_checkinteger(L, -1);
|
||||
}
|
||||
|
||||
// Report moved items
|
||||
void scriptapi_nodemeta_inventory_on_move(lua_State *L, v3s16 p,
|
||||
const std::string &from_list, int from_index,
|
||||
const std::string &to_list, int to_index,
|
||||
int count, ServerActiveObject *player)
|
||||
@@ -5456,18 +5925,26 @@ void scriptapi_node_on_metadata_inventory_move(lua_State *L, v3s16 p,
|
||||
return;
|
||||
|
||||
// function(pos, from_list, from_index, to_list, to_index, count, player)
|
||||
// pos
|
||||
push_v3s16(L, p);
|
||||
// from_list
|
||||
lua_pushstring(L, from_list.c_str());
|
||||
// from_index
|
||||
lua_pushinteger(L, from_index + 1);
|
||||
// to_list
|
||||
lua_pushstring(L, to_list.c_str());
|
||||
// to_index
|
||||
lua_pushinteger(L, to_index + 1);
|
||||
// count
|
||||
lua_pushinteger(L, count);
|
||||
// player
|
||||
objectref_get_or_create(L, player);
|
||||
if(lua_pcall(L, 7, 0, 0))
|
||||
script_error(L, "error: %s", lua_tostring(L, -1));
|
||||
}
|
||||
|
||||
ItemStack scriptapi_node_on_metadata_inventory_offer(lua_State *L, v3s16 p,
|
||||
// Report put items
|
||||
void scriptapi_nodemeta_inventory_on_put(lua_State *L, v3s16 p,
|
||||
const std::string &listname, int index, ItemStack &stack,
|
||||
ServerActiveObject *player)
|
||||
{
|
||||
@@ -5480,26 +5957,31 @@ ItemStack scriptapi_node_on_metadata_inventory_offer(lua_State *L, v3s16 p,
|
||||
// If node doesn't exist, we don't know what callback to call
|
||||
MapNode node = get_env(L)->getMap().getNodeNoEx(p);
|
||||
if(node.getContent() == CONTENT_IGNORE)
|
||||
return stack;
|
||||
return;
|
||||
|
||||
// Push callback function on stack
|
||||
if(!get_item_callback(L, ndef->get(node).name.c_str(),
|
||||
"on_metadata_inventory_offer"))
|
||||
return stack;
|
||||
"on_metadata_inventory_put"))
|
||||
return;
|
||||
|
||||
// Call function(pos, listname, index, stack, player)
|
||||
// pos
|
||||
push_v3s16(L, p);
|
||||
// listname
|
||||
lua_pushstring(L, listname.c_str());
|
||||
// index
|
||||
lua_pushinteger(L, index + 1);
|
||||
// stack
|
||||
LuaItemStack::create(L, stack);
|
||||
// player
|
||||
objectref_get_or_create(L, player);
|
||||
if(lua_pcall(L, 5, 1, 0))
|
||||
if(lua_pcall(L, 5, 0, 0))
|
||||
script_error(L, "error: %s", lua_tostring(L, -1));
|
||||
return read_item(L, -1);
|
||||
}
|
||||
|
||||
ItemStack scriptapi_node_on_metadata_inventory_take(lua_State *L, v3s16 p,
|
||||
const std::string &listname, int index, int count,
|
||||
// Report taken items
|
||||
void scriptapi_nodemeta_inventory_on_take(lua_State *L, v3s16 p,
|
||||
const std::string &listname, int index, ItemStack &stack,
|
||||
ServerActiveObject *player)
|
||||
{
|
||||
realitycheck(L);
|
||||
@@ -5511,22 +5993,276 @@ ItemStack scriptapi_node_on_metadata_inventory_take(lua_State *L, v3s16 p,
|
||||
// If node doesn't exist, we don't know what callback to call
|
||||
MapNode node = get_env(L)->getMap().getNodeNoEx(p);
|
||||
if(node.getContent() == CONTENT_IGNORE)
|
||||
return ItemStack();
|
||||
return;
|
||||
|
||||
// Push callback function on stack
|
||||
if(!get_item_callback(L, ndef->get(node).name.c_str(),
|
||||
"on_metadata_inventory_take"))
|
||||
return ItemStack();
|
||||
return;
|
||||
|
||||
// Call function(pos, listname, index, count, player)
|
||||
// Call function(pos, listname, index, stack, player)
|
||||
// pos
|
||||
push_v3s16(L, p);
|
||||
// listname
|
||||
lua_pushstring(L, listname.c_str());
|
||||
// index
|
||||
lua_pushinteger(L, index + 1);
|
||||
// stack
|
||||
LuaItemStack::create(L, stack);
|
||||
// player
|
||||
objectref_get_or_create(L, player);
|
||||
if(lua_pcall(L, 5, 0, 0))
|
||||
script_error(L, "error: %s", lua_tostring(L, -1));
|
||||
}
|
||||
|
||||
/*
|
||||
Detached inventory callbacks
|
||||
*/
|
||||
|
||||
// Retrieves minetest.detached_inventories[name][callbackname]
|
||||
// If that is nil or on error, return false and stack is unchanged
|
||||
// If that is a function, returns true and pushes the
|
||||
// function onto the stack
|
||||
static bool get_detached_inventory_callback(lua_State *L,
|
||||
const std::string &name, const char *callbackname)
|
||||
{
|
||||
lua_getglobal(L, "minetest");
|
||||
lua_getfield(L, -1, "detached_inventories");
|
||||
lua_remove(L, -2);
|
||||
luaL_checktype(L, -1, LUA_TTABLE);
|
||||
lua_getfield(L, -1, name.c_str());
|
||||
lua_remove(L, -2);
|
||||
// Should be a table
|
||||
if(lua_type(L, -1) != LUA_TTABLE)
|
||||
{
|
||||
errorstream<<"Item \""<<name<<"\" not defined"<<std::endl;
|
||||
lua_pop(L, 1);
|
||||
return false;
|
||||
}
|
||||
lua_getfield(L, -1, callbackname);
|
||||
lua_remove(L, -2);
|
||||
// Should be a function or nil
|
||||
if(lua_type(L, -1) == LUA_TFUNCTION)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if(lua_isnil(L, -1))
|
||||
{
|
||||
lua_pop(L, 1);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
errorstream<<"Detached inventory \""<<name<<"\" callback \""
|
||||
<<callbackname<<"\" is not a function"<<std::endl;
|
||||
lua_pop(L, 1);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Return number of accepted items to be moved
|
||||
int scriptapi_detached_inventory_allow_move(lua_State *L,
|
||||
const std::string &name,
|
||||
const std::string &from_list, int from_index,
|
||||
const std::string &to_list, int to_index,
|
||||
int count, ServerActiveObject *player)
|
||||
{
|
||||
realitycheck(L);
|
||||
assert(lua_checkstack(L, 20));
|
||||
StackUnroller stack_unroller(L);
|
||||
|
||||
// Push callback function on stack
|
||||
if(!get_detached_inventory_callback(L, name, "allow_move"))
|
||||
return count;
|
||||
|
||||
// function(inv, from_list, from_index, to_list, to_index, count, player)
|
||||
// inv
|
||||
InventoryLocation loc;
|
||||
loc.setDetached(name);
|
||||
InvRef::create(L, loc);
|
||||
// from_list
|
||||
lua_pushstring(L, from_list.c_str());
|
||||
// from_index
|
||||
lua_pushinteger(L, from_index + 1);
|
||||
// to_list
|
||||
lua_pushstring(L, to_list.c_str());
|
||||
// to_index
|
||||
lua_pushinteger(L, to_index + 1);
|
||||
// count
|
||||
lua_pushinteger(L, count);
|
||||
// player
|
||||
objectref_get_or_create(L, player);
|
||||
if(lua_pcall(L, 7, 1, 0))
|
||||
script_error(L, "error: %s", lua_tostring(L, -1));
|
||||
if(!lua_isnumber(L, -1))
|
||||
throw LuaError(L, "allow_move should return a number");
|
||||
return luaL_checkinteger(L, -1);
|
||||
}
|
||||
|
||||
// Return number of accepted items to be put
|
||||
int scriptapi_detached_inventory_allow_put(lua_State *L,
|
||||
const std::string &name,
|
||||
const std::string &listname, int index, ItemStack &stack,
|
||||
ServerActiveObject *player)
|
||||
{
|
||||
realitycheck(L);
|
||||
assert(lua_checkstack(L, 20));
|
||||
StackUnroller stack_unroller(L);
|
||||
|
||||
// Push callback function on stack
|
||||
if(!get_detached_inventory_callback(L, name, "allow_put"))
|
||||
return stack.count; // All will be accepted
|
||||
|
||||
// Call function(inv, listname, index, stack, player)
|
||||
// inv
|
||||
InventoryLocation loc;
|
||||
loc.setDetached(name);
|
||||
InvRef::create(L, loc);
|
||||
// listname
|
||||
lua_pushstring(L, listname.c_str());
|
||||
// index
|
||||
lua_pushinteger(L, index + 1);
|
||||
// stack
|
||||
LuaItemStack::create(L, stack);
|
||||
// player
|
||||
objectref_get_or_create(L, player);
|
||||
if(lua_pcall(L, 5, 1, 0))
|
||||
script_error(L, "error: %s", lua_tostring(L, -1));
|
||||
return read_item(L, -1);
|
||||
if(!lua_isnumber(L, -1))
|
||||
throw LuaError(L, "allow_put should return a number");
|
||||
return luaL_checkinteger(L, -1);
|
||||
}
|
||||
|
||||
// Return number of accepted items to be taken
|
||||
int scriptapi_detached_inventory_allow_take(lua_State *L,
|
||||
const std::string &name,
|
||||
const std::string &listname, int index, ItemStack &stack,
|
||||
ServerActiveObject *player)
|
||||
{
|
||||
realitycheck(L);
|
||||
assert(lua_checkstack(L, 20));
|
||||
StackUnroller stack_unroller(L);
|
||||
|
||||
// Push callback function on stack
|
||||
if(!get_detached_inventory_callback(L, name, "allow_take"))
|
||||
return stack.count; // All will be accepted
|
||||
|
||||
// Call function(inv, listname, index, stack, player)
|
||||
// inv
|
||||
InventoryLocation loc;
|
||||
loc.setDetached(name);
|
||||
InvRef::create(L, loc);
|
||||
// listname
|
||||
lua_pushstring(L, listname.c_str());
|
||||
// index
|
||||
lua_pushinteger(L, index + 1);
|
||||
// stack
|
||||
LuaItemStack::create(L, stack);
|
||||
// player
|
||||
objectref_get_or_create(L, player);
|
||||
if(lua_pcall(L, 5, 1, 0))
|
||||
script_error(L, "error: %s", lua_tostring(L, -1));
|
||||
if(!lua_isnumber(L, -1))
|
||||
throw LuaError(L, "allow_take should return a number");
|
||||
return luaL_checkinteger(L, -1);
|
||||
}
|
||||
|
||||
// Report moved items
|
||||
void scriptapi_detached_inventory_on_move(lua_State *L,
|
||||
const std::string &name,
|
||||
const std::string &from_list, int from_index,
|
||||
const std::string &to_list, int to_index,
|
||||
int count, ServerActiveObject *player)
|
||||
{
|
||||
realitycheck(L);
|
||||
assert(lua_checkstack(L, 20));
|
||||
StackUnroller stack_unroller(L);
|
||||
|
||||
// Push callback function on stack
|
||||
if(!get_detached_inventory_callback(L, name, "on_move"))
|
||||
return;
|
||||
|
||||
// function(inv, from_list, from_index, to_list, to_index, count, player)
|
||||
// inv
|
||||
InventoryLocation loc;
|
||||
loc.setDetached(name);
|
||||
InvRef::create(L, loc);
|
||||
// from_list
|
||||
lua_pushstring(L, from_list.c_str());
|
||||
// from_index
|
||||
lua_pushinteger(L, from_index + 1);
|
||||
// to_list
|
||||
lua_pushstring(L, to_list.c_str());
|
||||
// to_index
|
||||
lua_pushinteger(L, to_index + 1);
|
||||
// count
|
||||
lua_pushinteger(L, count);
|
||||
// player
|
||||
objectref_get_or_create(L, player);
|
||||
if(lua_pcall(L, 7, 0, 0))
|
||||
script_error(L, "error: %s", lua_tostring(L, -1));
|
||||
}
|
||||
|
||||
// Report put items
|
||||
void scriptapi_detached_inventory_on_put(lua_State *L,
|
||||
const std::string &name,
|
||||
const std::string &listname, int index, ItemStack &stack,
|
||||
ServerActiveObject *player)
|
||||
{
|
||||
realitycheck(L);
|
||||
assert(lua_checkstack(L, 20));
|
||||
StackUnroller stack_unroller(L);
|
||||
|
||||
// Push callback function on stack
|
||||
if(!get_detached_inventory_callback(L, name, "on_put"))
|
||||
return;
|
||||
|
||||
// Call function(inv, listname, index, stack, player)
|
||||
// inv
|
||||
InventoryLocation loc;
|
||||
loc.setDetached(name);
|
||||
InvRef::create(L, loc);
|
||||
// listname
|
||||
lua_pushstring(L, listname.c_str());
|
||||
// index
|
||||
lua_pushinteger(L, index + 1);
|
||||
// stack
|
||||
LuaItemStack::create(L, stack);
|
||||
// player
|
||||
objectref_get_or_create(L, player);
|
||||
if(lua_pcall(L, 5, 0, 0))
|
||||
script_error(L, "error: %s", lua_tostring(L, -1));
|
||||
}
|
||||
|
||||
// Report taken items
|
||||
void scriptapi_detached_inventory_on_take(lua_State *L,
|
||||
const std::string &name,
|
||||
const std::string &listname, int index, ItemStack &stack,
|
||||
ServerActiveObject *player)
|
||||
{
|
||||
realitycheck(L);
|
||||
assert(lua_checkstack(L, 20));
|
||||
StackUnroller stack_unroller(L);
|
||||
|
||||
// Push callback function on stack
|
||||
if(!get_detached_inventory_callback(L, name, "on_take"))
|
||||
return;
|
||||
|
||||
// Call function(inv, listname, index, stack, player)
|
||||
// inv
|
||||
InventoryLocation loc;
|
||||
loc.setDetached(name);
|
||||
InvRef::create(L, loc);
|
||||
// listname
|
||||
lua_pushstring(L, listname.c_str());
|
||||
// index
|
||||
lua_pushinteger(L, index + 1);
|
||||
// stack
|
||||
LuaItemStack::create(L, stack);
|
||||
// player
|
||||
objectref_get_or_create(L, player);
|
||||
if(lua_pcall(L, 5, 0, 0))
|
||||
script_error(L, "error: %s", lua_tostring(L, -1));
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -61,7 +61,6 @@ void scriptapi_on_dieplayer(lua_State *L, ServerActiveObject *player);
|
||||
bool scriptapi_on_respawnplayer(lua_State *L, ServerActiveObject *player);
|
||||
void scriptapi_on_joinplayer(lua_State *L, ServerActiveObject *player);
|
||||
void scriptapi_on_leaveplayer(lua_State *L, ServerActiveObject *player);
|
||||
void scriptapi_get_creative_inventory(lua_State *L, ServerActiveObject *player);
|
||||
bool scriptapi_get_auth(lua_State *L, const std::string &playername,
|
||||
std::string *dst_password, std::set<std::string> *dst_privs);
|
||||
void scriptapi_create_auth(lua_State *L, const std::string &playername,
|
||||
@@ -69,6 +68,12 @@ void scriptapi_create_auth(lua_State *L, const std::string &playername,
|
||||
bool scriptapi_set_password(lua_State *L, const std::string &playername,
|
||||
const std::string &password);
|
||||
|
||||
/* player */
|
||||
void scriptapi_on_player_receive_fields(lua_State *L,
|
||||
ServerActiveObject *player,
|
||||
const std::string &formname,
|
||||
const std::map<std::string, std::string> &fields);
|
||||
|
||||
/* item callbacks */
|
||||
bool scriptapi_item_on_drop(lua_State *L, ItemStack &item,
|
||||
ServerActiveObject *dropper, v3f pos);
|
||||
@@ -88,23 +93,74 @@ void scriptapi_node_on_construct(lua_State *L, v3s16 p, MapNode node);
|
||||
void scriptapi_node_on_destruct(lua_State *L, v3s16 p, MapNode node);
|
||||
// Node post-destructor
|
||||
void scriptapi_node_after_destruct(lua_State *L, v3s16 p, MapNode node);
|
||||
// Node Timer event
|
||||
bool scriptapi_node_on_timer(lua_State *L, v3s16 p, MapNode node, f32 dtime);
|
||||
// Called when a metadata form returns values
|
||||
void scriptapi_node_on_receive_fields(lua_State *L, v3s16 p,
|
||||
const std::string &formname,
|
||||
const std::map<std::string, std::string> &fields,
|
||||
ServerActiveObject *sender);
|
||||
// Moves items
|
||||
void scriptapi_node_on_metadata_inventory_move(lua_State *L, v3s16 p,
|
||||
|
||||
/* Node metadata inventory callbacks */
|
||||
// Return number of accepted items to be moved
|
||||
int scriptapi_nodemeta_inventory_allow_move(lua_State *L, v3s16 p,
|
||||
const std::string &from_list, int from_index,
|
||||
const std::string &to_list, int to_index,
|
||||
int count, ServerActiveObject *player);
|
||||
// Inserts items, returns rejected items
|
||||
ItemStack scriptapi_node_on_metadata_inventory_offer(lua_State *L, v3s16 p,
|
||||
// Return number of accepted items to be put
|
||||
int scriptapi_nodemeta_inventory_allow_put(lua_State *L, v3s16 p,
|
||||
const std::string &listname, int index, ItemStack &stack,
|
||||
ServerActiveObject *player);
|
||||
// Takes items, returns taken items
|
||||
ItemStack scriptapi_node_on_metadata_inventory_take(lua_State *L, v3s16 p,
|
||||
const std::string &listname, int index, int count,
|
||||
// Return number of accepted items to be taken
|
||||
int scriptapi_nodemeta_inventory_allow_take(lua_State *L, v3s16 p,
|
||||
const std::string &listname, int index, ItemStack &stack,
|
||||
ServerActiveObject *player);
|
||||
// Report moved items
|
||||
void scriptapi_nodemeta_inventory_on_move(lua_State *L, v3s16 p,
|
||||
const std::string &from_list, int from_index,
|
||||
const std::string &to_list, int to_index,
|
||||
int count, ServerActiveObject *player);
|
||||
// Report put items
|
||||
void scriptapi_nodemeta_inventory_on_put(lua_State *L, v3s16 p,
|
||||
const std::string &listname, int index, ItemStack &stack,
|
||||
ServerActiveObject *player);
|
||||
// Report taken items
|
||||
void scriptapi_nodemeta_inventory_on_take(lua_State *L, v3s16 p,
|
||||
const std::string &listname, int index, ItemStack &stack,
|
||||
ServerActiveObject *player);
|
||||
|
||||
/* Detached inventory callbacks */
|
||||
// Return number of accepted items to be moved
|
||||
int scriptapi_detached_inventory_allow_move(lua_State *L,
|
||||
const std::string &name,
|
||||
const std::string &from_list, int from_index,
|
||||
const std::string &to_list, int to_index,
|
||||
int count, ServerActiveObject *player);
|
||||
// Return number of accepted items to be put
|
||||
int scriptapi_detached_inventory_allow_put(lua_State *L,
|
||||
const std::string &name,
|
||||
const std::string &listname, int index, ItemStack &stack,
|
||||
ServerActiveObject *player);
|
||||
// Return number of accepted items to be taken
|
||||
int scriptapi_detached_inventory_allow_take(lua_State *L,
|
||||
const std::string &name,
|
||||
const std::string &listname, int index, ItemStack &stack,
|
||||
ServerActiveObject *player);
|
||||
// Report moved items
|
||||
void scriptapi_detached_inventory_on_move(lua_State *L,
|
||||
const std::string &name,
|
||||
const std::string &from_list, int from_index,
|
||||
const std::string &to_list, int to_index,
|
||||
int count, ServerActiveObject *player);
|
||||
// Report put items
|
||||
void scriptapi_detached_inventory_on_put(lua_State *L,
|
||||
const std::string &name,
|
||||
const std::string &listname, int index, ItemStack &stack,
|
||||
ServerActiveObject *player);
|
||||
// Report taken items
|
||||
void scriptapi_detached_inventory_on_take(lua_State *L,
|
||||
const std::string &name,
|
||||
const std::string &listname, int index, ItemStack &stack,
|
||||
ServerActiveObject *player);
|
||||
|
||||
/* luaentity */
|
||||
|
||||
@@ -57,9 +57,7 @@ void compressZlib(SharedBuffer<u8> data, std::ostream &os)
|
||||
{
|
||||
z_stream z;
|
||||
const s32 bufsize = 16384;
|
||||
//char input_buffer[bufsize];
|
||||
char output_buffer[bufsize];
|
||||
int input_i = 0;
|
||||
int status = 0;
|
||||
int ret;
|
||||
|
||||
@@ -71,26 +69,16 @@ void compressZlib(SharedBuffer<u8> data, std::ostream &os)
|
||||
if(ret != Z_OK)
|
||||
throw SerializationError("compressZlib: deflateInit failed");
|
||||
|
||||
z.avail_in = 0;
|
||||
|
||||
// Point zlib to our input buffer
|
||||
z.next_in = (Bytef*)&data[0];
|
||||
z.avail_in = data.getSize();
|
||||
// And get all output
|
||||
for(;;)
|
||||
{
|
||||
int flush = Z_NO_FLUSH;
|
||||
z.next_out = (Bytef*)output_buffer;
|
||||
z.avail_out = bufsize;
|
||||
|
||||
if(z.avail_in == 0)
|
||||
{
|
||||
//z.next_in = (char*)&data[input_i];
|
||||
z.next_in = (Bytef*)&data[input_i];
|
||||
z.avail_in = data.getSize() - input_i;
|
||||
input_i += z.avail_in;
|
||||
if(input_i == (int)data.getSize())
|
||||
flush = Z_FINISH;
|
||||
}
|
||||
if(z.avail_in == 0)
|
||||
break;
|
||||
status = deflate(&z, flush);
|
||||
|
||||
status = deflate(&z, Z_FINISH);
|
||||
if(status == Z_NEED_DICT || status == Z_DATA_ERROR
|
||||
|| status == Z_MEM_ERROR)
|
||||
{
|
||||
@@ -100,6 +88,9 @@ void compressZlib(SharedBuffer<u8> data, std::ostream &os)
|
||||
int count = bufsize - z.avail_out;
|
||||
if(count)
|
||||
os.write(output_buffer, count);
|
||||
// This determines zlib has given all output
|
||||
if(status == Z_STREAM_END)
|
||||
break;
|
||||
}
|
||||
|
||||
deflateEnd(&z);
|
||||
|
||||
@@ -58,12 +58,14 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
20: many existing content types translated to extended ones
|
||||
21: dynamic content type allocation
|
||||
22: minerals removed, facedir & wallmounted changed
|
||||
23: NodeTimers, new node metadata format
|
||||
23: new node metadata format
|
||||
24: 16-bit node ids and node timers (never released as stable)
|
||||
25: Improved node timer format
|
||||
*/
|
||||
// This represents an uninitialized or invalid format
|
||||
#define SER_FMT_VER_INVALID 255
|
||||
// Highest supported serialization version
|
||||
#define SER_FMT_VER_HIGHEST 23
|
||||
#define SER_FMT_VER_HIGHEST 25
|
||||
// Lowest supported serialization version
|
||||
#define SER_FMT_VER_LOWEST 0
|
||||
|
||||
|
||||
295
src/server.cpp
295
src/server.cpp
@@ -54,6 +54,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
#include "util/string.h"
|
||||
#include "util/pointedthing.h"
|
||||
#include "util/mathconstants.h"
|
||||
#include "rollback.h"
|
||||
|
||||
#define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")"
|
||||
|
||||
@@ -843,7 +844,7 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime,
|
||||
|
||||
/*timer_result = timer.stop(true);
|
||||
if(timer_result != 0)
|
||||
infostream<<"GetNextBlocks duration: "<<timer_result<<" (!=0)"<<std::endl;*/
|
||||
infostream<<"GetNextBlocks timeout: "<<timer_result<<" (!=0)"<<std::endl;*/
|
||||
}
|
||||
|
||||
void RemoteClient::GotBlock(v3s16 p)
|
||||
@@ -934,6 +935,9 @@ Server::Server(
|
||||
m_env(NULL),
|
||||
m_con(PROTOCOL_ID, 512, CONNECTION_TIMEOUT, this),
|
||||
m_banmanager(path_world+DIR_DELIM+"ipban.txt"),
|
||||
m_rollback(NULL),
|
||||
m_rollback_sink_enabled(true),
|
||||
m_enable_rollback_recording(false),
|
||||
m_lua(NULL),
|
||||
m_itemdef(createItemDefManager()),
|
||||
m_nodedef(createNodeDefManager()),
|
||||
@@ -973,6 +977,10 @@ Server::Server(
|
||||
infostream<<"- config: "<<m_path_config<<std::endl;
|
||||
infostream<<"- game: "<<m_gamespec.path<<std::endl;
|
||||
|
||||
// Create rollback manager
|
||||
std::string rollback_path = m_path_world+DIR_DELIM+"rollback.txt";
|
||||
m_rollback = createRollbackManager(rollback_path, this);
|
||||
|
||||
// Add world mod search path
|
||||
m_modspaths.push_front(m_path_world + DIR_DELIM + "worldmods");
|
||||
// Add addon mod search path
|
||||
@@ -1049,7 +1057,7 @@ Server::Server(
|
||||
|
||||
m_env = new ServerEnvironment(new ServerMap(path_world, this), m_lua,
|
||||
this, this);
|
||||
|
||||
|
||||
// Give environment reference to scripting api
|
||||
scriptapi_add_environment(m_lua, m_env);
|
||||
|
||||
@@ -1152,6 +1160,7 @@ Server::~Server()
|
||||
|
||||
// Delete things in the reverse order of creation
|
||||
delete m_env;
|
||||
delete m_rollback;
|
||||
delete m_event;
|
||||
delete m_itemdef;
|
||||
delete m_nodedef;
|
||||
@@ -1160,6 +1169,15 @@ Server::~Server()
|
||||
// Deinitialize scripting
|
||||
infostream<<"Server: Deinitializing scripting"<<std::endl;
|
||||
script_deinit(m_lua);
|
||||
|
||||
// Delete detached inventories
|
||||
{
|
||||
for(std::map<std::string, Inventory*>::iterator
|
||||
i = m_detached_inventories.begin();
|
||||
i != m_detached_inventories.end(); i++){
|
||||
delete i->second;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Server::start(unsigned short port)
|
||||
@@ -1856,6 +1874,10 @@ void Server::AsyncRunStep()
|
||||
counter = 0.0;
|
||||
|
||||
m_emergethread.trigger();
|
||||
|
||||
// Update m_enable_rollback_recording here too
|
||||
m_enable_rollback_recording =
|
||||
g_settings->getBool("enable_rollback_recording");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2250,10 +2272,13 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
|
||||
// Send inventory
|
||||
UpdateCrafting(peer_id);
|
||||
SendInventory(peer_id);
|
||||
|
||||
|
||||
// Send HP
|
||||
SendPlayerHP(peer_id);
|
||||
|
||||
// Send detached inventories
|
||||
sendDetachedInventories(peer_id);
|
||||
|
||||
// Show death screen if necessary
|
||||
if(player->hp == 0)
|
||||
SendDeathscreen(m_con, peer_id, false, v3f(0,0,0));
|
||||
@@ -2469,6 +2494,10 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
|
||||
return;
|
||||
}
|
||||
|
||||
// If something goes wrong, this player is to blame
|
||||
RollbackScopeActor rollback_scope(m_rollback,
|
||||
std::string("player:")+player->getName());
|
||||
|
||||
/*
|
||||
Note: Always set inventory not sent, to repair cases
|
||||
where the client made a bad prediction.
|
||||
@@ -2532,30 +2561,6 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
|
||||
delete a;
|
||||
return;
|
||||
}
|
||||
|
||||
// If player is not an admin, check for ownership of src and dst
|
||||
/*if(!checkPriv(player->getName(), "server"))
|
||||
{
|
||||
std::string owner_from = getInventoryOwner(ma->from_inv);
|
||||
if(owner_from != "" && owner_from != player->getName())
|
||||
{
|
||||
infostream<<"WARNING: "<<player->getName()
|
||||
<<" tried to access an inventory that"
|
||||
<<" belongs to "<<owner_from<<std::endl;
|
||||
delete a;
|
||||
return;
|
||||
}
|
||||
|
||||
std::string owner_to = getInventoryOwner(ma->to_inv);
|
||||
if(owner_to != "" && owner_to != player->getName())
|
||||
{
|
||||
infostream<<"WARNING: "<<player->getName()
|
||||
<<" tried to access an inventory that"
|
||||
<<" belongs to "<<owner_to<<std::endl;
|
||||
delete a;
|
||||
return;
|
||||
}
|
||||
}*/
|
||||
}
|
||||
/*
|
||||
Handle restrictions and special cases of the drop action
|
||||
@@ -2574,19 +2579,6 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
|
||||
delete a;
|
||||
return;
|
||||
}
|
||||
// If player is not an admin, check for ownership
|
||||
/*else if(!checkPriv(player->getName(), "server"))
|
||||
{
|
||||
std::string owner_from = getInventoryOwner(da->from_inv);
|
||||
if(owner_from != "" && owner_from != player->getName())
|
||||
{
|
||||
infostream<<"WARNING: "<<player->getName()
|
||||
<<" tried to access an inventory that"
|
||||
<<" belongs to "<<owner_from<<std::endl;
|
||||
delete a;
|
||||
return;
|
||||
}
|
||||
}*/
|
||||
}
|
||||
/*
|
||||
Handle restrictions and special cases of the craft action
|
||||
@@ -2611,20 +2603,6 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
|
||||
delete a;
|
||||
return;
|
||||
}
|
||||
|
||||
// If player is not an admin, check for ownership of inventory
|
||||
/*if(!checkPriv(player->getName(), "server"))
|
||||
{
|
||||
std::string owner_craft = getInventoryOwner(ca->craft_inv);
|
||||
if(owner_craft != "" && owner_craft != player->getName())
|
||||
{
|
||||
infostream<<"WARNING: "<<player->getName()
|
||||
<<" tried to access an inventory that"
|
||||
<<" belongs to "<<owner_craft<<std::endl;
|
||||
delete a;
|
||||
return;
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
// Do the action
|
||||
@@ -2654,6 +2632,10 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
|
||||
message += (wchar_t)readU16(buf);
|
||||
}
|
||||
|
||||
// If something goes wrong, this player is to blame
|
||||
RollbackScopeActor rollback_scope(m_rollback,
|
||||
std::string("player:")+player->getName());
|
||||
|
||||
// Get player name of this client
|
||||
std::wstring name = narrow_to_wide(player->getName());
|
||||
|
||||
@@ -2988,6 +2970,12 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
If something goes wrong, this player is to blame
|
||||
*/
|
||||
RollbackScopeActor rollback_scope(m_rollback,
|
||||
std::string("player:")+player->getName());
|
||||
|
||||
/*
|
||||
0: start digging or punch object
|
||||
*/
|
||||
@@ -3170,8 +3158,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
|
||||
// Placement was handled in lua
|
||||
|
||||
// Apply returned ItemStack
|
||||
if(g_settings->getBool("creative_mode") == false)
|
||||
playersao->setWieldedItem(item);
|
||||
playersao->setWieldedItem(item);
|
||||
}
|
||||
|
||||
// If item has node placement prediction, always send the above
|
||||
@@ -3197,8 +3184,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
|
||||
item, playersao, pointed))
|
||||
{
|
||||
// Apply returned ItemStack
|
||||
if(g_settings->getBool("creative_mode") == false)
|
||||
playersao->setWieldedItem(item);
|
||||
playersao->setWieldedItem(item);
|
||||
}
|
||||
|
||||
} // action == 4
|
||||
@@ -3245,8 +3231,39 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
|
||||
fields[fieldname] = fieldvalue;
|
||||
}
|
||||
|
||||
// If something goes wrong, this player is to blame
|
||||
RollbackScopeActor rollback_scope(m_rollback,
|
||||
std::string("player:")+player->getName());
|
||||
|
||||
// Check the target node for rollback data; leave others unnoticed
|
||||
RollbackNode rn_old(&m_env->getMap(), p, this);
|
||||
|
||||
scriptapi_node_on_receive_fields(m_lua, p, formname, fields,
|
||||
playersao);
|
||||
|
||||
// Report rollback data
|
||||
RollbackNode rn_new(&m_env->getMap(), p, this);
|
||||
if(rollback() && rn_new != rn_old){
|
||||
RollbackAction action;
|
||||
action.setSetNode(p, rn_old, rn_new);
|
||||
rollback()->reportAction(action);
|
||||
}
|
||||
}
|
||||
else if(command == TOSERVER_INVENTORY_FIELDS)
|
||||
{
|
||||
std::string datastring((char*)&data[2], datasize-2);
|
||||
std::istringstream is(datastring, std::ios_base::binary);
|
||||
|
||||
std::string formname = deSerializeString(is);
|
||||
int num = readU16(is);
|
||||
std::map<std::string, std::string> fields;
|
||||
for(int k=0; k<num; k++){
|
||||
std::string fieldname = deSerializeString(is);
|
||||
std::string fieldvalue = deSerializeLongString(is);
|
||||
fields[fieldname] = fieldvalue;
|
||||
}
|
||||
|
||||
scriptapi_on_player_receive_fields(m_lua, playersao, formname, fields);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -3302,6 +3319,13 @@ Inventory* Server::getInventory(const InventoryLocation &loc)
|
||||
return meta->getInventory();
|
||||
}
|
||||
break;
|
||||
case InventoryLocation::DETACHED:
|
||||
{
|
||||
if(m_detached_inventories.count(loc.name) == 0)
|
||||
return NULL;
|
||||
return m_detached_inventories[loc.name];
|
||||
}
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
@@ -3336,6 +3360,11 @@ void Server::setInventoryModified(const InventoryLocation &loc)
|
||||
setBlockNotSent(blockpos);
|
||||
}
|
||||
break;
|
||||
case InventoryLocation::DETACHED:
|
||||
{
|
||||
sendDetachedInventoryToAll(loc.name);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
@@ -4293,6 +4322,51 @@ void Server::sendRequestedMedia(u16 peer_id,
|
||||
}
|
||||
}
|
||||
|
||||
void Server::sendDetachedInventory(const std::string &name, u16 peer_id)
|
||||
{
|
||||
if(m_detached_inventories.count(name) == 0){
|
||||
errorstream<<__FUNCTION_NAME<<": \""<<name<<"\" not found"<<std::endl;
|
||||
return;
|
||||
}
|
||||
Inventory *inv = m_detached_inventories[name];
|
||||
|
||||
std::ostringstream os(std::ios_base::binary);
|
||||
writeU16(os, TOCLIENT_DETACHED_INVENTORY);
|
||||
os<<serializeString(name);
|
||||
inv->serialize(os);
|
||||
|
||||
// Make data buffer
|
||||
std::string s = os.str();
|
||||
SharedBuffer<u8> data((u8*)s.c_str(), s.size());
|
||||
// Send as reliable
|
||||
m_con.Send(peer_id, 0, data, true);
|
||||
}
|
||||
|
||||
void Server::sendDetachedInventoryToAll(const std::string &name)
|
||||
{
|
||||
DSTACK(__FUNCTION_NAME);
|
||||
|
||||
for(core::map<u16, RemoteClient*>::Iterator
|
||||
i = m_clients.getIterator();
|
||||
i.atEnd() == false; i++){
|
||||
RemoteClient *client = i.getNode()->getValue();
|
||||
sendDetachedInventory(name, client->peer_id);
|
||||
}
|
||||
}
|
||||
|
||||
void Server::sendDetachedInventories(u16 peer_id)
|
||||
{
|
||||
DSTACK(__FUNCTION_NAME);
|
||||
|
||||
for(std::map<std::string, Inventory*>::iterator
|
||||
i = m_detached_inventories.begin();
|
||||
i != m_detached_inventories.end(); i++){
|
||||
const std::string &name = i->first;
|
||||
//Inventory *inv = i->second;
|
||||
sendDetachedInventory(name, peer_id);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Something random
|
||||
*/
|
||||
@@ -4346,9 +4420,7 @@ void Server::UpdateCrafting(u16 peer_id)
|
||||
|
||||
// Get a preview for crafting
|
||||
ItemStack preview;
|
||||
// No crafting in creative mode
|
||||
if(g_settings->getBool("creative_mode") == false)
|
||||
getCraftingResult(&player->inventory, preview, false, this);
|
||||
getCraftingResult(&player->inventory, preview, false, this);
|
||||
|
||||
// Put the new preview in
|
||||
InventoryList *plist = player->inventory.getList("craftpreview");
|
||||
@@ -4477,6 +4549,88 @@ void Server::queueBlockEmerge(v3s16 blockpos, bool allow_generate)
|
||||
m_emerge_queue.addBlock(PEER_ID_INEXISTENT, blockpos, flags);
|
||||
}
|
||||
|
||||
Inventory* Server::createDetachedInventory(const std::string &name)
|
||||
{
|
||||
if(m_detached_inventories.count(name) > 0){
|
||||
infostream<<"Server clearing detached inventory \""<<name<<"\""<<std::endl;
|
||||
delete m_detached_inventories[name];
|
||||
} else {
|
||||
infostream<<"Server creating detached inventory \""<<name<<"\""<<std::endl;
|
||||
}
|
||||
Inventory *inv = new Inventory(m_itemdef);
|
||||
assert(inv);
|
||||
m_detached_inventories[name] = inv;
|
||||
sendDetachedInventoryToAll(name);
|
||||
return inv;
|
||||
}
|
||||
|
||||
class BoolScopeSet
|
||||
{
|
||||
public:
|
||||
BoolScopeSet(bool *dst, bool val):
|
||||
m_dst(dst)
|
||||
{
|
||||
m_orig_state = *m_dst;
|
||||
*m_dst = val;
|
||||
}
|
||||
~BoolScopeSet()
|
||||
{
|
||||
*m_dst = m_orig_state;
|
||||
}
|
||||
private:
|
||||
bool *m_dst;
|
||||
bool m_orig_state;
|
||||
};
|
||||
|
||||
// actions: time-reversed list
|
||||
// Return value: success/failure
|
||||
bool Server::rollbackRevertActions(const std::list<RollbackAction> &actions,
|
||||
std::list<std::string> *log)
|
||||
{
|
||||
infostream<<"Server::rollbackRevertActions(len="<<actions.size()<<")"<<std::endl;
|
||||
ServerMap *map = (ServerMap*)(&m_env->getMap());
|
||||
// Disable rollback report sink while reverting
|
||||
BoolScopeSet rollback_scope_disable(&m_rollback_sink_enabled, false);
|
||||
|
||||
// Fail if no actions to handle
|
||||
if(actions.empty()){
|
||||
log->push_back("Nothing to do.");
|
||||
return false;
|
||||
}
|
||||
|
||||
int num_tried = 0;
|
||||
int num_failed = 0;
|
||||
|
||||
for(std::list<RollbackAction>::const_iterator
|
||||
i = actions.begin();
|
||||
i != actions.end(); i++)
|
||||
{
|
||||
const RollbackAction &action = *i;
|
||||
num_tried++;
|
||||
bool success = action.applyRevert(map, this, this);
|
||||
if(!success){
|
||||
num_failed++;
|
||||
std::ostringstream os;
|
||||
os<<"Revert of step ("<<num_tried<<") "<<action.toString()<<" failed";
|
||||
infostream<<"Map::rollbackRevertActions(): "<<os.str()<<std::endl;
|
||||
if(log)
|
||||
log->push_back(os.str());
|
||||
}else{
|
||||
std::ostringstream os;
|
||||
os<<"Succesfully reverted step ("<<num_tried<<") "<<action.toString();
|
||||
infostream<<"Map::rollbackRevertActions(): "<<os.str()<<std::endl;
|
||||
if(log)
|
||||
log->push_back(os.str());
|
||||
}
|
||||
}
|
||||
|
||||
infostream<<"Map::rollbackRevertActions(): "<<num_failed<<"/"<<num_tried
|
||||
<<" failed"<<std::endl;
|
||||
|
||||
// Call it done if less than half failed
|
||||
return num_failed <= num_tried/2;
|
||||
}
|
||||
|
||||
// IGameDef interface
|
||||
// Under envlock
|
||||
IItemDefManager* Server::getItemDefManager()
|
||||
@@ -4507,6 +4661,14 @@ MtEventManager* Server::getEventManager()
|
||||
{
|
||||
return m_event;
|
||||
}
|
||||
IRollbackReportSink* Server::getRollbackReportSink()
|
||||
{
|
||||
if(!m_enable_rollback_recording)
|
||||
return NULL;
|
||||
if(!m_rollback_sink_enabled)
|
||||
return NULL;
|
||||
return m_rollback;
|
||||
}
|
||||
|
||||
IWritableItemDefManager* Server::getWritableItemDefManager()
|
||||
{
|
||||
@@ -4531,6 +4693,13 @@ const ModSpec* Server::getModSpec(const std::string &modname)
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
void Server::getModNames(core::list<std::string> &modlist)
|
||||
{
|
||||
for(core::list<ModSpec>::Iterator i = m_mods.begin(); i != m_mods.end(); i++)
|
||||
{
|
||||
modlist.push_back((*i).name);
|
||||
}
|
||||
}
|
||||
std::string Server::getBuiltinLuaPath()
|
||||
{
|
||||
return porting::path_share + DIR_DELIM + "builtin";
|
||||
@@ -4661,10 +4830,6 @@ PlayerSAO* Server::emergePlayer(const char *name, u16 peer_id)
|
||||
|
||||
scriptapi_on_joinplayer(m_lua, playersao);
|
||||
|
||||
/* Creative mode */
|
||||
if(g_settings->getBool("creative_mode"))
|
||||
playersao->createCreativeInventory();
|
||||
|
||||
return playersao;
|
||||
}
|
||||
|
||||
|
||||
30
src/server.h
30
src/server.h
@@ -36,6 +36,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
#include "sound.h"
|
||||
#include "util/thread.h"
|
||||
#include "util/string.h"
|
||||
#include "rollback_interface.h" // Needed for rollbackRevertActions()
|
||||
#include <list> // Needed for rollbackRevertActions()
|
||||
|
||||
struct LuaState;
|
||||
typedef struct lua_State lua_State;
|
||||
@@ -44,6 +46,7 @@ class IWritableNodeDefManager;
|
||||
class IWritableCraftDefManager;
|
||||
class EventManager;
|
||||
class PlayerSAO;
|
||||
class IRollbackManager;
|
||||
|
||||
class ServerError : public std::exception
|
||||
{
|
||||
@@ -538,8 +541,18 @@ class Server : public con::PeerHandler, public MapEventReceiver,
|
||||
|
||||
void queueBlockEmerge(v3s16 blockpos, bool allow_generate);
|
||||
|
||||
// Creates or resets inventory
|
||||
Inventory* createDetachedInventory(const std::string &name);
|
||||
|
||||
// Envlock and conlock should be locked when using Lua
|
||||
lua_State *getLua(){ return m_lua; }
|
||||
|
||||
// Envlock should be locked when using the rollback manager
|
||||
IRollbackManager *getRollbackManager(){ return m_rollback; }
|
||||
// actions: time-reversed list
|
||||
// Return value: success/failure
|
||||
bool rollbackRevertActions(const std::list<RollbackAction> &actions,
|
||||
std::list<std::string> *log);
|
||||
|
||||
// IGameDef interface
|
||||
// Under envlock
|
||||
@@ -550,12 +563,14 @@ class Server : public con::PeerHandler, public MapEventReceiver,
|
||||
virtual u16 allocateUnknownNodeId(const std::string &name);
|
||||
virtual ISoundManager* getSoundManager();
|
||||
virtual MtEventManager* getEventManager();
|
||||
virtual IRollbackReportSink* getRollbackReportSink();
|
||||
|
||||
IWritableItemDefManager* getWritableItemDefManager();
|
||||
IWritableNodeDefManager* getWritableNodeDefManager();
|
||||
IWritableCraftDefManager* getWritableCraftDefManager();
|
||||
|
||||
const ModSpec* getModSpec(const std::string &modname);
|
||||
void getModNames(core::list<std::string> &modlist);
|
||||
std::string getBuiltinLuaPath();
|
||||
|
||||
std::string getWorldPath(){ return m_path_world; }
|
||||
@@ -626,6 +641,10 @@ class Server : public con::PeerHandler, public MapEventReceiver,
|
||||
void sendMediaAnnouncement(u16 peer_id);
|
||||
void sendRequestedMedia(u16 peer_id,
|
||||
const core::list<MediaRequest> &tosend);
|
||||
|
||||
void sendDetachedInventory(const std::string &name, u16 peer_id);
|
||||
void sendDetachedInventoryToAll(const std::string &name);
|
||||
void sendDetachedInventories(u16 peer_id);
|
||||
|
||||
/*
|
||||
Something random
|
||||
@@ -712,6 +731,11 @@ class Server : public con::PeerHandler, public MapEventReceiver,
|
||||
// Bann checking
|
||||
BanManager m_banmanager;
|
||||
|
||||
// Rollback manager (behind m_env_mutex)
|
||||
IRollbackManager *m_rollback;
|
||||
bool m_rollback_sink_enabled;
|
||||
bool m_enable_rollback_recording; // Updated once in a while
|
||||
|
||||
// Scripting
|
||||
// Envlock and conlock should be locked when using Lua
|
||||
lua_State *m_lua;
|
||||
@@ -827,6 +851,12 @@ class Server : public con::PeerHandler, public MapEventReceiver,
|
||||
*/
|
||||
std::map<s32, ServerPlayingSound> m_playing_sounds;
|
||||
s32 m_next_sound_id;
|
||||
|
||||
/*
|
||||
Detached inventories (behind m_env_mutex)
|
||||
*/
|
||||
// key = name
|
||||
std::map<std::string, Inventory*> m_detached_inventories;
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
546
src/test.cpp
546
src/test.cpp
File diff suppressed because it is too large
Load Diff
@@ -324,7 +324,9 @@ def content_is_air(d):
|
||||
return d in [126, 127, 254, "air"]
|
||||
|
||||
def read_content(mapdata, version, datapos):
|
||||
if version >= 20:
|
||||
if version >= 24:
|
||||
return (mapdata[datapos*2] << 8) | (mapdata[datapos*2 + 1])
|
||||
elif version >= 20:
|
||||
if mapdata[datapos] < 0x80:
|
||||
return mapdata[datapos]
|
||||
else:
|
||||
@@ -387,6 +389,7 @@ def read_mapdata(mapdata, version, pixellist, water, day_night_differs, id_to_na
|
||||
#print("unknown node: %s/%s/%s x: %d y: %d z: %d block id: %x"
|
||||
# % (xhex, zhex, yhex, x, y, z, content))
|
||||
|
||||
|
||||
# Go through all sectors.
|
||||
for n in range(len(xlist)):
|
||||
#if n > 500:
|
||||
@@ -546,6 +549,14 @@ for n in range(len(xlist)):
|
||||
|
||||
if version == 23:
|
||||
readU8(f) # Unused node timer version (always 0)
|
||||
if version == 24:
|
||||
ver = readU8(f)
|
||||
if ver == 1:
|
||||
num = readU16(f)
|
||||
for i in range(0,num):
|
||||
readU16(f)
|
||||
readS32(f)
|
||||
readS32(f)
|
||||
|
||||
static_object_version = readU8(f)
|
||||
static_object_count = readU16(f)
|
||||
@@ -578,6 +589,15 @@ for n in range(len(xlist)):
|
||||
#print(str(node_id)+" = "+name)
|
||||
id_to_name[node_id] = name
|
||||
|
||||
# Node timers
|
||||
if version >= 25:
|
||||
timer_size = readU8(f)
|
||||
num = readU16(f)
|
||||
for i in range(0,num):
|
||||
readU16(f)
|
||||
readS32(f)
|
||||
readS32(f)
|
||||
|
||||
read_mapdata(mapdata, version, pixellist, water, day_night_differs, id_to_name)
|
||||
|
||||
# After finding all the pixels in the sector, we can move on to
|
||||
|
||||
Reference in New Issue
Block a user