Compare commits

...

49 Commits

Author SHA1 Message Date
Perttu Ahola
d38b465b7c Call this 0.4.2-rc1 2012-07-28 16:31:45 +03:00
Perttu Ahola
fd7ec2da91 Fix inventory segfault when rollback recording is disabled 2012-07-28 14:44:18 +03:00
Perttu Ahola
c9ed379e39 Add enable_rollback_recording setting, defaulting to false 2012-07-28 03:08:09 +03:00
Perttu Ahola
e64feefc61 Handle max<min in LuaPseudoRandom::l_next() 2012-07-27 19:03:15 +03:00
Perttu Ahola
3e754382af Tweak rollback and liquids 2012-07-27 15:46:51 +03:00
Perttu Ahola
7ef0a13250 Tweak rollback stuff 2012-07-27 14:52:29 +03:00
Perttu Ahola
0de3fb786d Increase automatic suspect guess timeframe 2012-07-27 13:54:14 +03:00
Perttu Ahola
1d44a98f2f ABM and liquid overload skip 2012-07-27 13:45:49 +03:00
Perttu Ahola
98ff4eb4ee Fix server build (a missing header) 2012-07-27 13:43:25 +03:00
Perttu Ahola
a9d8df83d2 Make the rollback system VERY FUCKING GOD DAMN POWERFUL 2012-07-27 13:24:28 +03:00
Perttu Ahola
508b7b5e51 Don't track liquids for rollback because of too much log 2012-07-27 02:46:54 +03:00
Perttu Ahola
f7dc72f8aa Properly rollback chat command triggered things 2012-07-27 02:37:04 +03:00
Perttu Ahola
0190f9b077 Experimental-ish rollback functionality 2012-07-27 02:27:18 +03:00
Perttu Ahola
0c91a0d59d Working group-shapeless and multigroup recipes 2012-07-26 13:49:13 +03:00
Perttu Ahola
a26a66a8c4 Restore focus to formspec menu when pressing a button 2012-07-25 18:28:40 +03:00
Perttu Ahola
100345f1e4 Deprecate minetest.add_to_creative_inventory and use group not_in_creative_inventory instead 2012-07-25 18:05:31 +03:00
Perttu Ahola
4535166a3b Add notice in the minimal game 2012-07-25 17:10:31 +03:00
Perttu Ahola
0346e68deb Add special return value -1 to inventry callbacks 2012-07-25 16:52:00 +03:00
Perttu Ahola
db62c227c8 Improve formspec positioning 2012-07-25 16:30:23 +03:00
Perttu Ahola
983e45ae92 Improve inventory callbacks a bit 2012-07-25 15:39:39 +03:00
Perttu Ahola
0a18dda158 Remove special handling of creative mode 2012-07-25 14:07:45 +03:00
Perttu Ahola
9eaf93d41d Detached inventory callbacks and reworked node metadata callbacks 2012-07-25 02:36:54 +03:00
Perttu Ahola
2ac20982e0 Detached inventories 2012-07-24 20:57:17 +03:00
Perttu Ahola
96eac87d47 builtin/item.lua: callbacks with copies of positions and nodes rather than recycle the same ones, which callbacks can modify 2012-07-24 17:46:17 +03:00
Perttu Ahola
0cf1ed544c darkrose should work at a nuclear power plant.
It'd take years to figure out what caused the accident.
2012-07-24 16:36:50 +03:00
Perttu Ahola
558e284e25 Update minetestmapper.py to support ver. 24 and 25 2012-07-24 15:17:00 +03:00
Perttu Ahola
5c31445117 Improve node timer format (map format version 25) and update mapformat.txt 2012-07-24 15:03:46 +03:00
Perttu Ahola
717ae67995 Add node timer test in minimal/experimental 2012-07-24 14:51:13 +03:00
Perttu Ahola
e8331f0c1d Add oldnode parameter to minetest.register_on_placenode callback 2012-07-23 20:44:56 +03:00
Perttu Ahola
c009aa3a22 Fix building on top of (pointable && buildable_to) nodes 2012-07-23 20:42:08 +03:00
Perttu Ahola
9af9d8f5d0 Describe node definition fields better in lua_api.txt 2012-07-23 20:17:44 +03:00
Perttu Ahola
2c027b03db Move /give, /giveme, /spawnentity and /pulverize to builtin/chatcommands.lua 2012-07-23 17:43:08 +03:00
Perttu Ahola
aef1332e42 Improve build configuration options 2012-07-23 15:23:33 +03:00
Perttu Ahola
16fc8b5fc2 Update lua_api.txt a bit 2012-07-23 08:48:55 +03:00
Perttu Ahola
fd845f27f5 Fix map deserialization and remove old serialization code 2012-07-23 08:18:39 +03:00
darkrose
ea62ee4b61 Increase node id/param0 to 16 bits, leaving param2 always with 8 bits 2012-07-23 08:18:39 +03:00
darkrose
cd6becd442 Implement node timers 2012-07-23 08:18:37 +03:00
Perttu Ahola
829f262c79 Fix terrible grammar in comment! 2012-07-22 20:36:06 +03:00
Perttu Ahola
246520b5cb Fix compressZlib() 2012-07-22 20:29:09 +03:00
Perttu Ahola
38bb649582 Test zlib wrapper's handling of large data 2012-07-22 20:27:55 +03:00
Perttu Ahola
82855a04ec Tweak test.c overally a bit 2012-07-22 20:26:54 +03:00
Perttu Ahola
6dfefaf229 Formspec button_exit[] and image_button_exit[] 2012-07-22 17:40:48 +03:00
darkrose
d44f8a854b Doc updates for formspec 2012-07-22 17:40:48 +03:00
Perttu Ahola
acf3a43095 Add /test1 command to minimal for testing a more complicated player inventory form 2012-07-22 17:40:48 +03:00
Perttu Ahola
4cc98d7add minetest.register_on_player_receive_fields() 2012-07-22 17:40:48 +03:00
darkrose
506203345b Implement formspec 2012-07-22 17:40:41 +03:00
Matthew I
c259f7c8bd Update Lua API documentation to include minetest.get_modnames() 2012-07-22 13:36:17 +03:00
Matthew I
c62a121cca Add "/mods" command to list mods to client 2012-07-22 13:36:10 +03:00
Matthew I
136eb32389 Add minetest.get_modnames() to Lua API 2012-07-22 13:36:03 +03:00
65 changed files with 4580 additions and 1505 deletions

View File

@@ -12,7 +12,7 @@ set(VERSION_EXTRA "" CACHE STRING "Stuff to append to version string")
# Also remember to set PROTOCOL_VERSION in clientserver.h when releasing
set(VERSION_MAJOR 0)
set(VERSION_MINOR 4)
set(VERSION_PATCH 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")

View File

@@ -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")

View File

@@ -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,
})

View File

@@ -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

View 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

View File

@@ -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

View File

@@ -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()

View File

@@ -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")

View File

@@ -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
}

View File

@@ -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.

View File

@@ -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

View File

@@ -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()))

View File

@@ -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

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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<<")"

View File

@@ -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;

View File

@@ -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)

View File

@@ -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;

View File

@@ -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

View File

@@ -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;]"

View File

@@ -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);

View File

@@ -163,8 +163,6 @@ class PlayerSAO : public ServerActiveObject
void disconnected();
void createCreativeInventory();
Player* getPlayer()
{
return m_player;

View File

@@ -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

View File

@@ -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");

View File

@@ -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())

View File

@@ -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;

View File

@@ -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);

View File

@@ -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

View File

@@ -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;
}
}
}

View File

@@ -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;

View File

@@ -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:

View File

@@ -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)

View File

@@ -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){}
};

View File

@@ -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

View File

@@ -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 +

View File

@@ -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
*/

View File

@@ -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

View File

@@ -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;

View File

@@ -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);
/*

View File

@@ -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

View File

@@ -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);
};

View File

@@ -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 != "")

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;]"

View File

@@ -150,7 +150,6 @@ class Player
u8 light;
// In creative mode, this is the invisible backup inventory
Inventory inventory;
u16 hp;

View File

@@ -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
View 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
View 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
View 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
View 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

View File

@@ -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));
}
/*

View File

@@ -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 */

View File

@@ -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);

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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;
};
/*

File diff suppressed because it is too large Load Diff

View File

@@ -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