Initial commit

This commit is contained in:
2022-10-08 17:16:13 -04:00
commit 385638c5e1
1925 changed files with 872504 additions and 0 deletions

View 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)

View 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> &current_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

View 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> &current_objects,
std::queue<u16> &added_objects);
};
} // namespace server

View 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
View 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
View 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
View 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
View 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
View 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) {}
};

View 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;
}
}

View 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;
};

View 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);
}
}

View 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
View 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
View 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;
};