]> git.lizzy.rs Git - LightOverlay.git/blob - common/src/main/java/me/shedaniel/lightoverlay/common/LightOverlayTicker.java
Update to Forge 1.18 and fix #128
[LightOverlay.git] / common / src / main / java / me / shedaniel / lightoverlay / common / LightOverlayTicker.java
1 package me.shedaniel.lightoverlay.common;
2
3 import com.google.common.base.Suppliers;
4 import com.google.common.collect.Maps;
5 import it.unimi.dsi.fastutil.longs.Long2ByteMap;
6 import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap;
7 import net.minecraft.client.Minecraft;
8 import net.minecraft.client.multiplayer.ClientLevel;
9 import net.minecraft.client.player.LocalPlayer;
10 import net.minecraft.core.BlockPos;
11 import net.minecraft.core.Direction;
12 import net.minecraft.tags.BlockTags;
13 import net.minecraft.util.Mth;
14 import net.minecraft.world.entity.Entity;
15 import net.minecraft.world.entity.EntityType;
16 import net.minecraft.world.entity.MobCategory;
17 import net.minecraft.world.level.BlockGetter;
18 import net.minecraft.world.level.Level;
19 import net.minecraft.world.level.LightLayer;
20 import net.minecraft.world.level.biome.Biome;
21 import net.minecraft.world.level.block.Block;
22 import net.minecraft.world.level.block.state.BlockState;
23 import net.minecraft.world.level.chunk.ChunkStatus;
24 import net.minecraft.world.level.chunk.LevelChunk;
25 import net.minecraft.world.level.lighting.LayerLightEventListener;
26 import net.minecraft.world.phys.shapes.CollisionContext;
27 import net.minecraft.world.phys.shapes.VoxelShape;
28 import org.apache.logging.log4j.LogManager;
29
30 import java.util.*;
31 import java.util.concurrent.Executors;
32 import java.util.concurrent.ThreadPoolExecutor;
33 import java.util.function.Supplier;
34
35 public class LightOverlayTicker {
36     private final Minecraft minecraft = Minecraft.getInstance();
37     private long ticks = 0;
38     private static int threadNumber = 0;
39     private static final ThreadPoolExecutor EXECUTOR = (ThreadPoolExecutor) Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), r -> {
40         Thread thread = new Thread(r, "light-overlay-" + threadNumber++);
41         thread.setDaemon(true);
42         return thread;
43     });
44     public final Set<CubicChunkPos> POS = Collections.synchronizedSet(new HashSet<>());
45     public final Set<CubicChunkPos> CALCULATING_POS = Collections.synchronizedSet(new HashSet<>());
46     public final Map<CubicChunkPos, Long2ByteMap> CHUNK_MAP = Maps.newConcurrentMap();
47     private static final Supplier<EntityType<Entity>> TESTING_ENTITY_TYPE = Suppliers.memoize(() ->
48             EntityType.Builder.createNothing(MobCategory.MONSTER).sized(0f, 0f).noSave().build(null));
49     
50     public void queueChunk(CubicChunkPos pos) {
51         if (LightOverlay.enabled && LightOverlay.caching && !CALCULATING_POS.contains(pos)) {
52             POS.add(pos);
53         }
54     }
55     
56     public void tick(Minecraft minecraft) {
57         while (LightOverlay.enableOverlay.consumeClick())
58             LightOverlay.enabled = !LightOverlay.enabled;
59         
60         try {
61             ticks++;
62             if (minecraft.player == null || !LightOverlay.enabled) {
63                 POS.clear();
64                 CALCULATING_POS.clear();
65                 EXECUTOR.getQueue().clear();
66                 CHUNK_MAP.clear();
67             } else {
68                 LocalPlayer player = minecraft.player;
69                 ClientLevel world = minecraft.level;
70                 CollisionContext collisionContext = CollisionContext.of(player);
71                 
72                 if (!LightOverlay.caching) {
73                     CALCULATING_POS.clear();
74                     POS.clear();
75                     CHUNK_MAP.clear();
76                     BlockPos playerPos = player.blockPosition();
77                     LayerLightEventListener block = world.getLightEngine().getLayerListener(LightLayer.BLOCK);
78                     LayerLightEventListener sky = LightOverlay.showNumber ? null : world.getLightEngine().getLayerListener(LightLayer.SKY);
79                     BlockPos.MutableBlockPos downPos = new BlockPos.MutableBlockPos();
80                     Iterable<BlockPos> iterate = BlockPos.betweenClosed(playerPos.getX() - LightOverlay.reach, playerPos.getY() - LightOverlay.reach, playerPos.getZ() - LightOverlay.reach,
81                             playerPos.getX() + LightOverlay.reach, playerPos.getY() + LightOverlay.reach, playerPos.getZ() + LightOverlay.reach);
82                     Long2ByteMap chunkData = new Long2ByteOpenHashMap();
83                     CHUNK_MAP.put(new CubicChunkPos(0, 0, 0), chunkData);
84                     for (BlockPos blockPos : iterate) {
85                         downPos.set(blockPos.getX(), blockPos.getY() - 1, blockPos.getZ());
86                         if (LightOverlay.showNumber) {
87                             int level = getCrossLevel(blockPos, downPos, world, block, collisionContext);
88                             if (level >= 0) {
89                                 chunkData.put(blockPos.asLong(), (byte) level);
90                             }
91                         } else {
92                             Biome biome = !LightOverlay.mushroom ? world.getBiome(blockPos) : null;
93                             byte type = getCrossType(blockPos, biome, downPos, world, block, sky, collisionContext);
94                             if (type != LightOverlay.CROSS_NONE) {
95                                 chunkData.put(blockPos.asLong(), type);
96                             }
97                         }
98                     }
99                 } else {
100                     var height = Mth.ceil(Minecraft.getInstance().level.getHeight() / 32.0);
101                     var start = Math.floorDiv(Minecraft.getInstance().level.getMinBuildHeight(), 32);
102                     int playerPosX = ((int) player.getX()) >> 4;
103                     int playerPosY = ((int) player.getY()) >> 5;
104                     int playerPosZ = ((int) player.getZ()) >> 4;
105                     var chunkRange = LightOverlay.getChunkRange();
106                     for (int chunkX = playerPosX - chunkRange; chunkX <= playerPosX + chunkRange; chunkX++) {
107                         for (int chunkY = Math.max(playerPosY - Math.max(1, chunkRange >> 1), start); chunkY <= playerPosY + Math.max(1, chunkRange >> 1) && chunkY <= start + height; chunkY++) {
108                             for (int chunkZ = playerPosZ - chunkRange; chunkZ <= playerPosZ + chunkRange; chunkZ++) {
109                                 if (Mth.abs(chunkX - playerPosX) > chunkRange || Mth.abs(chunkY - playerPosY) > chunkRange || Mth.abs(chunkZ - playerPosZ) > chunkRange)
110                                     continue;
111                                 CubicChunkPos chunkPos = new CubicChunkPos(chunkX, chunkY, chunkZ);
112                                 if (!CHUNK_MAP.containsKey(chunkPos))
113                                     queueChunk(chunkPos);
114                             }
115                         }
116                     }
117                     for (int p = 0; p < 3; p++) {
118                         if (EXECUTOR.getQueue().size() >= Runtime.getRuntime().availableProcessors()) break;
119                         double d1 = Double.MAX_VALUE, d2 = Double.MAX_VALUE, d3 = Double.MAX_VALUE;
120                         CubicChunkPos c1 = null, c2 = null, c3 = null;
121                         synchronized (POS) {
122                             Iterator<CubicChunkPos> iterator = POS.iterator();
123                             while (iterator.hasNext()) {
124                                 CubicChunkPos pos = iterator.next();
125                                 if (Mth.abs(pos.x - playerPosX) > chunkRange || Mth.abs(pos.y - playerPosY) > Math.max(1, chunkRange >> 1) || Mth.abs(pos.z - playerPosZ) > chunkRange || CALCULATING_POS.contains(pos)) {
126                                     iterator.remove();
127                                 } else {
128                                     if (LightOverlay.renderer.isFrustumVisible(pos.getMinBlockX(), pos.getMinBlockY(), pos.getMinBlockZ(), pos.getMaxBlockX(), pos.getMaxBlockY(), pos.getMaxBlockZ())) {
129                                         int dx = Math.abs(pos.x - playerPosX);
130                                         int dy = Math.abs(pos.y - playerPosY) << 1;
131                                         int dz = Math.abs(pos.z - playerPosZ);
132                                         double distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
133                                         if (distance < d1) {
134                                             d3 = d2;
135                                             d2 = d1;
136                                             d1 = distance;
137                                             c3 = c2;
138                                             c2 = c1;
139                                             c1 = pos;
140                                         } else if (distance < d2) {
141                                             d3 = d2;
142                                             d2 = distance;
143                                             c3 = c2;
144                                             c2 = pos;
145                                         } else if (distance < d3) {
146                                             d3 = distance;
147                                             c3 = pos;
148                                         }
149                                     }
150                                 }
151                             }
152                         }
153                         CubicChunkPos finalC1 = c1;
154                         CubicChunkPos finalC2 = c2;
155                         CubicChunkPos finalC3 = c3;
156                         if (finalC1 != null) {
157                             CALCULATING_POS.add(finalC1);
158                             POS.remove(finalC1);
159                             if (finalC2 != null) {
160                                 CALCULATING_POS.add(finalC2);
161                                 POS.remove(finalC2);
162                                 if (finalC3 != null) {
163                                     CALCULATING_POS.add(finalC3);
164                                     POS.remove(finalC3);
165                                 }
166                             }
167                             EXECUTOR.submit(() -> {
168                                 int playerPosX1 = ((int) minecraft.player.getX()) >> 4;
169                                 int playerPosY1 = ((int) minecraft.player.getY()) >> 5;
170                                 int playerPosZ1 = ((int) minecraft.player.getZ()) >> 4;
171                                 if (finalC1 != null) processChunk(finalC1, playerPosX1, playerPosY1, playerPosZ1, collisionContext);
172                                 if (finalC2 != null) processChunk(finalC2, playerPosX1, playerPosY1, playerPosZ1, collisionContext);
173                                 if (finalC3 != null) processChunk(finalC3, playerPosX1, playerPosY1, playerPosZ1, collisionContext);
174                             });
175                         }
176                     }
177                     if (ticks % 50 == 0) {
178                         CHUNK_MAP.entrySet().removeIf(entry -> Mth.abs(entry.getKey().x - playerPosX) > chunkRange * 2 || Mth.abs(entry.getKey().y - playerPosY) > chunkRange * 2 || Mth.abs(entry.getKey().z - playerPosZ) > chunkRange * 2);
179                     }
180                 }
181             }
182         } catch (Throwable throwable) {
183             LogManager.getLogger().throwing(throwable);
184         }
185     }
186     
187     private void processChunk(CubicChunkPos pos, int playerPosX, int playerPosY, int playerPosZ, CollisionContext context) {
188         CALCULATING_POS.remove(pos);
189         int chunkRange = LightOverlay.getChunkRange();
190         if (Mth.abs(pos.x - playerPosX) > chunkRange || Mth.abs(pos.y - playerPosY) > Math.max(1, chunkRange >> 1) || Mth.abs(pos.z - playerPosZ) > chunkRange || POS.contains(pos)) {
191             return;
192         }
193         try {
194             calculateChunk(minecraft.level.getChunkSource().getChunk(pos.x, pos.z, ChunkStatus.FULL, false), minecraft.level, pos, context);
195         } catch (Throwable throwable) {
196             LogManager.getLogger().throwing(throwable);
197         }
198     }
199     
200     private void calculateChunk(LevelChunk chunk, Level world, CubicChunkPos chunkPos, CollisionContext collisionContext) {
201         if (world != null && chunk != null) {
202             Long2ByteMap chunkData = new Long2ByteOpenHashMap();
203             LayerLightEventListener block = world.getLightEngine().getLayerListener(LightLayer.BLOCK);
204             LayerLightEventListener sky = LightOverlay.showNumber ? null : world.getLightEngine().getLayerListener(LightLayer.SKY);
205             for (BlockPos pos : BlockPos.betweenClosed(chunkPos.getMinBlockX(), chunkPos.getMinBlockY(), chunkPos.getMinBlockZ(), chunkPos.getMaxBlockX(), chunkPos.getMaxBlockY(), chunkPos.getMaxBlockZ())) {
206                 BlockPos down = pos.below();
207                 if (LightOverlay.showNumber) {
208                     int level = getCrossLevel(pos, down, chunk, block, collisionContext);
209                     if (level >= 0) {
210                         chunkData.put(pos.asLong(), (byte) level);
211                     }
212                 } else {
213                     Biome biome = !LightOverlay.mushroom ? world.getBiome(pos) : null;
214                     byte type = getCrossType(pos, biome, down, chunk, block, sky, collisionContext);
215                     if (type != LightOverlay.CROSS_NONE) {
216                         chunkData.put(pos.asLong(), type);
217                     }
218                 }
219             }
220             CHUNK_MAP.put(chunkPos, chunkData);
221         } else {
222             CHUNK_MAP.remove(chunkPos);
223         }
224     }
225     
226     public byte getCrossType(BlockPos pos, Biome biome, BlockPos down, BlockGetter world, LayerLightEventListener block, LayerLightEventListener sky, CollisionContext entityContext) {
227         BlockState blockBelowState = world.getBlockState(down);
228         BlockState blockUpperState = world.getBlockState(pos);
229         VoxelShape upperCollisionShape = blockUpperState.getCollisionShape(world, pos, entityContext);
230         if (!LightOverlay.underwater && !blockUpperState.getFluidState().isEmpty())
231             return LightOverlay.CROSS_NONE;
232         // Check if the outline is full
233         if (Block.isFaceFull(upperCollisionShape, Direction.UP))
234             return LightOverlay.CROSS_NONE;
235         // TODO: Not to hard code no redstone
236         if (blockUpperState.isSignalSource())
237             return LightOverlay.CROSS_NONE;
238         // Check if the collision has a bump
239         if (upperCollisionShape.max(Direction.Axis.Y) > 0)
240             return LightOverlay.CROSS_NONE;
241         if (blockUpperState.is(BlockTags.RAILS))
242             return LightOverlay.CROSS_NONE;
243         // Check block state allow spawning (excludes bedrock and barriers automatically)
244         if (!blockBelowState.isValidSpawn(world, down, TESTING_ENTITY_TYPE.get()))
245             return LightOverlay.CROSS_NONE;
246         if (!LightOverlay.mushroom && Biome.BiomeCategory.MUSHROOM == biome.getBiomeCategory())
247             return LightOverlay.CROSS_NONE;
248         int blockLightLevel = block.getLightValue(pos);
249         int skyLightLevel = sky.getLightValue(pos);
250         if (blockLightLevel > LightOverlay.higherCrossLevel)
251             return LightOverlay.CROSS_NONE;
252         if (skyLightLevel > LightOverlay.higherCrossLevel)
253             return LightOverlay.higherCross;
254         return LightOverlay.lowerCrossLevel >= 0 && blockLightLevel > LightOverlay.lowerCrossLevel ? 
255                 LightOverlay.lowerCross : LightOverlay.CROSS_RED;
256     }
257     
258     public static int getCrossLevel(BlockPos pos, BlockPos down, BlockGetter world, LayerLightEventListener view, CollisionContext collisionContext) {
259         BlockState blockBelowState = world.getBlockState(down);
260         BlockState blockUpperState = world.getBlockState(pos);
261         VoxelShape collisionShape = blockBelowState.getCollisionShape(world, down, collisionContext);
262         VoxelShape upperCollisionShape = blockUpperState.getCollisionShape(world, pos, collisionContext);
263         if (!LightOverlay.underwater && !blockUpperState.getFluidState().isEmpty())
264             return -1;
265         if (!blockBelowState.getFluidState().isEmpty())
266             return -1;
267         if (blockBelowState.isAir())
268             return -1;
269         if (Block.isFaceFull(upperCollisionShape, Direction.DOWN))
270             return -1;
271         return view.getLightValue(pos);
272     }
273 }