User:Libxfs/pigzombie - minecraft.fandom.com
The following is static analysis of Minecraft Alpha 1.2.6 source code decompiled and de-obfuscated with Minecraft Coder Pack. You can ask me for the source code for research purposes because it's intelletual property of Notch which I don't have the right to publish or distribute.
Naming[]
Notch calls it Pig Zombie or Zombie Pig in his official releases.
Notch codes it with internal names as pigzombie or zombiepig.
Code excerpt from EntitiyPigZombie.java
public class EntityPigZombie extends EntityZombie
{
...
public EntityPigZombie(World world)
{
...
texture = "/mob/pigzombie.png";
...
}
public void onUpdate()
{
...
worldObj.playSoundAtEntity(this, "mob.zombiepig.zpigangry", ...);
}
protected String getLivingSound()
{
return "mob.zombiepig.zpig";
}
protected String getHurtSound()
{
return "mob.zombiepig.zpighurt";
}
protected String getDeathSound()
{
return "mob.zombiepig.zpigdeath";
}
The conlusion is that the current "Zombie Pigman" is not official naming.
Why they never forgive[]
There is only one property of EntityPigZombie defining its hostility:
private int angerLevel;
which is only referenced in:
~/src/mcp25/alphasrc$ grep angerLevel -r *
minecraft/net/minecraft/src/EntityPigZombie.java: angerLevel = 0;
minecraft/net/minecraft/src/EntityPigZombie.java: nbttagcompound.setShort("Anger", (short)angerLevel);
minecraft/net/minecraft/src/EntityPigZombie.java: angerLevel = nbttagcompound.getShort("Anger");
minecraft/net/minecraft/src/EntityPigZombie.java: if(angerLevel == 0)
minecraft/net/minecraft/src/EntityPigZombie.java: angerLevel = 400 + rand.nextInt(400);
minecraft/net/minecraft/src/EntityPigZombie.java: private int angerLevel;
The zero assignment happens in the constructor:
public EntityPigZombie(World world)
{
super(world);
angerLevel = 0;
...
}
As you can see, it does not decrease. It would a logical fallacy of argument from authority if you assert that they forgive now by simply citing Notch's statement as fact without providing sufficient proof.
Anger behavior[]
Another assignment happens here:
private void becomeAngryAt(Entity entity)
{
playerToAttack = entity;
angerLevel = 400 + rand.nextInt(400);
randomSoundDelay = rand.nextInt(40);
}
which is only referenced by this function which determines how pigmen can be attacked by other entities:
public boolean canAttackEntity(Entity attackingEntity, int damage)
{
if (attackingEntity instanceof EntityPlayer) {
List list = worldObj.getEntitiesWithinAABBExcludingEntity(this, boundingBox.expands(32, 32, 32));
for(int j = 0; j < list.size(); j++) {
Entity nearbyEntity = (Entity)list.get(j);
if (nearbyEntity instanceof EntityPigZombie) {
EntityPigZombie nearbyPigZombie = (EntityPigZombie)nearbyEntity;
nearbyPigZombie.becomeAngryAt(entity);
}
}
becomeAngryAt(attackingEntity);
}
return super.canAttackEntity(attackingEntity, damage);
}
This means they only become angry when attacked by players.
And their anger level affects their behavior of target finding:
protected Entity findPlayerToAttack()
{
if (angerLevel == 0) {
return null;
} else {
return super.findPlayerToAttack();
}
}
How do they choose attack target[]
The direct superclass of EntityPigZombie is EntityZombie which doesn't overload findPlayerToAttack() or canAttackEntity().
The direct superclass of EntityZombie is EntityMobs which overloads findPlayerToAttack() or canAttackEntity() as:
EntityMobs.java:
protected Entity findPlayerToAttack()
{
EntityPlayer player = worldObj.getClosestPlayerToEntity(this, 16);
if (player != null && canEntityBeSeen(player)) {
return player;
} else {
return null;
}
}
public boolean canAttackEntity(Entity entity, int damage)
{
if (super.canAttackEntity(entity, damage)) {
if(riddenByEntity == entity || ridingEntity == entity) {
return true;
}
if(entity != this) {
playerToAttack = entity;
}
return true;
} else {
return false;
}
}
EntityMobs.canAttackEntity() defines that when a Mob is attacked, it will set its target (playerToAttack) to who attacks it.
The direct superclass of EntityMobs is EntityCreature which defines findPlayerToAttack() as a trivial function that returns null.
The playerToAttack is referenced as follows:
~/src/mcp25/alphasrc$ grep playerToAttack * -r minecraft/net/minecraft/src/EntitySpider.java: playerToAttack = null; minecraft/net/minecraft/src/EntityMobs.java: playerToAttack = entity; minecraft/net/minecraft/src/EntityPigZombie.java: field_9333_am = playerToAttack == null ? 0.5F : 0.95F; minecraft/net/minecraft/src/EntityPigZombie.java: playerToAttack = entity; minecraft/net/minecraft/src/EntityCreature.java: if(playerToAttack == null) minecraft/net/minecraft/src/EntityCreature.java: playerToAttack = findPlayerToAttack(); minecraft/net/minecraft/src/EntityCreature.java: if(playerToAttack != null) minecraft/net/minecraft/src/EntityCreature.java: pathToEntity = worldObj.getPathToEntity(this, playerToAttack, f); minecraft/net/minecraft/src/EntityCreature.java: if(!playerToAttack.isEntityAlive()) minecraft/net/minecraft/src/EntityCreature.java: playerToAttack = null; minecraft/net/minecraft/src/EntityCreature.java: float f1 = playerToAttack.getDistanceToEntity(this); minecraft/net/minecraft/src/EntityCreature.java: if(canEntityBeSeen(playerToAttack)) minecraft/net/minecraft/src/EntityCreature.java: attackEntity(playerToAttack, f1); minecraft/net/minecraft/src/EntityCreature.java: if(!hasAttacked && playerToAttack != null && (pathToEntity == null || rand.nextInt(20) == 0)) minecraft/net/minecraft/src/EntityCreature.java: pathToEntity = worldObj.getPathToEntity(this, playerToAttack, f); minecraft/net/minecraft/src/EntityCreature.java: if(hasAttacked && playerToAttack != null) minecraft/net/minecraft/src/EntityCreature.java: double d4 = playerToAttack.posX - posX; minecraft/net/minecraft/src/EntityCreature.java: double d5 = playerToAttack.posZ - posZ; minecraft/net/minecraft/src/EntityCreature.java: if(playerToAttack != null) minecraft/net/minecraft/src/EntityCreature.java: faceEntity(playerToAttack, 30F); minecraft/net/minecraft/src/EntityCreature.java: protected Entity playerToAttack;
Only these are assignments to playerToAttack:
minecraft/net/minecraft/src/EntitySpider.java: playerToAttack = null; minecraft/net/minecraft/src/EntityMobs.java: playerToAttack = entity; minecraft/net/minecraft/src/EntityPigZombie.java: playerToAttack = entity; minecraft/net/minecraft/src/EntityCreature.java: playerToAttack = findPlayerToAttack(); minecraft/net/minecraft/src/EntityCreature.java: playerToAttack = null;
where EntitySpider is unrelated to this analysis, and the assignment in EntityMobs and EntityPigZombie has been listed above.
The only two left assignments:
EntityCreature.java:
...
if (playerToAttack == null) {
playerToAttack = findPlayerToAttack();
if (playerToAttack != null) {
pathToEntity = worldObj.getPathToEntity(this, playerToAttack, f);
}
} else if(!playerToAttack.isEntityAlive()) {
playerToAttack = null;
} else
...
So when the playerToAttack is already set to non-null entity, the findPlayerToAttack() is actually not called.
And this is possible to happen in EntityMobs.canAttackEntity().
A thought experiment of how Pigmen turn hostile to Ghasts[]
When a fireball launched by a Ghast hits a Pigman:
EntityFireball.java:
if (movingobjectposition != null) {
if (movingobjectposition.entityHit != null) {
if (!movingobjectposition.entityHit.canAttackEntity(launchedBy, 0));//launchedBy: field_9397_j
}
worldObj.createExplosion(null, posX, posY, posZ, 1.0F, true);//createExplosion: func_12244_a
setEntityDead();
}
Then EntityPigZombie.canAttackEntity() is called, as the attacking entity is not EntityPlayer, so EntityMobs.canAttackEntity() is called.
In EntityMobs.canAttackEntity(), if this attack is successful, playerToAttack will be set to the Ghast, and will not be subsequently affected by findPlayerToAttack().
This is how Pigmen turn hostile to Ghasts.
Fire Immunity[]
Fire damage only takes place in three locations:
Entity.java:
...
if (worldObj.isBoundingBoxBurning(boundingBox)) {
func_355_a(1);
if(!flag2) {
fire++;
if (fire == 0) {
fire = 300;
}
}
} else
...
if (fire > 0) {
if (isImmuneToFire) {
fire -= 4;
if (fire < 0)
fire = 0;
} else {
if(fire % 20 == 0)
canAttackEntity(null, 1);
fire--;
}
}
if (handleLavaMovement()) {
func_4038_J();
}
where func_355_a() and func_4038_J() are:
Entity.java:
protected void func_355_a(int i)
{
if (!isImmuneToFire) {
canAttackEntity(null, i);
}
}
protected void func_4038_J()
{
if (!isImmuneToFire) {
canAttackEntity(null, 4);
fire = 600;
}
}
All of fire damage is controlled by isImmuneToFire flag which is true for EntityPigZombie.
Feather drops[]
On living entities death:
EntityLiving.java:
public void onDeath(Entity entity)
{
...
int i = getDropItemId();
if (i > 0) {
int j = rand.nextInt(3);
for(int k = 0; k < j; k++) {
dropItem(i, 1);
}
}
...
drop 0-2 items of getDropItemId() which is:
EntityPigZombie.java:
protected int getDropItemId()
{
return Item.porkCooked.shiftedIndex;
}