mirror of
https://github.com/4jcraft/4jcraft.git
synced 2026-04-24 09:07:48 +00:00
2683 lines
89 KiB
C++
2683 lines
89 KiB
C++
// 4J TODO
|
|
|
|
// All the instanceof s from Java have been converted to dynamic_cast in this
|
|
// file Once all the classes are finished it may be that we do not need to use
|
|
// dynamic_cast for every test and a simple virtual function should suffice. We
|
|
// probably only need dynamic_cast to find one of the classes that an object
|
|
// derives from, and not to find the derived class itself (which should own the
|
|
// virtual GetType function)
|
|
|
|
#include "../Platform/stdafx.h"
|
|
#include "../Util/JavaMath.h"
|
|
#include "../Headers/net.minecraft.h"
|
|
#include "../Headers/net.minecraft.world.h"
|
|
#include "../Headers/net.minecraft.stats.h"
|
|
#include "../Headers/net.minecraft.world.level.h"
|
|
#include "../Headers/net.minecraft.world.level.chunk.h"
|
|
#include "../Headers/net.minecraft.world.phys.h"
|
|
#include "../Headers/net.minecraft.world.entity.h"
|
|
#include "../Headers/net.minecraft.world.entity.ai.attributes.h"
|
|
#include "../Headers/net.minecraft.world.entity.animal.h"
|
|
#include "../Headers/net.minecraft.world.entity.boss.h"
|
|
#include "../Headers/net.minecraft.world.entity.monster.h"
|
|
#include "../Headers/net.minecraft.world.entity.item.h"
|
|
#include "../Headers/net.minecraft.world.item.h"
|
|
#include "../Headers/net.minecraft.world.item.enchantment.h"
|
|
#include "../Headers/net.minecraft.world.level.dimension.h"
|
|
#include "../Headers/net.minecraft.world.level.material.h"
|
|
#include "../Headers/net.minecraft.world.level.tile.h"
|
|
#include "../Headers/net.minecraft.world.level.tile.entity.h"
|
|
#include "../Headers/net.minecraft.world.scores.h"
|
|
#include "../Headers/net.minecraft.world.scores.criteria.h"
|
|
#include "../Headers/net.minecraft.world.entity.projectile.h"
|
|
#include "../Headers/net.minecraft.world.inventory.h"
|
|
#include "../Headers/net.minecraft.world.damagesource.h"
|
|
#include "../Headers/net.minecraft.world.effect.h"
|
|
#include "../Headers/net.minecraft.world.food.h"
|
|
#include "../Containers/Inventory.h"
|
|
#include "Player.h"
|
|
#include "../Util/ParticleTypes.h"
|
|
#include <string>
|
|
#include <functional>
|
|
#include "../../Minecraft.Client/Textures/Textures.h"
|
|
#include <limits.h>
|
|
#include "../../Minecraft.Client/Player/LocalPlayer.h"
|
|
#include "../../Minecraft.Client/Rendering/Models/HumanoidModel.h"
|
|
#include "../Util/SoundTypes.h"
|
|
|
|
void Player::_init() {
|
|
registerAttributes();
|
|
setHealth(getMaxHealth());
|
|
|
|
inventory = std::shared_ptr<Inventory>(new Inventory(this));
|
|
|
|
userType = 0;
|
|
oBob = bob = 0.0f;
|
|
|
|
xCloakO = yCloakO = zCloakO = 0.0;
|
|
xCloak = yCloak = zCloak = 0.0;
|
|
|
|
m_isSleeping = false;
|
|
|
|
customTextureUrl = L"";
|
|
customTextureUrl2 = L"";
|
|
m_uiPlayerCurrentSkin = 0;
|
|
|
|
bedPosition = nullptr;
|
|
|
|
sleepCounter = 0;
|
|
deathFadeCounter = 0;
|
|
|
|
bedOffsetX = bedOffsetY = bedOffsetZ = 0.0f;
|
|
stats = nullptr;
|
|
|
|
respawnPosition = nullptr;
|
|
respawnForced = false;
|
|
minecartAchievementPos = nullptr;
|
|
|
|
fishing = nullptr;
|
|
|
|
distanceWalk = distanceSwim = distanceFall = distanceClimb =
|
|
distanceMinecart = distanceBoat = distancePig = 0;
|
|
|
|
m_uiDebugOptions = 0L;
|
|
|
|
jumpTriggerTime = 0;
|
|
takeXpDelay = 0;
|
|
experienceLevel = totalExperience = 0;
|
|
experienceProgress = 0.0f;
|
|
|
|
useItem = nullptr;
|
|
useItemDuration = 0;
|
|
|
|
defaultWalkSpeed = 0.1f;
|
|
defaultFlySpeed = 0.02f;
|
|
|
|
lastLevelUpTime = 0;
|
|
|
|
m_uiGamePrivileges = 0;
|
|
|
|
m_ppAdditionalModelParts = nullptr;
|
|
m_bCheckedForModelParts = false;
|
|
m_bCheckedDLCForModelParts = false;
|
|
|
|
enderChestInventory = std::shared_ptr<PlayerEnderChestContainer>(
|
|
new PlayerEnderChestContainer());
|
|
|
|
m_bAwardedOnARail = false;
|
|
}
|
|
|
|
Player::Player(Level* level, const std::wstring& name) : LivingEntity(level) {
|
|
// 4J Stu - This function call had to be moved here from the Entity ctor to
|
|
// ensure that the derived version of the function is called
|
|
this->defineSynchedData();
|
|
|
|
this->name = name;
|
|
|
|
_init();
|
|
MemSect(11);
|
|
inventoryMenu = new InventoryMenu(inventory, !level->isClientSide, this);
|
|
MemSect(0);
|
|
|
|
containerMenu = inventoryMenu;
|
|
|
|
heightOffset = 1.62f;
|
|
Pos* spawnPos = level->getSharedSpawnPos();
|
|
moveTo(spawnPos->x + 0.5, spawnPos->y + 1, spawnPos->z + 0.5, 0, 0);
|
|
delete spawnPos;
|
|
|
|
rotOffs = 180;
|
|
flameTime = 20;
|
|
|
|
m_skinIndex = eDefaultSkins_Skin0;
|
|
m_playerIndex = 0;
|
|
m_dwSkinId = 0;
|
|
m_dwCapeId = 0;
|
|
|
|
// 4J Added
|
|
m_xuid = INVALID_XUID;
|
|
m_OnlineXuid = INVALID_XUID;
|
|
// m_bShownOnMaps = true;
|
|
setShowOnMaps(
|
|
app.GetGameHostOption(eGameHostOption_Gamertags) != 0 ? true : false);
|
|
m_bIsGuest = false;
|
|
|
|
// 4J: Set UUID to name on none-XB1 consoles, may change in future but for
|
|
// now ownership of animals on these consoles is done by name
|
|
setUUID(name);
|
|
}
|
|
|
|
Player::~Player() {
|
|
// TODO 4J
|
|
// printf("A player has been destroyed.\n");
|
|
delete inventoryMenu;
|
|
|
|
// 4J Stu - Fix for #10938 - CRASH - Game hardlocks when client has an open
|
|
// chest and Xbox Guide while host exits without saving. If the container
|
|
// menu is not the inventory menu, then the player has a menu open. These
|
|
// get deleted when the xui scene is destroyed, so we can not delete it here
|
|
// if( containerMenu != inventoryMenu ) delete containerMenu;
|
|
}
|
|
|
|
void Player::registerAttributes() {
|
|
LivingEntity::registerAttributes();
|
|
|
|
getAttributes()
|
|
->registerAttribute(SharedMonsterAttributes::ATTACK_DAMAGE)
|
|
->setBaseValue(1);
|
|
}
|
|
|
|
void Player::defineSynchedData() {
|
|
LivingEntity::defineSynchedData();
|
|
|
|
entityData->define(DATA_PLAYER_FLAGS_ID, (uint8_t)0);
|
|
entityData->define(DATA_PLAYER_ABSORPTION_ID, (float)0);
|
|
entityData->define(DATA_SCORE_ID, (int)0);
|
|
}
|
|
|
|
std::shared_ptr<ItemInstance> Player::getUseItem() { return useItem; }
|
|
|
|
int Player::getUseItemDuration() { return useItemDuration; }
|
|
|
|
bool Player::isUsingItem() { return useItem != nullptr; }
|
|
|
|
int Player::getTicksUsingItem() {
|
|
if (isUsingItem()) {
|
|
return useItem->getUseDuration() - useItemDuration;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void Player::releaseUsingItem() {
|
|
if (useItem != nullptr) {
|
|
useItem->releaseUsing(
|
|
level, std::dynamic_pointer_cast<Player>(shared_from_this()),
|
|
useItemDuration);
|
|
|
|
// 4J Stu - Fix for various bugs where an incorrect bow was displayed
|
|
// when it broke (#70859,#93972,#93974)
|
|
if (useItem->count == 0) {
|
|
removeSelectedItem();
|
|
}
|
|
}
|
|
stopUsingItem();
|
|
}
|
|
|
|
void Player::stopUsingItem() {
|
|
useItem = nullptr;
|
|
useItemDuration = 0;
|
|
if (!level->isClientSide) {
|
|
setUsingItemFlag(false);
|
|
}
|
|
}
|
|
|
|
bool Player::isBlocking() {
|
|
return isUsingItem() &&
|
|
Item::items[useItem->id]->getUseAnimation(useItem) == UseAnim_block;
|
|
}
|
|
|
|
// 4J Stu - Added for things that should only be ticked once per simulation
|
|
// frame
|
|
void Player::updateFrameTick() {
|
|
if (useItem != nullptr) {
|
|
std::shared_ptr<ItemInstance> item = inventory->getSelected();
|
|
// 4J Stu - Fix for #45508 - TU5: Gameplay: Eating one piece of food
|
|
// will result in a second piece being eaten as well Original code was
|
|
// item != useItem. Changed this now to use the equals function, and add
|
|
// the nullptr check as well for the other possible not equals (useItem
|
|
// is not nullptr if we are here) This is because the useItem and item
|
|
// could be different objects due to an inventory update from the
|
|
// server, but still be the same item (with the same id,count and
|
|
// auxvalue)
|
|
if (item == nullptr || !item->equals(useItem)) {
|
|
stopUsingItem();
|
|
} else {
|
|
if (useItemDuration <= 25 && useItemDuration % 4 == 0) {
|
|
spawnEatParticles(item, 5);
|
|
}
|
|
if (--useItemDuration == 0) {
|
|
if (!level->isClientSide) {
|
|
completeUsingItem();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (takeXpDelay > 0) takeXpDelay--;
|
|
|
|
if (isSleeping()) {
|
|
sleepCounter++;
|
|
if (sleepCounter > SLEEP_DURATION) {
|
|
sleepCounter = SLEEP_DURATION;
|
|
}
|
|
|
|
if (!level->isClientSide) {
|
|
if (!checkBed()) {
|
|
stopSleepInBed(true, true, false);
|
|
} else if (level->isDay()) {
|
|
stopSleepInBed(false, true, true);
|
|
}
|
|
}
|
|
} else if (sleepCounter > 0) {
|
|
sleepCounter++;
|
|
if (sleepCounter >= (SLEEP_DURATION + WAKE_UP_DURATION)) {
|
|
sleepCounter = 0;
|
|
}
|
|
}
|
|
|
|
if (!isAlive()) {
|
|
deathFadeCounter++;
|
|
if (deathFadeCounter > DEATHFADE_DURATION) {
|
|
deathFadeCounter = DEATHFADE_DURATION;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Player::tick() {
|
|
if (level->isClientSide) {
|
|
// 4J Stu - Server player calls this differently so that it only happens
|
|
// once per simulation tick
|
|
updateFrameTick();
|
|
}
|
|
|
|
LivingEntity::tick();
|
|
|
|
if (!level->isClientSide) {
|
|
if (containerMenu != nullptr &&
|
|
!containerMenu->stillValid(
|
|
std::dynamic_pointer_cast<Player>(shared_from_this()))) {
|
|
closeContainer();
|
|
containerMenu = inventoryMenu;
|
|
}
|
|
}
|
|
|
|
if (isOnFire() && (abilities.invulnerable || hasInvulnerablePrivilege())) {
|
|
clearFire();
|
|
}
|
|
|
|
xCloakO = xCloak;
|
|
yCloakO = yCloak;
|
|
zCloakO = zCloak;
|
|
|
|
double xca = x - xCloak;
|
|
double yca = y - yCloak;
|
|
double zca = z - zCloak;
|
|
|
|
double m = 10;
|
|
if (xca > m) xCloakO = xCloak = x;
|
|
if (zca > m) zCloakO = zCloak = z;
|
|
if (yca > m) yCloakO = yCloak = y;
|
|
if (xca < -m) xCloakO = xCloak = x;
|
|
if (zca < -m) zCloakO = zCloak = z;
|
|
if (yca < -m) yCloakO = yCloak = y;
|
|
|
|
xCloak += xca * 0.25;
|
|
zCloak += zca * 0.25;
|
|
yCloak += yca * 0.25;
|
|
|
|
if (riding == nullptr) {
|
|
if (minecartAchievementPos != nullptr) {
|
|
delete minecartAchievementPos;
|
|
minecartAchievementPos = nullptr;
|
|
}
|
|
}
|
|
|
|
if (!level->isClientSide) {
|
|
foodData.tick(std::dynamic_pointer_cast<Player>(shared_from_this()));
|
|
}
|
|
|
|
// 4J Stu Debugging
|
|
if (!level->isClientSide) {
|
|
static int count = 0;
|
|
if (count++ == 100) {
|
|
// 4J-PB - Throw items out at the start of the level
|
|
// this->drop( new ItemInstance( Item::pickAxe_diamond, 1 ) );
|
|
// this->drop( new ItemInstance( Tile::workBench, 1 ) );
|
|
// this->drop( new ItemInstance( Tile::treeTrunk, 8 ) );
|
|
// this->drop( shared_ptr<ItemInstance>( new ItemInstance(
|
|
// Item::milk, 3 ) ) ); this->drop( shared_ptr<ItemInstance>( new
|
|
// ItemInstance( Item::sugar, 2 ) ) ); this->drop( new ItemInstance(
|
|
// Tile::stoneBrick, 8 ) ); this->drop( shared_ptr<ItemInstance>(
|
|
// new ItemInstance( Item::wheat, 3 ) ) ); this->drop(
|
|
// shared_ptr<ItemInstance>( new ItemInstance( Item::egg, 1 ) ) );
|
|
// this->drop( new ItemInstance( Item::bow, 1 ) );
|
|
// this->drop( new ItemInstance( Item::arrow, 10 ) );
|
|
// this->drop( shared_ptr<ItemInstance>( new ItemInstance(
|
|
// Item::saddle, 10 ) ) ); this->drop( shared_ptr<ItemInstance>( new
|
|
// ItemInstance( Tile::fence, 64 ) ) ); this->drop(
|
|
// shared_ptr<ItemInstance>( new ItemInstance( Tile::fence, 64 ) )
|
|
// ); this->drop( shared_ptr<ItemInstance>( new ItemInstance(
|
|
// Tile::fence, 64 ) ) );
|
|
|
|
// shared_ptr<Mob> mob =
|
|
// std::dynamic_pointer_cast<Mob>(Pig::_class->newInstance( level
|
|
// )); mob->moveTo(x+1, y, z+1, level->random->nextFloat() * 360,
|
|
// 0); level->addEntity(mob);
|
|
|
|
// 4J : WESTY : Spawn some wolves to befriend!
|
|
/*
|
|
shared_ptr<Mob> mob1 =
|
|
std::dynamic_pointer_cast<Mob>(Wolf::_class->newInstance( level ));
|
|
mob1->moveTo(x+1, y, z+1, level->random->nextFloat() * 360, 0);
|
|
level->addEntity(mob1);
|
|
|
|
shared_ptr<Mob> mob2 =
|
|
std::dynamic_pointer_cast<Mob>(Wolf::_class->newInstance( level ));
|
|
mob2->moveTo(x+2, y, z+1, level->random->nextFloat() * 360, 0);
|
|
level->addEntity(mob2);
|
|
|
|
shared_ptr<Mob> mob3 =
|
|
std::dynamic_pointer_cast<Mob>(Wolf::_class->newInstance( level ));
|
|
mob3->moveTo(x+1, y, z+2, level->random->nextFloat() * 360, 0);
|
|
level->addEntity(mob3);
|
|
|
|
shared_ptr<Mob> mob4 =
|
|
std::dynamic_pointer_cast<Mob>(Wolf::_class->newInstance( level ));
|
|
mob4->moveTo(x+3, y, z+1, level->random->nextFloat() * 360, 0);
|
|
level->addEntity(mob4);
|
|
|
|
shared_ptr<Mob> mob5 =
|
|
std::dynamic_pointer_cast<Mob>(Wolf::_class->newInstance( level ));
|
|
mob5->moveTo(x+1, y, z+3, level->random->nextFloat() * 360, 0);
|
|
level->addEntity(mob5);
|
|
*/
|
|
|
|
// inventory.add(new ItemInstance(Item.potion, 1,
|
|
// PotionBrewing.THROWABLE_MASK | 0xc)); addEffect(new
|
|
// MobEffectInstance(MobEffect.blindness.id, 60));
|
|
// increaseXp(10);
|
|
|
|
{
|
|
// ItemInstance itemInstance = new
|
|
// ItemInstance(Item.pickAxe_diamond);
|
|
// itemInstance.enchant(Enchantment.diggingBonus, 3);
|
|
// inventory.add(itemInstance);
|
|
}
|
|
}
|
|
}
|
|
// End 4J sTU
|
|
}
|
|
|
|
int Player::getPortalWaitTime() {
|
|
return abilities.invulnerable ? 0 : SharedConstants::TICKS_PER_SECOND * 4;
|
|
}
|
|
|
|
int Player::getDimensionChangingDelay() {
|
|
return SharedConstants::TICKS_PER_SECOND / 2;
|
|
}
|
|
|
|
void Player::playSound(int iSound, float volume, float pitch) {
|
|
// this sound method will play locally for the local player, and
|
|
// broadcast to remote players
|
|
level->playPlayerSound(
|
|
std::dynamic_pointer_cast<Player>(shared_from_this()), iSound, volume,
|
|
pitch);
|
|
}
|
|
|
|
void Player::spawnEatParticles(std::shared_ptr<ItemInstance> useItem,
|
|
int count) {
|
|
if (useItem->getUseAnimation() == UseAnim_drink) {
|
|
playSound(eSoundType_RANDOM_DRINK, 0.5f,
|
|
level->random->nextFloat() * 0.1f + 0.9f);
|
|
}
|
|
if (useItem->getUseAnimation() == UseAnim_eat) {
|
|
for (int i = 0; i < count; i++) {
|
|
Vec3 d{(random->nextFloat() - 0.5) * 0.1,
|
|
Math::random() * 0.1 + 0.1, 0};
|
|
|
|
d.xRot(-xRot * PI / 180);
|
|
d.yRot(-yRot * PI / 180);
|
|
|
|
Vec3 p{(random->nextFloat() - 0.5) * 0.3,
|
|
-random->nextFloat() * 0.6 - 0.3, 0.6};
|
|
p.xRot(-xRot * PI / 180);
|
|
p.yRot(-yRot * PI / 180);
|
|
p = p.add(x, y + getHeadHeight(), z);
|
|
|
|
level->addParticle(PARTICLE_ICONCRACK(useItem->getItem()->id, 0),
|
|
p.x, p.y, p.z, d.x, d.y + 0.05, d.z);
|
|
}
|
|
|
|
// 4J Stu - Was L"mob.eat" which doesnt exist
|
|
playSound(eSoundType_RANDOM_EAT, 0.5f + 0.5f * random->nextInt(2),
|
|
(random->nextFloat() - random->nextFloat()) * 0.2f + 1.0f);
|
|
}
|
|
}
|
|
|
|
void Player::completeUsingItem() {
|
|
if (useItem != nullptr) {
|
|
spawnEatParticles(useItem, 16);
|
|
|
|
int oldCount = useItem->count;
|
|
std::shared_ptr<ItemInstance> itemInstance = useItem->useTimeDepleted(
|
|
level, std::dynamic_pointer_cast<Player>(shared_from_this()));
|
|
if (itemInstance != useItem ||
|
|
(itemInstance != nullptr && itemInstance->count != oldCount)) {
|
|
inventory->items[inventory->selected] = itemInstance;
|
|
if (itemInstance->count == 0) {
|
|
inventory->items[inventory->selected] = nullptr;
|
|
}
|
|
}
|
|
stopUsingItem();
|
|
}
|
|
}
|
|
|
|
void Player::handleEntityEvent(uint8_t id) {
|
|
if (id == EntityEvent::USE_ITEM_COMPLETE) {
|
|
completeUsingItem();
|
|
} else {
|
|
LivingEntity::handleEntityEvent(id);
|
|
}
|
|
}
|
|
|
|
bool Player::isImmobile() { return getHealth() <= 0 || isSleeping(); }
|
|
|
|
void Player::closeContainer() { containerMenu = inventoryMenu; }
|
|
|
|
void Player::ride(std::shared_ptr<Entity> e) {
|
|
if (riding != nullptr && e == nullptr) {
|
|
if (!level->isClientSide) findStandUpPosition(riding);
|
|
|
|
if (riding != nullptr) {
|
|
riding->rider = std::weak_ptr<Entity>();
|
|
}
|
|
riding = nullptr;
|
|
|
|
return;
|
|
}
|
|
LivingEntity::ride(e);
|
|
}
|
|
|
|
void Player::setPlayerDefaultSkin(EDefaultSkins skin) {
|
|
#if !defined(_CONTENT_PACKAGE)
|
|
wprintf(L"Setting default skin to %d for player %ls\n", skin, name.c_str());
|
|
#endif
|
|
m_skinIndex = skin;
|
|
}
|
|
|
|
void Player::setCustomSkin(std::uint32_t skinId) {
|
|
#if !defined(_CONTENT_PACKAGE)
|
|
wprintf(L"Attempting to set skin to %08X for player %ls\n", skinId,
|
|
name.c_str());
|
|
#endif
|
|
EDefaultSkins playerSkin = eDefaultSkins_ServerSelected;
|
|
|
|
// reset the idle
|
|
setIsIdle(false);
|
|
|
|
setAnimOverrideBitmask(getSkinAnimOverrideBitmask(skinId));
|
|
if (!GET_IS_DLC_SKIN_FROM_BITMASK(skinId)) {
|
|
// GET_UGC_SKIN_ID_FROM_BITMASK will always be zero - this was for a
|
|
// possible custom skin editor skin
|
|
std::uint32_t ugcSkinIndex = GET_UGC_SKIN_ID_FROM_BITMASK(skinId);
|
|
std::uint32_t defaultSkinIndex =
|
|
GET_DEFAULT_SKIN_ID_FROM_BITMASK(skinId);
|
|
if (ugcSkinIndex == 0 && defaultSkinIndex > 0) {
|
|
playerSkin = (EDefaultSkins)defaultSkinIndex;
|
|
}
|
|
}
|
|
|
|
if (playerSkin == eDefaultSkins_ServerSelected) {
|
|
playerSkin = (EDefaultSkins)(m_playerIndex + 1);
|
|
}
|
|
|
|
// We always set a default skin, since we may be waiting for the player's
|
|
// custom skin to be transmitted
|
|
setPlayerDefaultSkin(playerSkin);
|
|
|
|
m_dwSkinId = skinId;
|
|
this->customTextureUrl = app.getSkinPathFromId(skinId);
|
|
|
|
// set the new player additional boxes
|
|
/*vector<ModelPart *> *pvModelParts=app.GetAdditionalModelParts(m_dwSkinId);
|
|
|
|
if(pvModelParts==nullptr)
|
|
{
|
|
// we don't have the data from the dlc skin yet
|
|
app.DebugPrintf("Couldn't get model parts for skin %X\n",m_dwSkinId);
|
|
|
|
// do we have it from the DLC pack?
|
|
DLCSkinFile *pDLCSkinFile =
|
|
app.m_dlcManager.getSkinFile(this->customTextureUrl);
|
|
|
|
if(pDLCSkinFile!=nullptr)
|
|
{
|
|
const int additionalBoxCount =
|
|
pDLCSkinFile->getAdditionalBoxesCount(); if(additionalBoxCount != 0)
|
|
{
|
|
app.DebugPrintf("Got model parts from DLCskin for skin %X\n",m_dwSkinId);
|
|
pvModelParts=app.SetAdditionalSkinBoxes(m_dwSkinId,pDLCSkinFile->getAdditionalBoxes());
|
|
this->SetAdditionalModelParts(pvModelParts);
|
|
}
|
|
else
|
|
{
|
|
this->SetAdditionalModelParts(nullptr);
|
|
}
|
|
app.SetAnimOverrideBitmask(pDLCSkinFile->getSkinID(),pDLCSkinFile->getAnimOverrideBitmask());
|
|
}
|
|
else
|
|
{
|
|
this->SetAdditionalModelParts(nullptr);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
app.DebugPrintf("Got model parts from app.GetAdditionalModelParts for skin
|
|
%X\n",m_dwSkinId);
|
|
|
|
this->SetAdditionalModelParts(pvModelParts);
|
|
}*/
|
|
|
|
// reset the check for model parts
|
|
m_bCheckedForModelParts = false;
|
|
m_bCheckedDLCForModelParts = false;
|
|
this->SetAdditionalModelParts(nullptr);
|
|
}
|
|
|
|
unsigned int Player::getSkinAnimOverrideBitmask(std::uint32_t skinId) {
|
|
unsigned long bitmask = 0L;
|
|
if (GET_IS_DLC_SKIN_FROM_BITMASK(skinId)) {
|
|
// Temp check for anim override
|
|
switch (GET_DLC_SKIN_ID_FROM_BITMASK(skinId)) {
|
|
case 0x2: // SP1_ZOMBIE:
|
|
case 0x3: // SP1_HEROBRINE:
|
|
case 0xc8: // SP3_ZOMBIE_PIGMAN:
|
|
case 0xc9: // SP3_ZOMBIE_HEROBRINE:
|
|
case 0x1f8: // SPH_4JMUMMY
|
|
case 0x220: // SPH_AOT_MUMMY
|
|
case 0x23a: // SPH_CLIMAX_ZOMBIEBUSINESSMAN
|
|
case 0x23d: // SPH_CLIMAX_EVILROBOT
|
|
case 0x247: // SPH_CLIMAX_ZOMBIE
|
|
case 0x194: // SOA_DEADLIGHT_SKINNY_ZOMBIE
|
|
case 0x195: // SOA_DEADLIGHT_FEMALE_ZOMBIE
|
|
bitmask = 1 << HumanoidModel::eAnim_ArmsOutFront;
|
|
break;
|
|
case 0x1fa: // SPH_GHOST:
|
|
bitmask = 1 << HumanoidModel::eAnim_ArmsOutFront |
|
|
1 << HumanoidModel::eAnim_NoLegAnim;
|
|
break;
|
|
case 0x1f4: // SPH_GRIMREAPER:
|
|
bitmask = 1 << HumanoidModel::eAnim_ArmsDown |
|
|
1 << HumanoidModel::eAnim_NoLegAnim;
|
|
break;
|
|
case 0x1f7: // SPH_4J_FRANKENSTEIN
|
|
// bitmask = 1<<HumanoidModel::eAnim_HasIdle;
|
|
break;
|
|
break;
|
|
default:
|
|
// This is not one of the prefined skins
|
|
// Does the app have an anim override for this skin?
|
|
bitmask = app.GetAnimOverrideBitmask(skinId);
|
|
break;
|
|
}
|
|
}
|
|
return bitmask;
|
|
}
|
|
|
|
void Player::setXuid(PlayerUID xuid) { m_xuid = xuid; }
|
|
|
|
void Player::setCustomCape(std::uint32_t capeId) {
|
|
#if !defined(_CONTENT_PACKAGE)
|
|
wprintf(L"Attempting to set cape to %08X for player %s\n", capeId,
|
|
name.c_str());
|
|
#endif
|
|
|
|
m_dwCapeId = capeId;
|
|
|
|
if (capeId > 0) {
|
|
this->customTextureUrl2 = Player::getCapePathFromId(capeId);
|
|
} else {
|
|
MOJANG_DATA* pMojangData = app.GetMojangDataForXuid(getOnlineXuid());
|
|
if (pMojangData) {
|
|
// Cape
|
|
if (pMojangData->wchCape[0] != 0) {
|
|
this->customTextureUrl2 = pMojangData->wchCape;
|
|
} else {
|
|
if (app.DefaultCapeExists()) {
|
|
this->customTextureUrl2 = std::wstring(L"Special_Cape.png");
|
|
} else {
|
|
this->customTextureUrl2 = std::wstring(L"");
|
|
}
|
|
}
|
|
|
|
} else {
|
|
// if there is a custom default cloak, then set it here
|
|
if (app.DefaultCapeExists()) {
|
|
this->customTextureUrl2 = std::wstring(L"Special_Cape.png");
|
|
} else {
|
|
this->customTextureUrl2 = std::wstring(L"");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::uint32_t Player::getCapeIdFromPath(const std::wstring& cape) {
|
|
bool dlcCape = false;
|
|
std::uint32_t capeId = 0;
|
|
|
|
if (cape.size() >= 14) {
|
|
dlcCape = cape.substr(0, 3).compare(L"dlc") == 0;
|
|
|
|
std::wstring capeValue = cape.substr(7, cape.size());
|
|
capeValue = capeValue.substr(0, capeValue.find_first_of(L'.'));
|
|
|
|
std::wstringstream ss;
|
|
// 4J Stu - dlc skins are numbered using decimal to make it easier for
|
|
// artists/people to number manually Everything else is numbered using
|
|
// hex
|
|
if (dlcCape)
|
|
ss << std::dec << capeValue.c_str();
|
|
else
|
|
ss << std::hex << capeValue.c_str();
|
|
ss >> capeId;
|
|
|
|
capeId = MAKE_SKIN_BITMASK(dlcCape, capeId);
|
|
}
|
|
return capeId;
|
|
}
|
|
|
|
std::wstring Player::getCapePathFromId(std::uint32_t capeId) {
|
|
// 4J Stu - This function maps the encoded uint32_t we store in the player
|
|
// profile to a filename that is stored as a memory texture and shared
|
|
// between systems in game
|
|
wchar_t chars[256];
|
|
if (GET_IS_DLC_SKIN_FROM_BITMASK(capeId)) {
|
|
// 4J Stu - DLC skins are numbered using decimal rather than hex to make
|
|
// it easier to number manually
|
|
swprintf(chars, 256, L"dlccape%08d.png",
|
|
GET_DLC_SKIN_ID_FROM_BITMASK(capeId));
|
|
|
|
} else {
|
|
std::uint32_t ugcCapeIndex = GET_UGC_SKIN_ID_FROM_BITMASK(capeId);
|
|
std::uint32_t defaultCapeIndex =
|
|
GET_DEFAULT_SKIN_ID_FROM_BITMASK(capeId);
|
|
if (ugcCapeIndex == 0) {
|
|
swprintf(chars, 256, L"defcape%08X.png", defaultCapeIndex);
|
|
} else {
|
|
swprintf(chars, 256, L"ugccape%08X.png", ugcCapeIndex);
|
|
}
|
|
}
|
|
return chars;
|
|
}
|
|
|
|
void Player::ChangePlayerSkin() {
|
|
if (app.vSkinNames.size() > 0) {
|
|
m_uiPlayerCurrentSkin++;
|
|
if (m_uiPlayerCurrentSkin > app.vSkinNames.size()) {
|
|
m_uiPlayerCurrentSkin = 0;
|
|
this->customTextureUrl = L"";
|
|
} else {
|
|
if (m_uiPlayerCurrentSkin > 0) {
|
|
// change this players custom texture url
|
|
this->customTextureUrl =
|
|
app.vSkinNames[m_uiPlayerCurrentSkin - 1];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Player::prepareCustomTextures() {
|
|
MOJANG_DATA* pMojangData = app.GetMojangDataForXuid(getOnlineXuid());
|
|
|
|
if (pMojangData) {
|
|
// Skin
|
|
if (pMojangData->wchSkin[0] != 0) {
|
|
this->customTextureUrl = pMojangData->wchSkin;
|
|
}
|
|
|
|
// 4J Stu - Don't update the cape here, it gets set elsewhere
|
|
// Cape
|
|
// if(pMojangData->wchCape)
|
|
//{
|
|
// this->customTextureUrl2= pMojangData->wchCape;
|
|
//}
|
|
// else
|
|
//{
|
|
// if(app.DefaultCapeExists())
|
|
// {
|
|
// this->customTextureUrl2= wstring(L"Default_Cape.png");
|
|
// }
|
|
// else
|
|
// {
|
|
// this->customTextureUrl2= wstring(L"");
|
|
// }
|
|
//}
|
|
|
|
} else {
|
|
// 4J Stu - Don't update the cape here, it gets set elsewhere
|
|
// if there is a custom default cloak, then set it here
|
|
// if(app.DefaultCapeExists())
|
|
//{
|
|
// this->customTextureUrl2= wstring(L"Default_Cape.png");
|
|
//}
|
|
// else
|
|
//{
|
|
// this->customTextureUrl2 =wstring(L"");
|
|
//}
|
|
}
|
|
|
|
/*cloakTexture =
|
|
* wstring(L"http://s3.amazonaws.com/MinecraftCloaks/").append( name
|
|
* ).append( L".png" );*/
|
|
// this->customTextureUrl2 = cloakTexture;
|
|
}
|
|
|
|
void Player::rideTick() {
|
|
if (!level->isClientSide && isSneaking()) {
|
|
ride(nullptr);
|
|
setSneaking(false);
|
|
return;
|
|
}
|
|
|
|
double preX = x, preY = y, preZ = z;
|
|
float preYRot = yRot, preXRot = xRot;
|
|
|
|
LivingEntity::rideTick();
|
|
oBob = bob;
|
|
bob = 0;
|
|
|
|
checkRidingStatistiscs(x - preX, y - preY, z - preZ);
|
|
|
|
// riding can be set to null inside 'Entity::rideTick()'.
|
|
if (riding != nullptr && (riding->GetType() & eTYPE_PIG) == eTYPE_PIG) {
|
|
// 4J Stu - I don't know why we would want to do this, but it means that
|
|
// the players head is locked in position and can't move around
|
|
// xRot = preXRot;
|
|
// yRot = preYRot;
|
|
|
|
std::shared_ptr<Pig> pig = std::dynamic_pointer_cast<Pig>(riding);
|
|
yBodyRot = pig->yBodyRot;
|
|
|
|
while (yBodyRot - yBodyRotO < -180) yBodyRotO -= 360;
|
|
while (yBodyRot - yBodyRotO >= 180) yBodyRotO += 360;
|
|
}
|
|
}
|
|
|
|
void Player::resetPos() {
|
|
heightOffset = 1.62f;
|
|
setSize(0.6f, 1.8f);
|
|
LivingEntity::resetPos();
|
|
setHealth(getMaxHealth());
|
|
deathTime = 0;
|
|
}
|
|
|
|
void Player::serverAiStep() {
|
|
LivingEntity::serverAiStep();
|
|
updateSwingTime();
|
|
}
|
|
|
|
void Player::aiStep() {
|
|
if (jumpTriggerTime > 0) jumpTriggerTime--;
|
|
|
|
if (level->difficulty == Difficulty::PEACEFUL &&
|
|
getHealth() < getMaxHealth() &&
|
|
level->getGameRules()->getBoolean(
|
|
GameRules::RULE_NATURAL_REGENERATION)) {
|
|
if (tickCount % 20 * 12 == 0) heal(1);
|
|
}
|
|
inventory->tick();
|
|
oBob = bob;
|
|
|
|
LivingEntity::aiStep();
|
|
|
|
AttributeInstance* speed =
|
|
getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED);
|
|
if (!level->isClientSide) speed->setBaseValue(abilities.getWalkingSpeed());
|
|
flyingSpeed = defaultFlySpeed;
|
|
if (isSprinting()) {
|
|
flyingSpeed += defaultFlySpeed * 0.3f;
|
|
}
|
|
|
|
setSpeed((float)speed->getValue());
|
|
|
|
float tBob = (float)sqrt(xd * xd + zd * zd);
|
|
|
|
// 4J added - we were getting a NaN with zero xd & zd
|
|
if ((xd * xd + zd * zd) < 0.00001f) {
|
|
tBob = 0.0f;
|
|
}
|
|
|
|
float tTilt = (float)atan(-yd * 0.2f) * 15.0f;
|
|
if (tBob > 0.1f) tBob = 0.1f;
|
|
if (!onGround || getHealth() <= 0) tBob = 0;
|
|
if (onGround || getHealth() <= 0) tTilt = 0;
|
|
|
|
bob += (tBob - bob) * 0.4f;
|
|
|
|
tilt += (tTilt - tilt) * 0.8f;
|
|
|
|
if (getHealth() > 0) {
|
|
AABB pickupArea;
|
|
if (riding != nullptr && !riding->removed) {
|
|
// if the player is riding, also touch entities under the
|
|
// pig/horse
|
|
pickupArea = bb.minmax(riding->bb).grow(1, 0, 1);
|
|
} else {
|
|
pickupArea = bb.grow(1, .5, 1);
|
|
}
|
|
|
|
std::vector<std::shared_ptr<Entity> >* entities =
|
|
level->getEntities(shared_from_this(), &pickupArea);
|
|
if (entities != nullptr) {
|
|
auto itEnd = entities->end();
|
|
for (auto it = entities->begin(); it != itEnd; it++) {
|
|
std::shared_ptr<Entity> e = *it; // entities->at(i);
|
|
if (!e->removed) {
|
|
touch(e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Player::touch(std::shared_ptr<Entity> entity) {
|
|
entity->playerTouch(std::dynamic_pointer_cast<Player>(shared_from_this()));
|
|
}
|
|
|
|
int Player::getScore() { return entityData->getInteger(DATA_SCORE_ID); }
|
|
|
|
void Player::setScore(int value) { entityData->set(DATA_SCORE_ID, value); }
|
|
|
|
void Player::increaseScore(int amount) {
|
|
int score = getScore();
|
|
entityData->set(DATA_SCORE_ID, score + amount);
|
|
}
|
|
|
|
void Player::die(DamageSource* source) {
|
|
LivingEntity::die(source);
|
|
setSize(0.2f, 0.2f);
|
|
setPos(x, y, z);
|
|
yd = 0.1f;
|
|
|
|
// 4J - TODO need to use a xuid
|
|
if (app.isXuidNotch(m_xuid)) {
|
|
drop(std::shared_ptr<ItemInstance>(new ItemInstance(Item::apple, 1)),
|
|
true);
|
|
}
|
|
if (!level->getGameRules()->getBoolean(GameRules::RULE_KEEPINVENTORY)) {
|
|
inventory->dropAll();
|
|
}
|
|
|
|
if (source != nullptr) {
|
|
xd = -Mth::cos((hurtDir + yRot) * PI / 180) * 0.1f;
|
|
zd = -Mth::sin((hurtDir + yRot) * PI / 180) * 0.1f;
|
|
} else {
|
|
xd = zd = 0;
|
|
}
|
|
heightOffset = 0.1f;
|
|
}
|
|
|
|
void Player::awardKillScore(std::shared_ptr<Entity> victim, int awardPoints) {
|
|
increaseScore(awardPoints);
|
|
std::vector<Objective*>* objectives =
|
|
getScoreboard()->findObjectiveFor(ObjectiveCriteria::KILL_COUNT_ALL);
|
|
|
|
// if (victim instanceof Player)
|
|
//{
|
|
// awardStat(Stats::playerKills, 1);
|
|
// objectives.addAll(getScoreboard().findObjectiveFor(ObjectiveCriteria::KILL_COUNT_PLAYERS));
|
|
// }
|
|
// else
|
|
//{
|
|
// awardStat(Stats::mobKills, 1);
|
|
// }
|
|
|
|
if (objectives) {
|
|
for (auto it = objectives->begin(); it != objectives->end(); ++it) {
|
|
Objective* objective = *it;
|
|
Score* score =
|
|
getScoreboard()->getPlayerScore(getAName(), objective);
|
|
score->increment();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Player::isShootable() { return true; }
|
|
|
|
bool Player::isCreativeModeAllowed() { return true; }
|
|
|
|
std::shared_ptr<ItemEntity> Player::drop(bool all) {
|
|
return drop(inventory->removeItem(inventory->selected,
|
|
all && inventory->getSelected() != nullptr
|
|
? inventory->getSelected()->count
|
|
: 1),
|
|
false);
|
|
}
|
|
|
|
std::shared_ptr<ItemEntity> Player::drop(std::shared_ptr<ItemInstance> item) {
|
|
return drop(item, false);
|
|
}
|
|
|
|
std::shared_ptr<ItemEntity> Player::drop(std::shared_ptr<ItemInstance> item,
|
|
bool randomly) {
|
|
if (item == nullptr) return nullptr;
|
|
if (item->count == 0) return nullptr;
|
|
|
|
std::shared_ptr<ItemEntity> thrownItem = std::shared_ptr<ItemEntity>(
|
|
new ItemEntity(level, x, y - 0.3f + getHeadHeight(), z, item));
|
|
thrownItem->throwTime = 20 * 2;
|
|
|
|
thrownItem->setThrower(getName());
|
|
|
|
float pow = 0.1f;
|
|
if (randomly) {
|
|
float _pow = random->nextFloat() * 0.5f;
|
|
float dir = random->nextFloat() * PI * 2;
|
|
thrownItem->xd = -sin(dir) * _pow;
|
|
thrownItem->zd = cos(dir) * _pow;
|
|
thrownItem->yd = 0.2f;
|
|
|
|
} else {
|
|
pow = 0.3f;
|
|
thrownItem->xd = -sin(yRot / 180 * PI) * cos(xRot / 180 * PI) * pow;
|
|
thrownItem->zd = cos(yRot / 180 * PI) * cos(xRot / 180 * PI) * pow;
|
|
thrownItem->yd = -sin(xRot / 180 * PI) * pow + 0.1f;
|
|
pow = 0.02f;
|
|
|
|
float dir = random->nextFloat() * PI * 2;
|
|
pow *= random->nextFloat();
|
|
thrownItem->xd += cos(dir) * pow;
|
|
thrownItem->yd += (random->nextFloat() - random->nextFloat()) * 0.1f;
|
|
thrownItem->zd += sin(dir) * pow;
|
|
}
|
|
|
|
reallyDrop(thrownItem);
|
|
|
|
return thrownItem;
|
|
}
|
|
|
|
void Player::reallyDrop(std::shared_ptr<ItemEntity> thrownItem) {
|
|
level->addEntity(thrownItem);
|
|
}
|
|
|
|
float Player::getDestroySpeed(Tile* tile, bool hasProperTool) {
|
|
float speed = inventory->getDestroySpeed(tile);
|
|
|
|
if (speed > 1) {
|
|
int efficiency = EnchantmentHelper::getDiggingBonus(
|
|
std::dynamic_pointer_cast<LivingEntity>(shared_from_this()));
|
|
std::shared_ptr<ItemInstance> item = inventory->getSelected();
|
|
|
|
if (efficiency > 0 && item != nullptr) {
|
|
float boost = efficiency * efficiency + 1;
|
|
|
|
if (item->canDestroySpecial(tile) || speed > 1) {
|
|
speed += boost;
|
|
} else {
|
|
speed += boost * 0.08f;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (hasEffect(MobEffect::digSpeed)) {
|
|
speed *=
|
|
1.0f + (getEffect(MobEffect::digSpeed)->getAmplifier() + 1) * .2f;
|
|
}
|
|
if (hasEffect(MobEffect::digSlowdown)) {
|
|
speed *= 1.0f -
|
|
(getEffect(MobEffect::digSlowdown)->getAmplifier() + 1) * .2f;
|
|
}
|
|
|
|
if (isUnderLiquid(Material::water) &&
|
|
!EnchantmentHelper::hasWaterWorkerBonus(
|
|
std::dynamic_pointer_cast<LivingEntity>(shared_from_this())))
|
|
speed /= 5;
|
|
|
|
// 4J Stu - onGround is set to true on the client when we are flying, which
|
|
// means the dig speed is out of sync with the server. Removing this speed
|
|
// change when flying so that we always dig as the same speed
|
|
// if (!onGround) speed /= 5;
|
|
|
|
return speed;
|
|
}
|
|
|
|
bool Player::canDestroy(Tile* tile) { return inventory->canDestroy(tile); }
|
|
|
|
void Player::readAdditionalSaveData(CompoundTag* entityTag) {
|
|
LivingEntity::readAdditionalSaveData(entityTag);
|
|
ListTag<CompoundTag>* inventoryList =
|
|
(ListTag<CompoundTag>*)entityTag->getList(L"Inventory");
|
|
inventory->load(inventoryList);
|
|
inventory->selected = entityTag->getInt(L"SelectedItemSlot");
|
|
m_isSleeping = entityTag->getBoolean(L"Sleeping");
|
|
sleepCounter = entityTag->getShort(L"SleepTimer");
|
|
|
|
experienceProgress = entityTag->getFloat(L"XpP");
|
|
experienceLevel = entityTag->getInt(L"XpLevel");
|
|
totalExperience = entityTag->getInt(L"XpTotal");
|
|
setScore(entityTag->getInt(L"Score"));
|
|
|
|
if (m_isSleeping) {
|
|
bedPosition = new Pos(Mth::floor(x), Mth::floor(y), Mth::floor(z));
|
|
stopSleepInBed(true, true, false);
|
|
}
|
|
|
|
if (entityTag->contains(L"SpawnX") && entityTag->contains(L"SpawnY") &&
|
|
entityTag->contains(L"SpawnZ")) {
|
|
respawnPosition =
|
|
new Pos(entityTag->getInt(L"SpawnX"), entityTag->getInt(L"SpawnY"),
|
|
entityTag->getInt(L"SpawnZ"));
|
|
respawnForced = entityTag->getBoolean(L"SpawnForced");
|
|
}
|
|
|
|
foodData.readAdditionalSaveData(entityTag);
|
|
abilities.loadSaveData(entityTag);
|
|
|
|
if (entityTag->contains(L"EnderItems")) {
|
|
ListTag<CompoundTag>* enderItemsList =
|
|
(ListTag<CompoundTag>*)entityTag->getList(L"EnderItems");
|
|
enderChestInventory->setItemsByTag(enderItemsList);
|
|
}
|
|
|
|
// 4J Added
|
|
m_uiGamePrivileges = entityTag->getInt(L"GamePrivileges");
|
|
}
|
|
|
|
void Player::addAdditonalSaveData(CompoundTag* entityTag) {
|
|
LivingEntity::addAdditonalSaveData(entityTag);
|
|
entityTag->put(L"Inventory", inventory->save(new ListTag<CompoundTag>()));
|
|
entityTag->putInt(L"SelectedItemSlot", inventory->selected);
|
|
entityTag->putBoolean(L"Sleeping", m_isSleeping);
|
|
entityTag->putShort(L"SleepTimer", (short)sleepCounter);
|
|
|
|
entityTag->putFloat(L"XpP", experienceProgress);
|
|
entityTag->putInt(L"XpLevel", experienceLevel);
|
|
entityTag->putInt(L"XpTotal", totalExperience);
|
|
entityTag->putInt(L"Score", getScore());
|
|
|
|
if (respawnPosition != nullptr) {
|
|
entityTag->putInt(L"SpawnX", respawnPosition->x);
|
|
entityTag->putInt(L"SpawnY", respawnPosition->y);
|
|
entityTag->putInt(L"SpawnZ", respawnPosition->z);
|
|
entityTag->putBoolean(L"SpawnForced", respawnForced);
|
|
}
|
|
|
|
foodData.addAdditonalSaveData(entityTag);
|
|
abilities.addSaveData(entityTag);
|
|
|
|
entityTag->put(L"EnderItems", enderChestInventory->createTag());
|
|
|
|
// 4J Added
|
|
entityTag->putInt(L"GamePrivileges", m_uiGamePrivileges);
|
|
}
|
|
|
|
bool Player::openContainer(std::shared_ptr<Container> container) {
|
|
return true;
|
|
}
|
|
|
|
bool Player::openHopper(std::shared_ptr<HopperTileEntity> container) {
|
|
return true;
|
|
}
|
|
|
|
bool Player::openHopper(std::shared_ptr<MinecartHopper> container) {
|
|
return true;
|
|
}
|
|
|
|
bool Player::openHorseInventory(std::shared_ptr<EntityHorse> horse,
|
|
std::shared_ptr<Container> container) {
|
|
return true;
|
|
}
|
|
|
|
bool Player::startEnchanting(int x, int y, int z, const std::wstring& name) {
|
|
return true;
|
|
}
|
|
|
|
bool Player::startRepairing(int x, int y, int z) { return true; }
|
|
|
|
bool Player::startCrafting(int x, int y, int z) { return true; }
|
|
|
|
bool Player::openFireworks(int x, int y, int z) { return true; }
|
|
|
|
float Player::getHeadHeight() { return 0.12f; }
|
|
|
|
void Player::setDefaultHeadHeight() { heightOffset = 1.62f; }
|
|
|
|
bool Player::hurt(DamageSource* source, float dmg) {
|
|
if (isInvulnerable()) return false;
|
|
if (hasInvulnerablePrivilege() ||
|
|
(abilities.invulnerable && !source->isBypassInvul()))
|
|
return false;
|
|
|
|
// 4J-JEV: Fix for PSVita: #3987 - [IN GAME] The user can take damage/die,
|
|
// when attempting to re-enter fly mode when falling from a height.
|
|
if (source == DamageSource::fall && isAllowedToFly() && abilities.flying)
|
|
return false;
|
|
|
|
noActionTime = 0;
|
|
if (getHealth() <= 0) return false;
|
|
|
|
if (isSleeping() && !level->isClientSide) {
|
|
stopSleepInBed(true, true, false);
|
|
}
|
|
|
|
if (source->scalesWithDifficulty()) {
|
|
if (level->difficulty == Difficulty::PEACEFUL) dmg = 0;
|
|
if (level->difficulty == Difficulty::EASY) dmg = dmg / 2 + 1;
|
|
if (level->difficulty == Difficulty::HARD) dmg = dmg * 3 / 2;
|
|
}
|
|
|
|
if (dmg == 0) return false;
|
|
|
|
std::shared_ptr<Entity> attacker = source->getEntity();
|
|
if (attacker != nullptr && attacker->instanceof(eTYPE_ARROW)) {
|
|
std::shared_ptr<Arrow> arrow =
|
|
std::dynamic_pointer_cast<Arrow>(attacker);
|
|
if (arrow->owner != nullptr) {
|
|
attacker = arrow->owner;
|
|
}
|
|
}
|
|
|
|
return LivingEntity::hurt(source, dmg);
|
|
}
|
|
|
|
bool Player::canHarmPlayer(std::shared_ptr<Player> target) {
|
|
Team* team = getTeam();
|
|
Team* otherTeam = target->getTeam();
|
|
|
|
if (team == nullptr) {
|
|
return true;
|
|
}
|
|
if (!team->isAlliedTo(otherTeam)) {
|
|
return true;
|
|
}
|
|
return team->isAllowFriendlyFire();
|
|
}
|
|
|
|
bool Player::canHarmPlayer(std::wstring targetName) { return true; }
|
|
|
|
void Player::hurtArmor(float damage) { inventory->hurtArmor(damage); }
|
|
|
|
int Player::getArmorValue() { return inventory->getArmorValue(); }
|
|
|
|
float Player::getArmorCoverPercentage() {
|
|
int count = 0;
|
|
for (int i = 0; i < inventory->armor.length; i++) {
|
|
if (inventory->armor[i] != nullptr) {
|
|
count++;
|
|
}
|
|
}
|
|
return (float)count / (float)inventory->armor.length;
|
|
}
|
|
|
|
void Player::actuallyHurt(DamageSource* source, float dmg) {
|
|
if (isInvulnerable()) return;
|
|
if (!source->isBypassArmor() && isBlocking() && dmg > 0) {
|
|
dmg = (1 + dmg) * .5f;
|
|
}
|
|
dmg = getDamageAfterArmorAbsorb(source, dmg);
|
|
dmg = getDamageAfterMagicAbsorb(source, dmg);
|
|
|
|
float originalDamage = dmg;
|
|
dmg = std::max(dmg - getAbsorptionAmount(), 0.0f);
|
|
setAbsorptionAmount(getAbsorptionAmount() - (originalDamage - dmg));
|
|
if (dmg == 0) return;
|
|
|
|
causeFoodExhaustion(source->getFoodExhaustion());
|
|
float oldHealth = getHealth();
|
|
setHealth(getHealth() - dmg);
|
|
getCombatTracker()->recordDamage(source, oldHealth, dmg);
|
|
}
|
|
|
|
bool Player::openFurnace(std::shared_ptr<FurnaceTileEntity> container) {
|
|
return true;
|
|
}
|
|
|
|
bool Player::openTrap(std::shared_ptr<DispenserTileEntity> container) {
|
|
return true;
|
|
}
|
|
|
|
void Player::openTextEdit(std::shared_ptr<TileEntity> sign) {}
|
|
|
|
bool Player::openBrewingStand(
|
|
std::shared_ptr<BrewingStandTileEntity> brewingStand) {
|
|
return true;
|
|
}
|
|
|
|
bool Player::openBeacon(std::shared_ptr<BeaconTileEntity> beacon) {
|
|
return true;
|
|
}
|
|
|
|
bool Player::openTrading(std::shared_ptr<Merchant> traderTarget,
|
|
const std::wstring& name) {
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Opens an iteminstance-dependent user interface.
|
|
*
|
|
* @param itemInstance
|
|
*/
|
|
void Player::openItemInstanceGui(std::shared_ptr<ItemInstance> itemInstance) {}
|
|
|
|
bool Player::interact(std::shared_ptr<Entity> entity) {
|
|
std::shared_ptr<Player> thisPlayer =
|
|
std::dynamic_pointer_cast<Player>(shared_from_this());
|
|
|
|
std::shared_ptr<ItemInstance> item = getSelectedItem();
|
|
std::shared_ptr<ItemInstance> itemClone =
|
|
(item != nullptr) ? item->copy() : nullptr;
|
|
if (entity->interact(thisPlayer)) {
|
|
// [EB]: Added rude check to see if we're still talking about the
|
|
// same item; this code caused bucket->milkbucket to be deleted because
|
|
// the milkbuckets' stack got decremented to 0.
|
|
if (item != nullptr && item == getSelectedItem()) {
|
|
if (item->count <= 0 && !abilities.instabuild) {
|
|
removeSelectedItem();
|
|
} else if (item->count < itemClone->count && abilities.instabuild) {
|
|
item->count = itemClone->count;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if ((item != nullptr) && entity->instanceof(eTYPE_LIVINGENTITY)) {
|
|
// 4J - PC Comments
|
|
// Hack to prevent item stacks from decrementing if the player has
|
|
// the ability to instabuild
|
|
if (this->abilities.instabuild) item = itemClone;
|
|
if (item->interactEnemy(
|
|
thisPlayer, std::dynamic_pointer_cast<LivingEntity>(entity))) {
|
|
// 4J - PC Comments
|
|
// Don't remove the item in hand if the player has the ability
|
|
// to instabuild
|
|
if ((item->count <= 0) && !abilities.instabuild) {
|
|
removeSelectedItem();
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
std::shared_ptr<ItemInstance> Player::getSelectedItem() {
|
|
return inventory->getSelected();
|
|
}
|
|
|
|
void Player::removeSelectedItem() {
|
|
inventory->setItem(inventory->selected, nullptr);
|
|
}
|
|
|
|
double Player::getRidingHeight() { return heightOffset - 0.5f; }
|
|
|
|
void Player::attack(std::shared_ptr<Entity> entity) {
|
|
if (!entity->isAttackable()) {
|
|
return;
|
|
}
|
|
|
|
if (entity->skipAttackInteraction(shared_from_this())) {
|
|
return;
|
|
}
|
|
|
|
float dmg =
|
|
(float)getAttribute(SharedMonsterAttributes::ATTACK_DAMAGE)->getValue();
|
|
|
|
int knockback = 0;
|
|
float magicBoost = 0;
|
|
|
|
if (entity->instanceof(eTYPE_LIVINGENTITY)) {
|
|
std::shared_ptr<Player> thisPlayer =
|
|
std::dynamic_pointer_cast<Player>(shared_from_this());
|
|
std::shared_ptr<LivingEntity> mob =
|
|
std::dynamic_pointer_cast<LivingEntity>(entity);
|
|
magicBoost = EnchantmentHelper::getDamageBonus(thisPlayer, mob);
|
|
knockback += EnchantmentHelper::getKnockbackBonus(thisPlayer, mob);
|
|
}
|
|
if (isSprinting()) {
|
|
knockback += 1;
|
|
}
|
|
|
|
if (dmg > 0 || magicBoost > 0) {
|
|
bool bCrit = fallDistance > 0 && !onGround && !onLadder() &&
|
|
!isInWater() && !hasEffect(MobEffect::blindness) &&
|
|
(riding == nullptr) &&
|
|
entity->instanceof(eTYPE_LIVINGENTITY);
|
|
if (bCrit && dmg > 0) {
|
|
dmg *= 1.5f;
|
|
}
|
|
dmg += magicBoost;
|
|
|
|
// Ensure we put the entity on fire if we're hitting with a
|
|
// fire-enchanted weapon
|
|
bool setOnFireTemporatily = false;
|
|
int fireAspect = EnchantmentHelper::getFireAspect(
|
|
std::dynamic_pointer_cast<LivingEntity>(shared_from_this()));
|
|
if (entity->instanceof(eTYPE_MOB) && fireAspect > 0 &&
|
|
!entity->isOnFire()) {
|
|
setOnFireTemporatily = true;
|
|
entity->setOnFire(1);
|
|
}
|
|
|
|
DamageSource* damageSource = DamageSource::playerAttack(
|
|
std::dynamic_pointer_cast<Player>(shared_from_this()));
|
|
bool wasHurt = entity->hurt(damageSource, dmg);
|
|
delete damageSource;
|
|
if (wasHurt) {
|
|
if (knockback > 0) {
|
|
entity->push(-Mth::sin(yRot * PI / 180) * knockback * .5f, 0.1,
|
|
Mth::cos(yRot * PI / 180) * knockback * .5f);
|
|
xd *= 0.6;
|
|
zd *= 0.6;
|
|
setSprinting(false);
|
|
}
|
|
|
|
if (bCrit) {
|
|
crit(entity);
|
|
}
|
|
if (magicBoost > 0) {
|
|
magicCrit(entity);
|
|
}
|
|
|
|
if (dmg >= 18) {
|
|
awardStat(GenericStats::overkill(),
|
|
GenericStats::param_overkill(dmg));
|
|
}
|
|
setLastHurtMob(entity);
|
|
|
|
if (entity->instanceof(eTYPE_LIVINGENTITY)) {
|
|
std::shared_ptr<LivingEntity> mob =
|
|
std::dynamic_pointer_cast<LivingEntity>(entity);
|
|
ThornsEnchantment::doThornsAfterAttack(shared_from_this(), mob,
|
|
random);
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<ItemInstance> item = getSelectedItem();
|
|
std::shared_ptr<Entity> hurtTarget = entity;
|
|
if (entity->instanceof(eTYPE_MULTIENTITY_MOB_PART)) {
|
|
std::shared_ptr<Entity> multiMob =
|
|
std::dynamic_pointer_cast<Entity>(
|
|
(std::dynamic_pointer_cast<MultiEntityMobPart>(entity))
|
|
->parentMob.lock());
|
|
if ((multiMob != nullptr) &&
|
|
multiMob->instanceof(eTYPE_LIVINGENTITY)) {
|
|
hurtTarget = std::dynamic_pointer_cast<LivingEntity>(multiMob);
|
|
}
|
|
}
|
|
if ((item != nullptr) && hurtTarget->instanceof(eTYPE_LIVINGENTITY)) {
|
|
item->hurtEnemy(
|
|
std::dynamic_pointer_cast<LivingEntity>(hurtTarget),
|
|
std::dynamic_pointer_cast<Player>(shared_from_this()));
|
|
if (item->count <= 0) {
|
|
removeSelectedItem();
|
|
}
|
|
}
|
|
if (entity->instanceof(eTYPE_LIVINGENTITY)) {
|
|
// awardStat(Stats.damageDealt, (int) Math.round(dmg * 10));
|
|
|
|
if (fireAspect > 0 && wasHurt) {
|
|
entity->setOnFire(fireAspect * 4);
|
|
} else if (setOnFireTemporatily) {
|
|
entity->clearFire();
|
|
}
|
|
}
|
|
|
|
causeFoodExhaustion(FoodConstants::EXHAUSTION_ATTACK);
|
|
}
|
|
|
|
// if (SharedConstants::INGAME_DEBUG_OUTPUT)
|
|
// {
|
|
// //sendMessage(ChatMessageComponent.forPlainText("DMG " + dmg +
|
|
// ", " + magicBoost + ", " + knockback));
|
|
// }
|
|
}
|
|
|
|
void Player::crit(std::shared_ptr<Entity> entity) {}
|
|
|
|
void Player::magicCrit(std::shared_ptr<Entity> entity) {}
|
|
|
|
void Player::respawn() { deathFadeCounter = 0; }
|
|
|
|
void Player::animateRespawn(std::shared_ptr<Player> player, Level* level) {
|
|
for (int i = 0; i < 45; i++) {
|
|
float angle = i * PI * 4.0f / 25.0f;
|
|
float xo = Mth::cos(angle) * 0.7f;
|
|
float zo = Mth::sin(angle) * 0.7f;
|
|
|
|
level->addParticle(eParticleType_netherportal, player->x + xo,
|
|
player->y - player->heightOffset + 1.62f - i * .05f,
|
|
player->z + zo, 0, 0, 0);
|
|
}
|
|
}
|
|
|
|
Slot* Player::getInventorySlot(int slotId) { return nullptr; }
|
|
|
|
void Player::remove() {
|
|
LivingEntity::remove();
|
|
inventoryMenu->removed(
|
|
std::dynamic_pointer_cast<Player>(shared_from_this()));
|
|
if (containerMenu != nullptr) {
|
|
containerMenu->removed(
|
|
std::dynamic_pointer_cast<Player>(shared_from_this()));
|
|
}
|
|
}
|
|
|
|
bool Player::isInWall() { return !m_isSleeping && LivingEntity::isInWall(); }
|
|
|
|
bool Player::isLocalPlayer() { return false; }
|
|
|
|
Player::BedSleepingResult Player::startSleepInBed(int x, int y, int z,
|
|
bool bTestUse) {
|
|
if (!level->isClientSide || bTestUse) {
|
|
if (isSleeping() || !isAlive()) {
|
|
return OTHER_PROBLEM;
|
|
}
|
|
|
|
if (!level->dimension->isNaturalDimension()) {
|
|
// may not sleep in this dimension
|
|
return NOT_POSSIBLE_HERE;
|
|
}
|
|
|
|
// 4J-PB - I'm going to move the position of these tests below
|
|
// The distance check should be before the day check, otherwise you can
|
|
// use the bed in daytime from far away and you'll get the message about
|
|
// only sleeping at night
|
|
|
|
if (abs(this->x - x) > 3 || abs(this->y - y) > 2 ||
|
|
abs(this->z - z) > 3) {
|
|
// too far away
|
|
return TOO_FAR_AWAY;
|
|
}
|
|
|
|
if (!bTestUse) {
|
|
// 4J-PB - We still want the tooltip for Sleep
|
|
|
|
double hRange = 8;
|
|
double vRange = 5;
|
|
AABB monster_bb =
|
|
AABB(x, y, z, x, y, z).grow(hRange, vRange, hRange);
|
|
std::vector<std::shared_ptr<Entity> >* monsters =
|
|
level->getEntitiesOfClass(typeid(Monster), &monster_bb);
|
|
if (!monsters->empty()) {
|
|
delete monsters;
|
|
return NOT_SAFE;
|
|
}
|
|
delete monsters;
|
|
}
|
|
|
|
// This causes a message to be displayed, so we do want to show the
|
|
// tooltip in test mode
|
|
if (!bTestUse && level->isDay()) {
|
|
// may not sleep during day
|
|
return NOT_POSSIBLE_NOW;
|
|
}
|
|
}
|
|
|
|
if (bTestUse) {
|
|
// 4J-PB - we're just testing use, and we get here, then the bed can be
|
|
// used
|
|
return OK;
|
|
}
|
|
|
|
if (isRiding()) {
|
|
ride(nullptr);
|
|
}
|
|
|
|
setSize(0.2f, 0.2f);
|
|
heightOffset = .2f;
|
|
if (level->hasChunkAt(x, y, z)) {
|
|
int data = level->getData(x, y, z);
|
|
int direction = BedTile::getDirection(data);
|
|
float xo = .5f, zo = .5f;
|
|
|
|
switch (direction) {
|
|
case Direction::SOUTH:
|
|
zo = .9f;
|
|
break;
|
|
case Direction::NORTH:
|
|
zo = .1f;
|
|
break;
|
|
case Direction::WEST:
|
|
xo = .1f;
|
|
break;
|
|
case Direction::EAST:
|
|
xo = .9f;
|
|
break;
|
|
}
|
|
setBedOffset(direction);
|
|
setPos(x + xo, y + 15.0f / 16.0f, z + zo);
|
|
} else {
|
|
setPos(x + .5f, y + 15.0f / 16.0f, z + .5f);
|
|
}
|
|
m_isSleeping = true;
|
|
sleepCounter = 0;
|
|
bedPosition = new Pos(x, y, z);
|
|
xd = zd = yd = 0;
|
|
|
|
if (!level->isClientSide) {
|
|
level->updateSleepingPlayerList();
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
void Player::setBedOffset(int bedDirection) {
|
|
// place position on pillow and feet at bottom
|
|
bedOffsetX = 0;
|
|
bedOffsetZ = 0;
|
|
|
|
switch (bedDirection) {
|
|
case Direction::SOUTH:
|
|
bedOffsetZ = -1.8f;
|
|
break;
|
|
case Direction::NORTH:
|
|
bedOffsetZ = 1.8f;
|
|
break;
|
|
case Direction::WEST:
|
|
bedOffsetX = 1.8f;
|
|
break;
|
|
case Direction::EAST:
|
|
bedOffsetX = -1.8f;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param forcefulWakeUp
|
|
* If the player has been forced to wake up. When this happens,
|
|
* the client will skip the wake-up animation. For example, when
|
|
* the player is hurt or the bed is destroyed.
|
|
* @param updateLevelList
|
|
* If the level's sleeping player list needs to be updated. This
|
|
* is usually the case.
|
|
* @param saveRespawnPoint
|
|
* TODO
|
|
*/
|
|
void Player::stopSleepInBed(bool forcefulWakeUp, bool updateLevelList,
|
|
bool saveRespawnPoint) {
|
|
setSize(0.6f, 1.8f);
|
|
setDefaultHeadHeight();
|
|
|
|
Pos* pos = bedPosition;
|
|
Pos* standUp = bedPosition;
|
|
if (pos != nullptr &&
|
|
level->getTile(pos->x, pos->y, pos->z) == Tile::bed_Id) {
|
|
BedTile::setOccupied(level, pos->x, pos->y, pos->z, false);
|
|
|
|
standUp =
|
|
BedTile::findStandUpPosition(level, pos->x, pos->y, pos->z, 0);
|
|
if (standUp == nullptr) {
|
|
standUp = new Pos(pos->x, pos->y + 1, pos->z);
|
|
}
|
|
setPos(standUp->x + .5f, standUp->y + heightOffset + .1f,
|
|
standUp->z + .5f);
|
|
}
|
|
|
|
m_isSleeping = false;
|
|
if (!level->isClientSide && updateLevelList) {
|
|
level->updateSleepingPlayerList();
|
|
}
|
|
if (forcefulWakeUp) {
|
|
sleepCounter = 0;
|
|
} else {
|
|
sleepCounter = SLEEP_DURATION;
|
|
}
|
|
if (saveRespawnPoint) {
|
|
setRespawnPosition(bedPosition, false);
|
|
}
|
|
}
|
|
|
|
bool Player::checkBed() {
|
|
return (level->getTile(bedPosition->x, bedPosition->y, bedPosition->z) ==
|
|
Tile::bed_Id);
|
|
}
|
|
|
|
Pos* Player::checkBedValidRespawnPosition(Level* level, Pos* pos, bool forced) {
|
|
// make sure the chunks around the bed exist
|
|
ChunkSource* chunkSource = level->getChunkSource();
|
|
chunkSource->create((pos->x - 3) >> 4, (pos->z - 3) >> 4);
|
|
chunkSource->create((pos->x + 3) >> 4, (pos->z - 3) >> 4);
|
|
chunkSource->create((pos->x - 3) >> 4, (pos->z + 3) >> 4);
|
|
chunkSource->create((pos->x + 3) >> 4, (pos->z + 3) >> 4);
|
|
|
|
// make sure the bed is still standing
|
|
if (level->getTile(pos->x, pos->y, pos->z) != Tile::bed_Id) {
|
|
Material* bottomMaterial = level->getMaterial(pos->x, pos->y, pos->z);
|
|
Material* topMaterial = level->getMaterial(pos->x, pos->y + 1, pos->z);
|
|
bool freeFeet =
|
|
!bottomMaterial->isSolid() && !bottomMaterial->isLiquid();
|
|
bool freeHead = !topMaterial->isSolid() && !topMaterial->isLiquid();
|
|
|
|
if (forced && freeFeet && freeHead) {
|
|
return pos;
|
|
}
|
|
return nullptr;
|
|
}
|
|
// make sure the bed still has a stand-up position
|
|
Pos* standUp =
|
|
BedTile::findStandUpPosition(level, pos->x, pos->y, pos->z, 0);
|
|
return standUp;
|
|
}
|
|
|
|
float Player::getSleepRotation() {
|
|
if (bedPosition != nullptr) {
|
|
int data =
|
|
level->getData(bedPosition->x, bedPosition->y, bedPosition->z);
|
|
int direction = BedTile::getDirection(data);
|
|
|
|
switch (direction) {
|
|
case Direction::SOUTH:
|
|
return 90;
|
|
case Direction::WEST:
|
|
return 0;
|
|
case Direction::NORTH:
|
|
return 270;
|
|
case Direction::EAST:
|
|
return 180;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool Player::isSleeping() { return m_isSleeping; }
|
|
|
|
bool Player::isSleepingLongEnough() {
|
|
return m_isSleeping && sleepCounter >= SLEEP_DURATION;
|
|
}
|
|
|
|
int Player::getSleepTimer() { return sleepCounter; }
|
|
|
|
// 4J-PB - added for death fade
|
|
int Player::getDeathFadeTimer() { return deathFadeCounter; }
|
|
|
|
bool Player::getPlayerFlag(int flag) {
|
|
return (entityData->getByte(DATA_PLAYER_FLAGS_ID) & (1 << flag)) != 0;
|
|
}
|
|
|
|
void Player::setPlayerFlag(int flag, bool value) {
|
|
uint8_t currentValue = entityData->getByte(DATA_PLAYER_FLAGS_ID);
|
|
if (value) {
|
|
entityData->set(DATA_PLAYER_FLAGS_ID,
|
|
(uint8_t)(currentValue | (1 << flag)));
|
|
} else {
|
|
entityData->set(DATA_PLAYER_FLAGS_ID,
|
|
(uint8_t)(currentValue & ~(1 << flag)));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This method is currently only relevant to client-side players. It will
|
|
* try to load the messageId from the language file and display it to the
|
|
* client.
|
|
*/
|
|
void Player::displayClientMessage(int messageId) {}
|
|
|
|
Pos* Player::getRespawnPosition() { return respawnPosition; }
|
|
|
|
bool Player::isRespawnForced() { return respawnForced; }
|
|
|
|
void Player::setRespawnPosition(Pos* respawnPosition, bool forced) {
|
|
if (respawnPosition != nullptr) {
|
|
this->respawnPosition = new Pos(*respawnPosition);
|
|
respawnForced = forced;
|
|
} else {
|
|
this->respawnPosition = nullptr;
|
|
respawnForced = false;
|
|
}
|
|
}
|
|
|
|
void Player::awardStat(Stat* stat, byteArray paramBlob) {
|
|
if (paramBlob.data != nullptr) {
|
|
delete[] paramBlob.data;
|
|
}
|
|
}
|
|
|
|
void Player::jumpFromGround() {
|
|
LivingEntity::jumpFromGround();
|
|
|
|
// 4J Stu - This seems to have been missed from 1.7.3, but do we care?
|
|
// awardStat(Stats::jump, 1);
|
|
|
|
if (isSprinting()) {
|
|
causeFoodExhaustion(FoodConstants::EXHAUSTION_SPRINT_JUMP);
|
|
} else {
|
|
causeFoodExhaustion(FoodConstants::EXHAUSTION_JUMP);
|
|
}
|
|
}
|
|
|
|
void Player::travel(float xa, float ya) {
|
|
double preX = x, preY = y, preZ = z;
|
|
|
|
if (abilities.flying && riding == nullptr) {
|
|
double ydo = yd;
|
|
float ofs = flyingSpeed;
|
|
flyingSpeed = abilities.getFlyingSpeed();
|
|
LivingEntity::travel(xa, ya);
|
|
yd = ydo * 0.6;
|
|
flyingSpeed = ofs;
|
|
} else {
|
|
LivingEntity::travel(xa, ya);
|
|
}
|
|
|
|
checkMovementStatistiscs(x - preX, y - preY, z - preZ);
|
|
}
|
|
|
|
float Player::getSpeed() {
|
|
return (float)getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED)
|
|
->getValue();
|
|
}
|
|
|
|
void Player::checkMovementStatistiscs(double dx, double dy, double dz) {
|
|
if (riding != nullptr) {
|
|
return;
|
|
}
|
|
if (isUnderLiquid(Material::water)) {
|
|
int distance =
|
|
(int)Math::round(sqrt(dx * dx + dy * dy + dz * dz) * 100.0f);
|
|
if (distance > 0) {
|
|
// awardStat(Stats::diveOneCm, distance);
|
|
causeFoodExhaustion(FoodConstants::EXHAUSTION_SWIM * distance *
|
|
.01f);
|
|
}
|
|
} else if (isInWater()) {
|
|
int horizontalDistance =
|
|
(int)Math::round(sqrt(dx * dx + dz * dz) * 100.0f);
|
|
if (horizontalDistance > 0) {
|
|
distanceSwim += horizontalDistance;
|
|
if (distanceSwim >= 100) {
|
|
int newDistance = distanceSwim - (distanceSwim % 100);
|
|
distanceSwim -= newDistance;
|
|
awardStat(GenericStats::swimOneM(),
|
|
GenericStats::param_swim(newDistance / 100));
|
|
}
|
|
causeFoodExhaustion(FoodConstants::EXHAUSTION_SWIM *
|
|
horizontalDistance * .01f);
|
|
}
|
|
} else if (onLadder()) {
|
|
if (dy > 0) {
|
|
distanceClimb += (int)Math::round(dy * 100.0f);
|
|
if (distanceClimb >= 100) {
|
|
int newDistance = distanceClimb - (distanceClimb % 100);
|
|
distanceClimb -= newDistance;
|
|
awardStat(GenericStats::climbOneM(),
|
|
GenericStats::param_climb(newDistance / 100));
|
|
}
|
|
}
|
|
} else if (onGround) {
|
|
int horizontalDistance =
|
|
(int)Math::round(sqrt(dx * dx + dz * dz) * 100.0f);
|
|
if (horizontalDistance > 0) {
|
|
distanceWalk += horizontalDistance;
|
|
if (distanceWalk >= 100) {
|
|
int newDistance = distanceWalk - (distanceWalk % 100);
|
|
distanceWalk -= newDistance;
|
|
awardStat(GenericStats::walkOneM(),
|
|
GenericStats::param_walk(newDistance / 100));
|
|
}
|
|
if (isSprinting()) {
|
|
causeFoodExhaustion(FoodConstants::EXHAUSTION_SPRINT *
|
|
horizontalDistance * .01f);
|
|
} else {
|
|
causeFoodExhaustion(FoodConstants::EXHAUSTION_WALK *
|
|
horizontalDistance * .01f);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Player::checkRidingStatistiscs(double dx, double dy, double dz) {
|
|
if (riding != nullptr) {
|
|
int distance =
|
|
(int)Math::round(sqrt(dx * dx + dy * dy + dz * dz) * 100.0f);
|
|
if (distance > 0) {
|
|
if (riding->instanceof(eTYPE_MINECART)) {
|
|
distanceMinecart += distance;
|
|
if (distanceMinecart >= 100) {
|
|
int newDistance =
|
|
distanceMinecart - (distanceMinecart % 100);
|
|
distanceMinecart -= newDistance;
|
|
awardStat(GenericStats::minecartOneM(),
|
|
GenericStats::param_minecart(newDistance / 100));
|
|
}
|
|
|
|
int dist = 0;
|
|
if (minecartAchievementPos == nullptr) {
|
|
minecartAchievementPos =
|
|
new Pos(Mth::floor(x), Mth::floor(y), Mth::floor(z));
|
|
}
|
|
// 4J-PB - changed this because our world isn't big enough to go
|
|
// 1000m
|
|
else {
|
|
// 4-JEV, changed slightly to add extra parameters for event
|
|
// on durango.
|
|
int dist = minecartAchievementPos->dist(
|
|
Mth::floor(x), Mth::floor(y), Mth::floor(z));
|
|
if ((m_bAwardedOnARail == false) && (dist >= 500)) {
|
|
awardStat(GenericStats::onARail(),
|
|
GenericStats::param_onARail(dist));
|
|
m_bAwardedOnARail = true;
|
|
}
|
|
}
|
|
|
|
} else if (riding->instanceof(eTYPE_BOAT)) {
|
|
distanceBoat += distance;
|
|
if (distanceBoat >= 100) {
|
|
int newDistance = distanceBoat - (distanceBoat % 100);
|
|
distanceBoat -= newDistance;
|
|
awardStat(GenericStats::boatOneM(),
|
|
GenericStats::param_boat(newDistance / 100));
|
|
}
|
|
} else if (riding->instanceof(eTYPE_PIG)) {
|
|
distancePig += distance;
|
|
if (distancePig >= 100) {
|
|
int newDistance = distancePig - (distancePig % 100);
|
|
distancePig -= newDistance;
|
|
awardStat(GenericStats::pigOneM(),
|
|
GenericStats::param_pig(newDistance / 100));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Player::causeFallDamage(float distance) {
|
|
if (abilities.mayfly) return;
|
|
|
|
if (distance >= 2) {
|
|
distanceFall += (int)Math::round(distance * 100.0);
|
|
if (distanceFall >= 100) {
|
|
int newDistance = distanceFall - (distanceFall % 100);
|
|
distanceFall -= newDistance;
|
|
awardStat(GenericStats::fallOneM(),
|
|
GenericStats::param_fall(newDistance / 100));
|
|
}
|
|
}
|
|
LivingEntity::causeFallDamage(distance);
|
|
}
|
|
|
|
void Player::killed(std::shared_ptr<LivingEntity> mob) {
|
|
// 4J-PB - added the lavaslime enemy - fix for #64007 - TU7: Code:
|
|
// Achievements: TCR#073: Killing Magma Cubes doesn't unlock "Monster
|
|
// Hunter" Achievement.
|
|
if (mob->instanceof(eTYPE_ENEMY) || mob->GetType() == eTYPE_GHAST ||
|
|
mob->GetType() == eTYPE_SLIME || mob->GetType() == eTYPE_LAVASLIME ||
|
|
mob->GetType() == eTYPE_ENDERDRAGON) {
|
|
awardStat(GenericStats::killEnemy(), GenericStats::param_noArgs());
|
|
|
|
switch (mob->GetType()) {
|
|
case eTYPE_CREEPER:
|
|
awardStat(GenericStats::killsCreeper(),
|
|
GenericStats::param_noArgs());
|
|
break;
|
|
case eTYPE_SKELETON:
|
|
if (mob->isRiding() && mob->riding->GetType() == eTYPE_SPIDER)
|
|
awardStat(GenericStats::killsSpiderJockey(),
|
|
GenericStats::param_noArgs());
|
|
else
|
|
awardStat(GenericStats::killsSkeleton(),
|
|
GenericStats::param_noArgs());
|
|
break;
|
|
case eTYPE_SPIDER:
|
|
if (mob->rider.lock() != nullptr &&
|
|
mob->rider.lock()->GetType() == eTYPE_SKELETON)
|
|
awardStat(GenericStats::killsSpiderJockey(),
|
|
GenericStats::param_noArgs());
|
|
else
|
|
awardStat(GenericStats::killsSpider(),
|
|
GenericStats::param_noArgs());
|
|
break;
|
|
case eTYPE_ZOMBIE:
|
|
awardStat(GenericStats::killsZombie(),
|
|
GenericStats::param_noArgs());
|
|
break;
|
|
case eTYPE_PIGZOMBIE:
|
|
if (level->dimension->id == 0)
|
|
awardStat(GenericStats::killsZombiePigman(),
|
|
GenericStats::param_noArgs());
|
|
else
|
|
awardStat(GenericStats::killsNetherZombiePigman(),
|
|
GenericStats::param_noArgs());
|
|
break;
|
|
case eTYPE_GHAST:
|
|
awardStat(GenericStats::killsGhast(),
|
|
GenericStats::param_noArgs());
|
|
break;
|
|
case eTYPE_SLIME:
|
|
awardStat(GenericStats::killsSlime(),
|
|
GenericStats::param_noArgs());
|
|
break;
|
|
case eTYPE_ENDERDRAGON:
|
|
awardStat(GenericStats::killsEnderdragon(),
|
|
GenericStats::param_noArgs());
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
} else if (mob->GetType() == eTYPE_COW) {
|
|
awardStat(GenericStats::killCow(), GenericStats::param_noArgs());
|
|
}
|
|
}
|
|
|
|
void Player::makeStuckInWeb() {
|
|
if (!abilities.flying) LivingEntity::makeStuckInWeb();
|
|
}
|
|
|
|
Icon* Player::getItemInHandIcon(std::shared_ptr<ItemInstance> item, int layer) {
|
|
Icon* icon = LivingEntity::getItemInHandIcon(item, layer);
|
|
if (item->id == Item::fishingRod->id && fishing != nullptr) {
|
|
icon = Item::fishingRod->getEmptyIcon();
|
|
} else if (item->getItem()->hasMultipleSpriteLayers()) {
|
|
return item->getItem()->getLayerIcon(item->getAuxValue(), layer);
|
|
} else if (useItem != nullptr && item->id == Item::bow_Id) {
|
|
int ticksHeld = (item->getUseDuration() - useItemDuration);
|
|
if (ticksHeld >= BowItem::MAX_DRAW_DURATION - 2) {
|
|
return Item::bow->getDrawnIcon(2);
|
|
}
|
|
if (ticksHeld > (2 * BowItem::MAX_DRAW_DURATION) / 3) {
|
|
return Item::bow->getDrawnIcon(1);
|
|
}
|
|
if (ticksHeld > 0) {
|
|
return Item::bow->getDrawnIcon(0);
|
|
}
|
|
}
|
|
return icon;
|
|
}
|
|
|
|
std::shared_ptr<ItemInstance> Player::getArmor(int pos) {
|
|
return inventory->getArmor(pos);
|
|
}
|
|
|
|
void Player::increaseXp(int i) {
|
|
increaseScore(i);
|
|
int max = INT_MAX - totalExperience;
|
|
if (i > max) {
|
|
i = max;
|
|
}
|
|
experienceProgress += (float)i / getXpNeededForNextLevel();
|
|
totalExperience += i;
|
|
while (experienceProgress >= 1) {
|
|
experienceProgress =
|
|
(experienceProgress - 1) * getXpNeededForNextLevel();
|
|
giveExperienceLevels(1);
|
|
experienceProgress /= getXpNeededForNextLevel();
|
|
}
|
|
}
|
|
|
|
void Player::giveExperienceLevels(int amount) {
|
|
experienceLevel += amount;
|
|
if (experienceLevel < 0) {
|
|
experienceLevel = 0;
|
|
experienceProgress = 0;
|
|
totalExperience = 0;
|
|
}
|
|
|
|
if (amount > 0 && experienceLevel % 5 == 0 &&
|
|
lastLevelUpTime <
|
|
tickCount - SharedConstants::TICKS_PER_SECOND * 5.0f) {
|
|
float vol = experienceLevel > 30 ? 1 : experienceLevel / 30.0f;
|
|
level->playEntitySound(shared_from_this(), eSoundType_RANDOM_LEVELUP,
|
|
vol * 0.75f, 1);
|
|
lastLevelUpTime = tickCount;
|
|
}
|
|
}
|
|
|
|
int Player::getXpNeededForNextLevel() {
|
|
// Update xp calculations from 1.3
|
|
if (experienceLevel >= 30) {
|
|
return 17 + 15 * 3 + (experienceLevel - 30) * 7;
|
|
}
|
|
if (experienceLevel >= 15) {
|
|
return 17 + (experienceLevel - 15) * 3;
|
|
}
|
|
return 17;
|
|
}
|
|
|
|
/**
|
|
* This method adds on to the player's exhaustion, which may decrease the
|
|
* player's food level.
|
|
*
|
|
* @param amount
|
|
* Amount of exhaustion to add, between 0 and 20 (setting it to
|
|
* 20 will guarantee that at least 1, and at most 4, food points
|
|
* are deducted). See FoodConstants for cost suggestions.
|
|
*/
|
|
void Player::causeFoodExhaustion(float amount) {
|
|
if (isAllowedToIgnoreExhaustion() || (isAllowedToFly() && abilities.flying))
|
|
return;
|
|
if (abilities.invulnerable || hasInvulnerablePrivilege()) return;
|
|
|
|
// 4J Stu - Added 1.8.2 bug fix (TU6) - If players cannot eat, then their
|
|
// food bar should not decrease due to exhaustion
|
|
if (app.GetGameHostOption(eGameHostOption_TrustPlayers) == 0 &&
|
|
getPlayerGamePrivilege(Player::ePlayerGamePrivilege_CannotBuild) != 0)
|
|
return;
|
|
|
|
if (!level->isClientSide) {
|
|
foodData.addExhaustion(amount);
|
|
}
|
|
}
|
|
|
|
FoodData* Player::getFoodData() { return &foodData; }
|
|
|
|
bool Player::canEat(bool magicalItem) {
|
|
return (magicalItem || foodData.needsFood()) && !abilities.invulnerable &&
|
|
!hasInvulnerablePrivilege();
|
|
}
|
|
|
|
bool Player::isHurt() {
|
|
return getHealth() > 0 && getHealth() < getMaxHealth();
|
|
}
|
|
|
|
void Player::startUsingItem(std::shared_ptr<ItemInstance> instance,
|
|
int duration) {
|
|
if (instance == useItem) return;
|
|
useItem = instance;
|
|
useItemDuration = duration;
|
|
if (!level->isClientSide) {
|
|
setUsingItemFlag(true);
|
|
}
|
|
|
|
// 4J-JEV, hook for ItemUsed event, and ironbelly achievement.
|
|
awardStat(
|
|
GenericStats::itemsUsed(instance->getItem()->id),
|
|
GenericStats::param_itemsUsed(
|
|
std::dynamic_pointer_cast<Player>(shared_from_this()), instance));
|
|
|
|
#if defined(_EXTENDED_ACHIEVEMENTS)
|
|
if ((instance->getItem()->id == Item::rotten_flesh_Id) &&
|
|
(getFoodData()->getFoodLevel() == 0))
|
|
awardStat(GenericStats::ironBelly(), GenericStats::param_ironBelly());
|
|
#endif
|
|
}
|
|
|
|
bool Player::mayDestroyBlockAt(int x, int y, int z) {
|
|
if (abilities.mayBuild) {
|
|
return true;
|
|
}
|
|
int t = level->getTile(x, y, z);
|
|
if (t > 0) {
|
|
Tile* tile = Tile::tiles[t];
|
|
|
|
if (tile->material->isDestroyedByHand()) {
|
|
return true;
|
|
} else if (getSelectedItem() != nullptr) {
|
|
std::shared_ptr<ItemInstance> carried = getSelectedItem();
|
|
|
|
if (carried->canDestroySpecial(tile) ||
|
|
carried->getDestroySpeed(tile) > 1) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Player::mayUseItemAt(int x, int y, int z, int face,
|
|
std::shared_ptr<ItemInstance> item) {
|
|
if (abilities.mayBuild) {
|
|
return true;
|
|
}
|
|
if (item != nullptr) {
|
|
return item->mayBePlacedInAdventureMode();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int Player::getExperienceReward(std::shared_ptr<Player> killedBy) {
|
|
if (level->getGameRules()->getBoolean(GameRules::RULE_KEEPINVENTORY))
|
|
return 0;
|
|
int reward = experienceLevel * 7;
|
|
if (reward > 100) {
|
|
return 100;
|
|
}
|
|
return reward;
|
|
}
|
|
|
|
bool Player::isAlwaysExperienceDropper() {
|
|
// players always drop experience
|
|
return true;
|
|
}
|
|
|
|
std::wstring Player::getAName() { return name; }
|
|
|
|
bool Player::shouldShowName() { return true; }
|
|
|
|
void Player::restoreFrom(std::shared_ptr<Player> oldPlayer, bool restoreAll) {
|
|
if (restoreAll) {
|
|
inventory->replaceWith(oldPlayer->inventory);
|
|
|
|
setHealth(oldPlayer->getHealth());
|
|
foodData = oldPlayer->foodData;
|
|
|
|
experienceLevel = oldPlayer->experienceLevel;
|
|
totalExperience = oldPlayer->totalExperience;
|
|
experienceProgress = oldPlayer->experienceProgress;
|
|
|
|
setScore(oldPlayer->getScore());
|
|
portalEntranceDir = oldPlayer->portalEntranceDir;
|
|
} else if (level->getGameRules()->getBoolean(
|
|
GameRules::RULE_KEEPINVENTORY)) {
|
|
inventory->replaceWith(oldPlayer->inventory);
|
|
|
|
experienceLevel = oldPlayer->experienceLevel;
|
|
totalExperience = oldPlayer->totalExperience;
|
|
experienceProgress = oldPlayer->experienceProgress;
|
|
setScore(oldPlayer->getScore());
|
|
}
|
|
enderChestInventory = oldPlayer->enderChestInventory;
|
|
}
|
|
|
|
bool Player::makeStepSound() { return !abilities.flying; }
|
|
|
|
void Player::onUpdateAbilities() {}
|
|
|
|
void Player::setGameMode(GameType* mode) {}
|
|
|
|
std::wstring Player::getName() { return name; }
|
|
|
|
std::wstring Player::getDisplayName() {
|
|
// PlayerTeam.formatNameForTeam(getTeam(), name);
|
|
|
|
// If player display name is not set, return name
|
|
return m_displayName.size() > 0 ? m_displayName : name;
|
|
}
|
|
|
|
std::wstring Player::getNetworkName() {
|
|
// 4J: We can only transmit gamertag in network packets
|
|
return name;
|
|
}
|
|
|
|
Level* Player::getCommandSenderWorld() { return level; }
|
|
|
|
std::shared_ptr<PlayerEnderChestContainer> Player::getEnderChestInventory() {
|
|
return enderChestInventory;
|
|
}
|
|
|
|
std::shared_ptr<ItemInstance> Player::getCarried(int slot) {
|
|
if (slot == 0) return inventory->getSelected();
|
|
return inventory->armor[slot - 1];
|
|
}
|
|
|
|
std::shared_ptr<ItemInstance> Player::getCarriedItem() {
|
|
return inventory->getSelected();
|
|
}
|
|
|
|
void Player::setEquippedSlot(int slot, std::shared_ptr<ItemInstance> item) {
|
|
inventory->armor[slot] = item;
|
|
}
|
|
|
|
bool Player::isInvisibleTo(std::shared_ptr<Player> player) {
|
|
return isInvisible();
|
|
}
|
|
|
|
ItemInstanceArray Player::getEquipmentSlots() { return inventory->armor; }
|
|
|
|
bool Player::isCapeHidden() { return getPlayerFlag(FLAG_HIDE_CAPE); }
|
|
|
|
bool Player::isPushedByWater() { return !abilities.flying; }
|
|
|
|
Scoreboard* Player::getScoreboard() { return level->getScoreboard(); }
|
|
|
|
Team* Player::getTeam() { return getScoreboard()->getPlayersTeam(name); }
|
|
|
|
void Player::setAbsorptionAmount(float absorptionAmount) {
|
|
if (absorptionAmount < 0) absorptionAmount = 0;
|
|
getEntityData()->set(DATA_PLAYER_ABSORPTION_ID, absorptionAmount);
|
|
}
|
|
|
|
float Player::getAbsorptionAmount() {
|
|
return getEntityData()->getFloat(DATA_PLAYER_ABSORPTION_ID);
|
|
}
|
|
|
|
int Player::getTexture() {
|
|
switch (m_skinIndex) {
|
|
case eDefaultSkins_Skin0:
|
|
return TN_MOB_CHAR; // 4J - was L"/mob/char.png";
|
|
case eDefaultSkins_Skin1:
|
|
return TN_MOB_CHAR1; // 4J - was L"/mob/char1.png";
|
|
case eDefaultSkins_Skin2:
|
|
return TN_MOB_CHAR2; // 4J - was L"/mob/char2.png";
|
|
case eDefaultSkins_Skin3:
|
|
return TN_MOB_CHAR3; // 4J - was L"/mob/char3.png";
|
|
case eDefaultSkins_Skin4:
|
|
return TN_MOB_CHAR4; // 4J - was L"/mob/char4.png";
|
|
case eDefaultSkins_Skin5:
|
|
return TN_MOB_CHAR5; // 4J - was L"/mob/char5.png";
|
|
case eDefaultSkins_Skin6:
|
|
return TN_MOB_CHAR6; // 4J - was L"/mob/char6.png";
|
|
case eDefaultSkins_Skin7:
|
|
return TN_MOB_CHAR7; // 4J - was L"/mob/char7.png";
|
|
|
|
default:
|
|
return TN_MOB_CHAR; // 4J - was L"/mob/char.png";
|
|
}
|
|
}
|
|
|
|
int Player::hash_fnct(const std::shared_ptr<Player> k) {
|
|
// TODO 4J Stu - Should we just be using the pointers and hashing them?
|
|
return (int)std::hash<std::wstring>()(k->name);
|
|
}
|
|
|
|
bool Player::eq_test(const std::shared_ptr<Player> x,
|
|
const std::shared_ptr<Player> y) {
|
|
// TODO 4J Stu - Should we just be using the pointers and comparing them for
|
|
// equality?
|
|
return x->name.compare(y->name) ==
|
|
0; // 4J Stu - Names are completely unique?
|
|
}
|
|
|
|
unsigned int Player::getPlayerGamePrivilege(EPlayerGamePrivileges privilege) {
|
|
return Player::getPlayerGamePrivilege(m_uiGamePrivileges, privilege);
|
|
}
|
|
|
|
unsigned int Player::getPlayerGamePrivilege(unsigned int uiGamePrivileges,
|
|
EPlayerGamePrivileges privilege) {
|
|
if (privilege == ePlayerGamePrivilege_All) {
|
|
return uiGamePrivileges;
|
|
} else if (privilege < ePlayerGamePrivilege_MAX) {
|
|
return uiGamePrivileges & (1 << privilege);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void Player::setPlayerGamePrivilege(EPlayerGamePrivileges privilege,
|
|
unsigned int value) {
|
|
Player::setPlayerGamePrivilege(m_uiGamePrivileges, privilege, value);
|
|
}
|
|
|
|
void Player::setPlayerGamePrivilege(unsigned int& uiGamePrivileges,
|
|
EPlayerGamePrivileges privilege,
|
|
unsigned int value) {
|
|
if (privilege == ePlayerGamePrivilege_All) {
|
|
uiGamePrivileges = value;
|
|
} else if (privilege == ePlayerGamePrivilege_HOST) {
|
|
if (value == 0) {
|
|
Player::setPlayerGamePrivilege(uiGamePrivileges,
|
|
ePlayerGamePrivilege_Op, 0);
|
|
} else {
|
|
Player::setPlayerGamePrivilege(uiGamePrivileges,
|
|
ePlayerGamePrivilege_Op, 1);
|
|
Player::setPlayerGamePrivilege(
|
|
uiGamePrivileges, ePlayerGamePrivilege_CanToggleInvisible, 1);
|
|
Player::setPlayerGamePrivilege(
|
|
uiGamePrivileges, ePlayerGamePrivilege_CanToggleFly, 1);
|
|
Player::setPlayerGamePrivilege(
|
|
uiGamePrivileges, ePlayerGamePrivilege_CanToggleClassicHunger,
|
|
1);
|
|
Player::setPlayerGamePrivilege(uiGamePrivileges,
|
|
ePlayerGamePrivilege_CanTeleport, 1);
|
|
}
|
|
} else if (privilege < ePlayerGamePrivilege_MAX) {
|
|
if (value != 0) {
|
|
uiGamePrivileges |= (1 << privilege);
|
|
} else {
|
|
// Some privileges will turn other things off as well
|
|
switch (privilege) {
|
|
case ePlayerGamePrivilege_CanToggleInvisible:
|
|
Player::setPlayerGamePrivilege(
|
|
uiGamePrivileges, ePlayerGamePrivilege_Invisible, 0);
|
|
Player::setPlayerGamePrivilege(
|
|
uiGamePrivileges, ePlayerGamePrivilege_Invulnerable, 0);
|
|
break;
|
|
case ePlayerGamePrivilege_CanToggleFly:
|
|
Player::setPlayerGamePrivilege(
|
|
uiGamePrivileges, ePlayerGamePrivilege_CanFly, 0);
|
|
break;
|
|
case ePlayerGamePrivilege_CanToggleClassicHunger:
|
|
Player::setPlayerGamePrivilege(
|
|
uiGamePrivileges, ePlayerGamePrivilege_ClassicHunger,
|
|
0);
|
|
break;
|
|
case ePlayerGamePrivilege_Op:
|
|
Player::setPlayerGamePrivilege(
|
|
uiGamePrivileges,
|
|
ePlayerGamePrivilege_CanToggleInvisible, 0);
|
|
Player::setPlayerGamePrivilege(
|
|
uiGamePrivileges, ePlayerGamePrivilege_CanToggleFly, 0);
|
|
Player::setPlayerGamePrivilege(
|
|
uiGamePrivileges,
|
|
ePlayerGamePrivilege_CanToggleClassicHunger, 0);
|
|
Player::setPlayerGamePrivilege(
|
|
uiGamePrivileges, ePlayerGamePrivilege_CanTeleport, 0);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
// off
|
|
uiGamePrivileges &= ~(1 << privilege);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Player::isAllowedToUse(Tile* tile) {
|
|
bool allowed = true;
|
|
if (tile != nullptr &&
|
|
app.GetGameHostOption(eGameHostOption_TrustPlayers) == 0) {
|
|
allowed = false;
|
|
|
|
if (getPlayerGamePrivilege(
|
|
Player::ePlayerGamePrivilege_CanUseDoorsAndSwitches) != 0) {
|
|
switch (tile->id) {
|
|
case Tile::door_wood_Id:
|
|
case Tile::button_stone_Id:
|
|
case Tile::button_wood_Id:
|
|
case Tile::lever_Id:
|
|
case Tile::fenceGate_Id:
|
|
case Tile::trapdoor_Id:
|
|
allowed = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (getPlayerGamePrivilege(
|
|
Player::ePlayerGamePrivilege_CanUseContainers) != 0) {
|
|
switch (tile->id) {
|
|
case Tile::chest_Id:
|
|
case Tile::furnace_Id:
|
|
case Tile::furnace_lit_Id:
|
|
case Tile::dispenser_Id:
|
|
case Tile::brewingStand_Id:
|
|
case Tile::enchantTable_Id:
|
|
case Tile::workBench_Id:
|
|
case Tile::anvil_Id:
|
|
case Tile::enderChest_Id:
|
|
allowed = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!allowed && getPlayerGamePrivilege(
|
|
Player::ePlayerGamePrivilege_CannotBuild) == 0) {
|
|
switch (tile->id) {
|
|
case Tile::door_wood_Id:
|
|
case Tile::button_stone_Id:
|
|
case Tile::button_wood_Id:
|
|
case Tile::lever_Id:
|
|
case Tile::fenceGate_Id:
|
|
case Tile::trapdoor_Id:
|
|
case Tile::chest_Id:
|
|
case Tile::furnace_Id:
|
|
case Tile::furnace_lit_Id:
|
|
case Tile::dispenser_Id:
|
|
case Tile::brewingStand_Id:
|
|
case Tile::enchantTable_Id:
|
|
case Tile::workBench_Id:
|
|
case Tile::anvil_Id:
|
|
case Tile::enderChest_Id:
|
|
allowed = false;
|
|
break;
|
|
default:
|
|
allowed = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return allowed;
|
|
}
|
|
|
|
bool Player::isAllowedToUse(std::shared_ptr<ItemInstance> item) {
|
|
bool allowed = true;
|
|
if (item != nullptr &&
|
|
app.GetGameHostOption(eGameHostOption_TrustPlayers) == 0) {
|
|
if (getPlayerGamePrivilege(Player::ePlayerGamePrivilege_CannotBuild) !=
|
|
0) {
|
|
allowed = false;
|
|
}
|
|
|
|
// 4J Stu - TU8 Players should always be able to eat food items, even if
|
|
// the build option is turned of
|
|
switch (item->id) {
|
|
// food
|
|
case Item::mushroomStew_Id:
|
|
case Item::apple_Id:
|
|
case Item::bread_Id:
|
|
case Item::porkChop_raw_Id:
|
|
case Item::porkChop_cooked_Id:
|
|
case Item::apple_gold_Id:
|
|
case Item::fish_raw_Id:
|
|
case Item::fish_cooked_Id:
|
|
case Item::cookie_Id:
|
|
case Item::beef_cooked_Id:
|
|
case Item::beef_raw_Id:
|
|
case Item::chicken_cooked_Id:
|
|
case Item::chicken_raw_Id:
|
|
case Item::melon_Id:
|
|
case Item::rotten_flesh_Id:
|
|
// bow
|
|
case Item::bow_Id:
|
|
case Item::sword_diamond_Id:
|
|
case Item::sword_gold_Id:
|
|
case Item::sword_iron_Id:
|
|
case Item::sword_stone_Id:
|
|
case Item::sword_wood_Id:
|
|
allowed = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
return allowed;
|
|
}
|
|
|
|
bool Player::isAllowedToInteract(std::shared_ptr<Entity> target) {
|
|
bool allowed = true;
|
|
if (app.GetGameHostOption(eGameHostOption_TrustPlayers) == 0) {
|
|
if (target->instanceof(eTYPE_MINECART)) {
|
|
if (getPlayerGamePrivilege(
|
|
Player::ePlayerGamePrivilege_CanUseContainers) == 0) {
|
|
std::shared_ptr<Minecart> minecart =
|
|
std::dynamic_pointer_cast<Minecart>(target);
|
|
if (minecart->getType() == Minecart::TYPE_CHEST)
|
|
allowed = false;
|
|
}
|
|
|
|
} else {
|
|
if (getPlayerGamePrivilege(
|
|
Player::ePlayerGamePrivilege_CannotBuild) != 0) {
|
|
allowed = false;
|
|
}
|
|
|
|
if (getPlayerGamePrivilege(
|
|
Player::ePlayerGamePrivilege_CannotMine) != 0) {
|
|
allowed = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return allowed;
|
|
}
|
|
|
|
bool Player::isAllowedToMine() {
|
|
bool allowed = true;
|
|
if (app.GetGameHostOption(eGameHostOption_TrustPlayers) == 0) {
|
|
if (getPlayerGamePrivilege(Player::ePlayerGamePrivilege_CannotMine) !=
|
|
0) {
|
|
allowed = false;
|
|
}
|
|
}
|
|
return allowed;
|
|
}
|
|
|
|
bool Player::isAllowedToAttackPlayers() {
|
|
bool allowed = true;
|
|
if (hasInvisiblePrivilege() ||
|
|
((app.GetGameHostOption(eGameHostOption_TrustPlayers) == 0) &&
|
|
getPlayerGamePrivilege(
|
|
Player::ePlayerGamePrivilege_CannotAttackPlayers))) {
|
|
allowed = false;
|
|
}
|
|
return allowed;
|
|
}
|
|
|
|
bool Player::isAllowedToAttackAnimals() {
|
|
bool allowed = true;
|
|
if ((app.GetGameHostOption(eGameHostOption_TrustPlayers) == 0) &&
|
|
getPlayerGamePrivilege(
|
|
Player::ePlayerGamePrivilege_CannotAttackAnimals)) {
|
|
allowed = false;
|
|
}
|
|
return allowed;
|
|
}
|
|
|
|
bool Player::isAllowedToHurtEntity(std::shared_ptr<Entity> target) {
|
|
bool allowed = true;
|
|
|
|
if (!isAllowedToMine()) {
|
|
switch (target->GetType()) {
|
|
case eTYPE_HANGING_ENTITY:
|
|
case eTYPE_PAINTING:
|
|
case eTYPE_ITEM_FRAME:
|
|
|
|
// 4J-JEV: Fix for #88212,
|
|
// Untrusted players shouldn't be able to damage minecarts or
|
|
// boats.
|
|
case eTYPE_BOAT:
|
|
case eTYPE_MINECART:
|
|
|
|
allowed = false;
|
|
break;
|
|
default:
|
|
break;
|
|
};
|
|
}
|
|
return allowed;
|
|
}
|
|
|
|
bool Player::isAllowedToFly() {
|
|
bool allowed = false;
|
|
if (app.GetGameHostOption(eGameHostOption_HostCanFly) != 0 &&
|
|
getPlayerGamePrivilege(Player::ePlayerGamePrivilege_CanFly) != 0) {
|
|
allowed = true;
|
|
}
|
|
return allowed;
|
|
}
|
|
|
|
bool Player::isAllowedToIgnoreExhaustion() {
|
|
bool allowed = false;
|
|
if ((app.GetGameHostOption(eGameHostOption_HostCanChangeHunger) != 0 &&
|
|
getPlayerGamePrivilege(Player::ePlayerGamePrivilege_ClassicHunger) !=
|
|
0) ||
|
|
(isAllowedToFly() && abilities.flying)) {
|
|
allowed = true;
|
|
}
|
|
return allowed;
|
|
}
|
|
|
|
bool Player::isAllowedToTeleport() {
|
|
bool allowed = false;
|
|
if (isModerator() &&
|
|
getPlayerGamePrivilege(Player::ePlayerGamePrivilege_CanTeleport) != 0) {
|
|
allowed = true;
|
|
}
|
|
return allowed;
|
|
}
|
|
|
|
bool Player::hasInvisiblePrivilege() {
|
|
bool enabled = false;
|
|
if (app.GetGameHostOption(eGameHostOption_HostCanBeInvisible) != 0 &&
|
|
getPlayerGamePrivilege(Player::ePlayerGamePrivilege_Invisible) != 0) {
|
|
enabled = true;
|
|
}
|
|
return enabled;
|
|
}
|
|
|
|
bool Player::hasInvulnerablePrivilege() {
|
|
bool enabled = false;
|
|
if (app.GetGameHostOption(eGameHostOption_HostCanBeInvisible) != 0 &&
|
|
getPlayerGamePrivilege(Player::ePlayerGamePrivilege_Invulnerable) !=
|
|
0) {
|
|
enabled = true;
|
|
}
|
|
return enabled;
|
|
}
|
|
|
|
bool Player::isModerator() {
|
|
return getPlayerGamePrivilege(Player::ePlayerGamePrivilege_Op) != 0;
|
|
}
|
|
|
|
void Player::enableAllPlayerPrivileges(unsigned int& uigamePrivileges,
|
|
bool enable) {
|
|
Player::setPlayerGamePrivilege(uigamePrivileges,
|
|
Player::ePlayerGamePrivilege_CannotMine,
|
|
enable ? 0 : 1);
|
|
Player::setPlayerGamePrivilege(uigamePrivileges,
|
|
Player::ePlayerGamePrivilege_CannotBuild,
|
|
enable ? 0 : 1);
|
|
Player::setPlayerGamePrivilege(
|
|
uigamePrivileges, Player::ePlayerGamePrivilege_CannotAttackPlayers,
|
|
enable ? 0 : 1);
|
|
Player::setPlayerGamePrivilege(
|
|
uigamePrivileges, Player::ePlayerGamePrivilege_CannotAttackAnimals,
|
|
enable ? 0 : 1);
|
|
Player::setPlayerGamePrivilege(
|
|
uigamePrivileges, Player::ePlayerGamePrivilege_CanUseDoorsAndSwitches,
|
|
enable ? 1 : 0);
|
|
Player::setPlayerGamePrivilege(
|
|
uigamePrivileges, Player::ePlayerGamePrivilege_CanUseContainers,
|
|
enable ? 1 : 0);
|
|
}
|
|
|
|
void Player::enableAllPlayerPrivileges(bool enable) {
|
|
Player::enableAllPlayerPrivileges(m_uiGamePrivileges, enable);
|
|
}
|
|
|
|
bool Player::canCreateParticles() { return !hasInvisiblePrivilege(); }
|
|
|
|
std::vector<ModelPart*>* Player::GetAdditionalModelParts() {
|
|
if (m_ppAdditionalModelParts == nullptr && !m_bCheckedForModelParts) {
|
|
bool hasCustomTexture = !customTextureUrl.empty();
|
|
bool customTextureIsDefaultSkin =
|
|
customTextureUrl.substr(0, 3).compare(L"def") == 0;
|
|
|
|
// see if we can find the parts
|
|
m_ppAdditionalModelParts = app.GetAdditionalModelParts(m_dwSkinId);
|
|
|
|
// If it's a default texture (which has no parts), we have the parts, or
|
|
// we already have the texture (in which case we should have parts if
|
|
// there are any) then we are done
|
|
if (!hasCustomTexture || customTextureIsDefaultSkin ||
|
|
m_ppAdditionalModelParts != nullptr ||
|
|
app.IsFileInMemoryTextures(customTextureUrl)) {
|
|
m_bCheckedForModelParts = true;
|
|
}
|
|
if (m_ppAdditionalModelParts == nullptr &&
|
|
!m_bCheckedDLCForModelParts) {
|
|
m_bCheckedDLCForModelParts = true;
|
|
|
|
// we don't have the data from the dlc skin yet
|
|
app.DebugPrintf(
|
|
"m_bCheckedForModelParts Couldn't get model parts for skin "
|
|
"%X\n",
|
|
m_dwSkinId);
|
|
|
|
// do we have it from the DLC pack?
|
|
DLCSkinFile* pDLCSkinFile =
|
|
app.m_dlcManager.getSkinFile(this->customTextureUrl);
|
|
|
|
if (pDLCSkinFile != nullptr) {
|
|
const int additionalBoxCount =
|
|
pDLCSkinFile->getAdditionalBoxesCount();
|
|
if (additionalBoxCount != 0) {
|
|
app.DebugPrintf(
|
|
"m_bCheckedForModelParts Got model parts from DLCskin "
|
|
"for skin %X\n",
|
|
m_dwSkinId);
|
|
m_ppAdditionalModelParts = app.SetAdditionalSkinBoxes(
|
|
m_dwSkinId, pDLCSkinFile->getAdditionalBoxes());
|
|
}
|
|
|
|
app.SetAnimOverrideBitmask(
|
|
pDLCSkinFile->getSkinID(),
|
|
pDLCSkinFile->getAnimOverrideBitmask());
|
|
|
|
m_bCheckedForModelParts = true;
|
|
}
|
|
}
|
|
|
|
if (m_bCheckedForModelParts)
|
|
setAnimOverrideBitmask(getSkinAnimOverrideBitmask(m_dwSkinId));
|
|
}
|
|
return m_ppAdditionalModelParts;
|
|
}
|
|
|
|
void Player::SetAdditionalModelParts(
|
|
std::vector<ModelPart*>* ppAdditionalModelParts) {
|
|
m_ppAdditionalModelParts = ppAdditionalModelParts;
|
|
}
|