voxelands/src/player.cpp

1312 lines
32 KiB
C++

/************************************************************************
* Minetest-c55
* Copyright (C) 2010-2011 celeron55, Perttu Ahola <celeron55@gmail.com>
*
* player.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 "nvp.h"
#include "player.h"
#include "map.h"
#include "connection.h"
#include "constants.h"
#include "utility.h"
#ifndef SERVER
#include "common_irrlicht.h"
#include "mesh.h"
#endif
#include "path.h"
#include "content_clothesitem.h"
/* character def:
gender:Yscale:XZscale:skintone:eyes:hairtone:hair:face:shirt-colour:pants-colour:shoe-type
the old default:
M:10:10:fair:blue:brown:medium:normal:green:blue:leather
*/
Player::Player():
touching_ground(false),
in_water(false),
in_water_stable(false),
is_climbing(false),
swimming_up(false),
in_bed(false),
wake_timeout(0.0),
inventory_modified(true),
craftresult_is_preview(true),
health(100),
air(100),
hunger(100),
energy_effect(0),
cold_effect(0),
dirt(0),
wet(0),
blood(0),
last_damage(0),
peer_id(PEER_ID_INEXISTENT),
m_selected_item(0),
m_pitch(0),
m_yaw(0),
m_speed(0,0,0),
m_position(0,0,0),
m_home(0,0,0),
m_hashome(false),
m_character(PLAYER_DEFAULT_CHARDEF),
m_given_clothes(false)
{
updateName("<not set>");
for (u8 i=0; i<PLAYERFLAG_COUNT; i++) {
m_hasflag[i] = false;
}
resetInventory();
}
Player::~Player()
{
}
void Player::wieldItem(u16 item)
{
m_selected_item = item;
}
void Player::resetInventory(bool include_clothes)
{
if (include_clothes) {
inventory.clear();
}else{
InventoryList *l = inventory.getList("main");
if (l)
l->clearItems();
l = inventory.getList("exo");
if (l)
l->clearItems();
l = inventory.getList("discard");
if (l)
l->clearItems();
l = inventory.getList("craft");
if (l)
l->clearItems();
l = inventory.getList("craftresult");
if (l)
l->clearItems();
}
checkInventory();
}
void Player::checkInventory()
{
if (!inventory.getList("main"))
inventory.addList("main", PLAYER_INVENTORY_SIZE);
if (!inventory.getList("exo"))
inventory.addList("exo", 18);
if (!inventory.getList("hat"))
inventory.addList("hat",1);
if (!inventory.getList("jacket"))
inventory.addList("jacket",1);
if (!inventory.getList("shirt"))
inventory.addList("shirt",1);
if (!inventory.getList("decorative"))
inventory.addList("decorative",1);
if (!inventory.getList("belt"))
inventory.addList("belt",1);
if (!inventory.getList("pants"))
inventory.addList("pants",1);
if (!inventory.getList("boots"))
inventory.addList("boots",1);
// this allows only the correct clothing type in a player's
// relevant clothing slot
{
int i;
InventoryList *h = inventory.getList("hat");
InventoryList *j = inventory.getList("jacket");
InventoryList *s = inventory.getList("shirt");
InventoryList *d = inventory.getList("decorative");
InventoryList *t = inventory.getList("belt");
InventoryList *p = inventory.getList("pants");
InventoryList *b = inventory.getList("boots");
h->setStackable(false);
h->clearAllowed();
j->setStackable(false);
j->clearAllowed();
s->setStackable(false);
s->clearAllowed();
d->setStackable(false);
d->clearAllowed();
t->setStackable(false);
t->clearAllowed();
p->setStackable(false);
p->clearAllowed();
b->setStackable(false);
b->clearAllowed();
for (i=0; i<4096; i++) {
ClothesItemFeatures *c = content_clothesitem_features(i|CONTENT_CLOTHESITEM_MASK);
switch (c->type) {
case CT_HAT:
h->addAllowed(c->content);
break;
case CT_JACKET:
j->addAllowed(c->content);
break;
case CT_SHIRT:
s->addAllowed(c->content);
break;
case CT_DECORATIVE:
case CT_MEDALLION:
d->addAllowed(c->content);
break;
case CT_BELT:
t->addAllowed(c->content);
break;
case CT_PANTS:
p->addAllowed(c->content);
break;
case CT_BOOTS:
b->addAllowed(c->content);
break;
default:;
}
}
}
if (!inventory.getList("discard"))
inventory.addList("discard",1);
if (!inventory.getList("craft"))
inventory.addList("craft", 9);
if (!inventory.getList("craftresult"))
inventory.addList("craftresult", 1);
}
// Y direction is ignored
void Player::accelerate(v3f target_speed, f32 max_increase)
{
v3f d_wanted = target_speed - m_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;
m_speed.X += d.X;
m_speed.Z += d.Z;
}
void Player::serialize(std::ostream &os)
{
// Utilize a Settings object for storing values
char* flags[8] = {
"flag_white",
"flag_blue",
"flag_green",
"flag_orange",
"flag_purple",
"flag_red",
"flag_yellow",
"flag_black"
};
nvp_t *list = NULL;
char buff[1024];
int i;
v3_t v;
nvp_set(&list,"version","1",NULL);
nvp_set(&list,"name",m_name,NULL);
nvp_set_float(&list,"pitch",m_pitch);
nvp_set_float(&list,"yaw",m_yaw);
v.x = m_position.X;
v.y = m_position.Y;
v.z = m_position.Z;
nvp_set_v3t(&list,"position",&v);
nvp_set_int(&list,"craftresult_is_preview",craftresult_is_preview);
nvp_set_int(&list,"health",health);
nvp_set_int(&list,"air",air);
nvp_set_int(&list,"hunger",hunger);
if (m_hashome) {
/* home */
v.x = m_home.X;
v.y = m_home.Y;
v.z = m_home.Z;
nvp_set_v3t(&list,"home",&v);
}
for (i=0; i<8; i++) {
if (!m_hasflag[i])
continue;
/* flags */
v.x = m_flag[i].X;
v.y = m_flag[i].Y;
v.z = m_flag[i].Z;
nvp_set_v3t(&list,flags[i],&v);
}
if (m_given_clothes)
nvp_set(&list,"clothes_given","true",NULL);
nvp_set_float(&list,"wake_timeout",wake_timeout);
if (nvp_to_str(&list,buff,1024) < 1) {
vlprintf(CN_DEBUG,"failed to serialise player data");
nvp_free(&list,0);
return;
}
nvp_free(&list,0);
os << buff;
os<<"PlayerArgsEnd\n";
inventory.serialize(os);
}
void Player::deSerialize(std::istream &is)
{
nvp_t *list = NULL;
std::string conf;
char* val;
v3_t v;
char* flags[8] = {
"flag_white",
"flag_blue",
"flag_green",
"flag_orange",
"flag_purple",
"flag_red",
"flag_yellow",
"flag_black"
};
for (;;) {
if (is.eof())
return;
std::string line;
std::getline(is, line);
std::string trimmedline = trim(line);
if (trimmedline == "PlayerArgsEnd")
break;
conf += line + "\n";
}
nvp_from_str(&list,(char*)conf.c_str());
val = nvp_get_str(&list,"name");
if (!val)
val = "Unknown";
updateName(val);
m_pitch = nvp_get_float(&list,"pitch");
m_yaw = nvp_get_float(&list,"yaw");
val = nvp_get_str(&list,"position");
if (val) {
if (!str_tov3t(val,&v))
m_position = v3f(v.x,v.y,v.z);
}
if (nvp_get(&list,"craftresult_is_preview")) {
craftresult_is_preview = nvp_get_bool(&list,"craftresult_is_preview");
}else{
craftresult_is_preview = true;
}
if (nvp_get(&list,"health")) {
health = nvp_get_int(&list,"health");
if (nvp_get(&list,"air")) {
air = nvp_get_int(&list,"air");
}else{
air = 100;
}
if (nvp_get(&list,"hunger")) {
hunger = nvp_get_int(&list,"hunger");
}else{
hunger = 100;
}
}else if (nvp_get(&list,"hp")) {
health = 5*nvp_get_int(&list,"hp");
if (nvp_get(&list,"air")) {
air = 5*nvp_get_int(&list,"air");
}else{
air = 100;
}
if (nvp_get(&list,"hunger")) {
hunger = 5*nvp_get_int(&list,"hunger");
}else{
hunger = 100;
}
}else{
health = 100;
}
m_hashome = false;
val = nvp_get_str(&list,"home");
if (val) {
if (!str_tov3t(val,&v)) {
m_home = v3f(v.x,v.y,v.z);
m_hashome = true;
}
}
for (u16 i=0; i<8; i++) {
m_hasflag[i] = false;
if (!nvp_get(&list,flags[i]))
continue;
val = nvp_get_str(&list,flags[i]);
if (val) {
if (!str_tov3t(val,&v))
m_flag[i] = v3f(v.x,v.y,v.z);
}
if (!m_hashome) {
m_home = m_flag[i];
m_hashome = true;
}
m_hasflag[i] = true;
}
m_given_clothes = nvp_get_bool(&list,"clothes_given");
wake_timeout = nvp_get_float(&list,"wake_timeout");
nvp_free(&list,0);
inventory.deSerialize(is);
checkInventory();
}
bool Player::getHome(s8 i, v3f &h)
{
if (i == PLAYERFLAG_HOME) {
if (!m_hashome)
return false;
h = m_home;
return true;
}else if (i > PLAYERFLAG_HOME && i < PLAYERFLAG_COUNT) {
if (!m_hasflag[i])
return false;
h = m_flag[i];
return true;
}else if (i == PLAYERFLAG_JUMP) {
h = m_position+intToFloat(v3s16(0,1500,0),BS);
return true;
}
return false;
}
void Player::setHome(s8 i, v3f h)
{
if (i > PLAYERFLAG_HOME && i < PLAYERFLAG_COUNT) {
m_flag[i] = h;
m_hasflag[i] = true;
}
m_home = h;
m_hashome = true;
}
void Player::unsetHome(s8 i)
{
bool reset = false;
if (i == PLAYERFLAG_HOME) {
reset = true;
}else if (i > PLAYERFLAG_HOME && i < PLAYERFLAG_COUNT) {
if (m_hashome && m_hasflag[i] && m_home == m_flag[i])
reset = true;
m_hasflag[i] = false;
}
if (!reset)
return;
m_hashome = false;
for (u8 i=0; i<PLAYERFLAG_COUNT; i++) {
if (!m_hasflag[i])
continue;
m_hashome = true;
m_home = m_flag[i];
}
}
v3f Player::getScale()
{
if (m_character == "")
m_character = std::string(PLAYER_DEFAULT_CHARDEF);
Strfnd f(m_character);
std::string gender = f.next(":");
std::string y = f.next(":"); // y scale
std::string xz = f.next(":"); // xz scale
f32 y_f = mystof(y)/10.0;
if (y_f < 0.8)
y_f = 0.8;
if (y_f > 1.1)
y_f = 1.1;
f32 xz_f = mystof(xz)/10.0;
if (xz_f < 0.8)
xz_f = 0.8;
if (xz_f > 1.1)
xz_f = 1.1;
return v3f(xz_f,y_f,xz_f);
}
//gender:Yscale:XZscale:skintone:eyes:hairtone:hair:face:shirt-colour:pants-colour:shoe-type
//M:10:10:fair:blue:brown:medium:human:green:blue:leather
void Player::getSkin(std::vector<std::string> &parts)
{
char buff[1024];
char buf[256];
if (m_character == "")
m_character = std::string(PLAYER_DEFAULT_CHARDEF);
Strfnd f(m_character);
std::string gender = f.next(":");
f.next(":"); // y scale
f.next(":"); // xz scale
std::string skintone = f.next(":");
std::string eyes = f.next(":");
std::string hairtone = f.next(":");
std::string hair = f.next(":");
std::string face = f.next(":");
if (gender != "M" && gender != "F")
gender = "M";
snprintf(buf,256,"skintone_%s_%s.png",skintone.c_str(),gender.c_str());
if (!path_get((char*)"skin",buf,1,buff,1024))
skintone = "fair";
snprintf(buf,256,"eyes_%s.png",eyes.c_str());
if (!path_get((char*)"skin",buf,1,buff,1024))
eyes = "blue";
snprintf(buf,256,"hair_%s_%s_%s.png",hair.c_str(),hairtone.c_str(),gender.c_str());
if (hairtone == "" || hair == "" || !path_get((char*)"skin",buf,1,buff,1024)) {
hairtone = "brown";
hair = "medium";
}
snprintf(buf,256,"face_%s%s.png",face.c_str(),gender.c_str());
if (!path_get((char*)"skin",buf,1,buff,1024))
face = "human";
parts.push_back(std::string("skins/")+"skintone_"+skintone+".png");
parts.push_back(std::string("skins/")+"gender_"+gender+".png");
parts.push_back(std::string("skins/")+"face_"+face+"_"+skintone+"_"+gender+".png");
parts.push_back(std::string("skins/")+"eyes_"+eyes+".png");
parts.push_back(std::string("skins/")+"hair_"+hair+"_"+hairtone+"_"+gender+".png");
}
/*
ServerRemotePlayer
*/
//gender:Yscale:XZscale:skintone:eyes:hairtone:hair:face:shirt-colour:pants-colour:shoe-type
//M:10:10:fair:blue:brown:medium:human:green:blue:leather
void ServerRemotePlayer::setCharDef(std::string d)
{
m_character = d;
if (m_given_clothes)
return;
if (m_character == "")
m_character = std::string(PLAYER_DEFAULT_CHARDEF);
Strfnd f(m_character);
f.next(":"); // gender
f.next(":"); // y scale
f.next(":"); // xz scale
f.next(":"); // skintone
f.next(":"); // eyes
f.next(":"); // hairtone
f.next(":"); // hair
f.next(":"); // face
std::string shirt = f.next(":");
std::string pants = f.next(":");
std::string shoes = f.next(":");
{
InventoryList *l = inventory.getList("shirt");
if (!l)
return;
InventoryItem *item = NULL;
if (shirt == "white") {
item = new ClothesItem(CONTENT_CLOTHESITEM_COTTON_TSHIRT,0,0);
}else if (shirt == "blue") {
item = new ClothesItem(CONTENT_CLOTHESITEM_COTTON_TSHIRT_BLUE,0,0);
}else if (shirt == "orange") {
item = new ClothesItem(CONTENT_CLOTHESITEM_COTTON_TSHIRT_ORANGE,0,0);
}else if (shirt == "purple") {
item = new ClothesItem(CONTENT_CLOTHESITEM_COTTON_TSHIRT_PURPLE,0,0);
}else if (shirt == "red") {
item = new ClothesItem(CONTENT_CLOTHESITEM_COTTON_TSHIRT_RED,0,0);
}else if (shirt == "yellow") {
item = new ClothesItem(CONTENT_CLOTHESITEM_COTTON_TSHIRT_YELLOW,0,0);
}else if (shirt == "black") {
item = new ClothesItem(CONTENT_CLOTHESITEM_COTTON_TSHIRT_BLACK,0,0);
}
if (item == NULL)
item = new ClothesItem(CONTENT_CLOTHESITEM_COTTON_TSHIRT_GREEN,0,0);
l->addItem(0,item);
}
{
InventoryList *l = inventory.getList("pants");
if (!l)
return;
InventoryItem *item = NULL;
if (pants == "white") {
item = new ClothesItem(CONTENT_CLOTHESITEM_CANVAS_PANTS,0,0);
}else if (pants == "green") {
item = new ClothesItem(CONTENT_CLOTHESITEM_CANVAS_PANTS_GREEN,0,0);
}else if (pants == "orange") {
item = new ClothesItem(CONTENT_CLOTHESITEM_CANVAS_PANTS_ORANGE,0,0);
}else if (pants == "purple") {
item = new ClothesItem(CONTENT_CLOTHESITEM_CANVAS_PANTS_PURPLE,0,0);
}else if (pants == "red") {
item = new ClothesItem(CONTENT_CLOTHESITEM_CANVAS_PANTS_RED,0,0);
}else if (pants == "yellow") {
item = new ClothesItem(CONTENT_CLOTHESITEM_CANVAS_PANTS_YELLOW,0,0);
}else if (pants == "black") {
item = new ClothesItem(CONTENT_CLOTHESITEM_CANVAS_PANTS_BLACK,0,0);
}
if (item == NULL)
item = new ClothesItem(CONTENT_CLOTHESITEM_CANVAS_PANTS_BLUE,0,0);
l->addItem(0,item);
}
{
InventoryList *l = inventory.getList("boots");
if (!l)
return;
InventoryItem *item = NULL;
if (shirt == "fur") {
item = new ClothesItem(CONTENT_CLOTHESITEM_FUR_SHOES,0,0);
}else if (shirt == "canvas") {
item = new ClothesItem(CONTENT_CLOTHESITEM_CANVAS_SHOES,0,0);
}
if (item == NULL)
item = new ClothesItem(CONTENT_CLOTHESITEM_LEATHER_SHOES,0,0);
l->addItem(0,item);
}
m_given_clothes = true;
}
/*
RemotePlayer
*/
#ifndef SERVER
#include "sound.h"
RemotePlayer::RemotePlayer(
scene::ISceneNode* parent,
IrrlichtDevice *device,
s32 id):
scene::ISceneNode(parent, (device==NULL)?NULL:device->getSceneManager(), id),
m_node(NULL),
m_text(NULL),
m_wield(NULL),
m_anim_id(PLAYERANIM_STAND),
m_pointed(CONTENT_IGNORE),
m_next_foot(0)
{
m_box = core::aabbox3d<f32>(-BS/2,0,-BS/2,BS/2,BS*2,BS/2);
if (parent != NULL && device != NULL) {
// ISceneNode stores a member called SceneManager
scene::ISceneManager* mgr = SceneManager;
video::IVideoDriver* driver = mgr->getVideoDriver();
gui::IGUIEnvironment* gui = device->getGUIEnvironment();
// Add a text node for showing the name
wchar_t wname[1] = {0};
m_text = mgr->addTextSceneNode(gui->getBuiltInFont(),
wname, video::SColor(255,255,255,255), this);
if (!m_text)
return;
m_text->setPosition(v3f(0, (f32)BS*2.1, 0));
// get a unique mesh so that the player model has it's own lighting
scene::IAnimatedMesh* mesh = createModelMesh(mgr,"character.b3d",true);
if (!mesh)
return;
m_node = mgr->addAnimatedMeshSceneNode(mesh,this);
if (m_node) {
char buff[1024];
m_node->setFrameLoop(0,79);
m_node->setScale(Player::getScale());
setMeshColor(m_node->getMesh(), video::SColor(255,255,255,255));
// Set material flags and texture
if (path_get((char*)"texture",(char*)"character.png",1,buff,1024))
m_node->setMaterialTexture(0, driver->getTexture(buff));
video::SMaterial& material = m_node->getMaterial(0);
material.setFlag(video::EMF_LIGHTING, false);
material.setFlag(video::EMF_BILINEAR_FILTER, false);
material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
m_node->setPosition(v3f(0,(f32)BS,0));
scene::IBoneSceneNode *arm = m_node->getJointNode("Arm_Low_Right");
if (arm)
m_wield = new ExtrudedSpriteSceneNode(arm,mgr,-1,v3f(0,5,1),v3f(-45,-90,45),v3f(4,4,4));
}
#if (IRRLICHT_VERSION_MAJOR >= 1 && IRRLICHT_VERSION_MINOR >= 8) || IRRLICHT_VERSION_MAJOR >= 2
mesh->drop();
#endif
}
}
RemotePlayer::~RemotePlayer()
{
if(SceneManager != NULL) {
ISceneNode::remove();
}
}
void RemotePlayer::updateName(const char *name)
{
if (name != NULL) {
Player::updateName(name);
if (m_text != NULL) {
wchar_t wname[PLAYERNAME_SIZE];
mbstowcs(wname, m_name, strlen(m_name)+1);
m_text->setText(wname);
}
}
if (m_node != NULL && !isLocal()) {
// Set material flags and texture
m_node->setMaterialTexture( 0, getTexture());
video::SMaterial& material = m_node->getMaterial(0);
material.setFlag(video::EMF_LIGHTING, false);
material.setFlag(video::EMF_BILINEAR_FILTER, false);
}
}
void RemotePlayer::wieldItem(u16 item)
{
m_selected_item = item;
if (!m_wield)
return;
const InventoryItem *i = getWieldItem();
if (!i) {
m_wield->setVisible(false);
return;
}
bool haveWield = false;
// Try to make a MaterialItem cube.
if (std::string(i->getName()) == "MaterialItem") {
// A block-type material
MaterialItem* mat_item = (MaterialItem*)i;
content_t content = mat_item->getMaterial();
ContentFeatures *f = &content_features(content);
if (
f->draw_type == CDT_CUBELIKE
|| f->draw_type == CDT_GLASSLIKE
|| f->draw_type == CDT_DIRTLIKE
|| f->draw_type == CDT_LIQUID_SOURCE
|| f->draw_type == CDT_MELONLIKE
) {
m_wield->setCube(f->tiles);
haveWield = true;
}else if (
(
f->draw_type == CDT_NODEBOX
|| f->draw_type == CDT_NODEBOX_META
|| f->draw_type == CDT_FENCELIKE
|| f->draw_type == CDT_WALLLIKE
)
&& f->wield_nodebox == true
) {
m_wield->setNodeBox(content);
haveWield = true;
}
m_wield->setScale(v3f(3,3,3));
}else{
m_wield->setScale(v3f(4.5,4.5,4.5));
}
// If that failed, make an extruded sprite.
if (!haveWield) {
m_wield->setSprite(i->getImageRaw());
}
m_wield->setVisible(true);
}
void RemotePlayer::move(f32 dtime, Map &map, f32 pos_max_d)
{
m_pos_animation_time_counter += dtime;
m_pos_animation_counter += dtime;
v3f movevector = m_position - m_oldpos;
f32 moveratio;
if (m_pos_animation_time < 0.001) {
moveratio = 1.0;
}else{
moveratio = m_pos_animation_counter / m_pos_animation_time;
}
if (moveratio > 1.5)
moveratio = 1.5;
m_showpos = m_oldpos + movevector * moveratio;
int frame = m_node->getFrameNr();
/* roughly sort of when a step sound should probably be heard, maybe */
if (frame == 218 || frame == 186 || frame == 209 || frame == 177) {
sound_play_step(&map,m_showpos,m_next_foot,1.0);
m_next_foot = !m_next_foot;
}
/* roughly sort of when a dig sound should probably be heard, maybe */
if (frame == 214 || frame == 205 || frame == 193) {
sound_play_dig(m_pointed,m_showpos);
}
if (m_anim_id == PLAYERANIM_DIE) {
if (m_node->getEndFrame() != 167)
m_node->setFrameLoop(162,167);
}else{
if (
(
movevector.X < 0.001
&& movevector.X > -0.001
) || (
movevector.Z < 0.001
&& movevector.Z > -0.001
)
) {
if (m_anim_id == PLAYERANIM_DIG) {
if (m_node->getEndFrame() != 198)
m_node->setFrameLoop(189,198);
}else if (m_node->getEndFrame() != 79) {
m_node->setFrameLoop(0,79);
}
}else{
if (m_anim_id == PLAYERANIM_DIG) { // walk/dig
if (m_node->getEndFrame() != 219)
m_node->setFrameLoop(200,219);
}else if (m_node->getEndFrame() != 187) { // walk
m_node->setFrameLoop(168,187);
}
}
}
ISceneNode::setPosition(m_showpos-m_camera_offset);
}
video::ITexture* RemotePlayer::getTexture()
{
std::string clothes[7];
const char* list[7] = {"hat","shirt","jacket","decorative","belt","pants","boots"};
std::vector<std::string> parts;
getSkin(parts);
for (int j=0; j<7; j++) {
InventoryList *l = inventory.getList(list[j]);
if (l == NULL)
continue;
InventoryItem *i = l->getItem(0);
if (i == NULL)
continue;
clothes[j] = content_clothesitem_features(i->getContent())->overlay_texture;
}
std::string tex = "";
tex += parts[0]; // skin
tex += "^" + parts[1]; // underwear
tex += "^" + parts[2]; // face
tex += "^" + parts[3]; // eyes
tex += "^" + clothes[5]; // pants
tex += "^" + clothes[1]; // shirt
tex += "^" + clothes[4]; // belt
tex += "^" + clothes[3]; // deco
tex += "^" + parts[4]; // hair
tex += "^" + clothes[2]; // jacket
tex += "^" + clothes[6]; // boots
tex += "^" + clothes[0]; // hat
return g_texturesource->getTextureRaw(tex);
}
#endif
#ifndef SERVER
/*
LocalPlayer
*/
LocalPlayer::LocalPlayer():
energy_effectf(0.0),
cold_effectf(0.0),
last_position(v3f(0,0,0)),
last_speed(v3f(0,0,0)),
last_pitch(0),
last_yaw(0),
shadow_timer(0.0),
m_sneak_node(32767,32767,32767),
m_sneak_node_exists(false),
m_can_use_energy(true),
m_ignore_energy(false),
m_low_energy_effect(0)
{
char* v;
m_energy = 10.0;
v = config_get("client.character");
if (!v)
v = PLAYER_DEFAULT_CHARDEF;
m_character = v;
}
LocalPlayer::~LocalPlayer()
{
}
void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d,
core::list<CollisionInfo> *collision_info)
{
v3f position = getPosition();
v3f old_speed = m_speed;
/*
Calculate new position
*/
// Skip collision detection if a special movement mode is used
if (control.free) {
position += m_speed * dtime;
setPosition(position);
return;
}
/*
Check if player is in water (the oscillating value)
*/
try{
// If in water, the threshold of coming out is at higher y
if (in_water) {
v3s16 pp = floatToInt(position + v3f(0,BS*0.1,0), BS);
in_water = content_features(map.getNode(pp).getContent()).liquid_type != LIQUID_NONE;
// If not in water, the threshold of going in is at lower y
}else{
v3s16 pp = floatToInt(position + v3f(0,BS*0.5,0), BS);
in_water = content_features(map.getNode(pp).getContent()).liquid_type != LIQUID_NONE;
}
}catch(InvalidPositionException &e) {
in_water = false;
}
/*
Check if player is in water (the stable value)
*/
try{
v3s16 pp = floatToInt(position + v3f(0,0,0), BS);
in_water_stable = content_features(map.getNode(pp).getContent()).liquid_type != LIQUID_NONE;
}catch(InvalidPositionException &e) {
in_water_stable = false;
}
/*
Check if player is climbing
*/
try{
v3s16 pp = floatToInt(position + v3f(0,0.5*BS,0), BS);
v3s16 pp2 = floatToInt(position + v3f(0,-0.2*BS,0), BS);
is_climbing = ((content_features(map.getNode(pp).getContent()).climbable ||
content_features(map.getNode(pp2).getContent()).climbable) && !control.free);
}catch(InvalidPositionException &e) {
is_climbing = false;
}
/*
Collision uncertainty radius
Make it a bit larger than the maximum distance of movement
*/
//f32 d = pos_max_d * 1.1;
// A fairly large value in here makes moving smoother
f32 d = 0.15*BS;
// This should always apply, otherwise there are glitches
assert(d > pos_max_d);
float player_radius = BS*0.35;
float player_height = BS*1.7;
// Maximum distance over border for sneaking
f32 sneak_max = BS*0.4;
if (control.sneak && m_sneak_node_exists) {
f32 maxd = 0.5*BS + sneak_max;
v3f lwn_f = intToFloat(m_sneak_node, BS);
position.X = rangelim(position.X, lwn_f.X-maxd, lwn_f.X+maxd);
position.Z = rangelim(position.Z, lwn_f.Z-maxd, lwn_f.Z+maxd);
f32 min_y = lwn_f.Y + 0.5*BS;
if (position.Y < min_y) {
position.Y = min_y;
if(m_speed.Y < 0)
m_speed.Y = 0;
}
}
/*
Calculate player collision box (new and old)
*/
core::aabbox3d<f32> playerbox(
-player_radius,
0.0,
-player_radius,
player_radius,
player_height,
player_radius
);
float player_stepheight = touching_ground ? (BS*0.6) : (BS*0.2);
v3f accel_f = v3f(0,0,0);
collisionMoveResult result = collisionMoveSimple(&map,
pos_max_d, playerbox,
player_stepheight, dtime,
position,
m_speed,
accel_f);
/*
If the player's feet touch the topside of any node, this is
set to true.
Player is allowed to jump when this is true.
*/
touching_ground = result.touching_ground;
/*
Check the nodes under the player to see from which node the
player is sneaking from, if any. If the node from under
the player has been removed, the player falls.
*/
v3s16 current_node = floatToInt(position - v3f(0,BS/2,0), BS);
if (m_sneak_node_exists && map.getNodeNoEx(m_old_node_below).getContent() == CONTENT_AIR && m_old_node_below_type != CONTENT_AIR) {
// Old node appears to have been removed; that is,
// it wasn't air before but now it is
m_refresh_sneak_node = false;
m_sneak_node_exists = false;
}else if (map.getNodeNoEx(current_node).getContent() != CONTENT_AIR) {
// We are on something, so make sure to recalculate the sneak
// node.
m_refresh_sneak_node = true;
}
if (m_refresh_sneak_node) {
v3s16 pos_i_bottom = floatToInt(position - v3f(0,BS/2,0), BS);
v2f player_p2df(position.X, position.Z);
f32 min_distance_f = 100000.0*BS;
v3s16 new_sneak_node = m_sneak_node;
for (s16 x=-1; x<=1; x++)
for (s16 z=-1; z<=1; z++)
{
v3s16 p = pos_i_bottom + v3s16(x,0,z);
v3f pf = intToFloat(p, BS);
v2f node_p2df(pf.X, pf.Z);
f32 distance_f = player_p2df.getDistanceFrom(node_p2df);
f32 max_axis_distance_f = MYMAX(
fabs(player_p2df.X-node_p2df.X),
fabs(player_p2df.Y-node_p2df.Y));
if (distance_f > min_distance_f || max_axis_distance_f > 0.5*BS + sneak_max + 0.1*BS)
continue;
try{
// The node to be sneaked on has to be walkable
if (content_features(map.getNode(p).getContent()).walkable == false)
continue;
// And the node above it has to be nonwalkable
if (content_features(map.getNode(p+v3s16(0,1,0)).getContent()).walkable == true)
continue;
}catch(InvalidPositionException &e) {
continue;
}
min_distance_f = distance_f;
new_sneak_node = p;
}
bool sneak_node_found = (min_distance_f < 100000.0*BS*0.9);
m_sneak_node = new_sneak_node;
m_sneak_node_exists = sneak_node_found;
/*
If sneaking, the player's collision box can be in air, so
this has to be set explicitly
*/
if (sneak_node_found && control.sneak)
touching_ground = true;
}
/*
Set new position
*/
setPosition(position);
/*
Report collisions
*/
if (collision_info) {
// Report fall collision
if (old_speed.Y < m_speed.Y - 0.1 && !result.standing_on_unloaded) {
CollisionInfo info;
info.t = COLLISION_FALL;
info.speed = m_speed.Y - old_speed.Y;
collision_info->push_back(info);
}
}
}
void LocalPlayer::move(f32 dtime, Map &map, f32 pos_max_d)
{
move(dtime, map, pos_max_d, NULL);
}
void LocalPlayer::applyControl(float dtime)
{
// Clear stuff
swimming_up = false;
if (m_ignore_energy) {
m_can_use_energy = true;
m_energy = 20.0;
}
// Random constants
f32 walk_acceleration = 4.0 * BS;
f32 walkspeed_max = 4.0 * BS;
setPitch(control.pitch);
setYaw(control.yaw);
v3f move_direction = v3f(0,0,1);
move_direction.rotateXZBy(getYaw());
v3f speed = v3f(0,0,0);
if (control.fast && !m_can_use_energy)
control.fast = false;
if (control.free || is_climbing) {
v3f speed = getSpeed();
speed.Y = 0;
setSpeed(speed);
}
if (control.forward)
speed += move_direction;
if (control.backward)
speed -= move_direction;
if (control.left)
speed += move_direction.crossProduct(v3f(0,1,0));
if (control.right)
speed += move_direction.crossProduct(v3f(0,-1,0));
if (control.jump && !control.free && m_can_use_energy) {
if (touching_ground) {
v3f speed = getSpeed();
/*
NOTE: The d value in move() affects jump height by
raising the height at which the jump speed is kept
at its starting value
*/
if (speed.Y < 6*BS && !energy_effectf)
m_energy -= 0.6;
speed.Y = 6.5*BS;
setSpeed(speed);
}else if (in_water) {
// Use the oscillating value for getting out of water
// (so that the player doesn't fly on the surface)
v3f speed = getSpeed();
speed.Y = 0.8*BS;
setSpeed(speed);
swimming_up = true;
if (!energy_effectf)
m_energy -= dtime*0.8;
}else if (in_water_stable && !energy_effectf) {
m_energy -= dtime*0.8;
}
}
if (control.up) {
if (control.free) {
v3f speed = getSpeed();
if (control.fast) {
speed.Y = 20*BS;
}else{
speed.Y = walkspeed_max;
}
setSpeed(speed);
}else if (in_water) {
// Use the oscillating value for getting out of water
// (so that the player doesn't fly on the surface)
if (m_can_use_energy) {
v3f speed = getSpeed();
speed.Y = 1.5*BS;
setSpeed(speed);
swimming_up = true;
if (!energy_effectf)
m_energy -= dtime;
}
}else if (is_climbing) {
v3f speed = getSpeed();
speed.Y = 3*BS;
setSpeed(speed);
}else if (in_water_stable && m_can_use_energy && !energy_effectf) {
m_energy -= dtime;
}
}
if (control.down) {
if (control.free) {
// In free movement mode, aux1 descends
v3f speed = getSpeed();
if (control.fast) {
speed.Y = -20*BS;
}else{
speed.Y = -walkspeed_max;
}
setSpeed(speed);
}else if (is_climbing) {
v3f speed = getSpeed();
speed.Y = -3*BS;
setSpeed(speed);
}else if (in_water) {
v3f speed = getSpeed();
speed.Y = -5*BS;
setSpeed(speed);
}
}
// The speed of the player (Y is ignored)
if (control.fast && m_can_use_energy) {
if (!energy_effectf && (speed.X || speed.Y || speed.Z))
m_energy -= dtime;
if (control.free) {
speed = speed.normalize() * walkspeed_max * 6.0;
}else{
speed = speed.normalize() * walkspeed_max * 2.0;
}
}else{
if (energy_effectf) {
if (m_energy < health)
m_energy += dtime*25.0;
}else if (control.digging) {
m_energy -= dtime;
}else if (m_energy < health) {
if (speed.X || speed.Y || speed.Z) {
m_energy += dtime*((float)hunger/30.0);
}else{
m_energy += dtime*((float)hunger/15.0);
}
}
if (control.sneak) {
speed = speed.normalize() * walkspeed_max / 3.0;
}else{
speed = speed.normalize() * walkspeed_max;
}
}
if (m_energy > health) {
m_energy = health;
}else if (m_energy < -0.1) {
m_can_use_energy = false;
m_energy = -0.1;
#if USE_AUDIO == 1
if (m_low_energy_effect == 0) {
if (m_character == "")
m_character = std::string(PLAYER_DEFAULT_CHARDEF);
Strfnd f(m_character);
std::string gender = f.next(":");
std::string snd("low-energy-");
snd += gender;
m_low_energy_effect = sound_play_effect((char*)snd.c_str(),1.0,1,NULL);
}
#endif
}else if (m_energy > 9.8) {
m_can_use_energy = true;
#if USE_AUDIO == 1
if (m_low_energy_effect) {
sound_stop_single(m_low_energy_effect);
m_low_energy_effect = 0;
}
#endif
}
if (energy_effectf > 0.0)
energy_effectf -= dtime;
if (energy_effectf < 0.0)
energy_effectf = 0.0;
if (cold_effectf > 0.0)
cold_effectf -= dtime;
if (cold_effectf < 0.0)
cold_effectf = 0.0;
if (health == 100)
last_damage = 0;
f32 inc = walk_acceleration * BS * dtime;
// Faster acceleration if fast and free movement
if (control.free && control.fast)
inc = walk_acceleration * BS * dtime * 10;
// Accelerate to target speed with maximum increment
accelerate(speed, inc);
}
video::ITexture* LocalPlayer::getTexture()
{
std::string clothes[7];
const char* list[7] = {"hat","shirt","jacket","decorative","belt","pants","boots"};
std::vector<std::string> parts;
getSkin(parts);
for (int j=0; j<7; j++) {
InventoryList *l = inventory.getList(list[j]);
if (l == NULL)
continue;
InventoryItem *i = l->getItem(0);
if (i == NULL)
continue;
clothes[j] = content_clothesitem_features(i->getContent())->overlay_texture;
}
std::string tex = "";
tex += parts[0]; // skin
tex += "^" + parts[1]; // face
tex += "^" + parts[2]; // eyes
tex += "^" + clothes[5]; // pants
tex += "^" + clothes[1]; // shirt
tex += "^" + clothes[4]; // belt
tex += "^" + clothes[3]; // deco
tex += "^" + parts[3]; // hair
tex += "^" + clothes[2]; // jacket
tex += "^" + clothes[6]; // boots
tex += "^" + clothes[0]; // hat
return g_texturesource->getTextureRaw(tex);
}
#endif