Merge remote-tracking branch 'itsRevela/main'
# Conflicts: # .github/workflows/nightly.yml # .gitignore # Minecraft.Client/ChatScreen.cpp # Minecraft.Client/ClientConnection.cpp # Minecraft.Client/Common/Audio/SoundEngine.cpp # Minecraft.Client/Common/Audio/SoundEngine.h # Minecraft.Client/Common/Media/MediaWindows64.arc # Minecraft.Client/Common/UI/IUIScene_HUD.cpp # Minecraft.Client/Common/UI/UIControl_Base.cpp # Minecraft.Client/Common/UI/UIScene_DeathMenu.cpp # Minecraft.Client/Common/UI/UIScene_JoinMenu.cpp # Minecraft.Client/Common/XUI/XUI_Chat.cpp # Minecraft.Client/Common/XUI/XUI_Death.cpp # Minecraft.Client/Font.cpp # Minecraft.Client/Gui.cpp # Minecraft.Client/PendingConnection.cpp # Minecraft.Client/PlayerConnection.cpp # Minecraft.Client/PlayerConnection.h # Minecraft.Client/PlayerList.cpp # Minecraft.Client/Windows64/Network/WinsockNetLayer.cpp # Minecraft.Client/Windows64/Network/WinsockNetLayer.h # Minecraft.Client/Windows64Media/strings.h # Minecraft.Client/cmake/sources/Common.cmake # Minecraft.Server/Console/ServerCliEngine.cpp # Minecraft.Server/Console/commands/whitelist/CliCommandWhitelist.cpp # Minecraft.Server/Windows64/ServerMain.cpp # Minecraft.World/WitherBoss.h # Minecraft.World/cmake/sources/Common.cmake # README.md
3
.gitattributes
vendored
|
|
@ -0,0 +1,3 @@
|
|||
.github/workflows/docker-nightly.yml merge=ours
|
||||
.github/workflows/nightly.yml merge=ours
|
||||
docker-compose.dedicated-server.ghcr.yml merge=ours
|
||||
BIN
.github/LCRE-banner.png
vendored
Normal file
|
After Width: | Height: | Size: 496 KiB |
BIN
.github/hardcore-hearts.png
vendored
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
.github/hardcore-preview.png
vendored
Normal file
|
After Width: | Height: | Size: 778 KiB |
494
.github/workflows/nightly.yml
vendored
|
|
@ -18,144 +18,144 @@ jobs:
|
|||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup MSVC
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
- name: Setup MSVC
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
|
||||
- name: Setup CMake
|
||||
uses: lukka/get-cmake@latest
|
||||
- name: Setup CMake
|
||||
uses: lukka/get-cmake@latest
|
||||
|
||||
- name: Run CMake
|
||||
uses: lukka/run-cmake@v10
|
||||
env:
|
||||
VCPKG_ROOT: ""
|
||||
with:
|
||||
configurePreset: windows64
|
||||
buildPreset: windows64-release
|
||||
buildPresetAdditionalArgs: "['--target', 'Minecraft.Client']"
|
||||
- name: Run CMake
|
||||
uses: lukka/run-cmake@v10
|
||||
env:
|
||||
VCPKG_ROOT: ""
|
||||
with:
|
||||
configurePreset: windows64
|
||||
buildPreset: windows64-release
|
||||
buildPresetAdditionalArgs: "['--target', 'Minecraft.Client']"
|
||||
|
||||
- name: Zip Build
|
||||
shell: pwsh
|
||||
run: |
|
||||
$source = "./build/windows64/Minecraft.Client/Release"
|
||||
$zip = "LCREWindows64.zip"
|
||||
$topLevel = "LCREWindows64"
|
||||
|
||||
# Collect files, excluding unwanted extensions
|
||||
$files = Get-ChildItem -Path $source -Recurse -File |
|
||||
Where-Object { $_.Extension -notin '.pch', '.zip', '.ipdb', '.iobj' }
|
||||
|
||||
Add-Type -AssemblyName System.IO.Compression
|
||||
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
||||
|
||||
$basePath = (Resolve-Path $source).Path
|
||||
$fs = [System.IO.File]::Open($zip, [System.IO.FileMode]::Create)
|
||||
- name: Zip Build
|
||||
shell: pwsh
|
||||
run: |
|
||||
$source = "./build/windows64/Minecraft.Client/Release"
|
||||
$zip = "LCREWindows64.zip"
|
||||
$topLevel = "LCREWindows64"
|
||||
|
||||
# Collect files, excluding unwanted extensions
|
||||
$files = Get-ChildItem -Path $source -Recurse -File |
|
||||
Where-Object { $_.Extension -notin '.pch', '.zip', '.ipdb', '.iobj' }
|
||||
|
||||
Add-Type -AssemblyName System.IO.Compression
|
||||
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
||||
|
||||
$basePath = (Resolve-Path $source).Path
|
||||
$fs = [System.IO.File]::Open($zip, [System.IO.FileMode]::Create)
|
||||
try {
|
||||
$archive = New-Object System.IO.Compression.ZipArchive($fs, [System.IO.Compression.ZipArchiveMode]::Create)
|
||||
try {
|
||||
$archive = New-Object System.IO.Compression.ZipArchive($fs, [System.IO.Compression.ZipArchiveMode]::Create)
|
||||
try {
|
||||
# Add directories
|
||||
Get-ChildItem -Path $basePath -Recurse -Directory | ForEach-Object {
|
||||
$rel = $_.FullName.Substring($basePath.Length).TrimStart('\', '/')
|
||||
$archive.CreateEntry("$topLevel/$($rel -replace '\\','/')/") | Out-Null
|
||||
}
|
||||
# Add files
|
||||
foreach ($file in $files) {
|
||||
$rel = $file.FullName.Substring($basePath.Length).TrimStart('\', '/')
|
||||
$entryName = "$topLevel/$($rel -replace '\\','/')"
|
||||
[System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile(
|
||||
$archive, $file.FullName, $entryName,
|
||||
[System.IO.Compression.CompressionLevel]::Optimal
|
||||
) | Out-Null
|
||||
}
|
||||
} finally { $archive.Dispose() }
|
||||
} finally { $fs.Dispose() }
|
||||
|
||||
Write-Host "Created $zip"
|
||||
# Add directories
|
||||
Get-ChildItem -Path $basePath -Recurse -Directory | ForEach-Object {
|
||||
$rel = $_.FullName.Substring($basePath.Length).TrimStart('\', '/')
|
||||
$archive.CreateEntry("$topLevel/$($rel -replace '\\','/')/") | Out-Null
|
||||
}
|
||||
# Add files
|
||||
foreach ($file in $files) {
|
||||
$rel = $file.FullName.Substring($basePath.Length).TrimStart('\', '/')
|
||||
$entryName = "$topLevel/$($rel -replace '\\','/')"
|
||||
[System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile(
|
||||
$archive, $file.FullName, $entryName,
|
||||
[System.IO.Compression.CompressionLevel]::Optimal
|
||||
) | Out-Null
|
||||
}
|
||||
} finally { $archive.Dispose() }
|
||||
} finally { $fs.Dispose() }
|
||||
|
||||
- name: Stage artifacts
|
||||
shell: pwsh
|
||||
run: |
|
||||
New-Item -ItemType Directory -Force -Path staging
|
||||
Copy-Item LCREWindows64.zip staging/
|
||||
Copy-Item ./build/windows64/Minecraft.Client/Release/Minecraft.Client.exe staging/
|
||||
Write-Host "Created $zip"
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: client-build
|
||||
path: staging/*
|
||||
- name: Stage artifacts
|
||||
shell: pwsh
|
||||
run: |
|
||||
New-Item -ItemType Directory -Force -Path staging
|
||||
Copy-Item LCREWindows64.zip staging/
|
||||
Copy-Item ./build/windows64/Minecraft.Client/Release/Minecraft.Client.exe staging/
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: client-build
|
||||
path: staging/*
|
||||
|
||||
build-server:
|
||||
name: Build Server
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup MSVC
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
- name: Setup MSVC
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
|
||||
- name: Setup CMake
|
||||
uses: lukka/get-cmake@latest
|
||||
- name: Setup CMake
|
||||
uses: lukka/get-cmake@latest
|
||||
|
||||
- name: Run CMake
|
||||
uses: lukka/run-cmake@v10
|
||||
env:
|
||||
VCPKG_ROOT: ""
|
||||
with:
|
||||
configurePreset: windows64
|
||||
buildPreset: windows64-release
|
||||
buildPresetAdditionalArgs: "['--target', 'Minecraft.Server']"
|
||||
- name: Run CMake
|
||||
uses: lukka/run-cmake@v10
|
||||
env:
|
||||
VCPKG_ROOT: ""
|
||||
with:
|
||||
configurePreset: windows64
|
||||
buildPreset: windows64-release
|
||||
buildPresetAdditionalArgs: "['--target', 'Minecraft.Server']"
|
||||
|
||||
- name: Zip Build
|
||||
shell: pwsh
|
||||
run: |
|
||||
$source = "./build/windows64/Minecraft.Server/Release"
|
||||
$zip = "LCREServerWindows64.zip"
|
||||
$topLevel = "LCREServerWindows64"
|
||||
|
||||
$files = Get-ChildItem -Path $source -Recurse -File |
|
||||
Where-Object { $_.Extension -notin '.pch', '.zip', '.ipdb', '.iobj' }
|
||||
|
||||
Add-Type -AssemblyName System.IO.Compression
|
||||
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
||||
|
||||
$basePath = (Resolve-Path $source).Path
|
||||
$fs = [System.IO.File]::Open($zip, [System.IO.FileMode]::Create)
|
||||
- name: Zip Build
|
||||
shell: pwsh
|
||||
run: |
|
||||
$source = "./build/windows64/Minecraft.Server/Release"
|
||||
$zip = "LCREServerWindows64.zip"
|
||||
$topLevel = "LCREServerWindows64"
|
||||
|
||||
$files = Get-ChildItem -Path $source -Recurse -File |
|
||||
Where-Object { $_.Extension -notin '.pch', '.zip', '.ipdb', '.iobj' }
|
||||
|
||||
Add-Type -AssemblyName System.IO.Compression
|
||||
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
||||
|
||||
$basePath = (Resolve-Path $source).Path
|
||||
$fs = [System.IO.File]::Open($zip, [System.IO.FileMode]::Create)
|
||||
try {
|
||||
$archive = New-Object System.IO.Compression.ZipArchive($fs, [System.IO.Compression.ZipArchiveMode]::Create)
|
||||
try {
|
||||
$archive = New-Object System.IO.Compression.ZipArchive($fs, [System.IO.Compression.ZipArchiveMode]::Create)
|
||||
try {
|
||||
Get-ChildItem -Path $basePath -Recurse -Directory | ForEach-Object {
|
||||
$rel = $_.FullName.Substring($basePath.Length).TrimStart('\', '/')
|
||||
$archive.CreateEntry("$topLevel/$($rel -replace '\\','/')/") | Out-Null
|
||||
}
|
||||
foreach ($file in $files) {
|
||||
$rel = $file.FullName.Substring($basePath.Length).TrimStart('\', '/')
|
||||
$entryName = "$topLevel/$($rel -replace '\\','/')"
|
||||
[System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile(
|
||||
$archive, $file.FullName, $entryName,
|
||||
[System.IO.Compression.CompressionLevel]::Optimal
|
||||
) | Out-Null
|
||||
}
|
||||
} finally { $archive.Dispose() }
|
||||
} finally { $fs.Dispose() }
|
||||
|
||||
Write-Host "Created $zip"
|
||||
Get-ChildItem -Path $basePath -Recurse -Directory | ForEach-Object {
|
||||
$rel = $_.FullName.Substring($basePath.Length).TrimStart('\', '/')
|
||||
$archive.CreateEntry("$topLevel/$($rel -replace '\\','/')/") | Out-Null
|
||||
}
|
||||
foreach ($file in $files) {
|
||||
$rel = $file.FullName.Substring($basePath.Length).TrimStart('\', '/')
|
||||
$entryName = "$topLevel/$($rel -replace '\\','/')"
|
||||
[System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile(
|
||||
$archive, $file.FullName, $entryName,
|
||||
[System.IO.Compression.CompressionLevel]::Optimal
|
||||
) | Out-Null
|
||||
}
|
||||
} finally { $archive.Dispose() }
|
||||
} finally { $fs.Dispose() }
|
||||
|
||||
- name: Stage artifacts
|
||||
shell: pwsh
|
||||
run: |
|
||||
New-Item -ItemType Directory -Force -Path staging
|
||||
Copy-Item LCREServerWindows64.zip staging/
|
||||
Write-Host "Created $zip"
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: server-build
|
||||
path: staging/*
|
||||
- name: Stage artifacts
|
||||
shell: pwsh
|
||||
run: |
|
||||
New-Item -ItemType Directory -Force -Path staging
|
||||
Copy-Item LCREServerWindows64.zip staging/
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: server-build
|
||||
path: staging/*
|
||||
|
||||
release-server:
|
||||
name: Release Server
|
||||
|
|
@ -163,58 +163,58 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Download server artifacts
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: server-build
|
||||
path: artifacts
|
||||
- name: Download server artifacts
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: server-build
|
||||
path: artifacts
|
||||
|
||||
- name: Attest artifacts
|
||||
uses: actions/attest-build-provenance@v2
|
||||
with:
|
||||
subject-path: |
|
||||
artifacts/LCREServerWindows64.zip
|
||||
- name: Attest artifacts
|
||||
uses: actions/attest-build-provenance@v2
|
||||
with:
|
||||
subject-path: |
|
||||
artifacts/LCREServerWindows64.zip
|
||||
|
||||
- name: Get short SHA
|
||||
id: sha
|
||||
run: echo "short=$(echo '${{ github.sha }}' | cut -c1-7)" >> "$GITHUB_OUTPUT"
|
||||
- name: Get short SHA
|
||||
id: sha
|
||||
run: echo "short=$(echo '${{ github.sha }}' | cut -c1-7)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Delete old release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: gh release delete Nightly-Dedicated-Server --yes || true
|
||||
- name: Delete old release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: gh release delete Nightly-Dedicated-Server --yes || true
|
||||
|
||||
- name: Delete old tag
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: gh api repos/${{ github.repository }}/git/refs/tags/Nightly-Dedicated-Server --method DELETE || true
|
||||
- name: Delete old tag
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: gh api repos/${{ github.repository }}/git/refs/tags/Nightly-Dedicated-Server --method DELETE || true
|
||||
|
||||
- name: Import GPG key
|
||||
uses: crazy-max/ghaction-import-gpg@v6
|
||||
with:
|
||||
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
passphrase: ${{ secrets.GPG_PASSPHRASE }}
|
||||
git_user_signingkey: true
|
||||
git_tag_gpgsign: true
|
||||
- name: Import GPG key
|
||||
uses: crazy-max/ghaction-import-gpg@v6
|
||||
with:
|
||||
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
passphrase: ${{ secrets.GPG_PASSPHRASE }}
|
||||
git_user_signingkey: true
|
||||
git_tag_gpgsign: true
|
||||
|
||||
- name: Create signed tag
|
||||
run: |
|
||||
git tag -s -f Nightly-Dedicated-Server -m "Nightly server release ${{ steps.sha.outputs.short }}"
|
||||
git push origin Nightly-Dedicated-Server --force
|
||||
- name: Create signed tag
|
||||
run: |
|
||||
git tag -s -f Nightly-Dedicated-Server -m "Nightly server release ${{ steps.sha.outputs.short }}"
|
||||
git push origin Nightly-Dedicated-Server --force
|
||||
|
||||
- name: Create release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
gh release create Nightly-Dedicated-Server artifacts/* \
|
||||
--title "Server: ${{ steps.sha.outputs.short }}" \
|
||||
--notes "Dedicated Server runtime for Windows64.
|
||||
|
||||
Download \`LCREServerWindows64.zip\` and extract it to a folder where you'd like to keep the server runtime." \
|
||||
--latest=false
|
||||
- name: Create release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
gh release create Nightly-Dedicated-Server artifacts/* \
|
||||
--title "Server: ${{ steps.sha.outputs.short }}" \
|
||||
--notes "Dedicated Server runtime for Windows64.
|
||||
|
||||
Download \`LCREServerWindows64.zip\` and extract it to a folder where you'd like to keep the server runtime." \
|
||||
--latest=false
|
||||
|
||||
release-client:
|
||||
name: Release Client
|
||||
|
|
@ -222,103 +222,103 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Download client artifacts
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: client-build
|
||||
path: artifacts
|
||||
- name: Download client artifacts
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: client-build
|
||||
path: artifacts
|
||||
|
||||
- name: Attest artifacts
|
||||
uses: actions/attest-build-provenance@v2
|
||||
with:
|
||||
subject-path: |
|
||||
artifacts/LCREWindows64.zip
|
||||
artifacts/Minecraft.Client.exe
|
||||
- name: Attest artifacts
|
||||
uses: actions/attest-build-provenance@v2
|
||||
with:
|
||||
subject-path: |
|
||||
artifacts/LCREWindows64.zip
|
||||
artifacts/Minecraft.Client.exe
|
||||
|
||||
- name: Get short SHA
|
||||
id: sha
|
||||
run: echo "short=$(echo '${{ github.sha }}' | cut -c1-7)" >> "$GITHUB_OUTPUT"
|
||||
- name: Get short SHA
|
||||
id: sha
|
||||
run: echo "short=$(echo '${{ github.sha }}' | cut -c1-7)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Delete old release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: gh release delete Nightly --yes || true
|
||||
- name: Delete old release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: gh release delete Nightly --yes || true
|
||||
|
||||
- name: Delete old tag
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: gh api repos/${{ github.repository }}/git/refs/tags/Nightly --method DELETE || true
|
||||
- name: Delete old tag
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: gh api repos/${{ github.repository }}/git/refs/tags/Nightly --method DELETE || true
|
||||
|
||||
- name: Import GPG key
|
||||
uses: crazy-max/ghaction-import-gpg@v6
|
||||
with:
|
||||
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
passphrase: ${{ secrets.GPG_PASSPHRASE }}
|
||||
git_user_signingkey: true
|
||||
git_tag_gpgsign: true
|
||||
- name: Import GPG key
|
||||
uses: crazy-max/ghaction-import-gpg@v6
|
||||
with:
|
||||
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
passphrase: ${{ secrets.GPG_PASSPHRASE }}
|
||||
git_user_signingkey: true
|
||||
git_tag_gpgsign: true
|
||||
|
||||
- name: Create signed tag
|
||||
run: |
|
||||
git tag -s -f Nightly -m "Nightly release ${{ steps.sha.outputs.short }}"
|
||||
git push origin Nightly --force
|
||||
- name: Create signed tag
|
||||
run: |
|
||||
git tag -s -f Nightly -m "Nightly release ${{ steps.sha.outputs.short }}"
|
||||
git push origin Nightly --force
|
||||
|
||||
- name: Write release notes
|
||||
run: |
|
||||
cat > notes.md <<'NOTES'
|
||||
# Instructions:
|
||||
**Newcomers:**
|
||||
- If this is your first time, download `LCREWindows64.zip` and extract it wherever you would like to keep it.
|
||||
- I would recommend to set your username prior to launch (create a file called `username.txt`, put your desired username into the file, and save).
|
||||
- To play, simply run `Minecraft.Client.exe`.
|
||||
|
||||
**For those that wish to update their existing installation with the latest build:**
|
||||
- Download `Minecraft.Client.exe` and `Minecraft.Client.pdb` and copy them over to your existing LCREWindows64 build (overwrite your old version of Minecraft.Client.exe and Minecraft.Client.pdb).
|
||||
|
||||
**Steam Deck & Linux:**
|
||||
- Y'all know the drill. Download the `LCREWindows64.zip`, extract it, add the `Minecraft.Client.exe` as a "Non-Steam Game" within the Steam library, turn on compatibility mode with Proton Experimental, and then run it!
|
||||
|
||||
# Multiplayer instructions:
|
||||
LAN games are natively supported, and any LAN games will appear automatically on the right. However, if you'd like to play with your friends online (and if you don't want to require them to setup a vpn, and/or if you don't want to port forward), I would recommend the following setup. Please keep in mind, you do NOT need to do this to enjoy the game. This is just how I have it setup for me so my friends can join without any hassle:
|
||||
|
||||
Prerequisites:
|
||||
- Premium playit.gg account, costs about $3 USD per month. This is for setting up the tunnel.
|
||||
- playit.gg agent installed on host PC.
|
||||
|
||||
How-to:
|
||||
- Ensure your playit.gg agent is connected to your playit.gg account
|
||||
- On the playit.gg website, setup a new tunnel (choose TCP). Ensure the configurable settings are set to the below values, assuming your agent is installed on the same computer as your online LCREMinecraft game is hosted from.
|
||||
- Configurable settings:
|
||||
- Local IP: `127.0.0.1`
|
||||
- Local Port: `25565`
|
||||
- Proxy Protocol: `None`
|
||||
- After creating your tunnel, navigate to the "Tunnels" main page. You'll see the IP address and port for your tunnel. This is what your friends will input when adding your server in order to join your online game!
|
||||
|
||||
|
||||
# Why this fork exists:
|
||||
Changes/additions that stray from the upstream repo (`smartcmd/MinecraftConsoles`:
|
||||
- See: https://github.com/itsRevela/MinecraftConsoles?tab=readme-ov-file#latest
|
||||
- I can tweak this fork while staying compatible with the upstream repo without needing to wait on my pull requests to get accepted upstream (while keeping this fork updated with the latest and greatest from upstream)
|
||||
NOTES
|
||||
- name: Write release notes
|
||||
run: |
|
||||
cat > notes.md <<'NOTES'
|
||||
# Instructions:
|
||||
**Newcomers:**
|
||||
- If this is your first time, download `LCREWindows64.zip` and extract it wherever you would like to keep it.
|
||||
- I would recommend to set your username prior to launch (create a file called `username.txt`, put your desired username into the file, and save).
|
||||
- To play, simply run `Minecraft.Client.exe`.
|
||||
|
||||
- name: Create release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
gh release create Nightly artifacts/* \
|
||||
--title "Client: ${{ steps.sha.outputs.short }}" \
|
||||
--notes-file notes.md
|
||||
**For those that wish to update their existing installation with the latest build:**
|
||||
- Download `Minecraft.Client.exe` and `Minecraft.Client.pdb` and copy them over to your existing LCREWindows64 build (overwrite your old version of Minecraft.Client.exe and Minecraft.Client.pdb).
|
||||
|
||||
**Steam Deck & Linux:**
|
||||
- Y'all know the drill. Download the `LCREWindows64.zip`, extract it, add the `Minecraft.Client.exe` as a "Non-Steam Game" within the Steam library, turn on compatibility mode with Proton Experimental, and then run it!
|
||||
|
||||
# Multiplayer instructions:
|
||||
LAN games are natively supported, and any LAN games will appear automatically on the right. However, if you'd like to play with your friends online (and if you don't want to require them to setup a vpn, and/or if you don't want to port forward), I would recommend the following setup. Please keep in mind, you do NOT need to do this to enjoy the game. This is just how I have it setup for me so my friends can join without any hassle:
|
||||
|
||||
Prerequisites:
|
||||
- Premium playit.gg account, costs about $3 USD per month. This is for setting up the tunnel.
|
||||
- playit.gg agent installed on host PC.
|
||||
|
||||
How-to:
|
||||
- Ensure your playit.gg agent is connected to your playit.gg account
|
||||
- On the playit.gg website, setup a new tunnel (choose TCP). Ensure the configurable settings are set to the below values, assuming your agent is installed on the same computer as your online LCREMinecraft game is hosted from.
|
||||
- Configurable settings:
|
||||
- Local IP: `127.0.0.1`
|
||||
- Local Port: `25565`
|
||||
- Proxy Protocol: `None`
|
||||
- After creating your tunnel, navigate to the "Tunnels" main page. You'll see the IP address and port for your tunnel. This is what your friends will input when adding your server in order to join your online game!
|
||||
|
||||
|
||||
# Why this fork exists:
|
||||
Changes/additions that stray from the upstream repo (`smartcmd/MinecraftConsoles`:
|
||||
- See: https://github.com/itsRevela/MinecraftConsoles?tab=readme-ov-file#latest
|
||||
- I can tweak this fork while staying compatible with the upstream repo without needing to wait on my pull requests to get accepted upstream (while keeping this fork updated with the latest and greatest from upstream)
|
||||
NOTES
|
||||
|
||||
- name: Create release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
gh release create Nightly artifacts/* \
|
||||
--title "Client: ${{ steps.sha.outputs.short }}" \
|
||||
--notes-file notes.md
|
||||
|
||||
cleanup:
|
||||
needs: [release-client, release-server]
|
||||
if: always()
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Cleanup artifacts
|
||||
uses: geekyeggo/delete-artifact@v5
|
||||
with:
|
||||
name: |
|
||||
client-build
|
||||
server-build
|
||||
- name: Cleanup artifacts
|
||||
uses: geekyeggo/delete-artifact@v5
|
||||
with:
|
||||
name: |
|
||||
client-build
|
||||
server-build
|
||||
|
|
|
|||
8
.gitignore
vendored
|
|
@ -26,6 +26,7 @@ mono_crash.*
|
|||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x64_*/
|
||||
x86/
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
|
|
@ -421,4 +422,9 @@ result-*
|
|||
.direnv/
|
||||
.xwin-cache/
|
||||
|
||||
.xwin
|
||||
.xwin
|
||||
# Tools build artifacts and intermediates
|
||||
tools/*.class
|
||||
tools/*.swf
|
||||
tools/staging/
|
||||
tools/server-monitor/
|
||||
|
|
|
|||
|
|
@ -68,9 +68,14 @@ set(MINECRAFT_SHARED_DEFINES
|
|||
$<$<CONFIG:Debug>:_DEBUG>
|
||||
_CRT_NON_CONFORMING_SWPRINTFS
|
||||
_CRT_SECURE_NO_WARNINGS
|
||||
_HAS_STD_BYTE=0
|
||||
)
|
||||
|
||||
# Add platform-specific defines
|
||||
if(PLATFORM_NAME STREQUAL "Windows64")
|
||||
list(APPEND MINECRAFT_SHARED_DEFINES _WINDOWS64)
|
||||
set(IGGY_LIBS iggy_w64.lib)
|
||||
endif()
|
||||
list(APPEND MINECRAFT_SHARED_DEFINES ${PLATFORM_DEFINES})
|
||||
|
||||
# ---
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ set_target_properties(Minecraft.Client PROPERTIES
|
|||
target_link_libraries(Minecraft.Client PRIVATE
|
||||
Minecraft.World
|
||||
d3d11
|
||||
dxgi
|
||||
d3dcompiler
|
||||
XInput9_1_0
|
||||
wsock32
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
#include "../Minecraft.World/SharedConstants.h"
|
||||
#include "../Minecraft.World/StringHelpers.h"
|
||||
#include "../Minecraft.World/ChatPacket.h"
|
||||
#include "../Minecraft.World/ArabicShaping.h"
|
||||
|
||||
const wstring ChatScreen::allowedChars = SharedConstants::acceptableLetters;
|
||||
vector<wstring> ChatScreen::s_chatHistory;
|
||||
|
|
@ -14,7 +15,12 @@ wstring ChatScreen::s_historyDraft;
|
|||
|
||||
bool ChatScreen::isAllowedChatChar(wchar_t c)
|
||||
{
|
||||
return c >= 0x20 && (c == L'\u00A7' || allowedChars.empty() || allowedChars.find(c) != wstring::npos);
|
||||
if (c < 0x20) return false;
|
||||
// Block Unicode bidirectional override characters that can be used to
|
||||
// spoof chat messages or impersonate players.
|
||||
if (c >= 0x202A && c <= 0x202E) return false; // LRE, RLE, PDF, LRO, RLO
|
||||
if (c >= 0x2066 && c <= 0x2069) return false; // LRI, RLI, FSI, PDI
|
||||
return true;
|
||||
}
|
||||
|
||||
ChatScreen::ChatScreen()
|
||||
|
|
@ -93,6 +99,9 @@ void ChatScreen::keyPressed(wchar_t ch, int eventKey)
|
|||
if (eventKey == Keyboard::KEY_RETURN)
|
||||
{
|
||||
wstring trim = trimString(message);
|
||||
{ char buf[64]; sprintf_s(buf, "[CHAT] Sending (%d chars): ", (int)trim.length()); OutputDebugStringA(buf); }
|
||||
OutputDebugStringW(trim.c_str());
|
||||
OutputDebugStringA("\n");
|
||||
if (trim.length() > 0)
|
||||
{
|
||||
if (!minecraft->handleClientSideCommand(trim))
|
||||
|
|
@ -145,14 +154,21 @@ void ChatScreen::render(int xm, int ym, float a)
|
|||
int x = 4;
|
||||
drawString(font, prefix, x, height - 12, 0xe0e0e0);
|
||||
x += font->width(prefix);
|
||||
wstring beforeCursor = message.substr(0, cursorIndex);
|
||||
wstring afterCursor = message.substr(cursorIndex);
|
||||
drawStringLiteral(font, beforeCursor, x, height - 12, 0xe0e0e0);
|
||||
x += font->widthLiteral(beforeCursor);
|
||||
|
||||
// Shape the full message as one unit so letter connections and word order
|
||||
// are correct. Track where the logical cursor maps in the visual string.
|
||||
int visualCursorPos = 0;
|
||||
wstring shaped = shapeArabicText(message, cursorIndex, &visualCursorPos);
|
||||
|
||||
// Render the full shaped message without re-shaping it
|
||||
drawStringPreshaped(font, shaped, x, height - 12, 0xe0e0e0);
|
||||
|
||||
// Place the cursor at the correct visual position
|
||||
wstring beforeCursorVisual = shaped.substr(0, visualCursorPos);
|
||||
int cursorX = x + font->widthPreshaped(beforeCursorVisual);
|
||||
if (frame / 6 % 2 == 0)
|
||||
drawString(font, L"_", x, height - 12, 0xe0e0e0);
|
||||
x += font->width(L"_");
|
||||
drawStringLiteral(font, afterCursor, x, height - 12, 0xe0e0e0);
|
||||
drawString(font, L"_", cursorX, height - 12, 0xe0e0e0);
|
||||
|
||||
Screen::render(xm, ym, a);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@
|
|||
#ifdef _WINDOWS64
|
||||
#include "Xbox/Network/NetworkPlayerXbox.h"
|
||||
#include "Common/Network/PlatformNetworkManagerStub.h"
|
||||
#include "Windows64\Network\WinsockNetLayer.h"
|
||||
#endif
|
||||
|
||||
|
||||
|
|
@ -65,6 +66,7 @@
|
|||
#include "../Minecraft.World/DurangoStats.h"
|
||||
#include "../Minecraft.World/GenericStats.h"
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
char mapIconToFrame(char iconSlot)
|
||||
|
|
@ -133,6 +135,7 @@ ClientConnection::ClientConnection(Minecraft *minecraft, Socket *socket, int iUs
|
|||
started = false;
|
||||
savedDataStorage = new SavedDataStorage(nullptr);
|
||||
maxPlayers = 20;
|
||||
m_isForkServer = false;
|
||||
|
||||
this->minecraft = minecraft;
|
||||
|
||||
|
|
@ -365,7 +368,7 @@ void ClientConnection::handleLogin(shared_ptr<LoginPacket> packet)
|
|||
Level *dimensionLevel = minecraft->getLevel( packet->dimension );
|
||||
if( dimensionLevel == nullptr )
|
||||
{
|
||||
level = new MultiPlayerLevel(this, new LevelSettings(packet->seed, GameType::byId(packet->gameType), false, false, packet->m_newSeaLevel, packet->m_pLevelType, packet->m_xzSize, packet->m_hellScale), packet->dimension, packet->difficulty);
|
||||
level = new MultiPlayerLevel(this, new LevelSettings(packet->seed, GameType::byId(packet->gameType), false, packet->m_isHardcore, packet->m_newSeaLevel, packet->m_pLevelType, packet->m_xzSize, packet->m_hellScale), packet->dimension, packet->difficulty);
|
||||
|
||||
// 4J Stu - We want to share the SavedDataStorage between levels
|
||||
int otherDimensionId = packet->dimension == 0 ? -1 : 0;
|
||||
|
|
@ -435,7 +438,7 @@ void ClientConnection::handleLogin(shared_ptr<LoginPacket> packet)
|
|||
activeLevel = minecraft->getLevel(otherDimensionId);
|
||||
}
|
||||
|
||||
MultiPlayerLevel *dimensionLevel = new MultiPlayerLevel(this, new LevelSettings(packet->seed, GameType::byId(packet->gameType), false, false, packet->m_newSeaLevel, packet->m_pLevelType, packet->m_xzSize, packet->m_hellScale), packet->dimension, packet->difficulty);
|
||||
MultiPlayerLevel *dimensionLevel = new MultiPlayerLevel(this, new LevelSettings(packet->seed, GameType::byId(packet->gameType), false, packet->m_isHardcore, packet->m_newSeaLevel, packet->m_pLevelType, packet->m_xzSize, packet->m_hellScale), packet->dimension, packet->difficulty);
|
||||
|
||||
dimensionLevel->savedDataStorage = activeLevel->savedDataStorage;
|
||||
|
||||
|
|
@ -1139,7 +1142,11 @@ void ClientConnection::handleMoveEntitySmall(shared_ptr<MoveEntityPacketSmall> p
|
|||
void ClientConnection::handleRemoveEntity(shared_ptr<RemoveEntitiesPacket> packet)
|
||||
{
|
||||
#ifdef _WINDOWS64
|
||||
if (!g_NetworkManager.IsHost())
|
||||
// On fork servers, IQNet cleanup is handled by the MC|ForkPLeave custom
|
||||
// payload so players stay in Tab regardless of render distance. On
|
||||
// upstream servers (no MC|ForkHello received), fall back to the old
|
||||
// behaviour of cleaning up IQNet here.
|
||||
if (!m_isForkServer && !g_NetworkManager.IsHost())
|
||||
{
|
||||
for (int i = 0; i < packet->ids.length; i++)
|
||||
{
|
||||
|
|
@ -1149,7 +1156,6 @@ void ClientConnection::handleRemoveEntity(shared_ptr<RemoveEntitiesPacket> packe
|
|||
shared_ptr<Player> player = dynamic_pointer_cast<Player>(entity);
|
||||
if (player != nullptr)
|
||||
{
|
||||
// Match by gamertag in the IQNet array (XUID may be 0 on dedicated servers)
|
||||
for (int s = 1; s < MINECRAFT_NET_MAX_PLAYERS; ++s)
|
||||
{
|
||||
IQNetPlayer* qp = &IQNet::m_player[s];
|
||||
|
|
@ -2925,7 +2931,7 @@ void ClientConnection::handleRespawn(shared_ptr<RespawnPacket> packet)
|
|||
MultiPlayerLevel *dimensionLevel = (MultiPlayerLevel *)minecraft->getLevel( packet->dimension );
|
||||
if( dimensionLevel == nullptr )
|
||||
{
|
||||
dimensionLevel = new MultiPlayerLevel(this, new LevelSettings(packet->mapSeed, packet->playerGameType, false, minecraft->level->getLevelData()->isHardcore(), packet->m_newSeaLevel, packet->m_pLevelType, packet->m_xzSize, packet->m_hellScale), packet->dimension, packet->difficulty);
|
||||
dimensionLevel = new MultiPlayerLevel(this, new LevelSettings(packet->mapSeed, packet->playerGameType, false, packet->m_isHardcore, packet->m_newSeaLevel, packet->m_pLevelType, packet->m_xzSize, packet->m_hellScale), packet->dimension, packet->difficulty);
|
||||
|
||||
// 4J Stu - We want to shared the savedDataStorage between both levels
|
||||
//if( dimensionLevel->savedDataStorage != nullptr )
|
||||
|
|
@ -3370,7 +3376,9 @@ void ClientConnection::handleTileEditorOpen(shared_ptr<TileEditorOpenPacket> pac
|
|||
|
||||
void ClientConnection::handleSignUpdate(shared_ptr<SignUpdatePacket> packet)
|
||||
{
|
||||
app.DebugPrintf("ClientConnection::handleSignUpdate - ");
|
||||
app.DebugPrintf("[SIGN] handleSignUpdate at (%d, %d, %d):\n", packet->x, packet->y, packet->z);
|
||||
for (int i = 0; i < MAX_SIGN_LINES; i++)
|
||||
app.DebugPrintf("[SIGN] Line%d: \"%ls\"\n", i+1, packet->lines[i].c_str());
|
||||
if (minecraft->level->hasChunkAt(packet->x, packet->y, packet->z))
|
||||
{
|
||||
shared_ptr<TileEntity> te = minecraft->level->getTileEntity(packet->x, packet->y, packet->z);
|
||||
|
|
@ -3384,7 +3392,7 @@ void ClientConnection::handleSignUpdate(shared_ptr<SignUpdatePacket> packet)
|
|||
ste->SetMessage(i,packet->lines[i]);
|
||||
}
|
||||
|
||||
app.DebugPrintf("verified = %d\tCensored = %d\n",packet->m_bVerified,packet->m_bCensored);
|
||||
app.DebugPrintf("[SIGN] verified=%d censored=%d\n", packet->m_bVerified, packet->m_bCensored);
|
||||
ste->SetVerified(packet->m_bVerified);
|
||||
ste->SetCensored(packet->m_bCensored);
|
||||
|
||||
|
|
@ -3392,12 +3400,12 @@ void ClientConnection::handleSignUpdate(shared_ptr<SignUpdatePacket> packet)
|
|||
}
|
||||
else
|
||||
{
|
||||
app.DebugPrintf("dynamic_pointer_cast<SignTileEntity>(te) == nullptr\n");
|
||||
app.DebugPrintf("[SIGN] ERROR: tile entity is not a SignTileEntity\n");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
app.DebugPrintf("hasChunkAt failed\n");
|
||||
app.DebugPrintf("[SIGN] ERROR: chunk not loaded at position\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3784,6 +3792,158 @@ void ClientConnection::handleSoundEvent(shared_ptr<LevelSoundPacket> packet)
|
|||
|
||||
void ClientConnection::handleCustomPayload(shared_ptr<CustomPayloadPacket> customPayloadPacket)
|
||||
{
|
||||
#ifdef _WINDOWS64
|
||||
// Build a server-specific identity token file path next to the executable.
|
||||
// Each server gets its own token file based on a hash of the server address,
|
||||
// so connecting to multiple secured servers doesn't overwrite tokens.
|
||||
auto buildIdentityTokenPath = []() -> std::string {
|
||||
char exePath[MAX_PATH] = {};
|
||||
DWORD len = GetModuleFileNameA(NULL, exePath, MAX_PATH);
|
||||
if (len == 0 || len >= MAX_PATH) return std::string();
|
||||
char *lastSlash = strrchr(exePath, '\\');
|
||||
if (lastSlash != NULL) *(lastSlash + 1) = 0;
|
||||
|
||||
// Hash the server IP:port to create a unique filename per server
|
||||
char serverAddr[300] = {};
|
||||
sprintf_s(serverAddr, sizeof(serverAddr), "%s:%d", g_Win64MultiplayerIP, g_Win64MultiplayerPort);
|
||||
unsigned int hash = 5381;
|
||||
for (const char *p = serverAddr; *p; ++p)
|
||||
hash = ((hash << 5) + hash) + static_cast<unsigned char>(*p);
|
||||
|
||||
char filename[64] = {};
|
||||
sprintf_s(filename, sizeof(filename), "identity-token-%08x.dat", hash);
|
||||
return std::string(exePath) + filename;
|
||||
};
|
||||
|
||||
// Identity token: server issued us a new token - store it locally
|
||||
if (CustomPayloadPacket::IDENTITY_TOKEN_ISSUE.compare(customPayloadPacket->identifier) == 0)
|
||||
{
|
||||
if (customPayloadPacket->data.data != nullptr && customPayloadPacket->length == 32)
|
||||
{
|
||||
std::string tokenPath = buildIdentityTokenPath();
|
||||
if (!tokenPath.empty())
|
||||
{
|
||||
FILE *f = nullptr;
|
||||
fopen_s(&f, tokenPath.c_str(), "wb");
|
||||
if (f != nullptr)
|
||||
{
|
||||
size_t written = fwrite(customPayloadPacket->data.data, 1, 32, f);
|
||||
fclose(f);
|
||||
if (written == 32)
|
||||
{
|
||||
app.DebugPrintf("Client: Stored identity token to %s\n", tokenPath.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
app.DebugPrintf("Client: Failed to write full identity token (wrote %zu/32)\n", written);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
app.DebugPrintf("Client: Failed to open %s for writing\n", tokenPath.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Identity token: server is challenging us to present our stored token
|
||||
if (CustomPayloadPacket::IDENTITY_TOKEN_CHALLENGE.compare(customPayloadPacket->identifier) == 0)
|
||||
{
|
||||
std::string tokenPath = buildIdentityTokenPath();
|
||||
FILE *f = nullptr;
|
||||
if (!tokenPath.empty())
|
||||
fopen_s(&f, tokenPath.c_str(), "rb");
|
||||
if (f != nullptr)
|
||||
{
|
||||
uint8_t token[32] = {};
|
||||
size_t bytesRead = fread(token, 1, 32, f);
|
||||
fclose(f);
|
||||
if (bytesRead == 32)
|
||||
{
|
||||
byteArray tokenData(32);
|
||||
memcpy(tokenData.data, token, 32);
|
||||
connection->send(std::make_shared<CustomPayloadPacket>(
|
||||
CustomPayloadPacket::IDENTITY_TOKEN_RESPONSE, tokenData));
|
||||
app.DebugPrintf("Client: Sent identity token response\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
app.DebugPrintf("Client: identity-token.dat is invalid (%zu bytes)\n", bytesRead);
|
||||
connection->send(std::make_shared<CustomPayloadPacket>(
|
||||
CustomPayloadPacket::IDENTITY_TOKEN_RESPONSE, byteArray()));
|
||||
}
|
||||
SecureZeroMemory(token, sizeof(token));
|
||||
}
|
||||
else
|
||||
{
|
||||
app.DebugPrintf("Client: No identity-token.dat found, sending empty response\n");
|
||||
connection->send(std::make_shared<CustomPayloadPacket>(
|
||||
CustomPayloadPacket::IDENTITY_TOKEN_RESPONSE, byteArray()));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Stream cipher handshake: server sent us a key
|
||||
if (CustomPayloadPacket::CIPHER_KEY_CHANNEL.compare(customPayloadPacket->identifier) == 0)
|
||||
{
|
||||
if (customPayloadPacket->length == ServerRuntime::Security::StreamCipher::KEY_SIZE &&
|
||||
customPayloadPacket->data.data != nullptr)
|
||||
{
|
||||
app.DebugPrintf("Client: Received MC|CKey from server (%d bytes)\n", customPayloadPacket->length);
|
||||
// Store key and send ack+activate atomically to prevent ResetClientCipher race
|
||||
WinsockNetLayer::StoreClientCipherKey(customPayloadPacket->data.data);
|
||||
if (!WinsockNetLayer::SendAckAndActivateClientSendCipher())
|
||||
{
|
||||
app.DebugPrintf("Client: Failed to send cipher ack, connection will be closed\n");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
app.DebugPrintf("Client: Received malformed MC|CKey (length=%d)\n", customPayloadPacket->length);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Fork server identification: enables render-distance-independent player list
|
||||
if (CustomPayloadPacket::FORK_HELLO_CHANNEL.compare(customPayloadPacket->identifier) == 0)
|
||||
{
|
||||
m_isForkServer = true;
|
||||
app.DebugPrintf("Client: Connected to fork server\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Fork server player leave: clean up IQNet slot so player leaves Tab list
|
||||
if (CustomPayloadPacket::FORK_PLAYER_LEAVE_CHANNEL.compare(customPayloadPacket->identifier) == 0)
|
||||
{
|
||||
if (customPayloadPacket->data.data != nullptr && customPayloadPacket->length > 0)
|
||||
{
|
||||
int nameLen = customPayloadPacket->length / static_cast<int>(sizeof(wchar_t));
|
||||
wstring leavingName(reinterpret_cast<const wchar_t*>(customPayloadPacket->data.data), nameLen);
|
||||
|
||||
for (int s = 1; s < MINECRAFT_NET_MAX_PLAYERS; ++s)
|
||||
{
|
||||
IQNetPlayer* qp = &IQNet::m_player[s];
|
||||
if (qp->GetCustomDataValue() != 0 &&
|
||||
_wcsicmp(qp->m_gamertag, leavingName.c_str()) == 0)
|
||||
{
|
||||
extern CPlatformNetworkManagerStub* g_pPlatformNetworkManager;
|
||||
g_pPlatformNetworkManager->NotifyPlayerLeaving(qp);
|
||||
qp->m_smallId = 0;
|
||||
qp->m_isRemote = false;
|
||||
qp->m_isHostPlayer = false;
|
||||
qp->m_resolvedXuid = INVALID_XUID;
|
||||
qp->m_gamertag[0] = 0;
|
||||
qp->SetCustomDataValue(0);
|
||||
app.DebugPrintf("Client: Player \"%ls\" left fork server, cleared IQNet slot %d\n", leavingName.c_str(), s);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (CustomPayloadPacket::TRADER_LIST_PACKET.compare(customPayloadPacket->identifier) == 0)
|
||||
{
|
||||
ByteArrayInputStream bais(customPayloadPacket->data);
|
||||
|
|
@ -4091,8 +4251,7 @@ void ClientConnection::handleSetPlayerTeamPacket(shared_ptr<SetPlayerTeamPacket>
|
|||
|
||||
void ClientConnection::handleParticleEvent(shared_ptr<LevelParticlesPacket> packet)
|
||||
{
|
||||
wstring particleName = packet->getName();
|
||||
ePARTICLE_TYPE particleId = (ePARTICLE_TYPE)Integer::parseInt(particleName);
|
||||
ePARTICLE_TYPE particleId = (ePARTICLE_TYPE)Integer::parseInt(packet->getName());
|
||||
|
||||
for (int i = 0; i < packet->getCount(); i++)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ private:
|
|||
|
||||
std::unordered_set<int> m_trackedEntityIds;
|
||||
std::unordered_set<int64_t> m_visibleChunks;
|
||||
bool m_isForkServer; // true when connected to a fork server (received MC|ForkHello)
|
||||
|
||||
static int64_t chunkKey(int x, int z) { return ((int64_t)x << 32) | ((int64_t)z & 0xFFFFFFFF); }
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,13 @@
|
|||
#include "stdafx.h"
|
||||
#include "ClientConstants.h"
|
||||
#include "Common/BuildVer.h"
|
||||
|
||||
const wstring ClientConstants::VERSION_STRING = wstring(L"Minecraft LCE ") + VER_FILEVERSION_STR_W;//+ SharedConstants::VERSION_STRING;
|
||||
const wstring ClientConstants::BRANCH_STRING = VER_BRANCHVERSION_STR_W;
|
||||
|
||||
// Default value for the toggle. If BuildVer defines VER_SHOW_VERSION_WATERMARK, use that.
|
||||
#ifdef VER_SHOW_VERSION_WATERMARK
|
||||
const bool ClientConstants::SHOW_VERSION_WATERMARK = (VER_SHOW_VERSION_WATERMARK != 0);
|
||||
#else
|
||||
const bool ClientConstants::SHOW_VERSION_WATERMARK = false;
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -15,5 +15,8 @@ public:
|
|||
static const wstring VERSION_STRING;
|
||||
static const wstring BRANCH_STRING;
|
||||
|
||||
// Toggle to show/hide the version/branch watermark in the debug overlay
|
||||
static const bool SHOW_VERSION_WATERMARK;
|
||||
|
||||
static const bool DEADMAU5_CAMERA_CHEATS = false;
|
||||
};
|
||||
|
|
@ -45,6 +45,7 @@
|
|||
#define GAME_HOST_OPTION_BITMASK_DOTILEDROPS 0x08000000
|
||||
#define GAME_HOST_OPTION_BITMASK_NATURALREGEN 0x10000000
|
||||
#define GAME_HOST_OPTION_BITMASK_DODAYLIGHTCYCLE 0x20000000
|
||||
#define GAME_HOST_OPTION_BITMASK_HARDCORE 0x40000000 // 4J Added - for hardcore mode
|
||||
#define GAME_HOST_OPTION_BITMASK_ALL 0xFFFFFFFF
|
||||
|
||||
#define GAME_HOST_OPTION_BITMASK_WORLDSIZE_BITSHIFT 20
|
||||
|
|
@ -104,6 +105,8 @@ enum EGameHostOptionWorldSize
|
|||
#define GAMESETTING_ANIMATEDCHARACTER 0x00008000
|
||||
#define GAMESETTING_PS3EULAREAD 0x00010000
|
||||
#define GAMESETTING_PSVITANETWORKMODEADHOC 0x00020000
|
||||
#define GAMESETTING_VSYNC 0x01000000
|
||||
#define GAMESETTING_EXCLUSIVEFULLSCREEN 0x02000000
|
||||
|
||||
|
||||
// defines for languages
|
||||
|
|
|
|||
|
|
@ -178,6 +178,9 @@ enum eGameSetting
|
|||
// PSVita
|
||||
eGameSetting_PSVita_NetworkModeAdhoc,
|
||||
|
||||
// PC
|
||||
eGameSetting_VSync,
|
||||
eGameSetting_ExclusiveFullscreen,
|
||||
|
||||
};
|
||||
|
||||
|
|
@ -660,6 +663,7 @@ enum eGameHostOption
|
|||
eGameHostOption_DoTileDrops,
|
||||
eGameHostOption_NaturalRegeneration,
|
||||
eGameHostOption_DoDaylightCycle,
|
||||
eGameHostOption_Hardcore, // 4J Added - for hardcore mode
|
||||
};
|
||||
|
||||
// 4J-PB - If any new DLC items are added to the TMSFiles, this array needs updated
|
||||
|
|
|
|||
|
|
@ -207,6 +207,8 @@ CMinecraftApp::CMinecraftApp()
|
|||
m_dwRequiredTexturePackID=0;
|
||||
|
||||
m_bResetNether=false;
|
||||
m_seedOverride = 0;
|
||||
m_hasSeedOverride = false;
|
||||
|
||||
#ifdef _XBOX
|
||||
// m_bTransferSavesToXboxOne=false;
|
||||
|
|
@ -1398,6 +1400,7 @@ void CMinecraftApp::ApplyGameSettingsChanged(int iPad)
|
|||
ActionGameSettings(iPad,eGameSetting_AnimatedCharacter);
|
||||
|
||||
ActionGameSettings(iPad,eGameSetting_PS3_EULA_Read);
|
||||
ActionGameSettings(iPad,eGameSetting_VSync);
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -1633,6 +1636,22 @@ void CMinecraftApp::ActionGameSettings(int iPad,eGameSetting eVal)
|
|||
case eGameSetting_PSVita_NetworkModeAdhoc:
|
||||
//nothing to do here
|
||||
break;
|
||||
case eGameSetting_VSync:
|
||||
#ifdef _WINDOWS64
|
||||
{
|
||||
extern bool g_bVSync;
|
||||
g_bVSync = (GetGameSettings(iPad, eGameSetting_VSync) != 0);
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case eGameSetting_ExclusiveFullscreen:
|
||||
#ifdef _WINDOWS64
|
||||
{
|
||||
extern void SetExclusiveFullscreen(bool enabled);
|
||||
SetExclusiveFullscreen(GetGameSettings(iPad, eGameSetting_ExclusiveFullscreen) != 0);
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2344,6 +2363,38 @@ void CMinecraftApp::SetGameSettings(int iPad,eGameSetting eVal,unsigned char ucV
|
|||
}
|
||||
break;
|
||||
|
||||
case eGameSetting_VSync:
|
||||
if(((GameSettingsA[iPad]->uiBitmaskValues&GAMESETTING_VSYNC)>>24)!=(ucVal&0x01))
|
||||
{
|
||||
if(ucVal==1)
|
||||
{
|
||||
GameSettingsA[iPad]->uiBitmaskValues|=GAMESETTING_VSYNC;
|
||||
}
|
||||
else
|
||||
{
|
||||
GameSettingsA[iPad]->uiBitmaskValues&=~GAMESETTING_VSYNC;
|
||||
}
|
||||
ActionGameSettings(iPad,eVal);
|
||||
GameSettingsA[iPad]->bSettingsChanged=true;
|
||||
}
|
||||
break;
|
||||
|
||||
case eGameSetting_ExclusiveFullscreen:
|
||||
if(((GameSettingsA[iPad]->uiBitmaskValues&GAMESETTING_EXCLUSIVEFULLSCREEN)>>25)!=(ucVal&0x01))
|
||||
{
|
||||
if(ucVal==1)
|
||||
{
|
||||
GameSettingsA[iPad]->uiBitmaskValues|=GAMESETTING_EXCLUSIVEFULLSCREEN;
|
||||
}
|
||||
else
|
||||
{
|
||||
GameSettingsA[iPad]->uiBitmaskValues&=~GAMESETTING_EXCLUSIVEFULLSCREEN;
|
||||
}
|
||||
ActionGameSettings(iPad,eVal);
|
||||
GameSettingsA[iPad]->bSettingsChanged=true;
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2479,6 +2530,12 @@ unsigned char CMinecraftApp::GetGameSettings(int iPad,eGameSetting eVal)
|
|||
case eGameSetting_PSVita_NetworkModeAdhoc:
|
||||
return (GameSettingsA[iPad]->uiBitmaskValues&GAMESETTING_PSVITANETWORKMODEADHOC)>>17;
|
||||
|
||||
case eGameSetting_VSync:
|
||||
return (GameSettingsA[iPad]->uiBitmaskValues&GAMESETTING_VSYNC)>>24;
|
||||
|
||||
case eGameSetting_ExclusiveFullscreen:
|
||||
return (GameSettingsA[iPad]->uiBitmaskValues&GAMESETTING_EXCLUSIVEFULLSCREEN)>>25;
|
||||
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -8103,6 +8160,16 @@ void CMinecraftApp::SetGameHostOption(unsigned int &uiHostSettings, eGameHostOpt
|
|||
uiHostSettings&=~GAME_HOST_OPTION_BITMASK_WORLDSIZE;
|
||||
uiHostSettings|=(GAME_HOST_OPTION_BITMASK_WORLDSIZE & (uiVal<<GAME_HOST_OPTION_BITMASK_WORLDSIZE_BITSHIFT));
|
||||
break;
|
||||
case eGameHostOption_Hardcore: // 4J Added - for hardcore mode
|
||||
if(uiVal!=0)
|
||||
{
|
||||
uiHostSettings |= GAME_HOST_OPTION_BITMASK_HARDCORE;
|
||||
}
|
||||
else
|
||||
{
|
||||
uiHostSettings &= ~GAME_HOST_OPTION_BITMASK_HARDCORE;
|
||||
}
|
||||
break;
|
||||
case eGameHostOption_All:
|
||||
uiHostSettings=uiVal;
|
||||
break;
|
||||
|
|
@ -8204,6 +8271,9 @@ unsigned int CMinecraftApp::GetGameHostOption(unsigned int uiHostSettings, eGame
|
|||
case eGameHostOption_DoDaylightCycle:
|
||||
return !(uiHostSettings&GAME_HOST_OPTION_BITMASK_DODAYLIGHTCYCLE);
|
||||
break;
|
||||
case eGameHostOption_Hardcore: // 4J Added - for hardcore mode
|
||||
return (uiHostSettings&GAME_HOST_OPTION_BITMASK_HARDCORE) ? 1 : 0;
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -707,6 +707,8 @@ private:
|
|||
bool m_bGameNewWorldSizeUseMoat;
|
||||
unsigned int m_GameNewHellScale;
|
||||
#endif
|
||||
int64_t m_seedOverride;
|
||||
bool m_hasSeedOverride;
|
||||
unsigned int FromBigEndian(unsigned int uiValue);
|
||||
|
||||
public:
|
||||
|
|
@ -724,6 +726,10 @@ public:
|
|||
void SetGameNewHellScale(unsigned int newScale) { m_GameNewHellScale = newScale; }
|
||||
unsigned int GetGameNewHellScale() { return m_GameNewHellScale; }
|
||||
#endif
|
||||
void SetSeedOverride(int64_t seed) { m_seedOverride = seed; m_hasSeedOverride = true; }
|
||||
bool HasSeedOverride() { return m_hasSeedOverride; }
|
||||
int64_t GetSeedOverride() { return m_seedOverride; }
|
||||
|
||||
void SetResetNether(bool bResetNether) {m_bResetNether=bResetNether;}
|
||||
bool GetResetNether() {return m_bResetNether;}
|
||||
bool CanRecordStatsAndAchievements();
|
||||
|
|
@ -810,6 +816,10 @@ public:
|
|||
void SetCorruptSaveDeleted(bool bVal) {m_bCorruptSaveDeleted=bVal;}
|
||||
bool GetCorruptSaveDeleted(void) {return m_bCorruptSaveDeleted;}
|
||||
|
||||
// 4J Added: Store save folder name for hardcore world deletion on Win64
|
||||
void SetCurrentSaveFolderName(const wstring& name) { m_currentSaveFolderName = name; }
|
||||
const wstring& GetCurrentSaveFolderName() const { return m_currentSaveFolderName; }
|
||||
|
||||
void EnterSaveNotificationSection();
|
||||
void LeaveSaveNotificationSection();
|
||||
private:
|
||||
|
|
@ -831,6 +841,7 @@ private:
|
|||
CRITICAL_SECTION csAdditionalSkinBoxes;
|
||||
CRITICAL_SECTION csAnimOverrideBitmask;
|
||||
bool m_bCorruptSaveDeleted;
|
||||
wstring m_currentSaveFolderName; // 4J Added: for hardcore world deletion on Win64
|
||||
|
||||
DWORD m_dwAdditionalModelParts[XUSER_MAX_COUNT];
|
||||
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 128 B |
|
After Width: | Height: | Size: 129 B |
|
After Width: | Height: | Size: 149 B |
|
After Width: | Height: | Size: 135 B |
|
After Width: | Height: | Size: 153 B |
|
After Width: | Height: | Size: 163 B |
|
After Width: | Height: | Size: 122 B |
|
After Width: | Height: | Size: 117 B |
|
After Width: | Height: | Size: 125 B |
|
After Width: | Height: | Size: 135 B |
|
|
@ -38220,6 +38220,56 @@
|
|||
<Time>19</Time>
|
||||
<Command>stop</Command>
|
||||
</NamedFrame>
|
||||
<NamedFrame>
|
||||
<Name>NormalHardcore</Name>
|
||||
<Time>20</Time>
|
||||
<Command>stop</Command>
|
||||
</NamedFrame>
|
||||
<NamedFrame>
|
||||
<Name>HalfHardcore</Name>
|
||||
<Time>21</Time>
|
||||
<Command>stop</Command>
|
||||
</NamedFrame>
|
||||
<NamedFrame>
|
||||
<Name>FullHardcore</Name>
|
||||
<Time>22</Time>
|
||||
<Command>stop</Command>
|
||||
</NamedFrame>
|
||||
<NamedFrame>
|
||||
<Name>HalfPoisonHardcore</Name>
|
||||
<Time>23</Time>
|
||||
<Command>stop</Command>
|
||||
</NamedFrame>
|
||||
<NamedFrame>
|
||||
<Name>FullPoisonHardcore</Name>
|
||||
<Time>24</Time>
|
||||
<Command>stop</Command>
|
||||
</NamedFrame>
|
||||
<NamedFrame>
|
||||
<Name>NormalFlashHardcore</Name>
|
||||
<Time>25</Time>
|
||||
<Command>stop</Command>
|
||||
</NamedFrame>
|
||||
<NamedFrame>
|
||||
<Name>HalfFlashHardcore</Name>
|
||||
<Time>26</Time>
|
||||
<Command>stop</Command>
|
||||
</NamedFrame>
|
||||
<NamedFrame>
|
||||
<Name>FullFlashHardcore</Name>
|
||||
<Time>27</Time>
|
||||
<Command>stop</Command>
|
||||
</NamedFrame>
|
||||
<NamedFrame>
|
||||
<Name>HalfPoisonFlashHardcore</Name>
|
||||
<Time>28</Time>
|
||||
<Command>stop</Command>
|
||||
</NamedFrame>
|
||||
<NamedFrame>
|
||||
<Name>FullPoisonFlashHardcore</Name>
|
||||
<Time>29</Time>
|
||||
<Command>stop</Command>
|
||||
</NamedFrame>
|
||||
</NamedFrames>
|
||||
<Timeline>
|
||||
<Id>Border</Id>
|
||||
|
|
@ -38274,6 +38324,16 @@
|
|||
<Interpolation>0</Interpolation>
|
||||
<Prop>Graphics\HUD\Health_Background_Flash.png</Prop>
|
||||
</KeyFrame>
|
||||
<KeyFrame>
|
||||
<Time>20</Time>
|
||||
<Interpolation>0</Interpolation>
|
||||
<Prop>Graphics\HUD\Health_Background_Hardcore.png</Prop>
|
||||
</KeyFrame>
|
||||
<KeyFrame>
|
||||
<Time>25</Time>
|
||||
<Interpolation>0</Interpolation>
|
||||
<Prop>Graphics\HUD\Health_Background_Hardcore_Flash.png</Prop>
|
||||
</KeyFrame>
|
||||
</Timeline>
|
||||
<Timeline>
|
||||
<Id>Heart</Id>
|
||||
|
|
@ -38399,6 +38459,66 @@
|
|||
<Prop>true</Prop>
|
||||
<Prop>Graphics\HUD\HorseHealth_Half_Flash.png</Prop>
|
||||
</KeyFrame>
|
||||
<KeyFrame>
|
||||
<Time>20</Time>
|
||||
<Interpolation>0</Interpolation>
|
||||
<Prop>false</Prop>
|
||||
<Prop></Prop>
|
||||
</KeyFrame>
|
||||
<KeyFrame>
|
||||
<Time>21</Time>
|
||||
<Interpolation>0</Interpolation>
|
||||
<Prop>true</Prop>
|
||||
<Prop>Graphics\HUD\Health_Half_Hardcore.png</Prop>
|
||||
</KeyFrame>
|
||||
<KeyFrame>
|
||||
<Time>22</Time>
|
||||
<Interpolation>0</Interpolation>
|
||||
<Prop>true</Prop>
|
||||
<Prop>Graphics\HUD\Health_Full_Hardcore.png</Prop>
|
||||
</KeyFrame>
|
||||
<KeyFrame>
|
||||
<Time>23</Time>
|
||||
<Interpolation>0</Interpolation>
|
||||
<Prop>true</Prop>
|
||||
<Prop>Graphics\HUD\Health_Half_Poison_Hardcore.png</Prop>
|
||||
</KeyFrame>
|
||||
<KeyFrame>
|
||||
<Time>24</Time>
|
||||
<Interpolation>0</Interpolation>
|
||||
<Prop>true</Prop>
|
||||
<Prop>Graphics\HUD\Health_Full_Poison_Hardcore.png</Prop>
|
||||
</KeyFrame>
|
||||
<KeyFrame>
|
||||
<Time>25</Time>
|
||||
<Interpolation>0</Interpolation>
|
||||
<Prop>false</Prop>
|
||||
<Prop></Prop>
|
||||
</KeyFrame>
|
||||
<KeyFrame>
|
||||
<Time>26</Time>
|
||||
<Interpolation>0</Interpolation>
|
||||
<Prop>true</Prop>
|
||||
<Prop>Graphics\HUD\Health_Half_Flash_Hardcore.png</Prop>
|
||||
</KeyFrame>
|
||||
<KeyFrame>
|
||||
<Time>27</Time>
|
||||
<Interpolation>0</Interpolation>
|
||||
<Prop>true</Prop>
|
||||
<Prop>Graphics\HUD\Health_Full_Flash_Hardcore.png</Prop>
|
||||
</KeyFrame>
|
||||
<KeyFrame>
|
||||
<Time>28</Time>
|
||||
<Interpolation>0</Interpolation>
|
||||
<Prop>true</Prop>
|
||||
<Prop>Graphics\HUD\Health_Half_Poison_Flash_Hardcore.png</Prop>
|
||||
</KeyFrame>
|
||||
<KeyFrame>
|
||||
<Time>29</Time>
|
||||
<Interpolation>0</Interpolation>
|
||||
<Prop>true</Prop>
|
||||
<Prop>Graphics\HUD\Health_Full_Poison_Flash_Hardcore.png</Prop>
|
||||
</KeyFrame>
|
||||
</Timeline>
|
||||
</Timelines>
|
||||
</XuiVisual>
|
||||
|
|
|
|||
|
|
@ -802,6 +802,9 @@ void CGameNetworkManager::CancelJoinGame(LPVOID lpParam)
|
|||
#ifdef _XBOX_ONE
|
||||
s_pPlatformNetworkManager->CancelJoinGame();
|
||||
#endif
|
||||
#ifdef _WINDOWS64
|
||||
WinsockNetLayer::CancelJoinGame();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool CGameNetworkManager::LeaveGame(bool bMigrateHost)
|
||||
|
|
|
|||
|
|
@ -175,8 +175,6 @@ bool CPlatformNetworkManagerStub::Initialise(CGameNetworkManager *pGameNetworkMa
|
|||
m_bIsOfflineGame = false;
|
||||
#ifdef _WINDOWS64
|
||||
m_bJoinPending = false;
|
||||
m_joinLocalUsersMask = 0;
|
||||
m_joinHostName[0] = 0;
|
||||
#endif
|
||||
m_pSearchParam = nullptr;
|
||||
m_SessionsUpdatedCallback = nullptr;
|
||||
|
|
@ -288,6 +286,8 @@ void CPlatformNetworkManagerStub::DoWork()
|
|||
}
|
||||
}
|
||||
|
||||
// Async join finalization: when the background thread reports success,
|
||||
// register players and transition the session to starting state.
|
||||
if (m_bJoinPending)
|
||||
{
|
||||
WinsockNetLayer::eJoinState state = WinsockNetLayer::GetJoinState();
|
||||
|
|
@ -296,7 +296,6 @@ void CPlatformNetworkManagerStub::DoWork()
|
|||
WinsockNetLayer::FinalizeJoin();
|
||||
|
||||
BYTE localSmallId = WinsockNetLayer::GetLocalSmallId();
|
||||
|
||||
IQNet::m_player[localSmallId].m_smallId = localSmallId;
|
||||
IQNet::m_player[localSmallId].m_isRemote = false;
|
||||
IQNet::m_player[localSmallId].m_isHostPlayer = false;
|
||||
|
|
@ -548,17 +547,15 @@ int CPlatformNetworkManagerStub::JoinGame(FriendSessionInfo* searchResult, int l
|
|||
IQNet::m_player[0].m_smallId = 0;
|
||||
IQNet::m_player[0].m_isRemote = true;
|
||||
IQNet::m_player[0].m_isHostPlayer = true;
|
||||
// Remote host still maps to legacy host XUID in mixed old/new sessions.
|
||||
IQNet::m_player[0].m_resolvedXuid = Win64Xuid::GetLegacyEmbeddedHostXuid();
|
||||
wcsncpy_s(IQNet::m_player[0].m_gamertag, 32, searchResult->data.hostName, _TRUNCATE);
|
||||
|
||||
WinsockNetLayer::StopDiscovery();
|
||||
|
||||
wcsncpy_s(m_joinHostName, 32, searchResult->data.hostName, _TRUNCATE);
|
||||
m_joinLocalUsersMask = localUsersMask;
|
||||
|
||||
if (!WinsockNetLayer::BeginJoinGame(hostIP, hostPort))
|
||||
{
|
||||
app.DebugPrintf("Win64 LAN: Failed to connect to %s:%d\n", hostIP, hostPort);
|
||||
app.DebugPrintf("Win64 LAN: Failed to start async join to %s:%d\n", hostIP, hostPort);
|
||||
return CGameNetworkManager::JOINGAME_FAIL_GENERAL;
|
||||
}
|
||||
|
||||
|
|
@ -978,6 +975,13 @@ void CPlatformNetworkManagerStub::ForceFriendsSessionRefresh()
|
|||
delete m_pSearchResults[i];
|
||||
m_pSearchResults[i] = nullptr;
|
||||
}
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
// Immediately rebuild the session list from servers.db so that
|
||||
// edits/deletions are visible as soon as the UI regains focus,
|
||||
// rather than waiting for the next TickSearch() cycle.
|
||||
SearchForGames();
|
||||
#endif
|
||||
}
|
||||
|
||||
INetworkPlayer *CPlatformNetworkManagerStub::addNetworkPlayer(IQNetPlayer *pQNetPlayer)
|
||||
|
|
|
|||
|
|
@ -76,11 +76,8 @@ private:
|
|||
bool m_bIsOfflineGame;
|
||||
bool m_bIsPrivateGame;
|
||||
int m_flagIndexSize;
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
bool m_bJoinPending;
|
||||
int m_joinLocalUsersMask;
|
||||
wchar_t m_joinHostName[32];
|
||||
#endif
|
||||
|
||||
// This is only maintained by the host, and is not valid on client machines
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@
|
|||
#include "../../../Minecraft.World/net.minecraft.world.item.h"
|
||||
#include "../../../Minecraft.World/net.minecraft.world.entity.ai.attributes.h"
|
||||
#include "../../../Minecraft.World/net.minecraft.world.entity.monster.h"
|
||||
#include "../../MultiPlayerLevel.h"
|
||||
#include "../../../Minecraft.World\LevelData.h"
|
||||
#include "IUIScene_HUD.h"
|
||||
|
||||
#include "UI.h"
|
||||
|
|
@ -20,6 +22,7 @@ IUIScene_HUD::IUIScene_HUD()
|
|||
m_lastMaxHealth = 20;
|
||||
m_lastHealthBlink = false;
|
||||
m_lastHealthPoison = false;
|
||||
m_lastHealthHardcore = false;
|
||||
m_iCurrentFood = -1;
|
||||
m_lastFoodPoison = false;
|
||||
m_lastAir = 10;
|
||||
|
|
@ -94,9 +97,10 @@ void IUIScene_HUD::updateFrameTick()
|
|||
ShowHealth(false);
|
||||
ShowFood(false);
|
||||
ShowAir(false);
|
||||
ShowArmour(false);
|
||||
ShowArmour(false);
|
||||
ShowExpBar(false);
|
||||
SetHealthAbsorb(0);
|
||||
SetHealthAbsorb(0);
|
||||
SetHardcoreMode(false);
|
||||
}
|
||||
|
||||
if(pMinecraft->localplayers[iPad]->isRidingJumpable())
|
||||
|
|
@ -206,6 +210,12 @@ void IUIScene_HUD::renderPlayerHealth()
|
|||
// Update armour
|
||||
int armor = pMinecraft->localplayers[iPad]->getArmorValue();
|
||||
|
||||
// Check hardcore mode
|
||||
bool bHardcore = pMinecraft->level != nullptr
|
||||
&& pMinecraft->level->getLevelData() != nullptr
|
||||
&& pMinecraft->level->getLevelData()->isHardcore();
|
||||
SetHardcoreMode(bHardcore);
|
||||
|
||||
SetHealth(currentHealth, oldHealth, blink, bHasPoison || bHasWither, bHasWither);
|
||||
SetHealthAbsorb(totalAbsorption);
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ protected:
|
|||
int m_iCurrentHealth;
|
||||
int m_lastMaxHealth;
|
||||
bool m_lastHealthBlink, m_lastHealthPoison, m_lastHealthWither;
|
||||
bool m_lastHealthHardcore;
|
||||
int m_iCurrentFood;
|
||||
bool m_lastFoodPoison;
|
||||
int m_lastAir, m_currentExtraAir;
|
||||
|
|
@ -46,6 +47,7 @@ protected:
|
|||
virtual void SetActiveSlot(int slot) = 0;
|
||||
|
||||
virtual void SetHealth(int iHealth, int iLastHealth, bool bBlink, bool bPoison, bool bWither) = 0;
|
||||
virtual void SetHardcoreMode(bool bHardcore) = 0;
|
||||
virtual void SetFood(int iFood, int iLastFood, bool bPoison) = 0;
|
||||
virtual void SetAir(int iAir, int extra) = 0;
|
||||
virtual void SetArmour(int iArmour) = 0;
|
||||
|
|
|
|||
|
|
@ -410,11 +410,72 @@ int IUIScene_PauseMenu::ExitWorldThreadProc( void* lpParameter )
|
|||
return S_OK;
|
||||
}
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
static bool Win64_DeleteSaveDirectory(const wchar_t* wPath)
|
||||
{
|
||||
wchar_t wSearch[MAX_PATH];
|
||||
swprintf_s(wSearch, MAX_PATH, L"%s\\*", wPath);
|
||||
WIN32_FIND_DATAW fd;
|
||||
HANDLE hFind = FindFirstFileW(wSearch, &fd);
|
||||
if (hFind != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
do
|
||||
{
|
||||
if (wcscmp(fd.cFileName, L".") == 0 || wcscmp(fd.cFileName, L"..") == 0) continue;
|
||||
wchar_t wChild[MAX_PATH];
|
||||
swprintf_s(wChild, MAX_PATH, L"%s\\%s", wPath, fd.cFileName);
|
||||
if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
||||
Win64_DeleteSaveDirectory(wChild);
|
||||
else
|
||||
DeleteFileW(wChild);
|
||||
} while (FindNextFileW(hFind, &fd));
|
||||
FindClose(hFind);
|
||||
}
|
||||
return RemoveDirectoryW(wPath) != 0;
|
||||
}
|
||||
#endif // _WINDOWS64
|
||||
|
||||
// This function performs the meat of exiting from a level. It should be called from a thread other than the main thread.
|
||||
void IUIScene_PauseMenu::_ExitWorld(LPVOID lpParameter)
|
||||
{
|
||||
Minecraft *pMinecraft=Minecraft::GetInstance();
|
||||
|
||||
// 4J Added: Capture hardcore delete info before the server is destroyed
|
||||
#ifdef _WINDOWS64
|
||||
bool shouldDeleteHardcoreWorld = false;
|
||||
wstring hardcoreSaveFolderName;
|
||||
if (MinecraftServer::getInstance() != nullptr && MinecraftServer::getInstance()->getDeleteWorldOnExit())
|
||||
{
|
||||
shouldDeleteHardcoreWorld = true;
|
||||
// Try 1: Use the save folder name stored by UIScene_LoadMenu::StartGameFromSave (works for existing saves)
|
||||
hardcoreSaveFolderName = app.GetCurrentSaveFolderName();
|
||||
if (!hardcoreSaveFolderName.empty())
|
||||
{
|
||||
app.DebugPrintf("Hardcore mode: save folder from app = '%ls'\n", hardcoreSaveFolderName.c_str());
|
||||
}
|
||||
// Try 2: StorageManager (may work for new saves after first autosave)
|
||||
if (hardcoreSaveFolderName.empty())
|
||||
{
|
||||
char szSaveFolder[MAX_SAVEFILENAME_LENGTH] = {};
|
||||
StorageManager.GetSaveUniqueFilename(szSaveFolder);
|
||||
if (szSaveFolder[0] != '\0')
|
||||
{
|
||||
wchar_t wSaveFolder[MAX_SAVEFILENAME_LENGTH] = {};
|
||||
mbstowcs(wSaveFolder, szSaveFolder, MAX_SAVEFILENAME_LENGTH - 1);
|
||||
hardcoreSaveFolderName = wSaveFolder;
|
||||
app.DebugPrintf("Hardcore mode: save folder from StorageManager = '%s'\n", szSaveFolder);
|
||||
}
|
||||
}
|
||||
// Try 3: Stored during loadLevel
|
||||
if (hardcoreSaveFolderName.empty())
|
||||
{
|
||||
hardcoreSaveFolderName = MinecraftServer::getInstance()->getSaveFolderName();
|
||||
app.DebugPrintf("Hardcore mode: save folder from server = '%ls'\n", hardcoreSaveFolderName.c_str());
|
||||
}
|
||||
MinecraftServer::getInstance()->setDeleteWorldOnExit(false);
|
||||
}
|
||||
#endif
|
||||
|
||||
int exitReasonStringId = pMinecraft->progressRenderer->getCurrentTitle();
|
||||
int exitReasonTitleId = IDS_CONNECTION_LOST;
|
||||
|
||||
|
|
@ -625,6 +686,17 @@ void IUIScene_PauseMenu::_ExitWorld(LPVOID lpParameter)
|
|||
{
|
||||
Sleep(1);
|
||||
}
|
||||
// 4J Added: Hardcore mode — delete world save data now that the server is fully stopped
|
||||
#ifdef _WINDOWS64
|
||||
if (shouldDeleteHardcoreWorld && !hardcoreSaveFolderName.empty())
|
||||
{
|
||||
wchar_t wFolderPath[MAX_PATH] = {};
|
||||
swprintf_s(wFolderPath, MAX_PATH, L"Windows64\\GameHDD\\%s", hardcoreSaveFolderName.c_str());
|
||||
app.DebugPrintf("Hardcore mode: Deleting world save folder '%ls'\n", wFolderPath);
|
||||
Win64_DeleteSaveDirectory(wFolderPath);
|
||||
}
|
||||
#endif
|
||||
|
||||
pMinecraft->setLevel(nullptr,exitReasonStringId,nullptr,saveStats);
|
||||
|
||||
TelemetryManager->Flush();
|
||||
|
|
|
|||
|
|
@ -141,6 +141,9 @@ S32 UIBitmapFont::GetCodepointGlyph(U32 codepoint)
|
|||
// 4J-JEV: Change "right single quotation marks" to apostrophies.
|
||||
if (codepoint == 0x2019) codepoint = 0x27;
|
||||
|
||||
if (!m_cFontData->hasGlyph(codepoint))
|
||||
return IGGY_GLYPH_INVALID;
|
||||
|
||||
return m_cFontData->getGlyphId(codepoint);
|
||||
}
|
||||
|
||||
|
|
@ -253,19 +256,6 @@ rrbool UIBitmapFont::GetGlyphBitmap(S32 glyph,F32 pixel_scale,IggyBitmapCharacte
|
|||
while ( (0.5f + glyphScale) * truePixelScale < pixel_scale)
|
||||
glyphScale++;
|
||||
|
||||
// Debug: log each unique (font, pixel_scale) pair
|
||||
{
|
||||
static std::unordered_set<int> s_loggedScaleKeys;
|
||||
// Encode font pointer + quantized scale into a key to log each combo once
|
||||
int scaleKey = (int)(pixel_scale * 100.0f) ^ (int)(uintptr_t)m_cFontData;
|
||||
if (s_loggedScaleKeys.find(scaleKey) == s_loggedScaleKeys.end() && s_loggedScaleKeys.size() < 50) {
|
||||
s_loggedScaleKeys.insert(scaleKey);
|
||||
float tps = truePixelScale;
|
||||
app.DebugPrintf("[FONT-DBG] GetGlyphBitmap: font=%s glyph=%d pixel_scale=%.3f truePixelScale=%.1f glyphScale=%.0f\n",
|
||||
m_cFontData->getFontName().c_str(), glyph, pixel_scale, tps, glyphScale);
|
||||
}
|
||||
}
|
||||
|
||||
// 4J-JEV: Debug code to check which font sizes are being used.
|
||||
#if (!defined _CONTENT_PACKAGE) && (VERBOSE_FONT_OUTPUT > 0)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
#include "stdafx.h"
|
||||
#include "UI.h"
|
||||
#include "UIControl.h"
|
||||
|
||||
#include "../../../Minecraft.World/StringHelpers.h"
|
||||
#include "../../../Minecraft.World/JavaMath.h"
|
||||
#include "../../../Minecraft.World/ArabicShaping.h"
|
||||
|
||||
UIControl_Base::UIControl_Base()
|
||||
{
|
||||
|
|
@ -47,13 +49,16 @@ void UIControl_Base::tick()
|
|||
//app.DebugPrintf("Calling SetLabel - '%ls'\n", m_label.c_str());
|
||||
m_bLabelChanged = false;
|
||||
|
||||
// Shape the text before sending to Iggy; m_label stays unshaped for future updates
|
||||
wstring shaped = shapeArabicText(m_label.getString());
|
||||
|
||||
IggyDataValue result;
|
||||
IggyDataValue value[1];
|
||||
value[0].type = IGGY_DATATYPE_string_UTF16;
|
||||
IggyStringUTF16 stringVal;
|
||||
|
||||
stringVal.string = (IggyUTF16*) m_label.c_str();
|
||||
stringVal.length = m_label.length();
|
||||
stringVal.string = (IggyUTF16*) shaped.c_str();
|
||||
stringVal.length = (int)shaped.length();
|
||||
value[0].string16 = stringVal;
|
||||
|
||||
IggyResult out = IggyPlayerCallMethodRS ( m_parentScene->getMovie() , &result, getIggyValuePath() , m_setLabelFunc , 1 , value );
|
||||
|
|
@ -71,13 +76,16 @@ void UIControl_Base::setLabel(UIString label, bool instant, bool force)
|
|||
{
|
||||
m_bLabelChanged = false;
|
||||
|
||||
// Shape the text before sending to Iggy; m_label stays unshaped for future updates
|
||||
wstring shaped = shapeArabicText(m_label.getString());
|
||||
|
||||
IggyDataValue result;
|
||||
IggyDataValue value[1];
|
||||
value[0].type = IGGY_DATATYPE_string_UTF16;
|
||||
IggyStringUTF16 stringVal;
|
||||
|
||||
stringVal.string = (IggyUTF16*)m_label.c_str();
|
||||
stringVal.length = m_label.length();
|
||||
stringVal.string = (IggyUTF16*) shaped.c_str();
|
||||
stringVal.length = (int)shaped.length();
|
||||
value[0].string16 = stringVal;
|
||||
|
||||
IggyResult out = IggyPlayerCallMethodRS ( m_parentScene->getMovie() , &result, getIggyValuePath() , m_setLabelFunc , 1 , value );
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#include "UI.h"
|
||||
#include "UIControl_Label.h"
|
||||
#include "..\..\..\Minecraft.World\StringHelpers.h"
|
||||
#include "..\..\..\Minecraft.World\ArabicShaping.h"
|
||||
|
||||
UIControl_Label::UIControl_Label()
|
||||
{
|
||||
|
|
@ -38,13 +39,15 @@ void UIControl_Label::init(UIString label, int id)
|
|||
m_label = label;
|
||||
m_id = id;
|
||||
|
||||
wstring shaped = shapeArabicText(m_label.getString());
|
||||
|
||||
IggyDataValue result;
|
||||
IggyDataValue value[2];
|
||||
value[0].type = IGGY_DATATYPE_string_UTF16;
|
||||
IggyStringUTF16 stringVal;
|
||||
|
||||
stringVal.string = (IggyUTF16*)label.c_str();
|
||||
stringVal.length = label.length();
|
||||
stringVal.string = (IggyUTF16*)shaped.c_str();
|
||||
stringVal.length = (int)shaped.length();
|
||||
value[0].string16 = stringVal;
|
||||
|
||||
value[1].type = IGGY_DATATYPE_number;
|
||||
|
|
|
|||
|
|
@ -15,6 +15,56 @@
|
|||
//#define SKIN_PREVIEW_BOB_ANIM
|
||||
#define SKIN_PREVIEW_WALKING_ANIM
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
// Frame-rate-independent animation scaling.
|
||||
// The skin preview animations were designed for ~60fps (VSync on).
|
||||
// With uncapped FPS, each frame's contribution must be scaled down.
|
||||
// The scale is computed once per frame (keyed by frame counter) so
|
||||
// that multiple skin previews rendered in the same frame all use the
|
||||
// same value instead of measuring near-zero deltas between each other.
|
||||
static float s_skinAnimCachedScale = 1.0f;
|
||||
static double s_skinAnimLastTime = 0.0;
|
||||
static double s_skinAnimFreqInv = 0.0;
|
||||
|
||||
static float GetSkinAnimDeltaScale()
|
||||
{
|
||||
// Use the main loop's frame counter to detect a new frame.
|
||||
// GetTickCount changes every ~16ms, but we need per-frame detection.
|
||||
// Use a simple time threshold: if <0.1ms since last call, same frame.
|
||||
if (s_skinAnimFreqInv == 0.0)
|
||||
{
|
||||
LARGE_INTEGER freq;
|
||||
QueryPerformanceFrequency(&freq);
|
||||
s_skinAnimFreqInv = 1.0 / (double)freq.QuadPart;
|
||||
}
|
||||
LARGE_INTEGER now;
|
||||
QueryPerformanceCounter(&now);
|
||||
double currentTime = (double)now.QuadPart * s_skinAnimFreqInv;
|
||||
|
||||
// If less than 0.5ms since last call, assume same frame -- reuse cached scale
|
||||
double elapsed = currentTime - s_skinAnimLastTime;
|
||||
if (s_skinAnimLastTime != 0.0 && elapsed < 0.0005)
|
||||
{
|
||||
return s_skinAnimCachedScale;
|
||||
}
|
||||
|
||||
if (s_skinAnimLastTime == 0.0)
|
||||
{
|
||||
s_skinAnimLastTime = currentTime;
|
||||
s_skinAnimCachedScale = 1.0f;
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
s_skinAnimLastTime = currentTime;
|
||||
|
||||
const double kBaselineFrameTime = 1.0 / 60.0;
|
||||
float scale = static_cast<float>(elapsed / kBaselineFrameTime);
|
||||
if (scale > 3.0f) scale = 3.0f;
|
||||
s_skinAnimCachedScale = scale;
|
||||
return scale;
|
||||
}
|
||||
#endif
|
||||
|
||||
UIControl_PlayerSkinPreview::UIControl_PlayerSkinPreview()
|
||||
{
|
||||
UIControl::setControlType(UIControl::ePlayerSkinPreview);
|
||||
|
|
@ -306,7 +356,11 @@ void UIControl_PlayerSkinPreview::render(EntityRenderer *renderer, double x, dou
|
|||
break;
|
||||
case e_SkinPreviewAnimation_Attacking:
|
||||
model->holdingRightHand = true;
|
||||
#ifdef _WINDOWS64
|
||||
m_swingTime += GetSkinAnimDeltaScale();
|
||||
#else
|
||||
m_swingTime++;
|
||||
#endif
|
||||
if (m_swingTime >= (Player::SWING_DURATION * 3) )
|
||||
{
|
||||
m_swingTime = 0;
|
||||
|
|
@ -359,8 +413,16 @@ void UIControl_PlayerSkinPreview::render(EntityRenderer *renderer, double x, dou
|
|||
|
||||
#ifdef SKIN_PREVIEW_WALKING_ANIM
|
||||
m_walkAnimSpeedO = m_walkAnimSpeed;
|
||||
#ifdef _WINDOWS64
|
||||
{
|
||||
float animScale = GetSkinAnimDeltaScale();
|
||||
m_walkAnimSpeed += (0.1f - m_walkAnimSpeed) * 0.4f * animScale;
|
||||
m_walkAnimPos += m_walkAnimSpeed * animScale;
|
||||
}
|
||||
#else
|
||||
m_walkAnimSpeed += (0.1f - m_walkAnimSpeed) * 0.4f;
|
||||
m_walkAnimPos += m_walkAnimSpeed;
|
||||
#endif
|
||||
float ws = m_walkAnimSpeedO + (m_walkAnimSpeed - m_walkAnimSpeedO) * a;
|
||||
float wp = m_walkAnimPos - m_walkAnimSpeed * (1 - a);
|
||||
#else
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include "stdafx.h"
|
||||
#include "UI.h"
|
||||
#include "UIControl_SaveList.h"
|
||||
#include "..\..\..\Minecraft.World\ArabicShaping.h"
|
||||
|
||||
bool UIControl_SaveList::setupControl(UIScene *scene, IggyValuePath *parent, const string &controlName)
|
||||
{
|
||||
|
|
@ -69,12 +70,14 @@ void UIControl_SaveList::addItem(const string &label, const wstring &iconName, i
|
|||
|
||||
void UIControl_SaveList::addItem(const wstring &label, const wstring &iconName, int data)
|
||||
{
|
||||
wstring shaped = shapeArabicText(label);
|
||||
|
||||
IggyDataValue result;
|
||||
IggyDataValue value[3];
|
||||
|
||||
IggyStringUTF16 stringVal;
|
||||
stringVal.string = (IggyUTF16*)label.c_str();
|
||||
stringVal.length = static_cast<S32>(label.length());
|
||||
stringVal.string = (IggyUTF16*)shaped.c_str();
|
||||
stringVal.length = static_cast<S32>(shaped.length());
|
||||
value[0].type = IGGY_DATATYPE_string_UTF16;
|
||||
value[0].string16 = stringVal;
|
||||
|
||||
|
|
|
|||
|
|
@ -220,7 +220,7 @@ UIControl_TextInput::EDirectEditResult UIControl_TextInput::tickDirectEdit()
|
|||
sanitized.reserve(pasted.length());
|
||||
|
||||
for (wchar_t pc : pasted)
|
||||
{
|
||||
{
|
||||
if (pc >= 0x20) // Keep printable characters
|
||||
{
|
||||
if (m_iCharLimit > 0 && (m_editBuffer.length() + sanitized.length()) >= (size_t)m_iCharLimit)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
#include "../../EnderDragonRenderer.h"
|
||||
#include "../../MultiPlayerLocalPlayer.h"
|
||||
#include "UIFontData.h"
|
||||
#include "UIUnicodeBitmapFont.h"
|
||||
#include "UISplitScreenHelpers.h"
|
||||
#ifdef _WINDOWS64
|
||||
#include "../../Windows64/KeyboardMouseInput.h"
|
||||
|
|
@ -193,6 +194,7 @@ UIController::UIController()
|
|||
m_mcTTFFont = nullptr;
|
||||
m_moj7 = nullptr;
|
||||
m_moj11 = nullptr;
|
||||
m_unicodeBitmapFont = nullptr;
|
||||
|
||||
// 4J-JEV: It's important that these remain the same, unless updateCurrentLanguage is going to be called.
|
||||
m_eCurrentFont = m_eTargetFont = eFont_NotLoaded;
|
||||
|
|
@ -307,6 +309,14 @@ void UIController::postInit()
|
|||
IggySetAS3ExternalFunctionCallbackUTF16 ( &UIController::ExternalFunctionCallback, this );
|
||||
IggySetTextureSubstitutionCallbacks ( &UIController::TextureSubstitutionCreateCallback , &UIController::TextureSubstitutionDestroyCallback, this );
|
||||
|
||||
// Load a unicode bitmap font as Iggy's global fallback for characters not
|
||||
// covered by the Mojangles bitmap font (CJK, Thai, Arabic, Korean, etc.).
|
||||
// Uses the same glyph page PNGs as the legacy Font class, with matching
|
||||
// Mojangles metrics for correct vertical alignment.
|
||||
m_unicodeBitmapFont = new UIUnicodeBitmapFont("Mojangles_Unicode_Bitmap", SFontData::Mojangles_7);
|
||||
m_unicodeBitmapFont->registerFont();
|
||||
IggyFontSetFallbackFontUTF8("Mojangles_Unicode_Bitmap", -1, IGGY_FONTFLAG_none);
|
||||
|
||||
SetupFont();
|
||||
//
|
||||
loadSkins();
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using namespace std;
|
|||
|
||||
class UIAbstractBitmapFont;
|
||||
class UIBitmapFont;
|
||||
class UIUnicodeBitmapFont;
|
||||
class UITTFFont;
|
||||
class UIComponent_DebugUIConsole;
|
||||
class UIComponent_DebugUIMarketingGuide;
|
||||
|
|
@ -63,6 +64,7 @@ private:
|
|||
UIAbstractBitmapFont *m_mcBitmapFont;
|
||||
UITTFFont *m_mcTTFFont;
|
||||
UIBitmapFont *m_moj7, *m_moj11;
|
||||
UIUnicodeBitmapFont *m_unicodeBitmapFont;
|
||||
|
||||
std::mt19937 m_randomGenerator;
|
||||
std::uniform_real_distribution<float> m_randomDistribution;
|
||||
|
|
|
|||
|
|
@ -335,6 +335,11 @@ bool CFontData::unicodeIsWhitespace(unsigned int unicode)
|
|||
return false;
|
||||
}
|
||||
|
||||
bool CFontData::hasGlyph(unsigned int unicodepoint)
|
||||
{
|
||||
return m_unicodeMap.find(unicodepoint) != m_unicodeMap.end();
|
||||
}
|
||||
|
||||
void CFontData::moveCursor(unsigned char *&cursor, unsigned int dx, unsigned int dy)
|
||||
{
|
||||
cursor += (dy * m_sFontData->m_uiGlyphMapX) + dx;
|
||||
|
|
|
|||
|
|
@ -125,6 +125,9 @@ public:
|
|||
// Returns true if this unicodepoint is whitespace
|
||||
bool unicodeIsWhitespace(unsigned int unicodepoint);
|
||||
|
||||
// Returns true if this unicodepoint exists in the font's glyph map.
|
||||
bool hasGlyph(unsigned int unicodepoint);
|
||||
|
||||
private:
|
||||
|
||||
// Move a pointer in an image dx pixels right and dy pixels down, wrap around in either dimension leads to unknown behaviour.
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
#include "..\..\Minecraft.h"
|
||||
#ifdef _WINDOWS64
|
||||
#include "..\..\Windows64\Network\WinsockNetLayer.h"
|
||||
#include "..\..\..\Minecraft.World\DisconnectPacket.h"
|
||||
|
||||
static int ConnectingProgress_OnRejectedDialogOK(LPVOID, int iPad, const C4JStorage::EMessageResult)
|
||||
{
|
||||
|
|
@ -53,10 +52,10 @@ UIScene_ConnectingProgress::UIScene_ConnectingProgress(int iPad, void *_initData
|
|||
m_cancelFuncParam = param->cancelFuncParam;
|
||||
m_removeLocalPlayer = false;
|
||||
m_showingButton = false;
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
WinsockNetLayer::eJoinState initState = WinsockNetLayer::GetJoinState();
|
||||
m_asyncJoinActive = (initState != WinsockNetLayer::eJoinState_Idle && initState != WinsockNetLayer::eJoinState_Cancelled);
|
||||
m_asyncJoinActive = (initState == WinsockNetLayer::eJoinState_Connecting ||
|
||||
initState == WinsockNetLayer::eJoinState_Success);
|
||||
m_asyncJoinFailed = false;
|
||||
#endif
|
||||
}
|
||||
|
|
@ -72,12 +71,12 @@ void UIScene_ConnectingProgress::updateTooltips()
|
|||
#ifdef _WINDOWS64
|
||||
if (m_asyncJoinActive)
|
||||
{
|
||||
ui.SetTooltips( m_iPad, -1, IDS_TOOLTIPS_BACK);
|
||||
ui.SetTooltips(m_iPad, -1, IDS_TOOLTIPS_BACK);
|
||||
return;
|
||||
}
|
||||
if (m_asyncJoinFailed)
|
||||
{
|
||||
ui.SetTooltips( m_iPad, IDS_TOOLTIPS_SELECT, -1);
|
||||
ui.SetTooltips(m_iPad, IDS_TOOLTIPS_SELECT, -1);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
|
@ -94,78 +93,54 @@ void UIScene_ConnectingProgress::tick()
|
|||
if (m_asyncJoinActive)
|
||||
{
|
||||
WinsockNetLayer::eJoinState state = WinsockNetLayer::GetJoinState();
|
||||
if (state == WinsockNetLayer::eJoinState_Connecting)
|
||||
switch (state)
|
||||
{
|
||||
case WinsockNetLayer::eJoinState_Connecting:
|
||||
{
|
||||
// connecting.............
|
||||
int attempt = WinsockNetLayer::GetJoinAttempt();
|
||||
int maxAttempts = WinsockNetLayer::GetJoinMaxAttempts();
|
||||
char buf[128];
|
||||
if (attempt <= 1)
|
||||
sprintf_s(buf, "Connecting...");
|
||||
wchar_t buf[128];
|
||||
if (attempt > 1)
|
||||
swprintf_s(buf, L"Connecting... (attempt %d/%d)", attempt, maxAttempts);
|
||||
else
|
||||
sprintf_s(buf, "Connecting failed, trying again (%d/%d)", attempt, maxAttempts);
|
||||
wchar_t wbuf[128];
|
||||
mbstowcs(wbuf, buf, 128);
|
||||
m_labelTitle.setLabel(wstring(wbuf));
|
||||
swprintf_s(buf, L"Connecting...");
|
||||
m_labelTitle.setLabel(buf);
|
||||
break;
|
||||
}
|
||||
else if (state == WinsockNetLayer::eJoinState_Success)
|
||||
{
|
||||
case WinsockNetLayer::eJoinState_Success:
|
||||
m_asyncJoinActive = false;
|
||||
// go go go
|
||||
}
|
||||
else if (state == WinsockNetLayer::eJoinState_Cancelled)
|
||||
{
|
||||
// cancel
|
||||
m_labelTitle.setLabel(L"Joining world...");
|
||||
break;
|
||||
case WinsockNetLayer::eJoinState_Cancelled:
|
||||
m_asyncJoinActive = false;
|
||||
navigateBack();
|
||||
}
|
||||
else if (state == WinsockNetLayer::eJoinState_Rejected)
|
||||
break;
|
||||
case WinsockNetLayer::eJoinState_Rejected:
|
||||
{
|
||||
// server full and banned are passed differently compared to other disconnects it seems
|
||||
m_asyncJoinActive = false;
|
||||
DisconnectPacket::eDisconnectReason reason = WinsockNetLayer::GetJoinRejectReason();
|
||||
int exitReasonStringId;
|
||||
switch (reason)
|
||||
{
|
||||
case DisconnectPacket::eDisconnect_ServerFull:
|
||||
exitReasonStringId = IDS_DISCONNECTED_SERVER_FULL;
|
||||
break;
|
||||
case DisconnectPacket::eDisconnect_Banned:
|
||||
exitReasonStringId = IDS_DISCONNECTED_KICKED;
|
||||
break;
|
||||
default:
|
||||
exitReasonStringId = IDS_CONNECTION_LOST_SERVER;
|
||||
break;
|
||||
}
|
||||
int reasonStringId = IDS_CONNECTION_LOST_SERVER;
|
||||
if (reason == DisconnectPacket::eDisconnect_ServerFull)
|
||||
reasonStringId = IDS_DISCONNECTED_SERVER_FULL;
|
||||
else if (reason == DisconnectPacket::eDisconnect_Kicked)
|
||||
reasonStringId = IDS_DISCONNECTED_KICKED;
|
||||
|
||||
UINT uiIDA[1];
|
||||
uiIDA[0] = IDS_CONFIRM_OK;
|
||||
ui.RequestErrorMessage(IDS_CONNECTION_FAILED, exitReasonStringId, uiIDA, 1, ProfileManager.GetPrimaryPad(), ConnectingProgress_OnRejectedDialogOK, nullptr, nullptr);
|
||||
ui.RequestErrorMessage(IDS_CONNECTION_FAILED, reasonStringId, uiIDA, 1, m_iPad, ConnectingProgress_OnRejectedDialogOK, nullptr);
|
||||
break;
|
||||
}
|
||||
else if (state == WinsockNetLayer::eJoinState_Failed)
|
||||
{
|
||||
// FAIL
|
||||
case WinsockNetLayer::eJoinState_Failed:
|
||||
m_asyncJoinActive = false;
|
||||
m_asyncJoinFailed = true;
|
||||
|
||||
int maxAttempts = WinsockNetLayer::GetJoinMaxAttempts();
|
||||
char buf[256];
|
||||
sprintf_s(buf, "Failed to connect after %d attempts. The server may be unavailable.", maxAttempts);
|
||||
wchar_t wbuf[256];
|
||||
mbstowcs(wbuf, buf, 256);
|
||||
|
||||
// TIL that these exist
|
||||
// not going to use a actual popup due to it requiring messing with strings which can really mess things up
|
||||
// i dont trust myself with that
|
||||
// these need to be touched up later as teh button is a bit offset
|
||||
m_labelTitle.setLabel(L"Unable to connect to server");
|
||||
m_progressBar.setLabel(wstring(wbuf));
|
||||
m_progressBar.showBar(false);
|
||||
m_progressBar.setVisible(true);
|
||||
m_labelTitle.setLabel(app.GetString(IDS_CONNECTION_FAILED));
|
||||
m_buttonConfirm.setVisible(true);
|
||||
m_showingButton = true;
|
||||
m_controlTimer.setVisible(false);
|
||||
updateTooltips();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
@ -202,7 +177,6 @@ void UIScene_ConnectingProgress::handleGainFocus(bool navBack)
|
|||
void UIScene_ConnectingProgress::handleLoseFocus()
|
||||
{
|
||||
if (!m_runFailTimer) return;
|
||||
|
||||
int millisecsLeft = getTimer(0)->targetTime - System::currentTimeMillis();
|
||||
int millisecsTaken = getTimer(0)->duration - millisecsLeft;
|
||||
app.DebugPrintf("\n");
|
||||
|
|
@ -316,7 +290,6 @@ void UIScene_ConnectingProgress::handleInput(int iPad, int key, bool repeat, boo
|
|||
|
||||
switch(key)
|
||||
{
|
||||
// 4J-PB - Removed the option to cancel join - it didn't work anyway
|
||||
#ifdef _WINDOWS64
|
||||
case ACTION_MENU_CANCEL:
|
||||
if (pressed && m_asyncJoinActive)
|
||||
|
|
@ -325,9 +298,11 @@ void UIScene_ConnectingProgress::handleInput(int iPad, int key, bool repeat, boo
|
|||
WinsockNetLayer::CancelJoinGame();
|
||||
navigateBack();
|
||||
handled = true;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
// 4J-PB - Removed the option to cancel join - it didn't work anyway
|
||||
// case ACTION_MENU_CANCEL:
|
||||
// {
|
||||
// if(m_cancelFunc != nullptr)
|
||||
|
|
@ -374,8 +349,8 @@ void UIScene_ConnectingProgress::handlePress(F64 controlId, F64 childId)
|
|||
if (m_asyncJoinFailed)
|
||||
{
|
||||
navigateBack();
|
||||
break;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
if( m_iPad != ProfileManager.GetPrimaryPad() && g_NetworkManager.IsInSession() )
|
||||
{
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ private:
|
|||
bool m_showingButton;
|
||||
void (*m_cancelFunc)(LPVOID param);
|
||||
LPVOID m_cancelFuncParam;
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
bool m_asyncJoinActive;
|
||||
bool m_asyncJoinFailed;
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@
|
|||
#define GAME_CREATE_ONLINE_TIMER_ID 0
|
||||
#define GAME_CREATE_ONLINE_TIMER_TIME 100
|
||||
|
||||
static bool s_bHardcore = false; // 4J Added: tracks when difficulty slider is at Hardcore position (file-scope to avoid header layout changes)
|
||||
|
||||
int UIScene_CreateWorldMenu::m_iDifficultyTitleSettingA[4]=
|
||||
{
|
||||
IDS_DIFFICULTY_TITLE_PEACEFUL,
|
||||
|
|
@ -59,8 +61,9 @@ UIScene_CreateWorldMenu::UIScene_CreateWorldMenu(int iPad, void *initData, UILay
|
|||
|
||||
WCHAR TempString[256];
|
||||
swprintf( (WCHAR *)TempString, 256, L"%ls: %ls", app.GetString( IDS_SLIDER_DIFFICULTY ),app.GetString(m_iDifficultyTitleSettingA[app.GetGameSettings(m_iPad,eGameSetting_Difficulty)]));
|
||||
m_sliderDifficulty.init(TempString,eControl_Difficulty,0,3,app.GetGameSettings(m_iPad,eGameSetting_Difficulty));
|
||||
m_sliderDifficulty.init(TempString,eControl_Difficulty,0,4,app.GetGameSettings(m_iPad,eGameSetting_Difficulty));
|
||||
|
||||
s_bHardcore = false;
|
||||
m_MoreOptionsParams.bGenerateOptions=TRUE;
|
||||
m_MoreOptionsParams.bStructures=TRUE;
|
||||
m_MoreOptionsParams.bFlatWorld=FALSE;
|
||||
|
|
@ -458,6 +461,8 @@ void UIScene_CreateWorldMenu::handlePress(F64 controlId, F64 childId)
|
|||
}
|
||||
break;
|
||||
case eControl_GameModeToggle:
|
||||
if (s_bHardcore)
|
||||
break; // Hardcore mode locks game mode to Survival
|
||||
switch(m_iGameModeId)
|
||||
{
|
||||
case 0: // Creative
|
||||
|
|
@ -470,7 +475,7 @@ void UIScene_CreateWorldMenu::handlePress(F64 controlId, F64 childId)
|
|||
m_iGameModeId = GameType::ADVENTURE->getId();
|
||||
m_bGameModeCreative = false;
|
||||
break;
|
||||
case 2: // Survival
|
||||
case 2: // Survival
|
||||
m_buttonGamemode.setLabel(app.GetString(IDS_GAMEMODE_SURVIVAL));
|
||||
m_iGameModeId = GameType::SURVIVAL->getId();
|
||||
m_bGameModeCreative = false;
|
||||
|
|
@ -654,9 +659,22 @@ void UIScene_CreateWorldMenu::handleSliderMove(F64 sliderId, F64 currentValue)
|
|||
case eControl_Difficulty:
|
||||
m_sliderDifficulty.handleSliderMove(value);
|
||||
|
||||
app.SetGameSettings(m_iPad,eGameSetting_Difficulty,value);
|
||||
swprintf( (WCHAR *)TempString, 256, L"%ls: %ls", app.GetString( IDS_SLIDER_DIFFICULTY ),app.GetString(m_iDifficultyTitleSettingA[value]));
|
||||
// 4J Added: Difficulty value 4 = Hardcore (store actual difficulty as Hard, track hardcore separately)
|
||||
s_bHardcore = (value >= 4);
|
||||
app.SetGameSettings(m_iPad, eGameSetting_Difficulty, s_bHardcore ? 3 : value);
|
||||
if (value >= 4)
|
||||
swprintf( (WCHAR *)TempString, 256, L"%ls: %ls", app.GetString( IDS_SLIDER_DIFFICULTY ), L"Hardcore");
|
||||
else
|
||||
swprintf( (WCHAR *)TempString, 256, L"%ls: %ls", app.GetString( IDS_SLIDER_DIFFICULTY ),app.GetString(m_iDifficultyTitleSettingA[value]));
|
||||
m_sliderDifficulty.setLabel(TempString);
|
||||
|
||||
// Hardcore locks game mode to Survival
|
||||
if (s_bHardcore && m_iGameModeId != GameType::SURVIVAL->getId())
|
||||
{
|
||||
m_iGameModeId = GameType::SURVIVAL->getId();
|
||||
m_bGameModeCreative = false;
|
||||
m_buttonGamemode.setLabel(app.GetString(IDS_GAMEMODE_SURVIVAL));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -823,7 +841,7 @@ void UIScene_CreateWorldMenu::checkStateAndStartGame()
|
|||
// 4J Stu - This is a bit messy and is due to the library incorrectly returning false for IsSignedInLive if the npAvailability isn't SCE_OK
|
||||
UINT uiIDA[1];
|
||||
uiIDA[0]=IDS_OK;
|
||||
ui.RequestErrorMessage(IDS_ONLINE_SERVICE_TITLE, IDS_CONTENT_RESTRICTION, uiIDA, 1, iPadNotSignedInLive);
|
||||
ui.RequestAlertMessage(IDS_ONLINE_SERVICE_TITLE, IDS_CONTENT_RESTRICTION, uiIDA, 1, iPadNotSignedInLive);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -1113,6 +1131,10 @@ void UIScene_CreateWorldMenu::CreateGame(UIScene_CreateWorldMenu* pClass, DWORD
|
|||
StorageManager.ResetSaveData();
|
||||
// Make our next save default to the name of the level
|
||||
StorageManager.SetSaveTitle((wchar_t *)wWorldName.c_str());
|
||||
#ifdef _WINDOWS64
|
||||
// New world — save folder doesn't exist yet, clear for now (will be set after first autosave)
|
||||
app.SetCurrentSaveFolderName(L"");
|
||||
#endif
|
||||
|
||||
wstring wSeed;
|
||||
if(!pClass->m_MoreOptionsParams.seed.empty() )
|
||||
|
|
@ -1179,7 +1201,9 @@ void UIScene_CreateWorldMenu::CreateGame(UIScene_CreateWorldMenu* pClass, DWORD
|
|||
Minecraft *pMinecraft = Minecraft::GetInstance();
|
||||
pMinecraft->skins->selectTexturePackById(pClass->m_MoreOptionsParams.dwTexturePack);
|
||||
|
||||
app.SetGameHostOption(eGameHostOption_Difficulty,Minecraft::GetInstance()->options->difficulty);
|
||||
// 4J Added: If hardcore was selected on difficulty slider, set difficulty to Hard and enable hardcore flag
|
||||
app.SetGameHostOption(eGameHostOption_Difficulty, Minecraft::GetInstance()->options->difficulty);
|
||||
app.SetGameHostOption(eGameHostOption_Hardcore, s_bHardcore ? 1 : 0);
|
||||
app.SetGameHostOption(eGameHostOption_FriendsOfFriends,pClass->m_MoreOptionsParams.bAllowFriendsOfFriends);
|
||||
app.SetGameHostOption(eGameHostOption_Gamertags,app.GetGameSettings(pClass->m_iPad,eGameSetting_GamertagsVisible)?1:0);
|
||||
|
||||
|
|
|
|||
|
|
@ -2,25 +2,46 @@
|
|||
#include "UI.h"
|
||||
#include "UIScene_DeathMenu.h"
|
||||
#include "IUIScene_PauseMenu.h"
|
||||
|
||||
#include "../../Minecraft.h"
|
||||
#include "../../MultiPlayerLocalPlayer.h"
|
||||
#include "../../MultiPlayerLevel.h"
|
||||
#include "../../MinecraftServer.h"
|
||||
|
||||
#include "../../../Minecraft.World/net.minecraft.world.level.storage.h"
|
||||
|
||||
UIScene_DeathMenu::UIScene_DeathMenu(int iPad, void *initData, UILayer *parentLayer) : UIScene(iPad, parentLayer)
|
||||
{
|
||||
// Setup all the Iggy references we need for this scene
|
||||
initialiseMovie();
|
||||
|
||||
m_buttonRespawn.init(app.GetString(IDS_RESPAWN),eControl_Respawn);
|
||||
m_buttonExitGame.init(app.GetString(IDS_EXIT_GAME),eControl_ExitGame);
|
||||
|
||||
m_labelTitle.setLabel(app.GetString(IDS_YOU_DIED));
|
||||
|
||||
// 4J Added: In hardcore mode, disable respawn and show hardcore death message
|
||||
Minecraft *pMC = Minecraft::GetInstance();
|
||||
bool isHardcore = false;
|
||||
if (pMC != nullptr && pMC->level != nullptr)
|
||||
{
|
||||
isHardcore = pMC->level->getLevelData()->isHardcore();
|
||||
}
|
||||
|
||||
if (isHardcore)
|
||||
{
|
||||
m_buttonRespawn.init(app.GetString(IDS_HARDCORE_DEATH_MESSAGE), eControl_Respawn);
|
||||
m_buttonRespawn.setVisible(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_buttonRespawn.init(app.GetString(IDS_RESPAWN), eControl_Respawn);
|
||||
}
|
||||
|
||||
m_bIgnoreInput = false;
|
||||
|
||||
Minecraft *pMinecraft = Minecraft::GetInstance();
|
||||
if(pMinecraft != nullptr && pMinecraft->localgameModes[iPad] != nullptr )
|
||||
if(pMC != nullptr && pMC->localgameModes[iPad] != nullptr )
|
||||
{
|
||||
TutorialMode *gameMode = static_cast<TutorialMode *>(pMinecraft->localgameModes[iPad]);
|
||||
TutorialMode *gameMode = static_cast<TutorialMode *>(pMC->localgameModes[iPad]);
|
||||
|
||||
// This just allows it to be shown
|
||||
gameMode->getTutorial()->showTutorialPopup(false);
|
||||
|
|
@ -84,8 +105,16 @@ void UIScene_DeathMenu::handlePress(F64 controlId, F64 childId)
|
|||
switch(static_cast<int>(controlId))
|
||||
{
|
||||
case eControl_Respawn:
|
||||
m_bIgnoreInput = true;
|
||||
app.SetAction(m_iPad,eAppAction_Respawn);
|
||||
{
|
||||
// 4J Added: Safeguard - don't respawn in hardcore mode
|
||||
Minecraft *pMC = Minecraft::GetInstance();
|
||||
if (pMC != nullptr && pMC->level != nullptr && pMC->level->getLevelData()->isHardcore())
|
||||
{
|
||||
break;
|
||||
}
|
||||
m_bIgnoreInput = true;
|
||||
app.SetAction(m_iPad,eAppAction_Respawn);
|
||||
}
|
||||
#ifdef _DURANGO
|
||||
//InputManager.SetEnabledGtcButtons(_360_GTC_MENU|_360_GTC_PAUSE|_360_GTC_VIEW);
|
||||
#endif
|
||||
|
|
@ -109,7 +138,16 @@ void UIScene_DeathMenu::handlePress(F64 controlId, F64 childId)
|
|||
playTime = static_cast<int>(pMinecraft->localplayers[m_iPad]->getSessionTimer());
|
||||
}
|
||||
TelemetryManager->RecordLevelExit(m_iPad, eSen_LevelExitStatus_Failed);
|
||||
|
||||
|
||||
// 4J Added: Hardcore mode — skip save dialog, exit without saving, delete world
|
||||
if (pMinecraft->level != nullptr && pMinecraft->level->getLevelData()->isHardcore() && g_NetworkManager.IsHost())
|
||||
{
|
||||
MinecraftServer::getInstance()->setSaveOnExit(false);
|
||||
MinecraftServer::getInstance()->setDeleteWorldOnExit(true);
|
||||
app.SetAction(m_iPad, eAppAction_ExitWorld);
|
||||
break;
|
||||
}
|
||||
|
||||
#if defined (_XBOX_ONE) || defined(__ORBIS__)
|
||||
if(g_NetworkManager.IsHost() && StorageManager.GetSaveDisabled())
|
||||
{
|
||||
|
|
|
|||
|
|
@ -50,13 +50,18 @@ UIScene_EndPoem::UIScene_EndPoem(int iPad, void *initData, UILayer *parentLayer)
|
|||
Minecraft *pMinecraft = Minecraft::GetInstance();
|
||||
|
||||
wstring playerName = L"";
|
||||
if(pMinecraft->localplayers[ui.GetWinUserIndex()] != nullptr)
|
||||
unsigned int winIdx = ui.GetWinUserIndex();
|
||||
if(winIdx < XUSER_MAX_COUNT && pMinecraft->localplayers[winIdx] != nullptr)
|
||||
{
|
||||
playerName = escapeXML( pMinecraft->localplayers[ui.GetWinUserIndex()]->getDisplayName() );
|
||||
playerName = escapeXML( pMinecraft->localplayers[winIdx]->getDisplayName() );
|
||||
}
|
||||
else if(pMinecraft->localplayers[ProfileManager.GetPrimaryPad()] != nullptr)
|
||||
{
|
||||
playerName = escapeXML( pMinecraft->localplayers[ProfileManager.GetPrimaryPad()]->getDisplayName() );
|
||||
}
|
||||
else
|
||||
{
|
||||
playerName = escapeXML( pMinecraft->localplayers[ProfileManager.GetPrimaryPad()]->getDisplayName() );
|
||||
playerName = L"Player";
|
||||
}
|
||||
noNoiseString = replaceAll(noNoiseString,L"{*PLAYER*}",playerName);
|
||||
|
||||
|
|
|
|||
|
|
@ -664,6 +664,23 @@ void UIScene_HUD::SetHorseJumpBarProgress(float progress)
|
|||
}
|
||||
}
|
||||
|
||||
void UIScene_HUD::SetHardcoreMode(bool bHardcore)
|
||||
{
|
||||
IggyDataValue result;
|
||||
IggyDataValue value[1];
|
||||
value[0].type = IGGY_DATATYPE_boolean;
|
||||
value[0].boolval = bHardcore;
|
||||
IggyResult out = IggyPlayerCallMethodRS ( getMovie() , &result, IggyPlayerRootPath( getMovie() ), m_funcSetHardcore , 1 , value );
|
||||
|
||||
// When hardcore state changes, invalidate SetHealth's dirty check
|
||||
// so hearts are redrawn with the correct frame set on the next tick
|
||||
if(bHardcore != m_lastHealthHardcore)
|
||||
{
|
||||
m_lastHealthHardcore = bHardcore;
|
||||
m_lastMaxHealth = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void UIScene_HUD::SetHealthAbsorb(int healthAbsorb)
|
||||
{
|
||||
if(m_iCurrentHealthAbsorb != healthAbsorb)
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ protected:
|
|||
IggyName m_funcRepositionHud, m_funcSetDisplayName, m_funcSetTooltipsEnabled;
|
||||
IggyName m_funcSetRidingHorse, m_funcSetHorseHealth, m_funcSetHorseJumpBarProgress;
|
||||
IggyName m_funcSetHealthAbsorb;
|
||||
IggyName m_funcSetHardcore;
|
||||
UI_BEGIN_MAP_ELEMENTS_AND_NAMES(UIScene)
|
||||
UI_MAP_ELEMENT(m_labelChatText[0],"Label1")
|
||||
UI_MAP_ELEMENT(m_labelChatText[1],"Label2")
|
||||
|
|
@ -89,6 +90,7 @@ protected:
|
|||
UI_MAP_NAME(m_funcSetHorseJumpBarProgress, L"SetHorseJumpBarProgress")
|
||||
|
||||
UI_MAP_NAME(m_funcSetHealthAbsorb, L"SetHealthAbsorb")
|
||||
UI_MAP_NAME(m_funcSetHardcore, L"SetHardcore")
|
||||
UI_END_MAP_ELEMENTS_AND_NAMES()
|
||||
|
||||
public:
|
||||
|
|
@ -159,6 +161,8 @@ private:
|
|||
|
||||
void SetHealthAbsorb(int healthAbsorb);
|
||||
|
||||
void SetHardcoreMode(bool bHardcore);
|
||||
|
||||
public:
|
||||
void SetSelectedLabel(const wstring &label);
|
||||
void ShowDisplayName(bool show);
|
||||
|
|
|
|||
|
|
@ -1,13 +1,19 @@
|
|||
#include "stdafx.h"
|
||||
#include "UI.h"
|
||||
#include "UIScene_JoinMenu.h"
|
||||
|
||||
#include "../../Minecraft.h"
|
||||
#include "../../TexturePackRepository.h"
|
||||
#include "../../Options.h"
|
||||
#include "../../MinecraftServer.h"
|
||||
|
||||
#include "../../../Minecraft.World/net.minecraft.world.level.h"
|
||||
#include "../../../Minecraft.World/net.minecraft.world.h"
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
#include "../../Windows64/Network/WinsockNetLayer.h"
|
||||
#endif
|
||||
|
||||
#define UPDATE_PLAYERS_TIMER_ID 0
|
||||
#define UPDATE_PLAYERS_TIMER_TIME 30000
|
||||
|
||||
|
|
@ -587,10 +593,9 @@ void UIScene_JoinMenu::JoinGame(UIScene_JoinMenu* pClass)
|
|||
if (result == CGameNetworkManager::JOINGAME_PENDING)
|
||||
{
|
||||
pClass->m_bIgnoreInput = false;
|
||||
|
||||
ConnectionProgressParams *param = new ConnectionProgressParams();
|
||||
param->iPad = ProfileManager.GetPrimaryPad();
|
||||
param->stringId = -1;
|
||||
param->stringId = IDS_PROGRESS_CONNECTING;
|
||||
param->showTooltips = true;
|
||||
param->setFailTimer = false;
|
||||
param->timerTime = 0;
|
||||
|
|
@ -655,16 +660,18 @@ void UIScene_JoinMenu::JoinGame(UIScene_JoinMenu* pClass)
|
|||
|
||||
if( exitReasonStringId == -1 )
|
||||
{
|
||||
ui.NavigateBack(pClass->m_iPad);
|
||||
// No specific disconnect reason was set — the server was likely
|
||||
// unreachable. Show a "Connection Failed" dialog instead of
|
||||
// silently navigating back so the user knows what happened.
|
||||
exitReasonStringId = IDS_CONNECTION_LOST_SERVER;
|
||||
}
|
||||
else
|
||||
|
||||
{
|
||||
UINT uiIDA[1];
|
||||
uiIDA[0]=IDS_CONFIRM_OK;
|
||||
ui.RequestErrorMessage( IDS_CONNECTION_FAILED, exitReasonStringId, uiIDA,1,ProfileManager.GetPrimaryPad());
|
||||
exitReasonStringId = -1;
|
||||
|
||||
ui.NavigateToHomeMenu();
|
||||
pClass->m_bIgnoreInput = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,12 +24,13 @@
|
|||
#define CHECKFORAVAILABLETEXTUREPACKS_TIMER_TIME 50
|
||||
#endif
|
||||
|
||||
int UIScene_LoadMenu::m_iDifficultyTitleSettingA[4]=
|
||||
int UIScene_LoadMenu::m_iDifficultyTitleSettingA[5]=
|
||||
{
|
||||
IDS_DIFFICULTY_TITLE_PEACEFUL,
|
||||
IDS_DIFFICULTY_TITLE_EASY,
|
||||
IDS_DIFFICULTY_TITLE_NORMAL,
|
||||
IDS_DIFFICULTY_TITLE_HARD
|
||||
IDS_DIFFICULTY_TITLE_HARD,
|
||||
IDS_GAMEMODE_HARDCORE
|
||||
};
|
||||
|
||||
int UIScene_LoadMenu::LoadSaveDataThumbnailReturned(LPVOID lpParam,PBYTE pbThumbnail,DWORD dwThumbnailBytes)
|
||||
|
|
@ -110,6 +111,7 @@ UIScene_LoadMenu::UIScene_LoadMenu(int iPad, void *initData, UILayer *parentLaye
|
|||
m_bThumbnailGetFailed = false;
|
||||
m_seed = 0;
|
||||
m_bIsCorrupt = false;
|
||||
m_bHardcore = false;
|
||||
|
||||
m_bMultiplayerAllowed = ProfileManager.IsSignedInLive( m_iPad ) && ProfileManager.AllowedToPlayMultiplayer(m_iPad);
|
||||
// 4J-PB - read the settings for the online flag. We'll only save this setting if the user changed it.
|
||||
|
|
@ -253,10 +255,34 @@ UIScene_LoadMenu::UIScene_LoadMenu(int iPad, void *initData, UILayer *parentLaye
|
|||
{
|
||||
wchar_t wSaveName[128];
|
||||
ZeroMemory(wSaveName, sizeof(wSaveName));
|
||||
mbstowcs(wSaveName, params->saveDetails->UTF8SaveName, 127);
|
||||
MultiByteToWideChar(CP_UTF8, 0, params->saveDetails->UTF8SaveName, -1, wSaveName, 127);
|
||||
m_levelName = wstring(wSaveName);
|
||||
m_labelGameName.init(m_levelName);
|
||||
}
|
||||
if (params->saveDetails != nullptr)
|
||||
{
|
||||
// Set thumbnail name from save filename (needed for texture display in tick)
|
||||
wchar_t wFilename[MAX_SAVEFILENAME_LENGTH];
|
||||
ZeroMemory(wFilename, sizeof(wFilename));
|
||||
mbstowcs(wFilename, params->saveDetails->UTF8SaveFilename, MAX_SAVEFILENAME_LENGTH - 1);
|
||||
m_thumbnailName = wFilename;
|
||||
|
||||
if (params->saveDetails->pbThumbnailData && params->saveDetails->dwThumbnailSize > 0)
|
||||
{
|
||||
m_pbThumbnailData = params->saveDetails->pbThumbnailData;
|
||||
m_uiThumbnailSize = params->saveDetails->dwThumbnailSize;
|
||||
m_bSaveThumbnailReady = true;
|
||||
m_bRetrievingSaveThumbnail = false;
|
||||
}
|
||||
|
||||
m_bHardcore = params->saveDetails->isHardcore;
|
||||
if (m_bHardcore)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
@ -546,6 +572,19 @@ void UIScene_LoadMenu::tick()
|
|||
{
|
||||
m_MoreOptionsParams.bAllowFriendsOfFriends = TRUE;
|
||||
}
|
||||
|
||||
m_bHardcore = app.GetGameHostOption(uiHostOptions, eGameHostOption_Hardcore) > 0;
|
||||
if (m_bHardcore)
|
||||
{
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
Minecraft *pMinecraft = Minecraft::GetInstance();
|
||||
|
|
@ -699,6 +738,8 @@ void UIScene_LoadMenu::handlePress(F64 controlId, F64 childId)
|
|||
switch(static_cast<int>(controlId))
|
||||
{
|
||||
case eControl_GameMode:
|
||||
if (m_bHardcore)
|
||||
break; // Hardcore mode locks game mode to Survival
|
||||
switch(m_iGameModeId)
|
||||
{
|
||||
case 0: // Survival
|
||||
|
|
@ -950,10 +991,15 @@ void UIScene_LoadMenu::handleSliderMove(F64 sliderId, F64 currentValue)
|
|||
switch(static_cast<int>(sliderId))
|
||||
{
|
||||
case eControl_Difficulty:
|
||||
if (m_bHardcore)
|
||||
{
|
||||
m_sliderDifficulty.handleSliderMove(4);
|
||||
break;
|
||||
}
|
||||
m_sliderDifficulty.handleSliderMove(value);
|
||||
|
||||
app.SetGameSettings(m_iPad,eGameSetting_Difficulty,value);
|
||||
swprintf( (WCHAR *)TempString, 256, L"%ls: %ls", app.GetString( IDS_SLIDER_DIFFICULTY ),app.GetString(m_iDifficultyTitleSettingA[value]));
|
||||
swprintf( (WCHAR *)TempString, 256, L"%ls: %ls", app.GetString( IDS_SLIDER_DIFFICULTY ),app.GetString(m_iDifficultyTitleSettingA[value]));
|
||||
m_sliderDifficulty.setLabel(TempString);
|
||||
break;
|
||||
}
|
||||
|
|
@ -1167,7 +1213,7 @@ void UIScene_LoadMenu::LaunchGame(void)
|
|||
#if TO_BE_IMPLEMENTED
|
||||
if(eLoadStatus==C4JStorage::ELoadGame_DeviceRemoved)
|
||||
{
|
||||
// disable saving
|
||||
// disable saving
|
||||
StorageManager.SetSaveDisabled(true);
|
||||
StorageManager.SetSaveDeviceSelected(m_iPad,false);
|
||||
UINT uiIDA[1];
|
||||
|
|
@ -1580,6 +1626,24 @@ void UIScene_LoadMenu::StartGameFromSave(UIScene_LoadMenu* pClass, DWORD dwLocal
|
|||
|
||||
PSAVE_DETAILS pSaveDetails=StorageManager.ReturnSavesInfo();
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
// 4J Added: Store save folder name for potential hardcore world deletion
|
||||
app.DebugPrintf("StartGameFromSave: pSaveDetails=%p, levelGen=%p, saveInfoIndex=%d\n", pSaveDetails, pClass->m_levelGen, pClass->m_iSaveGameInfoIndex);
|
||||
if (pSaveDetails != nullptr && pClass->m_levelGen == nullptr)
|
||||
{
|
||||
app.DebugPrintf("StartGameFromSave: UTF8SaveFilename='%s'\n", pSaveDetails->SaveInfoA[(int)pClass->m_iSaveGameInfoIndex].UTF8SaveFilename);
|
||||
wchar_t wFolder[MAX_SAVEFILENAME_LENGTH] = {};
|
||||
mbstowcs(wFolder, pSaveDetails->SaveInfoA[(int)pClass->m_iSaveGameInfoIndex].UTF8SaveFilename, MAX_SAVEFILENAME_LENGTH - 1);
|
||||
app.SetCurrentSaveFolderName(wFolder);
|
||||
app.DebugPrintf("StartGameFromSave: stored folder name '%ls'\n", wFolder);
|
||||
}
|
||||
else
|
||||
{
|
||||
app.DebugPrintf("StartGameFromSave: no save details or is levelGen, clearing folder name\n");
|
||||
app.SetCurrentSaveFolderName(L"");
|
||||
}
|
||||
#endif
|
||||
|
||||
NetworkGameInitData *param = new NetworkGameInitData();
|
||||
param->seed = pClass->m_seed;
|
||||
param->saveData = nullptr;
|
||||
|
|
@ -1612,6 +1676,7 @@ void UIScene_LoadMenu::StartGameFromSave(UIScene_LoadMenu* pClass, DWORD dwLocal
|
|||
app.SetGameHostOption(eGameHostOption_DoTileDrops, pClass->m_MoreOptionsParams.bDoTileDrops);
|
||||
app.SetGameHostOption(eGameHostOption_NaturalRegeneration, pClass->m_MoreOptionsParams.bNaturalRegeneration);
|
||||
app.SetGameHostOption(eGameHostOption_DoDaylightCycle, pClass->m_MoreOptionsParams.bDoDaylightCycle);
|
||||
app.SetGameHostOption(eGameHostOption_Hardcore, pClass->m_bHardcore ? 1 : 0);
|
||||
|
||||
#ifdef _LARGE_WORLDS
|
||||
app.SetGameHostOption(eGameHostOption_WorldSize, pClass->m_MoreOptionsParams.worldSize+1 ); // 0 is GAME_HOST_OPTION_WORLDSIZE_UNKNOWN
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ private:
|
|||
eControl_OnlineGame,
|
||||
};
|
||||
|
||||
static int m_iDifficultyTitleSettingA[4];
|
||||
static int m_iDifficultyTitleSettingA[5];
|
||||
|
||||
UIControl m_controlMainPanel;
|
||||
UIControl_Label m_labelGameName, m_labelSeed, m_labelCreatedMode;
|
||||
|
|
@ -71,6 +71,7 @@ private:
|
|||
wstring m_thumbnailName;
|
||||
|
||||
bool m_bRebuildTouchBoxes;
|
||||
bool m_bHardcore;
|
||||
public:
|
||||
UIScene_LoadMenu(int iPad, void *initData, UILayer *parentLayer);
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
#include "../../../Minecraft.World/NbtIo.h"
|
||||
#include "../../../Minecraft.World/compression.h"
|
||||
|
||||
static wstring ReadLevelNameFromSaveFile(const wstring& filePath)
|
||||
static wstring ReadLevelNameFromSaveFile(const wstring& filePath, bool *outHardcore = nullptr)
|
||||
{
|
||||
// Check for a worldname.txt sidecar written by the rename feature first
|
||||
size_t slashPos = filePath.rfind(L'\\');
|
||||
|
|
@ -49,7 +49,7 @@ static wstring ReadLevelNameFromSaveFile(const wstring& filePath)
|
|||
if (len > 0)
|
||||
{
|
||||
wchar_t wbuf[128] = {};
|
||||
mbstowcs(wbuf, buf, 127);
|
||||
MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, 127);
|
||||
return wstring(wbuf);
|
||||
}
|
||||
}
|
||||
|
|
@ -124,7 +124,11 @@ static wstring ReadLevelNameFromSaveFile(const wstring& filePath)
|
|||
{
|
||||
CompoundTag *dataTag = root->getCompound(L"Data");
|
||||
if (dataTag != nullptr)
|
||||
{
|
||||
result = dataTag->getString(L"LevelName");
|
||||
if (outHardcore)
|
||||
*outHardcore = dataTag->getBoolean(L"hardcore");
|
||||
}
|
||||
delete root;
|
||||
}
|
||||
}
|
||||
|
|
@ -633,6 +637,11 @@ void UIScene_LoadOrJoinMenu::handleGainFocus(bool navBack)
|
|||
|
||||
if( m_bMultiplayerAllowed )
|
||||
{
|
||||
#ifdef _WINDOWS64
|
||||
// Refresh the games list immediately so that any server
|
||||
// edits/deletions made in the JoinMenu are visible now.
|
||||
UpdateGamesList();
|
||||
#endif
|
||||
#if TO_BE_IMPLEMENTED
|
||||
HXUICLASS hClassFullscreenProgress = XuiFindClass( L"CScene_FullscreenProgress" );
|
||||
HXUICLASS hClassConnectingProgress = XuiFindClass( L"CScene_ConnectingProgress" );
|
||||
|
|
@ -788,13 +797,17 @@ void UIScene_LoadOrJoinMenu::tick()
|
|||
#else
|
||||
#ifdef _WINDOWS64
|
||||
{
|
||||
wstring levelName = ReadLevelNameFromSaveFile(filePath);
|
||||
|
||||
bool saveHardcore = false;
|
||||
wstring levelName = ReadLevelNameFromSaveFile(filePath, &saveHardcore);
|
||||
m_saveDetails[i].isHardcore = saveHardcore;
|
||||
if (!levelName.empty())
|
||||
{
|
||||
m_buttonListSaves.addItem(levelName, wstring(L""));
|
||||
wcstombs(m_saveDetails[i].UTF8SaveName, levelName.c_str(), 127);
|
||||
m_saveDetails[i].UTF8SaveName[127] = '\0';
|
||||
{
|
||||
int n = WideCharToMultiByte(CP_UTF8, 0, levelName.c_str(), -1, m_saveDetails[i].UTF8SaveName, 127, nullptr, nullptr);
|
||||
if (n <= 0) m_saveDetails[i].UTF8SaveName[0] = '\0';
|
||||
m_saveDetails[i].UTF8SaveName[127] = '\0';
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -1439,9 +1452,9 @@ int UIScene_LoadOrJoinMenu::KeyboardCompleteWorldNameCallback(LPVOID lpParam,boo
|
|||
for (int k = 0; k < 127 && ui16Text[k]; k++)
|
||||
wNewName[k] = static_cast<wchar_t>(ui16Text[k]);
|
||||
|
||||
// Convert to narrow for storage and in-memory update
|
||||
char narrowName[128] = {};
|
||||
wcstombs(narrowName, wNewName, 127);
|
||||
// Convert to narrow for storage and in-memory update (UTF-8 to preserve Unicode)
|
||||
char narrowName[256] = {};
|
||||
WideCharToMultiByte(CP_UTF8, 0, wNewName, -1, narrowName, 255, nullptr, nullptr);
|
||||
|
||||
// Build the sidecar path: Windows64\GameHDD\{folder}\worldname.txt
|
||||
wchar_t wFilename[MAX_SAVEFILENAME_LENGTH] = {};
|
||||
|
|
@ -1457,7 +1470,7 @@ int UIScene_LoadOrJoinMenu::KeyboardCompleteWorldNameCallback(LPVOID lpParam,boo
|
|||
|
||||
// Update the in-memory display name so the list reflects it immediately
|
||||
strncpy_s(pClass->m_saveDetails[listPos].UTF8SaveName, narrowName, 127);
|
||||
pClass->m_saveDetails[listPos].UTF8SaveName[127] = '\0';
|
||||
pClass->m_saveDetails[listPos].UTF8SaveName[127] = '\0'; // UTF8SaveName is still 128 bytes; narrowName fits as Arabic is <=2 bytes/char in UTF-8
|
||||
|
||||
// Reuse the existing callback to trigger the list repopulate
|
||||
UIScene_LoadOrJoinMenu::RenameSaveDataReturned(pClass, true);
|
||||
|
|
@ -2525,7 +2538,7 @@ int UIScene_LoadOrJoinMenu::SaveOptionsDialogReturned(void *pParam,int iPad,C4JS
|
|||
{
|
||||
wchar_t wSaveName[128];
|
||||
ZeroMemory(wSaveName, 128 * sizeof(wchar_t));
|
||||
mbstowcs_s(nullptr, wSaveName, 128, pClass->m_saveDetails[pClass->m_iSaveListIndex - pClass->m_iDefaultButtonsC].UTF8SaveName, _TRUNCATE);
|
||||
MultiByteToWideChar(CP_UTF8, 0, pClass->m_saveDetails[pClass->m_iSaveListIndex - pClass->m_iDefaultButtonsC].UTF8SaveName, -1, wSaveName, 127);
|
||||
UIKeyboardInitData kbData;
|
||||
kbData.title = app.GetString(IDS_RENAME_WORLD_TITLE);
|
||||
kbData.defaultText = wSaveName;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,11 @@
|
|||
#include "../../Options.h"
|
||||
#include "../../GameRenderer.h"
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
extern bool g_bVSync;
|
||||
extern void SetExclusiveFullscreen(bool enabled);
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr int FOV_MIN = 70;
|
||||
|
|
@ -62,12 +67,14 @@ UIScene_SettingsGraphicsMenu::UIScene_SettingsGraphicsMenu(int iPad, void *initD
|
|||
m_checkboxClouds.init(app.GetString(IDS_CHECKBOX_RENDER_CLOUDS),eControl_Clouds,(app.GetGameSettings(m_iPad,eGameSetting_Clouds)!=0));
|
||||
m_checkboxBedrockFog.init(app.GetString(IDS_CHECKBOX_RENDER_BEDROCKFOG),eControl_BedrockFog,(app.GetGameSettings(m_iPad,eGameSetting_BedrockFog)!=0));
|
||||
m_checkboxCustomSkinAnim.init(app.GetString(IDS_CHECKBOX_CUSTOM_SKIN_ANIM),eControl_CustomSkinAnim,(app.GetGameSettings(m_iPad,eGameSetting_CustomSkinAnim)!=0));
|
||||
m_checkboxVSync.init(L"VSync",eControl_VSync,(app.GetGameSettings(m_iPad,eGameSetting_VSync)!=0));
|
||||
m_checkboxExclusiveFullscreen.init(L"Fullscreen",eControl_ExclusiveFullscreen,(app.GetGameSettings(m_iPad,eGameSetting_ExclusiveFullscreen)!=0));
|
||||
|
||||
|
||||
|
||||
WCHAR TempString[256];
|
||||
|
||||
swprintf(TempString, 256, L"Render Distance: %d",app.GetGameSettings(m_iPad,eGameSetting_RenderDistance));
|
||||
m_sliderRenderDistance.init(TempString,eControl_RenderDistance,0,5,DistanceToLevel(app.GetGameSettings(m_iPad,eGameSetting_RenderDistance)));
|
||||
m_sliderRenderDistance.init(TempString,eControl_RenderDistance,0,3,DistanceToLevel(app.GetGameSettings(m_iPad,eGameSetting_RenderDistance)));
|
||||
|
||||
swprintf( TempString, 256, L"%ls: %d%%", app.GetString( IDS_SLIDER_GAMMA ),app.GetGameSettings(m_iPad,eGameSetting_Gamma));
|
||||
m_sliderGamma.init(TempString,eControl_Gamma,0,100,app.GetGameSettings(m_iPad,eGameSetting_Gamma));
|
||||
|
|
@ -82,28 +89,50 @@ UIScene_SettingsGraphicsMenu::UIScene_SettingsGraphicsMenu(int iPad, void *initD
|
|||
|
||||
doHorizontalResizeCheck();
|
||||
|
||||
#ifndef _WINDOWS64
|
||||
// VSync and Exclusive Fullscreen are only available on PC
|
||||
removeControl(&m_checkboxVSync, true);
|
||||
removeControl(&m_checkboxExclusiveFullscreen, true);
|
||||
#else
|
||||
// The SWF's original focus chain skips VSync, Fullscreen, and RenderDistance
|
||||
// (CustomSkinAnim -> Gamma). Rewire the navigation so all controls are reachable:
|
||||
// CustomSkinAnim -> VSync -> Fullscreen -> RenderDistance -> Gamma
|
||||
{
|
||||
IggyName navDown = registerFastName(L"m_objNavDown");
|
||||
IggyName navUp = registerFastName(L"m_objNavUp");
|
||||
|
||||
IggyValueSetStringUTF8RS(m_checkboxCustomSkinAnim.getIggyValuePath(), navDown, nullptr, "VSync", -1);
|
||||
|
||||
IggyValueSetStringUTF8RS(m_checkboxVSync.getIggyValuePath(), navUp, nullptr, "CustomSkinAnim", -1);
|
||||
IggyValueSetStringUTF8RS(m_checkboxVSync.getIggyValuePath(), navDown, nullptr, "ExclusiveFullscreen", -1);
|
||||
|
||||
IggyValueSetStringUTF8RS(m_checkboxExclusiveFullscreen.getIggyValuePath(), navUp, nullptr, "VSync", -1);
|
||||
IggyValueSetStringUTF8RS(m_checkboxExclusiveFullscreen.getIggyValuePath(), navDown, nullptr, "RenderDistance", -1);
|
||||
|
||||
IggyValueSetStringUTF8RS(m_sliderRenderDistance.getIggyValuePath(), navUp, nullptr, "ExclusiveFullscreen", -1);
|
||||
}
|
||||
#endif
|
||||
|
||||
const bool bInGame=(Minecraft::GetInstance()->level!=nullptr);
|
||||
const bool bIsPrimaryPad=(ProfileManager.GetPrimaryPad()==m_iPad);
|
||||
// if we're not in the game, we need to use basescene 0
|
||||
// if we're not in the game, we need to use basescene 0
|
||||
if(bInGame)
|
||||
{
|
||||
// If the game has started, then you need to be the host to change the in-game gamertags
|
||||
#ifndef _WINDOWS64
|
||||
// Console splitscreen: non-host and non-primary players can't change world-level settings
|
||||
if(bIsPrimaryPad)
|
||||
{
|
||||
// we are the primary player on this machine, but not the game host
|
||||
// are we the game host? If not, we need to remove the bedrockfog setting
|
||||
{
|
||||
if(!g_NetworkManager.IsHost())
|
||||
{
|
||||
// hide the in-game bedrock fog setting
|
||||
removeControl(&m_checkboxBedrockFog, true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// We shouldn't have the bedrock fog option, or the m_CustomSkinAnim option
|
||||
removeControl(&m_checkboxBedrockFog, true);
|
||||
removeControl(&m_checkboxCustomSkinAnim, true);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if(app.GetLocalPlayerCount()>1)
|
||||
|
|
@ -165,6 +194,12 @@ void UIScene_SettingsGraphicsMenu::handleInput(int iPad, int key, bool repeat, b
|
|||
app.SetGameSettings(m_iPad,eGameSetting_Clouds,m_checkboxClouds.IsChecked()?1:0);
|
||||
app.SetGameSettings(m_iPad,eGameSetting_BedrockFog,m_checkboxBedrockFog.IsChecked()?1:0);
|
||||
app.SetGameSettings(m_iPad,eGameSetting_CustomSkinAnim,m_checkboxCustomSkinAnim.IsChecked()?1:0);
|
||||
app.SetGameSettings(m_iPad,eGameSetting_VSync,m_checkboxVSync.IsChecked()?1:0);
|
||||
app.SetGameSettings(m_iPad,eGameSetting_ExclusiveFullscreen,m_checkboxExclusiveFullscreen.IsChecked()?1:0);
|
||||
#ifdef _WINDOWS64
|
||||
g_bVSync = m_checkboxVSync.IsChecked();
|
||||
SetExclusiveFullscreen(m_checkboxExclusiveFullscreen.IsChecked());
|
||||
#endif
|
||||
|
||||
navigateBack();
|
||||
handled = true;
|
||||
|
|
|
|||
|
|
@ -12,18 +12,22 @@ private:
|
|||
eControl_Clouds,
|
||||
eControl_BedrockFog,
|
||||
eControl_CustomSkinAnim,
|
||||
eControl_VSync,
|
||||
eControl_ExclusiveFullscreen,
|
||||
eControl_RenderDistance,
|
||||
eControl_Gamma,
|
||||
eControl_FOV,
|
||||
eControl_InterfaceOpacity
|
||||
};
|
||||
|
||||
UIControl_CheckBox m_checkboxClouds, m_checkboxBedrockFog, m_checkboxCustomSkinAnim; // Checkboxes
|
||||
UIControl_CheckBox m_checkboxClouds, m_checkboxBedrockFog, m_checkboxCustomSkinAnim, m_checkboxVSync, m_checkboxExclusiveFullscreen; // Checkboxes
|
||||
UIControl_Slider m_sliderRenderDistance, m_sliderGamma, m_sliderFOV, m_sliderInterfaceOpacity; // Sliders
|
||||
UI_BEGIN_MAP_ELEMENTS_AND_NAMES(UIScene)
|
||||
UI_MAP_ELEMENT( m_checkboxClouds, "Clouds")
|
||||
UI_MAP_ELEMENT( m_checkboxBedrockFog, "BedrockFog")
|
||||
UI_MAP_ELEMENT( m_checkboxCustomSkinAnim, "CustomSkinAnim")
|
||||
UI_MAP_ELEMENT( m_checkboxVSync, "VSync")
|
||||
UI_MAP_ELEMENT( m_checkboxExclusiveFullscreen, "ExclusiveFullscreen")
|
||||
UI_MAP_ELEMENT( m_sliderRenderDistance, "RenderDistance")
|
||||
UI_MAP_ELEMENT( m_sliderGamma, "Gamma")
|
||||
UI_MAP_ELEMENT(m_sliderFOV, "FOV")
|
||||
|
|
|
|||
|
|
@ -257,11 +257,14 @@ typedef struct _SaveListDetails
|
|||
#endif
|
||||
#endif
|
||||
|
||||
bool isHardcore;
|
||||
|
||||
_SaveListDetails()
|
||||
{
|
||||
saveId = 0;
|
||||
pbThumbnailData = nullptr;
|
||||
dwThumbnailSize = 0;
|
||||
isHardcore = false;
|
||||
#ifdef _DURANGO
|
||||
ZeroMemory(UTF16SaveName,sizeof(wchar_t)*128);
|
||||
ZeroMemory(UTF16SaveFilename,sizeof(wchar_t)*MAX_SAVEFILENAME_LENGTH);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
#include "../../../Minecraft.World/File.h"
|
||||
#include "UITTFFont.h"
|
||||
|
||||
UITTFFont::UITTFFont(const string &name, const string &path, S32 fallbackCharacter)
|
||||
UITTFFont::UITTFFont(const string &name, const string &path, S32 fallbackCharacter, bool registerAsDefaultFonts)
|
||||
: m_strFontName(name)
|
||||
{
|
||||
app.DebugPrintf("UITTFFont opening %s\n",path.c_str());
|
||||
|
|
@ -41,9 +41,12 @@ UITTFFont::UITTFFont(const string &name, const string &path, S32 fallbackCharact
|
|||
|
||||
IggyFontInstallTruetypeFallbackCodepointUTF8( m_strFontName.c_str(), -1, IGGY_FONTFLAG_none, fallbackCharacter );
|
||||
|
||||
// 4J Stu - These are so we can use the default flash controls
|
||||
IggyFontInstallTruetypeUTF8 ( (void *)pbData, IGGY_TTC_INDEX_none, "Times New Roman", -1, IGGY_FONTFLAG_none );
|
||||
IggyFontInstallTruetypeUTF8 ( (void *)pbData, IGGY_TTC_INDEX_none, "Arial", -1, IGGY_FONTFLAG_none );
|
||||
if (registerAsDefaultFonts)
|
||||
{
|
||||
// 4J Stu - These are so we can use the default flash controls
|
||||
IggyFontInstallTruetypeUTF8 ( (void *)pbData, IGGY_TTC_INDEX_none, "Times New Roman", -1, IGGY_FONTFLAG_none );
|
||||
IggyFontInstallTruetypeUTF8 ( (void *)pbData, IGGY_TTC_INDEX_none, "Arial", -1, IGGY_FONTFLAG_none );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ private:
|
|||
//DWORD dwDataSize;
|
||||
|
||||
public:
|
||||
UITTFFont(const string &name, const string &path, S32 fallbackCharacter);
|
||||
UITTFFont(const string &name, const string &path, S32 fallbackCharacter, bool registerAsDefaultFonts = true);
|
||||
~UITTFFont();
|
||||
|
||||
string getFontName();
|
||||
|
|
|
|||
164
Minecraft.Client/Common/UI/UIUnicodeBitmapFont.cpp
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
#include "stdafx.h"
|
||||
#include "BufferedImage.h"
|
||||
#include "UIFontData.h"
|
||||
#include "UIUnicodeBitmapFont.h"
|
||||
|
||||
UIUnicodeBitmapFont::UIUnicodeBitmapFont(const string &fontname, SFontData &referenceFontData)
|
||||
: UIAbstractBitmapFont(fontname)
|
||||
{
|
||||
m_numGlyphs = 65536;
|
||||
m_referenceFontData = &referenceFontData;
|
||||
memset(m_glyphPages, 0, sizeof(m_glyphPages));
|
||||
memset(m_unicodeWidth, 0, sizeof(m_unicodeWidth));
|
||||
|
||||
FILE *f = nullptr;
|
||||
fopen_s(&f, "Common/res/1_2_2/font/glyph_sizes.bin", "rb");
|
||||
if (f)
|
||||
{
|
||||
fread(m_unicodeWidth, 1, 65536, f);
|
||||
fclose(f);
|
||||
}
|
||||
}
|
||||
|
||||
UIUnicodeBitmapFont::~UIUnicodeBitmapFont()
|
||||
{
|
||||
for (int i = 0; i < 256; i++)
|
||||
delete[] m_glyphPages[i];
|
||||
}
|
||||
|
||||
void UIUnicodeBitmapFont::loadGlyphPage(int page)
|
||||
{
|
||||
wchar_t fileName[64];
|
||||
swprintf(fileName, 64, L"/1_2_2/font/glyph_%02X.png", page);
|
||||
BufferedImage bimg(fileName);
|
||||
int *rawData = bimg.getData();
|
||||
if (!rawData) return;
|
||||
|
||||
int size = 256 * 256;
|
||||
m_glyphPages[page] = new unsigned char[size];
|
||||
for (int i = 0; i < size; i++)
|
||||
m_glyphPages[page][i] = (rawData[i] & 0xFF000000) >> 24;
|
||||
}
|
||||
|
||||
IggyFontMetrics *UIUnicodeBitmapFont::GetFontMetrics(IggyFontMetrics *metrics)
|
||||
{
|
||||
metrics->ascent = m_referenceFontData->m_fAscent;
|
||||
metrics->descent = m_referenceFontData->m_fDescent;
|
||||
metrics->average_glyph_width_for_tab_stops = 8.0f;
|
||||
metrics->largest_glyph_bbox_y1 = metrics->descent;
|
||||
return metrics;
|
||||
}
|
||||
|
||||
S32 UIUnicodeBitmapFont::GetCodepointGlyph(U32 codepoint)
|
||||
{
|
||||
if (codepoint < 65536 && m_unicodeWidth[codepoint] != 0)
|
||||
return (S32)codepoint;
|
||||
return IGGY_GLYPH_INVALID;
|
||||
}
|
||||
|
||||
IggyGlyphMetrics *UIUnicodeBitmapFont::GetGlyphMetrics(S32 glyph, IggyGlyphMetrics *metrics)
|
||||
{
|
||||
if (glyph < 0 || glyph >= 65536) { metrics->x0 = metrics->x1 = metrics->advance = metrics->y0 = metrics->y1 = 0; return metrics; }
|
||||
int left = m_unicodeWidth[glyph] >> 4;
|
||||
int right = (m_unicodeWidth[glyph] & 0xF) + 1;
|
||||
float pixelWidth = (right - left) / 2.0f + 1.0f;
|
||||
float advance = pixelWidth * m_referenceFontData->m_fAdvPerPixel;
|
||||
|
||||
metrics->x0 = 0.0f;
|
||||
metrics->x1 = advance;
|
||||
metrics->advance = advance;
|
||||
metrics->y0 = 0.0f;
|
||||
metrics->y1 = 1.0f;
|
||||
return metrics;
|
||||
}
|
||||
|
||||
rrbool UIUnicodeBitmapFont::IsGlyphEmpty(S32 glyph)
|
||||
{
|
||||
if (glyph < 0 || glyph >= 65536) return true;
|
||||
return m_unicodeWidth[glyph] == 0;
|
||||
}
|
||||
|
||||
F32 UIUnicodeBitmapFont::GetKerningForGlyphPair(S32 first_glyph, S32 second_glyph)
|
||||
{
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
rrbool UIUnicodeBitmapFont::CanProvideBitmap(S32 glyph, F32 pixel_scale)
|
||||
{
|
||||
return glyph >= 0 && glyph < 65536;
|
||||
}
|
||||
|
||||
rrbool UIUnicodeBitmapFont::GetGlyphBitmap(S32 glyph, F32 pixel_scale, IggyBitmapCharacter *bitmap)
|
||||
{
|
||||
if (glyph < 0 || glyph >= 65536) return false;
|
||||
int page = glyph / 256;
|
||||
if (!m_glyphPages[page])
|
||||
{
|
||||
loadGlyphPage(page);
|
||||
if (!m_glyphPages[page]) return false;
|
||||
}
|
||||
|
||||
int cx = (glyph % 16) * 16;
|
||||
int cy = ((glyph & 0xFF) / 16) * 16;
|
||||
|
||||
bitmap->pixels_one_per_byte = m_glyphPages[page] + (cy * 256) + cx;
|
||||
bitmap->width_in_pixels = 16;
|
||||
bitmap->height_in_pixels = 16;
|
||||
bitmap->stride_in_bytes = 256;
|
||||
|
||||
bitmap->top_left_x = 0;
|
||||
bitmap->top_left_y = -static_cast<S32>(16) * m_referenceFontData->m_fAscent;
|
||||
|
||||
bitmap->oversample = 0;
|
||||
|
||||
// Scale parameters: match UIBitmapFont's approach.
|
||||
// truePixelScale = the pixel_scale at which 1 glyph pixel = 1 screen pixel.
|
||||
// For 16px glyphs displayed at the same visual size as Mojangles_7 (8px glyphs with advPerPixel 1/10):
|
||||
// The reference truePixelScale for Mojangles_7 is 1.0f/m_fAdvPerPixel = 10.0f
|
||||
// Since our glyphs are 16px (2x the Mojangles 8px), our truePixelScale is 20.0f
|
||||
float truePixelScale = 2.0f / m_referenceFontData->m_fAdvPerPixel;
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
bitmap->pixel_scale_correct = truePixelScale;
|
||||
if (pixel_scale < truePixelScale)
|
||||
{
|
||||
bitmap->pixel_scale_min = 0.0f;
|
||||
bitmap->pixel_scale_max = truePixelScale;
|
||||
bitmap->point_sample = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
bitmap->pixel_scale_min = truePixelScale;
|
||||
bitmap->pixel_scale_max = 99.0f;
|
||||
bitmap->point_sample = true;
|
||||
}
|
||||
#else
|
||||
float glyphScale = 1.0f;
|
||||
while ((0.5f + glyphScale) * truePixelScale < pixel_scale)
|
||||
glyphScale++;
|
||||
|
||||
if (glyphScale <= 1 && pixel_scale < truePixelScale)
|
||||
{
|
||||
bitmap->pixel_scale_correct = truePixelScale;
|
||||
bitmap->pixel_scale_min = 0.0f;
|
||||
bitmap->pixel_scale_max = truePixelScale * 1.001f;
|
||||
bitmap->point_sample = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
float actualScale = pixel_scale / glyphScale;
|
||||
bitmap->pixel_scale_correct = actualScale;
|
||||
bitmap->pixel_scale_min = truePixelScale;
|
||||
bitmap->pixel_scale_max = 99.0f;
|
||||
bitmap->point_sample = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
bitmap->user_context_for_free = nullptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
void UIUnicodeBitmapFont::FreeGlyphBitmap(S32 glyph, F32 pixel_scale, IggyBitmapCharacter *bitmap)
|
||||
{
|
||||
// Pixel data lives in m_glyphPages -- nothing to free.
|
||||
}
|
||||
27
Minecraft.Client/Common/UI/UIUnicodeBitmapFont.h
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
#pragma once
|
||||
#include "UIBitmapFont.h"
|
||||
|
||||
struct SFontData;
|
||||
|
||||
class UIUnicodeBitmapFont : public UIAbstractBitmapFont
|
||||
{
|
||||
private:
|
||||
unsigned char m_unicodeWidth[65536];
|
||||
unsigned char* m_glyphPages[256];
|
||||
SFontData* m_referenceFontData;
|
||||
|
||||
void loadGlyphPage(int page);
|
||||
|
||||
public:
|
||||
UIUnicodeBitmapFont(const string &fontname, SFontData &referenceFontData);
|
||||
~UIUnicodeBitmapFont();
|
||||
|
||||
virtual IggyFontMetrics *GetFontMetrics(IggyFontMetrics *metrics);
|
||||
virtual S32 GetCodepointGlyph(U32 codepoint);
|
||||
virtual IggyGlyphMetrics *GetGlyphMetrics(S32 glyph, IggyGlyphMetrics *metrics);
|
||||
virtual rrbool IsGlyphEmpty(S32 glyph);
|
||||
virtual F32 GetKerningForGlyphPair(S32 first_glyph, S32 second_glyph);
|
||||
virtual rrbool CanProvideBitmap(S32 glyph, F32 pixel_scale);
|
||||
virtual rrbool GetGlyphBitmap(S32 glyph, F32 pixel_scale, IggyBitmapCharacter *bitmap);
|
||||
virtual void FreeGlyphBitmap(S32 glyph, F32 pixel_scale, IggyBitmapCharacter *bitmap);
|
||||
};
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
#include "XUI_Chat.h"
|
||||
#include "../../Minecraft.h"
|
||||
#include "../../Gui.h"
|
||||
#include "../../../Minecraft.World\ArabicShaping.h"
|
||||
|
||||
HRESULT CScene_Chat::OnInit( XUIMessageInit* pInitData, BOOL& bHandled )
|
||||
{
|
||||
|
|
@ -29,7 +30,8 @@ HRESULT CScene_Chat::OnTimer( XUIMessageTimer *pXUIMessageTimer, BOOL &bHandled)
|
|||
{
|
||||
m_Backgrounds[i].SetOpacity(opacity);
|
||||
m_Labels[i].SetOpacity(opacity);
|
||||
m_Labels[i].SetText( pGui->getMessage(m_iPad,i).c_str() );
|
||||
wstring shaped = shapeArabicText(pGui->getMessage(m_iPad, i));
|
||||
m_Labels[i].SetText( shaped.c_str() );
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include "stdafx.h"
|
||||
#include "XUI_Ctrl_4JList.h"
|
||||
#include "..\..\..\Minecraft.World\ArabicShaping.h"
|
||||
|
||||
static bool TimeSortFn(const void *a, const void *b);
|
||||
|
||||
|
|
@ -294,8 +295,16 @@ HRESULT CXuiCtrl4JList::OnGetSourceDataText(XUIMessageGetSourceText *pGetSourceT
|
|||
if( ( 0 == pGetSourceTextData->iData ) && ( ( pGetSourceTextData->bItemData ) ) )
|
||||
{
|
||||
EnterCriticalSection(&m_AccessListData);
|
||||
pGetSourceTextData->szText =
|
||||
GetData(pGetSourceTextData->iItem).pwszText;
|
||||
LPCWSTR rawText = GetData(pGetSourceTextData->iItem).pwszText;
|
||||
if (rawText)
|
||||
{
|
||||
m_shapedTextCache = shapeArabicText(rawText);
|
||||
pGetSourceTextData->szText = m_shapedTextCache.c_str();
|
||||
}
|
||||
else
|
||||
{
|
||||
pGetSourceTextData->szText = rawText;
|
||||
}
|
||||
LeaveCriticalSection(&m_AccessListData);
|
||||
bHandled = TRUE;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,4 +75,5 @@ private:
|
|||
static bool IndexSortFn(const void *a, const void *b);
|
||||
|
||||
HXUIOBJ m_hSelectionChangedHandlerObj;
|
||||
std::wstring m_shapedTextCache; // temp buffer for Arabic-shaped text in OnGetSourceDataText
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include "stdafx.h"
|
||||
#include "../XUI/XUI_Death.h"
|
||||
#include <assert.h>
|
||||
|
||||
#include "../../../Minecraft.World/AABB.h"
|
||||
#include "../../../Minecraft.World/Vec3.h"
|
||||
#include "../../../Minecraft.World/net.minecraft.stats.h"
|
||||
|
|
@ -17,10 +18,13 @@
|
|||
#include "../../../Minecraft.Client/LevelRenderer.h"
|
||||
#include "../../../Minecraft.World/Pos.h"
|
||||
#include "../../../Minecraft.World/Dimension.h"
|
||||
#include "../../../Minecraft.World/net.minecraft.world.level.storage.h"
|
||||
#include "../../../Minecraft.World/compression.h"
|
||||
|
||||
#include "../../Minecraft.h"
|
||||
#include "../../MinecraftServer.h"
|
||||
#include "../../Options.h"
|
||||
#include "../../LocalPlayer.h"
|
||||
#include "../../../Minecraft.World/compression.h"
|
||||
//----------------------------------------------------------------------------------
|
||||
// Performs initialization tasks - retrieves controls.
|
||||
//----------------------------------------------------------------------------------
|
||||
|
|
@ -37,9 +41,26 @@ HRESULT CScene_Death::OnInit( XUIMessageInit* pInitData, BOOL& bHandled )
|
|||
}
|
||||
|
||||
XuiControlSetText(m_Title,app.GetString(IDS_YOU_DIED));
|
||||
XuiControlSetText(m_Buttons[BUTTON_DEATH_RESPAWN],app.GetString(IDS_RESPAWN));
|
||||
XuiControlSetText(m_Buttons[BUTTON_DEATH_EXITGAME],app.GetString(IDS_EXIT_GAME));
|
||||
|
||||
// 4J Added: In hardcore mode, disable respawn and show hardcore death message
|
||||
Minecraft *pMinecraft = Minecraft::GetInstance();
|
||||
bool isHardcore = false;
|
||||
if (pMinecraft != nullptr && pMinecraft->level != nullptr)
|
||||
{
|
||||
isHardcore = pMinecraft->level->getLevelData()->isHardcore();
|
||||
}
|
||||
|
||||
if (isHardcore)
|
||||
{
|
||||
XuiControlSetText(m_Buttons[BUTTON_DEATH_RESPAWN], app.GetString(IDS_HARDCORE_DEATH_MESSAGE));
|
||||
XuiElementSetShow(m_Buttons[BUTTON_DEATH_RESPAWN], FALSE);
|
||||
}
|
||||
else
|
||||
{
|
||||
XuiControlSetText(m_Buttons[BUTTON_DEATH_RESPAWN], app.GetString(IDS_RESPAWN));
|
||||
}
|
||||
|
||||
// Display the tooltips
|
||||
ui.SetTooltips( m_iPad, IDS_TOOLTIPS_SELECT);
|
||||
|
||||
|
|
@ -110,7 +131,16 @@ HRESULT CScene_Death::OnNotifyPressEx(HXUIOBJ hObjPressed, XUINotifyPress* pNoti
|
|||
playTime = static_cast<int>(pMinecraft->localplayers[pNotifyPressData->UserIndex]->getSessionTimer());
|
||||
}
|
||||
TelemetryManager->RecordLevelExit(pNotifyPressData->UserIndex, eSen_LevelExitStatus_Failed);
|
||||
|
||||
|
||||
// 4J Added: Hardcore mode — skip save dialog, exit without saving, delete world
|
||||
if (pMinecraft->level != nullptr && pMinecraft->level->getLevelData()->isHardcore() && g_NetworkManager.IsHost())
|
||||
{
|
||||
MinecraftServer::getInstance()->setSaveOnExit(false);
|
||||
MinecraftServer::getInstance()->setDeleteWorldOnExit(true);
|
||||
app.SetAction(pNotifyPressData->UserIndex, eAppAction_ExitWorld);
|
||||
break;
|
||||
}
|
||||
|
||||
if(StorageManager.GetSaveDisabled())
|
||||
{
|
||||
uiIDA[0]=IDS_CONFIRM_CANCEL;
|
||||
|
|
@ -172,6 +202,12 @@ HRESULT CScene_Death::OnNotifyPressEx(HXUIOBJ hObjPressed, XUINotifyPress* pNoti
|
|||
break;
|
||||
case BUTTON_DEATH_RESPAWN:
|
||||
{
|
||||
// 4J Added: Safeguard - don't respawn in hardcore mode
|
||||
Minecraft *pMC = Minecraft::GetInstance();
|
||||
if (pMC != nullptr && pMC->level != nullptr && pMC->level->getLevelData()->isHardcore())
|
||||
{
|
||||
break;
|
||||
}
|
||||
m_bIgnoreInput = true;
|
||||
app.SetAction(pNotifyPressData->UserIndex,eAppAction_Respawn);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -186,7 +186,8 @@ HRESULT CXuiSceneHud::OnCustomMessage_TickScene()
|
|||
if (pMinecraft->localplayers[m_iPad]->invulnerableTime < 10) blink = false;
|
||||
int iHealth = pMinecraft->localplayers[m_iPad]->getHealth();
|
||||
int iLastHealth = pMinecraft->localplayers[m_iPad]->lastHealth;
|
||||
bool bHasPoison = pMinecraft->localplayers[m_iPad]->hasEffect(MobEffect::poison);
|
||||
bool bHasPoison = pMinecraft->localplayers[m_iPad]->hasEffect(MobEffect::poison);
|
||||
bool isHardcore = pMinecraft->level != nullptr && pMinecraft->level->getLevelData()->isHardcore();
|
||||
for (int icon = 0; icon < Player::MAX_HEALTH / 2; icon++)
|
||||
{
|
||||
if(blink)
|
||||
|
|
@ -196,11 +197,15 @@ HRESULT CXuiSceneHud::OnCustomMessage_TickScene()
|
|||
// Full
|
||||
if(bHasPoison)
|
||||
{
|
||||
m_healthIcon[icon].PlayVisualRange(L"FullPoisonFlash",nullptr,L"FullPoisonFlash");
|
||||
m_healthIcon[icon].PlayVisualRange(
|
||||
isHardcore ? L"FullPoisonFlashHardcore" : L"FullPoisonFlash", nullptr,
|
||||
isHardcore ? L"FullPoisonFlashHardcore" : L"FullPoisonFlash");
|
||||
}
|
||||
else
|
||||
{
|
||||
m_healthIcon[icon].PlayVisualRange(L"FullFlash",nullptr,L"FullFlash");
|
||||
m_healthIcon[icon].PlayVisualRange(
|
||||
isHardcore ? L"FullFlashHardcore" : L"FullFlash", nullptr,
|
||||
isHardcore ? L"FullFlashHardcore" : L"FullFlash");
|
||||
}
|
||||
}
|
||||
else if (icon * 2 + 1 == iLastHealth || icon * 2 + 1 == iHealth)
|
||||
|
|
@ -208,17 +213,23 @@ HRESULT CXuiSceneHud::OnCustomMessage_TickScene()
|
|||
// Half
|
||||
if(bHasPoison)
|
||||
{
|
||||
m_healthIcon[icon].PlayVisualRange(L"HalfPoisonFlash",nullptr,L"HalfPoisonFlash");
|
||||
m_healthIcon[icon].PlayVisualRange(
|
||||
isHardcore ? L"HalfPoisonFlashHardcore" : L"HalfPoisonFlash", nullptr,
|
||||
isHardcore ? L"HalfPoisonFlashHardcore" : L"HalfPoisonFlash");
|
||||
}
|
||||
else
|
||||
{
|
||||
m_healthIcon[icon].PlayVisualRange(L"HalfFlash",nullptr,L"HalfFlash");
|
||||
m_healthIcon[icon].PlayVisualRange(
|
||||
isHardcore ? L"HalfFlashHardcore" : L"HalfFlash", nullptr,
|
||||
isHardcore ? L"HalfFlashHardcore" : L"HalfFlash");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Empty
|
||||
m_healthIcon[icon].PlayVisualRange(L"NormalFlash",nullptr,L"NormalFlash");
|
||||
m_healthIcon[icon].PlayVisualRange(
|
||||
isHardcore ? L"NormalFlashHardcore" : L"NormalFlash", nullptr,
|
||||
isHardcore ? L"NormalFlashHardcore" : L"NormalFlash");
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -228,11 +239,15 @@ HRESULT CXuiSceneHud::OnCustomMessage_TickScene()
|
|||
// Full
|
||||
if(bHasPoison)
|
||||
{
|
||||
m_healthIcon[icon].PlayVisualRange(L"FullPoison",nullptr,L"FullPoison");
|
||||
m_healthIcon[icon].PlayVisualRange(
|
||||
isHardcore ? L"FullPoisonHardcore" : L"FullPoison", nullptr,
|
||||
isHardcore ? L"FullPoisonHardcore" : L"FullPoison");
|
||||
}
|
||||
else
|
||||
{
|
||||
m_healthIcon[icon].PlayVisualRange(L"Full",nullptr,L"Full");
|
||||
m_healthIcon[icon].PlayVisualRange(
|
||||
isHardcore ? L"FullHardcore" : L"Full", nullptr,
|
||||
isHardcore ? L"FullHardcore" : L"Full");
|
||||
}
|
||||
}
|
||||
else if (icon * 2 + 1 == iHealth)
|
||||
|
|
@ -240,17 +255,23 @@ HRESULT CXuiSceneHud::OnCustomMessage_TickScene()
|
|||
// Half
|
||||
if(bHasPoison)
|
||||
{
|
||||
m_healthIcon[icon].PlayVisualRange(L"HalfPoison",nullptr,L"HalfPoison");
|
||||
m_healthIcon[icon].PlayVisualRange(
|
||||
isHardcore ? L"HalfPoisonHardcore" : L"HalfPoison", nullptr,
|
||||
isHardcore ? L"HalfPoisonHardcore" : L"HalfPoison");
|
||||
}
|
||||
else
|
||||
{
|
||||
m_healthIcon[icon].PlayVisualRange(L"Half",nullptr,L"Half");
|
||||
m_healthIcon[icon].PlayVisualRange(
|
||||
isHardcore ? L"HalfHardcore" : L"Half", nullptr,
|
||||
isHardcore ? L"HalfHardcore" : L"Half");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Empty
|
||||
m_healthIcon[icon].PlayVisualRange(L"Normal",nullptr,L"Normal");
|
||||
m_healthIcon[icon].PlayVisualRange(
|
||||
isHardcore ? L"NormalHardcore" : L"Normal", nullptr,
|
||||
isHardcore ? L"NormalHardcore" : L"Normal");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -89,6 +89,11 @@ void EnderDragonRenderer::render(shared_ptr<Entity> _mob, double x, double y, do
|
|||
// 4J - dynamic cast required because we aren't using templates/generics in our version
|
||||
shared_ptr<EnderDragon> mob = dynamic_pointer_cast<EnderDragon>(_mob);
|
||||
BossMobGuiInfo::setBossHealth(mob, false);
|
||||
if (!mob->getCustomName().empty())
|
||||
{
|
||||
BossMobGuiInfo::name = mob->getCustomName();
|
||||
}
|
||||
|
||||
MobRenderer::render(mob, x, y, z, rot, a);
|
||||
if (mob->nearestCrystal != nullptr)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -21,6 +21,10 @@
|
|||
#include "../Minecraft.World/net.minecraft.world.level.chunk.h"
|
||||
#include "PlayerConnection.h"
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
extern bool g_Win64DedicatedServer;
|
||||
#endif
|
||||
|
||||
EntityTracker::EntityTracker(ServerLevel *level)
|
||||
{
|
||||
this->level = level;
|
||||
|
|
@ -140,32 +144,40 @@ void EntityTracker::tick()
|
|||
// 4J Stu - If one player on a system is updated, then make sure they all are as they all have their
|
||||
// range extended to include entities visible by any other player on the system
|
||||
// Fix for #11194 - Gameplay: Host player and their split-screen avatars can become invisible and invulnerable to client.
|
||||
MinecraftServer *server = MinecraftServer::getInstance();
|
||||
for( unsigned int i = 0; i < server->getPlayers()->players.size(); i++ )
|
||||
// NOTE: On dedicated servers, IsSameSystem() always returns false for remote
|
||||
// players (no split-screen), so this loop does nothing. Skip it entirely to
|
||||
// avoid the O(players * movedPlayers) overhead.
|
||||
#ifdef _WINDOWS64
|
||||
if (!g_Win64DedicatedServer)
|
||||
#endif
|
||||
{
|
||||
shared_ptr<ServerPlayer> ep = server->getPlayers()->players[i];
|
||||
if( ep->dimension != level->dimension->id ) continue;
|
||||
|
||||
if( ep->connection == nullptr ) continue;
|
||||
INetworkPlayer *thisPlayer = ep->connection->getNetworkPlayer();
|
||||
if( thisPlayer == nullptr ) continue;
|
||||
|
||||
bool addPlayer = false;
|
||||
for (unsigned int j = 0; j < movedPlayers.size(); j++)
|
||||
MinecraftServer *server = MinecraftServer::getInstance();
|
||||
for( unsigned int i = 0; i < server->getPlayers()->players.size(); i++ )
|
||||
{
|
||||
shared_ptr<ServerPlayer> sp = movedPlayers[j];
|
||||
shared_ptr<ServerPlayer> ep = server->getPlayers()->players[i];
|
||||
if( ep->dimension != level->dimension->id ) continue;
|
||||
|
||||
if( sp == ep ) break;
|
||||
if( ep->connection == nullptr ) continue;
|
||||
INetworkPlayer *thisPlayer = ep->connection->getNetworkPlayer();
|
||||
if( thisPlayer == nullptr ) continue;
|
||||
|
||||
if(sp->connection == nullptr) continue;
|
||||
INetworkPlayer *otherPlayer = sp->connection->getNetworkPlayer();
|
||||
if( otherPlayer != nullptr && thisPlayer->IsSameSystem(otherPlayer) )
|
||||
bool addPlayer = false;
|
||||
for (unsigned int j = 0; j < movedPlayers.size(); j++)
|
||||
{
|
||||
addPlayer = true;
|
||||
break;
|
||||
shared_ptr<ServerPlayer> sp = movedPlayers[j];
|
||||
|
||||
if( sp == ep ) break;
|
||||
|
||||
if(sp->connection == nullptr) continue;
|
||||
INetworkPlayer *otherPlayer = sp->connection->getNetworkPlayer();
|
||||
if( otherPlayer != nullptr && thisPlayer->IsSameSystem(otherPlayer) )
|
||||
{
|
||||
addPlayer = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if( addPlayer ) movedPlayers.push_back( ep );
|
||||
if( addPlayer ) movedPlayers.push_back( ep );
|
||||
}
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < movedPlayers.size(); i++)
|
||||
|
|
|
|||
|
|
@ -196,9 +196,29 @@ void IQNetPlayer::SendData(IQNetPlayer * player, const void* pvData, DWORD dwDat
|
|||
{
|
||||
if (!WinsockNetLayer::IsHosting() && !m_isRemote)
|
||||
{
|
||||
// Client sending to server via local socket (bypasses SendToSmallId)
|
||||
SOCKET sock = WinsockNetLayer::GetLocalSocket(m_smallId);
|
||||
if (sock != INVALID_SOCKET)
|
||||
WinsockNetLayer::SendOnSocket(sock, pvData, dwDataSize);
|
||||
{
|
||||
// Encrypt if client send cipher is active
|
||||
if (dwDataSize > 0)
|
||||
{
|
||||
std::vector<BYTE> buf(static_cast<const BYTE*>(pvData),
|
||||
static_cast<const BYTE*>(pvData) + dwDataSize);
|
||||
if (WinsockNetLayer::TryEncryptClientOutgoing(buf.data(), static_cast<int>(dwDataSize)))
|
||||
{
|
||||
WinsockNetLayer::SendOnSocket(sock, buf.data(), static_cast<int>(dwDataSize));
|
||||
}
|
||||
else
|
||||
{
|
||||
WinsockNetLayer::SendOnSocket(sock, pvData, dwDataSize);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
WinsockNetLayer::SendOnSocket(sock, pvData, dwDataSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
#include "../Minecraft.World/net.minecraft.h"
|
||||
#include "../Minecraft.World/StringHelpers.h"
|
||||
#include "../Minecraft.World/Random.h"
|
||||
#include "..\Minecraft.World\ArabicShaping.h"
|
||||
|
||||
Font::Font(Options *options, const wstring& name, Textures* textures, bool enforceUnicode, ResourceLocation *textureLocation, int cols, int rows, int charWidth, int charHeight, unsigned short charMap[]/* = nullptr */) : textures(textures)
|
||||
{
|
||||
|
|
@ -16,7 +17,7 @@ Font::Font(Options *options, const wstring& name, Textures* textures, bool enfor
|
|||
charWidths = new int[charC];
|
||||
|
||||
// 4J - added initialisers
|
||||
memset(charWidths, 0, charC);
|
||||
memset(charWidths, 0, charC * sizeof(int));
|
||||
|
||||
enforceUnicodeSheet = false;
|
||||
bidirectional = false;
|
||||
|
|
@ -26,6 +27,19 @@ Font::Font(Options *options, const wstring& name, Textures* textures, bool enfor
|
|||
m_underline = false;
|
||||
m_strikethrough = false;
|
||||
|
||||
memset(unicodeTexID, 0, sizeof(unicodeTexID));
|
||||
memset(unicodeWidth, 0, sizeof(unicodeWidth));
|
||||
lastBoundTexture = 0;
|
||||
|
||||
// Load unicode glyph sizes
|
||||
FILE *glyphFile = nullptr;
|
||||
fopen_s(&glyphFile, "Common/res/1_2_2/font/glyph_sizes.bin", "rb");
|
||||
if (glyphFile)
|
||||
{
|
||||
fread(unicodeWidth, 1, 65536, glyphFile);
|
||||
fclose(glyphFile);
|
||||
}
|
||||
|
||||
// Set up member variables
|
||||
m_cols = cols;
|
||||
m_rows = rows;
|
||||
|
|
@ -268,7 +282,87 @@ void Font::drawLiteral(const wstring& str, int x, int y, int color)
|
|||
yPos = static_cast<float>(y);
|
||||
wstring cleanStr = sanitize(str);
|
||||
for (size_t i = 0; i < cleanStr.length(); ++i)
|
||||
renderCharacter(cleanStr.at(i));
|
||||
{
|
||||
wchar_t c = cleanStr.at(i);
|
||||
if (isUnicodeGlyphChar(c))
|
||||
{
|
||||
renderUnicodeCharacter(c);
|
||||
textures->bindTexture(m_textureLocation);
|
||||
lastBoundTexture = fontTexture;
|
||||
}
|
||||
else
|
||||
{
|
||||
renderCharacter(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Like sanitize() but skips the shapeArabicText() call - for pre-shaped strings.
|
||||
wstring Font::sanitizePreshaped(const wstring& str)
|
||||
{
|
||||
wstring sb = str;
|
||||
for (unsigned int i = 0; i < sb.length(); i++)
|
||||
{
|
||||
if (CharacterExists(sb[i]))
|
||||
sb[i] = MapCharacter(sb[i]);
|
||||
else if (unicodeWidth[sb[i]] != 0)
|
||||
{
|
||||
// Leave as-is: raw codepoint for glyph page rendering
|
||||
}
|
||||
else
|
||||
{
|
||||
sb[i] = 0;
|
||||
}
|
||||
}
|
||||
return sb;
|
||||
}
|
||||
|
||||
void Font::drawLiteralPreshaped(const wstring& str, int x, int y, int color)
|
||||
{
|
||||
if (str.empty()) return;
|
||||
if ((color & 0xFC000000) == 0) color |= 0xFF000000;
|
||||
textures->bindTexture(m_textureLocation);
|
||||
glColor4f((color >> 16 & 255) / 255.0F, (color >> 8 & 255) / 255.0F, (color & 255) / 255.0F, (color >> 24 & 255) / 255.0F);
|
||||
xPos = static_cast<float>(x);
|
||||
yPos = static_cast<float>(y);
|
||||
wstring cleanStr = sanitizePreshaped(str);
|
||||
for (size_t i = 0; i < cleanStr.length(); ++i)
|
||||
{
|
||||
wchar_t c = cleanStr.at(i);
|
||||
if (isUnicodeGlyphChar(c))
|
||||
{
|
||||
renderUnicodeCharacter(c);
|
||||
textures->bindTexture(m_textureLocation);
|
||||
lastBoundTexture = fontTexture;
|
||||
}
|
||||
else
|
||||
{
|
||||
renderCharacter(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Font::drawShadowLiteralPreshaped(const wstring& str, int x, int y, int color)
|
||||
{
|
||||
int shadowColor = (color & 0xFCFCFC) >> 2 | (color & 0xFF000000);
|
||||
drawLiteralPreshaped(str, x + 1, y + 1, shadowColor);
|
||||
drawLiteralPreshaped(str, x, y, color);
|
||||
}
|
||||
|
||||
int Font::widthPreshaped(const wstring& str)
|
||||
{
|
||||
wstring cleanStr = sanitizePreshaped(str);
|
||||
if (cleanStr.empty()) return 0;
|
||||
int len = 0;
|
||||
for (size_t i = 0; i < cleanStr.length(); ++i)
|
||||
{
|
||||
wchar_t wc = cleanStr.at(i);
|
||||
if (isUnicodeGlyphChar(wc))
|
||||
len += (int)unicodeCharWidth(wc);
|
||||
else
|
||||
len += charWidths[static_cast<unsigned>(wc)];
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
void Font::drawShadowWordWrap(const wstring &str, int x, int y, int w, int color, int h)
|
||||
|
|
@ -358,7 +452,7 @@ void Font::draw(const wstring &str, bool dropShadow, int initialColor)
|
|||
}
|
||||
|
||||
// "noise" for crazy splash screen message
|
||||
if (noise)
|
||||
if (noise && !isUnicodeGlyphChar(c))
|
||||
{
|
||||
int newc;
|
||||
do
|
||||
|
|
@ -368,7 +462,23 @@ void Font::draw(const wstring &str, bool dropShadow, int initialColor)
|
|||
c = newc;
|
||||
}
|
||||
|
||||
addCharacterQuad(c);
|
||||
if (isUnicodeGlyphChar(c))
|
||||
{
|
||||
t->end();
|
||||
// renderUnicodeCharacter uses its own begin/end and relies on glColor
|
||||
glColor4f((currentColor >> 16 & 255) / 255.0F, (currentColor >> 8 & 255) / 255.0F,
|
||||
(currentColor & 255) / 255.0F, (currentColor >> 24 & 255) / 255.0F);
|
||||
renderUnicodeCharacter(c);
|
||||
glColor4f(1.0F, 1.0F, 1.0F, 1.0F);
|
||||
textures->bindTexture(m_textureLocation);
|
||||
lastBoundTexture = fontTexture;
|
||||
t->begin();
|
||||
t->color(currentColor & 0x00ffffff, (currentColor >> 24) & 255);
|
||||
}
|
||||
else
|
||||
{
|
||||
addCharacterQuad(c);
|
||||
}
|
||||
}
|
||||
|
||||
t->end();
|
||||
|
|
@ -409,11 +519,22 @@ int Font::width(const wstring& str)
|
|||
++i;
|
||||
else
|
||||
{
|
||||
len += charWidths[167];
|
||||
if (isUnicodeGlyphChar(167))
|
||||
len += (int)unicodeCharWidth(167);
|
||||
else
|
||||
len += charWidths[167];
|
||||
if (i + 1 < cleanStr.length())
|
||||
len += charWidths[static_cast<unsigned>(cleanStr[++i])];
|
||||
{
|
||||
wchar_t nextC = cleanStr[++i];
|
||||
if (isUnicodeGlyphChar(nextC))
|
||||
len += (int)unicodeCharWidth(nextC);
|
||||
else if (static_cast<unsigned>(nextC) < static_cast<unsigned>(m_cols * m_rows))
|
||||
len += charWidths[static_cast<unsigned>(nextC)];
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (isUnicodeGlyphChar(c))
|
||||
len += (int)unicodeCharWidth(c);
|
||||
else
|
||||
len += charWidths[c];
|
||||
}
|
||||
|
|
@ -427,13 +548,19 @@ int Font::widthLiteral(const wstring& str)
|
|||
if (cleanStr == L"") return 0;
|
||||
int len = 0;
|
||||
for (size_t i = 0; i < cleanStr.length(); ++i)
|
||||
len += charWidths[static_cast<unsigned>(cleanStr.at(i))];
|
||||
{
|
||||
wchar_t wc = cleanStr.at(i);
|
||||
if (isUnicodeGlyphChar(wc))
|
||||
len += (int)unicodeCharWidth(wc);
|
||||
else
|
||||
len += charWidths[static_cast<unsigned>(wc)];
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
wstring Font::sanitize(const wstring& str)
|
||||
{
|
||||
wstring sb = str;
|
||||
wstring sb = shapeArabicText(str);
|
||||
|
||||
for (unsigned int i = 0; i < sb.length(); i++)
|
||||
{
|
||||
|
|
@ -441,6 +568,10 @@ wstring Font::sanitize(const wstring& str)
|
|||
{
|
||||
sb[i] = MapCharacter(sb[i]);
|
||||
}
|
||||
else if (unicodeWidth[sb[i]] != 0)
|
||||
{
|
||||
// Leave as-is: raw codepoint for glyph page rendering
|
||||
}
|
||||
else
|
||||
{
|
||||
// If this character isn't supported, just show the first character (empty square box character)
|
||||
|
|
@ -684,33 +815,22 @@ void Font::renderFakeCB(IntBuffer *ib)
|
|||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
void Font::loadUnicodePage(int page)
|
||||
{
|
||||
wchar_t fileName[25];
|
||||
//String fileName = String.format("/1_2_2/font/glyph_%02X.png", page);
|
||||
swprintf(fileName,25,L"/1_2_2/font/glyph_%02X.png",page);
|
||||
wchar_t fileName[40];
|
||||
swprintf(fileName, 40, L"/1_2_2/font/glyph_%02X.png", page);
|
||||
BufferedImage *image = new BufferedImage(fileName);
|
||||
//try
|
||||
//{
|
||||
// image = ImageIO.read(Textures.class.getResourceAsStream(fileName.toString()));
|
||||
//}
|
||||
//catch (IOException e)
|
||||
//{
|
||||
// throw new RuntimeException(e);
|
||||
//}
|
||||
|
||||
unicodeTexID[page] = textures->getTexture(image);
|
||||
lastBoundTexture = unicodeTexID[page];
|
||||
delete image;
|
||||
}
|
||||
|
||||
void Font::renderUnicodeCharacter(wchar_t c)
|
||||
{
|
||||
if (unicodeWidth[c] == 0)
|
||||
{
|
||||
// System.out.println("no-width char " + c);
|
||||
return;
|
||||
}
|
||||
|
||||
int page = c / 256;
|
||||
|
||||
|
|
@ -722,19 +842,17 @@ void Font::renderUnicodeCharacter(wchar_t c)
|
|||
lastBoundTexture = unicodeTexID[page];
|
||||
}
|
||||
|
||||
// first column with non-trans pixels
|
||||
int firstLeft = unicodeWidth[c] >> 4;
|
||||
// last column with non-trans pixels
|
||||
int firstRight = unicodeWidth[c] & 0xF;
|
||||
|
||||
float left = firstLeft;
|
||||
float right = firstRight + 1;
|
||||
float left = (float)firstLeft;
|
||||
float right = (float)(firstRight + 1);
|
||||
|
||||
float xOff = c % 16 * 16 + left;
|
||||
float yOff = (c & 0xFF) / 16 * 16;
|
||||
float xOff = (c % 16) * 16 + left;
|
||||
float yOff = ((c & 0xFF) / 16) * 16;
|
||||
float width = right - left - .02f;
|
||||
|
||||
Tesselator *t = Tesselator::getInstance();
|
||||
Tesselator *t = Tesselator::getInstance();
|
||||
t->begin(GL_TRIANGLE_STRIP);
|
||||
t->tex(xOff / 256.0F, yOff / 256.0F);
|
||||
t->vertex(xPos, yPos, 0.0f);
|
||||
|
|
@ -748,5 +866,17 @@ void Font::renderUnicodeCharacter(wchar_t c)
|
|||
|
||||
xPos += (right - left) / 2 + 1;
|
||||
}
|
||||
*/
|
||||
|
||||
float Font::unicodeCharWidth(wchar_t c)
|
||||
{
|
||||
if (unicodeWidth[c] == 0) return 0;
|
||||
int firstLeft = unicodeWidth[c] >> 4;
|
||||
int firstRight = unicodeWidth[c] & 0xF;
|
||||
return (firstRight + 1 - firstLeft) / 2.0f + 1;
|
||||
}
|
||||
|
||||
bool Font::isUnicodeGlyphChar(wchar_t c)
|
||||
{
|
||||
return c >= m_cols * m_rows && unicodeWidth[c] != 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,10 @@ private:
|
|||
|
||||
Textures *textures;
|
||||
|
||||
int unicodeTexID[256];
|
||||
unsigned char unicodeWidth[65536];
|
||||
int lastBoundTexture;
|
||||
|
||||
float xPos;
|
||||
float yPos;
|
||||
|
||||
|
|
@ -72,10 +76,18 @@ private:
|
|||
void drawLiteral(const wstring& str, int x, int y, int color); // no § parsing
|
||||
int MapCharacter(wchar_t c); // 4J added
|
||||
bool CharacterExists(wchar_t c); // 4J added
|
||||
void loadUnicodePage(int page);
|
||||
void renderUnicodeCharacter(wchar_t c);
|
||||
float unicodeCharWidth(wchar_t c);
|
||||
bool isUnicodeGlyphChar(wchar_t c);
|
||||
wstring sanitizePreshaped(const wstring& str); // sanitize without re-shaping Arabic
|
||||
void drawLiteralPreshaped(const wstring& str, int x, int y, int color);
|
||||
|
||||
public:
|
||||
int width(const wstring& str);
|
||||
int widthLiteral(const wstring& str); // width without skipping § codes (for chat input)
|
||||
int widthPreshaped(const wstring& str); // width of already-shaped text, no re-shaping
|
||||
void drawShadowLiteralPreshaped(const wstring& str, int x, int y, int color);
|
||||
wstring sanitize(const wstring& str);
|
||||
void drawWordWrap(const wstring &string, int x, int y, int w, int col, int h); // 4J Added h param
|
||||
|
||||
|
|
|
|||
|
|
@ -1546,7 +1546,7 @@ void GameRenderer::renderLevel(float a, int64_t until)
|
|||
if (visibleWaterChunks > 0)
|
||||
{
|
||||
PIXBeginNamedEvent(0,"Fancy second pass - actual rendering");
|
||||
levelRenderer->render(cameraEntity, 1, a, updateChunks); // 4J - chanaged, used to be renderSameAsLast but we don't support that anymore
|
||||
levelRenderer->renderChunksDirect(1, a); // Lightweight path — skips redundant allChanged/resortChunks checks
|
||||
PIXEndNamedEvent();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -499,11 +499,10 @@ void Gui::render(float a, bool mouseFree, int xMouse, int yMouse)
|
|||
|
||||
int y0 = 0;
|
||||
|
||||
// No hardcore on console
|
||||
/*if (minecraft->level.getLevelData().isHardcore())
|
||||
{
|
||||
y0 = 5;
|
||||
}*/
|
||||
//if (minecraft->level->getLevelData()->isHardcore())
|
||||
//{
|
||||
// y0 = 5;
|
||||
//}
|
||||
|
||||
blit(xo, yo, 16 + bg * 9, 9 * y0, 9, 9);
|
||||
if (blink)
|
||||
|
|
@ -854,10 +853,11 @@ void Gui::render(float a, bool mouseFree, int xMouse, int yMouse)
|
|||
// font.draw(str, x + 1, y, 0xffffff);
|
||||
// }
|
||||
|
||||
|
||||
lastTickA = a;
|
||||
// 4J Stu - This is now displayed in a xui scene
|
||||
#if 0
|
||||
// Jukebox CD message
|
||||
// Jukebox CD message
|
||||
if (overlayMessageTime > 0)
|
||||
{
|
||||
float t = overlayMessageTime - a;
|
||||
|
|
@ -1064,6 +1064,13 @@ void Gui::render(float a, bool mouseFree, int xMouse, int yMouse)
|
|||
|
||||
vector<wstring> lines;
|
||||
|
||||
// Only show version/branch for player 0 to avoid cluttering each splitscreen viewport
|
||||
if (iPad == 0 && ClientConstants::SHOW_VERSION_WATERMARK)
|
||||
{
|
||||
lines.push_back(ClientConstants::VERSION_STRING);
|
||||
lines.push_back(ClientConstants::BRANCH_STRING);
|
||||
}
|
||||
|
||||
if (minecraft->options->renderDebug && minecraft->player != nullptr && minecraft->level != nullptr)
|
||||
{
|
||||
lines.push_back(minecraft->fpsString);
|
||||
|
|
@ -1418,6 +1425,9 @@ void Gui::clearMessages(int iPad)
|
|||
|
||||
void Gui::addMessage(const wstring& _string,int iPad,bool bIsDeathMessage)
|
||||
{
|
||||
{ char buf[32]; sprintf_s(buf, "[CHAT] Display (pad=%d): ", iPad); OutputDebugStringA(buf); }
|
||||
OutputDebugStringW(_string.c_str());
|
||||
OutputDebugStringA("\n");
|
||||
wstring string = _string; // 4J - Take copy of input as it is const
|
||||
//int iScale=1;
|
||||
|
||||
|
|
|
|||
|
|
@ -110,6 +110,11 @@ void GuiComponent::drawStringLiteral(Font *font, const wstring& str, int x, int
|
|||
font->drawShadowLiteral(str, x, y, color);
|
||||
}
|
||||
|
||||
void GuiComponent::drawStringPreshaped(Font *font, const wstring& str, int x, int y, int color)
|
||||
{
|
||||
font->drawShadowLiteralPreshaped(str, x, y, color);
|
||||
}
|
||||
|
||||
void GuiComponent::blit(int x, int y, int sx, int sy, int w, int h)
|
||||
{
|
||||
float us = 1 / 256.0f;
|
||||
|
|
|
|||
|
|
@ -16,5 +16,6 @@ public:
|
|||
void drawCenteredString(Font *font, const wstring& str, int x, int y, int color);
|
||||
void drawString(Font *font, const wstring& str, int x, int y, int color);
|
||||
void drawStringLiteral(Font* font, const wstring& str, int x, int y, int color);
|
||||
void drawStringPreshaped(Font* font, const wstring& str, int x, int y, int color);
|
||||
void blit(int x, int y, int sx, int sy, int w, int h);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -442,7 +442,7 @@ void HumanoidModel::setupAnim(float time, float r, float bob, float yRot, float
|
|||
|
||||
if (riding)
|
||||
{
|
||||
if(uiBitmaskOverrideAnim&(1<<eAnim_SmallModel) == 0)
|
||||
if ((uiBitmaskOverrideAnim&(1<<eAnim_SmallModel)) == 0)
|
||||
{
|
||||
arm0->xRot += -HALF_PI * 0.4f;
|
||||
arm1->xRot += -HALF_PI * 0.4f;
|
||||
|
|
|
|||
|
|
@ -289,6 +289,20 @@ void ItemInHandRenderer::renderItem(shared_ptr<LivingEntity> mob, shared_ptr<Ite
|
|||
|
||||
float xo = 0.0f;
|
||||
float yo = 0.3f;
|
||||
|
||||
|
||||
// Re position height of held item if skin is small
|
||||
if (mob->getAnimOverrideBitmask() & (1 << HumanoidModel::eAnim_SmallModel))
|
||||
{
|
||||
if (mob->isRiding())
|
||||
{
|
||||
std::shared_ptr<Entity> ridingEntity = mob->riding;
|
||||
if (ridingEntity != nullptr) // Safety check;
|
||||
{
|
||||
yo += 0.3f; // reverts the change in Boat.cpp for smaller models.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
glEnable(GL_RESCALE_NORMAL);
|
||||
glTranslatef(-xo, -yo, 0);
|
||||
|
|
|
|||
|
|
@ -157,6 +157,11 @@ LevelRenderer::LevelRenderer(Minecraft *mc, Textures *textures)
|
|||
dirtyChunkPresent = false;
|
||||
lastDirtyChunkFound = 0;
|
||||
|
||||
visibleLists_layer0 = nullptr;
|
||||
visibleLists_layer1 = nullptr;
|
||||
visibleCount_layer0 = 0;
|
||||
visibleCount_layer1 = 0;
|
||||
|
||||
this->mc = mc;
|
||||
this->textures = textures;
|
||||
|
||||
|
|
@ -455,6 +460,12 @@ void LevelRenderer::allChanged(int playerIndex)
|
|||
// delete sortedChunks[playerIndex]; // 4J - removed - not sorting our chunks anymore
|
||||
}
|
||||
|
||||
// Free old visible chunk lists
|
||||
delete[] visibleLists_layer0;
|
||||
delete[] visibleLists_layer1;
|
||||
visibleLists_layer0 = nullptr;
|
||||
visibleLists_layer1 = nullptr;
|
||||
|
||||
chunks[playerIndex] = ClipChunkArray(xChunks * yChunks * zChunks);
|
||||
// sortedChunks[playerIndex] = new vector<Chunk *>(xChunks * yChunks * zChunks); // 4J - removed - not sorting our chunks anymore
|
||||
int id = 0;
|
||||
|
|
@ -487,6 +498,13 @@ void LevelRenderer::allChanged(int playerIndex)
|
|||
}
|
||||
nonStackDirtyChunksAdded();
|
||||
|
||||
// Allocate visible chunk lists (worst case: all chunks visible)
|
||||
int totalChunkCount = xChunks * yChunks * zChunks;
|
||||
visibleLists_layer0 = new int[totalChunkCount];
|
||||
visibleLists_layer1 = new int[totalChunkCount];
|
||||
visibleCount_layer0 = 0;
|
||||
visibleCount_layer1 = 0;
|
||||
|
||||
if (level != nullptr)
|
||||
{
|
||||
shared_ptr<Entity> player = mc->cameraTargetPlayer;
|
||||
|
|
@ -705,42 +723,42 @@ int LevelRenderer::render(shared_ptr<LivingEntity> player, int layer, double alp
|
|||
{
|
||||
int playerIndex = mc->player->GetXboxPad();
|
||||
|
||||
// 4J - added - if the number of players has changed, we need to rebuild things for the new draw distance this will require
|
||||
if( lastPlayerCount[playerIndex] != activePlayers() )
|
||||
{
|
||||
allChanged();
|
||||
}
|
||||
else if (mc->options->viewDistance != lastViewDistance)
|
||||
{
|
||||
allChanged();
|
||||
}
|
||||
|
||||
// Only check allChanged/resortChunks on layer 0 — they only need to run once per frame
|
||||
if (layer == 0)
|
||||
{
|
||||
// 4J - added - if the number of players has changed, we need to rebuild things for the new draw distance this will require
|
||||
if( lastPlayerCount[playerIndex] != activePlayers() )
|
||||
{
|
||||
allChanged();
|
||||
}
|
||||
else if (mc->options->viewDistance != lastViewDistance)
|
||||
{
|
||||
allChanged();
|
||||
}
|
||||
|
||||
totalChunks = 0;
|
||||
offscreenChunks = 0;
|
||||
occludedChunks = 0;
|
||||
renderedChunks = 0;
|
||||
emptyChunks = 0;
|
||||
|
||||
double xd = player->x - xOld[playerIndex];
|
||||
double yd = player->y - yOld[playerIndex];
|
||||
double zd = player->z - zOld[playerIndex];
|
||||
|
||||
if (xd * xd + yd * yd + zd * zd > 4 * 4)
|
||||
{
|
||||
xOld[playerIndex] = player->x;
|
||||
yOld[playerIndex] = player->y;
|
||||
zOld[playerIndex] = player->z;
|
||||
|
||||
resortChunks(Mth::floor(player->x), Mth::floor(player->y), Mth::floor(player->z));
|
||||
}
|
||||
}
|
||||
|
||||
double xOff = player->xOld + (player->x - player->xOld) * alpha;
|
||||
double yOff = player->yOld + (player->y - player->yOld) * alpha;
|
||||
double zOff = player->zOld + (player->z - player->zOld) * alpha;
|
||||
|
||||
double xd = player->x - xOld[playerIndex];
|
||||
double yd = player->y - yOld[playerIndex];
|
||||
double zd = player->z - zOld[playerIndex];
|
||||
|
||||
if (xd * xd + yd * yd + zd * zd > 4 * 4)
|
||||
{
|
||||
xOld[playerIndex] = player->x;
|
||||
yOld[playerIndex] = player->y;
|
||||
zOld[playerIndex] = player->z;
|
||||
|
||||
resortChunks(Mth::floor(player->x), Mth::floor(player->y), Mth::floor(player->z));
|
||||
// sort(sortedChunks[playerIndex]->begin(),sortedChunks[playerIndex]->end(), DistanceChunkSorter(player)); // 4J - removed - not sorting our chunks anymore
|
||||
}
|
||||
Lighting::turnOff();
|
||||
|
||||
int count = renderChunks(0, static_cast<int>(chunks[playerIndex].length), layer, alpha);
|
||||
|
|
@ -749,6 +767,38 @@ int LevelRenderer::render(shared_ptr<LivingEntity> player, int layer, double alp
|
|||
|
||||
}
|
||||
|
||||
// Lightweight render path for the second layer 1 pass — skips allChanged/resortChunks checks
|
||||
// and just does GL setup + visible list iteration + cleanup.
|
||||
// Assumes Lighting::turnOff() was already called by the prior render() invocation.
|
||||
void LevelRenderer::renderChunksDirect(int layer, double alpha)
|
||||
{
|
||||
shared_ptr<LivingEntity> player = mc->cameraTargetPlayer;
|
||||
if (player == nullptr) return;
|
||||
|
||||
mc->gameRenderer->turnOnLightLayer(alpha);
|
||||
double xOff = player->xOld + (player->x - player->xOld) * alpha;
|
||||
double yOff = player->yOld + (player->y - player->yOld) * alpha;
|
||||
double zOff = player->zOld + (player->z - player->zOld) * alpha;
|
||||
|
||||
glPushMatrix();
|
||||
glTranslatef(static_cast<float>(-xOff), static_cast<float>(-yOff), static_cast<float>(-zOff));
|
||||
|
||||
int *lists = (layer == 0) ? visibleLists_layer0 : visibleLists_layer1;
|
||||
int numVisible = (layer == 0) ? visibleCount_layer0 : visibleCount_layer1;
|
||||
bool first = true;
|
||||
if (lists != nullptr)
|
||||
{
|
||||
for (int i = 0; i < numVisible; i++)
|
||||
{
|
||||
if (RenderManager.CBuffCall(lists[i], first))
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
|
||||
glPopMatrix();
|
||||
mc->gameRenderer->turnOffLightLayer(alpha);
|
||||
}
|
||||
|
||||
#ifdef __PSVITA__
|
||||
#include <stdlib.h>
|
||||
|
||||
|
|
@ -819,23 +869,35 @@ int LevelRenderer::renderChunks(int from, int to, int layer, double alpha)
|
|||
|
||||
bool first = true;
|
||||
int count = 0;
|
||||
ClipChunk *pClipChunk = chunks[playerIndex].data;
|
||||
unsigned char emptyFlag = LevelRenderer::CHUNK_FLAG_EMPTY0 << layer;
|
||||
for( int i = 0; i < chunks[playerIndex].length; i++, pClipChunk++ )
|
||||
|
||||
// Use compact visible lists built during cull() instead of iterating all chunks
|
||||
int *lists = (layer == 0) ? visibleLists_layer0 : visibleLists_layer1;
|
||||
int numVisible = (layer == 0) ? visibleCount_layer0 : visibleCount_layer1;
|
||||
if (lists != nullptr)
|
||||
{
|
||||
if( !pClipChunk->visible ) continue; // This will be set if the chunk isn't visible, or isn't compiled, or has both empty flags set
|
||||
if( pClipChunk->globalIdx == -1 ) continue; // Not sure if we should ever encounter this... TODO check
|
||||
if( ( globalChunkFlags[pClipChunk->globalIdx] & emptyFlag ) == emptyFlag ) continue; // Check that this particular layer isn't empty
|
||||
|
||||
// List can be calculated directly from the chunk's global idex
|
||||
int list = pClipChunk->globalIdx * 2 + layer;
|
||||
list += chunkLists;
|
||||
|
||||
if(RenderManager.CBuffCall(list, first))
|
||||
for (int i = 0; i < numVisible; i++)
|
||||
{
|
||||
first = false;
|
||||
if (RenderManager.CBuffCall(lists[i], first))
|
||||
first = false;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback: iterate all chunks (before visible lists are allocated)
|
||||
ClipChunk *pClipChunk = chunks[playerIndex].data;
|
||||
unsigned char emptyFlag = LevelRenderer::CHUNK_FLAG_EMPTY0 << layer;
|
||||
for( int i = 0; i < chunks[playerIndex].length; i++, pClipChunk++ )
|
||||
{
|
||||
if( !pClipChunk->visible ) continue;
|
||||
if( pClipChunk->globalIdx == -1 ) continue;
|
||||
if( ( globalChunkFlags[pClipChunk->globalIdx] & emptyFlag ) == emptyFlag ) continue;
|
||||
int list = pClipChunk->globalIdx * 2 + layer;
|
||||
list += chunkLists;
|
||||
if(RenderManager.CBuffCall(list, first))
|
||||
first = false;
|
||||
count++;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
|
||||
#ifdef __PSVITA__
|
||||
|
|
@ -1968,6 +2030,9 @@ bool LevelRenderer::updateDirtyChunks()
|
|||
for( int y = 0; y < CHUNK_Y_COUNT; y++ )
|
||||
{
|
||||
ClipChunk *pClipChunk = &chunks[p][(z * yChunks + y) * xChunks + x];
|
||||
// Early-out for non-dirty chunks - avoids distance calculation for the vast majority at steady state
|
||||
if( !(globalChunkFlags[ pClipChunk->globalIdx ] & CHUNK_FLAG_DIRTY) )
|
||||
continue;
|
||||
// Get distance to this chunk - deliberately not calling the chunk's method of doing this to avoid overheads (passing entitie, type conversion etc.) that this involves
|
||||
int xd = pClipChunk->xm - px;
|
||||
int yd = pClipChunk->ym - py;
|
||||
|
|
@ -2163,7 +2228,9 @@ bool LevelRenderer::updateDirtyChunks()
|
|||
else
|
||||
{
|
||||
// Nothing to do - clear flags that there are things to process, unless it's been a while since we found any dirty chunks in which case force a check next time through
|
||||
if( ( System::currentTimeMillis() - lastDirtyChunkFound ) > FORCE_DIRTY_CHUNK_CHECK_PERIOD_MS )
|
||||
// Scale recheck period with render distance to reduce wasted full-scans at high distances
|
||||
int recheckPeriod = (xChunks >= 60) ? 1000 : (xChunks >= 40) ? 500 : FORCE_DIRTY_CHUNK_CHECK_PERIOD_MS;
|
||||
if( ( System::currentTimeMillis() - lastDirtyChunkFound ) > recheckPeriod )
|
||||
{
|
||||
dirtyChunkPresent = true;
|
||||
}
|
||||
|
|
@ -2564,32 +2631,81 @@ void LevelRenderer::cull(Culler *culler, float a)
|
|||
fdraw[i * 4 + 3] = static_cast<float>(fd->m_Frustum[i][3] + (fx * -fc->xOff) + (fy * -fc->yOff) + (fz * -fc->zOff));
|
||||
}
|
||||
|
||||
ClipChunk *pClipChunk = chunks[playerIndex].data;
|
||||
int vis = 0;
|
||||
int total = 0;
|
||||
int numWrong = 0;
|
||||
for (unsigned int i = 0; i < chunks[playerIndex].length; i++)
|
||||
|
||||
// Reset visible chunk lists for this frame
|
||||
visibleCount_layer0 = 0;
|
||||
visibleCount_layer1 = 0;
|
||||
|
||||
// Column-level frustum culling: test one AABB per XZ column before testing individual Y chunks.
|
||||
// At dist 64 this reduces ~278K clip() calls to ~17K column tests + per-chunk tests only for visible columns.
|
||||
for (int x = 0; x < xChunks; x++)
|
||||
{
|
||||
unsigned char flags = pClipChunk->globalIdx == -1 ? 0 : globalChunkFlags[ pClipChunk->globalIdx ];
|
||||
for (int z = 0; z < zChunks; z++)
|
||||
{
|
||||
// Build column AABB from bottom and top chunks in this column
|
||||
ClipChunk *bottomChunk = &chunks[playerIndex][(z * yChunks + 0) * xChunks + x];
|
||||
ClipChunk *topChunk = &chunks[playerIndex][(z * yChunks + (yChunks - 1)) * xChunks + x];
|
||||
float columnAABB[6] = {
|
||||
bottomChunk->aabb[0], bottomChunk->aabb[1], bottomChunk->aabb[2], // minX, minY, minZ
|
||||
bottomChunk->aabb[3], topChunk->aabb[4], bottomChunk->aabb[5] // maxX, maxY(top), maxZ
|
||||
};
|
||||
|
||||
// Always perform frustum cull test
|
||||
bool clipres = clip(pClipChunk->aabb, fdraw);
|
||||
// Test entire column against frustum
|
||||
if (!clip(columnAABB, fdraw))
|
||||
{
|
||||
// Entire column outside frustum — mark all Y chunks invisible
|
||||
for (int y = 0; y < yChunks; y++)
|
||||
{
|
||||
ClipChunk *pClipChunk = &chunks[playerIndex][(z * yChunks + y) * xChunks + x];
|
||||
pClipChunk->visible = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( (flags & CHUNK_FLAG_COMPILED ) && ( ( flags & CHUNK_FLAG_EMPTYBOTH ) != CHUNK_FLAG_EMPTYBOTH ) )
|
||||
{
|
||||
pClipChunk->visible = clipres;
|
||||
if( pClipChunk->visible ) vis++;
|
||||
total++;
|
||||
// Column is (partially) in frustum — test individual chunks
|
||||
for (int y = 0; y < yChunks; y++)
|
||||
{
|
||||
ClipChunk *pClipChunk = &chunks[playerIndex][(z * yChunks + y) * xChunks + x];
|
||||
unsigned char flags = pClipChunk->globalIdx == -1 ? 0 : globalChunkFlags[ pClipChunk->globalIdx ];
|
||||
|
||||
// Skip frustum test for confirmed-empty compiled chunks - they have nothing to render
|
||||
if ((flags & CHUNK_FLAG_COMPILED) && (flags & CHUNK_FLAG_EMPTYBOTH) == CHUNK_FLAG_EMPTYBOTH)
|
||||
{
|
||||
pClipChunk->visible = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
bool clipres = clip(pClipChunk->aabb, fdraw);
|
||||
|
||||
if ( (flags & CHUNK_FLAG_COMPILED ) && ( ( flags & CHUNK_FLAG_EMPTYBOTH ) != CHUNK_FLAG_EMPTYBOTH ) )
|
||||
{
|
||||
pClipChunk->visible = clipres;
|
||||
if( pClipChunk->visible ) vis++;
|
||||
total++;
|
||||
}
|
||||
else if (clipres)
|
||||
{
|
||||
pClipChunk->visible = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
pClipChunk->visible = false;
|
||||
}
|
||||
|
||||
// Build compact visible chunk lists for renderChunks()
|
||||
if (pClipChunk->visible && pClipChunk->globalIdx != -1 && visibleLists_layer0 != nullptr)
|
||||
{
|
||||
int list = pClipChunk->globalIdx * 2 + chunkLists;
|
||||
if (!((flags & CHUNK_FLAG_EMPTY0) == CHUNK_FLAG_EMPTY0))
|
||||
visibleLists_layer0[visibleCount_layer0++] = list;
|
||||
if (!((flags & CHUNK_FLAG_EMPTY1) == CHUNK_FLAG_EMPTY1))
|
||||
visibleLists_layer1[visibleCount_layer1++] = list + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (clipres)
|
||||
{
|
||||
pClipChunk->visible = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
pClipChunk->visible = false;
|
||||
}
|
||||
pClipChunk++;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -83,6 +83,7 @@ private:
|
|||
void resortChunks(int xc, int yc, int zc);
|
||||
public:
|
||||
int render(shared_ptr<LivingEntity> player, int layer, double alpha, bool updateChunks);
|
||||
void renderChunksDirect(int layer, double alpha);
|
||||
private:
|
||||
int renderChunks(int from, int to, int layer, double alpha);
|
||||
public:
|
||||
|
|
@ -270,6 +271,12 @@ public:
|
|||
|
||||
XLockFreeStack<int> dirtyChunksLockFreeStack;
|
||||
|
||||
// Visible chunk lists built by cull(), consumed by renderChunks()
|
||||
int *visibleLists_layer0;
|
||||
int *visibleLists_layer1;
|
||||
int visibleCount_layer0;
|
||||
int visibleCount_layer1;
|
||||
|
||||
bool dirtyChunkPresent;
|
||||
int64_t lastDirtyChunkFound;
|
||||
static const int FORCE_DIRTY_CHUNK_CHECK_PERIOD_MS = 125; // decreased from 250 to 125 - updated by detectiveren
|
||||
|
|
|
|||
|
|
@ -77,6 +77,11 @@
|
|||
#include "Windows64/stb_image_write.h"
|
||||
#endif
|
||||
|
||||
#ifdef _WINDOWS64
|
||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
#include "Windows64/stb_image_write.h"
|
||||
#endif
|
||||
|
||||
#ifdef __ORBIS__
|
||||
#include "Orbis/Network/PsPlusUpsellWrapper_Orbis.h"
|
||||
#endif
|
||||
|
|
@ -4338,8 +4343,8 @@ void Minecraft::setLevel(MultiPlayerLevel *level, int message /*=-1*/, shared_pt
|
|||
this->progressRenderer->progressStage(-1);
|
||||
}
|
||||
|
||||
// 4J-PB - since we now play music in the menu, just let it keep playing
|
||||
//soundEngine->playStreaming(L"", 0, 0, 0, 0, 0);
|
||||
// Stop menu music and transition to game music for the new level
|
||||
soundEngine->playStreaming(L"", 0, 0, 0, 1, 1);
|
||||
|
||||
// 4J - stop update thread from processing this level, which blocks until it is safe to move on - will be re-enabled if we set the level to be non-nullptr
|
||||
gameRenderer->DisableUpdateThread();
|
||||
|
|
|
|||
|
|
@ -34,6 +34,9 @@
|
|||
#ifdef _WINDOWS64
|
||||
#include "Windows64/Network/WinsockNetLayer.h"
|
||||
#endif
|
||||
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
||||
#include "..\Minecraft.Server\ServerLogger.h"
|
||||
#endif
|
||||
#include <sstream>
|
||||
#ifdef SPLIT_SAVES
|
||||
#include "../Minecraft.World/ConsoleSaveFileSplit.h"
|
||||
|
|
@ -561,6 +564,7 @@ MinecraftServer::MinecraftServer()
|
|||
m_serverPausedEvent = new C4JThread::Event;
|
||||
|
||||
m_saveOnExit = false;
|
||||
m_deleteWorldOnExit = false;
|
||||
m_suspending = false;
|
||||
|
||||
m_ugcPlayersVersion = 0;
|
||||
|
|
@ -734,10 +738,11 @@ bool MinecraftServer::initServer(int64_t seed, NetworkGameInitData *initData, DW
|
|||
|
||||
if( findSeed )
|
||||
{
|
||||
int worldSizeChunks = (initData && initData->xzSize > 0) ? (int)initData->xzSize : 54;
|
||||
#ifdef __PSVITA__
|
||||
seed = BiomeSource::findSeed(pLevelType, &running);
|
||||
seed = BiomeSource::findSeed(pLevelType, &running, worldSizeChunks);
|
||||
#else
|
||||
seed = BiomeSource::findSeed(pLevelType);
|
||||
seed = BiomeSource::findSeed(pLevelType, worldSizeChunks);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
@ -887,6 +892,15 @@ bool MinecraftServer::loadLevel(LevelStorageSource *storageSource, const wstring
|
|||
// }
|
||||
ProgressRenderer *mcprogress = Minecraft::GetInstance()->progressRenderer;
|
||||
|
||||
// 4J Added - store save folder name for potential hardcore world deletion
|
||||
{
|
||||
char szSaveFolder[MAX_SAVEFILENAME_LENGTH] = {};
|
||||
StorageManager.GetSaveUniqueFilename(szSaveFolder);
|
||||
wchar_t wSaveFolder[MAX_SAVEFILENAME_LENGTH] = {};
|
||||
mbstowcs(wSaveFolder, szSaveFolder, MAX_SAVEFILENAME_LENGTH - 1);
|
||||
m_saveFolderName = wSaveFolder;
|
||||
}
|
||||
|
||||
// 4J TODO - free levels here if there are already some?
|
||||
levels = ServerLevelArray(3);
|
||||
|
||||
|
|
@ -997,6 +1011,12 @@ bool MinecraftServer::loadLevel(LevelStorageSource *storageSource, const wstring
|
|||
#endif
|
||||
levels[i]->getLevelData()->setGameType(gameType);
|
||||
|
||||
#ifdef MINECRAFT_SERVER_BUILD
|
||||
// Dedicated server: server.properties hardcore flag is authoritative
|
||||
levels[i]->getLevelData()->setHardcore(isHardcore());
|
||||
#endif
|
||||
// Offline/client-hosted: keep the world's saved hardcore flag from NBT
|
||||
|
||||
if(app.getLevelGenerationOptions() != nullptr)
|
||||
{
|
||||
LevelGenerationOptions *mapOptions = app.getLevelGenerationOptions();
|
||||
|
|
@ -1642,7 +1662,7 @@ bool MinecraftServer::isNetherEnabled()
|
|||
|
||||
bool MinecraftServer::isHardcore()
|
||||
{
|
||||
return false;
|
||||
return app.GetGameHostOption(eGameHostOption_Hardcore) > 0;
|
||||
}
|
||||
|
||||
int MinecraftServer::getOperatorUserPermissionLevel()
|
||||
|
|
@ -1795,7 +1815,6 @@ 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);
|
||||
|
|
@ -1864,11 +1883,23 @@ void MinecraftServer::run(int64_t seed, void *lpParameter)
|
|||
QueryPerformanceCounter(&qwTime);
|
||||
#endif
|
||||
|
||||
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
||||
LARGE_INTEGER asTicksPerSec, asT0, asT1;
|
||||
QueryPerformanceFrequency(&asTicksPerSec);
|
||||
double asSecsPerTick = 1.0 / (double)asTicksPerSec.QuadPart;
|
||||
QueryPerformanceCounter(&asT0);
|
||||
LARGE_INTEGER asAfterPlayers, asAfterLevels, asAfterRules, asAfterFlush;
|
||||
#endif
|
||||
|
||||
if (players != nullptr)
|
||||
{
|
||||
players->saveAll(nullptr);
|
||||
}
|
||||
|
||||
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
||||
QueryPerformanceCounter(&asAfterPlayers);
|
||||
#endif
|
||||
|
||||
for (unsigned int j = 0; j < levels.length; j++)
|
||||
{
|
||||
if( s_bServerHalted ) break;
|
||||
|
|
@ -1884,6 +1915,11 @@ void MinecraftServer::run(int64_t seed, void *lpParameter)
|
|||
PIXEndNamedEvent();
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
||||
QueryPerformanceCounter(&asAfterLevels);
|
||||
#endif
|
||||
|
||||
if (!s_bServerHalted)
|
||||
{
|
||||
#if defined(_XBOX_ONE) || defined(__ORBIS__)
|
||||
|
|
@ -1895,7 +1931,24 @@ void MinecraftServer::run(int64_t seed, void *lpParameter)
|
|||
|
||||
PIXBeginNamedEvent(0, "Save to disc");
|
||||
#endif
|
||||
|
||||
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
||||
QueryPerformanceCounter(&asAfterRules);
|
||||
#endif
|
||||
|
||||
levels[0]->saveToDisc(Minecraft::GetInstance()->progressRenderer, true);
|
||||
|
||||
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
||||
QueryPerformanceCounter(&asAfterFlush);
|
||||
ServerRuntime::LogInfof("world-io",
|
||||
"autosave breakdown: players=%.0fms levels=%.0fms rules=%.0fms flush=%.0fms total=%.0fms",
|
||||
(asAfterPlayers.QuadPart - asT0.QuadPart) * asSecsPerTick * 1000.0,
|
||||
(asAfterLevels.QuadPart - asAfterPlayers.QuadPart) * asSecsPerTick * 1000.0,
|
||||
(asAfterRules.QuadPart - asAfterLevels.QuadPart) * asSecsPerTick * 1000.0,
|
||||
(asAfterFlush.QuadPart - asAfterRules.QuadPart) * asSecsPerTick * 1000.0,
|
||||
(asAfterFlush.QuadPart - asT0.QuadPart) * asSecsPerTick * 1000.0);
|
||||
#endif
|
||||
|
||||
#if defined(_XBOX_ONE) || defined(__ORBIS__)
|
||||
PIXEndNamedEvent();
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -265,6 +265,8 @@ private:
|
|||
private:
|
||||
// 4J Added
|
||||
bool m_saveOnExit;
|
||||
bool m_deleteWorldOnExit; // 4J Added - for hardcore mode world deletion
|
||||
wstring m_saveFolderName; // 4J Added - stored for hardcore world deletion
|
||||
bool m_suspending;
|
||||
|
||||
public:
|
||||
|
|
@ -278,6 +280,9 @@ public:
|
|||
void chunkPacketManagement_PostTick();
|
||||
|
||||
void setSaveOnExit(bool save) { m_saveOnExit = save; s_bSaveOnExitAnswered = true; }
|
||||
void setDeleteWorldOnExit(bool del) { m_deleteWorldOnExit = del; }
|
||||
bool getDeleteWorldOnExit() const { return m_deleteWorldOnExit; }
|
||||
const wstring& getSaveFolderName() const { return m_saveFolderName; }
|
||||
void Suspend();
|
||||
bool IsSuspending();
|
||||
|
||||
|
|
|
|||
|
|
@ -139,18 +139,24 @@ bool MultiPlayerChunkCache::reallyHasChunk(int x, int z)
|
|||
return hasData[idx];
|
||||
}
|
||||
|
||||
void MultiPlayerChunkCache::drop(int x, int z)
|
||||
void MultiPlayerChunkCache::drop(const int x, const int z)
|
||||
{
|
||||
// 4J Stu - We do want to drop any entities in the chunks, especially for the case when a player is dead as they will
|
||||
// not get the RemoveEntity packet if an entity is removed.
|
||||
LevelChunk *chunk = getChunk(x, z);
|
||||
if (!chunk->isEmpty())
|
||||
const int ix = x + XZOFFSET;
|
||||
const int iz = z + XZOFFSET;
|
||||
if ((ix < 0) || (ix >= XZSIZE)) return;
|
||||
if ((iz < 0) || (iz >= XZSIZE)) return;
|
||||
const int idx = ix * XZSIZE + iz;
|
||||
LevelChunk* chunk = cache[idx];
|
||||
|
||||
if (chunk != nullptr && !chunk->isEmpty())
|
||||
{
|
||||
// Added parameter here specifies that we don't want to delete tile entities, as they won't get recreated unless they've got update packets
|
||||
// The tile entities are in general only created on the client by virtue of the chunk rebuild
|
||||
// Drop entities in the chunks, especially for the case when a player is dead
|
||||
// as they will not get the RemoveEntity packet if an entity is removed.
|
||||
// Don't delete tile entities, as they won't get recreated unless they've got
|
||||
// update packets. Tile entities are created on the client by the chunk rebuild.
|
||||
chunk->unload(false);
|
||||
|
||||
// 4J - We just want to clear out the entities in the chunk, but everything else should be valid
|
||||
// Keep chunk in cache with structural data intact.
|
||||
chunk->loaded = true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ public:
|
|||
void adjustPlayer(shared_ptr<Player> player);
|
||||
bool isCutScene();
|
||||
void setLocalMode(GameType *mode);
|
||||
GameType* getLocalPlayerMode() const { return localPlayerMode; }
|
||||
virtual void initPlayer(shared_ptr<Player> player);
|
||||
virtual bool canHurtPlayer();
|
||||
virtual bool destroyBlock(int x, int y, int z, int face);
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@
|
|||
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
||||
#include "../Minecraft.Server/ServerLogManager.h"
|
||||
#include "../Minecraft.Server/Access/Access.h"
|
||||
#include "../Minecraft.World/Socket.h"
|
||||
#include "..\Minecraft.Server/Security/SecurityConfig.h"
|
||||
#include "..\Minecraft.World\Socket.h"
|
||||
#endif
|
||||
// #ifdef __PS3__
|
||||
// #include "PS3/Network/NetworkPlayerSony.h"
|
||||
|
|
@ -150,6 +151,20 @@ void PendingConnection::sendPreLoginResponse()
|
|||
}
|
||||
}
|
||||
|
||||
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
||||
// Security: strip real XUIDs from pre-login response to prevent unauthenticated enumeration.
|
||||
// The client receives the correct player count but cannot identify who is connected.
|
||||
// Real XUID data is sent post-login via PlayerInfoPacket broadcasts.
|
||||
if (ServerRuntime::Security::GetSettings().hidePlayerListPreLogin)
|
||||
{
|
||||
for (DWORD i = 0; i < ugcXuidCount; ++i)
|
||||
{
|
||||
ugcXuids[i] = INVALID_XUID;
|
||||
}
|
||||
ugcFriendsOnlyBits = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
if (false)// server->onlineMode) // 4J - removed
|
||||
{
|
||||
|
|
@ -203,6 +218,56 @@ void PendingConnection::handleLogin(shared_ptr<LoginPacket> packet)
|
|||
duplicateXuid = true;
|
||||
}
|
||||
|
||||
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
||||
// Cross-reference: if someone claims the same XUID as an existing player from a different IP,
|
||||
// log and reject as a potential spoofing attempt.
|
||||
// Note: this runs on the main tick thread (via PendingConnection::tick -> Connection::tick ->
|
||||
// handleLogin), same thread that mutates the player list, so no lock is needed.
|
||||
if (!duplicateXuid && loginXuid != INVALID_XUID)
|
||||
{
|
||||
std::string newIp;
|
||||
unsigned char newSmallId = GetPendingConnectionSmallId(connection);
|
||||
bool hasNewIp = ServerRuntime::ServerLogManager::TryGetConnectionRemoteIp(newSmallId, &newIp);
|
||||
|
||||
for (auto &existingPlayer : server->getPlayers()->players)
|
||||
{
|
||||
if (existingPlayer == nullptr) continue;
|
||||
PlayerUID existingXuid = existingPlayer->connection->m_offlineXUID;
|
||||
if (existingXuid == INVALID_XUID) existingXuid = existingPlayer->connection->m_onlineXUID;
|
||||
if (existingXuid == loginXuid)
|
||||
{
|
||||
if (hasNewIp)
|
||||
{
|
||||
std::string existingIp;
|
||||
INetworkPlayer *np = existingPlayer->connection->getNetworkPlayer();
|
||||
if (np != nullptr)
|
||||
{
|
||||
unsigned char existingSmallId = np->GetSmallId();
|
||||
if (ServerRuntime::ServerLogManager::TryGetConnectionRemoteIp(existingSmallId, &existingIp))
|
||||
{
|
||||
if (existingIp != newIp)
|
||||
{
|
||||
app.DebugPrintf("SECURITY: XUID spoofing suspected - XUID 0x%016llx claimed from IP %s while already connected from IP %s\n",
|
||||
(unsigned long long)loginXuid, newIp.c_str(), existingIp.c_str());
|
||||
ServerRuntime::ServerLogManager::OnXuidSpoofDetected(newSmallId, name, newIp.c_str(), existingIp.c_str());
|
||||
duplicateXuid = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Cannot verify IP -- treat same-XUID connection as suspicious
|
||||
app.DebugPrintf("SECURITY: XUID 0x%016llx claimed but could not verify source IP\n",
|
||||
(unsigned long long)loginXuid);
|
||||
duplicateXuid = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
bool bannedXuid = false;
|
||||
if (loginXuid != INVALID_XUID)
|
||||
{
|
||||
|
|
@ -243,7 +308,11 @@ void PendingConnection::handleLogin(shared_ptr<LoginPacket> packet)
|
|||
else if (!whitelistSatisfied)
|
||||
{
|
||||
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
||||
// Cache name->XUID so `whitelist add <name>` can resolve the XUID
|
||||
ServerRuntime::ServerLogManager::CachePlayerXuid(name, loginXuid);
|
||||
ServerRuntime::ServerLogManager::OnRejectedPlayerLogin(GetPendingConnectionSmallId(connection), name, ServerRuntime::ServerLogManager::eLoginRejectReason_NotWhitelisted);
|
||||
app.DebugPrintf("WHITELIST: Rejected %ls (XUID: 0x%016llx) - use 'whitelist add %ls' to allow\n",
|
||||
name.c_str(), (unsigned long long)loginXuid, name.c_str());
|
||||
#endif
|
||||
disconnect(DisconnectPacket::eDisconnect_Banned);
|
||||
}
|
||||
|
|
@ -330,11 +399,17 @@ void PendingConnection::handleAcceptedLogin(shared_ptr<LoginPacket> packet)
|
|||
PlayerUID playerXuid = packet->m_offlineXuid;
|
||||
if(playerXuid == INVALID_XUID) playerXuid = packet->m_onlineXuid;
|
||||
|
||||
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
||||
// Cache name->XUID for console commands (whitelist add, revoketoken, etc.)
|
||||
ServerRuntime::ServerLogManager::CachePlayerXuid(name, playerXuid);
|
||||
#endif
|
||||
|
||||
shared_ptr<ServerPlayer> playerEntity = server->getPlayers()->getPlayerForLogin(this, name, playerXuid,packet->m_onlineXuid);
|
||||
if (playerEntity != nullptr)
|
||||
{
|
||||
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
||||
ServerRuntime::ServerLogManager::OnAcceptedPlayerLogin(GetPendingConnectionSmallId(connection), name);
|
||||
ServerRuntime::ServerLogManager::OnAcceptedPlayerLogin(GetPendingConnectionSmallId(connection), name,
|
||||
packet->m_offlineXuid, packet->m_onlineXuid, packet->m_isGuest);
|
||||
#endif
|
||||
server->getPlayers()->placeNewPlayer(connection, playerEntity, packet);
|
||||
connection = nullptr; // We've moved responsibility for this over to the new PlayerConnection, nullptr so we don't delete our reference to it here in our dtor
|
||||
|
|
|
|||
|
|
@ -487,37 +487,52 @@ void PlayerChunkMap::getChunkAndRemovePlayer(int x, int z, shared_ptr<ServerPlay
|
|||
}
|
||||
|
||||
// 4J - added - actually create & add player to a playerchunk, if there is one queued for this player.
|
||||
// Processes up to CHUNKS_PER_PLAYER_PER_TICK requests per call to speed up initial chunk loading.
|
||||
|
||||
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
||||
static const int CHUNKS_PER_PLAYER_PER_TICK = 16;
|
||||
#else
|
||||
static const int CHUNKS_PER_PLAYER_PER_TICK = 1;
|
||||
#endif
|
||||
|
||||
void PlayerChunkMap::tickAddRequests(shared_ptr<ServerPlayer> player)
|
||||
{
|
||||
if( addRequests.size() )
|
||||
{
|
||||
// Find the nearest chunk request to the player
|
||||
int px = static_cast<int>(player->x);
|
||||
int pz = static_cast<int>(player->z);
|
||||
int minDistSq = -1;
|
||||
|
||||
auto itNearest = addRequests.end();
|
||||
for (auto it = addRequests.begin(); it != addRequests.end(); it++)
|
||||
{
|
||||
if( it->player == player )
|
||||
for (int processed = 0; processed < CHUNKS_PER_PLAYER_PER_TICK; processed++)
|
||||
{
|
||||
// Find the nearest chunk request to the player
|
||||
int minDistSq = -1;
|
||||
auto itNearest = addRequests.end();
|
||||
for (auto it = addRequests.begin(); it != addRequests.end(); it++)
|
||||
{
|
||||
int xm = ( it->x * 16 ) + 8;
|
||||
int zm = ( it->z * 16 ) + 8;
|
||||
int distSq = (xm - px) * (xm - px) +
|
||||
(zm - pz) * (zm - pz);
|
||||
if( ( minDistSq == -1 ) || ( distSq < minDistSq ) )
|
||||
if( it->player == player )
|
||||
{
|
||||
minDistSq = distSq;
|
||||
itNearest = it;
|
||||
int xm = ( it->x * 16 ) + 8;
|
||||
int zm = ( it->z * 16 ) + 8;
|
||||
int distSq = (xm - px) * (xm - px) +
|
||||
(zm - pz) * (zm - pz);
|
||||
if( ( minDistSq == -1 ) || ( distSq < minDistSq ) )
|
||||
{
|
||||
minDistSq = distSq;
|
||||
itNearest = it;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we found one at all, then do this one
|
||||
if( itNearest != addRequests.end() )
|
||||
{
|
||||
getChunk(itNearest->x, itNearest->z, true)->add(itNearest->player);
|
||||
addRequests.erase(itNearest);
|
||||
// If we found one, process it and continue; otherwise done
|
||||
if( itNearest != addRequests.end() )
|
||||
{
|
||||
getChunk(itNearest->x, itNearest->z, true)->add(itNearest->player);
|
||||
addRequests.erase(itNearest);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -792,6 +807,14 @@ void PlayerChunkMap::setRadius(int newRadius)
|
|||
int xc = static_cast<int>(player->x) >> 4;
|
||||
int zc = static_cast<int>(player->z) >> 4;
|
||||
|
||||
for (auto it = addRequests.begin(); it != addRequests.end(); )
|
||||
{
|
||||
if (it->player == player)
|
||||
it = addRequests.erase(it);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
|
||||
for (int x = xc - newRadius; x <= xc + newRadius; x++)
|
||||
for (int z = zc - newRadius; z <= zc + newRadius; z++)
|
||||
{
|
||||
|
|
@ -801,6 +824,18 @@ void PlayerChunkMap::setRadius(int newRadius)
|
|||
getChunkAndAddPlayer(x, z, player);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove chunks that are outside the new radius
|
||||
for (int x = xc - radius; x <= xc + radius; x++)
|
||||
{
|
||||
for (int z = zc - radius; z <= zc + radius; z++)
|
||||
{
|
||||
if (x < xc - newRadius || x > xc + newRadius || z < zc - newRadius || z > zc + newRadius)
|
||||
{
|
||||
getChunkAndRemovePlayer(x, z, player);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,27 +16,28 @@
|
|||
#include "../Minecraft.World/net.minecraft.world.level.tile.entity.h"
|
||||
#include "../Minecraft.World/net.minecraft.world.level.saveddata.h"
|
||||
#include "../Minecraft.World/net.minecraft.world.entity.animal.h"
|
||||
#include "../Minecraft.World/net.minecraft.network.h"
|
||||
#include "../Minecraft.World/net.minecraft.world.food.h"
|
||||
#include "../Minecraft.World/AABB.h"
|
||||
#include "../Minecraft.World/Pos.h"
|
||||
#include "../Minecraft.World/SharedConstants.h"
|
||||
#include "../Minecraft.World/ChatPacket.h"
|
||||
#include "../Minecraft.World/StringHelpers.h"
|
||||
#include "../Minecraft.World/Socket.h"
|
||||
#include "../Minecraft.World/Achievements.h"
|
||||
#include "../Minecraft.World/net.minecraft.h"
|
||||
#include "EntityTracker.h"
|
||||
#include "../Minecraft.World/LevelData.h"
|
||||
#include "ServerConnection.h"
|
||||
#include "../Minecraft.World/GenericStats.h"
|
||||
#include "../Minecraft.World/JavaMath.h"
|
||||
|
||||
#include "..\Minecraft.World\ListTag.h"
|
||||
// 4J Added
|
||||
#include "../Minecraft.World/net.minecraft.world.item.crafting.h"
|
||||
#include "Options.h"
|
||||
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
||||
#include "../Minecraft.Server/ServerLogManager.h"
|
||||
#include "../Minecraft.Server/Access/Access.h"
|
||||
#include "../Minecraft.Server/Security/IdentityTokenManager.h"
|
||||
#include "../Minecraft.Server/Security/SecurityConfig.h"
|
||||
#include "../Minecraft.Server/Security/ConnectionCipher.h"
|
||||
extern bool g_Win64DedicatedServer;
|
||||
#endif
|
||||
|
||||
namespace
|
||||
|
|
@ -85,6 +86,9 @@ PlayerConnection::PlayerConnection(MinecraftServer *server, Connection *connecti
|
|||
m_onlineXUID = INVALID_XUID;
|
||||
m_bHasClientTickedOnce = false;
|
||||
m_logSmallId = 0;
|
||||
m_identityVerified = false;
|
||||
m_identityChallengeTick = -1;
|
||||
m_securityGateOpen = true; // default open; closed when cipher is required
|
||||
|
||||
// Cache the first valid transport smallId because disconnect teardown can clear it before the server logger runs.
|
||||
if (this->connection != NULL && this->connection->getSocket() != NULL)
|
||||
|
|
@ -143,6 +147,14 @@ void PlayerConnection::tick()
|
|||
{
|
||||
dropSpamTickCount--;
|
||||
}
|
||||
|
||||
// Ensure server-side player tick runs even when no move packet was received this tick.
|
||||
// Without this, environmental damage (drowning, fire, lava) is never applied to clients
|
||||
// that don't send frequent move packets.
|
||||
if (!didTick && player != nullptr)
|
||||
{
|
||||
player->doTick(false);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerConnection::disconnect(DisconnectPacket::eDisconnectReason reason)
|
||||
|
|
@ -483,9 +495,9 @@ void PlayerConnection::handlePlayerAction(shared_ptr<PlayerActionPacket> packet)
|
|||
}
|
||||
else if (packet->action == PlayerActionPacket::STOP_DESTROY_BLOCK)
|
||||
{
|
||||
player->gameMode->stopDestroyBlock(x, y, z);
|
||||
bool destroyed = player->gameMode->stopDestroyBlock(x, y, z);
|
||||
server->getPlayers()->prioritiseTileChanges(x, y, z, level->dimension->id); // 4J added - make sure that the update packets for this get prioritised over other general world updates
|
||||
if (level->getTile(x, y, z) != 0) player->connection->send(std::make_shared<TileUpdatePacket>(x, y, z, level));
|
||||
if (!destroyed && level->getTile(x, y, z) != 0) player->connection->send(std::make_shared<TileUpdatePacket>(x, y, z, level));
|
||||
}
|
||||
else if (packet->action == PlayerActionPacket::ABORT_DESTROY_BLOCK)
|
||||
{
|
||||
|
|
@ -612,6 +624,22 @@ void PlayerConnection::onDisconnect(DisconnectPacket::eDisconnectReason reason,
|
|||
LeaveCriticalSection(&done_cs);
|
||||
}
|
||||
|
||||
void PlayerConnection::openSecurityGate()
|
||||
{
|
||||
if (m_securityGateOpen)
|
||||
return;
|
||||
|
||||
m_securityGateOpen = true;
|
||||
|
||||
// Flush all buffered packets now that the cipher is active
|
||||
for (auto &buffered : m_securityBuffer)
|
||||
{
|
||||
send(buffered);
|
||||
}
|
||||
m_securityBuffer.clear();
|
||||
m_securityBuffer.shrink_to_fit();
|
||||
}
|
||||
|
||||
void PlayerConnection::onUnhandledPacket(shared_ptr<Packet> packet)
|
||||
{
|
||||
// logger.warning(getClass() + " wasn't prepared to deal with a " + packet.getClass());
|
||||
|
|
@ -622,6 +650,39 @@ void PlayerConnection::send(shared_ptr<Packet> packet)
|
|||
{
|
||||
if( connection->getSocket() != nullptr )
|
||||
{
|
||||
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
||||
// Security gate: when require-secure-client is enabled, buffer ALL outgoing
|
||||
// packets until the cipher handshake completes. Only the cipher handshake
|
||||
// CustomPayloadPacket (MC|CKey) is sent immediately. Once the cipher activates,
|
||||
// openSecurityGate() flushes the buffer. This prevents unsecured/old clients
|
||||
// from receiving any game data (PlayerInfoPackets, XUIDs, etc.) before being kicked.
|
||||
if (!m_securityGateOpen)
|
||||
{
|
||||
// Allow cipher handshake packets through immediately
|
||||
if (packet->getId() == 250)
|
||||
{
|
||||
auto cpp = dynamic_pointer_cast<CustomPayloadPacket>(packet);
|
||||
if (cpp != nullptr &&
|
||||
(cpp->identifier == CustomPayloadPacket::CIPHER_KEY_CHANNEL ||
|
||||
cpp->identifier == CustomPayloadPacket::CIPHER_ACK_CHANNEL ||
|
||||
cpp->identifier == CustomPayloadPacket::CIPHER_ON_CHANNEL))
|
||||
{
|
||||
// Fall through to send
|
||||
}
|
||||
else
|
||||
{
|
||||
m_securityBuffer.push_back(packet);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_securityBuffer.push_back(packet);
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if( !server->getPlayers()->canReceiveAllPackets( player ) )
|
||||
{
|
||||
// Check if we are allowed to send this packet type
|
||||
|
|
@ -817,9 +878,7 @@ void PlayerConnection::handleInteract(shared_ptr<InteractPacket> packet)
|
|||
{
|
||||
if ((target->GetType() == eTYPE_ITEMENTITY) || (target->GetType() == eTYPE_EXPERIENCEORB) || (target->GetType() == eTYPE_ARROW) || target == player)
|
||||
{
|
||||
//disconnect("Attempting to attack an invalid entity");
|
||||
//server.warn("Player " + player.getName() + " tried to attack an invalid entity");
|
||||
return;
|
||||
return;
|
||||
}
|
||||
player->attack(target);
|
||||
}
|
||||
|
|
@ -1064,10 +1123,19 @@ void PlayerConnection::handleServerSettingsChanged(shared_ptr<ServerSettingsChan
|
|||
{
|
||||
if(packet->action==ServerSettingsChangedPacket::HOST_IN_GAME_SETTINGS)
|
||||
{
|
||||
// Need to check that this player has permission to change each individual setting?
|
||||
|
||||
INetworkPlayer *networkPlayer = getNetworkPlayer();
|
||||
if( (networkPlayer != nullptr && networkPlayer->IsHost()) || player->isModerator())
|
||||
bool isHost = (networkPlayer != nullptr && networkPlayer->IsHost());
|
||||
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
||||
// On dedicated servers, only the host can change server settings.
|
||||
// Moderators (OPs) should not be able to alter game rules.
|
||||
if (!isHost)
|
||||
{
|
||||
app.DebugPrintf("SECURITY: Non-host player %ls attempted to change server settings\n",
|
||||
player->getName().c_str());
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
if( isHost || player->isModerator())
|
||||
{
|
||||
app.SetGameHostOption(eGameHostOption_FireSpreads, app.GetGameHostOption(packet->data,eGameHostOption_FireSpreads));
|
||||
app.SetGameHostOption(eGameHostOption_TNT, app.GetGameHostOption(packet->data,eGameHostOption_TNT));
|
||||
|
|
@ -1090,14 +1158,81 @@ void PlayerConnection::handleServerSettingsChanged(shared_ptr<ServerSettingsChan
|
|||
void PlayerConnection::handleKickPlayer(shared_ptr<KickPlayerPacket> packet)
|
||||
{
|
||||
INetworkPlayer *networkPlayer = getNetworkPlayer();
|
||||
if( (networkPlayer != nullptr && networkPlayer->IsHost()) || player->isModerator())
|
||||
bool isHost = (networkPlayer != nullptr && networkPlayer->IsHost());
|
||||
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
||||
// Live ops.json check for non-host players
|
||||
if (!isHost)
|
||||
{
|
||||
PlayerUID kickerXuid = m_offlineXUID;
|
||||
if (kickerXuid == INVALID_XUID) kickerXuid = m_onlineXUID;
|
||||
if (!ServerRuntime::Access::IsPlayerOp(kickerXuid))
|
||||
{
|
||||
app.DebugPrintf("SECURITY: Non-OP player %ls attempted to kick\n", player->getName().c_str());
|
||||
{
|
||||
INetworkPlayer *npLog = getNetworkPlayer();
|
||||
if (npLog != nullptr)
|
||||
ServerRuntime::ServerLogManager::OnUnauthorizedCommand(npLog->GetSmallId(), player->getName(), "kick");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if( isHost || player->isModerator())
|
||||
{
|
||||
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
||||
// On dedicated servers, non-host moderators cannot kick other moderators or the host.
|
||||
if (!isHost)
|
||||
{
|
||||
for (auto &checkingPlayer : server->getPlayers()->players)
|
||||
{
|
||||
if (checkingPlayer != nullptr &&
|
||||
checkingPlayer->connection->getNetworkPlayer() != nullptr &&
|
||||
checkingPlayer->connection->getNetworkPlayer()->GetSmallId() == packet->m_networkSmallId)
|
||||
{
|
||||
if (checkingPlayer->isModerator() ||
|
||||
checkingPlayer->connection->getNetworkPlayer()->IsHost())
|
||||
{
|
||||
app.DebugPrintf("SECURITY: Moderator %ls tried to kick host/moderator %ls\n",
|
||||
player->getName().c_str(), checkingPlayer->getName().c_str());
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
app.DebugPrintf("CMD: Player %ls kicked player with smallId=%d\n",
|
||||
player->getName().c_str(), packet->m_networkSmallId);
|
||||
#endif
|
||||
server->getPlayers()->kickPlayerByShortId(packet->m_networkSmallId);
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerConnection::handleGameCommand(shared_ptr<GameCommandPacket> packet)
|
||||
{
|
||||
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
||||
INetworkPlayer *networkPlayer = getNetworkPlayer();
|
||||
bool isHost = (networkPlayer != nullptr && networkPlayer->IsHost());
|
||||
if (!isHost)
|
||||
{
|
||||
// Live ops.json check - in-memory isModerator() can be stale if ops.json was edited mid-session
|
||||
PlayerUID cmdXuid = m_offlineXUID;
|
||||
if (cmdXuid == INVALID_XUID) cmdXuid = m_onlineXUID;
|
||||
if (!ServerRuntime::Access::IsPlayerOp(cmdXuid))
|
||||
{
|
||||
app.DebugPrintf("SECURITY: Non-OP player %ls attempted server command id=%d\n",
|
||||
player->getName().c_str(), static_cast<int>(packet->command));
|
||||
{
|
||||
INetworkPlayer *npLog = getNetworkPlayer();
|
||||
if (npLog != nullptr)
|
||||
ServerRuntime::ServerLogManager::OnUnauthorizedCommand(npLog->GetSmallId(), player->getName(), "game-command");
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
app.DebugPrintf("CMD: Player %ls (OP=%d, Host=%d) executed command id=%d\n",
|
||||
player->getName().c_str(), player->isModerator() ? 1 : 0, isHost ? 1 : 0,
|
||||
static_cast<int>(packet->command));
|
||||
#endif
|
||||
MinecraftServer::getInstance()->getCommandDispatcher()->performCommand(player, packet->command, packet->data);
|
||||
}
|
||||
|
||||
|
|
@ -1110,22 +1245,12 @@ void PlayerConnection::handleClientCommand(shared_ptr<ClientCommandPacket> packe
|
|||
{
|
||||
player = server->getPlayers()->respawn(player, player->m_enteredEndExitPortal?0:player->dimension, true);
|
||||
}
|
||||
//else if (player.getLevel().getLevelData().isHardcore())
|
||||
//{
|
||||
// if (server.isSingleplayer() && player.name.equals(server.getSingleplayerName()))
|
||||
// {
|
||||
// player.connection.disconnect("You have died. Game over, man, it's game over!");
|
||||
// server.selfDestruct();
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// BanEntry ban = new BanEntry(player.name);
|
||||
// ban.setReason("Death in Hardcore");
|
||||
|
||||
// server.getPlayers().getBans().add(ban);
|
||||
// player.connection.disconnect("You have died. Game over, man, it's game over!");
|
||||
// }
|
||||
//}
|
||||
else if (player->level->getLevelData()->isHardcore())
|
||||
{
|
||||
// Hardcore mode — server rejects respawn. Ban and disconnect are already
|
||||
// handled in ServerPlayer::die() via banPlayerForHardcoreDeath().
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (player->getHealth() > 0) return;
|
||||
|
|
@ -1377,10 +1502,21 @@ void PlayerConnection::handleKeepAlive(shared_ptr<KeepAlivePacket> packet)
|
|||
|
||||
void PlayerConnection::handlePlayerInfo(shared_ptr<PlayerInfoPacket> packet)
|
||||
{
|
||||
// Need to check that this player has permission to change each individual setting?
|
||||
|
||||
INetworkPlayer *networkPlayer = getNetworkPlayer();
|
||||
if( (networkPlayer != nullptr && networkPlayer->IsHost()) || player->isModerator() )
|
||||
bool isHost = (networkPlayer != nullptr && networkPlayer->IsHost());
|
||||
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
||||
// Live ops.json check for non-host players
|
||||
if (!isHost)
|
||||
{
|
||||
PlayerUID infoXuid = m_offlineXUID;
|
||||
if (infoXuid == INVALID_XUID) infoXuid = m_onlineXUID;
|
||||
if (!ServerRuntime::Access::IsPlayerOp(infoXuid))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if( isHost || player->isModerator() )
|
||||
{
|
||||
shared_ptr<ServerPlayer> serverPlayer;
|
||||
// Find the player being edited
|
||||
|
|
@ -1458,7 +1594,24 @@ void PlayerConnection::handlePlayerInfo(shared_ptr<PlayerInfoPacket> packet)
|
|||
serverPlayer->setPlayerGamePrivilege(Player::ePlayerGamePrivilege_CanToggleClassicHunger,Player::getPlayerGamePrivilege(packet->m_playerPrivileges,Player::ePlayerGamePrivilege_CanToggleClassicHunger) );
|
||||
serverPlayer->setPlayerGamePrivilege(Player::ePlayerGamePrivilege_CanTeleport,Player::getPlayerGamePrivilege(packet->m_playerPrivileges,Player::ePlayerGamePrivilege_CanTeleport) );
|
||||
}
|
||||
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
||||
// On dedicated servers, OP can only be granted/revoked if the target is in ops.json.
|
||||
// This prevents runtime OP escalation via crafted PlayerInfoPackets.
|
||||
bool wantsOp = Player::getPlayerGamePrivilege(packet->m_playerPrivileges, Player::ePlayerGamePrivilege_Op) != 0;
|
||||
PlayerUID targetXuid = serverPlayer->connection->m_offlineXUID;
|
||||
if (targetXuid == INVALID_XUID) targetXuid = serverPlayer->connection->m_onlineXUID;
|
||||
if (wantsOp && !ServerRuntime::Access::IsPlayerOp(targetXuid))
|
||||
{
|
||||
app.DebugPrintf("SECURITY: Host tried to OP player %ls who is not in ops.json\n",
|
||||
serverPlayer->getName().c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
serverPlayer->setPlayerGamePrivilege(Player::ePlayerGamePrivilege_Op, wantsOp ? 1u : 0u);
|
||||
}
|
||||
#else
|
||||
serverPlayer->setPlayerGamePrivilege(Player::ePlayerGamePrivilege_Op,Player::getPlayerGamePrivilege(packet->m_playerPrivileges,Player::ePlayerGamePrivilege_Op) );
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1496,6 +1649,46 @@ void PlayerConnection::handlePlayerAbilities(shared_ptr<PlayerAbilitiesPacket> p
|
|||
|
||||
void PlayerConnection::handleCustomPayload(shared_ptr<CustomPayloadPacket> customPayloadPacket)
|
||||
{
|
||||
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
||||
// Identity token response from client
|
||||
if (CustomPayloadPacket::IDENTITY_TOKEN_RESPONSE.compare(customPayloadPacket->identifier) == 0)
|
||||
{
|
||||
PlayerUID xuid = m_offlineXUID;
|
||||
if (xuid == INVALID_XUID) xuid = m_onlineXUID;
|
||||
|
||||
bool tokenValid = false;
|
||||
if (customPayloadPacket->length == ServerRuntime::Security::IdentityTokenManager::TOKEN_SIZE &&
|
||||
customPayloadPacket->data.length == ServerRuntime::Security::IdentityTokenManager::TOKEN_SIZE &&
|
||||
customPayloadPacket->data.data != nullptr)
|
||||
{
|
||||
tokenValid = ServerRuntime::Security::GetIdentityTokenManager().VerifyToken(xuid, customPayloadPacket->data.data);
|
||||
}
|
||||
|
||||
if (tokenValid)
|
||||
{
|
||||
m_identityVerified = true;
|
||||
app.DebugPrintf("SECURITY: Identity token verified for player %ls\n", player->getName().c_str());
|
||||
INetworkPlayer *npLog = getNetworkPlayer();
|
||||
if (npLog != nullptr)
|
||||
ServerRuntime::ServerLogManager::OnIdentityTokenVerified(npLog->GetSmallId());
|
||||
}
|
||||
else
|
||||
{
|
||||
app.DebugPrintf("SECURITY: Identity token MISMATCH for player %ls - will disconnect\n", player->getName().c_str());
|
||||
app.DebugPrintf("SECURITY: If this player lost their token, use: revoketoken %ls\n", player->getName().c_str());
|
||||
INetworkPlayer *npLog = getNetworkPlayer();
|
||||
if (npLog != nullptr)
|
||||
ServerRuntime::ServerLogManager::OnIdentityTokenMismatch(npLog->GetSmallId(), player->getName());
|
||||
// Defer disconnect to avoid re-entrancy issues during packet dispatch
|
||||
setWasKicked();
|
||||
closeOnTick();
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
if (CustomPayloadPacket.CUSTOM_BOOK_PACKET.equals(customPayloadPacket.identifier))
|
||||
if (CustomPayloadPacket::CUSTOM_BOOK_PACKET.compare(customPayloadPacket->identifier) == 0)
|
||||
{
|
||||
ByteArrayInputStream bais(customPayloadPacket->data);
|
||||
|
|
@ -1535,7 +1728,9 @@ void PlayerConnection::handleCustomPayload(shared_ptr<CustomPayloadPacket> custo
|
|||
player->inventory->setItem(player->inventory->selected, sentItem);
|
||||
}
|
||||
}
|
||||
else if (CustomPayloadPacket::TRADER_SELECTION_PACKET.compare(customPayloadPacket->identifier) == 0)
|
||||
else
|
||||
#endif
|
||||
if (CustomPayloadPacket::TRADER_SELECTION_PACKET.compare(customPayloadPacket->identifier) == 0)
|
||||
{
|
||||
ByteArrayInputStream bais(customPayloadPacket->data);
|
||||
DataInputStream input(&bais);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include "ConsoleInputSource.h"
|
||||
#include "../Minecraft.World/PacketListener.h"
|
||||
#include "../Minecraft.World/JavaIntHash.h"
|
||||
#include <atomic>
|
||||
|
||||
class MinecraftServer;
|
||||
class Connection;
|
||||
|
|
@ -130,15 +131,30 @@ public:
|
|||
|
||||
void setShowOnMaps(bool bVal);
|
||||
|
||||
void setWasKicked() { m_bWasKicked = true; }
|
||||
bool getWasKicked() { return m_bWasKicked; }
|
||||
void setWasKicked() { m_bWasKicked.store(true); }
|
||||
bool getWasKicked() { return m_bWasKicked.load(); }
|
||||
|
||||
// 4J Added
|
||||
bool hasClientTickedOnce() { return m_bHasClientTickedOnce; }
|
||||
|
||||
// Identity token verification state (accessed from both recv and main threads)
|
||||
std::atomic<bool> m_identityVerified;
|
||||
std::atomic<int> m_identityChallengeTick;
|
||||
|
||||
// Security gate: buffer packets until cipher handshake completes
|
||||
bool m_securityGateOpen;
|
||||
vector<shared_ptr<Packet>> m_securityBuffer;
|
||||
|
||||
bool isIdentityVerified() const { return m_identityVerified; }
|
||||
int getIdentityChallengeTick() const { return m_identityChallengeTick; }
|
||||
void setIdentityChallengeTick(int tick) { m_identityChallengeTick = tick; }
|
||||
void setIdentityVerified(bool v) { m_identityVerified = v; }
|
||||
bool isSecurityGateOpen() const { return m_securityGateOpen; }
|
||||
void openSecurityGate();
|
||||
|
||||
private:
|
||||
bool m_bCloseOnTick;
|
||||
vector<wstring> m_texturesRequested;
|
||||
|
||||
bool m_bWasKicked;
|
||||
std::atomic<bool> m_bWasKicked{false};
|
||||
};
|
||||
|
|
@ -39,7 +39,17 @@
|
|||
|
||||
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
||||
#include "../Minecraft.Server/Access/Access.h"
|
||||
#include "../Minecraft.Server/Common\StringUtils.h"
|
||||
#include "../Minecraft.Server/ServerLogger.h"
|
||||
#include "../Minecraft.Server/ServerLogManager.h"
|
||||
#include "../Minecraft.Server/ServerProperties.h"
|
||||
#include "../Minecraft.Server/Security/SecurityConfig.h"
|
||||
#include "../Minecraft.Server/Security/ConnectionCipher.h"
|
||||
#include "../Minecraft.Server/Security/CipherHandshakeEnforcer.h"
|
||||
#include "../Minecraft.Server/Security/IdentityTokenManager.h"
|
||||
extern bool g_Win64DedicatedServer;
|
||||
static unsigned int s_playerListTickCount = 0;
|
||||
static const int kIdentityResponseGraceTicks = 200; // 10 seconds at 20 TPS
|
||||
#endif
|
||||
|
||||
// 4J - this class is fairly substantially altered as there didn't seem any point in porting code for banning, whitelisting, ops etc.
|
||||
|
|
@ -67,6 +77,7 @@ PlayerList::PlayerList(MinecraftServer *server)
|
|||
int rawMax = server->settings->getInt(L"max-players", 8);
|
||||
maxPlayers = static_cast<unsigned int>(Mth::clamp(rawMax, 1, MINECRAFT_NET_MAX_PLAYERS));
|
||||
doWhiteList = false;
|
||||
InitializeCriticalSection(&m_banCS);
|
||||
InitializeCriticalSection(&m_kickPlayersCS);
|
||||
InitializeCriticalSection(&m_closePlayersCS);
|
||||
}
|
||||
|
|
@ -80,6 +91,7 @@ PlayerList::~PlayerList()
|
|||
player->gameMode = nullptr;
|
||||
}
|
||||
|
||||
DeleteCriticalSection(&m_banCS);
|
||||
DeleteCriticalSection(&m_kickPlayersCS);
|
||||
DeleteCriticalSection(&m_closePlayersCS);
|
||||
}
|
||||
|
|
@ -271,12 +283,19 @@ bool PlayerList::placeNewPlayer(Connection *connection, shared_ptr<ServerPlayer>
|
|||
static_cast<BYTE>(playerIndex), level->useNewSeaLevel(),
|
||||
player->getAllPlayerGamePrivileges(),
|
||||
level->getLevelData()->getXZSize(),
|
||||
level->getLevelData()->getHellScale()));
|
||||
level->getLevelData()->getHellScale(),
|
||||
level->getLevelData()->isHardcore()));
|
||||
playerConnection->send(std::make_shared<SetSpawnPositionPacket>(spawnPos->x, spawnPos->y, spawnPos->z));
|
||||
playerConnection->send(std::make_shared<PlayerAbilitiesPacket>(&player->abilities));
|
||||
playerConnection->send(std::make_shared<SetCarriedItemPacket>(player->inventory->selected));
|
||||
delete spawnPos;
|
||||
|
||||
// Identify this server as a fork so the client can enable extended
|
||||
// features (e.g. render-distance-independent player list). Upstream
|
||||
// clients will silently ignore the unknown channel.
|
||||
playerConnection->send(std::make_shared<CustomPayloadPacket>(
|
||||
CustomPayloadPacket::FORK_HELLO_CHANNEL, byteArray()));
|
||||
|
||||
updateEntireScoreboard(reinterpret_cast<ServerScoreboard *>(level->getScoreboard()), player);
|
||||
|
||||
sendLevelInfo(player, level);
|
||||
|
|
@ -331,6 +350,49 @@ bool PlayerList::placeNewPlayer(Connection *connection, shared_ptr<ServerPlayer>
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
||||
// Initiate stream cipher handshake if enabled.
|
||||
// Send MC|CKey with the generated key. Old clients will ignore the unknown channel.
|
||||
if (g_Win64DedicatedServer && ServerRuntime::Security::GetSettings().enableStreamCipher)
|
||||
{
|
||||
BYTE smallId = 0;
|
||||
Socket *cipherSock = connection->getSocket();
|
||||
INetworkPlayer *cipherNp = cipherSock ? cipherSock->getPlayer() : nullptr;
|
||||
if (cipherNp != nullptr && !cipherNp->IsLocal())
|
||||
{
|
||||
smallId = cipherNp->GetSmallId();
|
||||
uint8_t key[ServerRuntime::Security::StreamCipher::KEY_SIZE];
|
||||
if (ServerRuntime::Security::GetCipherRegistry().PrepareKey(smallId, key))
|
||||
{
|
||||
byteArray keyData(ServerRuntime::Security::StreamCipher::KEY_SIZE);
|
||||
memcpy(keyData.data, key, ServerRuntime::Security::StreamCipher::KEY_SIZE);
|
||||
playerConnection->send(std::make_shared<CustomPayloadPacket>(
|
||||
CustomPayloadPacket::CIPHER_KEY_CHANNEL, keyData));
|
||||
SecureZeroMemory(key, sizeof(key));
|
||||
app.DebugPrintf("Server: Sent MC|CKey to player %ls (smallId=%d)\n",
|
||||
player->getName().c_str(), smallId);
|
||||
|
||||
// Register with enforcer for timeout tracking
|
||||
if (ServerRuntime::Security::GetSettings().requireSecureClient)
|
||||
{
|
||||
ServerRuntime::Security::GetHandshakeEnforcer().OnCipherKeySent(smallId, s_playerListTickCount);
|
||||
}
|
||||
}
|
||||
|
||||
// Close the security gate AFTER sending the essential login sequence
|
||||
// and MC|CKey. The login setup packets (LoginPacket, spawn position,
|
||||
// abilities, chunks, teleport) must arrive in plaintext before the
|
||||
// cipher handshake completes. Only subsequent tick data is buffered
|
||||
// until the handshake finishes and openSecurityGate() flushes.
|
||||
if (ServerRuntime::Security::GetSettings().requireSecureClient)
|
||||
{
|
||||
playerConnection->m_securityGateOpen = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -563,6 +625,16 @@ void PlayerList::move(shared_ptr<ServerPlayer> player)
|
|||
|
||||
void PlayerList::remove(shared_ptr<ServerPlayer> player)
|
||||
{
|
||||
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
||||
if (g_Win64DedicatedServer && player->connection != nullptr)
|
||||
{
|
||||
INetworkPlayer *np = player->connection->getNetworkPlayer();
|
||||
if (np != nullptr)
|
||||
{
|
||||
ServerRuntime::Security::GetHandshakeEnforcer().OnDisconnected(np->GetSmallId());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
save(player);
|
||||
//4J Stu - We don't want to save the map data for guests, so when we are sure that the player is gone delete the map
|
||||
if(player->isGuest()) playerIo->deleteMapFilesForPlayer(player);
|
||||
|
|
@ -588,7 +660,17 @@ if (player->riding != nullptr)
|
|||
{
|
||||
players.erase(it);
|
||||
}
|
||||
//broadcastAll(shared_ptr<PlayerInfoPacket>( new PlayerInfoPacket(player->name, false, 9999) ) );
|
||||
// Notify fork clients that this player has left the server so they can
|
||||
// clean up IQNet/Tab list entries. Uses a custom payload channel so the
|
||||
// wire format of existing packets is unchanged (upstream clients simply
|
||||
// ignore the unknown channel).
|
||||
{
|
||||
const wstring& name = player->getName();
|
||||
byteArray payload(static_cast<int>(name.size() * sizeof(wchar_t)));
|
||||
memcpy(payload.data, name.c_str(), payload.length);
|
||||
broadcastAll(std::make_shared<CustomPayloadPacket>(
|
||||
CustomPayloadPacket::FORK_PLAYER_LEAVE_CHANNEL, payload));
|
||||
}
|
||||
|
||||
removePlayerFromReceiving(player);
|
||||
player->connection = nullptr; // Must remove reference to connection, or else there is a circular dependency
|
||||
|
|
@ -754,6 +836,13 @@ shared_ptr<ServerPlayer> PlayerList::respawn(shared_ptr<ServerPlayer> serverPlay
|
|||
// necessary)
|
||||
updatePlayerGameMode(player, serverPlayer, level);
|
||||
|
||||
// 4J Added: Hardcore mode — force Adventure mode on respawn
|
||||
if (server->getLevel(0)->getLevelData()->isHardcore())
|
||||
{
|
||||
player->gameMode->setGameModeForPlayer(GameType::ADVENTURE);
|
||||
player->connection->send(std::make_shared<GameEventPacket>(GameEventPacket::CHANGE_GAME_MODE, GameType::ADVENTURE->getId()));
|
||||
}
|
||||
|
||||
if(serverPlayer->wonGame && targetDimension == oldDimension && serverPlayer->getHealth() > 0)
|
||||
{
|
||||
// If the player is still alive and respawning to the same dimension, they are just being added back from someone else viewing the Win screen
|
||||
|
|
@ -791,7 +880,7 @@ shared_ptr<ServerPlayer> PlayerList::respawn(shared_ptr<ServerPlayer> serverPlay
|
|||
|
||||
player->connection->send( std::make_shared<RespawnPacket>( static_cast<char>(player->dimension), player->level->getSeed(), player->level->getMaxBuildHeight(),
|
||||
player->gameMode->getGameModeForPlayer(), level->difficulty, level->getLevelData()->getGenerator(),
|
||||
player->level->useNewSeaLevel(), player->entityId, level->getLevelData()->getXZSize(), level->getLevelData()->getHellScale() ) );
|
||||
player->level->useNewSeaLevel(), player->entityId, level->getLevelData()->getXZSize(), level->getLevelData()->getHellScale(), level->getLevelData()->isHardcore() ) );
|
||||
player->connection->teleport(player->x, player->y, player->z, player->yRot, player->xRot);
|
||||
player->connection->send( std::make_shared<SetExperiencePacket>( player->experienceProgress, player->totalExperience, player->experienceLevel) );
|
||||
|
||||
|
|
@ -908,7 +997,7 @@ void PlayerList::toggleDimension(shared_ptr<ServerPlayer> player, int targetDime
|
|||
|
||||
player->connection->send(std::make_shared<RespawnPacket>(static_cast<char>(player->dimension), newLevel->getSeed(), newLevel->getMaxBuildHeight(),
|
||||
player->gameMode->getGameModeForPlayer(), newLevel->difficulty, newLevel->getLevelData()->getGenerator(),
|
||||
newLevel->useNewSeaLevel(), player->entityId, newLevel->getLevelData()->getXZSize(), newLevel->getLevelData()->getHellScale()));
|
||||
newLevel->useNewSeaLevel(), player->entityId, newLevel->getLevelData()->getXZSize(), newLevel->getLevelData()->getHellScale(), newLevel->getLevelData()->isHardcore()));
|
||||
|
||||
oldLevel->removeEntityImmediately(player);
|
||||
player->removed = false;
|
||||
|
|
@ -1003,15 +1092,16 @@ void PlayerList::repositionAcrossDimension(shared_ptr<Entity> entity, int lastDi
|
|||
addPlayerToReceiving(player);
|
||||
}
|
||||
|
||||
if (lastDimension != 1)
|
||||
xt = static_cast<double>(Mth::clamp(static_cast<int>(xt), -Level::MAX_LEVEL_SIZE + 128, Level::MAX_LEVEL_SIZE - 128));
|
||||
zt = static_cast<double>(Mth::clamp(static_cast<int>(zt), -Level::MAX_LEVEL_SIZE + 128, Level::MAX_LEVEL_SIZE - 128));
|
||||
if (entity->isAlive())
|
||||
{
|
||||
xt = static_cast<double>(Mth::clamp(static_cast<int>(xt), -Level::MAX_LEVEL_SIZE + 128, Level::MAX_LEVEL_SIZE - 128));
|
||||
zt = static_cast<double>(Mth::clamp(static_cast<int>(zt), -Level::MAX_LEVEL_SIZE + 128, Level::MAX_LEVEL_SIZE - 128));
|
||||
if (entity->isAlive())
|
||||
newLevel->addEntity(entity);
|
||||
entity->moveTo(xt, entity->y, zt, entity->yRot, entity->xRot);
|
||||
newLevel->tick(entity, false);
|
||||
// Portal forcing only for non-End exits (End exits go to spawn, not a portal)
|
||||
if (lastDimension != 1)
|
||||
{
|
||||
newLevel->addEntity(entity);
|
||||
entity->moveTo(xt, entity->y, zt, entity->yRot, entity->xRot);
|
||||
newLevel->tick(entity, false);
|
||||
newLevel->cache->autoCreate = true;
|
||||
newLevel->getPortalForcer()->force(entity, xOriginal, yOriginal, zOriginal, yRotOriginal);
|
||||
newLevel->cache->autoCreate = false;
|
||||
|
|
@ -1023,6 +1113,131 @@ void PlayerList::repositionAcrossDimension(shared_ptr<Entity> entity, int lastDi
|
|||
|
||||
void PlayerList::tick()
|
||||
{
|
||||
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
||||
++s_playerListTickCount;
|
||||
|
||||
// Cipher handshake enforcement: kick clients that haven't completed the handshake
|
||||
if (g_Win64DedicatedServer &&
|
||||
ServerRuntime::Security::GetSettings().enableStreamCipher &&
|
||||
ServerRuntime::Security::GetSettings().requireSecureClient)
|
||||
{
|
||||
std::vector<unsigned char> expired;
|
||||
std::vector<unsigned char> completed;
|
||||
ServerRuntime::Security::GetHandshakeEnforcer().CheckTimeouts(s_playerListTickCount, expired, completed);
|
||||
|
||||
for (unsigned char smallId : expired)
|
||||
{
|
||||
app.DebugPrintf("SECURITY: Kicking unsecured client (smallId=%d) - cipher handshake timed out\n", smallId);
|
||||
ServerRuntime::ServerLogManager::OnUnsecuredClientKicked(smallId);
|
||||
EnterCriticalSection(&m_closePlayersCS);
|
||||
m_smallIdsToClose.push_back(smallId);
|
||||
LeaveCriticalSection(&m_closePlayersCS);
|
||||
}
|
||||
|
||||
// Report cipher completion and open security gate for all completed handshakes
|
||||
for (unsigned char smallId : completed)
|
||||
{
|
||||
// Open the security gate -- flush buffered game packets now that cipher is active
|
||||
for (auto &p : players)
|
||||
{
|
||||
if (p == nullptr || p->connection == nullptr) continue;
|
||||
INetworkPlayer *np = p->connection->getNetworkPlayer();
|
||||
if (np != nullptr && np->GetSmallId() == smallId)
|
||||
{
|
||||
if (!p->connection->isSecurityGateOpen())
|
||||
{
|
||||
p->connection->openSecurityGate();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ServerRuntime::Security::GetSettings().requireChallengeToken)
|
||||
{
|
||||
ServerRuntime::ServerLogManager::OnCipherHandshakeCompleted(smallId);
|
||||
}
|
||||
else
|
||||
{
|
||||
ServerRuntime::ServerLogManager::OnCipherCompletedNoTokenRequired(smallId);
|
||||
}
|
||||
}
|
||||
|
||||
// For newly-completed cipher handshakes, initiate identity token exchange
|
||||
if (ServerRuntime::Security::GetSettings().requireChallengeToken)
|
||||
{
|
||||
for (unsigned char smallId : completed)
|
||||
{
|
||||
// Find the player by smallId
|
||||
for (auto &p : players)
|
||||
{
|
||||
if (p == nullptr || p->connection == nullptr) continue;
|
||||
INetworkPlayer *np = p->connection->getNetworkPlayer();
|
||||
if (np == nullptr || np->GetSmallId() != smallId) continue;
|
||||
|
||||
PlayerUID xuid = p->connection->m_offlineXUID;
|
||||
if (xuid == INVALID_XUID) xuid = p->connection->m_onlineXUID;
|
||||
|
||||
if (p->connection->getIdentityChallengeTick() >= 0)
|
||||
{
|
||||
// Already challenged, skip
|
||||
}
|
||||
else if (ServerRuntime::Security::GetIdentityTokenManager().HasToken(xuid))
|
||||
{
|
||||
// Returning player - challenge them
|
||||
p->connection->send(std::make_shared<CustomPayloadPacket>(
|
||||
CustomPayloadPacket::IDENTITY_TOKEN_CHALLENGE, byteArray()));
|
||||
p->connection->setIdentityChallengeTick(s_playerListTickCount);
|
||||
app.DebugPrintf("Server: Sent identity challenge to %ls (smallId=%d)\n",
|
||||
p->getName().c_str(), smallId);
|
||||
}
|
||||
else
|
||||
{
|
||||
// New player - issue a token over the encrypted channel
|
||||
uint8_t token[ServerRuntime::Security::IdentityTokenManager::TOKEN_SIZE];
|
||||
if (ServerRuntime::Security::GetIdentityTokenManager().IssueToken(xuid, token))
|
||||
{
|
||||
byteArray tokenData(ServerRuntime::Security::IdentityTokenManager::TOKEN_SIZE);
|
||||
memcpy(tokenData.data, token, ServerRuntime::Security::IdentityTokenManager::TOKEN_SIZE);
|
||||
p->connection->send(std::make_shared<CustomPayloadPacket>(
|
||||
CustomPayloadPacket::IDENTITY_TOKEN_ISSUE, tokenData));
|
||||
SecureZeroMemory(token, sizeof(token));
|
||||
p->connection->setIdentityVerified(true);
|
||||
app.DebugPrintf("Server: Issued identity token to %ls (smallId=%d)\n",
|
||||
p->getName().c_str(), smallId);
|
||||
ServerRuntime::ServerLogManager::OnIdentityTokenIssued(smallId);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Enforce identity token response timeout
|
||||
for (auto &p : players)
|
||||
{
|
||||
if (p == nullptr || p->connection == nullptr) continue;
|
||||
int challengeTick = p->connection->getIdentityChallengeTick();
|
||||
if (challengeTick >= 0 && !p->connection->isIdentityVerified() &&
|
||||
(s_playerListTickCount - challengeTick) > kIdentityResponseGraceTicks)
|
||||
{
|
||||
app.DebugPrintf("SECURITY: Kicking %ls - identity token response timed out\n",
|
||||
p->getName().c_str());
|
||||
INetworkPlayer *npLog = p->connection->getNetworkPlayer();
|
||||
if (npLog != nullptr)
|
||||
ServerRuntime::ServerLogManager::OnIdentityTokenTimeout(npLog->GetSmallId(), p->getName());
|
||||
p->connection->setIdentityChallengeTick(-1); // prevent re-queuing
|
||||
INetworkPlayer *np = p->connection->getNetworkPlayer();
|
||||
if (np != nullptr)
|
||||
{
|
||||
EnterCriticalSection(&m_closePlayersCS);
|
||||
m_smallIdsToClose.push_back(np->GetSmallId());
|
||||
LeaveCriticalSection(&m_closePlayersCS);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// 4J - brought changes to how often this is sent forward from 1.2.3
|
||||
if (++sendAllPlayerInfoIn > SEND_PLAYER_INFO_INTERVAL)
|
||||
{
|
||||
|
|
@ -1722,6 +1937,7 @@ bool PlayerList::isXuidBanned(PlayerUID xuid)
|
|||
|
||||
bool banned = false;
|
||||
|
||||
EnterCriticalSection(&m_banCS);
|
||||
for(PlayerUID it : m_bannedXuids)
|
||||
{
|
||||
if( ProfileManager.AreXUIDSEqual( xuid, it ) )
|
||||
|
|
@ -1730,6 +1946,7 @@ bool PlayerList::isXuidBanned(PlayerUID xuid)
|
|||
break;
|
||||
}
|
||||
}
|
||||
LeaveCriticalSection(&m_banCS);
|
||||
|
||||
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
||||
if (!banned && g_Win64DedicatedServer)
|
||||
|
|
@ -1741,8 +1958,118 @@ bool PlayerList::isXuidBanned(PlayerUID xuid)
|
|||
return banned;
|
||||
}
|
||||
|
||||
void PlayerList::banXuid(PlayerUID xuid)
|
||||
{
|
||||
// 4J Added - for hardcore mode ban-on-death
|
||||
// Ban a player's XUID. Used when a player dies in a hardcore multiplayer world.
|
||||
if(xuid == INVALID_XUID) return;
|
||||
|
||||
EnterCriticalSection(&m_banCS);
|
||||
|
||||
// Check if already banned
|
||||
bool alreadyBanned = false;
|
||||
for(PlayerUID it : m_bannedXuids)
|
||||
{
|
||||
if( ProfileManager.AreXUIDSEqual( xuid, it ) )
|
||||
{
|
||||
alreadyBanned = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!alreadyBanned)
|
||||
{
|
||||
m_bannedXuids.push_back(xuid);
|
||||
app.DebugPrintf("PlayerList::banXuid - Player XUID banned for hardcore death\n");
|
||||
}
|
||||
|
||||
LeaveCriticalSection(&m_banCS);
|
||||
}
|
||||
|
||||
void PlayerList::banPlayerForHardcoreDeath(ServerPlayer *player)
|
||||
{
|
||||
if (player == nullptr) return;
|
||||
|
||||
// Always apply the in-memory XUID ban (works for both client-hosted and dedicated)
|
||||
banXuid(player->getOnlineXuid());
|
||||
|
||||
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
||||
if (g_Win64DedicatedServer)
|
||||
{
|
||||
const std::string playerName = ServerRuntime::StringUtils::WideToUtf8(player->getName());
|
||||
|
||||
ServerRuntime::Access::BanMetadata metadata = ServerRuntime::Access::BanManager::BuildDefaultMetadata("Hardcore Death");
|
||||
metadata.reason = "Died in hardcore mode";
|
||||
|
||||
// Ban online XUID
|
||||
ServerRuntime::Access::AddPlayerBan(player->getOnlineXuid(), playerName, metadata);
|
||||
|
||||
// Also ban offline XUID if it differs (follows CliCommandBan pattern)
|
||||
PlayerUID offlineXuid = player->getXuid();
|
||||
if (offlineXuid != INVALID_XUID && offlineXuid != player->getOnlineXuid())
|
||||
{
|
||||
ServerRuntime::Access::AddPlayerBan(offlineXuid, playerName, metadata);
|
||||
}
|
||||
|
||||
// Ban the player's IP address (uses same access path as CliCommandBanIp)
|
||||
auto serverConfig = ServerRuntime::LoadServerPropertiesConfig();
|
||||
if (serverConfig.hardcoreBanIp)
|
||||
{
|
||||
if (player->connection != nullptr && player->connection->connection != nullptr && player->connection->connection->getSocket() != nullptr)
|
||||
{
|
||||
const unsigned char smallId = player->connection->connection->getSocket()->getSmallId();
|
||||
std::string ip;
|
||||
if (smallId != 0 && ServerRuntime::ServerLogManager::TryGetConnectionRemoteIp(smallId, &ip))
|
||||
{
|
||||
ServerRuntime::Access::AddIpBan(ip, metadata);
|
||||
ServerRuntime::LogInfof("Hardcore", "Player %s banned (XUID + IP %s) for dying in hardcore mode.", playerName.c_str(), ip.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
ServerRuntime::LogInfof("Hardcore", "Player %s banned (XUID only, IP not available) for dying in hardcore mode.", playerName.c_str());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ServerRuntime::LogInfof("Hardcore", "Player %s banned (XUID only, no connection) for dying in hardcore mode.", playerName.c_str());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ServerRuntime::LogInfof("Hardcore", "Player %s banned (XUID only, IP ban disabled) for dying in hardcore mode.", playerName.c_str());
|
||||
}
|
||||
|
||||
// Send ban reason then defer the actual close to the next tick, because this
|
||||
// method runs mid-tick inside ServerPlayer::die(). A synchronous disconnect
|
||||
// can invalidate the player/connection while the tick is still executing.
|
||||
if (player->connection != nullptr)
|
||||
{
|
||||
player->connection->send(std::make_shared<DisconnectPacket>(DisconnectPacket::eDisconnect_Banned));
|
||||
player->connection->closeOnTick();
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
// Client-hosted: force-save so the host cannot circumvent death by quitting without saving.
|
||||
// On dedicated server the autosave handles persistence, so skip the forced save to avoid
|
||||
// the client getting stuck on a "host is saving" screen during disconnect.
|
||||
app.SetXuiServerAction(ProfileManager.GetPrimaryPad(), eXuiServerAction_SaveGame);
|
||||
}
|
||||
}
|
||||
|
||||
// AP added for Vita so the range can be increased once the level starts
|
||||
void PlayerList::setViewDistance(int newViewDistance)
|
||||
void PlayerList::setViewDistance(const int newViewDistance)
|
||||
{
|
||||
viewDistance = newViewDistance;
|
||||
|
||||
for (size_t i = 0; i < server->levels.length; i++)
|
||||
{
|
||||
ServerLevel* level = server->levels[i];
|
||||
if (level != nullptr)
|
||||
{
|
||||
level->getChunkMap()->setRadius(newViewDistance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ private:
|
|||
|
||||
// 4J Added
|
||||
vector<PlayerUID> m_bannedXuids;
|
||||
CRITICAL_SECTION m_banCS; // 4J Added - protects m_bannedXuids for concurrent access
|
||||
deque<BYTE> m_smallIdsToKick;
|
||||
CRITICAL_SECTION m_kickPlayersCS;
|
||||
deque<BYTE> m_smallIdsToClose;
|
||||
|
|
@ -135,6 +136,8 @@ public:
|
|||
void closePlayerConnectionBySmallId(BYTE networkSmallId);
|
||||
void queueSmallIdForRecycle(BYTE smallId);
|
||||
bool isXuidBanned(PlayerUID xuid);
|
||||
void banXuid(PlayerUID xuid); // 4J Added - for hardcore mode ban-on-death
|
||||
void banPlayerForHardcoreDeath(ServerPlayer *player); // Persistent XUID + IP ban on hardcore death
|
||||
// AP added for Vita so the range can be increased once the level starts
|
||||
void setViewDistance(int newViewDistance);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -234,11 +234,26 @@ void PlayerRenderer::render(shared_ptr<Entity> _mob, double x, double y, double
|
|||
|
||||
armorParts1->sneaking = armorParts2->sneaking = resModel->sneaking = mob->isSneaking();
|
||||
double yp = y - mob->heightOffset;
|
||||
if (mob->isSneaking() && !mob->instanceof(eTYPE_LOCALPLAYER))
|
||||
if (mob->isSneaking())
|
||||
{
|
||||
yp -= 2 / 16.0f;
|
||||
}
|
||||
|
||||
if (mob->getAnimOverrideBitmask() & (1 << HumanoidModel::eAnim_SmallModel))
|
||||
{
|
||||
if (mob->isRiding())
|
||||
{
|
||||
std::shared_ptr<Entity> ridingEntity = mob->riding;
|
||||
if (ridingEntity != nullptr) // Safety check;
|
||||
{
|
||||
if (ridingEntity->instanceof(eTYPE_BOAT))
|
||||
{
|
||||
yp += 0.25f; // reverts the change in Boat.cpp for smaller models.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if an idle animation is needed
|
||||
if(mob->getAnimOverrideBitmask()&(1<<HumanoidModel::eAnim_HasIdle))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -303,6 +303,12 @@ void SelectWorldScreen::WorldSelectionList::renderItem(int i, int x, int y, int
|
|||
info = parent->conversionLang + L" " + info;
|
||||
}
|
||||
|
||||
// 4J Added: Show [Hardcore] badge for hardcore worlds
|
||||
if (levelSummary->isHardcore())
|
||||
{
|
||||
name = name + L" [Hardcore]";
|
||||
}
|
||||
|
||||
parent->drawString(parent->font, name, x + 2, y + 1, 0xffffff);
|
||||
parent->drawString(parent->font, id, x + 2, y + 12, 0x808080);
|
||||
parent->drawString(parent->font, info, x + 2, y + 12 + 10, 0x808080);
|
||||
|
|
|
|||
|
|
@ -80,54 +80,27 @@ vector<LevelChunk *> *ServerChunkCache::getLoadedChunkList()
|
|||
return &m_loadedChunkList;
|
||||
}
|
||||
|
||||
void ServerChunkCache::drop(int x, int z)
|
||||
void ServerChunkCache::drop(const int x, const int z)
|
||||
{
|
||||
// 4J - we're not dropping things anymore now that we have a fixed sized cache
|
||||
#ifdef _LARGE_WORLDS
|
||||
const int ix = x + XZOFFSET;
|
||||
const int iz = z + XZOFFSET;
|
||||
if ((ix < 0) || (ix >= XZSIZE)) return;
|
||||
if ((iz < 0) || (iz >= XZSIZE)) return;
|
||||
const int idx = ix * XZSIZE + iz;
|
||||
LevelChunk* chunk = cache[idx];
|
||||
|
||||
bool canDrop = false;
|
||||
// if (level->dimension->mayRespawn())
|
||||
// {
|
||||
// Pos *spawnPos = level->getSharedSpawnPos();
|
||||
// int xd = x * 16 + 8 - spawnPos->x;
|
||||
// int zd = z * 16 + 8 - spawnPos->z;
|
||||
// delete spawnPos;
|
||||
// int r = 128;
|
||||
// if (xd < -r || xd > r || zd < -r || zd > r)
|
||||
// {
|
||||
// canDrop = true;
|
||||
//}
|
||||
// }
|
||||
// else
|
||||
if (chunk != nullptr)
|
||||
{
|
||||
canDrop = true;
|
||||
m_toDrop.push_back(chunk);
|
||||
}
|
||||
if(canDrop)
|
||||
{
|
||||
int ix = x + XZOFFSET;
|
||||
int iz = z + XZOFFSET;
|
||||
// Check we're in range of the stored level
|
||||
if( ( ix < 0 ) || ( ix >= XZSIZE ) ) return;
|
||||
if( ( iz < 0 ) || ( iz >= XZSIZE ) ) return;
|
||||
int idx = ix * XZSIZE + iz;
|
||||
LevelChunk *chunk = cache[idx];
|
||||
|
||||
if(chunk)
|
||||
{
|
||||
m_toDrop.push_back(chunk);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void ServerChunkCache::dropAll()
|
||||
{
|
||||
#ifdef _LARGE_WORLDS
|
||||
for (LevelChunk *chunk : m_loadedChunkList)
|
||||
{
|
||||
drop(chunk->x, chunk->z);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// 4J - this is the original (and virtual) interface to create
|
||||
|
|
@ -954,9 +927,14 @@ bool ServerChunkCache::tick()
|
|||
int ix = chunk->x + XZOFFSET;
|
||||
int iz = chunk->z + XZOFFSET;
|
||||
int idx = ix * XZSIZE + iz;
|
||||
delete m_unloadedCache[idx];
|
||||
m_unloadedCache[idx] = chunk;
|
||||
cache[idx] = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
m_toDrop.pop_front();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,10 @@
|
|||
#include "../Minecraft.World/Socket.h"
|
||||
#include "../Minecraft.World/net.minecraft.world.level.h"
|
||||
#include "MultiPlayerLevel.h"
|
||||
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
||||
#include "..\Minecraft.Server\Security\SecurityConfig.h"
|
||||
#include "..\Minecraft.Server\ServerLogManager.h"
|
||||
#endif
|
||||
|
||||
ServerConnection::ServerConnection(MinecraftServer *server)
|
||||
{
|
||||
|
|
@ -40,6 +44,17 @@ void ServerConnection::addPlayerConnection(shared_ptr<PlayerConnection> uc)
|
|||
void ServerConnection::handleConnection(shared_ptr<PendingConnection> uc)
|
||||
{
|
||||
EnterCriticalSection(&pending_cs);
|
||||
#if defined(_WINDOWS64) && defined(MINECRAFT_SERVER_BUILD)
|
||||
int maxPending = ServerRuntime::Security::GetSettings().maxPendingConnections;
|
||||
if (maxPending > 0 && static_cast<int>(pending.size()) >= maxPending)
|
||||
{
|
||||
LeaveCriticalSection(&pending_cs);
|
||||
app.DebugPrintf("SECURITY: Rejecting connection, too many pending (%d/%d)\n",
|
||||
static_cast<int>(pending.size()), maxPending);
|
||||
uc->disconnect(DisconnectPacket::eDisconnect_ServerFull);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
pending.push_back(uc);
|
||||
LeaveCriticalSection(&pending_cs);
|
||||
}
|
||||
|
|
|
|||