using System.Runtime.InteropServices; using Minecraft.Server.FourKit.Chunk; using Minecraft.Server.FourKit.Entity; using Minecraft.Server.FourKit.Inventory; namespace Minecraft.Server.FourKit; /// /// Represents a world, which may contain entities, chunks and blocks. /// public class World { private readonly int _dimensionId; private readonly string _name; internal World(int dimensionId, string name) { _dimensionId = dimensionId; _name = name; } /// /// Gets the dimension ID of this world. /// /// Dimension ID of this world. public int getDimensionId() => _dimensionId; /// /// Gets the unique name of this world. /// /// Name of this world. public string getName() => _name; /// /// Gets the Block at the given coordinates. /// /// X-coordinate of the block. /// Y-coordinate of the block. /// Z-coordinate of the block. /// Block at the given coordinates. public Block.Block getBlockAt(int x, int y, int z) { return new Block.Block(this, x, y, z); } /// /// Gets the Block at the given Location. /// /// Location of the block. /// Block at the given location. public Block.Block getBlockAt(Location location) { return getBlockAt(location.getBlockX(), location.getBlockY(), location.getBlockZ()); } /// /// Gets the block type ID at the given coordinates. /// /// X-coordinate of the block. /// Y-coordinate of the block. /// Z-coordinate of the block. /// Type ID of the block. public int getBlockTypeIdAt(int x, int y, int z) { if (NativeBridge.GetTileId != null) return NativeBridge.GetTileId(_dimensionId, x, y, z); return 0; } /// /// Gets the block type ID at the given Location. /// /// Location of the block. /// Type ID of the block. public int getBlockTypeIdAt(Location location) { return getBlockTypeIdAt(location.getBlockX(), location.getBlockY(), location.getBlockZ()); } /// /// Gets the highest non-air coordinate at the given coordinates. /// /// X-coordinate. /// Z-coordinate. /// The Y-coordinate of the highest non-air block. public int getHighestBlockYAt(int x, int z) { if (NativeBridge.GetHighestBlockY != null) return NativeBridge.GetHighestBlockY(_dimensionId, x, z); return 0; } /// /// Gets the highest non-air coordinate at the given Location. /// /// Location to check. /// The Y-coordinate of the highest non-air block. public int getHighestBlockYAt(Location location) { return getHighestBlockYAt(location.getBlockX(), location.getBlockZ()); } /// /// Gets the highest non-empty block at the given coordinates. /// /// X-coordinate. /// Z-coordinate. /// Highest non-empty block. public Block.Block getHighestBlockAt(int x, int z) { int y = getHighestBlockYAt(x, z); return getBlockAt(x, y, z); } /// /// Gets the highest non-empty block at the given Location. /// /// Coordinates to get the highest block at. /// Highest non-empty block. public Block.Block getHighestBlockAt(Location location) { return getHighestBlockAt(location.getBlockX(), location.getBlockZ()); } private double[] GetWorldInfoSnapshot() { double[] buf = new double[7]; if (NativeBridge.GetWorldInfo != null) { var gh = GCHandle.Alloc(buf, GCHandleType.Pinned); try { NativeBridge.GetWorldInfo(_dimensionId, gh.AddrOfPinnedObject()); } finally { gh.Free(); } } return buf; } /// /// Gets the default spawn Location of this world. /// /// The spawn location of this world. public Location getSpawnLocation() { double[] info = GetWorldInfoSnapshot(); return new Location(this, info[0], info[1], info[2], 0f, 0f); } /// /// Sets the spawn location of the world. /// /// X-coordinate. /// Y-coordinate. /// Z-coordinate. /// True if the spawn was set successfully. public bool setSpawnLocation(int x, int y, int z) { if (NativeBridge.SetSpawnLocation != null) return NativeBridge.SetSpawnLocation(_dimensionId, x, y, z) != 0; return false; } /// /// Gets the Seed for this world. /// /// This world's Seed. public long getSeed() { double[] info = GetWorldInfoSnapshot(); return (long)info[3]; } /// /// Gets the relative in-game time of this world. /// /// The current relative time. public long getTime() { double[] info = GetWorldInfoSnapshot(); return (long)info[4]; } /// /// Sets the relative in-game time on the server. /// /// The new relative time to set the in-game time to. public void setTime(long time) { NativeBridge.SetWorldTime?.Invoke(_dimensionId, time); } /// /// Sets the in-game time on the server. /// /// The new absolute time to set this world to. public void setFullTime(long time) { NativeBridge.SetWorldTime?.Invoke(_dimensionId, time); } /// /// Set whether there is a storm. /// /// Whether there is rain and snow. public void setStorm(bool hasStorm) { NativeBridge.SetWeather?.Invoke(_dimensionId, hasStorm ? 1 : 0, -1, -1); } /// /// Set whether it is thundering. /// /// Whether it is thundering. public void setThundering(bool thundering) { NativeBridge.SetWeather?.Invoke(_dimensionId, -1, thundering ? 1 : 0, -1); } /// /// Set the thundering duration. /// /// Duration in ticks. public void setThunderDuration(int duration) { NativeBridge.SetWeather?.Invoke(_dimensionId, -1, -1, duration); } /// /// Get a list of all players in this World. /// /// A list of all Players currently residing in this world. public List getPlayers() { var all = FourKit.getOnlinePlayers(); var result = new List(); foreach (var p in all) { var loc = p.getLocation(); if (loc?.LocationWorld == this) result.Add(p); } return result; } /// /// Get a list of all entities in this World. /// /// A list of all Entities currently residing in this world. public List getEntities() { var result = new List(); if (NativeBridge.GetWorldEntities == null) return result; int count = NativeBridge.GetWorldEntities(_dimensionId, out IntPtr buf); if (count <= 0 || buf == IntPtr.Zero) return result; try { int[] data = new int[count * 3]; Marshal.Copy(buf, data, 0, count * 3); for (int i = 0; i < count; i++) { int entityId = data[i * 3]; int mappedType = data[i * 3 + 1]; int isLiving = data[i * 3 + 2]; var entityType = Enum.IsDefined(typeof(Entity.EntityType), mappedType) ? (Entity.EntityType)mappedType : Entity.EntityType.UNKNOWN; if (entityType == Entity.EntityType.PLAYER) { var player = FourKit.GetPlayerByEntityId(entityId); if (player != null) { result.Add(player); continue; } } if (isLiving == 1) { result.Add(new Entity.LivingEntity(entityId, entityType, _dimensionId, 0, 0, 0)); } else { var entity = new Entity.Entity(); entity.SetEntityIdInternal(entityId); entity.SetEntityTypeInternal(entityType); entity.SetDimensionInternal(_dimensionId); result.Add(entity); } } } finally { Marshal.FreeCoTaskMem(buf); } return result; } /// /// Get a list of all living entities in this World. /// /// A list of all LivingEntities currently residing in this world. public List getLivingEntities() { var result = new List(); if (NativeBridge.GetWorldEntities == null) return result; int count = NativeBridge.GetWorldEntities(_dimensionId, out IntPtr buf); if (count <= 0 || buf == IntPtr.Zero) return result; try { int[] data = new int[count * 3]; Marshal.Copy(buf, data, 0, count * 3); for (int i = 0; i < count; i++) { int entityId = data[i * 3]; int mappedType = data[i * 3 + 1]; int isLiving = data[i * 3 + 2]; if (isLiving != 1) continue; var entityType = Enum.IsDefined(typeof(Entity.EntityType), mappedType) ? (Entity.EntityType)mappedType : Entity.EntityType.UNKNOWN; if (entityType == Entity.EntityType.PLAYER) { var player = FourKit.GetPlayerByEntityId(entityId); if (player != null) { result.Add(player); continue; } } result.Add(new Entity.LivingEntity(entityId, entityType, _dimensionId, 0, 0, 0)); } } finally { Marshal.FreeCoTaskMem(buf); } return result; } /// /// Creates explosion at given coordinates with given power. /// /// X-coordinate. /// Y-coordinate. /// Z-coordinate. /// The power of explosion, where 4F is TNT. /// false if explosion was canceled, otherwise true. public bool createExplosion(double x, double y, double z, float power) { return createExplosion(x, y, z, power, false, true); } /// /// Creates explosion at given coordinates with given power and optionally /// setting blocks on fire. /// /// X-coordinate. /// Y-coordinate. /// Z-coordinate. /// The power of explosion, where 4F is TNT. /// Whether or not to set blocks on fire. /// false if explosion was canceled, otherwise true. public bool createExplosion(double x, double y, double z, float power, bool setFire) { return createExplosion(x, y, z, power, setFire, true); } /// /// Creates explosion at given coordinates with given power and optionally /// setting blocks on fire or breaking blocks. /// /// X-coordinate. /// Y-coordinate. /// Z-coordinate. /// The power of explosion, where 4F is TNT. /// Whether or not to set blocks on fire. /// Whether or not to have blocks be destroyed. /// false if explosion was canceled, otherwise true. public bool createExplosion(double x, double y, double z, float power, bool setFire, bool breakBlocks) { if (NativeBridge.CreateExplosion != null) return NativeBridge.CreateExplosion(_dimensionId, x, y, z, power, setFire ? 1 : 0, breakBlocks ? 1 : 0) != 0; return false; } /// /// Creates explosion at given coordinates with given power and optionally /// setting blocks on fire or breaking blocks. /// /// Location to blow up. /// The power of explosion, where 4F is TNT. /// Whether or not to set blocks on fire. /// Whether or not to have blocks be destroyed. /// false if explosion was canceled, otherwise true. public bool createExplosion(Location loc, float power, bool setFire, bool breakBlocks) { if (NativeBridge.CreateExplosion != null) return NativeBridge.CreateExplosion(_dimensionId, loc.X, loc.Y, loc.Z, power, setFire ? 1 : 0, breakBlocks ? 1 : 0) != 0; return false; } /// /// Creates explosion at given coordinates with given power. /// /// Location to blow up. /// The power of explosion, where 4F is TNT. /// false if explosion was canceled, otherwise true. public bool createExplosion(Location loc, float power) { return createExplosion(loc.X, loc.Y, loc.Z, power); } /// /// Creates explosion at given coordinates with given power and optionally /// setting blocks on fire. /// /// Location to blow up. /// The power of explosion, where 4F is TNT. /// Whether or not to set blocks on fire. /// false if explosion was canceled, otherwise true. public bool createExplosion(Location loc, float power, bool setFire) { return createExplosion(loc.X, loc.Y, loc.Z, power, setFire); } /// /// Strikes lightning at the given Location. /// /// The location to strike lightning. /// true if lightning was successfully summoned. public bool strikeLightning(Location loc) { if (NativeBridge.StrikeLightning != null) return NativeBridge.StrikeLightning(_dimensionId, loc.X, loc.Y, loc.Z, 0) != 0; return false; } /// /// Strikes lightning at the given Location without doing damage. /// /// The location to strike lightning. /// true if lightning was successfully summoned. public bool strikeLightningEffect(Location loc) { if (NativeBridge.StrikeLightning != null) return NativeBridge.StrikeLightning(_dimensionId, loc.X, loc.Y, loc.Z, 1) != 0; return false; } /// /// Drops an item at the specified Location. /// /// Location to drop the item. /// ItemStack to drop. public void dropItem(Location location, ItemStack item) { NativeBridge.DropItem?.Invoke(_dimensionId, location.X, location.Y, location.Z, item.getTypeId(), item.getAmount(), item.getDurability(), 0); } /// /// Drops an item at the specified Location with a random offset. /// /// Location to drop the item. /// ItemStack to drop. public void dropItemNaturally(Location location, ItemStack item) { NativeBridge.DropItem?.Invoke(_dimensionId, location.X, location.Y, location.Z, item.getTypeId(), item.getAmount(), item.getDurability(), 1); } /// /// Gets the Chunk at the given coordinates. /// /// X-coordinate of the chunk. /// Z-coordinate of the chunk. /// Chunk at the given coordinates. public Chunk.Chunk getChunkAt(int x, int z) { return new Chunk.Chunk(this, x, z); } /// /// Gets the Chunk at the given Location. /// /// Location of the chunk. /// Chunk at the given location. public Chunk.Chunk getChunkAt(Location location) { return getChunkAt(location.getBlockX() >> 4, location.getBlockZ() >> 4); } /// /// Gets the Chunk that contains the given Block. /// /// Block to get the containing chunk from. /// The chunk that contains the given block. public Chunk.Chunk getChunkAt(Block.Block block) { return getChunkAt(block.getX() >> 4, block.getZ() >> 4); } /// /// Checks if the specified Chunk is loaded. /// /// The chunk to check. /// true if the chunk is loaded, otherwise false. public bool isChunkLoaded(Chunk.Chunk chunk) { return isChunkLoaded(chunk.getX(), chunk.getZ()); } /// /// Checks if the Chunk at the specified coordinates is loaded. /// /// X-coordinate of the chunk. /// Z-coordinate of the chunk. /// true if the chunk is loaded, otherwise false. public bool isChunkLoaded(int x, int z) { if (NativeBridge.IsChunkLoaded != null) return NativeBridge.IsChunkLoaded(_dimensionId, x, z) != 0; return false; } /// /// Gets an array of all loaded Chunks. /// /// Chunk[] containing all loaded chunks. public Chunk.Chunk[] getLoadedChunks() { if (NativeBridge.GetLoadedChunks == null) return Array.Empty(); int count = NativeBridge.GetLoadedChunks(_dimensionId, out IntPtr buf); if (count <= 0 || buf == IntPtr.Zero) return Array.Empty(); try { int[] coords = new int[count * 2]; Marshal.Copy(buf, coords, 0, count * 2); var chunks = new Chunk.Chunk[count]; for (int i = 0; i < count; i++) chunks[i] = new Chunk.Chunk(this, coords[i * 2], coords[i * 2 + 1]); return chunks; } finally { Marshal.FreeCoTaskMem(buf); } } /// /// Loads the specified Chunk. /// /// The chunk to load. public void loadChunk(Chunk.Chunk chunk) { loadChunk(chunk.getX(), chunk.getZ()); } /// /// Loads the Chunk at the specified coordinates. /// If the chunk does not exist, it will be generated. This method is /// analogous to loadChunk(int, int, boolean) where generate is true. /// /// X-coordinate of the chunk. /// Z-coordinate of the chunk. public void loadChunk(int x, int z) { loadChunk(x, z, true); } /// /// Loads the Chunk at the specified coordinates. /// /// X-coordinate of the chunk. /// Z-coordinate of the chunk. /// Whether or not to generate a chunk if it doesn't already exist. /// true if the chunk has loaded successfully, otherwise false. public bool loadChunk(int x, int z, bool generate) { if (NativeBridge.LoadChunk != null) return NativeBridge.LoadChunk(_dimensionId, x, z, generate ? 1 : 0) != 0; return false; } /// /// Checks if the Chunk at the specified coordinates is loaded and in use /// by one or more players. /// /// X-coordinate of the chunk. /// Z-coordinate of the chunk. /// true if the chunk is loaded and in use by one or more players, otherwise false. public bool isChunkInUse(int x, int z) { if (NativeBridge.IsChunkInUse != null) return NativeBridge.IsChunkInUse(_dimensionId, x, z) != 0; return false; } /// /// Safely unloads and saves the Chunk at the specified coordinates. /// This method is analogous to unloadChunk(int, int, boolean, boolean) /// where safe and save is true. /// /// The chunk to unload. /// true if the chunk has unloaded successfully, otherwise false. public bool unloadChunk(Chunk.Chunk chunk) { return unloadChunk(chunk.getX(), chunk.getZ()); } /// /// Safely unloads and saves the Chunk at the specified coordinates. /// This method is analogous to unloadChunk(int, int, boolean, boolean) /// where safe and save is true. /// /// X-coordinate of the chunk. /// Z-coordinate of the chunk. /// true if the chunk has unloaded successfully, otherwise false. public bool unloadChunk(int x, int z) { return unloadChunk(x, z, true, true); } /// /// Safely unloads and optionally saves the Chunk at the specified coordinates. /// /// X-coordinate of the chunk. /// Z-coordinate of the chunk. /// Whether or not to save the chunk. /// true if the chunk has unloaded successfully, otherwise false. public bool unloadChunk(int x, int z, bool save) { return unloadChunk(x, z, save, true); } /// /// Unloads and optionally saves the Chunk at the specified coordinates. /// /// X-coordinate of the chunk. /// Z-coordinate of the chunk. /// Controls whether the chunk is saved. /// Controls whether to unload the chunk when players are nearby. /// true if the chunk has unloaded successfully, otherwise false. public bool unloadChunk(int x, int z, bool save, bool safe) { if (NativeBridge.UnloadChunk != null) return NativeBridge.UnloadChunk(_dimensionId, x, z, save ? 1 : 0, safe ? 1 : 0) != 0; return false; } /// /// Safely queues the Chunk at the specified coordinates for unloading. /// This method is analogous to unloadChunkRequest(int, int, boolean) /// where safe is true. /// /// X-coordinate of the chunk. /// Z-coordinate of the chunk. /// true is the queue attempt was successful, otherwise false. public bool unloadChunkRequest(int x, int z) { return unloadChunkRequest(x, z, true); } /// /// Queues the Chunk at the specified coordinates for unloading. /// /// X-coordinate of the chunk. /// Z-coordinate of the chunk. /// Controls whether to queue the chunk when players are nearby. /// Whether the chunk was actually queued. public bool unloadChunkRequest(int x, int z, bool safe) { if (NativeBridge.UnloadChunkRequest != null) return NativeBridge.UnloadChunkRequest(_dimensionId, x, z, safe ? 1 : 0) != 0; return false; } /// /// Regenerates the Chunk at the specified coordinates. /// /// X-coordinate of the chunk. /// Z-coordinate of the chunk. /// Whether the chunk was actually regenerated. public bool regenerateChunk(int x, int z) { if (NativeBridge.RegenerateChunk != null) return NativeBridge.RegenerateChunk(_dimensionId, x, z) != 0; return false; } /// /// Resends the Chunk to all clients. /// /// X-coordinate of the chunk. /// Z-coordinate of the chunk. /// Whether the chunk was actually refreshed. public bool refreshChunk(int x, int z) { if (NativeBridge.RefreshChunk != null) return NativeBridge.RefreshChunk(_dimensionId, x, z) != 0; return false; } }