Compare commits

..

86 Commits
0.4.1 ... 0.4.3

Author SHA1 Message Date
Perttu Ahola
9696ed31a4 Version 0.4.3 2012-09-05 01:31:06 +03:00
Matthew I
c8895dd43b Prevent world creation if the world already exists 2012-09-05 01:17:44 +03:00
Matthew I
5dd1d354f8 Enforce stricter world names using a blacklist
Blacklisted characters are: / \
2012-09-05 01:17:28 +03:00
Perttu Ahola
a0da6bcf43 Add aux1_descends to key change menu 2012-09-05 00:55:19 +03:00
Perttu Ahola
1b63f8d6b9 Version 0.4.3-rc0; increase PROTOCOL_VERSION due to backwards-incompatible InventoryList width field 2012-09-05 00:39:43 +03:00
Perttu Ahola
60932d2bd7 Remove "Unknown inventory identifier" deserialization errors, because they can break forward compatibility 2012-09-04 23:02:56 +03:00
Perttu Ahola
ee2d9d973a Reorganize ClientMap rendering code for a bit more performance
- Don't select blocks for drawing in every frame
- Sort meshbuffers by material before drawing
2012-09-04 22:41:03 +03:00
Perttu Ahola
0e6f7a21c6 Fix shift-descend to ladders from a floor 2012-09-03 23:31:51 +03:00
Perttu Ahola
cc47ff7c4c Fix moving stuff into a mismatched stack in a "infinite" inventory 2012-09-02 23:01:42 +03:00
Perttu Ahola
e1a495ee30 Make inventory GUI do sane things when server-side inventory acts unusually 2012-09-02 22:51:38 +03:00
Perttu Ahola
6495007924 Fix bug in moving a larger sized stack in inventory on top of another
- You have 2 stacks: a with x items and b with y<x items
- Take a with your mouse and place it on b
- Whole stack a moves to b
- (y * 2 - x) items move from b to a
- (x - y) items are left to the mouse cursor
2012-09-02 20:24:45 +03:00
Perttu Ahola
d53a6cd851 Add overridable function for handling dropped items from nodes 2012-09-02 19:38:44 +03:00
Perttu Ahola
b6c12d2aa3 Fix github issue #224 2012-09-01 18:32:15 +03:00
Thomas Lauro
7a14171be4 Fix issue with openal mac os x compile 2012-09-01 18:19:59 +03:00
Perttu Ahola
5194505407 Fix GUIKeyChangeMenu so that '/' can be inserted on a finnish keyboard 2012-09-01 18:03:50 +03:00
Thomas Lauro
3e7957512b A bunch of GUIKeyChangeMenu fixes
- Display message when associating a key already in use
- Fix issue for not beeing able to assign space to an action
- Cleaning up
- Make it build (by celeron55)
2012-09-01 18:03:40 +03:00
Perttu Ahola
c0bd96d9b3 Attempt to fix flying gravel and dirt 2012-09-01 13:41:15 +03:00
Sergey Gilfanov
0f7728f84d Smooth transition to the desert biome. 2012-09-01 13:22:07 +03:00
Perttu Ahola
c129b3852b Fix the bouncy node related stuff a bit 2012-09-01 13:21:52 +03:00
Perttu Ahola
3b43c69df4 Add bouncy node group 2012-09-01 12:58:37 +03:00
Perttu Ahola
b17d3e7ad7 Add disable_jump and fall_damage_add_percent node groups 2012-09-01 12:07:27 +03:00
Ilya Zhuravlev
6a16075912 Add InventoryList width property & allow custom crafting grids. 2012-09-01 10:01:41 +03:00
sfan5
43ebec2be1 Make shift the default descent control on ladders and when flying
"aux1_descends" setting switches to the old descend control.
2012-09-01 09:48:07 +03:00
Perttu Ahola
f60227f112 Don't crash in "unexpected multibyte character"; just print it in log. Github #222 2012-09-01 09:23:39 +03:00
Perttu Ahola
f3630e95d6 Use proper alpha blit in some more texture modifiers 2012-08-31 18:06:47 +03:00
Perttu Ahola
8887105ffd Properly initialize baseimg for texture modifier [combine:WxH:X,Y=filename:X,Y=filename2 2012-08-31 17:58:55 +03:00
Matthew I
1d7408a7b8 Fix black display inside opaque water
Opaque water's solidness was being set to 2, like a normal node.
When you swim, it is treated like a solid block, and the display
goes black.  Setting it to 1 like transparent water allows you to
see.

It looks somewhat awkward when you swim, look up, and see an opaque
wall of water (the surface), but there isn't much that can be done
about it.  If you made the water transparent so it looked good,
it would defeat the purpose :) .
2012-08-31 17:50:12 +03:00
Perttu Ahola
0b1ab41a95 Fix github issue #213: Dropping unknown items crashes the game
The items will now just disappear when dropped.
2012-08-19 10:55:04 +03:00
Perttu Ahola
b717b6505b Fix crash when furnace is full (minimal game) 2012-08-12 17:11:23 +03:00
Matthew I
b29d609b0b Move chat commands to Lua and remove servercommand.{cpp,h}
Commands moved:
 /me
 /status
 /time
 /shutdown
 /ban
 /clearobjects
2012-08-12 16:45:58 +03:00
Matthew I
8a3c777c40 Add ban management routines to Lua API
Those functions are:
 * minetest.get_ban_list()
 * minetest.get_ban_description(ip_or_name)
 * minetest.ban_player(name)
 * minetest.unban_player_or_ip(ip_or_name)
2012-08-12 16:45:58 +03:00
Matthew I
548da26ddc Extend Lua API in order to implement chat commands
minetest.get_server_status()
minetest.request_shutdown()
EnvRef:clear_objects()
2012-08-12 16:44:55 +03:00
Matthew I
8e0ac70c4d Remove trailing comma after player list in server status string 2012-08-12 16:44:54 +03:00
Matthew I
1ed559bd24 Allow digging of unknown nodes
This allows the removal of nodes with unknown types.
get_item_callback() (C++) would fail if a node has an unknown type.  Now it
will try using the callback from minetest.nodedef_default in this case.
Also, minetest.node_dig() (Lua) was altered to always allow digging when
the node definition is empty (i.e. unknown node).
2012-08-12 16:04:18 +03:00
Perttu Ahola
e3b831e975 Fix wrong amount of nodes being dropped from inventory 2012-08-12 15:49:23 +03:00
darkrose
d05e3adbc7 fix node timers so on_timer gets the correct position 2012-08-12 15:28:31 +03:00
Perttu Ahola
a9a475ad50 Remove unwanted ! from ifs in inventory record-for-rollback code 2012-08-12 11:20:36 +03:00
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
86 changed files with 5764 additions and 2575 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 3)
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

@@ -20,18 +20,13 @@ minetest.register_on_chat_message(function(name, message)
end
local cmd_def = minetest.chatcommands[cmd]
if cmd_def then
if not cmd_def.func then
-- This is a C++ command
return false
local has_privs, missing_privs = minetest.check_player_privs(name, cmd_def.privs)
if has_privs then
cmd_def.func(name, param)
else
local has_privs, missing_privs = minetest.check_player_privs(name, cmd_def.privs)
if has_privs then
cmd_def.func(name, param)
else
minetest.chat_send_player(name, "You don't have permission to run this command (missing privileges: "..table.concat(missing_privs, ", ")..")")
end
return true -- handled chat message
minetest.chat_send_player(name, "You don't have permission to run this command (missing privileges: "..table.concat(missing_privs, ", ")..")")
end
return true -- handled chat message
end
return false
end)
@@ -39,17 +34,15 @@ end)
--
-- Chat commands
--
minetest.register_chatcommand("me", {
params = "<action>",
description = "chat action (eg. /me orders a pizza)",
privs = {shout=true},
func = function(name, param)
minetest.chat_send_all("* " .. name .. " " .. param)
end,
})
-- Register C++ commands without functions
minetest.register_chatcommand("me", {params = nil, description = "chat action (eg. /me orders a pizza)", privs = {shout=true}})
minetest.register_chatcommand("status", {description = "print server status line"})
minetest.register_chatcommand("shutdown", {params = "", description = "shutdown server", privs = {server=true}})
minetest.register_chatcommand("clearobjects", {params = "", description = "clear all objects in world", privs = {server=true}})
minetest.register_chatcommand("time", {params = "<0...24000>", description = "set time of day", privs = {settime=true}})
minetest.register_chatcommand("ban", {params = "<name>", description = "ban IP of player", privs = {ban=true}})
minetest.register_chatcommand("unban", {params = "<name/ip>", description = "remove IP ban", privs = {ban=true}})
-- Register other commands
minetest.register_chatcommand("help", {
privs = {},
params = "(nothing)/all/privs/<cmd>",
@@ -371,3 +364,296 @@ 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,
})
minetest.register_chatcommand("status", {
params = "",
description = "print server status line",
privs = {},
func = function(name, param)
minetest.chat_send_player(name, minetest.get_server_status())
end,
})
minetest.register_chatcommand("time", {
params = "<0...24000>",
description = "set time of day",
privs = {settime=true},
func = function(name, param)
if param == "" then
minetest.chat_send_player(name, "Missing parameter")
return
end
local newtime = tonumber(param)
if newtime == nil then
minetest.chat_send_player(name, "Invalid time")
else
minetest.env:set_timeofday((newtime % 24000) / 24000)
minetest.chat_send_player(name, "Time of day changed.")
minetest.log("action", name .. " sets time " .. newtime)
end
end,
})
minetest.register_chatcommand("shutdown", {
params = "",
description = "shutdown server",
privs = {server=true},
func = function(name, param)
minetest.log("action", name .. " shuts down server")
minetest.request_shutdown()
minetest.chat_send_all("*** Server shutting down (operator request).")
end,
})
minetest.register_chatcommand("ban", {
params = "<name>",
description = "ban IP of player",
privs = {ban=true},
func = function(name, param)
if param == "" then
minetest.chat_send_player(name, "Ban list: " .. minetest.get_ban_list())
return
end
if not minetest.env:get_player_by_name(param) then
minetest.chat_send_player(name, "No such player")
return
end
if not minetest.ban_player(param) then
minetest.chat_send_player(name, "Failed to ban player")
else
local desc = minetest.get_ban_description(param)
minetest.chat_send_player(name, "Banned " .. desc .. ".")
minetest.log("action", name .. " bans " .. desc .. ".")
end
end,
})
minetest.register_chatcommand("unban", {
params = "<name/ip>",
description = "remove IP ban",
privs = {ban=true},
func = function(name, param)
if not minetest.unban_player_or_ip(param) then
minetest.chat_send_player(name, "Failed to unban player/IP")
else
minetest.chat_send_player(name, "Unbanned " .. param)
minetest.log("action", name .. " unbans " .. param)
end
end,
})
minetest.register_chatcommand("clearobjects", {
params = "",
description = "clear all objects in world",
privs = {server=true},
func = function(name, param)
minetest.log("action", name .. " clears all objects")
minetest.chat_send_all("Clearing all objects. This may take long. You may experience a timeout. (by " .. name .. ")")
minetest.env:clear_objects()
minetest.log("action", "object clearing done")
minetest.chat_send_all("*** Cleared all objects.")
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
@@ -198,10 +227,12 @@ function minetest.item_drop(itemstack, dropper, pos)
local v = dropper:get_look_dir()
local p = {x=pos.x+v.x, y=pos.y+1.5+v.y, z=pos.z+v.z}
local obj = minetest.env:add_item(p, itemstack)
v.x = v.x*2
v.y = v.y*2 + 1
v.z = v.z*2
obj:setvelocity(v)
if obj then
v.x = v.x*2
v.y = v.y*2 + 1
v.z = v.z*2
obj:setvelocity(v)
end
else
minetest.env:add_item(pos, itemstack)
end
@@ -222,16 +253,29 @@ 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.handle_node_drops(pos, drops, digger)
-- 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
end
function minetest.node_dig(pos, node, digger)
minetest.debug("node_dig")
local def = ItemStack({name=node.name}):get_definition()
if not def.diggable or (def.can_dig and not def.can_dig(pos,digger)) then
-- Check if def ~= 0 because we always want to be able to remove unknown nodes
if #def ~= 0 and not def.diggable or (def.can_dig and not def.can_dig(pos,digger)) then
minetest.debug("not diggable")
minetest.log("info", digger:get_player_name() .. " tried to dig "
.. node.name .. " which is not diggable "
@@ -242,29 +286,20 @@ 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)
-- 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
end
-- 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)
local oldnode = nil
-- Handle drops
minetest.handle_node_drops(pos, drops, digger)
local oldmetadata = nil
if def.after_dig_node then
oldnode = node;
oldmetadata = minetest.env:get_meta(pos):to_table()
end
@@ -273,51 +308,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
@@ -447,6 +453,9 @@ Special groups
- 2: node is removed without tool wear after 0.5 seconds or so
(rail, sign)
- 3: node is removed without tool wear immediately (torch)
- disable_jump: Player (and possibly other things) cannot jump from node
- fall_damage_add_percent: damage speed = speed * (1 + value/100)
- bouncy: value is bounce speed in percent
Known damage and digging time defining groups
----------------------------------------------
@@ -672,10 +681,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 +695,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 +762,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 +786,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 +810,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 +859,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)
@@ -836,6 +891,20 @@ minetest.get_craft_recipe(output) -> input
^ input.items = for example { stack 1, stack 2, stack 3, stack 4,
stack 5, stack 6, stack 7, stack 8, stack 9 }
^ input.items = nil if no recipe found
minetest.handle_node_drops(pos, drops, digger)
^ drops: list of itemstrings
^ Handles drops from nodes after digging: Default action is to put them into
digger's inventory
^ Can be overridden to get different functionality (eg. dropping items on
ground)
Rollbacks:
minetest.rollback_get_last_node_actor(p, range, seconds) -> actor, p, seconds
^ 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)
@@ -868,6 +937,16 @@ minetest.after(time, func, param)
^ Call function after time seconds
^ param is optional; to pass multiple parameters, pass a table.
Server:
minetest.request_shutdown() -> request for server shutdown
minetest.get_server_status() -> server status string
Bans:
minetest.get_ban_list() -> ban list (same as minetest.get_ban_description(""))
minetest.get_ban_description(ip_or_name) -> ban description (string)
minetest.ban_player(name) -> ban a player
minetest.unban_player_or_ip(name) -> unban player or IP address
Random:
minetest.get_connected_players() -> list of ObjectRefs
minetest.hash_node_position({x=,y=,z=}) -> 48-bit integer
@@ -939,12 +1018,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
@@ -955,6 +1036,8 @@ methods:
^ nodenames: eg. {"ignore", "group:tree"} or "default:dirt"
- get_perlin(seeddiff, octaves, persistence, scale)
^ Return world-specific perlin noise (int(worldseed)+seeddiff)
- clear_objects()
^ clear all objects in the environments
Deprecated:
- add_rat(pos): Add C++ rat object (no-op)
- add_firefly(pos): Add C++ firefly object (no-op)
@@ -973,6 +1056,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)
@@ -1024,6 +1127,8 @@ methods:
- is_empty(listname): return true if list is empty
- get_size(listname): get size of a list
- set_size(listname, size): set size of a list
- get_width(listname): get width of a list
- set_width(listname, width): set width of list; currently used for crafting
- get_stack(listname, i): get a copy of stack index i in list
- set_stack(listname, i, stack): copy stack to index i in list
- get_list(listname): return full list
@@ -1219,30 +1324,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 +1384,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 +1476,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;]"..
@@ -1393,7 +1384,7 @@ minetest.register_abm({
srcstack:take_item()
inv:set_stack("src", 1, srcstack)
else
print("Could not insert '"..cooked.item.."'")
print("Could not insert '"..cooked.item:to_string().."'")
end
meta:set_string("src_time", 0)
end
@@ -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

@@ -15,7 +15,7 @@
# https://bitbucket.org/celeron55/minetest/src/tip/src/defaultsettings.cpp
#
# A vim command to convert most of defaultsettings.cpp to conf file format:
# :'<,'>s/\tg_settings\.setDefault("\([^"]*\)", "\([^"]*\)");.*/#\1 = \2/g
# :'<,'>s/\tsettings->setDefault("\([^"]*\)", "\([^"]*\)");.*/#\1 = \2/g
# Note: Some of the settings are implemented in Lua
#
@@ -49,8 +49,14 @@
#keymap_freemove = KEY_KEY_K
#keymap_fastmove = KEY_KEY_J
#keymap_screenshot = KEY_F12
# If true, keymap_special1 instead of keymap_sneak is used for climbing down and descending
#aux1_descends = false
# Some (temporary) keys for debugging
#keymap_print_debug_stacks = KEY_KEY_P
#keymap_quicktune_prev = KEY_HOME
#keymap_quicktune_next = KEY_END
#keymap_quicktune_dec = KEY_NEXT
#keymap_quicktune_inc = KEY_PRIOR
# Minimum FPS
# The amount of rendered stuff is dynamically set according to this
@@ -160,6 +166,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
@@ -193,7 +193,6 @@ set(common_SRCS
connection.cpp
environment.cpp
server.cpp
servercommand.cpp
socket.cpp
mapblock.cpp
mapsector.cpp
@@ -251,7 +250,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

@@ -157,43 +157,21 @@ static bool isOccluded(Map *map, v3s16 p0, v3s16 p1, float step, float stepfac,
return false;
}
void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
void ClientMap::updateDrawList(video::IVideoDriver* driver)
{
ScopeProfiler sp(g_profiler, "CM::updateDrawList()", SPT_AVG);
g_profiler->add("CM::updateDrawList() count", 1);
INodeDefManager *nodemgr = m_gamedef->ndef();
//m_dout<<DTIME<<"Rendering map..."<<std::endl;
DSTACK(__FUNCTION_NAME);
bool is_transparent_pass = pass == scene::ESNRP_TRANSPARENT;
std::string prefix;
if(pass == scene::ESNRP_SOLID)
prefix = "CM: solid: ";
else
prefix = "CM: transparent: ";
/*
This is called two times per frame, reset on the non-transparent one
*/
if(pass == scene::ESNRP_SOLID)
for(core::map<v3s16, MapBlock*>::Iterator
i = m_drawlist.getIterator();
i.atEnd() == false; i++)
{
m_last_drawn_sectors.clear();
MapBlock *block = i.getNode()->getValue();
block->refDrop();
}
/*
Get time for measuring timeout.
Measuring time is very useful for long delays when the
machine is swapping a lot.
*/
int time1 = time(0);
/*
Get animation parameters
*/
float animation_time = m_client->getAnimationTime();
int crack = m_client->getCrackLevel();
u32 daynight_ratio = m_client->getEnv().getDayNightRatio();
m_drawlist.clear();
m_camera_mutex.Lock();
v3f camera_position = m_camera_position;
@@ -201,17 +179,15 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
f32 camera_fov = m_camera_fov;
m_camera_mutex.Unlock();
/*
Get all blocks and draw all visible ones
*/
// Use a higher fov to accomodate faster camera movements.
// Blocks are cropped better when they are drawn.
// Or maybe they aren't? Well whatever.
camera_fov *= 1.2;
v3s16 cam_pos_nodes = floatToInt(camera_position, BS);
v3s16 box_nodes_d = m_control.wanted_range * v3s16(1,1,1);
v3s16 p_nodes_min = cam_pos_nodes - box_nodes_d;
v3s16 p_nodes_max = cam_pos_nodes + box_nodes_d;
// Take a fair amount as we will be dropping more out later
// Umm... these additions are a bit strange but they are needed.
v3s16 p_blocks_min(
@@ -223,13 +199,6 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
p_nodes_max.Y / MAP_BLOCKSIZE + 1,
p_nodes_max.Z / MAP_BLOCKSIZE + 1);
u32 vertex_count = 0;
u32 meshbuffer_count = 0;
// For limiting number of mesh animations per frame
u32 mesh_animate_count = 0;
u32 mesh_animate_count_far = 0;
// Number of blocks in rendering range
u32 blocks_in_range = 0;
// Number of blocks occlusion culled
@@ -242,18 +211,9 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
// Blocks that were drawn and had a mesh
u32 blocks_drawn = 0;
// Blocks which had a corresponding meshbuffer for this pass
u32 blocks_had_pass_meshbuf = 0;
//u32 blocks_had_pass_meshbuf = 0;
// Blocks from which stuff was actually drawn
u32 blocks_without_stuff = 0;
/*
Collect a set of blocks for drawing
*/
core::map<v3s16, MapBlock*> drawset;
{
ScopeProfiler sp(g_profiler, prefix+"collecting blocks for drawing", SPT_AVG);
//u32 blocks_without_stuff = 0;
for(core::map<v2s16, MapSector*>::Iterator
si = m_sectors.getIterator();
@@ -380,36 +340,10 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
&& d > m_control.wanted_min_range * BS)
continue;
// Mesh animation
{
//JMutexAutoLock lock(block->mesh_mutex);
MapBlockMesh *mapBlockMesh = block->mesh;
// Pretty random but this should work somewhat nicely
bool faraway = d >= BS*50;
//bool faraway = d >= m_control.wanted_range * BS;
if(mapBlockMesh->isAnimationForced() ||
!faraway ||
mesh_animate_count_far < (m_control.range_all ? 200 : 50))
{
bool animated = mapBlockMesh->animate(
faraway,
animation_time,
crack,
daynight_ratio);
if(animated)
mesh_animate_count++;
if(animated && faraway)
mesh_animate_count_far++;
}
else
{
mapBlockMesh->decreaseAnimationForceTimer();
}
}
// Add to set
drawset[block->getPos()] = block;
block->refGrab();
m_drawlist[block->getPos()] = block;
sector_blocks_drawn++;
blocks_drawn++;
@@ -418,8 +352,127 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
if(sector_blocks_drawn != 0)
m_last_drawn_sectors[sp] = true;
}
} // ScopeProfiler
g_profiler->avg("CM: blocks in range", blocks_in_range);
g_profiler->avg("CM: blocks occlusion culled", blocks_occlusion_culled);
if(blocks_in_range != 0)
g_profiler->avg("CM: blocks in range without mesh (frac)",
(float)blocks_in_range_without_mesh/blocks_in_range);
g_profiler->avg("CM: blocks drawn", blocks_drawn);
}
struct MeshBufList
{
video::SMaterial m;
core::list<scene::IMeshBuffer*> bufs;
};
struct MeshBufListList
{
core::list<MeshBufList> lists;
void clear()
{
lists.clear();
}
void add(scene::IMeshBuffer *buf)
{
for(core::list<MeshBufList>::Iterator i = lists.begin();
i != lists.end(); i++){
MeshBufList &l = *i;
if(l.m == buf->getMaterial()){
l.bufs.push_back(buf);
return;
}
}
MeshBufList l;
l.m = buf->getMaterial();
l.bufs.push_back(buf);
lists.push_back(l);
}
};
void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
{
DSTACK(__FUNCTION_NAME);
bool is_transparent_pass = pass == scene::ESNRP_TRANSPARENT;
std::string prefix;
if(pass == scene::ESNRP_SOLID)
prefix = "CM: solid: ";
else
prefix = "CM: transparent: ";
/*
This is called two times per frame, reset on the non-transparent one
*/
if(pass == scene::ESNRP_SOLID)
{
m_last_drawn_sectors.clear();
}
/*
Get time for measuring timeout.
Measuring time is very useful for long delays when the
machine is swapping a lot.
*/
int time1 = time(0);
/*
Get animation parameters
*/
float animation_time = m_client->getAnimationTime();
int crack = m_client->getCrackLevel();
u32 daynight_ratio = m_client->getEnv().getDayNightRatio();
m_camera_mutex.Lock();
v3f camera_position = m_camera_position;
v3f camera_direction = m_camera_direction;
f32 camera_fov = m_camera_fov;
m_camera_mutex.Unlock();
/*
Get all blocks and draw all visible ones
*/
v3s16 cam_pos_nodes = floatToInt(camera_position, BS);
v3s16 box_nodes_d = m_control.wanted_range * v3s16(1,1,1);
v3s16 p_nodes_min = cam_pos_nodes - box_nodes_d;
v3s16 p_nodes_max = cam_pos_nodes + box_nodes_d;
// Take a fair amount as we will be dropping more out later
// Umm... these additions are a bit strange but they are needed.
v3s16 p_blocks_min(
p_nodes_min.X / MAP_BLOCKSIZE - 3,
p_nodes_min.Y / MAP_BLOCKSIZE - 3,
p_nodes_min.Z / MAP_BLOCKSIZE - 3);
v3s16 p_blocks_max(
p_nodes_max.X / MAP_BLOCKSIZE + 1,
p_nodes_max.Y / MAP_BLOCKSIZE + 1,
p_nodes_max.Z / MAP_BLOCKSIZE + 1);
u32 vertex_count = 0;
u32 meshbuffer_count = 0;
// For limiting number of mesh animations per frame
u32 mesh_animate_count = 0;
u32 mesh_animate_count_far = 0;
// Blocks that had mesh that would have been drawn according to
// rendering range (if max blocks limit didn't kick in)
u32 blocks_would_have_drawn = 0;
// Blocks that were drawn and had a mesh
u32 blocks_drawn = 0;
// Blocks which had a corresponding meshbuffer for this pass
u32 blocks_had_pass_meshbuf = 0;
// Blocks from which stuff was actually drawn
u32 blocks_without_stuff = 0;
/*
Draw the selected MapBlocks
*/
@@ -427,10 +480,90 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
{
ScopeProfiler sp(g_profiler, prefix+"drawing blocks", SPT_AVG);
int timecheck_counter = 0;
MeshBufListList drawbufs;
for(core::map<v3s16, MapBlock*>::Iterator
i = drawset.getIterator();
i = m_drawlist.getIterator();
i.atEnd() == false; i++)
{
MapBlock *block = i.getNode()->getValue();
// If the mesh of the block happened to get deleted, ignore it
if(block->mesh == NULL)
continue;
float d = 0.0;
if(isBlockInSight(block->getPos(), camera_position,
camera_direction, camera_fov,
100000*BS, &d) == false)
{
continue;
}
// Mesh animation
{
//JMutexAutoLock lock(block->mesh_mutex);
MapBlockMesh *mapBlockMesh = block->mesh;
assert(mapBlockMesh);
// Pretty random but this should work somewhat nicely
bool faraway = d >= BS*50;
//bool faraway = d >= m_control.wanted_range * BS;
if(mapBlockMesh->isAnimationForced() ||
!faraway ||
mesh_animate_count_far < (m_control.range_all ? 200 : 50))
{
bool animated = mapBlockMesh->animate(
faraway,
animation_time,
crack,
daynight_ratio);
if(animated)
mesh_animate_count++;
if(animated && faraway)
mesh_animate_count_far++;
}
else
{
mapBlockMesh->decreaseAnimationForceTimer();
}
}
/*
Get the meshbuffers of the block
*/
{
//JMutexAutoLock lock(block->mesh_mutex);
MapBlockMesh *mapBlockMesh = block->mesh;
assert(mapBlockMesh);
scene::SMesh *mesh = mapBlockMesh->getMesh();
assert(mesh);
u32 c = mesh->getMeshBufferCount();
for(u32 i=0; i<c; i++)
{
scene::IMeshBuffer *buf = mesh->getMeshBuffer(i);
const video::SMaterial& material = buf->getMaterial();
video::IMaterialRenderer* rnd =
driver->getMaterialRenderer(material.MaterialType);
bool transparent = (rnd && rnd->isTransparent());
if(transparent == is_transparent_pass)
{
if(buf->getVertexCount() == 0)
errorstream<<"Block ["<<analyze_block(block)
<<"] contains an empty meshbuf"<<std::endl;
drawbufs.add(buf);
}
}
}
}
core::list<MeshBufList> &lists = drawbufs.lists;
int timecheck_counter = 0;
for(core::list<MeshBufList>::Iterator i = lists.begin();
i != lists.end(); i++)
{
{
timecheck_counter++;
@@ -447,9 +580,20 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
}
}
}
MapBlock *block = i.getNode()->getValue();
MeshBufList &list = *i;
driver->setMaterial(list.m);
for(core::list<scene::IMeshBuffer*>::Iterator j = list.bufs.begin();
j != list.bufs.end(); j++)
{
scene::IMeshBuffer *buf = *j;
driver->drawMeshBuffer(buf);
vertex_count += buf->getVertexCount();
meshbuffer_count++;
}
#if 0
/*
Draw the faces of the block
*/
@@ -494,17 +638,12 @@ void ClientMap::renderMap(video::IVideoDriver* driver, s32 pass)
else
blocks_without_stuff++;
}
#endif
}
} // ScopeProfiler
// Log only on solid pass because values are the same
if(pass == scene::ESNRP_SOLID){
g_profiler->avg("CM: blocks in range", blocks_in_range);
g_profiler->avg("CM: blocks occlusion culled", blocks_occlusion_culled);
if(blocks_in_range != 0)
g_profiler->avg("CM: blocks in range without mesh (frac)",
(float)blocks_in_range_without_mesh/blocks_in_range);
g_profiler->avg("CM: blocks drawn", blocks_drawn);
g_profiler->avg("CM: animated meshes", mesh_animate_count);
g_profiler->avg("CM: animated meshes (far)", mesh_animate_count_far);
}

View File

@@ -113,7 +113,8 @@ class ClientMap : public Map, public scene::ISceneNode
{
return m_box;
}
void updateDrawList(video::IVideoDriver* driver);
void renderMap(video::IVideoDriver* driver, s32 pass);
int getBackgroundBrightness(float max_d, u32 daylight_factor,
@@ -141,6 +142,8 @@ class ClientMap : public Map, public scene::ISceneNode
v3f m_camera_direction;
f32 m_camera_fov;
JMutex m_camera_mutex;
core::map<v3s16, MapBlock*> m_drawlist;
core::map<v2s16, bool> m_last_drawn_sectors;
};

View File

@@ -58,10 +58,18 @@ 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
PROTOCOL_VERSION 13:
InventoryList field "Width" (deserialization fails with old versions)
*/
#define PROTOCOL_VERSION 11
#define PROTOCOL_VERSION 13
#define PROTOCOL_ID 0x4f457403
@@ -316,6 +324,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 +526,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

@@ -211,6 +211,8 @@ collisionMoveResult collisionMoveSimple(Map *map, IGameDef *gamedef,
std::vector<aabb3f> cboxes;
std::vector<bool> is_unloaded;
std::vector<bool> is_step_up;
std::vector<int> bouncy_values;
std::vector<v3s16> node_positions;
{
//TimeTaker tt2("collisionMoveSimple collect boxes");
ScopeProfiler sp(g_profiler, "collisionMoveSimple collect boxes avg", SPT_AVG);
@@ -228,11 +230,14 @@ collisionMoveResult collisionMoveSimple(Map *map, IGameDef *gamedef,
for(s16 y = min_y; y <= max_y; y++)
for(s16 z = min_z; z <= max_z; z++)
{
v3s16 p(x,y,z);
try{
// Object collides into walkable nodes
MapNode n = map->getNode(v3s16(x,y,z));
if(gamedef->getNodeDefManager()->get(n).walkable == false)
MapNode n = map->getNode(p);
const ContentFeatures &f = gamedef->getNodeDefManager()->get(n);
if(f.walkable == false)
continue;
int n_bouncy_value = itemgroup_get(f.groups, "bouncy");
std::vector<aabb3f> nodeboxes = n.getNodeBoxes(gamedef->ndef());
for(std::vector<aabb3f>::iterator
@@ -245,21 +250,27 @@ collisionMoveResult collisionMoveSimple(Map *map, IGameDef *gamedef,
cboxes.push_back(box);
is_unloaded.push_back(false);
is_step_up.push_back(false);
bouncy_values.push_back(n_bouncy_value);
node_positions.push_back(p);
}
}
catch(InvalidPositionException &e)
{
// Collide with unloaded nodes
aabb3f box = getNodeBox(v3s16(x,y,z), BS);
aabb3f box = getNodeBox(p, BS);
cboxes.push_back(box);
is_unloaded.push_back(true);
is_step_up.push_back(false);
bouncy_values.push_back(0);
node_positions.push_back(p);
}
}
} // tt2
assert(cboxes.size() == is_unloaded.size());
assert(cboxes.size() == is_step_up.size());
assert(cboxes.size() == bouncy_values.size());
assert(cboxes.size() == node_positions.size());
/*
Collision detection
@@ -342,6 +353,10 @@ collisionMoveResult collisionMoveSimple(Map *map, IGameDef *gamedef,
cbox.MaxEdge.Y - movingbox.MinEdge.Y,
d));
// Get bounce multiplier
bool bouncy = (bouncy_values[nearest_boxindex] >= 1);
float bounce = -(float)bouncy_values[nearest_boxindex] / 100.0;
// Move to the point of collision and reduce dtime by nearest_dtime
if(nearest_dtime < 0)
{
@@ -361,30 +376,58 @@ collisionMoveResult collisionMoveSimple(Map *map, IGameDef *gamedef,
pos_f += speed_f * nearest_dtime;
dtime -= nearest_dtime;
}
bool is_collision = true;
if(is_unloaded[nearest_boxindex])
is_collision = false;
CollisionInfo info;
info.type = COLLISION_NODE;
info.node_p = node_positions[nearest_boxindex];
info.bouncy = bouncy;
info.old_speed = speed_f;
// Set the speed component that caused the collision to zero
if(step_up)
{
// Special case: Handle stairs
is_step_up[nearest_boxindex] = true;
is_collision = false;
}
else if(nearest_collided == 0) // X
{
speed_f.X = 0;
if(fabs(speed_f.X) > BS*3)
speed_f.X *= bounce;
else
speed_f.X = 0;
result.collides = true;
result.collides_xz = true;
}
else if(nearest_collided == 1) // Y
{
speed_f.Y = 0;
if(fabs(speed_f.Y) > BS*3)
speed_f.Y *= bounce;
else
speed_f.Y = 0;
result.collides = true;
}
else if(nearest_collided == 2) // Z
{
speed_f.Z = 0;
if(fabs(speed_f.Z) > BS*3)
speed_f.Z *= bounce;
else
speed_f.Z = 0;
result.collides = true;
result.collides_xz = true;
}
info.new_speed = speed_f;
if(info.new_speed.getDistanceFrom(info.old_speed) < 0.1*BS)
is_collision = false;
if(is_collision){
result.collisions.push_back(info);
}
}
}

View File

@@ -26,12 +26,35 @@ with this program; if not, write to the Free Software Foundation, Inc.,
class Map;
class IGameDef;
enum CollisionType
{
COLLISION_NODE
};
struct CollisionInfo
{
enum CollisionType type;
v3s16 node_p; // COLLISION_NODE
bool bouncy;
v3f old_speed;
v3f new_speed;
CollisionInfo():
type(COLLISION_NODE),
node_p(-32768,-32768,-32768),
bouncy(false),
old_speed(0,0,0),
new_speed(0,0,0)
{}
};
struct collisionMoveResult
{
bool touching_ground;
bool collides;
bool collides_xz;
bool standing_on_unloaded;
std::vector<CollisionInfo> collisions;
collisionMoveResult():
touching_ground(false),
@@ -72,16 +95,5 @@ bool wouldCollideWithCeiling(
f32 y_increase, f32 d);
enum CollisionType
{
COLLISION_FALL
};
struct CollisionInfo
{
CollisionType t;
f32 speed;
};
#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

@@ -52,6 +52,7 @@ void set_default_settings(Settings *settings)
settings->setDefault("keymap_toggle_profiler", "KEY_F6");
settings->setDefault("keymap_increase_viewing_range_min", "+");
settings->setDefault("keymap_decrease_viewing_range_min", "-");
settings->setDefault("aux1_descends", "false");
// Some (temporary) keys for debugging
settings->setDefault("keymap_print_debug_stacks", "KEY_KEY_P");
settings->setDefault("keymap_quicktune_prev", "KEY_HOME");
@@ -122,6 +123,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,30 @@ 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);
p = i->first + block->getPosRelative();
if(scriptapi_node_on_timer(m_lua,p,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 +1135,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())
@@ -1998,19 +2034,32 @@ void ClientEnvironment::step(float dtime)
i != player_collisions.end(); i++)
{
CollisionInfo &info = *i;
if(info.t == COLLISION_FALL)
v3f speed_diff = info.new_speed - info.old_speed;;
// Handle only fall damage
// (because otherwise walking against something in fast_move kills you)
if(speed_diff.Y < 0 || info.old_speed.Y >= 0)
continue;
// Get rid of other components
speed_diff.X = 0;
speed_diff.Z = 0;
f32 pre_factor = 1; // 1 hp per node/s
f32 tolerance = BS*14; // 5 without damage
f32 post_factor = 1; // 1 hp per node/s
if(info.type == COLLISION_NODE)
{
//f32 tolerance = BS*10; // 2 without damage
//f32 tolerance = BS*12; // 3 without damage
f32 tolerance = BS*14; // 5 without damage
f32 factor = 1;
if(info.speed > tolerance)
{
f32 damage_f = (info.speed - tolerance)/BS*factor;
u16 damage = (u16)(damage_f+0.5);
if(damage != 0)
damageLocalPlayer(damage, true);
}
const ContentFeatures &f = m_gamedef->ndef()->
get(m_map->getNodeNoEx(info.node_p));
// Determine fall damage multiplier
int addp = itemgroup_get(f.groups, "fall_damage_add_percent");
pre_factor = 1.0 + (float)addp/100.0;
}
float speed = pre_factor * speed_diff.getLength();
if(speed > tolerance)
{
f32 damage_f = (speed - tolerance)/BS * post_factor;
u16 damage = (u16)(damage_f+0.5);
if(damage != 0)
damageLocalPlayer(damage, true);
}
}

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;
@@ -1205,6 +1235,9 @@ void the_game(
float object_hit_delay_timer = 0.0;
float time_from_last_punch = 10;
float update_draw_list_timer = 0.0;
v3f update_draw_list_last_cam_dir;
bool invert_mouse = g_settings->getBool("invert_mouse");
bool respawn_menu_active = false;
@@ -1479,8 +1512,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 +1523,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 +2253,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 +2279,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 +2307,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);
@@ -2657,7 +2700,19 @@ void the_game(
item = mlist->getItem(client.getPlayerItem());
camera.wield(item);
}
/*
Update block draw list every 200ms or when camera direction has
changed much
*/
update_draw_list_timer += dtime;
if(update_draw_list_timer >= 0.2 ||
update_draw_list_last_cam_dir.getDistanceFrom(camera_direction) > 0.2){
update_draw_list_timer = 0;
client.getEnv().getClientMap().updateDrawList(driver);
update_draw_list_last_cam_dir = camera_direction;
}
/*
Drawing begins
*/

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,8 +731,56 @@ void GUIInventoryMenu::drawMenu()
gui::IGUIElement::draw();
}
void GUIInventoryMenu::updateSelectedItem()
void GUIFormSpecMenu::updateSelectedItem()
{
// WARNING: BLACK MAGIC
// See if there is a stack suited for our current guess.
// If such stack does not exist, clear the guess.
if(m_selected_content_guess.name != "")
{
bool found = false;
for(u32 i=0; i<m_inventorylists.size() && !found; i++){
const ListDrawSpec &s = m_inventorylists[i];
Inventory *inv = m_invmgr->getInventory(s.inventoryloc);
if(!inv)
continue;
InventoryList *list = inv->getList(s.listname);
if(!list)
continue;
for(s32 i=0; i<s.geom.X*s.geom.Y && !found; i++){
u32 item_i = i + s.start_item_i;
if(item_i >= list->getSize())
continue;
ItemStack stack = list->getItem(item_i);
if(stack.name == m_selected_content_guess.name &&
stack.count == m_selected_content_guess.count){
found = true;
if(m_selected_item){
// If guessed stack is already selected, all is fine
if(m_selected_item->inventoryloc == s.inventoryloc &&
m_selected_item->listname == s.listname &&
m_selected_item->i == (s32)item_i &&
m_selected_amount == stack.count){
break;
}
delete m_selected_item;
m_selected_item = NULL;
}
infostream<<"Client: Changing selected content guess to "
<<s.inventoryloc.dump()<<" "<<s.listname
<<" "<<item_i<<std::endl;
m_selected_item = new ItemSpec(s.inventoryloc, s.listname, item_i);
m_selected_amount = stack.count;
break;
}
}
}
if(!found){
infostream<<"Client: Discarding selected content guess: "
<<m_selected_content_guess.getItemString()<<std::endl;
m_selected_content_guess.name = "";
}
}
// If the selected stack has become empty for some reason, deselect it.
// If the selected stack has become smaller, adjust m_selected_amount.
if(m_selected_item)
@@ -558,7 +846,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 +886,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 +938,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
@@ -779,21 +1102,28 @@ bool GUIInventoryMenu::OnEvent(const SEvent& event)
// Check how many items can be moved
move_amount = stack_from.count = MYMIN(move_amount, stack_from.count);
ItemStack leftover = stack_to.addItem(stack_from, m_gamedef->idef());
if(leftover.count == stack_from.count)
// If source stack cannot be added to destination stack at all,
// they are swapped
if(leftover.count == stack_from.count && leftover.name == stack_from.name)
{
// Swap the stacks
m_selected_amount -= stack_to.count;
m_selected_amount = stack_to.count;
// In case the server doesn't directly swap them but instead
// moves stack_to somewhere else, set this
m_selected_content_guess = stack_to;
m_selected_content_guess_inventory = s.inventoryloc;
}
// Source stack goes fully into destination stack
else if(leftover.empty())
{
// Item fits
m_selected_amount -= move_amount;
m_selected_content_guess = ItemStack(); // Clear
}
// Source stack goes partly into destination stack
else
{
// Item only fits partially
move_amount -= leftover.count;
m_selected_amount -= move_amount;
m_selected_content_guess = ItemStack(); // Clear
}
infostream<<"Handing IACTION_MOVE to manager"<<std::endl;
@@ -809,6 +1139,8 @@ bool GUIInventoryMenu::OnEvent(const SEvent& event)
}
else if(drop_amount > 0)
{
m_selected_content_guess = ItemStack(); // Clear
// Send IACTION_DROP
assert(m_selected_item && m_selected_item->isValid());
@@ -832,6 +1164,8 @@ bool GUIInventoryMenu::OnEvent(const SEvent& event)
}
else if(craft_amount > 0)
{
m_selected_content_guess = ItemStack(); // Clear
// Send IACTION_CRAFT
assert(s.isValid());
@@ -851,6 +1185,7 @@ bool GUIInventoryMenu::OnEvent(const SEvent& event)
m_selected_item = NULL;
m_selected_amount = 0;
m_selected_dragging = false;
m_selected_content_guess = ItemStack();
}
}
if(event.EventType==EET_GUI_EVENT)
@@ -860,7 +1195,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 +1203,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,13 +203,21 @@ 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;
bool m_selected_dragging;
// WARNING: BLACK MAGIC
// Used to guess and keep up with some special things the server can do.
// If name is "", no guess exists.
ItemStack m_selected_content_guess;
InventoryLocation m_selected_content_guess_inventory;
v2s32 m_pointer;
gui::IGUIStaticText *m_tooltip_element;

View File

@@ -30,11 +30,14 @@
#include <IGUIStaticText.h>
#include <IGUIFont.h>
#include "settings.h"
#include <algorithm>
#define KMaxButtonPerColumns 12
enum
{
GUI_ID_BACK_BUTTON = 101, GUI_ID_ABORT_BUTTON, GUI_ID_SCROLL_BAR,
//buttons
// buttons
GUI_ID_KEY_FORWARD_BUTTON,
GUI_ID_KEY_BACKWARD_BUTTON,
GUI_ID_KEY_LEFT_BUTTON,
@@ -50,15 +53,21 @@ enum
GUI_ID_KEY_DROP_BUTTON,
GUI_ID_KEY_INVENTORY_BUTTON,
GUI_ID_KEY_DUMP_BUTTON,
GUI_ID_KEY_RANGE_BUTTON
GUI_ID_KEY_RANGE_BUTTON,
// other
GUI_ID_CB_AUX1_DESCENDS,
};
GUIKeyChangeMenu::GUIKeyChangeMenu(gui::IGUIEnvironment* env,
gui::IGUIElement* parent, s32 id, IMenuManager *menumgr) :
GUIModalMenu(env, parent, id, menumgr)
gui::IGUIElement* parent, s32 id, IMenuManager *menumgr) :
GUIModalMenu(env, parent, id, menumgr)
{
shift_down = false;
activeKey = -1;
this->key_used_text = NULL;
init_keys();
for(size_t i=0; i<key_settings.size(); i++)
this->key_used.push_back(key_settings.at(i)->key);
}
GUIKeyChangeMenu::~GUIKeyChangeMenu()
@@ -71,12 +80,12 @@ void GUIKeyChangeMenu::removeChildren()
const core::list<gui::IGUIElement*> &children = getChildren();
core::list<gui::IGUIElement*> children_copy;
for (core::list<gui::IGUIElement*>::ConstIterator i = children.begin(); i
!= children.end(); i++)
!= children.end(); i++)
{
children_copy.push_back(*i);
}
for (core::list<gui::IGUIElement*>::Iterator i = children_copy.begin(); i
!= children_copy.end(); i++)
!= children_copy.end(); i++)
{
(*i)->remove();
}
@@ -84,20 +93,12 @@ void GUIKeyChangeMenu::removeChildren()
void GUIKeyChangeMenu::regenerateGui(v2u32 screensize)
{
/*
Remove stuff
*/
removeChildren();
/*
Calculate new sizes and positions
*/
v2s32 size(620, 430);
core::rect < s32 > rect(screensize.X / 2 - size.X / 2,
screensize.Y / 2 - size.Y / 2, screensize.X / 2 + size.X / 2,
screensize.Y / 2 + size.Y / 2);
screensize.Y / 2 - size.Y / 2, screensize.X / 2 + size.X / 2,
screensize.Y / 2 + size.Y / 2);
DesiredRect = rect;
recalculateAbsolutePosition(false);
@@ -109,271 +110,60 @@ void GUIKeyChangeMenu::regenerateGui(v2u32 screensize)
rect += topleft + v2s32(25, 3);
//gui::IGUIStaticText *t =
Environment->addStaticText(wgettext("Keybindings. (If this menu screws up, remove stuff from minetest.conf)"),
rect, false, true, this, -1);
rect, false, true, this, -1);
//t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
}
// Build buttons
v2s32 offset(25, 60);
// buttons
for(size_t i = 0; i < key_settings.size(); i++)
{
core::rect < s32 > rect(0, 0, 100, 20);
rect += topleft + v2s32(offset.X, offset.Y);
Environment->addStaticText(wgettext("Forward"),
rect, false, true, this, -1);
//t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
key_setting *k = key_settings.at(i);
{
core::rect < s32 > rect(0, 0, 100, 20);
rect += topleft + v2s32(offset.X, offset.Y);
Environment->addStaticText(k->button_name, rect, false, true, this, -1);
}
{
core::rect < s32 > rect(0, 0, 100, 30);
rect += topleft + v2s32(offset.X + 105, offset.Y - 5);
k->button = Environment->addButton(rect, this, k->id, wgettext(k->key.name()));
}
if(i + 1 == KMaxButtonPerColumns)
offset = v2s32(250, 60);
else
offset += v2s32(0, 25);
}
{
s32 option_x = offset.X + 10;
s32 option_y = offset.Y;
u32 option_w = 180;
{
core::rect<s32> rect(0, 0, option_w, 30);
rect += topleft + v2s32(option_x, option_y);
Environment->addCheckBox(g_settings->getBool("aux1_descends"), rect, this,
GUI_ID_CB_AUX1_DESCENDS, wgettext("\"Use\" = climb down"));
}
}
{
core::rect < s32 > rect(0, 0, 100, 30);
rect += topleft + v2s32(offset.X + 105, offset.Y - 5);
this->forward = Environment->addButton(rect, this,
GUI_ID_KEY_FORWARD_BUTTON,
wgettext(key_forward.name()));
}
offset += v2s32(0, 25);
{
core::rect < s32 > rect(0, 0, 100, 20);
rect += topleft + v2s32(offset.X, offset.Y);
Environment->addStaticText(wgettext("Backward"),
rect, false, true, this, -1);
//t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
}
{
core::rect < s32 > rect(0, 0, 100, 30);
rect += topleft + v2s32(offset.X + 105, offset.Y - 5);
this->backward = Environment->addButton(rect, this,
GUI_ID_KEY_BACKWARD_BUTTON,
wgettext(key_backward.name()));
}
offset += v2s32(0, 25);
{
core::rect < s32 > rect(0, 0, 100, 20);
rect += topleft + v2s32(offset.X, offset.Y);
Environment->addStaticText(wgettext("Left"),
rect, false, true, this, -1);
//t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
}
{
core::rect < s32 > rect(0, 0, 100, 30);
rect += topleft + v2s32(offset.X + 105, offset.Y - 5);
this->left = Environment->addButton(rect, this, GUI_ID_KEY_LEFT_BUTTON,
wgettext(key_left.name()));
}
offset += v2s32(0, 25);
{
core::rect < s32 > rect(0, 0, 100, 20);
rect += topleft + v2s32(offset.X, offset.Y);
Environment->addStaticText(wgettext("Right"),
rect, false, true, this, -1);
//t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
}
{
core::rect < s32 > rect(0, 0, 100, 30);
rect += topleft + v2s32(offset.X + 105, offset.Y - 5);
this->right = Environment->addButton(rect, this,
GUI_ID_KEY_RIGHT_BUTTON,
wgettext(key_right.name()));
}
offset += v2s32(0, 25);
{
core::rect < s32 > rect(0, 0, 100, 20);
rect += topleft + v2s32(offset.X, offset.Y);
Environment->addStaticText(wgettext("Use"),
rect, false, true, this, -1);
//t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
}
{
core::rect < s32 > rect(0, 0, 100, 30);
rect += topleft + v2s32(offset.X + 105, offset.Y - 5);
this->use = Environment->addButton(rect, this, GUI_ID_KEY_USE_BUTTON,
wgettext(key_use.name()));
}
offset += v2s32(0, 25);
{
core::rect < s32 > rect(0, 0, 100, 20);
rect += topleft + v2s32(offset.X, offset.Y);
Environment->addStaticText(wgettext("Sneak"),
rect, false, true, this, -1);
//t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
}
{
core::rect < s32 > rect(0, 0, 100, 30);
rect += topleft + v2s32(offset.X + 105, offset.Y - 5);
this->sneak = Environment->addButton(rect, this,
GUI_ID_KEY_SNEAK_BUTTON,
wgettext(key_sneak.name()));
}
offset += v2s32(0, 25);
{
core::rect < s32 > rect(0, 0, 100, 20);
rect += topleft + v2s32(offset.X, offset.Y);
Environment->addStaticText(wgettext("Jump"), rect, false, true, this, -1);
//t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
}
{
core::rect < s32 > rect(0, 0, 100, 30);
rect += topleft + v2s32(offset.X + 105, offset.Y - 5);
this->jump = Environment->addButton(rect, this, GUI_ID_KEY_JUMP_BUTTON,
wgettext(key_jump.name()));
}
offset += v2s32(0, 25);
{
core::rect < s32 > rect(0, 0, 100, 20);
rect += topleft + v2s32(offset.X, offset.Y);
Environment->addStaticText(wgettext("Drop"), rect, false, true, this, -1);
//t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
}
{
core::rect < s32 > rect(0, 0, 100, 30);
rect += topleft + v2s32(offset.X + 105, offset.Y - 5);
this->dropbtn = Environment->addButton(rect, this, GUI_ID_KEY_DROP_BUTTON,
wgettext(key_drop.name()));
}
offset += v2s32(0, 25);
{
core::rect < s32 > rect(0, 0, 100, 20);
rect += topleft + v2s32(offset.X, offset.Y);
Environment->addStaticText(wgettext("Inventory"),
rect, false, true, this, -1);
//t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
}
{
core::rect < s32 > rect(0, 0, 100, 30);
rect += topleft + v2s32(offset.X + 105, offset.Y - 5);
this->inventory = Environment->addButton(rect, this,
GUI_ID_KEY_INVENTORY_BUTTON,
wgettext(key_inventory.name()));
}
offset += v2s32(0, 25);
{
core::rect < s32 > rect(0, 0, 100, 20);
rect += topleft + v2s32(offset.X, offset.Y);
Environment->addStaticText(wgettext("Chat"), rect, false, true, this, -1);
//t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
}
{
core::rect < s32 > rect(0, 0, 100, 30);
rect += topleft + v2s32(offset.X + 105, offset.Y - 5);
this->chat = Environment->addButton(rect, this, GUI_ID_KEY_CHAT_BUTTON,
wgettext(key_chat.name()));
}
offset += v2s32(0, 25);
{
core::rect < s32 > rect(0, 0, 100, 20);
rect += topleft + v2s32(offset.X, offset.Y);
Environment->addStaticText(wgettext("Command"), rect, false, true, this, -1);
//t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
}
{
core::rect < s32 > rect(0, 0, 100, 30);
rect += topleft + v2s32(offset.X + 105, offset.Y - 5);
this->cmd = Environment->addButton(rect, this, GUI_ID_KEY_CMD_BUTTON,
wgettext(key_cmd.name()));
}
offset += v2s32(0, 25);
{
core::rect < s32 > rect(0, 0, 100, 20);
rect += topleft + v2s32(offset.X, offset.Y);
Environment->addStaticText(wgettext("Console"), rect, false, true, this, -1);
//t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
}
{
core::rect < s32 > rect(0, 0, 100, 30);
rect += topleft + v2s32(offset.X + 105, offset.Y - 5);
this->console = Environment->addButton(rect, this, GUI_ID_KEY_CONSOLE_BUTTON,
wgettext(key_console.name()));
}
//next col
offset = v2s32(250, 60);
{
core::rect < s32 > rect(0, 0, 100, 20);
rect += topleft + v2s32(offset.X, offset.Y);
Environment->addStaticText(wgettext("Toggle fly"),
rect, false, true, this, -1);
//t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
}
{
core::rect < s32 > rect(0, 0, 100, 30);
rect += topleft + v2s32(offset.X + 105, offset.Y - 5);
this->fly = Environment->addButton(rect, this, GUI_ID_KEY_FLY_BUTTON,
wgettext(key_fly.name()));
}
offset += v2s32(0, 25);
{
core::rect < s32 > rect(0, 0, 100, 20);
rect += topleft + v2s32(offset.X, offset.Y);
Environment->addStaticText(wgettext("Toggle fast"),
rect, false, true, this, -1);
//t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
}
{
core::rect < s32 > rect(0, 0, 100, 30);
rect += topleft + v2s32(offset.X + 105, offset.Y - 5);
this->fast = Environment->addButton(rect, this, GUI_ID_KEY_FAST_BUTTON,
wgettext(key_fast.name()));
}
offset += v2s32(0, 25);
{
core::rect < s32 > rect(0, 0, 100, 20);
rect += topleft + v2s32(offset.X, offset.Y);
Environment->addStaticText(wgettext("Range select"),
rect, false, true, this, -1);
//t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
}
{
core::rect < s32 > rect(0, 0, 100, 30);
rect += topleft + v2s32(offset.X + 105, offset.Y - 5);
this->range = Environment->addButton(rect, this,
GUI_ID_KEY_RANGE_BUTTON,
wgettext(key_range.name()));
}
offset += v2s32(0, 25);
{
core::rect < s32 > rect(0, 0, 100, 20);
rect += topleft + v2s32(offset.X, offset.Y);
Environment->addStaticText(wgettext("Print stacks"),
rect, false, true, this, -1);
//t->setTextAlignment(gui::EGUIA_CENTER, gui::EGUIA_UPPERLEFT);
}
{
core::rect < s32 > rect(0, 0, 100, 30);
rect += topleft + v2s32(offset.X + 105, offset.Y - 5);
this->dump = Environment->addButton(rect, this, GUI_ID_KEY_DUMP_BUTTON,
wgettext(key_dump.name()));
}
{
core::rect < s32 > rect(0, 0, 100, 30);
rect += topleft + v2s32(size.X - 100 - 20, size.Y - 40);
Environment->addButton(rect, this, GUI_ID_BACK_BUTTON,
wgettext("Save"));
wgettext("Save"));
}
{
core::rect < s32 > rect(0, 0, 100, 30);
rect += topleft + v2s32(size.X - 100 - 20 - 100 - 20, size.Y - 40);
Environment->addButton(rect, this, GUI_ID_ABORT_BUTTON,
wgettext("Cancel"));
wgettext("Cancel"));
}
changeCtype("C");
}
void GUIKeyChangeMenu::drawMenu()
@@ -396,102 +186,32 @@ void GUIKeyChangeMenu::drawMenu()
bool GUIKeyChangeMenu::acceptInput()
{
g_settings->set("keymap_forward", key_forward.sym());
g_settings->set("keymap_backward", key_backward.sym());
g_settings->set("keymap_left", key_left.sym());
g_settings->set("keymap_right", key_right.sym());
g_settings->set("keymap_jump", key_jump.sym());
g_settings->set("keymap_sneak", key_sneak.sym());
g_settings->set("keymap_drop", key_drop.sym());
g_settings->set("keymap_inventory", key_inventory.sym());
g_settings->set("keymap_chat", key_chat.sym());
g_settings->set("keymap_cmd", key_cmd.sym());
g_settings->set("keymap_console", key_console.sym());
g_settings->set("keymap_rangeselect", key_range.sym());
g_settings->set("keymap_freemove", key_fly.sym());
g_settings->set("keymap_fastmove", key_fast.sym());
g_settings->set("keymap_special1", key_use.sym());
g_settings->set("keymap_print_debug_stacks", key_dump.sym());
for(size_t i = 0; i < key_settings.size(); i++)
{
key_setting *k = key_settings.at(i);
g_settings->set(k->setting_name, k->key.sym());
}
{
gui::IGUIElement *e = getElementFromId(GUI_ID_CB_AUX1_DESCENDS);
if(e != NULL && e->getType() == gui::EGUIET_CHECK_BOX)
g_settings->setBool("aux1_descends", ((gui::IGUICheckBox*)e)->isChecked());
}
clearKeyCache();
return true;
}
void GUIKeyChangeMenu::init_keys()
{
key_forward = getKeySetting("keymap_forward");
key_backward = getKeySetting("keymap_backward");
key_left = getKeySetting("keymap_left");
key_right = getKeySetting("keymap_right");
key_jump = getKeySetting("keymap_jump");
key_sneak = getKeySetting("keymap_sneak");
key_drop = getKeySetting("keymap_drop");
key_inventory = getKeySetting("keymap_inventory");
key_chat = getKeySetting("keymap_chat");
key_cmd = getKeySetting("keymap_cmd");
key_console = getKeySetting("keymap_console");
key_range = getKeySetting("keymap_rangeselect");
key_fly = getKeySetting("keymap_freemove");
key_fast = getKeySetting("keymap_fastmove");
key_use = getKeySetting("keymap_special1");
key_dump = getKeySetting("keymap_print_debug_stacks");
}
bool GUIKeyChangeMenu::resetMenu()
{
if (activeKey >= 0)
{
switch (activeKey)
for(size_t i = 0; i < key_settings.size(); i++)
{
case GUI_ID_KEY_FORWARD_BUTTON:
this->forward->setText(
wgettext(key_forward.name()));
break;
case GUI_ID_KEY_BACKWARD_BUTTON:
this->backward->setText(
wgettext(key_backward.name()));
break;
case GUI_ID_KEY_LEFT_BUTTON:
this->left->setText(wgettext(key_left.name()));
break;
case GUI_ID_KEY_RIGHT_BUTTON:
this->right->setText(wgettext(key_right.name()));
break;
case GUI_ID_KEY_JUMP_BUTTON:
this->jump->setText(wgettext(key_jump.name()));
break;
case GUI_ID_KEY_SNEAK_BUTTON:
this->sneak->setText(wgettext(key_sneak.name()));
break;
case GUI_ID_KEY_DROP_BUTTON:
this->dropbtn->setText(wgettext(key_drop.name()));
break;
case GUI_ID_KEY_INVENTORY_BUTTON:
this->inventory->setText(
wgettext(key_inventory.name()));
break;
case GUI_ID_KEY_CHAT_BUTTON:
this->chat->setText(wgettext(key_chat.name()));
break;
case GUI_ID_KEY_CMD_BUTTON:
this->cmd->setText(wgettext(key_cmd.name()));
break;
case GUI_ID_KEY_CONSOLE_BUTTON:
this->console->setText(wgettext(key_console.name()));
break;
case GUI_ID_KEY_RANGE_BUTTON:
this->range->setText(wgettext(key_range.name()));
break;
case GUI_ID_KEY_FLY_BUTTON:
this->fly->setText(wgettext(key_fly.name()));
break;
case GUI_ID_KEY_FAST_BUTTON:
this->fast->setText(wgettext(key_fast.name()));
break;
case GUI_ID_KEY_USE_BUTTON:
this->use->setText(wgettext(key_use.name()));
break;
case GUI_ID_KEY_DUMP_BUTTON:
this->dump->setText(wgettext(key_dump.name()));
break;
key_setting *k = key_settings.at(i);
if(k->id == activeKey)
{
k->button->setText(wgettext(k->key.name()));
break;
}
}
activeKey = -1;
return false;
@@ -501,104 +221,72 @@ bool GUIKeyChangeMenu::resetMenu()
bool GUIKeyChangeMenu::OnEvent(const SEvent& event)
{
if (event.EventType == EET_KEY_INPUT_EVENT && activeKey >= 0
&& event.KeyInput.PressedDown)
&& event.KeyInput.PressedDown)
{
changeCtype("");
KeyPress kp(event.KeyInput);
bool prefer_character = shift_down;
KeyPress kp(event.KeyInput, prefer_character);
bool shift_went_down = false;
if(!shift_down &&
(event.KeyInput.Key == irr::KEY_SHIFT ||
event.KeyInput.Key == irr::KEY_LSHIFT ||
event.KeyInput.Key == irr::KEY_RSHIFT))
shift_went_down = true;
if (activeKey == GUI_ID_KEY_FORWARD_BUTTON)
// Remove Key already in use message
if(this->key_used_text)
{
this->forward->setText(wgettext(kp.name()));
this->key_forward = kp;
this->key_used_text->remove();
this->key_used_text = NULL;
}
else if (activeKey == GUI_ID_KEY_BACKWARD_BUTTON)
// Display Key already in use message
if (std::find(this->key_used.begin(), this->key_used.end(), kp) != this->key_used.end())
{
this->backward->setText(wgettext(kp.name()));
this->key_backward = kp;
core::rect < s32 > rect(0, 0, 600, 40);
rect += v2s32(0, 0) + v2s32(25, 30);
this->key_used_text = Environment->addStaticText(wgettext("Key already in use"),
rect, false, true, this, -1);
//infostream << "Key already in use" << std::endl;
}
else if (activeKey == GUI_ID_KEY_LEFT_BUTTON)
// But go on
{
this->left->setText(wgettext(kp.name()));
this->key_left = kp;
key_setting *k=NULL;
for(size_t i = 0; i < key_settings.size(); i++)
{
if(key_settings.at(i)->id == activeKey)
{
k = key_settings.at(i);
break;
}
}
assert(k);
k->key = kp;
k->button->setText(wgettext(k->key.name()));
this->key_used.push_back(kp);
changeCtype("C");
// Allow characters made with shift
if(shift_went_down){
shift_down = true;
return false;
}else{
activeKey = -1;
return true;
}
}
else if (activeKey == GUI_ID_KEY_RIGHT_BUTTON)
{
this->right->setText(wgettext(kp.name()));
this->key_right = kp;
}
else if (activeKey == GUI_ID_KEY_JUMP_BUTTON)
{
this->jump->setText(wgettext(kp.name()));
this->key_jump = kp;
}
else if (activeKey == GUI_ID_KEY_SNEAK_BUTTON)
{
this->sneak->setText(wgettext(kp.name()));
this->key_sneak = kp;
}
else if (activeKey == GUI_ID_KEY_DROP_BUTTON)
{
this->dropbtn->setText(wgettext(kp.name()));
this->key_drop = kp;
}
else if (activeKey == GUI_ID_KEY_INVENTORY_BUTTON)
{
this->inventory->setText(wgettext(kp.name()));
this->key_inventory = kp;
}
else if (activeKey == GUI_ID_KEY_CHAT_BUTTON)
{
this->chat->setText(wgettext(kp.name()));
this->key_chat = kp;
}
else if (activeKey == GUI_ID_KEY_CMD_BUTTON)
{
this->cmd->setText(wgettext(kp.name()));
this->key_cmd = kp;
}
else if (activeKey == GUI_ID_KEY_CONSOLE_BUTTON)
{
this->console->setText(wgettext(kp.name()));
this->key_console = kp;
}
else if (activeKey == GUI_ID_KEY_RANGE_BUTTON)
{
this->range->setText(wgettext(kp.name()));
this->key_range = kp;
}
else if (activeKey == GUI_ID_KEY_FLY_BUTTON)
{
this->fly->setText(wgettext(kp.name()));
this->key_fly = kp;
}
else if (activeKey == GUI_ID_KEY_FAST_BUTTON)
{
this->fast->setText(wgettext(kp.name()));
this->key_fast = kp;
}
else if (activeKey == GUI_ID_KEY_USE_BUTTON)
{
this->use->setText(wgettext(kp.name()));
this->key_use = kp;
}
else if (activeKey == GUI_ID_KEY_DUMP_BUTTON)
{
this->dump->setText(wgettext(kp.name()));
this->key_dump = kp;
}
changeCtype("C");
activeKey = -1;
return true;
}
if (event.EventType == EET_GUI_EVENT)
{
if (event.GUIEvent.EventType == gui::EGET_ELEMENT_FOCUS_LOST
&& isVisible())
&& isVisible())
{
if (!canTakeFocus(event.GUIEvent.Element))
{
dstream << "GUIMainMenu: Not allowing focus change."
<< std::endl;
<< std::endl;
// Returning true disables focus change
return true;
}
@@ -606,106 +294,74 @@ bool GUIKeyChangeMenu::OnEvent(const SEvent& event)
if (event.GUIEvent.EventType == gui::EGET_BUTTON_CLICKED)
{
if(event.GUIEvent.Caller->getID() != GUI_ID_BACK_BUTTON &&
event.GUIEvent.Caller->getID() != GUI_ID_ABORT_BUTTON)
event.GUIEvent.Caller->getID() != GUI_ID_ABORT_BUTTON)
{
changeCtype("");
}
switch (event.GUIEvent.Caller->getID())
{
case GUI_ID_BACK_BUTTON: //back
acceptInput();
quitMenu();
return true;
case GUI_ID_ABORT_BUTTON: //abort
quitMenu();
return true;
case GUI_ID_KEY_FORWARD_BUTTON:
resetMenu();
activeKey = event.GUIEvent.Caller->getID();
this->forward->setText(wgettext("press Key"));
break;
case GUI_ID_KEY_BACKWARD_BUTTON:
resetMenu();
activeKey = event.GUIEvent.Caller->getID();
this->backward->setText(wgettext("press Key"));
break;
case GUI_ID_KEY_LEFT_BUTTON:
resetMenu();
activeKey = event.GUIEvent.Caller->getID();
this->left->setText(wgettext("press Key"));
break;
case GUI_ID_KEY_RIGHT_BUTTON:
resetMenu();
activeKey = event.GUIEvent.Caller->getID();
this->right->setText(wgettext("press Key"));
break;
case GUI_ID_KEY_USE_BUTTON:
resetMenu();
activeKey = event.GUIEvent.Caller->getID();
this->use->setText(wgettext("press Key"));
break;
case GUI_ID_KEY_FLY_BUTTON:
resetMenu();
activeKey = event.GUIEvent.Caller->getID();
this->fly->setText(wgettext("press Key"));
break;
case GUI_ID_KEY_FAST_BUTTON:
resetMenu();
activeKey = event.GUIEvent.Caller->getID();
this->fast->setText(wgettext("press Key"));
break;
case GUI_ID_KEY_JUMP_BUTTON:
resetMenu();
activeKey = event.GUIEvent.Caller->getID();
this->jump->setText(wgettext("press Key"));
break;
case GUI_ID_KEY_DROP_BUTTON:
resetMenu();
activeKey = event.GUIEvent.Caller->getID();
this->dropbtn->setText(wgettext("press Key"));
break;
case GUI_ID_KEY_CHAT_BUTTON:
resetMenu();
activeKey = event.GUIEvent.Caller->getID();
this->chat->setText(wgettext("press Key"));
break;
case GUI_ID_KEY_CMD_BUTTON:
resetMenu();
activeKey = event.GUIEvent.Caller->getID();
this->cmd->setText(wgettext("press Key"));
break;
case GUI_ID_KEY_CONSOLE_BUTTON:
resetMenu();
activeKey = event.GUIEvent.Caller->getID();
this->console->setText(wgettext("press Key"));
break;
case GUI_ID_KEY_SNEAK_BUTTON:
resetMenu();
activeKey = event.GUIEvent.Caller->getID();
this->sneak->setText(wgettext("press Key"));
break;
case GUI_ID_KEY_INVENTORY_BUTTON:
resetMenu();
activeKey = event.GUIEvent.Caller->getID();
this->inventory->setText(wgettext("press Key"));
break;
case GUI_ID_KEY_DUMP_BUTTON:
resetMenu();
activeKey = event.GUIEvent.Caller->getID();
this->dump->setText(wgettext("press Key"));
break;
case GUI_ID_KEY_RANGE_BUTTON:
resetMenu();
activeKey = event.GUIEvent.Caller->getID();
this->range->setText(wgettext("press Key"));
break;
case GUI_ID_BACK_BUTTON: //back
acceptInput();
quitMenu();
return true;
case GUI_ID_ABORT_BUTTON: //abort
quitMenu();
return true;
default:
key_setting *k = NULL;
for(size_t i = 0; i < key_settings.size(); i++)
{
if(key_settings.at(i)->id == event.GUIEvent.Caller->getID())
{
k = key_settings.at(i);
break;
}
}
assert(k);
resetMenu();
shift_down = false;
activeKey = event.GUIEvent.Caller->getID();
k->button->setText(wgettext("press key"));
this->key_used.erase(std::remove(this->key_used.begin(),
this->key_used.end(), k->key), this->key_used.end());
break;
}
Environment->setFocus(this);
//Buttons
changeCtype("C");
}
}
return Parent ? Parent->OnEvent(event) : false;
}
void GUIKeyChangeMenu::add_key(int id, std::string button_name, std::string setting_name)
{
key_setting *k = new key_setting;
k->id = id;
k->button_name = wgettext(button_name.c_str());
k->setting_name = setting_name;
k->key = getKeySetting(k->setting_name.c_str());
key_settings.push_back(k);
}
void GUIKeyChangeMenu::init_keys()
{
this->add_key(GUI_ID_KEY_FORWARD_BUTTON, "Forward", "keymap_forward");
this->add_key(GUI_ID_KEY_BACKWARD_BUTTON, "Backward", "keymap_backward");
this->add_key(GUI_ID_KEY_LEFT_BUTTON, "Left", "keymap_left");
this->add_key(GUI_ID_KEY_RIGHT_BUTTON, "Right", "keymap_right");
this->add_key(GUI_ID_KEY_USE_BUTTON, "Use", "keymap_special1");
this->add_key(GUI_ID_KEY_JUMP_BUTTON, "Jump", "keymap_jump");
this->add_key(GUI_ID_KEY_SNEAK_BUTTON, "Sneak", "keymap_sneak");
this->add_key(GUI_ID_KEY_DROP_BUTTON, "Drop", "keymap_drop");
this->add_key(GUI_ID_KEY_INVENTORY_BUTTON, "Inventory", "keymap_inventory");
this->add_key(GUI_ID_KEY_CHAT_BUTTON, "Chat", "keymap_chat");
this->add_key(GUI_ID_KEY_CMD_BUTTON, "Command", "keymap_cmd");
this->add_key(GUI_ID_KEY_CONSOLE_BUTTON, "Console", "keymap_console");
this->add_key(GUI_ID_KEY_FLY_BUTTON, "Toggle fly", "keymap_freemove");
this->add_key(GUI_ID_KEY_FAST_BUTTON, "Toggle fast", "keymap_fastmove");
this->add_key(GUI_ID_KEY_RANGE_BUTTON, "Range select", "keymap_rangeselect");
this->add_key(GUI_ID_KEY_DUMP_BUTTON, "Print stacks", "keymap_print_debug_stacks");
}

View File

@@ -28,6 +28,16 @@
#include "gettext.h"
#include "keycode.h"
#include <string>
#include <vector>
typedef struct {
int id;
wchar_t *button_name;
KeyPress key;
std::string setting_name;
gui::IGUIButton *button;
} key_setting;
class GUIKeyChangeMenu: public GUIModalMenu
{
@@ -54,40 +64,15 @@ class GUIKeyChangeMenu: public GUIModalMenu
bool resetMenu();
gui::IGUIButton *forward;
gui::IGUIButton *backward;
gui::IGUIButton *left;
gui::IGUIButton *right;
gui::IGUIButton *use;
gui::IGUIButton *sneak;
gui::IGUIButton *jump;
gui::IGUIButton *dropbtn;
gui::IGUIButton *inventory;
gui::IGUIButton *fly;
gui::IGUIButton *fast;
gui::IGUIButton *range;
gui::IGUIButton *dump;
gui::IGUIButton *chat;
gui::IGUIButton *cmd;
gui::IGUIButton *console;
void add_key(int id, std::string setting_name, std::string button_name);
bool shift_down;
s32 activeKey;
KeyPress key_forward;
KeyPress key_backward;
KeyPress key_left;
KeyPress key_right;
KeyPress key_use;
KeyPress key_sneak;
KeyPress key_jump;
KeyPress key_drop;
KeyPress key_inventory;
KeyPress key_fly;
KeyPress key_fast;
KeyPress key_range;
KeyPress key_chat;
KeyPress key_cmd;
KeyPress key_console;
KeyPress key_dump;
std::vector<KeyPress> key_used;
gui::IGUIStaticText *key_used_text;
std::vector<key_setting *> key_settings;
};
#endif

View File

@@ -39,6 +39,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "tile.h" // getTexturePath
#include "filesys.h"
#include "util/string.h"
#include "subgame.h"
struct CreateWorldDestMainMenu : public CreateWorldDest
{
@@ -47,6 +48,22 @@ struct CreateWorldDestMainMenu : public CreateWorldDest
{}
void accepted(std::wstring name, std::string gameid)
{
std::string name_narrow = wide_to_narrow(name);
if(!string_allowed_blacklist(name_narrow, WORLDNAME_BLACKLISTED_CHARS))
{
m_menu->displayMessageMenu(wgettext("Cannot create world: Name contains invalid characters"));
return;
}
std::vector<WorldSpec> worlds = getAvailableWorlds();
for(std::vector<WorldSpec>::iterator i = worlds.begin();
i != worlds.end(); i++)
{
if((*i).name == name_narrow)
{
m_menu->displayMessageMenu(wgettext("Cannot create world: A world by this name already exists"));
return;
}
}
m_menu->createNewWorld(name, gameid);
}
GUIMainMenu *m_menu;
@@ -929,3 +946,7 @@ int GUIMainMenu::getTab()
return TAB_SINGLEPLAYER; // Default
}
void GUIMainMenu::displayMessageMenu(std::wstring msg)
{
(new GUIMessageMenu(env, parent, -1, menumgr, msg))->drop();
}

View File

@@ -92,6 +92,7 @@ class GUIMainMenu : public GUIModalMenu
void createNewWorld(std::wstring name, std::string gameid);
void deleteWorld(const std::vector<std::string> &paths);
int getTab();
void displayMessageMenu(std::wstring msg);
private:
MainMenuData *m_data;

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

@@ -431,6 +431,7 @@ InventoryList::InventoryList(std::string name, u32 size, IItemDefManager *itemde
{
m_name = name;
m_size = size;
m_width = 0;
m_itemdef = itemdef;
clearItems();
//m_dirty = false;
@@ -459,6 +460,11 @@ void InventoryList::setSize(u32 newsize)
m_size = newsize;
}
void InventoryList::setWidth(u32 newwidth)
{
m_width = newwidth;
}
void InventoryList::setName(const std::string &name)
{
m_name = name;
@@ -468,6 +474,8 @@ void InventoryList::serialize(std::ostream &os) const
{
//os.imbue(std::locale("C"));
os<<"Width "<<m_width<<"\n";
for(u32 i=0; i<m_items.size(); i++)
{
const ItemStack &item = m_items[i];
@@ -492,6 +500,7 @@ void InventoryList::deSerialize(std::istream &is)
clearItems();
u32 item_i = 0;
m_width = 0;
for(;;)
{
@@ -513,6 +522,12 @@ void InventoryList::deSerialize(std::istream &is)
{
break;
}
else if(name == "Width")
{
iss >> m_width;
if (iss.fail())
throw SerializationError("incorrect width property");
}
else if(name == "Item")
{
if(item_i > getSize() - 1)
@@ -527,10 +542,6 @@ void InventoryList::deSerialize(std::istream &is)
throw SerializationError("too many items");
m_items[item_i++].clear();
}
else
{
throw SerializationError("Unknown inventory identifier");
}
}
}
@@ -543,6 +554,7 @@ InventoryList & InventoryList::operator = (const InventoryList &other)
{
m_items = other.m_items;
m_size = other.m_size;
m_width = other.m_width;
m_name = other.m_name;
m_itemdef = other.m_itemdef;
//setDirty(true);
@@ -560,6 +572,11 @@ u32 InventoryList::getSize() const
return m_items.size();
}
u32 InventoryList::getWidth() const
{
return m_width;
}
u32 InventoryList::getUsedSlots() const
{
u32 num = 0;
@@ -886,10 +903,6 @@ void Inventory::deSerialize(std::istream &is)
m_lists.push_back(list);
}
else
{
throw SerializationError("Unknown inventory identifier");
}
}
}

View File

@@ -176,6 +176,7 @@ class InventoryList
~InventoryList();
void clearItems();
void setSize(u32 newsize);
void setWidth(u32 newWidth);
void setName(const std::string &name);
void serialize(std::ostream &os) const;
void deSerialize(std::istream &is);
@@ -185,6 +186,7 @@ class InventoryList
const std::string &getName() const;
u32 getSize() const;
u32 getWidth() const;
// Count used slots
u32 getUsedSlots() const;
u32 getFreeSlots() const;
@@ -240,7 +242,7 @@ class InventoryList
private:
std::vector<ItemStack> m_items;
u32 m_size;
u32 m_size, m_width;
std::string m_name;
IItemDefManager *m_itemdef;
};

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,156 @@ 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){
// If destination stack is of different type and there are leftover
// items, attempt to put the leftover items to a different place in the
// destination inventory.
// The client-side GUI will try to guess if this happens.
if(from_stack_was.name != to_stack_was.name){
for(u32 i=0; i<list_to->getSize(); i++){
if(list_to->getItem(i).empty()){
list_to->changeItem(i, to_stack_was);
break;
}
}
}
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->deleteItem(from_i);
list_from->addItem(from_i, from_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 +546,70 @@ 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);
item1.count = take_count;
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 +617,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)
@@ -531,18 +783,16 @@ bool getCraftingResult(Inventory *inv, ItemStack& result,
result.clear();
// TODO: Allow different sizes of crafting grids
// Get the InventoryList in which we will operate
InventoryList *clist = inv->getList("craft");
if(!clist || clist->getSize() != 9)
if(!clist)
return false;
// Mangle crafting grid to an another format
CraftInput ci;
ci.method = CRAFT_METHOD_NORMAL;
ci.width = 3;
for(u16 i=0; i<9; i++)
ci.width = clist->getWidth() ? clist->getWidth() : 3;
for(u16 i=0; i<clist->getSize(); i++)
ci.items.push_back(clist->getItem(i));
// Find out what is crafted and add it to result item slot
@@ -555,7 +805,7 @@ bool getCraftingResult(Inventory *inv, ItemStack& result,
if(found && decrementInput)
{
// CraftInput has been changed, apply changes in clist
for(u16 i=0; i<9; i++)
for(u16 i=0; i<clist->getSize(); i++)
{
clist->changeItem(i, ci.items[i]);
}

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,34 @@ struct InventoryLocation
type = NODEMETA;
p = p_;
}
void setDetached(const std::string &name_)
{
type = DETACHED;
name = name_;
}
bool operator==(const InventoryLocation &other) const
{
if(type != other.type)
return false;
switch(type){
case UNDEFINED:
return false;
case CURRENT_PLAYER:
return true;
case PLAYER:
return (name == other.name);
case NODEMETA:
return (p == other.p);
case DETACHED:
return (name == other.name);
}
return false;
}
bool operator!=(const InventoryLocation &other) const
{
return !(*this == other);
}
void applyCurrentPlayer(const std::string &name_)
{
@@ -80,13 +109,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

@@ -21,6 +21,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "main.h" // For g_settings
#include "exceptions.h"
#include "settings.h"
#include "log.h"
#include "hex.h"
class UnknownKeycode : public BaseException
{
@@ -286,16 +288,30 @@ KeyPress::KeyPress(const char *name)
m_name = name[0];
}
KeyPress::KeyPress(const irr::SEvent::SKeyInput &in)
KeyPress::KeyPress(const irr::SEvent::SKeyInput &in, bool prefer_character)
{
Key = in.Key;
Char = in.Char;
if(prefer_character){
m_name.resize(MB_CUR_MAX+1, '\0');
int written = wctomb(&m_name[0], Char);
if(written > 0){
infostream<<"KeyPress: Preferring character for "<<m_name<<std::endl;
Key = irr::KEY_KEY_CODES_COUNT;
return;
}
}
if (valid_kcode(Key)) {
m_name = KeyNames[Key];
} else {
m_name.resize(MB_CUR_MAX+1, '\0');
int written = wctomb(&m_name[0], Char);
assert (written >= 0 && "unexpected multibyte character");
if(written < 0){
std::string hexstr = hex_encode((const char*)&Char, sizeof(Char));
errorstream<<"KeyPress: Unexpected multibyte character "<<hexstr<<std::endl;
}
}
}

View File

@@ -33,7 +33,7 @@ class KeyPress
KeyPress();
KeyPress(const char *name);
KeyPress(const irr::SEvent::SKeyInput &in);
KeyPress(const irr::SEvent::SKeyInput &in, bool prefer_character=false);
bool operator==(const KeyPress &o) const
{

View File

@@ -38,7 +38,8 @@ LocalPlayer::LocalPlayer(IGameDef *gamedef):
m_sneak_node_exists(false),
m_old_node_below(32767,32767,32767),
m_old_node_below_type("air"),
m_need_to_get_new_sneak_node(true)
m_need_to_get_new_sneak_node(true),
m_can_jump(false)
{
// Initialize hp to 0, so that no hearts will be shown if server
// doesn't support health points
@@ -149,13 +150,16 @@ void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d,
position.X = rangelim(position.X, lwn_f.X-maxd, lwn_f.X+maxd);
position.Z = rangelim(position.Z, lwn_f.Z-maxd, lwn_f.Z+maxd);
f32 min_y = lwn_f.Y + 0.5*BS;
if(position.Y < min_y)
if(!is_climbing)
{
position.Y = min_y;
f32 min_y = lwn_f.Y + 0.5*BS;
if(position.Y < min_y)
{
position.Y = min_y;
if(m_speed.Y < 0)
m_speed.Y = 0;
if(m_speed.Y < 0)
m_speed.Y = 0;
}
}
}
@@ -188,7 +192,7 @@ void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d,
bool touching_ground_was = touching_ground;
touching_ground = result.touching_ground;
bool standing_on_unloaded = result.standing_on_unloaded;
//bool standing_on_unloaded = result.standing_on_unloaded;
/*
Check the nodes under the player to see from which node the
@@ -281,18 +285,25 @@ void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d,
/*
Report collisions
*/
bool bouncy_jump = false;
if(collision_info)
{
// Report fall collision
if(old_speed.Y < m_speed.Y - 0.1 && !standing_on_unloaded)
{
CollisionInfo info;
info.t = COLLISION_FALL;
info.speed = m_speed.Y - old_speed.Y;
for(size_t i=0; i<result.collisions.size(); i++){
const CollisionInfo &info = result.collisions[i];
collision_info->push_back(info);
if(info.new_speed.Y - info.old_speed.Y > 0.1*BS &&
info.bouncy)
bouncy_jump = true;
}
}
if(bouncy_jump && control.jump){
m_speed.Y += 6.5*BS;
touching_ground = false;
MtEvent *e = new SimpleTriggerEvent("PlayerJump");
m_gamedef->event()->put(e);
}
if(!touching_ground_was && touching_ground){
MtEvent *e = new SimpleTriggerEvent("PlayerRegainGround");
m_gamedef->event()->put(e);
@@ -314,6 +325,15 @@ void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d,
*/
m_old_node_below = floatToInt(position - v3f(0,BS/2,0), BS);
m_old_node_below_type = nodemgr->get(map.getNodeNoEx(m_old_node_below)).name;
/*
Check properties of the node on which the player is standing
*/
const ContentFeatures &f = nodemgr->get(map.getNodeNoEx(getStandingNodePos()));
// Determine if jumping is possible
m_can_jump = touching_ground;
if(itemgroup_get(f.groups, "disable_jump"))
m_can_jump = false;
}
void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d)
@@ -359,31 +379,70 @@ void LocalPlayer::applyControl(float dtime)
if(free_move && fast_move)
superspeed = true;
// Auxiliary button 1 (E)
if(control.aux1)
// Old descend control
if(g_settings->getBool("aux1_descends"))
{
if(free_move)
// Auxiliary button 1 (E)
if(control.aux1)
{
// In free movement mode, aux1 descends
v3f speed = getSpeed();
if(fast_move)
speed.Y = -20*BS;
if(free_move)
{
// In free movement mode, aux1 descends
v3f speed = getSpeed();
if(fast_move)
speed.Y = -20*BS;
else
speed.Y = -walkspeed_max;
setSpeed(speed);
}
else if(is_climbing)
{
v3f speed = getSpeed();
speed.Y = -3*BS;
setSpeed(speed);
}
else
speed.Y = -walkspeed_max;
setSpeed(speed);
{
// If not free movement but fast is allowed, aux1 is
// "Turbo button"
if(fast_move)
superspeed = true;
}
}
else if(is_climbing)
}
// New minecraft-like descend control
else
{
// Auxiliary button 1 (E)
if(control.aux1)
{
v3f speed = getSpeed();
speed.Y = -3*BS;
setSpeed(speed);
if(!free_move && !is_climbing)
{
// If not free movement but fast is allowed, aux1 is
// "Turbo button"
if(fast_move)
superspeed = true;
}
}
else
if(control.sneak)
{
// If not free movement but fast is allowed, aux1 is
// "Turbo button"
if(fast_move)
superspeed = true;
if(free_move)
{
// In free movement mode, sneak descends
v3f speed = getSpeed();
if(fast_move)
speed.Y = -20*BS;
else
speed.Y = -walkspeed_max;
setSpeed(speed);
}
else if(is_climbing)
{
v3f speed = getSpeed();
speed.Y = -3*BS;
setSpeed(speed);
}
}
}
@@ -420,7 +479,7 @@ void LocalPlayer::applyControl(float dtime)
speed.Y = walkspeed_max;
setSpeed(speed);
}
else if(touching_ground)
else if(m_can_jump)
{
/*
NOTE: The d value in move() affects jump height by

View File

@@ -101,6 +101,7 @@ class LocalPlayer : public Player
std::string m_old_node_below_type;
// Whether recalculation of the sneak node is needed
bool m_need_to_get_new_sneak_node;
bool m_can_jump;
};
#endif

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);
}
@@ -1435,8 +1468,8 @@ void Map::timerUpdate(float dtime, float unload_timeout,
MapBlock *block = (*i);
block->incrementUsageTimer(dtime);
if(block->getUsageTimer() > unload_timeout)
if(block->refGet() == 0 && block->getUsageTimer() > unload_timeout)
{
v3s16 p = block->getPos();
@@ -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

@@ -56,7 +56,8 @@ MapBlock::MapBlock(Map *parent, v3s16 pos, IGameDef *gamedef, bool dummy):
m_generated(false),
m_timestamp(BLOCK_TIMESTAMP_UNDEFINED),
m_disk_timestamp(BLOCK_TIMESTAMP_UNDEFINED),
m_usage_timer(0)
m_usage_timer(0),
m_refcount(0)
{
data = NULL;
if(dummy == false)
@@ -552,12 +553,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 +583,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 +593,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 +605,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 +613,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 +626,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 +663,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 +700,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 +727,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 +743,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

@@ -431,6 +431,42 @@ class MapBlock /*: public NodeContainer*/
return m_usage_timer;
}
/*
See m_refcount
*/
void refGrab()
{
m_refcount++;
}
void refDrop()
{
m_refcount--;
}
int refGet()
{
return m_refcount;
}
/*
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 +483,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);
/*
@@ -547,6 +582,12 @@ class MapBlock /*: public NodeContainer*/
Map will unload the block when this reaches a timeout.
*/
float m_usage_timer;
/*
Reference count; currently used for determining if this block is in
the list of blocks to be drawn.
*/
int m_refcount;
};
inline bool blockpos_over_limit(v3s16 p)

View File

@@ -1297,7 +1297,9 @@ BiomeType get_biome(u64 seed, v2s16 p2d)
double d = noise2d_perlin(
0.6+(float)p2d.X/250, 0.2+(float)p2d.Y/250,
seed+9130, 3, 0.50);
if(d > 0.35)
if(d > 0.45)
return BT_DESERT;
if(d > 0.35 && (noise2d( p2d.X, p2d.Y, int(seed) ) + 1.0) > ( 0.45 - d ) * 20.0 )
return BT_DESERT;
return BT_NORMAL;
};
@@ -1759,8 +1761,9 @@ void make_block(BlockMakeData *data)
vmanip.m_data[i] = airnode;
}
} else {
// Don't replace air or water or lava
if(vmanip.m_data[i].getContent() == CONTENT_AIR ||
// Don't replace air or water or lava or ignore
if(vmanip.m_data[i].getContent() == CONTENT_IGNORE ||
vmanip.m_data[i].getContent() == CONTENT_AIR ||
vmanip.m_data[i].getContent() == c_water_source ||
vmanip.m_data[i].getContent() == c_lava_source)
continue;

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 != "")
@@ -573,8 +556,6 @@ class CNodeDefManager: public IWritableNodeDefManager
f->solidness = 0;
} else {
f->solidness = 1;
if(f->alpha == 255)
f->solidness = 2;
f->backface_culling = false;
}
break;

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

@@ -45,12 +45,13 @@ Player::Player(IGameDef *gamedef):
updateName("<not set>");
inventory.clear();
inventory.addList("main", PLAYER_INVENTORY_SIZE);
inventory.addList("craft", 9);
InventoryList *craft = inventory.addList("craft", 9);
craft->setWidth(3);
inventory.addList("craftpreview", 1);
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

File diff suppressed because it is too large Load Diff

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

@@ -27,7 +27,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "constants.h"
#include "voxel.h"
#include "config.h"
#include "servercommand.h"
#include "filesys.h"
#include "mapblock.h"
#include "serverobject.h"
@@ -54,6 +53,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<<")"
@@ -442,9 +442,12 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime,
m_nearest_unsent_reset_timer += dtime;
if(m_nothing_to_send_pause_timer >= 0)
{
return;
}
Player *player = server->m_env->getPlayer(peer_id);
// This can happen sometimes; clients and players are not in perfect sync.
if(player == NULL)
return;
// Won't send anything if already sending
if(m_blocks_sending.size() >= g_settings->getU16
@@ -456,10 +459,6 @@ void RemoteClient::GetNextBlocks(Server *server, float dtime,
//TimeTaker timer("RemoteClient::GetNextBlocks");
Player *player = server->m_env->getPlayer(peer_id);
assert(player != NULL);
v3f playerpos = player->getPosition();
v3f playerspeed = player->getSpeed();
v3f playerspeeddir(0,0,0);
@@ -843,7 +842,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 +933,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 +975,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 +1055,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 +1158,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 +1167,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 +1872,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 +2270,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 +2492,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 +2559,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 +2577,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 +2601,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 +2630,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());
@@ -2671,36 +2651,16 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
// Whether to send to other players
bool send_to_others = false;
// Parse commands
// Commands are implemented in Lua, so only catch invalid
// commands that were not "eaten" and send an error back
if(message[0] == L'/')
{
size_t strip_size = 1;
if (message[1] == L'#') // support old-style commans
++strip_size;
message = message.substr(strip_size);
WStrfnd f1(message);
f1.next(L" "); // Skip over /#whatever
std::wstring paramstring = f1.next(L"");
ServerCommandContext *ctx = new ServerCommandContext(
str_split(message, L' '),
paramstring,
this,
m_env,
player);
std::wstring reply(processServerCommand(ctx));
send_to_sender = ctx->flags & SEND_TO_SENDER;
send_to_others = ctx->flags & SEND_TO_OTHERS;
if (ctx->flags & SEND_NO_PREFIX)
line += reply;
message = message.substr(1);
send_to_sender = true;
if(message.length() == 0)
line += L"-!- Empty command";
else
line += L"Server: " + reply;
delete ctx;
line += L"-!- Invalid command: " + str_split(message, L' ')[0];
}
else
{
@@ -2988,6 +2948,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 +3136,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 +3162,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 +3209,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 +3297,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 +3338,11 @@ void Server::setInventoryModified(const InventoryLocation &loc)
setBlockNotSent(blockpos);
}
break;
case InventoryLocation::DETACHED:
{
sendDetachedInventoryToAll(loc.name);
}
break;
default:
assert(0);
}
@@ -4293,6 +4300,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 +4398,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");
@@ -4377,9 +4427,10 @@ std::wstring Server::getStatusString()
// Uptime
os<<L", uptime="<<m_uptime.get();
// Information about clients
core::map<u16, RemoteClient*>::Iterator i;
bool first;
os<<L", clients={";
for(core::map<u16, RemoteClient*>::Iterator
i = m_clients.getIterator();
for(i = m_clients.getIterator(), first = true;
i.atEnd() == false; i++)
{
// Get client and check that it is valid
@@ -4394,7 +4445,11 @@ std::wstring Server::getStatusString()
if(player != NULL)
name = narrow_to_wide(player->getName());
// Add name to information string
os<<name<<L",";
if(!first)
os<<L",";
else
first = false;
os<<name;
}
os<<L"}";
if(((ServerMap*)(&m_env->getMap()))->isSavingEnabled() == false)
@@ -4477,6 +4532,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 +4644,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 +4676,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 +4813,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;
};
/*

View File

@@ -1,191 +0,0 @@
/*
Part of Minetest-c55
Copyright (C) 2010-11 celeron55, Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2011 Ciaran Gultnieks <ciaran@ciarang.com>
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "servercommand.h"
#include "settings.h"
#include "main.h" // For g_settings
#include "content_sao.h"
#define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")"
void cmd_status(std::wostringstream &os,
ServerCommandContext *ctx)
{
os<<ctx->server->getStatusString();
}
void cmd_me(std::wostringstream &os,
ServerCommandContext *ctx)
{
if(!ctx->server->checkPriv(ctx->player->getName(), "shout"))
{
os<<L"-!- You don't have permission to shout.";
return;
}
std::wstring name = narrow_to_wide(ctx->player->getName());
os << L"* " << name << L" " << ctx->paramstring;
ctx->flags |= SEND_TO_OTHERS | SEND_NO_PREFIX;
}
void cmd_time(std::wostringstream &os,
ServerCommandContext *ctx)
{
if(ctx->parms.size() != 2)
{
os<<L"-!- Missing parameter";
return;
}
if(!ctx->server->checkPriv(ctx->player->getName(), "settime"))
{
os<<L"-!- You don't have permission to do this.";
return;
}
u32 time = stoi(wide_to_narrow(ctx->parms[1]));
ctx->server->setTimeOfDay(time);
os<<L"-!- Time of day changed.";
actionstream<<ctx->player->getName()<<" sets time "
<<time<<std::endl;
}
void cmd_shutdown(std::wostringstream &os,
ServerCommandContext *ctx)
{
if(!ctx->server->checkPriv(ctx->player->getName(), "server"))
{
os<<L"-!- You don't have permission to do this.";
return;
}
actionstream<<ctx->player->getName()
<<" shuts down server"<<std::endl;
ctx->server->requestShutdown();
os<<L"*** Server shutting down (operator request).";
ctx->flags |= SEND_TO_OTHERS;
}
void cmd_banunban(std::wostringstream &os, ServerCommandContext *ctx)
{
if(!ctx->server->checkPriv(ctx->player->getName(), "ban"))
{
os<<L"-!- You don't have permission to do this.";
return;
}
if(ctx->parms.size() < 2)
{
std::string desc = ctx->server->getBanDescription("");
os<<L"-!- Ban list: "<<narrow_to_wide(desc);
return;
}
if(ctx->parms[0] == L"ban")
{
Player *player = ctx->env->getPlayer(wide_to_narrow(ctx->parms[1]).c_str());
if(player == NULL)
{
os<<L"-!- No such player";
return;
}
try{
Address address = ctx->server->getPeerAddress(player->peer_id);
std::string ip_string = address.serializeString();
ctx->server->setIpBanned(ip_string, player->getName());
os<<L"-!- Banned "<<narrow_to_wide(ip_string)<<L"|"
<<narrow_to_wide(player->getName());
actionstream<<ctx->player->getName()<<" bans "
<<player->getName()<<" / "<<ip_string<<std::endl;
} catch(con::PeerNotFoundException){
dstream<<__FUNCTION_NAME<<": peer was not found"<<std::endl;
}
}
else
{
std::string ip_or_name = wide_to_narrow(ctx->parms[1]);
std::string desc = ctx->server->getBanDescription(ip_or_name);
ctx->server->unsetIpBanned(ip_or_name);
os<<L"-!- Unbanned "<<narrow_to_wide(desc);
actionstream<<ctx->player->getName()<<" unbans "
<<ip_or_name<<std::endl;
}
}
void cmd_clearobjects(std::wostringstream &os,
ServerCommandContext *ctx)
{
if(!ctx->server->checkPriv(ctx->player->getName(), "server"))
{
os<<L"-!- You don't have permission to do this.";
return;
}
actionstream<<ctx->player->getName()
<<" clears all objects"<<std::endl;
{
std::wstring msg;
msg += L"Clearing all objects. This may take long.";
msg += L" You may experience a timeout. (by ";
msg += narrow_to_wide(ctx->player->getName());
msg += L")";
ctx->server->notifyPlayers(msg);
}
ctx->env->clearAllObjects();
actionstream<<"object clearing done"<<std::endl;
os<<L"*** Cleared all objects.";
ctx->flags |= SEND_TO_OTHERS;
}
std::wstring processServerCommand(ServerCommandContext *ctx)
{
std::wostringstream os(std::ios_base::binary);
ctx->flags = SEND_TO_SENDER; // Default, unless we change it.
if(ctx->parms.size() == 0)
os<<L"-!- Empty command";
else if(ctx->parms[0] == L"status")
cmd_status(os, ctx);
else if(ctx->parms[0] == L"time")
cmd_time(os, ctx);
else if(ctx->parms[0] == L"shutdown")
cmd_shutdown(os, ctx);
else if(ctx->parms[0] == L"ban" || ctx->parms[0] == L"unban")
cmd_banunban(os, ctx);
else if(ctx->parms[0] == L"me")
cmd_me(os, ctx);
else if(ctx->parms[0] == L"clearobjects")
cmd_clearobjects(os, ctx);
else
os<<L"-!- Invalid command: " + ctx->parms[0];
return os.str();
}

View File

@@ -1,62 +0,0 @@
/*
Part of Minetest-c55
Copyright (C) 2010-11 celeron55, Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2011 Ciaran Gultnieks <ciaran@ciarang.com>
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef SERVERCOMMAND_HEADER
#define SERVERCOMMAND_HEADER
#include <vector>
#include <sstream>
#include "irrlichttypes.h"
#include "player.h"
#include "server.h"
#define SEND_TO_SENDER (1<<0)
#define SEND_TO_OTHERS (1<<1)
#define SEND_NO_PREFIX (1<<2)
struct ServerCommandContext
{
std::vector<std::wstring> parms;
std::wstring paramstring;
Server* server;
ServerEnvironment *env;
Player* player;
u32 flags;
ServerCommandContext(
std::vector<std::wstring> parms,
std::wstring paramstring,
Server* server,
ServerEnvironment *env,
Player* player)
: parms(parms), paramstring(paramstring),
server(server), env(env), player(player)
{
}
};
// Process a command sent from a client. The environment and connection
// should be locked when this is called.
// Returns a response message, to be dealt with according to the flags set
// in the context.
std::wstring processServerCommand(ServerCommandContext *ctx);
#endif

View File

@@ -30,7 +30,7 @@ with this program; ifnot, write to the Free Software Foundation, Inc.,
#elif defined(__APPLE__)
#include <OpenAL/al.h>
#include <OpenAL/alc.h>
#include <OpenAL/alext.h>
//#include <OpenAL/alext.h>
#else
#include <AL/al.h>
#include <AL/alc.h>

View File

@@ -24,6 +24,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include <set>
#include <vector>
#define WORLDNAME_BLACKLISTED_CHARS "/\\"
struct SubgameSpec
{
std::string id; // "" = game does not exist

File diff suppressed because it is too large Load Diff

View File

@@ -1200,10 +1200,11 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg,
// Position to copy the blitted from in the blitted image
core::position2d<s32> pos_from(0,0);
// Blit
image->copyToWithAlpha(baseimg, pos_to,
/*image->copyToWithAlpha(baseimg, pos_to,
core::rect<s32>(pos_from, dim),
video::SColor(255,255,255,255),
NULL);
NULL);*/
blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
// Drop image
image->drop();
}
@@ -1344,7 +1345,11 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg,
u32 h0 = stoi(sf.next(":"));
infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
core::dimension2d<u32> dim(w0,h0);
baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
if(baseimg == NULL)
{
baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
baseimg->fill(video::SColor(0,0,0,0));
}
while(sf.atend() == false)
{
u32 x = stoi(sf.next(","));
@@ -1364,10 +1369,11 @@ bool generate_image(std::string part_of_name, video::IImage *& baseimg,
driver->createImage(video::ECF_A8R8G8B8, dim);
img->copyTo(img2);
img->drop();
img2->copyToWithAlpha(baseimg, pos_base,
/*img2->copyToWithAlpha(baseimg, pos_base,
core::rect<s32>(v2s32(0,0), dim),
video::SColor(255,255,255,255),
NULL);
NULL);*/
blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
img2->drop();
}
else

View File

@@ -242,6 +242,29 @@ inline bool string_allowed(const std::string &s, const std::string &allowed_char
return true;
}
/*
Checks if a string contains no blacklisted characters (opposite
function of string_allowed())
*/
inline bool string_allowed_blacklist(const std::string & s, const std::string & blacklisted_chars)
{
for(unsigned int i = 0; i < s.length(); i++)
{
bool invalid = false;
for(unsigned int j = 0; j < blacklisted_chars.length(); j++)
{
if(s[i] == blacklisted_chars[j])
{
invalid = true;
break;
}
}
if(invalid)
return false;
}
return true;
}
/*
Forcefully wraps string into rows using \n
(no word wrap, used for showing paths in gui)

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