This code was not tested and breaks in Release builds, reverting to restore
functionality of the nightly. All in-game menus do not work and generating
a world crashes.
This reverts commit a9be52c41a.
1077 lines
25 KiB
C++
1077 lines
25 KiB
C++
#include "stdafx.h"
|
|
#include "JavaMath.h"
|
|
#include "net.minecraft.network.packet.h"
|
|
#include "net.minecraft.world.level.tile.h"
|
|
#include "net.minecraft.world.phys.h"
|
|
#include "net.minecraft.world.entity.h"
|
|
#include "net.minecraft.world.entity.ai.attributes.h"
|
|
#include "net.minecraft.world.entity.ai.control.h"
|
|
#include "net.minecraft.world.entity.ai.navigation.h"
|
|
#include "net.minecraft.world.entity.ai.sensing.h"
|
|
#include "net.minecraft.world.entity.player.h"
|
|
#include "net.minecraft.world.entity.animal.h"
|
|
#include "net.minecraft.world.entity.monster.h"
|
|
#include "net.minecraft.world.item.h"
|
|
#include "net.minecraft.world.level.h"
|
|
#include "net.minecraft.world.level.material.h"
|
|
#include "net.minecraft.world.damagesource.h"
|
|
#include "net.minecraft.world.effect.h"
|
|
#include "net.minecraft.world.item.alchemy.h"
|
|
#include "net.minecraft.world.item.enchantment.h"
|
|
#include "net.minecraft.world.h"
|
|
#include "..\Minecraft.Client\ServerLevel.h"
|
|
#include "..\Minecraft.Client\EntityTracker.h"
|
|
#include "com.mojang.nbt.h"
|
|
#include "Mob.h"
|
|
#include "..\Minecraft.Client\Textures.h"
|
|
#include "SoundTypes.h"
|
|
#include "BasicTypeContainers.h"
|
|
#include "ParticleTypes.h"
|
|
#include "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;
|
|
}
|
|
|
|
shared_ptr<LivingEntity> Mob::getTarget()
|
|
{
|
|
return target;
|
|
}
|
|
|
|
void Mob::setTarget(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, (byte) 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(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( std::to_wstring(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)
|
|
shared_ptr<HangingEntity> hangInThere = 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))
|
|
{
|
|
vector<shared_ptr<Entity> > *entities = level->getEntitiesOfClass(typeid(ItemEntity), bb->grow(1, 0, 1));
|
|
for (auto& it : *entities)
|
|
{
|
|
shared_ptr<ItemEntity> entity = dynamic_pointer_cast<ItemEntity>(it);
|
|
if (entity->removed || entity->getItem() == NULL) continue;
|
|
shared_ptr<ItemInstance> item = entity->getItem();
|
|
int slot = getEquipmentSlotForItem(item);
|
|
|
|
if (slot > -1)
|
|
{
|
|
bool replace = true;
|
|
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;
|
|
}
|
|
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)
|
|
{
|
|
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(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) )
|
|
{
|
|
shared_ptr<LivingEntity> mob = 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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
shared_ptr<ItemInstance> Mob::getCarriedItem()
|
|
{
|
|
return equipment[SLOT_WEAPON];
|
|
}
|
|
|
|
shared_ptr<ItemInstance> Mob::getCarried(int slot)
|
|
{
|
|
return equipment[slot];
|
|
}
|
|
|
|
shared_ptr<ItemInstance> Mob::getArmor(int pos)
|
|
{
|
|
return equipment[pos + 1];
|
|
}
|
|
|
|
void Mob::setEquippedSlot(int slot, 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++)
|
|
{
|
|
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 = 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--)
|
|
{
|
|
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, shared_ptr<ItemInstance>(new ItemInstance(equip)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int Mob::getEquipmentSlotForItem(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++)
|
|
{
|
|
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;
|
|
}
|
|
|
|
wstring Mob::getAName()
|
|
{
|
|
if (hasCustomName()) return getCustomName();
|
|
return LivingEntity::getAName();
|
|
}
|
|
|
|
void Mob::setPersistenceRequired()
|
|
{
|
|
persistenceRequired = true;
|
|
}
|
|
|
|
void Mob::setCustomName(const wstring &name)
|
|
{
|
|
entityData->set(DATA_CUSTOM_NAME, name);
|
|
}
|
|
|
|
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 ? (byte) 1 : (byte) 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(shared_ptr<Player> player)
|
|
{
|
|
|
|
if (isLeashed() && getLeashHolder() == player)
|
|
{
|
|
dropLeash(true, !player->abilities.instabuild);
|
|
return true;
|
|
}
|
|
|
|
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())
|
|
{
|
|
shared_ptr<TamableAnimal> tamableAnimal = nullptr;
|
|
if ( shared_from_this()->instanceof(eTYPE_TAMABLE_ANIMAL)
|
|
&& (tamableAnimal = 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(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(), 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;
|
|
}
|
|
|
|
shared_ptr<Entity> Mob::getLeashHolder()
|
|
{
|
|
return leashHolder;
|
|
}
|
|
|
|
void Mob::setLeashedTo(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(), 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"))
|
|
{
|
|
wstring leashUuid = leashInfoTag->getString(L"UUID");
|
|
vector<shared_ptr<Entity> > *livingEnts = level->getEntitiesOfClass(typeid(LivingEntity), bb->grow(10, 10, 10));
|
|
for(auto& livingEnt : *livingEnts)
|
|
{
|
|
shared_ptr<LivingEntity> le = dynamic_pointer_cast<LivingEntity>(livingEnt);
|
|
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");
|
|
|
|
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);
|
|
} |