Unicopia/src/main/java/com/minelittlepony/unicopia/BlockDestructionManager.java
2021-12-22 11:15:09 +02:00

163 lines
5.2 KiB
Java

package com.minelittlepony.unicopia;
import java.util.Arrays;
import java.util.List;
import com.minelittlepony.unicopia.network.Channel;
import com.minelittlepony.unicopia.network.MsgBlockDestruction;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import net.minecraft.block.BlockState;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.server.world.ThreadedAnvilChunkStorage;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
import net.minecraft.world.World;
public class BlockDestructionManager {
public static final int UNSET_DAMAGE = -1;
public static final int MAX_DAMAGE = 10;
private final Destruction emptyDestruction = new Destruction();
private final World world;
private final Long2ObjectMap<Chunk> destructions = new Long2ObjectOpenHashMap<>();
private final Object locker = new Object();
public BlockDestructionManager(World world) {
this.world = world;
}
public int getBlockDestruction(BlockPos pos) {
return getDestructions(pos).getBlockDestruction(pos);
}
private Chunk getDestructions(BlockPos pos) {
return destructions.computeIfAbsent(new ChunkPos(pos).toLong(), Chunk::new);
}
public void clearBlockDestruction(BlockPos pos) {
setBlockDestruction(pos, -1);
}
public void setBlockDestruction(BlockPos pos, int amount) {
synchronized (locker) {
getDestructions(pos).setBlockDestruction(pos, amount);
}
}
public int damageBlock(BlockPos pos, int amount) {
if (amount == 0) {
return getBlockDestruction(pos);
}
amount = Math.max(getBlockDestruction(pos), 0) + amount;
setBlockDestruction(pos, amount);
return amount;
}
public void onBlockChanged(BlockPos pos, BlockState oldState, BlockState newstate) {
if (oldState.getBlock() != newstate.getBlock()) {
clearBlockDestruction(pos);
}
}
public void tick() {
synchronized (locker) {
destructions.long2ObjectEntrySet().removeIf(entry -> entry.getValue().tick());
if (world instanceof ServerWorld) {
destructions.forEach((chunkPos, chunk) -> chunk.sendUpdates((ServerWorld)world));
}
}
}
private class Chunk {
private final Long2ObjectMap<Destruction> destructions = new Long2ObjectOpenHashMap<>();
private final long pos;
Chunk(long pos) {
this.pos = pos;
}
public int getBlockDestruction(BlockPos pos) {
return destructions.getOrDefault(pos.asLong(), emptyDestruction).amount;
}
public void setBlockDestruction(BlockPos pos, int amount) {
destructions.computeIfAbsent(pos.asLong(), p -> new Destruction()).set(amount);
}
boolean tick() {
destructions.long2ObjectEntrySet().removeIf(e -> e.getValue().tick());
return destructions.isEmpty();
}
void sendUpdates(ServerWorld world) {
if (!world.getChunkManager().isChunkLoaded(ChunkPos.getPackedX(pos), ChunkPos.getPackedZ(pos))) {
return;
}
ThreadedAnvilChunkStorage storage = world.getChunkManager().threadedAnvilChunkStorage;
List<ServerPlayerEntity> players = storage.getPlayersWatchingChunk(new ChunkPos(pos), false);
if (!players.isEmpty()) {
Long2ObjectOpenHashMap<Integer> values = new Long2ObjectOpenHashMap<>();
destructions.forEach((blockPos, item) -> {
if (item.dirty) {
item.dirty = false;
values.put(blockPos.longValue(), (Integer)item.amount);
}
});
MsgBlockDestruction msg = new MsgBlockDestruction(values);
if (msg.toBuffer().writerIndex() > 1048576) {
throw new IllegalStateException("Payload may not be larger than 1048576 bytes. Here's what we were trying to send: ["
+ values.size() + "]\n"
+ Arrays.toString(values.values().stream().mapToInt(Integer::intValue).toArray()));
}
players.forEach(player -> {
if (player instanceof ServerPlayerEntity) {
Channel.SERVER_BLOCK_DESTRUCTION.send(player, msg);
}
});
}
}
}
private class Destruction {
int amount = -1;
int age = 50;
boolean dirty;
boolean tick() {
if (age-- > 0) {
return false;
}
if (amount >= 0) {
set(amount - 1);
}
return amount < 0 || age-- <= 0;
}
void set(int amount) {
this.age = 50;
this.amount = amount >= 0 && amount < MAX_DAMAGE ? amount : UNSET_DAMAGE;
this.dirty = true;
}
}
public interface Source {
BlockDestructionManager getDestructionManager();
}
}