-
Notifications
You must be signed in to change notification settings - Fork 50
/
DynamicLights.java
412 lines (357 loc) · 15.5 KB
/
DynamicLights.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
package com.gtnewhorizons.angelica.dynamiclights;
import baubles.common.lib.PlayerHandler;
import com.gtnewhorizon.gtnhlib.blockpos.BlockPos;
import com.gtnewhorizon.gtnhlib.blockpos.IBlockPos;
import com.gtnewhorizon.gtnhlib.util.CoordinatePacker;
import com.gtnewhorizons.angelica.api.IDynamicLightProducer;
import com.gtnewhorizons.angelica.compat.ModStatus;
import com.gtnewhorizons.angelica.config.AngelicaConfig;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
import me.jellysquid.mods.sodium.client.render.SodiumWorldRenderer;
import mods.battlegear2.api.core.IBattlePlayer;
import mods.battlegear2.api.core.IInventoryPlayerBattle;
import net.irisshaders.iris.api.v0.IrisApi;
import net.minecraft.block.Block;
import net.minecraft.block.material.Material;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityLivingBase;
import net.minecraft.entity.item.EntityItem;
import net.minecraft.entity.monster.EntityCreeper;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Blocks;
import net.minecraft.init.Items;
import net.minecraft.item.Item;
import net.minecraft.item.ItemBlock;
import net.minecraft.item.ItemStack;
import net.minecraft.util.MathHelper;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import xonin.backhand.api.core.BackhandUtils;
import java.util.Set;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Predicate;
public class DynamicLights {
private static DynamicLights instance;
public static DynamicLightsMode Mode = DynamicLightsMode.OFF;
public static boolean ShaderForce = false;
private static final double MAX_RADIUS = 7.75;
private static final double MAX_RADIUS_SQUARED = MAX_RADIUS * MAX_RADIUS;
private final Set<IDynamicLightSource> dynamicLightSources = new ObjectOpenHashSet<>();
private final ReentrantReadWriteLock lightSourcesLock = new ReentrantReadWriteLock();
private long lastUpdate = System.currentTimeMillis();
private int lastUpdateCount = 0;
public static DynamicLights get() {
if (instance == null)
instance = new DynamicLights();
return instance;
}
public static boolean isEnabled() {
return AngelicaConfig.enableDynamicLights &&
Mode.isEnabled() &&
// if shader force is enabled then true
// if not, then true when shaders are not in use
(ShaderForce || !IrisApi.getInstance().isShaderPackInUse());
}
/**
* Updates all light sources.
*
* @param renderer the renderer
*/
public void updateAll(@NotNull SodiumWorldRenderer renderer) {
if (!isEnabled())
return;
long now = System.currentTimeMillis();
if (now >= this.lastUpdate + Mode.getDelay()) {
this.lastUpdate = now;
this.lastUpdateCount = 0;
this.lightSourcesLock.readLock().lock();
for (var lightSource : this.dynamicLightSources) {
if (lightSource.angelica$updateDynamicLight(renderer))
this.lastUpdateCount++;
}
this.lightSourcesLock.readLock().unlock();
}
}
public int getLastUpdateCount() {
return this.lastUpdateCount;
}
public void addLightSource(IDynamicLightSource lightSource) {
this.lightSourcesLock.writeLock().lock();
this.dynamicLightSources.add(lightSource);
this.lightSourcesLock.writeLock().unlock();
}
/**
* Removes the light source from the tracked light sources.
*
* @param lightSource the light source to remove
*/
public void removeLightSource(@NotNull IDynamicLightSource lightSource) {
this.lightSourcesLock.writeLock().lock();
var dynamicLightSources = this.dynamicLightSources.iterator();
IDynamicLightSource it;
while (dynamicLightSources.hasNext()) {
it = dynamicLightSources.next();
if (it.equals(lightSource)) {
dynamicLightSources.remove();
if (SodiumWorldRenderer.getInstance() != null)
lightSource.angelica$scheduleTrackedChunksRebuild(SodiumWorldRenderer.getInstance());
break;
}
}
this.lightSourcesLock.writeLock().unlock();
}
/**
* Removes light sources if the filter matches.
*
* @param filter the removal filter
*/
public void removeLightSources(@NotNull Predicate<IDynamicLightSource> filter) {
this.lightSourcesLock.writeLock().lock();
var dynamicLightSources = this.dynamicLightSources.iterator();
IDynamicLightSource it;
while (dynamicLightSources.hasNext()) {
it = dynamicLightSources.next();
if (filter.test(it)) {
dynamicLightSources.remove();
if (SodiumWorldRenderer.getInstance() != null) {
if (it.angelica$getLuminance() > 0)
it.angelica$resetDynamicLight();
it.angelica$scheduleTrackedChunksRebuild(SodiumWorldRenderer.getInstance());
}
break;
}
}
this.lightSourcesLock.writeLock().unlock();
}
/**
* Clears light sources.
*/
public void clearLightSources() {
this.lightSourcesLock.writeLock().lock();
var dynamicLightSources = this.dynamicLightSources.iterator();
IDynamicLightSource it;
while (dynamicLightSources.hasNext()) {
it = dynamicLightSources.next();
dynamicLightSources.remove();
if (SodiumWorldRenderer.getInstance() != null) {
if (it.angelica$getLuminance() > 0)
it.angelica$resetDynamicLight();
it.angelica$scheduleTrackedChunksRebuild(SodiumWorldRenderer.getInstance());
}
}
this.lightSourcesLock.writeLock().unlock();
}
/**
* Returns whether the light source is tracked or not.
*
* @param lightSource the light source to check
* @return {@code true} if the light source is tracked, else {@code false}
*/
public boolean containsLightSource(@NotNull IDynamicLightSource lightSource) {
boolean result;
this.lightSourcesLock.readLock().lock();
result = this.dynamicLightSources.contains(lightSource);
this.lightSourcesLock.readLock().unlock();
return result;
}
/**
* Returns the number of dynamic light sources that currently emit lights.
*
* @return the number of dynamic light sources emitting light
*/
public int getLightSourcesCount() {
int result;
this.lightSourcesLock.readLock().lock();
result = this.dynamicLightSources.size();
this.lightSourcesLock.readLock().unlock();
return result;
}
public double getDynamicLightLevel(int x, int y, int z) {
double result = 0;
this.lightSourcesLock.readLock().lock();
for (var lightSource : this.dynamicLightSources) {
result = maxDynamicLightLevel(x, y, z, lightSource, result);
}
this.lightSourcesLock.readLock().unlock();
return MathHelper.clamp_double(result, 0, 15);
}
public double getDynamicLightLevel(@NotNull BlockPos pos) {
return this.getDynamicLightLevel(pos.getX(), pos.getY(), pos.getZ());
}
public static double maxDynamicLightLevel(int x, int y, int z, @NotNull IDynamicLightSource lightSource, double currentLightLevel) {
int luminance = lightSource.angelica$getLuminance();
if (luminance > 0) {
// Can't use Entity#squaredDistanceTo because of eye Y coordinate.
double dx = x - lightSource.angelica$getDynamicLightX() + 0.5;
double dy = y - lightSource.angelica$getDynamicLightY() + 0.5;
double dz = z - lightSource.angelica$getDynamicLightZ() + 0.5;
double distanceSquared = dx * dx + dy * dy + dz * dz;
// 7.75 because else we would have to update more chunks and that's not a good idea.
// 15 (max range for blocks) would be too much and a bit cheaty.
if (distanceSquared <= MAX_RADIUS_SQUARED) {
double multiplier = 1.0 - Math.sqrt(distanceSquared) / MAX_RADIUS;
double lightLevel = multiplier * (double) luminance;
if (lightLevel > currentLightLevel) {
return lightLevel;
}
}
}
return currentLightLevel;
}
/**
* Returns the dynamic light level generated by the light source at the specified position.
*
* @param pos the position
* @param lightSource the light source
* @param currentLightLevel the current surrounding dynamic light level
* @return the dynamic light level at the specified position
*/
public static double maxDynamicLightLevel(@NotNull BlockPos pos, @NotNull IDynamicLightSource lightSource, double currentLightLevel) {
return maxDynamicLightLevel(pos.getX(), pos.getY(), pos.getZ(), lightSource, currentLightLevel);
}
public int getLightmapWithDynamicLight(int x, int y, int z, int lightmap) {
return this.getLightmapWithDynamicLight(this.getDynamicLightLevel(x, y, z), lightmap);
}
/**
* Returns the lightmap with combined light levels.
*
* @param dynamicLightLevel the dynamic light level
* @param lightmap the vanilla lightmap coordinates
* @return the modified lightmap coordinates
*/
public int getLightmapWithDynamicLight(double dynamicLightLevel, int lightmap) {
if (dynamicLightLevel > 0) {
// lightmap is (skyLevel << 20 | blockLevel << 4)
// Get vanilla block light level.
int blockLevel = (lightmap & 0xFFFF) >> 4;
if (dynamicLightLevel > blockLevel) {
// Equivalent to a << 4 bitshift with a little quirk: this one ensure more precision (more decimals are saved).
int luminance = (int) (dynamicLightLevel * 16.0);
lightmap &= 0xfff00000;
lightmap |= luminance & 0x000fffff;
}
}
return lightmap;
}
/**
* Schedules a chunk rebuild at the specified chunk position.
*
* @param renderer the renderer
* @param chunkPos the chunk position
*/
public static void scheduleChunkRebuild(@NotNull SodiumWorldRenderer renderer, @NotNull IBlockPos chunkPos) {
scheduleChunkRebuild(renderer, chunkPos.getX(), chunkPos.getY(), chunkPos.getZ());
}
/**
* Schedules a chunk rebuild at the specified chunk position.
*
* @param renderer the renderer
* @param chunkPos the packed chunk position
*/
public static void scheduleChunkRebuild(@NotNull SodiumWorldRenderer renderer, long chunkPos) {
scheduleChunkRebuild(renderer, CoordinatePacker.unpackX(chunkPos), CoordinatePacker.unpackY(chunkPos), CoordinatePacker.unpackZ(chunkPos));
}
public static void scheduleChunkRebuild(@NotNull SodiumWorldRenderer renderer, int x, int y, int z) {
renderer.scheduleRebuildForChunk(x, y, z, false);
}
/**
* Updates the tracked chunk sets.
*
* @param chunkPos the packed chunk position
* @param old the set of old chunk coordinates to remove this chunk from it
* @param newPos the set of new chunk coordinates to add this chunk to it
*/
public static void updateTrackedChunks(@NotNull IBlockPos chunkPos, @Nullable LongOpenHashSet old, @Nullable LongOpenHashSet newPos) {
if (old != null || newPos != null) {
final long pos = chunkPos.asLong();
if (old != null)
old.remove(pos);
if (newPos != null)
newPos.add(pos);
}
}
/**
* Updates the dynamic lights tracking.
*
* @param lightSource the light source
*/
public static void updateTracking(@NotNull IDynamicLightSource lightSource) {
boolean enabled = lightSource.angelica$isDynamicLightEnabled();
int luminance = lightSource.angelica$getLuminance();
if (!enabled && luminance > 0) {
lightSource.angelica$setDynamicLightEnabled(true);
} else if (enabled && luminance < 1) {
lightSource.angelica$setDynamicLightEnabled(false);
}
}
/**
* Returns the luminance from an item stack.
*
* @param stack the item stack
* @param submergedInWater {@code true} if the stack is submerged in water, else {@code false}
* @return the luminance of the item
*/
public static int getLuminanceFromItemStack(@NotNull ItemStack stack, boolean submergedInWater) {
// TODO only have certain items not glow in water?
if (submergedInWater) return 0;
Item item = stack.getItem();
if (item instanceof ItemBlock itemBlock) {
Block block = itemBlock.field_150939_a;
if (block != null) {
return block.getLightValue();
}
} else if (item instanceof IDynamicLightProducer lightProducer){
return lightProducer.getLuminance();
}
if (item == Items.lava_bucket) return Blocks.lava.getLightValue();
return 0;
}
public static int getLuminanceFromEntity(@NotNull Entity entity) {
if (entity.fire > 0) return 15;
if (entity instanceof EntityItem item) {
return getLuminanceFromItemStack(item.getEntityItem(), item.isInsideOfMaterial(Material.water));
}
if (entity instanceof EntityLivingBase living) {
int luminance = 0;
boolean inWater = living.isInsideOfMaterial(Material.water);
// check equipment + hand for light (should work for all entities)
ItemStack itemStack;
for (int i = 0; i < 5; i++) {
if ((itemStack = living.getEquipmentInSlot(i)) != null) {
luminance = Math.max(luminance, getLuminanceFromItemStack(itemStack, inWater));
}
}
if (ModStatus.isBattlegearLoaded &&
living instanceof EntityPlayer player &&
player instanceof IBattlePlayer battlePlayer &&
battlePlayer.battlegear2$isBattlemode()
) {
ItemStack offhand = ((IInventoryPlayerBattle) player.inventory).battlegear2$getCurrentOffhandWeapon();
if (offhand != null) {
luminance = Math.max(luminance, getLuminanceFromItemStack(offhand, inWater));
}
}
else if (ModStatus.isBackhandLoaded && living instanceof EntityPlayer player){
ItemStack offhand = BackhandUtils.getOffhandItem(player);
if (offhand != null) {
luminance = Math.max(luminance, getLuminanceFromItemStack(offhand, inWater));
}
}
if (ModStatus.isBaublesLoaded && living instanceof EntityPlayer player){
var playerBaubles = PlayerHandler.getPlayerBaubles(player);
if (playerBaubles != null){
for (int i = 0; i < playerBaubles.getSizeInventory(); i++){
var stack = playerBaubles.getStackInSlot(i);
if (stack != null){
luminance = Math.max(luminance, getLuminanceFromItemStack(stack, inWater));
}
}
}
}
return luminance;
}
// TODO: Creepers? TNT? Dusts?
return 0;
}
}