Inspired by 2020's April Fools' 20w14infinite Snapshot, this mod brings endless randomly generated dimensions into Minecraft.
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}