1312 lines
32 KiB
C++
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
|
|
|