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.block.custom.AntBlock;
4import net.lerariemann.infinity.util.VersionMethods;
5import net.lerariemann.infinity.util.core.NbtUtils;
6import net.lerariemann.infinity.util.var.BishopBattle;
7import net.minecraft.core.BlockPos;
8import net.minecraft.core.Direction;
9import net.minecraft.nbt.CompoundTag;
10import net.minecraft.resources.ResourceKey;
11import net.minecraft.server.level.ServerLevel;
12import net.minecraft.sounds.SoundEvent;
13import net.minecraft.sounds.SoundEvents;
14import net.minecraft.tags.FluidTags;
15import net.minecraft.world.InteractionHand;
16import net.minecraft.world.InteractionResult;
17import net.minecraft.world.damagesource.DamageSource;
18import net.minecraft.world.entity.Entity;
19import net.minecraft.world.entity.EntityType;
20import net.minecraft.world.entity.LivingEntity;
21import net.minecraft.world.entity.Mob;
22import net.minecraft.world.entity.ai.attributes.AttributeSupplier;
23import net.minecraft.world.entity.ai.attributes.Attributes;
24import net.minecraft.world.entity.ai.goal.FloatGoal;
25import net.minecraft.world.entity.ai.goal.Goal;
26import net.minecraft.world.entity.ai.goal.LookAtPlayerGoal;
27import net.minecraft.world.entity.ai.goal.RandomLookAroundGoal;
28import net.minecraft.world.entity.ai.goal.WaterAvoidingRandomStrollGoal;
29import net.minecraft.world.entity.ai.goal.target.NearestAttackableTargetGoal;
30import net.minecraft.world.entity.monster.Monster;
31import net.minecraft.world.entity.player.Player;
32import net.minecraft.world.item.ItemStack;
33import net.minecraft.world.level.Level;
34import net.minecraft.world.level.block.Block;
35import net.minecraft.world.level.block.state.BlockState;
36import net.minecraft.world.level.material.FluidState;
37//? if >1.21.4 {
38import net.minecraft.world.level.storage.ValueInput;
39import net.minecraft.world.level.storage.ValueOutput;
40//?}
41import net.minecraft.world.level.storage.loot.LootParams;
42import net.minecraft.world.level.storage.loot.LootTable;
43import net.minecraft.world.phys.Vec3;
44import net.minecraft.world.phys.shapes.CollisionContext;
45import net.minecraft.world.phys.shapes.VoxelShape;
46import org.jetbrains.annotations.Nullable;
47
48import java.util.Optional;
49import java.util.function.BiConsumer;
50import java.util.function.Function;
51
52public class AntEntity extends AbstractChessFigure {
53 @Nullable
54 protected BlockPos lastChangedPos;
55 private Direction direction;
56 private boolean dropsLoot;
57
58 public AntEntity(EntityType<? extends AbstractChessFigure> entityType, Level world) {
59 super(entityType, world);
60 direction = Direction.EAST;
61 dropsLoot = true;
62 }
63
64 @Override
65 public boolean isBlackOrWhite() {
66 return true;
67 }
68 //? if <1.21.2 {
69 /*@Override
70 public boolean shouldDropLoot() {
71 return dropsLoot;
72 }
73 *///?} else {
74 @Override
75 protected boolean dropFromLootTable(ServerLevel level, ResourceKey<LootTable> lootTable, Function<LootParams.Builder, LootParams> paramsBuilder, BiConsumer<ServerLevel, ItemStack> dropConsumer) {
76 if (dropsLoot)
77 return super.dropFromLootTable(level, lootTable, paramsBuilder, dropConsumer);
78 return false;
79 }
80 //?}
81
82 public void addToBattle(BishopBattle battle) {
83 battle.addEntity(this);
84 dropsLoot = false;
85 }
86
87 @Override
88 protected SoundEvent getAmbientSound() {
89 return null;
90 }
91 @Override
92 protected SoundEvent getHurtSound(DamageSource source) {
93 return SoundEvents.SILVERFISH_HURT;
94 }
95 @Override
96 protected SoundEvent getDeathSound() {
97 return SoundEvents.SILVERFISH_DEATH;
98 }
99
100 @Override
101 protected void registerGoals() {
102 targetSelector.addGoal(2, new AntBattleGoal<>(this, Player.class, true));
103 goalSelector.addGoal(3, new AntBlockRecolorGoal(this));
104 super.registerGoals();
105 }
106 @Override
107 protected void initRegularGoals() {
108 goalSelector.addGoal(0, new FloatGoal(this));
109 goalSelector.addGoal(5, new WanderConditionalGoal(this, 1.0));
110 goalSelector.addGoal(6, new LookAtEntityConditionalGoal(this, Player.class, 6.0F));
111 goalSelector.addGoal(7, new LookAroundConditionalGoal(this));
112 }
113
114 public static AttributeSupplier.Builder createAttributes() {
115 return Monster.createMonsterAttributes()
116 .add(Attributes.MOVEMENT_SPEED, 0.1f)
117 .add(Attributes.MAX_HEALTH, 6);
118 }
119
120 @Override
121 public void readAdditionalSaveData(
122 //? if >1.21.2 {
123 ValueInput
124 //?} else {
125 /*CompoundTag
126 *///?}
127 nbt) {
128 super.readAdditionalSaveData(nbt);
129 this.dropsLoot = NbtUtils.getBoolean(nbt, "dropsLoot", true);
130 this.direction = switch(NbtUtils.getString(nbt, "direction")) {
131 case "N" -> Direction.NORTH;
132 case "W" -> Direction.WEST;
133 case "S" -> Direction.SOUTH;
134 default -> Direction.EAST;
135 };
136 //? if >1.21.2 {
137 this.lastChangedPos = nbt.read("last_changed_pos", BlockPos.CODEC).orElse(lastChangedPos);
138 //?} else {
139 /*if (nbt.contains("last_changed_pos")) {
140 CompoundTag pos = NbtUtils.getCompound(nbt, "last_changed_pos");
141 this.lastChangedPos = new BlockPos(NbtUtils.getInt(pos, "x"), NbtUtils.getInt(nbt,"y"), NbtUtils.getInt(nbt, "z")); }
142 *///?}
143 }
144
145 @Override
146 public void addAdditionalSaveData(
147 //? if >1.21.2 {
148 ValueOutput
149 //?} else {
150 /*CompoundTag
151 *///?}
152 nbt) {
153 super.addAdditionalSaveData(nbt);
154 nbt.putBoolean("dropsLoot", dropsLoot);
155 nbt.putString("direction", switch(this.direction) {
156 case NORTH -> "N";
157 case WEST -> "W";
158 case SOUTH -> "S";
159 default -> "E";
160 });
161 if (lastChangedPos != null) {
162 //? if >1.21.2 {
163 nbt.store("last_changed_pos", BlockPos.CODEC, lastChangedPos);
164 //?} else {
165 /*CompoundTag pos = new CompoundTag();
166 pos.putInt("x", lastChangedPos.getX());
167 pos.putInt("y", lastChangedPos.getY());
168 pos.putInt("z", lastChangedPos.getZ());
169 nbt.put("last_changed_pos", pos);
170 *///?}
171 }
172 }
173
174 @Override
175 public boolean shouldPursueRegularGoals() {
176 Level w = level();
177 Optional<BlockPos> bp = mainSupportingBlockPos;
178 return (bp.isEmpty()
179 || isInBattle()
180 || !AntBlock.isSafeToRecolor(w, bp.get()))
181 && super.shouldPursueRegularGoals();
182 }
183
184 //? if >1.21 {
185 @Override
186 public boolean canBeLeashed() {
187 return !isInBattle();
188 }
189 //?}
190 @Override
191 public boolean canStandOnFluid(FluidState state) {
192 return state.is(FluidTags.WATER);
193 }
194
195 public static VoxelShape getWaterCollisionShape(int level) {
196 return Block.box(0.0, 0.0, 0.0, 16.0, level, 16.0);
197 }
198
199 @Override
200 public void aiStep() {
201 super.aiStep();
202 if (!this.firstTick && this.fluidHeight.getDouble(FluidTags.WATER) > 0.0) {
203 CollisionContext shapeContext = CollisionContext.of(this);
204 if (shapeContext.isAbove(getWaterCollisionShape(15),
205 this.blockPosition(), true)
206 && !this.level().getFluidState(this.blockPosition().above()).is(FluidTags.WATER)) {
207 this.setOnGround(true);
208 } else {
209 this.setDeltaMovement(this.getDeltaMovement().scale(0.5).add(0.0, 0.05, 0.0));
210 }
211 }
212 }
213
214 @Override
215 public InteractionResult mobInteract(Player player, InteractionHand hand) {
216 if (!this.isVehicle()
217 //? if >1.21
218 && this.getAttributeValue(Attributes.SCALE) > 2
219 && player.getItemInHand(hand).isEmpty()) {
220 this.putPlayerOnBack(player);
221 return VersionMethods.sidedSuccess(this.level().isClientSide());
222 }
223 return super.mobInteract(player, hand);
224 }
225 protected void putPlayerOnBack(Player player) {
226 if (!this.level().isClientSide()) {
227 player.setYRot(this.getYRot());
228 player.setXRot(this.getXRot());
229 player.startRiding(this);
230 }
231 }
232 @Override
233 protected Vec3 getRiddenInput(Player controllingPlayer, Vec3 movementInput) {
234 float f = controllingPlayer.zza;
235 return new Vec3(controllingPlayer.xxa * 0.5f, 0, f < 0 ? f*0.25f : f);
236 }
237 @Override
238 protected float getRiddenSpeed(Player controllingPlayer) {
239 return (float)this.getAttributeValue(Attributes.MOVEMENT_SPEED) * 0.8f;
240 }
241 @Nullable
242 @Override
243 public LivingEntity getControllingPassenger() {
244 if (this.getFirstPassenger() instanceof Player player) {
245 return player;
246 }
247 return super.getControllingPassenger();
248 }
249 @Override
250 protected void positionRider(Entity passenger, MoveFunction positionUpdater) {
251 super.positionRider(passenger, positionUpdater);
252 if (passenger instanceof LivingEntity) {
253 ((LivingEntity)passenger).yBodyRot = this.yBodyRot;
254 }
255 }
256 @Override
257 protected void tickRidden(Player controllingPlayer, Vec3 movementInput) {
258 super.tickRidden(controllingPlayer, movementInput);
259 this.setRot(controllingPlayer.getYRot(),controllingPlayer.getXRot() * 0.5F);
260 this.yRotO = this.yBodyRot = this.yHeadRot = this.getYRot();
261 }
262
263 public static class AntBattleGoal<T extends LivingEntity> extends NearestAttackableTargetGoal<T> {
264 public AntBattleGoal(Mob mob, Class<T> targetClass, boolean checkVisibility) {
265 super(mob, targetClass, checkVisibility);
266 }
267
268 @Override
269 public boolean canUse() {
270 if (mob instanceof AbstractChessFigure e && !e.isInBattle()) return false;
271 return super.canUse();
272 }
273 }
274
275 public static class AntBlockRecolorGoal extends Goal {
276 private final AntEntity mob;
277 @Nullable
278 private BlockPos targetPos;
279
280 public AntBlockRecolorGoal(AntEntity mob) {
281 this.mob = mob;
282 }
283
284 @Override
285 public boolean canUse() {
286 if (mob.shouldPursueRegularGoals()) return false;
287 Optional<BlockPos> bp = mob.mainSupportingBlockPos;
288 if (bp.isEmpty() || bp.get().equals(mob.lastChangedPos)) return false;
289 mob.lastChangedPos = bp.get();
290
291 BlockState down = mob.level().getBlockState(bp.get());
292 AntBlock.Clockwiseness cw = AntBlock.getCW(down.getBlock());
293 if (cw == null) return false;
294
295 Direction direction = mob.direction;
296 Direction direction2 = cw.equals(AntBlock.Clockwiseness.CW)
297 ? direction.getClockWise()
298 : direction.getCounterClockWise();
299
300 Block newBlock = AntBlock.recolor(down.getBlock(), cw.equals(AntBlock.Clockwiseness.CCW));
301 if (newBlock == null) return false;
302
303 targetPos = mob.blockPosition().relative(direction2);
304 mob.level().setBlock(bp.get(), newBlock.withPropertiesOf(down), 19);
305 mob.direction = direction2;
306 return true;
307 }
308
309 @Override
310 public boolean canContinueToUse() {
311 return false;
312 }
313
314 @Override
315 public void start() {
316 if (targetPos == null) return;
317 //? if >1.21 {
318 Vec3 v = targetPos.getBottomCenter();
319 //?} else {
320 /*Vec3 v = targetPos.getCenter();
321 *///?}
322 mob.randomTeleport(v.x, v.y, v.z, false);
323 mob.setYRot(mob.direction.toYRot());
324 }
325 }
326
327 public static class WanderConditionalGoal extends WaterAvoidingRandomStrollGoal {
328 private final AntEntity mob1;
329 public WanderConditionalGoal(AntEntity pathAwareEntity, double d) {
330 super(pathAwareEntity, d);
331 mob1 = pathAwareEntity;
332 }
333
334 @Override
335 public boolean canUse() {
336 return super.canUse() && mob1.shouldPursueRegularGoals();
337 }
338 }
339
340 public static class LookAroundConditionalGoal extends RandomLookAroundGoal {
341 private final AntEntity mob1;
342 public LookAroundConditionalGoal(AntEntity mob) {
343 super(mob);
344 mob1 = mob;
345 }
346
347 @Override
348 public boolean canUse() {
349 return super.canUse() && mob1.shouldPursueRegularGoals();
350 }
351 }
352
353 public static class LookAtEntityConditionalGoal extends LookAtPlayerGoal {
354 private final AntEntity mob1;
355
356 public LookAtEntityConditionalGoal(AntEntity mob, Class<? extends LivingEntity> targetType, float range) {
357 super(mob, targetType, range);
358 mob1 = mob;
359 }
360
361 @Override
362 public boolean canUse() {
363 return super.canUse() && mob1.shouldPursueRegularGoals();
364 }
365 }
366}