diff --git a/.clang-format b/.clang-format
index 4a24aec430..a7443c22bf 100644
--- a/.clang-format
+++ b/.clang-format
@@ -1,7 +1,7 @@
BasedOnStyle: LLVM
AccessModifierOffset: -4
AlignAfterOpenBracket: BlockIndent
-AlignArrayOfStructures: Left
+AlignArrayOfStructures: Right
AlignConsecutiveDeclarations:
Enabled: true
AcrossEmptyLines: false
@@ -44,3 +44,8 @@ PointerAlignment: Left
TabWidth: 4
UseTab: Never
SortIncludes: CaseSensitive
+# RemoveEmptyLinesInUnwrappedLines: true
+RemoveSemicolon: true
+# SeparateDefinitionBlocks: Always
+SortUsingDeclarations: LexicographicNumeric
+Standard: Latest
\ No newline at end of file
diff --git a/.clang-tidy b/.clang-tidy
index 915eee83b0..e9b22e2f83 100644
--- a/.clang-tidy
+++ b/.clang-tidy
@@ -65,7 +65,6 @@ cppcoreguidelines-narrowing-conversions,
cppcoreguidelines-pro-type-member-init,
cppcoreguidelines-slicing,
google-default-arguments,
-google-explicit-constructor,
google-runtime-operator,
hicpp-exception-baseclass,
hicpp-multiway-paths-covered,
@@ -123,7 +122,6 @@ portability-simd-intrinsics,
readability-avoid-const-params-in-decls,
readability-const-return-type,
readability-container-size-empty,
-readability-convert-member-functions-to-static,
readability-delete-null-pointer,
readability-deleted-default,
readability-inconsistent-declaration-parameter-name,
diff --git a/.clangd b/.clangd
index 8f64971a9f..c2234e99b3 100644
--- a/.clangd
+++ b/.clangd
@@ -1,6 +1,6 @@
Diagnostics:
UnusedIncludes: Strict
- MissingIncludes: Strict
+ MissingIncludes: None
Suppress:
- "-Wmicrosoft-enum-forward-reference"
- "-Wc++11-narrowing"
@@ -11,8 +11,3 @@ CompileFlags:
- "-ferror-limit=0"
- '-D__FUNCTION__="dummy"'
- '-D__clangd__'
- - "-Yusrc/ll/api/Global.h"
- - "-FIsrc/ll/api/Global.h" # clangd bug can't find pch file
- Remove:
- - "/YuGlobal.h"
- - "/FIGlobal.h"
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index 9d718c93e4..82d15552be 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -1,4 +1,3 @@
-github: LiteLDev
-open_collective: liteloaderbds
+open_collective: LeviMC
custom:
- - https://afdian.net/@liteldev
+ - https://afdian.com/@liteldev
diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml
new file mode 100644
index 0000000000..96b9c20f5e
--- /dev/null
+++ b/.github/workflows/build-docs.yml
@@ -0,0 +1,88 @@
+on:
+ pull_request:
+ paths:
+ - .github/workflows/build-docs.yml
+ - docs/**
+ - src/**/*.h
+ push:
+ paths:
+ - .github/workflows/build-docs.yml
+ - docs/**
+ - src/**/*.h
+ workflow_dispatch:
+
+jobs:
+ build-doxygen:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - run: |
+ git clone --depth 1 --branch v2.3.4 https://github.com/jothepro/doxygen-awesome-css.git docs/api/doxygen-awesome-css
+
+ - run : |
+ mkdir -p site/api
+
+ - uses: mattnotmitt/doxygen-action@v1
+ with:
+ doxyfile-path: ./docs/api/Doxyfile
+
+ - run: |
+ tar -cvf artifact.tar -C site/ .
+
+ - uses: actions/upload-artifact@v4
+ with:
+ name: doxygen-pages
+ path: |
+ artifact.tar
+
+ build-mkdocs:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - run: |
+ pip install -r docs/main/requirements.txt
+
+ - run: |
+ mkdocs build -f docs/main/mkdocs.yml -d ../../site
+
+ - run: |
+ tar -cvf artifact.tar -C site/ .
+
+ - uses: actions/upload-artifact@v4
+ with:
+ name: mkdocs-pages
+ path: |
+ artifact.tar
+
+ merge:
+ needs:
+ - build-doxygen
+ - build-mkdocs
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/download-artifact@v4
+ with:
+ path: artifacts/
+
+ - name: Extract artifacts
+ run: |
+ mkdir site
+ tar -xvf artifacts/doxygen-pages/artifact.tar -C site/
+ tar -xvf artifacts/mkdocs-pages/artifact.tar -C site/
+
+ - uses: actions/upload-pages-artifact@v3
+ with:
+ path: site/
+
+ deploy:
+ if: github.ref == 'refs/heads/main' && github.event_name == 'push'
+ needs:
+ - merge
+ permissions:
+ id-token: write
+ pages: write
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/deploy-pages@v4
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 19e021d53a..722d901702 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -1,23 +1,36 @@
on:
- push:
+ pull_request:
paths:
- .github/workflows/build.yml
- src/**
+ - src-server/**
+ - src-client/**
+ - src-test/**
- xmake.lua
- pull_request:
+ push:
paths:
- .github/workflows/build.yml
- src/**
+ - src-server/**
+ - src-client/**
+ - src-test/**
- xmake.lua
workflow_dispatch:
jobs:
build:
strategy:
+ fail-fast: false
matrix:
+ target_type:
+ - server
+ - client
mode:
- debug
- release
+ tests:
+ - true
+ - false
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
@@ -25,6 +38,8 @@ jobs:
fetch-depth: 0
- uses: xmake-io/github-action-setup-xmake@v1
+ # with:
+ # xmake-version: branch@master
- uses: actions/cache@v4
with:
@@ -34,32 +49,19 @@ jobs:
restore-keys: |
xmake-
+ - uses: microsoft/setup-msbuild@v2
+
- run: |
xmake repo -u
- run: |
- xmake f -a x64 -m ${{ matrix.mode }} -p windows -v -y
+ xmake f -a x64 -m ${{ matrix.mode }} -p windows -v -y --target_type=${{ matrix.target_type }} --tests=${{ matrix.tests }}
- run: |
xmake -v -w -y
- - run: |
- xmake package -v -y
-
- uses: actions/upload-artifact@v4
with:
- name: levilamina-${{ matrix.mode }}-windows-x64-${{ github.sha }}
+ name: levilamina-${{ matrix.target_type }}-${{ matrix.mode }}${{ matrix.tests == true && '-tests' || '' }}-windows-x64-${{ github.sha }}
path: |
- build/packages/l/levilamina/windows/x64/${{ matrix.mode }}/bin/
-
- check-style:
- runs-on: windows-latest
- steps:
- - uses: actions/checkout@v4
-
- - run: |
- choco install llvm -y --version=17.0.6
-
- - run: |
- Get-ChildItem src/ -Filter *.cpp -Recurse | ForEach-Object { clang-format -i -Werror $_.FullName }
- Get-ChildItem src/ -Filter *.h -Recurse | ForEach-Object { clang-format -i -Werror $_.FullName }
+ bin/
diff --git a/.github/workflows/build_docs.yml b/.github/workflows/build_docs.yml
deleted file mode 100644
index ccd8d99c9e..0000000000
--- a/.github/workflows/build_docs.yml
+++ /dev/null
@@ -1,83 +0,0 @@
-on:
- push:
- paths:
- - .github/workflows/build_docs.yml
- - docs/**
- - src/**/*.h
- - Doxyfile
- - DoxygenLayout.xml
- - mkdocs.yml
- workflow_dispatch:
-
-jobs:
- build-doxygen:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
- with:
- submodules: true
-
- - run: |
- mkdir -p site/api
-
- - uses: mattnotmitt/doxygen-action@1.9.5
-
- - run: |
- tar -cvf artifact.tar -C site/ .
-
- - uses: actions/upload-artifact@v3
- with:
- name: doxygen-pages
- path: |
- artifact.tar
-
- build-mkdocs:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
-
- - run: |
- pip install -r requirements.txt
-
- - run: |
- mkdocs build
-
- - run: |
- tar -cvf artifact.tar -C site/ .
-
- - uses: actions/upload-artifact@v3
- with:
- name: mkdocs-pages
- path: |
- artifact.tar
-
- merge:
- needs:
- - build-doxygen
- - build-mkdocs
- runs-on: ubuntu-latest
- steps:
- - uses: actions/download-artifact@v3
- with:
- path: artifacts/
-
- - name: Extract artifacts
- run: |
- mkdir site
- tar -xvf artifacts/doxygen-pages/artifact.tar -C site/
- tar -xvf artifacts/mkdocs-pages/artifact.tar -C site/
-
- - uses: actions/upload-pages-artifact@v2
- with:
- path: site/
-
- deploy:
- # if: github.ref == 'refs/heads/main' && github.event_name == 'push'
- needs:
- - merge
- permissions:
- id-token: write
- pages: write
- runs-on: ubuntu-latest
- steps:
- - uses: actions/deploy-pages@v2
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 18fe40dceb..7fc6db0613 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -6,6 +6,7 @@ on:
jobs:
build:
strategy:
+ fail-fast: false
matrix:
mode:
- debug
@@ -17,6 +18,8 @@ jobs:
fetch-depth: 0
- uses: xmake-io/github-action-setup-xmake@v1
+ # with:
+ # xmake-version: branch@master
- uses: actions/cache@v4
with:
@@ -26,6 +29,8 @@ jobs:
restore-keys: |
xmake-
+ - uses: microsoft/setup-msbuild@v2
+
- run: |
xmake repo -u
@@ -35,14 +40,14 @@ jobs:
- run: |
xmake -v -w -y
- - run: |
- xmake package -v -y
+ # - run: |
+ # xmake package -v -y
- uses: actions/upload-artifact@v4
with:
name: levilamina-${{ matrix.mode }}-windows-x64-${{ github.sha }}
path: |
- build/packages/l/levilamina/windows/x64/${{ matrix.mode }}/bin/
+ bin/
update-release-notes:
permissions:
@@ -59,9 +64,9 @@ jobs:
- id: extract-release-notes
uses: ffurrer2/extract-release-notes@v2
- - uses: softprops/action-gh-release@v1
+ - uses: softprops/action-gh-release@v2
with:
- body: |
+ body: |-
${{ steps.extract-release-notes.outputs.release_notes }}
| File | SHA256 |
@@ -98,10 +103,10 @@ jobs:
run: |
echo sha256=$(sha256sum levilamina-${{ matrix.mode }}-windows-x64.zip | awk '{print $1}') >> $GITHUB_OUTPUT
- - uses: softprops/action-gh-release@v1
+ - uses: softprops/action-gh-release@v2
with:
append_body: true
- body: |
+ body: |-
| levilamina-${{ matrix.mode }}-windows-x64.zip | ${{ steps.calculate-sha256.outputs.sha256 }} |
files: |
levilamina-${{ matrix.mode }}-windows-x64.zip
diff --git a/.gitignore b/.gitignore
index 214ac9e3c8..2b87d1445e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -33,6 +33,7 @@
*.app
+
## https://github.com/github/gitignore/blob/main/CMake.gitignore
CMakeLists.txt.user
CMakeCache.txt
@@ -44,7 +45,7 @@ cmake_install.cmake
install_manifest.txt
compile_commands.json
CTestTestfile.cmake
-_deps
+
## https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
@@ -67,20 +68,10 @@ _deps
mono_crash.*
# Build results
-[Dd]ebug/
-[Dd]ebugPublic/
-[Rr]elease/
-[Rr]eleases/
-x64/
-x86/
-[Ww][Ii][Nn]32/
-[Aa][Rr][Mm]/
-[Aa][Rr][Mm]64/
-bld/
-[Bb]in/
-[Oo]bj/
-[Ll]og/
-[Ll]ogs/
+/[Dd]ebug/
+/[Dd]ebugPublic/
+/[Rr]elease/
+/[Rr]eleases/
# Visual Studio 2015/2017 cache/options directory
.vs/
@@ -214,20 +205,20 @@ AutoTest.Net/
.sass-cache/
# Installshield output folder
-[Ee]xpress/
+/[Ee]xpress/
# DocProject is a documentation generator add-in
-DocProject/buildhelp/
-DocProject/Help/*.HxT
-DocProject/Help/*.HxC
-DocProject/Help/*.hhc
-DocProject/Help/*.hhk
-DocProject/Help/*.hhp
-DocProject/Help/Html2
-DocProject/Help/html
+/DocProject/buildhelp/
+/DocProject/Help/*.HxT
+/DocProject/Help/*.HxC
+/DocProject/Help/*.hhc
+/DocProject/Help/*.hhk
+/DocProject/Help/*.hhp
+/DocProject/Help/Html2
+/DocProject/Help/html
# Click-Once directory
-publish/
+/publish/
# Publish Web Output
*.[Pp]ublish.xml
@@ -448,6 +439,7 @@ FodyWeavers.xsd
*.sln.iml
+
## https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
.vscode/*
!.vscode/settings.json
@@ -463,6 +455,7 @@ FodyWeavers.xsd
*.vsix
+
## https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
@@ -543,20 +536,24 @@ fabric.properties
.idea/caches/build_file_checksums.ser
-## XMake
+
+## Miscellaneous
+# XMake
/.xmake/
/build/
/CMakeLists.txt
/vsxmake2022
-
-## Project specific
-# IDE
-/.idea/
-/.vscode/
-
-# MkDocs
+## Documentation
+/docs/api/doxygen-awesome-css/
/site/
-# Test
-src/ll/test/include_all.cpp
+# IDE
+.idea/
+.vscode/
+
+# Testing
+/bin/
+/src/ll/test/include_all.cpp
+/src-test/**/include_all.cpp
+/builder/
diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index 090915e637..0000000000
--- a/.gitmodules
+++ /dev/null
@@ -1,3 +0,0 @@
-[submodule "docs/api/assets/doxygen-awesome-css"]
- path = docs/api/assets/doxygen-awesome-css
- url = https://github.com/jothepro/doxygen-awesome-css.git
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 32ab836ce8..a97574b47d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,501 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
+## [1.0.0-rc.1] - 2025-01-04
+
+### Added
+
+- **Adapted to BDS version *1.21.5x*** @OEOTYAN @ddf8196 @Dofes @dreamguxiang @futrime @KawaiiNahida @Lovelylavender4 @PA733 @Pd233 @RimuruChan @ShrBox @wu-vincent
+- **Added support for *Windows* platform *clients*** @OEOTYAN @ddf8196 @Dofes
+- Added the missing field to CommandRegistry::Overload @wu-vincent
+- Added last member variable of ChunkSource @xNotTozic
+- Added StdoutRedirector @OEOTYAN
+- Added imgui and some relate codes @ddf8196 @OEOTYAN
+- Added function definitions for ItemStackBase::getTypeName, ItemStackBase::getDescriptionName, ItemStackBase::getDamageValue to the source file @Dofes
+- Added some headers for ui @Dofes
+- Added compiler specific defines for clang @Redbeanw44602
+- Added clang/gcc virtualDetector @OEOTYAN
+- Added hints for double enable/disable @OEOTYAN
+- Added new signature resolver @OEOTYAN
+- Added ll::memory::IndirectValue @OEOTYAN
+- Added tight pair @OEOTYAN
+- Added pl config to formatter @OEOTYAN
+- Added default tick pool @OEOTYAN
+- Added TypeTraits, AnyFunction @OEOTYAN
+- Added ConcurrentPriorityQueue and try_pop_if @OEOTYAN
+- Added addTaskAfter to Executer @OEOTYAN
+- Added coroutine support @OEOTYAN
+- Added keepThis keep lambda lifetime @OEOTYAN
+- Added getNonOwnerRef to EnableNonOwnerReferences @OEOTYAN
+- Added param traits to custom command param @OEOTYAN
+- Added thunk to all types @OEOTYAN
+- Added LoggerRegistry [#1582] @OEOTYAN
+- Added UntypedStorage @OEOTYAN
+- Added TypedStorage @OEOTYAN
+- Added initializer_list for CompoundTagVariant @OEOTYAN
+- Adaptted to new preloader @OEOTYAN
+- Completes OreFeature, BlockDescriptor @Lovelylavender4
+- Completes the FertilizerType enum class @zimuya4153
+- Completes GameRulesIndex @KobeBryant114514
+- Completes BookEditAction and EmotePacket::Flags @zimuya4153
+- Completes ItemReleaseInventoryTransaction and ItemUseInventoryTransaction and ItemUseOnActorInventoryTransaction member variables @xNotTozic
+- Completes DirtyTicksCounter and SubChunk and SubChunkStorage and Dimension and BuildInfo member variables @OEOTYAN
+- Completes InteractPacket::Action @xNotTozic
+- Completes ReplaceRule member variables @Lovelylavender4
+- Moved concepts to internal @OEOTYAN
+- Filled sim::LookDuration @ShrBox
+- Resolved [#1519] @OEOTYAN
+- Initially drafted the EULA and user guidelines @Lovelylavender4
+
+### Changed
+
+- Refactorred splitByPattern @OEOTYAN
+- Refactorred mc header file format and location, etc @OEOTYAN
+- Refactorred Logger system @OEOTYAN
+- Refactorred stacktrace @OEOTYAN
+- Refactorred i18n @OEOTYAN
+- Refactorred StdoutRedirector @OEOTYAN
+- Refactorred filehandle/expected @OEOTYAN
+- Refactorred virtual memory alloc @OEOTYAN
+- Refactorred event system @OEOTYAN
+- Refactorred literals @OEOTYAN
+- Refactorred TaskPool @OEOTYAN
+- Refactorred Scheduler @OEOTYAN
+- Refactorred Closure @OEOTYAN
+- Removed some function deps @OEOTYAN
+- Removed useless convert @OEOTYAN
+- Removed Logger player output @OEOTYAN
+- Removed some internal deps @OEOTYAN
+- Removed function jump in queue @OEOTYAN
+- Removed crypto utils @OEOTYAN
+- Replaced some containers @OEOTYAN
+- Rewrited log info @OEOTYAN
+- Reseted enum parser to keep reload dll working @OEOTYAN
+- Changed some api file locations @OEOTYAN
+- Changed locale code name @OEOTYAN
+- Changed param to string_view @OEOTYAN
+- Updated quickstart.zh.md docs @ShrBox
+- Outputed demangle symbol @OEOTYAN
+- Made command param checker into compile time @OEOTYAN
+- Made param option and praser into traits @OEOTYAN
+- Finished [#1172] @OEOTYAN
+- Made mod manager auto release @OEOTYAN
+- Used dual mapping replace virtual protect @OEOTYAN
+- Relaxed virtual clone requires @OEOTYAN
+
+### Fixed
+
+- Fixed Dispatcher::operator= @OEOTYAN
+- Fixed FixedBiomeSource::getBiomeArea @OEOTYAN
+- Fixed quickstart docs @ShrBox
+- Fixed SubChunk define bugs @OEOTYAN
+- Fixed linux self maps reader @OEOTYAN
+- Fixed NativeModManager::load wrong error type @OEOTYAN
+- Fixed [#1574] @OEOTYAN
+- Fixed dll reload @OEOTYAN
+- Fixed libhat compile @OEOTYAN
+- Fixed custom form empty dropdown crash @Dofes
+- Fixed dataitem constexpr @OEOTYAN
+- Fixed AllExperiments enum missed @Dofes
+- Removed optional_ref wrong static cast @OEOTYAN
+- Replaced invoke tag with elide for latest msstl @OEOTYAN
+
+## [0.13.5] - 2024-08-04
+
+### Added
+
+- Completes the member variables of the ItemStackRequestActionTransferBase class
+
+### Changed
+
+- Remove cstring error
+
+### Fixed
+
+- Fix mod dependency [#1559]
+- Ensure valid reference return in FeatureRegistry
+
+## [0.13.4] - 2024-07-23
+
+### Added
+
+- Adapted to BDS version 1.21.3 @Lovelylavender4
+- Added abi compatibility to mods @OEOTYAN
+- Completes the member variables of ISurfaceBuilder::BuildParameters,BiomeArea,SpikeFeature @Lovelylavender4
+- Completes the member variables of ClimateParameters,BiomeDecorationFeature,ScatterParams::CoordinateRange @killcerr
+
+### Changed
+
+- Move levilamina to standard folder @OEOTYAN
+- Rename plugin to mod @OEOTYAN
+- Refactored nbt @OEOTYAN
+- Make the naming of Bounds and GridArea member variables more standardized @Lovelylavender4
+
+### Fixed
+
+- Fixed a bug where optional would not compile under C++20 @OEOTYAN
+- Fixed the size of HardcodedSpawnAreaRegistry @Lovelylavender4
+- Fixed hash with size_t @OEOTYAN
+- Fixed the parent class of MultidimensionalArray @Lovelylavender4
+
+## [0.13.3] - 2024-06-24
+
+### Added
+
+- Adapted to BDS version 1.21.1 @Lovelylavender4
+
+### Changed
+
+- Adjusted the position of UpdateSubChunkBlocksChangedInfo and UpdateSubChunkNetworkBlockInfo. @Lovelylavender4
+
+### Fixed
+
+- Fixed missing include in memory.h @ShrBox
+- Fixed UpdateSubChunkBlocksPacket. @Lovelylavender4
+
+## [0.13.2] - 2024-06-23
+
+### Changed
+
+- Remove PatchHelper
+- Use libhat search signature
+- Update header to 1.21.1.03
+- Refactoring list snbt parse
+
+### Fixed
+
+- Fix optional_ref at c++20
+- Fix unload
+- Fix empty numarray parse [#1552]
+- Fix initializer_list invoke
+
+## [0.13.1] - 2024-06-19
+
+### Fixed
+
+- Fix exception while shutting down
+
+## [0.13.0] - 2024-06-19
+
+### Added
+
+- Adapted to BDS version 1.21.0x @Lovelylavender4
+
+### Changed
+
+- Fix Concepts (#1547)
+- Add vector helper operators
+
+## [0.12.4] - 2024-05-31
+
+### Changed
+
+- Allow copy SpawnParticleEffectPacket
+- Update MaterialType.h (#1529)
+- Remove useless check in plugin manager
+- Refactoring winapis
+- Fill LogArea (#1535)
+- Fix Dimension vtable (#1538)
+
+## [0.12.3] - 2024-05-03
+
+### Added
+
+- Add helper function for optional_ref
+- Add convertion in Pos2d
+- Update header to 1.20.81.
+
+### Changed
+
+- Forward bad_expected_access
+
+### Fixed
+
+- Fix number range of CompoundTag::fromSnbt
+
+## [0.12.2] - 2024-05-01
+
+### Changed
+
+- Remove biome accessor
+
+### Fixed
+
+- Fix fake Intellisense error
+- Fix [#1520]
+- Fix StructureTemplate
+
+## [0.12.1] - 2024-04-29
+
+### Added
+
+- Add i18n getter
+
+### Changed
+
+- Refactoring resolveSymbol
+
+### Fixed
+
+- Fix serialize_to return value
+- Remove constexpr to fix clang-tidy
+- Fix plugin dependency
+
+## [0.12.0] - 2024-04-28
+
+### Added
+
+- Adapted to BDS version 1.20.8x @Lovelylavender4
+
+### Changed
+
+- Refactoring Expected
+- Refactoring Command system
+- Refactoring Plugin system
+- Refactoring Closure
+- Refactoring Nbt
+- Refactoring Reflection
+
+## [0.11.1] - 2024-04-13
+
+### Fixed
+
+- Fix getEnderChestContainer
+- Add missing export for ServiceManager::getInstance (#1506)
+
+## [0.11.0] - 2024-04-13
+
+### Added
+
+- Add ll::concepts::is_in_types
+- Add ItemLockMode struct
+- Add peers structure
+- Add event::getId
+- Add CompoundTagVariant::emplace
+- Rewrite snbt to add error message
+- Add param names from 1.16.201
+
+### Changed
+
+- Refactoring HookRegistrar
+- A more standard way of naming member variables has been adopted
+- Refactoring forEachPos
+- Update enum ContainerEnumName (#1503)
+- Update enum ContainerType (#1504)
+- Refactoring multilistener
+- Refactoring TickSyncTaskPool
+- Update ActorDataIDs
+- Refactoring printDependencyError
+- Refactoring bedrock service
+- Add static_assert message in serialize associative container
+- Refactoring MemoryAllocator
+- Remove to_underlying in DataItem
+
+### Fixed
+
+- Fix LoopbackPacketSender member
+- Fix the member order of PlayerAuthInputPacket
+- Fix RakNetNetworkPeer
+- Fix Bedrock::Threading namespace
+- Fix PlayerActionType enum
+- Fix wrong packet send logic
+- Move PlayerInfoEntry to public
+- Fix StructureTemplate::create
+
+## [0.10.5] - 2024-04-01
+
+### Fixed
+
+- Fix CustomFormHandler [#1502]
+
+## [0.10.4] - 2024-03-31
+
+### Added
+
+- Complete `DividedPos2d`
+- Add memory operator detect
+- Add optional to form result and add cancel result
+
+### Changed
+
+- Refactoring Core::Path
+
+### Fixed
+
+- Fix Dimension, BiomeSource, Generator vtable
+- Fix uname enum bugs
+- Fix allocator bugs
+- Fix plugin ptr bugs
+- Fix soft enum register
+- Fix send packet
+
+## [0.10.2] - 2024-03-28
+
+### Changed
+
+- Make event diagnostic clear
+
+### Fixed
+
+- Fix IConstBlockSource [#1499]
+
+## [0.10.1] - 2024-03-26
+
+### Added
+
+- Add member to GenerateMessageResult
+
+### Fixed
+
+- Fix vec3 bugs
+
+## [0.10.0] - 2024-03-26
+
+### Added
+
+- **Adapted to BDS version `1.20.7x`** @Lovelylavender4
+
+### Changed
+
+- Make NativeClosure safer @OEOTYAN
+- Refactored visitIndex @OEOTYAN
+
+### Fixed
+
+- Fixed [#1492] @OEOTYAN
+
+## [0.9.5] - 2024-03-18
+
+### Added
+
+- Support runtime command registration
+
+### Fixed
+
+- Fix plugin disable callback is not called
+
+## [0.9.4] - 2024-03-14
+
+### Fixed
+
+- Fix logic error in TickSyncSleep
+- Fix Actor::traceRay [#1481]
+
+## [0.9.3] - 2024-03-11
+
+### Added
+
+- Add Randomize and VariableMaxAutoStepComponent struct
+- Add mvs
+- Add visitIndex
+- Add plugin register helper
+- Add `PostprocessingManager::LockedChunk`,`buffer_span` , `DividedPos2d`add some member (#1484)
+
+### Changed
+
+- Add catch in TickSyncTaskPool
+- Refactoring TickSyncTaskPool
+- Refactoring CommandHandle
+- Refactoring TickSyncSleep
+
+### Fixed
+
+- Fix missing dllexport in `SimpleForm::appendButton`
+- Fix missing include in file_utils
+- Fix string utils
+- Fix [#1483]
+
+## [0.9.2] - 2024-03-01
+
+### Fixed
+
+- Fix command problem caused by plugin manage command
+- Fix BossBarColor enum values
+
+## [0.9.1] - 2024-02-29
+
+### Added
+
+- Add new BossBarColor enum value
+- Add BlockVolume::block
+- Add statistics service
+- Add `ll reactivate` command
+
+### Changed
+
+- Optimize `ll show` command output
+
+## [0.9.0] - 2024-02-24
+
+### Added
+
+- **Adapted to BDS version `1.20.62`**
+
+## [0.8.4] - 2024-02-23
+
+### Added
+
+- Add plugin manage command `/levilamina` `/ll`
+
+## [0.8.3] - 2024-02-21
+
+### Added
+
+- Added dependencies and updated internal types in BlockTickingQueue
+- Completed the member variables of HeightmapWrapper, LevelChunkPacket, StructureFeatureRegistry, ChunkGeneratorStructureState, ResourceDrops, and StructureSetRegistry
+
+### Changed
+
+- BlockUpdateFlag has been updated
+- Used latest version of dependencies
+- Added exception handling for member functions in the Plugin class
+
+### Fixed
+
+- `Fixed the errors related to CrashLogger`
+- `Fixed potential update issues caused by the 'post_uninstall' command in tooth.json`
+- `Fixed issue caused by incorrectly formatted dependency versions in tooth.json`
+- Fixed the size of EndCityFeature and StructureCache::StructurePair
+- Corrected offset comments for certain class member variables
+- Corrected symbol comment for Minecraft::earlyShutdownMainThread
+
+## [0.8.1] - 2024-02-13
+
+### Added
+
+- Added a built-in command 'crash' to simulate server crash
+
+### Changed
+
+- Removed the restriction on a maximum of 255 buttons in the form
+- Refactored FormAPI
+
+### Fixed
+
+- Fixed the issue of getDefaultAllocator not being exported
+- Fixed the ServerScriptManager structure
+
+## [0.8.0] - 2024-02-13
+
+### Added
+
+- **Adapted to BDS version `1.20.61`**
+- Added a method to allow OPs to use cheat commands when cheats are disabled in the server properties
+- Added built-in command 'memstatus' to query memory status
+- Added new memory allocator
+- Added more color conversion functions
+
+### Changed
+
+- Refactored BuiltinCommands
+- Allow instantiation of FlatWorldGeneratorOptions and BlockVolume without parameters
+- Modify the installation details of Levilamina
+- Complete the pure virtual class IMemoryAllocator
+
+### Fixed
+
+- Fixed bugs in Molang and HashedString
+
## [0.7.2] - 2024-02-05
### Added
@@ -218,7 +713,49 @@ First preview release.
For lip and tooth-hub test only.
-[Unreleased]: https://github.com/LiteLDev/LeviLamina/compare/v0.7.2...HEAD
+[#1172]: https://github.com/LiteLDev/LeviLamina/issues/1172
+[#1481]: https://github.com/LiteLDev/LeviLamina/issues/1481
+[#1483]: https://github.com/LiteLDev/LeviLamina/issues/1483
+[#1492]: https://github.com/LiteLDev/LeviLamina/issues/1492
+[#1499]: https://github.com/LiteLDev/LeviLamina/issues/1499
+[#1502]: https://github.com/LiteLDev/LeviLamina/issues/1502
+[#1519]: https://github.com/LiteLDev/LeviLamina/issues/1519
+[#1520]: https://github.com/LiteLDev/LeviLamina/issues/1520
+[#1552]: https://github.com/LiteLDev/LeviLamina/issues/1552
+[#1559]: https://github.com/LiteLDev/LeviLamina/issues/1559
+[#1574]: https://github.com/LiteLDev/LeviLamina/issues/1574
+[#1582]: https://github.com/LiteLDev/LeviLamina/issues/1582
+
+[Unreleased]: https://github.com/LiteLDev/LeviLamina/compare/v1.0.0-rc.1...HEAD
+[1.0.0-rc.1]: https://github.com/LiteLDev/LeviLamina/compare/v0.13.5...v1.0.0-rc.1
+[0.13.5]: https://github.com/LiteLDev/LeviLamina/compare/v0.13.4...v0.13.5
+[0.13.4]: https://github.com/LiteLDev/LeviLamina/compare/v0.13.3...v0.13.4
+[0.13.3]: https://github.com/LiteLDev/LeviLamina/compare/v0.13.2...v0.13.3
+[0.13.2]: https://github.com/LiteLDev/LeviLamina/compare/v0.13.1...v0.13.2
+[0.13.1]: https://github.com/LiteLDev/LeviLamina/compare/v0.13.0...v0.13.1
+[0.13.0]: https://github.com/LiteLDev/LeviLamina/compare/v0.12.4...v0.13.0
+[0.12.4]: https://github.com/LiteLDev/LeviLamina/compare/v0.12.3...v0.12.4
+[0.12.3]: https://github.com/LiteLDev/LeviLamina/compare/v0.12.2...v0.12.3
+[0.12.2]: https://github.com/LiteLDev/LeviLamina/compare/v0.12.1...v0.12.2
+[0.12.1]: https://github.com/LiteLDev/LeviLamina/compare/v0.12.0...v0.12.1
+[0.12.0]: https://github.com/LiteLDev/LeviLamina/compare/v0.11.1...v0.12.0
+[0.11.1]: https://github.com/LiteLDev/LeviLamina/compare/v0.11.0...v0.11.1
+[0.11.0]: https://github.com/LiteLDev/LeviLamina/compare/v0.10.5...v0.11.0
+[0.10.5]: https://github.com/LiteLDev/LeviLamina/compare/v0.10.4...v0.10.5
+[0.10.4]: https://github.com/LiteLDev/LeviLamina/compare/v0.10.2...v0.10.4
+[0.10.2]: https://github.com/LiteLDev/LeviLamina/compare/v0.10.1...v0.10.2
+[0.10.1]: https://github.com/LiteLDev/LeviLamina/compare/v0.10.0...v0.10.1
+[0.10.0]: https://github.com/LiteLDev/LeviLamina/compare/v0.9.5...v0.10.0
+[0.9.5]: https://github.com/LiteLDev/LeviLamina/compare/v0.9.4...v0.9.5
+[0.9.4]: https://github.com/LiteLDev/LeviLamina/compare/v0.9.3...v0.9.4
+[0.9.3]: https://github.com/LiteLDev/LeviLamina/compare/v0.9.2...v0.9.3
+[0.9.2]: https://github.com/LiteLDev/LeviLamina/compare/v0.9.1...v0.9.2
+[0.9.1]: https://github.com/LiteLDev/LeviLamina/compare/v0.9.0...v0.9.1
+[0.9.0]: https://github.com/LiteLDev/LeviLamina/compare/v0.8.4...v0.9.0
+[0.8.4]: https://github.com/LiteLDev/LeviLamina/compare/v0.8.3...v0.8.4
+[0.8.3]: https://github.com/LiteLDev/LeviLamina/compare/v0.8.1...v0.8.3
+[0.8.1]: https://github.com/LiteLDev/LeviLamina/compare/v0.8.0...v0.8.1
+[0.8.0]: https://github.com/LiteLDev/LeviLamina/compare/v0.7.2...v0.8.0
[0.7.2]: https://github.com/LiteLDev/LeviLamina/compare/v0.6.3...v0.7.2
[0.6.3]: https://github.com/LiteLDev/LeviLamina/compare/v0.6.2...v0.6.3
[0.6.2]: https://github.com/LiteLDev/LeviLamina/compare/v0.6.1...v0.6.2
diff --git a/EULA.en.md b/EULA.en.md
new file mode 100644
index 0000000000..0cba6e9fec
--- /dev/null
+++ b/EULA.en.md
@@ -0,0 +1,111 @@
+### LeviLamina
+
+## End-User License Agreement
Version 0.1.0 - 2024.11.1
Copyright © Levimc. All Rights Reserved
+
+This document is only a **translated copy** of the EULA, the original document is the **Chinese version**. This means that if there is a conflict between this document and the Chinese version of the document, **the Chinese version of the document shall prevail**. In addition, since this document is a translated copy, it may not be updated **in time**, and we recommend that you check the Chinese version to make sure that you are **aware** of the update.
+
+### Summary
+
+This End-User License Agreement (**"EULA"**) is a legal agreement between you and us ([*Levimc*](https://github.com/LiteLDev)). You should read this agreement in full. The following is a brief summary of important points to help you understand the basic terms of the agreement, however, the full terms and conditions still apply.
+
+This *LeviLamina* EULA applies to all *Levimc* services.
+
+- Your content belongs to you, but you are responsible for sharing it safely and responsibly.
+- Our community standards help build an open and safe community for everyone.
+- You can develop tools, plugins, and services as long as they do not appear to be official or approved by us, such as using our trademarks.
+- Do not distribute or engage in any commercial use without our permission.
+- We aim to maintain openness, honesty, and trust, and we expect you to treat us the same way.
+- Attempting to obtain data that violates the [Minecraft EULA](https://www.minecraft.net/en-us/eula) or could compromise [Minecraft](https://www.minecraft.net/) security, such as dumping data from [*LeviLamina*](https://github.com/LiteLDev/LeviLamina) or other products (as described in the first entry of the "**Definitions**" section), is unwise!
+- Do not reverse-engineer *LeviLamina* or related programs for malicious purposes!
+- We do not want you to use *LeviLamina* to develop programs that could compromise Minecraft security, or any other product we believe could compromise Minecraft security.
+
+### Introduction
+
+By using [*LeviLamina*](https://github.com/LiteLDev/LeviLamina), you explicitly agree to the terms of this EULA and the [Minecraft EULA](https://www.minecraft.net/en-us/eula). If you are a minor and have difficulty understanding these terms and conditions, please have your parents or legal guardians explain them to you, as your parents or legal guardians will accept these terms on your behalf.
+
+If you agree to the terms and conditions of this EULA, you agree to use *LeviLamina* only in accordance with this EULA, and you agree to be bound by these terms and become a party to this agreement. If you are agreeing to this agreement on behalf of a company, organization, or other entity (**"Company"**), then (i) "you" includes both you and the Company, (ii) you represent and warrant that you are an authorized representative of the Company and have the authority to bind the Company to this EULA.
+
+If you do not agree to the terms and conditions of this EULA, or if you do not have the legal capacity, or you are not an authorized representative of a company to sign this EULA, do not download, install, access, or otherwise use *LeviLamina*, and do not click any button or other mechanism intended to confirm acceptance of the terms.
+
+In specific cases, this EULA may include additional licensing terms or specific terms provided when browsing the *Levimc* website, ordering, installing, downloading, accessing, or using any *Levimc* software. If there is a conflict between this EULA and these additional terms, the additional terms will take precedence. You agree to fully comply with the EULA.
+
+*Levimc* reserves the right to modify this EULA at any time. Your continued use of *LeviLamina* after receiving notice of a change constitutes your acceptance of the modification.
+
+### Definitions
+
+For clarity, when we refer to:
+
+- Our name (or the name of *LeviLamina*), we mean the name of any of our games, products, slogans, features, events, or company logos. We also refer to any name that is similar to or might cause confusion with our name.
+- Our brand, refers to any name, logo, font, texture, or other distinctive features related to our name and games.
+- Our assets, refers to any code, software, graphics, textures, images, models, sounds, and other audio in our game, as well as any video or screenshots extracted from our game.
+
+### What You Can and Cannot Do: LEVILAMINA Software and Content
+
+We are relatively permissive about what you can do, in fact, we strongly encourage you to do cool things—but please do not do anything we have explicitly prohibited. We have prepared detailed [*LeviLamina* Usage Guidelines](docs/main/common_guides/usage_guidelines.md), which outline what you can or cannot do. These [*LeviLamina* Usage Guidelines](docs/main/common_guides/usage_guidelines.md) may change frequently, and we reserve the right to revoke these permissions.
+
+### Developing Mods
+
+Programs modified using *LeviLamina* related to Minecraft are collectively referred to as "**MODS**". Here, "MODS" refers to original content created by you or others, **not including** substantial parts of our copyrighted code or content. We have the **final judgment** on what constitutes a MOD and what does not. We hope you **do not** use MODS for destructive purposes. **Typically**, MODs can be distributed; however, modified versions **cannot** be distributed.
+
+Mods you create from scratch using *LeviLamina* belong to you (including pre-running mods and mods in memory), and you may handle these mods as you wish, as long as you **comply with this EULA**. Remember, a MOD refers to **original** works you create, **not including** substantial parts of our code or content. You **only own** the content you create, and **do not own** our code or content.
+
+When we update *LeviLamina*, certain changes may not be compatible with other software (such as MODs). While unfortunate, we **are not liable** for this. If this happens, please try running an earlier version.
+
+To ensure the integrity of our product, we require all downloads and updates to come from authorized sources. We also prefer that third-party tools/services **do not** appear “official,” as we cannot guarantee their quality. This is part of our responsibility to *LeviLamina* users. Please make sure you **carefully** read our EULA, as we may **remove** MODs or other software that violates the EULA.
+
+### Content
+
+Your content remains your content; we **do not** own your **original works**. However, we will own copies (or substantial copies) or derivatives of our property and creations—but if you create original content, it does not belong to us.
+
+### Our Values
+
+**Inclusivity** is at the core of our values, and these values are essential to maintaining a vibrant and welcoming community.
+
+- *LeviLamina* is for **everyone**.
+- Diversity is the strength of our community.
+- Playing with others should be **safe and inclusive**.
+- **Hate has no place here**.
+
+### LEVILAMINA Serves Everyone, and Diversity Empowers Our Community
+
+*LeviLamina* serves multiple countries/regions around the world, and we want our platform to make people from diverse backgrounds feel safe and included. Every *LeviLamina* community user should have a place to express their true self, and a space to build, create, and express in an environment open to everyone. We want to create a community where everyone can enjoy *LeviLamina*.
+
+The *LeviLamina* community belongs to you, but also to everyone. When you express your views, please be **polite** and **respectful**—don’t explode like a creeper!
+
+### User Responsibility
+
+Every *LeviLamina* user is responsible for reporting misconduct within the community. Users can report misconduct through [Email](feedback@levimc.org).
+
+### Playing Together Should Be Safe and Inclusive, and Hate Has No Place Here
+
+To keep the *LeviLamina* community welcoming and inclusive for everyone, we have a **zero-tolerance policy** for hate speech, terrorism or extreme violence content, bullying, harassment, sexual harassment, fraud, or threatening behavior. MODs and other content you create using *LeviLamina* can be a great way to showcase your concerns and express creativity. However, content that expresses hate, extreme prejudice, or encourages illegal activities is not allowed.
+
+Fraud refers to any act of personal or financial gain through deception or false statements. When someone commits fraud, they bypass the procedures designed to maintain fairness and financial security.
+
+### General Terms
+
+Your country's laws may grant you rights under this EULA that **cannot be altered**; if so, this EULA will apply **to the extent permitted by law**.
+
+We may occasionally update this EULA based on changes to our products, practices, or legal obligations, but these changes will only take effect **to the extent permitted by law**. In such cases, we will post updates on GitHub, so please check regularly to stay informed. We will not take unfair actions on these changes—however, sometimes laws change, or someone's actions affect other users, and we need to address this.
+
+If you provide a suggestion for *LeviLamina*, it is offered freely, and we are **under no obligation to accept or consider** it. This means we can choose whether or not to use your suggestion, **without compensation**. If you believe a suggestion deserves compensation, do not share it with us until we have agreed upon it in writing.
+
+If you wish to notify us about intellectual property infringement in our services, please submit a notification here. We reserve the right to remove any content at our discretion.
+
+If you violate this E
+
+ULA, the consequences are **entirely your responsibility**, whether as an individual, organization, or company. Levimc **does not bear any legal responsibility** and **is not liable** for these consequences. Please read and follow this EULA carefully to avoid unnecessary issues.
+
+## Contact Information
+
+Our Name: [Levimc](https://github.com/LiteLDev) (LiteLDev)
+Our Official Website: [https://levimc.org/](https://levimc.org/)
+Our Official Discord: [https://discord.gg/v5R5P4vRZk](https://discord.gg/v5R5P4vRZk)
+Our Official GitHub: [https://github.com/LiteLDev](https://github.com/LiteLDev)
+Our Official Email: contact@levimc.org
+
+## History of Changes
+
+- 2025.01.04 Completed the first official version @Lovelylavender4
+- 2024.12.11 Corrected the name of the first draft file @Lovelylavender4
+- 2024.11.03 Completed the first draft @Lovelylavender4
\ No newline at end of file
diff --git a/EULA.zh.md b/EULA.zh.md
new file mode 100644
index 0000000000..735262e027
--- /dev/null
+++ b/EULA.zh.md
@@ -0,0 +1,109 @@
+### LeviLamina
+
+## 最终用户许可协议
版本 0.1.0 - 2024.11.1
版权所有 © Levimc. 保留所有权利
+
+### 摘要
+
+本最终用户许可协议 (**"EULA"**) 是您与我们([*Levimc*](https://github.com/LiteLDev))之间的法律协议。您应当完整阅读本协议,以下是一些重要内容的简要总结,帮助您了解本协议的基本要点,然而,完整的条款和条件仍然适用。
+
+本 *LeviLamina* EULA 适用于所有 *Levimc* 服务。
+
+- 您的内容归您所有,但请负责任并安全地分享它。
+- 我们的社区标准有助于构建一个对每个人开放且安全的社区。
+- 您可以开发工具、插件和服务,只要它们不会看起来像是官方的或经过我们批准的,例如使用我们的商标。
+- 未经我们许可,请勿分发或进行任何商业用途。
+- 我们希望保持开放、诚实和信任,希望您也能同样对待我们。
+- 试图获取违反 [Minecraft EULA](https://www.minecraft.net/en-us/eula) 的数据或可能危害 [Minecraft](https://www.minecraft.net/) 安全的行为,例如对 [*LeviLamina*](https://github.com/LiteLDev/LeviLamina) 或我们其他产品的dump(详见 "**定义**" 部分的第一个条目),是不明智的!
+- 请勿出于恶意目的反向工程 *LeviLamina* 以及相关程序!
+- 我们不希望您使用 *LeviLamina* 开发任何可能危害 Minecraft 安全的程序,或任何我们认为会危害 Minecraft 安全的其他产品。
+
+### 引言
+
+您使用 [*LeviLamina*](https://github.com/LiteLDev/LeviLamina) 明确受到本 EULA 条款和 [Minecraft EULA](https://www.minecraft.net/en-us/eula) 条款的约束。如果您是未成年人,并且对这些条款和条件存在理解困难,请让您的父母或法定监护人向您解释,特别是因为您的父母或法定监护人会代表您接受所有条款。
+
+如果您同意本 EULA 的条款和条件,则您同意仅根据本 EULA 的条款和条件使用 *LeviLamina*,并同意您将受这些条款约束并成为本协议的当事方。如果您代表公司、组织或其他实体(**"公司"**)同意接受本协议,则 (i) "您" 包括您和该公司,(ii) 您声明并保证您是该公司的授权代表,并有权将该公司绑定至本 EULA。
+
+如果您不同意本 EULA 的条款和条件,或者如果您没有法律能力,或您不是公司授权代表来签署本 EULA,请勿下载、安装、访问和/或以其他方式使用 *LeviLamina*,并且不要点击任何旨在确认接受条款的按钮或其他机制。
+
+根据具体情况,EULA 可能会附加补充许可条款和在浏览 *Levimc* 网站、订购、安装、下载、访问或使用任何 *Levimc* 软件时提供的特定条款。如果 EULA 与这些附加条款发生冲突,则以附加条款为准。您同意完全遵守 EULA。
+
+*Levimc* 保留随时修改 EULA 的权利。您在接到修改通知后继续使用 *LeviLamina* 即表示接受该修改。
+
+### 定义
+
+为明确起见,当我们提到:
+
+- 我们的名称(或 *LeviLamina* 名称)时,指的是我们任何一个游戏、产品、标语、功能、事件或公司标识的名称。我们还指任何与我们名称相似或可能引起混淆的名称。
+- 我们的品牌,指的是与我们名称和游戏的任何部分相关的名称、徽标、字体、纹理和其他独特特征。
+- 我们的资产,指的是任何我们游戏中的代码、软件、图形、纹理、图像、模型、声音和其他音频,及从我们游戏中提取的任何视频或截图。
+
+### 您可以和不能做的事情:LEVILAMINA 软件与内容
+
+我们对于您可以做的事情相对宽松,实际上,我们强烈鼓励您做一些很酷的事情——但请不要做我们已经明确禁止的事情。我们已经整理了详细的 [*LeviLamina* 使用指南](docs/main/common_guides/usage_guidelines.zh.md),概述了您可以或不能做的事情。这些 [*LeviLamina* 使用指南](docs/main/common_guides/usage_guidelines.zh.md) 可能会频繁变动,我们保留撤销这些许可的权利。
+
+### 开发 MOD
+
+使用 *LeviLamina* 修改的与 Minecraft 相关的程序统称为 "**MODS**"。在此,“MODS”指的是您或他人创建的原始内容,**不包含**我们可版权化的代码或内容的实质部分。我们拥有对什么构成 MOD 和不构成 MOD 的**最终判断权**。我们希望您**不要**用 MOD 来进行破坏性行为。**通常**,MOD 可以分发;但是,修改版 **不能** 分发。
+
+您使用 *LeviLamina* 从零开始创建的 MOD 属于您(包括预运行 MOD 和内存中的 MOD),并且您可以按自己的意愿处理这些 MOD,只要您**遵守本 EULA**。请记住,MOD 是指您**原创**的作品,**不包含**我们代码或内容的实质部分。您**只拥有**您创建的内容,而**不拥有**我们的代码或内容。
+
+当我们更新 *LeviLamina* 时,某些变动可能与其他软件(如 MOD)不兼容。这虽然不幸,但我们**不承担责任**。如果发生这种情况,请尝试运行较早的版本。
+
+为了确保我们产品的完整性,我们要求所有游戏下载和更新来自授权来源。我们也更倾向于第三方工具/服务**不要**看起来像是“官方的”,因为我们无法保证它们的质量。这是我们对 *LeviLamina* 用户的责任的一部分。请确保您也**仔细**阅读我们的 EULA,因为我们可能会**移除**违反 EULA 的 MOD 或其他软件。
+
+### 内容
+
+您的内容仍然是您的内容;我们**不**拥有您创建的**原始作品**。然而,我们将拥有我们的财产和创作的副本(或实质副本)或衍生物——但如果您创建了原创内容,它们不属于我们。
+
+### 我们的价值观
+
+**包容性**精神是我们价值观的核心,这些价值观对维持一个充满活力和欢迎的社区至关重要。
+
+- *LeviLamina* 是为 **每个人** 提供的
+- 多样性是我们社区的力量
+- 与他人一起玩应该是 **安全和包容** 的
+- **仇恨在这里没有立足之地**
+
+### LEVILAMINA 为每个人提供服务,且多样性赋能我们的社区
+
+*LeviLamina* 在世界多个国家/地区提供服务,我们希望我们的平台能够让来自不同背景的人感到安全和包容。每个 *LeviLamina* 社区的用户都应该拥有展示自己真实自我的地方,可以在一个对所有人开放的环境中进行建造、创作和表达。我们希望能打造一个每个人都能享受 *LeviLamina* 的社区。
+
+*LeviLamina* 社区属于您,但也属于每一个人。当您表达您的观点时,请做到**礼貌**和**尊重**——不要像爬行者一样爆炸!
+
+### 用户责任
+
+每个 *LeviLamina* 用户都有责任报告社区中的不当行为。用户可以通过 [我们的邮箱](feedback@levimc.org) 举报不当行为。
+
+### 与他人一起玩应该是安全和包容的,仇恨在这里没有立足之地
+
+为了保持 *LeviLamina* 社区对每个人的欢迎和包容,我们对仇恨言论、恐怖主义或极端暴力内容、欺凌、骚扰、性骚扰、诈骗或威胁他人的行为采取**零容忍政策**。您使用 *LeviLamina* 创建的 MOD 和其他内容可以是展示您关注的事情并表达创造力的好方式。但是,任何表现仇恨、极端偏见或鼓励非法活动的内容都不被允许。
+
+诈骗指的是任何通过欺骗或虚假陈述进行个人
+
+或财务获利的行为。当某人实施诈骗时,他们绕过了保持公平和财务安全的程序。
+
+### 一般条款
+
+您的国家的法律可能赋予您本 EULA **无法改变**的权利;如果是这样,本 EULA 将**在法律允许的范围内**适用。
+
+我们可能会根据我们的产品、做法或法律义务的变化偶尔更新本 EULA,但这些更改只有在**法律适用的范围内**才会生效。在这种情况下,我们会在 GitHub 上发布更新,因此请定期查看以保持了解。我们不会在这些更改上采取不公正的行动——然而,有时法律会发生变化,或者某人的行为影响到其他用户,我们需要对此进行处理。
+
+如果您对 *LeviLamina* 提出建议,该建议是免费提供的,我们**没有义务接受或考虑**它。这意味着我们可以选择使用或不使用您的建议,**不提供补偿**。如果您认为某个建议值得支付报酬,在与我们商定并书面要求之前,请不要与我们分享此建议。
+
+如果您希望就我们服务中的知识产权侵权问题通知我们,请在此提交通知。我们保留自行删除任何内容的权利。
+
+如果您违反了本 EULA,所产生的后果**完全由您负责**,无论是个人、组织还是公司。Levimc **不承担任何法律责任**,并**不对这些结果负责**。请仔细阅读并遵守本 EULA,以避免不必要的问题。
+
+## 联系信息
+
+我们的名称: [Levimc](https://github.com/LiteLDev) (LiteLDev)
+我们的官方网站: [https://levimc.org/](https://levimc.org/)
+我们的官方 Discord: [https://discord.gg/v5R5P4vRZk](https://discord.gg/v5R5P4vRZk)
+我们的官方 GitHub: [https://github.com/LiteLDev](https://github.com/LiteLDev)
+我们的官方邮箱: contact@levimc.org
+
+## 历史变更信息
+
+- 2025.01.04 完成了第一正式版 @Lovelylavender4
+- 2024.12.11 更正了首个草稿文件的名称 @Lovelylavender4
+- 2024.11.03 完成了首个草稿 @Lovelylavender4
\ No newline at end of file
diff --git a/README.md b/README.md
index 17baddb358..9142dd7c36 100644
--- a/README.md
+++ b/README.md
@@ -1,33 +1,31 @@
# LeviLamina
-![LeviLamina](https://socialify.git.ci/LiteLDev/LeviLamina/image?description=1&font=Raleway&forks=1&issues=1&logo=https%3A%2F%2Fraw.githubusercontent.com%2FLiteLDev%2FLeviLamina%2FHEAD%2Fdocs%2Fimg%2Flogo.svg&name=1&owner=1&pattern=Circuit%20Board&pulls=1&stargazers=1&theme=Auto)
+![LeviLamina](https://socialify.git.ci/LiteLDev/LeviLamina/image?description=1&font=Raleway&forks=1&issues=1&logo=https%3A%2F%2Fraw.githubusercontent.com%2FLiteLDev%2FLeviLamina%2FHEAD%2Fdocs%2Fmain%2Flogo.svg&name=1&owner=1&pattern=Circuit%20Board&pulls=1&stargazers=1&theme=Auto)
+
+[![Discord](https://img.shields.io/discord/849252980430864384?style=for-the-badge&logo=discord)](https://discord.gg/v5R5P4vRZk)
+[![Telegram](https://img.shields.io/badge/Telegram-blue?style=for-the-badge&logo=telegram)](https://t.me/LiteLoader)
+[![656669024](https://img.shields.io/badge/656669024-red?style=for-the-badge&logo=tencent%20qq)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=ndxRXO1HARA8ing7OunMClOz3cQTogL0&authKey=D7QTcqnzhBzuh3zc%2F70FjgklsVvkCImTjSRqHMwYGCLwIFpxzp%2FflC97Y7AUG%2Fpy&noverify=0&group_code=656669024)
+[![937236109](https://img.shields.io/badge/937236109-red?style=for-the-badge&logo=tencent%20qq)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=1u0nmmUIZOB716neFTlbyj_2aOQn_TV-&authKey=1lBqM20oOfdKjDnxkq09DjR729fqFfWVnaLQ7VjrDB%2FAg6qwvw6QCwdwYoRUrewU&noverify=0&group_code=937236109)
+[![850517473](https://img.shields.io/badge/850517473-red?style=for-the-badge&logo=tencent%20qq)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=3Fxt0gwMYkoLPani_vQ9tsNfYrnVy4hK&authKey=2A%2BNk3jmRaK%2FO1FBQSjTIbStAU1kbZWkjEkyh2RTVA015eTg6c4CvVhfByc1BtGZ&noverify=0&group_code=850517473)
+[![1evilamina](https://img.shields.io/badge/1evilamina-red?style=for-the-badge&logo=tencent%20qq)](https://pd.qq.com/s/a13gu04rv)
![English](https://img.shields.io/badge/English-inactive?style=for-the-badge)
[![中文](https://img.shields.io/badge/简体中文-informational?style=for-the-badge)](README.zh.md)
-A lightweight, modular and versatile plugin loader for Minecraft Bedrock Server BDS, formerly known as LiteLoaderBDS
-
-LeviLamina is an unofficial plugin loader designed to offer indispensable API support for Minecraft Bedrock Server BDS. It boasts a comprehensive API, an array of utility interfaces, a robust event system, and comprehensive support for basic interfaces. LeviLamina provides an expansive API, a powerful event system, and a wealth of encapsulated development infrastructure interfaces, forming a solid foundation for augmenting the Bedrock Edition BDS with additional gameplay features and functionalities. By leveraging plugins, the process of extending BDS functionality becomes effortless, with a user-friendly development process and an adaptable approach.
-
-Developers can effortlessly author plugins in languages such as C++, JavaScript, Lua, Python, C#, and others. This seamless integration empowers them to effortlessly expand and personalize BDS functionality, facilitating an intuitive learning experience and unparalleled flexibility.
+A lightweight, modular and versatile mod loader for Minecraft Bedrock Edition, formerly known as LiteLoaderBDS
-For more information, please refer to [the documentation](https://levilamina.liteldev.com).
+LeviLamina is an unofficial mod loader designed to offer indispensable API support for Minecraft Bedrock Edition. It boasts a comprehensive API, an array of utility interfaces, a robust event system, and comprehensive support for basic interfaces. LeviLamina provides an expansive API, a powerful event system, and a wealth of encapsulated development infrastructure interfaces, forming a solid foundation for augmenting the Minecraft Bedrock Edition with additional gameplay features and functionalities. By leveraging mods, the process of extending Bedrock functionality becomes effortless, with a user-friendly development process and an adaptable approach.
-## Table of Contents
+Developers can effortlessly author mods in languages such as C++, JavaScript, Lua, Python, C#, and others. This seamless integration empowers them to effortlessly expand and personalize Bedrock functionality, facilitating an intuitive learning experience and unparalleled flexibility.
-- [Security](#security)
-- [Install](#install)
- - [Updating](#updating)
-- [Usage](#usage)
-- [Star History](#star-history)
-- [Thanks](#thanks)
-- [Contributing](#contributing)
- - [Contributors](#contributors)
-- [License](#license)
+For more information, please refer to [the documentation](https://lamina.levimc.org).
## Security
-LeviLamina (hereinafter referred to as "this software") is developed and provided by LiteLDev (hereinafter referred to as "the developer"). This software is designed to enable users to extend the functionality of Minecraft Bedrock Server BDS (hereinafter referred to as "BDS") by loading plugins. This software is not affiliated with Mojang Studios (hereinafter referred to as "Mojang") or Microsoft Corporation (hereinafter referred to as "Microsoft"). The developer is not responsible for any content, quality, functionality, security or legality of any plugins loaded by this software. Users should use this software at their own discretion and assume all related risks.
+> [!WARNING]
+> Before installing and using LeviLamina, please be sure to read and understand the following disclaimer.
+
+LeviLamina (hereinafter referred to as "this software") is developed and provided by Levimc (hereinafter referred to as "the developer"). This software is designed to enable users to extend the functionality of Minecraft Bedrock Edition (hereinafter referred to as "MCBE") by loading mods. This software is not affiliated with Mojang Studios (hereinafter referred to as "Mojang") or Microsoft Corporation (hereinafter referred to as "Microsoft"). The developer is not responsible for any content, quality, functionality, security or legality of any mods loaded by this software. Users should use this software at their own discretion and assume all related risks.
The developer does not guarantee the stability, reliability, accuracy or completeness of this software. The developer is not liable for any defects, errors, viruses or other harmful components that may exist in this software. The developer is not liable for any direct or indirect damages (including but not limited to data loss, device damage, profit loss etc.) caused by the use of this software.
@@ -37,52 +35,21 @@ Users should comply with relevant laws and regulations when using this software,
If you have any questions or comments about this disclaimer, please contact the developer.
-## Install
-
-This project uses [lip](https://github.com/lippkg/lip). Go check them out if you don't have them locally installed.
-
-First, create a new directory for your Minecraft server and enter it:
-
-```sh
-mkdir myserver
-cd myserver
-```
-
-Then, install LeviLamina bundled with Minecraft Bedrock Server using lip:
-
-```sh
-lip install github.com/LiteLDev/LeviLamina
-```
-
-For more information, please refer to [the documentation](https://levilamina.liteldev.com).
-
-### Updating
+## Install & Usage
-When it comes to data security, we advise against updating LeviLamina in its current location. Instead, we recommend creating a new directory, installing the new version of LeviLamina there, and subsequently copying the `worlds` directory from the old location to the new one. Next, follow the instructions provided by the plugin developers to migrate the configuration files and data files of the plugins you are using to the new directory.
-
-However, if you insist on updating in the same location, you can utilize the following command to update LeviLamina:
-
-```sh
-lip install --upgrade github.com/LiteLDev/LeviLamina
-```
-
-## Usage
-
-To start the server, simply run `bedrock_server_mod.exe`:
-
-```sh
-./bedrock_server_mod.exe
-```
-
-For more information, please refer to [the documentation](https://levilamina.liteldev.com).
+Please refer to [Quickstart](https://lamina.levimc.org/quickstart/)
## Star History
![Star History Chart](https://api.star-history.com/svg?repos=LiteLDev/LeviLamina&type=Date)
-## Thanks
+## Acknowledgements
+
+We extend sincere thanks to [all donors](https://5g8svn.sharepoint.com/:x:/s/LiteLDev/EXx2ndbuC-9Bj5SR-FlJ-HUBZWy0wODjQCDb8OkzuKTFJg?e=QBF6nQ) and all the developers who have contributed to this project.
-We extend sincere thanks to [all donors](https://5g8svn.sharepoint.com/:x:/s/LiteLDev/EXx2ndbuC-9Bj5SR-FlJ-HUBZWy0wODjQCDb8OkzuKTFJg?e=QBF6nQ)!
+**Thanks to [JetBrains](https://www.jetbrains.com/) for allocating free open-source licences for IDEs such
+as [CLion](https://www.jetbrains.com/clion/)**.
+[](https://www.jetbrains.com/)
## Contributing
@@ -96,6 +63,12 @@ This project exists thanks to all the people who contribute.
![Contributors](https://contrib.rocks/image?repo=LiteLDev/LeviLamina)
+## End(er)-User License Agreement (EULA)
+
+**As a developer, you should carefully read and adhere to our [EULA](EULA.en.md) and [usage guidelines](docs/main/common_guides/usage_guidelines.en.md).**
+
## License
-LGPL-3.0-only © LiteLDev
+Copyright © 2024 LeviMC, All rights reserved.
+
+This project is licensed under the LGPL-3.0 License - see the [COPYING](COPYING) and [COPYING.LESSER](COPYING.LESSER) files for details.
diff --git a/README.zh.md b/README.zh.md
index 7ed59f3a05..8ecff0ebf4 100644
--- a/README.zh.md
+++ b/README.zh.md
@@ -1,84 +1,52 @@
# LeviLamina
-![LeviLamina](https://socialify.git.ci/LiteLDev/LeviLamina/image?description=1&font=Raleway&forks=1&issues=1&logo=https%3A%2F%2Fraw.githubusercontent.com%2FLiteLDev%2FLeviLamina%2FHEAD%2Fdocs%2Fimg%2Flogo.svg&name=1&owner=1&pattern=Circuit%20Board&pulls=1&stargazers=1&theme=Auto)
+![LeviLamina](https://socialify.git.ci/LiteLDev/LeviLamina/image?description=1&font=Raleway&forks=1&issues=1&logo=https%3A%2F%2Fraw.githubusercontent.com%2FLiteLDev%2FLeviLamina%2FHEAD%2Fdocs%2Fmain%2Flogo.svg&name=1&owner=1&pattern=Circuit%20Board&pulls=1&stargazers=1&theme=Auto)
-[![English](https://img.shields.io/badge/English-inactive?style=for-the-badge)](README.md)
-![中文](https://img.shields.io/badge/简体中文-informational?style=for-the-badge)
+[![Discord](https://img.shields.io/discord/849252980430864384?style=for-the-badge&logo=discord)](https://discord.gg/v5R5P4vRZk)
+[![Telegram](https://img.shields.io/badge/Telegram-blue?style=for-the-badge&logo=telegram)](https://t.me/LiteLoader)
+[![656669024](https://img.shields.io/badge/656669024-red?style=for-the-badge&logo=tencent%20qq)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=ndxRXO1HARA8ing7OunMClOz3cQTogL0&authKey=D7QTcqnzhBzuh3zc%2F70FjgklsVvkCImTjSRqHMwYGCLwIFpxzp%2FflC97Y7AUG%2Fpy&noverify=0&group_code=656669024)
+[![937236109](https://img.shields.io/badge/937236109-red?style=for-the-badge&logo=tencent%20qq)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=1u0nmmUIZOB716neFTlbyj_2aOQn_TV-&authKey=1lBqM20oOfdKjDnxkq09DjR729fqFfWVnaLQ7VjrDB%2FAg6qwvw6QCwdwYoRUrewU&noverify=0&group_code=937236109)
+[![850517473](https://img.shields.io/badge/850517473-red?style=for-the-badge&logo=tencent%20qq)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=3Fxt0gwMYkoLPani_vQ9tsNfYrnVy4hK&authKey=2A%2BNk3jmRaK%2FO1FBQSjTIbStAU1kbZWkjEkyh2RTVA015eTg6c4CvVhfByc1BtGZ&noverify=0&group_code=850517473)
+[![1evilamina](https://img.shields.io/badge/1evilamina-red?style=for-the-badge&logo=tencent%20qq)](https://pd.qq.com/s/a13gu04rv)
-轻量级、模块化和多功能的Minecraft Bedrock Server BDS插件加载器,曾被称为LiteLoaderBDS
+[![English](https://img.shields.io/badge/English-informational?style=for-the-badge)](README.md)
+![中文](https://img.shields.io/badge/简体中文-inactive?style=for-the-badge)
-LeviLamina是一个非官方的插件加载器,旨在为Minecraft Bedrock Server BDS提供必不可少的API支持。它拥有全面的API,一系列的实用接口,一个强大的事件系统,以及对基本接口的全面支持。LeviLamina提供了一个广泛的API,一个强大的事件系统,以及丰富的封装开发基础设施接口,为增强Bedrock Edition BDS的附加游戏功能和功能提供了坚实的基础。通过利用插件,扩展BDS功能的过程变得轻而易举,具有用户友好的开发过程和灵活的方法。
+轻量级、模块化和多功能的Minecraft基岩版模组加载器,曾被称为LiteLoaderBDS
-开发者可以轻松地用C++,JavaScript,Lua,Python,C#等语言编写插件。这种无缝集成赋予了他们轻松扩展和个性化BDS功能的能力,促进了直观的学习体验和无与伦比的灵活性。
+LeviLamina是一个非官方的模组加载器,旨在为Minecraft Bedrock Edition提供必不可少的API支持。它拥有全面的API,一系列的实用接口,一个强大的事件系统,以及对基本接口的全面支持。LeviLamina提供了一个广泛的API,一个强大的事件系统,以及丰富的封装开发基础设施接口,为增强Bedrock Edition的附加游戏功能和功能提供了坚实的基础。通过利用模组,扩展游戏功能的过程变得轻而易举,具有用户友好的开发过程和灵活的方法。
-有关更多信息,请参阅[文档](https://levilamina.liteldev.com).
+开发者可以轻松地用C++,JavaScript,Lua,Python,C#等语言编写模组。这种无缝集成赋予了他们轻松扩展和个性化游戏功能的能力,促进了直观的学习体验和无与伦比的灵活性。
-## 目录
-
-- [安全](#安全)
-- [安装](#安装)
- - [更新](#更新)
-- [使用方法](#使用方法)
-- [星星历史](#星星历史)
-- [感谢](#感谢)
-- [贡献](#贡献)
- - [贡献者](#贡献者)
-- [许可证](#许可证)
+有关更多信息,请参阅[文档](https://lamina.levimc.org/quickstart/zh/).
## 安全
-LeviLamina(以下简称“本软件”)由LiteLDev(以下简称“开发者”)开发和提供。本软件的设计目的是使用户能够通过加载插件来扩展Minecraft Bedrock Server BDS(以下简称“BDS”)的功能。本软件与Mojang Studios(以下简称“Mojang”)或Microsoft Corporation(以下简称“Microsoft”)没有任何关联。开发者对本软件加载的任何插件的内容、质量、功能、安全性或合法性不承担任何责任。用户应自行判断并承担所有相关风险。
+> [!WARNING]
+> 在安装和使用LeviLamina前,请务必阅读并理解以下免责声明。
+
+LeviLamina(以下简称“本软件”)由Levimc(以下简称“开发者”)开发和提供。本软件的设计目的是使用户能够通过加载模组来扩展Minecraft Bedrock Edition(以下简称“MCBE”)的功能。本软件与Mojang Studios(以下简称“Mojang”)或Microsoft Corporation(以下简称“Microsoft”)没有任何关联。开发者对本软件加载的任何模组的内容、质量、功能、安全性或合法性不承担任何责任。用户应自行判断并承担所有相关风险。
开发者不保证本软件的稳定性、可靠性、准确性或完整性。开发者不对本软件中可能存在的任何缺陷、错误、病毒或其他有害组件负责。开发者不对用户使用本软件造成的任何直接或间接损害(包括但不限于数据丢失、设备损坏、利润损失等)负责。
开发者保留随时修改、更新或终止本软件及其相关服务的权利,无需事先通知用户。用户应备份重要数据并定期检查本软件的更新。用户在使用本软件时应遵守相关法律法规,尊重他人的知识产权和隐私权,不得将本软件用于任何非法或侵权活动。如果用户违反上述规定,给任何第三方造成任何损害或被任何第三方索赔,开发者不承担任何责任。如果您对本免责声明有任何疑问或意见,请联系开发者。
-## 安装
-
-该项目使用 [lip](https://github.com/lippkg/lip)。如果您尚未在本地安装,请前往查看。
-
-首先,创建一个新的目录用于您的Minecraft服务器,并进入该目录:
-
-```sh
-mkdir myserver
-cd myserver
-```
-
-接下来,使用lip安装捆绑了Minecraft Bedrock Server的LeviLamina:
-
-```sh
-lip install github.com/LiteLDev/LeviLamina
-```
-
-如需更多信息,请参阅[文档](https://levilamina.liteldev.com)。
+## 安装和使用
-### 更新
+请参考 [快速入门](https://lamina.levimc.org/zh/quickstart/)
-在涉及数据安全时,我们建议不要在当前位置更新LeviLamina。相反,我们建议创建一个新目录,在新目录中安装新版本的LeviLamina,并将旧位置的`worlds`目录复制到新目录中。然后,按照插件开发者提供的说明,将您使用的插件的配置文件和数据文件迁移到新目录中。
+## 星标蹭蹭涨
-然而,如果您坚持要在相同位置更新,您可以使用以下命令来更新LeviLamina:
+![星标历史图](https://api.star-history.com/svg?repos=LiteLDev/LeviLamina&type=Date)
-```sh
-lip install --upgrade github.com/LiteLDev/LeviLamina
-```
+## 致谢
-## 使用方法
+我们衷心感谢[所有捐赠者](https://5g8svn.sharepoint.com/:x:/s/LiteLDev/EXx2ndbuC-9Bj5SR-FlJ-HUBZWy0wODjQCDb8OkzuKTFJg?e=QBF6nQ)和所有的开发者!
-要启动服务器,只需运行`bedrock_server_mod.exe`:
+**感谢 [JetBrains](https://www.jetbrains.com/)
+给开源开发者分配免费的IDE许可证,例如 [CLion](https://www.jetbrains.com/clion/)** 。
-```sh
-./bedrock_server_mod.exe
-```
-
-如需更多信息,请参阅[文档](https://levilamina.liteldev.com)。
-
-## 星星历史
-
-![星星历史图](https://api.star-history.com/svg?repos=LiteLDev/LeviLamina&type=Date)
-
-## 感谢
-
-我们衷心感谢[所有捐赠者](https://5g8svn.sharepoint.com/:x:/s/LiteLDev/EXx2ndbuC-9Bj5SR-FlJ-HUBZWy0wODjQCDb8OkzuKTFJg?e=QBF6nQ)!
+[](https://www.jetbrains.com/)
## 贡献
@@ -92,6 +60,12 @@ LeviLamina遵循[Code of Conduct](https://www.contributor-covenant.org/version/2
![贡献者](https://contrib.rocks/image?repo=LiteLDev/LeviLamina)
+## 最终用户许可协议 (EULA)
+
+**作为一个开发者,你应该仔细阅读并遵守我们的[EULA](EULA.zh.md)与[使用准则](docs/main/common_guides/usage_guidelines.zh.md)。**
+
## 许可证
-LGPL-3.0-only © LiteLDev
+版权所有 © 2024 LeviMC, 保留所有权利.
+
+本项目采用 LGPL-3.0 许可证发行 - 阅读 [COPYING](COPYING) 和 [COPYING.LESSER](COPYING.LESSER) 文件获取更多信息。
diff --git a/Doxyfile b/docs/api/Doxyfile
similarity index 99%
rename from Doxyfile
rename to docs/api/Doxyfile
index bc470bc9da..84e3e68d09 100644
--- a/Doxyfile
+++ b/docs/api/Doxyfile
@@ -61,7 +61,7 @@ PROJECT_BRIEF =
# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy
# the logo to the output directory.
-PROJECT_LOGO = docs/img/logo_small.svg
+PROJECT_LOGO = docs/api/logo.svg
# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path
# into which the generated documentation will be written. If a relative path is
@@ -815,7 +815,7 @@ FILE_VERSION_FILTER =
# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE
# tag is left empty.
-LAYOUT_FILE = DoxygenLayout.xml
+LAYOUT_FILE = docs/api/DoxygenLayout.xml
# The CITE_BIB_FILES tag can be used to specify one or more bib files containing
# the reference definitions. This must be a list of .bib files. The .bib
@@ -1309,7 +1309,7 @@ HTML_FILE_EXTENSION = .html
# of the possible markers and block names see the documentation.
# This tag requires that the tag GENERATE_HTML is set to YES.
-HTML_HEADER = docs/api/assets/header.html
+HTML_HEADER = docs/api/header.html
# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each
# generated HTML page. If the tag is left blank doxygen will generate a standard
@@ -1349,7 +1349,7 @@ HTML_STYLESHEET =
# documentation.
# This tag requires that the tag GENERATE_HTML is set to YES.
-HTML_EXTRA_STYLESHEET = docs/api/assets/doxygen-awesome-css/doxygen-awesome.css
+HTML_EXTRA_STYLESHEET = docs/api/doxygen-awesome-css/doxygen-awesome.css
# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
# other source files which should be copied to the HTML output directory. Note
@@ -1359,7 +1359,7 @@ HTML_EXTRA_STYLESHEET = docs/api/assets/doxygen-awesome-css/doxygen-awesome.css
# files will be copied as-is; there are no commands or markers available.
# This tag requires that the tag GENERATE_HTML is set to YES.
-HTML_EXTRA_FILES = docs/api/assets/doxygen-awesome-css/doxygen-awesome-darkmode-toggle.js
+HTML_EXTRA_FILES = docs/api/doxygen-awesome-css/doxygen-awesome-darkmode-toggle.js
# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output
# should be rendered with a dark or light theme.
diff --git a/DoxygenLayout.xml b/docs/api/DoxygenLayout.xml
similarity index 99%
rename from DoxygenLayout.xml
rename to docs/api/DoxygenLayout.xml
index 02ce4b6cf3..39bbe51d24 100644
--- a/DoxygenLayout.xml
+++ b/docs/api/DoxygenLayout.xml
@@ -4,7 +4,7 @@
-
+
diff --git a/docs/api/assets/doxygen-awesome-css b/docs/api/assets/doxygen-awesome-css
deleted file mode 160000
index df83fbf22c..0000000000
--- a/docs/api/assets/doxygen-awesome-css
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit df83fbf22cfff76b875c13d324baf584c74e96d0
diff --git a/docs/api/assets/header.html b/docs/api/header.html
similarity index 100%
rename from docs/api/assets/header.html
rename to docs/api/header.html
diff --git a/docs/img/logo_small.svg b/docs/api/logo.svg
similarity index 100%
rename from docs/img/logo_small.svg
rename to docs/api/logo.svg
diff --git a/docs/discuss.md b/docs/discuss.md
deleted file mode 100644
index 122ece4851..0000000000
--- a/docs/discuss.md
+++ /dev/null
@@ -1,23 +0,0 @@
-# Discuss
-
-## Users and Creators
-
-We provide a variety of ways to discuss, you can choose the one you like best.
-
-[![Discord](https://img.shields.io/discord/849252980430864384?style=for-the-badge&logo=discord)
-](https://discord.gg/v5R5P4vRZk)
-[![Telegram](https://img.shields.io/badge/Telegram-blue?style=for-the-badge&logo=telegram)
-](https://t.me/LiteLoader)
-[![656669024](https://img.shields.io/badge/656669024-red?style=for-the-badge&logo=tencent%20qq)
-](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=ndxRXO1HARA8ing7OunMClOz3cQTogL0&authKey=D7QTcqnzhBzuh3zc%2F70FjgklsVvkCImTjSRqHMwYGCLwIFpxzp%2FflC97Y7AUG%2Fpy&noverify=0&group_code=656669024)
-[![850517473](https://img.shields.io/badge/850517473-red?style=for-the-badge&logo=tencent%20qq)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=YFHRYvpO6mjqE5QeZxcMIlahGzWR3JLA&authKey=M8p8hkdctNSyXJo7Ux%2FzdNu4VL2jLiqMGakM3eHlA4ZLvjdwtL%2F1SIKE51s%2FKcp6&noverify=0&group_code=850517473)
-[![1evilamina](https://img.shields.io/badge/1evilamina-red?style=for-the-badge&logo=tencent%20qq)](https://pd.qq.com/s/a13gu04rv)
-
-## Developers
-
-To contact the core developers of LeviLamina and discuss matters pertaining to the development process, as well as to ask the difficult questions, you can use the following means:
-
-[![GitHub issues](https://img.shields.io/github/issues-raw/LiteLDev/LeviLamina?style=for-the-badge&logo=github)
-](https://github.com/LiteLDev/LeviLamina/issues)
-[![GitHub Discussions](https://img.shields.io/github/discussions/LiteLDev/LeviLamina?style=for-the-badge&logo=github)
-](https://github.com/LiteLDev/LeviLamina/discussions)
diff --git a/docs/discuss.zh.md b/docs/discuss.zh.md
deleted file mode 100644
index 4d36f0e8ff..0000000000
--- a/docs/discuss.zh.md
+++ /dev/null
@@ -1,22 +0,0 @@
-# 讨论
-
-## 用户和创造者
-
-我们提供了各种讨论方式,你可以选择你最喜欢的方式。
-
-[![Discord](https://img.shields.io/discord/849252980430864384?style=for-the-badge&logo=discord)
-](https://discord.gg/v5R5P4vRZk)
-[![Telegram](https://img.shields.io/badge/Telegram-blue?style=for-the-badge&logo=telegram)
-](https://t.me/LiteLoader)
-[![656669024](https://img.shields.io/badge/656669024-red?style=for-the-badge&logo=tencent%20qq)
-](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=ndxRXO1HARA8ing7OunMClOz3cQTogL0&authKey=D7QTcqnzhBzuh3zc%2F70FjgklsVvkCImTjSRqHMwYGCLwIFpxzp%2FflC97Y7AUG%2Fpy&noverify=0&group_code=656669024)
-[![850517473](https://img.shields.io/badge/850517473-red?style=for-the-badge&logo=tencent%20qq)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=YFHRYvpO6mjqE5QeZxcMIlahGzWR3JLA&authKey=M8p8hkdctNSyXJo7Ux%2FzdNu4VL2jLiqMGakM3eHlA4ZLvjdwtL%2F1SIKE51s%2FKcp6&noverify=0&group_code=850517473)
-[![1evilamina](https://img.shields.io/badge/1evilamina-red?style=for-the-badge&logo=tencent%20qq)](https://pd.qq.com/s/a13gu04rv)
-## 开发者
-
-要联系LeviLamina的核心开发人员并讨论与开发过程相关的事宜,以及提出困难的问题,您可以使用以下方式:
-
-[![GitHub issues](https://img.shields.io/github/issues-raw/LiteLDev/LeviLamina?style=for-the-badge&logo=github)
-](https://github.com/LiteLDev/LeviLamina/issues)
-[![GitHub Discussions](https://img.shields.io/github/discussions/LiteLDev/LeviLamina?style=for-the-badge&logo=github)
-](https://github.com/LiteLDev/LeviLamina/discussions)
diff --git a/docs/engines/legacy_script_engine.md b/docs/engines/legacy_script_engine.md
deleted file mode 100644
index c4adb15b5a..0000000000
--- a/docs/engines/legacy_script_engine.md
+++ /dev/null
@@ -1,29 +0,0 @@
-# LegacyScriptEngine
-
-A plugin engine for running LLSE plugins on LeviLamina
-
-## Install
-
-```sh
-lip install github.com/LiteLDev/LegacyScriptEngine
-```
-
-## Usage
-
-1. Put LLSE plugins directly in `/path/to/bedrock_dedicated_server/plugins/`。
-
-2. Run the server, then the plugins will be migrated to LeviLamina plugin manifest automatically.
-
-3. To load them, you need to restart the server.
-
-For more information, please refer to [the documentation](https://lse.liteldev.com).
-
-## Contributing
-
-If you have any questions, please open an issue to discuss it.
-
-PRs are welcome.
-
-## License
-
-GPL-3.0-only © LiteLDev
diff --git a/docs/faq.md b/docs/faq.md
deleted file mode 100644
index 88fdfadc72..0000000000
--- a/docs/faq.md
+++ /dev/null
@@ -1,23 +0,0 @@
-# FAQ
-
-## Where does LeviLamina come from?
-
-LeviLamina emerges from the depths of Minecraft Bedrock Server, sprouting forth from the seeds of the [LiteLoaderBDS](https://github.com/LiteLDev/LiteLoaderBDSv2) project.
-
-The initial versions of Minecraft Bedrock Edition lacked the extensive mod and server plugin ecosystem present in Minecraft Java Edition, thereby limiting its gameplay possibilities. A collective of C++ enthusiasts, well-versed in Minecraft Bedrock Server, conducted an analysis and utilized reverse engineering techniques and hook injection mechanisms to intervene in the operation of the game server, thus pioneering the development of the first set of server plugins.
-
-However, this development paradigm encountered several challenges. Primarily, the absence of underlying framework support necessitated the reliance on diverse low-level tools for symbol analysis, injection implementation, hook registration, and other functionalities during plugin development. Consequently, this led to code redundancy and duplication across different plugins, as well as the potential for unforeseen conflicts among them. Moreover, the lack of explicit type definitions compelled developers to engage in reverse engineering analysis while creating plugins, resulting in elevated development barriers and diminished efficiency.
-
-In response to these predicaments, the maintainers of the precursor project, LiteLoaderBDS, constructed an injection-based plugin loading engine and a plugin development framework. In addition, they provided type information, enabling plugin developers to create plugins without requiring an exhaustive comprehension of the underlying principles. This significantly mitigated the entry barriers for plugin development and facilitated the flourishing of the plugin ecosystem.
-
-Nevertheless, as LiteLoaderBDS progressed, certain issues came to the fore. The early design failed to account for future advancements, rendering many aspects obsolete and impeding the utilization of contemporary tools, thereby falling short of meeting the latest performance requirements. Furthermore, its tightly coupled design engendered substantial efforts when adapting to new iterations of Minecraft Bedrock Server. Consequently, LiteLDev made the decision to commence from scratch, leveraging existing expertise, and undertaking a comprehensive framework redesign to cultivate a plugin engine that promotes user-friendliness for maintainers, developers, and users alike.
-
-## Why was LiteLoaderBDS renamed to LeviLamina?
-
-Oh, it's quite a tale! You see, LiteLoaderBDS started off as a little joke, a playful experiment. But lo and behold, it grew faster than a creeper on steroids!
-
-At first, we noticed that the name LiteLoaderBDS sounded strikingly similar to a mod launcher project in the Minecraft Java version. So, in a moment of genius, we slapped a "BDS" suffix on it to differentiate ourselves. Clever, right?
-
-But as LiteLoaderBDS 2.0 expanded, it started bulking up like a piglet at an all-you-can-eat buffet. Its performance took a nosedive, leaving us scratching our heads.
-
-That's when we made a bold decision to rebuild the entire project from scratch. And as we delved deeper into the revamping process, it became clear that a fresh start deserved a brand-new moniker. So, we dabbled in the ancient arts of Latin roots, preserving the beloved abbreviation `ll`, and voila! LeviLamina was born, a name befitting our magnificent creation.
diff --git a/docs/index.md b/docs/index.md
deleted file mode 100644
index 055ff8941c..0000000000
--- a/docs/index.md
+++ /dev/null
@@ -1,80 +0,0 @@
-# LeviLamina
-
-![LeviLamina](https://socialify.git.ci/LiteLDev/LeviLamina/image?description=1&font=Raleway&forks=1&issues=1&logo=https%3A%2F%2Fraw.githubusercontent.com%2FLiteLDev%2FLeviLamina%2FHEAD%2Fdocs%2Fimg%2Flogo.svg&name=1&owner=1&pattern=Circuit%20Board&pulls=1&stargazers=1&theme=Auto)
-
-A lightweight, modular and versatile plugin loader for Minecraft Bedrock Server BDS, formerly known as LiteLoaderBDS
-
-LeviLamina is an unofficial plugin loader designed to offer indispensable API support for Minecraft Bedrock Server BDS. It boasts a comprehensive API, an array of utility interfaces, a robust event system, and comprehensive support for basic interfaces. LeviLamina provides an expansive API, a powerful event system, and a wealth of encapsulated development infrastructure interfaces, forming a solid foundation for augmenting the Bedrock Edition BDS with additional gameplay features and functionalities. By leveraging plugins, the process of extending BDS functionality becomes effortless, with a user-friendly development process and an adaptable approach.
-
-Developers can effortlessly author plugins in languages such as C++, JavaScript, Lua, Python, C#, and others. This seamless integration empowers them to effortlessly expand and personalize BDS functionality, facilitating an intuitive learning experience and unparalleled flexibility.
-
-## Security
-
-LeviLamina (hereinafter referred to as "this software") is developed and provided by LiteLDev (hereinafter referred to as "the developer"). This software is designed to enable users to extend the functionality of Minecraft Bedrock Server BDS (hereinafter referred to as "BDS") by loading plugins. This software is not affiliated with Mojang Studios (hereinafter referred to as "Mojang") or Microsoft Corporation (hereinafter referred to as "Microsoft"). The developer is not responsible for any content, quality, functionality, security or legality of any plugins loaded by this software. Users should use this software at their own discretion and assume all related risks.
-
-The developer does not guarantee the stability, reliability, accuracy or completeness of this software. The developer is not liable for any defects, errors, viruses or other harmful components that may exist in this software. The developer is not liable for any direct or indirect damages (including but not limited to data loss, device damage, profit loss etc.) caused by the use of this software.
-
-The developer reserves the right to modify, update or terminate this software and its related services at any time without prior notice to users. Users should back up important data and check regularly for updates of this software.
-
-Users should comply with relevant laws and regulations when using this software, respect the intellectual property rights and privacy rights of others, and not use this software for any illegal or infringing activities. If users violate the above provisions and cause any damage to any third party or are claimed by any third party, the developer does not bear any responsibility.
-
-If you have any questions or comments about this disclaimer, please contact the developer.
-
-## Install
-
-This project uses [lip](https://github.com/lippkg/lip). Go check them out if you don't have them locally installed.
-
-First, create a new directory for your Minecraft server and enter it:
-
-```sh
-mkdir myserver
-cd myserver
-```
-
-Then, install LeviLamina bundled with Minecraft Bedrock Server using lip:
-
-```sh
-lip install github.com/LiteLDev/LeviLamina
-```
-
-### Updating
-
-When it comes to data security, we advise against updating LeviLamina in its current location. Instead, we recommend creating a new directory, installing the new version of LeviLamina there, and subsequently copying the `worlds` directory from the old location to the new one. Next, follow the instructions provided by the plugin developers to migrate the configuration files and data files of the plugins you are using to the new directory.
-
-However, if you insist on updating in the same location, you can utilize the following command to update LeviLamina:
-
-```sh
-lip install --upgrade github.com/LiteLDev/LeviLamina
-```
-
-## Usage
-
-To start the server, simply run `bedrock_server_mod.exe`:
-
-```sh
-./bedrock_server_mod.exe
-```
-
-## Star History
-
-![Star History Chart](https://api.star-history.com/svg?repos=LiteLDev/LeviLamina&type=Date)
-
-## Thanks
-
-We extend sincere thanks to [all donors](https://5g8svn.sharepoint.com/:x:/s/LiteLDev/EXx2ndbuC-9Bj5SR-FlJ-HUBZWy0wODjQCDb8OkzuKTFJg?e=QBF6nQ)!
-
-## Contributing
-
-Feel free to dive in! [Open an issue](https://github.com/LiteLDev/LeviLamina/issues/new/choose) or submit PRs.
-
-LeviLamina follows the [Contributor Covenant](https://www.contributor-covenant.org/version/2/1/code_of_conduct/) Code of Conduct.
-
-### Contributors
-
-This project exists thanks to all the people who contribute.
-
-![Contributors](https://contrib.rocks/image?repo=LiteLDev/LeviLamina)
-
-## License
-
-LGPL-3.0-only © LiteLDev
diff --git a/docs/index.zh.md b/docs/index.zh.md
deleted file mode 100644
index 88dd104929..0000000000
--- a/docs/index.zh.md
+++ /dev/null
@@ -1,80 +0,0 @@
-# LeviLamina
-
-![LeviLamina](https://socialify.git.ci/LiteLDev/LeviLamina/image?description=1&font=Raleway&forks=1&issues=1&logo=https%3A%2F%2Fraw.githubusercontent.com%2FLiteLDev%2FLeviLamina%2FHEAD%2Fdocs%2Fimg%2Flogo.svg&name=1&owner=1&pattern=Circuit%20Board&pulls=1&stargazers=1&theme=Auto)
-
-轻量级、模块化和多功能的Minecraft Bedrock Server BDS插件加载器,曾被称为LiteLoaderBDS
-
-LeviLamina是一个非官方的插件加载器,旨在为Minecraft Bedrock Server BDS提供必不可少的API支持。它拥有全面的API,一系列的实用接口,一个强大的事件系统,以及对基本接口的全面支持。LeviLamina提供了一个广泛的API,一个强大的事件系统,以及丰富的封装开发基础设施接口,为增强Bedrock Edition BDS的附加游戏功能和功能提供了坚实的基础。通过利用插件,扩展BDS功能的过程变得轻而易举,具有用户友好的开发过程和灵活的方法。
-
-开发者可以轻松地用C++,JavaScript,Lua,Python,C#等语言编写插件。这种无缝集成赋予了他们轻松扩展和个性化BDS功能的能力,促进了直观的学习体验和无与伦比的灵活性。
-
-## 安全
-
-LeviLamina(以下简称“本软件”)由LiteLDev(以下简称“开发者”)开发和提供。本软件的设计目的是使用户能够通过加载插件来扩展Minecraft Bedrock Server BDS(以下简称“BDS”)的功能。本软件与Mojang Studios(以下简称“Mojang”)或Microsoft Corporation(以下简称“Microsoft”)没有任何关联。开发者对本软件加载的任何插件的内容、质量、功能、安全性或合法性不承担任何责任。用户应自行判断并承担所有相关风险。
-
-开发者不保证本软件的稳定性、可靠性、准确性或完整性。开发者不对本软件中可能存在的任何缺陷、错误、病毒或其他有害组件负责。开发者不对用户使用本软件造成的任何直接或间接损害(包括但不限于数据丢失、设备损坏、利润损失等)负责。
-
-开发者保留随时修改、更新或终止本软件及其相关服务的权利,无需事先通知用户。用户应备份重要数据并定期检查本软件的更新。用户在使用本软件时应遵守相关法律法规,尊重他人的知识产权和隐私权,不得将本软件用于任何非法或侵权活动。如果用户违反上述规定,给任何第三方造成任何损害或被任何第三方索赔,开发者不承担任何责任。如果您对本免责声明有任何疑问或意见,请联系开发者。
-
-## 安装
-
-该项目使用 [lip](https://github.com/lippkg/lip)。如果您尚未在本地安装,请前往查看。
-
-首先,创建一个新的目录用于您的Minecraft服务器,并进入该目录:
-
-```sh
-mkdir myserver
-cd myserver
-```
-
-接下来,使用lip安装捆绑了Minecraft Bedrock Server的LeviLamina:
-
-```sh
-lip install github.com/LiteLDev/LeviLamina
-```
-
-如需更多信息,请参阅[文档](https://levilamina.liteldev.com)。
-
-### 更新
-
-在涉及数据安全时,我们建议不要在当前位置更新LeviLamina。相反,我们建议创建一个新目录,在新目录中安装新版本的LeviLamina,并将旧位置的`worlds`目录复制到新目录中。然后,按照插件开发者提供的说明,将您使用的插件的配置文件和数据文件迁移到新目录中。
-
-然而,如果您坚持要在相同位置更新,您可以使用以下命令来更新LeviLamina:
-
-```sh
-lip install --upgrade github.com/LiteLDev/LeviLamina
-```
-
-## 使用方法
-
-要启动服务器,只需运行`bedrock_server_mod.exe`:
-
-```sh
-./bedrock_server_mod.exe
-```
-
-如需更多信息,请参阅[文档](https://levilamina.liteldev.com)。
-
-## 星星历史
-
-![星星历史图](https://api.star-history.com/svg?repos=LiteLDev/LeviLamina&type=Date)
-
-## 感谢
-
-我们衷心感谢[所有捐赠者](https://5g8svn.sharepoint.com/:x:/s/LiteLDev/EXx2ndbuC-9Bj5SR-FlJ-HUBZWy0wODjQCDb8OkzuKTFJg?e=QBF6nQ)!
-
-## 贡献
-
-欢迎参与![打开一个问题](https://github.com/LiteLDev/LeviLamina/issues/new/choose)或提交Pull Requests。
-
-LeviLamina遵循[Code of Conduct](https://www.contributor-covenant.org/version/2/1/code_of_conduct/)行为准则。
-
-### 贡献者
-
-这个项目的存在要感谢所有的贡献者。
-
-![贡献者](https://contrib.rocks/image?repo=LiteLDev/LeviLamina)
-
-## 许可证
-
-LGPL-3.0-only © LiteLDev
diff --git a/docs/install.md b/docs/install.md
deleted file mode 100644
index 6c12b14cf3..0000000000
--- a/docs/install.md
+++ /dev/null
@@ -1,100 +0,0 @@
-# Install on Windows
-
-## Prerequisites
-
-To install LeviLamina, you need one of the following Windows versions:
-
-- Windows 10
-- Windows 11
-- Windows Server 2019
-- Windows Server 2022
-
-To run Bedrock Dedicated Server for Minecraft, you need to install the following software:
-
-- [Visual C++ Redistributable for Visual Studio 2015, 2017, 2019, and 2022](https://aka.ms/vs/17/release/vc_redist.x64.exe)
-
-Since LeviLamina is not compatible with the previous LiteLoaderBDS 2, you need to uninstall LiteLoaderBDS 2 before installing LeviLamina.
-
-## Installation Methods
-
-You can install LeviLamina in different ways, depending on your needs:
-
-- You can [install via LipUI](#install-via-lipui), which is a graphical user interface for lip. This is useful if you are not familiar with the command line.
-
-- You can [install via lip](#install-via-lip), for ease of installation and upgrade tasks. This is the recommended approach.
-
-- You can download the modules and [install them manually](#install-manually) and manage upgrades completely manually. This is useful in situations such as installing LeviLamina on air-gapped systems with no access to the internet.
-
-### Install via LipUI
-
-See [LipUI](https://github.com/lippkg/LipUI) for more information.
-
-### Install via lip
-
-If you have not installed lip, you can install it following the instructions in [lip installation guide](https://docs.lippkg.com/installation.html).
-
-After installing lip, you can install LeviLamina by running the following command:
-
-```powershell
-lip install github.com/LiteLDev/LeviLamina
-```
-
-To install a specific version of LeviLamina, for example, 0.4.2, you can run the following command:
-
-```powershell
-lip install github.com/LiteLDev/LeviLamina@0.4.2
-```
-
-During the installation, you might be asked to confirm some prompts. You can press `y` to confirm the prompts. To skip the prompts, you can add the `-y` option to the command.
-
-```powershell
-lip install -y github.com/LiteLDev/LeviLamina
-```
-
-You have now successfully installed LeviLamina.
-
-To upgrade LeviLamina, you can run the following command:
-
-```powershell
-lip install --upgrade github.com/LiteLDev/LeviLamina
-```
-
-!!! danger
- Upgrading LeviLamina may result in data loss. Please make sure you have a backup of your data before upgrading.
-
-### Install Manually
-
-#### Downloading Necessary Files
-
-##### 1. **Download LeviLamina**:
- - Go to [LeviLamina's Release Page on GitHub](https://github.com/LiteLDev/LeviLamina/releases).
- - Choose the version you want.
- - Download the `levilamina-windows-x64.zip` file from the selected version's assets.
-
-##### 2. **Download BDS (Minecraft Server)**:
- - Visit [Minecraft's server download page](https://www.minecraft.net/en-us/download/server/bedrock).
- - Get the Bedrock Dedicated Server (BDS) version that corresponds with your LeviLamina version, named `bedrock-server-.zip`.
-
-##### 3. **Get PeEditor and PreLoader**:
- - Head to their respective GitHub release pages: [PeEditor Releases](https://github.com/LiteLDev/PeEditor/releases) and [PreLoader Releases](https://github.com/LiteLDev/PreLoader/releases).
- - Download the latest `PeEditor.exe` and `PreLoader.dll`.
-
-> **Note**: Typically, the latest version of LeviLamina aligns with the newest versions of PeEditor and PreLoader. Ensure they are compatible with your BDS version.
-
-#### Installation Steps
-
-##### 1. **Unzip the BDS File**:
- - Extract the `bedrock-server-.zip` file, which you obtained from step 2, into a new, empty folder.
-
-##### 2. **Place PeEditor and PreLoader**:
- - Move `PeEditor.exe` and `PreLoader.dll` into the same directory where you extracted the BDS files, ensuring they are alongside `bedrock_server.exe`.
-
-##### 3. **Include LeviLamina Files**:
- - Unzip the `levilamina-windows-x64.zip` file from step 1.
- - Take the `bin/LeviLamina.dll` and `bin/LeviLamina.pdb` files and place them in the same directory as the BDS files.
-
-##### 4. **Run PeEditor**:
- - Double-click `PeEditor.exe` and wait for the process to complete.
- - You should then find a new file named `bedrock_server_mod.exe` in the directory, and the original `bedrock_server.exe` will be renamed to `bedrock_server.exe.bak`.
-
-Now, you have successfully installed LeviLamina. To start it, run `bedrock_server_mod.exe`.
diff --git a/docs/install.zh.md b/docs/install.zh.md
deleted file mode 100644
index 53d44fc261..0000000000
--- a/docs/install.zh.md
+++ /dev/null
@@ -1,101 +0,0 @@
-# 在Windows上安装
-
-## 前提条件
-
-要安装 LeviLamina,你需要以下 Windows 版本之一:
-
-- Windows 10
-- Windows 11
-- Windows Server 2019
-- Windows Server 2022
-
-要运行 Minecraft 的 Bedrock Dedicated Server,你需要安装以下软件:
-
-- [Visual C++ Redistributable for Visual Studio 2015, 2017, 2019, and 2022](https://aka.ms/vs/17/release/vc_redist.x64.exe)
-
-由于 LeviLamina 不兼容以前的 LiteLoaderBDS 2,你需要在安装 LeviLamina 之前卸载 LiteLoaderBDS 2。
-
-## 安装方法
-
-你可以通过不同的方式安装 LeviLamina,取决于你的需求:
-
-- 你可以[通过 lipUI 安装](#通过-lipui-安装),这是 lip 的图形用户界面。这对于不熟悉命令行的用户很有用。
-
-- 你可以[通过 lip 安装](#通过-lip-安装),以便于安装和升级任务。这是推荐的方法。
-
-- 你可以下载模块并[手动安装](#手动安装)并完全手动管理升级。这在一些情况下很有用,比如在没有网络访问的隔离系统上安装 LeviLamina。
-
-### 通过 lipUI 安装
-
-你可以前往[LipUI](https://github.com/lippkg/LipUI)了解更多信息。
-
-### 通过 lip 安装
-
-如果你还没有安装 lip,你可以按照[lip 安装指南](https://docs.lippkg.com/installation.html)中的说明进行安装。
-
-安装 lip 后,你可以通过运行以下命令来安装 LeviLamina:
-
-```powershell
-lip install github.com/LiteLDev/LeviLamina
-```
-
-要安装 LeviLamina 的特定版本,例如,1.0.0,你可以运行以下命令:
-
-```powershell
-lip install github.com/LiteLDev/LeviLamina@1.0.0
-```
-
-在安装过程中,你可能会被要求确认一些提示。你可以按 `y` 来确认提示。要跳过提示,你可以在命令中添加 `-y` 选项。
-
-```powershell
-lip install -y github.com/LiteLDev/LeviLamina
-```
-
-你现在已经成功安装了 LeviLamina。要升级 LeviLamina,你可以运行以下命令:
-
-```powershell
-lip install --upgrade github.com/LiteLDev/LeviLamina
-```
-
-!!! danger
- 升级 LeviLamina 可能会导致数据丢失。请确保在升级之前备份你的数据。
-
-### 手动安装
-
-#### 准备所需文件
-
-##### 1. **下载 LeviLamina**
- - 访问 LeviLamina 在 GitHub 的 [发布页面](https://github.com/LiteLDev/LeviLamina/releases)。
- - 选择你需要的版本。
- - 下载该版本的 `levilamina-windows-x64.zip` 文件。
-
-##### 2. **下载 BDS 服务端**
- - 前往 [Minecraft 官网](https://www.minecraft.net/zh-hans/download/server/bedrock)。
- - 下载与 LeviLamina 版本相对应的 Bedrock Dedicated Server (BDS) 版本。文件名为 `bedrock-server-.zip`。
-
-##### 3. **下载其他必要文件**
- - 访问 PeEditor 和 PreLoader 在 GitHub 的发布页面 ([PeEditor](https://github.com/LiteLDev/PeEditor/releases) 和 [PreLoader](https://github.com/LiteLDev/PreLoader/releases))。
- - 下载最新版本的 `PeEditor.exe` 和 `PreLoader.dll` 文件。
-
-> **提示**: LeviLamina 的最新版通常与 PeEditor 和 PreLoader 的最新版相匹配。确保你下载的 LeviLamina 版本与 BDS 版本一致。
-
-#### 安装步骤
-
-##### 1. **解压 BDS 文件**
- - 将你在第二步中下载的 `bedrock-server-.zip` 解压到一个新的、空的文件夹中。
-
-##### 2. **移动 PeEditor 和 PreLoader**
- - 把 `PeEditor.exe` 和 `PreLoader.dll` 移到解压后的 BDS 文件夹中。
- - 确保这些文件与 `bedrock_server.exe` 在同一目录下。
-
-##### 3. **添加 LeviLamina 文件**
- - 解压你在第一步中下载的 `levilamina-windows-x64.zip`。
- - 从中提取 `bin/LeviLamina.dll` 和 `bin/LeviLamina.pdb` 文件。
- - 将这些文件也移动到 BDS 文件夹中,与 `bedrock_server.exe` 放在同一目录下。
-
-##### 4. **运行 PeEditor**
- - 双击 `PeEditor.exe` 并等待其执行完毕。
- - 执行完毕后,应该在文件夹中看到一个新的 `bedrock_server_mod.exe` 文件。
- - 原来的 `bedrock_server.exe` 将被重命名为 `bedrock_server.exe.bak`。
-
-完成以上步骤后,LeviLamina 就安装好了。你可以通过双击 `bedrock_server_mod.exe` 来启动服务端。
diff --git a/docs/links.md b/docs/links.md
deleted file mode 100644
index 4c33c32fe6..0000000000
--- a/docs/links.md
+++ /dev/null
@@ -1,15 +0,0 @@
-# Links
-
-## LegacyScriptEngine
-
-A plugin engine for running LLSE plugins on LeviLamina
-
-GitHub repository:
-
-Documentation:
-
-## MoreDimensions
-
-More than three dimensions on BDS!
-
-GitHub repository:
diff --git a/docs/main/common_guides/usage_guidelines.en.md b/docs/main/common_guides/usage_guidelines.en.md
new file mode 100644
index 0000000000..9526b57f3f
--- /dev/null
+++ b/docs/main/common_guides/usage_guidelines.en.md
@@ -0,0 +1,92 @@
+# LeviLamina Usage Guidelines
+
+This document is intended only as a **translation** of the usage guidelines, the original text being the **Chinese version**. In other words, if there is a conflict between this document and the Chinese version, **the Chinese version shall prevail**. In addition, since this document is a translated version and may not be updated in a timely manner**, we recommend that you check the Chinese version to ensure that you are **aware** of updates.
+
+## Purpose
+
+We really enjoy seeing our users and fans do cool things and share them with the community. Our goal is to create an environment where you can make great contributions to the *LeviLamina* community while ensuring others do not spoil the experience for everyone. We recognize that some of the content you create and share is for the community, while other content may be something you create and sell. Therefore, we have established these guidelines to explain how to use the *LeviLamina* name, brand, and assets in your creations.
+
+In short, we hope these guidelines help you understand what you can and cannot do, helping you create and share more, while limiting those who might cross the line.
+
+### Important Legal Agreements
+
+[The LeviLamina End User License Agreement](../../../EULA.en.md) (EULA) is a legal agreement between you and us ([*Levimc*](https://github.com/Levimc)). The EULA covers your use of *LeviLamina* products and governs your use of our website and how we resolve disputes. It is your responsibility to understand and comply with the EULA.
+
+These guidelines should be followed alongside the EULA but do not form part of the EULA. They exist because we currently believe that allowing some flexibility in the use of *LeviLamina* is a good idea.
+
+## All Uses
+
+For all uses (whether permitted or not):
+
+- All rights (including copyright, trademark rights, and related rights) in the name, brand, assets, and any derivatives remain owned by *Levimc* and will always be retained.
+- All permissions and consents are granted at our discretion and may be revoked at any time if we deem it appropriate or if we do not like what you are doing.
+- All rights are expressly reserved.
+
+If you are interested in proposing a formal partnership with us, please maintain that interest, as we are currently **not** considering partnerships with other individuals, organizations, companies, etc. If you wish to report a violation of these guidelines, you can send us information through [our email](feedback@levimc.org). We **cannot** provide advice on whether a specific project complies with these guidelines. If you are unsure, you should consult with a lawyer for assistance.
+
+We will add more guidelines as we decide when and what to include—feel free to make requests.
+
+## Essential Guidelines
+
+We want to make it clear that these are the basic guidelines for **prohibited** uses of our name, brand, and assets. You should still read all policies and guidelines in full, but here is a brief summary of the key points to help guide you.
+
+If you use any part of any name, brand, or asset, then:
+
+**Do not** engage in any activities or include any content that might lead people to believe that the content you share could be construed as official, approved, endorsed, associated with, supported by, or related to us.
+
+- **Do not** engage in any unlawful, misleading, obscene, harmful, or offensive activities.
+- **Do not** engage in any activities that could damage or undermine our name, brand, or assets (for example: gambling, pornography, violence, terrorism, or other unsafe/adult content).
+- **Do not** redistribute our products or any modifications of our products or product documentation.
+- **Do not** allow unfair or unreasonable access to any content created by us.
+- **Do not** impersonate *Levimc* or appear to be associated with or supported by *Levimc*, and explicitly state:
+ - You (rather than us) are responsible for the products or services (including events, servers, books, and handmade items).
+ - Who the publisher, manufacturer, seller, organizer, and/or owner is.
+ - The contact and contact information for products, services, or any related purchases (chat and forum links are not acceptable).
+- Attempting to obtain any data that violates the [Minecraft EULA](https://www.minecraft.net/en-us/eula) or could harm the security of [Minecraft](https://www.minecraft.net/) by dumping [*LeviLamina*](https://github.com/LiteLDev/LeviLamina) or any of [our other products](https://github.com/orgs/LiteLDev/repositories) (see the first entry under "**DEFINITIONS**" for more details) is unwise!
+- **Do not** attempt to reverse *LeviLamina* for malicious purposes!
+- We do **not** want you to use *LeviLamina* to develop any programs that could compromise Minecraft's security, or any other products that we consider to be a threat to Minecraft's security.
+
+Include a disclaimer in a prominent position on your products, listings, descriptions, websites/pages, and all other related materials stating: "Not an official *Levimc* product [product/service/event/etc.]. Not approved by *Levimc* and not associated with *Levimc*."
+
+These guidelines are designed to help you understand what we do and do not want to happen when you use our name, brand, and assets. We aim to be as open, honest, and, most importantly, trustworthy as possible. We hope you will treat us the same way.
+
+## Naming Guidelines
+
+You may use our name in connection with your product or service, title, or listing (including on websites, video platforms, or merchandise) if you follow the guidelines in this section.
+
+If you:
+
+- Use the *LeviLamina* name as a secondary name, secondary title, or description because it is necessary to honestly and fairly describe your creations or their purpose.
+- Ensure that the secondary title (which includes the *LeviLamina* name) is not the dominant element or distinctive part of the complete name or title.
+- Do not use any other aspect of our brand or assets as part of any related branding, including as a logo or part of a logo.
+- Do not use our name as keywords or search tags for products that have no relationship with them or that are infringing or counterfeit.
+
+Then you may use the *LeviLamina* name.
+
+## Personal Use
+
+We are very relaxed about things you create for yourself. Pretty much anything goes, so go ahead and have fun—just remember to follow the policies and **avoid any illegal activities (such as writing cheats), infringing on others' rights, or violating the Minecraft EULA.**
+
+If you decide you want to share your creation with the community, you can do so. In fact, we are excited for you and can’t wait to see what you've been creating. When you decide to share your content with the community (whether you plan to make money off it or not), we consider that a commercial activity. When you engage in commercial activities, you must follow the Commercial Use guidelines.
+
+This applies, for example, if you want to set up and run any non-commercial blogs, servers, community forums, fan sites, fan clubs, news groups, events, and gatherings.
+
+## Commercial Use
+
+We are stricter about using our name, brand, and assets for commercial use. While we love that you want to share your creations with the community and understand that you may want to charge others, we do have some limits regarding what we consider acceptable *LeviLamina* commercial use.
+
+In these guidelines, commercial use means any use of our name, brand, or assets that you use and share with others (whether or not you receive payment or provide it for free). This can be through videos and streams you create, merchandise you make, or even a server you host. We may change this definition over time, but for now, we hope this helps you understand what you can and can’t do, helping you and other fans like you create more while limiting those who try to overstep.
+
+Currently, we follow the commercial use guidelines of Minecraft’s usage rules.
+
+## Summary
+
+We love the idea of you, as part of our community, doing cool things, and we want to support your creativity. We understand that you may want to share your creations with others and even make money off what you've created. We want to empower you to create and share. At the same time, we want to limit those who would take things too far. Hopefully, these guidelines help you understand what we are okay with you doing and what we aren’t.
+
+If something isn’t covered by these guidelines and we haven’t explicitly said it’s okay, it probably means we don’t want you to do it. In any case, if it isn’t covered, please don’t do it without getting written permission from us. If something is specifically covered and permitted by these guidelines, our account terms, or the [End User License Agreement](../../../EULA.en.md) applicable to any of our games, then you don’t need to contact us.
+
+## Historical Change Information
+
+- 2025.01.04 Completed the first official version. @Lovelylavender4
+- 2024.12.11 Corrected the name of the first draft file. @Lovelylavender4
+- 2024.11.03 Completed the first draft. @Lovelylavender4
diff --git a/docs/main/common_guides/usage_guidelines.zh.md b/docs/main/common_guides/usage_guidelines.zh.md
new file mode 100644
index 0000000000..e64967e6ac
--- /dev/null
+++ b/docs/main/common_guides/usage_guidelines.zh.md
@@ -0,0 +1,89 @@
+# LeviLamina 使用指南
+
+## 目的
+
+我们非常喜欢用户和粉丝们做出酷炫的事情并与社区分享。我们的目标是创造一个环境,让您能为 *LeviLamina* 社区做出伟大的贡献,同时确保其他人不会破坏这一环境。我们意识到,您创建和分享的内容中,一部分是分享给社区的,另一部分可能是您创建并销售的内容。因此,我们制定了这些指南,来解释如何在您的创作中使用 *LeviLamina* 的名称、品牌和资产。
+
+简而言之,我们希望这些指南能帮助您理解可以做什么和不能做什么,帮助您创造并分享更多内容,同时限制那些越界的行为。
+
+### 重要法律协议
+
+[LeviLamina 最终用户许可协议](../../../EULA.zh.md)(EULA)是您与我们([*Levimc*](https://github.com/LiteLDev))之间的法律协议。EULA 涉及您对 *LeviLamina* 产品的使用,并规范了您如何使用我们的网站以及我们如何解决争议。您有责任理解并遵守 EULA。
+
+本指南应与 EULA 一起遵守,但并不构成 EULA 的一部分。它们存在的原因是,我们目前认为,在使用 *LeviLamina* 时,允许一些灵活的使用方式是一个不错的主意。
+
+## 所有用途
+
+对于所有用途(无论是否允许):
+
+- *LeviLamina* 名称、品牌、资产及其任何衍生物的所有权利(包括版权、商标权和相关权利)均归 *Levimc* 所有,且将始终保持所有权。
+- 所有权限和同意授予将由我们酌情决定,并且在我们认为适当时可以随时撤销,或者如果我们不喜欢您的行为。
+- 所有权利均明确保留。
+如果您有兴趣向我们提出正式的合作建议,请保持此兴趣,因为我们目前**不**考虑与其他个人、组织、公司等进行合作。如果您希望举报违反这些指南的行为,可以通过 [我们的电子邮件](feedback@levimc.org) 向我们发送违规信息。我们**无法**提供关于某个具体项目是否符合这些指南的建议。如果您不确定,您应该咨询律师以获取帮助。
+
+随着我们决定何时增加更多指南,您可以随时提出请求。
+
+## 基本指南
+
+我们希望明确这些是适用于您**禁止**使用我们的名称、品牌和资产的基本指南。您仍然应该完整阅读所有政策和指南,但这里有一个简要的总结,帮助您理解关键点。
+
+如果您使用了任何名称、品牌或资产的部分:
+
+**不要**从事任何可能使人误以为您分享的内容可以被解释为官方、批准、支持、与我们相关或由我们支持的活动或包含此类内容。
+
+- **不要**从事任何非法、误导、猥亵、有害或令人反感的活动。
+- **不要**从事任何可能损害或削弱我们名称、品牌或资产的活动(例如:赌博、色情、暴力、恐怖主义或其他不安全/成人内容)。
+- **不要**重新分发我们的产品或任何修改过的产品或产品文档。
+- **不要**允许不公平或不合理的访问我们创建的任何内容。
+- **不要**冒充 *Levimc* 或显得与 *Levimc* 有关联或得到 *Levimc* 支持,并明确声明:
+ - 您(而非我们)对产品或服务(包括事件、服务器、书籍和手工物品)负责。
+ - 谁是出版商、制造商、卖家、组织者和/或所有者。
+ - 产品、服务或任何相关购买的联系方式和联系信息(聊天和论坛链接不可接受)。
+- 尝试获取违反 [Minecraft EULA](https://www.minecraft.net/en-us/eula) 的数据或可能危害 [Minecraft](https://www.minecraft.net/) 安全的行为,例如对 [*LeviLamina*](https://github.com/LiteLDev/LeviLamina) 或我们其他产品的 dump(详见 "**定义**" 部分的第一个条目)是不明智的!
+- **不要**出于恶意目的进行 *LeviLamina* 的反向工程!
+- 我们**不**希望您使用 *LeviLamina* 开发任何可能危害 Minecraft 安全的程序,或任何我们认为会危害 Minecraft 安全的其他产品。
+
+在您的产品、列表、描述、网站/页面及所有其他相关材料中,显著位置标明免责声明:“非官方 *Levimc* 产品 [产品/服务/事件等]。未获得 *Levimc* 批准,与 *Levimc* 无关联。”
+
+这些指南旨在帮助您理解我们希望您在使用我们的名称、品牌和资产时不会发生什么。我们力求尽可能公开、诚实,最重要的是值得信任。我们希望您能以同样的方式对待我们。
+
+## 命名指南
+
+如果您遵守以下本节中的指南,您可以在与您的产品或服务、标题或列表(包括在网站、视频平台或商品上)相关的地方使用我们的名称。
+
+如果您:
+
+- 使用 *LeviLamina* 名称作为副标题、辅助标题或描述,因为它确实有助于真实、公正地描述您的创作或其目的。
+- 确保副标题(包含 *LeviLamina* 名称)不是完整名称或标题的主导元素或显著部分。
+- 不使用我们品牌或资产的其他任何方面作为相关品牌的一部分,包括作为徽标或徽标的一部分。
+- 不使用我们的名称作为与之无关的产品的关键词或搜索标签,或者作为侵权或仿冒产品的标签。
+
+那么,您可以使用 *LeviLamina* 名称。
+
+## 个人使用
+
+对于您为自己创建的内容,我们非常宽松。几乎所有内容都可以,因此请尽情创作并享受其中,只要记得遵守政策,并**避免任何非法活动(例如编写作弊程序)、侵犯他人权利或违反 Minecraft EULA**。
+
+如果您决定与社区分享您的创作,您可以这么做。事实上,我们为您感到兴奋,迫不及待地想看到您的创作。当您决定与社区分享您的内容时(无论您是否计划通过此内容赚钱),我们认为这属于商业行为。当您从事商业活动时,必须遵循商业使用指南。
+
+例如,如果您想设置并运行非商业博客、服务器、社区论坛、粉丝网站、粉丝俱乐部、新闻组、活动和聚会,这些都适用。
+
+## 商业使用
+
+对于使用我们的名称、品牌和资产进行商业用途,我们的态度相对严格。虽然我们很高兴您想与社区分享您的创作,并理解您可能希望通过它向他人收费,但我们对于 *LeviLamina* 商业使用有一些限制。
+
+在这些指南中,商业使用指的是您使用并与他人分享我们的名称、品牌或资产的任何行为(无论您是否收费或免费提供)。这可以通过您创建的视频和直播、制作的商品,甚至是您托管的服务器来实现。我们可能会随着时间的推移改变这一定义,但目前,这应有助于您理解什么是可以做的,什么是不可以做的,帮助您和其他粉丝像您一样创造更多内容,同时限制那些过于越界的行为。
+
+目前我们沿用Minecraft的使用守则的商业使用规范
+
+## 总结
+
+我们非常喜欢您,作为我们社区的一员,做出酷炫的事情,并希望支持您的创造力。我们理解您可能希望与他人分享创作,甚至通过这些创作赚钱。我们希望能够赋予您创作并分享的能力。同时,我们也希望限制那些会过度的行为。希望这些指南能帮助您理解我们同意您做的事情以及我们不同意的事情。
+
+如果某些内容没有被这些指南涵盖,并且我们没有明确表示允许,那很可能意味着我们不希望您这样做。在任何情况下,如果没有被涵盖,请不要在未经我们书面许可的情况下做出此类行为。如果某些内容被这些指南、我们的账户条款或适用于任何我们游戏的 [最终用户许可协议](../../../EULA.zh.md) 明确允许,那么您无需与我们联系。
+
+## 历史变更信息
+
+- 2025.01.04 完成了第一正式版 @Lovelylavender4
+- 2024.12.11 更正了首个草稿文件的名称。@Lovelylavender4
+- 2024.11.03 完成了首个草稿。@Lovelylavender4
\ No newline at end of file
diff --git a/docs/main/developer_guides/create_your_first_mod.md b/docs/main/developer_guides/create_your_first_mod.md
new file mode 100644
index 0000000000..433a2b3f8f
--- /dev/null
+++ b/docs/main/developer_guides/create_your_first_mod.md
@@ -0,0 +1,688 @@
+# Create your first mod
+
+## Introduction
+
+This tutorial is designed to help you get started with mod development in LeviLamina. It is by no means a complete tutorial of all the possibilities in LeviLamina, but rather an overview of the basics. First, make sure you understand C++, set up your workspace in the IDE, and then introduce the basics of most LeviLamina mods.
+
+In this tutorial, we will create a simple mod that implements the following features:
+
+- Players can enter the `/suicide` command to suicide
+- Players are given a clock when they first log in to the server
+- When players use the clock, a confirmation window pops up asking if they want to suicide, and if they confirm, they suicide
+
+This tutorial covers the following topics:
+
+- Logging output
+- Subscribing and unsubscribing events
+- Registering commands
+- Reading configuration files
+- Database access
+- Using forms
+- Constructing Minecraft objects
+- Calling Minecraft functions
+
+!!! info
+ All the source code for this tutorial can be found at [futrime/better-suicide](https://github.com/futrime/better-suicide). We recommend that you look at the source code while reading the tutorial. If you have installed [lip](https://lip.futrime.com), you can also run the following code to install the mod implemented in this tutorial in the LeviLamina instance environment.
+
+ ```shell
+ lip install github.com/futrime/better-suicide
+ ```
+
+## Learn C++
+
+These tutorials require basic knowledge of the C++ programming language. If you are just starting out with C++ or need a refresher, here is a non-exhaustive list.
+
+- [C++ Developer Roadmap](https://roadmap.sh/cpp)
+- [cppreference.com](https://en.cppreference.com/w/)
+- [C++ Tutorial](https://www.w3schools.com/cpp/)
+- [C++ Language Tutorial](https://cplusplus.com/doc/tutorial/)
+- [hacking C++](https://hackingcpp.com/)
+- [C++ Core Guidelines](http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines)
+
+## Set up your workspace
+
+Before developing mods (or learning C++), you need to set up a development environment. This includes but is not limited to the following:
+
+- [xmake](https://xmake.io)
+- [Visual Studio Code](https://code.visualstudio.com)
+- [Git](https://git-scm.com)
+- [Visual Studio 2022](https://visualstudio.microsoft.com/) (When installing Visual Studio 2022, make sure to check the C++ desktop application development option)
+
+!!! warning
+ If you are not using the latest version of Visual Studio 2022, MSVC, and Windows SDK, you may encounter problems in building, loading, and running mods later. If you encounter problems like `xxx is not a member of std`, please consider this possibility. The environment for testing the build of this tutorial is Visual Studio Community 2022 17.8.1, MSVC v143 - VS 2022 C++ x64/x86 build tools (v14.38-17.8), Windows 11 SDK (10.0.22000.0)
+
+!!! tip
+ Because the LeviLamina project is huge, if you use Visual Studio Code, its built-in Intellisense system may be overwhelmed. We recommend that you install the clangd plugin and use clangd for code checking and so on. After installing clangd and the corresponding plugin, you need to run the following command to generate `compile_commands.json`, and then restart VSCode to make clangd effective.
+
+ ```shell
+ xmake project -k compile_commands
+ ```
+
+Then, you need to install LeviLamina somewhere. This tutorial is for LeviLamina 0.9.2, and some modifications may be required for other versions.
+
+## Create a mod repository
+
+Visit [levilamina-mod-template](https://github.com/LiteLDev/levilamina-mod-template) and click `Use this template` to use this template to initialize your mod repository.
+
+![Create from template](levilamina-mod-template.png)
+
+Clone the mod repository to your local machine using Git, and then open it with VSCode. You need to modify some of the files to fill in your mod information.
+
+First, you need to modify the mod name information in `xmake.lua`. Modify the mod name to specify the name of your mod, which will be displayed in LeviLamina. The name allows uppercase and lowercase English letters, numbers, and hyphens, and does not allow spaces and other special characters. It is recommended to use `example-mod` or `ExampleMod` these two forms. Here, our mod is named `better-suicide`.
+
+```lua
+target("better-suicide") -- Change this to your mod name.
+```
+
+Next, modify the contents of `tooth.json`. `tooth.json` provides relevant information for lip to install mod packages. After proper configuration, your mod will be included in the [Bedrinth](https://bedrinth.com) and can be downloaded and installed by users around the world. Change the value of the `tooth` field to the GitHub repository address of this mod, fill in the information fields in `info`, and then fill in the `asset_url` field according to the release address of the repository, modify the dependent LeviLamina version, and modify the `place` of `src` and `dest` according to the mod name filled in `xmake.lua`. For the mod in this article, the following is a feasible reference:
+
+```json
+{
+ "format_version": 2,
+ "tooth": "github.com/futrime/better-suicide",
+ "version": "0.6.0",
+ "info": {
+ "name": "better-suicide",
+ "description": "Allow players to suicide in Minecraft.",
+ "author": "futrime",
+ "tags": [
+ "levilamina",
+ "mod"
+ ]
+ },
+ "asset_url": "https://github.com/futrime/better-suicide/releases/download/v0.6.0/better-suicide-windows-x64.zip",
+ "prerequisites": {
+ "github.com/LiteLDev/LeviLamina": "0.9.x"
+ },
+ "files": {
+ "place": [
+ {
+ "src": "better-suicide/*",
+ "dest": "plugins/better-suicide/"
+ }
+ ]
+ }
+}
+
+```
+
+Then, you need to modify the copyright information in the `LICENSE` file. You can choose an open source license that suits your mod [here](https://choosealicense.com/licenses/). Rest assured, your mod does not need to be open source, because the mod template uses the CC0 license, you can freely modify or delete the `LICENSE` file. However, we recommend that you use an open source license, because this way you can make it easier for others to use your mod and help you improve your mod.
+
+Next, you need to modify the contents of the `README.md` file. This file will be displayed on the main page of your mod repository, where you can introduce the features, usage, configuration files, commands, etc. of your mod.
+
+Finally, you need to change the namespace name. Change the namespace `my_mod` in `MyMod.cpp` and `MyMod.h` to the name you want. Following common C++ conventions, namespace names should use lowercase letters and underscores, and should be consistent. Here, we uniformly change it to `better_suicide`. Similarly, you can change `MyMod.cpp` and `MyMod.h` to the names you want, but at the same time remember to change `#include MyMod.h` in the source file to the new header file name.
+
+## Build your mod
+
+Before we start, let's try to build an empty mod.
+
+First, update the repository:
+
+```shell
+xmake repo -u
+```
+
+Configure the build:
+
+```shell
+xmake f -m debug
+```
+
+!!! tip
+ If you want to build in other modes, you can also use `-m release` or `-m releasedbg`. These two modes will enable the `fastest` optimization level. Among them, `-m release` will turn off debugging information, while `-m releasedbg` will turn on debugging information, just like `-m debug`. For their specific differences, please refer to [Custom Rules - xmake](https://xmake.io/#/en/manual/custom_rule).
+
+!!! failure
+ If you encounter a download failure during the repository update or build configuration process, you may need to [configure GitHub mirror proxy](https://xmake.io/#/en/package/remote_package?id=mirror-proxy):
+
+ ```shell
+ xmake g --proxy_pac=github_mirror.lua
+ ```
+
+ Or [configure HTTP proxy](https://xmake.io/#/en/package/remote_package?id=set-proxy):
+
+
+Then build:
+
+```shell
+xmake
+```
+
+!!! failure
+ Build failed? Try upgrading Visual Studio 2022, MSVC, and Windows SDK. Remember, be sure to upgrade to the latest version.
+
+## Add `#include`
+
+Add `#include` in `MyMod.cpp`, the final effect looks like this:
+
+```cpp
+#include "MyMod.h"
+
+#include "Config.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+```
+
+## Register command `/suicide`
+
+In BDS, commands are not registered from the beginning, but need to be registered after a specific program is executed. Therefore, you cannot register mods when they are loaded, but only when they are enabled. Generally speaking, you should also unregister commands when the mod is disabled to prevent undefined behavior.
+
+!!! warning
+ The mod will call its constructor when it is loaded. But please do not put event subscription, command registration, and any other game-related operations in the constructor, because these operations need to be performed after the game is loaded. If you do these operations in the constructor, your mod will most likely crash when loading.
+
+!!! tip
+ Generally speaking, the mod's constructor only needs to perform some game-independent initialization operations, such as initializing the logging system, initializing the configuration file, initializing the database, etc.
+
+```cpp
+bool enable() {
+
+ // ...
+
+ // Register commands.
+ auto commandRegistry = ll::service::getCommandRegistry();
+ if (!commandRegistry) {
+ throw std::runtime_error("failed to get command registry");
+ }
+
+ auto& command = ll::command::CommandRegistrar::getInstance()
+ .getOrCreateCommand("suicide", "Commits suicide.", CommandPermissionLevel::Any);
+ command.overload().execute<[](CommandOrigin const& origin, CommandOutput& output) {
+ auto* entity = origin.getEntity();
+ if (entity == nullptr || !entity->isType(ActorType::Player)) {
+ output.error("Only players can commit suicide");
+ return;
+ }
+
+ auto* player = static_cast(entity); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast)
+ player->kill();
+
+ getSelf().getLogger().info("{} killed themselves", player->getRealName());
+ }>();
+
+ // ...
+
+ return true;
+}
+```
+
+Let's break down these codes and see. The following statement gets the command registry. The command registry only takes effect after a specific time, so its type is `optional_ref`. We need to determine whether the command registry obtained is valid.
+
+```cpp
+auto commandRegistry = ll::service::getCommandRegistry();
+if (!commandRegistry) {
+ throw std::runtime_error("failed to get command registry");
+}
+```
+
+LeviLamina's command system supports using the `CommandRegistrar::getOrCreateCommand()` function to directly register or get commands.
+
+```cpp
+auto& command = ll::command::CommandRegistrar::getInstance()
+ .getOrCreateCommand("suicide", "Commits suicide.", CommandPermissionLevel::Any);
+```
+
+The first parameter is the command itself, which is the character entered in the console or chat bar. Although it has not been tested whether various special characters can work, we still recommend that only lowercase English letters be included. The second parameter is the command introduction. When entering part of the command in the chat bar, the candidate command and its introduction will be displayed in a semi-transparent gray form above. The third parameter is the command's permission level, which is defined as follows. Among them, if we want ordinary players in survival mode to be able to execute, we should choose `Any`. And `GameDirectors` corresponds to the permission of players who are at least in creative mode, `Admin` corresponds to the permission of at least OP, and `Host` corresponds to the console's permission.
+
+```cpp
+enum class CommandPermissionLevel : schar {
+ Any = 0x0,
+ GameDirectors = 0x1,
+ Admin = 0x2,
+ Host = 0x3,
+ Owner = 0x4,
+ Internal = 0x5,
+};
+```
+
+Then, we need to add an overload to the command and set the corresponding callback.
+
+```cpp
+command.overload().execute<[](CommandOrigin const& origin, CommandOutput& output) {
+ // ...
+}>();
+```
+
+!!! note
+ The command overload means a mode of the command, such as `ll ` is an overload, and `ll list` is another overload. Here is an example, from LeviLamina's mod management command:
+
+```cpp
+enum LeviCommandOperation : int {
+ unload,
+ reload,
+ reactivate,
+};
+struct LeviCommand {
+ LeviCommandOperation operation;
+ SoftEnum mod;
+};
+
+void registerModManageCommand() {
+cmd.alias("ll");
+ cmd.overload().text("load").required("mod").execute(
+ [](CommandOrigin const&, CommandOutput& output, LeviCommand3 const& param) {
+ // ...
+ }
+ ); // ll load
+ cmd.overload()
+ .required("operation")
+ .required("mod")
+ .execute([](CommandOrigin const&, CommandOutput& output, LeviCommand const& param) {
+ // ...
+ }); // ll
+ cmd.overload().text("list").execute([](CommandOrigin const&, CommandOutput& output) {
+ // ...
+ }); // ll list
+}
+```
+
+In the callback function, we first try to get the source of the command execution. Here, we need to make a judgment, because the console, command block and even various entities can execute commands, but the suicide mod should only respond to the player's request. If the wrong source of execution executes the suicide command, an error message should be prompted.
+
+```cpp
+auto* entity = origin.getEntity();
+if (entity == nullptr || !entity->isType(ActorType::Player)) {
+ output.error("Only players can commit suicide");
+ return;
+}
+```
+
+After we confirm that the source of execution is the player, we can convert the entity pointer to the player pointer and kill it.
+
+```cpp
+auto* player = static_cast(entity);
+player->kill();
+
+getInstance().getLogger().info("{} killed themselves", player->getRealName());
+```
+
+!!! warning
+ Because BDS lacks RTTI information, `dynamic_cast()` cannot be used.
+
+!!! tip
+ You may have noticed another function `player->getName()`, but we did not use it. This is because the player's name can be modified by mods or other means, while the result of `player->getRealName()` is (generally speaking) fixed.
+
+At this point, the command object has been configured, and the command object will be loaded into the game after the server starts.
+
+At the end of the `enable()` function, return a `true` to indicate that the mod is successfully enabled. If `false` is returned in the `enable()` function, LeviLamina will think that the mod is enabled to fail and prompt an error message on the console.
+
+## Read the configuration file
+
+The second feature of our mod is to give a clock when the player first enters the server; the third feature is to pop up a confirmation of suicide when using the clock, and the player can suicide after confirmation. But there is a small problem with these two features: the server administrator may have installed other mods that implement similar functions, and do not want to use these functions in this suicide mod. We hope to provide some way to allow administrators to turn these two functions on and off.
+
+We are very pleased to announce that LeviLamina has implemented the reflection of configuration files and configuration information structures in C++. This means that we can define a structure in C++, and then define an instance of this structure in the configuration file, and LeviLamina will automatically read the contents of the configuration file into the structure instance. In this way, we can directly use this structure instance in C++, without having to parse the configuration file ourselves.
+
+First, we create another `Config.h` file, define a structure `Config`, and use it to store configuration information.
+
+```cpp
+struct Config {
+ int version = 1;
+ bool doGiveClockOnFirstJoin = true;
+ bool enableClockMenu = true;
+};
+```
+
+We add a member variable in the anonymous namespace to store the configuration information in the configuration file.
+
+```cpp
+namespace {
+
+// ...
+
+Config config;
+
+}
+```
+
+Then, we read the configuration file and save the configuration information to the member variable.
+
+```cpp
+bool load() {
+
+ // ...
+
+ // Load or initialize configurations.
+ const auto& configFilePath = self.getConfigDir() / "config.json";
+ if (!ll::config::loadConfig(config, configFilePath)) {
+ logger.warn("Cannot load configurations from {}", configFilePath);
+ logger.info("Saving default configurations");
+
+ if (!ll::config::saveConfig(config, configFilePath)) {
+ logger.error("Cannot save default configurations to {}", configFilePath);
+ }
+ }
+
+ // ...
+
+}
+```
+
+In this code, we first get the mod's configuration file path, and then call the `ll::config::loadConfig()` function to read the configuration information in the configuration file into the structure instance. If the read fails, we will output a warning message on the console and save the default configuration information to the configuration file.
+
+!!! note
+ Since the configuration file is read in the constructor, it can be guaranteed that the configuration file has been read successfully in subsequent operations.
+
+## Persistently save player entry information in the database
+
+The second feature of our mod is to give a clock when the player first enters the server. However, if we save the entry information in memory, the player's entry information will be lost when the server restarts. Therefore, we need to persistently save the player's entry information in the database. LeviLamina provides a KV database wrapper that allows us to use the database directly in C++.
+
+First, we add a member variable in the anonymous namespace to store the database instance.
+
+```cpp
+std::unique_ptr playerDb;
+```
+
+!!! note
+ Why is it `std::unique_ptr` instead of `ll::KeyValueDB`? This is because `ll::KeyValueDB` prohibits copying and can only be moved. Therefore, we need to use `std::unique_ptr` to store the `ll::KeyValueDB` instance.
+
+!!! warning
+ Please do not use ordinary pointers to store the `ll::KeyValueDB` instance, because this can easily make the life cycle management complicated, resulting in memory leaks and other problems. Remember: you are writing C++, not C.
+
+Then, in the `load` function, we initialize the database instance.
+
+```cpp
+bool load() {
+
+ // ...
+
+ // Initialize databases;
+ const auto& playerDbPath = self.getDataDir() / "players";
+ playerDb = std::make_unique(playerDbPath);
+
+ // ...
+}
+```
+
+In this code, we first get the mod's database path, and then call the `std::make_unique()` function to create a database instance. If the database path does not exist, the `std::make_unique()` function will automatically create the database path.
+
+!!! note
+ Since the database initialization is done in the constructor, it can be guaranteed that the database has been initialized successfully in subsequent operations.
+
+# Give a clock when the player first enters the server
+
+The second feature of our mod is to give a clock when the player first enters the server. We need to judge whether the player is entering the server for the first time when the player enters the server, and if so, give a clock.
+
+In BDS, when the player enters the server, the event `PlayerJoinEvent` is triggered. In LeviLamina, we can subscribe to this event, and when this event is triggered, the mod can implement the logic when the player enters the server.
+
+In the anonymous namespace, we add an event listener pointer:
+
+```cpp
+ll::event::ListenerPtr playerJoinEventListener;
+```
+
+In the `enable()` function, we register this event listener, and in the `disable()` function, we unregister it.
+
+```cpp
+bool enable() {
+
+ // ...
+
+ auto& eventBus = ll::event::EventBus::getInstance();
+
+ playerJoinEventListener = eventBus.emplaceListener(
+ [doGiveClockOnFirstJoin = config.doGiveClockOnFirstJoin,
+ &logger,
+ &playerDb = playerDb](ll::event::player::PlayerJoinEvent& event) {
+ if (doGiveClockOnFirstJoin) {
+ auto& player = event.self();
+
+ const auto& uuid = player.getUuid();
+
+ // Check if the player has joined before.
+ if (!playerDb->get(uuid.asString())) {
+
+ ItemStack itemStack("clock", 1);
+ player.add(itemStack);
+
+ // Must refresh inventory to see the clock.
+ player.refreshInventory();
+
+ // Mark the player as joined.
+ if (!playerDb->set(uuid.asString(), "true")) {
+ logger.error("Cannot mark {} as joined in database", player.getRealName());
+ }
+
+ logger.info("First join of {}! Giving them a clock", player.getRealName());
+ }
+ }
+ }
+ );
+
+ // ...
+
+}
+
+bool disable() {
+
+ // ...
+
+ auto& eventBus = ll::event::EventBus::getInstance();
+
+ eventBus.removeListener(playerJoinEventListener);
+
+ // ...
+
+}
+```
+
+Let's break down these codes and see. In the callback lambda function, we capture the `doGiveClockOnFirstJoin` in the configuration, as well as the mod's logger and database instance. Then, we judge whether the `doGiveClockOnFirstJoin` in the configuration is `true`, and if so, continue to execute the logic.
+
+```cpp
+[doGiveClockOnFirstJoin = config.doGiveClockOnFirstJoin,
+ &logger,
+ &playerDb = playerDb](ll::event::player::PlayerJoinEvent& event) {
+ if (doGiveClockOnFirstJoin) {
+ // ...
+ }
+}
+```
+
+Next, we get the player instance and the player's UUID from the event instance.
+
+```cpp
+auto& player = event.self();
+auto& uuid = player.getUuid();
+```
+
+!!! note
+ The type of UUID obtained here is `mce::UUID` instead of `std::string`. We recommend that you only convert UUID to `std::string` when needed, because `mce::UUID` is more efficient.
+
+!!! danger
+ Please do not use XUID as the player's unique identifier. Although in the LiteLoaderBDS era, many mods used XUID as the player's unique identifier, this is incorrect. XUID is the identifier of Xbox Live, not the player. If the server does not enable online mode, or there are fake players, the behavior of XUID will be unpredictable. Therefore, we strongly recommend using UUID as the player's unique identifier.
+
+Then, we use the player's UUID as the key to get whether the player has entered the server before from the database. If the player has entered the server before, then we don't need to give the player a clock again.
+
+```cpp
+// Check if the player has joined before.
+if (!playerDb->get(uuid.asString())) {
+ // ...
+}
+```
+
+Next, we create a clock item stack and add it to the player's backpack.
+
+```cpp
+ItemStack itemStack("clock", 1);
+player.add(itemStack);
+```
+
+!!! note
+ Here we use the `ItemStack` class instead of the `Item` class. The `ItemStack` class is a wrapper of the `Item` class, which contains the quantity, enchantment, durability and other information of the item, while the `Item` class only represents the item category. Therefore, the `ItemStack` class should be used instead of the `Item` class.
+
+Then, we need to refresh the player's inventory so that the player can see the clock.
+
+```cpp
+player.refreshInventory();
+```
+
+Finally, we use the player's UUID as the key to mark the player as having entered the server.
+
+```cpp
+// Mark the player as joined.
+if (!playerDb->set(uuid.asString(), "true")) {
+ logger.error("Cannot mark {} as joined in database", player.getRealName());
+}
+```
+
+In the `disable()` function, we need to remove the event listener from the event bus to unsubscribe from the event.
+
+```cpp
+eventBus.removeListener(playerJoinEventListener);
+```
+
+## Pop up a confirmation of suicide when using the clock
+
+The third feature of our mod is to pop up a confirmation of suicide when using the clock, and the player can suicide after confirmation. We need to subscribe to the player's use of items event, and when the player uses the clock, pop up a confirmation of suicide.
+
+In the anonymous namespace, we add an event listener pointer:
+
+```cpp
+ll::event::ListenerPtr playerUseItemEventListener;
+```
+
+In the `enable()` function, we register this event listener, and in the `disable()` function, we unregister it.
+
+```cpp
+bool enable() {
+
+ // ...
+
+ playerUseItemEventListener =
+ eventBus.emplaceListener([enableClockMenu = config.enableClockMenu,
+ &logger](ll::event::PlayerUseItemEvent& event) {
+ if (enableClockMenu) {
+ auto& player = event.self();
+ auto& itemStack = event.item();
+
+ if (itemStack.getRawNameId() == "clock") {
+ ll::form::ModalForm form(
+ "Warning",
+ "Are you sure you want to kill yourself?",
+ "Yes",
+ "No",
+ [&logger](Player& player, bool yes) {
+ if (yes) {
+ player.kill();
+
+ logger.info("{} killed themselves", player.getRealName());
+ }
+ }
+ );
+
+ form.sendTo(player);
+ }
+ }
+ });
+
+ // ...
+
+}
+
+bool disable() {
+
+ // ...
+
+ eventBus.removeListener(playerUseItemEventListener);
+
+ // ...
+
+}
+```
+
+Let's break down the code and see. In the callback lambda function, we capture the configuration item `enableClockMenu` and logger, and then judge, only when the configuration item is enabled, execute the logic.
+
+```cpp
+playerUseItemEventListener = eventBus.emplaceListener(
+ [enableClockMenu = config.enableClockMenu, &logger](ll::event::PlayerUseItemEvent& event) {
+ if (enableClockMenu) {
+ // ...
+ }
+ }
+);
+```
+
+In the logic, we first get the two attributes of the event, the player who uses the item and the item being used. Then judge whether the item id is `clock`, and execute the pop-up form logic.
+
+```cpp
+auto& player = event.self();
+auto& itemStack = event.item();
+
+if (itemStack.getRawNameId() == "clock") {
+ // ...
+}
+```
+
+!!! warning
+ Do not use `itemStack.getName()`, because this function returns the name of the item displayed, such as `Clock` or `Iron Sword`.
+
+Here we use the simplest modal form `ModalForm`, the first parameter of the constructor is the title of the form, the second parameter is the prompt content of the form, the third parameter is the content of the lower left button, and the fourth parameter is the content of the lower right button. The callback function receives two parameters, the first parameter is the player to whom the form is sent, and the second parameter is the player's choice, `true` means that the lower left button is selected.
+
+```cpp
+ll::form::ModalForm form(
+ "Warning",
+ "Are you sure you want to kill yourself?",
+ "Yes",
+ "No",
+ [&logger](Player& player, bool yes) {
+ if (yes) {
+ player.kill();
+
+ logger.info("{} killed themselves", player.getRealName());
+ }
+ }
+);
+```
+
+Next, send the form to the player.
+
+```cpp
+form.sendTo(player);
+```
+
+## Run your mod
+
+If your mod is built normally, you should be able to see a directory named after your mod in the `bin/` directory. Copy this directory to the `plugins/` directory in the LeviLamina directory (create it if it does not exist), and get the following file structure:
+
+```text
+/path/to/levilamina/plugins/better-suicide
+├── better-suicide.dll
+└── manifest.json
+```
+
+Then run the LeviLamina server (`bedrock_server_mod.exe`) and you're done.
+
+## Next steps?
+
+You can [publicly release your mod](./publish_your_first_mod.md) and let more people use your mod.
+
+## Further exercises
+
+We can add some features to this mod based on this mod to practice more knowledge of LeviLamina mod development. Here are some possible exercises:
+
+- Set the cooldown time for player suicide
+- Keep all items from dropping when the player suicide
+- Keep the experience when the player suicide
+- Suicide at the original place when the player suicide
+- Count the number of player suicides and display the leaderboard on the sidebar
+- Use more advanced forms to let the player choose the way of suicide
+- Show a custom death message when the player suicide
diff --git a/docs/main/developer_guides/create_your_first_mod.zh.md b/docs/main/developer_guides/create_your_first_mod.zh.md
new file mode 100644
index 0000000000..fa9d295a87
--- /dev/null
+++ b/docs/main/developer_guides/create_your_first_mod.zh.md
@@ -0,0 +1,688 @@
+# 创建你的第一个模组
+
+## 简介
+
+这个教程旨在帮助你开始在LeviLamina中进行模组开发。它绝不是LeviLamina中所有可能性的完整教程,而是基础知识的总体概述。首先确保您了解C++,在 IDE中设置工作区,然后介绍大多数LeviLamina模组的基本知识。
+
+在这个教程中,我们将会创建一个简单的模组,用于实现以下功能:
+
+- 玩家可以输入`/suicide`指令自杀
+- 玩家首次登录服务器时给予一个钟
+- 玩家使用钟时,弹出确认窗口询问是否自杀,如果确认则自杀
+
+这个教程包含以下知识点:
+
+- 日志输出
+- 订阅和退订事件
+- 注册指令
+- 读取配置文件
+- 数据库存取
+- 使用表单
+- 构造Minecraft对象
+- 调用Minecraft函数
+
+!!! info
+ 本教程的所有源码可以在[futrime/better-suicide](https://github.com/futrime/better-suicide)找到。我们建议你一边看源码一边看教程。如果你已经安装了[lip](https://lip.futrime.com),你还可以直接运行以下代码在LeviLamina实例环境中安装本教程中实现的模组。
+
+ ```shell
+ lip install github.com/futrime/better-suicide
+ ```
+
+## 学习C++
+
+这些教程需要C++编程语言的基础知识。如果您刚刚开始使用C++或需要复习一下,以下是一个非详尽的列表。
+
+- [C++ Developer Roadmap](https://roadmap.sh/cpp)
+- [cppreference.com](https://en.cppreference.com/w/)
+- [C++ Tutorial](https://www.w3schools.com/cpp/)
+- [C++ Language Tutorial](https://cplusplus.com/doc/tutorial/)
+- [hacking C++](https://hackingcpp.com/)
+- [C++ Core Guidelines](http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines)
+
+## 设置工作区
+
+在开发模组(或学习C++)之前,您需要设置一个开发环境。这包括但不限于以下内容:
+
+- [xmake](https://xmake.io)
+- [Visual Studio Code](https://code.visualstudio.com)
+- [Git](https://git-scm.com)
+- [Visual Studio 2022](https://visualstudio.microsoft.com/) (安装Visual Studio 2022时,请确保勾选了C++桌面应用开发这一项)
+
+!!! warning
+ 如果你安装的不是最新版本的Visual Studio 2022、MSVC和Windows SDK,则后续在构建、加载、运行模组中有可能遇到问题。如果你遇到了类似`xxx is not a member of std`这样的问题,请考虑这个可能性。本教程测试构建的环境是Visual Studio Community 2022 17.8.1、MSVC v143 - VS 2022 C++ x64/x86 build tools (v14.38-17.8)、Windows 11 SDK (10.0.22000.0)
+
+!!! tip
+ 由于LeviLamina项目极大,如果你使用Visual Studio Code,其自带的Intellisense系统可能不堪重负。我们建议你安装clangd插件并使用clangd进行代码检查等。安装clangd和对应的插件后,你需要运行以下命令生成`compile_commands.json`,然后重启VSCode以使clangd生效。
+
+ ```shell
+ xmake project -k compile_commands
+ ```
+
+然后,你需要在某处安装LeviLamina。本教程针对的是LeviLamina 0.6.3,对于其它版本,可能需要做一些修改。
+
+## 创建模组仓库
+
+访问[levilamina-mod-template](https://github.com/LiteLDev/levilamina-mod-template),点击`Use this template`以使用这个模板初始化你的模组仓库。
+
+![Create from template](levilamina-mod-template.png)
+
+将模组仓库使用Git克隆到本地,然后使用VSCode打开。你需要修改其中的一些文件,填写你的模组信息。
+
+首先,你需要修改`xmake.lua`中模组名字信息。修改模组名字是为了指定你的模组的名字,这个名字将会在LeviLamina中显示。名字允许英文大小写、数字、中划线,不允许包括空格和其他特殊字符,建议采用`example-mod`或`ExampleMod`这两种形式。在这里,我们的模组命名为`better-suicide`。
+
+```lua
+target("better-suicide") -- Change this to your mod name.
+```
+
+接着,修改`tooth.json`的内容。`tooth.json`为lip安装模组包提供了相关信息,正确配置后,你的模组将会被[Bedrinth](https://bedrinth.com)收录,并能被全世界的用户下载安装。将`tooth`字段的值改为这个模组的GitHub仓库地址,填写`info`中各个信息字段,然后根据仓库release地址填写`asset_url`字段,修改依赖的LeviLamina版本,并根据在`xmake.lua`中填写的模组名修改`place`的`src`和`dest`。对于本文的模组,以下是一个可行的参考:
+
+```json
+{
+ "format_version": 2,
+ "tooth": "github.com/futrime/better-suicide",
+ "version": "0.6.0",
+ "info": {
+ "name": "better-suicide",
+ "description": "Allow players to suicide in Minecraft.",
+ "author": "futrime",
+ "tags": [
+ "levilamina",
+ "mod"
+ ]
+ },
+ "asset_url": "https://github.com/futrime/better-suicide/releases/download/v0.6.0/better-suicide-windows-x64.zip",
+ "prerequisites": {
+ "github.com/LiteLDev/LeviLamina": "0.9.x"
+ },
+ "files": {
+ "place": [
+ {
+ "src": "better-suicide/*",
+ "dest": "plugins/better-suicide/"
+ }
+ ]
+ }
+}
+
+```
+
+然后,你需要修改`LICENSE`文件中的版权信息。你可以在[这里](https://choosealicense.com/licenses/)选择一个适合你的模组的开源协议。请放心,你的模组不需要开源,因为模组模板使用了CC0协议,你可以随意修改或删除`LICENSE`文件。但是,我们建议你使用一个开源协议,因为这样可以让其他人更容易地使用你的模组和帮助你改进你的模组。
+
+接下来,你需要修改`README.md`文件中的内容。这个文件将会在你的模组仓库主页显示,你可以在这里介绍你的模组的功能、使用方法、配置文件、指令等等。
+
+最后,你需要修改命名空间名。将`MyMod.cpp`和`MyMod.h`中命名空间`my_mod`改成你想要的名字。按照C++常见惯例,命名空间名应当使用小写字母和下划线,且应当保持一致。这里,我们统一改成`better_suicide`。同样,你可以将`MyMod.cpp`和`MyMod.h`改为你想要的名字,但同时要记得把源文件中的`#include MyMod.h`改为新的头文件名。
+
+## 构建你的模组
+
+在一切开始之前,先让我们尝试构建一下空的模组。
+
+先更新一下仓库:
+
+```shell
+xmake repo -u
+```
+
+配置构建:
+
+```shell
+xmake f -m debug
+```
+
+!!! tip
+ 如果你想以其它模式构建,也可以使用`-m release`或`-m releasedbg`。这两个模式会开启`fastest`优化等级。其中,`-m release`会关闭调试信息,而`-m releasedbg`会开启调试信息,就像`-m debug`一样。对于它们的具体区别,请参考[自定义规则 - xmake](https://xmake.io/#/zh-cn/manual/custom_rule)。
+
+!!! failure
+ 如果你在更新仓库或配置构建过程中,出现了下载失败的情况,那么可能需要[配置GitHub镜像代理](https://xmake.io/#/zh-cn/package/remote_package?id=%e9%95%9c%e5%83%8f%e4%bb%a3%e7%90%86):
+
+ ```shell
+ xmake g --proxy_pac=github_mirror.lua
+ ```
+
+ 或者[配置HTTP代理](https://xmake.io/#/zh-cn/package/remote_package?id=%e8%ae%be%e7%bd%ae%e4%bb%a3%e7%90%86):
+
+
+然后构建:
+
+```shell
+xmake
+```
+
+!!! failure
+ 构建失败了?尝试升级一下Visual Studio 2022、MSVC和Windows SDK吧。记住,一定要升级到最新版本。
+
+## 补充`#include`
+
+在`MyMod.cpp`中补充`#include`,最终效果看起来是这样的:
+
+```cpp
+#include "MyPMod.h"
+
+#include "Config.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+```
+
+## 注册指令`/suicide`
+
+在BDS中,指令并不是一开始就能够注册的,而是需要在特定的程序执行之后才能注册。因此,你不能在模组加载时注册模组,而只能在模组启用时注册指令。一般来说,还应当在模组禁用时解注册指令,以防止出现未定义行为。
+
+!!! warning
+ 模组在加载时,会调用其构造函数。但请不要将事件订阅、指令注册等任何与游戏相关的操作放在构造函数中,因为这些操作需要在游戏加载完成后才能进行。如果你在构造函数中进行了这些操作,那么你的模组将很有可能会在加载时崩溃。
+
+!!! tip
+ 一般来说,模组的构造函数中只需要进行一些与游戏无关初始化操作即可,例如初始化日志系统、初始化配置文件、初始化数据库等等。
+
+```cpp
+bool enable() {
+
+ // ...
+
+ // Register commands.
+ auto commandRegistry = ll::service::getCommandRegistry();
+ if (!commandRegistry) {
+ throw std::runtime_error("failed to get command registry");
+ }
+
+ auto& command = ll::command::CommandRegistrar::getInstance()
+ .getOrCreateCommand("suicide", "Commits suicide.", CommandPermissionLevel::Any);
+ command.overload().execute<[](CommandOrigin const& origin, CommandOutput& output) {
+ auto* entity = origin.getEntity();
+ if (entity == nullptr || !entity->isType(ActorType::Player)) {
+ output.error("Only players can commit suicide");
+ return;
+ }
+
+ auto* player = static_cast(entity); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast)
+ player->kill();
+
+ getInstance().getLogger().info("{} killed themselves", player->getRealName());
+ }>();
+
+ // ...
+
+ return true;
+}
+```
+
+让我们将这些代码拆开来看。下列语句获取指令注册表。指令注册表只有在特定时机之后才会生效,因此其类型为`optional_ref`。我们需要判定获取到的指令注册表是否有效。
+
+```cpp
+auto commandRegistry = ll::service::getCommandRegistry();
+if (!commandRegistry) {
+ throw std::runtime_error("failed to get command registry");
+}
+```
+
+LeviLamina的指令系统支持使用`CommandRegistrar::getOrCreateCommand()`函数直接注册或获取指令。
+
+```cpp
+auto& command = ll::command::CommandRegistrar::getInstance()
+ .getOrCreateCommand("suicide", "Commits suicide.", CommandPermissionLevel::Any);
+```
+
+其中,第一个参数是指令本身,即在控制台或聊天栏内输入的字符。虽然尚未测试各种特殊字符能否生效,但我们仍然建议只包含小写英文字母。第二个参数是指令简介,在聊天栏输入指令的一部分时,会在上方以半透明灰色的形式显示候选指令及其简介。第三个参数是指令的权限等级,其定义如下。其中,如果我们希望生存模式下的普通玩家也能执行,应当选择`Any`。而`GameDirectors`对应至少为创造模式的玩家的权限,`Admin`对应至少为OP的权限,`Host`对应控制台的权限。
+
+```cpp
+enum class CommandPermissionLevel : schar {
+ Any = 0x0,
+ GameDirectors = 0x1,
+ Admin = 0x2,
+ Host = 0x3,
+ Owner = 0x4,
+ Internal = 0x5,
+};
+```
+
+然后,我们需要为指令增加一个重载并设置对应的回调。
+
+```cpp
+command.overload().execute<[](CommandOrigin const& origin, CommandOutput& output) {
+ // ...
+}>();
+```
+
+!!! note
+ 指令的重载意味着指令的一个模式,例如`ll ` 是一个重载,而`ll list`是另一个重载。下面是一个例子,来自LeviLamina的模组管理指令:
+
+```cpp
+enum LeviCommandOperation : int {
+ unload,
+ reload,
+ reactivate,
+};
+struct LeviCommand {
+ LeviCommandOperation operation;
+ SoftEnum mod;
+};
+
+void registerModManageCommand() {
+cmd.alias("ll");
+ cmd.overload().text("load").required("mod").execute(
+ [](CommandOrigin const&, CommandOutput& output, LeviCommand3 const& param) {
+ // ...
+ }
+ ); // ll load
+ cmd.overload()
+ .required("operation")
+ .required("mod")
+ .execute([](CommandOrigin const&, CommandOutput& output, LeviCommand const& param) {
+ // ...
+ }); // ll
+ cmd.overload().text("list").execute([](CommandOrigin const&, CommandOutput& output) {
+ // ...
+ }); // ll list
+}
+```
+
+在回调函数中,我们首先尝试获取指令的执行来源。在这里,我们需要进行一个判定,因为控制台、命令方块乃至各种实体都能够执行指令,但自杀模组应当只响应玩家的请求。如果错误的执行来源执行了自杀指令,那么应当提示一个错误信息。
+
+```cpp
+auto* entity = origin.getEntity();
+if (entity == nullptr || !entity->isType(ActorType::Player)) {
+ output.error("Only players can commit suicide");
+ return;
+}
+```
+
+当我们确认了执行来源为玩家后,我们就可以将实体指针转换为玩家指针,并杀死之。
+
+```cpp
+auto* player = static_cast(entity);
+player->kill();
+
+getInstance().getLogger().info("{} killed themselves", player->getRealName());
+```
+
+!!! warning
+ 由于BDS缺乏RTTI信息,因此不能够使用`dynamic_cast()`。
+
+!!! tip
+ 你可能注意到另一个函数`player->getName()`,但我们并没有使用它。这是因为玩家的名字是可以通过模组或其它方式进行修改的,而`player->getRealName()`的结果则是(一般来说较为)固定的。
+
+到这一步,指令对象已经配置完毕,当服务器启动后,指令对象将被加载到游戏中。
+
+在`enable()`函数的末尾,返回一个`true`,代表模组启用成功。如果在`enable()`函数中返回了`false`,则LeviLamina会认为模组启用失败,并在控制台上提示错误信息。
+
+## 读取配置文件
+
+我们的模组的第二个功能是玩家首次进入服务器时,给予一个钟;第三个功能是使用钟的时候,弹出确认自杀的提示,玩家确认后可以自杀。但这两个功能有个小问题:服务器管理员可能已经安装了其它的模组,实现了类似的功能,而不希望使用这个自杀模组中这几个功能。我们希望能提供某种方式,允许管理员开关这两个功能。
+
+我们在此非常高兴地宣布,LeviLamina在C++中,实现了配置文件与配置信息结构体的反射。这意味着,我们可以在C++中定义一个结构体,然后在配置文件中定义这个结构体的实例,LeviLamina会自动将配置文件中的内容读取到结构体实例中。这样,我们就可以在C++中直接使用这个结构体实例,而不需要自己去解析配置文件。
+
+首先,我们另外创建一个`Config.h`文件,定义一个结构体`Config`,用于保存配置信息。
+
+```cpp
+struct Config {
+ int version = 1;
+ bool doGiveClockOnFirstJoin = true;
+ bool enableClockMenu = true;
+};
+```
+
+我们在匿名命名空间中增加一个成员变量,用于保存配置文件中的配置信息。
+
+```cpp
+namespace {
+
+// ...
+
+Config config;
+
+}
+```
+
+然后,我们读取配置文件并将配置信息保存到成员变量中。
+
+```cpp
+bool load() {
+
+ // ...
+
+ // Load or initialize configurations.
+ const auto& configFilePath = self.getConfigDir() / "config.json";
+ if (!ll::config::loadConfig(config, configFilePath)) {
+ logger.warn("Cannot load configurations from {}", configFilePath);
+ logger.info("Saving default configurations");
+
+ if (!ll::config::saveConfig(config, configFilePath)) {
+ logger.error("Cannot save default configurations to {}", configFilePath);
+ }
+ }
+
+ // ...
+
+}
+```
+
+在这段代码中,我们首先获取模组的配置文件路径,然后调用`ll::config::loadConfig()`函数,将配置文件中的配置信息读取到结构体实例中。如果读取失败,我们将会在控制台上输出警告信息,并将默认配置信息保存到配置文件中。
+
+!!! note
+ 由于配置文件读取是在构造函数内进行的,所以在后续操作中可以保证配置文件已经读取成功了。
+
+## 将玩家进服信息持久化保存在数据库中
+
+我们的模组的第二个功能是玩家首次进入服务器时,给予一个钟。但是,如果我们将进服信息保存在内存中,那么当服务器重启后,玩家的进服信息就会丢失。因此,我们需要将玩家的进服信息持久化保存在数据库中。LeviLamina提供了KV数据库的封装,可以让我们在C++中直接使用数据库。
+
+首先,我们在匿名命名空间中增加一个成员变量,用于保存数据库实例。
+
+```cpp
+std::unique_ptr playerDb;
+```
+
+!!! note
+ 为什么是`std::unique_ptr`而不是`ll::KeyValueDB`?这是因为`ll::KeyValueDB`禁止拷贝,只能移动。因此,我们需要使用`std::unique_ptr`来保存`ll::KeyValueDB`的实例。
+
+!!! warning
+ 请不要使用普通的指针来保存`ll::KeyValueDB`的实例,因为这样很容易使得生命周期管理变得复杂,从而导致内存泄漏和其他问题。请记住:你在写C++,而不是C。
+
+然后,我们在`load`函数中,初始化数据库实例。
+
+```cpp
+bool load() {
+
+ // ...
+
+ // Initialize databases;
+ const auto& playerDbPath = self.getDataDir() / "players";
+ playerDb = std::make_unique(playerDbPath);
+
+ // ...
+}
+```
+
+在这段代码中,我们首先获取模组的数据库路径,然后调用`std::make_unique()`函数,创建一个数据库实例。如果数据库路径不存在,那么`std::make_unique()`函数会自动创建数据库路径。
+
+!!! note
+ 由于数据库初始化是在构造函数内进行的,所以在后续操作中可以保证数据库已经初始化成功了。
+
+## 玩家首次进服时,给予一个钟
+
+我们的模组的第二个功能是玩家首次进入服务器时,给予一个钟。我们需要在玩家进服时,判断玩家是否首次进服,如果是,则给予一个钟。
+
+在BDS中,玩家进服时,会触发事件`PlayerJoinEvent`。在LeviLamina中,我们可以订阅这个事件,当这个事件被触发时,模组可以在这里实现玩家进服时的逻辑。
+
+在匿名命名空间中,我们增加一个事件监听器指针:
+
+```cpp
+ll::event::ListenerPtr playerJoinEventListener;
+```
+
+在`enable()`函数中注册这个事件监听器,并在`disable()`函数中取消注册。
+
+```cpp
+bool enable() {
+
+ // ...
+
+ auto& eventBus = ll::event::EventBus::getInstance();
+
+ playerJoinEventListener = eventBus.emplaceListener(
+ [doGiveClockOnFirstJoin = config.doGiveClockOnFirstJoin,
+ &logger,
+ &playerDb = playerDb](ll::event::player::PlayerJoinEvent& event) {
+ if (doGiveClockOnFirstJoin) {
+ auto& player = event.self();
+
+ const auto& uuid = player.getUuid();
+
+ // Check if the player has joined before.
+ if (!playerDb->get(uuid.asString())) {
+
+ ItemStack itemStack("clock", 1);
+ player.add(itemStack);
+
+ // Must refresh inventory to see the clock.
+ player.refreshInventory();
+
+ // Mark the player as joined.
+ if (!playerDb->set(uuid.asString(), "true")) {
+ logger.error("Cannot mark {} as joined in database", player.getRealName());
+ }
+
+ logger.info("First join of {}! Giving them a clock", player.getRealName());
+ }
+ }
+ }
+ );
+
+ // ...
+
+}
+
+bool disable() {
+
+ // ...
+
+ auto& eventBus = ll::event::EventBus::getInstance();
+
+ eventBus.removeListener(playerJoinEventListener);
+
+ // ...
+
+}
+```
+
+让我们将这些代码拆开来看。在回调lambda函数中,我们捕获了配置中的`doGiveClockOnFirstJoin`,以及模组的logger和数据库实例。然后,我们判断配置中的`doGiveClockOnFirstJoin`是否为`true`,如果是,则继续执行逻辑。
+
+```cpp
+[doGiveClockOnFirstJoin = config.doGiveClockOnFirstJoin,
+ &logger,
+ &playerDb = playerDb](ll::event::player::PlayerJoinEvent& event) {
+ if (doGiveClockOnFirstJoin) {
+ // ...
+ }
+}
+```
+
+接下来,我们获取事件实例中的玩家实例和玩家的UUID。
+
+```cpp
+auto& player = event.self();
+auto& uuid = player.getUuid();
+```
+
+!!! note
+ 这里获取的UUID的类型是`mce::UUID`而不是`std::string`。我们建议只有在需要时才将UUID转换为`std::string`,因为`mce::UUID`的实现更加高效。
+
+!!! danger
+ 请不要使用XUID作为玩家的唯一标识符。虽然在LiteLoaderBDS时代,不少模组使用XUID作为玩家的唯一标识符,但这是不正确的。XUID是Xbox Live的标识符,而不是玩家的标识符。如果服务器没有开启在线模式,或者存在假人,那么XUID的行为将是不可预测的。因此,我们强烈建议使用UUID作为玩家的唯一标识符。
+
+然后,我们使用玩家的UUID作为键,从数据库中获取玩家是否已经进服过。如果玩家已经进服过,那么我们就不需要再给予玩家一个钟了。
+
+```cpp
+// Check if the player has joined before.
+if (!playerDb->get(uuid.asString())) {
+ // ...
+}
+```
+
+接下来,我们创建一个钟的物品栈,并将这个物品栈添加到玩家的背包中。
+
+```cpp
+ItemStack itemStack("clock", 1);
+player.add(itemStack);
+```
+
+!!! note
+ 这里使用了`ItemStack`类,而不是`Item`类。`ItemStack`类是`Item`类的一个包装,它包含了物品的数量、附魔、耐久等信息,而`Item`类仅仅代表这个物品类别。因此应当使用`ItemStack`类而不是`Item`类。
+
+然后,我们需要刷新玩家的背包,以便玩家能够看到钟。
+
+```cpp
+player.refreshInventory();
+```
+
+最后,我们将玩家的UUID作为键,将玩家标记为已经进服过。
+
+```cpp
+// Mark the player as joined.
+if (!playerDb->set(uuid.asString(), "true")) {
+ logger.error("Cannot mark {} as joined in database", player.getRealName());
+}
+```
+
+在`disable()`函数中,我们需要在事件总线上移除事件监听器以取消对事件的订阅。
+
+```cpp
+eventBus.removeListener(playerJoinEventListener);
+```
+
+## 使用钟的时候,弹出确认自杀的提示
+
+我们的模组的第三个功能是使用钟的时候,弹出确认自杀的提示,玩家确认后可以自杀。我们需要订阅玩家使用物品的事件,当玩家使用钟时,弹出确认自杀的提示。
+
+在匿名命名空间中,我们增加一个事件监听器指针:
+
+```cpp
+ll::event::ListenerPtr playerUseItemEventListener;
+```
+
+在`enable()`函数中注册这个事件监听器,并在`disable()`函数中取消注册。
+
+```cpp
+bool enable() {
+
+ // ...
+
+ playerUseItemEventListener =
+ eventBus.emplaceListener([enableClockMenu = config.enableClockMenu,
+ &logger](ll::event::PlayerUseItemEvent& event) {
+ if (enableClockMenu) {
+ auto& player = event.self();
+ auto& itemStack = event.item();
+
+ if (itemStack.getRawNameId() == "clock") {
+ ll::form::ModalForm form(
+ "Warning",
+ "Are you sure you want to kill yourself?",
+ "Yes",
+ "No",
+ [&logger](Player& player, bool yes) {
+ if (yes) {
+ player.kill();
+
+ logger.info("{} killed themselves", player.getRealName());
+ }
+ }
+ );
+
+ form.sendTo(player);
+ }
+ }
+ });
+
+ // ...
+
+}
+
+bool disable() {
+
+ // ...
+
+ eventBus.removeListener(playerUseItemEventListener);
+
+ // ...
+
+}
+```
+
+让我们将代码拆开来看。在回调lambda函数中,我们捕获了配置项`enableClockMenu`和logger,然后进行判断,只有配置项启用时,才执行逻辑。
+
+```cpp
+playerUseItemEventListener = eventBus.emplaceListener(
+ [enableClockMenu = config.enableClockMenu, &logger](ll::event::PlayerUseItemEvent& event) {
+ if (enableClockMenu) {
+ // ...
+ }
+ }
+);
+```
+
+在逻辑中,我们首先获取该事件的两个属性,即使用物品的玩家和被使用的物品。然后判断物品id是否为`clock`,并执行弹出表单的逻辑。
+
+```cpp
+auto& player = event.self();
+auto& itemStack = event.item();
+
+if (itemStack.getRawNameId() == "clock") {
+ // ...
+}
+```
+
+!!! warning
+ 不要使用`itemStack.getName()`,因为这个函数返回的是物品显示的名字,比如`Clock`或`Iron Sword`。
+
+在这里我们使用了最简单的模态表单`ModalForm`,其构造函数的第一个参数是表单的标题,第二个参数是表单提示内容,第三个参数是左下角按钮内容,第四个参数是右下角按钮内容。回调函数接收两个参数,第一个参数是表单发送向的玩家,第二个参数是玩家的选择,`true`代表选择了左下角按钮。
+
+```cpp
+ll::form::ModalForm form(
+ "Warning",
+ "Are you sure you want to kill yourself?",
+ "Yes",
+ "No",
+ [&logger](Player& player, bool yes) {
+ if (yes) {
+ player.kill();
+
+ logger.info("{} killed themselves", player.getRealName());
+ }
+ }
+);
+```
+
+接下来将表单发送给玩家即可。
+
+```cpp
+form.sendTo(player);
+```
+
+## 运行你的模组
+
+如果你的模组正常构建完毕,你应该能看到`bin/`目录内有一个以你的模组名为名的目录。将这个目录拷贝到LeviLamina目录中的`plugins/`目录里面(如果没有,请创建),得到如下的文件结构:
+
+```text
+/path/to/levilamina/plugins/better-suicide
+├── better-suicide.dll
+└── manifest.json
+```
+
+然后运行LeviLamina服务器(`bedrock_server_mod.exe`)即可。
+
+## 下一步?
+
+你可以[公开发布你的模组](./publish_your_first_mod.zh.md),让更多的人使用你的模组。
+
+## 更进一步的练习
+
+我们可以在这个模组的基础上,增加一些功能,来练习LeviLamina模组开发的更多知识。下面是一些可能的练习:
+
+- 设置玩家自杀的冷却时间
+- 让玩家自杀时,保留所有物品不掉落
+- 让玩家自杀时,保留经验
+- 让玩家自杀时,在原地重生
+- 统计玩家自杀次数,并在侧边栏显示排行榜
+- 使用更高级的表单,让玩家选择自杀的方式
+- 让玩家自杀时,显示一个自定义的死亡信息
diff --git a/docs/guides/event_guide.md b/docs/main/developer_guides/how_to_guides/event_guide.md
similarity index 100%
rename from docs/guides/event_guide.md
rename to docs/main/developer_guides/how_to_guides/event_guide.md
diff --git a/docs/guides/event_guide.zh.md b/docs/main/developer_guides/how_to_guides/event_guide.zh.md
similarity index 100%
rename from docs/guides/event_guide.zh.md
rename to docs/main/developer_guides/how_to_guides/event_guide.zh.md
diff --git a/docs/guides/export_interface_guide.md b/docs/main/developer_guides/how_to_guides/export_interface_guide.md
similarity index 100%
rename from docs/guides/export_interface_guide.md
rename to docs/main/developer_guides/how_to_guides/export_interface_guide.md
diff --git a/docs/guides/export_interface_guide.zh.md b/docs/main/developer_guides/how_to_guides/export_interface_guide.zh.md
similarity index 100%
rename from docs/guides/export_interface_guide.zh.md
rename to docs/main/developer_guides/how_to_guides/export_interface_guide.zh.md
diff --git a/docs/guides/find_function_guide.md b/docs/main/developer_guides/how_to_guides/find_function_guide.md
similarity index 97%
rename from docs/guides/find_function_guide.md
rename to docs/main/developer_guides/how_to_guides/find_function_guide.md
index 247de91d5a..312e192229 100644
--- a/docs/guides/find_function_guide.md
+++ b/docs/main/developer_guides/how_to_guides/find_function_guide.md
@@ -4,7 +4,7 @@ This article will introduce how to understand the functions in BDS and how to fi
## How to Start?
-Before you begin, it's best if you have already read the [Hook Guide](../guides/hook_guide.md). You should also have some understanding of C++ and low-level knowledge.
+Before you begin, it's best if you have already read the [Hook Guide](hook_guide.md). You should also have some understanding of C++ and low-level knowledge.
### Tools Needed
@@ -85,7 +85,7 @@ Our subsequent practice proved that this function indeed returns the player's co
In fact, this function is very simple, the naming of the function is also very intuitive, and the process of finding it is also straightforward. In our development, relying on such a method to find the target is the most basic skill.
-## How to Find Functions After Determining the Plugin Purpose?
+## How to Find Functions After Determining the Mod Purpose?
Sometimes, our purpose in finding functions is not to call them, but to modify their behavior to meet our needs.
@@ -106,7 +106,7 @@ public: virtual void __cdecl ServerNetworkHandler::handle(class NetworkIdentifie
//symbol: ?handle@ServerNetworkHandler@@UEAAXAEBVNetworkIdentifier@@AEBVTextPacket@@@Z
```
-Using the knowledge we learned in the [Hook Guide](../guides/hook_guide.md), we can easily hook this function to achieve our functionality. (Right?)
+Using the knowledge we learned in the [Hook Guide](hook_guide.md), we can easily hook this function to achieve our functionality. (Right?)
Well, actually, we encounter a second problem: there doesn't seem to be a `std::string` type parameter here, and we seem unable to get the player's chat content.
diff --git a/docs/guides/find_function_guide.zh.md b/docs/main/developer_guides/how_to_guides/find_function_guide.zh.md
similarity index 97%
rename from docs/guides/find_function_guide.zh.md
rename to docs/main/developer_guides/how_to_guides/find_function_guide.zh.md
index 903b9c5d25..3cdf3fc7ad 100644
--- a/docs/guides/find_function_guide.zh.md
+++ b/docs/main/developer_guides/how_to_guides/find_function_guide.zh.md
@@ -4,7 +4,7 @@
## 如何开始?
-在开始之前,你最好已经阅读了[Hook指南](../guides/hook_guide.md)。并且对 C++ 和底层知识有一定的了解。
+在开始之前,你最好已经阅读了[Hook指南](hook_guide.md)。并且对 C++ 和底层知识有一定的了解。
### 需要的工具
@@ -86,7 +86,7 @@ IDA Pro给出这样的结果:
事实上,这个函数非常简单,函数的命名也十分直观,寻找到它的过程也非常简单,而在我们的开发中,依靠这样的方法来寻找目标是最基本的技能。
-## 确定插件目的后,如何寻找函数?
+## 确定模组目的后,如何寻找函数?
有时候,我们寻找函数的目的不是为了调用,而是为了修改函数的行为,使其符合我们的需求。
@@ -107,7 +107,7 @@ public: virtual void __cdecl ServerNetworkHandler::handle(class NetworkIdentifie
//symbol: ?handle@ServerNetworkHandler@@UEAAXAEBVNetworkIdentifier@@AEBVTextPacket@@@Z
```
-利用我们在[Hook指南](../guides/hook_guide.md)中学到的知识,我们可以很容易的 Hook 这个函数,实现我们的功能。(吗?)
+利用我们在[Hook指南](hook_guide.md)中学到的知识,我们可以很容易的 Hook 这个函数,实现我们的功能。(吗?)
好吧,事实上我们遇到第二个问题,这里似乎没有一个 `std::string` 类型的参数,我们似乎无法获取到玩家的聊天内容。
diff --git a/docs/guides/form_guide.md b/docs/main/developer_guides/how_to_guides/form_guide.md
similarity index 100%
rename from docs/guides/form_guide.md
rename to docs/main/developer_guides/how_to_guides/form_guide.md
diff --git a/docs/guides/form_guide.zh.md b/docs/main/developer_guides/how_to_guides/form_guide.zh.md
similarity index 100%
rename from docs/guides/form_guide.zh.md
rename to docs/main/developer_guides/how_to_guides/form_guide.zh.md
diff --git a/docs/guides/hook_guide.md b/docs/main/developer_guides/how_to_guides/hook_guide.md
similarity index 92%
rename from docs/guides/hook_guide.md
rename to docs/main/developer_guides/how_to_guides/hook_guide.md
index b200148b66..f761ada888 100644
--- a/docs/guides/hook_guide.md
+++ b/docs/main/developer_guides/how_to_guides/hook_guide.md
@@ -92,7 +92,7 @@ Referring to the [`DedicatedServer.h`](https://github.com/LiteLDev/LeviLamina/bl
Since the constructor is a member function of the class, we used the `INSTANCE_HOOK` type of Hook, which means we don't need to fill in the first [`this pointer`](https://en.cppreference.com/w/cpp/language/this) parameter generated by the compiler.
-We used the `AUTO`-marked Hook because we want it to automatically register when the plugin is loaded.
+We used the `AUTO`-marked Hook because we want it to automatically register when the mod is loaded.
Finally, for convenience, we used the `TYPE`-marked Hook, so we can directly call functions under the DedicatedServer type in the function body. Although we did not use it in this code, it is a good habit.
@@ -102,14 +102,14 @@ Finally, for convenience, we used the `TYPE`-marked Hook, so we can directly cal
For non-automatically registered Hooks, you need
- to call the `hook()` function to register at the moment your plugin needs to register the Hook.
+ to call the `hook()` function to register at the moment your mod needs to register the Hook.
#### Unloading
-All Hooks will automatically unload when BDS unloads. You can also call the `unhook()` function to unload when your plugin needs to unload the Hook.
+All Hooks will automatically unload when BDS unloads. You can also call the `unhook()` function to unload when your mod needs to unload the Hook.
## In Conclusion
-Hook is a very powerful technique but also a double-edged sword. If used improperly, it can cause crashes of the BDS core or plugins, and in severe cases, affect the game saves.
+Hook is a very powerful technique but also a double-edged sword. If used improperly, it can cause crashes of the BDS core or mods, and in severe cases, affect the game saves.
Therefore, please be cautious when using Hooks, check your code carefully to avoid unnecessary errors.
diff --git a/docs/guides/hook_guide.zh.md b/docs/main/developer_guides/how_to_guides/hook_guide.zh.md
similarity index 94%
rename from docs/guides/hook_guide.zh.md
rename to docs/main/developer_guides/how_to_guides/hook_guide.zh.md
index cfffba40f8..a18a2215b7 100644
--- a/docs/guides/hook_guide.zh.md
+++ b/docs/main/developer_guides/how_to_guides/hook_guide.zh.md
@@ -92,7 +92,7 @@ LL_AUTO_TYPE_INSTANCE_HOOK(
又由于构造函数是类的成员函数,所以我们使用了 ```INSTANCE_HOOK```类型的 Hook,这使我们不需要填写由编译器产生的第一个[```this指针```](https://zh.cppreference.com/w/cpp/language/this)参数。
-又由于我们希望在插件被加载的时候自动注册,所以我们使用了 ```AUTO```标注的 Hook。
+又由于我们希望在模组被加载的时候自动注册,所以我们使用了 ```AUTO```标注的 Hook。
最后由于方便,我们使用了 ```TYPE```标注的 Hook,使得我们可以直接在函数体内调用 DedicatedServer 类型下的函数,虽然在这段代码中我们并没有使用到,但是这是一个好习惯。
@@ -100,14 +100,14 @@ LL_AUTO_TYPE_INSTANCE_HOOK(
#### 注册
-针对非自动注册的 Hook,你需要在你插件需要注册 Hook 的时机调用```hook()```函数进行注册。
+针对非自动注册的 Hook,你需要在你模组需要注册 Hook 的时机调用```hook()```函数进行注册。
#### 卸载
-所有的 Hook 都会在 BDS 卸载时自动卸载,你也可以在你插件需要卸载 Hook 的时机调用```unhook()```函数进行卸载。
+所有的 Hook 都会在 BDS 卸载时自动卸载,你也可以在你模组需要卸载 Hook 的时机调用```unhook()```函数进行卸载。
## 写在最后
-Hook 是一种非常强大的技术,但是也是一把双刃剑,如果使用不当,可能会导致BDS本体崩溃,或者插件崩溃,严重的甚至会对存档产生影响。
+Hook 是一种非常强大的技术,但是也是一把双刃剑,如果使用不当,可能会导致BDS本体崩溃,或者模组崩溃,严重的甚至会对存档产生影响。
所以请在使用 Hook 的时候一定要谨慎,仔细检查你的代码,避免出现不必要的错误。
diff --git a/docs/guides/i18n_guide.md b/docs/main/developer_guides/how_to_guides/i18n_guide.md
similarity index 100%
rename from docs/guides/i18n_guide.md
rename to docs/main/developer_guides/how_to_guides/i18n_guide.md
diff --git a/docs/guides/i18n_guide.zh.md b/docs/main/developer_guides/how_to_guides/i18n_guide.zh.md
similarity index 100%
rename from docs/guides/i18n_guide.zh.md
rename to docs/main/developer_guides/how_to_guides/i18n_guide.zh.md
diff --git a/docs/guides/item_guide.md b/docs/main/developer_guides/how_to_guides/item_guide.md
similarity index 100%
rename from docs/guides/item_guide.md
rename to docs/main/developer_guides/how_to_guides/item_guide.md
diff --git a/docs/guides/item_guide.zh.md b/docs/main/developer_guides/how_to_guides/item_guide.zh.md
similarity index 100%
rename from docs/guides/item_guide.zh.md
rename to docs/main/developer_guides/how_to_guides/item_guide.zh.md
diff --git a/docs/guides/permission_guide.md b/docs/main/developer_guides/how_to_guides/permission_guide.md
similarity index 100%
rename from docs/guides/permission_guide.md
rename to docs/main/developer_guides/how_to_guides/permission_guide.md
diff --git a/docs/guides/permission_guide.zh.md b/docs/main/developer_guides/how_to_guides/permission_guide.zh.md
similarity index 100%
rename from docs/guides/permission_guide.zh.md
rename to docs/main/developer_guides/how_to_guides/permission_guide.zh.md
diff --git a/docs/tutorials/img/levilamina-plugin-template.png b/docs/main/developer_guides/levilamina-mod-template.png
similarity index 100%
rename from docs/tutorials/img/levilamina-plugin-template.png
rename to docs/main/developer_guides/levilamina-mod-template.png
diff --git a/docs/main/developer_guides/publish_your_first_mod.md b/docs/main/developer_guides/publish_your_first_mod.md
new file mode 100644
index 0000000000..6cf9eb6c7f
--- /dev/null
+++ b/docs/main/developer_guides/publish_your_first_mod.md
@@ -0,0 +1,80 @@
+# Publish your first mod
+
+In the tutorial [Create Your First Mod](create_your_first_mod.md), we created a simple mod. In this tutorial, we will learn how to publish a mod.
+
+### Prerequisites
+
+First, make sure you have installed [lip](https://github.com/lippkg/lip).
+
+You should also have followed the steps in [Create Your First Mod](create_your_first_mod.md) to create a mod, where the exported mod directory has a structure similar to the following:
+
+```
+.
+└── mod-name
+ ├── manifest.json
+ ├── mod-name.dll
+ └── mod-name.pdb
+```
+
+Here, `mod-name` is the name of the mod.
+
+### Create a GitHub Repository
+
+First, you need to create a GitHub repository to store your mod. You can use any name you like, such as `mod-name`.
+
+In the repository, we recommend including a `README.md` file to describe the mod and a `logo.png` file to serve as the mod's icon.
+
+### Create a tooth.json
+
+Create a `tooth.json` file in the exported mod directory with the following content:
+
+```json
+{
+ "format_version": 2,
+ "tooth": "github.com/my-github-username/mod-name",
+ "version": "0.1.0",
+ "info": {
+ "name": "MyMod",
+ "description": "MyMod is a great mod!",
+ "author": "My Name",
+ "source": "github.com/my-github-username/my-source-code",
+ "tags": [
+ "mod",
+ "ll"
+ ]
+ },
+ "dependencies": {
+ "github.com/tooth-hub/another-mod": "2.0.x"
+ },
+ "prerequisites": {
+ "github.com/LiteLDev/LeviLamina": "1.0.x"
+ },
+ "files": {
+ "place": [
+ {
+ "src": "mod-name/*",
+ "dest": "plugins/mod-name"
+ }
+ ]
+ }
+}
+```
+
+Replace the value of the `tooth` field with the GitHub repository address of your mod, replace the value of the `version` field with the version number of your mod, fill in the values of the fields in the `info` section, and fill in the values of the fields in the `dependencies` and `prerequisites` sections.
+
+!!! note
+ The `dependencies` section automatically installs the required mods when installing the mod and uninstalls them when uninstalling the mod. However, the `prerequisites` section does not automatically install the required mods; instead, it throws an error if the dependencies are missing. Generally, the mods listed in the `prerequisites` section should be fundamental and framework-level packages, such as `github.com/LiteLDev/LeviLamina`, to avoid accidentally uninstalling them during the mod uninstallation process.
+
+For more information about `tooth.json`, please refer to .
+
+### Try Packing and Installing the Mod
+
+In the exported mod directory, run `lip tooth pack mod.tth`. This will generate a `mod.tth` file in the current directory, which is a packaged mod. You can move this mod to a suitable location and try installing it using `lip install mod.tth`.
+
+### Publish the Mod
+
+Commit your changes to the GitHub repository, then click on "Releases" in the repository on GitHub, click on "Create a new release," fill in the "Tag version" and "Release title" fields, and then click on "Publish release" to publish the mod. Note that the corresponding tag must be in a format similar to `v0.1.0`, which means it should be the value of the `version` field in `tooth.json` prefixed with a `v`.
+
+You can then install your mod using `lip install github.com/my-github-username/mod-name`. Due to synchronization delays in the version listing, this command may throw an error in the initial period after the release. In such cases, you can specify the version number to install the mod, for example, `lip install github.com/my-github-username/mod-name@0.1.0`.
+
+After some time, you will also be able to see your mod in LipUI and .
diff --git a/docs/main/developer_guides/publish_your_first_mod.zh.md b/docs/main/developer_guides/publish_your_first_mod.zh.md
new file mode 100644
index 0000000000..16c236c55e
--- /dev/null
+++ b/docs/main/developer_guides/publish_your_first_mod.zh.md
@@ -0,0 +1,80 @@
+# 发布你的第一个模组
+
+在教程[创建你的第一个模组](create_your_first_mod.md)中,我们创建了一个简单的模组。在本教程中,我们将学习如何发布模组。
+
+### 前置条件
+
+首先,你应当确保你安装了[lip](https://github.com/lippkg/lip)。
+
+你还应当按照[创建你的第一个模组](create_your_first_mod.md)中的步骤创建了一个模组,其中模组导出目录有类似如下的结构:
+
+```
+.
+└── mod-name
+ ├── manifest.json
+ ├── mod-name.dll
+ └── mod-name.pdb
+```
+
+其中,`mod-name`是模组名。
+
+### 创建一个GitHub仓库
+
+首先,你需要创建一个GitHub仓库,用于存放你的模组。你可以使用任何你喜欢的名称,例如`mod-name`。
+
+在仓库中,我们建议放一个`README.md`文件,用于描述模组;并放一个`logo.png`文件,用于作为模组的图标。
+
+### 创建一个tooth.json
+
+在模组导出目录中创建一个`tooth.json`文件,内容如下:
+
+```json
+{
+ "format_version": 2,
+ "tooth": "github.com/my-github-username/mod-name",
+ "version": "0.1.0",
+ "info": {
+ "name": "MyMod",
+ "description": "MyMod is a great mod!",
+ "source": "github.com/my-github-username/my-source-code",
+ "author": "My Name",
+ "tags": [
+ "mod",
+ "ll"
+ ]
+ },
+ "dependencies": {
+ "github.com/tooth-hub/another-mod": "2.0.x"
+ },
+ "prerequisites": {
+ "github.com/LiteLDev/LeviLamina": "1.0.x"
+ },
+ "files": {
+ "place": [
+ {
+ "src": "mod-name/*",
+ "dest": "plugins/mod-name"
+ }
+ ]
+ }
+}
+```
+
+替换`tooth`字段的值为你的模组的GitHub仓库地址,替换`version`字段的值为你的模组的版本号,填写`info`中各个字段的值,填写`dependencies`和`prerequisites`中各个字段的值。
+
+!!! note
+ `dependencies`在安装模组时,会自动安装依赖的模组,在卸载模组时,会自动卸载依赖的模组。但是`prerequisites`不会自动安装,而是在缺少依赖时报错。一般来说,`prerequisites`中的模组应当是一些基础性的、框架级别的包,例如`github.com/LiteLDev/LeviLamina`,以避免模组的卸载过程中误卸载了这些包。
+
+更多关于`tooth.json`的信息,请参考。
+
+### 尝试打包和安装模组
+
+在模组导出目录中运行`lip tooth pack mod.tth`,将会在当前目录下生成一个`mod.tth`文件,这是一个打包好的模组。你可以移动这个模组到合适的地方,并使用`lip install mod.tth`尝试安装这个模组。
+
+### 发布模组
+
+将更改提交到GitHub仓库,然后在GitHub仓库中点击`Releases`,点击`Create a new release`,填写`Tag version`和`Release title`,然后点击`Publish release`,即可发布模组。注意对应的tag必须为类似`v0.1.0`的格式,也就是`tooth.json`中`version`字段的值加上一个`v`。
+
+然后你就可以通过`lip install github.com/my-github-username/mod-name`来安装你的模组了。由于版本列表同步延迟,这个命令可能会在刚发布的一段时间内报错,你可以指定版本号来安装模组,例如`lip install github.com/my-github-username/mod-name@0.1.0`。
+
+在一段时间后,你也可以在LipUI和查看到你的模组了。
diff --git a/docs/tutorials/publish_your_first_pack.md b/docs/main/developer_guides/publish_your_first_pack.md
similarity index 95%
rename from docs/tutorials/publish_your_first_pack.md
rename to docs/main/developer_guides/publish_your_first_pack.md
index 3fbddd893e..94a6f4ebb5 100644
--- a/docs/tutorials/publish_your_first_pack.md
+++ b/docs/main/developer_guides/publish_your_first_pack.md
@@ -1,4 +1,4 @@
-# Publishing Your First Pack
+# Publishing your first pack
First, you should ensure that you have installed [lip](https://github.com/lippkg/lip).
@@ -36,7 +36,7 @@ Replace the value of the `tooth` field with the GitHub repository address, repla
In the `dependencies` section, specify LeviLamina and any other packages to be integrated.
-For more information about `tooth.json`, please refer to .
+For more information about `tooth.json`, please refer to .
### Trying to Pack and Install the Pack
@@ -48,4 +48,4 @@ Commit the changes to the GitHub repository, then click on `Releases` in the rep
Afterwards, you can install your pack using `lip install github.com/my-github-username/pack-name`. Due to synchronization delays in the version listing, this command may throw an error in the initial period after the release. In such cases, you can specify the version number to install the pack, for example, `lip install github.com/my-github-username/pack-name@0.1.0`.
-After some time, you will be able to see your pack in LipUI and on .
+After some time, you will be able to see your pack in LipUI and on .
diff --git a/docs/tutorials/publish_your_first_pack.zh.md b/docs/main/developer_guides/publish_your_first_pack.zh.md
similarity index 90%
rename from docs/tutorials/publish_your_first_pack.zh.md
rename to docs/main/developer_guides/publish_your_first_pack.zh.md
index 52d9456e74..b94dd2e284 100644
--- a/docs/tutorials/publish_your_first_pack.zh.md
+++ b/docs/main/developer_guides/publish_your_first_pack.zh.md
@@ -35,7 +35,7 @@
在`dependencies`中填写LeviLamina和要整合的一切包。
-更多关于`tooth.json`的信息,请参考。
+更多关于`tooth.json`的信息,请参考。
### 尝试打包和安装整合包
@@ -47,4 +47,4 @@
然后你就可以通过`lip install github.com/my-github-username/pack-name`来安装你的整合包了。由于版本列表同步延迟,这个命令可能会在刚发布的一段时间内报错,你可以指定版本号来安装整合包,例如`lip install github.com/my-github-username/pack-name@0.1.0`。
-在一段时间后,你也可以在LipUI和查看到你的整合包了。
+在一段时间后,你也可以在LipUI和查看到你的整合包了。
diff --git a/docs/main/faq.md b/docs/main/faq.md
new file mode 100644
index 0000000000..5a3981e71b
--- /dev/null
+++ b/docs/main/faq.md
@@ -0,0 +1,23 @@
+# Frequently Asked Questions
+
+## Where does LeviLamina come from?
+
+LeviLamina emerges from the depths of Minecraft Bedrock Server, sprouting forth from the seeds of the [LiteLoaderBDS](https://github.com/LiteLDev/LiteLoaderBDSv2) project.
+
+The initial versions of Minecraft Bedrock Edition lacked the extensive mod and server plugin ecosystem present in Minecraft Java Edition, thereby limiting its gameplay possibilities. A collective of C++ enthusiasts, well-versed in Minecraft Bedrock Server, conducted an analysis and utilized reverse engineering techniques and hook injection mechanisms to intervene in the operation of the game server, thus pioneering the development of the first set of server mods.
+
+However, this development paradigm encountered several challenges. Primarily, the absence of underlying framework support necessitated the reliance on diverse low-level tools for symbol analysis, injection implementation, hook registration, and other functionalities during mod development. Consequently, this led to code redundancy and duplication across different mods, as well as the potential for unforeseen conflicts among them. Moreover, the lack of explicit type definitions compelled developers to engage in reverse engineering analysis while creating mods, resulting in elevated development barriers and diminished efficiency.
+
+In response to these predicaments, the maintainers of the precursor project, LiteLoaderBDS, constructed an injection-based mod loading engine and a mod development framework. In addition, they provided type information, enabling mod developers to create mods without requiring an exhaustive comprehension of the underlying principles. This significantly mitigated the entry barriers for mod development and facilitated the flourishing of the mod ecosystem.
+
+Nevertheless, as LiteLoaderBDS progressed, certain issues came to the fore. The early design failed to account for future advancements, rendering many aspects obsolete and impeding the utilization of contemporary tools, thereby falling short of meeting the latest performance requirements. Furthermore, its tightly coupled design engendered substantial efforts when adapting to new iterations of Minecraft Bedrock Server. Consequently, Levimc made the decision to commence from scratch, leveraging existing expertise, and undertaking a comprehensive framework redesign to cultivate a mod engine that promotes user-friendliness for maintainers, developers, and users alike.
+
+## Why was LiteLoaderBDS renamed to LeviLamina?
+
+Oh, it's quite a tale! You see, LiteLoaderBDS started off as a little joke, a playful experiment. But lo and behold, it grew faster than a creeper on steroids!
+
+At first, we noticed that the name LiteLoaderBDS sounded strikingly similar to a mod launcher project in the Minecraft Java version. So, in a moment of genius, we slapped a "BDS" suffix on it to differentiate ourselves. Clever, right?
+
+But as LiteLoaderBDS 2.0 expanded, it started bulking up like a piglet at an all-you-can-eat buffet. Its performance took a nosedive, leaving us scratching our heads.
+
+That's when we made a bold decision to rebuild the entire project from scratch. And as we delved deeper into the revamping process, it became clear that a fresh start deserved a brand-new moniker. So, we dabbled in the ancient arts of Latin roots, preserving the beloved abbreviation `ll`, and voila! LeviLamina was born, a name befitting our magnificent creation.
diff --git a/docs/faq.zh.md b/docs/main/faq.zh.md
similarity index 75%
rename from docs/faq.zh.md
rename to docs/main/faq.zh.md
index 150a58344e..709505e3a7 100644
--- a/docs/faq.zh.md
+++ b/docs/main/faq.zh.md
@@ -4,13 +4,13 @@
LeviLamina从Minecraft基岩服务器的深处出现,从[LiteLoaderBDS](https://github.com/LiteLDev/LiteLoaderBDSv2)项目的种子中发芽。
-Minecraft Bedrock Edition的最初版本缺乏Minecraft Java Edition中存在的广泛的模组和服务器插件生态系统,从而限制了其游戏可能性。一群精通Minecraft Bedrock Server的C++爱好者,进行了分析,并利用逆向工程技术和钩子注入机制,干预了游戏服务器的运行,从而开创了第一套服务器插件的开发。
+Minecraft Bedrock Edition的最初版本缺乏Minecraft Java Edition中存在的广泛的模组和服务器插件生态系统,从而限制了其游戏可能性。一群精通Minecraft Bedrock Server的C++爱好者,进行了分析,并利用逆向工程技术和钩子注入机制,干预了游戏服务器的运行,从而开创了第一套服务器模组的开发。
-然而,这种开发范式遇到了一些挑战。首先,缺乏底层框架的支持,使得插件开发过程中需要依赖各种低级工具来进行符号分析、注入实现、钩子注册等功能。这导致了不同插件之间的代码冗余和重复,以及潜在的冲突。其次,缺乏明确的类型定义,迫使开发者在创建插件时进行逆向工程分析,导致了开发门槛的提高和效率的降低。
+然而,这种开发范式遇到了一些挑战。首先,缺乏底层框架的支持,使得模组开发过程中需要依赖各种低级工具来进行符号分析、注入实现、钩子注册等功能。这导致了不同模组之间的代码冗余和重复,以及潜在的冲突。其次,缺乏明确的类型定义,迫使开发者在创建模组时进行逆向工程分析,导致了开发门槛的提高和效率的降低。
-为了应对这些困境,前期项目LiteLoaderBDS的维护者,构建了一个基于注入的插件加载引擎和一个插件开发框架。此外,他们还提供了类型信息,使插件开发者无需深入了解底层原理就能创建插件。这大大降低了插件开发的入门难度,促进了插件生态系统的繁荣。
+为了应对这些困境,前期项目LiteLoaderBDS的维护者,构建了一个基于注入的模组加载引擎和一个模组开发框架。此外,他们还提供了类型信息,使模组开发者无需深入了解底层原理就能创建模组。这大大降低了模组开发的入门难度,促进了模组生态系统的繁荣。
-然而,随着LiteLoaderBDS的进展,一些问题也暴露出来。早期的设计没有考虑到未来的发展,使得许多方面过时和难以使用现代化的工具,从而无法满足最新的性能要求。此外,它的紧耦合设计在适应新版本的Minecraft Bedrock Server时也需要付出巨大的努力。因此,LiteLDev决定从头开始,利用现有的专业知识,进行全面的框架重设计,培育一个对维护者、开发者和用户都友好的插件引擎。
+然而,随着LiteLoaderBDS的进展,一些问题也暴露出来。早期的设计没有考虑到未来的发展,使得许多方面过时和难以使用现代化的工具,从而无法满足最新的性能要求。此外,它的紧耦合设计在适应新版本的Minecraft Bedrock Server时也需要付出巨大的努力。因此,Levimc决定从头开始,利用现有的专业知识,进行全面的框架重设计,培育一个对维护者、开发者和用户都友好的模组引擎。
## 为什么LiteLoaderBDS更名为LeviLamina?
diff --git a/docs/img/favicon.ico b/docs/main/favicon.ico
similarity index 100%
rename from docs/img/favicon.ico
rename to docs/main/favicon.ico
diff --git a/docs/main/index.md b/docs/main/index.md
new file mode 100644
index 0000000000..caaf25a410
--- /dev/null
+++ b/docs/main/index.md
@@ -0,0 +1,10 @@
+# Welcome to the LeviLamina Docs
+
+[![Discord](https://img.shields.io/discord/849252980430864384?style=for-the-badge&logo=discord)
+](https://discord.gg/v5R5P4vRZk)
+[![Telegram](https://img.shields.io/badge/Telegram-blue?style=for-the-badge&logo=telegram)
+](https://t.me/LiteLoader)
+
+LeviLamina is an unofficial mod loader that provides essential API support for Minecraft Bedrock Dedicated Server (BDS). It features a comprehensive API, a powerful event system, and utility interfaces, enabling developers to enhance BDS with new gameplay features effortlessly.
+
+Developers can create mods using languages such as C++, JavaScript, Lua, Python, and C#. This seamless integration allows for easy customization and an intuitive learning experience.
diff --git a/docs/main/index.zh.md b/docs/main/index.zh.md
new file mode 100644
index 0000000000..a291b17a90
--- /dev/null
+++ b/docs/main/index.zh.md
@@ -0,0 +1,14 @@
+# 欢迎来到LeviLamina文档
+
+[![Discord](https://img.shields.io/discord/849252980430864384?style=for-the-badge&logo=discord)
+](https://discord.gg/v5R5P4vRZk)
+[![Telegram](https://img.shields.io/badge/Telegram-blue?style=for-the-badge&logo=telegram)
+](https://t.me/LiteLoader)
+[![656669024](https://img.shields.io/badge/QQ群-red?style=for-the-badge&logo=tencent%20qq)
+](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=ndxRXO1HARA8ing7OunMClOz3cQTogL0&authKey=D7QTcqnzhBzuh3zc%2F70FjgklsVvkCImTjSRqHMwYGCLwIFpxzp%2FflC97Y7AUG%2Fpy&noverify=0&group_code=656669024)
+[![850517473](https://img.shields.io/badge/QQ群-red?style=for-the-badge&logo=tencent%20qq)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=YFHRYvpO6mjqE5QeZxcMIlahGzWR3JLA&authKey=M8p8hkdctNSyXJo7Ux%2FzdNu4VL2jLiqMGakM3eHlA4ZLvjdwtL%2F1SIKE51s%2FKcp6&noverify=0&group_code=850517473)
+[![1evilamina](https://img.shields.io/badge/QQ频道-red?style=for-the-badge&logo=tencent%20qq)](https://pd.qq.com/s/a13gu04rv)
+
+LeviLamina 是一个非官方的模组加载器,为 Minecraft 基岩专用服务器(BDS)提供必需的 API 支持。它具有全面的 API、强大的事件系统和实用接口,使开发者能够轻松增强 BDS 的新游戏功能。
+
+开发者可以使用 C++、JavaScript、Lua、Python 和 C# 等语言创建模组。这种无缝集成允许轻松自定义,并提供直观的学习体验。
\ No newline at end of file
diff --git a/docs/img/logo.svg b/docs/main/logo.svg
similarity index 100%
rename from docs/img/logo.svg
rename to docs/main/logo.svg
diff --git a/docs/main/maintainer_guides/cpp_style_guide.md b/docs/main/maintainer_guides/cpp_style_guide.md
new file mode 100644
index 0000000000..28a045494e
--- /dev/null
+++ b/docs/main/maintainer_guides/cpp_style_guide.md
@@ -0,0 +1,92 @@
+# C++ Style Guide
+
+## Naming
+
+### Directory Names
+
+In snake case, e.g. `my_directory`
+
+### File Names
+
+In pascal case, e.g. `MyFile.cpp`. For single-class files, the filename should be consistent with the class name.
+
+### Type Names
+
+In pascal case, e.g. `MyClass`. Names of all types including classes, structs, type aliases, enums and type template parameters should follow this rule.
+
+### Variable Names
+
+#### Common Variable Names
+
+In camel case, e.g. `myVariable`.
+
+#### Class and Struct Data Members
+
+For non-public ones, in Hungarian, e.g. `mMyMember`.
+
+For public ones, in camelCase, e.g. `myMember`.
+
+### Constant Names
+
+In Pascal case, e.g. `MyConst`.
+
+### Function Names
+
+In camel case, e.g. `myFunction`.
+
+### Namespace Names
+
+In lower case, e.g. `my_namespace`.
+
+### Enumerator Names
+
+In pascal case, e.g. `MyEnumerator`.
+
+### Macro Names
+
+In upper snake case, e.g. `MY_MACRO`.
+
+## Comments
+
+### Comment Style
+
+Use `// ...` for end-of-line comments. Use `/* ... */` for mid-line comments. Use `/// ...` for Doxygen comments.
+
+### File Comments
+
+Start each file with license boilerplate.
+
+If a source file (such as a .h file) declares multiple user-facing abstractions (common functions, related classes, etc.), include a comment describing the collection of those abstractions.
+
+### Struct and Class Comments
+
+Every non-obvious class or struct declaration should have an accompanying comment that describes what it is for and how it should be used, including at least a brief introduction and public attribute description.
+
+For separated classes, e.g. `.h` and `.cpp`, comments should go with header files.
+
+### Function Comments
+
+Including at least a brief introduction, parameter and return value description, and exception description.
+
+### Variable Comments
+
+#### Class Data Members
+
+All public members should have a comment.
+
+#### Global Variables
+
+All global variables should have a comment describing what they are, what they are used for, and (if unclear) why they need to be global.
+
+### Implementation Comments
+
+In third-person tense.
+
+### Function Argument Comments
+
+If function arguments are not clear, comments should be added, e.g. `/*count=*/`.
+
+### TODO Comments
+
+TODOs should include the string TODO in all caps, followed by the issue ID and the description referenced by the TODO, e.g. `// TODO(#1234): Update this list after the Foo service is turned down.`.
+
diff --git a/docs/main/maintainer_guides/cpp_style_guide.zh.md b/docs/main/maintainer_guides/cpp_style_guide.zh.md
new file mode 100644
index 0000000000..05a49970bd
--- /dev/null
+++ b/docs/main/maintainer_guides/cpp_style_guide.zh.md
@@ -0,0 +1,95 @@
+# C++ 风格指南
+
+## 命名
+
+### 目录名
+
+使用 小蛇式 命名法 例如: `my_directory`
+
+### 文件名
+
+使用 大驼峰 命名法, 例如: `MyFile.cpp`。 对于单个类文件,文件名应该与类名一致。
+
+### 类型名称
+
+使用 大驼峰 命名法, 例如: `MyClass`。 所有类型(包括类、结构、类型别名、枚举和类型模板参数)的名称都应遵循此规则
+
+### 变量名
+
+#### 常用变量名
+
+使用 小驼峰 命名法, 例如: `myVariable`。
+
+#### 类和结构数据成员
+
+对于私有成员使用 匈牙利 命名法, 例如: `mMyMember`。
+
+对于公开成员使用 小驼峰 命名法, 例如: `myMember`。
+
+### 常量名称
+
+使用 大驼峰 命名法, 例如: `MyConst`。
+
+### 函数名称
+
+使用 小驼峰 命名法, 例如: `myFunction`。
+
+### 命名空间名称
+
+使用 小蛇式 命名法, 例如: `my_namespace`。
+
+### 枚举名称
+
+使用 大驼峰 命名法, 例如: `MyEnumerator`。
+
+### 宏定义名称
+
+使用 大蛇式 命名法, 例如: `MY_MACRO`。
+
+## 注释
+
+### 注释风格
+
+- 使用 `// ...` 表示行尾注释。
+- 使用 `/* ... */` 用于行中注释。
+- 使用 `/// ...` 表示 Doxygen 注释。
+
+### 文件注释
+
+在每个文件的开头使用许可证模板。
+
+如果一个源文件(如 .h 文件)声明了多个面向用户的抽象(常用函数、相关类等),请在注释中说明这些抽象的集合。
+
+### 结构和类注释
+
+每个非显而易见的类或结构体声明都应附有注释,说明其用途和使用方法,至少包括简要介绍和公有属性说明。
+
+对于分离的类,例如:`.h` 和 `.cpp`,注释应与头文件放在一起。
+
+### 函数注释
+
+至少包括简要介绍、参数和返回值描述以及异常描述。
+
+### 变量的注释
+
+#### 类数据成员
+
+所有公共成员都应有注释。
+
+#### 全局变量
+
+所有全局变量都应有注释,说明它们是什么,有什么用途,以及(如果不清楚)为什么要全局化。
+
+### 实现注释
+
+使用第三人称叙述。
+
+### 函数参数注释
+
+如果函数参数不清楚,应添加注释,例如: `/*count=*/`。
+
+### TODO 注释
+
+TODO 应包括大写的 TODO 字符串,之后是问题 ID 和 TODO 引用的描述。
+例如:`// TODO(#1234): 在 Foo 服务关闭后更新此列表。`
+
diff --git a/docs/main/mkdocs.yml b/docs/main/mkdocs.yml
new file mode 100644
index 0000000000..911b465ae4
--- /dev/null
+++ b/docs/main/mkdocs.yml
@@ -0,0 +1,112 @@
+site_name: LeviLamina Docs
+repo_url: https://github.com/LiteLDev/LeviLamina
+docs_dir: .
+
+exclude_docs: |
+ /mkdocs.yml
+ /requirements.txt
+
+nav:
+ - Home: index.md
+ - Quickstart: quickstart.md
+ - FAQ: faq.md
+
+ - Player Guides:
+ - Install:
+ - player_guides/install.md
+ - Install on Docker: https://github.com/LiteLDev/levilamina-docker-server
+ - player_guides/troubleshooting.md
+
+ - Developer Guides:
+ - developer_guides/create_your_first_mod.md
+ - developer_guides/publish_your_first_mod.md
+ - developer_guides/publish_your_first_pack.md
+
+ - How-to Guides:
+ - developer_guides/how_to_guides/event_guide.md
+ - developer_guides/how_to_guides/export_interface_guide.md
+ - developer_guides/how_to_guides/find_function_guide.md
+ - developer_guides/how_to_guides/form_guide.md
+ - developer_guides/how_to_guides/hook_guide.md
+ - developer_guides/how_to_guides/i18n_guide.md
+ - developer_guides/how_to_guides/item_guide.md
+ - developer_guides/how_to_guides/permission_guide.md
+
+ - API 🔗: https://lamina.levimc.org/api
+
+ - Maintainer Guides:
+ - maintainer_guides/cpp_style_guide.md
+
+theme:
+ name: material
+ features:
+ - navigation.tabs
+ - navigation.tabs.sticky
+ favicon: favicon.ico
+ logo: logo.svg
+ palette:
+ - media: "(prefers-color-scheme: light)"
+ scheme: default
+ primary: white
+ toggle:
+ icon: material/brightness-7
+ name: Switch to dark mode
+
+ - media: "(prefers-color-scheme: dark)"
+ scheme: slate
+ toggle:
+ icon: material/brightness-4
+ name: Switch to light mode
+
+markdown_extensions:
+ - abbr
+ - admonition
+ - attr_list
+ - def_list
+ - footnotes
+ - md_in_html
+ - toc
+ - tables
+ - pymdownx.arithmatex
+ - pymdownx.betterem
+ - pymdownx.caret
+ - pymdownx.mark
+ - pymdownx.tilde
+ - pymdownx.critic
+ - pymdownx.details
+ - pymdownx.emoji
+ - pymdownx.highlight:
+ auto_title: true
+ linenums: true
+ - pymdownx.inlinehilite
+ - pymdownx.keys
+ - pymdownx.smartsymbols
+ - pymdownx.snippets
+ - pymdownx.superfences
+ - pymdownx.tabbed:
+ alternate_style: true
+ - pymdownx.tasklist
+
+plugins:
+ - i18n:
+ languages:
+ - locale: en
+ default: true
+ name: English
+
+ - locale: zh
+ name: 中文
+ nav_translations:
+ Home: 主页
+ Quickstart: 快速开始
+ FAQ: 常见问题
+ Player Guides: 玩家指南
+ Install: 安装
+ Install on Docker: 在Docker上安装
+ Developer Guides: 开发者指南
+ Tutorials: 教程
+ How-to Guides: 如何做到···
+ API 🔗: 接口 🔗
+ Maintainer Guides: 维护者指南
+
+ - search
diff --git a/docs/main/player_guides/install.md b/docs/main/player_guides/install.md
new file mode 100644
index 0000000000..43afad424f
--- /dev/null
+++ b/docs/main/player_guides/install.md
@@ -0,0 +1,102 @@
+# Install on Windows
+
+## Prerequisites
+
+To install LeviLamina, you need one of the following Windows versions:
+
+- Windows 10
+- Windows 11
+- Windows Server 2019
+- Windows Server 2022
+
+To run Bedrock Dedicated Server for Minecraft, you need to install the following software:
+
+- [Visual C++ Redistributable for Visual Studio 2015, 2017, 2019, and 2022](https://aka.ms/vs/17/release/vc_redist.x64.exe)
+
+Since LeviLamina is not compatible with the previous LiteLoaderBDS 2, you need to uninstall LiteLoaderBDS 2 before installing LeviLamina.
+
+## Installation Methods
+
+You can install LeviLamina in different ways, depending on your needs:
+
+- You can [install via LipUI](#install-via-lipui), which is a graphical user interface for lip. This is useful if you are not familiar with the command line.
+
+- You can [install via lip](#install-via-lip), for ease of installation and upgrade tasks. This is the recommended approach.
+
+- You can download the modules and [install them manually](#install-manually) and manage upgrades completely manually. This is useful in situations such as installing LeviLamina on air-gapped systems with no access to the internet.
+
+### Install via LipUI
+
+Simply download and run [LipUI](https://github.com/lippkg/LipUI), and then install LeviLamina from the package index.
+
+### Install via lip
+
+If you have not installed lip, you can install it following the instructions in [lip installation guide](https://lip.futrime.com/install/).
+
+After installing lip, you can install LeviLamina by running the following command:
+
+```powershell
+lip install github.com/LiteLDev/LeviLamina
+```
+
+To install a specific version of LeviLamina, for example, 0.4.2, you can run the following command:
+
+```powershell
+lip install github.com/LiteLDev/LeviLamina@0.4.2
+```
+
+During the installation, you might be asked to confirm some prompts. You can press `y` to confirm the prompts. To skip the prompts, you can add the `-y` option to the command.
+
+```powershell
+lip install -y github.com/LiteLDev/LeviLamina
+```
+
+You have now successfully installed LeviLamina.
+
+To upgrade LeviLamina, you can run the following command:
+
+```powershell
+lip install --upgrade github.com/LiteLDev/LeviLamina
+```
+
+!!! danger
+ Upgrading LeviLamina may result in data loss. Please make sure you have a backup of your data before upgrading.
+
+### Install Manually(Not recommended)
+
+Only developers who want to debug new version will install manually, manual installation will only install basic parts that allow LeviLamina run, it lacks of CrashLogger, I18N and so on.
+
+#### Downloading Necessary Files
+
+1. **Download LeviLamina**:
+ - Go to [LeviLamina's Release Page on GitHub](https://github.com/LiteLDev/LeviLamina/releases).
+ - Choose the version you want.
+ - Download the `levilamina-windows-x64.zip` file from the selected version's assets.
+
+2. **Download BDS (Minecraft Server)**:
+ - Visit [Minecraft's server download page](https://www.minecraft.net/en-us/download/server/bedrock).
+ - Get the Bedrock Dedicated Server (BDS) version that corresponds with your LeviLamina version, named `bedrock-server-.zip`.
+
+3. **Get PeEditor and PreLoader**:
+ - Head to their respective GitHub release pages: [PeEditor Releases](https://github.com/LiteLDev/PeEditor/releases) and [PreLoader Releases](https://github.com/LiteLDev/PreLoader/releases).
+ - Download the latest `PeEditor.exe` and `PreLoader.dll`.
+
+> **Note**: Typically, the latest version of LeviLamina aligns with the newest versions of PeEditor and PreLoader. Ensure they are compatible with your BDS version.
+
+#### Installation Steps
+
+1. **Unzip the BDS File**:
+ - Extract the `bedrock-server-.zip` file, which you obtained from step 2, into a new, empty folder.
+
+2. **Place PeEditor and PreLoader**:
+ - Move `PeEditor.exe` and `PreLoader.dll` into the same directory where you extracted the BDS files, ensuring they are alongside `bedrock_server.exe`.
+
+3. **Include LeviLamina Files**:
+ - Unzip the `levilamina-windows-x64.zip` file from step 1.
+ - Take the `levilamina/` folder and place them in `plugins/`, if `plugins/` is not exist, just create it.
+
+4. **Run PeEditor**:
+ - Double-click `PeEditor.exe` and wait for the process to complete.
+ - You should then find a new file named `bedrock_server_mod.exe` in the directory, and the original `bedrock_server.exe` will be renamed to `bedrock_server.exe.bak`.
+
+Now, you have successfully installed LeviLamina. To start it, run `bedrock_server_mod.exe`.
diff --git a/docs/main/player_guides/install.zh.md b/docs/main/player_guides/install.zh.md
new file mode 100644
index 0000000000..bb29ee834a
--- /dev/null
+++ b/docs/main/player_guides/install.zh.md
@@ -0,0 +1,103 @@
+# 在Windows上安装
+
+## 前提条件
+
+要安装 LeviLamina,你需要以下 Windows 版本之一:
+
+- Windows 10
+- Windows 11
+- Windows Server 2019
+- Windows Server 2022
+
+要运行 Minecraft 的 Bedrock Dedicated Server,你需要安装以下软件:
+
+- [Visual C++ Redistributable for Visual Studio 2015, 2017, 2019, and 2022](https://aka.ms/vs/17/release/vc_redist.x64.exe)
+
+由于 LeviLamina 不兼容以前的 LiteLoaderBDS 2,你需要在安装 LeviLamina 之前卸载 LiteLoaderBDS 2。
+
+## 安装方法
+
+你可以通过不同的方式安装 LeviLamina,取决于你的需求:
+
+- 你可以[通过 lipUI 安装](#通过-lipui-安装),这是 lip 的图形用户界面。这对于不熟悉命令行的用户很有用。
+
+- 你可以[通过 lip 安装](#通过-lip-安装),以便于安装和升级任务。这是推荐的方法。
+
+- 你可以下载模块并[手动安装](#手动安装)并完全手动管理升级。这在一些情况下很有用,比如在没有网络访问的隔离系统上安装 LeviLamina。
+
+### 通过 lipUI 安装
+
+只需要下载并运行[LipUI](https://github.com/lippkg/LipUI)并在包市场中选择LeviLamina安装即可。
+
+### 通过 lip 安装
+
+如果你还没有安装 lip,你可以按照[lip 安装指南](https://lip.futrime.com/zh/install/)中的说明进行安装。
+
+安装 lip 后,你可以通过运行以下命令来安装 LeviLamina:
+
+```powershell
+lip install github.com/LiteLDev/LeviLamina
+```
+
+要安装 LeviLamina 的特定版本,例如,1.0.0,你可以运行以下命令:
+
+```powershell
+lip install github.com/LiteLDev/LeviLamina@1.0.0
+```
+
+在安装过程中,你可能会被要求确认一些提示。你可以按 `y` 来确认提示。要跳过提示,你可以在命令中添加 `-y` 选项。
+
+```powershell
+lip install -y github.com/LiteLDev/LeviLamina
+```
+
+你现在已经成功安装了 LeviLamina。要升级 LeviLamina,你可以运行以下命令:
+
+```powershell
+lip install --upgrade github.com/LiteLDev/LeviLamina
+```
+
+!!! danger
+ 升级 LeviLamina 可能会导致数据丢失。请确保在升级之前备份你的数据。
+
+### 手动安装(不推荐)
+
+手动安装是开发者在调试新版本时才会使用的安装方式,手动安装只会安装能够让LeviLamina运行的基本组件,缺少CrashLogger和I18N等组件
+
+#### 准备所需文件
+
+1. **下载 LeviLamina**
+ - 访问 LeviLamina 在 GitHub 的 [发布页面](https://github.com/LiteLDev/LeviLamina/releases)。
+ - 选择你需要的版本。
+ - 下载该版本的 `levilamina-windows-x64.zip` 文件。
+
+2. **下载 BDS 服务端**
+ - 前往 [Minecraft 官网](https://www.minecraft.net/zh-hans/download/server/bedrock)。
+ - 下载与 LeviLamina 版本相对应的 Bedrock Dedicated Server (BDS) 版本。文件名为 `bedrock-server-.zip`。
+
+3. **下载其他必要文件**
+ - 访问 PeEditor 和 PreLoader 在 GitHub 的发布页面 ([PeEditor](https://github.com/LiteLDev/PeEditor/releases) 和 [PreLoader](https://github.com/LiteLDev/PreLoader/releases))。
+ - 下载最新版本的 `PeEditor.exe` 和 `PreLoader.dll` 文件。
+
+> **提示**: LeviLamina 的最新版通常与 PeEditor 和 PreLoader 的最新版相匹配。确保你下载的 LeviLamina 版本与 BDS 版本一致。
+
+#### 安装步骤
+
+1. **解压 BDS 文件**
+ - 将你在第二步中下载的 `bedrock-server-.zip` 解压到一个新的、空的文件夹中。
+
+2. **移动 PeEditor 和 PreLoader**
+ - 把 `PeEditor.exe` 和 `PreLoader.dll` 移到解压后的 BDS 文件夹中。
+ - 确保这些文件与 `bedrock_server.exe` 在同一目录下。
+
+3. **添加 LeviLamina 文件**
+ - 解压你在第一步中下载的 `levilamina-windows-x64.zip`。
+ - 从中提取 `levilamina/` 文件夹
+ - 将该文件夹也移动到 `plugins/` 文件夹中,如果没有就手动创建一个。
+
+4. **运行 PeEditor**
+ - 双击 `PeEditor.exe` 并等待其执行完毕。
+ - 执行完毕后,应该在文件夹中看到一个新的 `bedrock_server_mod.exe` 文件。
+ - 原来的 `bedrock_server.exe` 将被重命名为 `bedrock_server.exe.bak`。
+
+完成以上步骤后,LeviLamina 就安装好了。你可以通过双击 `bedrock_server_mod.exe` 来启动服务端。
diff --git a/docs/main/player_guides/troubleshooting.md b/docs/main/player_guides/troubleshooting.md
new file mode 100644
index 0000000000..1302962173
--- /dev/null
+++ b/docs/main/player_guides/troubleshooting.md
@@ -0,0 +1,15 @@
+# Troubleshooting
+
+## Error codes
+
+### 126
+
+Missing dependencies for LeviLamina or mod, ensure LeviLamina or mod is fully installed. You can see missing dependencies in dependency diagnostic.
+
+### 127
+
+The dependencies version of LeviLamina or mod are not correct. You can find out which dependencies have incorrect versions in dependency diagnostic.
+
+### 1114
+
+Vcredist is too old. Updating [vcredist](https://learn.microsoft.com/cpp/windows/latest-supported-vc-redist?view=msvc-170) to solve this error.
diff --git a/docs/main/player_guides/troubleshooting.zh.md b/docs/main/player_guides/troubleshooting.zh.md
new file mode 100644
index 0000000000..3d2743b4db
--- /dev/null
+++ b/docs/main/player_guides/troubleshooting.zh.md
@@ -0,0 +1,15 @@
+# 问题排除
+
+## 常见错误码
+
+### 126
+
+缺少LeviLamina或模组的依赖项,请确保LeviLamina或模组已完全安装。您可以在依赖诊断(Dependency diagnostic)中看到缺少的依赖项。
+
+### 127
+
+LeviLamina或模组的依赖版本不正确。您可以在依赖诊断(Dependency diagnostic)中知道哪些依赖项版本不正确。
+
+### 1114
+
+Vcredist版本太老。更新[vcredist](https://learn.microsoft.com/cpp/windows/latest-supported-vc-redist?view=msvc-170)以解决此错误。
diff --git a/docs/main/quickstart.md b/docs/main/quickstart.md
new file mode 100644
index 0000000000..82367b2be0
--- /dev/null
+++ b/docs/main/quickstart.md
@@ -0,0 +1,110 @@
+# Quickstart
+
+## Before You Begin
+
+We strongly recommend using [lip](https://lip.futrime.com/install/) for mod installation. If you have not installed it locally yet, please go ahead and check it out.
+
+The following content is for a quick start with using lip.
+
+!!! tip
+ Not accustomed to using command-line tools? You can use [LipUI](https://github.com/lippkg/LipUI).
+
+## Installing LeviLamina
+
+First, create a new directory for your Minecraft server and enter it:
+
+```sh
+mkdir myserver
+cd myserver
+```
+
+Then, install LeviLamina bundled with Minecraft Bedrock Server using lip:
+
+```sh
+lip install github.com/LiteLDev/LeviLamina
+```
+
+## Running the Server
+
+To start the server, simply run `bedrock_server_mod.exe`:
+
+```sh
+./bedrock_server_mod.exe
+```
+
+## Updating LeviLamina
+
+Don't update LeviLamina in the same location when it comes to data security. Instead, we recommend creating a new directory, installing the new version of LeviLamina there, and subsequently copying the `worlds` directory from the old location to the new location. Then, follow the instructions provided by the mod developers to migrate the configuration files and data files of the mods you are using to the new directory.
+
+## Finding Mods
+
+Before installing a mod, you'll need to find what you want to install. The best place to find mods is [Bedrinth](https://bedrinth.com), the official mod index, but you can also find many mods elsewhere around the web.
+
+## Installing Mods
+
+For most mods, especially those on Bedrinth, installation is as simple as running `lip install `.
+
+For example, to install [levianticheat](https://github.com/LiteLDev/LeviAntiCheat), you would run:
+
+```sh
+lip install github.com/LiteLDev/LeviAntiCheat
+```
+
+Or install a specific version:
+
+```sh
+lip install github.com/LiteLDev/LeviAntiCheat@1.0.0
+```
+
+However, some mods may require additional steps to install. Follow the instructions provided by the mod developers to install the mods you are using.
+
+## Troubleshooting
+
+If the problem occurred during playing the game, the first step to troubleshooting installing mods is to check the log of your server. Your server's most recent logs will be stored to the `logs/server-latest.log` file. You may need to scroll near the beginning of this file to see when mods were loaded.
+
+Check [FAQ](faq.md) for frequently occurring problems.
+
+If you see something like this:
+
+```plaintext
+ERROR: failed to parse and download specifier string list: failed to install specifier: failed to download from all Go proxies: failed to download from all Go proxies: [failed to download file: cannot download file (HTTP 404 Not Found): https://goproxy.io/github.com/tooth-hub/mymod/@v/v1.1.0.zip]
+```
+
+This means that the version of the mod you tried to install does not exist. You should double-check that you typed the mod name and version correctly.
+
+If you see something like this:
+
+```plaintext
+ERROR: failed to resolve dependencies: installed tooth github.com/tooth-hub/othermod does not match dependency 1.20.41
+```
+
+This means that the version of the mod you tried to install is incompatible with the installed version of another mod, LeviLamina or BDS. Try to install another version of the mod or update the installed mod.
+
+If your network cannot directly access GitHub or GoProxy, you can set up a proxy or mirror:
+
+### Configuring a Proxy or Mirror
+
+If you have a proxy available , use the following command to configure the proxy (supports socks5, http, https):
+
+```sh
+lip config ProxyURL
+```
+
+If you have mistakenly configured a proxy server when you don't have one, you can unconfigure it with the following command:
+
+```sh
+lip config ProxyURL ""
+```
+
+For example, if your proxy address is `http://127.0.0.1:8080`, you can use the following command to configure the proxy:
+
+```sh
+lip config ProxyURL http://127.0.0.1:8080
+```
+
+If you do not have a proxy available locally, you can use the following commands to configure a mirror source:
+
+```sh
+lip config GoModuleProxyURL
+lip config GitHubMirrorURL
+```
diff --git a/docs/main/quickstart.zh.md b/docs/main/quickstart.zh.md
new file mode 100644
index 0000000000..af61337d30
--- /dev/null
+++ b/docs/main/quickstart.zh.md
@@ -0,0 +1,126 @@
+# 快速入门
+
+## 开始之前
+
+我们强烈推荐你使用 [lip](https://lip.futrime.com/zh/install/) 来进行模组安装。如果您尚未在本地安装,请前往查看。
+
+以下内容针对使用 lip 的情况下进行快速入门。
+
+!!! tip
+ 不习惯使用命令行工具?你可以使用[LipUI](https://github.com/lippkg/LipUI)。
+
+## 配置代理或镜像
+
+如果你有**代理服务器**可用,使用以下命令配置代理(支持socks5,http,https):
+
+```sh
+lip config ProxyURL
+```
+
+例如你的**代理服务器**地址是 `http://127.0.0.1:8080`,你可以使用以下命令配置代理:
+
+```sh
+lip config ProxyURL http://127.0.0.1:8080
+```
+
+如果你在没有代理服务器的情况下错误地配置了代理服务器,可以使用以下命令取消配置:
+
+```sh
+lip config ProxyURL ""
+```
+
+如果你本地没有代理可用,你可以使用以下命令配置**镜像源**:
+
+```sh
+lip config GoModuleProxyURL
+lip config GitHubMirrorURL
+```
+
+例如:
+
+```sh
+lip config GoModuleProxyURL https://goproxy.cn
+lip config GitHubMirrorURL https://github.bibk.top
+```
+
+这两个镜像我们已在中国大陆测试过可用,当然你仍然可以使用其他镜像。
+
+## 安装BDS
+
+你可以跳过这一步,因为LeviLamina安装过程中会自动安装BDS。
+
+当你的服务器的IP归属地是中国大陆时,bdsdown会自动为你配置镜像站,当然,你也可以通过定义`BDSDOWN_MIRROR_URL`环境变量来自定义镜像站,例如: `https://mcdl.bibk.top`
+
+你也可以手动在Minecraft官网下载BDS,然后把压缩文件丢到`.cache/bdsdown`目录下,如果该目录不存在请手动创建。
+> 该目录基于您安装BDS的路径,例如`C:\Users\YourName\BDS\.cache\bdsdown`
+
+## 安装 LeviLamina
+
+首先,创建一个新目录用于您的 Minecraft 服务器,并进入该目录:
+
+```sh
+mkdir myserver
+cd myserver
+```
+
+然后,使用 lip 安装捆绑了 Minecraft Bedrock Server 的 LeviLamina:
+
+```sh
+lip install github.com/LiteLDev/LeviLamina
+```
+
+## 运行服务器
+
+要启动服务器,只需运行 `bedrock_server_mod.exe`:
+
+```sh
+./bedrock_server_mod.exe
+```
+
+## 更新 LeviLamina
+
+不要在同一位置更新 LeviLamina,这样做会影响数据安全。相反,我们建议创建一个新目录,在那里安装 LeviLamina 的新版本,并随后将 `worlds` 目录从旧位置复制到新位置。然后,按照模组开发者提供的说明迁移您正在使用的模组的配置文件和数据文件。
+
+## 查找模组
+
+在安装模组之前,您需要找到想要安装的模组。最好的地方是 [Bedrinth](https://bedrinth.com),但您也可以在其他网站上找到许多模组。
+
+## 安装模组
+
+对于大多数模组,特别是在 Bedrinth 上的模组,安装就像运行 `lip install ` 一样简单。
+
+例如,要安装 [levianticheat](https://github.com/LiteLDev/LeviAntiCheat),您可以运行:
+
+```sh
+lip install github.com/LiteLDev/LeviAntiCheat
+```
+
+或安装特定版本:
+
+```sh
+lip install github.com/LiteLDev/LeviAntiCheat@1.0.0
+```
+
+但是,有些模组可能需要额外的安装步骤。请按照模组开发者提供的说明安装您正在使用的模组。
+
+## 故障排除
+
+如果问题发生在游戏过程中,解决安装模组的故障排除的第一步是检查服务器的日志。您服务器的最新日志将存储在 `logs/server-latest.log` 文件中。您可能需要在该文件的开头附近滚动以查看模组何时被加载。
+
+请查看 [FAQ](faq.md) 获取常见问题的答案。
+
+如果您看到类似以下内容:
+
+```plaintext
+ERROR: failed to parse and download specifier string list: failed to install specifier: failed to download from all Go proxies: failed to download from all Go proxies: [failed to download file: cannot download file (HTTP 404 Not Found): https://goproxy.io/github.com/tooth-hub/mymod/@v/v1.1.0.zip]
+```
+
+这意味着您尝试安装的模组版本不存在。您应该仔细检查您输入的模组名称和版本是否正确。
+
+如果您看到类似以下内容:
+
+```plaintext
+ERROR: failed to resolve dependencies: installed tooth github.com/tooth-hub/othermod does not match dependency 1.20.41
+```
+
+这意味着您尝试安装的模组版本与另一个模组、LeviLamina 或 BDS 的已安装版本不兼容。尝试安装模组的另一个版本或更新已安装的模组。
diff --git a/requirements.txt b/docs/main/requirements.txt
similarity index 100%
rename from requirements.txt
rename to docs/main/requirements.txt
diff --git a/docs/quickstart.md b/docs/quickstart.md
deleted file mode 100644
index 9a1be7d2a6..0000000000
--- a/docs/quickstart.md
+++ /dev/null
@@ -1,85 +0,0 @@
-# Quickstart
-
-## Before You Begin
-
-This project uses [lip](https://github.com/lippkg/lip). Go check them out if you don't have them locally installed.
-
-!!! tip
- Not accustomed to using command-line tools? You can use [LipUI](https://github.com/lippkg/LipUI).
-
-## Installing LeviLamina
-
-First, create a new directory for your Minecraft server and enter it:
-
-```sh
-mkdir myserver
-cd myserver
-```
-
-Then, install LeviLamina bundled with Minecraft Bedrock Server using lip:
-
-```sh
-lip install github.com/LiteLDev/LeviLamina
-```
-
-## Running the Server
-
-To start the server, simply run `bedrock_server_mod.exe`:
-
-```sh
-./bedrock_server_mod.exe
-```
-
-## Updating LeviLamina
-
-When it comes to data security, we advise against updating LeviLamina in its current location. Instead, we recommend creating a new directory, installing the new version of LeviLamina there, and subsequently copying the `worlds` directory from the old location to the new one. Next, follow the instructions provided by the plugin developers to migrate the configuration files and data files of the plugins you are using to the new directory.
-
-However, if you insist on updating in the same location, you can utilize the following command to update LeviLamina:
-
-```sh
-lip install --upgrade github.com/LiteLDev/LeviLamina
-```
-
-## Finding Plugins
-
-Before installing a plugin, you'll need to find what you want to install. The best place to find plugins is [lip index](https://www.lippkg.com), lip's official plugin registry, but you can also find many plugins on [our forum](https://bbs.liteldev.com/) and other places around the web.
-
-## Installing Plugins
-
-For most plugins, especially those on lip index, installation is as simple as running `lip install `.
-
-For example, to install [levianticheat](https://github.com/LiteLDev/LeviAntiCheat), you would run:
-
-```sh
-lip install github.com/LiteLDev/LeviAntiCheat
-```
-
-Or install a specific version:
-
-```sh
-lip install github.com/LiteLDev/LeviAntiCheat@1.0.0
-```
-
-However, some plugins may require additional steps to install. Follow the instructions provided by the plugin developers to install the plugins you are using.
-
-## Troubleshooting
-
-If the problem occurred during playing the game, the first step to troubleshooting installing plugins is to check the log of your server. Your server's most recent logs will be stored to the `logs/LeviLamina-latest.log` file. You may need to scroll near the beginning of this file to see when plugins were loaded.
-
-Check [FAQ](faq.md) for frequently occurring problems.
-
-If you see something like this:
-
-```plaintext
-ERROR: failed to parse and download specifier string list: failed to install specifier: failed to download from all Go proxies: failed to download from all Go proxies: [failed to download file: cannot download file (HTTP 404 Not Found): https://goproxy.io/github.com/tooth-hub/myplugin/@v/v1.1.0.zip]
-```
-
-This means that the version of the plugin you tried to install does not exist. You should double-check that you typed the plugin name and version correctly.
-
-If you see something like this:
-
-```plaintext
-ERROR: failed to resolve dependencies: installed tooth github.com/tooth-hub/otherplugin does not match dependency 1.20.41
-```
-
-This means that the version of the plugin you tried to install is incompatible with the installed version of another plugin, LeviLamina or BDS. Try to install another version of the plugin or update the installed plugin.
diff --git a/docs/quickstart.zh.md b/docs/quickstart.zh.md
deleted file mode 100644
index 06324f6f94..0000000000
--- a/docs/quickstart.zh.md
+++ /dev/null
@@ -1,92 +0,0 @@
-# 快速入门
-
-## 开始之前
-
-该项目使用 [lip](https://github.com/lippkg/lip)。如果您尚未在本地安装,请前往查看。
-
-!!! tip
- 不习惯使用命令行工具?你可以使用[LipUI](https://github.com/lippkg/LipUI)。
-
-## 安装 LeviLamina
-
-首先,创建一个新目录用于您的 Minecraft 服务器,并进入该目录:
-
-```sh
-mkdir myserver
-cd myserver
-```
-
-然后,使用 lip 安装捆绑了 Minecraft Bedrock Server 的 LeviLamina:
-
-```sh
-lip install github.com/LiteLDev/LeviLamina
-```
-
-!!! tip
- 网络环境不好?你可以添加GitHub镜像源,以加速下载速度。以下是一个示例:
-
- ```sh
- lip config GitHubMirrorURL https://github.bibk.top
- ```
-
-## 运行服务器
-
-要启动服务器,只需运行 `bedrock_server_mod.exe`:
-
-```sh
-./bedrock_server_mod.exe
-```
-
-## 更新 LeviLamina
-
-在涉及数据安全性时,我们建议不要在当前位置更新 LeviLamina。相反,我们建议创建一个新目录,在那里安装新版本的 LeviLamina,并随后将旧位置的 `worlds` 目录复制到新位置。然后,按照插件开发者提供的说明,迁移您正在使用的插件的配置文件和数据文件到新目录。
-
-但是,如果您坚持要在相同位置进行更新,您可以使用以下命令更新 LeviLamina:
-
-```sh
-lip install --upgrade github.com/LiteLDev/LeviLamina
-```
-
-## 查找插件
-
-在安装插件之前,您需要找到想要安装的插件。最好的地方是 [lip index](https://www.lippkg.com),即 lip 的官方插件注册表,但您也可以在 [我们的论坛](https://bbs.liteldev.com/) 和其他网络上找到许多插件。
-
-## 安装插件
-
-对于大多数插件,特别是在 lip index 上的插件,安装就像运行 `lip install ` 一样简单。
-
-例如,要安装 [levianticheat](https://github.com/LiteLDev/LeviAntiCheat),您可以运行:
-
-```sh
-lip install github.com/LiteLDev/LeviAntiCheat
-```
-
-或安装特定版本:
-
-```sh
-lip install github.com/LiteLDev/LeviAntiCheat@1.0.0
-```
-
-但是,有些插件可能需要额外的安装步骤。请按照插件开发者提供的说明安装您正在使用的插件。
-
-## 故障排除
-
-如果问题发生在游戏过程中,解决安装插件的故障排除的第一步是检查服务器的日志。您服务器的最新日志将存储在 `logs/LeviLamina-latest.log` 文件中。您可能需要在该文件的开头附近滚动以查看插件何时被加载。
-
-请查看 [FAQ](faq.md) 获取常见问题的答案。
-
-如果您看到类似以下内容:
-
-```plaintext
-ERROR: failed to parse and download specifier string list: failed to install specifier: failed to download from all Go proxies: failed to download from all Go proxies: [failed to download file: cannot download file (HTTP 404 Not Found): https://goproxy.io/github.com/tooth-hub/myplugin/@v/v1.1.0.zip]
-```
-
-这意味着您尝试安装的插件版本不存在。您应该仔细检查您输入的插件名称和版本是否正确。
-
-如果您看到类似以下内容:
-
-```plaintext
-ERROR: failed to resolve dependencies: installed tooth github.com/tooth-hub/otherplugin does not match dependency 1.20.41
-```
-
-这意味着您尝试安装的插件版本与另一个插件、LeviLamina 或 BDS 的已安装版本不兼容。尝试安装插件的另一个版本或更新已安装的插件。
diff --git a/docs/tutorials/create_your_first_plugin.md b/docs/tutorials/create_your_first_plugin.md
deleted file mode 100644
index 53a411a5f6..0000000000
--- a/docs/tutorials/create_your_first_plugin.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Create Your First Plugin
-
-_Not translated yet. Please help us!_
diff --git a/docs/tutorials/create_your_first_plugin.zh.md b/docs/tutorials/create_your_first_plugin.zh.md
deleted file mode 100644
index b39290179d..0000000000
--- a/docs/tutorials/create_your_first_plugin.zh.md
+++ /dev/null
@@ -1,719 +0,0 @@
-# 创建你的第一个插件
-
-## 简介
-
-这个教程旨在帮助你开始在LeviLamina中进行插件开发。它绝不是LeviLamina中所有可能性的完整教程,而是基础知识的总体概述。首先确保您了解C++,在 IDE中设置工作区,然后介绍大多数LeviLamina插件的基本知识。
-
-在这个教程中,我们将会创建一个简单的插件,用于实现以下功能:
-
-- 玩家可以输入`/suicide`指令自杀
-- 玩家首次登录服务器时给予一个钟
-- 玩家使用钟时,弹出确认窗口询问是否自杀,如果确认则自杀
-
-这个教程包含以下知识点:
-
-- 日志输出
-- 订阅和退订事件
-- 注册指令
-- 读取配置文件
-- 数据库存取
-- 使用表单
-- 构造Minecraft对象
-- 调用Minecraft函数
-
-!!! info
- 本教程的所有源码可以在[futrime/better-suicide](https://github.com/futrime/better-suicide)找到。我们建议你一边看源码一边看教程。如果你已经安装了[lip](https://docs.lippkg.com),你还可以直接运行以下代码在LeviLamina实例环境中安装本教程中实现的插件。
-
- ```shell
- lip install github.com/futrime/better-suicide
- ```
-
-## 学习C++
-
-这些教程需要C++编程语言的基础知识。如果您刚刚开始使用C++或需要复习一下,以下是一个非详尽的列表。
-
-- [C++ Developer Roadmap](https://roadmap.sh/cpp)
-- [cppreference.com](https://en.cppreference.com/w/)
-- [C++ Tutorial](https://www.w3schools.com/cpp/)
-- [C++ Language Tutorial](https://cplusplus.com/doc/tutorial/)
-- [hacking C++](https://hackingcpp.com/)
-- [C++ Core Guidelines](http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines)
-
-## 设置工作区
-
-在开发插件(或学习C++)之前,您需要设置一个开发环境。这包括但不限于以下内容:
-
-- [xmake](https://xmake.io)
-- [Visual Studio Code](https://code.visualstudio.com)
-- [Git](https://git-scm.com)
-- [Visual Studio 2022](https://visualstudio.microsoft.com/) (安装Visual Studio 2022时,请确保勾选了C++桌面应用开发这一项)
-
-!!! warning
- 如果你安装的不是最新版本的Visual Studio 2022、MSVC和Windows SDK,则后续在构建、加载、运行插件中有可能遇到问题。如果你遇到了类似`xxx is not a member of std`这样的问题,请考虑这个可能性。本教程测试构建的环境是Visual Studio Community 2022 17.8.1、MSVC v143 - VS 2022 C++ x64/x86 build tools (v14.38-17.8)、Windows 11 SDK (10.0.22000.0)
-
-!!! tip
- 由于LeviLamina项目极大,如果你使用Visual Studio Code,其自带的Intellisense系统可能不堪重负。我们建议你安装clangd插件并使用clangd进行代码检查等。安装clangd和对应的插件后,你需要运行以下命令生成`compile_commands.json`,然后重启VSCode以使clangd生效。
-
- ```shell
- xmake project -k compile_commands
- ```
-
-然后,你需要在某处安装LeviLamina。本教程针对的是LeviLamina 0.6.3,对于其它版本,可能需要做一些修改。
-
-## 创建插件仓库
-
-访问[levilamina-plugin-template](https://github.com/futrime/levilamina-plugin-template),点击`Use this template`以使用这个模板初始化你的插件仓库。
-
-![Create from template](img/levilamina-plugin-template.png)
-
-将插件仓库使用Git克隆到本地,然后使用VSCode打开。你需要修改其中的一些文件,填写你的插件信息。
-
-首先,你需要修改`xmake.lua`中插件名字信息。修改插件名字是为了指定你的插件的名字,这个名字将会在LeviLamina中显示。名字允许英文大小写、数字、中划线,不允许包括空格和其他特殊字符,建议采用`example-plugin`或`ExamplePlugin`这两种形式。在这里,我们的插件命名为`better-suicide`。
-
-```lua
-target("better-suicide") -- Change this to your plugin name.
-```
-
-接着,修改`tooth.json`的内容。`tooth.json`为lip安装插件包提供了相关信息,正确配置后,你的插件将会被[lip Index](https://lippkg.com)收录,并能被全世界的用户下载安装。将`tooth`字段的值改为这个插件的GitHub仓库地址,填写`info`中各个信息字段,然后根据仓库release地址填写`asset_url`字段,修改依赖的LeviLamina版本,并根据在`xmake.lua`中填写的插件名修改`place`的`src`和`dest`。对于本文的插件,以下是一个可行的参考:
-
-```json
-{
- "format_version": 2,
- "tooth": "github.com/futrime/better-suicide",
- "version": "0.5.0",
- "info": {
- "name": "better-suicide",
- "description": "Allow players to suicide in Minecraft.",
- "author": "futrime",
- "tags": [
- "levilamina",
- "plugin"
- ]
- },
- "asset_url": "https://github.com/futrime/better-suicide/releases/download/v0.5.0/better-suicide-windows-x64.zip",
- "prerequisites": {
- "github.com/LiteLDev/LeviLamina": "0.6.x"
- },
- "files": {
- "place": [
- {
- "src": "better-suicide/*",
- "dest": "plugins/better-suicide/"
- }
- ]
- }
-}
-```
-
-然后,你需要修改`LICENSE`文件中的版权信息。你可以在[这里](https://choosealicense.com/licenses/)选择一个适合你的插件的开源协议。请放心,你的插件不需要开源,因为插件模板使用了CC0协议,你可以随意修改或删除`LICENSE`文件。但是,我们建议你使用一个开源协议,因为这样可以让其他人更容易地使用你的插件和帮助你改进你的插件。
-
-接下来,你需要修改`README.md`文件中的内容。这个文件将会在你的插件仓库主页显示,你可以在这里介绍你的插件的功能、使用方法、配置文件、指令等等。
-
-最后,你需要修改目录名和命名空间名。将`rename_this`目录改成你喜欢的名字,并将`Entry.cpp`和`Entry.h`中命名空间`rename_this`改成相同的名字。按照C++常见惯例,目录名和命名空间名应当使用小写字母和下划线,且应当保持一致。这里,我们统一改成`better_suicide`。
-
-## 构建你的插件
-
-在一切开始之前,先让我们尝试构建一下空的插件。
-
-先更新一下仓库:
-
-```shell
-xmake repo -u
-```
-
-配置构建:
-
-```shell
-xmake f -m debug
-```
-
-!!! tip
- 如果你想以其它模式构建,也可以使用`-m release`或`-m releasedbg`。这两个模式会开启`fastest`优化等级。其中,`-m release`会关闭调试信息,而`-m releasedbg`会开启调试信息,就像`-m debug`一样。对于它们的具体区别,请参考[自定义规则 - xmake](https://xmake.io/#/zh-cn/manual/custom_rule)。
-
-!!! failure
- 如果你在更新仓库或配置构建过程中,出现了下载失败的情况,那么可能需要[配置GitHub镜像代理](https://xmake.io/#/zh-cn/package/remote_package?id=%e9%95%9c%e5%83%8f%e4%bb%a3%e7%90%86):
-
- ```shell
- xmake g --proxy_pac=github_mirror.lua
- ```
-
- 或者[配置HTTP代理](https://xmake.io/#/zh-cn/package/remote_package?id=%e8%ae%be%e7%bd%ae%e4%bb%a3%e7%90%86):
-
-
-然后构建:
-
-```shell
-xmake
-```
-
-!!! failure
- 构建失败了?尝试升级一下Visual Studio 2022、MSVC和Windows SDK吧。记住,一定要升级到最新版本。
-
-## 补充`#include`
-
-在`Entry.cpp`中补充`#include`,最终效果看起来是这样的:
-
-```cpp
-#include "Entry.h"
-
-#include "Config.h"
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-```
-
-## 注册指令`/suicide`
-
-在BDS中,指令并不是一开始就能够注册的,而是需要在特定的程序执行之后才能注册。因此,你不能在插件加载时注册插件,而只能在插件启用时注册指令。一般来说,还应当在插件禁用时解注册指令,以防止出现未定义行为。
-
-!!! warning
- 插件在加载时,会调用其构造函数。但请不要将事件订阅、指令注册等任何与游戏相关的操作放在构造函数中,因为这些操作需要在游戏加载完成后才能进行。如果你在构造函数中进行了这些操作,那么你的插件将很有可能会在加载时崩溃。
-
-!!! tip
- 一般来说,插件的构造函数中只需要进行一些与游戏无关初始化操作即可,例如初始化日志系统、初始化配置文件、初始化数据库等等。
-
-```cpp
-auto enable(ll::plugin::NativePlugin& /*self*/) -> bool {
-
- // ...
-
- // Register commands.
- auto commandRegistry = ll::service::getCommandRegistry();
- if (!commandRegistry) {
- throw std::runtime_error("failed to get command registry");
- }
-
- auto command =
- DynamicCommand::createCommand(commandRegistry, "suicide", "Commits suicide.", CommandPermissionLevel::Any);
- command->addOverload();
- command->setCallback(
- [&logger](DynamicCommand const&, CommandOrigin const& origin, CommandOutput& output, std::unordered_map&) {
- auto* entity = origin.getEntity();
- if (entity == nullptr || !entity->isType(ActorType::Player)) {
- output.error("Only players can commit suicide");
- return;
- }
-
- auto* player = static_cast(entity); // NOLINT(cppcoreguidelines-pro-type-static-cast-downcast)
- player->kill();
-
- logger.info("{} killed themselves", player->getRealName());
- }
- );
- DynamicCommand::setup(commandRegistry, std::move(command));
-
- // ...
-
- return true;
-}
-
-auto disable(ll::plugin::NativePlugin& /*self*/) -> bool {
-
- // ...
-
- // Unregister commands.
- auto commandRegistry = ll::service::getCommandRegistry();
- if (!commandRegistry) {
- throw std::runtime_error("failed to get command registry");
- }
-
- commandRegistry->unregisterCommand("suicide");
-
- // ...
-
- return true;
-}
-```
-
-让我们将这些代码拆开来看。下列语句获取指令注册表。指令注册表只有在特定时机之后才会生效,因此其类型为`optional_ref`。我们需要判定获取到的指令注册表是否有效。
-
-```cpp
-auto commandRegistry = ll::service::getCommandRegistry();
-if (!commandRegistry) {
- throw std::runtime_error("failed to get command registry");
-}
-```
-
-动态指令系统支持使用`DynamicCommand::createCommand()`函数直接注册指令。
-
-```cpp
-auto command =
- DynamicCommand::createCommand(commandRegistry, "suicide", "Commits suicide.", CommandPermissionLevel::Any);
-```
-
-其中,第二个参数是指令本身,即在控制台或聊天栏内输入的字符。虽然尚未测试各种特殊字符能否生效,但我们仍然建议只包含小写英文字母。第三个参数是指令简介,在聊天栏输入指令的一部分时,会在上方以半透明灰色的形式显示候选指令及其简介。第四个参数是指令的权限等级,其定义如下。其中,如果我们希望生存模式下的普通玩家也能执行,应当选择`Any`。而`GameDirectors`对应至少为创造模式的玩家的权限,`Admin`对应至少为OP的权限,`Host`对应控制台的权限。
-
-```cpp
-enum class CommandPermissionLevel : schar {
- Any = 0x0,
- GameDirectors = 0x1,
- Admin = 0x2,
- Host = 0x3,
- Owner = 0x4,
- Internal = 0x5,
-};
-```
-
-然后,我们需要为指令增加一个重载并设置对应的回调。
-
-```cpp
-command->addOverload();
-command->setCallback([&logger](DynamicCommand const&,
- CommandOrigin const& origin,
- CommandOutput& output,
- std::unordered_map&) {
- // ...
-});
-```
-
-!!! note
- 指令的重载意味着指令的一个模式,例如`dyncmd ` 是一个重载,而`dyncmd list`是另一个重载。下面是一个例子,来自LeviLamina的测试样例:
-
- ```cpp
- auto command =
- DynamicCommand::createCommand(registry, "testcmd", "dynamic command", CommandPermissionLevel::GameDirectors);
-
- auto& optionsAdd = command->setEnum("TestOperation1", {"add", "remove"});
- auto& optionsList = command->setEnum("TestOperation2", {"list"});
-
- command->mandatory("testEnum", ParamType::Enum, optionsAdd, CommandParameterOption::EnumAutocompleteExpansion);
- command->mandatory("testEnum", ParamType::Enum, optionsList, CommandParameterOption::EnumAutocompleteExpansion);
- command->mandatory("testString", ParamType::String);
-
- command->addOverload({optionsAdd, "testString"}); // dyncmd
- command->addOverload({"TestOperation2"}); // dyncmd
- ```
-
-在回调函数中,我们首先尝试获取指令的执行来源。在这里,我们需要进行一个判定,因为控制台、命令方块乃至各种实体都能够执行指令,但自杀插件应当只响应玩家的请求。如果错误的执行来源执行了自杀指令,那么应当提示一个错误信息。
-
-```cpp
-auto* entity = origin.getEntity();
-if (entity == nullptr || !entity->isType(ActorType::Player)) {
- output.error("Only players can commit suicide");
- return;
-}
-```
-
-当我们确认了执行来源为玩家后,我们就可以将实体指针转换为玩家指针,并杀死之。
-
-```cpp
-auto* player = static_cast(entity);
-player->kill();
-
-logger.info("{} killed themselves", player->getRealName());
-```
-
-!!! warning
- 由于BDS缺乏RTTI信息,因此不能够使用`dynamic_cast()`。
-
-!!! tip
- 你可能注意到另一个函数`player->getName()`,但我们并没有使用它。这是因为玩家的名字是可以通过插件或其它方式进行修改的,而`player->getRealName()`的结果则是(一般来说较为)固定的。
-
-到这一步,指令对象已经配置完毕,我们调用`DynamicCommand::setup()`,将指令对象加载到游戏中。注意这里需要使用`std::move()`,因为其接收一个右值引用。
-
-```cpp
-DynamicCommand::setup(event.registry(), std::move(command));
-```
-
-在`enable()`函数的末尾,返回一个`true`,代表插件启用成功。如果在`enable()`函数中返回了`false`,则LeviLamina会认为插件启用失败,并在控制台上提示错误信息。
-
-在`disable()`函数中,我们需要解注册指令:
-
-```cpp
-// Unregister commands.
-auto commandRegistry = ll::service::getCommandRegistry();
-if (!commandRegistry) {
- throw std::runtime_error("failed to get command registry");
-}
-
-commandRegistry->unregisterCommand("suicide");
-```
-
-## 读取配置文件
-
-我们的插件的第二个功能是玩家首次进入服务器时,给予一个钟;第三个功能是使用钟的时候,弹出确认自杀的提示,玩家确认后可以自杀。但这两个功能有个小问题:服务器管理员可能已经安装了其它的插件,实现了类似的功能,而不希望使用这个自杀插件中这几个功能。我们希望能提供某种方式,允许管理员开关这两个功能。
-
-我们在此非常高兴地宣布,LeviLamina在C++中,实现了配置文件与配置信息结构体的反射。这意味着,我们可以在C++中定义一个结构体,然后在配置文件中定义这个结构体的实例,LeviLamina会自动将配置文件中的内容读取到结构体实例中。这样,我们就可以在C++中直接使用这个结构体实例,而不需要自己去解析配置文件。
-
-首先,我们另外创建一个`Config.h`文件,定义一个结构体`Config`,用于保存配置信息。
-
-```cpp
-struct Config {
- int version = 1;
- bool doGiveClockOnFirstJoin = true;
- bool enableClockMenu = true;
-};
-```
-
-我们在匿名命名空间中增加一个成员变量,用于保存配置文件中的配置信息。
-
-```cpp
-namespace {
-
-// ...
-
-Config config;
-
-}
-```
-
-然后,我们读取配置文件并将配置信息保存到成员变量中。
-
-```cpp
-auto load(ll::plugin::NativePlugin& self) -> bool {
-
- // ...
-
- // Load or initialize configurations.
- const auto& configFilePath = self.getConfigDir() / "config.json";
- if (!ll::config::loadConfig(config, configFilePath)) {
- logger.warn("Cannot load configurations from {}", configFilePath);
- logger.info("Saving default configurations");
-
- if (!ll::config::saveConfig(config, configFilePath)) {
- logger.error("Cannot save default configurations to {}", configFilePath);
- }
- }
-
- // ...
-
-}
-```
-
-在这段代码中,我们首先获取插件的配置文件路径,然后调用`ll::config::loadConfig()`函数,将配置文件中的配置信息读取到结构体实例中。如果读取失败,我们将会在控制台上输出警告信息,并将默认配置信息保存到配置文件中。
-
-!!! note
- 由于配置文件读取是在构造函数内进行的,所以在后续操作中可以保证配置文件已经读取成功了。
-
-## 将玩家进服信息持久化保存在数据库中
-
-我们的插件的第二个功能是玩家首次进入服务器时,给予一个钟。但是,如果我们将进服信息保存在内存中,那么当服务器重启后,玩家的进服信息就会丢失。因此,我们需要将玩家的进服信息持久化保存在数据库中。LeviLamina提供了KV数据库的封装,可以让我们在C++中直接使用数据库。
-
-首先,我们在匿名命名空间中增加一个成员变量,用于保存数据库实例。
-
-```cpp
-std::unique_ptr playerDb;
-```
-
-!!! note
- 为什么是`std::unique_ptr`而不是`ll::KeyValueDB`?这是因为`ll::KeyValueDB`禁止拷贝,只能移动。因此,我们需要使用`std::unique_ptr`来保存`ll::KeyValueDB`的实例。
-
-!!! warning
- 请不要使用普通的指针来保存`ll::KeyValueDB`的实例,因为这样很容易使得生命周期管理变得复杂,从而导致内存泄漏和其他问题。请记住:你在写C++,而不是C。
-
-然后,我们在`load`函数中,初始化数据库实例。
-
-```cpp
-auto load(ll::plugin::NativePlugin& self) -> bool {
-
- // ...
-
- // Initialize databases;
- const auto& playerDbPath = self.getDataDir() / "players";
- playerDb = std::make_unique(playerDbPath);
-
- // ...
-}
-```
-
-在这段代码中,我们首先获取插件的数据库路径,然后调用`std::make_unique()`函数,创建一个数据库实例。如果数据库路径不存在,那么`std::make_unique()`函数会自动创建数据库路径。
-
-!!! note
- 由于数据库初始化是在构造函数内进行的,所以在后续操作中可以保证数据库已经初始化成功了。
-
-## 玩家首次进服时,给予一个钟
-
-我们的插件的第二个功能是玩家首次进入服务器时,给予一个钟。我们需要在玩家进服时,判断玩家是否首次进服,如果是,则给予一个钟。
-
-在BDS中,玩家进服时,会触发事件`PlayerJoinEvent`。在LeviLamina中,我们可以订阅这个事件,当这个事件被触发时,插件可以在这里实现玩家进服时的逻辑。
-
-在匿名命名空间中,我们增加一个事件监听器指针:
-
-```cpp
-ll::event::ListenerPtr playerJoinEventListener;
-```
-
-在`enable()`函数中注册这个事件监听器,并在`disable()`函数中取消注册。
-
-```cpp
-auto enable(ll::plugin::NativePlugin& /*self*/) -> bool {
-
- // ...
-
- auto& eventBus = ll::event::EventBus::getInstance();
-
- playerJoinEventListener = eventBus.emplaceListener(
- [doGiveClockOnFirstJoin = config.doGiveClockOnFirstJoin,
- &logger,
- &playerDb = playerDb](ll::event::player::PlayerJoinEvent& event) {
- if (doGiveClockOnFirstJoin) {
- auto& player = event.self();
-
- const auto& uuid = player.getUuid();
-
- // Check if the player has joined before.
- if (!playerDb->get(uuid.asString())) {
-
- ItemStack itemStack("clock", 1);
- player.add(itemStack);
-
- // Must refresh inventory to see the clock.
- player.refreshInventory();
-
- // Mark the player as joined.
- if (!playerDb->set(uuid.asString(), "true")) {
- logger.error("Cannot mark {} as joined in database", player.getRealName());
- }
-
- logger.info("First join of {}! Giving them a clock", player.getRealName());
- }
- }
- }
- );
-
- // ...
-
-}
-
-auto disable(ll::plugin::NativePlugin& /*self*/) -> bool {
-
- // ...
-
- auto& eventBus = ll::event::EventBus::getInstance();
-
- eventBus.removeListener(playerJoinEventListener);
-
- // ...
-
-}
-```
-
-让我们将这些代码拆开来看。在回调lambda函数中,我们捕获了配置中的`doGiveClockOnFirstJoin`,以及插件的logger和数据库实例。然后,我们判断配置中的`doGiveClockOnFirstJoin`是否为`true`,如果是,则继续执行逻辑。
-
-```cpp
-[doGiveClockOnFirstJoin = config.doGiveClockOnFirstJoin,
- &logger,
- &playerDb = playerDb](ll::event::player::PlayerJoinEvent& event) {
- if (doGiveClockOnFirstJoin) {
- // ...
- }
-}
-```
-
-接下来,我们获取事件实例中的玩家实例和玩家的UUID。
-
-```cpp
-auto& player = event.self();
-auto& uuid = player.getUuid();
-```
-
-!!! note
- 这里获取的UUID的类型是`mce::UUID`而不是`std::string`。我们建议只有在需要时才将UUID转换为`std::string`,因为`mce::UUID`的实现更加高效。
-
-!!! danger
- 请不要使用XUID作为玩家的唯一标识符。虽然在LiteLoaderBDS时代,不少插件使用XUID作为玩家的唯一标识符,但这是不正确的。XUID是Xbox Live的标识符,而不是玩家的标识符。如果服务器没有开启在线模式,或者存在假人,那么XUID的行为将是不可预测的。因此,我们强烈建议使用UUID作为玩家的唯一标识符。
-
-然后,我们使用玩家的UUID作为键,从数据库中获取玩家是否已经进服过。如果玩家已经进服过,那么我们就不需要再给予玩家一个钟了。
-
-```cpp
-// Check if the player has joined before.
-if (!playerDb->get(uuid.asString())) {
- // ...
-}
-```
-
-接下来,我们创建一个钟的物品栈,并将这个物品栈添加到玩家的背包中。
-
-```cpp
-ItemStack itemStack("clock", 1);
-player.add(itemStack);
-```
-
-!!! note
- 这里使用了`ItemStack`类,而不是`Item`类。`ItemStack`类是`Item`类的一个包装,它包含了物品的数量、附魔、耐久等信息,而`Item`类仅仅代表这个物品类别。因此应当使用`ItemStack`类而不是`Item`类。
-
-然后,我们需要刷新玩家的背包,以便玩家能够看到钟。
-
-```cpp
-player.refreshInventory();
-```
-
-最后,我们将玩家的UUID作为键,将玩家标记为已经进服过。
-
-```cpp
-// Mark the player as joined.
-if (!playerDb->set(uuid.asString(), "true")) {
- logger.error("Cannot mark {} as joined in database", player.getRealName());
-}
-```
-
-在`disable()`函数中,我们需要在事件总线上移除事件监听器以取消对事件的订阅。
-
-```cpp
-eventBus.removeListener(playerJoinEventListener);
-```
-
-## 使用钟的时候,弹出确认自杀的提示
-
-我们的插件的第三个功能是使用钟的时候,弹出确认自杀的提示,玩家确认后可以自杀。我们需要订阅玩家使用物品的事件,当玩家使用钟时,弹出确认自杀的提示。
-
-在匿名命名空间中,我们增加一个事件监听器指针:
-
-```cpp
-ll::event::ListenerPtr playerUseItemEventListener;
-```
-
-在`enable()`函数中注册这个事件监听器,并在`disable()`函数中取消注册。
-
-```cpp
-auto enable(ll::plugin::NativePlugin& /*self*/) -> bool {
-
- // ...
-
- playerUseItemEventListener =
- eventBus.emplaceListener([enableClockMenu = config.enableClockMenu,
- &logger](ll::event::PlayerUseItemEvent& event) {
- if (enableClockMenu) {
- auto& player = event.self();
- auto& itemStack = event.item();
-
- if (itemStack.getRawNameId() == "clock") {
- ll::form::ModalForm form(
- "Warning",
- "Are you sure you want to kill yourself?",
- "Yes",
- "No",
- [&logger](Player& player, bool yes) {
- if (yes) {
- player.kill();
-
- logger.info("{} killed themselves", player.getRealName());
- }
- }
- );
-
- form.sendTo(player);
- }
- }
- });
-
- // ...
-
-}
-
-auto disable(ll::plugin::NativePlugin& /*self*/) -> bool {
-
- // ...
-
- eventBus.removeListener(playerUseItemEventListener);
-
- // ...
-
-}
-```
-
-让我们将代码拆开来看。在回调lambda函数中,我们捕获了配置项`enableClockMenu`和logger,然后进行判断,只有配置项启用时,才执行逻辑。
-
-```cpp
-playerUseItemEventListener = eventBus.emplaceListener(
- [enableClockMenu = config.enableClockMenu, &logger](ll::event::PlayerUseItemEvent& event) {
- if (enableClockMenu) {
- // ...
- }
- }
-);
-```
-
-在逻辑中,我们首先获取该事件的两个属性,即使用物品的玩家和被使用的物品。然后判断物品id是否为`clock`,并执行弹出表单的逻辑。
-
-```cpp
-auto& player = event.self();
-auto& itemStack = event.item();
-
-if (itemStack.getRawNameId() == "clock") {
- // ...
-}
-```
-
-!!! warning
- 不要使用`itemStack.getName()`,因为这个函数返回的是物品显示的名字,比如`Clock`或`Iron Sword`。
-
-在这里我们使用了最简单的模态表单`ModalForm`,其构造函数的第一个参数是表单的标题,第二个参数是表单提示内容,第三个参数是左下角按钮内容,第四个参数是右下角按钮内容。回调函数接收两个参数,第一个参数是表单发送向的玩家,第二个参数是玩家的选择,`true`代表选择了左下角按钮。
-
-```cpp
-ll::form::ModalForm form(
- "Warning",
- "Are you sure you want to kill yourself?",
- "Yes",
- "No",
- [&logger](Player& player, bool yes) {
- if (yes) {
- player.kill();
-
- logger.info("{} killed themselves", player.getRealName());
- }
- }
-);
-```
-
-接下来将表单发送给玩家即可。
-
-```cpp
-form.sendTo(player);
-```
-
-## 运行你的插件
-
-如果你的插件正常构建完毕,你应该能看到`bin/`目录内有一个以你的插件名为名的目录。将这个目录拷贝到LeviLamina目录中的`plugins/`目录里面(如果没有,请创建),得到如下的文件结构:
-
-```text
-/path/to/levilamina/plugins/better-suicide
-├── better-suicide.dll
-└── manifest.json
-```
-
-然后运行LeviLamina服务器(`bedrock_server_mod.exe`)即可。
-
-## 下一步?
-
-你可以[公开发布你的插件](./publish_your_first_plugin.zh.md),让更多的人使用你的插件。
-
-## 更进一步的练习
-
-我们可以在这个插件的基础上,增加一些功能,来练习LeviLamina插件开发的更多知识。下面是一些可能的练习:
-
-- 设置玩家自杀的冷却时间
-- 让玩家自杀时,保留所有物品不掉落
-- 让玩家自杀时,保留经验
-- 让玩家自杀时,在原地重生
-- 统计玩家自杀次数,并在侧边栏显示排行榜
-- 使用更高级的表单,让玩家选择自杀的方式
-- 让玩家自杀时,显示一个自定义的死亡信息
-
-以下是你可能需要的一些参考资料:
-
-- [事件指南](../guides/event_guide.zh.md)
-- [接口导出指南](../guides/export_interface_guide.zh.md)
-- [表单指南](../guides/form_guide.zh.md)
-- [Hook指南](../guides/hook_guide.zh.md)
-- [找函数指南](../guides/find_function_guide.zh.md)
diff --git a/docs/tutorials/publish_your_first_plugin.md b/docs/tutorials/publish_your_first_plugin.md
deleted file mode 100644
index 2bc8af5f47..0000000000
--- a/docs/tutorials/publish_your_first_plugin.md
+++ /dev/null
@@ -1,80 +0,0 @@
-# Publish Your First Plugin
-
-In the tutorial [Create Your First Plugin](create_your_first_plugin.md), we created a simple plugin. In this tutorial, we will learn how to publish a plugin.
-
-### Prerequisites
-
-First, make sure you have installed [lip](https://github.com/lippkg/lip).
-
-You should also have followed the steps in [Create Your First Plugin](create_your_first_plugin.md) to create a plugin, where the exported plugin directory has a structure similar to the following:
-
-```
-.
-└── plugin-name
- ├── manifest.json
- ├── plugin-name.dll
- └── plugin-name.pdb
-```
-
-Here, `plugin-name` is the name of the plugin.
-
-### Create a GitHub Repository
-
-First, you need to create a GitHub repository to store your plugin. You can use any name you like, such as `plugin-name`.
-
-In the repository, we recommend including a `README.md` file to describe the plugin and a `logo.png` file to serve as the plugin's icon.
-
-### Create a tooth.json
-
-Create a `tooth.json` file in the exported plugin directory with the following content:
-
-```json
-{
- "format_version": 2,
- "tooth": "github.com/my-github-username/plugin-name",
- "version": "0.1.0",
- "info": {
- "name": "MyPlugin",
- "description": "MyPlugin is a great plugin!",
- "author": "My Name",
- "source": "github.com/my-github-username/my-source-code",
- "tags": [
- "plugin",
- "ll"
- ]
- },
- "dependencies": {
- "github.com/tooth-hub/another-plugin": "2.0.x"
- },
- "prerequisites": {
- "github.com/LiteLDev/LeviLamina": "1.0.x"
- },
- "files": {
- "place": [
- {
- "src": "plugin-name/*",
- "dest": "plugins/plugin-name"
- }
- ]
- }
-}
-```
-
-Replace the value of the `tooth` field with the GitHub repository address of your plugin, replace the value of the `version` field with the version number of your plugin, fill in the values of the fields in the `info` section, and fill in the values of the fields in the `dependencies` and `prerequisites` sections.
-
-!!! note
- The `dependencies` section automatically installs the required plugins when installing the plugin and uninstalls them when uninstalling the plugin. However, the `prerequisites` section does not automatically install the required plugins; instead, it throws an error if the dependencies are missing. Generally, the plugins listed in the `prerequisites` section should be fundamental and framework-level packages, such as `github.com/LiteLDev/LeviLamina`, to avoid accidentally uninstalling them during the plugin uninstallation process.
-
-For more information about `tooth.json`, please refer to .
-
-### Try Packing and Installing the Plugin
-
-In the exported plugin directory, run `lip tooth pack plugin.tth`. This will generate a `plugin.tth` file in the current directory, which is a packaged plugin. You can move this plugin to a suitable location and try installing it using `lip install plugin.tth`.
-
-### Publish the Plugin
-
-Commit your changes to the GitHub repository, then click on "Releases" in the repository on GitHub, click on "Create a new release," fill in the "Tag version" and "Release title" fields, and then click on "Publish release" to publish the plugin. Note that the corresponding tag must be in a format similar to `v0.1.0`, which means it should be the value of the `version` field in `tooth.json` prefixed with a `v`.
-
-You can then install your plugin using `lip install github.com/my-github-username/plugin-name`. Due to synchronization delays in the version listing, this command may throw an error in the initial period after the release. In such cases, you can specify the version number to install the plugin, for example, `lip install github.com/my-github-username/plugin-name@0.1.0`.
-
-After some time, you will also be able to see your plugin in LipUI and .
diff --git a/docs/tutorials/publish_your_first_plugin.zh.md b/docs/tutorials/publish_your_first_plugin.zh.md
deleted file mode 100644
index de07c3663c..0000000000
--- a/docs/tutorials/publish_your_first_plugin.zh.md
+++ /dev/null
@@ -1,80 +0,0 @@
-# 发布你的第一个插件
-
-在教程[创建你的第一个插件](create_your_first_plugin.md)中,我们创建了一个简单的插件。在本教程中,我们将学习如何发布插件。
-
-### 前置条件
-
-首先,你应当确保你安装了[lip](https://github.com/lippkg/lip)。
-
-你还应当按照[创建你的第一个插件](create_your_first_plugin.md)中的步骤创建了一个插件,其中插件导出目录有类似如下的结构:
-
-```
-.
-└── plugin-name
- ├── manifest.json
- ├── plugin-name.dll
- └── plugin-name.pdb
-```
-
-其中,`plugin-name`是插件名。
-
-### 创建一个GitHub仓库
-
-首先,你需要创建一个GitHub仓库,用于存放你的插件。你可以使用任何你喜欢的名称,例如`plugin-name`。
-
-在仓库中,我们建议放一个`README.md`文件,用于描述插件;并放一个`logo.png`文件,用于作为插件的图标。
-
-### 创建一个tooth.json
-
-在插件导出目录中创建一个`tooth.json`文件,内容如下:
-
-```json
-{
- "format_version": 2,
- "tooth": "github.com/my-github-username/plugin-name",
- "version": "0.1.0",
- "info": {
- "name": "MyPlugin",
- "description": "MyPlugin is a great plugin!",
- "source": "github.com/my-github-username/my-source-code",
- "author": "My Name",
- "tags": [
- "plugin",
- "ll"
- ]
- },
- "dependencies": {
- "github.com/tooth-hub/another-plugin": "2.0.x"
- },
- "prerequisites": {
- "github.com/LiteLDev/LeviLamina": "1.0.x"
- },
- "files": {
- "place": [
- {
- "src": "plugin-name/*",
- "dest": "plugins/plugin-name"
- }
- ]
- }
-}
-```
-
-替换`tooth`字段的值为你的插件的GitHub仓库地址,替换`version`字段的值为你的插件的版本号,填写`info`中各个字段的值,填写`dependencies`和`prerequisites`中各个字段的值。
-
-!!! note
- `dependencies`在安装插件时,会自动安装依赖的插件,在卸载插件时,会自动卸载依赖的插件。但是`prerequisites`不会自动安装,而是在缺少依赖时报错。一般来说,`prerequisites`中的插件应当是一些基础性的、框架级别的包,例如`github.com/LiteLDev/LeviLamina`,以避免插件的卸载过程中误卸载了这些包。
-
-更多关于`tooth.json`的信息,请参考。
-
-### 尝试打包和安装插件
-
-在插件导出目录中运行`lip tooth pack plugin.tth`,将会在当前目录下生成一个`plugin.tth`文件,这是一个打包好的插件。你可以移动这个插件到合适的地方,并使用`lip install plugin.tth`尝试安装这个插件。
-
-### 发布插件
-
-将更改提交到GitHub仓库,然后在GitHub仓库中点击`Releases`,点击`Create a new release`,填写`Tag version`和`Release title`,然后点击`Publish release`,即可发布插件。注意对应的tag必须为类似`v0.1.0`的格式,也就是`tooth.json`中`version`字段的值加上一个`v`。
-
-然后你就可以通过`lip install github.com/my-github-username/plugin-name`来安装你的插件了。由于版本列表同步延迟,这个命令可能会在刚发布的一段时间内报错,你可以指定版本号来安装插件,例如`lip install github.com/my-github-username/plugin-name@0.1.0`。
-
-在一段时间后,你也可以在LipUI和查看到你的插件了。
diff --git a/manifest.json b/manifest.json
new file mode 100644
index 0000000000..7ea95c5517
--- /dev/null
+++ b/manifest.json
@@ -0,0 +1,6 @@
+{
+ "name": "${modName}",
+ "entry": "${modFile}",
+ "version": "${modVersion}",
+ "type": "preload-native"
+}
\ No newline at end of file
diff --git a/mkdocs.yml b/mkdocs.yml
deleted file mode 100644
index 435b354ef4..0000000000
--- a/mkdocs.yml
+++ /dev/null
@@ -1,110 +0,0 @@
-site_name: LeviLamina
-repo_url: https://github.com/LiteLDev/LeviLamina
-
-nav:
- - Home: index.md
-
- - quickstart.md
-
- - Install:
- - install.md
- - Install on Docker: https://github.com/LiteLDev/levilamina-docker-server
-
- - faq.md
-
- - Tutorials:
- - tutorials/create_your_first_plugin.md
- - tutorials/publish_your_first_plugin.md
- - tutorials/publish_your_first_pack.md
-
- - Guides:
- - guides/event_guide.md
- - guides/export_interface_guide.md
- - guides/find_function_guide.md
- - guides/form_guide.md
- - guides/hook_guide.md
- - guides/i18n_guide.md
- - guides/item_guide.md
- - guides/permission_guide.md
-
- - APIs: https://levilamina.liteldev.com/api
-
- - Plugins: https://lippkg.com
-
- - discuss.md
-
- - links.md
-
-exclude_docs: |
- api/assets/
-
-theme:
- name: material
- features:
- - navigation.tabs
- - navigation.tabs.sticky
- favicon: img/favicon.ico
- logo: img/logo.svg
- palette:
- - media: "(prefers-color-scheme: light)"
- scheme: default
- primary: white
- toggle:
- icon: material/brightness-7
- name: Switch to dark mode
-
- - media: "(prefers-color-scheme: dark)"
- scheme: slate
- toggle:
- icon: material/brightness-4
- name: Switch to light mode
-
-markdown_extensions:
- - abbr
- - admonition
- - attr_list
- - def_list
- - footnotes
- - md_in_html
- - toc
- - tables
- - pymdownx.arithmatex
- - pymdownx.betterem
- - pymdownx.caret
- - pymdownx.mark
- - pymdownx.tilde
- - pymdownx.critic
- - pymdownx.details
- - pymdownx.emoji
- - pymdownx.highlight:
- auto_title: true
- linenums: true
- - pymdownx.inlinehilite
- - pymdownx.keys
- - pymdownx.smartsymbols
- - pymdownx.snippets
- - pymdownx.superfences
- - pymdownx.tabbed:
- alternate_style: true
- - pymdownx.tasklist
-
-plugins:
- - i18n:
- languages:
- - locale: en
- default: true
- name: English
-
- - locale: zh
- name: 中文
- nav_translations:
- Home: 主页
- Install: 安装
- Install on Docker: 在Docker上安装
- Tutorials: 教程
- Guides: 指南
- APIs: 接口
- Engines: 引擎
- Plugins: 插件
-
- - search
diff --git a/scripts/encode_dectect.py b/scripts/encode_dectect.py
new file mode 100644
index 0000000000..a6ca18af27
--- /dev/null
+++ b/scripts/encode_dectect.py
@@ -0,0 +1,14 @@
+# execute clang-format at src with multi-threading
+
+import os
+import chardet
+
+if __name__ == "__main__":
+ for path in ["./src", "./src-server", "./src-client", "./src-test"]:
+ for root, dirs, files in os.walk(path):
+ for file in files:
+ if file.endswith(".h") or file.endswith(".cpp"):
+ with open(os.path.join(root, file), "rb") as f:
+ d = chardet.detect(f.read())
+ if d.get('encoding') != 'ascii' and d.get('encoding') != 'utf-8':
+ print(d, file)
diff --git a/scripts/format_all.py b/scripts/format_all.py
index fbca83f3de..d2a908244e 100644
--- a/scripts/format_all.py
+++ b/scripts/format_all.py
@@ -1,41 +1,61 @@
-# execute clang-format at src with multi-threading
-
import os
import subprocess
+from concurrent.futures import ThreadPoolExecutor
import multiprocessing
-import re
-def format_file(file):
- print("formatting {0}".format(file))
+def get_all_code_files(directory, extensions):
+ """获取指定目录及其子目录下的所有代码文件"""
+ code_files = []
+ for root, _, files in os.walk(directory):
+ for file in files:
+ if file.endswith(extensions):
+ code_files.append(os.path.join(root, file))
+ return code_files
- with open(file, "r") as f:
- content = f.read()
- with open(file, "w") as f:
- if not content.endswith("\n"):
- content += "\n"
- if file.endswith(".h") and not content.startswith("#pragma once"):
- content = "#pragma once\n" + content
- f.write(content)
- f.close()
- subprocess.run(["clang-format", "-i", file])
+def format_file(file_path, clang_format_path):
+ """使用 clang-format 格式化单个文件"""
+ try:
+ with open(file_path, "r") as f:
+ content = f.read()
+ with open(file_path, "w") as f:
+ if not content.endswith("\n"):
+ content += "\n"
+ if file_path.endswith(".h") and not content.startswith("#pragma once"):
+ content = "#pragma once\n" + content
+ f.write(content)
+ f.close()
+ subprocess.run([clang_format_path, "-i", file_path], check=True)
+ print(f"Formatted: {file_path}")
+ except subprocess.CalledProcessError as e:
+ print(f"Failed to format {file_path}: {e}")
+
+
+def format_code_files(
+ directory, clang_format_path, extensions=(".cpp", ".c", ".h"), threads=None
+):
+ """格式化指定目录及其子目录下的所有代码文件"""
+ code_files = get_all_code_files(directory, extensions)
+ print(f"Found {len(code_files)} files to format.")
+
+ if threads is None:
+ threads = multiprocessing.cpu_count()
+ print(f"Using {threads} threads (number of CPU cores).")
+
+ with ThreadPoolExecutor(max_workers=threads) as executor:
+ executor.map(lambda file: format_file(
+ file, clang_format_path), code_files)
-def format_all():
- print("formatting")
- pool = multiprocessing.Pool(
- processes=max(multiprocessing.cpu_count()-2, 1))
- for root, dirs, files in os.walk("./src"):
- for file in files:
- if file.endswith(".h") or file.endswith(".cpp"):
- pool.apply_async(format_file, (os.path.join(root, file),))
- pool.close()
- pool.join()
+if __name__ == "__main__":
+ clang_format_path = r"clang-format"
+ import time
-if __name__ == "__main__":
- try:
- format_all()
- except Exception as e:
- print(f"An error occurred: {e}")
+ print(f"开始 {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())}")
+ format_code_files("./src", clang_format_path)
+ format_code_files("./src-server", clang_format_path)
+ format_code_files("./src-client", clang_format_path)
+ format_code_files("./src-test", clang_format_path)
+ print(f"结束 {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())}")
diff --git a/scripts/localbdslibrary.lua b/scripts/localbdslibrary.lua
deleted file mode 100644
index 6a178f1246..0000000000
--- a/scripts/localbdslibrary.lua
+++ /dev/null
@@ -1,92 +0,0 @@
-package("localbdslibrary")
- set_description("use local generated bds library")
-
- on_load(function (package)
- import("core.project.config")
- import("net.http")
-
- local pe_editor_version = "v3.3.0"
-
- package:set("installdir", path.join(config.buildir(), "bds"))
- local bedrock_server_api = path.join(package:installdir("lib"), "bedrock_server_api.lib")
- local bedrock_server_var = path.join(package:installdir("lib"), "bedrock_server_var.lib")
- if os.isfile(bedrock_server_api) and os.isfile(bedrock_server_var) then
- print("[localbdslibrary] Local bds library found, skipping generation")
- return
- end
- -- generate lib
- print("[localbdslibrary] Local bds library not found, attempting to generate it...")
- local tools_path = path.join(config.buildir(), "tools", "PeEditor-" .. pe_editor_version .. ".exe")
- if not os.isfile(tools_path) then
- -- not found, download it
- print("[localbdslibrary] Downloading PeEditor.exe...")
- http.download("https://github.com/LiteLDev/PeEditor/releases/download/" .. pe_editor_version .. "/PeEditor.exe", tools_path)
- if not os.isfile(tools_path) then
- raise("[localbdslibrary] Failed to download PeEditor.exe")
- end
- print("[localbdslibrary] PeEditor.exe downloaded successfully")
- end
- print("[localbdslibrary] Running PeEditor.exe to generate local bds library...")
- os.vexec("%s -c -l -o %s", tools_path, package:installdir("lib"))
- if not os.isfile(bedrock_server_api) or not os.isfile(bedrock_server_var) then
- raise("[localbdslibrary] Failed to generate bedrock_server_api.lib or bedrock_server_var.lib")
- end
- print("[localbdslibrary] Local bds library generated successfully")
- end)
-
- on_fetch(function (package)
- local result = {}
- result.linkdirs = package:installdir("lib")
- result.links = {"bedrock_server_api", "bedrock_server_var"}
- return result
- end)
-package_end()
-
-
-task("bds-lib")
- on_run(function ()
- import("core.base.option")
- local actions = {"remote", "local", "clean", "tool"}
- -- error if multiple actions are specified or no action is specified
- local action = nil
- for _, a in ipairs(actions) do
- if option.get(a) then
- if action then
- raise("only one action can be specified")
- end
- action = a
- end
- end
- if not action then
- raise("no action specified")
- end
- if action == "remote" then
- print("[localbdslibrary] Using remote BDS library.")
- os.exec("xmake config --localbdslibrary=n")
- elseif action == "local" then
- print("[localbdslibrary] Using local BDS library.")
- os.exec("xmake config --localbdslibrary=y")
- elseif action == "clean" then
- import("core.project.config")
- local lib_dir = path.join(config.buildir(), "bds")
- os.rm(lib_dir)
- print("[localbdslibrary] Local bdslibrary has been cleared.")
- elseif action == "tool" then
- import("core.project.config")
- local tool_dir = path.join(config.buildir(), "tools")
- os.rm(tool_dir)
- print("[localbdslibrary] Toolchain has been cleared.")
- end
- end)
-
- set_menu {
- usage = "xmake bds-lib",
- description = "Manage local BDS library",
- options = {
- {'r', "remote", "k", nil, "Use remote BDS library"},
- {'l', "local", "k", nil, "Use local BDS library"},
- {'c', "clean", "k", nil, "Clean local BDS library"},
- {'t', "tool", "k", nil, "Remove toolchain"}
- }
- }
-task_end()
diff --git a/src-client/ll/api/Versions.cpp b/src-client/ll/api/Versions.cpp
new file mode 100644
index 0000000000..d3e9c887a8
--- /dev/null
+++ b/src-client/ll/api/Versions.cpp
@@ -0,0 +1,19 @@
+#include "ll/api/Versions.h"
+#include "ll/core/Version.h"
+#include "mc/common/BuildInfo.h"
+#include "mc/common/Common.h"
+
+namespace ll {
+data::Version getGameVersion() {
+ static auto ver = [] {
+ auto info = Common::getBuildInfo();
+ auto v = data::Version{info.mBuildId};
+ v.build = info.mCommitId.substr(0, std::min(info.mCommitId.size(), (size_t)7));
+ return v;
+ }();
+ return ver;
+}
+
+int getNetworkProtocolVersion() { return TARGET_BDS_PROTOCOL_VERSION; }
+
+} // namespace ll
diff --git a/src-client/ll/api/service/TargetedBedrock.cpp b/src-client/ll/api/service/TargetedBedrock.cpp
new file mode 100644
index 0000000000..a26b85886e
--- /dev/null
+++ b/src-client/ll/api/service/TargetedBedrock.cpp
@@ -0,0 +1,76 @@
+#include "ll/api/service/Bedrock.h"
+
+#include
+
+#include "ll/api/memory/Hook.h"
+
+#include "mc/deps/ecs/systems/EntitySystemsCollection.h"
+#include "mc/deps/raknet/RakPeer.h"
+#include "mc/deps/raknet/RakPeerInterface.h"
+#include "mc/network/NetworkSystem.h"
+#include "mc/network/RakNetConnector.h"
+#include "mc/network/ServerNetworkHandler.h"
+#include "mc/resources/ResourcePackRepository.h"
+#include "mc/server/DedicatedServer.h"
+#include "mc/server/PropertiesSettings.h"
+#include "mc/server/ServerInstance.h"
+#include "mc/server/ServerLevel.h"
+#include "mc/server/commands/AllowListCommand.h"
+#include "mc/server/commands/CommandRegistry.h"
+#include "mc/server/commands/MinecraftCommands.h"
+#include "mc/server/commands/standard/TeleportCommand.h"
+#include "mc/world/GameSession.h"
+#include "mc/world/Minecraft.h"
+#include "mc/world/events/ServerInstanceEventCoordinator.h"
+
+#include "ll/core/LeviLamina.h"
+
+namespace ll::service::inline bedrock {
+
+std::atomic commandRegistry;
+
+LL_AUTO_STATIC_HOOK(
+ registerTpdimCommands,
+ memory::HookPriority::High,
+ &TeleportCommand::setup,
+ void,
+ CommandRegistry& registry
+) {
+ getLogger().debug("TeleportCommand::setup");
+
+ commandRegistry = std::addressof(registry);
+ origin(registry);
+}
+
+std::atomic serverInstance;
+
+optional_ref getServerInstance() { return serverInstance.load(); }
+
+optional_ref getMinecraft() {
+ return getServerInstance().and_then([](auto& server) { return optional_ref{server.mMinecraft.get()}; });
+}
+
+optional_ref getLevel() {
+ return getMinecraft().and_then([](auto& minecraft) { return optional_ref{minecraft.mGameSession->mLevel.get()}; });
+}
+
+optional_ref getServerNetworkHandler() {
+ return getMinecraft().and_then([](auto& minecraft) {
+ return optional_ref{minecraft.mGameSession->mServerNetworkHandler.get()};
+ });
+}
+optional_ref getRakPeer() { return nullptr; }
+
+optional_ref getNetworkSystem() {
+ return getMinecraft().transform([](auto& minecraft) -> NetworkSystem& {
+ return (NetworkSystem&)(minecraft.mNetwork.toClientNetworkSystem());
+ });
+}
+
+optional_ref getCommandRegistry() {
+ // return getMinecraft().transform([](auto& minecraft) -> CommandRegistry& {
+ // return *minecraft.mCommands->mRegistry;
+ // });
+ return commandRegistry.load();
+}
+} // namespace ll::service::inline bedrock
diff --git a/src-client/ll/api/service/TargetedBedrock.h b/src-client/ll/api/service/TargetedBedrock.h
new file mode 100644
index 0000000000..b6b871a5ee
--- /dev/null
+++ b/src-client/ll/api/service/TargetedBedrock.h
@@ -0,0 +1,7 @@
+#pragma once
+
+#include "ll/api/base/Macro.h"
+
+#include "mc/deps/core/utility/optional_ref.h"
+
+namespace ll::service::inline bedrock {}
diff --git a/src-client/ll/core/TargetedConfig.h b/src-client/ll/core/TargetedConfig.h
new file mode 100644
index 0000000000..6c4a460cbb
--- /dev/null
+++ b/src-client/ll/core/TargetedConfig.h
@@ -0,0 +1,5 @@
+#pragma once
+
+struct TargetedConfig {
+ bool showOutputWindow = false;
+};
diff --git a/src-client/ll/core/gui/GUI.h b/src-client/ll/core/gui/GUI.h
new file mode 100644
index 0000000000..d5c26741ea
--- /dev/null
+++ b/src-client/ll/core/gui/GUI.h
@@ -0,0 +1,13 @@
+#pragma once
+
+namespace ll::gui {
+
+void initializeImGui();
+
+void updateImGui();
+
+void updateScaling(float dpi);
+
+void updateFonts(float scale);
+
+} // namespace ll::gui
diff --git a/src-client/ll/core/gui/ImGuiAnsiColor.cpp b/src-client/ll/core/gui/ImGuiAnsiColor.cpp
new file mode 100644
index 0000000000..1eff6bfef8
--- /dev/null
+++ b/src-client/ll/core/gui/ImGuiAnsiColor.cpp
@@ -0,0 +1,557 @@
+// Based on https://gist.github.com/ddovod/be210315f285becc6b0e455b775286e1
+#include
+#include
+
+#define IMGUI_DEFINE_MATH_OPERATORS
+#include "imgui.h"
+#include "imgui_internal.h"
+#include "ll/api/utils/StringUtils.h"
+
+namespace ll::gui {
+
+using namespace ImGui;
+
+static constexpr uint colors256[] = {
+ 0xff000000, 0xff000080, 0xff008000, 0xff008080, 0xff800000, 0xff800080, 0xff808000, 0xffc0c0c0, 0xff808080,
+ 0xff0000ff, 0xff00ff00, 0xff00ffff, 0xffff0000, 0xffff00ff, 0xffffff00, 0xffffffff, 0xff000000, 0xff5f0000,
+ 0xff870000, 0xffaf0000, 0xffd70000, 0xffff0000, 0xff005f00, 0xff5f5f00, 0xff875f00, 0xffaf5f00, 0xffd75f00,
+ 0xffff5f00, 0xff008700, 0xff5f8700, 0xff878700, 0xffaf8700, 0xffd78700, 0xffff8700, 0xff00af00, 0xff5faf00,
+ 0xff87af00, 0xffafaf00, 0xffd7af00, 0xffffaf00, 0xff00d700, 0xff5fd700, 0xff87d700, 0xffafd700, 0xffd7d700,
+ 0xffffd700, 0xff00ff00, 0xff5fff00, 0xff87ff00, 0xffafff00, 0xffd7ff00, 0xffffff00, 0xff00005f, 0xff5f005f,
+ 0xff87005f, 0xffaf005f, 0xffd7005f, 0xffff005f, 0xff005f5f, 0xff5f5f5f, 0xff875f5f, 0xffaf5f5f, 0xffd75f5f,
+ 0xffff5f5f, 0xff00875f, 0xff5f875f, 0xff87875f, 0xffaf875f, 0xffd7875f, 0xffff875f, 0xff00af5f, 0xff5faf5f,
+ 0xff87af5f, 0xffafaf5f, 0xffd7af5f, 0xffffaf5f, 0xff00d75f, 0xff5fd75f, 0xff87d75f, 0xffafd75f, 0xffd7d75f,
+ 0xffffd75f, 0xff00ff5f, 0xff5fff5f, 0xff87ff5f, 0xffafff5f, 0xffd7ff5f, 0xffffff5f, 0xff000087, 0xff5f0087,
+ 0xff870087, 0xffaf0087, 0xffd70087, 0xffff0087, 0xff005f87, 0xff5f5f87, 0xff875f87, 0xffaf5f87, 0xffd75f87,
+ 0xffff5f87, 0xff008787, 0xff5f8787, 0xff878787, 0xffaf8787, 0xffd78787, 0xffff8787, 0xff00af87, 0xff5faf87,
+ 0xff87af87, 0xffafaf87, 0xffd7af87, 0xffffaf87, 0xff00d787, 0xff5fd787, 0xff87d787, 0xffafd787, 0xffd7d787,
+ 0xffffd787, 0xff00ff87, 0xff5fff87, 0xff87ff87, 0xffafff87, 0xffd7ff87, 0xffffff87, 0xff0000af, 0xff5f00af,
+ 0xff8700af, 0xffaf00af, 0xffd700af, 0xffff00af, 0xff005faf, 0xff5f5faf, 0xff875faf, 0xffaf5faf, 0xffd75faf,
+ 0xffff5faf, 0xff0087af, 0xff5f87af, 0xff8787af, 0xffaf87af, 0xffd787af, 0xffff87af, 0xff00afaf, 0xff5fafaf,
+ 0xff87afaf, 0xffafafaf, 0xffd7afaf, 0xffffafaf, 0xff00d7af, 0xff5fd7af, 0xff87d7af, 0xffafd7af, 0xffd7d7af,
+ 0xffffd7af, 0xff00ffaf, 0xff5fffaf, 0xff87ffaf, 0xffafffaf, 0xffd7ffaf, 0xffffffaf, 0xff0000d7, 0xff5f00d7,
+ 0xff8700d7, 0xffaf00d7, 0xffd700d7, 0xffff00d7, 0xff005fd7, 0xff5f5fd7, 0xff875fd7, 0xffaf5fd7, 0xffd75fd7,
+ 0xffff5fd7, 0xff0087d7, 0xff5f87d7, 0xff8787d7, 0xffaf87d7, 0xffd787d7, 0xffff87d7, 0xff00afd7, 0xff5fafd7,
+ 0xff87afd7, 0xffafafd7, 0xffd7afd7, 0xffffafd7, 0xff00d7d7, 0xff5fd7d7, 0xff87d7d7, 0xffafd7d7, 0xffd7d7d7,
+ 0xffffd7d7, 0xff00ffd7, 0xff5fffd7, 0xff87ffd7, 0xffafffd7, 0xffd7ffd7, 0xffffffd7, 0xff0000ff, 0xff5f00ff,
+ 0xff8700ff, 0xffaf00ff, 0xffd700ff, 0xffff00ff, 0xff005fff, 0xff5f5fff, 0xff875fff, 0xffaf5fff, 0xffd75fff,
+ 0xffff5fff, 0xff0087ff, 0xff5f87ff, 0xff8787ff, 0xffaf87ff, 0xffd787ff, 0xffff87ff, 0xff00afff, 0xff5fafff,
+ 0xff87afff, 0xffafafff, 0xffd7afff, 0xffffafff, 0xff00d7ff, 0xff5fd7ff, 0xff87d7ff, 0xffafd7ff, 0xffd7d7ff,
+ 0xffffd7ff, 0xff00ffff, 0xff5fffff, 0xff87ffff, 0xffafffff, 0xffd7ffff, 0xffffffff, 0xff080808, 0xff121212,
+ 0xff1c1c1c, 0xff262626, 0xff303030, 0xff3a3a3a, 0xff444444, 0xff4e4e4e, 0xff585858, 0xff606060, 0xff666666,
+ 0xff767676, 0xff808080, 0xff8a8a8a, 0xff949494, 0xff9e9e9e, 0xffa8a8a8, 0xffb2b2b2, 0xffbcbcbc, 0xffc6c6c6,
+ 0xffd0d0d0, 0xffdadada, 0xffe4e4e4, 0xffeeeeee
+};
+
+bool parseColor(const char* s, ImU32* col, int* skipChars) {
+ if (s[0] != '\033' || s[1] != '[') {
+ return false;
+ }
+
+ if (s[2] == 'm') {
+ *col = 0xffcccccc;
+ *skipChars = 3;
+ return true;
+ }
+
+ if (s[2] == '0' && s[3] == 'm') {
+ *col = 0xffcccccc;
+ *skipChars = 4;
+ return true;
+ }
+
+ const char* seqEnd = &s[2];
+ while (*seqEnd != 'm') {
+ seqEnd++;
+ }
+
+ std::string seq{&s[2], seqEnd};
+ std::string colorStr;
+ char colorArgsType = 0;
+ std::vector colorArgs;
+ for (const auto& el : string_utils::splitByPattern(seq, ";")) {
+ if (colorStr.empty()) {
+ if (el.size() == 2 && el[0] == '3') { // 30-39
+ colorStr = el;
+ }
+ continue;
+ }
+ if (!colorArgsType) {
+ if (el.size() == 1 && (el[0] == '2' || el[0] == '5')) {
+ colorArgsType = el[0];
+ continue;
+ }
+ break;
+ }
+ colorArgs.push_back(el);
+ }
+
+ if (!colorStr.empty()) {
+ switch (colorStr[1]) {
+ case '0':
+ *col = 0xffcccccc;
+ break;
+ case '1':
+ *col = 0xff7a77f2;
+ break;
+ case '2':
+ *col = 0xff99cc99;
+ break;
+ case '3':
+ *col = 0xff66ccff;
+ break;
+ case '4':
+ *col = 0xffcc9966;
+ break;
+ case '5':
+ *col = 0xffcc99cc;
+ break;
+ case '6':
+ *col = 0xffcccc66;
+ break;
+ case '7':
+ *col = 0xff2d2d2d;
+ break;
+ case '8':
+ if (colorArgsType == '5' && colorArgs.size() >= 1) {
+ uchar bitNumber = ll::string_utils::svtouc(colorArgs[0]).value_or(0);
+ *col = colors256[bitNumber];
+ } else if (colorArgsType == '2' && colorArgs.size() >= 3) {
+ uchar r = ll::string_utils::svtouc(colorArgs[0]).value_or(0);
+ uchar g = ll::string_utils::svtouc(colorArgs[1]).value_or(0);
+ uchar b = ll::string_utils::svtouc(colorArgs[2]).value_or(0);
+ *col = 0xff000000 | (b << 16) | (g << 8) | r;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ *skipChars = static_cast(seqEnd - s + 1);
+ return true;
+}
+
+void ImFont_RenderAnsiText(
+ const ImFont* font,
+ ImDrawList* draw_list,
+ float size,
+ ImVec2 pos,
+ ImU32 col,
+ const ImVec4& clip_rect,
+ const char* text_begin,
+ const char* text_end,
+ float wrap_width = 0.0f,
+ bool cpu_fine_clip = false
+) {
+ if (!text_end)
+ text_end = text_begin + strlen(text_begin); // ImGui functions generally already provides a valid text_end,
+ // so this is merely to handle direct calls.
+
+
+ // Align to be pixel perfect
+ pos.x = (float)(int)pos.x;
+ pos.y = (float)(int)pos.y;
+ float x = pos.x;
+ float y = pos.y;
+ if (y > clip_rect.w) return;
+
+ const float scale = size / font->FontSize;
+ const float line_height = font->FontSize * scale;
+ const bool word_wrap_enabled = (wrap_width > 0.0f);
+ const char* word_wrap_eol = nullptr;
+
+ // Fast-forward to first visible line
+ const char* s = text_begin;
+ if (y + line_height < clip_rect.y && !word_wrap_enabled)
+ while (y + line_height < clip_rect.y && s < text_end) {
+ s = (const char*)memchr(s, '\n', text_end - s);
+ s = s ? s + 1 : text_end;
+ y += line_height;
+ }
+
+ // For large text, scan for the last visible line in order to avoid over-reserving in the call to PrimReserve()
+ // Note that very large horizontal line will still be affected by the issue (e.g. a one megabyte string buffer
+ // without a newline will likely crash atm)
+ if (text_end - s > 10000 && !word_wrap_enabled) {
+ const char* s_end = s;
+ float y_end = y;
+ while (y_end < clip_rect.w && s_end < text_end) {
+ s_end = (const char*)memchr(s_end, '\n', text_end - s_end);
+ s = s ? s + 1 : text_end;
+ y_end += line_height;
+ }
+ text_end = s_end;
+ }
+ if (s == text_end) return;
+
+ // Reserve vertices for remaining worse case (over-reserving is useful and easily amortized)
+ const int vtx_count_max = (int)(text_end - s) * 4;
+ const int idx_count_max = (int)(text_end - s) * 6;
+ const int idx_expected_size = draw_list->IdxBuffer.Size + idx_count_max;
+ draw_list->PrimReserve(idx_count_max, vtx_count_max);
+
+ ImDrawVert* vtx_write = draw_list->_VtxWritePtr;
+ ImDrawIdx* idx_write = draw_list->_IdxWritePtr;
+ uint vtx_current_idx = draw_list->_VtxCurrentIdx;
+
+ auto col_buf = std::vector(text_end - text_begin, col);
+ auto char_skip = std::vector(text_end - text_begin, false);
+ {
+ int index = 0;
+ int skipChars = 0;
+ const char* sLocal = s;
+ ImU32 temp_col = col;
+ while (sLocal < text_end) {
+ if (sLocal <= text_end - 4 && parseColor(sLocal, &temp_col, &skipChars)) {
+ sLocal += skipChars;
+ for (int i = 0; i < skipChars; i++) {
+ char_skip[index + i] = true;
+ }
+ index += skipChars;
+ } else {
+ col_buf[index] = temp_col;
+ char_skip[index] = false;
+ ++index;
+ ++sLocal;
+ }
+ }
+ }
+
+
+ const char* s1 = s;
+ while (s < text_end) {
+ if (char_skip[s - s1]) {
+ s++;
+ continue;
+ }
+ if (word_wrap_enabled) {
+ // Calculate how far we can render. Requires two passes on the string data but keeps the code simple and
+ // not intrusive for what's essentially an uncommon feature.
+ if (!word_wrap_eol) {
+ word_wrap_eol = font->CalcWordWrapPositionA(scale, s, text_end, wrap_width - (x - pos.x));
+ if (word_wrap_eol == s) // Wrap_width is too small to fit anything. Force displaying 1 character to
+ // minimize the height discontinuity.
+ word_wrap_eol++; // +1 may not be a character start point in UTF-8 but it's ok because we use s
+ // >= word_wrap_eol below
+ }
+
+ if (s >= word_wrap_eol) {
+ x = pos.x;
+ y += line_height;
+ word_wrap_eol = nullptr;
+
+ // Wrapping skips upcoming blanks
+ while (s < text_end) {
+ const char c = *s;
+ if (ImCharIsBlankA(c)) {
+ s++;
+ } else if (c == '\n') {
+ s++;
+ break;
+ } else {
+ break;
+ }
+ }
+ continue;
+ }
+ }
+
+ // Decode and advance source
+ uint c = (uint)*s;
+ if (c < 0x80) {
+ s += 1;
+ } else {
+ s += ImTextCharFromUtf8(&c, s, text_end);
+ if (c == 0) // Malformed UTF-8?
+ break;
+ }
+
+ if (c < 32) {
+ if (c == '\n') {
+ x = pos.x;
+ y += line_height;
+ if (y > clip_rect.w) break; // break out of main loop
+ continue;
+ }
+ if (c == '\r') continue;
+ }
+
+ float char_width = 0.0f;
+ if (const ImFontGlyph* glyph = font->FindGlyph((ImWchar)c)) {
+ char_width = glyph->AdvanceX * scale;
+
+ // Arbitrarily assume that both space and tabs are empty glyphs as an optimization
+ if (c != ' ' && c != '\t') {
+ // We don't do a second finer clipping test on the Y axis as we've already skipped anything before
+ // clip_rect.y and exit once we pass clip_rect.w
+ float x1 = x + glyph->X0 * scale;
+ float x2 = x + glyph->X1 * scale;
+ float y1 = y + glyph->Y0 * scale;
+ float y2 = y + glyph->Y1 * scale;
+ if (x1 <= clip_rect.z && x2 >= clip_rect.x) {
+ // Render a character
+ float u1 = glyph->U0;
+ float v1 = glyph->V0;
+ float u2 = glyph->U1;
+ float v2 = glyph->V1;
+
+ // CPU side clipping used to fit text in their frame when the frame is too small. Only does
+ // clipping for axis aligned quads.
+ if (cpu_fine_clip) {
+ if (x1 < clip_rect.x) {
+ u1 = u1 + (1.0f - (x2 - clip_rect.x) / (x2 - x1)) * (u2 - u1);
+ x1 = clip_rect.x;
+ }
+ if (y1 < clip_rect.y) {
+ v1 = v1 + (1.0f - (y2 - clip_rect.y) / (y2 - y1)) * (v2 - v1);
+ y1 = clip_rect.y;
+ }
+ if (x2 > clip_rect.z) {
+ u2 = u1 + ((clip_rect.z - x1) / (x2 - x1)) * (u2 - u1);
+ x2 = clip_rect.z;
+ }
+ if (y2 > clip_rect.w) {
+ v2 = v1 + ((clip_rect.w - y1) / (y2 - y1)) * (v2 - v1);
+ y2 = clip_rect.w;
+ }
+ if (y1 >= y2) {
+ x += char_width;
+ continue;
+ }
+ }
+
+ // We are NOT calling PrimRectUV() here because non-inlined causes too much overhead in a debug
+ // builds. Inlined here:
+ ImU32 temp_col = col_buf[s - text_begin - 1];
+ {
+ idx_write[0] = (ImDrawIdx)(vtx_current_idx);
+ idx_write[1] = (ImDrawIdx)(vtx_current_idx + 1);
+ idx_write[2] = (ImDrawIdx)(vtx_current_idx + 2);
+ idx_write[3] = (ImDrawIdx)(vtx_current_idx);
+ idx_write[4] = (ImDrawIdx)(vtx_current_idx + 2);
+ idx_write[5] = (ImDrawIdx)(vtx_current_idx + 3);
+ vtx_write[0].pos.x = x1;
+ vtx_write[0].pos.y = y1;
+ vtx_write[0].col = temp_col;
+ vtx_write[0].uv.x = u1;
+ vtx_write[0].uv.y = v1;
+ vtx_write[1].pos.x = x2;
+ vtx_write[1].pos.y = y1;
+ vtx_write[1].col = temp_col;
+ vtx_write[1].uv.x = u2;
+ vtx_write[1].uv.y = v1;
+ vtx_write[2].pos.x = x2;
+ vtx_write[2].pos.y = y2;
+ vtx_write[2].col = temp_col;
+ vtx_write[2].uv.x = u2;
+ vtx_write[2].uv.y = v2;
+ vtx_write[3].pos.x = x1;
+ vtx_write[3].pos.y = y2;
+ vtx_write[3].col = temp_col;
+ vtx_write[3].uv.x = u1;
+ vtx_write[3].uv.y = v2;
+ vtx_write += 4;
+ vtx_current_idx += 4;
+ idx_write += 6;
+ }
+ }
+ }
+ }
+
+ x += char_width;
+ }
+
+ // Give back unused vertices
+ draw_list->VtxBuffer.resize((int)(vtx_write - draw_list->VtxBuffer.Data));
+ draw_list->IdxBuffer.resize((int)(idx_write - draw_list->IdxBuffer.Data));
+ draw_list->CmdBuffer[draw_list->CmdBuffer.Size - 1].ElemCount -= (idx_expected_size - draw_list->IdxBuffer.Size);
+ draw_list->_VtxWritePtr = vtx_write;
+ draw_list->_IdxWritePtr = idx_write;
+ draw_list->_VtxCurrentIdx = (uint)draw_list->VtxBuffer.Size;
+}
+
+void ImDrawList_AddAnsiText(
+ ImDrawList* drawList,
+ const ImFont* font,
+ float font_size,
+ const ImVec2& pos,
+ ImU32 col,
+ const char* text_begin,
+ const char* text_end = nullptr,
+ float wrap_width = 0.0f,
+ const ImVec4* cpu_fine_clip_rect = nullptr
+) {
+ if ((col & IM_COL32_A_MASK) == 0) return;
+
+ if (text_end == nullptr) text_end = text_begin + strlen(text_begin);
+ if (text_begin == text_end) return;
+
+ // Pull default font/size from the shared ImDrawListSharedData instance
+ if (font == nullptr) font = drawList->_Data->Font;
+ if (font_size == 0.0f) font_size = drawList->_Data->FontSize;
+
+ IM_ASSERT(font->ContainerAtlas->TexID == drawList->_TextureIdStack.back()); // Use high-level ImGui::PushFont()
+ // or low-level
+ // ImDrawList::PushTextureId() to
+ // change font.
+
+ ImVec4 clip_rect = drawList->_ClipRectStack.back();
+ if (cpu_fine_clip_rect) {
+ clip_rect.x = ImMax(clip_rect.x, cpu_fine_clip_rect->x);
+ clip_rect.y = ImMax(clip_rect.y, cpu_fine_clip_rect->y);
+ clip_rect.z = ImMin(clip_rect.z, cpu_fine_clip_rect->z);
+ clip_rect.w = ImMin(clip_rect.w, cpu_fine_clip_rect->w);
+ }
+ ImFont_RenderAnsiText(
+ font,
+ drawList,
+ font_size,
+ pos,
+ col,
+ clip_rect,
+ text_begin,
+ text_end,
+ wrap_width,
+ cpu_fine_clip_rect != nullptr
+ );
+}
+
+void RenderAnsiText(ImVec2 pos, const char* text, const char* text_end, bool hide_text_after_hash) {
+ ImGuiContext& g = *GImGui;
+ ImGuiWindow* window = g.CurrentWindow;
+
+ // Hide anything after a '##' string
+ const char* text_display_end;
+ if (hide_text_after_hash) {
+ text_display_end = FindRenderedTextEnd(text, text_end);
+ } else {
+ text_display_end = text_end;
+ }
+
+ if (text != text_display_end) {
+ ImDrawList_AddAnsiText(
+ window->DrawList,
+ g.Font,
+ g.FontSize,
+ pos,
+ GetColorU32(ImGuiCol_Text),
+ text,
+ text_display_end
+ );
+ if (g.LogEnabled) LogRenderedText(&pos, text, text_display_end);
+ }
+}
+
+void RenderAnsiTextWrapped(ImVec2 pos, const char* text, const char* text_end, float wrap_width) {
+ ImGuiContext& g = *GImGui;
+ ImGuiWindow* window = g.CurrentWindow;
+
+ if (text != text_end) {
+ ImDrawList_AddAnsiText(
+ window->DrawList,
+ g.Font,
+ g.FontSize,
+ pos,
+ GetColorU32(ImGuiCol_Text),
+ text,
+ text_end,
+ wrap_width
+ );
+ if (g.LogEnabled) LogRenderedText(&pos, text, text_end);
+ }
+}
+
+void textAnsiUnformatted(std::string_view view) {
+ auto text = &*view.begin(), text_end = &*view.end();
+
+ ImGuiWindow* window = GetCurrentWindow();
+ if (window->SkipItems) return;
+
+ ImGuiContext& g = *GImGui;
+ IM_ASSERT(text != nullptr);
+ const char* text_begin = text;
+
+ const ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
+ const float wrap_pos_x = window->DC.TextWrapPos;
+ const bool wrap_enabled = wrap_pos_x >= 0.0f;
+ if (text_end - text > 2000 && !wrap_enabled) {
+ // Long text!
+ // Perform manual coarse clipping to optimize for long multi-line text
+ // - From this point we will only compute the width of lines that are visible. Optimization only available
+ // when word-wrapping is disabled.
+ // - We also don't vertically center the text within the line full height, which is unlikely to matter
+ // because we are likely the biggest and only item on the line.
+ // - We use memchr(), pay attention that well optimized versions of those str/mem functions are much faster
+ // than a casually written loop.
+ const char* line = text;
+ const float line_height = GetTextLineHeight();
+ const ImRect clip_rect = window->ClipRect;
+ ImVec2 text_size(0, 0);
+
+ if (text_pos.y <= clip_rect.Max.y) {
+ ImVec2 pos = text_pos;
+
+ // Lines to skip (can't skip when logging text)
+ if (!g.LogEnabled) {
+ int lines_skippable = (int)((clip_rect.Min.y - text_pos.y) / line_height);
+ if (lines_skippable > 0) {
+ int lines_skipped = 0;
+ while (line < text_end && lines_skipped < lines_skippable) {
+ const char* line_end = (const char*)memchr(line, '\n', text_end - line);
+ if (!line_end) line_end = text_end;
+ line = line_end + 1;
+ lines_skipped++;
+ }
+ pos.y += lines_skipped * line_height;
+ }
+ }
+
+ // Lines to render
+ if (line < text_end) {
+ ImRect line_rect(pos, pos + ImVec2(FLT_MAX, line_height));
+ while (line < text_end) {
+ if (IsClippedEx(line_rect, 0)) break;
+
+ const char* line_end = (const char*)memchr(line, '\n', text_end - line);
+ if (!line_end) line_end = text_end;
+ const ImVec2 line_size = CalcTextSize(line, line_end, false);
+ text_size.x = ImMax(text_size.x, line_size.x);
+ RenderAnsiText(pos, line, line_end, false);
+ line = line_end + 1;
+ line_rect.Min.y += line_height;
+ line_rect.Max.y += line_height;
+ pos.y += line_height;
+ }
+
+ // Count remaining lines
+ int lines_skipped = 0;
+ while (line < text_end) {
+ const char* line_end = (const char*)memchr(line, '\n', text_end - line);
+ if (!line_end) line_end = text_end;
+ line = line_end + 1;
+ lines_skipped++;
+ }
+ pos.y += lines_skipped * line_height;
+ }
+
+ text_size.y += (pos - text_pos).y;
+ }
+
+ ImRect bb(text_pos, text_pos + text_size);
+ ItemSize(bb);
+ ItemAdd(bb, 0);
+ } else {
+ const float wrap_width = wrap_enabled ? CalcWrapWidthForPos(window->DC.CursorPos, wrap_pos_x) : 0.0f;
+ const ImVec2 text_size = CalcTextSize(text_begin, text_end, false, wrap_width);
+
+ // Account of baseline offset
+ ImRect bb(text_pos, text_pos + text_size);
+ ItemSize(text_size);
+ if (!ItemAdd(bb, 0)) return;
+
+ // Render (we don't hide text after ## in this end-user function)
+ RenderAnsiTextWrapped(bb.Min, text_begin, text_end, wrap_width);
+ }
+}
+} // namespace ll::gui
diff --git a/src-client/ll/core/gui/ImGuiAnsiColor.h b/src-client/ll/core/gui/ImGuiAnsiColor.h
new file mode 100644
index 0000000000..4605af9e7e
--- /dev/null
+++ b/src-client/ll/core/gui/ImGuiAnsiColor.h
@@ -0,0 +1,7 @@
+#pragma once
+
+#include
+
+namespace ll::gui {
+void textAnsiUnformatted(std::string_view view);
+} // namespace ll::gui
diff --git a/src-client/ll/core/gui/ImGuiHooks.h b/src-client/ll/core/gui/ImGuiHooks.h
new file mode 100644
index 0000000000..ce18aa8bf8
--- /dev/null
+++ b/src-client/ll/core/gui/ImGuiHooks.h
@@ -0,0 +1,9 @@
+#pragma once
+
+#include
+
+namespace ll::gui {
+void init();
+const std::string& getGPUName();
+const std::string& getRendererType();
+} // namespace ll::gui
diff --git a/src-client/ll/core/gui/ImguiConfig.cpp b/src-client/ll/core/gui/ImguiConfig.cpp
new file mode 100644
index 0000000000..a4a596614c
--- /dev/null
+++ b/src-client/ll/core/gui/ImguiConfig.cpp
@@ -0,0 +1,37 @@
+#include "ll/core/gui/ImguiConfig.h"
+#include "ll/core/ConfigImpl.h"
+
+namespace mce {
+template
+static inline ll::Expected serialize(Color const& c) {
+ return std::format("#{:06x}", (uint)c.toARGB() % (1u << 24))
+ + (c.a == 1 ? "" : std::format("{:02x}", (uint)c.toARGB() / (1u << 24)));
+}
+template
+ requires(std::same_as)
+static inline ll::Expected<> deserialize(Color& v, J const& j) {
+ if (j.is_string()) {
+ v = mce::Color{(std::string_view)j};
+ }
+ return {};
+}
+} // namespace mce
+
+namespace ll::gui {
+
+LL_CONFIG_IMPL(LeviImguiConfig, u8"ImguiConfig.json");
+
+
+std::vector LeviImguiConfig::getDefaultFonts() {
+ if (i18n::getDefaultLocaleCode().starts_with("zh")) {
+ return {
+ {"default", Font::GlyphRange::Latins, Font::Width::Half},
+ { "system", Font::GlyphRange::ChineseFull, Font::Width::Full}
+ };
+ }
+ return {
+ {"default", Font::GlyphRange::Latins}
+ };
+}
+
+} // namespace ll::gui
diff --git a/src-client/ll/core/gui/ImguiConfig.h b/src-client/ll/core/gui/ImguiConfig.h
new file mode 100644
index 0000000000..6789fe2c5f
--- /dev/null
+++ b/src-client/ll/core/gui/ImguiConfig.h
@@ -0,0 +1,50 @@
+#pragma once
+
+#include "ll/api/utils/FontUtils.h"
+#include "ll/core/gui/styles/Custom.h"
+
+#include
+
+namespace ll::gui {
+
+struct LeviImguiConfig {
+ int version = 0;
+
+ struct Font {
+ enum class GlyphRange {
+ Latins,
+ Greek,
+ Korean,
+ Japanese,
+ ChineseFull,
+ ChineseSimplifiedCommon,
+ Cyrillic,
+ Thai,
+ Vietnamese,
+ };
+ enum class Width {
+ Fit,
+ Half,
+ Full,
+ };
+ std::string name;
+ GlyphRange range;
+ std::optional width;
+ std::optional weight;
+ std::optional stretch;
+ std::optional style;
+ };
+ struct FontSettings {
+ float size = 16.0f;
+ std::vector fonts{getDefaultFonts()};
+ } fontSettings{};
+ LeviImGuiStyle style{};
+
+ static std::vector getDefaultFonts();
+};
+
+LeviImguiConfig& getLeviImguiConfig();
+
+bool saveLeviImguiConfig();
+
+} // namespace ll::gui
diff --git a/src-client/ll/core/gui/styles/Custom.cpp b/src-client/ll/core/gui/styles/Custom.cpp
new file mode 100644
index 0000000000..2e1185ec16
--- /dev/null
+++ b/src-client/ll/core/gui/styles/Custom.cpp
@@ -0,0 +1,11 @@
+#include "ll/core/gui/styles/Custom.h"
+
+#include "imgui.h"
+
+namespace ll::gui {
+
+void LeviImGuiStyle::getFromImgui() { *this = (LeviImGuiStyle&)ImGui::GetStyle(); }
+
+void LeviImGuiStyle::applyToImgui() { ((LeviImGuiStyle&)ImGui::GetStyle()) = *this; }
+
+} // namespace ll::gui
diff --git a/src-client/ll/core/gui/styles/Custom.h b/src-client/ll/core/gui/styles/Custom.h
new file mode 100644
index 0000000000..ce13cde1a6
--- /dev/null
+++ b/src-client/ll/core/gui/styles/Custom.h
@@ -0,0 +1,127 @@
+#pragma once
+
+#include "ll/api/Expected.h"
+#include "mc/deps/core/math/Color.h"
+#include "mc/deps/core/math/Vec2.h"
+#include "nlohmann/json.hpp"
+
+namespace ll::gui {
+
+struct LeviImGuiStyle {
+ enum class Dir : int { None = -1, Left = 0, Right = 1, Up = 2, Down = 3 };
+
+ float alpha{1.0f};
+ float disabledAlpha{0.6f};
+ Vec2 windowPadding{8.0f};
+ float windowRounding{8.0f};
+ float windowBorderSize{1.0f};
+ Vec2 windowMinSize{32.0f};
+ Vec2 windowTitleAlign{0.0f, 0.5f};
+ Dir windowMenuButtonPosition{Dir::Left};
+ float childRounding{6.0f};
+ float childBorderSize{1.0f};
+ float popupRounding{6.0f};
+ float popupBorderSize{1.0f};
+ Vec2 framePadding{4.0f, 3.0f};
+ float frameRounding{4.0f};
+ float frameBorderSize{0.0f};
+ Vec2 itemSpacing{8.0f, 4.0f};
+ Vec2 itemInnerSpacing{4.0f};
+ Vec2 cellPadding{4.0f, 2.0f};
+ Vec2 touchExtraPadding{0.0f};
+ float indentSpacing{21.0f};
+ float columnsMinSpacing{6.0f};
+ float scrollbarSize{14.0f};
+ float scrollbarRounding{9.0f};
+ float grabMinSize{12.0f};
+ float grabRounding{4.0f};
+ float logSliderDeadzone{4.0f};
+ float tabRounding{8.0f};
+ float tabBorderSize{0.0f};
+ float tabMinWidthForCloseButton{0.0f};
+ float tabBarBorderSize{1.0f};
+ float tabBarOverlineSize{2.0f};
+ float tableAngledHeadersAngle{(float)(35 * std::numbers::pi / 180)};
+ Vec2 tableAngledHeadersTextAlign{5.0f, 0.0f};
+ Dir colorButtonPosition{Dir::Right};
+ Vec2 buttonTextAlign{0.5f};
+ Vec2 selectableTextAlign{0.0f};
+ float separatorTextBorderSize{3.0f};
+ Vec2 separatorTextAlign{0.0f, 0.5f};
+ Vec2 separatorTextPadding{20.0f, 3.0f};
+ Vec2 displayWindowPadding{19.0f};
+ Vec2 displaySafeAreaPadding{3.0f};
+ float dockingSeparatorSize{2.0f};
+ float mouseCursorScale{1.0f};
+ bool antiAliasedLines{true};
+ bool antiAliasedLinesUseTex{true};
+ bool antiAliasedFill{true};
+ float curveTessellationTol{1.25f};
+ float circleTessellationMaxError{0.3f};
+
+ struct ColorStyle {
+ ::mce::Color text{"#ffffff"};
+ ::mce::Color textDisabled{"#7f7f7f"};
+ ::mce::Color windowBg{"#0f0f0fef"};
+ ::mce::Color childBg{"#00000000"};
+ ::mce::Color popupBg{"#141414ef"};
+ ::mce::Color border{"#6d6d7f7f"};
+ ::mce::Color borderShadow{"#00000000"};
+ ::mce::Color frameBg{"#28497a89"};
+ ::mce::Color frameBgHovered{"#4296f966"};
+ ::mce::Color frameBgActive{"#4296f9aa"};
+ ::mce::Color titleBg{"#0a0a0a"};
+ ::mce::Color titleBgActive{"#28497a"};
+ ::mce::Color titleBgCollapsed{"#00000082"};
+ ::mce::Color menuBarBg{"#232323"};
+ ::mce::Color scrollbarBg{"#05050587"};
+ ::mce::Color scrollbarGrab{"#4f4f4f"};
+ ::mce::Color scrollbarGrabHovered{"#686868"};
+ ::mce::Color scrollbarGrabActive{"#828282"};
+ ::mce::Color checkMark{"#4296f9"};
+ ::mce::Color sliderGrab{"#3d84e0"};
+ ::mce::Color sliderGrabActive{"#4296f9"};
+ ::mce::Color button{"#4296f966"};
+ ::mce::Color buttonHovered{"#4296f9"};
+ ::mce::Color buttonActive{"#0f87f9"};
+ ::mce::Color header{"#4296f94f"};
+ ::mce::Color headerHovered{"#4296f9cc"};
+ ::mce::Color headerActive{"#4296f9"};
+ ::mce::Color separator{"#6d6d7f7f"};
+ ::mce::Color separatorHovered{"#1966bfc6"};
+ ::mce::Color separatorActive{"#1966bf"};
+ ::mce::Color resizeGrip{"#4296f933"};
+ ::mce::Color resizeGripHovered{"#4296f9aa"};
+ ::mce::Color resizeGripActive{"#4296f9f2"};
+ ::mce::Color tabHovered{"#4296f9cc"};
+ ::mce::Color tab{"#2d5993db"};
+ ::mce::Color tabSelected{"#3268ad"};
+ ::mce::Color tabSelectedOverline{"#4296f9"};
+ ::mce::Color tabDimmed{"#111a25f7"};
+ ::mce::Color tabDimmedSelected{"#22426c"};
+ ::mce::Color tabDimmedSelectedOverline{"#7f7f7f"};
+ ::mce::Color dockingPreview{"#4296f9b2"};
+ ::mce::Color dockingEmptyBg{"#333333"};
+ ::mce::Color plotLines{"#9b9b9b"};
+ ::mce::Color plotLinesHovered{"#ff6d59"};
+ ::mce::Color plotHistogram{"#e5b200"};
+ ::mce::Color plotHistogramHovered{"#ff9900"};
+ ::mce::Color tableHeaderBg{"#303033"};
+ ::mce::Color tableBorderStrong{"#4f4f59"};
+ ::mce::Color tableBorderLight{"#3a3a3f"};
+ ::mce::Color tableRowBg{"#00000000"};
+ ::mce::Color tableRowBgAlt{"#ffffff0f"};
+ ::mce::Color textLink{"#4296f9"};
+ ::mce::Color textSelectedBg{"#4296f959"};
+ ::mce::Color dragDropTarget{"#ffff00e5"};
+ ::mce::Color navHighlight{"#4296f9"};
+ ::mce::Color navWindowingHighlight{"#ffffffb2"};
+ ::mce::Color navWindowingDimBg{"#cccccc33"};
+ ::mce::Color modalWindowDimBg{"#cccccc59"};
+ } colors{};
+
+ void getFromImgui();
+
+ void applyToImgui();
+};
+} // namespace ll::gui
diff --git a/src-client/ll/core/gui/styles/ImguiSpectrum.cpp b/src-client/ll/core/gui/styles/ImguiSpectrum.cpp
new file mode 100644
index 0000000000..9f952b7e65
--- /dev/null
+++ b/src-client/ll/core/gui/styles/ImguiSpectrum.cpp
@@ -0,0 +1,293 @@
+#include "ll/core/gui/styles/ImguiSpectrum.h"
+#include "imgui.h"
+#include "ll/api/base/StdInt.h"
+
+namespace ll::gui {
+namespace Spectrum {
+namespace { // Unnamed namespace, since we only use this here.
+uint Color(uint c) {
+ // add alpha.
+ const short a = 0xFF;
+ const short r = (c >> 16) & 0xFF;
+ const short g = (c >> 8) & 0xFF;
+ const short b = (c >> 0) & 0xFF;
+ return (a << 24) | (r << 0) | (g << 8) | (b << 16);
+}
+} // namespace
+// all colors are from http://spectrum.corp.adobe.com/color.html
+
+inline uint color_alpha(uint alpha, uint c) { return ((alpha & 0xFF) << 24) | (c & 0x00FFFFFF); }
+
+namespace Static { // static colors
+const uint NONE = 0x00000000; // transparent
+const uint WHITE = Color(0xFFFFFF);
+const uint BLACK = Color(0x000000);
+const uint GRAY200 = Color(0xF4F4F4);
+const uint GRAY300 = Color(0xEAEAEA);
+const uint GRAY400 = Color(0xD3D3D3);
+const uint GRAY500 = Color(0xBCBCBC);
+const uint GRAY600 = Color(0x959595);
+const uint GRAY700 = Color(0x767676);
+const uint GRAY800 = Color(0x505050);
+const uint GRAY900 = Color(0x323232);
+const uint BLUE400 = Color(0x378EF0);
+const uint BLUE500 = Color(0x2680EB);
+const uint BLUE600 = Color(0x1473E6);
+const uint BLUE700 = Color(0x0D66D0);
+const uint RED400 = Color(0xEC5B62);
+const uint RED500 = Color(0xE34850);
+const uint RED600 = Color(0xD7373F);
+const uint RED700 = Color(0xC9252D);
+const uint ORANGE400 = Color(0xF29423);
+const uint ORANGE500 = Color(0xE68619);
+const uint ORANGE600 = Color(0xDA7B11);
+const uint ORANGE700 = Color(0xCB6F10);
+const uint GREEN400 = Color(0x33AB84);
+const uint GREEN500 = Color(0x2D9D78);
+const uint GREEN600 = Color(0x268E6C);
+const uint GREEN700 = Color(0x12805C);
+} // namespace Static
+
+namespace Light {
+const uint GRAY50 = Color(0xFFFFFF);
+const uint GRAY75 = Color(0xFAFAFA);
+const uint GRAY100 = Color(0xF5F5F5);
+const uint GRAY200 = Color(0xEAEAEA);
+const uint GRAY300 = Color(0xE1E1E1);
+const uint GRAY400 = Color(0xCACACA);
+const uint GRAY500 = Color(0xB3B3B3);
+const uint GRAY600 = Color(0x8E8E8E);
+const uint GRAY700 = Color(0x707070);
+const uint GRAY800 = Color(0x4B4B4B);
+const uint GRAY900 = Color(0x2C2C2C);
+const uint BLUE400 = Color(0x2680EB);
+const uint BLUE500 = Color(0x1473E6);
+const uint BLUE600 = Color(0x0D66D0);
+const uint BLUE700 = Color(0x095ABA);
+const uint RED400 = Color(0xE34850);
+const uint RED500 = Color(0xD7373F);
+const uint RED600 = Color(0xC9252D);
+const uint RED700 = Color(0xBB121A);
+const uint ORANGE400 = Color(0xE68619);
+const uint ORANGE500 = Color(0xDA7B11);
+const uint ORANGE600 = Color(0xCB6F10);
+const uint ORANGE700 = Color(0xBD640D);
+const uint GREEN400 = Color(0x2D9D78);
+const uint GREEN500 = Color(0x268E6C);
+const uint GREEN600 = Color(0x12805C);
+const uint GREEN700 = Color(0x107154);
+const uint INDIGO400 = Color(0x6767EC);
+const uint INDIGO500 = Color(0x5C5CE0);
+const uint INDIGO600 = Color(0x5151D3);
+const uint INDIGO700 = Color(0x4646C6);
+const uint CELERY400 = Color(0x44B556);
+const uint CELERY500 = Color(0x3DA74E);
+const uint CELERY600 = Color(0x379947);
+const uint CELERY700 = Color(0x318B40);
+const uint MAGENTA400 = Color(0xD83790);
+const uint MAGENTA500 = Color(0xCE2783);
+const uint MAGENTA600 = Color(0xBC1C74);
+const uint MAGENTA700 = Color(0xAE0E66);
+const uint YELLOW400 = Color(0xDFBF00);
+const uint YELLOW500 = Color(0xD2B200);
+const uint YELLOW600 = Color(0xC4A600);
+const uint YELLOW700 = Color(0xB79900);
+const uint FUCHSIA400 = Color(0xC038CC);
+const uint FUCHSIA500 = Color(0xB130BD);
+const uint FUCHSIA600 = Color(0xA228AD);
+const uint FUCHSIA700 = Color(0x93219E);
+const uint SEAFOAM400 = Color(0x1B959A);
+const uint SEAFOAM500 = Color(0x16878C);
+const uint SEAFOAM600 = Color(0x0F797D);
+const uint SEAFOAM700 = Color(0x096C6F);
+const uint CHARTREUSE400 = Color(0x85D044);
+const uint CHARTREUSE500 = Color(0x7CC33F);
+const uint CHARTREUSE600 = Color(0x73B53A);
+const uint CHARTREUSE700 = Color(0x6AA834);
+const uint PURPLE400 = Color(0x9256D9);
+const uint PURPLE500 = Color(0x864CCC);
+const uint PURPLE600 = Color(0x7A42BF);
+const uint PURPLE700 = Color(0x6F38B1);
+} // namespace Light
+
+namespace Dark {
+const uint GRAY50 = Color(0x252525);
+const uint GRAY75 = Color(0x2F2F2F);
+const uint GRAY100 = Color(0x323232);
+const uint GRAY200 = Color(0x393939);
+const uint GRAY300 = Color(0x3E3E3E);
+const uint GRAY400 = Color(0x4D4D4D);
+const uint GRAY500 = Color(0x5C5C5C);
+const uint GRAY600 = Color(0x7B7B7B);
+const uint GRAY700 = Color(0x999999);
+const uint GRAY800 = Color(0xCDCDCD);
+const uint GRAY900 = Color(0xFFFFFF);
+const uint BLUE400 = Color(0x2680EB);
+const uint BLUE500 = Color(0x378EF0);
+const uint BLUE600 = Color(0x4B9CF5);
+const uint BLUE700 = Color(0x5AA9FA);
+const uint RED400 = Color(0xE34850);
+const uint RED500 = Color(0xEC5B62);
+const uint RED600 = Color(0xF76D74);
+const uint RED700 = Color(0xFF7B82);
+const uint ORANGE400 = Color(0xE68619);
+const uint ORANGE500 = Color(0xF29423);
+const uint ORANGE600 = Color(0xF9A43F);
+const uint ORANGE700 = Color(0xFFB55B);
+const uint GREEN400 = Color(0x2D9D78);
+const uint GREEN500 = Color(0x33AB84);
+const uint GREEN600 = Color(0x39B990);
+const uint GREEN700 = Color(0x3FC89C);
+const uint INDIGO400 = Color(0x6767EC);
+const uint INDIGO500 = Color(0x7575F1);
+const uint INDIGO600 = Color(0x8282F6);
+const uint INDIGO700 = Color(0x9090FA);
+const uint CELERY400 = Color(0x44B556);
+const uint CELERY500 = Color(0x4BC35F);
+const uint CELERY600 = Color(0x51D267);
+const uint CELERY700 = Color(0x58E06F);
+const uint MAGENTA400 = Color(0xD83790);
+const uint MAGENTA500 = Color(0xE2499D);
+const uint MAGENTA600 = Color(0xEC5AAA);
+const uint MAGENTA700 = Color(0xF56BB7);
+const uint YELLOW400 = Color(0xDFBF00);
+const uint YELLOW500 = Color(0xEDCC00);
+const uint YELLOW600 = Color(0xFAD900);
+const uint YELLOW700 = Color(0xFFE22E);
+const uint FUCHSIA400 = Color(0xC038CC);
+const uint FUCHSIA500 = Color(0xCF3EDC);
+const uint FUCHSIA600 = Color(0xD951E5);
+const uint FUCHSIA700 = Color(0xE366EF);
+const uint SEAFOAM400 = Color(0x1B959A);
+const uint SEAFOAM500 = Color(0x20A3A8);
+const uint SEAFOAM600 = Color(0x23B2B8);
+const uint SEAFOAM700 = Color(0x26C0C7);
+const uint CHARTREUSE400 = Color(0x85D044);
+const uint CHARTREUSE500 = Color(0x8EDE49);
+const uint CHARTREUSE600 = Color(0x9BEC54);
+const uint CHARTREUSE700 = Color(0xA3F858);
+const uint PURPLE400 = Color(0x9256D9);
+const uint PURPLE500 = Color(0x9D64E1);
+const uint PURPLE600 = Color(0xA873E9);
+const uint PURPLE700 = Color(0xB483F0);
+} // namespace Dark
+} // namespace Spectrum
+
+void setImguiStyleSpectrum(SpectrumStyle st) {
+ auto& style = ImGui::GetStyle();
+ style.WindowBorderSize = 1.0f;
+ style.ChildBorderSize = 1.0f;
+ style.PopupBorderSize = 1.0f;
+ style.FrameBorderSize = 1.0f;
+ style.TabBorderSize = 1.0f;
+ style.WindowRounding = 8.0f;
+ style.TabRounding = 6.0f;
+ style.ChildRounding = 6.0f;
+ style.PopupRounding = 6.0f;
+ style.FrameRounding = 4.0f;
+ style.ScrollbarRounding = 4.0f;
+ style.GrabRounding = 4.0f;
+
+ auto& colors = style.Colors;
+
+ using ImGui::ColorConvertU32ToFloat4;
+ using namespace Spectrum;
+
+ colors[ImGuiCol_BorderShadow] = ColorConvertU32ToFloat4(Static::NONE); // We don't want shadows. Ever.
+ colors[ImGuiCol_ChildBg] = ColorConvertU32ToFloat4(Static::NONE);
+ colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f);
+ colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f);
+ colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.20f, 0.20f, 0.20f, 0.35f);
+ colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f);
+
+ if (st == SpectrumStyle::Light) {
+ using namespace Light;
+
+ colors[ImGuiCol_Text] = ColorConvertU32ToFloat4(GRAY800); // text on hovered controls is gray900
+ colors[ImGuiCol_TextDisabled] = ColorConvertU32ToFloat4(GRAY500);
+ colors[ImGuiCol_WindowBg] = ColorConvertU32ToFloat4(GRAY100);
+ colors[ImGuiCol_PopupBg] =
+ ColorConvertU32ToFloat4(GRAY50); // not sure about this. Note: applies to tooltips too.
+ colors[ImGuiCol_Border] = ColorConvertU32ToFloat4(GRAY300);
+ colors[ImGuiCol_FrameBg] =
+ ColorConvertU32ToFloat4(GRAY75); // this isnt right, spectrum does not do this, but it's a good fallback
+ colors[ImGuiCol_FrameBgHovered] = ColorConvertU32ToFloat4(GRAY50);
+ colors[ImGuiCol_FrameBgActive] = ColorConvertU32ToFloat4(GRAY200);
+ colors[ImGuiCol_TitleBg] =
+ ColorConvertU32ToFloat4(GRAY300); // those titlebar values are totally made up, spectrum does not have this.
+ colors[ImGuiCol_TitleBgActive] = ColorConvertU32ToFloat4(GRAY200);
+ colors[ImGuiCol_TitleBgCollapsed] = ColorConvertU32ToFloat4(GRAY400);
+ colors[ImGuiCol_MenuBarBg] = ColorConvertU32ToFloat4(GRAY100);
+ colors[ImGuiCol_ScrollbarBg] = ColorConvertU32ToFloat4(GRAY100); // same as regular background
+ colors[ImGuiCol_ScrollbarGrab] = ColorConvertU32ToFloat4(GRAY400);
+ colors[ImGuiCol_ScrollbarGrabHovered] = ColorConvertU32ToFloat4(GRAY600);
+ colors[ImGuiCol_ScrollbarGrabActive] = ColorConvertU32ToFloat4(GRAY700);
+ colors[ImGuiCol_CheckMark] = ColorConvertU32ToFloat4(BLUE500);
+ colors[ImGuiCol_SliderGrab] = ColorConvertU32ToFloat4(GRAY700);
+ colors[ImGuiCol_SliderGrabActive] = ColorConvertU32ToFloat4(GRAY800);
+ colors[ImGuiCol_Button] =
+ ColorConvertU32ToFloat4(GRAY75); // match default button to Spectrum's 'Action Button'.
+ colors[ImGuiCol_ButtonHovered] = ColorConvertU32ToFloat4(GRAY50);
+ colors[ImGuiCol_ButtonActive] = ColorConvertU32ToFloat4(GRAY200);
+ colors[ImGuiCol_Header] = ColorConvertU32ToFloat4(BLUE400);
+ colors[ImGuiCol_HeaderHovered] = ColorConvertU32ToFloat4(BLUE500);
+ colors[ImGuiCol_HeaderActive] = ColorConvertU32ToFloat4(BLUE600);
+ colors[ImGuiCol_Separator] = ColorConvertU32ToFloat4(GRAY400);
+ colors[ImGuiCol_SeparatorHovered] = ColorConvertU32ToFloat4(GRAY600);
+ colors[ImGuiCol_SeparatorActive] = ColorConvertU32ToFloat4(GRAY700);
+ colors[ImGuiCol_ResizeGrip] = ColorConvertU32ToFloat4(GRAY400);
+ colors[ImGuiCol_ResizeGripHovered] = ColorConvertU32ToFloat4(GRAY600);
+ colors[ImGuiCol_ResizeGripActive] = ColorConvertU32ToFloat4(GRAY700);
+ colors[ImGuiCol_PlotLines] = ColorConvertU32ToFloat4(BLUE400);
+ colors[ImGuiCol_PlotLinesHovered] = ColorConvertU32ToFloat4(BLUE600);
+ colors[ImGuiCol_PlotHistogram] = ColorConvertU32ToFloat4(BLUE400);
+ colors[ImGuiCol_PlotHistogramHovered] = ColorConvertU32ToFloat4(BLUE600);
+ colors[ImGuiCol_TextSelectedBg] = ColorConvertU32ToFloat4((BLUE400 & 0x00FFFFFF) | 0x33000000);
+ colors[ImGuiCol_NavHighlight] = ColorConvertU32ToFloat4((GRAY900 & 0x00FFFFFF) | 0x0A000000);
+ } else {
+ using namespace Dark;
+
+ colors[ImGuiCol_Text] = ColorConvertU32ToFloat4(GRAY800); // text on hovered controls is gray900
+ colors[ImGuiCol_TextDisabled] = ColorConvertU32ToFloat4(GRAY500);
+ colors[ImGuiCol_WindowBg] = ColorConvertU32ToFloat4(GRAY100);
+ colors[ImGuiCol_PopupBg] =
+ ColorConvertU32ToFloat4(GRAY50); // not sure about this. Note: applies to tooltips too.
+ colors[ImGuiCol_Border] = ColorConvertU32ToFloat4(GRAY300);
+ colors[ImGuiCol_FrameBg] =
+ ColorConvertU32ToFloat4(GRAY75); // this isnt right, spectrum does not do this, but it's a good fallback
+ colors[ImGuiCol_FrameBgHovered] = ColorConvertU32ToFloat4(GRAY50);
+ colors[ImGuiCol_FrameBgActive] = ColorConvertU32ToFloat4(GRAY200);
+ colors[ImGuiCol_TitleBg] =
+ ColorConvertU32ToFloat4(GRAY300); // those titlebar values are totally made up, spectrum does not have this.
+ colors[ImGuiCol_TitleBgActive] = ColorConvertU32ToFloat4(GRAY200);
+ colors[ImGuiCol_TitleBgCollapsed] = ColorConvertU32ToFloat4(GRAY400);
+ colors[ImGuiCol_MenuBarBg] = ColorConvertU32ToFloat4(GRAY100);
+ colors[ImGuiCol_ScrollbarBg] = ColorConvertU32ToFloat4(GRAY100); // same as regular background
+ colors[ImGuiCol_ScrollbarGrab] = ColorConvertU32ToFloat4(GRAY400);
+ colors[ImGuiCol_ScrollbarGrabHovered] = ColorConvertU32ToFloat4(GRAY600);
+ colors[ImGuiCol_ScrollbarGrabActive] = ColorConvertU32ToFloat4(GRAY700);
+ colors[ImGuiCol_CheckMark] = ColorConvertU32ToFloat4(BLUE500);
+ colors[ImGuiCol_SliderGrab] = ColorConvertU32ToFloat4(GRAY700);
+ colors[ImGuiCol_SliderGrabActive] = ColorConvertU32ToFloat4(GRAY800);
+ colors[ImGuiCol_Button] =
+ ColorConvertU32ToFloat4(GRAY75); // match default button to Spectrum's 'Action Button'.
+ colors[ImGuiCol_ButtonHovered] = ColorConvertU32ToFloat4(GRAY50);
+ colors[ImGuiCol_ButtonActive] = ColorConvertU32ToFloat4(GRAY200);
+ colors[ImGuiCol_Header] = ColorConvertU32ToFloat4(BLUE400);
+ colors[ImGuiCol_HeaderHovered] = ColorConvertU32ToFloat4(BLUE500);
+ colors[ImGuiCol_HeaderActive] = ColorConvertU32ToFloat4(BLUE600);
+ colors[ImGuiCol_Separator] = ColorConvertU32ToFloat4(GRAY400);
+ colors[ImGuiCol_SeparatorHovered] = ColorConvertU32ToFloat4(GRAY600);
+ colors[ImGuiCol_SeparatorActive] = ColorConvertU32ToFloat4(GRAY700);
+ colors[ImGuiCol_ResizeGrip] = ColorConvertU32ToFloat4(GRAY400);
+ colors[ImGuiCol_ResizeGripHovered] = ColorConvertU32ToFloat4(GRAY600);
+ colors[ImGuiCol_ResizeGripActive] = ColorConvertU32ToFloat4(GRAY700);
+ colors[ImGuiCol_PlotLines] = ColorConvertU32ToFloat4(BLUE400);
+ colors[ImGuiCol_PlotLinesHovered] = ColorConvertU32ToFloat4(BLUE600);
+ colors[ImGuiCol_PlotHistogram] = ColorConvertU32ToFloat4(BLUE400);
+ colors[ImGuiCol_PlotHistogramHovered] = ColorConvertU32ToFloat4(BLUE600);
+ colors[ImGuiCol_TextSelectedBg] = ColorConvertU32ToFloat4((BLUE400 & 0x00FFFFFF) | 0x33000000);
+ colors[ImGuiCol_NavHighlight] = ColorConvertU32ToFloat4((GRAY900 & 0x00FFFFFF) | 0x0A000000);
+ }
+}
+} // namespace ll::gui
diff --git a/src-client/ll/core/gui/styles/ImguiSpectrum.h b/src-client/ll/core/gui/styles/ImguiSpectrum.h
new file mode 100644
index 0000000000..c628b6c5ba
--- /dev/null
+++ b/src-client/ll/core/gui/styles/ImguiSpectrum.h
@@ -0,0 +1,9 @@
+#pragma once
+
+namespace ll::gui {
+
+enum class SpectrumStyle { Dark, Light };
+
+void setImguiStyleSpectrum(SpectrumStyle);
+
+} // namespace ll::gui
diff --git a/src-client/ll/core/gui/win/GUI.cpp b/src-client/ll/core/gui/win/GUI.cpp
new file mode 100644
index 0000000000..33f0ee47dc
--- /dev/null
+++ b/src-client/ll/core/gui/win/GUI.cpp
@@ -0,0 +1,278 @@
+#include "ll/core/gui/GUI.h"
+
+#include
+#include
+#include
+
+#include "dwrite.h"
+#include "windows.h"
+#include "wingdi.h"
+#include "winuser.h"
+
+#include "atlbase.h"
+
+#include "imgui.h"
+
+#include "ll/api/utils/FontUtils.h"
+#include "ll/api/utils/StringUtils.h"
+#include "ll/core/gui/ImGuiAnsiColor.h"
+#include "ll/core/gui/ImGuiHooks.h"
+#include "ll/core/gui/ImguiConfig.h"
+#include "ll/core/gui/styles/ImguiSpectrum.h"
+#include "ll/core/io/LogPipe.h"
+
+namespace ll::gui {
+class LogWindow {
+ std::string title;
+ bool open = false;
+ ImGuiTextBuffer buf;
+ ImGuiTextFilter filter;
+ std::vector lineOffsets;
+ bool autoScroll = true;
+
+public:
+ LogWindow(const std::string& title) : title(title) { clear(); }
+
+ void clear() {
+ buf.clear();
+ lineOffsets.clear();
+ lineOffsets.push_back(0);
+ }
+
+ void addLog(const std::string& log) {
+ if (log.empty()) {
+ return;
+ }
+ int oldSize = buf.size();
+ buf.append(log.c_str());
+ for (int newSize = buf.size(); oldSize < newSize; oldSize++) {
+ if (buf[oldSize] == '\n') {
+ lineOffsets.push_back(oldSize + 1);
+ }
+ }
+ }
+
+ void draw() {
+ if (!open) {
+ return;
+ }
+
+ if (!ImGui::Begin(title.c_str(), &open)) {
+ ImGui::End();
+ return;
+ }
+
+ bool clear = ImGui::Button("Clear");
+ ImGui::SameLine();
+ bool copy = ImGui::Button("Copy");
+ ImGui::SameLine();
+ ImGui::Checkbox("Auto-scroll", &autoScroll);
+ ImGui::SameLine();
+ filter.Draw("Filter", -100.0f);
+
+ ImGui::Separator();
+
+ if (ImGui::BeginChild("scrolling", ImVec2(0, 0), false, ImGuiWindowFlags_HorizontalScrollbar)) {
+ if (clear) {
+ this->clear();
+ }
+ if (copy) {
+ ImGui::LogToClipboard();
+ }
+ ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
+ const char* bufBegin = this->buf.begin();
+ const char* bufEnd = this->buf.end();
+ if (filter.IsActive()) {
+ // Don't use the clipper when Filter is enabled because we don't have random access
+ // to the result of the filter
+ for (int lineNum = 0; lineNum < lineOffsets.size(); lineNum++) {
+ const char* lineStart = bufBegin + lineOffsets[lineNum];
+ const char* lineEnd =
+ (lineNum + 1 < lineOffsets.size()) ? (bufBegin + lineOffsets[lineNum + 1] - 1) : bufEnd;
+ if (filter.PassFilter(lineStart, lineEnd)) {
+ textAnsiUnformatted({lineStart, lineEnd});
+ }
+ }
+ } else {
+ // Use ImGuiListClipper for better performance
+ ImGuiListClipper clipper;
+ clipper.Begin((int)lineOffsets.size());
+ while (clipper.Step()) {
+ for (int lineNum = clipper.DisplayStart; lineNum < clipper.DisplayEnd; lineNum++) {
+ const char* lineStart = bufBegin + lineOffsets[lineNum];
+ const char* lineEnd =
+ (lineNum + 1 < lineOffsets.size()) ? (bufBegin + lineOffsets[lineNum + 1] - 1) : bufEnd;
+ textAnsiUnformatted({lineStart, lineEnd});
+ }
+ }
+ clipper.End();
+ }
+ ImGui::PopStyleVar();
+
+ if (autoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) {
+ ImGui::SetScrollHereY(1.0f);
+ }
+ }
+ ImGui::EndChild();
+ ImGui::End();
+ }
+
+ bool isOpen() const { return open; }
+
+ void setOpen(bool b) { this->open = b; }
+};
+
+static LogWindow logWindow("Log");
+
+void initializeImGui() {
+ ImGui::CreateContext();
+
+ ImGuiIO& io = ImGui::GetIO();
+ io.IniFilename = nullptr;
+
+ io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
+
+ // io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;
+ // io.BackendFlags |= ImGuiBackendFlags_PlatformHasViewports;
+ // io.BackendFlags |= ImGuiBackendFlags_HasMouseHoveredViewport;
+ // io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports;
+
+ logWindow.setOpen(true);
+}
+
+void updateImGui() {
+ static bool showPerformance = true;
+ static bool showDemo = true;
+
+ logWindow.addLog(io::getDefaultLogPipe().read());
+
+ auto& io = ImGui::GetIO();
+ ImGui::NewFrame();
+ if (showPerformance) {
+ ImGui::SetNextWindowPos(ImVec2(10, 10), ImGuiCond_FirstUseEver);
+ if (ImGui::Begin("Performance", &showPerformance)) {
+ static float framerate = io.Framerate;
+ static float deltaTime = io.DeltaTime;
+ static float updateTimer = 0.5f;
+ updateTimer -= io.DeltaTime;
+ if (updateTimer <= 0) {
+ framerate = io.Framerate;
+ deltaTime = io.DeltaTime;
+ updateTimer = 0.5f;
+ }
+ ImGui::Text("Renderer: %s", getRendererType().c_str());
+ ImGui::Text("Renderer Name: %s", getGPUName().c_str());
+ ImGui::Text("FPS: %.01f", framerate);
+ ImGui::Text("Frame Time: %.01fms", deltaTime * 1000.0f);
+ }
+ }
+
+ if (logWindow.isOpen()) {
+ ImGui::SetNextWindowPos(ImVec2(10, 200), ImGuiCond_FirstUseEver);
+ ImGui::SetNextWindowSize(ImVec2(860, 480), ImGuiCond_FirstUseEver);
+ logWindow.draw();
+ }
+
+ if (showDemo) {
+ ImGui::ShowDemoWindow(&showDemo);
+ }
+ ImGui::EndFrame();
+}
+
+void updateScaling(float dpi) {
+ float scale = dpi / 96.0f;
+
+ getLeviImguiConfig().style.applyToImgui();
+
+ ImGuiStyle& style = ImGui::GetStyle();
+
+ style.ScaleAllSizes(scale);
+
+ updateFonts(scale);
+}
+
+void updateFonts(float scale) {
+ using namespace font_utils;
+ ImGuiIO& io = ImGui::GetIO();
+ io.Fonts->Clear();
+ auto& settings = getLeviImguiConfig().fontSettings;
+ auto size = settings.size;
+ auto& fonts = settings.fonts;
+ if (fonts.empty()) {
+ ImFontConfig fontCfg;
+ fontCfg.SizePixels = size * scale;
+ fontCfg.RasterizerDensity = scale;
+ io.Fonts->AddFontDefault(&fontCfg);
+ return;
+ }
+ bool first = true;
+ for (auto& font : fonts) {
+ ImFontConfig config;
+ config.MergeMode = true;
+ config.SizePixels = size * scale;
+ config.RasterizerDensity = scale;
+ switch (font.range) {
+ case LeviImguiConfig::Font::GlyphRange::Greek:
+ config.GlyphRanges = io.Fonts->GetGlyphRangesGreek();
+ break;
+ case LeviImguiConfig::Font::GlyphRange::Korean:
+ config.GlyphRanges = io.Fonts->GetGlyphRangesKorean();
+ break;
+ case LeviImguiConfig::Font::GlyphRange::Japanese:
+ config.GlyphRanges = io.Fonts->GetGlyphRangesJapanese();
+ break;
+ case LeviImguiConfig::Font::GlyphRange::ChineseFull:
+ config.GlyphRanges = io.Fonts->GetGlyphRangesChineseFull();
+ break;
+ case LeviImguiConfig::Font::GlyphRange::ChineseSimplifiedCommon:
+ config.GlyphRanges = io.Fonts->GetGlyphRangesChineseSimplifiedCommon();
+ break;
+ case LeviImguiConfig::Font::GlyphRange::Cyrillic:
+ config.GlyphRanges = io.Fonts->GetGlyphRangesCyrillic();
+ break;
+ case LeviImguiConfig::Font::GlyphRange::Thai:
+ config.GlyphRanges = io.Fonts->GetGlyphRangesThai();
+ break;
+ case LeviImguiConfig::Font::GlyphRange::Vietnamese:
+ config.GlyphRanges = io.Fonts->GetGlyphRangesVietnamese();
+ break;
+ default:
+ config.GlyphRanges = io.Fonts->GetGlyphRangesDefault();
+ }
+ if (font.width) {
+ if (*font.width == LeviImguiConfig::Font::Width::Half) {
+ config.GlyphMaxAdvanceX = config.SizePixels * 0.5f;
+ config.GlyphMinAdvanceX = config.SizePixels * 0.5f;
+ } else if (*font.width == LeviImguiConfig::Font::Width::Full) {
+ config.GlyphMaxAdvanceX = config.SizePixels;
+ config.GlyphMinAdvanceX = config.SizePixels;
+ }
+ }
+ if (first) {
+ first = false;
+ config.MergeMode = false;
+ }
+ if (font.name == "default") {
+ io.Fonts->AddFontDefault(&config);
+ } else {
+ std::optional name;
+ if (font.name == "system") {
+ name = getSystemDefaultFontName();
+ } else {
+ name = font.name;
+ }
+ if (name) {
+ auto paths =
+ getFilePathFromFontName(*name, (uint&)config.FontNo, font.weight, font.stretch, font.style);
+ if (!paths.empty())
+ io.Fonts->AddFontFromFileTTF(
+ (char const*)(paths.front().u8string().c_str()),
+ config.SizePixels,
+ &config,
+ config.GlyphRanges
+ );
+ }
+ }
+ }
+}
+} // namespace ll::gui
diff --git a/src-client/ll/core/gui/win/ImGuiHooks.cpp b/src-client/ll/core/gui/win/ImGuiHooks.cpp
new file mode 100644
index 0000000000..b5e68c688c
--- /dev/null
+++ b/src-client/ll/core/gui/win/ImGuiHooks.cpp
@@ -0,0 +1,497 @@
+#include "ll/core/gui/ImGuiHooks.h"
+
+#include "windows.h"
+
+#include "psapi.h"
+
+#include "atlbase.h"
+
+#include "initguid.h"
+
+#include "d3d12.h"
+
+#include "d3d11.h"
+
+#include
+#include
+
+#include "backends/imgui_impl_dx12.h"
+
+#include "backends/imgui_impl_dx11.h"
+
+#include "backends/imgui_impl_winrt.h"
+
+#include "dxgi1_6.h"
+
+#include "ll/api/memory/Hook.h"
+#include "ll/api/utils/StringUtils.h"
+
+#include "ll/core/gui/GUI.h"
+
+
+namespace ll::gui {
+typedef HRESULT(STDMETHODCALLTYPE* PFN_CreateDXGIFactory1)(REFIID riid, _COM_Outptr_ void** ppFactory);
+// 16
+typedef HRESULT(STDMETHODCALLTYPE* PFN_IDXGIFactory2_CreateSwapChainForCoreWindow)(
+ IDXGIFactory2* This,
+ /* [annotation][in] */
+ _In_ IUnknown* pDevice,
+ /* [annotation][in] */
+ _In_ IUnknown* pWindow,
+ /* [annotation][in] */
+ _In_ const DXGI_SWAP_CHAIN_DESC1* pDesc,
+ /* [annotation][in] */
+ _In_opt_ IDXGIOutput* pRestrictToOutput,
+ /* [annotation][out] */
+ _COM_Outptr_ IDXGISwapChain1** ppSwapChain
+);
+// 8
+typedef HRESULT(STDMETHODCALLTYPE* PFN_IDXGISwapChain_Present)(
+ IDXGISwapChain* This,
+ /* [in] */ UINT SyncInterval,
+ /* [in] */ UINT Flags
+);
+// 13
+typedef HRESULT(STDMETHODCALLTYPE* PFN_IDXGISwapChain_ResizeBuffers)(
+ IDXGISwapChain* This,
+ /* [in] */ UINT BufferCount,
+ /* [in] */ UINT Width,
+ /* [in] */ UINT Height,
+ /* [in] */ DXGI_FORMAT NewFormat,
+ /* [in] */ UINT SwapChainFlags
+);
+
+static IDXGIFactory2* factory;
+static IUnknown* coreWindow;
+static bool imguiInitialized = false;
+static std::string rendererType;
+static std::string gpuName = "unknown";
+static uint64_t gpuVideoMemory;
+static uint32_t gpuVendorID;
+static uint32_t gpuDeviceID;
+
+const std::string& getGPUName() { return gpuName; }
+
+const std::string& getRendererType() { return rendererType; }
+
+static void replaceVtable(void* vptr, size_t index, void** outOldFunc, void* newFunc) {
+ void** ptr = (void**)vptr;
+ void* oldFunc = ptr[index];
+ if (oldFunc == newFunc) {
+ return;
+ }
+ if (outOldFunc != nullptr) {
+ *outOldFunc = oldFunc;
+ }
+
+ ll::memory::modify(&ptr[index], sizeof(void*), [=]() { ptr[index] = newFunc; });
+}
+
+namespace D3D12 {
+CComPtr device;
+CComPtr descriptorHeapBackBuffers;
+CComPtr descriptorHeapImGuiRender;
+ID3D12CommandQueue* commandQueue;
+
+struct BackBufferContext {
+ CComPtr commandAllocator;
+ CComPtr commandList;
+ ID3D12Resource* resource = nullptr;
+ D3D12_CPU_DESCRIPTOR_HANDLE descriptorHandle = {0};
+};
+
+uint32_t backBufferCount = 0;
+std::unique_ptr backBufferContext;
+
+void createRT(IDXGISwapChain* swapChain) {
+ const auto rtvDescriptorSize = device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
+ D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = descriptorHeapBackBuffers->GetCPUDescriptorHandleForHeapStart();
+
+ for (uint32_t i = 0; i < backBufferCount; i++) {
+ ID3D12Resource* pBackBuffer = nullptr;
+ backBufferContext[i].descriptorHandle = rtvHandle;
+ swapChain->GetBuffer(i, IID_PPV_ARGS(&pBackBuffer));
+ device->CreateRenderTargetView(pBackBuffer, nullptr, rtvHandle);
+ backBufferContext[i].resource = pBackBuffer;
+ rtvHandle.ptr += rtvDescriptorSize;
+ }
+}
+
+void releaseRT() {
+ for (size_t i = 0; i < backBufferCount; i++) {
+ if (backBufferContext[i].resource) {
+ backBufferContext[i].resource->Release();
+ backBufferContext[i].resource = nullptr;
+ }
+ }
+}
+
+bool initializeImguiBackend(IDXGISwapChain* pSwapChain) {
+ if (SUCCEEDED(pSwapChain->GetDevice(IID_ID3D12Device, (void**)&device))) {
+ rendererType = "Direct3D 12";
+ CComPtr factory4;
+ if (SUCCEEDED(factory->QueryInterface(&factory4))) {
+ CComPtr adapter;
+ if (SUCCEEDED(factory4->EnumAdapterByLuid(device->GetAdapterLuid(), IID_PPV_ARGS(&adapter)))) {
+ DXGI_ADAPTER_DESC1 adapterDesc;
+ adapter->GetDesc1(&adapterDesc);
+ gpuName = ll::string_utils::wstr2str(adapterDesc.Description);
+ gpuVendorID = adapterDesc.VendorId;
+ gpuDeviceID = adapterDesc.DeviceId;
+ gpuVideoMemory = adapterDesc.DedicatedVideoMemory;
+ }
+ }
+
+ initializeImGui();
+
+ DXGI_SWAP_CHAIN_DESC desc;
+ pSwapChain->GetDesc(&desc);
+
+ backBufferCount = desc.BufferCount;
+ backBufferContext.reset(new BackBufferContext[backBufferCount]);
+
+ for (size_t i = 0; i < backBufferCount; i++) {
+ if (device->CreateCommandAllocator(
+ D3D12_COMMAND_LIST_TYPE_DIRECT,
+ IID_PPV_ARGS(&backBufferContext[i].commandAllocator)
+ )
+ != S_OK) {
+ return false;
+ }
+
+ if (device->CreateCommandList(
+ 0,
+ D3D12_COMMAND_LIST_TYPE_DIRECT,
+ backBufferContext[i].commandAllocator,
+ nullptr,
+ IID_PPV_ARGS(&backBufferContext[i].commandList)
+ ) != S_OK
+ || backBufferContext[i].commandList->Close() != S_OK) {
+ return false;
+ }
+ }
+
+ D3D12_DESCRIPTOR_HEAP_DESC descriptorImGuiRender = {};
+ descriptorImGuiRender.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
+ descriptorImGuiRender.NumDescriptors = backBufferCount;
+ descriptorImGuiRender.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
+
+ if (device->CreateDescriptorHeap(&descriptorImGuiRender, IID_PPV_ARGS(&descriptorHeapImGuiRender)) != S_OK) {
+ return false;
+ }
+
+ D3D12_DESCRIPTOR_HEAP_DESC descriptorBackBuffers;
+ descriptorBackBuffers.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
+ descriptorBackBuffers.NumDescriptors = backBufferCount;
+ descriptorBackBuffers.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
+ descriptorBackBuffers.NodeMask = 1;
+
+ if (device->CreateDescriptorHeap(&descriptorBackBuffers, IID_PPV_ARGS(&descriptorHeapBackBuffers)) != S_OK) {
+ return false;
+ }
+
+ createRT(pSwapChain);
+
+ ImGui_ImplWinRT_Init(coreWindow);
+ ImGui_ImplDX12_Init(
+ device,
+ backBufferCount,
+ DXGI_FORMAT_R8G8B8A8_UNORM,
+ descriptorHeapImGuiRender,
+ descriptorHeapImGuiRender->GetCPUDescriptorHandleForHeapStart(),
+ descriptorHeapImGuiRender->GetGPUDescriptorHandleForHeapStart()
+ );
+ ImGui_ImplDX12_CreateDeviceObjects();
+ }
+ return true;
+}
+
+void renderImGui(IDXGISwapChain3* swapChain) {
+ ImGui_ImplWinRT_Data* bd = (ImGui_ImplWinRT_Data*)ImGui::GetIO().BackendPlatformUserData;
+ if (bd && bd->DPIChanged) {
+ ImGui_ImplDX12_InvalidateDeviceObjects();
+ updateScaling(bd->DPI);
+ ImGui_ImplDX12_CreateDeviceObjects();
+ bd->DPIChanged = false;
+ }
+
+ ImGui_ImplDX12_NewFrame();
+ ImGui_ImplWinRT_NewFrame();
+
+ updateImGui();
+
+ BackBufferContext& currentBufferContext = backBufferContext[swapChain->GetCurrentBackBufferIndex()];
+ currentBufferContext.commandAllocator->Reset();
+
+ D3D12_RESOURCE_BARRIER barrier;
+ barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
+ barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
+ barrier.Transition.pResource = currentBufferContext.resource;
+ barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
+ barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT;
+ barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;
+
+ ID3D12DescriptorHeap* descriptorHeaps[1] = {descriptorHeapImGuiRender.p};
+ currentBufferContext.commandList->Reset(currentBufferContext.commandAllocator, nullptr);
+ currentBufferContext.commandList->ResourceBarrier(1, &barrier);
+ currentBufferContext.commandList->OMSetRenderTargets(1, ¤tBufferContext.descriptorHandle, FALSE, nullptr);
+ currentBufferContext.commandList->SetDescriptorHeaps(1, descriptorHeaps);
+
+ ImGui::Render();
+ ImGui_ImplDX12_RenderDrawData(ImGui::GetDrawData(), currentBufferContext.commandList);
+
+ ID3D12CommandList* commandLists[1] = {currentBufferContext.commandList.p};
+ barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
+ barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT;
+ currentBufferContext.commandList->ResourceBarrier(1, &barrier);
+ currentBufferContext.commandList->Close();
+ commandQueue->ExecuteCommandLists(1, commandLists);
+}
+
+PFN_IDXGISwapChain_Present IDXGISwapChain_Present_Original = nullptr;
+HRESULT STDMETHODCALLTYPE IDXGISwapChain_Present_Hook(IDXGISwapChain* This, UINT SyncInterval, UINT Flags) {
+ CComPtr swapChain3;
+ if (FAILED(This->QueryInterface(&swapChain3))) {
+ return IDXGISwapChain_Present_Original(This, SyncInterval, Flags);
+ }
+
+ if (!imguiInitialized) {
+ // printf("Initializing ImGui on Direct3D 12\n");
+ if (initializeImguiBackend(This)) {
+ imguiInitialized = true;
+ } else {
+ // printf("ImGui is not initialized\n");
+ return IDXGISwapChain_Present_Original(This, SyncInterval, Flags);
+ }
+ }
+
+ renderImGui(swapChain3);
+
+ return IDXGISwapChain_Present_Original(This, SyncInterval, Flags);
+}
+
+PFN_IDXGISwapChain_ResizeBuffers IDXGISwapChain_ResizeBuffers_Original;
+HRESULT STDMETHODCALLTYPE IDXGISwapChain_ResizeBuffers_Hook(
+ IDXGISwapChain* This,
+ UINT BufferCount,
+ UINT Width,
+ UINT Height,
+ DXGI_FORMAT NewFormat,
+ UINT SwapChainFlags
+ ) {
+ releaseRT();
+ HRESULT hResult =
+ IDXGISwapChain_ResizeBuffers_Original(This, BufferCount, Width, Height, NewFormat, SwapChainFlags);
+ createRT(This);
+ return hResult;
+}
+} // namespace D3D12
+
+namespace D3D11 {
+ID3D11Device* device;
+CComPtr deviceContext;
+
+uint32_t backBufferCount = 0;
+ID3D11RenderTargetView** renderTargetViews;
+
+void createRT(IDXGISwapChain* swapChain) {
+ for (uint32_t i = 0; i < backBufferCount; i++) {
+ CComPtr backBuffer;
+ swapChain->GetBuffer(i, IID_PPV_ARGS(&backBuffer));
+
+ ID3D11RenderTargetView* rtv;
+ device->CreateRenderTargetView(backBuffer, nullptr, &rtv);
+
+ renderTargetViews[i] = rtv;
+ }
+}
+
+void releaseRT() {
+ for (size_t i = 0; i < backBufferCount; i++) {
+ if (renderTargetViews[i]) {
+ renderTargetViews[i]->Release();
+ renderTargetViews[i] = nullptr;
+ }
+ }
+}
+
+bool initializeImguiBackend(IDXGISwapChain* pSwapChain) {
+ rendererType = "Direct3D 11";
+
+ initializeImGui();
+
+ DXGI_SWAP_CHAIN_DESC desc;
+ pSwapChain->GetDesc(&desc);
+
+ backBufferCount = desc.BufferCount;
+ renderTargetViews = new ID3D11RenderTargetView*[backBufferCount];
+
+ device->GetImmediateContext(&deviceContext);
+
+ createRT(pSwapChain);
+
+ ImGui_ImplWinRT_Init(coreWindow);
+ ImGui_ImplDX11_Init(device, deviceContext);
+ ImGui_ImplDX11_CreateDeviceObjects();
+
+ return true;
+}
+
+void renderImGui(IDXGISwapChain3* swapChain) {
+ ImGui_ImplWinRT_Data* bd = (ImGui_ImplWinRT_Data*)ImGui::GetIO().BackendPlatformUserData;
+ if (bd && bd->DPIChanged) {
+ ImGui_ImplDX11_InvalidateDeviceObjects();
+ updateScaling(bd->DPI);
+ ImGui_ImplDX11_CreateDeviceObjects();
+ bd->DPIChanged = false;
+ }
+
+ ImGui_ImplDX11_NewFrame();
+ ImGui_ImplWinRT_NewFrame();
+
+ updateImGui();
+
+ ID3D11RenderTargetView* currentRTV = renderTargetViews[swapChain->GetCurrentBackBufferIndex()];
+ deviceContext->OMSetRenderTargets(1, ¤tRTV, nullptr);
+
+ ImGui::Render();
+ ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
+}
+
+PFN_IDXGISwapChain_Present IDXGISwapChain_Present_Original = nullptr;
+HRESULT STDMETHODCALLTYPE IDXGISwapChain_Present_Hook(IDXGISwapChain* This, UINT SyncInterval, UINT Flags) {
+ CComPtr swapChain3;
+ if (FAILED(This->QueryInterface(&swapChain3))) {
+ return IDXGISwapChain_Present_Original(This, SyncInterval, Flags);
+ }
+
+ if (imguiInitialized) {
+ renderImGui(swapChain3);
+ }
+
+ return IDXGISwapChain_Present_Original(This, SyncInterval, Flags);
+}
+
+PFN_IDXGISwapChain_ResizeBuffers IDXGISwapChain_ResizeBuffers_Original;
+HRESULT STDMETHODCALLTYPE IDXGISwapChain_ResizeBuffers_Hook(
+ IDXGISwapChain* This,
+ UINT BufferCount,
+ UINT Width,
+ UINT Height,
+ DXGI_FORMAT NewFormat,
+ UINT SwapChainFlags
+ ) {
+ releaseRT();
+ HRESULT hResult =
+ IDXGISwapChain_ResizeBuffers_Original(This, BufferCount, Width, Height, NewFormat, SwapChainFlags);
+ createRT(This);
+ return hResult;
+}
+} // namespace D3D11
+
+PFN_IDXGIFactory2_CreateSwapChainForCoreWindow IDXGIFactory2_CreateSwapChainForCoreWindow_Original;
+HRESULT STDMETHODCALLTYPE IDXGIFactory2_CreateSwapChainForCoreWindow_Hook(
+ IDXGIFactory2* This,
+ IUnknown* pDevice,
+ IUnknown* pWindow,
+ const DXGI_SWAP_CHAIN_DESC1* pDesc,
+ IDXGIOutput* pRestrictToOutput,
+ IDXGISwapChain1** ppSwapChain
+ ) {
+ if (pWindow) {
+ coreWindow = pWindow;
+ }
+
+ HRESULT hResult = IDXGIFactory2_CreateSwapChainForCoreWindow_Original(
+ This,
+ pDevice,
+ pWindow,
+ pDesc,
+ pRestrictToOutput,
+ ppSwapChain
+ );
+ if (SUCCEEDED(hResult)) {
+ IDXGISwapChain1* swapChain = *ppSwapChain;
+ CComPtr d3d12CommandQueue;
+ CComPtr d3d11Device;
+ if (SUCCEEDED(pDevice->QueryInterface(&d3d12CommandQueue))) {
+ // Direct3D 12
+ D3D12::commandQueue = (ID3D12CommandQueue*)pDevice;
+ if (!D3D12::IDXGISwapChain_Present_Original) {
+ replaceVtable(
+ *(void**)swapChain,
+ 8,
+ (void**)&D3D12::IDXGISwapChain_Present_Original,
+ D3D12::IDXGISwapChain_Present_Hook
+ );
+ }
+ if (!D3D12::IDXGISwapChain_ResizeBuffers_Original) {
+ replaceVtable(
+ *(void**)swapChain,
+ 13,
+ (void**)&D3D12::IDXGISwapChain_ResizeBuffers_Original,
+ D3D12::IDXGISwapChain_ResizeBuffers_Hook
+ );
+ }
+ // When the graphics API used by RenderDragon is D3D12, this function will be called in a non-main thread
+ // and later IDXGISwapChain::Present will be called three times in the main thread, so initialize ImGui
+ // later in IDXGISwapChain::Present
+ } else if (SUCCEEDED(pDevice->QueryInterface(&d3d11Device))) {
+ // Direct3D 11
+ D3D11::device = (ID3D11Device*)pDevice;
+ if (!D3D11::IDXGISwapChain_Present_Original)
+ replaceVtable(
+ *(void**)swapChain,
+ 8,
+ (void**)&D3D11::IDXGISwapChain_Present_Original,
+ D3D11::IDXGISwapChain_Present_Hook
+ );
+ if (!D3D11::IDXGISwapChain_ResizeBuffers_Original)
+ replaceVtable(
+ *(void**)swapChain,
+ 13,
+ (void**)&D3D11::IDXGISwapChain_ResizeBuffers_Original,
+ D3D11::IDXGISwapChain_ResizeBuffers_Hook
+ );
+ // When the graphics API used by RenderDragon is D3D11, this function will be called in the main thread, and
+ // IDXGISwapChain::Present will all be called in a non-main thread, so initialize ImGui here immediately
+ // printf("Initializing ImGui on Direct3D 11\n");
+ imguiInitialized = D3D11::initializeImguiBackend(swapChain);
+ // if (!imguiInitialized) {
+ // printf("Failed to initialize ImGui on Direct3D 11\n");
+ // }
+ } else {
+ // not D3D11 and D3D12, wtf?
+ }
+ }
+ return hResult;
+}
+
+HRESULT (*createDXGIFactory1Original)(REFIID riid, void** ppFactory) = nullptr;
+HRESULT createDXGIFactory1Hook(REFIID riid, void** ppFactory) {
+ HRESULT hResult = createDXGIFactory1Original(riid, ppFactory);
+ if (IsEqualIID(IID_IDXGIFactory2, riid) && SUCCEEDED(hResult)) {
+ // printf("CreateDXGIFactory1 riid=IID_IDXGIFactory2\n");
+ IDXGIFactory2* factory2 = (IDXGIFactory2*)*ppFactory;
+ if (!IDXGIFactory2_CreateSwapChainForCoreWindow_Original) {
+ replaceVtable(
+ *(void**)factory2,
+ 16,
+ (void**)&IDXGIFactory2_CreateSwapChainForCoreWindow_Original,
+ IDXGIFactory2_CreateSwapChainForCoreWindow_Hook
+ );
+ }
+ factory = factory2;
+ }
+ return hResult;
+}
+
+void init() {
+ ll::memory::hook(
+ CreateDXGIFactory1,
+ createDXGIFactory1Hook,
+ (ll::memory::FuncPtr*)&createDXGIFactory1Original,
+ ll::memory::HookPriority::Normal
+ );
+}
+} // namespace ll::gui
diff --git a/src-client/ll/core/gui/win/backends/imgui_impl_winrt.cpp b/src-client/ll/core/gui/win/backends/imgui_impl_winrt.cpp
new file mode 100644
index 0000000000..53499278d3
--- /dev/null
+++ b/src-client/ll/core/gui/win/backends/imgui_impl_winrt.cpp
@@ -0,0 +1,517 @@
+#include "imgui_impl_winrt.h"
+#include "imgui.h"
+
+#include
+#include
+
+#include "CoreWindow.h"
+
+using ABI::Windows::UI::Core::ICharacterReceivedEventArgs;
+using ABI::Windows::UI::Core::ICoreWindow;
+using ABI::Windows::UI::Core::IKeyEventArgs;
+using ABI::Windows::UI::Core::IPointerEventArgs;
+
+using Microsoft::WRL::Callback;
+using Microsoft::WRL::ComPtr;
+using Microsoft::WRL::Wrappers::HStringReference;
+
+static ImGui_ImplWinRT_Data* ImGui_ImplWinRT_GetBackendData() {
+ return ImGui::GetCurrentContext() ? (ImGui_ImplWinRT_Data*)ImGui::GetIO().BackendPlatformUserData : nullptr;
+}
+
+bool ImGui_ImplWinRT_Init(IUnknown* window) {
+ ImGuiIO& io = ImGui::GetIO();
+ IM_ASSERT(io.BackendPlatformUserData == nullptr && "Already initialized a platform backend!");
+
+ ComPtr coreWindow;
+ if (FAILED(window->QueryInterface(coreWindow.GetAddressOf()))) {
+ printf("Query ICoreWindow FAILED\n");
+ return false;
+ }
+
+ ComPtr coreWindowInterop;
+ if (FAILED(window->QueryInterface(coreWindowInterop.GetAddressOf()))) {
+ printf("Query ICoreWindowInterop FAILED\n");
+ return false;
+ }
+
+ HWND hWnd;
+ if (FAILED(coreWindowInterop->get_WindowHandle(&hWnd))) {
+ printf("get_WindowHandle FAILED\n");
+ return false;
+ }
+
+ INT64 perf_frequency, perf_counter;
+ if (!::QueryPerformanceFrequency((LARGE_INTEGER*)&perf_frequency)) return false;
+ if (!::QueryPerformanceCounter((LARGE_INTEGER*)&perf_counter)) return false;
+
+ // Setup backend capabilities flags
+ ImGui_ImplWinRT_Data* bd = IM_NEW(ImGui_ImplWinRT_Data)();
+ io.BackendPlatformUserData = (void*)bd;
+ io.BackendPlatformName = "imgui_impl_winrt";
+
+ bd->InputEventHandler = IM_NEW(ImGuiInputEventHandler)(coreWindow.Get());
+ bd->hWnd = hWnd;
+ bd->TicksPerSecond = perf_frequency;
+ bd->Time = perf_counter;
+
+ // Set platform dependent data in viewport
+ ImGui::GetMainViewport()->PlatformHandleRaw = (void*)hWnd;
+
+ return true;
+}
+
+void ImGui_ImplWinRT_Shutdown() {
+ ImGui_ImplWinRT_Data* bd = ImGui_ImplWinRT_GetBackendData();
+ IM_ASSERT(bd != nullptr && "No platform backend to shutdown, or already shutdown?");
+
+ ImGuiIO& io = ImGui::GetIO();
+ io.BackendPlatformName = nullptr;
+ io.BackendPlatformUserData = nullptr;
+
+ IM_DELETE(bd->InputEventHandler);
+ IM_DELETE(bd);
+}
+
+void ImGui_ImplWinRT_NewFrame() {
+ ImGuiIO& io = ImGui::GetIO();
+ ImGui_ImplWinRT_Data* bd = ImGui_ImplWinRT_GetBackendData();
+ IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplWinRT_Init()?");
+
+ // Setup display size (every frame to accommodate for window resizing)
+ RECT rect = {0, 0, 0, 0};
+ ::GetClientRect(bd->hWnd, &rect);
+ io.DisplaySize = ImVec2((float)(rect.right - rect.left), (float)(rect.bottom - rect.top));
+
+ // Setup time step
+ INT64 current_time = 0;
+ ::QueryPerformanceCounter((LARGE_INTEGER*)¤t_time);
+ io.DeltaTime = (float)(current_time - bd->Time) / bd->TicksPerSecond;
+ bd->Time = current_time;
+}
+
+// Map ABI::Windows::System::VirtualKey to ImGuiKey.
+static ImGuiKey ImGui_ImplWinRT_VirtualKeyToImGuiKey(ABI::Windows::System::VirtualKey vk) {
+ using ABI::Windows::System::VirtualKey;
+ switch (vk) {
+ case VirtualKey::VirtualKey_Tab:
+ return ImGuiKey_Tab;
+ case VirtualKey::VirtualKey_Left:
+ return ImGuiKey_LeftArrow;
+ case VirtualKey::VirtualKey_Right:
+ return ImGuiKey_RightArrow;
+ case VirtualKey::VirtualKey_Up:
+ return ImGuiKey_UpArrow;
+ case VirtualKey::VirtualKey_Down:
+ return ImGuiKey_DownArrow;
+ case VirtualKey::VirtualKey_PageUp:
+ return ImGuiKey_PageUp;
+ case VirtualKey::VirtualKey_PageDown:
+ return ImGuiKey_PageDown;
+ case VirtualKey::VirtualKey_Home:
+ return ImGuiKey_Home;
+ case VirtualKey::VirtualKey_End:
+ return ImGuiKey_End;
+ case VirtualKey::VirtualKey_Insert:
+ return ImGuiKey_Insert;
+ case VirtualKey::VirtualKey_Delete:
+ return ImGuiKey_Delete;
+ case VirtualKey::VirtualKey_Back:
+ return ImGuiKey_Backspace;
+ case VirtualKey::VirtualKey_Space:
+ return ImGuiKey_Space;
+ case VirtualKey::VirtualKey_Enter:
+ return ImGuiKey_Enter;
+ case VirtualKey::VirtualKey_Escape:
+ return ImGuiKey_Escape;
+ // case VK_OEM_7: return ImGuiKey_Apostrophe;
+ // case VK_OEM_COMMA: return ImGuiKey_Comma;
+ // case VK_OEM_MINUS: return ImGuiKey_Minus;
+ // case VK_OEM_PERIOD: return ImGuiKey_Period;
+ // case VK_OEM_2: return ImGuiKey_Slash;
+ // case VK_OEM_1: return ImGuiKey_Semicolon;
+ // case VK_OEM_PLUS: return ImGuiKey_Equal;
+ // case VK_OEM_4: return ImGuiKey_LeftBracket;
+ // case VK_OEM_5: return ImGuiKey_Backslash;
+ // case VK_OEM_6: return ImGuiKey_RightBracket;
+ // case VK_OEM_3: return ImGuiKey_GraveAccent;
+ case VirtualKey::VirtualKey_CapitalLock:
+ return ImGuiKey_CapsLock;
+ case VirtualKey::VirtualKey_Scroll:
+ return ImGuiKey_ScrollLock;
+ case VirtualKey::VirtualKey_NumberKeyLock:
+ return ImGuiKey_NumLock;
+ case VirtualKey::VirtualKey_Snapshot:
+ return ImGuiKey_PrintScreen;
+ case VirtualKey::VirtualKey_Pause:
+ return ImGuiKey_Pause;
+ case VirtualKey::VirtualKey_NumberPad0:
+ return ImGuiKey_Keypad0;
+ case VirtualKey::VirtualKey_NumberPad1:
+ return ImGuiKey_Keypad1;
+ case VirtualKey::VirtualKey_NumberPad2:
+ return ImGuiKey_Keypad2;
+ case VirtualKey::VirtualKey_NumberPad3:
+ return ImGuiKey_Keypad3;
+ case VirtualKey::VirtualKey_NumberPad4:
+ return ImGuiKey_Keypad4;
+ case VirtualKey::VirtualKey_NumberPad5:
+ return ImGuiKey_Keypad5;
+ case VirtualKey::VirtualKey_NumberPad6:
+ return ImGuiKey_Keypad6;
+ case VirtualKey::VirtualKey_NumberPad7:
+ return ImGuiKey_Keypad7;
+ case VirtualKey::VirtualKey_NumberPad8:
+ return ImGuiKey_Keypad8;
+ case VirtualKey::VirtualKey_NumberPad9:
+ return ImGuiKey_Keypad9;
+ case VirtualKey::VirtualKey_Decimal:
+ return ImGuiKey_KeypadDecimal;
+ case VirtualKey::VirtualKey_Divide:
+ return ImGuiKey_KeypadDivide;
+ case VirtualKey::VirtualKey_Multiply:
+ return ImGuiKey_KeypadMultiply;
+ case VirtualKey::VirtualKey_Subtract:
+ return ImGuiKey_KeypadSubtract;
+ case VirtualKey::VirtualKey_Add:
+ return ImGuiKey_KeypadAdd;
+ case VirtualKey::VirtualKey_LeftShift:
+ return ImGuiKey_LeftShift;
+ case VirtualKey::VirtualKey_LeftControl:
+ return ImGuiKey_LeftCtrl;
+ // case VK_LMENU: return ImGuiKey_LeftAlt;
+ // case VK_LWIN: return ImGuiKey_LeftSuper;
+ case VirtualKey::VirtualKey_RightShift:
+ return ImGuiKey_RightShift;
+ case VirtualKey::VirtualKey_RightControl:
+ return ImGuiKey_RightCtrl;
+ // case VK_RMENU: return ImGuiKey_RightAlt;
+ // case VK_RWIN: return ImGuiKey_RightSuper;
+ case VirtualKey::VirtualKey_Control:
+ return ImGuiKey_LeftCtrl;
+ case VirtualKey::VirtualKey_Shift:
+ return ImGuiKey_LeftShift;
+ case VirtualKey::VirtualKey_Application:
+ return ImGuiKey_Menu;
+ case VirtualKey::VirtualKey_Number0:
+ return ImGuiKey_0;
+ case VirtualKey::VirtualKey_Number1:
+ return ImGuiKey_1;
+ case VirtualKey::VirtualKey_Number2:
+ return ImGuiKey_2;
+ case VirtualKey::VirtualKey_Number3:
+ return ImGuiKey_3;
+ case VirtualKey::VirtualKey_Number4:
+ return ImGuiKey_4;
+ case VirtualKey::VirtualKey_Number5:
+ return ImGuiKey_5;
+ case VirtualKey::VirtualKey_Number6:
+ return ImGuiKey_6;
+ case VirtualKey::VirtualKey_Number7:
+ return ImGuiKey_7;
+ case VirtualKey::VirtualKey_Number8:
+ return ImGuiKey_8;
+ case VirtualKey::VirtualKey_Number9:
+ return ImGuiKey_9;
+ case VirtualKey::VirtualKey_A:
+ return ImGuiKey_A;
+ case VirtualKey::VirtualKey_B:
+ return ImGuiKey_B;
+ case VirtualKey::VirtualKey_C:
+ return ImGuiKey_C;
+ case VirtualKey::VirtualKey_D:
+ return ImGuiKey_D;
+ case VirtualKey::VirtualKey_E:
+ return ImGuiKey_E;
+ case VirtualKey::VirtualKey_F:
+ return ImGuiKey_F;
+ case VirtualKey::VirtualKey_G:
+ return ImGuiKey_G;
+ case VirtualKey::VirtualKey_H:
+ return ImGuiKey_H;
+ case VirtualKey::VirtualKey_I:
+ return ImGuiKey_I;
+ case VirtualKey::VirtualKey_J:
+ return ImGuiKey_J;
+ case VirtualKey::VirtualKey_K:
+ return ImGuiKey_K;
+ case VirtualKey::VirtualKey_L:
+ return ImGuiKey_L;
+ case VirtualKey::VirtualKey_M:
+ return ImGuiKey_M;
+ case VirtualKey::VirtualKey_N:
+ return ImGuiKey_N;
+ case VirtualKey::VirtualKey_O:
+ return ImGuiKey_O;
+ case VirtualKey::VirtualKey_P:
+ return ImGuiKey_P;
+ case VirtualKey::VirtualKey_Q:
+ return ImGuiKey_Q;
+ case VirtualKey::VirtualKey_R:
+ return ImGuiKey_R;
+ case VirtualKey::VirtualKey_S:
+ return ImGuiKey_S;
+ case VirtualKey::VirtualKey_T:
+ return ImGuiKey_T;
+ case VirtualKey::VirtualKey_U:
+ return ImGuiKey_U;
+ case VirtualKey::VirtualKey_V:
+ return ImGuiKey_V;
+ case VirtualKey::VirtualKey_W:
+ return ImGuiKey_W;
+ case VirtualKey::VirtualKey_X:
+ return ImGuiKey_X;
+ case VirtualKey::VirtualKey_Y:
+ return ImGuiKey_Y;
+ case VirtualKey::VirtualKey_Z:
+ return ImGuiKey_Z;
+ case VirtualKey::VirtualKey_F1:
+ return ImGuiKey_F1;
+ case VirtualKey::VirtualKey_F2:
+ return ImGuiKey_F2;
+ case VirtualKey::VirtualKey_F3:
+ return ImGuiKey_F3;
+ case VirtualKey::VirtualKey_F4:
+ return ImGuiKey_F4;
+ case VirtualKey::VirtualKey_F5:
+ return ImGuiKey_F5;
+ case VirtualKey::VirtualKey_F6:
+ return ImGuiKey_F6;
+ case VirtualKey::VirtualKey_F7:
+ return ImGuiKey_F7;
+ case VirtualKey::VirtualKey_F8:
+ return ImGuiKey_F8;
+ case VirtualKey::VirtualKey_F9:
+ return ImGuiKey_F9;
+ case VirtualKey::VirtualKey_F10:
+ return ImGuiKey_F10;
+ case VirtualKey::VirtualKey_F11:
+ return ImGuiKey_F11;
+ case VirtualKey::VirtualKey_F12:
+ return ImGuiKey_F12;
+ default:
+ return ImGuiKey_None;
+ }
+}
+
+// Converts a length in device-independent pixels (DIPs) to a length in physical pixels.
+static float ConvertDipsToPixels(float dips, float dpi) {
+ static const float dipsPerInch = 96.0f;
+ return floorf(dips * dpi / dipsPerInch + 0.5f); // Round to nearest integer.
+}
+
+ImGuiInputEventHandler::ImGuiInputEventHandler(ICoreWindow* window) : window(window) {
+ using PointerEventHandler = ABI::Windows::Foundation::
+ ITypedEventHandler;
+ using KeyEventHandler = ABI::Windows::Foundation::
+ ITypedEventHandler;
+ using CharacterReceivedEventHandler = ABI::Windows::Foundation::
+ ITypedEventHandler;
+
+ this->window->add_PointerMoved(
+ Callback(this, &ImGuiInputEventHandler::onPointerMoved).Get(),
+ &pointerMovedToken
+ );
+ this->window->add_PointerExited(
+ Callback(this, &ImGuiInputEventHandler::onPointerExited).Get(),
+ &pointerExitedToken
+ );
+ this->window->add_PointerPressed(
+ Callback(this, &ImGuiInputEventHandler::onPointerPressed).Get(),
+ &pointerPressedToken
+ );
+ this->window->add_PointerReleased(
+ Callback(this, &ImGuiInputEventHandler::onPointerReleased).Get(),
+ &pointerReleasedToken
+ );
+ this->window->add_PointerWheelChanged(
+ Callback(this, &ImGuiInputEventHandler::onPointerWheelChanged).Get(),
+ &pointerWheelChangedToken
+ );
+ this->window->add_KeyDown(Callback(this, &ImGuiInputEventHandler::onKeyDown).Get(), &keyDownToken);
+ this->window->add_KeyUp(Callback(this, &ImGuiInputEventHandler::onKeyUp).Get(), &keyUpToken);
+ this->window->add_CharacterReceived(
+ Callback(this, &ImGuiInputEventHandler::onCharacterReceived).Get(),
+ &characterReceivedToken
+ );
+
+ dpi = 96;
+ ComPtr displayInfoStatics;
+ if (FAILED(RoGetActivationFactory(
+ HStringReference(RuntimeClass_Windows_Graphics_Display_DisplayInformation).Get(),
+ IID_PPV_ARGS(&displayInfoStatics)
+ ))) {
+ printf("Failed to get IDisplayInformationStatics\n");
+ return;
+ }
+ if (FAILED(displayInfoStatics->GetForCurrentView(&displayInfo))) {
+ printf("Failed to get IDisplayInformation\n");
+ return;
+ }
+ if (FAILED(displayInfo->get_LogicalDpi(&dpi))) {
+ printf("Failed to get DPI\n");
+ }
+ // printf("DPI=%f\n", dpi);
+ ImGui_ImplWinRT_Data* bd = ImGui_ImplWinRT_GetBackendData();
+ if (bd != nullptr) {
+ bd->DPI = dpi;
+ bd->DPIChanged = true;
+ }
+ displayInfo->add_DpiChanged(
+ Callback<__FITypedEventHandler_2_Windows__CGraphics__CDisplay__CDisplayInformation_IInspectable>(
+ this,
+ &ImGuiInputEventHandler::onDpiChanged
+ )
+ .Get(),
+ &dpiChangedToken
+ );
+}
+
+ImGuiInputEventHandler::~ImGuiInputEventHandler() {
+ this->window->remove_PointerMoved(pointerMovedToken);
+ this->window->remove_PointerExited(pointerExitedToken);
+ this->window->remove_PointerPressed(pointerPressedToken);
+ this->window->remove_PointerReleased(pointerReleasedToken);
+ this->window->remove_PointerWheelChanged(pointerWheelChangedToken);
+ this->window->remove_KeyDown(keyDownToken);
+ this->window->remove_KeyUp(keyUpToken);
+ this->window->remove_CharacterReceived(characterReceivedToken);
+
+ if (this->displayInfo) {
+ this->displayInfo->remove_DpiChanged(dpiChangedToken);
+ }
+}
+
+void ImGuiInputEventHandler::updateMouseButtonState(IPointerEventArgs* args) {
+ auto& io = ImGui::GetIO();
+ ComPtr currentPoint;
+ args->get_CurrentPoint(¤tPoint);
+
+ ComPtr properties;
+ currentPoint->get_Properties(&properties);
+
+ ABI::Windows::Foundation::Point pos;
+ currentPoint->get_Position(&pos);
+
+ io.AddMouseSourceEvent(ImGuiMouseSource_Mouse);
+ io.AddMousePosEvent(ConvertDipsToPixels(pos.X, dpi), ConvertDipsToPixels(pos.Y, dpi));
+
+ using ABI::Windows::UI::Input::PointerUpdateKind;
+ PointerUpdateKind kind;
+ properties->get_PointerUpdateKind(&kind);
+ switch (kind) {
+ case PointerUpdateKind::PointerUpdateKind_LeftButtonPressed:
+ io.AddMouseButtonEvent(0, true);
+ break;
+ case PointerUpdateKind::PointerUpdateKind_LeftButtonReleased:
+ io.AddMouseButtonEvent(0, false);
+ break;
+ case PointerUpdateKind::PointerUpdateKind_RightButtonPressed:
+ io.AddMouseButtonEvent(1, true);
+ break;
+ case PointerUpdateKind::PointerUpdateKind_RightButtonReleased:
+ io.AddMouseButtonEvent(1, false);
+ break;
+ case PointerUpdateKind::PointerUpdateKind_MiddleButtonPressed:
+ io.AddMouseButtonEvent(2, true);
+ break;
+ case PointerUpdateKind::PointerUpdateKind_MiddleButtonReleased:
+ io.AddMouseButtonEvent(2, false);
+ break;
+ }
+}
+
+HRESULT ImGuiInputEventHandler::onPointerMoved(ICoreWindow* /*sender*/, IPointerEventArgs* args) {
+ auto& io = ImGui::GetIO();
+ ComPtr currentPoint;
+ args->get_CurrentPoint(¤tPoint);
+
+ ABI::Windows::Foundation::Point pos;
+ currentPoint->get_Position(&pos);
+
+ io.AddMouseSourceEvent(ImGuiMouseSource_Mouse);
+ io.AddMousePosEvent(ConvertDipsToPixels(pos.X, dpi), ConvertDipsToPixels(pos.Y, dpi));
+ return S_OK;
+}
+
+HRESULT ImGuiInputEventHandler::onPointerExited(ICoreWindow* /*sender*/, IPointerEventArgs* /*args*/) {
+ auto& io = ImGui::GetIO();
+ io.AddMousePosEvent(-FLT_MAX, -FLT_MAX);
+ return S_OK;
+}
+
+HRESULT ImGuiInputEventHandler::onPointerPressed(ICoreWindow* /*sender*/, IPointerEventArgs* args) {
+ updateMouseButtonState(args);
+ return S_OK;
+}
+
+HRESULT ImGuiInputEventHandler::onPointerReleased(ICoreWindow* /*sender*/, IPointerEventArgs* args) {
+ updateMouseButtonState(args);
+ return S_OK;
+}
+
+HRESULT ImGuiInputEventHandler::onPointerWheelChanged(ICoreWindow* /*sender*/, IPointerEventArgs* args) {
+ auto& io = ImGui::GetIO();
+
+ ComPtr currentPoint;
+ args->get_CurrentPoint(¤tPoint);
+
+ ComPtr properties;
+ currentPoint->get_Properties(&properties);
+
+ INT32 mouseWheelDelta;
+ properties->get_MouseWheelDelta(&mouseWheelDelta);
+
+ io.MouseWheel += mouseWheelDelta > 0 ? +1 : -1;
+
+ return S_OK;
+}
+
+HRESULT ImGuiInputEventHandler::onKeyDown(ICoreWindow* /*sender*/, IKeyEventArgs* args) {
+ auto& io = ImGui::GetIO();
+
+ ABI::Windows::System::VirtualKey vk;
+ args->get_VirtualKey(&vk);
+
+ io.AddKeyEvent(ImGui_ImplWinRT_VirtualKeyToImGuiKey(vk), true);
+
+ return S_OK;
+}
+
+HRESULT ImGuiInputEventHandler::onKeyUp(ICoreWindow* /*sender*/, IKeyEventArgs* args) {
+ auto& io = ImGui::GetIO();
+
+ ABI::Windows::System::VirtualKey vk;
+ args->get_VirtualKey(&vk);
+
+ io.AddKeyEvent(ImGui_ImplWinRT_VirtualKeyToImGuiKey(vk), false);
+
+ return S_OK;
+}
+
+HRESULT ImGuiInputEventHandler::onCharacterReceived(ICoreWindow* /*sender*/, ICharacterReceivedEventArgs* args) {
+ auto& io = ImGui::GetIO();
+
+ UINT32 c;
+ args->get_KeyCode(&c);
+
+ io.AddInputCharacter(c);
+
+ return S_OK;
+}
+
+HRESULT ImGuiInputEventHandler::
+ onDpiChanged(ABI::Windows::Graphics::Display::IDisplayInformation* sender, IInspectable* /*args*/) {
+ // printf("DPI changed\n");
+ if (FAILED(sender->get_LogicalDpi(&dpi))) {
+ printf("Failed to get DPI\n");
+ dpi = 96;
+ }
+ // printf("DPI=%f\n", dpi);
+ ImGui_ImplWinRT_Data* bd = ImGui_ImplWinRT_GetBackendData();
+ if (bd != nullptr) {
+ bd->DPI = dpi;
+ bd->DPIChanged = true;
+ }
+ return S_OK;
+}
diff --git a/src-client/ll/core/gui/win/backends/imgui_impl_winrt.h b/src-client/ll/core/gui/win/backends/imgui_impl_winrt.h
new file mode 100644
index 0000000000..bc360fb5bb
--- /dev/null
+++ b/src-client/ll/core/gui/win/backends/imgui_impl_winrt.h
@@ -0,0 +1,66 @@
+#pragma once
+#include "windows.h"
+
+#pragma warning(push)
+#pragma warning(disable : 4265)
+#include "Windows.Graphics.Display.h"
+#include "Windows.UI.Core.h"
+#include "wrl.h"
+#pragma warning(pop)
+
+#include "EventToken.h"
+#include "inspectable.h"
+
+#include "imgui.h"
+
+class ImGuiInputEventHandler {
+ using ICoreWindow = ABI::Windows::UI::Core::ICoreWindow;
+ using IDisplayInformation = ABI::Windows::Graphics::Display::IDisplayInformation;
+ using IKeyEventArgs = ABI::Windows::UI::Core::IKeyEventArgs;
+ using IPointerEventArgs = ABI::Windows::UI::Core::IPointerEventArgs;
+ using ICharacterReceivedEventArgs = ABI::Windows::UI::Core::ICharacterReceivedEventArgs;
+
+public:
+ ImGuiInputEventHandler(ICoreWindow* window);
+ ~ImGuiInputEventHandler();
+
+private:
+ Microsoft::WRL::ComPtr window;
+ Microsoft::WRL::ComPtr displayInfo;
+ EventRegistrationToken pointerMovedToken;
+ EventRegistrationToken pointerExitedToken;
+ EventRegistrationToken pointerPressedToken;
+ EventRegistrationToken pointerReleasedToken;
+ EventRegistrationToken pointerWheelChangedToken;
+ EventRegistrationToken keyDownToken;
+ EventRegistrationToken keyUpToken;
+ EventRegistrationToken characterReceivedToken;
+ EventRegistrationToken dpiChangedToken;
+ float dpi;
+
+ void updateMouseButtonState(IPointerEventArgs* args);
+ HRESULT onPointerMoved(ICoreWindow* sender, IPointerEventArgs* args);
+ HRESULT onPointerExited(ICoreWindow* sender, IPointerEventArgs* args);
+ HRESULT onPointerPressed(ICoreWindow* sender, IPointerEventArgs* args);
+ HRESULT onPointerReleased(ICoreWindow* sender, IPointerEventArgs* args);
+ HRESULT onPointerWheelChanged(ICoreWindow* sender, IPointerEventArgs* args);
+ HRESULT onKeyDown(ICoreWindow* sender, IKeyEventArgs* args);
+ HRESULT onKeyUp(ICoreWindow* sender, IKeyEventArgs* args);
+ HRESULT onCharacterReceived(ICoreWindow* sender, ICharacterReceivedEventArgs* args);
+ HRESULT onDpiChanged(IDisplayInformation* sender, IInspectable* args);
+};
+
+struct ImGui_ImplWinRT_Data {
+ ImGuiInputEventHandler* InputEventHandler;
+ HWND hWnd;
+ INT64 Time;
+ INT64 TicksPerSecond;
+ float DPI;
+ bool DPIChanged;
+
+ ImGui_ImplWinRT_Data() { memset((void*)this, 0, sizeof(*this)); }
+};
+
+bool ImGui_ImplWinRT_Init(IUnknown* CoreWindow);
+void ImGui_ImplWinRT_Shutdown();
+void ImGui_ImplWinRT_NewFrame();
diff --git a/src-client/ll/core/io/LogPipe.cpp b/src-client/ll/core/io/LogPipe.cpp
new file mode 100644
index 0000000000..c1595e4735
--- /dev/null
+++ b/src-client/ll/core/io/LogPipe.cpp
@@ -0,0 +1,8 @@
+#include "ll/core/io/LogPipe.h"
+
+namespace ll::io {
+ll::io::Pipe& getDefaultLogPipe() {
+ static ll::io::Pipe defaultLogPipe;
+ return defaultLogPipe;
+}
+} // namespace ll::io
diff --git a/src-client/ll/core/io/LogPipe.h b/src-client/ll/core/io/LogPipe.h
new file mode 100644
index 0000000000..88bfdbd7d4
--- /dev/null
+++ b/src-client/ll/core/io/LogPipe.h
@@ -0,0 +1,7 @@
+#pragma once
+
+#include "ll/api/io/Pipe.h"
+
+namespace ll::io {
+ll::io::Pipe& getDefaultLogPipe();
+} // namespace ll::io
diff --git a/src-client/ll/core/io/Output.cpp b/src-client/ll/core/io/Output.cpp
new file mode 100644
index 0000000000..3351e6a2d2
--- /dev/null
+++ b/src-client/ll/core/io/Output.cpp
@@ -0,0 +1,6 @@
+#include "ll/core/io/Output.h"
+#include "ll/core/io/LogPipe.h"
+
+namespace ll::io {
+void defaultOutput(std::string_view sv) { getDefaultLogPipe().write(sv); }
+} // namespace ll::io
diff --git a/src-client/ll/core/main_win.cpp b/src-client/ll/core/main_win.cpp
new file mode 100644
index 0000000000..bb1451c9ac
--- /dev/null
+++ b/src-client/ll/core/main_win.cpp
@@ -0,0 +1,133 @@
+#include "ll/core/LeviLamina.h"
+
+#include "ll/api/Versions.h"
+#include "ll/api/command/CommandRegistrar.h"
+#include "ll/api/event/EventBus.h"
+#include "ll/api/i18n/I18n.h"
+#include "ll/api/memory/Hook.h"
+#include "ll/api/service/GamingStatus.h"
+#include "ll/api/utils/ErrorUtils.h"
+
+#include "ll/core/Config.h"
+#include "ll/core/CrashLogger.h"
+#include "ll/core/command/BuiltinCommands.h"
+#include "ll/core/gui/ImGuiHooks.h"
+#include "ll/core/mod/ModRegistrar.h"
+
+#include "mc/server/ServerInstance.h"
+#include "mc/world/events/ServerInstanceEventCoordinator.h"
+#include "mc/world/level/Level.h"
+
+#include "mc/deps/core/string/StringHash.h"
+#include "mc/gui/screens/controllers/StartMenuScreenController.h"
+
+#include "windows.h"
+
+namespace ll {
+void leviLaminaMain() {
+ error_utils::initExceptionTranslator();
+
+ gui::init();
+
+ if (auto res = i18n::getInstance().load(getSelfModIns()->getLangDir()); !res) {
+ getLogger().error("i18n load failed");
+ res.error().log(getLogger());
+ }
+
+ auto& config = getLeviConfig();
+
+ if (config.language != "system") {
+ i18n::defaultLocaleCode() = config.language;
+ }
+ CrashLogger::init();
+
+ printWelcomeMsg();
+
+ command::registerCommands();
+
+ mod::ModRegistrar::getInstance().loadAllMods();
+}
+
+namespace service::bedrock {
+extern std::atomic serverInstance;
+}
+
+LL_AUTO_TYPE_INSTANCE_HOOK(
+ EnableAllModsHook,
+ HookPriority::High,
+ ServerInstanceEventCoordinator,
+ &ServerInstanceEventCoordinator::sendServerInitializeEnd,
+ void,
+ ::ServerInstance& ins
+) {
+ getLogger().debug("sendServerInitializeEnd");
+
+ origin(ins);
+ service::bedrock::serverInstance = std::addressof(ins);
+
+ mod::ModRegistrar::getInstance().enableAllMods();
+
+ setGamingStatus(GamingStatus::Running);
+}
+LL_AUTO_TYPE_INSTANCE_HOOK(
+ DisableAllModsHook,
+ HookPriority::Low,
+ ServerInstance,
+ &ServerInstance::startLeaveGame,
+ void
+) {
+ getLogger().debug("ServerInstance::startLeaveGame");
+
+ mod::ModRegistrar::getInstance().disableAllMods();
+
+ command::CommandRegistrar::getInstance().clear();
+
+ service::bedrock::serverInstance = nullptr;
+
+ origin();
+}
+
+LL_AUTO_TYPE_INSTANCE_HOOK(
+ PatchVersionBinding,
+ HookPriority::Normal,
+ StartMenuScreenController,
+ &StartMenuScreenController::_registerBindings,
+ void
+) {
+ bindString(
+ StringHash("#version"),
+ []() -> auto {
+ auto gameVer = getGameVersion();
+ auto loaderVer = getLoaderVersion();
+ return fmt::format(
+ "v{}.{}.{}/LL-{}.{}.{}",
+ gameVer.major,
+ gameVer.minor,
+ gameVer.patch,
+ loaderVer.major,
+ loaderVer.minor,
+ loaderVer.patch
+ );
+ },
+ []() -> auto { return true; }
+ );
+ origin();
+}
+} // namespace ll
+
+BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD ulReasonForCall, LPVOID /*lpReserved*/) {
+ using namespace ll;
+ if (ulReasonForCall == DLL_PROCESS_DETACH) {
+ mod::ModRegistrar::getInstance().releaseAllMods();
+ }
+ if (ulReasonForCall != DLL_PROCESS_ATTACH) return TRUE;
+
+ setGamingStatus(GamingStatus::Default);
+ try {
+ leviLaminaMain();
+ } catch (...) {
+ error_utils::printCurrentException(getLogger());
+ }
+ setGamingStatus(GamingStatus::Starting);
+ return TRUE;
+}
diff --git a/src-client/ll/core/tweak/ModifyBedrockLogInfo.cpp b/src-client/ll/core/tweak/ModifyBedrockLogInfo.cpp
new file mode 100644
index 0000000000..30ee47043a
--- /dev/null
+++ b/src-client/ll/core/tweak/ModifyBedrockLogInfo.cpp
@@ -0,0 +1,3 @@
+#include "ll/core/tweak/ModifyInfomation.h"
+
+bool ll::tryModifyBedrockLogInfo(uint32_t, std::string&) { return true; }
diff --git a/src-client/mc/gui/controls/UIPropertyBag.h b/src-client/mc/gui/controls/UIPropertyBag.h
new file mode 100644
index 0000000000..83891f8f1c
--- /dev/null
+++ b/src-client/mc/gui/controls/UIPropertyBag.h
@@ -0,0 +1,52 @@
+#pragma once
+
+#include "mc/_HeaderOutputPredefine.h"
+#include "mc/deps/core/utility/PropertyBag.h"
+
+// auto generated forward declare list
+// clang-format off
+namespace Json { class Value; }
+// clang-format on
+
+class UIPropertyBag : public PropertyBag {
+public:
+ // UIPropertyBag inner types declare
+ // clang-format off
+ struct ContextObject;
+ struct PropertyChangedNotificationInfo;
+ // clang-format on
+
+ // UIPropertyBag inner types define
+ struct ContextObject {
+ public:
+ // prevent constructor by default
+ ContextObject& operator=(ContextObject const&);
+ ContextObject(ContextObject const&);
+ ContextObject();
+ };
+
+ struct PropertyChangedNotificationInfo {
+ public:
+ // prevent constructor by default
+ PropertyChangedNotificationInfo& operator=(PropertyChangedNotificationInfo const&);
+ PropertyChangedNotificationInfo(PropertyChangedNotificationInfo const&);
+ PropertyChangedNotificationInfo();
+
+ public:
+ // NOLINTBEGIN
+ MCAPI ~PropertyChangedNotificationInfo();
+
+ // NOLINTEND
+ };
+
+public:
+ // prevent constructor by default
+ UIPropertyBag& operator=(UIPropertyBag const&);
+ UIPropertyBag(UIPropertyBag const&);
+ UIPropertyBag();
+
+public:
+ // NOLINTBEGIN
+
+ // NOLINTEND
+};
diff --git a/src-client/mc/gui/screens/ScreenController.h b/src-client/mc/gui/screens/ScreenController.h
new file mode 100644
index 0000000000..13e6a06e46
--- /dev/null
+++ b/src-client/mc/gui/screens/ScreenController.h
@@ -0,0 +1,40 @@
+#pragma once
+
+#include "mc/_HeaderOutputPredefine.h"
+#include "mc/gui/controls/UIPropertyBag.h"
+
+// auto generated forward declare list
+// clang-format off
+namespace Json { class Value; }
+namespace mce { class Color; }
+// clang-format on
+
+class ScreenController {
+public:
+ char pad_0x0000[2512];
+ std::unordered_map> mBindCallbacks;
+ std::unordered_map> mCollectionBindCallbacks;
+ std::unordered_map>
+ mAnyCollectionBindCallbacks;
+
+public:
+ // ScreenController inner types define
+ enum class PreviousButtonStateRequirement {};
+
+public:
+ // prevent constructor by default
+ ScreenController& operator=(ScreenController const&);
+ ScreenController(ScreenController const&);
+ ScreenController();
+
+public:
+ // NOLINTBEGIN
+
+ MCAPI void bindString(
+ class StringHash const& bindingName,
+ class std::function const& callback,
+ class std::function const& condition
+ );
+
+ // NOLINTEND
+};
diff --git a/src-client/mc/gui/screens/controllers/MainMenuScreenController.h b/src-client/mc/gui/screens/controllers/MainMenuScreenController.h
new file mode 100644
index 0000000000..407318ffee
--- /dev/null
+++ b/src-client/mc/gui/screens/controllers/MainMenuScreenController.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include "mc/_HeaderOutputPredefine.h"
+#include "mc/gui/screens/controllers/MinecraftScreenController.h"
+
+
+class MainMenuScreenController : public MinecraftScreenController {
+public:
+ // prevent constructor by default
+ MainMenuScreenController& operator=(MainMenuScreenController const&);
+ MainMenuScreenController(MainMenuScreenController const&);
+ MainMenuScreenController();
+
+public:
+ // NOLINTBEGIN
+
+ // NOLINTEND
+};
diff --git a/src-client/mc/gui/screens/controllers/MinecraftScreenController.h b/src-client/mc/gui/screens/controllers/MinecraftScreenController.h
new file mode 100644
index 0000000000..4c29be65fe
--- /dev/null
+++ b/src-client/mc/gui/screens/controllers/MinecraftScreenController.h
@@ -0,0 +1,19 @@
+#pragma once
+
+#include "mc/_HeaderOutputPredefine.h"
+#include "mc/gui/screens/ScreenController.h"
+
+
+class MinecraftScreenController : public ScreenController,
+ public std::enable_shared_from_this {
+public:
+ // prevent constructor by default
+ MinecraftScreenController& operator=(MinecraftScreenController const&);
+ MinecraftScreenController(MinecraftScreenController const&);
+ MinecraftScreenController();
+
+public:
+ // NOLINTBEGIN
+
+ // NOLINTEND
+};
diff --git a/src-client/mc/gui/screens/controllers/StartMenuScreenController.h b/src-client/mc/gui/screens/controllers/StartMenuScreenController.h
new file mode 100644
index 0000000000..a33e402552
--- /dev/null
+++ b/src-client/mc/gui/screens/controllers/StartMenuScreenController.h
@@ -0,0 +1,46 @@
+#pragma once
+
+#include "mc/_HeaderOutputPredefine.h"
+#include "mc/client/gui/GameEventNotification.h"
+#include "mc/gui/screens/controllers/MainMenuScreenController.h"
+
+// auto generated forward declare list
+// clang-format off
+namespace Json { class Value; }
+namespace Social { class User; }
+// clang-format on
+
+class StartMenuScreenController : public ::MainMenuScreenController {
+public:
+ // StartMenuScreenController inner types declare
+ // clang-format off
+ class SignOutObserver;
+ // clang-format on
+
+ // StartMenuScreenController inner types define
+ class SignOutObserver {
+ public:
+ // prevent constructor by default
+ SignOutObserver& operator=(SignOutObserver const&);
+ SignOutObserver(SignOutObserver const&);
+ SignOutObserver();
+
+ public:
+ // NOLINTBEGIN
+
+ // NOLINTEND
+ };
+
+public:
+ // prevent constructor by default
+ StartMenuScreenController& operator=(StartMenuScreenController const&);
+ StartMenuScreenController(StartMenuScreenController const&);
+ StartMenuScreenController();
+
+public:
+ // NOLINTBEGIN
+
+ MCAPI void _registerBindings();
+
+ // NOLINTEND
+};
diff --git a/src-client/mc/util/VarIntDataInput.cpp b/src-client/mc/util/VarIntDataInput.cpp
new file mode 100644
index 0000000000..51c6d448d6
--- /dev/null
+++ b/src-client/mc/util/VarIntDataInput.cpp
@@ -0,0 +1,3 @@
+#include "mc/util/VarIntDataInput.h"
+
+Bedrock::Result VarIntDataInput::readLongStringResult() { return readStringResult(); }
diff --git a/src-client/mc/util/VarIntDataOutput.cpp b/src-client/mc/util/VarIntDataOutput.cpp
new file mode 100644
index 0000000000..f89cf948f1
--- /dev/null
+++ b/src-client/mc/util/VarIntDataOutput.cpp
@@ -0,0 +1,3 @@
+#include "mc/util/VarIntDataOutput.h"
+
+void VarIntDataOutput::writeLongString(std::string_view v) { writeString(v); }
diff --git a/src-client/mc/world/actor/Actor.cpp b/src-client/mc/world/actor/Actor.cpp
new file mode 100644
index 0000000000..12f4f81959
--- /dev/null
+++ b/src-client/mc/world/actor/Actor.cpp
@@ -0,0 +1,32 @@
+#include "mc/world/actor/Actor.h"
+
+#include
+
+#include "mc/_HeaderOutputPredefine.h"
+#include "mc/deps/core/math/Vec3.h"
+#include "mc/deps/ecs/gamerefs_entity/EntityContext.h"
+#include "mc/entity/systems/OnFireSystem.h"
+#include "mc/server/ServerLevel.h"
+#include "mc/server/commands/CommandUtils.h"
+#include "mc/server/commands/standard/TeleportCommand.h"
+#include "mc/world//actor/player/Player.h"
+#include "mc/world/actor/ActorDamageByActorSource.h"
+#include "mc/world/actor/provider/ActorCollision.h"
+#include "mc/world/level/BlockPos.h"
+#include "mc/world/level/BlockSource.h"
+#include "mc/world/level/dimension/Dimension.h"
+#include "mc/world/phys/HitDetection.h"
+#include "mc/world/phys/HitResult.h"
+
+class EntityContext& Actor::getEntityContext() { return mEntityContext.get(); }
+class EntityContext const& Actor::getEntityContext() const { return mEntityContext.get(); }
+
+void Actor::refresh() { _sendDirtyActorData(); }
+
+void Actor::teleport(class Vec3 const& pos, DimensionType dimId) {
+ TeleportCommand::applyTarget(
+ *this,
+ TeleportCommand::computeTarget(*this, pos, nullptr, dimId, std::nullopt, 1),
+ false
+ );
+}
diff --git a/src-client/mc/world/item/item.cpp b/src-client/mc/world/item/item.cpp
new file mode 100644
index 0000000000..4d4d862bfa
--- /dev/null
+++ b/src-client/mc/world/item/item.cpp
@@ -0,0 +1,22 @@
+#include "mc/world/item/Item.h"
+#include "mc/world/item/ItemStackBase.h"
+
+
+std::string ItemStackBase::getTypeName() const {
+ if (auto item = getItem(); item) {
+ return item->getSerializedName();
+ }
+ return {};
+}
+
+std::string ItemStackBase::getDescriptionName() const {
+ if (auto item = getItem(); item) {
+ return item->buildDescriptionName(*this);
+ }
+ return {};
+}
+
+LLAPI short ItemStackBase::getDamageValue() const {
+ if (mItem && mUserData) return mItem->getDamageValue(mUserData.get());
+ return 0;
+}
diff --git a/src-server/ll/api/Versions.cpp b/src-server/ll/api/Versions.cpp
new file mode 100644
index 0000000000..9896e36913
--- /dev/null
+++ b/src-server/ll/api/Versions.cpp
@@ -0,0 +1,22 @@
+#include "ll/api/Versions.h"
+#include "mc/common/BuildInfo.h"
+#include "mc/common/Common.h"
+#include "mc/common/SharedConstants.h"
+
+namespace ll {
+data::Version getGameVersion() {
+ static auto ver = [] {
+ auto info = Common::getBuildInfo();
+ auto v = data::Version{info.mBuildId};
+ v.preRelease = data::PreRelease{};
+ v.preRelease->values.emplace_back((uint16_t)SharedConstants::RevisionVersion());
+ v.preRelease->values.emplace_back((uint16_t)SharedConstants::NetworkProtocolVersion());
+ v.build = info.mCommitId.substr(0, std::min(info.mCommitId.size(), (size_t)7));
+ return v;
+ }();
+ return ver;
+}
+
+int getNetworkProtocolVersion() { return SharedConstants::NetworkProtocolVersion(); }
+
+} // namespace ll
diff --git a/src/ll/api/event/player/PlayerChangePermEvent.cpp b/src-server/ll/api/event/player/PlayerChangePermEvent.cpp
similarity index 85%
rename from src/ll/api/event/player/PlayerChangePermEvent.cpp
rename to src-server/ll/api/event/player/PlayerChangePermEvent.cpp
index a2c8e7fa39..16754380e1 100644
--- a/src/ll/api/event/player/PlayerChangePermEvent.cpp
+++ b/src-server/ll/api/event/player/PlayerChangePermEvent.cpp
@@ -13,13 +13,11 @@ void PlayerChangePermEvent::serialize(CompoundTag& nbt) const {
void PlayerChangePermEvent::deserialize(CompoundTag const& nbt) {
Cancellable::deserialize(nbt);
-#if _HAS_CXX23
magic_enum::enum_cast((std::string_view)nbt["permission"])
.transform([this](CommandPermissionLevel val) {
newPerm() = val;
return true;
});
-#endif
}
CommandPermissionLevel& PlayerChangePermEvent::newPerm() const { return mMewPerm; }
@@ -40,13 +38,11 @@ LL_TYPE_INSTANCE_HOOK(
origin(perm);
}
-static std::unique_ptr emitterFactory(ListenerBase&);
+static std::unique_ptr emitterFactory();
class PlayerChangePermEventEmitter : public Emitter {
memory::HookRegistrar hook;
};
-static std::unique_ptr