forked from Kolyah35/minecraft-pe-0.6.1
Added Hand Sway (not an option yet) from b1,8 Fire works properly in multiplayer now and also renders on undead mobs in sunlight now hopefully fixed bow stuck bug on touchscreen devices Fire on entites uses updated rendering from b1.6.6 Shadow bug fix attempt the 15th Added looking at block in debug screen Zombie Pigman now renders his hat layer Temporarily disabled most randomlevelsource cave, canyon and tall grass code just for this commit to test performance
865 lines
25 KiB
C++
Executable File
865 lines
25 KiB
C++
Executable File
#include "LocalPlayer.h"
|
|
#include "../Minecraft.h"
|
|
#include "../../ErrorCodes.h"
|
|
#include "../../world/entity/EntityEvent.h"
|
|
#include "../../world/entity/player/Player.h"
|
|
#include "../../world/inventory/BaseContainerMenu.h"
|
|
#include "../../world/item/BowItem.h"
|
|
#include "../../world/level/Level.h"
|
|
#include "../../world/level/tile/Tile.h"
|
|
#include "../../world/level/tile/entity/TileEntity.h"
|
|
#include "../../world/level/material/Material.h"
|
|
#include "../../network/packet/ContainerClosePacket.h"
|
|
#include "../../network/packet/MovePlayerPacket.h"
|
|
#include "../../network/packet/PlayerEquipmentPacket.h"
|
|
#include "../../network/RakNetInstance.h"
|
|
#include "../../network/packet/DropItemPacket.h"
|
|
#include "../../network/packet/SetHealthPacket.h"
|
|
#include "../../network/packet/SendInventoryPacket.h"
|
|
#include "../../network/packet/EntityEventPacket.h"
|
|
#include "../../network/packet/PlayerActionPacket.h"
|
|
#include <vector>
|
|
#include <cctype>
|
|
#include "../../platform/log.h"
|
|
#include "../../platform/HttpClient.h"
|
|
#include "../../platform/CThread.h"
|
|
#include "../../util/StringUtils.h"
|
|
#include "client/Options.h"
|
|
|
|
#if defined(_WIN32)
|
|
#include <direct.h>
|
|
#include <sys/stat.h>
|
|
#else
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <errno.h>
|
|
#endif
|
|
|
|
#ifndef STANDALONE_SERVER
|
|
#include "../gui/Screen.h"
|
|
#include "../gui/screens/FurnaceScreen.h"
|
|
#include "../gui/screens/ChestScreen.h"
|
|
#include "../gui/screens/crafting/WorkbenchScreen.h"
|
|
#include "../gui/screens/crafting/StonecutterScreen.h"
|
|
#include "../gui/screens/InBedScreen.h"
|
|
#include "../gui/screens/TextEditScreen.h"
|
|
#include "../particle/TakeAnimationParticle.h"
|
|
#endif
|
|
#include "../../network/packet/AnimatePacket.h"
|
|
#include "../../world/item/ArmorItem.h"
|
|
#include "../../network/packet/PlayerArmorEquipmentPacket.h"
|
|
|
|
namespace {
|
|
#ifndef STANDALONE_SERVER
|
|
|
|
static bool isBase64(unsigned char c) {
|
|
return (std::isalnum(c) || (c == '+') || (c == '/'));
|
|
}
|
|
|
|
static std::string base64Decode(const std::string& encoded) {
|
|
static const std::string base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
std::string out;
|
|
int in_len = (int)encoded.size();
|
|
int i = 0;
|
|
int in_ = 0;
|
|
unsigned char char_array_4[4], char_array_3[3];
|
|
|
|
while (in_len-- && (encoded[in_] != '=') && isBase64(encoded[in_])) {
|
|
char_array_4[i++] = encoded[in_]; in_++;
|
|
if (i == 4) {
|
|
for (i = 0; i < 4; i++)
|
|
char_array_4[i] = (unsigned char)base64Chars.find(char_array_4[i]);
|
|
|
|
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
|
|
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
|
|
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
|
|
|
|
for (i = 0; i < 3; i++)
|
|
out += char_array_3[i];
|
|
i = 0;
|
|
}
|
|
}
|
|
|
|
if (i) {
|
|
for (int j = i; j < 4; j++)
|
|
char_array_4[j] = 0;
|
|
for (int j = 0; j < 4; j++)
|
|
char_array_4[j] = (unsigned char)base64Chars.find(char_array_4[j]);
|
|
|
|
char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
|
|
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
|
|
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];
|
|
|
|
for (int j = 0; (j < i - 1); j++)
|
|
out += char_array_3[j];
|
|
}
|
|
return out;
|
|
}
|
|
|
|
static std::string extractJsonString(const std::string& json, const std::string& key) {
|
|
std::string search = "\"" + key + "\"";
|
|
size_t pos = json.find(search);
|
|
if (pos == std::string::npos) return "";
|
|
pos = json.find(':', pos + search.size());
|
|
if (pos == std::string::npos) return "";
|
|
pos++;
|
|
while (pos < json.size() && std::isspace((unsigned char)json[pos])) pos++;
|
|
if (pos >= json.size() || json[pos] != '"') return "";
|
|
pos++;
|
|
size_t end = json.find('"', pos);
|
|
if (end == std::string::npos) return "";
|
|
return json.substr(pos, end - pos);
|
|
}
|
|
|
|
static std::string getTextureUrlForUsername(const std::string& username, const std::string& textureKey) {
|
|
if (username.empty()) {
|
|
LOGI("[%s] username empty\n", textureKey.c_str());
|
|
return "";
|
|
}
|
|
|
|
LOGI("[%s] resolving UUID for user '%s'...\n", textureKey.c_str(), username.c_str());
|
|
std::vector<unsigned char> body;
|
|
std::string apiUrl = "http://api.mojang.com/users/profiles/minecraft/" + username;
|
|
if (!HttpClient::download(apiUrl, body)) {
|
|
LOGW("[%s] failed to download UUID for %s\n", textureKey.c_str(), username.c_str());
|
|
return "";
|
|
}
|
|
|
|
std::string response(body.begin(), body.end());
|
|
std::string uuid = extractJsonString(response, "id");
|
|
if (uuid.empty()) {
|
|
LOGW("[%s] no UUID found in Mojang response for %s\n", textureKey.c_str(), username.c_str());
|
|
return "";
|
|
}
|
|
|
|
LOGI("[%s] UUID=%s for user %s\n", textureKey.c_str(), uuid.c_str(), username.c_str());
|
|
|
|
std::string profileUrl = "http://sessionserver.mojang.com/session/minecraft/profile/" + uuid;
|
|
if (!HttpClient::download(profileUrl, body)) {
|
|
LOGW("[%s] failed to download profile for UUID %s\n", textureKey.c_str(), uuid.c_str());
|
|
return "";
|
|
}
|
|
|
|
response.assign(body.begin(), body.end());
|
|
std::string encoded = extractJsonString(response, "value");
|
|
if (encoded.empty()) {
|
|
LOGW("[%s] no value field in profile response for UUID %s\n", textureKey.c_str(), uuid.c_str());
|
|
return "";
|
|
}
|
|
|
|
std::string decoded = base64Decode(encoded);
|
|
|
|
std::string searchKey = "\"" + textureKey + "\"";
|
|
size_t texturePos = decoded.find(searchKey);
|
|
if (texturePos == std::string::npos) {
|
|
LOGW("[%s] no %s entry in decoded profile for UUID %s\n", textureKey.c_str(), textureKey.c_str(), uuid.c_str());
|
|
return "";
|
|
}
|
|
size_t urlPos = decoded.find("\"url\"", texturePos);
|
|
if (urlPos == std::string::npos) {
|
|
LOGW("[%s] no url field under %s for UUID %s\n", textureKey.c_str(), textureKey.c_str(), uuid.c_str());
|
|
return "";
|
|
}
|
|
|
|
// extract the URL value from the substring starting at urlPos
|
|
std::string urlFragment = decoded.substr(urlPos);
|
|
std::string textureUrl = extractJsonString(urlFragment, "url");
|
|
if (textureUrl.empty()) {
|
|
LOGW("[%s] failed to parse %s URL for UUID %s\n", textureKey.c_str(), textureKey.c_str(), uuid.c_str());
|
|
return "";
|
|
}
|
|
|
|
LOGI("[%s] %s URL for %s: %s\n", textureKey.c_str(), textureKey.c_str(), username.c_str(), textureUrl.c_str());
|
|
return textureUrl;
|
|
}
|
|
|
|
static std::string getSkinUrlForUsername(const std::string& username) {
|
|
return getTextureUrlForUsername(username, "SKIN");
|
|
}
|
|
|
|
static std::string getCapeUrlForUsername(const std::string& username) {
|
|
return getTextureUrlForUsername(username, "CAPE");
|
|
}
|
|
|
|
#endif
|
|
|
|
static bool ensureDirectoryExists(const std::string& path) {
|
|
#if defined(_WIN32)
|
|
return _mkdir(path.c_str()) == 0 || errno == EEXIST;
|
|
#else
|
|
struct stat st;
|
|
if (stat(path.c_str(), &st) == 0 && S_ISDIR(st.st_mode))
|
|
return true;
|
|
|
|
std::string subPath;
|
|
size_t i = 0;
|
|
while (i < path.length()) {
|
|
i = path.find_first_of("/\\", i);
|
|
if (i == std::string::npos) {
|
|
subPath = path;
|
|
} else {
|
|
subPath = path.substr(0, i);
|
|
}
|
|
|
|
if (!subPath.empty()) {
|
|
if (stat(subPath.c_str(), &st) != 0) {
|
|
if (mkdir(subPath.c_str(), 0755) != 0 && errno != EEXIST)
|
|
return false;
|
|
} else if (!S_ISDIR(st.st_mode)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (i == std::string::npos)
|
|
break;
|
|
i += 1;
|
|
}
|
|
|
|
if (stat(path.c_str(), &st) == 0 && S_ISDIR(st.st_mode))
|
|
return true;
|
|
return mkdir(path.c_str(), 0755) == 0 || errno == EEXIST;
|
|
#endif
|
|
}
|
|
|
|
static bool fileExists(const std::string& path) {
|
|
struct stat st;
|
|
if (stat(path.c_str(), &st) != 0)
|
|
return false;
|
|
|
|
#if defined(_WIN32)
|
|
return (st.st_mode & _S_IFREG) != 0;
|
|
#else
|
|
return S_ISREG(st.st_mode);
|
|
#endif
|
|
}
|
|
|
|
#ifndef STANDALONE_SERVER
|
|
|
|
static void* fetchSkinForPlayer(void* param) {
|
|
LocalPlayer* player = (LocalPlayer*)param;
|
|
if (!player) return NULL;
|
|
|
|
LOGI("[Skin] starting skin download for %s\n", player->name.c_str());
|
|
|
|
const std::string cacheDir = "data/images/skins";
|
|
if (!ensureDirectoryExists(cacheDir)) {
|
|
LOGW("[Skin] failed to create cache directory %s\n", cacheDir.c_str());
|
|
}
|
|
|
|
std::string cacheFile = cacheDir + "/" + player->name + ".png";
|
|
if (fileExists(cacheFile)) {
|
|
LOGI("[Skin] using cached skin for %s\n", player->name.c_str());
|
|
player->setTextureName("skins/" + player->name + ".png");
|
|
return NULL;
|
|
}
|
|
|
|
std::string skinUrl = getSkinUrlForUsername(player->name);
|
|
if (skinUrl.empty()) {
|
|
LOGW("[Skin] skin URL lookup failed for %s\n", player->name.c_str());
|
|
player->setTextureName("mob/char.png");
|
|
return NULL;
|
|
}
|
|
|
|
LOGI("[Skin] downloading skin from %s\n", skinUrl.c_str());
|
|
std::vector<unsigned char> skinData;
|
|
if (!HttpClient::download(skinUrl, skinData) || skinData.empty()) {
|
|
LOGW("[Skin] download failed for %s\n", skinUrl.c_str());
|
|
player->setTextureName("mob/char.png");
|
|
return NULL;
|
|
}
|
|
|
|
// Save to cache
|
|
FILE* fp = fopen(cacheFile.c_str(), "wb");
|
|
if (fp) {
|
|
fwrite(skinData.data(), 1, skinData.size(), fp);
|
|
fclose(fp);
|
|
LOGI("[Skin] cached skin to %s\n", cacheFile.c_str());
|
|
|
|
player->setTextureName("skins/" + player->name + ".png");
|
|
} else {
|
|
LOGW("[Skin] failed to write skin cache %s\n", cacheFile.c_str());
|
|
player->setTextureName("mob/char.png");
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void* fetchCapeForPlayer(void* param) {
|
|
LocalPlayer* player = (LocalPlayer*)param;
|
|
if (!player) return NULL;
|
|
|
|
LOGI("[Cape] starting cape download for %s\n", player->name.c_str());
|
|
|
|
const std::string cacheDir = "data/images/capes";
|
|
if (!ensureDirectoryExists(cacheDir)) {
|
|
LOGW("[Cape] failed to create cache directory %s\n", cacheDir.c_str());
|
|
}
|
|
|
|
std::string cacheFile = cacheDir + "/" + player->name + ".png";
|
|
if (fileExists(cacheFile)) {
|
|
LOGI("[Cape] using cached cape for %s\n", player->name.c_str());
|
|
player->setCapeTextureName("capes/" + player->name + ".png");
|
|
return NULL;
|
|
}
|
|
|
|
std::string capeUrl = getCapeUrlForUsername(player->name);
|
|
if (capeUrl.empty()) {
|
|
LOGW("[Cape] cape URL lookup failed for %s\n", player->name.c_str());
|
|
return NULL;
|
|
}
|
|
|
|
LOGI("[Cape] downloading cape from %s\n", capeUrl.c_str());
|
|
std::vector<unsigned char> capeData;
|
|
if (!HttpClient::download(capeUrl, capeData) || capeData.empty()) {
|
|
LOGW("[Cape] download failed for %s\n", capeUrl.c_str());
|
|
return NULL;
|
|
}
|
|
|
|
// Save to cache
|
|
FILE* fp = fopen(cacheFile.c_str(), "wb");
|
|
if (fp) {
|
|
fwrite(capeData.data(), 1, capeData.size(), fp);
|
|
fclose(fp);
|
|
LOGI("[Cape] cached cape to %s\n", cacheFile.c_str());
|
|
|
|
player->setCapeTextureName("capes/" + player->name + ".png");
|
|
} else {
|
|
LOGW("[Cape] failed to write cape cache %s\n", cacheFile.c_str());
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
#endif
|
|
|
|
//@note: doesn't work completely, since it doesn't care about stairs rotation
|
|
static bool isJumpable(int tileId) {
|
|
return tileId != Tile::fence->id
|
|
&& tileId != Tile::fenceGate->id
|
|
&& tileId != Tile::stoneSlabHalf->id
|
|
&& tileId != Tile::trapdoor->id
|
|
&& tileId != Tile::sign->id
|
|
&& tileId != Tile::wallSign->id
|
|
&& (Tile::tiles[tileId] != NULL && Tile::tiles[tileId]->getRenderShape() != Tile::SHAPE_STAIRS);
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
LocalPlayer::LocalPlayer(Minecraft* minecraft, Level* level, const std::string& username, int dimension, bool isCreative)
|
|
: Player(level, isCreative),
|
|
minecraft(minecraft),
|
|
input(NULL),
|
|
sentInventoryItemId(-1),
|
|
sentInventoryItemData(-1),
|
|
autoJumpEnabled(true),
|
|
armorTypeHash(0),
|
|
sprinting(false),
|
|
sprintDoubleTapTimer(0),
|
|
prevForwardHeld(false),
|
|
xBob(0.0f),
|
|
yBob(0.0f),
|
|
xBobO(0.0f),
|
|
yBobO(0.0f)
|
|
{
|
|
this->dimension = dimension;
|
|
_init();
|
|
#ifndef STANDALONE_SERVER
|
|
|
|
if (minecraft->options.getStringValue(OPTIONS_USERNAME).size() != 0) {
|
|
textureName = "mob/char.png";
|
|
|
|
this->name = minecraft->options.getStringValue(OPTIONS_USERNAME);
|
|
printf("test \n");
|
|
// Fetch user skin and cape from Mojang servers in the background (avoids blocking the main thread)
|
|
// TODO: Fix this memory leak
|
|
new CThread(fetchSkinForPlayer, this);
|
|
new CThread(fetchCapeForPlayer, this);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
LocalPlayer::~LocalPlayer() {
|
|
//delete input;
|
|
//input = NULL;
|
|
}
|
|
|
|
/*private*/
|
|
void LocalPlayer::calculateFlight(float xa, float ya, float za) {
|
|
float flySpeed = minecraft->options.getProgressValue(OPTIONS_FLY_SPEED);
|
|
float sensivity = minecraft->options.getProgressValue(OPTIONS_SENSITIVITY);
|
|
|
|
xa = xa * flySpeed;
|
|
ya = 0;
|
|
za = za * flySpeed;
|
|
|
|
if (sprinting) {
|
|
float sprintBoost = getWalkingSpeedModifier(); // 1.3x
|
|
xa *= sprintBoost;
|
|
za *= sprintBoost;
|
|
}
|
|
|
|
#ifdef ANDROID
|
|
if (Keyboard::isKeyDown(103)) ya = .2f * flySpeed;
|
|
if (Keyboard::isKeyDown(102)) ya = -.2f * flySpeed;
|
|
#else
|
|
if (Keyboard::isKeyDown(Keyboard::KEY_E)) ya = .2f * flySpeed;
|
|
if (Keyboard::isKeyDown(Keyboard::KEY_Q)) ya = -.2f * flySpeed;
|
|
#endif
|
|
|
|
flyX = 10 * smoothFlyX.getNewDeltaValue(xa, .35f * sensivity);
|
|
flyY = 10 * smoothFlyY.getNewDeltaValue(ya, .35f * sensivity);
|
|
flyZ = 10 * smoothFlyZ.getNewDeltaValue(za, .35f * sensivity);
|
|
}
|
|
|
|
bool LocalPlayer::isSolidTile(int x, int y, int z) {
|
|
int tileId = level->getTile(x, y, z);
|
|
return tileId > 0 && Tile::tiles[tileId]->material->isSolid();
|
|
}
|
|
|
|
void LocalPlayer::tick() {
|
|
|
|
super::tick();
|
|
if(!useItem.isNull()) {
|
|
ItemInstance* item = inventory->getSelected();
|
|
if(item != NULL && *item == useItem) {
|
|
if (useItemDuration <= 25 && useItemDuration % 4 == 0) {
|
|
spawnEatParticles(item, 5);
|
|
}
|
|
if(--useItemDuration == 0) {
|
|
if(!level->isClientSide) {
|
|
completeUsingItem();
|
|
} else {
|
|
EntityEventPacket p(entityId, EntityEvent::USE_ITEM_COMPLETE);
|
|
level->raknetInstance->send(p);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
stopUsingItem();
|
|
}
|
|
}
|
|
if (minecraft->isOnline())
|
|
{
|
|
if (std::abs(x - sentX) > .1f || std::abs(y - sentY) > .01f || std::abs(z - sentZ) > .1f || std::abs(sentRotX - xRot) > 1 || std::abs(sentRotY - yRot) > 1)
|
|
{
|
|
MovePlayerPacket packet(entityId, x, y - heightOffset, z, xRot, yRot);
|
|
minecraft->raknetInstance->send(packet);
|
|
sentX = x;
|
|
sentY = y;
|
|
sentZ = z;
|
|
sentRotX = xRot;
|
|
sentRotY = yRot;
|
|
}
|
|
|
|
ItemInstance* item = inventory->getSelected();
|
|
int newItemId = (item && item->count > 0) ? item->id : 0;
|
|
int newItemData = (item && item->count > 0) ? item->getAuxValue() : 0;
|
|
|
|
if (sentInventoryItemId != newItemId
|
|
|| sentInventoryItemData != newItemData)
|
|
{
|
|
sentInventoryItemId = newItemId;
|
|
sentInventoryItemData = newItemData;
|
|
PlayerEquipmentPacket packet(entityId, newItemId, newItemData);
|
|
minecraft->raknetInstance->send(packet);
|
|
}
|
|
}
|
|
/*
|
|
for (int i = 0; i < 4; ++i) {
|
|
ItemInstance* a = getArmor(i);
|
|
if (!a) continue;
|
|
|
|
ArmorItem* item = (ArmorItem*) a->getItem();
|
|
|
|
printf("armor %d: %d\n", i, a->getAuxValue());
|
|
}
|
|
*/
|
|
|
|
updateArmorTypeHash();
|
|
#ifndef STANDALONE_SERVER
|
|
if (!minecraft->screen && containerMenu) {
|
|
static bool hasPostedError = false;
|
|
if (!hasPostedError) {
|
|
minecraft->gui.postError( ErrorCodes::ContainerRefStillExistsAfterDestruction );
|
|
hasPostedError = true;
|
|
}
|
|
}
|
|
#endif
|
|
//LOGI("biome: %s\n", level->getBiomeSource()->getBiome((int)x >> 4, (int)z >> 4)->name.c_str());
|
|
}
|
|
|
|
/*public*/
|
|
void LocalPlayer::aiStep() {
|
|
jumpTriggerTime--;
|
|
ascendTriggerTime--;
|
|
descendTriggerTime--;
|
|
|
|
bool wasJumping = input->jumping;
|
|
#ifndef STANDALONE_SERVER
|
|
bool screenCovering = minecraft->screen && !minecraft->screen->passEvents;
|
|
if (!screenCovering)
|
|
input->tick(this);
|
|
|
|
// Sprint: detect W double-tap
|
|
{
|
|
bool forwardHeld = (input->ya > 0);
|
|
if (forwardHeld && !prevForwardHeld && minecraft->options.getBooleanValue(OPTIONS_ALLOW_SPRINT)) {
|
|
// leading edge of W press
|
|
if (sprintDoubleTapTimer > 0)
|
|
sprinting = true;
|
|
else
|
|
sprintDoubleTapTimer = 7;
|
|
}
|
|
if (!forwardHeld) {
|
|
sprinting = false;
|
|
}
|
|
if (sprintDoubleTapTimer > 0) sprintDoubleTapTimer--;
|
|
prevForwardHeld = forwardHeld;
|
|
}
|
|
if (input->sneaking)
|
|
sprinting = false;
|
|
|
|
if (input->sneaking) {
|
|
if (ySlideOffset < 0.2f) ySlideOffset = 0.2f;
|
|
}
|
|
#endif
|
|
if (abilities.mayfly) {
|
|
// Check for flight toggle
|
|
if (!wasJumping && input->jumping) {
|
|
if (jumpTriggerTime <= 0) jumpTriggerTime = 7;
|
|
else {
|
|
abilities.flying = !abilities.flying;
|
|
jumpTriggerTime = 0;
|
|
}
|
|
}
|
|
if (abilities.flying) {
|
|
if (input->wantUp) {
|
|
yd += 0.15f;
|
|
//xd = zd = 0;
|
|
}
|
|
if (input->wantDown) {
|
|
yd -= 0.15f;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(isUsingItem()) {
|
|
const float k = 0.35f;
|
|
input->xa *= k;
|
|
input->ya *= k;
|
|
}
|
|
|
|
Mob::aiStep();
|
|
super::aiStep();
|
|
|
|
//if (onGround && abilities.flying)
|
|
// abilities.flying = false;
|
|
yBobO = yBob;
|
|
xBobO = xBob;
|
|
xBob += (xRot - xBob) * 0.5;
|
|
yBob += (yRot - yBob) * 0.5;
|
|
|
|
if (interpolateOnly())
|
|
updateAi();
|
|
}
|
|
|
|
/*public*/
|
|
void LocalPlayer::closeContainer() {
|
|
if (level->isClientSide) {
|
|
ContainerClosePacket packet(containerMenu->containerId);
|
|
minecraft->raknetInstance->send(packet);
|
|
}
|
|
super::closeContainer();
|
|
minecraft->setScreen(NULL);
|
|
}
|
|
|
|
//@Override
|
|
void LocalPlayer::move(float xa, float ya, float za) {
|
|
//@note: why is this == minecraft->player needed?
|
|
if (this == minecraft->player && minecraft->options.getBooleanValue(OPTIONS_IS_FLYING)) {
|
|
noPhysics = true;
|
|
float tmp = walkDist; // update
|
|
calculateFlight((float) xa, (float) ya, (float) za);
|
|
fallDistance = 0;
|
|
yd = 0;
|
|
super::move(flyX, flyY, flyZ);
|
|
onGround = true;
|
|
walkDist = tmp;
|
|
} else {
|
|
if (autoJumpTime > 0) {
|
|
autoJumpTime--;
|
|
input->jumping = true;
|
|
}
|
|
float prevX = x, prevZ = z;
|
|
|
|
super::move(xa, ya, za);
|
|
|
|
float newX = x, newZ = z;
|
|
|
|
if (autoJumpTime <= 0 && minecraft->options.getBooleanValue(OPTIONS_AUTOJUMP))
|
|
{
|
|
// auto-jump when crossing the middle of a tile, and the tile in the front is blocked
|
|
bool jump = false;
|
|
if (Mth::floor(prevX * 2.0f) != Mth::floor(newX * 2.0f) || Mth::floor(prevZ * 2.0f) != Mth::floor(newZ * 2.0f))
|
|
{
|
|
float dist = Mth::sqrt(xa * xa + za * za);
|
|
const int xx = Mth::floor(x + xa / dist);
|
|
const int zz = Mth::floor(z + za / dist);
|
|
const int tileId = level->getTile(xx, (int)(y-1), zz);
|
|
jump = (isSolidTile(xx, (int)(y-1), zz) // Solid block to jump up on
|
|
&& !isSolidTile(xx, (int)y, zz) && !isSolidTile(xx, (int)(y+1), zz)) // Enough space
|
|
&& isJumpable(tileId);
|
|
}
|
|
if (jump)
|
|
{
|
|
autoJumpTime = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void LocalPlayer::updateAi() {
|
|
super::updateAi();
|
|
this->xxa = input->xa;
|
|
this->yya = input->ya;
|
|
this->jumping = input->jumping || autoJumpTime > 0;
|
|
}
|
|
|
|
void LocalPlayer::take( Entity* e, int orgCount )
|
|
{
|
|
#ifndef STANDALONE_SERVER
|
|
if (e->isItemEntity())
|
|
minecraft->particleEngine->add(new TakeAnimationParticle(minecraft->level, (ItemEntity*)e, this, -0.5f));
|
|
#endif
|
|
}
|
|
|
|
void LocalPlayer::setKey( int eventKey, bool eventKeyState )
|
|
{
|
|
input->setKey(eventKey, eventKeyState);
|
|
}
|
|
|
|
void LocalPlayer::releaseAllKeys()
|
|
{
|
|
if (input) input->releaseAllKeys();
|
|
}
|
|
|
|
float LocalPlayer::getWalkingSpeedModifier() {
|
|
return sprinting ? 1.3f : 1.0f;
|
|
}
|
|
|
|
float LocalPlayer::getFieldOfViewModifier() {
|
|
float targetFov = 1.0f;
|
|
if(abilities.flying) targetFov *= 1.1f;
|
|
targetFov *= ((walkingSpeed * getWalkingSpeedModifier()) / DEFAULT_WALK_SPEED +1) / 2;
|
|
|
|
if(isUsingItem() && getUseItem()->id == Item::bow->id) {
|
|
float ticksHeld = (float)getTicksUsingItem();
|
|
float scale = ticksHeld / BowItem::MAX_DRAW_DURATION;
|
|
if(scale > 1) {
|
|
scale = 1;
|
|
}
|
|
else {
|
|
scale *= scale;
|
|
}
|
|
targetFov *= 1.0f - scale * 0.15f;
|
|
}
|
|
return targetFov;
|
|
}
|
|
void LocalPlayer::addAdditonalSaveData( CompoundTag* entityTag )
|
|
{
|
|
super::addAdditonalSaveData(entityTag);
|
|
entityTag->putInt("Score", score);
|
|
}
|
|
|
|
void LocalPlayer::readAdditionalSaveData( CompoundTag* entityTag )
|
|
{
|
|
super::readAdditionalSaveData(entityTag);
|
|
score = entityTag->getInt("Score");
|
|
}
|
|
|
|
bool LocalPlayer::isSneaking()
|
|
{
|
|
return input->sneaking;
|
|
}
|
|
|
|
void LocalPlayer::hurtTo( int newHealth )
|
|
{
|
|
int dmg = health - newHealth;
|
|
if (dmg <= 0) {
|
|
this->health = newHealth;
|
|
} else {
|
|
lastHurt = dmg;
|
|
lastHealth = health;
|
|
invulnerableTime = invulnerableDuration;
|
|
|
|
minecraft->player->bypassArmor = true;
|
|
actuallyHurt(dmg);
|
|
minecraft->player->bypassArmor = false;
|
|
|
|
hurtTime = hurtDuration = 10;
|
|
}
|
|
}
|
|
|
|
void LocalPlayer::actuallyHurt( int dmg )
|
|
{
|
|
#ifndef STANDALONE_SERVER
|
|
if (minecraft->screen && minecraft->screen->closeOnPlayerHurt()) {
|
|
if (containerMenu) closeContainer();
|
|
else minecraft->setScreen(NULL);
|
|
}
|
|
#endif
|
|
super::actuallyHurt(dmg);
|
|
}
|
|
|
|
void LocalPlayer::respawn()
|
|
{
|
|
minecraft->respawnPlayer();
|
|
}
|
|
|
|
void LocalPlayer::die(Entity* source)
|
|
{
|
|
// If we're an online client, send the inventory to be dropped
|
|
// If we're the server, drop the inventory immediately
|
|
if (level->isClientSide) {
|
|
SendInventoryPacket packet(this, true);
|
|
minecraft->raknetInstance->send(packet);
|
|
}
|
|
inventory->dropAll(level->isClientSide);
|
|
for (int i = 0; i < NUM_ARMOR; ++i) {
|
|
ItemInstance* item = getArmor(i);
|
|
if (!ItemInstance::isArmorItem(item)) return;
|
|
|
|
drop(new ItemInstance(*item), true);
|
|
setArmor(i, NULL);
|
|
}
|
|
|
|
super::die(source);
|
|
}
|
|
|
|
void LocalPlayer::swing() {
|
|
super::swing();
|
|
|
|
if (swingTime == -1) {
|
|
AnimatePacket packet(AnimatePacket::Swing, this);
|
|
packet.reliability = UNRELIABLE;
|
|
packet.priority = MEDIUM_PRIORITY;
|
|
minecraft->raknetInstance->send(packet);
|
|
}
|
|
}
|
|
|
|
void LocalPlayer::reset() {
|
|
super::reset();
|
|
this->_init();
|
|
}
|
|
|
|
void LocalPlayer::_init() {
|
|
autoJumpTime = 0;
|
|
jumpTriggerTime = 0;
|
|
ascendTriggerTime = 0;
|
|
descendTriggerTime = 0;
|
|
ascending = false;
|
|
descending = false;
|
|
|
|
ItemInstance* item = inventory->getSelected();
|
|
sentInventoryItemId = item? item->id : 0;
|
|
sentInventoryItemData = item? item->getAuxValue() : 0;
|
|
}
|
|
|
|
void LocalPlayer::startCrafting(int x, int y, int z, int tableSize) {
|
|
#ifndef STANDALONE_SERVER
|
|
if (!minecraft->isCreativeMode())
|
|
minecraft->setScreen( new WorkbenchScreen(tableSize) );
|
|
#endif
|
|
}
|
|
|
|
void LocalPlayer::startStonecutting(int x, int y, int z) {
|
|
#ifndef STANDALONE_SERVER
|
|
if (!minecraft->isCreativeMode())
|
|
minecraft->setScreen( new StonecutterScreen() );
|
|
#endif
|
|
}
|
|
|
|
void LocalPlayer::openFurnace( FurnaceTileEntity* e ) {
|
|
#ifndef STANDALONE_SERVER
|
|
if (!minecraft->isCreativeMode())
|
|
minecraft->setScreen( new FurnaceScreen(this, e) );
|
|
#endif
|
|
}
|
|
|
|
void LocalPlayer::openContainer( ChestTileEntity* container ) {
|
|
#ifndef STANDALONE_SERVER
|
|
if (!minecraft->isCreativeMode())
|
|
minecraft->setScreen( new ChestScreen(this, container) );
|
|
#endif
|
|
}
|
|
|
|
void LocalPlayer::drop( ItemInstance* item, bool randomly )
|
|
{
|
|
if (!item)
|
|
return;
|
|
|
|
if (level->isClientSide) {
|
|
DropItemPacket packet(entityId, *item);
|
|
minecraft->raknetInstance->send(packet);
|
|
// delete the ItemEntity here, since we don't add it to level
|
|
delete item;
|
|
} else {
|
|
super::drop(item, randomly);
|
|
}
|
|
}
|
|
|
|
void LocalPlayer::causeFallDamage( float distance )
|
|
{
|
|
int dmg = (int) ceil((distance - 3));
|
|
if (dmg > 0) {
|
|
if (level->isClientSide) {
|
|
SetHealthPacket packet(SetHealthPacket::HEALTH_MODIFY_OFFSET + dmg);
|
|
minecraft->raknetInstance->send(packet);
|
|
}
|
|
}
|
|
super::causeFallDamage(distance);
|
|
|
|
}
|
|
|
|
void LocalPlayer::displayClientMessage( const std::string& messageId ) {
|
|
#ifndef STANDALONE_SERVER
|
|
minecraft->gui.displayClientMessage(messageId);
|
|
#endif
|
|
}
|
|
|
|
int LocalPlayer::startSleepInBed( int x, int y, int z ) {
|
|
int startSleepInBedReturnValue = super::startSleepInBed(x, y, z);
|
|
#ifndef STANDALONE_SERVER
|
|
if(startSleepInBedReturnValue == BedSleepingResult::OK)
|
|
minecraft->setScreen(new InBedScreen());
|
|
#endif
|
|
return startSleepInBedReturnValue;
|
|
}
|
|
|
|
void LocalPlayer::stopSleepInBed( bool forcefulWakeUp, bool updateLevelList, bool saveRespawnPoint ) {
|
|
if(level->isClientSide) {
|
|
PlayerActionPacket packet(PlayerActionPacket::STOP_SLEEPING, 0, 0, 0, 0, entityId);
|
|
minecraft->raknetInstance->send(packet);
|
|
}
|
|
#ifndef STANDALONE_SERVER
|
|
minecraft->setScreen(NULL);
|
|
#endif
|
|
super::stopSleepInBed(forcefulWakeUp, updateLevelList, saveRespawnPoint);
|
|
}
|
|
|
|
void LocalPlayer::openTextEdit( TileEntity* tileEntity ) {
|
|
#if !defined(STANDALONE_SERVER) && !defined(RPI)
|
|
if(tileEntity->type == TileEntityType::Sign)
|
|
minecraft->setScreen(new TextEditScreen((SignTileEntity*) tileEntity));
|
|
#endif
|
|
}
|
|
|
|
void LocalPlayer::updateArmorTypeHash() {
|
|
int hash = getArmorTypeHash();
|
|
if (hash != armorTypeHash) {
|
|
PlayerArmorEquipmentPacket p(this);
|
|
minecraft->raknetInstance->send(p);
|
|
armorTypeHash = hash;
|
|
}
|
|
}
|