generated from tilera/1710mod
458 lines
14 KiB
Java
458 lines
14 KiB
Java
package mods.immibis.subworlds.mws;
|
|
|
|
|
|
|
|
import java.lang.ref.WeakReference;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Collection;
|
|
import java.util.HashSet;
|
|
import java.util.LinkedList;
|
|
import java.util.Queue;
|
|
import java.util.Set;
|
|
|
|
import mods.immibis.core.api.APILocator;
|
|
import mods.immibis.subworlds.dw.DWWorldProvider;
|
|
import mods.immibis.subworlds.mws.packets.*;
|
|
import net.minecraft.entity.Entity;
|
|
import net.minecraft.entity.player.EntityPlayer;
|
|
import net.minecraft.network.NetworkManager;
|
|
import net.minecraft.network.Packet;
|
|
import net.minecraft.server.MinecraftServer;
|
|
import net.minecraft.tileentity.TileEntity;
|
|
import net.minecraft.util.LongHashMap;
|
|
import net.minecraft.world.ChunkCoordIntPair;
|
|
import net.minecraft.world.IWorldAccess;
|
|
import net.minecraft.world.World;
|
|
import net.minecraft.world.chunk.Chunk;
|
|
|
|
/**
|
|
* Holds MWS state for one world.
|
|
*/
|
|
public class MWSWorldManager {
|
|
private final WeakReference<World> worldObj;
|
|
private final int dimensionID; // if world is unloaded, we need to know the dimension number to inform clients
|
|
|
|
private final int VIEW_DISTANCE = MinecraftServer.getServer().getConfigurationManager().getViewDistance();
|
|
|
|
MWSWorldManager(World world) {
|
|
this.worldObj = new WeakReference<World>(world);
|
|
this.dimensionID = world.provider.dimensionId;
|
|
world.addWorldAccess(new WorldListener());
|
|
}
|
|
|
|
private class Range {
|
|
// inclusive min, exclusive max
|
|
public final int minx, miny, minz, maxx, maxy, maxz;
|
|
public Range(int minx, int miny, int minz, int maxx, int maxy, int maxz) {
|
|
this.minx = minx;
|
|
this.miny = miny;
|
|
this.minz = minz;
|
|
this.maxx = maxx;
|
|
this.maxy = maxy;
|
|
this.maxz = maxz;
|
|
}
|
|
|
|
public int getBlockCount() {
|
|
return (maxx - minx) * (maxy - miny) * (maxz - minz);
|
|
}
|
|
}
|
|
|
|
private Set<MWSListener> listeners = new HashSet<MWSListener>();
|
|
|
|
private class SyncedChunk {
|
|
public final int x, z;
|
|
|
|
public SyncedChunk(int x, int z) {
|
|
this.x = x;
|
|
this.z = z;
|
|
}
|
|
|
|
private Range[] updateRanges = new Range[8];
|
|
private int nUpdateRanges;
|
|
private int nUpdatedBlocks;
|
|
|
|
//public Set<MWSListener> listeners = new HashSet<MWSListener>();
|
|
|
|
public void addRange(Range r) {
|
|
if(nUpdateRanges < updateRanges.length) {
|
|
updateRanges[nUpdateRanges] = r;
|
|
nUpdatedBlocks += r.getBlockCount();
|
|
}
|
|
nUpdateRanges++;
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
public Collection<Packet> nextUpdatePackets() {
|
|
if(nUpdateRanges == 0)
|
|
return null;
|
|
|
|
World world = worldObj.get();
|
|
if(world == null)
|
|
return null;
|
|
|
|
Collection<Packet> rv = null;
|
|
|
|
if(nUpdatedBlocks == 1) {
|
|
int x = updateRanges[0].minx + (this.x << 4);
|
|
int y = updateRanges[0].miny;
|
|
int z = updateRanges[0].minz + (this.z << 4);
|
|
|
|
updateRanges[0] = null;
|
|
|
|
Packet blockPacket = APILocator.getNetManager().wrap(new PacketMWSBlock(x, y, z, world), true);
|
|
|
|
TileEntity te = world.getTileEntity(x, y, z);
|
|
Packet tedesc = te == null ? null : te.getDescriptionPacket();
|
|
if(tedesc != null)
|
|
rv = Arrays.asList(APILocator.getNetManager().wrap(new PacketMWSTile(world, tedesc), true), blockPacket);
|
|
else
|
|
rv = Arrays.asList(blockPacket);
|
|
|
|
} else if(nUpdatedBlocks < 64 && nUpdateRanges <= updateRanges.length) {
|
|
PacketMWSMultiBlock mbp = new PacketMWSMultiBlock(this.x, this.z, nUpdatedBlocks, world);
|
|
rv = new ArrayList<Packet>(8);
|
|
rv.add(APILocator.getNetManager().wrap(mbp, true));
|
|
|
|
//Chunk c = world.getChunkFromChunkCoords(this.x, this.z);
|
|
|
|
for(int k = 0; k < nUpdateRanges; k++) {
|
|
Range r = updateRanges[k];
|
|
updateRanges[k] = null;
|
|
|
|
for(int x_ = r.minx; x_ < r.maxx; x_++)
|
|
for(int y = r.miny; y < r.maxy; y++)
|
|
for(int z_ = r.minz; z_ < r.maxz; z_++) {
|
|
int x = x_ + (this.x << 4);
|
|
int z = z_ + (this.z << 4);
|
|
|
|
mbp.addBlock(x, y, z, world);
|
|
|
|
TileEntity te = world.getTileEntity(x, y, z);
|
|
if(te != null) {
|
|
Packet teDesc = te.getDescriptionPacket();
|
|
if(teDesc != null)
|
|
rv.add(APILocator.getNetManager().wrap(new PacketMWSTile(world, teDesc), true));
|
|
}
|
|
}
|
|
}
|
|
|
|
} else {
|
|
Chunk c = world.getChunkFromChunkCoords(this.x, this.z);
|
|
rv = new ArrayList<Packet>(16);
|
|
rv.add(APILocator.getNetManager().wrap(new PacketMWSChunk(this.x, this.z, world, c), true));
|
|
for(TileEntity te : (Collection<TileEntity>)c.chunkTileEntityMap.values()) {
|
|
Packet teDesc = te.getDescriptionPacket();
|
|
if(teDesc != null)
|
|
rv.add(APILocator.getNetManager().wrap(new PacketMWSTile(world, teDesc), true));
|
|
}
|
|
}
|
|
|
|
nUpdateRanges = 0;
|
|
nUpdatedBlocks = 0;
|
|
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
private Set<SyncedChunk> changedChunks = new HashSet<SyncedChunk>();
|
|
private Set<SyncedChunk> allChunks = new HashSet<SyncedChunk>();
|
|
private LongHashMap chunks = new LongHashMap();
|
|
|
|
private SyncedChunk getSyncedChunk(int x, int z, boolean markForUpdate) {
|
|
long index = ChunkCoordIntPair.chunkXZ2Int(x, z);
|
|
SyncedChunk s = (SyncedChunk)chunks.getValueByKey(index);
|
|
if(s == null) {
|
|
s = new SyncedChunk(x, z);
|
|
chunks.add(index, s);
|
|
allChunks.add(s);
|
|
}
|
|
if(markForUpdate)
|
|
synchronized(changedChunks) {
|
|
changedChunks.add(s);
|
|
}
|
|
return s;
|
|
}
|
|
|
|
private class WorldListener implements IWorldAccess {
|
|
|
|
@Override
|
|
public void markBlockForUpdate(int var1, int var2, int var3) {
|
|
getSyncedChunk(var1 >> 4, var3 >> 4, true).addRange(new Range(var1&15, var2, var3&15, var1&15, var2, var3&15));
|
|
}
|
|
|
|
@Override
|
|
public void markBlockForRenderUpdate(int var1, int var2, int var3) {}
|
|
|
|
@Override
|
|
public void markBlockRangeForRenderUpdate(int var1, int var2, int var3,int var4, int var5, int var6) {
|
|
int mincx = var1 >> 4;
|
|
int mincz = var3 >> 4;
|
|
int maxcx = var4 >> 4;
|
|
int maxcz = var6 >> 4;
|
|
for(int cx = mincx; cx <= maxcx; cx++) {
|
|
int minx = (cx == mincx ? var1 & 15 : 0);
|
|
int maxx = (cx == maxcx ? (var4 & 15) + 1 : 16);
|
|
for(int cz = mincz; cz <= maxcz; cz++) {
|
|
int minz = (cz == mincz ? var3 & 15 : 0);
|
|
int maxz = (cz == maxcz ? (var6 & 15) + 1 : 16);
|
|
|
|
getSyncedChunk(cx, cz, true).addRange(new Range(minx, var2, minz, maxx, var5, maxz));
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void playSound(String var1, double var2, double var4, double var6, float var8, float var9) {
|
|
}
|
|
|
|
@Override
|
|
public void spawnParticle(String var1, double var2, double var4, double var6, double var8, double var10, double var12) {
|
|
}
|
|
|
|
@Override
|
|
public void onEntityCreate(Entity var1) {
|
|
}
|
|
|
|
@Override
|
|
public void onEntityDestroy(Entity var1) {
|
|
}
|
|
|
|
@Override
|
|
public void playRecord(String var1, int var2, int var3, int var4) {
|
|
}
|
|
|
|
@Override
|
|
public void playAuxSFX(EntityPlayer var1, int var2, int var3, int var4, int var5, int var6) {
|
|
}
|
|
|
|
@Override
|
|
public void destroyBlockPartially(int var1, int var2, int var3, int var4, int var5) {
|
|
}
|
|
|
|
@Override
|
|
public void playSoundToNearExcept(EntityPlayer var1, String var2, double var3, double var5, double var7, float var9, float var10) {
|
|
}
|
|
|
|
@Override
|
|
public void broadcastSound(int var1, int var2, int var3, int var4, int var5) {
|
|
}
|
|
|
|
@Override
|
|
public void onStaticEntitiesChanged() {
|
|
// TODO Auto-generated method stub
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/*private boolean isInViewingRange(SyncedChunk sc, MWSListener w) {
|
|
int minx = (sc.x - VIEW_DISTANCE) * 16;
|
|
int maxx = (sc.x + VIEW_DISTANCE + 1) * 16;
|
|
int minz = (sc.z - VIEW_DISTANCE) * 16;
|
|
int maxz = (sc.z + VIEW_DISTANCE + 1) * 16;
|
|
return w.x >= minx && w.z >= minz && w.x <= maxx && w.z <= maxz;
|
|
}*/
|
|
|
|
private void sendUpdate(SyncedChunk sc) {
|
|
Collection<Packet> packets = sc.nextUpdatePackets();
|
|
if(packets == null)
|
|
return;
|
|
|
|
int minx = (sc.x - VIEW_DISTANCE) * 16;
|
|
int maxx = (sc.x + VIEW_DISTANCE + 1) * 16;
|
|
int minz = (sc.z - VIEW_DISTANCE) * 16;
|
|
int maxz = (sc.z + VIEW_DISTANCE + 1) * 16;
|
|
|
|
for(MWSListener w : listeners) {
|
|
if(w.x >= minx && w.x <= maxx && w.z >= minz && w.z <= maxz) {
|
|
for(Packet p : packets) {
|
|
w.client.scheduleOutboundPacket(p);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void tick() {
|
|
for(MWSListener l : new ArrayList<MWSListener>(listeners)) {
|
|
l.update();
|
|
if(l.isDead)
|
|
listeners.remove(l);
|
|
|
|
if(l.ticksToNextRangeCheck <= 0) {
|
|
l.ticksToNextRangeCheck = l.RANGE_CHECK_INTERVAL;
|
|
updateLoadedChunks(l);
|
|
} else
|
|
l.ticksToNextRangeCheck--;
|
|
}
|
|
|
|
synchronized(changedChunks) {
|
|
if(!changedChunks.isEmpty()) {
|
|
Collection<SyncedChunk> copy = new ArrayList<SyncedChunk>(changedChunks);
|
|
changedChunks.clear();
|
|
for(SyncedChunk sc : copy)
|
|
sendUpdate(sc);
|
|
}
|
|
}
|
|
|
|
final int MAX_CHUNKS_PER_TICK = 5;
|
|
|
|
int nSent = 0;
|
|
|
|
while(sendQueue.size() > 0) {
|
|
SendQueueEntry e = sendQueue.poll();
|
|
if(!listeners.contains(e.l))
|
|
continue;
|
|
|
|
//if(SubWorldsMod.sendQueueThrottle(e.l.client))
|
|
// break;
|
|
|
|
//System.out.println("sending "+e.hashCode()+": "+e.cx+","+e.cz+" to "+e.l);
|
|
|
|
e.send();
|
|
nSent++;
|
|
if(nSent >= MAX_CHUNKS_PER_TICK)
|
|
break;
|
|
}
|
|
}
|
|
|
|
private static class SendQueueEntry {
|
|
public MWSListener l;
|
|
public int cx, cz;
|
|
public World world;
|
|
public Chunk c;
|
|
|
|
public SendQueueEntry(MWSListener l, int cx, int cz, World world, Chunk c) {
|
|
this.c = c;
|
|
this.cx = cx;
|
|
this.cz = cz;
|
|
this.world = world;
|
|
this.l = l;
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
public void send() {
|
|
APILocator.getNetManager().send(new PacketMWSChunk(cx, cz, world, c), l.client, true);
|
|
|
|
//APILocator.getNetManager().send(new PacketMWSSetWorld(world.provider.dimensionId), l.client, true);
|
|
for(TileEntity te : (Collection<TileEntity>)c.chunkTileEntityMap.values()) {
|
|
Packet teDesc = te.getDescriptionPacket();
|
|
if(teDesc != null)
|
|
l.client.scheduleOutboundPacket(APILocator.getNetManager().wrap(new PacketMWSTile(world, teDesc), true));
|
|
//l.client.scheduleOutboundPacket(teDesc);
|
|
}
|
|
//APILocator.getNetManager().send(new PacketMWSSetWorld(PacketMWSSetWorld.NORMAL_DIM), l.client, true);
|
|
}
|
|
}
|
|
|
|
private Queue<SendQueueEntry> sendQueue = new LinkedList<SendQueueEntry>();
|
|
|
|
public void addListener(MWSListener l) {
|
|
World world = worldObj.get();
|
|
if(world == null)
|
|
return;
|
|
|
|
l.update();
|
|
|
|
if(l.isDead)
|
|
return;
|
|
|
|
if(!listeners.add(l))
|
|
return;
|
|
|
|
int minx, minz, maxx, maxz;
|
|
|
|
if(world.provider instanceof DWWorldProvider) {
|
|
minx = minz = 0;
|
|
maxx = ((DWWorldProvider)world.provider).props.xsize >> 4;
|
|
maxz = ((DWWorldProvider)world.provider).props.zsize >> 4;
|
|
} else {
|
|
minx = (l.x>>4) - VIEW_DISTANCE;
|
|
maxx = (l.x>>4) + VIEW_DISTANCE + 1;
|
|
minz = (l.z>>4) - VIEW_DISTANCE;
|
|
maxz = (l.z>>4) + VIEW_DISTANCE + 1;
|
|
}
|
|
|
|
l.client.scheduleOutboundPacket(APILocator.getNetManager().wrap(new PacketMWSBegin(world.provider.dimensionId), true));
|
|
|
|
for(int x = minx; x <= maxx; x++)
|
|
for(int z = minz; z <= maxz; z++) {
|
|
sendQueue.add(new SendQueueEntry(l, x, z, world, world.getChunkFromChunkCoords(x, z)));
|
|
}
|
|
}
|
|
|
|
private void updateLoadedChunks(MWSListener l) {
|
|
int minx, minz, maxx, maxz;
|
|
|
|
World world = worldObj.get();
|
|
if(world == null)
|
|
return;
|
|
|
|
if(world.provider instanceof DWWorldProvider) {
|
|
minx = minz = 0;
|
|
maxx = ((DWWorldProvider)world.provider).props.xsize >> 4;
|
|
maxz = ((DWWorldProvider)world.provider).props.zsize >> 4;
|
|
} else {
|
|
minx = (l.x>>4) - VIEW_DISTANCE;
|
|
maxx = (l.x>>4) + VIEW_DISTANCE + 1;
|
|
minz = (l.z>>4) - VIEW_DISTANCE;
|
|
maxz = (l.z>>4) + VIEW_DISTANCE + 1;
|
|
}
|
|
|
|
Set<ChunkCoordIntPair> targetChunks = new HashSet<ChunkCoordIntPair>();
|
|
|
|
for(int x = minx; x <= maxx; x++)
|
|
for(int z = minz; z <= maxz; z++) {
|
|
//System.out.println(l.x+" "+l.z+" "+x+" "+z);
|
|
targetChunks.add(new ChunkCoordIntPair(x, z));
|
|
}
|
|
|
|
Set<ChunkCoordIntPair> toLoad = new HashSet<ChunkCoordIntPair>(targetChunks);
|
|
Set<ChunkCoordIntPair> toUnload = new HashSet<ChunkCoordIntPair>(l.loadedChunks);
|
|
toLoad.removeAll(l.loadedChunks);
|
|
toUnload.removeAll(targetChunks);
|
|
|
|
/*System.out.println("target: "+targetChunks);
|
|
System.out.println("loaded: "+l.loadedChunks);
|
|
System.out.println(" load: "+toLoad);
|
|
System.out.println("unload: "+toUnload);*/
|
|
|
|
for(ChunkCoordIntPair ccip : toLoad) {
|
|
l.loadedChunks.add(ccip);
|
|
//System.out.println("load "+ccip);
|
|
sendQueue.add(new SendQueueEntry(l, ccip.chunkXPos, ccip.chunkZPos, world, world.getChunkFromChunkCoords(ccip.chunkXPos, ccip.chunkZPos)));
|
|
}
|
|
|
|
for(ChunkCoordIntPair ccip : toUnload) {
|
|
l.client.scheduleOutboundPacket(APILocator.getNetManager().wrap(new PacketMWSUnload(world, ccip.chunkXPos, ccip.chunkZPos), true));
|
|
//System.out.println("unload "+ccip);
|
|
l.loadedChunks.remove(ccip);
|
|
}
|
|
}
|
|
|
|
public void removeListener(NetworkManager client) {
|
|
for(MWSListener l : listeners) {
|
|
if(l.client == client) {
|
|
removeListener(l);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void removeListener(MWSListener l) {
|
|
l.isDead = true;
|
|
listeners.remove(l);
|
|
l.client.scheduleOutboundPacket(APILocator.getNetManager().wrap(new PacketMWSEnd(dimensionID), true));
|
|
}
|
|
|
|
void onWorldUnload() {
|
|
Packet p = APILocator.getNetManager().wrap(new PacketMWSEnd(dimensionID), true);
|
|
for(MWSListener l : listeners) {
|
|
l.isDead = true;
|
|
l.client.scheduleOutboundPacket(p);
|
|
}
|
|
listeners.clear();
|
|
worldObj.clear();
|
|
}
|
|
}
|