mirror of
https://github.com/neoStudiosLCE/neoLegacy.git
synced 2026-05-22 02:28:07 +00:00
690 lines
22 KiB
C++
690 lines
22 KiB
C++
#include "stdafx.h"
|
|
#include "com.mojang.nbt.h"
|
|
#include "net.minecraft.world.entity.ai.attributes.h"
|
|
#include "net.minecraft.world.entity.ai.navigation.h"
|
|
#include "net.minecraft.world.entity.ai.goal.h"
|
|
#include "net.minecraft.world.level.tile.h"
|
|
#include "net.minecraft.world.level.h"
|
|
#include "net.minecraft.world.item.h"
|
|
#include "net.minecraft.world.entity.player.h"
|
|
#include "net.minecraft.world.damagesource.h"
|
|
#include "SynchedEntityData.h"
|
|
#include "SharedMonsterAttributes.h"
|
|
#include "ArmorStand.h"
|
|
#include "BlockPos.h"
|
|
#include "../Minecraft.Client/ServerLevel.h"
|
|
#include "ParticleTypes.h"
|
|
#include "Random.h"
|
|
#include "AABB.h"
|
|
|
|
const Rotations ArmorStand::DEFAULT_HEAD_POSE (0.0f, 0.0f, 0.0f);
|
|
const Rotations ArmorStand::DEFAULT_BODY_POSE (0.0f, 0.0f, 0.0f);
|
|
const Rotations ArmorStand::DEFAULT_LEFT_ARM_POSE (-10.0f, 0.0f, -10.0f);
|
|
const Rotations ArmorStand::DEFAULT_RIGHT_ARM_POSE (-15.0f, 0.0f, 10.0f);
|
|
const Rotations ArmorStand::DEFAULT_LEFT_LEG_POSE (-1.0f, 0.0f, -1.0f);
|
|
const Rotations ArmorStand::DEFAULT_RIGHT_LEG_POSE (1.0f, 0.0f, 1.0f);
|
|
|
|
ArmorStand::ArmorStand(Level* level)
|
|
: LivingEntity(level)
|
|
{
|
|
ArmorStand::init();
|
|
}
|
|
|
|
void ArmorStand::init()
|
|
{
|
|
registerAttributes();
|
|
defineSynchedData();
|
|
|
|
lastHit = -100LL;
|
|
standDamage = 0.0f;
|
|
hurtDir = 1;
|
|
disabledSlots = 0;
|
|
invisible = false;
|
|
isMarkerFlag = false;
|
|
heightOffset = 0.0f;
|
|
for (int i = 0; i < equipmentCount; i++)
|
|
equipment[i] = nullptr;
|
|
|
|
headPose = DEFAULT_HEAD_POSE;
|
|
bodyPose = DEFAULT_BODY_POSE;
|
|
leftArmPose = DEFAULT_LEFT_ARM_POSE;
|
|
rightArmPose = DEFAULT_RIGHT_ARM_POSE;
|
|
leftLegPose = DEFAULT_LEFT_LEG_POSE;
|
|
rightLegPose = DEFAULT_RIGHT_LEG_POSE;
|
|
|
|
byte flags = entityData->getByte(DATA_CLIENT_FLAGS);
|
|
noPhysics = (flags & FLAG_NO_GRAVITY) != 0;
|
|
|
|
setSize(0.5f, 1.975f);
|
|
}
|
|
|
|
void ArmorStand::registerAttributes()
|
|
{
|
|
LivingEntity::registerAttributes();
|
|
getAttribute(SharedMonsterAttributes::MAX_HEALTH)->setBaseValue(20.0);
|
|
getAttribute(SharedMonsterAttributes::MOVEMENT_SPEED)->setBaseValue(0.0);
|
|
getAttribute(SharedMonsterAttributes::KNOCKBACK_RESISTANCE)->setBaseValue(1.0);
|
|
}
|
|
|
|
void ArmorStand::defineSynchedData()
|
|
{
|
|
LivingEntity::defineSynchedData();
|
|
|
|
entityData->define(3, static_cast<byte>(0)); // DATA_CUSTOM_NAME_VISIBLE
|
|
entityData->define(2, wstring(L"")); // DATA_CUSTOM_NAME
|
|
entityData->define(DATA_CLIENT_FLAGS, static_cast<byte>(0));
|
|
entityData->define(DATA_HEAD_POSE, DEFAULT_HEAD_POSE);
|
|
entityData->define(DATA_BODY_POSE, DEFAULT_BODY_POSE);
|
|
entityData->define(DATA_LEFT_ARM_POSE, DEFAULT_LEFT_ARM_POSE);
|
|
entityData->define(DATA_RIGHT_ARM_POSE, DEFAULT_RIGHT_ARM_POSE);
|
|
entityData->define(DATA_LEFT_LEG_POSE, DEFAULT_LEFT_LEG_POSE);
|
|
entityData->define(DATA_RIGHT_LEG_POSE, DEFAULT_RIGHT_LEG_POSE);
|
|
}
|
|
|
|
ArmorStand::~ArmorStand() {}
|
|
|
|
|
|
bool ArmorStand::isBaby() const { return (entityData->getByte(DATA_CLIENT_FLAGS) & FLAG_SMALL) != 0; }
|
|
bool ArmorStand::isSmall() const { return isBaby(); }
|
|
bool ArmorStand::isShowArms() const { return (entityData->getByte(DATA_CLIENT_FLAGS) & FLAG_SHOW_ARMS) != 0; }
|
|
bool ArmorStand::isNoBasePlate() const { return (entityData->getByte(DATA_CLIENT_FLAGS) & FLAG_NO_BASEPLATE)!= 0; }
|
|
bool ArmorStand::showBasePlate() const { return !isNoBasePlate(); }
|
|
bool ArmorStand::isMarker() const { return (entityData->getByte(DATA_CLIENT_FLAGS) & FLAG_MARKER) != 0; }
|
|
bool ArmorStand::isEffectiveAi() const
|
|
{
|
|
if (!LivingEntity::isEffectiveAi()) return false;
|
|
return (entityData->getByte(DATA_CLIENT_FLAGS) & FLAG_NO_GRAVITY) == 0;
|
|
}
|
|
|
|
void ArmorStand::setSmall(bool v)
|
|
{
|
|
byte b = entityData->getByte(DATA_CLIENT_FLAGS);
|
|
entityData->set(DATA_CLIENT_FLAGS, v ? (byte)(b | FLAG_SMALL) : (byte)(b & ~FLAG_SMALL));
|
|
}
|
|
void ArmorStand::setShowArms(bool v)
|
|
{
|
|
byte b = entityData->getByte(DATA_CLIENT_FLAGS);
|
|
entityData->set(DATA_CLIENT_FLAGS, v ? (byte)(b | FLAG_SHOW_ARMS) : (byte)(b & ~FLAG_SHOW_ARMS));
|
|
}
|
|
void ArmorStand::setNoBasePlate(bool v)
|
|
{
|
|
byte b = entityData->getByte(DATA_CLIENT_FLAGS);
|
|
entityData->set(DATA_CLIENT_FLAGS, v ? (byte)(b | FLAG_NO_BASEPLATE) : (byte)(b & ~FLAG_NO_BASEPLATE));
|
|
}
|
|
void ArmorStand::setMarker(bool v)
|
|
{
|
|
byte b = entityData->getByte(DATA_CLIENT_FLAGS);
|
|
entityData->set(DATA_CLIENT_FLAGS, v ? (byte)(b | FLAG_MARKER) : (byte)(b & ~FLAG_MARKER));
|
|
}
|
|
void ArmorStand::setNoGravity(bool v)
|
|
{
|
|
byte b = entityData->getByte(DATA_CLIENT_FLAGS);
|
|
entityData->set(DATA_CLIENT_FLAGS, v ? (byte)(b | FLAG_NO_GRAVITY) : (byte)(b & ~FLAG_NO_GRAVITY));
|
|
}
|
|
void ArmorStand::setInvisible(bool v)
|
|
{
|
|
invisible = v;
|
|
Entity::setInvisible(v);
|
|
}
|
|
|
|
|
|
Rotations ArmorStand::getHeadPose() const { return entityData->getRotations(DATA_HEAD_POSE); }
|
|
Rotations ArmorStand::getBodyPose() const { return entityData->getRotations(DATA_BODY_POSE); }
|
|
Rotations ArmorStand::getLeftArmPose() const { return entityData->getRotations(DATA_LEFT_ARM_POSE); }
|
|
Rotations ArmorStand::getRightArmPose() const { return entityData->getRotations(DATA_RIGHT_ARM_POSE); }
|
|
Rotations ArmorStand::getLeftLegPose() const { return entityData->getRotations(DATA_LEFT_LEG_POSE); }
|
|
Rotations ArmorStand::getRightLegPose() const { return entityData->getRotations(DATA_RIGHT_LEG_POSE); }
|
|
|
|
void ArmorStand::setHeadPose (const Rotations& r) { headPose = r; entityData->set(DATA_HEAD_POSE, r); }
|
|
void ArmorStand::setBodyPose (const Rotations& r) { bodyPose = r; entityData->set(DATA_BODY_POSE, r); }
|
|
void ArmorStand::setLeftArmPose (const Rotations& r) { leftArmPose = r; entityData->set(DATA_LEFT_ARM_POSE, r); }
|
|
void ArmorStand::setRightArmPose(const Rotations& r) { rightArmPose = r; entityData->set(DATA_RIGHT_ARM_POSE, r); }
|
|
void ArmorStand::setLeftLegPose (const Rotations& r) { leftLegPose = r; entityData->set(DATA_LEFT_LEG_POSE, r); }
|
|
void ArmorStand::setRightLegPose(const Rotations& r) { rightLegPose = r; entityData->set(DATA_RIGHT_LEG_POSE, r); }
|
|
|
|
|
|
float ArmorStand::getEyeHeight()
|
|
{
|
|
return isSmall() ? (bbHeight * 0.5f) : (bbHeight * 0.9f);
|
|
}
|
|
|
|
bool ArmorStand::isPickable()
|
|
{
|
|
if (!LivingEntity::isPickable()) return false;
|
|
return !isMarker();
|
|
}
|
|
|
|
bool ArmorStand::isPushable() { return false; }
|
|
|
|
|
|
bool ArmorStand::ignoreExplosion()
|
|
{
|
|
return isInvisible();
|
|
}
|
|
|
|
void ArmorStand::kill()
|
|
{
|
|
remove();
|
|
}
|
|
|
|
bool ArmorStand::shouldRenderAtSqrDistance(double distSq)
|
|
{
|
|
double size = getBoundingBox()->getSize();
|
|
if (size == 0.0) size = 4.0;
|
|
double limit = size * 64.0;
|
|
return distSq < limit * limit;
|
|
}
|
|
|
|
void ArmorStand::updateBoundingBox(bool markerMode)
|
|
{
|
|
float ox = x, oy = y, oz = z;
|
|
if (markerMode) setSize(0.0f, 0.0f);
|
|
else setSize(0.5f, 1.975f);
|
|
moveTo(ox, oy, oz, yRot, xRot);
|
|
}
|
|
|
|
void ArmorStand::updateInvisibilityStatus()
|
|
{
|
|
setInvisible(invisible);
|
|
}
|
|
|
|
|
|
void ArmorStand::tick()
|
|
{
|
|
float lockedRot = this->yRot;
|
|
LivingEntity::tick();
|
|
if (onGround)
|
|
{
|
|
BlockPos pos((int)floorf(x), (int)floorf(y) - 1, (int)floorf(z));
|
|
int blockId = level->getTile(pos.getX(), pos.getY(), pos.getZ());
|
|
if (blockId == Tile::topSnow->id)
|
|
{
|
|
int meta = level->getData(pos.getX(), pos.getY(), pos.getZ());
|
|
float snowHeight = ((meta & 0x7) + 1) * 0.125f;
|
|
moveTo(x, floorf(y) + snowHeight, z, yRot, xRot);
|
|
}
|
|
}
|
|
this->yRot = lockedRot;
|
|
this->yRotO = lockedRot;
|
|
this->yBodyRot = lockedRot;
|
|
this->yBodyRotO = lockedRot;
|
|
this->yHeadRot = lockedRot;
|
|
this->yHeadRotO = lockedRot;
|
|
|
|
if ((long long)tickCount - lastHit > 20)
|
|
standDamage = 0.0f;
|
|
|
|
auto syncPose = [&](int slot, Rotations& local)
|
|
{
|
|
Rotations net = entityData->getRotations(slot);
|
|
if (local != net) { local = net; entityData->set(slot, net); }
|
|
};
|
|
syncPose(DATA_HEAD_POSE, headPose);
|
|
syncPose(DATA_BODY_POSE, bodyPose);
|
|
syncPose(DATA_LEFT_ARM_POSE, leftArmPose);
|
|
syncPose(DATA_RIGHT_ARM_POSE, rightArmPose);
|
|
syncPose(DATA_LEFT_LEG_POSE, leftLegPose);
|
|
syncPose(DATA_RIGHT_LEG_POSE, rightLegPose);
|
|
|
|
bool markerNow = (entityData->getByte(DATA_CLIENT_FLAGS) & FLAG_MARKER) != 0;
|
|
if (isMarkerFlag && !markerNow)
|
|
{
|
|
float ox = x, oy = y, oz = z;
|
|
setSize(0.5f, 1.975f);
|
|
moveTo(ox, oy, oz, yRot, xRot);
|
|
isMarkerFlag = false;
|
|
}
|
|
else if (!isMarkerFlag && markerNow)
|
|
{
|
|
float ox = x, oy = y, oz = z;
|
|
setSize(0.0f, 0.0f);
|
|
moveTo(ox, oy, oz, yRot, xRot);
|
|
isMarkerFlag = true;
|
|
}
|
|
}
|
|
|
|
|
|
void ArmorStand::brokenByAnything()
|
|
{
|
|
for (int i = 0; i < equipmentCount; i++)
|
|
{
|
|
if (equipment[i] && equipment[i]->count > 0)
|
|
{
|
|
spawnAtLocation(equipment[i], 0.0f);
|
|
equipment[i] = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ArmorStand::brokenByPlayer(shared_ptr<Player> player, DamageSource* source)
|
|
{
|
|
spawnAtLocation(Item::armor_stand_Id, 1);
|
|
brokenByAnything();
|
|
}
|
|
|
|
void ArmorStand::causeDamage(float damage)
|
|
{
|
|
float h = getHealth();
|
|
h -= damage;
|
|
if (h <= 0.5f)
|
|
{
|
|
spawnAtLocation(Item::armor_stand_Id, 1);
|
|
brokenByAnything();
|
|
remove();
|
|
}
|
|
else
|
|
{
|
|
setHealth(h);
|
|
}
|
|
}
|
|
|
|
bool ArmorStand::hurt(DamageSource* source, float damage)
|
|
{
|
|
if (isInvulnerable() || level->isClientSide || removed || isMarker()) return false;
|
|
|
|
if (source->getMsgId() == eEntityDamageType_Suffocate) return false;
|
|
|
|
if (dynamic_cast<EntityDamageSource*>(source) != nullptr)
|
|
{
|
|
shared_ptr<Entity> attacker = source->getDirectEntity();
|
|
if (attacker && attacker->instanceof(eTYPE_PLAYER) &&
|
|
!dynamic_pointer_cast<Player>(attacker)->isAllowedToHurtEntity(shared_from_this()))
|
|
return false;
|
|
}
|
|
|
|
if (source->isExplosion())
|
|
{
|
|
spawnAtLocation(Item::armor_stand_Id, 1);
|
|
brokenByAnything();
|
|
remove();
|
|
return true;
|
|
}
|
|
|
|
if (dynamic_cast<EntityDamageSource*>(source) != nullptr)
|
|
{
|
|
shared_ptr<Entity> attacker = source->getEntity();
|
|
if (attacker && attacker->instanceof(eTYPE_PLAYER) &&
|
|
dynamic_pointer_cast<Player>(attacker)->abilities.instabuild)
|
|
{
|
|
level->broadcastEntityEvent(shared_from_this(), (byte)31);
|
|
remove();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (source->isFire())
|
|
{
|
|
if (isInWater()) return false;
|
|
float h = getHealth() - 0.15f;
|
|
setHealth(h);
|
|
if (h <= 0.5f)
|
|
{
|
|
brokenByAnything();
|
|
remove();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
hurtDir = -hurtDir;
|
|
standDamage += damage * 10.0f;
|
|
lastHit = (long long)tickCount;
|
|
level->broadcastEntityEvent(shared_from_this(), (byte)32);
|
|
|
|
if (standDamage >= 40.0f)
|
|
{
|
|
spawnAtLocation(Item::armor_stand_Id, 1);
|
|
brokenByAnything();
|
|
remove();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void ArmorStand::pushEntities()
|
|
{
|
|
AABB* myBox = getBoundingBox();
|
|
AABB* grown = myBox->grow(0.2, 0.0, 0.2);
|
|
auto* entities = level->getEntities(shared_from_this(), grown);
|
|
if (!entities) return;
|
|
|
|
for (auto& e : *entities)
|
|
{
|
|
if (!e) continue;
|
|
|
|
|
|
if (!e->instanceof(eTYPE_MINECART)) continue;
|
|
|
|
|
|
if (distanceToSqr(e) > 0.2) continue;
|
|
|
|
|
|
e->push(shared_from_this());
|
|
}
|
|
}
|
|
|
|
|
|
shared_ptr<ItemInstance> ArmorStand::getCarriedItem() { return equipment[SLOT_WEAPON]; }
|
|
shared_ptr<ItemInstance> ArmorStand::getCarried(int slot) { return (slot >= 0 && slot < equipmentCount) ? equipment[slot] : nullptr; }
|
|
shared_ptr<ItemInstance> ArmorStand::getArmor(int pos)
|
|
{
|
|
int idx = pos + 1;
|
|
return (idx >= 1 && idx < equipmentCount) ? equipment[idx] : nullptr;
|
|
}
|
|
void ArmorStand::setEquippedSlot(int slot, shared_ptr<ItemInstance> item)
|
|
{
|
|
if (slot >= 0 && slot < equipmentCount) equipment[slot] = item;
|
|
}
|
|
ItemInstanceArray ArmorStand::getEquipmentSlots() { return ItemInstanceArray(equipment, equipmentCount); }
|
|
|
|
int ArmorStand::getEquipmentSlotForItem(shared_ptr<ItemInstance> item) const
|
|
{
|
|
if (!item) return SLOT_WEAPON;
|
|
return Mob::getEquipmentSlotForItem(item);
|
|
}
|
|
|
|
bool ArmorStand::setSlot(int inventorySlot, shared_ptr<ItemInstance> item)
|
|
{
|
|
int localSlot;
|
|
if (inventorySlot == 0x63) localSlot = SLOT_WEAPON;
|
|
else
|
|
{
|
|
localSlot = inventorySlot - 0x63;
|
|
if (localSlot < 0 || localSlot >= equipmentCount) return false;
|
|
}
|
|
if (item)
|
|
{
|
|
int naturalSlot = getEquipmentSlotForItem(item);
|
|
if (naturalSlot != localSlot)
|
|
{
|
|
if (localSlot == SLOT_HELM)
|
|
{
|
|
auto bi = dynamic_cast<TileItem*>(item->getItem());
|
|
if (!bi) return false;
|
|
}
|
|
else return false;
|
|
}
|
|
}
|
|
setEquippedSlot(localSlot, item);
|
|
return true;
|
|
}
|
|
|
|
void ArmorStand::swapItem(shared_ptr<Player> player, int slot)
|
|
{
|
|
shared_ptr<ItemInstance> standItem = equipment[slot];
|
|
shared_ptr<ItemInstance> playerItem = player->getCarriedItem();
|
|
int disabledFlags = disabledSlots;
|
|
|
|
if (!standItem)
|
|
{
|
|
if ((disabledFlags & (1 << (slot + 0x10))) != 0) return;
|
|
if (!playerItem) goto do_take;
|
|
}
|
|
if ((disabledFlags & (1 << (slot + 8))) != 0) goto do_take;
|
|
if (playerItem)
|
|
{
|
|
if (player->abilities.instabuild)
|
|
{
|
|
auto toPlace = ItemInstance::clone(playerItem);
|
|
toPlace->count = 1;
|
|
setEquippedSlot(slot, toPlace);
|
|
}
|
|
else
|
|
{
|
|
auto toPlace = ItemInstance::clone(playerItem);
|
|
toPlace->count = 1;
|
|
setEquippedSlot(slot, toPlace);
|
|
if (standItem)
|
|
{
|
|
if (playerItem->count > 1) { playerItem->remove(1); if (!player->inventory->add(standItem)) spawnAtLocation(standItem, 0.0f); }
|
|
else player->inventory->setItem(player->inventory->selected, standItem);
|
|
}
|
|
else playerItem->remove(1);
|
|
}
|
|
return;
|
|
}
|
|
do_take:
|
|
if (standItem) { player->inventory->setItem(player->inventory->selected, standItem); setEquippedSlot(slot, nullptr); }
|
|
}
|
|
|
|
bool ArmorStand::interact(shared_ptr<Player> player)
|
|
{
|
|
if (level->isClientSide) return true;
|
|
if (isMarker()) return false;
|
|
if (player->isSpectator()) return true;
|
|
|
|
|
|
float dX = this->x - player->x;
|
|
float dZ = this->z - player->z;
|
|
float distHoriz = sqrtf(dX * dX + dZ * dZ);
|
|
float pitchRad = player->xRot * (3.14159265f / 180.0f);
|
|
float lookYDiff = -tanf(pitchRad) * distHoriz;
|
|
float hitY = (player->y + 1.62f + lookYDiff) - this->y;
|
|
|
|
if (isSmall()) hitY *= 2.0f;
|
|
|
|
shared_ptr<ItemInstance> carried = player->getCarriedItem();
|
|
|
|
int targetSlot;
|
|
if (carried)
|
|
{
|
|
targetSlot = getEquipmentSlotForItem(carried);
|
|
}
|
|
else
|
|
{
|
|
if (hitY >= 0.1f && hitY < 0.55f) targetSlot = SLOT_BOOTS;
|
|
else if (hitY >= 0.55f && hitY < 0.9f) targetSlot = SLOT_LEGGINGS;
|
|
else if (hitY >= 0.9f && hitY < 1.6f) targetSlot = SLOT_CHEST;
|
|
else if (hitY >= 1.6f) targetSlot = SLOT_HELM;
|
|
else targetSlot = SLOT_WEAPON;
|
|
}
|
|
|
|
if (targetSlot == SLOT_WEAPON && !isShowArms()) return false;
|
|
|
|
|
|
if ((disabledSlots & (1 << targetSlot)) != 0) return false;
|
|
|
|
shared_ptr<ItemInstance> standItem = equipment[targetSlot];
|
|
|
|
if (carried)
|
|
{
|
|
auto toPlace = ItemInstance::clone(carried);
|
|
toPlace->count = 1;
|
|
setEquippedSlot(targetSlot, toPlace);
|
|
|
|
if (standItem)
|
|
{
|
|
if (carried->count > 1)
|
|
{
|
|
carried->remove(1);
|
|
if (!player->inventory->add(standItem))
|
|
spawnAtLocation(standItem, 0.0f);
|
|
}
|
|
else
|
|
{
|
|
player->inventory->setItem(player->inventory->selected, standItem);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
carried->remove(1);
|
|
}
|
|
}
|
|
else if (standItem)
|
|
{
|
|
player->inventory->setItem(player->inventory->selected, standItem);
|
|
setEquippedSlot(targetSlot, nullptr);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
bool ArmorStand::interactAt(shared_ptr<Player> player, const Vec3& hitVec)
|
|
{
|
|
if (isMarker()) return false;
|
|
if (level->isClientSide) return true;
|
|
if (player->isSpectator()) return true;
|
|
|
|
shared_ptr<ItemInstance> carried = player->getCarriedItem();
|
|
|
|
int targetSlot;
|
|
if (carried)
|
|
{
|
|
|
|
targetSlot = getEquipmentSlotForItem(carried);
|
|
}
|
|
else
|
|
{
|
|
|
|
float hitY = (float)hitVec.y;
|
|
bool isSmallStand = isSmall();
|
|
if (isSmallStand) hitY *= 2.0f;
|
|
|
|
float bootsMax = isSmallStand ? 0.9f : 0.55f;
|
|
float legsMin = isSmallStand ? 1.2f : 0.9f;
|
|
float legsMax = isSmallStand ? 1.9f : 1.6f;
|
|
|
|
targetSlot = SLOT_WEAPON;
|
|
if (hitY >= 0.1f && hitY < bootsMax)
|
|
targetSlot = SLOT_BOOTS;
|
|
else if (hitY >= legsMin && hitY < legsMax)
|
|
targetSlot = SLOT_LEGGINGS;
|
|
if (hitY >= 1.6f && equipment[SLOT_HELM])
|
|
targetSlot = SLOT_HELM;
|
|
}
|
|
|
|
|
|
if (targetSlot == SLOT_WEAPON && !isShowArms()) return false;
|
|
|
|
|
|
if ((disabledSlots & (1 << targetSlot)) != 0) return false;
|
|
|
|
swapItem(player, targetSlot);
|
|
return true;
|
|
}
|
|
|
|
int ArmorStand::test_interactAt(shared_ptr<Player> player, const Vec3& hitVec)
|
|
{
|
|
if (isMarker()) return 0;
|
|
if (player->isSpectator()) return 0;
|
|
shared_ptr<ItemInstance> carried = player->getCarriedItem();
|
|
int playerSlot = carried ? getEquipmentSlotForItem(carried) : SLOT_WEAPON;
|
|
bool hasStandItem = equipment[playerSlot] != nullptr;
|
|
if ((disabledSlots & (1 << playerSlot)) != 0 && (disabledSlots & 1) != 0) return 0;
|
|
if (!carried) return hasStandItem ? 2 : 0;
|
|
return hasStandItem ? 3 : 1;
|
|
}
|
|
|
|
|
|
void ArmorStand::readPose(CompoundTag* poseTag)
|
|
{
|
|
auto loadRot = [&](const wchar_t* key, const Rotations& def, int dataSlot)
|
|
{
|
|
Rotations r = def;
|
|
|
|
ListTag<FloatTag>* list = static_cast<ListTag<FloatTag>*>(
|
|
static_cast<void*>(poseTag->getList(key)));
|
|
if (list && list->size() >= 3)
|
|
r = Rotations(list);
|
|
entityData->set(dataSlot, r);
|
|
};
|
|
loadRot(L"Head", DEFAULT_HEAD_POSE, DATA_HEAD_POSE);
|
|
loadRot(L"Body", DEFAULT_BODY_POSE, DATA_BODY_POSE);
|
|
loadRot(L"LeftArm", DEFAULT_LEFT_ARM_POSE, DATA_LEFT_ARM_POSE);
|
|
loadRot(L"RightArm", DEFAULT_RIGHT_ARM_POSE, DATA_RIGHT_ARM_POSE);
|
|
loadRot(L"LeftLeg", DEFAULT_LEFT_LEG_POSE, DATA_LEFT_LEG_POSE);
|
|
loadRot(L"RightLeg", DEFAULT_RIGHT_LEG_POSE, DATA_RIGHT_LEG_POSE);
|
|
|
|
headPose = entityData->getRotations(DATA_HEAD_POSE);
|
|
bodyPose = entityData->getRotations(DATA_BODY_POSE);
|
|
leftArmPose = entityData->getRotations(DATA_LEFT_ARM_POSE);
|
|
rightArmPose = entityData->getRotations(DATA_RIGHT_ARM_POSE);
|
|
leftLegPose = entityData->getRotations(DATA_LEFT_LEG_POSE);
|
|
rightLegPose = entityData->getRotations(DATA_RIGHT_LEG_POSE);
|
|
}
|
|
|
|
CompoundTag* ArmorStand::writePose()
|
|
{
|
|
CompoundTag* pose = new CompoundTag();
|
|
auto save = [&](const wchar_t* key, const Rotations& cur, const Rotations& def)
|
|
{ if (cur != def) pose->put(key, cur.save()); };
|
|
save(L"Head", headPose, DEFAULT_HEAD_POSE);
|
|
save(L"Body", bodyPose, DEFAULT_BODY_POSE);
|
|
save(L"LeftArm", leftArmPose, DEFAULT_LEFT_ARM_POSE);
|
|
save(L"RightArm", rightArmPose, DEFAULT_RIGHT_ARM_POSE);
|
|
save(L"LeftLeg", leftLegPose, DEFAULT_LEFT_LEG_POSE);
|
|
save(L"RightLeg", rightLegPose, DEFAULT_RIGHT_LEG_POSE);
|
|
return pose;
|
|
}
|
|
|
|
void ArmorStand::readAdditionalSaveData(CompoundTag* tag)
|
|
{
|
|
LivingEntity::readAdditionalSaveData(tag);
|
|
|
|
if (tag->contains(L"ArmorStandEquipment", 9))
|
|
{
|
|
ListTag<CompoundTag>* eqList = static_cast<ListTag<CompoundTag>*>(
|
|
static_cast<void*>(tag->getList(L"ArmorStandEquipment")));
|
|
if (eqList)
|
|
for (int i = 0; i < equipmentCount && i < eqList->size(); i++)
|
|
equipment[i] = ItemInstance::fromTag(eqList->get(i));
|
|
}
|
|
|
|
setInvisible (tag->getBoolean(L"Invisible"));
|
|
setSmall (tag->getBoolean(L"Small"));
|
|
setShowArms (tag->getBoolean(L"ShowArms"));
|
|
setNoBasePlate (tag->getBoolean(L"NoBasePlate"));
|
|
setNoGravity (tag->getBoolean(L"NoGravity"));
|
|
setMarker (tag->getBoolean(L"Marker"));
|
|
disabledSlots = tag->getInt(L"DisabledSlots");
|
|
|
|
isMarkerFlag = isMarker();
|
|
noPhysics = (entityData->getByte(DATA_CLIENT_FLAGS) & FLAG_NO_GRAVITY) != 0;
|
|
|
|
CompoundTag* pose = tag->getCompound(L"Pose");
|
|
if (pose && !pose->isEmpty()) readPose(pose);
|
|
}
|
|
|
|
void ArmorStand::addAdditonalSaveData(CompoundTag* tag)
|
|
{
|
|
LivingEntity::addAdditonalSaveData(tag);
|
|
|
|
ListTag<CompoundTag>* eqList = new ListTag<CompoundTag>();
|
|
for (int i = 0; i < equipmentCount; i++)
|
|
{
|
|
CompoundTag* itemTag = new CompoundTag();
|
|
if (equipment[i]) equipment[i]->save(itemTag);
|
|
eqList->add(itemTag);
|
|
}
|
|
tag->put(L"ArmorStandEquipment", eqList);
|
|
|
|
if (isInvisible()) tag->putBoolean(L"Invisible", true);
|
|
tag->putBoolean(L"Small", isSmall());
|
|
tag->putBoolean(L"ShowArms", isShowArms());
|
|
tag->putBoolean(L"NoBasePlate", isNoBasePlate());
|
|
tag->putInt (L"DisabledSlots",disabledSlots);
|
|
if (isMarker()) tag->putBoolean(L"Marker", true);
|
|
tag->putBoolean(L"NoGravity", noPhysics);
|
|
tag->put(L"Pose", writePose());
|
|
}
|
|
|
|
|
|
void ArmorStand::handleEntityEvent(byte id)
|
|
{
|
|
if (id == 31)
|
|
{
|
|
int woodId = Tile::wood ? Tile::wood->id : 5;
|
|
ePARTICLE_TYPE p = PARTICLE_TILECRACK(woodId, 0);
|
|
for (int i = 0; i < 10; i++)
|
|
{
|
|
double xa = random->nextGaussian() * 0.01;
|
|
double ya = -0.05;
|
|
double za = random->nextGaussian() * 0.01;
|
|
double px = x + (random->nextFloat() * 0.1f - 0.05f);
|
|
double py = y + (bbHeight * 0.6f) + (random->nextFloat() * 0.2f);
|
|
double pz = z + (random->nextFloat() * 0.1f - 0.05f);
|
|
level->addParticle(p, px, py, pz, xa, ya, za);
|
|
}
|
|
}
|
|
else if (id == 32) lastHit = (long long)tickCount;
|
|
else LivingEntity::handleEntityEvent(id);
|
|
} |