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;
}
}