Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3ddbe8c6b | ||
|
|
d085139057 | ||
|
|
28e7443f9b | ||
|
|
e79ad21aeb | ||
|
|
0b61253931 | ||
|
|
a2738dec59 | ||
|
|
1788709e2d | ||
|
|
47d30d12cb | ||
|
|
43df78102c | ||
|
|
cc10eec6c6 | ||
|
|
15bf9a7026 | ||
|
|
2795f44f03 |
@@ -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 0)
|
||||
set(VERSION_PATCH 1)
|
||||
if(VERSION_EXTRA)
|
||||
set(VERSION_PATCH ${VERSION_PATCH}-${VERSION_EXTRA})
|
||||
endif()
|
||||
|
||||
@@ -148,11 +148,13 @@ function minetest.item_place_node(itemstack, placer, pointed_thing)
|
||||
local dir = {x = under.x - above.x, y = under.y - above.y, z = under.z - above.z}
|
||||
newnode.param2 = minetest.dir_to_wallmounted(dir)
|
||||
-- Calculate the direction for furnaces and chests and stuff
|
||||
elseif def.paramtype2 == 'facedir' and placer then
|
||||
local playerpos = placer:getpos()
|
||||
local dir = {x = pos.x - playerpos.x, y = pos.y - playerpos.y, z = pos.z - playerpos.z}
|
||||
newnode.param2 = minetest.dir_to_facedir(dir)
|
||||
minetest.log("action", "facedir: " .. newnode.param2)
|
||||
elseif def.paramtype2 == 'facedir' then
|
||||
local placer_pos = placer:getpos()
|
||||
if placer_pos then
|
||||
local dir = {x = pos.x - placer_pos.x, y = pos.y - placer_pos.y, z = pos.z - placer_pos.z}
|
||||
newnode.param2 = minetest.dir_to_facedir(dir)
|
||||
minetest.log("action", "facedir: " .. newnode.param2)
|
||||
end
|
||||
end
|
||||
|
||||
-- Add node and update
|
||||
|
||||
@@ -72,7 +72,8 @@ minetest.register_entity("__builtin:item", {
|
||||
local p = self.object:getpos()
|
||||
p.y = p.y - 0.3
|
||||
local nn = minetest.env:get_node(p).name
|
||||
if minetest.registered_nodes[nn].walkable then
|
||||
-- If node is not registered or node is walkably solid
|
||||
if not minetest.registered_nodes[nn] or minetest.registered_nodes[nn].walkable then
|
||||
if self.physical_state then
|
||||
self.object:setvelocity({x=0,y=0,z=0})
|
||||
self.object:setacceleration({x=0, y=0, z=0})
|
||||
|
||||
@@ -447,6 +447,9 @@ minetest.register_node("experimental:tester_node_1", {
|
||||
tile_images = {"wieldhand.png"},
|
||||
groups = {oddly_breakable_by_hand=2},
|
||||
sounds = default.node_sound_wood_defaults(),
|
||||
-- This was known to cause a bug in minetest.item_place_node() when used
|
||||
-- via minetest.env:place_node(), causing a placer with no position
|
||||
paramtype2 = "facedir",
|
||||
|
||||
on_construct = function(pos)
|
||||
experimental.print_to_everything("experimental:tester_node_1:on_construct("..minetest.pos_to_string(pos)..")")
|
||||
|
||||
@@ -158,6 +158,8 @@
|
||||
#static_spawnpoint = 0, 10, 0
|
||||
# If true, new players cannot join with an empty password
|
||||
#disallow_empty_password = false
|
||||
# If true, disable cheat prevention in multiplayer
|
||||
#disable_anticheat = false
|
||||
|
||||
# Profiler data print interval. #0 = disable.
|
||||
#profiler_print_interval = 0
|
||||
|
||||
@@ -40,7 +40,7 @@ static inline bool is_base64(unsigned char c) {
|
||||
|
||||
bool base64_is_valid(std::string const& s)
|
||||
{
|
||||
for(int i=0; i<s.size(); i++)
|
||||
for(size_t i=0; i<s.size(); i++)
|
||||
if(!is_base64(s[i])) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1029,10 +1029,20 @@ void mapblock_mesh_generate_special(MeshMakeData *data,
|
||||
break;}
|
||||
case NDT_NODEBOX:
|
||||
{
|
||||
static const v3s16 tile_dirs[6] = {
|
||||
v3s16(0, 1, 0),
|
||||
v3s16(0, -1, 0),
|
||||
v3s16(1, 0, 0),
|
||||
v3s16(-1, 0, 0),
|
||||
v3s16(0, 0, 1),
|
||||
v3s16(0, 0, -1)
|
||||
};
|
||||
|
||||
TileSpec tiles[6];
|
||||
for(int i = 0; i < 6; i++)
|
||||
{
|
||||
tiles[i] = getNodeTileN(n, p, i, data);
|
||||
// Handles facedir rotation for textures
|
||||
tiles[i] = getNodeTile(n, p, tile_dirs[i], data);
|
||||
}
|
||||
|
||||
u16 l = getInteriorLight(n, 0, data);
|
||||
|
||||
@@ -764,6 +764,8 @@ PlayerSAO::PlayerSAO(ServerEnvironment *env_, Player *player_, u16 peer_id_,
|
||||
m_last_good_position(0,0,0),
|
||||
m_last_good_position_age(0),
|
||||
m_time_from_last_punch(0),
|
||||
m_nocheat_dig_pos(32767, 32767, 32767),
|
||||
m_nocheat_dig_time(0),
|
||||
m_wield_index(0),
|
||||
m_position_not_sent(false),
|
||||
m_armor_groups_sent(false),
|
||||
@@ -874,8 +876,9 @@ void PlayerSAO::step(float dtime, bool send_recommended)
|
||||
}
|
||||
|
||||
m_time_from_last_punch += dtime;
|
||||
m_nocheat_dig_time += dtime;
|
||||
|
||||
if(m_is_singleplayer)
|
||||
if(m_is_singleplayer || g_settings->getBool("disable_anticheat"))
|
||||
{
|
||||
m_last_good_position = m_player->getPosition();
|
||||
m_last_good_position_age = 0;
|
||||
@@ -888,7 +891,8 @@ void PlayerSAO::step(float dtime, bool send_recommended)
|
||||
NOTE: Actually the server should handle player physics like the
|
||||
client does and compare player's position to what is calculated
|
||||
on our side. This is required when eg. players fly due to an
|
||||
explosion.
|
||||
explosion. Altough a node-based alternative might be possible
|
||||
too, and much more lightweight.
|
||||
*/
|
||||
|
||||
float player_max_speed = 0;
|
||||
|
||||
@@ -173,6 +173,9 @@ class PlayerSAO : public ServerActiveObject
|
||||
{
|
||||
return m_peer_id;
|
||||
}
|
||||
|
||||
// Cheat prevention
|
||||
|
||||
v3f getLastGoodPosition() const
|
||||
{
|
||||
return m_last_good_position;
|
||||
@@ -183,6 +186,26 @@ class PlayerSAO : public ServerActiveObject
|
||||
m_time_from_last_punch = 0.0;
|
||||
return r;
|
||||
}
|
||||
void noCheatDigStart(v3s16 p)
|
||||
{
|
||||
m_nocheat_dig_pos = p;
|
||||
m_nocheat_dig_time = 0;
|
||||
}
|
||||
v3s16 getNoCheatDigPos()
|
||||
{
|
||||
return m_nocheat_dig_pos;
|
||||
}
|
||||
float getNoCheatDigTime()
|
||||
{
|
||||
return m_nocheat_dig_time;
|
||||
}
|
||||
void noCheatDigEnd()
|
||||
{
|
||||
m_nocheat_dig_pos = v3s16(32767, 32767, 32767);
|
||||
}
|
||||
|
||||
// Other
|
||||
|
||||
void updatePrivileges(const std::set<std::string> &privs,
|
||||
bool is_singleplayer)
|
||||
{
|
||||
@@ -196,9 +219,14 @@ class PlayerSAO : public ServerActiveObject
|
||||
Player *m_player;
|
||||
u16 m_peer_id;
|
||||
Inventory *m_inventory;
|
||||
|
||||
// Cheat prevention
|
||||
v3f m_last_good_position;
|
||||
float m_last_good_position_age;
|
||||
float m_time_from_last_punch;
|
||||
v3s16 m_nocheat_dig_pos;
|
||||
float m_nocheat_dig_time;
|
||||
|
||||
int m_wield_index;
|
||||
bool m_position_not_sent;
|
||||
ItemGroupList m_armor_groups;
|
||||
|
||||
@@ -121,6 +121,7 @@ void set_default_settings(Settings *settings)
|
||||
settings->setDefault("unlimited_player_transfer_distance", "true");
|
||||
settings->setDefault("enable_pvp", "true");
|
||||
settings->setDefault("disallow_empty_password", "false");
|
||||
settings->setDefault("disable_anticheat", "false");
|
||||
|
||||
settings->setDefault("profiler_print_interval", "0");
|
||||
settings->setDefault("enable_mapgen_debug_info", "false");
|
||||
|
||||
14
src/game.cpp
14
src/game.cpp
@@ -367,6 +367,14 @@ PointedThing getPointedThing(Client *client, v3f player_position,
|
||||
s16 zend = pos_i.Z + (camera_direction.Z>0 ? a : 1);
|
||||
s16 xend = pos_i.X + (camera_direction.X>0 ? a : 1);
|
||||
|
||||
// Prevent signed number overflow
|
||||
if(yend==32767)
|
||||
yend=32766;
|
||||
if(zend==32767)
|
||||
zend=32766;
|
||||
if(xend==32767)
|
||||
xend=32766;
|
||||
|
||||
for(s16 y = ystart; y <= yend; y++)
|
||||
for(s16 z = zstart; z <= zend; z++)
|
||||
for(s16 x = xstart; x <= xend; x++)
|
||||
@@ -2112,7 +2120,9 @@ void the_game(
|
||||
ldown_for_dig = true;
|
||||
}
|
||||
MapNode n = client.getEnv().getClientMap().getNode(nodepos);
|
||||
|
||||
|
||||
// NOTE: Similar piece of code exists on the server side for
|
||||
// cheat detection.
|
||||
// Get digging parameters
|
||||
DigParams params = getDigParams(nodedef->get(n).groups,
|
||||
&playeritem_toolcap);
|
||||
@@ -2160,7 +2170,7 @@ void the_game(
|
||||
{
|
||||
dig_index = crack_animation_length;
|
||||
}
|
||||
|
||||
|
||||
// Don't show cracks if not diggable
|
||||
if(dig_time_complete >= 100000.0)
|
||||
{
|
||||
|
||||
@@ -258,9 +258,10 @@ KeyPress::KeyPress(const char *name)
|
||||
try {
|
||||
Key = keyname_to_keycode(name);
|
||||
m_name = name;
|
||||
if (strlen(name) > 8)
|
||||
mbtowc(&Char, name + 8, 1);
|
||||
else
|
||||
if (strlen(name) > 8) {
|
||||
int chars_read = mbtowc(&Char, name + 8, 1);
|
||||
assert (chars_read == 1 && "unexpected multibyte character");
|
||||
} else
|
||||
Char = L'\0';
|
||||
return;
|
||||
} catch (UnknownKeycode &e) {};
|
||||
@@ -270,7 +271,8 @@ KeyPress::KeyPress(const char *name)
|
||||
m_name += name;
|
||||
try {
|
||||
Key = keyname_to_keycode(m_name.c_str());
|
||||
mbtowc(&Char, name, 1);
|
||||
int chars_read = mbtowc(&Char, name, 1);
|
||||
assert (chars_read == 1 && "unexpected multibyte character");
|
||||
return;
|
||||
} catch (UnknownKeycode &e) {};
|
||||
}
|
||||
@@ -279,7 +281,8 @@ KeyPress::KeyPress(const char *name)
|
||||
|
||||
Key = irr::KEY_KEY_CODES_COUNT;
|
||||
|
||||
mbtowc(&Char, name, 1);
|
||||
int mbtowc_ret = mbtowc(&Char, name, 1);
|
||||
assert (mbtowc_ret == 1 && "unexpected multibyte character");
|
||||
m_name = name[0];
|
||||
}
|
||||
|
||||
@@ -290,9 +293,9 @@ KeyPress::KeyPress(const irr::SEvent::SKeyInput &in)
|
||||
if (valid_kcode(Key)) {
|
||||
m_name = KeyNames[Key];
|
||||
} else {
|
||||
size_t maxlen = wctomb(NULL, Char);
|
||||
m_name.resize(maxlen+1, '\0');
|
||||
wctomb(&m_name[0], Char);
|
||||
m_name.resize(MB_CUR_MAX+1, '\0');
|
||||
int written = wctomb(&m_name[0], Char);
|
||||
assert (written >= 0 && "unexpected multibyte character");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,10 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
LocalPlayer::LocalPlayer(IGameDef *gamedef):
|
||||
Player(gamedef),
|
||||
m_sneak_node(32767,32767,32767),
|
||||
m_sneak_node_exists(false)
|
||||
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)
|
||||
{
|
||||
// Initialize hp to 0, so that no hearts will be shown if server
|
||||
// doesn't support health points
|
||||
@@ -189,8 +192,26 @@ void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d,
|
||||
|
||||
/*
|
||||
Check the nodes under the player to see from which node the
|
||||
player is sneaking from, if any.
|
||||
player is sneaking from, if any. If the node from under
|
||||
the player has been removed, the player falls.
|
||||
*/
|
||||
v3s16 current_node = floatToInt(position - v3f(0,BS/2,0), BS);
|
||||
if(m_sneak_node_exists &&
|
||||
nodemgr->get(map.getNodeNoEx(m_old_node_below)).name == "air" &&
|
||||
m_old_node_below_type != "air")
|
||||
{
|
||||
// Old node appears to have been removed; that is,
|
||||
// it wasn't air before but now it is
|
||||
m_need_to_get_new_sneak_node = false;
|
||||
m_sneak_node_exists = false;
|
||||
}
|
||||
else if(nodemgr->get(map.getNodeNoEx(current_node)).name != "air")
|
||||
{
|
||||
// We are on something, so make sure to recalculate the sneak
|
||||
// node.
|
||||
m_need_to_get_new_sneak_node = true;
|
||||
}
|
||||
if(m_need_to_get_new_sneak_node)
|
||||
{
|
||||
v3s16 pos_i_bottom = floatToInt(position - v3f(0,BS/2,0), BS);
|
||||
v2f player_p2df(position.X, position.Z);
|
||||
@@ -240,17 +261,9 @@ void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d,
|
||||
}
|
||||
|
||||
bool sneak_node_found = (min_distance_f < 100000.0*BS*0.9);
|
||||
|
||||
if(control.sneak && m_sneak_node_exists)
|
||||
{
|
||||
if(sneak_node_found)
|
||||
m_sneak_node = new_sneak_node;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_sneak_node = new_sneak_node;
|
||||
m_sneak_node_exists = sneak_node_found;
|
||||
}
|
||||
|
||||
m_sneak_node = new_sneak_node;
|
||||
m_sneak_node_exists = sneak_node_found;
|
||||
|
||||
/*
|
||||
If sneaking, the player's collision box can be in air, so
|
||||
@@ -295,6 +308,12 @@ void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Update the node last under the player
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d)
|
||||
|
||||
@@ -95,6 +95,12 @@ class LocalPlayer : public Player
|
||||
v3s16 m_sneak_node;
|
||||
// Whether the player is allowed to sneak
|
||||
bool m_sneak_node_exists;
|
||||
// Node below player, used to determine whether it has been removed,
|
||||
// and its old type
|
||||
v3s16 m_old_node_below;
|
||||
std::string m_old_node_below_type;
|
||||
// Whether recalculation of the sneak node is needed
|
||||
bool m_need_to_get_new_sneak_node;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -605,7 +605,46 @@ TileSpec getNodeTile(MapNode mn, v3s16 p, v3s16 dir, MeshMakeData *data)
|
||||
0, 5, 0, 2, 0, 3, 1, 4, // facedir = 3
|
||||
};
|
||||
u8 tileindex = dir_to_tile[facedir*8 + dir_i];
|
||||
return getNodeTileN(mn, p, tileindex, data);
|
||||
|
||||
// If not rotated or is side tile, we're done
|
||||
if(facedir == 0 || (tileindex != 0 && tileindex != 1))
|
||||
return getNodeTileN(mn, p, tileindex, data);
|
||||
|
||||
// This is the top or bottom tile, and it shall be rotated; thus rotate it
|
||||
TileSpec spec = getNodeTileN(mn, p, tileindex, data);
|
||||
if(tileindex == 0){
|
||||
if(facedir == 1){ // -90
|
||||
std::string name = data->m_gamedef->tsrc()->getTextureName(spec.texture.id);
|
||||
name += "^[transformR270";
|
||||
spec.texture = data->m_gamedef->tsrc()->getTexture(name);
|
||||
}
|
||||
else if(facedir == 2){ // 180
|
||||
spec.texture.pos += spec.texture.size;
|
||||
spec.texture.size *= -1;
|
||||
}
|
||||
else if(facedir == 3){ // 90
|
||||
std::string name = data->m_gamedef->tsrc()->getTextureName(spec.texture.id);
|
||||
name += "^[transformR90";
|
||||
spec.texture = data->m_gamedef->tsrc()->getTexture(name);
|
||||
}
|
||||
}
|
||||
else if(tileindex == 1){
|
||||
if(facedir == 1){ // -90
|
||||
std::string name = data->m_gamedef->tsrc()->getTextureName(spec.texture.id);
|
||||
name += "^[transformR90";
|
||||
spec.texture = data->m_gamedef->tsrc()->getTexture(name);
|
||||
}
|
||||
else if(facedir == 2){ // 180
|
||||
spec.texture.pos += spec.texture.size;
|
||||
spec.texture.size *= -1;
|
||||
}
|
||||
else if(facedir == 3){ // 90
|
||||
std::string name = data->m_gamedef->tsrc()->getTextureName(spec.texture.id);
|
||||
name += "^[transformR270";
|
||||
spec.texture = data->m_gamedef->tsrc()->getTexture(name);
|
||||
}
|
||||
}
|
||||
return spec;
|
||||
}
|
||||
|
||||
static void getTileInfo(
|
||||
|
||||
@@ -4593,7 +4593,7 @@ static int l_get_craft_recipe(lua_State *L)
|
||||
setintfield(L, -1, "width", input.width);
|
||||
switch (input.method) {
|
||||
case CRAFT_METHOD_NORMAL:
|
||||
lua_pushstring(L,"noraml");
|
||||
lua_pushstring(L,"normal");
|
||||
break;
|
||||
case CRAFT_METHOD_COOKING:
|
||||
lua_pushstring(L,"cooking");
|
||||
|
||||
@@ -3014,6 +3014,8 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
|
||||
}
|
||||
if(n.getContent() != CONTENT_IGNORE)
|
||||
scriptapi_node_on_punch(m_lua, p_under, n, playersao);
|
||||
// Cheat prevention
|
||||
playersao->noCheatDigStart(p_under);
|
||||
}
|
||||
else if(pointed.type == POINTEDTHING_OBJECT)
|
||||
{
|
||||
@@ -3051,7 +3053,7 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
|
||||
*/
|
||||
else if(action == 2)
|
||||
{
|
||||
// Only complete digging of nodes
|
||||
// Only digging of nodes
|
||||
if(pointed.type == POINTEDTHING_NODE)
|
||||
{
|
||||
MapNode n(CONTENT_IGNORE);
|
||||
@@ -3067,10 +3069,65 @@ void Server::ProcessData(u8 *data, u32 datasize, u16 peer_id)
|
||||
m_emerge_queue.addBlock(peer_id,
|
||||
getNodeBlockPos(p_above), BLOCK_EMERGE_FLAG_FROMDISK);
|
||||
}
|
||||
if(n.getContent() != CONTENT_IGNORE)
|
||||
|
||||
/* Cheat prevention */
|
||||
bool is_valid_dig = true;
|
||||
if(!isSingleplayer() && !g_settings->getBool("disable_anticheat"))
|
||||
{
|
||||
v3s16 nocheat_p = playersao->getNoCheatDigPos();
|
||||
float nocheat_t = playersao->getNoCheatDigTime();
|
||||
playersao->noCheatDigEnd();
|
||||
// If player didn't start digging this, ignore dig
|
||||
if(nocheat_p != p_under){
|
||||
infostream<<"Server: NoCheat: "<<player->getName()
|
||||
<<" started digging "
|
||||
<<PP(nocheat_p)<<" and completed digging "
|
||||
<<PP(p_under)<<"; not digging."<<std::endl;
|
||||
is_valid_dig = false;
|
||||
}
|
||||
// Get player's wielded item
|
||||
ItemStack playeritem;
|
||||
InventoryList *mlist = playersao->getInventory()->getList("main");
|
||||
if(mlist != NULL)
|
||||
playeritem = mlist->getItem(playersao->getWieldIndex());
|
||||
ToolCapabilities playeritem_toolcap =
|
||||
playeritem.getToolCapabilities(m_itemdef);
|
||||
// Get diggability and expected digging time
|
||||
DigParams params = getDigParams(m_nodedef->get(n).groups,
|
||||
&playeritem_toolcap);
|
||||
// If can't dig, try hand
|
||||
if(!params.diggable){
|
||||
const ItemDefinition &hand = m_itemdef->get("");
|
||||
const ToolCapabilities *tp = hand.tool_capabilities;
|
||||
if(tp)
|
||||
params = getDigParams(m_nodedef->get(n).groups, tp);
|
||||
}
|
||||
// If can't dig, ignore dig
|
||||
if(!params.diggable){
|
||||
infostream<<"Server: NoCheat: "<<player->getName()
|
||||
<<" completed digging "<<PP(p_under)
|
||||
<<", which is not diggable with tool. not digging."
|
||||
<<std::endl;
|
||||
is_valid_dig = false;
|
||||
}
|
||||
// If time is considerably too short, ignore dig
|
||||
// Check time only for medium and slow timed digs
|
||||
if(params.diggable && params.time > 0.3 && nocheat_t < 0.5 * params.time){
|
||||
infostream<<"Server: NoCheat: "<<player->getName()
|
||||
<<" completed digging "
|
||||
<<PP(p_under)<<" in "<<nocheat_t<<"s; expected "
|
||||
<<params.time<<"s; not digging."<<std::endl;
|
||||
is_valid_dig = false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Actually dig node */
|
||||
|
||||
if(is_valid_dig && n.getContent() != CONTENT_IGNORE)
|
||||
scriptapi_node_on_dig(m_lua, p_under, n, playersao);
|
||||
|
||||
if (m_env->getMap().getNodeNoEx(p_under).getContent() != CONTENT_AIR)
|
||||
// Send unusual result (that is, node not being removed)
|
||||
if(m_env->getMap().getNodeNoEx(p_under).getContent() != CONTENT_AIR)
|
||||
{
|
||||
// Re-send block to revert change on client-side
|
||||
RemoteClient *client = getClient(peer_id);
|
||||
|
||||
Reference in New Issue
Block a user