/* Minetest Copyright (C) 2010-2013 celeron55, Perttu Ahola 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 "content_sao.h" #include "util/serialize.h" #include "collision.h" #include "environment.h" #include "tool.h" // For ToolCapabilities #include "gamedef.h" #include "nodedef.h" #include "remoteplayer.h" #include "server.h" #include "scripting_server.h" #include "genericobject.h" #include "settings.h" #include #include std::map ServerActiveObject::m_types; /* TestSAO */ class TestSAO : public ServerActiveObject { public: TestSAO(ServerEnvironment *env, v3f pos): ServerActiveObject(env, pos), m_timer1(0), m_age(0) { ServerActiveObject::registerType(getType(), create); } ActiveObjectType getType() const { return ACTIVEOBJECT_TYPE_TEST; } static ServerActiveObject* create(ServerEnvironment *env, v3f pos, const std::string &data) { return new TestSAO(env, pos); } void step(float dtime, bool send_recommended) { m_age += dtime; if(m_age > 10) { m_pending_removal = true; return; } m_base_position.Y += dtime * BS * 2; if(m_base_position.Y > 8*BS) m_base_position.Y = 2*BS; if (!send_recommended) return; m_timer1 -= dtime; if(m_timer1 < 0.0) { m_timer1 += 0.125; std::string data; data += itos(0); // 0 = position data += " "; data += itos(m_base_position.X); data += " "; data += itos(m_base_position.Y); data += " "; data += itos(m_base_position.Z); ActiveObjectMessage aom(getId(), false, data); m_messages_out.push(aom); } } bool getCollisionBox(aabb3f *toset) const { return false; } virtual bool getSelectionBox(aabb3f *toset) const { return false; } bool collideWithObjects() const { return false; } private: float m_timer1; float m_age; }; // Prototype (registers item for deserialization) TestSAO proto_TestSAO(NULL, v3f(0,0,0)); /* UnitSAO */ 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() { 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(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; } 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) { *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 &UnitSAO::getAttachmentChildIds() { 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; } /* LuaEntitySAO */ // Prototype (registers item for deserialization) LuaEntitySAO proto_LuaEntitySAO(NULL, v3f(0,0,0), "_prototype", ""); LuaEntitySAO::LuaEntitySAO(ServerEnvironment *env, v3f pos, const std::string &name, const std::string &state): UnitSAO(env, pos), m_init_name(name), m_init_state(state) { // Only register type if no environment supplied if(env == NULL){ ServerActiveObject::registerType(getType(), create); return; } } 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, &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; } } ServerActiveObject* LuaEntitySAO::create(ServerEnvironment *env, v3f pos, const std::string &data) { std::string name; std::string state; s16 hp = 1; v3f velocity; float yaw = 0; if (!data.empty()) { std::istringstream is(data, std::ios::binary); // read version u8 version = readU8(is); // check if version is supported if(version == 0){ name = deSerializeString(is); state = deSerializeLongString(is); } else if(version == 1){ name = deSerializeString(is); state = deSerializeLongString(is); hp = readS16(is); velocity = readV3F1000(is); yaw = readF1000(is); } } // create object infostream<<"LuaEntitySAO::create(name=\""<m_hp = hp; sao->m_velocity = velocity; sao->m_yaw = yaw; return sao; } 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 ActiveObjectMessage aom(getId(), true, str); m_messages_out.push(aom); } // 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,0,0); m_attachment_rotation = v3f(0,0,0); sendPosition(false, true); } m_last_sent_position_timer += dtime; // 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; collisionMoveResult moveresult; 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); // 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_delta = dtime * m_prop.automatic_face_movement_max_rotation_per_sec; m_yaw = wrapDegrees_0_360(m_yaw); wrappedApproachShortest(m_yaw, target_yaw, max_rotation_delta, 360.f); } } if(m_registered){ m_env->getScriptIface()->luaentity_Step(m_id, dtime); } 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_yaw - m_last_sent_yaw) > 1.0) { sendPosition(true, false); } } if (!m_armor_groups_sent) { m_armor_groups_sent = true; std::string str = gob_cmd_update_armor_groups( m_armor_groups); // create message and add to list ActiveObjectMessage aom(getId(), true, str); m_messages_out.push(aom); } if (!m_animation_sent) { m_animation_sent = true; std::string str = gob_cmd_update_animation( m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop); // create message and add to list ActiveObjectMessage aom(getId(), true, str); m_messages_out.push(aom); } if (!m_animation_speed_sent) { m_animation_speed_sent = true; std::string str = gob_cmd_update_animation_speed(m_animation_speed); // create message and add to list ActiveObjectMessage aom(getId(), true, str); m_messages_out.push(aom); } if (!m_bone_position_sent) { m_bone_position_sent = true; for (std::unordered_map>::const_iterator ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii){ std::string str = gob_cmd_update_bone_position((*ii).first, (*ii).second.X, (*ii).second.Y); // create message and add to list ActiveObjectMessage aom(getId(), true, str); m_messages_out.push(aom); } } if (!m_attachment_sent) { m_attachment_sent = true; std::string str = gob_cmd_update_attachment(m_attachment_parent_id, m_attachment_bone, m_attachment_position, m_attachment_rotation); // create message and add to list ActiveObjectMessage aom(getId(), true, str); m_messages_out.push(aom); } } std::string LuaEntitySAO::getClientInitializationData(u16 protocol_version) { std::ostringstream os(std::ios::binary); // protocol >= 14 writeU8(os, 1); // version os << serializeString(""); // name writeU8(os, 0); // is_player writeS16(os, getId()); //id writeV3F1000(os, m_base_position); writeF1000(os, m_yaw); writeS16(os, m_hp); std::ostringstream msg_os(std::ios::binary); msg_os << serializeLongString(getPropertyPacket()); // message 1 msg_os << serializeLongString(gob_cmd_update_armor_groups(m_armor_groups)); // 2 msg_os << serializeLongString(gob_cmd_update_animation( m_animation_range, m_animation_speed, m_animation_blend, m_animation_loop)); // 3 for (std::unordered_map>::const_iterator ii = m_bone_position.begin(); ii != m_bone_position.end(); ++ii) { msg_os << serializeLongString(gob_cmd_update_bone_position((*ii).first, (*ii).second.X, (*ii).second.Y)); // m_bone_position.size } msg_os << serializeLongString(gob_cmd_update_attachment(m_attachment_parent_id, m_attachment_bone, m_attachment_position, m_attachment_rotation)); // 4 int message_count = 4 + m_bone_position.size(); for (std::unordered_set::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(gob_cmd_update_infant(*ii, obj->getSendType(), obj->getClientInitializationData(protocol_version))); } } msg_os << serializeLongString(gob_cmd_set_texture_mod(m_current_texture_modifier)); message_count++; writeU8(os, message_count); os.write(msg_os.str().c_str(), msg_os.str().size()); // return result return os.str(); } void LuaEntitySAO::getStaticData(std::string *result) const { verbosestream<getScriptIface()-> luaentity_GetStaticdata(m_id); os<getWieldedItem(); punchitem = &punchitem_static; } PunchDamageResult result = getPunchDamage( m_armor_groups, toolcap, punchitem, 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(getHP() - result.damage, PlayerHPChangeReason(PlayerHPChangeReason::SET_HP)); if (result.damage > 0) { std::string punchername = puncher ? puncher->getDescription() : "nil"; actionstream << getDescription() << " punched by " << punchername << ", damage " << result.damage << " hp, health now " << getHP() << " hp" << std::endl; } std::string str = gob_cmd_punched(result.damage, getHP()); // create message and add to list ActiveObjectMessage aom(getId(), true, str); m_messages_out.push(aom); } } if (getHP() == 0 && !isGone()) { m_pending_removal = true; clearParentAttachment(); clearChildAttachments(); m_env->getScriptIface()->luaentity_on_death(m_id, puncher); } 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 os(std::ios::binary); os<<"LuaEntitySAO at ("; os<<(m_base_position.X/BS)<<","; os<<(m_base_position.Y/BS)<<","; os<<(m_base_position.Z/BS); os<<")"; return os.str(); } void LuaEntitySAO::setHP(s16 hp, const PlayerHPChangeReason &reason) { if (hp < 0) hp = 0; m_hp = hp; } s16 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) { std::/* Minetest Copyright (C) 2010-2017 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 "activeobject.h" #include "environment.h" #include "mapnode.h" #include "settings.h" #include "util/numeric.h" #include <set> class IGameDef; class ServerMap; struct GameParams; class MapBlock; class RemotePlayer; class PlayerDatabase; class AuthDatabase; class PlayerSAO; class ServerEnvironment; class ActiveBlockModifier; struct StaticObject; class ServerActiveObject; class Server; class ServerScripting; /* {Active, Loading} block modifier interface. These are fed into ServerEnvironment at initialization time; ServerEnvironment handles deleting them. */ class ActiveBlockModifier { public: ActiveBlockModifier() = default; virtual ~ActiveBlockModifier() = default; // Set of contents to trigger on virtual const std::vector<std::string> &getTriggerContents() const = 0; // Set of required neighbors (trigger doesn't happen if none are found) // Empty = do not check neighbors virtual const std::vector<std::string> &getRequiredNeighbors() const = 0; // Trigger interval in seconds virtual float getTriggerInterval() = 0; // Random chance of (1 / return value), 0 is disallowed virtual u32 getTriggerChance() = 0; // Whether to modify chance to simulate time lost by an unnattended block virtual bool getSimpleCatchUp() = 0; // This is called usually at interval for 1/chance of the nodes virtual void trigger(ServerEnvironment *env, v3s16 p, MapNode n){}; virtual void trigger(ServerEnvironment *env, v3s16 p, MapNode n, u32 active_object_count, u32 active_object_count_wider){}; }; struct ABMWithState { ActiveBlockModifier *abm; float timer = 0.0f; ABMWithState(ActiveBlockModifier *abm_); }; struct LoadingBlockModifierDef { // Set of contents to trigger on std::set<std::string> trigger_contents; std::string name; bool run_at_every_load = false; virtual ~LoadingBlockModifierDef() = default; virtual void trigger(ServerEnvironment *env, v3s16 p, MapNode n){}; }; struct LBMContentMapping { typedef std::unordered_map<content_t, std::vector<LoadingBlockModifierDef *>> lbm_map; lbm_map map; std::vector<LoadingBlockModifierDef *> lbm_list; // Needs to be separate method (not inside destructor), // because the LBMContentMapping may be copied and destructed // many times during operation in the lbm_lookup_map. void deleteContents(); void addLBM(LoadingBlockModifierDef *lbm_def, IGameDef *gamedef); const std::vector<LoadingBlockModifierDef *> *lookup(content_t c) const; }; class LBMManager { public: LBMManager() = default; ~LBMManager(); // Don't call this after loadIntroductionTimes() ran. void addLBMDef(LoadingBlockModifierDef *lbm_def); void loadIntroductionTimes(const std::string &times, IGameDef *gamedef, u32 now); // Don't call this before loadIntroductionTimes() ran. std::string createIntroductionTimesString(); // Don't call this before loadIntroductionTimes() ran. void applyLBMs(ServerEnvironment *env, MapBlock *block, u32 stamp); // Warning: do not make this std::unordered_map, order is relevant here typedef std::map<u32, LBMContentMapping> lbm_lookup_map; private: // Once we set this to true, we can only query, // not modify bool m_query_mode = false; // For m_query_mode == false: // The key of the map is the LBM def's name. // TODO make this std::unordered_map std::map<std::string, LoadingBlockModifierDef *> m_lbm_defs; // For m_query_mode == true: // The key of the map is the LBM def's first introduction time. lbm_lookup_map m_lbm_lookup; // Returns an iterator to the LBMs that were introduced // after the given time. This is guaranteed to return // valid values for everything lbm_lookup_map::const_iterator getLBMsIntroducedAfter(u32 time) { return m_lbm_lookup.lower_bound(time); } }; /* List of active blocks, used by ServerEnvironment */ class ActiveBlockList { public: void update(std::vector<PlayerSAO*> &active_players, s16 active_block_range, s16 active_object_range, std::set<v3s16> &blocks_removed, std::set<v3s16> &blocks_added); bool contains(v3s16 p){ return (m_list.find(p) != m_list.end()); } void clear(){ m_list.clear(); } std::set<v3s16> m_list; std::set<v3s16> m_abm_list; std::set<v3s16> m_forceloaded_list; private: }; /* Operation mode for ServerEnvironment::clearObjects() */ enum ClearObjectsMode { // Load and go through every mapblock, clearing objects CLEAR_OBJECTS_MODE_FULL, // Clear objects immediately in loaded mapblocks; // clear objects in unloaded mapblocks only when the mapblocks are next activated. CLEAR_OBJECTS_MODE_QUICK, }; /* The server-side environment. This is not thread-safe. Server uses an environment mutex. */ typedef std::unordered_map<u16, ServerActiveObject *> ServerActiveObjectMap; class ServerEnvironment : public Environment { public: ServerEnvironment(ServerMap *map, ServerScripting *scriptIface, Server *server, const std::string &path_world); ~ServerEnvironment(); Map & getMap(); ServerMap & getServerMap(); //TODO find way to remove this fct! ServerScripting* getScriptIface() { return m_script; } Server *getGameDef() { return m_server; } float getSendRecommendedInterval() { return m_recommended_send_interval; } void kickAllPlayers(AccessDeniedCode reason, const std::string &str_reason, bool reconnect); // Save players void saveLoadedPlayers(); void savePlayer(RemotePlayer *player); PlayerSAO *loadPlayer(RemotePlayer *player, bool *new_player, session_t peer_id, bool is_singleplayer); void addPlayer(RemotePlayer *player); void removePlayer(RemotePlayer *player); bool removePlayerFromDatabase(const std::string &name); /* Save and load time of day and game timer */ void saveMeta(); void loadMeta(); u32 addParticleSpawner(float exptime); u32 addParticleSpawner(float exptime, u16 attached_id); void deleteParticleSpawner(u32 id, bool remove_from_object = true); /*