diff options
-rw-r--r-- | src/clientiface.cpp | 3 | ||||
-rw-r--r-- | src/content_sao.cpp | 676 | ||||
-rw-r--r-- | src/content_sao.h | 299 | ||||
-rw-r--r-- | src/database/database-files.cpp | 2 | ||||
-rw-r--r-- | src/database/database-postgresql.cpp | 2 | ||||
-rw-r--r-- | src/database/database-sqlite3.cpp | 2 | ||||
-rw-r--r-- | src/network/serverpackethandler.cpp | 2 | ||||
-rw-r--r-- | src/remoteplayer.cpp | 2 | ||||
-rw-r--r-- | src/script/common/c_content.cpp | 3 | ||||
-rw-r--r-- | src/script/cpp_api/s_base.cpp | 2 | ||||
-rw-r--r-- | src/script/cpp_api/s_player.h | 1 | ||||
-rw-r--r-- | src/script/lua_api/l_env.cpp | 3 | ||||
-rw-r--r-- | src/script/lua_api/l_object.cpp | 2 | ||||
-rw-r--r-- | src/server.cpp | 2 | ||||
-rw-r--r-- | src/server/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/server/player_sao.cpp | 695 | ||||
-rw-r--r-- | src/server/player_sao.h | 325 | ||||
-rw-r--r-- | src/server/unit_sao.cpp | 4 | ||||
-rw-r--r-- | src/serverenvironment.cpp | 4 |
19 files changed, 1038 insertions, 992 deletions
diff --git a/src/clientiface.cpp b/src/clientiface.cpp index dceaa64f2..17237f73e 100644 --- a/src/clientiface.cpp +++ b/src/clientiface.cpp @@ -19,6 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <sstream> #include "clientiface.h" +#include "content_sao.h" #include "network/connection.h" #include "network/serveropcodes.h" #include "remoteplayer.h" @@ -27,7 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "serverenvironment.h" #include "map.h" #include "emerge.h" -#include "content_sao.h" // TODO this is used for cleanup of only +#include "server/player_sao.h" #include "log.h" #include "util/srp.h" #include "face_position_cache.h" diff --git a/src/content_sao.cpp b/src/content_sao.cpp index 0d387b53a..7ec17aa82 100644 --- a/src/content_sao.cpp +++ b/src/content_sao.cpp @@ -27,6 +27,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "remoteplayer.h" #include "server.h" #include "scripting_server.h" +#include "server/player_sao.h" #include "settings.h" #include <algorithm> #include <cmath> @@ -678,678 +679,3 @@ bool LuaEntitySAO::collideWithObjects() const { return m_prop.collideWithObjects; } - -/* - PlayerSAO -*/ - -// No prototype, PlayerSAO does not need to be deserialized - -PlayerSAO::PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, session_t peer_id_, - bool is_singleplayer): - UnitSAO(env_, v3f(0,0,0)), - m_player(player_), - m_peer_id(peer_id_), - m_is_singleplayer(is_singleplayer) -{ - SANITY_CHECK(m_peer_id != PEER_ID_INEXISTENT); - - m_prop.hp_max = PLAYER_MAX_HP_DEFAULT; - m_prop.breath_max = PLAYER_MAX_BREATH_DEFAULT; - m_prop.physical = false; - m_prop.collisionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f); - m_prop.selectionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f); - m_prop.pointable = true; - // Start of default appearance, this should be overwritten by Lua - m_prop.visual = "upright_sprite"; - m_prop.visual_size = v3f(1, 2, 1); - m_prop.textures.clear(); - m_prop.textures.emplace_back("player.png"); - m_prop.textures.emplace_back("player_back.png"); - m_prop.colors.clear(); - m_prop.colors.emplace_back(255, 255, 255, 255); - m_prop.spritediv = v2s16(1,1); - m_prop.eye_height = 1.625f; - // End of default appearance - m_prop.is_visible = true; - m_prop.backface_culling = false; - m_prop.makes_footstep_sound = true; - m_prop.stepheight = PLAYER_DEFAULT_STEPHEIGHT * BS; - m_hp = m_prop.hp_max; - m_breath = m_prop.breath_max; - // Disable zoom in survival mode using a value of 0 - m_prop.zoom_fov = g_settings->getBool("creative_mode") ? 15.0f : 0.0f; - - if (!g_settings->getBool("enable_damage")) - m_armor_groups["immortal"] = 1; -} - -void PlayerSAO::finalize(RemotePlayer *player, const std::set<std::string> &privs) -{ - assert(player); - m_player = player; - m_privs = privs; -} - -v3f PlayerSAO::getEyeOffset() const -{ - return v3f(0, BS * m_prop.eye_height, 0); -} - -std::string PlayerSAO::getDescription() -{ - return std::string("player ") + m_player->getName(); -} - -// Called after id has been set and has been inserted in environment -void PlayerSAO::addedToEnvironment(u32 dtime_s) -{ - ServerActiveObject::addedToEnvironment(dtime_s); - ServerActiveObject::setBasePosition(m_base_position); - m_player->setPlayerSAO(this); - m_player->setPeerId(m_peer_id); - m_last_good_position = m_base_position; -} - -// Called before removing from environment -void PlayerSAO::removingFromEnvironment() -{ - ServerActiveObject::removingFromEnvironment(); - if (m_player->getPlayerSAO() == this) { - unlinkPlayerSessionAndSave(); - for (u32 attached_particle_spawner : m_attached_particle_spawners) { - m_env->deleteParticleSpawner(attached_particle_spawner, false); - } - } -} - -std::string PlayerSAO::getClientInitializationData(u16 protocol_version) -{ - std::ostringstream os(std::ios::binary); - - // Protocol >= 15 - writeU8(os, 1); // version - os << serializeString(m_player->getName()); // name - writeU8(os, 1); // is_player - writeS16(os, getId()); // id - writeV3F32(os, m_base_position); - writeV3F32(os, m_rotation); - writeU16(os, getHP()); - - std::ostringstream msg_os(std::ios::binary); - msg_os << serializeLongString(getPropertyPacket()); // message 1 - msg_os << serializeLongString(generateUpdateArmorGroupsCommand()); // 2 - msg_os << serializeLongString(generateUpdateAnimationCommand()); // 3 - for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator - ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) { - msg_os << serializeLongString(generateUpdateBonePositionCommand((*ii).first, - (*ii).second.X, (*ii).second.Y)); // m_bone_position.size - } - msg_os << serializeLongString(generateUpdateAttachmentCommand()); // 4 - msg_os << serializeLongString(generateUpdatePhysicsOverrideCommand()); // 5 - // (AO_CMD_UPDATE_NAMETAG_ATTRIBUTES) : Deprecated, for backwards compatibility only. - msg_os << serializeLongString(generateUpdateNametagAttributesCommand(m_prop.nametag_color)); // 6 - int message_count = 6 + m_bone_position.size(); - for (std::unordered_set<int>::const_iterator ii = m_attachment_child_ids.begin(); - ii != m_attachment_child_ids.end(); ++ii) { - if (ServerActiveObject *obj = m_env->getActiveObject(*ii)) { - message_count++; - msg_os << serializeLongString(obj->generateUpdateInfantCommand(*ii, protocol_version)); - } - } - - writeU8(os, message_count); - os.write(msg_os.str().c_str(), msg_os.str().size()); - - // return result - return os.str(); -} - -void PlayerSAO::getStaticData(std::string * result) const -{ - FATAL_ERROR("Obsolete function"); -} - -void PlayerSAO::step(float dtime, bool send_recommended) -{ - if (!isImmortal() && m_drowning_interval.step(dtime, 2.0f)) { - // Get nose/mouth position, approximate with eye position - v3s16 p = floatToInt(getEyePosition(), BS); - MapNode n = m_env->getMap().getNode(p); - const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n); - // If node generates drown - if (c.drowning > 0 && m_hp > 0) { - if (m_breath > 0) - setBreath(m_breath - 1); - - // No more breath, damage player - if (m_breath == 0) { - PlayerHPChangeReason reason(PlayerHPChangeReason::DROWNING); - setHP(m_hp - c.drowning, reason); - m_env->getGameDef()->SendPlayerHPOrDie(this, reason); - } - } - } - - if (m_breathing_interval.step(dtime, 0.5f) && !isImmortal()) { - // Get nose/mouth position, approximate with eye position - v3s16 p = floatToInt(getEyePosition(), BS); - MapNode n = m_env->getMap().getNode(p); - const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n); - // If player is alive & not drowning & not in ignore & not immortal, breathe - if (m_breath < m_prop.breath_max && c.drowning == 0 && - n.getContent() != CONTENT_IGNORE && m_hp > 0) - setBreath(m_breath + 1); - } - - if (!isImmortal() && m_node_hurt_interval.step(dtime, 1.0f)) { - u32 damage_per_second = 0; - std::string nodename; - // Lowest and highest damage points are 0.1 within collisionbox - float dam_top = m_prop.collisionbox.MaxEdge.Y - 0.1f; - - // Sequence of damage points, starting 0.1 above feet and progressing - // upwards in 1 node intervals, stopping below top damage point. - for (float dam_height = 0.1f; dam_height < dam_top; dam_height++) { - v3s16 p = floatToInt(m_base_position + - v3f(0.0f, dam_height * BS, 0.0f), BS); - MapNode n = m_env->getMap().getNode(p); - const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n); - if (c.damage_per_second > damage_per_second) { - damage_per_second = c.damage_per_second; - nodename = c.name; - } - } - - // Top damage point - v3s16 ptop = floatToInt(m_base_position + - v3f(0.0f, dam_top * BS, 0.0f), BS); - MapNode ntop = m_env->getMap().getNode(ptop); - const ContentFeatures &c = m_env->getGameDef()->ndef()->get(ntop); - if (c.damage_per_second > damage_per_second) { - damage_per_second = c.damage_per_second; - nodename = c.name; - } - - if (damage_per_second != 0 && m_hp > 0) { - s32 newhp = (s32)m_hp - (s32)damage_per_second; - PlayerHPChangeReason reason(PlayerHPChangeReason::NODE_DAMAGE, nodename); - setHP(newhp, reason); - m_env->getGameDef()->SendPlayerHPOrDie(this, reason); - } - } - - if (!m_properties_sent) { - m_properties_sent = true; - std::string str = getPropertyPacket(); - // create message and add to list - ActiveObjectMessage aom(getId(), true, str); - m_messages_out.push(aom); - m_env->getScriptIface()->player_event(this, "properties_changed"); - } - - // If attached, check that our parent is still there. If it isn't, detach. - if (m_attachment_parent_id && !isAttached()) { - m_attachment_parent_id = 0; - m_attachment_bone = ""; - m_attachment_position = v3f(0.0f, 0.0f, 0.0f); - m_attachment_rotation = v3f(0.0f, 0.0f, 0.0f); - setBasePosition(m_last_good_position); - m_env->getGameDef()->SendMovePlayer(m_peer_id); - } - - //dstream<<"PlayerSAO::step: dtime: "<<dtime<<std::endl; - - // Set lag pool maximums based on estimated lag - const float LAG_POOL_MIN = 5.0f; - float lag_pool_max = m_env->getMaxLagEstimate() * 2.0f; - if(lag_pool_max < LAG_POOL_MIN) - lag_pool_max = LAG_POOL_MIN; - m_dig_pool.setMax(lag_pool_max); - m_move_pool.setMax(lag_pool_max); - - // Increment cheat prevention timers - m_dig_pool.add(dtime); - m_move_pool.add(dtime); - m_time_from_last_teleport += dtime; - m_time_from_last_punch += dtime; - m_nocheat_dig_time += dtime; - m_max_speed_override_time = MYMAX(m_max_speed_override_time - dtime, 0.0f); - - // Each frame, parent position is copied if the object is attached, - // otherwise it's calculated normally. - // If the object gets detached this comes into effect automatically from - // the last known origin. - if (isAttached()) { - v3f pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition(); - m_last_good_position = pos; - setBasePosition(pos); - } - - if (!send_recommended) - return; - - if (m_position_not_sent) { - m_position_not_sent = false; - float update_interval = m_env->getSendRecommendedInterval(); - v3f pos; - // When attached, the position is only sent to clients where the - // parent isn't known - if (isAttached()) - pos = m_last_good_position; - else - pos = m_base_position; - - std::string str = generateUpdatePositionCommand( - pos, - v3f(0.0f, 0.0f, 0.0f), - v3f(0.0f, 0.0f, 0.0f), - m_rotation, - true, - false, - update_interval - ); - // create message and add to list - m_messages_out.emplace(getId(), false, str); - } - - if (!m_armor_groups_sent) { - m_armor_groups_sent = true; - // create message and add to list - m_messages_out.emplace(getId(), true, generateUpdateArmorGroupsCommand()); - } - - if (!m_physics_override_sent) { - m_physics_override_sent = true; - // create message and add to list - m_messages_out.emplace(getId(), true, generateUpdatePhysicsOverrideCommand()); - } - - if (!m_animation_sent) { - m_animation_sent = true; - // create message and add to list - m_messages_out.emplace(getId(), true, generateUpdateAnimationCommand()); - } - - if (!m_bone_position_sent) { - m_bone_position_sent = true; - for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator - ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) { - std::string str = generateUpdateBonePositionCommand((*ii).first, - (*ii).second.X, (*ii).second.Y); - // create message and add to list - m_messages_out.emplace(getId(), true, str); - } - } - - if (!m_attachment_sent) { - m_attachment_sent = true; - std::string str = generateUpdateAttachmentCommand(); - // create message and add to list - ActiveObjectMessage aom(getId(), true, str); - m_messages_out.push(aom); - } -} - -std::string PlayerSAO::generateUpdatePhysicsOverrideCommand() const -{ - std::ostringstream os(std::ios::binary); - // command - writeU8(os, AO_CMD_SET_PHYSICS_OVERRIDE); - // parameters - writeF32(os, m_physics_override_speed); - writeF32(os, m_physics_override_jump); - writeF32(os, m_physics_override_gravity); - // these are sent inverted so we get true when the server sends nothing - writeU8(os, !m_physics_override_sneak); - writeU8(os, !m_physics_override_sneak_glitch); - writeU8(os, !m_physics_override_new_move); - return os.str(); -} - -void PlayerSAO::setBasePosition(const v3f &position) -{ - if (m_player && position != m_base_position) - m_player->setDirty(true); - - // This needs to be ran for attachments too - ServerActiveObject::setBasePosition(position); - - // Updating is not wanted/required for player migration - if (m_env) { - m_position_not_sent = true; - } -} - -void PlayerSAO::setPos(const v3f &pos) -{ - if(isAttached()) - return; - - // Send mapblock of target location - v3s16 blockpos = v3s16(pos.X / MAP_BLOCKSIZE, pos.Y / MAP_BLOCKSIZE, pos.Z / MAP_BLOCKSIZE); - m_env->getGameDef()->SendBlock(m_peer_id, blockpos); - - setBasePosition(pos); - // Movement caused by this command is always valid - m_last_good_position = pos; - m_move_pool.empty(); - m_time_from_last_teleport = 0.0; - m_env->getGameDef()->SendMovePlayer(m_peer_id); -} - -void PlayerSAO::moveTo(v3f pos, bool continuous) -{ - if(isAttached()) - return; - - setBasePosition(pos); - // Movement caused by this command is always valid - m_last_good_position = pos; - m_move_pool.empty(); - m_time_from_last_teleport = 0.0; - m_env->getGameDef()->SendMovePlayer(m_peer_id); -} - -void PlayerSAO::setPlayerYaw(const float yaw) -{ - v3f rotation(0, yaw, 0); - if (m_player && yaw != m_rotation.Y) - m_player->setDirty(true); - - // Set player model yaw, not look view - UnitSAO::setRotation(rotation); -} - -void PlayerSAO::setFov(const float fov) -{ - if (m_player && fov != m_fov) - m_player->setDirty(true); - - m_fov = fov; -} - -void PlayerSAO::setWantedRange(const s16 range) -{ - if (m_player && range != m_wanted_range) - m_player->setDirty(true); - - m_wanted_range = range; -} - -void PlayerSAO::setPlayerYawAndSend(const float yaw) -{ - setPlayerYaw(yaw); - m_env->getGameDef()->SendMovePlayer(m_peer_id); -} - -void PlayerSAO::setLookPitch(const float pitch) -{ - if (m_player && pitch != m_pitch) - m_player->setDirty(true); - - m_pitch = pitch; -} - -void PlayerSAO::setLookPitchAndSend(const float pitch) -{ - setLookPitch(pitch); - m_env->getGameDef()->SendMovePlayer(m_peer_id); -} - -u16 PlayerSAO::punch(v3f dir, - const ToolCapabilities *toolcap, - ServerActiveObject *puncher, - float time_from_last_punch) -{ - if (!toolcap) - return 0; - - FATAL_ERROR_IF(!puncher, "Punch action called without SAO"); - - // No effect if PvP disabled or if immortal - if (isImmortal() || !g_settings->getBool("enable_pvp")) { - if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) { - // create message and add to list - sendPunchCommand(); - return 0; - } - } - - s32 old_hp = getHP(); - HitParams hitparams = getHitParams(m_armor_groups, toolcap, - time_from_last_punch); - - PlayerSAO *playersao = m_player->getPlayerSAO(); - - bool damage_handled = m_env->getScriptIface()->on_punchplayer(playersao, - puncher, time_from_last_punch, toolcap, dir, - hitparams.hp); - - if (!damage_handled) { - setHP((s32)getHP() - (s32)hitparams.hp, - PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, puncher)); - } else { // override client prediction - if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) { - // create message and add to list - sendPunchCommand(); - } - } - - actionstream << puncher->getDescription() << " (id=" << puncher->getId() << - ", hp=" << puncher->getHP() << ") punched " << - getDescription() << " (id=" << m_id << ", hp=" << m_hp << - "), damage=" << (old_hp - (s32)getHP()) << - (damage_handled ? " (handled by Lua)" : "") << std::endl; - - return hitparams.wear; -} - -void PlayerSAO::setHP(s32 hp, const PlayerHPChangeReason &reason) -{ - s32 oldhp = m_hp; - - hp = rangelim(hp, 0, m_prop.hp_max); - - if (oldhp != hp) { - s32 hp_change = m_env->getScriptIface()->on_player_hpchange(this, hp - oldhp, reason); - if (hp_change == 0) - return; - - hp = rangelim(oldhp + hp_change, 0, m_prop.hp_max); - } - - if (hp < oldhp && isImmortal()) - return; - - m_hp = hp; - - // Update properties on death - if ((hp == 0) != (oldhp == 0)) - m_properties_sent = false; -} - -void PlayerSAO::setBreath(const u16 breath, bool send) -{ - if (m_player && breath != m_breath) - m_player->setDirty(true); - - m_breath = rangelim(breath, 0, m_prop.breath_max); - - if (send) - m_env->getGameDef()->SendPlayerBreath(this); -} - -Inventory *PlayerSAO::getInventory() const -{ - return m_player ? &m_player->inventory : nullptr; -} - -InventoryLocation PlayerSAO::getInventoryLocation() const -{ - InventoryLocation loc; - loc.setPlayer(m_player->getName()); - return loc; -} - -u16 PlayerSAO::getWieldIndex() const -{ - return m_player->getWieldIndex(); -} - -ItemStack PlayerSAO::getWieldedItem(ItemStack *selected, ItemStack *hand) const -{ - return m_player->getWieldedItem(selected, hand); -} - -bool PlayerSAO::setWieldedItem(const ItemStack &item) -{ - InventoryList *mlist = m_player->inventory.getList(getWieldList()); - if (mlist) { - mlist->changeItem(m_player->getWieldIndex(), item); - return true; - } - return false; -} - -void PlayerSAO::disconnected() -{ - m_peer_id = PEER_ID_INEXISTENT; - m_pending_removal = true; -} - -void PlayerSAO::unlinkPlayerSessionAndSave() -{ - assert(m_player->getPlayerSAO() == this); - m_player->setPeerId(PEER_ID_INEXISTENT); - m_env->savePlayer(m_player); - m_player->setPlayerSAO(NULL); - m_env->removePlayer(m_player); -} - -std::string PlayerSAO::getPropertyPacket() -{ - m_prop.is_visible = (true); - return generateSetPropertiesCommand(m_prop); -} - -void PlayerSAO::setMaxSpeedOverride(const v3f &vel) -{ - if (m_max_speed_override_time == 0.0f) - m_max_speed_override = vel; - else - m_max_speed_override += vel; - if (m_player) { - float accel = MYMIN(m_player->movement_acceleration_default, - m_player->movement_acceleration_air); - m_max_speed_override_time = m_max_speed_override.getLength() / accel / BS; - } -} - -bool PlayerSAO::checkMovementCheat() -{ - if (isAttached() || m_is_singleplayer || - g_settings->getBool("disable_anticheat")) { - m_last_good_position = m_base_position; - return false; - } - - bool cheated = false; - /* - Check player movements - - 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. Altough a node-based alternative might be possible - too, and much more lightweight. - */ - - float override_max_H, override_max_V; - if (m_max_speed_override_time > 0.0f) { - override_max_H = MYMAX(fabs(m_max_speed_override.X), fabs(m_max_speed_override.Z)); - override_max_V = fabs(m_max_speed_override.Y); - } else { - override_max_H = override_max_V = 0.0f; - } - - float player_max_walk = 0; // horizontal movement - float player_max_jump = 0; // vertical upwards movement - - if (m_privs.count("fast") != 0) - player_max_walk = m_player->movement_speed_fast; // Fast speed - else - player_max_walk = m_player->movement_speed_walk; // Normal speed - player_max_walk *= m_physics_override_speed; - player_max_walk = MYMAX(player_max_walk, override_max_H); - - player_max_jump = m_player->movement_speed_jump * m_physics_override_jump; - // FIXME: Bouncy nodes cause practically unbound increase in Y speed, - // until this can be verified correctly, tolerate higher jumping speeds - player_max_jump *= 2.0; - player_max_jump = MYMAX(player_max_jump, override_max_V); - - // Don't divide by zero! - if (player_max_walk < 0.0001f) - player_max_walk = 0.0001f; - if (player_max_jump < 0.0001f) - player_max_jump = 0.0001f; - - v3f diff = (m_base_position - m_last_good_position); - float d_vert = diff.Y; - diff.Y = 0; - float d_horiz = diff.getLength(); - float required_time = d_horiz / player_max_walk; - - // FIXME: Checking downwards movement is not easily possible currently, - // the server could calculate speed differences to examine the gravity - if (d_vert > 0) { - // In certain cases (water, ladders) walking speed is applied vertically - float s = MYMAX(player_max_jump, player_max_walk); - required_time = MYMAX(required_time, d_vert / s); - } - - if (m_move_pool.grab(required_time)) { - m_last_good_position = m_base_position; - } else { - const float LAG_POOL_MIN = 5.0; - float lag_pool_max = m_env->getMaxLagEstimate() * 2.0; - lag_pool_max = MYMAX(lag_pool_max, LAG_POOL_MIN); - if (m_time_from_last_teleport > lag_pool_max) { - actionstream << "Player " << m_player->getName() - << " moved too fast; resetting position" - << std::endl; - cheated = true; - } - setBasePosition(m_last_good_position); - } - return cheated; -} - -bool PlayerSAO::getCollisionBox(aabb3f *toset) const -{ - //update collision box - toset->MinEdge = m_prop.collisionbox.MinEdge * BS; - toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS; - - toset->MinEdge += m_base_position; - toset->MaxEdge += m_base_position; - return true; -} - -bool PlayerSAO::getSelectionBox(aabb3f *toset) const -{ - if (!m_prop.is_visible || !m_prop.pointable) { - return false; - } - - toset->MinEdge = m_prop.selectionbox.MinEdge * BS; - toset->MaxEdge = m_prop.selectionbox.MaxEdge * BS; - - return true; -} - -float PlayerSAO::getZoomFOV() const -{ - return m_prop.zoom_fov; -} diff --git a/src/content_sao.h b/src/content_sao.h index 32ab922d6..5387fd108 100644 --- a/src/content_sao.h +++ b/src/content_sao.h @@ -99,302 +99,3 @@ private: std::string m_current_texture_modifier = ""; }; -/* - PlayerSAO needs some internals exposed. -*/ - -class LagPool -{ - float m_pool = 15.0f; - float m_max = 15.0f; -public: - LagPool() = default; - - void setMax(float new_max) - { - m_max = new_max; - if(m_pool > new_max) - m_pool = new_max; - } - - void add(float dtime) - { - m_pool -= dtime; - if(m_pool < 0) - m_pool = 0; - } - - void empty() - { - m_pool = m_max; - } - - bool grab(float dtime) - { - if(dtime <= 0) - return true; - if(m_pool + dtime > m_max) - return false; - m_pool += dtime; - return true; - } -}; - -class RemotePlayer; - -class PlayerSAO : public UnitSAO -{ -public: - PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, session_t peer_id_, - bool is_singleplayer); - - ActiveObjectType getType() const - { return ACTIVEOBJECT_TYPE_PLAYER; } - ActiveObjectType getSendType() const - { return ACTIVEOBJECT_TYPE_GENERIC; } - std::string getDescription(); - - /* - Active object <-> environment interface - */ - - void addedToEnvironment(u32 dtime_s); - void removingFromEnvironment(); - bool isStaticAllowed() const { return false; } - std::string getClientInitializationData(u16 protocol_version); - void getStaticData(std::string *result) const; - void step(float dtime, bool send_recommended); - void setBasePosition(const v3f &position); - void setPos(const v3f &pos); - void moveTo(v3f pos, bool continuous); - void setPlayerYaw(const float yaw); - // Data should not be sent at player initialization - void setPlayerYawAndSend(const float yaw); - void setLookPitch(const float pitch); - // Data should not be sent at player initialization - void setLookPitchAndSend(const float pitch); - f32 getLookPitch() const { return m_pitch; } - f32 getRadLookPitch() const { return m_pitch * core::DEGTORAD; } - // Deprecated - f32 getRadLookPitchDep() const { return -1.0 * m_pitch * core::DEGTORAD; } - void setFov(const float pitch); - f32 getFov() const { return m_fov; } - void setWantedRange(const s16 range); - s16 getWantedRange() const { return m_wanted_range; } - - /* - Interaction interface - */ - - u16 punch(v3f dir, - const ToolCapabilities *toolcap, - ServerActiveObject *puncher, - float time_from_last_punch); - void rightClick(ServerActiveObject *clicker) {} - void setHP(s32 hp, const PlayerHPChangeReason &reason); - void setHPRaw(u16 hp) { m_hp = hp; } - s16 readDamage(); - u16 getBreath() const { return m_breath; } - void setBreath(const u16 breath, bool send = true); - - /* - Inventory interface - */ - Inventory *getInventory() const; - InventoryLocation getInventoryLocation() const; - void setInventoryModified() {} - std::string getWieldList() const { return "main"; } - u16 getWieldIndex() const; - ItemStack getWieldedItem(ItemStack *selected, ItemStack *hand = nullptr) const; - bool setWieldedItem(const ItemStack &item); - - /* - PlayerSAO-specific - */ - - void disconnected(); - - RemotePlayer *getPlayer() { return m_player; } - session_t getPeerID() const { return m_peer_id; } - - // Cheat prevention - - v3f getLastGoodPosition() const - { - return m_last_good_position; - } - float resetTimeFromLastPunch() - { - float r = m_time_from_last_punch; - m_time_from_last_punch = 0.0; - return r; - } - void noCheatDigStart(const 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); - } - LagPool& getDigPool() - { - return m_dig_pool; - } - void setMaxSpeedOverride(const v3f &vel); - // Returns true if cheated - bool checkMovementCheat(); - - // Other - - void updatePrivileges(const std::set<std::string> &privs, - bool is_singleplayer) - { - m_privs = privs; - m_is_singleplayer = is_singleplayer; - } - - bool getCollisionBox(aabb3f *toset) const; - bool getSelectionBox(aabb3f *toset) const; - bool collideWithObjects() const { return true; } - - void finalize(RemotePlayer *player, const std::set<std::string> &privs); - - v3f getEyePosition() const { return m_base_position + getEyeOffset(); } - v3f getEyeOffset() const; - float getZoomFOV() const; - - inline Metadata &getMeta() { return m_meta; } - -private: - std::string getPropertyPacket(); - void unlinkPlayerSessionAndSave(); - std::string generateUpdatePhysicsOverrideCommand() const; - - RemotePlayer *m_player = nullptr; - session_t m_peer_id = 0; - - // Cheat prevention - LagPool m_dig_pool; - LagPool m_move_pool; - v3f m_last_good_position; - float m_time_from_last_teleport = 0.0f; - float m_time_from_last_punch = 0.0f; - v3s16 m_nocheat_dig_pos = v3s16(32767, 32767, 32767); - float m_nocheat_dig_time = 0.0f; - float m_max_speed_override_time = 0.0f; - v3f m_max_speed_override = v3f(0.0f, 0.0f, 0.0f); - - // Timers - IntervalLimiter m_breathing_interval; - IntervalLimiter m_drowning_interval; - IntervalLimiter m_node_hurt_interval; - - bool m_position_not_sent = false; - - // Cached privileges for enforcement - std::set<std::string> m_privs; - bool m_is_singleplayer; - - u16 m_breath = PLAYER_MAX_BREATH_DEFAULT; - f32 m_pitch = 0.0f; - f32 m_fov = 0.0f; - s16 m_wanted_range = 0.0f; - - Metadata m_meta; -public: - float m_physics_override_speed = 1.0f; - float m_physics_override_jump = 1.0f; - float m_physics_override_gravity = 1.0f; - bool m_physics_override_sneak = true; - bool m_physics_override_sneak_glitch = false; - bool m_physics_override_new_move = true; - bool m_physics_override_sent = false; -}; - - -struct PlayerHPChangeReason { - enum Type : u8 { - SET_HP, - PLAYER_PUNCH, - FALL, - NODE_DAMAGE, - DROWNING, - RESPAWN - }; - - Type type = SET_HP; - bool from_mod = false; - int lua_reference = -1; - - // For PLAYER_PUNCH - ServerActiveObject *object = nullptr; - // For NODE_DAMAGE - std::string node; - - inline bool hasLuaReference() const - { - return lua_reference >= 0; - } - - bool setTypeFromString(const std::string &typestr) - { - if (typestr == "set_hp") - type = SET_HP; - else if (typestr == "punch") - type = PLAYER_PUNCH; - else if (typestr == "fall") - type = FALL; - else if (typestr == "node_damage") - type = NODE_DAMAGE; - else if (typestr == "drown") - type = DROWNING; - else if (typestr == "respawn") - type = RESPAWN; - else - return false; - - return true; - } - - std::string getTypeAsString() const - { - switch (type) { - case PlayerHPChangeReason::SET_HP: - return "set_hp"; - case PlayerHPChangeReason::PLAYER_PUNCH: - return "punch"; - case PlayerHPChangeReason::FALL: - return "fall"; - case PlayerHPChangeReason::NODE_DAMAGE: - return "node_damage"; - case PlayerHPChangeReason::DROWNING: - return "drown"; - case PlayerHPChangeReason::RESPAWN: - return "respawn"; - default: - return "?"; - } - } - - PlayerHPChangeReason(Type type): - type(type) - {} - - PlayerHPChangeReason(Type type, ServerActiveObject *object): - type(type), object(object) - {} - - PlayerHPChangeReason(Type type, std::string node): - type(type), node(node) - {} -}; diff --git a/src/database/database-files.cpp b/src/database/database-files.cpp index d09f1c074..d2b0b1543 100644 --- a/src/database/database-files.cpp +++ b/src/database/database-files.cpp @@ -20,11 +20,11 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <cassert> #include <json/json.h> #include "database-files.h" -#include "content_sao.h" #include "remoteplayer.h" #include "settings.h" #include "porting.h" #include "filesys.h" +#include "server/player_sao.h" #include "util/string.h" // !!! WARNING !!! diff --git a/src/database/database-postgresql.cpp b/src/database/database-postgresql.cpp index d7c94ff15..77385e240 100644 --- a/src/database/database-postgresql.cpp +++ b/src/database/database-postgresql.cpp @@ -36,8 +36,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "debug.h" #include "exceptions.h" #include "settings.h" -#include "content_sao.h" #include "remoteplayer.h" +#include "server/player_sao.h" Database_PostgreSQL::Database_PostgreSQL(const std::string &connect_string) : m_connect_string(connect_string) diff --git a/src/database/database-sqlite3.cpp b/src/database/database-sqlite3.cpp index 1bacdfe6c..4560743b9 100644 --- a/src/database/database-sqlite3.cpp +++ b/src/database/database-sqlite3.cpp @@ -33,8 +33,8 @@ SQLite format specification: #include "settings.h" #include "porting.h" #include "util/string.h" -#include "content_sao.h" #include "remoteplayer.h" +#include "server/player_sao.h" #include <cassert> diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index 23bcc867f..b2fdb2a22 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -20,7 +20,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "chatmessage.h" #include "server.h" #include "log.h" -#include "content_sao.h" #include "emerge.h" #include "mapblock.h" #include "modchannels.h" @@ -34,6 +33,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "network/connection.h" #include "network/networkprotocol.h" #include "network/serveropcodes.h" +#include "server/player_sao.h" #include "util/auth.h" #include "util/base64.h" #include "util/pointedthing.h" diff --git a/src/remoteplayer.cpp b/src/remoteplayer.cpp index 1a8fec68c..7a603d53e 100644 --- a/src/remoteplayer.cpp +++ b/src/remoteplayer.cpp @@ -20,13 +20,13 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "remoteplayer.h" #include <json/json.h> -#include "content_sao.h" #include "filesys.h" #include "gamedef.h" #include "porting.h" // strlcpy #include "server.h" #include "settings.h" #include "convert_json.h" +#include "server/player_sao.h" /* RemotePlayer diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index 60f12052f..c8cd7539f 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -21,7 +21,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "common/c_types.h" #include "nodedef.h" #include "object_properties.h" -#include "content_sao.h" #include "cpp_api/s_node.h" #include "lua_api/l_object.h" #include "lua_api/l_item.h" @@ -29,10 +28,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "server.h" #include "log.h" #include "tool.h" -#include "server/serveractiveobject.h" #include "porting.h" #include "mapgen/mg_schematic.h" #include "noise.h" +#include "server/player_sao.h" #include "util/pointedthing.h" #include "debug.h" // For FATAL_ERROR #include <json/json.h> diff --git a/src/script/cpp_api/s_base.cpp b/src/script/cpp_api/s_base.cpp index 16c20eeae..150baf77e 100644 --- a/src/script/cpp_api/s_base.cpp +++ b/src/script/cpp_api/s_base.cpp @@ -22,7 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "cpp_api/s_security.h" #include "lua_api/l_object.h" #include "common/c_converter.h" -#include "server/serveractiveobject.h" +#include "server/player_sao.h" #include "filesys.h" #include "content/mods.h" #include "porting.h" diff --git a/src/script/cpp_api/s_player.h b/src/script/cpp_api/s_player.h index cf24ddc73..7ca3d8f30 100644 --- a/src/script/cpp_api/s_player.h +++ b/src/script/cpp_api/s_player.h @@ -28,6 +28,7 @@ struct InventoryLocation; struct ItemStack; struct ToolCapabilities; struct PlayerHPChangeReason; +class ServerActiveObject; class ScriptApiPlayer : virtual public ScriptApiBase { diff --git a/src/script/lua_api/l_env.cpp b/src/script/lua_api/l_env.cpp index 31e582d3d..438669feb 100644 --- a/src/script/lua_api/l_env.cpp +++ b/src/script/lua_api/l_env.cpp @@ -17,6 +17,7 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include <algorithm> #include "lua_api/l_env.h" #include "lua_api/l_internal.h" #include "lua_api/l_nodemeta.h" @@ -25,7 +26,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_vmanip.h" #include "common/c_converter.h" #include "common/c_content.h" -#include <algorithm> #include "scripting_server.h" #include "environment.h" #include "mapblock.h" @@ -39,6 +39,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "pathfinder.h" #include "face_position_cache.h" #include "remoteplayer.h" +#include "server/player_sao.h" #ifndef SERVER #include "client/client.h" #endif diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index 1ea144a1c..95c96235e 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -27,12 +27,12 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "common/c_content.h" #include "log.h" #include "tool.h" -#include "server/serveractiveobject.h" #include "content_sao.h" #include "remoteplayer.h" #include "server.h" #include "hud.h" #include "scripting_server.h" +#include "server/player_sao.h" /* ObjectRef diff --git a/src/server.cpp b/src/server.cpp index 529466f6b..85d07fbc4 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -47,7 +47,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "mapgen/mg_biome.h" #include "content_mapnode.h" #include "content_nodemeta.h" -#include "content_sao.h" #include "content/mods.h" #include "modchannels.h" #include "serverlist.h" @@ -64,6 +63,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "chatmessage.h" #include "chat_interface.h" #include "remoteplayer.h" +#include "server/player_sao.h" class ClientNotFoundException : public BaseException { diff --git a/src/server/CMakeLists.txt b/src/server/CMakeLists.txt index 9fa5ed9fa..26eaed5ac 100644 --- a/src/server/CMakeLists.txt +++ b/src/server/CMakeLists.txt @@ -1,6 +1,7 @@ set(server_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/activeobjectmgr.cpp ${CMAKE_CURRENT_SOURCE_DIR}/mods.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/player_sao.cpp ${CMAKE_CURRENT_SOURCE_DIR}/serveractiveobject.cpp ${CMAKE_CURRENT_SOURCE_DIR}/unit_sao.cpp PARENT_SCOPE) diff --git a/src/server/player_sao.cpp b/src/server/player_sao.cpp new file mode 100644 index 000000000..58fcea5fe --- /dev/null +++ b/src/server/player_sao.cpp @@ -0,0 +1,695 @@ +/* +Minetest +Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com> +Copyright (C) 2013-2020 Minetest core developers & community + +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 "player_sao.h" +#include "nodedef.h" +#include "remoteplayer.h" +#include "scripting_server.h" +#include "server.h" +#include "serverenvironment.h" + +PlayerSAO::PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, session_t peer_id_, + bool is_singleplayer): + UnitSAO(env_, v3f(0,0,0)), + m_player(player_), + m_peer_id(peer_id_), + m_is_singleplayer(is_singleplayer) +{ + SANITY_CHECK(m_peer_id != PEER_ID_INEXISTENT); + + m_prop.hp_max = PLAYER_MAX_HP_DEFAULT; + m_prop.breath_max = PLAYER_MAX_BREATH_DEFAULT; + m_prop.physical = false; + m_prop.collisionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f); + m_prop.selectionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f); + m_prop.pointable = true; + // Start of default appearance, this should be overwritten by Lua + m_prop.visual = "upright_sprite"; + m_prop.visual_size = v3f(1, 2, 1); + m_prop.textures.clear(); + m_prop.textures.emplace_back("player.png"); + m_prop.textures.emplace_back("player_back.png"); + m_prop.colors.clear(); + m_prop.colors.emplace_back(255, 255, 255, 255); + m_prop.spritediv = v2s16(1,1); + m_prop.eye_height = 1.625f; + // End of default appearance + m_prop.is_visible = true; + m_prop.backface_culling = false; + m_prop.makes_footstep_sound = true; + m_prop.stepheight = PLAYER_DEFAULT_STEPHEIGHT * BS; + m_hp = m_prop.hp_max; + m_breath = m_prop.breath_max; + // Disable zoom in survival mode using a value of 0 + m_prop.zoom_fov = g_settings->getBool("creative_mode") ? 15.0f : 0.0f; + + if (!g_settings->getBool("enable_damage")) + m_armor_groups["immortal"] = 1; +} + +void PlayerSAO::finalize(RemotePlayer *player, const std::set<std::string> &privs) +{ + assert(player); + m_player = player; + m_privs = privs; +} + +v3f PlayerSAO::getEyeOffset() const +{ + return v3f(0, BS * m_prop.eye_height, 0); +} + +std::string PlayerSAO::getDescription() +{ + return std::string("player ") + m_player->getName(); +} + +// Called after id has been set and has been inserted in environment +void PlayerSAO::addedToEnvironment(u32 dtime_s) +{ + ServerActiveObject::addedToEnvironment(dtime_s); + ServerActiveObject::setBasePosition(m_base_position); + m_player->setPlayerSAO(this); + m_player->setPeerId(m_peer_id); + m_last_good_position = m_base_position; +} + +// Called before removing from environment +void PlayerSAO::removingFromEnvironment() +{ + ServerActiveObject::removingFromEnvironment(); + if (m_player->getPlayerSAO() == this) { + unlinkPlayerSessionAndSave(); + for (u32 attached_particle_spawner : m_attached_particle_spawners) { + m_env->deleteParticleSpawner(attached_particle_spawner, false); + } + } +} + +std::string PlayerSAO::getClientInitializationData(u16 protocol_version) +{ + std::ostringstream os(std::ios::binary); + + // Protocol >= 15 + writeU8(os, 1); // version + os << serializeString(m_player->getName()); // name + writeU8(os, 1); // is_player + writeS16(os, getId()); // id + writeV3F32(os, m_base_position); + writeV3F32(os, m_rotation); + writeU16(os, getHP()); + + std::ostringstream msg_os(std::ios::binary); + msg_os << serializeLongString(getPropertyPacket()); // message 1 + msg_os << serializeLongString(generateUpdateArmorGroupsCommand()); // 2 + msg_os << serializeLongString(generateUpdateAnimationCommand()); // 3 + for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator + ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) { + msg_os << serializeLongString(generateUpdateBonePositionCommand((*ii).first, + (*ii).second.X, (*ii).second.Y)); // m_bone_position.size + } + msg_os << serializeLongString(generateUpdateAttachmentCommand()); // 4 + msg_os << serializeLongString(generateUpdatePhysicsOverrideCommand()); // 5 + // (AO_CMD_UPDATE_NAMETAG_ATTRIBUTES) : Deprecated, for backwards compatibility only. + msg_os << serializeLongString(generateUpdateNametagAttributesCommand(m_prop.nametag_color)); // 6 + int message_count = 6 + m_bone_position.size(); + for (std::unordered_set<int>::const_iterator ii = m_attachment_child_ids.begin(); + ii != m_attachment_child_ids.end(); ++ii) { + if (ServerActiveObject *obj = m_env->getActiveObject(*ii)) { + message_count++; + msg_os << serializeLongString(obj->generateUpdateInfantCommand(*ii, protocol_version)); + } + } + + writeU8(os, message_count); + os.write(msg_os.str().c_str(), msg_os.str().size()); + + // return result + return os.str(); +} + +void PlayerSAO::getStaticData(std::string * result) const +{ + FATAL_ERROR("Obsolete function"); +} + +void PlayerSAO::step(float dtime, bool send_recommended) +{ + if (!isImmortal() && m_drowning_interval.step(dtime, 2.0f)) { + // Get nose/mouth position, approximate with eye position + v3s16 p = floatToInt(getEyePosition(), BS); + MapNode n = m_env->getMap().getNode(p); + const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n); + // If node generates drown + if (c.drowning > 0 && m_hp > 0) { + if (m_breath > 0) + setBreath(m_breath - 1); + + // No more breath, damage player + if (m_breath == 0) { + PlayerHPChangeReason reason(PlayerHPChangeReason::DROWNING); + setHP(m_hp - c.drowning, reason); + m_env->getGameDef()->SendPlayerHPOrDie(this, reason); + } + } + } + + if (m_breathing_interval.step(dtime, 0.5f) && !isImmortal()) { + // Get nose/mouth position, approximate with eye position + v3s16 p = floatToInt(getEyePosition(), BS); + MapNode n = m_env->getMap().getNode(p); + const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n); + // If player is alive & not drowning & not in ignore & not immortal, breathe + if (m_breath < m_prop.breath_max && c.drowning == 0 && + n.getContent() != CONTENT_IGNORE && m_hp > 0) + setBreath(m_breath + 1); + } + + if (!isImmortal() && m_node_hurt_interval.step(dtime, 1.0f)) { + u32 damage_per_second = 0; + std::string nodename; + // Lowest and highest damage points are 0.1 within collisionbox + float dam_top = m_prop.collisionbox.MaxEdge.Y - 0.1f; + + // Sequence of damage points, starting 0.1 above feet and progressing + // upwards in 1 node intervals, stopping below top damage point. + for (float dam_height = 0.1f; dam_height < dam_top; dam_height++) { + v3s16 p = floatToInt(m_base_position + + v3f(0.0f, dam_height * BS, 0.0f), BS); + MapNode n = m_env->getMap().getNode(p); + const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n); + if (c.damage_per_second > damage_per_second) { + damage_per_second = c.damage_per_second; + nodename = c.name; + } + } + + // Top damage point + v3s16 ptop = floatToInt(m_base_position + + v3f(0.0f, dam_top * BS, 0.0f), BS); + MapNode ntop = m_env->getMap().getNode(ptop); + const ContentFeatures &c = m_env->getGameDef()->ndef()->get(ntop); + if (c.damage_per_second > damage_per_second) { + damage_per_second = c.damage_per_second; + nodename = c.name; + } + + if (damage_per_second != 0 && m_hp > 0) { + s32 newhp = (s32)m_hp - (s32)damage_per_second; + PlayerHPChangeReason reason(PlayerHPChangeReason::NODE_DAMAGE, nodename); + setHP(newhp, reason); + m_env->getGameDef()->SendPlayerHPOrDie(this, reason); + } + } + + if (!m_properties_sent) { + m_properties_sent = true; + std::string str = getPropertyPacket(); + // create message and add to list + ActiveObjectMessage aom(getId(), true, str); + m_messages_out.push(aom); + m_env->getScriptIface()->player_event(this, "properties_changed"); + } + + // If attached, check that our parent is still there. If it isn't, detach. + if (m_attachment_parent_id && !isAttached()) { + m_attachment_parent_id = 0; + m_attachment_bone = ""; + m_attachment_position = v3f(0.0f, 0.0f, 0.0f); + m_attachment_rotation = v3f(0.0f, 0.0f, 0.0f); + setBasePosition(m_last_good_position); + m_env->getGameDef()->SendMovePlayer(m_peer_id); + } + + //dstream<<"PlayerSAO::step: dtime: "<<dtime<<std::endl; + + // Set lag pool maximums based on estimated lag + const float LAG_POOL_MIN = 5.0f; + float lag_pool_max = m_env->getMaxLagEstimate() * 2.0f; + if(lag_pool_max < LAG_POOL_MIN) + lag_pool_max = LAG_POOL_MIN; + m_dig_pool.setMax(lag_pool_max); + m_move_pool.setMax(lag_pool_max); + + // Increment cheat prevention timers + m_dig_pool.add(dtime); + m_move_pool.add(dtime); + m_time_from_last_teleport += dtime; + m_time_from_last_punch += dtime; + m_nocheat_dig_time += dtime; + m_max_speed_override_time = MYMAX(m_max_speed_override_time - dtime, 0.0f); + + // Each frame, parent position is copied if the object is attached, + // otherwise it's calculated normally. + // If the object gets detached this comes into effect automatically from + // the last known origin. + if (isAttached()) { + v3f pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition(); + m_last_good_position = pos; + setBasePosition(pos); + } + + if (!send_recommended) + return; + + if (m_position_not_sent) { + m_position_not_sent = false; + float update_interval = m_env->getSendRecommendedInterval(); + v3f pos; + // When attached, the position is only sent to clients where the + // parent isn't known + if (isAttached()) + pos = m_last_good_position; + else + pos = m_base_position; + + std::string str = generateUpdatePositionCommand( + pos, + v3f(0.0f, 0.0f, 0.0f), + v3f(0.0f, 0.0f, 0.0f), + m_rotation, + true, + false, + update_interval + ); + // create message and add to list + m_messages_out.emplace(getId(), false, str); + } + + if (!m_armor_groups_sent) { + m_armor_groups_sent = true; + // create message and add to list + m_messages_out.emplace(getId(), true, generateUpdateArmorGroupsCommand()); + } + + if (!m_physics_override_sent) { + m_physics_override_sent = true; + // create message and add to list + m_messages_out.emplace(getId(), true, generateUpdatePhysicsOverrideCommand()); + } + + if (!m_animation_sent) { + m_animation_sent = true; + // create message and add to list + m_messages_out.emplace(getId(), true, generateUpdateAnimationCommand()); + } + + if (!m_bone_position_sent) { + m_bone_position_sent = true; + for (std::unordered_map<std::string, core::vector2d<v3f>>::const_iterator + ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) { + std::string str = generateUpdateBonePositionCommand((*ii).first, + (*ii).second.X, (*ii).second.Y); + // create message and add to list + m_messages_out.emplace(getId(), true, str); + } + } + + if (!m_attachment_sent) { + m_attachment_sent = true; + std::string str = generateUpdateAttachmentCommand(); + // create message and add to list + ActiveObjectMessage aom(getId(), true, str); + m_messages_out.push(aom); + } +} + +std::string PlayerSAO::generateUpdatePhysicsOverrideCommand() const +{ + std::ostringstream os(std::ios::binary); + // command + writeU8(os, AO_CMD_SET_PHYSICS_OVERRIDE); + // parameters + writeF32(os, m_physics_override_speed); + writeF32(os, m_physics_override_jump); + writeF32(os, m_physics_override_gravity); + // these are sent inverted so we get true when the server sends nothing + writeU8(os, !m_physics_override_sneak); + writeU8(os, !m_physics_override_sneak_glitch); + writeU8(os, !m_physics_override_new_move); + return os.str(); +} + +void PlayerSAO::setBasePosition(const v3f &position) +{ + if (m_player && position != m_base_position) + m_player->setDirty(true); + + // This needs to be ran for attachments too + ServerActiveObject::setBasePosition(position); + + // Updating is not wanted/required for player migration + if (m_env) { + m_position_not_sent = true; + } +} + +void PlayerSAO::setPos(const v3f &pos) +{ + if(isAttached()) + return; + + // Send mapblock of target location + v3s16 blockpos = v3s16(pos.X / MAP_BLOCKSIZE, pos.Y / MAP_BLOCKSIZE, pos.Z / MAP_BLOCKSIZE); + m_env->getGameDef()->SendBlock(m_peer_id, blockpos); + + setBasePosition(pos); + // Movement caused by this command is always valid + m_last_good_position = pos; + m_move_pool.empty(); + m_time_from_last_teleport = 0.0; + m_env->getGameDef()->SendMovePlayer(m_peer_id); +} + +void PlayerSAO::moveTo(v3f pos, bool continuous) +{ + if(isAttached()) + return; + + setBasePosition(pos); + // Movement caused by this command is always valid + m_last_good_position = pos; + m_move_pool.empty(); + m_time_from_last_teleport = 0.0; + m_env->getGameDef()->SendMovePlayer(m_peer_id); +} + +void PlayerSAO::setPlayerYaw(const float yaw) +{ + v3f rotation(0, yaw, 0); + if (m_player && yaw != m_rotation.Y) + m_player->setDirty(true); + + // Set player model yaw, not look view + UnitSAO::setRotation(rotation); +} + +void PlayerSAO::setFov(const float fov) +{ + if (m_player && fov != m_fov) + m_player->setDirty(true); + + m_fov = fov; +} + +void PlayerSAO::setWantedRange(const s16 range) +{ + if (m_player && range != m_wanted_range) + m_player->setDirty(true); + + m_wanted_range = range; +} + +void PlayerSAO::setPlayerYawAndSend(const float yaw) +{ + setPlayerYaw(yaw); + m_env->getGameDef()->SendMovePlayer(m_peer_id); +} + +void PlayerSAO::setLookPitch(const float pitch) +{ + if (m_player && pitch != m_pitch) + m_player->setDirty(true); + + m_pitch = pitch; +} + +void PlayerSAO::setLookPitchAndSend(const float pitch) +{ + setLookPitch(pitch); + m_env->getGameDef()->SendMovePlayer(m_peer_id); +} + +u16 PlayerSAO::punch(v3f dir, + const ToolCapabilities *toolcap, + ServerActiveObject *puncher, + float time_from_last_punch) +{ + if (!toolcap) + return 0; + + FATAL_ERROR_IF(!puncher, "Punch action called without SAO"); + + // No effect if PvP disabled or if immortal + if (isImmortal() || !g_settings->getBool("enable_pvp")) { + if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) { + // create message and add to list + sendPunchCommand(); + return 0; + } + } + + s32 old_hp = getHP(); + HitParams hitparams = getHitParams(m_armor_groups, toolcap, + time_from_last_punch); + + PlayerSAO *playersao = m_player->getPlayerSAO(); + + bool damage_handled = m_env->getScriptIface()->on_punchplayer(playersao, + puncher, time_from_last_punch, toolcap, dir, + hitparams.hp); + + if (!damage_handled) { + setHP((s32)getHP() - (s32)hitparams.hp, + PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, puncher)); + } else { // override client prediction + if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) { + // create message and add to list + sendPunchCommand(); + } + } + + actionstream << puncher->getDescription() << " (id=" << puncher->getId() << + ", hp=" << puncher->getHP() << ") punched " << + getDescription() << " (id=" << m_id << ", hp=" << m_hp << + "), damage=" << (old_hp - (s32)getHP()) << + (damage_handled ? " (handled by Lua)" : "") << std::endl; + + return hitparams.wear; +} + +void PlayerSAO::setHP(s32 hp, const PlayerHPChangeReason &reason) +{ + s32 oldhp = m_hp; + + hp = rangelim(hp, 0, m_prop.hp_max); + + if (oldhp != hp) { + s32 hp_change = m_env->getScriptIface()->on_player_hpchange(this, hp - oldhp, reason); + if (hp_change == 0) + return; + + hp = rangelim(oldhp + hp_change, 0, m_prop.hp_max); + } + + if (hp < oldhp && isImmortal()) + return; + + m_hp = hp; + + // Update properties on death + if ((hp == 0) != (oldhp == 0)) + m_properties_sent = false; +} + +void PlayerSAO::setBreath(const u16 breath, bool send) +{ + if (m_player && breath != m_breath) + m_player->setDirty(true); + + m_breath = rangelim(breath, 0, m_prop.breath_max); + + if (send) + m_env->getGameDef()->SendPlayerBreath(this); +} + +Inventory *PlayerSAO::getInventory() const +{ + return m_player ? &m_player->inventory : nullptr; +} + +InventoryLocation PlayerSAO::getInventoryLocation() const +{ + InventoryLocation loc; + loc.setPlayer(m_player->getName()); + return loc; +} + +u16 PlayerSAO::getWieldIndex() const +{ + return m_player->getWieldIndex(); +} + +ItemStack PlayerSAO::getWieldedItem(ItemStack *selected, ItemStack *hand) const +{ + return m_player->getWieldedItem(selected, hand); +} + +bool PlayerSAO::setWieldedItem(const ItemStack &item) +{ + InventoryList *mlist = m_player->inventory.getList(getWieldList()); + if (mlist) { + mlist->changeItem(m_player->getWieldIndex(), item); + return true; + } + return false; +} + +void PlayerSAO::disconnected() +{ + m_peer_id = PEER_ID_INEXISTENT; + m_pending_removal = true; +} + +void PlayerSAO::unlinkPlayerSessionAndSave() +{ + assert(m_player->getPlayerSAO() == this); + m_player->setPeerId(PEER_ID_INEXISTENT); + m_env->savePlayer(m_player); + m_player->setPlayerSAO(NULL); + m_env->removePlayer(m_player); +} + +std::string PlayerSAO::getPropertyPacket() +{ + m_prop.is_visible = (true); + return generateSetPropertiesCommand(m_prop); +} + +void PlayerSAO::setMaxSpeedOverride(const v3f &vel) +{ + if (m_max_speed_override_time == 0.0f) + m_max_speed_override = vel; + else + m_max_speed_override += vel; + if (m_player) { + float accel = MYMIN(m_player->movement_acceleration_default, + m_player->movement_acceleration_air); + m_max_speed_override_time = m_max_speed_override.getLength() / accel / BS; + } +} + +bool PlayerSAO::checkMovementCheat() +{ + if (isAttached() || m_is_singleplayer || + g_settings->getBool("disable_anticheat")) { + m_last_good_position = m_base_position; + return false; + } + + bool cheated = false; + /* + Check player movements + + 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. Altough a node-based alternative might be possible + too, and much more lightweight. + */ + + float override_max_H, override_max_V; + if (m_max_speed_override_time > 0.0f) { + override_max_H = MYMAX(fabs(m_max_speed_override.X), fabs(m_max_speed_override.Z)); + override_max_V = fabs(m_max_speed_override.Y); + } else { + override_max_H = override_max_V = 0.0f; + } + + float player_max_walk = 0; // horizontal movement + float player_max_jump = 0; // vertical upwards movement + + if (m_privs.count("fast") != 0) + player_max_walk = m_player->movement_speed_fast; // Fast speed + else + player_max_walk = m_player->movement_speed_walk; // Normal speed + player_max_walk *= m_physics_override_speed; + player_max_walk = MYMAX(player_max_walk, override_max_H); + + player_max_jump = m_player->movement_speed_jump * m_physics_override_jump; + // FIXME: Bouncy nodes cause practically unbound increase in Y speed, + // until this can be verified correctly, tolerate higher jumping speeds + player_max_jump *= 2.0; + player_max_jump = MYMAX(player_max_jump, override_max_V); + + // Don't divide by zero! + if (player_max_walk < 0.0001f) + player_max_walk = 0.0001f; + if (player_max_jump < 0.0001f) + player_max_jump = 0.0001f; + + v3f diff = (m_base_position - m_last_good_position); + float d_vert = diff.Y; + diff.Y = 0; + float d_horiz = diff.getLength(); + float required_time = d_horiz / player_max_walk; + + // FIXME: Checking downwards movement is not easily possible currently, + // the server could calculate speed differences to examine the gravity + if (d_vert > 0) { + // In certain cases (water, ladders) walking speed is applied vertically + float s = MYMAX(player_max_jump, player_max_walk); + required_time = MYMAX(required_time, d_vert / s); + } + + if (m_move_pool.grab(required_time)) { + m_last_good_position = m_base_position; + } else { + const float LAG_POOL_MIN = 5.0; + float lag_pool_max = m_env->getMaxLagEstimate() * 2.0; + lag_pool_max = MYMAX(lag_pool_max, LAG_POOL_MIN); + if (m_time_from_last_teleport > lag_pool_max) { + actionstream << "Player " << m_player->getName() + << " moved too fast; resetting position" + << std::endl; + cheated = true; + } + setBasePosition(m_last_good_position); + } + return cheated; +} + +bool PlayerSAO::getCollisionBox(aabb3f *toset) const +{ + //update collision box + toset->MinEdge = m_prop.collisionbox.MinEdge * BS; + toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS; + + toset->MinEdge += m_base_position; + toset->MaxEdge += m_base_position; + return true; +} + +bool PlayerSAO::getSelectionBox(aabb3f *toset) const +{ + if (!m_prop.is_visible || !m_prop.pointable) { + return false; + } + + toset->MinEdge = m_prop.selectionbox.MinEdge * BS; + toset->MaxEdge = m_prop.selectionbox.MaxEdge * BS; + + return true; +} + +float PlayerSAO::getZoomFOV() const +{ + return m_prop.zoom_fov; +} diff --git a/src/server/player_sao.h b/src/server/player_sao.h new file mode 100644 index 000000000..ce1cb1677 --- /dev/null +++ b/src/server/player_sao.h @@ -0,0 +1,325 @@ + +/* +Minetest +Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com> +Copyright (C) 2013-2020 Minetest core developers & community + +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 "constants.h" +#include "network/networkprotocol.h" +#include "unit_sao.h" +#include "util/numeric.h" + +/* + PlayerSAO needs some internals exposed. +*/ + +class LagPool +{ + float m_pool = 15.0f; + float m_max = 15.0f; +public: + LagPool() = default; + + void setMax(float new_max) + { + m_max = new_max; + if(m_pool > new_max) + m_pool = new_max; + } + + void add(float dtime) + { + m_pool -= dtime; + if(m_pool < 0) + m_pool = 0; + } + + void empty() + { + m_pool = m_max; + } + + bool grab(float dtime) + { + if(dtime <= 0) + return true; + if(m_pool + dtime > m_max) + return false; + m_pool += dtime; + return true; + } +}; + +class RemotePlayer; + +class PlayerSAO : public UnitSAO +{ +public: + PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, session_t peer_id_, + bool is_singleplayer); + + ActiveObjectType getType() const + { return ACTIVEOBJECT_TYPE_PLAYER; } + ActiveObjectType getSendType() const + { return ACTIVEOBJECT_TYPE_GENERIC; } + std::string getDescription(); + + /* + Active object <-> environment interface + */ + + void addedToEnvironment(u32 dtime_s); + void removingFromEnvironment(); + bool isStaticAllowed() const { return false; } + std::string getClientInitializationData(u16 protocol_version); + void getStaticData(std::string *result) const; + void step(float dtime, bool send_recommended); + void setBasePosition(const v3f &position); + void setPos(const v3f &pos); + void moveTo(v3f pos, bool continuous); + void setPlayerYaw(const float yaw); + // Data should not be sent at player initialization + void setPlayerYawAndSend(const float yaw); + void setLookPitch(const float pitch); + // Data should not be sent at player initialization + void setLookPitchAndSend(const float pitch); + f32 getLookPitch() const { return m_pitch; } + f32 getRadLookPitch() const { return m_pitch * core::DEGTORAD; } + // Deprecated + f32 getRadLookPitchDep() const { return -1.0 * m_pitch * core::DEGTORAD; } + void setFov(const float pitch); + f32 getFov() const { return m_fov; } + void setWantedRange(const s16 range); + s16 getWantedRange() const { return m_wanted_range; } + + /* + Interaction interface + */ + + u16 punch(v3f dir, + const ToolCapabilities *toolcap, + ServerActiveObject *puncher, + float time_from_last_punch); + void rightClick(ServerActiveObject *clicker) {} + void setHP(s32 hp, const PlayerHPChangeReason &reason); + void setHPRaw(u16 hp) { m_hp = hp; } + s16 readDamage(); + u16 getBreath() const { return m_breath; } + void setBreath(const u16 breath, bool send = true); + + /* + Inventory interface + */ + Inventory *getInventory() const; + InventoryLocation getInventoryLocation() const; + void setInventoryModified() {} + std::string getWieldList() const { return "main"; } + u16 getWieldIndex() const; + ItemStack getWieldedItem(ItemStack *selected, ItemStack *hand = nullptr) const; + bool setWieldedItem(const ItemStack &item); + + /* + PlayerSAO-specific + */ + + void disconnected(); + + RemotePlayer *getPlayer() { return m_player; } + session_t getPeerID() const { return m_peer_id; } + + // Cheat prevention + + v3f getLastGoodPosition() const + { + return m_last_good_position; + } + float resetTimeFromLastPunch() + { + float r = m_time_from_last_punch; + m_time_from_last_punch = 0.0; + return r; + } + void noCheatDigStart(const 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); + } + LagPool& getDigPool() + { + return m_dig_pool; + } + void setMaxSpeedOverride(const v3f &vel); + // Returns true if cheated + bool checkMovementCheat(); + + // Other + + void updatePrivileges(const std::set<std::string> &privs, + bool is_singleplayer) + { + m_privs = privs; + m_is_singleplayer = is_singleplayer; + } + + bool getCollisionBox(aabb3f *toset) const; + bool getSelectionBox(aabb3f *toset) const; + bool collideWithObjects() const { return true; } + + void finalize(RemotePlayer *player, const std::set<std::string> &privs); + + v3f getEyePosition() const { return m_base_position + getEyeOffset(); } + v3f getEyeOffset() const; + float getZoomFOV() const; + + inline Metadata &getMeta() { return m_meta; } + +private: + std::string getPropertyPacket(); + void unlinkPlayerSessionAndSave(); + std::string generateUpdatePhysicsOverrideCommand() const; + + RemotePlayer *m_player = nullptr; + session_t m_peer_id = 0; + + // Cheat prevention + LagPool m_dig_pool; + LagPool m_move_pool; + v3f m_last_good_position; + float m_time_from_last_teleport = 0.0f; + float m_time_from_last_punch = 0.0f; + v3s16 m_nocheat_dig_pos = v3s16(32767, 32767, 32767); + float m_nocheat_dig_time = 0.0f; + float m_max_speed_override_time = 0.0f; + v3f m_max_speed_override = v3f(0.0f, 0.0f, 0.0f); + + // Timers + IntervalLimiter m_breathing_interval; + IntervalLimiter m_drowning_interval; + IntervalLimiter m_node_hurt_interval; + + bool m_position_not_sent = false; + + // Cached privileges for enforcement + std::set<std::string> m_privs; + bool m_is_singleplayer; + + u16 m_breath = PLAYER_MAX_BREATH_DEFAULT; + f32 m_pitch = 0.0f; + f32 m_fov = 0.0f; + s16 m_wanted_range = 0.0f; + + Metadata m_meta; +public: + float m_physics_override_speed = 1.0f; + float m_physics_override_jump = 1.0f; + float m_physics_override_gravity = 1.0f; + bool m_physics_override_sneak = true; + bool m_physics_override_sneak_glitch = false; + bool m_physics_override_new_move = true; + bool m_physics_override_sent = false; +}; + + +struct PlayerHPChangeReason { + enum Type : u8 { + SET_HP, + PLAYER_PUNCH, + FALL, + NODE_DAMAGE, + DROWNING, + RESPAWN + }; + + Type type = SET_HP; + bool from_mod = false; + int lua_reference = -1; + + // For PLAYER_PUNCH + ServerActiveObject *object = nullptr; + // For NODE_DAMAGE + std::string node; + + inline bool hasLuaReference() const + { + return lua_reference >= 0; + } + + bool setTypeFromString(const std::string &typestr) + { + if (typestr == "set_hp") + type = SET_HP; + else if (typestr == "punch") + type = PLAYER_PUNCH; + else if (typestr == "fall") + type = FALL; + else if (typestr == "node_damage") + type = NODE_DAMAGE; + else if (typestr == "drown") + type = DROWNING; + else if (typestr == "respawn") + type = RESPAWN; + else + return false; + + return true; + } + + std::string getTypeAsString() const + { + switch (type) { + case PlayerHPChangeReason::SET_HP: + return "set_hp"; + case PlayerHPChangeReason::PLAYER_PUNCH: + return "punch"; + case PlayerHPChangeReason::FALL: + return "fall"; + case PlayerHPChangeReason::NODE_DAMAGE: + return "node_damage"; + case PlayerHPChangeReason::DROWNING: + return "drown"; + case PlayerHPChangeReason::RESPAWN: + return "respawn"; + default: + return "?"; + } + } + + PlayerHPChangeReason(Type type): + type(type) + {} + + PlayerHPChangeReason(Type type, ServerActiveObject *object): + type(type), object(object) + {} + + PlayerHPChangeReason(Type type, std::string node): + type(type), node(node) + {} +}; diff --git a/src/server/unit_sao.cpp b/src/server/unit_sao.cpp index b30b7a76b..74b0508b8 100644 --- a/src/server/unit_sao.cpp +++ b/src/server/unit_sao.cpp @@ -22,10 +22,6 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "scripting_server.h" #include "serverenvironment.h" -/* - UnitSAO - */ - UnitSAO::UnitSAO(ServerEnvironment *env, v3f pos) : ServerActiveObject(env, pos) { // Initialize something to armor groups diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp index 0ccbd772b..c2ab5c07d 100644 --- a/src/serverenvironment.cpp +++ b/src/serverenvironment.cpp @@ -17,8 +17,8 @@ with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include <algorithm> #include "serverenvironment.h" -#include "content_sao.h" #include "settings.h" #include "log.h" #include "mapblock.h" @@ -44,7 +44,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #if USE_POSTGRESQL #include "database/database-postgresql.h" #endif -#include <algorithm> +#include "server/player_sao.h" #define LBM_NAME_ALLOWED_CHARS "abcdefghijklmnopqrstuvwxyz0123456789_:" |