Fixed dynamic lighting entities

This commit is contained in:
Sollace 2023-06-07 20:35:29 +01:00
parent 6c1c3e17d2
commit fb742644af
5 changed files with 273 additions and 22 deletions

View file

@ -2,6 +2,8 @@ package com.minelittlepony.unicopia.entity;
import org.jetbrains.annotations.Nullable;
import com.minelittlepony.unicopia.server.world.LightSources;
import net.minecraft.entity.Entity;
import net.minecraft.util.math.BlockPos;
@ -22,35 +24,35 @@ public interface DynamicLightSource {
@SuppressWarnings("deprecation")
void tick() {
if (entity.getWorld().isClient) {
if (entity.isRemoved()) {
remove();
return;
}
if (entity.isRemoved()) {
remove();
return;
}
int light = entity.getLightLevel();
int light = entity.getLightLevel();
if (light <= 0) {
return;
}
if (light <= 0) {
return;
}
BlockPos currentPos = entity.getBlockPos();
BlockPos currentPos = entity.getBlockPos();
if (!currentPos.equals(lastPos) && entity.getWorld().isChunkLoaded(currentPos)) {
try {
if (lastPos != null) {
entity.getWorld().getLightingProvider().checkBlock(lastPos);
}
// TODO: store this in the ether and inject into Chunk#forEachLightSource
//entity.getWorld().getLightingProvider().addLightSource(currentPos, light);
lastPos = currentPos;
} catch (Exception ignored) { }
}
if (!currentPos.equals(lastPos) && entity.getWorld().isChunkLoaded(currentPos)) {
LightSources.get(entity.getWorld()).addLightSource(entity);
try {
if (lastPos != null) {
entity.getWorld().getLightingProvider().checkBlock(lastPos);
entity.getWorld().getLightingProvider().checkBlock(currentPos);
}
lastPos = currentPos;
} catch (Exception ignored) { }
}
}
void remove() {
if (entity.getWorld().isClient && lastPos != null) {
LightSources.get(entity.getWorld()).removeLightSource(entity);
if (lastPos != null) {
try {
entity.getWorld().getLightingProvider().checkBlock(lastPos);
} catch (Exception ignored) {}

View file

@ -0,0 +1,99 @@
package com.minelittlepony.unicopia.mixin;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.gen.Invoker;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import com.minelittlepony.unicopia.server.world.LightSources;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.util.math.ChunkSectionPos;
import net.minecraft.util.math.Direction;
import net.minecraft.world.chunk.WorldChunk;
import net.minecraft.world.chunk.light.ChunkBlockLightProvider;
import net.minecraft.world.chunk.light.ChunkLightProvider;
import net.minecraft.world.chunk.light.LightStorage;
@SuppressWarnings({ "rawtypes", "unchecked" })
@Mixin(ChunkBlockLightProvider.class)
abstract class MixinChunkBlockLightProvider extends ChunkLightProvider {
@Shadow
private @Final BlockPos.Mutable mutablePos;
MixinChunkBlockLightProvider() { super(null, null); }
@Inject(method = "propagateLight", at = @At("TAIL"))
private void onPropagateLight(ChunkPos chunkPos, CallbackInfo info) {
if (chunkProvider.getChunk(chunkPos.x, chunkPos.z) instanceof WorldChunk chunk && chunk.getWorld() instanceof ServerWorld world) {
LightSources.get(world).forEachLightSource(chunkPos, (pos, level) -> {
method_51566(pos.asLong(), ChunkLightProvider.class_8531.method_51573(level, false));
});
}
}
/*
@Inject(method = "getLightSourceLuminance", at = @At("RETURN"), cancellable = true)
private void onGetLightSourceLuminance(long blockPos, BlockState blockState, CallbackInfoReturnable<Integer> info) {
int x = ChunkSectionPos.getSectionCoord(BlockPos.unpackLongX(blockPos));
int z = ChunkSectionPos.getSectionCoord(BlockPos.unpackLongZ(blockPos));
if (chunkProvider.getChunk(x, z) instanceof WorldChunk chunk) {
info.setReturnValue(Math.max(info.getReturnValue(), LightSources.get(chunk.getWorld()).getLuminance(blockPos)));
}
}*/
@Inject(method = "method_51529", at = @At("TAIL"))
private void onMethod_51529(long blockPos, CallbackInfo info) {
long sectionPos = ChunkSectionPos.fromBlockPos(blockPos);
if (!((MutableBlockLightStorage)lightStorage).invokeHasSection(sectionPos)) {
return;
}
int x = ChunkSectionPos.getSectionCoord(BlockPos.unpackLongX(blockPos));
int z = ChunkSectionPos.getSectionCoord(BlockPos.unpackLongZ(blockPos));
if (chunkProvider.getChunk(x, z) instanceof WorldChunk chunk) {
int lightSourceLight = LightSources.get(chunk.getWorld()).getLuminance(blockPos);
if (lightSourceLight > 0) {
method_51566(blockPos, ChunkLightProvider.class_8531.method_51573(lightSourceLight, false));
}
}
}
@Inject(method = "method_51530", at = @At("TAIL"))
private void onMethod_51530(long blockPos, long flags, CallbackInfo info) {
int x = ChunkSectionPos.getSectionCoord(BlockPos.unpackLongX(blockPos));
int z = ChunkSectionPos.getSectionCoord(BlockPos.unpackLongZ(blockPos));
if (chunkProvider.getChunk(x, z) instanceof WorldChunk chunk) {
int lightLevel = ChunkLightProvider.class_8531.getLightLevel(flags);
for (Direction direction : DIRECTIONS) {
int j;
long m;
if (!ChunkLightProvider.class_8531.isDirectionBitSet(flags, direction)
|| !((MutableBlockLightStorage)lightStorage).invokeHasSection(ChunkSectionPos.fromBlockPos(m = BlockPos.offset(blockPos, direction)))
|| (j = ((MutableBlockLightStorage)lightStorage).invokeGet(m)) == 0) continue;
if (j <= lightLevel - 1) {
int lightSourceLight = LightSources.get(chunk.getWorld()).getLuminance(blockPos);
if (lightSourceLight <= 0) continue;
method_51566(m, ChunkLightProvider.class_8531.method_51573(lightSourceLight, false));
continue;
}
method_51566(m, ChunkLightProvider.class_8531.method_51579(j, false, direction.getOpposite()));
}
}
}
}
@Mixin(LightStorage.class)
interface MutableBlockLightStorage {
@Invoker("hasSection")
boolean invokeHasSection(long sectionPos);
@Invoker("get")
int invokeGet(long blockPos);
@Invoker("set")
void invokeSet(long blockPos, int light);
}

View file

@ -0,0 +1,121 @@
package com.minelittlepony.unicopia.server.world;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.function.Function;
import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.entity.DynamicLightSource;
import it.unimi.dsi.fastutil.objects.AbstractObject2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import net.minecraft.entity.Entity;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.world.PersistentState;
import net.minecraft.world.World;
public class LightSources extends PersistentState {
private static final Identifier ID = Unicopia.id("light_sources");
private final Set<UUID> lightSourceIds = new HashSet<>();
private final AbstractObject2IntMap<UUID> lightSourceClientIds = new Object2IntOpenHashMap<>();
private volatile boolean empty = true;
private final World world;
private final Function<UUID, Entity> entitySupplier;
public static LightSources get(World world) {
return WorldOverlay.getPersistableStorage(world, ID, LightSources::new, LightSources::new);
}
LightSources(World world, NbtCompound compound) {
this(world);
}
LightSources(World world) {
this.world = world;
entitySupplier = world instanceof ServerWorld s ? s::getEntity : id -> world.getEntityById(lightSourceClientIds.getInt(id));
}
@Override
public NbtCompound writeNbt(NbtCompound compound) {
return compound;
}
public void addLightSource(Entity entity) {
synchronized (lightSourceIds) {
lightSourceIds.add(entity.getUuid());
lightSourceClientIds.put(entity.getUuid(), entity.getId());
empty = false;
}
}
public void removeLightSource(Entity entity) {
if (empty) {
return;
}
synchronized (lightSourceIds) {
lightSourceIds.remove(entity.getUuid());
lightSourceClientIds.removeInt(entity.getUuid());
empty = lightSourceIds.isEmpty();
}
}
public int getLuminance(long blockPos) {
if (empty) {
return 0;
}
final int[] result = {0};
forEachLightSource((pos, level) -> {
if (pos.asLong() == blockPos) {
result[0] += level;
}
});
return result[0];
}
public void forEachLightSource(ChunkPos chunkPos, LightSourceConsumer consumer) {
forEachLightSource((pos, level) -> {
if (checkPos(chunkPos, pos)) {
consumer.accept(pos, level);
}
});
}
public void forEachLightSource(LightSourceConsumer consumer) {
if (empty) {
return;
}
synchronized (lightSourceIds) {
lightSourceIds.removeIf(id -> {
Entity entity = entitySupplier.apply(id);
if (entity instanceof DynamicLightSource source) {
consumer.accept(entity.getBlockPos(), source.getLightLevel());
return false;
}
return true;
});
empty = lightSourceIds.isEmpty();
}
}
private boolean checkPos(ChunkPos chunkPos, BlockPos pos) {
return world.isInBuildLimit(pos)
&& checkPos(pos.getX(), chunkPos.getStartX(), chunkPos.getEndX())
&& checkPos(pos.getZ(), chunkPos.getStartZ(), chunkPos.getEndZ());
}
private boolean checkPos(int p, int min, int max) {
return p >= min && p <= max;
}
public interface LightSourceConsumer {
void accept(BlockPos pos, int light);
}
}

View file

@ -1,6 +1,9 @@
package com.minelittlepony.unicopia.server.world;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.*;
import org.jetbrains.annotations.Nullable;
@ -21,6 +24,7 @@ import net.minecraft.world.PersistentState;
import net.minecraft.world.World;
public class WorldOverlay<T extends WorldOverlay.State> extends PersistentState implements Tickable {
private final World world;
private final Long2ObjectMap<Chunk> chunks = new Long2ObjectOpenHashMap<>();
@ -39,7 +43,8 @@ public class WorldOverlay<T extends WorldOverlay.State> extends PersistentState
id.toString()
);
}
return factory.apply(world);
return ClientInstance.of(world, id, factory).instance();
}
public static <T extends State> WorldOverlay<T> getOverlay(World world, Identifier id, Supplier<T> factory, @Nullable BiConsumer<Long2ObjectMap<T>, List<ServerPlayerEntity>> updateSender) {
@ -181,4 +186,26 @@ public class WorldOverlay<T extends WorldOverlay.State> extends PersistentState
public interface State extends NbtSerialisable {
boolean tick();
}
record ClientInstance<T extends PersistentState>(WeakReference<World> world, T instance) {
private static final Map<Identifier, ClientInstance<?>> INSTANCES = new HashMap<>();
@SuppressWarnings("unchecked")
public static <T extends PersistentState> ClientInstance<T> of(World world, Identifier id, Function<World, T> factory) {
return (ClientInstance<T>)INSTANCES.compute(id, (i, instance) -> {
if (instance == null || !instance.matches(world)) {
return new ClientInstance<>(world, factory);
}
return instance;
});
}
public ClientInstance(World world, Function<World, T> factory) {
this(new WeakReference<>(world), factory.apply(world));
}
public boolean matches(World world) {
return this.world().get() == world;
}
}
}

View file

@ -12,6 +12,8 @@
"MixinBlockItem",
"MixinBoatEntity",
"MixinBrain",
"MixinChunkBlockLightProvider",
"MutableBlockLightStorage",
"MixinDamageSource",
"MixinFallLocation",
"MixinEntity",