blockstate, more block events, command preprocess event.

This commit is contained in:
sylvessa 2026-04-06 01:52:58 -05:00
parent 21b5accc69
commit 18a673bd46
34 changed files with 1359 additions and 58 deletions

View file

@ -938,7 +938,10 @@ void PlayerConnection::handleChat(shared_ptr<ChatPacket> packet)
void PlayerConnection::handleCommand(const wstring& message)
{
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
if (FourKitBridge::HandlePlayerCommand(player->entityId, message))
std::wstring commandLine = message;
if (FourKitBridge::FireCommandPreprocess(player->entityId, commandLine, commandLine))
return;
if (FourKitBridge::HandlePlayerCommand(player->entityId, commandLine))
return;
#endif
// 4J - TODO

View file

@ -39,6 +39,9 @@
#include "..\Minecraft.World\ProgressListener.h"
#include "PS3\PS3Extras\ShutdownManager.h"
#include "PlayerChunkMap.h"
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
#include "..\Minecraft.Server\FourKitBridge.h"
#endif
WeighedTreasureArray ServerLevel::RANDOM_BONUS_ITEMS;
@ -511,15 +514,21 @@ void ServerLevel::tickTiles()
int val = (randValue >> 2);
int x = (val & 15);
int z = ((val >> 8) & 15);
int yy = getTopRainBlock(x + xo, z + zo);
if (shouldFreeze(x + xo, yy - 1, z + zo))
{
setTileAndUpdate(x + xo, yy - 1, z + zo, Tile::ice_Id);
}
if (isRaining() && shouldSnow(x + xo, yy, z + zo))
{
setTileAndUpdate(x + xo, yy, z + zo, Tile::topSnow_Id);
}
int yy = getTopRainBlock(x + xo, z + zo);
if (shouldFreeze(x + xo, yy - 1, z + zo))
{
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
if (!FourKitBridge::FireBlockForm(dimension->id, x + xo, yy - 1, z + zo, Tile::ice_Id, 0))
#endif
setTileAndUpdate(x + xo, yy - 1, z + zo, Tile::ice_Id);
}
if (isRaining() && shouldSnow(x + xo, yy, z + zo))
{
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
if (!FourKitBridge::FireBlockForm(dimension->id, x + xo, yy, z + zo, Tile::topSnow_Id, 0))
#endif
setTileAndUpdate(x + xo, yy, z + zo, Tile::topSnow_Id);
}
if (isRaining())
{
Biome *b = getBiome(x + xo, z + zo);

View file

@ -0,0 +1,197 @@
namespace Minecraft.Server.FourKit.Block;
/// <summary>
/// Represents a captured state of a block, which will not change
/// automatically.
///
/// <para>Unlike <see cref="Block"/>, which only one object can exist per
/// coordinate, BlockState can exist multiple times for any given Block.
/// Note that another plugin may change the state of the block and you will
/// not know, or they may change the block to another type entirely, causing
/// your BlockState to become invalid.</para>
/// </summary>
public class BlockState
{
private readonly World _world;
private readonly int _x;
private readonly int _y;
private readonly int _z;
private int _typeId;
private int _data;
internal BlockState(World world, int x, int y, int z, int typeId, int data)
{
_world = world;
_x = x;
_y = y;
_z = z;
_typeId = typeId;
_data = data;
}
/// <summary>
/// Gets the block represented by this BlockState.
/// </summary>
/// <returns>Block that this BlockState represents.</returns>
public Block getBlock()
{
return new Block(_world, _x, _y, _z);
}
/// <summary>
/// Gets the metadata for this block.
/// </summary>
/// <returns>Block specific metadata.</returns>
public int getData() => _data;
/// <summary>
/// Sets the metadata for this block.
/// </summary>
/// <param name="data">New block specific metadata.</param>
public void setData(int data)
{
_data = data;
}
/// <summary>
/// Gets the type of this block.
/// </summary>
/// <returns>Block type.</returns>
public Material getType()
{
return Enum.IsDefined(typeof(Material), _typeId) ? (Material)_typeId : Material.AIR;
}
/// <summary>
/// Gets the type ID of this block.
/// </summary>
/// <returns>Block type ID.</returns>
public int getTypeId() => _typeId;
/// <summary>
/// Gets the world which contains this Block.
/// </summary>
/// <returns>World containing this block.</returns>
public World getWorld() => _world;
/// <summary>
/// Gets the x-coordinate of this block.
/// </summary>
/// <returns>X-coordinate.</returns>
public int getX() => _x;
/// <summary>
/// Gets the y-coordinate of this block.
/// </summary>
/// <returns>Y-coordinate.</returns>
public int getY() => _y;
/// <summary>
/// Gets the z-coordinate of this block.
/// </summary>
/// <returns>Z-coordinate.</returns>
public int getZ() => _z;
/// <summary>
/// Gets the location of this block.
/// </summary>
/// <returns>Location.</returns>
public Location getLocation()
{
return new Location(_world, _x, _y, _z, 0f, 0f);
}
/// <summary>
/// Stores the location of this block in the provided Location object.
/// If the provided Location is null this method does nothing and returns
/// null.
/// </summary>
/// <param name="loc">The location object to store in.</param>
/// <returns>The Location object provided or null.</returns>
public Location? getLocation(Location? loc)
{
if (loc == null) return null;
loc.X = _x;
loc.Y = _y;
loc.Z = _z;
loc.LocationWorld = _world;
return loc;
}
/// <summary>
/// Sets the type of this block.
/// </summary>
/// <param name="type">Material to change this block to.</param>
public void setType(Material type)
{
_typeId = (int)type;
}
/// <summary>
/// Sets the type ID of this block.
/// </summary>
/// <param name="type">Type ID to change this block to.</param>
/// <returns>Whether the change was accepted.</returns>
public bool setTypeId(int type)
{
_typeId = type;
return true;
}
/// <summary>
/// Attempts to update the block represented by this state, setting it to
/// the new values as defined by this state.
/// <para>This has the same effect as calling <c>update(false)</c>.</para>
/// </summary>
/// <returns><c>true</c> if the update was successful, otherwise
/// <c>false</c>.</returns>
public bool update()
{
return update(false);
}
/// <summary>
/// Attempts to update the block represented by this state, setting it to
/// the new values as defined by this state.
/// <para>This has the same effect as calling
/// <c>update(force, true)</c>.</para>
/// </summary>
/// <param name="force"><c>true</c> to forcefully set the state.</param>
/// <returns><c>true</c> if the update was successful, otherwise
/// <c>false</c>.</returns>
public bool update(bool force)
{
return update(force, true);
}
/// <summary>
/// Attempts to update the block represented by this state, setting it to
/// the new values as defined by this state.
/// <para>Unless <paramref name="force"/> is true, this will not modify the
/// state of a block if it is no longer the same type as it was when this
/// state was taken. It will return false in this eventuality.</para>
/// <para>If <paramref name="force"/> is true, it will set the type of the
/// block to match the new state, set the state data and then return
/// true.</para>
/// <para>If <paramref name="applyPhysics"/> is true, it will trigger a
/// physics update on surrounding blocks which could cause them to update
/// or disappear.</para>
/// </summary>
/// <param name="force"><c>true</c> to forcefully set the state.</param>
/// <param name="applyPhysics"><c>false</c> to cancel updating physics on
/// surrounding blocks.</param>
/// <returns><c>true</c> if the update was successful, otherwise
/// <c>false</c>.</returns>
public bool update(bool force, bool applyPhysics)
{
if (NativeBridge.GetTileId == null || NativeBridge.SetTile == null)
return false;
int currentType = NativeBridge.GetTileId(_world.getDimensionId(), _x, _y, _z);
if (!force && currentType != _typeId)
return false;
NativeBridge.SetTile(_world.getDimensionId(), _x, _y, _z, _typeId, _data);
return true;
}
}

View file

@ -39,17 +39,10 @@ public class BlockBreakEvent : BlockExpEvent, Cancellable
/// <returns>The Player that is breaking the block involved in this event.</returns>
public Player getPlayer() => _player;
/// <summary>
/// Gets the cancellation state of this event.
/// </summary>
/// <returns><c>true</c> if this event is cancelled.</returns>
/// <inheritdoc/>
public bool isCancelled() => _cancel;
/// <summary>
/// Sets the cancellation state of this event. A cancelled event will not be
/// executed in the server, but will still pass to other plugins.
/// </summary>
/// <param name="cancel"><c>true</c> if you wish to cancel this event.</param>
/// <inheritdoc/>
public void setCancelled(bool cancel)
{
_cancel = cancel;

View file

@ -0,0 +1,30 @@
namespace Minecraft.Server.FourKit.Event.Block;
using Minecraft.Server.FourKit.Block;
/// <summary>
/// Called when a block is destroyed as a result of being burnt by fire.
///
/// <para>If a Block Burn event is cancelled, the block will not be destroyed
/// as a result of being burnt by fire.</para>
/// </summary>
public class BlockBurnEvent : BlockEvent, Cancellable
{
private bool _cancel;
internal BlockBurnEvent(Block block) : base(block)
{
_cancel = false;
}
/// <inheritdoc />
public bool isCancelled() => _cancel;
/// <inheritdoc />
public void setCancelled(bool cancel)
{
_cancel = cancel;
}
}

View file

@ -0,0 +1,23 @@
namespace Minecraft.Server.FourKit.Event.Block;
using Minecraft.Server.FourKit.Block;
/// <summary>
/// Called when a block is formed or spreads based on world conditions.
/// Use <see cref="BlockSpreadEvent"/> to catch blocks that actually spread
/// and don't just "randomly" form.
///
/// <para>Examples:</para>
/// <list type="bullet">
/// <item><description>Snow forming due to a snow storm.</description></item>
/// <item><description>Ice forming in a snowy Biome like Taiga or Tundra.</description></item>
/// </list>
///
/// <para>If a Block Form event is cancelled, the block will not be formed.</para>
/// </summary>
public class BlockFormEvent : BlockGrowEvent, Cancellable
{
internal BlockFormEvent(Block block, BlockState newState) : base(block, newState)
{
}
}

View file

@ -0,0 +1,59 @@
namespace Minecraft.Server.FourKit.Event.Block;
using Minecraft.Server.FourKit.Block;
/// <summary>
/// Represents events with a source block and a destination block, currently
/// only applies to liquid (lava and water) and teleporting dragon eggs.
///
/// <para>If a Block From To event is cancelled, the block will not move
/// (the liquid will not flow).</para>
/// </summary>
public class BlockFromToEvent : BlockEvent, Cancellable
{
private readonly Block _to;
private readonly BlockFace _face;
private bool _cancel;
internal BlockFromToEvent(Block block, BlockFace face) : base(block)
{
_face = face;
_to = block.getRelative(face);
_cancel = false;
}
internal BlockFromToEvent(Block block, Block toBlock) : base(block)
{
_to = toBlock;
_face = BlockFace.SELF;
_cancel = false;
}
internal BlockFromToEvent(Block block, Block toBlock, BlockFace face) : base(block)
{
_to = toBlock;
_face = face;
_cancel = false;
}
/// <summary>
/// Gets the BlockFace that the block is moving to.
/// </summary>
/// <returns>The BlockFace that the block is moving to.</returns>
public BlockFace getFace() => _face;
/// <summary>
/// Convenience method for getting the faced Block.
/// </summary>
/// <returns>The faced Block.</returns>
public Block getToBlock() => _to;
/// <inheritdoc/>
public bool isCancelled() => _cancel;
/// <inheritdoc/>
public void setCancelled(bool cancel)
{
_cancel = cancel;
}
}

View file

@ -0,0 +1,44 @@
namespace Minecraft.Server.FourKit.Event.Block;
using Minecraft.Server.FourKit.Block;
/// <summary>
/// Called when a block grows naturally in the world.
///
/// <para>Examples:</para>
/// <list type="bullet">
/// <item><description>Wheat</description></item>
/// <item><description>Sugar Cane</description></item>
/// <item><description>Cactus</description></item>
/// <item><description>Watermelon</description></item>
/// <item><description>Pumpkin</description></item>
/// </list>
///
/// <para>If a Block Grow event is cancelled, the block will not grow.</para>
/// </summary>
public class BlockGrowEvent : BlockEvent, Cancellable
{
private bool _cancel;
private readonly BlockState _newState;
internal BlockGrowEvent(Block block, BlockState newState) : base(block)
{
_cancel = false;
_newState = newState;
}
/// <summary>
/// Gets the state of the block where it will form or spread to.
/// </summary>
/// <returns>The block state for this events block.</returns>
public BlockState getNewState() => _newState;
/// <inheritdoc/>
public bool isCancelled() => _cancel;
/// <inheritdoc/>
public void setCancelled(bool cancel)
{
_cancel = cancel;
}
}

View file

@ -0,0 +1,45 @@
namespace Minecraft.Server.FourKit.Event.Block;
using Minecraft.Server.FourKit.Block;
/// <summary>
/// Called when a piston block is triggered.
/// </summary>
public abstract class BlockPistonEvent : BlockEvent, Cancellable
{
private bool _cancel;
private readonly BlockFace _direction;
internal protected BlockPistonEvent(Block block, BlockFace direction) : base(block)
{
_direction = direction;
_cancel = false;
}
/// <inheritdoc />
public bool isCancelled() => _cancel;
/// <inheritdoc />
public void setCancelled(bool cancelled)
{
_cancel = cancelled;
}
/// <summary>
/// Returns true if the Piston in the event is sticky.
/// </summary>
/// <returns>Stickiness of the piston.</returns>
public bool isSticky()
{
var type = getBlock().getType();
return type == Material.PISTON_STICKY_BASE;
}
/// <summary>
/// Return the direction in which the piston will operate.
/// </summary>
/// <returns>Direction of the piston.</returns>
public BlockFace getDirection() => _direction;
}

View file

@ -0,0 +1,46 @@
namespace Minecraft.Server.FourKit.Event.Block;
using Minecraft.Server.FourKit.Block;
/// <summary>
/// Called when a piston extends.
/// </summary>
public class BlockPistonExtendEvent : BlockPistonEvent
{
private readonly int _length;
internal BlockPistonExtendEvent(Block block, int length, BlockFace direction)
: base(block, direction)
{
_length = length;
}
/// <summary>
/// Get the amount of blocks which will be moved while extending.
/// </summary>
/// <returns>The amount of moving blocks.</returns>
public int getLength() => _length;
/// <summary>
/// Get an immutable list of the blocks which will be moved by the extending.
/// </summary>
/// <returns>Immutable list of the moved blocks.</returns>
public List<Block> getBlocks()
{
var blocks = new List<Block>();
var world = getBlock().getWorld();
int x = getBlock().getX();
int y = getBlock().getY();
int z = getBlock().getZ();
var dir = getDirection();
for (int i = 0; i < _length; i++)
{
x += dir.getModX();
y += dir.getModY();
z += dir.getModZ();
blocks.Add(new Block(world, x, y, z));
}
return blocks.AsReadOnly().ToList();
}
}

View file

@ -0,0 +1,30 @@
namespace Minecraft.Server.FourKit.Event.Block;
using Minecraft.Server.FourKit.Block;
/// <summary>
/// Called when a piston retracts.
/// </summary>
public class BlockPistonRetractEvent : BlockPistonEvent
{
internal BlockPistonRetractEvent(Block block, BlockFace direction)
: base(block, direction)
{
}
/// <summary>
/// Gets the location where the possible moving block might be if the
/// retracting piston is sticky.
/// </summary>
/// <returns>The possible location of the possibly moving block.</returns>
public Location getRetractLocation()
{
var block = getBlock();
var dir = getDirection();
return new Location(
block.getWorld(),
block.getX() + dir.getModX() * 2,
block.getY() + dir.getModY() * 2,
block.getZ() + dir.getModZ() * 2);
}
}

View file

@ -0,0 +1,32 @@
namespace Minecraft.Server.FourKit.Event.Block;
using Minecraft.Server.FourKit.Block;
/// <summary>
/// Called when a block spreads based on world conditions.
/// Use <see cref="BlockFormEvent"/> to catch blocks that "randomly" form
/// instead of actually spread.
///
/// <para>Examples:</para>
/// <list type="bullet">
/// <item><description>Mushrooms spreading.</description></item>
/// <item><description>Fire spreading.</description></item>
/// </list>
///
/// <para>If a Block Spread event is cancelled, the block will not spread.</para>
/// </summary>
public class BlockSpreadEvent : BlockFormEvent, Cancellable
{
private readonly Block _source;
internal BlockSpreadEvent(Block block, Block source, BlockState newState) : base(block, newState)
{
_source = source;
}
/// <summary>
/// Gets the source block involved in this event.
/// </summary>
/// <returns>The Block for the source block involved in this event.</returns>
public Block getSource() => _source;
}

View file

@ -90,17 +90,12 @@ public class EntityDamageEvent : EntityEvent, Cancellable
/// <returns>The amount of damage after reduction.</returns>
public double getFinalDamage() => _finalDamage;
/// <summary>
/// Gets the cancellation state of this event.
/// </summary>
/// <returns><c>true</c> if this event is cancelled.</returns>
/// <inheritdoc />
public bool isCancelled() => _cancel;
/// <summary>
/// Sets the cancellation state of this event. A cancelled event will not
/// be executed in the server, but will still pass to other plugins.
/// </summary>
/// <param name="cancel"><c>true</c> if you wish to cancel this event.</param>
/// <inheritdoc />
public void setCancelled(bool cancel)
{
_cancel = cancel;

View file

@ -24,17 +24,11 @@ public abstract class InventoryInteractEvent : InventoryEvent, Cancellable
return transaction.getPlayer();
}
/// <summary>
/// Gets the cancellation state of this event. A cancelled event will not
/// be executed in the server, but will still pass to other plugins.
/// </summary>
/// <returns>true if this event is cancelled.</returns>
/// <inheritdoc />
public bool isCancelled() => _cancelled;
/// <summary>
/// Sets the cancellation state of this event. A cancelled event will not
/// be executed in the server, but will still pass to other plugins.
/// </summary>
/// <param name="cancel">true if you wish to cancel this event.</param>
/// <inheritdoc />
public void setCancelled(bool cancel) => _cancelled = cancel;
}

View file

@ -0,0 +1,53 @@
namespace Minecraft.Server.FourKit.Event.Player;
using Minecraft.Server.FourKit.Entity;
/// <summary>
/// Called early in the command handling process. This event is only for very
/// exceptional cases and you should not normally use it.
///
/// <para>If a PlayerCommandPreprocessEvent is cancelled, the command will not
/// be executed in the server, but will still pass to other plugins.</para>
/// </summary>
public class PlayerCommandPreprocessEvent : PlayerEvent, Cancellable
{
private string _message;
private bool _cancel;
internal PlayerCommandPreprocessEvent(Player player, string message) : base(player)
{
_message = message;
_cancel = false;
}
/// <inheritdoc/>
public bool isCancelled() => _cancel;
/// <inheritdoc/>
public void setCancelled(bool cancel)
{
_cancel = cancel;
}
/// <summary>
/// Gets the command that the player is attempting to send. All commands
/// begin with a special character; implementations do not consider the
/// first character when executing the content.
/// </summary>
/// <returns>Message the player is attempting to send.</returns>
public string getMessage() => _message;
/// <summary>
/// Sets the command that the player will send. All commands begin with a
/// special character; implementations do not consider the first character
/// when executing the content.
/// </summary>
/// <param name="command">New message that the player will send.</param>
/// <exception cref="ArgumentException">If command is null or empty.</exception>
public void setMessage(string command)
{
if (string.IsNullOrEmpty(command))
throw new ArgumentException("Command may not be null or empty", nameof(command));
_message = command;
}
}

View file

@ -871,6 +871,51 @@ public static partial class FourKitHost
}
}
[UnmanagedCallersOnly]
public static int FireCommandPreprocess(int entityId, IntPtr cmdUtf8, int cmdByteLen, IntPtr outBuf, int outBufSize, IntPtr outLenPtr)
{
try
{
var player = FourKit.GetPlayerByEntityId(entityId);
if (player == null)
return 0;
SyncPlayerFromNative(player);
string commandLine = cmdByteLen > 0
? Marshal.PtrToStringUTF8(cmdUtf8, cmdByteLen) ?? string.Empty
: string.Empty;
if (string.IsNullOrEmpty(commandLine))
return 0;
var preEvt = new Event.Player.PlayerCommandPreprocessEvent(player, commandLine);
FourKit.FireEvent(preEvt);
if (preEvt.isCancelled())
return 1;
string modified = preEvt.getMessage();
if (modified != commandLine && outBuf != IntPtr.Zero && outBufSize > 0)
{
byte[] utf8Bytes = System.Text.Encoding.UTF8.GetBytes(modified);
if (utf8Bytes.Length < outBufSize)
{
Marshal.Copy(utf8Bytes, 0, outBuf, utf8Bytes.Length);
if (outLenPtr != IntPtr.Zero)
Marshal.WriteInt32(outLenPtr, utf8Bytes.Length);
}
}
return 0;
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"FireCommandPreprocess error: {ex}");
return 0;
}
}
[UnmanagedCallersOnly]
public static int HandleConsoleCommand(IntPtr cmdUtf8, int cmdByteLen)
{
@ -1065,4 +1110,144 @@ public static partial class FourKitHost
ServerLog.Error("fourkit", $"FireBedLeave error: {ex}");
}
}
[UnmanagedCallersOnly]
public static int FireBlockGrow(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.BlockGrowEvent(block, newState);
FourKit.FireEvent(evt);
return evt.isCancelled() ? 1 : 0;
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"FireBlockGrow error: {ex}");
return 0;
}
}
[UnmanagedCallersOnly]
public static int FireBlockForm(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.BlockFormEvent(block, newState);
FourKit.FireEvent(evt);
return evt.isCancelled() ? 1 : 0;
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"FireBlockForm error: {ex}");
return 0;
}
}
[UnmanagedCallersOnly]
public static int FireBlockBurn(int dimId, int x, int y, int z)
{
try
{
var world = FourKit.getWorld(dimId);
var block = new Block.Block(world, x, y, z);
var evt = new Event.Block.BlockBurnEvent(block);
FourKit.FireEvent(evt);
return evt.isCancelled() ? 1 : 0;
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"FireBlockBurn error: {ex}");
return 0;
}
}
[UnmanagedCallersOnly]
public static int FireBlockSpread(int dimId, int x, int y, int z, int srcX, int srcY, int srcZ, int newTileId, int newTileData)
{
try
{
var world = FourKit.getWorld(dimId);
var block = new Block.Block(world, x, y, z);
var source = new Block.Block(world, srcX, srcY, srcZ);
var newState = new Block.BlockState(world, x, y, z, newTileId, newTileData);
var evt = new Event.Block.BlockSpreadEvent(block, source, newState);
FourKit.FireEvent(evt);
return evt.isCancelled() ? 1 : 0;
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"FireBlockSpread error: {ex}");
return 0;
}
}
[UnmanagedCallersOnly]
public static int FirePistonExtend(int dimId, int x, int y, int z, int direction, int length)
{
try
{
var world = FourKit.getWorld(dimId);
var block = new Block.Block(world, x, y, z);
var face = Enum.IsDefined(typeof(Block.BlockFace), direction)
? (Block.BlockFace)direction
: Block.BlockFace.SELF;
var evt = new Event.Block.BlockPistonExtendEvent(block, length, face);
FourKit.FireEvent(evt);
return evt.isCancelled() ? 1 : 0;
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"FirePistonExtend error: {ex}");
return 0;
}
}
[UnmanagedCallersOnly]
public static int FirePistonRetract(int dimId, int x, int y, int z, int direction)
{
try
{
var world = FourKit.getWorld(dimId);
var block = new Block.Block(world, x, y, z);
var face = Enum.IsDefined(typeof(Block.BlockFace), direction)
? (Block.BlockFace)direction
: Block.BlockFace.SELF;
var evt = new Event.Block.BlockPistonRetractEvent(block, face);
FourKit.FireEvent(evt);
return evt.isCancelled() ? 1 : 0;
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"FirePistonRetract error: {ex}");
return 0;
}
}
[UnmanagedCallersOnly]
public static int FireBlockFromTo(int dimId, int fromX, int fromY, int fromZ, int toX, int toY, int toZ, int face)
{
try
{
var world = FourKit.getWorld(dimId);
var from = new Block.Block(world, fromX, fromY, fromZ);
var to = new Block.Block(world, toX, toY, toZ);
var blockFace = Enum.IsDefined(typeof(Block.BlockFace), face)
? (Block.BlockFace)face
: Block.BlockFace.SELF;
var evt = new Event.Block.BlockFromToEvent(from, to, blockFace);
FourKit.FireEvent(evt);
return evt.isCancelled() ? 1 : 0;
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"FireBlockFromTo error: {ex}");
return 0;
}
}
}

View file

@ -187,5 +187,5 @@ public class Location
public Location clone() => new Location(LocationWorld, X, Y, Z, Yaw, Pitch);
/// <inheritdoc/>
public override string ToString() => $"Location(world={LocationWorld}, x={X}, y={Y}, z={Z}, yaw={Yaw}, pitch={Pitch})";
public override string ToString() => $"Location(world={LocationWorld.getName()}, x={X}, y={Y}, z={Z}, yaw={Yaw}, pitch={Pitch})";
}

View file

@ -462,6 +462,56 @@ public void onBedLeave(PlayerBedLeaveEvent e)
---
@subsection playercommandpreprocessevent PlayerCommandPreprocessEvent
\ref Minecraft.Server.FourKit.Event.Player.PlayerCommandPreprocessEvent "PlayerCommandPreprocessEvent" is fired early in the command handling process when a player sends a command. You can modify the command, cancel it, or use it for logging. This fires before the command is dispatched to any command handler.
```csharp
[EventHandler]
public void onCommand(PlayerCommandPreprocessEvent e)
{
// log all commands
Console.WriteLine(e.getPlayer().getName() + " issued command: " + e.getMessage());
}
```
```csharp
[EventHandler]
public void onCommand(PlayerCommandPreprocessEvent e)
{
// block a specific command
if (e.getMessage().StartsWith("/secret"))
{
e.setCancelled(true);
e.getPlayer().sendMessage("That command is disabled!");
}
}
```
```csharp
[EventHandler]
public void onCommand(PlayerCommandPreprocessEvent e)
{
// redirect a command alias
if (e.getMessage().StartsWith("/gm "))
{
e.setMessage("/gamemode " + e.getMessage().Substring(4));
}
}
```
| Method | Description |
|--------|-------------|
| `getPlayer()` | The player who issued the command. |
| `getMessage()` | The full command string the player is sending. |
| `setMessage(string)` | Change the command that will be processed. |
| `isCancelled()` | Whether the command is cancelled. |
| `setCancelled(bool)` | Cancel or allow the command. |
> **Cancellable:** Yes
---
@section entity_events Entity Events
@subsection entitydamageevent EntityDamageEvent
@ -745,6 +795,259 @@ public void onSign(SignChangeEvent e)
---
---
@subsection blockgrowevent BlockGrowEvent
\ref Minecraft.Server.FourKit.Event.Block.BlockGrowEvent "BlockGrowEvent" is fired when a block grows naturally in the world. This includes crops (wheat, nether wart, cocoa beans), sugar cane, cactus, and melon/pumpkin fruit placement.
```csharp
[EventHandler]
public void onGrow(BlockGrowEvent e)
{
// prevent all crop growth
e.setCancelled(true);
}
```
```csharp
[EventHandler]
public void onGrow(BlockGrowEvent e)
{
// log growth events
var b = e.getBlock();
Console.WriteLine($"Block grew at {b.getX()}, {b.getY()}, {b.getZ()} type={b.getType()}");
}
```
| Method | Description |
|--------|-------------|
| `getBlock()` | The `Block` that is growing. |
| `getNewState()` | The `BlockState` representing what the block will become. |
| `isCancelled()` | Whether the growth is cancelled. |
| `setCancelled(bool)` | Cancel or allow the growth. |
> **Cancellable:** Yes
---
@subsection blockformevent BlockFormEvent
\ref Minecraft.Server.FourKit.Event.Block.BlockFormEvent "BlockFormEvent" extends `BlockGrowEvent` and is fired when a block forms due to world conditions. Examples include snow forming during a storm and ice forming in cold biomes. Use \ref Minecraft.Server.FourKit.Event.Block.BlockSpreadEvent "BlockSpreadEvent" to catch blocks that actually spread instead of randomly forming.
```csharp
[EventHandler]
public void onForm(BlockFormEvent e)
{
// prevent snow and ice from forming
e.setCancelled(true);
}
```
| Method | Description |
|--------|-------------|
| `getBlock()` | The `Block` that is forming. |
| `getNewState()` | The `BlockState` representing what the block will become. |
| `isCancelled()` | Whether the formation is cancelled. |
| `setCancelled(bool)` | Cancel or allow the formation. |
> **Cancellable:** Yes (inherited from `BlockGrowEvent`)
---
@subsection blockspreadevent BlockSpreadEvent
\ref Minecraft.Server.FourKit.Event.Block.BlockSpreadEvent "BlockSpreadEvent" extends `BlockFormEvent` and is fired when a block spreads from one location to another. Examples include fire spreading, mushrooms spreading, and grass spreading to dirt.
```csharp
[EventHandler]
public void onSpread(BlockSpreadEvent e)
{
// prevent fire from spreading
if (e.getSource().getType() == Material.FIRE)
{
e.setCancelled(true);
}
}
```
```csharp
[EventHandler]
public void onSpread(BlockSpreadEvent e)
{
// prevent grass from spreading
if (e.getBlock().getType() == Material.GRASS)
{
e.setCancelled(true);
}
}
```
| Method | Description |
|--------|-------------|
| `getBlock()` | The `Block` where the spread is occurring (destination). |
| `getSource()` | The source `Block` that is spreading. |
| `getNewState()` | The `BlockState` representing what the block will become. |
| `isCancelled()` | Whether the spread is cancelled. |
| `setCancelled(bool)` | Cancel or allow the spread. |
> **Cancellable:** Yes (inherited)
---
@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.
```csharp
[EventHandler]
public void onBurn(BlockBurnEvent e)
{
// prevent wooden planks from burning
if (e.getBlock().getType() == Material.WOOD)
{
e.setCancelled(true);
}
}
```
```csharp
[EventHandler]
public void onBurn(BlockBurnEvent e)
{
// prevent all fire destruction
e.setCancelled(true);
}
```
| Method | Description |
|--------|-------------|
| `getBlock()` | The `Block` that is being burnt. |
| `isCancelled()` | Whether the burn is cancelled. |
| `setCancelled(bool)` | Cancel or allow the burn. |
> **Cancellable:** Yes
---
@subsection blockfromtoevent BlockFromToEvent
\ref Minecraft.Server.FourKit.Event.Block.BlockFromToEvent "BlockFromToEvent" is fired when a block moves from one location to another. This currently only applies to liquid flow (lava and water) and teleporting dragon eggs.
```csharp
[EventHandler]
public void onFromTo(BlockFromToEvent e)
{
// prevent water from flowing
if (e.getBlock().getType() == Material.WATER || e.getBlock().getType() == Material.STATIONARY_WATER)
{
e.setCancelled(true);
}
}
```
```csharp
[EventHandler]
public void onFromTo(BlockFromToEvent e)
{
// log liquid flow
var from = e.getBlock();
var to = e.getToBlock();
Console.WriteLine($"Block moving from {from.getX()},{from.getY()},{from.getZ()} to {to.getX()},{to.getY()},{to.getZ()}");
}
```
| Method | Description |
|--------|-------------|
| `getBlock()` | The source `Block` that is moving. |
| `getToBlock()` | The destination `Block`. |
| `getFace()` | The `BlockFace` direction the block is moving to. |
| `isCancelled()` | Whether the move is cancelled. |
| `setCancelled(bool)` | Cancel or allow the move. |
> **Cancellable:** Yes
---
@subsection blockpistonextendevent BlockPistonExtendEvent
\ref Minecraft.Server.FourKit.Event.Block.BlockPistonExtendEvent "BlockPistonExtendEvent" extends `BlockPistonEvent` and is fired when a piston extends. You can inspect the piston direction, stickiness, number of blocks being pushed, and cancel the extension.
```csharp
[EventHandler]
public void onPiston(BlockPistonExtendEvent e)
{
// prevent sticky pistons from extending
if (e.isSticky())
{
e.setCancelled(true);
}
}
```
```csharp
[EventHandler]
public void onPiston(BlockPistonExtendEvent e)
{
// log piston activity
Console.WriteLine($"Piston extending {e.getDirection()} pushing {e.getLength()} blocks");
}
```
| Method | Description |
|--------|-------------|
| `getBlock()` | The piston `Block`. |
| `getDirection()` | The `BlockFace` direction the piston is extending. |
| `isSticky()` | Whether the piston is a sticky piston. |
| `getLength()` | The number of blocks being pushed. |
| `getBlocks()` | List of `Block` objects that will be moved. |
| `isCancelled()` | Whether the extension is cancelled. |
| `setCancelled(bool)` | Cancel or allow the extension. |
> **Cancellable:** Yes
---
@subsection blockpistonretractevent BlockPistonRetractEvent
\ref Minecraft.Server.FourKit.Event.Block.BlockPistonRetractEvent "BlockPistonRetractEvent" extends `BlockPistonEvent` and is fired when a piston retracts. For sticky pistons, the retract location indicates where the block being pulled is located.
```csharp
[EventHandler]
public void onPiston(BlockPistonRetractEvent e)
{
// prevent all sticky piston retractions
if (e.isSticky())
{
e.setCancelled(true);
}
}
```
```csharp
[EventHandler]
public void onPiston(BlockPistonRetractEvent e)
{
// log where the retract is pulling from
var loc = e.getRetractLocation();
Console.WriteLine($"Piston retracting, pull location: {loc.getBlockX()}, {loc.getBlockY()}, {loc.getBlockZ()}");
}
```
| Method | Description |
|--------|-------------|
| `getBlock()` | The piston `Block`. |
| `getDirection()` | The `BlockFace` direction the piston is retracting. |
| `isSticky()` | Whether the piston is a sticky piston. |
| `getRetractLocation()` | The `Location` of the block that may be pulled (for sticky pistons). |
| `isCancelled()` | Whether the retraction is cancelled. |
| `setCancelled(bool)` | Cancel or allow the retraction. |
> **Cancellable:** Yes
---
@section inventory_events Inventory Events
@subsection inventoryopenevent InventoryOpenEvent

View file

@ -95,6 +95,14 @@ typedef void(__stdcall *fn_set_entity_callbacks)(void *setSneaking, void *setVel
typedef void(__stdcall *fn_set_experience_callbacks)(void *setLevel, void *setExp, void *giveExp, void *giveExpLevels, void *setFoodLevel, void *setSaturation, void *setExhaustion);
typedef void(__stdcall *fn_set_particle_callbacks)(void *spawnParticle);
typedef void(__stdcall *fn_set_vehicle_callbacks)(void *setPassenger, void *leaveVehicle, void *eject, void *getVehicleId, void *getPassengerId, void *getEntityInfo);
typedef int(__stdcall *fn_fire_block_grow)(int dimId, int x, int y, int z, int newTileId, int newTileData);
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_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);
typedef int(__stdcall *fn_fire_block_from_to)(int dimId, int fromX, int fromY, int fromZ, int toX, int toY, int toZ, int face);
struct OpenContainerInfo
{
@ -144,6 +152,14 @@ static fn_set_entity_callbacks s_managedSetEntityCallbacks = nullptr;
static fn_set_experience_callbacks s_managedSetExperienceCallbacks = nullptr;
static fn_set_particle_callbacks s_managedSetParticleCallbacks = nullptr;
static fn_set_vehicle_callbacks s_managedSetVehicleCallbacks = nullptr;
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_piston_extend s_managedFirePistonExtend = nullptr;
static fn_fire_piston_retract s_managedFirePistonRetract = nullptr;
static fn_fire_command_preprocess s_managedFireCommandPreprocess = nullptr;
static fn_fire_block_from_to s_managedFireBlockFromTo = nullptr;
static bool s_initialized = false;
@ -214,6 +230,14 @@ void Initialize()
{L"SetExperienceCallbacks", (void **)&s_managedSetExperienceCallbacks},
{L"SetParticleCallbacks", (void **)&s_managedSetParticleCallbacks},
{L"SetVehicleCallbacks", (void **)&s_managedSetVehicleCallbacks},
{L"FireBlockGrow", (void **)&s_managedFireBlockGrow},
{L"FireBlockForm", (void **)&s_managedFireBlockForm},
{L"FireBlockBurn", (void **)&s_managedFireBlockBurn},
{L"FireBlockSpread", (void **)&s_managedFireBlockSpread},
{L"FirePistonExtend", (void **)&s_managedFirePistonExtend},
{L"FirePistonRetract", (void **)&s_managedFirePistonRetract},
{L"FireCommandPreprocess", (void **)&s_managedFireCommandPreprocess},
{L"FireBlockFromTo", (void **)&s_managedFireBlockFromTo},
};
bool ok = true;
@ -902,4 +926,88 @@ void FireBedLeave(int entityId, int dimId, int bedX, int bedY, int bedZ)
return;
s_managedFireBedLeave(entityId, dimId, bedX, bedY, bedZ);
}
bool FireBlockGrow(int dimId, int x, int y, int z, int newTileId, int newTileData)
{
if (!s_initialized || !s_managedFireBlockGrow)
return false;
return s_managedFireBlockGrow(dimId, x, y, z, newTileId, newTileData) != 0;
}
bool FireBlockForm(int dimId, int x, int y, int z, int newTileId, int newTileData)
{
if (!s_initialized || !s_managedFireBlockForm)
return false;
return s_managedFireBlockForm(dimId, x, y, z, newTileId, newTileData) != 0;
}
bool FireBlockBurn(int dimId, int x, int y, int z)
{
if (!s_initialized || !s_managedFireBlockBurn)
return false;
return s_managedFireBlockBurn(dimId, x, y, z) != 0;
}
bool FireBlockSpread(int dimId, int x, int y, int z, int srcX, int srcY, int srcZ, int newTileId, int newTileData)
{
if (!s_initialized || !s_managedFireBlockSpread)
return false;
return s_managedFireBlockSpread(dimId, x, y, z, srcX, srcY, srcZ, newTileId, newTileData) != 0;
}
bool FirePistonExtend(int dimId, int x, int y, int z, int direction, int length)
{
if (!s_initialized || !s_managedFirePistonExtend)
return false;
return s_managedFirePistonExtend(dimId, x, y, z, direction, length) != 0;
}
bool FirePistonRetract(int dimId, int x, int y, int z, int direction)
{
if (!s_initialized || !s_managedFirePistonRetract)
return false;
return s_managedFirePistonRetract(dimId, x, y, z, direction) != 0;
}
bool FireCommandPreprocess(int entityId, const std::wstring &commandLine, std::wstring &outCommand)
{
if (!s_initialized || !s_managedFireCommandPreprocess)
{
return false;
}
std::string cmdUtf8 = ServerRuntime::StringUtils::WideToUtf8(commandLine);
const int kBufSize = 2048;
char outBuf[kBufSize] = {};
int outLen = 0;
int cancelled = s_managedFireCommandPreprocess(entityId,
cmdUtf8.empty() ? "" : cmdUtf8.data(), (int)cmdUtf8.size(),
outBuf, kBufSize, &outLen);
if (cancelled != 0)
{
return true;
}
if (outLen > 0 && outLen < kBufSize)
{
std::string resultUtf8(outBuf, outLen);
outCommand = ServerRuntime::StringUtils::Utf8ToWide(resultUtf8);
}
else
{
outCommand = commandLine;
}
return false;
}
bool FireBlockFromTo(int dimId, int fromX, int fromY, int fromZ, int toX, int toY, int toZ, int face)
{
if (!s_initialized || !s_managedFireBlockFromTo)
return false;
return s_managedFireBlockFromTo(dimId, fromX, fromY, fromZ, toX, toY, toZ, face) != 0;
}
} // namespace FourKitBridge

View file

@ -84,4 +84,12 @@ namespace FourKitBridge
int FireInventoryClick(int entityId, int slot, int button, int clickType);
bool FireBedEnter(int entityId, int dimId, int bedX, int bedY, int bedZ);
void FireBedLeave(int entityId, int dimId, int bedX, int bedY, int bedZ);
bool FireBlockGrow(int dimId, int x, int y, int z, int newTileId, int newTileData);
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 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);
bool FireBlockFromTo(int dimId, int fromX, int fromY, int fromZ, int toX, int toY, int toZ, int face);
}

View file

@ -512,6 +512,17 @@ set(_MINECRAFT_SERVER_COMMON_ROOT
"${CMAKE_CURRENT_SOURCE_DIR}/../Minecraft.World/Sapling.h"
"${CMAKE_CURRENT_SOURCE_DIR}/../Minecraft.World/ConsoleSaveFileOriginal.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/../Minecraft.World/ConsoleSaveFileOriginal.h"
"${CMAKE_CURRENT_SOURCE_DIR}/../Minecraft.World/CactusTile.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/../Minecraft.World/CocoaTile.cpp"
"${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/PistonBaseTile.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/../Minecraft.World/ReedTile.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"
"${CMAKE_CURRENT_SOURCE_DIR}/../Minecraft.World/EggTile.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/../include/lce_filesystem/lce_filesystem.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/Console/ServerCliInput.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/Console/ServerCliInput.h"

View file

@ -7,6 +7,10 @@
#include "net.minecraft.h"
#include "net.minecraft.world.h"
#include "CactusTile.h"
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
#include "..\Minecraft.Server\FourKitBridge.h"
#include "Dimension.h"
#endif
CactusTile::CactusTile(int id) : Tile(id, Material::cactus,isSolidRender())
{
@ -29,6 +33,10 @@ void CactusTile::tick(Level *level, int x, int y, int z, Random *random)
int age = level->getData(x, y, z);
if (age == 15)
{
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
if (FourKitBridge::FireBlockGrow(level->dimension->id, x, y + 1, z, id, 0))
return;
#endif
level->setTileAndUpdate(x, y + 1, z, id);
level->setData(x, y, z, 0, Tile::UPDATE_NONE);
neighborChanged(level, x, y + 1, z, id);

View file

@ -5,6 +5,10 @@
#include "net.minecraft.world.h"
#include "net.minecraft.h"
#include "CocoaTile.h"
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
#include "..\Minecraft.Server\FourKitBridge.h"
#include "Dimension.h"
#endif
const wstring CocoaTile::TEXTURE_AGES[] = { L"cocoa_0", L"cocoa_1", L"cocoa_2"};
@ -40,6 +44,10 @@ void CocoaTile::tick(Level *level, int x, int y, int z, Random *random)
int age = getAge(data);
if (age < 2)
{
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
if (FourKitBridge::FireBlockGrow(level->dimension->id, x, y, z, level->getTile(x, y, z), ((age + 1) << 2) | getDirection(data)))
return;
#endif
age++;
level->setData(x, y, z, (age << 2) | (getDirection(data)), Tile::UPDATE_CLIENTS);
}

View file

@ -4,6 +4,10 @@
#include "net.minecraft.world.item.h"
#include "net.minecraft.world.h"
#include "CropTile.h"
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
#include "..\Minecraft.Server\FourKitBridge.h"
#include "Dimension.h"
#endif
CropTile::CropTile(int id) : Bush(id)
{
@ -42,6 +46,10 @@ void CropTile::tick(Level *level, int x, int y, int z, Random *random)
if (random->nextInt(static_cast<int>(25 / growthSpeed) + 1) == 0)
{
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
if (FourKitBridge::FireBlockGrow(level->dimension->id, x, y, z, level->getTile(x, y, z), age + 1))
return;
#endif
age++;
level->setData(x, y, z, age, Tile::UPDATE_CLIENTS);
}

View file

@ -1,8 +1,12 @@
#include "stdafx.h"
#include "stdafx.h"
#include "EggTile.h"
#include "net.minecraft.world.level.h"
#include "net.minecraft.world.level.tile.h"
#include "net.minecraft.world.entity.item.h"
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
#include "..\Minecraft.Server\FourKitBridge.h"
#include "Dimension.h"
#endif
EggTile::EggTile(int id) : Tile(id, Material::egg, isSolidRender())
{
@ -70,11 +74,15 @@ void EggTile::teleport(Level *level, int x, int y, int z)
int zt = z + level->random->nextInt(16) - level->random->nextInt(16);
if (level->getTile(xt, yt, zt) == 0)
{
// Fix for TU9: Content: Art: Dragon egg teleport particle effect isn't present.
// Don't set tiles on client, and don't create particles on the server (matches later change in Java)
if(!level->isClientSide)
{
level->setTileAndData(xt, yt, zt, id, level->getData(x, y, z), Tile::UPDATE_CLIENTS);
// Fix for TU9: Content: Art: Dragon egg teleport particle effect isn't present.
// Don't set tiles on client, and don't create particles on the server (matches later change in Java)
if(!level->isClientSide)
{
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
if (FourKitBridge::FireBlockFromTo(level->dimension->id, x, y, z, xt, yt, zt, 6 /*SELF*/))
continue;
#endif
level->setTileAndData(xt, yt, zt, id, level->getData(x, y, z), Tile::UPDATE_CLIENTS);
level->removeTile(x, y, z);
// 4J Stu - The PC version is wrong as the particles calculated on the client side will point towards a different

View file

@ -7,6 +7,9 @@
#include "SoundTypes.h"
#include "..\Minecraft.Client\MinecraftServer.h"
#include "..\Minecraft.Client\PlayerList.h"
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
#include "..\Minecraft.Server\FourKitBridge.h"
#endif
// AP - added for Vita to set Alpha Cut out
#include "IntBuffer.h"
@ -197,11 +200,16 @@ void FireTile::tick(Level *level, int x, int y, int z, Random *random)
}
if (odds > 0 && random->nextInt(rate) <= odds)
{
if (!(level->isRaining() && level->isRainingAt(xx, yy, zz) || level->isRainingAt(xx - 1, yy, z) || level->isRainingAt(xx + 1, yy, zz) || level->isRainingAt(xx, yy, zz - 1) || level->isRainingAt(xx, yy, zz + 1)))
{
int tAge = age + random->nextInt(5) / 4;
if (tAge > 15) tAge = 15;
level->setTileAndData(xx, yy, zz, id, tAge, Tile::UPDATE_ALL);
if (!(level->isRaining() && level->isRainingAt(xx, yy, zz) || level->isRainingAt(xx - 1, yy, z) || level->isRainingAt(xx + 1, yy, zz) || level->isRainingAt(xx, yy, zz - 1) || level->isRainingAt(xx, yy, zz + 1)))
{
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
if (!FourKitBridge::FireBlockSpread(level->dimension->id, xx, yy, zz, x, y, z, id, min(age + random->nextInt(5) / 4, 15)))
#endif
{
int tAge = age + random->nextInt(5) / 4;
if (tAge > 15) tAge = 15;
level->setTileAndData(xx, yy, zz, id, tAge, Tile::UPDATE_ALL);
}
}
}
}
@ -221,6 +229,10 @@ void FireTile::checkBurnOut(Level *level, int x, int y, int z, int chance, Rando
int odds = burnOdds[level->getTile(x, y, z)];
if (random->nextInt(chance) < odds)
{
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
if (FourKitBridge::FireBlockBurn(level->dimension->id, x, y, z))
return;
#endif
bool wasTnt = level->getTile(x, y, z) == Tile::tnt_Id;
if (random->nextInt(age + 10) < 5 && !level->isRainingAt(x, y, z) && app.GetGameHostOption(eGameHostOption_FireSpreads))
{

View file

@ -5,6 +5,10 @@
#include "net.minecraft.world.level.biome.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
// AP - included for PSVita Alpha cut out optimisation
#include "IntBuffer.h"
@ -108,6 +112,9 @@ void GrassTile::tick(Level *level, int x, int y, int z, Random *random)
int above = level->getTile(xt, yt + 1, zt);
if (level->getTile(xt, yt, zt) == Tile::dirt_Id && level->getRawBrightness(xt, yt + 1, zt) >= MIN_BRIGHTNESS && Tile::lightBlock[above] <= 2)
{
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
if (!FourKitBridge::FireBlockSpread(level->dimension->id, xt, yt, zt, x, y, z, Tile::grass_Id, 0))
#endif
level->setTileAndUpdate(xt, yt, zt, Tile::grass_Id);
}
}

View file

@ -2,6 +2,10 @@
#include "net.minecraft.world.level.h"
#include "LiquidTileDynamic.h"
#include "net.minecraft.world.level.dimension.h"
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
#include "..\Minecraft.Server\FourKitBridge.h"
#include "Dimension.h"
#endif
LiquidTileDynamic::LiquidTileDynamic(int id, Material *material) : LiquidTile(id, material)
{
@ -156,8 +160,8 @@ void LiquidTileDynamic::mainTick(Level *level, int x, int y, int z, Random *rand
}
}
if (depth >= 8) trySpreadTo(level, x, y - 1, z, depth);
else trySpreadTo(level, x, y - 1, z, depth + 8);
if (depth >= 8) trySpreadTo(level, x, y - 1, z, x, y, z, depth);
else trySpreadTo(level, x, y - 1, z, x, y, z, depth + 8);
}
else if (depth >= 0 && (depth == 0 || isWaterBlocking(level, x, y - 1, z)))
{
@ -168,17 +172,31 @@ void LiquidTileDynamic::mainTick(Level *level, int x, int y, int z, Random *rand
neighbor = 1;
}
if (neighbor >= 8) return;
if (spreads[0]) trySpreadTo(level, x - 1, y, z, neighbor);
if (spreads[1]) trySpreadTo(level, x + 1, y, z, neighbor);
if (spreads[2]) trySpreadTo(level, x, y, z - 1, neighbor);
if (spreads[3]) trySpreadTo(level, x, y, z + 1, neighbor);
if (spreads[0]) trySpreadTo(level, x - 1, y, z, x, y, z, neighbor);
if (spreads[1]) trySpreadTo(level, x + 1, y, z, x, y, z, neighbor);
if (spreads[2]) trySpreadTo(level, x, y, z - 1, x, y, z, neighbor);
if (spreads[3]) trySpreadTo(level, x, y, z + 1, x, y, z, neighbor);
}
}
void LiquidTileDynamic::trySpreadTo(Level *level, int x, int y, int z, int neighbor)
void LiquidTileDynamic::trySpreadTo(Level *level, int x, int y, int z, int fromX, int fromY, int fromZ, int neighbor)
{
if (canSpreadTo(level, x, y, z))
{
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
{
int face = 6; // SELF
int dx = x - fromX, dy = y - fromY, dz = z - fromZ;
if (dy < 0) face = 0; // DOWN
else if (dy > 0) face = 1; // UP
else if (dz < 0) face = 2; // NORTH
else if (dz > 0) face = 3; // SOUTH
else if (dx < 0) face = 4; // WEST
else if (dx > 0) face = 5; // EAST
if (FourKitBridge::FireBlockFromTo(level->dimension->id, fromX, fromY, fromZ, x, y, z, face))
return;
}
#endif
{
int old = level->getTile(x, y, z);
if (old > 0)

View file

@ -36,7 +36,7 @@ private:
public:
void tick(Level *level, int x, int y, int z, Random *random);
private:
void trySpreadTo(Level *level, int x, int y, int z, int neighbor);
void trySpreadTo(Level *level, int x, int y, int z, int fromX, int fromY, int fromZ, int neighbor);
bool *result;
int *dist;

View file

@ -53,6 +53,9 @@ void Mushroom::tick(Level *level, int x, int y, int z, Random *random)
if (level->isEmptyTile(x2, y2, z2) && canSurvive(level, x2, y2, z2))
{
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
if (!FourKitBridge::FireBlockSpread(level->dimension->id, x2, y2, z2, x, y, z, id, 0))
#endif
level->setTileAndData(x2, y2, z2, id, 0, UPDATE_CLIENTS);
}
}

View file

@ -4,6 +4,10 @@
#include "net.minecraft.world.level.biome.h"
#include "net.minecraft.world.item.h"
#include "net.minecraft.world.h"
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
#include "..\Minecraft.Server\FourKitBridge.h"
#include "Dimension.h"
#endif
NetherWartTile::NetherWartTile(int id) : Bush(id)
{
@ -36,6 +40,12 @@ void NetherWartTile::tick(Level *level, int x, int y, int z, Random *random)
{
if (random->nextInt(10) == 0)
{
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
if (FourKitBridge::FireBlockGrow(level->dimension->id, x, y, z, level->getTile(x, y, z), age + 1))
{
return;
}
#endif
age++;
level->setData(x, y, z, age, Tile::UPDATE_CLIENTS);
}

View file

@ -10,6 +10,9 @@
#include "net.minecraft.world.h"
#include "LevelChunk.h"
#include "Dimension.h"
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
#include "..\Minecraft.Server\FourKitBridge.h"
#endif
const wstring PistonBaseTile::EDGE_TEX = L"piston_side";
const wstring PistonBaseTile::PLATFORM_TEX = L"piston_top";
@ -230,6 +233,31 @@ bool PistonBaseTile::triggerEvent(Level *level, int x, int y, int z, int param1,
if (param1 == TRIGGER_EXTEND)
{
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
{
int pushLength = 0;
int cx = x + Facing::STEP_X[facing];
int cy = y + Facing::STEP_Y[facing];
int cz = z + Facing::STEP_Z[facing];
for (int i = 0; i < MAX_PUSH_DEPTH + 1; i++)
{
int block = level->getTile(cx, cy, cz);
if (block == 0) break;
if (!isPushable(block, level, cx, cy, cz, true)) break;
if (Tile::tiles[block]->getPistonPushReaction() == Material::PUSH_DESTROY) { pushLength++; break; }
pushLength++;
if (i == MAX_PUSH_DEPTH) break;
cx += Facing::STEP_X[facing];
cy += Facing::STEP_Y[facing];
cz += Facing::STEP_Z[facing];
}
if (FourKitBridge::FirePistonExtend(level->dimension->id, x, y, z, facing, pushLength))
{
ignoreUpdate(false);
return false;
}
}
#endif
PIXBeginNamedEvent(0,"Create push\n");
if (createPush(level, x, y, z, facing))
{
@ -256,6 +284,14 @@ bool PistonBaseTile::triggerEvent(Level *level, int x, int y, int z, int param1,
}
else if (param1 == TRIGGER_CONTRACT)
{
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
if (FourKitBridge::FirePistonRetract(level->dimension->id, x, y, z, facing))
{
level->setData(x, y, z, facing | EXTENDED_BIT, UPDATE_CLIENTS);
ignoreUpdate(false);
return false;
}
#endif
PIXBeginNamedEvent(0,"Contract phase A\n");
shared_ptr<TileEntity> prevTileEntity = level->getTileEntity(x + Facing::STEP_X[facing], y + Facing::STEP_Y[facing], z + Facing::STEP_Z[facing]);
if (prevTileEntity != nullptr && dynamic_pointer_cast<PistonPieceEntity>(prevTileEntity) != nullptr)

View file

@ -5,6 +5,10 @@
#include "net.minecraft.world.level.material.h"
#include "net.minecraft.world.phys.h"
#include "ReedTile.h"
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
#include "..\Minecraft.Server\FourKitBridge.h"
#include "Dimension.h"
#endif
ReedTile::ReedTile(int id) : Tile( id, Material::plant,isSolidRender() )
{
@ -33,6 +37,10 @@ void ReedTile::tick(Level *level, int x, int y, int z, Random* random)
int age = level->getData(x, y, z);
if (age == 15)
{
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
if (FourKitBridge::FireBlockGrow(level->dimension->id, x, y + 1, z, id, 0))
return;
#endif
level->setTileAndUpdate(x, y + 1, z, id);
level->setData(x, y, z, 0, Tile::UPDATE_NONE);
}

View file

@ -6,6 +6,10 @@
#include "..\Minecraft.Client\Minecraft.h"
#include "..\Minecraft.Client\Common\Colours\ColourTable.h"
#include "StemTile.h"
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
#include "..\Minecraft.Server\FourKitBridge.h"
#include "Dimension.h"
#endif
const wstring StemTile::TEXTURE_ANGLED = L"stem_bent";
@ -60,6 +64,9 @@ void StemTile::tick(Level *level, int x, int y, int z, Random *random)
int below = level->getTile(xx, y - 1, zz);
if (level->getTile(xx, y, zz) == 0 && (below == Tile::farmland_Id || below == Tile::dirt_Id || below == Tile::grass_Id))
{
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
if (!FourKitBridge::FireBlockGrow(level->dimension->id, xx, y, zz, fruit->id, 0))
#endif
level->setTileAndUpdate(xx, y, zz, fruit->id);
}