73 Commits

Author SHA1 Message Date
Fireblade da91372804 chore: clean up paper doll code for redundancy 2026-04-25 12:09:30 -04:00
Fireblade 1672dbab05 fix: paper doll viewmodel on non-16:9 window aspect ratios 2026-04-24 22:54:06 -04:00
itsRevela 18a08ec693 docs: README entry for FourKit dispatch performance fix 2026-04-24 21:35:34 -05:00
itsRevela b64b1f17db perf(fourkit): server gc, fast-path event dispatch, tps probe 2026-04-24 21:31:06 -05:00
itsRevela 6db68bda06 test(fourkit): add setblock and chatcolor subcommands to test plugin 2026-04-18 20:22:26 -05:00
sylvessa cb941db3ad i forgot to add chunk events to the docs 2026-04-18 19:57:20 -05:00
sylvessa b441998732 new funcs in Block, chatcolor enum 2026-04-18 19:57:14 -05:00
itsRevela 8dd9d92ad6 feat: add FourKit test plugin for chunk/inventory/disenchant verification 2026-04-18 19:15:56 -05:00
sylvessa 2e0f4f91d6 fix disenchant bugs from setdurability etc 2026-04-18 18:16:29 -05:00
sylvessa f9cac6395b fill in old stubs regaridng chunk stuff & another inv fix 2026-04-18 18:16:24 -05:00
sylvessa a9c735e7aa fix bugs found regarding inventories 2026-04-18 18:15:03 -05:00
sylvessa a3221c9e14 chunk additions 2026-04-18 18:14:58 -05:00
itsRevela 84e4a77409 fix: exit save sometimes dropped during shutdown race 2026-04-18 10:56:54 -05:00
itsRevela 68c3440394 docs: note breeding/spawn-egg scaling with mob cap config 2026-04-18 10:19:27 -05:00
itsRevela 7e586e0d49 fix: scale breeding and spawn-egg caps with configured mob caps 2026-04-18 10:15:33 -05:00
itsRevela defd9ebd00 docs: update readme with recent fixes and mob cap config 2026-04-18 09:39:09 -05:00
itsRevela ffdad03aca feat: configurable mob spawn caps via server.properties 2026-04-18 09:31:45 -05:00
itsRevela c297b26f36 fix: duplicate join message on FourKit servers 2026-04-18 09:10:47 -05:00
itsRevela b4a34df1a9 fix: apostrophes rendering as ' in Iggy HTML chat labels 2026-04-17 16:55:56 -05:00
itsRevela ee3cf5117b chore: exclude .exp and .lib build artifacts from release zips 2026-04-17 15:31:53 -05:00
itsRevela afa3e22715 fix: force backbuffer clear alpha to 1.0 to prevent DWM window transparency 2026-04-17 12:43:46 -05:00
itsRevela cf81dd9d13 fix: apostrophes and quotes displaying as HTML entities in chat 2026-04-17 12:20:58 -05:00
itsRevela fde5fbe6f4 fix: force VSync on main menu to prevent GPU coil whine 2026-04-17 11:55:04 -05:00
itsRevela af39797303 chore: add PDB to nightly release, update readme
- Enable /Zi and /DEBUG for Release builds to generate PDB files
- Stage Minecraft.Client.pdb as standalone release asset
- Exclude .pdb from the zip release (dev-only artifact)
- Update release notes to separate player and developer instructions
- Update readme Latest section with recent bug fixes
2026-04-17 07:21:29 -05:00
itsRevela ce2efc1a4d fix: crash when opening UI scenes at sub-720p window size
SWF movie loading crashed with __debugbreak when the window height was
below 720px (e.g. after leaving exclusive fullscreen). The fallback
chain only tried 720.swf which doesn't exist. Now falls back through
720 -> 1080 so scenes always find a valid SWF.
2026-04-17 07:07:02 -05:00
itsRevela eb5aa9fa8f fix: chat color codes not rendering in singleplayer
PlayerConnection::handleChat called FormatChatMessage with
applyStyling=false before broadcasting, which stripped all color codes
from the message. The client then had nothing left to format. Send the
raw message and let the client handle formatting on receive.
2026-04-17 07:06:39 -05:00
itsRevela 59f9dbc8c6 fix: hardcore mode UI not locking difficulty/gamemode on world load
Three issues fixed:

- Save file path used hardcoded saveData.ms but new 4JLibs names files
  as <title>.ms. ReadLevelNameFromSaveFile now constructs the correct
  path with fallback to saveData.ms for old saves.
- The level.dat code path for reading the hardcore flag (sidecar rename)
  returned early without ever parsing level.dat. Now stores the sidecar
  name and continues to read the hardcore flag from NBT.
- The thumbnail host options path could overwrite m_bHardcore to false.
  Now only upgrades to true, never downgrades.
- Load menu constructor and tick handler both lock difficulty slider to
  Hardcore and gamemode to Survival when hardcore is detected.
- Hide title logo on load menu to match create world menu.
2026-04-17 07:06:26 -05:00
itsRevela bc6e445f0c fix: capture save thumbnails directly from D3D11 backbuffer
The new 4JLibs CaptureThumbnail produces all-black 64x64 PNGs because
its internal m_backBufferTexture copy fails silently. Bypass it entirely
and capture from the swap chain backbuffer using the same proven approach
as our F2 screenshot, with center-crop and downsample to 64x64 PNG.
2026-04-17 07:06:02 -05:00
itsRevela ec5ec84407 feat: add 4JLibs comparison tooling
Tools for comparing 4JLibs binary changes between git refs:
- compare-4jlibs.py: Extracts libs, parses symbol tables via dumpbin,
  demangles with undname, generates structured diff reports
- extract_lib.py: Extracts .obj members from COFF .lib archives
- ExportLibInfo.java: Ghidra headless script for non-LTCG object files
- list-lib-symbols.sh / compare-4jlibs.sh: Shell wrappers
2026-04-17 05:55:25 -05:00
itsRevela 019b790fb3 chore: sync upstream MinecraftConsoles through 1a552fbd 2026-04-17 05:53:46 -05:00
DrPerkyLegit 9ddb95e7ad Fix broken Chat Formatting behavior (#1520)
- Remove shadowcolor from font tags (Iggy doesn't support multiple)
- Enforce HTML text mode on chat labels and jukebox
- Move IDS translatable pattern matching into FormatChatMessage
- Add §r (reset) color code support
- Fix message truncation to count visible characters, skipping HTML tags
- Fix CJK truncation using raw index instead of visible char count
2026-04-17 05:52:26 -05:00
dtentiion 8464bfd448 fix: Skin pack names showing only first letter after 4JLibs update (#1518) 2026-04-17 05:49:22 -05:00
Loki Rautio 0e0f8f696d feat: Upgrade to 4JLibs libraries
Includes fixes and some modernizations compared to the original 4J
library binaries. Also introduces functionality to support stuff like
F2 screenshots, etc. This is basically the beginning of modernizing the
codebase.
2026-04-17 05:40:38 -05:00
Loki 6ef535d53e Add new Discord invite to issue config 2026-04-17 05:39:51 -05:00
Alisa Vulpes 004cd72ba5 Add .DS_Store to .gitignore (#1509) 2026-04-17 05:38:39 -05:00
DrPerkyLegit 1a552fbd0c Fix broken Chat Formatting behavior (#1520)
* fix formatting in chat

iggy doesnt like multiple shadow colors

* enforce html labels

* dont format before sending packet, client formats anyway

* move translateables to chat format, restore original empty message results

* fix crappy string cutoff

* forgot a line on last commit, reset color support

* restore function to strip styling from player messages
2026-04-16 22:41:50 -05:00
dtentiion cccfd860b9 fix: Skin pack names showing only first letter after 4JLibs update (#1518) 2026-04-16 22:35:14 -05:00
Revela d0094a6409 Add F2 screenshot functionality (#1262)
- Updated `EControllerActions` to include `MINECRAFT_ACTION_SCREENSHOT`.
- Added conditional compilation for `stb_image_write.h` in `Minecraft.cpp`.
- Modified `run_middle()` to handle screenshot key press.
- Updated `tick()` to capture and save screenshots as PNG files.
- Introduced `KEY_SCREENSHOT` in `KeyboardMouseInput.h` mapped to F2.
- Added `stb_image_write.h` for image writing capabilities.
2026-04-16 22:33:30 -05:00
Loki Rautio 78d4bc80cf feat: Upgrade to 4JLibs libraries
Includes fixes and some modernizations compared to the original 4J
library binaries. Also introduces functionality to support stuff like
F2 screenshots, etc. This is basically the beginning of modernizing the
codebase.
2026-04-15 19:07:15 -05:00
Loki 6595b3820d Add new Discord invite to issue config 2026-04-15 18:22:01 -05:00
Alisa Vulpes 7f0237a286 Add .DS_Store to .gitignore (#1509)
macOS automatically creates a .DS_Store file if any custom macOS attributes are set to files (like visual position in Finder). It's redundant for this repo and to avoid that to be accidentally committed/pushed, I added it to .gitignore.
2026-04-15 17:26:27 -05:00
itsRevela d694cd1c9d chore: sync upstream MinecraftConsoles through 2fba264c 2026-04-15 05:25:33 -05:00
Loki Rautio 9d92d5eb9b Swap critical.mp3 for critical.ogg (#1503) 2026-04-15 05:22:37 -05:00
Loki 2fba264c08 Revert "Prevent contextually wrong music from playing (#1138)"
This reverts commit 2d41711055.
2026-04-14 17:00:35 -05:00
Loki Rautio 1533b2138f Swap critical.mp3 for critical.ogg (#1503) 2026-04-14 16:59:58 -05:00
Riley M. c0da06e4ee major: Switch to forward slashes(+more) to fix compilation on Linux (#1403)
Notably also adds some metadata files for NixOS 

* add support for linux clang cross compiles

* add linux clang instructions

* un-capitalize Mob.horse.*

* update the description in flake.nix

---------

Co-authored-by: Loki <lokirautio@gmail.com>
2026-04-14 16:47:37 -05:00
itsRevela 5133925be0 chore: sync upstream MinecraftConsoles through f78dec6e 2026-04-13 03:16:34 -05:00
itsRevela 0544add4e2 chore: remove redundant/unused 4JLibs files 2026-04-13 03:15:58 -05:00
Loki Rautio f78dec6e00 Remove redundant/unused 4JLibs files
also adds some countermeasures against, yknow.
2026-04-13 01:26:36 -05:00
itsRevela 0d102b167f docs: add Latest entries for upstream sync through 78afb091 2026-04-13 01:00:03 -05:00
itsRevela 8c34855343 chore: sync upstream MinecraftConsoles through 78afb091 2026-04-13 00:54:24 -05:00
Tyler Reese 6913ce5323 fix: Implement missing critical hit sound (#1141) 2026-04-13 00:51:16 -05:00
DrPerkyLegit 71707fbb8c Add Chat Formatting Support For Servers (#1483) 2026-04-13 00:49:49 -05:00
GabsPuNs ae9dbd837d Exclude more files, reduce build .zip size (#1374) 2026-04-13 00:48:43 -05:00
Toru the Red Fox 825e96da54 Request dedicated GPU (#850) 2026-04-13 00:47:00 -05:00
Botch dea2bad0c4 fix: Disable font mipmapping (#1410) 2026-04-13 00:46:32 -05:00
DrPerkyLegit ae3c843ee5 fix: Increase entity network limit to 16k entities (#1492) 2026-04-13 00:43:18 -05:00
DrPerkyLegit 6c572d4940 feat: Scrollable chat (#1493) 2026-04-13 00:42:04 -05:00
DrPerkyLegit 5e7ad7333e Better Text Scaling (#1494)
* f3 menu text scaling

* Reduce overscaling above 1080p

Restores original scaling for 1440p to try and keep the text size more
sane on high DPI monitors

---------

Co-authored-by: Loki Rautio <lokirautio@gmail.com>
2026-04-13 00:37:54 -05:00
Tyler Reese 78afb091a4 fix: Implement missing critical hit sound (#1141)
* Fix

* Crit Sound Now Plays On Death

* Revert BuildVer.h

---------

Co-authored-by: Loki <lokio.casebstv@gmail.com>
2026-04-12 23:24:29 -05:00
Tyler Reese 2d41711055 fix: Prevent contextually wrong music from playing (#1138) 2026-04-12 23:22:14 -05:00
DrPerkyLegit 14f8d793dd Add Chat Formatting Support For Servers (#1483)
* add chat support for html formatting

* html character serialization, normal color format support

* change for chat input handling on color

has a bug where the text after the cursor gets stripped of its color, need to make a function to backstep on a string and find the last used color codes, or get all color codes used before the string is split, and apply them to the start of the next string

* expose jukebox label as action bar like java

* prevent players from sending chat color

* restore non styled chat size check
2026-04-12 23:17:45 -05:00
GabsPuNs bfcb621808 Exclude more files, reduce build .zip size (#1374)
* Fix Sign

* Exclude more files from the compilation.

* Remove Trial and Tutorial folder in Common, Tutorial world is in windows64media
2026-04-12 23:17:21 -05:00
Toru the Red Fox 050c501786 Request dedicated GPU (#850) 2026-04-12 22:56:24 -05:00
Botch e5ce9a06cd fix: Disable font mipmapping (#1410)
Fixes visual artifacts with signs, improving legibility at distances
2026-04-12 22:55:07 -05:00
Us3ful"-Dev e730033bcc fix: Prevent end poem crash (#1489)
Removed player name check that always fails to work on non host instances
2026-04-12 22:53:39 -05:00
rtm516 9e6e3de338 Improve actions file ignore rules (#1491)
* Add clang-format workflow for pull request checks

* Modify push paths in nightly workflow

Updated paths for push event to include all files except specified ones.

* Update paths for nightly-server workflow triggers

* Modify paths for pull request triggers

Update pull request workflow to include specific paths.

* Remove formatting check workflow
2026-04-12 22:52:34 -05:00
DrPerkyLegit 82c1ae1968 fix: Increase entity network limit to 16k entities (#1492) 2026-04-12 22:50:46 -05:00
DrPerkyLegit c7014f6b18 feat: Scrollable chat (#1493)
* chat scrolling

* allow escape to close chat instead of opening pause
2026-04-12 22:50:16 -05:00
DrPerkyLegit 744048f455 Better Text Scaling (#1494)
* f3 menu text scaling

* Reduce overscaling above 1080p

Restores original scaling for 1440p to try and keep the text size more
sane on high DPI monitors

---------

Co-authored-by: Loki Rautio <lokirautio@gmail.com>
2026-04-12 22:48:08 -05:00
itsRevela 33bec8a93c docs: add Latest entries for recent client fixes
Covers the join-flow kick bug, the fullscreen and frame rate work, and the controller cursor speed fix.
2026-04-11 08:13:58 -05:00
itsRevela abb18e3e12 fix: new players kicked when joining right after being whitelisted
When a new player was whitelisted while they were sitting on the join screen, their next attempt would insta-kick before the world ever loaded, and the retry after that would let them in for roughly 20 seconds before booting them with "connection closed". Two separate bugs were colliding.

The first kick was a stale cancel flag on the client. When the server rejects a join, the "Connecting to host..." screen tears down, and its teardown path fires the cancel callback defensively. That flag would latch on without ever being consumed, so the very first tick of the next join attempt saw it and immediately closed the fresh connection. Clearing the flag when a new join starts prevents this.

The second kick was an orphan on the server. When the first failed join's TCP dropped, its slot got recycled for the next successful join, but the half-built login object from the broken attempt was still in the pending queue. 30 seconds later its "login took too long" timer fired, and the disconnect packet it tried to send was routed to whoever currently held that slot, which was now the new in-world player. It landed on their live socket and kicked them. Telling the game's Socket layer about the TCP drop lets the orphan clean itself up, and refusing to write on an already-closing socket stops any late packet from leaking into the recycled slot.
2026-04-11 08:13:46 -05:00
itsRevela f189234614 feat: uncap fps in fullscreen for lower input latency
Before this change, turning VSync off did not actually uncap your frame rate. The game ran in borderless fullscreen, where Windows' desktop compositor owns the display pipeline and applies its own VSync regardless of what the game asks for. So on a 240Hz monitor with a GPU that could draw 800fps, you still only saw 240 frames per second and the other 560 were thrown away. Worse, the frames you did see were up to a refresh cycle stale by the time they hit your eyes, which is input latency you can feel when moving the camera or aiming.

Fullscreen now uses true DXGI exclusive mode, where the compositor is out of the way and the swap chain writes directly to the display. Every frame the GPU produces lands on screen as soon as it is ready, so "VSync off" actually does something. Expect FPS to climb well past your monitor's refresh rate and the mouse to feel noticeably more responsive.

Exclusive fullscreen also displays at your monitor's native resolution with no driver-side scaling or filtering. The backbuffer is grown to match the monitor exactly before the transition, and the display mode is pinned to the backbuffer size so nothing in the output pipeline resamples your pixels on the way to the screen. The result is a crisp 1:1 image with none of the softening or greyish filter that stretched output can introduce.

Yes, this is the mode where screen tearing can happen. Tearing gets a bad reputation but it is a visual tradeoff, not a bug, and many players prefer it over the latency VSync causes. If you want to avoid tearing, just turn VSync on in the settings and the game will cap cleanly to your refresh rate. You now have a real choice between the two instead of "off" quietly being broken.

Under the hood, F11 and the saved fullscreen preference both route through a new ApplyExclusiveFullscreen path that does Microsoft's recommended transition: grow the window to cover the monitor, ResizeTarget with no scaling so the display mode is pinned to the backbuffer size, SetFullscreenState(TRUE), SetColorSpace1(sRGB), then a second ResizeTarget so picky drivers actually apply the mode. Exit forces a real decorated windowed state so F11 cycles cleanly between windowed and exclusive fullscreen. ResizeD3D skips its swap-chain-recreate path while in exclusive mode so it does not fight DXGI for ownership.

The swap chain's RefreshRate changes from a hardcoded 60Hz to 0/0 so DXGI matches the current display mode. Fixes an "input signal out of range" error that could happen on high-refresh monitors after entering fullscreen, where the monitor was being asked to renegotiate timing off of 240Hz down to 60Hz on every launch.
2026-04-11 06:19:22 -05:00
115 changed files with 5402 additions and 284 deletions
+2 -2
View File
@@ -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.
+9 -4
View File
@@ -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!
+3
View File
@@ -429,3 +429,6 @@ result-*
.direnv/
.xwin-cache/
.xwin/
# macOS
.DS_Store
+2 -2
View File
@@ -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()
-2
View File
@@ -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}")
+18 -1
View File
@@ -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++;
+4
View File
@@ -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:
+18 -5
View File
@@ -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,
};
+92
View File
@@ -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"&amp;"},
{L'<', L"&lt;"},
{L'>', L"&gt;"},
};
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);
+3 -1
View File
@@ -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
+12 -4
View File
@@ -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();
}
}
}
+28 -7
View File
@@ -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;
}
+1 -1
View File
@@ -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;
}
+1 -1
View File
@@ -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();
}
+4
View File
@@ -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
View File
@@ -697,8 +697,8 @@ void Gui::render(float a, bool mouseFree, int xMouse, int yMouse)
glEnable(GL_COLOR_MATERIAL);
// 4J - TomK now using safe zone values directly instead of the magic number calculation that lived here before (which only worked for medium scale, the other two were off!)
int xo = iSafezoneXHalf + 10;
int yo = iSafezoneTopYHalf + 10;
int xo = iSafezoneXHalf + 10; // TODO: fix relative scaling for atrocious aspect ratios
int yo = iSafezoneTopYHalf + 10; // TODO: fix relative scaling for atrocious aspect ratios
#ifdef __PSVITA__
// align directly with corners, there are no safe zones on vita
@@ -708,8 +708,29 @@ void Gui::render(float a, bool mouseFree, int xMouse, int yMouse)
glPushMatrix();
glTranslatef(static_cast<float>(xo), static_cast<float>(yo), 50);
float ss = 12;
glScalef(-ss, ss, ss);
// correct paper doll aspect ratio
float ss = 12.0f;
float aspectScaleX = 1.0f;
float aspectScaleY = 1.0f;
extern int g_rScreenWidth;
extern int g_rScreenHeight;
if (g_rScreenWidth > 0 && g_rScreenHeight > 0) {
float screenAspect = (float)g_rScreenWidth / (float)g_rScreenHeight;
const float targetAspect = 16.0f / 9.0f;
// apply correction if window is not already at a 16:9 aspect ratio
if (fabs(screenAspect - targetAspect) > 0.01f) {
if (screenAspect > targetAspect)
aspectScaleX = targetAspect / screenAspect;
else
aspectScaleY = screenAspect / targetAspect;
}
}
glScalef(-ss * aspectScaleX, ss * aspectScaleY, ss);
glRotatef(180, 0, 0, 1);
float oyr = minecraft->player->yRot;
@@ -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)
+3
View File
@@ -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);
+6 -2
View File
@@ -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))
+1
View File
@@ -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);
+2
View File
@@ -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);
+1 -1
View File
@@ -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()));
}
}
+1 -1
View File
@@ -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++)
+53 -4
View File
@@ -12,6 +12,9 @@
#include "../Minecraft.World/compression.h"
#include "../Minecraft.World/OldChunkStorage.h"
#include "../Minecraft.World/Tile.h"
#ifdef MINECRAFT_SERVER_BUILD
#include "../Minecraft.Server/FourKitBridge.h"
#endif
ServerChunkCache::ServerChunkCache(ServerLevel *level, ChunkStorage *storage, ChunkSource *source)
{
@@ -125,7 +128,10 @@ LevelChunk *ServerChunkCache::create(int x, int z, bool asyncPostProcess) // 4J
{
EnterCriticalSection(&m_csLoadCreate);
chunk = load(x, z);
if (chunk == nullptr)
#ifdef MINECRAFT_SERVER_BUILD
bool isNewChunk = (chunk == nullptr);
#endif
if (chunk == nullptr)
{
if (source == nullptr)
{
@@ -204,6 +210,10 @@ LevelChunk *ServerChunkCache::create(int x, int z, bool asyncPostProcess) // 4J
if( hasChunk( x - 1, z ) && hasChunk( x + 1, z ) && hasChunk ( x, z - 1 ) && hasChunk( x, z + 1 ) ) chunk->checkChests( this, x, z );
LeaveCriticalSection(&m_csLoadCreate);
#ifdef MINECRAFT_SERVER_BUILD
FourKitBridge::FireChunkLoad(level->dimension->id, x, z, isNewChunk);
#endif
}
else
{
@@ -351,6 +361,38 @@ void ServerChunkCache::overwriteHellLevelChunkFromSource(int x, int z, int minVa
#endif
#ifdef MINECRAFT_SERVER_BUILD
void ServerChunkCache::regenerateChunk(int x, int z)
{
if (!source)
return;
LevelChunk *freshChunk = source->getChunk(x, z);
if (!freshChunk)
return;
LevelChunk *cachedChunk = nullptr;
if (hasChunk(x, z))
cachedChunk = getChunk(x, z);
if (cachedChunk && cachedChunk != emptyChunk)
{
for (int lx = 0; lx < 16; lx++)
for (int ly = 0; ly < 128; ly++)
for (int lz = 0; lz < 16; lz++)
cachedChunk->setTileAndData(lx, ly, lz, freshChunk->getTile(lx, ly, lz), freshChunk->getData(lx, ly, lz));
save(cachedChunk);
}
else
{
save(freshChunk);
}
freshChunk->unload(false);
delete freshChunk;
}
#endif
// 4J Added //
#ifdef _LARGE_WORLDS
void ServerChunkCache::dontDrop(int x, int z)
@@ -914,15 +956,19 @@ bool ServerChunkCache::tick()
// player's tick is called to remove them from the chunk they used to be in, and add them to their current chunk. This will only be a temporary state and
// we should be able to unload the chunk on the next call to this tick.
if( !chunk->containsPlayer() )
{
{
#ifdef MINECRAFT_SERVER_BUILD
if (!FourKitBridge::FireChunkUnload(level->dimension->id, chunk->x, chunk->z))
{
#endif
save(chunk);
saveEntities(chunk);
chunk->unload(true);
//loadedChunks.remove(cp);
//loadedChunkList.remove(chunk);
auto it = std::find(m_loadedChunkList.begin(), m_loadedChunkList.end(), chunk);
if(it != m_loadedChunkList.end()) m_loadedChunkList.erase(it);
auto it = std::find(m_loadedChunkList.begin(), m_loadedChunkList.end(), chunk);
if(it != m_loadedChunkList.end()) m_loadedChunkList.erase(it);
int ix = chunk->x + XZOFFSET;
int iz = chunk->z + XZOFFSET;
@@ -930,6 +976,9 @@ bool ServerChunkCache::tick()
delete m_unloadedCache[idx];
m_unloadedCache[idx] = chunk;
cache[idx] = nullptr;
#ifdef MINECRAFT_SERVER_BUILD
}
#endif
}
else
{
+3
View File
@@ -53,6 +53,9 @@ public:
#endif
virtual LevelChunk **getCache() { return cache; } // 4J added
#ifdef MINECRAFT_SERVER_BUILD
void regenerateChunk(int x, int z);
#endif
// 4J-JEV Added; Remove chunk from the toDrop queue.
#ifdef _LARGE_WORLDS
+2 -2
View File
@@ -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.
+117 -1
View File
@@ -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
@@ -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}
+1 -1
View File
@@ -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
}
+109
View File
@@ -0,0 +1,109 @@
namespace Minecraft.Server.FourKit.Block;
public enum Biome
{
OCEAN = 0,
PLAINS = 1,
DESERT = 2,
EXTREME_HILLS = 3,
FOREST = 4,
TAIGA = 5,
SWAMPLAND = 6,
RIVER = 7,
HELL = 8,
SKY = 9,
FROZEN_OCEAN = 10,
FROZEN_RIVER = 11,
ICE_PLAINS = 12,
ICE_MOUNTAINS = 13,
MUSHROOM_ISLAND = 14,
MUSHROOM_SHORE = 15,
BEACH = 16,
DESERT_HILLS = 17,
FOREST_HILLS = 18,
TAIGA_HILLS = 19,
SMALL_MOUNTAINS = 20,
JUNGLE = 21,
JUNGLE_HILLS = 22,
}
// more for internal
// eliminates unnecessary overhead
internal static class BiomeHelper
{
private static readonly double[] _temperatures = new double[23];
private static readonly double[] _rainfalls = new double[23];
static BiomeHelper()
{
_temperatures[(int)Biome.OCEAN] = 0.5;
_temperatures[(int)Biome.PLAINS] = 0.8;
_temperatures[(int)Biome.DESERT] = 2.0;
_temperatures[(int)Biome.EXTREME_HILLS] = 0.2;
_temperatures[(int)Biome.FOREST] = 0.7;
_temperatures[(int)Biome.TAIGA] = 0.05;
_temperatures[(int)Biome.SWAMPLAND] = 0.8;
_temperatures[(int)Biome.RIVER] = 0.5;
_temperatures[(int)Biome.HELL] = 2.0;
_temperatures[(int)Biome.SKY] = 0.5;
_temperatures[(int)Biome.FROZEN_OCEAN] = 0.0;
_temperatures[(int)Biome.FROZEN_RIVER] = 0.0;
_temperatures[(int)Biome.ICE_PLAINS] = 0.0;
_temperatures[(int)Biome.ICE_MOUNTAINS] = 0.0;
_temperatures[(int)Biome.MUSHROOM_ISLAND] = 0.9;
_temperatures[(int)Biome.MUSHROOM_SHORE] = 0.9;
_temperatures[(int)Biome.BEACH] = 0.8;
_temperatures[(int)Biome.DESERT_HILLS] = 2.0;
_temperatures[(int)Biome.FOREST_HILLS] = 0.7;
_temperatures[(int)Biome.TAIGA_HILLS] = 0.05;
_temperatures[(int)Biome.SMALL_MOUNTAINS] = 0.2;
_temperatures[(int)Biome.JUNGLE] = 1.2;
_temperatures[(int)Biome.JUNGLE_HILLS] = 1.2;
_rainfalls[(int)Biome.OCEAN] = 0.5;
_rainfalls[(int)Biome.PLAINS] = 0.4;
_rainfalls[(int)Biome.DESERT] = 0.0;
_rainfalls[(int)Biome.EXTREME_HILLS] = 0.3;
_rainfalls[(int)Biome.FOREST] = 0.8;
_rainfalls[(int)Biome.TAIGA] = 0.8;
_rainfalls[(int)Biome.SWAMPLAND] = 0.9;
_rainfalls[(int)Biome.RIVER] = 0.5;
_rainfalls[(int)Biome.HELL] = 0.0;
_rainfalls[(int)Biome.SKY] = 0.5;
_rainfalls[(int)Biome.FROZEN_OCEAN] = 0.5;
_rainfalls[(int)Biome.FROZEN_RIVER] = 0.5;
_rainfalls[(int)Biome.ICE_PLAINS] = 0.5;
_rainfalls[(int)Biome.ICE_MOUNTAINS] = 0.5;
_rainfalls[(int)Biome.MUSHROOM_ISLAND] = 1.0;
_rainfalls[(int)Biome.MUSHROOM_SHORE] = 1.0;
_rainfalls[(int)Biome.BEACH] = 0.4;
_rainfalls[(int)Biome.DESERT_HILLS] = 0.0;
_rainfalls[(int)Biome.FOREST_HILLS] = 0.8;
_rainfalls[(int)Biome.TAIGA_HILLS] = 0.8;
_rainfalls[(int)Biome.SMALL_MOUNTAINS] = 0.3;
_rainfalls[(int)Biome.JUNGLE] = 0.9;
_rainfalls[(int)Biome.JUNGLE_HILLS] = 0.9;
}
public static double getTemperature(this Biome biome)
{
int id = (int)biome;
if (id >= 0 && id < _temperatures.Length) return _temperatures[id];
return 0.5;
}
public static double getRainfall(this Biome biome)
{
int id = (int)biome;
if (id >= 0 && id < _rainfalls.Length) return _rainfalls[id];
return 0.5;
}
public static Biome fromId(int id)
{
if (Enum.IsDefined(typeof(Biome), id)) return (Biome)id;
return Biome.PLAINS;
}
}
+148 -3
View File
@@ -89,7 +89,19 @@ public class Block
/// <returns>Whether the change was successful.</returns>
public bool setTypeId(int type)
{
NativeBridge.SetTile?.Invoke(_world.getDimensionId(), _x, _y, _z, type, 0);
return setTypeId(type, true);
}
/// <summary>
/// Sets the type ID of this block.
/// </summary>
/// <param name="type">Type ID to change this block to.</param>
/// <param name="applyPhysics">False to cancel physics on the changed block.</param>
/// <returns>Whether the block was changed.</returns>
public bool setTypeId(int type, bool applyPhysics)
{
int flags = applyPhysics ? 3 : 2;
NativeBridge.SetTile?.Invoke(_world.getDimensionId(), _x, _y, _z, type, 0, flags);
return true;
}
@@ -108,7 +120,43 @@ public class Block
/// <param name="data">New block specific metadata.</param>
public void setData(byte data)
{
NativeBridge.SetTileData?.Invoke(_world.getDimensionId(), _x, _y, _z, data);
setData(data, true);
}
/// <summary>
/// Sets the metadata for this block.
/// </summary>
/// <param name="data">New block specific metadata.</param>
/// <param name="applyPhysics">False to cancel physics from the changed block.</param>
public void setData(byte data, bool applyPhysics)
{
int flags = applyPhysics ? 3 : 2;
NativeBridge.SetTileData?.Invoke(_world.getDimensionId(), _x, _y, _z, data, flags);
}
/// <summary>
/// Sets the type ID and data of this block.
/// </summary>
/// <param name="type">Type ID to change this block to.</param>
/// <param name="data">The data value to change this block to.</param>
/// <returns>Whether the block was changed.</returns>
public bool setTypeIdAndData(int type, byte data)
{
return setTypeIdAndData(type, data, true);
}
/// <summary>
/// Sets the type ID and data of this block.
/// </summary>
/// <param name="type">Type ID to change this block to.</param>
/// <param name="data">The data value to change this block to.</param>
/// <param name="applyPhysics">False to cancel physics on the changed block.</param>
/// <returns>Whether the block was changed.</returns>
public bool setTypeIdAndData(int type, byte data, bool applyPhysics)
{
int flags = applyPhysics ? 3 : 2;
NativeBridge.SetTile?.Invoke(_world.getDimensionId(), _x, _y, _z, type, data, flags);
return true;
}
/// <summary>
@@ -134,6 +182,15 @@ public class Block
return getWorld().getBlockAt(getX() + modX, getY() + modY, getZ() + modZ);
}
/// <summary>
/// Gets the chunk which contains this block.
/// </summary>
/// <returns>Containing Chunk.</returns>
public Chunk.Chunk getChunk()
{
return getWorld().getChunkAt(getX() >> 4, getZ() >> 4);
}
/// <summary>
/// Gets the block at the given face
/// <para>This method is equal to getRelative(face, 1)</para>
@@ -162,5 +219,93 @@ public class Block
{
return getRelative(face.getModX() * distance, face.getModY() * distance, face.getModZ() * distance);
}
/// <summary>
/// Returns the biome that this block resides in.
/// </summary>
/// <returns>Biome type containing this block.</returns>
public Biome getBiome()
{
if (NativeBridge.GetBiomeId != null)
return BiomeHelper.fromId(NativeBridge.GetBiomeId(_world.getDimensionId(), _x, _z));
return Biome.PLAINS;
}
/// <summary>
/// Sets the biome that this block resides in.
/// </summary>
/// <param name="bio">New Biome type for this block.</param>
public void setBiome(Biome bio)
{
NativeBridge.SetBiomeId?.Invoke(_world.getDimensionId(), _x, _z, (int)bio);
}
/// <summary>
/// Gets the humidity of the biome of this block.
/// </summary>
/// <returns>Humidity of this block.</returns>
public double getHumidity()
{
return getBiome().getRainfall();
}
/// <summary>
/// Gets the temperature of the biome of this block.
/// </summary>
/// <returns>Temperature of this block.</returns>
public double getTemperature()
{
return getBiome().getTemperature();
}
/// <summary>
/// Checks if this block is liquid.
/// <para>A block is considered liquid when <see cref="getType()"/> returns
/// <see cref="Material.WATER"/>, <see cref="Material.STATIONARY_WATER"/>,
/// <see cref="Material.LAVA"/> or <see cref="Material.STATIONARY_LAVA"/>.</para>
/// </summary>
/// <returns>true if this block is liquid.</returns>
public bool isLiquid()
{
Material type = getType();
return type == Material.WATER || type == Material.STATIONARY_WATER ||
type == Material.LAVA || type == Material.STATIONARY_LAVA;
}
/// <summary>
/// Gets the light level between 0-15.
/// </summary>
/// <returns>Light level.</returns>
public byte getLightLevel()
{
int sky = getLightFromSky();
int block = getLightFromBlocks();
return (byte)(sky > block ? sky : block);
}
/// <summary>
/// Get the amount of light at this block from the sky.
/// Any light given from other sources (such as blocks like torches) will be ignored.
/// </summary>
/// <returns>Sky light level.</returns>
public byte getLightFromSky()
{
if (NativeBridge.GetSkyLight != null)
return (byte)NativeBridge.GetSkyLight(_world.getDimensionId(), _x, _y, _z);
return 0;
}
/// <summary>
/// Get the amount of light at this block from nearby blocks.
/// Any light given from other sources (such as the sun) will be ignored.
/// </summary>
/// <returns>Block light level.</returns>
public byte getLightFromBlocks()
{
if (NativeBridge.GetBlockLight != null)
return (byte)NativeBridge.GetBlockLight(_world.getDimensionId(), _x, _y, _z);
return 0;
}
}
+19 -1
View File
@@ -92,6 +92,15 @@ public class BlockState
/// <returns>Z-coordinate.</returns>
public int getZ() => _z;
/// <summary>
/// Gets the chunk which contains this block.
/// </summary>
/// <returns>Containing Chunk.</returns>
public Chunk.Chunk getChunk()
{
return _world.getChunkAt(_x >> 4, _z >> 4);
}
/// <summary>
/// Gets the location of this block.
/// </summary>
@@ -191,7 +200,16 @@ public class BlockState
if (!force && currentType != _typeId)
return false;
NativeBridge.SetTile(_world.getDimensionId(), _x, _y, _z, _typeId, _data);
NativeBridge.SetTile(_world.getDimensionId(), _x, _y, _z, _typeId, _data, applyPhysics ? 3 : 2);
return true;
}
/// <summary>
/// Gets the light level between 0-15.
/// </summary>
/// <returns>Light level.</returns>
public byte getLightLevel()
{
return getBlock().getLightLevel();
}
}
+173
View File
@@ -0,0 +1,173 @@
using System.Text;
using System.Text.RegularExpressions;
namespace Minecraft.Server.FourKit;
/// <summary>
/// All supported color values for chat.
/// </summary>
public class ChatColor
{
/// <summary>
/// The special character which prefixes all chat colour codes.
/// </summary>
public static readonly char COLOR_CHAR = '\u00A7'; // F
private static readonly Regex STRIP_COLOR_PATTERN =
new Regex("(?i)" + COLOR_CHAR + "[0-9A-FK-OR]", RegexOptions.Compiled);
private static readonly Dictionary<char, ChatColor> BY_CHAR = new();
/// <summary>Represents black.</summary>
public static readonly ChatColor BLACK = new('0', false);
/// <summary>Represents dark blue.</summary>
public static readonly ChatColor DARK_BLUE = new('1', false);
/// <summary>Represents dark green.</summary>
public static readonly ChatColor DARK_GREEN = new('2', false);
/// <summary>Represents dark blue (aqua).</summary>
public static readonly ChatColor DARK_AQUA = new('3', false);
/// <summary>Represents dark red.</summary>
public static readonly ChatColor DARK_RED = new('4', false);
/// <summary>Represents dark purple.</summary>
public static readonly ChatColor DARK_PURPLE = new('5', false);
/// <summary>Represents gold.</summary>
public static readonly ChatColor GOLD = new('6', false);
/// <summary>Represents gray.</summary>
public static readonly ChatColor GRAY = new('7', false);
/// <summary>Represents dark gray.</summary>
public static readonly ChatColor DARK_GRAY = new('8', false);
/// <summary>Represents blue.</summary>
public static readonly ChatColor BLUE = new('9', false);
/// <summary>Represents green.</summary>
public static readonly ChatColor GREEN = new('a', false);
/// <summary>Represents aqua.</summary>
public static readonly ChatColor AQUA = new('b', false);
/// <summary>Represents red.</summary>
public static readonly ChatColor RED = new('c', false);
/// <summary>Represents light purple.</summary>
public static readonly ChatColor LIGHT_PURPLE = new('d', false);
/// <summary>Represents yellow.</summary>
public static readonly ChatColor YELLOW = new('e', false);
/// <summary>Represents white.</summary>
public static readonly ChatColor WHITE = new('f', false);
/// <summary>Resets all previous chat colors or formats.</summary>
public static readonly ChatColor RESET = new('r', false);
private readonly char _code;
private readonly bool _isFormat;
private readonly string _toString;
private ChatColor(char code, bool isFormat)
{
_code = code;
_isFormat = isFormat;
_toString = new string(new[] { COLOR_CHAR, code });
BY_CHAR[code] = this;
}
/// <summary>
/// Gets the char value associated with this color.
/// </summary>
/// <returns>A char value of this color code.</returns>
public char getChar() => _code;
/// <summary>
/// Checks if this code is a format code as opposed to a color code.
/// </summary>
/// <returns><c>true</c> if this is a format code.</returns>
public bool isFormat() => _isFormat;
/// <summary>
/// Checks if this code is a color code as opposed to a format code.
/// </summary>
/// <returns><c>true</c> if this is a color code.</returns>
public bool isColor() => !_isFormat && this != RESET;
/// <summary>
/// Gets the color represented by the specified color code.
/// </summary>
/// <param name="code">Code to check.</param>
/// <returns>Associative ChatColor with the given code, or null if it doesn't exist.</returns>
public static ChatColor? getByChar(char code)
{
return BY_CHAR.TryGetValue(char.ToLower(code), out var color) ? color : null;
}
/// <summary>
/// Gets the color represented by the specified color code.
/// </summary>
/// <param name="code">Code to check.</param>
/// <returns>Associative ChatColor with the given code, or null if it doesn't exist.</returns>
public static ChatColor? getByChar(string code)
{
if (string.IsNullOrEmpty(code)) return null;
return getByChar(code[0]);
}
/// <summary>
/// Strips the given message of all color codes.
/// </summary>
/// <param name="input">String to strip of color.</param>
/// <returns>A copy of the input string, without any coloring.</returns>
public static string? stripColor(string? input)
{
if (input == null) return null;
return STRIP_COLOR_PATTERN.Replace(input, "");
}
/// <summary>
/// Translates a string using an alternate color code character into a string
/// that uses the internal <see cref="COLOR_CHAR"/> color code character.
/// The alternate color code character will only be replaced if it is immediately
/// followed by 0-9, A-F, a-f, K-O, k-o, R or r.
/// </summary>
/// <param name="altColorChar">The alternate color code character to replace. Ex: &amp;</param>
/// <param name="textToTranslate">Text containing the alternate color code character.</param>
/// <returns>Text containing the <see cref="COLOR_CHAR"/> color code character.</returns>
public static string translateAlternateColorCodes(char altColorChar, string textToTranslate)
{
char[] b = textToTranslate.ToCharArray();
for (int i = 0; i < b.Length - 1; i++)
{
if (b[i] == altColorChar && "0123456789AaBbCcDdEeFfKkLlMmNnOoRr".IndexOf(b[i + 1]) > -1)
{
b[i] = COLOR_CHAR;
}
}
return new string(b);
}
/// <summary>
/// Gets the ChatColors used at the end of the given input string.
/// </summary>
/// <param name="input">Input string to retrieve the colors from.</param>
/// <returns>Any remaining ChatColors to pass onto the next line.</returns>
public static string getLastColors(string input)
{
var result = new StringBuilder();
int length = input.Length;
for (int index = length - 1; index > -1; index--)
{
char section = input[index];
if (section == COLOR_CHAR && index < length - 1)
{
char c = input[index + 1];
ChatColor? color = getByChar(c);
if (color != null)
{
result.Insert(0, color.ToString());
if (color.isColor() || color == RESET)
break;
}
}
}
return result.ToString();
}
public static string operator +(ChatColor color, string text) => color.ToString() + text;
/// <inheritdoc/>
public override string ToString() => _toString;
}
+311
View File
@@ -0,0 +1,311 @@
using Minecraft.Server.FourKit.Block;
using Minecraft.Server.FourKit.Entity;
using System.Runtime.InteropServices;
namespace Minecraft.Server.FourKit.Chunk;
/// <summary>
/// Represents a chunk of blocks.
/// </summary>
public class Chunk
{
private readonly World _world;
private readonly int _chunkX;
private readonly int _chunkZ;
internal Chunk(World world, int chunkX, int chunkZ)
{
_world = world;
_chunkX = chunkX;
_chunkZ = chunkZ;
}
/// <summary>
/// Gets the X-coordinate of this chunk.
/// </summary>
/// <returns>X-coordinate.</returns>
public int getX() => _chunkX;
/// <summary>
/// Gets the Z-coordinate of this chunk.
/// </summary>
/// <returns>Z-coordinate.</returns>
public int getZ() => _chunkZ;
/// <summary>
/// Gets the world containing this chunk.
/// </summary>
/// <returns>Parent World.</returns>
public World getWorld() => _world;
/// <summary>
/// Gets a block from this chunk.
/// </summary>
/// <param name="x">0-15</param>
/// <param name="y">0-127</param>
/// <param name="z">0-15</param>
/// <returns>The Block.</returns>
public Block.Block getBlock(int x, int y, int z)
{
return _world.getBlockAt((_chunkX << 4) + x, y, (_chunkZ << 4) + z);
}
/// <summary>
/// Capture thread-safe read-only snapshot of chunk data.
/// </summary>
/// <returns>ChunkSnapshot.</returns>
public ChunkSnapshot getChunkSnapshot()
{
return getChunkSnapshot(false, false);
}
/// <summary>
/// Capture thread-safe read-only snapshot of chunk data.
/// </summary>
/// <param name="includeBiome">If true, snapshot includes per-coordinate biome type.</param>
/// <param name="includeBiomeTempRain">If true, snapshot includes per-coordinate raw biome temperature and rainfall.</param>
/// <returns>ChunkSnapshot.</returns>
public ChunkSnapshot getChunkSnapshot(bool includeBiome, bool includeBiomeTempRain)
{
// this has a lot of overhead
// (SYLV)todo: clean this up
int dimId = _world.getDimensionId();
int[] blockIds = new int[16 * 128 * 16];
int[] blockData = new int[16 * 128 * 16];
int[] maxBlockY = new int[16 * 16];
int[] skyLight = new int[16 * 128 * 16];
int[] blockLight = new int[16 * 128 * 16];
int[]? biomeIds = includeBiome ? new int[16 * 16] : null;
double[]? biomeTemp = includeBiomeTempRain ? new double[16 * 16] : null;
double[]? biomeRainfall = includeBiomeTempRain ? new double[16 * 16] : null;
if (NativeBridge.GetChunkSnapshot != null)
{
var hIds = GCHandle.Alloc(blockIds, GCHandleType.Pinned);
var hData = GCHandle.Alloc(blockData, GCHandleType.Pinned);
var hMaxY = GCHandle.Alloc(maxBlockY, GCHandleType.Pinned);
try
{
NativeBridge.GetChunkSnapshot(dimId, _chunkX, _chunkZ,
hIds.AddrOfPinnedObject(),
hData.AddrOfPinnedObject(),
hMaxY.AddrOfPinnedObject());
}
finally
{
hIds.Free();
hData.Free();
hMaxY.Free();
}
}
else
{
for (int lx = 0; lx < 16; lx++)
{
for (int lz = 0; lz < 16; lz++)
{
int worldX = (_chunkX << 4) + lx;
int worldZ = (_chunkZ << 4) + lz;
int highest = 0;
for (int ly = 0; ly < 128; ly++)
{
int idx = (lx * 128 * 16) + (ly * 16) + lz;
if (NativeBridge.GetTileId != null)
blockIds[idx] = NativeBridge.GetTileId(dimId, worldX, ly, worldZ);
if (NativeBridge.GetTileData != null)
blockData[idx] = NativeBridge.GetTileData(dimId, worldX, ly, worldZ);
if (blockIds[idx] != 0)
highest = ly;
}
maxBlockY[lx * 16 + lz] = highest;
}
}
}
for (int lx = 0; lx < 16; lx++)
{
for (int lz = 0; lz < 16; lz++)
{
int worldX = (_chunkX << 4) + lx;
int worldZ = (_chunkZ << 4) + lz;
for (int ly = 0; ly < 128; ly++)
{
int idx = (lx * 128 * 16) + (ly * 16) + lz;
if (NativeBridge.GetSkyLight != null)
skyLight[idx] = NativeBridge.GetSkyLight(dimId, worldX, ly, worldZ);
if (NativeBridge.GetBlockLight != null)
blockLight[idx] = NativeBridge.GetBlockLight(dimId, worldX, ly, worldZ);
}
if (includeBiome && NativeBridge.GetBiomeId != null)
{
int colIdx = lx * 16 + lz;
int biomeId = NativeBridge.GetBiomeId(dimId, worldX, worldZ);
biomeIds![colIdx] = biomeId;
if (includeBiomeTempRain)
{
var biome = Block.BiomeHelper.fromId(biomeId);
biomeTemp![colIdx] = biome.getTemperature();
biomeRainfall![colIdx] = biome.getRainfall();
}
}
}
}
long captureTime = 0;
if (NativeBridge.GetWorldInfo != null)
{
double[] info = new double[7];
var hInfo = GCHandle.Alloc(info, GCHandleType.Pinned);
try
{
NativeBridge.GetWorldInfo(dimId, hInfo.AddrOfPinnedObject());
}
finally
{
hInfo.Free();
}
captureTime = (long)info[4];
}
return new ChunkSnapshot(_chunkX, _chunkZ, _world.getName(), captureTime,
blockIds, blockData, maxBlockY,
skyLight, blockLight, biomeIds, biomeTemp, biomeRainfall);
}
/// <summary>
/// Capture thread-safe read-only snapshot of chunk data.
/// </summary>
/// <param name="includeMaxblocky">(NONFUNCTIONAL) Only here for parity.</param>
/// <param name="includeBiome">If true, snapshot includes per-coordinate biome type.</param>
/// <param name="includeBiomeTempRain">If true, snapshot includes per-coordinate raw biome temperature and rainfall.</param>
/// <returns>ChunkSnapshot.</returns>
public ChunkSnapshot getChunkSnapshot(bool includeMaxblocky, bool includeBiome, bool includeBiomeTempRain)
{
return getChunkSnapshot(includeBiome, includeBiomeTempRain);
}
/// <summary>
/// Get a list of all entities in the chunk.
/// </summary>
/// <returns>The entities.</returns>
public Entity.Entity[] getEntities()
{
if (NativeBridge.GetChunkEntities == null) return Array.Empty<Entity.Entity>();
int dimId = _world.getDimensionId();
int count = NativeBridge.GetChunkEntities(dimId, _chunkX, _chunkZ, out IntPtr buf);
if (count <= 0 || buf == IntPtr.Zero) return Array.Empty<Entity.Entity>();
var result = new Entity.Entity[count];
try
{
int[] data = new int[count * 3];
Marshal.Copy(buf, data, 0, count * 3);
for (int i = 0; i < count; i++)
{
int entityId = data[i * 3];
int mappedType = data[i * 3 + 1];
int isLiving = data[i * 3 + 2];
var entityType = Enum.IsDefined(typeof(Entity.EntityType), mappedType)
? (Entity.EntityType)mappedType
: Entity.EntityType.UNKNOWN;
if (entityType == Entity.EntityType.PLAYER)
{
var player = FourKit.GetPlayerByEntityId(entityId);
if (player != null)
{
result[i] = player;
continue;
}
}
if (isLiving == 1)
{
result[i] = new Entity.LivingEntity(entityId, entityType, dimId, 0, 0, 0);
}
else
{
var entity = new Entity.Entity();
entity.SetEntityIdInternal(entityId);
entity.SetEntityTypeInternal(entityType);
entity.SetDimensionInternal(dimId);
result[i] = entity;
}
}
}
finally
{
Marshal.FreeCoTaskMem(buf);
}
return result;
}
/// <summary>
/// Checks if the chunk is loaded.
/// </summary>
/// <returns>True if it is loaded.</returns>
public bool isLoaded()
{
if (NativeBridge.IsChunkLoaded != null)
return NativeBridge.IsChunkLoaded(_world.getDimensionId(), _chunkX, _chunkZ) != 0;
return false;
}
/// <summary>
/// Loads the chunk.
/// </summary>
/// <param name="generate">Whether or not to generate a chunk if it doesn't already exist.</param>
/// <returns>true if the chunk has loaded successfully, otherwise false.</returns>
public bool load(bool generate)
{
if (NativeBridge.LoadChunk != null)
return NativeBridge.LoadChunk(_world.getDimensionId(), _chunkX, _chunkZ, generate ? 1 : 0) != 0;
return false;
}
/// <summary>
/// Loads the chunk.
/// </summary>
/// <returns>true if the chunk has loaded successfully, otherwise false.</returns>
public bool load()
{
return load(true);
}
/// <summary>
/// Unloads and optionally saves the Chunk.
/// </summary>
/// <param name="save">Controls whether the chunk is saved.</param>
/// <param name="safe">Controls whether to unload the chunk when players are nearby.</param>
/// <returns>true if the chunk has unloaded successfully, otherwise false.</returns>
public bool unload(bool save, bool safe)
{
if (NativeBridge.UnloadChunk != null)
return NativeBridge.UnloadChunk(_world.getDimensionId(), _chunkX, _chunkZ, save ? 1 : 0, safe ? 1 : 0) != 0;
return false;
}
/// <summary>
/// Unloads and optionally saves the Chunk.
/// </summary>
/// <param name="save">Controls whether the chunk is saved.</param>
/// <returns>true if the chunk has unloaded successfully, otherwise false.</returns>
public bool unload(bool save)
{
return unload(save, true);
}
/// <summary>
/// Unloads and optionally saves the Chunk.
/// </summary>
/// <returns>true if the chunk has unloaded successfully, otherwise false.</returns>
public bool unload()
{
return unload(true, true);
}
}
@@ -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();
}
}
+35 -3
View File
@@ -11,7 +11,7 @@ public abstract class HumanEntity : LivingEntity, InventoryHolder
private GameMode _gameMode = GameMode.SURVIVAL;
private string _name = string.Empty;
internal PlayerInventory _playerInventory = new();
internal Inventory _enderChestInventory = new("Ender Chest", InventoryType.ENDER_CHEST, 27);
internal EnderChestInventory? _enderChestInventory;
private ItemStack? _cursorItem;
private bool _sleeping;
private int _sleepTicks;
@@ -59,6 +59,8 @@ public abstract class HumanEntity : LivingEntity, InventoryHolder
/// <returns>The EnderChest of the player.</returns>
public Inventory getEnderChest()
{
// AAAAAH
_enderChestInventory ??= new EnderChestInventory(getEntityId());
return _enderChestInventory;
}
@@ -86,14 +88,44 @@ public abstract class HumanEntity : LivingEntity, InventoryHolder
/// Will always be empty if the player currently has no open window.
/// </summary>
/// <returns>The ItemStack of the item you are currently moving around.</returns>
public ItemStack? getItemOnCursor() => _cursorItem;
public ItemStack? getItemOnCursor()
{
if (NativeBridge.GetCarriedItem != null)
{
int[] buf = new int[3];
var gh = System.Runtime.InteropServices.GCHandle.Alloc(buf, System.Runtime.InteropServices.GCHandleType.Pinned);
try
{
NativeBridge.GetCarriedItem(getEntityId(), gh.AddrOfPinnedObject());
}
finally
{
gh.Free();
}
int id = buf[0];
int aux = buf[1];
int count = buf[2];
if (id > 0 && count > 0)
_cursorItem = new ItemStack(id, count, (short)aux);
else
_cursorItem = null;
}
return _cursorItem;
}
/// <summary>
/// Sets the item to the given ItemStack, this will replace whatever the
/// user was moving. Will always be empty if the player currently has no open window.
/// </summary>
/// <param name="item">The ItemStack which will end up in the hand.</param>
public void setItemOnCursor(ItemStack? item) => _cursorItem = item;
public void setItemOnCursor(ItemStack? item)
{
_cursorItem = item;
NativeBridge.SetCarriedItem?.Invoke(getEntityId(),
item?.getTypeId() ?? 0,
item?.getAmount() ?? 0,
item?.getDurability() ?? 0);
}
/// <summary>
/// If the player currently has an inventory window open, this method will
@@ -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;
}
}
+61 -39
View File
@@ -22,60 +22,80 @@ internal sealed class EventDispatcher
public int CompareTo(RegisteredHandler other) => Priority.CompareTo(other.Priority);
}
private readonly Dictionary<Type, List<RegisteredHandler>> _handlers = new();
private readonly object _lock = new();
// Snapshot-on-write: writers swap _handlers atomically; Fire reads it lock-free.
private volatile Dictionary<Type, RegisteredHandler[]> _handlers = new();
private readonly object _writeLock = new();
// Fired when an event type gains its first handler.
internal Action<Type>? OnSubscriptionChanged;
public void Register(Listener listener)
{
var methods = listener.GetType().GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
lock (_lock)
List<(Type eventType, RegisteredHandler handler)>? pending = null;
foreach (var method in methods)
{
foreach (var method in methods)
var attr = method.GetCustomAttribute<Event.EventHandlerAttribute>();
if (attr == null)
continue;
var parameters = method.GetParameters();
if (parameters.Length != 1)
{
var attr = method.GetCustomAttribute<Event.EventHandlerAttribute>();
if (attr == null)
continue;
var parameters = method.GetParameters();
if (parameters.Length != 1)
{
Console.WriteLine($"[FourKit] Warning: @EventHandler method {method.Name} must have exactly 1 parameter, skipping.");
continue;
}
var eventType = parameters[0].ParameterType;
if (!typeof(Event.Event).IsAssignableFrom(eventType))
{
Console.WriteLine($"[FourKit] Warning: @EventHandler method {method.Name} parameter must extend Event, skipping.");
continue;
}
if (!_handlers.TryGetValue(eventType, out var list))
{
list = new List<RegisteredHandler>();
_handlers[eventType] = list;
}
list.Add(new RegisteredHandler(listener, method, attr.Priority, attr.IgnoreCancelled));
_handlers[eventType] = list.OrderBy(h => h.Priority).ToList();
Console.WriteLine($"[FourKit] Warning: @EventHandler method {method.Name} must have exactly 1 parameter, skipping.");
continue;
}
var eventType = parameters[0].ParameterType;
if (!typeof(Event.Event).IsAssignableFrom(eventType))
{
Console.WriteLine($"[FourKit] Warning: @EventHandler method {method.Name} parameter must extend Event, skipping.");
continue;
}
pending ??= new List<(Type, RegisteredHandler)>();
pending.Add((eventType, new RegisteredHandler(listener, method, attr.Priority, attr.IgnoreCancelled)));
}
if (pending == null) return;
HashSet<Type> newlySubscribed = new();
lock (_writeLock)
{
var newDict = new Dictionary<Type, RegisteredHandler[]>(_handlers);
foreach (var (eventType, handler) in pending)
{
bool hadAny = newDict.TryGetValue(eventType, out var existing);
existing ??= Array.Empty<RegisteredHandler>();
// OrderBy is stable; Array.Sort is not.
var combined = existing.Append(handler).OrderBy(h => h.Priority).ToArray();
newDict[eventType] = combined;
if (!hadAny) newlySubscribed.Add(eventType);
}
_handlers = newDict;
}
if (OnSubscriptionChanged != null)
{
foreach (var t in newlySubscribed)
OnSubscriptionChanged(t);
}
}
public void Fire(Event.Event evt)
{
List<RegisteredHandler>? handlers;
lock (_lock)
{
if (!_handlers.TryGetValue(evt.GetType(), out handlers))
return;
handlers = new List<RegisteredHandler>(handlers);
}
var snapshot = _handlers;
if (!snapshot.TryGetValue(evt.GetType(), out var handlers))
return;
var cancellable = evt as Cancellable;
foreach (var handler in handlers)
for (int i = 0; i < handlers.Length; i++)
{
ref readonly var handler = ref handlers[i];
if (handler.IgnoreCancelled && cancellable != null && cancellable.isCancelled())
continue;
@@ -89,4 +109,6 @@ internal sealed class EventDispatcher
}
}
}
internal bool IsSubscribed(Type eventType) => _handlers.ContainsKey(eventType);
}
+58 -1
View File
@@ -11,11 +11,68 @@ using Minecraft.Server.FourKit.Plugin;
/// </summary>
public static class FourKit
{
private static readonly EventDispatcher _dispatcher = new();
private static readonly EventDispatcher _dispatcher;
private static readonly Dictionary<string, Player> _players = new(StringComparer.OrdinalIgnoreCase);
private static readonly Dictionary<int, Player> _playersByEntityId = new();
private static readonly object _playerLock = new();
// Must match HandlerKind in FourKitNatives.h.
private enum HandlerKind
{
ChunkLoad = 0,
ChunkUnload = 1,
PlayerMove = 2,
}
private static uint _handlerMask;
private static readonly object _handlerMaskLock = new();
static FourKit()
{
_dispatcher = new EventDispatcher();
_dispatcher.OnSubscriptionChanged = OnEventSubscribed;
}
private static HandlerKind? MapEventTypeToHandlerKind(Type eventType)
{
if (eventType == typeof(Event.World.ChunkLoadEvent)) return HandlerKind.ChunkLoad;
if (eventType == typeof(Event.World.ChunkUnloadEvent)) return HandlerKind.ChunkUnload;
if (eventType == typeof(Event.Player.PlayerMoveEvent)) return HandlerKind.PlayerMove;
return null;
}
private static void OnEventSubscribed(Type eventType)
{
var kind = MapEventTypeToHandlerKind(eventType);
if (kind == null) return;
lock (_handlerMaskLock)
{
uint newMask = _handlerMask | (1u << (int)kind.Value);
if (newMask == _handlerMask) return;
_handlerMask = newMask;
NativeBridge.SetHandlerMask?.Invoke(_handlerMask);
}
}
internal static void ResyncHandlerMask()
{
lock (_handlerMaskLock)
{
NativeBridge.SetHandlerMask?.Invoke(_handlerMask);
}
}
/// <summary>
/// Gets the current server tick count. Increments once per server tick
/// (~20 per second under nominal load). Useful for measuring TPS by
/// sampling the delta against wall clock time.
/// </summary>
public static int getServerTick()
{
return NativeBridge.GetServerTickCount?.Invoke() ?? 0;
}
internal const int MAX_CHAT_LENGTH = 123;
private static readonly Dictionary<int, World> _worldsByDimId = new();
@@ -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;
}
+9
View File
@@ -186,6 +186,15 @@ public class Location
public Location clone() => new Location(LocationWorld, X, Y, Z, Yaw, Pitch);
/// <summary>
/// Gets the chunk at the represented location.
/// </summary>
/// <returns>Chunk at the represented location.</returns>
public Chunk.Chunk getChunk()
{
return getWorld().getChunkAt(getBlockX() >> 4, getBlockZ() >> 4);
}
/// <inheritdoc/>
public override string ToString() => $"Location(world={LocationWorld.getName()}, x={X}, y={Y}, z={Z}, yaw={Yaw}, pitch={Pitch})";
}
@@ -7,5 +7,7 @@
<AssemblyName>Minecraft.Server.FourKit</AssemblyName>
<EnableDynamicLoading>true</EnableDynamicLoading>
<BaseOutputPath>bin</BaseOutputPath>
<ServerGarbageCollection>true</ServerGarbageCollection>
<ConcurrentGarbageCollection>true</ConcurrentGarbageCollection>
</PropertyGroup>
</Project>
+127 -3
View File
@@ -43,10 +43,10 @@ internal static class NativeBridge
internal delegate int NativeGetTileDataDelegate(int dimId, int x, int y, int z);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void NativeSetTileDelegate(int dimId, int x, int y, int z, int tileId, int data);
internal delegate void NativeSetTileDelegate(int dimId, int x, int y, int z, int tileId, int data, int flags);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void NativeSetTileDataDelegate(int dimId, int x, int y, int z, int data);
internal delegate void NativeSetTileDataDelegate(int dimId, int x, int y, int z, int data, int flags);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate int NativeBreakBlockDelegate(int dimId, int x, int y, int z);
@@ -123,6 +123,18 @@ internal static class NativeBridge
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void NativeSetHeldItemSlotDelegate(int entityId, int slot);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void NativeGetCarriedItemDelegate(int entityId, IntPtr outData);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void NativeSetCarriedItemDelegate(int entityId, int itemId, int count, int aux);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void NativeGetEnderChestContentsDelegate(int entityId, IntPtr outData);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void NativeSetEnderChestSlotDelegate(int entityId, int slot, int itemId, int count, int aux);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void NativeSetSneakingDelegate(int entityId, int sneak);
@@ -180,6 +192,58 @@ internal static class NativeBridge
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void NativeGetEntityInfoDelegate(int entityId, IntPtr outBuf);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate int NativeIsChunkLoadedDelegate(int dimId, int chunkX, int chunkZ);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate int NativeLoadChunkDelegate(int dimId, int chunkX, int chunkZ, int generate);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate int NativeUnloadChunkDelegate(int dimId, int chunkX, int chunkZ, int save, int safe);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate int NativeGetLoadedChunksDelegate(int dimId, out IntPtr coordBuf);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate int NativeIsChunkInUseDelegate(int dimId, int chunkX, int chunkZ);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void NativeGetChunkSnapshotDelegate(int dimId, int chunkX, int chunkZ, IntPtr blockIds, IntPtr blockData, IntPtr maxBlockY);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate int NativeUnloadChunkRequestDelegate(int dimId, int chunkX, int chunkZ, int safe);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate int NativeRegenerateChunkDelegate(int dimId, int chunkX, int chunkZ);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate int NativeRefreshChunkDelegate(int dimId, int chunkX, int chunkZ);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate int NativeGetWorldEntitiesDelegate(int dimId, out IntPtr outBuf);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate int NativeGetChunkEntitiesDelegate(int dimId, int chunkX, int chunkZ, out IntPtr outBuf);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate int NativeGetSkyLightDelegate(int dimId, int x, int y, int z);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate int NativeGetBlockLightDelegate(int dimId, int x, int y, int z);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate int NativeGetBiomeIdDelegate(int dimId, int x, int z);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void NativeSetBiomeIdDelegate(int dimId, int x, int z, int biomeId);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void NativeSetHandlerMaskDelegate(uint mask);
internal static NativeSetHandlerMaskDelegate? SetHandlerMask;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate int NativeGetServerTickCountDelegate();
internal static NativeGetServerTickCountDelegate? GetServerTickCount;
internal static NativeDamageDelegate? DamagePlayer;
internal static NativeSetHealthDelegate? SetPlayerHealth;
@@ -221,6 +285,10 @@ internal static class NativeBridge
internal static NativeGetItemMetaDelegate? GetItemMeta;
internal static NativeSetItemMetaDelegate? SetItemMeta;
internal static NativeSetHeldItemSlotDelegate? SetHeldItemSlot;
internal static NativeGetCarriedItemDelegate? GetCarriedItem;
internal static NativeSetCarriedItemDelegate? SetCarriedItem;
internal static NativeGetEnderChestContentsDelegate? GetEnderChestContents;
internal static NativeSetEnderChestSlotDelegate? SetEnderChestSlot;
internal static NativeSetSneakingDelegate? SetSneaking;
internal static NativeSetVelocityDelegate? SetVelocity;
internal static NativeSetAllowFlightDelegate? SetAllowFlight;
@@ -240,6 +308,21 @@ internal static class NativeBridge
internal static NativeGetVehicleIdDelegate? GetVehicleId;
internal static NativeGetPassengerIdDelegate? GetPassengerId;
internal static NativeGetEntityInfoDelegate? GetEntityInfo;
internal static NativeIsChunkLoadedDelegate? IsChunkLoaded;
internal static NativeLoadChunkDelegate? LoadChunk;
internal static NativeUnloadChunkDelegate? UnloadChunk;
internal static NativeGetLoadedChunksDelegate? GetLoadedChunks;
internal static NativeIsChunkInUseDelegate? IsChunkInUse;
internal static NativeGetChunkSnapshotDelegate? GetChunkSnapshot;
internal static NativeUnloadChunkRequestDelegate? UnloadChunkRequest;
internal static NativeRegenerateChunkDelegate? RegenerateChunk;
internal static NativeRefreshChunkDelegate? RefreshChunk;
internal static NativeGetWorldEntitiesDelegate? GetWorldEntities;
internal static NativeGetChunkEntitiesDelegate? GetChunkEntities;
internal static NativeGetSkyLightDelegate? GetSkyLight;
internal static NativeGetBlockLightDelegate? GetBlockLight;
internal static NativeGetBiomeIdDelegate? GetBiomeId;
internal static NativeSetBiomeIdDelegate? SetBiomeId;
internal static void SetCallbacks(IntPtr damage, IntPtr setHealth, IntPtr teleport, IntPtr setGameMode, IntPtr broadcastMessage, IntPtr setFallDistance, IntPtr getPlayerSnapshot, IntPtr sendMessage, IntPtr setWalkSpeed, IntPtr teleportEntity)
{
@@ -286,7 +369,7 @@ internal static class NativeBridge
SendRaw = Marshal.GetDelegateForFunctionPointer<NativeSendRawDelegate>(sendRaw);
}
internal static void SetInventoryCallbacks(IntPtr getPlayerInventory, IntPtr setPlayerInventorySlot, IntPtr getContainerContents, IntPtr setContainerSlot, IntPtr getContainerViewerEntityIds, IntPtr closeContainer, IntPtr openVirtualContainer, IntPtr getItemMeta, IntPtr setItemMeta, IntPtr setHeldItemSlot)
internal static void SetInventoryCallbacks(IntPtr getPlayerInventory, IntPtr setPlayerInventorySlot, IntPtr getContainerContents, IntPtr setContainerSlot, IntPtr getContainerViewerEntityIds, IntPtr closeContainer, IntPtr openVirtualContainer, IntPtr getItemMeta, IntPtr setItemMeta, IntPtr setHeldItemSlot, IntPtr getCarriedItem, IntPtr setCarriedItem, IntPtr getEnderChestContents, IntPtr setEnderChestSlot)
{
GetPlayerInventory = Marshal.GetDelegateForFunctionPointer<NativeGetPlayerInventoryDelegate>(getPlayerInventory);
SetPlayerInventorySlot = Marshal.GetDelegateForFunctionPointer<NativeSetPlayerInventorySlotDelegate>(setPlayerInventorySlot);
@@ -298,6 +381,10 @@ internal static class NativeBridge
GetItemMeta = Marshal.GetDelegateForFunctionPointer<NativeGetItemMetaDelegate>(getItemMeta);
SetItemMeta = Marshal.GetDelegateForFunctionPointer<NativeSetItemMetaDelegate>(setItemMeta);
SetHeldItemSlot = Marshal.GetDelegateForFunctionPointer<NativeSetHeldItemSlotDelegate>(setHeldItemSlot);
GetCarriedItem = Marshal.GetDelegateForFunctionPointer<NativeGetCarriedItemDelegate>(getCarriedItem);
SetCarriedItem = Marshal.GetDelegateForFunctionPointer<NativeSetCarriedItemDelegate>(setCarriedItem);
GetEnderChestContents = Marshal.GetDelegateForFunctionPointer<NativeGetEnderChestContentsDelegate>(getEnderChestContents);
SetEnderChestSlot = Marshal.GetDelegateForFunctionPointer<NativeSetEnderChestSlotDelegate>(setEnderChestSlot);
}
internal static void SetEntityCallbacks(IntPtr setSneaking, IntPtr setVelocity, IntPtr setAllowFlight, IntPtr playSound, IntPtr setSleepingIgnored)
@@ -334,4 +421,41 @@ internal static class NativeBridge
GetPassengerId = Marshal.GetDelegateForFunctionPointer<NativeGetPassengerIdDelegate>(getPassengerId);
GetEntityInfo = Marshal.GetDelegateForFunctionPointer<NativeGetEntityInfoDelegate>(getEntityInfo);
}
internal static void SetChunkCallbacks(IntPtr isChunkLoaded, IntPtr loadChunk, IntPtr unloadChunk, IntPtr getLoadedChunks, IntPtr isChunkInUse, IntPtr getChunkSnapshot, IntPtr unloadChunkRequest, IntPtr regenerateChunk, IntPtr refreshChunk)
{
IsChunkLoaded = Marshal.GetDelegateForFunctionPointer<NativeIsChunkLoadedDelegate>(isChunkLoaded);
LoadChunk = Marshal.GetDelegateForFunctionPointer<NativeLoadChunkDelegate>(loadChunk);
UnloadChunk = Marshal.GetDelegateForFunctionPointer<NativeUnloadChunkDelegate>(unloadChunk);
GetLoadedChunks = Marshal.GetDelegateForFunctionPointer<NativeGetLoadedChunksDelegate>(getLoadedChunks);
IsChunkInUse = Marshal.GetDelegateForFunctionPointer<NativeIsChunkInUseDelegate>(isChunkInUse);
GetChunkSnapshot = Marshal.GetDelegateForFunctionPointer<NativeGetChunkSnapshotDelegate>(getChunkSnapshot);
UnloadChunkRequest = Marshal.GetDelegateForFunctionPointer<NativeUnloadChunkRequestDelegate>(unloadChunkRequest);
RegenerateChunk = Marshal.GetDelegateForFunctionPointer<NativeRegenerateChunkDelegate>(regenerateChunk);
RefreshChunk = Marshal.GetDelegateForFunctionPointer<NativeRefreshChunkDelegate>(refreshChunk);
}
internal static void SetWorldEntityCallbacks(IntPtr getWorldEntities, IntPtr getChunkEntities)
{
GetWorldEntities = Marshal.GetDelegateForFunctionPointer<NativeGetWorldEntitiesDelegate>(getWorldEntities);
GetChunkEntities = Marshal.GetDelegateForFunctionPointer<NativeGetChunkEntitiesDelegate>(getChunkEntities);
}
internal static void SetBlockInfoCallbacks(IntPtr getSkyLight, IntPtr getBlockLight, IntPtr getBiomeId, IntPtr setBiomeId)
{
GetSkyLight = Marshal.GetDelegateForFunctionPointer<NativeGetSkyLightDelegate>(getSkyLight);
GetBlockLight = Marshal.GetDelegateForFunctionPointer<NativeGetBlockLightDelegate>(getBlockLight);
GetBiomeId = Marshal.GetDelegateForFunctionPointer<NativeGetBiomeIdDelegate>(getBiomeId);
SetBiomeId = Marshal.GetDelegateForFunctionPointer<NativeSetBiomeIdDelegate>(setBiomeId);
}
internal static void SetSubscriptionCallbacks(IntPtr setHandlerMask)
{
SetHandlerMask = Marshal.GetDelegateForFunctionPointer<NativeSetHandlerMaskDelegate>(setHandlerMask);
}
internal static void SetServerCallbacks(IntPtr getServerTickCount)
{
GetServerTickCount = Marshal.GetDelegateForFunctionPointer<NativeGetServerTickCountDelegate>(getServerTickCount);
}
}
+346
View File
@@ -1,4 +1,5 @@
using System.Runtime.InteropServices;
using Minecraft.Server.FourKit.Chunk;
using Minecraft.Server.FourKit.Entity;
using Minecraft.Server.FourKit.Inventory;
@@ -245,6 +246,115 @@ public class World
return result;
}
/// <summary>
/// Get a list of all entities in this World.
/// </summary>
/// <returns>A list of all Entities currently residing in this world.</returns>
public List<Entity.Entity> getEntities()
{
var result = new List<Entity.Entity>();
if (NativeBridge.GetWorldEntities == null) return result;
int count = NativeBridge.GetWorldEntities(_dimensionId, out IntPtr buf);
if (count <= 0 || buf == IntPtr.Zero) return result;
try
{
int[] data = new int[count * 3];
Marshal.Copy(buf, data, 0, count * 3);
for (int i = 0; i < count; i++)
{
int entityId = data[i * 3];
int mappedType = data[i * 3 + 1];
int isLiving = data[i * 3 + 2];
var entityType = Enum.IsDefined(typeof(Entity.EntityType), mappedType)
? (Entity.EntityType)mappedType
: Entity.EntityType.UNKNOWN;
if (entityType == Entity.EntityType.PLAYER)
{
var player = FourKit.GetPlayerByEntityId(entityId);
if (player != null)
{
result.Add(player);
continue;
}
}
if (isLiving == 1)
{
result.Add(new Entity.LivingEntity(entityId, entityType, _dimensionId, 0, 0, 0));
}
else
{
var entity = new Entity.Entity();
entity.SetEntityIdInternal(entityId);
entity.SetEntityTypeInternal(entityType);
entity.SetDimensionInternal(_dimensionId);
result.Add(entity);
}
}
}
finally
{
Marshal.FreeCoTaskMem(buf);
}
return result;
}
/// <summary>
/// Get a list of all living entities in this World.
/// </summary>
/// <returns>A list of all LivingEntities currently residing in this world.</returns>
public List<Entity.LivingEntity> getLivingEntities()
{
var result = new List<Entity.LivingEntity>();
if (NativeBridge.GetWorldEntities == null) return result;
int count = NativeBridge.GetWorldEntities(_dimensionId, out IntPtr buf);
if (count <= 0 || buf == IntPtr.Zero) return result;
try
{
int[] data = new int[count * 3];
Marshal.Copy(buf, data, 0, count * 3);
for (int i = 0; i < count; i++)
{
int entityId = data[i * 3];
int mappedType = data[i * 3 + 1];
int isLiving = data[i * 3 + 2];
if (isLiving != 1) continue;
var entityType = Enum.IsDefined(typeof(Entity.EntityType), mappedType)
? (Entity.EntityType)mappedType
: Entity.EntityType.UNKNOWN;
if (entityType == Entity.EntityType.PLAYER)
{
var player = FourKit.GetPlayerByEntityId(entityId);
if (player != null)
{
result.Add(player);
continue;
}
}
result.Add(new Entity.LivingEntity(entityId, entityType, _dimensionId, 0, 0, 0));
}
}
finally
{
Marshal.FreeCoTaskMem(buf);
}
return result;
}
/// <summary>
/// Creates explosion at given coordinates with given power.
/// </summary>
@@ -374,4 +484,240 @@ public class World
{
NativeBridge.DropItem?.Invoke(_dimensionId, location.X, location.Y, location.Z, item.getTypeId(), item.getAmount(), item.getDurability(), 1);
}
/// <summary>
/// Gets the Chunk at the given coordinates.
/// </summary>
/// <param name="x">X-coordinate of the chunk.</param>
/// <param name="z">Z-coordinate of the chunk.</param>
/// <returns>Chunk at the given coordinates.</returns>
public Chunk.Chunk getChunkAt(int x, int z)
{
return new Chunk.Chunk(this, x, z);
}
/// <summary>
/// Gets the Chunk at the given Location.
/// </summary>
/// <param name="location">Location of the chunk.</param>
/// <returns>Chunk at the given location.</returns>
public Chunk.Chunk getChunkAt(Location location)
{
return getChunkAt(location.getBlockX() >> 4, location.getBlockZ() >> 4);
}
/// <summary>
/// Gets the Chunk that contains the given Block.
/// </summary>
/// <param name="block">Block to get the containing chunk from.</param>
/// <returns>The chunk that contains the given block.</returns>
public Chunk.Chunk getChunkAt(Block.Block block)
{
return getChunkAt(block.getX() >> 4, block.getZ() >> 4);
}
/// <summary>
/// Checks if the specified Chunk is loaded.
/// </summary>
/// <param name="chunk">The chunk to check.</param>
/// <returns>true if the chunk is loaded, otherwise false.</returns>
public bool isChunkLoaded(Chunk.Chunk chunk)
{
return isChunkLoaded(chunk.getX(), chunk.getZ());
}
/// <summary>
/// Checks if the Chunk at the specified coordinates is loaded.
/// </summary>
/// <param name="x">X-coordinate of the chunk.</param>
/// <param name="z">Z-coordinate of the chunk.</param>
/// <returns>true if the chunk is loaded, otherwise false.</returns>
public bool isChunkLoaded(int x, int z)
{
if (NativeBridge.IsChunkLoaded != null)
return NativeBridge.IsChunkLoaded(_dimensionId, x, z) != 0;
return false;
}
/// <summary>
/// Gets an array of all loaded Chunks.
/// </summary>
/// <returns>Chunk[] containing all loaded chunks.</returns>
public Chunk.Chunk[] getLoadedChunks()
{
if (NativeBridge.GetLoadedChunks == null)
return Array.Empty<Chunk.Chunk>();
int count = NativeBridge.GetLoadedChunks(_dimensionId, out IntPtr buf);
if (count <= 0 || buf == IntPtr.Zero)
return Array.Empty<Chunk.Chunk>();
try
{
int[] coords = new int[count * 2];
Marshal.Copy(buf, coords, 0, count * 2);
var chunks = new Chunk.Chunk[count];
for (int i = 0; i < count; i++)
chunks[i] = new Chunk.Chunk(this, coords[i * 2], coords[i * 2 + 1]);
return chunks;
}
finally
{
Marshal.FreeCoTaskMem(buf);
}
}
/// <summary>
/// Loads the specified Chunk.
/// </summary>
/// <param name="chunk">The chunk to load.</param>
public void loadChunk(Chunk.Chunk chunk)
{
loadChunk(chunk.getX(), chunk.getZ());
}
/// <summary>
/// Loads the Chunk at the specified coordinates.
/// If the chunk does not exist, it will be generated. This method is
/// analogous to loadChunk(int, int, boolean) where generate is true.
/// </summary>
/// <param name="x">X-coordinate of the chunk.</param>
/// <param name="z">Z-coordinate of the chunk.</param>
public void loadChunk(int x, int z)
{
loadChunk(x, z, true);
}
/// <summary>
/// Loads the Chunk at the specified coordinates.
/// </summary>
/// <param name="x">X-coordinate of the chunk.</param>
/// <param name="z">Z-coordinate of the chunk.</param>
/// <param name="generate">Whether or not to generate a chunk if it doesn't already exist.</param>
/// <returns>true if the chunk has loaded successfully, otherwise false.</returns>
public bool loadChunk(int x, int z, bool generate)
{
if (NativeBridge.LoadChunk != null)
return NativeBridge.LoadChunk(_dimensionId, x, z, generate ? 1 : 0) != 0;
return false;
}
/// <summary>
/// Checks if the Chunk at the specified coordinates is loaded and in use
/// by one or more players.
/// </summary>
/// <param name="x">X-coordinate of the chunk.</param>
/// <param name="z">Z-coordinate of the chunk.</param>
/// <returns>true if the chunk is loaded and in use by one or more players, otherwise false.</returns>
public bool isChunkInUse(int x, int z)
{
if (NativeBridge.IsChunkInUse != null)
return NativeBridge.IsChunkInUse(_dimensionId, x, z) != 0;
return false;
}
/// <summary>
/// Safely unloads and saves the Chunk at the specified coordinates.
/// This method is analogous to unloadChunk(int, int, boolean, boolean)
/// where safe and save is true.
/// </summary>
/// <param name="chunk">The chunk to unload.</param>
/// <returns>true if the chunk has unloaded successfully, otherwise false.</returns>
public bool unloadChunk(Chunk.Chunk chunk)
{
return unloadChunk(chunk.getX(), chunk.getZ());
}
/// <summary>
/// Safely unloads and saves the Chunk at the specified coordinates.
/// This method is analogous to unloadChunk(int, int, boolean, boolean)
/// where safe and save is true.
/// </summary>
/// <param name="x">X-coordinate of the chunk.</param>
/// <param name="z">Z-coordinate of the chunk.</param>
/// <returns>true if the chunk has unloaded successfully, otherwise false.</returns>
public bool unloadChunk(int x, int z)
{
return unloadChunk(x, z, true, true);
}
/// <summary>
/// Safely unloads and optionally saves the Chunk at the specified coordinates.
/// </summary>
/// <param name="x">X-coordinate of the chunk.</param>
/// <param name="z">Z-coordinate of the chunk.</param>
/// <param name="save">Whether or not to save the chunk.</param>
/// <returns>true if the chunk has unloaded successfully, otherwise false.</returns>
public bool unloadChunk(int x, int z, bool save)
{
return unloadChunk(x, z, save, true);
}
/// <summary>
/// Unloads and optionally saves the Chunk at the specified coordinates.
/// </summary>
/// <param name="x">X-coordinate of the chunk.</param>
/// <param name="z">Z-coordinate of the chunk.</param>
/// <param name="save">Controls whether the chunk is saved.</param>
/// <param name="safe">Controls whether to unload the chunk when players are nearby.</param>
/// <returns>true if the chunk has unloaded successfully, otherwise false.</returns>
public bool unloadChunk(int x, int z, bool save, bool safe)
{
if (NativeBridge.UnloadChunk != null)
return NativeBridge.UnloadChunk(_dimensionId, x, z, save ? 1 : 0, safe ? 1 : 0) != 0;
return false;
}
/// <summary>
/// Safely queues the Chunk at the specified coordinates for unloading.
/// This method is analogous to unloadChunkRequest(int, int, boolean)
/// where safe is true.
/// </summary>
/// <param name="x">X-coordinate of the chunk.</param>
/// <param name="z">Z-coordinate of the chunk.</param>
/// <returns>true is the queue attempt was successful, otherwise false.</returns>
public bool unloadChunkRequest(int x, int z)
{
return unloadChunkRequest(x, z, true);
}
/// <summary>
/// Queues the Chunk at the specified coordinates for unloading.
/// </summary>
/// <param name="x">X-coordinate of the chunk.</param>
/// <param name="z">Z-coordinate of the chunk.</param>
/// <param name="safe">Controls whether to queue the chunk when players are nearby.</param>
/// <returns>Whether the chunk was actually queued.</returns>
public bool unloadChunkRequest(int x, int z, bool safe)
{
if (NativeBridge.UnloadChunkRequest != null)
return NativeBridge.UnloadChunkRequest(_dimensionId, x, z, safe ? 1 : 0) != 0;
return false;
}
/// <summary>
/// Regenerates the Chunk at the specified coordinates.
/// </summary>
/// <param name="x">X-coordinate of the chunk.</param>
/// <param name="z">Z-coordinate of the chunk.</param>
/// <returns>Whether the chunk was actually regenerated.</returns>
public bool regenerateChunk(int x, int z)
{
if (NativeBridge.RegenerateChunk != null)
return NativeBridge.RegenerateChunk(_dimensionId, x, z) != 0;
return false;
}
/// <summary>
/// Resends the Chunk to all clients.
/// </summary>
/// <param name="x">X-coordinate of the chunk.</param>
/// <param name="z">Z-coordinate of the chunk.</param>
/// <returns>Whether the chunk was actually refreshed.</returns>
public bool refreshChunk(int x, int z)
{
if (NativeBridge.RefreshChunk != null)
return NativeBridge.RefreshChunk(_dimensionId, x, z) != 0;
return false;
}
}
@@ -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>
+77 -3
View File
@@ -62,7 +62,7 @@ typedef void(__stdcall *fn_set_player_connection_callbacks)(void *sendRaw);
typedef long long(__stdcall *fn_fire_player_drop_item)(int entityId,
int itemId, int itemCount, int itemAux,
int *outItemId, int *outItemCount, int *outItemAux);
typedef void(__stdcall *fn_set_inventory_callbacks)(void *getPlayerInventory, void *setPlayerInventorySlot, void *getContainerContents, void *setContainerSlot, void *getContainerViewerEntityIds, void *closeContainer, void *openVirtualContainer, void *getItemMeta, void *setItemMeta, void *setHeldItemSlot);
typedef void(__stdcall *fn_set_inventory_callbacks)(void *getPlayerInventory, void *setPlayerInventorySlot, void *getContainerContents, void *setContainerSlot, void *getContainerViewerEntityIds, void *closeContainer, void *openVirtualContainer, void *getItemMeta, void *setItemMeta, void *setHeldItemSlot, void *getCarriedItem, void *setCarriedItem, void *getEnderChestContents, void *setEnderChestSlot);
typedef int(__stdcall *fn_fire_player_interact)(int entityId, int action,
int itemId, int itemCount, int itemAux,
int clickedX, int clickedY, int clickedZ,
@@ -103,6 +103,13 @@ typedef int(__stdcall *fn_fire_piston_extend)(int dimId, int x, int y, int z, in
typedef int(__stdcall *fn_fire_piston_retract)(int dimId, int x, int y, int z, int direction);
typedef int(__stdcall *fn_fire_command_preprocess)(int entityId, const char *cmdUtf8, int cmdByteLen, char *outBuf, int outBufSize, int *outLen);
typedef int(__stdcall *fn_fire_block_from_to)(int dimId, int fromX, int fromY, int fromZ, int toX, int toY, int toZ, int face);
typedef void(__stdcall *fn_set_chunk_callbacks)(void *isChunkLoaded, void *loadChunk, void *unloadChunk, void *getLoadedChunks, void *isChunkInUse, void *getChunkSnapshot, void *unloadChunkRequest, void *regenerateChunk, void *refreshChunk);
typedef void(__stdcall *fn_set_block_info_callbacks)(void *getSkyLight, void *getBlockLight, void *getBiomeId, void *setBiomeId);
typedef void(__stdcall *fn_set_world_entity_callbacks)(void *getWorldEntities, void *getChunkEntities);
typedef void(__stdcall *fn_set_subscription_callbacks)(void *setHandlerMask);
typedef void(__stdcall *fn_set_server_callbacks)(void *getServerTickCount);
typedef void(__stdcall *fn_fire_chunk_load)(int dimId, int chunkX, int chunkZ, int isNewChunk);
typedef int(__stdcall *fn_fire_chunk_unload)(int dimId, int chunkX, int chunkZ);
struct OpenContainerInfo
{
@@ -160,6 +167,13 @@ static fn_fire_piston_extend s_managedFirePistonExtend = nullptr;
static fn_fire_piston_retract s_managedFirePistonRetract = nullptr;
static fn_fire_command_preprocess s_managedFireCommandPreprocess = nullptr;
static fn_fire_block_from_to s_managedFireBlockFromTo = nullptr;
static fn_set_chunk_callbacks s_managedSetChunkCallbacks = nullptr;
static fn_set_block_info_callbacks s_managedSetBlockInfoCallbacks = nullptr;
static fn_set_world_entity_callbacks s_managedSetWorldEntityCallbacks = nullptr;
static fn_set_subscription_callbacks s_managedSetSubscriptionCallbacks = nullptr;
static fn_set_server_callbacks s_managedSetServerCallbacks = nullptr;
static fn_fire_chunk_load s_managedFireChunkLoad = nullptr;
static fn_fire_chunk_unload s_managedFireChunkUnload = nullptr;
static bool s_initialized = false;
@@ -242,6 +256,13 @@ void Initialize()
{L"FirePistonRetract", (void **)&s_managedFirePistonRetract},
{L"FireCommandPreprocess", (void **)&s_managedFireCommandPreprocess},
{L"FireBlockFromTo", (void **)&s_managedFireBlockFromTo},
{L"SetChunkCallbacks", (void **)&s_managedSetChunkCallbacks},
{L"SetBlockInfoCallbacks", (void **)&s_managedSetBlockInfoCallbacks},
{L"SetWorldEntityCallbacks", (void **)&s_managedSetWorldEntityCallbacks},
{L"SetSubscriptionCallbacks", (void **)&s_managedSetSubscriptionCallbacks},
{L"SetServerCallbacks", (void **)&s_managedSetServerCallbacks},
{L"FireChunkLoad", (void **)&s_managedFireChunkLoad},
{L"FireChunkUnload", (void **)&s_managedFireChunkUnload},
};
bool ok = true;
@@ -307,7 +328,11 @@ void Initialize()
(void *)&NativeOpenVirtualContainer,
(void *)&NativeGetItemMeta,
(void *)&NativeSetItemMeta,
(void *)&NativeSetHeldItemSlot);
(void *)&NativeSetHeldItemSlot,
(void *)&NativeGetCarriedItem,
(void *)&NativeSetCarriedItem,
(void *)&NativeGetEnderChestContents,
(void *)&NativeSetEnderChestSlot);
s_managedSetEntityCallbacks(
(void *)&NativeSetSneaking,
@@ -336,6 +361,33 @@ void Initialize()
(void *)&NativeGetPassengerId,
(void *)&NativeGetEntityInfo);
s_managedSetChunkCallbacks(
(void *)&NativeIsChunkLoaded,
(void *)&NativeLoadChunk,
(void *)&NativeUnloadChunk,
(void *)&NativeGetLoadedChunks,
(void *)&NativeIsChunkInUse,
(void *)&NativeGetChunkSnapshot,
(void *)&NativeUnloadChunkRequest,
(void *)&NativeRegenerateChunk,
(void *)&NativeRefreshChunk);
s_managedSetBlockInfoCallbacks(
(void *)&NativeGetSkyLight,
(void *)&NativeGetBlockLight,
(void *)&NativeGetBiomeId,
(void *)&NativeSetBiomeId);
s_managedSetWorldEntityCallbacks(
(void *)&NativeGetWorldEntities,
(void *)&NativeGetChunkEntities);
s_managedSetSubscriptionCallbacks(
(void *)&NativeSetHandlerMask);
s_managedSetServerCallbacks(
(void *)&NativeGetServerTickCount);
LogInfo("fourkit", "FourKit initialized successfully.");
}
@@ -481,8 +533,12 @@ bool FirePlayerMove(int entityId,
double toX, double toY, double toZ,
double *outToX, double *outToY, double *outToZ)
{
if (!s_initialized || !s_managedFireMove)
// Caller reads outTo* unconditionally; init on every early-return.
if (!s_initialized || !s_managedFireMove || !HasHandlers(kHandlerKind_PlayerMove))
{
*outToX = toX;
*outToY = toY;
*outToZ = toZ;
return false;
}
@@ -1014,4 +1070,22 @@ bool FireBlockFromTo(int dimId, int fromX, int fromY, int fromZ, int toX, int to
return false;
return s_managedFireBlockFromTo(dimId, fromX, fromY, fromZ, toX, toY, toZ, face) != 0;
}
void FireChunkLoad(int dimId, int chunkX, int chunkZ, bool isNewChunk)
{
if (!s_initialized || !s_managedFireChunkLoad)
return;
if (!HasHandlers(kHandlerKind_ChunkLoad))
return;
s_managedFireChunkLoad(dimId, chunkX, chunkZ, isNewChunk ? 1 : 0);
}
bool FireChunkUnload(int dimId, int chunkX, int chunkZ)
{
if (!s_initialized || !s_managedFireChunkUnload)
return false;
if (!HasHandlers(kHandlerKind_ChunkUnload))
return false;
return s_managedFireChunkUnload(dimId, chunkX, chunkZ) != 0;
}
} // namespace FourKitBridge
+4
View File
@@ -105,6 +105,8 @@ namespace FourKitBridge
bool FirePistonRetract(int dimId, int x, int y, int z, int direction);
bool FireCommandPreprocess(int entityId, const std::wstring &commandLine, std::wstring &outCommand);
bool FireBlockFromTo(int dimId, int fromX, int fromY, int fromZ, int toX, int toY, int toZ, int face);
void FireChunkLoad(int dimId, int chunkX, int chunkZ, bool isNewChunk);
bool FireChunkUnload(int dimId, int chunkX, int chunkZ);
#else
// Standalone build: every hook is an inline no-op. Cancellable hooks
// return false so vanilla code paths run unmodified, AND every out
@@ -211,5 +213,7 @@ namespace FourKitBridge
inline bool FirePistonRetract(int, int, int, int, int) { return false; }
inline bool FireCommandPreprocess(int, const std::wstring &commandLine, std::wstring &outCommand) { outCommand = commandLine; return false; }
inline bool FireBlockFromTo(int, int, int, int, int, int, int, int) { return false; }
inline void FireChunkLoad(int, int, int, bool) {}
inline bool FireChunkUnload(int, int, int) { return false; }
#endif
}
+377 -11
View File
@@ -3,6 +3,7 @@
#include "Common/StringUtils.h"
#include "stdafx.h"
#include <atomic>
#include <string>
#include <vector>
@@ -13,6 +14,10 @@
#include "../Minecraft.Client/ServerLevel.h"
#include "../Minecraft.Client/ServerPlayer.h"
#include "../Minecraft.Client/ServerPlayerGameMode.h"
#include "../Minecraft.Client/ServerChunkCache.h"
#include "../Minecraft.World/LevelChunk.h"
#include "../Minecraft.World/Biome.h"
#include "../Minecraft.World/LightLayer.h"
#include "../Minecraft.Client/Windows64/Network/WinsockNetLayer.h"
#include "../Minecraft.World/AbstractContainerMenu.h"
#include "../Minecraft.World/AddGlobalEntityPacket.h"
@@ -29,6 +34,7 @@
#include "../Minecraft.World/Player.h"
#include "../Minecraft.World/PlayerAbilitiesPacket.h"
#include "../Minecraft.World/SetCarriedItemPacket.h"
#include "../Minecraft.World/BlockRegionUpdatePacket.h"
#include "../Minecraft.World/SetExperiencePacket.h"
#include "../Minecraft.World/SetHealthPacket.h"
#include "../Minecraft.World/LevelSoundPacket.h"
@@ -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
+42 -2
View File
@@ -1,8 +1,21 @@
#pragma once
#include <cstdint>
namespace FourKitBridge
{
// Must match HandlerKind in FourKit.cs.
enum HandlerKind : int {
kHandlerKind_ChunkLoad = 0,
kHandlerKind_ChunkUnload = 1,
kHandlerKind_PlayerMove = 2,
};
void __cdecl NativeSetHandlerMask(uint32_t mask);
bool HasHandlers(int kind);
int __cdecl NativeGetServerTickCount();
// core
void __cdecl NativeDamagePlayer(int entityId, float amount);
void __cdecl NativeSetPlayerHealth(int entityId, float health);
@@ -18,8 +31,8 @@ namespace FourKitBridge
// World
int __cdecl NativeGetTileId(int dimId, int x, int y, int z);
int __cdecl NativeGetTileData(int dimId, int x, int y, int z);
void __cdecl NativeSetTile(int dimId, int x, int y, int z, int tileId, int data);
void __cdecl NativeSetTileData(int dimId, int x, int y, int z, int data);
void __cdecl NativeSetTile(int dimId, int x, int y, int z, int tileId, int data, int flags);
void __cdecl NativeSetTileData(int dimId, int x, int y, int z, int data, int flags);
int __cdecl NativeBreakBlock(int dimId, int x, int y, int z);
int __cdecl NativeGetHighestBlockY(int dimId, int x, int z);
void __cdecl NativeGetWorldInfo(int dimId, double *outBuf);
@@ -52,6 +65,12 @@ namespace FourKitBridge
void __cdecl NativeSetItemMeta(int entityId, int slot, const char *inBuf, int bufSize);
void __cdecl NativeSetHeldItemSlot(int entityId, int slot);
// carried item (cursor) & ender chest
void __cdecl NativeGetCarriedItem(int entityId, int *outData);
void __cdecl NativeSetCarriedItem(int entityId, int itemId, int count, int aux);
void __cdecl NativeGetEnderChestContents(int entityId, int *outData);
void __cdecl NativeSetEnderChestSlot(int entityId, int slot, int itemId, int count, int aux);
// ent
void __cdecl NativeSetSneaking(int entityId, int sneak);
void __cdecl NativeSetVelocity(int entityId, double x, double y, double z);
@@ -78,4 +97,25 @@ namespace FourKitBridge
int __cdecl NativeGetVehicleId(int entityId);
int __cdecl NativeGetPassengerId(int entityId);
void __cdecl NativeGetEntityInfo(int entityId, double *outData);
// chunk
int __cdecl NativeIsChunkLoaded(int dimId, int chunkX, int chunkZ);
int __cdecl NativeLoadChunk(int dimId, int chunkX, int chunkZ, int generate);
int __cdecl NativeUnloadChunk(int dimId, int chunkX, int chunkZ, int save, int safe);
int __cdecl NativeGetLoadedChunks(int dimId, int **coordBuf);
int __cdecl NativeIsChunkInUse(int dimId, int chunkX, int chunkZ);
void __cdecl NativeGetChunkSnapshot(int dimId, int chunkX, int chunkZ, int *blockIds, int *blockData, int *maxBlockY);
int __cdecl NativeUnloadChunkRequest(int dimId, int chunkX, int chunkZ, int safe);
int __cdecl NativeRegenerateChunk(int dimId, int chunkX, int chunkZ);
int __cdecl NativeRefreshChunk(int dimId, int chunkX, int chunkZ);
// world entity bs
int __cdecl NativeGetWorldEntities(int dimId, int **outBuf);
int __cdecl NativeGetChunkEntities(int dimId, int chunkX, int chunkZ, int **outBuf);
// block info (light, biome)
int __cdecl NativeGetSkyLight(int dimId, int x, int y, int z);
int __cdecl NativeGetBlockLight(int dimId, int x, int y, int z);
int __cdecl NativeGetBiomeId(int dimId, int x, int z);
void __cdecl NativeSetBiomeId(int dimId, int x, int z, int biomeId);
}
+1 -1
View File
@@ -4,7 +4,7 @@
#include <vector>
#include <stdarg.h>
#include "..\Minecraft.World\DisconnectPacket.h"
#include "../Minecraft.World/DisconnectPacket.h"
namespace ServerRuntime
{
+8
View File
@@ -888,6 +888,14 @@ ServerPropertiesConfig LoadServerPropertiesConfig()
config.hardcore = ReadNormalizedBoolProperty(&merged, "hardcore", false, &shouldWrite);
config.hardcoreBanIp = ReadNormalizedBoolProperty(&merged, "hardcore-ban-ip", false, &shouldWrite);
config.maxMonsters = ReadNormalizedIntProperty(&merged, "max-monsters", 50, 0, 1000, &shouldWrite);
config.maxAnimals = ReadNormalizedIntProperty(&merged, "max-animals", 50, 0, 1000, &shouldWrite);
config.maxAmbient = ReadNormalizedIntProperty(&merged, "max-ambient", 20, 0, 1000, &shouldWrite);
config.maxWaterAnimals = ReadNormalizedIntProperty(&merged, "max-water-animals", 5, 0, 1000, &shouldWrite);
config.maxWolves = ReadNormalizedIntProperty(&merged, "max-wolves", 8, 0, 1000, &shouldWrite);
config.maxChickens = ReadNormalizedIntProperty(&merged, "max-chickens", 8, 0, 1000, &shouldWrite);
config.maxMushroomCows = ReadNormalizedIntProperty(&merged, "max-mushroom-cows", 2, 0, 1000, &shouldWrite);
config.maxBuildHeight = ReadNormalizedIntProperty(&merged, "max-build-height", 256, 64, 256, &shouldWrite);
config.motd = ReadNormalizedStringProperty(&merged, "motd", "A Minecraft Server", 255, &shouldWrite);
+15
View File
@@ -80,6 +80,21 @@ namespace ServerRuntime
/** `hardcore-ban-ip` — whether hardcore death bans include IP bans */
bool hardcoreBanIp;
/** `max-monsters` natural spawn cap for monsters (zombies, skeletons, creepers, etc.) */
int maxMonsters;
/** `max-animals` natural spawn cap for animals (cows, sheep, pigs) */
int maxAnimals;
/** `max-ambient` natural spawn cap for ambient mobs (bats) */
int maxAmbient;
/** `max-water-animals` natural spawn cap for water mobs (squid) */
int maxWaterAnimals;
/** `max-wolves` natural spawn cap for wolves */
int maxWolves;
/** `max-chickens` natural spawn cap for chickens */
int maxChickens;
/** `max-mushroom-cows` natural spawn cap for mooshrooms */
int maxMushroomCows;
/** security settings */
/** `hide-player-list-prelogin` — strip XUIDs from PreLoginPacket response */
bool hidePlayerListPreLogin;
+34 -11
View File
@@ -37,6 +37,7 @@
#include "../../Minecraft.World/ConsoleSaveFileOriginal.h"
#include "../../Minecraft.World/net.minecraft.world.level.tile.h"
#include "../../Minecraft.World/Random.h"
#include "../../Minecraft.World/MobCategory.h"
#include <stdio.h>
#include <stdlib.h>
@@ -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"
+1
View File
@@ -98,6 +98,7 @@ public:
e_ChatCommandTeleportMe,
e_ChatCommandTeleportToMe,
e_ChatActionBar,
};
public:
+10 -1
View File
@@ -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;
+3
View File
@@ -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
View File
@@ -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;
+6 -3
View File
@@ -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;
+3
View File
@@ -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
View File
@@ -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)
{
+39 -8
View File
@@ -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);
+1
View File
@@ -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();
+18
View File
@@ -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 -13
View File
@@ -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();
+1 -1
View File
@@ -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();
+5 -5
View File
@@ -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();
+4
View File
@@ -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)
+2 -1
View File
@@ -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;
+4
View File
@@ -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