From b68fd76c8ff864eb8d345589877a849e5761d683 Mon Sep 17 00:00:00 2001 From: Timo Ley Date: Sat, 13 May 2023 14:28:00 +0200 Subject: [PATCH] chore: add original code --- .gitignore | 1 + LICENSE | 19 + build.gradle | 14 +- .../immibis/subworlds/ClientFakeEntities.java | 65 +++ .../immibis/subworlds/ExitTeleporter.java | 27 ++ .../mods/immibis/subworlds/FakeEntity.java | 110 +++++ .../mods/immibis/subworlds/SubWorldsMod.java | 52 ++ .../subworlds/dw/ChunkLoadingCallback.java | 53 ++ .../subworlds/dw/DWChunkGenerator.java | 160 ++++++ .../mods/immibis/subworlds/dw/DWEntity.java | 167 +++++++ .../subworlds/dw/DWEntityRenderer.java | 406 ++++++++++++++++ .../mods/immibis/subworlds/dw/DWManager.java | 231 +++++++++ .../immibis/subworlds/dw/DWTeleporter.java | 31 ++ .../mods/immibis/subworlds/dw/DWUtils.java | 22 + .../subworlds/dw/DWWorldChunkManager.java | 83 ++++ .../immibis/subworlds/dw/DWWorldProvider.java | 226 +++++++++ .../subworlds/dw/PacketDWDimensionList.java | 61 +++ .../mods/immibis/subworlds/dw/WorldProps.java | 32 ++ .../immibis/subworlds/mws/MWSClientWorld.java | 79 +++ .../immibis/subworlds/mws/MWSListener.java | 34 ++ .../immibis/subworlds/mws/MWSManager.java | 204 ++++++++ .../subworlds/mws/MWSWorldManager.java | 457 ++++++++++++++++++ .../subworlds/mws/packets/PacketMWSBegin.java | 53 ++ .../subworlds/mws/packets/PacketMWSBlock.java | 77 +++ .../subworlds/mws/packets/PacketMWSChunk.java | 186 +++++++ .../subworlds/mws/packets/PacketMWSEnd.java | 55 +++ .../mws/packets/PacketMWSMultiBlock.java | 107 ++++ .../mws/packets/PacketMWSSetWorld.java | 64 +++ .../subworlds/mws/packets/PacketMWSTile.java | 124 +++++ .../mws/packets/PacketMWSUnload.java | 62 +++ .../tinycarts/BlockTransparentBedrock.java | 85 ++++ .../tinycarts/CartExternalFakeEntity.java | 271 +++++++++++ .../immibis/tinycarts/CommandExitCart.java | 31 ++ .../tinycarts/EntityMinecartAwesome.java | 340 +++++++++++++ .../immibis/tinycarts/InteriorChunkGen.java | 120 +++++ .../tinycarts/ItemMinecartAwesome.java | 57 +++ .../immibis/tinycarts/NetworkHandler.java | 34 ++ .../tinycarts/RenderMinecartAwesome.java | 14 + .../mods/immibis/tinycarts/TinyCartsMod.java | 122 +++++ .../assets/tinycarts/lang/en_US.lang | 2 + .../tinycarts/textures/entity/minecart.png | Bin 0 -> 2345 bytes .../tinycarts/textures/items/minecart.png | Bin 0 -> 354 bytes 42 files changed, 4334 insertions(+), 4 deletions(-) create mode 100644 LICENSE create mode 100644 src/main/java/mods/immibis/subworlds/ClientFakeEntities.java create mode 100644 src/main/java/mods/immibis/subworlds/ExitTeleporter.java create mode 100644 src/main/java/mods/immibis/subworlds/FakeEntity.java create mode 100644 src/main/java/mods/immibis/subworlds/SubWorldsMod.java create mode 100644 src/main/java/mods/immibis/subworlds/dw/ChunkLoadingCallback.java create mode 100644 src/main/java/mods/immibis/subworlds/dw/DWChunkGenerator.java create mode 100644 src/main/java/mods/immibis/subworlds/dw/DWEntity.java create mode 100644 src/main/java/mods/immibis/subworlds/dw/DWEntityRenderer.java create mode 100644 src/main/java/mods/immibis/subworlds/dw/DWManager.java create mode 100644 src/main/java/mods/immibis/subworlds/dw/DWTeleporter.java create mode 100644 src/main/java/mods/immibis/subworlds/dw/DWUtils.java create mode 100644 src/main/java/mods/immibis/subworlds/dw/DWWorldChunkManager.java create mode 100644 src/main/java/mods/immibis/subworlds/dw/DWWorldProvider.java create mode 100644 src/main/java/mods/immibis/subworlds/dw/PacketDWDimensionList.java create mode 100644 src/main/java/mods/immibis/subworlds/dw/WorldProps.java create mode 100644 src/main/java/mods/immibis/subworlds/mws/MWSClientWorld.java create mode 100644 src/main/java/mods/immibis/subworlds/mws/MWSListener.java create mode 100644 src/main/java/mods/immibis/subworlds/mws/MWSManager.java create mode 100644 src/main/java/mods/immibis/subworlds/mws/MWSWorldManager.java create mode 100644 src/main/java/mods/immibis/subworlds/mws/packets/PacketMWSBegin.java create mode 100644 src/main/java/mods/immibis/subworlds/mws/packets/PacketMWSBlock.java create mode 100644 src/main/java/mods/immibis/subworlds/mws/packets/PacketMWSChunk.java create mode 100644 src/main/java/mods/immibis/subworlds/mws/packets/PacketMWSEnd.java create mode 100644 src/main/java/mods/immibis/subworlds/mws/packets/PacketMWSMultiBlock.java create mode 100644 src/main/java/mods/immibis/subworlds/mws/packets/PacketMWSSetWorld.java create mode 100644 src/main/java/mods/immibis/subworlds/mws/packets/PacketMWSTile.java create mode 100644 src/main/java/mods/immibis/subworlds/mws/packets/PacketMWSUnload.java create mode 100644 src/main/java/mods/immibis/tinycarts/BlockTransparentBedrock.java create mode 100644 src/main/java/mods/immibis/tinycarts/CartExternalFakeEntity.java create mode 100644 src/main/java/mods/immibis/tinycarts/CommandExitCart.java create mode 100644 src/main/java/mods/immibis/tinycarts/EntityMinecartAwesome.java create mode 100644 src/main/java/mods/immibis/tinycarts/InteriorChunkGen.java create mode 100644 src/main/java/mods/immibis/tinycarts/ItemMinecartAwesome.java create mode 100644 src/main/java/mods/immibis/tinycarts/NetworkHandler.java create mode 100644 src/main/java/mods/immibis/tinycarts/RenderMinecartAwesome.java create mode 100644 src/main/java/mods/immibis/tinycarts/TinyCartsMod.java create mode 100644 src/main/resources/assets/tinycarts/lang/en_US.lang create mode 100644 src/main/resources/assets/tinycarts/textures/entity/minecart.png create mode 100644 src/main/resources/assets/tinycarts/textures/items/minecart.png diff --git a/.gitignore b/.gitignore index 8d16946..4d59512 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ .idea build run +bin \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..519cd73 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2012 Alex "immibis" Campbell + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/build.gradle b/build.gradle index 585fd0f..93177d2 100644 --- a/build.gradle +++ b/build.gradle @@ -23,9 +23,9 @@ apply plugin: 'maven-publish' sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 -version = "1.0" -group= "modgroup" -archivesBaseName = "modid" +version = "0.2" +group= "tinycarts" +archivesBaseName = "com.immibis" minecraft { version = "1.7.10-10.13.4.1614-1.7.10" @@ -34,10 +34,16 @@ minecraft { repositories { maven { url = "https://maven.tilera.xyz" } + maven { + url = "https://s3.tilera.xyz/cdn/minecraft/libs/" + metadataSources { + artifact() + } + } } dependencies { - + implementation 'com.immibis:immibis-core:59.1.4:deobf' } processResources { diff --git a/src/main/java/mods/immibis/subworlds/ClientFakeEntities.java b/src/main/java/mods/immibis/subworlds/ClientFakeEntities.java new file mode 100644 index 0000000..8a483c1 --- /dev/null +++ b/src/main/java/mods/immibis/subworlds/ClientFakeEntities.java @@ -0,0 +1,65 @@ +package mods.immibis.subworlds; + +import java.util.HashMap; +import java.util.Map; + +import net.minecraftforge.client.event.RenderWorldLastEvent; +import net.minecraftforge.common.MinecraftForge; +import cpw.mods.fml.common.FMLCommonHandler; +import cpw.mods.fml.common.eventhandler.SubscribeEvent; +import cpw.mods.fml.common.gameevent.TickEvent; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +// maintains a list of fake entities on the client. there is no server equivalent. +// on the server, fake entities are updated and destroyed by whatever created them. +@SideOnly(Side.CLIENT) +public class ClientFakeEntities { + private static Map ents = new HashMap(); + + public static void debug(String s) { + System.out.println("[SubWorlds CFE Debug] "+s); + } + + public static void add(FakeEntity ent) { + debug("Add CFE "+ent.entityID+": "+ent); + ents.put(ent.entityID, ent); + } + + public static FakeEntity remove(int entID) { + debug("Remove CFE "+entID); + return ents.remove(entID); + } + + public static void reset() { + debug("Reset CFEs"); + ents.clear(); + } + + public static FakeEntity get(int entID) { + return ents.get(entID); + } + + public static void init() { + FMLCommonHandler.instance().bus().register(new ClientTickHandler()); + MinecraftForge.EVENT_BUS.register(new EventListener()); + } + + public static class ClientTickHandler { + @SubscribeEvent + public void onClientTickEnd(TickEvent.ClientTickEvent evt) { + if(evt.phase != TickEvent.Phase.END) return; + for(FakeEntity e : ents.values()) + e.tick(); + } + } + + // must be public or forge throws errors + public static class EventListener { + @SubscribeEvent + public void onRWL(RenderWorldLastEvent evt) { + for(FakeEntity e : ents.values()) + e.renderBase(evt.partialTicks); + } + } +} diff --git a/src/main/java/mods/immibis/subworlds/ExitTeleporter.java b/src/main/java/mods/immibis/subworlds/ExitTeleporter.java new file mode 100644 index 0000000..5864c68 --- /dev/null +++ b/src/main/java/mods/immibis/subworlds/ExitTeleporter.java @@ -0,0 +1,27 @@ +package mods.immibis.subworlds; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.world.Teleporter; +import net.minecraft.world.WorldServer; + +public class ExitTeleporter extends Teleporter { + public WorldServer world; + public double x, y, z; + + public ExitTeleporter(WorldServer par1WorldServer, double x, double y, double z) { + super(par1WorldServer); + world = par1WorldServer; + this.x = x; + this.y = y; + this.z = z; + } + + @Override + public void placeInPortal(Entity par1Entity, double par2, double par4, double par6, float par8) { + if(par1Entity instanceof EntityLivingBase) + ((EntityLivingBase)par1Entity).setPositionAndUpdate(x, y, z); + else + par1Entity.setPosition(x, y, z); + } +} diff --git a/src/main/java/mods/immibis/subworlds/FakeEntity.java b/src/main/java/mods/immibis/subworlds/FakeEntity.java new file mode 100644 index 0000000..5646d3d --- /dev/null +++ b/src/main/java/mods/immibis/subworlds/FakeEntity.java @@ -0,0 +1,110 @@ +package mods.immibis.subworlds; + +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.entity.EntityClientPlayerMP; +import net.minecraft.client.renderer.entity.RenderEntity; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.network.Packet; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +/** + * An object which is synced with clients, like an entity, but isn't actually an entity. + * They are not automatically associated with a world. + */ +public abstract class FakeEntity { + public abstract Packet getUpdatePacket(); + public abstract Packet getDescriptionPacket(); + public abstract Packet getDestructionPacket(); + + private static AtomicInteger nextEntityID = new AtomicInteger(); + + public final int entityID; + public final boolean isClient; + + public FakeEntity(boolean isClient) { + entityID = nextEntityID.incrementAndGet(); + this.isClient = isClient; + } + + public FakeEntity(boolean isClient, int entityID) { + this.entityID = entityID; + this.isClient = isClient; + } + + private Set trackingPlayers = new HashSet(); + + public final void setTrackingPlayers(Set _new) { + for(EntityPlayerMP pl : _new) + if(!trackingPlayers.contains(pl)) { + Packet packet = getDescriptionPacket(); + if(packet != null) + pl.playerNetServerHandler.netManager.channel().write(packet); + } + + for(EntityPlayerMP pl : trackingPlayers) + if(!_new.contains(pl)) { + Packet packet = getDestructionPacket(); + if(packet != null) + pl.playerNetServerHandler.netManager.channel().write(packet); + } + + trackingPlayers.clear(); + trackingPlayers.addAll(_new); + } + + public void tick() { + if(!isClient) { + Packet p = getUpdatePacket(); + + if(p != null) + for(EntityPlayerMP pl : trackingPlayers) + pl.playerNetServerHandler.netManager.channel().write(p); + } + } + + @SideOnly(Side.CLIENT) + protected static RenderEntity defaultRender; + @SideOnly(Side.CLIENT) + protected static Entity defaultRenderEntity; + + static { + try { + RenderEntity rd = new RenderEntity(); + defaultRender = rd; + defaultRenderEntity = new Entity(null) { + { + boundingBox.setBounds(-1, -1, -1, 1, 1, 1); + lastTickPosX = lastTickPosY = lastTickPosZ = 0; + } + + @Override protected void entityInit() {} + @Override protected void readEntityFromNBT(NBTTagCompound var1) {} + @Override protected void writeEntityToNBT(NBTTagCompound var1) {} + }; + } catch(Error e) { + } + } + + @SideOnly(Side.CLIENT) + // rendering origin is at world coordinates: -pl_x,-pl_y,-pl_z + public void render(double pl_x, double pl_y, double pl_z, float partialTick) { + defaultRender.doRender(defaultRenderEntity, 3, 3, 3, 0, partialTick); + defaultRender.doRender(defaultRenderEntity, -pl_x-2, -pl_y-2, -pl_z-2, 0, partialTick); + } + + @SideOnly(Side.CLIENT) + void renderBase(float partialTick) { + EntityClientPlayerMP pl = Minecraft.getMinecraft().thePlayer; + double pl_x = pl.lastTickPosX + (pl.posX - pl.lastTickPosX) * partialTick; + double pl_y = pl.lastTickPosY + (pl.posY - pl.lastTickPosY) * partialTick; + double pl_z = pl.lastTickPosZ + (pl.posZ - pl.lastTickPosZ) * partialTick; + render(pl_x, pl_y, pl_z, partialTick); + } +} diff --git a/src/main/java/mods/immibis/subworlds/SubWorldsMod.java b/src/main/java/mods/immibis/subworlds/SubWorldsMod.java new file mode 100644 index 0000000..a4a0878 --- /dev/null +++ b/src/main/java/mods/immibis/subworlds/SubWorldsMod.java @@ -0,0 +1,52 @@ +package mods.immibis.subworlds; + +import mods.immibis.core.api.porting.SidedProxy; +import mods.immibis.subworlds.dw.DWEntity; +import mods.immibis.subworlds.dw.DWEntityRenderer; +import mods.immibis.subworlds.dw.DWManager; +import mods.immibis.subworlds.mws.MWSManager; +import cpw.mods.fml.client.registry.RenderingRegistry; +import cpw.mods.fml.common.FMLCommonHandler; +import cpw.mods.fml.common.Mod; +import cpw.mods.fml.common.Mod.EventHandler; +import cpw.mods.fml.common.Mod.Instance; +import cpw.mods.fml.common.event.FMLInitializationEvent; +import cpw.mods.fml.common.eventhandler.SubscribeEvent; +import cpw.mods.fml.common.network.FMLNetworkEvent; +import cpw.mods.fml.common.registry.EntityRegistry; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +@Mod(version="0.0", dependencies="required-after:ImmibisCore", modid=SubWorldsMod.MODID, name="SubWorlds") +public class SubWorldsMod { + + public static final String MODID = "SubWorlds"; + + @Instance(MODID) + public static SubWorldsMod INSTANCE; + + @SubscribeEvent + @SideOnly(Side.CLIENT) + public void onClientConnectedToServer(FMLNetworkEvent.ClientConnectedToServerEvent evt) { + ClientFakeEntities.reset(); + } + + @SideOnly(Side.CLIENT) + public static class ClientProxy { + public ClientProxy() { + ClientFakeEntities.init(); + RenderingRegistry.registerEntityRenderingHandler(DWEntity.class, new DWEntityRenderer()); + } + } + + @EventHandler + public void init(FMLInitializationEvent evt) { + DWManager.init(); + MWSManager.init(); + + FMLCommonHandler.instance().bus().register(this); + + SidedProxy.instance.createSidedObject("mods.immibis.subworlds.SubWorldsMod$ClientProxy", null); + EntityRegistry.registerModEntity(DWEntity.class, "detachedWorld", 0, this, 200, 5, true); + } +} diff --git a/src/main/java/mods/immibis/subworlds/dw/ChunkLoadingCallback.java b/src/main/java/mods/immibis/subworlds/dw/ChunkLoadingCallback.java new file mode 100644 index 0000000..0e0dc1a --- /dev/null +++ b/src/main/java/mods/immibis/subworlds/dw/ChunkLoadingCallback.java @@ -0,0 +1,53 @@ +package mods.immibis.subworlds.dw; + + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import mods.immibis.subworlds.SubWorldsMod; +import net.minecraft.world.World; +import net.minecraftforge.common.ForgeChunkManager; +import net.minecraftforge.common.ForgeChunkManager.LoadingCallback; +import net.minecraftforge.common.ForgeChunkManager.OrderedLoadingCallback; +import net.minecraftforge.common.ForgeChunkManager.Ticket; +import cpw.mods.fml.relauncher.ReflectionHelper; + +public class ChunkLoadingCallback implements LoadingCallback, OrderedLoadingCallback { + + @Override + public void ticketsLoaded(List tickets, World world) { + } + + @Override + public List ticketsLoaded(List tickets, World world, int maxTicketCount) { + // do not keep chunks loaded through world reloads + return Collections.emptyList(); + } + + public ChunkLoadingCallback() { + try { + boolean overridesEnabled = ReflectionHelper.getPrivateValue(ForgeChunkManager.class, null, "overridesEnabled"); + + Map ticketConstraints = ReflectionHelper.getPrivateValue(ForgeChunkManager.class, null, "ticketConstraints"); + Map chunkConstraints = ReflectionHelper.getPrivateValue(ForgeChunkManager.class, null, "chunkConstraints"); + + if(!overridesEnabled) { + ticketConstraints.clear(); + chunkConstraints.clear(); + ReflectionHelper.findField(ForgeChunkManager.class, "overridesEnabled").set(null, true); + } + + String modid = SubWorldsMod.MODID; + + ticketConstraints.put(modid, Integer.MAX_VALUE); + chunkConstraints.put(modid, Integer.MAX_VALUE); + + } catch(Exception e) { + throw new RuntimeException(e); + } + + ForgeChunkManager.setForcedChunkLoadingCallback(SubWorldsMod.INSTANCE, this); + } + +} diff --git a/src/main/java/mods/immibis/subworlds/dw/DWChunkGenerator.java b/src/main/java/mods/immibis/subworlds/dw/DWChunkGenerator.java new file mode 100644 index 0000000..8d9ef22 --- /dev/null +++ b/src/main/java/mods/immibis/subworlds/dw/DWChunkGenerator.java @@ -0,0 +1,160 @@ +package mods.immibis.subworlds.dw; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import net.minecraft.entity.EnumCreatureType; +import net.minecraft.init.Blocks; +import net.minecraft.util.IProgressUpdate; +import net.minecraft.world.ChunkPosition; +import net.minecraft.world.World; +import net.minecraft.world.WorldServer; +import net.minecraft.world.biome.BiomeGenBase; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.IChunkProvider; + +public class DWChunkGenerator implements IChunkProvider { + + public static final BiomeGenBase BIOME = BiomeGenBase.plains; + + public final DWWorldProvider provider; + private final int HSIZE; // chunks + + private WorldServer worldObj; + //private EmptyChunk emptyChunk; + + public DWChunkGenerator(WorldServer worldObj, DWWorldProvider provider) { + this.worldObj = worldObj; + this.provider = provider; + this.HSIZE = Math.max(provider.props.zsize, provider.props.xsize) / 16; + //emptyChunk = new EmptyChunk(worldObj, -10000, -10000); + } + + @Override + public boolean chunkExists(int var1, int var2) { + return true; + } + + @Override + public Chunk provideChunk(int var1, int var2) { + boolean outOfBounds = var1 < 0 || var2 < 0 || var1 >= HSIZE || var2 >= HSIZE; + + // if(outOfBounds) return emptyChunk; + + Chunk chunk = new Chunk(this.worldObj, var1, var2); + + if(!outOfBounds) { + + /*for(int y = 0; y < provider.props.vsize; y++) { + if(var1 == 0 && var2 == 0) + chunk.setBlockIDWithMetadata(0, y, 0, Block.stone.blockID, 0); + if(var1 == HSIZE-1 && var2 == 0) + chunk.setBlockIDWithMetadata(15, y, 0, Block.stone.blockID, 0); + if(var1 == 0 && var2 == HSIZE-1) + chunk.setBlockIDWithMetadata(0, y, 15, Block.stone.blockID, 0); + if(var1 == HSIZE-1 && var2 == HSIZE-1) + chunk.setBlockIDWithMetadata(15, y, 15, Block.stone.blockID, 0); + } + + for(int x = 0; x < 16; x++) { + if(var1 == 0) { + chunk.setBlockIDWithMetadata(0, 0, x, Block.stone.blockID, 0); + chunk.setBlockIDWithMetadata(0, provider.props.vsize-1, x, Block.stone.blockID, 0); + } + + if(var1 == HSIZE-1) { + chunk.setBlockIDWithMetadata(15, 0, x, Block.stone.blockID, 0); + chunk.setBlockIDWithMetadata(15, provider.props.vsize-1, x, Block.stone.blockID, 0); + } + + if(var2 == 0) { + chunk.setBlockIDWithMetadata(x, 0, 0, Block.stone.blockID, 0); + chunk.setBlockIDWithMetadata(x, provider.props.vsize-1, 0, Block.stone.blockID, 0); + } + if(var2 == HSIZE-1) { + chunk.setBlockIDWithMetadata(x, 0, 15, Block.stone.blockID, 0); + chunk.setBlockIDWithMetadata(x, provider.props.vsize-1, 15, Block.stone.blockID, 0); + } + } + + for(int x = 0; x < 16; x++) + for(int z = 0; z < 16; z++) + chunk.setBlockIDWithMetadata(x, 0, z, Block.stone.blockID, 0);*/ + + + int nx = provider.props.xsize/2 - var1 * 16; + int nz = provider.props.zsize/2 - var2 * 16; + + for(int dx = 0; dx < 2; dx++) + for(int dy = 0; dy < 2; dy++) + for(int dz = 0; dz < 2; dz++) { + int x = nx+dx, y = provider.props.ysize/2+dy, z = nz+dz; + if(x >= 0 && y >= 0 && z >= 0 && x < 16 && y < provider.props.ysize && z < 16) + chunk.func_150807_a(x, y, z, Blocks.stone, 0); + } + } + + chunk.generateSkylightMap(); + + Arrays.fill(chunk.getBiomeArray(), (byte)BIOME.biomeID); + + chunk.isTerrainPopulated = true; + + return chunk; + } + + @Override + public Chunk loadChunk(int var1, int var2) { + return provideChunk(var1, var2); + } + + @Override + public void populate(IChunkProvider var1, int var2, int var3) { + } + + @Override + public boolean saveChunks(boolean var1, IProgressUpdate var2) { + return true; + } + + @Override + public boolean unloadQueuedChunks() { + return false; + } + + @Override + public boolean canSave() { + return true; + } + + @Override + public String makeString() { + return "DWChunkGenerator"; + } + + @SuppressWarnings("rawtypes") + @Override + public List getPossibleCreatures(EnumCreatureType var1, int var2, int var3, int var4) { + return Collections.emptyList(); + } + + @Override + public ChunkPosition func_147416_a(World var1, String var2, int var3, int var4, int var5) { + return null; + } + + @Override + public int getLoadedChunkCount() { + return 0; + } + + @Override + public void recreateStructures(int var1, int var2) { + } + + @Override + public void saveExtraData() { + } + +} diff --git a/src/main/java/mods/immibis/subworlds/dw/DWEntity.java b/src/main/java/mods/immibis/subworlds/dw/DWEntity.java new file mode 100644 index 0000000..b0ea76f --- /dev/null +++ b/src/main/java/mods/immibis/subworlds/dw/DWEntity.java @@ -0,0 +1,167 @@ +package mods.immibis.subworlds.dw; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import mods.immibis.subworlds.FakeEntity; +import mods.immibis.subworlds.mws.MWSListener; +import mods.immibis.subworlds.mws.MWSManager; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.world.World; +import net.minecraft.world.WorldServer; + +/** + * DW stands for "Detached World". + * Maximum size is always 48x48x48, this cannot easily be changed. + * There might be smaller, 16x16x16 + */ +public abstract class DWEntity extends Entity { + + // set of players last known to be tracking this entity which are outside it + private Set trackingPlayers = new HashSet(); + + // set of players last known to be inside this entity + private Set enclosedPlayers = new HashSet(); + + // fake entity sent to enclosed players to render the external world. + private FakeEntity externalWorldFE; + + // internal world, cannot change after initialization + private WorldServer internalWorld; + + //private int lastChunkX = Integer.MAX_VALUE, lastChunkZ = Integer.MAX_VALUE; + + public DWEntity(World par1World) { + super(par1World); + setSize(48, 48); + } + + private static final int WATCHER_INDEX_DIMID = 8; + + @Override + protected void entityInit() { + dataWatcher.addObject(WATCHER_INDEX_DIMID, 0); + } + + @Override + protected void readEntityFromNBT(NBTTagCompound var1) { + + } + + @Override + protected void writeEntityToNBT(NBTTagCompound var1) { + + } + + protected abstract WorldServer getInternalWorld(); + protected abstract void updateExternalWorldFE(FakeEntity e); + protected abstract FakeEntity createExternalWorldFE(); + protected abstract void applyGLRotation(float partialTick); + + private boolean requiresInteriorLoaded = false; + public boolean requiresInteriorLoaded() {return requiresInteriorLoaded;} + + /*@Override + public void setPosition(double par1, double par3, double par5) { + } + @Override + public void setPositionAndRotation(double par1, double par3, double par5, float par7, float par8) {}; + */ + + @Override + public void onUpdate() { + + this.prevPosX = this.posX; + this.prevPosY = this.posY; + this.prevPosZ = this.posZ; + + /*if(worldObj.isRemote) { + posX = serverPosX / 32.0; + posY = serverPosY / 32.0; + posZ = serverPosZ / 32.0; + }*/ + + if(!worldObj.isRemote) { + + + int lastDimID = (internalWorld == null ? 0 : internalWorld.provider.dimensionId); + internalWorld = getInternalWorld(); + if(internalWorld.provider.dimensionId != lastDimID) + dataWatcher.updateObject(WATCHER_INDEX_DIMID, internalWorld.provider.dimensionId); + + // enclosed players see the outside world via MWS + + @SuppressWarnings("unchecked") + Set nowEnclosed = new HashSet(internalWorld.playerEntities); + for(final EntityPlayerMP pl : nowEnclosed) + if(!enclosedPlayers.contains(pl)) { + MWSManager.getWorldManager(worldObj).addListener(new MWSListener(pl.playerNetServerHandler.netManager) { + @Override + public void update() { + /*x = (int)DWEntity.this.comX; + y = (int)DWEntity.this.comY; + z = (int)DWEntity.this.comZ;*/ + x = (int)posX; + y = (int)posY; + z = (int)posZ; + } + }); + } + for(final EntityPlayerMP pl : enclosedPlayers) + if(!nowEnclosed.contains(pl)) + MWSManager.getWorldManager(worldObj).removeListener(pl.playerNetServerHandler.netManager); + + enclosedPlayers = nowEnclosed; + + // tracking players see the enclosed world via MWS + + Set nowTracking = DWUtils.getTrackingPlayers(this); + for(final EntityPlayerMP pl : trackingPlayers) + if(!nowTracking.contains(pl)) + MWSManager.getWorldManager(internalWorld).removeListener(pl.playerNetServerHandler.netManager); + trackingPlayers.retainAll(nowTracking); + + for(final EntityPlayerMP pl : nowTracking) + if(!trackingPlayers.contains(pl)) { + MWSManager.getWorldManager(internalWorld).addListener(new MWSListener(pl.playerNetServerHandler.netManager) { + @Override public void update() {} + }); + trackingPlayers.add(pl); + } + + requiresInteriorLoaded = enclosedPlayers.size() > 0 || trackingPlayers.size() > 0; + + if(externalWorldFE == null) { + externalWorldFE = createExternalWorldFE(); + } + + //DWWorldProvider provider = (DWWorldProvider)getInternalWorld().provider; + + updateExternalWorldFE(externalWorldFE); + externalWorldFE.setTrackingPlayers(enclosedPlayers); + externalWorldFE.tick(); + } + } + + @Override + public boolean isInRangeToRenderDist(double distsq) { + return true; + } + + public int getEnclosedDimensionClient() { + return dataWatcher.getWatchableObjectInt(WATCHER_INDEX_DIMID); + } + + public void onUnloadOrDestroy() { + for(final EntityPlayerMP pl : enclosedPlayers) + MWSManager.getWorldManager(worldObj).removeListener(pl.playerNetServerHandler.netManager); + if(internalWorld != null) + for(final EntityPlayerMP pl : trackingPlayers) + MWSManager.getWorldManager(internalWorld).removeListener(pl.playerNetServerHandler.netManager); + if(externalWorldFE != null) + externalWorldFE.setTrackingPlayers(Collections.emptySet()); + } +} diff --git a/src/main/java/mods/immibis/subworlds/dw/DWEntityRenderer.java b/src/main/java/mods/immibis/subworlds/dw/DWEntityRenderer.java new file mode 100644 index 0000000..7fa7116 --- /dev/null +++ b/src/main/java/mods/immibis/subworlds/dw/DWEntityRenderer.java @@ -0,0 +1,406 @@ +package mods.immibis.subworlds.dw; + + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +import mods.immibis.subworlds.mws.MWSClientWorld; +import mods.immibis.subworlds.mws.MWSManager; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.WorldClient; +import net.minecraft.client.particle.EffectRenderer; +import net.minecraft.client.renderer.EntityRenderer; +import net.minecraft.client.renderer.RenderGlobal; +import net.minecraft.client.renderer.RenderHelper; +import net.minecraft.client.renderer.culling.ClippingHelperImpl; +import net.minecraft.client.renderer.culling.Frustrum; +import net.minecraft.client.renderer.entity.RenderEntity; +import net.minecraft.client.renderer.texture.TextureMap; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraftforge.client.ForgeHooksClient; +import net.minecraftforge.client.MinecraftForgeClient; + +import org.lwjgl.opengl.GL11; + +public class DWEntityRenderer extends RenderEntity { + private static List clipperStack = new ArrayList(); + private static Field clipperInstanceField; + static { + clipperInstanceField = ClippingHelperImpl.class.getDeclaredFields()[0]; + clipperInstanceField.setAccessible(true); + } + + public static void pushClipper() { + try { + clipperStack.add(clipperInstanceField.get(null)); + clipperInstanceField.set(null, new ClippingHelperImpl()); + } catch(Exception e) { + throw new RuntimeException(e); + } + } + + public static void popClipper() { + try { + clipperInstanceField.set(null, clipperStack.remove(clipperStack.size() - 1)); + } catch(Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public void doRender(Entity ent_, double x, double y, double z, float yaw, float partialTick) { + + DWEntity ent = (DWEntity)ent_; + + double entX = ent.prevPosX + (ent.posX - ent.prevPosX) * partialTick; + double entY = ent.prevPosY + (ent.posY - ent.prevPosY) * partialTick; + double entZ = ent.prevPosZ + (ent.posZ - ent.prevPosZ) * partialTick; + + GL11.glPushMatrix(); + GL11.glTranslated(x, y, z); + + ent.applyGLRotation(partialTick); + + int dimID = ent.getEnclosedDimensionClient(); + + MWSClientWorld subWorld = dimID == 0 ? null : MWSManager.getClientWorld(dimID); + if(subWorld == null) + super.doRender(ent_, 0, 0, 0, yaw, partialTick); + else + { + //System.out.println(ent.hashCode()+"\t"+ent.entityId+" at "+entX+" "+entY+" "+entZ+" "+ent.world.getBlockId(5, 0, 5)); + //System.out.println("Render world "+ent.world+" for "+ent.zeppelinID); + //GL11.glTranslated(x - entX, y - entY, z - entZ); + //GL11.glTranslated(entX - x, entY - y, entZ - z); + + DWWorldProvider pv = (DWWorldProvider)subWorld.provider; + GL11.glTranslatef(-pv.props.xsize/2.0f, 0, -pv.props.zsize/2.0f); + + renderClientWorld(subWorld, partialTick, entX, entY, entZ, 0, 0, 0); + } + GL11.glPopMatrix(); + + } + + // e_*: unused. + // pl_*: centre of rendered area (we pretend the player is here) + // pl_* is also the point in the world that will be rendered at 0,0,0. + public static void renderClientWorld(MWSClientWorld world, float partialTick, double e_x, double e_y, double e_z, double pl_x, double pl_y, double pl_z) { + Minecraft mc = Minecraft.getMinecraft(); + + GL11.glColor3f(1, 1, 1); + + RenderGlobal oldRG = mc.renderGlobal; + EntityRenderer oldER = mc.entityRenderer; + WorldClient oldWorld = mc.theWorld; + mc.renderGlobal = world.render; + mc.entityRenderer = world.erender; + mc.theWorld = world; + int oldPass = MinecraftForgeClient.getRenderPass(); + pushClipper(); + + + // Pretend the player is at the origin (of the zeppelin world) + EntityLivingBase pl = mc.renderViewEntity; + double realX = pl.posX, realY = pl.posY, realZ = pl.posZ; + double realPrevX = pl.prevPosX, realPrevY = pl.prevPosY, realPrevZ = pl.prevPosZ; + double realLastX = pl.lastTickPosX, realLastY = pl.lastTickPosY, realLastZ = pl.lastTickPosZ; + pl.posX = pl.prevPosX = pl.lastTickPosX = pl_x; + pl.posY = pl.prevPosY = pl.lastTickPosY = pl_y; + pl.posZ = pl.prevPosZ = pl.lastTickPosZ = pl_z; + + er_renderWorld(mc.entityRenderer, partialTick, 0); + + popClipper(); + ForgeHooksClient.setRenderPass(oldPass); + mc.theWorld = oldWorld; + mc.entityRenderer = oldER; + mc.renderGlobal = oldRG; + pl.posX = realX; + pl.posY = realY; + pl.posZ = realZ; + pl.prevPosX = realPrevX; + pl.prevPosY = realPrevY; + pl.prevPosZ = realPrevZ; + pl.lastTickPosX = realLastX; + pl.lastTickPosY = realLastY; + pl.lastTickPosZ = realLastZ; + } + + private static void er_renderWorld(EntityRenderer self, float par1, long par2) { + Minecraft mc = Minecraft.getMinecraft(); + + mc.mcProfiler.startSection("lightTex"); + + /*if (self.lightmapUpdateNeeded) + { + self.updateLightmap(par1); + }*/ + + GL11.glEnable(GL11.GL_CULL_FACE); + GL11.glEnable(GL11.GL_DEPTH_TEST); + + if (mc.renderViewEntity == null) + { + mc.renderViewEntity = mc.thePlayer; + } + + mc.mcProfiler.endStartSection("pick"); + //self.getMouseOver(par1); + EntityLivingBase entitylivingbase = mc.renderViewEntity; + RenderGlobal renderglobal = mc.renderGlobal; + EffectRenderer effectrenderer = mc.effectRenderer; + double d0 = entitylivingbase.lastTickPosX + (entitylivingbase.posX - entitylivingbase.lastTickPosX) * (double)par1; + double d1 = entitylivingbase.lastTickPosY + (entitylivingbase.posY - entitylivingbase.lastTickPosY) * (double)par1; + double d2 = entitylivingbase.lastTickPosZ + (entitylivingbase.posZ - entitylivingbase.lastTickPosZ) * (double)par1; + mc.mcProfiler.endStartSection("center"); + + //for (int j = 0; j < 2; ++j) + //{ + /*if (mc.gameSettings.anaglyph) + { + anaglyphField = j; + + if (anaglyphField == 0) + { + GL11.glColorMask(false, true, true, false); + } + else + { + GL11.glColorMask(true, false, false, false); + } + }*/ + + mc.mcProfiler.endStartSection("clear"); + //GL11.glViewport(0, 0, mc.displayWidth, mc.displayHeight); + //self.updateFogColor(par1); + //GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT); + GL11.glEnable(GL11.GL_CULL_FACE); + mc.mcProfiler.endStartSection("camera"); + //self.setupCameraTransform(par1, j); + //ActiveRenderInfo.updateRenderInfo(mc.thePlayer, mc.gameSettings.thirdPersonView == 2); + mc.mcProfiler.endStartSection("frustrum"); + ClippingHelperImpl.getInstance(); + + /*if (mc.gameSettings.renderDistance < 2) + { + self.setupFog(-1, par1); + mc.mcProfiler.endStartSection("sky"); + renderglobal.renderSky(par1); + }*/ + + //GL11.glEnable(GL11.GL_FOG); + //self.setupFog(1, par1); + + if (mc.gameSettings.ambientOcclusion != 0) + { + GL11.glShadeModel(GL11.GL_SMOOTH); + } + + + mc.mcProfiler.endStartSection("culling"); + Frustrum frustrum = new Frustrum(); + frustrum.setPosition(d0, d1, d2); + mc.renderGlobal.clipRenderersByFrustum(frustrum, par1); + + + //if (j == 0) + //{ + mc.mcProfiler.endStartSection("updatechunks"); + + while (!mc.renderGlobal.updateRenderers(entitylivingbase, false) && par2 != 0L) + { + long k = par2 - System.nanoTime(); + + if (k < 0L || k > 1000000000L) + { + break; + } + } + //} + + //if (entitylivingbase.posY < 128.0D) + //{ + // self.renderCloudsCheck(renderglobal, par1); + //} + + + mc.mcProfiler.endStartSection("prepareterrain"); + //self.setupFog(0, par1); + //GL11.glEnable(GL11.GL_FOG); + mc.getTextureManager().bindTexture(TextureMap.locationBlocksTexture); + RenderHelper.disableStandardItemLighting(); + mc.mcProfiler.endStartSection("terrain"); + renderglobal.sortAndRender(entitylivingbase, 0, (double)par1); + GL11.glShadeModel(GL11.GL_FLAT); + EntityPlayer entityplayer; + + if (self.debugViewDirection == 0) + { + /* XXX TODO: enable entity rendering inside DW's. renderEntities uses statics that need to be reset afterwards. + RenderHelper.enableStandardItemLighting(); + mc.mcProfiler.endStartSection("entities"); + ForgeHooksClient.setRenderPass(0); + renderglobal.renderEntities(entitylivingbase.getPosition(par1), frustrum, par1); + ForgeHooksClient.setRenderPass(0); + */ + + /* Forge: Moved down + self.enableLightmap((double)par1); + mc.mcProfiler.endStartSection("litParticles"); + effectrenderer.renderLitParticles(entitylivingbase, par1); + RenderHelper.disableStandardItemLighting(); + self.setupFog(0, par1); + mc.mcProfiler.endStartSection("particles"); + effectrenderer.renderParticles(entitylivingbase, par1); + self.disableLightmap((double)par1); + */ + + /*if (mc.objectMouseOver != null && entitylivingbase.isInsideOfMaterial(Material.water) && entitylivingbase instanceof EntityPlayer && !mc.gameSettings.hideGUI) + { + entityplayer = (EntityPlayer)entitylivingbase; + GL11.glDisable(GL11.GL_ALPHA_TEST); + mc.mcProfiler.endStartSection("outline"); + if (!ForgeHooksClient.onDrawBlockHighlight(renderglobal, entityplayer, mc.objectMouseOver, 0, entityplayer.inventory.getCurrentItem(), par1)) + { + renderglobal.drawSelectionBox(entityplayer, mc.objectMouseOver, 0, par1); + } + GL11.glEnable(GL11.GL_ALPHA_TEST); + }*/ + } + + + GL11.glDisable(GL11.GL_BLEND); + GL11.glEnable(GL11.GL_CULL_FACE); + GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); + GL11.glDepthMask(true); + //self.setupFog(0, par1); + GL11.glEnable(GL11.GL_BLEND); + GL11.glDisable(GL11.GL_CULL_FACE); + mc.getTextureManager().bindTexture(TextureMap.locationBlocksTexture); + + if (mc.gameSettings.fancyGraphics) + { + mc.mcProfiler.endStartSection("water"); + + if (mc.gameSettings.ambientOcclusion != 0) + { + GL11.glShadeModel(GL11.GL_SMOOTH); + } + + GL11.glColorMask(false, false, false, false); + int l = renderglobal.sortAndRender(entitylivingbase, 1, (double)par1); + + + /*if (mc.gameSettings.anaglyph) + { + if (anaglyphField == 0) + { + GL11.glColorMask(false, true, true, true); + } + else + { + GL11.glColorMask(true, false, false, true); + } + } + else*/ + + { + GL11.glColorMask(true, true, true, true); + } + + if (l > 0) + { + renderglobal.renderAllRenderLists(1, (double)par1); + } + + GL11.glShadeModel(GL11.GL_FLAT); + } + else + { + mc.mcProfiler.endStartSection("water"); + renderglobal.sortAndRender(entitylivingbase, 1, (double)par1); + } + + + + /* XXX TODO: enable entity rendering inside DW's. renderEntities uses statics that need to be reset afterwards. + if (self.debugViewDirection == 0) //Only render if render pass 0 happens as well. + { + RenderHelper.enableStandardItemLighting(); + mc.mcProfiler.endStartSection("entities"); + ForgeHooksClient.setRenderPass(1); + renderglobal.renderEntities(entitylivingbase.getPosition(par1), frustrum, par1); + ForgeHooksClient.setRenderPass(-1); + RenderHelper.disableStandardItemLighting(); + } */ + + GL11.glDepthMask(true); + GL11.glEnable(GL11.GL_CULL_FACE); + GL11.glDisable(GL11.GL_BLEND); + + RenderHelper.enableStandardItemLighting(); + + /*if (self.cameraZoom == 1.0D && entitylivingbase instanceof EntityPlayer && !mc.gameSettings.hideGUI && mc.objectMouseOver != null && !entitylivingbase.isInsideOfMaterial(Material.water)) + { + entityplayer = (EntityPlayer)entitylivingbase; + GL11.glDisable(GL11.GL_ALPHA_TEST); + mc.mcProfiler.endStartSection("outline"); + if (!ForgeHooksClient.onDrawBlockHighlight(renderglobal, entityplayer, mc.objectMouseOver, 0, entityplayer.inventory.getCurrentItem(), par1)) + { + renderglobal.drawSelectionBox(entityplayer, mc.objectMouseOver, 0, par1); + } + GL11.glEnable(GL11.GL_ALPHA_TEST); + }*/ + + //mc.mcProfiler.endStartSection("destroyProgress"); + //GL11.glEnable(GL11.GL_BLEND); + //GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE); + //renderglobal.drawBlockDamageTexture(Tessellator.instance, entitylivingbase, par1); + //GL11.glDisable(GL11.GL_BLEND); + //mc.mcProfiler.endStartSection("weather"); + //self.renderRainSnow(par1); + //GL11.glDisable(GL11.GL_FOG); + + //if (entitylivingbase.posY >= 128.0D) + //{ + // self.renderCloudsCheck(renderglobal, par1); + //} + + //Forge: Moved section from above, now particles are the last thing to render. + /*self.enableLightmap((double)par1); + mc.mcProfiler.endStartSection("litParticles"); + effectrenderer.renderLitParticles(entitylivingbase, par1); + RenderHelper.disableStandardItemLighting(); + //self.setupFog(0, par1); + mc.mcProfiler.endStartSection("particles"); + effectrenderer.renderParticles(entitylivingbase, par1); + self.disableLightmap((double)par1);*/ + //Forge: End Move + + //mc.mcProfiler.endStartSection("FRenderLast"); + //ForgeHooksClient.dispatchRenderLast(renderglobal, par1); + + //mc.mcProfiler.endStartSection("hand"); + + //if (self.cameraZoom == 1.0D) + //{ + // GL11.glClear(GL11.GL_DEPTH_BUFFER_BIT); + // self.renderHand(par1, j); + //} + + //if (!mc.gameSettings.anaglyph) + //{ + mc.mcProfiler.endSection(); + //return; + //} + //} + + //GL11.glColorMask(true, true, true, false); + //mc.mcProfiler.endSection(); + } + +} diff --git a/src/main/java/mods/immibis/subworlds/dw/DWManager.java b/src/main/java/mods/immibis/subworlds/dw/DWManager.java new file mode 100644 index 0000000..cb3fe5c --- /dev/null +++ b/src/main/java/mods/immibis/subworlds/dw/DWManager.java @@ -0,0 +1,231 @@ +package mods.immibis.subworlds.dw; + + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import mods.immibis.core.api.APILocator; +import mods.immibis.core.api.net.IPacket; +import mods.immibis.core.api.net.IPacketMap; +import mods.immibis.core.api.util.NBTType; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.IWorldAccess; +import net.minecraft.world.World; +import net.minecraft.world.WorldSavedData; +import net.minecraft.world.storage.MapStorage; +import net.minecraftforge.common.DimensionManager; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.world.WorldEvent; +import cpw.mods.fml.common.FMLCommonHandler; +import cpw.mods.fml.common.eventhandler.SubscribeEvent; +import cpw.mods.fml.common.network.FMLNetworkEvent; + +public class DWManager { + + // Note: DimensionManager isn't thread safe. + // We avoid calling it from the client thread when the integrated server is running. + + public static final int PROVIDER_TYPE_ID = "SpaceCraftMod".hashCode(); + + public static final String CHANNEL = "SpaceCraftDW"; + public static final byte PKT_DIMENSION_LIST = 0; + + private static Map registeredDimensions = new HashMap(); + + private static SaveSpecificData getSaveData() { + MapStorage ms = DimensionManager.getWorld(0).mapStorage; + SaveSpecificData ssd = (SaveSpecificData)ms.loadData(SaveSpecificData.class, "subworlds-dw-save-data"); + if(ssd == null) + ms.setData("subworlds-dw-save-data", ssd = new SaveSpecificData("subworlds-dw-save-data")); + return ssd; + } + + /** + * Creates a new empty dimension for Detached World use, + * returns the dimension ID. + * only called on the server + */ + public static int createWorld(WorldProps props) { + int dim = DimensionManager.getNextFreeDimId(); + + System.out.println("DWManager: creating dimension #"+dim); + + registerDimensions(Collections.singletonMap(dim, props), false); + + MinecraftServer.getServer().worldServerForDimension(dim); + System.out.println("DWManager: created dimension #"+dim); + return dim; + } + public static int createWorld() { + return createWorld(new WorldProps()); + } + + public static void init() { + DimensionManager.registerProviderType(PROVIDER_TYPE_ID, DWWorldProvider.class, false); + MinecraftForge.EVENT_BUS.register(new EventHandler()); + FMLCommonHandler.instance().bus().register(new ConnectionHandler()); + APILocator.getNetManager().listen(new PacketMap()); + new ChunkLoadingCallback(); + } + + private static class PacketMap implements IPacketMap { + + @Override + public String getChannel() { + return CHANNEL; + } + + @Override + public IPacket createS2CPacket(byte id) { + if(id == PKT_DIMENSION_LIST) + return new PacketDWDimensionList(); + return null; + } + + @Override + public IPacket createC2SPacket(byte id) { + return null; + } + + } + + public static class ConnectionHandler { + @SubscribeEvent + public String connectionReceived(FMLNetworkEvent.ServerConnectionFromClientEvent evt) { + PacketDWDimensionList packet = new PacketDWDimensionList(); + packet.clearExisting = true; + packet.data = registeredDimensions; + APILocator.getNetManager().send(packet, evt.manager, true); + return null; + } + } + + // only called on the server, unless there is no server + @SuppressWarnings("unchecked") + static void registerDimensions(Map dimIDs, boolean clearExisting) { + if(clearExisting) { + for(int i : registeredDimensions.keySet()) + DimensionManager.unregisterDimension(i); + registeredDimensions.clear(); + } + SaveSpecificData sd = MinecraftServer.getServer() == null ? null : getSaveData(); + for(int i : dimIDs.keySet()) { + DimensionManager.registerDimension(i, PROVIDER_TYPE_ID); + registeredDimensions.put(i, dimIDs.get(i)); + + if(sd != null && !sd.dw_worlds.containsKey(i)) { + sd.dw_worlds.put(i, dimIDs.get(i)); + sd.setDirty(true); + } + } + + if(sd != null) + { + PacketDWDimensionList packet = new PacketDWDimensionList(); + packet.clearExisting = clearExisting; + packet.data = dimIDs; + for(EntityPlayer pl : (List)MinecraftServer.getServer().getConfigurationManager().playerEntityList) { + APILocator.getNetManager().sendToClient(packet, pl); + } + } + + + + } + + // must be public or Forge crashes + public static class EventHandler { + @SubscribeEvent + public void onWorldLoad(WorldEvent.Load evt) { + if(!evt.world.isRemote && evt.world.provider.dimensionId == 0) { + registerDimensions(getSaveData().dw_worlds, true); + } + if(!evt.world.isRemote) + evt.world.addWorldAccess(new WorldListener(evt.world)); + } + + @SubscribeEvent + public void onWorldUnload(WorldEvent.Unload evt) { + if(!evt.world.isRemote && evt.world.provider.dimensionId == 0) { + registerDimensions(Collections.emptyMap(), true); + } + } + } + + // must be public as instances are created by reflection + public static class SaveSpecificData extends WorldSavedData { + public SaveSpecificData(String par1Str) { + super(par1Str); + } + + public Map dw_worlds = new HashMap(); + + @Override + public void readFromNBT(NBTTagCompound var1) { + if(var1.hasKey("dw_worlds")) { + NBTTagList t = var1.getTagList("dw_worlds", NBTType.COMPOUND); + for(int k = 0; k < t.tagCount(); k++) + { + WorldProps props = new WorldProps(); + NBTTagCompound tag = t.getCompoundTagAt(k); + int dim = tag.getInteger("dim"); + props.read(tag); + dw_worlds.put(dim, props); + } + } + } + + @Override + public void writeToNBT(NBTTagCompound var1) { + NBTTagList t = new NBTTagList(); + for(Map.Entry i : dw_worlds.entrySet()) { + NBTTagCompound c = new NBTTagCompound(); + c.setInteger("dim", i.getKey()); + i.getValue().write(c); + t.appendTag(c); + } + var1.setTag("dw_worlds", t); + } + + } + + private static class WorldListener implements IWorldAccess { + //public final World worldObj; + public WorldListener(World world) { + //this.worldObj = world; + } + + @Override + public void onEntityDestroy(Entity var1) { + if(var1 instanceof DWEntity) + ((DWEntity)var1).onUnloadOrDestroy(); + } + + @Override public void markBlockForUpdate(int var1, int var2, int var3) {} + @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) {} + @Override public void playSound(String var1, double var2, double var4, double var6, float var8, float var9) {} + @Override public void playSoundToNearExcept(EntityPlayer var1, String var2, double var3, double var5, double var7, float var9, float var10) {} + @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 playRecord(String var1, int var2, int var3, int var4) {} + @Override public void broadcastSound(int var1, int var2, int var3, int var4, int var5) {} + @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 onStaticEntitiesChanged() {} + } + + public static WorldProps getProps(int dim) { + WorldProps p = registeredDimensions.get(dim); + if(p == null) + return new WorldProps(); // probably on client. TODO verify correct + return p; + } + +} diff --git a/src/main/java/mods/immibis/subworlds/dw/DWTeleporter.java b/src/main/java/mods/immibis/subworlds/dw/DWTeleporter.java new file mode 100644 index 0000000..b07504a --- /dev/null +++ b/src/main/java/mods/immibis/subworlds/dw/DWTeleporter.java @@ -0,0 +1,31 @@ +package mods.immibis.subworlds.dw; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.world.Teleporter; +import net.minecraft.world.WorldServer; + +public class DWTeleporter extends Teleporter { + + public WorldServer world; + + public DWTeleporter(WorldServer par1WorldServer) { + super(par1WorldServer); + world = par1WorldServer; + } + + @Override + public void placeInPortal(Entity par1Entity, double par2, double par4, double par6, float par8) { + DWWorldProvider provider = (DWWorldProvider)world.provider; + + double x = provider.props.xsize/2; + double y = 5; + double z = provider.props.zsize/2; + + if(par1Entity instanceof EntityLivingBase) + ((EntityLivingBase)par1Entity).setPositionAndUpdate(x, y, z); + else + par1Entity.setPosition(x, y, z); + } + +} diff --git a/src/main/java/mods/immibis/subworlds/dw/DWUtils.java b/src/main/java/mods/immibis/subworlds/dw/DWUtils.java new file mode 100644 index 0000000..2b19634 --- /dev/null +++ b/src/main/java/mods/immibis/subworlds/dw/DWUtils.java @@ -0,0 +1,22 @@ +package mods.immibis.subworlds.dw; + +import java.util.Set; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityTracker; +import net.minecraft.entity.EntityTrackerEntry; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.util.IntHashMap; +import net.minecraft.world.WorldServer; +import cpw.mods.fml.relauncher.ReflectionHelper; + +public class DWUtils { + @SuppressWarnings("unchecked") + public static Set getTrackingPlayers(Entity ent) { + EntityTracker t = ((WorldServer)ent.worldObj).getEntityTracker(); + + IntHashMap trackedEntityIDs = ReflectionHelper.getPrivateValue(EntityTracker.class, t, 3); + EntityTrackerEntry entry = (EntityTrackerEntry)trackedEntityIDs.lookup(ent.getEntityId()); + return entry.trackingPlayers; + } +} diff --git a/src/main/java/mods/immibis/subworlds/dw/DWWorldChunkManager.java b/src/main/java/mods/immibis/subworlds/dw/DWWorldChunkManager.java new file mode 100644 index 0000000..c58731e --- /dev/null +++ b/src/main/java/mods/immibis/subworlds/dw/DWWorldChunkManager.java @@ -0,0 +1,83 @@ +package mods.immibis.subworlds.dw; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Random; + +import net.minecraft.world.ChunkPosition; +import net.minecraft.world.WorldType; +import net.minecraft.world.biome.BiomeGenBase; +import net.minecraft.world.biome.WorldChunkManager; +import net.minecraft.world.gen.layer.GenLayer; + +@SuppressWarnings("rawtypes") +public class DWWorldChunkManager extends WorldChunkManager { + public final DWWorldProvider provider; + + public DWWorldChunkManager(DWWorldProvider provider) { + this.provider = provider; + } + + @Override + public boolean areBiomesViable(int par1, int par2, int par3, List par4List) { + return par4List.contains(DWChunkGenerator.BIOME); + } + + @Override + public ChunkPosition findBiomePosition(int par1, int par2, int par3, List par4List, Random par5Random) { + if(par4List.contains(DWChunkGenerator.BIOME)) + return new ChunkPosition(provider.props.xsize/2, provider.props.ysize/2, provider.props.zsize/2); + return null; + } + + @Override + public BiomeGenBase[] getBiomeGenAt(BiomeGenBase[] ar, int par2, int par3, int par4, int par5, boolean par6) { + if (ar == null || ar.length < par4 * par5) + ar = new BiomeGenBase[par4 * par5]; + + Arrays.fill(ar, DWChunkGenerator.BIOME); + + return ar; + } + + @Override + public BiomeGenBase getBiomeGenAt(int par1, int par2) { + return DWChunkGenerator.BIOME; + } + + @Override + public BiomeGenBase[] getBiomesForGeneration(BiomeGenBase[] ar, int par2, int par3, int par4, int par5) { + if (ar == null || ar.length < par4 * par5) + ar = new BiomeGenBase[par4 * par5]; + + Arrays.fill(ar, DWChunkGenerator.BIOME); + + return ar; + } + + @Override + public List getBiomesToSpawnIn() { + return Collections.singletonList(DWChunkGenerator.BIOME); + } + + @Override + public GenLayer[] getModdedBiomeGenerators(WorldType worldType, long seed, GenLayer[] original) { + return original; + } + + @Override + public float[] getRainfall(float[] ar, int par2, int par3, int par4, int par5) { + if(ar == null || ar.length < par4 * par5) + ar = new float[par4 * par5]; + + Arrays.fill(ar, DWChunkGenerator.BIOME.rainfall); + + return ar; + } + + @Override + public float getTemperatureAtHeight(float par1, int par2) { + return par1; + } +} diff --git a/src/main/java/mods/immibis/subworlds/dw/DWWorldProvider.java b/src/main/java/mods/immibis/subworlds/dw/DWWorldProvider.java new file mode 100644 index 0000000..cc6445b --- /dev/null +++ b/src/main/java/mods/immibis/subworlds/dw/DWWorldProvider.java @@ -0,0 +1,226 @@ +package mods.immibis.subworlds.dw; + +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.util.ChunkCoordinates; +import net.minecraft.util.Vec3; +import net.minecraft.world.WorldProvider; +import net.minecraft.world.WorldServer; +import net.minecraft.world.biome.BiomeGenBase; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.IChunkProvider; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +public class DWWorldProvider extends WorldProvider { + + public WorldProps props; + + public static final boolean + LIGHTNING = false, + RAIN = false, + RESPAWN_HERE = false, + FOG = false, + FULLBRIGHT = true; + + public DWTeleporter teleporter; + + @Override + public void setDimension(int dim) { + props = DWManager.getProps(dim); + super.setDimension(dim); + } + + @Override + @SideOnly(Side.CLIENT) + public Vec3 getSkyColor(Entity cameraEntity, float partialTicks) { + return super.getSkyColor(cameraEntity, partialTicks); + } + + @Override + public String getDimensionName() { + return "Detached World"; + } + + @Override + @SideOnly(Side.CLIENT) + public float[] calcSunriseSunsetColors(float par1, float par2) { + return null; + } + + @Override + public boolean canCoordinateBeSpawn(int par1, int par2) { + return par1 >= 0 && par1 < props.xsize && par2 >= 0 && par2 < props.zsize; + } + + @Override + public boolean canDoLightning(Chunk chunk) { + return LIGHTNING; + } + + @Override + public boolean canDoRainSnowIce(Chunk chunk) { + return RAIN; + } + + /*@Override + public boolean canMineBlock(EntityPlayer player, int x, int y, int z) { + return x >= 0 && y >= 0 && z >= 0 && x < HSIZE && y < VSIZE && z < HSIZE && super.canMineBlock(player, x, y, z); + }*/ + + @Override + public boolean canRespawnHere() { + return RESPAWN_HERE; + } + + @Override + public IChunkProvider createChunkGenerator() { + try { + return props.generatorClass.getConstructor(WorldServer.class, DWWorldProvider.class).newInstance((WorldServer)worldObj, this); + } catch (Exception e) { + throw new RuntimeException(e); + } + //new DWChunkGenerator((WorldServer)worldObj, this); + } + + @Override + @SideOnly(Side.CLIENT) + public boolean doesXZShowFog(int par1, int par2) { + return FOG; + } + + @Override + protected void generateLightBrightnessTable() { + if(FULLBRIGHT) { + for(int k = 0; k < 16; k++) + lightBrightnessTable[k] = 1; + } else + super.generateLightBrightnessTable(); + } + + @Override + public int getActualHeight() { + return props.ysize; + } + + @Override + public int getAverageGroundLevel() { + return 0; + } + + @Override + public BiomeGenBase getBiomeGenForCoords(int x, int z) { + return DWChunkGenerator.BIOME; + } + + @Override + @SideOnly(Side.CLIENT) + public Vec3 getFogColor(float par1, float par2) { + //return worldObj.getWorldVec3Pool().getVecFromPool((int)(par1*255), 255, (int)(par2*255)); + return super.getFogColor(par1, par2); + } + + @Override + public int getHeight() { + return props.ysize; + } + + @Override + public ChunkCoordinates getRandomizedSpawnPoint() { + return new ChunkCoordinates(props.xsize/2, props.ysize/2, props.zsize/2); + } + + @Override + public int getRespawnDimension(EntityPlayerMP player) { + return RESPAWN_HERE ? dimensionId : 0; + } + + @Override + public String getSaveFolder() { + return "CRAFT" + dimensionId; + } + + @Override + public long getSeed() { + return 0; + } + + @Override + public ChunkCoordinates getSpawnPoint() { + return getRandomizedSpawnPoint(); + } + + @Override + @SideOnly(Side.CLIENT) + public double getVoidFogYFactor() { + return 1; + } + + @Override + public String getWelcomeMessage() { + return "Entering sub-world..."; + } + + @Override + public String getDepartMessage() { + return "Leaving sub-world..."; + } + + @Override + @SideOnly(Side.CLIENT) + public boolean getWorldHasVoidParticles() { + return false; + } + + @Override + public boolean isDaytime() { + return true; + } + + @Override + public boolean isSurfaceWorld() { + return false; + } + + @Override + protected void registerWorldChunkManager() { + worldChunkMgr = new DWWorldChunkManager(this); + if(!worldObj.isRemote) + teleporter = new DWTeleporter((WorldServer)worldObj); + } + + @Override + public void setAllowedSpawnTypes(boolean allowHostile, boolean allowPeaceful) { + super.setAllowedSpawnTypes(false, false); + } + + @Override + public void updateWeather() { + } + + @Override + public boolean canSnowAt(int x, int y, int z, boolean checkLight) { + return false; + } + + @Override + public boolean canBlockFreeze(int x, int y, int z, boolean byWater) { + return false; + } + + @Override + public boolean canMineBlock(EntityPlayer player, int x, int y, int z) { + return super.canMineBlock(player, x, y, z); + } + + @Override + public float calculateCelestialAngle(long par1, float par3) { + return super.calculateCelestialAngle(par1, par3); + } + + @Override + public void calculateInitialWeather() { + } + +} diff --git a/src/main/java/mods/immibis/subworlds/dw/PacketDWDimensionList.java b/src/main/java/mods/immibis/subworlds/dw/PacketDWDimensionList.java new file mode 100644 index 0000000..a693a67 --- /dev/null +++ b/src/main/java/mods/immibis/subworlds/dw/PacketDWDimensionList.java @@ -0,0 +1,61 @@ +package mods.immibis.subworlds.dw; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import mods.immibis.core.api.net.IPacket; +import mods.immibis.core.api.net.PacketUtils; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.server.MinecraftServer; + +public class PacketDWDimensionList implements IPacket { + + public boolean clearExisting = false; + public Map data = new HashMap(); + + @Override + public byte getID() { + return DWManager.PKT_DIMENSION_LIST; + } + + @Override + public String getChannel() { + return DWManager.CHANNEL; + } + + @Override + public void read(DataInputStream in) throws IOException { + clearExisting = in.readBoolean(); + int len = in.readInt(); + for(int k = 0; k < len; k++) { + int id = in.readInt(); + WorldProps wp = new WorldProps(); + wp.read(PacketUtils.readNBT(in)); + data.put(id, wp); + } + } + + @Override + public void write(DataOutputStream out) throws IOException { + out.writeBoolean(clearExisting); + out.writeInt(data.size()); + for(Map.Entry e : data.entrySet()) { + out.writeInt(e.getKey()); + NBTTagCompound tag = new NBTTagCompound(); + e.getValue().write(tag); + PacketUtils.writeNBT(tag, out); + } + } + + @Override + public void onReceived(EntityPlayer source) { + if(source == null && MinecraftServer.getServer() == null) { + DWManager.registerDimensions(data, clearExisting); + } + } + +} diff --git a/src/main/java/mods/immibis/subworlds/dw/WorldProps.java b/src/main/java/mods/immibis/subworlds/dw/WorldProps.java new file mode 100644 index 0000000..eb172d7 --- /dev/null +++ b/src/main/java/mods/immibis/subworlds/dw/WorldProps.java @@ -0,0 +1,32 @@ +package mods.immibis.subworlds.dw; + +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.world.chunk.IChunkProvider; + +// POD structure with extra info about a DW world +public final class WorldProps { + public int xsize = 48; + public int ysize = 48; + public int zsize = 48; + + // class must have constructor taking (WorldServer, DWWorldProvider) + public Class generatorClass = DWChunkGenerator.class; + + public void read(NBTTagCompound tag) { + xsize = tag.getInteger("xsize"); + ysize = tag.getInteger("ysize"); + zsize = tag.getInteger("zsize"); + try { + generatorClass = Class.forName(tag.getString("genclass")).asSubclass(IChunkProvider.class); + } catch(ClassNotFoundException e) { + generatorClass = DWChunkGenerator.class; + } + } + + public void write(NBTTagCompound tag) { + tag.setInteger("xsize", xsize); + tag.setInteger("ysize", ysize); + tag.setInteger("zsize", zsize); + tag.setString("genclass", generatorClass.getName()); + } +} diff --git a/src/main/java/mods/immibis/subworlds/mws/MWSClientWorld.java b/src/main/java/mods/immibis/subworlds/mws/MWSClientWorld.java new file mode 100644 index 0000000..c51fc81 --- /dev/null +++ b/src/main/java/mods/immibis/subworlds/mws/MWSClientWorld.java @@ -0,0 +1,79 @@ +package mods.immibis.subworlds.mws; + + +import mods.immibis.subworlds.dw.DWWorldProvider; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.WorldClient; +import net.minecraft.client.network.NetHandlerPlayClient; +import net.minecraft.client.renderer.EntityRenderer; +import net.minecraft.client.renderer.RenderGlobal; +import net.minecraft.world.EnumDifficulty; +import net.minecraft.world.WorldSettings; +import cpw.mods.fml.relauncher.ReflectionHelper; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +@SideOnly(Side.CLIENT) +public class MWSClientWorld extends WorldClient { + public int dimension; + + //private Chunk blankChunk; + + public boolean isDead = false; + + public MWSClientWorld(int dimID) { + // need a valid NetClientHandler for construction so grab one from the real world + super((NetHandlerPlayClient)ReflectionHelper.getPrivateValue(WorldClient.class, Minecraft.getMinecraft().theWorld, 0), + new WorldSettings(Minecraft.getMinecraft().theWorld.getWorldInfo()), + dimID, + EnumDifficulty.PEACEFUL, // difficulty + Minecraft.getMinecraft().theWorld.theProfiler); + + // but now we don't need it any more + ReflectionHelper.setPrivateValue(WorldClient.class, this, null, 0); + + dimension = dimID; + + isRemote = true; + + Minecraft mc = Minecraft.getMinecraft(); + + render = new RenderGlobal(mc); /*{ + @Override + public void renderAllRenderLists(int par1, double par2) { + super.renderAllRenderLists(par1, par2); + System.out.println(par1+" "+par2+" "+((java.util.List)ReflectionHelper.getPrivateValue(RenderGlobal.class, this, "glRenderLists")).size()); + } + };*/ + final EntityRenderer normalER = mc.entityRenderer; + erender = new EntityRenderer(mc, mc.getResourceManager()) { + + // use the normal lightmap, not our separate one + @Override public void disableLightmap(double par1) {normalER.disableLightmap(par1);} + @Override public void enableLightmap(double par1) {normalER.enableLightmap(par1);} + }; + + render.setWorldAndLoadRenderers(this); + + //blankChunk = getChunkFromChunkCoords(0, 0); + + if(provider instanceof DWWorldProvider) { + DWWorldProvider pv = (DWWorldProvider)provider; + + for(int x = -1; x <= pv.props.xsize; x++) + for(int z = -1; z <= pv.props.zsize; z++) + doPreChunk(x, z, true); + } + } + + public RenderGlobal render; + public EntityRenderer erender; + + public void zeppelinTick() { + updateEntities(); + } + + public void unloadChunk(int x, int z) { + doPreChunk(x, z, false); + } +} diff --git a/src/main/java/mods/immibis/subworlds/mws/MWSListener.java b/src/main/java/mods/immibis/subworlds/mws/MWSListener.java new file mode 100644 index 0000000..71f86dd --- /dev/null +++ b/src/main/java/mods/immibis/subworlds/mws/MWSListener.java @@ -0,0 +1,34 @@ +package mods.immibis.subworlds.mws; + +import java.util.HashSet; +import java.util.Set; + +import net.minecraft.network.NetworkManager; +import net.minecraft.world.ChunkCoordIntPair; + + +/** + * Contains a reference to a client, as well as a + * mechanism for determining which part of the world to send to this client. + */ +public abstract class MWSListener { + public final NetworkManager client; + + public MWSListener(NetworkManager client) { + this.client = client; + } + + // See update() + public int x, y, z; + public boolean isDead; + + public int ticksToNextRangeCheck = 0; + public int RANGE_CHECK_INTERVAL = 20; + Set loadedChunks = new HashSet(); + + /** + * Override this to update x, y and z with the centre of the area that will be sent to the client. + * Set isDead to true to remove the listener. + */ + public abstract void update(); +} diff --git a/src/main/java/mods/immibis/subworlds/mws/MWSManager.java b/src/main/java/mods/immibis/subworlds/mws/MWSManager.java new file mode 100644 index 0000000..1328ebd --- /dev/null +++ b/src/main/java/mods/immibis/subworlds/mws/MWSManager.java @@ -0,0 +1,204 @@ +package mods.immibis.subworlds.mws; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; + +import mods.immibis.core.api.APILocator; +import mods.immibis.core.api.net.IPacket; +import mods.immibis.core.api.net.IPacketMap; +import mods.immibis.core.api.net.IPacketWrapper; +import mods.immibis.subworlds.mws.packets.*; +import net.minecraft.entity.Entity; +import net.minecraft.world.World; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.world.WorldEvent; +import cpw.mods.fml.common.FMLCommonHandler; +import cpw.mods.fml.common.eventhandler.SubscribeEvent; +import cpw.mods.fml.common.gameevent.TickEvent; +import cpw.mods.fml.common.network.FMLNetworkEvent; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +/** + * MWS is Multi-World Sync. It handles sending worlds to clients that the player isn't in. + * + * Each server world is associated with an MWSWorldManager, even if no clients are watching it. + */ +public class MWSManager { + + public static void debug(String s) { + System.out.println("[SubWorlds MWS Debug] "+s); + } + + public static final String CHANNEL = "ImmMWS"; + + private static WeakHashMap managers = new WeakHashMap(); + + /** + * Returns the MWSWorldManager that controls MWS for a given server world. + */ + public static MWSWorldManager getWorldManager(World world) { + if(world.isRemote) + throw new IllegalArgumentException("Argument must be a server world."); + + MWSWorldManager m = managers.get(world); + if(m == null) + managers.put(world, m = new MWSWorldManager(world)); + return m; + } + + @SideOnly(Side.CLIENT) + private static Map clientWorlds; + static { + try { + clientWorlds = new HashMap(); + } catch(NoSuchFieldError e) { + } + } + + /** + * Returns the MWSClientWorld for a given dimension ID, or null if none exists. + */ + @SideOnly(Side.CLIENT) + public static MWSClientWorld getClientWorld(int dimID) { + return clientWorlds.get(dimID); + } + + + + @SideOnly(Side.CLIENT) + public static void clientBegin(int dimID) { + debug("MWS client start: "+dimID); + clientWorlds.put(dimID, new MWSClientWorld(dimID)); + } + + @SideOnly(Side.CLIENT) + public static void clientEnd(int dimID) { + debug("MWS client end: "+dimID); + MWSClientWorld w = clientWorlds.remove(dimID); + if(w != null) { + // is any cleanup necessary? + } + } + + @SideOnly(Side.CLIENT) + public static Entity findClientEntity(int entID) { + for(MWSClientWorld w : clientWorlds.values()) { + Entity e = w.getEntityByID(entID); + if(e != null) + return e; + } + return null; + } + + + + + + public static void init() { + MinecraftForge.EVENT_BUS.register(new ForgeEventListener()); + APILocator.getNetManager().listen(new PacketMap()); + FMLCommonHandler.instance().bus().register(new FMLEventListener()); + } + + // must be public or Forge throws an exception + public static class ForgeEventListener { + @SubscribeEvent + public void onWorldUnload(WorldEvent.Unload evt) { + if(!evt.world.isRemote) { + MWSWorldManager m = managers.get(evt.world); + if(m != null) + m.onWorldUnload(); + } + } + } + + public static final byte PKT_BEGIN = 0; + public static final byte PKT_BLOCK = 1; + public static final byte PKT_CHUNK = 2; + public static final byte PKT_MULTIBLOCK = 3; + public static final byte PKT_TILE = 4; + public static final byte PKT_UNLOAD = 5; + public static final byte PKT_END = 6; + public static final byte PKT_SET_WORLD = 7; + + private static class PacketMap implements IPacketMap { + + @Override + public String getChannel() { + return CHANNEL; + } + + @Override + public IPacket createS2CPacket(byte id) { + switch(id) { + case PKT_BEGIN: return new PacketMWSBegin(); + case PKT_BLOCK: return new PacketMWSBlock(); + case PKT_CHUNK: return new PacketMWSChunk(); + case PKT_MULTIBLOCK: return new PacketMWSMultiBlock(); + case PKT_TILE: return new PacketMWSTile(); + case PKT_UNLOAD: return new PacketMWSUnload(); + case PKT_END: return new PacketMWSEnd(); + case PKT_SET_WORLD: return new PacketMWSSetWorld(); + default: + return null; + } + } + + @Override + public IPacket createC2SPacket(byte id) { + return null; + } + } + + // must be public or forge crashes + public static class FMLEventListener { + @SubscribeEvent + public void onServerTickEnd(TickEvent.ServerTickEvent evt) { + if(evt.phase != TickEvent.Phase.END) return; + for(MWSWorldManager m : managers.values()) + m.tick(); + } + @SubscribeEvent + @SideOnly(Side.CLIENT) + public void onClientSideConnect(FMLNetworkEvent.ClientConnectedToServerEvent evt) { + evt.manager.channel().pipeline().addBefore("packet_handler", "immibis subworlds mws subworld packet delayer congratulations if you are reading this far", new ChannelInboundHandlerAdapter() { + boolean delayingMessages = false; + int delayingMessagesForWorld; + List delayedMessages = new ArrayList<>(); + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + if(msg instanceof IPacketWrapper) { + IPacket wp = ((IPacketWrapper)msg).packet; + if(wp instanceof PacketMWSSetWorld) { + int dim = ((PacketMWSSetWorld)wp).dim; + if(delayingMessages && dim != delayingMessagesForWorld) { + delayingMessages = false; + delayedMessages.add(new IPacketWrapper(new PacketMWSSetWorld(PacketMWSSetWorld.NORMAL_DIM))); + for(Object obj : delayedMessages) + ctx.fireChannelRead(obj); + delayedMessages.clear(); + } + if(dim != PacketMWSSetWorld.NORMAL_DIM) { + delayingMessages = true; + delayingMessagesForWorld = dim; + delayedMessages.add(msg); + } + return; + } + } + if(delayingMessages) + delayedMessages.add(msg); + else + ctx.fireChannelRead(msg); + } + }); + } + } +} diff --git a/src/main/java/mods/immibis/subworlds/mws/MWSWorldManager.java b/src/main/java/mods/immibis/subworlds/mws/MWSWorldManager.java new file mode 100644 index 0000000..8b5c486 --- /dev/null +++ b/src/main/java/mods/immibis/subworlds/mws/MWSWorldManager.java @@ -0,0 +1,457 @@ +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 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); + 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 listeners = new HashSet(); + + 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 listeners = new HashSet(); + + public void addRange(Range r) { + if(nUpdateRanges < updateRanges.length) { + updateRanges[nUpdateRanges] = r; + nUpdatedBlocks += r.getBlockCount(); + } + nUpdateRanges++; + } + + @SuppressWarnings("unchecked") + public Collection nextUpdatePackets() { + if(nUpdateRanges == 0) + return null; + + World world = worldObj.get(); + if(world == null) + return null; + + Collection 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(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(16); + rv.add(APILocator.getNetManager().wrap(new PacketMWSChunk(this.x, this.z, world, c), true)); + for(TileEntity te : (Collection)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 changedChunks = new HashSet(); + private Set allChunks = new HashSet(); + 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 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(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 copy = new ArrayList(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)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 sendQueue = new LinkedList(); + + 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 targetChunks = new HashSet(); + + 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 toLoad = new HashSet(targetChunks); + Set toUnload = new HashSet(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(); + } +} diff --git a/src/main/java/mods/immibis/subworlds/mws/packets/PacketMWSBegin.java b/src/main/java/mods/immibis/subworlds/mws/packets/PacketMWSBegin.java new file mode 100644 index 0000000..6f5d90a --- /dev/null +++ b/src/main/java/mods/immibis/subworlds/mws/packets/PacketMWSBegin.java @@ -0,0 +1,53 @@ +package mods.immibis.subworlds.mws.packets; + + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +import mods.immibis.core.api.net.IPacket; +import mods.immibis.subworlds.mws.MWSManager; +import net.minecraft.entity.player.EntityPlayer; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +public class PacketMWSBegin implements /*Runnable,*/ IPacket { + + public int dim; + + public PacketMWSBegin() {} + public PacketMWSBegin(int dim) {this.dim = dim;} + + @Override + public byte getID() { + return MWSManager.PKT_BEGIN; + } + + @Override + public void write(DataOutputStream out) throws IOException { + out.writeInt(dim); + } + + @Override + public void read(DataInputStream in) throws IOException { + dim = in.readInt(); + } + + @Override + @SideOnly(Side.CLIENT) + public void onReceived(EntityPlayer source) { + /*MainThreadTaskQueue.enqueue(this, Side.CLIENT); + } + + @Override + @SideOnly(Side.CLIENT) + public void run() {*/ + MWSManager.clientBegin(dim); + } + + @Override + public String getChannel() { + return MWSManager.CHANNEL; + } + +} diff --git a/src/main/java/mods/immibis/subworlds/mws/packets/PacketMWSBlock.java b/src/main/java/mods/immibis/subworlds/mws/packets/PacketMWSBlock.java new file mode 100644 index 0000000..5f69190 --- /dev/null +++ b/src/main/java/mods/immibis/subworlds/mws/packets/PacketMWSBlock.java @@ -0,0 +1,77 @@ +package mods.immibis.subworlds.mws.packets; + + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +import mods.immibis.core.MainThreadTaskQueue; +import mods.immibis.core.api.net.IPacket; +import mods.immibis.subworlds.mws.MWSManager; +import net.minecraft.block.Block; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.world.World; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +public class PacketMWSBlock implements IPacket, Runnable { + + int x, y, z, type, meta, dim; + + public PacketMWSBlock(int x, int y, int z, World w) { + this.x = x; + this.y = y; + this.z = z; + this.dim = w.provider.dimensionId; + this.type = Block.getIdFromBlock(w.getBlock(x, y, z)); + this.meta = w.getBlockMetadata(x, y, z); + } + + public PacketMWSBlock() {} + + @Override + public byte getID() { + return MWSManager.PKT_BLOCK; + } + + @Override + public void write(DataOutputStream out) throws IOException { + out.writeInt(x); + out.writeInt(y); + out.writeInt(z); + out.writeShort((short)type); + out.writeByte((byte)meta); + out.writeInt(dim); + } + + @Override + public void read(DataInputStream in) throws IOException { + x = in.readInt(); + y = in.readInt(); + z = in.readInt(); + type = in.readShort(); + meta = in.readByte(); + dim = in.readInt(); + } + + @Override + public String getChannel() { + return MWSManager.CHANNEL; + } + + @Override + @SideOnly(Side.CLIENT) + public void onReceived(EntityPlayer source) { + MainThreadTaskQueue.enqueue(this, Side.CLIENT); + } + + @Override + @SideOnly(Side.CLIENT) + public void run() { + World w = MWSManager.getClientWorld(dim); + if(w != null) { + w.setBlock(x, y, z, Block.getBlockById(type), meta, 3); + } + } + +} diff --git a/src/main/java/mods/immibis/subworlds/mws/packets/PacketMWSChunk.java b/src/main/java/mods/immibis/subworlds/mws/packets/PacketMWSChunk.java new file mode 100644 index 0000000..8705c89 --- /dev/null +++ b/src/main/java/mods/immibis/subworlds/mws/packets/PacketMWSChunk.java @@ -0,0 +1,186 @@ +package mods.immibis.subworlds.mws.packets; + + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.zip.Deflater; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.InflaterInputStream; + +import mods.immibis.core.api.net.IPacket; +import mods.immibis.subworlds.mws.MWSManager; +import net.minecraft.block.Block; +import net.minecraft.client.multiplayer.WorldClient; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.world.EnumSkyBlock; +import net.minecraft.world.World; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.storage.ExtendedBlockStorage; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +public class PacketMWSChunk implements IPacket { + + public int cx, cz, dim; + public short[] type; + public byte[] meta; + public byte[] light; + public short mask; + + public PacketMWSChunk(int x, int z, World w, Chunk c) { + cx = x; + cz = z; + dim = w.provider.dimensionId; + + fromChunk(c); + } + + public PacketMWSChunk() {} + + @Override + public byte getID() { + return MWSManager.PKT_CHUNK; + } + + public void fromChunk(Chunk c) { + type = new short[16*16*256]; + meta = new byte[16*16*256]; + light = new byte[16*16*256]; + + mask = 0; + for(int k = 0; k < 16; k++) + if(c.getBlockStorageArray()[k] != null) + mask |= (1 << k); + + int pos = 0; + for(int y = 0; y < 256; y++) + for(int x = 0; x < 16; x++) + for(int z = 0; z < 16; z++, pos++) { + type[pos] = (short)Block.getIdFromBlock(c.getBlock(x, y, z)); + meta[pos] = (byte)c.getBlockMetadata(x, y, z); + byte L = (byte)(c.getSavedLightValue(EnumSkyBlock.Block, x, y, z) & 15); + byte SL = (byte)(c.getSavedLightValue(EnumSkyBlock.Sky, x, y, z) & 15); + light[pos] = (byte)((SL << 4) | L); + } + } + + + public void toChunk(Chunk c) { + ExtendedBlockStorage[] ebs = c.getBlockStorageArray(); + + int pos = 0; + for(int y = 0; y < 256; y++) { + ExtendedBlockStorage segment = ebs[y >> 4]; + if((mask & (1 << (y >> 4))) == 0) { + pos += 256; + ebs[y >> 4] = null; + continue; + } + if(segment == null) + segment = ebs[y >> 4] = new ExtendedBlockStorage(y >> 4, true); + for(int x = 0; x < 16; x++) + for(int z = 0; z < 16; z++, pos++) { + int SL = (light[pos] >> 4) & 15; + int L = light[pos] & 15; + segment.setExtBlocklightValue(x, y&15, z, L); + segment.setExtSkylightValue(x, y&15, z, SL); + segment.func_150818_a(x, y&15, z, Block.getBlockById(type[pos])); + segment.setExtBlockMetadata(x, y&15, z, meta[pos]); + } + } + + int x = c.xPosition << 4; + int z = c.zPosition << 4; + + for(int k = 0; k < 16; k++) + if((mask & (1 << k)) != 0) + c.worldObj.markBlockRangeForRenderUpdate(x, k << 4, z, x+15, (k<<4)+15, z+15); + } + + private static ThreadLocal buffer128kb = new ThreadLocal() { + @Override + protected byte[] initialValue() {return new byte[131072];} + }; + + @Override + public void write(DataOutputStream out) throws IOException { + out.writeInt(cx); + out.writeInt(cz); + out.writeInt(dim); + out.writeShort(mask); + DeflaterOutputStream o = new DeflaterOutputStream(out, new Deflater()); + //OutputStream o = out; + byte[] buffer = buffer128kb.get(); + int pos = 0; + for(short s : type) { + buffer[pos++] = (byte)(s >> 8); + buffer[pos++] = (byte)s; + } + o.write(buffer); + o.write(meta); + o.write(light); + o.finish(); + } + + @Override + public void read(DataInputStream in) throws IOException { + cx = in.readInt(); + cz = in.readInt(); + dim = in.readInt(); + mask = in.readShort(); + + InflaterInputStream i = new InflaterInputStream(in); + //InputStream i = in; + type = new short[65536]; + meta = new byte[65536]; + light = new byte[65536]; + byte[] buffer = buffer128kb.get(); + int pos = 0; + readFully(i, buffer); + for(int k = 0; k < 65536; k++, pos += 2) { + int b1 = buffer[pos] & 255; + int b2 = buffer[pos+1] & 255; + //if(b1 == -1 || b2 == -1) + // throw new IOException("unexpected EOF"); + type[k] = (short)((b1 << 8) | b2); + } + readFully(i, meta); + readFully(i, light); + } + + private void readFully(InputStream i, byte[] b) throws IOException { + int pos = 0; + while(pos < b.length) { + int read = i.read(b, pos, b.length - pos); + if(read < 0) + throw new IOException("Unexpected end of stream (after "+pos+" bytes, need "+b.length+")"); + pos += read; + } + } + + @Override + public String getChannel() { + return MWSManager.CHANNEL; + } + + @Override + @SideOnly(Side.CLIENT) + public void onReceived(EntityPlayer source) { + /*MainThreadTaskQueue.enqueue(this, Side.CLIENT); + } + + @Override + @SideOnly(Side.CLIENT) + public void run() {*/ + WorldClient w = MWSManager.getClientWorld(dim); + if(w != null) { + w.doPreChunk(cx, cz, true); + Chunk c = w.getChunkFromChunkCoords(cx, cz); + toChunk(c); + System.out.println("Received chunk "+dim+"/"+cx+"/"+cz); + } + } + +} diff --git a/src/main/java/mods/immibis/subworlds/mws/packets/PacketMWSEnd.java b/src/main/java/mods/immibis/subworlds/mws/packets/PacketMWSEnd.java new file mode 100644 index 0000000..bc43a21 --- /dev/null +++ b/src/main/java/mods/immibis/subworlds/mws/packets/PacketMWSEnd.java @@ -0,0 +1,55 @@ +package mods.immibis.subworlds.mws.packets; + + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +import mods.immibis.core.MainThreadTaskQueue; +import mods.immibis.core.api.net.IPacket; +import mods.immibis.subworlds.mws.MWSManager; +import net.minecraft.entity.player.EntityPlayer; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +public class PacketMWSEnd implements IPacket, Runnable { + + public int dim; + + public PacketMWSEnd() {} + public PacketMWSEnd(int dim) {this.dim = dim;} + + @Override + public byte getID() { + return MWSManager.PKT_END; + } + + @Override + public void write(DataOutputStream out) throws IOException { + out.writeInt(dim); + } + + @Override + public void read(DataInputStream in) throws IOException { + dim = in.readInt(); + } + + + @Override + public String getChannel() { + return MWSManager.CHANNEL; + } + + @Override + @SideOnly(Side.CLIENT) + public void onReceived(EntityPlayer source) { + MainThreadTaskQueue.enqueue(this, Side.CLIENT); + } + + @Override + @SideOnly(Side.CLIENT) + public void run() { + MWSManager.clientEnd(dim); + } + +} diff --git a/src/main/java/mods/immibis/subworlds/mws/packets/PacketMWSMultiBlock.java b/src/main/java/mods/immibis/subworlds/mws/packets/PacketMWSMultiBlock.java new file mode 100644 index 0000000..bddb97a --- /dev/null +++ b/src/main/java/mods/immibis/subworlds/mws/packets/PacketMWSMultiBlock.java @@ -0,0 +1,107 @@ +package mods.immibis.subworlds.mws.packets; + + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +import mods.immibis.core.MainThreadTaskQueue; +import mods.immibis.core.api.net.IPacket; +import mods.immibis.subworlds.mws.MWSManager; +import net.minecraft.block.Block; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.world.World; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +public class PacketMWSMultiBlock implements IPacket.Asynchronous, Runnable { + + public PacketMWSMultiBlock() {} + + public int size, pos, dim, cx, cz; + public byte[] meta; + public short[] type; + public byte[] x; + public int[] y; + public byte[] z; + + public PacketMWSMultiBlock(int cx, int cz, int size, World w) { + this.size = size; + this.cx = cx; + this.cz = cz; + dim = w.provider.dimensionId; + pos = 0; + meta = new byte[size]; + type = new short[size]; + x = new byte[size]; + y = new int[size]; + z = new byte[size]; + } + + @Override + public byte getID() { + return MWSManager.PKT_MULTIBLOCK; + } + + @Override + public void write(DataOutputStream out) throws IOException { + out.writeInt(pos); + out.writeInt(cx); + out.writeInt(cz); + out.writeInt(dim); + for(int k = 0; k < pos; k++) { + out.writeByte(x[k]); + out.writeInt(y[k]); + out.writeByte(z[k]); + out.writeByte(meta[k]); + out.writeShort(type[k]); + } + } + + @Override + public void read(DataInputStream in) throws IOException { + size = pos = in.readInt(); + cx = in.readInt(); + cz = in.readInt(); + dim = in.readInt(); + for(int k = 0; k < size; k++) { + x[k] = in.readByte(); + y[k] = in.readInt(); + z[k] = in.readByte(); + meta[k] = in.readByte(); + type[k] = in.readShort(); + } + } + + public void addBlock(int x2, int y2, int z2, World w) { + x[pos] = (byte)x2; + y[pos] = y2; + z[pos] = (byte)z2; + type[pos] = (short)Block.getIdFromBlock(w.getBlock(x2 + (cx << 4), y2, z2 + (cz << 4))); + meta[pos] = (byte)w.getBlockMetadata(x2 + (cx << 4), y2, z2 + (cz << 4)); + pos++; + } + + @Override + public String getChannel() { + return MWSManager.CHANNEL; + } + + @Override + @SideOnly(Side.CLIENT) + public void onReceived(EntityPlayer source) { + MainThreadTaskQueue.enqueue(this, Side.CLIENT); + } + + @Override + @SideOnly(Side.CLIENT) + public void run() { + World w = MWSManager.getClientWorld(dim); + if(w != null) { + for(int k = 0; k < pos; k++) { + w.setBlock((cx<<4) + x[k], y[k], (cz<<4) + z[k], Block.getBlockById(type[k]), meta[k], 2); + } + } + } + +} diff --git a/src/main/java/mods/immibis/subworlds/mws/packets/PacketMWSSetWorld.java b/src/main/java/mods/immibis/subworlds/mws/packets/PacketMWSSetWorld.java new file mode 100644 index 0000000..e7b23b9 --- /dev/null +++ b/src/main/java/mods/immibis/subworlds/mws/packets/PacketMWSSetWorld.java @@ -0,0 +1,64 @@ +package mods.immibis.subworlds.mws.packets; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.WorldClient; +import net.minecraft.entity.player.EntityPlayer; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import mods.immibis.core.api.net.IPacket; +import mods.immibis.subworlds.mws.MWSManager; + +public class PacketMWSSetWorld implements IPacket { + + public static final int NORMAL_DIM = Integer.MIN_VALUE; + + public int dim; + + public PacketMWSSetWorld() {} + public PacketMWSSetWorld(int dim) {this.dim = dim;} + + @Override + public byte getID() { + return MWSManager.PKT_SET_WORLD; + } + + @Override + public void write(DataOutputStream out) throws IOException { + out.writeInt(dim); + } + + @Override + public void read(DataInputStream in) throws IOException { + dim = in.readInt(); + } + + @SideOnly(Side.CLIENT) + private static WorldClient oldWorld; + + @Override + @SideOnly(Side.CLIENT) + public void onReceived(EntityPlayer source) { + if(dim == NORMAL_DIM) { + if(oldWorld == null) + throw new AssertionError(); + Minecraft.getMinecraft().theWorld = oldWorld; + oldWorld = null; + } else { + oldWorld = Minecraft.getMinecraft().theWorld; + WorldClient newWorld = MWSManager.getClientWorld(dim); + if(newWorld != null) + Minecraft.getMinecraft().theWorld = newWorld; + else + System.out.println("[WARNING] [SubWorlds MWS] Got set-world packet for unknown client world "+dim); + } + } + + @Override + public String getChannel() { + return MWSManager.CHANNEL; + } +} diff --git a/src/main/java/mods/immibis/subworlds/mws/packets/PacketMWSTile.java b/src/main/java/mods/immibis/subworlds/mws/packets/PacketMWSTile.java new file mode 100644 index 0000000..7d14cb8 --- /dev/null +++ b/src/main/java/mods/immibis/subworlds/mws/packets/PacketMWSTile.java @@ -0,0 +1,124 @@ +package mods.immibis.subworlds.mws.packets; + + +import io.netty.buffer.Unpooled; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +import mods.immibis.core.api.net.IPacket; +import mods.immibis.subworlds.mws.MWSManager; +import net.minecraft.client.Minecraft; +import net.minecraft.client.multiplayer.WorldClient; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.network.EnumConnectionState; +import net.minecraft.network.INetHandler; +import net.minecraft.network.Packet; +import net.minecraft.network.PacketBuffer; +import net.minecraft.world.World; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +public class PacketMWSTile implements IPacket { + + public Packet teDesc; + public int dim; + public IOException exception; + + public PacketMWSTile() {} + + public PacketMWSTile(World w, Packet teDesc) { + this.teDesc = teDesc; + dim = w.provider.dimensionId; + } + + @Override + public byte getID() { + return MWSManager.PKT_TILE; + } + + @Override + public void write(DataOutputStream out) throws IOException { + if(exception != null) + throw new IOException("delayed wrapped encoding exception", exception); + + Integer packetID = (Integer)EnumConnectionState.PLAY.func_150755_b().inverse().get(teDesc.getClass()); + if(packetID == null) { + //System.out.println("can't send "+teDesc); + out.writeByte(0); + } + else + out.writeByte(packetID.intValue()); + + out.writeInt(dim); + + if(packetID != null) { + PacketBuffer packetbuffer = new PacketBuffer(Unpooled.buffer()); + teDesc.writePacketData(packetbuffer); + + // TODO waste of memory/garbage + byte[] data = new byte[packetbuffer.readableBytes()]; + packetbuffer.readBytes(data); + out.writeInt(data.length); + out.write(data); + } + } + + @Override + public void read(DataInputStream in) throws IOException { + int packetID = in.readByte() & 255; + dim = in.readInt(); + + if(packetID == 0) + return; + + int len = in.readInt(); + if(len > 2097152) + throw new IOException("input packet size > 2MB"); + + // TODO waste of memory/garbage (considering this is coming from a ByteArrayInputStream) + byte[] data = new byte[len]; + in.readFully(data); + + Packet packet; + try { + packet = ((Class)EnumConnectionState.PLAY.func_150755_b().get(packetID)).getConstructor().newInstance(); + } catch (ReflectiveOperationException e) { + throw new IOException("failed to instantiate packet", e); + } + packet.readPacketData(new PacketBuffer(Unpooled.wrappedBuffer(data))); + this.teDesc = packet; + } + + @Override + public String getChannel() { + return MWSManager.CHANNEL; + } + + @Override + @SideOnly(Side.CLIENT) + public void onReceived(EntityPlayer source) { + if(teDesc == null) + return; + + WorldClient w = MWSManager.getClientWorld(dim); + if(w != null) { + Minecraft mc = Minecraft.getMinecraft(); + WorldClient oldWorld = mc.theWorld; + + mc.theWorld = w; + + try { + INetHandler handler = Minecraft.getMinecraft().getNetHandler(); + System.out.println("Received tile packet "+teDesc); + teDesc.processPacket(handler); + } catch(Exception e) { + throw new RuntimeException(e); + } finally { + mc.theWorld = oldWorld; + } + } + } + +} diff --git a/src/main/java/mods/immibis/subworlds/mws/packets/PacketMWSUnload.java b/src/main/java/mods/immibis/subworlds/mws/packets/PacketMWSUnload.java new file mode 100644 index 0000000..31d12fb --- /dev/null +++ b/src/main/java/mods/immibis/subworlds/mws/packets/PacketMWSUnload.java @@ -0,0 +1,62 @@ +package mods.immibis.subworlds.mws.packets; + + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +import mods.immibis.core.MainThreadTaskQueue; +import mods.immibis.core.api.net.IPacket; +import mods.immibis.subworlds.mws.MWSManager; +import net.minecraft.client.multiplayer.WorldClient; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.world.World; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +public class PacketMWSUnload implements IPacket, Runnable { + + public int dim, x, z; + + public PacketMWSUnload() {} + public PacketMWSUnload(World world, int x, int z) {this.dim = world.provider.dimensionId; this.x = x; this.z = z;} + + @Override + public byte getID() { + return MWSManager.PKT_UNLOAD; + } + + @Override + public void write(DataOutputStream out) throws IOException { + out.writeInt(dim); + out.writeInt(x); + out.writeInt(z); + } + + @Override + public void read(DataInputStream in) throws IOException { + dim = in.readInt(); + x = in.readInt(); + z = in.readInt(); + } + + @Override + public String getChannel() { + return MWSManager.CHANNEL; + } + + @Override + @SideOnly(Side.CLIENT) + public void onReceived(EntityPlayer source) { + MainThreadTaskQueue.enqueue(this, Side.CLIENT); + } + + @Override + @SideOnly(Side.CLIENT) + public void run() { + WorldClient w = MWSManager.getClientWorld(dim); + if(w != null) + w.doPreChunk(x, z, false); + } + +} diff --git a/src/main/java/mods/immibis/tinycarts/BlockTransparentBedrock.java b/src/main/java/mods/immibis/tinycarts/BlockTransparentBedrock.java new file mode 100644 index 0000000..cca4ce6 --- /dev/null +++ b/src/main/java/mods/immibis/tinycarts/BlockTransparentBedrock.java @@ -0,0 +1,85 @@ +package mods.immibis.tinycarts; + +import java.util.concurrent.atomic.AtomicInteger; + +import net.minecraft.block.Block; +import net.minecraft.block.material.Material; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.util.AxisAlignedBB; +import net.minecraft.world.World; + +public class BlockTransparentBedrock extends Block { + public BlockTransparentBedrock() { + super(Material.rock); + + setBlockUnbreakable(); + setResistance(6000000.0F); + setStepSound(soundTypeStone); + setBlockName("tinycarts.transparent_bedrock"); + disableStats(); + setBlockTextureName("bedrock"); + } + + @Override + public boolean getUseNeighborBrightness() { + return true; + } + + @Override + public AxisAlignedBB getCollisionBoundingBoxFromPool(World par1World, int par2, int par3, int par4) { + final float border = 1/16f; + return AxisAlignedBB.getBoundingBox((double)par2 + border, (double)par3 + this.minY, (double)par4 + border, (double)par2 + 1-border, (double)par3 + 1-border, (double)par4 + 1-border); + } + + @Override + public void onEntityCollidedWithBlock(World par1World, int par2, int par3, int par4, Entity par5Entity) { + if(par5Entity instanceof EntityPlayerMP) + TinyCartsMod.removeFromCart(par5Entity, false); + } + + // render in no passes = invisible + @Override + public boolean canRenderInPass(int pass) { + return false; + } + + @Override + public boolean renderAsNormalBlock() { + return false; + } + + @Override + public boolean isOpaqueCube() { + return false; + } + + @Override + public void breakBlock(World par1World, int par2, int par3, int par4, Block par5, int par6) { + if(allowRemoval.get() > 0) + return; + + allowPlacement.incrementAndGet(); + try { + par1World.setBlock(par2, par3, par4, par5); + } finally { + allowPlacement.decrementAndGet(); + } + } + + @Override + public void onBlockAdded(World par1World, int par2, int par3, int par4) { + if(allowPlacement.get() > 0) + return; + + allowRemoval.incrementAndGet(); + try { + par1World.setBlockToAir(par2, par3, par4); + } finally { + allowRemoval.decrementAndGet(); + } + } + + static AtomicInteger allowPlacement = new AtomicInteger(); + static AtomicInteger allowRemoval = new AtomicInteger(); +} diff --git a/src/main/java/mods/immibis/tinycarts/CartExternalFakeEntity.java b/src/main/java/mods/immibis/tinycarts/CartExternalFakeEntity.java new file mode 100644 index 0000000..ffa25e4 --- /dev/null +++ b/src/main/java/mods/immibis/tinycarts/CartExternalFakeEntity.java @@ -0,0 +1,271 @@ +package mods.immibis.tinycarts; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +import mods.immibis.core.api.APILocator; +import mods.immibis.core.api.net.IPacket; +import mods.immibis.subworlds.ClientFakeEntities; +import mods.immibis.subworlds.FakeEntity; +import mods.immibis.subworlds.dw.DWEntityRenderer; +import mods.immibis.subworlds.dw.DWWorldProvider; +import mods.immibis.subworlds.mws.MWSClientWorld; +import mods.immibis.subworlds.mws.MWSManager; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.OpenGlHelper; +import net.minecraft.client.renderer.RenderHelper; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.network.Packet; +import net.minecraft.world.World; + +import org.lwjgl.opengl.GL11; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +public class CartExternalFakeEntity extends FakeEntity { + public final int dimID; + + // rotation is applied first when rendering, then translation. + // x/y/z is the coordinates of the centre of the player's world in the FE world (not vice versa) + public double x, y, z, yaw; + //public Quaternion rotation = new Quaternion(); + + private double prevX, prevY, prevZ, prevYaw; + //private Quaternion prevRotation = rotation.clone(); + + public CartExternalFakeEntity(boolean isClient, int dimID) { + super(isClient); + this.dimID = dimID; + } + + public CartExternalFakeEntity(boolean isClient, int entID, int dimID) { + super(isClient, entID); + this.dimID = dimID; + } + + @Override + public Packet getUpdatePacket() { + final double MOVE_THRESHOLD = 0.2; + if(prevYaw == yaw && Math.abs(x - prevX) < MOVE_THRESHOLD && Math.abs(y - prevY) < MOVE_THRESHOLD && Math.abs(z - prevZ) < MOVE_THRESHOLD) { + return null; + } + + prevYaw = yaw; + prevX = x; + prevY = y; + prevZ = z; + + return APILocator.getNetManager().wrap(new UpdatePacket(this), true); + } + + @Override + public Packet getDescriptionPacket() { + return APILocator.getNetManager().wrap(new CreatePacket(this), true); + } + + @Override + public Packet getDestructionPacket() { + return APILocator.getNetManager().wrap(new DeletePacket(entityID), true); + } + + private boolean updated = false; + @Override + public void tick() { + super.tick(); + + if(isClient) { + if(updated) + updated = false; + else { + prevX = x; + prevY = y; + prevZ = z; + prevYaw = yaw; + } + } + } + + @Override + @SideOnly(Side.CLIENT) + public void render(double pl_x, double pl_y, double pl_z, float partialTick) { + MWSClientWorld subWorld = MWSManager.getClientWorld(dimID); + World plWorld = Minecraft.getMinecraft().theWorld; + if(subWorld == null || !(plWorld.provider instanceof DWWorldProvider)) { + super.render(pl_x, pl_y, pl_z, partialTick); + return; + } + + GL11.glPushMatrix(); + GL11.glTranslated(-pl_x, -pl_y, -pl_z); + // now 0,0,0 = origin of the world the player is in + + DWWorldProvider pv = (DWWorldProvider)plWorld.provider; + GL11.glTranslated(pv.props.xsize/2, 0, pv.props.zsize/2); + // now 0,0,0 = centre-bottom of world the player is in + + double cx = prevX + (x - prevX) * partialTick; + double cy = prevY + (y - prevY) * partialTick; + double cz = prevZ + (z - prevZ) * partialTick; + + // why not just cx,cy,cz? + GL11.glTranslated(cx-x, cy-y, cz-z); + + GL11.glScalef(EntityMinecartAwesome.SCALE, EntityMinecartAwesome.SCALE, EntityMinecartAwesome.SCALE); + GL11.glRotatef((float)yaw, 0, 1, 0); + + DWEntityRenderer.renderClientWorld(subWorld, partialTick, 0, 0, 0, x, y, z); + GL11.glPopMatrix(); + + OpenGlHelper.setActiveTexture(OpenGlHelper.lightmapTexUnit); + GL11.glDisable(GL11.GL_TEXTURE_2D); + OpenGlHelper.setActiveTexture(OpenGlHelper.defaultTexUnit); + RenderHelper.disableStandardItemLighting(); + GL11.glColor3f(1, 1, 1); + } + + public static class DeletePacket implements IPacket { + @Override + public String getChannel() { + return NetworkHandler.CHANNEL; + } + + @Override + public byte getID() { + return NetworkHandler.PKT_CEFE_DELETE; + } + + public int entID; + + public DeletePacket() {} + public DeletePacket(int id) {entID = id;} + + @Override + public void onReceived(EntityPlayer source) { + ClientFakeEntities.remove(entID); + } + + @Override + public void read(DataInputStream in) throws IOException { + entID = in.readInt(); + } + + @Override + public void write(DataOutputStream out) throws IOException { + out.writeInt(entID); + } + } + + public static class CreatePacket implements IPacket { + @Override + public String getChannel() { + return NetworkHandler.CHANNEL; + } + + @Override + public byte getID() { + return NetworkHandler.PKT_CEFE_CREATE; + } + + public int entID, dimID; + public double x, y, z, r; + + public CreatePacket() {} + public CreatePacket(CartExternalFakeEntity ent) { + entID = ent.entityID; + dimID = ent.dimID; + x = ent.x; + y = ent.y; + z = ent.z; + r = ent.yaw; + } + + @Override + public void onReceived(EntityPlayer source) { + CartExternalFakeEntity e = new CartExternalFakeEntity(true, entID, dimID); + e.x = x; + e.y = y; + e.z = z; + e.yaw = r; + ClientFakeEntities.add(e); + } + + @Override + public void read(DataInputStream in) throws IOException { + entID = in.readInt(); + x = in.readDouble(); + y = in.readDouble(); + z = in.readDouble(); + r = in.readDouble(); + } + + @Override + public void write(DataOutputStream out) throws IOException { + out.writeInt(entID); + out.writeDouble(x); + out.writeDouble(y); + out.writeDouble(z); + out.writeDouble(r); + } + } + + public static class UpdatePacket implements IPacket { + @Override + public String getChannel() { + return NetworkHandler.CHANNEL; + } + + @Override + public byte getID() { + return NetworkHandler.PKT_CEFE_UPDATE; + } + + public int entID; + public double x, y, z; + public float rot; + + public UpdatePacket() {} + public UpdatePacket(CartExternalFakeEntity ent) { + entID = ent.entityID; + x = ent.x; + y = ent.y; + z = ent.z; + rot = (float)ent.yaw; + } + + @Override + public void onReceived(EntityPlayer source) { + CartExternalFakeEntity ent = (CartExternalFakeEntity)ClientFakeEntities.get(entID); + + ent.prevX = x; + ent.prevY = y; + ent.prevZ = z; + + ent.updated = true; + + ent.x = x; + ent.y = y; + ent.z = z; + ent.yaw = rot; + } + + @Override + public void read(DataInputStream in) throws IOException { + entID = in.readInt(); + x = in.readDouble(); + y = in.readDouble(); + z = in.readDouble(); + rot = in.readFloat(); + } + + @Override + public void write(DataOutputStream out) throws IOException { + out.writeInt(entID); + out.writeDouble(x); + out.writeDouble(y); + out.writeDouble(z); + out.writeFloat(rot); + } + } +} diff --git a/src/main/java/mods/immibis/tinycarts/CommandExitCart.java b/src/main/java/mods/immibis/tinycarts/CommandExitCart.java new file mode 100644 index 0000000..86b4f53 --- /dev/null +++ b/src/main/java/mods/immibis/tinycarts/CommandExitCart.java @@ -0,0 +1,31 @@ +package mods.immibis.tinycarts; + +import net.minecraft.command.CommandBase; +import net.minecraft.command.ICommandSender; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.util.ChatComponentText; + +public class CommandExitCart extends CommandBase { + @Override + public boolean canCommandSenderUseCommand(ICommandSender par1iCommandSender) { + return par1iCommandSender instanceof EntityPlayer; + } + + @Override + public String getCommandName() { + return "exitcart"; + } + + @Override + public String getCommandUsage(ICommandSender icommandsender) { + return "/exitcart"; + } + + @Override + public void processCommand(ICommandSender icommandsender, String[] astring) { + if(icommandsender instanceof EntityPlayer) + TinyCartsMod.removeFromCart((EntityPlayer)icommandsender, true); + else + icommandsender.addChatMessage(new ChatComponentText("That command is only applicable to players.")); + } +} diff --git a/src/main/java/mods/immibis/tinycarts/EntityMinecartAwesome.java b/src/main/java/mods/immibis/tinycarts/EntityMinecartAwesome.java new file mode 100644 index 0000000..847da0b --- /dev/null +++ b/src/main/java/mods/immibis/tinycarts/EntityMinecartAwesome.java @@ -0,0 +1,340 @@ +package mods.immibis.tinycarts; + +import java.lang.ref.WeakReference; +import java.util.HashMap; +import java.util.Map; + +import mods.immibis.subworlds.FakeEntity; +import mods.immibis.subworlds.SubWorldsMod; +import mods.immibis.subworlds.dw.DWEntity; +import mods.immibis.subworlds.dw.DWManager; +import mods.immibis.subworlds.dw.DWWorldProvider; +import mods.immibis.subworlds.dw.WorldProps; +import net.minecraft.entity.item.EntityMinecart; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.management.ServerConfigurationManager; +import net.minecraft.util.Vec3; +import net.minecraft.world.ChunkCoordIntPair; +import net.minecraft.world.World; +import net.minecraft.world.WorldServer; +import net.minecraftforge.common.ForgeChunkManager; +import net.minecraftforge.common.ForgeChunkManager.Ticket; +import net.minecraftforge.common.ForgeChunkManager.Type; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.entity.minecart.MinecartInteractEvent; + +import org.lwjgl.opengl.GL11; + +public class EntityMinecartAwesome extends EntityMinecart { + + public static final int XSIZE = 16; + public static final int ZSIZE = 10; + public static final int YSIZE = 10; + + public static final int SCALE = 10; // render scale; internal blocks per external block + + public EntityMinecartAwesome(World par1World) { + super(par1World); + } + + public EntityMinecartAwesome(World w, double x, double y, double z) { + super(w, x, y, z); + if(w.isRemote) + throw new IllegalStateException(); + + WorldProps props = new WorldProps(); + props.xsize = XSIZE; + props.ysize = YSIZE; + props.zsize = ZSIZE; + props.generatorClass = InteriorChunkGen.class; + internalWorldID = DWManager.createWorld(props); + } + + + @Override + public double getMountedYOffset() { + return 0.3f; + } + + + private int internalWorldID; + @Override + protected void readEntityFromNBT(NBTTagCompound par1nbtTagCompound) { + super.readEntityFromNBT(par1nbtTagCompound); + internalWorldID = par1nbtTagCompound.getInteger("intwid"); + } + @Override + protected void writeEntityToNBT(NBTTagCompound par1nbtTagCompound) { + super.writeEntityToNBT(par1nbtTagCompound); + par1nbtTagCompound.setInteger("intwid", internalWorldID); + } + + private DWSubEntity getDW() { + if(riddenByEntity instanceof DWSubEntity) + return (DWSubEntity)riddenByEntity; + else if(worldObj.isRemote) + return null; + else { + DWSubEntity dwsub = new DWSubEntity(worldObj); + dwsub.posX = posX; + dwsub.posY = posY; + dwsub.posZ = posZ; + worldObj.spawnEntityInWorld(dwsub); + dwsub.mountEntity(this); + return dwsub; + } + } + + @Override + public void onUpdate() { + super.onUpdate(); + + getDW(); + } + + @Override + public boolean interactFirst(EntityPlayer par1EntityPlayer) + { + if(MinecraftForge.EVENT_BUS.post(new MinecartInteractEvent(this, par1EntityPlayer))) + return true; + + if(!(par1EntityPlayer instanceof EntityPlayerMP)) + return false; + + if(worldObj.isRemote) + return true; + + + + ServerConfigurationManager scm = MinecraftServer.getServer().getConfigurationManager(); + scm.transferPlayerToDimension((EntityPlayerMP)par1EntityPlayer, internalWorldID, ((DWWorldProvider)getDW().getInternalWorld().provider).teleporter); + + return true; + } + + @Override + public int getMinecartType() { + return -1; + } + + // XXX the whole purpose of this field is hacky. subworlds should keep track of this for us. + static Map> entitiesByInternalID = new HashMap>(); + + public static class DWSubEntity extends DWEntity { + public DWSubEntity(World w) { + super(w); + setSize(Math.min(XSIZE, ZSIZE)/(float)SCALE, YSIZE/(float)SCALE); + } + + public EntityMinecartAwesome getCart() { + if(ridingEntity instanceof EntityMinecartAwesome) + return (EntityMinecartAwesome)ridingEntity; + return null; + } + + private int internalWorldID = Integer.MIN_VALUE; + + @Override + public void onUnloadOrDestroy() { + if(!worldObj.isRemote) { + synchronized(entitiesByInternalID) { + int intWID = internalWorldID; + if(intWID != Integer.MIN_VALUE) { + WeakReference ent = entitiesByInternalID.get(intWID); + if(ent != null && ent.get() == this) { + entitiesByInternalID.remove(intWID); + } + } + } + } + mountEntity(null); + super.onUnloadOrDestroy(); + } + + private Ticket externalTicket, internalTicket; + + private static final int EXTERNAL_VIEW_DISTANCE = 7; + + private static final boolean DEBUG = false; + + private void updateChunkLoading() { + boolean load = requiresInteriorLoaded(); + if(load != (externalTicket != null)) { + if(DEBUG) System.out.println("[TinyCarts DEBUG] External loading: now "+load); + if(load) { + WorldServer world = (WorldServer)worldObj; + + externalTicket = ForgeChunkManager.requestTicket(SubWorldsMod.INSTANCE, world, Type.NORMAL); + + int baseX = (int)(posX) >> 4; + int baseZ = (int)(posZ) >> 4; + + for(int dx = -EXTERNAL_VIEW_DISTANCE; dx <= EXTERNAL_VIEW_DISTANCE; dx++) + for(int dz = -EXTERNAL_VIEW_DISTANCE; dz <= EXTERNAL_VIEW_DISTANCE; dz++) { + int x = dx + baseX, z = dz + baseZ; + ForgeChunkManager.forceChunk(externalTicket, new ChunkCoordIntPair(x, z)); + world.theChunkProviderServer.loadChunk(x, z); + } + + } else { + + ForgeChunkManager.releaseTicket(externalTicket); + externalTicket = null; + } + } + + if(load != (internalTicket != null)) { + if(load) { + WorldServer world = getInternalWorld(); + if(world == null) + throw new IllegalStateException("Failed to load internal world."); + + internalTicket = ForgeChunkManager.requestTicket(SubWorldsMod.INSTANCE, world, Type.NORMAL); + + int size = (Math.max(XSIZE, YSIZE)+15)/16; + + if(DEBUG) System.out.println("[TinyCarts DEBUG] Internal loading: now true ("+size+"x"+size+")"); + for(int x = 0; x < size; x++) + for(int z = 0; z < size; z++) { + ForgeChunkManager.forceChunk(internalTicket, new ChunkCoordIntPair(x, z)); + world.theChunkProviderServer.loadChunk(x, z); + } + + } else { + + if(DEBUG) System.out.println("[TinyCarts DEBUG] Internal loading: now false"); + ForgeChunkManager.releaseTicket(internalTicket); + internalTicket = null; + } + } + + if(!isDead) { // should always be true + int intWID = internalWorldID = getCart().internalWorldID; + synchronized(entitiesByInternalID) { + WeakReference ref = entitiesByInternalID.get(intWID); + DWSubEntity reffed = (ref == null ? null : ref.get()); + if(reffed == null || (reffed.isDead && reffed != this)) { + entitiesByInternalID.put(intWID, new WeakReference(this)); + } + } + } else assert false : "updateChunkLoading on dead entity?"; + } + + private static float getActualClientMinecartYaw(EntityMinecart cart, double old) { + double x = cart.posX, y = cart.posY, z = cart.posZ; + Vec3 vec3 = cart.func_70489_a(x, y, z); + //float f5 = cart.rotationPitch; + double d6 = 0.30000001192092896D; + float yaw = cart.rotationYaw; + + if (vec3 != null) + { + Vec3 vec31 = cart.func_70495_a(x, y, z, d6); + Vec3 vec32 = cart.func_70495_a(x, y, z, -d6); + + if (vec31 == null) + { + vec31 = vec3; + } + + if (vec32 == null) + { + vec32 = vec3; + } + + //renderX += vec3.xCoord - x; + //renderY += (vec31.yCoord + vec32.yCoord) / 2.0D - y; + //renderZ += vec3.zCoord - z; + Vec3 vec33 = vec32.addVector(-vec31.xCoord, -vec31.yCoord, -vec31.zCoord); + + if (vec33.lengthVector() != 0.0D) + { + vec33 = vec33.normalize(); + yaw = (float)(Math.atan2(vec33.zCoord, vec33.xCoord) * 180.0D / Math.PI); + //f5 = (float)(Math.atan(vec33.yCoord) * 73.0D); + } + } + + /*if(Math.abs(angleDiff(old, yaw)) > Math.abs(angleDiff(old, yaw+180))) + if(yaw > 0) + yaw -= 180; + else + yaw += 180;*/ + + return yaw; + } + + /*private static double angleDiff(double a, double b) { + b -= a; + if(b < 0) + b = (b % 360) + 360; + b = (b + 180) % 360 - 180; + assert !(b < -180 || b > 180) : "angleDiff is broken"; + return b; + }*/ + + @Override + public void onUpdate() { + if(!worldObj.isRemote && (getCart() == null || getCart().getDW() != this)) { + setDead(); + return; + } + + //if(ridingEntity != null && worldObj.isRemote) + // System.out.println(worldObj.isRemote+" "+(int)posX+" "+(int)posY+" "+(int)posZ+" "+(int)ridingEntity.posX+" "+(int)ridingEntity.posY+" "+(int)ridingEntity.posZ); + + if(getCart() != null) + rotationYaw = !worldObj.isRemote ? ridingEntity.rotationYaw : getActualClientMinecartYaw(getCart(), rotationYaw); + //if(worldObj.isRemote) { + //System.out.println("yaw "+rotationYaw); + //} + + super.onUpdate(); + + if(!worldObj.isRemote) { + updateChunkLoading(); + } + } + + @Override + protected void applyGLRotation(float partialTick) { + GL11.glScalef(1.0f/SCALE, 1.0f/SCALE, 1.0f/SCALE); + if(ridingEntity != null) + GL11.glRotatef(-rotationYaw, 0, 1, 0); + } + + @Override + protected FakeEntity createExternalWorldFE() { + return new CartExternalFakeEntity(false, getCart().internalWorldID); + } + + @Override + protected WorldServer getInternalWorld() { + return MinecraftServer.getServer().worldServerForDimension(getCart().internalWorldID); + } + + @Override + protected void updateExternalWorldFE(FakeEntity e_) { + CartExternalFakeEntity e = (CartExternalFakeEntity)e_; + e.x = posX; + e.y = posY; + e.z = posZ; + e.yaw = ridingEntity == null ? 0 : ridingEntity.rotationYaw; + } + + // this is called when a position update is received from the server. + // by overriding it we make sure the client doesn't try to be smart and push this entity out of bounding boxes. + // also, ignore the position from the update packet. because it's incorrect for entities that are riding other entities. + // also, ignore the rotation. because we copy that from the cart entity. + // in fact, just do nothing when a position/rotation update is received. + @Override + public void setPositionAndRotation2(double par1, double par3, double par5, float par7, float par8, int par9) { + //this.setPosition(par1, par3, par5); + //this.setRotation(par7, par8); + } + } + +} diff --git a/src/main/java/mods/immibis/tinycarts/InteriorChunkGen.java b/src/main/java/mods/immibis/tinycarts/InteriorChunkGen.java new file mode 100644 index 0000000..ca7649a --- /dev/null +++ b/src/main/java/mods/immibis/tinycarts/InteriorChunkGen.java @@ -0,0 +1,120 @@ +package mods.immibis.tinycarts; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import mods.immibis.subworlds.dw.DWWorldProvider; +import mods.immibis.subworlds.dw.WorldProps; +import net.minecraft.entity.EnumCreatureType; +import net.minecraft.init.Blocks; +import net.minecraft.util.IProgressUpdate; +import net.minecraft.world.ChunkPosition; +import net.minecraft.world.World; +import net.minecraft.world.WorldServer; +import net.minecraft.world.biome.BiomeGenBase; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.IChunkProvider; + +public class InteriorChunkGen implements IChunkProvider { + + private WorldProps props; + private WorldServer w; + + public InteriorChunkGen(WorldServer w, DWWorldProvider wp) { + this.props = wp.props; + this.w = w; + } + + @Override + public Chunk provideChunk(int i, int j) { + Chunk c = new Chunk(w, i, j); + + BlockTransparentBedrock.allowPlacement.incrementAndGet(); + + try { + if(i == 0 && j == 0) { + for(int x = 0; x < props.xsize; x++) + for(int z = 0; z < props.zsize; z++) { + c.func_150807_a(x, 0, z, TinyCartsMod.transparentBedrock, 0); + c.func_150807_a(x, props.ysize-1, z, TinyCartsMod.transparentBedrock, 0); + c.func_150807_a(x, 1, z, Blocks.planks, 0); + } + + for(int x = 0; x < props.xsize; x++) + for(int y = 0; y < props.ysize; y++) { + c.func_150807_a(x, y, 0, TinyCartsMod.transparentBedrock, 0); + c.func_150807_a(x, y, props.zsize-1, TinyCartsMod.transparentBedrock, 0); + c.func_150807_a(0, y, x, TinyCartsMod.transparentBedrock, 0); + c.func_150807_a(props.xsize-1, y, x, TinyCartsMod.transparentBedrock, 0); + } + } + } finally { + BlockTransparentBedrock.allowPlacement.decrementAndGet(); + } + + c.generateSkylightMap(); + Arrays.fill(c.getBiomeArray(), (byte)BiomeGenBase.plains.biomeID); + c.isTerrainPopulated = true; + + return c; + } + + @Override + public boolean chunkExists(int i, int j) { + return true; + } + + @Override + public Chunk loadChunk(int i, int j) { + return provideChunk(i, j); + } + + @Override + public void populate(IChunkProvider ichunkprovider, int i, int j) { + + } + + @Override + public boolean saveChunks(boolean flag, IProgressUpdate iprogressupdate) { + return true; + } + + @Override + public boolean unloadQueuedChunks() { + return false; + } + + @Override + public boolean canSave() { + return true; + } + + @Override + public String makeString() { + return getClass().getName(); + } + + @Override + public List getPossibleCreatures(EnumCreatureType enumcreaturetype, int i, int j, int k) { + return Collections.EMPTY_LIST; + } + + @Override + public ChunkPosition func_147416_a(World var1, String var2, int var3, int var4, int var5) { + return null; + } + + @Override + public int getLoadedChunkCount() { + return 0; + } + + @Override + public void recreateStructures(int i, int j) { + } + + @Override + public void saveExtraData() { + } +} diff --git a/src/main/java/mods/immibis/tinycarts/ItemMinecartAwesome.java b/src/main/java/mods/immibis/tinycarts/ItemMinecartAwesome.java new file mode 100644 index 0000000..4bd8c62 --- /dev/null +++ b/src/main/java/mods/immibis/tinycarts/ItemMinecartAwesome.java @@ -0,0 +1,57 @@ +package mods.immibis.tinycarts; + +import mods.immibis.subworlds.dw.DWWorldProvider; +import net.minecraft.block.Block; +import net.minecraft.block.BlockRailBase; +import net.minecraft.creativetab.CreativeTabs; +import net.minecraft.entity.item.EntityMinecart; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.util.ChatComponentText; +import net.minecraft.world.World; + +public class ItemMinecartAwesome extends Item { + public ItemMinecartAwesome() + { + this.maxStackSize = 1; + + this.setCreativeTab(CreativeTabs.tabTransport); + this.setTextureName("tinycarts:minecart"); + this.setUnlocalizedName("tinycarts.cart"); + } + + @Override + public boolean onItemUse(ItemStack par1ItemStack, EntityPlayer par2EntityPlayer, World par3World, int par4, int par5, int par6, int par7, float par8, float par9, float par10) + { + if(par3World.provider instanceof DWWorldProvider) { + if(par3World.isRemote) + par2EntityPlayer.addChatMessage(new ChatComponentText("Don't even think about it.")); + return false; + } + + Block i1 = par3World.getBlock(par4, par5, par6); + + if (BlockRailBase.func_150051_a(i1)) + { + if (!par3World.isRemote) + { + EntityMinecart entityminecart = new EntityMinecartAwesome(par3World, (double)((float)par4 + 0.5F), (double)((float)par5 + 0.5F), (double)((float)par6 + 0.5F)); + + if (par1ItemStack.hasDisplayName()) + { + entityminecart.setMinecartName(par1ItemStack.getDisplayName()); + } + + par3World.spawnEntityInWorld(entityminecart); + } + + --par1ItemStack.stackSize; + return true; + } + else + { + return false; + } + } +} diff --git a/src/main/java/mods/immibis/tinycarts/NetworkHandler.java b/src/main/java/mods/immibis/tinycarts/NetworkHandler.java new file mode 100644 index 0000000..89d65f1 --- /dev/null +++ b/src/main/java/mods/immibis/tinycarts/NetworkHandler.java @@ -0,0 +1,34 @@ +package mods.immibis.tinycarts; + +import mods.immibis.core.api.net.IPacket; +import mods.immibis.core.api.net.IPacketMap; + +public class NetworkHandler implements IPacketMap { + + public static final String CHANNEL = "TinyCarts"; + + public static final byte PKT_CEFE_CREATE = 0; + public static final byte PKT_CEFE_DELETE = 1; + public static final byte PKT_CEFE_UPDATE = 2; + + @Override + public String getChannel() { + return CHANNEL; + } + + @Override + public IPacket createS2CPacket(byte id) { + switch(id) { + case PKT_CEFE_CREATE: return new CartExternalFakeEntity.CreatePacket(); + case PKT_CEFE_DELETE: return new CartExternalFakeEntity.DeletePacket(); + case PKT_CEFE_UPDATE: return new CartExternalFakeEntity.UpdatePacket(); + } + return null; + } + + @Override + public IPacket createC2SPacket(byte id) { + return null; + } + +} diff --git a/src/main/java/mods/immibis/tinycarts/RenderMinecartAwesome.java b/src/main/java/mods/immibis/tinycarts/RenderMinecartAwesome.java new file mode 100644 index 0000000..28a8297 --- /dev/null +++ b/src/main/java/mods/immibis/tinycarts/RenderMinecartAwesome.java @@ -0,0 +1,14 @@ +package mods.immibis.tinycarts; + +import net.minecraft.client.renderer.entity.RenderMinecart; +import net.minecraft.entity.item.EntityMinecart; +import net.minecraft.util.ResourceLocation; + +public class RenderMinecartAwesome extends RenderMinecart { + private static final ResourceLocation texture = new ResourceLocation("tinycarts", "textures/entity/minecart.png"); + + @Override + protected ResourceLocation getEntityTexture(EntityMinecart par1EntityMinecart) { + return texture; + } +} diff --git a/src/main/java/mods/immibis/tinycarts/TinyCartsMod.java b/src/main/java/mods/immibis/tinycarts/TinyCartsMod.java new file mode 100644 index 0000000..be0b998 --- /dev/null +++ b/src/main/java/mods/immibis/tinycarts/TinyCartsMod.java @@ -0,0 +1,122 @@ +package mods.immibis.tinycarts; + +import java.lang.ref.WeakReference; + +import mods.immibis.cobaltite.AssignedBlock; +import mods.immibis.cobaltite.AssignedItem; +import mods.immibis.cobaltite.CobaltiteMod; +import mods.immibis.cobaltite.ModBase; +import mods.immibis.core.api.APILocator; +import mods.immibis.core.api.FMLModInfo; +import mods.immibis.subworlds.ExitTeleporter; +import mods.immibis.subworlds.dw.DWWorldProvider; +import net.minecraft.entity.Entity; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.init.Items; +import net.minecraft.item.ItemStack; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.management.ServerConfigurationManager; +import net.minecraft.util.ChatComponentText; +import net.minecraft.world.WorldServer; +import cpw.mods.fml.client.registry.RenderingRegistry; +import cpw.mods.fml.common.Mod; +import cpw.mods.fml.common.Mod.EventHandler; +import cpw.mods.fml.common.event.FMLInitializationEvent; +import cpw.mods.fml.common.event.FMLPreInitializationEvent; +import cpw.mods.fml.common.event.FMLServerStartingEvent; +import cpw.mods.fml.common.registry.EntityRegistry; +import cpw.mods.fml.common.registry.GameRegistry; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +/** + * IMPORTANT SMALL DETAIL: -X EITHER FORWARD OR BACKWARD, -Z IS SIDEWAYS. + */ +@Mod(modid="TinyCarts", name="TinyCarts", version="0.2", dependencies="required-after:SubWorlds") +@CobaltiteMod() +@FMLModInfo(authors="immibis", description="", url="") +public class TinyCartsMod extends ModBase { + @AssignedItem(id="awesomecart") + public static ItemMinecartAwesome itemCart; + + @AssignedBlock(id="transparentbedrock") + public static BlockTransparentBedrock transparentBedrock; + + @EventHandler + public void init(FMLInitializationEvent evt) { + EntityRegistry.registerModEntity(EntityMinecartAwesome.class, "tinycarts.cart", 0, this, 50, 1, true); + EntityRegistry.registerModEntity(EntityMinecartAwesome.DWSubEntity.class, "tinycarts.cart.dwsub", 1, this, 50, 1, true); + + APILocator.getNetManager().listen(new NetworkHandler()); + } + + @Override + protected void addRecipes() throws Exception { + GameRegistry.addRecipe(new ItemStack(itemCart), "D D", "DDD", 'D', Items.diamond); + } + + @EventHandler + public void onServerStart(FMLServerStartingEvent evt) { + evt.registerServerCommand(new CommandExitCart()); + } + + @Override + @SideOnly(Side.CLIENT) + protected void clientInit() throws Exception { + RenderingRegistry.registerEntityRenderingHandler(EntityMinecartAwesome.class, new RenderMinecartAwesome()); + } + + @EventHandler + public void __init(FMLInitializationEvent evt) {super._init(evt);} + @EventHandler + public void __preinit(FMLPreInitializationEvent evt) {super._preinit(evt);} + + private static long lastRFCWarnTime = 0; + public static void removeFromCart(Entity ent, boolean ignoreIfNotInCart) { + if(ent.worldObj.isRemote) { + assert false : "removeFromCart - on client world!"; + return; + } + + if(!(ent.worldObj.provider instanceof DWWorldProvider)) { + assert ignoreIfNotInCart : "removeFromCart - not in DW world!"; + return; + } + + DWWorldProvider wp = (DWWorldProvider)ent.worldObj.provider; + if(wp.props.generatorClass != InteriorChunkGen.class) { + assert ignoreIfNotInCart : "removeFromCart - not in TinyCart world!"; + return; + } + + // XXX this way of getting the entity is hacky + WeakReference dwsub_ref = EntityMinecartAwesome.entitiesByInternalID.get(wp.dimensionId); + + EntityMinecartAwesome.DWSubEntity dwsub = dwsub_ref == null ? null : dwsub_ref.get(); + if(dwsub == null) { + if(lastRFCWarnTime - System.nanoTime() > 5000) { + System.out.println("[TinyCarts] Potential problem - entity is leaving a cart, but we can't find the outside of the cart! Entity is "+ent+", internal world ID is "+ent.worldObj.provider.dimensionId+". This message will only be printed once every 5 seconds, maximum."); + lastRFCWarnTime = System.nanoTime(); + } + if(ent instanceof EntityPlayer) + ((EntityPlayer)ent).addChatMessage(new ChatComponentText("Uh oh! Outside of the cart could not be found! Cannot teleport you, sorry.")); + return; + } + + WorldServer outsideWorld = (WorldServer)dwsub.worldObj; + double x = dwsub.posX; + double y = dwsub.posY; + double z = dwsub.posZ; + + x += (outsideWorld.rand.nextDouble() + 1) * (outsideWorld.rand.nextBoolean() ? -1 : 1); + z += (outsideWorld.rand.nextDouble() + 1) * (outsideWorld.rand.nextBoolean() ? -1 : 1); + + ServerConfigurationManager scm = MinecraftServer.getServer().getConfigurationManager(); + if(ent instanceof EntityPlayerMP) + scm.transferPlayerToDimension((EntityPlayerMP)ent, outsideWorld.provider.dimensionId, new ExitTeleporter(outsideWorld, x, y, z)); + //else + // scm.transferEntityToWorld(ent, par2, par3WorldServer, par4WorldServer) + + } +} diff --git a/src/main/resources/assets/tinycarts/lang/en_US.lang b/src/main/resources/assets/tinycarts/lang/en_US.lang new file mode 100644 index 0000000..c92b721 --- /dev/null +++ b/src/main/resources/assets/tinycarts/lang/en_US.lang @@ -0,0 +1,2 @@ +tile.tinycarts.transparent_bedrock.name=You're not supposed to have this block! +item.tinycarts.cart.name=Expandable Minecart \ No newline at end of file diff --git a/src/main/resources/assets/tinycarts/textures/entity/minecart.png b/src/main/resources/assets/tinycarts/textures/entity/minecart.png new file mode 100644 index 0000000000000000000000000000000000000000..c8e203eeb89724a25a15f93aea878cf52a8cfcb6 GIT binary patch literal 2345 zcmV+^3D)+BP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!T1;MAa*k@H7+qRNAp5A000P>Nkl9Yrg7#I5Hlb`Gqdh8GsGx!pDky>q`Vi5_=-ci5 zp7)&V`=92cPDH+NxZ8Q(xzBT+bMF05C7$W8zn^Bl_o)VS~MH1*k+ z)08KlQ;SX8`$}qj>5Xjung8xH`H5%KsLhwADSKW_bz5&uBThLZ^>1oT!&a|P!`5s_ z!`5v~{bx0&e$!{Ap(m_LwHIBH>hIsJcX-!7^WS@#zx+;-p7#8!Y06W3Q^Ny~rG|%} zP`~lzw^HMauZtAMxiy>o*dE!WT~EtcYx?|~!l$H^Nsl~Pe9z_}idH^m(_VN@e;b9N zD9pFjbI+Xbf7M26FWHjDT)kD1uD$FkMHU-#<@H$+tdG9pnyd=^hOJtcMqPNhz|Q{Y zO98CE_fd@}-u0k>PPp?vfgiu)&NONFGik!kdo*8v&m#ie@ZjT_2;WxEJ#D}Ip*$Bb z0|PLC4nO%cjYpn;aq8DNQ^btjc3T=S=cs}x2r$y{fe3&!efo8Egl$fJR|I_sKoyu2 zVRNCDe?I0m#OY@hM3@@z zM@6mQ{wxsPMHw{!n0cw;j=NMtP(={*uMp6(|3^u0KxBaI*v@Z_{wE2sb_+K|8)BB&9J$8Fy! z@Z)a2Jx#p(A(?Nh=bk8nL>tLN1Qv=A5oIM9SlWIwn^YSh0s|0aq5$p%M4RtMpLG;L z%e>6y%QXQN__liPY5)DNH0#4JGVp8;7$Pu;K=v`OF=l;;VDnURWDIDjzyK0;#?%79 zqXs}z6PO1$ty0G3eOo>MwEgtEh-mt3zwQLo0#1fFqI6Sy=VKL2{E$3S<_N^_0|+4F zMv43bB*m1=6TqboRAK877WV}Fs3-cno-X*z`rz|JPfYu7f2OI=?b9V^aL3}*yKbC} zJ{KM~!@4$XV2S9U1)bTCJ{6sO20V8H2*P*J{DrCa=sMZJV-|=6P6O1S(}4?>MPJ{; zuXaot*gj8dd~?Q|9~?RYh$IYi)D2y^wzvyeo^ubNCQ$fv3|isKJ`}?+FNzezA;+DV zjmQD2_o&eseD5)1iRt z+u5rArbTnp1-755Km_ldxLQQ;4g_EUE)#%ax+B5?V!cMz7X5BW@NJ8LD_6` zcF1m6wNSkXjO5gcfGsPb>_ck!XDSu)pUN%? z6Imx>`1(_dv8#YFHG#IQhOUay`!=Fri}XVkRRmA~*(`aB z31k^qInS*HDD#XXHIz0KNIS-|0{AWjI6!_y3RnqnnTY615%{=YpN7e4z})s?zC^&8 z^P2?{K-ZphfCva+e*3zD5LAGn4#p6{$>5(UP5~}9A!z_W1b{NQ$aAX!_yk#@3cVXj zF~V3wRS+P_8yFN&j%)x@-=izgP5{!Uk0KyI-!Wg`b_EcCC)$9HQ~*4LV9Znb0Umx~ z{+S{LP(Y+WIr>tBMKk9;3P2IA1`)xz4;iis`B)zUR0IKJ8z0zqbS`*(>L(o_B0&3T zQC0+Fh~Q)hvV7YW08s>yHGmjHLRicNDU4|`?hpQ%f&h-JLGwCvzN_k6)J24WR^|8T zOAXdSLZn8hL+_IUP6P!I5+c%o0DKj2Yat^{5x2@Ouwmw>0_p_$wp9dCKtvdT^qIpL zhbov?T9_ZCRULyw2?ENuxEy&SfnS9DM3w?v?oxzDnl?usVmyVAKPQZ&Yrr_lqAv>) z!fYHeXRH)3(9Cgv2ts_@6#!ncyj{S^YsP>BFfGxC2-YzCNDJ-{RmULUQ9)M%Xt}o& zRn-E&5vs~l3%q6o5h{+Z3Z=k4`&n!jz$k*M@*ZL4bk;M6G9V?0VBX0R0dWkL2;e#! z2KcxN6I~<@Pz)ld7jX^7x9Sw&#|^*2036ZFB$(AKJ6^zHCVnJgQSKL>DxYdPfmeu zA|7+S!(xrTOT1ePUo+qK!F#$A5T;*sMh5+myVimT zQ-Ty@K2k)902GjqK>(IXC=-B{*S*qXaU5#w@;@XgaQHd=9De@CPo?rVTKYM}FatH5 P00000NkvXXu0mjfk!Ue8 literal 0 HcmV?d00001 diff --git a/src/main/resources/assets/tinycarts/textures/items/minecart.png b/src/main/resources/assets/tinycarts/textures/items/minecart.png new file mode 100644 index 0000000000000000000000000000000000000000..e192dbb041c1a15e45526432faf1e17a2591bc19 GIT binary patch literal 354 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCij$3p^r=85sBugD~Uq{1qucL8%hgh?3y^w370~qEv=}#LT=BJwMkFg)(D3 zQ$0gN_s>q|Kvfq#T^vI!{I~Y`avd@dXl+h7x?{tR7fvf&S;UhZKd?9n#O(N>q*uin zE_Zn8@-=%dgsa(4ZLs(-Bjv<*-z)dCV?=Z>_g?C{@TOqP%=tdEjM@JLLx&Bud*2GAGet&zJvcYv zYegFqkLE-Lr-I7-H`ZSq7$Us>^a{E0v$QNd(w