package playasmob;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.GameRules;
import net.minecraft.world.item.Items;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Item;
import net.minecraft.world.food.Foods;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.npc.Villager;
import net.minecraft.world.entity.monster.ZombieVillager;
import net.minecraft.world.entity.monster.Zombie;
import net.minecraft.world.entity.monster.Creeper;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
import net.minecraft.world.entity.SpawnPlacements;
import net.minecraft.world.entity.Pose;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.EquipmentSlot;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.EntitySpawnReason;
import net.minecraft.world.entity.EntityDimensions;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.ConversionParams;
import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.Difficulty;
import net.minecraft.util.Mth;
import net.minecraft.tags.FluidTags;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.nbt.NbtOps;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.core.BlockPos;
import net.minecraft.client.renderer.entity.state.ZombieRenderState;
import net.minecraft.client.renderer.entity.state.EntityRenderState;
import net.minecraft.client.renderer.entity.ZombieRenderer;
import net.minecraft.client.renderer.entity.EntityRenderer;
public class ZombieInfo extends MonsterInfo implements MaturableInfo {
public static final ResourceLocation ChargeId = ResourceLocation.withDefaultNamespace("reinforcement_caller_charge");
public static final AttributeModifier ChargeMod = new AttributeModifier(ChargeId, -0.05F, AttributeModifier.Operation.ADD_VALUE);
public static final EntityDimensions BabyHitbox = EntityType.ZOMBIE.getDimensions().scale(0.5F).withEyeHeight(0.93F);
public static final Synched specialType = Synched.SpecialType;
public static final Synched waterConverting = Synched.DrownedConverting;
public int inWaterTime;
public int conversionTime;
public ZombieInfo(EntityType type, Class typeClass, Player player, CompoundTag compound) {
super(type, typeClass, player, compound);
}
public ZombieInfo(Player player, CompoundTag compound) {
this(EntityType.ZOMBIE, Zombie.class, player, compound);
}
public ZombieInfo(Player player) {
this(player, null);
}
public ZombieInfo() {
this(null);
}
// Custom Code
@Override
public void reset() {
super.reset();
this.reset(specialType);
this.reset(waterConverting);
this.inWaterTime = 0;
this.conversionTime = 0;
}
@Override
public float babySpawnChance() {
return 0.05f;
}
@Override
public void setupAttributes() {
super.setupAttributes();
this.randomizeReinforcementsChance();
}
@Override
public EntityRenderer getRenderer() {
return new ZombieRenderer(Utils.getContext());
}
@Override
public EntityRenderState createState() {
return new ZombieRenderState();
}
@Override
public void modifyState(EntityRenderState state) {
super.modifyState(state);
if (state instanceof ZombieRenderState zombieState) {
zombieState.isAggressive = this.isAggressive();
zombieState.isConverting = this.isUnderWaterConverting();
}
}
@Override
public boolean canTradeWith(Entity entity) {
return entity instanceof ZombieVillager;
}
@Override
public Diet getDiet() {
Diet diet = Diet.carnivore(true);
diet.addFood(Items.ROTTEN_FLESH, true, Foods.BREAD);
diet.addFood(Items.SPIDER_EYE, true, Foods.GOLDEN_CARROT);
diet.addFood(Items.POISONOUS_POTATO, true, Foods.BAKED_POTATO);
return diet;
}
@Override
public float getSpeed(float original) {
float baseSpeed = super.getSpeed(original);
return this.isBaby() ? baseSpeed * 1.5f : baseSpeed;
}
@Override
public float getAgeScale(float original) {
return 1f;
}
public void resetDrownedConverting() {
this.inWaterTime = -1;
this.conversionTime = 0;
this.set(waterConverting, false);
}
// Zombie.class Code
public boolean isUnderWaterConverting() {
return this.get(waterConverting);
}
public boolean convertsInWater() {
return true;
}
@Override
public void preTick() {
if (!this.level().isClientSide && this.isAlive() && this.convertsInWater()) {
if (this.isUnderWaterConverting()) {
if (this.conversionTime-- < 0)
this.doUnderWaterConversion();
} else {
if (this.isEyeInFluid(FluidTags.WATER)) {
if (this.inWaterTime++ >= 600)
this.startUnderWaterConversion(300);
} else {
this.inWaterTime = -1;
}
}
}
super.preTick();
}
@Override
public boolean aiStep(Object object) {
if (this.isAlive()) {
boolean shouldBurn = this.isSunSensitive() && this.isSunBurnTick();
if (shouldBurn) {
ItemStack itemstack = this.getItemBySlot(EquipmentSlot.HEAD);
if (!itemstack.isEmpty()) {
if (itemstack.isDamageableItem()) {
Item item = itemstack.getItem();
itemstack.setDamageValue(itemstack.getDamageValue() + this.random.nextInt(2));
if (itemstack.getDamageValue() >= itemstack.getMaxDamage()) {
this.onEquippedItemBroken(item, EquipmentSlot.HEAD);
this.setItemSlot(EquipmentSlot.HEAD, ItemStack.EMPTY);
}
}
shouldBurn = false;
}
if (shouldBurn)
this.igniteForSeconds(8);
}
}
return super.aiStep(null);
}
public void startUnderWaterConversion(int time) {
this.conversionTime = time;
this.set(waterConverting, true);
}
public void doUnderWaterConversion() {
this.convertToZombieType(EntityType.DROWNED);
if (!this.isSilent())
this.level().levelEvent(null, 1040, this.blockPosition(), 0);
}
public void convertToZombieType(EntityType<? extends Zombie> type) {
this.resetDrownedConverting();
this.changeInfo(type, this.saveData());
}
public boolean convertVillagerToZombieVillager(ServerLevel server, Villager villager) {
ZombieVillager zombieVillager = villager.convertTo(EntityType.ZOMBIE_VILLAGER, ConversionParams.single(villager, true, true), zombie -> {
zombie.finalizeSpawn(server, server.getCurrentDifficultyAt(zombie.blockPosition()), EntitySpawnReason.CONVERSION, new Zombie.ZombieGroupData(false, true));
zombie.setVillagerData(villager.getVillagerData());
zombie.setGossips(villager.getGossips().store(NbtOps.INSTANCE));
zombie.setTradeOffers(villager.getOffers().copy());
zombie.setVillagerXp(villager.getVillagerXp());
net.neoforged.neoforge.event.EventHooks.onLivingConvert(villager, zombie);
if (!this.isSilent())
server.levelEvent(null, 1026, this.blockPosition(), 0);
});
return zombieVillager != null;
}
public boolean isSunSensitive() {
return true;
}
@Override
public boolean hurtServer(boolean original, ServerLevel server, DamageSource source, float damage) {
if (!super.hurtServer(original, server, source, damage))
return false;
LivingEntity target = this.getTarget();
if (target == null && source.getEntity() instanceof LivingEntity attacker)
target = attacker;
if (target != null && server.getDifficulty() == Difficulty.HARD && (double) this.random.nextFloat() < this.getAttributeValue(Attributes.SPAWN_REINFORCEMENTS_CHANCE) && server.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING)) {
int i = Mth.floor(this.getX());
int j = Mth.floor(this.getY());
int k = Mth.floor(this.getZ());
EntityType<? extends Zombie> entitytype = this.getType(null);
Zombie zombie = entitytype.create(server, EntitySpawnReason.REINFORCEMENT);
if (zombie == null)
return false;
for (int l = 0; l < 50; l++) {
int i1 = i + Mth.nextInt(this.random, 7, 40) * Mth.nextInt(this.random, -1, 1);
int j1 = j + Mth.nextInt(this.random, 7, 40) * Mth.nextInt(this.random, -1, 1);
int k1 = k + Mth.nextInt(this.random, 7, 40) * Mth.nextInt(this.random, -1, 1);
BlockPos blockpos = new BlockPos(i1, j1, k1);
if (SpawnPlacements.isSpawnPositionOk(entitytype, server, blockpos) && SpawnPlacements.checkSpawnRules(entitytype, server, EntitySpawnReason.REINFORCEMENT, blockpos, server.random)) {
zombie.setPos((double) i1, (double) j1, (double) k1);
if (!server.hasNearbyAlivePlayer((double) i1, (double) j1, (double) k1, 7.0) && server.isUnobstructed(zombie) && server.noCollision(zombie) && (this.canSpawnInLiquids() || !server.containsAnyLiquid(zombie.getBoundingBox()))) {
zombie.setTarget(target);
zombie.finalizeSpawn(server, server.getCurrentDifficultyAt(zombie.blockPosition()), EntitySpawnReason.REINFORCEMENT, null);
server.addFreshEntityWithPassengers(zombie);
AttributeInstance attributeinstance = this.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE);
AttributeModifier attributemodifier = attributeinstance.getModifier(ChargeId);
double d0 = attributemodifier != null ? attributemodifier.amount() : 0.0;
attributeinstance.removeModifier(ChargeId);
attributeinstance.addPermanentModifier(new AttributeModifier(ChargeId, d0 - 0.05, AttributeModifier.Operation.ADD_VALUE));
zombie.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).addPermanentModifier(ChargeMod);
break;
}
}
}
}
return true;
}
@Override
public SoundEvent getAmbientSound() {
return SoundEvents.ZOMBIE_AMBIENT;
}
@Override
public SoundEvent getHurtSound(SoundEvent original, DamageSource source) {
return SoundEvents.ZOMBIE_HURT;
}
@Override
public SoundEvent getDeathSound(SoundEvent original) {
return SoundEvents.ZOMBIE_DEATH;
}
public SoundEvent getStepSound() {
return SoundEvents.ZOMBIE_STEP;
}
@Override
public boolean playStepSound(Object object, BlockPos pos, BlockState state) {
this.playSound(this.getStepSound(), 0.15f, 1);
return false;
}
@Override
public EntityType<? extends Zombie> getType(EntityType original) {
return (EntityType<? extends Zombie>) super.getType(original);
}
public boolean canSpawnInLiquids() {
return false;
}
@Override
public ItemStack spawnedMainHandStack() {
if (this.random.nextFloat() < (this.level().getDifficulty() == Difficulty.HARD ? 0.05F : 0.01F)) {
int i = this.random.nextInt(3);
if (i == 0)
return new ItemStack(Items.IRON_SWORD);
return new ItemStack(Items.IRON_SHOVEL);
}
return super.spawnedMainHandStack();
}
@Override
public CompoundTag saveData() {
CompoundTag compound = super.saveData();
compound.putInt("InWaterTime", this.isInWater() ? this.inWaterTime : -1);
compound.putInt("DrownedConversionTime", this.isUnderWaterConverting() ? this.conversionTime : -1);
return compound;
}
@Override
public void loadData(CompoundTag compound) {
super.loadData(compound);
this.inWaterTime = compound.getInt("InWaterTime");
if (compound.contains("DrownedConversionTime", 99) && compound.getInt("DrownedConversionTime") > -1)
this.startUnderWaterConversion(compound.getInt("DrownedConversionTime"));
}
@Override
public boolean killedEntity(boolean original, ServerLevel server, LivingEntity victim) {
boolean flag = super.killedEntity(original, server, victim);
DamageSource source = victim.getLastDamageSource();
boolean isClose = this.closerThan(victim, 5);
if (!isClose || (source != null && !source.isDirect()))
return flag;
if ((server.getDifficulty() == Difficulty.NORMAL || server.getDifficulty() == Difficulty.HARD) && victim instanceof Villager villager && net.neoforged.neoforge.event.EventHooks.canLivingConvert(victim, EntityType.ZOMBIE_VILLAGER, (timer) -> {
})) {
if (server.getDifficulty() != Difficulty.HARD && this.random.nextBoolean())
return flag;
if (this.convertVillagerToZombieVillager(server, villager))
flag = false;
}
return flag;
}
@Override
public EntityDimensions getDimensions(Pose pose) {
return this.isBaby() ? this.dynamicHitbox(BabyHitbox, pose) : super.getDimensions(pose);
}
public void setInWaterTime(int time) {
this.inWaterTime = time;
}
public void setConversionTime(int time) {
this.conversionTime = time;
}
public void randomizeReinforcementsChance() {
if (this.isBound())
this.player.getAttribute(Attributes.SPAWN_REINFORCEMENTS_CHANCE).setBaseValue(this.random.nextDouble() * 0.1F);
}
@Override
public boolean dropCustomDeathLoot(Object object, ServerLevel server, DamageSource source, boolean booleanValue) {
if (!super.dropCustomDeathLoot(null, server, source, booleanValue))
return false;
ItemStack skull = this.getSkull();
if (!skull.isEmpty()) {
if (source.getEntity() instanceof Creeper creeper && creeper.canDropMobsSkull()) {
creeper.increaseDroppedSkulls();
this.spawnAtLocation(server, skull);
} else if (source.getEntity() instanceof InfoHolder holder && holder.getInfo() instanceof CreeperInfo creeper && creeper.canDropMobsSkull()) {
creeper.increaseDroppedSkulls();
this.spawnAtLocation(server, skull);
}
}
return true;
}
public ItemStack getSkull() {
return new ItemStack(Items.ZOMBIE_HEAD);
}
}