#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(0)); // DATA_CUSTOM_NAME_VISIBLE entityData->define(2, wstring(L"")); // DATA_CUSTOM_NAME entityData->define(DATA_CLIENT_FLAGS, static_cast(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, 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(source) != nullptr) { shared_ptr attacker = source->getDirectEntity(); if (attacker && attacker->instanceof(eTYPE_PLAYER) && !dynamic_pointer_cast(attacker)->isAllowedToHurtEntity(shared_from_this())) return false; } if (source->isExplosion()) { spawnAtLocation(Item::armor_stand_Id, 1); brokenByAnything(); remove(); return true; } if (dynamic_cast(source) != nullptr) { shared_ptr attacker = source->getEntity(); if (attacker && attacker->instanceof(eTYPE_PLAYER) && dynamic_pointer_cast(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 ArmorStand::getCarriedItem() { return equipment[SLOT_WEAPON]; } shared_ptr ArmorStand::getCarried(int slot) { return (slot >= 0 && slot < equipmentCount) ? equipment[slot] : nullptr; } shared_ptr ArmorStand::getArmor(int pos) { int idx = pos + 1; return (idx >= 1 && idx < equipmentCount) ? equipment[idx] : nullptr; } void ArmorStand::setEquippedSlot(int slot, shared_ptr item) { if (slot >= 0 && slot < equipmentCount) equipment[slot] = item; } ItemInstanceArray ArmorStand::getEquipmentSlots() { return ItemInstanceArray(equipment, equipmentCount); } int ArmorStand::getEquipmentSlotForItem(shared_ptr item) const { if (!item) return SLOT_WEAPON; return Mob::getEquipmentSlotForItem(item); } bool ArmorStand::setSlot(int inventorySlot, shared_ptr 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(item->getItem()); if (!bi) return false; } else return false; } } setEquippedSlot(localSlot, item); return true; } void ArmorStand::swapItem(shared_ptr player, int slot) { shared_ptr standItem = equipment[slot]; shared_ptr 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) { 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 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 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, const Vec3& hitVec) { if (isMarker()) return false; if (level->isClientSide) return true; if (player->isSpectator()) return true; shared_ptr 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, const Vec3& hitVec) { if (isMarker()) return 0; if (player->isSpectator()) return 0; shared_ptr 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* list = static_cast*>( static_cast(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* eqList = static_cast*>( static_cast(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* eqList = new ListTag(); 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); }