feat: add FourKit plugin host with dual server build

Adds the FourKit .NET 10 plugin host as a second dedicated server
build flavour alongside the existing vanilla server. Both flavours
build from the same source tree, with FourKit gated by the
MINECRAFT_SERVER_FOURKIT_BUILD preprocessor define.

Build layout:

  Minecraft.Server         vanilla, no plugin support, no .NET dep
  Minecraft.Server.FourKit FourKit-enabled, ships with bundled
                           .NET 10 self-contained runtime in runtime/
                           and an empty plugins/ folder

Both produce a Minecraft.Server.exe in their own per-target output
dir. The variant identity lives in the directory name, not the
binary name, so either flavour can be shipped as a drop-in.

Native bridge (Minecraft.Server/FourKit*.{cpp,h}):

* FourKitRuntime: hosts CoreCLR via hostfxr's command-line init API
  (the runtime-config API does not support self-contained components)
* FourKitBridge: ~50 Fire* event entry points, with inline no-op
  stubs for the standalone build so gameplay code can call them
  unconditionally
* FourKitNatives: ~80 native callbacks the managed side invokes
  for player/world/inventory mutations
* FourKitMappers: type and enum mapping helpers

Managed plugin host (Minecraft.Server.FourKit/):

* Bukkit-style API: Player, World, Block, Inventory, Command,
  Listener, EventHandler attribute, ~54 event classes
* PluginLoader with per-plugin AssemblyLoadContext
* FourKitHost as the [UnmanagedCallersOnly] entry point table
* Runtime resolves plugins relative to the host process so they
  always live next to Minecraft.Server.exe regardless of where the
  managed assembly itself is loaded from

Engine hooks (Minecraft.Client/, Minecraft.World/):

* Player lifecycle (PreLogin, Login, Join, Quit, Kick, Move,
  Teleport, Portal, Death) wired into PendingConnection and
  PlayerConnection without disturbing the cipher handshake or
  identity-token security flow
* Inventory open/click/drop hooks across every container menu type
* Block place/break/grow/burn/spread/from-to hooks across the
  full tile family
* Bed enter/leave, sign change, entity damage/death, ender pearl
  teleport hooks

Regression fixes preserved while applying donor diffs:

* ServerPlayer::die() retains the LCE-Revelations hardcore branch
  (setGameMode(ADVENTURE) + banPlayerForHardcoreDeath) in both the
  FourKit and non-FourKit code paths
* ServerLevel::entityAdded() retains the sub-entity ID reassignment
  loop required by the client's handleAddMob offset, fixing Ender
  Dragon and Wither boss multi-part hit detection
* LivingEntity::travel() retains the raw Player* cast and the
  cached frictionTile, both Revelations perf wins that the donor
  silently reverted
* ServerLogger.cpp keeps the file-logging code donor stripped
* PlayerList.cpp end portal transition fix and UIScene_EndPoem
  bounds-check are intact

Build system:

* Top-level CMakeLists.txt adds the Minecraft.Server.FourKit
  subdirectory and pulls in the new shared cmake/ServerTarget.cmake
  helper
* Minecraft.Server/cmake/sources/Common.cmake is now location
  independent (uses CMAKE_CURRENT_LIST_DIR) so the source list
  can be consumed from either server target's CMakeLists.txt
* The seven FourKit*.cpp/h files live in their own
  _MINECRAFT_SERVER_COMMON_SERVER_FOURKIT variable so the
  standalone target omits them
* configure-time .NET 10 SDK check fails fast with a clear
  download link if the SDK is missing
* global.json pins the SDK to 10.0.100 with latestFeature
  rollforward

Sample plugin (samples/HelloPlugin/) demonstrates the loader and
the PlayerJoinEvent listener pattern.

CI:

* nightly.yml builds both server flavours, ships
  LCE-Revelations-Server-Win64.zip and
  LCE-Revelations-Server-Win64-FourKit.zip, attests both, and
  updates release notes for the dual-flavour layout
* pull-request.yml pulls in actions/setup-dotnet so the FourKit
  publish step works in PR validation
* All zip artifacts and the client zip are renamed from
  LCREWindows64 to LCE-Revelations-{Client,Server}-Win64

Documentation:

* COMPILE.md gets a VS 2022 quick start, .NET 10 prereq section,
  server flavours explanation, and a troubleshooting section
* docs/FOURKIT_PORT_RECON.md captures the file-by-file recon that
  drove the port
* docs/FOURKIT_PARITY.md is the canonical reference for which
  events FourKit fires

Docker:

* docker-compose.dedicated-server.yml MC_RUNTIME_DIR default points
  at the vanilla CMake output. The FourKit Docker image is
  intentionally NOT shipped yet because hosting .NET 10 self
  contained inside Wine has not been smoke-tested
This commit is contained in:
itsRevela
2026-04-08 02:32:31 -05:00
parent 5d56f5080f
commit 42a582fb9f
197 changed files with 20946 additions and 788 deletions

View File

@@ -40,8 +40,8 @@ jobs:
shell: pwsh
run: |
$source = "./build/windows64/Minecraft.Client/Release"
$zip = "LCREWindows64.zip"
$topLevel = "LCREWindows64"
$zip = "LCE-Revelations-Client-Win64.zip"
$topLevel = "LCE-Revelations-Client-Win64"
# Collect files, excluding unwanted extensions
$files = Get-ChildItem -Path $source -Recurse -File |
@@ -78,7 +78,7 @@ jobs:
shell: pwsh
run: |
New-Item -ItemType Directory -Force -Path staging
Copy-Item LCREWindows64.zip staging/
Copy-Item LCE-Revelations-Client-Win64.zip staging/
Copy-Item ./build/windows64/Minecraft.Client/Release/Minecraft.Client.exe staging/
- name: Upload artifacts
@@ -101,6 +101,11 @@ jobs:
- name: Setup CMake
uses: lukka/get-cmake@latest
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
global-json-file: global.json
- name: Run CMake
uses: lukka/run-cmake@v10
env:
@@ -108,14 +113,49 @@ jobs:
with:
configurePreset: windows64
buildPreset: windows64-release
buildPresetAdditionalArgs: "['--target', 'Minecraft.Server']"
buildPresetAdditionalArgs: "['--target', 'Minecraft.Server', '--target', 'Minecraft.Server.FourKit']"
- name: Zip Build
- name: Zip Build (vanilla)
shell: pwsh
run: |
$source = "./build/windows64/Minecraft.Server/Release"
$zip = "LCREServerWindows64.zip"
$topLevel = "LCREServerWindows64"
$zip = "LCE-Revelations-Server-Win64.zip"
$topLevel = "LCE-Revelations-Server-Win64"
$files = Get-ChildItem -Path $source -Recurse -File |
Where-Object { $_.Extension -notin '.pch', '.zip', '.ipdb', '.iobj' }
Add-Type -AssemblyName System.IO.Compression
Add-Type -AssemblyName System.IO.Compression.FileSystem
$basePath = (Resolve-Path $source).Path
$fs = [System.IO.File]::Open($zip, [System.IO.FileMode]::Create)
try {
$archive = New-Object System.IO.Compression.ZipArchive($fs, [System.IO.Compression.ZipArchiveMode]::Create)
try {
Get-ChildItem -Path $basePath -Recurse -Directory | ForEach-Object {
$rel = $_.FullName.Substring($basePath.Length).TrimStart('\', '/')
$archive.CreateEntry("$topLevel/$($rel -replace '\\','/')/") | Out-Null
}
foreach ($file in $files) {
$rel = $file.FullName.Substring($basePath.Length).TrimStart('\', '/')
$entryName = "$topLevel/$($rel -replace '\\','/')"
[System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile(
$archive, $file.FullName, $entryName,
[System.IO.Compression.CompressionLevel]::Optimal
) | Out-Null
}
} finally { $archive.Dispose() }
} finally { $fs.Dispose() }
Write-Host "Created $zip"
- name: Zip Build (FourKit)
shell: pwsh
run: |
$source = "./build/windows64/Minecraft.Server.FourKit/Release"
$zip = "LCE-Revelations-Server-Win64-FourKit.zip"
$topLevel = "LCE-Revelations-Server-Win64-FourKit"
$files = Get-ChildItem -Path $source -Recurse -File |
Where-Object { $_.Extension -notin '.pch', '.zip', '.ipdb', '.iobj' }
@@ -149,7 +189,8 @@ jobs:
shell: pwsh
run: |
New-Item -ItemType Directory -Force -Path staging
Copy-Item LCREServerWindows64.zip staging/
Copy-Item LCE-Revelations-Server-Win64.zip staging/
Copy-Item LCE-Revelations-Server-Win64-FourKit.zip staging/
- name: Upload artifacts
uses: actions/upload-artifact@v6
@@ -176,7 +217,8 @@ jobs:
uses: actions/attest-build-provenance@v2
with:
subject-path: |
artifacts/LCREServerWindows64.zip
artifacts/LCE-Revelations-Server-Win64.zip
artifacts/LCE-Revelations-Server-Win64-FourKit.zip
- name: Get short SHA
id: sha
@@ -213,7 +255,11 @@ jobs:
--title "Server: ${{ steps.sha.outputs.short }}" \
--notes "Dedicated Server runtime for Windows64.
Download \`LCREServerWindows64.zip\` and extract it to a folder where you'd like to keep the server runtime." \
Two flavours are attached:
- \`LCE-Revelations-Server-Win64.zip\`: vanilla server, no plugin support, smallest download.
- \`LCE-Revelations-Server-Win64-FourKit.zip\`: server with the FourKit plugin host, bundled .NET 10 runtime, and an empty \`plugins/\` folder ready for plugin authors to drop DLLs into.
Pick the flavour you want and extract it to a folder where you'd like to keep the server runtime." \
--latest=false
release-client:
@@ -235,7 +281,7 @@ jobs:
uses: actions/attest-build-provenance@v2
with:
subject-path: |
artifacts/LCREWindows64.zip
artifacts/LCE-Revelations-Client-Win64.zip
artifacts/Minecraft.Client.exe
- name: Get short SHA
@@ -270,15 +316,15 @@ jobs:
cat > notes.md <<'NOTES'
# Instructions:
**Newcomers:**
- If this is your first time, download `LCREWindows64.zip` and extract it wherever you would like to keep it.
- If this is your first time, download `LCE-Revelations-Client-Win64.zip` and extract it wherever you would like to keep it.
- I would recommend to set your username prior to launch (create a file called `username.txt`, put your desired username into the file, and save).
- To play, simply run `Minecraft.Client.exe`.
**For those that wish to update their existing installation with the latest build:**
- Download `Minecraft.Client.exe` and `Minecraft.Client.pdb` and copy them over to your existing LCREWindows64 build (overwrite your old version of Minecraft.Client.exe and Minecraft.Client.pdb).
- Download `Minecraft.Client.exe` and `Minecraft.Client.pdb` and copy them over to your existing LCE-Revelations-Client-Win64 build (overwrite your old version of Minecraft.Client.exe and Minecraft.Client.pdb).
**Steam Deck & Linux:**
- Y'all know the drill. Download the `LCREWindows64.zip`, extract it, add the `Minecraft.Client.exe` as a "Non-Steam Game" within the Steam library, turn on compatibility mode with Proton Experimental, and then run it!
- Y'all know the drill. Download the `LCE-Revelations-Client-Win64.zip`, extract it, add the `Minecraft.Client.exe` as a "Non-Steam Game" within the Steam library, turn on compatibility mode with Proton Experimental, and then run it!
# Multiplayer instructions:
LAN games are natively supported, and any LAN games will appear automatically on the right. However, if you'd like to play with your friends online (and if you don't want to require them to setup a vpn, and/or if you don't want to port forward), I would recommend the following setup. Please keep in mind, you do NOT need to do this to enjoy the game. This is just how I have it setup for me so my friends can join without any hassle:
@@ -289,7 +335,7 @@ jobs:
How-to:
- Ensure your playit.gg agent is connected to your playit.gg account
- On the playit.gg website, setup a new tunnel (choose TCP). Ensure the configurable settings are set to the below values, assuming your agent is installed on the same computer as your online LCREMinecraft game is hosted from.
- On the playit.gg website, setup a new tunnel (choose TCP). Ensure the configurable settings are set to the below values, assuming your agent is installed on the same computer as your online LCE Revelations game is hosted from.
- Configurable settings:
- Local IP: `127.0.0.1`
- Local Port: `25565`

View File

@@ -22,7 +22,12 @@ jobs:
- name: Setup CMake
uses: lukka/get-cmake@latest
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
global-json-file: global.json
- name: Run CMake
uses: lukka/run-cmake@v10
env:

View File

@@ -84,6 +84,7 @@ list(APPEND MINECRAFT_SHARED_DEFINES ${PLATFORM_DEFINES})
add_subdirectory(Minecraft.World)
add_subdirectory(Minecraft.Client)
if(PLATFORM_NAME STREQUAL "Windows64") # Server is only supported on Windows for now
add_subdirectory(Minecraft.Server.FourKit)
add_subdirectory(Minecraft.Server)
endif()

View File

@@ -1,24 +1,55 @@
# Compile Instructions
## Visual Studio
## Prerequisites
1. Clone or download the repository
1. Open the repo folder in Visual Studio 2022+.
2. Wait for cmake to configure the project and load all assets (this may take a few minutes on the first run).
3. Right click a folder in the solution explorer and switch to the 'CMake Targets View'
4. Select platform and configuration from the dropdown. EG: `Windows64 - Debug` or `Windows64 - Release`
5. Pick the startup project `Minecraft.Client.exe` or `Minecraft.Server.exe` using the debug targets dropdown
6. Build and run the project:
- `Build > Build Solution` (or `Ctrl+Shift+B`)
- Start debugging with `F5`.
- **Visual Studio 2022** with the **Desktop development with C++** workload (this includes the CMake tools, MSVC toolchain, and Windows 10 SDK).
- **.NET 10 SDK**, required to build the FourKit plugin host (`Minecraft.Server.FourKit`).
- Download: https://dotnet.microsoft.com/download/dotnet/10.0 (pick the **x64 SDK** installer)
- The exact SDK version is pinned in `global.json` at the repo root.
- CMake will fail configure with a clear error message if .NET 10 is not installed, so you find out immediately rather than partway through a build.
- The build invokes `dotnet publish ... --runtime win-x64 --self-contained true`, so the published output bundles a complete .NET 10 runtime alongside the FourKit assembly. End users running the produced server do **not** need to install .NET themselves.
- All FourKit runtime files (DLL + .NET runtime + `hostfxr.dll`) land in a `runtime/` subfolder next to `Minecraft.Server.exe`. An empty `plugins/` folder is also created. Both are produced automatically by the build.
## Visual Studio 2022 quick start (recommended)
VS 2022 has built-in CMake support, so there is no need to generate a `.sln` file by hand.
1. Install the prerequisites above.
2. Clone the repo.
3. In Visual Studio: `File > Open > Folder...` and select the **repo root** (the folder that contains `CMakeLists.txt`).
4. Wait for CMake to configure (~5 seconds on a warm cache, a few minutes on the first run while assets copy).
5. Pick a build configuration in the dropdown, for example `windows64-release`.
6. `Build > Build All` (or `F7`). Targets of interest:
- `Minecraft.Client`: the game client.
- `Minecraft.Server`: the **vanilla** dedicated server. Standalone C++ binary, no plugin host, no .NET dependency at runtime, smallest distribution.
- `Minecraft.Server.FourKit`: the **FourKit-enabled** dedicated server. Bundles the .NET 10 plugin host alongside the exe (in `runtime/`) and creates an empty `plugins/` folder for end users to drop plugin DLLs into. Building this target also triggers the `Minecraft.Server.FourKit.Managed` target which publishes the C# project.
7. Use the debug target dropdown to pick `Minecraft.Client.exe` or whichever server flavour you want, then `F5` to launch.
### Server flavours
Both server targets compile from the same source tree and produce a binary literally named `Minecraft.Server.exe`. The variant identity lives in the build directory:
```
build/<preset>/Minecraft.Server/Release/
Minecraft.Server.exe (vanilla, no plugin support)
Common/, Windows64/, ...
build/<preset>/Minecraft.Server.FourKit/Release/
Minecraft.Server.exe (FourKit-enabled, same exe name on purpose)
runtime/ (self-contained .NET 10 + Minecraft.Server.FourKit.dll)
plugins/ (empty drop point)
Common/, Windows64/, ...
```
The FourKit target gets the `MINECRAFT_SERVER_FOURKIT_BUILD` preprocessor define. Inside `FourKitBridge.h`, the real plugin entry points are conditional on that define; the vanilla target sees inline no-op stubs instead, so gameplay code can call `FourKitBridge::Fire*` unconditionally and produce the right behaviour for each flavour without per-call-site `#ifdef`s.
### Dedicated server debug arguments
- Default debugger arguments for `Minecraft.Server`:
- Default debugger arguments for both `Minecraft.Server` and `Minecraft.Server.FourKit`:
- `-port 25565 -bind 0.0.0.0 -name DedicatedServer`
- You can override arguments in:
- `Project Properties > Debugging > Command Arguments`
- `Minecraft.Server` post-build copies only the dedicated-server asset set:
- Both server targets post-build copy the dedicated-server asset set:
- `Common/Media/MediaWindows64.arc`
- `Common/res`
- `Windows64/GameHDD`
@@ -45,18 +76,36 @@ Build Release:
cmake --build --preset windows64-release --target Minecraft.Client
```
Build Dedicated Server (Debug):
Build vanilla Dedicated Server (Debug):
```powershell
cmake --build --preset windows64-debug --target Minecraft.Server
```
Build Dedicated Server (Release):
Build vanilla Dedicated Server (Release):
```powershell
cmake --build --preset windows64-release --target Minecraft.Server
```
Build FourKit Dedicated Server (Debug):
```powershell
cmake --build --preset windows64-debug --target Minecraft.Server.FourKit
```
Build FourKit Dedicated Server (Release):
```powershell
cmake --build --preset windows64-release --target Minecraft.Server.FourKit
```
Build everything (client + both server flavours):
```powershell
cmake --build --preset windows64-release
```
Run executable:
```powershell
@@ -64,15 +113,28 @@ cd .\build\windows64\Minecraft.Client\Debug
.\Minecraft.Client.exe
```
Run dedicated server:
Run vanilla dedicated server:
```powershell
cd .\build\windows64\Minecraft.Server\Debug
.\Minecraft.Server.exe -port 25565 -bind 0.0.0.0 -name DedicatedServer
```
Run FourKit dedicated server:
```powershell
cd .\build\windows64\Minecraft.Server.FourKit\Debug
.\Minecraft.Server.exe -port 25565 -bind 0.0.0.0 -name DedicatedServer
```
Notes:
- The CMake build is Windows-only and x64-only.
- Contributors on macOS or Linux need a Windows machine or VM to build the project. Running the game via Wine is separate from having a supported build environment.
- Post-build asset copy is automatic for `Minecraft.Client` in CMake (Debug and Release variants).
- The game relies on relative paths (for example `Common\Media\...`), so launching from the output directory is required.
### Troubleshooting
- **`'vswhere.exe' is not recognized`**: harmless warning. This appears if you ran `vcvars64.bat` from a plain command prompt instead of `Developer PowerShell for VS`. The Visual Studio Installer's `vswhere.exe` lives at `C:\Program Files (x86)\Microsoft Visual Studio\Installer\` and is not on the default `PATH`. Use the Developer PowerShell shortcut, or open the repo folder directly in VS (which handles the dev env for you).
- **`.NET 10 SDK not found` at configure time**: install the x64 SDK from https://dotnet.microsoft.com/download/dotnet/10.0 and re-run CMake configure (`Project > Configure Cache` in VS, or `cmake --preset windows64` from a shell).
- **Server starts but logs `hostfxr_initialize_for_dotnet_command_line failed`**: the `runtime/` folder next to `Minecraft.Server.exe` is missing or stale. Rebuild the `Minecraft.Server.FourKit` target (which re-stages `runtime/`), or do a clean rebuild of `Minecraft.Server`.

View File

@@ -19,6 +19,8 @@
#include "..\Minecraft.Server\Access\Access.h"
#include "..\Minecraft.Server\Security\SecurityConfig.h"
#include "..\Minecraft.World\Socket.h"
#include <FourKitBridge.h>
#include <Windows64/Network/WinsockNetLayer.h>
#endif
// #ifdef __PS3__
// #include "PS3\Network\NetworkPlayerSony.h"
@@ -112,6 +114,54 @@ void PendingConnection::handlePreLogin(shared_ptr<PreLoginPacket> packet)
}
return;
}
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
{
std::string connectionIp = "";
int connectionPort = 0;
if (connection && connection->getSocket()) {
unsigned char smallId = connection->getSocket()->getSmallId();
if (smallId != 0) {
if (!ServerRuntime::ServerLogManager::TryGetConnectionRemoteIp(smallId, &connectionIp))
{
SOCKET sock = WinsockNetLayer::GetSocketForSmallId(smallId);
if (sock != INVALID_SOCKET)
{
sockaddr_in addr;
int addrLen = sizeof(addr);
if (getpeername(sock, (sockaddr*)&addr, &addrLen) == 0)
{
char ipBuf[64] = {};
if (inet_ntop(AF_INET, &addr.sin_addr, ipBuf, sizeof(ipBuf)))
{
connectionIp = ipBuf;
connectionPort = (int)ntohs(addr.sin_port);
}
}
}
} else {
SOCKET sock = WinsockNetLayer::GetSocketForSmallId(smallId);
if (sock != INVALID_SOCKET)
{
sockaddr_in addr;
int addrLen = sizeof(addr);
if (getpeername(sock, (sockaddr*)&addr, &addrLen) == 0)
connectionPort = (int)ntohs(addr.sin_port);
}
}
if (!connectionIp.empty()) {
if (FourKitBridge::FirePlayerPreLogin(packet->loginKey, connectionIp, connectionPort)) {
disconnect(DisconnectPacket::eDisconnect_EndOfStream);
return;
}
}
}
}
}
#endif
// printf("Server: handlePreLogin\n");
name = packet->loginKey; // 4J Stu - Change from the login packet as we know better on client end during the pre-login packet
sendPreLoginResponse();
@@ -201,6 +251,62 @@ void PendingConnection::handleLogin(shared_ptr<LoginPacket> packet)
//if (true)// 4J removed !server->onlineMode)
bool sentDisconnect = false;
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
{
std::string connectionIp = "";
int connectionPort = 0;
if (!connection || !connection->getSocket()) {
disconnect(DisconnectPacket::eDisconnect_EndOfStream);
return;
}
unsigned char smallId = connection->getSocket()->getSmallId();
if (smallId == 0) {
disconnect(DisconnectPacket::eDisconnect_EndOfStream);
return;
}
if (!ServerRuntime::ServerLogManager::TryGetConnectionRemoteIp(smallId, &connectionIp))
{
SOCKET sock = WinsockNetLayer::GetSocketForSmallId(smallId);
if (sock != INVALID_SOCKET)
{
sockaddr_in addr;
int addrLen = sizeof(addr);
if (getpeername(sock, (sockaddr*)&addr, &addrLen) == 0)
{
char ipBuf[64] = {};
if (inet_ntop(AF_INET, &addr.sin_addr, ipBuf, sizeof(ipBuf)))
{
connectionIp = ipBuf;
connectionPort = (int)ntohs(addr.sin_port);
}
}
}
if (connectionIp.empty()) {
disconnect(DisconnectPacket::eDisconnect_EndOfStream);
return;
}
}
else {
SOCKET sock = WinsockNetLayer::GetSocketForSmallId(smallId);
if (sock != INVALID_SOCKET)
{
sockaddr_in addr;
int addrLen = sizeof(addr);
if (getpeername(sock, (sockaddr*)&addr, &addrLen) == 0)
connectionPort = (int)ntohs(addr.sin_port);
}
}
if (FourKitBridge::FirePlayerLogin(packet->userName, connectionIp, connectionPort, 1, &packet->m_onlineXuid, &packet->m_offlineXuid)) {
disconnect(DisconnectPacket::eDisconnect_EndOfStream);
return;
}
}
#endif
// Use the same Xuid choice as handleAcceptedLogin (offline first, online fallback).
//
PlayerUID loginXuid = packet->m_offlineXuid;
@@ -395,6 +501,62 @@ void PendingConnection::handleAcceptedLogin(shared_ptr<LoginPacket> packet)
return;
}
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
{
std::string connectionIp = "";
int connectionPort = 0;
if (!connection || !connection->getSocket()) {
disconnect(DisconnectPacket::eDisconnect_EndOfStream);
return;
}
unsigned char smallId = connection->getSocket()->getSmallId();
if (smallId == 0) {
disconnect(DisconnectPacket::eDisconnect_EndOfStream);
return;
}
if (!ServerRuntime::ServerLogManager::TryGetConnectionRemoteIp(smallId, &connectionIp))
{
SOCKET sock = WinsockNetLayer::GetSocketForSmallId(smallId);
if (sock != INVALID_SOCKET)
{
sockaddr_in addr;
int addrLen = sizeof(addr);
if (getpeername(sock, (sockaddr*)&addr, &addrLen) == 0)
{
char ipBuf[64] = {};
if (inet_ntop(AF_INET, &addr.sin_addr, ipBuf, sizeof(ipBuf)))
{
connectionIp = ipBuf;
connectionPort = (int)ntohs(addr.sin_port);
}
}
}
if (connectionIp.empty()) {
disconnect(DisconnectPacket::eDisconnect_EndOfStream);
return;
}
}
else {
SOCKET sock = WinsockNetLayer::GetSocketForSmallId(smallId);
if (sock != INVALID_SOCKET)
{
sockaddr_in addr;
int addrLen = sizeof(addr);
if (getpeername(sock, (sockaddr*)&addr, &addrLen) == 0)
connectionPort = (int)ntohs(addr.sin_port);
}
}
if (FourKitBridge::FirePlayerLogin(packet->userName, connectionIp, connectionPort, 2, &packet->m_onlineXuid, &packet->m_offlineXuid)) {
disconnect(DisconnectPacket::eDisconnect_EndOfStream);
return;
}
}
#endif
// Guests use the online xuid, everyone else uses the offline one
PlayerUID playerXuid = packet->m_offlineXuid;
if(playerXuid == INVALID_XUID) playerXuid = packet->m_onlineXuid;

View File

@@ -41,6 +41,7 @@
#include "..\Minecraft.Server\Security\IdentityTokenManager.h"
#include "..\Minecraft.Server\Security\SecurityConfig.h"
#include "..\Minecraft.Server\Security\ConnectionCipher.h"
#include "..\Minecraft.Server\FourKitBridge.h"
extern bool g_Win64DedicatedServer;
#endif
@@ -68,6 +69,7 @@ PlayerConnection::PlayerConnection(MinecraftServer *server, Connection *connecti
aboveGroundTickCount = 0;
xLastOk = yLastOk = zLastOk = 0;
synched = true;
hasDoneFirstTickFourKit = false;
didTick = false;
lastKeepAliveId = 0;
lastKeepAliveTime = 0;
@@ -122,6 +124,14 @@ unsigned char PlayerConnection::getLogSmallId()
void PlayerConnection::tick()
{
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
if (!hasDoneFirstTickFourKit)
{
FourKitBridge::FirePlayerJoin(player->entityId, player->name, player->getUUID(), (unsigned long long)player->getOnlineXuid(), (unsigned long long)player->getXuid());
hasDoneFirstTickFourKit = true;
}
#endif
if( done ) return;
if( m_bCloseOnTick )
@@ -170,12 +180,27 @@ void PlayerConnection::disconnect(DisconnectPacket::eDisconnectReason reason)
return;
}
std::wstring kickLeaveMessage;
bool fourKitHandledQuit = false;
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
if (reason != DisconnectPacket::eDisconnect_Closed &&
reason != DisconnectPacket::eDisconnect_ConnectionCreationFailed &&
reason != DisconnectPacket::eDisconnect_Quitting)
{
if (FourKitBridge::FirePlayerKick(player->entityId, (int)reason, kickLeaveMessage))
{
m_bWasKicked.store(false);
LeaveCriticalSection(&done_cs);
return;
}
}
ServerRuntime::ServerLogManager::OnPlayerDisconnected(
getLogSmallId(),
(player != NULL) ? player->name : std::wstring(),
reason,
true);
fourKitHandledQuit = FourKitBridge::FirePlayerQuit(player->entityId);
#endif
app.DebugPrintf("PlayerConnection disconect reason: %d\n", reason );
player->disconnect();
@@ -186,13 +211,20 @@ void PlayerConnection::disconnect(DisconnectPacket::eDisconnectReason reason)
connection->sendAndQuit();
// 4J-PB - removed, since it needs to be localised in the language the client is in
//server->players->broadcastAll( shared_ptr<ChatPacket>( new ChatPacket(L"<22>e" + player->name + L" left the game.") ) );
if(getWasKicked())
if (!kickLeaveMessage.empty())
{
server->getPlayers()->broadcastAll(std::make_shared<ChatPacket>(player->name, ChatPacket::e_ChatPlayerKickedFromGame));
server->getPlayers()->broadcastAll(std::make_shared<ChatPacket>(kickLeaveMessage));
}
else
else if (!fourKitHandledQuit)
{
server->getPlayers()->broadcastAll(std::make_shared<ChatPacket>(player->name, ChatPacket::e_ChatPlayerLeftGame));
if(getWasKicked())
{
server->getPlayers()->broadcastAll(std::make_shared<ChatPacket>(player->name, ChatPacket::e_ChatPlayerKickedFromGame));
}
else
{
server->getPlayers()->broadcastAll(std::make_shared<ChatPacket>(player->name, ChatPacket::e_ChatPlayerLeftGame));
}
}
server->getPlayers()->remove(player);
@@ -281,6 +313,8 @@ void PlayerConnection::handleMovePlayer(shared_ptr<MovePlayerPacket> packet)
float yRotT = player->yRot;
float xRotT = player->xRot;
const float yRotOld = yRotT;
const float xRotOld = xRotT;
if (packet->hasPos && packet->y == -999 && packet->yView == -999)
{
@@ -344,6 +378,31 @@ void PlayerConnection::handleMovePlayer(shared_ptr<MovePlayerPacket> packet)
return;
}
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
if (xLastOk != xt || yLastOk != yt || zLastOk != zt || yRotT != yRotOld || xRotT != xRotOld)
{
double moveToX, moveToY, moveToZ;
bool cancelled = FourKitBridge::FirePlayerMove(player->entityId,
xLastOk, yLastOk, zLastOk,
xt, yt, zt,
&moveToX, &moveToY, &moveToZ);
if (cancelled)
{
teleport(xLastOk, yLastOk, zLastOk, yRotT, xRotT);
return;
}
if (moveToX != xt || moveToY != yt || moveToZ != zt)
{
xt = moveToX;
yt = moveToY;
zt = moveToZ;
xDist = xt - player->x;
yDist = yt - player->y;
zDist = zt - player->z;
}
}
#endif
float r = 1 / 16.0f;
bool oldOk = level->getCubes(player, player->bb->copy()->shrink(r, r, r))->empty();
@@ -450,11 +509,55 @@ void PlayerConnection::handlePlayerAction(shared_ptr<PlayerActionPacket> packet)
if (packet->action == PlayerActionPacket::DROP_ITEM)
{
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
{
shared_ptr<ItemInstance> selected = player->inventory->getSelected();
if (selected != nullptr && selected->count > 0)
{
int outId = selected->id, outCount = 1, outAux = selected->getAuxValue();
bool cancelled = FourKitBridge::FirePlayerDropItem(
player->entityId, selected->id, 1, selected->getAuxValue(),
&outId, &outCount, &outAux);
if (cancelled)
return;
player->inventory->removeItem(player->inventory->selected, 1);
shared_ptr<ItemInstance> dropItem = (outId == selected->id)
? selected->copy()
: std::make_shared<ItemInstance>(outId, outCount, outAux);
dropItem->count = outCount;
if (outAux != selected->getAuxValue()) dropItem->setAuxValue(outAux);
player->drop(dropItem);
return;
}
}
#endif
player->drop(false);
return;
}
else if (packet->action == PlayerActionPacket::DROP_ALL_ITEMS)
{
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
{
shared_ptr<ItemInstance> selected = player->inventory->getSelected();
if (selected != nullptr && selected->count > 0)
{
int outId = selected->id, outCount = selected->count, outAux = selected->getAuxValue();
bool cancelled = FourKitBridge::FirePlayerDropItem(
player->entityId, selected->id, selected->count, selected->getAuxValue(),
&outId, &outCount, &outAux);
if (cancelled)
return;
player->inventory->removeItem(player->inventory->selected, selected->count);
shared_ptr<ItemInstance> dropItem = (outId == selected->id)
? selected->copy()
: std::make_shared<ItemInstance>(outId, outCount, outAux);
dropItem->count = outCount;
if (outAux != selected->getAuxValue()) dropItem->setAuxValue(outAux);
player->drop(dropItem);
return;
}
}
#endif
player->drop(true);
return;
}
@@ -492,6 +595,23 @@ void PlayerConnection::handlePlayerAction(shared_ptr<PlayerActionPacket> packet)
if (packet->action == PlayerActionPacket::START_DESTROY_BLOCK)
{
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
lastLeftClickTick = tickCount;
{
shared_ptr<ItemInstance> heldItem = player->inventory->getSelected();
int iId = heldItem ? heldItem->id : 0;
int iCount = heldItem ? heldItem->count : 0;
int iAux = heldItem ? heldItem->getAuxValue() : 0;
int useItemInHand = 1;
bool cancelled = FourKitBridge::FirePlayerInteract(
player->entityId, 1 /* LEFT_CLICK_BLOCK */,
iId, iCount, iAux,
x, y, z, packet->face, player->dimension,
&useItemInHand);
if (cancelled)
return;
}
#endif
// Anti-cheat: validate spawn protection on the server for mining start.
if (!server->isUnderSpawnProtection(level, x, y, z, player)) player->gameMode->startDestroyBlock(x, y, z, packet->face);
else player->connection->send(std::make_shared<TileUpdatePacket>(x, y, z, level));
@@ -499,9 +619,9 @@ void PlayerConnection::handlePlayerAction(shared_ptr<PlayerActionPacket> packet)
}
else if (packet->action == PlayerActionPacket::STOP_DESTROY_BLOCK)
{
bool destroyed = player->gameMode->stopDestroyBlock(x, y, z);
player->gameMode->stopDestroyBlock(x, y, z);
server->getPlayers()->prioritiseTileChanges(x, y, z, level->dimension->id); // 4J added - make sure that the update packets for this get prioritised over other general world updates
if (!destroyed && level->getTile(x, y, z) != 0) player->connection->send(std::make_shared<TileUpdatePacket>(x, y, z, level));
if (level->getTile(x, y, z) != 0) player->connection->send(std::make_shared<TileUpdatePacket>(x, y, z, level));
}
else if (packet->action == PlayerActionPacket::ABORT_DESTROY_BLOCK)
{
@@ -524,19 +644,128 @@ void PlayerConnection::handleUseItem(shared_ptr<UseItemPacket> packet)
if (packet->getFace() == 255)
{
if (item == nullptr) return;
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
{
int iId = item->id;
int iCount = item->count;
int iAux = item->getAuxValue();
int useItemInHand = 1;
bool cancelled = FourKitBridge::FirePlayerInteract(
player->entityId, 2,
iId, iCount, iAux,
0, 0, 0, 6, player->dimension,
&useItemInHand);
if (cancelled || !useItemInHand)
return;
}
#endif
player->gameMode->useItem(player, level, item);
}
else if ((packet->getY() < server->getMaxBuildHeight() - 1) || (packet->getFace() != Facing::UP && packet->getY() < server->getMaxBuildHeight()))
{
if (synched && player->distanceToSqr(x + 0.5, y + 0.5, z + 0.5) < 8 * 8)
{
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
{
int iId = item ? item->id : 0;
int iCount = item ? item->count : 0;
int iAux = item ? item->getAuxValue() : 0;
int useItemInHand = 1;
bool cancelled = FourKitBridge::FirePlayerInteract(
player->entityId, 3 /* RIGHT_CLICK_BLOCK */,
iId, iCount, iAux,
x, y, z, face, player->dimension,
&useItemInHand);
if (cancelled || !useItemInHand)
{
informClient = true;
goto skipUseItemOn;
}
}
#endif
// Anti-cheat: block placement/use must pass server-side spawn protection.
if (!server->isUnderSpawnProtection(level, x, y, z, player))
{
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
int placeX = x, placeY = y, placeZ = z;
bool validFace = true;
if (face == 0) placeY--;
else if (face == 1) placeY++;
else if (face == 2) placeZ--;
else if (face == 3) placeZ++;
else if (face == 4) placeX--;
else if (face == 5) placeX++;
else validFace = false;
int oldTileId = validFace ? level->getTile(placeX, placeY, placeZ) : 0;
int oldTileData = validFace ? level->getData(placeX, placeY, placeZ) : 0;
int oldClickedId = level->getTile(x, y, z);
int oldClickedData = level->getData(x, y, z);
int savedItemId = item ? item->id : 0;
int savedItemCount = item ? item->count : 0;
#endif
player->gameMode->useItemOn(player, level, item, x, y, z, face, packet->getClickX(), packet->getClickY(), packet->getClickZ());
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
if (validFace)
{
int newTileId = level->getTile(placeX, placeY, placeZ);
int newClickedId = level->getTile(x, y, z);
int fireX = placeX, fireY = placeY, fireZ = placeZ;
int againstX = x, againstY = y, againstZ = z;
int revertId = oldTileId, revertData = oldTileData;
bool shouldFire = false;
if (newTileId != 0 && newTileId != oldTileId)
{
shouldFire = true;
}
else if (newClickedId != 0 && newClickedId != oldClickedId)
{
shouldFire = true;
fireX = x; fireY = y; fireZ = z;
revertId = oldClickedId; revertData = oldClickedData;
againstX = x; againstY = y; againstZ = z;
if (face == 0) againstY++;
else if (face == 1) againstY--;
else if (face == 2) againstZ++;
else if (face == 3) againstZ--;
else if (face == 4) againstX++;
else if (face == 5) againstX--;
}
if (shouldFire)
{
bool cancelled = FourKitBridge::FireBlockPlace(
player->entityId, player->dimension,
fireX, fireY, fireZ,
againstX, againstY, againstZ,
savedItemId, savedItemCount, true);
if (cancelled)
{
level->setTileAndData(fireX, fireY, fireZ, revertId, revertData, Tile::UPDATE_ALL);
auto &slot = player->inventory->items[player->inventory->selected];
if (slot != nullptr)
{
slot->count = savedItemCount;
}
else if (savedItemId > 0 && savedItemId < 256 && Tile::tiles[savedItemId] != nullptr && savedItemCount > 0)
{
slot = std::make_shared<ItemInstance>(Tile::tiles[savedItemId], savedItemCount);
}
informClient = true;
}
}
}
#endif
}
}
skipUseItemOn:
informClient = true;
}
else
@@ -605,23 +834,28 @@ void PlayerConnection::onDisconnect(DisconnectPacket::eDisconnectReason reason,
LeaveCriticalSection(&done_cs);
return;
}
bool fourKitHandledQuit = false;
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
ServerRuntime::ServerLogManager::OnPlayerDisconnected(
getLogSmallId(),
(player != NULL) ? player->name : std::wstring(),
reason,
false);
fourKitHandledQuit = FourKitBridge::FirePlayerQuit(player->entityId);
#endif
// logger.info(player.name + " lost connection: " + reason);
// 4J-PB - removed, since it needs to be localised in the language the client is in
//server->players->broadcastAll( shared_ptr<ChatPacket>( new ChatPacket(L"<22>e" + player->name + L" left the game.") ) );
if(getWasKicked())
if (!fourKitHandledQuit)
{
server->getPlayers()->broadcastAll(std::make_shared<ChatPacket>(player->name, ChatPacket::e_ChatPlayerKickedFromGame));
}
else
{
server->getPlayers()->broadcastAll(std::make_shared<ChatPacket>(player->name, ChatPacket::e_ChatPlayerLeftGame));
if(getWasKicked())
{
server->getPlayers()->broadcastAll(std::make_shared<ChatPacket>(player->name, ChatPacket::e_ChatPlayerKickedFromGame));
}
else
{
server->getPlayers()->broadcastAll(std::make_shared<ChatPacket>(player->name, ChatPacket::e_ChatPlayerLeftGame));
}
}
server->getPlayers()->remove(player);
done = true;
@@ -744,8 +978,20 @@ void PlayerConnection::handleChat(shared_ptr<ChatPacket> packet)
handleCommand(message);
return;
}
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
{
std::wstring formatted;
if (!FourKitBridge::FirePlayerChat(player->entityId, message, formatted))
{
if (formatted.empty())
formatted = L"<" + player->name + L"> " + message;
server->getPlayers()->broadcastAll(std::make_shared<ChatPacket>(formatted));
}
}
#else
wstring formatted = L"<" + player->name + L"> " + message;
server->getPlayers()->broadcastAll(shared_ptr<ChatPacket>(new ChatPacket(formatted)));
#endif
chatSpamTickCount += SharedConstants::TICKS_PER_SECOND;
if (chatSpamTickCount > SharedConstants::TICKS_PER_SECOND * 10)
{
@@ -755,6 +1001,13 @@ void PlayerConnection::handleChat(shared_ptr<ChatPacket> packet)
void PlayerConnection::handleCommand(const wstring& message)
{
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
std::wstring commandLine = message;
if (FourKitBridge::FireCommandPreprocess(player->entityId, commandLine, commandLine))
return;
if (FourKitBridge::HandlePlayerCommand(player->entityId, commandLine))
return;
#endif
// 4J - TODO
#if 0
server.getCommandDispatcher().performCommand(player, message);
@@ -766,6 +1019,23 @@ void PlayerConnection::handleAnimate(shared_ptr<AnimatePacket> packet)
player->resetLastActionTime();
if (packet->action == AnimatePacket::SWING)
{
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
if (lastLeftClickTick != tickCount)
{
int useItemInHand = 1;
auto item = player->inventory->getSelected();
int iId = item ? item->id : 0;
int iCount = item ? item->count : 0;
int iAux = item ? item->getAuxValue() : 0;
bool cancelled = FourKitBridge::FirePlayerInteract(
player->entityId, 0,
iId, iCount, iAux,
0, 0, 0, 6, player->dimension,
&useItemInHand);
if (cancelled)
return;
}
#endif
player->swing();
}
}
@@ -876,10 +1146,31 @@ void PlayerConnection::handleInteract(shared_ptr<InteractPacket> packet)
if (packet->action == InteractPacket::INTERACT)
{
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
{
int mappedType = FourKitBridge::MapEntityType((int)target->GetType());
float targetHealth = 0, targetMaxHealth = 0, targetEyeHeight = 0;
auto living = dynamic_pointer_cast<LivingEntity>(target);
if (living)
{
targetHealth = living->getHealth();
targetMaxHealth = living->getMaxHealth();
targetEyeHeight = living->getHeadHeight();
}
if (FourKitBridge::FirePlayerInteractEntity(
player->entityId, target->entityId, mappedType,
player->dimension, target->x, target->y, target->z,
targetHealth, targetMaxHealth, targetEyeHeight))
return;
}
#endif
player->interact(target);
}
else if (packet->action == InteractPacket::ATTACK)
{
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
lastLeftClickTick = tickCount;
#endif
if ((target->GetType() == eTYPE_ITEMENTITY) || (target->GetType() == eTYPE_EXPERIENCEORB) || (target->GetType() == eTYPE_ARROW) || target == player)
{
return;
@@ -1247,7 +1538,13 @@ void PlayerConnection::handleClientCommand(shared_ptr<ClientCommandPacket> packe
{
if (player->wonGame)
{
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
int oldEntityId = player->entityId;
#endif
player = server->getPlayers()->respawn(player, player->m_enteredEndExitPortal?0:player->dimension, true);
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
FourKitBridge::UpdatePlayerEntityId(oldEntityId, player->entityId);
#endif
}
else if (player->level->getLevelData()->isHardcore())
{
@@ -1258,7 +1555,13 @@ void PlayerConnection::handleClientCommand(shared_ptr<ClientCommandPacket> packe
else
{
if (player->getHealth() > 0) return;
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
int oldEntityId = player->entityId;
#endif
player = server->getPlayers()->respawn(player, 0, false);
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
FourKitBridge::UpdatePlayerEntityId(oldEntityId, player->entityId);
#endif
}
}
}
@@ -1318,6 +1621,20 @@ void PlayerConnection::handleContainerClick(shared_ptr<ContainerClickPacket> pac
player->resetLastActionTime();
if (player->containerMenu->containerId == packet->containerId && player->containerMenu->isSynched(player))
{
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
int fourKitClickResult = FourKitBridge::FireInventoryClick(player->entityId, packet->slotNum, packet->buttonNum, packet->clickType);
if (fourKitClickResult == 1)
{
expectedAcks[player->containerMenu->containerId] = packet->uid;
player->connection->send(std::make_shared<ContainerAckPacket>(packet->containerId, packet->uid, false));
player->containerMenu->setSynched(player, false);
vector<shared_ptr<ItemInstance>> items;
for (unsigned int i = 0; i < player->containerMenu->slots.size(); i++)
items.push_back(player->containerMenu->slots.at(i)->getItem());
player->refreshContainer(player->containerMenu, &items);
return;
}
#endif
shared_ptr<ItemInstance> clicked = player->containerMenu->clicked(packet->slotNum, packet->buttonNum, packet->clickType, player);
if (ItemInstance::matches(packet->item, clicked))
@@ -1345,6 +1662,15 @@ void PlayerConnection::handleContainerClick(shared_ptr<ContainerClickPacket> pac
// player.containerMenu.broadcastChanges();
}
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
if (fourKitClickResult == 2)
{
vector<shared_ptr<ItemInstance>> refreshItems;
for (unsigned int i = 0; i < player->containerMenu->slots.size(); i++)
refreshItems.push_back(player->containerMenu->slots.at(i)->getItem());
player->refreshContainer(player->containerMenu, &refreshItems);
}
#endif
}
}
@@ -1482,10 +1808,31 @@ void PlayerConnection::handleSignUpdate(shared_ptr<SignUpdatePacket> packet)
int y = packet->y;
int z = packet->z;
shared_ptr<SignTileEntity> ste = dynamic_pointer_cast<SignTileEntity>(te);
wstring lines[4];
for (int i = 0; i < 4; i++)
{
wstring lineText = packet->lines[i].substr(0,15);
ste->SetMessage( i, lineText );
lines[i] = packet->lines[i].substr(0,15);
}
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
{
std::wstring outLines[4];
bool cancelled = FourKitBridge::FireSignChange(
player->entityId, player->dimension,
x, y, z,
lines[0], lines[1], lines[2], lines[3],
outLines);
if (cancelled)
return;
for (int i = 0; i < 4; i++)
lines[i] = outLines[i];
}
#endif
for (int i = 0; i < 4; i++)
{
ste->SetMessage( i, lines[i] );
}
ste->SetVerified(false);
ste->setChanged();

View File

@@ -30,6 +30,8 @@ private:
int aboveGroundTickCount;
bool didTick;
bool hasDoneFirstTickFourKit;
int lastLeftClickTick = 0;
int lastKeepAliveId;
int64_t lastKeepAliveTime;
static Random random;

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;
@@ -324,6 +327,8 @@ void ServerLevel::updateSleepingPlayerList()
for (auto& player : players)
{
if (player->fk_sleepingIgnored)
continue;
if (!player->isSleeping())
{
allPlayersSleeping = false;
@@ -368,6 +373,8 @@ bool ServerLevel::allPlayersAreSleeping()
// all players are sleeping, but have they slept long enough?
for (auto& player : players)
{
if (player->fk_sleepingIgnored)
continue;
// System.out.println(player->entityId + ": " + player->getSleepTimer());
if (!player->isSleepingLongEnough())
{
@@ -507,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

@@ -33,6 +33,154 @@
#include "..\Minecraft.World\LevelChunk.h"
#include "LevelRenderer.h"
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
#include "..\Minecraft.Server\FourKitBridge.h"
#include "..\Minecraft.World\ChatPacket.h"
static std::wstring FormatDeathMessage(const shared_ptr<ChatPacket>& packet)
{
if (!packet) return L"";
std::wstring message;
bool replacePlayer = false;
bool replaceEntitySource = false;
bool replaceItem = false;
// coug chough
// de hättn echt an gscheidern string konverter für de todesmeldungen macha soin
// a globaler helfer waar wahrscheinlich ganz bärig gwen
// waaaah
switch (packet->m_messageType)
{
case ChatPacket::e_ChatCustom:
return packet->m_stringArgs.size() > 0 ? packet->m_stringArgs[0] : L"";
case ChatPacket::e_ChatDeathInFire:
message = app.GetString(IDS_DEATH_INFIRE); replacePlayer = true; break;
case ChatPacket::e_ChatDeathOnFire:
message = app.GetString(IDS_DEATH_ONFIRE); replacePlayer = true; break;
case ChatPacket::e_ChatDeathLava:
message = app.GetString(IDS_DEATH_LAVA); replacePlayer = true; break;
case ChatPacket::e_ChatDeathInWall:
message = app.GetString(IDS_DEATH_INWALL); replacePlayer = true; break;
case ChatPacket::e_ChatDeathDrown:
message = app.GetString(IDS_DEATH_DROWN); replacePlayer = true; break;
case ChatPacket::e_ChatDeathStarve:
message = app.GetString(IDS_DEATH_STARVE); replacePlayer = true; break;
case ChatPacket::e_ChatDeathCactus:
message = app.GetString(IDS_DEATH_CACTUS); replacePlayer = true; break;
case ChatPacket::e_ChatDeathFall:
message = app.GetString(IDS_DEATH_FALL); replacePlayer = true; break;
case ChatPacket::e_ChatDeathOutOfWorld:
message = app.GetString(IDS_DEATH_OUTOFWORLD); replacePlayer = true; break;
case ChatPacket::e_ChatDeathGeneric:
message = app.GetString(IDS_DEATH_GENERIC); replacePlayer = true; break;
case ChatPacket::e_ChatDeathExplosion:
message = app.GetString(IDS_DEATH_EXPLOSION); replacePlayer = true; break;
case ChatPacket::e_ChatDeathMagic:
message = app.GetString(IDS_DEATH_MAGIC); replacePlayer = true; break;
case ChatPacket::e_ChatDeathWither:
message = app.GetString(IDS_DEATH_WITHER); replacePlayer = true; break;
case ChatPacket::e_ChatDeathDragonBreath:
message = app.GetString(IDS_DEATH_DRAGON_BREATH); replacePlayer = true; break;
case ChatPacket::e_ChatDeathAnvil:
message = app.GetString(IDS_DEATH_FALLING_ANVIL); replacePlayer = true; break;
case ChatPacket::e_ChatDeathFallingBlock:
message = app.GetString(IDS_DEATH_FALLING_TILE); replacePlayer = true; break;
case ChatPacket::e_ChatDeathFellAccidentLadder:
message = app.GetString(IDS_DEATH_FELL_ACCIDENT_LADDER); replacePlayer = true; break;
case ChatPacket::e_ChatDeathFellAccidentVines:
message = app.GetString(IDS_DEATH_FELL_ACCIDENT_VINES); replacePlayer = true; break;
case ChatPacket::e_ChatDeathFellAccidentWater:
message = app.GetString(IDS_DEATH_FELL_ACCIDENT_WATER); replacePlayer = true; break;
case ChatPacket::e_ChatDeathFellAccidentGeneric:
message = app.GetString(IDS_DEATH_FELL_ACCIDENT_GENERIC); replacePlayer = true; break;
case ChatPacket::e_ChatDeathFellKiller:
message = app.GetString(IDS_DEATH_FALL); replacePlayer = true; break;
case ChatPacket::e_ChatDeathMob:
message = app.GetString(IDS_DEATH_MOB); replacePlayer = true; replaceEntitySource = true; break;
case ChatPacket::e_ChatDeathPlayer:
message = app.GetString(IDS_DEATH_PLAYER); replacePlayer = true; replaceEntitySource = true; break;
case ChatPacket::e_ChatDeathArrow:
message = app.GetString(IDS_DEATH_ARROW); replacePlayer = true; replaceEntitySource = true; break;
case ChatPacket::e_ChatDeathFireball:
message = app.GetString(IDS_DEATH_FIREBALL); replacePlayer = true; replaceEntitySource = true; break;
case ChatPacket::e_ChatDeathThrown:
message = app.GetString(IDS_DEATH_THROWN); replacePlayer = true; replaceEntitySource = true; break;
case ChatPacket::e_ChatDeathIndirectMagic:
message = app.GetString(IDS_DEATH_INDIRECT_MAGIC); replacePlayer = true; replaceEntitySource = true; break;
case ChatPacket::e_ChatDeathThorns:
message = app.GetString(IDS_DEATH_THORNS); replacePlayer = true; replaceEntitySource = true; break;
case ChatPacket::e_ChatDeathExplosionPlayer:
message = app.GetString(IDS_DEATH_EXPLOSION_PLAYER); replacePlayer = true; replaceEntitySource = true; break;
case ChatPacket::e_ChatDeathInFirePlayer:
message = app.GetString(IDS_DEATH_INFIRE_PLAYER); replacePlayer = true; replaceEntitySource = true; break;
case ChatPacket::e_ChatDeathOnFirePlayer:
message = app.GetString(IDS_DEATH_ONFIRE_PLAYER); replacePlayer = true; replaceEntitySource = true; break;
case ChatPacket::e_ChatDeathLavaPlayer:
message = app.GetString(IDS_DEATH_LAVA_PLAYER); replacePlayer = true; replaceEntitySource = true; break;
case ChatPacket::e_ChatDeathDrownPlayer:
message = app.GetString(IDS_DEATH_DROWN_PLAYER); replacePlayer = true; replaceEntitySource = true; break;
case ChatPacket::e_ChatDeathCactusPlayer:
message = app.GetString(IDS_DEATH_CACTUS_PLAYER); replacePlayer = true; replaceEntitySource = true; break;
case ChatPacket::e_ChatDeathFellAssist:
message = app.GetString(IDS_DEATH_FELL_ASSIST); replacePlayer = true; replaceEntitySource = true; break;
case ChatPacket::e_ChatDeathFellFinish:
message = app.GetString(IDS_DEATH_FELL_FINISH); replacePlayer = true; replaceEntitySource = true; break;
case ChatPacket::e_ChatDeathPlayerItem:
message = app.GetString(IDS_DEATH_PLAYER_ITEM); replacePlayer = true; replaceEntitySource = true; replaceItem = true; break;
case ChatPacket::e_ChatDeathArrowItem:
message = app.GetString(IDS_DEATH_ARROW_ITEM); replacePlayer = true; replaceEntitySource = true; replaceItem = true; break;
case ChatPacket::e_ChatDeathFireballItem:
message = app.GetString(IDS_DEATH_FIREBALL_ITEM); replacePlayer = true; replaceEntitySource = true; replaceItem = true; break;
case ChatPacket::e_ChatDeathThrownItem:
message = app.GetString(IDS_DEATH_THROWN_ITEM); replacePlayer = true; replaceEntitySource = true; replaceItem = true; break;
case ChatPacket::e_ChatDeathIndirectMagicItem:
message = app.GetString(IDS_DEATH_INDIRECT_MAGIC_ITEM); replacePlayer = true; replaceEntitySource = true; replaceItem = true; break;
case ChatPacket::e_ChatDeathFellAssistItem:
message = app.GetString(IDS_DEATH_FELL_ASSIST_ITEM); replacePlayer = true; replaceEntitySource = true; replaceItem = true; break;
case ChatPacket::e_ChatDeathFellFinishItem:
message = app.GetString(IDS_DEATH_FELL_FINISH_ITEM); replacePlayer = true; replaceEntitySource = true; replaceItem = true; break;
default:
message = app.GetString(IDS_DEATH_GENERIC); replacePlayer = true; break;
}
if (replacePlayer)
{
std::wstring playerName = packet->m_stringArgs.size() > 0 ? packet->m_stringArgs[0] : L"";
message = replaceAll(message, L"{*PLAYER*}", playerName);
}
if (replaceEntitySource)
{
std::wstring sourceName;
if (!packet->m_intArgs.empty() && packet->m_intArgs[0] == eTYPE_SERVERPLAYER)
{
sourceName = packet->m_stringArgs.size() > 1 ? packet->m_stringArgs[1] : L"";
}
else
{
if (packet->m_stringArgs.size() > 1 && !packet->m_stringArgs[1].empty())
{
sourceName = packet->m_stringArgs[1];
}
else if (!packet->m_intArgs.empty())
{
sourceName = app.getEntityName((eINSTANCEOF)packet->m_intArgs[0]);
}
}
message = replaceAll(message, L"{*SOURCE*}", sourceName);
}
if (replaceItem)
{
std::wstring itemName = packet->m_stringArgs.size() > 2 ? packet->m_stringArgs[2] : L"";
message = replaceAll(message, L"{*ITEM*}", itemName);
}
return message;
}
#endif
ServerPlayer::ServerPlayer(MinecraftServer *server, Level *level, const wstring& name, ServerPlayerGameMode *gameMode) : Player(level, name)
{
@@ -567,6 +715,45 @@ shared_ptr<ItemInstance> ServerPlayer::getCarried(int slot)
void ServerPlayer::die(DamageSource *source)
{
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
shared_ptr<ChatPacket> deathPacket = getCombatTracker()->getDeathMessagePacket();
std::wstring deathMsg = FormatDeathMessage(deathPacket);
int exp = getExperienceReward(nullptr);
std::wstring outDeathMsg;
int keepInventory = 0;
int outNewExp = 0, outNewLevel = 0, outKeepLevel = 0;
int outExp = FourKitBridge::FirePlayerDeath(entityId, deathMsg, exp, outDeathMsg, &keepInventory,
&outNewExp, &outNewLevel, &outKeepLevel);
fk_hasDeathState = true;
fk_deathKeepInventory = (keepInventory != 0);
fk_deathKeepLevel = (outKeepLevel != 0);
fk_deathNewExp = outNewExp;
fk_deathNewLevel = outNewLevel;
if (!outDeathMsg.empty())
server->getPlayers()->broadcastAll(std::make_shared<ChatPacket>(outDeathMsg));
// LCE-Revelations: Hardcore mode enforcement. Donor's FourKit-aware die()
// rewrite dropped this branch; we restore it AFTER FirePlayerDeath so
// plugins still see the death event before the player is banned and
// switched to Adventure mode.
if (level->getLevelData()->isHardcore())
{
setGameMode(GameType::ADVENTURE);
// Ban this player's XUID and queue disconnect.
// The force-save is triggered inside banPlayerForHardcoreDeath after
// the disconnect is queued, so the client doesn't get stuck on a save
// screen.
server->getPlayers()->banPlayerForHardcoreDeath(this);
}
if (keepInventory == 0 && !level->getGameRules()->getBoolean(GameRules::RULE_KEEPINVENTORY))
{
inventory->dropAll();
}
#else
server->getPlayers()->broadcastAll(getCombatTracker()->getDeathMessagePacket());
// 4J Added: Hardcore mode — switch to Adventure mode on death (can look but not break/place blocks)
@@ -584,6 +771,7 @@ void ServerPlayer::die(DamageSource *source)
{
inventory->dropAll();
}
#endif
vector<Objective *> *objectives = level->getScoreboard()->findObjectiveFor(ObjectiveCriteria::DEATH_COUNT);
if(objectives)
@@ -776,6 +964,10 @@ void ServerPlayer::changeDimension(int i)
}
else
{
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
bool portalDestModified = false;
double portalOutX = 0, portalOutY = 0, portalOutZ = 0;
#endif
if (dimension == 0 && i == 1)
{
awardStat(GenericStats::theEnd(), GenericStats::param_theEnd());
@@ -783,7 +975,24 @@ void ServerPlayer::changeDimension(int i)
Pos *pos = server->getLevel(i)->getDimensionSpecificSpawn();
if (pos != nullptr)
{
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
{
double outX, outY, outZ;
bool cancelled = FourKitBridge::FirePlayerPortal(entityId,
x, y, z, dimension,
pos->x, pos->y, pos->z, i,
4,
&outX, &outY, &outZ);
if (cancelled)
{
delete pos;
return;
}
connection->teleport(outX, outY, outZ, 0, 0);
}
#else
connection->teleport(pos->x, pos->y, pos->z, 0, 0);
#endif
delete pos;
}
@@ -791,10 +1000,48 @@ void ServerPlayer::changeDimension(int i)
}
else
{
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
{
double scale = server->getLevel(i)->getLevelData()->getHellScale();
double toX = x, toY = y, toZ = z;
if (i == -1)
{
toX = x / scale;
toZ = z / scale;
}
else if (dimension == -1 && i == 0)
{
toX = x * scale;
toZ = z * scale;
}
double outX, outY, outZ;
bool cancelled = FourKitBridge::FirePlayerPortal(entityId,
x, y, z, dimension,
toX, toY, toZ, i,
3,
&outX, &outY, &outZ);
if (cancelled)
return;
if (outX != toX || outY != toY || outZ != toZ)
{
portalDestModified = true;
portalOutX = outX;
portalOutY = outY;
portalOutZ = outZ;
}
}
#endif
// 4J: Removed on the advice of the mighty King of Achievments (JV)
// awardStat(GenericStats::portal(), GenericStats::param_portal());
}
server->getPlayers()->toggleDimension( dynamic_pointer_cast<ServerPlayer>(shared_from_this()), i);
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
if (portalDestModified)
{
connection->teleport(portalOutX, portalOutY, portalOutZ, yRot, xRot);
}
#endif
lastSentExp = -1;
lastSentHealth = -1;
lastSentFood = -1;
@@ -824,6 +1071,10 @@ void ServerPlayer::take(shared_ptr<Entity> e, int orgCount)
Player::BedSleepingResult ServerPlayer::startSleepInBed(int x, int y, int z, bool bTestUse)
{
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
if (!bTestUse && FourKitBridge::FireBedEnter(entityId, dimension, x, y, z))
return OTHER_PROBLEM;
#endif
BedSleepingResult result = Player::startSleepInBed(x, y, z, bTestUse);
if (result == OK)
{
@@ -837,12 +1088,22 @@ Player::BedSleepingResult ServerPlayer::startSleepInBed(int x, int y, int z, boo
void ServerPlayer::stopSleepInBed(bool forcefulWakeUp, bool updateLevelList, bool saveRespawnPoint)
{
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
int bedX = bedPosition ? bedPosition->x : 0;
int bedY = bedPosition ? bedPosition->y : 0;
int bedZ = bedPosition ? bedPosition->z : 0;
bool wasSleeping = isSleeping();
#endif
if (isSleeping())
{
getLevel()->getTracker()->broadcastAndSend(shared_from_this(), std::make_shared<AnimatePacket>(shared_from_this(), AnimatePacket::WAKE_UP));
}
Player::stopSleepInBed(forcefulWakeUp, updateLevelList, saveRespawnPoint);
if (connection != nullptr) connection->teleport(x, y, z, yRot, xRot);
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
if (wasSleeping)
FourKitBridge::FireBedLeave(entityId, dimension, bedX, bedY, bedZ);
#endif
}
void ServerPlayer::ride(shared_ptr<Entity> e)
@@ -885,10 +1146,18 @@ bool ServerPlayer::startCrafting(int x, int y, int z)
if(containerMenu == inventoryMenu)
{
nextContainerCounter();
connection->send(std::make_shared<ContainerOpenPacket>(containerCounter, ContainerOpenPacket::WORKBENCH, L"", 9, false));
containerMenu = new CraftingMenu(inventory, level, x, y, z);
containerMenu->containerId = containerCounter;
containerMenu->addSlotListener(this);
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
if (FourKitBridge::FireInventoryOpen(entityId, ContainerOpenPacket::WORKBENCH, L"", 9))
{
doCloseContainer();
return true;
}
#endif
connection->send(std::make_shared<ContainerOpenPacket>(containerCounter, ContainerOpenPacket::WORKBENCH, L"", 9, false));
refreshContainer(containerMenu);
}
else
{
@@ -903,20 +1172,36 @@ bool ServerPlayer::openFireworks(int x, int y, int z)
if(containerMenu == inventoryMenu)
{
nextContainerCounter();
connection->send(std::make_shared<ContainerOpenPacket>(containerCounter, ContainerOpenPacket::FIREWORKS, L"", 9, false));
containerMenu = new FireworksMenu(inventory, level, x, y, z);
containerMenu->containerId = containerCounter;
containerMenu->addSlotListener(this);
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
if (FourKitBridge::FireInventoryOpen(entityId, ContainerOpenPacket::FIREWORKS, L"", 9))
{
doCloseContainer();
return true;
}
#endif
connection->send(std::make_shared<ContainerOpenPacket>(containerCounter, ContainerOpenPacket::FIREWORKS, L"", 9, false));
refreshContainer(containerMenu);
}
else if(dynamic_cast<CraftingMenu *>(containerMenu) != nullptr)
{
closeContainer();
nextContainerCounter();
connection->send(std::make_shared<ContainerOpenPacket>(containerCounter, ContainerOpenPacket::FIREWORKS, L"", 9, false));
containerMenu = new FireworksMenu(inventory, level, x, y, z);
containerMenu->containerId = containerCounter;
containerMenu->addSlotListener(this);
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
if (FourKitBridge::FireInventoryOpen(entityId, ContainerOpenPacket::FIREWORKS, L"", 9))
{
doCloseContainer();
return true;
}
#endif
connection->send(std::make_shared<ContainerOpenPacket>(containerCounter, ContainerOpenPacket::FIREWORKS, L"", 9, false));
refreshContainer(containerMenu);
}
else
{
@@ -931,10 +1216,18 @@ bool ServerPlayer::startEnchanting(int x, int y, int z, const wstring &name)
if(containerMenu == inventoryMenu)
{
nextContainerCounter();
connection->send(std::make_shared<ContainerOpenPacket>(containerCounter, ContainerOpenPacket::ENCHANTMENT, name.empty() ? L"" : name, 9, !name.empty()));
containerMenu = new EnchantmentMenu(inventory, level, x, y, z);
containerMenu->containerId = containerCounter;
containerMenu->addSlotListener(this);
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
if (FourKitBridge::FireInventoryOpen(entityId, ContainerOpenPacket::ENCHANTMENT, name.empty() ? L"" : name, 9))
{
doCloseContainer();
return true;
}
#endif
connection->send(std::make_shared<ContainerOpenPacket>(containerCounter, ContainerOpenPacket::ENCHANTMENT, name.empty() ? L"" : name, 9, !name.empty()));
refreshContainer(containerMenu);
}
else
{
@@ -949,10 +1242,18 @@ bool ServerPlayer::startRepairing(int x, int y, int z)
if(containerMenu == inventoryMenu)
{
nextContainerCounter();
connection->send(std::make_shared<ContainerOpenPacket>(containerCounter, ContainerOpenPacket::REPAIR_TABLE, L"", 9, false));
containerMenu = new AnvilMenu(inventory, level, x, y, z, dynamic_pointer_cast<Player>(shared_from_this()));
containerMenu->containerId = containerCounter;
containerMenu->addSlotListener(this);
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
if (FourKitBridge::FireInventoryOpen(entityId, ContainerOpenPacket::REPAIR_TABLE, L"", 9))
{
doCloseContainer();
return true;
}
#endif
connection->send(std::make_shared<ContainerOpenPacket>(containerCounter, ContainerOpenPacket::REPAIR_TABLE, L"", 9, false));
refreshContainer(containerMenu);
}
else
{
@@ -972,11 +1273,18 @@ bool ServerPlayer::openContainer(shared_ptr<Container> container)
int containerType = container->getContainerType();
assert(containerType >= 0);
connection->send(std::make_shared<ContainerOpenPacket>(containerCounter, containerType, container->getCustomName(), container->getContainerSize(), container->hasCustomName()));
containerMenu = new ContainerMenu(inventory, container);
containerMenu->containerId = containerCounter;
containerMenu->addSlotListener(this);
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
if (FourKitBridge::FireInventoryOpen(entityId, containerType, container->getCustomName(), container->getContainerSize()))
{
doCloseContainer();
return true;
}
#endif
connection->send(std::make_shared<ContainerOpenPacket>(containerCounter, containerType, container->getCustomName(), container->getContainerSize(), container->hasCustomName()));
refreshContainer(containerMenu);
}
else
{
@@ -991,10 +1299,18 @@ bool ServerPlayer::openHopper(shared_ptr<HopperTileEntity> container)
if(containerMenu == inventoryMenu)
{
nextContainerCounter();
connection->send(std::make_shared<ContainerOpenPacket>(containerCounter, ContainerOpenPacket::HOPPER, container->getCustomName(), container->getContainerSize(), container->hasCustomName()));
containerMenu = new HopperMenu(inventory, container);
containerMenu->containerId = containerCounter;
containerMenu->addSlotListener(this);
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
if (FourKitBridge::FireInventoryOpen(entityId, ContainerOpenPacket::HOPPER, container->getCustomName(), container->getContainerSize()))
{
doCloseContainer();
return true;
}
#endif
connection->send(std::make_shared<ContainerOpenPacket>(containerCounter, ContainerOpenPacket::HOPPER, container->getCustomName(), container->getContainerSize(), container->hasCustomName()));
refreshContainer(containerMenu);
}
else
{
@@ -1009,10 +1325,18 @@ bool ServerPlayer::openHopper(shared_ptr<MinecartHopper> container)
if(containerMenu == inventoryMenu)
{
nextContainerCounter();
connection->send(std::make_shared<ContainerOpenPacket>(containerCounter, ContainerOpenPacket::HOPPER, container->getCustomName(), container->getContainerSize(), container->hasCustomName()));
containerMenu = new HopperMenu(inventory, container);
containerMenu->containerId = containerCounter;
containerMenu->addSlotListener(this);
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
if (FourKitBridge::FireInventoryOpen(entityId, ContainerOpenPacket::HOPPER, container->getCustomName(), container->getContainerSize()))
{
doCloseContainer();
return true;
}
#endif
connection->send(std::make_shared<ContainerOpenPacket>(containerCounter, ContainerOpenPacket::HOPPER, container->getCustomName(), container->getContainerSize(), container->hasCustomName()));
refreshContainer(containerMenu);
}
else
{
@@ -1027,10 +1351,18 @@ bool ServerPlayer::openFurnace(shared_ptr<FurnaceTileEntity> furnace)
if(containerMenu == inventoryMenu)
{
nextContainerCounter();
connection->send(std::make_shared<ContainerOpenPacket>(containerCounter, ContainerOpenPacket::FURNACE, furnace->getCustomName(), furnace->getContainerSize(), furnace->hasCustomName()));
containerMenu = new FurnaceMenu(inventory, furnace);
containerMenu->containerId = containerCounter;
containerMenu->addSlotListener(this);
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
if (FourKitBridge::FireInventoryOpen(entityId, ContainerOpenPacket::FURNACE, furnace->getCustomName(), furnace->getContainerSize()))
{
doCloseContainer();
return true;
}
#endif
connection->send(std::make_shared<ContainerOpenPacket>(containerCounter, ContainerOpenPacket::FURNACE, furnace->getCustomName(), furnace->getContainerSize(), furnace->hasCustomName()));
refreshContainer(containerMenu);
}
else
{
@@ -1045,10 +1377,18 @@ bool ServerPlayer::openTrap(shared_ptr<DispenserTileEntity> trap)
if(containerMenu == inventoryMenu)
{
nextContainerCounter();
connection->send(std::make_shared<ContainerOpenPacket>(containerCounter, trap->GetType() == eTYPE_DROPPERTILEENTITY ? ContainerOpenPacket::DROPPER : ContainerOpenPacket::TRAP, trap->getCustomName(), trap->getContainerSize(), trap->hasCustomName()));
containerMenu = new TrapMenu(inventory, trap);
containerMenu->containerId = containerCounter;
containerMenu->addSlotListener(this);
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
if (FourKitBridge::FireInventoryOpen(entityId, trap->GetType() == eTYPE_DROPPERTILEENTITY ? ContainerOpenPacket::DROPPER : ContainerOpenPacket::TRAP, trap->getCustomName(), trap->getContainerSize()))
{
doCloseContainer();
return true;
}
#endif
connection->send(std::make_shared<ContainerOpenPacket>(containerCounter, trap->GetType() == eTYPE_DROPPERTILEENTITY ? ContainerOpenPacket::DROPPER : ContainerOpenPacket::TRAP, trap->getCustomName(), trap->getContainerSize(), trap->hasCustomName()));
refreshContainer(containerMenu);
}
else
{
@@ -1063,10 +1403,18 @@ bool ServerPlayer::openBrewingStand(shared_ptr<BrewingStandTileEntity> brewingSt
if(containerMenu == inventoryMenu)
{
nextContainerCounter();
connection->send(std::make_shared<ContainerOpenPacket>(containerCounter, ContainerOpenPacket::BREWING_STAND, brewingStand->getCustomName(), brewingStand->getContainerSize(), brewingStand->hasCustomName()));
containerMenu = new BrewingStandMenu(inventory, brewingStand);
containerMenu->containerId = containerCounter;
containerMenu->addSlotListener(this);
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
if (FourKitBridge::FireInventoryOpen(entityId, ContainerOpenPacket::BREWING_STAND, brewingStand->getCustomName(), brewingStand->getContainerSize()))
{
doCloseContainer();
return true;
}
#endif
connection->send(std::make_shared<ContainerOpenPacket>(containerCounter, ContainerOpenPacket::BREWING_STAND, brewingStand->getCustomName(), brewingStand->getContainerSize(), brewingStand->hasCustomName()));
refreshContainer(containerMenu);
}
else
{
@@ -1081,10 +1429,18 @@ bool ServerPlayer::openBeacon(shared_ptr<BeaconTileEntity> beacon)
if(containerMenu == inventoryMenu)
{
nextContainerCounter();
connection->send(std::make_shared<ContainerOpenPacket>(containerCounter, ContainerOpenPacket::BEACON, beacon->getCustomName(), beacon->getContainerSize(), beacon->hasCustomName()));
containerMenu = new BeaconMenu(inventory, beacon);
containerMenu->containerId = containerCounter;
containerMenu->addSlotListener(this);
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
if (FourKitBridge::FireInventoryOpen(entityId, ContainerOpenPacket::BEACON, beacon->getCustomName(), beacon->getContainerSize()))
{
doCloseContainer();
return true;
}
#endif
connection->send(std::make_shared<ContainerOpenPacket>(containerCounter, ContainerOpenPacket::BEACON, beacon->getCustomName(), beacon->getContainerSize(), beacon->hasCustomName()));
refreshContainer(containerMenu);
}
else
{
@@ -1104,7 +1460,15 @@ bool ServerPlayer::openTrading(shared_ptr<Merchant> traderTarget, const wstring
containerMenu->addSlotListener(this);
shared_ptr<Container> container = static_cast<MerchantMenu *>(containerMenu)->getTradeContainer();
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
if (FourKitBridge::FireInventoryOpen(entityId, ContainerOpenPacket::TRADER_NPC, name.empty() ? L"" : name, container->getContainerSize()))
{
doCloseContainer();
return true;
}
#endif
connection->send(std::make_shared<ContainerOpenPacket>(containerCounter, ContainerOpenPacket::TRADER_NPC, name.empty() ? L"" : name, container->getContainerSize(), !name.empty()));
refreshContainer(containerMenu);
MerchantRecipeList *offers = traderTarget->getOffers(dynamic_pointer_cast<Player>(shared_from_this()));
if (offers != nullptr)
@@ -1134,10 +1498,18 @@ bool ServerPlayer::openHorseInventory(shared_ptr<EntityHorse> horse, shared_ptr<
closeContainer();
}
nextContainerCounter();
connection->send(std::make_shared<ContainerOpenPacket>(containerCounter, ContainerOpenPacket::HORSE, horse->getCustomName(), container->getContainerSize(), container->hasCustomName(), horse->entityId));
containerMenu = new HorseInventoryMenu(inventory, container, horse);
containerMenu->containerId = containerCounter;
containerMenu->addSlotListener(this);
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
if (FourKitBridge::FireInventoryOpen(entityId, ContainerOpenPacket::HORSE, horse->getCustomName(), container->getContainerSize()))
{
doCloseContainer();
return true;
}
#endif
connection->send(std::make_shared<ContainerOpenPacket>(containerCounter, ContainerOpenPacket::HORSE, horse->getCustomName(), container->getContainerSize(), container->hasCustomName(), horse->entityId));
refreshContainer(containerMenu);
return true;
}
@@ -1620,9 +1992,9 @@ bool ServerPlayer::hasPermission(EGameCommand command)
//
// // 4J - Don't need
// //if (server.isSingleplayer() && server.getSingleplayerName().equals(name))
/// //{
// //{
// // server.setDifficulty(packet.getDifficulty());
/// //}
// //}
//}
int ServerPlayer::getViewDistance()

View File

@@ -10,9 +10,16 @@
#include "..\Minecraft.World\net.minecraft.world.level.h"
#include "..\Minecraft.World\net.minecraft.world.level.chunk.h"
#include "..\Minecraft.World\net.minecraft.world.level.dimension.h"
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
#include "..\Minecraft.World\EnchantmentHelper.h"
#include "..\Minecraft.World\ExperienceOrb.h"
#include "..\Minecraft.Server\FourKitBridge.h"
#endif
#include "MultiPlayerLevel.h"
#include "LevelRenderer.h"
extern bool g_suppressExpDrops;
ServerPlayerGameMode::ServerPlayerGameMode(Level *level)
{
// 4J - added initialisers
@@ -172,7 +179,7 @@ void ServerPlayerGameMode::startDestroyBlock(int x, int y, int z, int face)
}
}
bool ServerPlayerGameMode::stopDestroyBlock(int x, int y, int z)
void ServerPlayerGameMode::stopDestroyBlock(int x, int y, int z)
{
if (x == xDestroyBlock && y == yDestroyBlock && z == zDestroyBlock)
{
@@ -188,7 +195,6 @@ bool ServerPlayerGameMode::stopDestroyBlock(int x, int y, int z)
isDestroyingBlock = false;
level->destroyTileProgress(player->entityId, x, y, z, -1);
destroyBlock(x, y, z);
return true;
}
else if (!hasDelayedDestroy)
{
@@ -199,11 +205,9 @@ bool ServerPlayerGameMode::stopDestroyBlock(int x, int y, int z)
delayedDestroyY = y;
delayedDestroyZ = z;
delayedTickStart = destroyProgressStart;
return true;
}
}
}
return false;
}
void ServerPlayerGameMode::abortDestroyBlock(int x, int y, int z)
@@ -250,7 +254,43 @@ bool ServerPlayerGameMode::destroyBlock(int x, int y, int z)
int t = level->getTile(x, y, z);
int data = level->getData(x, y, z);
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
int eventExp = 0;
if (!isCreative() && !gameModeForPlayer->isAdventureRestricted())
{
Tile *tile = Tile::tiles[t];
if (tile != nullptr && player->canDestroy(tile))
{
if (!EnchantmentHelper::hasSilkTouch(player))
{
// (SYLV)todo: shouldnt we get these values from the actual blocks?
if (t == Tile::coalOre_Id)
eventExp = Mth::nextInt(level->random, 0, 2);
else if (t == Tile::diamondOre_Id)
eventExp = Mth::nextInt(level->random, 3, 7);
else if (t == Tile::emeraldOre_Id)
eventExp = Mth::nextInt(level->random, 3, 7);
else if (t == Tile::lapisOre_Id)
eventExp = Mth::nextInt(level->random, 2, 5);
else if (t == Tile::netherQuartz_Id)
eventExp = Mth::nextInt(level->random, 2, 5);
else if (t == Tile::redStoneOre_Id || t == Tile::redStoneOre_lit_Id)
eventExp = 1 + level->random->nextInt(5);
else if (t == Tile::mobSpawner_Id)
eventExp = 15 + level->random->nextInt(15) + level->random->nextInt(15);
}
}
}
int dimId = level->dimension ? level->dimension->id : 0;
int breakResult = FourKitBridge::FireBlockBreak(player->entityId, dimId, x, y, z, t, data, eventExp);
if (breakResult < 0)
{
player->connection->send(std::make_shared<TileUpdatePacket>(x, y, z, level));
return false;
}
int finalExp = breakResult;
#endif
level->levelEvent(player, LevelEvent::PARTICLES_DESTROY_BLOCK, x, y, z, t + (level->getData(x, y, z) << Tile::TILE_NUM_SHIFT));
// 4J - In creative mode, the point where we need to tell the renderer that we are about to destroy a tile via destroyingTileAt is quite complicated.
@@ -308,8 +348,25 @@ bool ServerPlayerGameMode::destroyBlock(int x, int y, int z)
}
}
if (changed && canDestroy)
{
{
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
g_suppressExpDrops = true;
#endif
Tile::tiles[t]->playerDestroy(level, player, x, y, z, data);
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
g_suppressExpDrops = false;
if (finalExp > 0)
{
while (finalExp > 0)
{
int xpDrop = ExperienceOrb::getExperienceValue(finalExp);
finalExp -= xpDrop;
level->addEntity(std::make_shared<ExperienceOrb>(level, x + .5, y + .5, z + .5, xpDrop));
}
}
#endif
}
}
return changed;

View File

@@ -45,7 +45,7 @@ public:
void tick();
void startDestroyBlock(int x, int y, int z, int face);
bool stopDestroyBlock(int x, int y, int z);
void stopDestroyBlock(int x, int y, int z);
void abortDestroyBlock(int x, int y, int z);
private:

View File

@@ -8,6 +8,9 @@
#include "..\Minecraft.World\net.minecraft.world.level.h"
#include "..\Minecraft.World\net.minecraft.world.level.dimension.h"
#include "TeleportCommand.h"
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
#include "..\Minecraft.Server\FourKitBridge.h"
#endif
EGameCommand TeleportCommand::getId()
{
@@ -32,7 +35,21 @@ void TeleportCommand::execute(shared_ptr<CommandSender> source, byteArray comman
if(subject != nullptr && destination != nullptr && subject->level->dimension->id == destination->level->dimension->id && subject->isAlive() )
{
subject->ride(nullptr);
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
{
double outX, outY, outZ;
bool cancelled = FourKitBridge::FirePlayerTeleport(subject->entityId,
subject->x, subject->y, subject->z, subject->dimension,
destination->x, destination->y, destination->z, destination->dimension,
1 /* COMMAND */,
&outX, &outY, &outZ);
if (cancelled)
return;
subject->connection->teleport(outX, outY, outZ, destination->yRot, destination->xRot);
}
#else
subject->connection->teleport(destination->x, destination->y, destination->z, destination->yRot, destination->xRot);
#endif
//logAdminAction(source, "commands.tp.success", subject->getAName(), destination->getAName());
logAdminAction(source, ChatPacket::e_ChatCommandTeleportSuccess, subject->getName(), eTYPE_SERVERPLAYER, destination->getName());

View File

@@ -0,0 +1,4 @@
[*.cs]
# IDE1006: Naming Styles
dotnet_diagnostic.IDE1006.severity = suggestion

View File

@@ -0,0 +1,26 @@
namespace Minecraft.Server.FourKit.Block;
/// <summary>
/// Represents the action type for a player interaction.
/// </summary>
public enum Action
{
/// <summary>Left-clicking the air.</summary>
LEFT_CLICK_AIR = 0,
/// <summary>Left-clicking a block.</summary>
LEFT_CLICK_BLOCK = 1,
/// <summary>Right-clicking the air.</summary>
RIGHT_CLICK_AIR = 2,
/// <summary>Right-clicking a block.</summary>
RIGHT_CLICK_BLOCK = 3,
/// <summary>
/// Stepping onto or into a block (Ass-pressure).
/// Examples: Jumping on soil, Standing on pressure plate,
/// Triggering redstone ore, Triggering tripwire.
/// </summary>
PHYSICAL = 4
}

View File

@@ -0,0 +1,166 @@
namespace Minecraft.Server.FourKit.Block;
/// <summary>
/// Represents a block. This is a live object, and only one Block may exist for
/// any given location in a world.
/// </summary>
public class Block
{
private readonly World _world;
private readonly int _x;
private readonly int _y;
private readonly int _z;
internal Block(World world, int x, int y, int z)
{
_world = world;
_x = x;
_y = y;
_z = z;
}
/// <summary>
/// Gets the Location of the block.
/// </summary>
/// <returns>Location of the block.</returns>
public Location getLocation()
{
return new Location(_world, _x, _y, _z, 0f, 0f);
}
/// <summary>
/// Gets the type of this block.
/// </summary>
/// <returns>Block type.</returns>
public Material getType()
{
int id = getTypeId();
return Enum.IsDefined(typeof(Material), id) ? (Material)id : Material.AIR;
}
/// <summary>
/// Gets the type ID of this block.
/// </summary>
/// <returns>Block type ID.</returns>
public int getTypeId()
{
if (NativeBridge.GetTileId != null)
return NativeBridge.GetTileId(_world.getDimensionId(), _x, _y, _z);
return 0;
}
/// <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>
/// Sets the type of this block.
/// </summary>
/// <param name="type">Material to change this block to.</param>
public void setType(Material type)
{
setTypeId((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 successful.</returns>
public bool setTypeId(int type)
{
NativeBridge.SetTile?.Invoke(_world.getDimensionId(), _x, _y, _z, type, 0);
return true;
}
/// <summary>
/// Gets the metadata value for this block.
/// </summary>
/// <returns>Block specific metadata.</returns>
public byte getData()
{
return (byte)NativeBridge.GetTileData(_world.getDimensionId(), _x, _y, _z);
}
/// <summary>
/// Sets the metadata value for this block.
/// </summary>
/// <param name="data">New block specific metadata.</param>
public void setData(byte data)
{
NativeBridge.SetTileData?.Invoke(_world.getDimensionId(), _x, _y, _z, data);
}
/// <summary>
/// Breaks the block and spawns items as if a player had digged it.
/// </summary>
/// <returns>true if the block was destroyed.</returns>
public bool breakNaturally()
{
if (NativeBridge.BreakBlock != null)
return NativeBridge.BreakBlock(_world.getDimensionId(), _x, _y, _z) != 0;
return false;
}
/// <summary>
/// Gets the block at the given offsets
/// </summary>
/// <param name="modX">X offset</param>
/// <param name="modY">Y offset</param>
/// <param name="modZ">Z offset</param>
/// <returns>Block at the given offsets</returns>
public Block getRelative(int modX, int modY, int modZ)
{
return getWorld().getBlockAt(getX() + modX, getY() + modY, getZ() + modZ);
}
/// <summary>
/// Gets the block at the given face
/// <para>This method is equal to getRelative(face, 1)</para>
/// </summary>
/// <param name="face">BlockFace to get relative to</param>
/// <returns>Block at the given face</returns>
public Block getRelative(BlockFace face)
{
return getRelative(face, 1);
}
/// <summary>
/// Gets the block at the given distance of the given face
/// <para>For example, the following method places water at 100,102,100; two
/// blocks above 100,100,100.</para>
/// <code>
/// Block block = world.getBlockAt(100, 100, 100);
/// Block shower = block.getRelative(BlockFace.UP, 2);
/// shower.setType(Material.WATER);
/// </code>
/// </summary>
/// <param name="face">BlockFace to get relative to</param>
/// <param name="distance">Distance to get relative to</param>
/// <returns>Block at the given distance of the given face</returns>
public Block getRelative(BlockFace face, int distance)
{
return getRelative(face.getModX() * distance, face.getModY() * distance, face.getModZ() * distance);
}
}

View File

@@ -0,0 +1,118 @@
namespace Minecraft.Server.FourKit.Block;
/// <summary>
/// Represents the face of a block.
/// </summary>
public enum BlockFace
{
DOWN = 0,
UP = 1,
NORTH = 2,
SOUTH = 3,
WEST = 4,
EAST = 5,
SELF = 6,
NORTH_EAST,
NORTH_WEST,
SOUTH_EAST,
SOUTH_WEST,
WEST_NORTH_WEST,
NORTH_NORTH_WEST,
NORTH_NORTH_EAST,
EAST_NORTH_EAST,
EAST_SOUTH_EAST,
SOUTH_SOUTH_EAST,
SOUTH_SOUTH_WEST,
WEST_SOUTH_WEST
}
public static class BlockFaceExtensions
{
/// <summary>
/// Get the amount of X-coordinates to modify to get the represented block.
/// </summary>
/// <param name="face">The block face.</param>
/// <returns>Amount of X-coordinates to modify.</returns>
public static int getModX(this BlockFace face) => face switch
{
BlockFace.EAST => 1,
BlockFace.WEST => -1,
BlockFace.NORTH_EAST => 1,
BlockFace.NORTH_WEST => -1,
BlockFace.SOUTH_EAST => 1,
BlockFace.SOUTH_WEST => -1,
BlockFace.EAST_NORTH_EAST => 1,
BlockFace.EAST_SOUTH_EAST => 1,
BlockFace.NORTH_NORTH_EAST => 1,
BlockFace.SOUTH_SOUTH_EAST => 1,
BlockFace.WEST_NORTH_WEST => -1,
BlockFace.WEST_SOUTH_WEST => -1,
BlockFace.NORTH_NORTH_WEST => -1,
BlockFace.SOUTH_SOUTH_WEST => -1,
_ => 0
};
/// <summary>
/// Get the amount of Y-coordinates to modify to get the represented block.
/// </summary>
/// <param name="face">The block face.</param>
/// <returns>Amount of Y-coordinates to modify.</returns>
public static int getModY(this BlockFace face) => face switch
{
BlockFace.UP => 1,
BlockFace.DOWN => -1,
_ => 0
};
/// <summary>
/// Get the amount of Z-coordinates to modify to get the represented block.
/// </summary>
/// <param name="face">The block face.</param>
/// <returns>Amount of Z-coordinates to modify.</returns>
public static int getModZ(this BlockFace face) => face switch
{
BlockFace.NORTH => -1,
BlockFace.SOUTH => 1,
BlockFace.NORTH_EAST => -1,
BlockFace.NORTH_WEST => -1,
BlockFace.SOUTH_EAST => 1,
BlockFace.SOUTH_WEST => 1,
BlockFace.NORTH_NORTH_EAST => -1,
BlockFace.NORTH_NORTH_WEST => -1,
BlockFace.EAST_NORTH_EAST => -1,
BlockFace.WEST_NORTH_WEST => -1,
BlockFace.EAST_SOUTH_EAST => 1,
BlockFace.WEST_SOUTH_WEST => 1,
BlockFace.SOUTH_SOUTH_EAST => 1,
BlockFace.SOUTH_SOUTH_WEST => 1,
_ => 0
};
/// <summary>
/// Gets the opposite face of this block face.
/// </summary>
/// <param name="face">The block face.</param>
/// <returns>The opposite block face.</returns>
public static BlockFace getOppositeFace(this BlockFace face) => face switch
{
BlockFace.NORTH => BlockFace.SOUTH,
BlockFace.SOUTH => BlockFace.NORTH,
BlockFace.EAST => BlockFace.WEST,
BlockFace.WEST => BlockFace.EAST,
BlockFace.UP => BlockFace.DOWN,
BlockFace.DOWN => BlockFace.UP,
BlockFace.NORTH_EAST => BlockFace.SOUTH_WEST,
BlockFace.NORTH_WEST => BlockFace.SOUTH_EAST,
BlockFace.SOUTH_EAST => BlockFace.NORTH_WEST,
BlockFace.SOUTH_WEST => BlockFace.NORTH_EAST,
BlockFace.WEST_NORTH_WEST => BlockFace.EAST_SOUTH_EAST,
BlockFace.NORTH_NORTH_WEST => BlockFace.SOUTH_SOUTH_EAST,
BlockFace.NORTH_NORTH_EAST => BlockFace.SOUTH_SOUTH_WEST,
BlockFace.EAST_NORTH_EAST => BlockFace.WEST_SOUTH_WEST,
BlockFace.EAST_SOUTH_EAST => BlockFace.WEST_NORTH_WEST,
BlockFace.SOUTH_SOUTH_EAST => BlockFace.NORTH_NORTH_WEST,
BlockFace.SOUTH_SOUTH_WEST => BlockFace.NORTH_NORTH_EAST,
BlockFace.WEST_SOUTH_WEST => BlockFace.EAST_NORTH_EAST,
_ => BlockFace.SELF
};
}

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

@@ -0,0 +1,117 @@
set(FOURKIT_PROJECT_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
set(FOURKIT_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/bin/$<CONFIG>")
set(FOURKIT_CSPROJ "${FOURKIT_PROJECT_DIR}/Minecraft.Server.FourKit.csproj")
# ---
# .NET 10 SDK check
# ---
# Fail configure with a clear message if .NET 10 SDK isn't installed. Otherwise
# the failure happens deep inside the dotnet publish step with a much less
# obvious error. The version pin in /global.json governs which 10.x SDK is
# selected at build time; this check just confirms one is installed at all.
find_program(DOTNET_EXECUTABLE NAMES dotnet
HINTS "$ENV{ProgramFiles}/dotnet" "$ENV{ProgramW6432}/dotnet"
DOC ".NET CLI executable")
if(NOT DOTNET_EXECUTABLE)
message(FATAL_ERROR
"FourKit requires the .NET 10 SDK but the 'dotnet' command was not found on PATH.\n"
"Install it from https://dotnet.microsoft.com/download/dotnet/10.0 (x64 SDK)\n"
"and reconfigure CMake.")
endif()
execute_process(
COMMAND "${DOTNET_EXECUTABLE}" --list-sdks
OUTPUT_VARIABLE FOURKIT_DOTNET_SDKS
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if(NOT FOURKIT_DOTNET_SDKS MATCHES "(^|\n)10\\.[0-9]+\\.[0-9]+")
if(FOURKIT_DOTNET_SDKS STREQUAL "")
set(_sdk_list " (none installed)")
else()
set(_sdk_list "${FOURKIT_DOTNET_SDKS}")
endif()
message(FATAL_ERROR
"FourKit requires a .NET 10 SDK but none was detected.\n"
"Install it from https://dotnet.microsoft.com/download/dotnet/10.0 (x64 SDK)\n"
"and reconfigure CMake.\n"
"\n"
"Currently installed .NET SDKs:\n"
"${_sdk_list}")
endif()
message(STATUS "FourKit: .NET CLI at ${DOTNET_EXECUTABLE}")
file(GLOB_RECURSE FOURKIT_SOURCES RELATIVE "${FOURKIT_PROJECT_DIR}" "${FOURKIT_PROJECT_DIR}/*.cs")
list(FILTER FOURKIT_SOURCES EXCLUDE REGEX "([/\\](obj|bin)[/\\])|^(obj|bin)[/\\]")
set(DOTNET_CONFIG "$<IF:$<CONFIG:Debug>,Debug,Release>")
foreach(src_file IN LISTS FOURKIT_SOURCES)
get_filename_component(src_path "${src_file}" PATH)
if(src_path)
string(REPLACE "/" "\\" group_path "${src_path}")
source_group("${group_path}" FILES "${FOURKIT_PROJECT_DIR}/${src_file}")
endif()
endforeach()
list(TRANSFORM FOURKIT_SOURCES PREPEND "${FOURKIT_PROJECT_DIR}/")
# Self-contained publish: bundles .NET 10 runtime so end users need nothing pre-installed.
# The publish AND the staging copy both live on this target so they re-run together
# whenever any FourKit C# source changes, even on incremental builds where the
# Minecraft.Server.FourKit C++ exe does not re-link.
#
# Target name note: the CMake target is "Minecraft.Server.FourKit.Managed" so it
# does not collide with the C++ executable target named "Minecraft.Server.FourKit".
# The .csproj file name, the assembly name, and the C# namespace all stay
# Minecraft.Server.FourKit because those are the donor's identifiers.
add_custom_target(Minecraft.Server.FourKit.Managed ALL
COMMAND dotnet publish "${FOURKIT_CSPROJ}"
--configuration "${DOTNET_CONFIG}"
--runtime win-x64
--self-contained true
--output "${FOURKIT_OUTPUT_DIR}"
COMMAND ${CMAKE_COMMAND} -E copy_directory
"${FOURKIT_OUTPUT_DIR}"
"$<TARGET_FILE_DIR:Minecraft.Server.FourKit>/runtime"
COMMAND ${CMAKE_COMMAND} -E make_directory
"$<TARGET_FILE_DIR:Minecraft.Server.FourKit>/plugins"
WORKING_DIRECTORY "${FOURKIT_PROJECT_DIR}"
SOURCES ${FOURKIT_SOURCES}
COMMENT "dotnet publish Minecraft.Server.FourKit + stage runtime/plugins next to server"
VERBATIM
)
set_target_properties(Minecraft.Server.FourKit.Managed PROPERTIES
OUTPUT_NAME "Minecraft.Server.FourKit.Managed"
)
# ---------------------------------------------------------------------------
# FourKit-enabled C++ server executable.
#
# Built from the same shared source list as the vanilla Minecraft.Server,
# plus the seven FourKit native bridge files. The MINECRAFT_SERVER_FOURKIT_BUILD
# preprocessor define switches FourKitBridge.h from inline no-op stubs to its
# real declarations, and the FourKitRuntime.cpp / FourKitBridge.cpp /
# FourKitNatives.cpp / FourKitMappers.cpp impls link in.
#
# This target is defined in the Minecraft.Server.FourKit/ source dir (not
# Minecraft.Server/) so the Visual Studio generator emits its .vcxproj files
# into build/<dir>/Minecraft.Server.FourKit/, keeping the variant identity
# consistent across source dir, project file, runtime output dir, and Docker
# image base.
# ---------------------------------------------------------------------------
include("${CMAKE_SOURCE_DIR}/Minecraft.Server/cmake/sources/Common.cmake")
include("${CMAKE_SOURCE_DIR}/cmake/CommonSources.cmake")
include("${CMAKE_SOURCE_DIR}/cmake/ServerTarget.cmake")
set(MINECRAFT_SERVER_FOURKIT_SOURCES
${MINECRAFT_SERVER_COMMON}
${SOURCES_COMMON}
${_MINECRAFT_SERVER_COMMON_SERVER_FOURKIT}
)
add_executable(Minecraft.Server.FourKit ${MINECRAFT_SERVER_FOURKIT_SOURCES})
configure_lce_server_target(Minecraft.Server.FourKit)
target_compile_definitions(Minecraft.Server.FourKit PRIVATE MINECRAFT_SERVER_FOURKIT_BUILD)
add_dependencies(Minecraft.Server.FourKit Minecraft.Server.FourKit.Managed)

View File

@@ -0,0 +1,110 @@
namespace Minecraft.Server.FourKit.Command;
/// <summary>
/// Represents a Command, which executes various tasks upon user input.
/// </summary>
public abstract class Command
{
private string _name;
private string _description;
private string _usage;
private List<string> _aliases;
/// <summary>
/// Creates a new command with the given name and no aliases.
/// </summary>
/// <param name="name">Name of this command.</param>
protected Command(string name)
{
_name = name;
_description = string.Empty;
_usage = "/" + name;
_aliases = new List<string>();
}
/// <summary>
/// Creates a new command with the given name, description, and aliases.
/// </summary>
/// <param name="name">Name of this command.</param>
/// <param name="description">A brief description of this command.</param>
/// <param name="aliases">A list of aliases for this command.</param>
protected Command(string name, string description, List<string> aliases)
{
_name = name;
_description = description ?? string.Empty;
_usage = "/" + name;
_aliases = aliases ?? new List<string>();
}
/// <summary>
/// Executes the command, returning its success.
/// </summary>
/// <param name="sender">Source of the command.</param>
/// <param name="commandLabel">Alias of the command which was used.</param>
/// <param name="args">Passed command arguments.</param>
/// <returns><c>true</c> if the command was successful, otherwise <c>false</c>.</returns>
public abstract bool execute(CommandSender sender, string commandLabel, string[] args);
/// <summary>
/// Returns a list of active aliases of this command.
/// </summary>
/// <returns>List of aliases.</returns>
public List<string> getAliases() => new(_aliases);
/// <summary>
/// Gets a brief description of this command.
/// </summary>
/// <returns>Description of this command.</returns>
public string getDescription() => _description;
/// <summary>
/// Returns the current label for this command.
/// </summary>
/// <returns>Current label.</returns>
public string getLabel() => _name;
/// <summary>
/// Returns the name of this command.
/// </summary>
/// <returns>Name of this command.</returns>
public string getName() => _name;
/// <summary>
/// Gets an example usage of this command.
/// </summary>
/// <returns>Usage string.</returns>
public string getUsage() => _usage;
/// <summary>
/// Sets the list of aliases to request on registration for this command.
/// </summary>
/// <param name="aliases">Aliases to register.</param>
/// <returns>This command.</returns>
public Command setAliases(List<string> aliases)
{
_aliases = aliases ?? new List<string>();
return this;
}
/// <summary>
/// Sets a brief description of this command.
/// </summary>
/// <param name="description">New command description.</param>
/// <returns>This command.</returns>
public Command setDescription(string description)
{
_description = description ?? string.Empty;
return this;
}
/// <summary>
/// Sets the example usage of this command.
/// </summary>
/// <param name="usage">New example usage.</param>
/// <returns>This command.</returns>
public Command setUsage(string usage)
{
_usage = usage ?? string.Empty;
return this;
}
}

View File

@@ -0,0 +1,17 @@
namespace Minecraft.Server.FourKit.Command;
/// <summary>
/// Represents a class which contains a single method for executing commands.
/// </summary>
public interface CommandExecutor
{
/// <summary>
/// Executes the given command, returning its success.
/// </summary>
/// <param name="sender">Source of the command.</param>
/// <param name="command">Command which was executed.</param>
/// <param name="label">Alias of the command which was used.</param>
/// <param name="args">Passed command arguments.</param>
/// <returns><c>true</c> if a valid command, otherwise <c>false</c>.</returns>
bool onCommand(CommandSender sender, Command command, string label, string[] args);
}

View File

@@ -0,0 +1,25 @@
namespace Minecraft.Server.FourKit.Command;
/// <summary>
/// Represents something that can send commands and receive messages.
/// </summary>
public interface CommandSender
{
/// <summary>
/// Sends this sender a message.
/// </summary>
/// <param name="message">Message to be displayed.</param>
void sendMessage(string message);
/// <summary>
/// Sends this sender multiple messages.
/// </summary>
/// <param name="messages">An array of messages to be displayed.</param>
void sendMessage(string[] messages);
/// <summary>
/// Gets the name of this command sender.
/// </summary>
/// <returns>Name of the sender.</returns>
string getName();
}

View File

@@ -0,0 +1,27 @@
namespace Minecraft.Server.FourKit.Command;
/// <summary>
/// Represents the server console as a command sender.
/// </summary>
public class ConsoleCommandSender : CommandSender
{
internal static readonly ConsoleCommandSender Instance = new();
private ConsoleCommandSender() { }
/// <inheritdoc/>
public void sendMessage(string message)
{
ServerLog.Info("console", message);
}
/// <inheritdoc/>
public void sendMessage(string[] messages)
{
foreach (var msg in messages)
sendMessage(msg);
}
/// <inheritdoc/>
public string getName() => "CONSOLE";
}

View File

@@ -0,0 +1,44 @@
namespace Minecraft.Server.FourKit.Command;
/// <summary>
/// Represents a <see cref="Command"/> belonging to a plugin.
/// </summary>
public class PluginCommand : Command
{
private CommandExecutor? _executor;
// should this remain internal?
/// <summary>
/// Creates a new plugin command with the given name.
/// Use <see cref="FourKit.getCommand"/> to obtain instances.
/// </summary>
/// <param name="name">Name of this command.</param>
internal PluginCommand(string name) : base(name)
{
}
/// <inheritdoc/>
public override bool execute(CommandSender sender, string commandLabel, string[] args)
{
if (_executor != null)
return _executor.onCommand(sender, this, commandLabel, args);
return false;
}
/// <summary>
/// Gets the <see cref="CommandExecutor"/> associated with this command.
/// </summary>
/// <returns>The command executor, or <c>null</c>.</returns>
public CommandExecutor? getExecutor() => _executor;
/// <summary>
/// Sets the <see cref="CommandExecutor"/> to run when the command is dispatched.
/// </summary>
/// <param name="executor">New executor to set.</param>
/// <returns><c>true</c> if the executor was set.</returns>
public bool setExecutor(CommandExecutor executor)
{
_executor = executor;
return true;
}
}

View File

@@ -0,0 +1,26 @@
using Minecraft.Server.FourKit.Inventory;
namespace Minecraft.Server.FourKit.Enchantments;
public class AquaAffinityEnchantment : Enchantment
{
static readonly Material[] supportedItems = {
Material.LEATHER_HELMET, Material.CHAINMAIL_HELMET, Material.GOLD_HELMET, Material.IRON_HELMET, Material.DIAMOND_HELMET,
};
static readonly EnchantmentType[] conflictedEnchants = { };
public override bool canEnchantItem(ItemStack item) => supportedItems.Contains(item.getType());
public override bool conflictsWith(Enchantment other) => conflictedEnchants.Contains(other.getEnchantType());
public override EnchantmentTarget getItemTarget() => EnchantmentTarget.ARMOR_HEAD;
public override EnchantmentType getEnchantType() => EnchantmentType.WATER_WORKER;
public override int getMaxLevel() => 1;
public override string getName() => "aquaaffinity";
public override int getStartLevel() => 1;
}

View File

@@ -0,0 +1,31 @@
using Minecraft.Server.FourKit.Inventory;
namespace Minecraft.Server.FourKit.Enchantments;
//name could be changed
public class BaneOfArthropodsEnchantment : Enchantment
{
static readonly Material[] supportedItems = {
Material.WOOD_SWORD, Material.STONE_SWORD, Material.IRON_SWORD, Material.GOLD_SWORD, Material.DIAMOND_SWORD,
Material.WOOD_AXE, Material.STONE_AXE, Material.IRON_AXE, Material.GOLD_AXE, Material.DIAMOND_AXE,
};
static readonly EnchantmentType[] conflictedEnchants = {
EnchantmentType.DAMAGE_ALL,
EnchantmentType.DAMAGE_UNDEAD,
};
public override bool canEnchantItem(ItemStack item) => supportedItems.Contains(item.getType());
public override bool conflictsWith(Enchantment other) => conflictedEnchants.Contains(other.getEnchantType());
public override EnchantmentTarget getItemTarget() => EnchantmentTarget.WEAPON;
public override EnchantmentType getEnchantType() => EnchantmentType.DAMAGE_ARTHOPODS;
public override int getMaxLevel() => 5;
public override string getName() => "arthopods"; //could be changed
public override int getStartLevel() => 1;
}

View File

@@ -0,0 +1,34 @@
using Minecraft.Server.FourKit.Inventory;
namespace Minecraft.Server.FourKit.Enchantments;
public class BlastProtectionEnchantment : Enchantment
{
static readonly Material[] supportedItems = {
Material.LEATHER_HELMET, Material.LEATHER_CHESTPLATE, Material.LEATHER_LEGGINGS, Material.LEATHER_BOOTS,
Material.CHAINMAIL_HELMET, Material.CHAINMAIL_CHESTPLATE, Material.CHAINMAIL_LEGGINGS, Material.CHAINMAIL_BOOTS,
Material.GOLD_HELMET, Material.GOLD_CHESTPLATE, Material.GOLD_LEGGINGS, Material.GOLD_BOOTS,
Material.IRON_HELMET, Material.IRON_CHESTPLATE, Material.IRON_LEGGINGS, Material.IRON_BOOTS,
Material.DIAMOND_HELMET, Material.DIAMOND_CHESTPLATE, Material.DIAMOND_LEGGINGS, Material.DIAMOND_BOOTS,
};
static readonly EnchantmentType[] conflictedEnchants = {
EnchantmentType.PROTECTION_ENVIRONMENTAL,
EnchantmentType.PROTECTION_FIRE,
EnchantmentType.PROTECTION_PROJECTILE,
};
public override bool canEnchantItem(ItemStack item) => supportedItems.Contains(item.getType());
public override bool conflictsWith(Enchantment other) => conflictedEnchants.Contains(other.getEnchantType());
public override EnchantmentTarget getItemTarget() => EnchantmentTarget.ARMOR;
public override EnchantmentType getEnchantType() => EnchantmentType.PROTECTION_EXPLOSIVE;
public override int getMaxLevel() => 4;
public override string getName() => "blastprotection";
public override int getStartLevel() => 1;
}

View File

@@ -0,0 +1,32 @@
using Minecraft.Server.FourKit.Inventory;
namespace Minecraft.Server.FourKit.Enchantments;
public class EfficiencyEnchantment : Enchantment
{
static readonly Material[] supportedItems = {
Material.WOOD_SWORD, Material.STONE_SWORD, Material.IRON_SWORD, Material.GOLD_SWORD, Material.DIAMOND_SWORD,
Material.WOOD_PICKAXE, Material.STONE_PICKAXE, Material.IRON_PICKAXE, Material.GOLD_PICKAXE, Material.DIAMOND_PICKAXE,
Material.WOOD_AXE, Material.STONE_AXE, Material.IRON_AXE, Material.GOLD_AXE, Material.DIAMOND_AXE,
Material.WOOD_SPADE, Material.STONE_SPADE, Material.IRON_SPADE, Material.GOLD_SPADE, Material.DIAMOND_SPADE,
Material.WOOD_HOE, Material.STONE_HOE, Material.IRON_HOE, Material.GOLD_HOE, Material.DIAMOND_HOE,
Material.SHEARS
};
static readonly EnchantmentType[] conflictedEnchants = { };
public override bool canEnchantItem(ItemStack item) => supportedItems.Contains(item.getType());
public override bool conflictsWith(Enchantment other) => conflictedEnchants.Contains(other.getEnchantType());
public override EnchantmentTarget getItemTarget() => EnchantmentTarget.TOOL;
public override EnchantmentType getEnchantType() => EnchantmentType.DIG_SPEAD;
public override int getMaxLevel() => 5;
public override string getName() => "efficiency";
public override int getStartLevel() => 1;
}

View File

@@ -0,0 +1,274 @@
namespace Minecraft.Server.FourKit.Enchantments;
using Minecraft.Server.FourKit.Inventory;
/// <summary>
/// Represents the applicable target for a Enchantment
/// </summary>
public enum EnchantmentTarget
{
/// <summary>
/// Allows the Enchantment to be placed on all items
/// </summary>
ALL,
/// <summary>
/// Allows the Enchantment to be placed on armor
/// </summary>
ARMOR,
/// <summary>
/// Allows the Enchantment to be placed on feet slot armor
/// </summary>
ARMOR_FEET,
/// <summary>
/// Allows the Enchantment to be placed on head slot armor
/// </summary>
ARMOR_HEAD,
/// <summary>
/// Allows the Enchantment to be placed on leg slot armor
/// </summary>
ARMOR_LEGS,
/// <summary>
/// Allows the Enchantment to be placed on torso slot armor
/// </summary>
ARMOR_TORSO,
/// <summary>
/// Allows the Enchantment to be placed on bows.
/// </summary>
BOW,
/// <summary>
/// Allows the Enchantment to be placed on tools (spades, pickaxe, hoes, axes)
/// </summary>
TOOL,
/// <summary>
/// Allows the Enchantment to be placed on weapons (swords)
/// </summary>
WEAPON,
}
//these numbers match the same ones that the enchants register as, Enchantment.cpp - 41
/// <summary>
/// The various type of enchantments that may be added to armour or weapons
/// </summary>
public enum EnchantmentType
{
/// <summary>
/// Provides extra damage when shooting arrows from bows
/// </summary>
ARROW_DAMAGE = 48,
/// <summary>
/// Sets entities on fire when hit by arrows shot from a bow
/// </summary>
ARROW_FIRE = 50,
/// <summary>
/// Provides infinite arrows when shooting a bow
/// </summary>
ARROW_INFINITE = 51,
/// <summary>
/// Provides a knockback when an entity is hit by an arrow from a bow
/// </summary>
ARROW_KNOCKBACK = 49,
/// <summary>
/// Increases damage against all targets
/// </summary>
DAMAGE_ALL = 16,
/// <summary>
/// Increases damage against arthropod targets
/// </summary>
DAMAGE_ARTHOPODS = 18,
/// <summary>
/// Increases damage against undead targets
/// </summary>
DAMAGE_UNDEAD = 17,
/// <summary>
/// Increases the rate at which you mine/dig
/// </summary>
DIG_SPEAD = 32,
/// <summary>
/// Decreases the rate at which a tool looses durability
/// </summary>
DURABILITY = 34,
/// <summary>
/// When attacking a target, has a chance to set them on fire
/// </summary>
FIRE_ASPECT = 20,
/// <summary>
/// All damage to other targets will knock them back when hit
/// </summary>
KNOCKBACK = 19,
/// <summary>
/// Provides a chance of gaining extra loot when destroying blocks
/// </summary>
LOOT_BONUS_BLOCKS = 35,
/// <summary>
/// Provides a chance of gaining extra loot when killing monsters
/// </summary>
LOOT_BONUS_MOBS = 21,
/// <summary>
/// Decreases the rate of air loss whilst underwater
/// </summary>
OXYGEN = 5,
/// <summary>
/// Provides protection against environmental damage
/// </summary>
PROTECTION_ENVIRONMENTAL = 0,
/// <summary>
/// Provides protection against explosive damage
/// </summary>
PROTECTION_EXPLOSIVE = 3,
/// <summary>
/// Provides protection against fall damage
/// </summary>
PROTECTION_FALL = 2,
/// <summary>
/// Provides protection against fire damage
/// </summary>
PROTECTION_FIRE = 1,
/// <summary>
/// Provides protection against projectile damage
/// </summary>
PROTECTION_PROJECTILE = 4,
/// <summary>
/// Allows blocks to drop themselves instead of fragments (for example, stone instead of cobblestone)
/// </summary>
SILK_TOUCH = 33,
/// <summary>
/// Damages the attacker
/// </summary>
THORNS = 7,
/// <summary>
/// Increases the speed at which a player may mine underwater
/// </summary>
WATER_WORKER = 6
}
public abstract class Enchantment
{
public static Enchantment PowerEnchantment => _registry[EnchantmentType.ARROW_DAMAGE];
public static Enchantment FlameEnchantment => _registry[EnchantmentType.ARROW_FIRE];
public static Enchantment InfinityEnchantment => _registry[EnchantmentType.ARROW_INFINITE];
public static Enchantment PunchEnchantment => _registry[EnchantmentType.ARROW_KNOCKBACK];
public static Enchantment SharpnessEnchantment => _registry[EnchantmentType.DAMAGE_ALL];
public static Enchantment BaneOfArthropodsEnchantment => _registry[EnchantmentType.DAMAGE_ARTHOPODS];
public static Enchantment SmiteEnchantment => _registry[EnchantmentType.DAMAGE_UNDEAD];
public static Enchantment EfficiencyEnchantment => _registry[EnchantmentType.DIG_SPEAD];
public static Enchantment UnbreakingEnchantment => _registry[EnchantmentType.DURABILITY];
public static Enchantment FireAspectEnchantment => _registry[EnchantmentType.FIRE_ASPECT];
public static Enchantment KnockbackEnchantment => _registry[EnchantmentType.KNOCKBACK];
public static Enchantment FortuneEnchantment => _registry[EnchantmentType.LOOT_BONUS_BLOCKS];
public static Enchantment LootingEnchantment => _registry[EnchantmentType.LOOT_BONUS_MOBS];
public static Enchantment RespirationEnchantment => _registry[EnchantmentType.OXYGEN];
public static Enchantment ProtectionEnchantment => _registry[EnchantmentType.PROTECTION_ENVIRONMENTAL];
public static Enchantment BlastProtectionEnchantment => _registry[EnchantmentType.PROTECTION_EXPLOSIVE];
public static Enchantment FeatherFallingEnchantment => _registry[EnchantmentType.PROTECTION_FALL];
public static Enchantment FireProtectionEnchantment => _registry[EnchantmentType.PROTECTION_FIRE];
public static Enchantment ProjectileProtectionEnchantment => _registry[EnchantmentType.PROTECTION_PROJECTILE];
public static Enchantment SilkTouchEnchantment => _registry[EnchantmentType.SILK_TOUCH];
public static Enchantment ThornsEnchantment => _registry[EnchantmentType.THORNS];
public static Enchantment AquaAffinityEnchantment => _registry[EnchantmentType.WATER_WORKER];
private static Dictionary<EnchantmentType, Enchantment> _registry = new Dictionary<EnchantmentType, Enchantment>()
{
{ EnchantmentType.ARROW_DAMAGE, new PowerEnchantment() },
{ EnchantmentType.ARROW_FIRE, new FlameEnchantment() },
{ EnchantmentType.ARROW_INFINITE, new InfinityEnchantment() },
{ EnchantmentType.ARROW_KNOCKBACK, new PunchEnchantment() },
{ EnchantmentType.DAMAGE_ALL, new SharpnessEnchantment() },
{ EnchantmentType.DAMAGE_ARTHOPODS, new BaneOfArthropodsEnchantment() },
{ EnchantmentType.DAMAGE_UNDEAD, new SmiteEnchantment() },
{ EnchantmentType.DIG_SPEAD, new EfficiencyEnchantment() },
{ EnchantmentType.DURABILITY, new UnbreakingEnchantment() },
{ EnchantmentType.FIRE_ASPECT, new FireAspectEnchantment() },
{ EnchantmentType.KNOCKBACK, new KnockbackEnchantment() },
{ EnchantmentType.LOOT_BONUS_BLOCKS, new FortuneEnchantment() },
{ EnchantmentType.LOOT_BONUS_MOBS, new LootingEnchantment() },
{ EnchantmentType.OXYGEN, new RespirationEnchantment() },
{ EnchantmentType.PROTECTION_ENVIRONMENTAL, new ProtectionEnchantment() },
{ EnchantmentType.PROTECTION_EXPLOSIVE, new BlastProtectionEnchantment() },
{ EnchantmentType.PROTECTION_FALL, new FeatherFallingEnchantment() },
{ EnchantmentType.PROTECTION_FIRE, new FireProtectionEnchantment() },
{ EnchantmentType.PROTECTION_PROJECTILE, new ProjectileProtectionEnchantment() },
{ EnchantmentType.SILK_TOUCH, new SilkTouchEnchantment() },
{ EnchantmentType.THORNS, new ThornsEnchantment() },
{ EnchantmentType.WATER_WORKER, new AquaAffinityEnchantment() },
};
/// <summary>
/// Checks if this Enchantment may be applied to the given <see cref="ItemStack"/>. This does not check if it conflicts with any enchantments already applied to the item.
/// </summary>
/// <param name="item">Item to test</param>
/// <returns>True if the enchantment may be applied, otherwise False</returns>
public abstract bool canEnchantItem(ItemStack item);
/// <summary>
/// Check if this enchantment conflicts with another enchantment.
/// </summary>
/// <param name="other">The enchantment to check against </param>
/// <returns>True if there is a conflict.</returns>
public abstract bool conflictsWith(Enchantment other);
//public abstract Enchantment getById(int id); //deprecated by bukkit
/// <summary>
/// Gets the Enchantment at the specified name
/// </summary>
/// <param name="name">Name to fetch.</param>
/// <returns>Resulting Enchantment, or null if not found</returns>
public static Enchantment? getByName(string name)
{
foreach (KeyValuePair<EnchantmentType, Enchantment> enchantmentPair in _registry)
{
if (enchantmentPair.Value.getName().Equals(name)) return enchantmentPair.Value;
}
return null;
}
/// <summary>
/// Gets the Enchantment at the specified type
/// </summary>
/// <param name="type">Type to fetch.</param>
/// <returns>Resulting Enchantment, or null if not found</returns>
public static Enchantment getByType(EnchantmentType type)
{
return _registry[type]; //we should always have the enchant based on the type
}
//public abstract int getId(); //deprecated by bukkit
/// <summary>
/// Gets the type of <see cref="ItemStack"/> that may fit this Enchantment.
/// </summary>
/// <returns>Gets the type of <see cref="ItemStack"/> that may fit this Enchantment.</returns>
public abstract EnchantmentTarget getItemTarget();
/// <summary>
/// Returns the <see cref="EnchantmentType"/>.
/// </summary>
/// <returns>Gets the enchantment type.</returns>
public abstract EnchantmentType getEnchantType();
/// <summary>
/// Gets the maximum level that this Enchantment may become.
/// </summary>
/// <returns>Maximum level of the Enchantment</returns>
public abstract int getMaxLevel();
/// <summary>
/// Gets the unique name of this enchantment
/// </summary>
/// <returns>Unique name</returns>
public abstract string getName();
/// <summary>
/// Gets the level that this Enchantment should start at
/// </summary>
/// <returns>Starting level of the Enchantment</returns>
public abstract int getStartLevel();
//public static bool isAcceptingRegistrations(); //we dont have enchant registrations
//public static void registerEnchantment(Enchantment enchantment); //we dont have enchant registrations
//public static void stopAcceptingRegistrations(); //we dont have enchant registrations
}

View File

@@ -0,0 +1,26 @@
using Minecraft.Server.FourKit.Inventory;
namespace Minecraft.Server.FourKit.Enchantments;
public class FeatherFallingEnchantment : Enchantment
{
static readonly Material[] supportedItems = {
Material.LEATHER_BOOTS, Material.LEATHER_BOOTS, Material.CHAINMAIL_BOOTS, Material.GOLD_BOOTS, Material.IRON_BOOTS, Material.DIAMOND_BOOTS,
};
static readonly EnchantmentType[] conflictedEnchants = { };
public override bool canEnchantItem(ItemStack item) => supportedItems.Contains(item.getType());
public override bool conflictsWith(Enchantment other) => conflictedEnchants.Contains(other.getEnchantType());
public override EnchantmentTarget getItemTarget() => EnchantmentTarget.ARMOR_FEET;
public override EnchantmentType getEnchantType() => EnchantmentType.PROTECTION_FALL;
public override int getMaxLevel() => 4;
public override string getName() => "featherfalling";
public override int getStartLevel() => 1;
}

View File

@@ -0,0 +1,27 @@
using Minecraft.Server.FourKit.Inventory;
namespace Minecraft.Server.FourKit.Enchantments;
public class FireAspectEnchantment : Enchantment
{
static readonly Material[] supportedItems = {
Material.WOOD_SWORD, Material.STONE_SWORD, Material.IRON_SWORD, Material.GOLD_SWORD, Material.DIAMOND_SWORD,
Material.WOOD_AXE, Material.STONE_AXE, Material.IRON_AXE, Material.GOLD_AXE, Material.DIAMOND_AXE,
};
static readonly EnchantmentType[] conflictedEnchants = { };
public override bool canEnchantItem(ItemStack item) => supportedItems.Contains(item.getType());
public override bool conflictsWith(Enchantment other) => conflictedEnchants.Contains(other.getEnchantType());
public override EnchantmentTarget getItemTarget() => EnchantmentTarget.WEAPON;
public override EnchantmentType getEnchantType() => EnchantmentType.FIRE_ASPECT;
public override int getMaxLevel() => 2;
public override string getName() => "fireaspect";
public override int getStartLevel() => 1;
}

View File

@@ -0,0 +1,34 @@
using Minecraft.Server.FourKit.Inventory;
namespace Minecraft.Server.FourKit.Enchantments;
public class FireProtectionEnchantment : Enchantment
{
static readonly Material[] supportedItems = {
Material.LEATHER_HELMET, Material.LEATHER_CHESTPLATE, Material.LEATHER_LEGGINGS, Material.LEATHER_BOOTS,
Material.CHAINMAIL_HELMET, Material.CHAINMAIL_CHESTPLATE, Material.CHAINMAIL_LEGGINGS, Material.CHAINMAIL_BOOTS,
Material.GOLD_HELMET, Material.GOLD_CHESTPLATE, Material.GOLD_LEGGINGS, Material.GOLD_BOOTS,
Material.IRON_HELMET, Material.IRON_CHESTPLATE, Material.IRON_LEGGINGS, Material.IRON_BOOTS,
Material.DIAMOND_HELMET, Material.DIAMOND_CHESTPLATE, Material.DIAMOND_LEGGINGS, Material.DIAMOND_BOOTS,
};
static readonly EnchantmentType[] conflictedEnchants = {
EnchantmentType.PROTECTION_ENVIRONMENTAL,
EnchantmentType.PROTECTION_EXPLOSIVE,
EnchantmentType.PROTECTION_PROJECTILE,
};
public override bool canEnchantItem(ItemStack item) => supportedItems.Contains(item.getType());
public override bool conflictsWith(Enchantment other) => conflictedEnchants.Contains(other.getEnchantType());
public override EnchantmentTarget getItemTarget() => EnchantmentTarget.ARMOR;
public override EnchantmentType getEnchantType() => EnchantmentType.PROTECTION_FIRE;
public override int getMaxLevel() => 4;
public override string getName() => "fireprotection";
public override int getStartLevel() => 1;
}

View File

@@ -0,0 +1,26 @@
using Minecraft.Server.FourKit.Inventory;
namespace Minecraft.Server.FourKit.Enchantments;
public class FlameEnchantment : Enchantment
{
static readonly Material[] supportedItems = {
Material.BOW
};
static readonly EnchantmentType[] conflictedEnchants = { };
public override bool canEnchantItem(ItemStack item) => supportedItems.Contains(item.getType());
public override bool conflictsWith(Enchantment other) => conflictedEnchants.Contains(other.getEnchantType());
public override EnchantmentTarget getItemTarget() => EnchantmentTarget.BOW;
public override EnchantmentType getEnchantType() => EnchantmentType.ARROW_FIRE;
public override int getMaxLevel() => 1;
public override string getName() => "flame";
public override int getStartLevel() => 1;
}

View File

@@ -0,0 +1,31 @@
using Minecraft.Server.FourKit.Inventory;
namespace Minecraft.Server.FourKit.Enchantments;
public class FortuneEnchantment : Enchantment
{
static readonly Material[] supportedItems = {
Material.WOOD_PICKAXE, Material.STONE_PICKAXE, Material.IRON_PICKAXE, Material.GOLD_PICKAXE, Material.DIAMOND_PICKAXE,
Material.WOOD_AXE, Material.STONE_AXE, Material.IRON_AXE, Material.GOLD_AXE, Material.DIAMOND_AXE,
Material.WOOD_SPADE, Material.STONE_SPADE, Material.IRON_SPADE, Material.GOLD_SPADE, Material.DIAMOND_SPADE,
Material.WOOD_HOE, Material.STONE_HOE, Material.IRON_HOE, Material.GOLD_HOE, Material.DIAMOND_HOE,
};
static readonly EnchantmentType[] conflictedEnchants = {
EnchantmentType.SILK_TOUCH
};
public override bool canEnchantItem(ItemStack item) => supportedItems.Contains(item.getType());
public override bool conflictsWith(Enchantment other) => conflictedEnchants.Contains(other.getEnchantType());
public override EnchantmentTarget getItemTarget() => EnchantmentTarget.TOOL;
public override EnchantmentType getEnchantType() => EnchantmentType.LOOT_BONUS_BLOCKS;
public override int getMaxLevel() => 3;
public override string getName() => "fortune";
public override int getStartLevel() => 1;
}

View File

@@ -0,0 +1,26 @@
using Minecraft.Server.FourKit.Inventory;
namespace Minecraft.Server.FourKit.Enchantments;
public class InfinityEnchantment : Enchantment
{
static readonly Material[] supportedItems = {
Material.BOW
};
static readonly EnchantmentType[] conflictedEnchants = { };
public override bool canEnchantItem(ItemStack item) => supportedItems.Contains(item.getType());
public override bool conflictsWith(Enchantment other) => conflictedEnchants.Contains(other.getEnchantType());
public override EnchantmentTarget getItemTarget() => EnchantmentTarget.BOW;
public override EnchantmentType getEnchantType() => EnchantmentType.ARROW_INFINITE;
public override int getMaxLevel() => 1;
public override string getName() => "infinity";
public override int getStartLevel() => 1;
}

View File

@@ -0,0 +1,27 @@
using Minecraft.Server.FourKit.Inventory;
namespace Minecraft.Server.FourKit.Enchantments;
public class KnockbackEnchantment : Enchantment
{
static readonly Material[] supportedItems = {
Material.WOOD_SWORD, Material.STONE_SWORD, Material.IRON_SWORD, Material.GOLD_SWORD, Material.DIAMOND_SWORD,
Material.WOOD_AXE, Material.STONE_AXE, Material.IRON_AXE, Material.GOLD_AXE, Material.DIAMOND_AXE,
};
static readonly EnchantmentType[] conflictedEnchants = { };
public override bool canEnchantItem(ItemStack item) => supportedItems.Contains(item.getType());
public override bool conflictsWith(Enchantment other) => conflictedEnchants.Contains(other.getEnchantType());
public override EnchantmentTarget getItemTarget() => EnchantmentTarget.WEAPON;
public override EnchantmentType getEnchantType() => EnchantmentType.KNOCKBACK;
public override int getMaxLevel() => 2;
public override string getName() => "knockback";
public override int getStartLevel() => 1;
}

View File

@@ -0,0 +1,27 @@
using Minecraft.Server.FourKit.Inventory;
namespace Minecraft.Server.FourKit.Enchantments;
public class LootingEnchantment : Enchantment
{
static readonly Material[] supportedItems = {
Material.WOOD_PICKAXE, Material.STONE_PICKAXE, Material.IRON_PICKAXE, Material.GOLD_PICKAXE, Material.DIAMOND_PICKAXE,
Material.WOOD_AXE, Material.STONE_AXE, Material.IRON_AXE, Material.GOLD_AXE, Material.DIAMOND_AXE,
};
static readonly EnchantmentType[] conflictedEnchants = { };
public override bool canEnchantItem(ItemStack item) => supportedItems.Contains(item.getType());
public override bool conflictsWith(Enchantment other) => conflictedEnchants.Contains(other.getEnchantType());
public override EnchantmentTarget getItemTarget() => EnchantmentTarget.WEAPON;
public override EnchantmentType getEnchantType() => EnchantmentType.LOOT_BONUS_MOBS;
public override int getMaxLevel() => 3;
public override string getName() => "looting";
public override int getStartLevel() => 1;
}

View File

@@ -0,0 +1,26 @@
using Minecraft.Server.FourKit.Inventory;
namespace Minecraft.Server.FourKit.Enchantments;
public class PowerEnchantment : Enchantment
{
static readonly Material[] supportedItems = {
Material.BOW
};
static readonly EnchantmentType[] conflictedEnchants = { };
public override bool canEnchantItem(ItemStack item) => supportedItems.Contains(item.getType());
public override bool conflictsWith(Enchantment other) => conflictedEnchants.Contains(other.getEnchantType());
public override EnchantmentTarget getItemTarget() => EnchantmentTarget.BOW;
public override EnchantmentType getEnchantType() => EnchantmentType.ARROW_DAMAGE;
public override int getMaxLevel() => 5;
public override string getName() => "power";
public override int getStartLevel() => 1;
}

View File

@@ -0,0 +1,34 @@
using Minecraft.Server.FourKit.Inventory;
namespace Minecraft.Server.FourKit.Enchantments;
public class ProjectileProtectionEnchantment : Enchantment
{
static readonly Material[] supportedItems = {
Material.LEATHER_HELMET, Material.LEATHER_CHESTPLATE, Material.LEATHER_LEGGINGS, Material.LEATHER_BOOTS,
Material.CHAINMAIL_HELMET, Material.CHAINMAIL_CHESTPLATE, Material.CHAINMAIL_LEGGINGS, Material.CHAINMAIL_BOOTS,
Material.GOLD_HELMET, Material.GOLD_CHESTPLATE, Material.GOLD_LEGGINGS, Material.GOLD_BOOTS,
Material.IRON_HELMET, Material.IRON_CHESTPLATE, Material.IRON_LEGGINGS, Material.IRON_BOOTS,
Material.DIAMOND_HELMET, Material.DIAMOND_CHESTPLATE, Material.DIAMOND_LEGGINGS, Material.DIAMOND_BOOTS,
};
static readonly EnchantmentType[] conflictedEnchants = {
EnchantmentType.PROTECTION_ENVIRONMENTAL,
EnchantmentType.PROTECTION_EXPLOSIVE,
EnchantmentType.PROTECTION_FIRE,
};
public override bool canEnchantItem(ItemStack item) => supportedItems.Contains(item.getType());
public override bool conflictsWith(Enchantment other) => conflictedEnchants.Contains(other.getEnchantType());
public override EnchantmentTarget getItemTarget() => EnchantmentTarget.ARMOR;
public override EnchantmentType getEnchantType() => EnchantmentType.PROTECTION_PROJECTILE;
public override int getMaxLevel() => 4;
public override string getName() => "projectileprotection";
public override int getStartLevel() => 1;
}

View File

@@ -0,0 +1,34 @@
using Minecraft.Server.FourKit.Inventory;
namespace Minecraft.Server.FourKit.Enchantments;
public class ProtectionEnchantment : Enchantment
{
static readonly Material[] supportedItems = {
Material.LEATHER_HELMET, Material.LEATHER_CHESTPLATE, Material.LEATHER_LEGGINGS, Material.LEATHER_BOOTS,
Material.CHAINMAIL_HELMET, Material.CHAINMAIL_CHESTPLATE, Material.CHAINMAIL_LEGGINGS, Material.CHAINMAIL_BOOTS,
Material.GOLD_HELMET, Material.GOLD_CHESTPLATE, Material.GOLD_LEGGINGS, Material.GOLD_BOOTS,
Material.IRON_HELMET, Material.IRON_CHESTPLATE, Material.IRON_LEGGINGS, Material.IRON_BOOTS,
Material.DIAMOND_HELMET, Material.DIAMOND_CHESTPLATE, Material.DIAMOND_LEGGINGS, Material.DIAMOND_BOOTS,
};
static readonly EnchantmentType[] conflictedEnchants = {
EnchantmentType.PROTECTION_EXPLOSIVE,
EnchantmentType.PROTECTION_FIRE,
EnchantmentType.PROTECTION_PROJECTILE,
};
public override bool canEnchantItem(ItemStack item) => supportedItems.Contains(item.getType());
public override bool conflictsWith(Enchantment other) => conflictedEnchants.Contains(other.getEnchantType());
public override EnchantmentTarget getItemTarget() => EnchantmentTarget.ARMOR;
public override EnchantmentType getEnchantType() => EnchantmentType.PROTECTION_ENVIRONMENTAL;
public override int getMaxLevel() => 4;
public override string getName() => "protection";
public override int getStartLevel() => 1;
}

View File

@@ -0,0 +1,26 @@
using Minecraft.Server.FourKit.Inventory;
namespace Minecraft.Server.FourKit.Enchantments;
public class PunchEnchantment : Enchantment
{
static readonly Material[] supportedItems = {
Material.BOW
};
static readonly EnchantmentType[] conflictedEnchants = { };
public override bool canEnchantItem(ItemStack item) => supportedItems.Contains(item.getType());
public override bool conflictsWith(Enchantment other) => conflictedEnchants.Contains(other.getEnchantType());
public override EnchantmentTarget getItemTarget() => EnchantmentTarget.BOW;
public override EnchantmentType getEnchantType() => EnchantmentType.ARROW_KNOCKBACK;
public override int getMaxLevel() => 2;
public override string getName() => "punch";
public override int getStartLevel() => 1;
}

View File

@@ -0,0 +1,26 @@
using Minecraft.Server.FourKit.Inventory;
namespace Minecraft.Server.FourKit.Enchantments;
public class RespirationEnchantment : Enchantment
{
static readonly Material[] supportedItems = {
Material.LEATHER_HELMET, Material.CHAINMAIL_HELMET, Material.GOLD_HELMET, Material.IRON_HELMET, Material.DIAMOND_HELMET,
};
static readonly EnchantmentType[] conflictedEnchants = { };
public override bool canEnchantItem(ItemStack item) => supportedItems.Contains(item.getType());
public override bool conflictsWith(Enchantment other) => conflictedEnchants.Contains(other.getEnchantType());
public override EnchantmentTarget getItemTarget() => EnchantmentTarget.ARMOR_HEAD;
public override EnchantmentType getEnchantType() => EnchantmentType.OXYGEN;
public override int getMaxLevel() => 3;
public override string getName() => "respiration";
public override int getStartLevel() => 1;
}

View File

@@ -0,0 +1,30 @@
using Minecraft.Server.FourKit.Inventory;
namespace Minecraft.Server.FourKit.Enchantments;
public class SharpnessEnchantment : Enchantment
{
static readonly Material[] supportedItems = {
Material.WOOD_SWORD, Material.STONE_SWORD, Material.IRON_SWORD, Material.GOLD_SWORD, Material.DIAMOND_SWORD,
Material.WOOD_AXE, Material.STONE_AXE, Material.IRON_AXE, Material.GOLD_AXE, Material.DIAMOND_AXE,
};
static readonly EnchantmentType[] conflictedEnchants = {
EnchantmentType.DAMAGE_ARTHOPODS,
EnchantmentType.DAMAGE_UNDEAD
};
public override bool canEnchantItem(ItemStack item) => supportedItems.Contains(item.getType());
public override bool conflictsWith(Enchantment other) => conflictedEnchants.Contains(other.getEnchantType());
public override EnchantmentTarget getItemTarget() => EnchantmentTarget.WEAPON;
public override EnchantmentType getEnchantType() => EnchantmentType.DAMAGE_ALL;
public override int getMaxLevel() => 5;
public override string getName() => "sharpness";
public override int getStartLevel() => 1;
}

View File

@@ -0,0 +1,33 @@
using Minecraft.Server.FourKit.Inventory;
namespace Minecraft.Server.FourKit.Enchantments;
public class SilkTouchEnchantment : Enchantment
{
static readonly Material[] supportedItems = {
Material.WOOD_PICKAXE, Material.STONE_PICKAXE, Material.IRON_PICKAXE, Material.GOLD_PICKAXE, Material.DIAMOND_PICKAXE,
Material.WOOD_AXE, Material.STONE_AXE, Material.IRON_AXE, Material.GOLD_AXE, Material.DIAMOND_AXE,
Material.WOOD_SPADE, Material.STONE_SPADE, Material.IRON_SPADE, Material.GOLD_SPADE, Material.DIAMOND_SPADE,
Material.WOOD_HOE, Material.STONE_HOE, Material.IRON_HOE, Material.GOLD_HOE, Material.DIAMOND_HOE,
Material.SHEARS
};
static readonly EnchantmentType[] conflictedEnchants = {
EnchantmentType.LOOT_BONUS_BLOCKS
};
public override bool canEnchantItem(ItemStack item) => supportedItems.Contains(item.getType());
public override bool conflictsWith(Enchantment other) => conflictedEnchants.Contains(other.getEnchantType());
public override EnchantmentTarget getItemTarget() => EnchantmentTarget.TOOL;
public override EnchantmentType getEnchantType() => EnchantmentType.SILK_TOUCH;
public override int getMaxLevel() => 1;
public override string getName() => "silktouch";
public override int getStartLevel() => 1;
}

View File

@@ -0,0 +1,30 @@
using Minecraft.Server.FourKit.Inventory;
namespace Minecraft.Server.FourKit.Enchantments;
public class SmiteEnchantment : Enchantment
{
static readonly Material[] supportedItems = {
Material.WOOD_SWORD, Material.STONE_SWORD, Material.IRON_SWORD, Material.GOLD_SWORD, Material.DIAMOND_SWORD,
Material.WOOD_AXE, Material.STONE_AXE, Material.IRON_AXE, Material.GOLD_AXE, Material.DIAMOND_AXE,
};
static readonly EnchantmentType[] conflictedEnchants = {
EnchantmentType.DAMAGE_ALL,
EnchantmentType.DAMAGE_ARTHOPODS
};
public override bool canEnchantItem(ItemStack item) => supportedItems.Contains(item.getType());
public override bool conflictsWith(Enchantment other) => conflictedEnchants.Contains(other.getEnchantType());
public override EnchantmentTarget getItemTarget() => EnchantmentTarget.WEAPON;
public override EnchantmentType getEnchantType() => EnchantmentType.DAMAGE_UNDEAD;
public override int getMaxLevel() => 5;
public override string getName() => "smite";
public override int getStartLevel() => 1;
}

View File

@@ -0,0 +1,30 @@
using Minecraft.Server.FourKit.Inventory;
namespace Minecraft.Server.FourKit.Enchantments;
public class ThornsEnchantment : Enchantment
{
static readonly Material[] supportedItems = {
Material.LEATHER_HELMET, Material.LEATHER_CHESTPLATE, Material.LEATHER_LEGGINGS, Material.LEATHER_BOOTS,
Material.CHAINMAIL_HELMET, Material.CHAINMAIL_CHESTPLATE, Material.CHAINMAIL_LEGGINGS, Material.CHAINMAIL_BOOTS,
Material.GOLD_HELMET, Material.GOLD_CHESTPLATE, Material.GOLD_LEGGINGS, Material.GOLD_BOOTS,
Material.IRON_HELMET, Material.IRON_CHESTPLATE, Material.IRON_LEGGINGS, Material.IRON_BOOTS,
Material.DIAMOND_HELMET, Material.DIAMOND_CHESTPLATE, Material.DIAMOND_LEGGINGS, Material.DIAMOND_BOOTS,
};
static readonly EnchantmentType[] conflictedEnchants = { };
public override bool canEnchantItem(ItemStack item) => supportedItems.Contains(item.getType());
public override bool conflictsWith(Enchantment other) => conflictedEnchants.Contains(other.getEnchantType());
public override EnchantmentTarget getItemTarget() => EnchantmentTarget.ARMOR;
public override EnchantmentType getEnchantType() => EnchantmentType.THORNS;
public override int getMaxLevel() => 3;
public override string getName() => "thorns";
public override int getStartLevel() => 1;
}

View File

@@ -0,0 +1,39 @@
using Minecraft.Server.FourKit.Inventory;
namespace Minecraft.Server.FourKit.Enchantments;
public class UnbreakingEnchantment : Enchantment
{
static readonly Material[] supportedItems = {
Material.WOOD_SWORD, Material.STONE_SWORD, Material.IRON_SWORD, Material.GOLD_SWORD, Material.DIAMOND_SWORD,
Material.WOOD_PICKAXE, Material.STONE_PICKAXE, Material.IRON_PICKAXE, Material.GOLD_PICKAXE, Material.DIAMOND_PICKAXE,
Material.WOOD_AXE, Material.STONE_AXE, Material.IRON_AXE, Material.GOLD_AXE, Material.DIAMOND_AXE,
Material.WOOD_SPADE, Material.STONE_SPADE, Material.IRON_SPADE, Material.GOLD_SPADE, Material.DIAMOND_SPADE,
Material.WOOD_HOE, Material.STONE_HOE, Material.IRON_HOE, Material.GOLD_HOE, Material.DIAMOND_HOE,
Material.LEATHER_HELMET, Material.LEATHER_CHESTPLATE, Material.LEATHER_LEGGINGS, Material.LEATHER_BOOTS,
Material.CHAINMAIL_HELMET, Material.CHAINMAIL_CHESTPLATE, Material.CHAINMAIL_LEGGINGS, Material.CHAINMAIL_BOOTS,
Material.GOLD_HELMET, Material.GOLD_CHESTPLATE, Material.GOLD_LEGGINGS, Material.GOLD_BOOTS,
Material.IRON_HELMET, Material.IRON_CHESTPLATE, Material.IRON_LEGGINGS, Material.IRON_BOOTS,
Material.DIAMOND_HELMET, Material.DIAMOND_CHESTPLATE, Material.DIAMOND_LEGGINGS, Material.DIAMOND_BOOTS,
Material.FISHING_ROD, Material.BOW,
Material.SHEARS, Material.FLINT_AND_STEEL, Material.CARROT_STICK
};
static readonly EnchantmentType[] conflictedEnchants = { };
public override bool canEnchantItem(ItemStack item) => supportedItems.Contains(item.getType());
public override bool conflictsWith(Enchantment other) => conflictedEnchants.Contains(other.getEnchantType());
public override EnchantmentTarget getItemTarget() => EnchantmentTarget.ALL;
public override EnchantmentType getEnchantType() => EnchantmentType.DURABILITY;
public override int getMaxLevel() => 3;
public override string getName() => "unbreaking";
public override int getStartLevel() => 1;
}

View File

@@ -0,0 +1,79 @@
namespace Minecraft.Server.FourKit.Entity;
/// <summary>
/// Represents an <see cref="Entity"/> that can take damage and has health.
/// </summary>
public class Damageable : Entity
{
private double _health = 20.0;
private double _maxHealth = 20.0;
private readonly double _originalMaxHealth = 20.0;
/// <summary>
/// Deals the given amount of damage to this entity.
/// This calls into the native server to apply real damage.
/// </summary>
/// <param name="amount">Amount of damage to deal.</param>
public void damage(double amount)
{
NativeBridge.DamagePlayer?.Invoke(getEntityId(), (float)amount);
}
/// <summary>
/// Gets the entity's health from 0 to <see cref="getMaxHealth"/>, where 0 is dead.
/// </summary>
/// <returns>The current health.</returns>
public double getHealth() => _health;
/// <summary>
/// Gets the maximum health this entity has.
/// </summary>
/// <returns>The maximum health.</returns>
public double getMaxHealth() => _maxHealth;
/// <summary>
/// Resets the max health to the original amount.
/// </summary>
public void resetMaxHealth()
{
_maxHealth = _originalMaxHealth;
if (_health > _maxHealth)
_health = _maxHealth;
}
/// <summary>
/// Sets the entity's health from 0 to <see cref="getMaxHealth"/>, where 0 is dead.
/// This calls into the native server to apply the health change.
/// </summary>
/// <param name="health">New health value.</param>
public void setHealth(double health)
{
NativeBridge.SetPlayerHealth?.Invoke(getEntityId(), (float)Math.Clamp(health, 0.0, _maxHealth));
}
/// <summary>
/// Sets the maximum health this entity can have.
/// If the entity's current health exceeds the new maximum, it is clamped.
/// </summary>
/// <param name="health">New maximum health value.</param>
public void setMaxHealth(double health)
{
_maxHealth = health;
if (_health > _maxHealth)
_health = _maxHealth;
}
// --- Internal setter used by the bridge ---
/// <summary>
/// Updates health directly. Called internally by the bridge.
/// </summary>
/// <param name="health">The new health value.</param>
internal void SetHealthInternal(double health) => _health = health;
/// <summary>
/// Updates max health directly. Called internally by the bridge.
/// </summary>
/// <param name="maxHealth">The new max health value.</param>
internal void SetMaxHealthInternal(double maxHealth) => _maxHealth = maxHealth;
}

View File

@@ -0,0 +1,66 @@
namespace Minecraft.Server.FourKit.Entity;
// eh
/// <summary>
/// Enum representing the reason a player was disconnected from the server.
/// mirrored from <c>DisconnectPacket::eDisconnectReason</c>.
/// </summary>
public enum DisconnectReason
{
/// <summary>No specific reason.</summary>
NONE = 0,
/// <summary>The player quit voluntarily.</summary>
QUITTING = 1,
/// <summary>The connection was closed.</summary>
CLOSED = 2,
/// <summary>The login took too long.</summary>
LOGIN_TOO_LONG = 3,
/// <summary>The player had an illegal stance.</summary>
ILLEGAL_STANCE = 4,
/// <summary>The player had an illegal position.</summary>
ILLEGAL_POSITION = 5,
/// <summary>The player moved too quickly.</summary>
MOVED_TOO_QUICKLY = 6,
/// <summary>The player was flying when not allowed.</summary>
NO_FLYING = 7,
/// <summary>The player was kicked by an operator or plugin.</summary>
KICKED = 8,
/// <summary>The connection timed out.</summary>
TIME_OUT = 9,
/// <summary>Packet overflow.</summary>
OVERFLOW = 10,
/// <summary>End of stream reached unexpectedly.</summary>
END_OF_STREAM = 11,
/// <summary>The server is full.</summary>
SERVER_FULL = 12,
/// <summary>The server is outdated.</summary>
OUTDATED_SERVER = 13,
/// <summary>The client is outdated.</summary>
OUTDATED_CLIENT = 14,
/// <summary>An unexpected packet was received.</summary>
UNEXPECTED_PACKET = 15,
/// <summary>Connection creation failed.</summary>
CONNECTION_CREATION_FAILED = 16,
/// <summary>The host does not have multiplayer privileges.</summary>
NO_MULTIPLAYER_PRIVILEGES_HOST = 17,
/// <summary>The joining player does not have multiplayer privileges.</summary>
NO_MULTIPLAYER_PRIVILEGES_JOIN = 18,
/// <summary>All local players lack UGC permissions.</summary>
NO_UGC_ALL_LOCAL = 19,
/// <summary>A single local player lacks UGC permissions.</summary>
NO_UGC_SINGLE_LOCAL = 20,
/// <summary>All local players have content restrictions.</summary>
CONTENT_RESTRICTED_ALL_LOCAL = 21,
/// <summary>A single local player has content restrictions.</summary>
CONTENT_RESTRICTED_SINGLE_LOCAL = 22,
/// <summary>A remote player lacks UGC permissions.</summary>
NO_UGC_REMOTE = 23,
/// <summary>No friends in the game.</summary>
NO_FRIENDS_IN_GAME = 24,
/// <summary>The player was banned.</summary>
BANNED = 25,
/// <summary>The player is not friends with the host.</summary>
NOT_FRIENDS_WITH_HOST = 26,
/// <summary>NAT type mismatch.</summary>
NAT_MISMATCH = 27,
}

View File

@@ -0,0 +1,192 @@
namespace Minecraft.Server.FourKit.Entity;
using Minecraft.Server.FourKit.Util;
/// <summary>
/// Represents a base entity in the world
/// </summary>
public class Entity
{
private Location _location = new();
private Guid _uniqueId = Guid.NewGuid();
private float _fallDistance;
private int _dimensionId;
private int _entityId;
private EntityType _entityType = EntityType.UNKNOWN;
private bool _onGround;
private double _velocityX, _velocityY, _velocityZ;
/// <summary>
/// Gets the entity's current position.
/// </summary>
/// <returns>a new copy of <see cref="Location"/> containing the position of this entity</returns>
public Location getLocation() => _location;
/// <summary>
/// Returns a unique id for this entity
/// </summary>
/// <returns>Entity id</returns>
public virtual int getEntityId() => _entityId;
/// <summary>
/// Get the type of the entity.
/// </summary>
/// <returns>The <see cref="EntityType"/> of this entity.</returns>
public new virtual EntityType getType() => _entityType;
public new virtual EntityType GetType() => _entityType;
/// <summary>
/// Returns a unique and persistent id for this entity. Note that this is not the standard UUID for players.
/// </summary>
/// <returns>A <see cref="Guid"/> unique to this entity.</returns>
public Guid getUniqueId() => _uniqueId;
/// <summary>
/// Teleports this entity to the given location.
/// This calls into the native server to perform the actual teleport.
/// </summary>
/// <param name="location">The destination location.</param>
/// <returns><c>true</c> if the teleport was successful.</returns>
public virtual bool teleport(Location location)
{
int targetDimId = location.LocationWorld?.getDimensionId() ?? _dimensionId;
NativeBridge.TeleportEntity?.Invoke(getEntityId(), targetDimId, location.getX(), location.getY(), location.getZ());
SetLocation(location);
return true;
}
/// <summary>
/// Sets the fall distance for this entity.
/// </summary>
/// <param name="distance">The fall distance value.</param>
public void setFallDistance(float distance)
{
_fallDistance = distance;
NativeBridge.SetFallDistance?.Invoke(getEntityId(), distance);
}
/// <summary>
/// Returns the distance this entity has fallen.
/// </summary>
/// <returns>The current fall distance.</returns>
public float getFallDistance() => _fallDistance;
/// <summary>
/// Gets the current world this entity resides in.
/// </summary>
/// <returns>World containing this entity.</returns>
public World getWorld() => FourKit.getWorld(_dimensionId);
/// <summary>
/// Returns true if the entity is supported by a block. This value is a
/// state updated by the server and is not recalculated unless the entity moves.
/// </summary>
/// <returns>True if entity is on ground.</returns>
public bool isOnGround() => _onGround;
/// <summary>
/// Gets this entity's current velocity.
/// </summary>
/// <returns>Current travelling velocity of this entity.</returns>
public Vector getVelocity() => new Vector(_velocityX, _velocityY, _velocityZ);
/// <summary>
/// Sets this entity's velocity.
/// </summary>
/// <param name="velocity">New velocity to travel with.</param>
public void setVelocity(Vector velocity)
{
_velocityX = velocity.getX();
_velocityY = velocity.getY();
_velocityZ = velocity.getZ();
NativeBridge.SetVelocity?.Invoke(getEntityId(), velocity.getX(), velocity.getY(), velocity.getZ());
}
/// <summary>
/// Returns whether this entity is inside a vehicle.
/// </summary>
/// <returns><c>true</c> if the entity is in a vehicle.</returns>
public bool isInsideVehicle()
{
return (NativeBridge.GetVehicleId?.Invoke(getEntityId()) ?? -1) >= 0;
}
/// <summary>
/// Leave the current vehicle. If the entity is currently in a vehicle
/// (and is removed from it), <c>true</c> will be returned, otherwise
/// <c>false</c> will be returned.
/// </summary>
/// <returns><c>true</c> if the entity was in a vehicle.</returns>
public bool leaveVehicle()
{
return NativeBridge.LeaveVehicle?.Invoke(getEntityId()) != 0;
}
/// <summary>
/// Get the vehicle that this entity is inside. If there is no vehicle,
/// <c>null</c> will be returned.
/// </summary>
/// <returns>The current vehicle, or <c>null</c>.</returns>
public Entity? getVehicle()
{
int vehicleId = NativeBridge.GetVehicleId?.Invoke(getEntityId()) ?? -1;
if (vehicleId < 0) return null;
return FourKit.GetEntityByEntityId(vehicleId);
}
/// <summary>
/// Eject any passenger.
/// </summary>
/// <returns><c>true</c> if there was a passenger.</returns>
public bool eject()
{
return NativeBridge.Eject?.Invoke(getEntityId()) != 0;
}
/// <summary>
/// Gets the primary passenger of a vehicle. For vehicles that could
/// have multiple passengers, this will only return the primary passenger.
/// </summary>
/// <returns>The passenger entity, or <c>null</c>.</returns>
public Entity? getPassenger()
{
int passengerId = NativeBridge.GetPassengerId?.Invoke(getEntityId()) ?? -1;
if (passengerId < 0) return null;
return FourKit.GetEntityByEntityId(passengerId);
}
/// <summary>
/// Set the passenger of a vehicle.
/// </summary>
/// <param name="passenger">The new passenger.</param>
/// <returns><c>false</c> if it could not be done for whatever reason.</returns>
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)
{
_location = location;
}
internal void SetFallDistanceInternal(float distance) => _fallDistance = distance;
internal void SetUniqueId(Guid id)
{
_uniqueId = id;
}
internal void SetDimensionInternal(int dimensionId) => _dimensionId = dimensionId;
internal void SetEntityIdInternal(int entityId) => _entityId = entityId;
internal void SetEntityTypeInternal(EntityType entityType) => _entityType = entityType;
internal void SetOnGroundInternal(bool onGround) => _onGround = onGround;
internal void SetVelocityInternal(double x, double y, double z)
{
_velocityX = x;
_velocityY = y;
_velocityZ = z;
}
}

View File

@@ -0,0 +1,132 @@
namespace Minecraft.Server.FourKit.Entity;
/// <summary>
/// Represents the type of an <see cref="Entity"/>.
/// </summary>
public enum EntityType
{
/// <summary>An arrow projectile; may get stuck in the ground.</summary>
ARROW,
/// <summary>A bat.</summary>
BAT,
/// <summary>A blaze.</summary>
BLAZE,
/// <summary>A placed boat.</summary>
BOAT,
/// <summary>A cave spider.</summary>
CAVE_SPIDER,
/// <summary>A chicken.</summary>
CHICKEN,
/// <summary>A complex entity part.</summary>
COMPLEX_PART,
/// <summary>A cow.</summary>
COW,
/// <summary>A creeper.</summary>
CREEPER,
/// <summary>An item resting on the ground.</summary>
DROPPED_ITEM,
/// <summary>A flying chicken egg.</summary>
EGG,
/// <summary>An ender crystal.</summary>
ENDER_CRYSTAL,
/// <summary>An ender dragon.</summary>
ENDER_DRAGON,
/// <summary>A flying ender pearl.</summary>
ENDER_PEARL,
/// <summary>An ender eye signal.</summary>
ENDER_SIGNAL,
/// <summary>An enderman.</summary>
ENDERMAN,
/// <summary>An experience orb.</summary>
EXPERIENCE_ORB,
/// <summary>A block that is going to or is about to fall.</summary>
FALLING_BLOCK,
/// <summary>A flying large fireball, as thrown by a Ghast for example.</summary>
FIREBALL,
/// <summary>A firework rocket.</summary>
FIREWORK,
/// <summary>A fishing line and bobber.</summary>
FISHING_HOOK,
/// <summary>A ghast.</summary>
GHAST,
/// <summary>A giant.</summary>
GIANT,
/// <summary>A horse.</summary>
HORSE,
/// <summary>An iron golem.</summary>
IRON_GOLEM,
/// <summary>An item frame on a wall.</summary>
ITEM_FRAME,
/// <summary>A leash attached to a fencepost.</summary>
LEASH_HITCH,
/// <summary>A bolt of lightning.</summary>
LIGHTNING,
/// <summary>A magma cube.</summary>
MAGMA_CUBE,
/// <summary>A minecart.</summary>
MINECART,
/// <summary>A minecart with a chest.</summary>
MINECART_CHEST,
/// <summary>A minecart with a command block.</summary>
MINECART_COMMAND,
/// <summary>A minecart with a furnace.</summary>
MINECART_FURNACE,
/// <summary>A minecart with a hopper.</summary>
MINECART_HOPPER,
/// <summary>A minecart with a mob spawner.</summary>
MINECART_MOB_SPAWNER,
/// <summary>A minecart with TNT.</summary>
MINECART_TNT,
/// <summary>A mooshroom.</summary>
MUSHROOM_COW,
/// <summary>An ocelot.</summary>
OCELOT,
/// <summary>A painting on a wall.</summary>
PAINTING,
/// <summary>A pig.</summary>
PIG,
/// <summary>A zombie pigman.</summary>
PIG_ZOMBIE,
/// <summary>A player.</summary>
PLAYER,
/// <summary>Primed TNT that is about to explode.</summary>
PRIMED_TNT,
/// <summary>A sheep.</summary>
SHEEP,
/// <summary>A silverfish.</summary>
SILVERFISH,
/// <summary>A skeleton.</summary>
SKELETON,
/// <summary>A slime.</summary>
SLIME,
/// <summary>A flying small fireball, such as thrown by a Blaze or player.</summary>
SMALL_FIREBALL,
/// <summary>A flying snowball.</summary>
SNOWBALL,
/// <summary>A snowman.</summary>
SNOWMAN,
/// <summary>A spider.</summary>
SPIDER,
/// <summary>A flying splash potion.</summary>
SPLASH_POTION,
/// <summary>A squid.</summary>
SQUID,
/// <summary>A flying experience bottle.</summary>
THROWN_EXP_BOTTLE,
/// <summary>An unknown entity without an Entity Class.</summary>
UNKNOWN,
/// <summary>A villager.</summary>
VILLAGER,
/// <summary>A weather entity.</summary>
WEATHER,
/// <summary>A witch.</summary>
WITCH,
/// <summary>A wither.</summary>
WITHER,
/// <summary>A flying wither skull projectile.</summary>
WITHER_SKULL,
/// <summary>A wolf.</summary>
WOLF,
/// <summary>A zombie.</summary>
ZOMBIE,
}

View File

@@ -0,0 +1,175 @@
namespace Minecraft.Server.FourKit.Entity;
using System.Runtime.InteropServices;
using Minecraft.Server.FourKit.Inventory;
/// <summary>
/// Represents a human entity in the world (e.g. a player).
/// </summary>
public abstract class HumanEntity : LivingEntity, InventoryHolder
{
private GameMode _gameMode = GameMode.SURVIVAL;
private string _name = string.Empty;
internal PlayerInventory _playerInventory = new();
internal Inventory _enderChestInventory = new("Ender Chest", InventoryType.ENDER_CHEST, 27);
private ItemStack? _cursorItem;
private bool _sleeping;
private int _sleepTicks;
/// <summary>
/// Gets this human's current <see cref="GameMode"/>.
/// </summary>
/// <returns>The current game mode.</returns>
public GameMode getGameMode() => _gameMode;
/// <summary>
/// Returns the name of this player.
/// </summary>
/// <returns>The display name.</returns>
public string getName() => _name;
/// <summary>
/// Sets this human's current <see cref="GameMode"/>.
/// </summary>
/// <param name="mode">The new game mode.</param>
public void setGameMode(GameMode mode)
{
NativeBridge.SetPlayerGameMode?.Invoke(getEntityId(), (int)mode);
}
/// <summary>
/// Get the player's inventory.
/// </summary>
/// <returns>The inventory of the player, this also contains the armor slots.</returns>
Inventory InventoryHolder.getInventory() => getInventory();
/// <summary>
/// Get the player's inventory.
/// This also contains the armor slots.
/// </summary>
/// <returns>The player's inventory.</returns>
public PlayerInventory getInventory()
{
return _playerInventory;
}
/// <summary>
/// Get the player's EnderChest inventory.
/// </summary>
/// <returns>The EnderChest of the player.</returns>
public Inventory getEnderChest()
{
return _enderChestInventory;
}
/// <summary>
/// Returns the ItemStack currently in your hand, can be empty.
/// </summary>
/// <returns>The ItemStack of the item you are currently holding.</returns>
public ItemStack? getItemInHand()
{
return _playerInventory.getItemInHand();
}
/// <summary>
/// Sets the item to the given ItemStack, this will replace whatever the
/// user was holding.
/// </summary>
/// <param name="item">The ItemStack which will end up in the hand.</param>
public void setItemInHand(ItemStack? item)
{
_playerInventory.setItemInHand(item);
}
/// <summary>
/// Returns the ItemStack currently on your cursor, can be empty.
/// Will always be empty if the player currently has no open window.
/// </summary>
/// <returns>The ItemStack of the item you are currently moving around.</returns>
public ItemStack? getItemOnCursor() => _cursorItem;
/// <summary>
/// Sets the item to the given ItemStack, this will replace whatever the
/// user was moving. Will always be empty if the player currently has no open window.
/// </summary>
/// <param name="item">The ItemStack which will end up in the hand.</param>
public void setItemOnCursor(ItemStack? item) => _cursorItem = item;
/// <summary>
/// If the player currently has an inventory window open, this method will
/// close it on both the server and client side.
/// </summary>
public void closeInventory()
{
NativeBridge.CloseContainer?.Invoke(getEntityId());
}
/// <summary>
/// Opens an inventory window with the specified inventory on the top.
/// </summary>
/// <param name="inventory">The inventory to open.</param>
/// <returns>The newly opened InventoryView, or null if it could not be opened.</returns>
public InventoryView? openInventory(Inventory inventory)
{
if (NativeBridge.OpenVirtualContainer == null)
return null;
closeInventory();
int nativeType = inventory.getType() switch
{
InventoryType.CHEST => 0,
InventoryType.DISPENSER => 3,
InventoryType.DROPPER => 10,
InventoryType.HOPPER => 5,
_ => 0,
};
int size = inventory.getSize();
int[] buf = new int[size * 3];
for (int i = 0; i < size; i++)
{
var item = inventory._items[i];
buf[i * 3] = item?.getTypeId() ?? 0;
buf[i * 3 + 1] = item?.getAmount() ?? 0;
buf[i * 3 + 2] = item?.getDurability() ?? 0;
}
string title = inventory.getName();
int titleByteLen = System.Text.Encoding.UTF8.GetByteCount(title);
IntPtr titlePtr = Marshal.StringToCoTaskMemUTF8(title);
var gh = GCHandle.Alloc(buf, GCHandleType.Pinned);
try
{
NativeBridge.OpenVirtualContainer(getEntityId(), nativeType, titlePtr, titleByteLen, size, gh.AddrOfPinnedObject());
}
finally
{
gh.Free();
Marshal.FreeCoTaskMem(titlePtr);
}
var view = new InventoryView(inventory, getInventory(), this, inventory.getType());
return view;
}
internal void SetGameModeInternal(GameMode mode) => _gameMode = mode;
internal void SetNameInternal(string name) => _name = name;
internal void SetSleepingInternal(bool sleeping) => _sleeping = sleeping;
internal void SetSleepTicksInternal(int ticks) => _sleepTicks = ticks;
/// <summary>
/// Returns whether this player is slumbering.
/// </summary>
/// <returns>slumber state</returns>
public bool isSleeping() => _sleeping;
/// <summary>
/// Get the sleep ticks of the player. This value may be capped.
/// </summary>
/// <returns>slumber ticks</returns>
public int getSleepTicks() => _sleepTicks;
}

View File

@@ -0,0 +1,32 @@
namespace Minecraft.Server.FourKit.Entity;
using Minecraft.Server.FourKit.Inventory;
/// <summary>
/// Represents a dropped item on the ground.
/// </summary>
public class Item : Entity
{
private ItemStack _itemStack;
internal Item(int entityId, int dimId, double x, double y, double z, ItemStack itemStack)
{
SetEntityIdInternal(entityId);
SetEntityTypeInternal(EntityType.DROPPED_ITEM);
SetDimensionInternal(dimId);
SetLocation(new Location(FourKit.getWorld(dimId), x, y, z));
_itemStack = itemStack;
}
/// <summary>
/// Gets the item stack associated with this item.
/// </summary>
/// <returns>An item stack.</returns>
public ItemStack getItemStack() => _itemStack;
/// <summary>
/// Sets the item stack of this item.
/// </summary>
/// <param name="stack">The new item stack.</param>
public void setItemStack(ItemStack stack) => _itemStack = stack;
}

View File

@@ -0,0 +1,51 @@
namespace Minecraft.Server.FourKit.Entity;
/// <summary>
/// Represents a living entity in the world that has health and can take damage.
/// </summary>
public class LivingEntity : Damageable
{
private double _eyeHeight = 1.62;
internal LivingEntity() { }
internal LivingEntity(int entityId, EntityType entityType, int dimId, double x, double y, double z,
float health = 20f, float maxHealth = 20f)
{
SetEntityIdInternal(entityId);
SetEntityTypeInternal(entityType);
SetDimensionInternal(dimId);
SetLocation(new Location(FourKit.getWorld(dimId), x, y, z));
if (maxHealth > 0)
SetMaxHealthInternal(maxHealth);
SetHealthInternal(health);
}
/// <summary>
/// Gets the height of the living entity's eyes above its <see cref="Location"/>.
/// </summary>
/// <returns>The eye height.</returns>
public double getEyeHeight() => _eyeHeight;
/// <summary>
/// Gets the height of the living entity's eyes above its <see cref="Location"/>.
/// </summary>
/// <param name="ignoreSneaking">If <c>true</c>, returns the standing eye height regardless of sneak state.</param>
/// <returns>The eye height.</returns>
public double getEyeHeight(bool ignoreSneaking)
{
if (ignoreSneaking)
return _eyeHeight;
// When sneaking the eye height is slightly lower
return _eyeHeight - 0.08;
}
// --- Internal setter used by the bridge ---
/// <summary>
/// Updates the eye height. Called internally by the bridge.
/// </summary>
/// <param name="eyeHeight">The new eye height.</param>
internal void SetEyeHeightInternal(double eyeHeight) => _eyeHeight = eyeHeight;
}

View File

@@ -0,0 +1,26 @@
namespace Minecraft.Server.FourKit.Entity;
/// <summary>
/// Represents a player identity that may or may not currently be online.
/// </summary>
public interface OfflinePlayer
{
/// <summary>Returns the name of this player.</summary>
/// <returns>The player's name.</returns>
string getName();
/// <summary>Gets a Player object that this represents, if there is one.</summary>
/// <returns>A <see cref="Player"/> instance if the player is online; otherwise <c>null</c>.</returns>
Player? getPlayer();
/// <summary>
/// Returns the UUID that uniquely identifies this player across sessions.
/// This is the player-specific UUID, not the entity UUID.
/// </summary>
/// <returns>The player's unique identifier.</returns>
Guid getUniqueId();
/// <summary>Checks if this player is currently online.</summary>
/// <returns><c>true</c> if the player is online; otherwise <c>false</c>.</returns>
bool isOnline();
}

View File

@@ -0,0 +1,665 @@
namespace Minecraft.Server.FourKit.Entity;
using System.Runtime.InteropServices;
using Minecraft.Server.FourKit.Command;
using Minecraft.Server.FourKit.Experimental;
using Minecraft.Server.FourKit.Inventory;
using Minecraft.Server.FourKit.Net;
/// <summary>
/// Represents a player connected to the server.
/// </summary>
public class Player : HumanEntity, OfflinePlayer, CommandSender
{
private float _saturation = 5.0f;
private float _walkSpeed = 0.2f;
private float _exhaustion;
private int _foodLevel = 20;
private int _level;
private float _exp;
private int _totalExperience;
private Guid _playerUniqueId;
private ulong _playerRawOnlineXUID;
private ulong _playerRawOfflineXUID;
private string? _displayName;
private bool _sneaking;
private bool _sprinting;
private bool _allowFlight;
private bool _sleepingIgnored;
private PlayerConnection _connection;
internal bool IsOnline { get; set; }
internal Player(int entityId, string name)
{
SetEntityIdInternal(entityId);
SetEntityTypeInternal(EntityType.PLAYER);
SetNameInternal(name);
IsOnline = true;
_playerInventory._holder = this;
_connection = new PlayerConnection(this);
}
/// <inheritdoc/>
public override EntityType getType() => EntityType.PLAYER;
/// <inheritdoc/>
public override EntityType GetType() => EntityType.PLAYER;
/// <inheritdoc/>
public override bool teleport(Location location)
{
int targetDimId = location.LocationWorld?.getDimensionId() ?? getLocation().LocationWorld?.getDimensionId() ?? 0;
NativeBridge.TeleportEntity?.Invoke(getEntityId(), targetDimId, location.X, location.Y, location.Z);
SetLocation(location);
return true;
}
/// <summary>
/// <b>Experimental.</b> Gets the player's <see cref="PlayerConnection"/>, which can be used
/// to send raw packet data directly to the client.
/// </summary>
/// <returns>The player's connection.</returns>
public PlayerConnection getConnection() => _connection;
/// <inheritdoc/>
public Player? getPlayer() => IsOnline ? this : null;
/// <summary>
/// Gets the "friendly" name to display of this player.
/// This may include color. If no custom display name has been set,
/// this returns the player's <see cref="HumanEntity.getName"/>.
/// </summary>
/// <returns>The display name.</returns>
public string getDisplayName() => _displayName ?? getName();
/// <summary>
/// Sets the "friendly" name to display of this player.
/// </summary>
/// <param name="name">The display name, or <c>null</c> to reset to <see cref="HumanEntity.getName"/>.</param>
public void setDisplayName(string? name)
{
_displayName = name;
}
/// <inheritdoc/>
public bool isOnline() => IsOnline;
/// <summary>
/// Returns the UUID that uniquely identifies this player across sessions.
/// This is the player-specific UUID, not the entity UUID.
/// </summary>
/// <returns>The player's unique identifier.</returns>
public new Guid getUniqueId() => _playerUniqueId;
/// <summary>
/// <b>Experimental.</b> Gets the raw online XUID (Xbox User ID) for this player.
/// The online XUID is used for guests.
/// </summary>
/// <returns>The raw online XUID value.</returns>
public ulong getRawOnlineXUID() => _playerRawOnlineXUID;
/// <summary>
/// <b>Experimental.</b> Gets the raw offline XUID (Xbox User ID) for this player.
/// The offline XUID is the main XUID used by the client.
/// </summary>
/// <returns>The raw offline XUID value.</returns>
public ulong getRawOfflineXUID() => _playerRawOfflineXUID;
/// <summary>
/// Gets the player's estimated ping in milliseconds.
/// This value represents a weighted average of the response time to application layer ping packets sent. This value does not represent the network round trip time and as such may have less granularity and be impacted by other sources. For these reasons it should not be used for anti-cheat purposes. Its recommended use is only as a qualitative indicator of connection quality.
/// </summary>
/// <returns>The player's estimated ping in milliseconds.</returns>
public int getPing()
{
if (NativeBridge.GetPlayerLatency == null)
return -1;
return NativeBridge.GetPlayerLatency(getEntityId());
}
/// <summary>
/// Gets the player's current saturation level.
/// Saturation acts as a buffer before hunger begins to deplete.
/// </summary>
/// <returns>The current saturation level.</returns>
public float getSaturation() => _saturation;
/// <summary>
/// Gets the current allowed speed that a client can walk.
/// The default value is 0.2.
/// </summary>
/// <returns>The current walk speed.</returns>
public float getWalkSpeed() => _walkSpeed;
/// <summary>
/// Sets the speed at which a client will walk.
/// This calls into the native server to apply the change.
/// </summary>
/// <param name="value">The new walk speed.</param>
public void setWalkSpeed(float value)
{
_walkSpeed = value;
NativeBridge.SetWalkSpeed?.Invoke(getEntityId(), value);
}
/// <summary>
/// Returns if the player is in sneak mode.
/// </summary>
/// <returns>True if player is in sneak mode.</returns>
public bool isSneaking() => _sneaking;
/// <summary>
/// Gets whether the player is sprinting or not.
/// </summary>
/// <returns>True if player is sprinting.</returns>
public bool isSprinting() => _sprinting;
/// <summary>
/// Sets whether the player is ignored as not sleeping. If everyone is
/// either sleeping or has this flag set, then time will advance to the
/// next day. If everyone has this flag set but no one is actually in
/// bed, then nothing will happen.
/// </summary>
/// <param name="isSleeping">Whether to ignore.</param>
public void setSleepingIgnored(bool isSleeping)
{
_sleepingIgnored = isSleeping;
NativeBridge.SetSleepingIgnored?.Invoke(getEntityId(), isSleeping ? 1 : 0);
}
/// <summary>
/// Returns whether the player is sleeping ignored.
/// </summary>
/// <returns>Whether player is ignoring sleep.</returns>
public bool isSleepingIgnored() => _sleepingIgnored;
/// <summary>
/// Play a sound for a player at the location.
/// This function will fail silently if Location or Sound are null.
/// </summary>
/// <param name="location">The location to play the sound.</param>
/// <param name="sound">The sound to play.</param>
/// <param name="volume">The volume of the sound.</param>
/// <param name="pitch">The pitch of the sound.</param>
public void playSound(Location location, Sound sound, float volume, float pitch)
{
if (location == null)
return;
NativeBridge.PlaySound?.Invoke(getEntityId(), (int)sound, location.X, location.Y, location.Z, volume, pitch);
}
/// <summary>
/// Determines if the Player is allowed to fly via jump key double-tap
/// like in creative mode.
/// </summary>
/// <returns>True if the player is allowed to fly.</returns>
public bool getAllowFlight() => _allowFlight;
/// <summary>
/// Sets if the Player is allowed to fly via jump key double-tap like
/// in creative mode.
/// </summary>
/// <param name="flight">If flight should be allowed.</param>
public void setAllowFlight(bool flight)
{
_allowFlight = flight;
NativeBridge.SetAllowFlight?.Invoke(getEntityId(), flight ? 1 : 0);
}
/// <inheritdoc/>
public void sendMessage(string message)
{
if (string.IsNullOrEmpty(message) || NativeBridge.SendMessage == null)
return;
if (message.Length > FourKit.MAX_CHAT_LENGTH)
message = message[..FourKit.MAX_CHAT_LENGTH];
IntPtr ptr = Marshal.StringToCoTaskMemUTF8(message);
try
{
NativeBridge.SendMessage(getEntityId(), ptr, System.Text.Encoding.UTF8.GetByteCount(message));
}
finally
{
Marshal.FreeCoTaskMem(ptr);
}
}
/// <inheritdoc/>
public void sendMessage(string[] messages)
{
foreach (var msg in messages)
sendMessage(msg);
}
/// <summary>
/// Kicks player with the default <see cref="DisconnectReason.KICKED"/> reason.
/// </summary>
public void kickPlayer()
{
NativeBridge.KickPlayer?.Invoke(getEntityId(), (int)DisconnectReason.KICKED);
}
/// <summary>
/// Bans the player by UID with the specified reason and disconnects them.
/// </summary>
/// <param name="reason">The ban reason.</param>
/// <returns><c>true</c> if the ban was applied successfully.</returns>
public bool banPlayer(string reason)
{
if (NativeBridge.BanPlayer == null) return false;
IntPtr ptr = Marshal.StringToCoTaskMemUTF8(reason ?? string.Empty);
try
{
int byteLen = System.Text.Encoding.UTF8.GetByteCount(reason ?? string.Empty);
return NativeBridge.BanPlayer(getEntityId(), ptr, byteLen) != 0;
}
finally
{
Marshal.FreeCoTaskMem(ptr);
}
}
/// <summary>
/// Bans the player's IP address with the specified reason.
/// </summary>
/// <param name="reason">The ban reason.</param>
/// <returns><c>true</c> if the IP ban was applied successfully.</returns>
public bool banPlayerIp(string reason)
{
if (NativeBridge.BanPlayerIp == null) return false;
IntPtr ptr = Marshal.StringToCoTaskMemUTF8(reason ?? string.Empty);
try
{
int byteLen = System.Text.Encoding.UTF8.GetByteCount(reason ?? string.Empty);
return NativeBridge.BanPlayerIp(getEntityId(), ptr, byteLen) != 0;
}
finally
{
Marshal.FreeCoTaskMem(ptr);
}
}
/// <summary>
/// Gets the socket address of this player.
/// </summary>
/// <returns>The player's socket address, or <c>null</c> if the address could not be determined.</returns>
public InetSocketAddress? getAddress()
{
if (NativeBridge.GetPlayerAddress == null)
return null;
const int ipBufSize = 64;
IntPtr ipBuf = Marshal.AllocCoTaskMem(ipBufSize);
IntPtr portBuf = Marshal.AllocCoTaskMem(sizeof(int));
try
{
int result = NativeBridge.GetPlayerAddress(getEntityId(), ipBuf, ipBufSize, portBuf);
if (result == 0)
return null;
string? ip = Marshal.PtrToStringAnsi(ipBuf);
int port = Marshal.ReadInt32(portBuf);
if (string.IsNullOrEmpty(ip))
return null;
return new InetSocketAddress(new InetAddress(ip), port);
}
finally
{
Marshal.FreeCoTaskMem(ipBuf);
Marshal.FreeCoTaskMem(portBuf);
}
}
/// <summary>
/// Gets the players current experience level.
/// </summary>
/// <returns>Current experience level.</returns>
public int getLevel() => _level;
/// <summary>
/// Sets the players current experience level.
/// </summary>
/// <param name="level">New experience level.</param>
public void setLevel(int level)
{
_level = level;
NativeBridge.SetLevel?.Invoke(getEntityId(), level);
}
/// <summary>
/// Gets the players current experience points towards the next level.
/// This is a percentage value. 0 is "no progress" and 1 is "next level".
/// </summary>
/// <returns>Current experience points.</returns>
public float getExp() => _exp;
/// <summary>
/// Sets the players current experience points towards the next level.
/// This is a percentage value. 0 is "no progress" and 1 is "next level".
/// </summary>
/// <param name="exp">New experience points.</param>
public void setExp(float exp)
{
_exp = exp;
NativeBridge.SetExp?.Invoke(getEntityId(), exp);
}
/// <summary>
/// Gives the player the amount of experience specified.
/// </summary>
/// <param name="amount">Exp amount to give.</param>
public void giveExp(int amount)
{
NativeBridge.GiveExp?.Invoke(getEntityId(), amount);
}
/// <summary>
/// Gives the player the amount of experience levels specified.
/// Levels can be taken by specifying a negative amount.
/// </summary>
/// <param name="amount">Amount of experience levels to give or take.</param>
public void giveExpLevels(int amount)
{
NativeBridge.GiveExpLevels?.Invoke(getEntityId(), amount);
}
/// <summary>
/// Gets the players current exhaustion level.
/// Exhaustion controls how fast the food level drops. While you have a
/// certain amount of exhaustion, your saturation will drop to zero, and
/// then your food will drop to zero.
/// </summary>
/// <returns>Exhaustion level.</returns>
public float getExhaustion() => _exhaustion;
/// <summary>
/// Sets the players current exhaustion level.
/// </summary>
/// <param name="value">Exhaustion level.</param>
public void setExhaustion(float value)
{
_exhaustion = value;
NativeBridge.SetExhaustion?.Invoke(getEntityId(), value);
}
/// <summary>
/// Sets the players current saturation level.
/// </summary>
/// <param name="value">Saturation level.</param>
public void setSaturation(float value)
{
_saturation = value;
NativeBridge.SetSaturation?.Invoke(getEntityId(), value);
}
/// <summary>
/// Gets the players current food level.
/// </summary>
/// <returns>Food level.</returns>
public int getFoodLevel() => _foodLevel;
/// <summary>
/// Sets the players current food level.
/// </summary>
/// <param name="value">New food level.</param>
public void setFoodLevel(int value)
{
_foodLevel = value;
NativeBridge.SetFoodLevel?.Invoke(getEntityId(), value);
}
/// <summary>
/// Spawns the particle (the number of times specified by count)
/// at the target location. Only this player will see the particle.
/// </summary>
/// <param name="particle">The particle to spawn.</param>
/// <param name="location">The location to spawn at.</param>
/// <param name="count">The number of particles.</param>
public void spawnParticle(Particle particle, Location location, int count)
{
spawnParticleInternal(particle, location.X, location.Y, location.Z, count, 0, 0, 0, 0, null);
}
/// <summary>
/// Spawns the particle (the number of times specified by count)
/// at the target location. Only this player will see the particle.
/// </summary>
/// <param name="particle">The particle to spawn.</param>
/// <param name="x">The position on the x axis to spawn at.</param>
/// <param name="y">The position on the y axis to spawn at.</param>
/// <param name="z">The position on the z axis to spawn at.</param>
/// <param name="count">The number of particles.</param>
public void spawnParticle(Particle particle, double x, double y, double z, int count)
{
spawnParticleInternal(particle, x, y, z, count, 0, 0, 0, 0, null);
}
/// <summary>
/// Spawns the particle (the number of times specified by count)
/// at the target location. Only this player will see the particle.
/// </summary>
/// <param name="particle">The particle to spawn.</param>
/// <param name="location">The location to spawn at.</param>
/// <param name="count">The number of particles.</param>
/// <param name="data">The data to use for the particle or null.</param>
/// <typeparam name="T">The type of the particle data.</typeparam>
public void spawnParticle<T>(Particle particle, Location location, int count, T? data)
{
spawnParticleInternal(particle, location.X, location.Y, location.Z, count, 0, 0, 0, 0, data);
}
/// <summary>
/// Spawns the particle (the number of times specified by count)
/// at the target location. Only this player will see the particle.
/// </summary>
/// <param name="particle">The particle to spawn.</param>
/// <param name="x">The position on the x axis to spawn at.</param>
/// <param name="y">The position on the y axis to spawn at.</param>
/// <param name="z">The position on the z axis to spawn at.</param>
/// <param name="count">The number of particles.</param>
/// <param name="data">The data to use for the particle or null.</param>
/// <typeparam name="T">The type of the particle data.</typeparam>
public void spawnParticle<T>(Particle particle, double x, double y, double z, int count, T? data)
{
spawnParticleInternal(particle, x, y, z, count, 0, 0, 0, 0, data);
}
/// <summary>
/// Spawns the particle (the number of times specified by count)
/// at the target location. The position of each particle will be
/// randomized positively and negatively by the offset parameters
/// on each axis. Only this player will see the particle.
/// </summary>
/// <param name="particle">The particle to spawn.</param>
/// <param name="location">The location to spawn at.</param>
/// <param name="count">The number of particles.</param>
/// <param name="offsetX">The maximum random offset on the X axis.</param>
/// <param name="offsetY">The maximum random offset on the Y axis.</param>
/// <param name="offsetZ">The maximum random offset on the Z axis.</param>
public void spawnParticle(Particle particle, Location location, int count, double offsetX, double offsetY, double offsetZ)
{
spawnParticleInternal(particle, location.X, location.Y, location.Z, count, offsetX, offsetY, offsetZ, 0, null);
}
/// <summary>
/// Spawns the particle (the number of times specified by count)
/// at the target location. The position of each particle will be
/// randomized positively and negatively by the offset parameters
/// on each axis. Only this player will see the particle.
/// </summary>
/// <param name="particle">The particle to spawn.</param>
/// <param name="x">The position on the x axis to spawn at.</param>
/// <param name="y">The position on the y axis to spawn at.</param>
/// <param name="z">The position on the z axis to spawn at.</param>
/// <param name="count">The number of particles.</param>
/// <param name="offsetX">The maximum random offset on the X axis.</param>
/// <param name="offsetY">The maximum random offset on the Y axis.</param>
/// <param name="offsetZ">The maximum random offset on the Z axis.</param>
public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ)
{
spawnParticleInternal(particle, x, y, z, count, offsetX, offsetY, offsetZ, 0, null);
}
/// <summary>
/// Spawns the particle (the number of times specified by count)
/// at the target location. The position of each particle will be
/// randomized positively and negatively by the offset parameters
/// on each axis. Only this player will see the particle.
/// </summary>
/// <param name="particle">The particle to spawn.</param>
/// <param name="location">The location to spawn at.</param>
/// <param name="count">The number of particles.</param>
/// <param name="offsetX">The maximum random offset on the X axis.</param>
/// <param name="offsetY">The maximum random offset on the Y axis.</param>
/// <param name="offsetZ">The maximum random offset on the Z axis.</param>
/// <param name="data">The data to use for the particle or null.</param>
/// <typeparam name="T">The type of the particle data.</typeparam>
public void spawnParticle<T>(Particle particle, Location location, int count, double offsetX, double offsetY, double offsetZ, T? data)
{
spawnParticleInternal(particle, location.X, location.Y, location.Z, count, offsetX, offsetY, offsetZ, 0, data);
}
/// <summary>
/// Spawns the particle (the number of times specified by count)
/// at the target location. The position of each particle will be
/// randomized positively and negatively by the offset parameters
/// on each axis. Only this player will see the particle.
/// </summary>
/// <param name="particle">The particle to spawn.</param>
/// <param name="x">The position on the x axis to spawn at.</param>
/// <param name="y">The position on the y axis to spawn at.</param>
/// <param name="z">The position on the z axis to spawn at.</param>
/// <param name="count">The number of particles.</param>
/// <param name="offsetX">The maximum random offset on the X axis.</param>
/// <param name="offsetY">The maximum random offset on the Y axis.</param>
/// <param name="offsetZ">The maximum random offset on the Z axis.</param>
/// <param name="data">The data to use for the particle or null.</param>
/// <typeparam name="T">The type of the particle data.</typeparam>
public void spawnParticle<T>(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, T? data)
{
spawnParticleInternal(particle, x, y, z, count, offsetX, offsetY, offsetZ, 0, data);
}
/// <summary>
/// Spawns the particle (the number of times specified by count)
/// at the target location. The position of each particle will be
/// randomized positively and negatively by the offset parameters
/// on each axis. Only this player will see the particle.
/// </summary>
/// <param name="particle">The particle to spawn.</param>
/// <param name="location">The location to spawn at.</param>
/// <param name="count">The number of particles.</param>
/// <param name="offsetX">The maximum random offset on the X axis.</param>
/// <param name="offsetY">The maximum random offset on the Y axis.</param>
/// <param name="offsetZ">The maximum random offset on the Z axis.</param>
/// <param name="extra">The extra data for this particle, depends on the particle used (normally speed).</param>
public void spawnParticle(Particle particle, Location location, int count, double offsetX, double offsetY, double offsetZ, double extra)
{
spawnParticleInternal(particle, location.X, location.Y, location.Z, count, offsetX, offsetY, offsetZ, extra, null);
}
/// <summary>
/// Spawns the particle (the number of times specified by count)
/// at the target location. The position of each particle will be
/// randomized positively and negatively by the offset parameters
/// on each axis. Only this player will see the particle.
/// </summary>
/// <param name="particle">The particle to spawn.</param>
/// <param name="x">The position on the x axis to spawn at.</param>
/// <param name="y">The position on the y axis to spawn at.</param>
/// <param name="z">The position on the z axis to spawn at.</param>
/// <param name="count">The number of particles.</param>
/// <param name="offsetX">The maximum random offset on the X axis.</param>
/// <param name="offsetY">The maximum random offset on the Y axis.</param>
/// <param name="offsetZ">The maximum random offset on the Z axis.</param>
/// <param name="extra">The extra data for this particle, depends on the particle used (normally speed).</param>
public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra)
{
spawnParticleInternal(particle, x, y, z, count, offsetX, offsetY, offsetZ, extra, null);
}
/// <summary>
/// Spawns the particle (the number of times specified by count)
/// at the target location. The position of each particle will be
/// randomized positively and negatively by the offset parameters
/// on each axis. Only this player will see the particle.
/// </summary>
/// <param name="particle">The particle to spawn.</param>
/// <param name="location">The location to spawn at.</param>
/// <param name="count">The number of particles.</param>
/// <param name="offsetX">The maximum random offset on the X axis.</param>
/// <param name="offsetY">The maximum random offset on the Y axis.</param>
/// <param name="offsetZ">The maximum random offset on the Z axis.</param>
/// <param name="extra">The extra data for this particle, depends on the particle used (normally speed).</param>
/// <param name="data">The data to use for the particle or null.</param>
/// <typeparam name="T">The type of the particle data.</typeparam>
public void spawnParticle<T>(Particle particle, Location location, int count, double offsetX, double offsetY, double offsetZ, double extra, T? data)
{
spawnParticleInternal(particle, location.X, location.Y, location.Z, count, offsetX, offsetY, offsetZ, extra, data);
}
/// <summary>
/// Spawns the particle (the number of times specified by count)
/// at the target location. The position of each particle will be
/// randomized positively and negatively by the offset parameters
/// on each axis. Only this player will see the particle.
/// </summary>
/// <param name="particle">The particle to spawn.</param>
/// <param name="x">The position on the x axis to spawn at.</param>
/// <param name="y">The position on the y axis to spawn at.</param>
/// <param name="z">The position on the z axis to spawn at.</param>
/// <param name="count">The number of particles.</param>
/// <param name="offsetX">The maximum random offset on the X axis.</param>
/// <param name="offsetY">The maximum random offset on the Y axis.</param>
/// <param name="offsetZ">The maximum random offset on the Z axis.</param>
/// <param name="extra">The extra data for this particle, depends on the particle used (normally speed).</param>
/// <param name="data">The data to use for the particle or null.</param>
/// <typeparam name="T">The type of the particle data.</typeparam>
public void spawnParticle<T>(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, T? data)
{
spawnParticleInternal(particle, x, y, z, count, offsetX, offsetY, offsetZ, extra, data);
}
private void spawnParticleInternal(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, object? data)
{
if (NativeBridge.SpawnParticle == null)
return;
int particleId = (int)particle;
if (data is ItemStack itemStack &&
(particle == Particle.ITEM_CRACK || particle == Particle.BLOCK_CRACK))
{
int id = itemStack.getTypeId();
int aux = itemStack.getDurability();
particleId = (int)particle | ((id & 0x0FFF) << 8) | (aux & 0xFF);
}
NativeBridge.SpawnParticle(getEntityId(), particleId,
(float)x, (float)y, (float)z,
(float)offsetX, (float)offsetY, (float)offsetZ,
(float)extra, count);
}
// INTERNAL
internal void SetSaturationInternal(float saturation) => _saturation = saturation;
internal void SetWalkSpeedInternal(float walkSpeed) => _walkSpeed = walkSpeed;
internal void SetPlayerUniqueIdInternal(Guid id) => _playerUniqueId = id;
internal void SetPlayerRawOnlineXUIDInternal(ulong xuid) => _playerRawOnlineXUID = xuid;
internal void SetPlayerRawOfflineXUIDInternal(ulong xuid) => _playerRawOfflineXUID = xuid;
internal void SetSneakingInternal(bool sneaking) => _sneaking = sneaking;
internal void SetSprintingInternal(bool sprinting) => _sprinting = sprinting;
internal void SetAllowFlightInternal(bool allowFlight) => _allowFlight = allowFlight;
internal void SetSleepingIgnoredInternal(bool ignored) => _sleepingIgnored = ignored;
internal void SetLevelInternal(int level) => _level = level;
internal void SetExpInternal(float exp) => _exp = exp;
internal void SetTotalExperienceInternal(int totalExp) => _totalExperience = totalExp;
internal void SetFoodLevelInternal(int foodLevel) => _foodLevel = foodLevel;
internal void SetExhaustionInternal(float exhaustion) => _exhaustion = exhaustion;
}

View File

@@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Minecraft.Server.FourKit.Enums;
public enum LoginType
{
INITIAL = 1,
ACCEPTED = 2,
}

View File

@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Minecraft.Server.FourKit.Enums;
/// <summary>
/// Tree and organic structure types.
/// </summary>
public enum TreeType {
/// <summary>
/// No tree type.
/// </summary>
None = 0,
/// <summary>
/// Redwood tree, shaped like a pine tree.
/// </summary>
SPRUCE = 1,
/// <summary>
/// Birch tree.
/// </summary>
BIRCH = 2,
/// <summary>
/// Standard jungle tree; 4 blocks wide and tall.
/// </summary>
JUNGLE = 3,
/// <summary>
/// Regular tree, extra tall with branches.
/// </summary>
BIG_OAK = 4,
/// <summary>
/// Regular tree, no branches.
/// </summary>
OAK = 5,
/// <summary>
/// Big brown mushroom; tall and umbrella-like.
/// </summary>
BROWN_MUSHROOM = 6,
/// <summary>
/// Big red mushroom; short and fat.
/// </summary>
RED_MUSHROOM = 7,
}

View File

@@ -0,0 +1,50 @@
namespace Minecraft.Server.FourKit.Event.Block;
using Minecraft.Server.FourKit.Block;
using Minecraft.Server.FourKit.Entity;
/// <summary>
/// Called when a block is broken by a player.
///
/// If you wish to have the block drop experience, you must set the experience
/// value above 0. By default, experience will be set in the event if:
/// <list type="bullet">
/// <item><description>The player is not in creative or adventure mode</description></item>
/// <item><description>The player can loot the block (ie: does not destroy it completely, by using the correct tool)</description></item>
/// <item><description>The player does not have silk touch</description></item>
/// <item><description>The block drops experience in vanilla Minecraft</description></item>
/// </list>
///
/// Note: Plugins wanting to simulate a traditional block drop should set the
/// block to air and utilize their own methods for determining what the default
/// drop for the block being broken is and what to do about it, if anything.
///
/// If a Block Break event is cancelled, the block will not break and experience
/// will not drop.
/// </summary>
public class BlockBreakEvent : BlockExpEvent, Cancellable
{
private readonly Player _player;
private bool _cancel;
internal BlockBreakEvent(Block block, Player player, int exp)
: base(block, exp)
{
_player = player;
_cancel = false;
}
/// <summary>
/// Gets the Player that is breaking the block involved in this event.
/// </summary>
/// <returns>The Player that is breaking the block involved in this event.</returns>
public Player getPlayer() => _player;
/// <inheritdoc/>
public bool isCancelled() => _cancel;
/// <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,22 @@
namespace Minecraft.Server.FourKit.Event.Block;
using Minecraft.Server.FourKit.Block;
/// <summary>
/// Represents a Block-related event.
/// </summary>
public abstract class BlockEvent : Event
{
private readonly Block _block;
internal protected BlockEvent(Block block)
{
_block = block;
}
/// <summary>
/// Gets the block involved in this event.
/// </summary>
/// <returns>The Block which is involved in this event.</returns>
public Block getBlock() => _block;
}

View File

@@ -0,0 +1,31 @@
namespace Minecraft.Server.FourKit.Event.Block;
using Minecraft.Server.FourKit.Block;
/// <summary>
/// An event that is called when a block yields experience.
/// </summary>
public class BlockExpEvent : BlockEvent
{
private int _exp;
internal BlockExpEvent(Block block, int exp)
: base(block)
{
_exp = exp;
}
/// <summary>
/// Get the experience dropped by the block after the event has processed.
/// </summary>
/// <returns>The experience to drop.</returns>
public int getExpToDrop() => _exp;
/// <summary>
/// Set the amount of experience dropped by the block after the event has processed.
/// </summary>
/// <param name="exp">1 or higher to drop experience, else nothing will drop.</param>
public void setExpToDrop(int exp)
{
_exp = exp;
}
}

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,58 @@
namespace Minecraft.Server.FourKit.Event.Block;
using Minecraft.Server.FourKit.Block;
using Minecraft.Server.FourKit.Entity;
using Minecraft.Server.FourKit.Inventory;
/// <summary>
/// Called when a block is placed by a player.
/// </summary>
public class BlockPlaceEvent : BlockEvent, Cancellable
{
protected Block placedAgainst;
protected ItemStack itemInHand;
protected Player player;
protected bool canBuild;
protected bool cancel;
internal BlockPlaceEvent(Block placedBlock, Block placedAgainst, ItemStack itemInHand, Player thePlayer, bool canBuild)
: base(placedBlock)
{
this.placedAgainst = placedAgainst;
this.itemInHand = itemInHand;
this.player = thePlayer;
this.canBuild = canBuild;
this.cancel = false;
}
/// <summary>
/// Gets the player who placed the block involved in this event.
/// </summary>
/// <returns>The Player who placed the block involved in this event.</returns>
public Player getPlayer() => player;
/// <summary>
/// Clarity method for getting the placed block. Not really needed except
/// for reasons of clarity.
/// </summary>
/// <returns>The Block that was placed.</returns>
public Block getBlockPlaced() => getBlock();
/// <summary>
/// Gets the block that this block was placed against.
/// </summary>
/// <returns>Block the block that the new block was placed against.</returns>
public Block getBlockAgainst() => placedAgainst;
/// <summary>
/// Gets the item in the player's hand when they placed the block.
/// </summary>
/// <returns>The ItemStack for the item in the player's hand when they placed the block.</returns>
public ItemStack getItemInHand() => itemInHand;
/// <inheritdoc />
public bool isCancelled() => cancel;
/// <inheritdoc />
public void setCancelled(bool cancel) => this.cancel = cancel;
}

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

@@ -0,0 +1,65 @@
namespace Minecraft.Server.FourKit.Event.Block;
using Minecraft.Server.FourKit.Block;
using Minecraft.Server.FourKit.Entity;
/// <summary>
/// Called when a sign is changed by a player.
/// </summary>
public class SignChangeEvent : BlockEvent, Cancellable
{
private readonly Player _player;
private readonly string[] _lines;
private bool _cancel;
internal SignChangeEvent(Block theBlock, Player thePlayer, string[] theLines)
: base(theBlock)
{
_player = thePlayer;
_lines = theLines;
_cancel = false;
}
/// <summary>
/// Gets the player changing the sign involved in this event.
/// </summary>
/// <returns>The Player involved in this event.</returns>
public Player getPlayer() => _player;
/// <summary>
/// Gets all of the lines of text from the sign involved in this event.
/// </summary>
/// <returns>The String array for the sign's lines new text.</returns>
public string[] getLines() => _lines;
/// <summary>
/// Gets a single line of text from the sign involved in this event.
/// </summary>
/// <param name="index">Index of the line to get.</param>
/// <returns>The String containing the line of text associated with the provided index.</returns>
/// <exception cref="IndexOutOfRangeException">Thrown when the provided index is &gt; 3 or &lt; 0.</exception>
public string getLine(int index)
{
if (index < 0 || index > 3)
throw new IndexOutOfRangeException($"Line index must be between 0 and 3, got {index}");
return _lines[index];
}
/// <summary>
/// Sets a single line for the sign involved in this event.
/// </summary>
/// <param name="index">Index of the line to set.</param>
/// <param name="line">Text to set.</param>
/// <exception cref="IndexOutOfRangeException">Thrown when the provided index is &gt; 3 or &lt; 0.</exception>
public void setLine(int index, string line)
{
if (index < 0 || index > 3)
throw new IndexOutOfRangeException($"Line index must be between 0 and 3, got {index}");
_lines[index] = line;
}
/// <inheritdoc />
public bool isCancelled() => _cancel;
/// <inheritdoc />
public void setCancelled(bool cancel) => _cancel = cancel;
}

View File

@@ -0,0 +1,14 @@
namespace Minecraft.Server.FourKit.Event;
/// <summary>
/// Interface for events that can be cancelled by a plugin.
/// When cancelled, the server will skip the default action.
/// </summary>
public interface Cancellable
{
/// <summary>Gets whether this event is cancelled.</summary>
bool isCancelled();
/// <summary>Sets whether this event is cancelled.</summary>
void setCancelled(bool cancel);
}

View File

@@ -0,0 +1,22 @@
namespace Minecraft.Server.FourKit.Event.Entity;
using FourKitEntity = Minecraft.Server.FourKit.Entity;
/// <summary>
/// Called when an entity is damaged by an entity.
/// </summary>
public class EntityDamageByEntityEvent : EntityDamageEvent
{
private readonly FourKitEntity.Entity _damager;
internal EntityDamageByEntityEvent(FourKitEntity.Entity damager, FourKitEntity.Entity damagee, EntityDamageEvent.DamageCause cause, double damage)
: base(damagee, cause, damage)
{
_damager = damager;
}
/// <summary>
/// Returns the entity that damaged the defender.
/// </summary>
/// <returns>The Entity that damaged the defender.</returns>
public FourKitEntity.Entity getDamager() => _damager;
}

View File

@@ -0,0 +1,112 @@
namespace Minecraft.Server.FourKit.Event.Entity;
using FourKitEntity = Minecraft.Server.FourKit.Entity;
/// <summary>
/// Stores data for damage events.
/// </summary>
public class EntityDamageEvent : EntityEvent, Cancellable
{
/// <summary>
/// An enum to specify the cause of the damage.
/// </summary>
public enum DamageCause
{
/// <summary>Damage caused by being in the area when a block explodes.</summary>
BLOCK_EXPLOSION,
/// <summary>Damage caused when an entity contacts a block such as a Cactus.</summary>
CONTACT,
/// <summary>Custom damage.</summary>
CUSTOM,
/// <summary>Damage caused by running out of air while in water.</summary>
DROWNING,
/// <summary>Damage caused when an entity attacks another entity.</summary>
ENTITY_ATTACK,
/// <summary>Damage caused by being in the area when an entity, such as a Creeper, explodes.</summary>
ENTITY_EXPLOSION,
/// <summary>Damage caused when an entity falls a distance greater than 3 blocks.</summary>
FALL,
/// <summary>Damage caused by being hit by a falling block which deals damage.</summary>
FALLING_BLOCK,
/// <summary>Damage caused by direct exposure to fire.</summary>
FIRE,
/// <summary>Damage caused due to burns caused by fire.</summary>
FIRE_TICK,
/// <summary>Damage caused by direct exposure to lava.</summary>
LAVA,
/// <summary>Damage caused by being struck by lightning.</summary>
LIGHTNING,
/// <summary>Damage caused by being hit by a damage potion or spell.</summary>
MAGIC,
/// <summary>Damage caused due to a snowman melting.</summary>
MELTING,
/// <summary>Damage caused due to an ongoing poison effect.</summary>
POISON,
/// <summary>Damage caused when attacked by a projectile.</summary>
PROJECTILE,
/// <summary>Damage caused by starving due to having an empty hunger bar.</summary>
STARVATION,
/// <summary>Damage caused by being put in a block.</summary>
SUFFOCATION,
/// <summary>Damage caused by committing suicide using the command "/kill".</summary>
SUICIDE,
/// <summary>Damage caused in retaliation to another attack by the Thorns enchantment.</summary>
THORNS,
/// <summary>Damage caused by falling into the void.</summary>
VOID,
/// <summary>Damage caused by Wither potion effect.</summary>
WITHER,
}
private readonly DamageCause _cause;
private double _damage;
private readonly double _finalDamage;
private bool _cancel;
internal EntityDamageEvent(FourKitEntity.Entity damagee, DamageCause cause, double damage)
: base(damagee)
{
_cause = cause;
_damage = damage;
_finalDamage = damage;
_cancel = false;
}
/// <summary>
/// Gets the cause of the damage.
/// </summary>
/// <returns>A <see cref="DamageCause"/> value detailing the cause of the damage.</returns>
public DamageCause getCause() => _cause;
/// <summary>
/// Gets the raw amount of damage caused by the event.
/// </summary>
/// <returns>The raw amount of damage.</returns>
public double getDamage() => _damage;
/// <summary>
/// Gets the amount of damage caused by the event after all damage
/// reduction is applied.
/// </summary>
/// <returns>The amount of damage after reduction.</returns>
public double getFinalDamage() => _finalDamage;
/// <inheritdoc />
public bool isCancelled() => _cancel;
/// <inheritdoc />
public void setCancelled(bool cancel)
{
_cancel = cancel;
}
/// <summary>
/// Sets the raw amount of damage caused by the event.
/// </summary>
/// <param name="damage">The raw amount of damage.</param>
public void setDamage(double damage)
{
_damage = damage;
}
}

View File

@@ -0,0 +1,53 @@
namespace Minecraft.Server.FourKit.Event.Entity;
using FourKitEntity = Minecraft.Server.FourKit.Entity;
using Minecraft.Server.FourKit.Inventory;
/// <summary>
/// Thrown whenever a LivingEntity dies.
/// </summary>
public class EntityDeathEvent : EntityEvent
{
private readonly List<ItemStack> _drops;
private int _droppedExp;
internal EntityDeathEvent(FourKitEntity.LivingEntity entity, List<ItemStack> drops)
: this(entity, drops, 0)
{
}
internal EntityDeathEvent(FourKitEntity.LivingEntity what, List<ItemStack> drops, int droppedExp)
: base(what)
{
_drops = drops;
_droppedExp = droppedExp;
}
/// <summary>
/// Returns the Entity involved in this event.
/// </summary>
/// <returns>Entity who is involved in this event.</returns>
public new FourKitEntity.LivingEntity getEntity() => (FourKitEntity.LivingEntity)entity;
/// <summary>
/// Gets how much EXP should be dropped from this death.
/// This does not indicate how much EXP should be taken from the entity
/// in question, merely how much should be created after its death.
/// </summary>
/// <returns>Amount of EXP to drop.</returns>
public int getDroppedExp() => _droppedExp;
/// <summary>
/// Sets how much EXP should be dropped from this death.
/// This does not indicate how much EXP should be taken from the entity
/// in question, merely how much should be created after its death.
/// </summary>
/// <param name="exp">Amount of EXP to drop.</param>
public void setDroppedExp(int exp) => _droppedExp = exp;
/// <summary>
/// Gets all the items which will drop when the entity dies.
/// </summary>
/// <returns>Items to drop when the entity dies.</returns>
public List<ItemStack> getDrops() => _drops;
}

View File

@@ -0,0 +1,28 @@
namespace Minecraft.Server.FourKit.Event.Entity;
using FourKitEntity = Minecraft.Server.FourKit.Entity;
/// <summary>
/// Represents an Entity-related event.
/// </summary>
public abstract class EntityEvent : Event
{
protected FourKitEntity.Entity entity;
protected EntityEvent(FourKitEntity.Entity what)
{
entity = what;
}
/// <summary>
/// Returns the Entity involved in this event.
/// </summary>
/// <returns>Entity who is involved in this event.</returns>
public FourKitEntity.Entity getEntity() => entity;
/// <summary>
/// Gets the EntityType of the Entity involved in this event.
/// </summary>
/// <returns>EntityType of the Entity involved in this event.</returns>
public FourKitEntity.EntityType getEntityType() => entity.getType();
}

View File

@@ -0,0 +1,110 @@
namespace Minecraft.Server.FourKit.Event.Entity;
using FourKitEntity = Minecraft.Server.FourKit.Entity;
using Minecraft.Server.FourKit.Inventory;
/// <summary>
/// Thrown whenever a Player dies.
/// </summary>
public class PlayerDeathEvent : EntityDeathEvent
{
private string _deathMessage;
private int _newExp;
private int _newLevel;
private bool _keepLevel;
private bool _keepInventory;
internal PlayerDeathEvent(FourKitEntity.Player player, List<ItemStack> drops, int droppedExp, string deathMessage)
: this(player, drops, droppedExp, 0, deathMessage)
{
}
/// <summary>
/// Creates a new <see cref="PlayerDeathEvent"/>.
/// </summary>
/// <param name="player">The Player who died.</param>
/// <param name="drops">The items to drop when the player dies.</param>
/// <param name="droppedExp">The amount of experience to drop.</param>
/// <param name="newExp">The new EXP the Player should have at respawn.</param>
/// <param name="deathMessage">The death message to display.</param>
public PlayerDeathEvent(FourKitEntity.Player player, List<ItemStack> drops, int droppedExp, int newExp, string deathMessage)
: base(player, drops, droppedExp)
{
_deathMessage = deathMessage;
_newExp = newExp;
_newLevel = 0;
_keepLevel = false;
_keepInventory = false;
}
/// <summary>
/// Returns the Entity involved in this event.
/// </summary>
/// <returns>Entity who is involved in this event.</returns>
public new FourKitEntity.Player getEntity() => (FourKitEntity.Player)entity;
/// <summary>
/// Get the death message that will appear to everyone on the server.
/// </summary>
/// <returns>Message to appear to other players on the server.</returns>
public string getDeathMessage() => _deathMessage;
/// <summary>
/// Set the death message that will appear to everyone on the server.
/// </summary>
/// <param name="deathMessage">Message to appear to other players on the server.</param>
public void setDeathMessage(string deathMessage) => _deathMessage = deathMessage;
/// <summary>
/// Gets how much EXP the Player should have at respawn.
/// This does not indicate how much EXP should be dropped, please see
/// <see cref="EntityDeathEvent.getDroppedExp"/> for that.
/// </summary>
/// <returns>New EXP of the respawned player.</returns>
public int getNewExp() => _newExp;
/// <summary>
/// Sets how much EXP the Player should have at respawn.
/// This does not indicate how much EXP should be dropped, please see
/// <see cref="EntityDeathEvent.setDroppedExp"/> for that.
/// </summary>
/// <param name="exp">New EXP of the respawned player.</param>
public void setNewExp(int exp) => _newExp = exp;
/// <summary>
/// Gets the Level the Player should have at respawn.
/// </summary>
/// <returns>New Level of the respawned player.</returns>
public int getNewLevel() => _newLevel;
/// <summary>
/// Sets the Level the Player should have at respawn.
/// </summary>
/// <param name="level">New Level of the respawned player.</param>
public void setNewLevel(int level) => _newLevel = level;
/// <summary>
/// Gets if the Player should keep all EXP at respawn.
/// This flag overrides other EXP settings.
/// </summary>
/// <returns><c>true</c> if Player should keep all pre-death exp.</returns>
public bool getKeepLevel() => _keepLevel;
/// <summary>
/// Sets if the Player should keep all EXP at respawn.
/// This overrides all other EXP settings.
/// </summary>
/// <param name="keepLevel"><c>true</c> to keep all current value levels.</param>
public void setKeepLevel(bool keepLevel) => _keepLevel = keepLevel;
/// <summary>
/// Gets if the Player keeps inventory on death.
/// </summary>
/// <returns><c>true</c> if the player keeps inventory on death.</returns>
public bool getKeepInventory() => _keepInventory;
/// <summary>
/// Sets if the Player keeps inventory on death.
/// </summary>
/// <param name="keepInventory"><c>true</c> to keep the inventory.</param>
public void setKeepInventory(bool keepInventory) => _keepInventory = keepInventory;
}

View File

@@ -0,0 +1,10 @@
namespace Minecraft.Server.FourKit.Event;
/// <summary>
/// Base class for all events dispatched by the server.
/// </summary>
public abstract class Event
{
/// <summary>Gets the name of this event (defaults to the class name).</summary>
public virtual string getEventName() => GetType().Name;
}

View File

@@ -0,0 +1,40 @@
namespace Minecraft.Server.FourKit.Event;
/// <summary>
/// Marks a method inside a <see cref="Listener"/> as an event handler.
/// This class is not named "EventHandler" due to a naming conflict with the existing System.EventHandler
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public sealed class EventHandlerAttribute : Attribute
{
/// <summary>
/// Priority of this handler. Lower values run first.
/// Default is <see cref="EventPriority.Normal"/>.
/// </summary>
public EventPriority Priority { get; set; } = EventPriority.Normal;
/// <summary>
/// Whether this handler should be skipped when the event is already
/// cancelled by a lower-priority handler. Default is <c>false</c>.
/// </summary>
public bool IgnoreCancelled { get; set; } = false;
}
/// <summary>
/// Execution priority for event handlers.
/// </summary>
public enum EventPriority
{
/// <summary>Event call is of very low importance and should be ran first, to allow other plugins to further customise the outcome</summary>
Lowest = 0,
/// <summary>Event call is of low importance</summary>
Low = 1,
/// <summary>Event call is neither important nor unimportant, and may be ran normally</summary>
Normal = 2,
/// <summary>Event call is of high importance</summary>
High = 3,
/// <summary>Event call is critical and must have the final say in what happens to the event</summary>
Highest = 4,
/// <summary>Event is listened to purely for monitoring the outcome of an event. Should not modify the event.</summary>
Monitor = 5
}

View File

@@ -0,0 +1,93 @@
namespace Minecraft.Server.FourKit.Event.Inventory;
/// <summary>
/// What the client did to trigger this action (not the result).
/// </summary>
public enum ClickType
{
/// <summary>The left (or primary) mouse button.</summary>
LEFT,
/// <summary>Holding shift while pressing the left mouse button.</summary>
SHIFT_LEFT,
/// <summary>The right mouse button.</summary>
RIGHT,
/// <summary>Holding shift while pressing the right mouse button.</summary>
SHIFT_RIGHT,
/// <summary>Clicking the left mouse button on the grey area around the inventory.</summary>
WINDOW_BORDER_LEFT,
/// <summary>Clicking the right mouse button on the grey area around the inventory.</summary>
WINDOW_BORDER_RIGHT,
/// <summary>The middle mouse button, or a "scrollwheel click".</summary>
MIDDLE,
/// <summary>One of the number keys 1-9, correspond to slots on the hotbar.</summary>
NUMBER_KEY,
/// <summary>Pressing the left mouse button twice in quick succession.</summary>
DOUBLE_CLICK,
/// <summary>The "Drop" key (defaults to Q).</summary>
DROP,
/// <summary>Holding Ctrl while pressing the "Drop" key (defaults to Q).</summary>
CONTROL_DROP,
/// <summary>Any action done with the Creative inventory open.</summary>
CREATIVE,
/// <summary>A type of inventory manipulation not yet recognized by Bukkit.</summary>
UNKNOWN,
}
/// <summary>
/// Extension methods for <see cref="ClickType"/>.
/// </summary>
public static class ClickTypeExtensions
{
/// <summary>
/// Gets whether this ClickType represents the pressing of a key on a keyboard.
/// </summary>
/// <param name="click">The click type.</param>
/// <returns>true if this ClickType represents the pressing of a key.</returns>
public static bool isKeyboardClick(this ClickType click)
{
return click == ClickType.NUMBER_KEY || click == ClickType.DROP || click == ClickType.CONTROL_DROP;
}
/// <summary>
/// Gets whether this ClickType represents an action that can only be performed
/// by a Player in creative mode.
/// </summary>
/// <param name="click">The click type.</param>
/// <returns>true if this action requires Creative mode.</returns>
public static bool isCreativeAction(this ClickType click)
{
return click == ClickType.CREATIVE || click == ClickType.MIDDLE;
}
/// <summary>
/// Gets whether this ClickType represents a right click.
/// </summary>
/// <param name="click">The click type.</param>
/// <returns>true if this ClickType represents a right click.</returns>
public static bool isRightClick(this ClickType click)
{
return click == ClickType.RIGHT || click == ClickType.SHIFT_RIGHT;
}
/// <summary>
/// Gets whether this ClickType represents a left click.
/// </summary>
/// <param name="click">The click type.</param>
/// <returns>true if this ClickType represents a left click.</returns>
public static bool isLeftClick(this ClickType click)
{
return click == ClickType.LEFT || click == ClickType.SHIFT_LEFT
|| click == ClickType.DOUBLE_CLICK || click == ClickType.CREATIVE;
}
/// <summary>
/// Gets whether this ClickType indicates that the shift key was pressed
/// down when the click was made.
/// </summary>
/// <param name="click">The click type.</param>
/// <returns>true if the action uses Shift.</returns>
public static bool isShiftClick(this ClickType click)
{
return click == ClickType.SHIFT_LEFT || click == ClickType.SHIFT_RIGHT;
}
}

View File

@@ -0,0 +1,46 @@
namespace Minecraft.Server.FourKit.Event.Inventory;
/// <summary>
/// An estimation of what the result will be.
/// </summary>
public enum InventoryAction
{
/// <summary>Nothing will happen from the click.</summary>
NOTHING,
/// <summary>All of the items on the clicked slot are moved to the cursor.</summary>
PICKUP_ALL,
/// <summary>Some of the items on the clicked slot are moved to the cursor.</summary>
PICKUP_SOME,
/// <summary>Half of the items on the clicked slot are moved to the cursor.</summary>
PICKUP_HALF,
/// <summary>One of the items on the clicked slot are moved to the cursor.</summary>
PICKUP_ONE,
/// <summary>All of the items on the cursor are moved to the clicked slot.</summary>
PLACE_ALL,
/// <summary>Some of the items from the cursor are moved to the clicked slot (usually up to the max stack size).</summary>
PLACE_SOME,
/// <summary>A single item from the cursor is moved to the clicked slot.</summary>
PLACE_ONE,
/// <summary>The clicked item and the cursor are exchanged.</summary>
SWAP_WITH_CURSOR,
/// <summary>The entire cursor item is dropped.</summary>
DROP_ALL_CURSOR,
/// <summary>One item is dropped from the cursor.</summary>
DROP_ONE_CURSOR,
/// <summary>The entire clicked slot is dropped.</summary>
DROP_ALL_SLOT,
/// <summary>One item is dropped from the clicked slot.</summary>
DROP_ONE_SLOT,
/// <summary>The item is moved to the opposite inventory if a space is found.</summary>
MOVE_TO_OTHER_INVENTORY,
/// <summary>The clicked item is moved to the hotbar, and the item currently there is re-added to the player's inventory.</summary>
HOTBAR_MOVE_AND_READD,
/// <summary>The clicked slot and the picked hotbar slot are swapped.</summary>
HOTBAR_SWAP,
/// <summary>A max-size stack of the clicked item is put on the cursor.</summary>
CLONE_STACK,
/// <summary>The inventory is searched for the same material, and they are put on the cursor up to Material.getMaxStackSize().</summary>
COLLECT_TO_CURSOR,
/// <summary>An unrecognized ClickType.</summary>
UNKNOWN,
}

View File

@@ -0,0 +1,157 @@
namespace Minecraft.Server.FourKit.Event.Inventory;
using Minecraft.Server.FourKit.Entity;
using Minecraft.Server.FourKit.Inventory;
/// <summary>
/// This event is called when a player clicks a slot in an inventory.
/// <para>
/// Because InventoryClickEvent occurs within a modification of the Inventory,
/// not all Inventory related methods are safe to use.
/// </para>
/// <para>
/// The following should never be invoked by an EventHandler for
/// InventoryClickEvent using the HumanEntity or InventoryView associated
/// with this event:
/// <list type="bullet">
/// <item><description><see cref="HumanEntity.closeInventory()"/></description></item>
/// <item><description><see cref="HumanEntity.openInventory(Inventory)"/></description></item>
/// <item><description><see cref="InventoryView.close()"/></description></item>
/// </list>
/// </para>
/// </summary>
public class InventoryClickEvent : InventoryInteractEvent
{
private readonly SlotType _slotType;
private readonly int _rawSlot;
private readonly int _whichSlot;
private readonly ClickType _click;
private readonly InventoryAction _action;
private readonly int _hotbarKey;
private ItemStack? _currentItem;
internal InventoryClickEvent(InventoryView view, SlotType type, int slot,
ClickType click, InventoryAction action)
: this(view, type, slot, click, action, -1)
{
}
internal InventoryClickEvent(InventoryView view, SlotType type, int slot,
ClickType click, InventoryAction action, int key)
: base(view)
{
_slotType = type;
_rawSlot = slot;
_click = click;
_action = action;
_hotbarKey = key;
_currentItem = view.getItem(slot);
_whichSlot = view.convertSlot(slot);
}
/// <summary>
/// Gets the inventory that was clicked, or null if outside of window.
/// </summary>
/// <returns>The clicked inventory.</returns>
public Inventory? getClickedInventory()
{
if (_rawSlot == InventoryView.OUTSIDE)
return null;
int topSize = getView().getTopInventory().getSize();
if (_rawSlot < topSize)
return getView().getTopInventory();
return getView().getBottomInventory();
}
/// <summary>
/// Gets the type of slot that was clicked.
/// </summary>
/// <returns>The slot type.</returns>
public SlotType getSlotType() => _slotType;
/// <summary>
/// Gets the current ItemStack on the cursor.
/// </summary>
/// <returns>The cursor ItemStack.</returns>
public ItemStack? getCursor() => getView().getCursor();
/// <summary>
/// Gets the ItemStack currently in the clicked slot.
/// </summary>
/// <returns>The item in the clicked slot.</returns>
public ItemStack? getCurrentItem() => _currentItem;
/// <summary>
/// Gets whether or not the ClickType for this event represents a right click.
/// </summary>
/// <returns>true if the ClickType uses the right mouse button.</returns>
public bool isRightClick() => _click.isRightClick();
/// <summary>
/// Gets whether or not the ClickType for this event represents a left click.
/// </summary>
/// <returns>true if the ClickType uses the left mouse button.</returns>
public bool isLeftClick() => _click.isLeftClick();
/// <summary>
/// Gets whether the ClickType for this event indicates that the key was
/// pressed down when the click was made.
/// </summary>
/// <returns>true if the ClickType uses Shift or Ctrl.</returns>
public bool isShiftClick() => _click.isShiftClick();
/// <summary>
/// Sets the item on the cursor.
/// </summary>
/// <param name="stack">The new cursor item.</param>
[Obsolete("This changes the ItemStack in their hand before any calculations are applied to the Inventory.")]
public void setCursor(ItemStack? stack) => getView().setCursor(stack);
/// <summary>
/// Sets the ItemStack currently in the clicked slot.
/// </summary>
/// <param name="stack">The item to be placed in the current slot.</param>
public void setCurrentItem(ItemStack? stack)
{
_currentItem = stack;
if (_rawSlot >= 0)
getView().setItem(_rawSlot, stack);
}
/// <summary>
/// The slot number that was clicked, ready for passing to
/// <see cref="Inventory.getItem(int)"/>. Note that there may be two slots
/// with the same slot number, since a view links two different inventories.
/// </summary>
/// <returns>The slot number.</returns>
public int getSlot() => _whichSlot;
/// <summary>
/// The raw slot number clicked, ready for passing to
/// <see cref="InventoryView.getItem(int)"/>. This slot number is unique
/// for the view.
/// </summary>
/// <returns>The raw slot number.</returns>
public int getRawSlot() => _rawSlot;
/// <summary>
/// If the ClickType is NUMBER_KEY, this method will return the index of
/// the pressed key (0-8).
/// </summary>
/// <returns>The number on the key minus 1 (range 0-8); or -1 if not a NUMBER_KEY action.</returns>
public int getHotbarButton() => _hotbarKey;
/// <summary>
/// Gets the InventoryAction that triggered this event.
/// This action cannot be changed, and represents what the normal outcome
/// of the event will be. To change the behavior of this InventoryClickEvent,
/// changes must be manually applied.
/// </summary>
/// <returns>The InventoryAction that triggered this event.</returns>
public InventoryAction getAction() => _action;
/// <summary>
/// Gets the ClickType for this event.
/// This is insulated against changes to the inventory by other plugins.
/// </summary>
/// <returns>The type of inventory click.</returns>
public ClickType getClick() => _click;
}

View File

@@ -0,0 +1,45 @@
namespace Minecraft.Server.FourKit.Event.Inventory;
using Minecraft.Server.FourKit.Entity;
using Minecraft.Server.FourKit.Inventory;
/// <summary>
/// Represents a player related inventory event.
/// </summary>
public class InventoryEvent : Event
{
/// <summary>The inventory view associated with this event.</summary>
protected readonly InventoryView transaction;
internal InventoryEvent(InventoryView transaction)
{
this.transaction = transaction;
}
/// <summary>
/// Gets the primary Inventory involved in this transaction.
/// </summary>
/// <returns>The upper inventory.</returns>
public global::Minecraft.Server.FourKit.Inventory.Inventory getInventory()
{
return transaction.getTopInventory();
}
/// <summary>
/// Gets the list of players viewing the primary (upper) inventory
/// involved in this event.
/// </summary>
/// <returns>A list of people viewing.</returns>
public List<HumanEntity> getViewers()
{
return transaction.getTopInventory().getViewers();
}
/// <summary>
/// Gets the view object itself.
/// </summary>
/// <returns>The InventoryView.</returns>
public InventoryView getView()
{
return transaction;
}
}

View File

@@ -0,0 +1,34 @@
namespace Minecraft.Server.FourKit.Event.Inventory;
using Minecraft.Server.FourKit.Entity;
using Minecraft.Server.FourKit.Inventory;
/// <summary>
/// An abstract base class for events that describe an interaction between a
/// <see cref="HumanEntity"/> and the contents of an <see cref="Inventory"/>.
/// This is currently not emitted anywhere, use <see cref="InventoryClickEvent"/> instead.
/// </summary>
public abstract class InventoryInteractEvent : InventoryEvent, Cancellable
{
private bool _cancelled;
internal protected InventoryInteractEvent(InventoryView transaction) : base(transaction)
{
}
/// <summary>
/// Gets the player who performed the click.
/// </summary>
/// <returns>The clicking player.</returns>
public HumanEntity getWhoClicked()
{
return transaction.getPlayer();
}
/// <inheritdoc />
public bool isCancelled() => _cancelled;
/// <inheritdoc />
public void setCancelled(bool cancel) => _cancelled = cancel;
}

View File

@@ -0,0 +1,38 @@
namespace Minecraft.Server.FourKit.Event.Inventory;
using Minecraft.Server.FourKit.Entity;
using Minecraft.Server.FourKit.Inventory;
/// <summary>
/// Called when a player opens an inventory. Cancelling this event will prevent
/// the inventory screen from showing.
/// </summary>
public class InventoryOpenEvent : InventoryEvent, Cancellable
{
private bool _cancelled;
internal InventoryOpenEvent(InventoryView transaction) : base(transaction)
{
}
/// <summary>
/// Returns the player involved in this event.
/// </summary>
/// <returns>Player who is involved in this event.</returns>
public HumanEntity getPlayer() => 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.
/// If an inventory open event is cancelled, the inventory screen will not show.
/// </summary>
/// <returns>true if this event is cancelled.</returns>
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.
/// If an inventory open event is cancelled, the inventory screen will not show.
/// </summary>
/// <param name="cancel">true if you wish to cancel this event.</param>
public void setCancelled(bool cancel) => _cancelled = cancel;
}

View File

@@ -0,0 +1,9 @@
namespace Minecraft.Server.FourKit.Event;
/// <summary>
/// Simple interface for tagging all EventListeners
/// Register instances with <see cref="FourKit.addListener(Listener)"/>.
/// </summary>
public interface Listener
{
}

View File

@@ -0,0 +1,33 @@
namespace Minecraft.Server.FourKit.Event.Player;
using Minecraft.Server.FourKit.Entity;
using Minecraft.Server.FourKit.Block;
/// <summary>
/// This event is fired when the player is almost about to enter the bed.
/// </summary>
public class PlayerBedEnterEvent : PlayerEvent, Cancellable
{
private readonly Block _bed;
private bool _cancelled;
internal PlayerBedEnterEvent(Player player, Block bed) : base(player)
{
_bed = bed;
}
/// <summary>
/// Returns the bed block involved in this event.
/// </summary>
/// <returns>the bed block involved in this event</returns>
public Block getBed() => _bed;
/// <inheritdoc />
public bool isCancelled() => _cancelled;
/// <inheritdoc />
public void setCancelled(bool cancel)
{
_cancelled = cancel;
}
}

View File

@@ -0,0 +1,23 @@
namespace Minecraft.Server.FourKit.Event.Player;
using Minecraft.Server.FourKit.Entity;
using Minecraft.Server.FourKit.Block;
/// <summary>
/// This event is fired when the player is leaving a bed.
/// </summary>
public class PlayerBedLeaveEvent : PlayerEvent
{
private readonly Block _bed;
internal PlayerBedLeaveEvent(Player player, Block bed) : base(player)
{
_bed = bed;
}
/// <summary>
/// Returns the bed block involved in this event.
/// </summary>
/// <returns>the bed block involved in this event</returns>
public Block getBed() => _bed;
}

View File

@@ -0,0 +1,74 @@
namespace Minecraft.Server.FourKit.Event.Player;
using Minecraft.Server.FourKit.Entity;
/// <summary>
/// Fired when a player sends a chat message.
///
/// <para>When the event finishes execution the server formats the final
/// output using the same format specifiers from Java.
/// <c>%1$s</c> is the player's display name and <c>%2$s</c> is the
/// message, exactly like Bukkits <c>PlayerChatEvent</c>.</para>
/// </summary>
public class PlayerChatEvent : PlayerEvent, Cancellable
{
private string _message;
private string _format;
private bool _cancelled;
internal PlayerChatEvent(Player player, string message) : base(player)
{
_message = message;
_format = "<%1$s> %2$s";
}
/// <summary>
/// Gets the message that the player is attempting to send.
/// This message will be used with <see cref="getFormat"/>.
/// </summary>
/// <returns>Message the player is attempting to send.</returns>
public string getMessage() => _message;
/// <summary>
/// Sets the message that the player will send.
/// This message will be used with <see cref="getFormat"/>.
/// </summary>
/// <param name="message">New message that the player will send.</param>
public void setMessage(string message)
{
_message = message;
}
/// <summary>
/// Gets the format used to display this chat message.
///
/// <para>When this event finishes execution, the first format parameter
/// (<c>%1$s</c>) is <c>Player.getDisplayName()</c> and the second
/// parameter (<c>%2$s</c>) is <c>getMessage()</c>.</para>
/// </summary>
/// <returns>A Java-style positional format string compatible with Bukkit.</returns>
public string getFormat() => _format;
/// <summary>
/// Sets the format used to display this chat message.
///
/// <para>When this event finishes execution, the first format parameter
/// (<c>%1$s</c>) is <c>Player.getDisplayName()</c> and the second
/// parameter (<c>%2$s</c>) is <c>getMessage()</c>.</para>
/// </summary>
/// <param name="format">A Java-style positional format string (e.g. <c>"&lt;%1$s&gt; %2$s"</c>).</param>
/// <exception cref="ArgumentNullException">If format is <c>null</c>.</exception>
public void setFormat(string format)
{
ArgumentNullException.ThrowIfNull(format);
_format = format;
}
/// <inheritdoc />
public bool isCancelled() => _cancelled;
/// <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

@@ -0,0 +1,46 @@
namespace Minecraft.Server.FourKit.Event.Player;
using Minecraft.Server.FourKit.Entity;
using Minecraft.Server.FourKit.Inventory;
// Yo this event pissed me the fuck off
/// <summary>
/// Fired when a player drops an item from their inventory.
/// If cancelled, the item will not be dropped and the player keeps it.
/// The dropped item can be modified by plugins.
/// </summary>
public class PlayerDropItemEvent : PlayerEvent, Cancellable
{
private ItemStack _itemDrop;
private bool _cancelled;
internal PlayerDropItemEvent(Player player, ItemStack drop)
: base(player)
{
_itemDrop = drop;
}
/// <summary>
/// Gets the ItemDrop created by the player.
/// </summary>
/// <returns>The ItemStack being dropped.</returns>
public ItemStack getItemDrop() => _itemDrop;
/// <summary>
/// Sets the item to be dropped. Plugins can modify which item
/// is actually dropped.
/// </summary>
/// <param name="item">The new item to drop.</param>
public void setItemDrop(ItemStack item)
{
_itemDrop = item;
}
/// <inheritdoc/>
public bool isCancelled() => _cancelled;
/// <inheritdoc/>
public void setCancelled(bool cancel)
{
_cancelled = cancel;
}
}

View File

@@ -0,0 +1,19 @@
namespace Minecraft.Server.FourKit.Event.Player;
using Minecraft.Server.FourKit.Entity;
/// <summary>
/// Base class for events related to a <see cref="FourKit.Player"/>.
/// </summary>
public abstract class PlayerEvent : Event
{
private readonly Player _player;
internal protected PlayerEvent(Player player)
{
_player = player;
}
/// <summary>Returns the player involved in this event.</summary>
public Player getPlayer() => _player;
}

View File

@@ -0,0 +1,32 @@
namespace Minecraft.Server.FourKit.Event.Player;
using Minecraft.Server.FourKit.Entity;
/// <summary>
/// Represents an event that is called when a player right clicks an entity.
/// </summary>
public class PlayerInteractEntityEvent : PlayerEvent, Cancellable
{
/// <summary>The entity that was right-clicked.</summary>
protected Entity clickedEntity;
private bool _cancelled;
internal PlayerInteractEntityEvent(Player who, Entity clickedEntity)
: base(who)
{
this.clickedEntity = clickedEntity;
}
/// <inheritdoc/>
public bool isCancelled() => _cancelled;
/// <inheritdoc/>
public void setCancelled(bool cancel) => _cancelled = cancel;
/// <summary>
/// Gets the entity that was right-clicked by the player.
/// </summary>
/// <returns>entity right clicked by player</returns>
public Entity getRightClicked() => clickedEntity;
}

View File

@@ -0,0 +1,111 @@
namespace Minecraft.Server.FourKit.Event.Player;
using Minecraft.Server.FourKit.Block;
using Minecraft.Server.FourKit.Entity;
using Minecraft.Server.FourKit.Inventory;
using FourKitBlock = Minecraft.Server.FourKit.Block.Block;
/// <summary>
/// Called when a player interacts with an object or air.
/// </summary>
public class PlayerInteractEvent : PlayerEvent, Cancellable
{
private readonly Action _action;
private readonly ItemStack? _item;
private readonly FourKitBlock? _clickedBlock;
private readonly BlockFace _clickedFace;
private bool _cancelled;
private bool _useItemInHand = true;
internal PlayerInteractEvent(Player who, Action action, ItemStack? item, FourKitBlock? clickedBlock, BlockFace clickedFace)
: base(who)
{
_action = action;
_item = item;
_clickedBlock = clickedBlock;
_clickedFace = clickedFace;
}
/// <summary>
/// Returns the action type.
/// </summary>
/// <returns>Action returns the type of interaction.</returns>
public Action getAction() => _action;
/// <inheritdoc/>
public bool isCancelled() => _cancelled;
/// <summary>
/// Sets the cancellation state of this event. A canceled event will not be
/// executed in the server, but will still pass to other plugins.
///
/// Canceling this event will prevent use of food (player won't lose the
/// food item), prevent bows/snowballs/eggs from firing, etc. (player won't
/// lose the ammo).
/// </summary>
/// <param name="cancel">true if you wish to cancel this event.</param>
public void setCancelled(bool cancel) => _cancelled = cancel;
/// <summary>
/// Returns the item in hand represented by this event.
/// </summary>
/// <returns>ItemStack the item used.</returns>
public ItemStack? getItem() => _item;
/// <summary>
/// Convenience method. Returns the material of the item represented by this event.
/// </summary>
/// <returns>Material the material of the item used.</returns>
public Material getMaterial() => _item?.getType() ?? Material.AIR;
/// <summary>
/// Check if this event involved a block.
/// </summary>
/// <returns>true if it did.</returns>
public bool hasBlock() => _clickedBlock != null;
/// <summary>
/// Check if this event involved an item.
/// </summary>
/// <returns>true if it did.</returns>
public bool hasItem() => _item != null;
/// <summary>
/// Convenience method to inform the user whether this was a block placement event.
/// </summary>
/// <returns>true if the item in hand was a block.</returns>
public bool isBlockInHand()
{
if (_item == null) return false;
int id = (int)_item.getType();
return id >= 1 && id <= 255;
}
/// <summary>
/// Returns the clicked block.
/// </summary>
/// <returns>Block returns the block clicked with this item.</returns>
public FourKitBlock? getClickedBlock() => _clickedBlock;
/// <summary>
/// Returns the face of the block that was clicked.
/// </summary>
/// <returns>BlockFace returns the face of the block that was clicked.</returns>
public BlockFace getBlockFace() => _clickedFace;
/// <summary>
/// This controls the action to take with the item the player is holding.
/// This includes both blocks and items (such as flint and steel or records).
/// When this is set to default, it will be allowed if no action is taken on
/// the interacted block.
/// </summary>
/// <returns>the action to take with the item in hand.</returns>
public bool useItemInHand() => _useItemInHand;
/// <summary>
/// Sets whether to use the item in hand.
/// </summary>
/// <param name="useItemInHand">the action to take with the item in hand.</param>
public void setUseItemInHand(bool useItemInHand) => _useItemInHand = useItemInHand;
}

View File

@@ -0,0 +1,30 @@
namespace Minecraft.Server.FourKit.Event.Player;
using Minecraft.Server.FourKit.Entity;
/// <summary>
/// Called when a player joins a server
/// </summary>
public class PlayerJoinEvent : PlayerEvent
{
private string _joinMessage;
internal PlayerJoinEvent(Player player) : base(player)
{
_joinMessage = $"{player.getName()} joined the game";
}
/// <summary>
/// Gets the join message to send to all online players
/// </summary>
/// <returns>string join message</returns>
public string getJoinMessage() => _joinMessage;
/// <summary>
/// Sets the join message to send to all online players
/// </summary>
/// <param name="joinMessage">join message.</param>
public void setJoinMessage(string? joinMessage)
{
_joinMessage = joinMessage ?? string.Empty;
}
}

View File

@@ -0,0 +1,62 @@
namespace Minecraft.Server.FourKit.Event.Player;
using Minecraft.Server.FourKit.Entity;
/// <summary>
/// Fired when a player is kicked from the server.
/// If cancelled, the kick will not take place and the player remains connected.
/// Plugins may modify the kick reason and the leave message broadcast to
/// all online players.
/// </summary>
public class PlayerKickEvent : PlayerEvent, Cancellable
{
private DisconnectReason _reason;
private string _leaveMessage;
private bool _cancelled;
internal PlayerKickEvent(Player playerKicked, DisconnectReason kickReason, string leaveMessage)
: base(playerKicked)
{
_reason = kickReason;
_leaveMessage = leaveMessage;
}
/// <summary>
/// Gets the reason why the player is getting kicked.
/// </summary>
/// <returns>The disconnect reason.</returns>
public DisconnectReason getReason() => _reason;
/// <summary>
/// Sets the reason why the player is getting kicked.
/// </summary>
/// <param name="kickReason">The new disconnect reason.</param>
public void setReason(DisconnectReason kickReason)
{
_reason = kickReason;
}
/// <summary>
/// Gets the leave message sent to all online players.
/// </summary>
/// <returns>The leave message.</returns>
public string getLeaveMessage() => _leaveMessage;
/// <summary>
/// Sets the leave message sent to all online players.
/// </summary>
/// <param name="leaveMessage">The new leave message.</param>
public void setLeaveMessage(string leaveMessage)
{
_leaveMessage = leaveMessage;
}
/// <inheritdoc/>
public bool isCancelled() => _cancelled;
/// <inheritdoc/>
public void setCancelled(bool cancel)
{
_cancelled = cancel;
}
}

View File

@@ -0,0 +1,93 @@
namespace Minecraft.Server.FourKit.Event.Player;
using Minecraft.Server.FourKit.Enums;
using Minecraft.Server.FourKit.Net;
/// <summary>
/// Stores details for players attempting to log in.
/// </summary>
public class PlayerLoginEvent : Event, Cancellable
{
private string name;
private InetSocketAddress ipAddress; //bukkit uses InetAddress but we expose port also
private ulong onlineXuid;
private ulong offlineXuid;
private bool changedXuidValues;
private LoginType loginType;
private bool _cancelled;
internal PlayerLoginEvent(string name, InetSocketAddress ipAddress, LoginType type, ulong onlineXuid, ulong offlineXuid) : base()
{
this.name = name;
this.ipAddress = ipAddress;
this.onlineXuid = onlineXuid;
this.offlineXuid = offlineXuid;
this.changedXuidValues = false;
this.loginType = type;
}
public LoginType getLoginType() => loginType;
/// <summary>
/// <b>Experimental.</b> Gets the online XUID (Xbox User ID), used for guests (splitscreen users).
/// </summary>
/// <returns>The online XUID value.</returns>
public ulong getOnlineXuid() => onlineXuid;
/// <summary>
/// <b>Experimental.</b> Sets the online XUID (Xbox User ID). Marks XUID values as changed.
/// </summary>
/// <param name="newXuid">The new online XUID value.</param>
public void setOnlineXuid(ulong newXuid)
{
this.onlineXuid = newXuid;
this.changedXuidValues = true;
}
/// <summary>
/// <b>Experimental.</b> Gets the offline XUID (Xbox User ID), which is the main XUID used by the client.
/// </summary>
/// <returns>The offline XUID value.</returns>
public ulong getOfflineXuid() => offlineXuid;
/// <summary>
/// <b>Experimental.</b> Sets the offline XUID (Xbox User ID). Marks XUID values as changed.
/// </summary>
/// <param name="newXuid">The new offline XUID value.</param>
public void setOfflineXuid(ulong newXuid)
{
this.offlineXuid = newXuid;
this.changedXuidValues = true;
}
/// <summary>
/// <b>Experimental.</b> Returns true if either XUID value has been changed via setters.
/// </summary>
/// <returns>True if XUID values have been changed; otherwise, false.</returns>
public bool hasChangedXuidValues() => changedXuidValues;
/// <summary>
/// Gets the player's name.
/// </summary>
/// <returns>The player's name.</returns>
public string getName() => name;
/// <summary>
/// Gets the player IP address.
/// </summary>
/// <returns>The IP address.</returns>
public InetSocketAddress getAddress() => ipAddress;
/// <inheritdoc/>
public bool isCancelled() => _cancelled;
/// <inheritdoc/>
public void setCancelled(bool cancel) => _cancelled = cancel;
}

View File

@@ -0,0 +1,59 @@
namespace Minecraft.Server.FourKit.Event.Player;
using Minecraft.Server.FourKit.Entity;
/// <summary>
/// Fired when a player moves. Plugins may modify the destination
/// or cancel the movement entirely.
/// </summary>
public class PlayerMoveEvent : PlayerEvent, Cancellable
{
private Location _from;
private Location _to;
private bool _cancelled;
internal PlayerMoveEvent(Player player, Location from, Location to) : base(player)
{
_from = from;
_to = to;
}
/// <summary>
/// Gets the location this player moved from.
/// </summary>
/// <returns>The from location.</returns>
public Location getFrom() => _from;
/// <summary>
/// Gets the location this player moved to.
/// </summary>
/// <returns>The to location.</returns>
public Location getTo() => _to;
/// <summary>
/// Sets the location to mark as where the player moved from.
/// </summary>
/// <param name="from">The new from location.</param>
public void setFrom(Location from)
{
_from = from;
}
/// <summary>
/// Sets the location that this player will move to.
/// </summary>
/// <param name="to">The new to location.</param>
public void setTo(Location to)
{
_to = to;
}
/// <inheritdoc />
public bool isCancelled() => _cancelled;
/// <inheritdoc />
public void setCancelled(bool cancel)
{
_cancelled = cancel;
}
}

View File

@@ -0,0 +1,41 @@
namespace Minecraft.Server.FourKit.Event.Player;
using Minecraft.Server.FourKit.Entity;
/// <summary>
/// Thrown when a player picks an item up from the ground.
/// If cancelled the item will not be picked up.
/// </summary>
public class PlayerPickupItemEvent : PlayerEvent, Cancellable
{
private readonly Item _item;
private readonly int _remaining;
private bool _cancelled;
internal PlayerPickupItemEvent(Player player, Item item, int remaining)
: base(player)
{
_item = item;
_remaining = remaining;
}
/// <summary>
/// Gets the Item picked up by the player.
/// </summary>
/// <returns>The <see cref="Item"/> entity.</returns>
public Item getItem() => _item;
/// <summary>
/// Gets the amount remaining on the ground, if any.
/// </summary>
/// <returns>Amount remaining on the ground.</returns>
public int getRemaining() => _remaining;
/// <inheritdoc/>
public bool isCancelled() => _cancelled;
/// <inheritdoc/>
public void setCancelled(bool cancel)
{
_cancelled = cancel;
}
}

View File

@@ -0,0 +1,22 @@
namespace Minecraft.Server.FourKit.Event.Player;
using Minecraft.Server.FourKit.Entity;
/// <summary>
/// Called when a player is about to teleport because it is in contact with a portal.
/// </summary>
public class PlayerPortalEvent : PlayerTeleportEvent
{
internal PlayerPortalEvent(Player player, Location from, Location to)
: base(player, from, to, TeleportCause.UNKNOWN) { }
/// <summary>
/// Constructs a new PlayerPortalEvent with the given cause.
/// </summary>
/// <param name="player">The player entering the portal.</param>
/// <param name="from">The location the player is coming from.</param>
/// <param name="to">The location the player is teleporting to.</param>
/// <param name="cause">The cause of this teleportation (should be a portal-related cause).</param>
public PlayerPortalEvent(Player player, Location from, Location to, TeleportCause cause)
: base(player, from, to, cause) { }
}

View File

@@ -0,0 +1,40 @@
namespace Minecraft.Server.FourKit.Event.Player;
using Minecraft.Server.FourKit.Net;
/// <summary>
/// Stores details for players attempting to log in.
/// </summary>
public class PlayerPreLoginEvent : Event, Cancellable
{
private string name;
private InetSocketAddress ipAddress; //bukkit uses InetAddress but we expose port also
private bool _cancelled;
internal PlayerPreLoginEvent(string name, InetSocketAddress ipAddress) : base()
{
this.name = name;
this.ipAddress = ipAddress;
}
/// <summary>
/// Gets the player's name.
/// </summary>
/// <returns>The player's name.</returns>
public string getName() => name;
/// <summary>
/// Gets the player IP address.
/// </summary>
/// <returns>The IP address.</returns>
public InetSocketAddress getAddress() => ipAddress;
/// <inheritdoc/>
public bool isCancelled() => _cancelled;
/// <inheritdoc/>
public void setCancelled(bool cancel) => _cancelled = cancel;
}

Some files were not shown because too many files have changed in this diff Show More