X-Git-Url: https://git.lizzy.rs/?a=blobdiff_plain;f=src%2Fcontent_sao.cpp;h=41b1aec18794c65db0bea633ccd167368f4cd3e4;hb=4aa9a669cb184b77213e8df82eb20eda5aad9004;hp=7526e03533bc1829bea4f9f9c5111f6ec99dce37;hpb=3cab24fbcfbbeb12cdd9f085167969e1a934bd59;p=dragonfireclient.git diff --git a/src/content_sao.cpp b/src/content_sao.cpp index 7526e0353..41b1aec18 100644 --- a/src/content_sao.cpp +++ b/src/content_sao.cpp @@ -1,6 +1,6 @@ /* -Minetest-c55 -Copyright (C) 2010-2011 celeron55, Perttu Ahola +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 @@ -18,60 +18,21 @@ with this program; if not, write to the Free Software Foundation, Inc., */ #include "content_sao.h" +#include "util/serialize.h" #include "collision.h" #include "environment.h" -#include "settings.h" -#include "main.h" // For g_profiler -#include "profiler.h" -#include "serialization.h" // For compressZlib #include "tool.h" // For ToolCapabilities #include "gamedef.h" -#include "player.h" -#include "scriptapi.h" +#include "nodedef.h" +#include "remoteplayer.h" +#include "server.h" +#include "scripting_server.h" #include "genericobject.h" -#include "util/serialize.h" - -core::map ServerActiveObject::m_types; - -/* - DummyLoadSAO -*/ - -class DummyLoadSAO : public ServerActiveObject -{ -public: - DummyLoadSAO(ServerEnvironment *env, v3f pos, u8 type): - ServerActiveObject(env, pos) - { - ServerActiveObject::registerType(type, create); - } - // Pretend to be the test object (to fool the client) - u8 getType() const - { return ACTIVEOBJECT_TYPE_TEST; } - // And never save to disk - bool isStaticAllowed() const - { return false; } - - static ServerActiveObject* create(ServerEnvironment *env, v3f pos, - const std::string &data) - { - return new DummyLoadSAO(env, pos, 0); - } - - void step(float dtime, bool send_recommended) - { - m_removed = true; - infostream<<"DummyLoadSAO step"< +#include -// Prototype (registers item for deserialization) -DummyLoadSAO proto1_DummyLoadSAO(NULL, v3f(0,0,0), ACTIVEOBJECT_TYPE_RAT); -DummyLoadSAO proto2_DummyLoadSAO(NULL, v3f(0,0,0), ACTIVEOBJECT_TYPE_OERKKI1); -DummyLoadSAO proto3_DummyLoadSAO(NULL, v3f(0,0,0), ACTIVEOBJECT_TYPE_FIREFLY); -DummyLoadSAO proto4_DummyLoadSAO(NULL, v3f(0,0,0), ACTIVEOBJECT_TYPE_MOBV2); +std::map ServerActiveObject::m_types; /* TestSAO @@ -87,9 +48,9 @@ class TestSAO : public ServerActiveObject { ServerActiveObject::registerType(getType(), create); } - u8 getType() const + ActiveObjectType getType() const { return ACTIVEOBJECT_TYPE_TEST; } - + static ServerActiveObject* create(ServerEnvironment *env, v3f pos, const std::string &data) { @@ -101,7 +62,7 @@ class TestSAO : public ServerActiveObject m_age += dtime; if(m_age > 10) { - m_removed = true; + m_pending_removal = true; return; } @@ -109,7 +70,7 @@ class TestSAO : public ServerActiveObject if(m_base_position.Y > 8*BS) m_base_position.Y = 2*BS; - if(send_recommended == false) + if (!send_recommended) return; m_timer1 -= dtime; @@ -128,10 +89,16 @@ class TestSAO : public ServerActiveObject data += itos(m_base_position.Z); ActiveObjectMessage aom(getId(), false, data); - m_messages_out.push_back(aom); + 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; @@ -141,195 +108,185 @@ class TestSAO : public ServerActiveObject TestSAO proto_TestSAO(NULL, v3f(0,0,0)); /* - ItemSAO + UnitSAO + */ - DEPRECATED: New dropped items are implemented in Lua; see - builtin/item_entity.lua. -*/ +UnitSAO::UnitSAO(ServerEnvironment *env, v3f pos): + ServerActiveObject(env, pos) +{ + // Initialize something to armor groups + m_armor_groups["fleshy"] = 100; +} -class ItemSAO : public ServerActiveObject +ServerActiveObject *UnitSAO::getParent() const { -public: - u8 getType() const - { return ACTIVEOBJECT_TYPE_ITEM; } - - float getMinimumSavedMovement() - { return 0.1*BS; } + if (!m_attachment_parent_id) + return nullptr; + // Check if the parent still exists + ServerActiveObject *obj = m_env->getActiveObject(m_attachment_parent_id); - static ServerActiveObject* create(ServerEnvironment *env, v3f pos, - const std::string &data) - { - std::istringstream is(data, std::ios::binary); - char buf[1]; - // read version - is.read(buf, 1); - u8 version = buf[0]; - // check if version is supported - if(version != 0) - return NULL; - std::string itemstring = deSerializeString(is); - infostream<<"create(): Creating item \"" - < box(-BS/3.,0.0,-BS/3., BS/3.,BS*2./3.,BS/3.); - collisionMoveResult moveresult; - // Apply gravity - m_speed_f += v3f(0, -dtime*9.81*BS, 0); - // Maximum movement without glitches - f32 pos_max_d = BS*0.25; - // Limit speed - if(m_speed_f.getLength()*dtime > pos_max_d) - m_speed_f *= pos_max_d / (m_speed_f.getLength()*dtime); - v3f pos_f = getBasePosition(); - v3f pos_f_old = pos_f; - v3f accel_f = v3f(0,0,0); - f32 stepheight = 0; - IGameDef *gamedef = m_env->getGameDef(); - moveresult = collisionMoveSimple(&m_env->getMap(), gamedef, - pos_max_d, box, stepheight, dtime, - pos_f, m_speed_f, accel_f); - - if(send_recommended == false) - return; +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; +} - if(pos_f.getDistanceFrom(m_last_sent_position) > 0.05*BS) - { - setBasePosition(pos_f); - m_last_sent_position = pos_f; - - std::ostringstream os(std::ios::binary); - // command (0 = update position) - writeU8(os, 0); - // pos - writeV3F1000(os, m_base_position); - // create message and add to list - ActiveObjectMessage aom(getId(), false, os.str()); - m_messages_out.push_back(aom); - } - if(m_itemstring_changed) - { - m_itemstring_changed = false; +void UnitSAO::setAnimationSpeed(float frame_speed) +{ + m_animation_speed = frame_speed; + m_animation_speed_sent = false; +} - std::ostringstream os(std::ios::binary); - // command (1 = update itemstring) - writeU8(os, 1); - // itemstring - os<(position, rotation); + m_bone_position_sent = false; +} - std::string getClientInitializationData() - { - std::ostringstream os(std::ios::binary); - // version - writeU8(os, 0); - // pos - writeV3F1000(os, m_base_position); - // itemstring - os<getActiveObject(child_id)) + child->setAttachment(0, "", v3f(0, 0, 0), v3f(0, 0, 0)); } + m_attachment_child_ids.clear(); +} - ItemStack createItemStack() - { - try{ - IItemDefManager *idef = m_env->getGameDef()->idef(); - ItemStack item; - item.deSerialize(m_itemstring, idef); - infostream<<__FUNCTION_NAME<<": m_itemstring=\""< item=\""<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); +} - int punch(v3f dir, - const ToolCapabilities *toolcap, - ServerActiveObject *puncher, - float time_from_last_punch) - { - // Take item into inventory - ItemStack item = createItemStack(); - Inventory *inv = puncher->getInventory(); - if(inv != NULL) - { - std::string wieldlist = puncher->getWieldList(); - ItemStack leftover = inv->addItem(wieldlist, item); - puncher->setInventoryModified(); - if(leftover.empty()) - { - m_removed = true; - } - else - { - m_itemstring = leftover.getItemString(); - m_itemstring_changed = true; - } - } - - return 0; +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() 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; -private: - std::string m_itemstring; - bool m_itemstring_changed; - v3f m_speed_f; - v3f m_last_sent_position; - IntervalLimiter m_move_interval; -}; + ServerActiveObject *parent = m_env->getActiveObject(parent_id); + if (getType() == ACTIVEOBJECT_TYPE_LUAENTITY) + m_env->getScriptIface()->luaentity_on_detach(m_id, parent); -// Prototype (registers item for deserialization) -ItemSAO proto_ItemSAO(NULL, v3f(0,0,0), ""); + 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); +} -ServerActiveObject* createItemSAO(ServerEnvironment *env, v3f pos, - const std::string itemstring) +ObjectProperties* UnitSAO::accessObjectProperties() { - return new ItemSAO(env, pos, itemstring); + return &m_prop; +} + +void UnitSAO::notifyObjectPropertiesModified() +{ + m_properties_sent = false; } /* @@ -341,56 +298,47 @@ LuaEntitySAO proto_LuaEntitySAO(NULL, v3f(0,0,0), "_prototype", ""); LuaEntitySAO::LuaEntitySAO(ServerEnvironment *env, v3f pos, const std::string &name, const std::string &state): - ServerActiveObject(env, pos), + UnitSAO(env, pos), m_init_name(name), - m_init_state(state), - m_registered(false), - m_hp(-1), - m_velocity(0,0,0), - m_acceleration(0,0,0), - m_yaw(0), - m_properties_sent(true), - m_last_sent_yaw(0), - m_last_sent_position(0,0,0), - m_last_sent_velocity(0,0,0), - m_last_sent_position_timer(0), - m_last_sent_move_precision(0), - m_armor_groups_sent(false) + m_init_state(state) { // Only register type if no environment supplied if(env == NULL){ ServerActiveObject::registerType(getType(), create); return; } - - // Initialize something to armor groups - m_armor_groups["fleshy"] = 3; - m_armor_groups["snappy"] = 2; } LuaEntitySAO::~LuaEntitySAO() { if(m_registered){ - lua_State *L = m_env->getLua(); - scriptapi_luaentity_rm(L, m_id); + 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 - lua_State *L = m_env->getLua(); - m_registered = scriptapi_luaentity_add(L, m_id, m_init_name.c_str()); - + m_registered = m_env->getScriptIface()-> + luaentity_Add(m_id, m_init_name.c_str()); + if(m_registered){ // Get properties - scriptapi_luaentity_get_properties(L, m_id, &m_prop); + m_env->getScriptIface()-> + luaentity_GetProperties(m_id, &m_prop); // Initialize HP from properties m_hp = m_prop.hp_max; // Activate entity, supplying serialized state - scriptapi_luaentity_activate(L, m_id, m_init_state.c_str(), dtime_s); + m_env->getScriptIface()-> + luaentity_Activate(m_id, m_init_state, dtime_s); + } else { + m_prop.infotext = m_init_name; } } @@ -399,33 +347,50 @@ ServerActiveObject* LuaEntitySAO::create(ServerEnvironment *env, v3f pos, { std::string name; std::string state; - s16 hp = 1; + u16 hp = 1; v3f velocity; - float yaw = 0; - if(data != ""){ + v3f rotation; + + while (!data.empty()) { // breakable, run for one iteration std::istringstream is(data, std::ios::binary); - // read version + // '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); - // 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); - } + + 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; + // + break; } // create object - infostream<<"LuaEntitySAO::create(name=\""<m_hp = hp; sao->m_velocity = velocity; - sao->m_yaw = yaw; + sao->m_rotation = rotation; return sao; } @@ -437,107 +402,226 @@ void LuaEntitySAO::step(float dtime, bool send_recommended) std::string str = getPropertyPacket(); // create message and add to list ActiveObjectMessage aom(getId(), true, str); - m_messages_out.push_back(aom); + 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; - - if(m_prop.physical){ - core::aabbox3d box = m_prop.collisionbox; - box.MinEdge *= BS; - box.MaxEdge *= BS; - collisionMoveResult moveresult; - f32 pos_max_d = BS*0.25; // Distance per iteration - f32 stepheight = 0; // Maximum climbable step height - v3f p_pos = m_base_position; - v3f p_velocity = m_velocity; - v3f p_acceleration = m_acceleration; - IGameDef *gamedef = m_env->getGameDef(); - moveresult = collisionMoveSimple(&m_env->getMap(), gamedef, - pos_max_d, box, stepheight, dtime, - p_pos, p_velocity, p_acceleration); - // 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; + + // 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_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){ - lua_State *L = m_env->getLua(); - scriptapi_luaentity_step(L, m_id, dtime); + m_env->getScriptIface()->luaentity_Step(m_id, dtime); } - if(send_recommended == false) + if (!send_recommended) return; - - // 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 || - fabs(m_yaw - m_last_sent_yaw) > 1.0){ - sendPosition(true, false); + + 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); + } } - if(m_armor_groups_sent == 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_back(aom); + 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() +std::string LuaEntitySAO::getClientInitializationData(u16 protocol_version) { std::ostringstream os(std::ios::binary); - writeU8(os, 0); // version - os<= 37 + writeU8(os, 1); // version + os << serializeString(""); // name writeU8(os, 0); // is_player - writeV3F1000(os, m_base_position); - writeF1000(os, m_yaw); - writeS16(os, m_hp); - writeU8(os, 2); // number of messages stuffed in here - os<>::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++; + // TODO after a protocol bump: only send the object initialization data + // to older clients (superfluous since this message exists) + 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(); } -std::string LuaEntitySAO::getStaticData() +void LuaEntitySAO::getStaticData(std::string *result) const { - verbosestream<<__FUNCTION_NAME<getLua(); - std::string state = scriptapi_luaentity_get_staticdata(L, m_id); + std::string state = m_env->getScriptIface()-> + luaentity_GetStaticdata(m_id); os<= 37 + + writeF1000(os, m_rotation.X); + writeF1000(os, m_rotation.Z); + + // + + *result = os.str(); } int LuaEntitySAO::punch(v3f dir, @@ -545,67 +629,74 @@ int LuaEntitySAO::punch(v3f dir, ServerActiveObject *puncher, float time_from_last_punch) { - if(!m_registered){ + if (!m_registered) { // Delete unknown LuaEntities when punched - m_removed = true; + m_pending_removal = true; return 0; } - - ItemStack *punchitem = NULL; - ItemStack punchitem_static; - if(puncher){ - punchitem_static = puncher->getWieldedItem(); - punchitem = &punchitem_static; - } + + FATAL_ERROR_IF(!puncher, "Punch action called without SAO"); + + s32 old_hp = getHP(); + const ItemStack &punchitem = puncher->getWieldedItem(); PunchDamageResult result = getPunchDamage( m_armor_groups, toolcap, - punchitem, + &punchitem, time_from_last_punch); - - if(result.did_punch) - { - setHP(getHP() - result.damage); - - actionstream<getDescription()<<", damage "<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::SET_HP)); + + std::string str = gob_cmd_punched(getHP()); // create message and add to list ActiveObjectMessage aom(getId(), true, str); - m_messages_out.push_back(aom); + m_messages_out.push(aom); } + } - if(getHP() == 0) - m_removed = true; + if (getHP() == 0 && !isGone()) { + m_pending_removal = true; + clearParentAttachment(); + clearChildAttachments(); + m_env->getScriptIface()->luaentity_on_death(m_id, puncher); } - lua_State *L = m_env->getLua(); - scriptapi_luaentity_punch(L, m_id, puncher, - time_from_last_punch, toolcap, dir); + 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 result.wear; } void LuaEntitySAO::rightClick(ServerActiveObject *clicker) { - if(!m_registered) + if (!m_registered) return; - lua_State *L = m_env->getLua(); - scriptapi_luaentity_rightclick(L, m_id, clicker); + + m_env->getScriptIface()->luaentity_Rightclick(m_id, clicker); } -void LuaEntitySAO::setPos(v3f pos) +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); @@ -627,33 +718,16 @@ std::string LuaEntitySAO::getDescription() return os.str(); } -void LuaEntitySAO::setHP(s16 hp) +void LuaEntitySAO::setHP(s32 hp, const PlayerHPChangeReason &reason) { - if(hp < 0) hp = 0; - m_hp = hp; + m_hp = rangelim(hp, 0, U16_MAX); } -s16 LuaEntitySAO::getHP() const +u16 LuaEntitySAO::getHP() const { return m_hp; } -void LuaEntitySAO::setArmorGroups(const ItemGroupList &armor_groups) -{ - m_armor_groups = armor_groups; - m_armor_groups_sent = false; -} - -ObjectProperties* LuaEntitySAO::accessObjectProperties() -{ - return &m_prop; -} - -void LuaEntitySAO::notifyObjectPropertiesModified() -{ - m_properties_sent = false; -} - void LuaEntitySAO::setVelocity(v3f velocity) { m_velocity = velocity; @@ -674,22 +748,18 @@ v3f LuaEntitySAO::getAcceleration() return m_acceleration; } -void LuaEntitySAO::setYaw(float yaw) -{ - m_yaw = yaw; -} - -float LuaEntitySAO::getYaw() -{ - return m_yaw; -} - void LuaEntitySAO::setTextureMod(const std::string &mod) { std::string str = gob_cmd_set_texture_mod(mod); + m_current_texture_modifier = mod; // create message and add to list ActiveObjectMessage aom(getId(), true, str); - m_messages_out.push_back(aom); + m_messages_out.push(aom); +} + +std::string LuaEntitySAO::getTextureMod() const +{ + return m_current_texture_modifier; } void LuaEntitySAO::setSprite(v2s16 p, int num_frames, float framelength, @@ -703,7 +773,7 @@ void LuaEntitySAO::setSprite(v2s16 p, int num_frames, float framelength, ); // create message and add to list ActiveObjectMessage aom(getId(), true, str); - m_messages_out.push_back(aom); + m_messages_out.push(aom); } std::string LuaEntitySAO::getName() @@ -718,13 +788,17 @@ std::string LuaEntitySAO::getPropertyPacket() 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_yaw = m_yaw; 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(); @@ -732,14 +806,48 @@ void LuaEntitySAO::sendPosition(bool do_interpolate, bool is_movement_end) m_base_position, m_velocity, m_acceleration, - m_yaw, + m_rotation, do_interpolate, is_movement_end, update_interval ); // create message and add to list ActiveObjectMessage aom(getId(), false, str); - m_messages_out.push_back(aom); + m_messages_out.push(aom); +} + +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; } /* @@ -748,55 +856,63 @@ void LuaEntitySAO::sendPosition(bool do_interpolate, bool is_movement_end) // No prototype, PlayerSAO does not need to be deserialized -PlayerSAO::PlayerSAO(ServerEnvironment *env_, Player *player_, u16 peer_id_, - const std::set &privs, bool is_singleplayer): - ServerActiveObject(env_, v3f(0,0,0)), +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_inventory(NULL), - m_last_good_position(0,0,0), - m_last_good_position_age(0), - m_time_from_last_punch(0), - m_nocheat_dig_pos(32767, 32767, 32767), - m_nocheat_dig_time(0), - m_wield_index(0), - m_position_not_sent(false), - m_armor_groups_sent(false), - m_properties_sent(true), - m_privs(privs), - m_is_singleplayer(is_singleplayer), - // public - m_teleported(false), - m_inventory_not_sent(false), - m_hp_not_sent(false), - m_wielded_item_not_sent(false) -{ - assert(m_player); - assert(m_peer_id != 0); - setBasePosition(m_player->getPosition()); - m_inventory = &m_player->inventory; - m_armor_groups["choppy"] = 2; - m_armor_groups["fleshy"] = 3; + m_is_singleplayer(is_singleplayer) +{ + assert(m_peer_id != 0); // pre-condition - m_prop.hp_max = PLAYER_MAX_HP; + m_prop.hp_max = PLAYER_MAX_HP_DEFAULT; + m_prop.breath_max = PLAYER_MAX_BREATH_DEFAULT; m_prop.physical = false; m_prop.weight = 75; - m_prop.collisionbox = core::aabbox3d(-1/3.,-1.0,-1/3., 1/3.,1.0,1/3.); + 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 = v2f(1, 2); + m_prop.visual_size = v3f(1, 2, 1); m_prop.textures.clear(); - m_prop.textures.push_back("player.png"); - m_prop.textures.push_back("player_back.png"); + 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.is_visible = (getHP() != 0); + 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; } PlayerSAO::~PlayerSAO() { if(m_inventory != &m_player->inventory) delete m_inventory; +} +void PlayerSAO::finalize(RemotePlayer *player, const std::set &privs) +{ + assert(player); + m_player = player; + m_privs = privs; + m_inventory = &m_player->inventory; +} + +v3f PlayerSAO::getEyeOffset() const +{ + return v3f(0, BS * m_prop.eye_height, 0); } std::string PlayerSAO::getDescription() @@ -808,185 +924,357 @@ std::string PlayerSAO::getDescription() void PlayerSAO::addedToEnvironment(u32 dtime_s) { ServerActiveObject::addedToEnvironment(dtime_s); - ServerActiveObject::setBasePosition(m_player->getPosition()); + ServerActiveObject::setBasePosition(m_base_position); m_player->setPlayerSAO(this); - m_player->peer_id = m_peer_id; - m_last_good_position = m_player->getPosition(); - m_last_good_position_age = 0.0; + 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) - { - m_player->setPlayerSAO(NULL); - m_player->peer_id = 0; + if (m_player->getPlayerSAO() == this) { + unlinkPlayerSessionAndSave(); + for (u32 attached_particle_spawner : m_attached_particle_spawners) { + m_env->deleteParticleSpawner(attached_particle_spawner, false); + } } } -bool PlayerSAO::isStaticAllowed() const -{ - return false; -} - -bool PlayerSAO::unlimitedTransferDistance() const -{ - return g_settings->getBool("unlimited_player_transfer_distance"); -} - -std::string PlayerSAO::getClientInitializationData() +std::string PlayerSAO::getClientInitializationData(u16 protocol_version) { std::ostringstream os(std::ios::binary); - writeU8(os, 0); // version - os<getName()); // name + + // Protocol >= 15 + writeU8(os, 1); // version + os << serializeString(m_player->getName()); // name writeU8(os, 1); // is_player - writeV3F1000(os, m_player->getPosition() + v3f(0,BS*1,0)); - writeF1000(os, m_player->getYaw()); - writeS16(os, getHP()); - writeU8(os, 2); // number of messages stuffed in here - os<>::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 + msg_os << serializeLongString(gob_cmd_update_physics_override(m_physics_override_speed, + m_physics_override_jump, m_physics_override_gravity, m_physics_override_sneak, + m_physics_override_sneak_glitch, m_physics_override_new_move)); // 5 + // (GENERIC_CMD_UPDATE_NAMETAG_ATTRIBUTES) : Deprecated, for backwards compatibility only. + msg_os << serializeLongString(gob_cmd_update_nametag_attributes(m_prop.nametag_color)); // 6 + int message_count = 6 + 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))); + } + } + + writeU8(os, message_count); + os.write(msg_os.str().c_str(), msg_os.str().size()); + + // return result return os.str(); } -std::string PlayerSAO::getStaticData() +void PlayerSAO::getStaticData(std::string * result) const { - assert(0); - return ""; + FATAL_ERROR("Deprecated function"); } void PlayerSAO::step(float dtime, bool send_recommended) { - if(!m_properties_sent) - { + 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().getNodeNoEx(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)) { + // Get nose/mouth position, approximate with eye position + v3s16 p = floatToInt(getEyePosition(), BS); + MapNode n = m_env->getMap().getNodeNoEx(p); + const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n); + // If player is alive & no drowning & not in ignore, 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().getNodeNoEx(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().getNodeNoEx(ptop); + const ContentFeatures &c = m_env->getGameDef()->ndef()->get(ntop); + if (c.damage_per_second > damage_per_second) { + damage_per_second = c.damage_per_second; + nodename = c.name; + } + + if (damage_per_second != 0 && m_hp > 0) { + s32 newhp = (s32)m_hp - (s32)damage_per_second; + PlayerHPChangeReason reason(PlayerHPChangeReason::NODE_DAMAGE, nodename); + setHP(newhp, reason); + m_env->getGameDef()->SendPlayerHPOrDie(this, reason); + } + } + + if (!m_properties_sent) { m_properties_sent = true; std::string str = getPropertyPacket(); // create message and add to list ActiveObjectMessage aom(getId(), true, str); - m_messages_out.push_back(aom); + 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.0f, 0.0f, 0.0f); + m_attachment_rotation = v3f(0.0f, 0.0f, 0.0f); + setBasePosition(m_last_good_position); + m_env->getGameDef()->SendMovePlayer(m_peer_id); + } + + //dstream<<"PlayerSAO::step: dtime: "<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; - - if(m_is_singleplayer || g_settings->getBool("disable_anticheat")) - { - m_last_good_position = m_player->getPosition(); - m_last_good_position_age = 0; - } - else - { - /* - 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 player_max_speed = 0; - float player_max_speed_up = 0; - if(m_privs.count("fast") != 0){ - // Fast speed - player_max_speed = BS * 20; - player_max_speed_up = BS * 20; - } else { - // Normal speed - player_max_speed = BS * 4.0; - player_max_speed_up = BS * 4.0; - } - // Tolerance - player_max_speed *= 2.5; - player_max_speed_up *= 2.5; - - m_last_good_position_age += dtime; - if(m_last_good_position_age >= 1.0){ - float age = m_last_good_position_age; - v3f diff = (m_player->getPosition() - m_last_good_position); - float d_vert = diff.Y; - diff.Y = 0; - float d_horiz = diff.getLength(); - /*infostream<getName()<<"'s horizontal speed is " - <<(d_horiz/age)<getPosition(); - } else { - actionstream<<"Player "<getName() - <<" moved too fast; resetting position" - <setPosition(m_last_good_position); - m_teleported = true; - } - m_last_good_position_age = 0; - } + + // 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 == false) + if (!send_recommended) return; - if(m_position_not_sent) - { + // If the object is attached client-side, don't waste bandwidth sending its + // position or rotation to clients. + if (m_position_not_sent && !isAttached()) { m_position_not_sent = false; float update_interval = m_env->getSendRecommendedInterval(); + v3f pos; + if (isAttached()) // Just in case we ever do send attachment position too + pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition(); + else + pos = m_base_position; + std::string str = gob_cmd_update_position( - m_player->getPosition() + v3f(0,BS*1,0), - v3f(0,0,0), - v3f(0,0,0), - m_player->getYaw(), + 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 ActiveObjectMessage aom(getId(), false, str); - m_messages_out.push_back(aom); + m_messages_out.push(aom); } - if(m_wielded_item_not_sent) - { - m_wielded_item_not_sent = false; - // GenericCAO has no special way to show this - } - - if(m_armor_groups_sent == 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_back(aom); + m_messages_out.push(aom); + } + + if (!m_physics_override_sent) { + m_physics_override_sent = true; + std::string str = gob_cmd_update_physics_override(m_physics_override_speed, + m_physics_override_jump, m_physics_override_gravity, + m_physics_override_sneak, m_physics_override_sneak_glitch, + m_physics_override_new_move); + // 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_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); } } 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); - m_position_not_sent = true; + + // Updating is not wanted/required for player migration + if (m_env) { + m_position_not_sent = true; + } } -void PlayerSAO::setPos(v3f pos) +void PlayerSAO::setPos(const v3f &pos) { - m_player->setPosition(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_last_good_position_age = 0; - // Force position change on client - m_teleported = true; + 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) { - m_player->setPosition(pos); + if(isAttached()) + return; + + setBasePosition(pos); // Movement caused by this command is always valid m_last_good_position = pos; - m_last_good_position_age = 0; - // Force position change on client - m_teleported = true; + 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); } int PlayerSAO::punch(v3f dir, @@ -994,95 +1282,86 @@ int PlayerSAO::punch(v3f dir, ServerActiveObject *puncher, float time_from_last_punch) { - if(!toolcap) + if (!toolcap) return 0; + FATAL_ERROR_IF(!puncher, "Punch action called without SAO"); + // No effect if PvP disabled - if(g_settings->getBool("enable_pvp") == false){ - if(puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER){ - std::string str = gob_cmd_punched(0, getHP()); + if (!g_settings->getBool("enable_pvp")) { + if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) { + std::string str = gob_cmd_punched(getHP()); // create message and add to list ActiveObjectMessage aom(getId(), true, str); - m_messages_out.push_back(aom); + m_messages_out.push(aom); return 0; } } + s32 old_hp = getHP(); HitParams hitparams = getHitParams(m_armor_groups, toolcap, time_from_last_punch); - actionstream<<"Player "<getName()<<" punched by " - <getDescription()<<", damage "<getPlayerSAO(); - setHP(getHP() - hitparams.hp); + bool damage_handled = m_env->getScriptIface()->on_punchplayer(playersao, + puncher, time_from_last_punch, toolcap, dir, + hitparams.hp); - if(hitparams.hp != 0) - { - std::string str = gob_cmd_punched(hitparams.hp, getHP()); - // create message and add to list - ActiveObjectMessage aom(getId(), true, str); - m_messages_out.push_back(aom); + if (!damage_handled) { + setHP((s32)getHP() - (s32)hitparams.hp, + PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, puncher)); + } else { // override client prediction + if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) { + std::string str = gob_cmd_punched(getHP()); + // create message and add to list + ActiveObjectMessage aom(getId(), true, str); + m_messages_out.push(aom); + } } - return hitparams.wear; -} + 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; -void PlayerSAO::rightClick(ServerActiveObject *clicker) -{ + return hitparams.wear; } -s16 PlayerSAO::getHP() const +void PlayerSAO::setHP(s32 hp, const PlayerHPChangeReason &reason) { - return m_player->hp; -} + s32 oldhp = m_hp; -void PlayerSAO::setHP(s16 hp) -{ - s16 oldhp = m_player->hp; + hp = rangelim(hp, 0, m_prop.hp_max); - if(hp < 0) - hp = 0; - else if(hp > PLAYER_MAX_HP) - hp = PLAYER_MAX_HP; + if (oldhp != hp) { + s32 hp_change = m_env->getScriptIface()->on_player_hpchange(this, hp - oldhp, reason); + if (hp_change == 0) + return; - if(hp < oldhp && g_settings->getBool("enable_damage") == false) - { - m_hp_not_sent = true; // fix wrong prediction on client - return; + hp = rangelim(oldhp + hp_change, 0, m_prop.hp_max); } - m_player->hp = hp; + if (hp < oldhp && isImmortal()) + return; - if(hp != oldhp) - m_hp_not_sent = true; + m_hp = hp; - // On death or reincarnation send an active object message - if((hp == 0) != (oldhp == 0)) - { - // Will send new is_visible value based on (getHP()!=0) + // Update properties on death + if ((hp == 0) != (oldhp == 0)) m_properties_sent = false; - // Send new HP - std::string str = gob_cmd_punched(0, getHP()); - ActiveObjectMessage aom(getId(), true, str); - m_messages_out.push_back(aom); - } } -void PlayerSAO::setArmorGroups(const ItemGroupList &armor_groups) +void PlayerSAO::setBreath(const u16 breath, bool send) { - m_armor_groups = armor_groups; - m_armor_groups_sent = false; -} + if (m_player && breath != m_breath) + m_player->setDirty(true); -ObjectProperties* PlayerSAO::accessObjectProperties() -{ - return &m_prop; -} + m_breath = rangelim(breath, 0, m_prop.breath_max); -void PlayerSAO::notifyObjectPropertiesModified() -{ - m_properties_sent = false; + if (send) + m_env->getGameDef()->SendPlayerBreath(this); } Inventory* PlayerSAO::getInventory() @@ -1101,14 +1380,47 @@ InventoryLocation PlayerSAO::getInventoryLocation() const return loc; } -void PlayerSAO::setInventoryModified() +std::string PlayerSAO::getWieldList() const { - m_inventory_not_sent = true; + return "main"; } -std::string PlayerSAO::getWieldList() const +ItemStack PlayerSAO::getWieldedItem() const { - return "main"; + const Inventory *inv = getInventory(); + ItemStack ret; + const InventoryList *mlist = inv->getList(getWieldList()); + if (mlist && getWieldIndex() < (s32)mlist->getSize()) + ret = mlist->getItem(getWieldIndex()); + return ret; +} + +ItemStack PlayerSAO::getWieldedItemOrHand() const +{ + const Inventory *inv = getInventory(); + ItemStack ret; + const InventoryList *mlist = inv->getList(getWieldList()); + if (mlist && getWieldIndex() < (s32)mlist->getSize()) + ret = mlist->getItem(getWieldIndex()); + if (ret.name.empty()) { + const InventoryList *hlist = inv->getList("hand"); + if (hlist) + ret = hlist->getItem(0); + } + return ret; +} + +bool PlayerSAO::setWieldedItem(const ItemStack &item) +{ + Inventory *inv = getInventory(); + if (inv) { + InventoryList *mlist = inv->getList(getWieldList()); + if (mlist) { + mlist->changeItem(getWieldIndex(), item); + return true; + } + } + return false; } int PlayerSAO::getWieldIndex() const @@ -1118,27 +1430,125 @@ int PlayerSAO::getWieldIndex() const void PlayerSAO::setWieldIndex(int i) { - if(i != m_wield_index) - { + if(i != m_wield_index) { m_wield_index = i; - m_wielded_item_not_sent = true; } } void PlayerSAO::disconnected() { m_peer_id = 0; - m_removed = true; - if(m_player->getPlayerSAO() == this) - { - m_player->setPlayerSAO(NULL); - m_player->peer_id = 0; - } + 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 = (getHP() != 0); + m_prop.is_visible = (true); return gob_cmd_set_properties(m_prop); } +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 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_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; + + // Don't divide by zero! + if (player_max_walk < 0.0001f) + player_max_walk = 0.0001f; + if (player_max_jump < 0.0001f) + player_max_jump = 0.0001f; + + v3f diff = (m_base_position - m_last_good_position); + float d_vert = diff.Y; + diff.Y = 0; + float d_horiz = diff.getLength(); + float required_time = d_horiz / player_max_walk; + + // FIXME: Checking downwards movement is not easily possible currently, + // the server could calculate speed differences to examine the gravity + if (d_vert > 0) { + // In certain cases (water, ladders) walking speed is applied vertically + float s = MYMAX(player_max_jump, player_max_walk); + required_time = MYMAX(required_time, d_vert / s); + } + + if (m_move_pool.grab(required_time)) { + m_last_good_position = m_base_position; + } else { + const float LAG_POOL_MIN = 5.0; + float lag_pool_max = m_env->getMaxLagEstimate() * 2.0; + lag_pool_max = MYMAX(lag_pool_max, LAG_POOL_MIN); + if (m_time_from_last_teleport > lag_pool_max) { + actionstream << "Player " << m_player->getName() + << " moved too fast; resetting position" + << std::endl; + cheated = true; + } + setBasePosition(m_last_good_position); + } + return cheated; +} + +bool PlayerSAO::getCollisionBox(aabb3f *toset) const +{ + //update collision box + toset->MinEdge = m_prop.collisionbox.MinEdge * BS; + toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS; + + toset->MinEdge += m_base_position; + toset->MaxEdge += m_base_position; + return true; +} + +bool PlayerSAO::getSelectionBox(aabb3f *toset) const +{ + if (!m_prop.is_visible || !m_prop.pointable) { + return false; + } + + toset->MinEdge = m_prop.selectionbox.MinEdge * BS; + toset->MaxEdge = m_prop.selectionbox.MaxEdge * BS; + + return true; +} + +float PlayerSAO::getZoomFOV() const +{ + return m_prop.zoom_fov; +}