diff --git a/Minecraft.Server.FourKit/Entity/Entity.cs b/Minecraft.Server.FourKit/Entity/Entity.cs index 586194496..95f52d697 100644 --- a/Minecraft.Server.FourKit/Entity/Entity.cs +++ b/Minecraft.Server.FourKit/Entity/Entity.cs @@ -5,7 +5,7 @@ using Minecraft.Server.FourKit.Util; /// /// Represents a base entity in the world /// -public abstract class Entity +public class Entity { private Location _location = new(); private Guid _uniqueId = Guid.NewGuid(); @@ -101,6 +101,70 @@ public abstract class Entity NativeBridge.SetVelocity?.Invoke(getEntityId(), velocity.getX(), velocity.getY(), velocity.getZ()); } + /// + /// Returns whether this entity is inside a vehicle. + /// + /// true if the entity is in a vehicle. + public bool isInsideVehicle() + { + return (NativeBridge.GetVehicleId?.Invoke(getEntityId()) ?? -1) >= 0; + } + + /// + /// Leave the current vehicle. If the entity is currently in a vehicle + /// (and is removed from it), true will be returned, otherwise + /// false will be returned. + /// + /// true if the entity was in a vehicle. + public bool leaveVehicle() + { + return NativeBridge.LeaveVehicle?.Invoke(getEntityId()) != 0; + } + + /// + /// Get the vehicle that this entity is inside. If there is no vehicle, + /// null will be returned. + /// + /// The current vehicle, or null. + public Entity? getVehicle() + { + int vehicleId = NativeBridge.GetVehicleId?.Invoke(getEntityId()) ?? -1; + if (vehicleId < 0) return null; + return FourKit.GetEntityByEntityId(vehicleId); + } + + /// + /// Eject any passenger. + /// + /// true if there was a passenger. + public bool eject() + { + return NativeBridge.Eject?.Invoke(getEntityId()) != 0; + } + + /// + /// Gets the primary passenger of a vehicle. For vehicles that could + /// have multiple passengers, this will only return the primary passenger. + /// + /// The passenger entity, or null. + public Entity? getPassenger() + { + int passengerId = NativeBridge.GetPassengerId?.Invoke(getEntityId()) ?? -1; + if (passengerId < 0) return null; + return FourKit.GetEntityByEntityId(passengerId); + } + + /// + /// Set the passenger of a vehicle. + /// + /// The new passenger. + /// false if it could not be done for whatever reason. + public bool setPassenger(Entity passenger) + { + if (passenger == null || NativeBridge.SetPassenger == null) return false; + return NativeBridge.SetPassenger(getEntityId(), passenger.getEntityId()) != 0; + } + // INTERNAL internal void SetLocation(Location location) { diff --git a/Minecraft.Server.FourKit/Event/Inventory/InventoryInteractEvent.cs b/Minecraft.Server.FourKit/Event/Inventory/InventoryInteractEvent.cs index 416f55f29..4d5873e6e 100644 --- a/Minecraft.Server.FourKit/Event/Inventory/InventoryInteractEvent.cs +++ b/Minecraft.Server.FourKit/Event/Inventory/InventoryInteractEvent.cs @@ -6,6 +6,7 @@ using Minecraft.Server.FourKit.Inventory; /// /// An abstract base class for events that describe an interaction between a /// and the contents of an . +/// This is currently not emitted anywhere, use instead. /// public abstract class InventoryInteractEvent : InventoryEvent, Cancellable { diff --git a/Minecraft.Server.FourKit/FourKit.cs b/Minecraft.Server.FourKit/FourKit.cs index a47184aa2..2c3a11b7b 100644 --- a/Minecraft.Server.FourKit/FourKit.cs +++ b/Minecraft.Server.FourKit/FourKit.cs @@ -97,6 +97,41 @@ public static class FourKit } } + internal static Entity.Entity? GetEntityByEntityId(int entityId) + { + var player = GetPlayerByEntityId(entityId); + if (player != null) return player; + + if (NativeBridge.GetEntityInfo == null) return null; + + IntPtr buf = System.Runtime.InteropServices.Marshal.AllocHGlobal(5 * sizeof(double)); + try + { + NativeBridge.GetEntityInfo(entityId, buf); + double[] data = new double[5]; + System.Runtime.InteropServices.Marshal.Copy(buf, data, 0, 5); + + int typeId = (int)data[0]; + if (typeId < 0) return null; + + var entityType = Enum.IsDefined(typeof(Entity.EntityType), typeId) + ? (Entity.EntityType)typeId + : Entity.EntityType.UNKNOWN; + int dimId = (int)data[4]; + + var entity = new Entity.Entity(); + entity.SetEntityIdInternal(entityId); + entity.SetEntityTypeInternal(entityType); + entity.SetDimensionInternal(dimId); + entity.SetLocation(new Location(getWorld(dimId), data[1], data[2], data[3])); + return entity; + } + finally + { + System.Runtime.InteropServices.Marshal.FreeHGlobal(buf); + } + } + internal static Player TrackPlayer(int entityId, string name) { lock (_playerLock) diff --git a/Minecraft.Server.FourKit/FourKitHost.cs b/Minecraft.Server.FourKit/FourKitHost.cs index 45594f359..092ac480d 100644 --- a/Minecraft.Server.FourKit/FourKitHost.cs +++ b/Minecraft.Server.FourKit/FourKitHost.cs @@ -638,6 +638,19 @@ public static class FourKitHost } } + [UnmanagedCallersOnly] + public static void SetVehicleCallbacks(IntPtr setPassenger, IntPtr leaveVehicle, IntPtr eject, IntPtr getVehicleId, IntPtr getPassengerId, IntPtr getEntityInfo) + { + try + { + NativeBridge.SetVehicleCallbacks(setPassenger, leaveVehicle, eject, getVehicleId, getPassengerId, getEntityInfo); + } + catch (Exception ex) + { + ServerLog.Error("fourkit", $"SetVehicleCallbacks error: {ex}"); + } + } + [UnmanagedCallersOnly] public static long FirePlayerDropItem(int entityId, int itemId, int itemCount, int itemAux, IntPtr outItemIdPtr, IntPtr outItemCountPtr, IntPtr outItemAuxPtr) diff --git a/Minecraft.Server.FourKit/NativeBridge.cs b/Minecraft.Server.FourKit/NativeBridge.cs index f415c6613..1ec577872 100644 --- a/Minecraft.Server.FourKit/NativeBridge.cs +++ b/Minecraft.Server.FourKit/NativeBridge.cs @@ -156,6 +156,24 @@ internal static class NativeBridge [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate void NativeSpawnParticleDelegate(int entityId, int particleId, float x, float y, float z, float offsetX, float offsetY, float offsetZ, float speed, int count); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate int NativeSetPassengerDelegate(int entityId, int passengerEntityId); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate int NativeLeaveVehicleDelegate(int entityId); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate int NativeEjectDelegate(int entityId); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate int NativeGetVehicleIdDelegate(int entityId); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate int NativeGetPassengerIdDelegate(int entityId); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate void NativeGetEntityInfoDelegate(int entityId, IntPtr outBuf); + internal static NativeDamageDelegate? DamagePlayer; internal static NativeSetHealthDelegate? SetPlayerHealth; @@ -208,6 +226,12 @@ internal static class NativeBridge internal static NativeSetSaturationDelegate? SetSaturation; internal static NativeSetExhaustionDelegate? SetExhaustion; internal static NativeSpawnParticleDelegate? SpawnParticle; + internal static NativeSetPassengerDelegate? SetPassenger; + internal static NativeLeaveVehicleDelegate? LeaveVehicle; + internal static NativeEjectDelegate? Eject; + internal static NativeGetVehicleIdDelegate? GetVehicleId; + internal static NativeGetPassengerIdDelegate? GetPassengerId; + internal static NativeGetEntityInfoDelegate? GetEntityInfo; internal static void SetCallbacks(IntPtr damage, IntPtr setHealth, IntPtr teleport, IntPtr setGameMode, IntPtr broadcastMessage, IntPtr setFallDistance, IntPtr getPlayerSnapshot, IntPtr sendMessage, IntPtr setWalkSpeed, IntPtr teleportEntity) { @@ -286,4 +310,14 @@ internal static class NativeBridge { SpawnParticle = Marshal.GetDelegateForFunctionPointer(spawnParticle); } + + internal static void SetVehicleCallbacks(IntPtr setPassenger, IntPtr leaveVehicle, IntPtr eject, IntPtr getVehicleId, IntPtr getPassengerId, IntPtr getEntityInfo) + { + SetPassenger = Marshal.GetDelegateForFunctionPointer(setPassenger); + LeaveVehicle = Marshal.GetDelegateForFunctionPointer(leaveVehicle); + Eject = Marshal.GetDelegateForFunctionPointer(eject); + GetVehicleId = Marshal.GetDelegateForFunctionPointer(getVehicleId); + GetPassengerId = Marshal.GetDelegateForFunctionPointer(getPassengerId); + GetEntityInfo = Marshal.GetDelegateForFunctionPointer(getEntityInfo); + } } diff --git a/Minecraft.Server/FourKitBridge.cpp b/Minecraft.Server/FourKitBridge.cpp index 4cecf840e..02b68c527 100644 --- a/Minecraft.Server/FourKitBridge.cpp +++ b/Minecraft.Server/FourKitBridge.cpp @@ -1,4 +1,5 @@ // todo: split into files for better readability +// todo: this needs to be made way more neat and less duplicate stuff #include "FourKitBridge.h" #include "Common/StringUtils.h" @@ -40,6 +41,7 @@ #include "..\Minecraft.World\SetHealthPacket.h" #include "..\Minecraft.World\LevelSoundPacket.h" #include "..\Minecraft.World\LevelParticlesPacket.h" +#include "..\Minecraft.World\SetEntityLinkPacket.h" #include "..\Minecraft.World\SimpleContainer.h" #include "..\Minecraft.World\Slot.h" #include "..\Minecraft.World\Tile.h" @@ -182,6 +184,7 @@ typedef void(__stdcall *fn_fire_bed_leave)(int entityId, int dimId, int bedX, in typedef void(__stdcall *fn_set_entity_callbacks)(void *setSneaking, void *setVelocity, void *setAllowFlight, void *playSound, void *setSleepingIgnored); 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); struct OpenContainerInfo { @@ -225,6 +228,7 @@ static fn_fire_bed_leave s_managedFireBedLeave = nullptr; 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 bool s_initialized = false; @@ -1445,6 +1449,76 @@ static void __cdecl NativeSpawnParticle(int entityId, int particleId, float x, f player->connection->send(std::make_shared(std::wstring(buf), x, y, z, offsetX, offsetY, offsetZ, speed, count)); } +static int __cdecl NativeSetPassenger(int entityId, int passengerEntityId) +{ + auto entity = FindEntity(entityId); + auto passenger = FindEntity(passengerEntityId); + if (!entity || !passenger) return 0; + passenger->ride(entity); + PlayerList *list = MinecraftServer::getPlayerList(); + if (list) + list->broadcastAll(std::make_shared(SetEntityLinkPacket::RIDING, passenger, entity), entity->dimension); + return 1; +} + +static int __cdecl NativeLeaveVehicle(int entityId) +{ + auto entity = FindEntity(entityId); + if (!entity || !entity->riding) return 0; + int dim = entity->riding->dimension; + entity->ride(nullptr); + PlayerList *list = MinecraftServer::getPlayerList(); + if (list) + list->broadcastAll(std::make_shared(SetEntityLinkPacket::RIDING, entity, nullptr), dim); + return 1; +} + +static int __cdecl NativeEject(int entityId) +{ + auto entity = FindEntity(entityId); + if (!entity) return 0; + auto riderPtr = entity->rider.lock(); + if (!riderPtr) return 0; + riderPtr->ride(nullptr); + PlayerList *list = MinecraftServer::getPlayerList(); + if (list) + list->broadcastAll(std::make_shared(SetEntityLinkPacket::RIDING, riderPtr, nullptr), entity->dimension); + return 1; +} + +static int __cdecl NativeGetVehicleId(int entityId) +{ + auto entity = FindEntity(entityId); + if (!entity || !entity->riding) return -1; + return entity->riding->entityId; +} + +static int __cdecl NativeGetPassengerId(int entityId) +{ + auto entity = FindEntity(entityId); + if (!entity) return -1; + auto riderPtr = entity->rider.lock(); + if (!riderPtr) return -1; + return riderPtr->entityId; +} + +static void __cdecl NativeGetEntityInfo(int entityId, double *outData) +{ + // this stinks iof shit and more shit + outData[0] = -1; + outData[1] = 0; + outData[2] = 0; + outData[3] = 0; + outData[4] = 0; + auto entity = FindEntity(entityId); + if (!entity) return; + outData[0] = (double)MapEntityType((int)entity->GetType()); + outData[1] = entity->x; + outData[2] = entity->y; + outData[3] = entity->z; + outData[4] = (double)entity->dimension; +} + static std::wstring FindNet10SystemRoot() { // overengineered @@ -1690,6 +1764,7 @@ void Initialize() ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"SetEntityCallbacks", (void **)&s_managedSetEntityCallbacks); ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"SetExperienceCallbacks", (void **)&s_managedSetExperienceCallbacks); ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"SetParticleCallbacks", (void **)&s_managedSetParticleCallbacks); + ok = ok && GetManagedEntryPoint(loadAssembly, assemblyPath.c_str(), typeName, L"SetVehicleCallbacks", (void **)&s_managedSetVehicleCallbacks); if (!ok) { @@ -1765,6 +1840,14 @@ void Initialize() s_managedSetParticleCallbacks( (void *)&NativeSpawnParticle); + s_managedSetVehicleCallbacks( + (void *)&NativeSetPassenger, + (void *)&NativeLeaveVehicle, + (void *)&NativeEject, + (void *)&NativeGetVehicleId, + (void *)&NativeGetPassengerId, + (void *)&NativeGetEntityInfo); + LogInfo("fourkit", "FourKit initialized successfully."); }