mirror of
https://github.com/MCLx86/xtreemtest.git
synced 2026-03-10 04:13:28 +01:00
Initial commit
This commit is contained in:
9
src/server/CMakeLists.txt
Normal file
9
src/server/CMakeLists.txt
Normal file
@@ -0,0 +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)
|
||||
184
src/server/activeobjectmgr.cpp
Normal file
184
src/server/activeobjectmgr.cpp
Normal file
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
Minetest
|
||||
Copyright (C) 2010-2018 nerzhul, Loic BLOT <loic.blot@unix-experience.fr>
|
||||
|
||||
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 <log.h>
|
||||
#include "mapblock.h"
|
||||
#include "profiler.h"
|
||||
#include "activeobjectmgr.h"
|
||||
|
||||
namespace server
|
||||
{
|
||||
|
||||
void ActiveObjectMgr::clear(const std::function<bool(ServerActiveObject *, u16)> &cb)
|
||||
{
|
||||
std::vector<u16> objects_to_remove;
|
||||
for (auto &it : m_active_objects) {
|
||||
if (cb(it.second, it.first)) {
|
||||
// Id to be removed from m_active_objects
|
||||
objects_to_remove.push_back(it.first);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove references from m_active_objects
|
||||
for (u16 i : objects_to_remove) {
|
||||
m_active_objects.erase(i);
|
||||
}
|
||||
}
|
||||
|
||||
void ActiveObjectMgr::step(
|
||||
float dtime, const std::function<void(ServerActiveObject *)> &f)
|
||||
{
|
||||
g_profiler->avg("ActiveObjectMgr: SAO count [#]", m_active_objects.size());
|
||||
for (auto &ao_it : m_active_objects) {
|
||||
f(ao_it.second);
|
||||
}
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
bool ActiveObjectMgr::registerObject(ServerActiveObject *obj)
|
||||
{
|
||||
assert(obj); // Pre-condition
|
||||
if (obj->getId() == 0) {
|
||||
u16 new_id = getFreeId();
|
||||
if (new_id == 0) {
|
||||
errorstream << "Server::ActiveObjectMgr::addActiveObjectRaw(): "
|
||||
<< "no free id available" << std::endl;
|
||||
if (obj->environmentDeletes())
|
||||
delete obj;
|
||||
return false;
|
||||
}
|
||||
obj->setId(new_id);
|
||||
} else {
|
||||
verbosestream << "Server::ActiveObjectMgr::addActiveObjectRaw(): "
|
||||
<< "supplied with id " << obj->getId() << std::endl;
|
||||
}
|
||||
|
||||
if (!isFreeId(obj->getId())) {
|
||||
errorstream << "Server::ActiveObjectMgr::addActiveObjectRaw(): "
|
||||
<< "id is not free (" << obj->getId() << ")" << std::endl;
|
||||
if (obj->environmentDeletes())
|
||||
delete obj;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (objectpos_over_limit(obj->getBasePosition())) {
|
||||
v3f p = obj->getBasePosition();
|
||||
warningstream << "Server::ActiveObjectMgr::addActiveObjectRaw(): "
|
||||
<< "object position (" << p.X << "," << p.Y << "," << p.Z
|
||||
<< ") outside maximum range" << std::endl;
|
||||
if (obj->environmentDeletes())
|
||||
delete obj;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_active_objects[obj->getId()] = obj;
|
||||
|
||||
verbosestream << "Server::ActiveObjectMgr::addActiveObjectRaw(): "
|
||||
<< "Added id=" << obj->getId() << "; there are now "
|
||||
<< m_active_objects.size() << " active objects." << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ActiveObjectMgr::removeObject(u16 id)
|
||||
{
|
||||
verbosestream << "Server::ActiveObjectMgr::removeObject(): "
|
||||
<< "id=" << id << std::endl;
|
||||
ServerActiveObject *obj = getActiveObject(id);
|
||||
if (!obj) {
|
||||
infostream << "Server::ActiveObjectMgr::removeObject(): "
|
||||
<< "id=" << id << " not found" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
m_active_objects.erase(id);
|
||||
delete obj;
|
||||
}
|
||||
|
||||
// clang-format on
|
||||
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;
|
||||
const v3f &objectpos = obj->getBasePosition();
|
||||
if (objectpos.getDistanceFromSQ(pos) > r2)
|
||||
continue;
|
||||
|
||||
if (!include_obj_cb || include_obj_cb(obj))
|
||||
result.push_back(obj);
|
||||
}
|
||||
}
|
||||
|
||||
void ActiveObjectMgr::getObjectsInArea(const aabb3f &box,
|
||||
std::vector<ServerActiveObject *> &result,
|
||||
std::function<bool(ServerActiveObject *obj)> include_obj_cb)
|
||||
{
|
||||
for (auto &activeObject : m_active_objects) {
|
||||
ServerActiveObject *obj = activeObject.second;
|
||||
const v3f &objectpos = obj->getBasePosition();
|
||||
if (!box.isPointInside(objectpos))
|
||||
continue;
|
||||
|
||||
if (!include_obj_cb || include_obj_cb(obj))
|
||||
result.push_back(obj);
|
||||
}
|
||||
}
|
||||
|
||||
void ActiveObjectMgr::getAddedActiveObjectsAroundPos(const v3f &player_pos, f32 radius,
|
||||
f32 player_radius, std::set<u16> ¤t_objects,
|
||||
std::queue<u16> &added_objects)
|
||||
{
|
||||
/*
|
||||
Go through the object list,
|
||||
- discard removed/deactivated objects,
|
||||
- discard objects that are too far away,
|
||||
- discard objects that are found in current_objects.
|
||||
- add remaining objects to added_objects
|
||||
*/
|
||||
for (auto &ao_it : m_active_objects) {
|
||||
u16 id = ao_it.first;
|
||||
|
||||
// Get object
|
||||
ServerActiveObject *object = ao_it.second;
|
||||
if (!object)
|
||||
continue;
|
||||
|
||||
if (object->isGone())
|
||||
continue;
|
||||
|
||||
f32 distance_f = object->getBasePosition().getDistanceFrom(player_pos);
|
||||
if (object->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
|
||||
// Discard if too far
|
||||
if (distance_f > player_radius && player_radius != 0)
|
||||
continue;
|
||||
} else if (distance_f > radius)
|
||||
continue;
|
||||
|
||||
// Discard if already on current_objects
|
||||
auto n = current_objects.find(id);
|
||||
if (n != current_objects.end())
|
||||
continue;
|
||||
// Add to added_objects
|
||||
added_objects.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace server
|
||||
49
src/server/activeobjectmgr.h
Normal file
49
src/server/activeobjectmgr.h
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
Minetest
|
||||
Copyright (C) 2010-2018 nerzhul, Loic BLOT <loic.blot@unix-experience.fr>
|
||||
|
||||
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 <functional>
|
||||
#include <vector>
|
||||
#include "../activeobjectmgr.h"
|
||||
#include "serveractiveobject.h"
|
||||
|
||||
namespace server
|
||||
{
|
||||
class ActiveObjectMgr : public ::ActiveObjectMgr<ServerActiveObject>
|
||||
{
|
||||
public:
|
||||
void clear(const std::function<bool(ServerActiveObject *, u16)> &cb);
|
||||
void step(float dtime,
|
||||
const std::function<void(ServerActiveObject *)> &f) override;
|
||||
bool registerObject(ServerActiveObject *obj) override;
|
||||
void removeObject(u16 id) override;
|
||||
|
||||
void getObjectsInsideRadius(const v3f &pos, float radius,
|
||||
std::vector<ServerActiveObject *> &result,
|
||||
std::function<bool(ServerActiveObject *obj)> include_obj_cb);
|
||||
void getObjectsInArea(const aabb3f &box,
|
||||
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,
|
||||
std::queue<u16> &added_objects);
|
||||
};
|
||||
} // namespace server
|
||||
560
src/server/luaentity_sao.cpp
Normal file
560
src/server/luaentity_sao.cpp
Normal file
@@ -0,0 +1,560 @@
|
||||
/*
|
||||
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 = deSerializeString16(is);
|
||||
state = deSerializeString32(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 {
|
||||
// It's an unknown object
|
||||
// Use entitystring as infotext for debugging
|
||||
m_prop.infotext = m_init_name;
|
||||
// Set unknown object texture
|
||||
m_prop.textures.clear();
|
||||
m_prop.textures.emplace_back("unknown_object.png");
|
||||
}
|
||||
}
|
||||
|
||||
void LuaEntitySAO::dispatchScriptDeactivate(bool removal)
|
||||
{
|
||||
// Ensure that this is in fact a registered entity,
|
||||
// and that it isn't already gone.
|
||||
// The latter also prevents this from ever being called twice.
|
||||
if (m_registered && !isGone())
|
||||
m_env->getScriptIface()->luaentity_Deactivate(m_id, removal);
|
||||
}
|
||||
|
||||
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 (auto *parent = getParent()) {
|
||||
m_base_position = parent->getBasePosition();
|
||||
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 << serializeString16(""); // 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 << serializeString32(getPropertyPacket()); // message 1
|
||||
msg_os << serializeString32(generateUpdateArmorGroupsCommand()); // 2
|
||||
msg_os << serializeString32(generateUpdateAnimationCommand()); // 3
|
||||
for (const auto &bone_pos : m_bone_position) {
|
||||
msg_os << serializeString32(generateUpdateBonePositionCommand(
|
||||
bone_pos.first, bone_pos.second.X, bone_pos.second.Y)); // 3 + N
|
||||
}
|
||||
msg_os << serializeString32(generateUpdateAttachmentCommand()); // 4 + m_bone_position.size
|
||||
|
||||
int message_count = 4 + m_bone_position.size();
|
||||
|
||||
for (const auto &id : getAttachmentChildIds()) {
|
||||
if (ServerActiveObject *obj = m_env->getActiveObject(id)) {
|
||||
message_count++;
|
||||
msg_os << serializeString32(obj->generateUpdateInfantCommand(
|
||||
id, protocol_version));
|
||||
}
|
||||
}
|
||||
|
||||
msg_os << serializeString32(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<<serializeString16(m_init_name);
|
||||
// state
|
||||
if(m_registered){
|
||||
std::string state = m_env->getScriptIface()->
|
||||
luaentity_GetStaticdata(m_id);
|
||||
os<<serializeString32(state);
|
||||
} else {
|
||||
os<<serializeString32(m_init_state);
|
||||
}
|
||||
writeU16(os, m_hp);
|
||||
writeV3F1000(os, clampToF1000(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();
|
||||
}
|
||||
|
||||
u32 LuaEntitySAO::punch(v3f dir,
|
||||
const ToolCapabilities *toolcap,
|
||||
ServerActiveObject *puncher,
|
||||
float time_from_last_punch,
|
||||
u16 initial_wear)
|
||||
{
|
||||
if (!m_registered) {
|
||||
// Delete unknown LuaEntities when punched
|
||||
markForRemoval();
|
||||
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,
|
||||
initial_wear);
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
sendPunchCommand();
|
||||
|
||||
if (m_hp == 0 && !isGone()) {
|
||||
clearParentAttachment();
|
||||
clearChildAttachments();
|
||||
if (m_registered) {
|
||||
ServerActiveObject *killer = nullptr;
|
||||
if (reason.type == PlayerHPChangeReason::PLAYER_PUNCH)
|
||||
killer = reason.object;
|
||||
m_env->getScriptIface()->luaentity_on_death(m_id, killer);
|
||||
}
|
||||
markForRemoval();
|
||||
}
|
||||
}
|
||||
|
||||
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 << serializeString16(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;
|
||||
|
||||
// Send attachment updates instantly to the client prior updating position
|
||||
sendOutdatedData();
|
||||
|
||||
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;
|
||||
}
|
||||
107
src/server/luaentity_sao.h
Normal file
107
src/server/luaentity_sao.h
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
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; }
|
||||
bool shouldUnload() const { return true; }
|
||||
void getStaticData(std::string *result) const;
|
||||
|
||||
u32 punch(v3f dir, const ToolCapabilities *toolcap = nullptr,
|
||||
ServerActiveObject *puncher = nullptr,
|
||||
float time_from_last_punch = 1000000.0f,
|
||||
u16 initial_wear = 0);
|
||||
|
||||
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;
|
||||
|
||||
protected:
|
||||
void dispatchScriptDeactivate(bool removal);
|
||||
virtual void onMarkedForDeactivation() { dispatchScriptDeactivate(false); }
|
||||
virtual void onMarkedForRemoval() { dispatchScriptDeactivate(true); }
|
||||
|
||||
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 = "";
|
||||
};
|
||||
108
src/server/mods.cpp
Normal file
108
src/server/mods.cpp
Normal file
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
Minetest
|
||||
Copyright (C) 2018 nerzhul, Loic Blot <loic.blot@unix-experience.fr>
|
||||
|
||||
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 "mods.h"
|
||||
#include "filesys.h"
|
||||
#include "log.h"
|
||||
#include "scripting_server.h"
|
||||
#include "content/subgames.h"
|
||||
#include "porting.h"
|
||||
|
||||
/**
|
||||
* Manage server mods
|
||||
*
|
||||
* All new calls to this class must be tested in test_servermodmanager.cpp
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates a ServerModManager which targets worldpath
|
||||
* @param worldpath
|
||||
*/
|
||||
ServerModManager::ServerModManager(const std::string &worldpath):
|
||||
configuration()
|
||||
{
|
||||
SubgameSpec gamespec = findWorldSubgame(worldpath);
|
||||
|
||||
// Add all game mods and all world mods
|
||||
configuration.addGameMods(gamespec);
|
||||
configuration.addModsInPath(worldpath + DIR_DELIM + "worldmods", "worldmods");
|
||||
|
||||
// Load normal mods
|
||||
std::string worldmt = worldpath + DIR_DELIM + "world.mt";
|
||||
configuration.addModsFromConfig(worldmt, gamespec.addon_mods_paths);
|
||||
configuration.checkConflictsAndDeps();
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
// This function cannot be currenctly easily tested but it should be ASAP
|
||||
void ServerModManager::loadMods(ServerScripting *script)
|
||||
{
|
||||
// Print mods
|
||||
infostream << "Server: Loading mods: ";
|
||||
for (const ModSpec &mod : configuration.getMods()) {
|
||||
infostream << mod.name << " ";
|
||||
}
|
||||
|
||||
infostream << std::endl;
|
||||
// Load and run "mod" scripts
|
||||
for (const ModSpec &mod : configuration.getMods()) {
|
||||
mod.checkAndLog();
|
||||
|
||||
std::string script_path = mod.path + DIR_DELIM + "init.lua";
|
||||
auto t = porting::getTimeMs();
|
||||
script->loadMod(script_path, mod.name);
|
||||
infostream << "Mod \"" << mod.name << "\" loaded after "
|
||||
<< (porting::getTimeMs() - t) << " ms" << std::endl;
|
||||
}
|
||||
|
||||
// Run a callback when mods are loaded
|
||||
script->on_mods_loaded();
|
||||
}
|
||||
|
||||
// clang-format on
|
||||
const ModSpec *ServerModManager::getModSpec(const std::string &modname) const
|
||||
{
|
||||
for (const auto &mod : configuration.getMods()) {
|
||||
if (mod.name == modname)
|
||||
return &mod;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ServerModManager::getModNames(std::vector<std::string> &modlist) const
|
||||
{
|
||||
for (const ModSpec &spec : configuration.getMods())
|
||||
modlist.push_back(spec.name);
|
||||
}
|
||||
|
||||
void ServerModManager::getModsMediaPaths(std::vector<std::string> &paths) const
|
||||
{
|
||||
// Iterate mods in reverse load order: Media loading expects higher priority media files first
|
||||
// and mods loading later should be able to override media of already loaded mods
|
||||
const auto &mods = configuration.getMods();
|
||||
for (auto it = mods.crbegin(); it != mods.crend(); it++) {
|
||||
const ModSpec &spec = *it;
|
||||
fs::GetRecursiveDirs(paths, spec.path + DIR_DELIM + "textures");
|
||||
fs::GetRecursiveDirs(paths, spec.path + DIR_DELIM + "sounds");
|
||||
fs::GetRecursiveDirs(paths, spec.path + DIR_DELIM + "media");
|
||||
fs::GetRecursiveDirs(paths, spec.path + DIR_DELIM + "models");
|
||||
fs::GetRecursiveDirs(paths, spec.path + DIR_DELIM + "locale");
|
||||
}
|
||||
}
|
||||
73
src/server/mods.h
Normal file
73
src/server/mods.h
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
Minetest
|
||||
Copyright (C) 2018 nerzhul, Loic Blot <loic.blot@unix-experience.fr>
|
||||
|
||||
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 "content/mod_configuration.h"
|
||||
#include <memory>
|
||||
|
||||
class MetricsBackend;
|
||||
class MetricCounter;
|
||||
class ServerScripting;
|
||||
|
||||
/**
|
||||
* Manage server mods
|
||||
*
|
||||
* All new calls to this class must be tested in test_servermodmanager.cpp
|
||||
*/
|
||||
class ServerModManager
|
||||
{
|
||||
ModConfiguration configuration;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Creates a ServerModManager which targets worldpath
|
||||
* @param worldpath
|
||||
*/
|
||||
ServerModManager(const std::string &worldpath);
|
||||
void loadMods(ServerScripting *script);
|
||||
const ModSpec *getModSpec(const std::string &modname) const;
|
||||
void getModNames(std::vector<std::string> &modlist) const;
|
||||
|
||||
inline const std::vector<ModSpec> &getMods() const {
|
||||
return configuration.getMods();
|
||||
}
|
||||
|
||||
inline const std::vector<ModSpec> &getUnsatisfiedMods() const {
|
||||
return configuration.getUnsatisfiedMods();
|
||||
}
|
||||
|
||||
inline bool isConsistent() const {
|
||||
return configuration.isConsistent();
|
||||
}
|
||||
|
||||
inline void printUnsatisfiedModsError() const {
|
||||
return configuration.printUnsatisfiedModsError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively gets all paths of mod folders that can contain media files.
|
||||
*
|
||||
* Result is ordered in descending priority, ie. files from an earlier path
|
||||
* should not be replaced by files from a latter one.
|
||||
*
|
||||
* @param paths result vector
|
||||
*/
|
||||
void getModsMediaPaths(std::vector<std::string> &paths) const;
|
||||
};
|
||||
679
src/server/player_sao.cpp
Normal file
679
src/server/player_sao.cpp
Normal file
@@ -0,0 +1,679 @@
|
||||
/*
|
||||
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_prop.show_on_minimap = true;
|
||||
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 << serializeString16(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 << serializeString32(getPropertyPacket()); // message 1
|
||||
msg_os << serializeString32(generateUpdateArmorGroupsCommand()); // 2
|
||||
msg_os << serializeString32(generateUpdateAnimationCommand()); // 3
|
||||
for (const auto &bone_pos : m_bone_position) {
|
||||
msg_os << serializeString32(generateUpdateBonePositionCommand(
|
||||
bone_pos.first, bone_pos.second.X, bone_pos.second.Y)); // 3 + N
|
||||
}
|
||||
msg_os << serializeString32(generateUpdateAttachmentCommand()); // 4 + m_bone_position.size
|
||||
msg_os << serializeString32(generateUpdatePhysicsOverrideCommand()); // 5 + m_bone_position.size
|
||||
|
||||
int message_count = 5 + m_bone_position.size();
|
||||
|
||||
for (const auto &id : getAttachmentChildIds()) {
|
||||
if (ServerActiveObject *obj = m_env->getActiveObject(id)) {
|
||||
message_count++;
|
||||
msg_os << serializeString32(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("This function shall not be called for PlayerSAO");
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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 (auto *parent = getParent()) {
|
||||
v3f pos = parent->getBasePosition();
|
||||
m_last_good_position = pos;
|
||||
setBasePosition(pos);
|
||||
|
||||
if (m_player)
|
||||
m_player->setSpeed(v3f());
|
||||
}
|
||||
|
||||
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(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 = getBasePosition();
|
||||
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 = getBasePosition();
|
||||
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);
|
||||
}
|
||||
|
||||
u32 PlayerSAO::punch(v3f dir,
|
||||
const ToolCapabilities *toolcap,
|
||||
ServerActiveObject *puncher,
|
||||
float time_from_last_punch,
|
||||
u16 initial_wear)
|
||||
{
|
||||
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, initial_wear);
|
||||
|
||||
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::rightClick(ServerActiveObject *clicker)
|
||||
{
|
||||
m_env->getScriptIface()->on_rightclickplayer(this, clicker);
|
||||
}
|
||||
|
||||
void PlayerSAO::setHP(s32 target_hp, const PlayerHPChangeReason &reason, bool from_client)
|
||||
{
|
||||
target_hp = rangelim(target_hp, 0, U16_MAX);
|
||||
|
||||
if (target_hp == m_hp)
|
||||
return; // Nothing to do
|
||||
|
||||
s32 hp_change = m_env->getScriptIface()->on_player_hpchange(this, target_hp - (s32)m_hp, reason);
|
||||
|
||||
s32 hp = (s32)m_hp + std::min(hp_change, U16_MAX); // Protection against s32 overflow
|
||||
hp = rangelim(hp, 0, U16_MAX);
|
||||
|
||||
if (hp > m_prop.hp_max)
|
||||
hp = m_prop.hp_max;
|
||||
|
||||
if (hp < m_hp && isImmortal())
|
||||
hp = m_hp; // Do not allow immortal players to be damaged
|
||||
|
||||
// Update properties on death
|
||||
if ((hp == 0) != (m_hp == 0))
|
||||
m_properties_sent = false;
|
||||
|
||||
if (hp != m_hp) {
|
||||
m_hp = hp;
|
||||
m_env->getGameDef()->HandlePlayerHPChange(this, reason);
|
||||
} else if (from_client)
|
||||
m_env->getGameDef()->SendPlayerHP(this, true);
|
||||
}
|
||||
|
||||
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;
|
||||
markForRemoval();
|
||||
}
|
||||
|
||||
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 (m_is_singleplayer ||
|
||||
isAttached() ||
|
||||
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;
|
||||
}
|
||||
306
src/server/player_sao.h
Normal file
306
src/server/player_sao.h
Normal file
@@ -0,0 +1,306 @@
|
||||
/*
|
||||
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 override { return ACTIVEOBJECT_TYPE_PLAYER; }
|
||||
ActiveObjectType getSendType() const override { return ACTIVEOBJECT_TYPE_GENERIC; }
|
||||
std::string getDescription() override;
|
||||
|
||||
/*
|
||||
Active object <-> environment interface
|
||||
*/
|
||||
|
||||
void addedToEnvironment(u32 dtime_s) override;
|
||||
void removingFromEnvironment() override;
|
||||
bool isStaticAllowed() const override { return false; }
|
||||
bool shouldUnload() const override { return false; }
|
||||
std::string getClientInitializationData(u16 protocol_version) override;
|
||||
void getStaticData(std::string *result) const override;
|
||||
void step(float dtime, bool send_recommended) override;
|
||||
void setBasePosition(v3f position);
|
||||
void setPos(const v3f &pos) override;
|
||||
void moveTo(v3f pos, bool continuous) override;
|
||||
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
|
||||
*/
|
||||
|
||||
u32 punch(v3f dir, const ToolCapabilities *toolcap, ServerActiveObject *puncher,
|
||||
float time_from_last_punch, u16 initial_wear = 0) override;
|
||||
void rightClick(ServerActiveObject *clicker) override;
|
||||
void setHP(s32 hp, const PlayerHPChangeReason &reason) override
|
||||
{
|
||||
return setHP(hp, reason, false);
|
||||
}
|
||||
void setHP(s32 hp, const PlayerHPChangeReason &reason, bool from_client);
|
||||
void setHPRaw(u16 hp) { m_hp = hp; }
|
||||
u16 getBreath() const { return m_breath; }
|
||||
void setBreath(const u16 breath, bool send = true);
|
||||
|
||||
/*
|
||||
Inventory interface
|
||||
*/
|
||||
Inventory *getInventory() const override;
|
||||
InventoryLocation getInventoryLocation() const override;
|
||||
void setInventoryModified() override {}
|
||||
std::string getWieldList() const override { return "main"; }
|
||||
u16 getWieldIndex() const override;
|
||||
ItemStack getWieldedItem(ItemStack *selected, ItemStack *hand = nullptr) const override;
|
||||
bool setWieldedItem(const ItemStack &item) override;
|
||||
|
||||
/*
|
||||
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 override;
|
||||
bool getSelectionBox(aabb3f *toset) const override;
|
||||
bool collideWithObjects() const override { 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,
|
||||
SET_HP_MAX, // internal type to allow distinguishing hp reset and damage (for effects)
|
||||
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:
|
||||
case PlayerHPChangeReason::SET_HP_MAX:
|
||||
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) {}
|
||||
};
|
||||
91
src/server/serveractiveobject.cpp
Normal file
91
src/server/serveractiveobject.cpp
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
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 << serializeString32(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();
|
||||
}
|
||||
}
|
||||
|
||||
void ServerActiveObject::markForRemoval()
|
||||
{
|
||||
if (!m_pending_removal) {
|
||||
onMarkedForRemoval();
|
||||
m_pending_removal = true;
|
||||
}
|
||||
}
|
||||
|
||||
void ServerActiveObject::markForDeactivation()
|
||||
{
|
||||
if (!m_pending_deactivation) {
|
||||
onMarkedForDeactivation();
|
||||
m_pending_deactivation = true;
|
||||
}
|
||||
}
|
||||
274
src/server/serveractiveobject.h
Normal file
274
src/server/serveractiveobject.h
Normal file
@@ -0,0 +1,274 @@
|
||||
/*
|
||||
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; }
|
||||
|
||||
// Safely mark the object for removal or deactivation
|
||||
void markForRemoval();
|
||||
void markForDeactivation();
|
||||
|
||||
// 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;}
|
||||
|
||||
/*
|
||||
Return false here to never unload the object.
|
||||
isStaticAllowed && shouldUnload -> unload when out of active block range
|
||||
!isStaticAllowed && shouldUnload -> unload when block is unloaded
|
||||
*/
|
||||
virtual bool shouldUnload() const
|
||||
{ return true; }
|
||||
|
||||
// Returns added tool wear
|
||||
virtual u32 punch(v3f dir,
|
||||
const ToolCapabilities *toolcap = nullptr,
|
||||
ServerActiveObject *puncher = nullptr,
|
||||
float time_from_last_punch = 1000000.0f,
|
||||
u16 initial_wear = 0)
|
||||
{ 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 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);
|
||||
|
||||
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;
|
||||
|
||||
/*
|
||||
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; }
|
||||
|
||||
inline bool isPendingRemoval() const
|
||||
{ return m_pending_removal; }
|
||||
|
||||
/*
|
||||
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 onMarkedForDeactivation() {}
|
||||
virtual void onMarkedForRemoval() {}
|
||||
|
||||
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;
|
||||
|
||||
/*
|
||||
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.
|
||||
Note: Do not assign this directly, use markForDeactivation() instead.
|
||||
*/
|
||||
bool m_pending_deactivation = false;
|
||||
|
||||
/*
|
||||
- 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.
|
||||
Note: Do not assign this directly, use markForRemoval() instead.
|
||||
*/
|
||||
bool m_pending_removal = false;
|
||||
|
||||
/*
|
||||
Queue of messages to be sent to the client
|
||||
*/
|
||||
std::queue<ActiveObjectMessage> m_messages_out;
|
||||
};
|
||||
210
src/server/serverinventorymgr.cpp
Normal file
210
src/server/serverinventorymgr.cpp
Normal file
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
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)
|
||||
{
|
||||
// No m_env check here: allow creation and modification of detached inventories
|
||||
|
||||
switch (loc.type) {
|
||||
case InventoryLocation::UNDEFINED:
|
||||
case InventoryLocation::CURRENT_PLAYER:
|
||||
break;
|
||||
case InventoryLocation::PLAYER: {
|
||||
if (!m_env)
|
||||
return nullptr;
|
||||
|
||||
RemotePlayer *player = m_env->getPlayer(loc.name.c_str());
|
||||
if (!player)
|
||||
return NULL;
|
||||
|
||||
PlayerSAO *playersao = player->getPlayerSAO();
|
||||
return playersao ? playersao->getInventory() : nullptr;
|
||||
} break;
|
||||
case InventoryLocation::NODEMETA: {
|
||||
if (!m_env)
|
||||
return nullptr;
|
||||
|
||||
NodeMetadata *meta = m_env->getMap().getNodeMetadata(loc.p);
|
||||
return meta ? meta->getInventory() : nullptr;
|
||||
} 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()) {
|
||||
if (m_env) {
|
||||
RemotePlayer *player = m_env->getPlayer(owner.c_str());
|
||||
|
||||
if (player && player->getPeerId() != PEER_ID_INEXISTENT)
|
||||
m_env->getGameDef()->sendDetachedInventory(
|
||||
nullptr, name, player->getPeerId());
|
||||
}
|
||||
} else if (m_env) {
|
||||
// Notify all players about the change as soon ServerEnv exists
|
||||
m_env->getGameDef()->sendDetachedInventory(
|
||||
nullptr, name, PEER_ID_INEXISTENT);
|
||||
}
|
||||
|
||||
m_detached_inventories.erase(inv_it);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ServerInventoryManager::checkDetachedInventoryAccess(
|
||||
const InventoryLocation &loc, const std::string &player) const
|
||||
{
|
||||
SANITY_CHECK(loc.type == InventoryLocation::DETACHED);
|
||||
|
||||
const auto &inv_it = m_detached_inventories.find(loc.name);
|
||||
if (inv_it == m_detached_inventories.end())
|
||||
return false;
|
||||
|
||||
return inv_it->second.owner.empty() || inv_it->second.owner == player;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
61
src/server/serverinventorymgr.h
Normal file
61
src/server/serverinventorymgr.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
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);
|
||||
bool checkDetachedInventoryAccess(const InventoryLocation &loc, const std::string &player) const;
|
||||
|
||||
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;
|
||||
};
|
||||
369
src/server/unit_sao.cpp
Normal file
369
src/server/unit_sao.cpp
Normal file
@@ -0,0 +1,369 @@
|
||||
/*
|
||||
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)
|
||||
{
|
||||
auto it = m_bone_position.find(bone);
|
||||
if (it != m_bone_position.end()) {
|
||||
*position = it->second.X;
|
||||
*rotation = it->second.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, bool force_visible)
|
||||
{
|
||||
auto *obj = parent_id ? m_env->getActiveObject(parent_id) : nullptr;
|
||||
if (obj) {
|
||||
// Do checks to avoid circular references
|
||||
// The chain of wanted parent must not refer or contain "this"
|
||||
for (obj = obj->getParent(); obj; obj = obj->getParent()) {
|
||||
if (obj == this) {
|
||||
warningstream << "Mod bug: Attempted to attach object " << m_id << " to parent "
|
||||
<< parent_id << " but former is an (in)direct parent of latter." << std::endl;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
// The detach callbacks might call to setAttachment() again.
|
||||
// Ensure the attachment params are applied after this callback is run.
|
||||
if (parent_id != old_parent)
|
||||
onDetach(old_parent);
|
||||
|
||||
m_attachment_parent_id = parent_id;
|
||||
m_attachment_bone = bone;
|
||||
m_attachment_position = position;
|
||||
m_attachment_rotation = rotation;
|
||||
m_force_visible = force_visible;
|
||||
m_attachment_sent = false;
|
||||
|
||||
if (parent_id != old_parent)
|
||||
onAttach(parent_id);
|
||||
}
|
||||
|
||||
void UnitSAO::getAttachment(int *parent_id, std::string *bone, v3f *position,
|
||||
v3f *rotation, bool *force_visible) const
|
||||
{
|
||||
*parent_id = m_attachment_parent_id;
|
||||
*bone = m_attachment_bone;
|
||||
*position = m_attachment_position;
|
||||
*rotation = m_attachment_rotation;
|
||||
*force_visible = m_force_visible;
|
||||
}
|
||||
|
||||
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), false);
|
||||
}
|
||||
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, false);
|
||||
} else {
|
||||
setAttachment(0, "", v3f(0, 0, 0), v3f(0, 0, 0), false);
|
||||
}
|
||||
// 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 << serializeString16(m_attachment_bone);
|
||||
writeV3F32(os, m_attachment_position);
|
||||
writeV3F32(os, m_attachment_rotation);
|
||||
writeU8(os, m_force_visible);
|
||||
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 << serializeString16(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 << serializeString16(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()));
|
||||
}
|
||||
137
src/server/unit_sao.h
Normal file
137
src/server/unit_sao.h
Normal file
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
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, bool force_visible);
|
||||
void getAttachment(int *parent_id, std::string *bone, v3f *position,
|
||||
v3f *rotation, bool *force_visible) 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;
|
||||
bool m_force_visible = false;
|
||||
};
|
||||
Reference in New Issue
Block a user