mirror of https://github.com/minetest/minetest.git
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
a48a29511c
|
@ -12,7 +12,7 @@ set(VERSION_EXTRA "" CACHE STRING "Stuff to append to version string")
|
|||
# Also remember to set PROTOCOL_VERSION in clientserver.h when releasing
|
||||
set(VERSION_MAJOR 0)
|
||||
set(VERSION_MINOR 4)
|
||||
set(VERSION_PATCH 1)
|
||||
set(VERSION_PATCH 2-rc1)
|
||||
if(VERSION_EXTRA)
|
||||
set(VERSION_PATCH ${VERSION_PATCH}-${VERSION_EXTRA})
|
||||
endif()
|
||||
|
|
|
@ -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>",
|
||||
|
@ -496,3 +489,171 @@ minetest.register_chatcommand("pulverize", {
|
|||
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,
|
||||
})
|
||||
|
|
|
@ -227,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
|
||||
|
@ -262,7 +264,8 @@ 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 "
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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
|
||||
----------------------------------------------
|
||||
|
@ -883,6 +892,14 @@ minetest.get_craft_recipe(output) -> input
|
|||
stack 5, stack 6, stack 7, stack 8, stack 9 }
|
||||
^ input.items = nil if no recipe found
|
||||
|
||||
Rollbacks:
|
||||
minetest.rollback_get_last_node_actor(p, range, seconds) -> actor, p, seconds
|
||||
^ Find who has done something to a node, or near a node
|
||||
^ actor: "player:<name>", also "liquid".
|
||||
minetest.rollback_revert_actions_by(actor, seconds) -> bool, log messages
|
||||
^ Revert latest actions of someone
|
||||
^ actor: "player:<name>", also "liquid".
|
||||
|
||||
Defaults for the on_* item definition functions:
|
||||
(These return the leftover itemstack)
|
||||
minetest.item_place_node(itemstack, placer, pointed_thing)
|
||||
|
@ -914,6 +931,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
|
||||
|
@ -1003,6 +1030,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)
|
||||
|
@ -1092,6 +1121,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
|
||||
|
|
|
@ -1384,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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -153,6 +153,8 @@ configure_file(
|
|||
)
|
||||
|
||||
set(common_SRCS
|
||||
rollback_interface.cpp
|
||||
rollback.cpp
|
||||
genericobject.cpp
|
||||
voxelalgorithms.cpp
|
||||
sound.cpp
|
||||
|
@ -191,7 +193,6 @@ set(common_SRCS
|
|||
connection.cpp
|
||||
environment.cpp
|
||||
server.cpp
|
||||
servercommand.cpp
|
||||
socket.cpp
|
||||
mapblock.cpp
|
||||
mapsector.cpp
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
#include "settings.h"
|
||||
#include "mapblock.h" // For getNodeBlockPos
|
||||
#include "mapgen.h" // For mapgen::make_tree
|
||||
#include "map.h"
|
||||
|
||||
#define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")"
|
||||
|
||||
|
|
|
@ -39,6 +39,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
#include "util/numeric.h" // For IntervalLimiter
|
||||
#include "util/serialize.h"
|
||||
#include "util/mathconstants.h"
|
||||
#include "map.h"
|
||||
|
||||
class Settings;
|
||||
struct ToolCapabilities;
|
||||
|
|
|
@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
#include "environment.h"
|
||||
#include "gamedef.h"
|
||||
#include "log.h"
|
||||
#include "map.h"
|
||||
|
||||
static void setBillboardTextureMatrix(scene::IBillboardSceneNode *bill,
|
||||
float txs, float tys, int col, int row)
|
||||
|
|
120
src/craftdef.cpp
120
src/craftdef.cpp
|
@ -23,9 +23,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
#include "log.h"
|
||||
#include <sstream>
|
||||
#include <set>
|
||||
#include <algorithm>
|
||||
#include "gamedef.h"
|
||||
#include "inventory.h"
|
||||
#include "util/serialize.h"
|
||||
#include "strfnd.h"
|
||||
|
||||
// Check if input matches recipe
|
||||
// Takes recipe groups into account
|
||||
|
@ -38,9 +40,17 @@ static bool inputItemMatchesRecipe(const std::string &inp_name,
|
|||
|
||||
// Group
|
||||
if(rec_name.substr(0,6) == "group:" && idef->isKnown(inp_name)){
|
||||
std::string rec_group = rec_name.substr(6);
|
||||
const struct ItemDefinition &def = idef->get(inp_name);
|
||||
if(itemgroup_get(def.groups, rec_group) != 0)
|
||||
Strfnd f(rec_name.substr(6));
|
||||
bool all_groups_match = true;
|
||||
do{
|
||||
std::string check_group = f.next(",");
|
||||
if(itemgroup_get(def.groups, check_group) == 0){
|
||||
all_groups_match = false;
|
||||
break;
|
||||
}
|
||||
}while(!f.atend());
|
||||
if(all_groups_match)
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -140,6 +150,8 @@ static bool craftGetBounds(const std::vector<std::string> &items, unsigned int w
|
|||
return success;
|
||||
}
|
||||
|
||||
#if 0
|
||||
// This became useless when group support was added to shapeless recipes
|
||||
// Convert a list of item names to a multiset
|
||||
static std::multiset<std::string> craftMakeMultiset(const std::vector<std::string> &names)
|
||||
{
|
||||
|
@ -153,6 +165,7 @@ static std::multiset<std::string> craftMakeMultiset(const std::vector<std::strin
|
|||
}
|
||||
return set;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Removes 1 from each item stack
|
||||
static void craftDecrementInput(CraftInput &input, IGameDef *gamedef)
|
||||
|
@ -508,17 +521,48 @@ bool CraftDefinitionShapeless::check(const CraftInput &input, IGameDef *gamedef)
|
|||
{
|
||||
if(input.method != CRAFT_METHOD_NORMAL)
|
||||
return false;
|
||||
|
||||
// Filter empty items out of input
|
||||
std::vector<std::string> input_filtered;
|
||||
for(std::vector<ItemStack>::const_iterator
|
||||
i = input.items.begin();
|
||||
i != input.items.end(); i++)
|
||||
{
|
||||
if(i->name != "")
|
||||
input_filtered.push_back(i->name);
|
||||
}
|
||||
|
||||
// Get input item multiset
|
||||
std::vector<std::string> inp_names = craftGetItemNames(input.items, gamedef);
|
||||
std::multiset<std::string> inp_names_multiset = craftMakeMultiset(inp_names);
|
||||
// If there is a wrong number of items in input, no match
|
||||
if(input_filtered.size() != recipe.size()){
|
||||
/*dstream<<"Number of input items ("<<input_filtered.size()
|
||||
<<") does not match recipe size ("<<recipe.size()<<") "
|
||||
<<"of recipe with output="<<output<<std::endl;*/
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get recipe item multiset
|
||||
std::vector<std::string> rec_names = craftGetItemNames(recipe, gamedef);
|
||||
std::multiset<std::string> rec_names_multiset = craftMakeMultiset(rec_names);
|
||||
// Try with all permutations of the recipe
|
||||
std::vector<std::string> recipe_copy = recipe;
|
||||
// Start from the lexicographically first permutation (=sorted)
|
||||
std::sort(recipe_copy.begin(), recipe_copy.end());
|
||||
//while(std::prev_permutation(recipe_copy.begin(), recipe_copy.end())){}
|
||||
do{
|
||||
// If all items match, the recipe matches
|
||||
bool all_match = true;
|
||||
//dstream<<"Testing recipe (output="<<output<<"):";
|
||||
for(size_t i=0; i<recipe.size(); i++){
|
||||
//dstream<<" ("<<input_filtered[i]<<" == "<<recipe_copy[i]<<")";
|
||||
if(!inputItemMatchesRecipe(input_filtered[i], recipe_copy[i],
|
||||
gamedef->idef())){
|
||||
all_match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
//dstream<<" -> match="<<all_match<<std::endl;
|
||||
if(all_match)
|
||||
return true;
|
||||
}while(std::next_permutation(recipe_copy.begin(), recipe_copy.end()));
|
||||
|
||||
// Recipe is matched when the multisets coincide
|
||||
return inp_names_multiset == rec_names_multiset;
|
||||
return false;
|
||||
}
|
||||
|
||||
CraftOutput CraftDefinitionShapeless::getOutput(const CraftInput &input, IGameDef *gamedef) const
|
||||
|
@ -694,16 +738,26 @@ bool CraftDefinitionCooking::check(const CraftInput &input, IGameDef *gamedef) c
|
|||
if(input.method != CRAFT_METHOD_COOKING)
|
||||
return false;
|
||||
|
||||
// Get input item multiset
|
||||
std::vector<std::string> inp_names = craftGetItemNames(input.items, gamedef);
|
||||
std::multiset<std::string> inp_names_multiset = craftMakeMultiset(inp_names);
|
||||
// Filter empty items out of input
|
||||
std::vector<std::string> input_filtered;
|
||||
for(std::vector<ItemStack>::const_iterator
|
||||
i = input.items.begin();
|
||||
i != input.items.end(); i++)
|
||||
{
|
||||
if(i->name != "")
|
||||
input_filtered.push_back(i->name);
|
||||
}
|
||||
|
||||
// Get recipe item multiset
|
||||
std::multiset<std::string> rec_names_multiset;
|
||||
rec_names_multiset.insert(craftGetItemName(recipe, gamedef));
|
||||
|
||||
// Recipe is matched when the multisets coincide
|
||||
return inp_names_multiset == rec_names_multiset;
|
||||
// If there is a wrong number of items in input, no match
|
||||
if(input_filtered.size() != 1){
|
||||
/*dstream<<"Number of input items ("<<input_filtered.size()
|
||||
<<") does not match recipe size (1) "
|
||||
<<"of cooking recipe with output="<<output<<std::endl;*/
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check the single input item
|
||||
return inputItemMatchesRecipe(input_filtered[0], recipe, gamedef->idef());
|
||||
}
|
||||
|
||||
CraftOutput CraftDefinitionCooking::getOutput(const CraftInput &input, IGameDef *gamedef) const
|
||||
|
@ -765,16 +819,26 @@ bool CraftDefinitionFuel::check(const CraftInput &input, IGameDef *gamedef) cons
|
|||
if(input.method != CRAFT_METHOD_FUEL)
|
||||
return false;
|
||||
|
||||
// Get input item multiset
|
||||
std::vector<std::string> inp_names = craftGetItemNames(input.items, gamedef);
|
||||
std::multiset<std::string> inp_names_multiset = craftMakeMultiset(inp_names);
|
||||
// Filter empty items out of input
|
||||
std::vector<std::string> input_filtered;
|
||||
for(std::vector<ItemStack>::const_iterator
|
||||
i = input.items.begin();
|
||||
i != input.items.end(); i++)
|
||||
{
|
||||
if(i->name != "")
|
||||
input_filtered.push_back(i->name);
|
||||
}
|
||||
|
||||
// Get recipe item multiset
|
||||
std::multiset<std::string> rec_names_multiset;
|
||||
rec_names_multiset.insert(craftGetItemName(recipe, gamedef));
|
||||
|
||||
// Recipe is matched when the multisets coincide
|
||||
return inp_names_multiset == rec_names_multiset;
|
||||
// If there is a wrong number of items in input, no match
|
||||
if(input_filtered.size() != 1){
|
||||
/*dstream<<"Number of input items ("<<input_filtered.size()
|
||||
<<") does not match recipe size (1) "
|
||||
<<"of fuel recipe with burntime="<<burntime<<std::endl;*/
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check the single input item
|
||||
return inputItemMatchesRecipe(input_filtered[0], recipe, gamedef->idef());
|
||||
}
|
||||
|
||||
CraftOutput CraftDefinitionFuel::getOutput(const CraftInput &input, IGameDef *gamedef) const
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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";
|
||||
|
@ -1074,7 +1087,8 @@ void ServerEnvironment::step(float dtime)
|
|||
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))
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
@ -1083,7 +1097,12 @@ void ServerEnvironment::step(float dtime)
|
|||
|
||||
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");
|
||||
|
||||
|
@ -1116,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())
|
||||
|
@ -2014,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 @@ public:
|
|||
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 @@ private:
|
|||
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;
|
||||
|
|
|
@ -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 @@ public:
|
|||
// 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 @@ public:
|
|||
ITextureSource* tsrc(){return getTextureSource();}
|
||||
ISoundManager* sound(){return getSoundManager();}
|
||||
MtEventManager* event(){return getEventManager();}
|
||||
IRollbackReportSink* rollback(){return getRollbackReportSink();}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -30,6 +30,9 @@
|
|||
#include <IGUIStaticText.h>
|
||||
#include <IGUIFont.h>
|
||||
#include "settings.h"
|
||||
#include <algorithm>
|
||||
|
||||
#define KMaxButtonPerColumns 12
|
||||
|
||||
enum
|
||||
{
|
||||
|
@ -54,11 +57,15 @@ enum
|
|||
};
|
||||
|
||||
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 +78,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 +91,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 +108,48 @@ 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);
|
||||
}
|
||||
|
||||
{
|
||||
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 +172,27 @@ 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());
|
||||
}
|
||||
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 +202,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 +275,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");
|
||||
}
|
||||
|
|
|
@ -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 @@ private:
|
|||
|
||||
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
|
||||
|
|
|
@ -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)
|
||||
|
@ -543,6 +558,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 +576,11 @@ u32 InventoryList::getSize() const
|
|||
return m_items.size();
|
||||
}
|
||||
|
||||
u32 InventoryList::getWidth() const
|
||||
{
|
||||
return m_width;
|
||||
}
|
||||
|
||||
u32 InventoryList::getUsedSlots() const
|
||||
{
|
||||
u32 num = 0;
|
||||
|
|
|
@ -176,6 +176,7 @@ public:
|
|||
~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 @@ public:
|
|||
|
||||
const std::string &getName() const;
|
||||
u32 getSize() const;
|
||||
u32 getWidth() const;
|
||||
// Count used slots
|
||||
u32 getUsedSlots() const;
|
||||
u32 getFreeSlots() const;
|
||||
|
@ -240,7 +242,7 @@ public:
|
|||
|
||||
private:
|
||||
std::vector<ItemStack> m_items;
|
||||
u32 m_size;
|
||||
u32 m_size, m_width;
|
||||
std::string m_name;
|
||||
IItemDefManager *m_itemdef;
|
||||
};
|
||||
|
|
|
@ -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<<")"
|
||||
|
||||
|
@ -199,6 +200,14 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame
|
|||
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
|
||||
*/
|
||||
|
@ -343,6 +352,41 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame
|
|||
<<" 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
|
||||
*/
|
||||
|
@ -405,7 +449,7 @@ void IMoveAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame
|
|||
L, from_inv.p, from_list, from_i, src_item, player);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
mgr->setInventoryModified(from_inv);
|
||||
if(inv_from != inv_to)
|
||||
mgr->setInventoryModified(to_inv);
|
||||
|
@ -488,6 +532,11 @@ void IDropAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame
|
|||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
Do not handle rollback if inventory is player's
|
||||
*/
|
||||
bool ignore_src_rollback = (from_inv.type == InventoryLocation::PLAYER);
|
||||
|
||||
/*
|
||||
Collect information of endpoints
|
||||
*/
|
||||
|
@ -526,6 +575,7 @@ void IDropAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame
|
|||
|
||||
// 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)))
|
||||
{
|
||||
|
@ -575,6 +625,28 @@ void IDropAction::apply(InventoryManager *mgr, ServerActiveObject *player, IGame
|
|||
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)
|
||||
|
@ -697,18 +769,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
|
||||
|
@ -721,7 +791,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]);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ public:
|
|||
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
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
@ -188,7 +189,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 +282,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 +322,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 +376,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 +476,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
|
||||
|
|
|
@ -101,6 +101,7 @@ private:
|
|||
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
|
||||
|
|
94
src/map.cpp
94
src/map.cpp
|
@ -32,6 +32,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
#include "nodedef.h"
|
||||
#include "gamedef.h"
|
||||
#include "util/directiontables.h"
|
||||
#include "rollback_interface.h"
|
||||
|
||||
#define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")"
|
||||
|
||||
|
@ -932,12 +933,12 @@ void Map::updateLighting(core::map<v3s16, MapBlock*> & a_blocks,
|
|||
void Map::addNodeAndUpdate(v3s16 p, MapNode n,
|
||||
core::map<v3s16, MapBlock*> &modified_blocks)
|
||||
{
|
||||
INodeDefManager *nodemgr = m_gamedef->ndef();
|
||||
INodeDefManager *ndef = m_gamedef->ndef();
|
||||
|
||||
/*PrintInfo(m_dout);
|
||||
m_dout<<DTIME<<"Map::addNodeAndUpdate(): p=("
|
||||
<<p.X<<","<<p.Y<<","<<p.Z<<")"<<std::endl;*/
|
||||
|
||||
|
||||
/*
|
||||
From this node to nodes underneath:
|
||||
If lighting is sunlight (1.0), unlight neighbours and
|
||||
|
@ -950,6 +951,11 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n,
|
|||
|
||||
bool node_under_sunlight = true;
|
||||
core::map<v3s16, bool> light_sources;
|
||||
|
||||
/*
|
||||
Collect old node for rollback
|
||||
*/
|
||||
RollbackNode rollback_oldnode(this, p, m_gamedef);
|
||||
|
||||
/*
|
||||
If there is a node at top and it doesn't have sunlight,
|
||||
|
@ -960,7 +966,7 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n,
|
|||
try{
|
||||
MapNode topnode = getNode(toppos);
|
||||
|
||||
if(topnode.getLight(LIGHTBANK_DAY, nodemgr) != LIGHT_SUN)
|
||||
if(topnode.getLight(LIGHTBANK_DAY, ndef) != LIGHT_SUN)
|
||||
node_under_sunlight = false;
|
||||
}
|
||||
catch(InvalidPositionException &e)
|
||||
|
@ -980,7 +986,7 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n,
|
|||
{
|
||||
enum LightBank bank = banks[i];
|
||||
|
||||
u8 lightwas = getNode(p).getLight(bank, nodemgr);
|
||||
u8 lightwas = getNode(p).getLight(bank, ndef);
|
||||
|
||||
// Add the block of the added node to modified_blocks
|
||||
v3s16 blockpos = getNodeBlockPos(p);
|
||||
|
@ -997,16 +1003,16 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n,
|
|||
// light again into this.
|
||||
unLightNeighbors(bank, p, lightwas, light_sources, modified_blocks);
|
||||
|
||||
n.setLight(bank, 0, nodemgr);
|
||||
n.setLight(bank, 0, ndef);
|
||||
}
|
||||
|
||||
/*
|
||||
If node lets sunlight through and is under sunlight, it has
|
||||
sunlight too.
|
||||
*/
|
||||
if(node_under_sunlight && nodemgr->get(n).sunlight_propagates)
|
||||
if(node_under_sunlight && ndef->get(n).sunlight_propagates)
|
||||
{
|
||||
n.setLight(LIGHTBANK_DAY, LIGHT_SUN, nodemgr);
|
||||
n.setLight(LIGHTBANK_DAY, LIGHT_SUN, ndef);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -1028,7 +1034,7 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n,
|
|||
TODO: This could be optimized by mass-unlighting instead
|
||||
of looping
|
||||
*/
|
||||
if(node_under_sunlight && !nodemgr->get(n).sunlight_propagates)
|
||||
if(node_under_sunlight && !ndef->get(n).sunlight_propagates)
|
||||
{
|
||||
s16 y = p.Y - 1;
|
||||
for(;; y--){
|
||||
|
@ -1044,12 +1050,12 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n,
|
|||
break;
|
||||
}
|
||||
|
||||
if(n2.getLight(LIGHTBANK_DAY, nodemgr) == LIGHT_SUN)
|
||||
if(n2.getLight(LIGHTBANK_DAY, ndef) == LIGHT_SUN)
|
||||
{
|
||||
unLightNeighbors(LIGHTBANK_DAY,
|
||||
n2pos, n2.getLight(LIGHTBANK_DAY, nodemgr),
|
||||
n2pos, n2.getLight(LIGHTBANK_DAY, ndef),
|
||||
light_sources, modified_blocks);
|
||||
n2.setLight(LIGHTBANK_DAY, 0, nodemgr);
|
||||
n2.setLight(LIGHTBANK_DAY, 0, ndef);
|
||||
setNode(n2pos, n2);
|
||||
}
|
||||
else
|
||||
|
@ -1078,6 +1084,17 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n,
|
|||
block->expireDayNightDiff();
|
||||
}
|
||||
|
||||
/*
|
||||
Report for rollback
|
||||
*/
|
||||
if(m_gamedef->rollback())
|
||||
{
|
||||
RollbackNode rollback_newnode(this, p, m_gamedef);
|
||||
RollbackAction action;
|
||||
action.setSetNode(p, rollback_oldnode, rollback_newnode);
|
||||
m_gamedef->rollback()->reportAction(action);
|
||||
}
|
||||
|
||||
/*
|
||||
Add neighboring liquid nodes and the node itself if it is
|
||||
liquid (=water node was added) to transform queue.
|
||||
|
@ -1099,7 +1116,7 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n,
|
|||
v3s16 p2 = p + dirs[i];
|
||||
|
||||
MapNode n2 = getNode(p2);
|
||||
if(nodemgr->get(n2).isLiquid() || n2.getContent() == CONTENT_AIR)
|
||||
if(ndef->get(n2).isLiquid() || n2.getContent() == CONTENT_AIR)
|
||||
{
|
||||
m_transforming_liquid.push_back(p2);
|
||||
}
|
||||
|
@ -1115,7 +1132,7 @@ void Map::addNodeAndUpdate(v3s16 p, MapNode n,
|
|||
void Map::removeNodeAndUpdate(v3s16 p,
|
||||
core::map<v3s16, MapBlock*> &modified_blocks)
|
||||
{
|
||||
INodeDefManager *nodemgr = m_gamedef->ndef();
|
||||
INodeDefManager *ndef = m_gamedef->ndef();
|
||||
|
||||
/*PrintInfo(m_dout);
|
||||
m_dout<<DTIME<<"Map::removeNodeAndUpdate(): p=("
|
||||
|
@ -1128,6 +1145,11 @@ void Map::removeNodeAndUpdate(v3s16 p,
|
|||
// Node will be replaced with this
|
||||
content_t replace_material = CONTENT_AIR;
|
||||
|
||||
/*
|
||||
Collect old node for rollback
|
||||
*/
|
||||
RollbackNode rollback_oldnode(this, p, m_gamedef);
|
||||
|
||||
/*
|
||||
If there is a node at top and it doesn't have sunlight,
|
||||
there will be no sunlight going down.
|
||||
|
@ -1135,7 +1157,7 @@ void Map::removeNodeAndUpdate(v3s16 p,
|
|||
try{
|
||||
MapNode topnode = getNode(toppos);
|
||||
|
||||
if(topnode.getLight(LIGHTBANK_DAY, nodemgr) != LIGHT_SUN)
|
||||
if(topnode.getLight(LIGHTBANK_DAY, ndef) != LIGHT_SUN)
|
||||
node_under_sunlight = false;
|
||||
}
|
||||
catch(InvalidPositionException &e)
|
||||
|
@ -1157,7 +1179,7 @@ void Map::removeNodeAndUpdate(v3s16 p,
|
|||
Unlight neighbors (in case the node is a light source)
|
||||
*/
|
||||
unLightNeighbors(bank, p,
|
||||
getNode(p).getLight(bank, nodemgr),
|
||||
getNode(p).getLight(bank, ndef),
|
||||
light_sources, modified_blocks);
|
||||
}
|
||||
|
||||
|
@ -1219,7 +1241,7 @@ void Map::removeNodeAndUpdate(v3s16 p,
|
|||
// TODO: Is this needed? Lighting is cleared up there already.
|
||||
try{
|
||||
MapNode n = getNode(p);
|
||||
n.setLight(LIGHTBANK_DAY, 0, nodemgr);
|
||||
n.setLight(LIGHTBANK_DAY, 0, ndef);
|
||||
setNode(p, n);
|
||||
}
|
||||
catch(InvalidPositionException &e)
|
||||
|
@ -1254,6 +1276,17 @@ void Map::removeNodeAndUpdate(v3s16 p,
|
|||
block->expireDayNightDiff();
|
||||
}
|
||||
|
||||
/*
|
||||
Report for rollback
|
||||
*/
|
||||
if(m_gamedef->rollback())
|
||||
{
|
||||
RollbackNode rollback_newnode(this, p, m_gamedef);
|
||||
RollbackAction action;
|
||||
action.setSetNode(p, rollback_oldnode, rollback_newnode);
|
||||
m_gamedef->rollback()->reportAction(action);
|
||||
}
|
||||
|
||||
/*
|
||||
Add neighboring liquid nodes and this node to transform queue.
|
||||
(it's vital for the node itself to get updated last.)
|
||||
|
@ -1275,7 +1308,7 @@ void Map::removeNodeAndUpdate(v3s16 p,
|
|||
v3s16 p2 = p + dirs[i];
|
||||
|
||||
MapNode n2 = getNode(p2);
|
||||
if(nodemgr->get(n2).isLiquid() || n2.getContent() == CONTENT_AIR)
|
||||
if(ndef->get(n2).isLiquid() || n2.getContent() == CONTENT_AIR)
|
||||
{
|
||||
m_transforming_liquid.push_back(p2);
|
||||
}
|
||||
|
@ -1603,7 +1636,7 @@ void Map::transformLiquids(core::map<v3s16, MapBlock*> & modified_blocks)
|
|||
while(m_transforming_liquid.size() != 0)
|
||||
{
|
||||
// This should be done here so that it is done when continue is used
|
||||
if(loopcount >= initial_size * 3)
|
||||
if(loopcount >= initial_size || loopcount >= 10000)
|
||||
break;
|
||||
loopcount++;
|
||||
|
||||
|
@ -1791,7 +1824,30 @@ void Map::transformLiquids(core::map<v3s16, MapBlock*> & modified_blocks)
|
|||
n0.param2 = ~(LIQUID_LEVEL_MASK | LIQUID_FLOW_DOWN_MASK);
|
||||
}
|
||||
n0.setContent(new_node_content);
|
||||
setNode(p0, n0);
|
||||
|
||||
// Find out whether there is a suspect for this action
|
||||
std::string suspect;
|
||||
if(m_gamedef->rollback()){
|
||||
suspect = m_gamedef->rollback()->getSuspect(p0, 83, 1);
|
||||
}
|
||||
|
||||
if(!suspect.empty()){
|
||||
// Blame suspect
|
||||
RollbackScopeActor rollback_scope(m_gamedef->rollback(), suspect, true);
|
||||
// Get old node for rollback
|
||||
RollbackNode rollback_oldnode(this, p0, m_gamedef);
|
||||
// Set node
|
||||
setNode(p0, n0);
|
||||
// Report
|
||||
RollbackNode rollback_newnode(this, p0, m_gamedef);
|
||||
RollbackAction action;
|
||||
action.setSetNode(p0, rollback_oldnode, rollback_newnode);
|
||||
m_gamedef->rollback()->reportAction(action);
|
||||
} else {
|
||||
// Set node
|
||||
setNode(p0, n0);
|
||||
}
|
||||
|
||||
v3s16 blockpos = getNodeBlockPos(p0);
|
||||
MapBlock *block = getBlockNoCreateNoEx(blockpos);
|
||||
if(block != NULL) {
|
||||
|
|
|
@ -44,6 +44,7 @@ class ServerMapSector;
|
|||
class MapBlock;
|
||||
class NodeMetadata;
|
||||
class IGameDef;
|
||||
class IRollbackReportSink;
|
||||
|
||||
namespace mapgen{
|
||||
struct BlockMakeData;
|
||||
|
@ -169,7 +170,7 @@ public:
|
|||
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)
|
||||
|
@ -336,7 +337,7 @@ protected:
|
|||
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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -556,8 +556,6 @@ public:
|
|||
f->solidness = 0;
|
||||
} else {
|
||||
f->solidness = 1;
|
||||
if(f->alpha == 255)
|
||||
f->solidness = 2;
|
||||
f->backface_culling = false;
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -45,7 +45,8 @@ 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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -0,0 +1,156 @@
|
|||
/*
|
||||
Minetest-c55
|
||||
Copyright (C) 2012 celeron55, Perttu Ahola <celeron55@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation; either version 2.1 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef ROLLBACK_INTERFACE_HEADER
|
||||
#define ROLLBACK_INTERFACE_HEADER
|
||||
|
||||
#include "irr_v3d.h"
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include "exceptions.h"
|
||||
|
||||
class Map;
|
||||
class IGameDef;
|
||||
struct MapNode;
|
||||
class InventoryManager;
|
||||
|
||||
struct RollbackNode
|
||||
{
|
||||
std::string name;
|
||||
int param1;
|
||||
int param2;
|
||||
std::string meta;
|
||||
|
||||
bool operator==(const RollbackNode &other)
|
||||
{
|
||||
return (name == other.name && param1 == other.param1 &&
|
||||
param2 == other.param2 && meta == other.meta);
|
||||
}
|
||||
bool operator!=(const RollbackNode &other)
|
||||
{
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
RollbackNode():
|
||||
param1(0),
|
||||
param2(0)
|
||||
{}
|
||||
|
||||
RollbackNode(Map *map, v3s16 p, IGameDef *gamedef);
|
||||
};
|
||||
|
||||
struct RollbackAction
|
||||
{
|
||||
enum Type{
|
||||
TYPE_NOTHING,
|
||||
TYPE_SET_NODE,
|
||||
TYPE_MODIFY_INVENTORY_STACK,
|
||||
} type;
|
||||
|
||||
int unix_time;
|
||||
std::string actor;
|
||||
bool actor_is_guess;
|
||||
|
||||
v3s16 p;
|
||||
RollbackNode n_old;
|
||||
RollbackNode n_new;
|
||||
|
||||
std::string inventory_location;
|
||||
std::string inventory_list;
|
||||
u32 inventory_index;
|
||||
bool inventory_add;
|
||||
std::string inventory_stack;
|
||||
|
||||
RollbackAction():
|
||||
type(TYPE_NOTHING),
|
||||
unix_time(0),
|
||||
actor_is_guess(false)
|
||||
{}
|
||||
|
||||
void setSetNode(v3s16 p_, const RollbackNode &n_old_,
|
||||
const RollbackNode &n_new_)
|
||||
{
|
||||
type = TYPE_SET_NODE;
|
||||
p = p_;
|
||||
n_old = n_old_;
|
||||
n_new = n_new_;
|
||||
}
|
||||
|
||||
void setModifyInventoryStack(const std::string &inventory_location_,
|
||||
const std::string &inventory_list_, int index_,
|
||||
bool add_, const std::string &inventory_stack_)
|
||||
{
|
||||
type = TYPE_MODIFY_INVENTORY_STACK;
|
||||
inventory_location = inventory_location_;
|
||||
inventory_list = inventory_list_;
|
||||
inventory_index = index_;
|
||||
inventory_add = add_;
|
||||
inventory_stack = inventory_stack_;
|
||||
}
|
||||
|
||||
// String should not contain newlines or nulls
|
||||
std::string toString() const;
|
||||
void fromStream(std::istream &is) throw(SerializationError);
|
||||
|
||||
// Eg. flowing water level changes are not important
|
||||
bool isImportant(IGameDef *gamedef) const;
|
||||
|
||||
bool getPosition(v3s16 *dst) const;
|
||||
|
||||
bool applyRevert(Map *map, InventoryManager *imgr, IGameDef *gamedef) const;
|
||||
};
|
||||
|
||||
class IRollbackReportSink
|
||||
{
|
||||
public:
|
||||
virtual ~IRollbackReportSink(){}
|
||||
virtual void reportAction(const RollbackAction &action) = 0;
|
||||
virtual std::string getActor() = 0;
|
||||
virtual bool isActorGuess() = 0;
|
||||
virtual void setActor(const std::string &actor, bool is_guess) = 0;
|
||||
virtual std::string getSuspect(v3s16 p, float nearness_shortcut,
|
||||
float min_nearness) = 0;
|
||||
};
|
||||
|
||||
class RollbackScopeActor
|
||||
{
|
||||
public:
|
||||
RollbackScopeActor(IRollbackReportSink *sink, const std::string &actor,
|
||||
bool is_guess=false):
|
||||
m_sink(sink)
|
||||
{
|
||||
if(m_sink){
|
||||
m_actor_was = m_sink->getActor();
|
||||
m_actor_was_guess = m_sink->isActorGuess();
|
||||
m_sink->setActor(actor, is_guess);
|
||||
}
|
||||
}
|
||||
~RollbackScopeActor()
|
||||
{
|
||||
if(m_sink){
|
||||
m_sink->setActor(m_actor_was, m_actor_was_guess);
|
||||
}
|
||||
}
|
||||
private:
|
||||
IRollbackReportSink *m_sink;
|
||||
std::string m_actor_was;
|
||||
bool m_actor_was_guess;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -47,6 +47,7 @@ extern "C" {
|
|||
#include "daynightratio.h"
|
||||
#include "noise.h" // PseudoRandom for LuaPseudoRandom
|
||||
#include "util/pointedthing.h"
|
||||
#include "rollback.h"
|
||||
|
||||
static void stackDump(lua_State *L, std::ostream &o)
|
||||
{
|
||||
|
@ -1846,6 +1847,20 @@ private:
|
|||
return 1;
|
||||
}
|
||||
|
||||
// get_width(self, listname)
|
||||
static int l_get_width(lua_State *L)
|
||||
{
|
||||
InvRef *ref = checkobject(L, 1);
|
||||
const char *listname = luaL_checkstring(L, 2);
|
||||
InventoryList *list = getlist(L, ref, listname);
|
||||
if(list){
|
||||
lua_pushinteger(L, list->getWidth());
|
||||
} else {
|
||||
lua_pushinteger(L, 0);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// set_size(self, listname, size)
|
||||
static int l_set_size(lua_State *L)
|
||||
{
|
||||
|
@ -1868,6 +1883,23 @@ private:
|
|||
return 0;
|
||||
}
|
||||
|
||||
// set_width(self, listname, size)
|
||||
static int l_set_width(lua_State *L)
|
||||
{
|
||||
InvRef *ref = checkobject(L, 1);
|
||||
const char *listname = luaL_checkstring(L, 2);
|
||||
int newwidth = luaL_checknumber(L, 3);
|
||||
Inventory *inv = getinv(L, ref);
|
||||
InventoryList *list = inv->getList(listname);
|
||||
if(list){
|
||||
list->setWidth(newwidth);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
reportInventoryChange(L, ref);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// get_stack(self, listname, i) -> itemstack
|
||||
static int l_get_stack(lua_State *L)
|
||||
{
|
||||
|
@ -2061,6 +2093,8 @@ const luaL_reg InvRef::methods[] = {
|
|||
method(InvRef, is_empty),
|
||||
method(InvRef, get_size),
|
||||
method(InvRef, set_size),
|
||||
method(InvRef, get_width),
|
||||
method(InvRef, set_width),
|
||||
method(InvRef, get_stack),
|
||||
method(InvRef, set_stack),
|
||||
method(InvRef, get_list),
|
||||
|
@ -2106,6 +2140,7 @@ private:
|
|||
|
||||
static void reportMetadataChange(NodeMetaRef *ref)
|
||||
{
|
||||
// NOTE: This same code is in rollback_interface.cpp
|
||||
// Inform other things that the metadata has changed
|
||||
v3s16 blockpos = getNodeBlockPos(ref->m_p);
|
||||
MapEditEvent event;
|
||||
|
@ -3808,6 +3843,15 @@ private:
|
|||
return 1;
|
||||
}
|
||||
|
||||
// EnvRef:clear_objects()
|
||||
// clear all objects in the environment
|
||||
static int l_clear_objects(lua_State *L)
|
||||
{
|
||||
EnvRef *o = checkobject(L, 1);
|
||||
o->m_env->clearAllObjects();
|
||||
return 0;
|
||||
}
|
||||
|
||||
public:
|
||||
EnvRef(ServerEnvironment *env):
|
||||
m_env(env)
|
||||
|
@ -3889,6 +3933,7 @@ const luaL_reg EnvRef::methods[] = {
|
|||
method(EnvRef, find_node_near),
|
||||
method(EnvRef, find_nodes_in_area),
|
||||
method(EnvRef, get_perlin),
|
||||
method(EnvRef, clear_objects),
|
||||
{0,0}
|
||||
};
|
||||
|
||||
|
@ -3926,6 +3971,10 @@ private:
|
|||
min = luaL_checkinteger(L, 2);
|
||||
if(!lua_isnil(L, 3))
|
||||
max = luaL_checkinteger(L, 3);
|
||||
if(max < min){
|
||||
errorstream<<"PseudoRandom.next(): max="<<max<<" min="<<min<<std::endl;
|
||||
throw LuaError(L, "PseudoRandom.next(): max < min");
|
||||
}
|
||||
if(max - min != 32767 && max - min > 32767/5)
|
||||
throw LuaError(L, "PseudoRandom.next() max-min is not 32767 and is > 32768/5. This is disallowed due to the bad random distribution the implementation would otherwise make.");
|
||||
PseudoRandom &pseudo = o->m_pseudo;
|
||||
|
@ -4164,6 +4213,20 @@ static int l_log(lua_State *L)
|
|||
return 0;
|
||||
}
|
||||
|
||||
// request_shutdown()
|
||||
static int l_request_shutdown(lua_State *L)
|
||||
{
|
||||
get_server(L)->requestShutdown();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// get_server_status()
|
||||
static int l_get_server_status(lua_State *L)
|
||||
{
|
||||
lua_pushstring(L, wide_to_narrow(get_server(L)->getStatusString()).c_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
// register_item_raw({lots of stuff})
|
||||
static int l_register_item_raw(lua_State *L)
|
||||
{
|
||||
|
@ -4554,6 +4617,56 @@ static int l_get_player_privs(lua_State *L)
|
|||
return 1;
|
||||
}
|
||||
|
||||
// get_ban_list()
|
||||
static int l_get_ban_list(lua_State *L)
|
||||
{
|
||||
lua_pushstring(L, get_server(L)->getBanDescription("").c_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
// get_ban_description()
|
||||
static int l_get_ban_description(lua_State *L)
|
||||
{
|
||||
const char * ip_or_name = luaL_checkstring(L, 1);
|
||||
lua_pushstring(L, get_server(L)->getBanDescription(std::string(ip_or_name)).c_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
// ban_player()
|
||||
static int l_ban_player(lua_State *L)
|
||||
{
|
||||
const char * name = luaL_checkstring(L, 1);
|
||||
Player *player = get_env(L)->getPlayer(name);
|
||||
if(player == NULL)
|
||||
{
|
||||
lua_pushboolean(L, false); // no such player
|
||||
return 1;
|
||||
}
|
||||
try
|
||||
{
|
||||
Address addr = get_server(L)->getPeerAddress(get_env(L)->getPlayer(name)->peer_id);
|
||||
std::string ip_str = addr.serializeString();
|
||||
get_server(L)->setIpBanned(ip_str, name);
|
||||
}
|
||||
catch(con::PeerNotFoundException) // unlikely
|
||||
{
|
||||
dstream << __FUNCTION_NAME << ": peer was not found" << std::endl;
|
||||
lua_pushboolean(L, false); // error
|
||||
return 1;
|
||||
}
|
||||
lua_pushboolean(L, true);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// unban_player_or_ip()
|
||||
static int l_unban_player_of_ip(lua_State *L)
|
||||
{
|
||||
const char * ip_or_name = luaL_checkstring(L, 1);
|
||||
get_server(L)->unsetIpBanned(ip_or_name);
|
||||
lua_pushboolean(L, true);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// get_inventory(location)
|
||||
static int l_get_inventory(lua_State *L)
|
||||
{
|
||||
|
@ -4853,9 +4966,60 @@ static int l_get_craft_recipe(lua_State *L)
|
|||
return 1;
|
||||
}
|
||||
|
||||
// rollback_get_last_node_actor(p, range, seconds) -> actor, p, seconds
|
||||
static int l_rollback_get_last_node_actor(lua_State *L)
|
||||
{
|
||||
v3s16 p = read_v3s16(L, 1);
|
||||
int range = luaL_checknumber(L, 2);
|
||||
int seconds = luaL_checknumber(L, 3);
|
||||
Server *server = get_server(L);
|
||||
IRollbackManager *rollback = server->getRollbackManager();
|
||||
v3s16 act_p;
|
||||
int act_seconds = 0;
|
||||
std::string actor = rollback->getLastNodeActor(p, range, seconds, &act_p, &act_seconds);
|
||||
lua_pushstring(L, actor.c_str());
|
||||
push_v3s16(L, act_p);
|
||||
lua_pushnumber(L, act_seconds);
|
||||
return 3;
|
||||
}
|
||||
|
||||
// rollback_revert_actions_by(actor, seconds) -> bool, log messages
|
||||
static int l_rollback_revert_actions_by(lua_State *L)
|
||||
{
|
||||
std::string actor = luaL_checkstring(L, 1);
|
||||
int seconds = luaL_checknumber(L, 2);
|
||||
Server *server = get_server(L);
|
||||
IRollbackManager *rollback = server->getRollbackManager();
|
||||
std::list<RollbackAction> actions = rollback->getRevertActions(actor, seconds);
|
||||
std::list<std::string> log;
|
||||
bool success = server->rollbackRevertActions(actions, &log);
|
||||
// Push boolean result
|
||||
lua_pushboolean(L, success);
|
||||
// Get the table insert function and push the log table
|
||||
lua_getglobal(L, "table");
|
||||
lua_getfield(L, -1, "insert");
|
||||
int table_insert = lua_gettop(L);
|
||||
lua_newtable(L);
|
||||
int table = lua_gettop(L);
|
||||
for(std::list<std::string>::const_iterator i = log.begin();
|
||||
i != log.end(); i++)
|
||||
{
|
||||
lua_pushvalue(L, table_insert);
|
||||
lua_pushvalue(L, table);
|
||||
lua_pushstring(L, i->c_str());
|
||||
if(lua_pcall(L, 2, 0, 0))
|
||||
script_error(L, "error: %s", lua_tostring(L, -1));
|
||||
}
|
||||
lua_remove(L, -2); // Remove table
|
||||
lua_remove(L, -2); // Remove insert
|
||||
return 2;
|
||||
}
|
||||
|
||||
static const struct luaL_Reg minetest_f [] = {
|
||||
{"debug", l_debug},
|
||||
{"log", l_log},
|
||||
{"request_shutdown", l_request_shutdown},
|
||||
{"get_server_status", l_get_server_status},
|
||||
{"register_item_raw", l_register_item_raw},
|
||||
{"register_alias_raw", l_register_alias_raw},
|
||||
{"register_craft", l_register_craft},
|
||||
|
@ -4865,6 +5029,10 @@ static const struct luaL_Reg minetest_f [] = {
|
|||
{"chat_send_all", l_chat_send_all},
|
||||
{"chat_send_player", l_chat_send_player},
|
||||
{"get_player_privs", l_get_player_privs},
|
||||
{"get_ban_list", l_get_ban_list},
|
||||
{"get_ban_description", l_get_ban_description},
|
||||
{"ban_player", l_ban_player},
|
||||
{"unban_player_or_ip", l_unban_player_of_ip},
|
||||
{"get_inventory", l_get_inventory},
|
||||
{"create_detached_inventory_raw", l_create_detached_inventory_raw},
|
||||
{"get_dig_params", l_get_dig_params},
|
||||
|
@ -4880,6 +5048,8 @@ static const struct luaL_Reg minetest_f [] = {
|
|||
{"notify_authentication_modified", l_notify_authentication_modified},
|
||||
{"get_craft_result", l_get_craft_result},
|
||||
{"get_craft_recipe", l_get_craft_recipe},
|
||||
{"rollback_get_last_node_actor", l_rollback_get_last_node_actor},
|
||||
{"rollback_revert_actions_by", l_rollback_revert_actions_by},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
|
@ -5453,6 +5623,8 @@ void scriptapi_on_player_receive_fields(lua_State *L,
|
|||
// If that is nil or on error, return false and stack is unchanged
|
||||
// If that is a function, returns true and pushes the
|
||||
// function onto the stack
|
||||
// If minetest.registered_items[name] doesn't exist, minetest.nodedef_default
|
||||
// is tried instead so unknown items can still be manipulated to some degree
|
||||
static bool get_item_callback(lua_State *L,
|
||||
const char *name, const char *callbackname)
|
||||
{
|
||||
|
@ -5465,9 +5637,15 @@ static bool get_item_callback(lua_State *L,
|
|||
// Should be a table
|
||||
if(lua_type(L, -1) != LUA_TTABLE)
|
||||
{
|
||||
// Report error and clean up
|
||||
errorstream<<"Item \""<<name<<"\" not defined"<<std::endl;
|
||||
lua_pop(L, 1);
|
||||
return false;
|
||||
|
||||
// Try minetest.nodedef_default instead
|
||||
lua_getglobal(L, "minetest");
|
||||
lua_getfield(L, -1, "nodedef_default");
|
||||
lua_remove(L, -2);
|
||||
luaL_checktype(L, -1, LUA_TTABLE);
|
||||
}
|
||||
lua_getfield(L, -1, callbackname);
|
||||
lua_remove(L, -2);
|
||||
|
|
176
src/server.cpp
176
src/server.cpp
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -1865,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");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2481,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.
|
||||
|
@ -2615,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());
|
||||
|
||||
|
@ -2632,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
|
||||
{
|
||||
|
@ -2949,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
|
||||
*/
|
||||
|
@ -3204,8 +3209,23 @@ 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)
|
||||
{
|
||||
|
@ -4407,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
|
||||
|
@ -4424,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)
|
||||
|
@ -4522,6 +4547,73 @@ Inventory* Server::createDetachedInventory(const std::string &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()
|
||||
|
@ -4552,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()
|
||||
{
|
||||
|
|
16
src/server.h
16
src/server.h
|
@ -36,6 +36,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
|||
#include "sound.h"
|
||||
#include "util/thread.h"
|
||||
#include "util/string.h"
|
||||
#include "rollback_interface.h" // Needed for rollbackRevertActions()
|
||||
#include <list> // Needed for rollbackRevertActions()
|
||||
|
||||
struct LuaState;
|
||||
typedef struct lua_State lua_State;
|
||||
|
@ -44,6 +46,7 @@ class IWritableNodeDefManager;
|
|||
class IWritableCraftDefManager;
|
||||
class EventManager;
|
||||
class PlayerSAO;
|
||||
class IRollbackManager;
|
||||
|
||||
class ServerError : public std::exception
|
||||
{
|
||||
|
@ -543,6 +546,13 @@ public:
|
|||
|
||||
// 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
|
||||
|
@ -553,6 +563,7 @@ public:
|
|||
virtual u16 allocateUnknownNodeId(const std::string &name);
|
||||
virtual ISoundManager* getSoundManager();
|
||||
virtual MtEventManager* getEventManager();
|
||||
virtual IRollbackReportSink* getRollbackReportSink();
|
||||
|
||||
IWritableItemDefManager* getWritableItemDefManager();
|
||||
IWritableNodeDefManager* getWritableNodeDefManager();
|
||||
|
@ -720,6 +731,11 @@ private:
|
|||
// 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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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>
|
||||
|
|
|
@ -698,6 +698,7 @@ struct TestInventory: public TestBase
|
|||
{
|
||||
std::string serialized_inventory =
|
||||
"List 0 32\n"
|
||||
"Width 3\n"
|
||||
"Empty\n"
|
||||
"Empty\n"
|
||||
"Empty\n"
|
||||
|
@ -735,6 +736,7 @@ struct TestInventory: public TestBase
|
|||
|
||||
std::string serialized_inventory_2 =
|
||||
"List main 32\n"
|
||||
"Width 5\n"
|
||||
"Empty\n"
|
||||
"Empty\n"
|
||||
"Empty\n"
|
||||
|
@ -778,6 +780,8 @@ struct TestInventory: public TestBase
|
|||
inv.getList("0")->setName("main");
|
||||
UASSERT(!inv.getList("0"));
|
||||
UASSERT(inv.getList("main"));
|
||||
UASSERT(inv.getList("main")->getWidth() == 3);
|
||||
inv.getList("main")->setWidth(5);
|
||||
std::ostringstream inv_os(std::ios::binary);
|
||||
inv.serialize(inv_os);
|
||||
UASSERT(inv_os.str() == serialized_inventory_2);
|
||||
|
|
16
src/tile.cpp
16
src/tile.cpp
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue