Merge remote-tracking branch 'Revelations/main' into upstream-merge

# Conflicts:
#	.github/workflows/nightly.yml
#	CMakeLists.txt
#	Minecraft.Client/CMakeLists.txt
#	Minecraft.Client/StringTable.cpp
#	Minecraft.Server/FourKitNatives.cpp
#	Minecraft.Server/Windows64/ServerMain.cpp
#	Minecraft.World/BiomeDecorator.cpp
#	Minecraft.World/BiomeSource.cpp
#	Minecraft.World/cmake/sources/Common.cmake
#	README.md
#	cmake/ServerTarget.cmake
This commit is contained in:
George V. 2026-04-28 03:01:30 +03:00
commit 2293dcbb9f
No known key found for this signature in database
GPG key ID: A3D551B5C5B36850
125 changed files with 4175 additions and 292 deletions

View file

@ -104,7 +104,7 @@ byteArray ArchiveFile::getFile(const wstring &filename)
app.DebugPrintf("Couldn't find file in archive\n");
app.DebugPrintf("Failed to find file '%ls' in archive\n", filename.c_str());
#ifndef _CONTENT_PACKAGE
__debugbreak();
DEBUG_BREAK();
#endif
app.FatalLoadError();
}

View file

@ -1036,7 +1036,7 @@ bool Chunk::isEmpty()
void Chunk::setDirty()
{
// 4J - not used, but if this starts being used again then we'll need to investigate how best to handle it.
__debugbreak();
DEBUG_BREAK();
levelRenderer->setGlobalChunkFlag(x, y, z, level, LevelRenderer::CHUNK_FLAG_DIRTY);
}

View file

@ -698,23 +698,26 @@ void SoundEngine::playUI(int iSound, float volume, float pitch)
{
U8 szSoundName[256];
wstring name;
const char* soundDir;
if (iSound >= eSFX_MAX)
{
strcpy((char*)szSoundName, "Minecraft/");
name = wchSoundNames[iSound];
soundDir = "Minecraft";
}
else
{
strcpy((char*)szSoundName, "Minecraft/UI/");
name = wchUISoundNames[iSound];
soundDir = "Minecraft/UI";
}
char* SoundName = (char*)ConvertSoundPathToName(name);
strcat((char*)szSoundName, SoundName);
char basePath[256];
sprintf_s(basePath, "Windows64Media/Sound/Minecraft/UI/%s", ConvertSoundPathToName(name));
sprintf_s(basePath, "Windows64Media/Sound/%s/%s", soundDir, ConvertSoundPathToName(name));
char finalPath[256];
sprintf_s(finalPath, "%s.wav", basePath);

View file

@ -96,7 +96,7 @@ CMinecraftApp::CMinecraftApp()
// 4J Stu - See comment for GAME_SETTINGS_PROFILE_DATA_BYTES in Xbox_App.h
DebugPrintf("WARNING: The size of the profile GAME_SETTINGS struct has changed, so all stat data is likely incorrect. Is: %d, Should be: %d\n",sizeof(GAME_SETTINGS),GAME_SETTINGS_PROFILE_DATA_BYTES);
#ifndef _CONTENT_PACKAGE
__debugbreak();
DEBUG_BREAK();
#endif
}
@ -1415,9 +1415,6 @@ int CMinecraftApp::OldProfileVersionCallback(LPVOID pParam,unsigned char *pucDat
{
// This might be from a version during testing of new profile updates
app.DebugPrintf("Don't know what to do with this profile version!\n");
#ifndef _CONTENT_PACKAGE
// __debugbreak();
#endif
GAME_SETTINGS *pGameSettings=(GAME_SETTINGS *)pucData;
pGameSettings->ucMenuSensitivity=100; //eGameSetting_Sensitivity_InMenu
@ -6647,7 +6644,7 @@ void CMinecraftApp::InitialiseTips()
{
// the m_TriviaTipA or the m_GameTipA are out of sync
#ifndef _CONTENT_PACKAGE
__debugbreak();
DEBUG_BREAK();
#endif
}
}
@ -6900,6 +6897,8 @@ wstring CMinecraftApp::FormatChatMessage(const wstring& desc, bool applyStyling)
results = replaceAll(results, L"§f", replacements);
results = replaceAll(results, L"§r", replacements); //we only support color so reset is the same as white color
results = replaceAll(results, L"'", L"\u2019");
if (applyStyling) {
std::wsmatch match;
while (std::regex_search(results, match, IDS_Pattern)) {

View file

@ -171,7 +171,7 @@ DLCPack *DLCManager::getPack(DWORD index, EDLCType type /*= e_DLCType_All*/)
if(index >= m_packs.size())
{
app.DebugPrintf("DLCManager: Trying to access a DLC pack beyond the range of valid packs\n");
__debugbreak();
DEBUG_BREAK();
}
pack = m_packs[index];
}
@ -186,7 +186,6 @@ DWORD DLCManager::getPackIndex(DLCPack *pack, bool &found, EDLCType type /*= e_D
if(pack == nullptr)
{
app.DebugPrintf("DLCManager: Attempting to find the index for a nullptr pack\n");
//__debugbreak();
return foundIndex;
}
if( type != e_DLCType_All )

View file

@ -107,7 +107,7 @@ void DLCPack::addChildPack(DLCPack *childPack)
#ifndef _CONTENT_PACKAGE
if(packId < 0 || packId > 15)
{
__debugbreak();
DEBUG_BREAK();
}
#endif
childPack->SetPackId( (packId<<24) | m_packId );
@ -362,7 +362,7 @@ DWORD DLCPack::getFileIndexAt(DLCManager::EDLCType type, const wstring &path, bo
{
app.DebugPrintf("Unimplemented\n");
#ifndef __CONTENT_PACKAGE
__debugbreak();
DEBUG_BREAK();
#endif
return 0;
}
@ -392,9 +392,6 @@ bool DLCPack::hasPurchasedFile(DLCManager::EDLCType type, const wstring &path)
/*if(type == DLCManager::e_DLCType_All)
{
app.DebugPrintf("Unimplemented\n");
#ifndef _CONTENT_PACKAGE
__debugbreak();
#endif
return false;
}
#ifndef _CONTENT_PACKAGE

View file

@ -122,7 +122,7 @@ void ConsoleSchematicFile::load(DataInputStream *dis)
{
#ifndef _CONTENT_PACKAGE
app.DebugPrintf("ConsoleSchematicFile has read a nullptr tile entity\n");
__debugbreak();
DEBUG_BREAK();
#endif
}
else
@ -635,7 +635,7 @@ void ConsoleSchematicFile::generateSchematicFile(DataOutputStream *dos, Level *l
}
#ifndef _CONTENT_PACKAGE
if(p!=blockCount) __debugbreak();
if(p!=blockCount) DEBUG_BREAK();
#endif
// We don't know how this will compress - just make a fixed length buffer to initially decompress into

View file

@ -24,7 +24,7 @@ GameRule::ValueType GameRule::getParameter(const wstring &parameterName)
{
#ifndef _CONTENT_PACKAGE
wprintf(L"WARNING: Parameter %ls was not set before being fetched\n", parameterName.c_str());
__debugbreak();
DEBUG_BREAK();
#endif
}
return m_parameters[parameterName];

View file

@ -238,7 +238,7 @@ HRESULT SonyLeaderboardManager::fillByIdsQuery(const SceNpId &myNpId, SceNpId* &
{
// 4J-JEV: Something terrible must have happend,
// 'getFriendslist' was supposed to be a synchronous operation.
__debugbreak();
DEBUG_BREAK();
// 4J-JEV: We can at least fall-back to just the players score.
len = 1;

View file

@ -247,8 +247,6 @@ void CPlatformNetworkManagerSony::HandlePlayerJoined(SQRNetworkPlayer *
void CPlatformNetworkManagerSony::HandlePlayerLeaving(SQRNetworkPlayer *pSQRPlayer)
{
//__debugbreak();
app.DebugPrintf( "Player 0x%p leaving.\n",
pSQRPlayer );

View file

@ -328,7 +328,7 @@ void UIScene::loadMovie()
{
app.DebugPrintf("ERROR: Could not find any iggy movie for %ls!\n", moviePath.c_str());
#ifndef _CONTENT_PACKAGE
__debugbreak();
DEBUG_BREAK();
#endif
app.FatalLoadError();
}
@ -344,7 +344,7 @@ void UIScene::loadMovie()
{
app.DebugPrintf("ERROR: Failed to load iggy scene!\n");
#ifndef _CONTENT_PACKAGE
__debugbreak();
DEBUG_BREAK();
#endif
app.FatalLoadError();
}
@ -969,9 +969,6 @@ void UIScene::_customDrawSlotControl(CustomDrawData *region, int iPad, shared_pt
// if(m_parentLayer == nullptr)
// {
// app.DebugPrintf("A scene is trying to navigate forwards, but it's parent layer is nullptr!\n");
//#ifndef _CONTENT_PACKAGE
// __debugbreak();
//#endif
// }
// else
// {
@ -988,10 +985,6 @@ void UIScene::navigateBack()
if(m_parentLayer == nullptr)
{
// app.DebugPrintf("A scene is trying to navigate back, but it's parent layer is nullptr!\n");
#ifndef _CONTENT_PACKAGE
// __debugbreak();
#endif
}
else
{
@ -1222,7 +1215,7 @@ void UIScene::externalCallback(IggyExternalFunctionCallUTF16 * call)
{
app.DebugPrintf("Callback for handlePress did not have the correct number of arguments\n");
#ifndef _CONTENT_PACKAGE
__debugbreak();
DEBUG_BREAK();
#endif
return;
}
@ -1230,7 +1223,7 @@ void UIScene::externalCallback(IggyExternalFunctionCallUTF16 * call)
{
app.DebugPrintf("Arguments for handlePress were not of the correct type\n");
#ifndef _CONTENT_PACKAGE
__debugbreak();
DEBUG_BREAK();
#endif
return;
}
@ -1242,7 +1235,7 @@ void UIScene::externalCallback(IggyExternalFunctionCallUTF16 * call)
{
app.DebugPrintf("Callback for handleFocusChange did not have the correct number of arguments\n");
#ifndef _CONTENT_PACKAGE
__debugbreak();
DEBUG_BREAK();
#endif
return;
}
@ -1250,7 +1243,7 @@ void UIScene::externalCallback(IggyExternalFunctionCallUTF16 * call)
{
app.DebugPrintf("Arguments for handleFocusChange were not of the correct type\n");
#ifndef _CONTENT_PACKAGE
__debugbreak();
DEBUG_BREAK();
#endif
return;
}
@ -1262,7 +1255,7 @@ void UIScene::externalCallback(IggyExternalFunctionCallUTF16 * call)
{
app.DebugPrintf("Callback for handleInitFocus did not have the correct number of arguments\n");
#ifndef _CONTENT_PACKAGE
__debugbreak();
DEBUG_BREAK();
#endif
return;
}
@ -1270,7 +1263,7 @@ void UIScene::externalCallback(IggyExternalFunctionCallUTF16 * call)
{
app.DebugPrintf("Arguments for handleInitFocus were not of the correct type\n");
#ifndef _CONTENT_PACKAGE
__debugbreak();
DEBUG_BREAK();
#endif
return;
}
@ -1282,7 +1275,7 @@ void UIScene::externalCallback(IggyExternalFunctionCallUTF16 * call)
{
app.DebugPrintf("Callback for handleCheckboxToggled did not have the correct number of arguments\n");
#ifndef _CONTENT_PACKAGE
__debugbreak();
DEBUG_BREAK();
#endif
return;
}
@ -1290,7 +1283,7 @@ void UIScene::externalCallback(IggyExternalFunctionCallUTF16 * call)
{
app.DebugPrintf("Arguments for handleCheckboxToggled were not of the correct type\n");
#ifndef _CONTENT_PACKAGE
__debugbreak();
DEBUG_BREAK();
#endif
return;
}
@ -1302,7 +1295,7 @@ void UIScene::externalCallback(IggyExternalFunctionCallUTF16 * call)
{
app.DebugPrintf("Callback for handleSliderMove did not have the correct number of arguments\n");
#ifndef _CONTENT_PACKAGE
__debugbreak();
DEBUG_BREAK();
#endif
return;
}
@ -1310,7 +1303,7 @@ void UIScene::externalCallback(IggyExternalFunctionCallUTF16 * call)
{
app.DebugPrintf("Arguments for handleSliderMove were not of the correct type\n");
#ifndef _CONTENT_PACKAGE
__debugbreak();
DEBUG_BREAK();
#endif
return;
}
@ -1322,7 +1315,7 @@ void UIScene::externalCallback(IggyExternalFunctionCallUTF16 * call)
{
app.DebugPrintf("Callback for handleAnimationEnd did not have the correct number of arguments\n");
#ifndef _CONTENT_PACKAGE
__debugbreak();
DEBUG_BREAK();
#endif
return;
}
@ -1334,7 +1327,7 @@ void UIScene::externalCallback(IggyExternalFunctionCallUTF16 * call)
{
app.DebugPrintf("Callback for handleSelectionChanged did not have the correct number of arguments\n");
#ifndef _CONTENT_PACKAGE
__debugbreak();
DEBUG_BREAK();
#endif
return;
}
@ -1342,7 +1335,7 @@ void UIScene::externalCallback(IggyExternalFunctionCallUTF16 * call)
{
app.DebugPrintf("Arguments for handleSelectionChanged were not of the correct type\n");
#ifndef _CONTENT_PACKAGE
__debugbreak();
DEBUG_BREAK();
#endif
return;
}
@ -1360,7 +1353,7 @@ void UIScene::externalCallback(IggyExternalFunctionCallUTF16 * call)
{
app.DebugPrintf("Callback for handleRequestMoreData did not have the correct number of arguments\n");
#ifndef _CONTENT_PACKAGE
__debugbreak();
DEBUG_BREAK();
#endif
return;
}
@ -1368,7 +1361,7 @@ void UIScene::externalCallback(IggyExternalFunctionCallUTF16 * call)
{
app.DebugPrintf("Arguments for handleRequestMoreData were not of the correct type\n");
#ifndef _CONTENT_PACKAGE
__debugbreak();
DEBUG_BREAK();
#endif
return;
}

View file

@ -409,7 +409,7 @@ void UIScene_MainMenu::handlePress(F64 controlId, F64 childId)
break;
#endif
default: __debugbreak();
default: DEBUG_BREAK();
}
bool confirmUser = false;

View file

@ -127,7 +127,7 @@ wstring DLCTexturePack::getResource(const wstring& name)
{
// 4J Stu - We should never call this function
#ifndef __CONTENT_PACKAGE
__debugbreak();
DEBUG_BREAK();
#endif
return L"";
}
@ -136,7 +136,7 @@ InputStream *DLCTexturePack::getResourceImplementation(const wstring &name) //th
{
// 4J Stu - We should never call this function
#ifndef _CONTENT_PACKAGE
__debugbreak();
DEBUG_BREAK();
if(hasFile(name)) return nullptr;
#endif
return nullptr; //resource;

View file

@ -641,7 +641,7 @@ int CConsoleMinecraftApp::Callback_TMSPPReadDLCFile(void *pParam,int iPad, int i
{
DWORD error = GetLastError();
app.DebugPrintf("Failed to open DLCXbox1.cmp with error code %d (%x)\n", error, error);
__debugbreak();
DEBUG_BREAK();
return 0;
}

View file

@ -20,7 +20,7 @@ void ConsoleUIController::init(Microsoft::WRL::ComPtr<ID3D11Device> dev, Microso
{
app.DebugPrintf("Failed to initialise GDraw!\n");
#ifndef _CONTENT_PACKAGE
__debugbreak();
DEBUG_BREAK();
#endif
app.FatalLoadError();
}

View file

@ -1619,7 +1619,7 @@ RADDEFSTART
#define RR_BREAK() __builtin_trap()
#define RR_CACHE_LINE_SIZE 32
#elif defined(__RADXENON__)
#define RR_BREAK() __debugbreak()
#define RR_BREAK() DEBUG_BREAK()
#define RR_CACHE_LINE_SIZE 128
#elif defined(__RADANDROID__)
#define RR_BREAK() __builtin_trap()

View file

@ -1619,7 +1619,7 @@ RADDEFSTART
#define RR_BREAK() __builtin_trap()
#define RR_CACHE_LINE_SIZE 32
#elif defined(__RADXENON__)
#define RR_BREAK() __debugbreak()
#define RR_BREAK() DEBUG_BREAK()
#define RR_CACHE_LINE_SIZE 128
#elif defined(__RADANDROID__)
#define RR_BREAK() __builtin_trap()

View file

@ -187,8 +187,6 @@ void CPlatformNetworkManagerDurango::HandlePlayerJoined(DQRNetworkPlayer *pDQRPl
void CPlatformNetworkManagerDurango::HandlePlayerLeaving(DQRNetworkPlayer *pDQRPlayer)
{
//__debugbreak();
app.DebugPrintf( "Player 0x%p leaving.\n",
pDQRPlayer );

View file

@ -204,7 +204,7 @@ EntityRenderer *EntityRenderDispatcher::getRenderer(eINSTANCEOF e)
{
app.DebugPrintf("Couldn't find renderer for entity of type %d\n", e);
// New renderer mapping required in above table
__debugbreak();
DEBUG_BREAK();
}
/* 4J - not doing this hierarchical search anymore. We need to explicitly add renderers for any eINSTANCEOF type that we want to be able to render
if (it == renderers.end() && e != Entity::_class)

View file

@ -88,7 +88,7 @@ void EntityTracker::addEntity(shared_ptr<Entity> e, int range, int updateInterva
}
if( e->entityId >= 16384 )
{
__debugbreak();
DEBUG_BREAK();
}
shared_ptr<TrackedEntity> te = std::make_shared<TrackedEntity>(e, range, updateInterval, trackDeltas);
entities.insert(te);

View file

@ -2219,7 +2219,7 @@ void GameRenderer::setupFog(int i, float alpha)
if (i == 999)
{
__debugbreak();
DEBUG_BREAK();
// 4J TODO
/*
glFog(GL_FOG_COLOR, getBuffer(0, 0, 0, 1));

View file

@ -697,8 +697,8 @@ void Gui::render(float a, bool mouseFree, int xMouse, int yMouse)
glEnable(GL_COLOR_MATERIAL);
// 4J - TomK now using safe zone values directly instead of the magic number calculation that lived here before (which only worked for medium scale, the other two were off!)
int xo = iSafezoneXHalf + 10;
int yo = iSafezoneTopYHalf + 10;
int xo = iSafezoneXHalf + 10; // TODO: fix relative scaling for atrocious aspect ratios
int yo = iSafezoneTopYHalf + 10; // TODO: fix relative scaling for atrocious aspect ratios
#ifdef __PSVITA__
// align directly with corners, there are no safe zones on vita
@ -708,8 +708,29 @@ void Gui::render(float a, bool mouseFree, int xMouse, int yMouse)
glPushMatrix();
glTranslatef(static_cast<float>(xo), static_cast<float>(yo), 50);
float ss = 12;
glScalef(-ss, ss, ss);
// correct paper doll aspect ratio
float ss = 12.0f;
float aspectScaleX = 1.0f;
float aspectScaleY = 1.0f;
extern int g_rScreenWidth;
extern int g_rScreenHeight;
if (g_rScreenWidth > 0 && g_rScreenHeight > 0) {
float screenAspect = (float)g_rScreenWidth / (float)g_rScreenHeight;
const float targetAspect = 16.0f / 9.0f;
// apply correction if window is not already at a 16:9 aspect ratio
if (fabs(screenAspect - targetAspect) > 0.01f) {
if (screenAspect > targetAspect)
aspectScaleX = targetAspect / screenAspect;
else
aspectScaleY = screenAspect / targetAspect;
}
}
glScalef(-ss * aspectScaleX, ss * aspectScaleY, ss);
glRotatef(180, 0, 0, 1);
float oyr = minecraft->player->yRot;

View file

@ -573,7 +573,7 @@ void LocalPlayer::changeDimension(int i)
//minecraft.setScreen(new WinScreen());
#ifndef _CONTENT_PACKAGE
app.DebugPrintf("LocalPlayer::changeDimension from 1 to 1 but WinScreen has not been implemented.\n");
__debugbreak();
DEBUG_BREAK();
#endif
}
else

View file

@ -4991,7 +4991,7 @@ void Minecraft::main()
app.DebugPrintf("<xs:enumeration value=\"%d\"><xs:annotation><xs:documentation>%ls</xs:documentation></xs:annotation></xs:enumeration>\n", i, app.GetString( Tile::tiles[i]->getDescriptionId() ));
}
}
__debugbreak();
DEBUG_BREAK();
#endif
// 4J-PB - Can't call this for the first 5 seconds of a game - MS rule

View file

@ -85,6 +85,9 @@ vector<INetworkPlayer *> MinecraftServer::s_sentTo;
int MinecraftServer::s_slowQueuePlayerIndex = 0;
int MinecraftServer::s_slowQueueLastTime = 0;
bool MinecraftServer::s_slowQueuePacketSent = false;
#ifdef MINECRAFT_SERVER_BUILD
int MinecraftServer::s_dedicatedChunkSendsThisTick = 0;
#endif
#endif
unordered_map<wstring, int> MinecraftServer::ironTimers;
@ -1772,6 +1775,12 @@ void MinecraftServer::run(int64_t seed, void *lpParameter)
int64_t unprocessedTime = 0;
while (running && !s_bServerHalted)
{
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
// Full wall-clock cost of one run loop iteration (catch-up ticks
// + setTime handlers + XUI delayed actions + Sleep).
int64_t outerIterStart = getCurrentTimeMillis();
int64_t outerIterTickWork = 0;
#endif
int64_t now = getCurrentTimeMillis();
// 4J Stu - When we pause the server, we don't want to count that as time passed
@ -1809,14 +1818,39 @@ void MinecraftServer::run(int64_t seed, void *lpParameter)
while (unprocessedTime > MS_PER_TICK)
{
unprocessedTime -= MS_PER_TICK;
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
// Per-iteration pre/tick/post timing.
int64_t iter_t0 = System::currentTimeMillis();
#endif
chunkPacketManagement_PreTick();
// int64_t before = System::currentTimeMillis();
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
int64_t iter_t1 = System::currentTimeMillis();
#endif
tick();
// int64_t after = System::currentTimeMillis();
// PIXReportCounter(L"Server time",(float)(after-before));
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
int64_t iter_t2 = System::currentTimeMillis();
#endif
chunkPacketManagement_PostTick();
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
int64_t iter_t3 = System::currentTimeMillis();
int64_t iter_total = iter_t3 - iter_t0;
outerIterTickWork += iter_total;
if (iter_total > 60)
{
ServerRuntime::LogInfof("perf",
"iter total=%lldms pre=%lld tick=%lld post=%lld",
(long long)iter_total,
(long long)(iter_t1 - iter_t0),
(long long)(iter_t2 - iter_t1),
(long long)(iter_t3 - iter_t2));
}
#endif
}
// Do NOT reset lastTime here. Resetting discards the wall
// time spent in the catch-up so passedTime restarts from
// post-tick, capping effective TPS at 1000 / (MS_PER_TICK
// + avgTickBody). Runaway after a real freeze is bounded
// by the passedTime > MS_PER_TICK * 40 cap above.
// int64_t afterall = System::currentTimeMillis();
// PIXReportCounter(L"Server time all",(float)(afterall-beforeall));
// PIXReportCounter(L"Server ticks",(float)tickcount);
@ -2102,6 +2136,73 @@ void MinecraftServer::run(int64_t seed, void *lpParameter)
}
Sleep(1);
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
int64_t outerIterTotal = getCurrentTimeMillis() - outerIterStart;
// Distribution histogram (gated). Buckets every outer iter, dumps
// the bucket counts + a self-computed TPS every ~10 seconds.
if (ServerRuntime::g_serverPerfTrace)
{
static const int kBucketCount = 14;
static int64_t s_bucketEdges[kBucketCount] = {
2, 5, 10, 20, 30, 40, 50, 60, 80, 100, 200, 500, 1000, INT64_MAX
};
static unsigned int s_buckets[kBucketCount] = {0};
static int64_t s_histWindowStartMs = 0;
static int s_histWindowStartTick = 0;
static int64_t s_histTotalIterMs = 0;
static unsigned int s_histTotalIters = 0;
static unsigned int s_histTickIters = 0;
int64_t nowMsForHist = getCurrentTimeMillis();
if (s_histWindowStartMs == 0)
{
s_histWindowStartMs = nowMsForHist;
s_histWindowStartTick = (int)tickCount;
}
for (int b = 0; b < kBucketCount; b++)
{
if (outerIterTotal <= s_bucketEdges[b])
{
s_buckets[b]++;
break;
}
}
s_histTotalIterMs += outerIterTotal;
s_histTotalIters++;
if (outerIterTickWork > 0) s_histTickIters++;
int ticksThisWindow = (int)tickCount - s_histWindowStartTick;
if (ticksThisWindow >= 200)
{
int64_t windowMs = nowMsForHist - s_histWindowStartMs;
double calcTps = windowMs > 0 ? (ticksThisWindow * 1000.0) / windowMs : 0.0;
double avgIterMs = s_histTotalIters > 0 ? (double)s_histTotalIterMs / s_histTotalIters : 0.0;
ServerRuntime::LogInfof("perf",
"histogram window: %d ticks in %lldms calcTps=%.2f iters=%u tickIters=%u avgIter=%.2fms | "
"<=2:%u <=5:%u <=10:%u <=20:%u <=30:%u <=40:%u <=50:%u <=60:%u <=80:%u <=100:%u <=200:%u <=500:%u <=1000:%u >1000:%u",
ticksThisWindow, (long long)windowMs, calcTps,
s_histTotalIters, s_histTickIters, avgIterMs,
s_buckets[0], s_buckets[1], s_buckets[2], s_buckets[3],
s_buckets[4], s_buckets[5], s_buckets[6], s_buckets[7],
s_buckets[8], s_buckets[9], s_buckets[10], s_buckets[11],
s_buckets[12], s_buckets[13]);
for (int b = 0; b < kBucketCount; b++) s_buckets[b] = 0;
s_histWindowStartMs = nowMsForHist;
s_histWindowStartTick = (int)tickCount;
s_histTotalIterMs = 0;
s_histTotalIters = 0;
s_histTickIters = 0;
}
}
if (outerIterTotal > 60)
{
ServerRuntime::LogInfof("perf",
"outerIter total=%lldms tickWork=%lld postTickOverhead=%lld",
(long long)outerIterTotal,
(long long)outerIterTickWork,
(long long)(outerIterTotal - outerIterTickWork));
}
#endif
}
}
//else
@ -2156,6 +2257,17 @@ void MinecraftServer::broadcastStopSavingPacket()
void MinecraftServer::tick()
{
// Per-substep wall-clock timing. Logs one summary line when total tick
// exceeds TICK_SLOW_THRESHOLD_MS.
const int64_t TICK_SLOW_THRESHOLD_MS = 60;
const int kMaxLevelsRecorded = 8;
int64_t tickStart = System::currentTimeMillis();
int64_t lvlTickMs[kMaxLevelsRecorded] = {0};
int64_t lvlEntMs[kMaxLevelsRecorded] = {0};
int64_t lvlTrkMs[kMaxLevelsRecorded] = {0};
int lvlDimId[kMaxLevelsRecorded] = {0};
unsigned int recordedLevels = 0;
vector<wstring> toRemove;
for ( auto& it : ironTimers )
{
@ -2219,11 +2331,8 @@ void MinecraftServer::tick()
int64_t st2 = System::currentTimeMillis();
PIXEndNamedEvent();
PIXBeginNamedEvent(0,"Entity tick %d",i);
// 4J added to stop ticking entities in levels when players are not in those levels.
// Note: now changed so that we also tick if there are entities to be removed, as this also happens as a result of calling tickEntities. If we don't do this, then the
// entities get removed at the first point that there is a player count in the level - this has been causing a problem when going from normal dimension -> nether -> normal,
// as the player is getting flagged as to be removed (from the normal dimension) when going to the nether, but Actually gets removed only when it returns
if( ( players->getPlayerCount(level) > 0) || ( level->hasEntitiesToRemove() ) )
// 4J added: do not tick entities in empty dimensions.
if ((players->getPlayerCount(level) > 0) || level->hasEntitiesToRemove())
{
#ifdef __PSVITA__
// AP - the PlayerList->viewDistance initially starts out at 3 to make starting a level speedy
@ -2240,6 +2349,8 @@ void MinecraftServer::tick()
}
PIXEndNamedEvent();
int64_t stEntDone = System::currentTimeMillis();
PIXBeginNamedEvent(0,"Entity tracker tick");
level->getTracker()->tick();
PIXEndNamedEvent();
@ -2248,9 +2359,21 @@ void MinecraftServer::tick()
// printf(">>>>>>>>>>>>>>>>>>>>>> Tick %d %d %d : %d\n", st1 - st0, st2 - st1, st3 - st2, st0 - stc );
stc = st0;
// #endif// __PS3__
// Record per-level breakdown for the slow-tick summary.
if (i < kMaxLevelsRecorded)
{
lvlTickMs[i] = st1 - st0; // Level::tick (mob spawner, chunk source, tile ticks, etc.)
lvlEntMs[i] = stEntDone - st2; // tickEntities (per-entity AI/physics)
lvlTrkMs[i] = st3 - stEntDone; // EntityTracker::tick (visibility & broadcasts)
lvlDimId[i] = level->dimension->id;
recordedLevels = i + 1;
}
}
}
int64_t afterLevels = System::currentTimeMillis();
Entity::tickExtraWandering(); // 4J added
int64_t afterExtraW = System::currentTimeMillis();
// Process player disconnect/kick queue BEFORE ticking connections.
// PendingConnection::handleLogin rejects duplicate XUIDs, so the old
@ -2259,9 +2382,11 @@ void MinecraftServer::tick()
PIXBeginNamedEvent(0,"Players tick");
players->tick();
PIXEndNamedEvent();
int64_t afterPlayers = System::currentTimeMillis();
PIXBeginNamedEvent(0,"Connection tick");
connection->tick();
PIXEndNamedEvent();
int64_t afterConn = System::currentTimeMillis();
// 4J - removed
#if 0
@ -2275,6 +2400,35 @@ void MinecraftServer::tick()
// } catch (Exception e) {
// logger.log(Level.WARNING, "Unexpected exception while parsing console command", e);
// }
int64_t totalMs = System::currentTimeMillis() - tickStart;
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
if (totalMs > TICK_SLOW_THRESHOLD_MS)
{
// Build a single one-line breakdown so it greps cleanly. Per-level:
// Level::tick / tickEntities / tracker tick. Then global subsystems.
char buf[512];
int n = 0;
for (unsigned int i = 0; i < recordedLevels && n >= 0 && n < (int)sizeof(buf); i++)
{
n += snprintf(buf + n, sizeof(buf) - n,
" L%d:tick=%lld ent=%lld trk=%lld",
lvlDimId[i],
(long long)lvlTickMs[i],
(long long)lvlEntMs[i],
(long long)lvlTrkMs[i]);
}
ServerRuntime::LogInfof("perf",
"slow tick total=%lldms%s | extraW=%lld players=%lld conn=%lld",
(long long)totalMs,
buf,
(long long)(afterExtraW - afterLevels),
(long long)(afterPlayers - afterExtraW),
(long long)(afterConn - afterPlayers));
}
#else
(void)totalMs;
#endif
}
void MinecraftServer::handleConsoleInput(const wstring& msg, ConsoleInputSource *source)
@ -2418,7 +2572,9 @@ bool MinecraftServer::chunkPacketManagement_CanSendTo(INetworkPlayer *player)
if( player == nullptr ) return false;
#ifdef MINECRAFT_SERVER_BUILD
return true;
// Cap chunk-data sends per tick. Other players are served on later ticks
// via the per-tick rotation in ServerConnection::tick.
return s_dedicatedChunkSendsThisTick < DEDICATED_MAX_CHUNK_SENDS_PER_TICK;
#else
int time = GetTickCount();
DWORD currentPlayerCount = g_NetworkManager.GetPlayerCount();
@ -2437,10 +2593,16 @@ bool MinecraftServer::chunkPacketManagement_CanSendTo(INetworkPlayer *player)
void MinecraftServer::chunkPacketManagement_DidSendTo(INetworkPlayer *player)
{
s_slowQueuePacketSent = true;
#ifdef MINECRAFT_SERVER_BUILD
s_dedicatedChunkSendsThisTick++;
#endif
}
void MinecraftServer::chunkPacketManagement_PreTick()
{
#ifdef MINECRAFT_SERVER_BUILD
s_dedicatedChunkSendsThisTick = 0;
#endif
}
void MinecraftServer::chunkPacketManagement_PostTick()

View file

@ -258,6 +258,12 @@ private:
static int s_slowQueuePlayerIndex;
static int s_slowQueueLastTime;
static bool s_slowQueuePacketSent;
#ifdef MINECRAFT_SERVER_BUILD
// Cap on chunk-data packet sends per tick. Paired with per-tick rotation
// in ServerConnection::tick so every player gets a turn even when bound.
static int s_dedicatedChunkSendsThisTick;
static const int DEDICATED_MAX_CHUNK_SENDS_PER_TICK = 10;
#endif
#endif
bool IsServerPaused() { return m_isServerPaused; }

View file

@ -1619,7 +1619,7 @@ RADDEFSTART
#define RR_BREAK() __builtin_trap()
#define RR_CACHE_LINE_SIZE 32
#elif defined(__RADXENON__)
#define RR_BREAK() __debugbreak()
#define RR_BREAK() DEBUG_BREAK()
#define RR_CACHE_LINE_SIZE 128
#elif defined(__RADANDROID__)
#define RR_BREAK() __builtin_trap()

View file

@ -245,7 +245,7 @@ bool OrbisLeaderboardManager::getScoreByIds()
{
// 4J-JEV: Something terrible must have happend,
// 'getFriendslist' was supposed to be a synchronous operation.
__debugbreak();
DEBUG_BREAK();
// 4J-JEV: We can at least fall-back to just the players score.
num = 1;

View file

@ -1619,7 +1619,7 @@ RADDEFSTART
#define RR_BREAK() __builtin_trap()
#define RR_CACHE_LINE_SIZE 32
#elif defined(__RADXENON__)
#define RR_BREAK() __debugbreak()
#define RR_BREAK() DEBUG_BREAK()
#define RR_CACHE_LINE_SIZE 128
#elif defined(__RADANDROID__)
#define RR_BREAK() __builtin_trap()

View file

@ -1619,7 +1619,7 @@ RADDEFSTART
#define RR_BREAK() __builtin_trap()
#define RR_CACHE_LINE_SIZE 32
#elif defined(__RADXENON__)
#define RR_BREAK() __debugbreak()
#define RR_BREAK() DEBUG_BREAK()
#define RR_CACHE_LINE_SIZE 128
#elif defined(__RADANDROID__)
#define RR_BREAK() __builtin_trap()

View file

@ -1619,7 +1619,7 @@ RADDEFSTART
#define RR_BREAK() __builtin_trap()
#define RR_CACHE_LINE_SIZE 32
#elif defined(__RADXENON__)
#define RR_BREAK() __debugbreak()
#define RR_BREAK() DEBUG_BREAK()
#define RR_CACHE_LINE_SIZE 128
#elif defined(__RADANDROID__)
#define RR_BREAK() __builtin_trap()

View file

@ -1619,7 +1619,7 @@ RADDEFSTART
#define RR_BREAK() __builtin_trap()
#define RR_CACHE_LINE_SIZE 32
#elif defined(__RADXENON__)
#define RR_BREAK() __debugbreak()
#define RR_BREAK() DEBUG_BREAK()
#define RR_CACHE_LINE_SIZE 128
#elif defined(__RADANDROID__)
#define RR_BREAK() __builtin_trap()

View file

@ -1619,7 +1619,7 @@ RADDEFSTART
#define RR_BREAK() __builtin_trap()
#define RR_CACHE_LINE_SIZE 32
#elif defined(__RADXENON__)
#define RR_BREAK() __debugbreak()
#define RR_BREAK() DEBUG_BREAK()
#define RR_CACHE_LINE_SIZE 128
#elif defined(__RADANDROID__)
#define RR_BREAK() __builtin_trap()

View file

@ -218,7 +218,7 @@ void Particle::setTex(Textures *textures, Icon *icon)
{
#ifndef _CONTENT_PACKAGE
printf("Invalid call to Particle.setTex, use coordinate methods\n");
__debugbreak();
DEBUG_BREAK();
#endif
//throw new RuntimeException("Invalid call to Particle.setTex, use coordinate methods");
}
@ -230,7 +230,7 @@ void Particle::setMiscTex(int slotIndex)
{
#ifndef _CONTENT_PACKAGE
printf("Invalid call to Particle.setMixTex\n");
__debugbreak();
DEBUG_BREAK();
//throw new RuntimeException("Invalid call to Particle.setMiscTex");
#endif
}

View file

@ -13,6 +13,14 @@
#include "../Minecraft.World/System.h"
#include "PlayerList.h"
#include <unordered_set>
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
#include "../Minecraft.Server/ServerLogger.h"
// Per-tick accumulators for tickAddRequests timing.
static int64_t g_findUsAccum = 0;
static int64_t g_addUsAccum = 0;
static int g_addCount = 0;
#endif
PlayerChunkMap::PlayerChunk::PlayerChunk(int x, int z, PlayerChunkMap *pcm) : pos(x,z)
{
@ -74,7 +82,7 @@ void PlayerChunkMap::PlayerChunk::add(shared_ptr<ServerPlayer> player, bool send
players.push_back(player);
player->chunksToSend.push_back(pos);
player->chunksToSend.insert(pos);
#ifdef _LARGE_WORLDS
parent->getLevel()->cache->dontDrop(pos.x, pos.z); // 4J Added;
@ -118,7 +126,7 @@ void PlayerChunkMap::PlayerChunk::remove(shared_ptr<ServerPlayer> player)
parent->getLevel()->cache->drop(pos.x, pos.z);
}
player->chunksToSend.remove(pos);
player->chunksToSend.erase(pos);
// 4J - I don't think there's any point sending these anymore, as we don't need to unload chunks with fixed sized maps
// 4J - We do need to send these to unload entities in chunks when players are dead. If we do not and the entity is removed
// while they are dead, that entity will remain in the clients world
@ -369,6 +377,15 @@ ServerLevel *PlayerChunkMap::getLevel()
void PlayerChunkMap::tick()
{
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
// Substep timing for slow chunkMap diagnostics.
const int64_t CHUNKMAP_SLOW_THRESHOLD_MS = 50;
int64_t cm_t0 = System::currentTimeMillis();
int cm_addReqStart = (int)addRequests.size();
g_findUsAccum = 0;
g_addUsAccum = 0;
g_addCount = 0;
#endif
int64_t time = level->getGameTime();
if (time - lastInhabitedUpdate > Level::TICKS_PER_DAY / 3)
@ -385,6 +402,9 @@ void PlayerChunkMap::tick()
chunk->updateInhabitedTime();
}
}
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
int64_t cm_t1 = System::currentTimeMillis();
#endif
// 4J - some changes here so that we only send one region update per tick. The chunks themselves also
// limit their resend rate to once every MIN_TICKS_BETWEEN_REGION_UPDATE ticks
@ -404,11 +424,35 @@ void PlayerChunkMap::tick()
i++;
}
}
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
int64_t cm_t2 = System::currentTimeMillis();
#endif
for( unsigned int i = 0; i < players.size(); i++ )
{
tickAddRequests(players[i]);
}
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
int64_t cm_t3 = System::currentTimeMillis();
int64_t cm_total = cm_t3 - cm_t0;
if (cm_total > CHUNKMAP_SLOW_THRESHOLD_MS)
{
ServerRuntime::LogInfof("perf",
"L%d chunkMap total=%lldms inhabited=%lld changed=%lld addReq=%lld | players=%d addReqQueue=%d->%d | adds=%d findUs=%lld addUs=%lld avgAddUs=%lld",
this->dimension,
(long long)cm_total,
(long long)(cm_t1 - cm_t0),
(long long)(cm_t2 - cm_t1),
(long long)(cm_t3 - cm_t2),
(int)players.size(),
cm_addReqStart,
(int)addRequests.size(),
g_addCount,
(long long)g_findUsAccum,
(long long)g_addUsAccum,
g_addCount > 0 ? (long long)(g_addUsAccum / g_addCount) : 0LL);
}
#endif
// 4J Stu - Added 1.1 but not relevant to us as we never no 0 players anyway, and don't think we should be dropping stuff
//if (players.isEmpty()) {
@ -490,7 +534,11 @@ void PlayerChunkMap::getChunkAndRemovePlayer(int x, int z, shared_ptr<ServerPlay
// Processes up to CHUNKS_PER_PLAYER_PER_TICK requests per call to speed up initial chunk loading.
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
static const int CHUNKS_PER_PLAYER_PER_TICK = 16;
// Per-player drain rate for the addRequest queue. Each PlayerChunk::add
// issues a synchronous ChunkVisibilityPacket, so per-tick send volume
// scales as players * this. Pair with DEDICATED_MAX_CHUNK_SENDS_PER_TICK
// on the chunk-data side.
static const int CHUNKS_PER_PLAYER_PER_TICK = 4;
#else
static const int CHUNKS_PER_PLAYER_PER_TICK = 1;
#endif
@ -504,6 +552,11 @@ void PlayerChunkMap::tickAddRequests(shared_ptr<ServerPlayer> player)
for (int processed = 0; processed < CHUNKS_PER_PLAYER_PER_TICK; processed++)
{
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
LARGE_INTEGER qpcFreq, qpcA, qpcB, qpcC;
QueryPerformanceFrequency(&qpcFreq);
QueryPerformanceCounter(&qpcA);
#endif
// Find the nearest chunk request to the player
int minDistSq = -1;
auto itNearest = addRequests.end();
@ -522,12 +575,21 @@ void PlayerChunkMap::tickAddRequests(shared_ptr<ServerPlayer> player)
}
}
}
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
QueryPerformanceCounter(&qpcB);
g_findUsAccum += (qpcB.QuadPart - qpcA.QuadPart) * 1000000 / qpcFreq.QuadPart;
#endif
// If we found one, process it and continue; otherwise done
if( itNearest != addRequests.end() )
{
getChunk(itNearest->x, itNearest->z, true)->add(itNearest->player);
addRequests.erase(itNearest);
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
QueryPerformanceCounter(&qpcC);
g_addUsAccum += (qpcC.QuadPart - qpcB.QuadPart) * 1000000 / qpcFreq.QuadPart;
g_addCount++;
#endif
}
else
{
@ -781,7 +843,7 @@ bool PlayerChunkMap::isPlayerIn(shared_ptr<ServerPlayer> player, int xChunk, int
else
{
auto it1 = find(chunk->players.begin(), chunk->players.end(), player);
auto it2 = find(player->chunksToSend.begin(), player->chunksToSend.end(), chunk->pos);
auto it2 = player->chunksToSend.find(chunk->pos);
return it1 != chunk->players.end() && it2 == player->chunksToSend.end();
}

View file

@ -329,7 +329,9 @@ bool PlayerList::placeNewPlayer(Connection *connection, shared_ptr<ServerPlayer>
// 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" + playerEntity->name + L" joined the game.") ) );
#if !(defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD))
broadcastAll(std::make_shared<ChatPacket>(player->name, ChatPacket::e_ChatPlayerJoinedGame));
#endif
MemSect(14);
add(player);

View file

@ -227,7 +227,6 @@ void PreStitchedTextureMap::makeTextureAnimated(TexturePack *texturePack, Stitch
if(first->getWidth() != tex->getWidth() || first->getHeight() != tex->getHeight())
{
app.DebugPrintf("%ls - first w - %d, h - %d, tex w - %d, h - %d\n",textureFileName.c_str(),first->getWidth(),tex->getWidth(),first->getHeight(),tex->getHeight());
//__debugbreak();
}
#endif
@ -246,7 +245,7 @@ StitchedTexture *PreStitchedTextureMap::getTexture(const wstring &name)
{
#ifndef _CONTENT_PACKAGE
app.DebugPrintf("Not implemented!\n");
__debugbreak();
DEBUG_BREAK();
#endif
return nullptr;
#if 0
@ -277,7 +276,7 @@ Icon *PreStitchedTextureMap::registerIcon(const wstring &name)
{
app.DebugPrintf("Don't register nullptr\n");
#ifndef _CONTENT_PACKAGE
__debugbreak();
DEBUG_BREAK();
#endif
result = missingPosition;
//new RuntimeException("Don't register null!").printStackTrace();
@ -290,7 +289,7 @@ Icon *PreStitchedTextureMap::registerIcon(const wstring &name)
{
#ifndef _CONTENT_PACKAGE
app.DebugPrintf("Could not find uv data for icon %ls\n", name.c_str() );
__debugbreak();
DEBUG_BREAK();
#endif
result = missingPosition;
}

View file

@ -12,6 +12,9 @@
#include "../Minecraft.World/compression.h"
#include "../Minecraft.World/OldChunkStorage.h"
#include "../Minecraft.World/Tile.h"
#ifdef MINECRAFT_SERVER_BUILD
#include "../Minecraft.Server/FourKitBridge.h"
#endif
ServerChunkCache::ServerChunkCache(ServerLevel *level, ChunkStorage *storage, ChunkSource *source)
{
@ -125,7 +128,10 @@ LevelChunk *ServerChunkCache::create(int x, int z, bool asyncPostProcess) // 4J
{
EnterCriticalSection(&m_csLoadCreate);
chunk = load(x, z);
if (chunk == nullptr)
#ifdef MINECRAFT_SERVER_BUILD
bool isNewChunk = (chunk == nullptr);
#endif
if (chunk == nullptr)
{
if (source == nullptr)
{
@ -204,6 +210,10 @@ LevelChunk *ServerChunkCache::create(int x, int z, bool asyncPostProcess) // 4J
if( hasChunk( x - 1, z ) && hasChunk( x + 1, z ) && hasChunk ( x, z - 1 ) && hasChunk( x, z + 1 ) ) chunk->checkChests( this, x, z );
LeaveCriticalSection(&m_csLoadCreate);
#ifdef MINECRAFT_SERVER_BUILD
FourKitBridge::FireChunkLoad(level->dimension->id, x, z, isNewChunk);
#endif
}
else
{
@ -351,6 +361,38 @@ void ServerChunkCache::overwriteHellLevelChunkFromSource(int x, int z, int minVa
#endif
#ifdef MINECRAFT_SERVER_BUILD
void ServerChunkCache::regenerateChunk(int x, int z)
{
if (!source)
return;
LevelChunk *freshChunk = source->getChunk(x, z);
if (!freshChunk)
return;
LevelChunk *cachedChunk = nullptr;
if (hasChunk(x, z))
cachedChunk = getChunk(x, z);
if (cachedChunk && cachedChunk != emptyChunk)
{
for (int lx = 0; lx < 16; lx++)
for (int ly = 0; ly < 128; ly++)
for (int lz = 0; lz < 16; lz++)
cachedChunk->setTileAndData(lx, ly, lz, freshChunk->getTile(lx, ly, lz), freshChunk->getData(lx, ly, lz));
save(cachedChunk);
}
else
{
save(freshChunk);
}
freshChunk->unload(false);
delete freshChunk;
}
#endif
// 4J Added //
#ifdef _LARGE_WORLDS
void ServerChunkCache::dontDrop(int x, int z)
@ -914,15 +956,19 @@ bool ServerChunkCache::tick()
// player's tick is called to remove them from the chunk they used to be in, and add them to their current chunk. This will only be a temporary state and
// we should be able to unload the chunk on the next call to this tick.
if( !chunk->containsPlayer() )
{
{
#ifdef MINECRAFT_SERVER_BUILD
if (!FourKitBridge::FireChunkUnload(level->dimension->id, chunk->x, chunk->z))
{
#endif
save(chunk);
saveEntities(chunk);
chunk->unload(true);
//loadedChunks.remove(cp);
//loadedChunkList.remove(chunk);
auto it = std::find(m_loadedChunkList.begin(), m_loadedChunkList.end(), chunk);
if(it != m_loadedChunkList.end()) m_loadedChunkList.erase(it);
auto it = std::find(m_loadedChunkList.begin(), m_loadedChunkList.end(), chunk);
if(it != m_loadedChunkList.end()) m_loadedChunkList.erase(it);
int ix = chunk->x + XZOFFSET;
int iz = chunk->z + XZOFFSET;
@ -930,6 +976,9 @@ bool ServerChunkCache::tick()
delete m_unloadedCache[idx];
m_unloadedCache[idx] = chunk;
cache[idx] = nullptr;
#ifdef MINECRAFT_SERVER_BUILD
}
#endif
}
else
{

View file

@ -53,6 +53,9 @@ public:
#endif
virtual LevelChunk **getCache() { return cache; } // 4J added
#ifdef MINECRAFT_SERVER_BUILD
void regenerateChunk(int x, int z);
#endif
// 4J-JEV Added; Remove chunk from the toDrop queue.
#ifdef _LARGE_WORLDS

View file

@ -13,6 +13,8 @@
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
#include "../Minecraft.Server/Security/SecurityConfig.h"
#include "../Minecraft.Server/ServerLogManager.h"
#include "../Minecraft.Server/ServerLogger.h"
#include "../Minecraft.World/System.h"
#endif
ServerConnection::ServerConnection(MinecraftServer *server)
@ -129,16 +131,45 @@ void ServerConnection::tick()
vector< shared_ptr<PlayerConnection> > tempPlayers = players;
LeaveCriticalSection(&players_cs);
for (unsigned int i = 0; i < tempPlayers.size(); i++)
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
// Substep timing for chunk / tick / flush across all players.
LARGE_INTEGER scFreq, scA, scB, scC, scD;
QueryPerformanceFrequency(&scFreq);
int64_t sc_chunkUs = 0;
int64_t sc_tickUs = 0;
int64_t sc_flushUs = 0;
int64_t sc_loopT0 = System::currentTimeMillis();
#endif
// Rotate the per-tick start offset so the chunk-send cap doesn't
// starve players at the back of the vector.
static unsigned int s_chunkRotationOffset = 0;
s_chunkRotationOffset++;
size_t playerCount = tempPlayers.size();
size_t startIdx = playerCount > 0 ? (s_chunkRotationOffset % playerCount) : 0;
for (unsigned int k = 0; k < tempPlayers.size(); k++)
{
unsigned int i = (unsigned int)((startIdx + k) % playerCount);
shared_ptr<PlayerConnection> player = tempPlayers[i];
shared_ptr<ServerPlayer> serverPlayer = player->getPlayer();
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
QueryPerformanceCounter(&scA);
#endif
if( serverPlayer )
{
serverPlayer->updateFrameTick();
serverPlayer->doChunkSendingTick(false);
}
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
QueryPerformanceCounter(&scB);
sc_chunkUs += (scB.QuadPart - scA.QuadPart) * 1000000 / scFreq.QuadPart;
#endif
player->tick();
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
QueryPerformanceCounter(&scC);
sc_tickUs += (scC.QuadPart - scB.QuadPart) * 1000000 / scFreq.QuadPart;
#endif
if (player->done)
{
EnterCriticalSection(&players_cs);
@ -150,7 +181,28 @@ void ServerConnection::tick()
{
player->connection->flush();
}
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
QueryPerformanceCounter(&scD);
sc_flushUs += (scD.QuadPart - scC.QuadPart) * 1000000 / scFreq.QuadPart;
#endif
}
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
int64_t sc_loopTotal = System::currentTimeMillis() - sc_loopT0;
if (sc_loopTotal > 50)
{
ServerRuntime::LogInfof("perf",
"conn playerLoop total=%lldms players=%d chunkUs=%lld tickUs=%lld flushUs=%lld | avg chunk=%lld tick=%lld flush=%lld",
(long long)sc_loopTotal,
(int)tempPlayers.size(),
(long long)sc_chunkUs,
(long long)sc_tickUs,
(long long)sc_flushUs,
tempPlayers.size() > 0 ? (long long)(sc_chunkUs / tempPlayers.size()) : 0LL,
tempPlayers.size() > 0 ? (long long)(sc_tickUs / tempPlayers.size()) : 0LL,
tempPlayers.size() > 0 ? (long long)(sc_flushUs / tempPlayers.size()) : 0LL);
}
#endif
}
}

View file

@ -41,6 +41,7 @@
#include "PlayerChunkMap.h"
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
#include "../Minecraft.Server/FourKitBridge.h"
#include "../Minecraft.Server/ServerLogger.h"
#endif
WeighedTreasureArray ServerLevel::RANDOM_BONUS_ITEMS;
@ -197,6 +198,14 @@ ServerLevel::~ServerLevel()
void ServerLevel::tick()
{
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
// ts[N] = wall-clock at substep N. Logged when level total exceeds
// LEVEL_SLOW_THRESHOLD_MS to pinpoint the dominant substep.
const int64_t LEVEL_SLOW_THRESHOLD_MS = 50;
int64_t ts[13];
ts[0] = System::currentTimeMillis();
#endif
Level::tick();
if (getLevelData()->isHardcore() && difficulty < 3)
{
@ -218,6 +227,9 @@ void ServerLevel::tick()
}
awakenAllPlayers();
}
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
ts[1] = System::currentTimeMillis();
#endif
PIXBeginNamedEvent(0,"Mob spawner tick");
// for Minecraft 1.8, spawn friendlies really rarely - 4J - altered from once every 400 ticks to 40 ticks as we depend on this a more than the original since we don't have chunk post-process spawning
@ -233,6 +245,9 @@ void ServerLevel::tick()
mobSpawner->tick(this, finalSpawnEnemies, finalSpawnFriendlies, finalSpawnPersistent);
}
PIXEndNamedEvent();
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
ts[2] = System::currentTimeMillis();
#endif
PIXBeginNamedEvent(0,"Chunk source tick");
chunkSource->tick();
PIXEndNamedEvent();
@ -248,6 +263,9 @@ void ServerLevel::tick()
}
}
}
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
ts[3] = System::currentTimeMillis();
#endif
//4J - temporarily disabling saves as they are causing gameplay to generally stutter quite a lot
@ -263,6 +281,9 @@ void ServerLevel::tick()
save(false, nullptr);
PIXEndNamedEvent();
}
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
ts[4] = System::currentTimeMillis();
#endif
// 4J : WESTY : Changed so that time update goes through stats tracking update code.
//levelData->setTime(time);
@ -278,19 +299,31 @@ void ServerLevel::tick()
setDayTime(levelData->getDayTime() + 1);
}
}
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
ts[5] = System::currentTimeMillis();
#endif
PIXBeginNamedEvent(0,"Tick pending ticks");
// if (tickCount % 5 == 0) {
tickPendingTicks(false);
PIXEndNamedEvent();
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
ts[6] = System::currentTimeMillis();
#endif
PIXBeginNamedEvent(0,"Tick tiles");
MemSect(18);
tickTiles();
MemSect(0);
PIXEndNamedEvent();
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
ts[7] = System::currentTimeMillis();
#endif
chunkMap->tick();
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
ts[8] = System::currentTimeMillis();
#endif
PIXBeginNamedEvent(0,"Tick villages");
//MemSect(18);
@ -298,18 +331,50 @@ void ServerLevel::tick()
villageSiege->tick();
//MemSect(0);
PIXEndNamedEvent();
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
ts[9] = System::currentTimeMillis();
#endif
PIXBeginNamedEvent(0,"Tick portal forcer");
portalForcer->tick(getGameTime());
PIXEndNamedEvent();
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
ts[10] = System::currentTimeMillis();
#endif
// repeat after tile ticks
PIXBeginNamedEvent(0,"runTileEvents");
runTileEvents();
PIXEndNamedEvent();
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
ts[11] = System::currentTimeMillis();
#endif
// 4J Added
runQueuedSendTileUpdates();
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
ts[12] = System::currentTimeMillis();
int64_t levelTotal = ts[12] - ts[0];
if (levelTotal > LEVEL_SLOW_THRESHOLD_MS)
{
ServerRuntime::LogInfof("perf",
"L%d substep total=%lldms base=%lld spawn=%lld chunkSrc=%lld save=%lld time=%lld pending=%lld tiles=%lld chunkMap=%lld villages=%lld portal=%lld evt=%lld sendTU=%lld",
dimension->id,
(long long)levelTotal,
(long long)(ts[1] - ts[0]),
(long long)(ts[2] - ts[1]),
(long long)(ts[3] - ts[2]),
(long long)(ts[4] - ts[3]),
(long long)(ts[5] - ts[4]),
(long long)(ts[6] - ts[5]),
(long long)(ts[7] - ts[6]),
(long long)(ts[8] - ts[7]),
(long long)(ts[9] - ts[8]),
(long long)(ts[10] - ts[9]),
(long long)(ts[11] - ts[10]),
(long long)(ts[12] - ts[11]));
}
#endif
}
Biome::MobSpawnerData *ServerLevel::getRandomMobSpawnAt(MobCategory *mobCategory, int x, int y, int z)

View file

@ -8,6 +8,9 @@
#include "Settings.h"
#include "PlayerList.h"
#include "MultiPlayerLevel.h"
#include "Minecraft.h"
#include "Common/Audio/SoundEngine.h"
#include "../Minecraft.World/SoundTypes.h"
#include "../Minecraft.World/net.minecraft.network.packet.h"
#include "../Minecraft.World/net.minecraft.world.damagesource.h"
@ -474,28 +477,82 @@ void ServerPlayer::doTickA()
}
// 4J - split off the chunk sending bit of the tick here from ::doTick so we can do this exactly once per player per server tick
//
// Find-nearest uses spiral iteration from the player's chunk position with
// O(1) hash lookups against chunksToSend. A bounded fallback walk covers
// the rare case of a stale entry outside the spiral radius.
void ServerPlayer::doChunkSendingTick(bool dontDelayChunks)
{
// printf("[%d] %s: sendChunks: %d, empty: %d\n",tickCount, connection->getNetworkPlayer()->GetUID().getOnlineID(),sendChunks,chunksToSend.empty());
if (!chunksToSend.empty())
{
ChunkPos nearest = chunksToSend.front();
ChunkPos nearest(0, 0);
bool nearestValid = false;
// 4J - reinstated and optimised some code that was commented out in the original, to make sure that we always
// send the nearest chunk to the player. The original uses the bukkit sorting thing to try and avoid doing this, but
// the player can quickly wander away from the centre of the spiral of chunks that that method creates, long before transmission
// of them is complete.
double dist = DBL_MAX;
for(ChunkPos chunk : chunksToSend)
const int px = (int)floor(x) >> 4;
const int pz = (int)floor(z) >> 4;
// Bound on spiral radius. Configured view distance is much smaller.
const int kMaxSpiralRadius = 32;
// Inline distance: ChunkPos::distanceToSqr is non-const so it can't
// be called on the const refs we get when iterating an unordered_set.
auto chunkDistSq = [&](int cx, int cz) -> double {
double xPos = cx * 16.0 + 8.0;
double zPos = cz * 16.0 + 8.0;
double xd = xPos - this->x;
double zd = zPos - this->z;
return xd * xd + zd * zd;
};
// Ring r=0: the player's own chunk.
{
if( level->isChunkFinalised(chunk.x, chunk.z) )
ChunkPos cp(px, pz);
auto it = chunksToSend.find(cp);
if (it != chunksToSend.end() && level->isChunkFinalised(cp.x, cp.z))
{
double newDist = chunk.distanceToSqr(x, z);
if ( (!nearestValid) || (newDist < dist) )
nearest = cp;
dist = chunkDistSq(cp.x, cp.z);
nearestValid = true;
}
}
// Rings r>=1: Chebyshev perimeter. First ring with a match wins;
// within it pick the closest by true Euclidean distance.
for (int r = 1; r <= kMaxSpiralRadius && !nearestValid; r++)
{
for (int dx = -r; dx <= r; dx++)
{
for (int dz = -r; dz <= r; dz++)
{
nearest = chunk;
dist = chunk.distanceToSqr(x, z);
int adx = dx < 0 ? -dx : dx;
int adz = dz < 0 ? -dz : dz;
if ((adx > adz ? adx : adz) != r) continue; // perimeter only
ChunkPos cp(px + dx, pz + dz);
if (chunksToSend.find(cp) == chunksToSend.end()) continue;
if (!level->isChunkFinalised(cp.x, cp.z)) continue;
double d = chunkDistSq(cp.x, cp.z);
if (!nearestValid || d < dist)
{
nearest = cp;
dist = d;
nearestValid = true;
}
}
}
}
// Fallback for chunks outside the spiral radius (rare).
if (!nearestValid)
{
for (const ChunkPos& cp : chunksToSend)
{
if (!level->isChunkFinalised(cp.x, cp.z)) continue;
double d = chunkDistSq(cp.x, cp.z);
if (!nearestValid || d < dist)
{
nearest = cp;
dist = d;
nearestValid = true;
}
}
@ -564,7 +621,7 @@ void ServerPlayer::doChunkSendingTick(bool dontDelayChunks)
{
ServerLevel *level = server->getLevel(dimension);
int flagIndex = getFlagIndexForChunk(nearest,this->level->dimension->id);
chunksToSend.remove(nearest);
chunksToSend.erase(nearest);
bool chunkDataSent = false;
@ -1036,6 +1093,13 @@ void ServerPlayer::changeDimension(int i)
// 4J: Removed on the advice of the mighty King of Achievments (JV)
// awardStat(GenericStats::portal(), GenericStats::param_portal());
}
// play the travel whoosh right before the actual dimension swap
Minecraft *mc = Minecraft::GetInstance();
if (mc != nullptr && mc->soundEngine != nullptr)
{
mc->soundEngine->playUI(eSoundType_PORTAL_TRAVEL, 1, 1.0f);
}
server->getPlayers()->toggleDimension( dynamic_pointer_cast<ServerPlayer>(shared_from_this()), i);
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
if (portalDestModified)

View file

@ -25,7 +25,9 @@ public:
MinecraftServer *server;
ServerPlayerGameMode *gameMode;
double lastMoveX, lastMoveZ;
list<ChunkPos> chunksToSend;
// Hash set for O(1) membership/erase. Find-nearest is done via
// spiral iteration in ServerPlayer::doChunkSendingTick, not a sweep.
unordered_set<ChunkPos, ChunkPosKeyHash, ChunkPosKeyEq> chunksToSend;
vector<int> entitiesToRemove;
unordered_set<ChunkPos, ChunkPosKeyHash, ChunkPosKeyEq> seenChunks;
int spewTimer;

View file

@ -87,7 +87,7 @@ void Stitcher::stitch()
{
app.DebugPrintf("Stitcher exception!\n");
#ifndef _CONTENT_PACKAGE
__debugbreak();
DEBUG_BREAK();
#endif
//throw new StitcherException(textureHolder);
}

View file

@ -214,7 +214,7 @@ Icon *TextureMap::registerIcon(const wstring &name)
{
app.DebugPrintf("Don't register nullptr\n");
#ifndef _CONTENT_PACKAGE
__debugbreak();
DEBUG_BREAK();
#endif
//new RuntimeException("Don't register null!").printStackTrace();
}

View file

@ -339,7 +339,6 @@ bool TexturePackRepository::selectTexturePackById(DWORD id)
app.DebugPrintf("Failed to select texture pack %d as it is not in the list\n", id);
#ifndef _CONTENT_PACKAGE
// TODO - 4J Stu: We should report this to the player in some way
//__debugbreak();
#endif
// Fail safely
if( selectSkin( DEFAULT_TEXTURE_PACK ) )

View file

@ -1619,7 +1619,7 @@ RADDEFSTART
#define RR_BREAK() __builtin_trap()
#define RR_CACHE_LINE_SIZE 32
#elif defined(__RADXENON__)
#define RR_BREAK() __debugbreak()
#define RR_BREAK() DEBUG_BREAK()
#define RR_CACHE_LINE_SIZE 128
#elif defined(__RADANDROID__)
#define RR_BREAK() __builtin_trap()

View file

@ -24,7 +24,7 @@ void ConsoleUIController::init(ID3D11Device *dev, ID3D11DeviceContext *ctx, ID3D
{
app.DebugPrintf("Failed to initialise GDraw!\n");
#ifndef _CONTENT_PACKAGE
__debugbreak();
DEBUG_BREAK();
#endif
app.FatalLoadError();
}

View file

@ -487,7 +487,7 @@ void SoundEngine::play(int iSound, float x, float y, float z, float volume, floa
{
#ifndef _CONTENT_PACKAGE
#ifdef _DEBUG
__debugbreak();
DEBUG_BREAK();
#endif
//wprintf(L"WARNING: Sound cue not found - %ls\n", name.c_str() );
app.DebugPrintf("Not found: %s\n",xboxName);

View file

@ -183,7 +183,7 @@ bool XboxLeaderboardManager::WriteStats(unsigned int viewCount, ViewIn views)
// some debug code to catch the leaderboard write with 7 views
#ifndef _CONTENT_PACKAGE
if(viewCount>5) __debugbreak();
if(viewCount>5) DEBUG_BREAK();
#endif
// 4J Stu - If we are online we already have a session, so use that

View file

@ -211,8 +211,6 @@ VOID CPlatformNetworkManagerXbox::NotifyPlayerLeaving(
__in IQNetPlayer * pQNetPlayer
)
{
//__debugbreak();
app.DebugPrintf( "Player 0x%p \"%ls\" leaving.\n",
pQNetPlayer,
pQNetPlayer->GetGamertag() );

View file

@ -315,8 +315,6 @@ int __cdecl main()
HRESULT hr;
static bool bTrialTimerDisplayed=true;
//__debugbreak();
#ifdef MEMORY_TRACKING
ResetMem();
MEMORYSTATUS memStat;

View file

@ -148,6 +148,7 @@ typedef XUID GameSessionUID;
#endif
#include "../Minecraft.World/Definitions.h"
#include "../Minecraft.World/Debug.h"
#include "../Minecraft.World/Class.h"
#include "../Minecraft.World/ArrayWithLength.h"
#include "../Minecraft.World/SharedConstants.h"

View file

@ -0,0 +1,109 @@
namespace Minecraft.Server.FourKit.Block;
public enum Biome
{
OCEAN = 0,
PLAINS = 1,
DESERT = 2,
EXTREME_HILLS = 3,
FOREST = 4,
TAIGA = 5,
SWAMPLAND = 6,
RIVER = 7,
HELL = 8,
SKY = 9,
FROZEN_OCEAN = 10,
FROZEN_RIVER = 11,
ICE_PLAINS = 12,
ICE_MOUNTAINS = 13,
MUSHROOM_ISLAND = 14,
MUSHROOM_SHORE = 15,
BEACH = 16,
DESERT_HILLS = 17,
FOREST_HILLS = 18,
TAIGA_HILLS = 19,
SMALL_MOUNTAINS = 20,
JUNGLE = 21,
JUNGLE_HILLS = 22,
}
// more for internal
// eliminates unnecessary overhead
internal static class BiomeHelper
{
private static readonly double[] _temperatures = new double[23];
private static readonly double[] _rainfalls = new double[23];
static BiomeHelper()
{
_temperatures[(int)Biome.OCEAN] = 0.5;
_temperatures[(int)Biome.PLAINS] = 0.8;
_temperatures[(int)Biome.DESERT] = 2.0;
_temperatures[(int)Biome.EXTREME_HILLS] = 0.2;
_temperatures[(int)Biome.FOREST] = 0.7;
_temperatures[(int)Biome.TAIGA] = 0.05;
_temperatures[(int)Biome.SWAMPLAND] = 0.8;
_temperatures[(int)Biome.RIVER] = 0.5;
_temperatures[(int)Biome.HELL] = 2.0;
_temperatures[(int)Biome.SKY] = 0.5;
_temperatures[(int)Biome.FROZEN_OCEAN] = 0.0;
_temperatures[(int)Biome.FROZEN_RIVER] = 0.0;
_temperatures[(int)Biome.ICE_PLAINS] = 0.0;
_temperatures[(int)Biome.ICE_MOUNTAINS] = 0.0;
_temperatures[(int)Biome.MUSHROOM_ISLAND] = 0.9;
_temperatures[(int)Biome.MUSHROOM_SHORE] = 0.9;
_temperatures[(int)Biome.BEACH] = 0.8;
_temperatures[(int)Biome.DESERT_HILLS] = 2.0;
_temperatures[(int)Biome.FOREST_HILLS] = 0.7;
_temperatures[(int)Biome.TAIGA_HILLS] = 0.05;
_temperatures[(int)Biome.SMALL_MOUNTAINS] = 0.2;
_temperatures[(int)Biome.JUNGLE] = 1.2;
_temperatures[(int)Biome.JUNGLE_HILLS] = 1.2;
_rainfalls[(int)Biome.OCEAN] = 0.5;
_rainfalls[(int)Biome.PLAINS] = 0.4;
_rainfalls[(int)Biome.DESERT] = 0.0;
_rainfalls[(int)Biome.EXTREME_HILLS] = 0.3;
_rainfalls[(int)Biome.FOREST] = 0.8;
_rainfalls[(int)Biome.TAIGA] = 0.8;
_rainfalls[(int)Biome.SWAMPLAND] = 0.9;
_rainfalls[(int)Biome.RIVER] = 0.5;
_rainfalls[(int)Biome.HELL] = 0.0;
_rainfalls[(int)Biome.SKY] = 0.5;
_rainfalls[(int)Biome.FROZEN_OCEAN] = 0.5;
_rainfalls[(int)Biome.FROZEN_RIVER] = 0.5;
_rainfalls[(int)Biome.ICE_PLAINS] = 0.5;
_rainfalls[(int)Biome.ICE_MOUNTAINS] = 0.5;
_rainfalls[(int)Biome.MUSHROOM_ISLAND] = 1.0;
_rainfalls[(int)Biome.MUSHROOM_SHORE] = 1.0;
_rainfalls[(int)Biome.BEACH] = 0.4;
_rainfalls[(int)Biome.DESERT_HILLS] = 0.0;
_rainfalls[(int)Biome.FOREST_HILLS] = 0.8;
_rainfalls[(int)Biome.TAIGA_HILLS] = 0.8;
_rainfalls[(int)Biome.SMALL_MOUNTAINS] = 0.3;
_rainfalls[(int)Biome.JUNGLE] = 0.9;
_rainfalls[(int)Biome.JUNGLE_HILLS] = 0.9;
}
public static double getTemperature(this Biome biome)
{
int id = (int)biome;
if (id >= 0 && id < _temperatures.Length) return _temperatures[id];
return 0.5;
}
public static double getRainfall(this Biome biome)
{
int id = (int)biome;
if (id >= 0 && id < _rainfalls.Length) return _rainfalls[id];
return 0.5;
}
public static Biome fromId(int id)
{
if (Enum.IsDefined(typeof(Biome), id)) return (Biome)id;
return Biome.PLAINS;
}
}

View file

@ -89,7 +89,19 @@ public class Block
/// <returns>Whether the change was successful.</returns>
public bool setTypeId(int type)
{
NativeBridge.SetTile?.Invoke(_world.getDimensionId(), _x, _y, _z, type, 0);
return setTypeId(type, true);
}
/// <summary>
/// Sets the type ID of this block.
/// </summary>
/// <param name="type">Type ID to change this block to.</param>
/// <param name="applyPhysics">False to cancel physics on the changed block.</param>
/// <returns>Whether the block was changed.</returns>
public bool setTypeId(int type, bool applyPhysics)
{
int flags = applyPhysics ? 3 : 2;
NativeBridge.SetTile?.Invoke(_world.getDimensionId(), _x, _y, _z, type, 0, flags);
return true;
}
@ -108,7 +120,43 @@ public class Block
/// <param name="data">New block specific metadata.</param>
public void setData(byte data)
{
NativeBridge.SetTileData?.Invoke(_world.getDimensionId(), _x, _y, _z, data);
setData(data, true);
}
/// <summary>
/// Sets the metadata for this block.
/// </summary>
/// <param name="data">New block specific metadata.</param>
/// <param name="applyPhysics">False to cancel physics from the changed block.</param>
public void setData(byte data, bool applyPhysics)
{
int flags = applyPhysics ? 3 : 2;
NativeBridge.SetTileData?.Invoke(_world.getDimensionId(), _x, _y, _z, data, flags);
}
/// <summary>
/// Sets the type ID and data of this block.
/// </summary>
/// <param name="type">Type ID to change this block to.</param>
/// <param name="data">The data value to change this block to.</param>
/// <returns>Whether the block was changed.</returns>
public bool setTypeIdAndData(int type, byte data)
{
return setTypeIdAndData(type, data, true);
}
/// <summary>
/// Sets the type ID and data of this block.
/// </summary>
/// <param name="type">Type ID to change this block to.</param>
/// <param name="data">The data value to change this block to.</param>
/// <param name="applyPhysics">False to cancel physics on the changed block.</param>
/// <returns>Whether the block was changed.</returns>
public bool setTypeIdAndData(int type, byte data, bool applyPhysics)
{
int flags = applyPhysics ? 3 : 2;
NativeBridge.SetTile?.Invoke(_world.getDimensionId(), _x, _y, _z, type, data, flags);
return true;
}
/// <summary>
@ -134,6 +182,15 @@ public class Block
return getWorld().getBlockAt(getX() + modX, getY() + modY, getZ() + modZ);
}
/// <summary>
/// Gets the chunk which contains this block.
/// </summary>
/// <returns>Containing Chunk.</returns>
public Chunk.Chunk getChunk()
{
return getWorld().getChunkAt(getX() >> 4, getZ() >> 4);
}
/// <summary>
/// Gets the block at the given face
/// <para>This method is equal to getRelative(face, 1)</para>
@ -162,5 +219,93 @@ public class Block
{
return getRelative(face.getModX() * distance, face.getModY() * distance, face.getModZ() * distance);
}
/// <summary>
/// Returns the biome that this block resides in.
/// </summary>
/// <returns>Biome type containing this block.</returns>
public Biome getBiome()
{
if (NativeBridge.GetBiomeId != null)
return BiomeHelper.fromId(NativeBridge.GetBiomeId(_world.getDimensionId(), _x, _z));
return Biome.PLAINS;
}
/// <summary>
/// Sets the biome that this block resides in.
/// </summary>
/// <param name="bio">New Biome type for this block.</param>
public void setBiome(Biome bio)
{
NativeBridge.SetBiomeId?.Invoke(_world.getDimensionId(), _x, _z, (int)bio);
}
/// <summary>
/// Gets the humidity of the biome of this block.
/// </summary>
/// <returns>Humidity of this block.</returns>
public double getHumidity()
{
return getBiome().getRainfall();
}
/// <summary>
/// Gets the temperature of the biome of this block.
/// </summary>
/// <returns>Temperature of this block.</returns>
public double getTemperature()
{
return getBiome().getTemperature();
}
/// <summary>
/// Checks if this block is liquid.
/// <para>A block is considered liquid when <see cref="getType()"/> returns
/// <see cref="Material.WATER"/>, <see cref="Material.STATIONARY_WATER"/>,
/// <see cref="Material.LAVA"/> or <see cref="Material.STATIONARY_LAVA"/>.</para>
/// </summary>
/// <returns>true if this block is liquid.</returns>
public bool isLiquid()
{
Material type = getType();
return type == Material.WATER || type == Material.STATIONARY_WATER ||
type == Material.LAVA || type == Material.STATIONARY_LAVA;
}
/// <summary>
/// Gets the light level between 0-15.
/// </summary>
/// <returns>Light level.</returns>
public byte getLightLevel()
{
int sky = getLightFromSky();
int block = getLightFromBlocks();
return (byte)(sky > block ? sky : block);
}
/// <summary>
/// Get the amount of light at this block from the sky.
/// Any light given from other sources (such as blocks like torches) will be ignored.
/// </summary>
/// <returns>Sky light level.</returns>
public byte getLightFromSky()
{
if (NativeBridge.GetSkyLight != null)
return (byte)NativeBridge.GetSkyLight(_world.getDimensionId(), _x, _y, _z);
return 0;
}
/// <summary>
/// Get the amount of light at this block from nearby blocks.
/// Any light given from other sources (such as the sun) will be ignored.
/// </summary>
/// <returns>Block light level.</returns>
public byte getLightFromBlocks()
{
if (NativeBridge.GetBlockLight != null)
return (byte)NativeBridge.GetBlockLight(_world.getDimensionId(), _x, _y, _z);
return 0;
}
}

View file

@ -92,6 +92,15 @@ public class BlockState
/// <returns>Z-coordinate.</returns>
public int getZ() => _z;
/// <summary>
/// Gets the chunk which contains this block.
/// </summary>
/// <returns>Containing Chunk.</returns>
public Chunk.Chunk getChunk()
{
return _world.getChunkAt(_x >> 4, _z >> 4);
}
/// <summary>
/// Gets the location of this block.
/// </summary>
@ -191,7 +200,16 @@ public class BlockState
if (!force && currentType != _typeId)
return false;
NativeBridge.SetTile(_world.getDimensionId(), _x, _y, _z, _typeId, _data);
NativeBridge.SetTile(_world.getDimensionId(), _x, _y, _z, _typeId, _data, applyPhysics ? 3 : 2);
return true;
}
/// <summary>
/// Gets the light level between 0-15.
/// </summary>
/// <returns>Light level.</returns>
public byte getLightLevel()
{
return getBlock().getLightLevel();
}
}

View file

@ -0,0 +1,173 @@
using System.Text;
using System.Text.RegularExpressions;
namespace Minecraft.Server.FourKit;
/// <summary>
/// All supported color values for chat.
/// </summary>
public class ChatColor
{
/// <summary>
/// The special character which prefixes all chat colour codes.
/// </summary>
public static readonly char COLOR_CHAR = '\u00A7'; // F
private static readonly Regex STRIP_COLOR_PATTERN =
new Regex("(?i)" + COLOR_CHAR + "[0-9A-FK-OR]", RegexOptions.Compiled);
private static readonly Dictionary<char, ChatColor> BY_CHAR = new();
/// <summary>Represents black.</summary>
public static readonly ChatColor BLACK = new('0', false);
/// <summary>Represents dark blue.</summary>
public static readonly ChatColor DARK_BLUE = new('1', false);
/// <summary>Represents dark green.</summary>
public static readonly ChatColor DARK_GREEN = new('2', false);
/// <summary>Represents dark blue (aqua).</summary>
public static readonly ChatColor DARK_AQUA = new('3', false);
/// <summary>Represents dark red.</summary>
public static readonly ChatColor DARK_RED = new('4', false);
/// <summary>Represents dark purple.</summary>
public static readonly ChatColor DARK_PURPLE = new('5', false);
/// <summary>Represents gold.</summary>
public static readonly ChatColor GOLD = new('6', false);
/// <summary>Represents gray.</summary>
public static readonly ChatColor GRAY = new('7', false);
/// <summary>Represents dark gray.</summary>
public static readonly ChatColor DARK_GRAY = new('8', false);
/// <summary>Represents blue.</summary>
public static readonly ChatColor BLUE = new('9', false);
/// <summary>Represents green.</summary>
public static readonly ChatColor GREEN = new('a', false);
/// <summary>Represents aqua.</summary>
public static readonly ChatColor AQUA = new('b', false);
/// <summary>Represents red.</summary>
public static readonly ChatColor RED = new('c', false);
/// <summary>Represents light purple.</summary>
public static readonly ChatColor LIGHT_PURPLE = new('d', false);
/// <summary>Represents yellow.</summary>
public static readonly ChatColor YELLOW = new('e', false);
/// <summary>Represents white.</summary>
public static readonly ChatColor WHITE = new('f', false);
/// <summary>Resets all previous chat colors or formats.</summary>
public static readonly ChatColor RESET = new('r', false);
private readonly char _code;
private readonly bool _isFormat;
private readonly string _toString;
private ChatColor(char code, bool isFormat)
{
_code = code;
_isFormat = isFormat;
_toString = new string(new[] { COLOR_CHAR, code });
BY_CHAR[code] = this;
}
/// <summary>
/// Gets the char value associated with this color.
/// </summary>
/// <returns>A char value of this color code.</returns>
public char getChar() => _code;
/// <summary>
/// Checks if this code is a format code as opposed to a color code.
/// </summary>
/// <returns><c>true</c> if this is a format code.</returns>
public bool isFormat() => _isFormat;
/// <summary>
/// Checks if this code is a color code as opposed to a format code.
/// </summary>
/// <returns><c>true</c> if this is a color code.</returns>
public bool isColor() => !_isFormat && this != RESET;
/// <summary>
/// Gets the color represented by the specified color code.
/// </summary>
/// <param name="code">Code to check.</param>
/// <returns>Associative ChatColor with the given code, or null if it doesn't exist.</returns>
public static ChatColor? getByChar(char code)
{
return BY_CHAR.TryGetValue(char.ToLower(code), out var color) ? color : null;
}
/// <summary>
/// Gets the color represented by the specified color code.
/// </summary>
/// <param name="code">Code to check.</param>
/// <returns>Associative ChatColor with the given code, or null if it doesn't exist.</returns>
public static ChatColor? getByChar(string code)
{
if (string.IsNullOrEmpty(code)) return null;
return getByChar(code[0]);
}
/// <summary>
/// Strips the given message of all color codes.
/// </summary>
/// <param name="input">String to strip of color.</param>
/// <returns>A copy of the input string, without any coloring.</returns>
public static string? stripColor(string? input)
{
if (input == null) return null;
return STRIP_COLOR_PATTERN.Replace(input, "");
}
/// <summary>
/// Translates a string using an alternate color code character into a string
/// that uses the internal <see cref="COLOR_CHAR"/> color code character.
/// The alternate color code character will only be replaced if it is immediately
/// followed by 0-9, A-F, a-f, K-O, k-o, R or r.
/// </summary>
/// <param name="altColorChar">The alternate color code character to replace. Ex: &amp;</param>
/// <param name="textToTranslate">Text containing the alternate color code character.</param>
/// <returns>Text containing the <see cref="COLOR_CHAR"/> color code character.</returns>
public static string translateAlternateColorCodes(char altColorChar, string textToTranslate)
{
char[] b = textToTranslate.ToCharArray();
for (int i = 0; i < b.Length - 1; i++)
{
if (b[i] == altColorChar && "0123456789AaBbCcDdEeFfKkLlMmNnOoRr".IndexOf(b[i + 1]) > -1)
{
b[i] = COLOR_CHAR;
}
}
return new string(b);
}
/// <summary>
/// Gets the ChatColors used at the end of the given input string.
/// </summary>
/// <param name="input">Input string to retrieve the colors from.</param>
/// <returns>Any remaining ChatColors to pass onto the next line.</returns>
public static string getLastColors(string input)
{
var result = new StringBuilder();
int length = input.Length;
for (int index = length - 1; index > -1; index--)
{
char section = input[index];
if (section == COLOR_CHAR && index < length - 1)
{
char c = input[index + 1];
ChatColor? color = getByChar(c);
if (color != null)
{
result.Insert(0, color.ToString());
if (color.isColor() || color == RESET)
break;
}
}
}
return result.ToString();
}
public static string operator +(ChatColor color, string text) => color.ToString() + text;
/// <inheritdoc/>
public override string ToString() => _toString;
}

View file

@ -0,0 +1,311 @@
using Minecraft.Server.FourKit.Block;
using Minecraft.Server.FourKit.Entity;
using System.Runtime.InteropServices;
namespace Minecraft.Server.FourKit.Chunk;
/// <summary>
/// Represents a chunk of blocks.
/// </summary>
public class Chunk
{
private readonly World _world;
private readonly int _chunkX;
private readonly int _chunkZ;
internal Chunk(World world, int chunkX, int chunkZ)
{
_world = world;
_chunkX = chunkX;
_chunkZ = chunkZ;
}
/// <summary>
/// Gets the X-coordinate of this chunk.
/// </summary>
/// <returns>X-coordinate.</returns>
public int getX() => _chunkX;
/// <summary>
/// Gets the Z-coordinate of this chunk.
/// </summary>
/// <returns>Z-coordinate.</returns>
public int getZ() => _chunkZ;
/// <summary>
/// Gets the world containing this chunk.
/// </summary>
/// <returns>Parent World.</returns>
public World getWorld() => _world;
/// <summary>
/// Gets a block from this chunk.
/// </summary>
/// <param name="x">0-15</param>
/// <param name="y">0-127</param>
/// <param name="z">0-15</param>
/// <returns>The Block.</returns>
public Block.Block getBlock(int x, int y, int z)
{
return _world.getBlockAt((_chunkX << 4) + x, y, (_chunkZ << 4) + z);
}
/// <summary>
/// Capture thread-safe read-only snapshot of chunk data.
/// </summary>
/// <returns>ChunkSnapshot.</returns>
public ChunkSnapshot getChunkSnapshot()
{
return getChunkSnapshot(false, false);
}
/// <summary>
/// Capture thread-safe read-only snapshot of chunk data.
/// </summary>
/// <param name="includeBiome">If true, snapshot includes per-coordinate biome type.</param>
/// <param name="includeBiomeTempRain">If true, snapshot includes per-coordinate raw biome temperature and rainfall.</param>
/// <returns>ChunkSnapshot.</returns>
public ChunkSnapshot getChunkSnapshot(bool includeBiome, bool includeBiomeTempRain)
{
// this has a lot of overhead
// (SYLV)todo: clean this up
int dimId = _world.getDimensionId();
int[] blockIds = new int[16 * 128 * 16];
int[] blockData = new int[16 * 128 * 16];
int[] maxBlockY = new int[16 * 16];
int[] skyLight = new int[16 * 128 * 16];
int[] blockLight = new int[16 * 128 * 16];
int[]? biomeIds = includeBiome ? new int[16 * 16] : null;
double[]? biomeTemp = includeBiomeTempRain ? new double[16 * 16] : null;
double[]? biomeRainfall = includeBiomeTempRain ? new double[16 * 16] : null;
if (NativeBridge.GetChunkSnapshot != null)
{
var hIds = GCHandle.Alloc(blockIds, GCHandleType.Pinned);
var hData = GCHandle.Alloc(blockData, GCHandleType.Pinned);
var hMaxY = GCHandle.Alloc(maxBlockY, GCHandleType.Pinned);
try
{
NativeBridge.GetChunkSnapshot(dimId, _chunkX, _chunkZ,
hIds.AddrOfPinnedObject(),
hData.AddrOfPinnedObject(),
hMaxY.AddrOfPinnedObject());
}
finally
{
hIds.Free();
hData.Free();
hMaxY.Free();
}
}
else
{
for (int lx = 0; lx < 16; lx++)
{
for (int lz = 0; lz < 16; lz++)
{
int worldX = (_chunkX << 4) + lx;
int worldZ = (_chunkZ << 4) + lz;
int highest = 0;
for (int ly = 0; ly < 128; ly++)
{
int idx = (lx * 128 * 16) + (ly * 16) + lz;
if (NativeBridge.GetTileId != null)
blockIds[idx] = NativeBridge.GetTileId(dimId, worldX, ly, worldZ);
if (NativeBridge.GetTileData != null)
blockData[idx] = NativeBridge.GetTileData(dimId, worldX, ly, worldZ);
if (blockIds[idx] != 0)
highest = ly;
}
maxBlockY[lx * 16 + lz] = highest;
}
}
}
for (int lx = 0; lx < 16; lx++)
{
for (int lz = 0; lz < 16; lz++)
{
int worldX = (_chunkX << 4) + lx;
int worldZ = (_chunkZ << 4) + lz;
for (int ly = 0; ly < 128; ly++)
{
int idx = (lx * 128 * 16) + (ly * 16) + lz;
if (NativeBridge.GetSkyLight != null)
skyLight[idx] = NativeBridge.GetSkyLight(dimId, worldX, ly, worldZ);
if (NativeBridge.GetBlockLight != null)
blockLight[idx] = NativeBridge.GetBlockLight(dimId, worldX, ly, worldZ);
}
if (includeBiome && NativeBridge.GetBiomeId != null)
{
int colIdx = lx * 16 + lz;
int biomeId = NativeBridge.GetBiomeId(dimId, worldX, worldZ);
biomeIds![colIdx] = biomeId;
if (includeBiomeTempRain)
{
var biome = Block.BiomeHelper.fromId(biomeId);
biomeTemp![colIdx] = biome.getTemperature();
biomeRainfall![colIdx] = biome.getRainfall();
}
}
}
}
long captureTime = 0;
if (NativeBridge.GetWorldInfo != null)
{
double[] info = new double[7];
var hInfo = GCHandle.Alloc(info, GCHandleType.Pinned);
try
{
NativeBridge.GetWorldInfo(dimId, hInfo.AddrOfPinnedObject());
}
finally
{
hInfo.Free();
}
captureTime = (long)info[4];
}
return new ChunkSnapshot(_chunkX, _chunkZ, _world.getName(), captureTime,
blockIds, blockData, maxBlockY,
skyLight, blockLight, biomeIds, biomeTemp, biomeRainfall);
}
/// <summary>
/// Capture thread-safe read-only snapshot of chunk data.
/// </summary>
/// <param name="includeMaxblocky">(NONFUNCTIONAL) Only here for parity.</param>
/// <param name="includeBiome">If true, snapshot includes per-coordinate biome type.</param>
/// <param name="includeBiomeTempRain">If true, snapshot includes per-coordinate raw biome temperature and rainfall.</param>
/// <returns>ChunkSnapshot.</returns>
public ChunkSnapshot getChunkSnapshot(bool includeMaxblocky, bool includeBiome, bool includeBiomeTempRain)
{
return getChunkSnapshot(includeBiome, includeBiomeTempRain);
}
/// <summary>
/// Get a list of all entities in the chunk.
/// </summary>
/// <returns>The entities.</returns>
public Entity.Entity[] getEntities()
{
if (NativeBridge.GetChunkEntities == null) return Array.Empty<Entity.Entity>();
int dimId = _world.getDimensionId();
int count = NativeBridge.GetChunkEntities(dimId, _chunkX, _chunkZ, out IntPtr buf);
if (count <= 0 || buf == IntPtr.Zero) return Array.Empty<Entity.Entity>();
var result = new Entity.Entity[count];
try
{
int[] data = new int[count * 3];
Marshal.Copy(buf, data, 0, count * 3);
for (int i = 0; i < count; i++)
{
int entityId = data[i * 3];
int mappedType = data[i * 3 + 1];
int isLiving = data[i * 3 + 2];
var entityType = Enum.IsDefined(typeof(Entity.EntityType), mappedType)
? (Entity.EntityType)mappedType
: Entity.EntityType.UNKNOWN;
if (entityType == Entity.EntityType.PLAYER)
{
var player = FourKit.GetPlayerByEntityId(entityId);
if (player != null)
{
result[i] = player;
continue;
}
}
if (isLiving == 1)
{
result[i] = new Entity.LivingEntity(entityId, entityType, dimId, 0, 0, 0);
}
else
{
var entity = new Entity.Entity();
entity.SetEntityIdInternal(entityId);
entity.SetEntityTypeInternal(entityType);
entity.SetDimensionInternal(dimId);
result[i] = entity;
}
}
}
finally
{
Marshal.FreeCoTaskMem(buf);
}
return result;
}
/// <summary>
/// Checks if the chunk is loaded.
/// </summary>
/// <returns>True if it is loaded.</returns>
public bool isLoaded()
{
if (NativeBridge.IsChunkLoaded != null)
return NativeBridge.IsChunkLoaded(_world.getDimensionId(), _chunkX, _chunkZ) != 0;
return false;
}
/// <summary>
/// Loads the chunk.
/// </summary>
/// <param name="generate">Whether or not to generate a chunk if it doesn't already exist.</param>
/// <returns>true if the chunk has loaded successfully, otherwise false.</returns>
public bool load(bool generate)
{
if (NativeBridge.LoadChunk != null)
return NativeBridge.LoadChunk(_world.getDimensionId(), _chunkX, _chunkZ, generate ? 1 : 0) != 0;
return false;
}
/// <summary>
/// Loads the chunk.
/// </summary>
/// <returns>true if the chunk has loaded successfully, otherwise false.</returns>
public bool load()
{
return load(true);
}
/// <summary>
/// Unloads and optionally saves the Chunk.
/// </summary>
/// <param name="save">Controls whether the chunk is saved.</param>
/// <param name="safe">Controls whether to unload the chunk when players are nearby.</param>
/// <returns>true if the chunk has unloaded successfully, otherwise false.</returns>
public bool unload(bool save, bool safe)
{
if (NativeBridge.UnloadChunk != null)
return NativeBridge.UnloadChunk(_world.getDimensionId(), _chunkX, _chunkZ, save ? 1 : 0, safe ? 1 : 0) != 0;
return false;
}
/// <summary>
/// Unloads and optionally saves the Chunk.
/// </summary>
/// <param name="save">Controls whether the chunk is saved.</param>
/// <returns>true if the chunk has unloaded successfully, otherwise false.</returns>
public bool unload(bool save)
{
return unload(save, true);
}
/// <summary>
/// Unloads and optionally saves the Chunk.
/// </summary>
/// <returns>true if the chunk has unloaded successfully, otherwise false.</returns>
public bool unload()
{
return unload(true, true);
}
}

View file

@ -0,0 +1,209 @@
namespace Minecraft.Server.FourKit.Chunk;
using Minecraft.Server.FourKit.Block;
/// <summary>
/// Represents a static, thread-safe snapshot of chunk of blocks.
/// Purpose is to allow clean, efficient copy of a chunk data to be made, and
/// then handed off for processing in another thread (e.g. map rendering).
/// </summary>
public class ChunkSnapshot
{
private readonly int _chunkX;
private readonly int _chunkZ;
private readonly string _worldName;
private readonly long _captureFullTime;
private readonly int[] _blockIds;
private readonly int[] _blockData;
private readonly int[] _maxBlockY;
private readonly int[]? _skyLight;
private readonly int[]? _blockLight;
private readonly int[]? _biome;
private readonly double[]? _biomeTemp;
private readonly double[]? _biomeRainfall;
internal ChunkSnapshot(int chunkX, int chunkZ, string worldName, long captureFullTime,
int[] blockIds, int[] blockData, int[] maxBlockY,
int[]? skyLight = null, int[]? blockLight = null,
int[]? biome = null, double[]? biomeTemp = null, double[]? biomeRainfall = null)
{
_chunkX = chunkX;
_chunkZ = chunkZ;
_worldName = worldName;
_captureFullTime = captureFullTime;
_blockIds = blockIds;
_blockData = blockData;
_maxBlockY = maxBlockY;
_skyLight = skyLight;
_blockLight = blockLight;
_biome = biome;
_biomeTemp = biomeTemp;
_biomeRainfall = biomeRainfall;
}
/// <summary>
/// Gets the X-coordinate of this chunk.
/// </summary>
/// <returns>X-coordinate.</returns>
public int getX() => _chunkX;
/// <summary>
/// Gets the Z-coordinate of this chunk.
/// </summary>
/// <returns>Z-coordinate.</returns>
public int getZ() => _chunkZ;
/// <summary>
/// Gets name of the world containing this chunk.
/// </summary>
/// <returns>Parent World Name.</returns>
public string getWorldName() => _worldName;
/// <summary>
/// Get block type for block at corresponding coordinate in the chunk.
/// </summary>
/// <param name="x">0-15</param>
/// <param name="y">0-127</param>
/// <param name="z">0-15</param>
/// <returns>0-255</returns>
public int getBlockTypeId(int x, int y, int z)
{
int idx = (x * 128 * 16) + (y * 16) + z;
if (idx < 0 || idx >= _blockIds.Length) return 0;
return _blockIds[idx];
}
/// <summary>
/// Get block data for block at corresponding coordinate in the chunk.
/// </summary>
/// <param name="x">0-15</param>
/// <param name="y">0-127</param>
/// <param name="z">0-15</param>
/// <returns>0-15</returns>
public int getBlockData(int x, int y, int z)
{
int idx = (x * 128 * 16) + (y * 16) + z;
if (idx < 0 || idx >= _blockData.Length) return 0;
return _blockData[idx];
}
/// <summary>
/// Gets the highest non-air coordinate at the given coordinates.
/// </summary>
/// <param name="x">X-coordinate of the blocks.</param>
/// <param name="z">Z-coordinate of the blocks.</param>
/// <returns>Y-coordinate of the highest non-air block.</returns>
public int getHighestBlockYAt(int x, int z)
{
int idx = x * 16 + z;
if (idx < 0 || idx >= _maxBlockY.Length) return 0;
return _maxBlockY[idx];
}
/// <summary>
/// Get world full time when chunk snapshot was captured.
/// </summary>
/// <returns>Time in ticks.</returns>
public long getCaptureFullTime() => _captureFullTime;
/// <summary>
/// Test if section is empty.
/// </summary>
/// <param name="sy">Section Y coordinate (block Y / 16).</param>
/// <returns>true if empty, false if not.</returns>
public bool isSectionEmpty(int sy)
{
int startY = sy * 16;
int endY = startY + 16;
if (endY > 128) endY = 128;
for (int x = 0; x < 16; x++)
{
for (int z = 0; z < 16; z++)
{
for (int y = startY; y < endY; y++)
{
int idx = (x * 128 * 16) + (y * 16) + z;
if (idx >= 0 && idx < _blockIds.Length && _blockIds[idx] != 0)
return false;
}
}
}
return true;
}
/// <summary>
/// Get sky light level for block at corresponding coordinate in the chunk.
/// </summary>
/// <param name="x">0-15</param>
/// <param name="y">0-127</param>
/// <param name="z">0-15</param>
/// <returns>0-15</returns>
public int getBlockSkyLight(int x, int y, int z)
{
if (_skyLight == null) return 0;
int idx = (x * 128 * 16) + (y * 16) + z;
if (idx < 0 || idx >= _skyLight.Length) return 0;
return _skyLight[idx];
}
/// <summary>
/// Get light level emitted by block at corresponding coordinate in the chunk.
/// </summary>
/// <param name="x">0-15</param>
/// <param name="y">0-127</param>
/// <param name="z">0-15</param>
/// <returns>0-15</returns>
public int getBlockEmittedLight(int x, int y, int z)
{
if (_blockLight == null) return 0;
int idx = (x * 128 * 16) + (y * 16) + z;
if (idx < 0 || idx >= _blockLight.Length) return 0;
return _blockLight[idx];
}
/// <summary>
/// Get biome at given coordinates.
/// </summary>
/// <param name="x">X-coordinate (0-15)</param>
/// <param name="z">Z-coordinate (0-15)</param>
/// <returns>Biome at given coordinate.</returns>
public Biome getBiome(int x, int z)
{
if (_biome == null) return Biome.PLAINS;
int idx = x * 16 + z;
if (idx < 0 || idx >= _biome.Length) return Biome.PLAINS;
return BiomeHelper.fromId(_biome[idx]);
}
/// <summary>
/// Get raw biome temperature (0.0-1.0) at given coordinate.
/// </summary>
/// <param name="x">X-coordinate (0-15)</param>
/// <param name="z">Z-coordinate (0-15)</param>
/// <returns>Temperature at given coordinate.</returns>
public double getRawBiomeTemperature(int x, int z)
{
if (_biomeTemp != null)
{
int idx = x * 16 + z;
if (idx >= 0 && idx < _biomeTemp.Length) return _biomeTemp[idx];
}
return getBiome(x, z).getTemperature();
}
/// <summary>
/// Get raw biome rainfall (0.0-1.0) at given coordinate.
/// </summary>
/// <param name="x">X-coordinate (0-15)</param>
/// <param name="z">Z-coordinate (0-15)</param>
/// <returns>Rainfall at given coordinate.</returns>
public double getRawBiomeRainfall(int x, int z)
{
if (_biomeRainfall != null)
{
int idx = x * 16 + z;
if (idx >= 0 && idx < _biomeRainfall.Length) return _biomeRainfall[idx];
}
return getBiome(x, z).getRainfall();
}
}

View file

@ -11,7 +11,7 @@ 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);
internal EnderChestInventory? _enderChestInventory;
private ItemStack? _cursorItem;
private bool _sleeping;
private int _sleepTicks;
@ -59,6 +59,8 @@ public abstract class HumanEntity : LivingEntity, InventoryHolder
/// <returns>The EnderChest of the player.</returns>
public Inventory getEnderChest()
{
// AAAAAH
_enderChestInventory ??= new EnderChestInventory(getEntityId());
return _enderChestInventory;
}
@ -86,14 +88,44 @@ public abstract class HumanEntity : LivingEntity, InventoryHolder
/// 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;
public ItemStack? getItemOnCursor()
{
if (NativeBridge.GetCarriedItem != null)
{
int[] buf = new int[3];
var gh = System.Runtime.InteropServices.GCHandle.Alloc(buf, System.Runtime.InteropServices.GCHandleType.Pinned);
try
{
NativeBridge.GetCarriedItem(getEntityId(), gh.AddrOfPinnedObject());
}
finally
{
gh.Free();
}
int id = buf[0];
int aux = buf[1];
int count = buf[2];
if (id > 0 && count > 0)
_cursorItem = new ItemStack(id, count, (short)aux);
else
_cursorItem = null;
}
return _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;
public void setItemOnCursor(ItemStack? item)
{
_cursorItem = item;
NativeBridge.SetCarriedItem?.Invoke(getEntityId(),
item?.getTypeId() ?? 0,
item?.getAmount() ?? 0,
item?.getDurability() ?? 0);
}
/// <summary>
/// If the player currently has an inventory window open, this method will

View file

@ -0,0 +1,22 @@
namespace Minecraft.Server.FourKit.Event.World;
using Minecraft.Server.FourKit.Chunk;
/// <summary>
/// Represents a Chunk related event.
/// </summary>
public abstract class ChunkEvent : WorldEvent
{
protected Chunk chunk;
protected ChunkEvent(Chunk chunk) : base(chunk.getWorld())
{
this.chunk = chunk;
}
/// <summary>
/// Gets the chunk being loaded/unloaded.
/// </summary>
/// <returns>Chunk that triggered this event.</returns>
public Chunk getChunk() => chunk;
}

View file

@ -0,0 +1,23 @@
namespace Minecraft.Server.FourKit.Event.World;
using Minecraft.Server.FourKit.Chunk;
/// <summary>
/// Called when a chunk is loaded.
/// </summary>
public class ChunkLoadEvent : ChunkEvent
{
private readonly bool _newChunk;
internal ChunkLoadEvent(Chunk chunk, bool newChunk) : base(chunk)
{
_newChunk = newChunk;
}
/// <summary>
/// Gets if this chunk was newly created or not. Note that if this chunk is
/// new, it will not be populated at this time.
/// </summary>
/// <returns>true if the chunk is new, otherwise false.</returns>
public bool isNewChunk() => _newChunk;
}

View file

@ -0,0 +1,33 @@
namespace Minecraft.Server.FourKit.Event.World;
using Minecraft.Server.FourKit.Chunk;
/// <summary>
/// Called when a chunk is unloaded.
/// </summary>
public class ChunkUnloadEvent : ChunkEvent, Cancellable
{
private bool _cancel;
internal ChunkUnloadEvent(Chunk chunk) : base(chunk)
{
_cancel = false;
}
/// <summary>
/// Gets the cancellation state of this event. A cancelled event will not
/// be executed in the server, but will still pass to other plugins.
/// </summary>
/// <returns>true if this event is cancelled.</returns>
public bool isCancelled() => _cancel;
/// <summary>
/// Sets the cancellation state of this event. A cancelled event will not
/// be executed in the server, but will still pass to other plugins.
/// </summary>
/// <param name="cancel">true if you wish to cancel this event.</param>
public void setCancelled(bool cancel)
{
_cancel = cancel;
}
}

View file

@ -22,60 +22,80 @@ internal sealed class EventDispatcher
public int CompareTo(RegisteredHandler other) => Priority.CompareTo(other.Priority);
}
private readonly Dictionary<Type, List<RegisteredHandler>> _handlers = new();
private readonly object _lock = new();
// Snapshot-on-write: writers swap _handlers atomically; Fire reads it lock-free.
private volatile Dictionary<Type, RegisteredHandler[]> _handlers = new();
private readonly object _writeLock = new();
// Fired when an event type gains its first handler.
internal Action<Type>? OnSubscriptionChanged;
public void Register(Listener listener)
{
var methods = listener.GetType().GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
lock (_lock)
List<(Type eventType, RegisteredHandler handler)>? pending = null;
foreach (var method in methods)
{
foreach (var method in methods)
var attr = method.GetCustomAttribute<Event.EventHandlerAttribute>();
if (attr == null)
continue;
var parameters = method.GetParameters();
if (parameters.Length != 1)
{
var attr = method.GetCustomAttribute<Event.EventHandlerAttribute>();
if (attr == null)
continue;
var parameters = method.GetParameters();
if (parameters.Length != 1)
{
Console.WriteLine($"[FourKit] Warning: @EventHandler method {method.Name} must have exactly 1 parameter, skipping.");
continue;
}
var eventType = parameters[0].ParameterType;
if (!typeof(Event.Event).IsAssignableFrom(eventType))
{
Console.WriteLine($"[FourKit] Warning: @EventHandler method {method.Name} parameter must extend Event, skipping.");
continue;
}
if (!_handlers.TryGetValue(eventType, out var list))
{
list = new List<RegisteredHandler>();
_handlers[eventType] = list;
}
list.Add(new RegisteredHandler(listener, method, attr.Priority, attr.IgnoreCancelled));
_handlers[eventType] = list.OrderBy(h => h.Priority).ToList();
Console.WriteLine($"[FourKit] Warning: @EventHandler method {method.Name} must have exactly 1 parameter, skipping.");
continue;
}
var eventType = parameters[0].ParameterType;
if (!typeof(Event.Event).IsAssignableFrom(eventType))
{
Console.WriteLine($"[FourKit] Warning: @EventHandler method {method.Name} parameter must extend Event, skipping.");
continue;
}
pending ??= new List<(Type, RegisteredHandler)>();
pending.Add((eventType, new RegisteredHandler(listener, method, attr.Priority, attr.IgnoreCancelled)));
}
if (pending == null) return;
HashSet<Type> newlySubscribed = new();
lock (_writeLock)
{
var newDict = new Dictionary<Type, RegisteredHandler[]>(_handlers);
foreach (var (eventType, handler) in pending)
{
bool hadAny = newDict.TryGetValue(eventType, out var existing);
existing ??= Array.Empty<RegisteredHandler>();
// OrderBy is stable; Array.Sort is not.
var combined = existing.Append(handler).OrderBy(h => h.Priority).ToArray();
newDict[eventType] = combined;
if (!hadAny) newlySubscribed.Add(eventType);
}
_handlers = newDict;
}
if (OnSubscriptionChanged != null)
{
foreach (var t in newlySubscribed)
OnSubscriptionChanged(t);
}
}
public void Fire(Event.Event evt)
{
List<RegisteredHandler>? handlers;
lock (_lock)
{
if (!_handlers.TryGetValue(evt.GetType(), out handlers))
return;
handlers = new List<RegisteredHandler>(handlers);
}
var snapshot = _handlers;
if (!snapshot.TryGetValue(evt.GetType(), out var handlers))
return;
var cancellable = evt as Cancellable;
foreach (var handler in handlers)
for (int i = 0; i < handlers.Length; i++)
{
ref readonly var handler = ref handlers[i];
if (handler.IgnoreCancelled && cancellable != null && cancellable.isCancelled())
continue;
@ -89,4 +109,6 @@ internal sealed class EventDispatcher
}
}
}
internal bool IsSubscribed(Type eventType) => _handlers.ContainsKey(eventType);
}

View file

@ -11,11 +11,68 @@ using Minecraft.Server.FourKit.Plugin;
/// </summary>
public static class FourKit
{
private static readonly EventDispatcher _dispatcher = new();
private static readonly EventDispatcher _dispatcher;
private static readonly Dictionary<string, Player> _players = new(StringComparer.OrdinalIgnoreCase);
private static readonly Dictionary<int, Player> _playersByEntityId = new();
private static readonly object _playerLock = new();
// Must match HandlerKind in FourKitNatives.h.
private enum HandlerKind
{
ChunkLoad = 0,
ChunkUnload = 1,
PlayerMove = 2,
}
private static uint _handlerMask;
private static readonly object _handlerMaskLock = new();
static FourKit()
{
_dispatcher = new EventDispatcher();
_dispatcher.OnSubscriptionChanged = OnEventSubscribed;
}
private static HandlerKind? MapEventTypeToHandlerKind(Type eventType)
{
if (eventType == typeof(Event.World.ChunkLoadEvent)) return HandlerKind.ChunkLoad;
if (eventType == typeof(Event.World.ChunkUnloadEvent)) return HandlerKind.ChunkUnload;
if (eventType == typeof(Event.Player.PlayerMoveEvent)) return HandlerKind.PlayerMove;
return null;
}
private static void OnEventSubscribed(Type eventType)
{
var kind = MapEventTypeToHandlerKind(eventType);
if (kind == null) return;
lock (_handlerMaskLock)
{
uint newMask = _handlerMask | (1u << (int)kind.Value);
if (newMask == _handlerMask) return;
_handlerMask = newMask;
NativeBridge.SetHandlerMask?.Invoke(_handlerMask);
}
}
internal static void ResyncHandlerMask()
{
lock (_handlerMaskLock)
{
NativeBridge.SetHandlerMask?.Invoke(_handlerMask);
}
}
/// <summary>
/// Gets the current server tick count. Increments once per server tick
/// (~20 per second under nominal load). Useful for measuring TPS by
/// sampling the delta against wall clock time.
/// </summary>
public static int getServerTick()
{
return NativeBridge.GetServerTickCount?.Invoke() ?? 0;
}
internal const int MAX_CHAT_LENGTH = 123;
private static readonly Dictionary<int, World> _worldsByDimId = new();

View file

@ -58,11 +58,11 @@ public static partial class FourKitHost
}
[UnmanagedCallersOnly]
public static void SetInventoryCallbacks(IntPtr getPlayerInventory, IntPtr setPlayerInventorySlot, IntPtr getContainerContents, IntPtr setContainerSlot, IntPtr getContainerViewerEntityIds, IntPtr closeContainer, IntPtr openVirtualContainer, IntPtr getItemMeta, IntPtr setItemMeta, IntPtr setHeldItemSlot)
public static void SetInventoryCallbacks(IntPtr getPlayerInventory, IntPtr setPlayerInventorySlot, IntPtr getContainerContents, IntPtr setContainerSlot, IntPtr getContainerViewerEntityIds, IntPtr closeContainer, IntPtr openVirtualContainer, IntPtr getItemMeta, IntPtr setItemMeta, IntPtr setHeldItemSlot, IntPtr getCarriedItem, IntPtr setCarriedItem, IntPtr getEnderChestContents, IntPtr setEnderChestSlot)
{
try
{
NativeBridge.SetInventoryCallbacks(getPlayerInventory, setPlayerInventorySlot, getContainerContents, setContainerSlot, getContainerViewerEntityIds, closeContainer, openVirtualContainer, getItemMeta, setItemMeta, setHeldItemSlot);
NativeBridge.SetInventoryCallbacks(getPlayerInventory, setPlayerInventorySlot, getContainerContents, setContainerSlot, getContainerViewerEntityIds, closeContainer, openVirtualContainer, getItemMeta, setItemMeta, setHeldItemSlot, getCarriedItem, setCarriedItem, getEnderChestContents, setEnderChestSlot);
}
catch (Exception ex)
{
@ -121,4 +121,71 @@ public static partial class FourKitHost
ServerLog.Error("fourkit", $"SetVehicleCallbacks error: {ex}");
}
}
[UnmanagedCallersOnly]
public static void SetChunkCallbacks(IntPtr isChunkLoaded, IntPtr loadChunk, IntPtr unloadChunk, IntPtr getLoadedChunks, IntPtr isChunkInUse, IntPtr getChunkSnapshot, IntPtr unloadChunkRequest, IntPtr regenerateChunk, IntPtr refreshChunk)
{
try
{
NativeBridge.SetChunkCallbacks(isChunkLoaded, loadChunk, unloadChunk, getLoadedChunks, isChunkInUse, getChunkSnapshot, unloadChunkRequest, regenerateChunk, refreshChunk);
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"SetChunkCallbacks error: {ex}");
}
}
[UnmanagedCallersOnly]
public static void SetBlockInfoCallbacks(IntPtr getSkyLight, IntPtr getBlockLight, IntPtr getBiomeId, IntPtr setBiomeId)
{
try
{
NativeBridge.SetBlockInfoCallbacks(getSkyLight, getBlockLight, getBiomeId, setBiomeId);
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"SetBlockInfoCallbacks error: {ex}");
}
}
[UnmanagedCallersOnly]
public static void SetWorldEntityCallbacks(IntPtr getWorldEntities, IntPtr getChunkEntities)
{
try
{
NativeBridge.SetWorldEntityCallbacks(getWorldEntities, getChunkEntities);
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"SetWorldEntityCallbacks error: {ex}");
}
}
[UnmanagedCallersOnly]
public static void SetSubscriptionCallbacks(IntPtr setHandlerMask)
{
try
{
NativeBridge.SetSubscriptionCallbacks(setHandlerMask);
// Flush the mask accumulated during plugin onEnable.
FourKit.ResyncHandlerMask();
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"SetSubscriptionCallbacks error: {ex}");
}
}
[UnmanagedCallersOnly]
public static void SetServerCallbacks(IntPtr getServerTickCount)
{
try
{
NativeBridge.SetServerCallbacks(getServerTickCount);
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"SetServerCallbacks error: {ex}");
}
}
}

View file

@ -1250,4 +1250,38 @@ public static partial class FourKitHost
return 0;
}
}
[UnmanagedCallersOnly]
public static void FireChunkLoad(int dimId, int chunkX, int chunkZ, int isNewChunk)
{
try
{
var world = FourKit.getWorld(dimId);
var chunk = new Chunk.Chunk(world, chunkX, chunkZ);
var evt = new Event.World.ChunkLoadEvent(chunk, isNewChunk != 0);
FourKit.FireEvent(evt);
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"FireChunkLoad error: {ex}");
}
}
[UnmanagedCallersOnly]
public static int FireChunkUnload(int dimId, int chunkX, int chunkZ)
{
try
{
var world = FourKit.getWorld(dimId);
var chunk = new Chunk.Chunk(world, chunkX, chunkZ);
var evt = new Event.World.ChunkUnloadEvent(chunk);
FourKit.FireEvent(evt);
return evt.isCancelled() ? 1 : 0;
}
catch (Exception ex)
{
ServerLog.Error("fourkit", $"FireChunkUnload error: {ex}");
return 0;
}
}
}

View file

@ -0,0 +1,85 @@
namespace Minecraft.Server.FourKit.Inventory;
using System.Runtime.InteropServices;
// todo: this needs to be removed at some point
internal class EnderChestInventory : Inventory
{
private readonly int _ownerEntityId;
internal EnderChestInventory(int ownerEntityId)
: base("Ender Chest", InventoryType.ENDER_CHEST, 27)
{
_ownerEntityId = ownerEntityId;
}
protected internal override void EnsureSynced()
{
if (NativeBridge.GetEnderChestContents == null)
return;
int[] buf = new int[27 * 3];
var gh = GCHandle.Alloc(buf, GCHandleType.Pinned);
try
{
NativeBridge.GetEnderChestContents(_ownerEntityId, gh.AddrOfPinnedObject());
}
finally
{
gh.Free();
}
for (int i = 0; i < 27; i++)
{
int id = buf[i * 3 + 0];
int aux = buf[i * 3 + 1];
int packed = buf[i * 3 + 2];
ushort count = (ushort)((packed >> 8) & 0xFFFF);
_items[i]?.UnbindFromInventory();
if (id > 0 && count > 0)
{
if (_items[i] == null)
{
_items[i] = new ItemStack(id, count, (short)aux);
}
else
{
_items[i]!.setTypeId(id);
_items[i]!.setAmount(count);
_items[i]!.setDurability((short)aux);
}
_items[i]!.BindToInventory(this, i);
}
else
{
_items[i] = null;
}
}
}
public override void setItem(int index, ItemStack? item)
{
if (index >= 0 && index < _items.Length)
{
var old = _items[index];
if (old != item)
{
old?.UnbindFromInventory();
item?.BindToInventory(this, index);
}
_items[index] = item;
_slotModifiedByPlugin = true;
}
if (NativeBridge.SetEnderChestSlot != null && index >= 0 && index < _items.Length)
{
int id = item?.getTypeId() ?? 0;
int count = item?.getAmount() ?? 0;
int aux = item?.getDurability() ?? 0;
NativeBridge.SetEnderChestSlot(_ownerEntityId, index, id, count, aux);
}
}
}

View file

@ -174,7 +174,9 @@ public class Inventory : IEnumerable<ItemStack>
if (_items[slot] == null)
{
int added = Math.Min(64, remaining);
setItem(slot, new ItemStack(toAdd.getType(), added, toAdd.getDurability()));
var newItem = new ItemStack(toAdd.getType(), added, toAdd.getDurability());
newItem.setItemMetaInternal(toAdd.getItemMetaInternal()?.clone());
setItem(slot, newItem);
remaining -= added;
}
}

View file

@ -154,7 +154,7 @@ public class ItemMeta
/// <param name="enchants">The enchantments to set.</param>
public void setEnchants(Dictionary<EnchantmentType, int>? enchants)
{
enchants = enchants != null ? new Dictionary<EnchantmentType, int>(enchants) : null;
_enchants = enchants != null ? new Dictionary<EnchantmentType, int>(enchants) : null;
}

View file

@ -186,6 +186,15 @@ public class Location
public Location clone() => new Location(LocationWorld, X, Y, Z, Yaw, Pitch);
/// <summary>
/// Gets the chunk at the represented location.
/// </summary>
/// <returns>Chunk at the represented location.</returns>
public Chunk.Chunk getChunk()
{
return getWorld().getChunkAt(getBlockX() >> 4, getBlockZ() >> 4);
}
/// <inheritdoc/>
public override string ToString() => $"Location(world={LocationWorld.getName()}, x={X}, y={Y}, z={Z}, yaw={Yaw}, pitch={Pitch})";
}

View file

@ -7,5 +7,7 @@
<AssemblyName>Minecraft.Server.FourKit</AssemblyName>
<EnableDynamicLoading>true</EnableDynamicLoading>
<BaseOutputPath>bin</BaseOutputPath>
<ServerGarbageCollection>true</ServerGarbageCollection>
<ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>
</PropertyGroup>
</Project>

View file

@ -43,10 +43,10 @@ internal static class NativeBridge
internal delegate int NativeGetTileDataDelegate(int dimId, int x, int y, int z);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void NativeSetTileDelegate(int dimId, int x, int y, int z, int tileId, int data);
internal delegate void NativeSetTileDelegate(int dimId, int x, int y, int z, int tileId, int data, int flags);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void NativeSetTileDataDelegate(int dimId, int x, int y, int z, int data);
internal delegate void NativeSetTileDataDelegate(int dimId, int x, int y, int z, int data, int flags);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate int NativeBreakBlockDelegate(int dimId, int x, int y, int z);
@ -123,6 +123,18 @@ internal static class NativeBridge
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void NativeSetHeldItemSlotDelegate(int entityId, int slot);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void NativeGetCarriedItemDelegate(int entityId, IntPtr outData);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void NativeSetCarriedItemDelegate(int entityId, int itemId, int count, int aux);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void NativeGetEnderChestContentsDelegate(int entityId, IntPtr outData);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void NativeSetEnderChestSlotDelegate(int entityId, int slot, int itemId, int count, int aux);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void NativeSetSneakingDelegate(int entityId, int sneak);
@ -180,6 +192,58 @@ internal static class NativeBridge
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void NativeGetEntityInfoDelegate(int entityId, IntPtr outBuf);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate int NativeIsChunkLoadedDelegate(int dimId, int chunkX, int chunkZ);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate int NativeLoadChunkDelegate(int dimId, int chunkX, int chunkZ, int generate);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate int NativeUnloadChunkDelegate(int dimId, int chunkX, int chunkZ, int save, int safe);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate int NativeGetLoadedChunksDelegate(int dimId, out IntPtr coordBuf);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate int NativeIsChunkInUseDelegate(int dimId, int chunkX, int chunkZ);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void NativeGetChunkSnapshotDelegate(int dimId, int chunkX, int chunkZ, IntPtr blockIds, IntPtr blockData, IntPtr maxBlockY);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate int NativeUnloadChunkRequestDelegate(int dimId, int chunkX, int chunkZ, int safe);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate int NativeRegenerateChunkDelegate(int dimId, int chunkX, int chunkZ);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate int NativeRefreshChunkDelegate(int dimId, int chunkX, int chunkZ);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate int NativeGetWorldEntitiesDelegate(int dimId, out IntPtr outBuf);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate int NativeGetChunkEntitiesDelegate(int dimId, int chunkX, int chunkZ, out IntPtr outBuf);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate int NativeGetSkyLightDelegate(int dimId, int x, int y, int z);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate int NativeGetBlockLightDelegate(int dimId, int x, int y, int z);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate int NativeGetBiomeIdDelegate(int dimId, int x, int z);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void NativeSetBiomeIdDelegate(int dimId, int x, int z, int biomeId);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void NativeSetHandlerMaskDelegate(uint mask);
internal static NativeSetHandlerMaskDelegate? SetHandlerMask;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate int NativeGetServerTickCountDelegate();
internal static NativeGetServerTickCountDelegate? GetServerTickCount;
internal static NativeDamageDelegate? DamagePlayer;
internal static NativeSetHealthDelegate? SetPlayerHealth;
@ -221,6 +285,10 @@ internal static class NativeBridge
internal static NativeGetItemMetaDelegate? GetItemMeta;
internal static NativeSetItemMetaDelegate? SetItemMeta;
internal static NativeSetHeldItemSlotDelegate? SetHeldItemSlot;
internal static NativeGetCarriedItemDelegate? GetCarriedItem;
internal static NativeSetCarriedItemDelegate? SetCarriedItem;
internal static NativeGetEnderChestContentsDelegate? GetEnderChestContents;
internal static NativeSetEnderChestSlotDelegate? SetEnderChestSlot;
internal static NativeSetSneakingDelegate? SetSneaking;
internal static NativeSetVelocityDelegate? SetVelocity;
internal static NativeSetAllowFlightDelegate? SetAllowFlight;
@ -240,6 +308,21 @@ internal static class NativeBridge
internal static NativeGetVehicleIdDelegate? GetVehicleId;
internal static NativeGetPassengerIdDelegate? GetPassengerId;
internal static NativeGetEntityInfoDelegate? GetEntityInfo;
internal static NativeIsChunkLoadedDelegate? IsChunkLoaded;
internal static NativeLoadChunkDelegate? LoadChunk;
internal static NativeUnloadChunkDelegate? UnloadChunk;
internal static NativeGetLoadedChunksDelegate? GetLoadedChunks;
internal static NativeIsChunkInUseDelegate? IsChunkInUse;
internal static NativeGetChunkSnapshotDelegate? GetChunkSnapshot;
internal static NativeUnloadChunkRequestDelegate? UnloadChunkRequest;
internal static NativeRegenerateChunkDelegate? RegenerateChunk;
internal static NativeRefreshChunkDelegate? RefreshChunk;
internal static NativeGetWorldEntitiesDelegate? GetWorldEntities;
internal static NativeGetChunkEntitiesDelegate? GetChunkEntities;
internal static NativeGetSkyLightDelegate? GetSkyLight;
internal static NativeGetBlockLightDelegate? GetBlockLight;
internal static NativeGetBiomeIdDelegate? GetBiomeId;
internal static NativeSetBiomeIdDelegate? SetBiomeId;
internal static void SetCallbacks(IntPtr damage, IntPtr setHealth, IntPtr teleport, IntPtr setGameMode, IntPtr broadcastMessage, IntPtr setFallDistance, IntPtr getPlayerSnapshot, IntPtr sendMessage, IntPtr setWalkSpeed, IntPtr teleportEntity)
{
@ -286,7 +369,7 @@ internal static class NativeBridge
SendRaw = Marshal.GetDelegateForFunctionPointer<NativeSendRawDelegate>(sendRaw);
}
internal static void SetInventoryCallbacks(IntPtr getPlayerInventory, IntPtr setPlayerInventorySlot, IntPtr getContainerContents, IntPtr setContainerSlot, IntPtr getContainerViewerEntityIds, IntPtr closeContainer, IntPtr openVirtualContainer, IntPtr getItemMeta, IntPtr setItemMeta, IntPtr setHeldItemSlot)
internal static void SetInventoryCallbacks(IntPtr getPlayerInventory, IntPtr setPlayerInventorySlot, IntPtr getContainerContents, IntPtr setContainerSlot, IntPtr getContainerViewerEntityIds, IntPtr closeContainer, IntPtr openVirtualContainer, IntPtr getItemMeta, IntPtr setItemMeta, IntPtr setHeldItemSlot, IntPtr getCarriedItem, IntPtr setCarriedItem, IntPtr getEnderChestContents, IntPtr setEnderChestSlot)
{
GetPlayerInventory = Marshal.GetDelegateForFunctionPointer<NativeGetPlayerInventoryDelegate>(getPlayerInventory);
SetPlayerInventorySlot = Marshal.GetDelegateForFunctionPointer<NativeSetPlayerInventorySlotDelegate>(setPlayerInventorySlot);
@ -298,6 +381,10 @@ internal static class NativeBridge
GetItemMeta = Marshal.GetDelegateForFunctionPointer<NativeGetItemMetaDelegate>(getItemMeta);
SetItemMeta = Marshal.GetDelegateForFunctionPointer<NativeSetItemMetaDelegate>(setItemMeta);
SetHeldItemSlot = Marshal.GetDelegateForFunctionPointer<NativeSetHeldItemSlotDelegate>(setHeldItemSlot);
GetCarriedItem = Marshal.GetDelegateForFunctionPointer<NativeGetCarriedItemDelegate>(getCarriedItem);
SetCarriedItem = Marshal.GetDelegateForFunctionPointer<NativeSetCarriedItemDelegate>(setCarriedItem);
GetEnderChestContents = Marshal.GetDelegateForFunctionPointer<NativeGetEnderChestContentsDelegate>(getEnderChestContents);
SetEnderChestSlot = Marshal.GetDelegateForFunctionPointer<NativeSetEnderChestSlotDelegate>(setEnderChestSlot);
}
internal static void SetEntityCallbacks(IntPtr setSneaking, IntPtr setVelocity, IntPtr setAllowFlight, IntPtr playSound, IntPtr setSleepingIgnored)
@ -334,4 +421,41 @@ internal static class NativeBridge
GetPassengerId = Marshal.GetDelegateForFunctionPointer<NativeGetPassengerIdDelegate>(getPassengerId);
GetEntityInfo = Marshal.GetDelegateForFunctionPointer<NativeGetEntityInfoDelegate>(getEntityInfo);
}
internal static void SetChunkCallbacks(IntPtr isChunkLoaded, IntPtr loadChunk, IntPtr unloadChunk, IntPtr getLoadedChunks, IntPtr isChunkInUse, IntPtr getChunkSnapshot, IntPtr unloadChunkRequest, IntPtr regenerateChunk, IntPtr refreshChunk)
{
IsChunkLoaded = Marshal.GetDelegateForFunctionPointer<NativeIsChunkLoadedDelegate>(isChunkLoaded);
LoadChunk = Marshal.GetDelegateForFunctionPointer<NativeLoadChunkDelegate>(loadChunk);
UnloadChunk = Marshal.GetDelegateForFunctionPointer<NativeUnloadChunkDelegate>(unloadChunk);
GetLoadedChunks = Marshal.GetDelegateForFunctionPointer<NativeGetLoadedChunksDelegate>(getLoadedChunks);
IsChunkInUse = Marshal.GetDelegateForFunctionPointer<NativeIsChunkInUseDelegate>(isChunkInUse);
GetChunkSnapshot = Marshal.GetDelegateForFunctionPointer<NativeGetChunkSnapshotDelegate>(getChunkSnapshot);
UnloadChunkRequest = Marshal.GetDelegateForFunctionPointer<NativeUnloadChunkRequestDelegate>(unloadChunkRequest);
RegenerateChunk = Marshal.GetDelegateForFunctionPointer<NativeRegenerateChunkDelegate>(regenerateChunk);
RefreshChunk = Marshal.GetDelegateForFunctionPointer<NativeRefreshChunkDelegate>(refreshChunk);
}
internal static void SetWorldEntityCallbacks(IntPtr getWorldEntities, IntPtr getChunkEntities)
{
GetWorldEntities = Marshal.GetDelegateForFunctionPointer<NativeGetWorldEntitiesDelegate>(getWorldEntities);
GetChunkEntities = Marshal.GetDelegateForFunctionPointer<NativeGetChunkEntitiesDelegate>(getChunkEntities);
}
internal static void SetBlockInfoCallbacks(IntPtr getSkyLight, IntPtr getBlockLight, IntPtr getBiomeId, IntPtr setBiomeId)
{
GetSkyLight = Marshal.GetDelegateForFunctionPointer<NativeGetSkyLightDelegate>(getSkyLight);
GetBlockLight = Marshal.GetDelegateForFunctionPointer<NativeGetBlockLightDelegate>(getBlockLight);
GetBiomeId = Marshal.GetDelegateForFunctionPointer<NativeGetBiomeIdDelegate>(getBiomeId);
SetBiomeId = Marshal.GetDelegateForFunctionPointer<NativeSetBiomeIdDelegate>(setBiomeId);
}
internal static void SetSubscriptionCallbacks(IntPtr setHandlerMask)
{
SetHandlerMask = Marshal.GetDelegateForFunctionPointer<NativeSetHandlerMaskDelegate>(setHandlerMask);
}
internal static void SetServerCallbacks(IntPtr getServerTickCount)
{
GetServerTickCount = Marshal.GetDelegateForFunctionPointer<NativeGetServerTickCountDelegate>(getServerTickCount);
}
}

View file

@ -1,4 +1,5 @@
using System.Runtime.InteropServices;
using Minecraft.Server.FourKit.Chunk;
using Minecraft.Server.FourKit.Entity;
using Minecraft.Server.FourKit.Inventory;
@ -245,6 +246,115 @@ public class World
return result;
}
/// <summary>
/// Get a list of all entities in this World.
/// </summary>
/// <returns>A list of all Entities currently residing in this world.</returns>
public List<Entity.Entity> getEntities()
{
var result = new List<Entity.Entity>();
if (NativeBridge.GetWorldEntities == null) return result;
int count = NativeBridge.GetWorldEntities(_dimensionId, out IntPtr buf);
if (count <= 0 || buf == IntPtr.Zero) return result;
try
{
int[] data = new int[count * 3];
Marshal.Copy(buf, data, 0, count * 3);
for (int i = 0; i < count; i++)
{
int entityId = data[i * 3];
int mappedType = data[i * 3 + 1];
int isLiving = data[i * 3 + 2];
var entityType = Enum.IsDefined(typeof(Entity.EntityType), mappedType)
? (Entity.EntityType)mappedType
: Entity.EntityType.UNKNOWN;
if (entityType == Entity.EntityType.PLAYER)
{
var player = FourKit.GetPlayerByEntityId(entityId);
if (player != null)
{
result.Add(player);
continue;
}
}
if (isLiving == 1)
{
result.Add(new Entity.LivingEntity(entityId, entityType, _dimensionId, 0, 0, 0));
}
else
{
var entity = new Entity.Entity();
entity.SetEntityIdInternal(entityId);
entity.SetEntityTypeInternal(entityType);
entity.SetDimensionInternal(_dimensionId);
result.Add(entity);
}
}
}
finally
{
Marshal.FreeCoTaskMem(buf);
}
return result;
}
/// <summary>
/// Get a list of all living entities in this World.
/// </summary>
/// <returns>A list of all LivingEntities currently residing in this world.</returns>
public List<Entity.LivingEntity> getLivingEntities()
{
var result = new List<Entity.LivingEntity>();
if (NativeBridge.GetWorldEntities == null) return result;
int count = NativeBridge.GetWorldEntities(_dimensionId, out IntPtr buf);
if (count <= 0 || buf == IntPtr.Zero) return result;
try
{
int[] data = new int[count * 3];
Marshal.Copy(buf, data, 0, count * 3);
for (int i = 0; i < count; i++)
{
int entityId = data[i * 3];
int mappedType = data[i * 3 + 1];
int isLiving = data[i * 3 + 2];
if (isLiving != 1) continue;
var entityType = Enum.IsDefined(typeof(Entity.EntityType), mappedType)
? (Entity.EntityType)mappedType
: Entity.EntityType.UNKNOWN;
if (entityType == Entity.EntityType.PLAYER)
{
var player = FourKit.GetPlayerByEntityId(entityId);
if (player != null)
{
result.Add(player);
continue;
}
}
result.Add(new Entity.LivingEntity(entityId, entityType, _dimensionId, 0, 0, 0));
}
}
finally
{
Marshal.FreeCoTaskMem(buf);
}
return result;
}
/// <summary>
/// Creates explosion at given coordinates with given power.
/// </summary>
@ -374,4 +484,240 @@ public class World
{
NativeBridge.DropItem?.Invoke(_dimensionId, location.X, location.Y, location.Z, item.getTypeId(), item.getAmount(), item.getDurability(), 1);
}
/// <summary>
/// Gets the Chunk at the given coordinates.
/// </summary>
/// <param name="x">X-coordinate of the chunk.</param>
/// <param name="z">Z-coordinate of the chunk.</param>
/// <returns>Chunk at the given coordinates.</returns>
public Chunk.Chunk getChunkAt(int x, int z)
{
return new Chunk.Chunk(this, x, z);
}
/// <summary>
/// Gets the Chunk at the given Location.
/// </summary>
/// <param name="location">Location of the chunk.</param>
/// <returns>Chunk at the given location.</returns>
public Chunk.Chunk getChunkAt(Location location)
{
return getChunkAt(location.getBlockX() >> 4, location.getBlockZ() >> 4);
}
/// <summary>
/// Gets the Chunk that contains the given Block.
/// </summary>
/// <param name="block">Block to get the containing chunk from.</param>
/// <returns>The chunk that contains the given block.</returns>
public Chunk.Chunk getChunkAt(Block.Block block)
{
return getChunkAt(block.getX() >> 4, block.getZ() >> 4);
}
/// <summary>
/// Checks if the specified Chunk is loaded.
/// </summary>
/// <param name="chunk">The chunk to check.</param>
/// <returns>true if the chunk is loaded, otherwise false.</returns>
public bool isChunkLoaded(Chunk.Chunk chunk)
{
return isChunkLoaded(chunk.getX(), chunk.getZ());
}
/// <summary>
/// Checks if the Chunk at the specified coordinates is loaded.
/// </summary>
/// <param name="x">X-coordinate of the chunk.</param>
/// <param name="z">Z-coordinate of the chunk.</param>
/// <returns>true if the chunk is loaded, otherwise false.</returns>
public bool isChunkLoaded(int x, int z)
{
if (NativeBridge.IsChunkLoaded != null)
return NativeBridge.IsChunkLoaded(_dimensionId, x, z) != 0;
return false;
}
/// <summary>
/// Gets an array of all loaded Chunks.
/// </summary>
/// <returns>Chunk[] containing all loaded chunks.</returns>
public Chunk.Chunk[] getLoadedChunks()
{
if (NativeBridge.GetLoadedChunks == null)
return Array.Empty<Chunk.Chunk>();
int count = NativeBridge.GetLoadedChunks(_dimensionId, out IntPtr buf);
if (count <= 0 || buf == IntPtr.Zero)
return Array.Empty<Chunk.Chunk>();
try
{
int[] coords = new int[count * 2];
Marshal.Copy(buf, coords, 0, count * 2);
var chunks = new Chunk.Chunk[count];
for (int i = 0; i < count; i++)
chunks[i] = new Chunk.Chunk(this, coords[i * 2], coords[i * 2 + 1]);
return chunks;
}
finally
{
Marshal.FreeCoTaskMem(buf);
}
}
/// <summary>
/// Loads the specified Chunk.
/// </summary>
/// <param name="chunk">The chunk to load.</param>
public void loadChunk(Chunk.Chunk chunk)
{
loadChunk(chunk.getX(), chunk.getZ());
}
/// <summary>
/// Loads the Chunk at the specified coordinates.
/// If the chunk does not exist, it will be generated. This method is
/// analogous to loadChunk(int, int, boolean) where generate is true.
/// </summary>
/// <param name="x">X-coordinate of the chunk.</param>
/// <param name="z">Z-coordinate of the chunk.</param>
public void loadChunk(int x, int z)
{
loadChunk(x, z, true);
}
/// <summary>
/// Loads the Chunk at the specified coordinates.
/// </summary>
/// <param name="x">X-coordinate of the chunk.</param>
/// <param name="z">Z-coordinate of the chunk.</param>
/// <param name="generate">Whether or not to generate a chunk if it doesn't already exist.</param>
/// <returns>true if the chunk has loaded successfully, otherwise false.</returns>
public bool loadChunk(int x, int z, bool generate)
{
if (NativeBridge.LoadChunk != null)
return NativeBridge.LoadChunk(_dimensionId, x, z, generate ? 1 : 0) != 0;
return false;
}
/// <summary>
/// Checks if the Chunk at the specified coordinates is loaded and in use
/// by one or more players.
/// </summary>
/// <param name="x">X-coordinate of the chunk.</param>
/// <param name="z">Z-coordinate of the chunk.</param>
/// <returns>true if the chunk is loaded and in use by one or more players, otherwise false.</returns>
public bool isChunkInUse(int x, int z)
{
if (NativeBridge.IsChunkInUse != null)
return NativeBridge.IsChunkInUse(_dimensionId, x, z) != 0;
return false;
}
/// <summary>
/// Safely unloads and saves the Chunk at the specified coordinates.
/// This method is analogous to unloadChunk(int, int, boolean, boolean)
/// where safe and save is true.
/// </summary>
/// <param name="chunk">The chunk to unload.</param>
/// <returns>true if the chunk has unloaded successfully, otherwise false.</returns>
public bool unloadChunk(Chunk.Chunk chunk)
{
return unloadChunk(chunk.getX(), chunk.getZ());
}
/// <summary>
/// Safely unloads and saves the Chunk at the specified coordinates.
/// This method is analogous to unloadChunk(int, int, boolean, boolean)
/// where safe and save is true.
/// </summary>
/// <param name="x">X-coordinate of the chunk.</param>
/// <param name="z">Z-coordinate of the chunk.</param>
/// <returns>true if the chunk has unloaded successfully, otherwise false.</returns>
public bool unloadChunk(int x, int z)
{
return unloadChunk(x, z, true, true);
}
/// <summary>
/// Safely unloads and optionally saves the Chunk at the specified coordinates.
/// </summary>
/// <param name="x">X-coordinate of the chunk.</param>
/// <param name="z">Z-coordinate of the chunk.</param>
/// <param name="save">Whether or not to save the chunk.</param>
/// <returns>true if the chunk has unloaded successfully, otherwise false.</returns>
public bool unloadChunk(int x, int z, bool save)
{
return unloadChunk(x, z, save, true);
}
/// <summary>
/// Unloads and optionally saves the Chunk at the specified coordinates.
/// </summary>
/// <param name="x">X-coordinate of the chunk.</param>
/// <param name="z">Z-coordinate of the chunk.</param>
/// <param name="save">Controls whether the chunk is saved.</param>
/// <param name="safe">Controls whether to unload the chunk when players are nearby.</param>
/// <returns>true if the chunk has unloaded successfully, otherwise false.</returns>
public bool unloadChunk(int x, int z, bool save, bool safe)
{
if (NativeBridge.UnloadChunk != null)
return NativeBridge.UnloadChunk(_dimensionId, x, z, save ? 1 : 0, safe ? 1 : 0) != 0;
return false;
}
/// <summary>
/// Safely queues the Chunk at the specified coordinates for unloading.
/// This method is analogous to unloadChunkRequest(int, int, boolean)
/// where safe is true.
/// </summary>
/// <param name="x">X-coordinate of the chunk.</param>
/// <param name="z">Z-coordinate of the chunk.</param>
/// <returns>true is the queue attempt was successful, otherwise false.</returns>
public bool unloadChunkRequest(int x, int z)
{
return unloadChunkRequest(x, z, true);
}
/// <summary>
/// Queues the Chunk at the specified coordinates for unloading.
/// </summary>
/// <param name="x">X-coordinate of the chunk.</param>
/// <param name="z">Z-coordinate of the chunk.</param>
/// <param name="safe">Controls whether to queue the chunk when players are nearby.</param>
/// <returns>Whether the chunk was actually queued.</returns>
public bool unloadChunkRequest(int x, int z, bool safe)
{
if (NativeBridge.UnloadChunkRequest != null)
return NativeBridge.UnloadChunkRequest(_dimensionId, x, z, safe ? 1 : 0) != 0;
return false;
}
/// <summary>
/// Regenerates the Chunk at the specified coordinates.
/// </summary>
/// <param name="x">X-coordinate of the chunk.</param>
/// <param name="z">Z-coordinate of the chunk.</param>
/// <returns>Whether the chunk was actually regenerated.</returns>
public bool regenerateChunk(int x, int z)
{
if (NativeBridge.RegenerateChunk != null)
return NativeBridge.RegenerateChunk(_dimensionId, x, z) != 0;
return false;
}
/// <summary>
/// Resends the Chunk to all clients.
/// </summary>
/// <param name="x">X-coordinate of the chunk.</param>
/// <param name="z">Z-coordinate of the chunk.</param>
/// <returns>Whether the chunk was actually refreshed.</returns>
public bool refreshChunk(int x, int z)
{
if (NativeBridge.RefreshChunk != null)
return NativeBridge.RefreshChunk(_dimensionId, x, z) != 0;
return false;
}
}

View file

@ -1132,4 +1132,60 @@ public void onClick(InventoryClickEvent e)
> **Cancellable:** Yes
---
@section chunk_events Chunk Events
@subsection chunkloadevent ChunkLoadEvent
\ref Minecraft.Server.FourKit.Event.World.ChunkLoadEvent "ChunkLoadEvent" is fired when a chunk is loaded. If the chunk is newly generated it will not yet be populated when this event fires.
```csharp
[EventHandler]
public void onChunkLoad(ChunkLoadEvent e)
{
if (e.isNewChunk())
{
Console.WriteLine($"New chunk generated at {e.getChunk().getX()}, {e.getChunk().getZ()}");
}
}
```
| Method | Description |
|--------|-------------|
| `getChunk()` | The `Chunk` that was loaded. |
| `isNewChunk()` | True if this chunk was newly generated. Note that new chunks will not yet be populated at this time. |
> **Cancellable:** No
---
@subsection chunkunloadevent ChunkUnloadEvent
\ref Minecraft.Server.FourKit.Event.World.ChunkUnloadEvent "ChunkUnloadEvent" is fired when a chunk is about to be unloaded. You can cancel it to prevent the chunk from being unloaded.
```csharp
[EventHandler]
public void onChunkUnload(ChunkUnloadEvent e)
{
// keep chunks near world origin loaded
Chunk chunk = e.getChunk();
if (Math.Abs(chunk.getX()) <= 2 && Math.Abs(chunk.getZ()) <= 2)
{
e.setCancelled(true);
}
}
```
| Method | Description |
|--------|-------------|
| `getChunk()` | The `Chunk` that is about to be unloaded. |
| `isCancelled()` | Whether the unload is cancelled. |
| `setCancelled(bool)` | Cancel or allow the chunk unload. |
> **Cancellable:** Yes
---
<h1>Page currently under construction</h1>

View file

@ -62,7 +62,7 @@ typedef void(__stdcall *fn_set_player_connection_callbacks)(void *sendRaw);
typedef long long(__stdcall *fn_fire_player_drop_item)(int entityId,
int itemId, int itemCount, int itemAux,
int *outItemId, int *outItemCount, int *outItemAux);
typedef void(__stdcall *fn_set_inventory_callbacks)(void *getPlayerInventory, void *setPlayerInventorySlot, void *getContainerContents, void *setContainerSlot, void *getContainerViewerEntityIds, void *closeContainer, void *openVirtualContainer, void *getItemMeta, void *setItemMeta, void *setHeldItemSlot);
typedef void(__stdcall *fn_set_inventory_callbacks)(void *getPlayerInventory, void *setPlayerInventorySlot, void *getContainerContents, void *setContainerSlot, void *getContainerViewerEntityIds, void *closeContainer, void *openVirtualContainer, void *getItemMeta, void *setItemMeta, void *setHeldItemSlot, void *getCarriedItem, void *setCarriedItem, void *getEnderChestContents, void *setEnderChestSlot);
typedef int(__stdcall *fn_fire_player_interact)(int entityId, int action,
int itemId, int itemCount, int itemAux,
int clickedX, int clickedY, int clickedZ,
@ -103,6 +103,13 @@ typedef int(__stdcall *fn_fire_piston_extend)(int dimId, int x, int y, int z, in
typedef int(__stdcall *fn_fire_piston_retract)(int dimId, int x, int y, int z, int direction);
typedef int(__stdcall *fn_fire_command_preprocess)(int entityId, const char *cmdUtf8, int cmdByteLen, char *outBuf, int outBufSize, int *outLen);
typedef int(__stdcall *fn_fire_block_from_to)(int dimId, int fromX, int fromY, int fromZ, int toX, int toY, int toZ, int face);
typedef void(__stdcall *fn_set_chunk_callbacks)(void *isChunkLoaded, void *loadChunk, void *unloadChunk, void *getLoadedChunks, void *isChunkInUse, void *getChunkSnapshot, void *unloadChunkRequest, void *regenerateChunk, void *refreshChunk);
typedef void(__stdcall *fn_set_block_info_callbacks)(void *getSkyLight, void *getBlockLight, void *getBiomeId, void *setBiomeId);
typedef void(__stdcall *fn_set_world_entity_callbacks)(void *getWorldEntities, void *getChunkEntities);
typedef void(__stdcall *fn_set_subscription_callbacks)(void *setHandlerMask);
typedef void(__stdcall *fn_set_server_callbacks)(void *getServerTickCount);
typedef void(__stdcall *fn_fire_chunk_load)(int dimId, int chunkX, int chunkZ, int isNewChunk);
typedef int(__stdcall *fn_fire_chunk_unload)(int dimId, int chunkX, int chunkZ);
struct OpenContainerInfo
{
@ -160,6 +167,13 @@ static fn_fire_piston_extend s_managedFirePistonExtend = nullptr;
static fn_fire_piston_retract s_managedFirePistonRetract = nullptr;
static fn_fire_command_preprocess s_managedFireCommandPreprocess = nullptr;
static fn_fire_block_from_to s_managedFireBlockFromTo = nullptr;
static fn_set_chunk_callbacks s_managedSetChunkCallbacks = nullptr;
static fn_set_block_info_callbacks s_managedSetBlockInfoCallbacks = nullptr;
static fn_set_world_entity_callbacks s_managedSetWorldEntityCallbacks = nullptr;
static fn_set_subscription_callbacks s_managedSetSubscriptionCallbacks = nullptr;
static fn_set_server_callbacks s_managedSetServerCallbacks = nullptr;
static fn_fire_chunk_load s_managedFireChunkLoad = nullptr;
static fn_fire_chunk_unload s_managedFireChunkUnload = nullptr;
static bool s_initialized = false;
@ -242,6 +256,13 @@ void Initialize()
{L"FirePistonRetract", (void **)&s_managedFirePistonRetract},
{L"FireCommandPreprocess", (void **)&s_managedFireCommandPreprocess},
{L"FireBlockFromTo", (void **)&s_managedFireBlockFromTo},
{L"SetChunkCallbacks", (void **)&s_managedSetChunkCallbacks},
{L"SetBlockInfoCallbacks", (void **)&s_managedSetBlockInfoCallbacks},
{L"SetWorldEntityCallbacks", (void **)&s_managedSetWorldEntityCallbacks},
{L"SetSubscriptionCallbacks", (void **)&s_managedSetSubscriptionCallbacks},
{L"SetServerCallbacks", (void **)&s_managedSetServerCallbacks},
{L"FireChunkLoad", (void **)&s_managedFireChunkLoad},
{L"FireChunkUnload", (void **)&s_managedFireChunkUnload},
};
bool ok = true;
@ -307,7 +328,11 @@ void Initialize()
(void *)&NativeOpenVirtualContainer,
(void *)&NativeGetItemMeta,
(void *)&NativeSetItemMeta,
(void *)&NativeSetHeldItemSlot);
(void *)&NativeSetHeldItemSlot,
(void *)&NativeGetCarriedItem,
(void *)&NativeSetCarriedItem,
(void *)&NativeGetEnderChestContents,
(void *)&NativeSetEnderChestSlot);
s_managedSetEntityCallbacks(
(void *)&NativeSetSneaking,
@ -336,6 +361,33 @@ void Initialize()
(void *)&NativeGetPassengerId,
(void *)&NativeGetEntityInfo);
s_managedSetChunkCallbacks(
(void *)&NativeIsChunkLoaded,
(void *)&NativeLoadChunk,
(void *)&NativeUnloadChunk,
(void *)&NativeGetLoadedChunks,
(void *)&NativeIsChunkInUse,
(void *)&NativeGetChunkSnapshot,
(void *)&NativeUnloadChunkRequest,
(void *)&NativeRegenerateChunk,
(void *)&NativeRefreshChunk);
s_managedSetBlockInfoCallbacks(
(void *)&NativeGetSkyLight,
(void *)&NativeGetBlockLight,
(void *)&NativeGetBiomeId,
(void *)&NativeSetBiomeId);
s_managedSetWorldEntityCallbacks(
(void *)&NativeGetWorldEntities,
(void *)&NativeGetChunkEntities);
s_managedSetSubscriptionCallbacks(
(void *)&NativeSetHandlerMask);
s_managedSetServerCallbacks(
(void *)&NativeGetServerTickCount);
LogInfo("fourkit", "FourKit initialized successfully.");
}
@ -481,8 +533,12 @@ bool FirePlayerMove(int entityId,
double toX, double toY, double toZ,
double *outToX, double *outToY, double *outToZ)
{
if (!s_initialized || !s_managedFireMove)
// Caller reads outTo* unconditionally; init on every early-return.
if (!s_initialized || !s_managedFireMove || !HasHandlers(kHandlerKind_PlayerMove))
{
*outToX = toX;
*outToY = toY;
*outToZ = toZ;
return false;
}
@ -1014,4 +1070,22 @@ bool FireBlockFromTo(int dimId, int fromX, int fromY, int fromZ, int toX, int to
return false;
return s_managedFireBlockFromTo(dimId, fromX, fromY, fromZ, toX, toY, toZ, face) != 0;
}
void FireChunkLoad(int dimId, int chunkX, int chunkZ, bool isNewChunk)
{
if (!s_initialized || !s_managedFireChunkLoad)
return;
if (!HasHandlers(kHandlerKind_ChunkLoad))
return;
s_managedFireChunkLoad(dimId, chunkX, chunkZ, isNewChunk ? 1 : 0);
}
bool FireChunkUnload(int dimId, int chunkX, int chunkZ)
{
if (!s_initialized || !s_managedFireChunkUnload)
return false;
if (!HasHandlers(kHandlerKind_ChunkUnload))
return false;
return s_managedFireChunkUnload(dimId, chunkX, chunkZ) != 0;
}
} // namespace FourKitBridge

View file

@ -105,6 +105,8 @@ namespace FourKitBridge
bool FirePistonRetract(int dimId, int x, int y, int z, int direction);
bool FireCommandPreprocess(int entityId, const std::wstring &commandLine, std::wstring &outCommand);
bool FireBlockFromTo(int dimId, int fromX, int fromY, int fromZ, int toX, int toY, int toZ, int face);
void FireChunkLoad(int dimId, int chunkX, int chunkZ, bool isNewChunk);
bool FireChunkUnload(int dimId, int chunkX, int chunkZ);
#else
// Standalone build: every hook is an inline no-op. Cancellable hooks
// return false so vanilla code paths run unmodified, AND every out
@ -211,5 +213,7 @@ namespace FourKitBridge
inline bool FirePistonRetract(int, int, int, int, int) { return false; }
inline bool FireCommandPreprocess(int, const std::wstring &commandLine, std::wstring &outCommand) { outCommand = commandLine; return false; }
inline bool FireBlockFromTo(int, int, int, int, int, int, int, int) { return false; }
inline void FireChunkLoad(int, int, int, bool) {}
inline bool FireChunkUnload(int, int, int) { return false; }
#endif
}

View file

@ -3,6 +3,7 @@
#include "Common/StringUtils.h"
#include "stdafx.h"
#include <atomic>
#include <string>
#include <vector>
@ -13,6 +14,10 @@
#include "../Minecraft.Client/ServerLevel.h"
#include "../Minecraft.Client/ServerPlayer.h"
#include "../Minecraft.Client/ServerPlayerGameMode.h"
#include "../Minecraft.Client/ServerChunkCache.h"
#include "../Minecraft.World/LevelChunk.h"
#include "../Minecraft.World/Biome.h"
#include "../Minecraft.World/LightLayer.h"
#include "../Minecraft.Client/Windows64/Network/WinsockNetLayer.h"
#include "../Minecraft.World/AbstractContainerMenu.h"
#include "../Minecraft.World/AddGlobalEntityPacket.h"
@ -29,6 +34,7 @@
#include "../Minecraft.World/Player.h"
#include "../Minecraft.World/PlayerAbilitiesPacket.h"
#include "../Minecraft.World/SetCarriedItemPacket.h"
#include "../Minecraft.World/BlockRegionUpdatePacket.h"
#include "../Minecraft.World/SetExperiencePacket.h"
#include "../Minecraft.World/SetHealthPacket.h"
#include "../Minecraft.World/LevelSoundPacket.h"
@ -47,6 +53,8 @@
namespace
{
std::atomic<uint32_t> g_handlerMask{0};
static shared_ptr<ServerPlayer> FindPlayer(int entityId)
{
PlayerList *list = MinecraftServer::getPlayerList();
@ -105,6 +113,24 @@ class VirtualContainer : public SimpleContainer
namespace FourKitBridge
{
void __cdecl NativeSetHandlerMask(uint32_t mask)
{
g_handlerMask.store(mask, std::memory_order_release);
}
bool HasHandlers(int kind)
{
if (kind < 0 || kind >= 32) return false;
return (g_handlerMask.load(std::memory_order_acquire) & (1u << kind)) != 0;
}
int __cdecl NativeGetServerTickCount()
{
MinecraftServer *srv = MinecraftServer::getInstance();
return srv ? srv->tickCount : 0;
}
void __cdecl NativeDamagePlayer(int entityId, float amount)
{
auto player = FindPlayer(entityId);
@ -305,20 +331,20 @@ int __cdecl NativeGetTileData(int dimId, int x, int y, int z)
return level->getData(x, y, z);
}
void __cdecl NativeSetTile(int dimId, int x, int y, int z, int tileId, int data)
void __cdecl NativeSetTile(int dimId, int x, int y, int z, int tileId, int data, int flags)
{
ServerLevel *level = GetLevel(dimId);
if (!level)
return;
level->setTileAndData(x, y, z, tileId, data, Tile::UPDATE_ALL);
level->setTileAndData(x, y, z, tileId, data, flags);
}
void __cdecl NativeSetTileData(int dimId, int x, int y, int z, int data)
void __cdecl NativeSetTileData(int dimId, int x, int y, int z, int data, int flags)
{
ServerLevel *level = GetLevel(dimId);
if (!level)
return;
level->setData(x, y, z, data, Tile::UPDATE_ALL);
level->setData(x, y, z, data, flags);
}
int __cdecl NativeBreakBlock(int dimId, int x, int y, int z)
@ -634,6 +660,9 @@ int __cdecl NativeSendRaw(int entityId, unsigned char *bufferData, int bufferSiz
void WriteInventoryItemData(std::shared_ptr<ItemInstance> item, int index, int* outBuffer) {
if (item) {
//ItemFlags Key:
// 0x1 = hasMetadata (has data that needs to be gotten from "ReadMetaFromNative")
uint8_t itemFlags = 0;
if (item->getTag() == nullptr) goto doneWithMetadataFlag;
CompoundTag* itemTag = item->getTag();
@ -642,7 +671,7 @@ void WriteInventoryItemData(std::shared_ptr<ItemInstance> item, int index, int*
itemFlags |= 0x1;
goto doneWithMetadataFlag;
}
else {
else { //we just want to check one tag for metadata and return for this flag, not all of them
CompoundTag* displayTag = itemTag->getCompound(L"display");
if (displayTag->contains(L"Name") || displayTag->contains(L"Lore")) {
itemFlags |= 0x1;
@ -650,7 +679,9 @@ void WriteInventoryItemData(std::shared_ptr<ItemInstance> item, int index, int*
}
}
doneWithMetadataFlag:
outBuffer[(index * 3) + 0] = item->id;
outBuffer[(index * 3) + 1] = item->getAuxValue();
outBuffer[(index * 3) + 2] = (((int)itemFlags << 24) | ((int)item->count << 8));
@ -659,6 +690,9 @@ void WriteInventoryItemData(std::shared_ptr<ItemInstance> item, int index, int*
void __cdecl NativeGetPlayerInventory(int entityId, int *outData)
{
// 9 slots per row, 3 slots in the inventory and the hotbar, 4 armor slots, 1 hand slot
// (((slotsPerRow * Rows) + ArmorSlots) * AmountOfIntsPerSlot) + hand slot
// (((9 * 4) + 4) * 3) + 1 = 121
memset(outData, 0, 121 * sizeof(int));
auto player = FindPlayer(entityId);
@ -799,7 +833,8 @@ void __cdecl NativeOpenVirtualContainer(int entityId, int nativeType, const char
player->openContainer(container);
}
//didnt update this for enchants
// [nameLen:int32][nameUTF8:bytes][loreCount:int32][lore0Len:int32][lore0UTF8:bytes]
int __cdecl NativeGetItemMeta(int entityId, int slot, char *outBuf, int bufSize)
{
auto player = FindPlayer(entityId);
@ -815,15 +850,14 @@ int __cdecl NativeGetItemMeta(int entityId, int slot, char *outBuf, int bufSize)
return 0;
CompoundTag *tag = item->getTag();
if (!tag || !tag->contains(L"display"))
return 0;
CompoundTag *display = tag->getCompound(L"display");
bool hasName = display->contains(L"Name");
bool hasLore = display->contains(L"Lore");
bool hasEnchantments = item->isEnchanted();
if (!hasName && !hasLore)
CompoundTag *display = (tag && tag->contains(L"display")) ? tag->getCompound(L"display") : nullptr;
bool hasName = display && display->contains(L"Name");
bool hasLore = display && display->contains(L"Lore");
if (!hasName && !hasLore && !hasEnchantments)
return 0;
int offset = 0;
@ -879,14 +913,18 @@ int __cdecl NativeGetItemMeta(int entityId, int slot, char *outBuf, int bufSize)
ListTag<CompoundTag>* list = item->getEnchantmentTags();
if (list != nullptr) {
int listSize = list->size();
if ((offset + 4 + (listSize * (4 + 4))) > bufSize) return 0;
memcpy(outBuf + offset, &listSize, 4);
offset += 4;
for (int i = 0; i < listSize; i++) {
int type = list->get(i)->getShort((wchar_t*)ItemInstance::TAG_ENCH_ID);
int level = list->get(i)->getShort((wchar_t*)ItemInstance::TAG_ENCH_LEVEL);
memcpy(outBuf + offset, &type, 4);
offset += 4;
memcpy(outBuf + offset, &level, 4);
offset += 4;
}
@ -933,8 +971,11 @@ void __cdecl NativeSetItemMeta(int entityId, int slot, const char *inBuf, int bu
item->setTag(nullptr);
}
}
if (tag && tag->contains(L"ench"))
{
tag->remove(L"ench");
}
}
return;
}
@ -1014,12 +1055,15 @@ void __cdecl NativeSetItemMeta(int entityId, int slot, const char *inBuf, int bu
for (int i = 0; i < enchantCount; i++) {
if (offset + (4 + 4) > bufSize) break;
int type = 0;
memcpy(&type, inBuf + offset, 4);
offset += 4;
int level = 0;
memcpy(&level, inBuf + offset, 4);
offset += 4;
CompoundTag* ench = new CompoundTag();
ench->putShort((wchar_t*)ItemInstance::TAG_ENCH_ID, static_cast<short>(type));
ench->putShort((wchar_t*)ItemInstance::TAG_ENCH_LEVEL, static_cast<byte>(level));
@ -1032,7 +1076,9 @@ void __cdecl NativeSetItemMeta(int entityId, int slot, const char *inBuf, int bu
{
CompoundTag* tag = item->getTag();
if (tag && tag->contains(L"ench"))
{
tag->remove(L"ench");
}
}
}
}
@ -1047,6 +1093,68 @@ void __cdecl NativeSetHeldItemSlot(int entityId, int slot)
player->connection->queueSend(std::make_shared<SetCarriedItemPacket>(slot));
}
void __cdecl NativeGetCarriedItem(int entityId, int *outData)
{
outData[0] = 0;
outData[1] = 0;
outData[2] = 0;
auto player = FindPlayer(entityId);
if (!player || !player->inventory)
return;
auto item = player->inventory->getCarried();
if (item)
{
outData[0] = item->id;
outData[1] = item->getAuxValue();
outData[2] = (int)item->count;
}
}
void __cdecl NativeSetCarriedItem(int entityId, int itemId, int count, int aux)
{
auto player = FindPlayer(entityId);
if (!player || !player->inventory)
return;
if (itemId <= 0 || count <= 0)
player->inventory->setCarried(nullptr);
else
player->inventory->setCarried(std::make_shared<ItemInstance>(itemId, count, aux));
}
void __cdecl NativeGetEnderChestContents(int entityId, int *outData)
{
memset(outData, 0, 27 * 3 * sizeof(int));
auto player = FindPlayer(entityId);
if (!player)
return;
auto ec = player->getEnderChestInventory();
if (!ec)
return;
unsigned int size = ec->getContainerSize();
if (size > 27)
size = 27;
for (unsigned int i = 0; i < size; i++)
{
WriteInventoryItemData(ec->getItem(i), i, outData);
}
}
void __cdecl NativeSetEnderChestSlot(int entityId, int slot, int itemId, int count, int aux)
{
auto player = FindPlayer(entityId);
if (!player)
return;
auto ec = player->getEnderChestInventory();
if (!ec)
return;
if (slot < 0 || slot >= (int)ec->getContainerSize())
return;
if (itemId <= 0 || count <= 0)
ec->setItem(slot, nullptr);
else
ec->setItem(slot, std::make_shared<ItemInstance>(itemId, count, aux));
}
void __cdecl NativeSetSneaking(int entityId, int sneak)
{
auto player = FindPlayer(entityId);
@ -1169,7 +1277,6 @@ void __cdecl NativeSetExhaustion(int entityId, float exhaustion)
fd->setExhaustion(exhaustion);
}
void __cdecl NativeSpawnParticle(int entityId, int particleId, float x, float y, float z, float offsetX, float offsetY, float offsetZ, float speed, int count)
{
auto player = FindPlayer(entityId);
@ -1249,4 +1356,284 @@ void __cdecl NativeGetEntityInfo(int entityId, double *outData)
outData[4] = (double)entity->dimension;
}
int __cdecl NativeGetWorldEntities(int dimId, int **outBuf)
{
*outBuf = nullptr;
ServerLevel *level = GetLevel(dimId);
if (!level)
return 0;
EnterCriticalSection(&level->m_entitiesCS);
int total = (int)level->entities.size();
int *buf = (int *)CoTaskMemAlloc(total * 3 * sizeof(int));
int count = 0;
if (buf)
{
for (auto &entity : level->entities)
{
if (!entity)
continue;
int idx = count * 3;
buf[idx] = entity->entityId;
buf[idx + 1] = MapEntityType((int)entity->GetType());
buf[idx + 2] = entity->instanceof(eTYPE_LIVINGENTITY) ? 1 : 0;
count++;
}
}
LeaveCriticalSection(&level->m_entitiesCS);
*outBuf = buf;
return count;
}
int __cdecl NativeGetChunkEntities(int dimId, int chunkX, int chunkZ, int **outBuf)
{
*outBuf = nullptr;
ServerLevel *level = GetLevel(dimId);
if (!level)
return 0;
EnterCriticalSection(&level->m_entitiesCS);
int total = (int)level->entities.size();
int *buf = (int *)CoTaskMemAlloc(total * 3 * sizeof(int));
int count = 0;
if (buf)
{
for (auto &entity : level->entities)
{
if (!entity)
continue;
int ecx = Mth::floor(entity->x / 16.0);
int ecz = Mth::floor(entity->z / 16.0);
if (ecx != chunkX || ecz != chunkZ)
continue;
int idx = count * 3;
buf[idx] = entity->entityId;
buf[idx + 1] = MapEntityType((int)entity->GetType());
buf[idx + 2] = entity->instanceof(eTYPE_LIVINGENTITY) ? 1 : 0;
count++;
}
}
LeaveCriticalSection(&level->m_entitiesCS);
*outBuf = buf;
return count;
}
int __cdecl NativeIsChunkLoaded(int dimId, int chunkX, int chunkZ)
{
ServerLevel *level = GetLevel(dimId);
if (!level || !level->cache)
return 0;
return level->cache->hasChunk(chunkX, chunkZ) ? 1 : 0;
}
int __cdecl NativeLoadChunk(int dimId, int chunkX, int chunkZ, int generate)
{
ServerLevel *level = GetLevel(dimId);
if (!level || !level->cache)
return 0;
LevelChunk *chunk = level->cache->create(chunkX, chunkZ);
return (chunk != nullptr) ? 1 : 0;
}
int __cdecl NativeUnloadChunk(int dimId, int chunkX, int chunkZ, int save, int safe)
{
ServerLevel *level = GetLevel(dimId);
if (!level || !level->cache)
return 0;
if (safe)
{
if (!level->cache->hasChunk(chunkX, chunkZ))
return 0;
LevelChunk *chunk = level->cache->getChunk(chunkX, chunkZ);
if (chunk && chunk->containsPlayer())
return 0;
}
level->cache->drop(chunkX, chunkZ);
return 1;
}
int __cdecl NativeGetLoadedChunks(int dimId, int **coordBuf)
{
// wow gay
*coordBuf = nullptr;
ServerLevel *level = GetLevel(dimId);
if (!level || !level->cache)
return 0;
std::vector<LevelChunk *> *list = level->cache->getLoadedChunkList();
if (!list)
return 0;
int total = (int)list->size();
int *buf = (int *)CoTaskMemAlloc(total * 2 * sizeof(int));
int count = 0;
if (buf)
{
for (auto *chunk : *list)
{
if (chunk)
{
buf[count * 2] = chunk->x;
buf[count * 2 + 1] = chunk->z;
count++;
}
}
}
*coordBuf = buf;
return count;
}
int __cdecl NativeIsChunkInUse(int dimId, int chunkX, int chunkZ)
{
PlayerList *list = MinecraftServer::getPlayerList();
if (!list)
return 0;
for (auto &p : list->players)
{
if (p && p->dimension == dimId)
{
int px = (int)floor(p->x) >> 4;
int pz = (int)floor(p->z) >> 4;
if (px == chunkX && pz == chunkZ)
return 1;
}
}
return 0;
}
void __cdecl NativeGetChunkSnapshot(int dimId, int chunkX, int chunkZ, int *blockIds, int *blockData, int *maxBlockY)
{
ServerLevel *level = GetLevel(dimId);
if (!level || !level->cache)
{
memset(blockIds, 0, 16 * 128 * 16 * sizeof(int));
memset(blockData, 0, 16 * 128 * 16 * sizeof(int));
memset(maxBlockY, 0, 16 * 16 * sizeof(int));
return;
}
if (!level->cache->hasChunk(chunkX, chunkZ))
{
memset(blockIds, 0, 16 * 128 * 16 * sizeof(int));
memset(blockData, 0, 16 * 128 * 16 * sizeof(int));
memset(maxBlockY, 0, 16 * 16 * sizeof(int));
return;
}
LevelChunk *chunk = level->cache->getChunk(chunkX, chunkZ);
if (!chunk)
{
memset(blockIds, 0, 16 * 128 * 16 * sizeof(int));
memset(blockData, 0, 16 * 128 * 16 * sizeof(int));
memset(maxBlockY, 0, 16 * 16 * sizeof(int));
return;
}
for (int lx = 0; lx < 16; lx++)
{
for (int lz = 0; lz < 16; lz++)
{
int highest = 0;
for (int ly = 0; ly < 128; ly++)
{
int idx = (lx * 128 * 16) + (ly * 16) + lz;
blockIds[idx] = chunk->getTile(lx, ly, lz);
blockData[idx] = chunk->getData(lx, ly, lz);
if (blockIds[idx] != 0)
highest = ly;
}
maxBlockY[lx * 16 + lz] = highest;
}
}
}
int __cdecl NativeUnloadChunkRequest(int dimId, int chunkX, int chunkZ, int safe)
{
ServerLevel *level = GetLevel(dimId);
if (!level || !level->cache)
return 0;
if (safe)
{
if (!level->cache->hasChunk(chunkX, chunkZ))
return 0;
LevelChunk *chunk = level->cache->getChunk(chunkX, chunkZ);
if (chunk && chunk->containsPlayer())
return 0;
}
level->cache->drop(chunkX, chunkZ);
return 1;
}
int __cdecl NativeRegenerateChunk(int dimId, int chunkX, int chunkZ)
{
ServerLevel *level = GetLevel(dimId);
if (!level || !level->cache)
return 0;
level->cache->regenerateChunk(chunkX, chunkZ);
return 1;
}
int __cdecl NativeRefreshChunk(int dimId, int chunkX, int chunkZ)
{
ServerLevel *level = GetLevel(dimId);
if (!level)
return 0;
PlayerList *list = MinecraftServer::getPlayerList();
if (!list)
return 0;
auto packet = std::make_shared<BlockRegionUpdatePacket>(chunkX * 16, 0, chunkZ * 16, 16, Level::maxBuildHeight, 16, level);
for (auto &p : list->players)
{
if (!p || p->dimension != dimId || !p->connection || p->connection->isLocal())
continue;
p->connection->send(packet);
}
return 1;
}
int __cdecl NativeGetSkyLight(int dimId, int x, int y, int z)
{
ServerLevel *level = GetLevel(dimId);
if (!level)
return 0;
return level->getBrightness(LightLayer::Sky, x, y, z);
}
int __cdecl NativeGetBlockLight(int dimId, int x, int y, int z)
{
ServerLevel *level = GetLevel(dimId);
if (!level)
return 0;
return level->getBrightness(LightLayer::Block, x, y, z);
}
int __cdecl NativeGetBiomeId(int dimId, int x, int z)
{
ServerLevel *level = GetLevel(dimId);
if (!level)
return 1;
Biome *biome = level->getBiome(x, z);
return biome ? biome->id : 1;
}
void __cdecl NativeSetBiomeId(int dimId, int x, int z, int biomeId)
{
ServerLevel *level = GetLevel(dimId);
if (!level)
return;
LevelChunk *chunk = level->getChunk(x >> 4, z >> 4);
if (!chunk)
return;
byteArray biomes = chunk->getBiomes();
if (biomes.data == nullptr)
return;
int lx = x & 0xf;
int lz = z & 0xf;
biomes.data[(lz << 4) | lx] = static_cast<unsigned char>(biomeId & 0xff);
}
} // namespace FourKitBridge

View file

@ -1,8 +1,21 @@
#pragma once
#include <cstdint>
namespace FourKitBridge
{
// Must match HandlerKind in FourKit.cs.
enum HandlerKind : int {
kHandlerKind_ChunkLoad = 0,
kHandlerKind_ChunkUnload = 1,
kHandlerKind_PlayerMove = 2,
};
void __cdecl NativeSetHandlerMask(uint32_t mask);
bool HasHandlers(int kind);
int __cdecl NativeGetServerTickCount();
// core
void __cdecl NativeDamagePlayer(int entityId, float amount);
void __cdecl NativeSetPlayerHealth(int entityId, float health);
@ -18,8 +31,8 @@ namespace FourKitBridge
// World
int __cdecl NativeGetTileId(int dimId, int x, int y, int z);
int __cdecl NativeGetTileData(int dimId, int x, int y, int z);
void __cdecl NativeSetTile(int dimId, int x, int y, int z, int tileId, int data);
void __cdecl NativeSetTileData(int dimId, int x, int y, int z, int data);
void __cdecl NativeSetTile(int dimId, int x, int y, int z, int tileId, int data, int flags);
void __cdecl NativeSetTileData(int dimId, int x, int y, int z, int data, int flags);
int __cdecl NativeBreakBlock(int dimId, int x, int y, int z);
int __cdecl NativeGetHighestBlockY(int dimId, int x, int z);
void __cdecl NativeGetWorldInfo(int dimId, double *outBuf);
@ -52,6 +65,12 @@ namespace FourKitBridge
void __cdecl NativeSetItemMeta(int entityId, int slot, const char *inBuf, int bufSize);
void __cdecl NativeSetHeldItemSlot(int entityId, int slot);
// carried item (cursor) & ender chest
void __cdecl NativeGetCarriedItem(int entityId, int *outData);
void __cdecl NativeSetCarriedItem(int entityId, int itemId, int count, int aux);
void __cdecl NativeGetEnderChestContents(int entityId, int *outData);
void __cdecl NativeSetEnderChestSlot(int entityId, int slot, int itemId, int count, int aux);
// ent
void __cdecl NativeSetSneaking(int entityId, int sneak);
void __cdecl NativeSetVelocity(int entityId, double x, double y, double z);
@ -78,4 +97,25 @@ namespace FourKitBridge
int __cdecl NativeGetVehicleId(int entityId);
int __cdecl NativeGetPassengerId(int entityId);
void __cdecl NativeGetEntityInfo(int entityId, double *outData);
// chunk
int __cdecl NativeIsChunkLoaded(int dimId, int chunkX, int chunkZ);
int __cdecl NativeLoadChunk(int dimId, int chunkX, int chunkZ, int generate);
int __cdecl NativeUnloadChunk(int dimId, int chunkX, int chunkZ, int save, int safe);
int __cdecl NativeGetLoadedChunks(int dimId, int **coordBuf);
int __cdecl NativeIsChunkInUse(int dimId, int chunkX, int chunkZ);
void __cdecl NativeGetChunkSnapshot(int dimId, int chunkX, int chunkZ, int *blockIds, int *blockData, int *maxBlockY);
int __cdecl NativeUnloadChunkRequest(int dimId, int chunkX, int chunkZ, int safe);
int __cdecl NativeRegenerateChunk(int dimId, int chunkX, int chunkZ);
int __cdecl NativeRefreshChunk(int dimId, int chunkX, int chunkZ);
// world entity bs
int __cdecl NativeGetWorldEntities(int dimId, int **outBuf);
int __cdecl NativeGetChunkEntities(int dimId, int chunkX, int chunkZ, int **outBuf);
// block info (light, biome)
int __cdecl NativeGetSkyLight(int dimId, int x, int y, int z);
int __cdecl NativeGetBlockLight(int dimId, int x, int y, int z);
int __cdecl NativeGetBiomeId(int dimId, int x, int z);
void __cdecl NativeSetBiomeId(int dimId, int x, int z, int biomeId);
}

View file

@ -15,6 +15,8 @@ static volatile LONG g_minLogLevel = (LONG)eServerLogLevel_Info;
static FILE *g_logFile = NULL;
static std::once_flag g_logFileOnce;
bool g_serverPerfTrace = false;
static void OpenLogFile()
{
if (g_logFile != NULL)

View file

@ -41,4 +41,9 @@ namespace ServerRuntime
void LogStartupStep(const char *message);
void LogWorldIO(const char *message);
void LogWorldName(const char *prefix, const std::wstring &name);
// When true, noisy [perf] sampling output (histograms, per-iter samples)
// is enabled. Threshold-fired warnings remain always-on. Toggled via the
// -perftrace CLI flag.
extern bool g_serverPerfTrace;
}

View file

@ -888,6 +888,14 @@ ServerPropertiesConfig LoadServerPropertiesConfig()
config.hardcore = ReadNormalizedBoolProperty(&merged, "hardcore", false, &shouldWrite);
config.hardcoreBanIp = ReadNormalizedBoolProperty(&merged, "hardcore-ban-ip", false, &shouldWrite);
config.maxMonsters = ReadNormalizedIntProperty(&merged, "max-monsters", 50, 0, 1000, &shouldWrite);
config.maxAnimals = ReadNormalizedIntProperty(&merged, "max-animals", 50, 0, 1000, &shouldWrite);
config.maxAmbient = ReadNormalizedIntProperty(&merged, "max-ambient", 20, 0, 1000, &shouldWrite);
config.maxWaterAnimals = ReadNormalizedIntProperty(&merged, "max-water-animals", 5, 0, 1000, &shouldWrite);
config.maxWolves = ReadNormalizedIntProperty(&merged, "max-wolves", 8, 0, 1000, &shouldWrite);
config.maxChickens = ReadNormalizedIntProperty(&merged, "max-chickens", 8, 0, 1000, &shouldWrite);
config.maxMushroomCows = ReadNormalizedIntProperty(&merged, "max-mushroom-cows", 2, 0, 1000, &shouldWrite);
config.maxBuildHeight = ReadNormalizedIntProperty(&merged, "max-build-height", 256, 64, 256, &shouldWrite);
config.motd = ReadNormalizedStringProperty(&merged, "motd", "A Minecraft Server", 255, &shouldWrite);

View file

@ -80,6 +80,21 @@ namespace ServerRuntime
/** `hardcore-ban-ip` — whether hardcore death bans include IP bans */
bool hardcoreBanIp;
/** `max-monsters` natural spawn cap for monsters (zombies, skeletons, creepers, etc.) */
int maxMonsters;
/** `max-animals` natural spawn cap for animals (cows, sheep, pigs) */
int maxAnimals;
/** `max-ambient` natural spawn cap for ambient mobs (bats) */
int maxAmbient;
/** `max-water-animals` natural spawn cap for water mobs (squid) */
int maxWaterAnimals;
/** `max-wolves` natural spawn cap for wolves */
int maxWolves;
/** `max-chickens` natural spawn cap for chickens */
int maxChickens;
/** `max-mushroom-cows` natural spawn cap for mooshrooms */
int maxMushroomCows;
/** security settings */
/** `hide-player-list-prelogin` — strip XUIDs from PreLoginPacket response */
bool hidePlayerListPreLogin;

View file

@ -37,6 +37,7 @@
#include "../../Minecraft.World/ConsoleSaveFileOriginal.h"
#include "../../Minecraft.World/net.minecraft.world.level.tile.h"
#include "../../Minecraft.World/Random.h"
#include "../../Minecraft.World/MobCategory.h"
#include <stdio.h>
#include <stdlib.h>
@ -165,6 +166,7 @@ static void PrintUsage()
ServerRuntime::LogInfo("usage", " -maxplayers <1-8> Public slots (default: server.properties:max-players)");
ServerRuntime::LogInfo("usage", " -seed <int64> World seed (overrides server.properties:level-seed)");
ServerRuntime::LogInfo("usage", " -loglevel <level> debug|info|warn|error (default: server.properties:log-level)");
ServerRuntime::LogInfo("usage", " -perftrace Enable noisy [perf] sampling output (histograms, per-iter samples)");
ServerRuntime::LogInfo("usage", " -help Show this help");
}
@ -271,6 +273,10 @@ static bool ParseCommandLine(int argc, char **argv, DedicatedServerConfig *confi
return false;
}
}
else if (_stricmp(arg, "-perftrace") == 0)
{
ServerRuntime::g_serverPerfTrace = true;
}
else
{
LogErrorf("startup", "Unknown or incomplete argument: %s", arg);
@ -557,10 +563,18 @@ int main(int argc, char **argv)
{
LogError("startup", "Minecraft initialization failed.");
CleanupDevice();
return 3;
}
MobCategory::monster->setMaxInstancesPerLevel(serverProperties.maxMonsters);
MobCategory::creature->setMaxInstancesPerLevel(serverProperties.maxAnimals);
MobCategory::ambient->setMaxInstancesPerLevel(serverProperties.maxAmbient);
MobCategory::waterCreature->setMaxInstancesPerLevel(serverProperties.maxWaterAnimals);
MobCategory::creature_wolf->setMaxInstancesPerLevel(serverProperties.maxWolves);
MobCategory::creature_chicken->setMaxInstancesPerLevel(serverProperties.maxChickens);
MobCategory::creature_mushroomcow->setMaxInstancesPerLevel(serverProperties.maxMushroomCows);
app.InitGameSettings();
MinecraftServer::resetFlags();
@ -737,7 +751,7 @@ int main(int argc, char **argv)
break;
}
if (autosaveRequested && app.GetXuiServerAction(kServerActionPad) == eXuiServerAction_Idle)
if (autosaveRequested && app.GetXuiServerAction(kServerActionPad) == eXuiServerAction_Idle && !ConsoleSaveFileOriginal::hasPendingBackgroundSave())
{
LogWorldIO("autosave completed");
autosaveRequested = false;
@ -749,7 +763,7 @@ int main(int argc, char **argv)
}
DWORD now = GetTickCount();
if ((LONG)(now - nextAutosaveTick) >= 0)
if ((LONG)(now - nextAutosaveTick) >= 0 && !IsShutdownRequested() && !app.m_bShutdown)
{
if (app.GetXuiServerAction(kServerActionPad) == eXuiServerAction_Idle)
{
@ -772,12 +786,28 @@ int main(int argc, char **argv)
MinecraftServer *server = MinecraftServer::getInstance();
if (server != NULL)
{
// Drain any in-flight autosave before requesting the exit save so the
// async autosave can't overwrite the exit save with an older snapshot,
// and so m_saveOnExit gets set (prior logic skipped it when a save was
// pending, causing silent data loss on restart).
if (ConsoleSaveFileOriginal::hasPendingBackgroundSave())
{
LogWorldIO("Draining pending autosave before exit save...");
const DWORD kDrainTimeoutMs = 30000;
DWORD drainStart = GetTickCount();
while (ConsoleSaveFileOriginal::hasPendingBackgroundSave())
{
if ((LONG)(GetTickCount() - drainStart) > (LONG)kDrainTimeoutMs)
{
LogWorldIO("Autosave drain timed out; continuing with exit save");
break;
}
TickCoreSystems();
Sleep(10);
}
}
server->setSaveOnExit(true);
}
if (server != NULL)
{
LogWorldIO("requesting save before shutdown");
LogWorldIO("using saveOnExit for shutdown");
LogWorldIO("requesting exit save");
}
MinecraftServer::HaltServer();

View file

@ -416,6 +416,7 @@ set(_MINECRAFT_SERVER_COMMON_ROOT
"${_MS_SRC}/../Minecraft.Client/ScrolledSelectionList.cpp"
"${_MS_SRC}/../Minecraft.Client/SelectWorldScreen.cpp"
"${_MS_SRC}/../Minecraft.Client/ServerChunkCache.cpp"
"${_MS_SRC}/../Minecraft.Client/ServerChunkCache.h"
"${_MS_SRC}/../Minecraft.Client/ServerCommandDispatcher.cpp"
"${_MS_SRC}/../Minecraft.Client/ServerConnection.cpp"
"${_MS_SRC}/../Minecraft.Client/ServerLevel.cpp"

View file

@ -244,7 +244,7 @@ void ArmorItem::setColor(shared_ptr<ItemInstance> item, int color)
{
#ifndef _CONTENT_PACKAGE
printf("Can't dye non-leather!");
__debugbreak();
DEBUG_BREAK();
#endif
//throw new UnsupportedOperationException("Can't dye non-leather!");
}

View file

@ -27,8 +27,8 @@ void BiomeDecorator::decorate(Level *level, Random *random, int xo, int zo)
{
app.DebugPrintf("BiomeDecorator::decorate - Already decorating!!\n");
#ifndef _CONTENT_PACKAGE
__debugbreak();
//throw new RuntimeException("Already decorating!!");
DEBUG_BREAK();
//throw new RuntimeException("Already decorating!!");
#endif
}
this->level = level;

View file

@ -32,7 +32,7 @@ BiomeOverrideLayer::BiomeOverrideLayer(int seedMixup) : Layer(seedMixup)
{
#ifdef _DURANGO
__debugbreak(); // TODO
DEBUG_BREAK(); // TODO
DWORD bytesRead,dwFileSize = 0;
#else
DWORD bytesRead,dwFileSize = GetFileSize(file,nullptr);
@ -40,7 +40,7 @@ BiomeOverrideLayer::BiomeOverrideLayer(int seedMixup) : Layer(seedMixup)
if(dwFileSize > m_biomeOverride.length)
{
app.DebugPrintf("Biomemap binary is too large!!\n");
__debugbreak();
DEBUG_BREAK();
}
BOOL bSuccess = ReadFile(file,m_biomeOverride.data,dwFileSize,&bytesRead,nullptr);

View file

@ -180,11 +180,11 @@ void BiomeSource::getRawBiomeBlock(BiomeArray &biomes, int x, int z, int w, int
{
biomes[i] = Biome::biomes[result[i]];
#ifndef _CONTENT_PACKAGE
if(biomes[i] == nullptr)
{
app.DebugPrintf("Tried to assign null biome %d\n", result[i]);
__debugbreak();
}
if(biomes[i] == nullptr)
{
app.DebugPrintf("Tried to assign null biome %d\n", result[i]);
DEBUG_BREAK();
}
#endif
}
}

View file

@ -1,4 +1,5 @@
#pragma once
#include "Debug.h"
using namespace std;
class InputStream;
@ -611,7 +612,7 @@ public:
if ( (m_falsePositives.size() > 0) || (m_falseNegatives.size() > 0) )
{
__debugbreak();
DEBUG_BREAK();
}
}
};

View file

@ -37,7 +37,7 @@ public:
{
#ifndef _CONTENT_PACKAGE
printf("Tried to read NBT tag with too high complexity, depth > %d" , MAX_DEPTH);
__debugbreak();
DEBUG_BREAK();
#endif
return;
}

View file

@ -1065,7 +1065,7 @@ void CompressedTileStorage::compress(int upgradeBlock/*=-1*/)
#ifndef _DURANGO
MEMORYSTATUS memStatus;
GlobalMemoryStatus(&memStatus);
__debugbreak();
DEBUG_BREAK();
#endif
}
unsigned char *pucData = newIndicesAndData + 1024;

View file

@ -90,7 +90,7 @@ ConsoleSaveFileOriginal::ConsoleSaveFileOriginal(const wstring &fileName, LPVOID
if( pagesCommitted != 0 )
{
#ifndef _CONTENT_PACKAGE
__debugbreak();
DEBUG_BREAK();
#endif
}
@ -101,7 +101,7 @@ ConsoleSaveFileOriginal::ConsoleSaveFileOriginal(const wstring &fileName, LPVOID
{
#ifndef _CONTENT_PACKAGE
// Out of physical memory
__debugbreak();
DEBUG_BREAK();
#endif
}
pagesCommitted = pagesRequired;
@ -206,7 +206,7 @@ ConsoleSaveFileOriginal::ConsoleSaveFileOriginal(const wstring &fileName, LPVOID
if( pvRet == nullptr )
{
// Out of physical memory
__debugbreak();
DEBUG_BREAK();
}
pagesCommitted = pagesRequired;
}
@ -503,7 +503,7 @@ void ConsoleSaveFileOriginal::finalizeWrite()
void *pvRet = VirtualAlloc(pvHeap, pagesRequired * CSF_PAGE_SIZE, COMMIT_ALLOCATION, PAGE_READWRITE);
if( pvRet == NULL )
{
__debugbreak();
DEBUG_BREAK();
}
pagesCommitted = pagesRequired;
}
@ -537,7 +537,7 @@ void ConsoleSaveFileOriginal::MoveDataBeyond(FileEntry *file, DWORD nNumberOfByt
if( pvRet == nullptr )
{
// Out of physical memory
__debugbreak();
DEBUG_BREAK();
}
pagesCommitted = pagesRequired;
}
@ -739,6 +739,7 @@ void ConsoleSaveFileOriginal::Flush(bool autosave, bool updateThumbnail )
s_bgSaveActive.store(true, std::memory_order_release);
std::thread([snap, fileSize, thumb, thumbSz, meta, metaLen, this]() {
Compression::UseDefaultThreadStorage();
unsigned int compLen = fileSize + 8;
byte *buf = static_cast<byte *>(StorageManager.AllocateSaveData(compLen));
if (!buf)

View file

@ -41,7 +41,7 @@ CustomLevelSource::CustomLevelSource(Level *level, int64_t seed, bool generateSt
{
#ifdef _DURANGO
__debugbreak(); // TODO
DEBUG_BREAK(); // TODO
DWORD bytesRead,dwFileSize = 0;
#else
DWORD bytesRead,dwFileSize = GetFileSize(file,nullptr);
@ -49,7 +49,7 @@ CustomLevelSource::CustomLevelSource(Level *level, int64_t seed, bool generateSt
if(dwFileSize > m_heightmapOverride.length)
{
app.DebugPrintf("Heightmap binary is too large!!\n");
__debugbreak();
DEBUG_BREAK();
}
BOOL bSuccess = ReadFile(file,m_heightmapOverride.data,dwFileSize,&bytesRead,nullptr);
@ -83,7 +83,7 @@ CustomLevelSource::CustomLevelSource(Level *level, int64_t seed, bool generateSt
{
#ifdef _DURANGO
__debugbreak(); // TODO
DEBUG_BREAK(); // TODO
DWORD bytesRead,dwFileSize = 0;
#else
DWORD bytesRead,dwFileSize = GetFileSize(file,nullptr);
@ -91,7 +91,7 @@ CustomLevelSource::CustomLevelSource(Level *level, int64_t seed, bool generateSt
if(dwFileSize > m_waterheightOverride.length)
{
app.DebugPrintf("waterheight binary is too large!!\n");
__debugbreak();
DEBUG_BREAK();
}
BOOL bSuccess = ReadFile(file,m_waterheightOverride.data,dwFileSize,&bytesRead,nullptr);

View file

@ -49,7 +49,7 @@ CustomPayloadPacket::CustomPayloadPacket(const wstring &identifier, byteArray da
{
app.DebugPrintf("Payload may not be larger than 32K\n");
#ifndef _CONTENT_PACKAGE
__debugbreak();
DEBUG_BREAK();
#endif
//throw new IllegalArgumentException("Payload may not be larger than 32k");
}

12
Minecraft.World/Debug.h Normal file
View file

@ -0,0 +1,12 @@
#pragma once
#include <csignal>
#if defined(_MSC_VER)
#define DEBUG_BREAK() __debugbreak()
#elif defined(__GNUC__) || defined(__clang__)
#define DEBUG_BREAK() __builtin_trap()
#elif defined(SIGTRAP)
#define DEBUG_BREAK() std::raise(SIGTRAP)
#else
#define DEBUG_BREAK() ((void)0)
#endif

View file

@ -44,7 +44,7 @@ int _MapDataMappings::getDimension(int id)
default:
#ifndef _CONTENT_PACKAGE
printf("Read invalid dimension from MapDataMapping\n");
__debugbreak();
DEBUG_BREAK();
#endif
break;
}
@ -73,7 +73,7 @@ void _MapDataMappings::setMapping(int id, PlayerUID xuid, int dimension)
default:
#ifndef _CONTENT_PACKAGE
printf("Trinyg to set a MapDataMapping for an invalid dimension.\n");
__debugbreak();
DEBUG_BREAK();
#endif
break;
}

View file

@ -94,7 +94,7 @@ void Enchantment::_init(int id)
{
app.DebugPrintf("Duplicate enchantment id!");
#ifndef _CONTENT_PACKAGE
__debugbreak();
DEBUG_BREAK();
#endif
//throw new IllegalArgumentException("Duplicate enchantment id!");
}

View file

@ -111,7 +111,7 @@ int Entity::getSmallId()
return fallbackId;
#else
app.DebugPrintf("Out of small entity Ids... possible leak?\n");
__debugbreak();
DEBUG_BREAK();
return -1;
#endif
}

View file

@ -239,7 +239,7 @@ bool File::renameTo(File dest)
std::string sourcePath = wstringtofilename(getPath());
const char *destPath = wstringtofilename(dest.getPath());
#ifdef _DURANGO
__debugbreak(); // TODO
DEBUG_BREAK(); // TODO
BOOL result = false;
#else
BOOL result = MoveFile(sourcePath.c_str(), destPath);

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