voxelands/src/content_sao.cpp

1542 lines
40 KiB
C++

/************************************************************************
* Minetest-c55
* Copyright (C) 2010-2011 celeron55, Perttu Ahola <celeron55@gmail.com>
*
* content_sao.cpp
* voxelands - 3d voxel world sandbox game
* Copyright (C) Lisa 'darkrose' Milne 2013-2014 <lisa@ltmnet.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
* License updated from GPLv2 or later to GPLv3 or later by Lisa Milne
* for Voxelands.
************************************************************************/
#include "common.h"
#include "content_sao.h"
#include "content_mob.h"
#include "collision.h"
#include "environment.h"
#include "profiler.h"
#include "nodemetadata.h"
#include "mapblock.h"
#include "array.h"
core::map<u16, ServerActiveObject::Factory> ServerActiveObject::m_types;
/* Some helper functions */
// Y is copied, X and Z change is limited
void accelerate_xz(v3f &speed, v3f target_speed, f32 max_increase)
{
v3f d_wanted = target_speed - speed;
d_wanted.Y = 0;
f32 dl_wanted = d_wanted.getLength();
f32 dl = dl_wanted;
if(dl > max_increase)
dl = max_increase;
v3f d = d_wanted.normalize() * dl;
speed.X += d.X;
speed.Z += d.Z;
speed.Y = target_speed.Y;
}
static void get_random_u32_array(u32 a[], u32 len)
{
u32 i, n;
for(i=0; i<len; i++)
a[i] = i;
n = len;
while(n > 1){
u32 k = myrand() % n;
n--;
u32 temp = a[n];
a[n] = a[k];
a[k] = temp;
}
}
#define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")"
/*
MobSAO
*/
// Prototype
MobSAO proto_MobSAO(NULL, 0, v3f(0,0,0), CONTENT_IGNORE);
MobSAO::MobSAO(ServerEnvironment *env, u16 id, v3f pos, content_t type):
ServerActiveObject(env, id, pos),
m_content(type),
m_speed(0,0,0),
m_last_sent_position(0,0,0),
m_oldpos(0,0,0),
m_initial_pos(pos),
m_yaw(0),
m_falling(false),
m_next_pos_exists(false),
m_hp(10),
m_angry(false),
m_special_count(0),
m_tamed_chance(0),
m_disturb_timer(100000),
m_random_disturb_timer(0),
m_walk_around(false),
m_walk_around_timer(0),
m_shoot_reload_timer(0),
m_shooting(false),
m_shooting_timer(0),
m_shoot_y(0),
m_last_sound(0)
{
ServerActiveObject::registerType(getType(), create);
if ((type&CONTENT_MOB_MASK) == CONTENT_MOB_MASK) {
m_hp = content_mob_features(type).hp;
m_special_count = content_mob_features(type).special_dropped_max;
m_tamed_chance = content_mob_features(type).level*5;
}
}
MobSAO::MobSAO(ServerEnvironment *env, u16 id, v3f pos, v3f speed, content_t type):
ServerActiveObject(env, id, pos),
m_content(type),
m_speed(speed),
m_last_sent_position(0,0,0),
m_oldpos(0,0,0),
m_initial_pos(pos),
m_yaw(0),
m_falling(false),
m_next_pos_exists(false),
m_hp(10),
m_angry(false),
m_special_count(0),
m_tamed_chance(0),
m_disturb_timer(100000),
m_random_disturb_timer(0),
m_walk_around(false),
m_walk_around_timer(0),
m_shoot_reload_timer(0),
m_shooting(false),
m_shooting_timer(0),
m_shoot_y(0),
m_last_sound(0)
{
ServerActiveObject::registerType(getType(), create);
if ((type&CONTENT_MOB_MASK) == CONTENT_MOB_MASK) {
m_hp = content_mob_features(type).hp;
m_special_count = content_mob_features(type).special_dropped_max;
m_tamed_chance = content_mob_features(type).level*5;
}
}
MobSAO::~MobSAO()
{
}
ServerActiveObject* MobSAO::create(ServerEnvironment *env, u16 id, v3f pos, const std::string &data)
{
std::istringstream is(data, std::ios::binary);
char buf[1];
// read version
is.read(buf, 1);
u8 version = buf[0];
// check if version is supported
if (version > 1)
return NULL;
v3f p = readV3F1000(is);
content_t c = readU16(is);
c = content_mob_features(c).content;
if (c == CONTENT_IGNORE)
return NULL;
MobSAO *o = new MobSAO(env,id,pos,c);
o->m_base_position = p;
o->m_yaw = readF1000(is);
o->m_speed = readV3F1000(is);
// this was age
if (version == 0)
readF1000(is);
o->m_hp = readU8(is);
return o;
}
std::string MobSAO::getStaticData()
{
std::ostringstream os(std::ios::binary);
// version
writeU8(os, 1);
// pos
writeV3F1000(os, m_base_position);
// content
writeU16(os,m_content);
// yaw
writeF1000(os,m_yaw);
// speed
writeV3F1000(os, m_speed);
// hp
writeU8(os,m_hp);
// shooting
writeU8(os,(u8)m_shooting);
return os.str();
}
std::string MobSAO::getClientInitializationData()
{
std::ostringstream os(std::ios::binary);
// version
writeU8(os, 1);
// pos
writeV3F1000(os, m_base_position);
// content
writeU16(os,m_content);
// yaw
writeF1000(os,m_yaw);
// shooting
writeU8(os,(u8)m_shooting);
return os.str();
}
void MobSAO::step(float dtime, bool send_recommended)
{
MobFeatures m = content_mob_features(m_content);
Player *disturbing_player = NULL;
v3f disturbing_player_off = v3f(0,1,0);
v3f disturbing_player_norm = v3f(0,1,0);
float disturbing_player_distance = 1000000;
float disturbing_player_dir = 0;
bool dont_move = false;
bool notices_player = false;
bool following = false;
/* don't do anything if there's no nearby player */
if (m_disturbing_player == "") {
float distance = 40*BS;
array_t *players = m_env->getPlayers(true);
Player *player;
uint32_t i;
for (i=0; i<players->length; i++) {
player = (Player*)array_get_ptr(players,i);
if (!player)
continue;
v3f playerpos = player->getPosition();
f32 dist = m_base_position.getDistanceFrom(playerpos);
if (dist < distance)
distance = dist;
}
array_free(players,1);
if (distance > 32*BS) {
/* kill of anything that shouldn't be on its own */
if (
m.level == MOB_AGGRESSIVE
|| (m.motion == MM_THROWN || m.motion == MM_CONSTANT || m.motion == MM_STATIC)
)
m_removed = true;
return;
}
if (distance > 16*BS && myrand_range(0,10) != 0)
dont_move = true;
}
if (m.follow_item != CONTENT_IGNORE || m.motion == MM_SEEKER || m.angry_motion == MM_SEEKER || m.angry_motion == MM_FLEE)
notices_player = true;
/* if it isn't a swimmer, kill it in liquid */
if (m.motion_type != MMT_SWIM) {
v3s16 p = floatToInt(m_base_position,BS);
MapNode n = m_env->getMap().getNodeNoEx(p);
if (content_features(n).liquid_type != LIQUID_NONE) {
m_hp--;
if (m_hp < 1) {
m_removed = true;
return;
}
}
}else{
v3s16 p = floatToInt(m_base_position,BS);
MapNode n = m_env->getMap().getNodeNoEx(p);
if (content_features(n).liquid_type == LIQUID_NONE) {
m_hp--;
if (m_hp < 1) {
m_removed = true;
return;
}
}
}
m_last_sound += dtime;
if (m_last_sound > 30.0) {
m_last_sound -= 5.0;
if (m.sound_random != "" && myrand_range(0,10) == 0) {
if (m.sound_random_extra != "" && myrand_range(0,100) == 0) {
m_env->addEnvEvent(ENV_EVENT_SOUND,m_base_position,m.sound_random_extra);
}else{
m_env->addEnvEvent(ENV_EVENT_SOUND,m_base_position,m.sound_random);
}
m_last_sound -= 30.0;
}
}
if (m.special_dropped_max > 0 && m_special_count < m.special_dropped_max && myrand_range(0,5000) == 0)
m_special_count++;
m_random_disturb_timer += dtime;
if (notices_player) {
if (m_random_disturb_timer >= 5.0) {
m_random_disturb_timer = 0;
if (
m_disturbing_player == ""
|| m_base_position.getDistanceFrom(m_env->getPlayer(m_disturbing_player.c_str())->getPosition()) > BS*16
) {
m_disturbing_player = "";
// Check connected players
array_t *players = m_env->getPlayers(true);
Player *player;
uint32_t i;
for (i=0; i<players->length; i++) {
player = (Player*)array_get_ptr(players,i);
if (!player)
continue;
v3f playerpos = player->getPosition();
f32 dist = m_base_position.getDistanceFrom(playerpos);
if (dist < BS*16) {
if (dist < BS*8 || myrand_range(0,2) == 0) {
vlprintf(
CN_ACTION,
(char*)"mob (%u) at (%f,%f,%f) was randomly disturbed by %s",
m_id,
m_base_position.X/BS,
m_base_position.Y/BS,
m_base_position.Z/BS,
player->getName()
);
m_disturbing_player = player->getName();
m_disturb_timer = 0;
break;
}
}
}
array_free(players,1);
}
}
if (m_disturbing_player != "") {
disturbing_player = m_env->getPlayer(m_disturbing_player.c_str());
if (disturbing_player) {
disturbing_player_off = disturbing_player->getPosition() - m_base_position;
disturbing_player_distance = disturbing_player_off.getLength();
disturbing_player_norm = disturbing_player_off;
disturbing_player_norm.normalize();
disturbing_player_dir = 180./PI*atan2(disturbing_player_norm.Z,disturbing_player_norm.X);
if (m.follow_item != CONTENT_IGNORE && !dont_move) {
u16 item_i = disturbing_player->getSelectedItem();
InventoryList *ilist = disturbing_player->inventory.getList("main");
if (ilist != NULL) {
InventoryItem *item = ilist->getItem(item_i);
if (item != NULL && item->getContent() == m.follow_item)
following = true;
}
}
}
}else if (m_angry) {
m_angry = false;
}else if (m.follow_item != CONTENT_IGNORE && !dont_move && myrand_range(0,100) == 0) {
core::array<DistanceSortedActiveObject> objects;
f32 range = 32*BS;
m_env->getActiveObjects(m_base_position, range, objects);
// Sort them.
// After this, the closest object is the first in the array.
objects.sort();
if (objects.size() < 3) {
for (u32 i=0; i<objects.size(); i++) {
ServerActiveObject *obj = (ServerActiveObject*)objects[i].obj;
if (obj->getId() == m_id)
continue;
if (obj->getType() == ACTIVEOBJECT_TYPE_MOB) {
MobSAO *mob = (MobSAO*)obj;
if (mob->getContent() == m_content) {
v3s16 p = floatToInt(m_base_position,BS);
mob_spawn(p,m_content,m_env);
}
}
}
}
}
m_disturb_timer += dtime;
if (!m_falling && m.attack_throw_object != CONTENT_IGNORE) {
m_shooting_timer -= dtime;
if (m_shooting_timer <= 0.0 && m_shooting) {
m_shooting = false;
v3f shoot_pos = m.attack_throw_offset * BS;
v3f dir(cos(m_yaw/180*PI),0,sin(m_yaw/180*PI));
dir.Y = m_shoot_y;
dir.normalize();
v3f speed = dir * BS * 10.0;
v3f pos = m_base_position + shoot_pos;
ServerActiveObject *obj = new MobSAO(m_env, 0, pos, speed, m.attack_throw_object);
m_env->addActiveObject(obj);
}
m_shoot_reload_timer += dtime;
float reload_time = 15.0;
if (m_disturb_timer <= 15.0)
reload_time = 3.0;
if (
!m_shooting
&& m_shoot_reload_timer >= reload_time
&& !m_next_pos_exists
&& m_disturb_timer <= 60.0
) {
m_shoot_y = 0;
if (
m_disturb_timer < 60.0
&& disturbing_player
&& disturbing_player_distance < 16*BS
&& fabs(disturbing_player_norm.Y) < 0.8
) {
m_yaw = disturbing_player_dir;
sendPosition();
m_shoot_y += disturbing_player_norm.Y;
}else{
m_shoot_y = 0.01 * myrand_range(-30,10);
}
m_shoot_reload_timer = 0.0;
m_shooting = true;
m_shooting_timer = 1.5;
{
std::ostringstream os(std::ios::binary);
// command (2 = shooting)
writeU8(os, 2);
// time
writeF1000(os, m_shooting_timer + 0.1);
// create message and add to list
ActiveObjectMessage aom(getId(), false, os.str());
m_messages_out.push_back(aom);
}
}
}
}
if (m.attack_mob_damage > 0) {
core::array<DistanceSortedActiveObject> objects;
f32 range = m.attack_mob_range.X;
if (m.attack_mob_range.Y > range)
range = m.attack_mob_range.Y;
if (m.attack_mob_range.Z > range)
range = m.attack_mob_range.Z;
m_env->getActiveObjects(m_base_position, range*BS, objects);
// Sort them.
// After this, the closest object is the first in the array.
objects.sort();
bool hit = false;
for (u32 i=0; i<objects.size() && !hit; i++) {
ServerActiveObject *obj = (ServerActiveObject*)objects[i].obj;
if (obj->getId() == m_id)
continue;
if (obj->getType() == ACTIVEOBJECT_TYPE_MOB) {
((MobSAO*)obj)->doDamage(m.attack_mob_damage);
hit = true;
}
}
if (hit)
m_removed = true;
}
if (!dont_move) {
MobMotion mot = getMotion();
if (mot != MM_CONSTANT && mot != MM_STATIC) {
m_walk_around_timer -= dtime;
if (m_walk_around_timer <= 0.0) {
if (m.motion_type == MMT_FLY || (disturbing_player && (mot == MM_SEEKER || mot == MM_FLEE))) {
if (!m_walk_around) {
m_walk_around_timer = 0.2;
m_walk_around = true;
}
}else{
m_walk_around = !m_walk_around;
if (m_walk_around) {
if ((!notices_player && !disturbing_player) || mot != MM_SEEKER)
m_walk_around_timer = myrand_range(10,20);
}else{
m_walk_around_timer = myrand_range(10,20);
}
}
}else if (m_walk_around_timer > 10.0) {
m_walk_around_timer = 0.2;
m_walk_around = true;
}
if (m_next_pos_exists) {
v3f pos_f = m_base_position;
v3f next_pos_f = intToFloat(m_next_pos_i, BS);
v3f diff = next_pos_f - pos_f;
v3f dir = diff;
dir.normalize();
float speed = BS;
if (((mot == MM_SEEKER && m.level == MOB_AGGRESSIVE) || mot == MM_FLEE) && disturbing_player)
speed = BS * 3.0;
if (m_falling)
speed = BS * 3.0;
dir *= dtime * speed;
bool arrived = false;
if (dir.getLength() > diff.getLength()) {
dir = diff;
arrived = true;
}
pos_f += dir;
m_yaw = wrapDegrees_180(180./PI*atan2(dir.Z, dir.X));
m_base_position = pos_f;
if ((pos_f - next_pos_f).getLength() < 0.1 || arrived)
m_next_pos_exists = false;
}
}
mot = getMotion();
if (mot == MM_WANDER) {
if (following) {
stepMotionSeeker(dtime,1.5);
}else{
stepMotionWander(dtime);
}
}else if (mot == MM_FLEE) {
stepMotionFlee(dtime);
}else if (mot == MM_SEEKER) {
if (!disturbing_player) {
stepMotionWander(dtime);
}else{
stepMotionSeeker(dtime,0.5);
}
}else if (mot == MM_SENTRY) {
stepMotionSentry(dtime);
}else if (mot == MM_THROWN) {
stepMotionThrown(dtime);
}else if (mot == MM_CONSTANT) {
stepMotionConstant(dtime);
}
}
if (send_recommended == false)
return;
if (m_base_position.getDistanceFrom(m_last_sent_position) > 0.5*BS)
sendPosition();
}
void MobSAO::stepMotionWander(float dtime)
{
MobFeatures m = content_mob_features(m_content);
v3s16 pos_i = floatToInt(m_base_position, BS);
v3s16 pos_size_off(0,0,0);
if (m.motion_type == MMT_WALK) {
if (!m_next_pos_exists) {
/* Check whether to drop down */
if (checkFreePosition(pos_i + pos_size_off + v3s16(0,-1,0))) {
m_next_pos_i = pos_i + v3s16(0,-1,0);
m_next_pos_exists = true;
m_falling = true;
}else{
m_falling = false;
}
}
if (m_walk_around && !m_next_pos_exists) {
/* Find some position where to go next */
v3s16 dps[3*3*3];
int num_dps = 0;
for (int dx=-1; dx<=1; dx++)
for (int dy=-1; dy<=1; dy++)
for (int dz=-1; dz<=1; dz++) {
if (dx == 0 && dz == 0)
continue;
if (dx != 0 && dz != 0 && dy != 0)
continue;
dps[num_dps++] = v3s16(dx,dy,dz);
}
u32 order[3*3*3];
get_random_u32_array(order, num_dps);
v3s16 op = floatToInt(m_oldpos,BS);
for (int i=0; i<num_dps; i++) {
v3s16 p = dps[order[i]] + pos_i;
if (p == op)
continue;
if (!checkFreeAndWalkablePosition(p + pos_size_off))
continue;
m_next_pos_i = p;
m_next_pos_exists = true;
break;
}
}
}else if (m.motion_type == MMT_FLY) {
bool falling = false;
bool raising = false;
if (!m_next_pos_exists) {
u16 above;
v3s16 p = pos_i + pos_size_off;
for (above=0; above < 14; above++) {
p.Y--;
if (!checkFreePosition(p))
break;
}
if (above > 12) {
/* Check whether to drop down */
if (checkFreePosition(pos_i + pos_size_off + v3s16(0,-1,0))) {
m_next_pos_i = pos_i + v3s16(0,-1,0);
falling = true;
}
}else if (above < 8) {
/* Check whether to rise up */
if (checkFreePosition(pos_i + pos_size_off + v3s16(0,1,0))) {
m_next_pos_i = pos_i + v3s16(0,1,0);
raising = true;
}
}
}
if (m_walk_around && !m_next_pos_exists) {
/* Find some position where to go next */
v3s16 dps[3*3*3];
int num_dps = 0;
for (int dx=-1; dx<=1; dx++)
for (int dy=-1; dy<=1; dy++)
for (int dz=-1; dz<=1; dz++) {
if (dx == 0 && dy == 0)
continue;
if (dx != 0 && dz != 0 && dy != 0)
continue;
if (falling && dy > 0)
continue;
if (raising && dy < 0)
continue;
dps[num_dps++] = v3s16(dx,dy,dz);
}
u32 order[3*3*3];
get_random_u32_array(order, num_dps);
v3s16 op = floatToInt(m_oldpos,BS);
for (int i=0; i<num_dps; i++) {
v3s16 p = dps[order[i]] + pos_i;
if (p == op)
continue;
if (!checkFreePosition(p + pos_size_off))
continue;
m_next_pos_i = p;
m_next_pos_exists = true;
break;
}
}
}else if (m.motion_type == MMT_FLYLOW || m.motion_type == MMT_SWIM) {
bool falling = false;
bool raising = false;
if (!m_next_pos_exists) {
u16 above;
v3s16 p = pos_i + pos_size_off;
for (above=0; above < 6; above++) {
p.Y--;
if (!checkFreePosition(p))
break;
}
if (!above) {
m_next_pos_i = pos_i + v3s16(0,1,0);
raising = true;
}else if (above > 5) {
/* Check whether to drop down */
if (checkFreePosition(pos_i + pos_size_off + v3s16(0,-1,0))) {
m_next_pos_i = pos_i + v3s16(0,-1,0);
falling = true;
}
}else if (above < 2) {
/* Check whether to rise up */
if (checkFreePosition(pos_i + pos_size_off + v3s16(0,1,0))) {
m_next_pos_i = pos_i + v3s16(0,1,0);
raising = true;
}
}
}
if (m_walk_around && !m_next_pos_exists) {
/* Find some position where to go next */
v3s16 dps[3*3*3];
int num_dps = 0;
for (int dx=-1; dx<=1; dx++)
for (int dy=-1; dy<=1; dy++)
for (int dz=-1; dz<=1; dz++) {
if (dx == 0 && dy == 0)
continue;
if (dx != 0 && dz != 0 && dy != 0)
continue;
if (falling && dy > 0)
continue;
if (raising && dy < 0)
continue;
dps[num_dps++] = v3s16(dx,dy,dz);
}
u32 order[3*3*3];
get_random_u32_array(order, num_dps);
v3s16 op = floatToInt(m_oldpos,BS);
for (int i=0; i<num_dps; i++) {
v3s16 p = dps[order[i]] + pos_i;
if (p == op)
continue;
if (!checkFreePosition(p + pos_size_off))
continue;
m_next_pos_i = p;
m_next_pos_exists = true;
break;
}
}
}
}
void MobSAO::stepMotionSeeker(float dtime, float offset)
{
MobFeatures m = content_mob_features(m_content);
v3s16 pos_i = floatToInt(m_base_position, BS);
Player *disturbing_player = m_env->getPlayer(m_disturbing_player.c_str());
if (!disturbing_player) {
m_next_pos_exists = false;
return;
}
v3f player_pos = disturbing_player->getPosition();
float distance = m_base_position.getDistanceFrom(player_pos);
float d;
float min;
float max;
offset = offset*(float)BS;
min = offset;
max = offset+6.0;
if (m.motion_type == MMT_WALK) {
if (!m_next_pos_exists) {
/* Check whether to drop down */
if (checkFreePosition(pos_i + v3s16(0,-1,0))) {
m_next_pos_i = pos_i + v3s16(0,-1,0);
m_next_pos_exists = true;
m_falling = true;
}else{
m_falling = false;
}
}
if (m_walk_around && !m_next_pos_exists) {
/* Find some position where to go next */
v3s16 dps[3*3*3];
int num_dps = 0;
for (int dx=-1; dx<=1; dx++)
for (int dy=-1; dy<=1; dy++)
for (int dz=-1; dz<=1; dz++) {
if (dx == 0 && dz == 0)
continue;
if (dx != 0 && dz != 0 && dy != 0)
continue;
d = (m_base_position+intToFloat(v3s16(dx,dy,dz),BS)).getDistanceFrom(player_pos);
if (distance < min) {
if (d > max)
continue;
}else if (d > distance) {
continue;
}
dps[num_dps++] = v3s16(dx,dy,dz);
}
u32 order[3*3*3];
get_random_u32_array(order, num_dps);
v3s16 op = floatToInt(m_oldpos,BS);
for (int i=0; i<num_dps; i++) {
v3s16 p = dps[order[i]] + pos_i;
if (p == op)
continue;
if (!checkFreeAndWalkablePosition(p))
continue;
m_next_pos_i = p;
m_next_pos_exists = true;
break;
}
}
}else if (m.motion_type == MMT_FLY) {
bool falling = false;
bool raising = false;
if (!m_next_pos_exists) {
u16 above;
v3s16 p = pos_i;
for (above=0; above < 14; above++) {
p.Y--;
if (!checkFreePosition(p))
break;
}
if (above > 12) {
/* Check whether to drop down */
if (checkFreePosition(pos_i + v3s16(0,-1,0))) {
m_next_pos_i = pos_i + v3s16(0,-1,0);
falling = true;
}
}else if (above < 8) {
/* Check whether to rise up */
if (checkFreePosition(pos_i + v3s16(0,1,0))) {
m_next_pos_i = pos_i + v3s16(0,1,0);
raising = true;
}
}
}
if (m_walk_around && !m_next_pos_exists) {
/* Find some position where to go next */
v3s16 dps[3*3*3];
int num_dps = 0;
for (int dx=-1; dx<=1; dx++)
for (int dy=-1; dy<=1; dy++)
for (int dz=-1; dz<=1; dz++) {
if (dx == 0 && dy == 0)
continue;
if (dx != 0 && dz != 0 && dy != 0)
continue;
if (falling && dy > 0)
continue;
if (raising && dy < 0)
continue;
d = (m_base_position+intToFloat(v3s16(dx,dy,dz),BS)).getDistanceFrom(player_pos);
if (distance < min) {
if (d > max)
continue;
}else if (d > distance) {
continue;
}
dps[num_dps++] = v3s16(dx,dy,dz);
}
u32 order[3*3*3];
get_random_u32_array(order, num_dps);
v3s16 op = floatToInt(m_oldpos,BS);
for (int i=0; i<num_dps; i++) {
v3s16 p = dps[order[i]] + pos_i;
if (p == op)
continue;
if (!checkFreePosition(p))
continue;
m_next_pos_i = p;
m_next_pos_exists = true;
break;
}
}
}else if (m.motion_type == MMT_FLYLOW || m.motion_type == MMT_SWIM) {
bool falling = false;
bool raising = false;
if (!m_next_pos_exists) {
u16 above;
v3s16 p = pos_i;
for (above=0; above < 6; above++) {
p.Y--;
if (!checkFreePosition(p))
break;
}
if (above > 5) {
/* Check whether to drop down */
if (checkFreePosition(pos_i + v3s16(0,-1,0))) {
m_next_pos_i = pos_i + v3s16(0,-1,0);
falling = true;
}
}else if (above < 2) {
/* Check whether to rise up */
if (checkFreePosition(pos_i + v3s16(0,1,0))) {
m_next_pos_i = pos_i + v3s16(0,1,0);
raising = true;
}
}
}
if (m_walk_around && !m_next_pos_exists) {
/* Find some position where to go next */
v3s16 dps[3*3*3];
int num_dps = 0;
for (int dx=-1; dx<=1; dx++)
for (int dy=-1; dy<=1; dy++)
for (int dz=-1; dz<=1; dz++) {
if (dx == 0 && dy == 0)
continue;
if (dx != 0 && dz != 0 && dy != 0)
continue;
if (falling && dy > 0)
continue;
if (raising && dy < 0)
continue;
d = (m_base_position+intToFloat(v3s16(dx,dy,dz),BS)).getDistanceFrom(player_pos);
if (distance < min) {
if (d > max)
continue;
}else if (d > distance) {
continue;
}
dps[num_dps++] = v3s16(dx,dy,dz);
}
u32 order[3*3*3];
get_random_u32_array(order, num_dps);
v3s16 op = floatToInt(m_oldpos,BS);
for (int i=0; i<num_dps; i++) {
v3s16 p = dps[order[i]] + pos_i;
if (p == op)
continue;
if (!checkFreePosition(p))
continue;
m_next_pos_i = p;
m_next_pos_exists = true;
break;
}
}
}
}
void MobSAO::stepMotionFlee(float dtime)
{
MobFeatures m = content_mob_features(m_content);
v3s16 pos_i = floatToInt(m_base_position, BS);
Player *disturbing_player = m_env->getPlayer(m_disturbing_player.c_str());
if (!disturbing_player) {
m_next_pos_exists = false;
return;
}
v3f player_pos = disturbing_player->getPosition();
float distance = m_base_position.getDistanceFrom(player_pos);
if (m.motion_type == MMT_WALK) {
if (!m_next_pos_exists) {
/* Check whether to drop down */
if (checkFreePosition(pos_i + v3s16(0,-1,0))) {
m_next_pos_i = pos_i + v3s16(0,-1,0);
m_next_pos_exists = true;
m_falling = true;
}else{
m_falling = false;
}
}
if (m_walk_around && !m_next_pos_exists) {
/* Find some position where to go next */
v3s16 dps[3*3*3];
int num_dps = 0;
for (int dx=-1; dx<=1; dx++)
for (int dy=-1; dy<=1; dy++)
for (int dz=-1; dz<=1; dz++) {
if (dx == 0 && dz == 0)
continue;
if (dx != 0 && dz != 0 && dy != 0)
continue;
if ((m_base_position+intToFloat(v3s16(dx,dy,dz),BS)).getDistanceFrom(player_pos) < distance)
continue;
dps[num_dps++] = v3s16(dx,dy,dz);
}
u32 order[3*3*3];
get_random_u32_array(order, num_dps);
v3s16 op = floatToInt(m_oldpos,BS);
for (int i=0; i<num_dps; i++) {
v3s16 p = dps[order[i]] + pos_i;
if (p == op)
continue;
if (!checkFreeAndWalkablePosition(p))
continue;
m_next_pos_i = p;
m_next_pos_exists = true;
break;
}
}
}else if (m.motion_type == MMT_FLY) {
bool falling = false;
bool raising = false;
if (!m_next_pos_exists) {
u16 above;
v3s16 p = pos_i;
for (above=0; above < 14; above++) {
p.Y--;
if (!checkFreePosition(p))
break;
}
if (above > 12) {
/* Check whether to drop down */
if (checkFreePosition(pos_i + v3s16(0,-1,0))) {
m_next_pos_i = pos_i + v3s16(0,-1,0);
falling = true;
}
}else if (above < 8) {
/* Check whether to rise up */
if (checkFreePosition(pos_i + v3s16(0,1,0))) {
m_next_pos_i = pos_i + v3s16(0,1,0);
raising = true;
}
}
}
if (m_walk_around && !m_next_pos_exists) {
/* Find some position where to go next */
v3s16 dps[3*3*3];
int num_dps = 0;
for (int dx=-1; dx<=1; dx++)
for (int dy=-1; dy<=1; dy++)
for (int dz=-1; dz<=1; dz++) {
if (dx == 0 && dy == 0)
continue;
if (dx != 0 && dz != 0 && dy != 0)
continue;
if (falling && dy > 0)
continue;
if (raising && dy < 0)
continue;
if ((m_base_position+intToFloat(v3s16(dx,dy,dz),BS)).getDistanceFrom(player_pos) < distance)
continue;
dps[num_dps++] = v3s16(dx,dy,dz);
}
u32 order[3*3*3];
get_random_u32_array(order, num_dps);
v3s16 op = floatToInt(m_oldpos,BS);
for (int i=0; i<num_dps; i++) {
v3s16 p = dps[order[i]] + pos_i;
if (p == op)
continue;
if (!checkFreePosition(p))
continue;
m_next_pos_i = p;
m_next_pos_exists = true;
break;
}
}
}else if (m.motion_type == MMT_FLYLOW || m.motion_type == MMT_SWIM) {
bool falling = false;
bool raising = false;
if (!m_next_pos_exists) {
u16 above;
v3s16 p = pos_i;
for (above=0; above < 6; above++) {
p.Y--;
if (!checkFreePosition(p))
break;
}
if (above > 5) {
/* Check whether to drop down */
if (checkFreePosition(pos_i + v3s16(0,-1,0))) {
m_next_pos_i = pos_i + v3s16(0,-1,0);
falling = true;
}
}else if (above < 2) {
/* Check whether to rise up */
if (checkFreePosition(pos_i + v3s16(0,1,0))) {
m_next_pos_i = pos_i + v3s16(0,1,0);
raising = true;
}
}
}
if (m_walk_around && !m_next_pos_exists) {
/* Find some position where to go next */
v3s16 dps[3*3*3];
int num_dps = 0;
for (int dx=-1; dx<=1; dx++)
for (int dy=-1; dy<=1; dy++)
for (int dz=-1; dz<=1; dz++) {
if (dx == 0 && dy == 0)
continue;
if (dx != 0 && dz != 0 && dy != 0)
continue;
if (falling && dy > 0)
continue;
if (raising && dy < 0)
continue;
if ((m_base_position+intToFloat(v3s16(dx,dy,dz),BS)).getDistanceFrom(player_pos) < distance)
continue;
dps[num_dps++] = v3s16(dx,dy,dz);
}
u32 order[3*3*3];
get_random_u32_array(order, num_dps);
v3s16 op = floatToInt(m_oldpos,BS);
for (int i=0; i<num_dps; i++) {
v3s16 p = dps[order[i]] + pos_i;
if (p == op)
continue;
if (!checkFreePosition(p))
continue;
m_next_pos_i = p;
m_next_pos_exists = true;
break;
}
}
}
}
void MobSAO::stepMotionSentry(float dtime)
{
MobFeatures m = content_mob_features(m_content);
v3s16 pos_i = floatToInt(m_base_position, BS);
if (m.motion_type == MMT_WALK) {
if (!m_next_pos_exists) {
/* Check whether to drop down */
if (checkFreePosition(pos_i + v3s16(0,-1,0))) {
m_next_pos_i = pos_i + v3s16(0,-1,0);
m_next_pos_exists = true;
m_falling = true;
}else{
m_falling = false;
}
}
if (m_walk_around && !m_next_pos_exists) {
/* Find some position where to go next */
v3s16 dps[3*3*3];
int num_dps = 0;
for (int dx=-1; dx<=1; dx++)
for (int dy=-1; dy<=1; dy++)
for (int dz=-1; dz<=1; dz++) {
if (dx == 0 && dz == 0)
continue;
if (dx != 0 && dz != 0 && dy != 0)
continue;
if ((m_base_position+intToFloat(v3s16(dx,dy,dz),BS)).getDistanceFrom(m_initial_pos) > (10.0*BS))
continue;
dps[num_dps++] = v3s16(dx,dy,dz);
}
u32 order[3*3*3];
get_random_u32_array(order, num_dps);
for (int i=0; i<num_dps; i++) {
v3s16 p = dps[order[i]] + pos_i;
if (!checkFreeAndWalkablePosition(p))
continue;
m_next_pos_i = p;
m_next_pos_exists = true;
break;
}
}
}else if (m.motion_type == MMT_FLY) {
bool falling = false;
bool raising = false;
if (!m_next_pos_exists) {
u16 above;
v3s16 p = pos_i;
for (above=0; above < 14; above++) {
p.Y--;
if (!checkFreePosition(p))
break;
}
if (above > 12) {
/* Check whether to drop down */
if (checkFreePosition(pos_i + v3s16(0,-1,0))) {
m_next_pos_i = pos_i + v3s16(0,-1,0);
falling = true;
}
}else if (above < 8) {
/* Check whether to rise up */
if (checkFreePosition(pos_i + v3s16(0,1,0))) {
m_next_pos_i = pos_i + v3s16(0,1,0);
raising = true;
}
}
}
if (m_walk_around && !m_next_pos_exists) {
/* Find some position where to go next */
v3s16 dps[3*3*3];
int num_dps = 0;
for (int dx=-1; dx<=1; dx++)
for (int dy=-1; dy<=1; dy++)
for (int dz=-1; dz<=1; dz++) {
if (dx == 0 && dy == 0)
continue;
if (dx != 0 && dz != 0 && dy != 0)
continue;
if (falling && dy > 0)
continue;
if (raising && dy < 0)
continue;
if ((m_base_position+intToFloat(v3s16(dx,dy,dz),BS)).getDistanceFrom(m_initial_pos) > (10.0*BS))
continue;
dps[num_dps++] = v3s16(dx,dy,dz);
}
u32 order[3*3*3];
get_random_u32_array(order, num_dps);
for (int i=0; i<num_dps; i++) {
v3s16 p = dps[order[i]] + pos_i;
if (!checkFreePosition(p))
continue;
m_next_pos_i = p;
m_next_pos_exists = true;
break;
}
}
}else if (m.motion_type == MMT_FLYLOW || m.motion_type == MMT_SWIM) {
bool falling = false;
if (!m_next_pos_exists) {
/* Check whether to drop down */
if (checkFreePosition(pos_i + v3s16(0,-1,0))) {
m_next_pos_i = pos_i + v3s16(0,-1,0);
falling = true;
}
}
if (m_walk_around && !m_next_pos_exists) {
/* Find some position where to go next */
v3s16 dps[3*3*3];
int num_dps = 0;
for (int dx=-1; dx<=1; dx++)
for (int dy=-1; dy<=1; dy++)
for (int dz=-1; dz<=1; dz++) {
if (dx == 0 && dy == 0)
continue;
if (dx != 0 && dz != 0 && dy != 0)
continue;
if (falling && dy > 0)
continue;
if ((m_base_position+intToFloat(v3s16(dx,dy,dz),BS)).getDistanceFrom(m_initial_pos) > (10.0*BS))
continue;
dps[num_dps++] = v3s16(dx,dy,dz);
}
u32 order[3*3*3];
get_random_u32_array(order, num_dps);
for (int i=0; i<num_dps; i++) {
v3s16 p = dps[order[i]] + pos_i;
if (!checkFreePosition(p))
continue;
m_next_pos_i = p;
m_next_pos_exists = true;
break;
}
}
}
}
void MobSAO::stepMotionThrown(float dtime)
{
MobFeatures m = content_mob_features(m_content);
m_base_position += m_speed * dtime;
m_speed.Y -= 10.0*BS*dtime;
m_yaw = wrapDegrees_180(180./PI*atan2(m_speed.Z, m_speed.X));
v3s16 pos_i = floatToInt(m_base_position, BS);
if (!checkFreePosition(pos_i) || m_removed) {
if (m.contact_explosion_diameter > 0)
explodeSquare(pos_i, v3s16(m.contact_explosion_diameter,m.contact_explosion_diameter,m.contact_explosion_diameter));
if (m.contact_place_node != CONTENT_IGNORE && checkFreeAndWalkablePosition(pos_i+v3s16(0,1,0))) {
v3s16 pos = pos_i+v3s16(0,1,0);
m_env->getMap().addNodeWithEvent(pos,MapNode(m.contact_place_node));
}else if (m.contact_drop_item != CONTENT_IGNORE) {
InventoryItem *i = InventoryItem::create(m.contact_drop_item,1);
if (i) {
ServerActiveObject *obj = i->createSAO(m_env,0,m_base_position);
if (obj)
m_env->addActiveObject(obj);
}
}
m_removed = true;
return;
}
}
void MobSAO::stepMotionConstant(float dtime)
{
MobFeatures m = content_mob_features(m_content);
m_base_position += m_speed * dtime;
m_yaw = wrapDegrees_180(180./PI*atan2(m_speed.Z, m_speed.X));
v3s16 pos_i = floatToInt(m_base_position, BS);
if (!checkFreePosition(pos_i)) {
if (m.contact_explosion_diameter > 0)
explodeSquare(pos_i, v3s16(m.contact_explosion_diameter,m.contact_explosion_diameter,m.contact_explosion_diameter));
m_removed = true;
return;
}
}
bool MobSAO::checkFreePosition(v3s16 p0)
{
assert(m_env);
Map *map = &m_env->getMap();
v3s16 size = content_mob_features(m_content).getSizeBlocks();
if (content_mob_features(m_content).motion_type == MMT_SWIM) {
for (int dx=0; dx<size.X; dx++)
for (int dy=0; dy<size.Y; dy++)
for (int dz=0; dz<size.Z; dz++) {
v3s16 dp(dx, dy, dz);
v3s16 p = p0 + dp;
MapNode n = map->getNodeNoEx(p);
if (n.getContent() != CONTENT_WATERSOURCE)
return false;
}
}else{
for (int dx=0; dx<size.X; dx++)
for (int dy=0; dy<size.Y; dy++)
for (int dz=0; dz<size.Z; dz++) {
v3s16 dp(dx, dy, dz);
v3s16 p = p0 + dp;
MapNode n = map->getNodeNoEx(p);
if (n.getContent() != CONTENT_AIR && content_features(n).walkable)
return false;
if (content_features(n).liquid_type == LIQUID_SOURCE)
return false;
}
}
MapNode n = map->getNodeNoEx(p0+v3s16(0,-1,0));
if (!content_features(n).jumpable)
return false;
return true;
}
bool MobSAO::checkWalkablePosition(v3s16 p0)
{
assert(m_env);
v3s16 p = p0 + v3s16(0,-1,0);
MapNode n = m_env->getMap().getNodeNoEx(p);
if (n.getContent() != CONTENT_AIR) {
if (content_features(n).liquid_type == LIQUID_NONE && content_features(n).walkable)
return true;
}
return false;
}
bool MobSAO::checkFreeAndWalkablePosition(v3s16 p0)
{
if (!checkFreePosition(p0))
return false;
if (!checkWalkablePosition(p0))
return false;
return true;
}
void MobSAO::explodeSquare(v3s16 p0, v3s16 size)
{
assert(m_env);
Map *map = &m_env->getMap();
core::map<v3s16, MapBlock*> modified_blocks;
MapNode fire(CONTENT_FIRE);
std::string blame("damned mobs");
if (content_mob_features(m_content).level != MOB_DESTRUCTIVE) {
if (m_env->searchNear(p0,size+v3s16(5,5,5),CONTENT_BORDERSTONE,NULL))
return;
}
for (int dx=0; dx<size.X; dx++)
for (int dy=0; dy<size.Y; dy++)
for (int dz=0; dz<size.Z; dz++) {
v3s16 dp(dx - size.X/2, dy - size.Y/2, dz - size.Z/2);
v3s16 p = p0 + dp;
MapNode n = map->getNodeNoEx(p);
if (n.getContent() == CONTENT_IGNORE)
continue;
if (content_features(n).flammable)
map->addNodeAndUpdate(p,fire,modified_blocks,blame);
}
// Send a MEET_OTHER event
MapEditEvent event;
event.type = MEET_OTHER;
for (core::map<v3s16, MapBlock*>::Iterator i = modified_blocks.getIterator(); i.atEnd() == false; i++) {
v3s16 p = i.getNode()->getKey();
event.modified_blocks.insert(p, true);
}
map->dispatchEvent(&event);
}
InventoryItem* MobSAO::createPickedUpItem(content_t punch_item)
{
MobFeatures m = content_mob_features(m_content);
ToolItemFeatures f = content_toolitem_features(punch_item);
if (m.punch_action != MPA_PICKUP) {
if (!m_removed) {
if (m.special_dropped_item != CONTENT_IGNORE && (m.special_punch_item == TT_NONE || f.type == m.special_punch_item)) {
if (m.special_dropped_max > 0) {
if (m_special_count < m.special_dropped_count)
return NULL;
m_special_count -= m.special_dropped_count;
if (m_special_count < 0) {
m_special_count = 0;
return NULL;
}
}else{
m_removed = true;
}
return InventoryItem::create(m.special_dropped_item,m.special_dropped_count);
}
return NULL;
}
}
if (m.dropped_item == "")
return NULL;
std::istringstream is(m.dropped_item, std::ios_base::binary);
InventoryItem *item = InventoryItem::deSerialize(is);
if (!m_removed)
m_removed = true;
return item;
}
u16 MobSAO::punch(content_t punch_item, v3f dir, const std::string &playername)
{
MobFeatures m = content_mob_features(m_content);
if (m.sound_punch != "")
m_env->addEnvEvent(ENV_EVENT_SOUND,m_base_position,m.sound_punch);
if (m.punch_action == MPA_IGNORE)
return 0;
ToolItemFeatures f = content_toolitem_features(punch_item);
u16 wear = 655;
vlprintf(
CN_ACTION,
(char*)"%s punches mod (%u) with a '%s' at (%f,%f,%f)",
playername.c_str(),
m_id,
f.description,
m_base_position.X/BS,
m_base_position.Y/BS,
m_base_position.Z/BS
);
if (m.special_dropped_item != CONTENT_IGNORE && (m.special_punch_item == TT_NONE || f.type == m.special_punch_item))
return 0;
if (m.punch_action == MPA_HARM) {
m_disturb_timer = 0;
m_disturbing_player = playername;
m_next_pos_exists = false; // Cancel moving immediately
m_angry = true;
m_walk_around_timer = 0.2;
m_walk_around = true;
m_yaw = wrapDegrees_180(180./PI*atan2(dir.Z, dir.X) + 180.);
v3f new_base_position = m_base_position + dir * BS;
{
v3s16 pos_i = floatToInt(new_base_position, BS);
v3s16 pos_size_off(0,0,0);
if (m.getSize().X >= 2.5) {
pos_size_off.X = -1;
pos_size_off.Y = -1;
}
bool free = checkFreePosition(pos_i + pos_size_off);
if (free)
m_base_position = new_base_position;
}
sendPosition();
tooluse_t usage;
if (!get_tool_use(&usage,m_content,0,punch_item,0)) {
if (usage.diggable)
doDamage(usage.data);
wear = usage.wear;
}
}else if (m.punch_action == MPA_DIE) {
m_hp = 0;
m_removed = true;
}
return wear;
}
bool MobSAO::rightClick(Player *player)
{
// so get the player
if (!player)
return false;
// see if mob is tamable
MobFeatures m = content_mob_features(m_content);
if (m.tamed_mob == CONTENT_IGNORE)
return false;
// get the wielded item
u16 item_i = player->getSelectedItem();
InventoryList *ilist = player->inventory.getList("main");
if (ilist == NULL)
return false;
InventoryItem *item = ilist->getItem(item_i);
if (!item)
return false;
// check if it's a craft item
content_t c = item->getContent();
if ((c&CONTENT_CRAFTITEM_MASK) != CONTENT_CRAFTITEM_MASK)
return false;
CraftItemFeatures *f = content_craftitem_features(c);
if (f->content != c)
return false;
// and edible
if (!f->consumable || !f->hunger_effect)
return false;
// feed the mob
// after this always return true as inventory has been modified
if (!config_get_bool((char*)"world.player.inventory.creative") && ilist) {
// Remove from inventory
if (item->getCount() == 1) {
ilist->deleteItem(item_i);
}else{
item->remove(1);
ilist->addDiff(item_i,item);
}
}
if (m_tamed_chance < 1)
m_tamed_chance = 1;
// tame it maybe
if (m.level > MOB_PASSIVE && myrand_range(0,m_tamed_chance) != 0) {
if (m_tamed_chance > 1)
m_tamed_chance--;
return true;
}
// add new tamed mob
ServerActiveObject *obj = new MobSAO(m_env, 0, m_base_position, m.tamed_mob);
if (obj)
m_env->addActiveObject(obj);
// delete this one
m_removed = true;
return true;
}
u8 MobSAO::level()
{
return content_mob_features(m_content).level;
}
void MobSAO::sendPosition()
{
m_last_sent_position = m_base_position;
std::ostringstream os(std::ios::binary);
// command (0 = update position)
writeU8(os, 0);
// pos
writeV3F1000(os, m_base_position);
// yaw
writeF1000(os, m_yaw);
// create message and add to list
ActiveObjectMessage aom(getId(), false, os.str());
m_messages_out.push_back(aom);
}
void MobSAO::doDamage(u16 d)
{
if (d >= m_hp) {
vlprintf(
CN_ACTION,
(char*)"mob (%u) dies at (%f,%f,%f)",
m_id,
m_base_position.X/BS,
m_base_position.Y/BS,
m_base_position.Z/BS
);
// Die
m_hp = 0;
m_removed = true;
return;
}
m_hp -= d;
{
std::ostringstream os(std::ios::binary);
// command (1 = damage)
writeU8(os, 1);
// amount
writeU16(os, d);
// create message and add to list
ActiveObjectMessage aom(getId(), false, os.str());
m_messages_out.push_back(aom);
}
}