Compare commits
73 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| da91372804 | |||
| 1672dbab05 | |||
| 18a08ec693 | |||
| b64b1f17db | |||
| 6db68bda06 | |||
| cb941db3ad | |||
| b441998732 | |||
| 8dd9d92ad6 | |||
| 2e0f4f91d6 | |||
| f9cac6395b | |||
| a9c735e7aa | |||
| a3221c9e14 | |||
| 84e4a77409 | |||
| 68c3440394 | |||
| 7e586e0d49 | |||
| defd9ebd00 | |||
| ffdad03aca | |||
| c297b26f36 | |||
| b4a34df1a9 | |||
| ee3cf5117b | |||
| afa3e22715 | |||
| cf81dd9d13 | |||
| fde5fbe6f4 | |||
| af39797303 | |||
| ce2efc1a4d | |||
| eb5aa9fa8f | |||
| 59f9dbc8c6 | |||
| bc6e445f0c | |||
| ec5ec84407 | |||
| 019b790fb3 | |||
| 9ddb95e7ad | |||
| 8464bfd448 | |||
| 0e0f8f696d | |||
| 6ef535d53e | |||
| 004cd72ba5 | |||
| 1a552fbd0c | |||
| cccfd860b9 | |||
| d0094a6409 | |||
| 78d4bc80cf | |||
| 6595b3820d | |||
| 7f0237a286 | |||
| d694cd1c9d | |||
| 9d92d5eb9b | |||
| 2fba264c08 | |||
| 1533b2138f | |||
| c0da06e4ee | |||
| 5133925be0 | |||
| 0544add4e2 | |||
| f78dec6e00 | |||
| 0d102b167f | |||
| 8c34855343 | |||
| 6913ce5323 | |||
| 71707fbb8c | |||
| ae9dbd837d | |||
| 825e96da54 | |||
| dea2bad0c4 | |||
| ae3c843ee5 | |||
| 6c572d4940 | |||
| 5e7ad7333e | |||
| 78afb091a4 | |||
| 2d41711055 | |||
| 14f8d793dd | |||
| bfcb621808 | |||
| 050c501786 | |||
| e5ce9a06cd | |||
| e730033bcc | |||
| 9e6e3de338 | |||
| 82c1ae1968 | |||
| c7014f6b18 | |||
| 744048f455 | |||
| 33bec8a93c | |||
| abb18e3e12 | |||
| f189234614 |
@@ -1,5 +1,5 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: MinecraftConsoles Community Discord
|
||||
url: https://discord.gg/jrum7HhegA
|
||||
- name: ❗NEW❗ MinecraftConsoles Community Discord
|
||||
url: https://discord.gg/dH8AZWGcau
|
||||
about: If you need help, please ask for it in our Discord! You will get assistance much faster there, including help getting the project to compile.
|
||||
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
|
||||
# Collect files, excluding unwanted extensions
|
||||
$files = Get-ChildItem -Path $source -Recurse -File |
|
||||
Where-Object { $_.Extension -notin '.pch', '.zip', '.ipdb', '.iobj' }
|
||||
Where-Object { $_.Extension -notin '.pch', '.pdb', '.zip', '.ipdb', '.iobj', '.exp', '.lib' }
|
||||
|
||||
Add-Type -AssemblyName System.IO.Compression
|
||||
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
||||
@@ -80,6 +80,7 @@ jobs:
|
||||
New-Item -ItemType Directory -Force -Path staging
|
||||
Copy-Item LCE-Revelations-Client-Win64.zip staging/
|
||||
Copy-Item ./build/windows64/Minecraft.Client/Release/Minecraft.Client.exe staging/
|
||||
Copy-Item ./build/windows64/Minecraft.Client/Release/Minecraft.Client.pdb staging/
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v6
|
||||
@@ -123,7 +124,7 @@ jobs:
|
||||
$topLevel = "LCE-Revelations-Server-Win64"
|
||||
|
||||
$files = Get-ChildItem -Path $source -Recurse -File |
|
||||
Where-Object { $_.Extension -notin '.pch', '.zip', '.ipdb', '.iobj' }
|
||||
Where-Object { $_.Extension -notin '.pch', '.zip', '.ipdb', '.iobj', '.exp', '.lib' }
|
||||
|
||||
Add-Type -AssemblyName System.IO.Compression
|
||||
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
||||
@@ -158,7 +159,7 @@ jobs:
|
||||
$topLevel = "LCE-Revelations-Server-Win64-FourKit"
|
||||
|
||||
$files = Get-ChildItem -Path $source -Recurse -File |
|
||||
Where-Object { $_.Extension -notin '.pch', '.zip', '.ipdb', '.iobj' }
|
||||
Where-Object { $_.Extension -notin '.pch', '.zip', '.ipdb', '.iobj', '.exp', '.lib' }
|
||||
|
||||
Add-Type -AssemblyName System.IO.Compression
|
||||
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
||||
@@ -283,6 +284,7 @@ jobs:
|
||||
subject-path: |
|
||||
artifacts/LCE-Revelations-Client-Win64.zip
|
||||
artifacts/Minecraft.Client.exe
|
||||
artifacts/Minecraft.Client.pdb
|
||||
|
||||
- name: Get short SHA
|
||||
id: sha
|
||||
@@ -321,7 +323,10 @@ jobs:
|
||||
- To play, simply run `Minecraft.Client.exe`.
|
||||
|
||||
**For those that wish to update their existing installation with the latest build:**
|
||||
- Download `Minecraft.Client.exe` and `Minecraft.Client.pdb` and copy them over to your existing LCE-Revelations-Client-Win64 build (overwrite your old version of Minecraft.Client.exe and Minecraft.Client.pdb).
|
||||
- Download `Minecraft.Client.exe` and copy it over to your existing LCE-Revelations-Client-Win64 build (overwrite your old version of Minecraft.Client.exe).
|
||||
|
||||
**For developers:**
|
||||
- `Minecraft.Client.pdb` contains debug symbols for crash analysis and development. Place it next to `Minecraft.Client.exe` for stack traces to show function names and line numbers.
|
||||
|
||||
**Steam Deck & Linux:**
|
||||
- Y'all know the drill. Download the `LCE-Revelations-Client-Win64.zip`, extract it, add the `Minecraft.Client.exe` as a "Non-Steam Game" within the Steam library, turn on compatibility mode with Proton Experimental, and then run it!
|
||||
|
||||
@@ -429,3 +429,6 @@ result-*
|
||||
.direnv/
|
||||
.xwin-cache/
|
||||
.xwin/
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
|
||||
+2
-2
@@ -39,10 +39,10 @@ function(configure_compiler_target target)
|
||||
# MSVC
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
|
||||
target_compile_options(${target} PRIVATE
|
||||
$<$<AND:$<CONFIG:Release>,$<COMPILE_LANGUAGE:C,CXX>>:/GL>
|
||||
$<$<AND:$<CONFIG:Release>,$<COMPILE_LANGUAGE:C,CXX>>:/GL /Zi>
|
||||
)
|
||||
target_link_options(${target} PRIVATE
|
||||
$<$<CONFIG:Release>:/LTCG:incremental>
|
||||
$<$<CONFIG:Release>:/LTCG:incremental /DEBUG /OPT:REF /OPT:ICF>
|
||||
)
|
||||
endif()
|
||||
|
||||
|
||||
@@ -82,8 +82,6 @@ set(ASSET_FOLDER_PAIRS
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/music" "music"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/Common/Media" "Common/Media"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/Common/res" "Common/res"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/Common/Trial" "Common/Trial"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/Common/Tutorial" "Common/Tutorial"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/${PLATFORM_NAME}Media" "${PLATFORM_NAME}Media"
|
||||
)
|
||||
setup_asset_folder_copy(Minecraft.Client "${ASSET_FOLDER_PAIRS}")
|
||||
|
||||
@@ -12,6 +12,7 @@ const wstring ChatScreen::allowedChars = SharedConstants::acceptableLetters;
|
||||
vector<wstring> ChatScreen::s_chatHistory;
|
||||
int ChatScreen::s_historyIndex = -1;
|
||||
wstring ChatScreen::s_historyDraft;
|
||||
int ChatScreen::s_chatIndex = 0;
|
||||
|
||||
bool ChatScreen::isAllowedChatChar(wchar_t c)
|
||||
{
|
||||
@@ -28,6 +29,8 @@ ChatScreen::ChatScreen()
|
||||
frame = 0;
|
||||
cursorIndex = 0;
|
||||
s_historyIndex = -1;
|
||||
|
||||
ChatScreen::s_chatIndex = 0;
|
||||
}
|
||||
|
||||
void ChatScreen::init()
|
||||
@@ -89,6 +92,20 @@ void ChatScreen::handleHistoryDown()
|
||||
applyHistoryMessage();
|
||||
}
|
||||
|
||||
int ChatScreen::getChatIndex()
|
||||
{
|
||||
return ChatScreen::s_chatIndex;
|
||||
}
|
||||
|
||||
void ChatScreen::correctChatIndex(int newChatIndex) {
|
||||
ChatScreen::s_chatIndex = newChatIndex;
|
||||
}
|
||||
|
||||
void ChatScreen::setWheelValue(int wheel) {
|
||||
ChatScreen::s_chatIndex += wheel;
|
||||
if (ChatScreen::s_chatIndex < 0) ChatScreen::s_chatIndex = 0;
|
||||
}
|
||||
|
||||
void ChatScreen::keyPressed(wchar_t ch, int eventKey)
|
||||
{
|
||||
if (eventKey == Keyboard::KEY_ESCAPE)
|
||||
@@ -140,7 +157,7 @@ void ChatScreen::keyPressed(wchar_t ch, int eventKey)
|
||||
cursorIndex--;
|
||||
return;
|
||||
}
|
||||
if (isAllowedChatChar(ch) && static_cast<int>(message.length()) < SharedConstants::maxChatLength)
|
||||
if (isAllowedChatChar(ch) && static_cast<int>(message.length()) < SharedConstants::maxVisibleLength)
|
||||
{
|
||||
message.insert(cursorIndex, 1, ch);
|
||||
cursorIndex++;
|
||||
|
||||
@@ -16,6 +16,7 @@ private:
|
||||
static std::vector<wstring> s_chatHistory;
|
||||
static int s_historyIndex;
|
||||
static wstring s_historyDraft;
|
||||
static int s_chatIndex;
|
||||
static const wstring allowedChars;
|
||||
static bool isAllowedChatChar(wchar_t c);
|
||||
|
||||
@@ -28,6 +29,9 @@ public:
|
||||
virtual void handleHistoryUp();
|
||||
virtual void handleHistoryDown();
|
||||
|
||||
static int getChatIndex();
|
||||
static void correctChatIndex(int newChatIndex);
|
||||
static void setWheelValue(int wheel);
|
||||
protected:
|
||||
void keyPressed(wchar_t ch, int eventKey);
|
||||
public:
|
||||
|
||||
@@ -1607,17 +1607,28 @@ void ClientConnection::handleChat(shared_ptr<ChatPacket> packet)
|
||||
bool replaceEntitySource = false;
|
||||
bool replaceItem = false;
|
||||
|
||||
int stringArgsSize = packet->m_stringArgs.size();
|
||||
|
||||
wstring playerDisplayName = L"";
|
||||
wstring sourceDisplayName = L"";
|
||||
|
||||
// On platforms other than Xbox One this just sets display name to gamertag
|
||||
if (packet->m_stringArgs.size() >= 1) playerDisplayName = GetDisplayNameByGamertag(packet->m_stringArgs[0]);
|
||||
if (packet->m_stringArgs.size() >= 2) sourceDisplayName = GetDisplayNameByGamertag(packet->m_stringArgs[1]);
|
||||
if (stringArgsSize >= 1) playerDisplayName = GetDisplayNameByGamertag(packet->m_stringArgs[0]);
|
||||
if (stringArgsSize >= 2) sourceDisplayName = GetDisplayNameByGamertag(packet->m_stringArgs[1]);
|
||||
|
||||
switch(packet->m_messageType)
|
||||
{
|
||||
case ChatPacket::e_ChatCustom:
|
||||
message = (packet->m_stringArgs.size() >= 1) ? packet->m_stringArgs[0] : L"";
|
||||
case ChatPacket::e_ChatActionBar:
|
||||
if (stringArgsSize >= 1) {
|
||||
message = packet->m_stringArgs[0];
|
||||
|
||||
message = app.EscapeHTMLString(message); //do this to enforce escaped string
|
||||
message = app.FormatChatMessage(message); //this needs to be last cause it converts colors to html colors that would have been escaped
|
||||
} else {
|
||||
message = L"";
|
||||
}
|
||||
displayOnGui = (packet->m_messageType == ChatPacket::e_ChatCustom);
|
||||
break;
|
||||
case ChatPacket::e_ChatBedOccupied:
|
||||
message = app.GetString(IDS_TILE_BED_OCCUPIED);
|
||||
@@ -1967,7 +1978,7 @@ void ClientConnection::handleChat(shared_ptr<ChatPacket> packet)
|
||||
|
||||
if(replacePlayer)
|
||||
{
|
||||
message = replaceAll(message,L"{*PLAYER*}",playerDisplayName);
|
||||
message = replaceAll(message,L"{*PLAYER*}", playerDisplayName);
|
||||
}
|
||||
|
||||
if(replaceEntitySource)
|
||||
@@ -2002,7 +2013,9 @@ void ClientConnection::handleChat(shared_ptr<ChatPacket> packet)
|
||||
// flag that a message is a death message
|
||||
bool bIsDeathMessage = (packet->m_messageType>=ChatPacket::e_ChatDeathInFire) && (packet->m_messageType<=ChatPacket::e_ChatDeathIndirectMagicItem);
|
||||
|
||||
if( displayOnGui ) minecraft->gui->addMessage(message,m_userIndex, bIsDeathMessage);
|
||||
if( displayOnGui ) minecraft->gui->addMessage(message, m_userIndex, bIsDeathMessage);
|
||||
|
||||
if (!displayOnGui && !message.empty()) minecraft->gui->setActionBarMessage(message);
|
||||
}
|
||||
|
||||
void ClientConnection::handleAnimate(shared_ptr<AnimatePacket> packet)
|
||||
|
||||
@@ -223,6 +223,8 @@ const WCHAR *ConsoleSoundEngine::wchSoundNames[eSoundType_MAX]=
|
||||
// 4J-PB - Some sounds were updated, but we can't do that for the 360 or we have to do a new sound bank
|
||||
// instead, we'll add the sounds as new ones and change the code to reference them
|
||||
L"fire.new_ignite",
|
||||
|
||||
L"damage.critical", //eSoundType_DAMAGE_CRITICAL,
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
#endif
|
||||
|
||||
#include "../Common/Leaderboards/LeaderboardManager.h"
|
||||
#include <regex>
|
||||
|
||||
//CMinecraftApp app;
|
||||
unsigned int CMinecraftApp::m_uiLastSignInData = 0;
|
||||
@@ -6653,6 +6654,97 @@ wstring CMinecraftApp::FormatHTMLString(int iPad, const wstring &desc, int shado
|
||||
return text;
|
||||
}
|
||||
|
||||
//found list of html escapes at https://stackoverflow.com/questions/7381974/which-characters-need-to-be-escaped-in-html
|
||||
wstring CMinecraftApp::EscapeHTMLString(const wstring& desc)
|
||||
{
|
||||
static std::unordered_map<wchar_t, wchar_t*> replacementMap = {
|
||||
{L'&', L"&"},
|
||||
{L'<', L"<"},
|
||||
{L'>', L">"},
|
||||
};
|
||||
|
||||
wstring finalString = L"";
|
||||
for (int i = 0; i < desc.size(); i++) {
|
||||
wchar_t _char = desc[i];
|
||||
auto it = replacementMap.find(_char);
|
||||
|
||||
if (it != replacementMap.end()) finalString += it->second;
|
||||
else finalString += _char;
|
||||
}
|
||||
|
||||
return finalString;
|
||||
}
|
||||
|
||||
wstring CMinecraftApp::FormatChatMessage(const wstring& desc, bool applyStyling)
|
||||
{
|
||||
static std::wregex IDS_Pattern(LR"(\{\*IDS_(\d+)\*\})"); //maybe theres a better way to do translateable IDS
|
||||
static std::wstring_view colorFormatString = L"<font color=\"#%08x\">";
|
||||
|
||||
wstring results = desc;
|
||||
wchar_t replacements[64];
|
||||
|
||||
swprintf(replacements, 64, (applyStyling ? colorFormatString.data() : L""), GetHTMLColour(eHTMLColor_0), 0xFFFFFFFF);
|
||||
results = replaceAll(results, L"§0", replacements);
|
||||
|
||||
swprintf(replacements, 64, (applyStyling ? colorFormatString.data() : L""), GetHTMLColour(eHTMLColor_1), 0xFFFFFFFF);
|
||||
results = replaceAll(results, L"§1", replacements);
|
||||
|
||||
swprintf(replacements, 64, (applyStyling ? colorFormatString.data() : L""), GetHTMLColour(eHTMLColor_2), 0xFFFFFFFF);
|
||||
results = replaceAll(results, L"§2", replacements);
|
||||
|
||||
swprintf(replacements, 64, (applyStyling ? colorFormatString.data() : L""), GetHTMLColour(eHTMLColor_3), 0xFFFFFFFF);
|
||||
results = replaceAll(results, L"§3", replacements);
|
||||
|
||||
swprintf(replacements, 64, (applyStyling ? colorFormatString.data() : L""), GetHTMLColour(eHTMLColor_4), 0xFFFFFFFF);
|
||||
results = replaceAll(results, L"§4", replacements);
|
||||
|
||||
swprintf(replacements, 64, (applyStyling ? colorFormatString.data() : L""), GetHTMLColour(eHTMLColor_5), 0xFFFFFFFF);
|
||||
results = replaceAll(results, L"§5", replacements);
|
||||
|
||||
swprintf(replacements, 64, (applyStyling ? colorFormatString.data() : L""), GetHTMLColour(eHTMLColor_6), 0xFFFFFFFF);
|
||||
results = replaceAll(results, L"§6", replacements);
|
||||
|
||||
swprintf(replacements, 64, (applyStyling ? colorFormatString.data() : L""), GetHTMLColour(eHTMLColor_7), 0xFFFFFFFF);
|
||||
results = replaceAll(results, L"§7", replacements);
|
||||
|
||||
swprintf(replacements, 64, (applyStyling ? colorFormatString.data() : L""), GetHTMLColour(eHTMLColor_8), 0xFFFFFFFF);
|
||||
results = replaceAll(results, L"§8", replacements);
|
||||
|
||||
swprintf(replacements, 64, (applyStyling ? colorFormatString.data() : L""), GetHTMLColour(eHTMLColor_9), 0xFFFFFFFF);
|
||||
results = replaceAll(results, L"§9", replacements);
|
||||
|
||||
swprintf(replacements, 64, (applyStyling ? colorFormatString.data() : L""), GetHTMLColour(eHTMLColor_a), 0xFFFFFFFF);
|
||||
results = replaceAll(results, L"§a", replacements);
|
||||
|
||||
swprintf(replacements, 64, (applyStyling ? colorFormatString.data() : L""), GetHTMLColour(eHTMLColor_b), 0xFFFFFFFF);
|
||||
results = replaceAll(results, L"§b", replacements);
|
||||
|
||||
swprintf(replacements, 64, (applyStyling ? colorFormatString.data() : L""), GetHTMLColour(eHTMLColor_c), 0xFFFFFFFF);
|
||||
results = replaceAll(results, L"§c", replacements);
|
||||
|
||||
swprintf(replacements, 64, (applyStyling ? colorFormatString.data() : L""), GetHTMLColour(eHTMLColor_d), 0xFFFFFFFF);
|
||||
results = replaceAll(results, L"§d", replacements);
|
||||
|
||||
swprintf(replacements, 64, (applyStyling ? colorFormatString.data() : L""), GetHTMLColour(eHTMLColor_e), 0xFFFFFFFF);
|
||||
results = replaceAll(results, L"§e", replacements);
|
||||
|
||||
swprintf(replacements, 64, (applyStyling ? colorFormatString.data() : L""), GetHTMLColour(eHTMLColor_f), 0xFFFFFFFF);
|
||||
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)) {
|
||||
results = replaceAll(results, match[0], app.GetString(std::stoi(match[1].str())));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
wstring CMinecraftApp::GetActionReplacement(int iPad, unsigned char ucAction)
|
||||
{
|
||||
unsigned int input = InputManager.GetGameJoypadMaps(InputManager.GetJoypadMapVal(iPad) ,ucAction);
|
||||
|
||||
@@ -564,7 +564,9 @@ public:
|
||||
int GetHTMLColour(eMinecraftColour colour);
|
||||
int GetHTMLColor(eMinecraftColour colour) { return GetHTMLColour(colour); }
|
||||
int GetHTMLFontSize(EHTMLFontSize size);
|
||||
wstring FormatHTMLString(int iPad, const wstring &desc, int shadowColour = 0xFFFFFFFF);
|
||||
wstring FormatHTMLString(int iPad, const wstring& desc, int shadowColour = 0xFFFFFFFF);
|
||||
wstring EscapeHTMLString(const wstring &desc);
|
||||
wstring FormatChatMessage(const wstring& desc, bool applyStyling = true);
|
||||
wstring GetActionReplacement(int iPad, unsigned char ucAction);
|
||||
wstring GetVKReplacement(unsigned int uiVKey);
|
||||
wstring GetIconReplacement(unsigned int uiIcon);
|
||||
|
||||
@@ -204,6 +204,12 @@ bool CGameNetworkManager::StartNetworkGame(Minecraft *minecraft, LPVOID lpParame
|
||||
ProfileManager.SetDeferredSignoutEnabled(true);
|
||||
#endif
|
||||
|
||||
// Clear any stale cancel flag latched by the previous join's progress
|
||||
// UI teardown, otherwise the next join's first tick insta-closes.
|
||||
EnterCriticalSection(&bCancelRequestedCS);
|
||||
g_NetworkManager.m_bCancelRequested = false;
|
||||
LeaveCriticalSection(&bCancelRequestedCS);
|
||||
|
||||
int64_t seed = 0;
|
||||
bool dedicatedNoLocalHostPlayer = false;
|
||||
if (lpParameter != nullptr)
|
||||
|
||||
@@ -134,7 +134,14 @@ void CPlatformNetworkManagerStub::NotifyPlayerLeaving(IQNetPlayer* pQNetPlayer)
|
||||
if (socket != nullptr)
|
||||
{
|
||||
if (m_pIQNet->IsHost())
|
||||
{
|
||||
g_NetworkManager.CloseConnection(networkPlayer);
|
||||
|
||||
// Propagate the TCP drop to the game Socket so any orphaned
|
||||
// PendingConnection at this smallId cleans up before its login
|
||||
// timer fires and leaks a DisconnectPacket to the reused slot.
|
||||
socket->close(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_pIQNet->IsHost())
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "stdafx.h"
|
||||
#include "UIController.h"
|
||||
#include <ChatScreen.h>
|
||||
#include "UI.h"
|
||||
#include "UIScene.h"
|
||||
#include "UIControl_Slider.h"
|
||||
@@ -1438,6 +1439,9 @@ void UIController::handleKeyPress(unsigned int iPad, unsigned int key)
|
||||
}
|
||||
#endif
|
||||
|
||||
if (key == 4) ChatScreen::setWheelValue(1);
|
||||
if (key == 5) ChatScreen::setWheelValue(-1);
|
||||
|
||||
if(pressed) app.DebugPrintf("Pressed %d\n",key);
|
||||
if(released) app.DebugPrintf("Released %d\n",key);
|
||||
// Repeat handling
|
||||
|
||||
@@ -310,19 +310,27 @@ void UIScene::loadMovie()
|
||||
|
||||
if(!app.hasArchiveFile(moviePath))
|
||||
{
|
||||
app.DebugPrintf("WARNING: Could not find iggy movie %ls, falling back on 720\n", moviePath.c_str());
|
||||
app.DebugPrintf("WARNING: Could not find iggy movie %ls, trying other resolutions\n", moviePath.c_str());
|
||||
|
||||
// Try 720 first, then 1080 as final fallback
|
||||
moviePath = getMoviePath();
|
||||
moviePath.append(L"720.swf");
|
||||
m_loadedResolution = eSceneResolution_720;
|
||||
|
||||
if(!app.hasArchiveFile(moviePath))
|
||||
{
|
||||
app.DebugPrintf("ERROR: Could not find any iggy movie for %ls!\n", moviePath.c_str());
|
||||
moviePath = getMoviePath();
|
||||
moviePath.append(L"1080.swf");
|
||||
m_loadedResolution = eSceneResolution_1080;
|
||||
|
||||
if(!app.hasArchiveFile(moviePath))
|
||||
{
|
||||
app.DebugPrintf("ERROR: Could not find any iggy movie for %ls!\n", moviePath.c_str());
|
||||
#ifndef _CONTENT_PACKAGE
|
||||
__debugbreak();
|
||||
__debugbreak();
|
||||
#endif
|
||||
app.FatalLoadError();
|
||||
app.FatalLoadError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "../../EnderDragonRenderer.h"
|
||||
#include "../../../Minecraft.World/net.minecraft.world.inventory.h"
|
||||
#include "../../../Minecraft.World/StringHelpers.h"
|
||||
#include <ChatScreen.h>
|
||||
|
||||
UIScene_HUD::UIScene_HUD(int iPad, void *initData, UILayer *parentLayer) : UIScene(iPad, parentLayer)
|
||||
{
|
||||
@@ -23,8 +24,10 @@ UIScene_HUD::UIScene_HUD(int iPad, void *initData, UILayer *parentLayer) : UISce
|
||||
for(unsigned int i = 0; i < CHAT_LINES_COUNT; ++i)
|
||||
{
|
||||
m_labelChatText[i].init(L"");
|
||||
IggyValueSetBooleanRS(m_labelChatText[i].getIggyValuePath(), 0, "m_bUseHtmlText", true);
|
||||
}
|
||||
m_labelJukebox.init(L"");
|
||||
IggyValueSetBooleanRS(m_labelJukebox.getIggyValuePath(), 0, "m_bUseHtmlText", true);
|
||||
|
||||
addTimer(0, 100);
|
||||
}
|
||||
@@ -254,8 +257,10 @@ void UIScene_HUD::handleReload()
|
||||
for(unsigned int i = 0; i < CHAT_LINES_COUNT; ++i)
|
||||
{
|
||||
m_labelChatText[i].init(L"");
|
||||
IggyValueSetBooleanRS(m_labelChatText[i].getIggyValuePath(), 0, "m_bUseHtmlText", true);
|
||||
}
|
||||
m_labelJukebox.init(L"");
|
||||
IggyValueSetBooleanRS(m_labelJukebox.getIggyValuePath(), 0, "m_bUseHtmlText", true);
|
||||
|
||||
int iGuiScale;
|
||||
Minecraft *pMinecraft = Minecraft::GetInstance();
|
||||
@@ -778,16 +783,31 @@ void UIScene_HUD::render(S32 width, S32 height, C4JRender::eViewportType viewpor
|
||||
void UIScene_HUD::handleTimerComplete(int id)
|
||||
{
|
||||
Minecraft *pMinecraft = Minecraft::GetInstance();
|
||||
bool isChatOpen = (dynamic_cast<ChatScreen*>(pMinecraft->getScreen()) != nullptr);
|
||||
|
||||
bool anyVisible = false;
|
||||
if(pMinecraft->localplayers[m_iPad]!= nullptr)
|
||||
{
|
||||
Gui *pGui = pMinecraft->gui;
|
||||
//DWORD messagesToDisplay = min( CHAT_LINES_COUNT, pGui->getMessagesCount(m_iPad) );
|
||||
for( unsigned int i = 0; i < CHAT_LINES_COUNT; ++i )
|
||||
DWORD totalMessages = pGui->getMessagesCount(m_iPad);
|
||||
DWORD messagesToDisplay = min( CHAT_LINES_COUNT, totalMessages);
|
||||
DWORD maxScroll = max(0, totalMessages - messagesToDisplay);
|
||||
|
||||
bool canScroll = messagesToDisplay < totalMessages;
|
||||
int startIndex = (canScroll && isChatOpen ? ChatScreen::getChatIndex() : 0);
|
||||
|
||||
if (startIndex > maxScroll) {
|
||||
ChatScreen::correctChatIndex(maxScroll);
|
||||
startIndex = maxScroll;
|
||||
}
|
||||
|
||||
app.DebugPrintf("handleTimerComplete: %d | %d | %d\n", maxScroll, startIndex, totalMessages);
|
||||
|
||||
for( unsigned int i = 0; i < messagesToDisplay; ++i )
|
||||
{
|
||||
float opacity = pGui->getOpacity(m_iPad, i);
|
||||
if( opacity > 0 )
|
||||
unsigned int msgIndex = startIndex + i;
|
||||
float opacity = pGui->getOpacity(m_iPad, msgIndex);
|
||||
if( opacity > 0 || isChatOpen)
|
||||
{
|
||||
#if 0 // def _WINDOWS64 // Use Iggy chat until Gui::render has visual parity
|
||||
// Chat drawn by Gui::render with color codes. Hides Iggy chat to avoid double chats.
|
||||
@@ -795,9 +815,10 @@ void UIScene_HUD::handleTimerComplete(int id)
|
||||
m_labelChatText[i].setOpacity(0);
|
||||
m_labelChatText[i].setLabel(L"");
|
||||
#else
|
||||
m_controlLabelBackground[i].setOpacity(opacity);
|
||||
m_labelChatText[i].setOpacity(opacity);
|
||||
m_labelChatText[i].setLabel( pGui->getMessagesCount(m_iPad) ? pGui->getMessage(m_iPad,i) : L"" );
|
||||
|
||||
m_controlLabelBackground[i].setOpacity((isChatOpen ? 1 : opacity));
|
||||
m_labelChatText[i].setOpacity((isChatOpen ? 1 : opacity));
|
||||
m_labelChatText[i].setLabel(pGui->getMessage(m_iPad, msgIndex));
|
||||
#endif
|
||||
anyVisible = true;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ private:
|
||||
bool m_bSplitscreen;
|
||||
|
||||
protected:
|
||||
UIControl_Label m_labelChatText[CHAT_LINES_COUNT];
|
||||
UIControl_HTMLLabel m_labelChatText[CHAT_LINES_COUNT];
|
||||
UIControl_Label m_labelJukebox;
|
||||
UIControl m_controlLabelBackground[CHAT_LINES_COUNT];
|
||||
UIControl_Label m_labelDisplayName;
|
||||
|
||||
@@ -281,6 +281,11 @@ UIScene_LoadMenu::UIScene_LoadMenu(int iPad, void *initData, UILayer *parentLaye
|
||||
WCHAR TempString[256];
|
||||
swprintf((WCHAR *)TempString, 256, L"%ls: %ls", app.GetString(IDS_SLIDER_DIFFICULTY), L"Hardcore");
|
||||
m_sliderDifficulty.init(TempString, eControl_Difficulty, 0, 4, 4);
|
||||
|
||||
// Hardcore locks game mode to Survival
|
||||
m_iGameModeId = GameType::SURVIVAL->getId();
|
||||
m_bGameModeCreative = false;
|
||||
m_buttonGamemode.setLabel(app.GetString(IDS_GAMEMODE_SURVIVAL));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -421,15 +426,7 @@ void UIScene_LoadMenu::updateTooltips()
|
||||
void UIScene_LoadMenu::updateComponents()
|
||||
{
|
||||
m_parentLayer->showComponent(m_iPad,eUIComponent_Panorama,true);
|
||||
|
||||
if(RenderManager.IsWidescreen())
|
||||
{
|
||||
m_parentLayer->showComponent(m_iPad,eUIComponent_Logo,true);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_parentLayer->showComponent(m_iPad,eUIComponent_Logo,false);
|
||||
}
|
||||
m_parentLayer->showComponent(m_iPad,eUIComponent_Logo,false);
|
||||
}
|
||||
|
||||
wstring UIScene_LoadMenu::getMoviePath()
|
||||
@@ -573,7 +570,9 @@ void UIScene_LoadMenu::tick()
|
||||
m_MoreOptionsParams.bAllowFriendsOfFriends = TRUE;
|
||||
}
|
||||
|
||||
m_bHardcore = app.GetGameHostOption(uiHostOptions, eGameHostOption_Hardcore) > 0;
|
||||
// Use thumbnail host options if available, otherwise preserve the level.dat value
|
||||
if (app.GetGameHostOption(uiHostOptions, eGameHostOption_Hardcore) > 0)
|
||||
m_bHardcore = true;
|
||||
if (m_bHardcore)
|
||||
{
|
||||
WCHAR TempString[256];
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
static wstring ReadLevelNameFromSaveFile(const wstring& filePath, bool *outHardcore = nullptr)
|
||||
{
|
||||
// Check for a worldname.txt sidecar written by the rename feature first
|
||||
wstring sidecarName = L"";
|
||||
size_t slashPos = filePath.rfind(L'\\');
|
||||
if (slashPos != wstring::npos)
|
||||
{
|
||||
@@ -50,7 +51,7 @@ static wstring ReadLevelNameFromSaveFile(const wstring& filePath, bool *outHardc
|
||||
{
|
||||
wchar_t wbuf[128] = {};
|
||||
MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, 127);
|
||||
return wstring(wbuf);
|
||||
sidecarName = wbuf;
|
||||
}
|
||||
}
|
||||
else fclose(fr);
|
||||
@@ -58,10 +59,10 @@ static wstring ReadLevelNameFromSaveFile(const wstring& filePath, bool *outHardc
|
||||
}
|
||||
|
||||
HANDLE hFile = CreateFileW(filePath.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, nullptr);
|
||||
if (hFile == INVALID_HANDLE_VALUE) return L"";
|
||||
if (hFile == INVALID_HANDLE_VALUE) return sidecarName;
|
||||
|
||||
DWORD fileSize = GetFileSize(hFile, nullptr);
|
||||
if (fileSize < 12 || fileSize == INVALID_FILE_SIZE) { CloseHandle(hFile); return L""; }
|
||||
if (fileSize < 12 || fileSize == INVALID_FILE_SIZE) { CloseHandle(hFile); return sidecarName; }
|
||||
|
||||
unsigned char *rawData = new unsigned char[fileSize];
|
||||
DWORD bytesRead = 0;
|
||||
@@ -69,7 +70,7 @@ static wstring ReadLevelNameFromSaveFile(const wstring& filePath, bool *outHardc
|
||||
{
|
||||
CloseHandle(hFile);
|
||||
delete[] rawData;
|
||||
return L"";
|
||||
return sidecarName;
|
||||
}
|
||||
CloseHandle(hFile);
|
||||
|
||||
@@ -84,7 +85,7 @@ static wstring ReadLevelNameFromSaveFile(const wstring& filePath, bool *outHardc
|
||||
if (decompSize == 0 || decompSize > 128 * 1024 * 1024)
|
||||
{
|
||||
delete[] rawData;
|
||||
return L"";
|
||||
return sidecarName;
|
||||
}
|
||||
saveData = new unsigned char[decompSize];
|
||||
Compression::getCompression()->Decompress(saveData, &decompSize, rawData + 8, fileSize - 8);
|
||||
@@ -140,6 +141,10 @@ static wstring ReadLevelNameFromSaveFile(const wstring& filePath, bool *outHardc
|
||||
|
||||
if (freeSaveData) delete[] saveData;
|
||||
delete[] rawData;
|
||||
|
||||
// Prefer the sidecar name (user-renamed) over the level.dat name
|
||||
if (!sidecarName.empty()) return sidecarName;
|
||||
|
||||
// "world" is the engine default - it means no real name was ever set,
|
||||
// so return empty to let the caller fall back to the save filename (timestamp).
|
||||
if (result == L"world") result = L"";
|
||||
@@ -774,7 +779,16 @@ void UIScene_LoadOrJoinMenu::tick()
|
||||
wchar_t wFilename[MAX_SAVEFILENAME_LENGTH];
|
||||
ZeroMemory(wFilename, sizeof(wFilename));
|
||||
mbstowcs(wFilename, m_pSaveDetails->SaveInfoA[origIdx].UTF8SaveFilename, MAX_SAVEFILENAME_LENGTH - 1);
|
||||
wstring filePath = wstring(L"Windows64\\GameHDD\\") + wstring(wFilename) + wstring(L"\\saveData.ms");
|
||||
wchar_t wTitle[MAX_DISPLAYNAME_LENGTH];
|
||||
ZeroMemory(wTitle, sizeof(wTitle));
|
||||
mbstowcs(wTitle, m_pSaveDetails->SaveInfoA[origIdx].UTF8SaveTitle, MAX_DISPLAYNAME_LENGTH - 1);
|
||||
wstring filePath = wstring(L"Windows64\\GameHDD\\") + wstring(wFilename) + wstring(L"\\") + wstring(wTitle) + wstring(L".ms");
|
||||
// Fallback to legacy saveData.ms if new-style filename doesn't exist
|
||||
{
|
||||
DWORD attrs = GetFileAttributesW(filePath.c_str());
|
||||
if (attrs == INVALID_FILE_ATTRIBUTES)
|
||||
filePath = wstring(L"Windows64\\GameHDD\\") + wstring(wFilename) + wstring(L"\\saveData.ms");
|
||||
}
|
||||
|
||||
HANDLE hFile = CreateFileW(filePath.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, nullptr);
|
||||
DWORD fileSize = 0;
|
||||
|
||||
@@ -1127,15 +1127,6 @@ void UIScene_SkinSelectMenu::handlePackIndexChanged()
|
||||
updatePackDisplay();
|
||||
}
|
||||
|
||||
std::wstring fakeWideToRealWide(const wchar_t* original)
|
||||
{
|
||||
const char* name = reinterpret_cast<const char*>(original);
|
||||
int len = MultiByteToWideChar(CP_UTF8, 0, name, -1, nullptr, 0);
|
||||
std::wstring wName(len, 0);
|
||||
MultiByteToWideChar(CP_UTF8, 0, name, -1, &wName[0], len);
|
||||
return wName.c_str();
|
||||
}
|
||||
|
||||
void UIScene_SkinSelectMenu::updatePackDisplay()
|
||||
{
|
||||
m_currentPackCount = app.m_dlcManager.getPackCount(DLCManager::e_DLCType_Skin) + SKIN_SELECT_MAX_DEFAULTS;
|
||||
@@ -1143,18 +1134,16 @@ void UIScene_SkinSelectMenu::updatePackDisplay()
|
||||
if(m_packIndex >= SKIN_SELECT_MAX_DEFAULTS)
|
||||
{
|
||||
DLCPack *thisPack = app.m_dlcManager.getPack(m_packIndex - SKIN_SELECT_MAX_DEFAULTS, DLCManager::e_DLCType_Skin);
|
||||
// Fix the incorrect string type on title to display correctly
|
||||
setCentreLabel(fakeWideToRealWide(thisPack->getName().c_str()));
|
||||
//setCentreLabel(thisPack->getName().c_str());
|
||||
setCentreLabel(thisPack->getName().c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
switch(m_packIndex)
|
||||
{
|
||||
case SKIN_SELECT_PACK_DEFAULT:
|
||||
case SKIN_SELECT_PACK_DEFAULT:
|
||||
setCentreLabel(app.GetString(IDS_NO_SKIN_PACK));
|
||||
break;
|
||||
case SKIN_SELECT_PACK_FAVORITES:
|
||||
case SKIN_SELECT_PACK_FAVORITES:
|
||||
setCentreLabel(app.GetString(IDS_FAVORITES_SKIN_PACK));
|
||||
break;
|
||||
}
|
||||
@@ -1164,18 +1153,16 @@ void UIScene_SkinSelectMenu::updatePackDisplay()
|
||||
if(nextPackIndex >= SKIN_SELECT_MAX_DEFAULTS)
|
||||
{
|
||||
DLCPack *thisPack = app.m_dlcManager.getPack(nextPackIndex - SKIN_SELECT_MAX_DEFAULTS, DLCManager::e_DLCType_Skin);
|
||||
// Fix the incorrect string type on title to display correctly
|
||||
setRightLabel(fakeWideToRealWide(thisPack->getName().c_str()));
|
||||
//setRightLabel(thisPack->getName().c_str());
|
||||
setRightLabel(thisPack->getName().c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
switch(nextPackIndex)
|
||||
{
|
||||
case SKIN_SELECT_PACK_DEFAULT:
|
||||
case SKIN_SELECT_PACK_DEFAULT:
|
||||
setRightLabel(app.GetString(IDS_NO_SKIN_PACK));
|
||||
break;
|
||||
case SKIN_SELECT_PACK_FAVORITES:
|
||||
case SKIN_SELECT_PACK_FAVORITES:
|
||||
setRightLabel(app.GetString(IDS_FAVORITES_SKIN_PACK));
|
||||
break;
|
||||
}
|
||||
@@ -1185,18 +1172,16 @@ void UIScene_SkinSelectMenu::updatePackDisplay()
|
||||
if(previousPackIndex >= SKIN_SELECT_MAX_DEFAULTS)
|
||||
{
|
||||
DLCPack *thisPack = app.m_dlcManager.getPack(previousPackIndex - SKIN_SELECT_MAX_DEFAULTS, DLCManager::e_DLCType_Skin);
|
||||
// Fix the incorrect string type on title to display correctly
|
||||
setLeftLabel(fakeWideToRealWide(thisPack->getName().c_str()));
|
||||
//setLeftLabel(thisPack->getName().c_str());
|
||||
setLeftLabel(thisPack->getName().c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
switch(previousPackIndex)
|
||||
{
|
||||
case SKIN_SELECT_PACK_DEFAULT:
|
||||
case SKIN_SELECT_PACK_DEFAULT:
|
||||
setLeftLabel(app.GetString(IDS_NO_SKIN_PACK));
|
||||
break;
|
||||
case SKIN_SELECT_PACK_FAVORITES:
|
||||
case SKIN_SELECT_PACK_FAVORITES:
|
||||
setLeftLabel(app.GetString(IDS_FAVORITES_SKIN_PACK));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ void EntityTracker::addEntity(shared_ptr<Entity> e, int range, int updateInterva
|
||||
{
|
||||
assert(false); // Entity already tracked
|
||||
}
|
||||
if( e->entityId >= 2048 )
|
||||
if( e->entityId >= 16384 )
|
||||
{
|
||||
__debugbreak();
|
||||
}
|
||||
|
||||
@@ -404,6 +404,8 @@ void Font::draw(const wstring &str, bool dropShadow, int initialColor)
|
||||
t->begin();
|
||||
t->color(currentColor & 0x00ffffff, (currentColor >> 24) & 255);
|
||||
|
||||
bool prev = t->setMipmapEnable(false); // Disable mipmapping for fonts, and save previous enabled value to be restored later - Botch
|
||||
|
||||
for (int i = 0; i < static_cast<int>(cleanStr.length()); ++i)
|
||||
{
|
||||
// Map character
|
||||
@@ -481,6 +483,8 @@ void Font::draw(const wstring &str, bool dropShadow, int initialColor)
|
||||
}
|
||||
}
|
||||
|
||||
t->setMipmapEnable(prev); //Reinstates previously used enabled value - Botch
|
||||
|
||||
t->end();
|
||||
}
|
||||
|
||||
|
||||
+87
-16
@@ -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;
|
||||
@@ -1208,7 +1229,20 @@ void Gui::render(float a, bool mouseFree, int xMouse, int yMouse)
|
||||
|
||||
// Disable the depth test so the text shows on top of the paperdoll
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
#ifdef _WINDOWS64
|
||||
float scaleWidth = (g_rScreenWidth / 1920.0f);
|
||||
float scaleHeight = (g_rScreenHeight / 1080.0f);
|
||||
|
||||
float scale = min(scaleWidth, scaleHeight); //stop stretching
|
||||
|
||||
if (scale < 0.5f) scale = 0.5f; // force minimum scale
|
||||
if (scale > 1.2f) // resolutions over 1296 pixels tall
|
||||
{
|
||||
scale = scale - 0.33f; // tame overscaling on 1440p
|
||||
}
|
||||
|
||||
glScalef(scale, scale, 1);
|
||||
#endif
|
||||
// Loop through the lines and draw them all on screen
|
||||
int yPos = debugTop;
|
||||
for (const auto &line : lines)
|
||||
@@ -1217,6 +1251,9 @@ void Gui::render(float a, bool mouseFree, int xMouse, int yMouse)
|
||||
yPos += 10;
|
||||
}
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
glScalef(1, 1, 1);
|
||||
#endif
|
||||
// Restore the depth test
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
|
||||
@@ -1421,6 +1458,37 @@ void Gui::clearMessages(int iPad)
|
||||
}
|
||||
}
|
||||
|
||||
int getVisibleMessageLength(const wstring& _string) {
|
||||
int visibleMessageLength = 0;
|
||||
bool inHtmlTag = false;
|
||||
|
||||
for (wchar_t _char : _string) {
|
||||
if (_char == L'<') inHtmlTag = true;
|
||||
if (_char == L'>') inHtmlTag = false;
|
||||
|
||||
if (!inHtmlTag) visibleMessageLength++;
|
||||
}
|
||||
|
||||
return visibleMessageLength;
|
||||
}
|
||||
|
||||
int getVisibleIndexToRaw(const wstring& _string, size_t target) {
|
||||
int visibleMessageLength = 0;
|
||||
bool inHtmlTag = false;
|
||||
|
||||
for (size_t i = 0; i < _string.size(); i++) {
|
||||
if (_string[i] == L'<') inHtmlTag = true;
|
||||
if (_string[i] == L'>') inHtmlTag = false;
|
||||
|
||||
if (!inHtmlTag) {
|
||||
if (visibleMessageLength == target) return i;
|
||||
|
||||
visibleMessageLength++;
|
||||
}
|
||||
}
|
||||
return _string.size();
|
||||
}
|
||||
|
||||
|
||||
void Gui::addMessage(const wstring& _string,int iPad,bool bIsDeathMessage)
|
||||
{
|
||||
@@ -1504,29 +1572,25 @@ void Gui::addMessage(const wstring& _string,int iPad,bool bIsDeathMessage)
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
while (string.length() > maximumChars)
|
||||
while (getVisibleMessageLength(string) > maximumChars)
|
||||
{
|
||||
unsigned int i = 1;
|
||||
while (i < string.length() && (i + 1) <= maximumChars)
|
||||
{
|
||||
i++;
|
||||
}
|
||||
size_t iLast=string.find_last_of(L" ",i);
|
||||
size_t cutOffset = getVisibleIndexToRaw(string, maximumChars);
|
||||
|
||||
size_t iLast=string.find_last_of(L" ", cutOffset);
|
||||
switch(XGetLanguage())
|
||||
{
|
||||
case XC_LANGUAGE_JAPANESE:
|
||||
case XC_LANGUAGE_TCHINESE:
|
||||
case XC_LANGUAGE_KOREAN:
|
||||
iLast = maximumChars;
|
||||
iLast = cutOffset;
|
||||
break;
|
||||
default:
|
||||
iLast=string.find_last_of(L" ",i);
|
||||
iLast=string.find_last_of(L" ", cutOffset);
|
||||
break;
|
||||
}
|
||||
|
||||
// if a space was found, include the space on this line
|
||||
if(iLast!=i)
|
||||
if(iLast!=cutOffset)
|
||||
{
|
||||
iLast++;
|
||||
}
|
||||
@@ -1578,6 +1642,13 @@ float Gui::getOpacity(int iPad, DWORD index)
|
||||
return opacityPercentage;
|
||||
}
|
||||
|
||||
//just like java functionality it overwrites the jukebox label
|
||||
void Gui::setActionBarMessage(wstring message)
|
||||
{
|
||||
overlayMessageString = message;
|
||||
overlayMessageTime = 20 * 4; //idk how long it should last, need to check java usage
|
||||
}
|
||||
|
||||
float Gui::getJukeboxOpacity(int iPad)
|
||||
{
|
||||
float t = overlayMessageTime - lastTickA;
|
||||
@@ -1593,7 +1664,7 @@ void Gui::setNowPlaying(const wstring& string)
|
||||
// overlayMessageString = L"Now playing: " + string;
|
||||
overlayMessageString = app.GetString(IDS_NOWPLAYING) + string;
|
||||
overlayMessageTime = 20 * 3;
|
||||
animateOverlayMessageColor = true;
|
||||
animateOverlayMessageColor = true; //appears to be unused, @DrPerkyLegit plans to add in later pr
|
||||
}
|
||||
|
||||
void Gui::displayClientMessage(int messageId, int iPad)
|
||||
|
||||
@@ -17,6 +17,7 @@ private:
|
||||
static const int m_iMaxMessageWidth = 280;
|
||||
static ItemRenderer *itemRenderer;
|
||||
vector<GuiMessage> guiMessages[XUSER_MAX_COUNT];
|
||||
int chatIndex = 0;
|
||||
Random *random;
|
||||
|
||||
Minecraft *minecraft;
|
||||
@@ -63,6 +64,8 @@ public:
|
||||
wstring getMessage(int iPad, DWORD index) { return guiMessages[iPad].at(index).string; }
|
||||
float getOpacity(int iPad, DWORD index);
|
||||
|
||||
void setActionBarMessage(wstring message); //uses jukebox label
|
||||
|
||||
wstring getJukeboxMessage(int iPad) { return overlayMessageString; }
|
||||
float getJukeboxOpacity(int iPad);
|
||||
|
||||
|
||||
@@ -1542,8 +1542,12 @@ void Minecraft::run_middle()
|
||||
// Utility keys always work regardless of KBM active state
|
||||
if(g_KBMInput.IsKeyPressed(KeyboardMouseInput::KEY_PAUSE) && !ui.GetMenuDisplayed(i))
|
||||
{
|
||||
localplayers[i]->ullButtonsPressed|=1LL<<MINECRAFT_ACTION_PAUSEMENU;
|
||||
app.DebugPrintf("PAUSE PRESSED (keyboard) - ipad = %d\n",i);
|
||||
if (dynamic_cast<ChatScreen*>(getScreen()) != nullptr) {
|
||||
setScreen(nullptr);
|
||||
} else {
|
||||
localplayers[i]->ullButtonsPressed|=1LL<<MINECRAFT_ACTION_PAUSEMENU;
|
||||
app.DebugPrintf("PAUSE PRESSED (keyboard) - ipad = %d\n",i);
|
||||
}
|
||||
}
|
||||
|
||||
if(g_KBMInput.IsKeyPressed(KeyboardMouseInput::KEY_THIRD_PERSON))
|
||||
|
||||
@@ -1815,6 +1815,7 @@ void MinecraftServer::run(int64_t seed, void *lpParameter)
|
||||
|
||||
chunkPacketManagement_PostTick();
|
||||
}
|
||||
lastTime = getCurrentTimeMillis();
|
||||
// int64_t afterall = System::currentTimeMillis();
|
||||
// PIXReportCounter(L"Server time all",(float)(afterall-beforeall));
|
||||
// PIXReportCounter(L"Server ticks",(float)tickcount);
|
||||
|
||||
@@ -327,7 +327,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"�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);
|
||||
|
||||
@@ -618,4 +618,4 @@ ResourceLocation *PlayerRenderer::getTextureLocation(shared_ptr<Entity> entity)
|
||||
{
|
||||
shared_ptr<Player> player = dynamic_pointer_cast<Player>(entity);
|
||||
return new ResourceLocation(static_cast<_TEXTURE_NAME>(player->getTexture()));
|
||||
}
|
||||
}
|
||||
@@ -161,7 +161,7 @@ void Screen::updateEvents()
|
||||
static bool s_arrowFirstRepeat[2] = { false, false };
|
||||
const DWORD ARROW_REPEAT_DELAY_MS = 250;
|
||||
const DWORD ARROW_REPEAT_INTERVAL_MS = 50;
|
||||
DWORD now = GetTickCount();
|
||||
DWORD now = GetTickCount64();
|
||||
|
||||
// Poll keyboard events (special keys that may not come through WM_CHAR, e.g. Escape, arrows)
|
||||
for (int vk = 0; vk < 256; vk++)
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -295,12 +295,12 @@ void ServerPlayer::flagEntitiesToBeRemoved(unsigned int *flags, bool *removedFou
|
||||
{
|
||||
*removedFound = true;
|
||||
// before this left 192 bytes uninitialized!!!!!
|
||||
memset(flags, 0, (2048 / 32) * sizeof(unsigned int));
|
||||
memset(flags, 0, (16384 / 32) * sizeof(unsigned int));
|
||||
}
|
||||
|
||||
for(int index : entitiesToRemove)
|
||||
{
|
||||
if( index < 2048 )
|
||||
if( index < 16384 )
|
||||
{
|
||||
unsigned int i = index / 32;
|
||||
unsigned int j = index % 32;
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -9,6 +9,11 @@
|
||||
#include "../../Minecraft.World/LevelSettings.h"
|
||||
#include "../../Minecraft.World/BiomeSource.h"
|
||||
#include "../../Minecraft.World/LevelType.h"
|
||||
#include "stb_image_write.h"
|
||||
|
||||
extern ID3D11Device* g_pd3dDevice;
|
||||
extern ID3D11DeviceContext* g_pImmediateContext;
|
||||
extern IDXGISwapChain* g_pSwapChain;
|
||||
|
||||
CConsoleMinecraftApp app;
|
||||
|
||||
@@ -33,9 +38,120 @@ void CConsoleMinecraftApp::FatalLoadError()
|
||||
{
|
||||
}
|
||||
|
||||
static const int THUMBNAIL_SIZE = 64;
|
||||
|
||||
void CConsoleMinecraftApp::CaptureSaveThumbnail()
|
||||
{
|
||||
RenderManager.CaptureThumbnail(&m_ThumbnailBuffer);
|
||||
if (!g_pSwapChain || !g_pd3dDevice || !g_pImmediateContext)
|
||||
return;
|
||||
|
||||
// Release any previous capture
|
||||
if (m_ThumbnailBuffer.Allocated())
|
||||
m_ThumbnailBuffer.Release();
|
||||
|
||||
// Get the backbuffer
|
||||
ID3D11Texture2D* pBackBuffer = nullptr;
|
||||
HRESULT hr = g_pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)&pBackBuffer);
|
||||
if (FAILED(hr))
|
||||
return;
|
||||
|
||||
D3D11_TEXTURE2D_DESC backDesc = {};
|
||||
pBackBuffer->GetDesc(&backDesc);
|
||||
|
||||
// Create a staging texture at backbuffer size to read pixels
|
||||
D3D11_TEXTURE2D_DESC stagingDesc = backDesc;
|
||||
stagingDesc.Usage = D3D11_USAGE_STAGING;
|
||||
stagingDesc.BindFlags = 0;
|
||||
stagingDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
|
||||
stagingDesc.MiscFlags = 0;
|
||||
|
||||
ID3D11Texture2D* pStaging = nullptr;
|
||||
hr = g_pd3dDevice->CreateTexture2D(&stagingDesc, nullptr, &pStaging);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
pBackBuffer->Release();
|
||||
return;
|
||||
}
|
||||
|
||||
g_pImmediateContext->CopyResource(pStaging, pBackBuffer);
|
||||
pBackBuffer->Release();
|
||||
|
||||
D3D11_MAPPED_SUBRESOURCE mapped = {};
|
||||
hr = g_pImmediateContext->Map(pStaging, 0, D3D11_MAP_READ, 0, &mapped);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
pStaging->Release();
|
||||
return;
|
||||
}
|
||||
|
||||
// Downsample to THUMBNAIL_SIZE x THUMBNAIL_SIZE with simple box filter
|
||||
unsigned char* thumb = new unsigned char[THUMBNAIL_SIZE * THUMBNAIL_SIZE * 4];
|
||||
|
||||
// Crop to square (center crop), then scale down
|
||||
UINT srcSize = (backDesc.Width < backDesc.Height) ? backDesc.Width : backDesc.Height;
|
||||
UINT offsetX = (backDesc.Width - srcSize) / 2;
|
||||
UINT offsetY = (backDesc.Height - srcSize) / 2;
|
||||
|
||||
for (int ty = 0; ty < THUMBNAIL_SIZE; ty++)
|
||||
{
|
||||
for (int tx = 0; tx < THUMBNAIL_SIZE; tx++)
|
||||
{
|
||||
// Map thumbnail pixel to source region
|
||||
UINT sx = offsetX + (tx * srcSize) / THUMBNAIL_SIZE;
|
||||
UINT sy = offsetY + (ty * srcSize) / THUMBNAIL_SIZE;
|
||||
|
||||
const unsigned char* src = (const unsigned char*)mapped.pData + sy * mapped.RowPitch + sx * 4;
|
||||
unsigned char* dst = thumb + (ty * THUMBNAIL_SIZE + tx) * 4;
|
||||
|
||||
dst[0] = src[0]; // R (or B depending on format, but BGRA->RGBA swap below)
|
||||
dst[1] = src[1]; // G
|
||||
dst[2] = src[2]; // B
|
||||
dst[3] = 0xFF; // A
|
||||
}
|
||||
}
|
||||
|
||||
g_pImmediateContext->Unmap(pStaging, 0);
|
||||
pStaging->Release();
|
||||
|
||||
// If backbuffer is BGRA, swap to RGBA for PNG
|
||||
if (backDesc.Format == DXGI_FORMAT_B8G8R8A8_UNORM || backDesc.Format == DXGI_FORMAT_B8G8R8A8_UNORM_SRGB)
|
||||
{
|
||||
for (int i = 0; i < THUMBNAIL_SIZE * THUMBNAIL_SIZE; i++)
|
||||
{
|
||||
unsigned char tmp = thumb[i * 4];
|
||||
thumb[i * 4] = thumb[i * 4 + 2];
|
||||
thumb[i * 4 + 2] = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
// Encode to PNG in memory using stbi_write_png_to_func
|
||||
struct PngBuffer { unsigned char* data; int size; int capacity; } pngBuf = {};
|
||||
pngBuf.capacity = THUMBNAIL_SIZE * THUMBNAIL_SIZE * 4 + 256;
|
||||
pngBuf.data = (unsigned char*)malloc(pngBuf.capacity);
|
||||
pngBuf.size = 0;
|
||||
|
||||
stbi_write_png_to_func([](void* ctx, void* data, int size) {
|
||||
PngBuffer* buf = (PngBuffer*)ctx;
|
||||
if (buf->size + size > buf->capacity)
|
||||
{
|
||||
buf->capacity = (buf->size + size) * 2;
|
||||
buf->data = (unsigned char*)realloc(buf->data, buf->capacity);
|
||||
}
|
||||
memcpy(buf->data + buf->size, data, size);
|
||||
buf->size += size;
|
||||
}, &pngBuf, THUMBNAIL_SIZE, THUMBNAIL_SIZE, 4, thumb, THUMBNAIL_SIZE * 4);
|
||||
delete[] thumb;
|
||||
|
||||
if (pngBuf.size > 0)
|
||||
{
|
||||
m_ThumbnailBuffer.m_type = ImageFileBuffer::e_typePNG;
|
||||
m_ThumbnailBuffer.m_pBuffer = pngBuf.data;
|
||||
m_ThumbnailBuffer.m_bufferSize = pngBuf.size;
|
||||
}
|
||||
else
|
||||
{
|
||||
free(pngBuf.data);
|
||||
}
|
||||
}
|
||||
void CConsoleMinecraftApp::GetSaveThumbnail(PBYTE *pbData,DWORD *pdwSize)
|
||||
{
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
#include <dxgi1_4.h> // IDXGISwapChain3 for SetColorSpace1
|
||||
|
||||
#include <assert.h>
|
||||
#include <iostream>
|
||||
#include <ShellScalingApi.h>
|
||||
@@ -58,6 +60,13 @@ extern Renderer InternalRenderManager;
|
||||
|
||||
#include "Xbox/Resource.h"
|
||||
|
||||
// request use of dedicated GPU from AMD and Nvidia drivers
|
||||
extern "C"
|
||||
{
|
||||
__declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
|
||||
__declspec(dllexport) unsigned long NvOptimusEnablement = 0x00000001;
|
||||
}
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma comment(lib, "legacy_stdio_definitions.lib")
|
||||
#endif
|
||||
@@ -551,6 +560,10 @@ ID3D11Texture2D* g_pDepthStencilBuffer = nullptr;
|
||||
static const float kClearColorWhite[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
|
||||
static const float kClearColorBlack[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
|
||||
|
||||
// True when the swap chain is in DXGI exclusive fullscreen. Lets ResizeD3D
|
||||
// skip its destroy-and-recreate path, which would break exclusive ownership.
|
||||
static bool g_bDxgiExclusiveFullscreen = false;
|
||||
|
||||
//
|
||||
// FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
|
||||
//
|
||||
@@ -905,14 +918,17 @@ HRESULT InitDevice()
|
||||
// backbuffer refs we can't release). Bitblt DISCARD has no HWND lock, so
|
||||
// the "destroy old, create new" resize path in ResizeD3D() works cleanly.
|
||||
// VSync toggle still works via the SyncInterval parameter on Present().
|
||||
// RefreshRate=0/0 so DXGI matches the current display mode. Hardcoding a
|
||||
// rate would force a mode switch on SetFullscreenState, which can produce
|
||||
// "input signal out of range" errors on high-refresh monitors.
|
||||
DXGI_SWAP_CHAIN_DESC sd;
|
||||
ZeroMemory( &sd, sizeof( sd ) );
|
||||
sd.BufferCount = 1;
|
||||
sd.BufferDesc.Width = width;
|
||||
sd.BufferDesc.Height = height;
|
||||
sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
sd.BufferDesc.RefreshRate.Numerator = 60;
|
||||
sd.BufferDesc.RefreshRate.Denominator = 1;
|
||||
sd.BufferDesc.RefreshRate.Numerator = 0;
|
||||
sd.BufferDesc.RefreshRate.Denominator = 0;
|
||||
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_SHADER_INPUT;
|
||||
sd.OutputWindow = g_hWnd;
|
||||
sd.SampleDesc.Count = 1;
|
||||
@@ -1005,6 +1021,9 @@ static bool ResizeD3D(int newW, int newH)
|
||||
if (newW <= 0 || newH <= 0) return false;
|
||||
if (!g_pSwapChain) return false;
|
||||
if (!g_bResizeReady) return false;
|
||||
// In exclusive fullscreen the swap chain must not be recreated.
|
||||
if (g_bDxgiExclusiveFullscreen)
|
||||
return false;
|
||||
|
||||
int bbW = newW;
|
||||
int bbH = newH;
|
||||
@@ -1101,8 +1120,9 @@ static bool ResizeD3D(int newW, int newH)
|
||||
sd.BufferDesc.Width = bbW;
|
||||
sd.BufferDesc.Height = bbH;
|
||||
sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
sd.BufferDesc.RefreshRate.Numerator = 60;
|
||||
sd.BufferDesc.RefreshRate.Denominator = 1;
|
||||
// RefreshRate=0/0 matches InitDevice; see comment there.
|
||||
sd.BufferDesc.RefreshRate.Numerator = 0;
|
||||
sd.BufferDesc.RefreshRate.Denominator = 0;
|
||||
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT | DXGI_USAGE_SHADER_INPUT;
|
||||
sd.OutputWindow = g_hWnd;
|
||||
sd.SampleDesc.Count = 1;
|
||||
@@ -1318,16 +1338,99 @@ void SetExclusiveFullscreen(bool enabled)
|
||||
g_bPendingExclusiveFullscreenValue = enabled;
|
||||
}
|
||||
|
||||
// Uses borderless fullscreen (ToggleFullscreen) rather than DXGI SetFullscreenState.
|
||||
// True DXGI exclusive fullscreen is blocked by the 4J Renderer holding hidden
|
||||
// backbuffer references that prevent ResizeBuffers.
|
||||
// Enter or leave true DXGI exclusive fullscreen. With our bitblt swap chain,
|
||||
// Present(SyncInterval=0) in exclusive mode produces real screen tearing via
|
||||
// direct scanout (DWM is out of the pipeline). Flip mode with ALLOW_TEARING
|
||||
// would also work but is blocked by the 4J Renderer's deferred context refs
|
||||
// on the backbuffer, which DXGI's ResizeBuffers cannot release.
|
||||
static void ApplyExclusiveFullscreen(bool enabled)
|
||||
{
|
||||
// Toggle into/out of borderless fullscreen if state doesn't match
|
||||
if (enabled && !g_isFullscreen)
|
||||
ToggleFullscreen();
|
||||
else if (!enabled && g_isFullscreen)
|
||||
ToggleFullscreen();
|
||||
if (!g_pSwapChain)
|
||||
return;
|
||||
|
||||
LONG styleBefore = GetWindowLong(g_hWnd, GWL_STYLE);
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
// Grow the window to cover the monitor first. This fires WM_SIZE which
|
||||
// runs ResizeD3D and recreates the backbuffer at monitor-native size.
|
||||
// Otherwise a small windowed backbuffer would enter exclusive fullscreen
|
||||
// at that smaller size and DXGI would scale it to fill the monitor,
|
||||
// producing a filtered / washed-out look.
|
||||
HMONITOR hMon = MonitorFromWindow(g_hWnd, MONITOR_DEFAULTTOPRIMARY);
|
||||
MONITORINFO mi = {};
|
||||
mi.cbSize = sizeof(mi);
|
||||
if (GetMonitorInfo(hMon, &mi))
|
||||
{
|
||||
int monW = mi.rcMonitor.right - mi.rcMonitor.left;
|
||||
int monH = mi.rcMonitor.bottom - mi.rcMonitor.top;
|
||||
SetWindowLong(g_hWnd, GWL_STYLE, (styleBefore & ~WS_OVERLAPPEDWINDOW) | WS_VISIBLE);
|
||||
SetWindowPos(g_hWnd, HWND_TOP,
|
||||
mi.rcMonitor.left, mi.rcMonitor.top, monW, monH,
|
||||
SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
|
||||
|
||||
// ResizeTarget pins the display mode to the backbuffer size with
|
||||
// no scaling. Microsoft's pattern is ResizeTarget then
|
||||
// SetFullscreenState then ResizeTarget again (see below).
|
||||
DXGI_MODE_DESC targetMode = {};
|
||||
targetMode.Width = monW;
|
||||
targetMode.Height = monH;
|
||||
targetMode.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
targetMode.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
|
||||
targetMode.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
|
||||
g_pSwapChain->ResizeTarget(&targetMode);
|
||||
}
|
||||
}
|
||||
|
||||
HRESULT hr = g_pSwapChain->SetFullscreenState(enabled ? TRUE : FALSE, nullptr);
|
||||
if (FAILED(hr))
|
||||
return;
|
||||
|
||||
g_bDxgiExclusiveFullscreen = enabled;
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
// Explicitly declare sRGB. Default for R8G8B8A8_UNORM but some drivers
|
||||
// behave differently if the color space is never set.
|
||||
IDXGISwapChain3* pSwapChain3 = nullptr;
|
||||
if (SUCCEEDED(g_pSwapChain->QueryInterface(__uuidof(IDXGISwapChain3), (void**)&pSwapChain3)) && pSwapChain3)
|
||||
{
|
||||
UINT colorSpaceSupport = 0;
|
||||
pSwapChain3->CheckColorSpaceSupport(DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709, &colorSpaceSupport);
|
||||
if (colorSpaceSupport & DXGI_SWAP_CHAIN_COLOR_SPACE_SUPPORT_FLAG_PRESENT)
|
||||
pSwapChain3->SetColorSpace1(DXGI_COLOR_SPACE_RGB_FULL_G22_NONE_P709);
|
||||
pSwapChain3->Release();
|
||||
}
|
||||
|
||||
// Second ResizeTarget per Microsoft's recommendation to make the mode stick.
|
||||
HMONITOR hMon2 = MonitorFromWindow(g_hWnd, MONITOR_DEFAULTTOPRIMARY);
|
||||
MONITORINFO mi2 = {};
|
||||
mi2.cbSize = sizeof(mi2);
|
||||
if (GetMonitorInfo(hMon2, &mi2))
|
||||
{
|
||||
DXGI_MODE_DESC targetMode2 = {};
|
||||
targetMode2.Width = mi2.rcMonitor.right - mi2.rcMonitor.left;
|
||||
targetMode2.Height = mi2.rcMonitor.bottom - mi2.rcMonitor.top;
|
||||
targetMode2.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
targetMode2.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
|
||||
targetMode2.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
|
||||
g_pSwapChain->ResizeTarget(&targetMode2);
|
||||
}
|
||||
g_isFullscreen = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Force a real decorated windowed state on exit. DXGI would otherwise
|
||||
// restore whatever state the window had before SetFullscreenState,
|
||||
// which may still be borderless.
|
||||
SetWindowLong(g_hWnd, GWL_STYLE, WS_OVERLAPPEDWINDOW | WS_VISIBLE);
|
||||
const int w = 1280, h = 720;
|
||||
const int sw = GetSystemMetrics(SM_CXSCREEN);
|
||||
const int sh = GetSystemMetrics(SM_CYSCREEN);
|
||||
SetWindowPos(g_hWnd, HWND_TOP, (sw - w) / 2, (sh - h) / 2, w, h,
|
||||
SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
|
||||
g_isFullscreen = false;
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------
|
||||
@@ -1562,10 +1665,13 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Restore fullscreen state from previous session
|
||||
if (LoadFullscreenOption() && !g_isFullscreen || launchOptions.fullscreen)
|
||||
// Restore fullscreen state from previous session. Route through the
|
||||
// deferred exclusive fullscreen path so the main loop applies it on the
|
||||
// first tick (safer than transitioning during init).
|
||||
if ((LoadFullscreenOption() && !g_isFullscreen) || launchOptions.fullscreen)
|
||||
{
|
||||
ToggleFullscreen();
|
||||
g_bPendingExclusiveFullscreen = true;
|
||||
g_bPendingExclusiveFullscreenValue = true;
|
||||
}
|
||||
|
||||
#if 0
|
||||
@@ -1857,11 +1963,14 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
|
||||
|
||||
RenderManager.Set_matrixDirty();
|
||||
#endif
|
||||
// Present the frame.
|
||||
// RenderManager.Present() hardcodes SyncInterval=1 internally.
|
||||
// When VSync is off, bypass it and call the swap chain directly
|
||||
// with SyncInterval=0.
|
||||
if (!g_bVSync && g_pSwapChain)
|
||||
// Present the frame. RenderManager.Present() hardcodes SyncInterval=1,
|
||||
// so when VSync is off we bypass it for uncapped frames. In DXGI
|
||||
// exclusive fullscreen this produces real tearing via direct scanout.
|
||||
// Force VSync on the main menu regardless of user setting: an uncapped
|
||||
// simple menu scene drives the GPU to thousands of FPS and causes coil
|
||||
// whine on many cards.
|
||||
const bool forceVSyncForMenu = !app.GetGameStarted();
|
||||
if (!g_bVSync && !forceVSyncForMenu && g_pSwapChain)
|
||||
{
|
||||
HRESULT hrPresent = g_pSwapChain->Present(0, 0);
|
||||
// If the direct Present fails (e.g. during fullscreen transition),
|
||||
@@ -1962,11 +2071,11 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
|
||||
}
|
||||
#endif
|
||||
|
||||
// toggle fullscreen
|
||||
// toggle fullscreen (DXGI exclusive via ApplyExclusiveFullscreen)
|
||||
if (g_KBMInput.IsKeyPressed(KeyboardMouseInput::KEY_FULLSCREEN))
|
||||
{
|
||||
ToggleFullscreen();
|
||||
app.SetGameSettings(ProfileManager.GetPrimaryPad(), eGameSetting_ExclusiveFullscreen, g_isFullscreen ? 1 : 0);
|
||||
ApplyExclusiveFullscreen(!g_bDxgiExclusiveFullscreen);
|
||||
app.SetGameSettings(ProfileManager.GetPrimaryPad(), eGameSetting_ExclusiveFullscreen, g_bDxgiExclusiveFullscreen ? 1 : 0);
|
||||
}
|
||||
|
||||
// Apply deferred exclusive fullscreen toggle
|
||||
|
||||
Binary file not shown.
@@ -1,12 +1,5 @@
|
||||
set(BASE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/Windows64/")
|
||||
|
||||
set(_MINECRAFT_CLIENT_WINDOWS_COMMON_RES_AUDIO
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/Common/res/audio/minecraft.xsb"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/Common/res/audio/resident.xwb"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/Common/res/audio/streamed.xwb"
|
||||
)
|
||||
source_group("Common/res/audio" FILES ${_MINECRAFT_CLIENT_WINDOWS_COMMON_RES_AUDIO})
|
||||
|
||||
set(_MINECRAFT_CLIENT_WINDOWS_COMMON_AUDIO
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/Common/Audio/SoundEngine.cpp"
|
||||
)
|
||||
@@ -472,7 +465,6 @@ set(_MINECRAFT_CLIENT_WINDOWS_NET_MINECRAFT_STATS
|
||||
source_group("net/minecraft/stats" FILES ${_MINECRAFT_CLIENT_WINDOWS_NET_MINECRAFT_STATS})
|
||||
|
||||
set(MINECRAFT_CLIENT_WINDOWS
|
||||
${_MINECRAFT_CLIENT_WINDOWS_COMMON_RES_AUDIO}
|
||||
${_MINECRAFT_CLIENT_WINDOWS_COMMON_AUDIO}
|
||||
${_MINECRAFT_CLIENT_WINDOWS_COMMON_NETWORK}
|
||||
${_MINECRAFT_CLIENT_WINDOWS_COMMON_UI}
|
||||
|
||||
@@ -123,7 +123,7 @@ void glClearColor(float r, float g, float b, float a)
|
||||
|
||||
RenderManager.SetClearColour(D3DCOLOR_RGBA(ir,ig,ib,ia));
|
||||
#else
|
||||
float rgba[4] = {r,g,b,a};
|
||||
float rgba[4] = {r,g,b,1.0f}; // Force alpha=1 to prevent DWM window transparency
|
||||
RenderManager.SetClearColour(rgba);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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: &</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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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})";
|
||||
}
|
||||
|
||||
@@ -7,5 +7,7 @@
|
||||
<AssemblyName>Minecraft.Server.FourKit</AssemblyName>
|
||||
<EnableDynamicLoading>true</EnableDynamicLoading>
|
||||
<BaseOutputPath>bin</BaseOutputPath>
|
||||
<ServerGarbageCollection>true</ServerGarbageCollection>
|
||||
<ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
@@ -46,6 +52,8 @@
|
||||
namespace
|
||||
{
|
||||
|
||||
std::atomic<uint32_t> g_handlerMask{0};
|
||||
|
||||
static shared_ptr<ServerPlayer> FindPlayer(int entityId)
|
||||
{
|
||||
PlayerList *list = MinecraftServer::getPlayerList();
|
||||
@@ -104,6 +112,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);
|
||||
@@ -304,20 +330,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)
|
||||
@@ -823,16 +849,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;
|
||||
@@ -1068,6 +1092,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);
|
||||
@@ -1268,4 +1354,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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include <vector>
|
||||
#include <stdarg.h>
|
||||
|
||||
#include "..\Minecraft.World\DisconnectPacket.h"
|
||||
#include "../Minecraft.World/DisconnectPacket.h"
|
||||
|
||||
namespace ServerRuntime
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
@@ -557,10 +558,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();
|
||||
@@ -747,7 +756,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 && !ConsoleSaveFileOriginal::hasPendingBackgroundSave())
|
||||
{
|
||||
@@ -768,16 +777,30 @@ int main(int argc, char **argv)
|
||||
|
||||
LogInfof("shutdown", "Dedicated server stopped");
|
||||
MinecraftServer *server = MinecraftServer::getInstance();
|
||||
if (server != NULL && !ConsoleSaveFileOriginal::hasPendingBackgroundSave())
|
||||
if (server != NULL)
|
||||
{
|
||||
server->setSaveOnExit(true);
|
||||
LogWorldIO("requesting save before shutdown");
|
||||
LogWorldIO("using saveOnExit for shutdown");
|
||||
}
|
||||
|
||||
if (ConsoleSaveFileOriginal::hasPendingBackgroundSave())
|
||||
{
|
||||
LogWorldIO("Waiting for autosave to complete...");
|
||||
// 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);
|
||||
LogWorldIO("requesting exit save");
|
||||
}
|
||||
|
||||
MinecraftServer::HaltServer();
|
||||
|
||||
@@ -400,6 +400,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"
|
||||
@@ -501,6 +502,7 @@ set(_MINECRAFT_SERVER_COMMON_ROOT
|
||||
"${_MS_SRC}/../Minecraft.Client/iob_shim.asm"
|
||||
"${_MS_SRC}/../Minecraft.Client/stdafx.cpp"
|
||||
"${_MS_SRC}/../Minecraft.Client/stubs.cpp"
|
||||
"${_MS_SRC}/../Minecraft.World/Entity.cpp"
|
||||
"${_MS_SRC}/../Minecraft.World/AbstractContainerMenu.cpp"
|
||||
"${_MS_SRC}/../Minecraft.World/CompoundContainer.h"
|
||||
"${_MS_SRC}/../Minecraft.World/ItemEntity.cpp"
|
||||
|
||||
@@ -98,6 +98,7 @@ public:
|
||||
e_ChatCommandTeleportMe,
|
||||
e_ChatCommandTeleportToMe,
|
||||
|
||||
e_ChatActionBar,
|
||||
};
|
||||
|
||||
public:
|
||||
|
||||
@@ -124,6 +124,7 @@ DamageSource::DamageSource(ChatPacket::EChatPacketMessage msgId, ChatPacket::ECh
|
||||
_isProjectile = false;
|
||||
_isMagic = false;
|
||||
_isExplosion = false;
|
||||
_isCritical = false;
|
||||
|
||||
//this->msgId = msgId;
|
||||
m_msgId = msgId;
|
||||
@@ -153,7 +154,15 @@ DamageSource *DamageSource::bypassInvul()
|
||||
_bypassInvul = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
bool DamageSource::isCritical()
|
||||
{
|
||||
return _isCritical;
|
||||
}
|
||||
DamageSource *DamageSource::setIsCritical()
|
||||
{
|
||||
_isCritical = true;
|
||||
return this;
|
||||
}
|
||||
DamageSource *DamageSource::setIsFire()
|
||||
{
|
||||
isFireSource = true;
|
||||
|
||||
@@ -47,8 +47,11 @@ private:
|
||||
bool _scalesWithDifficulty;
|
||||
bool _isMagic;
|
||||
bool _isExplosion;
|
||||
bool _isCritical;
|
||||
|
||||
public:
|
||||
bool isCritical();
|
||||
DamageSource *setIsCritical();
|
||||
bool isProjectile();
|
||||
DamageSource *setProjectile();
|
||||
bool isExplosion();
|
||||
|
||||
+19
-15
@@ -27,13 +27,17 @@
|
||||
|
||||
const wstring Entity::RIDING_TAG = L"Riding";
|
||||
|
||||
int Entity::entityCounter = 2048; // 4J - changed initialiser to 2048, as we are using range 0 - 2047 as special unique smaller ids for things that need network tracked
|
||||
//int Entity::entityCounter = 2048; // 4J - changed initialiser to 2048, as we are using range 0 - 2047 as special unique smaller ids for things that need network tracked
|
||||
int Entity::entityCounter = 16384; //now using full range of 0 - 16383, limit is 32k but we shouldnt need that yet
|
||||
DWORD Entity::tlsIdx = TlsAlloc();
|
||||
|
||||
// 4J - added getSmallId & freeSmallId methods
|
||||
unsigned int Entity::entityIdUsedFlags[2048/32] = {0};
|
||||
unsigned int Entity::entityIdWanderFlags[2048/32] = {0};
|
||||
unsigned int Entity::entityIdRemovingFlags[2048/32] = {0};
|
||||
//unsigned int Entity::entityIdUsedFlags[2048/32] = {0};
|
||||
//unsigned int Entity::entityIdWanderFlags[2048/32] = {0};
|
||||
//unsigned int Entity::entityIdRemovingFlags[2048/32] = {0};
|
||||
unsigned int Entity::entityIdUsedFlags[16384/32] = {0};
|
||||
unsigned int Entity::entityIdWanderFlags[16384/32] = {0};
|
||||
unsigned int Entity::entityIdRemovingFlags[16384/32] = {0};
|
||||
int Entity::extraWanderIds[EXTRA_WANDER_MAX] = {0};
|
||||
int Entity::extraWanderTicks = 0;
|
||||
int Entity::extraWanderCount = 0;
|
||||
@@ -65,7 +69,7 @@ int Entity::getSmallId()
|
||||
}
|
||||
}
|
||||
|
||||
for( int i = 0; i < (2048 / 32 ); i++ )
|
||||
for( int i = 0; i < (16384 / 32 ); i++ )
|
||||
{
|
||||
unsigned int uiFlags = *puiUsedFlags;
|
||||
if( uiFlags != 0xffffffff )
|
||||
@@ -102,7 +106,7 @@ int Entity::getSmallId()
|
||||
|
||||
if (entityCounter == 0x7ffffff)
|
||||
{
|
||||
entityCounter = 2048;
|
||||
entityCounter = 16384;
|
||||
}
|
||||
return fallbackId;
|
||||
#else
|
||||
@@ -116,7 +120,7 @@ void Entity::countFlagsForPIX()
|
||||
{
|
||||
int freecount = 0;
|
||||
unsigned int *puiUsedFlags = entityIdUsedFlags;
|
||||
for( int i = 0; i < (2048 / 32 ); i++ )
|
||||
for( int i = 0; i < (16384 / 32 ); i++ )
|
||||
{
|
||||
unsigned int uiFlags = *puiUsedFlags;
|
||||
if( uiFlags != 0xffffffff )
|
||||
@@ -134,7 +138,7 @@ void Entity::countFlagsForPIX()
|
||||
puiUsedFlags++;
|
||||
}
|
||||
PIXAddNamedCounter(freecount,"Small Ids free");
|
||||
PIXAddNamedCounter(2048 - freecount,"Small Ids used");
|
||||
PIXAddNamedCounter(16384 - freecount,"Small Ids used");
|
||||
}
|
||||
|
||||
void Entity::resetSmallId()
|
||||
@@ -149,7 +153,7 @@ void Entity::resetSmallId()
|
||||
void Entity::freeSmallId(int index)
|
||||
{
|
||||
if( ( (size_t)TlsGetValue(tlsIdx) ) == 0 ) return; // Don't do anything with small ids if this isn't the server thread
|
||||
if( index >= 2048 ) return; // Don't do anything if this isn't a short id
|
||||
if( index >= 16384 ) return; // Don't do anything if this isn't a short id
|
||||
|
||||
unsigned int i = index / 32;
|
||||
unsigned int j = index % 32;
|
||||
@@ -172,7 +176,7 @@ void Entity::useSmallIds()
|
||||
void Entity::considerForExtraWandering(bool enable)
|
||||
{
|
||||
if( ( (size_t)TlsGetValue(tlsIdx) ) == 0 ) return; // Don't do anything with small ids if this isn't the server thread
|
||||
if( entityId >= 2048 ) return; // Don't do anything if this isn't a short id
|
||||
if( entityId >= 16384 ) return; // Don't do anything if this isn't a short id
|
||||
|
||||
unsigned int i = entityId / 32;
|
||||
unsigned int j = entityId % 32;
|
||||
@@ -192,7 +196,7 @@ void Entity::considerForExtraWandering(bool enable)
|
||||
bool Entity::isExtraWanderingEnabled()
|
||||
{
|
||||
if( ( (size_t)TlsGetValue(tlsIdx) ) == 0 ) return false; // Don't do anything with small ids if this isn't the server thread
|
||||
if( entityId >= 2048 ) return false; // Don't do anything if this isn't a short id
|
||||
if( entityId >= 16384 ) return false; // Don't do anything if this isn't a short id
|
||||
|
||||
for( int i = 0; i < extraWanderCount; i++ )
|
||||
{
|
||||
@@ -224,12 +228,12 @@ void Entity::tickExtraWandering()
|
||||
int entityId = 0;
|
||||
if( extraWanderCount )
|
||||
{
|
||||
entityId = ( extraWanderIds[ extraWanderCount - 1 ] + 1 ) % 2048;
|
||||
entityId = ( extraWanderIds[ extraWanderCount - 1 ] + 1 ) % 16384;
|
||||
}
|
||||
|
||||
extraWanderCount = 0;
|
||||
|
||||
for( int k = 0; ( k < 2048 ) && ( extraWanderCount < EXTRA_WANDER_MAX); k++ )
|
||||
for( int k = 0; ( k < 16384 ) && ( extraWanderCount < EXTRA_WANDER_MAX); k++ )
|
||||
{
|
||||
unsigned int i = entityId / 32;
|
||||
unsigned int j = entityId % 32;
|
||||
@@ -241,7 +245,7 @@ void Entity::tickExtraWandering()
|
||||
// printf("%d, ", entityId);
|
||||
}
|
||||
|
||||
entityId = ( entityId + 1 ) % 2048;
|
||||
entityId = ( entityId + 1 ) % 16384;
|
||||
}
|
||||
// printf("\n");
|
||||
}
|
||||
@@ -261,7 +265,7 @@ void Entity::_init(bool useSmallId, Level *level)
|
||||
else
|
||||
{
|
||||
entityId = Entity::entityCounter++;
|
||||
if(entityCounter == 0x7ffffff ) entityCounter = 2048;
|
||||
if(entityCounter == 0x7ffffff ) entityCounter = 16384;
|
||||
}
|
||||
|
||||
viewScale = 1.0;
|
||||
|
||||
@@ -382,9 +382,12 @@ private:
|
||||
|
||||
int getSmallId();
|
||||
void freeSmallId(int index);
|
||||
static unsigned int entityIdUsedFlags[2048/32];
|
||||
static unsigned int entityIdWanderFlags[2048/32];
|
||||
static unsigned int entityIdRemovingFlags[2048/32];
|
||||
//static unsigned int entityIdUsedFlags[2048/32];
|
||||
//static unsigned int entityIdWanderFlags[2048/32];
|
||||
//static unsigned int entityIdRemovingFlags[2048/32];
|
||||
static unsigned int entityIdUsedFlags[16384/32];
|
||||
static unsigned int entityIdWanderFlags[16384/32];
|
||||
static unsigned int entityIdRemovingFlags[16384/32];
|
||||
static int extraWanderIds[EXTRA_WANDER_MAX];
|
||||
static int extraWanderCount;
|
||||
static int extraWanderTicks;
|
||||
|
||||
@@ -5,6 +5,9 @@ class EntityEvent
|
||||
public:
|
||||
static const BYTE JUMP = 1;
|
||||
static const BYTE HURT = 2;
|
||||
//New
|
||||
static const BYTE HURT_CRITICAL = 19;
|
||||
static const BYTE DEATH_CRITICAL = 20;
|
||||
static const BYTE DEATH = 3;
|
||||
static const BYTE START_ATTACKING = 4;
|
||||
static const BYTE STOP_ATTACKING = 5;
|
||||
|
||||
+11
-11
@@ -4699,19 +4699,19 @@ bool Level::canCreateMore(eINSTANCEOF type, ESPAWN_TYPE spawnType)
|
||||
break;
|
||||
case eTYPE_CHICKEN:
|
||||
count = countInstanceOf( eTYPE_CHICKEN, true);
|
||||
max = MobCategory::MAX_XBOX_CHICKENS_WITH_SPAWN_EGG;
|
||||
max = MobCategory::maxChickensWithSpawnEgg();
|
||||
break;
|
||||
case eTYPE_WOLF:
|
||||
count = countInstanceOf( eTYPE_WOLF, true);
|
||||
max = MobCategory::MAX_XBOX_WOLVES_WITH_SPAWN_EGG;
|
||||
max = MobCategory::maxWolvesWithSpawnEgg();
|
||||
break;
|
||||
case eTYPE_MUSHROOMCOW:
|
||||
count = countInstanceOf( eTYPE_MUSHROOMCOW, true);
|
||||
max = MobCategory::MAX_XBOX_MUSHROOMCOWS_WITH_SPAWN_EGG;
|
||||
max = MobCategory::maxMushroomCowsWithSpawnEgg();
|
||||
break;
|
||||
case eTYPE_SQUID:
|
||||
count = countInstanceOf( eTYPE_SQUID, true);
|
||||
max = MobCategory::MAX_XBOX_SQUIDS_WITH_SPAWN_EGG;
|
||||
max = MobCategory::maxSquidsWithSpawnEgg();
|
||||
break;
|
||||
case eTYPE_SNOWMAN:
|
||||
count = countInstanceOf( eTYPE_SNOWMAN, true);
|
||||
@@ -4729,18 +4729,18 @@ bool Level::canCreateMore(eINSTANCEOF type, ESPAWN_TYPE spawnType)
|
||||
if((type & eTYPE_ANIMALS_SPAWN_LIMIT_CHECK) == eTYPE_ANIMALS_SPAWN_LIMIT_CHECK)
|
||||
{
|
||||
count = countInstanceOf( eTYPE_ANIMALS_SPAWN_LIMIT_CHECK, false);
|
||||
max = MobCategory::MAX_XBOX_ANIMALS_WITH_SPAWN_EGG;
|
||||
max = MobCategory::maxAnimalsWithSpawnEgg();
|
||||
}
|
||||
// 4J: Use eTYPE_ENEMY instead of monster (slimes and ghasts aren't monsters)
|
||||
else if(Entity::instanceof(type, eTYPE_ENEMY))
|
||||
{
|
||||
count = countInstanceOf(eTYPE_ENEMY, false);
|
||||
max = MobCategory::MAX_XBOX_MONSTERS_WITH_SPAWN_EGG;
|
||||
max = MobCategory::maxMonstersWithSpawnEgg();
|
||||
}
|
||||
else if( (type & eTYPE_AMBIENT) == eTYPE_AMBIENT)
|
||||
{
|
||||
count = countInstanceOf( eTYPE_AMBIENT, false);
|
||||
max = MobCategory::MAX_AMBIENT_WITH_SPAWN_EGG;
|
||||
max = MobCategory::maxAmbientWithSpawnEgg();
|
||||
}
|
||||
// 4J: Added minecart and boats
|
||||
else if (Entity::instanceof(type, eTYPE_MINECART))
|
||||
@@ -4765,21 +4765,21 @@ bool Level::canCreateMore(eINSTANCEOF type, ESPAWN_TYPE spawnType)
|
||||
break;
|
||||
case eTYPE_CHICKEN:
|
||||
count = countInstanceOf( eTYPE_CHICKEN, true);
|
||||
max = MobCategory::MAX_XBOX_CHICKENS_WITH_BREEDING;
|
||||
max = MobCategory::maxChickensWithBreeding();
|
||||
break;
|
||||
case eTYPE_WOLF:
|
||||
count = countInstanceOf( eTYPE_WOLF, true);
|
||||
max = MobCategory::MAX_XBOX_WOLVES_WITH_BREEDING;
|
||||
max = MobCategory::maxWolvesWithBreeding();
|
||||
break;
|
||||
case eTYPE_MUSHROOMCOW:
|
||||
count = countInstanceOf( eTYPE_MUSHROOMCOW, true);
|
||||
max = MobCategory::MAX_XBOX_MUSHROOMCOWS_WITH_BREEDING;
|
||||
max = MobCategory::maxMushroomCowsWithBreeding();
|
||||
break;
|
||||
default:
|
||||
if((type & eTYPE_ANIMALS_SPAWN_LIMIT_CHECK) == eTYPE_ANIMALS_SPAWN_LIMIT_CHECK)
|
||||
{
|
||||
count = countInstanceOf( eTYPE_ANIMALS_SPAWN_LIMIT_CHECK, false);
|
||||
max = MobCategory::MAX_XBOX_ANIMALS_WITH_BREEDING;
|
||||
max = MobCategory::maxAnimalsWithBreeding();
|
||||
}
|
||||
else if( (type & eTYPE_MONSTER) == eTYPE_MONSTER)
|
||||
{
|
||||
|
||||
@@ -874,7 +874,12 @@ bool LivingEntity::hurt(DamageSource *source, float dmg)
|
||||
|
||||
if (sound)
|
||||
{
|
||||
level->broadcastEntityEvent(shared_from_this(), EntityEvent::HURT);
|
||||
if (source->isCritical()) {
|
||||
level->broadcastEntityEvent(shared_from_this(), EntityEvent::HURT_CRITICAL);
|
||||
}
|
||||
else {
|
||||
level->broadcastEntityEvent(shared_from_this(), EntityEvent::HURT);
|
||||
}
|
||||
if (source != DamageSource::drown) markHurt();
|
||||
if (sourceEntity != nullptr)
|
||||
{
|
||||
@@ -897,12 +902,19 @@ bool LivingEntity::hurt(DamageSource *source, float dmg)
|
||||
MemSect(31);
|
||||
if (getHealth() <= 0)
|
||||
{
|
||||
if (sound) playSound(getDeathSound(), getSoundVolume(), getVoicePitch());
|
||||
if (sound) {
|
||||
//New: both death AND hurt sounds should play critical sound as well.
|
||||
if (source->isCritical()) playSound(getCriticalSound(), getSoundVolume(), getVoicePitch());
|
||||
playSound(getDeathSound(), getSoundVolume(), getVoicePitch());
|
||||
};
|
||||
die(source);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (sound) playSound(getHurtSound(), getSoundVolume(), getVoicePitch());
|
||||
if (sound) {
|
||||
if (source->isCritical()) playSound(getCriticalSound(), getSoundVolume(), getVoicePitch());
|
||||
playSound(getHurtSound(), getSoundVolume(), getVoicePitch());
|
||||
}
|
||||
}
|
||||
MemSect(0);
|
||||
|
||||
@@ -981,7 +993,11 @@ void LivingEntity::die(DamageSource *source)
|
||||
}
|
||||
}
|
||||
|
||||
level->broadcastEntityEvent(shared_from_this(), EntityEvent::DEATH);
|
||||
if (source->isCritical()) {
|
||||
level->broadcastEntityEvent(shared_from_this(), EntityEvent::DEATH_CRITICAL);
|
||||
} else {
|
||||
level->broadcastEntityEvent(shared_from_this(), EntityEvent::DEATH);
|
||||
}
|
||||
}
|
||||
|
||||
void LivingEntity::dropEquipment(bool byPlayer, int playerBonusLevel)
|
||||
@@ -1014,7 +1030,10 @@ int LivingEntity::getHurtSound()
|
||||
{
|
||||
return eSoundType_DAMAGE_HURT;
|
||||
}
|
||||
|
||||
int LivingEntity::getCriticalSound()
|
||||
{
|
||||
return eSoundType_DAMAGE_CRITICAL;
|
||||
}
|
||||
int LivingEntity::getDeathSound()
|
||||
{
|
||||
return eSoundType_DAMAGE_HURT;
|
||||
@@ -1236,7 +1255,8 @@ void LivingEntity::swing()
|
||||
|
||||
void LivingEntity::handleEntityEvent(byte id)
|
||||
{
|
||||
if (id == EntityEvent::HURT)
|
||||
//These gotta be in parentheses
|
||||
if ((id == EntityEvent::HURT) || (id == EntityEvent::HURT_CRITICAL))
|
||||
{
|
||||
walkAnimSpeed = 1.5f;
|
||||
|
||||
@@ -1246,19 +1266,30 @@ void LivingEntity::handleEntityEvent(byte id)
|
||||
|
||||
MemSect(31);
|
||||
// 4J-PB -added because villagers have no sounds
|
||||
int iHurtSound=getHurtSound();
|
||||
int iHurtSound = getHurtSound();
|
||||
int iCritSound = getCriticalSound();
|
||||
if(iHurtSound!=-1)
|
||||
{
|
||||
playSound(iHurtSound, getSoundVolume(), (random->nextFloat() - random->nextFloat()) * 0.2f + 1.0f);
|
||||
}
|
||||
if(iCritSound!=-1 && (id == EntityEvent::HURT_CRITICAL))
|
||||
{
|
||||
playSound(iCritSound, getSoundVolume(), (random->nextFloat() - random->nextFloat()) * 0.2f + 1.0f);
|
||||
}
|
||||
MemSect(0);
|
||||
hurt(DamageSource::genericSource, 0);
|
||||
}
|
||||
else if (id == EntityEvent::DEATH)
|
||||
else if ((id == EntityEvent::DEATH) || (id == EntityEvent::DEATH_CRITICAL))
|
||||
{
|
||||
MemSect(31);
|
||||
// 4J-PB -added because villagers have no sounds
|
||||
int iDeathSound=getDeathSound();
|
||||
int iCritSound = getCriticalSound();
|
||||
|
||||
if (iCritSound != -1 && (id == EntityEvent::DEATH_CRITICAL))
|
||||
{
|
||||
playSound(iCritSound, getSoundVolume(), (random->nextFloat() - random->nextFloat()) * 0.2f + 1.0f);
|
||||
}
|
||||
if(iDeathSound!=-1)
|
||||
{
|
||||
playSound(iDeathSound, getSoundVolume(), (random->nextFloat() - random->nextFloat()) * 0.2f + 1.0f);
|
||||
|
||||
@@ -190,6 +190,7 @@ public:
|
||||
virtual void knockback(shared_ptr<Entity> source, float dmg, double xd, double zd);
|
||||
|
||||
protected:
|
||||
virtual int getCriticalSound();
|
||||
virtual int getHurtSound();
|
||||
virtual int getDeathSound();
|
||||
|
||||
|
||||
@@ -58,6 +58,24 @@ int MobCategory::getMaxInstancesPerLevel() // 4J added
|
||||
return m_maxPerLevel;
|
||||
}
|
||||
|
||||
void MobCategory::setMaxInstancesPerLevel(int max)
|
||||
{
|
||||
m_maxPerLevel = max;
|
||||
}
|
||||
|
||||
int MobCategory::maxAnimalsWithBreeding() { return creature->getMaxInstancesPerLevel() + 20; }
|
||||
int MobCategory::maxChickensWithBreeding() { return creature_chicken->getMaxInstancesPerLevel() + 8; }
|
||||
int MobCategory::maxMushroomCowsWithBreeding() { return creature_mushroomcow->getMaxInstancesPerLevel() + 20; }
|
||||
int MobCategory::maxWolvesWithBreeding() { return creature_wolf->getMaxInstancesPerLevel() + 8; }
|
||||
|
||||
int MobCategory::maxAnimalsWithSpawnEgg() { return maxAnimalsWithBreeding() + 20; }
|
||||
int MobCategory::maxChickensWithSpawnEgg() { return maxChickensWithBreeding() + 10; }
|
||||
int MobCategory::maxWolvesWithSpawnEgg() { return maxWolvesWithBreeding() + 10; }
|
||||
int MobCategory::maxMonstersWithSpawnEgg() { return monster->getMaxInstancesPerLevel() + 20; }
|
||||
int MobCategory::maxMushroomCowsWithSpawnEgg() { return maxMushroomCowsWithBreeding() + 8; }
|
||||
int MobCategory::maxSquidsWithSpawnEgg() { return waterCreature->getMaxInstancesPerLevel() + 8; }
|
||||
int MobCategory::maxAmbientWithSpawnEgg() { return ambient->getMaxInstancesPerLevel() + 8; }
|
||||
|
||||
Material *MobCategory::getSpawnPositionMaterial()
|
||||
{
|
||||
return (Material *) spawnPositionMaterial;
|
||||
|
||||
@@ -19,20 +19,25 @@ public:
|
||||
static const int CONSOLE_SQUID_HARD_LIMIT = 5;
|
||||
static const int MAX_CONSOLE_BOSS = 1; // Max number of bosses (enderdragon/wither)
|
||||
|
||||
static const int MAX_XBOX_ANIMALS_WITH_BREEDING = CONSOLE_ANIMALS_HARD_LIMIT + 20; // Max number of animals that we can produce (in total), when breeding
|
||||
static const int MAX_XBOX_CHICKENS_WITH_BREEDING = MAX_XBOX_CHICKENS + 8; // Max number of chickens that we can produce (in total), when breeding/hatching
|
||||
static const int MAX_XBOX_MUSHROOMCOWS_WITH_BREEDING = MAX_XBOX_MUSHROOMCOWS + 20; // Max number of mushroom cows that we can produce (in total), when breeding
|
||||
static const int MAX_XBOX_WOLVES_WITH_BREEDING = MAX_XBOX_WOLVES + 8; // Max number of wolves that we can produce (in total), when breeding
|
||||
// 4J Villager breeding/egg limits - villagers are not a MobCategory so these stay hardcoded
|
||||
static const int MAX_VILLAGERS_WITH_BREEDING = 35;
|
||||
static const int MAX_XBOX_VILLAGERS_WITH_SPAWN_EGG = MAX_VILLAGERS_WITH_BREEDING + 15;
|
||||
|
||||
static const int MAX_XBOX_ANIMALS_WITH_SPAWN_EGG = MAX_XBOX_ANIMALS_WITH_BREEDING + 20;
|
||||
static const int MAX_XBOX_CHICKENS_WITH_SPAWN_EGG = MAX_XBOX_CHICKENS_WITH_BREEDING + 10;
|
||||
static const int MAX_XBOX_WOLVES_WITH_SPAWN_EGG = MAX_XBOX_WOLVES_WITH_BREEDING + 10;
|
||||
static const int MAX_XBOX_MONSTERS_WITH_SPAWN_EGG = CONSOLE_MONSTERS_HARD_LIMIT + 20;
|
||||
static const int MAX_XBOX_VILLAGERS_WITH_SPAWN_EGG = MAX_VILLAGERS_WITH_BREEDING + 15; // 4J-PB - increased this limit due to player requests
|
||||
static const int MAX_XBOX_MUSHROOMCOWS_WITH_SPAWN_EGG = MAX_XBOX_MUSHROOMCOWS_WITH_BREEDING + 8;
|
||||
static const int MAX_XBOX_SQUIDS_WITH_SPAWN_EGG = CONSOLE_SQUID_HARD_LIMIT + 8;
|
||||
static const int MAX_AMBIENT_WITH_SPAWN_EGG = CONSOLE_AMBIENT_HARD_LIMIT + 8;
|
||||
// Breeding headroom above the natural spawn cap. Read at call time so these
|
||||
// respect max-* overrides from server.properties.
|
||||
static int maxAnimalsWithBreeding();
|
||||
static int maxChickensWithBreeding();
|
||||
static int maxMushroomCowsWithBreeding();
|
||||
static int maxWolvesWithBreeding();
|
||||
|
||||
// Spawn-egg headroom above the natural (or breeding) cap.
|
||||
static int maxAnimalsWithSpawnEgg();
|
||||
static int maxChickensWithSpawnEgg();
|
||||
static int maxWolvesWithSpawnEgg();
|
||||
static int maxMonstersWithSpawnEgg();
|
||||
static int maxMushroomCowsWithSpawnEgg();
|
||||
static int maxSquidsWithSpawnEgg();
|
||||
static int maxAmbientWithSpawnEgg();
|
||||
|
||||
/*
|
||||
Maximum animals = 50 + 20 + 20 = 90
|
||||
@@ -65,7 +70,7 @@ public:
|
||||
|
||||
private:
|
||||
const int m_max;
|
||||
const int m_maxPerLevel;
|
||||
int m_maxPerLevel;
|
||||
const Material *spawnPositionMaterial;
|
||||
const bool m_isFriendly;
|
||||
const bool m_isPersistent;
|
||||
@@ -79,6 +84,7 @@ public:
|
||||
const eINSTANCEOF getEnumBaseClass(); // 4J added
|
||||
int getMaxInstancesPerChunk();
|
||||
int getMaxInstancesPerLevel(); // 4J added
|
||||
void setMaxInstancesPerLevel(int max); // 4J added
|
||||
Material *getSpawnPositionMaterial();
|
||||
bool isFriendly();
|
||||
bool isSingleType();
|
||||
|
||||
@@ -35,7 +35,7 @@ void MoveEntityPacket::read(DataInputStream *dis) //throws IOException
|
||||
|
||||
void MoveEntityPacket::write(DataOutputStream *dos) //throws IOException
|
||||
{
|
||||
if( (id < 0 ) || (id >= 2048 ) )
|
||||
if( (id < 0 ) || (id >= 16384 ) )
|
||||
{
|
||||
// We shouln't be tracking an entity that doesn't have a short type of id
|
||||
__debugbreak();
|
||||
|
||||
@@ -19,7 +19,7 @@ MoveEntityPacketSmall::MoveEntityPacketSmall()
|
||||
|
||||
MoveEntityPacketSmall::MoveEntityPacketSmall(int id)
|
||||
{
|
||||
if( (id < 0 ) || (id >= 2048 ) )
|
||||
if( (id < 0 ) || (id >= 16384 ) )
|
||||
{
|
||||
// We shouln't be tracking an entity that doesn't have a short type of id
|
||||
__debugbreak();
|
||||
@@ -42,7 +42,7 @@ void MoveEntityPacketSmall::read(DataInputStream *dis) //throws IOException
|
||||
|
||||
void MoveEntityPacketSmall::write(DataOutputStream *dos) //throws IOException
|
||||
{
|
||||
if( (id < 0 ) || (id >= 2048 ) )
|
||||
if( (id < 0 ) || (id >= 16384 ) )
|
||||
{
|
||||
// We shouln't be tracking an entity that doesn't have a short type of id
|
||||
__debugbreak();
|
||||
@@ -99,7 +99,7 @@ void MoveEntityPacketSmall::PosRot::read(DataInputStream *dis) //throws IOExcept
|
||||
|
||||
void MoveEntityPacketSmall::PosRot::write(DataOutputStream *dos) //throws IOException
|
||||
{
|
||||
if( (id < 0 ) || (id >= 2048 ) )
|
||||
if( (id < 0 ) || (id >= 16384 ) )
|
||||
{
|
||||
// We shouln't be tracking an entity that doesn't have a short type of id
|
||||
__debugbreak();
|
||||
@@ -138,7 +138,7 @@ void MoveEntityPacketSmall::Pos::read(DataInputStream *dis) //throws IOException
|
||||
|
||||
void MoveEntityPacketSmall::Pos::write(DataOutputStream *dos) //throws IOException
|
||||
{
|
||||
if( (id < 0 ) || (id >= 2048 ) )
|
||||
if( (id < 0 ) || (id >= 16384 ) )
|
||||
{
|
||||
// We shouln't be tracking an entity that doesn't have a short type of id
|
||||
__debugbreak();
|
||||
@@ -176,7 +176,7 @@ void MoveEntityPacketSmall::Rot::read(DataInputStream *dis) //throws IOException
|
||||
|
||||
void MoveEntityPacketSmall::Rot::write(DataOutputStream *dos) //throws IOException
|
||||
{
|
||||
if( (id < 0 ) || (id >= 2048 ) )
|
||||
if( (id < 0 ) || (id >= 16384 ) )
|
||||
{
|
||||
// We shouln't be tracking an entity that doesn't have a short type of id
|
||||
__debugbreak();
|
||||
|
||||
@@ -1631,6 +1631,10 @@ void Player::attack(shared_ptr<Entity> entity)
|
||||
}
|
||||
|
||||
DamageSource *damageSource = DamageSource::playerAttack(dynamic_pointer_cast<Player>(shared_from_this()));
|
||||
|
||||
if (bCrit) {
|
||||
damageSource->setIsCritical();
|
||||
}
|
||||
bool wasHurt = entity->hurt(damageSource, dmg);
|
||||
delete damageSource;
|
||||
if (wasHurt)
|
||||
|
||||
@@ -20,7 +20,8 @@ class SharedConstants
|
||||
static wstring readAcceptableChars();
|
||||
|
||||
public:
|
||||
static const int maxChatLength = 100;
|
||||
static const int maxChatLength = 255;
|
||||
static const int maxVisibleLength = 100; //to be changed
|
||||
static wstring acceptableLetters;
|
||||
|
||||
static const int ILLEGAL_FILE_CHARACTERS_LENGTH = 15;
|
||||
|
||||
@@ -493,6 +493,10 @@ void Socket::SocketOutputStreamNetwork::writeWithFlags(byteArray b, unsigned int
|
||||
}
|
||||
else
|
||||
{
|
||||
// Don't write on a closing socket: an orphan whose smallId has been
|
||||
// recycled would otherwise leak a packet onto the new client.
|
||||
if( m_socket->isClosing() ) return;
|
||||
|
||||
XRNM_SEND_BUFFER buffer;
|
||||
buffer.pbyData = &b[offset];
|
||||
buffer.dwDataSize = length;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user