diff options
Diffstat (limited to 'src/server')
-rw-r--r-- | src/server/CMakeLists.txt | 5 | ||||
-rw-r--r-- | src/server/activeobjectmgr.cpp | 10 | ||||
-rw-r--r-- | src/server/activeobjectmgr.h | 7 | ||||
-rw-r--r-- | src/server/luaentity_sao.cpp | 541 | ||||
-rw-r--r-- | src/server/luaentity_sao.h | 93 | ||||
-rw-r--r-- | src/server/mods.cpp | 10 | ||||
-rw-r--r-- | src/server/mods.h | 3 | ||||
-rw-r--r-- | src/server/player_sao.cpp | 665 | ||||
-rw-r--r-- | src/server/player_sao.h | 300 | ||||
-rw-r--r-- | src/server/serveractiveobject.cpp | 75 | ||||
-rw-r--r-- | src/server/serveractiveobject.h | 255 | ||||
-rw-r--r-- | src/server/serverinventorymgr.cpp | 192 | ||||
-rw-r--r-- | src/server/serverinventorymgr.h | 60 | ||||
-rw-r--r-- | src/server/unit_sao.cpp | 345 | ||||
-rw-r--r-- | src/server/unit_sao.h | 136 |
15 files changed, 2684 insertions, 13 deletions
diff --git a/src/server/CMakeLists.txt b/src/server/CMakeLists.txt index e964c69ff..0a5a8f3a7 100644 --- a/src/server/CMakeLists.txt +++ b/src/server/CMakeLists.txt @@ -1,4 +1,9 @@ set(server_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/activeobjectmgr.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/luaentity_sao.cpp ${CMAKE_CURRENT_SOURCE_DIR}/mods.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/player_sao.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/serveractiveobject.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/serverinventorymgr.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/unit_sao.cpp PARENT_SCOPE) diff --git a/src/server/activeobjectmgr.cpp b/src/server/activeobjectmgr.cpp index 984ae7794..1b8e31409 100644 --- a/src/server/activeobjectmgr.cpp +++ b/src/server/activeobjectmgr.cpp @@ -111,17 +111,19 @@ void ActiveObjectMgr::removeObject(u16 id) } // clang-format on -void ActiveObjectMgr::getObjectsInsideRadius( - const v3f &pos, float radius, std::vector<u16> &result) +void ActiveObjectMgr::getObjectsInsideRadius(const v3f &pos, float radius, + std::vector<ServerActiveObject *> &result, + std::function<bool(ServerActiveObject *obj)> include_obj_cb) { float r2 = radius * radius; for (auto &activeObject : m_active_objects) { ServerActiveObject *obj = activeObject.second; - u16 id = activeObject.first; const v3f &objectpos = obj->getBasePosition(); if (objectpos.getDistanceFromSQ(pos) > r2) continue; - result.push_back(id); + + if (!include_obj_cb || include_obj_cb(obj)) + result.push_back(obj); } } diff --git a/src/server/activeobjectmgr.h b/src/server/activeobjectmgr.h index a502ac6ed..bc2085499 100644 --- a/src/server/activeobjectmgr.h +++ b/src/server/activeobjectmgr.h @@ -22,7 +22,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include <functional> #include <vector> #include "../activeobjectmgr.h" -#include "serverobject.h" +#include "serveractiveobject.h" namespace server { @@ -35,8 +35,9 @@ public: bool registerObject(ServerActiveObject *obj) override; void removeObject(u16 id) override; - void getObjectsInsideRadius( - const v3f &pos, float radius, std::vector<u16> &result); + void getObjectsInsideRadius(const v3f &pos, float radius, + std::vector<ServerActiveObject *> &result, + std::function<bool(ServerActiveObject *obj)> include_obj_cb); void getAddedActiveObjectsAroundPos(const v3f &player_pos, f32 radius, f32 player_radius, std::set<u16> ¤t_objects, diff --git a/src/server/luaentity_sao.cpp b/src/server/luaentity_sao.cpp new file mode 100644 index 000000000..d504c42ca --- /dev/null +++ b/src/server/luaentity_sao.cpp @@ -0,0 +1,541 @@ +/* +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 "luaentity_sao.h" +#include "collision.h" +#include "constants.h" +#include "player_sao.h" +#include "scripting_server.h" +#include "server.h" +#include "serverenvironment.h" + +LuaEntitySAO::LuaEntitySAO(ServerEnvironment *env, v3f pos, const std::string &data) + : UnitSAO(env, pos) +{ + std::string name; + std::string state; + u16 hp = 1; + v3f velocity; + v3f rotation; + + while (!data.empty()) { // breakable, run for one iteration + std::istringstream is(data, std::ios::binary); + // 'version' does not allow to incrementally extend the parameter list thus + // we need another variable to build on top of 'version=1'. Ugly hack but works™ + u8 version2 = 0; + u8 version = readU8(is); + + name = deSerializeString(is); + state = deSerializeLongString(is); + + if (version < 1) + break; + + hp = readU16(is); + velocity = readV3F1000(is); + // yaw must be yaw to be backwards-compatible + rotation.Y = readF1000(is); + + if (is.good()) // EOF for old formats + version2 = readU8(is); + + if (version2 < 1) // PROTOCOL_VERSION < 37 + break; + + // version2 >= 1 + rotation.X = readF1000(is); + rotation.Z = readF1000(is); + + // if (version2 < 2) + // break; + // <read new values> + break; + } + // create object + infostream << "LuaEntitySAO::create(name=\"" << name << "\" state=\"" + << state << "\")" << std::endl; + + m_init_name = name; + m_init_state = state; + m_hp = hp; + m_velocity = velocity; + m_rotation = rotation; +} + +LuaEntitySAO::~LuaEntitySAO() +{ + if(m_registered){ + m_env->getScriptIface()->luaentity_Remove(m_id); + } + + for (u32 attached_particle_spawner : m_attached_particle_spawners) { + m_env->deleteParticleSpawner(attached_particle_spawner, false); + } +} + +void LuaEntitySAO::addedToEnvironment(u32 dtime_s) +{ + ServerActiveObject::addedToEnvironment(dtime_s); + + // Create entity from name + m_registered = m_env->getScriptIface()-> + luaentity_Add(m_id, m_init_name.c_str()); + + if(m_registered){ + // Get properties + m_env->getScriptIface()-> + luaentity_GetProperties(m_id, this, &m_prop); + // Initialize HP from properties + m_hp = m_prop.hp_max; + // Activate entity, supplying serialized state + m_env->getScriptIface()-> + luaentity_Activate(m_id, m_init_state, dtime_s); + } else { + m_prop.infotext = m_init_name; + } +} + +void LuaEntitySAO::step(float dtime, bool send_recommended) +{ + if(!m_properties_sent) + { + m_properties_sent = true; + std::string str = getPropertyPacket(); + // create message and add to list + m_messages_out.emplace(getId(), true, str); + } + + // If attached, check that our parent is still there. If it isn't, detach. + if (m_attachment_parent_id && !isAttached()) { + // This is handled when objects are removed from the map + warningstream << "LuaEntitySAO::step() id=" << m_id << + " is attached to nonexistent parent. This is a bug." << std::endl; + clearParentAttachment(); + sendPosition(false, true); + } + + m_last_sent_position_timer += dtime; + + collisionMoveResult moveresult, *moveresult_p = nullptr; + + // 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_base_position = pos; + m_velocity = v3f(0,0,0); + m_acceleration = v3f(0,0,0); + } + else + { + if(m_prop.physical){ + aabb3f box = m_prop.collisionbox; + box.MinEdge *= BS; + box.MaxEdge *= BS; + f32 pos_max_d = BS*0.25; // Distance per iteration + v3f p_pos = m_base_position; + v3f p_velocity = m_velocity; + v3f p_acceleration = m_acceleration; + moveresult = collisionMoveSimple(m_env, m_env->getGameDef(), + pos_max_d, box, m_prop.stepheight, dtime, + &p_pos, &p_velocity, p_acceleration, + this, m_prop.collideWithObjects); + moveresult_p = &moveresult; + + // Apply results + m_base_position = p_pos; + m_velocity = p_velocity; + m_acceleration = p_acceleration; + } else { + m_base_position += dtime * m_velocity + 0.5 * dtime + * dtime * m_acceleration; + m_velocity += dtime * m_acceleration; + } + + if (m_prop.automatic_face_movement_dir && + (fabs(m_velocity.Z) > 0.001 || fabs(m_velocity.X) > 0.001)) { + float target_yaw = atan2(m_velocity.Z, m_velocity.X) * 180 / M_PI + + m_prop.automatic_face_movement_dir_offset; + float max_rotation_per_sec = + m_prop.automatic_face_movement_max_rotation_per_sec; + + if (max_rotation_per_sec > 0) { + m_rotation.Y = wrapDegrees_0_360(m_rotation.Y); + wrappedApproachShortest(m_rotation.Y, target_yaw, + dtime * max_rotation_per_sec, 360.f); + } else { + // Negative values of max_rotation_per_sec mean disabled. + m_rotation.Y = target_yaw; + } + } + } + + if(m_registered) { + m_env->getScriptIface()->luaentity_Step(m_id, dtime, moveresult_p); + } + + if (!send_recommended) + return; + + if(!isAttached()) + { + // TODO: force send when acceleration changes enough? + float minchange = 0.2*BS; + if(m_last_sent_position_timer > 1.0){ + minchange = 0.01*BS; + } else if(m_last_sent_position_timer > 0.2){ + minchange = 0.05*BS; + } + float move_d = m_base_position.getDistanceFrom(m_last_sent_position); + move_d += m_last_sent_move_precision; + float vel_d = m_velocity.getDistanceFrom(m_last_sent_velocity); + if (move_d > minchange || vel_d > minchange || + std::fabs(m_rotation.X - m_last_sent_rotation.X) > 1.0f || + std::fabs(m_rotation.Y - m_last_sent_rotation.Y) > 1.0f || + std::fabs(m_rotation.Z - m_last_sent_rotation.Z) > 1.0f) { + + sendPosition(true, false); + } + } + + sendOutdatedData(); +} + +std::string LuaEntitySAO::getClientInitializationData(u16 protocol_version) +{ + std::ostringstream os(std::ios::binary); + + // PROTOCOL_VERSION >= 37 + writeU8(os, 1); // version + os << serializeString(""); // name + writeU8(os, 0); // is_player + writeU16(os, getId()); //id + writeV3F32(os, m_base_position); + writeV3F32(os, m_rotation); + writeU16(os, m_hp); + + std::ostringstream msg_os(std::ios::binary); + msg_os << serializeLongString(getPropertyPacket()); // message 1 + msg_os << serializeLongString(generateUpdateArmorGroupsCommand()); // 2 + msg_os << serializeLongString(generateUpdateAnimationCommand()); // 3 + for (const auto &bone_pos : m_bone_position) { + msg_os << serializeLongString(generateUpdateBonePositionCommand( + bone_pos.first, bone_pos.second.X, bone_pos.second.Y)); // m_bone_position.size + } + msg_os << serializeLongString(generateUpdateAttachmentCommand()); // 4 + + int message_count = 4 + m_bone_position.size(); + + for (const auto &id : getAttachmentChildIds()) { + if (ServerActiveObject *obj = m_env->getActiveObject(id)) { + message_count++; + msg_os << serializeLongString(obj->generateUpdateInfantCommand( + id, protocol_version)); + } + } + + msg_os << serializeLongString(generateSetTextureModCommand()); + message_count++; + + writeU8(os, message_count); + std::string serialized = msg_os.str(); + os.write(serialized.c_str(), serialized.size()); + + // return result + return os.str(); +} + +void LuaEntitySAO::getStaticData(std::string *result) const +{ + verbosestream<<FUNCTION_NAME<<std::endl; + std::ostringstream os(std::ios::binary); + // version must be 1 to keep backwards-compatibility. See version2 + writeU8(os, 1); + // name + os<<serializeString(m_init_name); + // state + if(m_registered){ + std::string state = m_env->getScriptIface()-> + luaentity_GetStaticdata(m_id); + os<<serializeLongString(state); + } else { + os<<serializeLongString(m_init_state); + } + writeU16(os, m_hp); + writeV3F1000(os, m_velocity); + // yaw + writeF1000(os, m_rotation.Y); + + // version2. Increase this variable for new values + writeU8(os, 1); // PROTOCOL_VERSION >= 37 + + writeF1000(os, m_rotation.X); + writeF1000(os, m_rotation.Z); + + // <write new values> + + *result = os.str(); +} + +u16 LuaEntitySAO::punch(v3f dir, + const ToolCapabilities *toolcap, + ServerActiveObject *puncher, + float time_from_last_punch) +{ + if (!m_registered) { + // Delete unknown LuaEntities when punched + m_pending_removal = true; + return 0; + } + + FATAL_ERROR_IF(!puncher, "Punch action called without SAO"); + + s32 old_hp = getHP(); + ItemStack selected_item, hand_item; + ItemStack tool_item = puncher->getWieldedItem(&selected_item, &hand_item); + + PunchDamageResult result = getPunchDamage( + m_armor_groups, + toolcap, + &tool_item, + time_from_last_punch); + + bool damage_handled = m_env->getScriptIface()->luaentity_Punch(m_id, puncher, + time_from_last_punch, toolcap, dir, result.did_punch ? result.damage : 0); + + if (!damage_handled) { + if (result.did_punch) { + setHP((s32)getHP() - result.damage, + PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, puncher)); + + // create message and add to list + sendPunchCommand(); + } + } + + if (getHP() == 0 && !isGone()) { + clearParentAttachment(); + clearChildAttachments(); + m_env->getScriptIface()->luaentity_on_death(m_id, puncher); + m_pending_removal = true; + } + + 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; + + // TODO: give Lua control over wear + return result.wear; +} + +void LuaEntitySAO::rightClick(ServerActiveObject *clicker) +{ + if (!m_registered) + return; + + m_env->getScriptIface()->luaentity_Rightclick(m_id, clicker); +} + +void LuaEntitySAO::setPos(const v3f &pos) +{ + if(isAttached()) + return; + m_base_position = pos; + sendPosition(false, true); +} + +void LuaEntitySAO::moveTo(v3f pos, bool continuous) +{ + if(isAttached()) + return; + m_base_position = pos; + if(!continuous) + sendPosition(true, true); +} + +float LuaEntitySAO::getMinimumSavedMovement() +{ + return 0.1 * BS; +} + +std::string LuaEntitySAO::getDescription() +{ + std::ostringstream oss; + oss << "LuaEntitySAO \"" << m_init_name << "\" "; + auto pos = floatToInt(m_base_position, BS); + oss << "at " << PP(pos); + return oss.str(); +} + +void LuaEntitySAO::setHP(s32 hp, const PlayerHPChangeReason &reason) +{ + m_hp = rangelim(hp, 0, U16_MAX); +} + +u16 LuaEntitySAO::getHP() const +{ + return m_hp; +} + +void LuaEntitySAO::setVelocity(v3f velocity) +{ + m_velocity = velocity; +} + +v3f LuaEntitySAO::getVelocity() +{ + return m_velocity; +} + +void LuaEntitySAO::setAcceleration(v3f acceleration) +{ + m_acceleration = acceleration; +} + +v3f LuaEntitySAO::getAcceleration() +{ + return m_acceleration; +} + +void LuaEntitySAO::setTextureMod(const std::string &mod) +{ + m_current_texture_modifier = mod; + // create message and add to list + m_messages_out.emplace(getId(), true, generateSetTextureModCommand()); +} + +std::string LuaEntitySAO::getTextureMod() const +{ + return m_current_texture_modifier; +} + + +std::string LuaEntitySAO::generateSetTextureModCommand() const +{ + std::ostringstream os(std::ios::binary); + // command + writeU8(os, AO_CMD_SET_TEXTURE_MOD); + // parameters + os << serializeString(m_current_texture_modifier); + return os.str(); +} + +std::string LuaEntitySAO::generateSetSpriteCommand(v2s16 p, u16 num_frames, + f32 framelength, bool select_horiz_by_yawpitch) +{ + std::ostringstream os(std::ios::binary); + // command + writeU8(os, AO_CMD_SET_SPRITE); + // parameters + writeV2S16(os, p); + writeU16(os, num_frames); + writeF32(os, framelength); + writeU8(os, select_horiz_by_yawpitch); + return os.str(); +} + +void LuaEntitySAO::setSprite(v2s16 p, int num_frames, float framelength, + bool select_horiz_by_yawpitch) +{ + std::string str = generateSetSpriteCommand( + p, + num_frames, + framelength, + select_horiz_by_yawpitch + ); + // create message and add to list + m_messages_out.emplace(getId(), true, str); +} + +std::string LuaEntitySAO::getName() +{ + return m_init_name; +} + +std::string LuaEntitySAO::getPropertyPacket() +{ + return generateSetPropertiesCommand(m_prop); +} + +void LuaEntitySAO::sendPosition(bool do_interpolate, bool is_movement_end) +{ + // If the object is attached client-side, don't waste bandwidth sending its position to clients + if(isAttached()) + return; + + m_last_sent_move_precision = m_base_position.getDistanceFrom( + m_last_sent_position); + m_last_sent_position_timer = 0; + m_last_sent_position = m_base_position; + m_last_sent_velocity = m_velocity; + //m_last_sent_acceleration = m_acceleration; + m_last_sent_rotation = m_rotation; + + float update_interval = m_env->getSendRecommendedInterval(); + + std::string str = generateUpdatePositionCommand( + m_base_position, + m_velocity, + m_acceleration, + m_rotation, + do_interpolate, + is_movement_end, + update_interval + ); + // create message and add to list + m_messages_out.emplace(getId(), false, str); +} + +bool LuaEntitySAO::getCollisionBox(aabb3f *toset) const +{ + if (m_prop.physical) + { + //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; + } + + return false; +} + +bool LuaEntitySAO::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; +} + +bool LuaEntitySAO::collideWithObjects() const +{ + return m_prop.collideWithObjects; +} diff --git a/src/server/luaentity_sao.h b/src/server/luaentity_sao.h new file mode 100644 index 000000000..2520c8f5d --- /dev/null +++ b/src/server/luaentity_sao.h @@ -0,0 +1,93 @@ +/* +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. +*/ + +#pragma once + +#include "unit_sao.h" + +class LuaEntitySAO : public UnitSAO +{ +public: + LuaEntitySAO() = delete; + // Used by the environment to load SAO + LuaEntitySAO(ServerEnvironment *env, v3f pos, const std::string &data); + // Used by the Lua API + LuaEntitySAO(ServerEnvironment *env, v3f pos, const std::string &name, + const std::string &state) : + UnitSAO(env, pos), + m_init_name(name), m_init_state(state) + { + } + ~LuaEntitySAO(); + ActiveObjectType getType() const { return ACTIVEOBJECT_TYPE_LUAENTITY; } + ActiveObjectType getSendType() const { return ACTIVEOBJECT_TYPE_GENERIC; } + virtual void addedToEnvironment(u32 dtime_s); + void step(float dtime, bool send_recommended); + std::string getClientInitializationData(u16 protocol_version); + bool isStaticAllowed() const { return m_prop.static_save; } + void getStaticData(std::string *result) const; + u16 punch(v3f dir, const ToolCapabilities *toolcap = nullptr, + ServerActiveObject *puncher = nullptr, + float time_from_last_punch = 1000000.0f); + void rightClick(ServerActiveObject *clicker); + void setPos(const v3f &pos); + void moveTo(v3f pos, bool continuous); + float getMinimumSavedMovement(); + std::string getDescription(); + void setHP(s32 hp, const PlayerHPChangeReason &reason); + u16 getHP() const; + + /* LuaEntitySAO-specific */ + void setVelocity(v3f velocity); + void addVelocity(v3f velocity) { m_velocity += velocity; } + v3f getVelocity(); + void setAcceleration(v3f acceleration); + v3f getAcceleration(); + + void setTextureMod(const std::string &mod); + std::string getTextureMod() const; + void setSprite(v2s16 p, int num_frames, float framelength, + bool select_horiz_by_yawpitch); + std::string getName(); + bool getCollisionBox(aabb3f *toset) const; + bool getSelectionBox(aabb3f *toset) const; + bool collideWithObjects() const; + +private: + std::string getPropertyPacket(); + void sendPosition(bool do_interpolate, bool is_movement_end); + std::string generateSetTextureModCommand() const; + static std::string generateSetSpriteCommand(v2s16 p, u16 num_frames, + f32 framelength, bool select_horiz_by_yawpitch); + + std::string m_init_name; + std::string m_init_state; + bool m_registered = false; + + v3f m_velocity; + v3f m_acceleration; + + v3f m_last_sent_position; + v3f m_last_sent_velocity; + v3f m_last_sent_rotation; + float m_last_sent_position_timer = 0.0f; + float m_last_sent_move_precision = 0.0f; + std::string m_current_texture_modifier = ""; +}; diff --git a/src/server/mods.cpp b/src/server/mods.cpp index c5616dcd6..6ac530739 100644 --- a/src/server/mods.cpp +++ b/src/server/mods.cpp @@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "log.h" #include "scripting_server.h" #include "content/subgames.h" +#include "porting.h" +#include "util/metricsbackend.h" /** * Manage server mods @@ -66,14 +68,10 @@ void ServerModManager::loadMods(ServerScripting *script) "Only characters [a-z0-9_] are allowed."); } std::string script_path = mod.path + DIR_DELIM + "init.lua"; - infostream << " [" << padStringRight(mod.name, 12) << "] [\"" - << script_path << "\"]" << std::endl; - auto t = std::chrono::steady_clock::now(); + auto t = porting::getTimeMs(); script->loadMod(script_path, mod.name); infostream << "Mod \"" << mod.name << "\" loaded after " - << std::chrono::duration_cast<std::chrono::milliseconds>( - std::chrono::steady_clock::now() - t).count() * 0.001f - << " seconds" << std::endl; + << (porting::getTimeMs() - t) << " ms" << std::endl; } // Run a callback when mods are loaded diff --git a/src/server/mods.h b/src/server/mods.h index 2bc1aa22f..54774bd86 100644 --- a/src/server/mods.h +++ b/src/server/mods.h @@ -20,7 +20,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once #include "content/mods.h" +#include <memory> +class MetricsBackend; +class MetricCounter; class ServerScripting; /** diff --git a/src/server/player_sao.cpp b/src/server/player_sao.cpp new file mode 100644 index 000000000..67efed210 --- /dev/null +++ b/src/server/player_sao.cpp @@ -0,0 +1,665 @@ +/* +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 (const auto &bone_pos : m_bone_position) { + msg_os << serializeLongString(generateUpdateBonePositionCommand( + bone_pos.first, bone_pos.second.X, bone_pos.second.Y)); // m_bone_position.size + } + msg_os << serializeLongString(generateUpdateAttachmentCommand()); // 4 + msg_os << serializeLongString(generateUpdatePhysicsOverrideCommand()); // 5 + + int message_count = 5 + m_bone_position.size(); + + for (const auto &id : getAttachmentChildIds()) { + if (ServerActiveObject *obj = m_env->getActiveObject(id)) { + message_count++; + msg_os << serializeLongString(obj->generateUpdateInfantCommand( + id, protocol_version)); + } + } + + writeU8(os, message_count); + std::string serialized = msg_os.str(); + os.write(serialized.c_str(), serialized.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 + m_messages_out.emplace(getId(), true, str); + 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()) { + // This is handled when objects are removed from the map + warningstream << "PlayerSAO::step() id=" << m_id << + " is attached to nonexistent parent. This is a bug." << std::endl; + clearParentAttachment(); + 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_physics_override_sent) { + m_physics_override_sent = true; + // create message and add to list + m_messages_out.emplace(getId(), true, generateUpdatePhysicsOverrideCommand()); + } + + sendOutdatedData(); +} + +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 << "Server: " << m_player->getName() + << " moved too fast: V=" << d_vert << ", H=" << d_horiz + << "; 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..8571bd4f9 --- /dev/null +++ b/src/server/player_sao.h @@ -0,0 +1,300 @@ +/* +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. +*/ + +#pragma once + +#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/serveractiveobject.cpp b/src/server/serveractiveobject.cpp new file mode 100644 index 000000000..3341dc008 --- /dev/null +++ b/src/server/serveractiveobject.cpp @@ -0,0 +1,75 @@ +/* +Minetest +Copyright (C) 2010-2013 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 "serveractiveobject.h" +#include <fstream> +#include "inventory.h" +#include "constants.h" // BS +#include "log.h" + +ServerActiveObject::ServerActiveObject(ServerEnvironment *env, v3f pos): + ActiveObject(0), + m_env(env), + m_base_position(pos) +{ +} + +float ServerActiveObject::getMinimumSavedMovement() +{ + return 2.0*BS; +} + +ItemStack ServerActiveObject::getWieldedItem(ItemStack *selected, ItemStack *hand) const +{ + *selected = ItemStack(); + if (hand) + *hand = ItemStack(); + + return ItemStack(); +} + +bool ServerActiveObject::setWieldedItem(const ItemStack &item) +{ + return false; +} + +std::string ServerActiveObject::generateUpdateInfantCommand(u16 infant_id, u16 protocol_version) +{ + std::ostringstream os(std::ios::binary); + // command + writeU8(os, AO_CMD_SPAWN_INFANT); + // parameters + writeU16(os, infant_id); + writeU8(os, getSendType()); + if (protocol_version < 38) { + // Clients since 4aa9a66 so no longer need this data + // Version 38 is the first bump after that commit. + // See also: ClientEnvironment::addActiveObject + os << serializeLongString(getClientInitializationData(protocol_version)); + } + return os.str(); +} + +void ServerActiveObject::dumpAOMessagesToQueue(std::queue<ActiveObjectMessage> &queue) +{ + while (!m_messages_out.empty()) { + queue.push(std::move(m_messages_out.front())); + m_messages_out.pop(); + } +} diff --git a/src/server/serveractiveobject.h b/src/server/serveractiveobject.h new file mode 100644 index 000000000..927009aef --- /dev/null +++ b/src/server/serveractiveobject.h @@ -0,0 +1,255 @@ +/* +Minetest +Copyright (C) 2010-2013 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. +*/ + +#pragma once + +#include <unordered_set> +#include "irrlichttypes_bloated.h" +#include "activeobject.h" +#include "inventorymanager.h" +#include "itemgroup.h" +#include "util/container.h" + +/* + +Some planning +------------- + +* Server environment adds an active object, which gets the id 1 +* The active object list is scanned for each client once in a while, + and it finds out what objects have been added that are not known + by the client yet. This scan is initiated by the Server class and + the result ends up directly to the server. +* A network packet is created with the info and sent to the client. +* Environment converts objects to static data and static data to + objects, based on how close players are to them. + +*/ + +class ServerEnvironment; +struct ItemStack; +struct ToolCapabilities; +struct ObjectProperties; +struct PlayerHPChangeReason; + +class ServerActiveObject : public ActiveObject +{ +public: + /* + NOTE: m_env can be NULL, but step() isn't called if it is. + Prototypes are used that way. + */ + ServerActiveObject(ServerEnvironment *env, v3f pos); + virtual ~ServerActiveObject() = default; + + virtual ActiveObjectType getSendType() const + { return getType(); } + + // Called after id has been set and has been inserted in environment + virtual void addedToEnvironment(u32 dtime_s){}; + // Called before removing from environment + virtual void removingFromEnvironment(){}; + // Returns true if object's deletion is the job of the + // environment + virtual bool environmentDeletes() const + { return true; } + + // Create a certain type of ServerActiveObject + static ServerActiveObject* create(ActiveObjectType type, + ServerEnvironment *env, u16 id, v3f pos, + const std::string &data); + + /* + Some simple getters/setters + */ + v3f getBasePosition() const { return m_base_position; } + void setBasePosition(v3f pos){ m_base_position = pos; } + ServerEnvironment* getEnv(){ return m_env; } + + /* + Some more dynamic interface + */ + + virtual void setPos(const v3f &pos) + { setBasePosition(pos); } + // continuous: if true, object does not stop immediately at pos + virtual void moveTo(v3f pos, bool continuous) + { setBasePosition(pos); } + // If object has moved less than this and data has not changed, + // saving to disk may be omitted + virtual float getMinimumSavedMovement(); + + virtual std::string getDescription(){return "SAO";} + + /* + Step object in time. + Messages added to messages are sent to client over network. + + send_recommended: + True at around 5-10 times a second, same for all objects. + This is used to let objects send most of the data at the + same time so that the data can be combined in a single + packet. + */ + virtual void step(float dtime, bool send_recommended){} + + /* + The return value of this is passed to the client-side object + when it is created + */ + virtual std::string getClientInitializationData(u16 protocol_version) {return "";} + + /* + The return value of this is passed to the server-side object + when it is created (converted from static to active - actually + the data is the static form) + */ + virtual void getStaticData(std::string *result) const + { + assert(isStaticAllowed()); + *result = ""; + } + /* + Return false in here to never save and instead remove object + on unload. getStaticData() will not be called in that case. + */ + virtual bool isStaticAllowed() const + {return true;} + + // Returns tool wear + virtual u16 punch(v3f dir, + const ToolCapabilities *toolcap = nullptr, + ServerActiveObject *puncher = nullptr, + float time_from_last_punch = 1000000.0f) + { return 0; } + virtual void rightClick(ServerActiveObject *clicker) + {} + virtual void setHP(s32 hp, const PlayerHPChangeReason &reason) + {} + virtual u16 getHP() const + { return 0; } + + virtual void setArmorGroups(const ItemGroupList &armor_groups) + {} + virtual const ItemGroupList &getArmorGroups() const + { static ItemGroupList rv; return rv; } + virtual void setPhysicsOverride(float physics_override_speed, float physics_override_jump, float physics_override_gravity) + {} + virtual void setAnimation(v2f frames, float frame_speed, float frame_blend, bool frame_loop) + {} + virtual void getAnimation(v2f *frames, float *frame_speed, float *frame_blend, bool *frame_loop) + {} + virtual void setAnimationSpeed(float frame_speed) + {} + virtual void setBonePosition(const std::string &bone, v3f position, v3f rotation) + {} + virtual void getBonePosition(const std::string &bone, v3f *position, v3f *lotation) + {} + virtual const std::unordered_set<int> &getAttachmentChildIds() const + { static std::unordered_set<int> rv; return rv; } + virtual ServerActiveObject *getParent() const { return nullptr; } + virtual ObjectProperties* accessObjectProperties() + { return NULL; } + virtual void notifyObjectPropertiesModified() + {} + + // Inventory and wielded item + virtual Inventory *getInventory() const + { return NULL; } + virtual InventoryLocation getInventoryLocation() const + { return InventoryLocation(); } + virtual void setInventoryModified() + {} + virtual std::string getWieldList() const + { return ""; } + virtual u16 getWieldIndex() const + { return 0; } + virtual ItemStack getWieldedItem(ItemStack *selected, + ItemStack *hand = nullptr) const; + virtual bool setWieldedItem(const ItemStack &item); + inline void attachParticleSpawner(u32 id) + { + m_attached_particle_spawners.insert(id); + } + inline void detachParticleSpawner(u32 id) + { + m_attached_particle_spawners.erase(id); + } + + std::string generateUpdateInfantCommand(u16 infant_id, u16 protocol_version); + std::string generateUpdateNametagAttributesCommand(const video::SColor &color) const; + + void dumpAOMessagesToQueue(std::queue<ActiveObjectMessage> &queue); + + /* + Number of players which know about this object. Object won't be + deleted until this is 0 to keep the id preserved for the right + object. + */ + u16 m_known_by_count = 0; + + /* + - Whether this object is to be removed when nobody knows about + it anymore. + - Removal is delayed to preserve the id for the time during which + it could be confused to some other object by some client. + - This is usually set to true by the step() method when the object wants + to be deleted but can be set by anything else too. + */ + bool m_pending_removal = false; + + /* + Same purpose as m_pending_removal but for deactivation. + deactvation = save static data in block, remove active object + + If this is set alongside with m_pending_removal, removal takes + priority. + */ + bool m_pending_deactivation = false; + + /* + A getter that unifies the above to answer the question: + "Can the environment still interact with this object?" + */ + inline bool isGone() const + { return m_pending_removal || m_pending_deactivation; } + + /* + Whether the object's static data has been stored to a block + */ + bool m_static_exists = false; + /* + The block from which the object was loaded from, and in which + a copy of the static data resides. + */ + v3s16 m_static_block = v3s16(1337,1337,1337); + +protected: + virtual void onAttach(int parent_id) {} + virtual void onDetach(int parent_id) {} + + ServerEnvironment *m_env; + v3f m_base_position; + std::unordered_set<u32> m_attached_particle_spawners; + + /* + Queue of messages to be sent to the client + */ + std::queue<ActiveObjectMessage> m_messages_out; +}; diff --git a/src/server/serverinventorymgr.cpp b/src/server/serverinventorymgr.cpp new file mode 100644 index 000000000..555e01ec6 --- /dev/null +++ b/src/server/serverinventorymgr.cpp @@ -0,0 +1,192 @@ +/* +Minetest +Copyright (C) 2010-2020 Minetest core development team + +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 "serverinventorymgr.h" +#include "map.h" +#include "nodemetadata.h" +#include "player_sao.h" +#include "remoteplayer.h" +#include "server.h" +#include "serverenvironment.h" + +ServerInventoryManager::ServerInventoryManager() : InventoryManager() +{ +} + +ServerInventoryManager::~ServerInventoryManager() +{ + // Delete detached inventories + for (auto &detached_inventory : m_detached_inventories) { + delete detached_inventory.second.inventory; + } +} + +Inventory *ServerInventoryManager::getInventory(const InventoryLocation &loc) +{ + switch (loc.type) { + case InventoryLocation::UNDEFINED: + case InventoryLocation::CURRENT_PLAYER: + break; + case InventoryLocation::PLAYER: { + RemotePlayer *player = m_env->getPlayer(loc.name.c_str()); + if (!player) + return NULL; + PlayerSAO *playersao = player->getPlayerSAO(); + if (!playersao) + return NULL; + return playersao->getInventory(); + } break; + case InventoryLocation::NODEMETA: { + NodeMetadata *meta = m_env->getMap().getNodeMetadata(loc.p); + if (!meta) + return NULL; + return meta->getInventory(); + } break; + case InventoryLocation::DETACHED: { + auto it = m_detached_inventories.find(loc.name); + if (it == m_detached_inventories.end()) + return nullptr; + return it->second.inventory; + } break; + default: + sanity_check(false); // abort + break; + } + return NULL; +} + +void ServerInventoryManager::setInventoryModified(const InventoryLocation &loc) +{ + switch (loc.type) { + case InventoryLocation::UNDEFINED: + break; + case InventoryLocation::PLAYER: { + + RemotePlayer *player = m_env->getPlayer(loc.name.c_str()); + + if (!player) + return; + + player->setModified(true); + player->inventory.setModified(true); + // Updates are sent in ServerEnvironment::step() + } break; + case InventoryLocation::NODEMETA: { + MapEditEvent event; + event.type = MEET_BLOCK_NODE_METADATA_CHANGED; + event.p = loc.p; + m_env->getMap().dispatchEvent(event); + } break; + case InventoryLocation::DETACHED: { + // Updates are sent in ServerEnvironment::step() + } break; + default: + sanity_check(false); // abort + break; + } +} + +Inventory *ServerInventoryManager::createDetachedInventory( + const std::string &name, IItemDefManager *idef, const std::string &player) +{ + if (m_detached_inventories.count(name) > 0) { + infostream << "Server clearing detached inventory \"" << name << "\"" + << std::endl; + delete m_detached_inventories[name].inventory; + } else { + infostream << "Server creating detached inventory \"" << name << "\"" + << std::endl; + } + + Inventory *inv = new Inventory(idef); + sanity_check(inv); + m_detached_inventories[name].inventory = inv; + if (!player.empty()) { + m_detached_inventories[name].owner = player; + + if (!m_env) + return inv; // Mods are not loaded yet, ignore + + RemotePlayer *p = m_env->getPlayer(name.c_str()); + + // if player is connected, send him the inventory + if (p && p->getPeerId() != PEER_ID_INEXISTENT) { + m_env->getGameDef()->sendDetachedInventory( + inv, name, p->getPeerId()); + } + } else { + if (!m_env) + return inv; // Mods are not loaded yet, don't send + + // Inventory is for everybody, broadcast + m_env->getGameDef()->sendDetachedInventory(inv, name, PEER_ID_INEXISTENT); + } + + return inv; +} + +bool ServerInventoryManager::removeDetachedInventory(const std::string &name) +{ + const auto &inv_it = m_detached_inventories.find(name); + if (inv_it == m_detached_inventories.end()) + return false; + + delete inv_it->second.inventory; + const std::string &owner = inv_it->second.owner; + + if (!owner.empty()) { + RemotePlayer *player = m_env->getPlayer(owner.c_str()); + + if (player && player->getPeerId() != PEER_ID_INEXISTENT) + m_env->getGameDef()->sendDetachedInventory( + nullptr, name, player->getPeerId()); + + } else { + // Notify all players about the change + m_env->getGameDef()->sendDetachedInventory( + nullptr, name, PEER_ID_INEXISTENT); + } + + m_detached_inventories.erase(inv_it); + + return true; +} + +void ServerInventoryManager::sendDetachedInventories(const std::string &peer_name, + bool incremental, + std::function<void(const std::string &, Inventory *)> apply_cb) +{ + for (const auto &detached_inventory : m_detached_inventories) { + const DetachedInventory &dinv = detached_inventory.second; + if (incremental) { + if (!dinv.inventory || !dinv.inventory->checkModified()) + continue; + } + + // if we are pushing inventories to a specific player + // we should filter to send only the right inventories + if (!peer_name.empty()) { + const std::string &attached_player = dinv.owner; + if (!attached_player.empty() && peer_name != attached_player) + continue; + } + + apply_cb(detached_inventory.first, detached_inventory.second.inventory); + } +} diff --git a/src/server/serverinventorymgr.h b/src/server/serverinventorymgr.h new file mode 100644 index 000000000..ccf6d3b2e --- /dev/null +++ b/src/server/serverinventorymgr.h @@ -0,0 +1,60 @@ +/* +Minetest +Copyright (C) 2010-2020 Minetest core development team + +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. +*/ + +#pragma once + +#include "inventorymanager.h" +#include <functional> + +class ServerEnvironment; + +class ServerInventoryManager : public InventoryManager +{ +public: + ServerInventoryManager(); + virtual ~ServerInventoryManager(); + + void setEnv(ServerEnvironment *env) + { + assert(!m_env); + m_env = env; + } + + Inventory *getInventory(const InventoryLocation &loc); + void setInventoryModified(const InventoryLocation &loc); + + // Creates or resets inventory + Inventory *createDetachedInventory(const std::string &name, IItemDefManager *idef, + const std::string &player = ""); + bool removeDetachedInventory(const std::string &name); + + void sendDetachedInventories(const std::string &peer_name, bool incremental, + std::function<void(const std::string &, Inventory *)> apply_cb); + +private: + struct DetachedInventory + { + Inventory *inventory; + std::string owner; + }; + + ServerEnvironment *m_env = nullptr; + + std::unordered_map<std::string, DetachedInventory> m_detached_inventories; +}; diff --git a/src/server/unit_sao.cpp b/src/server/unit_sao.cpp new file mode 100644 index 000000000..ef0e87f2c --- /dev/null +++ b/src/server/unit_sao.cpp @@ -0,0 +1,345 @@ +/* +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 "unit_sao.h" +#include "scripting_server.h" +#include "serverenvironment.h" + +UnitSAO::UnitSAO(ServerEnvironment *env, v3f pos) : ServerActiveObject(env, pos) +{ + // Initialize something to armor groups + m_armor_groups["fleshy"] = 100; +} + +ServerActiveObject *UnitSAO::getParent() const +{ + if (!m_attachment_parent_id) + return nullptr; + // Check if the parent still exists + ServerActiveObject *obj = m_env->getActiveObject(m_attachment_parent_id); + + return obj; +} + +void UnitSAO::setArmorGroups(const ItemGroupList &armor_groups) +{ + m_armor_groups = armor_groups; + m_armor_groups_sent = false; +} + +const ItemGroupList &UnitSAO::getArmorGroups() const +{ + return m_armor_groups; +} + +void UnitSAO::setAnimation( + v2f frame_range, float frame_speed, float frame_blend, bool frame_loop) +{ + // store these so they can be updated to clients + m_animation_range = frame_range; + m_animation_speed = frame_speed; + m_animation_blend = frame_blend; + m_animation_loop = frame_loop; + m_animation_sent = false; +} + +void UnitSAO::getAnimation(v2f *frame_range, float *frame_speed, float *frame_blend, + bool *frame_loop) +{ + *frame_range = m_animation_range; + *frame_speed = m_animation_speed; + *frame_blend = m_animation_blend; + *frame_loop = m_animation_loop; +} + +void UnitSAO::setAnimationSpeed(float frame_speed) +{ + m_animation_speed = frame_speed; + m_animation_speed_sent = false; +} + +void UnitSAO::setBonePosition(const std::string &bone, v3f position, v3f rotation) +{ + // store these so they can be updated to clients + m_bone_position[bone] = core::vector2d<v3f>(position, rotation); + m_bone_position_sent = false; +} + +void UnitSAO::getBonePosition(const std::string &bone, v3f *position, v3f *rotation) +{ + *position = m_bone_position[bone].X; + *rotation = m_bone_position[bone].Y; +} + +// clang-format off +void UnitSAO::sendOutdatedData() +{ + if (!m_armor_groups_sent) { + m_armor_groups_sent = true; + m_messages_out.emplace(getId(), true, generateUpdateArmorGroupsCommand()); + } + + if (!m_animation_sent) { + m_animation_sent = true; + m_animation_speed_sent = true; + m_messages_out.emplace(getId(), true, generateUpdateAnimationCommand()); + } else if (!m_animation_speed_sent) { + // Animation speed is also sent when 'm_animation_sent == false' + m_animation_speed_sent = true; + m_messages_out.emplace(getId(), true, generateUpdateAnimationSpeedCommand()); + } + + if (!m_bone_position_sent) { + m_bone_position_sent = true; + for (const auto &bone_pos : m_bone_position) { + m_messages_out.emplace(getId(), true, generateUpdateBonePositionCommand( + bone_pos.first, bone_pos.second.X, bone_pos.second.Y)); + } + } + + if (!m_attachment_sent) { + m_attachment_sent = true; + m_messages_out.emplace(getId(), true, generateUpdateAttachmentCommand()); + } +} +// clang-format on + +void UnitSAO::setAttachment( + int parent_id, const std::string &bone, v3f position, v3f rotation) +{ + // Attachments need to be handled on both the server and client. + // If we just attach on the server, we can only copy the position of the parent. + // Attachments are still sent to clients at an interval so players might see them + // lagging, plus we can't read and attach to skeletal bones. If we just attach on + // the client, the server still sees the child at its original location. This + // breaks some things so we also give the server the most accurate representation + // even if players only see the client changes. + + int old_parent = m_attachment_parent_id; + m_attachment_parent_id = parent_id; + m_attachment_bone = bone; + m_attachment_position = position; + m_attachment_rotation = rotation; + m_attachment_sent = false; + + if (parent_id != old_parent) { + onDetach(old_parent); + onAttach(parent_id); + } +} + +void UnitSAO::getAttachment( + int *parent_id, std::string *bone, v3f *position, v3f *rotation) const +{ + *parent_id = m_attachment_parent_id; + *bone = m_attachment_bone; + *position = m_attachment_position; + *rotation = m_attachment_rotation; +} + +void UnitSAO::clearChildAttachments() +{ + for (int child_id : m_attachment_child_ids) { + // Child can be NULL if it was deleted earlier + if (ServerActiveObject *child = m_env->getActiveObject(child_id)) + child->setAttachment(0, "", v3f(0, 0, 0), v3f(0, 0, 0)); + } + m_attachment_child_ids.clear(); +} + +void UnitSAO::clearParentAttachment() +{ + ServerActiveObject *parent = nullptr; + if (m_attachment_parent_id) { + parent = m_env->getActiveObject(m_attachment_parent_id); + setAttachment(0, "", m_attachment_position, m_attachment_rotation); + } else { + setAttachment(0, "", v3f(0, 0, 0), v3f(0, 0, 0)); + } + // Do it + if (parent) + parent->removeAttachmentChild(m_id); +} + +void UnitSAO::addAttachmentChild(int child_id) +{ + m_attachment_child_ids.insert(child_id); +} + +void UnitSAO::removeAttachmentChild(int child_id) +{ + m_attachment_child_ids.erase(child_id); +} + +const std::unordered_set<int> &UnitSAO::getAttachmentChildIds() const +{ + return m_attachment_child_ids; +} + +void UnitSAO::onAttach(int parent_id) +{ + if (!parent_id) + return; + + ServerActiveObject *parent = m_env->getActiveObject(parent_id); + + if (!parent || parent->isGone()) + return; // Do not try to notify soon gone parent + + if (parent->getType() == ACTIVEOBJECT_TYPE_LUAENTITY) { + // Call parent's on_attach field + m_env->getScriptIface()->luaentity_on_attach_child(parent_id, this); + } +} + +void UnitSAO::onDetach(int parent_id) +{ + if (!parent_id) + return; + + ServerActiveObject *parent = m_env->getActiveObject(parent_id); + if (getType() == ACTIVEOBJECT_TYPE_LUAENTITY) + m_env->getScriptIface()->luaentity_on_detach(m_id, parent); + + if (!parent || parent->isGone()) + return; // Do not try to notify soon gone parent + + if (parent->getType() == ACTIVEOBJECT_TYPE_LUAENTITY) + m_env->getScriptIface()->luaentity_on_detach_child(parent_id, this); +} + +ObjectProperties *UnitSAO::accessObjectProperties() +{ + return &m_prop; +} + +void UnitSAO::notifyObjectPropertiesModified() +{ + m_properties_sent = false; +} + +std::string UnitSAO::generateUpdateAttachmentCommand() const +{ + std::ostringstream os(std::ios::binary); + // command + writeU8(os, AO_CMD_ATTACH_TO); + // parameters + writeS16(os, m_attachment_parent_id); + os << serializeString(m_attachment_bone); + writeV3F32(os, m_attachment_position); + writeV3F32(os, m_attachment_rotation); + return os.str(); +} + +std::string UnitSAO::generateUpdateBonePositionCommand( + const std::string &bone, const v3f &position, const v3f &rotation) +{ + std::ostringstream os(std::ios::binary); + // command + writeU8(os, AO_CMD_SET_BONE_POSITION); + // parameters + os << serializeString(bone); + writeV3F32(os, position); + writeV3F32(os, rotation); + return os.str(); +} + +std::string UnitSAO::generateUpdateAnimationSpeedCommand() const +{ + std::ostringstream os(std::ios::binary); + // command + writeU8(os, AO_CMD_SET_ANIMATION_SPEED); + // parameters + writeF32(os, m_animation_speed); + return os.str(); +} + +std::string UnitSAO::generateUpdateAnimationCommand() const +{ + std::ostringstream os(std::ios::binary); + // command + writeU8(os, AO_CMD_SET_ANIMATION); + // parameters + writeV2F32(os, m_animation_range); + writeF32(os, m_animation_speed); + writeF32(os, m_animation_blend); + // these are sent inverted so we get true when the server sends nothing + writeU8(os, !m_animation_loop); + return os.str(); +} + +std::string UnitSAO::generateUpdateArmorGroupsCommand() const +{ + std::ostringstream os(std::ios::binary); + writeU8(os, AO_CMD_UPDATE_ARMOR_GROUPS); + writeU16(os, m_armor_groups.size()); + for (const auto &armor_group : m_armor_groups) { + os << serializeString(armor_group.first); + writeS16(os, armor_group.second); + } + return os.str(); +} + +std::string UnitSAO::generateUpdatePositionCommand(const v3f &position, + const v3f &velocity, const v3f &acceleration, const v3f &rotation, + bool do_interpolate, bool is_movement_end, f32 update_interval) +{ + std::ostringstream os(std::ios::binary); + // command + writeU8(os, AO_CMD_UPDATE_POSITION); + // pos + writeV3F32(os, position); + // velocity + writeV3F32(os, velocity); + // acceleration + writeV3F32(os, acceleration); + // rotation + writeV3F32(os, rotation); + // do_interpolate + writeU8(os, do_interpolate); + // is_end_position (for interpolation) + writeU8(os, is_movement_end); + // update_interval (for interpolation) + writeF32(os, update_interval); + return os.str(); +} + +std::string UnitSAO::generateSetPropertiesCommand(const ObjectProperties &prop) const +{ + std::ostringstream os(std::ios::binary); + writeU8(os, AO_CMD_SET_PROPERTIES); + prop.serialize(os); + return os.str(); +} + +std::string UnitSAO::generatePunchCommand(u16 result_hp) const +{ + std::ostringstream os(std::ios::binary); + // command + writeU8(os, AO_CMD_PUNCHED); + // result_hp + writeU16(os, result_hp); + return os.str(); +} + +void UnitSAO::sendPunchCommand() +{ + m_messages_out.emplace(getId(), true, generatePunchCommand(getHP())); +} diff --git a/src/server/unit_sao.h b/src/server/unit_sao.h new file mode 100644 index 000000000..3cb7f0ad5 --- /dev/null +++ b/src/server/unit_sao.h @@ -0,0 +1,136 @@ +/* +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. +*/ + +#pragma once + +#include "object_properties.h" +#include "serveractiveobject.h" + +class UnitSAO : public ServerActiveObject +{ +public: + UnitSAO(ServerEnvironment *env, v3f pos); + virtual ~UnitSAO() = default; + + u16 getHP() const { return m_hp; } + // Use a function, if isDead can be defined by other conditions + bool isDead() const { return m_hp == 0; } + + // Rotation + void setRotation(v3f rotation) { m_rotation = rotation; } + const v3f &getRotation() const { return m_rotation; } + v3f getRadRotation() { return m_rotation * core::DEGTORAD; } + + // Deprecated + f32 getRadYawDep() const { return (m_rotation.Y + 90.) * core::DEGTORAD; } + + // Armor groups + inline bool isImmortal() const + { + return itemgroup_get(getArmorGroups(), "immortal"); + } + void setArmorGroups(const ItemGroupList &armor_groups); + const ItemGroupList &getArmorGroups() const; + + // Animation + void setAnimation(v2f frame_range, float frame_speed, float frame_blend, + bool frame_loop); + void getAnimation(v2f *frame_range, float *frame_speed, float *frame_blend, + bool *frame_loop); + void setAnimationSpeed(float frame_speed); + + // Bone position + void setBonePosition(const std::string &bone, v3f position, v3f rotation); + void getBonePosition(const std::string &bone, v3f *position, v3f *rotation); + + // Attachments + ServerActiveObject *getParent() const; + inline bool isAttached() const { return getParent(); } + void setAttachment(int parent_id, const std::string &bone, v3f position, + v3f rotation); + void getAttachment(int *parent_id, std::string *bone, v3f *position, + v3f *rotation) const; + void clearChildAttachments(); + void clearParentAttachment(); + void addAttachmentChild(int child_id); + void removeAttachmentChild(int child_id); + const std::unordered_set<int> &getAttachmentChildIds() const; + + // Object properties + ObjectProperties *accessObjectProperties(); + void notifyObjectPropertiesModified(); + void sendOutdatedData(); + + // Update packets + std::string generateUpdateAttachmentCommand() const; + std::string generateUpdateAnimationSpeedCommand() const; + std::string generateUpdateAnimationCommand() const; + std::string generateUpdateArmorGroupsCommand() const; + static std::string generateUpdatePositionCommand(const v3f &position, + const v3f &velocity, const v3f &acceleration, const v3f &rotation, + bool do_interpolate, bool is_movement_end, f32 update_interval); + std::string generateSetPropertiesCommand(const ObjectProperties &prop) const; + static std::string generateUpdateBonePositionCommand(const std::string &bone, + const v3f &position, const v3f &rotation); + void sendPunchCommand(); + +protected: + u16 m_hp = 1; + + v3f m_rotation; + + ItemGroupList m_armor_groups; + + // Object properties + bool m_properties_sent = true; + ObjectProperties m_prop; + + // Stores position and rotation for each bone name + std::unordered_map<std::string, core::vector2d<v3f>> m_bone_position; + + int m_attachment_parent_id = 0; + +private: + void onAttach(int parent_id); + void onDetach(int parent_id); + + std::string generatePunchCommand(u16 result_hp) const; + + // Armor groups + bool m_armor_groups_sent = false; + + // Animation + v2f m_animation_range; + float m_animation_speed = 0.0f; + float m_animation_blend = 0.0f; + bool m_animation_loop = true; + bool m_animation_sent = false; + bool m_animation_speed_sent = false; + + // Bone positions + bool m_bone_position_sent = false; + + // Attachments + std::unordered_set<int> m_attachment_child_ids; + std::string m_attachment_bone = ""; + v3f m_attachment_position; + v3f m_attachment_rotation; + bool m_attachment_sent = false; +}; |