diff --git a/Minecraft.Server.FourKit/Event/Block/BlockFadeEvent.cs b/Minecraft.Server.FourKit/Event/Block/BlockFadeEvent.cs
new file mode 100644
index 000000000..edb45ae5c
--- /dev/null
+++ b/Minecraft.Server.FourKit/Event/Block/BlockFadeEvent.cs
@@ -0,0 +1,41 @@
+namespace Minecraft.Server.FourKit.Event.Block;
+
+using Minecraft.Server.FourKit.Block;
+
+///
+/// Called when a block fades, melts or disappears based on world conditions.
+///
+/// Examples:
+///
+/// - Snow melting due to being near a light source.
+/// - Ice melting due to being near a light source.
+///
+///
+/// If a Block Fade event is cancelled, the block will not fade, melt or disappear.
+///
+public class BlockFadeEvent : BlockEvent, Cancellable
+{
+ private readonly BlockState _newState;
+ private bool _cancel;
+
+ internal BlockFadeEvent(Block block, BlockState newState) : base(block)
+ {
+ _newState = newState;
+ _cancel = false;
+ }
+
+ ///
+ /// Gets the state of the block that will be fading, melting or disappearing.
+ ///
+ /// The block state of the block that will be fading, melting or disappearing.
+ public BlockState getNewState() => _newState;
+
+ ///
+ public bool isCancelled() => _cancel;
+
+ ///
+ public void setCancelled(bool cancel)
+ {
+ _cancel = cancel;
+ }
+}
diff --git a/Minecraft.Server.FourKit/FourKitHost.Events.cs b/Minecraft.Server.FourKit/FourKitHost.Events.cs
index f6c6ef253..baa9543c2 100644
--- a/Minecraft.Server.FourKit/FourKitHost.Events.cs
+++ b/Minecraft.Server.FourKit/FourKitHost.Events.cs
@@ -1187,6 +1187,25 @@ public static partial class FourKitHost
}
}
+ [UnmanagedCallersOnly]
+ public static int FireBlockFade(int dimId, int x, int y, int z, int newTileId, int newTileData)
+ {
+ try
+ {
+ var world = FourKit.getWorld(dimId);
+ var block = new Block.Block(world, x, y, z);
+ var newState = new Block.BlockState(world, x, y, z, newTileId, newTileData);
+ var evt = new Event.Block.BlockFadeEvent(block, newState);
+ FourKit.FireEvent(evt);
+ return evt.isCancelled() ? 1 : 0;
+ }
+ catch (Exception ex)
+ {
+ ServerLog.Error("fourkit", $"FireBlockFade error: {ex}");
+ return 0;
+ }
+ }
+
[UnmanagedCallersOnly]
public static int FirePistonExtend(int dimId, int x, int y, int z, int direction, int length)
{
diff --git a/Minecraft.Server.FourKit/docs/usage-of-all-events.md b/Minecraft.Server.FourKit/docs/usage-of-all-events.md
index 234ede222..6004e850b 100644
--- a/Minecraft.Server.FourKit/docs/usage-of-all-events.md
+++ b/Minecraft.Server.FourKit/docs/usage-of-all-events.md
@@ -896,6 +896,42 @@ public void onSpread(BlockSpreadEvent e)
---
+@subsection blockfadeevent BlockFadeEvent
+
+\ref Minecraft.Server.FourKit.Event.Block.BlockFadeEvent "BlockFadeEvent" is fired when a block fades, melts or disappears based on world conditions. Examples include snow melting near a light source and ice melting near a light source. If cancelled, the block will not fade, melt or disappear.
+
+```csharp
+[EventHandler]
+public void onFade(BlockFadeEvent e)
+{
+ // prevent ice from melting
+ if (e.getBlock().getType() == Material.ICE)
+ {
+ e.setCancelled(true);
+ }
+}
+```
+
+```csharp
+[EventHandler]
+public void onFade(BlockFadeEvent e)
+{
+ // log what a block will become when it fades
+ Console.WriteLine($"{e.getBlock().getType()} at {e.getBlock().getX()},{e.getBlock().getY()},{e.getBlock().getZ()} is fading into {e.getNewState().getType()}");
+}
+```
+
+| Method | Description |
+|--------|-------------|
+| `getBlock()` | The `Block` that is fading, melting or disappearing. |
+| `getNewState()` | The `BlockState` representing what the block will become after fading. |
+| `isCancelled()` | Whether the fade is cancelled. |
+| `setCancelled(bool)` | Cancel or allow the fade. |
+
+> **Cancellable:** Yes
+
+---
+
@subsection blockburnevent BlockBurnEvent
\ref Minecraft.Server.FourKit.Event.Block.BlockBurnEvent "BlockBurnEvent" is fired when a block is destroyed as a result of being burnt by fire.
diff --git a/Minecraft.Server/FourKitBridge.cpp b/Minecraft.Server/FourKitBridge.cpp
index e7f64cd19..f1a166d53 100644
--- a/Minecraft.Server/FourKitBridge.cpp
+++ b/Minecraft.Server/FourKitBridge.cpp
@@ -99,6 +99,7 @@ typedef int(__stdcall *fn_fire_block_grow)(int dimId, int x, int y, int z, int n
typedef int(__stdcall *fn_fire_block_form)(int dimId, int x, int y, int z, int newTileId, int newTileData);
typedef int(__stdcall *fn_fire_block_burn)(int dimId, int x, int y, int z);
typedef int(__stdcall *fn_fire_block_spread)(int dimId, int x, int y, int z, int srcX, int srcY, int srcZ, int newTileId, int newTileData);
+typedef int(__stdcall *fn_fire_block_fade)(int dimId, int x, int y, int z, int newTileId, int newTileData);
typedef int(__stdcall *fn_fire_piston_extend)(int dimId, int x, int y, int z, int direction, int length);
typedef int(__stdcall *fn_fire_piston_retract)(int dimId, int x, int y, int z, int direction);
typedef int(__stdcall *fn_fire_command_preprocess)(int entityId, const char *cmdUtf8, int cmdByteLen, char *outBuf, int outBufSize, int *outLen);
@@ -161,6 +162,7 @@ static fn_fire_block_grow s_managedFireBlockGrow = nullptr;
static fn_fire_block_form s_managedFireBlockForm = nullptr;
static fn_fire_block_burn s_managedFireBlockBurn = nullptr;
static fn_fire_block_spread s_managedFireBlockSpread = nullptr;
+static fn_fire_block_fade s_managedFireBlockFade = nullptr;
static fn_fire_piston_extend s_managedFirePistonExtend = nullptr;
static fn_fire_piston_retract s_managedFirePistonRetract = nullptr;
static fn_fire_command_preprocess s_managedFireCommandPreprocess = nullptr;
@@ -244,6 +246,7 @@ void Initialize()
{L"FireBlockForm", (void **)&s_managedFireBlockForm},
{L"FireBlockBurn", (void **)&s_managedFireBlockBurn},
{L"FireBlockSpread", (void **)&s_managedFireBlockSpread},
+ {L"FireBlockFade", (void **)&s_managedFireBlockFade},
{L"FirePistonExtend", (void **)&s_managedFirePistonExtend},
{L"FirePistonRetract", (void **)&s_managedFirePistonRetract},
{L"FireCommandPreprocess", (void **)&s_managedFireCommandPreprocess},
@@ -1064,4 +1067,11 @@ bool FireChunkUnload(int dimId, int chunkX, int chunkZ)
return false;
return s_managedFireChunkUnload(dimId, chunkX, chunkZ) != 0;
}
+
+bool FireBlockFade(int dimId, int x, int y, int z, int newTileId, int newTileData)
+{
+ if (!s_initialized || !s_managedFireBlockFade)
+ return false;
+ return s_managedFireBlockFade(dimId, x, y, z, newTileId, newTileData) != 0;
+}
} // namespace FourKitBridge
diff --git a/Minecraft.Server/FourKitBridge.h b/Minecraft.Server/FourKitBridge.h
index eafb3c827..754807700 100644
--- a/Minecraft.Server/FourKitBridge.h
+++ b/Minecraft.Server/FourKitBridge.h
@@ -88,6 +88,7 @@ namespace FourKitBridge
bool FireBlockForm(int dimId, int x, int y, int z, int newTileId, int newTileData);
bool FireBlockBurn(int dimId, int x, int y, int z);
bool FireBlockSpread(int dimId, int x, int y, int z, int srcX, int srcY, int srcZ, int newTileId, int newTileData);
+ bool FireBlockFade(int dimId, int x, int y, int z, int newTileId, int newTileData);
bool FirePistonExtend(int dimId, int x, int y, int z, int direction, int length);
bool FirePistonRetract(int dimId, int x, int y, int z, int direction);
bool FireCommandPreprocess(int entityId, const std::wstring &commandLine, std::wstring &outCommand);
diff --git a/Minecraft.Server/cmake/sources/Common.cmake b/Minecraft.Server/cmake/sources/Common.cmake
index 47917e606..6f4be05f9 100644
--- a/Minecraft.Server/cmake/sources/Common.cmake
+++ b/Minecraft.Server/cmake/sources/Common.cmake
@@ -519,8 +519,13 @@ set(_MINECRAFT_SERVER_COMMON_ROOT
"${CMAKE_CURRENT_SOURCE_DIR}/../Minecraft.World/CropTile.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/../Minecraft.World/FireTile.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/../Minecraft.World/GrassTile.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/../Minecraft.World/MycelTile.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/../Minecraft.World/FarmTile.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/../Minecraft.World/RedstoneOreTile.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/../Minecraft.World/PistonBaseTile.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/../Minecraft.World/ReedTile.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/../Minecraft.World/SnowTile.cpp"
+ "${CMAKE_CURRENT_SOURCE_DIR}/../Minecraft.World/IceTile.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/../Minecraft.World/StemTile.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/../Minecraft.World/NetherWartTile.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/../Minecraft.World/LiquidTileDynamic.cpp"
diff --git a/Minecraft.World/FarmTile.cpp b/Minecraft.World/FarmTile.cpp
index 39bf685f5..f15893dab 100644
--- a/Minecraft.World/FarmTile.cpp
+++ b/Minecraft.World/FarmTile.cpp
@@ -4,6 +4,10 @@
#include "net.minecraft.h"
#include "net.minecraft.world.h"
#include "FarmTile.h"
+#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
+#include "../Minecraft.Server/FourKitBridge.h"
+#include "Dimension.h"
+#endif
FarmTile::FarmTile(int id) : Tile(id, Material::dirt,isSolidRender())
@@ -70,6 +74,10 @@ void FarmTile::tick(Level *level, int x, int y, int z, Random *random)
{
if (!isUnderCrops(level, x, y, z))
{
+#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
+ if (FourKitBridge::FireBlockFade(level->dimension->id, x, y, z, Tile::dirt_Id, 0))
+ return;
+#endif
level->setTileAndUpdate(x, y, z, Tile::dirt_Id);
}
}
diff --git a/Minecraft.World/GrassTile.cpp b/Minecraft.World/GrassTile.cpp
index afa5ed26a..08d5ce8e4 100644
--- a/Minecraft.World/GrassTile.cpp
+++ b/Minecraft.World/GrassTile.cpp
@@ -98,6 +98,10 @@ void GrassTile::tick(Level *level, int x, int y, int z, Random *random)
if (level->getRawBrightness(x, y + 1, z) < MIN_BRIGHTNESS && Tile::lightBlock[level->getTile(x, y + 1, z)] > 2)
{
+#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
+ if (FourKitBridge::FireBlockFade(level->dimension->id, x, y, z, Tile::dirt_Id, 0))
+ return;
+#endif
level->setTileAndUpdate(x, y, z, Tile::dirt_Id);
}
else
diff --git a/Minecraft.World/IceTile.cpp b/Minecraft.World/IceTile.cpp
index 60e162916..0cf11f09c 100644
--- a/Minecraft.World/IceTile.cpp
+++ b/Minecraft.World/IceTile.cpp
@@ -5,6 +5,10 @@
#include "net.minecraft.world.food.h"
#include "net.minecraft.stats.h"
#include "IceTile.h"
+#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
+#include "../Minecraft.Server/FourKitBridge.h"
+#include "Dimension.h"
+#endif
IceTile::IceTile(int id) : HalfTransparentTile(id, L"ice", Material::ice, false)
{
@@ -64,9 +68,17 @@ void IceTile::tick(Level *level, int x, int y, int z, Random *random)
{
if (level->dimension->ultraWarm)
{
+#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
+ if (FourKitBridge::FireBlockFade(level->dimension->id, x, y, z, 0, 0))
+ return;
+#endif
level->removeTile(x, y, z);
return;
}
+#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
+ if (FourKitBridge::FireBlockFade(level->dimension->id, x, y, z, Tile::calmWater_Id, 0))
+ return;
+#endif
this->spawnResources(level, x, y, z, level->getData(x, y, z), 0);
level->setTileAndUpdate(x, y, z, Tile::calmWater_Id);
}
diff --git a/Minecraft.World/MycelTile.cpp b/Minecraft.World/MycelTile.cpp
index 76b742e34..fb6d73ae6 100644
--- a/Minecraft.World/MycelTile.cpp
+++ b/Minecraft.World/MycelTile.cpp
@@ -3,6 +3,10 @@
#include "net.minecraft.world.level.h"
#include "net.minecraft.h"
#include "net.minecraft.world.h"
+#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
+#include "../Minecraft.Server/FourKitBridge.h"
+#include "Dimension.h"
+#endif
MycelTile::MycelTile(int id) : Tile(id, Material::grass)
{
@@ -40,6 +44,10 @@ void MycelTile::tick(Level *level, int x, int y, int z, Random *random)
if (level->getRawBrightness(x, y + 1, z) < MIN_BRIGHTNESS && Tile::lightBlock[level->getTile(x, y + 1, z)] > 2)
{
+#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
+ if (FourKitBridge::FireBlockFade(level->dimension->id, x, y, z, Tile::dirt_Id, 0))
+ return;
+#endif
level->setTileAndUpdate(x, y, z, Tile::dirt_Id);
}
else
diff --git a/Minecraft.World/RedStoneOreTile.cpp b/Minecraft.World/RedStoneOreTile.cpp
index 407da2cc3..9711f260c 100644
--- a/Minecraft.World/RedStoneOreTile.cpp
+++ b/Minecraft.World/RedStoneOreTile.cpp
@@ -2,6 +2,10 @@
#include "net.minecraft.world.level.h"
#include "RedStoneOreTile.h"
#include "net.minecraft.world.item.h"
+#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
+#include "../Minecraft.Server/FourKitBridge.h"
+#include "Dimension.h"
+#endif
RedStoneOreTile::RedStoneOreTile(int id, bool lit) : Tile(id, Material::stone)
{
@@ -56,6 +60,10 @@ void RedStoneOreTile::tick(Level *level, int x, int y, int z, Random* random)
{
if (id == Tile::redStoneOre_lit_Id)
{
+#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
+ if (FourKitBridge::FireBlockFade(level->dimension->id, x, y, z, Tile::redStoneOre_Id, 0))
+ return;
+#endif
level->setTileAndUpdate(x, y, z, Tile::redStoneOre_Id);
}
}
diff --git a/Minecraft.World/SnowTile.cpp b/Minecraft.World/SnowTile.cpp
index e73f5ceb3..2a86ea3ab 100644
--- a/Minecraft.World/SnowTile.cpp
+++ b/Minecraft.World/SnowTile.cpp
@@ -4,6 +4,11 @@
#include "net.minecraft.world.item.h"
#include "SnowTile.h"
+#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
+#include "../Minecraft.Server/FourKitBridge.h"
+#include "Dimension.h"
+#endif
+
SnowTile::SnowTile(int id) : Tile(id, Material::snow)
{
setTicking(true);
@@ -23,7 +28,11 @@ int SnowTile::getResourceCount(Random *random)
void SnowTile::tick(Level *level, int x, int y, int z, Random *random)
{
if (level->getBrightness(LightLayer::Block, x, y, z) > 11)
- {
+ {
+#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
+ if (FourKitBridge::FireBlockFade(level->dimension->id, x, y, z, 0, 0))
+ return;
+#endif
this->spawnResources(level, x, y, z, level->getData(x, y, z), 0);
level->removeTile(x, y, z);
}