mirror of
https://github.com/4jcraft/4jcraft.git
synced 2026-04-23 20:53:39 +00:00
904 lines
30 KiB
C++
904 lines
30 KiB
C++
#include "../Platform/stdafx.h"
|
|
#include "../Util/JavaMath.h"
|
|
#include "../Headers/net.minecraft.network.packet.h"
|
|
#include "../Headers/net.minecraft.world.level.tile.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.ai.control.h"
|
|
#include "../Headers/net.minecraft.world.entity.ai.navigation.h"
|
|
#include "../Headers/net.minecraft.world.entity.ai.sensing.h"
|
|
#include "../Headers/net.minecraft.world.entity.player.h"
|
|
#include "../Headers/net.minecraft.world.entity.animal.h"
|
|
#include "../Headers/net.minecraft.world.entity.monster.h"
|
|
#include "../Headers/net.minecraft.world.item.h"
|
|
#include "../Headers/net.minecraft.world.level.h"
|
|
#include "../Headers/net.minecraft.world.level.material.h"
|
|
#include "../Headers/net.minecraft.world.damagesource.h"
|
|
#include "../Headers/net.minecraft.world.effect.h"
|
|
#include "../Headers/net.minecraft.world.item.alchemy.h"
|
|
#include "../Headers/net.minecraft.world.item.enchantment.h"
|
|
#include "../Headers/net.minecraft.world.h"
|
|
#include "../../Minecraft.Client/Level/ServerLevel.h"
|
|
#include "../../Minecraft.Client/Player/EntityTracker.h"
|
|
#include "../Headers/com.mojang.nbt.h"
|
|
#include "Mob.h"
|
|
#include "../../Minecraft.Client/Textures/Textures.h"
|
|
#include "../Util/SoundTypes.h"
|
|
#include "../Util/BasicTypeContainers.h"
|
|
#include "../Util/ParticleTypes.h"
|
|
#include "../Stats/GenericStats.h"
|
|
#include "ItemEntity.h"
|
|
|
|
const float Mob::MAX_WEARING_ARMOR_CHANCE = 0.15f;
|
|
const float Mob::MAX_PICKUP_LOOT_CHANCE = 0.55f;
|
|
const float Mob::MAX_ENCHANTED_ARMOR_CHANCE = 0.50f;
|
|
const float Mob::MAX_ENCHANTED_WEAPON_CHANCE = 0.25f;
|
|
|
|
void Mob::_init() {
|
|
ambientSoundTime = 0;
|
|
xpReward = 0;
|
|
defaultLookAngle = 0.0f;
|
|
lookingAt = nullptr;
|
|
lookTime = 0;
|
|
target = nullptr;
|
|
sensing = NULL;
|
|
|
|
equipment = ItemInstanceArray(5);
|
|
dropChances = floatArray(5);
|
|
for (unsigned int i = 0; i < 5; ++i) {
|
|
equipment[i] = nullptr;
|
|
dropChances[i] = 0.0f;
|
|
}
|
|
|
|
_canPickUpLoot = false;
|
|
persistenceRequired = false;
|
|
|
|
_isLeashed = false;
|
|
leashHolder = nullptr;
|
|
leashInfoTag = NULL;
|
|
}
|
|
|
|
Mob::Mob(Level* level) : LivingEntity(level) {
|
|
MemSect(57);
|
|
_init();
|
|
MemSect(0);
|
|
|
|
MemSect(58);
|
|
// 4J Stu - We call this again in the derived classes, but need to do it
|
|
// here for some internal members
|
|
registerAttributes();
|
|
MemSect(0);
|
|
|
|
lookControl = new LookControl(this);
|
|
moveControl = new MoveControl(this);
|
|
jumpControl = new JumpControl(this);
|
|
bodyControl = new BodyControl(this);
|
|
navigation = new PathNavigation(this, level);
|
|
sensing = new Sensing(this);
|
|
|
|
for (int i = 0; i < 5; i++) {
|
|
dropChances[i] = 0.085f;
|
|
}
|
|
}
|
|
|
|
Mob::~Mob() {
|
|
if (lookControl != NULL) delete lookControl;
|
|
if (moveControl != NULL) delete moveControl;
|
|
if (jumpControl != NULL) delete jumpControl;
|
|
if (bodyControl != NULL) delete bodyControl;
|
|
if (navigation != NULL) delete navigation;
|
|
if (sensing != NULL) delete sensing;
|
|
|
|
if (leashInfoTag != NULL) delete leashInfoTag;
|
|
|
|
if (equipment.data != NULL) delete[] equipment.data;
|
|
delete[] dropChances.data;
|
|
}
|
|
|
|
void Mob::registerAttributes() {
|
|
LivingEntity::registerAttributes();
|
|
|
|
getAttributes()
|
|
->registerAttribute(SharedMonsterAttributes::FOLLOW_RANGE)
|
|
->setBaseValue(16);
|
|
}
|
|
|
|
LookControl* Mob::getLookControl() { return lookControl; }
|
|
|
|
MoveControl* Mob::getMoveControl() { return moveControl; }
|
|
|
|
JumpControl* Mob::getJumpControl() { return jumpControl; }
|
|
|
|
PathNavigation* Mob::getNavigation() { return navigation; }
|
|
|
|
Sensing* Mob::getSensing() { return sensing; }
|
|
|
|
std::shared_ptr<LivingEntity> Mob::getTarget() { return target; }
|
|
|
|
void Mob::setTarget(std::shared_ptr<LivingEntity> target) {
|
|
this->target = target;
|
|
}
|
|
|
|
bool Mob::canAttackType(eINSTANCEOF targetType) {
|
|
return !(targetType == eTYPE_CREEPER || targetType == eTYPE_GHAST);
|
|
}
|
|
|
|
// Called by eatTileGoal
|
|
void Mob::ate() {}
|
|
|
|
void Mob::defineSynchedData() {
|
|
LivingEntity::defineSynchedData();
|
|
entityData->define(DATA_CUSTOM_NAME_VISIBLE, (uint8_t)0);
|
|
entityData->define(DATA_CUSTOM_NAME, L"");
|
|
}
|
|
|
|
int Mob::getAmbientSoundInterval() { return 20 * 4; }
|
|
|
|
void Mob::playAmbientSound() {
|
|
MemSect(31);
|
|
int ambient = getAmbientSound();
|
|
if (ambient != -1) {
|
|
playSound(ambient, getSoundVolume(), getVoicePitch());
|
|
}
|
|
MemSect(0);
|
|
}
|
|
|
|
void Mob::baseTick() {
|
|
LivingEntity::baseTick();
|
|
|
|
if (isAlive() && random->nextInt(1000) < ambientSoundTime++) {
|
|
ambientSoundTime = -getAmbientSoundInterval();
|
|
|
|
playAmbientSound();
|
|
}
|
|
}
|
|
|
|
int Mob::getExperienceReward(std::shared_ptr<Player> killedBy) {
|
|
if (xpReward > 0) {
|
|
int result = xpReward;
|
|
|
|
ItemInstanceArray slots = getEquipmentSlots();
|
|
for (int i = 0; i < slots.length; i++) {
|
|
if (slots[i] != NULL && dropChances[i] <= 1) {
|
|
result += 1 + random->nextInt(3);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
} else {
|
|
return xpReward;
|
|
}
|
|
}
|
|
void Mob::spawnAnim() {
|
|
for (int i = 0; i < 20; i++) {
|
|
double xa = random->nextGaussian() * 0.02;
|
|
double ya = random->nextGaussian() * 0.02;
|
|
double za = random->nextGaussian() * 0.02;
|
|
double dd = 10;
|
|
level->addParticle(
|
|
eParticleType_explode,
|
|
x + random->nextFloat() * bbWidth * 2 - bbWidth - xa * dd,
|
|
y + random->nextFloat() * bbHeight - ya * dd,
|
|
z + random->nextFloat() * bbWidth * 2 - bbWidth - za * dd, xa, ya,
|
|
za);
|
|
}
|
|
}
|
|
|
|
void Mob::tick() {
|
|
LivingEntity::tick();
|
|
|
|
if (!level->isClientSide) {
|
|
tickLeash();
|
|
}
|
|
}
|
|
|
|
float Mob::tickHeadTurn(float yBodyRotT, float walkSpeed) {
|
|
if (useNewAi()) {
|
|
bodyControl->clientTick();
|
|
return walkSpeed;
|
|
} else {
|
|
return LivingEntity::tickHeadTurn(yBodyRotT, walkSpeed);
|
|
}
|
|
}
|
|
|
|
int Mob::getAmbientSound() { return -1; }
|
|
|
|
int Mob::getDeathLoot() { return 0; }
|
|
|
|
void Mob::dropDeathLoot(bool wasKilledByPlayer, int playerBonusLevel) {
|
|
int loot = getDeathLoot();
|
|
if (loot > 0) {
|
|
int count = random->nextInt(3);
|
|
if (playerBonusLevel > 0) {
|
|
count += random->nextInt(playerBonusLevel + 1);
|
|
}
|
|
for (int i = 0; i < count; i++) spawnAtLocation(loot, 1);
|
|
}
|
|
}
|
|
|
|
void Mob::addAdditonalSaveData(CompoundTag* entityTag) {
|
|
LivingEntity::addAdditonalSaveData(entityTag);
|
|
entityTag->putBoolean(L"CanPickUpLoot", canPickUpLoot());
|
|
entityTag->putBoolean(L"PersistenceRequired", persistenceRequired);
|
|
|
|
ListTag<CompoundTag>* gear = new ListTag<CompoundTag>();
|
|
for (int i = 0; i < equipment.length; i++) {
|
|
CompoundTag* tag = new CompoundTag();
|
|
if (equipment[i] != NULL) equipment[i]->save(tag);
|
|
gear->add(tag);
|
|
}
|
|
entityTag->put(L"Equipment", gear);
|
|
|
|
ListTag<FloatTag>* dropChanceList = new ListTag<FloatTag>();
|
|
for (int i = 0; i < dropChances.length; i++) {
|
|
dropChanceList->add(new FloatTag(_toString(i), dropChances[i]));
|
|
}
|
|
entityTag->put(L"DropChances", dropChanceList);
|
|
entityTag->putString(L"CustomName", getCustomName());
|
|
entityTag->putBoolean(L"CustomNameVisible", isCustomNameVisible());
|
|
|
|
// leash info
|
|
entityTag->putBoolean(L"Leashed", _isLeashed);
|
|
if (leashHolder != NULL) {
|
|
CompoundTag* leashTag = new CompoundTag(L"Leash");
|
|
if (leashHolder->instanceof(eTYPE_LIVINGENTITY)) {
|
|
// a walking, talking, leash holder
|
|
leashTag->putString(L"UUID", leashHolder->getUUID());
|
|
} else if (leashHolder->instanceof(eTYPE_HANGING_ENTITY)) {
|
|
// a fixed holder (that doesn't save itself)
|
|
std::shared_ptr<HangingEntity> hangInThere =
|
|
std::dynamic_pointer_cast<HangingEntity>(leashHolder);
|
|
leashTag->putInt(L"X", hangInThere->xTile);
|
|
leashTag->putInt(L"Y", hangInThere->yTile);
|
|
leashTag->putInt(L"Z", hangInThere->zTile);
|
|
}
|
|
entityTag->put(L"Leash", leashTag);
|
|
}
|
|
}
|
|
|
|
void Mob::readAdditionalSaveData(CompoundTag* tag) {
|
|
LivingEntity::readAdditionalSaveData(tag);
|
|
|
|
setCanPickUpLoot(tag->getBoolean(L"CanPickUpLoot"));
|
|
persistenceRequired = tag->getBoolean(L"PersistenceRequired");
|
|
if (tag->contains(L"CustomName") &&
|
|
tag->getString(L"CustomName").length() > 0)
|
|
setCustomName(tag->getString(L"CustomName"));
|
|
setCustomNameVisible(tag->getBoolean(L"CustomNameVisible"));
|
|
|
|
if (tag->contains(L"Equipment")) {
|
|
ListTag<CompoundTag>* gear =
|
|
(ListTag<CompoundTag>*)tag->getList(L"Equipment");
|
|
|
|
for (int i = 0; i < equipment.length; i++) {
|
|
equipment[i] = ItemInstance::fromTag(gear->get(i));
|
|
}
|
|
}
|
|
|
|
if (tag->contains(L"DropChances")) {
|
|
ListTag<FloatTag>* items =
|
|
(ListTag<FloatTag>*)tag->getList(L"DropChances");
|
|
for (int i = 0; i < items->size(); i++) {
|
|
dropChances[i] = items->get(i)->data;
|
|
}
|
|
}
|
|
|
|
_isLeashed = tag->getBoolean(L"Leashed");
|
|
if (_isLeashed && tag->contains(L"Leash")) {
|
|
leashInfoTag = (CompoundTag*)tag->getCompound(L"Leash")->copy();
|
|
}
|
|
}
|
|
|
|
void Mob::setYya(float yya) { this->yya = yya; }
|
|
|
|
void Mob::setSpeed(float speed) {
|
|
LivingEntity::setSpeed(speed);
|
|
setYya(speed);
|
|
}
|
|
|
|
void Mob::aiStep() {
|
|
LivingEntity::aiStep();
|
|
|
|
if (!level->isClientSide && canPickUpLoot() && !dead &&
|
|
level->getGameRules()->getBoolean(GameRules::RULE_MOBGRIEFING)) {
|
|
std::vector<std::shared_ptr<Entity> >* entities =
|
|
level->getEntitiesOfClass(typeid(ItemEntity), bb->grow(1, 0, 1));
|
|
for (AUTO_VAR(it, entities->begin()); it != entities->end(); ++it) {
|
|
std::shared_ptr<ItemEntity> entity =
|
|
std::dynamic_pointer_cast<ItemEntity>(*it);
|
|
if (entity->removed || entity->getItem() == NULL) continue;
|
|
std::shared_ptr<ItemInstance> item = entity->getItem();
|
|
int slot = getEquipmentSlotForItem(item);
|
|
|
|
if (slot > -1) {
|
|
bool replace = true;
|
|
std::shared_ptr<ItemInstance> current = getCarried(slot);
|
|
|
|
if (current != NULL) {
|
|
if (slot == SLOT_WEAPON) {
|
|
WeaponItem* newWeapon =
|
|
dynamic_cast<WeaponItem*>(item->getItem());
|
|
WeaponItem* oldWeapon =
|
|
dynamic_cast<WeaponItem*>(current->getItem());
|
|
if (newWeapon != NULL && oldWeapon == NULL) {
|
|
replace = true;
|
|
} else if (newWeapon != NULL && oldWeapon != NULL) {
|
|
if (newWeapon->getTierDamage() ==
|
|
oldWeapon->getTierDamage()) {
|
|
replace = item->getAuxValue() >
|
|
current->getAuxValue() ||
|
|
item->hasTag() && !current->hasTag();
|
|
} else {
|
|
replace = newWeapon->getTierDamage() >
|
|
oldWeapon->getTierDamage();
|
|
}
|
|
} else {
|
|
replace = false;
|
|
}
|
|
} else {
|
|
ArmorItem* newArmor =
|
|
dynamic_cast<ArmorItem*>(item->getItem());
|
|
ArmorItem* oldArmor =
|
|
dynamic_cast<ArmorItem*>(current->getItem());
|
|
if (newArmor != NULL && oldArmor == NULL) {
|
|
replace = true;
|
|
} else if (newArmor != NULL && oldArmor != NULL) {
|
|
if (newArmor->defense == oldArmor->defense) {
|
|
replace = item->getAuxValue() >
|
|
current->getAuxValue() ||
|
|
item->hasTag() && !current->hasTag();
|
|
} else {
|
|
replace = newArmor->defense > oldArmor->defense;
|
|
}
|
|
} else {
|
|
replace = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (replace) {
|
|
if (current != NULL &&
|
|
random->nextFloat() - 0.1f < dropChances[slot]) {
|
|
spawnAtLocation(current, 0);
|
|
}
|
|
|
|
setEquippedSlot(slot, item);
|
|
dropChances[slot] = 2;
|
|
persistenceRequired = true;
|
|
take(entity, 1);
|
|
entity->remove();
|
|
}
|
|
}
|
|
}
|
|
delete entities;
|
|
}
|
|
}
|
|
|
|
bool Mob::useNewAi() { return false; }
|
|
|
|
bool Mob::removeWhenFarAway() { return true; }
|
|
|
|
void Mob::checkDespawn() {
|
|
if (persistenceRequired) {
|
|
noActionTime = 0;
|
|
return;
|
|
}
|
|
std::shared_ptr<Entity> player =
|
|
level->getNearestPlayer(shared_from_this(), -1);
|
|
if (player != NULL) {
|
|
double xd = player->x - x;
|
|
double yd = player->y - y;
|
|
double zd = player->z - z;
|
|
double sd = xd * xd + yd * yd + zd * zd;
|
|
|
|
if (removeWhenFarAway() && sd > 128 * 128) {
|
|
remove();
|
|
}
|
|
|
|
if (noActionTime > 20 * 30 && random->nextInt(800) == 0 &&
|
|
sd > 32 * 32 && removeWhenFarAway()) {
|
|
remove();
|
|
} else if (sd < 32 * 32) {
|
|
noActionTime = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Mob::newServerAiStep() {
|
|
PIXBeginNamedEvent(0, "Tick target selector for %d", GetType());
|
|
MemSect(51);
|
|
noActionTime++;
|
|
PIXBeginNamedEvent(0, "Check despawn");
|
|
checkDespawn();
|
|
PIXEndNamedEvent();
|
|
PIXBeginNamedEvent(0, "Tick sensing");
|
|
sensing->tick();
|
|
PIXEndNamedEvent();
|
|
PIXBeginNamedEvent(0, "Tick target selector");
|
|
targetSelector.tick();
|
|
PIXEndNamedEvent();
|
|
PIXBeginNamedEvent(0, "Tick goal selectors");
|
|
goalSelector.tick();
|
|
PIXEndNamedEvent();
|
|
PIXBeginNamedEvent(0, "Tick navigation");
|
|
navigation->tick();
|
|
PIXEndNamedEvent();
|
|
PIXBeginNamedEvent(0, "Tick server ai mob step");
|
|
serverAiMobStep();
|
|
PIXEndNamedEvent();
|
|
PIXBeginNamedEvent(0, "Tick move");
|
|
moveControl->tick();
|
|
PIXEndNamedEvent();
|
|
PIXBeginNamedEvent(0, "Tick look");
|
|
lookControl->tick();
|
|
PIXEndNamedEvent();
|
|
PIXBeginNamedEvent(0, "Tick jump");
|
|
jumpControl->tick();
|
|
PIXEndNamedEvent();
|
|
// Consider this for extra strolling if it is protected against despawning.
|
|
// We aren't interested in ones that aren't protected as the whole point of
|
|
// this extra wandering is to potentially transition from protected to not
|
|
// protected.
|
|
PIXBeginNamedEvent(0, "Consider extra wandering");
|
|
considerForExtraWandering(isDespawnProtected());
|
|
PIXEndNamedEvent();
|
|
MemSect(0);
|
|
PIXEndNamedEvent();
|
|
}
|
|
|
|
void Mob::serverAiStep() {
|
|
LivingEntity::serverAiStep();
|
|
|
|
xxa = 0;
|
|
yya = 0;
|
|
|
|
checkDespawn();
|
|
|
|
float lookDistance = 8;
|
|
if (random->nextFloat() < 0.02f) {
|
|
std::shared_ptr<Player> player =
|
|
level->getNearestPlayer(shared_from_this(), lookDistance);
|
|
if (player != NULL) {
|
|
lookingAt = player;
|
|
lookTime = 10 + random->nextInt(20);
|
|
} else {
|
|
yRotA = (random->nextFloat() - 0.5f) * 20;
|
|
}
|
|
}
|
|
|
|
if (lookingAt != NULL) {
|
|
lookAt(lookingAt, 10.0f, (float)getMaxHeadXRot());
|
|
if (lookTime-- <= 0 || lookingAt->removed ||
|
|
lookingAt->distanceToSqr(shared_from_this()) >
|
|
lookDistance * lookDistance) {
|
|
lookingAt = nullptr;
|
|
}
|
|
} else {
|
|
if (random->nextFloat() < 0.05f) {
|
|
yRotA = (random->nextFloat() - 0.5f) * 20;
|
|
}
|
|
yRot += yRotA;
|
|
xRot = defaultLookAngle;
|
|
}
|
|
|
|
bool inWater = isInWater();
|
|
bool inLava = isInLava();
|
|
if (inWater || inLava) jumping = random->nextFloat() < 0.8f;
|
|
}
|
|
|
|
int Mob::getMaxHeadXRot() { return 40; }
|
|
|
|
void Mob::lookAt(std::shared_ptr<Entity> e, float yMax, float xMax) {
|
|
double xd = e->x - x;
|
|
double yd;
|
|
double zd = e->z - z;
|
|
|
|
if (e->instanceof(eTYPE_LIVINGENTITY)) {
|
|
std::shared_ptr<LivingEntity> mob =
|
|
std::dynamic_pointer_cast<LivingEntity>(e);
|
|
yd = (mob->y + mob->getHeadHeight()) - (y + getHeadHeight());
|
|
} else {
|
|
yd = (e->bb->y0 + e->bb->y1) / 2 - (y + getHeadHeight());
|
|
}
|
|
|
|
double sd = Mth::sqrt(xd * xd + zd * zd);
|
|
|
|
float yRotD = (float)(atan2(zd, xd) * 180 / PI) - 90;
|
|
float xRotD = (float)-(atan2(yd, sd) * 180 / PI);
|
|
xRot = rotlerp(xRot, xRotD, xMax);
|
|
yRot = rotlerp(yRot, yRotD, yMax);
|
|
}
|
|
|
|
bool Mob::isLookingAtAnEntity() { return lookingAt != NULL; }
|
|
|
|
std::shared_ptr<Entity> Mob::getLookingAt() { return lookingAt; }
|
|
|
|
float Mob::rotlerp(float a, float b, float max) {
|
|
float diff = Mth::wrapDegrees(b - a);
|
|
if (diff > max) {
|
|
diff = max;
|
|
}
|
|
if (diff < -max) {
|
|
diff = -max;
|
|
}
|
|
return a + diff;
|
|
}
|
|
|
|
bool Mob::canSpawn() {
|
|
// 4J - altered to use special containsAnyLiquid variant
|
|
return level->isUnobstructed(bb) &&
|
|
level->getCubes(shared_from_this(), bb)->empty() &&
|
|
!level->containsAnyLiquid_NoLoad(bb);
|
|
}
|
|
|
|
float Mob::getSizeScale() { return 1.0f; }
|
|
|
|
float Mob::getHeadSizeScale() { return 1.0f; }
|
|
|
|
int Mob::getMaxSpawnClusterSize() { return 4; }
|
|
|
|
int Mob::getMaxFallDistance() {
|
|
if (getTarget() == NULL) return 3;
|
|
int sacrifice = (int)(getHealth() - (getMaxHealth() * 0.33f));
|
|
sacrifice -= (3 - level->difficulty) * 4;
|
|
if (sacrifice < 0) sacrifice = 0;
|
|
return sacrifice + 3;
|
|
}
|
|
|
|
std::shared_ptr<ItemInstance> Mob::getCarriedItem() {
|
|
return equipment[SLOT_WEAPON];
|
|
}
|
|
|
|
std::shared_ptr<ItemInstance> Mob::getCarried(int slot) {
|
|
return equipment[slot];
|
|
}
|
|
|
|
std::shared_ptr<ItemInstance> Mob::getArmor(int pos) {
|
|
return equipment[pos + 1];
|
|
}
|
|
|
|
void Mob::setEquippedSlot(int slot, std::shared_ptr<ItemInstance> item) {
|
|
equipment[slot] = item;
|
|
}
|
|
|
|
ItemInstanceArray Mob::getEquipmentSlots() { return equipment; }
|
|
|
|
void Mob::dropEquipment(bool byPlayer, int playerBonusLevel) {
|
|
for (int slot = 0; slot < getEquipmentSlots().length; slot++) {
|
|
std::shared_ptr<ItemInstance> item = getCarried(slot);
|
|
bool preserve = dropChances[slot] > 1;
|
|
|
|
if (item != NULL && (byPlayer || preserve) &&
|
|
random->nextFloat() - playerBonusLevel * 0.01f <
|
|
dropChances[slot]) {
|
|
if (!preserve && item->isDamageableItem()) {
|
|
int _max = std::max(item->getMaxDamage() - 25, 1);
|
|
int damage = item->getMaxDamage() -
|
|
random->nextInt(random->nextInt(_max) + 1);
|
|
if (damage > _max) damage = _max;
|
|
if (damage < 1) damage = 1;
|
|
item->setAuxValue(damage);
|
|
}
|
|
spawnAtLocation(item, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Mob::populateDefaultEquipmentSlots() {
|
|
if (random->nextFloat() <
|
|
MAX_WEARING_ARMOR_CHANCE * level->getDifficulty(x, y, z)) {
|
|
int armorType = random->nextInt(2);
|
|
float partialChance =
|
|
level->difficulty == Difficulty::HARD ? 0.1f : 0.25f;
|
|
if (random->nextFloat() < 0.095f) armorType++;
|
|
if (random->nextFloat() < 0.095f) armorType++;
|
|
if (random->nextFloat() < 0.095f) armorType++;
|
|
|
|
for (int i = 3; i >= 0; i--) {
|
|
std::shared_ptr<ItemInstance> item = getArmor(i);
|
|
if (i < 3 && random->nextFloat() < partialChance) break;
|
|
if (item == NULL) {
|
|
Item* equip = getEquipmentForSlot(i + 1, armorType);
|
|
if (equip != NULL)
|
|
setEquippedSlot(i + 1, std::shared_ptr<ItemInstance>(
|
|
new ItemInstance(equip)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int Mob::getEquipmentSlotForItem(std::shared_ptr<ItemInstance> item) {
|
|
if (item->id == Tile::pumpkin_Id || item->id == Item::skull_Id) {
|
|
return SLOT_HELM;
|
|
}
|
|
|
|
ArmorItem* armorItem = dynamic_cast<ArmorItem*>(item->getItem());
|
|
if (armorItem != NULL) {
|
|
switch (armorItem->slot) {
|
|
case ArmorItem::SLOT_FEET:
|
|
return SLOT_BOOTS;
|
|
case ArmorItem::SLOT_LEGS:
|
|
return SLOT_LEGGINGS;
|
|
case ArmorItem::SLOT_TORSO:
|
|
return SLOT_CHEST;
|
|
case ArmorItem::SLOT_HEAD:
|
|
return SLOT_HELM;
|
|
}
|
|
}
|
|
|
|
return SLOT_WEAPON;
|
|
}
|
|
|
|
Item* Mob::getEquipmentForSlot(int slot, int type) {
|
|
switch (slot) {
|
|
case SLOT_HELM:
|
|
if (type == 0) return Item::helmet_leather;
|
|
if (type == 1) return Item::helmet_gold;
|
|
if (type == 2) return Item::helmet_chain;
|
|
if (type == 3) return Item::helmet_iron;
|
|
if (type == 4) return Item::helmet_diamond;
|
|
case SLOT_CHEST:
|
|
if (type == 0) return Item::chestplate_leather;
|
|
if (type == 1) return Item::chestplate_gold;
|
|
if (type == 2) return Item::chestplate_chain;
|
|
if (type == 3) return Item::chestplate_iron;
|
|
if (type == 4) return Item::chestplate_diamond;
|
|
case SLOT_LEGGINGS:
|
|
if (type == 0) return Item::leggings_leather;
|
|
if (type == 1) return Item::leggings_gold;
|
|
if (type == 2) return Item::leggings_chain;
|
|
if (type == 3) return Item::leggings_iron;
|
|
if (type == 4) return Item::leggings_diamond;
|
|
case SLOT_BOOTS:
|
|
if (type == 0) return Item::boots_leather;
|
|
if (type == 1) return Item::boots_gold;
|
|
if (type == 2) return Item::boots_chain;
|
|
if (type == 3) return Item::boots_iron;
|
|
if (type == 4) return Item::boots_diamond;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void Mob::populateDefaultEquipmentEnchantments() {
|
|
float difficulty = level->getDifficulty(x, y, z);
|
|
|
|
if (getCarriedItem() != NULL &&
|
|
random->nextFloat() < MAX_ENCHANTED_WEAPON_CHANCE * difficulty) {
|
|
EnchantmentHelper::enchantItem(
|
|
random, getCarriedItem(),
|
|
(int)(5 + difficulty * random->nextInt(18)));
|
|
}
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
std::shared_ptr<ItemInstance> item = getArmor(i);
|
|
if (item != NULL &&
|
|
random->nextFloat() < MAX_ENCHANTED_ARMOR_CHANCE * difficulty) {
|
|
EnchantmentHelper::enchantItem(
|
|
random, item, (int)(5 + difficulty * random->nextInt(18)));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Added this method so mobs can handle their own spawn settings instead of
|
|
* hacking MobSpawner.java
|
|
*
|
|
* @param groupData
|
|
* TODO
|
|
* @return TODO
|
|
*/
|
|
MobGroupData* Mob::finalizeMobSpawn(
|
|
MobGroupData* groupData, int extraData /*= 0*/) // 4J Added extraData param
|
|
{
|
|
// 4J Stu - Take this out, it's not great and nobody will notice. Also not
|
|
// great for performance.
|
|
// getAttribute(SharedMonsterAttributes::FOLLOW_RANGE)->addModifier(new
|
|
// AttributeModifier(random->nextGaussian() * 0.05,
|
|
// AttributeModifier::OPERATION_MULTIPLY_BASE));
|
|
|
|
return groupData;
|
|
}
|
|
|
|
void Mob::finalizeSpawnEggSpawn(int extraData) {}
|
|
|
|
bool Mob::canBeControlledByRider() { return false; }
|
|
|
|
std::wstring Mob::getAName() {
|
|
if (hasCustomName()) return getCustomName();
|
|
return LivingEntity::getAName();
|
|
}
|
|
|
|
void Mob::setPersistenceRequired() { persistenceRequired = true; }
|
|
|
|
void Mob::setCustomName(const std::wstring& name) {
|
|
entityData->set(DATA_CUSTOM_NAME, name);
|
|
}
|
|
|
|
std::wstring Mob::getCustomName() {
|
|
return entityData->getString(DATA_CUSTOM_NAME);
|
|
}
|
|
|
|
bool Mob::hasCustomName() {
|
|
return entityData->getString(DATA_CUSTOM_NAME).length() > 0;
|
|
}
|
|
|
|
void Mob::setCustomNameVisible(bool visible) {
|
|
entityData->set(DATA_CUSTOM_NAME_VISIBLE,
|
|
visible ? (uint8_t)1 : (uint8_t)0);
|
|
}
|
|
|
|
bool Mob::isCustomNameVisible() {
|
|
return entityData->getByte(DATA_CUSTOM_NAME_VISIBLE) == 1;
|
|
}
|
|
|
|
bool Mob::shouldShowName() { return isCustomNameVisible(); }
|
|
|
|
void Mob::setDropChance(int slot, float pct) { dropChances[slot] = pct; }
|
|
|
|
bool Mob::canPickUpLoot() { return _canPickUpLoot; }
|
|
|
|
void Mob::setCanPickUpLoot(bool canPickUpLoot) {
|
|
_canPickUpLoot = canPickUpLoot;
|
|
}
|
|
|
|
bool Mob::isPersistenceRequired() { return persistenceRequired; }
|
|
|
|
bool Mob::interact(std::shared_ptr<Player> player) {
|
|
if (isLeashed() && getLeashHolder() == player) {
|
|
dropLeash(true, !player->abilities.instabuild);
|
|
return true;
|
|
}
|
|
|
|
std::shared_ptr<ItemInstance> itemstack = player->inventory->getSelected();
|
|
if (itemstack != NULL) {
|
|
// it's inconvenient to have the leash code here, but it's because
|
|
// the mob.interact(player) method has priority over
|
|
// item.interact(mob)
|
|
if (itemstack->id == Item::lead_Id) {
|
|
if (canBeLeashed()) {
|
|
std::shared_ptr<TamableAnimal> tamableAnimal = nullptr;
|
|
if (shared_from_this()->instanceof(eTYPE_TAMABLE_ANIMAL) &&
|
|
(tamableAnimal = std::dynamic_pointer_cast<TamableAnimal>(
|
|
shared_from_this()))
|
|
->isTame()) // 4J-JEV: excuse the assignment operator
|
|
// in here, don't want to dyn-cast if it's
|
|
// avoidable.
|
|
{
|
|
if (player->getUUID().compare(
|
|
tamableAnimal->getOwnerUUID()) == 0) {
|
|
setLeashedTo(player, true);
|
|
itemstack->count--;
|
|
return true;
|
|
}
|
|
} else {
|
|
setLeashedTo(player, true);
|
|
itemstack->count--;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mobInteract(player)) {
|
|
return true;
|
|
}
|
|
|
|
return LivingEntity::interact(player);
|
|
}
|
|
|
|
bool Mob::mobInteract(std::shared_ptr<Player> player) { return false; }
|
|
|
|
void Mob::tickLeash() {
|
|
if (leashInfoTag != NULL) {
|
|
restoreLeashFromSave();
|
|
}
|
|
if (!_isLeashed) {
|
|
return;
|
|
}
|
|
|
|
if (leashHolder == NULL || leashHolder->removed) {
|
|
dropLeash(true, true);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void Mob::dropLeash(bool synch, bool createItemDrop) {
|
|
if (_isLeashed) {
|
|
_isLeashed = false;
|
|
leashHolder = nullptr;
|
|
if (!level->isClientSide && createItemDrop) {
|
|
spawnAtLocation(Item::lead_Id, 1);
|
|
}
|
|
|
|
ServerLevel* serverLevel = dynamic_cast<ServerLevel*>(level);
|
|
if (!level->isClientSide && synch && serverLevel != NULL) {
|
|
serverLevel->getTracker()->broadcast(
|
|
shared_from_this(),
|
|
std::shared_ptr<SetEntityLinkPacket>(new SetEntityLinkPacket(
|
|
SetEntityLinkPacket::LEASH, shared_from_this(), nullptr)));
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Mob::canBeLeashed() {
|
|
return !isLeashed() && !shared_from_this()->instanceof(eTYPE_ENEMY);
|
|
}
|
|
|
|
bool Mob::isLeashed() { return _isLeashed; }
|
|
|
|
std::shared_ptr<Entity> Mob::getLeashHolder() { return leashHolder; }
|
|
|
|
void Mob::setLeashedTo(std::shared_ptr<Entity> holder, bool synch) {
|
|
_isLeashed = true;
|
|
leashHolder = holder;
|
|
|
|
ServerLevel* serverLevel = dynamic_cast<ServerLevel*>(level);
|
|
if (!level->isClientSide && synch && serverLevel) {
|
|
serverLevel->getTracker()->broadcast(
|
|
shared_from_this(),
|
|
std::shared_ptr<SetEntityLinkPacket>(new SetEntityLinkPacket(
|
|
SetEntityLinkPacket::LEASH, shared_from_this(), leashHolder)));
|
|
}
|
|
}
|
|
|
|
void Mob::restoreLeashFromSave() {
|
|
// after being added to the world, attempt to recreate leash bond
|
|
if (_isLeashed && leashInfoTag != NULL) {
|
|
if (leashInfoTag->contains(L"UUID")) {
|
|
std::wstring leashUuid = leashInfoTag->getString(L"UUID");
|
|
std::vector<std::shared_ptr<Entity> >* livingEnts =
|
|
level->getEntitiesOfClass(typeid(LivingEntity),
|
|
bb->grow(10, 10, 10));
|
|
for (AUTO_VAR(it, livingEnts->begin()); it != livingEnts->end();
|
|
++it) {
|
|
std::shared_ptr<LivingEntity> le =
|
|
std::dynamic_pointer_cast<LivingEntity>(*it);
|
|
if (le->getUUID().compare(leashUuid) == 0) {
|
|
leashHolder = le;
|
|
setLeashedTo(leashHolder, true);
|
|
break;
|
|
}
|
|
}
|
|
delete livingEnts;
|
|
} else if (leashInfoTag->contains(L"X") &&
|
|
leashInfoTag->contains(L"Y") &&
|
|
leashInfoTag->contains(L"Z")) {
|
|
int x = leashInfoTag->getInt(L"X");
|
|
int y = leashInfoTag->getInt(L"Y");
|
|
int z = leashInfoTag->getInt(L"Z");
|
|
|
|
std::shared_ptr<LeashFenceKnotEntity> activeKnot =
|
|
LeashFenceKnotEntity::findKnotAt(level, x, y, z);
|
|
if (activeKnot == NULL) {
|
|
activeKnot =
|
|
LeashFenceKnotEntity::createAndAddKnot(level, x, y, z);
|
|
}
|
|
leashHolder = activeKnot;
|
|
setLeashedTo(leashHolder, true);
|
|
} else {
|
|
dropLeash(false, true);
|
|
}
|
|
}
|
|
leashInfoTag = NULL;
|
|
}
|
|
|
|
// 4J added so we can not render mobs before their chunks are loaded - to
|
|
// resolve bug 10327 :Gameplay: NPCs can spawn over chunks that have not yet
|
|
// been streamed and display jitter.
|
|
bool Mob::shouldRender(Vec3* c) {
|
|
if (!level->reallyHasChunksAt(Mth::floor(bb->x0), Mth::floor(bb->y0),
|
|
Mth::floor(bb->z0), Mth::floor(bb->x1),
|
|
Mth::floor(bb->y1), Mth::floor(bb->z1))) {
|
|
return false;
|
|
}
|
|
return Entity::shouldRender(c);
|
|
}
|
|
|
|
void Mob::setLevel(Level* level) {
|
|
Entity::setLevel(level);
|
|
navigation->setLevel(level);
|
|
goalSelector.setLevel(level);
|
|
targetSelector.setLevel(level);
|
|
} |