Skip to content

Commit f5248c9

Browse files
committed
Client virtual worlds, first draft
1 parent eb2f77d commit f5248c9

File tree

13 files changed

+810
-2
lines changed

13 files changed

+810
-2
lines changed

src/main/java/net/earthcomputer/clientcommands/ClientCommandsMod.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import net.earthcomputer.clientcommands.command.CommandCGive;
99
import net.earthcomputer.clientcommands.command.CommandCHelp;
1010
import net.earthcomputer.clientcommands.command.CommandCTime;
11+
import net.earthcomputer.clientcommands.command.CommandCVW;
1112
import net.earthcomputer.clientcommands.command.CommandCalc;
1213
import net.earthcomputer.clientcommands.command.CommandFind;
1314
import net.earthcomputer.clientcommands.command.CommandFindBlock;
@@ -61,6 +62,7 @@ private void registerCommands() {
6162
ClientCommandHandler.instance.registerCommand(new CommandTempRule());
6263
ClientCommandHandler.instance.registerCommand(new CommandCEnchant());
6364
ClientCommandHandler.instance.registerCommand(new CommandCTime());
65+
ClientCommandHandler.instance.registerCommand(new CommandCVW());
6466
}
6567

6668
private void registerEventStuff() {
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package net.earthcomputer.clientcommands.command;
2+
3+
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
4+
import net.earthcomputer.clientcommands.cvw.ClientVirtualChunkLoader;
5+
import net.earthcomputer.clientcommands.cvw.ServerConnector;
6+
import net.earthcomputer.clientcommands.cvw.ServerConnectorCVW;
7+
import net.minecraft.client.Minecraft;
8+
import net.minecraft.client.multiplayer.WorldClient;
9+
import net.minecraft.command.CommandException;
10+
import net.minecraft.command.ICommandSender;
11+
import net.minecraft.command.WrongUsageException;
12+
import net.minecraft.nbt.NBTTagCompound;
13+
import net.minecraft.server.MinecraftServer;
14+
import net.minecraft.world.GameType;
15+
import net.minecraft.world.WorldSettings;
16+
17+
public class CommandCVW extends ClientCommandBase {
18+
19+
@Override
20+
public String getName() {
21+
return "cvw";
22+
}
23+
24+
@Override
25+
public String getUsage(ICommandSender sender) {
26+
return "commands.cvw.usage";
27+
}
28+
29+
@Override
30+
public void execute(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException {
31+
if (args.length < 1) {
32+
throw new WrongUsageException(getUsage(sender));
33+
}
34+
switch (args[0]) {
35+
case "stop":
36+
ServerConnector connector = ServerConnector.forCurrentServer();
37+
if (connector instanceof ServerConnectorCVW) {
38+
connector.onDisconnectButtonPressed();
39+
} else {
40+
throw new CommandException("commands.cvw.stop.notRunning");
41+
}
42+
break;
43+
case "start":
44+
GameType gameType;
45+
if (args.length < 2) {
46+
gameType = GameType.CREATIVE;
47+
} else {
48+
gameType = GameType.parseGameTypeWithDefault(args[1], GameType.NOT_SET);
49+
if (gameType == GameType.NOT_SET) {
50+
gameType = WorldSettings.getGameTypeById(parseInt(args[1], 0, GameType.values().length - 2));
51+
}
52+
}
53+
startCVW(gameType);
54+
break;
55+
default:
56+
throw new WrongUsageException(getUsage(sender));
57+
}
58+
}
59+
60+
private static void startCVW(GameType gameType) {
61+
WorldClient world = Minecraft.getMinecraft().world;
62+
Long2ObjectMap<NBTTagCompound> chunks = ClientVirtualChunkLoader.generateChunkMap(world,
63+
world.getChunkProvider());
64+
NBTTagCompound playerTag = Minecraft.getMinecraft().player.writeToNBT(new NBTTagCompound());
65+
ServerConnector oldServer = ServerConnector.forCurrentServer();
66+
oldServer.disconnect();
67+
ServerConnectorCVW newServer = new ServerConnectorCVW(world.provider.getDimension(), gameType, oldServer,
68+
chunks, playerTag);
69+
newServer.connect();
70+
}
71+
72+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package net.earthcomputer.clientcommands.cvw;
2+
3+
import java.util.List;
4+
5+
import net.minecraft.entity.EnumCreatureType;
6+
import net.minecraft.util.math.BlockPos;
7+
import net.minecraft.world.World;
8+
import net.minecraft.world.biome.Biome.SpawnListEntry;
9+
import net.minecraft.world.chunk.Chunk;
10+
import net.minecraft.world.chunk.EmptyChunk;
11+
import net.minecraft.world.gen.IChunkGenerator;
12+
13+
public class ClientVirtualChunkGenerator implements IChunkGenerator {
14+
15+
private World world;
16+
private IChunkGenerator proxy;
17+
18+
public ClientVirtualChunkGenerator(World world, IChunkGenerator proxy) {
19+
this.world = world;
20+
this.proxy = proxy;
21+
}
22+
23+
@Override
24+
public Chunk generateChunk(int x, int z) {
25+
return new EmptyChunk(world, x, z);
26+
}
27+
28+
@Override
29+
public void populate(int x, int z) {
30+
}
31+
32+
@Override
33+
public boolean generateStructures(Chunk chunkIn, int x, int z) {
34+
return false;
35+
}
36+
37+
@Override
38+
public List<SpawnListEntry> getPossibleCreatures(EnumCreatureType creatureType, BlockPos pos) {
39+
return proxy.getPossibleCreatures(creatureType, pos);
40+
}
41+
42+
@Override
43+
public BlockPos getNearestStructurePos(World worldIn, String structureName, BlockPos position,
44+
boolean findUnexplored) {
45+
return null;
46+
}
47+
48+
@Override
49+
public void recreateStructures(Chunk chunkIn, int x, int z) {
50+
}
51+
52+
@Override
53+
public boolean isInsideStructure(World worldIn, String structureName, BlockPos pos) {
54+
return false;
55+
}
56+
57+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package net.earthcomputer.clientcommands.cvw;
2+
3+
import java.io.IOException;
4+
import java.lang.reflect.Method;
5+
6+
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
7+
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
8+
import net.minecraft.client.multiplayer.ChunkProviderClient;
9+
import net.minecraft.client.multiplayer.WorldClient;
10+
import net.minecraft.nbt.NBTTagCompound;
11+
import net.minecraft.util.math.ChunkPos;
12+
import net.minecraft.world.MinecraftException;
13+
import net.minecraft.world.World;
14+
import net.minecraft.world.chunk.Chunk;
15+
import net.minecraft.world.chunk.storage.AnvilChunkLoader;
16+
import net.minecraft.world.chunk.storage.IChunkLoader;
17+
import net.minecraftforge.common.ForgeChunkManager;
18+
import net.minecraftforge.common.MinecraftForge;
19+
import net.minecraftforge.event.world.ChunkDataEvent;
20+
import net.minecraftforge.fml.common.FMLCommonHandler;
21+
import net.minecraftforge.fml.relauncher.ReflectionHelper;
22+
23+
public class ClientVirtualChunkLoader implements IChunkLoader {
24+
25+
private static final AnvilChunkLoader ANVIL_CHUNK_LOADER = new AnvilChunkLoader(null, null);
26+
27+
private static final Method READ_FROM_NBT_METHOD = ReflectionHelper.findMethod(AnvilChunkLoader.class,
28+
"checkedReadChunkFromNBT__Async", "checkedReadChunkFromNBT__Async", World.class, int.class, int.class,
29+
NBTTagCompound.class);
30+
31+
private Long2ObjectMap<NBTTagCompound> chunks;
32+
33+
public ClientVirtualChunkLoader(Long2ObjectMap<NBTTagCompound> chunks) {
34+
this.chunks = chunks;
35+
}
36+
37+
public static Long2ObjectMap<NBTTagCompound> generateChunkMap(WorldClient world,
38+
ChunkProviderClient chunkProvider) {
39+
Long2ObjectMap<NBTTagCompound> chunkMap = new Long2ObjectOpenHashMap<>();
40+
chunkProvider.chunkMapping.forEach((pos, chunk) -> {
41+
NBTTagCompound nbt = new NBTTagCompound();
42+
NBTTagCompound levelTag = new NBTTagCompound();
43+
nbt.setTag("Level", levelTag);
44+
nbt.setInteger("DataVersion", 1343);
45+
FMLCommonHandler.instance().getDataFixer().writeVersionData(nbt);
46+
ANVIL_CHUNK_LOADER.writeChunkToNBT(chunk, world, levelTag);
47+
ForgeChunkManager.storeChunkNBT(chunk, levelTag);
48+
MinecraftForge.EVENT_BUS.post(new ChunkDataEvent.Save(chunk, nbt));
49+
chunkMap.put(pos, nbt);
50+
});
51+
return chunkMap;
52+
}
53+
54+
@Override
55+
public Chunk loadChunk(World world, int x, int z) throws IOException {
56+
NBTTagCompound nbt = chunks.get(ChunkPos.asLong(x, z));
57+
if (nbt == null) {
58+
return null;
59+
}
60+
61+
Object[] ret;
62+
try {
63+
ret = (Object[]) READ_FROM_NBT_METHOD.invoke(ANVIL_CHUNK_LOADER, world, x, z, nbt);
64+
} catch (Exception e) {
65+
throw new RuntimeException(e);
66+
}
67+
if (ret != null) {
68+
Chunk chunk = (Chunk) ret[0];
69+
nbt = (NBTTagCompound) ret[1];
70+
ANVIL_CHUNK_LOADER.loadEntities(world, nbt.getCompoundTag("Level"), chunk);
71+
return chunk;
72+
}
73+
74+
return null;
75+
}
76+
77+
@Override
78+
public void saveChunk(World worldIn, Chunk chunkIn) throws MinecraftException, IOException {
79+
}
80+
81+
@Override
82+
public void saveExtraChunkData(World worldIn, Chunk chunkIn) throws IOException {
83+
}
84+
85+
@Override
86+
public void chunkTick() {
87+
}
88+
89+
@Override
90+
public void flush() {
91+
}
92+
93+
@Override
94+
public boolean isChunkGeneratedAt(int x, int z) {
95+
return chunks.containsKey(ChunkPos.asLong(x, z));
96+
}
97+
98+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package net.earthcomputer.clientcommands.cvw;
2+
3+
import net.minecraft.world.chunk.storage.IChunkLoader;
4+
import net.minecraft.world.gen.ChunkProviderServer;
5+
import net.minecraft.world.gen.IChunkGenerator;
6+
7+
public class ClientVirtualChunkProvider extends ChunkProviderServer {
8+
9+
public ClientVirtualChunkProvider(ClientVirtualWorld world, IChunkLoader chunkLoader,
10+
IChunkGenerator chunkGenerator) {
11+
super(world, chunkLoader, chunkGenerator);
12+
}
13+
14+
@Override
15+
public boolean tick() {
16+
world.disableLevelSaving = false;
17+
boolean ret = super.tick();
18+
world.disableLevelSaving = true;
19+
return ret;
20+
}
21+
22+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package net.earthcomputer.clientcommands.cvw;
2+
3+
import java.io.File;
4+
import java.util.HashMap;
5+
import java.util.Map;
6+
import java.util.UUID;
7+
8+
import net.minecraft.client.Minecraft;
9+
import net.minecraft.entity.player.EntityPlayer;
10+
import net.minecraft.nbt.NBTTagCompound;
11+
import net.minecraft.server.MinecraftServer;
12+
import net.minecraft.util.ResourceLocation;
13+
import net.minecraft.world.MinecraftException;
14+
import net.minecraft.world.WorldProvider;
15+
import net.minecraft.world.WorldServerDemo;
16+
import net.minecraft.world.chunk.storage.IChunkLoader;
17+
import net.minecraft.world.gen.structure.template.TemplateManager;
18+
import net.minecraft.world.storage.IPlayerFileData;
19+
import net.minecraft.world.storage.SaveHandler;
20+
import net.minecraft.world.storage.WorldInfo;
21+
import net.minecraftforge.event.ForgeEventFactory;
22+
23+
public class ClientVirtualSaveHandler extends SaveHandler {
24+
25+
public static final File DUMMY_DIR = new File(Minecraft.getMinecraft().mcDataDir, "cvwWorldDir");
26+
27+
static {
28+
DUMMY_DIR.mkdirs();
29+
DUMMY_DIR.deleteOnExit();
30+
}
31+
32+
public ClientVirtualSaveHandler() {
33+
super(DUMMY_DIR, "CVW", false, Minecraft.getMinecraft().getDataFixer());
34+
}
35+
36+
@Override
37+
public WorldInfo loadWorldInfo() {
38+
return new WorldInfo(WorldServerDemo.DEMO_WORLD_SETTINGS, "CVW");
39+
}
40+
41+
@Override
42+
public void checkSessionLock() throws MinecraftException {
43+
}
44+
45+
@Override
46+
public IChunkLoader getChunkLoader(WorldProvider provider) {
47+
throw new UnsupportedOperationException("This should only be called on world creation");
48+
}
49+
50+
@Override
51+
public void saveWorldInfoWithPlayer(WorldInfo worldInformation, NBTTagCompound tagCompound) {
52+
}
53+
54+
@Override
55+
public void saveWorldInfo(WorldInfo worldInformation) {
56+
}
57+
58+
@Override
59+
public IPlayerFileData getPlayerNBTManager() {
60+
return this;
61+
}
62+
63+
@Override
64+
public void flush() {
65+
}
66+
67+
@Override
68+
public File getWorldDirectory() {
69+
return DUMMY_DIR;
70+
}
71+
72+
@Override
73+
public File getMapFileFromName(String mapName) {
74+
return new File(DUMMY_DIR, mapName + ".dat");
75+
}
76+
77+
@Override
78+
public TemplateManager getStructureTemplateManager() {
79+
return new TemplateManager(null, Minecraft.getMinecraft().getDataFixer()) {
80+
@Override
81+
public boolean readTemplate(ResourceLocation server) {
82+
return false;
83+
}
84+
85+
@Override
86+
public boolean writeTemplate(MinecraftServer server, ResourceLocation id) {
87+
return false;
88+
}
89+
};
90+
}
91+
92+
private Map<UUID, NBTTagCompound> playerData = new HashMap<>();
93+
94+
@Override
95+
public void writePlayerData(EntityPlayer player) {
96+
playerData.put(player.getUniqueID(), player.writeToNBT(new NBTTagCompound()));
97+
ForgeEventFactory.firePlayerSavingEvent(player, new File(DUMMY_DIR, "players"),
98+
player.getUniqueID().toString());
99+
}
100+
101+
@Override
102+
public NBTTagCompound readPlayerData(EntityPlayer player) {
103+
NBTTagCompound nbt = playerData.get(player.getUniqueID());
104+
if (nbt != null) {
105+
player.readFromNBT(nbt);
106+
}
107+
ForgeEventFactory.firePlayerLoadingEvent(player, new File(DUMMY_DIR, "players"),
108+
player.getUniqueID().toString());
109+
return nbt;
110+
}
111+
112+
@Override
113+
public String[] getAvailablePlayerDat() {
114+
return playerData.keySet().stream().map(UUID::toString).toArray(String[]::new);
115+
}
116+
117+
}

0 commit comments

Comments
 (0)