Inspired by 2020's April Fools' 20w14infinite Snapshot, this mod brings endless randomly generated dimensions into Minecraft.
at master 300 lines 11 kB view raw
1package net.lerariemann.infinity.entity.custom; 2 3import net.lerariemann.infinity.iridescence.Iridescence; 4import net.minecraft.core.BlockPos; 5import net.minecraft.nbt.CompoundTag; 6import net.minecraft.server.level.ServerLevel; 7import net.minecraft.sounds.SoundEvent; 8import net.minecraft.sounds.SoundEvents; 9import net.minecraft.tags.FluidTags; 10import net.minecraft.util.TimeUtil; 11import net.minecraft.world.damagesource.DamageSource; 12import net.minecraft.world.entity.EntitySelector; 13import net.minecraft.world.entity.EntityType; 14import net.minecraft.world.entity.LivingEntity; 15import net.minecraft.world.entity.Mob; 16import net.minecraft.world.entity.NeutralMob; 17import net.minecraft.world.entity.PathfinderMob; 18import net.minecraft.world.entity.ai.attributes.Attributes; 19import net.minecraft.world.entity.ai.goal.Goal; 20import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal; 21import net.minecraft.world.entity.ai.goal.MeleeAttackGoal; 22import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal; 23import net.minecraft.world.entity.ai.goal.WaterAvoidingRandomStrollGoal; 24import net.minecraft.world.entity.ai.goal.target.HurtByTargetGoal; 25import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal; 26import net.minecraft.world.entity.monster.Monster; 27import net.minecraft.world.entity.player.Player; 28import net.minecraft.world.level.GameRules; 29import net.minecraft.world.level.Level; 30import net.minecraft.world.level.LevelReader; 31//? if >1.21.4 { 32import net.minecraft.world.level.storage.ValueInput; 33import net.minecraft.world.level.storage.ValueOutput; 34//?} 35import net.minecraft.world.phys.AABB; 36import net.minecraft.world.scores.Team; 37import org.jetbrains.annotations.Nullable; 38 39import java.util.EnumSet; 40import java.util.List; 41import java.util.UUID; 42 43public abstract class AbstractChessFigure extends Monster implements NeutralMob { 44 protected int angerTime; 45 @Nullable 46 protected UUID angryAt; 47 48 protected AbstractChessFigure(EntityType<? extends AbstractChessFigure> entityType, Level world) { 49 super(entityType, world); 50 } 51 52 public abstract boolean isBlackOrWhite(); 53 54 @Override 55 protected void registerGoals() { 56 initRegularGoals(); 57 initChessGoals(); 58 initAttackType(); 59 } 60 protected void initAttackType() { 61 this.goalSelector.addGoal(2, new MeleeAttackGoal(this, 1.0, false)); 62 } 63 protected void initChessGoals() { 64 this.targetSelector.addGoal(1, new ChessRevengeGoal(this).setAlertOthers()); 65 this.targetSelector.addGoal(3, new ChaosCleanseGoal<>(this, ChaosSlime.class, true)); 66 this.targetSelector.addGoal(3, new ChaosCleanseGoal<>(this, ChaosSkeleton.class, true)); 67 this.targetSelector.addGoal(3, new ChessUniversalAngerGoal(this)); 68 } 69 protected void initRegularGoals() { 70 this.goalSelector.addGoal(0, new SwimWithVehicleGoal(this)); 71 this.goalSelector.addGoal(7, new WaterAvoidingRandomStrollGoal(this, 1.0)); 72 this.goalSelector.addGoal(8, new LookAtPlayerGoal(this, Player.class, 8.0f)); 73 this.goalSelector.addGoal(8, new RandomLookAroundGoal(this)); 74 } 75 76 @Override 77 public void setRemainingPersistentAngerTime(int angerTime) { 78 this.angerTime = angerTime; 79 } 80 @Override 81 public int getRemainingPersistentAngerTime() { 82 return this.angerTime; 83 } 84 @Override 85 public void setPersistentAngerTarget(@Nullable UUID angryAt) { 86 this.angryAt = angryAt; 87 } 88 @Override 89 @Nullable 90 public UUID getPersistentAngerTarget() { 91 return this.angryAt; 92 } 93 @Override 94 public void startPersistentAngerTimer() { 95 this.setRemainingPersistentAngerTime(TimeUtil.rangeOfSeconds(20, 40).sample(this.random)); 96 } 97 98 public boolean isInBattle() { 99 return isInBattle("battle"); 100 } 101 public boolean isInBattle(String battleName) { 102 Team t = getTeam(); 103 return (t != null && t.getName().contains(battleName)); 104 } 105 106 @Override 107 public void addAdditionalSaveData( 108 //? if >1.21.2 { 109 ValueOutput 110 //?} else { 111 /*CompoundTag 112 *///?} 113 nbt) { 114 super.addAdditionalSaveData(nbt); 115 this.addPersistentAngerSaveData(nbt); 116 } 117 @Override 118 public void readAdditionalSaveData( 119 //? if >1.21.2 { 120 ValueInput 121 //?} else { 122 /*CompoundTag 123 *///?} 124 nbt) { 125 super.readAdditionalSaveData(nbt); 126 this.readPersistentAngerSaveData(this.level(), nbt); 127 } 128 129 @Override 130 protected void customServerAiStep( 131 //? if >1.21.4 { 132 ServerLevel serverLevel 133 //?} 134 ) { 135 //? if <1.21.4 { 136 /*var serverLevel = (ServerLevel)this.level(); 137 *///?} 138 this.updatePersistentAnger(serverLevel, false); 139 super.customServerAiStep( 140 //? if >1.21.4 { 141 serverLevel 142 //?} 143 ); 144 } 145 146 @Override 147 public float getWalkTargetValue(BlockPos pos, LevelReader world) { 148 if (!isBlackOrWhite()) return 0.0f; 149 if (Iridescence.isIridescence(world, pos)) return -1.0F; 150 return 0.0f; 151 } 152 153 @Override 154 protected SoundEvent getAmbientSound() { 155 return isBlackOrWhite() ? SoundEvents.PLAYER_BREATH : SoundEvents.AMETHYST_BLOCK_CHIME; 156 } 157 158 @Override 159 protected SoundEvent getHurtSound(DamageSource source) { 160 return isBlackOrWhite() ? SoundEvents.PLAYER_HURT : SoundEvents.AMETHYST_BLOCK_HIT; 161 } 162 163 @Override 164 protected SoundEvent getDeathSound() { 165 return isBlackOrWhite() ? SoundEvents.PLAYER_DEATH : SoundEvents.AMETHYST_BLOCK_BREAK; 166 } 167 168 public boolean shouldPursueRegularGoals() { 169 return (!Iridescence.isUnderEffect(this)); 170 } 171 public boolean shouldPursueChessGoals() { 172 return shouldPursueRegularGoals() && isBlackOrWhite(); 173 } 174 175 public static boolean isAngerCompatible(AbstractChessFigure fig1, AbstractChessFigure fig2) { 176 if (fig1 instanceof ChaosPawn p1 && fig2 instanceof ChaosPawn p2) return p1.getCase() == p2.getCase(); 177 return fig1.isBlackOrWhite() ^ !fig2.isBlackOrWhite(); 178 } 179 180 public static class SwimWithVehicleGoal extends Goal { 181 private final Mob mob; 182 183 public SwimWithVehicleGoal(Mob mob) { 184 this.mob = mob; 185 this.setFlags(EnumSet.of(Flag.JUMP)); 186 mob.getNavigation().setCanFloat(true); 187 } 188 189 @Override 190 public void start() { 191 if (mob.getControlledVehicle() instanceof Mob vehicle) { 192 vehicle.getNavigation().setCanFloat(true); 193 } 194 } 195 196 @Override 197 public boolean canUse() { 198 if (mob.getControlledVehicle() instanceof Mob vehicle) { 199 return shouldMobSwim(vehicle); 200 } 201 return shouldMobSwim(mob); 202 } 203 204 public static boolean shouldMobSwim(Mob mob) { 205 return mob.isInWater() && mob.getFluidHeight(FluidTags.WATER) > mob.getFluidJumpThreshold() || mob.isInLava(); 206 } 207 208 @Override 209 public boolean requiresUpdateEveryTick() { 210 return true; 211 } 212 213 @Override 214 public void tick() { 215 if (mob.getRandom().nextFloat() < 0.8F) { 216 if (mob.getControlledVehicle() instanceof Mob vehicle) { 217 vehicle.getJumpControl().jump(); 218 } 219 mob.getJumpControl().jump(); 220 } 221 } 222 } 223 224 public static class ChaosCleanseGoal<T extends LivingEntity> extends NearestAttackableTargetGoal<T> { 225 public ChaosCleanseGoal(Mob mob, Class<T> targetClass, boolean checkVisibility) { 226 super(mob, targetClass, checkVisibility); 227 } 228 229 @Override 230 public boolean canUse() { 231 if (mob instanceof AbstractChessFigure e && !e.shouldPursueChessGoals()) return false; 232 return super.canUse(); 233 } 234 } 235 236 public static class ChessUniversalAngerGoal extends Goal { 237 private final AbstractChessFigure mob; 238 private int lastAttackedTime; 239 240 public ChessUniversalAngerGoal(AbstractChessFigure mob) { 241 this.mob = mob; 242 } 243 244 @Override 245 public boolean canUse() { 246 return ((ServerLevel) this.mob.level()).getGameRules().getBoolean(GameRules.RULE_UNIVERSAL_ANGER) && this.canStartUniversalAnger(); 247 } 248 249 private boolean canStartUniversalAnger() { 250 return this.mob.getLastHurtByMob() != null 251 && this.mob.getLastHurtByMob().getType() == EntityType.PLAYER 252 && this.mob.getLastHurtByMobTimestamp() > this.lastAttackedTime 253 && this.mob.shouldPursueRegularGoals(); 254 } 255 256 @Override 257 public void start() { 258 this.lastAttackedTime = this.mob.getLastHurtByMobTimestamp(); 259 this.mob.forgetCurrentTargetAndRefreshUniversalAnger(); 260 this.getOthersInRange().stream().filter(entity -> { 261 if (entity == mob) return false; 262 if (Iridescence.isUnderEffect(entity)) return false; 263 if (entity instanceof AbstractChessFigure) { 264 return isAngerCompatible(mob, entity); 265 } 266 return true; 267 }).map(entity -> (NeutralMob)entity).forEach(NeutralMob::forgetCurrentTargetAndRefreshUniversalAnger); 268 super.start(); 269 } 270 271 private List<? extends AbstractChessFigure> getOthersInRange() { 272 double d = this.mob.getAttributeValue(Attributes.FOLLOW_RANGE); 273 AABB box = AABB.unitCubeFromLowerCorner(this.mob.position()).inflate(d, 10.0, d); 274 return this.mob.level().getEntitiesOfClass(this.mob.getClass(), box, EntitySelector.NO_SPECTATORS); 275 } 276 } 277 278 public static class ChessRevengeGoal extends HurtByTargetGoal { 279 public ChessRevengeGoal(PathfinderMob mob, Class<?>... noRevengeTypes) { 280 super(mob, noRevengeTypes); 281 } 282 283 @Override 284 protected void alertOthers() { 285 if (mob instanceof AbstractChessFigure figure && figure.shouldPursueRegularGoals()) { 286 double d = this.getFollowDistance(); 287 AABB box = AABB.unitCubeFromLowerCorner(figure.position()).inflate(d, 10.0, d); 288 List<AbstractChessFigure> list = figure.level().getEntitiesOfClass(AbstractChessFigure.class, box, EntitySelector.NO_SPECTATORS); 289 for (AbstractChessFigure pawn2 : list) { 290 if (figure != pawn2 291 && pawn2.getTarget() == null 292 && !pawn2.isAlliedTo(figure.getLastHurtByMob()) 293 && !Iridescence.isUnderEffect(pawn2) 294 && isAngerCompatible(pawn2, figure)) 295 this.alertOther(pawn2, figure.getLastHurtByMob()); 296 } 297 } 298 } 299 } 300}